Allow unsubscribing from client events

Summary: In the next diffs, we are going to show a message only once when the plugin gets activated. This diff adds a way to unsubscribe from Flipper events after the first execution.

Reviewed By: LukeDefeo

Differential Revision: D47366239

fbshipit-source-id: 18cb99df865f9cf26c055a99b7d1b7058dcb123c
This commit is contained in:
Andrey Goncharov
2023-07-12 08:56:50 -07:00
committed by Facebook GitHub Bot
parent 7644c9092a
commit f59a2e5fba
2 changed files with 84 additions and 21 deletions

View File

@@ -59,34 +59,44 @@ export interface PluginClient<
* 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,
* for background plugins when the initial connection is made. * for background plugins when the initial connection is made.
*
* @returns an unsubscribe callback
*/ */
onConnect(cb: () => void): void; onConnect(cb: () => void): () => void;
/** /**
* The counterpart of the `onConnect` handler. * The counterpart of the `onConnect` handler.
* Will also be fired before the plugin is cleaned up if the connection is currently active: * Will also be fired before the plugin is cleaned up if the connection is currently active:
* - when the client disconnects * - when the client disconnects
* - when the plugin is disabled * - when the plugin is disabled
*
* @returns an unsubscribe callback
*/ */
onDisconnect(cb: () => void): void; onDisconnect(cb: () => void): () => void;
/** /**
* Subscribe to a specific event arriving from the device. * Subscribe to a specific event arriving from the device.
* *
* Messages can only arrive if the plugin is enabled and connected. * Messages can only arrive if the plugin is enabled and connected.
* For background plugins messages will be batched and arrive the next time the plugin is connected. * For background plugins messages will be batched and arrive the next time the plugin is connected.
*
* @returns an unsubscribe callback
*/ */
onMessage<Event extends keyof Events>( onMessage<Event extends keyof Events>(
event: Event, event: Event,
callback: (params: Events[Event]) => void, callback: (params: Events[Event]) => void,
): void; ): () => void;
/** /**
* Subscribe to all messages arriving from the devices not handled by another listener. * Subscribe to all messages arriving from the devices not handled by another listener.
* *
* This handler is untyped, and onMessage should be favored over using onUnhandledMessage if the event name is known upfront. * This handler is untyped, and onMessage should be favored over using onUnhandledMessage if the event name is known upfront.
*
* @returns an unsubscribe callback
*/ */
onUnhandledMessage(callback: (event: string, params: any) => void): void; onUnhandledMessage(
callback: (event: string, params: any) => void,
): () => void;
/** /**
* Send a message to the connected client * Send a message to the connected client
@@ -192,10 +202,18 @@ export class SandyPluginInstance extends BasePluginInstance {
return self.connected.get(); return self.connected.get();
}, },
onConnect: (cb) => { onConnect: (cb) => {
this.events.on('connect', batched(cb)); const cbWrapped = batched(cb);
this.events.on('connect', cbWrapped);
return () => {
this.events.off('connect', cbWrapped);
};
}, },
onDisconnect: (cb) => { onDisconnect: (cb) => {
this.events.on('disconnect', batched(cb)); const cbWrapped = batched(cb);
this.events.on('disconnect', cbWrapped);
return () => {
this.events.off('disconnect', cbWrapped);
};
}, },
send: async (method, params) => { send: async (method, params) => {
this.assertConnected(); this.assertConnected();
@@ -207,10 +225,19 @@ export class SandyPluginInstance extends BasePluginInstance {
); );
}, },
onMessage: (event, cb) => { onMessage: (event, cb) => {
this.events.on(`event-${event.toString()}`, batched(cb)); const cbWrapped = batched(cb);
const eventName = `event-${event.toString()}`;
this.events.on(eventName, cbWrapped);
return () => {
this.events.off(eventName, cbWrapped);
};
}, },
onUnhandledMessage: (cb) => { onUnhandledMessage: (cb) => {
this.events.on('unhandled-event', batched(cb)); const cbWrapped = batched(cb);
this.events.on('unhandled-event', cbWrapped);
return () => {
this.events.off('unhandled-event', cbWrapped);
};
}, },
supportsMethod: async (method) => { supportsMethod: async (method) => {
return await realClient.supportsMethod( return await realClient.supportsMethod(

View File

@@ -46,18 +46,24 @@ export interface BasePluginClient<
/** /**
* the onActivate event is fired whenever the plugin is actived in the UI * the onActivate event is fired whenever the plugin is actived in the UI
*
* @returns an unsubscribe callback
*/ */
onActivate(cb: () => void): void; onActivate(cb: () => void): () => void;
/** /**
* The counterpart of the `onActivate` handler. * The counterpart of the `onActivate` handler.
*
* @returns an unsubscribe callback
*/ */
onDeactivate(cb: () => void): void; onDeactivate(cb: () => void): () => void;
/** /**
* Triggered when this plugin is opened through a deeplink * Triggered when this plugin is opened through a deeplink
*
* @returns an unsubscribe callback
*/ */
onDeepLink(cb: (deepLink: unknown) => void): void; onDeepLink(cb: (deepLink: unknown) => void): () => void;
/** /**
* Triggered when the current plugin is being exported and should create a snapshot of the state exported. * Triggered when the current plugin is being exported and should create a snapshot of the state exported.
@@ -78,8 +84,10 @@ export interface BasePluginClient<
* The `onReady` event is triggered immediately after a plugin has been initialized and any pending state was restored. * The `onReady` event is triggered immediately after a plugin has been initialized and any pending state was restored.
* This event fires after `onImport` / the interpretation of any `persist` flags and indicates that the initialization process has finished. * This event fires after `onImport` / the interpretation of any `persist` flags and indicates that the initialization process has finished.
* This event does not signal that the plugin is loaded in the UI yet (see `onActivated`) and does fire before deeplinks (see `onDeepLink`) are handled. * This event does not signal that the plugin is loaded in the UI yet (see `onActivated`) and does fire before deeplinks (see `onDeepLink`) are handled.
*
* @returns an unsubscribe callback
*/ */
onReady(handler: () => void): void; onReady(handler: () => void): () => void;
/** /**
* Register menu entries in the Flipper toolbar * Register menu entries in the Flipper toolbar
@@ -141,14 +149,18 @@ export interface BasePluginClient<
* You should send messages to the server add-on only after it connects. * You should send messages to the server add-on only after it connects.
* Do not forget to stop all communication when the add-on stops. * Do not forget to stop all communication when the add-on stops.
* See `onServerAddStop`. * See `onServerAddStop`.
*
* @returns an unsubscribe callback
*/ */
onServerAddOnStart(callback: () => void): void; onServerAddOnStart(callback: () => void): () => void;
/** /**
* Triggered when a server add-on stops. * Triggered when a server add-on stops.
* You should stop all communication with the server add-on when the add-on stops. * You should stop all communication with the server add-on when the add-on stops.
*
* @returns an unsubscribe callback
*/ */
onServerAddOnStop(callback: () => void): void; onServerAddOnStop(callback: () => void): () => void;
/** /**
* Subscribe to a specific event arriving from the server add-on. * Subscribe to a specific event arriving from the server add-on.
@@ -333,13 +345,25 @@ export abstract class BasePluginInstance {
pluginKey: this.pluginKey, pluginKey: this.pluginKey,
device: this.device, device: this.device,
onActivate: (cb) => { onActivate: (cb) => {
this.events.on('activate', batched(cb)); const cbWrapped = batched(cb);
this.events.on('activate', cbWrapped);
return () => {
this.events.off('activate', cbWrapped);
};
}, },
onDeactivate: (cb) => { onDeactivate: (cb) => {
const cbWrapped = batched(cb);
this.events.on('deactivate', batched(cb)); this.events.on('deactivate', batched(cb));
return () => {
this.events.off('deactivate', cbWrapped);
};
}, },
onDeepLink: (cb) => { onDeepLink: (cb) => {
this.events.on('deeplink', batched(cb)); const cbWrapped = batched(cb);
this.events.on('deeplink', cbWrapped);
return () => {
this.events.off('deeplink', cbWrapped);
};
}, },
onDestroy: (cb) => { onDestroy: (cb) => {
this.events.on('destroy', batched(cb)); this.events.on('destroy', batched(cb));
@@ -357,7 +381,11 @@ export abstract class BasePluginInstance {
this.importHandler = cb; this.importHandler = cb;
}, },
onReady: (cb) => { onReady: (cb) => {
this.events.on('ready', batched(cb)); const cbWrapped = batched(cb);
this.events.on('ready', cbWrapped);
return () => {
this.events.off('ready', cbWrapped);
};
}, },
addMenuEntry: (...entries) => { addMenuEntry: (...entries) => {
for (const entry of entries) { for (const entry of entries) {
@@ -401,16 +429,24 @@ export abstract class BasePluginInstance {
}, },
logger: this.flipperLib.logger, logger: this.flipperLib.logger,
onServerAddOnStart: (cb) => { onServerAddOnStart: (cb) => {
this.events.on('serverAddOnStart', batched(cb)); const cbWrapped = batched(cb);
this.events.on('serverAddOnStart', cbWrapped);
if (this.serverAddOnStarted) { if (this.serverAddOnStarted) {
batched(cb)(); cbWrapped();
} }
return () => {
this.events.off('serverAddOnStart', cbWrapped);
};
}, },
onServerAddOnStop: (cb) => { onServerAddOnStop: (cb) => {
this.events.on('serverAddOnStop', batched(cb)); const cbWrapped = batched(cb);
this.events.on('serverAddOnStop', cbWrapped);
if (this.serverAddOnStopped) { if (this.serverAddOnStopped) {
batched(cb)(); cbWrapped();
} }
return () => {
this.events.off('serverAddOnStop', cbWrapped);
};
}, },
sendToServerAddOn: (method, params) => sendToServerAddOn: (method, params) =>
this.serverAddOnControls.sendMessage( this.serverAddOnControls.sendMessage(