Move settings, launcherSettings, GKs to app / flipper-server-core
Summary: This diff moves a lot of stuff from the client to the server. This diff is fairly large, as a lot of concept closely relate, although some things have split off to the earlier diffs in the stack, or are still to follow (like making intern requests). This diff primarily moves reading and storing settings and GKs from client to server (both flipper and launcher settings). This means that settings are no longer persisted by Redux (which only exists on client). Most other changes are fallout from that. For now settings are just one big object, although we might need to separate settings that are only make sense in an Electron context. For example launcher settings. Reviewed By: passy, aigoncharov Differential Revision: D32498649 fbshipit-source-id: d842faf7a7f03774b621c7656e53a9127afc6192
This commit is contained in:
committed by
Facebook GitHub Bot
parent
eed19b3a3d
commit
bca169df73
@@ -17,6 +17,7 @@
|
|||||||
"flipper-common": "0.0.0",
|
"flipper-common": "0.0.0",
|
||||||
"flipper-server-core": "0.0.0",
|
"flipper-server-core": "0.0.0",
|
||||||
"flipper-ui-core": "0.0.0",
|
"flipper-ui-core": "0.0.0",
|
||||||
|
"fs-extra": "^10.0.0",
|
||||||
"invariant": "^2.2.2",
|
"invariant": "^2.2.2",
|
||||||
"metro-runtime": "^0.66.2",
|
"metro-runtime": "^0.66.2",
|
||||||
"pretty-format": "^27.3.1"
|
"pretty-format": "^27.3.1"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
_setGlobalInteractionReporter,
|
_setGlobalInteractionReporter,
|
||||||
_LoggerContext,
|
_LoggerContext,
|
||||||
} from 'flipper-plugin';
|
} 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 {
|
import {
|
||||||
ipcRenderer,
|
ipcRenderer,
|
||||||
remote,
|
remote,
|
||||||
@@ -26,7 +26,7 @@ import type {RenderHost} from 'flipper-ui-core';
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import {setupMenuBar} from './setupMenuBar';
|
import {setupMenuBar} from './setupMenuBar';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import {FlipperServerImpl} from 'flipper-server-core';
|
import {FlipperServerConfig} from 'flipper-common';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@@ -45,12 +45,9 @@ if (process.env.NODE_ENV === 'development' && os.platform() === 'darwin') {
|
|||||||
global.electronRequire('mac-ca');
|
global.electronRequire('mac-ca');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initializeElectron() {
|
export function initializeElectron(flipperServerConfig: FlipperServerConfig) {
|
||||||
const app = remote.app;
|
|
||||||
const execPath = process.execPath || remote.process.execPath;
|
const execPath = process.execPath || remote.process.execPath;
|
||||||
const isProduction = !/node_modules[\\/]electron[\\/]/.test(execPath);
|
const isProduction = !/node_modules[\\/]electron[\\/]/.test(execPath);
|
||||||
const staticPath = getStaticDir();
|
|
||||||
const tempPath = app.getPath('temp');
|
|
||||||
|
|
||||||
function restart(update: boolean = false) {
|
function restart(update: boolean = false) {
|
||||||
if (isProduction) {
|
if (isProduction) {
|
||||||
@@ -68,12 +65,9 @@ export function initializeElectron() {
|
|||||||
} else {
|
} else {
|
||||||
// Relaunching the process with the standard way doesn't work in dev mode.
|
// 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.
|
// So instead we're sending a signal to dev server to kill the current instance of electron and launch new.
|
||||||
fetch(
|
fetch(`${flipperServerConfig.env.DEV_SERVER_URL}/_restartElectron`, {
|
||||||
`${window.FlipperRenderHostInstance.env.DEV_SERVER_URL}/_restartElectron`,
|
method: 'POST',
|
||||||
{
|
});
|
||||||
method: 'POST',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,25 +186,10 @@ export function initializeElectron() {
|
|||||||
restartFlipper() {
|
restartFlipper() {
|
||||||
restart();
|
restart();
|
||||||
},
|
},
|
||||||
env: process.env,
|
|
||||||
paths: {
|
|
||||||
appPath: app.getAppPath(),
|
|
||||||
homePath: app.getPath('home'),
|
|
||||||
execPath,
|
|
||||||
staticPath,
|
|
||||||
tempPath,
|
|
||||||
desktopPath: app.getPath('desktop'),
|
|
||||||
},
|
|
||||||
loadDefaultPlugins: getDefaultPluginsIndex,
|
loadDefaultPlugins: getDefaultPluginsIndex,
|
||||||
startFlipperServer({logger, ...config}) {
|
serverConfig: flipperServerConfig,
|
||||||
return new FlipperServerImpl(
|
GK(gatekeeper) {
|
||||||
{
|
return flipperServerConfig.gatekeepers[gatekeeper] ?? false;
|
||||||
...config,
|
|
||||||
staticPath,
|
|
||||||
tempPath,
|
|
||||||
},
|
|
||||||
logger,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -222,17 +201,3 @@ function getDefaultPluginsIndex() {
|
|||||||
const index = require('../defaultPlugins');
|
const index = require('../defaultPlugins');
|
||||||
return index.default || index;
|
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;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,12 +8,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Deliberate use of remote in this context.
|
// Deliberate use of remote in this context.
|
||||||
/* eslint-disable no-restricted-properties */
|
/* eslint-disable no-restricted-properties, no-restricted-imports */
|
||||||
|
import electron, {MenuItemConstructorOptions, webFrame} from 'electron';
|
||||||
import electron, {MenuItemConstructorOptions} from 'electron';
|
|
||||||
import {getLogger} from 'flipper-common';
|
import {getLogger} from 'flipper-common';
|
||||||
import {_buildInMenuEntries, _wrapInteractionHandler} from 'flipper-plugin';
|
import {_buildInMenuEntries, _wrapInteractionHandler} from 'flipper-plugin';
|
||||||
import {webFrame} from 'electron';
|
|
||||||
|
|
||||||
export function setupMenuBar() {
|
export function setupMenuBar() {
|
||||||
const template = getTemplate(electron.remote.app);
|
const template = getTemplate(electron.remote.app);
|
||||||
|
|||||||
18
desktop/app/src/fb-stubs/constants.tsx
Normal file
18
desktop/app/src/fb-stubs/constants.tsx
Normal file
@@ -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://',
|
||||||
|
],
|
||||||
|
});
|
||||||
@@ -7,16 +7,168 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {initializeElectron} from './electron/initializeElectron';
|
|
||||||
import {enableMapSet} from 'immer';
|
import {enableMapSet} from 'immer';
|
||||||
|
import {
|
||||||
initializeElectron();
|
_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();
|
enableMapSet();
|
||||||
|
|
||||||
// By turning this in a require, we force the JS that the body of this module (init) has completed (initializeElectron),
|
declare global {
|
||||||
// before starting the rest of the Flipper process.
|
interface Window {
|
||||||
// This prevent issues where the render host is referred at module initialisation level,
|
// We store this as a global, to make sure the renderHost is available
|
||||||
// but not set yet, which might happen when using normal imports.
|
// before flipper-ui-core is loaded and needs those during module initialisation
|
||||||
// eslint-disable-next-line import/no-commonjs
|
FlipperRenderHostInstance: RenderHost;
|
||||||
require('flipper-ui-core').startFlipperDesktop();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,3 +44,4 @@ export {
|
|||||||
export * from './user-session';
|
export * from './user-session';
|
||||||
export * from './GK';
|
export * from './GK';
|
||||||
export * from './clientUtils';
|
export * from './clientUtils';
|
||||||
|
export * from './settings';
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
DeviceType as PluginDeviceType,
|
DeviceType as PluginDeviceType,
|
||||||
OS as PluginOS,
|
OS as PluginOS,
|
||||||
} from 'flipper-plugin-lib';
|
} 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.
|
// 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.
|
// 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 = {
|
export type FlipperServerCommands = {
|
||||||
|
'get-config': () => Promise<FlipperServerConfig>;
|
||||||
'device-start-logging': (serial: string) => Promise<void>;
|
'device-start-logging': (serial: string) => Promise<void>;
|
||||||
'device-stop-logging': (serial: string) => Promise<void>;
|
'device-stop-logging': (serial: string) => Promise<void>;
|
||||||
'device-supports-screenshot': (serial: string) => Promise<boolean>;
|
'device-supports-screenshot': (serial: string) => Promise<boolean>;
|
||||||
@@ -151,10 +153,36 @@ export type FlipperServerCommands = {
|
|||||||
'android-launch-emulator': (name: string, coldboot: boolean) => Promise<void>;
|
'android-launch-emulator': (name: string, coldboot: boolean) => Promise<void>;
|
||||||
'ios-get-simulators': (bootedOnly: boolean) => Promise<IOSDeviceParams[]>;
|
'ios-get-simulators': (bootedOnly: boolean) => Promise<IOSDeviceParams[]>;
|
||||||
'ios-launch-simulator': (udid: string) => Promise<void>;
|
'ios-launch-simulator': (udid: string) => Promise<void>;
|
||||||
|
'persist-settings': (settings: Settings) => Promise<void>;
|
||||||
|
'persist-launcher-settings': (settings: LauncherSettings) => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
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<string, boolean>;
|
||||||
|
env: Partial<Record<ENVIRONMENT_VARIABLES, string>>;
|
||||||
|
paths: Record<ENVIRONMENT_PATHS, string>;
|
||||||
|
settings: Settings;
|
||||||
|
launcherSettings: LauncherSettings;
|
||||||
|
processConfig: ProcessConfig;
|
||||||
|
validWebSocketOrigins: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface FlipperServer {
|
export interface FlipperServer {
|
||||||
start(): Promise<void>;
|
connect(): Promise<void>;
|
||||||
on<Event extends keyof FlipperServerEvents>(
|
on<Event extends keyof FlipperServerEvents>(
|
||||||
event: Event,
|
event: Event,
|
||||||
callback: (payload: FlipperServerEvents[Event]) => void,
|
callback: (payload: FlipperServerEvents[Event]) => void,
|
||||||
|
|||||||
70
desktop/flipper-common/src/settings.tsx
Normal file
70
desktop/flipper-common/src/settings.tsx
Normal file
@@ -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<string>;
|
||||||
|
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;
|
||||||
|
};
|
||||||
@@ -64,9 +64,6 @@ export function getLogger(): Logger {
|
|||||||
|
|
||||||
// only for testing
|
// only for testing
|
||||||
export function setLoggerInstance(logger: Logger) {
|
export function setLoggerInstance(logger: Logger) {
|
||||||
if (!isTest() && instance) {
|
|
||||||
console.warn('Logger was already initialised');
|
|
||||||
}
|
|
||||||
instance = logger;
|
instance = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
Stand alone Flipper command, that uses flipper-server-core to connect to apps and dump all incoming messages.
|
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:
|
This package is currently a proof of concept and can be used like:
|
||||||
|
|
||||||
`yarn start --device='iPhone 12' --client='Instagram' --plugin='AnalyticsLogging'`
|
`yarn start --device='iPhone 12' --client='Instagram' --plugin='AnalyticsLogging'`
|
||||||
|
|||||||
@@ -10,7 +10,12 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import yargs from 'yargs';
|
import yargs from 'yargs';
|
||||||
import {FlipperServerImpl} from 'flipper-server-core';
|
import {
|
||||||
|
FlipperServerImpl,
|
||||||
|
loadLauncherSettings,
|
||||||
|
loadProcessConfig,
|
||||||
|
loadSettings,
|
||||||
|
} from 'flipper-server-core';
|
||||||
import {
|
import {
|
||||||
ClientDescription,
|
ClientDescription,
|
||||||
Logger,
|
Logger,
|
||||||
@@ -48,7 +53,7 @@ const argv = yargs
|
|||||||
.parse(process.argv.slice(1));
|
.parse(process.argv.slice(1));
|
||||||
|
|
||||||
async function start(deviceTitle: string, appName: string, pluginId: string) {
|
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 device: DeviceDescription | undefined;
|
||||||
let deviceResolver: () => void;
|
let deviceResolver: () => void;
|
||||||
const devicePromise: Promise<void> = new Promise((resolve) => {
|
const devicePromise: Promise<void> = new Promise((resolve) => {
|
||||||
@@ -67,14 +72,20 @@ async function start(deviceTitle: string, appName: string, pluginId: string) {
|
|||||||
|
|
||||||
const server = new FlipperServerImpl(
|
const server = new FlipperServerImpl(
|
||||||
{
|
{
|
||||||
// TODO: make these better overridable
|
env: process.env,
|
||||||
enableAndroid: true,
|
gatekeepers: {},
|
||||||
androidHome: process.env.ANDROID_HOME || '/opt/android_sdk',
|
isProduction: false,
|
||||||
idbPath: '/usr/local/bin/idb',
|
paths: {
|
||||||
enableIOS: true,
|
staticPath: path.resolve(__dirname, '..', '..', 'static'),
|
||||||
enablePhysicalIOS: true,
|
tempPath: os.tmpdir(),
|
||||||
staticPath: path.resolve(__dirname, '..', '..', 'static'),
|
appPath: `/dev/null`,
|
||||||
tempPath: os.tmpdir(),
|
homePath: `/dev/null`,
|
||||||
|
execPath: process.execPath,
|
||||||
|
desktopPath: `/dev/null`,
|
||||||
|
},
|
||||||
|
launcherSettings: await loadLauncherSettings(),
|
||||||
|
processConfig: loadProcessConfig(process.env),
|
||||||
|
settings: await loadSettings(),
|
||||||
validWebSocketOrigins: [],
|
validWebSocketOrigins: [],
|
||||||
},
|
},
|
||||||
logger,
|
logger,
|
||||||
@@ -196,7 +207,7 @@ async function start(deviceTitle: string, appName: string, pluginId: string) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server
|
server
|
||||||
.start()
|
.connect()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
logger.info(
|
logger.info(
|
||||||
'Flipper server started and accepting device / client connections',
|
'Flipper server started and accepting device / client connections',
|
||||||
|
|||||||
@@ -590,7 +590,7 @@ export function createFlipperServerMock(
|
|||||||
overrides?: Partial<FlipperServerCommands>,
|
overrides?: Partial<FlipperServerCommands>,
|
||||||
): FlipperServer {
|
): FlipperServer {
|
||||||
return {
|
return {
|
||||||
async start() {},
|
async connect() {},
|
||||||
on: jest.fn(),
|
on: jest.fn(),
|
||||||
off: jest.fn(),
|
off: jest.fn(),
|
||||||
exec: jest
|
exec: jest
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bugs": "https://github.com/facebook/flipper/issues",
|
"bugs": "https://github.com/facebook/flipper/issues",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@iarna/toml": "^2.2.5",
|
||||||
"JSONStream": "^1.3.1",
|
"JSONStream": "^1.3.1",
|
||||||
"adbkit": "^2.11.1",
|
"adbkit": "^2.11.1",
|
||||||
"adbkit-logcat": "^2.0.1",
|
"adbkit-logcat": "^2.0.1",
|
||||||
@@ -31,7 +32,8 @@
|
|||||||
"tmp": "^0.2.1",
|
"tmp": "^0.2.1",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"which": "^2.0.2",
|
"which": "^2.0.2",
|
||||||
"ws": "^8.2.3"
|
"ws": "^8.2.3",
|
||||||
|
"xdg-basedir": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {},
|
"devDependencies": {},
|
||||||
"peerDependencies": {},
|
"peerDependencies": {},
|
||||||
|
|||||||
@@ -7,43 +7,22 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {isTest} from 'flipper-common';
|
import {FlipperServerConfig} from 'flipper-common';
|
||||||
import {parseFlipperPorts} from './utils/environmentVariables';
|
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;
|
let currentConfig: FlipperServerConfig | undefined = undefined;
|
||||||
|
|
||||||
|
// just an ugly utility to not need a reference to FlipperServerImpl itself everywhere
|
||||||
export function getFlipperServerConfig(): FlipperServerConfig {
|
export function getFlipperServerConfig(): FlipperServerConfig {
|
||||||
if (!currentConfig) {
|
if (!currentConfig) {
|
||||||
if (isTest()) return testConfig;
|
|
||||||
throw new Error('FlipperServerConfig has not been set');
|
throw new Error('FlipperServerConfig has not been set');
|
||||||
}
|
}
|
||||||
return currentConfig;
|
return currentConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setFlipperServerConfig(config: FlipperServerConfig) {
|
export function setFlipperServerConfig(
|
||||||
|
config: FlipperServerConfig | undefined,
|
||||||
|
) {
|
||||||
currentConfig = config;
|
currentConfig = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,15 +24,15 @@ import {
|
|||||||
FlipperServerCommands,
|
FlipperServerCommands,
|
||||||
FlipperServer,
|
FlipperServer,
|
||||||
UninitializedClient,
|
UninitializedClient,
|
||||||
|
FlipperServerConfig,
|
||||||
} from 'flipper-common';
|
} from 'flipper-common';
|
||||||
import {ServerDevice} from './devices/ServerDevice';
|
import {ServerDevice} from './devices/ServerDevice';
|
||||||
import {Base64} from 'js-base64';
|
import {Base64} from 'js-base64';
|
||||||
import MetroDevice from './devices/metro/MetroDevice';
|
import MetroDevice from './devices/metro/MetroDevice';
|
||||||
import {launchEmulator} from './devices/android/AndroidDevice';
|
import {launchEmulator} from './devices/android/AndroidDevice';
|
||||||
import {
|
import {setFlipperServerConfig} from './FlipperServerConfig';
|
||||||
FlipperServerConfig,
|
import {saveSettings} from './utils/settings';
|
||||||
setFlipperServerConfig,
|
import {saveLauncherSettings} from './utils/launcherSettings';
|
||||||
} from './FlipperServerConfig';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FlipperServer takes care of all incoming device & client connections.
|
* FlipperServer takes care of all incoming device & client connections.
|
||||||
@@ -52,7 +52,7 @@ export class FlipperServerImpl implements FlipperServer {
|
|||||||
android: AndroidDeviceManager;
|
android: AndroidDeviceManager;
|
||||||
ios: IOSDeviceManager;
|
ios: IOSDeviceManager;
|
||||||
|
|
||||||
constructor(config: FlipperServerConfig, public logger: Logger) {
|
constructor(public config: FlipperServerConfig, public logger: Logger) {
|
||||||
setFlipperServerConfig(config);
|
setFlipperServerConfig(config);
|
||||||
const server = (this.server = new ServerController(this));
|
const server = (this.server = new ServerController(this));
|
||||||
this.android = new AndroidDeviceManager(this);
|
this.android = new AndroidDeviceManager(this);
|
||||||
@@ -107,7 +107,7 @@ export class FlipperServerImpl implements FlipperServer {
|
|||||||
/**
|
/**
|
||||||
* Starts listening to parts and watching for devices
|
* Starts listening to parts and watching for devices
|
||||||
*/
|
*/
|
||||||
async start() {
|
async connect() {
|
||||||
if (this.state !== 'pending') {
|
if (this.state !== 'pending') {
|
||||||
throw new Error('Server already started');
|
throw new Error('Server already started');
|
||||||
}
|
}
|
||||||
@@ -170,6 +170,7 @@ export class FlipperServerImpl implements FlipperServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private commandHandler: FlipperServerCommands = {
|
private commandHandler: FlipperServerCommands = {
|
||||||
|
'get-config': async () => this.config,
|
||||||
'device-start-logging': async (serial: string) =>
|
'device-start-logging': async (serial: string) =>
|
||||||
this.getDevice(serial).startLogging(),
|
this.getDevice(serial).startLogging(),
|
||||||
'device-stop-logging': async (serial: string) =>
|
'device-stop-logging': async (serial: string) =>
|
||||||
@@ -223,6 +224,9 @@ export class FlipperServerImpl implements FlipperServer {
|
|||||||
'ios-get-simulators': async (bootedOnly) =>
|
'ios-get-simulators': async (bootedOnly) =>
|
||||||
this.ios.getSimulators(bootedOnly),
|
this.ios.getSimulators(bootedOnly),
|
||||||
'ios-launch-simulator': async (udid) => launchSimulator(udid),
|
'ios-launch-simulator': async (udid) => launchSimulator(udid),
|
||||||
|
'persist-settings': async (settings) => saveSettings(settings),
|
||||||
|
'persist-launcher-settings': async (settings) =>
|
||||||
|
saveLauncherSettings(settings),
|
||||||
};
|
};
|
||||||
|
|
||||||
registerDevice(device: ServerDevice) {
|
registerDevice(device: ServerDevice) {
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class ServerController extends EventEmitter implements ServerEventsListener {
|
|||||||
this.certificateProvider = new CertificateProvider(
|
this.certificateProvider = new CertificateProvider(
|
||||||
this,
|
this,
|
||||||
this.logger,
|
this.logger,
|
||||||
getFlipperServerConfig(),
|
getFlipperServerConfig().settings,
|
||||||
);
|
);
|
||||||
this.connectionTracker = new ConnectionTracker(this.logger);
|
this.connectionTracker = new ConnectionTracker(this.logger);
|
||||||
this.secureServer = null;
|
this.secureServer = null;
|
||||||
@@ -244,13 +244,13 @@ class ServerController extends EventEmitter implements ServerEventsListener {
|
|||||||
|
|
||||||
const {os, app, device_id} = clientQuery;
|
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
|
// 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(
|
console.error(
|
||||||
`Refusing connection from ${app} on ${device_id}, since iOS support is disabled in settings`,
|
`Refusing connection from ${app} on ${device_id}, since iOS support is disabled in settings`,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (os === 'Android' && !getFlipperServerConfig().enableAndroid) {
|
if (os === 'Android' && !getFlipperServerConfig().settings.enableAndroid) {
|
||||||
console.error(
|
console.error(
|
||||||
`Refusing connection from ${app} on ${device_id}, since Android support is disabled in settings`,
|
`Refusing connection from ${app} on ${device_id}, since Android support is disabled in settings`,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ export class AndroidDeviceManager {
|
|||||||
|
|
||||||
async watchAndroidDevices() {
|
async watchAndroidDevices() {
|
||||||
try {
|
try {
|
||||||
const client = await getAdbClient(getFlipperServerConfig());
|
const client = await getAdbClient(getFlipperServerConfig().settings);
|
||||||
client
|
client
|
||||||
.trackDevices()
|
.trackDevices()
|
||||||
.then((tracker) => {
|
.then((tracker) => {
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export function xcrunStartLogListener(udid: string, deviceType: DeviceType) {
|
|||||||
|
|
||||||
function makeTempScreenshotFilePath() {
|
function makeTempScreenshotFilePath() {
|
||||||
const imageName = uuid() + '.png';
|
const imageName = uuid() + '.png';
|
||||||
return path.join(getFlipperServerConfig().tempPath, imageName);
|
return path.join(getFlipperServerConfig().paths.tempPath, imageName);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runScreenshotCommand(
|
async function runScreenshotCommand(
|
||||||
|
|||||||
@@ -7,13 +7,23 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {makeIOSBridge} from '../IOSBridge';
|
|
||||||
import childProcess from 'child_process';
|
import childProcess from 'child_process';
|
||||||
import * as promisifyChildProcess from 'promisify-child-process';
|
|
||||||
|
|
||||||
jest.mock('child_process');
|
jest.mock('child_process');
|
||||||
jest.mock('promisify-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 () => {
|
test('uses xcrun with no idb when xcode is detected', async () => {
|
||||||
const ib = await makeIOSBridge('', true);
|
const ib = await makeIOSBridge('', true);
|
||||||
|
|
||||||
@@ -95,10 +105,10 @@ test.unix(
|
|||||||
async () => {
|
async () => {
|
||||||
const ib = await makeIOSBridge('', true);
|
const ib = await makeIOSBridge('', true);
|
||||||
|
|
||||||
ib.screenshot('deadbeef');
|
await expect(() => ib.screenshot('deadbeef')).rejects.toThrow();
|
||||||
|
|
||||||
expect(promisifyChildProcess.exec).toHaveBeenCalledWith(
|
expect((promisifyChildProcess.exec as any).mock.calls[0][0]).toMatch(
|
||||||
'xcrun simctl io deadbeef screenshot /temp/00000000-0000-0000-0000-000000000000.png',
|
'xcrun simctl io deadbeef screenshot',
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -106,17 +116,17 @@ test.unix(
|
|||||||
test.unix('uses idb to take screenshots when available', async () => {
|
test.unix('uses idb to take screenshots when available', async () => {
|
||||||
const ib = await makeIOSBridge('/usr/local/bin/idb', true, async (_) => true);
|
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(
|
expect((promisifyChildProcess.exec as any).mock.calls[0][0]).toMatch(
|
||||||
'idb screenshot --udid deadbeef /temp/00000000-0000-0000-0000-000000000000.png',
|
'idb screenshot --udid deadbeef ',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('uses xcrun to navigate with no idb when xcode is detected', async () => {
|
test('uses xcrun to navigate with no idb when xcode is detected', async () => {
|
||||||
const ib = await makeIOSBridge('', true);
|
const ib = await makeIOSBridge('', true);
|
||||||
|
|
||||||
ib.navigate('deadbeef', 'fb://dummy');
|
await ib.navigate('deadbeef', 'fb://dummy');
|
||||||
|
|
||||||
expect(promisifyChildProcess.exec).toHaveBeenCalledWith(
|
expect(promisifyChildProcess.exec).toHaveBeenCalledWith(
|
||||||
'xcrun simctl io deadbeef launch url "fb://dummy"',
|
'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 () => {
|
test('uses idb to navigate when available', async () => {
|
||||||
const ib = await makeIOSBridge('/usr/local/bin/idb', true, async (_) => true);
|
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(
|
expect(promisifyChildProcess.exec).toHaveBeenCalledWith(
|
||||||
'idb open --udid deadbeef "fb://dummy"',
|
'idb open --udid deadbeef "fb://dummy"',
|
||||||
|
|||||||
@@ -11,9 +11,19 @@ import {parseXcodeFromCoreSimPath} from '../iOSDeviceManager';
|
|||||||
import {getLogger} from 'flipper-common';
|
import {getLogger} from 'flipper-common';
|
||||||
import {IOSBridge} from '../IOSBridge';
|
import {IOSBridge} from '../IOSBridge';
|
||||||
import {FlipperServerImpl} from '../../../FlipperServerImpl';
|
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 =
|
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';
|
'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', () => {
|
test('test getAllPromisesForQueryingDevices when xcode detected', () => {
|
||||||
const flipperServer = new FlipperServerImpl(testConfig, getLogger());
|
const flipperServer = new FlipperServerImpl(
|
||||||
|
getFlipperServerConfig(),
|
||||||
|
getLogger(),
|
||||||
|
);
|
||||||
flipperServer.ios.iosBridge = {} as IOSBridge;
|
flipperServer.ios.iosBridge = {} as IOSBridge;
|
||||||
const promises = flipperServer.ios.getAllPromisesForQueryingDevices(
|
const promises = flipperServer.ios.getAllPromisesForQueryingDevices(
|
||||||
true,
|
true,
|
||||||
@@ -66,7 +79,10 @@ test('test getAllPromisesForQueryingDevices when xcode detected', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('test getAllPromisesForQueryingDevices when xcode is not 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;
|
flipperServer.ios.iosBridge = {} as IOSBridge;
|
||||||
const promises = flipperServer.ios.getAllPromisesForQueryingDevices(
|
const promises = flipperServer.ios.getAllPromisesForQueryingDevices(
|
||||||
false,
|
false,
|
||||||
@@ -76,7 +92,10 @@ test('test getAllPromisesForQueryingDevices when xcode is not detected', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('test getAllPromisesForQueryingDevices when xcode and idb are both unavailable', () => {
|
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;
|
flipperServer.ios.iosBridge = {} as IOSBridge;
|
||||||
const promises = flipperServer.ios.getAllPromisesForQueryingDevices(
|
const promises = flipperServer.ios.getAllPromisesForQueryingDevices(
|
||||||
false,
|
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', () => {
|
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;
|
flipperServer.ios.iosBridge = {} as IOSBridge;
|
||||||
const promises = flipperServer.ios.getAllPromisesForQueryingDevices(
|
const promises = flipperServer.ios.getAllPromisesForQueryingDevices(
|
||||||
true,
|
true,
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export class IOSDeviceManager {
|
|||||||
private portForwarders: Array<ChildProcess> = [];
|
private portForwarders: Array<ChildProcess> = [];
|
||||||
|
|
||||||
private portforwardingClient = path.join(
|
private portforwardingClient = path.join(
|
||||||
getFlipperServerConfig().staticPath,
|
getFlipperServerConfig().paths.staticPath,
|
||||||
'PortForwardingMacApp.app',
|
'PortForwardingMacApp.app',
|
||||||
'Contents',
|
'Contents',
|
||||||
'MacOS',
|
'MacOS',
|
||||||
@@ -111,7 +111,7 @@ export class IOSDeviceManager {
|
|||||||
isXcodeDetected: boolean,
|
isXcodeDetected: boolean,
|
||||||
isIdbAvailable: boolean,
|
isIdbAvailable: boolean,
|
||||||
): Array<Promise<any>> {
|
): Array<Promise<any>> {
|
||||||
const config = getFlipperServerConfig();
|
const config = getFlipperServerConfig().settings;
|
||||||
return [
|
return [
|
||||||
isIdbAvailable
|
isIdbAvailable
|
||||||
? getActiveDevices(config.idbPath, config.enablePhysicalIOS).then(
|
? getActiveDevices(config.idbPath, config.enablePhysicalIOS).then(
|
||||||
@@ -130,7 +130,7 @@ export class IOSDeviceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async queryDevices(): Promise<any> {
|
private async queryDevices(): Promise<any> {
|
||||||
const config = getFlipperServerConfig();
|
const config = getFlipperServerConfig().settings;
|
||||||
const isXcodeInstalled = await iosUtil.isXcodeDetected();
|
const isXcodeInstalled = await iosUtil.isXcodeDetected();
|
||||||
const isIdbAvailable = await iosUtil.isAvailable(config.idbPath);
|
const isIdbAvailable = await iosUtil.isAvailable(config.idbPath);
|
||||||
console.debug(
|
console.debug(
|
||||||
@@ -182,21 +182,19 @@ export class IOSDeviceManager {
|
|||||||
|
|
||||||
public async watchIOSDevices() {
|
public async watchIOSDevices() {
|
||||||
// TODO: pull this condition up
|
// TODO: pull this condition up
|
||||||
if (!getFlipperServerConfig().enableIOS) {
|
const settings = getFlipperServerConfig().settings;
|
||||||
|
if (!settings.enableIOS) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const isDetected = await iosUtil.isXcodeDetected();
|
const isDetected = await iosUtil.isXcodeDetected();
|
||||||
this.xcodeCommandLineToolsDetected = isDetected;
|
this.xcodeCommandLineToolsDetected = isDetected;
|
||||||
if (getFlipperServerConfig().enablePhysicalIOS) {
|
if (settings.enablePhysicalIOS) {
|
||||||
this.startDevicePortForwarders();
|
this.startDevicePortForwarders();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// Awaiting the promise here to trigger immediate error handling.
|
// Awaiting the promise here to trigger immediate error handling.
|
||||||
this.iosBridge = await makeIOSBridge(
|
this.iosBridge = await makeIOSBridge(settings.idbPath, isDetected);
|
||||||
getFlipperServerConfig().idbPath,
|
|
||||||
isDetected,
|
|
||||||
);
|
|
||||||
this.queryDevicesForever();
|
this.queryDevicesForever();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// This case is expected if both Xcode and idb are missing.
|
// This case is expected if both Xcode and idb are missing.
|
||||||
|
|||||||
@@ -42,10 +42,6 @@ export default class GK {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static serializeGKs() {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
static async withWhitelistedGK(
|
static async withWhitelistedGK(
|
||||||
id: GKID,
|
id: GKID,
|
||||||
callback: () => Promise<void> | void,
|
callback: () => Promise<void> | void,
|
||||||
@@ -63,4 +59,8 @@ export default class GK {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static allGKs(): GKMap {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,10 +7,8 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export enum ReleaseChannel {
|
import {Tristate} from 'flipper-common';
|
||||||
DEFAULT = 'default',
|
|
||||||
STABLE = 'stable',
|
|
||||||
INSIDERS = 'insiders',
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ReleaseChannel;
|
export async function setupPrefetcher(_settings: {
|
||||||
|
enablePrefetching: Tristate;
|
||||||
|
}) {}
|
||||||
@@ -8,3 +8,20 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export {FlipperServerImpl} from './FlipperServerImpl';
|
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<string, boolean> {
|
||||||
|
if (!loaded) {
|
||||||
|
// this starts fetching gatekeepers, note that they will only be available on next restart!
|
||||||
|
GKImplementation.init();
|
||||||
|
loaded = true;
|
||||||
|
}
|
||||||
|
return GKImplementation.allGKs();
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,22 +7,20 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {default as config, resetConfigForTesting} from '../processConfig';
|
import {loadProcessConfig} from '../processConfig';
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
resetConfigForTesting();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('config is decoded from env', () => {
|
test('config is decoded from env', () => {
|
||||||
process.env.CONFIG = JSON.stringify({
|
const config = loadProcessConfig({
|
||||||
disabledPlugins: ['pluginA', 'pluginB', 'pluginC'],
|
CONFIG: JSON.stringify({
|
||||||
lastWindowPosition: {x: 4, y: 8, width: 15, height: 16},
|
disabledPlugins: ['pluginA', 'pluginB', 'pluginC'],
|
||||||
launcherMsg: 'wubba lubba dub dub',
|
lastWindowPosition: {x: 4, y: 8, width: 15, height: 16},
|
||||||
screenCapturePath: '/my/screenshot/path',
|
launcherMsg: 'wubba lubba dub dub',
|
||||||
launcherEnabled: false,
|
screenCapturePath: '/my/screenshot/path',
|
||||||
|
launcherEnabled: false,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(config()).toEqual({
|
expect(config).toEqual({
|
||||||
disabledPlugins: new Set(['pluginA', 'pluginB', 'pluginC']),
|
disabledPlugins: new Set(['pluginA', 'pluginB', 'pluginC']),
|
||||||
lastWindowPosition: {x: 4, y: 8, width: 15, height: 16},
|
lastWindowPosition: {x: 4, y: 8, width: 15, height: 16},
|
||||||
launcherMsg: 'wubba lubba dub dub',
|
launcherMsg: 'wubba lubba dub dub',
|
||||||
@@ -32,9 +30,7 @@ test('config is decoded from env', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('config is decoded from env with defaults', () => {
|
test('config is decoded from env with defaults', () => {
|
||||||
process.env.CONFIG = '{}';
|
expect(loadProcessConfig({CONFIG: '{}'})).toEqual({
|
||||||
|
|
||||||
expect(config()).toEqual({
|
|
||||||
disabledPlugins: new Set([]),
|
disabledPlugins: new Set([]),
|
||||||
lastWindowPosition: undefined,
|
lastWindowPosition: undefined,
|
||||||
launcherMsg: undefined,
|
launcherMsg: undefined,
|
||||||
91
desktop/flipper-server-core/src/utils/launcherSettings.tsx
Normal file
91
desktop/flipper-server-core/src/utils/launcherSettings.tsx
Normal file
@@ -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<LauncherSettings> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
22
desktop/flipper-server-core/src/utils/processConfig.tsx
Normal file
22
desktop/flipper-server-core/src/utils/processConfig.tsx
Normal file
@@ -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<string>(json.disabledPlugins || []),
|
||||||
|
lastWindowPosition: json.lastWindowPosition,
|
||||||
|
launcherMsg: json.launcherMsg,
|
||||||
|
screenCapturePath: json.screenCapturePath,
|
||||||
|
launcherEnabled:
|
||||||
|
typeof json.launcherEnabled === 'boolean' ? json.launcherEnabled : true,
|
||||||
|
};
|
||||||
|
}
|
||||||
67
desktop/flipper-server-core/src/utils/settings.tsx
Normal file
67
desktop/flipper-server-core/src/utils/settings.tsx
Normal file
@@ -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<Settings> {
|
||||||
|
if (!access(getSettingsFile())) {
|
||||||
|
return getDefaultSettings();
|
||||||
|
}
|
||||||
|
const json = await readFile(getSettingsFile(), {encoding: 'utf8'});
|
||||||
|
return JSON.parse(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveSettings(settings: Settings): Promise<void> {
|
||||||
|
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`;
|
||||||
|
}
|
||||||
@@ -14,7 +14,6 @@
|
|||||||
"@emotion/css": "^11.5.0",
|
"@emotion/css": "^11.5.0",
|
||||||
"@emotion/react": "^11.6.0",
|
"@emotion/react": "^11.6.0",
|
||||||
"@emotion/styled": "^11.6.0",
|
"@emotion/styled": "^11.6.0",
|
||||||
"@iarna/toml": "^2.2.5",
|
|
||||||
"@tanishiking/aho-corasick": "^0.0.1",
|
"@tanishiking/aho-corasick": "^0.0.1",
|
||||||
"@types/archiver": "^5.1.1",
|
"@types/archiver": "^5.1.1",
|
||||||
"@types/uuid": "^8.3.1",
|
"@types/uuid": "^8.3.1",
|
||||||
@@ -67,8 +66,7 @@
|
|||||||
"tmp": "^0.2.1",
|
"tmp": "^0.2.1",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"which": "^2.0.1",
|
"which": "^2.0.1",
|
||||||
"ws": "^8.2.3",
|
"ws": "^8.2.3"
|
||||||
"xdg-basedir": "^4.0.0"
|
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"7zip-bin-mac": "^1.0.1"
|
"7zip-bin-mac": "^1.0.1"
|
||||||
|
|||||||
@@ -10,18 +10,11 @@
|
|||||||
import type {NotificationEvents} from './dispatcher/notifications';
|
import type {NotificationEvents} from './dispatcher/notifications';
|
||||||
import type {PluginNotification} from './reducers/notifications';
|
import type {PluginNotification} from './reducers/notifications';
|
||||||
import type {NotificationConstructorOptions} from 'electron';
|
import type {NotificationConstructorOptions} from 'electron';
|
||||||
import {FlipperLib, TestUtils} from 'flipper-plugin';
|
import {FlipperLib} from 'flipper-plugin';
|
||||||
import path from 'path';
|
import {FlipperServerConfig, ReleaseChannel, Tristate} from 'flipper-common';
|
||||||
import {FlipperServer, Logger} from 'flipper-common';
|
// TODO: those imports are only used for testing, require conditionally?
|
||||||
|
import {tmpdir} from 'os';
|
||||||
type ENVIRONMENT_VARIABLES = 'NODE_ENV' | 'DEV_SERVER_URL' | 'CONFIG';
|
import {resolve} from 'path';
|
||||||
type ENVIRONMENT_PATHS =
|
|
||||||
| 'appPath'
|
|
||||||
| 'homePath'
|
|
||||||
| 'execPath'
|
|
||||||
| 'staticPath'
|
|
||||||
| 'tempPath'
|
|
||||||
| 'desktopPath';
|
|
||||||
|
|
||||||
// Events that are emitted from the main.ts ovr the IPC process bridge in Electron
|
// Events that are emitted from the main.ts ovr the IPC process bridge in Electron
|
||||||
type MainProcessEvents = {
|
type MainProcessEvents = {
|
||||||
@@ -103,20 +96,10 @@ export interface RenderHost {
|
|||||||
): void;
|
): void;
|
||||||
shouldUseDarkColors(): boolean;
|
shouldUseDarkColors(): boolean;
|
||||||
restartFlipper(update?: boolean): void;
|
restartFlipper(update?: boolean): void;
|
||||||
env: Partial<Record<ENVIRONMENT_VARIABLES, string>>;
|
|
||||||
paths: Record<ENVIRONMENT_PATHS, string>;
|
|
||||||
openLink(url: string): void;
|
openLink(url: string): void;
|
||||||
loadDefaultPlugins(): Record<string, any>;
|
loadDefaultPlugins(): Record<string, any>;
|
||||||
startFlipperServer(config: {
|
GK(gatekeeper: string): boolean;
|
||||||
// TODO: this config is temporarily, settings should be loaded/stored by server, not client
|
serverConfig: FlipperServerConfig;
|
||||||
logger: Logger;
|
|
||||||
enableAndroid: boolean;
|
|
||||||
androidHome: string;
|
|
||||||
enableIOS: boolean;
|
|
||||||
enablePhysicalIOS: boolean;
|
|
||||||
idbPath: string;
|
|
||||||
validWebSocketOrigins: string[];
|
|
||||||
}): FlipperServer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRenderHostInstance(): RenderHost {
|
export function getRenderHostInstance(): RenderHost {
|
||||||
@@ -127,6 +110,50 @@ export function getRenderHostInstance(): RenderHost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'test') {
|
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 = {
|
window.FlipperRenderHostInstance = {
|
||||||
processId: -1,
|
processId: -1,
|
||||||
isProduction: false,
|
isProduction: false,
|
||||||
@@ -153,18 +180,12 @@ if (process.env.NODE_ENV === 'test') {
|
|||||||
},
|
},
|
||||||
restartFlipper() {},
|
restartFlipper() {},
|
||||||
openLink() {},
|
openLink() {},
|
||||||
env: process.env,
|
serverConfig: stubConfig,
|
||||||
paths: {
|
|
||||||
appPath: process.cwd(),
|
|
||||||
homePath: `/dev/null`,
|
|
||||||
desktopPath: `/dev/null`,
|
|
||||||
execPath: process.cwd(),
|
|
||||||
staticPath: path.join(process.cwd(), 'static'),
|
|
||||||
tempPath: `/tmp/`,
|
|
||||||
},
|
|
||||||
loadDefaultPlugins() {
|
loadDefaultPlugins() {
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
startFlipperServer: () => TestUtils.createFlipperServerMock(),
|
GK(gk) {
|
||||||
|
return stubConfig.gatekeepers[gk] ?? false;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,10 +37,10 @@ Object {
|
|||||||
},
|
},
|
||||||
"flipperServer": Object {
|
"flipperServer": Object {
|
||||||
"close": [MockFunction],
|
"close": [MockFunction],
|
||||||
|
"connect": [Function],
|
||||||
"exec": [MockFunction],
|
"exec": [MockFunction],
|
||||||
"off": [MockFunction],
|
"off": [MockFunction],
|
||||||
"on": [MockFunction],
|
"on": [MockFunction],
|
||||||
"start": [Function],
|
|
||||||
},
|
},
|
||||||
"pluginMenuEntries": Array [],
|
"pluginMenuEntries": Array [],
|
||||||
"selectedAppId": "TestApp#Android#MockAndroidDevice#serial",
|
"selectedAppId": "TestApp#Android#MockAndroidDevice#serial",
|
||||||
|
|||||||
@@ -11,11 +11,10 @@ import React, {Component} from 'react';
|
|||||||
import {updateSettings, Action} from '../reducers/settings';
|
import {updateSettings, Action} from '../reducers/settings';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import {State as Store} from '../reducers';
|
import {State as Store} from '../reducers';
|
||||||
import {Settings} from '../reducers/settings';
|
|
||||||
import {flush} from '../utils/persistor';
|
import {flush} from '../utils/persistor';
|
||||||
import ToggledSection from './settings/ToggledSection';
|
import ToggledSection from './settings/ToggledSection';
|
||||||
import {isEqual} from 'lodash';
|
import {isEqual} from 'lodash';
|
||||||
import {reportUsage} from 'flipper-common';
|
import {reportUsage, Settings} from 'flipper-common';
|
||||||
import {Modal, Button} from 'antd';
|
import {Modal, Button} from 'antd';
|
||||||
import {Layout, withTrackingScope, _NuxManagerContext} from 'flipper-plugin';
|
import {Layout, withTrackingScope, _NuxManagerContext} from 'flipper-plugin';
|
||||||
import {getRenderHostInstance} from '../RenderHost';
|
import {getRenderHostInstance} from '../RenderHost';
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import {
|
|||||||
Link,
|
Link,
|
||||||
} from '../ui';
|
} from '../ui';
|
||||||
import {LeftRailButton} from '../sandy-chrome/LeftRail';
|
import {LeftRailButton} from '../sandy-chrome/LeftRail';
|
||||||
import GK from '../fb-stubs/GK';
|
|
||||||
import * as UserFeedback from '../fb-stubs/UserFeedback';
|
import * as UserFeedback from '../fb-stubs/UserFeedback';
|
||||||
import {FeedbackPrompt} from '../fb-stubs/UserFeedback';
|
import {FeedbackPrompt} from '../fb-stubs/UserFeedback';
|
||||||
import {StarOutlined} from '@ant-design/icons';
|
import {StarOutlined} from '@ant-design/icons';
|
||||||
@@ -33,6 +32,7 @@ import {useStore} from '../utils/useStore';
|
|||||||
import {isLoggedIn} from '../fb-stubs/user';
|
import {isLoggedIn} from '../fb-stubs/user';
|
||||||
import {useValue} from 'flipper-plugin';
|
import {useValue} from 'flipper-plugin';
|
||||||
import {reportPlatformFailures} from 'flipper-common';
|
import {reportPlatformFailures} from 'flipper-common';
|
||||||
|
import {getRenderHostInstance} from '../RenderHost';
|
||||||
|
|
||||||
type NextAction = 'select-rating' | 'leave-comment' | 'finished';
|
type NextAction = 'select-rating' | 'leave-comment' | 'finished';
|
||||||
|
|
||||||
@@ -281,7 +281,11 @@ export function SandyRatingButton() {
|
|||||||
}, [hasTriggered]);
|
}, [hasTriggered]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (GK.get('flipper_enable_star_ratiings') && !hasTriggered && loggedIn) {
|
if (
|
||||||
|
getRenderHostInstance().GK('flipper_enable_star_ratiings') &&
|
||||||
|
!hasTriggered &&
|
||||||
|
loggedIn
|
||||||
|
) {
|
||||||
reportPlatformFailures(
|
reportPlatformFailures(
|
||||||
UserFeedback.getPrompt().then((prompt) => {
|
UserFeedback.getPrompt().then((prompt) => {
|
||||||
setPromptData(prompt);
|
setPromptData(prompt);
|
||||||
|
|||||||
@@ -12,22 +12,21 @@ import {Radio} from 'antd';
|
|||||||
import {updateSettings, Action} from '../reducers/settings';
|
import {updateSettings, Action} from '../reducers/settings';
|
||||||
import {
|
import {
|
||||||
Action as LauncherAction,
|
Action as LauncherAction,
|
||||||
LauncherSettings,
|
|
||||||
updateLauncherSettings,
|
updateLauncherSettings,
|
||||||
} from '../reducers/launcherSettings';
|
} from '../reducers/launcherSettings';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import {State as Store} from '../reducers';
|
import {State as Store} from '../reducers';
|
||||||
import {Settings, DEFAULT_ANDROID_SDK_PATH} from '../reducers/settings';
|
|
||||||
import {flush} from '../utils/persistor';
|
import {flush} from '../utils/persistor';
|
||||||
import ToggledSection from './settings/ToggledSection';
|
import ToggledSection from './settings/ToggledSection';
|
||||||
import {FilePathConfigField, ConfigText} from './settings/configFields';
|
import {FilePathConfigField, ConfigText} from './settings/configFields';
|
||||||
import KeyboardShortcutInput from './settings/KeyboardShortcutInput';
|
import KeyboardShortcutInput from './settings/KeyboardShortcutInput';
|
||||||
import {isEqual, isMatch, isEmpty} from 'lodash';
|
import {isEqual, isMatch, isEmpty} from 'lodash';
|
||||||
import LauncherSettingsPanel from '../fb-stubs/LauncherSettingsPanel';
|
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 {Modal, message, Button} from 'antd';
|
||||||
import {Layout, withTrackingScope, _NuxManagerContext} from 'flipper-plugin';
|
import {Layout, withTrackingScope, _NuxManagerContext} from 'flipper-plugin';
|
||||||
import {getRenderHostInstance} from '../RenderHost';
|
import {getRenderHostInstance} from '../RenderHost';
|
||||||
|
import {loadTheme} from '../utils/loadTheme';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
onHide: () => void;
|
onHide: () => void;
|
||||||
@@ -142,7 +141,9 @@ class SettingsSheet extends Component<Props, State> {
|
|||||||
}}>
|
}}>
|
||||||
<FilePathConfigField
|
<FilePathConfigField
|
||||||
label="Android SDK location"
|
label="Android SDK location"
|
||||||
resetValue={DEFAULT_ANDROID_SDK_PATH}
|
resetValue={
|
||||||
|
getRenderHostInstance().serverConfig.settings.androidHome
|
||||||
|
}
|
||||||
defaultValue={androidHome}
|
defaultValue={androidHome}
|
||||||
onChange={(v) => {
|
onChange={(v) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -255,6 +256,7 @@ class SettingsSheet extends Component<Props, State> {
|
|||||||
darkMode: event.target.value,
|
darkMode: event.target.value,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
loadTheme(event.target.value);
|
||||||
}}>
|
}}>
|
||||||
<Radio.Button value="dark">Dark</Radio.Button>
|
<Radio.Button value="dark">Dark</Radio.Button>
|
||||||
<Radio.Button value="light">Light</Radio.Button>
|
<Radio.Button value="light">Light</Radio.Button>
|
||||||
|
|||||||
@@ -9,13 +9,12 @@
|
|||||||
|
|
||||||
import {notification, Typography} from 'antd';
|
import {notification, Typography} from 'antd';
|
||||||
import isProduction from '../utils/isProduction';
|
import isProduction from '../utils/isProduction';
|
||||||
import {reportPlatformFailures} from 'flipper-common';
|
import {reportPlatformFailures, ReleaseChannel} from 'flipper-common';
|
||||||
import React, {useEffect, useState} from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import fbConfig from '../fb-stubs/config';
|
import fbConfig from '../fb-stubs/config';
|
||||||
import {useStore} from '../utils/useStore';
|
import {useStore} from '../utils/useStore';
|
||||||
import {getAppVersion} from '../utils/info';
|
import {getAppVersion} from '../utils/info';
|
||||||
import {checkForUpdate} from '../fb-stubs/checkForUpdate';
|
import {checkForUpdate} from '../fb-stubs/checkForUpdate';
|
||||||
import ReleaseChannel from '../ReleaseChannel';
|
|
||||||
|
|
||||||
export type VersionCheckResult =
|
export type VersionCheckResult =
|
||||||
| {
|
| {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export default class ArchivedDevice extends BaseDevice {
|
|||||||
}) {
|
}) {
|
||||||
super(
|
super(
|
||||||
{
|
{
|
||||||
async start() {},
|
async connect() {},
|
||||||
close() {},
|
close() {},
|
||||||
exec(command, ..._args: any[]) {
|
exec(command, ..._args: any[]) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|||||||
@@ -21,12 +21,11 @@ import path from 'path';
|
|||||||
import {createRootReducer, State} from '../../reducers/index';
|
import {createRootReducer, State} from '../../reducers/index';
|
||||||
import {getLogger} from 'flipper-common';
|
import {getLogger} from 'flipper-common';
|
||||||
import configureStore from 'redux-mock-store';
|
import configureStore from 'redux-mock-store';
|
||||||
import {TEST_PASSING_GK, TEST_FAILING_GK} from '../../fb-stubs/GK';
|
|
||||||
import TestPlugin from './TestPlugin';
|
import TestPlugin from './TestPlugin';
|
||||||
import {resetConfigForTesting} from '../../utils/processConfig';
|
|
||||||
import {_SandyPluginDefinition} from 'flipper-plugin';
|
import {_SandyPluginDefinition} from 'flipper-plugin';
|
||||||
import {mocked} from 'ts-jest/utils';
|
import {mocked} from 'ts-jest/utils';
|
||||||
import loadDynamicPlugins from '../../utils/loadDynamicPlugins';
|
import loadDynamicPlugins from '../../utils/loadDynamicPlugins';
|
||||||
|
import {getRenderHostInstance} from '../../RenderHost';
|
||||||
|
|
||||||
const loadDynamicPluginsMock = mocked(loadDynamicPlugins);
|
const loadDynamicPluginsMock = mocked(loadDynamicPlugins);
|
||||||
|
|
||||||
@@ -57,7 +56,6 @@ const sampleBundledPluginDetails: BundledPluginDetails = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
resetConfigForTesting();
|
|
||||||
loadDynamicPluginsMock.mockResolvedValue([]);
|
loadDynamicPluginsMock.mockResolvedValue([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -80,10 +78,13 @@ test('getDynamicPlugins returns empty array on errors', async () => {
|
|||||||
|
|
||||||
test('checkDisabled', () => {
|
test('checkDisabled', () => {
|
||||||
const disabledPlugin = 'pluginName';
|
const disabledPlugin = 'pluginName';
|
||||||
const config = {disabledPlugins: [disabledPlugin]};
|
const hostConfig = getRenderHostInstance().serverConfig;
|
||||||
const orig = process.env.CONFIG;
|
const orig = hostConfig.processConfig;
|
||||||
try {
|
try {
|
||||||
process.env.CONFIG = JSON.stringify(config);
|
hostConfig.processConfig = {
|
||||||
|
...orig,
|
||||||
|
disabledPlugins: new Set([disabledPlugin]),
|
||||||
|
};
|
||||||
const disabled = checkDisabled([]);
|
const disabled = checkDisabled([]);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
@@ -93,7 +94,6 @@ test('checkDisabled', () => {
|
|||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
}),
|
}),
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
disabled({
|
disabled({
|
||||||
...sampleBundledPluginDetails,
|
...sampleBundledPluginDetails,
|
||||||
@@ -102,7 +102,7 @@ test('checkDisabled', () => {
|
|||||||
}),
|
}),
|
||||||
).toBeFalsy();
|
).toBeFalsy();
|
||||||
} finally {
|
} finally {
|
||||||
process.env.CONFIG = orig;
|
hostConfig.processConfig = orig;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ test('checkGK for passing plugin', () => {
|
|||||||
checkGK([])({
|
checkGK([])({
|
||||||
...sampleBundledPluginDetails,
|
...sampleBundledPluginDetails,
|
||||||
name: 'pluginID',
|
name: 'pluginID',
|
||||||
gatekeeper: TEST_PASSING_GK,
|
gatekeeper: 'TEST_PASSING_GK',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
}),
|
}),
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
@@ -133,7 +133,7 @@ test('checkGK for failing plugin', () => {
|
|||||||
const plugins = checkGK(gatekeepedPlugins)({
|
const plugins = checkGK(gatekeepedPlugins)({
|
||||||
...sampleBundledPluginDetails,
|
...sampleBundledPluginDetails,
|
||||||
name,
|
name,
|
||||||
gatekeeper: TEST_FAILING_GK,
|
gatekeeper: 'TEST_FAILING_GK',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
FlipperServer,
|
FlipperServer,
|
||||||
Logger,
|
Logger,
|
||||||
NoLongerConnectedToClientError,
|
NoLongerConnectedToClientError,
|
||||||
|
isTest,
|
||||||
} from 'flipper-common';
|
} from 'flipper-common';
|
||||||
import Client from '../Client';
|
import Client from '../Client';
|
||||||
import {notification} from 'antd';
|
import {notification} from 'antd';
|
||||||
@@ -20,23 +21,12 @@ import BaseDevice from '../devices/BaseDevice';
|
|||||||
import {ClientDescription, timeout} from 'flipper-common';
|
import {ClientDescription, timeout} from 'flipper-common';
|
||||||
import {reportPlatformFailures} from 'flipper-common';
|
import {reportPlatformFailures} from 'flipper-common';
|
||||||
import {sideEffect} from '../utils/sideEffect';
|
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({
|
store.dispatch({
|
||||||
type: 'SET_FLIPPER_SERVER',
|
type: 'SET_FLIPPER_SERVER',
|
||||||
payload: server,
|
payload: server,
|
||||||
@@ -147,25 +137,55 @@ export default async (store: Store, logger: Logger) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
server
|
let sideEffectDisposer: undefined | (() => void);
|
||||||
.start()
|
|
||||||
.then(() => {
|
if (!isTest()) {
|
||||||
console.log(
|
sideEffectDisposer = startSideEffects(store, server);
|
||||||
'Flipper server started and accepting device / client connections',
|
}
|
||||||
);
|
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,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
sideEffectDisposer?.();
|
||||||
server.close();
|
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(
|
export async function handleClientConnected(
|
||||||
server: Pick<FlipperServer, 'exec'>,
|
server: Pick<FlipperServer, 'exec'>,
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Used responsibly.
|
// Used responsibly.
|
||||||
import flipperServer from './flipperServer';
|
|
||||||
import application from './application';
|
import application from './application';
|
||||||
import tracking from './tracking';
|
import tracking from './tracking';
|
||||||
import notifications from './notifications';
|
import notifications from './notifications';
|
||||||
@@ -32,7 +31,6 @@ export default function (store: Store, logger: Logger): () => Promise<void> {
|
|||||||
const dispatchers: Array<Dispatcher> = [
|
const dispatchers: Array<Dispatcher> = [
|
||||||
application,
|
application,
|
||||||
tracking,
|
tracking,
|
||||||
flipperServer,
|
|
||||||
notifications,
|
notifications,
|
||||||
plugins,
|
plugins,
|
||||||
user,
|
user,
|
||||||
|
|||||||
@@ -24,11 +24,9 @@ import {
|
|||||||
MarketplacePluginDetails,
|
MarketplacePluginDetails,
|
||||||
pluginsInitialized,
|
pluginsInitialized,
|
||||||
} from '../reducers/plugins';
|
} from '../reducers/plugins';
|
||||||
import GK from '../fb-stubs/GK';
|
|
||||||
import {FlipperBasePlugin} from '../plugin';
|
import {FlipperBasePlugin} from '../plugin';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {default as config} from '../utils/processConfig';
|
|
||||||
import {notNull} from '../utils/typeUtils';
|
import {notNull} from '../utils/typeUtils';
|
||||||
import {
|
import {
|
||||||
ActivatablePluginDetails,
|
ActivatablePluginDetails,
|
||||||
@@ -151,6 +149,9 @@ export function getLatestCompatibleVersionOfEachPlugin<
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getBundledPlugins(): Promise<Array<BundledPluginDetails>> {
|
async function getBundledPlugins(): Promise<Array<BundledPluginDetails>> {
|
||||||
|
if (process.env.NODE_ENV === 'test') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
// defaultPlugins that are included in the Flipper distributive.
|
// defaultPlugins that are included in the Flipper distributive.
|
||||||
// List of default bundled plugins is written at build time to defaultPlugins/bundled.json.
|
// List of default bundled plugins is written at build time to defaultPlugins/bundled.json.
|
||||||
const pluginPath = getStaticPath(
|
const pluginPath = getStaticPath(
|
||||||
@@ -183,7 +184,7 @@ export const checkGK =
|
|||||||
if (!plugin.gatekeeper) {
|
if (!plugin.gatekeeper) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const result = GK.get(plugin.gatekeeper);
|
const result = getRenderHostInstance().GK(plugin.gatekeeper);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
gatekeepedPlugins.push(plugin);
|
gatekeepedPlugins.push(plugin);
|
||||||
}
|
}
|
||||||
@@ -197,15 +198,16 @@ export const checkGK =
|
|||||||
export const checkDisabled = (
|
export const checkDisabled = (
|
||||||
disabledPlugins: Array<ActivatablePluginDetails>,
|
disabledPlugins: Array<ActivatablePluginDetails>,
|
||||||
) => {
|
) => {
|
||||||
|
const config = getRenderHostInstance().serverConfig;
|
||||||
let enabledList: Set<string> | null = null;
|
let enabledList: Set<string> | null = null;
|
||||||
let disabledList: Set<string> = new Set();
|
let disabledList: Set<string> = new Set();
|
||||||
try {
|
try {
|
||||||
if (process.env.FLIPPER_ENABLED_PLUGINS) {
|
if (config.env.FLIPPER_ENABLED_PLUGINS) {
|
||||||
enabledList = new Set<string>(
|
enabledList = new Set<string>(
|
||||||
process.env.FLIPPER_ENABLED_PLUGINS.split(','),
|
config.env.FLIPPER_ENABLED_PLUGINS.split(','),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
disabledList = config().disabledPlugins;
|
disabledList = config.processConfig.disabledPlugins;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to compute enabled/disabled plugins', e);
|
console.error('Failed to compute enabled/disabled plugins', e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default (store: Store) => {
|
|||||||
const settings = store.getState().settingsState.reactNative;
|
const settings = store.getState().settingsState.reactNative;
|
||||||
const renderHost = getRenderHostInstance();
|
const renderHost = getRenderHostInstance();
|
||||||
|
|
||||||
if (!settings.shortcuts.enabled) {
|
if (!settings?.shortcuts.enabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Tristate} from '../reducers/settings';
|
import {Tristate, ReleaseChannel} from 'flipper-common';
|
||||||
import ReleaseChannel from '../ReleaseChannel';
|
|
||||||
|
|
||||||
export default function (_props: {
|
export default function (_props: {
|
||||||
isPrefetchingEnabled: Tristate;
|
isPrefetchingEnabled: Tristate;
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ReleaseChannel from '../ReleaseChannel';
|
import {ReleaseChannel} from 'flipper-common';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
updateServer: 'https://www.facebook.com/fbflipper/public/latest.json',
|
updateServer: 'https://www.facebook.com/fbflipper/public/latest.json',
|
||||||
|
|||||||
@@ -42,12 +42,4 @@ export default Object.freeze({
|
|||||||
},
|
},
|
||||||
|
|
||||||
SUPPORT_GROUPS: [],
|
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://',
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {StoreEnhancerStoreCreator} from 'redux';
|
import {StoreEnhancerStoreCreator} from 'redux';
|
||||||
import {Store} from './reducers';
|
|
||||||
import {RenderHost} from './RenderHost';
|
import {RenderHost} from './RenderHost';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@@ -17,8 +16,6 @@ declare global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Window {
|
interface Window {
|
||||||
flipperGlobalStoreDispatch: Store['dispatch'];
|
|
||||||
|
|
||||||
__REDUX_DEVTOOLS_EXTENSION__:
|
__REDUX_DEVTOOLS_EXTENSION__:
|
||||||
| undefined
|
| undefined
|
||||||
| (StoreEnhancerStoreCreator & StoreEnhancerStateSanitizer);
|
| (StoreEnhancerStoreCreator & StoreEnhancerStateSanitizer);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Logger} from 'flipper-common';
|
import {Logger, Settings} from 'flipper-common';
|
||||||
import Client from './Client';
|
import Client from './Client';
|
||||||
import {Component} from 'react';
|
import {Component} from 'react';
|
||||||
import BaseDevice from './devices/BaseDevice';
|
import BaseDevice from './devices/BaseDevice';
|
||||||
@@ -15,7 +15,6 @@ import {StaticView} from './reducers/connections';
|
|||||||
import {State as ReduxState} from './reducers';
|
import {State as ReduxState} from './reducers';
|
||||||
import {DEFAULT_MAX_QUEUE_SIZE} from './reducers/pluginMessageQueue';
|
import {DEFAULT_MAX_QUEUE_SIZE} from './reducers/pluginMessageQueue';
|
||||||
import {ActivatablePluginDetails} from 'flipper-plugin-lib';
|
import {ActivatablePluginDetails} from 'flipper-plugin-lib';
|
||||||
import {Settings} from './reducers/settings';
|
|
||||||
import {
|
import {
|
||||||
Notification,
|
Notification,
|
||||||
Idler,
|
Idler,
|
||||||
|
|||||||
@@ -7,30 +7,17 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {default as reducer, updateSettings, Tristate} from '../settings';
|
import {default as reducer, updateSettings} from '../settings';
|
||||||
|
import {Tristate} from 'flipper-common';
|
||||||
test('init', () => {
|
|
||||||
const res = reducer(undefined, {type: 'INIT'});
|
|
||||||
expect(res.enableAndroid).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('updateSettings', () => {
|
test('updateSettings', () => {
|
||||||
const initialSettings = reducer(undefined, {type: 'INIT'});
|
const initialSettings = reducer(undefined, {type: 'INIT'});
|
||||||
const updatedSettings = Object.assign(initialSettings, {
|
const updatedSettings = Object.assign(initialSettings, {
|
||||||
enableAndroid: false,
|
enableAndroid: false,
|
||||||
enablePrefetching: Tristate.True,
|
enablePrefetching: Tristate.True,
|
||||||
jsApps: {
|
|
||||||
webAppLauncher: {
|
|
||||||
height: 900,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
const res = reducer(initialSettings, updateSettings(updatedSettings));
|
const res = reducer(initialSettings, updateSettings(updatedSettings));
|
||||||
|
|
||||||
expect(res.enableAndroid).toBeFalsy();
|
expect(res.enableAndroid).toBeFalsy();
|
||||||
expect(res.enablePrefetching).toEqual(Tristate.True);
|
expect(res.enablePrefetching).toEqual(Tristate.True);
|
||||||
expect(res.jsApps.webAppLauncher.height).toEqual(900);
|
|
||||||
expect(res.jsApps.webAppLauncher.width).toEqual(
|
|
||||||
initialSettings.jsApps.webAppLauncher.width,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,12 +36,8 @@ import supportForm, {
|
|||||||
State as SupportFormState,
|
State as SupportFormState,
|
||||||
Action as SupportFormAction,
|
Action as SupportFormAction,
|
||||||
} from './supportForm';
|
} from './supportForm';
|
||||||
import settings, {
|
import settings, {Action as SettingsAction} from './settings';
|
||||||
Settings as SettingsState,
|
|
||||||
Action as SettingsAction,
|
|
||||||
} from './settings';
|
|
||||||
import launcherSettings, {
|
import launcherSettings, {
|
||||||
LauncherSettings as LauncherSettingsState,
|
|
||||||
Action as LauncherSettingsAction,
|
Action as LauncherSettingsAction,
|
||||||
} from './launcherSettings';
|
} from './launcherSettings';
|
||||||
import pluginManager, {
|
import pluginManager, {
|
||||||
@@ -61,18 +57,13 @@ import usageTracking, {
|
|||||||
State as TrackingState,
|
State as TrackingState,
|
||||||
} from './usageTracking';
|
} from './usageTracking';
|
||||||
import user, {State as UserState, Action as UserAction} from './user';
|
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 {createMigrate, createTransform, persistReducer} from 'redux-persist';
|
||||||
import {PersistPartial} from 'redux-persist/es/persistReducer';
|
import {PersistPartial} from 'redux-persist/es/persistReducer';
|
||||||
|
|
||||||
import {Store as ReduxStore, MiddlewareAPI as ReduxMiddlewareAPI} from 'redux';
|
import {Store as ReduxStore, MiddlewareAPI as ReduxMiddlewareAPI} from 'redux';
|
||||||
import storage from 'redux-persist/lib/storage';
|
import storage from 'redux-persist/lib/storage';
|
||||||
import {TransformConfig} from 'redux-persist/es/createTransform';
|
import {TransformConfig} from 'redux-persist/es/createTransform';
|
||||||
|
import {LauncherSettings, Settings} from 'flipper-common';
|
||||||
|
|
||||||
export type Actions =
|
export type Actions =
|
||||||
| ApplicationAction
|
| ApplicationAction
|
||||||
@@ -97,8 +88,8 @@ export type State = {
|
|||||||
notifications: NotificationsState & PersistPartial;
|
notifications: NotificationsState & PersistPartial;
|
||||||
plugins: PluginsState & PersistPartial;
|
plugins: PluginsState & PersistPartial;
|
||||||
user: UserState & PersistPartial;
|
user: UserState & PersistPartial;
|
||||||
settingsState: SettingsState & PersistPartial;
|
settingsState: Settings;
|
||||||
launcherSettingsState: LauncherSettingsState & PersistPartial;
|
launcherSettingsState: LauncherSettings;
|
||||||
supportForm: SupportFormState;
|
supportForm: SupportFormState;
|
||||||
pluginManager: PluginManagerState;
|
pluginManager: PluginManagerState;
|
||||||
healthchecks: HealthcheckState & PersistPartial;
|
healthchecks: HealthcheckState & PersistPartial;
|
||||||
@@ -109,14 +100,6 @@ export type State = {
|
|||||||
export type Store = ReduxStore<State, Actions>;
|
export type Store = ReduxStore<State, Actions>;
|
||||||
export type MiddlewareAPI = ReduxMiddlewareAPI<Dispatch<Actions>, State>;
|
export type MiddlewareAPI = ReduxMiddlewareAPI<Dispatch<Actions>, State>;
|
||||||
|
|
||||||
const settingsStorage = new JsonFileStorage(
|
|
||||||
resolve(
|
|
||||||
...(xdg.config ? [xdg.config] : [os.homedir(), '.config']),
|
|
||||||
'flipper',
|
|
||||||
'settings.json',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const setTransformer = (config: TransformConfig) =>
|
const setTransformer = (config: TransformConfig) =>
|
||||||
createTransform(
|
createTransform(
|
||||||
(set: Set<string>) => Array.from(set),
|
(set: Set<string>) => Array.from(set),
|
||||||
@@ -124,10 +107,6 @@ const setTransformer = (config: TransformConfig) =>
|
|||||||
config,
|
config,
|
||||||
);
|
);
|
||||||
|
|
||||||
const launcherSettingsStorage = new LauncherSettingsStorage(
|
|
||||||
resolve(launcherConfigDir(), 'flipper-launcher.toml'),
|
|
||||||
);
|
|
||||||
|
|
||||||
export function createRootReducer() {
|
export function createRootReducer() {
|
||||||
return combineReducers<State, Actions>({
|
return combineReducers<State, Actions>({
|
||||||
application,
|
application,
|
||||||
@@ -181,20 +160,8 @@ export function createRootReducer() {
|
|||||||
},
|
},
|
||||||
user,
|
user,
|
||||||
),
|
),
|
||||||
settingsState: persistReducer(
|
settingsState: settings,
|
||||||
{key: 'settings', storage: settingsStorage},
|
launcherSettingsState: launcherSettings,
|
||||||
settings,
|
|
||||||
),
|
|
||||||
launcherSettingsState: persistReducer(
|
|
||||||
{
|
|
||||||
key: 'launcherSettings',
|
|
||||||
storage: launcherSettingsStorage,
|
|
||||||
serialize: false,
|
|
||||||
// @ts-ignore: property is erroneously missing in redux-persist type definitions
|
|
||||||
deserialize: false,
|
|
||||||
},
|
|
||||||
launcherSettings,
|
|
||||||
),
|
|
||||||
healthchecks: persistReducer<HealthcheckState, Actions>(
|
healthchecks: persistReducer<HealthcheckState, Actions>(
|
||||||
{
|
{
|
||||||
key: 'healthchecks',
|
key: 'healthchecks',
|
||||||
|
|||||||
@@ -7,26 +7,18 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {LauncherSettings} from 'flipper-common';
|
||||||
|
import {getRenderHostInstance} from '../RenderHost';
|
||||||
import {Actions} from './index';
|
import {Actions} from './index';
|
||||||
import ReleaseChannel from '../ReleaseChannel';
|
|
||||||
|
|
||||||
export type LauncherSettings = {
|
|
||||||
releaseChannel: ReleaseChannel;
|
|
||||||
ignoreLocalPin: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Action = {
|
export type Action = {
|
||||||
type: 'UPDATE_LAUNCHER_SETTINGS';
|
type: 'UPDATE_LAUNCHER_SETTINGS';
|
||||||
payload: LauncherSettings;
|
payload: LauncherSettings;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultLauncherSettings: LauncherSettings = {
|
|
||||||
releaseChannel: ReleaseChannel.DEFAULT,
|
|
||||||
ignoreLocalPin: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function reducer(
|
export default function reducer(
|
||||||
state: LauncherSettings = defaultLauncherSettings,
|
state: LauncherSettings = getRenderHostInstance().serverConfig
|
||||||
|
.launcherSettings,
|
||||||
action: Actions,
|
action: Actions,
|
||||||
): LauncherSettings {
|
): LauncherSettings {
|
||||||
if (action.type === 'UPDATE_LAUNCHER_SETTINGS') {
|
if (action.type === 'UPDATE_LAUNCHER_SETTINGS') {
|
||||||
|
|||||||
@@ -8,45 +8,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {Actions} from './index';
|
import {Actions} from './index';
|
||||||
import os from 'os';
|
|
||||||
import {getRenderHostInstance} from '../RenderHost';
|
import {getRenderHostInstance} from '../RenderHost';
|
||||||
|
import {Settings} from 'flipper-common';
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Action =
|
export type Action =
|
||||||
| {type: 'INIT'}
|
| {type: 'INIT'}
|
||||||
@@ -55,36 +18,8 @@ export type Action =
|
|||||||
payload: Settings;
|
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(
|
export default function reducer(
|
||||||
state: Settings = initialState,
|
state: Settings = getRenderHostInstance().serverConfig.settings,
|
||||||
action: Actions,
|
action: Actions,
|
||||||
): Settings {
|
): Settings {
|
||||||
if (action.type === 'UPDATE_SETTINGS') {
|
if (action.type === 'UPDATE_SETTINGS') {
|
||||||
@@ -99,13 +34,3 @@ export function updateSettings(settings: Settings): Action {
|
|||||||
payload: settings,
|
payload: settings,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDefaultAndroidSdkPath() {
|
|
||||||
return os.platform() === 'win32' ? getWindowsSdkPath() : '/opt/android_sdk';
|
|
||||||
}
|
|
||||||
|
|
||||||
function getWindowsSdkPath() {
|
|
||||||
return `${
|
|
||||||
getRenderHostInstance().paths.homePath
|
|
||||||
}\\AppData\\Local\\android\\sdk`;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ import constants from '../fb-stubs/constants';
|
|||||||
import config from '../fb-stubs/config';
|
import config from '../fb-stubs/config';
|
||||||
import isProduction from '../utils/isProduction';
|
import isProduction from '../utils/isProduction';
|
||||||
import {getAppVersion} from '../utils/info';
|
import {getAppVersion} from '../utils/info';
|
||||||
import ReleaseChannel from '../ReleaseChannel';
|
|
||||||
import {getFlipperLib} from 'flipper-plugin';
|
import {getFlipperLib} from 'flipper-plugin';
|
||||||
import ChangelogSheet from '../chrome/ChangelogSheet';
|
import ChangelogSheet from '../chrome/ChangelogSheet';
|
||||||
|
import {ReleaseChannel} from 'flipper-common';
|
||||||
|
|
||||||
const RowContainer = styled(FlexRow)({
|
const RowContainer = styled(FlexRow)({
|
||||||
alignItems: 'flex-start',
|
alignItems: 'flex-start',
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ import Client from '../../Client';
|
|||||||
import {State} from '../../reducers';
|
import {State} from '../../reducers';
|
||||||
import {brandColors, brandIcons, colors} from '../../ui/components/colors';
|
import {brandColors, brandIcons, colors} from '../../ui/components/colors';
|
||||||
import {TroubleshootingGuide} from './fb-stubs/TroubleshootingGuide';
|
import {TroubleshootingGuide} from './fb-stubs/TroubleshootingGuide';
|
||||||
import GK from '../../fb-stubs/GK';
|
|
||||||
import {getSelectableDevices} from '../../selectors/connections';
|
import {getSelectableDevices} from '../../selectors/connections';
|
||||||
|
import {getRenderHostInstance} from '../../RenderHost';
|
||||||
|
|
||||||
const {Text} = Typography;
|
const {Text} = Typography;
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ export function AppSelector() {
|
|||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
)}
|
)}
|
||||||
<TroubleshootingGuide
|
<TroubleshootingGuide
|
||||||
showGuide={GK.get('flipper_self_sufficiency')}
|
showGuide={getRenderHostInstance().GK('flipper_self_sufficiency')}
|
||||||
devicesDetected={entries.length}
|
devicesDetected={entries.length}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -10,16 +10,11 @@
|
|||||||
import {Provider} from 'react-redux';
|
import {Provider} from 'react-redux';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
import GK from './fb-stubs/GK';
|
|
||||||
import {init as initLogger} from './fb-stubs/Logger';
|
import {init as initLogger} from './fb-stubs/Logger';
|
||||||
import {SandyApp} from './sandy-chrome/SandyApp';
|
import {SandyApp} from './sandy-chrome/SandyApp';
|
||||||
import setupPrefetcher from './fb-stubs/Prefetcher';
|
|
||||||
import {Persistor, persistStore} from 'redux-persist';
|
import {Persistor, persistStore} from 'redux-persist';
|
||||||
import {Store} from './reducers/index';
|
|
||||||
import dispatcher from './dispatcher/index';
|
import dispatcher from './dispatcher/index';
|
||||||
import TooltipProvider from './ui/components/TooltipProvider';
|
import TooltipProvider from './ui/components/TooltipProvider';
|
||||||
import config from './utils/processConfig';
|
|
||||||
import {initLauncherHooks} from './utils/launcher';
|
|
||||||
import {setPersistor} from './utils/persistor';
|
import {setPersistor} from './utils/persistor';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
@@ -28,7 +23,6 @@ import {cache} from '@emotion/css';
|
|||||||
import {CacheProvider} from '@emotion/react';
|
import {CacheProvider} from '@emotion/react';
|
||||||
import {initializeFlipperLibImplementation} from './utils/flipperLibImplementation';
|
import {initializeFlipperLibImplementation} from './utils/flipperLibImplementation';
|
||||||
import {enableConsoleHook} from './chrome/ConsoleLogs';
|
import {enableConsoleHook} from './chrome/ConsoleLogs';
|
||||||
import {sideEffect} from './utils/sideEffect';
|
|
||||||
import {
|
import {
|
||||||
_NuxManagerContext,
|
_NuxManagerContext,
|
||||||
_createNuxManager,
|
_createNuxManager,
|
||||||
@@ -49,11 +43,14 @@ import {PersistGate} from 'redux-persist/integration/react';
|
|||||||
import {
|
import {
|
||||||
setLoggerInstance,
|
setLoggerInstance,
|
||||||
setUserSessionManagerInstance,
|
setUserSessionManagerInstance,
|
||||||
GK as flipperCommonGK,
|
Settings,
|
||||||
|
FlipperServer,
|
||||||
} from 'flipper-common';
|
} from 'flipper-common';
|
||||||
import {internGraphPOSTAPIRequest} from './fb-stubs/user';
|
import {internGraphPOSTAPIRequest} from './fb-stubs/user';
|
||||||
import {getRenderHostInstance} from './RenderHost';
|
import {getRenderHostInstance} from './RenderHost';
|
||||||
import {startGlobalErrorHandling} from './utils/globalErrorHandling';
|
import {startGlobalErrorHandling} from './utils/globalErrorHandling';
|
||||||
|
import {loadTheme} from './utils/loadTheme';
|
||||||
|
import {connectFlipperServerToStore} from './dispatcher/flipperServer';
|
||||||
|
|
||||||
class AppFrame extends React.Component<
|
class AppFrame extends React.Component<
|
||||||
{logger: Logger; persistor: Persistor},
|
{logger: Logger; persistor: Persistor},
|
||||||
@@ -145,8 +142,7 @@ class AppFrame extends React.Component<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setProcessState(store: Store) {
|
function setProcessState(settings: Settings) {
|
||||||
const settings = store.getState().settingsState;
|
|
||||||
const androidHome = settings.androidHome;
|
const androidHome = settings.androidHome;
|
||||||
const idbPath = settings.idbPath;
|
const idbPath = settings.idbPath;
|
||||||
|
|
||||||
@@ -162,27 +158,22 @@ function setProcessState(store: Store) {
|
|||||||
.join(':') +
|
.join(':') +
|
||||||
`:${idbPath}` +
|
`:${idbPath}` +
|
||||||
`:${process.env.PATH}`;
|
`:${process.env.PATH}`;
|
||||||
|
|
||||||
window.requestIdleCallback(() => {
|
|
||||||
setupPrefetcher(settings);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function init(flipperServer: FlipperServer) {
|
||||||
GK.init();
|
const settings = getRenderHostInstance().serverConfig.settings;
|
||||||
|
|
||||||
// TODO: centralise all those initialisations in a single configuration call
|
|
||||||
flipperCommonGK.get = (name) => GK.get(name);
|
|
||||||
const store = getStore();
|
const store = getStore();
|
||||||
const logger = initLogger(store);
|
const logger = initLogger(store);
|
||||||
setLoggerInstance(logger);
|
|
||||||
|
|
||||||
|
setLoggerInstance(logger);
|
||||||
startGlobalErrorHandling();
|
startGlobalErrorHandling();
|
||||||
|
loadTheme(settings.darkMode);
|
||||||
|
connectFlipperServerToStore(flipperServer, store, logger);
|
||||||
|
|
||||||
// rehydrate app state before exposing init
|
// rehydrate app state before exposing init
|
||||||
const persistor = persistStore(store, undefined, () => {
|
const persistor = persistStore(store, undefined, () => {
|
||||||
// Make sure process state is set before dispatchers run
|
// Make sure process state is set before dispatchers run
|
||||||
setProcessState(store);
|
setProcessState(settings);
|
||||||
dispatcher(store, logger);
|
dispatcher(store, logger);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -205,39 +196,25 @@ function init() {
|
|||||||
<AppFrame logger={logger} persistor={persistor} />,
|
<AppFrame logger={logger} persistor={persistor} />,
|
||||||
document.getElementById('root'),
|
document.getElementById('root'),
|
||||||
);
|
);
|
||||||
initLauncherHooks(config(), store);
|
|
||||||
enableConsoleHook();
|
|
||||||
window.flipperGlobalStoreDispatch = store.dispatch;
|
|
||||||
|
|
||||||
// listen to settings and load the right theme
|
enableConsoleHook();
|
||||||
sideEffect(
|
|
||||||
store,
|
const launcherMessage =
|
||||||
{name: 'loadTheme', fireImmediately: false, throttleMs: 500},
|
getRenderHostInstance().serverConfig.processConfig.launcherMsg;
|
||||||
(state) => state.settingsState.darkMode,
|
if (launcherMessage) {
|
||||||
(theme) => {
|
store.dispatch({
|
||||||
let shouldUseDarkMode = false;
|
type: 'LAUNCHER_MSG',
|
||||||
if (theme === 'dark') {
|
payload: {
|
||||||
shouldUseDarkMode = true;
|
severity: 'warning',
|
||||||
} else if (theme === 'light') {
|
message: launcherMessage,
|
||||||
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);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function startFlipperDesktop() {
|
export async function startFlipperDesktop(flipperServer: FlipperServer) {
|
||||||
setImmediate(() => {
|
getRenderHostInstance(); // renderHost instance should be set at this point!
|
||||||
// make sure all modules are loaded
|
init(flipperServer);
|
||||||
// @ts-ignore
|
|
||||||
window.flipperInit = init;
|
|
||||||
window.dispatchEvent(new Event('flipper-store-ready'));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const CodeBlock = styled(Input.TextArea)({
|
const CodeBlock = styled(Input.TextArea)({
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export class TestDevice extends BaseDevice {
|
|||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
{
|
{
|
||||||
async start() {},
|
async connect() {},
|
||||||
on: jest.fn(),
|
on: jest.fn(),
|
||||||
off: jest.fn(),
|
off: jest.fn(),
|
||||||
exec: jest.fn(),
|
exec: jest.fn(),
|
||||||
|
|||||||
@@ -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);
|
|
||||||
});
|
|
||||||
@@ -11,7 +11,6 @@ import {_setFlipperLibImplementation} from 'flipper-plugin';
|
|||||||
import type {Logger} from 'flipper-common';
|
import type {Logger} from 'flipper-common';
|
||||||
import type {Store} from '../reducers';
|
import type {Store} from '../reducers';
|
||||||
import createPaste from '../fb-stubs/createPaste';
|
import createPaste from '../fb-stubs/createPaste';
|
||||||
import GK from '../fb-stubs/GK';
|
|
||||||
import type BaseDevice from '../devices/BaseDevice';
|
import type BaseDevice from '../devices/BaseDevice';
|
||||||
import constants from '../fb-stubs/constants';
|
import constants from '../fb-stubs/constants';
|
||||||
import {addNotification} from '../reducers/notifications';
|
import {addNotification} from '../reducers/notifications';
|
||||||
@@ -32,9 +31,7 @@ export function initializeFlipperLibImplementation(
|
|||||||
store.dispatch(setMenuEntries(entries));
|
store.dispatch(setMenuEntries(entries));
|
||||||
},
|
},
|
||||||
createPaste,
|
createPaste,
|
||||||
GK(gatekeeper: string) {
|
GK: renderHost.GK,
|
||||||
return GK.get(gatekeeper);
|
|
||||||
},
|
|
||||||
selectPlugin(device, client, pluginId, deeplink) {
|
selectPlugin(device, client, pluginId, deeplink) {
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: 'SELECT_PLUGIN',
|
type: 'SELECT_PLUGIN',
|
||||||
@@ -63,8 +60,8 @@ export function initializeFlipperLibImplementation(
|
|||||||
importFile: renderHost.importFile,
|
importFile: renderHost.importFile,
|
||||||
exportFile: renderHost.exportFile,
|
exportFile: renderHost.exportFile,
|
||||||
paths: {
|
paths: {
|
||||||
appPath: renderHost.paths.appPath,
|
appPath: renderHost.serverConfig.paths.appPath,
|
||||||
homePath: renderHost.paths.homePath,
|
homePath: renderHost.serverConfig.paths.homePath,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,18 +73,14 @@ export function buildLocalIconURL(name: string, size: number, density: number) {
|
|||||||
export function buildIconURLSync(name: string, size: number, density: number) {
|
export function buildIconURLSync(name: string, size: number, density: number) {
|
||||||
const icon = getIconPartsFromName(name);
|
const icon = getIconPartsFromName(name);
|
||||||
// eslint-disable-next-line prettier/prettier
|
// eslint-disable-next-line prettier/prettier
|
||||||
const url = `https://facebook.com/assets/?name=${
|
const url = `https://facebook.com/assets/?name=${icon.trimmedName}&variant=${icon.variant}&size=${size}&set=facebook_icons&density=${density}x`;
|
||||||
icon.trimmedName
|
|
||||||
}&variant=${
|
|
||||||
icon.variant
|
|
||||||
}&size=${size}&set=facebook_icons&density=${density}x`;
|
|
||||||
if (
|
if (
|
||||||
typeof window !== 'undefined' &&
|
typeof window !== 'undefined' &&
|
||||||
(!getIconsSync()[name] || !getIconsSync()[name].includes(size))
|
(!getIconsSync()[name] || !getIconsSync()[name].includes(size))
|
||||||
) {
|
) {
|
||||||
// From utils/isProduction
|
// From utils/isProduction
|
||||||
const isProduction = !/node_modules[\\/]electron[\\/]/.test(
|
const isProduction = !/node_modules[\\/]electron[\\/]/.test(
|
||||||
getRenderHostInstance().paths.execPath,
|
getRenderHostInstance().serverConfig.paths.execPath,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isProduction) {
|
if (!isProduction) {
|
||||||
@@ -108,9 +104,7 @@ export function buildIconURLSync(name: string, size: number, density: number) {
|
|||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
// eslint-disable-next-line prettier/prettier
|
// 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}: ${
|
`Trying to use icon '${name}' with size ${size} and density ${density}, however the icon doesn't seem to exists at ${url}: ${res.status}`,
|
||||||
res.status
|
|
||||||
}`,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -129,7 +123,7 @@ export function getIconURLSync(
|
|||||||
name: string,
|
name: string,
|
||||||
size: number,
|
size: number,
|
||||||
density: number,
|
density: number,
|
||||||
basePath: string = getRenderHostInstance().paths.appPath,
|
basePath: string = getRenderHostInstance().serverConfig.paths.appPath,
|
||||||
) {
|
) {
|
||||||
if (name.indexOf('/') > -1) {
|
if (name.indexOf('/') > -1) {
|
||||||
return name;
|
return name;
|
||||||
|
|||||||
@@ -9,13 +9,13 @@
|
|||||||
|
|
||||||
import {PluginDetails} from 'flipper-plugin-lib';
|
import {PluginDetails} from 'flipper-plugin-lib';
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
import GK from '../fb-stubs/GK';
|
import {getRenderHostInstance} from '../RenderHost';
|
||||||
import {getAppVersion} from './info';
|
import {getAppVersion} from './info';
|
||||||
|
|
||||||
export function isPluginCompatible(plugin: PluginDetails) {
|
export function isPluginCompatible(plugin: PluginDetails) {
|
||||||
const flipperVersion = getAppVersion();
|
const flipperVersion = getAppVersion();
|
||||||
return (
|
return (
|
||||||
GK.get('flipper_disable_plugin_compatibility_checks') ||
|
getRenderHostInstance().GK('flipper_disable_plugin_compatibility_checks') ||
|
||||||
flipperVersion === '0.0.0' ||
|
flipperVersion === '0.0.0' ||
|
||||||
!plugin.engines?.flipper ||
|
!plugin.engines?.flipper ||
|
||||||
semver.lte(plugin.engines?.flipper, flipperVersion)
|
semver.lte(plugin.engines?.flipper, flipperVersion)
|
||||||
|
|||||||
@@ -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<any> {
|
|
||||||
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<any> {
|
|
||||||
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<any> {
|
|
||||||
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<void> {
|
|
||||||
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<void> {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<any> {
|
|
||||||
return await this.parseFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
async setItem(_key: string, value: LauncherSettings): Promise<any> {
|
|
||||||
const originalValue = await this.parseFile();
|
|
||||||
await this.writeFile(value);
|
|
||||||
return originalValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
removeItem(_key: string): Promise<void> {
|
|
||||||
return this.writeFile(defaultLauncherSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async parseFile(): Promise<LauncherSettings> {
|
|
||||||
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<void> {
|
|
||||||
this.ensureDirExists();
|
|
||||||
const content = serialize(value);
|
|
||||||
return fs.writeFile(this.filepath, content);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ensureDirExists(): Promise<void> {
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -23,6 +23,9 @@ import {getStaticPath} from '../utils/pathUtils';
|
|||||||
export default async function loadDynamicPlugins(): Promise<
|
export default async function loadDynamicPlugins(): Promise<
|
||||||
InstalledPluginDetails[]
|
InstalledPluginDetails[]
|
||||||
> {
|
> {
|
||||||
|
if (process.env.NODE_ENV === 'test') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
if (process.env.FLIPPER_FAST_REFRESH) {
|
if (process.env.FLIPPER_FAST_REFRESH) {
|
||||||
console.log(
|
console.log(
|
||||||
'❌ Skipping loading of dynamic plugins because Fast Refresh is enabled. Fast Refresh only works with bundled plugins.',
|
'❌ Skipping loading of dynamic plugins because Fast Refresh is enabled. Fast Refresh only works with bundled plugins.',
|
||||||
|
|||||||
26
desktop/flipper-ui-core/src/utils/loadTheme.tsx
Normal file
26
desktop/flipper-ui-core/src/utils/loadTheme.tsx
Normal file
@@ -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);
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ import {promisify} from 'util';
|
|||||||
import {getRenderHostInstance} from '../RenderHost';
|
import {getRenderHostInstance} from '../RenderHost';
|
||||||
|
|
||||||
const getPackageJSON = async () => {
|
const getPackageJSON = async () => {
|
||||||
const base = getRenderHostInstance().paths.appPath;
|
const base = getRenderHostInstance().serverConfig.paths.appPath;
|
||||||
const content = await promisify(fs.readFile)(
|
const content = await promisify(fs.readFile)(
|
||||||
path.join(base, 'package.json'),
|
path.join(base, 'package.json'),
|
||||||
'utf-8',
|
'utf-8',
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export function getStaticPath(
|
|||||||
relativePath: string = '.',
|
relativePath: string = '.',
|
||||||
{asarUnpacked}: {asarUnpacked: boolean} = {asarUnpacked: false},
|
{asarUnpacked}: {asarUnpacked: boolean} = {asarUnpacked: false},
|
||||||
) {
|
) {
|
||||||
const staticDir = getRenderHostInstance().paths.staticPath;
|
const staticDir = getRenderHostInstance().serverConfig.paths.staticPath;
|
||||||
const absolutePath = path.resolve(staticDir, relativePath);
|
const absolutePath = path.resolve(staticDir, relativePath);
|
||||||
// Unfortunately, path.resolve, fs.pathExists, fs.read etc do not automatically work with asarUnpacked files.
|
// 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.
|
// All these functions still look for files in "app.asar" even if they are unpacked.
|
||||||
|
|||||||
@@ -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<string>;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@@ -12,12 +12,12 @@ import path from 'path';
|
|||||||
import BaseDevice from '../devices/BaseDevice';
|
import BaseDevice from '../devices/BaseDevice';
|
||||||
import {reportPlatformFailures} from 'flipper-common';
|
import {reportPlatformFailures} from 'flipper-common';
|
||||||
import expandTilde from 'expand-tilde';
|
import expandTilde from 'expand-tilde';
|
||||||
import config from '../utils/processConfig';
|
|
||||||
import {getRenderHostInstance} from '../RenderHost';
|
import {getRenderHostInstance} from '../RenderHost';
|
||||||
|
|
||||||
export function getCaptureLocation() {
|
export function getCaptureLocation() {
|
||||||
return expandTilde(
|
return expandTilde(
|
||||||
config().screenCapturePath || getRenderHostInstance().paths.desktopPath,
|
getRenderHostInstance().serverConfig.processConfig.screenCapturePath ||
|
||||||
|
getRenderHostInstance().serverConfig.paths.desktopPath,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
import isProduction from '../utils/isProduction';
|
import isProduction from '../utils/isProduction';
|
||||||
import {getAppVersion} from './info';
|
import {getAppVersion} from './info';
|
||||||
import config from '../fb-stubs/config';
|
import config from '../fb-stubs/config';
|
||||||
import ReleaseChannel from '../ReleaseChannel';
|
import {ReleaseChannel} from 'flipper-common';
|
||||||
|
|
||||||
export function getVersionString() {
|
export function getVersionString() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -98,8 +98,6 @@
|
|||||||
openError('Script failure. Check Chrome console for more info.');
|
openError('Script failure. Check Chrome console for more info.');
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('flipper-store-ready', () => global.flipperInit());
|
|
||||||
|
|
||||||
document.body.appendChild(script);
|
document.body.appendChild(script);
|
||||||
}
|
}
|
||||||
init();
|
init();
|
||||||
|
|||||||
@@ -26,8 +26,6 @@
|
|||||||
console.error("Failed to initialize theme", e);
|
console.error("Failed to initialize theme", e);
|
||||||
document.getElementById('flipper-theme-import').href="themes/light.css";
|
document.getElementById('flipper-theme-import').href="themes/light.css";
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('flipper-store-ready', () => global.flipperInit());
|
|
||||||
</script>
|
</script>
|
||||||
<script src="bundle.js"></script>
|
<script src="bundle.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user