Draft communication with server add-ons from the client side
Reviewed By: nikoant Differential Revision: D34075379 fbshipit-source-id: 09f575f5cced866ad7b9290d7739ce60f38edeee
This commit is contained in:
committed by
Facebook GitHub Bot
parent
db976d5113
commit
b80755721c
@@ -8,7 +8,12 @@
|
||||
*/
|
||||
|
||||
import {SandyPluginDefinition} from './SandyPluginDefinition';
|
||||
import {BasePluginInstance, BasePluginClient} from './PluginBase';
|
||||
import {
|
||||
BasePluginInstance,
|
||||
BasePluginClient,
|
||||
EventsContract,
|
||||
MethodsContract,
|
||||
} from './PluginBase';
|
||||
import {FlipperLib} from './FlipperLib';
|
||||
import {Atom, ReadOnlyAtom} from '../state/atom';
|
||||
import {
|
||||
@@ -46,12 +51,14 @@ export type DevicePluginPredicate = (device: Device) => boolean;
|
||||
|
||||
export type DevicePluginFactory = (client: DevicePluginClient) => object;
|
||||
|
||||
export interface DevicePluginClient extends BasePluginClient {
|
||||
export interface DevicePluginClient<
|
||||
ServerAddOnEvents extends EventsContract = {},
|
||||
ServerAddOnMethods extends MethodsContract = {},
|
||||
> extends BasePluginClient<ServerAddOnEvents, ServerAddOnMethods> {
|
||||
/**
|
||||
* opens a different plugin by id, optionally providing a deeplink to bring the plugin to a certain state
|
||||
*/
|
||||
selectPlugin(pluginId: string, deeplinkPayload?: unknown): void;
|
||||
|
||||
readonly isConnected: boolean;
|
||||
readonly connected: ReadOnlyAtom<boolean>;
|
||||
}
|
||||
@@ -65,14 +72,21 @@ export class SandyDevicePluginInstance extends BasePluginInstance {
|
||||
readonly client: DevicePluginClient;
|
||||
|
||||
constructor(
|
||||
private readonly serverAddOnControls: ServerAddOnControls,
|
||||
serverAddOnControls: ServerAddOnControls,
|
||||
flipperLib: FlipperLib,
|
||||
definition: SandyPluginDefinition,
|
||||
device: Device,
|
||||
pluginKey: string,
|
||||
initialStates?: Record<string, any>,
|
||||
) {
|
||||
super(flipperLib, definition, device, pluginKey, initialStates);
|
||||
super(
|
||||
serverAddOnControls,
|
||||
flipperLib,
|
||||
definition,
|
||||
device,
|
||||
pluginKey,
|
||||
initialStates,
|
||||
);
|
||||
this.client = {
|
||||
...this.createBasePluginClient(),
|
||||
selectPlugin(pluginId: string, deeplink?: unknown) {
|
||||
@@ -98,31 +112,7 @@ export class SandyDevicePluginInstance extends BasePluginInstance {
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
private startServerAddOn() {
|
||||
const {serverAddOn, name} = this.definition.details;
|
||||
if (serverAddOn) {
|
||||
this.serverAddOnControls.start(name, this.device.serial).catch((e) => {
|
||||
console.warn(
|
||||
'Failed to start a server add on',
|
||||
name,
|
||||
this.device.serial,
|
||||
e,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private stopServerAddOn() {
|
||||
const {serverAddOn, name} = this.definition.details;
|
||||
if (serverAddOn) {
|
||||
this.serverAddOnControls.stop(name, this.device.serial).catch((e) => {
|
||||
console.warn(
|
||||
'Failed to start a server add on',
|
||||
name,
|
||||
this.device.serial,
|
||||
e,
|
||||
);
|
||||
});
|
||||
}
|
||||
protected get serverAddOnOwner() {
|
||||
return this.device.serial;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,15 +8,21 @@
|
||||
*/
|
||||
|
||||
import {SandyPluginDefinition} from './SandyPluginDefinition';
|
||||
import {BasePluginInstance, BasePluginClient} from './PluginBase';
|
||||
import {
|
||||
BasePluginInstance,
|
||||
BasePluginClient,
|
||||
EventsContract,
|
||||
MethodsContract,
|
||||
} from './PluginBase';
|
||||
import {FlipperLib} from './FlipperLib';
|
||||
import {Device} from './DevicePlugin';
|
||||
import {batched} from '../state/batch';
|
||||
import {Atom, createState, ReadOnlyAtom} from '../state/atom';
|
||||
import {ServerAddOnControls} from 'flipper-common';
|
||||
|
||||
type EventsContract = Record<string, any>;
|
||||
type MethodsContract = Record<string, (params: any) => Promise<any>>;
|
||||
type PreventIntersectionWith<Contract extends Record<string, any>> = {
|
||||
[Key in keyof Contract]?: never;
|
||||
};
|
||||
|
||||
type Message = {
|
||||
method: string;
|
||||
@@ -29,7 +35,11 @@ type Message = {
|
||||
export interface PluginClient<
|
||||
Events extends EventsContract = {},
|
||||
Methods extends MethodsContract = {},
|
||||
> extends BasePluginClient {
|
||||
ServerAddOnEvents extends EventsContract &
|
||||
PreventIntersectionWith<Events> = {},
|
||||
ServerAddOnMethods extends MethodsContract &
|
||||
PreventIntersectionWith<Methods> = {},
|
||||
> extends BasePluginClient<ServerAddOnEvents, ServerAddOnMethods> {
|
||||
/**
|
||||
* Identifier that uniquely identifies the connected application
|
||||
*/
|
||||
@@ -125,7 +135,11 @@ export interface RealFlipperClient {
|
||||
export type PluginFactory<
|
||||
Events extends EventsContract,
|
||||
Methods extends MethodsContract,
|
||||
> = (client: PluginClient<Events, Methods>) => object;
|
||||
ServerAddOnEvents extends EventsContract & PreventIntersectionWith<Events>,
|
||||
ServerAddOnMethods extends MethodsContract & PreventIntersectionWith<Methods>,
|
||||
> = (
|
||||
client: PluginClient<Events, Methods, ServerAddOnEvents, ServerAddOnMethods>,
|
||||
) => object;
|
||||
|
||||
export type FlipperPluginComponent = React.FC<{}>;
|
||||
|
||||
@@ -137,19 +151,26 @@ export class SandyPluginInstance extends BasePluginInstance {
|
||||
/** base client provided by Flipper */
|
||||
readonly realClient: RealFlipperClient;
|
||||
/** client that is bound to this instance */
|
||||
readonly client: PluginClient<any, any>;
|
||||
readonly client: PluginClient<any, any, any, any>;
|
||||
/** connection alive? */
|
||||
readonly connected = createState(false);
|
||||
|
||||
constructor(
|
||||
private readonly serverAddOnControls: ServerAddOnControls,
|
||||
serverAddOnControls: ServerAddOnControls,
|
||||
flipperLib: FlipperLib,
|
||||
definition: SandyPluginDefinition,
|
||||
realClient: RealFlipperClient,
|
||||
pluginKey: string,
|
||||
initialStates?: Record<string, any>,
|
||||
) {
|
||||
super(flipperLib, definition, realClient.device, pluginKey, initialStates);
|
||||
super(
|
||||
serverAddOnControls,
|
||||
flipperLib,
|
||||
definition,
|
||||
realClient.device,
|
||||
pluginKey,
|
||||
initialStates,
|
||||
);
|
||||
this.realClient = realClient;
|
||||
this.definition = definition;
|
||||
const self = this;
|
||||
@@ -231,18 +252,7 @@ export class SandyPluginInstance extends BasePluginInstance {
|
||||
connect() {
|
||||
this.assertNotDestroyed();
|
||||
if (!this.connected.get()) {
|
||||
const {serverAddOn, name} = this.definition.details;
|
||||
if (serverAddOn) {
|
||||
this.serverAddOnControls.start(name, this.realClient.id).catch((e) => {
|
||||
console.warn(
|
||||
'Failed to start a server add on',
|
||||
name,
|
||||
this.realClient.id,
|
||||
e,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
this.startServerAddOn();
|
||||
this.connected.set(true);
|
||||
this.events.emit('connect');
|
||||
}
|
||||
@@ -251,18 +261,7 @@ export class SandyPluginInstance extends BasePluginInstance {
|
||||
disconnect() {
|
||||
this.assertNotDestroyed();
|
||||
if (this.connected.get()) {
|
||||
const {serverAddOn, name} = this.definition.details;
|
||||
if (serverAddOn) {
|
||||
this.serverAddOnControls.stop(name, this.realClient.id).catch((e) => {
|
||||
console.warn(
|
||||
'Failed to stop a server add on',
|
||||
name,
|
||||
this.realClient.id,
|
||||
e,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
this.stopServerAddOn();
|
||||
this.connected.set(false);
|
||||
this.events.emit('disconnect');
|
||||
}
|
||||
@@ -289,6 +288,10 @@ export class SandyPluginInstance extends BasePluginInstance {
|
||||
return '[SandyPluginInstance]';
|
||||
}
|
||||
|
||||
protected get serverAddOnOwner() {
|
||||
return this.realClient.id;
|
||||
}
|
||||
|
||||
private assertConnected() {
|
||||
this.assertNotDestroyed();
|
||||
if (
|
||||
|
||||
@@ -18,6 +18,10 @@ import {Idler} from '../utils/Idler';
|
||||
import {Notification} from './Notification';
|
||||
import {Logger} from '../utils/Logger';
|
||||
import {CreatePasteArgs, CreatePasteResult} from './Paste';
|
||||
import {ServerAddOnControls} from 'flipper-common';
|
||||
|
||||
export type EventsContract = Record<string, any>;
|
||||
export type MethodsContract = Record<string, (params: any) => Promise<any>>;
|
||||
|
||||
type StateExportHandler<T = any> = (
|
||||
idler: Idler,
|
||||
@@ -25,7 +29,10 @@ type StateExportHandler<T = any> = (
|
||||
) => Promise<T | undefined | void>;
|
||||
type StateImportHandler<T = any> = (data: T) => void;
|
||||
|
||||
export interface BasePluginClient {
|
||||
export interface BasePluginClient<
|
||||
ServerAddOnEvents extends EventsContract = {},
|
||||
ServerAddOnMethods extends MethodsContract = {},
|
||||
> {
|
||||
/**
|
||||
* A key that uniquely identifies this plugin instance, captures the current device/client/plugin combination.
|
||||
*/
|
||||
@@ -128,6 +135,36 @@ export interface BasePluginClient {
|
||||
* Logger instance that logs information to the console, but also to the internal logging (in FB only builds) and which can be used to track performance.
|
||||
*/
|
||||
logger: Logger;
|
||||
|
||||
/**
|
||||
* Subscribe to a specific event arriving from the server add-on.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
onServerAddOnMessage<Event extends keyof ServerAddOnEvents>(
|
||||
event: Event,
|
||||
callback: (params: ServerAddOnEvents[Event]) => void,
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Subscribe to all messages arriving from the server add-ons not handled by another listener.
|
||||
*
|
||||
* This handler is untyped, and onMessage should be favored over using onUnhandledMessage if the event name is known upfront.
|
||||
*/
|
||||
onServerAddOnUnhandledMessage(
|
||||
callback: (event: string, params: any) => void,
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Send a message to the server add-on
|
||||
*/
|
||||
sendToServerAddOn<Method extends keyof ServerAddOnMethods>(
|
||||
method: Method,
|
||||
...params: Parameters<ServerAddOnMethods[Method]> extends []
|
||||
? []
|
||||
: [Parameters<ServerAddOnMethods[Method]>[0]]
|
||||
): ReturnType<ServerAddOnMethods[Method]>;
|
||||
}
|
||||
|
||||
let currentPluginInstance: BasePluginInstance | undefined = undefined;
|
||||
@@ -200,6 +237,7 @@ export abstract class BasePluginInstance {
|
||||
readonly instanceId = ++staticInstanceId;
|
||||
|
||||
constructor(
|
||||
private readonly serverAddOnControls: ServerAddOnControls,
|
||||
flipperLib: FlipperLib,
|
||||
definition: SandyPluginDefinition,
|
||||
device: Device,
|
||||
@@ -269,7 +307,7 @@ export abstract class BasePluginInstance {
|
||||
}
|
||||
}
|
||||
|
||||
protected createBasePluginClient(): BasePluginClient {
|
||||
protected createBasePluginClient(): BasePluginClient<any, any> {
|
||||
return {
|
||||
pluginKey: this.pluginKey,
|
||||
device: this.device,
|
||||
@@ -341,6 +379,15 @@ export abstract class BasePluginInstance {
|
||||
this.flipperLib.showNotification(this.pluginKey, notification);
|
||||
},
|
||||
logger: this.flipperLib.logger,
|
||||
sendToServerAddOn: (_method, _params): any => {
|
||||
// TODO: Implement me
|
||||
},
|
||||
onServerAddOnMessage: (_event, _cb) => {
|
||||
// TODO: Implement me
|
||||
},
|
||||
onServerAddOnUnhandledMessage: (_cb) => {
|
||||
// TODO: Implement me
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -447,4 +494,34 @@ export abstract class BasePluginInstance {
|
||||
}
|
||||
|
||||
abstract toJSON(): string;
|
||||
|
||||
protected abstract serverAddOnOwner: string;
|
||||
|
||||
protected startServerAddOn() {
|
||||
const {serverAddOn, name} = this.definition.details;
|
||||
if (serverAddOn) {
|
||||
this.serverAddOnControls.start(name, this.serverAddOnOwner).catch((e) => {
|
||||
console.warn(
|
||||
'Failed to start a server add on',
|
||||
name,
|
||||
this.serverAddOnOwner,
|
||||
e,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected stopServerAddOn() {
|
||||
const {serverAddOn, name} = this.definition.details;
|
||||
if (serverAddOn) {
|
||||
this.serverAddOnControls.stop(name, this.serverAddOnOwner).catch((e) => {
|
||||
console.warn(
|
||||
'Failed to start a server add on',
|
||||
name,
|
||||
this.serverAddOnOwner,
|
||||
e,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export function usePluginInstanceMaybe():
|
||||
}
|
||||
|
||||
export function usePlugin<
|
||||
Factory extends PluginFactory<any, any> | DevicePluginFactory,
|
||||
Factory extends PluginFactory<any, any, any, any> | DevicePluginFactory,
|
||||
>(plugin: Factory): ReturnType<Factory> {
|
||||
const pluginInstance = usePluginInstance();
|
||||
// In principle we don't *need* the plugin, but having it passed it makes sure the
|
||||
|
||||
@@ -26,7 +26,9 @@ export type FlipperDevicePluginModule = {
|
||||
/**
|
||||
* FlipperPluginModule describe the exports that are provided by a typical Flipper Desktop plugin
|
||||
*/
|
||||
export type FlipperPluginModule<Factory extends PluginFactory<any, any>> = {
|
||||
export type FlipperPluginModule<
|
||||
Factory extends PluginFactory<any, any, any, any>,
|
||||
> = {
|
||||
/** the factory function that initializes a plugin instance */
|
||||
plugin: Factory;
|
||||
/** the component type that can render this plugin */
|
||||
|
||||
@@ -505,7 +505,7 @@ export function createMockPluginDetails(
|
||||
};
|
||||
}
|
||||
|
||||
export function createTestPlugin<T extends PluginFactory<any, any>>(
|
||||
export function createTestPlugin<T extends PluginFactory<any, any, any, any>>(
|
||||
implementation: Pick<FlipperPluginModule<T>, 'plugin'> &
|
||||
Partial<FlipperPluginModule<T>>,
|
||||
details?: Partial<InstalledPluginDetails>,
|
||||
|
||||
Reference in New Issue
Block a user