diff --git a/desktop/flipper-common/src/ServerAddOn.tsx b/desktop/flipper-common/src/ServerAddOn.tsx new file mode 100644 index 000000000..2795083ef --- /dev/null +++ b/desktop/flipper-common/src/ServerAddOn.tsx @@ -0,0 +1,15 @@ +/** + * 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 {FlipperServerCommands} from './server-types'; + +export interface ServerAddOnControls { + start: FlipperServerCommands['plugins-server-add-on-start']; + stop: FlipperServerCommands['plugins-server-add-on-stop']; +} diff --git a/desktop/flipper-common/src/index.tsx b/desktop/flipper-common/src/index.tsx index 10f44dbf0..b8e6aa16a 100644 --- a/desktop/flipper-common/src/index.tsx +++ b/desktop/flipper-common/src/index.tsx @@ -17,6 +17,7 @@ export { NoopLogger, } from './utils/Logger'; export * from './server-types'; +export * from './ServerAddOn'; export {sleep} from './utils/sleep'; export {timeout} from './utils/timeout'; export {isTest} from './utils/isTest'; diff --git a/desktop/flipper-plugin/src/plugin/DevicePlugin.tsx b/desktop/flipper-plugin/src/plugin/DevicePlugin.tsx index 793cb0e19..7064556d7 100644 --- a/desktop/flipper-plugin/src/plugin/DevicePlugin.tsx +++ b/desktop/flipper-plugin/src/plugin/DevicePlugin.tsx @@ -11,7 +11,13 @@ import {SandyPluginDefinition} from './SandyPluginDefinition'; import {BasePluginInstance, BasePluginClient} from './PluginBase'; import {FlipperLib} from './FlipperLib'; import {Atom, ReadOnlyAtom} from '../state/atom'; -import {DeviceOS, DeviceType, DeviceLogEntry, CrashLog} from 'flipper-common'; +import { + DeviceOS, + DeviceType, + DeviceLogEntry, + CrashLog, + ServerAddOnControls, +} from 'flipper-common'; export type DeviceLogListener = (entry: DeviceLogEntry) => void; export type CrashLogListener = (crash: CrashLog) => void; @@ -59,6 +65,7 @@ export class SandyDevicePluginInstance extends BasePluginInstance { readonly client: DevicePluginClient; constructor( + private readonly serverAddOnControls: ServerAddOnControls, flipperLib: FlipperLib, definition: SandyPluginDefinition, device: Device, @@ -79,9 +86,33 @@ export class SandyDevicePluginInstance extends BasePluginInstance { this.initializePlugin(() => definition.asDevicePluginModule().devicePlugin(this.client), ); + this.startServerAddOn(); } toJSON() { return '[SandyDevicePluginInstance]'; } + + destroy() { + this.stopServerAddOn(); + super.destroy(); + } + + private startServerAddOn() { + const {serverAddOn, name} = this.definition.details; + if (serverAddOn) { + this.serverAddOnControls.start(name).catch((e) => { + console.warn('Failed to start a server add on', name, e); + }); + } + } + + private stopServerAddOn() { + const {serverAddOn, name} = this.definition.details; + if (serverAddOn) { + this.serverAddOnControls.stop(name).catch((e) => { + console.warn('Failed to start a server add on', name, e); + }); + } + } } diff --git a/desktop/flipper-plugin/src/plugin/Plugin.tsx b/desktop/flipper-plugin/src/plugin/Plugin.tsx index 89e873246..142004a8e 100644 --- a/desktop/flipper-plugin/src/plugin/Plugin.tsx +++ b/desktop/flipper-plugin/src/plugin/Plugin.tsx @@ -13,6 +13,7 @@ import {FlipperLib} from './FlipperLib'; import {Device} from './DevicePlugin'; import {batched} from '../state/batch'; import {Atom, createState, ReadOnlyAtom} from '../state/atom'; +import {ServerAddOnControls} from 'flipper-common'; type EventsContract = Record; type MethodsContract = Record Promise>; @@ -141,6 +142,7 @@ export class SandyPluginInstance extends BasePluginInstance { readonly connected = createState(false); constructor( + private readonly serverAddOnControls: ServerAddOnControls, flipperLib: FlipperLib, definition: SandyPluginDefinition, realClient: RealFlipperClient, @@ -229,6 +231,13 @@ export class SandyPluginInstance extends BasePluginInstance { connect() { this.assertNotDestroyed(); if (!this.connected.get()) { + const {serverAddOn, name} = this.definition.details; + if (serverAddOn) { + this.serverAddOnControls.start(name).catch((e) => { + console.warn('Failed to start a server add on', name, e); + }); + } + this.connected.set(true); this.events.emit('connect'); } @@ -237,6 +246,13 @@ export class SandyPluginInstance extends BasePluginInstance { disconnect() { this.assertNotDestroyed(); if (this.connected.get()) { + const {serverAddOn, name} = this.definition.details; + if (serverAddOn) { + this.serverAddOnControls.stop(name).catch((e) => { + console.warn('Failed to stop a server add on', name, e); + }); + } + this.connected.set(false); this.events.emit('disconnect'); } diff --git a/desktop/flipper-plugin/src/test-utils/test-utils.tsx b/desktop/flipper-plugin/src/test-utils/test-utils.tsx index c0f163caa..9296afa80 100644 --- a/desktop/flipper-plugin/src/test-utils/test-utils.tsx +++ b/desktop/flipper-plugin/src/test-utils/test-utils.tsx @@ -14,6 +14,7 @@ import { BundledPluginDetails, fsConstants, InstalledPluginDetails, + ServerAddOnControls, } from 'flipper-common'; import { @@ -116,6 +117,11 @@ interface BasePluginResult { * Trigger menu entry by label */ triggerMenuEntry(label: string): void; + // TODO: Refine server add-on test methods + /** + * Communication with a server add-on + */ + serverAddOnControls: ServerAddOnControls; } interface StartPluginResult> @@ -254,7 +260,10 @@ export function startPlugin>( }, }; + const serverAddOnControls = createServerAddOnControlsMock(); + const pluginInstance = new SandyPluginInstance( + serverAddOnControls, flipperUtils, definition, fakeFlipperClient, @@ -263,7 +272,7 @@ export function startPlugin>( ); const res: StartPluginResult = { - ...createBasePluginResult(pluginInstance), + ...createBasePluginResult(pluginInstance, serverAddOnControls), instance: pluginInstance.instanceApi, module, connect: () => pluginInstance.connect(), @@ -282,6 +291,7 @@ export function startPlugin>( pluginInstance.receiveMessages(messages as any); }); }, + serverAddOnControls, }; (res as any)._backingInstance = pluginInstance; // we start activated @@ -337,7 +347,9 @@ export function startDevicePlugin( const flipperLib = createMockFlipperLib(options); const testDevice = createMockDevice(options); + const serverAddOnControls = createServerAddOnControlsMock(); const pluginInstance = new SandyDevicePluginInstance( + serverAddOnControls, flipperLib, definition, testDevice, @@ -346,7 +358,7 @@ export function startDevicePlugin( ); const res: StartDevicePluginResult = { - ...createBasePluginResult(pluginInstance), + ...createBasePluginResult(pluginInstance, serverAddOnControls), module, instance: pluginInstance.instanceApi, sendLogEntry: (entry) => { @@ -444,6 +456,7 @@ export function createMockFlipperLib(options?: StartPluginOptions): FlipperLib { function createBasePluginResult( pluginInstance: BasePluginInstance, + serverAddOnControls: ServerAddOnControls, ): BasePluginResult { return { flipperLib: pluginInstance.flipperLib, @@ -469,6 +482,7 @@ function createBasePluginResult( } entry.handler(); }, + serverAddOnControls, }; } @@ -628,3 +642,10 @@ export function createFlipperServerMock( close: createStubFunction(), }; } + +function createServerAddOnControlsMock(): ServerAddOnControls { + return { + start: createStubFunction(), + stop: createStubFunction(), + }; +} diff --git a/desktop/flipper-ui-core/src/Client.tsx b/desktop/flipper-ui-core/src/Client.tsx index 127579e04..3ff467e5f 100644 --- a/desktop/flipper-ui-core/src/Client.tsx +++ b/desktop/flipper-ui-core/src/Client.tsx @@ -12,7 +12,7 @@ import {PluginDefinition} from './plugin'; import BaseDevice from './devices/BaseDevice'; -import {Logger} from 'flipper-common'; +import {Logger, FlipperServer, ServerAddOnControls} from 'flipper-common'; import {Store} from './reducers/index'; import { reportPluginFailures, @@ -46,6 +46,7 @@ import { registerFlipperDebugMessage, } from './chrome/FlipperMessages'; import {waitFor} from './utils/waitFor'; +import {createServerAddOnControls} from './utils/createServerAddOnControls'; type Plugins = Set; type PluginsArr = Array; @@ -124,6 +125,8 @@ export default class Client extends EventEmitter { } > = {}; sandyPluginStates = new Map(); + private readonly serverAddOnControls: ServerAddOnControls; + private readonly flipperServer: Pick; constructor( id: string, @@ -133,6 +136,7 @@ export default class Client extends EventEmitter { store: Store, plugins: Plugins | null | undefined, device: BaseDevice, + flipperServer: Pick, ) { super(); this.connected.set(!!conn); @@ -148,6 +152,8 @@ export default class Client extends EventEmitter { this.broadcastCallbacks = new Map(); this.activePlugins = new Set(); this.device = device; + this.flipperServer = flipperServer; + this.serverAddOnControls = createServerAddOnControls(this.flipperServer); } supportsPlugin(pluginId: string): boolean { @@ -218,6 +224,7 @@ export default class Client extends EventEmitter { this.sandyPluginStates.set( plugin.id, new _SandyPluginInstance( + this.serverAddOnControls, getFlipperLib(), plugin, this, diff --git a/desktop/flipper-ui-core/src/__tests__/test-utils/MockFlipper.tsx b/desktop/flipper-ui-core/src/__tests__/test-utils/MockFlipper.tsx index cdac92ef9..80ef54f68 100644 --- a/desktop/flipper-ui-core/src/__tests__/test-utils/MockFlipper.tsx +++ b/desktop/flipper-ui-core/src/__tests__/test-utils/MockFlipper.tsx @@ -198,6 +198,7 @@ export default class MockFlipper { this._store, new Set(supportedPlugins), device, + this.flipperServer, ); client.rawCall = async ( method: string, diff --git a/desktop/flipper-ui-core/src/devices/BaseDevice.tsx b/desktop/flipper-ui-core/src/devices/BaseDevice.tsx index 5a61368a5..166f8a28b 100644 --- a/desktop/flipper-ui-core/src/devices/BaseDevice.tsx +++ b/desktop/flipper-ui-core/src/devices/BaseDevice.tsx @@ -24,10 +24,12 @@ import { DeviceDescription, FlipperServer, CrashLog, + ServerAddOnControls, } from 'flipper-common'; import {DeviceSpec, PluginDetails} from 'flipper-common'; import {getPluginKey} from '../utils/pluginKey'; import {Base64} from 'js-base64'; +import {createServerAddOnControls} from '../utils/createServerAddOnControls'; type PluginDefinition = _SandyPluginDefinition; type PluginMap = Map; @@ -45,10 +47,12 @@ export default class BaseDevice implements Device { flipperServer: FlipperServer; isArchived = false; hasDevicePlugins = false; // true if there are device plugins for this device (not necessarily enabled) + private readonly serverAddOnControls: ServerAddOnControls; constructor(flipperServer: FlipperServer, description: DeviceDescription) { this.flipperServer = flipperServer; this.description = description; + this.serverAddOnControls = createServerAddOnControls(this.flipperServer); } get isConnected(): boolean { @@ -349,6 +353,7 @@ export default class BaseDevice implements Device { this.sandyPluginStates.set( plugin.id, new _SandyDevicePluginInstance( + this.serverAddOnControls, getFlipperLib(), plugin, this, diff --git a/desktop/flipper-ui-core/src/dispatcher/flipperServer.tsx b/desktop/flipper-ui-core/src/dispatcher/flipperServer.tsx index f07c8c901..ab5f5be0d 100644 --- a/desktop/flipper-ui-core/src/dispatcher/flipperServer.tsx +++ b/desktop/flipper-ui-core/src/dispatcher/flipperServer.tsx @@ -301,6 +301,7 @@ export async function handleClientConnected( store, undefined, device, + server, ); console.debug( diff --git a/desktop/flipper-ui-core/src/utils/__tests__/exportData.node.tsx b/desktop/flipper-ui-core/src/utils/__tests__/exportData.node.tsx index 2f099ab0f..f5d064113 100644 --- a/desktop/flipper-ui-core/src/utils/__tests__/exportData.node.tsx +++ b/desktop/flipper-ui-core/src/utils/__tests__/exportData.node.tsx @@ -38,6 +38,7 @@ import { import {selectPlugin, getAllClients} from '../../reducers/connections'; import {TestIdler} from '../Idler'; import {TestDevice} from '../../devices/TestDevice'; +import {FlipperServer} from 'flipper-common'; const testIdler = new TestIdler(); @@ -65,6 +66,14 @@ const logger = { }; const mockStore = configureStore([])(); +const flipperServer = { + connect: async () => {}, + on: () => {}, + off: () => {}, + close: () => {}, + exec: () => {}, +} as FlipperServer; + function generateNotifications( id: string, title: string, @@ -725,6 +734,7 @@ test('test determinePluginsToProcess for mutilple clients having plugins present mockStore, new Set(['TestPlugin', 'TestDevicePlugin']), device1, + flipperServer, ); const client2 = new Client( generateClientIdentifier(device1, 'app2'), @@ -739,6 +749,7 @@ test('test determinePluginsToProcess for mutilple clients having plugins present mockStore, new Set(['TestDevicePlugin']), device1, + flipperServer, ); const client3 = new Client( generateClientIdentifier(device1, 'app3'), @@ -753,6 +764,7 @@ test('test determinePluginsToProcess for mutilple clients having plugins present mockStore, new Set(['TestPlugin', 'TestDevicePlugin']), device1, + flipperServer, ); const plugins: PluginsState = { clientPlugins: new Map([ @@ -812,6 +824,7 @@ test('test determinePluginsToProcess for no selected plugin present in any clien mockStore, new Set(['TestPlugin', 'TestDevicePlugin']), device1, + flipperServer, ); const client2 = new Client( generateClientIdentifier(device1, 'app2'), @@ -826,6 +839,7 @@ test('test determinePluginsToProcess for no selected plugin present in any clien mockStore, new Set(['TestDevicePlugin']), device1, + flipperServer, ); const plugins: PluginsState = { clientPlugins: new Map([ @@ -868,6 +882,7 @@ test('test determinePluginsToProcess for multiple clients on same device', async mockStore, new Set(['TestPlugin', 'TestDevicePlugin']), device1, + flipperServer, ); const client2 = new Client( generateClientIdentifier(device1, 'app2'), @@ -882,6 +897,7 @@ test('test determinePluginsToProcess for multiple clients on same device', async mockStore, new Set(['TestDevicePlugin']), device1, + flipperServer, ); const plugins: PluginsState = { clientPlugins: new Map([['TestPlugin', TestPlugin]]), @@ -929,6 +945,7 @@ test('test determinePluginsToProcess for multiple clients on different device', mockStore, new Set(['TestPlugin', 'TestDevicePlugin']), device1, + flipperServer, ); const client2Device1 = new Client( generateClientIdentifier(device1, 'app2'), @@ -943,6 +960,7 @@ test('test determinePluginsToProcess for multiple clients on different device', mockStore, new Set(['TestDevicePlugin']), device1, + flipperServer, ); const client1Device2 = new Client( generateClientIdentifier(device2, 'app'), @@ -957,6 +975,7 @@ test('test determinePluginsToProcess for multiple clients on different device', mockStore, new Set(['TestPlugin', 'TestDevicePlugin']), device1, + flipperServer, ); const client2Device2 = new Client( generateClientIdentifier(device2, 'app2'), @@ -971,6 +990,7 @@ test('test determinePluginsToProcess for multiple clients on different device', mockStore, new Set(['TestDevicePlugin']), device1, + flipperServer, ); const plugins: PluginsState = { clientPlugins: new Map([['TestPlugin', TestPlugin]]), @@ -1042,6 +1062,7 @@ test('test determinePluginsToProcess to ignore archived clients', async () => { mockStore, new Set(['TestPlugin', 'TestDevicePlugin']), archivedDevice, + flipperServer, ); const archivedClient = new Client( generateClientIdentifier(archivedDevice, 'app'), @@ -1056,6 +1077,7 @@ test('test determinePluginsToProcess to ignore archived clients', async () => { mockStore, new Set(['TestPlugin', 'TestDevicePlugin']), archivedDevice, + flipperServer, ); const plugins: PluginsState = { clientPlugins: new Map([['TestPlugin', TestPlugin]]), diff --git a/desktop/flipper-ui-core/src/utils/createServerAddOnControls.tsx b/desktop/flipper-ui-core/src/utils/createServerAddOnControls.tsx new file mode 100644 index 000000000..159b0e6df --- /dev/null +++ b/desktop/flipper-ui-core/src/utils/createServerAddOnControls.tsx @@ -0,0 +1,19 @@ +/** + * 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 {FlipperServer, ServerAddOnControls} from 'flipper-common'; + +export const createServerAddOnControls = ( + flipperServer: Pick, +): ServerAddOnControls => ({ + start: (pluginName) => + flipperServer.exec('plugins-server-add-on-start', pluginName), + stop: (pluginName) => + flipperServer.exec('plugins-server-add-on-stop', pluginName), +}); diff --git a/desktop/flipper-ui-core/src/utils/exportData.tsx b/desktop/flipper-ui-core/src/utils/exportData.tsx index 855444f03..2246aed31 100644 --- a/desktop/flipper-ui-core/src/utils/exportData.tsx +++ b/desktop/flipper-ui-core/src/utils/exportData.tsx @@ -575,6 +575,7 @@ export function importDataToStore(source: string, data: string, store: Store) { store, clientPlugins, archivedDevice, + getRenderHostInstance().flipperServer, ).initFromImport(sandyPluginStates), }); });