diff --git a/desktop/flipper-frontend-core/src/RenderHost.tsx b/desktop/flipper-frontend-core/src/RenderHost.tsx index 27a4a0577..1662b40dd 100644 --- a/desktop/flipper-frontend-core/src/RenderHost.tsx +++ b/desktop/flipper-frontend-core/src/RenderHost.tsx @@ -67,7 +67,7 @@ interface NotificationConstructorOptions { // Events that are emitted from the main.ts ovr the IPC process bridge in Electron type MainProcessEvents = { 'flipper-protocol-handler': [query: string]; - 'open-flipper-file': [url: string]; + 'open-flipper-file': [name: string, data: string]; notificationEvent: [ eventName: NotificationEvents, pluginNotification: PluginNotification, @@ -88,7 +88,7 @@ type ChildProcessEvents = { }, ]; getLaunchTime: []; - componentDidMount: []; + storeRehydrated: []; }; /** diff --git a/desktop/flipper-ui-browser/src/index.tsx b/desktop/flipper-ui-browser/src/index.tsx index 06b79d61b..f836700b5 100644 --- a/desktop/flipper-ui-browser/src/index.tsx +++ b/desktop/flipper-ui-browser/src/index.tsx @@ -71,6 +71,7 @@ async function start() { const flipperServerConfig = await flipperServer.exec('get-config'); initializeRenderHost(flipperServer, flipperServerConfig); + initializePWA(); // By turning this in a require, we force the JS that the body of this module (init) has completed (initializeElectron), // before starting the rest of the Flipper process. @@ -78,6 +79,7 @@ async function start() { // but not set yet, which might happen when using normal imports. // TODO: remove window.flipperShowError?.('Connected to Flipper Server successfully'); + // @ts-ignore // eslint-disable-next-line import/no-commonjs require('flipper-ui-core').startFlipperDesktop(flipperServer); @@ -89,6 +91,76 @@ start().catch((e) => { window.flipperShowError?.('Failed to start flipper-ui-browser: ' + e); }); +async function initializePWA() { + console.log('[PWA] Initialization'); + + let cachedFile: {name: string; data: string} | undefined; + let rehydrated = false; + const openFileIfAny = () => { + if (!cachedFile || !rehydrated) { + return; + } + window.dispatchEvent( + new CustomEvent('open-flipper-file', { + detail: [cachedFile.name, cachedFile.data], + }), + ); + cachedFile = undefined; + }; + + if ('serviceWorker' in navigator) { + navigator.serviceWorker + .register('/service-worker.js') + .then(() => { + console.log('[PWA] Service Worker has been registered'); + }) + .catch((e) => { + console.error('[PWA] failed to register Service Worker', e); + }); + } + + if ('launchQueue' in window) { + console.log('[PWA] File Handling API is supported'); + + // @ts-ignore + window.launchQueue.setConsumer(async (launchParams) => { + if (!launchParams || !launchParams.files) { + return; + } + console.log('[PWA] Attempt to to open a file'); + for (const file of launchParams.files) { + const blob = await file.getFile(); + blob.handle = file; + + const data = await blob.text(); + const name = file.name; + + cachedFile = {name, data}; + + openFileIfAny(); + } + }); + } else { + console.warn('[PWA] File Handling API is not supported'); + } + + console.log('[PWA] Add before install prompt listener'); + window.addEventListener('beforeinstallprompt', (e) => { + // Prevent Chrome 67 and earlier from automatically showing the prompt. + e.preventDefault(); + // Stash the event so it can be triggered later. + // @ts-ignore + global.PWAppInstallationEvent = e; + console.log('[PWA] Installation event has been captured'); + }); + + window.addEventListener('storeRehydrated', () => { + console.info('[PWA] Store is rehydrated'); + rehydrated = true; + openFileIfAny(); + }); +} + // getLogger() is not yet created when the electron app starts. // we can't create it here yet, as the real logger is wired up to // the redux store and the rest of the world. So we create a delegating logger diff --git a/desktop/flipper-ui-browser/src/initializeRenderHost.tsx b/desktop/flipper-ui-browser/src/initializeRenderHost.tsx index 97d0c580c..4decaff5f 100644 --- a/desktop/flipper-ui-browser/src/initializeRenderHost.tsx +++ b/desktop/flipper-ui-browser/src/initializeRenderHost.tsx @@ -153,11 +153,13 @@ export function initializeRenderHost( hasFocus() { return document.hasFocus(); }, - onIpcEvent(_event) { - // no-op + onIpcEvent(event, cb) { + window.addEventListener(event as string, (ev) => { + cb(...((ev as CustomEvent).detail as any)); + }); }, - sendIpcEvent(_event, ..._args: any[]) { - // no-op + sendIpcEvent(event, ...args: any[]) { + window.dispatchEvent(new CustomEvent(event, {detail: args})); }, shouldUseDarkColors() { return !!( diff --git a/desktop/flipper-ui-core/src/dispatcher/application.tsx b/desktop/flipper-ui-core/src/dispatcher/application.tsx index 903443ca2..936b23e61 100644 --- a/desktop/flipper-ui-core/src/dispatcher/application.tsx +++ b/desktop/flipper-ui-core/src/dispatcher/application.tsx @@ -10,7 +10,7 @@ import {Store} from '../reducers/index'; import {Logger} from 'flipper-common'; import { - importFileToStore, + importDataToStore, IMPORT_FLIPPER_TRACE_EVENT, } from '../utils/exportData'; import {tryCatchReportPlatformFailures} from 'flipper-common'; @@ -67,9 +67,9 @@ export default (store: Store, logger: Logger) => { }); }); - renderHost.onIpcEvent('open-flipper-file', (url: string) => { + renderHost.onIpcEvent('open-flipper-file', (name: string, data: string) => { tryCatchReportPlatformFailures(() => { - return importFileToStore(url, store); + return importDataToStore(name, data, store); }, `${IMPORT_FLIPPER_TRACE_EVENT}:Deeplink`); }); }; diff --git a/desktop/flipper-ui-core/src/dispatcher/index.tsx b/desktop/flipper-ui-core/src/dispatcher/index.tsx index 1e9090b38..ebd445f2f 100644 --- a/desktop/flipper-ui-core/src/dispatcher/index.tsx +++ b/desktop/flipper-ui-core/src/dispatcher/index.tsx @@ -26,7 +26,10 @@ import {Store} from '../reducers/index'; import {Dispatcher} from './types'; import {notNull} from '../utils/typeUtils'; -export default function (store: Store, logger: Logger): () => Promise { +export default async function ( + store: Store, + logger: Logger, +): Promise<() => Promise> { // This only runs in development as when the reload // kicks in it doesn't unregister the shortcuts const dispatchers: Array = [ @@ -43,10 +46,11 @@ export default function (store: Store, logger: Logger): () => Promise { pluginChangeListener, pluginsSourceUpdateListener, ].filter(notNull); - const globalCleanup = dispatchers - .map((dispatcher) => dispatcher(store, logger)) - .filter(Boolean); - return () => { - return Promise.all(globalCleanup).then(() => {}); + const globalCleanup = await Promise.all( + dispatchers.map((dispatcher) => dispatcher(store, logger)).filter(Boolean), + ); + + return async () => { + await Promise.all(globalCleanup); }; } diff --git a/desktop/flipper-ui-core/src/sandy-chrome/SandyApp.tsx b/desktop/flipper-ui-core/src/sandy-chrome/SandyApp.tsx index 543f738d6..dfdb33568 100644 --- a/desktop/flipper-ui-core/src/sandy-chrome/SandyApp.tsx +++ b/desktop/flipper-ui-core/src/sandy-chrome/SandyApp.tsx @@ -196,5 +196,4 @@ function registerStartupTime(logger: Logger) { }); renderHost.sendIpcEvent('getLaunchTime'); - renderHost.sendIpcEvent('componentDidMount'); } diff --git a/desktop/flipper-ui-core/src/startFlipperDesktop.tsx b/desktop/flipper-ui-core/src/startFlipperDesktop.tsx index 2e4a9edc3..b19f9b5fa 100644 --- a/desktop/flipper-ui-core/src/startFlipperDesktop.tsx +++ b/desktop/flipper-ui-core/src/startFlipperDesktop.tsx @@ -149,9 +149,10 @@ function init(flipperServer: FlipperServer) { loadTheme(settings.darkMode); // rehydrate app state before exposing init - const persistor = persistStore(store, undefined, () => { + const persistor = persistStore(store, undefined, async () => { // Make sure process state is set before dispatchers run - dispatcher(store, logger); + await dispatcher(store, logger); + getRenderHostInstance().sendIpcEvent('storeRehydrated'); }); setPersistor(persistor); @@ -168,17 +169,17 @@ function init(flipperServer: FlipperServer) { connectFlipperServerToStore(flipperServer, store, logger); + enableConsoleHook(); + enableConnectivityHook(flipperServer); + // TODO T116224873: Return the following code back instead of ReactDOM.react when the following issue is fixed: https://github.com/react-component/trigger/issues/288 // const root = createRoot(document.getElementById('root')!); // root.render(); - ReactDOM.render( - , - document.getElementById('root')!, - ); - - enableConsoleHook(); - enableConnectivityHook(flipperServer); + const root = document.getElementById('root'); + if (root) { + ReactDOM.render(, root); + } const launcherMessage = getRenderHostInstance().serverConfig.processConfig.launcherMsg; @@ -193,8 +194,8 @@ function init(flipperServer: FlipperServer) { } } -export async function startFlipperDesktop(flipperServer: FlipperServer) { - getRenderHostInstance(); // renderHost instance should be set at this point! +export function startFlipperDesktop(flipperServer: FlipperServer) { + getRenderHostInstance(); init(flipperServer); } diff --git a/desktop/flipper-ui-core/src/utils/exportData.tsx b/desktop/flipper-ui-core/src/utils/exportData.tsx index 2bcfb4356..f2de950a4 100644 --- a/desktop/flipper-ui-core/src/utils/exportData.tsx +++ b/desktop/flipper-ui-core/src/utils/exportData.tsx @@ -30,7 +30,7 @@ import {TestIdler} from './Idler'; import {processMessageQueue} from './messageQueue'; import {getPluginTitle} from './pluginUtils'; import {capture} from './screenshot'; -import {Dialog, getFlipperLib, Idler, path} from 'flipper-plugin'; +import {Dialog, getFlipperLib, Idler} from 'flipper-plugin'; import {ClientQuery} from 'flipper-common'; import ShareSheetExportUrl from '../chrome/ShareSheetExportUrl'; import ShareSheetExportFile from '../chrome/ShareSheetExportFile'; @@ -43,6 +43,7 @@ import {safeFilename} from './safeFilename'; import {getExportablePlugins} from '../selectors/connections'; import {notification} from 'antd'; import openSupportRequestForm from '../fb-stubs/openSupportRequestForm'; +import {getStore} from '../store'; export const IMPORT_FLIPPER_TRACE_EVENT = 'import-flipper-trace'; export const EXPORT_FLIPPER_TRACE_EVENT = 'export-flipper-trace'; @@ -526,7 +527,7 @@ export const exportStoreToFile = ( export async function importDataToStore( source: string, data: string, - store: Store, + store: Store = getStore(), ) { getLogger().track('usage', IMPORT_FLIPPER_TRACE_EVENT); const json: ExportType = JSON.parse(data); diff --git a/desktop/static/index.web.dev.html b/desktop/static/index.web.dev.html index 64f8808e5..8d27031d5 100644 --- a/desktop/static/index.web.dev.html +++ b/desktop/static/index.web.dev.html @@ -138,25 +138,6 @@ document.body.appendChild(script); } - if ('serviceWorker' in navigator) { - navigator.serviceWorker - .register('/service-worker.js') - .then(() => { - console.log('Flipper Service Worker has been registered'); - }) - .catch((e) => { - console.error('Flipper failed to register Service Worker', e); - }); - } - - window.addEventListener('beforeinstallprompt', (e) => { - console.log('Flipper PWA before install prompt with event', e); - // Prevent Chrome 67 and earlier from automatically showing the prompt. - e.preventDefault(); - // Stash the event so it can be triggered later. - global.PWAppInstallationEvent = e; - }); - init(); })(); diff --git a/desktop/static/index.web.html b/desktop/static/index.web.html index 9095cb6d1..3a1e68d9f 100644 --- a/desktop/static/index.web.html +++ b/desktop/static/index.web.html @@ -59,7 +59,7 @@ -