/** * 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; }; type Events = { nav_event: RawNavigationEvent; }; type Methods = { navigate_to(params: {url: string}): Promise; }; export type NavigationPlugin = ReturnType; export function plugin(client: PluginClient) { const bookmarks = createState(new Map(), { persist: 'bookmarks', }); const navigationEvents = createState([], { persist: 'navigationEvents', }); const appMatchPatterns = createState([], { persist: 'appMatchPatterns', }); const currentURI = createState(''); const shouldShowSaveBookmarkDialog = createState(false); const saveBookmarkURI = createState(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.realDevice.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.realDevice) .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.realDevice.navigateToLocation( filterOptionalParameters(filteredQuery), ); } } else { renderReactRoot((unmount) => ( )); } } 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, 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 */