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
@@ -1,16 +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
|
||||
*/
|
||||
|
||||
export enum ReleaseChannel {
|
||||
DEFAULT = 'default',
|
||||
STABLE = 'stable',
|
||||
INSIDERS = 'insiders',
|
||||
}
|
||||
|
||||
export default ReleaseChannel;
|
||||
@@ -10,18 +10,11 @@
|
||||
import type {NotificationEvents} from './dispatcher/notifications';
|
||||
import type {PluginNotification} from './reducers/notifications';
|
||||
import type {NotificationConstructorOptions} from 'electron';
|
||||
import {FlipperLib, TestUtils} from 'flipper-plugin';
|
||||
import path from 'path';
|
||||
import {FlipperServer, Logger} from 'flipper-common';
|
||||
|
||||
type ENVIRONMENT_VARIABLES = 'NODE_ENV' | 'DEV_SERVER_URL' | 'CONFIG';
|
||||
type ENVIRONMENT_PATHS =
|
||||
| 'appPath'
|
||||
| 'homePath'
|
||||
| 'execPath'
|
||||
| 'staticPath'
|
||||
| 'tempPath'
|
||||
| 'desktopPath';
|
||||
import {FlipperLib} from 'flipper-plugin';
|
||||
import {FlipperServerConfig, ReleaseChannel, Tristate} from 'flipper-common';
|
||||
// TODO: those imports are only used for testing, require conditionally?
|
||||
import {tmpdir} from 'os';
|
||||
import {resolve} from 'path';
|
||||
|
||||
// Events that are emitted from the main.ts ovr the IPC process bridge in Electron
|
||||
type MainProcessEvents = {
|
||||
@@ -103,20 +96,10 @@ export interface RenderHost {
|
||||
): void;
|
||||
shouldUseDarkColors(): boolean;
|
||||
restartFlipper(update?: boolean): void;
|
||||
env: Partial<Record<ENVIRONMENT_VARIABLES, string>>;
|
||||
paths: Record<ENVIRONMENT_PATHS, string>;
|
||||
openLink(url: string): void;
|
||||
loadDefaultPlugins(): Record<string, any>;
|
||||
startFlipperServer(config: {
|
||||
// TODO: this config is temporarily, settings should be loaded/stored by server, not client
|
||||
logger: Logger;
|
||||
enableAndroid: boolean;
|
||||
androidHome: string;
|
||||
enableIOS: boolean;
|
||||
enablePhysicalIOS: boolean;
|
||||
idbPath: string;
|
||||
validWebSocketOrigins: string[];
|
||||
}): FlipperServer;
|
||||
GK(gatekeeper: string): boolean;
|
||||
serverConfig: FlipperServerConfig;
|
||||
}
|
||||
|
||||
export function getRenderHostInstance(): RenderHost {
|
||||
@@ -127,6 +110,50 @@ export function getRenderHostInstance(): RenderHost {
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
const rootPath = resolve(__dirname, '..', '..');
|
||||
const stubConfig: FlipperServerConfig = {
|
||||
env: {...process.env},
|
||||
gatekeepers: {
|
||||
TEST_PASSING_GK: true,
|
||||
TEST_FAILING_GK: false,
|
||||
},
|
||||
isProduction: false,
|
||||
launcherSettings: {
|
||||
ignoreLocalPin: false,
|
||||
releaseChannel: ReleaseChannel.DEFAULT,
|
||||
},
|
||||
paths: {
|
||||
appPath: rootPath,
|
||||
desktopPath: `/dev/null`,
|
||||
execPath: process.execPath,
|
||||
homePath: `/dev/null`,
|
||||
staticPath: resolve(rootPath, 'static'),
|
||||
tempPath: tmpdir(),
|
||||
},
|
||||
processConfig: {
|
||||
disabledPlugins: new Set(),
|
||||
lastWindowPosition: null,
|
||||
launcherEnabled: false,
|
||||
launcherMsg: null,
|
||||
screenCapturePath: `/dev/null`,
|
||||
},
|
||||
settings: {
|
||||
androidHome: `/dev/null`,
|
||||
darkMode: 'light',
|
||||
enableAndroid: false,
|
||||
enableIOS: false,
|
||||
enablePhysicalIOS: false,
|
||||
enablePrefetching: Tristate.False,
|
||||
idbPath: `/dev/null`,
|
||||
reactNative: {
|
||||
shortcuts: {enabled: false, openDevMenu: '', reload: ''},
|
||||
},
|
||||
showWelcomeAtStartup: false,
|
||||
suppressPluginErrors: false,
|
||||
},
|
||||
validWebSocketOrigins: [],
|
||||
};
|
||||
|
||||
window.FlipperRenderHostInstance = {
|
||||
processId: -1,
|
||||
isProduction: false,
|
||||
@@ -153,18 +180,12 @@ if (process.env.NODE_ENV === 'test') {
|
||||
},
|
||||
restartFlipper() {},
|
||||
openLink() {},
|
||||
env: process.env,
|
||||
paths: {
|
||||
appPath: process.cwd(),
|
||||
homePath: `/dev/null`,
|
||||
desktopPath: `/dev/null`,
|
||||
execPath: process.cwd(),
|
||||
staticPath: path.join(process.cwd(), 'static'),
|
||||
tempPath: `/tmp/`,
|
||||
},
|
||||
serverConfig: stubConfig,
|
||||
loadDefaultPlugins() {
|
||||
return {};
|
||||
},
|
||||
startFlipperServer: () => TestUtils.createFlipperServerMock(),
|
||||
GK(gk) {
|
||||
return stubConfig.gatekeepers[gk] ?? false;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,10 +37,10 @@ Object {
|
||||
},
|
||||
"flipperServer": Object {
|
||||
"close": [MockFunction],
|
||||
"connect": [Function],
|
||||
"exec": [MockFunction],
|
||||
"off": [MockFunction],
|
||||
"on": [MockFunction],
|
||||
"start": [Function],
|
||||
},
|
||||
"pluginMenuEntries": Array [],
|
||||
"selectedAppId": "TestApp#Android#MockAndroidDevice#serial",
|
||||
|
||||
@@ -11,11 +11,10 @@ import React, {Component} from 'react';
|
||||
import {updateSettings, Action} from '../reducers/settings';
|
||||
import {connect} from 'react-redux';
|
||||
import {State as Store} from '../reducers';
|
||||
import {Settings} from '../reducers/settings';
|
||||
import {flush} from '../utils/persistor';
|
||||
import ToggledSection from './settings/ToggledSection';
|
||||
import {isEqual} from 'lodash';
|
||||
import {reportUsage} from 'flipper-common';
|
||||
import {reportUsage, Settings} from 'flipper-common';
|
||||
import {Modal, Button} from 'antd';
|
||||
import {Layout, withTrackingScope, _NuxManagerContext} from 'flipper-plugin';
|
||||
import {getRenderHostInstance} from '../RenderHost';
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
Link,
|
||||
} from '../ui';
|
||||
import {LeftRailButton} from '../sandy-chrome/LeftRail';
|
||||
import GK from '../fb-stubs/GK';
|
||||
import * as UserFeedback from '../fb-stubs/UserFeedback';
|
||||
import {FeedbackPrompt} from '../fb-stubs/UserFeedback';
|
||||
import {StarOutlined} from '@ant-design/icons';
|
||||
@@ -33,6 +32,7 @@ import {useStore} from '../utils/useStore';
|
||||
import {isLoggedIn} from '../fb-stubs/user';
|
||||
import {useValue} from 'flipper-plugin';
|
||||
import {reportPlatformFailures} from 'flipper-common';
|
||||
import {getRenderHostInstance} from '../RenderHost';
|
||||
|
||||
type NextAction = 'select-rating' | 'leave-comment' | 'finished';
|
||||
|
||||
@@ -281,7 +281,11 @@ export function SandyRatingButton() {
|
||||
}, [hasTriggered]);
|
||||
|
||||
useEffect(() => {
|
||||
if (GK.get('flipper_enable_star_ratiings') && !hasTriggered && loggedIn) {
|
||||
if (
|
||||
getRenderHostInstance().GK('flipper_enable_star_ratiings') &&
|
||||
!hasTriggered &&
|
||||
loggedIn
|
||||
) {
|
||||
reportPlatformFailures(
|
||||
UserFeedback.getPrompt().then((prompt) => {
|
||||
setPromptData(prompt);
|
||||
|
||||
@@ -12,22 +12,21 @@ import {Radio} from 'antd';
|
||||
import {updateSettings, Action} from '../reducers/settings';
|
||||
import {
|
||||
Action as LauncherAction,
|
||||
LauncherSettings,
|
||||
updateLauncherSettings,
|
||||
} from '../reducers/launcherSettings';
|
||||
import {connect} from 'react-redux';
|
||||
import {State as Store} from '../reducers';
|
||||
import {Settings, DEFAULT_ANDROID_SDK_PATH} from '../reducers/settings';
|
||||
import {flush} from '../utils/persistor';
|
||||
import ToggledSection from './settings/ToggledSection';
|
||||
import {FilePathConfigField, ConfigText} from './settings/configFields';
|
||||
import KeyboardShortcutInput from './settings/KeyboardShortcutInput';
|
||||
import {isEqual, isMatch, isEmpty} from 'lodash';
|
||||
import LauncherSettingsPanel from '../fb-stubs/LauncherSettingsPanel';
|
||||
import {reportUsage} from 'flipper-common';
|
||||
import {LauncherSettings, reportUsage, Settings} from 'flipper-common';
|
||||
import {Modal, message, Button} from 'antd';
|
||||
import {Layout, withTrackingScope, _NuxManagerContext} from 'flipper-plugin';
|
||||
import {getRenderHostInstance} from '../RenderHost';
|
||||
import {loadTheme} from '../utils/loadTheme';
|
||||
|
||||
type OwnProps = {
|
||||
onHide: () => void;
|
||||
@@ -142,7 +141,9 @@ class SettingsSheet extends Component<Props, State> {
|
||||
}}>
|
||||
<FilePathConfigField
|
||||
label="Android SDK location"
|
||||
resetValue={DEFAULT_ANDROID_SDK_PATH}
|
||||
resetValue={
|
||||
getRenderHostInstance().serverConfig.settings.androidHome
|
||||
}
|
||||
defaultValue={androidHome}
|
||||
onChange={(v) => {
|
||||
this.setState({
|
||||
@@ -255,6 +256,7 @@ class SettingsSheet extends Component<Props, State> {
|
||||
darkMode: event.target.value,
|
||||
},
|
||||
}));
|
||||
loadTheme(event.target.value);
|
||||
}}>
|
||||
<Radio.Button value="dark">Dark</Radio.Button>
|
||||
<Radio.Button value="light">Light</Radio.Button>
|
||||
|
||||
@@ -9,13 +9,12 @@
|
||||
|
||||
import {notification, Typography} from 'antd';
|
||||
import isProduction from '../utils/isProduction';
|
||||
import {reportPlatformFailures} from 'flipper-common';
|
||||
import {reportPlatformFailures, ReleaseChannel} from 'flipper-common';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import fbConfig from '../fb-stubs/config';
|
||||
import {useStore} from '../utils/useStore';
|
||||
import {getAppVersion} from '../utils/info';
|
||||
import {checkForUpdate} from '../fb-stubs/checkForUpdate';
|
||||
import ReleaseChannel from '../ReleaseChannel';
|
||||
|
||||
export type VersionCheckResult =
|
||||
| {
|
||||
|
||||
@@ -26,7 +26,7 @@ export default class ArchivedDevice extends BaseDevice {
|
||||
}) {
|
||||
super(
|
||||
{
|
||||
async start() {},
|
||||
async connect() {},
|
||||
close() {},
|
||||
exec(command, ..._args: any[]) {
|
||||
throw new Error(
|
||||
|
||||
@@ -21,12 +21,11 @@ import path from 'path';
|
||||
import {createRootReducer, State} from '../../reducers/index';
|
||||
import {getLogger} from 'flipper-common';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import {TEST_PASSING_GK, TEST_FAILING_GK} from '../../fb-stubs/GK';
|
||||
import TestPlugin from './TestPlugin';
|
||||
import {resetConfigForTesting} from '../../utils/processConfig';
|
||||
import {_SandyPluginDefinition} from 'flipper-plugin';
|
||||
import {mocked} from 'ts-jest/utils';
|
||||
import loadDynamicPlugins from '../../utils/loadDynamicPlugins';
|
||||
import {getRenderHostInstance} from '../../RenderHost';
|
||||
|
||||
const loadDynamicPluginsMock = mocked(loadDynamicPlugins);
|
||||
|
||||
@@ -57,7 +56,6 @@ const sampleBundledPluginDetails: BundledPluginDetails = {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
resetConfigForTesting();
|
||||
loadDynamicPluginsMock.mockResolvedValue([]);
|
||||
});
|
||||
|
||||
@@ -80,10 +78,13 @@ test('getDynamicPlugins returns empty array on errors', async () => {
|
||||
|
||||
test('checkDisabled', () => {
|
||||
const disabledPlugin = 'pluginName';
|
||||
const config = {disabledPlugins: [disabledPlugin]};
|
||||
const orig = process.env.CONFIG;
|
||||
const hostConfig = getRenderHostInstance().serverConfig;
|
||||
const orig = hostConfig.processConfig;
|
||||
try {
|
||||
process.env.CONFIG = JSON.stringify(config);
|
||||
hostConfig.processConfig = {
|
||||
...orig,
|
||||
disabledPlugins: new Set([disabledPlugin]),
|
||||
};
|
||||
const disabled = checkDisabled([]);
|
||||
|
||||
expect(
|
||||
@@ -93,7 +94,6 @@ test('checkDisabled', () => {
|
||||
version: '1.0.0',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
|
||||
expect(
|
||||
disabled({
|
||||
...sampleBundledPluginDetails,
|
||||
@@ -102,7 +102,7 @@ test('checkDisabled', () => {
|
||||
}),
|
||||
).toBeFalsy();
|
||||
} finally {
|
||||
process.env.CONFIG = orig;
|
||||
hostConfig.processConfig = orig;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -121,7 +121,7 @@ test('checkGK for passing plugin', () => {
|
||||
checkGK([])({
|
||||
...sampleBundledPluginDetails,
|
||||
name: 'pluginID',
|
||||
gatekeeper: TEST_PASSING_GK,
|
||||
gatekeeper: 'TEST_PASSING_GK',
|
||||
version: '1.0.0',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
@@ -133,7 +133,7 @@ test('checkGK for failing plugin', () => {
|
||||
const plugins = checkGK(gatekeepedPlugins)({
|
||||
...sampleBundledPluginDetails,
|
||||
name,
|
||||
gatekeeper: TEST_FAILING_GK,
|
||||
gatekeeper: 'TEST_FAILING_GK',
|
||||
version: '1.0.0',
|
||||
});
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
FlipperServer,
|
||||
Logger,
|
||||
NoLongerConnectedToClientError,
|
||||
isTest,
|
||||
} from 'flipper-common';
|
||||
import Client from '../Client';
|
||||
import {notification} from 'antd';
|
||||
@@ -20,23 +21,12 @@ import BaseDevice from '../devices/BaseDevice';
|
||||
import {ClientDescription, timeout} from 'flipper-common';
|
||||
import {reportPlatformFailures} from 'flipper-common';
|
||||
import {sideEffect} from '../utils/sideEffect';
|
||||
import constants from '../fb-stubs/constants';
|
||||
import {getRenderHostInstance} from '../RenderHost';
|
||||
|
||||
export default async (store: Store, logger: Logger) => {
|
||||
const {enableAndroid, androidHome, idbPath, enableIOS, enablePhysicalIOS} =
|
||||
store.getState().settingsState;
|
||||
|
||||
const server = getRenderHostInstance().startFlipperServer({
|
||||
logger,
|
||||
enableAndroid,
|
||||
androidHome,
|
||||
idbPath,
|
||||
enableIOS,
|
||||
enablePhysicalIOS,
|
||||
validWebSocketOrigins: constants.VALID_WEB_SOCKET_REQUEST_ORIGIN_PREFIXES,
|
||||
});
|
||||
|
||||
export function connectFlipperServerToStore(
|
||||
server: FlipperServer,
|
||||
store: Store,
|
||||
logger: Logger,
|
||||
) {
|
||||
store.dispatch({
|
||||
type: 'SET_FLIPPER_SERVER',
|
||||
payload: server,
|
||||
@@ -147,25 +137,55 @@ export default async (store: Store, logger: Logger) => {
|
||||
});
|
||||
}
|
||||
|
||||
server
|
||||
.start()
|
||||
.then(() => {
|
||||
console.log(
|
||||
'Flipper server started and accepting device / client connections',
|
||||
);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error('Failed to start Flipper server', e);
|
||||
notification.error({
|
||||
message: 'Failed to start Flipper server',
|
||||
description: 'error: ' + e,
|
||||
});
|
||||
});
|
||||
let sideEffectDisposer: undefined | (() => void);
|
||||
|
||||
if (!isTest()) {
|
||||
sideEffectDisposer = startSideEffects(store, server);
|
||||
}
|
||||
console.log(
|
||||
'Flipper server started and accepting device / client connections',
|
||||
);
|
||||
|
||||
return () => {
|
||||
sideEffectDisposer?.();
|
||||
server.close();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function startSideEffects(store: Store, server: FlipperServer) {
|
||||
const dispose1 = sideEffect(
|
||||
store,
|
||||
{
|
||||
name: 'settingsPersistor',
|
||||
throttleMs: 100,
|
||||
},
|
||||
(state) => state.settingsState,
|
||||
(settings) => {
|
||||
server.exec('persist-settings', settings).catch((e) => {
|
||||
console.error('Failed to persist Flipper settings', e);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const dispose2 = sideEffect(
|
||||
store,
|
||||
{
|
||||
name: 'launcherSettingsPersistor',
|
||||
throttleMs: 100,
|
||||
},
|
||||
(state) => state.launcherSettingsState,
|
||||
(settings) => {
|
||||
server.exec('persist-launcher-settings', settings).catch((e) => {
|
||||
console.error('Failed to persist launcher settings', e);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
dispose1();
|
||||
dispose2();
|
||||
};
|
||||
}
|
||||
|
||||
export async function handleClientConnected(
|
||||
server: Pick<FlipperServer, 'exec'>,
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
*/
|
||||
|
||||
// Used responsibly.
|
||||
import flipperServer from './flipperServer';
|
||||
import application from './application';
|
||||
import tracking from './tracking';
|
||||
import notifications from './notifications';
|
||||
@@ -32,7 +31,6 @@ export default function (store: Store, logger: Logger): () => Promise<void> {
|
||||
const dispatchers: Array<Dispatcher> = [
|
||||
application,
|
||||
tracking,
|
||||
flipperServer,
|
||||
notifications,
|
||||
plugins,
|
||||
user,
|
||||
|
||||
@@ -24,11 +24,9 @@ import {
|
||||
MarketplacePluginDetails,
|
||||
pluginsInitialized,
|
||||
} from '../reducers/plugins';
|
||||
import GK from '../fb-stubs/GK';
|
||||
import {FlipperBasePlugin} from '../plugin';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import {default as config} from '../utils/processConfig';
|
||||
import {notNull} from '../utils/typeUtils';
|
||||
import {
|
||||
ActivatablePluginDetails,
|
||||
@@ -151,6 +149,9 @@ export function getLatestCompatibleVersionOfEachPlugin<
|
||||
}
|
||||
|
||||
async function getBundledPlugins(): Promise<Array<BundledPluginDetails>> {
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
return [];
|
||||
}
|
||||
// defaultPlugins that are included in the Flipper distributive.
|
||||
// List of default bundled plugins is written at build time to defaultPlugins/bundled.json.
|
||||
const pluginPath = getStaticPath(
|
||||
@@ -183,7 +184,7 @@ export const checkGK =
|
||||
if (!plugin.gatekeeper) {
|
||||
return true;
|
||||
}
|
||||
const result = GK.get(plugin.gatekeeper);
|
||||
const result = getRenderHostInstance().GK(plugin.gatekeeper);
|
||||
if (!result) {
|
||||
gatekeepedPlugins.push(plugin);
|
||||
}
|
||||
@@ -197,15 +198,16 @@ export const checkGK =
|
||||
export const checkDisabled = (
|
||||
disabledPlugins: Array<ActivatablePluginDetails>,
|
||||
) => {
|
||||
const config = getRenderHostInstance().serverConfig;
|
||||
let enabledList: Set<string> | null = null;
|
||||
let disabledList: Set<string> = new Set();
|
||||
try {
|
||||
if (process.env.FLIPPER_ENABLED_PLUGINS) {
|
||||
if (config.env.FLIPPER_ENABLED_PLUGINS) {
|
||||
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) {
|
||||
console.error('Failed to compute enabled/disabled plugins', e);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export default (store: Store) => {
|
||||
const settings = store.getState().settingsState.reactNative;
|
||||
const renderHost = getRenderHostInstance();
|
||||
|
||||
if (!settings.shortcuts.enabled) {
|
||||
if (!settings?.shortcuts.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,66 +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
|
||||
*/
|
||||
|
||||
export type GKID = string;
|
||||
|
||||
export const TEST_PASSING_GK = 'TEST_PASSING_GK';
|
||||
export const TEST_FAILING_GK = 'TEST_FAILING_GK';
|
||||
export type GKMap = {[key: string]: boolean};
|
||||
|
||||
const whitelistedGKs: Array<GKID> = [];
|
||||
|
||||
export function loadGKs(_username: string, _gks: Array<GKID>): Promise<GKMap> {
|
||||
return Promise.reject(
|
||||
new Error('Implement your custom logic for loading GK'),
|
||||
);
|
||||
}
|
||||
|
||||
export function loadDistilleryGK(
|
||||
_gk: GKID,
|
||||
): Promise<{[key: string]: {result: boolean}}> {
|
||||
return Promise.reject(
|
||||
new Error('Implement your custom logic for loading GK'),
|
||||
);
|
||||
}
|
||||
|
||||
export default class GK {
|
||||
static init() {}
|
||||
|
||||
static get(id: GKID): boolean {
|
||||
if (process.env.NODE_ENV === 'test' && id === TEST_PASSING_GK) {
|
||||
return true;
|
||||
}
|
||||
if (whitelistedGKs.includes(id)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static serializeGKs() {
|
||||
return '';
|
||||
}
|
||||
|
||||
static async withWhitelistedGK(
|
||||
id: GKID,
|
||||
callback: () => Promise<void> | void,
|
||||
) {
|
||||
whitelistedGKs.push(id);
|
||||
try {
|
||||
const p = callback();
|
||||
if (p) {
|
||||
await p;
|
||||
}
|
||||
} finally {
|
||||
const idx = whitelistedGKs.indexOf(id);
|
||||
if (idx !== -1) {
|
||||
whitelistedGKs.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,7 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {Tristate} from '../reducers/settings';
|
||||
import ReleaseChannel from '../ReleaseChannel';
|
||||
import {Tristate, ReleaseChannel} from 'flipper-common';
|
||||
|
||||
export default function (_props: {
|
||||
isPrefetchingEnabled: Tristate;
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
import ReleaseChannel from '../ReleaseChannel';
|
||||
import {ReleaseChannel} from 'flipper-common';
|
||||
|
||||
export default {
|
||||
updateServer: 'https://www.facebook.com/fbflipper/public/latest.json',
|
||||
|
||||
@@ -42,12 +42,4 @@ export default Object.freeze({
|
||||
},
|
||||
|
||||
SUPPORT_GROUPS: [],
|
||||
|
||||
// Only WebSocket requests from the following origin prefixes will be accepted
|
||||
VALID_WEB_SOCKET_REQUEST_ORIGIN_PREFIXES: [
|
||||
'chrome-extension://',
|
||||
'localhost:',
|
||||
'http://localhost:',
|
||||
'app://',
|
||||
],
|
||||
});
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
*/
|
||||
|
||||
import {StoreEnhancerStoreCreator} from 'redux';
|
||||
import {Store} from './reducers';
|
||||
import {RenderHost} from './RenderHost';
|
||||
|
||||
declare global {
|
||||
@@ -17,8 +16,6 @@ declare global {
|
||||
}
|
||||
|
||||
interface Window {
|
||||
flipperGlobalStoreDispatch: Store['dispatch'];
|
||||
|
||||
__REDUX_DEVTOOLS_EXTENSION__:
|
||||
| undefined
|
||||
| (StoreEnhancerStoreCreator & StoreEnhancerStateSanitizer);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {Logger} from 'flipper-common';
|
||||
import {Logger, Settings} from 'flipper-common';
|
||||
import Client from './Client';
|
||||
import {Component} from 'react';
|
||||
import BaseDevice from './devices/BaseDevice';
|
||||
@@ -15,7 +15,6 @@ import {StaticView} from './reducers/connections';
|
||||
import {State as ReduxState} from './reducers';
|
||||
import {DEFAULT_MAX_QUEUE_SIZE} from './reducers/pluginMessageQueue';
|
||||
import {ActivatablePluginDetails} from 'flipper-plugin-lib';
|
||||
import {Settings} from './reducers/settings';
|
||||
import {
|
||||
Notification,
|
||||
Idler,
|
||||
|
||||
@@ -7,30 +7,17 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {default as reducer, updateSettings, Tristate} from '../settings';
|
||||
|
||||
test('init', () => {
|
||||
const res = reducer(undefined, {type: 'INIT'});
|
||||
expect(res.enableAndroid).toBeTruthy();
|
||||
});
|
||||
import {default as reducer, updateSettings} from '../settings';
|
||||
import {Tristate} from 'flipper-common';
|
||||
|
||||
test('updateSettings', () => {
|
||||
const initialSettings = reducer(undefined, {type: 'INIT'});
|
||||
const updatedSettings = Object.assign(initialSettings, {
|
||||
enableAndroid: false,
|
||||
enablePrefetching: Tristate.True,
|
||||
jsApps: {
|
||||
webAppLauncher: {
|
||||
height: 900,
|
||||
},
|
||||
},
|
||||
});
|
||||
const res = reducer(initialSettings, updateSettings(updatedSettings));
|
||||
|
||||
expect(res.enableAndroid).toBeFalsy();
|
||||
expect(res.enablePrefetching).toEqual(Tristate.True);
|
||||
expect(res.jsApps.webAppLauncher.height).toEqual(900);
|
||||
expect(res.jsApps.webAppLauncher.width).toEqual(
|
||||
initialSettings.jsApps.webAppLauncher.width,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -36,12 +36,8 @@ import supportForm, {
|
||||
State as SupportFormState,
|
||||
Action as SupportFormAction,
|
||||
} from './supportForm';
|
||||
import settings, {
|
||||
Settings as SettingsState,
|
||||
Action as SettingsAction,
|
||||
} from './settings';
|
||||
import settings, {Action as SettingsAction} from './settings';
|
||||
import launcherSettings, {
|
||||
LauncherSettings as LauncherSettingsState,
|
||||
Action as LauncherSettingsAction,
|
||||
} from './launcherSettings';
|
||||
import pluginManager, {
|
||||
@@ -61,18 +57,13 @@ import usageTracking, {
|
||||
State as TrackingState,
|
||||
} from './usageTracking';
|
||||
import user, {State as UserState, Action as UserAction} from './user';
|
||||
import JsonFileStorage from '../utils/jsonFileReduxPersistStorage';
|
||||
import LauncherSettingsStorage from '../utils/launcherSettingsStorage';
|
||||
import {launcherConfigDir} from '../utils/launcher';
|
||||
import os from 'os';
|
||||
import {resolve} from 'path';
|
||||
import xdg from 'xdg-basedir';
|
||||
import {createMigrate, createTransform, persistReducer} from 'redux-persist';
|
||||
import {PersistPartial} from 'redux-persist/es/persistReducer';
|
||||
|
||||
import {Store as ReduxStore, MiddlewareAPI as ReduxMiddlewareAPI} from 'redux';
|
||||
import storage from 'redux-persist/lib/storage';
|
||||
import {TransformConfig} from 'redux-persist/es/createTransform';
|
||||
import {LauncherSettings, Settings} from 'flipper-common';
|
||||
|
||||
export type Actions =
|
||||
| ApplicationAction
|
||||
@@ -97,8 +88,8 @@ export type State = {
|
||||
notifications: NotificationsState & PersistPartial;
|
||||
plugins: PluginsState & PersistPartial;
|
||||
user: UserState & PersistPartial;
|
||||
settingsState: SettingsState & PersistPartial;
|
||||
launcherSettingsState: LauncherSettingsState & PersistPartial;
|
||||
settingsState: Settings;
|
||||
launcherSettingsState: LauncherSettings;
|
||||
supportForm: SupportFormState;
|
||||
pluginManager: PluginManagerState;
|
||||
healthchecks: HealthcheckState & PersistPartial;
|
||||
@@ -109,14 +100,6 @@ export type State = {
|
||||
export type Store = ReduxStore<State, Actions>;
|
||||
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) =>
|
||||
createTransform(
|
||||
(set: Set<string>) => Array.from(set),
|
||||
@@ -124,10 +107,6 @@ const setTransformer = (config: TransformConfig) =>
|
||||
config,
|
||||
);
|
||||
|
||||
const launcherSettingsStorage = new LauncherSettingsStorage(
|
||||
resolve(launcherConfigDir(), 'flipper-launcher.toml'),
|
||||
);
|
||||
|
||||
export function createRootReducer() {
|
||||
return combineReducers<State, Actions>({
|
||||
application,
|
||||
@@ -181,20 +160,8 @@ export function createRootReducer() {
|
||||
},
|
||||
user,
|
||||
),
|
||||
settingsState: persistReducer(
|
||||
{key: 'settings', storage: settingsStorage},
|
||||
settings,
|
||||
),
|
||||
launcherSettingsState: persistReducer(
|
||||
{
|
||||
key: 'launcherSettings',
|
||||
storage: launcherSettingsStorage,
|
||||
serialize: false,
|
||||
// @ts-ignore: property is erroneously missing in redux-persist type definitions
|
||||
deserialize: false,
|
||||
},
|
||||
launcherSettings,
|
||||
),
|
||||
settingsState: settings,
|
||||
launcherSettingsState: launcherSettings,
|
||||
healthchecks: persistReducer<HealthcheckState, Actions>(
|
||||
{
|
||||
key: 'healthchecks',
|
||||
|
||||
@@ -7,26 +7,18 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {LauncherSettings} from 'flipper-common';
|
||||
import {getRenderHostInstance} from '../RenderHost';
|
||||
import {Actions} from './index';
|
||||
import ReleaseChannel from '../ReleaseChannel';
|
||||
|
||||
export type LauncherSettings = {
|
||||
releaseChannel: ReleaseChannel;
|
||||
ignoreLocalPin: boolean;
|
||||
};
|
||||
|
||||
export type Action = {
|
||||
type: 'UPDATE_LAUNCHER_SETTINGS';
|
||||
payload: LauncherSettings;
|
||||
};
|
||||
|
||||
export const defaultLauncherSettings: LauncherSettings = {
|
||||
releaseChannel: ReleaseChannel.DEFAULT,
|
||||
ignoreLocalPin: false,
|
||||
};
|
||||
|
||||
export default function reducer(
|
||||
state: LauncherSettings = defaultLauncherSettings,
|
||||
state: LauncherSettings = getRenderHostInstance().serverConfig
|
||||
.launcherSettings,
|
||||
action: Actions,
|
||||
): LauncherSettings {
|
||||
if (action.type === 'UPDATE_LAUNCHER_SETTINGS') {
|
||||
|
||||
@@ -8,45 +8,8 @@
|
||||
*/
|
||||
|
||||
import {Actions} from './index';
|
||||
import os from 'os';
|
||||
import {getRenderHostInstance} from '../RenderHost';
|
||||
|
||||
export enum Tristate {
|
||||
True,
|
||||
False,
|
||||
Unset,
|
||||
}
|
||||
|
||||
export type Settings = {
|
||||
androidHome: string;
|
||||
enableAndroid: boolean;
|
||||
enableIOS: boolean;
|
||||
enablePhysicalIOS: boolean;
|
||||
/**
|
||||
* If unset, this will assume the value of the GK setting.
|
||||
* Note that this setting has no effect in the open source version
|
||||
* of Flipper.
|
||||
*/
|
||||
enablePrefetching: Tristate;
|
||||
idbPath: string;
|
||||
jsApps: {
|
||||
webAppLauncher: {
|
||||
url: string;
|
||||
height: number;
|
||||
width: number;
|
||||
};
|
||||
};
|
||||
reactNative: {
|
||||
shortcuts: {
|
||||
enabled: boolean;
|
||||
reload: string;
|
||||
openDevMenu: string;
|
||||
};
|
||||
};
|
||||
darkMode: 'dark' | 'light' | 'system';
|
||||
showWelcomeAtStartup: boolean;
|
||||
suppressPluginErrors: boolean;
|
||||
};
|
||||
import {Settings} from 'flipper-common';
|
||||
|
||||
export type Action =
|
||||
| {type: 'INIT'}
|
||||
@@ -55,36 +18,8 @@ export type Action =
|
||||
payload: Settings;
|
||||
};
|
||||
|
||||
export const DEFAULT_ANDROID_SDK_PATH = getDefaultAndroidSdkPath();
|
||||
|
||||
const initialState: Settings = {
|
||||
androidHome: getDefaultAndroidSdkPath(),
|
||||
enableAndroid: true,
|
||||
enableIOS: os.platform() === 'darwin',
|
||||
enablePhysicalIOS: os.platform() === 'darwin',
|
||||
enablePrefetching: Tristate.Unset,
|
||||
idbPath: '/usr/local/bin/idb',
|
||||
jsApps: {
|
||||
webAppLauncher: {
|
||||
url: 'http://localhost:8888',
|
||||
height: 600,
|
||||
width: 800,
|
||||
},
|
||||
},
|
||||
reactNative: {
|
||||
shortcuts: {
|
||||
enabled: false,
|
||||
reload: 'Alt+Shift+R',
|
||||
openDevMenu: 'Alt+Shift+D',
|
||||
},
|
||||
},
|
||||
darkMode: 'light',
|
||||
showWelcomeAtStartup: true,
|
||||
suppressPluginErrors: false,
|
||||
};
|
||||
|
||||
export default function reducer(
|
||||
state: Settings = initialState,
|
||||
state: Settings = getRenderHostInstance().serverConfig.settings,
|
||||
action: Actions,
|
||||
): Settings {
|
||||
if (action.type === 'UPDATE_SETTINGS') {
|
||||
@@ -99,13 +34,3 @@ export function updateSettings(settings: Settings): Action {
|
||||
payload: settings,
|
||||
};
|
||||
}
|
||||
|
||||
function getDefaultAndroidSdkPath() {
|
||||
return os.platform() === 'win32' ? getWindowsSdkPath() : '/opt/android_sdk';
|
||||
}
|
||||
|
||||
function getWindowsSdkPath() {
|
||||
return `${
|
||||
getRenderHostInstance().paths.homePath
|
||||
}\\AppData\\Local\\android\\sdk`;
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@ import constants from '../fb-stubs/constants';
|
||||
import config from '../fb-stubs/config';
|
||||
import isProduction from '../utils/isProduction';
|
||||
import {getAppVersion} from '../utils/info';
|
||||
import ReleaseChannel from '../ReleaseChannel';
|
||||
import {getFlipperLib} from 'flipper-plugin';
|
||||
import ChangelogSheet from '../chrome/ChangelogSheet';
|
||||
import {ReleaseChannel} from 'flipper-common';
|
||||
|
||||
const RowContainer = styled(FlexRow)({
|
||||
alignItems: 'flex-start',
|
||||
|
||||
@@ -30,8 +30,8 @@ import Client from '../../Client';
|
||||
import {State} from '../../reducers';
|
||||
import {brandColors, brandIcons, colors} from '../../ui/components/colors';
|
||||
import {TroubleshootingGuide} from './fb-stubs/TroubleshootingGuide';
|
||||
import GK from '../../fb-stubs/GK';
|
||||
import {getSelectableDevices} from '../../selectors/connections';
|
||||
import {getRenderHostInstance} from '../../RenderHost';
|
||||
|
||||
const {Text} = Typography;
|
||||
|
||||
@@ -127,7 +127,7 @@ export function AppSelector() {
|
||||
</Layout.Horizontal>
|
||||
)}
|
||||
<TroubleshootingGuide
|
||||
showGuide={GK.get('flipper_self_sufficiency')}
|
||||
showGuide={getRenderHostInstance().GK('flipper_self_sufficiency')}
|
||||
devicesDetected={entries.length}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -10,16 +10,11 @@
|
||||
import {Provider} from 'react-redux';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import GK from './fb-stubs/GK';
|
||||
import {init as initLogger} from './fb-stubs/Logger';
|
||||
import {SandyApp} from './sandy-chrome/SandyApp';
|
||||
import setupPrefetcher from './fb-stubs/Prefetcher';
|
||||
import {Persistor, persistStore} from 'redux-persist';
|
||||
import {Store} from './reducers/index';
|
||||
import dispatcher from './dispatcher/index';
|
||||
import TooltipProvider from './ui/components/TooltipProvider';
|
||||
import config from './utils/processConfig';
|
||||
import {initLauncherHooks} from './utils/launcher';
|
||||
import {setPersistor} from './utils/persistor';
|
||||
import React from 'react';
|
||||
import path from 'path';
|
||||
@@ -28,7 +23,6 @@ import {cache} from '@emotion/css';
|
||||
import {CacheProvider} from '@emotion/react';
|
||||
import {initializeFlipperLibImplementation} from './utils/flipperLibImplementation';
|
||||
import {enableConsoleHook} from './chrome/ConsoleLogs';
|
||||
import {sideEffect} from './utils/sideEffect';
|
||||
import {
|
||||
_NuxManagerContext,
|
||||
_createNuxManager,
|
||||
@@ -49,11 +43,14 @@ import {PersistGate} from 'redux-persist/integration/react';
|
||||
import {
|
||||
setLoggerInstance,
|
||||
setUserSessionManagerInstance,
|
||||
GK as flipperCommonGK,
|
||||
Settings,
|
||||
FlipperServer,
|
||||
} from 'flipper-common';
|
||||
import {internGraphPOSTAPIRequest} from './fb-stubs/user';
|
||||
import {getRenderHostInstance} from './RenderHost';
|
||||
import {startGlobalErrorHandling} from './utils/globalErrorHandling';
|
||||
import {loadTheme} from './utils/loadTheme';
|
||||
import {connectFlipperServerToStore} from './dispatcher/flipperServer';
|
||||
|
||||
class AppFrame extends React.Component<
|
||||
{logger: Logger; persistor: Persistor},
|
||||
@@ -145,8 +142,7 @@ class AppFrame extends React.Component<
|
||||
}
|
||||
}
|
||||
|
||||
function setProcessState(store: Store) {
|
||||
const settings = store.getState().settingsState;
|
||||
function setProcessState(settings: Settings) {
|
||||
const androidHome = settings.androidHome;
|
||||
const idbPath = settings.idbPath;
|
||||
|
||||
@@ -162,27 +158,22 @@ function setProcessState(store: Store) {
|
||||
.join(':') +
|
||||
`:${idbPath}` +
|
||||
`:${process.env.PATH}`;
|
||||
|
||||
window.requestIdleCallback(() => {
|
||||
setupPrefetcher(settings);
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
GK.init();
|
||||
|
||||
// TODO: centralise all those initialisations in a single configuration call
|
||||
flipperCommonGK.get = (name) => GK.get(name);
|
||||
function init(flipperServer: FlipperServer) {
|
||||
const settings = getRenderHostInstance().serverConfig.settings;
|
||||
const store = getStore();
|
||||
const logger = initLogger(store);
|
||||
setLoggerInstance(logger);
|
||||
|
||||
setLoggerInstance(logger);
|
||||
startGlobalErrorHandling();
|
||||
loadTheme(settings.darkMode);
|
||||
connectFlipperServerToStore(flipperServer, store, logger);
|
||||
|
||||
// rehydrate app state before exposing init
|
||||
const persistor = persistStore(store, undefined, () => {
|
||||
// Make sure process state is set before dispatchers run
|
||||
setProcessState(store);
|
||||
setProcessState(settings);
|
||||
dispatcher(store, logger);
|
||||
});
|
||||
|
||||
@@ -205,39 +196,25 @@ function init() {
|
||||
<AppFrame logger={logger} persistor={persistor} />,
|
||||
document.getElementById('root'),
|
||||
);
|
||||
initLauncherHooks(config(), store);
|
||||
enableConsoleHook();
|
||||
window.flipperGlobalStoreDispatch = store.dispatch;
|
||||
|
||||
// listen to settings and load the right theme
|
||||
sideEffect(
|
||||
store,
|
||||
{name: 'loadTheme', fireImmediately: false, throttleMs: 500},
|
||||
(state) => state.settingsState.darkMode,
|
||||
(theme) => {
|
||||
let shouldUseDarkMode = false;
|
||||
if (theme === 'dark') {
|
||||
shouldUseDarkMode = true;
|
||||
} else if (theme === 'light') {
|
||||
shouldUseDarkMode = false;
|
||||
} else if (theme === 'system') {
|
||||
shouldUseDarkMode = getRenderHostInstance().shouldUseDarkColors();
|
||||
}
|
||||
(
|
||||
document.getElementById('flipper-theme-import') as HTMLLinkElement
|
||||
).href = `themes/${shouldUseDarkMode ? 'dark' : 'light'}.css`;
|
||||
getRenderHostInstance().sendIpcEvent('setTheme', theme);
|
||||
},
|
||||
);
|
||||
enableConsoleHook();
|
||||
|
||||
const launcherMessage =
|
||||
getRenderHostInstance().serverConfig.processConfig.launcherMsg;
|
||||
if (launcherMessage) {
|
||||
store.dispatch({
|
||||
type: 'LAUNCHER_MSG',
|
||||
payload: {
|
||||
severity: 'warning',
|
||||
message: launcherMessage,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function startFlipperDesktop() {
|
||||
setImmediate(() => {
|
||||
// make sure all modules are loaded
|
||||
// @ts-ignore
|
||||
window.flipperInit = init;
|
||||
window.dispatchEvent(new Event('flipper-store-ready'));
|
||||
});
|
||||
export async function startFlipperDesktop(flipperServer: FlipperServer) {
|
||||
getRenderHostInstance(); // renderHost instance should be set at this point!
|
||||
init(flipperServer);
|
||||
}
|
||||
|
||||
const CodeBlock = styled(Input.TextArea)({
|
||||
|
||||
@@ -21,7 +21,7 @@ export class TestDevice extends BaseDevice {
|
||||
) {
|
||||
super(
|
||||
{
|
||||
async start() {},
|
||||
async connect() {},
|
||||
on: jest.fn(),
|
||||
off: 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);
|
||||
});
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
import {default as config, resetConfigForTesting} from '../processConfig';
|
||||
|
||||
afterEach(() => {
|
||||
resetConfigForTesting();
|
||||
});
|
||||
|
||||
test('config is decoded from env', () => {
|
||||
process.env.CONFIG = JSON.stringify({
|
||||
disabledPlugins: ['pluginA', 'pluginB', 'pluginC'],
|
||||
lastWindowPosition: {x: 4, y: 8, width: 15, height: 16},
|
||||
launcherMsg: 'wubba lubba dub dub',
|
||||
screenCapturePath: '/my/screenshot/path',
|
||||
launcherEnabled: false,
|
||||
});
|
||||
|
||||
expect(config()).toEqual({
|
||||
disabledPlugins: new Set(['pluginA', 'pluginB', 'pluginC']),
|
||||
lastWindowPosition: {x: 4, y: 8, width: 15, height: 16},
|
||||
launcherMsg: 'wubba lubba dub dub',
|
||||
screenCapturePath: '/my/screenshot/path',
|
||||
launcherEnabled: false,
|
||||
});
|
||||
});
|
||||
|
||||
test('config is decoded from env with defaults', () => {
|
||||
process.env.CONFIG = '{}';
|
||||
|
||||
expect(config()).toEqual({
|
||||
disabledPlugins: new Set([]),
|
||||
lastWindowPosition: undefined,
|
||||
launcherMsg: undefined,
|
||||
screenCapturePath: undefined,
|
||||
launcherEnabled: true,
|
||||
});
|
||||
});
|
||||
@@ -11,7 +11,6 @@ import {_setFlipperLibImplementation} from 'flipper-plugin';
|
||||
import type {Logger} from 'flipper-common';
|
||||
import type {Store} from '../reducers';
|
||||
import createPaste from '../fb-stubs/createPaste';
|
||||
import GK from '../fb-stubs/GK';
|
||||
import type BaseDevice from '../devices/BaseDevice';
|
||||
import constants from '../fb-stubs/constants';
|
||||
import {addNotification} from '../reducers/notifications';
|
||||
@@ -32,9 +31,7 @@ export function initializeFlipperLibImplementation(
|
||||
store.dispatch(setMenuEntries(entries));
|
||||
},
|
||||
createPaste,
|
||||
GK(gatekeeper: string) {
|
||||
return GK.get(gatekeeper);
|
||||
},
|
||||
GK: renderHost.GK,
|
||||
selectPlugin(device, client, pluginId, deeplink) {
|
||||
store.dispatch({
|
||||
type: 'SELECT_PLUGIN',
|
||||
@@ -63,8 +60,8 @@ export function initializeFlipperLibImplementation(
|
||||
importFile: renderHost.importFile,
|
||||
exportFile: renderHost.exportFile,
|
||||
paths: {
|
||||
appPath: renderHost.paths.appPath,
|
||||
homePath: renderHost.paths.homePath,
|
||||
appPath: renderHost.serverConfig.paths.appPath,
|
||||
homePath: renderHost.serverConfig.paths.homePath,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -73,18 +73,14 @@ export function buildLocalIconURL(name: string, size: number, density: number) {
|
||||
export function buildIconURLSync(name: string, size: number, density: number) {
|
||||
const icon = getIconPartsFromName(name);
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
const url = `https://facebook.com/assets/?name=${
|
||||
icon.trimmedName
|
||||
}&variant=${
|
||||
icon.variant
|
||||
}&size=${size}&set=facebook_icons&density=${density}x`;
|
||||
const url = `https://facebook.com/assets/?name=${icon.trimmedName}&variant=${icon.variant}&size=${size}&set=facebook_icons&density=${density}x`;
|
||||
if (
|
||||
typeof window !== 'undefined' &&
|
||||
(!getIconsSync()[name] || !getIconsSync()[name].includes(size))
|
||||
) {
|
||||
// From utils/isProduction
|
||||
const isProduction = !/node_modules[\\/]electron[\\/]/.test(
|
||||
getRenderHostInstance().paths.execPath,
|
||||
getRenderHostInstance().serverConfig.paths.execPath,
|
||||
);
|
||||
|
||||
if (!isProduction) {
|
||||
@@ -108,9 +104,7 @@ export function buildIconURLSync(name: string, size: number, density: number) {
|
||||
} else {
|
||||
throw new Error(
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
`Trying to use icon '${name}' with size ${size} and density ${density}, however the icon doesn't seem to exists at ${url}: ${
|
||||
res.status
|
||||
}`,
|
||||
`Trying to use icon '${name}' with size ${size} and density ${density}, however the icon doesn't seem to exists at ${url}: ${res.status}`,
|
||||
);
|
||||
}
|
||||
})
|
||||
@@ -129,7 +123,7 @@ export function getIconURLSync(
|
||||
name: string,
|
||||
size: number,
|
||||
density: number,
|
||||
basePath: string = getRenderHostInstance().paths.appPath,
|
||||
basePath: string = getRenderHostInstance().serverConfig.paths.appPath,
|
||||
) {
|
||||
if (name.indexOf('/') > -1) {
|
||||
return name;
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
|
||||
import {PluginDetails} from 'flipper-plugin-lib';
|
||||
import semver from 'semver';
|
||||
import GK from '../fb-stubs/GK';
|
||||
import {getRenderHostInstance} from '../RenderHost';
|
||||
import {getAppVersion} from './info';
|
||||
|
||||
export function isPluginCompatible(plugin: PluginDetails) {
|
||||
const flipperVersion = getAppVersion();
|
||||
return (
|
||||
GK.get('flipper_disable_plugin_compatibility_checks') ||
|
||||
getRenderHostInstance().GK('flipper_disable_plugin_compatibility_checks') ||
|
||||
flipperVersion === '0.0.0' ||
|
||||
!plugin.engines?.flipper ||
|
||||
semver.lte(plugin.engines?.flipper, flipperVersion)
|
||||
|
||||
@@ -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<
|
||||
InstalledPluginDetails[]
|
||||
> {
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
return [];
|
||||
}
|
||||
if (process.env.FLIPPER_FAST_REFRESH) {
|
||||
console.log(
|
||||
'❌ Skipping loading of dynamic plugins because Fast Refresh is enabled. Fast Refresh only works with bundled plugins.',
|
||||
|
||||
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';
|
||||
|
||||
const getPackageJSON = async () => {
|
||||
const base = getRenderHostInstance().paths.appPath;
|
||||
const base = getRenderHostInstance().serverConfig.paths.appPath;
|
||||
const content = await promisify(fs.readFile)(
|
||||
path.join(base, 'package.json'),
|
||||
'utf-8',
|
||||
|
||||
@@ -20,7 +20,7 @@ export function getStaticPath(
|
||||
relativePath: string = '.',
|
||||
{asarUnpacked}: {asarUnpacked: boolean} = {asarUnpacked: false},
|
||||
) {
|
||||
const staticDir = getRenderHostInstance().paths.staticPath;
|
||||
const staticDir = getRenderHostInstance().serverConfig.paths.staticPath;
|
||||
const absolutePath = path.resolve(staticDir, relativePath);
|
||||
// Unfortunately, path.resolve, fs.pathExists, fs.read etc do not automatically work with asarUnpacked files.
|
||||
// All these functions still look for files in "app.asar" even if they are unpacked.
|
||||
|
||||
@@ -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 {reportPlatformFailures} from 'flipper-common';
|
||||
import expandTilde from 'expand-tilde';
|
||||
import config from '../utils/processConfig';
|
||||
import {getRenderHostInstance} from '../RenderHost';
|
||||
|
||||
export function getCaptureLocation() {
|
||||
return expandTilde(
|
||||
config().screenCapturePath || getRenderHostInstance().paths.desktopPath,
|
||||
getRenderHostInstance().serverConfig.processConfig.screenCapturePath ||
|
||||
getRenderHostInstance().serverConfig.paths.desktopPath,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import isProduction from '../utils/isProduction';
|
||||
import {getAppVersion} from './info';
|
||||
import config from '../fb-stubs/config';
|
||||
import ReleaseChannel from '../ReleaseChannel';
|
||||
import {ReleaseChannel} from 'flipper-common';
|
||||
|
||||
export function getVersionString() {
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user