Add generics to server add-on connection
Reviewed By: mweststrate Differential Revision: D34307356 fbshipit-source-id: 27e61355a85995368ebb197c42d58f4145473567
This commit is contained in:
committed by
Facebook GitHub Bot
parent
673bb9135e
commit
01a5f3da90
@@ -48,13 +48,25 @@ export type FlipperPluginReceiverRes =
|
|||||||
| undefined
|
| undefined
|
||||||
| void;
|
| void;
|
||||||
|
|
||||||
export type FlipperPluginReceiver = (
|
export type FlipperPluginReceiver<T> = (
|
||||||
data: any,
|
data: T,
|
||||||
) => FlipperPluginReceiverRes | Promise<FlipperPluginReceiverRes>;
|
) => FlipperPluginReceiverRes | Promise<FlipperPluginReceiverRes>;
|
||||||
|
|
||||||
export interface ServerAddOnPluginConnection {
|
export type EventsContract = Record<string, any>;
|
||||||
send(method: string, params: unknown): void;
|
export type MethodsContract = Record<string, (params: any) => Promise<any>>;
|
||||||
receive(method: string, receiver: FlipperPluginReceiver): void;
|
|
||||||
|
export interface ServerAddOnPluginConnection<
|
||||||
|
Events extends EventsContract,
|
||||||
|
Methods extends MethodsContract,
|
||||||
|
> {
|
||||||
|
send<T extends keyof Events & string>(
|
||||||
|
method: T,
|
||||||
|
...params: Events[T] extends never ? [] : [Events[T]]
|
||||||
|
): void;
|
||||||
|
receive<T extends keyof Methods & string>(
|
||||||
|
method: T,
|
||||||
|
receiver: FlipperPluginReceiver<Parameters<Methods[T]>[0]>,
|
||||||
|
): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FlipperServerForServerAddOn extends FlipperServer {
|
export interface FlipperServerForServerAddOn extends FlipperServer {
|
||||||
@@ -65,7 +77,10 @@ export interface FlipperServerForServerAddOn extends FlipperServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type ServerAddOnCleanup = () => Promise<void>;
|
export type ServerAddOnCleanup = () => Promise<void>;
|
||||||
export type ServerAddOn = (
|
export type ServerAddOn<
|
||||||
connection: ServerAddOnPluginConnection,
|
Events extends EventsContract,
|
||||||
|
Methods extends MethodsContract,
|
||||||
|
> = (
|
||||||
|
connection: ServerAddOnPluginConnection<Events, Methods>,
|
||||||
{flipperServer}: {flipperServer: FlipperServerForServerAddOn},
|
{flipperServer}: {flipperServer: FlipperServerForServerAddOn},
|
||||||
) => Promise<ServerAddOnCleanup>;
|
) => Promise<ServerAddOnCleanup>;
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export {
|
|||||||
getErrorFromErrorLike,
|
getErrorFromErrorLike,
|
||||||
deserializeRemoteError,
|
deserializeRemoteError,
|
||||||
} from './utils/errors';
|
} from './utils/errors';
|
||||||
|
export {createControlledPromise} from './utils/controlledPromise';
|
||||||
export * from './GK';
|
export * from './GK';
|
||||||
export * from './clientUtils';
|
export * from './clientUtils';
|
||||||
export * from './settings';
|
export * from './settings';
|
||||||
|
|||||||
47
desktop/flipper-common/src/utils/controlledPromise.tsx
Normal file
47
desktop/flipper-common/src/utils/controlledPromise.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
type Res<T> = {
|
||||||
|
promise: Promise<T>;
|
||||||
|
resolve: (...res: T extends void ? [] : [T]) => void;
|
||||||
|
reject: (reason: unknown) => void;
|
||||||
|
} & (
|
||||||
|
| {
|
||||||
|
state: 'pending';
|
||||||
|
promiseVal: undefined;
|
||||||
|
}
|
||||||
|
| {state: 'resolved'; promiseVal: T}
|
||||||
|
| {state: 'rejected'; promiseVal: unknown}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const createControlledPromise = <T,>(): Res<T> => {
|
||||||
|
let resolve!: Res<T>['resolve'];
|
||||||
|
let reject!: Res<T>['reject'];
|
||||||
|
let state: 'pending' | 'resolved' | 'rejected' = 'pending';
|
||||||
|
let promiseVal: T | unknown | undefined;
|
||||||
|
const promise = new Promise<T>((resolveP, rejectP) => {
|
||||||
|
resolve = ((val) => {
|
||||||
|
state = 'resolved';
|
||||||
|
promiseVal = val;
|
||||||
|
resolveP(val as T | PromiseLike<T>);
|
||||||
|
}) as typeof resolve;
|
||||||
|
reject = (err) => {
|
||||||
|
state = 'rejected';
|
||||||
|
promiseVal = err;
|
||||||
|
rejectP(err);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
promise,
|
||||||
|
resolve,
|
||||||
|
reject,
|
||||||
|
state,
|
||||||
|
promiseVal,
|
||||||
|
} as Res<T>;
|
||||||
|
};
|
||||||
@@ -52,6 +52,7 @@ test('Correct top level API exposed', () => {
|
|||||||
"Tracked",
|
"Tracked",
|
||||||
"TrackingScope",
|
"TrackingScope",
|
||||||
"batch",
|
"batch",
|
||||||
|
"createControlledPromise",
|
||||||
"createDataSource",
|
"createDataSource",
|
||||||
"createState",
|
"createState",
|
||||||
"createTablePlugin",
|
"createTablePlugin",
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ export const TestUtils = TestUtilites;
|
|||||||
export {
|
export {
|
||||||
sleep,
|
sleep,
|
||||||
timeout,
|
timeout,
|
||||||
|
createControlledPromise,
|
||||||
DeviceOS,
|
DeviceOS,
|
||||||
DeviceType,
|
DeviceType,
|
||||||
DeviceLogEntry,
|
DeviceLogEntry,
|
||||||
|
|||||||
@@ -8,12 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {SandyPluginDefinition} from './SandyPluginDefinition';
|
import {SandyPluginDefinition} from './SandyPluginDefinition';
|
||||||
import {
|
import {BasePluginInstance, BasePluginClient} from './PluginBase';
|
||||||
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 {
|
||||||
@@ -22,6 +17,8 @@ import {
|
|||||||
DeviceLogEntry,
|
DeviceLogEntry,
|
||||||
CrashLog,
|
CrashLog,
|
||||||
ServerAddOnControls,
|
ServerAddOnControls,
|
||||||
|
EventsContract,
|
||||||
|
MethodsContract,
|
||||||
} from 'flipper-common';
|
} from 'flipper-common';
|
||||||
|
|
||||||
export type DeviceLogListener = (entry: DeviceLogEntry) => void;
|
export type DeviceLogListener = (entry: DeviceLogEntry) => void;
|
||||||
|
|||||||
@@ -8,17 +8,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {SandyPluginDefinition} from './SandyPluginDefinition';
|
import {SandyPluginDefinition} from './SandyPluginDefinition';
|
||||||
import {
|
import {BasePluginInstance, BasePluginClient} from './PluginBase';
|
||||||
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,
|
||||||
|
EventsContract,
|
||||||
|
MethodsContract,
|
||||||
|
} from 'flipper-common';
|
||||||
|
|
||||||
type PreventIntersectionWith<Contract extends Record<string, any>> = {
|
type PreventIntersectionWith<Contract extends Record<string, any>> = {
|
||||||
[Key in keyof Contract]?: never;
|
[Key in keyof Contract]?: never;
|
||||||
|
|||||||
@@ -18,10 +18,11 @@ 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';
|
import {
|
||||||
|
EventsContract,
|
||||||
export type EventsContract = Record<string, any>;
|
MethodsContract,
|
||||||
export type MethodsContract = Record<string, (params: any) => Promise<any>>;
|
ServerAddOnControls,
|
||||||
|
} from 'flipper-common';
|
||||||
|
|
||||||
type StateExportHandler<T = any> = (
|
type StateExportHandler<T = any> = (
|
||||||
idler: Idler,
|
idler: Idler,
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ export type ServerAddOnModuleToDesktopConnectionEvents = {
|
|||||||
|
|
||||||
export class ServerAddOnModuleToDesktopConnection
|
export class ServerAddOnModuleToDesktopConnection
|
||||||
extends EventEmitter
|
extends EventEmitter
|
||||||
implements ServerAddOnPluginConnection
|
implements ServerAddOnPluginConnection<any, any>
|
||||||
{
|
{
|
||||||
private subscriptions: Map<string, FlipperPluginReceiver> = new Map();
|
private subscriptions: Map<string, FlipperPluginReceiver<any>> = new Map();
|
||||||
|
|
||||||
constructor(private readonly pluginName: string) {
|
constructor(private readonly pluginName: string) {
|
||||||
super();
|
super();
|
||||||
@@ -44,7 +44,7 @@ export class ServerAddOnModuleToDesktopConnection
|
|||||||
this.emit('message', message);
|
this.emit('message', message);
|
||||||
}
|
}
|
||||||
|
|
||||||
receive(method: string, receiver: FlipperPluginReceiver) {
|
receive(method: string, receiver: FlipperPluginReceiver<any>) {
|
||||||
this.subscriptions.set(method, receiver);
|
this.subscriptions.set(method, receiver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,13 +7,12 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ServerAddOnStartDetails} from 'flipper-common';
|
import {ServerAddOnStartDetails, createControlledPromise} from 'flipper-common';
|
||||||
import {loadServerAddOn} from '../loadServerAddOn';
|
import {loadServerAddOn} from '../loadServerAddOn';
|
||||||
import {PluginManager} from '../PluginManager';
|
import {PluginManager} from '../PluginManager';
|
||||||
import {ServerAddOnManager} from '../ServerAddManager';
|
import {ServerAddOnManager} from '../ServerAddManager';
|
||||||
import {ServerAddOnModuleToDesktopConnection} from '../ServerAddOnModuleToDesktopConnection';
|
import {ServerAddOnModuleToDesktopConnection} from '../ServerAddOnModuleToDesktopConnection';
|
||||||
import {
|
import {
|
||||||
createControlledPromise,
|
|
||||||
detailsBundled,
|
detailsBundled,
|
||||||
detailsInstalled,
|
detailsInstalled,
|
||||||
initialOwner,
|
initialOwner,
|
||||||
|
|||||||
@@ -7,12 +7,11 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ServerAddOnStartDetails} from 'flipper-common';
|
import {ServerAddOnStartDetails, createControlledPromise} from 'flipper-common';
|
||||||
import {loadServerAddOn} from '../loadServerAddOn';
|
import {loadServerAddOn} from '../loadServerAddOn';
|
||||||
import {ServerAddOn} from '../ServerAddOn';
|
import {ServerAddOn} from '../ServerAddOn';
|
||||||
import {ServerAddOnModuleToDesktopConnection} from '../ServerAddOnModuleToDesktopConnection';
|
import {ServerAddOnModuleToDesktopConnection} from '../ServerAddOnModuleToDesktopConnection';
|
||||||
import {
|
import {
|
||||||
createControlledPromise,
|
|
||||||
detailsBundled,
|
detailsBundled,
|
||||||
detailsInstalled,
|
detailsInstalled,
|
||||||
initialOwner,
|
initialOwner,
|
||||||
|
|||||||
@@ -17,17 +17,3 @@ export const detailsBundled: ServerAddOnStartDetails = {
|
|||||||
export const detailsInstalled: ServerAddOnStartDetails = {
|
export const detailsInstalled: ServerAddOnStartDetails = {
|
||||||
path: '/dagobar/',
|
path: '/dagobar/',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createControlledPromise = <T,>() => {
|
|
||||||
let resolve!: (...res: T extends void ? [] : [T]) => void;
|
|
||||||
let reject!: (reason: unknown) => void;
|
|
||||||
const promise = new Promise<T>((resolveP, rejectP) => {
|
|
||||||
resolve = resolveP as typeof resolve;
|
|
||||||
reject = rejectP;
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
promise,
|
|
||||||
resolve,
|
|
||||||
reject,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {assertNotNull} from '../comms/Utilities';
|
|||||||
import defaultPlugins from '../defaultPlugins';
|
import defaultPlugins from '../defaultPlugins';
|
||||||
|
|
||||||
interface ServerAddOnModule {
|
interface ServerAddOnModule {
|
||||||
default: ServerAddOnFn;
|
default: ServerAddOnFn<any, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loadServerAddOn = (
|
export const loadServerAddOn = (
|
||||||
|
|||||||
@@ -1174,6 +1174,43 @@ Usage: `safeStringify(dataStructure)`
|
|||||||
|
|
||||||
Serialises the given data structure using `JSON.stringify`, but doesn't throw if the processes failed, but rather returns a `<unserializable ...>` string.
|
Serialises the given data structure using `JSON.stringify`, but doesn't throw if the processes failed, but rather returns a `<unserializable ...>` string.
|
||||||
|
|
||||||
|
### createControlledPromise
|
||||||
|
|
||||||
|
Creates a promise and functions to resolve/reject it externally. Alsoprovides its current state.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
```ts
|
||||||
|
// When the promise is pending
|
||||||
|
type Res<T> = {
|
||||||
|
promise: Promise<T>;
|
||||||
|
resolve: (...res: T extends void ? [] : [T]) => void;
|
||||||
|
reject: (reason: unknown) => void;
|
||||||
|
state: 'pending';
|
||||||
|
promiseVal: undefined;
|
||||||
|
} | {
|
||||||
|
promise: Promise<T>;
|
||||||
|
resolve: (...res: T extends void ? [] : [T]) => void;
|
||||||
|
reject: (reason: unknown) => void;
|
||||||
|
state: 'resolved';
|
||||||
|
// Resolved value
|
||||||
|
promiseVal: T;
|
||||||
|
} | {
|
||||||
|
promise: Promise<T>;
|
||||||
|
resolve: (...res: T extends void ? [] : [T]) => void;
|
||||||
|
reject: (reason: unknown) => void;
|
||||||
|
state: 'rejected';
|
||||||
|
// Rejection reason
|
||||||
|
promiseVal: unknown;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
```js
|
||||||
|
const controllerPromise = createControlledPromise()
|
||||||
|
someService.on('event', (val) => controllerPromise.resolve(val))
|
||||||
|
await controllerPromise.promise
|
||||||
|
```
|
||||||
|
|
||||||
## TestUtils
|
## TestUtils
|
||||||
|
|
||||||
The object `TestUtils` as exposed from `flipper-plugin` exposes utilities to write unit tests for Sandy plugins.
|
The object `TestUtils` as exposed from `flipper-plugin` exposes utilities to write unit tests for Sandy plugins.
|
||||||
|
|||||||
Reference in New Issue
Block a user