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,
|
||||
SandyPluginDefinition,
|
||||
} from 'flipper-plugin';
|
||||
import {DevicePluginMap} from '../plugin';
|
||||
import {DevicePluginMap, FlipperDevicePlugin} from '../plugin';
|
||||
|
||||
export type DeviceShell = {
|
||||
stdout: stream.Readable;
|
||||
@@ -179,13 +179,18 @@ export default class BaseDevice {
|
||||
const plugins = Array.from(devicePlugins.values());
|
||||
plugins.sort(sortPluginsByName);
|
||||
for (const plugin of plugins) {
|
||||
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: pass initial state if applicable
|
||||
); // TODO T70582933: pass initial state if applicable
|
||||
}
|
||||
} else {
|
||||
if (plugin.supportsDevice(this)) {
|
||||
@@ -194,4 +199,3 @@ export default class BaseDevice {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +277,6 @@ const requirePluginInternal = (
|
||||
|
||||
if (pluginDetails.flipperSDKVersion) {
|
||||
// Sandy plugin
|
||||
// TODO: suppor device Plugins T68738317
|
||||
return new SandyPluginDefinition(pluginDetails, plugin);
|
||||
} else {
|
||||
// classic plugin
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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)
|
||||
: {};
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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<number>;
|
||||
};
|
||||
|
||||
export function plugin(client: FlipperClient<Events, Methods>) {
|
||||
export function plugin(client: PluginClient<Events, Methods>) {
|
||||
const connectStub = jest.fn();
|
||||
const disconnectStub = jest.fn();
|
||||
const activateStub = jest.fn();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<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(
|
||||
realDevice: RealFlipperDevice,
|
||||
definition: SandyPluginDefinition,
|
||||
initialStates?: Record<string, any>,
|
||||
) {
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<string, any>;
|
||||
type MethodsContract = Record<string, (params: any) => Promise<any>>;
|
||||
@@ -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<Events, Methods>) => object;
|
||||
> = (client: PluginClient<Events, Methods>) => 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<any, any>;
|
||||
/** the original plugin definition */
|
||||
definition: SandyPluginDefinition;
|
||||
/** the plugin instance api as used inside components and such */
|
||||
instanceApi: any;
|
||||
|
||||
activated = false;
|
||||
client: PluginClient<any, any>;
|
||||
/** connection alive? */
|
||||
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(
|
||||
realClient: RealFlipperClient,
|
||||
definition: SandyPluginDefinition,
|
||||
initialStates?: Record<string, any>,
|
||||
) {
|
||||
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');
|
||||
super.activate();
|
||||
const pluginId = this.definition.id;
|
||||
if (!this.realClient.isBackgroundPlugin(pluginId)) {
|
||||
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) {
|
||||
|
||||
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 {useState, useEffect} from 'react';
|
||||
import {getCurrentPluginInstance} from '../plugin/Plugin';
|
||||
import {getCurrentPluginInstance} from '../plugin/PluginBase';
|
||||
|
||||
export type Atom<T> = {
|
||||
get(): T;
|
||||
|
||||
@@ -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<typeof queries>;
|
||||
|
||||
@@ -49,17 +50,44 @@ type ExtractClientType<Module extends FlipperPluginModule<any>> = Parameters<
|
||||
|
||||
type ExtractMethodsType<
|
||||
Module extends FlipperPluginModule<any>
|
||||
> = ExtractClientType<Module> extends FlipperClient<any, infer Methods>
|
||||
> = ExtractClientType<Module> extends PluginClient<any, infer Methods>
|
||||
? Methods
|
||||
: never;
|
||||
|
||||
type ExtractEventsType<
|
||||
Module extends FlipperPluginModule<any>
|
||||
> = ExtractClientType<Module> extends FlipperClient<infer Events, any>
|
||||
> = ExtractClientType<Module> extends PluginClient<infer Events, any>
|
||||
? Events
|
||||
: 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
|
||||
*/
|
||||
@@ -68,15 +96,6 @@ interface StartPluginResult<Module extends FlipperPluginModule<any>> {
|
||||
* 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<Module extends FlipperPluginModule<any>> {
|
||||
* 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<Module extends FlipperPluginModule<any>> {
|
||||
params: any; // afaik we can't type this :-(
|
||||
}[],
|
||||
): 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
|
||||
*/
|
||||
@@ -132,30 +144,10 @@ interface StartDevicePluginResult<Module extends FlipperDevicePluginModule> {
|
||||
* 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<Module extends FlipperPluginModule<any>>(
|
||||
@@ -198,28 +190,13 @@ export function startPlugin<Module extends FlipperPluginModule<any>>(
|
||||
definition,
|
||||
options?.initialState,
|
||||
);
|
||||
if (options?.isBackgroundPlugin) {
|
||||
pluginInstance.connect(); // otherwise part of activate
|
||||
}
|
||||
// we start activated
|
||||
pluginInstance.activate();
|
||||
|
||||
const res: StartPluginResult<Module> = {
|
||||
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<Module extends FlipperPluginModule<any>>(
|
||||
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<Module extends FlipperPluginModule<any>>(
|
||||
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(<SandyPluginRenderer plugin={pluginInstance} />);
|
||||
|
||||
@@ -288,27 +264,20 @@ export function startDevicePlugin<Module extends FlipperDevicePluginModule>(
|
||||
definition,
|
||||
options?.initialState,
|
||||
);
|
||||
// we start connected
|
||||
pluginInstance.activate();
|
||||
|
||||
const res: StartDevicePluginResult<Module> = {
|
||||
...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<Module extends FlipperDevicePluginModule>(
|
||||
} {
|
||||
const res = startDevicePlugin(module, options);
|
||||
// @ts-ignore hidden api
|
||||
const pluginInstance: SandyDevicePluginInstance = res._backingInstance;
|
||||
const pluginInstance: SandyDevicePluginInstance = (res as any)
|
||||
._backingInstance;
|
||||
|
||||
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(
|
||||
details?: Partial<PluginDetails>,
|
||||
): PluginDetails {
|
||||
|
||||
Reference in New Issue
Block a user