diff --git a/desktop/app/src/RenderHost.tsx b/desktop/app/src/RenderHost.tsx
index 663edca4a..c503fe3bd 100644
--- a/desktop/app/src/RenderHost.tsx
+++ b/desktop/app/src/RenderHost.tsx
@@ -7,10 +7,20 @@
* @format
*/
-import {NotificationEvents} from './dispatcher/notifications';
-import {PluginNotification} from './reducers/notifications';
+import type {NotificationEvents} from './dispatcher/notifications';
+import type {PluginNotification} from './reducers/notifications';
import type {NotificationConstructorOptions} from 'electron';
-import {FlipperLib} from 'flipper-plugin';
+import type {FlipperLib} from 'flipper-plugin';
+import path from 'path';
+
+type ENVIRONMENT_VARIABLES = 'NODE_ENV' | 'DEV_SERVER_URL' | 'CONFIG';
+type ENVIRONMENT_PATHS =
+ | 'appPath'
+ | 'homePath'
+ | 'execPath'
+ | 'staticPath'
+ | 'tempPath'
+ | 'desktopPath';
// Events that are emitted from the main.ts ovr the IPC process bridge in Electron
type MainProcessEvents = {
@@ -45,6 +55,7 @@ type ChildProcessEvents = {
export interface RenderHost {
readonly processId: number;
readTextFromClipboard(): string | undefined;
+ writeTextToClipboard(text: string): void;
showSaveDialog?: FlipperLib['showSaveDialog'];
showOpenDialog?: FlipperLib['showOpenDialog'];
showSelectDirectoryDialog?(defaultPath?: string): Promise;
@@ -60,6 +71,9 @@ export interface RenderHost {
): void;
shouldUseDarkColors(): boolean;
restartFlipper(update?: boolean): void;
+ env: Partial>;
+ paths: Record;
+ openLink(url: string): void;
}
let renderHostInstance: RenderHost | undefined;
@@ -81,6 +95,7 @@ if (process.env.NODE_ENV === 'test') {
readTextFromClipboard() {
return '';
},
+ writeTextToClipboard() {},
registerShortcut() {},
hasFocus() {
return true;
@@ -91,5 +106,15 @@ if (process.env.NODE_ENV === 'test') {
return false;
},
restartFlipper() {},
+ openLink() {},
+ env: process.env,
+ paths: {
+ appPath: process.cwd(),
+ homePath: `/dev/null`,
+ desktopPath: `/dev/null`,
+ execPath: process.cwd(),
+ staticPath: path.join(process.cwd(), 'static'),
+ tempPath: `/tmp/`,
+ },
});
}
diff --git a/desktop/app/src/chrome/ScreenCaptureButtons.tsx b/desktop/app/src/chrome/ScreenCaptureButtons.tsx
index bf1b70279..3ba3c4333 100644
--- a/desktop/app/src/chrome/ScreenCaptureButtons.tsx
+++ b/desktop/app/src/chrome/ScreenCaptureButtons.tsx
@@ -12,7 +12,7 @@ import React, {useState, useEffect, useCallback} from 'react';
import path from 'path';
import fs from 'fs-extra';
import open from 'open';
-import {capture, CAPTURE_LOCATION, getFileName} from '../utils/screenshot';
+import {capture, getCaptureLocation, getFileName} from '../utils/screenshot';
import {CameraOutlined, VideoCameraOutlined} from '@ant-design/icons';
import {useStore} from '../utils/useStore';
@@ -83,7 +83,7 @@ export default function ScreenCaptureButtons() {
}
if (!isRecording) {
setIsRecording(true);
- const videoPath = path.join(CAPTURE_LOCATION, getFileName('mp4'));
+ const videoPath = path.join(getCaptureLocation(), getFileName('mp4'));
return selectedDevice.startScreenCapture(videoPath).catch((e) => {
console.error('Failed to start recording', e);
message.error('Failed to start recording' + e);
diff --git a/desktop/app/src/dispatcher/flipperServer.tsx b/desktop/app/src/dispatcher/flipperServer.tsx
index 9fd4c0a3d..688ce5869 100644
--- a/desktop/app/src/dispatcher/flipperServer.tsx
+++ b/desktop/app/src/dispatcher/flipperServer.tsx
@@ -18,8 +18,9 @@ import BaseDevice from '../devices/BaseDevice';
import {ClientDescription, timeout} from 'flipper-common';
import {reportPlatformFailures} from 'flipper-common';
import {sideEffect} from '../utils/sideEffect';
-import {getAppTempPath, getStaticPath} from '../utils/pathUtils';
+import {getStaticPath} from '../utils/pathUtils';
import constants from '../fb-stubs/constants';
+import {getRenderHostInstance} from '../RenderHost';
export default async (store: Store, logger: Logger) => {
const {enableAndroid, androidHome, idbPath, enableIOS, enablePhysicalIOS} =
@@ -33,7 +34,7 @@ export default async (store: Store, logger: Logger) => {
enableIOS,
enablePhysicalIOS,
staticPath: getStaticPath(),
- tmpPath: getAppTempPath(),
+ tmpPath: getRenderHostInstance().paths.tempPath,
validWebSocketOrigins: constants.VALID_WEB_SOCKET_REQUEST_ORIGIN_PREFIXES,
},
logger,
diff --git a/desktop/app/src/electron/initializeElectron.tsx b/desktop/app/src/electron/initializeElectron.tsx
index c91dbfdfc..6ac7b2708 100644
--- a/desktop/app/src/electron/initializeElectron.tsx
+++ b/desktop/app/src/electron/initializeElectron.tsx
@@ -15,17 +15,27 @@ import {
_LoggerContext,
} from 'flipper-plugin';
// eslint-disable-next-line flipper/no-electron-remote-imports
-import {ipcRenderer, remote, SaveDialogReturnValue} from 'electron';
-import {setRenderHostInstance} from '../RenderHost';
-import {clipboard} from 'electron';
-import restart from './restartFlipper';
+import {
+ ipcRenderer,
+ remote,
+ SaveDialogReturnValue,
+ clipboard,
+ shell,
+} from 'electron';
+import {getRenderHostInstance, setRenderHostInstance} from '../RenderHost';
+import isProduction from '../utils/isProduction';
+import fs from 'fs';
export function initializeElectron() {
+ const app = remote.app;
setRenderHostInstance({
processId: remote.process.pid,
readTextFromClipboard() {
return clipboard.readText();
},
+ writeTextToClipboard(text: string) {
+ clipboard.writeText(text);
+ },
async showSaveDialog(options) {
return (await remote.dialog.showSaveDialog(options))?.filePath;
},
@@ -56,6 +66,9 @@ export function initializeElectron() {
return undefined;
});
},
+ openLink(url: string) {
+ shell.openExternal(url);
+ },
registerShortcut(shortcut, callback) {
remote.globalShortcut.register(shortcut, callback);
},
@@ -76,5 +89,50 @@ export function initializeElectron() {
restartFlipper() {
restart();
},
+ env: process.env,
+ paths: {
+ appPath: app.getAppPath(),
+ homePath: app.getPath('home'),
+ execPath: process.execPath || remote.process.execPath,
+ staticPath: getStaticDir(),
+ tempPath: app.getPath('temp'),
+ desktopPath: app.getPath('desktop'),
+ },
});
}
+
+function getStaticDir() {
+ let _staticPath = path.resolve(__dirname, '..', '..', '..', 'static');
+ if (fs.existsSync(_staticPath)) {
+ return _staticPath;
+ }
+ if (remote && fs.existsSync(remote.app.getAppPath())) {
+ _staticPath = path.join(remote.app.getAppPath());
+ }
+ if (!fs.existsSync(_staticPath)) {
+ throw new Error('Static path does not exist: ' + _staticPath);
+ }
+ return _staticPath;
+}
+
+function restart(update: boolean = false) {
+ if (isProduction()) {
+ if (update) {
+ const options = {
+ args: process.argv
+ .splice(0, 1)
+ .filter((arg) => arg !== '--no-launcher' && arg !== '--no-updater'),
+ };
+ remote.app.relaunch(options);
+ } else {
+ remote.app.relaunch();
+ }
+ remote.app.exit();
+ } else {
+ // Relaunching the process with the standard way doesn't work in dev mode.
+ // So instead we're sending a signal to dev server to kill the current instance of electron and launch new.
+ fetch(`${getRenderHostInstance().env.DEV_SERVER_URL}/_restartElectron`, {
+ method: 'POST',
+ });
+ }
+}
diff --git a/desktop/app/src/electron/restartFlipper.tsx b/desktop/app/src/electron/restartFlipper.tsx
deleted file mode 100644
index f4677531c..000000000
--- a/desktop/app/src/electron/restartFlipper.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * 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
- */
-
-// eslint-disable-next-line flipper/no-electron-remote-imports
-import {remote} from 'electron';
-import isProduction from '../utils/isProduction';
-
-export default function restart(update: boolean = false) {
- if (isProduction()) {
- if (update) {
- const options = {
- args: process.argv
- .splice(0, 1)
- .filter((arg) => arg !== '--no-launcher' && arg !== '--no-updater'),
- };
- remote.app.relaunch(options);
- } else {
- remote.app.relaunch();
- }
- remote.app.exit();
- } else {
- // Relaunching the process with the standard way doesn't work in dev mode.
- // So instead we're sending a signal to dev server to kill the current instance of electron and launch new.
- fetch(`${remote.process.env.DEV_SERVER_URL}/_restartElectron`, {
- method: 'POST',
- });
- }
-}
diff --git a/desktop/app/src/init.tsx b/desktop/app/src/init.tsx
index 3bfc6cd20..f28227fc1 100644
--- a/desktop/app/src/init.tsx
+++ b/desktop/app/src/init.tsx
@@ -7,55 +7,13 @@
* @format
*/
-import {Provider} from 'react-redux';
-import ReactDOM from 'react-dom';
+import {initializeElectron} from './electron/initializeElectron';
import GK from './fb-stubs/GK';
-import {init as initLogger} from './fb-stubs/Logger';
-import {SandyApp} from './sandy-chrome/SandyApp';
-import setupPrefetcher from './fb-stubs/Prefetcher';
-import {Persistor, persistStore} from 'redux-persist';
-import {Store} from './reducers/index';
-import dispatcher from './dispatcher/index';
-import TooltipProvider from './ui/components/TooltipProvider';
-import config from './utils/processConfig';
-import {initLauncherHooks} from './utils/launcher';
-import {setPersistor} from './utils/persistor';
-import React from 'react';
-import path from 'path';
-import {getStore} from './store';
-import {cache} from '@emotion/css';
-import {CacheProvider} from '@emotion/react';
import {enableMapSet} from 'immer';
import os from 'os';
-import {initializeFlipperLibImplementation} from './utils/flipperLibImplementation';
-import {enableConsoleHook} from './chrome/ConsoleLogs';
-import {sideEffect} from './utils/sideEffect';
-import {
- _NuxManagerContext,
- _createNuxManager,
- _setGlobalInteractionReporter,
- Logger,
- _LoggerContext,
- Layout,
- theme,
- getFlipperLib,
-} from 'flipper-plugin';
-import isProduction from './utils/isProduction';
-import {Button, Input, Result, Typography} from 'antd';
-import constants from './fb-stubs/constants';
-import styled from '@emotion/styled';
-import {CopyOutlined} from '@ant-design/icons';
-import {getVersionString} from './utils/versionString';
-import {PersistGate} from 'redux-persist/integration/react';
-import {
- setLoggerInstance,
- setUserSessionManagerInstance,
- GK as flipperCommonGK,
-} from 'flipper-common';
-import {internGraphPOSTAPIRequest} from './fb-stubs/user';
-import {getRenderHostInstance} from './RenderHost';
-import {initializeElectron} from './electron/initializeElectron';
+
+initializeElectron();
if (process.env.NODE_ENV === 'development' && os.platform() === 'darwin') {
// By default Node.JS has its internal certificate storage and doesn't use
@@ -70,187 +28,9 @@ enableMapSet();
GK.init();
-class AppFrame extends React.Component<
- {logger: Logger; persistor: Persistor},
- {error: any; errorInfo: any}
-> {
- state = {error: undefined as any, errorInfo: undefined as any};
-
- getError() {
- return this.state.error
- ? `${
- this.state.error
- }\n\nFlipper version: ${getVersionString()}\n\nComponent stack:\n${
- this.state.errorInfo?.componentStack
- }\n\nError stacktrace:\n${this.state.error?.stack}`
- : '';
- }
-
- render() {
- const {logger, persistor} = this.props;
- return this.state.error ? (
-
-
-
- A crash was detected in the Flipper chrome. Filing a{' '}
-
- bug report
- {' '}
- would be appreciated! Please include the details below.
-
- }
- extra={[
- }
- onClick={() => {
- getFlipperLib().writeTextToClipboard(this.getError());
- }}>
- Copy error
- ,
- ,
- ]}
- />
-
-
-
- ) : (
- <_LoggerContext.Provider value={logger}>
-
-
-
-
- <_NuxManagerContext.Provider value={_createNuxManager()}>
-
-
-
-
-
-
-
- );
- }
-
- componentDidCatch(error: any, errorInfo: any) {
- console.error(
- `Flipper chrome crash: ${error}`,
- error,
- '\nComponents: ' + errorInfo?.componentStack,
- );
- this.setState({
- error,
- errorInfo,
- });
- }
-}
-
-function setProcessState(store: Store) {
- const settings = store.getState().settingsState;
- const androidHome = settings.androidHome;
- const idbPath = settings.idbPath;
-
- if (!process.env.ANDROID_HOME && !process.env.ANDROID_SDK_ROOT) {
- process.env.ANDROID_HOME = androidHome;
- }
-
- // emulator/emulator is more reliable than tools/emulator, so prefer it if
- // it exists
- process.env.PATH =
- ['emulator', 'tools', 'platform-tools']
- .map((directory) => path.resolve(androidHome, directory))
- .join(':') +
- `:${idbPath}` +
- `:${process.env.PATH}`;
-
- window.requestIdleCallback(() => {
- setupPrefetcher(settings);
- });
-}
-
-function init() {
- initializeElectron();
- // TODO: centralise all those initialisations in a single configuration call
- flipperCommonGK.get = (name) => GK.get(name);
- const store = getStore();
- const logger = initLogger(store);
- setLoggerInstance(logger);
-
- // rehydrate app state before exposing init
- const persistor = persistStore(store, undefined, () => {
- // Make sure process state is set before dispatchers run
- setProcessState(store);
- dispatcher(store, logger);
- });
-
- setPersistor(persistor);
-
- initializeFlipperLibImplementation(getRenderHostInstance(), store, logger);
- _setGlobalInteractionReporter((r) => {
- logger.track('usage', 'interaction', r);
- if (!isProduction()) {
- const msg = `[interaction] ${r.scope}:${r.action} in ${r.duration}ms`;
- if (r.success) console.debug(msg);
- else console.warn(msg, r.error);
- }
- });
- setUserSessionManagerInstance({
- internGraphPOSTAPIRequest,
- });
-
- ReactDOM.render(
- ,
- document.getElementById('root'),
- );
- initLauncherHooks(config(), store);
- enableConsoleHook();
- window.flipperGlobalStoreDispatch = store.dispatch;
-
- // listen to settings and load the right theme
- sideEffect(
- store,
- {name: 'loadTheme', fireImmediately: false, throttleMs: 500},
- (state) => state.settingsState.darkMode,
- (theme) => {
- let shouldUseDarkMode = false;
- if (theme === 'dark') {
- shouldUseDarkMode = true;
- } else if (theme === 'light') {
- shouldUseDarkMode = false;
- } else if (theme === 'system') {
- shouldUseDarkMode = getRenderHostInstance().shouldUseDarkColors();
- }
- (
- document.getElementById('flipper-theme-import') as HTMLLinkElement
- ).href = `themes/${shouldUseDarkMode ? 'dark' : 'light'}.css`;
- getRenderHostInstance().sendIpcEvent('setTheme', theme);
- },
- );
-}
-
-setImmediate(() => {
- // make sure all modules are loaded
- // @ts-ignore
- window.flipperInit = init;
- window.dispatchEvent(new Event('flipper-store-ready'));
-});
-
-const CodeBlock = styled(Input.TextArea)({
- ...theme.monospace,
- color: theme.textColorSecondary,
-});
+// 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.
+// This prevent issues where the render host is referred at module initialisation level,
+// but not set yet, which might happen when using normal imports.
+// eslint-disable-next-line import/no-commonjs
+require('./startFlipperDesktop');
diff --git a/desktop/app/src/reducers/settings.tsx b/desktop/app/src/reducers/settings.tsx
index 665d3bb24..29944fd81 100644
--- a/desktop/app/src/reducers/settings.tsx
+++ b/desktop/app/src/reducers/settings.tsx
@@ -9,7 +9,7 @@
import {Actions} from './index';
import os from 'os';
-import electron from 'electron';
+import {getRenderHostInstance} from '../RenderHost';
export enum Tristate {
True,
@@ -105,6 +105,7 @@ function getDefaultAndroidSdkPath() {
}
function getWindowsSdkPath() {
- const app = electron.app || electron.remote.app;
- return `${app.getPath('home')}\\AppData\\Local\\android\\sdk`;
+ return `${
+ getRenderHostInstance().paths.homePath
+ }\\AppData\\Local\\android\\sdk`;
}
diff --git a/desktop/app/src/startFlipperDesktop.tsx b/desktop/app/src/startFlipperDesktop.tsx
new file mode 100644
index 000000000..1100cec3b
--- /dev/null
+++ b/desktop/app/src/startFlipperDesktop.tsx
@@ -0,0 +1,239 @@
+/**
+ * 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
+ */
+
+import {Provider} from 'react-redux';
+import ReactDOM from 'react-dom';
+
+import GK from './fb-stubs/GK';
+import {init as initLogger} from './fb-stubs/Logger';
+import {SandyApp} from './sandy-chrome/SandyApp';
+import setupPrefetcher from './fb-stubs/Prefetcher';
+import {Persistor, persistStore} from 'redux-persist';
+import {Store} from './reducers/index';
+import dispatcher from './dispatcher/index';
+import TooltipProvider from './ui/components/TooltipProvider';
+import config from './utils/processConfig';
+import {initLauncherHooks} from './utils/launcher';
+import {setPersistor} from './utils/persistor';
+import React from 'react';
+import path from 'path';
+import {getStore} from './store';
+import {cache} from '@emotion/css';
+import {CacheProvider} from '@emotion/react';
+import {initializeFlipperLibImplementation} from './utils/flipperLibImplementation';
+import {enableConsoleHook} from './chrome/ConsoleLogs';
+import {sideEffect} from './utils/sideEffect';
+import {
+ _NuxManagerContext,
+ _createNuxManager,
+ _setGlobalInteractionReporter,
+ Logger,
+ _LoggerContext,
+ Layout,
+ theme,
+ getFlipperLib,
+} from 'flipper-plugin';
+import isProduction from './utils/isProduction';
+import {Button, Input, Result, Typography} from 'antd';
+import constants from './fb-stubs/constants';
+import styled from '@emotion/styled';
+import {CopyOutlined} from '@ant-design/icons';
+import {getVersionString} from './utils/versionString';
+import {PersistGate} from 'redux-persist/integration/react';
+import {
+ setLoggerInstance,
+ setUserSessionManagerInstance,
+ GK as flipperCommonGK,
+} from 'flipper-common';
+import {internGraphPOSTAPIRequest} from './fb-stubs/user';
+import {getRenderHostInstance} from './RenderHost';
+
+class AppFrame extends React.Component<
+ {logger: Logger; persistor: Persistor},
+ {error: any; errorInfo: any}
+> {
+ state = {error: undefined as any, errorInfo: undefined as any};
+
+ getError() {
+ return this.state.error
+ ? `${
+ this.state.error
+ }\n\nFlipper version: ${getVersionString()}\n\nComponent stack:\n${
+ this.state.errorInfo?.componentStack
+ }\n\nError stacktrace:\n${this.state.error?.stack}`
+ : '';
+ }
+
+ render() {
+ const {logger, persistor} = this.props;
+ return this.state.error ? (
+
+
+
+ A crash was detected in the Flipper chrome. Filing a{' '}
+
+ bug report
+ {' '}
+ would be appreciated! Please include the details below.
+
+ }
+ extra={[
+ }
+ onClick={() => {
+ getFlipperLib().writeTextToClipboard(this.getError());
+ }}>
+ Copy error
+ ,
+ ,
+ ]}
+ />
+
+
+
+ ) : (
+ <_LoggerContext.Provider value={logger}>
+
+
+
+
+ <_NuxManagerContext.Provider value={_createNuxManager()}>
+
+
+
+
+
+
+
+ );
+ }
+
+ componentDidCatch(error: any, errorInfo: any) {
+ console.error(
+ `Flipper chrome crash: ${error}`,
+ error,
+ '\nComponents: ' + errorInfo?.componentStack,
+ );
+ this.setState({
+ error,
+ errorInfo,
+ });
+ }
+}
+
+function setProcessState(store: Store) {
+ const settings = store.getState().settingsState;
+ const androidHome = settings.androidHome;
+ const idbPath = settings.idbPath;
+
+ if (!process.env.ANDROID_HOME && !process.env.ANDROID_SDK_ROOT) {
+ process.env.ANDROID_HOME = androidHome;
+ }
+
+ // emulator/emulator is more reliable than tools/emulator, so prefer it if
+ // it exists
+ process.env.PATH =
+ ['emulator', 'tools', 'platform-tools']
+ .map((directory) => path.resolve(androidHome, directory))
+ .join(':') +
+ `:${idbPath}` +
+ `:${process.env.PATH}`;
+
+ window.requestIdleCallback(() => {
+ setupPrefetcher(settings);
+ });
+}
+
+function init() {
+ // TODO: centralise all those initialisations in a single configuration call
+ flipperCommonGK.get = (name) => GK.get(name);
+ const store = getStore();
+ const logger = initLogger(store);
+ setLoggerInstance(logger);
+
+ // rehydrate app state before exposing init
+ const persistor = persistStore(store, undefined, () => {
+ // Make sure process state is set before dispatchers run
+ setProcessState(store);
+ dispatcher(store, logger);
+ });
+
+ setPersistor(persistor);
+
+ initializeFlipperLibImplementation(getRenderHostInstance(), store, logger);
+ _setGlobalInteractionReporter((r) => {
+ logger.track('usage', 'interaction', r);
+ if (!isProduction()) {
+ const msg = `[interaction] ${r.scope}:${r.action} in ${r.duration}ms`;
+ if (r.success) console.debug(msg);
+ else console.warn(msg, r.error);
+ }
+ });
+ setUserSessionManagerInstance({
+ internGraphPOSTAPIRequest,
+ });
+
+ ReactDOM.render(
+ ,
+ document.getElementById('root'),
+ );
+ initLauncherHooks(config(), store);
+ enableConsoleHook();
+ window.flipperGlobalStoreDispatch = store.dispatch;
+
+ // listen to settings and load the right theme
+ sideEffect(
+ store,
+ {name: 'loadTheme', fireImmediately: false, throttleMs: 500},
+ (state) => state.settingsState.darkMode,
+ (theme) => {
+ let shouldUseDarkMode = false;
+ if (theme === 'dark') {
+ shouldUseDarkMode = true;
+ } else if (theme === 'light') {
+ shouldUseDarkMode = false;
+ } else if (theme === 'system') {
+ shouldUseDarkMode = getRenderHostInstance().shouldUseDarkColors();
+ }
+ (
+ document.getElementById('flipper-theme-import') as HTMLLinkElement
+ ).href = `themes/${shouldUseDarkMode ? 'dark' : 'light'}.css`;
+ getRenderHostInstance().sendIpcEvent('setTheme', theme);
+ },
+ );
+}
+
+setImmediate(() => {
+ // make sure all modules are loaded
+ // @ts-ignore
+ window.flipperInit = init;
+ window.dispatchEvent(new Event('flipper-store-ready'));
+});
+
+const CodeBlock = styled(Input.TextArea)({
+ ...theme.monospace,
+ color: theme.textColorSecondary,
+});
diff --git a/desktop/app/src/utils/flipperLibImplementation.tsx b/desktop/app/src/utils/flipperLibImplementation.tsx
index 69280aa6d..6ee718565 100644
--- a/desktop/app/src/utils/flipperLibImplementation.tsx
+++ b/desktop/app/src/utils/flipperLibImplementation.tsx
@@ -13,7 +13,6 @@ import type {Store} from '../reducers';
import createPaste from '../fb-stubs/createPaste';
import GK from '../fb-stubs/GK';
import type BaseDevice from '../devices/BaseDevice';
-import {clipboard, shell} from 'electron';
import constants from '../fb-stubs/constants';
import {addNotification} from '../reducers/notifications';
import {deconstructPluginKey} from 'flipper-common';
@@ -49,12 +48,8 @@ export function initializeFlipperLibImplementation(
},
});
},
- writeTextToClipboard(text: string) {
- clipboard.writeText(text);
- },
- openLink(url: string) {
- shell.openExternal(url);
- },
+ writeTextToClipboard: renderHost.writeTextToClipboard,
+ openLink: renderHost.openLink,
showNotification(pluginId, notification) {
const parts = deconstructPluginKey(pluginId);
store.dispatch(
@@ -69,5 +64,9 @@ export function initializeFlipperLibImplementation(
showSaveDialog: renderHost.showSaveDialog,
showOpenDialog: renderHost.showOpenDialog,
showSelectDirectoryDialog: renderHost.showSelectDirectoryDialog,
+ paths: {
+ appPath: renderHost.paths.appPath,
+ homePath: renderHost.paths.homePath,
+ },
});
}
diff --git a/desktop/app/src/utils/icons.ts b/desktop/app/src/utils/icons.ts
index 24b5cdadd..730d19e89 100644
--- a/desktop/app/src/utils/icons.ts
+++ b/desktop/app/src/utils/icons.ts
@@ -13,8 +13,7 @@
import fs from 'fs';
import path from 'path';
-// eslint-disable-next-line flipper/no-electron-remote-imports
-import {remote} from 'electron';
+import {getRenderHostInstance} from '../RenderHost';
import {getStaticPath} from './pathUtils';
const AVAILABLE_SIZES = [8, 10, 12, 16, 18, 20, 24, 32];
@@ -85,7 +84,7 @@ export function buildIconURLSync(name: string, size: number, density: number) {
) {
// From utils/isProduction
const isProduction = !/node_modules[\\/]electron[\\/]/.test(
- process.execPath || remote.process.execPath,
+ getRenderHostInstance().paths.execPath,
);
if (!isProduction) {
@@ -126,7 +125,12 @@ export function buildIconURLSync(name: string, size: number, density: number) {
return url;
}
-export function getIconURLSync(name: string, size: number, density: number) {
+export function getIconURLSync(
+ name: string,
+ size: number,
+ density: number,
+ basePath: string = getRenderHostInstance().paths.appPath,
+) {
if (name.indexOf('/') > -1) {
return name;
}
@@ -161,15 +165,8 @@ export function getIconURLSync(name: string, size: number, density: number) {
}
// resolve icon locally if possible
- if (
- remote &&
- fs.existsSync(
- path.join(
- remote.app.getAppPath(),
- buildLocalIconPath(name, size, density),
- ),
- )
- ) {
+ const iconPath = path.join(basePath, buildLocalIconPath(name, size, density));
+ if (fs.existsSync(iconPath)) {
return buildLocalIconURL(name, size, density);
}
return buildIconURLSync(name, requestedSize, density);
diff --git a/desktop/app/src/utils/isProduction.tsx b/desktop/app/src/utils/isProduction.tsx
index 72e69c09f..301fa790b 100644
--- a/desktop/app/src/utils/isProduction.tsx
+++ b/desktop/app/src/utils/isProduction.tsx
@@ -7,14 +7,15 @@
* @format
*/
-import electron from 'electron';
+import {getRenderHostInstance} from '../RenderHost';
-const _isProduction = !/node_modules[\\/]electron[\\/]/.test(
- // We only run this once and cache the output so this slow access is okay.
- // eslint-disable-next-line no-restricted-properties
- process.execPath || electron.remote.process.execPath,
-);
+let _isProduction: boolean | undefined;
export default function isProduction(): boolean {
+ if (_isProduction === undefined) {
+ _isProduction = !/node_modules[\\/]electron[\\/]/.test(
+ getRenderHostInstance().paths.execPath,
+ );
+ }
return _isProduction;
}
diff --git a/desktop/app/src/utils/packageMetadata.tsx b/desktop/app/src/utils/packageMetadata.tsx
index b4d3aaa7b..9c3c4e559 100644
--- a/desktop/app/src/utils/packageMetadata.tsx
+++ b/desktop/app/src/utils/packageMetadata.tsx
@@ -7,18 +7,14 @@
* @format
*/
-import electron from 'electron';
import lodash from 'lodash';
-import isProduction from './isProduction';
import path from 'path';
import fs from 'fs';
import {promisify} from 'util';
+import {getRenderHostInstance} from '../RenderHost';
const getPackageJSON = async () => {
- const base =
- isProduction() && electron.remote
- ? electron.remote.app.getAppPath()
- : process.cwd();
+ const base = getRenderHostInstance().paths.appPath;
const content = await promisify(fs.readFile)(
path.join(base, 'package.json'),
'utf-8',
diff --git a/desktop/app/src/utils/pathUtils.tsx b/desktop/app/src/utils/pathUtils.tsx
index 6807590e3..32a4da8d7 100644
--- a/desktop/app/src/utils/pathUtils.tsx
+++ b/desktop/app/src/utils/pathUtils.tsx
@@ -12,35 +12,15 @@
import path from 'path';
import fs from 'fs';
-// In utils this is fine when used with caching.
-// eslint-disable-next-line flipper/no-electron-remote-imports
-import {default as electron, remote} from 'electron';
+
import config from '../fb-stubs/config';
-
-let _staticPath = '';
-
-function getStaticDir() {
- if (_staticPath) {
- return _staticPath;
- }
- _staticPath = path.resolve(__dirname, '..', '..', '..', 'static');
- if (fs.existsSync(_staticPath)) {
- return _staticPath;
- }
- if (remote && fs.existsSync(remote.app.getAppPath())) {
- _staticPath = path.join(remote.app.getAppPath());
- }
- if (!fs.existsSync(_staticPath)) {
- throw new Error('Static path does not exist: ' + _staticPath);
- }
- return _staticPath;
-}
+import {getRenderHostInstance} from '../RenderHost';
export function getStaticPath(
relativePath: string = '.',
{asarUnpacked}: {asarUnpacked: boolean} = {asarUnpacked: false},
) {
- const staticDir = getStaticDir();
+ const staticDir = getRenderHostInstance().paths.staticPath;
const absolutePath = path.resolve(staticDir, relativePath);
// Unfortunately, path.resolve, fs.pathExists, fs.read etc do not automatically work with asarUnpacked files.
// All these functions still look for files in "app.asar" even if they are unpacked.
@@ -51,26 +31,6 @@ export function getStaticPath(
: absolutePath;
}
-let _appPath: string | undefined = undefined;
-export function getAppPath() {
- if (!_appPath) {
- _appPath = getStaticPath('..');
- }
-
- return _appPath;
-}
-
-let _tempPath: string | undefined = undefined;
-export function getAppTempPath() {
- if (!_tempPath) {
- // We cache this.
- // eslint-disable-next-line no-restricted-properties
- _tempPath = (electron.app || electron.remote.app).getPath('temp');
- }
-
- return _tempPath;
-}
-
export function getChangelogPath() {
const changelogPath = getStaticPath(config.isFBBuild ? 'facebook' : '.');
if (fs.existsSync(changelogPath)) {
diff --git a/desktop/app/src/utils/processConfig.tsx b/desktop/app/src/utils/processConfig.tsx
index 7cf0f9f03..b611c07fa 100644
--- a/desktop/app/src/utils/processConfig.tsx
+++ b/desktop/app/src/utils/processConfig.tsx
@@ -7,8 +7,7 @@
* @format
*/
-// eslint-disable-next-line flipper/no-electron-remote-imports
-import {remote} from 'electron';
+import {getRenderHostInstance} from '../RenderHost';
export type ProcessConfig = {
disabledPlugins: Set;
@@ -27,9 +26,7 @@ export type ProcessConfig = {
let configObj: ProcessConfig | null = null;
export default function config(): ProcessConfig {
if (configObj === null) {
- const json = JSON.parse(
- (remote && remote.process.env.CONFIG) || process.env.CONFIG || '{}',
- );
+ const json = JSON.parse(getRenderHostInstance().env.CONFIG || '{}');
configObj = {
disabledPlugins: new Set(json.disabledPlugins || []),
lastWindowPosition: json.lastWindowPosition,
diff --git a/desktop/app/src/utils/screenshot.tsx b/desktop/app/src/utils/screenshot.tsx
index ad9a19e55..2305654cf 100644
--- a/desktop/app/src/utils/screenshot.tsx
+++ b/desktop/app/src/utils/screenshot.tsx
@@ -12,14 +12,14 @@ import path from 'path';
import BaseDevice from '../devices/BaseDevice';
import {reportPlatformFailures} from 'flipper-common';
import expandTilde from 'expand-tilde';
-// eslint-disable-next-line flipper/no-electron-remote-imports
-import {remote} from 'electron';
import config from '../utils/processConfig';
+import {getRenderHostInstance} from '../RenderHost';
-// TODO: refactor so this doesn't need to be exported
-export const CAPTURE_LOCATION = expandTilde(
- config().screenCapturePath || remote.app.getPath('desktop'),
-);
+export function getCaptureLocation() {
+ return expandTilde(
+ config().screenCapturePath || getRenderHostInstance().paths.desktopPath,
+ );
+}
// TODO: refactor so this doesn't need to be exported
export function getFileName(extension: 'png' | 'mp4'): string {
@@ -32,7 +32,7 @@ export async function capture(device: BaseDevice): Promise {
console.log('Skipping screenshot for disconnected device');
return '';
}
- const pngPath = path.join(CAPTURE_LOCATION, getFileName('png'));
+ const pngPath = path.join(getCaptureLocation(), getFileName('png'));
return reportPlatformFailures(
device.screenshot().then((buffer) => writeBufferToFile(pngPath, buffer)),
'captureScreenshot',
diff --git a/desktop/flipper-plugin/src/plugin/FlipperLib.tsx b/desktop/flipper-plugin/src/plugin/FlipperLib.tsx
index a76df8e8b..062bc967f 100644
--- a/desktop/flipper-plugin/src/plugin/FlipperLib.tsx
+++ b/desktop/flipper-plugin/src/plugin/FlipperLib.tsx
@@ -48,6 +48,10 @@ export interface FlipperLib {
};
}): Promise;
showSelectDirectoryDialog?(defaultPath?: string): Promise;
+ paths: {
+ homePath: string;
+ appPath: string;
+ };
}
export let flipperLibInstance: FlipperLib | undefined;
diff --git a/desktop/flipper-plugin/src/test-utils/test-utils.tsx b/desktop/flipper-plugin/src/test-utils/test-utils.tsx
index 74364fc72..85e1e410a 100644
--- a/desktop/flipper-plugin/src/test-utils/test-utils.tsx
+++ b/desktop/flipper-plugin/src/test-utils/test-utils.tsx
@@ -379,6 +379,10 @@ export function createMockFlipperLib(options?: StartPluginOptions): FlipperLib {
writeTextToClipboard: jest.fn(),
openLink: jest.fn(),
showNotification: jest.fn(),
+ paths: {
+ appPath: process.cwd(),
+ homePath: `/dev/null`,
+ },
};
}
diff --git a/desktop/plugins/public/hermesdebuggerrn/ChromeDevTools.tsx b/desktop/plugins/public/hermesdebuggerrn/ChromeDevTools.tsx
index 4776a9dcc..d6859fb22 100644
--- a/desktop/plugins/public/hermesdebuggerrn/ChromeDevTools.tsx
+++ b/desktop/plugins/public/hermesdebuggerrn/ChromeDevTools.tsx
@@ -10,8 +10,6 @@
import React from 'react';
import {styled, colors, FlexColumn} from 'flipper';
-import electron from 'electron';
-
const devToolsNodeId = (url: string) =>
`hermes-chromedevtools-out-of-react-node-${url.replace(/\W+/g, '-')}`;
@@ -28,10 +26,16 @@ function createDevToolsNode(
return existing;
}
- // It is necessary to activate chrome devtools in electron
- electron.remote.getCurrentWindow().webContents.toggleDevTools();
- electron.remote.getCurrentWindow().webContents.closeDevTools();
-
+ // It is necessary to deactivate chrome devtools in electron
+ try {
+ const electron = require('electron');
+ if (electron.default) {
+ electron.default.remote.getCurrentWindow().webContents.toggleDevTools();
+ electron.default.remote.getCurrentWindow().webContents.closeDevTools();
+ }
+ } catch (e) {
+ console.warn('Failed to close Electron devtools: ', e);
+ }
const wrapper = document.createElement('div');
wrapper.id = devToolsNodeId(url);
wrapper.style.height = '100%';
diff --git a/desktop/plugins/public/navigation/util/appMatchPatterns.tsx b/desktop/plugins/public/navigation/util/appMatchPatterns.tsx
index 9298b6900..53004ae02 100644
--- a/desktop/plugins/public/navigation/util/appMatchPatterns.tsx
+++ b/desktop/plugins/public/navigation/util/appMatchPatterns.tsx
@@ -9,14 +9,14 @@
import fs from 'fs';
import path from 'path';
-import {getAppPath} from 'flipper';
import {AppMatchPattern} from '../types';
-import {Device} from 'flipper-plugin';
+import {Device, getFlipperLib} from 'flipper-plugin';
let patternsPath: string | undefined;
function getPatternsBasePath() {
- return (patternsPath = patternsPath ?? path.join(getAppPath(), 'facebook'));
+ return (patternsPath =
+ patternsPath ?? path.join(getFlipperLib().paths.appPath, 'facebook'));
}
const extractAppNameFromSelectedApp = (selectedApp: string | null) => {
diff --git a/desktop/scripts/build-release.ts b/desktop/scripts/build-release.ts
index 6f30e0490..9ba47e329 100755
--- a/desktop/scripts/build-release.ts
+++ b/desktop/scripts/build-release.ts
@@ -33,6 +33,7 @@ import {
getIconsSync,
buildLocalIconPath,
getIconURLSync,
+ Icons,
} from '../app/src/utils/icons';
import isFB from './isFB';
import copyPackageWithDependencies from './copy-package-with-dependencies';
@@ -309,7 +310,12 @@ async function copyStaticFolder(buildFolder: string) {
}
function downloadIcons(buildFolder: string) {
- const iconURLs = Object.entries(getIconsSync()).reduce<
+ const icons: Icons = JSON.parse(
+ fs.readFileSync(path.join(buildFolder, 'icons.json'), {
+ encoding: 'utf8',
+ }),
+ );
+ const iconURLs = Object.entries(icons).reduce<
{
name: string;
size: number;
@@ -326,7 +332,7 @@ function downloadIcons(buildFolder: string) {
return Promise.all(
iconURLs.map(({name, size, density}) => {
- const url = getIconURLSync(name, size, density);
+ const url = getIconURLSync(name, size, density, buildFolder);
return fetch(url, {
retryOptions: {
// Be default, only 5xx are retried but we're getting the odd 404