Implement sending messages from a server add-on

Reviewed By: mweststrate

Differential Revision: D34074383

fbshipit-source-id: de85e7a22dc9bb780163fc5b522708e8bc976df3
This commit is contained in:
Andrey Goncharov
2022-02-28 03:50:34 -08:00
committed by Facebook GitHub Bot
parent 842b2c810a
commit db976d5113
6 changed files with 82 additions and 19 deletions

View File

@@ -125,9 +125,7 @@ export type FlipperServerEvents = {
id: string; id: string;
message: string; message: string;
}; };
'plugins-server-add-on-message': { 'plugins-server-add-on-message': ExecuteMessage;
payload: ExecuteMessage;
};
'download-file-update': DownloadFileUpdate; 'download-file-update': DownloadFileUpdate;
}; };

View File

@@ -88,7 +88,7 @@ export class FlipperServerImpl implements FlipperServer {
// given flipper-dump, it might make more sense to have the plugin command // given flipper-dump, it might make more sense to have the plugin command
// handling (like download, install, etc) moved to flipper-server & app, // handling (like download, install, etc) moved to flipper-server & app,
// but let's keep things simple for now // but let's keep things simple for now
this.pluginManager = new PluginManager(); this.pluginManager = new PluginManager(this);
server.addListener('error', (err) => { server.addListener('error', (err) => {
this.emit('server-error', err); this.emit('server-error', err);

View File

@@ -32,6 +32,7 @@ import {
installPluginFromNpm, installPluginFromNpm,
} from 'flipper-plugin-lib'; } from 'flipper-plugin-lib';
import {ServerAddOn} from './ServerAddOn'; import {ServerAddOn} from './ServerAddOn';
import {FlipperServerForServerAddOn} from './ServerAddOnDesktopToModuleConnection';
const maxInstalledPluginVersionsToKeep = 2; const maxInstalledPluginVersionsToKeep = 2;
@@ -50,6 +51,8 @@ const isExecuteMessage = (message: object): message is ExecuteMessage =>
export class PluginManager { export class PluginManager {
private readonly serverAddOns = new Map<string, ServerAddOn>(); private readonly serverAddOns = new Map<string, ServerAddOn>();
constructor(private readonly flipperServer: FlipperServerForServerAddOn) {}
async start() { async start() {
// This needn't happen immediately and is (light) I/O work. // This needn't happen immediately and is (light) I/O work.
setTimeout(() => { setTimeout(() => {
@@ -183,8 +186,11 @@ export class PluginManager {
return; return;
} }
const newServerAddOn = await ServerAddOn.start(pluginName, owner, () => const newServerAddOn = await ServerAddOn.start(
this.serverAddOns.delete(pluginName), pluginName,
owner,
() => this.serverAddOns.delete(pluginName),
this.flipperServer,
); );
this.serverAddOns.set(pluginName, newServerAddOn); this.serverAddOns.set(pluginName, newServerAddOn);
} }

View File

@@ -9,13 +9,17 @@
import assert from 'assert'; import assert from 'assert';
import {assertNotNull} from '../comms/Utilities'; import {assertNotNull} from '../comms/Utilities';
import {ServerAddOnDesktopToModuleConnection} from './ServerAddOnDesktopToModuleConnection'; import {
ServerAddOnDesktopToModuleConnection,
FlipperServerForServerAddOn,
} from './ServerAddOnDesktopToModuleConnection';
import {ServerAddOnModuleToDesktopConnection} from './ServerAddOnModuleToDesktopConnection'; import {ServerAddOnModuleToDesktopConnection} from './ServerAddOnModuleToDesktopConnection';
type ServerAddOnCleanup = () => Promise<void>; type ServerAddOnCleanup = () => Promise<void>;
interface ServerAddOnModule { interface ServerAddOnModule {
serverAddOn?: ( serverAddOn?: (
connection: ServerAddOnModuleToDesktopConnection, connection: ServerAddOnModuleToDesktopConnection,
{flipperServer}: {flipperServer: FlipperServerForServerAddOn},
) => Promise<ServerAddOnCleanup>; ) => Promise<ServerAddOnCleanup>;
} }
@@ -34,13 +38,14 @@ export class ServerAddOn {
public readonly connection: ServerAddOnDesktopToModuleConnection, public readonly connection: ServerAddOnDesktopToModuleConnection,
initialOwner: string, initialOwner: string,
) { ) {
this.owners = new Set(initialOwner); this.owners = new Set([initialOwner]);
} }
static async start( static async start(
pluginName: string, pluginName: string,
initialOwner: string, initialOwner: string,
onStop: () => void, onStop: () => void,
flipperServer: FlipperServerForServerAddOn,
): Promise<ServerAddOn> { ): Promise<ServerAddOn> {
console.info('ServerAddOn.start', pluginName); console.info('ServerAddOn.start', pluginName);
@@ -54,7 +59,9 @@ export class ServerAddOn {
const serverAddOnModuleToDesktopConnection = const serverAddOnModuleToDesktopConnection =
new ServerAddOnModuleToDesktopConnection(); new ServerAddOnModuleToDesktopConnection();
const cleanup = await serverAddOn(serverAddOnModuleToDesktopConnection); const cleanup = await serverAddOn(serverAddOnModuleToDesktopConnection, {
flipperServer,
});
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}.`,
@@ -65,12 +72,15 @@ export class ServerAddOn {
await cleanup(); await cleanup();
}; };
const desktopToModuleConnection = new ServerAddOnDesktopToModuleConnection(
serverAddOnModuleToDesktopConnection,
flipperServer,
);
return new ServerAddOn( return new ServerAddOn(
pluginName, pluginName,
onStopCombined, onStopCombined,
new ServerAddOnDesktopToModuleConnection( desktopToModuleConnection,
serverAddOnModuleToDesktopConnection,
),
initialOwner, initialOwner,
); );
} }

View File

@@ -8,13 +8,37 @@
*/ */
import assert from 'assert'; import assert from 'assert';
import {ClientResponseType, ExecuteMessage} from 'flipper-common'; import {
import {ServerAddOnModuleToDesktopConnection} from './ServerAddOnModuleToDesktopConnection'; ClientResponseType,
ExecuteMessage,
FlipperServer,
FlipperServerEvents,
} from 'flipper-common';
import {ServerDevice} from '../devices/ServerDevice';
import {
ServerAddOnModuleToDesktopConnection,
ServerAddOnModuleToDesktopConnectionEvents,
} from './ServerAddOnModuleToDesktopConnection';
export interface FlipperServerForServerAddOn extends FlipperServer {
emit(
event: 'plugins-server-add-on-message',
payload: FlipperServerEvents['plugins-server-add-on-message'],
): void;
registerDevice(device: ServerDevice): void;
unregisterDevice(serial: string): void;
getDevice(serial: string): ServerDevice;
getDeviceSerials(): string[];
getDevices(): ServerDevice[];
}
export class ServerAddOnDesktopToModuleConnection { export class ServerAddOnDesktopToModuleConnection {
constructor( constructor(
private readonly moduleToDesktopConnection: ServerAddOnModuleToDesktopConnection, private readonly moduleToDesktopConnection: ServerAddOnModuleToDesktopConnection,
) {} private readonly flipperServer: FlipperServerForServerAddOn,
) {
this.subscribeToMessagesFromServerAddOn();
}
async sendExpectResponse({ async sendExpectResponse({
method, method,
@@ -34,4 +58,15 @@ export class ServerAddOnDesktopToModuleConnection {
length, length,
}; };
} }
private subscribeToMessagesFromServerAddOn() {
const event = 'message';
const onMessage = (
message: ServerAddOnModuleToDesktopConnectionEvents[typeof event],
) => {
this.flipperServer.emit('plugins-server-add-on-message', message);
};
this.moduleToDesktopConnection.on(event, onMessage);
}
} }

View File

@@ -7,7 +7,8 @@
* @format * @format
*/ */
import {ResponseMessage, ClientErrorType} from 'flipper-common'; import EventEmitter from 'events';
import {ResponseMessage, ClientErrorType, ExecuteMessage} from 'flipper-common';
import {safeJSONStringify} from '../utils/safeJSONStringify'; import {safeJSONStringify} from '../utils/safeJSONStringify';
// TODO: Share with js-flipper? Is it worth it? // TODO: Share with js-flipper? Is it worth it?
@@ -24,11 +25,24 @@ type FlipperPluginReceiver = (
data: any, data: any,
) => FlipperPluginReceiverRes | Promise<FlipperPluginReceiverRes>; ) => FlipperPluginReceiverRes | Promise<FlipperPluginReceiverRes>;
export class ServerAddOnModuleToDesktopConnection { export type ServerAddOnModuleToDesktopConnectionEvents = {
message: ExecuteMessage;
};
export class ServerAddOnModuleToDesktopConnection extends EventEmitter {
private subscriptions: Map<string, FlipperPluginReceiver> = new Map(); private subscriptions: Map<string, FlipperPluginReceiver> = new Map();
send() { send(method: string, params: unknown) {
// TODO: Implement me const event = 'message';
const message: ServerAddOnModuleToDesktopConnectionEvents[typeof event] = {
method: 'execute',
params: {
method,
params,
api: '', // TODO: Consider using here pluginId and validate it
},
};
this.emit('message', message);
} }
receive(method: string, receiver: FlipperPluginReceiver) { receive(method: string, receiver: FlipperPluginReceiver) {