Separate Client in server and client part
Summary: This diff separates the concept of a Client as now on the UI, from the concept of a Client as known on the server, and makes all interactions with client and vice versa async. Reviewed By: timur-valiev Differential Revision: D31235682 fbshipit-source-id: 99089e9b390b4c5359f97f6f2b15bf4b182b6cb9
This commit is contained in:
committed by
Facebook GitHub Bot
parent
eab4804792
commit
740093d0d9
@@ -33,6 +33,8 @@ import {
|
|||||||
timeout,
|
timeout,
|
||||||
ClientQuery,
|
ClientQuery,
|
||||||
_SandyPluginDefinition,
|
_SandyPluginDefinition,
|
||||||
|
ClientResponseType,
|
||||||
|
ClientErrorType,
|
||||||
} from 'flipper-plugin';
|
} from 'flipper-plugin';
|
||||||
import {freeze} from 'immer';
|
import {freeze} from 'immer';
|
||||||
import {message} from 'antd';
|
import {message} from 'antd';
|
||||||
@@ -40,11 +42,6 @@ import {
|
|||||||
isFlipperMessageDebuggingEnabled,
|
isFlipperMessageDebuggingEnabled,
|
||||||
registerFlipperDebugMessage,
|
registerFlipperDebugMessage,
|
||||||
} from './chrome/FlipperMessages';
|
} from './chrome/FlipperMessages';
|
||||||
import {
|
|
||||||
ConnectionStatus,
|
|
||||||
ErrorType,
|
|
||||||
ClientConnection,
|
|
||||||
} from './server/comms/ClientConnection';
|
|
||||||
|
|
||||||
type Plugins = Set<string>;
|
type Plugins = Set<string>;
|
||||||
type PluginsArr = Array<string>;
|
type PluginsArr = Array<string>;
|
||||||
@@ -65,7 +62,11 @@ export type RequestMetadata = {
|
|||||||
params: Params | undefined;
|
params: Params | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleError = (store: Store, device: BaseDevice, error: ErrorType) => {
|
const handleError = (
|
||||||
|
store: Store,
|
||||||
|
device: BaseDevice,
|
||||||
|
error: ClientErrorType,
|
||||||
|
) => {
|
||||||
if (store.getState().settingsState.suppressPluginErrors) {
|
if (store.getState().settingsState.suppressPluginErrors) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -91,13 +92,18 @@ const handleError = (store: Store, device: BaseDevice, error: ErrorType) => {
|
|||||||
crashReporterPlugin.instanceApi.reportCrash(payload);
|
crashReporterPlugin.instanceApi.reportCrash(payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface ClientConnection {
|
||||||
|
send(data: any): void;
|
||||||
|
sendExpectResponse(data: any): Promise<ClientResponseType>;
|
||||||
|
}
|
||||||
|
|
||||||
export default class Client extends EventEmitter {
|
export default class Client extends EventEmitter {
|
||||||
connected = createState(false);
|
connected = createState(false);
|
||||||
id: string;
|
id: string;
|
||||||
query: ClientQuery;
|
query: ClientQuery;
|
||||||
sdkVersion: number;
|
sdkVersion: number;
|
||||||
messageIdCounter: number;
|
messageIdCounter: number;
|
||||||
plugins: Plugins;
|
plugins: Plugins; // TODO: turn into atom, and remove eventEmitter
|
||||||
backgroundPlugins: Plugins;
|
backgroundPlugins: Plugins;
|
||||||
connection: ClientConnection | null | undefined;
|
connection: ClientConnection | null | undefined;
|
||||||
store: Store;
|
store: Store;
|
||||||
@@ -138,18 +144,6 @@ export default class Client extends EventEmitter {
|
|||||||
this.broadcastCallbacks = new Map();
|
this.broadcastCallbacks = new Map();
|
||||||
this.activePlugins = new Set();
|
this.activePlugins = new Set();
|
||||||
this.device = device;
|
this.device = device;
|
||||||
|
|
||||||
const client = this;
|
|
||||||
if (conn) {
|
|
||||||
conn.subscribeToEvents((status) => {
|
|
||||||
if (
|
|
||||||
status === ConnectionStatus.CLOSED ||
|
|
||||||
status === ConnectionStatus.ERROR
|
|
||||||
) {
|
|
||||||
client.connected.set(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
supportsPlugin(pluginId: string): boolean {
|
supportsPlugin(pluginId: string): boolean {
|
||||||
@@ -185,6 +179,7 @@ export default class Client extends EventEmitter {
|
|||||||
this.initPlugin(plugin);
|
this.initPlugin(plugin);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.emit('plugins-change');
|
||||||
}
|
}
|
||||||
|
|
||||||
initFromImport(initialStates: Record<string, Record<string, any>>): this {
|
initFromImport(initialStates: Record<string, Record<string, any>>): this {
|
||||||
@@ -194,6 +189,7 @@ export default class Client extends EventEmitter {
|
|||||||
this.loadPlugin(plugin, initialStates[pluginId]);
|
this.loadPlugin(plugin, initialStates[pluginId]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.emit('plugins-change');
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +262,6 @@ export default class Client extends EventEmitter {
|
|||||||
});
|
});
|
||||||
this.emit('close');
|
this.emit('close');
|
||||||
this.connected.set(false);
|
this.connected.set(false);
|
||||||
this.connection = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// clean up this client
|
// clean up this client
|
||||||
@@ -346,7 +341,7 @@ export default class Client extends EventEmitter {
|
|||||||
method?: string;
|
method?: string;
|
||||||
params?: Params;
|
params?: Params;
|
||||||
success?: Object;
|
success?: Object;
|
||||||
error?: ErrorType;
|
error?: ClientErrorType;
|
||||||
} = rawData;
|
} = rawData;
|
||||||
|
|
||||||
const {id, method} = data;
|
const {id, method} = data;
|
||||||
@@ -438,10 +433,10 @@ export default class Client extends EventEmitter {
|
|||||||
onResponse(
|
onResponse(
|
||||||
data: {
|
data: {
|
||||||
success?: Object;
|
success?: Object;
|
||||||
error?: ErrorType;
|
error?: ClientErrorType;
|
||||||
},
|
},
|
||||||
resolve: ((a: any) => any) | undefined,
|
resolve: ((a: any) => any) | undefined,
|
||||||
reject: (error: ErrorType) => any,
|
reject: (error: ClientErrorType) => any,
|
||||||
) {
|
) {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
resolve && resolve(data.success);
|
resolve && resolve(data.success);
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import {produce} from 'immer';
|
|||||||
import {reportUsage} from './utils/metrics';
|
import {reportUsage} from './utils/metrics';
|
||||||
import {PluginInfo} from './chrome/fb-stubs/PluginInfo';
|
import {PluginInfo} from './chrome/fb-stubs/PluginInfo';
|
||||||
import {getActiveClient, getActivePlugin} from './selectors/connections';
|
import {getActiveClient, getActivePlugin} from './selectors/connections';
|
||||||
|
import {isTest} from './utils/isProduction';
|
||||||
|
|
||||||
const {Text, Link} = Typography;
|
const {Text, Link} = Typography;
|
||||||
|
|
||||||
@@ -250,7 +251,7 @@ class PluginContainer extends PureComponent<Props, State> {
|
|||||||
render() {
|
render() {
|
||||||
const {activePlugin, pluginKey, target, pendingMessages} = this.props;
|
const {activePlugin, pluginKey, target, pendingMessages} = this.props;
|
||||||
if (!activePlugin || !target || !pluginKey) {
|
if (!activePlugin || !target || !pluginKey) {
|
||||||
return null;
|
return this.renderNoPluginActive();
|
||||||
}
|
}
|
||||||
if (activePlugin.status !== 'enabled') {
|
if (activePlugin.status !== 'enabled') {
|
||||||
return this.renderPluginInfo();
|
return this.renderPluginInfo();
|
||||||
@@ -294,6 +295,9 @@ class PluginContainer extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderNoPluginActive() {
|
renderNoPluginActive() {
|
||||||
|
if (isTest()) {
|
||||||
|
return <>No plugin selected</>; // to keep 'nothing' clearly recognisable in unit tests
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<View grow>
|
<View grow>
|
||||||
<Waiting>
|
<Waiting>
|
||||||
|
|||||||
@@ -260,7 +260,9 @@ test('PluginContainer can render Sandy plugins', async () => {
|
|||||||
|
|
||||||
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
||||||
<body>
|
<body>
|
||||||
<div />
|
<div>
|
||||||
|
No plugin selected
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
`);
|
`);
|
||||||
expect(pluginInstance.connectedStub).toBeCalledTimes(1);
|
expect(pluginInstance.connectedStub).toBeCalledTimes(1);
|
||||||
@@ -801,7 +803,9 @@ test('PluginContainer can render Sandy device plugins', async () => {
|
|||||||
|
|
||||||
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
||||||
<body>
|
<body>
|
||||||
<div />
|
<div>
|
||||||
|
No plugin selected
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
`);
|
`);
|
||||||
expect(pluginInstance.activatedStub).toBeCalledTimes(1);
|
expect(pluginInstance.activatedStub).toBeCalledTimes(1);
|
||||||
@@ -1232,7 +1236,9 @@ test('PluginContainer can render Sandy plugins for archived devices', async () =
|
|||||||
|
|
||||||
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
||||||
<body>
|
<body>
|
||||||
<div />
|
<div>
|
||||||
|
No plugin selected
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
`);
|
`);
|
||||||
expect(pluginInstance.connectedStub).toBeCalledTimes(0);
|
expect(pluginInstance.connectedStub).toBeCalledTimes(0);
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ test('new clients replace old ones', async () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const {client, store, device, createClient} =
|
const {client, store, device, createClient, logger} =
|
||||||
await createMockFlipperWithPlugin(plugin, {
|
await createMockFlipperWithPlugin(plugin, {
|
||||||
asBackgroundPlugin: true,
|
asBackgroundPlugin: true,
|
||||||
});
|
});
|
||||||
@@ -225,7 +225,7 @@ test('new clients replace old ones', async () => {
|
|||||||
expect(instance.instanceApi.disconnect).toBeCalledTimes(0);
|
expect(instance.instanceApi.disconnect).toBeCalledTimes(0);
|
||||||
|
|
||||||
const client2 = await createClient(device, 'AnotherApp', client.query, true);
|
const client2 = await createClient(device, 'AnotherApp', client.query, true);
|
||||||
handleClientConnected(store, client2);
|
handleClientConnected(null as any, store, logger, client2);
|
||||||
|
|
||||||
expect(client2.connected.get()).toBe(true);
|
expect(client2.connected.get()).toBe(true);
|
||||||
const instance2 = client2.sandyPluginStates.get(plugin.id)!;
|
const instance2 = client2.sandyPluginStates.get(plugin.id)!;
|
||||||
|
|||||||
@@ -193,9 +193,11 @@ test('triggering a deeplink without applicable device can wait for a device', as
|
|||||||
);
|
);
|
||||||
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
||||||
<body>
|
<body>
|
||||||
<div />
|
<div>
|
||||||
|
No plugin selected
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const handlePromise = handleDeeplink(
|
const handlePromise = handleDeeplink(
|
||||||
store,
|
store,
|
||||||
@@ -207,9 +209,11 @@ test('triggering a deeplink without applicable device can wait for a device', as
|
|||||||
|
|
||||||
// No device yet available (dialogs are not renderable atm)
|
// No device yet available (dialogs are not renderable atm)
|
||||||
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
||||||
<body>
|
<body>
|
||||||
<div />
|
<div>
|
||||||
</body>
|
No plugin selected
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// create a new device
|
// create a new device
|
||||||
@@ -273,9 +277,11 @@ test('triggering a deeplink without applicable client can wait for a device', as
|
|||||||
);
|
);
|
||||||
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
||||||
<body>
|
<body>
|
||||||
<div />
|
<div>
|
||||||
|
No plugin selected
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const handlePromise = handleDeeplink(
|
const handlePromise = handleDeeplink(
|
||||||
store,
|
store,
|
||||||
@@ -288,7 +294,9 @@ test('triggering a deeplink without applicable client can wait for a device', as
|
|||||||
// No device yet available (dialogs are not renderable atm)
|
// No device yet available (dialogs are not renderable atm)
|
||||||
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
||||||
<body>
|
<body>
|
||||||
<div />
|
<div>
|
||||||
|
No plugin selected
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
|||||||
@@ -8,13 +8,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Store} from '../reducers/index';
|
import {State, Store} from '../reducers/index';
|
||||||
import {Logger} from '../fb-interfaces/Logger';
|
import {Logger} from '../fb-interfaces/Logger';
|
||||||
import {FlipperServerImpl} from '../server/FlipperServerImpl';
|
import {FlipperServerImpl} from '../server/FlipperServerImpl';
|
||||||
import {selectClient, selectDevice} from '../reducers/connections';
|
import {selectClient, selectDevice} from '../reducers/connections';
|
||||||
import Client from '../Client';
|
import Client from '../Client';
|
||||||
import {notification} from 'antd';
|
import {notification} from 'antd';
|
||||||
import BaseDevice from '../devices/BaseDevice';
|
import BaseDevice from '../devices/BaseDevice';
|
||||||
|
import {ClientDescription, timeout} from 'flipper-plugin';
|
||||||
|
import {reportPlatformFailures} from '../utils/metrics';
|
||||||
|
import {sideEffect} from '../utils/sideEffect';
|
||||||
|
|
||||||
export default async (store: Store, logger: Logger) => {
|
export default async (store: Store, logger: Logger) => {
|
||||||
const {enableAndroid, androidHome, idbPath, enableIOS, enablePhysicalIOS} =
|
const {enableAndroid, androidHome, idbPath, enableIOS, enablePhysicalIOS} =
|
||||||
@@ -111,11 +114,20 @@ export default async (store: Store, logger: Logger) => {
|
|||||||
// N.B.: note that we don't remove the device, we keep it in offline
|
// N.B.: note that we don't remove the device, we keep it in offline
|
||||||
});
|
});
|
||||||
|
|
||||||
server.on('client-connected', (payload) =>
|
server.on('client-connected', (payload: ClientDescription) =>
|
||||||
// TODO: fixed later in this stack
|
handleClientConnected(server, store, logger, payload),
|
||||||
handleClientConnected(store, payload as any),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
server.on('client-disconnected', ({id}) => {
|
||||||
|
const existingClient = store.getState().connections.clients.get(id);
|
||||||
|
existingClient?.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on('client-message', ({id, message}) => {
|
||||||
|
const existingClient = store.getState().connections.clients.get(id);
|
||||||
|
existingClient?.onMessage(message);
|
||||||
|
});
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
window.addEventListener('beforeunload', () => {
|
window.addEventListener('beforeunload', () => {
|
||||||
server.close();
|
server.close();
|
||||||
@@ -142,25 +154,51 @@ export default async (store: Store, logger: Logger) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function handleClientConnected(store: Store, client: Client) {
|
export async function handleClientConnected(
|
||||||
|
server: FlipperServerImpl,
|
||||||
|
store: Store,
|
||||||
|
logger: Logger,
|
||||||
|
{id, query}: ClientDescription,
|
||||||
|
) {
|
||||||
const {connections} = store.getState();
|
const {connections} = store.getState();
|
||||||
const existingClient = connections.clients.get(client.id);
|
const existingClient = connections.clients.get(id);
|
||||||
|
|
||||||
if (existingClient) {
|
if (existingClient) {
|
||||||
existingClient.destroy();
|
existingClient.destroy();
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: 'CLEAR_CLIENT_PLUGINS_STATE',
|
type: 'CLEAR_CLIENT_PLUGINS_STATE',
|
||||||
payload: {
|
payload: {
|
||||||
clientId: client.id,
|
clientId: id,
|
||||||
devicePlugins: new Set(),
|
devicePlugins: new Set(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: 'CLIENT_REMOVED',
|
type: 'CLIENT_REMOVED',
|
||||||
payload: client.id,
|
payload: id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const device =
|
||||||
|
getDeviceBySerial(store.getState(), query.device_id) ??
|
||||||
|
(await findDeviceForConnection(store, query.app, query.device_id));
|
||||||
|
|
||||||
|
const client = new Client(
|
||||||
|
id,
|
||||||
|
query,
|
||||||
|
{
|
||||||
|
send(data: any) {
|
||||||
|
server.exec('client-request', id, data);
|
||||||
|
},
|
||||||
|
async sendExpectResponse(data: any) {
|
||||||
|
return await server.exec('client-request-response', id, data);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
logger,
|
||||||
|
store,
|
||||||
|
undefined,
|
||||||
|
device,
|
||||||
|
);
|
||||||
|
|
||||||
console.debug(
|
console.debug(
|
||||||
`Device client initialized: ${client.id}. Supported plugins: ${Array.from(
|
`Device client initialized: ${client.id}. Supported plugins: ${Array.from(
|
||||||
client.plugins,
|
client.plugins,
|
||||||
@@ -174,5 +212,64 @@ export async function handleClientConnected(store: Store, client: Client) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
store.dispatch(selectClient(client.id));
|
store.dispatch(selectClient(client.id));
|
||||||
client.emit('plugins-change');
|
|
||||||
|
await timeout(
|
||||||
|
30 * 1000,
|
||||||
|
client.init(),
|
||||||
|
`[conn] Failed to initialize client ${query.app} on ${query.device_id} in a timely manner`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDeviceBySerial(
|
||||||
|
state: State,
|
||||||
|
serial: string,
|
||||||
|
): BaseDevice | undefined {
|
||||||
|
return state.connections.devices.find((device) => device.serial === serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function findDeviceForConnection(
|
||||||
|
store: Store,
|
||||||
|
clientId: string,
|
||||||
|
serial: string,
|
||||||
|
): Promise<BaseDevice> {
|
||||||
|
let lastSeenDeviceList: BaseDevice[] = [];
|
||||||
|
/* All clients should have a corresponding Device in the store.
|
||||||
|
However, clients can connect before a device is registered, so wait a
|
||||||
|
while for the device to be registered if it isn't already. */
|
||||||
|
return reportPlatformFailures(
|
||||||
|
new Promise<BaseDevice>((resolve, reject) => {
|
||||||
|
let unsubscribe: () => void = () => {};
|
||||||
|
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
unsubscribe();
|
||||||
|
const error = `Timed out waiting for device ${serial} for client ${clientId}`;
|
||||||
|
console.error(
|
||||||
|
'[conn] Unable to find device for connection. Error:',
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
reject(error);
|
||||||
|
}, 15000);
|
||||||
|
unsubscribe = sideEffect(
|
||||||
|
store,
|
||||||
|
{name: 'waitForDevice', throttleMs: 100},
|
||||||
|
(state) => state.connections.devices,
|
||||||
|
(newDeviceList) => {
|
||||||
|
if (newDeviceList === lastSeenDeviceList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastSeenDeviceList = newDeviceList;
|
||||||
|
const matchingDevice = newDeviceList.find(
|
||||||
|
(device) => device.serial === serial,
|
||||||
|
);
|
||||||
|
if (matchingDevice) {
|
||||||
|
console.log(`[conn] Found device for: ${clientId} on ${serial}.`);
|
||||||
|
clearTimeout(timeout);
|
||||||
|
resolve(matchingDevice);
|
||||||
|
unsubscribe();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
'client-setMatchingDevice',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export default (store: Store, _logger: Logger) => {
|
|||||||
|
|
||||||
sideEffect(
|
sideEffect(
|
||||||
store,
|
store,
|
||||||
{name: 'pluginsChangeListener', throttleMs: 100, fireImmediately: true},
|
{name: 'pluginsChangeListener', throttleMs: 10, fireImmediately: true},
|
||||||
getActiveClient,
|
getActiveClient,
|
||||||
(activeClient, _store) => {
|
(activeClient, _store) => {
|
||||||
if (activeClient !== prevClient) {
|
if (activeClient !== prevClient) {
|
||||||
@@ -33,6 +33,7 @@ export default (store: Store, _logger: Logger) => {
|
|||||||
prevClient = activeClient;
|
prevClient = activeClient;
|
||||||
if (prevClient) {
|
if (prevClient) {
|
||||||
prevClient.on('plugins-change', onActiveAppPluginListChanged);
|
prevClient.on('plugins-change', onActiveAppPluginListChanged);
|
||||||
|
store.dispatch(appPluginListChanged()); // force refresh
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
import Client from '../Client';
|
|
||||||
import {Store} from '../reducers/index';
|
import {Store} from '../reducers/index';
|
||||||
import {Logger} from '../fb-interfaces/Logger';
|
import {Logger} from '../fb-interfaces/Logger';
|
||||||
import ServerController from './comms/ServerController';
|
import ServerController from './comms/ServerController';
|
||||||
@@ -91,10 +90,6 @@ export class FlipperServerImpl implements FlipperServer {
|
|||||||
this.android = new AndroidDeviceManager(this);
|
this.android = new AndroidDeviceManager(this);
|
||||||
this.ios = new IOSDeviceManager(this);
|
this.ios = new IOSDeviceManager(this);
|
||||||
|
|
||||||
server.addListener('new-client', (client: Client) => {
|
|
||||||
this.emit('client-connected', client);
|
|
||||||
});
|
|
||||||
|
|
||||||
server.addListener('error', (err) => {
|
server.addListener('error', (err) => {
|
||||||
this.emit('server-error', err);
|
this.emit('server-error', err);
|
||||||
});
|
});
|
||||||
@@ -263,6 +258,25 @@ export class FlipperServerImpl implements FlipperServer {
|
|||||||
}
|
}
|
||||||
device.sendCommand(command);
|
device.sendCommand(command);
|
||||||
},
|
},
|
||||||
|
'client-request': async (clientId, payload) => {
|
||||||
|
this.server.connections.get(clientId)?.connection?.send(payload);
|
||||||
|
},
|
||||||
|
'client-request-response': async (clientId, payload) => {
|
||||||
|
const client = this.server.connections.get(clientId);
|
||||||
|
if (client && client.connection) {
|
||||||
|
return await client.connection.sendExpectResponse(payload);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
length: 0,
|
||||||
|
error: {
|
||||||
|
message: `Client '${clientId} is no longer connected, failed to deliver: ${JSON.stringify(
|
||||||
|
payload,
|
||||||
|
)}`,
|
||||||
|
name: 'CLIENT_DISCONNECTED',
|
||||||
|
stacktrace: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
registerDevice(device: ServerDevice) {
|
registerDevice(device: ServerDevice) {
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {ClientResponseType} from 'flipper-plugin';
|
||||||
import WebSocket from 'ws';
|
import WebSocket from 'ws';
|
||||||
import {
|
import {
|
||||||
ConnectionStatusChange,
|
ConnectionStatusChange,
|
||||||
ConnectionStatus,
|
ConnectionStatus,
|
||||||
ClientConnection,
|
ClientConnection,
|
||||||
ResponseType,
|
|
||||||
} from './ClientConnection';
|
} from './ClientConnection';
|
||||||
|
|
||||||
export class BrowserClientFlipperConnection implements ClientConnection {
|
export class BrowserClientFlipperConnection implements ClientConnection {
|
||||||
@@ -40,7 +40,7 @@ export class BrowserClientFlipperConnection implements ClientConnection {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
sendExpectResponse(data: any): Promise<ResponseType> {
|
sendExpectResponse(data: any): Promise<ClientResponseType> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const {id: callId = undefined, method = undefined} =
|
const {id: callId = undefined, method = undefined} =
|
||||||
data != null ? data : {};
|
data != null ? data : {};
|
||||||
|
|||||||
@@ -7,17 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type ErrorType = {
|
import {ClientResponseType} from 'flipper-plugin';
|
||||||
message: string;
|
|
||||||
stacktrace: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ResponseType = {
|
|
||||||
success?: Object;
|
|
||||||
error?: ErrorType;
|
|
||||||
length: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum ConnectionStatus {
|
export enum ConnectionStatus {
|
||||||
ERROR = 'error',
|
ERROR = 'error',
|
||||||
@@ -33,5 +23,5 @@ export interface ClientConnection {
|
|||||||
subscribeToEvents(subscriber: ConnectionStatusChange): void;
|
subscribeToEvents(subscriber: ConnectionStatusChange): void;
|
||||||
close(): void;
|
close(): void;
|
||||||
send(data: any): void;
|
send(data: any): void;
|
||||||
sendExpectResponse(data: any): Promise<ResponseType>;
|
sendExpectResponse(data: any): Promise<ClientResponseType>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,9 @@ import {
|
|||||||
CertificateExchangeMedium,
|
CertificateExchangeMedium,
|
||||||
SecureServerConfig,
|
SecureServerConfig,
|
||||||
} from '../utils/CertificateProvider';
|
} from '../utils/CertificateProvider';
|
||||||
import Client from '../../Client';
|
|
||||||
import {ClientConnection} from './ClientConnection';
|
import {ClientConnection} from './ClientConnection';
|
||||||
import {transformCertificateExchangeMediumToType} from './Utilities';
|
import {transformCertificateExchangeMediumToType} from './Utilities';
|
||||||
import {ClientQuery} from 'flipper-plugin';
|
import {ClientDescription, ClientQuery} from 'flipper-plugin';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ClientCsrQuery defines a client query with CSR
|
* ClientCsrQuery defines a client query with CSR
|
||||||
@@ -87,7 +86,7 @@ export interface ServerEventsListener {
|
|||||||
onConnectionCreated(
|
onConnectionCreated(
|
||||||
clientQuery: SecureClientQuery,
|
clientQuery: SecureClientQuery,
|
||||||
clientConnection: ClientConnection,
|
clientConnection: ClientConnection,
|
||||||
): Promise<Client>;
|
): Promise<ClientDescription>;
|
||||||
/**
|
/**
|
||||||
* A connection with a client has been closed.
|
* A connection with a client has been closed.
|
||||||
* @param id The client identifier.
|
* @param id The client identifier.
|
||||||
@@ -98,6 +97,11 @@ export interface ServerEventsListener {
|
|||||||
* @param error An Error instance.
|
* @param error An Error instance.
|
||||||
*/
|
*/
|
||||||
onError(error: Error): void;
|
onError(error: Error): void;
|
||||||
|
/**
|
||||||
|
* A message was received for a specif client
|
||||||
|
* // TODO: payload should become JSON
|
||||||
|
*/
|
||||||
|
onClientMessage(clientId: string, payload: string): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,10 +9,9 @@
|
|||||||
|
|
||||||
import {CertificateExchangeMedium} from '../utils/CertificateProvider';
|
import {CertificateExchangeMedium} from '../utils/CertificateProvider';
|
||||||
import {Logger} from '../../fb-interfaces/Logger';
|
import {Logger} from '../../fb-interfaces/Logger';
|
||||||
import {ClientQuery} from 'flipper-plugin';
|
import {ClientDescription, ClientQuery} from 'flipper-plugin';
|
||||||
import {Store, State} from '../../reducers/index';
|
import {Store} from '../../reducers/index';
|
||||||
import CertificateProvider from '../utils/CertificateProvider';
|
import CertificateProvider from '../utils/CertificateProvider';
|
||||||
import Client from '../../Client';
|
|
||||||
import {ClientConnection, ConnectionStatus} from './ClientConnection';
|
import {ClientConnection, ConnectionStatus} from './ClientConnection';
|
||||||
import {UninitializedClient} from '../UninitializedClient';
|
import {UninitializedClient} from '../UninitializedClient';
|
||||||
import {reportPlatformFailures} from '../../utils/metrics';
|
import {reportPlatformFailures} from '../../utils/metrics';
|
||||||
@@ -21,8 +20,6 @@ import invariant from 'invariant';
|
|||||||
import GK from '../../fb-stubs/GK';
|
import GK from '../../fb-stubs/GK';
|
||||||
import {buildClientId} from '../../utils/clientUtils';
|
import {buildClientId} from '../../utils/clientUtils';
|
||||||
import DummyDevice from '../../server/devices/DummyDevice';
|
import DummyDevice from '../../server/devices/DummyDevice';
|
||||||
import BaseDevice from '../../devices/BaseDevice';
|
|
||||||
import {sideEffect} from '../../utils/sideEffect';
|
|
||||||
import {
|
import {
|
||||||
appNameWithUpdateHint,
|
appNameWithUpdateHint,
|
||||||
transformCertificateExchangeMediumToType,
|
transformCertificateExchangeMediumToType,
|
||||||
@@ -38,11 +35,10 @@ import {
|
|||||||
} from './ServerFactory';
|
} from './ServerFactory';
|
||||||
import {FlipperServerImpl} from '../FlipperServerImpl';
|
import {FlipperServerImpl} from '../FlipperServerImpl';
|
||||||
import {isTest} from '../../utils/isProduction';
|
import {isTest} from '../../utils/isProduction';
|
||||||
import {timeout} from 'flipper-plugin';
|
|
||||||
|
|
||||||
type ClientInfo = {
|
type ClientInfo = {
|
||||||
connection: ClientConnection | null | undefined;
|
connection: ClientConnection | null | undefined;
|
||||||
client: Client;
|
client: ClientDescription;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ClientCsrQuery = {
|
type ClientCsrQuery = {
|
||||||
@@ -51,9 +47,7 @@ type ClientCsrQuery = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
declare interface ServerController {
|
declare interface ServerController {
|
||||||
on(event: 'new-client', callback: (client: Client) => void): this;
|
|
||||||
on(event: 'error', callback: (err: Error) => void): this;
|
on(event: 'error', callback: (err: Error) => void): this;
|
||||||
on(event: 'clients-change', callback: () => void): this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -99,6 +93,13 @@ class ServerController extends EventEmitter implements ServerEventsListener {
|
|||||||
this.initialized = null;
|
this.initialized = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onClientMessage(clientId: string, payload: string): void {
|
||||||
|
this.flipperServer.emit('client-message', {
|
||||||
|
id: clientId,
|
||||||
|
message: payload,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
get logger(): Logger {
|
get logger(): Logger {
|
||||||
return this.flipperServer.logger;
|
return this.flipperServer.logger;
|
||||||
}
|
}
|
||||||
@@ -188,7 +189,7 @@ class ServerController extends EventEmitter implements ServerEventsListener {
|
|||||||
onConnectionCreated(
|
onConnectionCreated(
|
||||||
clientQuery: SecureClientQuery,
|
clientQuery: SecureClientQuery,
|
||||||
clientConnection: ClientConnection,
|
clientConnection: ClientConnection,
|
||||||
): Promise<Client> {
|
): Promise<ClientDescription> {
|
||||||
const {app, os, device, device_id, sdk_version, csr, csr_path, medium} =
|
const {app, os, device, device_id, sdk_version, csr, csr_path, medium} =
|
||||||
clientQuery;
|
clientQuery;
|
||||||
const transformedMedium = transformCertificateExchangeMediumToType(medium);
|
const transformedMedium = transformCertificateExchangeMediumToType(medium);
|
||||||
@@ -334,7 +335,7 @@ class ServerController extends EventEmitter implements ServerEventsListener {
|
|||||||
connection: ClientConnection,
|
connection: ClientConnection,
|
||||||
query: ClientQuery & {medium: CertificateExchangeMedium},
|
query: ClientQuery & {medium: CertificateExchangeMedium},
|
||||||
csrQuery: ClientCsrQuery,
|
csrQuery: ClientCsrQuery,
|
||||||
): Promise<Client> {
|
): Promise<ClientDescription> {
|
||||||
invariant(query, 'expected query');
|
invariant(query, 'expected query');
|
||||||
|
|
||||||
// try to get id by comparing giving `csr` to file from `csr_path`
|
// try to get id by comparing giving `csr` to file from `csr_path`
|
||||||
@@ -371,20 +372,11 @@ class ServerController extends EventEmitter implements ServerEventsListener {
|
|||||||
console.log(
|
console.log(
|
||||||
`[conn] Matching device for ${query.app} on ${query.device_id}...`,
|
`[conn] Matching device for ${query.app} on ${query.device_id}...`,
|
||||||
);
|
);
|
||||||
// TODO: grab device from flipperServer.devices instead of store
|
|
||||||
const device =
|
|
||||||
getDeviceBySerial(this.store.getState(), query.device_id) ??
|
|
||||||
(await findDeviceForConnection(this.store, query.app, query.device_id));
|
|
||||||
|
|
||||||
const client = new Client(
|
const client: ClientDescription = {
|
||||||
id,
|
id,
|
||||||
query,
|
query,
|
||||||
connection,
|
};
|
||||||
this.logger,
|
|
||||||
this.store,
|
|
||||||
undefined,
|
|
||||||
device,
|
|
||||||
);
|
|
||||||
|
|
||||||
const info = {
|
const info = {
|
||||||
client,
|
client,
|
||||||
@@ -395,12 +387,6 @@ class ServerController extends EventEmitter implements ServerEventsListener {
|
|||||||
`[conn] Initializing client ${query.app} on ${query.device_id}...`,
|
`[conn] Initializing client ${query.app} on ${query.device_id}...`,
|
||||||
);
|
);
|
||||||
|
|
||||||
await timeout(
|
|
||||||
30 * 1000,
|
|
||||||
client.init(),
|
|
||||||
`[conn] Failed to initialize client ${query.app} on ${query.device_id} in a timely manner`,
|
|
||||||
);
|
|
||||||
|
|
||||||
connection.subscribeToEvents((status: ConnectionStatus) => {
|
connection.subscribeToEvents((status: ConnectionStatus) => {
|
||||||
if (
|
if (
|
||||||
status === ConnectionStatus.CLOSED ||
|
status === ConnectionStatus.CLOSED ||
|
||||||
@@ -410,12 +396,7 @@ class ServerController extends EventEmitter implements ServerEventsListener {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.debug(
|
console.debug(`[conn] Device client initialized: ${id}.`, 'server');
|
||||||
`[conn] Device client initialized: ${id}. Supported plugins: ${Array.from(
|
|
||||||
client.plugins,
|
|
||||||
).join(', ')}`,
|
|
||||||
'server',
|
|
||||||
);
|
|
||||||
|
|
||||||
/* If a device gets disconnected without being cleaned up properly,
|
/* If a device gets disconnected without being cleaned up properly,
|
||||||
* Flipper won't be aware until it attempts to reconnect.
|
* Flipper won't be aware until it attempts to reconnect.
|
||||||
@@ -435,14 +416,12 @@ class ServerController extends EventEmitter implements ServerEventsListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.connections.set(id, info);
|
this.connections.set(id, info);
|
||||||
this.emit('new-client', client);
|
this.flipperServer.emit('client-connected', client);
|
||||||
this.emit('clients-change');
|
|
||||||
client.emit('plugins-change');
|
|
||||||
|
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
attachFakeClient(client: Client) {
|
attachFakeClient(client: ClientDescription) {
|
||||||
this.connections.set(client.id, {
|
this.connections.set(client.id, {
|
||||||
client,
|
client,
|
||||||
connection: null,
|
connection: null,
|
||||||
@@ -460,10 +439,8 @@ class ServerController extends EventEmitter implements ServerEventsListener {
|
|||||||
console.log(
|
console.log(
|
||||||
`[conn] Disconnected: ${info.client.query.app} on ${info.client.query.device_id}.`,
|
`[conn] Disconnected: ${info.client.query.app} on ${info.client.query.device_id}.`,
|
||||||
);
|
);
|
||||||
info.client.disconnect();
|
this.flipperServer.emit('client-disconnected', {id});
|
||||||
this.connections.delete(id);
|
this.connections.delete(id);
|
||||||
this.emit('clients-change');
|
|
||||||
this.emit('removed-client', id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -499,60 +476,6 @@ class ConnectionTracker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDeviceBySerial(
|
|
||||||
state: State,
|
|
||||||
serial: string,
|
|
||||||
): BaseDevice | undefined {
|
|
||||||
return state.connections.devices.find((device) => device.serial === serial);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function findDeviceForConnection(
|
|
||||||
store: Store,
|
|
||||||
clientId: string,
|
|
||||||
serial: string,
|
|
||||||
): Promise<BaseDevice> {
|
|
||||||
let lastSeenDeviceList: BaseDevice[] = [];
|
|
||||||
/* All clients should have a corresponding Device in the store.
|
|
||||||
However, clients can connect before a device is registered, so wait a
|
|
||||||
while for the device to be registered if it isn't already. */
|
|
||||||
return reportPlatformFailures(
|
|
||||||
new Promise<BaseDevice>((resolve, reject) => {
|
|
||||||
let unsubscribe: () => void = () => {};
|
|
||||||
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
unsubscribe();
|
|
||||||
const error = `Timed out waiting for device ${serial} for client ${clientId}`;
|
|
||||||
console.error(
|
|
||||||
'[conn] Unable to find device for connection. Error:',
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
reject(error);
|
|
||||||
}, 15000);
|
|
||||||
unsubscribe = sideEffect(
|
|
||||||
store,
|
|
||||||
{name: 'waitForDevice', throttleMs: 100},
|
|
||||||
(state) => state.connections.devices,
|
|
||||||
(newDeviceList) => {
|
|
||||||
if (newDeviceList === lastSeenDeviceList) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lastSeenDeviceList = newDeviceList;
|
|
||||||
const matchingDevice = newDeviceList.find(
|
|
||||||
(device) => device.serial === serial,
|
|
||||||
);
|
|
||||||
if (matchingDevice) {
|
|
||||||
console.log(`[conn] Found device for: ${clientId} on ${serial}.`);
|
|
||||||
clearTimeout(timeout);
|
|
||||||
resolve(matchingDevice);
|
|
||||||
unsubscribe();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
'client-setMatchingDevice',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ServerController;
|
export default ServerController;
|
||||||
|
|
||||||
function clientQueryToKey(clientQuery: ClientQuery): string {
|
function clientQueryToKey(clientQuery: ClientQuery): string {
|
||||||
|
|||||||
@@ -17,15 +17,17 @@ import net, {Socket} from 'net';
|
|||||||
import {RSocketServer} from 'rsocket-core';
|
import {RSocketServer} from 'rsocket-core';
|
||||||
import RSocketTCPServer from 'rsocket-tcp-server';
|
import RSocketTCPServer from 'rsocket-tcp-server';
|
||||||
import {Payload, ReactiveSocket, Responder} from 'rsocket-types';
|
import {Payload, ReactiveSocket, Responder} from 'rsocket-types';
|
||||||
import Client from '../../Client';
|
|
||||||
import {Single} from 'rsocket-flowable';
|
import {Single} from 'rsocket-flowable';
|
||||||
import {
|
import {
|
||||||
ClientConnection,
|
ClientConnection,
|
||||||
ConnectionStatusChange,
|
ConnectionStatusChange,
|
||||||
ConnectionStatus,
|
ConnectionStatus,
|
||||||
ResponseType,
|
|
||||||
} from './ClientConnection';
|
} from './ClientConnection';
|
||||||
import {ClientQuery} from 'flipper-plugin';
|
import {
|
||||||
|
ClientDescription,
|
||||||
|
ClientQuery,
|
||||||
|
ClientResponseType,
|
||||||
|
} from 'flipper-plugin';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RSocket based server. RSocket uses its own protocol for communication between
|
* RSocket based server. RSocket uses its own protocol for communication between
|
||||||
@@ -153,7 +155,7 @@ class ServerRSocket extends ServerAdapter {
|
|||||||
})
|
})
|
||||||
.subscribe({
|
.subscribe({
|
||||||
onComplete: (payload: Payload<any, any>) => {
|
onComplete: (payload: Payload<any, any>) => {
|
||||||
const response: ResponseType = JSON.parse(payload.data);
|
const response: ClientResponseType = JSON.parse(payload.data);
|
||||||
response.length = payload.data.length;
|
response.length = payload.data.length;
|
||||||
resolve(response);
|
resolve(response);
|
||||||
},
|
},
|
||||||
@@ -165,11 +167,9 @@ class ServerRSocket extends ServerAdapter {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let resolvedClient: Client | undefined;
|
let resolvedClient: ClientDescription | undefined;
|
||||||
const client: Promise<Client> = this.listener.onConnectionCreated(
|
const client: Promise<ClientDescription> =
|
||||||
clientQuery,
|
this.listener.onConnectionCreated(clientQuery, clientConnection);
|
||||||
clientConnection,
|
|
||||||
);
|
|
||||||
client
|
client
|
||||||
.then((client) => {
|
.then((client) => {
|
||||||
console.log(
|
console.log(
|
||||||
@@ -184,12 +184,12 @@ class ServerRSocket extends ServerAdapter {
|
|||||||
return {
|
return {
|
||||||
fireAndForget: (payload: {data: string}) => {
|
fireAndForget: (payload: {data: string}) => {
|
||||||
if (resolvedClient) {
|
if (resolvedClient) {
|
||||||
resolvedClient.onMessage(payload.data);
|
this.listener.onClientMessage(resolvedClient.id, payload.data);
|
||||||
} else {
|
} else {
|
||||||
client &&
|
client &&
|
||||||
client
|
client
|
||||||
.then((client) => {
|
.then((client) => {
|
||||||
client.onMessage(payload.data);
|
this.listener.onClientMessage(client.id, payload.data);
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error('Could not deliver message: ', e);
|
console.error('Could not deliver message: ', e);
|
||||||
|
|||||||
@@ -12,15 +12,18 @@ import WebSocket from 'ws';
|
|||||||
import ws from 'ws';
|
import ws from 'ws';
|
||||||
import {SecureClientQuery, ServerEventsListener} from './ServerAdapter';
|
import {SecureClientQuery, ServerEventsListener} from './ServerAdapter';
|
||||||
import querystring from 'querystring';
|
import querystring from 'querystring';
|
||||||
import Client from '../../Client';
|
|
||||||
import {
|
import {
|
||||||
ClientConnection,
|
ClientConnection,
|
||||||
ConnectionStatus,
|
ConnectionStatus,
|
||||||
ConnectionStatusChange,
|
ConnectionStatusChange,
|
||||||
ErrorType,
|
|
||||||
} from './ClientConnection';
|
} from './ClientConnection';
|
||||||
import {IncomingMessage} from 'http';
|
import {IncomingMessage} from 'http';
|
||||||
import {ClientQuery, DeviceOS} from 'flipper-plugin';
|
import {
|
||||||
|
ClientDescription,
|
||||||
|
ClientErrorType,
|
||||||
|
ClientQuery,
|
||||||
|
DeviceOS,
|
||||||
|
} from 'flipper-plugin';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WebSocket-based server.
|
* WebSocket-based server.
|
||||||
@@ -121,11 +124,9 @@ class ServerWebSocket extends ServerWebSocketBase {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let resolvedClient: Client | undefined;
|
let resolvedClient: ClientDescription | undefined;
|
||||||
const client: Promise<Client> = this.listener.onConnectionCreated(
|
const client: Promise<ClientDescription> =
|
||||||
clientQuery,
|
this.listener.onConnectionCreated(clientQuery, clientConnection);
|
||||||
clientConnection,
|
|
||||||
);
|
|
||||||
client
|
client
|
||||||
.then((client) => (resolvedClient = client))
|
.then((client) => (resolvedClient = client))
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
@@ -147,7 +148,7 @@ class ServerWebSocket extends ServerWebSocketBase {
|
|||||||
const data: {
|
const data: {
|
||||||
id?: number;
|
id?: number;
|
||||||
success?: Object | undefined;
|
success?: Object | undefined;
|
||||||
error?: ErrorType | undefined;
|
error?: ClientErrorType | undefined;
|
||||||
} = json;
|
} = json;
|
||||||
|
|
||||||
if (data.hasOwnProperty('id') && data.id !== undefined) {
|
if (data.hasOwnProperty('id') && data.id !== undefined) {
|
||||||
@@ -165,14 +166,19 @@ class ServerWebSocket extends ServerWebSocketBase {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (resolvedClient) {
|
if (resolvedClient) {
|
||||||
resolvedClient.onMessage(message);
|
this.listener.onClientMessage(resolvedClient.id, message);
|
||||||
} else {
|
} else {
|
||||||
client &&
|
client &&
|
||||||
client
|
client
|
||||||
.then((client) => {
|
.then((client) => {
|
||||||
client.onMessage(message);
|
this.listener.onClientMessage(client.id, message);
|
||||||
})
|
})
|
||||||
.catch((_) => {});
|
.catch((e) => {
|
||||||
|
console.warn(
|
||||||
|
'Could not deliver message, client did not resolve. ',
|
||||||
|
e,
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,13 +10,12 @@
|
|||||||
import ServerWebSocketBase from './ServerWebSocketBase';
|
import ServerWebSocketBase from './ServerWebSocketBase';
|
||||||
import WebSocket from 'ws';
|
import WebSocket from 'ws';
|
||||||
import querystring from 'querystring';
|
import querystring from 'querystring';
|
||||||
import Client from '../../Client';
|
|
||||||
import {BrowserClientFlipperConnection} from './BrowserClientFlipperConnection';
|
import {BrowserClientFlipperConnection} from './BrowserClientFlipperConnection';
|
||||||
import {ServerEventsListener} from './ServerAdapter';
|
import {ServerEventsListener} from './ServerAdapter';
|
||||||
import constants from '../../fb-stubs/constants';
|
import constants from '../../fb-stubs/constants';
|
||||||
import ws from 'ws';
|
import ws from 'ws';
|
||||||
import {IncomingMessage} from 'http';
|
import {IncomingMessage} from 'http';
|
||||||
import {ClientQuery} from 'flipper-plugin';
|
import {ClientDescription, ClientQuery} from 'flipper-plugin';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WebSocket-based server which uses a connect/disconnect handshake over an insecure channel.
|
* WebSocket-based server which uses a connect/disconnect handshake over an insecure channel.
|
||||||
@@ -47,7 +46,7 @@ class ServerWebSocketBrowser extends ServerWebSocketBase {
|
|||||||
*/
|
*/
|
||||||
onConnection(ws: WebSocket, message: any): void {
|
onConnection(ws: WebSocket, message: any): void {
|
||||||
const clients: {
|
const clients: {
|
||||||
[app: string]: Promise<Client>;
|
[app: string]: Promise<ClientDescription>;
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -113,12 +112,13 @@ class ServerWebSocketBrowser extends ServerWebSocketBase {
|
|||||||
`[conn] Local websocket connection established: ${clientQuery.app} on ${clientQuery.device_id}.`,
|
`[conn] Local websocket connection established: ${clientQuery.app} on ${clientQuery.device_id}.`,
|
||||||
);
|
);
|
||||||
|
|
||||||
let resolvedClient: Client | null = null;
|
let resolvedClient: ClientDescription | null = null;
|
||||||
this.listener.onSecureConnectionAttempt(extendedClientQuery);
|
this.listener.onSecureConnectionAttempt(extendedClientQuery);
|
||||||
const client: Promise<Client> = this.listener.onConnectionCreated(
|
const client: Promise<ClientDescription> =
|
||||||
extendedClientQuery,
|
this.listener.onConnectionCreated(
|
||||||
clientConnection,
|
extendedClientQuery,
|
||||||
);
|
clientConnection,
|
||||||
|
);
|
||||||
client
|
client
|
||||||
.then((client) => {
|
.then((client) => {
|
||||||
console.log(
|
console.log(
|
||||||
@@ -148,9 +148,17 @@ class ServerWebSocketBrowser extends ServerWebSocketBase {
|
|||||||
if (parsed.app === app && parsed.payload?.id == null) {
|
if (parsed.app === app && parsed.payload?.id == null) {
|
||||||
const message = JSON.stringify(parsed.payload);
|
const message = JSON.stringify(parsed.payload);
|
||||||
if (resolvedClient) {
|
if (resolvedClient) {
|
||||||
resolvedClient.onMessage(message);
|
this.listener.onClientMessage(resolvedClient.id, message);
|
||||||
} else {
|
} else {
|
||||||
client.then((c) => c.onMessage(message)).catch((_) => {});
|
client
|
||||||
|
.then((c) => this.listener.onClientMessage(c.id, message))
|
||||||
|
.catch((e) => {
|
||||||
|
console.warn(
|
||||||
|
'Could not deliver message, client did not resolve: ' +
|
||||||
|
app,
|
||||||
|
e,
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,12 +9,10 @@
|
|||||||
|
|
||||||
import {reportPlatformFailures} from '../../../utils/metrics';
|
import {reportPlatformFailures} from '../../../utils/metrics';
|
||||||
import {execFile} from 'promisify-child-process';
|
import {execFile} from 'promisify-child-process';
|
||||||
import promiseRetry from 'promise-retry';
|
|
||||||
import adbConfig from './adbConfig';
|
import adbConfig from './adbConfig';
|
||||||
import adbkit, {Client} from 'adbkit';
|
import adbkit, {Client} from 'adbkit';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
const MAX_RETRIES = 5;
|
|
||||||
let instance: Promise<Client>;
|
let instance: Promise<Client>;
|
||||||
|
|
||||||
type Config = {
|
type Config = {
|
||||||
|
|||||||
@@ -51,7 +51,18 @@ export type ClientQuery = {
|
|||||||
export type ClientDescription = {
|
export type ClientDescription = {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly query: ClientQuery;
|
readonly query: ClientQuery;
|
||||||
readonly sdkVersion: number;
|
};
|
||||||
|
|
||||||
|
export type ClientErrorType = {
|
||||||
|
message: string;
|
||||||
|
stacktrace: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ClientResponseType = {
|
||||||
|
success?: Object;
|
||||||
|
error?: ClientErrorType;
|
||||||
|
length: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FlipperServerEvents = {
|
export type FlipperServerEvents = {
|
||||||
@@ -64,11 +75,16 @@ export type FlipperServerEvents = {
|
|||||||
};
|
};
|
||||||
'device-connected': DeviceDescription;
|
'device-connected': DeviceDescription;
|
||||||
'device-disconnected': DeviceDescription;
|
'device-disconnected': DeviceDescription;
|
||||||
'client-connected': ClientDescription;
|
|
||||||
'device-log': {
|
'device-log': {
|
||||||
serial: string;
|
serial: string;
|
||||||
entry: DeviceLogEntry;
|
entry: DeviceLogEntry;
|
||||||
};
|
};
|
||||||
|
'client-connected': ClientDescription;
|
||||||
|
'client-disconnected': {id: string};
|
||||||
|
'client-message': {
|
||||||
|
id: string;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FlipperServerCommands = {
|
export type FlipperServerCommands = {
|
||||||
@@ -91,6 +107,11 @@ export type FlipperServerCommands = {
|
|||||||
'device-clear-logs': (serial: string) => Promise<void>;
|
'device-clear-logs': (serial: string) => Promise<void>;
|
||||||
'device-navigate': (serial: string, location: string) => Promise<void>;
|
'device-navigate': (serial: string, location: string) => Promise<void>;
|
||||||
'metro-command': (serial: string, command: string) => Promise<void>;
|
'metro-command': (serial: string, command: string) => Promise<void>;
|
||||||
|
'client-request': (clientId: string, payload: any) => Promise<void>;
|
||||||
|
'client-request-response': (
|
||||||
|
clientId: string,
|
||||||
|
payload: any,
|
||||||
|
) => Promise<ClientResponseType>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface FlipperServer {
|
export interface FlipperServer {
|
||||||
|
|||||||
Reference in New Issue
Block a user