diff --git a/desktop/app/package.json b/desktop/app/package.json index 1a18d0d60..4ec4fb27c 100644 --- a/desktop/app/package.json +++ b/desktop/app/package.json @@ -17,6 +17,7 @@ "flipper-common": "0.0.0", "flipper-server-core": "0.0.0", "flipper-ui-core": "0.0.0", + "fs-extra": "^10.0.0", "invariant": "^2.2.2", "metro-runtime": "^0.66.2", "pretty-format": "^27.3.1" diff --git a/desktop/app/src/electron/initializeElectron.tsx b/desktop/app/src/electron/initializeElectron.tsx index 03f4cce4c..3a54fbf65 100644 --- a/desktop/app/src/electron/initializeElectron.tsx +++ b/desktop/app/src/electron/initializeElectron.tsx @@ -14,7 +14,7 @@ import { _setGlobalInteractionReporter, _LoggerContext, } from 'flipper-plugin'; -// eslint-disable-next-line flipper/no-electron-remote-imports +// eslint-disable-next-line no-restricted-imports,flipper/no-electron-remote-imports import { ipcRenderer, remote, @@ -26,7 +26,7 @@ import type {RenderHost} from 'flipper-ui-core'; import fs from 'fs'; import {setupMenuBar} from './setupMenuBar'; import os from 'os'; -import {FlipperServerImpl} from 'flipper-server-core'; +import {FlipperServerConfig} from 'flipper-common'; declare global { interface Window { @@ -45,12 +45,9 @@ if (process.env.NODE_ENV === 'development' && os.platform() === 'darwin') { global.electronRequire('mac-ca'); } -export function initializeElectron() { - const app = remote.app; +export function initializeElectron(flipperServerConfig: FlipperServerConfig) { const execPath = process.execPath || remote.process.execPath; const isProduction = !/node_modules[\\/]electron[\\/]/.test(execPath); - const staticPath = getStaticDir(); - const tempPath = app.getPath('temp'); function restart(update: boolean = false) { if (isProduction) { @@ -68,12 +65,9 @@ export function initializeElectron() { } 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( - `${window.FlipperRenderHostInstance.env.DEV_SERVER_URL}/_restartElectron`, - { - method: 'POST', - }, - ); + fetch(`${flipperServerConfig.env.DEV_SERVER_URL}/_restartElectron`, { + method: 'POST', + }); } } @@ -192,25 +186,10 @@ export function initializeElectron() { restartFlipper() { restart(); }, - env: process.env, - paths: { - appPath: app.getAppPath(), - homePath: app.getPath('home'), - execPath, - staticPath, - tempPath, - desktopPath: app.getPath('desktop'), - }, loadDefaultPlugins: getDefaultPluginsIndex, - startFlipperServer({logger, ...config}) { - return new FlipperServerImpl( - { - ...config, - staticPath, - tempPath, - }, - logger, - ); + serverConfig: flipperServerConfig, + GK(gatekeeper) { + return flipperServerConfig.gatekeepers[gatekeeper] ?? false; }, }; @@ -222,17 +201,3 @@ function getDefaultPluginsIndex() { const index = require('../defaultPlugins'); return index.default || index; } - -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; -} diff --git a/desktop/app/src/electron/setupMenuBar.tsx b/desktop/app/src/electron/setupMenuBar.tsx index 0c998f7da..73bd8df20 100644 --- a/desktop/app/src/electron/setupMenuBar.tsx +++ b/desktop/app/src/electron/setupMenuBar.tsx @@ -8,12 +8,10 @@ */ // Deliberate use of remote in this context. -/* eslint-disable no-restricted-properties */ - -import electron, {MenuItemConstructorOptions} from 'electron'; +/* eslint-disable no-restricted-properties, no-restricted-imports */ +import electron, {MenuItemConstructorOptions, webFrame} from 'electron'; import {getLogger} from 'flipper-common'; import {_buildInMenuEntries, _wrapInteractionHandler} from 'flipper-plugin'; -import {webFrame} from 'electron'; export function setupMenuBar() { const template = getTemplate(electron.remote.app); diff --git a/desktop/app/src/fb-stubs/constants.tsx b/desktop/app/src/fb-stubs/constants.tsx new file mode 100644 index 000000000..c4f62e96f --- /dev/null +++ b/desktop/app/src/fb-stubs/constants.tsx @@ -0,0 +1,18 @@ +/** + * 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 + */ + +export default Object.freeze({ + // Only WebSocket requests from the following origin prefixes will be accepted + VALID_WEB_SOCKET_REQUEST_ORIGIN_PREFIXES: [ + 'chrome-extension://', + 'localhost:', + 'http://localhost:', + 'app://', + ], +}); diff --git a/desktop/app/src/init.tsx b/desktop/app/src/init.tsx index 297fe3eee..8e6e03e67 100644 --- a/desktop/app/src/init.tsx +++ b/desktop/app/src/init.tsx @@ -7,16 +7,168 @@ * @format */ -import {initializeElectron} from './electron/initializeElectron'; import {enableMapSet} from 'immer'; - -initializeElectron(); +import { + _NuxManagerContext, + _createNuxManager, + _setGlobalInteractionReporter, + _LoggerContext, +} from 'flipper-plugin'; +// eslint-disable-next-line no-restricted-imports,flipper/no-electron-remote-imports +import {remote} from 'electron'; +import type {RenderHost} from 'flipper-ui-core'; +import os from 'os'; +import { + FlipperServerImpl, + getGatekeepers, + loadLauncherSettings, + loadProcessConfig, + loadSettings, + setupPrefetcher, +} from 'flipper-server-core'; +import {getLogger, Logger, setLoggerInstance} from 'flipper-common'; +import constants from './fb-stubs/constants'; +import {initializeElectron} from './electron/initializeElectron'; +import path from 'path'; +import fs from 'fs'; enableMapSet(); -// 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('flipper-ui-core').startFlipperDesktop(); +declare global { + interface Window { + // We store this as a global, to make sure the renderHost is available + // before flipper-ui-core is loaded and needs those during module initialisation + FlipperRenderHostInstance: RenderHost; + } +} + +if (process.env.NODE_ENV === 'development' && os.platform() === 'darwin') { + // By default Node.JS has its internal certificate storage and doesn't use + // the system store. Because of this, it's impossible to access ondemand / devserver + // which are signed using some internal self-issued FB certificates. These certificates + // are automatically installed to MacOS system store on FB machines, so here we're using + // this "mac-ca" library to load them into Node.JS. + global.electronRequire('mac-ca'); +} + +async function start() { + const app = remote.app; + const execPath = process.execPath || remote.process.execPath; + const isProduction = !/node_modules[\\/]electron[\\/]/.test(execPath); + const env = process.env; + + const logger = createDelegatedLogger(); + setLoggerInstance(logger); + + const flipperServer = new FlipperServerImpl( + { + env, + gatekeepers: getGatekeepers(), + isProduction, + paths: { + appPath: app.getAppPath(), + homePath: app.getPath('home'), + execPath, + staticPath: getStaticDir(), + tempPath: app.getPath('temp'), + desktopPath: app.getPath('desktop'), + }, + launcherSettings: await loadLauncherSettings(), + processConfig: loadProcessConfig(env), + settings: await loadSettings(), + validWebSocketOrigins: constants.VALID_WEB_SOCKET_REQUEST_ORIGIN_PREFIXES, + }, + logger, + ); + + await flipperServer.connect(); + const flipperServerConfig = await flipperServer.exec('get-config'); + + initializeElectron(flipperServerConfig); + + // 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('flipper-ui-core').startFlipperDesktop(flipperServer); + + // Initialize launcher + setupPrefetcher(flipperServerConfig.settings); +} + +start().catch((e) => { + console.error('Failed to start Flipper desktop', e); + document.getElementById('root')!.textContent = + 'Failed to start Flipper desktop: ' + e; +}); + +function getStaticDir() { + let _staticPath = path.resolve(__dirname, '..', '..', 'static'); + // fs.existSync used here, as fs-extra doesn't resovle properly in the app.asar + /* eslint-disable node/no-sync*/ + if (fs.existsSync(_staticPath)) { + // True in unit tests + 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); + } + /* eslint-enable node/no-sync*/ + return _staticPath; +} + +// 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 +// that uses a simple implementation until the real one comes available +function createDelegatedLogger(): Logger { + const naiveLogger: Logger = { + track(...args: [any, any, any?, any?]) { + console.warn('(skipper track)', args); + }, + trackTimeSince(...args: [any, any, any?]) { + console.warn('(skipped trackTimeSince)', args); + }, + debug(...args: any[]) { + console.debug(...args); + }, + error(...args: any[]) { + console.error(...args); + console.warn('(skipped error reporting)'); + }, + warn(...args: any[]) { + console.warn(...args); + console.warn('(skipped error reporting)'); + }, + info(...args: any[]) { + console.info(...args); + }, + }; + // will be overwrittingen later + setLoggerInstance(naiveLogger); + + return { + track() { + // noop + }, + trackTimeSince() { + // noop + }, + debug(...args: any[]) { + getLogger().debug(...args); + }, + error(...args: any[]) { + getLogger().error(...args); + }, + warn(...args: any[]) { + getLogger().warn(...args); + }, + info(...args: any[]) { + getLogger().info(...args); + }, + }; +} diff --git a/desktop/flipper-common/src/index.tsx b/desktop/flipper-common/src/index.tsx index 8d3e4e300..ee783bb70 100644 --- a/desktop/flipper-common/src/index.tsx +++ b/desktop/flipper-common/src/index.tsx @@ -44,3 +44,4 @@ export { export * from './user-session'; export * from './GK'; export * from './clientUtils'; +export * from './settings'; diff --git a/desktop/flipper-common/src/server-types.tsx b/desktop/flipper-common/src/server-types.tsx index 4e67125bf..ef2f9dab9 100644 --- a/desktop/flipper-common/src/server-types.tsx +++ b/desktop/flipper-common/src/server-types.tsx @@ -12,6 +12,7 @@ import { DeviceType as PluginDeviceType, OS as PluginOS, } from 'flipper-plugin-lib'; +import {LauncherSettings, ProcessConfig, Settings} from './settings'; // In the future, this file would deserve it's own package, as it doesn't really relate to plugins. // Since flipper-plugin however is currently shared among server, client and defines a lot of base types, leaving it here for now. @@ -123,6 +124,7 @@ export type IOSDeviceParams = { }; export type FlipperServerCommands = { + 'get-config': () => Promise; 'device-start-logging': (serial: string) => Promise; 'device-stop-logging': (serial: string) => Promise; 'device-supports-screenshot': (serial: string) => Promise; @@ -151,10 +153,36 @@ export type FlipperServerCommands = { 'android-launch-emulator': (name: string, coldboot: boolean) => Promise; 'ios-get-simulators': (bootedOnly: boolean) => Promise; 'ios-launch-simulator': (udid: string) => Promise; + 'persist-settings': (settings: Settings) => Promise; + 'persist-launcher-settings': (settings: LauncherSettings) => Promise; +}; + +type ENVIRONMENT_VARIABLES = + | 'NODE_ENV' + | 'DEV_SERVER_URL' + | 'CONFIG' + | 'FLIPPER_ENABLED_PLUGINS'; +type ENVIRONMENT_PATHS = + | 'appPath' + | 'homePath' + | 'execPath' + | 'staticPath' + | 'tempPath' + | 'desktopPath'; + +export type FlipperServerConfig = { + isProduction: boolean; + gatekeepers: Record; + env: Partial>; + paths: Record; + settings: Settings; + launcherSettings: LauncherSettings; + processConfig: ProcessConfig; + validWebSocketOrigins: string[]; }; export interface FlipperServer { - start(): Promise; + connect(): Promise; on( event: Event, callback: (payload: FlipperServerEvents[Event]) => void, diff --git a/desktop/flipper-common/src/settings.tsx b/desktop/flipper-common/src/settings.tsx new file mode 100644 index 000000000..c3a4af432 --- /dev/null +++ b/desktop/flipper-common/src/settings.tsx @@ -0,0 +1,70 @@ +/** + * 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 + */ + +export enum Tristate { + True, + False, + Unset, +} + +/** Settings used by both Server and UI. + * TODO: some settings might be flipper environment specific, + * and should ideally bemoved to local storage, like 'darkMode' + */ +export type Settings = { + androidHome: string; + enableAndroid: boolean; + enableIOS: boolean; + enablePhysicalIOS: boolean; + /** + * If unset, this will assume the value of the GK setting. + * Note that this setting has no effect in the open source version + * of Flipper. + */ + enablePrefetching: Tristate; + idbPath: string; + reactNative: { + shortcuts: { + enabled: boolean; + reload: string; + openDevMenu: string; + }; + }; + darkMode: 'dark' | 'light' | 'system'; + showWelcomeAtStartup: boolean; + suppressPluginErrors: boolean; +}; + +export enum ReleaseChannel { + DEFAULT = 'default', + STABLE = 'stable', + INSIDERS = 'insiders', +} + +/** Launcher settings only apply to Electron, and aren't managed or relevant for flipper-server-core */ +export type LauncherSettings = { + releaseChannel: ReleaseChannel; + ignoreLocalPin: boolean; +}; + +// Settings that primarily only apply to Eelectron atm +// TODO: further separte between flipper-ui config and Electron config +export type ProcessConfig = { + disabledPlugins: Set; + lastWindowPosition: { + x: number; + y: number; + width: number; + height: number; + } | null; + screenCapturePath: string | null; + launcherMsg: string | null; + // Controls whether to delegate to the launcher if present. + launcherEnabled: boolean; +}; diff --git a/desktop/flipper-common/src/utils/Logger.tsx b/desktop/flipper-common/src/utils/Logger.tsx index b2e23c397..00ef07857 100644 --- a/desktop/flipper-common/src/utils/Logger.tsx +++ b/desktop/flipper-common/src/utils/Logger.tsx @@ -64,9 +64,6 @@ export function getLogger(): Logger { // only for testing export function setLoggerInstance(logger: Logger) { - if (!isTest() && instance) { - console.warn('Logger was already initialised'); - } instance = logger; } diff --git a/desktop/flipper-dump/README.md b/desktop/flipper-dump/README.md index b014f2853..896b3cd6a 100644 --- a/desktop/flipper-dump/README.md +++ b/desktop/flipper-dump/README.md @@ -2,6 +2,8 @@ Stand alone Flipper command, that uses flipper-server-core to connect to apps and dump all incoming messages. +To get started, run `yarn install` in the `desktop/` folder once. + This package is currently a proof of concept and can be used like: `yarn start --device='iPhone 12' --client='Instagram' --plugin='AnalyticsLogging'` diff --git a/desktop/flipper-dump/src/index.tsx b/desktop/flipper-dump/src/index.tsx index 629d17573..a4458b228 100644 --- a/desktop/flipper-dump/src/index.tsx +++ b/desktop/flipper-dump/src/index.tsx @@ -10,7 +10,12 @@ import fs from 'fs'; import os from 'os'; import yargs from 'yargs'; -import {FlipperServerImpl} from 'flipper-server-core'; +import { + FlipperServerImpl, + loadLauncherSettings, + loadProcessConfig, + loadSettings, +} from 'flipper-server-core'; import { ClientDescription, Logger, @@ -48,7 +53,7 @@ const argv = yargs .parse(process.argv.slice(1)); async function start(deviceTitle: string, appName: string, pluginId: string) { - return new Promise((_resolve, reject) => { + return new Promise(async (_resolve, reject) => { let device: DeviceDescription | undefined; let deviceResolver: () => void; const devicePromise: Promise = new Promise((resolve) => { @@ -67,14 +72,20 @@ async function start(deviceTitle: string, appName: string, pluginId: string) { const server = new FlipperServerImpl( { - // TODO: make these better overridable - enableAndroid: true, - androidHome: process.env.ANDROID_HOME || '/opt/android_sdk', - idbPath: '/usr/local/bin/idb', - enableIOS: true, - enablePhysicalIOS: true, - staticPath: path.resolve(__dirname, '..', '..', 'static'), - tempPath: os.tmpdir(), + env: process.env, + gatekeepers: {}, + isProduction: false, + paths: { + staticPath: path.resolve(__dirname, '..', '..', 'static'), + tempPath: os.tmpdir(), + appPath: `/dev/null`, + homePath: `/dev/null`, + execPath: process.execPath, + desktopPath: `/dev/null`, + }, + launcherSettings: await loadLauncherSettings(), + processConfig: loadProcessConfig(process.env), + settings: await loadSettings(), validWebSocketOrigins: [], }, logger, @@ -196,7 +207,7 @@ async function start(deviceTitle: string, appName: string, pluginId: string) { }); server - .start() + .connect() .then(() => { logger.info( 'Flipper server started and accepting device / client connections', diff --git a/desktop/flipper-plugin/src/test-utils/test-utils.tsx b/desktop/flipper-plugin/src/test-utils/test-utils.tsx index 86c8fe328..8896c785c 100644 --- a/desktop/flipper-plugin/src/test-utils/test-utils.tsx +++ b/desktop/flipper-plugin/src/test-utils/test-utils.tsx @@ -590,7 +590,7 @@ export function createFlipperServerMock( overrides?: Partial, ): FlipperServer { return { - async start() {}, + async connect() {}, on: jest.fn(), off: jest.fn(), exec: jest diff --git a/desktop/flipper-server-core/package.json b/desktop/flipper-server-core/package.json index 759c3d0ff..2764a6561 100644 --- a/desktop/flipper-server-core/package.json +++ b/desktop/flipper-server-core/package.json @@ -10,6 +10,7 @@ "license": "MIT", "bugs": "https://github.com/facebook/flipper/issues", "dependencies": { + "@iarna/toml": "^2.2.5", "JSONStream": "^1.3.1", "adbkit": "^2.11.1", "adbkit-logcat": "^2.0.1", @@ -31,7 +32,8 @@ "tmp": "^0.2.1", "uuid": "^8.3.2", "which": "^2.0.2", - "ws": "^8.2.3" + "ws": "^8.2.3", + "xdg-basedir": "^4.0.0" }, "devDependencies": {}, "peerDependencies": {}, diff --git a/desktop/flipper-server-core/src/FlipperServerConfig.tsx b/desktop/flipper-server-core/src/FlipperServerConfig.tsx index ced983527..fee41789b 100644 --- a/desktop/flipper-server-core/src/FlipperServerConfig.tsx +++ b/desktop/flipper-server-core/src/FlipperServerConfig.tsx @@ -7,43 +7,22 @@ * @format */ -import {isTest} from 'flipper-common'; +import {FlipperServerConfig} from 'flipper-common'; import {parseFlipperPorts} from './utils/environmentVariables'; -export interface FlipperServerConfig { - enableAndroid: boolean; - androidHome: string; - enableIOS: boolean; - idbPath: string; - enablePhysicalIOS: boolean; - validWebSocketOrigins: string[]; - staticPath: string; - tempPath: string; -} - -// defaultConfig should be used for testing only, and disables by default all features -const testConfig: FlipperServerConfig = { - androidHome: '', - enableAndroid: false, - enableIOS: false, - enablePhysicalIOS: false, - idbPath: '', - validWebSocketOrigins: [], - staticPath: '/static/', - tempPath: '/temp/', -}; - let currentConfig: FlipperServerConfig | undefined = undefined; +// just an ugly utility to not need a reference to FlipperServerImpl itself everywhere export function getFlipperServerConfig(): FlipperServerConfig { if (!currentConfig) { - if (isTest()) return testConfig; throw new Error('FlipperServerConfig has not been set'); } return currentConfig; } -export function setFlipperServerConfig(config: FlipperServerConfig) { +export function setFlipperServerConfig( + config: FlipperServerConfig | undefined, +) { currentConfig = config; } diff --git a/desktop/flipper-server-core/src/FlipperServerImpl.tsx b/desktop/flipper-server-core/src/FlipperServerImpl.tsx index 19f6295f1..ae956d0ae 100644 --- a/desktop/flipper-server-core/src/FlipperServerImpl.tsx +++ b/desktop/flipper-server-core/src/FlipperServerImpl.tsx @@ -24,15 +24,15 @@ import { FlipperServerCommands, FlipperServer, UninitializedClient, + FlipperServerConfig, } from 'flipper-common'; import {ServerDevice} from './devices/ServerDevice'; import {Base64} from 'js-base64'; import MetroDevice from './devices/metro/MetroDevice'; import {launchEmulator} from './devices/android/AndroidDevice'; -import { - FlipperServerConfig, - setFlipperServerConfig, -} from './FlipperServerConfig'; +import {setFlipperServerConfig} from './FlipperServerConfig'; +import {saveSettings} from './utils/settings'; +import {saveLauncherSettings} from './utils/launcherSettings'; /** * FlipperServer takes care of all incoming device & client connections. @@ -52,7 +52,7 @@ export class FlipperServerImpl implements FlipperServer { android: AndroidDeviceManager; ios: IOSDeviceManager; - constructor(config: FlipperServerConfig, public logger: Logger) { + constructor(public config: FlipperServerConfig, public logger: Logger) { setFlipperServerConfig(config); const server = (this.server = new ServerController(this)); this.android = new AndroidDeviceManager(this); @@ -107,7 +107,7 @@ export class FlipperServerImpl implements FlipperServer { /** * Starts listening to parts and watching for devices */ - async start() { + async connect() { if (this.state !== 'pending') { throw new Error('Server already started'); } @@ -170,6 +170,7 @@ export class FlipperServerImpl implements FlipperServer { } private commandHandler: FlipperServerCommands = { + 'get-config': async () => this.config, 'device-start-logging': async (serial: string) => this.getDevice(serial).startLogging(), 'device-stop-logging': async (serial: string) => @@ -223,6 +224,9 @@ export class FlipperServerImpl implements FlipperServer { 'ios-get-simulators': async (bootedOnly) => this.ios.getSimulators(bootedOnly), 'ios-launch-simulator': async (udid) => launchSimulator(udid), + 'persist-settings': async (settings) => saveSettings(settings), + 'persist-launcher-settings': async (settings) => + saveLauncherSettings(settings), }; registerDevice(device: ServerDevice) { diff --git a/desktop/flipper-server-core/src/comms/ServerController.tsx b/desktop/flipper-server-core/src/comms/ServerController.tsx index 103fc949f..8d3aa652b 100644 --- a/desktop/flipper-server-core/src/comms/ServerController.tsx +++ b/desktop/flipper-server-core/src/comms/ServerController.tsx @@ -96,7 +96,7 @@ class ServerController extends EventEmitter implements ServerEventsListener { this.certificateProvider = new CertificateProvider( this, this.logger, - getFlipperServerConfig(), + getFlipperServerConfig().settings, ); this.connectionTracker = new ConnectionTracker(this.logger); this.secureServer = null; @@ -244,13 +244,13 @@ class ServerController extends EventEmitter implements ServerEventsListener { const {os, app, device_id} = clientQuery; // without these checks, the user might see a connection timeout error instead, which would be much harder to track down - if (os === 'iOS' && !getFlipperServerConfig().enableIOS) { + if (os === 'iOS' && !getFlipperServerConfig().settings.enableIOS) { console.error( `Refusing connection from ${app} on ${device_id}, since iOS support is disabled in settings`, ); return; } - if (os === 'Android' && !getFlipperServerConfig().enableAndroid) { + if (os === 'Android' && !getFlipperServerConfig().settings.enableAndroid) { console.error( `Refusing connection from ${app} on ${device_id}, since Android support is disabled in settings`, ); diff --git a/desktop/flipper-server-core/src/devices/android/androidDeviceManager.tsx b/desktop/flipper-server-core/src/devices/android/androidDeviceManager.tsx index a28709064..d209892ca 100644 --- a/desktop/flipper-server-core/src/devices/android/androidDeviceManager.tsx +++ b/desktop/flipper-server-core/src/devices/android/androidDeviceManager.tsx @@ -184,7 +184,7 @@ export class AndroidDeviceManager { async watchAndroidDevices() { try { - const client = await getAdbClient(getFlipperServerConfig()); + const client = await getAdbClient(getFlipperServerConfig().settings); client .trackDevices() .then((tracker) => { diff --git a/desktop/flipper-server-core/src/devices/ios/IOSBridge.tsx b/desktop/flipper-server-core/src/devices/ios/IOSBridge.tsx index f9c4bea10..af1e8320a 100644 --- a/desktop/flipper-server-core/src/devices/ios/IOSBridge.tsx +++ b/desktop/flipper-server-core/src/devices/ios/IOSBridge.tsx @@ -98,7 +98,7 @@ export function xcrunStartLogListener(udid: string, deviceType: DeviceType) { function makeTempScreenshotFilePath() { const imageName = uuid() + '.png'; - return path.join(getFlipperServerConfig().tempPath, imageName); + return path.join(getFlipperServerConfig().paths.tempPath, imageName); } async function runScreenshotCommand( diff --git a/desktop/flipper-server-core/src/devices/ios/__tests__/IOSBridge.node.tsx b/desktop/flipper-server-core/src/devices/ios/__tests__/IOSBridge.node.tsx index e37234c5f..10ee78e74 100644 --- a/desktop/flipper-server-core/src/devices/ios/__tests__/IOSBridge.node.tsx +++ b/desktop/flipper-server-core/src/devices/ios/__tests__/IOSBridge.node.tsx @@ -7,13 +7,23 @@ * @format */ -import {makeIOSBridge} from '../IOSBridge'; import childProcess from 'child_process'; -import * as promisifyChildProcess from 'promisify-child-process'; - jest.mock('child_process'); jest.mock('promisify-child-process'); +import {makeIOSBridge} from '../IOSBridge'; +import * as promisifyChildProcess from 'promisify-child-process'; +import {setFlipperServerConfig} from '../../../FlipperServerConfig'; +import {getRenderHostInstance} from 'flipper-ui-core'; + +beforeEach(() => { + setFlipperServerConfig(getRenderHostInstance().serverConfig); +}); + +afterEach(() => { + setFlipperServerConfig(undefined); +}); + test('uses xcrun with no idb when xcode is detected', async () => { const ib = await makeIOSBridge('', true); @@ -95,10 +105,10 @@ test.unix( async () => { const ib = await makeIOSBridge('', true); - ib.screenshot('deadbeef'); + await expect(() => ib.screenshot('deadbeef')).rejects.toThrow(); - expect(promisifyChildProcess.exec).toHaveBeenCalledWith( - 'xcrun simctl io deadbeef screenshot /temp/00000000-0000-0000-0000-000000000000.png', + expect((promisifyChildProcess.exec as any).mock.calls[0][0]).toMatch( + 'xcrun simctl io deadbeef screenshot', ); }, ); @@ -106,17 +116,17 @@ test.unix( test.unix('uses idb to take screenshots when available', async () => { const ib = await makeIOSBridge('/usr/local/bin/idb', true, async (_) => true); - ib.screenshot('deadbeef'); + await expect(() => ib.screenshot('deadbeef')).rejects.toThrow(); - expect(promisifyChildProcess.exec).toHaveBeenCalledWith( - 'idb screenshot --udid deadbeef /temp/00000000-0000-0000-0000-000000000000.png', + expect((promisifyChildProcess.exec as any).mock.calls[0][0]).toMatch( + 'idb screenshot --udid deadbeef ', ); }); test('uses xcrun to navigate with no idb when xcode is detected', async () => { const ib = await makeIOSBridge('', true); - ib.navigate('deadbeef', 'fb://dummy'); + await ib.navigate('deadbeef', 'fb://dummy'); expect(promisifyChildProcess.exec).toHaveBeenCalledWith( 'xcrun simctl io deadbeef launch url "fb://dummy"', @@ -126,7 +136,7 @@ test('uses xcrun to navigate with no idb when xcode is detected', async () => { test('uses idb to navigate when available', async () => { const ib = await makeIOSBridge('/usr/local/bin/idb', true, async (_) => true); - ib.navigate('deadbeef', 'fb://dummy'); + await ib.navigate('deadbeef', 'fb://dummy'); expect(promisifyChildProcess.exec).toHaveBeenCalledWith( 'idb open --udid deadbeef "fb://dummy"', diff --git a/desktop/flipper-server-core/src/devices/ios/__tests__/iOSDevice.node.tsx b/desktop/flipper-server-core/src/devices/ios/__tests__/iOSDevice.node.tsx index a0658b274..bbb613f2e 100644 --- a/desktop/flipper-server-core/src/devices/ios/__tests__/iOSDevice.node.tsx +++ b/desktop/flipper-server-core/src/devices/ios/__tests__/iOSDevice.node.tsx @@ -11,9 +11,19 @@ import {parseXcodeFromCoreSimPath} from '../iOSDeviceManager'; import {getLogger} from 'flipper-common'; import {IOSBridge} from '../IOSBridge'; import {FlipperServerImpl} from '../../../FlipperServerImpl'; -import {getFlipperServerConfig} from '../../../FlipperServerConfig'; +import {getRenderHostInstance} from 'flipper-ui-core'; +import { + getFlipperServerConfig, + setFlipperServerConfig, +} from '../../../FlipperServerConfig'; -const testConfig = getFlipperServerConfig(); +beforeEach(() => { + setFlipperServerConfig(getRenderHostInstance().serverConfig); +}); + +afterEach(() => { + setFlipperServerConfig(undefined); +}); const standardCoresimulatorLog = 'username 1264 0.0 0.1 5989740 41648 ?? Ss 2:23PM 0:12.92 /Applications/Xcode_12.4.0_fb.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/libexec/mobileassetd'; @@ -56,7 +66,10 @@ test('test parseXcodeFromCoreSimPath from standard locations', () => { }); test('test getAllPromisesForQueryingDevices when xcode detected', () => { - const flipperServer = new FlipperServerImpl(testConfig, getLogger()); + const flipperServer = new FlipperServerImpl( + getFlipperServerConfig(), + getLogger(), + ); flipperServer.ios.iosBridge = {} as IOSBridge; const promises = flipperServer.ios.getAllPromisesForQueryingDevices( true, @@ -66,7 +79,10 @@ test('test getAllPromisesForQueryingDevices when xcode detected', () => { }); test('test getAllPromisesForQueryingDevices when xcode is not detected', () => { - const flipperServer = new FlipperServerImpl(testConfig, getLogger()); + const flipperServer = new FlipperServerImpl( + getFlipperServerConfig(), + getLogger(), + ); flipperServer.ios.iosBridge = {} as IOSBridge; const promises = flipperServer.ios.getAllPromisesForQueryingDevices( false, @@ -76,7 +92,10 @@ test('test getAllPromisesForQueryingDevices when xcode is not detected', () => { }); test('test getAllPromisesForQueryingDevices when xcode and idb are both unavailable', () => { - const flipperServer = new FlipperServerImpl(testConfig, getLogger()); + const flipperServer = new FlipperServerImpl( + getFlipperServerConfig(), + getLogger(), + ); flipperServer.ios.iosBridge = {} as IOSBridge; const promises = flipperServer.ios.getAllPromisesForQueryingDevices( false, @@ -86,7 +105,10 @@ test('test getAllPromisesForQueryingDevices when xcode and idb are both unavaila }); test('test getAllPromisesForQueryingDevices when both idb and xcode are available', () => { - const flipperServer = new FlipperServerImpl(testConfig, getLogger()); + const flipperServer = new FlipperServerImpl( + getFlipperServerConfig(), + getLogger(), + ); flipperServer.ios.iosBridge = {} as IOSBridge; const promises = flipperServer.ios.getAllPromisesForQueryingDevices( true, diff --git a/desktop/flipper-server-core/src/devices/ios/iOSDeviceManager.tsx b/desktop/flipper-server-core/src/devices/ios/iOSDeviceManager.tsx index 58855b609..458498dbb 100644 --- a/desktop/flipper-server-core/src/devices/ios/iOSDeviceManager.tsx +++ b/desktop/flipper-server-core/src/devices/ios/iOSDeviceManager.tsx @@ -46,7 +46,7 @@ export class IOSDeviceManager { private portForwarders: Array = []; private portforwardingClient = path.join( - getFlipperServerConfig().staticPath, + getFlipperServerConfig().paths.staticPath, 'PortForwardingMacApp.app', 'Contents', 'MacOS', @@ -111,7 +111,7 @@ export class IOSDeviceManager { isXcodeDetected: boolean, isIdbAvailable: boolean, ): Array> { - const config = getFlipperServerConfig(); + const config = getFlipperServerConfig().settings; return [ isIdbAvailable ? getActiveDevices(config.idbPath, config.enablePhysicalIOS).then( @@ -130,7 +130,7 @@ export class IOSDeviceManager { } private async queryDevices(): Promise { - const config = getFlipperServerConfig(); + const config = getFlipperServerConfig().settings; const isXcodeInstalled = await iosUtil.isXcodeDetected(); const isIdbAvailable = await iosUtil.isAvailable(config.idbPath); console.debug( @@ -182,21 +182,19 @@ export class IOSDeviceManager { public async watchIOSDevices() { // TODO: pull this condition up - if (!getFlipperServerConfig().enableIOS) { + const settings = getFlipperServerConfig().settings; + if (!settings.enableIOS) { return; } try { const isDetected = await iosUtil.isXcodeDetected(); this.xcodeCommandLineToolsDetected = isDetected; - if (getFlipperServerConfig().enablePhysicalIOS) { + if (settings.enablePhysicalIOS) { this.startDevicePortForwarders(); } try { // Awaiting the promise here to trigger immediate error handling. - this.iosBridge = await makeIOSBridge( - getFlipperServerConfig().idbPath, - isDetected, - ); + this.iosBridge = await makeIOSBridge(settings.idbPath, isDetected); this.queryDevicesForever(); } catch (err) { // This case is expected if both Xcode and idb are missing. diff --git a/desktop/flipper-ui-core/src/fb-stubs/GK.tsx b/desktop/flipper-server-core/src/fb-stubs/GK.tsx similarity index 97% rename from desktop/flipper-ui-core/src/fb-stubs/GK.tsx rename to desktop/flipper-server-core/src/fb-stubs/GK.tsx index 0807ae4fd..8f18bb3d1 100644 --- a/desktop/flipper-ui-core/src/fb-stubs/GK.tsx +++ b/desktop/flipper-server-core/src/fb-stubs/GK.tsx @@ -42,10 +42,6 @@ export default class GK { return false; } - static serializeGKs() { - return ''; - } - static async withWhitelistedGK( id: GKID, callback: () => Promise | void, @@ -63,4 +59,8 @@ export default class GK { } } } + + static allGKs(): GKMap { + return {}; + } } diff --git a/desktop/flipper-ui-core/src/ReleaseChannel.tsx b/desktop/flipper-server-core/src/fb-stubs/Prefetcher.tsx similarity index 61% rename from desktop/flipper-ui-core/src/ReleaseChannel.tsx rename to desktop/flipper-server-core/src/fb-stubs/Prefetcher.tsx index e3a71620f..890da47ad 100644 --- a/desktop/flipper-ui-core/src/ReleaseChannel.tsx +++ b/desktop/flipper-server-core/src/fb-stubs/Prefetcher.tsx @@ -7,10 +7,8 @@ * @format */ -export enum ReleaseChannel { - DEFAULT = 'default', - STABLE = 'stable', - INSIDERS = 'insiders', -} +import {Tristate} from 'flipper-common'; -export default ReleaseChannel; +export async function setupPrefetcher(_settings: { + enablePrefetching: Tristate; +}) {} diff --git a/desktop/flipper-server-core/src/index.tsx b/desktop/flipper-server-core/src/index.tsx index d802e22cd..f966acc00 100644 --- a/desktop/flipper-server-core/src/index.tsx +++ b/desktop/flipper-server-core/src/index.tsx @@ -8,3 +8,20 @@ */ export {FlipperServerImpl} from './FlipperServerImpl'; +export {loadSettings} from './utils/settings'; +export {loadLauncherSettings} from './utils/launcherSettings'; +export {loadProcessConfig} from './utils/processConfig'; + +import GKImplementation from './fb-stubs/GK'; +export {setupPrefetcher} from './fb-stubs/Prefetcher'; + +let loaded = false; + +export function getGatekeepers(): Record { + if (!loaded) { + // this starts fetching gatekeepers, note that they will only be available on next restart! + GKImplementation.init(); + loaded = true; + } + return GKImplementation.allGKs(); +} diff --git a/desktop/flipper-ui-core/src/utils/__tests__/processConfig.node.tsx b/desktop/flipper-server-core/src/utils/__tests__/processConfig.node.tsx similarity index 60% rename from desktop/flipper-ui-core/src/utils/__tests__/processConfig.node.tsx rename to desktop/flipper-server-core/src/utils/__tests__/processConfig.node.tsx index 8038f3236..ac30f0c8c 100644 --- a/desktop/flipper-ui-core/src/utils/__tests__/processConfig.node.tsx +++ b/desktop/flipper-server-core/src/utils/__tests__/processConfig.node.tsx @@ -7,22 +7,20 @@ * @format */ -import {default as config, resetConfigForTesting} from '../processConfig'; - -afterEach(() => { - resetConfigForTesting(); -}); +import {loadProcessConfig} from '../processConfig'; test('config is decoded from env', () => { - process.env.CONFIG = JSON.stringify({ - disabledPlugins: ['pluginA', 'pluginB', 'pluginC'], - lastWindowPosition: {x: 4, y: 8, width: 15, height: 16}, - launcherMsg: 'wubba lubba dub dub', - screenCapturePath: '/my/screenshot/path', - launcherEnabled: false, + const config = loadProcessConfig({ + CONFIG: JSON.stringify({ + disabledPlugins: ['pluginA', 'pluginB', 'pluginC'], + lastWindowPosition: {x: 4, y: 8, width: 15, height: 16}, + launcherMsg: 'wubba lubba dub dub', + screenCapturePath: '/my/screenshot/path', + launcherEnabled: false, + }), }); - expect(config()).toEqual({ + expect(config).toEqual({ disabledPlugins: new Set(['pluginA', 'pluginB', 'pluginC']), lastWindowPosition: {x: 4, y: 8, width: 15, height: 16}, launcherMsg: 'wubba lubba dub dub', @@ -32,9 +30,7 @@ test('config is decoded from env', () => { }); test('config is decoded from env with defaults', () => { - process.env.CONFIG = '{}'; - - expect(config()).toEqual({ + expect(loadProcessConfig({CONFIG: '{}'})).toEqual({ disabledPlugins: new Set([]), lastWindowPosition: undefined, launcherMsg: undefined, diff --git a/desktop/flipper-server-core/src/utils/launcherSettings.tsx b/desktop/flipper-server-core/src/utils/launcherSettings.tsx new file mode 100644 index 000000000..f40c7ca0e --- /dev/null +++ b/desktop/flipper-server-core/src/utils/launcherSettings.tsx @@ -0,0 +1,91 @@ +/** + * 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 path from 'path'; +import os from 'os'; +import xdg from 'xdg-basedir'; +import fs from 'fs-extra'; +import TOML, {JsonMap} from '@iarna/toml'; +import {LauncherSettings, ReleaseChannel} from 'flipper-common'; + +export function xdgConfigDir() { + return os.platform() === 'darwin' + ? path.join(os.homedir(), 'Library', 'Preferences') + : xdg.config || path.join(os.homedir(), '.config'); +} + +export function launcherConfigDir() { + return path.join( + xdgConfigDir(), + os.platform() == 'darwin' ? 'rs.flipper-launcher' : 'flipper-launcher', + ); +} + +function getLauncherSettingsFile(): string { + // There is some disagreement among the XDG Base Directory implementations + // whether to use ~/Library/Preferences or ~/.config on MacOS. The Launcher + // expects the former, whereas `xdg-basedir` implements the latter. + return path.resolve(launcherConfigDir(), 'flipper-launcher.toml'); +} + +const defaultLauncherSettings: LauncherSettings = { + releaseChannel: ReleaseChannel.DEFAULT, + ignoreLocalPin: false, +}; + +interface FormattedSettings { + ignore_local_pin?: boolean; + release_channel?: ReleaseChannel; +} + +function serialize(value: LauncherSettings): string { + const {ignoreLocalPin, releaseChannel, ...rest} = value; + const formattedSettings: FormattedSettings = { + ...rest, + ignore_local_pin: ignoreLocalPin, + release_channel: releaseChannel, + }; + return TOML.stringify(formattedSettings as JsonMap); +} + +function deserialize(content: string): LauncherSettings { + const {ignore_local_pin, release_channel, ...rest} = TOML.parse( + content, + ) as FormattedSettings; + return { + ...rest, + ignoreLocalPin: !!ignore_local_pin, + releaseChannel: release_channel ?? ReleaseChannel.DEFAULT, + }; +} + +export async function loadLauncherSettings(): Promise { + const fileName = getLauncherSettingsFile(); + try { + const content = (await fs.readFile(fileName)).toString(); + return deserialize(content); + } catch (e) { + console.warn( + `Failed to read settings file: "${fileName}". ${e}. Replacing file with default settings.`, + ); + await saveLauncherSettings(defaultLauncherSettings); + return defaultLauncherSettings; + } +} + +export async function saveLauncherSettings(settings: LauncherSettings) { + const fileName = getLauncherSettingsFile(); + const dir = path.dirname(fileName); + const exists = await fs.pathExists(dir); + if (!exists) { + await fs.mkdir(dir, {recursive: true}); + } + const content = serialize(settings); + return fs.writeFile(fileName, content); +} diff --git a/desktop/flipper-server-core/src/utils/processConfig.tsx b/desktop/flipper-server-core/src/utils/processConfig.tsx new file mode 100644 index 000000000..ba01e7246 --- /dev/null +++ b/desktop/flipper-server-core/src/utils/processConfig.tsx @@ -0,0 +1,22 @@ +/** + * 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 {ProcessConfig} from 'flipper-common'; + +export function loadProcessConfig(env: NodeJS.ProcessEnv): ProcessConfig { + const json = JSON.parse(env.CONFIG || '{}'); + return { + disabledPlugins: new Set(json.disabledPlugins || []), + lastWindowPosition: json.lastWindowPosition, + launcherMsg: json.launcherMsg, + screenCapturePath: json.screenCapturePath, + launcherEnabled: + typeof json.launcherEnabled === 'boolean' ? json.launcherEnabled : true, + }; +} diff --git a/desktop/flipper-server-core/src/utils/settings.tsx b/desktop/flipper-server-core/src/utils/settings.tsx new file mode 100644 index 000000000..37c90ca68 --- /dev/null +++ b/desktop/flipper-server-core/src/utils/settings.tsx @@ -0,0 +1,67 @@ +/** + * 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 os from 'os'; +import {resolve} from 'path'; +import xdg from 'xdg-basedir'; +import {Settings, Tristate} from 'flipper-common'; +import {readFile, writeFile, access} from 'fs-extra'; + +export async function loadSettings(): Promise { + if (!access(getSettingsFile())) { + return getDefaultSettings(); + } + const json = await readFile(getSettingsFile(), {encoding: 'utf8'}); + return JSON.parse(json); +} + +export async function saveSettings(settings: Settings): Promise { + await writeFile(getSettingsFile(), JSON.stringify(settings, null, 2), { + encoding: 'utf8', + }); +} + +function getSettingsFile() { + return resolve( + ...(xdg.config ? [xdg.config] : [os.homedir(), '.config']), + 'flipper', + 'settings.json', + ); +} + +export const DEFAULT_ANDROID_SDK_PATH = getDefaultAndroidSdkPath(); + +function getDefaultSettings(): Settings { + return { + androidHome: getDefaultAndroidSdkPath(), + enableAndroid: true, + enableIOS: os.platform() === 'darwin', + enablePhysicalIOS: os.platform() === 'darwin', + enablePrefetching: Tristate.Unset, + idbPath: '/usr/local/bin/idb', + reactNative: { + shortcuts: { + enabled: false, + reload: 'Alt+Shift+R', + openDevMenu: 'Alt+Shift+D', + }, + }, + darkMode: 'light', + showWelcomeAtStartup: true, + suppressPluginErrors: false, + }; +} + +function getDefaultAndroidSdkPath() { + return os.platform() === 'win32' ? getWindowsSdkPath() : '/opt/android_sdk'; +} + +function getWindowsSdkPath() { + return `${os.homedir()}\\AppData\\Local\\android\\sdk`; +} diff --git a/desktop/flipper-ui-core/package.json b/desktop/flipper-ui-core/package.json index 4760c3372..431d2dbc9 100644 --- a/desktop/flipper-ui-core/package.json +++ b/desktop/flipper-ui-core/package.json @@ -14,7 +14,6 @@ "@emotion/css": "^11.5.0", "@emotion/react": "^11.6.0", "@emotion/styled": "^11.6.0", - "@iarna/toml": "^2.2.5", "@tanishiking/aho-corasick": "^0.0.1", "@types/archiver": "^5.1.1", "@types/uuid": "^8.3.1", @@ -67,8 +66,7 @@ "tmp": "^0.2.1", "uuid": "^8.3.2", "which": "^2.0.1", - "ws": "^8.2.3", - "xdg-basedir": "^4.0.0" + "ws": "^8.2.3" }, "optionalDependencies": { "7zip-bin-mac": "^1.0.1" diff --git a/desktop/flipper-ui-core/src/RenderHost.tsx b/desktop/flipper-ui-core/src/RenderHost.tsx index 63bf118f5..5b864791c 100644 --- a/desktop/flipper-ui-core/src/RenderHost.tsx +++ b/desktop/flipper-ui-core/src/RenderHost.tsx @@ -10,18 +10,11 @@ import type {NotificationEvents} from './dispatcher/notifications'; import type {PluginNotification} from './reducers/notifications'; import type {NotificationConstructorOptions} from 'electron'; -import {FlipperLib, TestUtils} from 'flipper-plugin'; -import path from 'path'; -import {FlipperServer, Logger} from 'flipper-common'; - -type ENVIRONMENT_VARIABLES = 'NODE_ENV' | 'DEV_SERVER_URL' | 'CONFIG'; -type ENVIRONMENT_PATHS = - | 'appPath' - | 'homePath' - | 'execPath' - | 'staticPath' - | 'tempPath' - | 'desktopPath'; +import {FlipperLib} from 'flipper-plugin'; +import {FlipperServerConfig, ReleaseChannel, Tristate} from 'flipper-common'; +// TODO: those imports are only used for testing, require conditionally? +import {tmpdir} from 'os'; +import {resolve} from 'path'; // Events that are emitted from the main.ts ovr the IPC process bridge in Electron type MainProcessEvents = { @@ -103,20 +96,10 @@ export interface RenderHost { ): void; shouldUseDarkColors(): boolean; restartFlipper(update?: boolean): void; - env: Partial>; - paths: Record; openLink(url: string): void; loadDefaultPlugins(): Record; - startFlipperServer(config: { - // TODO: this config is temporarily, settings should be loaded/stored by server, not client - logger: Logger; - enableAndroid: boolean; - androidHome: string; - enableIOS: boolean; - enablePhysicalIOS: boolean; - idbPath: string; - validWebSocketOrigins: string[]; - }): FlipperServer; + GK(gatekeeper: string): boolean; + serverConfig: FlipperServerConfig; } export function getRenderHostInstance(): RenderHost { @@ -127,6 +110,50 @@ export function getRenderHostInstance(): RenderHost { } if (process.env.NODE_ENV === 'test') { + const rootPath = resolve(__dirname, '..', '..'); + const stubConfig: FlipperServerConfig = { + env: {...process.env}, + gatekeepers: { + TEST_PASSING_GK: true, + TEST_FAILING_GK: false, + }, + isProduction: false, + launcherSettings: { + ignoreLocalPin: false, + releaseChannel: ReleaseChannel.DEFAULT, + }, + paths: { + appPath: rootPath, + desktopPath: `/dev/null`, + execPath: process.execPath, + homePath: `/dev/null`, + staticPath: resolve(rootPath, 'static'), + tempPath: tmpdir(), + }, + processConfig: { + disabledPlugins: new Set(), + lastWindowPosition: null, + launcherEnabled: false, + launcherMsg: null, + screenCapturePath: `/dev/null`, + }, + settings: { + androidHome: `/dev/null`, + darkMode: 'light', + enableAndroid: false, + enableIOS: false, + enablePhysicalIOS: false, + enablePrefetching: Tristate.False, + idbPath: `/dev/null`, + reactNative: { + shortcuts: {enabled: false, openDevMenu: '', reload: ''}, + }, + showWelcomeAtStartup: false, + suppressPluginErrors: false, + }, + validWebSocketOrigins: [], + }; + window.FlipperRenderHostInstance = { processId: -1, isProduction: false, @@ -153,18 +180,12 @@ if (process.env.NODE_ENV === 'test') { }, 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/`, - }, + serverConfig: stubConfig, loadDefaultPlugins() { return {}; }, - startFlipperServer: () => TestUtils.createFlipperServerMock(), + GK(gk) { + return stubConfig.gatekeepers[gk] ?? false; + }, }; } diff --git a/desktop/flipper-ui-core/src/__tests__/__snapshots__/createMockFlipperWithPlugin.node.tsx.snap b/desktop/flipper-ui-core/src/__tests__/__snapshots__/createMockFlipperWithPlugin.node.tsx.snap index 2cd6f85e5..1cc7fdeb8 100644 --- a/desktop/flipper-ui-core/src/__tests__/__snapshots__/createMockFlipperWithPlugin.node.tsx.snap +++ b/desktop/flipper-ui-core/src/__tests__/__snapshots__/createMockFlipperWithPlugin.node.tsx.snap @@ -37,10 +37,10 @@ Object { }, "flipperServer": Object { "close": [MockFunction], + "connect": [Function], "exec": [MockFunction], "off": [MockFunction], "on": [MockFunction], - "start": [Function], }, "pluginMenuEntries": Array [], "selectedAppId": "TestApp#Android#MockAndroidDevice#serial", diff --git a/desktop/flipper-ui-core/src/chrome/PlatformSelectWizard.tsx b/desktop/flipper-ui-core/src/chrome/PlatformSelectWizard.tsx index a128b45ae..100c929fe 100644 --- a/desktop/flipper-ui-core/src/chrome/PlatformSelectWizard.tsx +++ b/desktop/flipper-ui-core/src/chrome/PlatformSelectWizard.tsx @@ -11,11 +11,10 @@ import React, {Component} from 'react'; import {updateSettings, Action} from '../reducers/settings'; import {connect} from 'react-redux'; import {State as Store} from '../reducers'; -import {Settings} from '../reducers/settings'; import {flush} from '../utils/persistor'; import ToggledSection from './settings/ToggledSection'; import {isEqual} from 'lodash'; -import {reportUsage} from 'flipper-common'; +import {reportUsage, Settings} from 'flipper-common'; import {Modal, Button} from 'antd'; import {Layout, withTrackingScope, _NuxManagerContext} from 'flipper-plugin'; import {getRenderHostInstance} from '../RenderHost'; diff --git a/desktop/flipper-ui-core/src/chrome/RatingButton.tsx b/desktop/flipper-ui-core/src/chrome/RatingButton.tsx index 16173967a..ff3a321b6 100644 --- a/desktop/flipper-ui-core/src/chrome/RatingButton.tsx +++ b/desktop/flipper-ui-core/src/chrome/RatingButton.tsx @@ -24,7 +24,6 @@ import { Link, } from '../ui'; import {LeftRailButton} from '../sandy-chrome/LeftRail'; -import GK from '../fb-stubs/GK'; import * as UserFeedback from '../fb-stubs/UserFeedback'; import {FeedbackPrompt} from '../fb-stubs/UserFeedback'; import {StarOutlined} from '@ant-design/icons'; @@ -33,6 +32,7 @@ import {useStore} from '../utils/useStore'; import {isLoggedIn} from '../fb-stubs/user'; import {useValue} from 'flipper-plugin'; import {reportPlatformFailures} from 'flipper-common'; +import {getRenderHostInstance} from '../RenderHost'; type NextAction = 'select-rating' | 'leave-comment' | 'finished'; @@ -281,7 +281,11 @@ export function SandyRatingButton() { }, [hasTriggered]); useEffect(() => { - if (GK.get('flipper_enable_star_ratiings') && !hasTriggered && loggedIn) { + if ( + getRenderHostInstance().GK('flipper_enable_star_ratiings') && + !hasTriggered && + loggedIn + ) { reportPlatformFailures( UserFeedback.getPrompt().then((prompt) => { setPromptData(prompt); diff --git a/desktop/flipper-ui-core/src/chrome/SettingsSheet.tsx b/desktop/flipper-ui-core/src/chrome/SettingsSheet.tsx index e308cbda6..c6258ceb6 100644 --- a/desktop/flipper-ui-core/src/chrome/SettingsSheet.tsx +++ b/desktop/flipper-ui-core/src/chrome/SettingsSheet.tsx @@ -12,22 +12,21 @@ import {Radio} from 'antd'; import {updateSettings, Action} from '../reducers/settings'; import { Action as LauncherAction, - LauncherSettings, updateLauncherSettings, } from '../reducers/launcherSettings'; import {connect} from 'react-redux'; import {State as Store} from '../reducers'; -import {Settings, DEFAULT_ANDROID_SDK_PATH} from '../reducers/settings'; import {flush} from '../utils/persistor'; import ToggledSection from './settings/ToggledSection'; import {FilePathConfigField, ConfigText} from './settings/configFields'; import KeyboardShortcutInput from './settings/KeyboardShortcutInput'; import {isEqual, isMatch, isEmpty} from 'lodash'; import LauncherSettingsPanel from '../fb-stubs/LauncherSettingsPanel'; -import {reportUsage} from 'flipper-common'; +import {LauncherSettings, reportUsage, Settings} from 'flipper-common'; import {Modal, message, Button} from 'antd'; import {Layout, withTrackingScope, _NuxManagerContext} from 'flipper-plugin'; import {getRenderHostInstance} from '../RenderHost'; +import {loadTheme} from '../utils/loadTheme'; type OwnProps = { onHide: () => void; @@ -142,7 +141,9 @@ class SettingsSheet extends Component { }}> { this.setState({ @@ -255,6 +256,7 @@ class SettingsSheet extends Component { darkMode: event.target.value, }, })); + loadTheme(event.target.value); }}> Dark Light diff --git a/desktop/flipper-ui-core/src/chrome/UpdateIndicator.tsx b/desktop/flipper-ui-core/src/chrome/UpdateIndicator.tsx index f67c6ac34..36e9d0100 100644 --- a/desktop/flipper-ui-core/src/chrome/UpdateIndicator.tsx +++ b/desktop/flipper-ui-core/src/chrome/UpdateIndicator.tsx @@ -9,13 +9,12 @@ import {notification, Typography} from 'antd'; import isProduction from '../utils/isProduction'; -import {reportPlatformFailures} from 'flipper-common'; +import {reportPlatformFailures, ReleaseChannel} from 'flipper-common'; import React, {useEffect, useState} from 'react'; import fbConfig from '../fb-stubs/config'; import {useStore} from '../utils/useStore'; import {getAppVersion} from '../utils/info'; import {checkForUpdate} from '../fb-stubs/checkForUpdate'; -import ReleaseChannel from '../ReleaseChannel'; export type VersionCheckResult = | { diff --git a/desktop/flipper-ui-core/src/devices/ArchivedDevice.tsx b/desktop/flipper-ui-core/src/devices/ArchivedDevice.tsx index 74b668485..db86be368 100644 --- a/desktop/flipper-ui-core/src/devices/ArchivedDevice.tsx +++ b/desktop/flipper-ui-core/src/devices/ArchivedDevice.tsx @@ -26,7 +26,7 @@ export default class ArchivedDevice extends BaseDevice { }) { super( { - async start() {}, + async connect() {}, close() {}, exec(command, ..._args: any[]) { throw new Error( diff --git a/desktop/flipper-ui-core/src/dispatcher/__tests__/plugins.node.tsx b/desktop/flipper-ui-core/src/dispatcher/__tests__/plugins.node.tsx index 5732e9dfc..74ed87930 100644 --- a/desktop/flipper-ui-core/src/dispatcher/__tests__/plugins.node.tsx +++ b/desktop/flipper-ui-core/src/dispatcher/__tests__/plugins.node.tsx @@ -21,12 +21,11 @@ import path from 'path'; import {createRootReducer, State} from '../../reducers/index'; import {getLogger} from 'flipper-common'; import configureStore from 'redux-mock-store'; -import {TEST_PASSING_GK, TEST_FAILING_GK} from '../../fb-stubs/GK'; import TestPlugin from './TestPlugin'; -import {resetConfigForTesting} from '../../utils/processConfig'; import {_SandyPluginDefinition} from 'flipper-plugin'; import {mocked} from 'ts-jest/utils'; import loadDynamicPlugins from '../../utils/loadDynamicPlugins'; +import {getRenderHostInstance} from '../../RenderHost'; const loadDynamicPluginsMock = mocked(loadDynamicPlugins); @@ -57,7 +56,6 @@ const sampleBundledPluginDetails: BundledPluginDetails = { }; beforeEach(() => { - resetConfigForTesting(); loadDynamicPluginsMock.mockResolvedValue([]); }); @@ -80,10 +78,13 @@ test('getDynamicPlugins returns empty array on errors', async () => { test('checkDisabled', () => { const disabledPlugin = 'pluginName'; - const config = {disabledPlugins: [disabledPlugin]}; - const orig = process.env.CONFIG; + const hostConfig = getRenderHostInstance().serverConfig; + const orig = hostConfig.processConfig; try { - process.env.CONFIG = JSON.stringify(config); + hostConfig.processConfig = { + ...orig, + disabledPlugins: new Set([disabledPlugin]), + }; const disabled = checkDisabled([]); expect( @@ -93,7 +94,6 @@ test('checkDisabled', () => { version: '1.0.0', }), ).toBeTruthy(); - expect( disabled({ ...sampleBundledPluginDetails, @@ -102,7 +102,7 @@ test('checkDisabled', () => { }), ).toBeFalsy(); } finally { - process.env.CONFIG = orig; + hostConfig.processConfig = orig; } }); @@ -121,7 +121,7 @@ test('checkGK for passing plugin', () => { checkGK([])({ ...sampleBundledPluginDetails, name: 'pluginID', - gatekeeper: TEST_PASSING_GK, + gatekeeper: 'TEST_PASSING_GK', version: '1.0.0', }), ).toBeTruthy(); @@ -133,7 +133,7 @@ test('checkGK for failing plugin', () => { const plugins = checkGK(gatekeepedPlugins)({ ...sampleBundledPluginDetails, name, - gatekeeper: TEST_FAILING_GK, + gatekeeper: 'TEST_FAILING_GK', version: '1.0.0', }); diff --git a/desktop/flipper-ui-core/src/dispatcher/flipperServer.tsx b/desktop/flipper-ui-core/src/dispatcher/flipperServer.tsx index a8bdb5cf8..97e38b843 100644 --- a/desktop/flipper-ui-core/src/dispatcher/flipperServer.tsx +++ b/desktop/flipper-ui-core/src/dispatcher/flipperServer.tsx @@ -13,6 +13,7 @@ import { FlipperServer, Logger, NoLongerConnectedToClientError, + isTest, } from 'flipper-common'; import Client from '../Client'; import {notification} from 'antd'; @@ -20,23 +21,12 @@ import BaseDevice from '../devices/BaseDevice'; import {ClientDescription, timeout} from 'flipper-common'; import {reportPlatformFailures} from 'flipper-common'; import {sideEffect} from '../utils/sideEffect'; -import constants from '../fb-stubs/constants'; -import {getRenderHostInstance} from '../RenderHost'; - -export default async (store: Store, logger: Logger) => { - const {enableAndroid, androidHome, idbPath, enableIOS, enablePhysicalIOS} = - store.getState().settingsState; - - const server = getRenderHostInstance().startFlipperServer({ - logger, - enableAndroid, - androidHome, - idbPath, - enableIOS, - enablePhysicalIOS, - validWebSocketOrigins: constants.VALID_WEB_SOCKET_REQUEST_ORIGIN_PREFIXES, - }); +export function connectFlipperServerToStore( + server: FlipperServer, + store: Store, + logger: Logger, +) { store.dispatch({ type: 'SET_FLIPPER_SERVER', payload: server, @@ -147,25 +137,55 @@ export default async (store: Store, logger: Logger) => { }); } - server - .start() - .then(() => { - console.log( - 'Flipper server started and accepting device / client connections', - ); - }) - .catch((e) => { - console.error('Failed to start Flipper server', e); - notification.error({ - message: 'Failed to start Flipper server', - description: 'error: ' + e, - }); - }); + let sideEffectDisposer: undefined | (() => void); + + if (!isTest()) { + sideEffectDisposer = startSideEffects(store, server); + } + console.log( + 'Flipper server started and accepting device / client connections', + ); return () => { + sideEffectDisposer?.(); server.close(); }; -}; +} + +function startSideEffects(store: Store, server: FlipperServer) { + const dispose1 = sideEffect( + store, + { + name: 'settingsPersistor', + throttleMs: 100, + }, + (state) => state.settingsState, + (settings) => { + server.exec('persist-settings', settings).catch((e) => { + console.error('Failed to persist Flipper settings', e); + }); + }, + ); + + const dispose2 = sideEffect( + store, + { + name: 'launcherSettingsPersistor', + throttleMs: 100, + }, + (state) => state.launcherSettingsState, + (settings) => { + server.exec('persist-launcher-settings', settings).catch((e) => { + console.error('Failed to persist launcher settings', e); + }); + }, + ); + + return () => { + dispose1(); + dispose2(); + }; +} export async function handleClientConnected( server: Pick, diff --git a/desktop/flipper-ui-core/src/dispatcher/index.tsx b/desktop/flipper-ui-core/src/dispatcher/index.tsx index e287cc05f..28a364214 100644 --- a/desktop/flipper-ui-core/src/dispatcher/index.tsx +++ b/desktop/flipper-ui-core/src/dispatcher/index.tsx @@ -8,7 +8,6 @@ */ // Used responsibly. -import flipperServer from './flipperServer'; import application from './application'; import tracking from './tracking'; import notifications from './notifications'; @@ -32,7 +31,6 @@ export default function (store: Store, logger: Logger): () => Promise { const dispatchers: Array = [ application, tracking, - flipperServer, notifications, plugins, user, diff --git a/desktop/flipper-ui-core/src/dispatcher/plugins.tsx b/desktop/flipper-ui-core/src/dispatcher/plugins.tsx index 14896a950..12caf76b1 100644 --- a/desktop/flipper-ui-core/src/dispatcher/plugins.tsx +++ b/desktop/flipper-ui-core/src/dispatcher/plugins.tsx @@ -24,11 +24,9 @@ import { MarketplacePluginDetails, pluginsInitialized, } from '../reducers/plugins'; -import GK from '../fb-stubs/GK'; import {FlipperBasePlugin} from '../plugin'; import fs from 'fs-extra'; import path from 'path'; -import {default as config} from '../utils/processConfig'; import {notNull} from '../utils/typeUtils'; import { ActivatablePluginDetails, @@ -151,6 +149,9 @@ export function getLatestCompatibleVersionOfEachPlugin< } async function getBundledPlugins(): Promise> { + if (process.env.NODE_ENV === 'test') { + return []; + } // defaultPlugins that are included in the Flipper distributive. // List of default bundled plugins is written at build time to defaultPlugins/bundled.json. const pluginPath = getStaticPath( @@ -183,7 +184,7 @@ export const checkGK = if (!plugin.gatekeeper) { return true; } - const result = GK.get(plugin.gatekeeper); + const result = getRenderHostInstance().GK(plugin.gatekeeper); if (!result) { gatekeepedPlugins.push(plugin); } @@ -197,15 +198,16 @@ export const checkGK = export const checkDisabled = ( disabledPlugins: Array, ) => { + const config = getRenderHostInstance().serverConfig; let enabledList: Set | null = null; let disabledList: Set = new Set(); try { - if (process.env.FLIPPER_ENABLED_PLUGINS) { + if (config.env.FLIPPER_ENABLED_PLUGINS) { enabledList = new Set( - process.env.FLIPPER_ENABLED_PLUGINS.split(','), + config.env.FLIPPER_ENABLED_PLUGINS.split(','), ); } - disabledList = config().disabledPlugins; + disabledList = config.processConfig.disabledPlugins; } catch (e) { console.error('Failed to compute enabled/disabled plugins', e); } diff --git a/desktop/flipper-ui-core/src/dispatcher/reactNative.tsx b/desktop/flipper-ui-core/src/dispatcher/reactNative.tsx index ed5ab2f9d..4cf223207 100644 --- a/desktop/flipper-ui-core/src/dispatcher/reactNative.tsx +++ b/desktop/flipper-ui-core/src/dispatcher/reactNative.tsx @@ -21,7 +21,7 @@ export default (store: Store) => { const settings = store.getState().settingsState.reactNative; const renderHost = getRenderHostInstance(); - if (!settings.shortcuts.enabled) { + if (!settings?.shortcuts.enabled) { return; } diff --git a/desktop/flipper-ui-core/src/fb-stubs/LauncherSettingsPanel.tsx b/desktop/flipper-ui-core/src/fb-stubs/LauncherSettingsPanel.tsx index 2873603a4..006f6dadf 100644 --- a/desktop/flipper-ui-core/src/fb-stubs/LauncherSettingsPanel.tsx +++ b/desktop/flipper-ui-core/src/fb-stubs/LauncherSettingsPanel.tsx @@ -7,8 +7,7 @@ * @format */ -import {Tristate} from '../reducers/settings'; -import ReleaseChannel from '../ReleaseChannel'; +import {Tristate, ReleaseChannel} from 'flipper-common'; export default function (_props: { isPrefetchingEnabled: Tristate; diff --git a/desktop/flipper-ui-core/src/fb-stubs/Prefetcher.tsx b/desktop/flipper-ui-core/src/fb-stubs/Prefetcher.tsx deleted file mode 100644 index a9cd824c1..000000000 --- a/desktop/flipper-ui-core/src/fb-stubs/Prefetcher.tsx +++ /dev/null @@ -1,13 +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 - */ - -import {Settings} from '../reducers/settings'; - -export default async function setupPrefetcher(_settings: Settings) {} -export const shouldInstallPrefetcher = () => false; diff --git a/desktop/flipper-ui-core/src/fb-stubs/config.tsx b/desktop/flipper-ui-core/src/fb-stubs/config.tsx index dcbf058ad..9f1234b56 100644 --- a/desktop/flipper-ui-core/src/fb-stubs/config.tsx +++ b/desktop/flipper-ui-core/src/fb-stubs/config.tsx @@ -7,7 +7,7 @@ * @format */ -import ReleaseChannel from '../ReleaseChannel'; +import {ReleaseChannel} from 'flipper-common'; export default { updateServer: 'https://www.facebook.com/fbflipper/public/latest.json', diff --git a/desktop/flipper-ui-core/src/fb-stubs/constants.tsx b/desktop/flipper-ui-core/src/fb-stubs/constants.tsx index be2786a2f..2c7e688e7 100644 --- a/desktop/flipper-ui-core/src/fb-stubs/constants.tsx +++ b/desktop/flipper-ui-core/src/fb-stubs/constants.tsx @@ -42,12 +42,4 @@ export default Object.freeze({ }, SUPPORT_GROUPS: [], - - // Only WebSocket requests from the following origin prefixes will be accepted - VALID_WEB_SOCKET_REQUEST_ORIGIN_PREFIXES: [ - 'chrome-extension://', - 'localhost:', - 'http://localhost:', - 'app://', - ], }); diff --git a/desktop/flipper-ui-core/src/global.ts b/desktop/flipper-ui-core/src/global.ts index 1bc85b671..188b501f3 100644 --- a/desktop/flipper-ui-core/src/global.ts +++ b/desktop/flipper-ui-core/src/global.ts @@ -8,7 +8,6 @@ */ import {StoreEnhancerStoreCreator} from 'redux'; -import {Store} from './reducers'; import {RenderHost} from './RenderHost'; declare global { @@ -17,8 +16,6 @@ declare global { } interface Window { - flipperGlobalStoreDispatch: Store['dispatch']; - __REDUX_DEVTOOLS_EXTENSION__: | undefined | (StoreEnhancerStoreCreator & StoreEnhancerStateSanitizer); diff --git a/desktop/flipper-ui-core/src/plugin.tsx b/desktop/flipper-ui-core/src/plugin.tsx index 298025fa7..023ab9df8 100644 --- a/desktop/flipper-ui-core/src/plugin.tsx +++ b/desktop/flipper-ui-core/src/plugin.tsx @@ -7,7 +7,7 @@ * @format */ -import {Logger} from 'flipper-common'; +import {Logger, Settings} from 'flipper-common'; import Client from './Client'; import {Component} from 'react'; import BaseDevice from './devices/BaseDevice'; @@ -15,7 +15,6 @@ import {StaticView} from './reducers/connections'; import {State as ReduxState} from './reducers'; import {DEFAULT_MAX_QUEUE_SIZE} from './reducers/pluginMessageQueue'; import {ActivatablePluginDetails} from 'flipper-plugin-lib'; -import {Settings} from './reducers/settings'; import { Notification, Idler, diff --git a/desktop/flipper-ui-core/src/reducers/__tests__/settings.node.tsx b/desktop/flipper-ui-core/src/reducers/__tests__/settings.node.tsx index 02c20c2c6..8df1b3f2a 100644 --- a/desktop/flipper-ui-core/src/reducers/__tests__/settings.node.tsx +++ b/desktop/flipper-ui-core/src/reducers/__tests__/settings.node.tsx @@ -7,30 +7,17 @@ * @format */ -import {default as reducer, updateSettings, Tristate} from '../settings'; - -test('init', () => { - const res = reducer(undefined, {type: 'INIT'}); - expect(res.enableAndroid).toBeTruthy(); -}); +import {default as reducer, updateSettings} from '../settings'; +import {Tristate} from 'flipper-common'; test('updateSettings', () => { const initialSettings = reducer(undefined, {type: 'INIT'}); const updatedSettings = Object.assign(initialSettings, { enableAndroid: false, enablePrefetching: Tristate.True, - jsApps: { - webAppLauncher: { - height: 900, - }, - }, }); const res = reducer(initialSettings, updateSettings(updatedSettings)); expect(res.enableAndroid).toBeFalsy(); expect(res.enablePrefetching).toEqual(Tristate.True); - expect(res.jsApps.webAppLauncher.height).toEqual(900); - expect(res.jsApps.webAppLauncher.width).toEqual( - initialSettings.jsApps.webAppLauncher.width, - ); }); diff --git a/desktop/flipper-ui-core/src/reducers/index.tsx b/desktop/flipper-ui-core/src/reducers/index.tsx index e58945988..64feea21b 100644 --- a/desktop/flipper-ui-core/src/reducers/index.tsx +++ b/desktop/flipper-ui-core/src/reducers/index.tsx @@ -36,12 +36,8 @@ import supportForm, { State as SupportFormState, Action as SupportFormAction, } from './supportForm'; -import settings, { - Settings as SettingsState, - Action as SettingsAction, -} from './settings'; +import settings, {Action as SettingsAction} from './settings'; import launcherSettings, { - LauncherSettings as LauncherSettingsState, Action as LauncherSettingsAction, } from './launcherSettings'; import pluginManager, { @@ -61,18 +57,13 @@ import usageTracking, { State as TrackingState, } from './usageTracking'; import user, {State as UserState, Action as UserAction} from './user'; -import JsonFileStorage from '../utils/jsonFileReduxPersistStorage'; -import LauncherSettingsStorage from '../utils/launcherSettingsStorage'; -import {launcherConfigDir} from '../utils/launcher'; -import os from 'os'; -import {resolve} from 'path'; -import xdg from 'xdg-basedir'; import {createMigrate, createTransform, persistReducer} from 'redux-persist'; import {PersistPartial} from 'redux-persist/es/persistReducer'; import {Store as ReduxStore, MiddlewareAPI as ReduxMiddlewareAPI} from 'redux'; import storage from 'redux-persist/lib/storage'; import {TransformConfig} from 'redux-persist/es/createTransform'; +import {LauncherSettings, Settings} from 'flipper-common'; export type Actions = | ApplicationAction @@ -97,8 +88,8 @@ export type State = { notifications: NotificationsState & PersistPartial; plugins: PluginsState & PersistPartial; user: UserState & PersistPartial; - settingsState: SettingsState & PersistPartial; - launcherSettingsState: LauncherSettingsState & PersistPartial; + settingsState: Settings; + launcherSettingsState: LauncherSettings; supportForm: SupportFormState; pluginManager: PluginManagerState; healthchecks: HealthcheckState & PersistPartial; @@ -109,14 +100,6 @@ export type State = { export type Store = ReduxStore; export type MiddlewareAPI = ReduxMiddlewareAPI, State>; -const settingsStorage = new JsonFileStorage( - resolve( - ...(xdg.config ? [xdg.config] : [os.homedir(), '.config']), - 'flipper', - 'settings.json', - ), -); - const setTransformer = (config: TransformConfig) => createTransform( (set: Set) => Array.from(set), @@ -124,10 +107,6 @@ const setTransformer = (config: TransformConfig) => config, ); -const launcherSettingsStorage = new LauncherSettingsStorage( - resolve(launcherConfigDir(), 'flipper-launcher.toml'), -); - export function createRootReducer() { return combineReducers({ application, @@ -181,20 +160,8 @@ export function createRootReducer() { }, user, ), - settingsState: persistReducer( - {key: 'settings', storage: settingsStorage}, - settings, - ), - launcherSettingsState: persistReducer( - { - key: 'launcherSettings', - storage: launcherSettingsStorage, - serialize: false, - // @ts-ignore: property is erroneously missing in redux-persist type definitions - deserialize: false, - }, - launcherSettings, - ), + settingsState: settings, + launcherSettingsState: launcherSettings, healthchecks: persistReducer( { key: 'healthchecks', diff --git a/desktop/flipper-ui-core/src/reducers/launcherSettings.tsx b/desktop/flipper-ui-core/src/reducers/launcherSettings.tsx index 33451a028..a11c2d668 100644 --- a/desktop/flipper-ui-core/src/reducers/launcherSettings.tsx +++ b/desktop/flipper-ui-core/src/reducers/launcherSettings.tsx @@ -7,26 +7,18 @@ * @format */ +import {LauncherSettings} from 'flipper-common'; +import {getRenderHostInstance} from '../RenderHost'; import {Actions} from './index'; -import ReleaseChannel from '../ReleaseChannel'; - -export type LauncherSettings = { - releaseChannel: ReleaseChannel; - ignoreLocalPin: boolean; -}; export type Action = { type: 'UPDATE_LAUNCHER_SETTINGS'; payload: LauncherSettings; }; -export const defaultLauncherSettings: LauncherSettings = { - releaseChannel: ReleaseChannel.DEFAULT, - ignoreLocalPin: false, -}; - export default function reducer( - state: LauncherSettings = defaultLauncherSettings, + state: LauncherSettings = getRenderHostInstance().serverConfig + .launcherSettings, action: Actions, ): LauncherSettings { if (action.type === 'UPDATE_LAUNCHER_SETTINGS') { diff --git a/desktop/flipper-ui-core/src/reducers/settings.tsx b/desktop/flipper-ui-core/src/reducers/settings.tsx index 29944fd81..80f379ae5 100644 --- a/desktop/flipper-ui-core/src/reducers/settings.tsx +++ b/desktop/flipper-ui-core/src/reducers/settings.tsx @@ -8,45 +8,8 @@ */ import {Actions} from './index'; -import os from 'os'; import {getRenderHostInstance} from '../RenderHost'; - -export enum Tristate { - True, - False, - Unset, -} - -export type Settings = { - androidHome: string; - enableAndroid: boolean; - enableIOS: boolean; - enablePhysicalIOS: boolean; - /** - * If unset, this will assume the value of the GK setting. - * Note that this setting has no effect in the open source version - * of Flipper. - */ - enablePrefetching: Tristate; - idbPath: string; - jsApps: { - webAppLauncher: { - url: string; - height: number; - width: number; - }; - }; - reactNative: { - shortcuts: { - enabled: boolean; - reload: string; - openDevMenu: string; - }; - }; - darkMode: 'dark' | 'light' | 'system'; - showWelcomeAtStartup: boolean; - suppressPluginErrors: boolean; -}; +import {Settings} from 'flipper-common'; export type Action = | {type: 'INIT'} @@ -55,36 +18,8 @@ export type Action = payload: Settings; }; -export const DEFAULT_ANDROID_SDK_PATH = getDefaultAndroidSdkPath(); - -const initialState: Settings = { - androidHome: getDefaultAndroidSdkPath(), - enableAndroid: true, - enableIOS: os.platform() === 'darwin', - enablePhysicalIOS: os.platform() === 'darwin', - enablePrefetching: Tristate.Unset, - idbPath: '/usr/local/bin/idb', - jsApps: { - webAppLauncher: { - url: 'http://localhost:8888', - height: 600, - width: 800, - }, - }, - reactNative: { - shortcuts: { - enabled: false, - reload: 'Alt+Shift+R', - openDevMenu: 'Alt+Shift+D', - }, - }, - darkMode: 'light', - showWelcomeAtStartup: true, - suppressPluginErrors: false, -}; - export default function reducer( - state: Settings = initialState, + state: Settings = getRenderHostInstance().serverConfig.settings, action: Actions, ): Settings { if (action.type === 'UPDATE_SETTINGS') { @@ -99,13 +34,3 @@ export function updateSettings(settings: Settings): Action { payload: settings, }; } - -function getDefaultAndroidSdkPath() { - return os.platform() === 'win32' ? getWindowsSdkPath() : '/opt/android_sdk'; -} - -function getWindowsSdkPath() { - return `${ - getRenderHostInstance().paths.homePath - }\\AppData\\Local\\android\\sdk`; -} diff --git a/desktop/flipper-ui-core/src/sandy-chrome/WelcomeScreen.tsx b/desktop/flipper-ui-core/src/sandy-chrome/WelcomeScreen.tsx index 592807b82..2952db3a4 100644 --- a/desktop/flipper-ui-core/src/sandy-chrome/WelcomeScreen.tsx +++ b/desktop/flipper-ui-core/src/sandy-chrome/WelcomeScreen.tsx @@ -32,9 +32,9 @@ import constants from '../fb-stubs/constants'; import config from '../fb-stubs/config'; import isProduction from '../utils/isProduction'; import {getAppVersion} from '../utils/info'; -import ReleaseChannel from '../ReleaseChannel'; import {getFlipperLib} from 'flipper-plugin'; import ChangelogSheet from '../chrome/ChangelogSheet'; +import {ReleaseChannel} from 'flipper-common'; const RowContainer = styled(FlexRow)({ alignItems: 'flex-start', diff --git a/desktop/flipper-ui-core/src/sandy-chrome/appinspect/AppSelector.tsx b/desktop/flipper-ui-core/src/sandy-chrome/appinspect/AppSelector.tsx index 5bfbb76cf..00144e218 100644 --- a/desktop/flipper-ui-core/src/sandy-chrome/appinspect/AppSelector.tsx +++ b/desktop/flipper-ui-core/src/sandy-chrome/appinspect/AppSelector.tsx @@ -30,8 +30,8 @@ import Client from '../../Client'; import {State} from '../../reducers'; import {brandColors, brandIcons, colors} from '../../ui/components/colors'; import {TroubleshootingGuide} from './fb-stubs/TroubleshootingGuide'; -import GK from '../../fb-stubs/GK'; import {getSelectableDevices} from '../../selectors/connections'; +import {getRenderHostInstance} from '../../RenderHost'; const {Text} = Typography; @@ -127,7 +127,7 @@ export function AppSelector() { )} diff --git a/desktop/flipper-ui-core/src/startFlipperDesktop.tsx b/desktop/flipper-ui-core/src/startFlipperDesktop.tsx index e2ffd24d5..8e27047bf 100644 --- a/desktop/flipper-ui-core/src/startFlipperDesktop.tsx +++ b/desktop/flipper-ui-core/src/startFlipperDesktop.tsx @@ -10,16 +10,11 @@ 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'; @@ -28,7 +23,6 @@ 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, @@ -49,11 +43,14 @@ import {PersistGate} from 'redux-persist/integration/react'; import { setLoggerInstance, setUserSessionManagerInstance, - GK as flipperCommonGK, + Settings, + FlipperServer, } from 'flipper-common'; import {internGraphPOSTAPIRequest} from './fb-stubs/user'; import {getRenderHostInstance} from './RenderHost'; import {startGlobalErrorHandling} from './utils/globalErrorHandling'; +import {loadTheme} from './utils/loadTheme'; +import {connectFlipperServerToStore} from './dispatcher/flipperServer'; class AppFrame extends React.Component< {logger: Logger; persistor: Persistor}, @@ -145,8 +142,7 @@ class AppFrame extends React.Component< } } -function setProcessState(store: Store) { - const settings = store.getState().settingsState; +function setProcessState(settings: Settings) { const androidHome = settings.androidHome; const idbPath = settings.idbPath; @@ -162,27 +158,22 @@ function setProcessState(store: Store) { .join(':') + `:${idbPath}` + `:${process.env.PATH}`; - - window.requestIdleCallback(() => { - setupPrefetcher(settings); - }); } -function init() { - GK.init(); - - // TODO: centralise all those initialisations in a single configuration call - flipperCommonGK.get = (name) => GK.get(name); +function init(flipperServer: FlipperServer) { + const settings = getRenderHostInstance().serverConfig.settings; const store = getStore(); const logger = initLogger(store); - setLoggerInstance(logger); + setLoggerInstance(logger); startGlobalErrorHandling(); + loadTheme(settings.darkMode); + connectFlipperServerToStore(flipperServer, store, logger); // rehydrate app state before exposing init const persistor = persistStore(store, undefined, () => { // Make sure process state is set before dispatchers run - setProcessState(store); + setProcessState(settings); dispatcher(store, logger); }); @@ -205,39 +196,25 @@ function init() { , 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); - }, - ); + enableConsoleHook(); + + const launcherMessage = + getRenderHostInstance().serverConfig.processConfig.launcherMsg; + if (launcherMessage) { + store.dispatch({ + type: 'LAUNCHER_MSG', + payload: { + severity: 'warning', + message: launcherMessage, + }, + }); + } } -export function startFlipperDesktop() { - setImmediate(() => { - // make sure all modules are loaded - // @ts-ignore - window.flipperInit = init; - window.dispatchEvent(new Event('flipper-store-ready')); - }); +export async function startFlipperDesktop(flipperServer: FlipperServer) { + getRenderHostInstance(); // renderHost instance should be set at this point! + init(flipperServer); } const CodeBlock = styled(Input.TextArea)({ diff --git a/desktop/flipper-ui-core/src/test-utils/TestDevice.tsx b/desktop/flipper-ui-core/src/test-utils/TestDevice.tsx index 3fcec6728..6b9c3be58 100644 --- a/desktop/flipper-ui-core/src/test-utils/TestDevice.tsx +++ b/desktop/flipper-ui-core/src/test-utils/TestDevice.tsx @@ -21,7 +21,7 @@ export class TestDevice extends BaseDevice { ) { super( { - async start() {}, + async connect() {}, on: jest.fn(), off: jest.fn(), exec: jest.fn(), diff --git a/desktop/flipper-ui-core/src/utils/__tests__/jsonFileStorage.node.tsx b/desktop/flipper-ui-core/src/utils/__tests__/jsonFileStorage.node.tsx deleted file mode 100644 index 4b363987d..000000000 --- a/desktop/flipper-ui-core/src/utils/__tests__/jsonFileStorage.node.tsx +++ /dev/null @@ -1,44 +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 node/no-sync */ - -import JsonFileStorage from '../jsonFileReduxPersistStorage'; -import fs from 'fs'; - -const validSerializedData = fs - .readFileSync( - 'flipper-ui-core/src/utils/__tests__/data/settings-v1-valid.json', - ) - .toString() - .replace(/\r\n/g, '\n') - .trim(); - -const validDeserializedData = - '{"androidHome":"\\"/opt/android_sdk\\"","something":"{\\"else\\":4}","_persist":"{\\"version\\":-1,\\"rehydrated\\":true}"}'; - -const storage = new JsonFileStorage( - 'flipper-ui-core/src/utils/__tests__/data/settings-v1-valid.json', -); - -test('A valid settings file gets parsed correctly', () => { - return storage - .getItem('anykey') - .then((result) => expect(result).toEqual(validDeserializedData)); -}); - -test('deserialize works as expected', () => { - const deserialized = storage.deserializeValue(validSerializedData); - expect(deserialized).toEqual(validDeserializedData); -}); - -test('serialize works as expected', () => { - const serialized = storage.serializeValue(validDeserializedData); - expect(serialized).toEqual(validSerializedData); -}); diff --git a/desktop/flipper-ui-core/src/utils/flipperLibImplementation.tsx b/desktop/flipper-ui-core/src/utils/flipperLibImplementation.tsx index 87c26ef7f..702142732 100644 --- a/desktop/flipper-ui-core/src/utils/flipperLibImplementation.tsx +++ b/desktop/flipper-ui-core/src/utils/flipperLibImplementation.tsx @@ -11,7 +11,6 @@ import {_setFlipperLibImplementation} from 'flipper-plugin'; import type {Logger} from 'flipper-common'; import type {Store} from '../reducers'; import createPaste from '../fb-stubs/createPaste'; -import GK from '../fb-stubs/GK'; import type BaseDevice from '../devices/BaseDevice'; import constants from '../fb-stubs/constants'; import {addNotification} from '../reducers/notifications'; @@ -32,9 +31,7 @@ export function initializeFlipperLibImplementation( store.dispatch(setMenuEntries(entries)); }, createPaste, - GK(gatekeeper: string) { - return GK.get(gatekeeper); - }, + GK: renderHost.GK, selectPlugin(device, client, pluginId, deeplink) { store.dispatch({ type: 'SELECT_PLUGIN', @@ -63,8 +60,8 @@ export function initializeFlipperLibImplementation( importFile: renderHost.importFile, exportFile: renderHost.exportFile, paths: { - appPath: renderHost.paths.appPath, - homePath: renderHost.paths.homePath, + appPath: renderHost.serverConfig.paths.appPath, + homePath: renderHost.serverConfig.paths.homePath, }, }); } diff --git a/desktop/flipper-ui-core/src/utils/icons.ts b/desktop/flipper-ui-core/src/utils/icons.ts index 730d19e89..173c84eb7 100644 --- a/desktop/flipper-ui-core/src/utils/icons.ts +++ b/desktop/flipper-ui-core/src/utils/icons.ts @@ -73,18 +73,14 @@ export function buildLocalIconURL(name: string, size: number, density: number) { export function buildIconURLSync(name: string, size: number, density: number) { const icon = getIconPartsFromName(name); // eslint-disable-next-line prettier/prettier - const url = `https://facebook.com/assets/?name=${ - icon.trimmedName - }&variant=${ - icon.variant - }&size=${size}&set=facebook_icons&density=${density}x`; + const url = `https://facebook.com/assets/?name=${icon.trimmedName}&variant=${icon.variant}&size=${size}&set=facebook_icons&density=${density}x`; if ( typeof window !== 'undefined' && (!getIconsSync()[name] || !getIconsSync()[name].includes(size)) ) { // From utils/isProduction const isProduction = !/node_modules[\\/]electron[\\/]/.test( - getRenderHostInstance().paths.execPath, + getRenderHostInstance().serverConfig.paths.execPath, ); if (!isProduction) { @@ -108,9 +104,7 @@ export function buildIconURLSync(name: string, size: number, density: number) { } else { throw new Error( // eslint-disable-next-line prettier/prettier - `Trying to use icon '${name}' with size ${size} and density ${density}, however the icon doesn't seem to exists at ${url}: ${ - res.status - }`, + `Trying to use icon '${name}' with size ${size} and density ${density}, however the icon doesn't seem to exists at ${url}: ${res.status}`, ); } }) @@ -129,7 +123,7 @@ export function getIconURLSync( name: string, size: number, density: number, - basePath: string = getRenderHostInstance().paths.appPath, + basePath: string = getRenderHostInstance().serverConfig.paths.appPath, ) { if (name.indexOf('/') > -1) { return name; diff --git a/desktop/flipper-ui-core/src/utils/isPluginCompatible.tsx b/desktop/flipper-ui-core/src/utils/isPluginCompatible.tsx index ca25ab358..32012d6cf 100644 --- a/desktop/flipper-ui-core/src/utils/isPluginCompatible.tsx +++ b/desktop/flipper-ui-core/src/utils/isPluginCompatible.tsx @@ -9,13 +9,13 @@ import {PluginDetails} from 'flipper-plugin-lib'; import semver from 'semver'; -import GK from '../fb-stubs/GK'; +import {getRenderHostInstance} from '../RenderHost'; import {getAppVersion} from './info'; export function isPluginCompatible(plugin: PluginDetails) { const flipperVersion = getAppVersion(); return ( - GK.get('flipper_disable_plugin_compatibility_checks') || + getRenderHostInstance().GK('flipper_disable_plugin_compatibility_checks') || flipperVersion === '0.0.0' || !plugin.engines?.flipper || semver.lte(plugin.engines?.flipper, flipperVersion) diff --git a/desktop/flipper-ui-core/src/utils/jsonFileReduxPersistStorage.tsx b/desktop/flipper-ui-core/src/utils/jsonFileReduxPersistStorage.tsx deleted file mode 100644 index ded99fdbd..000000000 --- a/desktop/flipper-ui-core/src/utils/jsonFileReduxPersistStorage.tsx +++ /dev/null @@ -1,98 +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 - */ - -import {readFile, pathExists, mkdirp, writeFile} from 'fs-extra'; -import path from 'path'; - -/** - * Redux-persist storage engine for storing state in a human readable JSON file. - * - * Differs from the usual engines in two ways: - * * The key is ignored. This storage will only hold one key, so each setItem() call will overwrite the previous one. - * * Stored files are "human readable". Redux-persist calls storage engines with preserialized values that contain escaped strings inside json. - * This engine re-serializes them by parsing the inner strings to store them as top-level json. - * Transforms haven't been used because they operate before serialization, so all serialized values would still end up as strings. - */ -export default class JsonFileStorage { - filepath: string; - constructor(filepath: string) { - this.filepath = filepath; - } - - private parseFile(): Promise { - return readFile(this.filepath) - .then((buffer) => buffer.toString()) - .then(this.deserializeValue) - .catch(async (e) => { - console.warn( - `Failed to read settings file: "${this.filepath}". ${e}. Replacing file with default settings.`, - ); - await this.writeContents(prettyStringify({})); - return {}; - }); - } - - getItem(_key: string, callback?: (_: any) => any): Promise { - const promise = this.parseFile(); - callback && promise.then(callback); - return promise; - } - - // Sets a new value and returns the value that was PREVIOUSLY set. - // This mirrors the behaviour of the localForage storage engine. - // Not thread-safe. - setItem(_key: string, value: any, callback?: (_: any) => any): Promise { - const originalValue = this.parseFile(); - const writePromise = originalValue.then((_) => - this.writeContents(this.serializeValue(value)), - ); - - return Promise.all([originalValue, writePromise]).then(([o, _]) => { - callback && callback(o); - return o; - }); - } - - removeItem(_key: string, callback?: () => any): Promise { - return this.writeContents(prettyStringify({})) - .then((_) => callback && callback()) - .then(() => {}); - } - - serializeValue(value: string): string { - const reconstructedObject = Object.entries(JSON.parse(value)) - .map(([k, v]: [string, unknown]) => [k, JSON.parse(v as string)]) - .reduce((acc: {[key: string]: any}, cv) => { - acc[cv[0]] = cv[1]; - return acc; - }, {}); - return prettyStringify(reconstructedObject); - } - - deserializeValue(value: string): string { - const reconstructedObject = Object.entries(JSON.parse(value)) - .map(([k, v]: [string, unknown]) => [k, JSON.stringify(v)]) - .reduce((acc: {[key: string]: string}, cv) => { - acc[cv[0]] = cv[1]; - return acc; - }, {}); - return JSON.stringify(reconstructedObject); - } - - writeContents(content: string): Promise { - const dir = path.dirname(this.filepath); - return pathExists(dir) - .then((dirExists) => (dirExists ? Promise.resolve() : mkdirp(dir))) - .then(() => writeFile(this.filepath, content)); - } -} - -function prettyStringify(data: Object) { - return JSON.stringify(data, null, 2); -} diff --git a/desktop/flipper-ui-core/src/utils/launcher.tsx b/desktop/flipper-ui-core/src/utils/launcher.tsx deleted file mode 100644 index 2df544b73..000000000 --- a/desktop/flipper-ui-core/src/utils/launcher.tsx +++ /dev/null @@ -1,40 +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 - */ - -import path from 'path'; -import os from 'os'; -import xdg from 'xdg-basedir'; -import {ProcessConfig} from './processConfig'; -import {Store} from '../reducers/index'; - -// There is some disagreement among the XDG Base Directory implementations -// whether to use ~/Library/Preferences or ~/.config on MacOS. The Launcher -// expects the former, whereas `xdg-basedir` implements the latter. -const xdgConfigDir = () => - os.platform() === 'darwin' - ? path.join(os.homedir(), 'Library', 'Preferences') - : xdg.config || path.join(os.homedir(), '.config'); - -export const launcherConfigDir = () => - path.join( - xdgConfigDir(), - os.platform() == 'darwin' ? 'rs.flipper-launcher' : 'flipper-launcher', - ); - -export function initLauncherHooks(config: ProcessConfig, store: Store) { - if (config.launcherMsg) { - store.dispatch({ - type: 'LAUNCHER_MSG', - payload: { - severity: 'warning', - message: config.launcherMsg, - }, - }); - } -} diff --git a/desktop/flipper-ui-core/src/utils/launcherSettingsStorage.tsx b/desktop/flipper-ui-core/src/utils/launcherSettingsStorage.tsx deleted file mode 100644 index fbf9089b9..000000000 --- a/desktop/flipper-ui-core/src/utils/launcherSettingsStorage.tsx +++ /dev/null @@ -1,89 +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 - */ - -import fs from 'fs-extra'; -import path from 'path'; -import TOML, {JsonMap} from '@iarna/toml'; -import {Storage} from 'redux-persist/es/types'; -import { - defaultLauncherSettings, - LauncherSettings, -} from '../reducers/launcherSettings'; -import ReleaseChannel from '../ReleaseChannel'; - -export default class LauncherSettingsStorage implements Storage { - constructor(readonly filepath: string) {} - - async getItem(_key: string): Promise { - return await this.parseFile(); - } - - async setItem(_key: string, value: LauncherSettings): Promise { - const originalValue = await this.parseFile(); - await this.writeFile(value); - return originalValue; - } - - removeItem(_key: string): Promise { - return this.writeFile(defaultLauncherSettings); - } - - private async parseFile(): Promise { - try { - const content = (await fs.readFile(this.filepath)).toString(); - return deserialize(content); - } catch (e) { - console.warn( - `Failed to read settings file: "${this.filepath}". ${e}. Replacing file with default settings.`, - ); - await this.writeFile(defaultLauncherSettings); - return defaultLauncherSettings; - } - } - - private async writeFile(value: LauncherSettings): Promise { - this.ensureDirExists(); - const content = serialize(value); - return fs.writeFile(this.filepath, content); - } - - private async ensureDirExists(): Promise { - const dir = path.dirname(this.filepath); - const exists = await fs.pathExists(dir); - if (!exists) { - await fs.mkdir(dir, {recursive: true}); - } - } -} - -interface FormattedSettings { - ignore_local_pin?: boolean; - release_channel?: ReleaseChannel; -} - -function serialize(value: LauncherSettings): string { - const {ignoreLocalPin, releaseChannel, ...rest} = value; - const formattedSettings: FormattedSettings = { - ...rest, - ignore_local_pin: ignoreLocalPin, - release_channel: releaseChannel, - }; - return TOML.stringify(formattedSettings as JsonMap); -} - -function deserialize(content: string): LauncherSettings { - const {ignore_local_pin, release_channel, ...rest} = TOML.parse( - content, - ) as FormattedSettings; - return { - ...rest, - ignoreLocalPin: !!ignore_local_pin, - releaseChannel: release_channel ?? ReleaseChannel.DEFAULT, - }; -} diff --git a/desktop/flipper-ui-core/src/utils/loadDynamicPlugins.tsx b/desktop/flipper-ui-core/src/utils/loadDynamicPlugins.tsx index e5f3d8869..3cec38033 100644 --- a/desktop/flipper-ui-core/src/utils/loadDynamicPlugins.tsx +++ b/desktop/flipper-ui-core/src/utils/loadDynamicPlugins.tsx @@ -23,6 +23,9 @@ import {getStaticPath} from '../utils/pathUtils'; export default async function loadDynamicPlugins(): Promise< InstalledPluginDetails[] > { + if (process.env.NODE_ENV === 'test') { + return []; + } if (process.env.FLIPPER_FAST_REFRESH) { console.log( '❌ Skipping loading of dynamic plugins because Fast Refresh is enabled. Fast Refresh only works with bundled plugins.', diff --git a/desktop/flipper-ui-core/src/utils/loadTheme.tsx b/desktop/flipper-ui-core/src/utils/loadTheme.tsx new file mode 100644 index 000000000..4b76cc80f --- /dev/null +++ b/desktop/flipper-ui-core/src/utils/loadTheme.tsx @@ -0,0 +1,26 @@ +/** + * 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 {Settings} from 'flipper-common'; +import {getRenderHostInstance} from '../RenderHost'; + +export function loadTheme(theme: Settings['darkMode']) { + 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); +} diff --git a/desktop/flipper-ui-core/src/utils/packageMetadata.tsx b/desktop/flipper-ui-core/src/utils/packageMetadata.tsx index 9c3c4e559..f3cd27996 100644 --- a/desktop/flipper-ui-core/src/utils/packageMetadata.tsx +++ b/desktop/flipper-ui-core/src/utils/packageMetadata.tsx @@ -14,7 +14,7 @@ import {promisify} from 'util'; import {getRenderHostInstance} from '../RenderHost'; const getPackageJSON = async () => { - const base = getRenderHostInstance().paths.appPath; + const base = getRenderHostInstance().serverConfig.paths.appPath; const content = await promisify(fs.readFile)( path.join(base, 'package.json'), 'utf-8', diff --git a/desktop/flipper-ui-core/src/utils/pathUtils.tsx b/desktop/flipper-ui-core/src/utils/pathUtils.tsx index 32a4da8d7..c122fcf06 100644 --- a/desktop/flipper-ui-core/src/utils/pathUtils.tsx +++ b/desktop/flipper-ui-core/src/utils/pathUtils.tsx @@ -20,7 +20,7 @@ export function getStaticPath( relativePath: string = '.', {asarUnpacked}: {asarUnpacked: boolean} = {asarUnpacked: false}, ) { - const staticDir = getRenderHostInstance().paths.staticPath; + const staticDir = getRenderHostInstance().serverConfig.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. diff --git a/desktop/flipper-ui-core/src/utils/processConfig.tsx b/desktop/flipper-ui-core/src/utils/processConfig.tsx deleted file mode 100644 index b611c07fa..000000000 --- a/desktop/flipper-ui-core/src/utils/processConfig.tsx +++ /dev/null @@ -1,45 +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 - */ - -import {getRenderHostInstance} from '../RenderHost'; - -export type ProcessConfig = { - disabledPlugins: Set; - lastWindowPosition: { - x: number; - y: number; - width: number; - height: number; - } | null; - screenCapturePath: string | null; - launcherMsg: string | null; - // Controls whether to delegate to the launcher if present. - launcherEnabled: boolean; -}; - -let configObj: ProcessConfig | null = null; -export default function config(): ProcessConfig { - if (configObj === null) { - const json = JSON.parse(getRenderHostInstance().env.CONFIG || '{}'); - configObj = { - disabledPlugins: new Set(json.disabledPlugins || []), - lastWindowPosition: json.lastWindowPosition, - launcherMsg: json.launcherMsg, - screenCapturePath: json.screenCapturePath, - launcherEnabled: - typeof json.launcherEnabled === 'boolean' ? json.launcherEnabled : true, - }; - } - - return configObj; -} - -export function resetConfigForTesting() { - configObj = null; -} diff --git a/desktop/flipper-ui-core/src/utils/screenshot.tsx b/desktop/flipper-ui-core/src/utils/screenshot.tsx index 2305654cf..9fb92e5d3 100644 --- a/desktop/flipper-ui-core/src/utils/screenshot.tsx +++ b/desktop/flipper-ui-core/src/utils/screenshot.tsx @@ -12,12 +12,12 @@ import path from 'path'; import BaseDevice from '../devices/BaseDevice'; import {reportPlatformFailures} from 'flipper-common'; import expandTilde from 'expand-tilde'; -import config from '../utils/processConfig'; import {getRenderHostInstance} from '../RenderHost'; export function getCaptureLocation() { return expandTilde( - config().screenCapturePath || getRenderHostInstance().paths.desktopPath, + getRenderHostInstance().serverConfig.processConfig.screenCapturePath || + getRenderHostInstance().serverConfig.paths.desktopPath, ); } diff --git a/desktop/flipper-ui-core/src/utils/versionString.tsx b/desktop/flipper-ui-core/src/utils/versionString.tsx index 3c88ff0bf..af82058f0 100644 --- a/desktop/flipper-ui-core/src/utils/versionString.tsx +++ b/desktop/flipper-ui-core/src/utils/versionString.tsx @@ -10,7 +10,7 @@ import isProduction from '../utils/isProduction'; import {getAppVersion} from './info'; import config from '../fb-stubs/config'; -import ReleaseChannel from '../ReleaseChannel'; +import {ReleaseChannel} from 'flipper-common'; export function getVersionString() { return ( diff --git a/desktop/static/index.dev.html b/desktop/static/index.dev.html index aa5b5e3ff..6436a1f99 100644 --- a/desktop/static/index.dev.html +++ b/desktop/static/index.dev.html @@ -98,8 +98,6 @@ openError('Script failure. Check Chrome console for more info.'); }; - window.addEventListener('flipper-store-ready', () => global.flipperInit()); - document.body.appendChild(script); } init(); diff --git a/desktop/static/index.html b/desktop/static/index.html index 475260e2f..b752be3c7 100644 --- a/desktop/static/index.html +++ b/desktop/static/index.html @@ -26,8 +26,6 @@ console.error("Failed to initialize theme", e); document.getElementById('flipper-theme-import').href="themes/light.css"; } - - window.addEventListener('flipper-store-ready', () => global.flipperInit());