From 842b2c810adc8ee26e92771c0c7cbf3af308bc99 Mon Sep 17 00:00:00 2001 From: Andrey Goncharov Date: Mon, 28 Feb 2022 03:50:34 -0800 Subject: [PATCH] Implement sending messages to server add-on Reviewed By: mweststrate Differential Revision: D34073403 fbshipit-source-id: eacd73811b436f2b5c4255a83d8eb09367a96a67 --- desktop/flipper-common/src/server-types.tsx | 2 + .../src/plugins/ServerAddOn.tsx | 28 +++++---- .../ServerAddOnDesktopToModuleConnection.tsx | 37 ++++++++++++ .../ServerAddOnModuleToDesktopConnection.tsx | 60 +++++++++++++++++++ .../src/utils/safeJSONStringify.tsx | 16 +++++ 5 files changed, 133 insertions(+), 10 deletions(-) create mode 100644 desktop/flipper-server-core/src/plugins/ServerAddOnDesktopToModuleConnection.tsx create mode 100644 desktop/flipper-server-core/src/plugins/ServerAddOnModuleToDesktopConnection.tsx create mode 100644 desktop/flipper-server-core/src/utils/safeJSONStringify.tsx diff --git a/desktop/flipper-common/src/server-types.tsx b/desktop/flipper-common/src/server-types.tsx index 2395a2f8d..932411d76 100644 --- a/desktop/flipper-common/src/server-types.tsx +++ b/desktop/flipper-common/src/server-types.tsx @@ -580,6 +580,8 @@ export type ExecuteMessage = { params?: unknown; }; }; + +// TODO: Could we merge it with ClientResponseType? export type ResponseMessage = | { id: number; diff --git a/desktop/flipper-server-core/src/plugins/ServerAddOn.tsx b/desktop/flipper-server-core/src/plugins/ServerAddOn.tsx index c44e8d7ee..f9ba9a2f2 100644 --- a/desktop/flipper-server-core/src/plugins/ServerAddOn.tsx +++ b/desktop/flipper-server-core/src/plugins/ServerAddOn.tsx @@ -8,12 +8,15 @@ */ import assert from 'assert'; -import {ClientResponseType, ExecuteMessage} from 'flipper-common'; import {assertNotNull} from '../comms/Utilities'; +import {ServerAddOnDesktopToModuleConnection} from './ServerAddOnDesktopToModuleConnection'; +import {ServerAddOnModuleToDesktopConnection} from './ServerAddOnModuleToDesktopConnection'; type ServerAddOnCleanup = () => Promise; interface ServerAddOnModule { - serverAddOn?: () => Promise; + serverAddOn?: ( + connection: ServerAddOnModuleToDesktopConnection, + ) => Promise; } const loadPlugin = (_pluginName: string): ServerAddOnModule => { @@ -21,19 +24,14 @@ const loadPlugin = (_pluginName: string): ServerAddOnModule => { return {serverAddOn: async () => async () => {}}; }; -interface ServerAddOnConnection { - sendExpectResponse(payload: ExecuteMessage): Promise; -} - // TODO: Fix potential race conditions when starting/stopping concurrently export class ServerAddOn { private owners: Set; - // TODO: Implement connection - public readonly connection!: ServerAddOnConnection; constructor( public readonly pluginName: string, private readonly cleanup: ServerAddOnCleanup, + public readonly connection: ServerAddOnDesktopToModuleConnection, initialOwner: string, ) { this.owners = new Set(initialOwner); @@ -53,7 +51,10 @@ export class ServerAddOn { `ServerAddOn ${pluginName} must export "serverAddOn" function.`, ); - const cleanup = await serverAddOn(); + const serverAddOnModuleToDesktopConnection = + new ServerAddOnModuleToDesktopConnection(); + + const cleanup = await serverAddOn(serverAddOnModuleToDesktopConnection); assert( typeof cleanup === 'function', `ServerAddOn ${pluginName} must return a clean up function, instead it returned ${typeof cleanup}.`, @@ -64,7 +65,14 @@ export class ServerAddOn { await cleanup(); }; - return new ServerAddOn(pluginName, onStopCombined, initialOwner); + return new ServerAddOn( + pluginName, + onStopCombined, + new ServerAddOnDesktopToModuleConnection( + serverAddOnModuleToDesktopConnection, + ), + initialOwner, + ); } addOwner(owner: string) { diff --git a/desktop/flipper-server-core/src/plugins/ServerAddOnDesktopToModuleConnection.tsx b/desktop/flipper-server-core/src/plugins/ServerAddOnDesktopToModuleConnection.tsx new file mode 100644 index 000000000..9ffa4b278 --- /dev/null +++ b/desktop/flipper-server-core/src/plugins/ServerAddOnDesktopToModuleConnection.tsx @@ -0,0 +1,37 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +import assert from 'assert'; +import {ClientResponseType, ExecuteMessage} from 'flipper-common'; +import {ServerAddOnModuleToDesktopConnection} from './ServerAddOnModuleToDesktopConnection'; + +export class ServerAddOnDesktopToModuleConnection { + constructor( + private readonly moduleToDesktopConnection: ServerAddOnModuleToDesktopConnection, + ) {} + + async sendExpectResponse({ + method, + params, + }: ExecuteMessage): Promise { + assert( + method === 'execute', + 'ServerAddOnDesktopToModuleConnection supports only "execute" messages', + ); + const response = await this.moduleToDesktopConnection.call( + params.method, + params.params, + ); + const length = JSON.stringify(response).length; + return { + ...response, + length, + }; + } +} diff --git a/desktop/flipper-server-core/src/plugins/ServerAddOnModuleToDesktopConnection.tsx b/desktop/flipper-server-core/src/plugins/ServerAddOnModuleToDesktopConnection.tsx new file mode 100644 index 000000000..92693352e --- /dev/null +++ b/desktop/flipper-server-core/src/plugins/ServerAddOnModuleToDesktopConnection.tsx @@ -0,0 +1,60 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +import {ResponseMessage, ClientErrorType} from 'flipper-common'; +import {safeJSONStringify} from '../utils/safeJSONStringify'; + +// TODO: Share with js-flipper? Is it worth it? +type FlipperPluginReceiverRes = + | object + | string + | number + | boolean + | null + | undefined + | void; + +type FlipperPluginReceiver = ( + data: any, +) => FlipperPluginReceiverRes | Promise; + +export class ServerAddOnModuleToDesktopConnection { + private subscriptions: Map = new Map(); + + send() { + // TODO: Implement me + } + + receive(method: string, receiver: FlipperPluginReceiver) { + this.subscriptions.set(method, receiver); + } + + async call(method: string, params: unknown): Promise { + try { + const receiver = this.subscriptions.get(method); + if (!receiver) { + throw new Error(`Receiver ${method} not found.`); + } + const response = await receiver.call(receiver, params); + return { + id: 0, // Not used in server <-> desktop connections. Used only in server <-> client connections. + success: response == null ? null : response, + }; + } catch (e) { + const errorMessage: ClientErrorType = + e instanceof Error + ? {name: e.name, message: e.message, stacktrace: e.stack ?? ''} + : {name: 'Unknown', message: safeJSONStringify(e), stacktrace: ''}; + return { + id: 0, // Not used in server <-> desktop connections. Used only in server <-> client connections. + error: errorMessage, + }; + } + } +} diff --git a/desktop/flipper-server-core/src/utils/safeJSONStringify.tsx b/desktop/flipper-server-core/src/utils/safeJSONStringify.tsx new file mode 100644 index 000000000..2828da249 --- /dev/null +++ b/desktop/flipper-server-core/src/utils/safeJSONStringify.tsx @@ -0,0 +1,16 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +export const safeJSONStringify = (data: unknown): string => { + try { + return JSON.stringify(data); + } catch { + return 'Unable to serialize'; + } +};