Open file import
Summary: This change only adds the PWA as capable of handling files with the ".flipper" extension. Reviewed By: aigoncharov Differential Revision: D48353437 fbshipit-source-id: fd78942ac4dffb7d26d5ca5be826290018465b93
This commit is contained in:
committed by
Facebook GitHub Bot
parent
9728155cbf
commit
ce13ee426f
@@ -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: [];
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 !!(
|
||||
|
||||
@@ -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`);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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<void> {
|
||||
export default async function (
|
||||
store: Store,
|
||||
logger: Logger,
|
||||
): Promise<() => Promise<void>> {
|
||||
// This only runs in development as when the reload
|
||||
// kicks in it doesn't unregister the shortcuts
|
||||
const dispatchers: Array<Dispatcher> = [
|
||||
@@ -43,10 +46,11 @@ export default function (store: Store, logger: Logger): () => Promise<void> {
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -196,5 +196,4 @@ function registerStartupTime(logger: Logger) {
|
||||
});
|
||||
|
||||
renderHost.sendIpcEvent('getLaunchTime');
|
||||
renderHost.sendIpcEvent('componentDidMount');
|
||||
}
|
||||
|
||||
@@ -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(<AppFrame logger={logger} persistor={persistor} />);
|
||||
|
||||
ReactDOM.render(
|
||||
<AppFrame logger={logger} persistor={persistor} />,
|
||||
document.getElementById('root')!,
|
||||
);
|
||||
|
||||
enableConsoleHook();
|
||||
enableConnectivityHook(flipperServer);
|
||||
const root = document.getElementById('root');
|
||||
if (root) {
|
||||
ReactDOM.render(<AppFrame logger={logger} persistor={persistor} />, 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="__infinity-dev-box __infinity-dev-box-error" hidden />
|
||||
<div class="__infinity-dev-box __infinity-dev-box-error" hidden></div>
|
||||
<script>
|
||||
(function () {
|
||||
// FIXME: needed to make Metro work
|
||||
@@ -114,25 +114,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) => {
|
||||
// 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();
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -63,14 +63,6 @@ if (process.platform === 'darwin') {
|
||||
const argv = yargs
|
||||
.usage('$0 [args]')
|
||||
.options({
|
||||
file: {
|
||||
describe: 'Define a file to open on startup.',
|
||||
type: 'string',
|
||||
},
|
||||
url: {
|
||||
describe: 'Define a flipper:// URL to open on startup.',
|
||||
type: 'string',
|
||||
},
|
||||
updater: {
|
||||
default: true,
|
||||
describe: 'Toggle the built-in update mechanism.',
|
||||
@@ -114,9 +106,8 @@ if (argv['disable-gpu'] || process.env.FLIPPER_DISABLE_GPU === '1') {
|
||||
// possible reference to main app window
|
||||
let win: BrowserWindow;
|
||||
let appReady = false;
|
||||
let deeplinkURL: string | undefined = argv.url;
|
||||
let filePath: string | undefined = argv.file;
|
||||
let didMount = false;
|
||||
let deeplinkURL: string | undefined;
|
||||
let filePath: string | undefined;
|
||||
|
||||
// check if we already have an instance of this app open
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
@@ -138,6 +129,24 @@ if (!gotTheLock) {
|
||||
app.on('ready', () => {});
|
||||
}
|
||||
|
||||
const openFileIfAny = () => {
|
||||
if (!filePath || !win) {
|
||||
return;
|
||||
}
|
||||
fs.readFile(filePath, {encoding: 'utf-8'}, (_err, data) => {
|
||||
win.webContents.send('open-flipper-file', filePath, data);
|
||||
filePath = undefined;
|
||||
});
|
||||
};
|
||||
|
||||
const openURLIfAny = () => {
|
||||
if (!deeplinkURL || !win) {
|
||||
return;
|
||||
}
|
||||
win.webContents.send('flipper-protocol-handler', deeplinkURL);
|
||||
deeplinkURL = undefined;
|
||||
};
|
||||
|
||||
// quit app once all windows are closed
|
||||
app.on('window-all-closed', () => {
|
||||
appReady = false;
|
||||
@@ -148,22 +157,16 @@ app.on('will-finish-launching', () => {
|
||||
// Protocol handler for osx
|
||||
app.on('open-url', function (event, url) {
|
||||
event.preventDefault();
|
||||
argv.url = url;
|
||||
if (win && didMount) {
|
||||
win.webContents.send('flipper-protocol-handler', url);
|
||||
} else {
|
||||
deeplinkURL = url;
|
||||
}
|
||||
openURLIfAny();
|
||||
});
|
||||
app.on('open-file', (event, path) => {
|
||||
// When flipper app is running, and someone double clicks the import file, `componentDidMount` will not be called again and windows object will exist in that case. That's why calling `win.webContents.send('open-flipper-file', filePath);` again.
|
||||
// When flipper app is running, and someone double clicks the import file,
|
||||
// component and store is already mounted and the windows object will exist.
|
||||
// In that case, the file can be immediately opened.
|
||||
event.preventDefault();
|
||||
filePath = path;
|
||||
argv.file = path;
|
||||
if (win) {
|
||||
win.webContents.send('open-flipper-file', filePath);
|
||||
filePath = undefined;
|
||||
}
|
||||
openFileIfAny();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -256,17 +259,9 @@ function configureSession() {
|
||||
);
|
||||
}
|
||||
|
||||
ipcMain.on('componentDidMount', (_event) => {
|
||||
didMount = true;
|
||||
if (deeplinkURL) {
|
||||
win.webContents.send('flipper-protocol-handler', deeplinkURL);
|
||||
deeplinkURL = undefined;
|
||||
}
|
||||
if (filePath) {
|
||||
// When flipper app is not running, the windows object might not exist in the callback of `open-file`, but after ``componentDidMount` it will definitely exist.
|
||||
win.webContents.send('open-flipper-file', filePath);
|
||||
filePath = undefined;
|
||||
}
|
||||
ipcMain.on('storeRehydrated', (_event) => {
|
||||
openFileIfAny();
|
||||
openURLIfAny();
|
||||
});
|
||||
|
||||
ipcMain.on('getLaunchTime', (event) => {
|
||||
|
||||
@@ -14,5 +14,15 @@
|
||||
"type": "image/png",
|
||||
"sizes": "256x256"
|
||||
}
|
||||
],
|
||||
"file_handlers": [
|
||||
{
|
||||
"action": "/",
|
||||
"accept": {
|
||||
"text/*": [
|
||||
".flipper"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -52,8 +52,7 @@ self.addEventListener('fetch', (event) => {
|
||||
}
|
||||
|
||||
// Always try the network first (try flipper server)
|
||||
const networkResponse = await fetch(event.request);
|
||||
return networkResponse;
|
||||
return await fetch(event.request);
|
||||
} catch (error) {
|
||||
// Catch is only triggered if an exception is thrown, which is likely
|
||||
// due to a network error.
|
||||
@@ -62,8 +61,7 @@ self.addEventListener('fetch', (event) => {
|
||||
console.log('Fetch failed; returning offline page instead.', error);
|
||||
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
const cachedResponse = await cache.match(OFFLINE_URL);
|
||||
return cachedResponse;
|
||||
return await cache.match(OFFLINE_URL);
|
||||
}
|
||||
})());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user