Files
flipper/desktop/flipper-server-core/src/plugins/ServerAddOn.tsx
Andrey Goncharov 81d0057a8d Fix potential race conditions for starting/stopping server add-ons
Reviewed By: mweststrate

Differential Revision: D34301593

fbshipit-source-id: 2950de8a8567318cd3e87eff176657df5ba8fd1b
2022-02-28 03:50:34 -08:00

120 lines
3.1 KiB
TypeScript

/**
* 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 {assertNotNull} from '../comms/Utilities';
import {
FlipperServerForServerAddOn,
ServerAddOnCleanup,
ServerAddOn as ServerAddOnFn,
ServerAddOnStartDetails,
} from 'flipper-common';
import {ServerAddOnDesktopToModuleConnection} from './ServerAddOnDesktopToModuleConnection';
import {ServerAddOnModuleToDesktopConnection} from './ServerAddOnModuleToDesktopConnection';
// @ts-ignore
import defaultPlugins from '../defaultPlugins';
interface ServerAddOnModule {
default: ServerAddOnFn;
}
const loadPlugin = (
pluginName: string,
details: ServerAddOnStartDetails,
): ServerAddOnModule => {
console.debug('loadPlugin', pluginName, details);
if (details.isBundled) {
const bundledPlugin = defaultPlugins[pluginName];
assertNotNull(
bundledPlugin,
`loadPlugin (isBundled = true) -> plugin ${pluginName} not found.`,
);
return bundledPlugin;
}
assertNotNull(
details.path,
`loadPlugin (isBundled = false) -> server add-on path is empty plugin ${pluginName}.`,
);
// eslint-disable-next-line no-eval
const serverAddOnModule = eval(`require("${details.path}")`);
return serverAddOnModule;
};
export class ServerAddOn {
private owners: Set<string>;
constructor(
public readonly pluginName: string,
private readonly cleanup: ServerAddOnCleanup,
public readonly connection: ServerAddOnDesktopToModuleConnection,
initialOwner: string,
) {
this.owners = new Set([initialOwner]);
}
static async start(
pluginName: string,
details: ServerAddOnStartDetails,
initialOwner: string,
flipperServer: FlipperServerForServerAddOn,
): Promise<ServerAddOn> {
console.info('ServerAddOn.start', pluginName, details);
const {default: serverAddOn} = loadPlugin(pluginName, details);
assertNotNull(serverAddOn);
assert(
typeof serverAddOn === 'function',
`ServerAddOn ${pluginName} must export "serverAddOn" function as a default export.`,
);
const serverAddOnModuleToDesktopConnection =
new ServerAddOnModuleToDesktopConnection(pluginName);
const cleanup = await serverAddOn(serverAddOnModuleToDesktopConnection, {
flipperServer,
});
assert(
typeof cleanup === 'function',
`ServerAddOn ${pluginName} must return a clean up function, instead it returned ${typeof cleanup}.`,
);
const desktopToModuleConnection = new ServerAddOnDesktopToModuleConnection(
serverAddOnModuleToDesktopConnection,
flipperServer,
);
return new ServerAddOn(
pluginName,
cleanup,
desktopToModuleConnection,
initialOwner,
);
}
addOwner(owner: string) {
this.owners.add(owner);
}
removeOwner(owner: string) {
const ownerExisted = this.owners.delete(owner);
if (!this.owners.size && ownerExisted) {
return this.stop();
}
}
async stop() {
console.info('ServerAddOn.stop', this.pluginName);
await this.cleanup();
}
}