Cleanup and some code reuse between Device- and normal Plugins
Summary: Introducing a base abstract class (blegh) to share some life cycle management between Device- and normal plugins. Cleaned up the test utils a bit as well + some old TODO's that now have been taken care of Reviewed By: nikoant Differential Revision: D22727089 fbshipit-source-id: 507816f1bfdbc6e7e71d4bd365b881b6710ca917
This commit is contained in:
committed by
Facebook GitHub Bot
parent
b9c9e89b53
commit
642261c0d0
@@ -15,7 +15,7 @@ import {
|
|||||||
SandyDevicePluginInstance,
|
SandyDevicePluginInstance,
|
||||||
SandyPluginDefinition,
|
SandyPluginDefinition,
|
||||||
} from 'flipper-plugin';
|
} from 'flipper-plugin';
|
||||||
import {DevicePluginMap} from '../plugin';
|
import {DevicePluginMap, FlipperDevicePlugin} from '../plugin';
|
||||||
|
|
||||||
export type DeviceShell = {
|
export type DeviceShell = {
|
||||||
stdout: stream.Readable;
|
stdout: stream.Readable;
|
||||||
@@ -179,18 +179,22 @@ export default class BaseDevice {
|
|||||||
const plugins = Array.from(devicePlugins.values());
|
const plugins = Array.from(devicePlugins.values());
|
||||||
plugins.sort(sortPluginsByName);
|
plugins.sort(sortPluginsByName);
|
||||||
for (const plugin of plugins) {
|
for (const plugin of plugins) {
|
||||||
if (plugin instanceof SandyPluginDefinition) {
|
this.loadDevicePlugin(plugin);
|
||||||
if (plugin.asDevicePluginModule().supportsDevice(this as any)) {
|
}
|
||||||
this.devicePlugins.push(plugin.id);
|
}
|
||||||
this.sandyPluginStates.set(
|
|
||||||
plugin.id,
|
loadDevicePlugin(plugin: typeof FlipperDevicePlugin | SandyPluginDefinition) {
|
||||||
new SandyDevicePluginInstance(this, plugin),
|
if (plugin instanceof SandyPluginDefinition) {
|
||||||
); // TODO: pass initial state if applicable
|
if (plugin.asDevicePluginModule().supportsDevice(this as any)) {
|
||||||
}
|
this.devicePlugins.push(plugin.id);
|
||||||
} else {
|
this.sandyPluginStates.set(
|
||||||
if (plugin.supportsDevice(this)) {
|
plugin.id,
|
||||||
this.devicePlugins.push(plugin.id);
|
new SandyDevicePluginInstance(this, plugin),
|
||||||
}
|
); // TODO T70582933: pass initial state if applicable
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (plugin.supportsDevice(this)) {
|
||||||
|
this.devicePlugins.push(plugin.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -277,7 +277,6 @@ const requirePluginInternal = (
|
|||||||
|
|
||||||
if (pluginDetails.flipperSDKVersion) {
|
if (pluginDetails.flipperSDKVersion) {
|
||||||
// Sandy plugin
|
// Sandy plugin
|
||||||
// TODO: suppor device Plugins T68738317
|
|
||||||
return new SandyPluginDefinition(pluginDetails, plugin);
|
return new SandyPluginDefinition(pluginDetails, plugin);
|
||||||
} else {
|
} else {
|
||||||
// classic plugin
|
// classic plugin
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const WelcomeScreen = isHeadless()
|
|||||||
import NotificationScreen from '../chrome/NotificationScreen';
|
import NotificationScreen from '../chrome/NotificationScreen';
|
||||||
import SupportRequestFormV2 from '../fb-stubs/SupportRequestFormV2';
|
import SupportRequestFormV2 from '../fb-stubs/SupportRequestFormV2';
|
||||||
import SupportRequestDetails from '../fb-stubs/SupportRequestDetails';
|
import SupportRequestDetails from '../fb-stubs/SupportRequestDetails';
|
||||||
import {getPluginKey} from '../utils/pluginUtils';
|
import {getPluginKey, isDevicePluginDefinition} from '../utils/pluginUtils';
|
||||||
import {deconstructClientId} from '../utils/clientUtils';
|
import {deconstructClientId} from '../utils/clientUtils';
|
||||||
import {FlipperDevicePlugin, PluginDefinition, isSandyPlugin} from '../plugin';
|
import {FlipperDevicePlugin, PluginDefinition, isSandyPlugin} from '../plugin';
|
||||||
import {RegisterPluginAction} from './plugins';
|
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
|
// plugins are registered after creating the base devices, so update them
|
||||||
const plugins = action.payload;
|
const plugins = action.payload;
|
||||||
plugins.forEach((plugin) => {
|
plugins.forEach((plugin) => {
|
||||||
// TODO: T68738317 support sandy device plugin
|
if (isDevicePluginDefinition(plugin)) {
|
||||||
if (
|
|
||||||
!isSandyPlugin(plugin) &&
|
|
||||||
plugin.prototype instanceof FlipperDevicePlugin
|
|
||||||
) {
|
|
||||||
// smell: devices are mutable
|
// smell: devices are mutable
|
||||||
state.devices.forEach((device) => {
|
state.devices.forEach((device) => {
|
||||||
// @ts-ignore
|
device.loadDevicePlugin(plugin);
|
||||||
if (plugin.supportsDevice(device)) {
|
|
||||||
device.devicePlugins = [
|
|
||||||
...(device.devicePlugins || []),
|
|
||||||
plugin.id,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -662,7 +662,7 @@ async function getStoreExport(
|
|||||||
const newPluginState = metadata.pluginStates;
|
const newPluginState = metadata.pluginStates;
|
||||||
|
|
||||||
// TODO: support async export like fetchMetaData T68683476
|
// TODO: support async export like fetchMetaData T68683476
|
||||||
// TODO: support device plugins T68738317
|
// TODO: support device plugins T70582933
|
||||||
const pluginStates2 = pluginsToProcess
|
const pluginStates2 = pluginsToProcess
|
||||||
? exportSandyPluginStates(pluginsToProcess)
|
? exportSandyPluginStates(pluginsToProcess)
|
||||||
: {};
|
: {};
|
||||||
|
|||||||
@@ -128,7 +128,6 @@ export function processMessagesLater(
|
|||||||
case isSelected && getPendingMessages(store, pluginKey).length === 0:
|
case isSelected && getPendingMessages(store, pluginKey).length === 0:
|
||||||
processMessagesImmediately(store, pluginKey, plugin, messages);
|
processMessagesImmediately(store, pluginKey, plugin, messages);
|
||||||
break;
|
break;
|
||||||
// TODO: support SandyDevicePlugin T68738317
|
|
||||||
case isSelected:
|
case isSelected:
|
||||||
case plugin instanceof SandyPluginInstance:
|
case plugin instanceof SandyPluginInstance:
|
||||||
case plugin instanceof FlipperDevicePlugin:
|
case plugin instanceof FlipperDevicePlugin:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {FlipperClient} from '../plugin/Plugin';
|
import {PluginClient} from '../plugin/Plugin';
|
||||||
import {usePlugin} from '../plugin/PluginContext';
|
import {usePlugin} from '../plugin/PluginContext';
|
||||||
import {createState, useValue} from '../state/atom';
|
import {createState, useValue} from '../state/atom';
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ type Methods = {
|
|||||||
currentState(params: {since: number}): Promise<number>;
|
currentState(params: {since: number}): Promise<number>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function plugin(client: FlipperClient<Events, Methods>) {
|
export function plugin(client: PluginClient<Events, Methods>) {
|
||||||
const connectStub = jest.fn();
|
const connectStub = jest.fn();
|
||||||
const disconnectStub = jest.fn();
|
const disconnectStub = jest.fn();
|
||||||
const activateStub = jest.fn();
|
const activateStub = jest.fn();
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
import * as TestUtils from '../test-utils/test-utils';
|
import * as TestUtils from '../test-utils/test-utils';
|
||||||
import * as testPlugin from './TestPlugin';
|
import * as testPlugin from './TestPlugin';
|
||||||
import {createState} from '../state/atom';
|
import {createState} from '../state/atom';
|
||||||
import {FlipperClient} from '../plugin/Plugin';
|
import {PluginClient} from '../plugin/Plugin';
|
||||||
import {DevicePluginClient} from '../plugin/DevicePlugin';
|
import {DevicePluginClient} from '../plugin/DevicePlugin';
|
||||||
|
|
||||||
test('it can start a plugin and lifecycle events', () => {
|
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 () => {
|
test('plugins can receive deeplinks', async () => {
|
||||||
const plugin = TestUtils.startPlugin({
|
const plugin = TestUtils.startPlugin({
|
||||||
plugin(client: FlipperClient) {
|
plugin(client: PluginClient) {
|
||||||
client.onDeepLink((deepLink) => {
|
client.onDeepLink((deepLink) => {
|
||||||
if (typeof deepLink === 'string') {
|
if (typeof deepLink === 'string') {
|
||||||
field1.set(deepLink);
|
field1.set(deepLink);
|
||||||
|
|||||||
@@ -9,7 +9,10 @@
|
|||||||
|
|
||||||
import * as TestUtilites from './test-utils/test-utils';
|
import * as TestUtilites from './test-utils/test-utils';
|
||||||
|
|
||||||
export {SandyPluginInstance, FlipperClient} from './plugin/Plugin';
|
export {
|
||||||
|
SandyPluginInstance,
|
||||||
|
PluginClient as FlipperClient,
|
||||||
|
} from './plugin/Plugin';
|
||||||
export {
|
export {
|
||||||
Device,
|
Device,
|
||||||
DeviceLogEntry,
|
DeviceLogEntry,
|
||||||
|
|||||||
@@ -8,9 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {SandyPluginDefinition} from './SandyPluginDefinition';
|
import {SandyPluginDefinition} from './SandyPluginDefinition';
|
||||||
import {EventEmitter} from 'events';
|
import {BasePluginInstance, BasePluginClient} from './PluginBase';
|
||||||
import {Atom} from '../state/atom';
|
|
||||||
import {setCurrentPluginInstance} from './Plugin';
|
|
||||||
|
|
||||||
export type DeviceLogListener = (entry: DeviceLogEntry) => void;
|
export type DeviceLogListener = (entry: DeviceLogEntry) => void;
|
||||||
|
|
||||||
@@ -42,31 +40,13 @@ export type DevicePluginPredicate = (device: Device) => boolean;
|
|||||||
|
|
||||||
export type DevicePluginFactory = (client: DevicePluginClient) => object;
|
export type DevicePluginFactory = (client: DevicePluginClient) => object;
|
||||||
|
|
||||||
// TODO: better name?
|
export interface DevicePluginClient extends BasePluginClient {
|
||||||
export interface DevicePluginClient {
|
|
||||||
readonly device: Device;
|
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 {
|
export interface RealFlipperDevice {
|
||||||
isArchived: boolean;
|
isArchived: boolean;
|
||||||
addLogListener(callback: DeviceLogListener): Symbol;
|
addLogListener(callback: DeviceLogListener): Symbol;
|
||||||
@@ -74,35 +54,20 @@ export interface RealFlipperDevice {
|
|||||||
addLogEntry(entry: DeviceLogEntry): void;
|
addLogEntry(entry: DeviceLogEntry): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SandyDevicePluginInstance {
|
export class SandyDevicePluginInstance extends BasePluginInstance {
|
||||||
static is(thing: any): thing is SandyDevicePluginInstance {
|
static is(thing: any): thing is SandyDevicePluginInstance {
|
||||||
return thing instanceof SandyDevicePluginInstance;
|
return thing instanceof SandyDevicePluginInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** client that is bound to this instance */
|
/** client that is bound to this instance */
|
||||||
client: DevicePluginClient;
|
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<string, any>;
|
|
||||||
// all the atoms that should be serialized when making an export / import
|
|
||||||
rootStates: Record<string, Atom<any>> = {};
|
|
||||||
// last seen deeplink
|
|
||||||
lastDeeplink?: any;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
realDevice: RealFlipperDevice,
|
realDevice: RealFlipperDevice,
|
||||||
definition: SandyPluginDefinition,
|
definition: SandyPluginDefinition,
|
||||||
initialStates?: Record<string, any>,
|
initialStates?: Record<string, any>,
|
||||||
) {
|
) {
|
||||||
this.definition = definition;
|
super(definition, initialStates);
|
||||||
const device: Device = {
|
const device: Device = {
|
||||||
get isArchived() {
|
get isArchived() {
|
||||||
return realDevice.isArchived;
|
return realDevice.isArchived;
|
||||||
@@ -115,84 +80,15 @@ export class SandyDevicePluginInstance {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
this.client = {
|
this.client = {
|
||||||
|
...this.createBasePluginClient(),
|
||||||
device,
|
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.initializePlugin(() =>
|
||||||
this.initialStates = initialStates;
|
definition.asDevicePluginModule().devicePlugin(this.client),
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return '[SandyDevicePluginInstance]';
|
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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {SandyPluginDefinition} from './SandyPluginDefinition';
|
import {SandyPluginDefinition} from './SandyPluginDefinition';
|
||||||
import {EventEmitter} from 'events';
|
import {BasePluginInstance, BasePluginClient} from './PluginBase';
|
||||||
import {Atom} from '../state/atom';
|
|
||||||
import {SandyDevicePluginInstance} 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>>;
|
||||||
@@ -23,25 +21,10 @@ type Message = {
|
|||||||
/**
|
/**
|
||||||
* API available to a plugin factory
|
* API available to a plugin factory
|
||||||
*/
|
*/
|
||||||
export interface FlipperClient<
|
export interface PluginClient<
|
||||||
Events extends EventsContract = {},
|
Events extends EventsContract = {},
|
||||||
Methods extends MethodsContract = {}
|
Methods extends MethodsContract = {}
|
||||||
> {
|
> extends BasePluginClient {
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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,
|
||||||
@@ -57,11 +40,6 @@ export interface FlipperClient<
|
|||||||
*/
|
*/
|
||||||
onDisconnect(cb: () => void): void;
|
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
|
* Send a message to the connected client
|
||||||
*/
|
*/
|
||||||
@@ -101,26 +79,11 @@ export interface RealFlipperClient {
|
|||||||
export type PluginFactory<
|
export type PluginFactory<
|
||||||
Events extends EventsContract,
|
Events extends EventsContract,
|
||||||
Methods extends MethodsContract
|
Methods extends MethodsContract
|
||||||
> = (client: FlipperClient<Events, Methods>) => object;
|
> = (client: PluginClient<Events, Methods>) => object;
|
||||||
|
|
||||||
export type FlipperPluginComponent = React.FC<{}>;
|
export type FlipperPluginComponent = React.FC<{}>;
|
||||||
|
|
||||||
let currentPluginInstance:
|
export class SandyPluginInstance extends BasePluginInstance {
|
||||||
| SandyPluginInstance
|
|
||||||
| SandyDevicePluginInstance
|
|
||||||
| undefined = undefined;
|
|
||||||
|
|
||||||
export function setCurrentPluginInstance(
|
|
||||||
instance: typeof currentPluginInstance,
|
|
||||||
) {
|
|
||||||
currentPluginInstance = instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCurrentPluginInstance(): typeof currentPluginInstance {
|
|
||||||
return currentPluginInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SandyPluginInstance {
|
|
||||||
static is(thing: any): thing is SandyPluginInstance {
|
static is(thing: any): thing is SandyPluginInstance {
|
||||||
return thing instanceof SandyPluginInstance;
|
return thing instanceof SandyPluginInstance;
|
||||||
}
|
}
|
||||||
@@ -128,41 +91,20 @@ export class SandyPluginInstance {
|
|||||||
/** base client provided by Flipper */
|
/** base client provided by Flipper */
|
||||||
realClient: RealFlipperClient;
|
realClient: RealFlipperClient;
|
||||||
/** client that is bound to this instance */
|
/** client that is bound to this instance */
|
||||||
client: FlipperClient<any, any>;
|
client: PluginClient<any, any>;
|
||||||
/** the original plugin definition */
|
/** connection alive? */
|
||||||
definition: SandyPluginDefinition;
|
|
||||||
/** the plugin instance api as used inside components and such */
|
|
||||||
instanceApi: any;
|
|
||||||
|
|
||||||
activated = false;
|
|
||||||
connected = false;
|
connected = false;
|
||||||
destroyed = false;
|
|
||||||
events = new EventEmitter();
|
|
||||||
|
|
||||||
// temporarily field that is used during deserialization
|
|
||||||
initialStates?: Record<string, any>;
|
|
||||||
// all the atoms that should be serialized when making an export / import
|
|
||||||
rootStates: Record<string, Atom<any>> = {};
|
|
||||||
// last seen deeplink
|
|
||||||
lastDeeplink?: any;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
realClient: RealFlipperClient,
|
realClient: RealFlipperClient,
|
||||||
definition: SandyPluginDefinition,
|
definition: SandyPluginDefinition,
|
||||||
initialStates?: Record<string, any>,
|
initialStates?: Record<string, any>,
|
||||||
) {
|
) {
|
||||||
|
super(definition, initialStates);
|
||||||
this.realClient = realClient;
|
this.realClient = realClient;
|
||||||
this.definition = definition;
|
this.definition = definition;
|
||||||
this.client = {
|
this.client = {
|
||||||
onDestroy: (cb) => {
|
...this.createBasePluginClient(),
|
||||||
this.events.on('destroy', cb);
|
|
||||||
},
|
|
||||||
onActivate: (cb) => {
|
|
||||||
this.events.on('activate', cb);
|
|
||||||
},
|
|
||||||
onDeactivate: (cb) => {
|
|
||||||
this.events.on('deactivate', cb);
|
|
||||||
},
|
|
||||||
onConnect: (cb) => {
|
onConnect: (cb) => {
|
||||||
this.events.on('connect', cb);
|
this.events.on('connect', cb);
|
||||||
},
|
},
|
||||||
@@ -181,45 +123,24 @@ export class SandyPluginInstance {
|
|||||||
onMessage: (event, callback) => {
|
onMessage: (event, callback) => {
|
||||||
this.events.on('event-' + event, callback);
|
this.events.on('event-' + event, callback);
|
||||||
},
|
},
|
||||||
onDeepLink: (callback) => {
|
|
||||||
this.events.on('deeplink', callback);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
setCurrentPluginInstance(this);
|
this.initializePlugin(() =>
|
||||||
this.initialStates = initialStates;
|
definition.asPluginModule().plugin(this.client),
|
||||||
try {
|
);
|
||||||
this.instanceApi = definition.asPluginModule().plugin(this.client);
|
|
||||||
} finally {
|
|
||||||
this.initialStates = undefined;
|
|
||||||
setCurrentPluginInstance(undefined);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// the plugin is selected in the UI
|
// the plugin is selected in the UI
|
||||||
activate() {
|
activate() {
|
||||||
this.assertNotDestroyed();
|
super.activate();
|
||||||
if (!this.activated) {
|
const pluginId = this.definition.id;
|
||||||
this.activated = true;
|
if (!this.connected && !this.realClient.isBackgroundPlugin(pluginId)) {
|
||||||
this.events.emit('activate');
|
this.realClient.initPlugin(pluginId); // will call connect() if needed
|
||||||
const pluginId = this.definition.id;
|
|
||||||
if (!this.realClient.isBackgroundPlugin(pluginId)) {
|
|
||||||
this.realClient.initPlugin(pluginId); // will call connect() if needed
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// the plugin is deselected in the UI
|
// the plugin is deselected in the UI
|
||||||
deactivate() {
|
deactivate() {
|
||||||
if (this.destroyed) {
|
super.deactivate();
|
||||||
// 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');
|
|
||||||
}
|
|
||||||
const pluginId = this.definition.id;
|
const pluginId = this.definition.id;
|
||||||
if (this.connected && !this.realClient.isBackgroundPlugin(pluginId)) {
|
if (this.connected && !this.realClient.isBackgroundPlugin(pluginId)) {
|
||||||
this.realClient.deinitPlugin(pluginId);
|
this.realClient.deinitPlugin(pluginId);
|
||||||
@@ -243,13 +164,10 @@ export class SandyPluginInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.assertNotDestroyed();
|
|
||||||
this.deactivate();
|
|
||||||
if (this.connected) {
|
if (this.connected) {
|
||||||
this.realClient.deinitPlugin(this.definition.id);
|
this.realClient.deinitPlugin(this.definition.id);
|
||||||
}
|
}
|
||||||
this.events.emit('destroy');
|
super.destroy();
|
||||||
this.destroyed = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
receiveMessages(messages: Message[]) {
|
receiveMessages(messages: Message[]) {
|
||||||
@@ -262,30 +180,6 @@ export class SandyPluginInstance {
|
|||||||
return '[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() {
|
private assertConnected() {
|
||||||
this.assertNotDestroyed();
|
this.assertNotDestroyed();
|
||||||
if (!this.connected) {
|
if (!this.connected) {
|
||||||
|
|||||||
153
desktop/flipper-plugin/src/plugin/PluginBase.tsx
Normal file
153
desktop/flipper-plugin/src/plugin/PluginBase.tsx
Normal file
@@ -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<string, any>;
|
||||||
|
// all the atoms that should be serialized when making an export / import
|
||||||
|
rootStates: Record<string, Atom<any>> = {};
|
||||||
|
// last seen deeplink
|
||||||
|
lastDeeplink?: any;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
definition: SandyPluginDefinition,
|
||||||
|
initialStates?: Record<string, any>,
|
||||||
|
) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
import {produce} from 'immer';
|
import {produce} from 'immer';
|
||||||
import {useState, useEffect} from 'react';
|
import {useState, useEffect} from 'react';
|
||||||
import {getCurrentPluginInstance} from '../plugin/Plugin';
|
import {getCurrentPluginInstance} from '../plugin/PluginBase';
|
||||||
|
|
||||||
export type Atom<T> = {
|
export type Atom<T> = {
|
||||||
get(): T;
|
get(): T;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {PluginDetails} from 'flipper-plugin-lib';
|
|||||||
import {
|
import {
|
||||||
RealFlipperClient,
|
RealFlipperClient,
|
||||||
SandyPluginInstance,
|
SandyPluginInstance,
|
||||||
FlipperClient,
|
PluginClient,
|
||||||
} from '../plugin/Plugin';
|
} from '../plugin/Plugin';
|
||||||
import {
|
import {
|
||||||
SandyPluginDefinition,
|
SandyPluginDefinition,
|
||||||
@@ -34,6 +34,7 @@ import {
|
|||||||
RealFlipperDevice,
|
RealFlipperDevice,
|
||||||
DeviceLogListener,
|
DeviceLogListener,
|
||||||
} from '../plugin/DevicePlugin';
|
} from '../plugin/DevicePlugin';
|
||||||
|
import {BasePluginInstance} from '../plugin/PluginBase';
|
||||||
|
|
||||||
type Renderer = RenderResult<typeof queries>;
|
type Renderer = RenderResult<typeof queries>;
|
||||||
|
|
||||||
@@ -49,17 +50,44 @@ type ExtractClientType<Module extends FlipperPluginModule<any>> = Parameters<
|
|||||||
|
|
||||||
type ExtractMethodsType<
|
type ExtractMethodsType<
|
||||||
Module extends FlipperPluginModule<any>
|
Module extends FlipperPluginModule<any>
|
||||||
> = ExtractClientType<Module> extends FlipperClient<any, infer Methods>
|
> = ExtractClientType<Module> extends PluginClient<any, infer Methods>
|
||||||
? Methods
|
? Methods
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
type ExtractEventsType<
|
type ExtractEventsType<
|
||||||
Module extends FlipperPluginModule<any>
|
Module extends FlipperPluginModule<any>
|
||||||
> = ExtractClientType<Module> extends FlipperClient<infer Events, any>
|
> = ExtractClientType<Module> extends PluginClient<infer Events, any>
|
||||||
? Events
|
? Events
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
interface StartPluginResult<Module extends FlipperPluginModule<any>> {
|
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<Module extends FlipperPluginModule<any>>
|
||||||
|
extends BasePluginResult {
|
||||||
/**
|
/**
|
||||||
* the instantiated plugin for this test
|
* the instantiated plugin for this test
|
||||||
*/
|
*/
|
||||||
@@ -68,15 +96,6 @@ interface StartPluginResult<Module extends FlipperPluginModule<any>> {
|
|||||||
* module, from which any other exposed methods can be accessed during testing
|
* module, from which any other exposed methods can be accessed during testing
|
||||||
*/
|
*/
|
||||||
module: Module;
|
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
|
* Emulates the 'onConnect' event
|
||||||
*/
|
*/
|
||||||
@@ -85,10 +104,6 @@ interface StartPluginResult<Module extends FlipperPluginModule<any>> {
|
|||||||
* Emulatese the 'onDisconnect' event
|
* Emulatese the 'onDisconnect' event
|
||||||
*/
|
*/
|
||||||
disconnect(): void;
|
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.
|
* Jest Stub that is called whenever client.send() is called by the plugin.
|
||||||
* Use send.mockImplementation(function) to intercept the calls.
|
* Use send.mockImplementation(function) to intercept the calls.
|
||||||
@@ -117,13 +132,10 @@ interface StartPluginResult<Module extends FlipperPluginModule<any>> {
|
|||||||
params: any; // afaik we can't type this :-(
|
params: any; // afaik we can't type this :-(
|
||||||
}[],
|
}[],
|
||||||
): void;
|
): void;
|
||||||
|
|
||||||
triggerDeepLink(deeplink: unknown): void;
|
|
||||||
|
|
||||||
exportState(): any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StartDevicePluginResult<Module extends FlipperDevicePluginModule> {
|
interface StartDevicePluginResult<Module extends FlipperDevicePluginModule>
|
||||||
|
extends BasePluginResult {
|
||||||
/**
|
/**
|
||||||
* the instantiated plugin for this test
|
* the instantiated plugin for this test
|
||||||
*/
|
*/
|
||||||
@@ -132,30 +144,10 @@ interface StartDevicePluginResult<Module extends FlipperDevicePluginModule> {
|
|||||||
* module, from which any other exposed methods can be accessed during testing
|
* module, from which any other exposed methods can be accessed during testing
|
||||||
*/
|
*/
|
||||||
module: Module;
|
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
|
* Emulates sending a log message arriving from the device
|
||||||
*/
|
*/
|
||||||
sendLogEntry(logEntry: DeviceLogEntry): void;
|
sendLogEntry(logEntry: DeviceLogEntry): void;
|
||||||
/**
|
|
||||||
* Emulates triggering a deeplik
|
|
||||||
*/
|
|
||||||
triggerDeepLink(deeplink: unknown): void;
|
|
||||||
/**
|
|
||||||
* Grabs the current (exportable) state
|
|
||||||
*/
|
|
||||||
exportState(): any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function startPlugin<Module extends FlipperPluginModule<any>>(
|
export function startPlugin<Module extends FlipperPluginModule<any>>(
|
||||||
@@ -198,28 +190,13 @@ export function startPlugin<Module extends FlipperPluginModule<any>>(
|
|||||||
definition,
|
definition,
|
||||||
options?.initialState,
|
options?.initialState,
|
||||||
);
|
);
|
||||||
if (options?.isBackgroundPlugin) {
|
|
||||||
pluginInstance.connect(); // otherwise part of activate
|
|
||||||
}
|
|
||||||
// we start activated
|
|
||||||
pluginInstance.activate();
|
|
||||||
|
|
||||||
const res: StartPluginResult<Module> = {
|
const res: StartPluginResult<Module> = {
|
||||||
module,
|
...createBasePluginResult(pluginInstance),
|
||||||
instance: pluginInstance.instanceApi,
|
instance: pluginInstance.instanceApi,
|
||||||
activate() {
|
module,
|
||||||
pluginInstance.activate();
|
|
||||||
pluginInstance.connect();
|
|
||||||
},
|
|
||||||
deactivate() {
|
|
||||||
pluginInstance.deactivate();
|
|
||||||
if (!fakeFlipper.isBackgroundPlugin) {
|
|
||||||
pluginInstance.disconnect();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
connect: () => pluginInstance.connect(),
|
connect: () => pluginInstance.connect(),
|
||||||
disconnect: () => pluginInstance.disconnect(),
|
disconnect: () => pluginInstance.disconnect(),
|
||||||
destroy: () => pluginInstance.destroy(),
|
|
||||||
onSend: sendStub,
|
onSend: sendStub,
|
||||||
sendEvent: (event, params) => {
|
sendEvent: (event, params) => {
|
||||||
res.sendEvents([
|
res.sendEvents([
|
||||||
@@ -234,13 +211,13 @@ export function startPlugin<Module extends FlipperPluginModule<any>>(
|
|||||||
pluginInstance.receiveMessages(messages as any);
|
pluginInstance.receiveMessages(messages as any);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
exportState: () => pluginInstance.exportState(),
|
|
||||||
triggerDeepLink: (deepLink: unknown) => {
|
|
||||||
pluginInstance.triggerDeepLink(deepLink);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
// @ts-ignore
|
(res as any)._backingInstance = pluginInstance;
|
||||||
res._backingInstance = pluginInstance;
|
// we start activated
|
||||||
|
if (options?.isBackgroundPlugin) {
|
||||||
|
pluginInstance.connect(); // otherwise part of activate
|
||||||
|
}
|
||||||
|
pluginInstance.activate();
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,8 +229,7 @@ export function renderPlugin<Module extends FlipperPluginModule<any>>(
|
|||||||
act: (cb: () => void) => void;
|
act: (cb: () => void) => void;
|
||||||
} {
|
} {
|
||||||
const res = startPlugin(module, options);
|
const res = startPlugin(module, options);
|
||||||
// @ts-ignore hidden api
|
const pluginInstance: SandyPluginInstance = (res as any)._backingInstance;
|
||||||
const pluginInstance: SandyPluginInstance = res._backingInstance;
|
|
||||||
|
|
||||||
const renderer = render(<SandyPluginRenderer plugin={pluginInstance} />);
|
const renderer = render(<SandyPluginRenderer plugin={pluginInstance} />);
|
||||||
|
|
||||||
@@ -288,27 +264,20 @@ export function startDevicePlugin<Module extends FlipperDevicePluginModule>(
|
|||||||
definition,
|
definition,
|
||||||
options?.initialState,
|
options?.initialState,
|
||||||
);
|
);
|
||||||
// we start connected
|
|
||||||
pluginInstance.activate();
|
|
||||||
|
|
||||||
const res: StartDevicePluginResult<Module> = {
|
const res: StartDevicePluginResult<Module> = {
|
||||||
|
...createBasePluginResult(pluginInstance),
|
||||||
module,
|
module,
|
||||||
instance: pluginInstance.instanceApi,
|
instance: pluginInstance.instanceApi,
|
||||||
activate: () => pluginInstance.activate(),
|
|
||||||
deactivate: () => pluginInstance.deactivate(),
|
|
||||||
destroy: () => pluginInstance.destroy(),
|
|
||||||
sendLogEntry: (entry) => {
|
sendLogEntry: (entry) => {
|
||||||
act(() => {
|
act(() => {
|
||||||
testDevice.addLogEntry(entry);
|
testDevice.addLogEntry(entry);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
exportState: () => pluginInstance.exportState(),
|
|
||||||
triggerDeepLink: (deepLink: unknown) => {
|
|
||||||
pluginInstance.triggerDeepLink(deepLink);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
// @ts-ignore
|
(res as any)._backingInstance = pluginInstance;
|
||||||
res._backingInstance = pluginInstance;
|
// we start connected
|
||||||
|
pluginInstance.activate();
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,7 +290,8 @@ export function renderDevicePlugin<Module extends FlipperDevicePluginModule>(
|
|||||||
} {
|
} {
|
||||||
const res = startDevicePlugin(module, options);
|
const res = startDevicePlugin(module, options);
|
||||||
// @ts-ignore hidden api
|
// @ts-ignore hidden api
|
||||||
const pluginInstance: SandyDevicePluginInstance = res._backingInstance;
|
const pluginInstance: SandyDevicePluginInstance = (res as any)
|
||||||
|
._backingInstance;
|
||||||
|
|
||||||
const renderer = render(<SandyPluginRenderer plugin={pluginInstance} />);
|
const renderer = render(<SandyPluginRenderer plugin={pluginInstance} />);
|
||||||
|
|
||||||
@@ -336,6 +306,20 @@ export function renderDevicePlugin<Module extends FlipperDevicePluginModule>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
export function createMockPluginDetails(
|
||||||
details?: Partial<PluginDetails>,
|
details?: Partial<PluginDetails>,
|
||||||
): PluginDetails {
|
): PluginDetails {
|
||||||
|
|||||||
Reference in New Issue
Block a user