Implement sending messages to server add-on

Reviewed By: mweststrate

Differential Revision: D34073403

fbshipit-source-id: eacd73811b436f2b5c4255a83d8eb09367a96a67
This commit is contained in:
Andrey Goncharov
2022-02-28 03:50:34 -08:00
committed by Facebook GitHub Bot
parent 12151e4a71
commit 842b2c810a
5 changed files with 133 additions and 10 deletions

View File

@@ -580,6 +580,8 @@ export type ExecuteMessage = {
params?: unknown; params?: unknown;
}; };
}; };
// TODO: Could we merge it with ClientResponseType?
export type ResponseMessage = export type ResponseMessage =
| { | {
id: number; id: number;

View File

@@ -8,12 +8,15 @@
*/ */
import assert from 'assert'; import assert from 'assert';
import {ClientResponseType, ExecuteMessage} from 'flipper-common';
import {assertNotNull} from '../comms/Utilities'; import {assertNotNull} from '../comms/Utilities';
import {ServerAddOnDesktopToModuleConnection} from './ServerAddOnDesktopToModuleConnection';
import {ServerAddOnModuleToDesktopConnection} from './ServerAddOnModuleToDesktopConnection';
type ServerAddOnCleanup = () => Promise<void>; type ServerAddOnCleanup = () => Promise<void>;
interface ServerAddOnModule { interface ServerAddOnModule {
serverAddOn?: () => Promise<ServerAddOnCleanup>; serverAddOn?: (
connection: ServerAddOnModuleToDesktopConnection,
) => Promise<ServerAddOnCleanup>;
} }
const loadPlugin = (_pluginName: string): ServerAddOnModule => { const loadPlugin = (_pluginName: string): ServerAddOnModule => {
@@ -21,19 +24,14 @@ const loadPlugin = (_pluginName: string): ServerAddOnModule => {
return {serverAddOn: async () => async () => {}}; return {serverAddOn: async () => async () => {}};
}; };
interface ServerAddOnConnection {
sendExpectResponse(payload: ExecuteMessage): Promise<ClientResponseType>;
}
// TODO: Fix potential race conditions when starting/stopping concurrently // TODO: Fix potential race conditions when starting/stopping concurrently
export class ServerAddOn { export class ServerAddOn {
private owners: Set<string>; private owners: Set<string>;
// TODO: Implement connection
public readonly connection!: ServerAddOnConnection;
constructor( constructor(
public readonly pluginName: string, public readonly pluginName: string,
private readonly cleanup: ServerAddOnCleanup, private readonly cleanup: ServerAddOnCleanup,
public readonly connection: ServerAddOnDesktopToModuleConnection,
initialOwner: string, initialOwner: string,
) { ) {
this.owners = new Set(initialOwner); this.owners = new Set(initialOwner);
@@ -53,7 +51,10 @@ export class ServerAddOn {
`ServerAddOn ${pluginName} must export "serverAddOn" function.`, `ServerAddOn ${pluginName} must export "serverAddOn" function.`,
); );
const cleanup = await serverAddOn(); const serverAddOnModuleToDesktopConnection =
new ServerAddOnModuleToDesktopConnection();
const cleanup = await serverAddOn(serverAddOnModuleToDesktopConnection);
assert( assert(
typeof cleanup === 'function', typeof cleanup === 'function',
`ServerAddOn ${pluginName} must return a clean up function, instead it returned ${typeof cleanup}.`, `ServerAddOn ${pluginName} must return a clean up function, instead it returned ${typeof cleanup}.`,
@@ -64,7 +65,14 @@ export class ServerAddOn {
await cleanup(); await cleanup();
}; };
return new ServerAddOn(pluginName, onStopCombined, initialOwner); return new ServerAddOn(
pluginName,
onStopCombined,
new ServerAddOnDesktopToModuleConnection(
serverAddOnModuleToDesktopConnection,
),
initialOwner,
);
} }
addOwner(owner: string) { addOwner(owner: string) {

View File

@@ -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<ClientResponseType> {
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,
};
}
}

View File

@@ -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<FlipperPluginReceiverRes>;
export class ServerAddOnModuleToDesktopConnection {
private subscriptions: Map<string, FlipperPluginReceiver> = new Map();
send() {
// TODO: Implement me
}
receive(method: string, receiver: FlipperPluginReceiver) {
this.subscriptions.set(method, receiver);
}
async call(method: string, params: unknown): Promise<ResponseMessage> {
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,
};
}
}
}

View File

@@ -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';
}
};