From 2e7015388c75ee88a0e7c538758bd0169b1b2322 Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Wed, 3 Nov 2021 07:00:03 -0700 Subject: [PATCH] Remove remaining Electron imports from product code: paths & env Summary: This diff removes most remaining Electron imports, by storing env and path constants on the RenderHost. As nice side effect those paths are all cached now as well. To make sure RenderHost is initialised before Flipper itself, forced loading Flipper through a require. Otherwise the setup is super sensitive to circular import statements, since a lot of module initialisation code depends on those paths / env vars. Reviewed By: nikoant Differential Revision: D31992230 fbshipit-source-id: 91beb430902272aaf4b051b35cdf12d2fc993347 --- desktop/app/src/RenderHost.tsx | 31 ++- .../app/src/chrome/ScreenCaptureButtons.tsx | 4 +- desktop/app/src/dispatcher/flipperServer.tsx | 5 +- .../app/src/electron/initializeElectron.tsx | 66 ++++- desktop/app/src/electron/restartFlipper.tsx | 34 --- desktop/app/src/init.tsx | 238 +---------------- desktop/app/src/reducers/settings.tsx | 7 +- desktop/app/src/startFlipperDesktop.tsx | 239 ++++++++++++++++++ .../src/utils/flipperLibImplementation.tsx | 13 +- desktop/app/src/utils/icons.ts | 23 +- desktop/app/src/utils/isProduction.tsx | 13 +- desktop/app/src/utils/packageMetadata.tsx | 8 +- desktop/app/src/utils/pathUtils.tsx | 46 +--- desktop/app/src/utils/processConfig.tsx | 7 +- desktop/app/src/utils/screenshot.tsx | 14 +- .../flipper-plugin/src/plugin/FlipperLib.tsx | 4 + .../src/test-utils/test-utils.tsx | 4 + .../hermesdebuggerrn/ChromeDevTools.tsx | 16 +- .../navigation/util/appMatchPatterns.tsx | 6 +- desktop/scripts/build-release.ts | 10 +- 20 files changed, 413 insertions(+), 375 deletions(-) delete mode 100644 desktop/app/src/electron/restartFlipper.tsx create mode 100644 desktop/app/src/startFlipperDesktop.tsx 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={[ - , - , - ]} - /> - -
-
- ) : ( - <_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={[ + , + , + ]} + /> + +
+
+ ) : ( + <_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