Skip to content

Commit

Permalink
Allow unsubscribing from client events
Browse files Browse the repository at this point in the history
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
  • Loading branch information
aigoncharov authored and facebook-github-bot committed Jul 12, 2023
1 parent 7644c90 commit f59a2e5
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 21 deletions.
43 changes: 35 additions & 8 deletions desktop/flipper-plugin-core/src/plugin/Plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* For most plugins this event is fired if the user selects the plugin,
* 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.
* Will also be fired before the plugin is cleaned up if the connection is currently active:
* - when the client disconnects
* - 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.
*
* 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.
*
* @returns an unsubscribe callback
*/
onMessage<Event extends keyof Events>(
event: Event,
callback: (params: Events[Event]) => void,
): void;
): () => void;

/**
* 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.
*
* @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
Expand Down Expand Up @@ -192,10 +202,18 @@ export class SandyPluginInstance extends BasePluginInstance {
return self.connected.get();
},
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) => {
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) => {
this.assertConnected();
Expand All @@ -207,10 +225,19 @@ export class SandyPluginInstance extends BasePluginInstance {
);
},
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) => {
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) => {
return await realClient.supportsMethod(
Expand Down
62 changes: 49 additions & 13 deletions desktop/flipper-plugin-core/src/plugin/PluginBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,24 @@ export interface BasePluginClient<

/**
* 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.
*
* @returns an unsubscribe callback
*/
onDeactivate(cb: () => void): void;
onDeactivate(cb: () => void): () => void;

/**
* 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.
Expand All @@ -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.
* 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.
*
* @returns an unsubscribe callback
*/
onReady(handler: () => void): void;
onReady(handler: () => void): () => void;

/**
* Register menu entries in the Flipper toolbar
Expand Down Expand Up @@ -141,14 +149,18 @@ export interface BasePluginClient<
* 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.
* See `onServerAddStop`.
*
* @returns an unsubscribe callback
*/
onServerAddOnStart(callback: () => void): void;
onServerAddOnStart(callback: () => void): () => void;

/**
* Triggered when a server 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.
Expand Down Expand Up @@ -333,13 +345,25 @@ export abstract class BasePluginInstance {
pluginKey: this.pluginKey,
device: this.device,
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) => {
const cbWrapped = batched(cb);
this.events.on('deactivate', batched(cb));
return () => {
this.events.off('deactivate', cbWrapped);
};
},
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) => {
this.events.on('destroy', batched(cb));
Expand All @@ -357,7 +381,11 @@ export abstract class BasePluginInstance {
this.importHandler = 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) => {
for (const entry of entries) {
Expand Down Expand Up @@ -401,16 +429,24 @@ export abstract class BasePluginInstance {
},
logger: this.flipperLib.logger,
onServerAddOnStart: (cb) => {
this.events.on('serverAddOnStart', batched(cb));
const cbWrapped = batched(cb);
this.events.on('serverAddOnStart', cbWrapped);
if (this.serverAddOnStarted) {
batched(cb)();
cbWrapped();
}
return () => {
this.events.off('serverAddOnStart', cbWrapped);
};
},
onServerAddOnStop: (cb) => {
this.events.on('serverAddOnStop', batched(cb));
const cbWrapped = batched(cb);
this.events.on('serverAddOnStop', cbWrapped);
if (this.serverAddOnStopped) {
batched(cb)();
cbWrapped();
}
return () => {
this.events.off('serverAddOnStop', cbWrapped);
};
},
sendToServerAddOn: (method, params) =>
this.serverAddOnControls.sendMessage(
Expand Down

0 comments on commit f59a2e5

Please sign in to comment.