diff --git a/desktop/app/src/devices/BaseDevice.tsx b/desktop/app/src/devices/BaseDevice.tsx index 423e1221b..ad40e3ec1 100644 --- a/desktop/app/src/devices/BaseDevice.tsx +++ b/desktop/app/src/devices/BaseDevice.tsx @@ -15,7 +15,7 @@ import { SandyDevicePluginInstance, SandyPluginDefinition, } from 'flipper-plugin'; -import {DevicePluginMap} from '../plugin'; +import {DevicePluginMap, FlipperDevicePlugin} from '../plugin'; export type DeviceShell = { stdout: stream.Readable; @@ -179,18 +179,22 @@ export default class BaseDevice { const plugins = Array.from(devicePlugins.values()); plugins.sort(sortPluginsByName); for (const plugin of plugins) { - if (plugin instanceof SandyPluginDefinition) { - if (plugin.asDevicePluginModule().supportsDevice(this as any)) { - this.devicePlugins.push(plugin.id); - this.sandyPluginStates.set( - plugin.id, - new SandyDevicePluginInstance(this, plugin), - ); // TODO: pass initial state if applicable - } - } else { - if (plugin.supportsDevice(this)) { - this.devicePlugins.push(plugin.id); - } + this.loadDevicePlugin(plugin); + } + } + + loadDevicePlugin(plugin: typeof FlipperDevicePlugin | SandyPluginDefinition) { + if (plugin instanceof SandyPluginDefinition) { + if (plugin.asDevicePluginModule().supportsDevice(this as any)) { + this.devicePlugins.push(plugin.id); + this.sandyPluginStates.set( + plugin.id, + new SandyDevicePluginInstance(this, plugin), + ); // TODO T70582933: pass initial state if applicable + } + } else { + if (plugin.supportsDevice(this)) { + this.devicePlugins.push(plugin.id); } } } diff --git a/desktop/app/src/dispatcher/plugins.tsx b/desktop/app/src/dispatcher/plugins.tsx index 51d6b6a7c..44cb1651b 100644 --- a/desktop/app/src/dispatcher/plugins.tsx +++ b/desktop/app/src/dispatcher/plugins.tsx @@ -277,7 +277,6 @@ const requirePluginInternal = ( if (pluginDetails.flipperSDKVersion) { // Sandy plugin - // TODO: suppor device Plugins T68738317 return new SandyPluginDefinition(pluginDetails, plugin); } else { // classic plugin diff --git a/desktop/app/src/reducers/connections.tsx b/desktop/app/src/reducers/connections.tsx index 62ce3415a..f61a2a914 100644 --- a/desktop/app/src/reducers/connections.tsx +++ b/desktop/app/src/reducers/connections.tsx @@ -23,7 +23,7 @@ const WelcomeScreen = isHeadless() import NotificationScreen from '../chrome/NotificationScreen'; import SupportRequestFormV2 from '../fb-stubs/SupportRequestFormV2'; import SupportRequestDetails from '../fb-stubs/SupportRequestDetails'; -import {getPluginKey} from '../utils/pluginUtils'; +import {getPluginKey, isDevicePluginDefinition} from '../utils/pluginUtils'; import {deconstructClientId} from '../utils/clientUtils'; import {FlipperDevicePlugin, PluginDefinition, isSandyPlugin} from '../plugin'; import {RegisterPluginAction} from './plugins'; @@ -393,20 +393,10 @@ export default (state: State = INITAL_STATE, action: Actions): State => { // plugins are registered after creating the base devices, so update them const plugins = action.payload; plugins.forEach((plugin) => { - // TODO: T68738317 support sandy device plugin - if ( - !isSandyPlugin(plugin) && - plugin.prototype instanceof FlipperDevicePlugin - ) { + if (isDevicePluginDefinition(plugin)) { // smell: devices are mutable state.devices.forEach((device) => { - // @ts-ignore - if (plugin.supportsDevice(device)) { - device.devicePlugins = [ - ...(device.devicePlugins || []), - plugin.id, - ]; - } + device.loadDevicePlugin(plugin); }); } }); diff --git a/desktop/app/src/utils/exportData.tsx b/desktop/app/src/utils/exportData.tsx index d251477f8..d84219d96 100644 --- a/desktop/app/src/utils/exportData.tsx +++ b/desktop/app/src/utils/exportData.tsx @@ -662,7 +662,7 @@ async function getStoreExport( const newPluginState = metadata.pluginStates; // TODO: support async export like fetchMetaData T68683476 - // TODO: support device plugins T68738317 + // TODO: support device plugins T70582933 const pluginStates2 = pluginsToProcess ? exportSandyPluginStates(pluginsToProcess) : {}; diff --git a/desktop/app/src/utils/messageQueue.tsx b/desktop/app/src/utils/messageQueue.tsx index bbe0dee14..b6d9c9d0a 100644 --- a/desktop/app/src/utils/messageQueue.tsx +++ b/desktop/app/src/utils/messageQueue.tsx @@ -128,7 +128,6 @@ export function processMessagesLater( case isSelected && getPendingMessages(store, pluginKey).length === 0: processMessagesImmediately(store, pluginKey, plugin, messages); break; - // TODO: support SandyDevicePlugin T68738317 case isSelected: case plugin instanceof SandyPluginInstance: case plugin instanceof FlipperDevicePlugin: diff --git a/desktop/flipper-plugin/src/__tests__/TestPlugin.tsx b/desktop/flipper-plugin/src/__tests__/TestPlugin.tsx index 32a0f49f3..f8d387ae9 100644 --- a/desktop/flipper-plugin/src/__tests__/TestPlugin.tsx +++ b/desktop/flipper-plugin/src/__tests__/TestPlugin.tsx @@ -8,7 +8,7 @@ */ import * as React from 'react'; -import {FlipperClient} from '../plugin/Plugin'; +import {PluginClient} from '../plugin/Plugin'; import {usePlugin} from '../plugin/PluginContext'; import {createState, useValue} from '../state/atom'; @@ -22,7 +22,7 @@ type Methods = { currentState(params: {since: number}): Promise; }; -export function plugin(client: FlipperClient) { +export function plugin(client: PluginClient) { const connectStub = jest.fn(); const disconnectStub = jest.fn(); const activateStub = jest.fn(); diff --git a/desktop/flipper-plugin/src/__tests__/test-utils.node.tsx b/desktop/flipper-plugin/src/__tests__/test-utils.node.tsx index f56cbfdac..0aaa3e323 100644 --- a/desktop/flipper-plugin/src/__tests__/test-utils.node.tsx +++ b/desktop/flipper-plugin/src/__tests__/test-utils.node.tsx @@ -10,7 +10,7 @@ import * as TestUtils from '../test-utils/test-utils'; import * as testPlugin from './TestPlugin'; import {createState} from '../state/atom'; -import {FlipperClient} from '../plugin/Plugin'; +import {PluginClient} from '../plugin/Plugin'; import {DevicePluginClient} from '../plugin/DevicePlugin'; test('it can start a plugin and lifecycle events', () => { @@ -246,7 +246,7 @@ test('plugins cannot use a persist key twice', async () => { test('plugins can receive deeplinks', async () => { const plugin = TestUtils.startPlugin({ - plugin(client: FlipperClient) { + plugin(client: PluginClient) { client.onDeepLink((deepLink) => { if (typeof deepLink === 'string') { field1.set(deepLink); diff --git a/desktop/flipper-plugin/src/index.ts b/desktop/flipper-plugin/src/index.ts index cb9638494..6b0486f17 100644 --- a/desktop/flipper-plugin/src/index.ts +++ b/desktop/flipper-plugin/src/index.ts @@ -9,7 +9,10 @@ import * as TestUtilites from './test-utils/test-utils'; -export {SandyPluginInstance, FlipperClient} from './plugin/Plugin'; +export { + SandyPluginInstance, + PluginClient as FlipperClient, +} from './plugin/Plugin'; export { Device, DeviceLogEntry, diff --git a/desktop/flipper-plugin/src/plugin/DevicePlugin.tsx b/desktop/flipper-plugin/src/plugin/DevicePlugin.tsx index 70336869d..aabf516e6 100644 --- a/desktop/flipper-plugin/src/plugin/DevicePlugin.tsx +++ b/desktop/flipper-plugin/src/plugin/DevicePlugin.tsx @@ -8,9 +8,7 @@ */ import {SandyPluginDefinition} from './SandyPluginDefinition'; -import {EventEmitter} from 'events'; -import {Atom} from '../state/atom'; -import {setCurrentPluginInstance} from './Plugin'; +import {BasePluginInstance, BasePluginClient} from './PluginBase'; export type DeviceLogListener = (entry: DeviceLogEntry) => void; @@ -42,31 +40,13 @@ export type DevicePluginPredicate = (device: Device) => boolean; export type DevicePluginFactory = (client: DevicePluginClient) => object; -// TODO: better name? -export interface DevicePluginClient { +export interface DevicePluginClient extends BasePluginClient { readonly device: Device; - - /** - * the onDestroy event is fired whenever a device is unloaded from Flipper, or a plugin is disabled. - */ - onDestroy(cb: () => void): void; - - /** - * the onActivate event is fired whenever the plugin is actived in the UI - */ - onActivate(cb: () => void): void; - - /** - * The counterpart of the `onActivate` handler. - */ - onDeactivate(cb: () => void): void; - - /** - * Triggered when this plugin is opened through a deeplink - */ - onDeepLink(cb: (deepLink: unknown) => void): void; } +/** + * Wrapper interface around BaseDevice in Flipper + */ export interface RealFlipperDevice { isArchived: boolean; addLogListener(callback: DeviceLogListener): Symbol; @@ -74,35 +54,20 @@ export interface RealFlipperDevice { addLogEntry(entry: DeviceLogEntry): void; } -export class SandyDevicePluginInstance { +export class SandyDevicePluginInstance extends BasePluginInstance { static is(thing: any): thing is SandyDevicePluginInstance { return thing instanceof SandyDevicePluginInstance; } /** client that is bound to this instance */ client: DevicePluginClient; - /** the original plugin definition */ - definition: SandyPluginDefinition; - /** the plugin instance api as used inside components and such */ - instanceApi: any; - - activated = false; - destroyed = false; - events = new EventEmitter(); - - // temporarily field that is used during deserialization - initialStates?: Record; - // all the atoms that should be serialized when making an export / import - rootStates: Record> = {}; - // last seen deeplink - lastDeeplink?: any; constructor( realDevice: RealFlipperDevice, definition: SandyPluginDefinition, initialStates?: Record, ) { - this.definition = definition; + super(definition, initialStates); const device: Device = { get isArchived() { return realDevice.isArchived; @@ -115,84 +80,15 @@ export class SandyDevicePluginInstance { }, }; this.client = { + ...this.createBasePluginClient(), device, - onDestroy: (cb) => { - this.events.on('destroy', cb); - }, - onActivate: (cb) => { - this.events.on('activate', cb); - }, - onDeactivate: (cb) => { - this.events.on('deactivate', cb); - }, - onDeepLink: (callback) => { - this.events.on('deeplink', callback); - }, }; - setCurrentPluginInstance(this); - this.initialStates = initialStates; - try { - this.instanceApi = definition - .asDevicePluginModule() - .devicePlugin(this.client); - } finally { - this.initialStates = undefined; - setCurrentPluginInstance(undefined); - } - } - - // the plugin is selected in the UI - activate() { - this.assertNotDestroyed(); - if (!this.activated) { - this.activated = true; - this.events.emit('activate'); - } - } - - deactivate() { - if (this.destroyed) { - return; - } - if (this.activated) { - this.lastDeeplink = undefined; - this.activated = false; - this.events.emit('deactivate'); - } - } - - destroy() { - this.assertNotDestroyed(); - this.deactivate(); - this.events.emit('destroy'); - this.destroyed = true; + this.initializePlugin(() => + definition.asDevicePluginModule().devicePlugin(this.client), + ); } toJSON() { return '[SandyDevicePluginInstance]'; } - - triggerDeepLink(deepLink: unknown) { - this.assertNotDestroyed(); - if (deepLink !== this.lastDeeplink) { - this.lastDeeplink = deepLink; - this.events.emit('deeplink', deepLink); - } - } - - exportState() { - return Object.fromEntries( - Object.entries(this.rootStates).map(([key, atom]) => [key, atom.get()]), - ); - } - - isPersistable(): boolean { - return Object.keys(this.rootStates).length > 0; - } - - private assertNotDestroyed() { - if (this.destroyed) { - throw new Error('Plugin has been destroyed already'); - } - } } diff --git a/desktop/flipper-plugin/src/plugin/Plugin.tsx b/desktop/flipper-plugin/src/plugin/Plugin.tsx index f7f967fca..70194db43 100644 --- a/desktop/flipper-plugin/src/plugin/Plugin.tsx +++ b/desktop/flipper-plugin/src/plugin/Plugin.tsx @@ -8,9 +8,7 @@ */ import {SandyPluginDefinition} from './SandyPluginDefinition'; -import {EventEmitter} from 'events'; -import {Atom} from '../state/atom'; -import {SandyDevicePluginInstance} from './DevicePlugin'; +import {BasePluginInstance, BasePluginClient} from './PluginBase'; type EventsContract = Record; type MethodsContract = Record Promise>; @@ -23,25 +21,10 @@ type Message = { /** * API available to a plugin factory */ -export interface FlipperClient< +export interface PluginClient< Events extends EventsContract = {}, Methods extends MethodsContract = {} -> { - /** - * the onDestroy event is fired whenever a client is unloaded from Flipper, or a plugin is disabled. - */ - onDestroy(cb: () => void): void; - - /** - * the onActivate event is fired whenever the plugin is actived in the UI - */ - onActivate(cb: () => void): void; - - /** - * The counterpart of the `onActivate` handler. - */ - onDeactivate(cb: () => void): void; - +> extends BasePluginClient { /** * the onConnect event is fired whenever the plugin is connected to it's counter part on the device. * For most plugins this event is fired if the user selects the plugin, @@ -57,11 +40,6 @@ export interface FlipperClient< */ onDisconnect(cb: () => void): void; - /** - * Triggered when this plugin is opened through a deeplink - */ - onDeepLink(cb: (deepLink: unknown) => void): void; - /** * Send a message to the connected client */ @@ -101,26 +79,11 @@ export interface RealFlipperClient { export type PluginFactory< Events extends EventsContract, Methods extends MethodsContract -> = (client: FlipperClient) => object; +> = (client: PluginClient) => object; export type FlipperPluginComponent = React.FC<{}>; -let currentPluginInstance: - | SandyPluginInstance - | SandyDevicePluginInstance - | undefined = undefined; - -export function setCurrentPluginInstance( - instance: typeof currentPluginInstance, -) { - currentPluginInstance = instance; -} - -export function getCurrentPluginInstance(): typeof currentPluginInstance { - return currentPluginInstance; -} - -export class SandyPluginInstance { +export class SandyPluginInstance extends BasePluginInstance { static is(thing: any): thing is SandyPluginInstance { return thing instanceof SandyPluginInstance; } @@ -128,41 +91,20 @@ export class SandyPluginInstance { /** base client provided by Flipper */ realClient: RealFlipperClient; /** client that is bound to this instance */ - client: FlipperClient; - /** the original plugin definition */ - definition: SandyPluginDefinition; - /** the plugin instance api as used inside components and such */ - instanceApi: any; - - activated = false; + client: PluginClient; + /** connection alive? */ connected = false; - destroyed = false; - events = new EventEmitter(); - - // temporarily field that is used during deserialization - initialStates?: Record; - // all the atoms that should be serialized when making an export / import - rootStates: Record> = {}; - // last seen deeplink - lastDeeplink?: any; constructor( realClient: RealFlipperClient, definition: SandyPluginDefinition, initialStates?: Record, ) { + super(definition, initialStates); this.realClient = realClient; this.definition = definition; this.client = { - onDestroy: (cb) => { - this.events.on('destroy', cb); - }, - onActivate: (cb) => { - this.events.on('activate', cb); - }, - onDeactivate: (cb) => { - this.events.on('deactivate', cb); - }, + ...this.createBasePluginClient(), onConnect: (cb) => { this.events.on('connect', cb); }, @@ -181,45 +123,24 @@ export class SandyPluginInstance { onMessage: (event, callback) => { this.events.on('event-' + event, callback); }, - onDeepLink: (callback) => { - this.events.on('deeplink', callback); - }, }; - setCurrentPluginInstance(this); - this.initialStates = initialStates; - try { - this.instanceApi = definition.asPluginModule().plugin(this.client); - } finally { - this.initialStates = undefined; - setCurrentPluginInstance(undefined); - } + this.initializePlugin(() => + definition.asPluginModule().plugin(this.client), + ); } // the plugin is selected in the UI activate() { - this.assertNotDestroyed(); - if (!this.activated) { - this.activated = true; - this.events.emit('activate'); - const pluginId = this.definition.id; - if (!this.realClient.isBackgroundPlugin(pluginId)) { - this.realClient.initPlugin(pluginId); // will call connect() if needed - } + super.activate(); + const pluginId = this.definition.id; + if (!this.connected && !this.realClient.isBackgroundPlugin(pluginId)) { + this.realClient.initPlugin(pluginId); // will call connect() if needed } } // the plugin is deselected in the UI deactivate() { - if (this.destroyed) { - // this can happen if the plugin is disabled while active in the UI. - // In that case deinit & destroy is already triggered from the STAR_PLUGIN action - return; - } - if (this.activated) { - this.lastDeeplink = undefined; - this.activated = false; - this.events.emit('deactivate'); - } + super.deactivate(); const pluginId = this.definition.id; if (this.connected && !this.realClient.isBackgroundPlugin(pluginId)) { this.realClient.deinitPlugin(pluginId); @@ -243,13 +164,10 @@ export class SandyPluginInstance { } destroy() { - this.assertNotDestroyed(); - this.deactivate(); if (this.connected) { this.realClient.deinitPlugin(this.definition.id); } - this.events.emit('destroy'); - this.destroyed = true; + super.destroy(); } receiveMessages(messages: Message[]) { @@ -262,30 +180,6 @@ export class SandyPluginInstance { return '[SandyPluginInstance]'; } - triggerDeepLink(deepLink: unknown) { - this.assertNotDestroyed(); - if (deepLink !== this.lastDeeplink) { - this.lastDeeplink = deepLink; - this.events.emit('deeplink', deepLink); - } - } - - exportState() { - return Object.fromEntries( - Object.entries(this.rootStates).map(([key, atom]) => [key, atom.get()]), - ); - } - - isPersistable(): boolean { - return Object.keys(this.rootStates).length > 0; - } - - private assertNotDestroyed() { - if (this.destroyed) { - throw new Error('Plugin has been destroyed already'); - } - } - private assertConnected() { this.assertNotDestroyed(); if (!this.connected) { diff --git a/desktop/flipper-plugin/src/plugin/PluginBase.tsx b/desktop/flipper-plugin/src/plugin/PluginBase.tsx new file mode 100644 index 000000000..f5c0a46ef --- /dev/null +++ b/desktop/flipper-plugin/src/plugin/PluginBase.tsx @@ -0,0 +1,153 @@ +/** + * Copyright (c) Facebook, Inc. and its 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 {SandyPluginDefinition} from './SandyPluginDefinition'; +import {EventEmitter} from 'events'; +import {Atom} from '../state/atom'; + +export interface BasePluginClient { + /** + * the onDestroy event is fired whenever a device is unloaded from Flipper, or a plugin is disabled. + */ + onDestroy(cb: () => void): void; + + /** + * the onActivate event is fired whenever the plugin is actived in the UI + */ + onActivate(cb: () => void): void; + + /** + * The counterpart of the `onActivate` handler. + */ + onDeactivate(cb: () => void): void; + + /** + * Triggered when this plugin is opened through a deeplink + */ + onDeepLink(cb: (deepLink: unknown) => void): void; +} + +let currentPluginInstance: BasePluginInstance | undefined = undefined; + +export function setCurrentPluginInstance( + instance: typeof currentPluginInstance, +) { + currentPluginInstance = instance; +} + +export function getCurrentPluginInstance(): typeof currentPluginInstance { + return currentPluginInstance; +} + +export abstract class BasePluginInstance { + /** the original plugin definition */ + definition: SandyPluginDefinition; + /** the plugin instance api as used inside components and such */ + instanceApi: any; + + activated = false; + destroyed = false; + events = new EventEmitter(); + + // temporarily field that is used during deserialization + initialStates?: Record; + // all the atoms that should be serialized when making an export / import + rootStates: Record> = {}; + // last seen deeplink + lastDeeplink?: any; + + constructor( + definition: SandyPluginDefinition, + initialStates?: Record, + ) { + this.definition = definition; + this.initialStates = initialStates; + } + + protected initializePlugin(factory: () => any) { + // To be called from constructory + setCurrentPluginInstance(this); + try { + this.instanceApi = factory(); + } finally { + this.initialStates = undefined; + setCurrentPluginInstance(undefined); + } + } + + protected createBasePluginClient(): BasePluginClient { + return { + onActivate: (cb) => { + this.events.on('activate', cb); + }, + onDeactivate: (cb) => { + this.events.on('deactivate', cb); + }, + onDeepLink: (callback) => { + this.events.on('deeplink', callback); + }, + onDestroy: (cb) => { + this.events.on('destroy', cb); + }, + }; + } + + // the plugin is selected in the UI + activate() { + this.assertNotDestroyed(); + if (!this.activated) { + this.activated = true; + this.events.emit('activate'); + } + } + + deactivate() { + if (this.destroyed) { + return; + } + if (this.activated) { + this.lastDeeplink = undefined; + this.activated = false; + this.events.emit('deactivate'); + } + } + + destroy() { + this.assertNotDestroyed(); + this.deactivate(); + this.events.emit('destroy'); + this.destroyed = true; + } + + triggerDeepLink(deepLink: unknown) { + this.assertNotDestroyed(); + if (deepLink !== this.lastDeeplink) { + this.lastDeeplink = deepLink; + this.events.emit('deeplink', deepLink); + } + } + + exportState() { + return Object.fromEntries( + Object.entries(this.rootStates).map(([key, atom]) => [key, atom.get()]), + ); + } + + isPersistable(): boolean { + return Object.keys(this.rootStates).length > 0; + } + + protected assertNotDestroyed() { + if (this.destroyed) { + throw new Error('Plugin has been destroyed already'); + } + } + + abstract toJSON(): string; +} diff --git a/desktop/flipper-plugin/src/state/atom.tsx b/desktop/flipper-plugin/src/state/atom.tsx index d00dc3b50..5a9cb2ab7 100644 --- a/desktop/flipper-plugin/src/state/atom.tsx +++ b/desktop/flipper-plugin/src/state/atom.tsx @@ -9,7 +9,7 @@ import {produce} from 'immer'; import {useState, useEffect} from 'react'; -import {getCurrentPluginInstance} from '../plugin/Plugin'; +import {getCurrentPluginInstance} from '../plugin/PluginBase'; export type Atom = { get(): T; diff --git a/desktop/flipper-plugin/src/test-utils/test-utils.tsx b/desktop/flipper-plugin/src/test-utils/test-utils.tsx index 687a2e899..e78c8e8b8 100644 --- a/desktop/flipper-plugin/src/test-utils/test-utils.tsx +++ b/desktop/flipper-plugin/src/test-utils/test-utils.tsx @@ -19,7 +19,7 @@ import {PluginDetails} from 'flipper-plugin-lib'; import { RealFlipperClient, SandyPluginInstance, - FlipperClient, + PluginClient, } from '../plugin/Plugin'; import { SandyPluginDefinition, @@ -34,6 +34,7 @@ import { RealFlipperDevice, DeviceLogListener, } from '../plugin/DevicePlugin'; +import {BasePluginInstance} from '../plugin/PluginBase'; type Renderer = RenderResult; @@ -49,17 +50,44 @@ type ExtractClientType> = Parameters< type ExtractMethodsType< Module extends FlipperPluginModule -> = ExtractClientType extends FlipperClient +> = ExtractClientType extends PluginClient ? Methods : never; type ExtractEventsType< Module extends FlipperPluginModule -> = ExtractClientType extends FlipperClient +> = ExtractClientType extends PluginClient ? Events : never; -interface StartPluginResult> { +interface BasePluginResult { + /** + * Emulates the 'onActivate' event + */ + activate(): void; + /** + * Emulates the 'onActivate' event (when the user opens the plugin in the UI). + * Will also trigger the `onConnect` event for non-background plugins + */ + deactivate(): void; + /** + * Emulates the 'destroy' event. After calling destroy this plugin instance won't be usable anymore + */ + destroy(): void; + + /** + * Emulate triggering a deeplink + */ + triggerDeepLink(deeplink: unknown): void; + + /** + * Grab all the persistable state + */ + exportState(): any; +} + +interface StartPluginResult> + extends BasePluginResult { /** * the instantiated plugin for this test */ @@ -68,15 +96,6 @@ interface StartPluginResult> { * module, from which any other exposed methods can be accessed during testing */ module: Module; - /** - * Emulates the 'onActivate' event (when the user opens the plugin in the UI). - * Will also trigger the `onConnect` event for non-background plugins - */ - activate(): void; - /** - * Emulatese the 'onDeactivate' event - */ - deactivate(): void; /** * Emulates the 'onConnect' event */ @@ -85,10 +104,6 @@ interface StartPluginResult> { * Emulatese the 'onDisconnect' event */ disconnect(): void; - /** - * Emulates the 'destroy' event. After calling destroy this plugin instance won't be usable anymore - */ - destroy(): void; /** * Jest Stub that is called whenever client.send() is called by the plugin. * Use send.mockImplementation(function) to intercept the calls. @@ -117,13 +132,10 @@ interface StartPluginResult> { params: any; // afaik we can't type this :-( }[], ): void; - - triggerDeepLink(deeplink: unknown): void; - - exportState(): any; } -interface StartDevicePluginResult { +interface StartDevicePluginResult + extends BasePluginResult { /** * the instantiated plugin for this test */ @@ -132,30 +144,10 @@ interface StartDevicePluginResult { * module, from which any other exposed methods can be accessed during testing */ module: Module; - /** - * Emulates the 'onActivate' event - */ - activate(): void; - /** - * Emulates the 'onDeactivate' event - */ - deactivate(): void; - /** - * Emulates the 'destroy' event. After calling destroy this plugin instance won't be usable anymore - */ - destroy(): void; /** * Emulates sending a log message arriving from the device */ sendLogEntry(logEntry: DeviceLogEntry): void; - /** - * Emulates triggering a deeplik - */ - triggerDeepLink(deeplink: unknown): void; - /** - * Grabs the current (exportable) state - */ - exportState(): any; } export function startPlugin>( @@ -198,28 +190,13 @@ export function startPlugin>( definition, options?.initialState, ); - if (options?.isBackgroundPlugin) { - pluginInstance.connect(); // otherwise part of activate - } - // we start activated - pluginInstance.activate(); const res: StartPluginResult = { - module, + ...createBasePluginResult(pluginInstance), instance: pluginInstance.instanceApi, - activate() { - pluginInstance.activate(); - pluginInstance.connect(); - }, - deactivate() { - pluginInstance.deactivate(); - if (!fakeFlipper.isBackgroundPlugin) { - pluginInstance.disconnect(); - } - }, + module, connect: () => pluginInstance.connect(), disconnect: () => pluginInstance.disconnect(), - destroy: () => pluginInstance.destroy(), onSend: sendStub, sendEvent: (event, params) => { res.sendEvents([ @@ -234,13 +211,13 @@ export function startPlugin>( pluginInstance.receiveMessages(messages as any); }); }, - exportState: () => pluginInstance.exportState(), - triggerDeepLink: (deepLink: unknown) => { - pluginInstance.triggerDeepLink(deepLink); - }, }; - // @ts-ignore - res._backingInstance = pluginInstance; + (res as any)._backingInstance = pluginInstance; + // we start activated + if (options?.isBackgroundPlugin) { + pluginInstance.connect(); // otherwise part of activate + } + pluginInstance.activate(); return res; } @@ -252,8 +229,7 @@ export function renderPlugin>( act: (cb: () => void) => void; } { const res = startPlugin(module, options); - // @ts-ignore hidden api - const pluginInstance: SandyPluginInstance = res._backingInstance; + const pluginInstance: SandyPluginInstance = (res as any)._backingInstance; const renderer = render(); @@ -288,27 +264,20 @@ export function startDevicePlugin( definition, options?.initialState, ); - // we start connected - pluginInstance.activate(); const res: StartDevicePluginResult = { + ...createBasePluginResult(pluginInstance), module, instance: pluginInstance.instanceApi, - activate: () => pluginInstance.activate(), - deactivate: () => pluginInstance.deactivate(), - destroy: () => pluginInstance.destroy(), sendLogEntry: (entry) => { act(() => { testDevice.addLogEntry(entry); }); }, - exportState: () => pluginInstance.exportState(), - triggerDeepLink: (deepLink: unknown) => { - pluginInstance.triggerDeepLink(deepLink); - }, }; - // @ts-ignore - res._backingInstance = pluginInstance; + (res as any)._backingInstance = pluginInstance; + // we start connected + pluginInstance.activate(); return res; } @@ -321,7 +290,8 @@ export function renderDevicePlugin( } { const res = startDevicePlugin(module, options); // @ts-ignore hidden api - const pluginInstance: SandyDevicePluginInstance = res._backingInstance; + const pluginInstance: SandyDevicePluginInstance = (res as any) + ._backingInstance; const renderer = render(); @@ -336,6 +306,20 @@ export function renderDevicePlugin( }; } +function createBasePluginResult( + pluginInstance: BasePluginInstance, +): BasePluginResult { + return { + activate: () => pluginInstance.activate(), + deactivate: () => pluginInstance.deactivate(), + exportState: () => pluginInstance.exportState(), + triggerDeepLink: (deepLink: unknown) => { + pluginInstance.triggerDeepLink(deepLink); + }, + destroy: () => pluginInstance.destroy(), + }; +} + export function createMockPluginDetails( details?: Partial, ): PluginDetails {