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 {SandyPluginDefinition} from './SandyPluginDefinition';
|
||||||
import {BasePluginInstance, BasePluginClient} from './PluginBase';
|
import {
|
||||||
|
BasePluginInstance,
|
||||||
|
BasePluginClient,
|
||||||
|
EventsContract,
|
||||||
|
MethodsContract,
|
||||||
|
} from './PluginBase';
|
||||||
import {FlipperLib} from './FlipperLib';
|
import {FlipperLib} from './FlipperLib';
|
||||||
import {Atom, ReadOnlyAtom} from '../state/atom';
|
import {Atom, ReadOnlyAtom} from '../state/atom';
|
||||||
import {
|
import {
|
||||||
@@ -46,12 +51,14 @@ export type DevicePluginPredicate = (device: Device) => boolean;
|
|||||||
|
|
||||||
export type DevicePluginFactory = (client: DevicePluginClient) => object;
|
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
|
* opens a different plugin by id, optionally providing a deeplink to bring the plugin to a certain state
|
||||||
*/
|
*/
|
||||||
selectPlugin(pluginId: string, deeplinkPayload?: unknown): void;
|
selectPlugin(pluginId: string, deeplinkPayload?: unknown): void;
|
||||||
|
|
||||||
readonly isConnected: boolean;
|
readonly isConnected: boolean;
|
||||||
readonly connected: ReadOnlyAtom<boolean>;
|
readonly connected: ReadOnlyAtom<boolean>;
|
||||||
}
|
}
|
||||||
@@ -65,14 +72,21 @@ export class SandyDevicePluginInstance extends BasePluginInstance {
|
|||||||
readonly client: DevicePluginClient;
|
readonly client: DevicePluginClient;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly serverAddOnControls: ServerAddOnControls,
|
serverAddOnControls: ServerAddOnControls,
|
||||||
flipperLib: FlipperLib,
|
flipperLib: FlipperLib,
|
||||||
definition: SandyPluginDefinition,
|
definition: SandyPluginDefinition,
|
||||||
device: Device,
|
device: Device,
|
||||||
pluginKey: string,
|
pluginKey: string,
|
||||||
initialStates?: Record<string, any>,
|
initialStates?: Record<string, any>,
|
||||||
) {
|
) {
|
||||||
super(flipperLib, definition, device, pluginKey, initialStates);
|
super(
|
||||||
|
serverAddOnControls,
|
||||||
|
flipperLib,
|
||||||
|
definition,
|
||||||
|
device,
|
||||||
|
pluginKey,
|
||||||
|
initialStates,
|
||||||
|
);
|
||||||
this.client = {
|
this.client = {
|
||||||
...this.createBasePluginClient(),
|
...this.createBasePluginClient(),
|
||||||
selectPlugin(pluginId: string, deeplink?: unknown) {
|
selectPlugin(pluginId: string, deeplink?: unknown) {
|
||||||
@@ -98,31 +112,7 @@ export class SandyDevicePluginInstance extends BasePluginInstance {
|
|||||||
super.destroy();
|
super.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private startServerAddOn() {
|
protected get serverAddOnOwner() {
|
||||||
const {serverAddOn, name} = this.definition.details;
|
return this.device.serial;
|
||||||
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,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,21 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {SandyPluginDefinition} from './SandyPluginDefinition';
|
import {SandyPluginDefinition} from './SandyPluginDefinition';
|
||||||
import {BasePluginInstance, BasePluginClient} from './PluginBase';
|
import {
|
||||||
|
BasePluginInstance,
|
||||||
|
BasePluginClient,
|
||||||
|
EventsContract,
|
||||||
|
MethodsContract,
|
||||||
|
} from './PluginBase';
|
||||||
import {FlipperLib} from './FlipperLib';
|
import {FlipperLib} from './FlipperLib';
|
||||||
import {Device} from './DevicePlugin';
|
import {Device} from './DevicePlugin';
|
||||||
import {batched} from '../state/batch';
|
import {batched} from '../state/batch';
|
||||||
import {Atom, createState, ReadOnlyAtom} from '../state/atom';
|
import {Atom, createState, ReadOnlyAtom} from '../state/atom';
|
||||||
import {ServerAddOnControls} from 'flipper-common';
|
import {ServerAddOnControls} from 'flipper-common';
|
||||||
|
|
||||||
type EventsContract = Record<string, any>;
|
type PreventIntersectionWith<Contract extends Record<string, any>> = {
|
||||||
type MethodsContract = Record<string, (params: any) => Promise<any>>;
|
[Key in keyof Contract]?: never;
|
||||||
|
};
|
||||||
|
|
||||||
type Message = {
|
type Message = {
|
||||||
method: string;
|
method: string;
|
||||||
@@ -29,7 +35,11 @@ type Message = {
|
|||||||
export interface PluginClient<
|
export interface PluginClient<
|
||||||
Events extends EventsContract = {},
|
Events extends EventsContract = {},
|
||||||
Methods extends MethodsContract = {},
|
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
|
* Identifier that uniquely identifies the connected application
|
||||||
*/
|
*/
|
||||||
@@ -125,7 +135,11 @@ export interface RealFlipperClient {
|
|||||||
export type PluginFactory<
|
export type PluginFactory<
|
||||||
Events extends EventsContract,
|
Events extends EventsContract,
|
||||||
Methods extends MethodsContract,
|
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<{}>;
|
export type FlipperPluginComponent = React.FC<{}>;
|
||||||
|
|
||||||
@@ -137,19 +151,26 @@ export class SandyPluginInstance extends BasePluginInstance {
|
|||||||
/** base client provided by Flipper */
|
/** base client provided by Flipper */
|
||||||
readonly realClient: RealFlipperClient;
|
readonly realClient: RealFlipperClient;
|
||||||
/** client that is bound to this instance */
|
/** client that is bound to this instance */
|
||||||
readonly client: PluginClient<any, any>;
|
readonly client: PluginClient<any, any, any, any>;
|
||||||
/** connection alive? */
|
/** connection alive? */
|
||||||
readonly connected = createState(false);
|
readonly connected = createState(false);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly serverAddOnControls: ServerAddOnControls,
|
serverAddOnControls: ServerAddOnControls,
|
||||||
flipperLib: FlipperLib,
|
flipperLib: FlipperLib,
|
||||||
definition: SandyPluginDefinition,
|
definition: SandyPluginDefinition,
|
||||||
realClient: RealFlipperClient,
|
realClient: RealFlipperClient,
|
||||||
pluginKey: string,
|
pluginKey: string,
|
||||||
initialStates?: Record<string, any>,
|
initialStates?: Record<string, any>,
|
||||||
) {
|
) {
|
||||||
super(flipperLib, definition, realClient.device, pluginKey, initialStates);
|
super(
|
||||||
|
serverAddOnControls,
|
||||||
|
flipperLib,
|
||||||
|
definition,
|
||||||
|
realClient.device,
|
||||||
|
pluginKey,
|
||||||
|
initialStates,
|
||||||
|
);
|
||||||
this.realClient = realClient;
|
this.realClient = realClient;
|
||||||
this.definition = definition;
|
this.definition = definition;
|
||||||
const self = this;
|
const self = this;
|
||||||
@@ -231,18 +252,7 @@ export class SandyPluginInstance extends BasePluginInstance {
|
|||||||
connect() {
|
connect() {
|
||||||
this.assertNotDestroyed();
|
this.assertNotDestroyed();
|
||||||
if (!this.connected.get()) {
|
if (!this.connected.get()) {
|
||||||
const {serverAddOn, name} = this.definition.details;
|
this.startServerAddOn();
|
||||||
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.connected.set(true);
|
this.connected.set(true);
|
||||||
this.events.emit('connect');
|
this.events.emit('connect');
|
||||||
}
|
}
|
||||||
@@ -251,18 +261,7 @@ export class SandyPluginInstance extends BasePluginInstance {
|
|||||||
disconnect() {
|
disconnect() {
|
||||||
this.assertNotDestroyed();
|
this.assertNotDestroyed();
|
||||||
if (this.connected.get()) {
|
if (this.connected.get()) {
|
||||||
const {serverAddOn, name} = this.definition.details;
|
this.stopServerAddOn();
|
||||||
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.connected.set(false);
|
this.connected.set(false);
|
||||||
this.events.emit('disconnect');
|
this.events.emit('disconnect');
|
||||||
}
|
}
|
||||||
@@ -289,6 +288,10 @@ export class SandyPluginInstance extends BasePluginInstance {
|
|||||||
return '[SandyPluginInstance]';
|
return '[SandyPluginInstance]';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected get serverAddOnOwner() {
|
||||||
|
return this.realClient.id;
|
||||||
|
}
|
||||||
|
|
||||||
private assertConnected() {
|
private assertConnected() {
|
||||||
this.assertNotDestroyed();
|
this.assertNotDestroyed();
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ import {Idler} from '../utils/Idler';
|
|||||||
import {Notification} from './Notification';
|
import {Notification} from './Notification';
|
||||||
import {Logger} from '../utils/Logger';
|
import {Logger} from '../utils/Logger';
|
||||||
import {CreatePasteArgs, CreatePasteResult} from './Paste';
|
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> = (
|
type StateExportHandler<T = any> = (
|
||||||
idler: Idler,
|
idler: Idler,
|
||||||
@@ -25,7 +29,10 @@ type StateExportHandler<T = any> = (
|
|||||||
) => Promise<T | undefined | void>;
|
) => Promise<T | undefined | void>;
|
||||||
type StateImportHandler<T = any> = (data: T) => 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.
|
* 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 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;
|
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;
|
let currentPluginInstance: BasePluginInstance | undefined = undefined;
|
||||||
@@ -200,6 +237,7 @@ export abstract class BasePluginInstance {
|
|||||||
readonly instanceId = ++staticInstanceId;
|
readonly instanceId = ++staticInstanceId;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private readonly serverAddOnControls: ServerAddOnControls,
|
||||||
flipperLib: FlipperLib,
|
flipperLib: FlipperLib,
|
||||||
definition: SandyPluginDefinition,
|
definition: SandyPluginDefinition,
|
||||||
device: Device,
|
device: Device,
|
||||||
@@ -269,7 +307,7 @@ export abstract class BasePluginInstance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createBasePluginClient(): BasePluginClient {
|
protected createBasePluginClient(): BasePluginClient<any, any> {
|
||||||
return {
|
return {
|
||||||
pluginKey: this.pluginKey,
|
pluginKey: this.pluginKey,
|
||||||
device: this.device,
|
device: this.device,
|
||||||
@@ -341,6 +379,15 @@ export abstract class BasePluginInstance {
|
|||||||
this.flipperLib.showNotification(this.pluginKey, notification);
|
this.flipperLib.showNotification(this.pluginKey, notification);
|
||||||
},
|
},
|
||||||
logger: this.flipperLib.logger,
|
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;
|
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<
|
export function usePlugin<
|
||||||
Factory extends PluginFactory<any, any> | DevicePluginFactory,
|
Factory extends PluginFactory<any, any, any, any> | DevicePluginFactory,
|
||||||
>(plugin: Factory): ReturnType<Factory> {
|
>(plugin: Factory): ReturnType<Factory> {
|
||||||
const pluginInstance = usePluginInstance();
|
const pluginInstance = usePluginInstance();
|
||||||
// In principle we don't *need* the plugin, but having it passed it makes sure the
|
// 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
|
* 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 */
|
/** the factory function that initializes a plugin instance */
|
||||||
plugin: Factory;
|
plugin: Factory;
|
||||||
/** the component type that can render this plugin */
|
/** 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'> &
|
implementation: Pick<FlipperPluginModule<T>, 'plugin'> &
|
||||||
Partial<FlipperPluginModule<T>>,
|
Partial<FlipperPluginModule<T>>,
|
||||||
details?: Partial<InstalledPluginDetails>,
|
details?: Partial<InstalledPluginDetails>,
|
||||||
|
|||||||
Reference in New Issue
Block a user