Summary: `device.realDevice` was the escape hatch used in Sandy plugins to give access to device specific features like taking screenshots, clearing logs or accessing `adb`. Since in decapitated Flipper that won't be possible anymore (since plugins run in the client but device implementations on the server), all escape hatches have been bridged in this stack, and we can get of the `realDevice` interaction, by explicitly exposing those cases, which makes it type safe as well. Reviewed By: passy Differential Revision: D31079509 fbshipit-source-id: c9ec2e044d0dec0ccb1de287cf424907b198f818
190 lines
5.3 KiB
TypeScript
190 lines
5.3 KiB
TypeScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @format
|
|
* @flow strict-local
|
|
*/
|
|
|
|
import {bufferToBlob} from 'flipper';
|
|
import {RequiredParametersDialog} from './components';
|
|
import {
|
|
removeBookmarkFromDB,
|
|
readBookmarksFromDB,
|
|
writeBookmarkToDB,
|
|
} from './util/indexedDB';
|
|
import {} from './util/autoCompleteProvider';
|
|
import {getAppMatchPatterns} from './util/appMatchPatterns';
|
|
import {getRequiredParameters, filterOptionalParameters} from './util/uri';
|
|
import {
|
|
Bookmark,
|
|
NavigationEvent,
|
|
AppMatchPattern,
|
|
URI,
|
|
RawNavigationEvent,
|
|
} from './types';
|
|
import React from 'react';
|
|
import {PluginClient, createState, renderReactRoot} from 'flipper-plugin';
|
|
|
|
export type State = {
|
|
shouldShowSaveBookmarkDialog: boolean;
|
|
shouldShowURIErrorDialog: boolean;
|
|
saveBookmarkURI: URI | null;
|
|
requiredParameters: Array<string>;
|
|
};
|
|
|
|
type Events = {
|
|
nav_event: RawNavigationEvent;
|
|
};
|
|
|
|
type Methods = {
|
|
navigate_to(params: {url: string}): Promise<void>;
|
|
};
|
|
|
|
export type NavigationPlugin = ReturnType<typeof plugin>;
|
|
|
|
export function plugin(client: PluginClient<Events, Methods>) {
|
|
const bookmarks = createState(new Map<URI, Bookmark>(), {
|
|
persist: 'bookmarks',
|
|
});
|
|
const navigationEvents = createState<NavigationEvent[]>([], {
|
|
persist: 'navigationEvents',
|
|
});
|
|
const appMatchPatterns = createState<AppMatchPattern[]>([], {
|
|
persist: 'appMatchPatterns',
|
|
});
|
|
const currentURI = createState('');
|
|
const shouldShowSaveBookmarkDialog = createState(false);
|
|
const saveBookmarkURI = createState<null | string>(null);
|
|
|
|
client.onMessage('nav_event', async (payload) => {
|
|
const navigationEvent: NavigationEvent = {
|
|
uri: payload.uri === undefined ? null : decodeURIComponent(payload.uri),
|
|
date: payload.date ? new Date(payload.date) : new Date(),
|
|
className: payload.class === undefined ? null : payload.class,
|
|
screenshot: null,
|
|
};
|
|
|
|
if (navigationEvent.uri) currentURI.set(navigationEvent.uri);
|
|
|
|
navigationEvents.update((draft) => {
|
|
draft.unshift(navigationEvent);
|
|
});
|
|
|
|
const screenshot: Buffer = await client.device.screenshot();
|
|
const blobURL = URL.createObjectURL(bufferToBlob(screenshot));
|
|
// this process is async, make sure we update the correct one..
|
|
const navigationEventIndex = navigationEvents
|
|
.get()
|
|
.indexOf(navigationEvent);
|
|
if (navigationEventIndex !== -1) {
|
|
navigationEvents.update((draft) => {
|
|
draft[navigationEventIndex].screenshot = blobURL;
|
|
});
|
|
}
|
|
});
|
|
|
|
getAppMatchPatterns(client.appId, client.device)
|
|
.then((patterns) => {
|
|
appMatchPatterns.set(patterns);
|
|
})
|
|
.catch((e) => {
|
|
console.error('[Navigation] Failed to find appMatchPatterns', e);
|
|
});
|
|
|
|
readBookmarksFromDB()
|
|
.then((bookmarksData) => {
|
|
bookmarks.set(bookmarksData);
|
|
})
|
|
.catch((e) => console.error('[navigation] readBookmarksFromDB failed:', e));
|
|
|
|
function navigateTo(query: string) {
|
|
const filteredQuery = filterOptionalParameters(query);
|
|
currentURI.set(filteredQuery);
|
|
const params = getRequiredParameters(filteredQuery);
|
|
if (params.length === 0) {
|
|
if (client.appName === 'Facebook' && client.device.os === 'iOS') {
|
|
// use custom navigate_to event for Wilde
|
|
client.send('navigate_to', {
|
|
url: filterOptionalParameters(filteredQuery),
|
|
});
|
|
} else {
|
|
client.device.navigateToLocation(
|
|
filterOptionalParameters(filteredQuery),
|
|
);
|
|
}
|
|
} else {
|
|
renderReactRoot((unmount) => (
|
|
<RequiredParametersDialog
|
|
onHide={unmount}
|
|
uri={filteredQuery}
|
|
requiredParameters={params}
|
|
onSubmit={navigateTo}
|
|
/>
|
|
));
|
|
}
|
|
}
|
|
|
|
function onFavorite(uri: string) {
|
|
shouldShowSaveBookmarkDialog.set(true);
|
|
saveBookmarkURI.set(uri);
|
|
}
|
|
|
|
function addBookmark(bookmark: Bookmark) {
|
|
const newBookmark = {
|
|
uri: bookmark.uri,
|
|
commonName: bookmark.commonName,
|
|
};
|
|
|
|
bookmarks.update((draft) => {
|
|
draft.set(newBookmark.uri, newBookmark);
|
|
});
|
|
writeBookmarkToDB(newBookmark);
|
|
}
|
|
|
|
function removeBookmark(uri: string) {
|
|
bookmarks.update((draft) => {
|
|
draft.delete(uri);
|
|
});
|
|
removeBookmarkFromDB(uri);
|
|
}
|
|
|
|
return {
|
|
navigateTo,
|
|
onFavorite,
|
|
addBookmark,
|
|
removeBookmark,
|
|
bookmarks,
|
|
saveBookmarkURI,
|
|
shouldShowSaveBookmarkDialog,
|
|
appMatchPatterns,
|
|
navigationEvents,
|
|
currentURI,
|
|
getAutoCompleteAppMatchPatterns(
|
|
query: string,
|
|
bookmarks: Map<string, Bookmark>,
|
|
appMatchPatterns: AppMatchPattern[],
|
|
limit: number,
|
|
): AppMatchPattern[] {
|
|
const q = query.toLowerCase();
|
|
const results: AppMatchPattern[] = [];
|
|
for (const item of appMatchPatterns) {
|
|
if (
|
|
!bookmarks.has(item.pattern) &&
|
|
(item.className.toLowerCase().includes(q) ||
|
|
item.pattern.toLowerCase().includes(q))
|
|
) {
|
|
results.push(item);
|
|
if (--limit < 1) break;
|
|
}
|
|
}
|
|
return results;
|
|
},
|
|
};
|
|
}
|
|
|
|
/* @scarf-info: do not remove, more info: https://fburl.com/scarf */
|
|
/* @scarf-generated: flipper-plugin index.js.template 0bfa32e5-fb15-4705-81f8-86260a1f3f8e */
|