Move app/server to flipper-server-core

Summary: moved `app/src/server` to `flipper-server-core/src` and fixed any fallout from that (aka integration points I missed on the preparing diffs).

Reviewed By: passy

Differential Revision: D31541378

fbshipit-source-id: 8a7e0169ebefa515781f6e5e0f7b926415d4b7e9
This commit is contained in:
Michel Weststrate
2021-10-12 15:59:44 -07:00
committed by Facebook GitHub Bot
parent 3e7a6b1b4b
commit d88b28330a
73 changed files with 563 additions and 534 deletions

View File

@@ -22,7 +22,6 @@
"@tanishiking/aho-corasick": "^0.0.1",
"@types/archiver": "^5.1.1",
"@types/uuid": "^8.3.1",
"JSONStream": "^1.3.1",
"adbkit": "^2.11.1",
"adbkit-logcat": "^2.0.1",
"antd": "4.16.8",
@@ -69,12 +68,7 @@
"redux": "^4.1.1",
"redux-persist": "^6.0.0",
"reselect": "^4.0.0",
"rsocket-core": "^0.0.27",
"rsocket-flowable": "^0.0.27",
"rsocket-tcp-server": "^0.0.25",
"rsocket-types": "^0.0.25",
"semver": "^7.3.5",
"split2": "^3.2.2",
"tmp": "^0.2.1",
"uuid": "^8.3.2",
"which": "^2.0.1",

View File

@@ -94,7 +94,7 @@ const handleError = (
crashReporterPlugin.instanceApi.reportCrash(payload);
};
interface ClientConnection {
export interface ClientConnection {
send(data: any): void;
sendExpectResponse(data: any): Promise<ClientResponseType>;
}

View File

@@ -35,7 +35,12 @@ Object {
"TestPlugin",
],
},
"flipperServer": undefined,
"flipperServer": Object {
"close": [MockFunction],
"exec": [MockFunction],
"off": [MockFunction],
"on": [MockFunction],
},
"selectedAppId": "TestApp#Android#MockAndroidDevice#serial",
"selectedAppPluginListRevision": 0,
"selectedDevice": Object {

View File

@@ -8,7 +8,7 @@
*/
import React, {useCallback, useEffect, useState} from 'react';
import {MetroReportableEvent} from '../server/devices/metro/MetroDevice';
import {MetroReportableEvent} from 'flipper-common';
import {useStore} from '../utils/useStore';
import {Button as AntButton} from 'antd';
import {MenuOutlined, ReloadOutlined} from '@ant-design/icons';

View File

@@ -38,7 +38,6 @@ type OwnProps = {
type StateFromProps = {
settings: Settings;
launcherSettings: LauncherSettings;
isXcodeDetected: boolean;
};
type DispatchFromProps = {
@@ -356,11 +355,9 @@ class SettingsSheet extends Component<Props, State> {
}
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
({settingsState, launcherSettingsState, connections}) => ({
({settingsState, launcherSettingsState}) => ({
settings: settingsState,
launcherSettings: launcherSettingsState,
isXcodeDetected:
connections.flipperServer?.ios.xcodeCommandLineToolsDetected ?? false,
}),
{updateSettings, updateLauncherSettings},
)(withTrackingScope(SettingsSheet));

View File

@@ -7,11 +7,11 @@
* @format
*/
import * as DeviceTestPluginModule from '../../../test-utils/DeviceTestPlugin';
import * as DeviceTestPluginModule from '../../test-utils/DeviceTestPlugin';
import {TestUtils, _SandyPluginDefinition} from 'flipper-plugin';
import {createMockFlipperWithPlugin} from '../../../test-utils/createMockFlipperWithPlugin';
import {TestDevice} from '../../../test-utils/TestDevice';
import ArchivedDevice from '../../../devices/ArchivedDevice';
import {createMockFlipperWithPlugin} from '../../test-utils/createMockFlipperWithPlugin';
import {TestDevice} from '../../test-utils/TestDevice';
import ArchivedDevice from '../../devices/ArchivedDevice';
const physicalDevicePluginDetails = TestUtils.createMockPluginDetails({
id: 'physicalDevicePlugin',

View File

@@ -12,7 +12,6 @@
import {remote, ipcRenderer, IpcRendererEvent} from 'electron';
import {Store} from '../reducers/index';
import {Logger} from 'flipper-common';
import {parseFlipperPorts} from '../server/utils/environmentVariables';
import {
importFileToStore,
IMPORT_FLIPPER_TRACE_EVENT,
@@ -80,36 +79,4 @@ export default (store: Store, logger: Logger) => {
}, `${IMPORT_FLIPPER_TRACE_EVENT}:Deeplink`);
},
);
if (process.env.FLIPPER_PORTS) {
const portOverrides = parseFlipperPorts(process.env.FLIPPER_PORTS);
if (portOverrides) {
store.dispatch({
type: 'SET_SERVER_PORTS',
payload: portOverrides,
});
} else {
console.error(
`Ignoring malformed FLIPPER_PORTS env variable:
"${process.env.FLIPPER_PORTS || ''}".
Example expected format: "1111,2222".`,
);
}
}
if (process.env.FLIPPER_ALT_PORTS) {
const portOverrides = parseFlipperPorts(process.env.FLIPPER_ALT_PORTS);
if (portOverrides) {
store.dispatch({
type: 'SET_ALT_SERVER_PORTS',
payload: portOverrides,
});
} else {
console.error(
`Ignoring malformed FLIPPER_ALT_PORTS env variable:
"${process.env.FLIPPER_ALT_PORTS || ''}".
Example expected format: "1111,2222".`,
);
}
}
};

View File

@@ -10,10 +10,7 @@
import React from 'react';
import {State, Store} from '../reducers/index';
import {Logger} from 'flipper-common';
import {
FlipperServerConfig,
FlipperServerImpl,
} from '../server/FlipperServerImpl';
import {FlipperServerImpl, setFlipperServerConfig} from 'flipper-server-core';
import {selectClient} from '../reducers/connections';
import Client from '../Client';
import {notification} from 'antd';
@@ -21,22 +18,23 @@ import BaseDevice from '../devices/BaseDevice';
import {ClientDescription, timeout} from 'flipper-common';
import {reportPlatformFailures} from 'flipper-common';
import {sideEffect} from '../utils/sideEffect';
import {getAppTempPath, getStaticPath} from '../utils/pathUtils';
import constants from '../fb-stubs/constants';
export default async (store: Store, logger: Logger) => {
const {enableAndroid, androidHome, idbPath, enableIOS, enablePhysicalIOS} =
store.getState().settingsState;
const server = new FlipperServerImpl(
{
enableAndroid,
androidHome,
idbPath,
enableIOS,
enablePhysicalIOS,
serverPorts: store.getState().application.serverPorts,
altServerPorts: store.getState().application.altServerPorts,
} as FlipperServerConfig,
logger,
);
setFlipperServerConfig({
enableAndroid,
androidHome,
idbPath,
enableIOS,
enablePhysicalIOS,
staticPath: getStaticPath(),
tmpPath: getAppTempPath(),
validWebSocketOrigins: constants.VALID_WEB_SOCKET_REQUEST_ORIGIN_PREFIXES,
});
const server = new FlipperServerImpl(logger);
store.dispatch({
type: 'SET_FLIPPER_SERVER',

View File

@@ -43,7 +43,7 @@ import {
getClientsByAppName,
getAllClients,
} from '../reducers/connections';
import {deconstructClientId} from '../utils/clientUtils';
import {deconstructClientId} from 'flipper-common';
import {clearMessageQueue} from '../reducers/pluginMessageQueue';
import {
isDevicePluginDefinition,

View File

@@ -27,7 +27,7 @@ import {
} from '../reducers/usageTracking';
import produce from 'immer';
import BaseDevice from '../devices/BaseDevice';
import {deconstructClientId} from '../utils/clientUtils';
import {deconstructClientId} from 'flipper-common';
import {getCPUUsage} from 'process';
import {sideEffect} from '../utils/sideEffect';
import {getSelectionInfo} from '../utils/info';

View File

@@ -126,7 +126,6 @@ export {Rect} from './utils/geometry';
export {Logger} from 'flipper-common';
export {getLogger} from 'flipper-common';
export {callVSCode} from './utils/vscodeUtils';
export {checkIdbIsInstalled} from './server/devices/ios/iOSContainerUtility';
export {IDEFileResolver, IDEType} from './fb-stubs/IDEFileResolver';
export {renderMockFlipperWithPlugin} from './test-utils/createMockFlipperWithPlugin';
export {Tracked} from 'flipper-plugin'; // To be able to use it in legacy plugins

View File

@@ -51,12 +51,13 @@ import {getVersionString} from './utils/versionString';
import {PersistGate} from 'redux-persist/integration/react';
// eslint-disable-next-line flipper/no-electron-remote-imports
import {ipcRenderer, remote} from 'electron';
import {helloWorld} from 'flipper-server-core';
import {setLoggerInstance, setUserSessionManagerInstance} from 'flipper-common';
import {
setLoggerInstance,
setUserSessionManagerInstance,
GK as flipperCommonGK,
} from 'flipper-common';
import {internGraphPOSTAPIRequest} from './fb-stubs/user';
helloWorld();
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
@@ -186,6 +187,8 @@ function setProcessState(store: Store) {
}
function init() {
// TODO: centralise all those initialisations in a single configuration call
flipperCommonGK.get = (name) => GK.get(name);
const store = getStore();
const logger = initLogger(store);
setLoggerInstance(logger);

View File

@@ -17,10 +17,6 @@ export type LauncherMsg = {
message: string;
severity: 'warning' | 'error';
};
export type ServerPorts = {
insecure: number;
secure: number;
};
export type StatusMessageType = {
msg: string;
@@ -49,8 +45,6 @@ export type State = {
windowIsFocused: boolean;
share: ShareType | null;
sessionId: string | null;
serverPorts: ServerPorts;
altServerPorts: ServerPorts;
launcherMsg: LauncherMsg;
statusMessages: Array<string>;
};
@@ -69,20 +63,6 @@ export type Action =
type: 'windowIsFocused';
payload: {isFocused: boolean; time: number};
}
| {
type: 'SET_SERVER_PORTS';
payload: {
insecure: number;
secure: number;
};
}
| {
type: 'SET_ALT_SERVER_PORTS';
payload: {
insecure: number;
secure: number;
};
}
| {
type: 'LAUNCHER_MSG';
payload: {
@@ -107,14 +87,6 @@ export const initialState: () => State = () => ({
activeSheet: null,
share: null,
sessionId: uuidv1(),
serverPorts: {
insecure: 8089,
secure: 8088,
},
altServerPorts: {
insecure: 9089,
secure: 9088,
},
launcherMsg: {
severity: 'warning',
message: '',
@@ -163,16 +135,6 @@ export default function reducer(
...state,
windowIsFocused: action.payload.isFocused,
};
} else if (action.type === 'SET_SERVER_PORTS') {
return {
...state,
serverPorts: action.payload,
};
} else if (action.type === 'SET_ALT_SERVER_PORTS') {
return {
...state,
altServerPorts: action.payload,
};
} else if (action.type === 'LAUNCHER_MSG') {
return {
...state,

View File

@@ -12,16 +12,20 @@ import {produce} from 'immer';
import type BaseDevice from '../devices/BaseDevice';
import type Client from '../Client';
import type {UninitializedClient, DeviceOS, Logger} from 'flipper-common';
import type {
UninitializedClient,
DeviceOS,
Logger,
FlipperServer,
} from 'flipper-common';
import {performance} from 'perf_hooks';
import type {Actions} from '.';
import {WelcomeScreenStaticView} from '../sandy-chrome/WelcomeScreen';
import {isDevicePluginDefinition} from '../utils/pluginUtils';
import {getPluginKey} from '../utils/pluginKey';
import {deconstructClientId} from '../utils/clientUtils';
import {deconstructClientId} from 'flipper-common';
import type {RegisterPluginAction} from './plugins';
import {FlipperServerImpl} from '../server/FlipperServerImpl';
import {shallowEqual} from 'react-redux';
export type StaticViewProps = {logger: Logger};
@@ -74,7 +78,7 @@ type StateV2 = {
deepLinkPayload: unknown;
staticView: StaticView;
selectedAppPluginListRevision: number;
flipperServer: FlipperServerImpl | undefined;
flipperServer: FlipperServer | undefined;
};
type StateV1 = Omit<StateV2, 'enabledPlugins' | 'enabledDevicePlugins'> & {
@@ -158,7 +162,7 @@ export type Action =
}
| {
type: 'SET_FLIPPER_SERVER';
payload: FlipperServerImpl;
payload: FlipperServer;
}
| RegisterPluginAction;

View File

@@ -8,7 +8,7 @@
*/
import produce from 'immer';
import {deconstructPluginKey} from '../utils/clientUtils';
import {deconstructPluginKey} from 'flipper-common';
export const DEFAULT_MAX_QUEUE_SIZE = 10000;

View File

@@ -9,7 +9,7 @@
import {Actions, Store} from './';
import {setStaticView} from './connections';
import {deconstructClientId} from '../utils/clientUtils';
import {deconstructClientId} from 'flipper-common';
import {switchPlugin} from './pluginManager';
import {showStatusUpdatesForDuration} from '../utils/promiseTimeout';
import {selectedPlugins as setSelectedPlugins} from './plugins';

View File

@@ -17,13 +17,9 @@ import {
} from '@ant-design/icons';
import {Store} from '../../reducers';
import {useStore} from '../../utils/useStore';
import {launchEmulator} from '../../server/devices/android/AndroidDevice';
import {Layout, renderReactRoot, withTrackingScope} from 'flipper-plugin';
import {Provider} from 'react-redux';
import {
launchSimulator, // TODO: move to iOSDeviceManager
IOSDeviceParams,
} from '../../server/devices/ios/iOSDeviceManager';
import {IOSDeviceParams} from 'flipper-common';
const COLD_BOOT = 'cold-boot';
@@ -36,26 +32,12 @@ export function showEmulatorLauncher(store: Store) {
}
function LaunchEmulatorContainer({onClose}: {onClose: () => void}) {
const flipperServer = useStore((state) => state.connections.flipperServer);
return (
<LaunchEmulatorDialog
onClose={onClose}
getSimulators={() => flipperServer!.ios.getSimulators(false)}
getEmulators={() => flipperServer!.android.getAndroidEmulators()}
/>
);
return <LaunchEmulatorDialog onClose={onClose} />;
}
export const LaunchEmulatorDialog = withTrackingScope(
function LaunchEmulatorDialog({
onClose,
getSimulators,
getEmulators,
}: {
onClose: () => void;
getSimulators: () => Promise<IOSDeviceParams[]>;
getEmulators: () => Promise<string[]>;
}) {
function LaunchEmulatorDialog({onClose}: {onClose: () => void}) {
const flipperServer = useStore((state) => state.connections.flipperServer);
const iosEnabled = useStore((state) => state.settingsState.enableIOS);
const androidEnabled = useStore(
(state) => state.settingsState.enableAndroid,
@@ -63,12 +45,12 @@ export const LaunchEmulatorDialog = withTrackingScope(
const [iosEmulators, setIosEmulators] = useState<IOSDeviceParams[]>([]);
const [androidEmulators, setAndroidEmulators] = useState<string[]>([]);
const store = useStore();
useEffect(() => {
if (!iosEnabled) {
return;
}
getSimulators()
flipperServer!
.exec('ios-get-simulators', false)
.then((emulators) => {
setIosEmulators(
emulators.filter(
@@ -81,20 +63,21 @@ export const LaunchEmulatorDialog = withTrackingScope(
.catch((e) => {
console.warn('Failed to find simulators', e);
});
}, [iosEnabled, getSimulators, store]);
}, [iosEnabled, flipperServer]);
useEffect(() => {
if (!androidEnabled) {
return;
}
getEmulators()
flipperServer!
.exec('android-get-emulators')
.then((emulators) => {
setAndroidEmulators(emulators);
})
.catch((e) => {
console.warn('Failed to find emulators', e);
});
}, [androidEnabled, getEmulators]);
}, [androidEnabled, flipperServer]);
const items = [
...(androidEmulators.length > 0
@@ -102,7 +85,8 @@ export const LaunchEmulatorDialog = withTrackingScope(
: []),
...androidEmulators.map((name) => {
const launch = (coldBoot: boolean) => {
launchEmulator(name, coldBoot)
flipperServer!
.exec('android-launch-emulator', name, coldBoot)
.then(onClose)
.catch((e) => {
console.error('Failed to start emulator: ', e);
@@ -139,7 +123,8 @@ export const LaunchEmulatorDialog = withTrackingScope(
<Button
key={device.udid}
onClick={() =>
launchSimulator(device.udid)
flipperServer!
.exec('ios-launch-simulator', device.udid)
.catch((e) => {
console.error('Failed to start simulator: ', e);
message.error('Failed to start simulator: ' + e);

View File

@@ -15,23 +15,23 @@ import {LaunchEmulatorDialog} from '../LaunchEmulator';
import {createRootReducer} from '../../../reducers';
import {sleep} from 'flipper-plugin';
import {launchEmulator} from '../../../server/devices/android/AndroidDevice';
jest.mock('../../../server/devices/android/AndroidDevice', () => ({
launchEmulator: jest.fn(() => Promise.resolve([])),
}));
import {createFlipperServerMock} from '../../../test-utils/createFlipperServerMock';
test('Can render and launch android apps - empty', async () => {
const store = createStore(createRootReducer());
const mockServer = createFlipperServerMock({
'ios-get-simulators': () => Promise.resolve([]),
'android-get-emulators': () => Promise.resolve([]),
});
store.dispatch({
type: 'SET_FLIPPER_SERVER',
payload: mockServer,
});
const onClose = jest.fn();
const renderer = render(
<Provider store={store}>
<LaunchEmulatorDialog
onClose={onClose}
getSimulators={() => Promise.resolve([])}
getEmulators={() => Promise.resolve([])}
/>
<LaunchEmulatorDialog onClose={onClose} />
</Provider>,
);
@@ -45,7 +45,21 @@ test('Can render and launch android apps - empty', async () => {
});
test('Can render and launch android apps', async () => {
let p: Promise<any> | undefined = undefined;
const store = createStore(createRootReducer());
const launch = jest.fn().mockImplementation(() => Promise.resolve());
const mockServer = createFlipperServerMock({
'ios-get-simulators': () => Promise.resolve([]),
'android-get-emulators': () =>
(p = Promise.resolve(['emulator1', 'emulator2'])),
'android-launch-emulator': launch,
});
store.dispatch({
type: 'SET_FLIPPER_SERVER',
payload: mockServer,
});
store.dispatch({
type: 'UPDATE_SETTINGS',
payload: {
@@ -55,15 +69,9 @@ test('Can render and launch android apps', async () => {
});
const onClose = jest.fn();
let p: Promise<any> | undefined = undefined;
const renderer = render(
<Provider store={store}>
<LaunchEmulatorDialog
onClose={onClose}
getSimulators={() => Promise.resolve([])}
getEmulators={() => (p = Promise.resolve(['emulator1', 'emulator2']))}
/>
<LaunchEmulatorDialog onClose={onClose} />
</Provider>,
);
@@ -84,5 +92,5 @@ test('Can render and launch android apps', async () => {
fireEvent.click(renderer.getByText('emulator2'));
await sleep(1000);
expect(onClose).toBeCalled();
expect(launchEmulator).toBeCalledWith('emulator2', false);
expect(launch).toBeCalledWith('emulator2', false);
});

View File

@@ -1,31 +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
*/
/**
* A set of utilities that should be provided by the hosting environment
*/
export interface FlipperServerHost {}
let instance: FlipperServerHost | undefined;
export function getFlipperServerHost(): FlipperServerHost {
if (!instance) {
throw new Error(
'FlipperServerHost not set, call setFlipperServerHost first',
);
}
return instance;
}
export function setFlipperServerHost(impl: FlipperServerHost) {
if (instance) {
throw new Error('FlipperServerHost was already set');
}
instance = impl;
}

View File

@@ -11,13 +11,8 @@ import {createStore} from 'redux';
import BaseDevice from '../devices/BaseDevice';
import {createRootReducer} from '../reducers';
import {Store} from '../reducers/index';
import Client from '../Client';
import {
ClientConnection,
ConnectionStatusChange,
} from '../server/comms/ClientConnection';
import {buildClientId} from '../utils/clientUtils';
import {Logger} from 'flipper-common';
import Client, {ClientConnection} from '../Client';
import {Logger, buildClientId, FlipperServer} from 'flipper-common';
import {PluginDefinition} from '../plugin';
import {registerPlugins} from '../reducers/plugins';
import {getLogger} from 'flipper-common';
@@ -27,6 +22,7 @@ import {PluginDetails} from 'flipper-plugin-lib';
import ArchivedDevice from '../devices/ArchivedDevice';
import {ClientQuery, DeviceOS} from 'flipper-common';
import {TestDevice} from './TestDevice';
import {createFlipperServerMock} from './createFlipperServerMock';
export interface AppOptions {
plugins?: PluginDefinition[];
@@ -56,6 +52,7 @@ export default class MockFlipper {
private _clients: Client[] = [];
private _deviceCounter: number = 0;
private _clientCounter: number = 0;
private _flipperServer: FlipperServer = createFlipperServerMock();
public get store(): Store {
return this._store;
@@ -89,6 +86,10 @@ export default class MockFlipper {
});
initializeFlipperLibImplementation(this._store, this._logger);
this._store.dispatch(registerPlugins(plugins ?? []));
this._store.dispatch({
type: 'SET_FLIPPER_SERVER',
payload: this._flipperServer,
});
}
public async initWithDeviceAndClient(
@@ -237,12 +238,9 @@ export default class MockFlipper {
return client;
}
}
function createStubConnection(): ClientConnection | null | undefined {
function createStubConnection(): ClientConnection {
return {
subscribeToEvents(_: ConnectionStatusChange) {},
close() {
throw new Error('Should not be called in test');
},
send(_: any) {
throw new Error('Should not be called in test');
},

View File

@@ -0,0 +1,32 @@
/**
* 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 {FlipperServer, FlipperServerCommands} from 'flipper-common';
export function createFlipperServerMock(
overrides?: Partial<FlipperServerCommands>,
): FlipperServer {
return {
on: jest.fn(),
off: jest.fn(),
exec: jest
.fn()
.mockImplementation(
(cmd: keyof FlipperServerCommands, ...args: any[]) => {
if (overrides?.[cmd]) {
return (overrides[cmd] as any)(...args);
}
return Promise.reject(
new Error(`FlipperServerMock exec not implemented: ${cmd}}`),
);
},
),
close: jest.fn(),
};
}

View File

@@ -7,39 +7,10 @@
* @format
*/
import {deconstructClientId} from 'flipper-common';
import type Client from '../Client';
import type BaseDevice from '../devices/BaseDevice';
/* A Client uniuely identifies an app running on some device.
Always use this utility to construct and parse clientId strings.
*/
export type ClientIdConstituents = {
app: string;
os: string;
device: string;
device_id: string;
};
/* A plugin key is a string uniquely identifying an instance of a plugin.
This can be a device plugin for a particular device, or a client plugin for a particular client (app).
In the device plugin case, the "client" is the device it's connected to.
In the client plugin case (normal plugins), the "client" is the app it's connected to.
Always use this utility to construct and parse pluginKey strings.
*/
type PluginKeyConstituents =
| {
type: 'device';
pluginName: string;
client: string;
}
| ({
type: 'client';
pluginName: string;
client: string;
} & ClientIdConstituents);
export function currentActiveApps(
clients: Array<Client>,
selectedDevice: null | BaseDevice,
@@ -56,65 +27,3 @@ export function currentActiveApps(
.map((client) => client.appName);
return currentActiveApps;
}
export function buildClientId(clientInfo: {
app: string;
os: string;
device: string;
device_id: string;
}): string {
// N.B.: device_id can be empty, which designates the host device
for (const key of ['app', 'os', 'device'] as Array<
keyof ClientIdConstituents
>) {
if (!clientInfo[key]) {
console.error(
`Attempted to build clientId with invalid ${key}: "${clientInfo[key]}`,
);
}
}
const escapedName = escape(clientInfo.app);
return `${escapedName}#${clientInfo.os}#${clientInfo.device}#${clientInfo.device_id}`;
}
export function deconstructClientId(clientId: string): ClientIdConstituents {
if (!clientId || clientId.split('#').length !== 4) {
console.error(`Attempted to deconstruct invalid clientId: "${clientId}"`);
}
let [app, os, device, device_id] = clientId.split('#');
app = unescape(app);
return {
app,
os,
device,
device_id,
};
}
export function deconstructPluginKey(pluginKey: string): PluginKeyConstituents {
const parts = pluginKey.split('#');
if (parts.length === 2) {
// Device plugin
return {
type: 'device',
client: parts[0],
pluginName: parts[1],
};
} else {
// Client plugin
const lastHashIndex = pluginKey.lastIndexOf('#');
const clientId = pluginKey.slice(0, lastHashIndex);
const pluginName = pluginKey.slice(lastHashIndex + 1);
if (!pluginName) {
console.error(
`Attempted to deconstruct invalid pluginKey: "${pluginKey}"`,
);
}
return {
type: 'client',
...deconstructClientId(clientId),
client: clientId,
pluginName: pluginName,
};
}
}

View File

@@ -34,7 +34,7 @@ import {
resetSupportFormV2State,
SupportFormRequestDetailsState,
} from '../reducers/supportForm';
import {deconstructClientId} from '../utils/clientUtils';
import {deconstructClientId} from 'flipper-common';
import {performance} from 'perf_hooks';
import {processMessageQueue} from './messageQueue';
import {getPluginTitle} from './pluginUtils';

View File

@@ -16,7 +16,7 @@ import type BaseDevice from '../devices/BaseDevice';
import {clipboard, shell} from 'electron';
import constants from '../fb-stubs/constants';
import {addNotification} from '../reducers/notifications';
import {deconstructPluginKey} from './clientUtils';
import {deconstructPluginKey} from 'flipper-common';
import {DetailSidebarImpl} from '../sandy-chrome/DetailSidebarImpl';
export function initializeFlipperLibImplementation(

View File

@@ -12,9 +12,8 @@ import isProduction from './isProduction';
import fs from 'fs-extra';
import {getStaticPath} from './pathUtils';
import type {State, Store} from '../reducers/index';
import {deconstructClientId} from './clientUtils';
import {sideEffect} from './sideEffect';
import {Logger, isTest} from 'flipper-common';
import {Logger, isTest, deconstructClientId} from 'flipper-common';
type PlatformInfo = {
arch: string;

View File

@@ -17,7 +17,7 @@ import {
} from '../reducers/pluginMessageQueue';
import {IdlerImpl} from './Idler';
import {isPluginEnabled, getSelectedPluginKey} from '../reducers/connections';
import {deconstructPluginKey} from './clientUtils';
import {deconstructPluginKey} from 'flipper-common';
import {defaultEnabledBackgroundPlugins} from './pluginUtils';
import {batch, Idler, _SandyPluginInstance} from 'flipper-plugin';
import {addBackgroundStat} from './pluginStats';

View File

@@ -0,0 +1,14 @@
/**
* 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 const GK = {
get(_name: string): boolean {
return false;
},
};

View File

@@ -0,0 +1,100 @@
/**
* 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
*/
/* A Client uniuely identifies an app running on some device.
Always use this utility to construct and parse clientId strings.
*/
export type ClientIdConstituents = {
app: string;
os: string;
device: string;
device_id: string;
};
/* A plugin key is a string uniquely identifying an instance of a plugin.
This can be a device plugin for a particular device, or a client plugin for a particular client (app).
In the device plugin case, the "client" is the device it's connected to.
In the client plugin case (normal plugins), the "client" is the app it's connected to.
Always use this utility to construct and parse pluginKey strings.
*/
type PluginKeyConstituents =
| {
type: 'device';
pluginName: string;
client: string;
}
| ({
type: 'client';
pluginName: string;
client: string;
} & ClientIdConstituents);
export function buildClientId(clientInfo: {
app: string;
os: string;
device: string;
device_id: string;
}): string {
// N.B.: device_id can be empty, which designates the host device
for (const key of ['app', 'os', 'device'] as Array<
keyof ClientIdConstituents
>) {
if (!clientInfo[key]) {
console.error(
`Attempted to build clientId with invalid ${key}: "${clientInfo[key]}`,
);
}
}
const escapedName = escape(clientInfo.app);
return `${escapedName}#${clientInfo.os}#${clientInfo.device}#${clientInfo.device_id}`;
}
export function deconstructClientId(clientId: string): ClientIdConstituents {
if (!clientId || clientId.split('#').length !== 4) {
console.error(`Attempted to deconstruct invalid clientId: "${clientId}"`);
}
let [app, os, device, device_id] = clientId.split('#');
app = unescape(app);
return {
app,
os,
device,
device_id,
};
}
export function deconstructPluginKey(pluginKey: string): PluginKeyConstituents {
const parts = pluginKey.split('#');
if (parts.length === 2) {
// Device plugin
return {
type: 'device',
client: parts[0],
pluginName: parts[1],
};
} else {
// Client plugin
const lastHashIndex = pluginKey.lastIndexOf('#');
const clientId = pluginKey.slice(0, lastHashIndex);
const pluginName = pluginKey.slice(lastHashIndex + 1);
if (!pluginName) {
console.error(
`Attempted to deconstruct invalid pluginKey: "${pluginKey}"`,
);
}
return {
type: 'client',
...deconstructClientId(clientId),
client: clientId,
pluginName: pluginName,
};
}
}

View File

@@ -41,3 +41,5 @@ export {
getErrorFromErrorLike,
} from './utils/errors';
export * from './user-session';
export * from './GK';
export * from './clientUtils';

View File

@@ -112,6 +112,14 @@ export type FlipperServerEvents = {
};
};
export type IOSDeviceParams = {
udid: string;
type: DeviceType;
name: string;
deviceTypeIdentifier?: string;
state?: string;
};
export type FlipperServerCommands = {
'device-start-logging': (serial: string) => Promise<void>;
'device-stop-logging': (serial: string) => Promise<void>;
@@ -137,6 +145,10 @@ export type FlipperServerCommands = {
clientId: string,
payload: any,
) => Promise<ClientResponseType>;
'android-get-emulators': () => Promise<string[]>;
'android-launch-emulator': (name: string, coldboot: boolean) => Promise<void>;
'ios-get-simulators': (bootedOnly: boolean) => Promise<IOSDeviceParams[]>;
'ios-launch-simulator': (udid: string) => Promise<void>;
};
export interface FlipperServer {
@@ -154,3 +166,95 @@ export interface FlipperServer {
): ReturnType<FlipperServerCommands[Event]>;
close(): void;
}
// From xplat/js/metro/packages/metro/src/lib/reporting.js
export type MetroBundleDetails = {
entryFile: string;
platform?: string;
dev: boolean;
minify: boolean;
bundleType: string;
};
// From xplat/js/metro/packages/metro/src/lib/reporting.js
export type MetroGlobalCacheDisabledReason =
| 'too_many_errors'
| 'too_many_misses';
/**
* A tagged union of all the actions that may happen and we may want to
* report to the tool user.
*
* Based on xplat/js/metro/packages/metro/src/lib/TerminalReporter.js
*/
export type MetroReportableEvent =
| {
port: number;
projectRoots: ReadonlyArray<string>;
type: 'initialize_started';
}
| {type: 'initialize_done'}
| {
type: 'initialize_failed';
port: number;
error: Error;
}
| {
buildID: string;
type: 'bundle_build_done';
}
| {
buildID: string;
type: 'bundle_build_failed';
}
| {
buildID: string;
bundleDetails: MetroBundleDetails;
type: 'bundle_build_started';
}
| {
error: Error;
type: 'bundling_error';
}
| {type: 'dep_graph_loading'}
| {type: 'dep_graph_loaded'}
| {
buildID: string;
type: 'bundle_transform_progressed';
transformedFileCount: number;
totalFileCount: number;
}
| {
type: 'global_cache_error';
error: Error;
}
| {
type: 'global_cache_disabled';
reason: MetroGlobalCacheDisabledReason;
}
| {type: 'transform_cache_reset'}
| {
type: 'worker_stdout_chunk';
chunk: string;
}
| {
type: 'worker_stderr_chunk';
chunk: string;
}
| {
type: 'hmr_client_error';
error: Error;
}
| {
type: 'client_log';
level:
| 'trace'
| 'info'
| 'warn'
| 'log'
| 'group'
| 'groupCollapsed'
| 'groupEnd'
| 'debug';
data: Array<any>;
};

View File

@@ -9,7 +9,29 @@
"types": "lib/index.d.ts",
"license": "MIT",
"bugs": "https://github.com/facebook/flipper/issues",
"dependencies": {},
"dependencies": {
"JSONStream": "^1.3.1",
"adbkit": "^2.11.1",
"adbkit-logcat": "^2.0.1",
"archiver": "^5.3.0",
"async-mutex": "^0.3.2",
"flipper-common": "0.0.0",
"fs-extra": "^10.0.0",
"invariant": "^2.2.4",
"js-base64": "^3.7.2",
"lodash.memoize": "^4.1.2",
"openssl-wrapper": "^0.3.4",
"promisify-child-process": "^4.1.1",
"rsocket-core": "^0.0.27",
"rsocket-flowable": "^0.0.27",
"rsocket-tcp-server": "^0.0.25",
"rsocket-types": "^0.0.25",
"split2": "^3.2.2",
"tmp": "^0.2.1",
"uuid": "^8.3.2",
"which": "^2.0.2",
"ws": "^8.2.3"
},
"devDependencies": {},
"peerDependencies": {},
"scripts": {

View File

@@ -0,0 +1,93 @@
/**
* 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 {isTest} from 'flipper-common';
import {parseFlipperPorts} from './utils/environmentVariables';
export interface FlipperServerConfig {
enableAndroid: boolean;
androidHome: string;
enableIOS: boolean;
idbPath: string;
enablePhysicalIOS: boolean;
validWebSocketOrigins: string[];
staticPath: string;
tmpPath: 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/',
tmpPath: '/temp/',
};
let currentConfig: FlipperServerConfig | undefined = undefined;
export function getFlipperServerConfig(): FlipperServerConfig {
if (!currentConfig) {
if (isTest()) return testConfig;
throw new Error('FlipperServerConfig has not been set');
}
return currentConfig;
}
export function setFlipperServerConfig(config: FlipperServerConfig) {
currentConfig = config;
}
type ServerPorts = {
insecure: number;
secure: number;
};
export function getServerPortsConfig(): {
serverPorts: ServerPorts;
altServerPorts: ServerPorts;
} {
let portOverrides: ServerPorts | undefined;
if (process.env.FLIPPER_PORTS) {
portOverrides = parseFlipperPorts(process.env.FLIPPER_PORTS);
if (!portOverrides) {
console.error(
`Ignoring malformed FLIPPER_PORTS env variable:
"${process.env.FLIPPER_PORTS || ''}".
Example expected format: "1111,2222".`,
);
}
}
let portAltOverrides: ServerPorts | undefined;
if (process.env.FLIPPER_ALT_PORTS) {
portAltOverrides = parseFlipperPorts(process.env.FLIPPER_ALT_PORTS);
if (!portAltOverrides) {
console.error(
`Ignoring malformed FLIPPER_ALT_PORTS env variable:
"${process.env.FLIPPER_ALT_PORTS || ''}".
Example expected format: "1111,2222".`,
);
}
}
return {
serverPorts: portOverrides ?? {
insecure: 8089,
secure: 8088,
},
altServerPorts: portAltOverrides ?? {
insecure: 9089,
secure: 9088,
},
};
}

View File

@@ -11,9 +11,11 @@ import EventEmitter from 'events';
import {Logger} from 'flipper-common';
import ServerController from './comms/ServerController';
import {CertificateExchangeMedium} from './utils/CertificateProvider';
import {ServerPorts} from '../reducers/application';
import {AndroidDeviceManager} from './devices/android/androidDeviceManager';
import {IOSDeviceManager} from './devices/ios/iOSDeviceManager';
import {
IOSDeviceManager,
launchSimulator,
} from './devices/ios/iOSDeviceManager';
import metroDevice from './devices/metro/metroDeviceManager';
import desktopDevice from './devices/desktop/desktopDeviceManager';
import {
@@ -26,33 +28,8 @@ import {
import {ServerDevice} from './devices/ServerDevice';
import {Base64} from 'js-base64';
import MetroDevice from './devices/metro/MetroDevice';
export interface FlipperServerConfig {
enableAndroid: boolean;
androidHome: string;
enableIOS: boolean;
idbPath: string;
enablePhysicalIOS: boolean;
serverPorts: ServerPorts;
altServerPorts: ServerPorts;
}
// defaultConfig should be used for testing only, and disables by default all features
const defaultConfig: FlipperServerConfig = {
androidHome: '',
enableAndroid: false,
enableIOS: false,
enablePhysicalIOS: false,
idbPath: '',
serverPorts: {
insecure: -1,
secure: -1,
},
altServerPorts: {
insecure: -1,
secure: -1,
},
};
import {launchEmulator} from './devices/android/AndroidDevice';
import {getFlipperServerConfig} from './FlipperServerConfig';
/**
* FlipperServer takes care of all incoming device & client connections.
@@ -63,8 +40,6 @@ const defaultConfig: FlipperServerConfig = {
* using '.on'. All events are strongly typed.
*/
export class FlipperServerImpl implements FlipperServer {
public config: FlipperServerConfig;
private readonly events = new EventEmitter();
// server handles the incoming RSocket / WebSocket connections from Flipper clients
readonly server: ServerController;
@@ -74,8 +49,8 @@ export class FlipperServerImpl implements FlipperServer {
android: AndroidDeviceManager;
ios: IOSDeviceManager;
constructor(config: Partial<FlipperServerConfig>, public logger: Logger) {
this.config = {...defaultConfig, ...config};
constructor(public logger: Logger) {
getFlipperServerConfig(); // Config should be available at this point!
const server = (this.server = new ServerController(this));
this.android = new AndroidDeviceManager(this);
this.ios = new IOSDeviceManager(this);
@@ -239,6 +214,12 @@ export class FlipperServerImpl implements FlipperServer {
},
};
},
'android-get-emulators': async () => this.android.getAndroidEmulators(),
'android-launch-emulator': async (name, coldBoot) =>
launchEmulator(name, coldBoot),
'ios-get-simulators': async (bootedOnly) =>
this.ios.getSimulators(bootedOnly),
'ios-launch-simulator': async (udid) => launchSimulator(udid),
};
registerDevice(device: ServerDevice) {

View File

@@ -9,16 +9,20 @@
import {CertificateExchangeMedium} from '../utils/CertificateProvider';
import {Logger} from 'flipper-common';
import {ClientDescription, ClientQuery, isTest} from 'flipper-common';
import {
ClientDescription,
ClientQuery,
isTest,
GK,
buildClientId,
} from 'flipper-common';
import CertificateProvider from '../utils/CertificateProvider';
import {ClientConnection, ConnectionStatus} from './ClientConnection';
import {UninitializedClient} from 'flipper-common';
import {reportPlatformFailures} from 'flipper-common';
import {EventEmitter} from 'events';
import invariant from 'invariant';
import GK from '../../fb-stubs/GK';
import {buildClientId} from '../../utils/clientUtils';
import DummyDevice from '../../server/devices/DummyDevice';
import DummyDevice from '../devices/DummyDevice';
import {
appNameWithUpdateHint,
transformCertificateExchangeMediumToType,
@@ -33,6 +37,10 @@ import {
TransportType,
} from './ServerFactory';
import {FlipperServerImpl} from '../FlipperServerImpl';
import {
getServerPortsConfig,
getFlipperServerConfig,
} from '../FlipperServerConfig';
type ClientInfo = {
connection: ClientConnection | null | undefined;
@@ -80,7 +88,7 @@ class ServerController extends EventEmitter implements ServerEventsListener {
this.certificateProvider = new CertificateProvider(
this,
this.logger,
this.flipperServer.config,
getFlipperServerConfig(),
);
this.connectionTracker = new ConnectionTracker(this.logger);
this.secureServer = null;
@@ -111,7 +119,7 @@ class ServerController extends EventEmitter implements ServerEventsListener {
if (isTest()) {
throw new Error('Spawing new server is not supported in test');
}
const {insecure, secure} = this.flipperServer.config.serverPorts;
const {insecure, secure} = getServerPortsConfig().serverPorts;
this.initialized = this.certificateProvider
.loadSecureServerConfig()
@@ -119,7 +127,7 @@ class ServerController extends EventEmitter implements ServerEventsListener {
console.info('[conn] secure server listening at port: ', secure);
this.secureServer = createServer(secure, this, options);
if (GK.get('flipper_websocket_server')) {
const {secure: altSecure} = this.flipperServer.config.altServerPorts;
const {secure: altSecure} = getServerPortsConfig().altServerPorts;
console.info(
'[conn] secure server (ws) listening at port: ',
altSecure,
@@ -136,8 +144,7 @@ class ServerController extends EventEmitter implements ServerEventsListener {
console.info('[conn] insecure server listening at port: ', insecure);
this.insecureServer = createServer(insecure, this);
if (GK.get('flipper_websocket_server')) {
const {insecure: altInsecure} =
this.flipperServer.config.altServerPorts;
const {insecure: altInsecure} = getServerPortsConfig().altServerPorts;
console.info(
'[conn] insecure server (ws) listening at port: ',
altInsecure,
@@ -214,13 +221,13 @@ class ServerController extends EventEmitter implements ServerEventsListener {
const {os, app, device_id} = clientQuery;
// without these checks, the user might see a connection timeout error instead, which would be much harder to track down
if (os === 'iOS' && !this.flipperServer.config.enableIOS) {
if (os === 'iOS' && !getFlipperServerConfig().enableIOS) {
console.error(
`Refusing connection from ${app} on ${device_id}, since iOS support is disabled in settings`,
);
return;
}
if (os === 'Android' && !this.flipperServer.config.enableAndroid) {
if (os === 'Android' && !getFlipperServerConfig().enableAndroid) {
console.error(
`Refusing connection from ${app} on ${device_id}, since Android support is disabled in settings`,
);

View File

@@ -12,10 +12,10 @@ import WebSocket from 'ws';
import querystring from 'querystring';
import {BrowserClientFlipperConnection} from './BrowserClientFlipperConnection';
import {ServerEventsListener} from './ServerAdapter';
import constants from '../../fb-stubs/constants';
import ws from 'ws';
import {IncomingMessage} from 'http';
import {ClientDescription, ClientQuery} from 'flipper-common';
import {getFlipperServerConfig} from '../FlipperServerConfig';
/**
* WebSocket-based server which uses a connect/disconnect handshake over an insecure channel.
@@ -27,7 +27,7 @@ class ServerWebSocketBrowser extends ServerWebSocketBase {
verifyClient(): ws.VerifyClientCallbackSync {
return (info: {origin: string; req: IncomingMessage; secure: boolean}) => {
const ok = constants.VALID_WEB_SOCKET_REQUEST_ORIGIN_PREFIXES.some(
const ok = getFlipperServerConfig().validWebSocketOrigins.some(
(validPrefix) => info.origin.startsWith(validPrefix),
);
if (!ok) {

View File

@@ -17,6 +17,10 @@ import {Client as ADBClient, Device} from 'adbkit';
import {join} from 'path';
import {FlipperServerImpl} from '../../FlipperServerImpl';
import {notNull} from '../../utils/typeUtils';
import {
getServerPortsConfig,
getFlipperServerConfig,
} from '../../FlipperServerConfig';
export class AndroidDeviceManager {
// cache emulator path
@@ -58,12 +62,10 @@ export class AndroidDeviceManager {
abiList,
sdkVersion,
);
if (this.flipperServer.config.serverPorts) {
const ports = getServerPortsConfig();
if (ports.serverPorts) {
await androidLikeDevice
.reverse([
this.flipperServer.config.serverPorts.secure,
this.flipperServer.config.serverPorts.insecure,
])
.reverse([ports.serverPorts.secure, ports.serverPorts.insecure])
// We may not be able to establish a reverse connection, e.g. for old Android SDKs.
// This is *generally* fine, because we hard-code the ports on the SDK side.
.catch((e) => {
@@ -177,7 +179,7 @@ export class AndroidDeviceManager {
async watchAndroidDevices() {
try {
const client = await getAdbClient(this.flipperServer.config);
const client = await getAdbClient(getFlipperServerConfig());
client
.trackDevices()
.then((tracker) => {

View File

@@ -13,7 +13,7 @@ import {DeviceType} from 'flipper-plugin-lib';
import {v1 as uuid} from 'uuid';
import path from 'path';
import {exec} from 'promisify-child-process';
import {getAppTempPath} from '../../../utils/pathUtils';
import {getFlipperServerConfig} from '../../FlipperServerConfig';
export const ERR_NO_IDB_OR_XCODE_AVAILABLE =
'Neither Xcode nor idb available. Cannot provide iOS device functionality.';
@@ -98,8 +98,7 @@ export function xcrunStartLogListener(udid: string, deviceType: DeviceType) {
function makeTempScreenshotFilePath() {
const imageName = uuid() + '.png';
const directory = getAppTempPath();
return path.join(directory, imageName);
return path.join(getFlipperServerConfig().tmpPath, imageName);
}
async function runScreenshotCommand(

View File

@@ -10,20 +10,16 @@
import {makeIOSBridge} from '../IOSBridge';
import childProcess from 'child_process';
import * as promisifyChildProcess from 'promisify-child-process';
import {mocked} from 'ts-jest/utils';
jest.mock('child_process');
const spawn = mocked(childProcess.spawn);
jest.mock('promisify-child-process');
const exec = mocked(promisifyChildProcess.exec);
test('uses xcrun with no idb when xcode is detected', async () => {
const ib = await makeIOSBridge('', true);
ib.startLogListener('deadbeef', 'emulator');
expect(spawn).toHaveBeenCalledWith(
expect(childProcess.spawn).toHaveBeenCalledWith(
'xcrun',
[
'simctl',
@@ -47,7 +43,7 @@ test('uses idb when present and xcode detected', async () => {
ib.startLogListener('deadbeef', 'emulator');
expect(spawn).toHaveBeenCalledWith(
expect(childProcess.spawn).toHaveBeenCalledWith(
'/usr/local/bin/idb',
[
'log',
@@ -70,7 +66,7 @@ test('uses idb when present and xcode detected and physical device connected', a
ib.startLogListener('deadbeef', 'physical');
expect(spawn).toHaveBeenCalledWith(
expect(childProcess.spawn).toHaveBeenCalledWith(
'/usr/local/bin/idb',
[
'log',
@@ -101,7 +97,7 @@ test.unix(
ib.screenshot('deadbeef');
expect(exec).toHaveBeenCalledWith(
expect(promisifyChildProcess.exec).toHaveBeenCalledWith(
'xcrun simctl io deadbeef screenshot /temp/00000000-0000-0000-0000-000000000000.png',
);
},
@@ -112,7 +108,7 @@ test.unix('uses idb to take screenshots when available', async () => {
ib.screenshot('deadbeef');
expect(exec).toHaveBeenCalledWith(
expect(promisifyChildProcess.exec).toHaveBeenCalledWith(
'idb screenshot --udid deadbeef /temp/00000000-0000-0000-0000-000000000000.png',
);
});
@@ -122,7 +118,7 @@ test('uses xcrun to navigate with no idb when xcode is detected', async () => {
ib.navigate('deadbeef', 'fb://dummy');
expect(exec).toHaveBeenCalledWith(
expect(promisifyChildProcess.exec).toHaveBeenCalledWith(
'xcrun simctl io deadbeef launch url "fb://dummy"',
);
});
@@ -132,7 +128,9 @@ test('uses idb to navigate when available', async () => {
ib.navigate('deadbeef', 'fb://dummy');
expect(exec).toHaveBeenCalledWith('idb open --udid deadbeef "fb://dummy"');
expect(promisifyChildProcess.exec).toHaveBeenCalledWith(
'idb open --udid deadbeef "fb://dummy"',
);
});
test('uses xcrun to record with no idb when xcode is detected', async () => {
@@ -140,7 +138,7 @@ test('uses xcrun to record with no idb when xcode is detected', async () => {
ib.recordVideo('deadbeef', '/tmp/video.mp4');
expect(exec).toHaveBeenCalledWith(
expect(promisifyChildProcess.exec).toHaveBeenCalledWith(
'xcrun simctl io deadbeef recordVideo --codec=h264 --force /tmp/video.mp4',
);
});
@@ -150,7 +148,7 @@ test('uses idb to record when available', async () => {
ib.recordVideo('deadbeef', '/tmo/video.mp4');
expect(exec).toHaveBeenCalledWith(
expect(promisifyChildProcess.exec).toHaveBeenCalledWith(
'idb record-video --udid deadbeef /tmo/video.mp4',
);
});

View File

@@ -8,16 +8,10 @@
*/
import {parseXcodeFromCoreSimPath} from '../iOSDeviceManager';
import configureStore from 'redux-mock-store';
import {State, createRootReducer} from '../../../../reducers/index';
import {getLogger} from 'flipper-common';
import {IOSBridge} from '../IOSBridge';
import {FlipperServerImpl} from '../../../FlipperServerImpl';
const mockStore = configureStore<State, {}>([])(
createRootReducer()(undefined, {type: 'INIT'}),
);
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';
@@ -59,7 +53,7 @@ test('test parseXcodeFromCoreSimPath from standard locations', () => {
});
test('test getAllPromisesForQueryingDevices when xcode detected', () => {
const flipperServer = new FlipperServerImpl({}, getLogger());
const flipperServer = new FlipperServerImpl(getLogger());
flipperServer.ios.iosBridge = {} as IOSBridge;
const promises = flipperServer.ios.getAllPromisesForQueryingDevices(
true,
@@ -69,7 +63,7 @@ test('test getAllPromisesForQueryingDevices when xcode detected', () => {
});
test('test getAllPromisesForQueryingDevices when xcode is not detected', () => {
const flipperServer = new FlipperServerImpl({}, getLogger());
const flipperServer = new FlipperServerImpl(getLogger());
flipperServer.ios.iosBridge = {} as IOSBridge;
const promises = flipperServer.ios.getAllPromisesForQueryingDevices(
false,
@@ -79,7 +73,7 @@ test('test getAllPromisesForQueryingDevices when xcode is not detected', () => {
});
test('test getAllPromisesForQueryingDevices when xcode and idb are both unavailable', () => {
const flipperServer = new FlipperServerImpl({}, getLogger());
const flipperServer = new FlipperServerImpl(getLogger());
flipperServer.ios.iosBridge = {} as IOSBridge;
const promises = flipperServer.ios.getAllPromisesForQueryingDevices(
false,
@@ -89,7 +83,7 @@ test('test getAllPromisesForQueryingDevices when xcode and idb are both unavaila
});
test('test getAllPromisesForQueryingDevices when both idb and xcode are available', () => {
const flipperServer = new FlipperServerImpl({}, getLogger());
const flipperServer = new FlipperServerImpl(getLogger());
flipperServer.ios.iosBridge = {} as IOSBridge;
const promises = flipperServer.ios.getAllPromisesForQueryingDevices(
true,

View File

@@ -7,7 +7,6 @@
* @format
*/
import React from 'react';
import {Mutex} from 'async-mutex';
import {exec as unsafeExec, Output} from 'promisify-child-process';
import {reportPlatformFailures} from 'flipper-common';
@@ -18,8 +17,6 @@ import {promisify} from 'util';
import child_process from 'child_process';
import fs from 'fs-extra';
import path from 'path';
import fbConfig from '../../../fb-stubs/config';
import {notification, Typography} from 'antd';
const exec = promisify(child_process.exec);
// Use debug to get helpful logs when idb fails
@@ -251,14 +248,9 @@ function handleMissingIdb(e: Error, idbPath: string): void {
e.message.includes('sudo: no tty present and no askpass program specified')
) {
console.warn(e);
const message = `idb doesn't appear to be installed. Run "${idbPath} list-targets" to fix this.`;
notification.error({
message: 'Cannot connect to idb',
duration: null,
description: message,
key: 'idb_connection_failed',
});
return;
throw new Error(
`idb doesn't appear to be installed. Run "${idbPath} list-targets" to fix this.`,
);
}
throw e;
}
@@ -271,24 +263,11 @@ function handleMissingPermissions(e: Error): void {
e.message.includes('sonar/app.csr')
) {
console.warn(e);
const message = fbConfig.isFBBuild ? (
<>
Idb lacks permissions to exchange certificates. Did you install a source
build or a debug build with certificate exchange enabled?{' '}
<Typography.Link href="https://www.internalfb.com/intern/staticdocs/flipper/docs/getting-started/fb/connecting-to-flipper#app-on-physical-ios-device">
How To
</Typography.Link>
</>
) : (
'Idb lacks permissions to exchange certificates. Did you install a source build?'
throw new Error(
'Cannot connect to iOS application. idb_certificate_pull_failed' +
'Idb lacks permissions to exchange certificates. Did you install a source build ([FB] or enable certificate exchange)? ' +
e,
);
notification.error({
message: 'Cannot connect to iOS application',
duration: null,
description: message,
key: 'idb_certificate_pull_failed',
});
return;
}
throw e;
}

View File

@@ -8,13 +8,12 @@
*/
import {ChildProcess} from 'child_process';
import type {DeviceType} from 'flipper-common';
import type {IOSDeviceParams} from 'flipper-common';
import path from 'path';
import childProcess from 'child_process';
import {exec, execFile} from 'promisify-child-process';
import iosUtil from './iOSContainerUtility';
import IOSDevice from './IOSDevice';
import {getStaticPath} from '../../../utils/pathUtils';
import {
ERR_NO_IDB_OR_XCODE_AVAILABLE,
IOSBridge,
@@ -22,6 +21,7 @@ import {
} from './IOSBridge';
import {FlipperServerImpl} from '../../FlipperServerImpl';
import {notNull} from '../../utils/typeUtils';
import {getFlipperServerConfig} from '../../FlipperServerConfig';
type iOSSimulatorDevice = {
state: 'Booted' | 'Shutdown' | 'Shutting Down';
@@ -31,14 +31,6 @@ type iOSSimulatorDevice = {
udid: string;
};
export type IOSDeviceParams = {
udid: string;
type: DeviceType;
name: string;
deviceTypeIdentifier?: string;
state?: string;
};
function isAvailable(simulator: iOSSimulatorDevice): boolean {
// For some users "availability" is set, for others it's "isAvailable"
// It's not clear which key is set, so we are checking both.
@@ -53,13 +45,12 @@ function isAvailable(simulator: iOSSimulatorDevice): boolean {
export class IOSDeviceManager {
private portForwarders: Array<ChildProcess> = [];
private portforwardingClient = getStaticPath(
path.join(
'PortForwardingMacApp.app',
'Contents',
'MacOS',
'PortForwardingMacApp',
),
private portforwardingClient = path.join(
getFlipperServerConfig().staticPath,
'PortForwardingMacApp.app',
'Contents',
'MacOS',
'PortForwardingMacApp',
);
iosBridge: IOSBridge | undefined;
private xcodeVersionMismatchFound = false;
@@ -114,7 +105,7 @@ export class IOSDeviceManager {
isXcodeDetected: boolean,
isIdbAvailable: boolean,
): Array<Promise<any>> {
const {config} = this.flipperServer;
const config = getFlipperServerConfig();
return [
isIdbAvailable
? getActiveDevices(config.idbPath, config.enablePhysicalIOS).then(
@@ -133,7 +124,7 @@ export class IOSDeviceManager {
}
private async queryDevices(): Promise<any> {
const {config} = this.flipperServer;
const config = getFlipperServerConfig();
const isXcodeInstalled = await iosUtil.isXcodeDetected();
const isIdbAvailable = await iosUtil.isAvailable(config.idbPath);
return Promise.all(
@@ -177,19 +168,19 @@ export class IOSDeviceManager {
public async watchIOSDevices() {
// TODO: pull this condition up
if (!this.flipperServer.config.enableIOS) {
if (!getFlipperServerConfig().enableIOS) {
return;
}
try {
const isDetected = await iosUtil.isXcodeDetected();
this.xcodeCommandLineToolsDetected = isDetected;
if (this.flipperServer.config.enablePhysicalIOS) {
if (getFlipperServerConfig().enablePhysicalIOS) {
this.startDevicePortForwarders();
}
try {
// Awaiting the promise here to trigger immediate error handling.
this.iosBridge = await makeIOSBridge(
this.flipperServer.config.idbPath,
getFlipperServerConfig().idbPath,
isDetected,
);
this.queryDevicesForever();

View File

@@ -7,101 +7,11 @@
* @format
*/
import {DeviceLogLevel} from 'flipper-common';
import {DeviceLogLevel, MetroReportableEvent} from 'flipper-common';
import util from 'util';
import {FlipperServerImpl} from '../../FlipperServerImpl';
import {ServerDevice} from '../ServerDevice';
// From xplat/js/metro/packages/metro/src/lib/reporting.js
export type BundleDetails = {
entryFile: string;
platform?: string;
dev: boolean;
minify: boolean;
bundleType: string;
};
// From xplat/js/metro/packages/metro/src/lib/reporting.js
export type GlobalCacheDisabledReason = 'too_many_errors' | 'too_many_misses';
/**
* A tagged union of all the actions that may happen and we may want to
* report to the tool user.
*
* Based on xplat/js/metro/packages/metro/src/lib/TerminalReporter.js
*/
export type MetroReportableEvent =
| {
port: number;
projectRoots: ReadonlyArray<string>;
type: 'initialize_started';
}
| {type: 'initialize_done'}
| {
type: 'initialize_failed';
port: number;
error: Error;
}
| {
buildID: string;
type: 'bundle_build_done';
}
| {
buildID: string;
type: 'bundle_build_failed';
}
| {
buildID: string;
bundleDetails: BundleDetails;
type: 'bundle_build_started';
}
| {
error: Error;
type: 'bundling_error';
}
| {type: 'dep_graph_loading'}
| {type: 'dep_graph_loaded'}
| {
buildID: string;
type: 'bundle_transform_progressed';
transformedFileCount: number;
totalFileCount: number;
}
| {
type: 'global_cache_error';
error: Error;
}
| {
type: 'global_cache_disabled';
reason: GlobalCacheDisabledReason;
}
| {type: 'transform_cache_reset'}
| {
type: 'worker_stdout_chunk';
chunk: string;
}
| {
type: 'worker_stderr_chunk';
chunk: string;
}
| {
type: 'hmr_client_error';
error: Error;
}
| {
type: 'client_log';
level:
| 'trace'
| 'info'
| 'warn'
| 'log'
| 'group'
| 'groupCollapsed'
| 'groupEnd'
| 'debug';
data: Array<any>;
};
const metroLogLevelMapping: {[key: string]: DeviceLogLevel} = {
trace: 'verbose',
info: 'info',

View File

@@ -7,6 +7,10 @@
* @format
*/
export function helloWorld() {
return true;
}
export {
FlipperServerConfig,
getFlipperServerConfig,
setFlipperServerConfig,
} from './FlipperServerConfig';
export {FlipperServerImpl} from './FlipperServerImpl';

View File

@@ -3865,7 +3865,7 @@ archiver-utils@^2.1.0:
normalize-path "^3.0.0"
readable-stream "^2.0.0"
archiver@^5.0.2:
archiver@^5.0.2, archiver@^5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.0.tgz#dd3e097624481741df626267564f7dd8640a45ba"
integrity sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==
@@ -5829,9 +5829,9 @@ electron-publish@22.11.1:
mime "^2.5.0"
electron-to-chromium@^1.3.857:
version "1.3.862"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.862.tgz#c1c5d4382449e2c9b0e67fe1652f4fc451d6d8c0"
integrity sha512-o+FMbCD+hAUJ9S8bfz/FaqA0gE8OpCCm58KhhGogOEqiA1BLFSoVYLi+tW+S/ZavnqBn++n0XZm7HQiBVPs8Jg==
version "1.3.864"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.864.tgz#6a993bcc196a2b8b3df84d28d5d4dd912393885f"
integrity sha512-v4rbad8GO6/yVI92WOeU9Wgxc4NA0n4f6P1FvZTY+jyY7JHEhw3bduYu60v3Q1h81Cg6eo4ApZrFPuycwd5hGw==
electron@11.2.3:
version "11.2.3"
@@ -10692,7 +10692,7 @@ promise@^7.1.1:
dependencies:
asap "~2.0.3"
promisify-child-process@^4.1.0:
promisify-child-process@^4.1.0, promisify-child-process@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/promisify-child-process/-/promisify-child-process-4.1.1.tgz#290659e079f9c7bd46708404d4488a1a6b802686"
integrity sha512-/sRjHZwoXf1rJ+8s4oWjYjGRVKNK1DUnqfRC1Zek18pl0cN6k3yJ1cCbqd0tWNe4h0Gr+SY4vR42N33+T82WkA==
@@ -13661,7 +13661,7 @@ ws@1.1.5, ws@^1.1.5:
options ">=0.0.5"
ultron "1.0.x"
ws@^7.4.5, ws@^7.4.6, ws@^8.2.1, ws@~7.4.2:
ws@^7.4.5, ws@^7.4.6, ws@^8.2.1, ws@^8.2.3, ws@~7.4.2:
version "7.5.5"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881"
integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==