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:
Michel Weststrate
2021-10-06 09:08:47 -07:00
committed by Facebook GitHub Bot
parent eab4804792
commit 740093d0d9
17 changed files with 276 additions and 201 deletions

View File

@@ -8,13 +8,16 @@
*/
import React from 'react';
import {Store} from '../reducers/index';
import {State, Store} from '../reducers/index';
import {Logger} from '../fb-interfaces/Logger';
import {FlipperServerImpl} from '../server/FlipperServerImpl';
import {selectClient, selectDevice} from '../reducers/connections';
import Client from '../Client';
import {notification} from 'antd';
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) => {
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
});
server.on('client-connected', (payload) =>
// TODO: fixed later in this stack
handleClientConnected(store, payload as any),
server.on('client-connected', (payload: ClientDescription) =>
handleClientConnected(server, store, logger, payload),
);
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') {
window.addEventListener('beforeunload', () => {
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 existingClient = connections.clients.get(client.id);
const existingClient = connections.clients.get(id);
if (existingClient) {
existingClient.destroy();
store.dispatch({
type: 'CLEAR_CLIENT_PLUGINS_STATE',
payload: {
clientId: client.id,
clientId: id,
devicePlugins: new Set(),
},
});
store.dispatch({
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(
`Device client initialized: ${client.id}. Supported plugins: ${Array.from(
client.plugins,
@@ -174,5 +212,64 @@ export async function handleClientConnected(store: Store, client: Client) {
});
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',
);
}