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

@@ -9,10 +9,9 @@
import {CertificateExchangeMedium} from '../utils/CertificateProvider';
import {Logger} from '../../fb-interfaces/Logger';
import {ClientQuery} from 'flipper-plugin';
import {Store, State} from '../../reducers/index';
import {ClientDescription, ClientQuery} from 'flipper-plugin';
import {Store} from '../../reducers/index';
import CertificateProvider from '../utils/CertificateProvider';
import Client from '../../Client';
import {ClientConnection, ConnectionStatus} from './ClientConnection';
import {UninitializedClient} from '../UninitializedClient';
import {reportPlatformFailures} from '../../utils/metrics';
@@ -21,8 +20,6 @@ import invariant from 'invariant';
import GK from '../../fb-stubs/GK';
import {buildClientId} from '../../utils/clientUtils';
import DummyDevice from '../../server/devices/DummyDevice';
import BaseDevice from '../../devices/BaseDevice';
import {sideEffect} from '../../utils/sideEffect';
import {
appNameWithUpdateHint,
transformCertificateExchangeMediumToType,
@@ -38,11 +35,10 @@ import {
} from './ServerFactory';
import {FlipperServerImpl} from '../FlipperServerImpl';
import {isTest} from '../../utils/isProduction';
import {timeout} from 'flipper-plugin';
type ClientInfo = {
connection: ClientConnection | null | undefined;
client: Client;
client: ClientDescription;
};
type ClientCsrQuery = {
@@ -51,9 +47,7 @@ type ClientCsrQuery = {
};
declare interface ServerController {
on(event: 'new-client', callback: (client: Client) => 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;
}
onClientMessage(clientId: string, payload: string): void {
this.flipperServer.emit('client-message', {
id: clientId,
message: payload,
});
}
get logger(): Logger {
return this.flipperServer.logger;
}
@@ -188,7 +189,7 @@ class ServerController extends EventEmitter implements ServerEventsListener {
onConnectionCreated(
clientQuery: SecureClientQuery,
clientConnection: ClientConnection,
): Promise<Client> {
): Promise<ClientDescription> {
const {app, os, device, device_id, sdk_version, csr, csr_path, medium} =
clientQuery;
const transformedMedium = transformCertificateExchangeMediumToType(medium);
@@ -334,7 +335,7 @@ class ServerController extends EventEmitter implements ServerEventsListener {
connection: ClientConnection,
query: ClientQuery & {medium: CertificateExchangeMedium},
csrQuery: ClientCsrQuery,
): Promise<Client> {
): Promise<ClientDescription> {
invariant(query, 'expected query');
// 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(
`[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,
query,
connection,
this.logger,
this.store,
undefined,
device,
);
};
const info = {
client,
@@ -395,12 +387,6 @@ class ServerController extends EventEmitter implements ServerEventsListener {
`[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) => {
if (
status === ConnectionStatus.CLOSED ||
@@ -410,12 +396,7 @@ class ServerController extends EventEmitter implements ServerEventsListener {
}
});
console.debug(
`[conn] Device client initialized: ${id}. Supported plugins: ${Array.from(
client.plugins,
).join(', ')}`,
'server',
);
console.debug(`[conn] Device client initialized: ${id}.`, 'server');
/* If a device gets disconnected without being cleaned up properly,
* 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.emit('new-client', client);
this.emit('clients-change');
client.emit('plugins-change');
this.flipperServer.emit('client-connected', client);
return client;
}
attachFakeClient(client: Client) {
attachFakeClient(client: ClientDescription) {
this.connections.set(client.id, {
client,
connection: null,
@@ -460,10 +439,8 @@ class ServerController extends EventEmitter implements ServerEventsListener {
console.log(
`[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.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;
function clientQueryToKey(clientQuery: ClientQuery): string {