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
This commit is contained in:
Michel Weststrate
2020-11-11 07:57:14 -08:00
committed by Facebook GitHub Bot
parent 9b4e7e873c
commit 1157976eef
7 changed files with 87 additions and 23 deletions

View File

@@ -67,6 +67,8 @@ export function plugin(client: PluginClient<Events, Methods>) {
return client.send('currentState', {since: 0}); return client.send('currentState', {since: 0});
} }
expect(client.device).not.toBeNull();
return { return {
activateStub, activateStub,
deactivateStub, deactivateStub,
@@ -75,6 +77,8 @@ export function plugin(client: PluginClient<Events, Methods>) {
disconnectStub, disconnectStub,
getCurrentState, getCurrentState,
state, state,
appId: client.appId,
appName: client.appName,
}; };
} }

View File

@@ -56,6 +56,9 @@ test('it can start a plugin and lifecycle events', () => {
expect(instance.deactivateStub).toBeCalledTimes(2); expect(instance.deactivateStub).toBeCalledTimes(2);
expect(instance.destroyStub).toBeCalledTimes(1); expect(instance.destroyStub).toBeCalledTimes(1);
expect(instance.appName).toBe('TestApplication');
expect(instance.appId).toBe('TestApplication#Android#TestDevice#serial-000');
// cannot interact with destroyed plugin // cannot interact with destroyed plugin
expect(() => { expect(() => {
p.connect(); p.connect();

View File

@@ -46,15 +46,14 @@ export type DevicePluginPredicate = (device: Device) => boolean;
export type DevicePluginFactory = (client: DevicePluginClient) => object; export type DevicePluginFactory = (client: DevicePluginClient) => object;
export interface DevicePluginClient extends BasePluginClient { export interface DevicePluginClient extends BasePluginClient {}
readonly device: Device;
}
/** /**
* Wrapper interface around BaseDevice in Flipper * Wrapper interface around BaseDevice in Flipper
*/ */
export interface RealFlipperDevice { export interface RealFlipperDevice {
os: string; os: string;
serial: string;
isArchived: boolean; isArchived: boolean;
deviceType: DeviceType; deviceType: DeviceType;
addLogListener(callback: DeviceLogListener): Symbol; addLogListener(callback: DeviceLogListener): Symbol;
@@ -76,25 +75,8 @@ export class SandyDevicePluginInstance extends BasePluginInstance {
realDevice: RealFlipperDevice, realDevice: RealFlipperDevice,
initialStates?: Record<string, any>, initialStates?: Record<string, any>,
) { ) {
super(flipperLib, definition, initialStates); super(flipperLib, definition, realDevice, initialStates);
const device: Device = { this.client = this.createBasePluginClient();
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,
};
this.initializePlugin(() => this.initializePlugin(() =>
definition.asDevicePluginModule().devicePlugin(this.client), definition.asDevicePluginModule().devicePlugin(this.client),
); );

View File

@@ -10,6 +10,7 @@
import {SandyPluginDefinition} from './SandyPluginDefinition'; import {SandyPluginDefinition} from './SandyPluginDefinition';
import {BasePluginInstance, BasePluginClient} from './PluginBase'; import {BasePluginInstance, BasePluginClient} from './PluginBase';
import {FlipperLib} from './FlipperLib'; import {FlipperLib} from './FlipperLib';
import {RealFlipperDevice} from './DevicePlugin';
type EventsContract = Record<string, any>; type EventsContract = Record<string, any>;
type MethodsContract = Record<string, (params: any) => Promise<any>>; type MethodsContract = Record<string, (params: any) => Promise<any>>;
@@ -26,6 +27,16 @@ export interface PluginClient<
Events extends EventsContract = {}, Events extends EventsContract = {},
Methods extends MethodsContract = {} Methods extends MethodsContract = {}
> extends BasePluginClient { > 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. * 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, * 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 * Plugin Factory. For internal purposes only
*/ */
export interface RealFlipperClient { export interface RealFlipperClient {
id: string;
query: {
app: string;
os: string;
device: string;
device_id: string;
};
deviceSync: RealFlipperDevice;
isBackgroundPlugin(pluginId: string): boolean; isBackgroundPlugin(pluginId: string): boolean;
initPlugin(pluginId: string): void; initPlugin(pluginId: string): void;
deinitPlugin(pluginId: string): void; deinitPlugin(pluginId: string): void;
@@ -108,11 +127,17 @@ export class SandyPluginInstance extends BasePluginInstance {
realClient: RealFlipperClient, realClient: RealFlipperClient,
initialStates?: Record<string, any>, initialStates?: Record<string, any>,
) { ) {
super(flipperLib, definition, initialStates); super(flipperLib, definition, realClient.deviceSync, initialStates);
this.realClient = realClient; this.realClient = realClient;
this.definition = definition; this.definition = definition;
this.client = { this.client = {
...this.createBasePluginClient(), ...this.createBasePluginClient(),
get appId() {
return realClient.id;
},
get appName() {
return realClient.query.app;
},
onConnect: (cb) => { onConnect: (cb) => {
this.events.on('connect', cb); this.events.on('connect', cb);
}, },

View File

@@ -12,8 +12,11 @@ import {EventEmitter} from 'events';
import {Atom} from '../state/atom'; import {Atom} from '../state/atom';
import {MenuEntry, NormalizedMenuEntry, normalizeMenuEntry} from './MenuEntry'; import {MenuEntry, NormalizedMenuEntry, normalizeMenuEntry} from './MenuEntry';
import {FlipperLib} from './FlipperLib'; import {FlipperLib} from './FlipperLib';
import {Device, RealFlipperDevice} from './DevicePlugin';
export interface BasePluginClient { export interface BasePluginClient {
readonly device: Device;
/** /**
* the onDestroy event is fired whenever a device is unloaded from Flipper, or a plugin is disabled. * 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; definition: SandyPluginDefinition;
/** the plugin instance api as used inside components and such */ /** the plugin instance api as used inside components and such */
instanceApi: any; instanceApi: any;
/** the device owning this plugin */
device: Device;
activated = false; activated = false;
destroyed = false; destroyed = false;
@@ -82,11 +87,29 @@ export abstract class BasePluginInstance {
constructor( constructor(
flipperLib: FlipperLib, flipperLib: FlipperLib,
definition: SandyPluginDefinition, definition: SandyPluginDefinition,
realDevice: RealFlipperDevice,
initialStates?: Record<string, any>, initialStates?: Record<string, any>,
) { ) {
this.flipperLib = flipperLib; this.flipperLib = flipperLib;
this.definition = definition; this.definition = definition;
this.initialStates = initialStates; 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) { protected initializePlugin(factory: () => any) {
@@ -102,6 +125,7 @@ export abstract class BasePluginInstance {
protected createBasePluginClient(): BasePluginClient { protected createBasePluginClient(): BasePluginClient {
return { return {
device: this.device,
onActivate: (cb) => { onActivate: (cb) => {
this.events.on('activate', cb); this.events.on('activate', cb);
}, },

View File

@@ -180,7 +180,18 @@ export function startPlugin<Module extends FlipperPluginModule<any>>(
const sendStub = jest.fn(); const sendStub = jest.fn();
const flipperUtils = createMockFlipperLib(); const flipperUtils = createMockFlipperLib();
const testDevice = createMockDevice(options);
const appName = 'TestApplication';
const deviceName = 'TestDevice';
const fakeFlipperClient: RealFlipperClient = { 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) { isBackgroundPlugin(_pluginId: string) {
return !!options?.isBackgroundPlugin; return !!options?.isBackgroundPlugin;
}, },
@@ -381,6 +392,7 @@ function createMockDevice(options?: StartPluginOptions): RealFlipperDevice {
return { return {
os: 'Android', os: 'Android',
deviceType: 'emulator', deviceType: 'emulator',
serial: 'serial-000',
isArchived: !!options?.isArchived, isArchived: !!options?.isArchived,
addLogListener(cb) { addLogListener(cb) {
logListeners.push(cb); logListeners.push(cb);

View File

@@ -36,6 +36,20 @@ export function plugin(client: PluginClient<Events, Methods>) {
The `PluginClient` received by the `plugin` exposes the following members: 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 ### Events listeners
#### `onMessage` #### `onMessage`