From 1157976eef882b04f80f57e9447b6ddc2e812a28 Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Wed, 11 Nov 2020 07:57:14 -0800 Subject: [PATCH] Expose more meta information for plugins Summary: expose `appName`, `appId` and `device` to Sandy plugins. Will be used in next diff to migrate navigation plugin Reviewed By: cekkaewnumchai Differential Revision: D24857253 fbshipit-source-id: 03ac3d376d5d1950bcf3d78386a65ce167b517e3 --- .../src/__tests__/TestPlugin.tsx | 4 +++ .../src/__tests__/test-utils.node.tsx | 3 +++ .../src/plugin/DevicePlugin.tsx | 26 +++--------------- desktop/flipper-plugin/src/plugin/Plugin.tsx | 27 ++++++++++++++++++- .../flipper-plugin/src/plugin/PluginBase.tsx | 24 +++++++++++++++++ .../src/test-utils/test-utils.tsx | 12 +++++++++ docs/extending/flipper-plugin.mdx | 14 ++++++++++ 7 files changed, 87 insertions(+), 23 deletions(-) diff --git a/desktop/flipper-plugin/src/__tests__/TestPlugin.tsx b/desktop/flipper-plugin/src/__tests__/TestPlugin.tsx index f8d387ae9..433ac5fed 100644 --- a/desktop/flipper-plugin/src/__tests__/TestPlugin.tsx +++ b/desktop/flipper-plugin/src/__tests__/TestPlugin.tsx @@ -67,6 +67,8 @@ export function plugin(client: PluginClient) { return client.send('currentState', {since: 0}); } + expect(client.device).not.toBeNull(); + return { activateStub, deactivateStub, @@ -75,6 +77,8 @@ export function plugin(client: PluginClient) { disconnectStub, getCurrentState, state, + appId: client.appId, + appName: client.appName, }; } diff --git a/desktop/flipper-plugin/src/__tests__/test-utils.node.tsx b/desktop/flipper-plugin/src/__tests__/test-utils.node.tsx index 2efc5fea1..0ad83cb8b 100644 --- a/desktop/flipper-plugin/src/__tests__/test-utils.node.tsx +++ b/desktop/flipper-plugin/src/__tests__/test-utils.node.tsx @@ -56,6 +56,9 @@ test('it can start a plugin and lifecycle events', () => { expect(instance.deactivateStub).toBeCalledTimes(2); expect(instance.destroyStub).toBeCalledTimes(1); + expect(instance.appName).toBe('TestApplication'); + expect(instance.appId).toBe('TestApplication#Android#TestDevice#serial-000'); + // cannot interact with destroyed plugin expect(() => { p.connect(); diff --git a/desktop/flipper-plugin/src/plugin/DevicePlugin.tsx b/desktop/flipper-plugin/src/plugin/DevicePlugin.tsx index 6aa938135..1dccffdbd 100644 --- a/desktop/flipper-plugin/src/plugin/DevicePlugin.tsx +++ b/desktop/flipper-plugin/src/plugin/DevicePlugin.tsx @@ -46,15 +46,14 @@ export type DevicePluginPredicate = (device: Device) => boolean; export type DevicePluginFactory = (client: DevicePluginClient) => object; -export interface DevicePluginClient extends BasePluginClient { - readonly device: Device; -} +export interface DevicePluginClient extends BasePluginClient {} /** * Wrapper interface around BaseDevice in Flipper */ export interface RealFlipperDevice { os: string; + serial: string; isArchived: boolean; deviceType: DeviceType; addLogListener(callback: DeviceLogListener): Symbol; @@ -76,25 +75,8 @@ export class SandyDevicePluginInstance extends BasePluginInstance { realDevice: RealFlipperDevice, initialStates?: Record, ) { - super(flipperLib, definition, initialStates); - const device: Device = { - realDevice, // TODO: temporarily, clean up T70688226 - // N.B. we model OS as string, not as enum, to make custom device types possible in the future - os: realDevice.os, - isArchived: realDevice.isArchived, - deviceType: realDevice.deviceType, - - onLogEntry(cb) { - const handle = realDevice.addLogListener(cb); - return () => { - realDevice.removeLogListener(handle); - }; - }, - }; - this.client = { - ...this.createBasePluginClient(), - device, - }; + super(flipperLib, definition, realDevice, initialStates); + this.client = this.createBasePluginClient(); this.initializePlugin(() => definition.asDevicePluginModule().devicePlugin(this.client), ); diff --git a/desktop/flipper-plugin/src/plugin/Plugin.tsx b/desktop/flipper-plugin/src/plugin/Plugin.tsx index ea194d0a6..3fdf08400 100644 --- a/desktop/flipper-plugin/src/plugin/Plugin.tsx +++ b/desktop/flipper-plugin/src/plugin/Plugin.tsx @@ -10,6 +10,7 @@ import {SandyPluginDefinition} from './SandyPluginDefinition'; import {BasePluginInstance, BasePluginClient} from './PluginBase'; import {FlipperLib} from './FlipperLib'; +import {RealFlipperDevice} from './DevicePlugin'; type EventsContract = Record; type MethodsContract = Record Promise>; @@ -26,6 +27,16 @@ export interface PluginClient< Events extends EventsContract = {}, Methods extends MethodsContract = {} > extends BasePluginClient { + /** + * Identifier that uniquely identifies the connected application + */ + readonly appId: string; + + /** + * Registered name for the connected application + */ + readonly appName: string; + /** * 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, @@ -71,6 +82,14 @@ export interface PluginClient< * Plugin Factory. For internal purposes only */ export interface RealFlipperClient { + id: string; + query: { + app: string; + os: string; + device: string; + device_id: string; + }; + deviceSync: RealFlipperDevice; isBackgroundPlugin(pluginId: string): boolean; initPlugin(pluginId: string): void; deinitPlugin(pluginId: string): void; @@ -108,11 +127,17 @@ export class SandyPluginInstance extends BasePluginInstance { realClient: RealFlipperClient, initialStates?: Record, ) { - super(flipperLib, definition, initialStates); + super(flipperLib, definition, realClient.deviceSync, initialStates); this.realClient = realClient; this.definition = definition; this.client = { ...this.createBasePluginClient(), + get appId() { + return realClient.id; + }, + get appName() { + return realClient.query.app; + }, onConnect: (cb) => { this.events.on('connect', cb); }, diff --git a/desktop/flipper-plugin/src/plugin/PluginBase.tsx b/desktop/flipper-plugin/src/plugin/PluginBase.tsx index ed5ae9325..b632d4e39 100644 --- a/desktop/flipper-plugin/src/plugin/PluginBase.tsx +++ b/desktop/flipper-plugin/src/plugin/PluginBase.tsx @@ -12,8 +12,11 @@ import {EventEmitter} from 'events'; import {Atom} from '../state/atom'; import {MenuEntry, NormalizedMenuEntry, normalizeMenuEntry} from './MenuEntry'; import {FlipperLib} from './FlipperLib'; +import {Device, RealFlipperDevice} from './DevicePlugin'; export interface BasePluginClient { + readonly device: Device; + /** * the onDestroy event is fired whenever a device is unloaded from Flipper, or a plugin is disabled. */ @@ -65,6 +68,8 @@ export abstract class BasePluginInstance { definition: SandyPluginDefinition; /** the plugin instance api as used inside components and such */ instanceApi: any; + /** the device owning this plugin */ + device: Device; activated = false; destroyed = false; @@ -82,11 +87,29 @@ export abstract class BasePluginInstance { constructor( flipperLib: FlipperLib, definition: SandyPluginDefinition, + realDevice: RealFlipperDevice, initialStates?: Record, ) { this.flipperLib = flipperLib; this.definition = definition; this.initialStates = initialStates; + if (!realDevice) { + throw new Error('Illegal State: Device has not yet been loaded'); + } + this.device = { + realDevice, // TODO: temporarily, clean up T70688226 + // N.B. we model OS as string, not as enum, to make custom device types possible in the future + os: realDevice.os, + isArchived: realDevice.isArchived, + deviceType: realDevice.deviceType, + + onLogEntry(cb) { + const handle = realDevice.addLogListener(cb); + return () => { + realDevice.removeLogListener(handle); + }; + }, + }; } protected initializePlugin(factory: () => any) { @@ -102,6 +125,7 @@ export abstract class BasePluginInstance { protected createBasePluginClient(): BasePluginClient { return { + device: this.device, onActivate: (cb) => { this.events.on('activate', cb); }, diff --git a/desktop/flipper-plugin/src/test-utils/test-utils.tsx b/desktop/flipper-plugin/src/test-utils/test-utils.tsx index 8b3fe2a87..d01de0fee 100644 --- a/desktop/flipper-plugin/src/test-utils/test-utils.tsx +++ b/desktop/flipper-plugin/src/test-utils/test-utils.tsx @@ -180,7 +180,18 @@ export function startPlugin>( const sendStub = jest.fn(); const flipperUtils = createMockFlipperLib(); + const testDevice = createMockDevice(options); + const appName = 'TestApplication'; + const deviceName = 'TestDevice'; const fakeFlipperClient: RealFlipperClient = { + id: `${appName}#${testDevice.os}#${deviceName}#${testDevice.serial}`, + query: { + app: appName, + device: deviceName, + device_id: testDevice.serial, + os: testDevice.serial, + }, + deviceSync: testDevice, isBackgroundPlugin(_pluginId: string) { return !!options?.isBackgroundPlugin; }, @@ -381,6 +392,7 @@ function createMockDevice(options?: StartPluginOptions): RealFlipperDevice { return { os: 'Android', deviceType: 'emulator', + serial: 'serial-000', isArchived: !!options?.isArchived, addLogListener(cb) { logListeners.push(cb); diff --git a/docs/extending/flipper-plugin.mdx b/docs/extending/flipper-plugin.mdx index 3a24d2c0f..94f5dc505 100644 --- a/docs/extending/flipper-plugin.mdx +++ b/docs/extending/flipper-plugin.mdx @@ -36,6 +36,20 @@ export function plugin(client: PluginClient) { The `PluginClient` received by the `plugin` exposes the following members: +### Properties + +#### `device` + +Returns the [`Device`](#device) this plugin is connected to. + +#### `appName` + +The name of the application, for example 'Facebook', 'Instagram' or 'Slack'. + +#### `appId` + +A string that uniquely identifies the current application, is based on a combination of the application name and device serial on which the application is running. + ### Events listeners #### `onMessage`