Support receiving messages in Sandy plugins
Summary: This diffs adds the capability to listen to messages in Sandy plugins. Although API wise it looks more like the old `this.subscribe`, semantically it behaves like the `persistedStateReducer`; messages are queued if the plugin is enabled but not active. Reviewed By: nikoant Differential Revision: D22282711 fbshipit-source-id: 885faa702fe779ac8d593c1d224b2be13e688d47
This commit is contained in:
committed by
Facebook GitHub Bot
parent
6c79408b0f
commit
bb0c8e0df0
@@ -24,6 +24,9 @@ export function plugin(client: FlipperClient<Events, Methods>) {
|
||||
const connectStub = jest.fn();
|
||||
const disconnectStub = jest.fn();
|
||||
const destroyStub = jest.fn();
|
||||
const state = {
|
||||
count: 0,
|
||||
};
|
||||
|
||||
// TODO: add tests for sending and receiving data T68683442
|
||||
// including typescript assertions
|
||||
@@ -31,12 +34,23 @@ export function plugin(client: FlipperClient<Events, Methods>) {
|
||||
client.onConnect(connectStub);
|
||||
client.onDisconnect(disconnectStub);
|
||||
client.onDestroy(destroyStub);
|
||||
client.onMessage('inc', ({delta}) => {
|
||||
state.count += delta;
|
||||
});
|
||||
|
||||
function _unused_JustTypeChecks() {
|
||||
// @ts-expect-error Argument of type '"bla"' is not assignable
|
||||
client.send('bla', {});
|
||||
// @ts-expect-error Argument of type '{ stuff: string; }' is not assignable to parameter of type
|
||||
client.send('currentState', {stuff: 'nope'});
|
||||
// @ts-expect-error
|
||||
client.onMessage('stuff', (_params) => {
|
||||
// noop
|
||||
});
|
||||
client.onMessage('inc', (params) => {
|
||||
// @ts-expect-error
|
||||
params.bla;
|
||||
});
|
||||
}
|
||||
|
||||
async function getCurrentState() {
|
||||
@@ -48,6 +62,7 @@ export function plugin(client: FlipperClient<Events, Methods>) {
|
||||
destroyStub,
|
||||
disconnectStub,
|
||||
getCurrentState,
|
||||
state,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -93,3 +93,11 @@ test('a plugin cannot send messages after being disconnected', async () => {
|
||||
}
|
||||
expect(threw).toBeTruthy();
|
||||
});
|
||||
|
||||
test('a plugin can receive messages', async () => {
|
||||
const {instance, sendEvent} = TestUtils.startPlugin(testPlugin);
|
||||
expect(instance.state.count).toBe(0);
|
||||
|
||||
sendEvent('inc', {delta: 2});
|
||||
expect(instance.state.count).toBe(2);
|
||||
});
|
||||
|
||||
@@ -13,6 +13,11 @@ import {EventEmitter} from 'events';
|
||||
type EventsContract = Record<string, any>;
|
||||
type MethodsContract = Record<string, (params: any) => Promise<any>>;
|
||||
|
||||
type Message = {
|
||||
method: string;
|
||||
params?: any;
|
||||
};
|
||||
|
||||
/**
|
||||
* API available to a plugin factory
|
||||
*/
|
||||
@@ -47,6 +52,17 @@ export interface FlipperClient<
|
||||
method: Method,
|
||||
params: Parameters<Methods[Method]>[0],
|
||||
): ReturnType<Methods[Method]>;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
onMessage<Event extends keyof Events>(
|
||||
event: Event,
|
||||
callback: (params: Events[Event]) => void,
|
||||
): void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,6 +89,10 @@ export type FlipperPluginFactory<
|
||||
export type FlipperPluginComponent = React.FC<{}>;
|
||||
|
||||
export class SandyPluginInstance {
|
||||
static is(thing: any): thing is SandyPluginInstance {
|
||||
return thing instanceof SandyPluginInstance;
|
||||
}
|
||||
|
||||
/** base client provided by Flipper */
|
||||
realClient: RealFlipperClient;
|
||||
/** client that is bound to this instance */
|
||||
@@ -111,6 +131,9 @@ export class SandyPluginInstance {
|
||||
params as any,
|
||||
);
|
||||
},
|
||||
onMessage: (event, callback) => {
|
||||
this.events.on('event-' + event, callback);
|
||||
},
|
||||
};
|
||||
this.instanceApi = definition.module.plugin(this.client);
|
||||
}
|
||||
@@ -156,6 +179,12 @@ export class SandyPluginInstance {
|
||||
this.destroyed = true;
|
||||
}
|
||||
|
||||
receiveMessages(messages: Message[]) {
|
||||
messages.forEach((message) => {
|
||||
this.events.emit('event-' + message.method, message.params);
|
||||
});
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
this.assertNotDestroyed();
|
||||
// TODO: T68683449
|
||||
|
||||
@@ -36,12 +36,19 @@ interface StartPluginOptions {
|
||||
type ExtractClientType<Module extends FlipperPluginModule<any>> = Parameters<
|
||||
Module['plugin']
|
||||
>[0];
|
||||
|
||||
type ExtractMethodsType<
|
||||
Module extends FlipperPluginModule<any>
|
||||
> = ExtractClientType<Module> extends FlipperClient<any, infer Methods>
|
||||
? Methods
|
||||
: never;
|
||||
|
||||
type ExtractEventsType<
|
||||
Module extends FlipperPluginModule<any>
|
||||
> = ExtractClientType<Module> extends FlipperClient<infer Events, any>
|
||||
? Events
|
||||
: never;
|
||||
|
||||
interface StartPluginResult<Module extends FlipperPluginModule<any>> {
|
||||
/**
|
||||
* the instantiated plugin for this test
|
||||
@@ -73,6 +80,24 @@ interface StartPluginResult<Module extends FlipperPluginModule<any>> {
|
||||
params: Parameters<ExtractMethodsType<Module>[Method]>[0],
|
||||
) => ReturnType<ExtractMethodsType<Module>[Method]>
|
||||
>;
|
||||
/**
|
||||
* Send event to the plugin
|
||||
*/
|
||||
sendEvent<Event extends keyof ExtractEventsType<Module>>(
|
||||
event: Event,
|
||||
params: ExtractEventsType<Module>[Event],
|
||||
): void;
|
||||
/**
|
||||
* Send events to the plugin
|
||||
* The structure used here reflects events that can be recorded
|
||||
* with the pluginRecorder
|
||||
*/
|
||||
sendEvents(
|
||||
events: {
|
||||
method: keyof ExtractEventsType<Module>;
|
||||
params: any; // afaik we can't type this :-(
|
||||
}[],
|
||||
): void;
|
||||
}
|
||||
|
||||
export function startPlugin<Module extends FlipperPluginModule<any>>(
|
||||
@@ -107,16 +132,28 @@ export function startPlugin<Module extends FlipperPluginModule<any>>(
|
||||
// we start connected
|
||||
pluginInstance.connect();
|
||||
|
||||
return {
|
||||
const res: StartPluginResult<Module> = {
|
||||
module,
|
||||
instance: pluginInstance.instanceApi,
|
||||
connect: () => pluginInstance.connect(),
|
||||
disconnect: () => pluginInstance.disconnect(),
|
||||
destroy: () => pluginInstance.destroy(),
|
||||
onSend: sendStub,
|
||||
// @ts-ignore
|
||||
_backingInstance: pluginInstance,
|
||||
sendEvent: (event, params) => {
|
||||
res.sendEvents([
|
||||
{
|
||||
method: event,
|
||||
params,
|
||||
},
|
||||
]);
|
||||
},
|
||||
sendEvents: (messages) => {
|
||||
pluginInstance.receiveMessages(messages as any);
|
||||
},
|
||||
};
|
||||
// @ts-ignore
|
||||
res._backingInstance = pluginInstance;
|
||||
return res;
|
||||
}
|
||||
|
||||
export function renderPlugin<Module extends FlipperPluginModule<any>>(
|
||||
|
||||
Reference in New Issue
Block a user