diff --git a/desktop/app/src/Client.tsx b/desktop/app/src/Client.tsx index 626af5dff..c3ca09b99 100644 --- a/desktop/app/src/Client.tsx +++ b/desktop/app/src/Client.tsx @@ -33,6 +33,8 @@ import { timeout, ClientQuery, _SandyPluginDefinition, + ClientResponseType, + ClientErrorType, } from 'flipper-plugin'; import {freeze} from 'immer'; import {message} from 'antd'; @@ -40,11 +42,6 @@ import { isFlipperMessageDebuggingEnabled, registerFlipperDebugMessage, } from './chrome/FlipperMessages'; -import { - ConnectionStatus, - ErrorType, - ClientConnection, -} from './server/comms/ClientConnection'; type Plugins = Set; type PluginsArr = Array; @@ -65,7 +62,11 @@ export type RequestMetadata = { params: Params | undefined; }; -const handleError = (store: Store, device: BaseDevice, error: ErrorType) => { +const handleError = ( + store: Store, + device: BaseDevice, + error: ClientErrorType, +) => { if (store.getState().settingsState.suppressPluginErrors) { return; } @@ -91,13 +92,18 @@ const handleError = (store: Store, device: BaseDevice, error: ErrorType) => { crashReporterPlugin.instanceApi.reportCrash(payload); }; +interface ClientConnection { + send(data: any): void; + sendExpectResponse(data: any): Promise; +} + export default class Client extends EventEmitter { connected = createState(false); id: string; query: ClientQuery; sdkVersion: number; messageIdCounter: number; - plugins: Plugins; + plugins: Plugins; // TODO: turn into atom, and remove eventEmitter backgroundPlugins: Plugins; connection: ClientConnection | null | undefined; store: Store; @@ -138,18 +144,6 @@ export default class Client extends EventEmitter { this.broadcastCallbacks = new Map(); this.activePlugins = new Set(); 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 { @@ -185,6 +179,7 @@ export default class Client extends EventEmitter { this.initPlugin(plugin); } }); + this.emit('plugins-change'); } initFromImport(initialStates: Record>): this { @@ -194,6 +189,7 @@ export default class Client extends EventEmitter { this.loadPlugin(plugin, initialStates[pluginId]); } }); + this.emit('plugins-change'); return this; } @@ -266,7 +262,6 @@ export default class Client extends EventEmitter { }); this.emit('close'); this.connected.set(false); - this.connection = undefined; } // clean up this client @@ -346,7 +341,7 @@ export default class Client extends EventEmitter { method?: string; params?: Params; success?: Object; - error?: ErrorType; + error?: ClientErrorType; } = rawData; const {id, method} = data; @@ -438,10 +433,10 @@ export default class Client extends EventEmitter { onResponse( data: { success?: Object; - error?: ErrorType; + error?: ClientErrorType; }, resolve: ((a: any) => any) | undefined, - reject: (error: ErrorType) => any, + reject: (error: ClientErrorType) => any, ) { if (data.success) { resolve && resolve(data.success); diff --git a/desktop/app/src/PluginContainer.tsx b/desktop/app/src/PluginContainer.tsx index 04abcf747..29804037f 100644 --- a/desktop/app/src/PluginContainer.tsx +++ b/desktop/app/src/PluginContainer.tsx @@ -49,6 +49,7 @@ import {produce} from 'immer'; import {reportUsage} from './utils/metrics'; import {PluginInfo} from './chrome/fb-stubs/PluginInfo'; import {getActiveClient, getActivePlugin} from './selectors/connections'; +import {isTest} from './utils/isProduction'; const {Text, Link} = Typography; @@ -250,7 +251,7 @@ class PluginContainer extends PureComponent { render() { const {activePlugin, pluginKey, target, pendingMessages} = this.props; if (!activePlugin || !target || !pluginKey) { - return null; + return this.renderNoPluginActive(); } if (activePlugin.status !== 'enabled') { return this.renderPluginInfo(); @@ -294,6 +295,9 @@ class PluginContainer extends PureComponent { } renderNoPluginActive() { + if (isTest()) { + return <>No plugin selected; // to keep 'nothing' clearly recognisable in unit tests + } return ( diff --git a/desktop/app/src/__tests__/PluginContainer.node.tsx b/desktop/app/src/__tests__/PluginContainer.node.tsx index 245a3ba65..1b21db2ee 100644 --- a/desktop/app/src/__tests__/PluginContainer.node.tsx +++ b/desktop/app/src/__tests__/PluginContainer.node.tsx @@ -260,7 +260,9 @@ test('PluginContainer can render Sandy plugins', async () => { expect(renderer.baseElement).toMatchInlineSnapshot(` -
+
+ No plugin selected +
`); expect(pluginInstance.connectedStub).toBeCalledTimes(1); @@ -801,7 +803,9 @@ test('PluginContainer can render Sandy device plugins', async () => { expect(renderer.baseElement).toMatchInlineSnapshot(` -
+
+ No plugin selected +
`); expect(pluginInstance.activatedStub).toBeCalledTimes(1); @@ -1232,7 +1236,9 @@ test('PluginContainer can render Sandy plugins for archived devices', async () = expect(renderer.baseElement).toMatchInlineSnapshot(` -
+
+ No plugin selected +
`); expect(pluginInstance.connectedStub).toBeCalledTimes(0); diff --git a/desktop/app/src/__tests__/disconnect.node.tsx b/desktop/app/src/__tests__/disconnect.node.tsx index 1b7100b16..1e816e137 100644 --- a/desktop/app/src/__tests__/disconnect.node.tsx +++ b/desktop/app/src/__tests__/disconnect.node.tsx @@ -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, { asBackgroundPlugin: true, }); @@ -225,7 +225,7 @@ test('new clients replace old ones', async () => { expect(instance.instanceApi.disconnect).toBeCalledTimes(0); 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); const instance2 = client2.sandyPluginStates.get(plugin.id)!; diff --git a/desktop/app/src/dispatcher/__tests__/handleOpenPluginDeeplink.node.tsx b/desktop/app/src/dispatcher/__tests__/handleOpenPluginDeeplink.node.tsx index 6ac5c541b..150f7e361 100644 --- a/desktop/app/src/dispatcher/__tests__/handleOpenPluginDeeplink.node.tsx +++ b/desktop/app/src/dispatcher/__tests__/handleOpenPluginDeeplink.node.tsx @@ -193,9 +193,11 @@ test('triggering a deeplink without applicable device can wait for a device', as ); expect(renderer.baseElement).toMatchInlineSnapshot(` -
+
+ No plugin selected +
- `); + `); const handlePromise = handleDeeplink( 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) expect(renderer.baseElement).toMatchInlineSnapshot(` - -
- + +
+ No plugin selected +
+ `); // 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(` -
+
+ No plugin selected +
- `); + `); const handlePromise = handleDeeplink( 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) expect(renderer.baseElement).toMatchInlineSnapshot(` -
+
+ No plugin selected +
`); diff --git a/desktop/app/src/dispatcher/flipperServer.tsx b/desktop/app/src/dispatcher/flipperServer.tsx index 3ce945408..406c63f67 100644 --- a/desktop/app/src/dispatcher/flipperServer.tsx +++ b/desktop/app/src/dispatcher/flipperServer.tsx @@ -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 { + 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((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', + ); } diff --git a/desktop/app/src/dispatcher/pluginsChangeListener.tsx b/desktop/app/src/dispatcher/pluginsChangeListener.tsx index f15a6c09f..0594dc1da 100644 --- a/desktop/app/src/dispatcher/pluginsChangeListener.tsx +++ b/desktop/app/src/dispatcher/pluginsChangeListener.tsx @@ -23,7 +23,7 @@ export default (store: Store, _logger: Logger) => { sideEffect( store, - {name: 'pluginsChangeListener', throttleMs: 100, fireImmediately: true}, + {name: 'pluginsChangeListener', throttleMs: 10, fireImmediately: true}, getActiveClient, (activeClient, _store) => { if (activeClient !== prevClient) { @@ -33,6 +33,7 @@ export default (store: Store, _logger: Logger) => { prevClient = activeClient; if (prevClient) { prevClient.on('plugins-change', onActiveAppPluginListChanged); + store.dispatch(appPluginListChanged()); // force refresh } } }, diff --git a/desktop/app/src/server/FlipperServerImpl.tsx b/desktop/app/src/server/FlipperServerImpl.tsx index 0284cdd1f..c340ff6f3 100644 --- a/desktop/app/src/server/FlipperServerImpl.tsx +++ b/desktop/app/src/server/FlipperServerImpl.tsx @@ -8,7 +8,6 @@ */ import EventEmitter from 'events'; -import Client from '../Client'; import {Store} from '../reducers/index'; import {Logger} from '../fb-interfaces/Logger'; import ServerController from './comms/ServerController'; @@ -91,10 +90,6 @@ export class FlipperServerImpl implements FlipperServer { this.android = new AndroidDeviceManager(this); this.ios = new IOSDeviceManager(this); - server.addListener('new-client', (client: Client) => { - this.emit('client-connected', client); - }); - server.addListener('error', (err) => { this.emit('server-error', err); }); @@ -263,6 +258,25 @@ export class FlipperServerImpl implements FlipperServer { } 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) { diff --git a/desktop/app/src/server/comms/BrowserClientFlipperConnection.tsx b/desktop/app/src/server/comms/BrowserClientFlipperConnection.tsx index 4e907e162..bd91cb6fd 100644 --- a/desktop/app/src/server/comms/BrowserClientFlipperConnection.tsx +++ b/desktop/app/src/server/comms/BrowserClientFlipperConnection.tsx @@ -7,12 +7,12 @@ * @format */ +import {ClientResponseType} from 'flipper-plugin'; import WebSocket from 'ws'; import { ConnectionStatusChange, ConnectionStatus, ClientConnection, - ResponseType, } from './ClientConnection'; export class BrowserClientFlipperConnection implements ClientConnection { @@ -40,7 +40,7 @@ export class BrowserClientFlipperConnection implements ClientConnection { }), ); } - sendExpectResponse(data: any): Promise { + sendExpectResponse(data: any): Promise { return new Promise((resolve, reject) => { const {id: callId = undefined, method = undefined} = data != null ? data : {}; diff --git a/desktop/app/src/server/comms/ClientConnection.tsx b/desktop/app/src/server/comms/ClientConnection.tsx index 0192a8249..46bb5b58e 100644 --- a/desktop/app/src/server/comms/ClientConnection.tsx +++ b/desktop/app/src/server/comms/ClientConnection.tsx @@ -7,17 +7,7 @@ * @format */ -export type ErrorType = { - message: string; - stacktrace: string; - name: string; -}; - -export type ResponseType = { - success?: Object; - error?: ErrorType; - length: number; -}; +import {ClientResponseType} from 'flipper-plugin'; export enum ConnectionStatus { ERROR = 'error', @@ -33,5 +23,5 @@ export interface ClientConnection { subscribeToEvents(subscriber: ConnectionStatusChange): void; close(): void; send(data: any): void; - sendExpectResponse(data: any): Promise; + sendExpectResponse(data: any): Promise; } diff --git a/desktop/app/src/server/comms/ServerAdapter.tsx b/desktop/app/src/server/comms/ServerAdapter.tsx index 2a42425e1..3521e6cb6 100644 --- a/desktop/app/src/server/comms/ServerAdapter.tsx +++ b/desktop/app/src/server/comms/ServerAdapter.tsx @@ -11,10 +11,9 @@ import { CertificateExchangeMedium, SecureServerConfig, } from '../utils/CertificateProvider'; -import Client from '../../Client'; import {ClientConnection} from './ClientConnection'; import {transformCertificateExchangeMediumToType} from './Utilities'; -import {ClientQuery} from 'flipper-plugin'; +import {ClientDescription, ClientQuery} from 'flipper-plugin'; /** * ClientCsrQuery defines a client query with CSR @@ -87,7 +86,7 @@ export interface ServerEventsListener { onConnectionCreated( clientQuery: SecureClientQuery, clientConnection: ClientConnection, - ): Promise; + ): Promise; /** * A connection with a client has been closed. * @param id The client identifier. @@ -98,6 +97,11 @@ export interface ServerEventsListener { * @param error An Error instance. */ onError(error: Error): void; + /** + * A message was received for a specif client + * // TODO: payload should become JSON + */ + onClientMessage(clientId: string, payload: string): void; } /** diff --git a/desktop/app/src/server/comms/ServerController.tsx b/desktop/app/src/server/comms/ServerController.tsx index b4f14e471..240dfb73a 100644 --- a/desktop/app/src/server/comms/ServerController.tsx +++ b/desktop/app/src/server/comms/ServerController.tsx @@ -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 { + ): Promise { 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 { + ): Promise { 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 { - 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((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 { diff --git a/desktop/app/src/server/comms/ServerRSocket.tsx b/desktop/app/src/server/comms/ServerRSocket.tsx index 9123091ec..d66c59d98 100644 --- a/desktop/app/src/server/comms/ServerRSocket.tsx +++ b/desktop/app/src/server/comms/ServerRSocket.tsx @@ -17,15 +17,17 @@ import net, {Socket} from 'net'; import {RSocketServer} from 'rsocket-core'; import RSocketTCPServer from 'rsocket-tcp-server'; import {Payload, ReactiveSocket, Responder} from 'rsocket-types'; -import Client from '../../Client'; import {Single} from 'rsocket-flowable'; import { ClientConnection, ConnectionStatusChange, ConnectionStatus, - ResponseType, } 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 @@ -153,7 +155,7 @@ class ServerRSocket extends ServerAdapter { }) .subscribe({ onComplete: (payload: Payload) => { - const response: ResponseType = JSON.parse(payload.data); + const response: ClientResponseType = JSON.parse(payload.data); response.length = payload.data.length; resolve(response); }, @@ -165,11 +167,9 @@ class ServerRSocket extends ServerAdapter { }, }; - let resolvedClient: Client | undefined; - const client: Promise = this.listener.onConnectionCreated( - clientQuery, - clientConnection, - ); + let resolvedClient: ClientDescription | undefined; + const client: Promise = + this.listener.onConnectionCreated(clientQuery, clientConnection); client .then((client) => { console.log( @@ -184,12 +184,12 @@ class ServerRSocket extends ServerAdapter { return { fireAndForget: (payload: {data: string}) => { if (resolvedClient) { - resolvedClient.onMessage(payload.data); + this.listener.onClientMessage(resolvedClient.id, payload.data); } else { client && client .then((client) => { - client.onMessage(payload.data); + this.listener.onClientMessage(client.id, payload.data); }) .catch((e) => { console.error('Could not deliver message: ', e); diff --git a/desktop/app/src/server/comms/ServerWebSocket.tsx b/desktop/app/src/server/comms/ServerWebSocket.tsx index 18e0daa94..26ce8f8e9 100644 --- a/desktop/app/src/server/comms/ServerWebSocket.tsx +++ b/desktop/app/src/server/comms/ServerWebSocket.tsx @@ -12,15 +12,18 @@ import WebSocket from 'ws'; import ws from 'ws'; import {SecureClientQuery, ServerEventsListener} from './ServerAdapter'; import querystring from 'querystring'; -import Client from '../../Client'; import { ClientConnection, ConnectionStatus, ConnectionStatusChange, - ErrorType, } from './ClientConnection'; import {IncomingMessage} from 'http'; -import {ClientQuery, DeviceOS} from 'flipper-plugin'; +import { + ClientDescription, + ClientErrorType, + ClientQuery, + DeviceOS, +} from 'flipper-plugin'; /** * WebSocket-based server. @@ -121,11 +124,9 @@ class ServerWebSocket extends ServerWebSocketBase { }, }; - let resolvedClient: Client | undefined; - const client: Promise = this.listener.onConnectionCreated( - clientQuery, - clientConnection, - ); + let resolvedClient: ClientDescription | undefined; + const client: Promise = + this.listener.onConnectionCreated(clientQuery, clientConnection); client .then((client) => (resolvedClient = client)) .catch((e) => { @@ -147,7 +148,7 @@ class ServerWebSocket extends ServerWebSocketBase { const data: { id?: number; success?: Object | undefined; - error?: ErrorType | undefined; + error?: ClientErrorType | undefined; } = json; if (data.hasOwnProperty('id') && data.id !== undefined) { @@ -165,14 +166,19 @@ class ServerWebSocket extends ServerWebSocketBase { } } else { if (resolvedClient) { - resolvedClient.onMessage(message); + this.listener.onClientMessage(resolvedClient.id, message); } else { client && 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, + ); + }); } } }); diff --git a/desktop/app/src/server/comms/ServerWebSocketBrowser.tsx b/desktop/app/src/server/comms/ServerWebSocketBrowser.tsx index 2407c41ad..bc2627b32 100644 --- a/desktop/app/src/server/comms/ServerWebSocketBrowser.tsx +++ b/desktop/app/src/server/comms/ServerWebSocketBrowser.tsx @@ -10,13 +10,12 @@ import ServerWebSocketBase from './ServerWebSocketBase'; import WebSocket from 'ws'; import querystring from 'querystring'; -import Client from '../../Client'; import {BrowserClientFlipperConnection} from './BrowserClientFlipperConnection'; import {ServerEventsListener} from './ServerAdapter'; import constants from '../../fb-stubs/constants'; import ws from 'ws'; 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. @@ -47,7 +46,7 @@ class ServerWebSocketBrowser extends ServerWebSocketBase { */ onConnection(ws: WebSocket, message: any): void { const clients: { - [app: string]: Promise; + [app: string]: Promise; } = {}; /** @@ -113,12 +112,13 @@ class ServerWebSocketBrowser extends ServerWebSocketBase { `[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); - const client: Promise = this.listener.onConnectionCreated( - extendedClientQuery, - clientConnection, - ); + const client: Promise = + this.listener.onConnectionCreated( + extendedClientQuery, + clientConnection, + ); client .then((client) => { console.log( @@ -148,9 +148,17 @@ class ServerWebSocketBrowser extends ServerWebSocketBase { if (parsed.app === app && parsed.payload?.id == null) { const message = JSON.stringify(parsed.payload); if (resolvedClient) { - resolvedClient.onMessage(message); + this.listener.onClientMessage(resolvedClient.id, message); } 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, + ); + }); } } }); diff --git a/desktop/app/src/server/devices/android/adbClient.tsx b/desktop/app/src/server/devices/android/adbClient.tsx index 833b2a92a..64de7c469 100644 --- a/desktop/app/src/server/devices/android/adbClient.tsx +++ b/desktop/app/src/server/devices/android/adbClient.tsx @@ -9,12 +9,10 @@ import {reportPlatformFailures} from '../../../utils/metrics'; import {execFile} from 'promisify-child-process'; -import promiseRetry from 'promise-retry'; import adbConfig from './adbConfig'; import adbkit, {Client} from 'adbkit'; import path from 'path'; -const MAX_RETRIES = 5; let instance: Promise; type Config = { diff --git a/desktop/flipper-plugin/src/types/server-types.tsx b/desktop/flipper-plugin/src/types/server-types.tsx index 04e766cbe..833ca9011 100644 --- a/desktop/flipper-plugin/src/types/server-types.tsx +++ b/desktop/flipper-plugin/src/types/server-types.tsx @@ -51,7 +51,18 @@ export type ClientQuery = { export type ClientDescription = { readonly id: string; 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 = { @@ -64,11 +75,16 @@ export type FlipperServerEvents = { }; 'device-connected': DeviceDescription; 'device-disconnected': DeviceDescription; - 'client-connected': ClientDescription; 'device-log': { serial: string; entry: DeviceLogEntry; }; + 'client-connected': ClientDescription; + 'client-disconnected': {id: string}; + 'client-message': { + id: string; + message: string; + }; }; export type FlipperServerCommands = { @@ -91,6 +107,11 @@ export type FlipperServerCommands = { 'device-clear-logs': (serial: string) => Promise; 'device-navigate': (serial: string, location: string) => Promise; 'metro-command': (serial: string, command: string) => Promise; + 'client-request': (clientId: string, payload: any) => Promise; + 'client-request-response': ( + clientId: string, + payload: any, + ) => Promise; }; export interface FlipperServer {