diff --git a/desktop/flipper-common/src/ServerAddOn.tsx b/desktop/flipper-common/src/ServerAddOn.tsx index d0200725d..2de78cef3 100644 --- a/desktop/flipper-common/src/ServerAddOn.tsx +++ b/desktop/flipper-common/src/ServerAddOn.tsx @@ -48,13 +48,25 @@ export type FlipperPluginReceiverRes = | undefined | void; -export type FlipperPluginReceiver = ( - data: any, +export type FlipperPluginReceiver = ( + data: T, ) => FlipperPluginReceiverRes | Promise; -export interface ServerAddOnPluginConnection { - send(method: string, params: unknown): void; - receive(method: string, receiver: FlipperPluginReceiver): void; +export type EventsContract = Record; +export type MethodsContract = Record Promise>; + +export interface ServerAddOnPluginConnection< + Events extends EventsContract, + Methods extends MethodsContract, +> { + send( + method: T, + ...params: Events[T] extends never ? [] : [Events[T]] + ): void; + receive( + method: T, + receiver: FlipperPluginReceiver[0]>, + ): void; } export interface FlipperServerForServerAddOn extends FlipperServer { @@ -65,7 +77,10 @@ export interface FlipperServerForServerAddOn extends FlipperServer { } export type ServerAddOnCleanup = () => Promise; -export type ServerAddOn = ( - connection: ServerAddOnPluginConnection, +export type ServerAddOn< + Events extends EventsContract, + Methods extends MethodsContract, +> = ( + connection: ServerAddOnPluginConnection, {flipperServer}: {flipperServer: FlipperServerForServerAddOn}, ) => Promise; diff --git a/desktop/flipper-common/src/index.tsx b/desktop/flipper-common/src/index.tsx index d3bc1f5e4..8cd13aca4 100644 --- a/desktop/flipper-common/src/index.tsx +++ b/desktop/flipper-common/src/index.tsx @@ -47,6 +47,7 @@ export { getErrorFromErrorLike, deserializeRemoteError, } from './utils/errors'; +export {createControlledPromise} from './utils/controlledPromise'; export * from './GK'; export * from './clientUtils'; export * from './settings'; diff --git a/desktop/flipper-common/src/utils/controlledPromise.tsx b/desktop/flipper-common/src/utils/controlledPromise.tsx new file mode 100644 index 000000000..723c53221 --- /dev/null +++ b/desktop/flipper-common/src/utils/controlledPromise.tsx @@ -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 = { + promise: Promise; + 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 = (): Res => { + let resolve!: Res['resolve']; + let reject!: Res['reject']; + let state: 'pending' | 'resolved' | 'rejected' = 'pending'; + let promiseVal: T | unknown | undefined; + const promise = new Promise((resolveP, rejectP) => { + resolve = ((val) => { + state = 'resolved'; + promiseVal = val; + resolveP(val as T | PromiseLike); + }) as typeof resolve; + reject = (err) => { + state = 'rejected'; + promiseVal = err; + rejectP(err); + }; + }); + return { + promise, + resolve, + reject, + state, + promiseVal, + } as Res; +}; diff --git a/desktop/flipper-plugin/src/__tests__/api.node.tsx b/desktop/flipper-plugin/src/__tests__/api.node.tsx index 306246c14..8233da242 100644 --- a/desktop/flipper-plugin/src/__tests__/api.node.tsx +++ b/desktop/flipper-plugin/src/__tests__/api.node.tsx @@ -52,6 +52,7 @@ test('Correct top level API exposed', () => { "Tracked", "TrackingScope", "batch", + "createControlledPromise", "createDataSource", "createState", "createTablePlugin", diff --git a/desktop/flipper-plugin/src/index.tsx b/desktop/flipper-plugin/src/index.tsx index f96fc32ed..b0656be65 100644 --- a/desktop/flipper-plugin/src/index.tsx +++ b/desktop/flipper-plugin/src/index.tsx @@ -146,6 +146,7 @@ export const TestUtils = TestUtilites; export { sleep, timeout, + createControlledPromise, DeviceOS, DeviceType, DeviceLogEntry, diff --git a/desktop/flipper-plugin/src/plugin/DevicePlugin.tsx b/desktop/flipper-plugin/src/plugin/DevicePlugin.tsx index cb629cd9c..df5d774a9 100644 --- a/desktop/flipper-plugin/src/plugin/DevicePlugin.tsx +++ b/desktop/flipper-plugin/src/plugin/DevicePlugin.tsx @@ -8,12 +8,7 @@ */ import {SandyPluginDefinition} from './SandyPluginDefinition'; -import { - BasePluginInstance, - BasePluginClient, - EventsContract, - MethodsContract, -} from './PluginBase'; +import {BasePluginInstance, BasePluginClient} from './PluginBase'; import {FlipperLib} from './FlipperLib'; import {Atom, ReadOnlyAtom} from '../state/atom'; import { @@ -22,6 +17,8 @@ import { DeviceLogEntry, CrashLog, ServerAddOnControls, + EventsContract, + MethodsContract, } from 'flipper-common'; export type DeviceLogListener = (entry: DeviceLogEntry) => void; diff --git a/desktop/flipper-plugin/src/plugin/Plugin.tsx b/desktop/flipper-plugin/src/plugin/Plugin.tsx index 817822c08..f9645b015 100644 --- a/desktop/flipper-plugin/src/plugin/Plugin.tsx +++ b/desktop/flipper-plugin/src/plugin/Plugin.tsx @@ -8,17 +8,16 @@ */ import {SandyPluginDefinition} from './SandyPluginDefinition'; -import { - BasePluginInstance, - BasePluginClient, - EventsContract, - MethodsContract, -} from './PluginBase'; +import {BasePluginInstance, BasePluginClient} 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'; +import { + ServerAddOnControls, + EventsContract, + MethodsContract, +} from 'flipper-common'; type PreventIntersectionWith> = { [Key in keyof Contract]?: never; diff --git a/desktop/flipper-plugin/src/plugin/PluginBase.tsx b/desktop/flipper-plugin/src/plugin/PluginBase.tsx index a815465c5..e41cca8a2 100644 --- a/desktop/flipper-plugin/src/plugin/PluginBase.tsx +++ b/desktop/flipper-plugin/src/plugin/PluginBase.tsx @@ -18,10 +18,11 @@ 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; -export type MethodsContract = Record Promise>; +import { + EventsContract, + MethodsContract, + ServerAddOnControls, +} from 'flipper-common'; type StateExportHandler = ( idler: Idler, diff --git a/desktop/flipper-server-core/src/plugins/ServerAddOnModuleToDesktopConnection.tsx b/desktop/flipper-server-core/src/plugins/ServerAddOnModuleToDesktopConnection.tsx index 995bc639b..2c3d0a3af 100644 --- a/desktop/flipper-server-core/src/plugins/ServerAddOnModuleToDesktopConnection.tsx +++ b/desktop/flipper-server-core/src/plugins/ServerAddOnModuleToDesktopConnection.tsx @@ -23,9 +23,9 @@ export type ServerAddOnModuleToDesktopConnectionEvents = { export class ServerAddOnModuleToDesktopConnection extends EventEmitter - implements ServerAddOnPluginConnection + implements ServerAddOnPluginConnection { - private subscriptions: Map = new Map(); + private subscriptions: Map> = new Map(); constructor(private readonly pluginName: string) { super(); @@ -44,7 +44,7 @@ export class ServerAddOnModuleToDesktopConnection this.emit('message', message); } - receive(method: string, receiver: FlipperPluginReceiver) { + receive(method: string, receiver: FlipperPluginReceiver) { this.subscriptions.set(method, receiver); } diff --git a/desktop/flipper-server-core/src/plugins/__tests__/PluginManager.node.tsx b/desktop/flipper-server-core/src/plugins/__tests__/PluginManager.node.tsx index fb8b9d11d..99974c271 100644 --- a/desktop/flipper-server-core/src/plugins/__tests__/PluginManager.node.tsx +++ b/desktop/flipper-server-core/src/plugins/__tests__/PluginManager.node.tsx @@ -7,13 +7,12 @@ * @format */ -import {ServerAddOnStartDetails} from 'flipper-common'; +import {ServerAddOnStartDetails, createControlledPromise} from 'flipper-common'; import {loadServerAddOn} from '../loadServerAddOn'; import {PluginManager} from '../PluginManager'; import {ServerAddOnManager} from '../ServerAddManager'; import {ServerAddOnModuleToDesktopConnection} from '../ServerAddOnModuleToDesktopConnection'; import { - createControlledPromise, detailsBundled, detailsInstalled, initialOwner, diff --git a/desktop/flipper-server-core/src/plugins/__tests__/ServerAddOn.node.tsx b/desktop/flipper-server-core/src/plugins/__tests__/ServerAddOn.node.tsx index 99cadf0e8..8dc6ff101 100644 --- a/desktop/flipper-server-core/src/plugins/__tests__/ServerAddOn.node.tsx +++ b/desktop/flipper-server-core/src/plugins/__tests__/ServerAddOn.node.tsx @@ -7,12 +7,11 @@ * @format */ -import {ServerAddOnStartDetails} from 'flipper-common'; +import {ServerAddOnStartDetails, createControlledPromise} from 'flipper-common'; import {loadServerAddOn} from '../loadServerAddOn'; import {ServerAddOn} from '../ServerAddOn'; import {ServerAddOnModuleToDesktopConnection} from '../ServerAddOnModuleToDesktopConnection'; import { - createControlledPromise, detailsBundled, detailsInstalled, initialOwner, diff --git a/desktop/flipper-server-core/src/plugins/__tests__/utils.tsx b/desktop/flipper-server-core/src/plugins/__tests__/utils.tsx index 4b0c5b0d5..cdd3dd649 100644 --- a/desktop/flipper-server-core/src/plugins/__tests__/utils.tsx +++ b/desktop/flipper-server-core/src/plugins/__tests__/utils.tsx @@ -17,17 +17,3 @@ export const detailsBundled: ServerAddOnStartDetails = { export const detailsInstalled: ServerAddOnStartDetails = { path: '/dagobar/', }; - -export const createControlledPromise = () => { - let resolve!: (...res: T extends void ? [] : [T]) => void; - let reject!: (reason: unknown) => void; - const promise = new Promise((resolveP, rejectP) => { - resolve = resolveP as typeof resolve; - reject = rejectP; - }); - return { - promise, - resolve, - reject, - }; -}; diff --git a/desktop/flipper-server-core/src/plugins/loadServerAddOn.tsx b/desktop/flipper-server-core/src/plugins/loadServerAddOn.tsx index 62f27f35e..2691dce45 100644 --- a/desktop/flipper-server-core/src/plugins/loadServerAddOn.tsx +++ b/desktop/flipper-server-core/src/plugins/loadServerAddOn.tsx @@ -17,7 +17,7 @@ import {assertNotNull} from '../comms/Utilities'; import defaultPlugins from '../defaultPlugins'; interface ServerAddOnModule { - default: ServerAddOnFn; + default: ServerAddOnFn; } export const loadServerAddOn = ( diff --git a/docs/extending/flipper-plugin.mdx b/docs/extending/flipper-plugin.mdx index 26ffddfae..aa44959ea 100644 --- a/docs/extending/flipper-plugin.mdx +++ b/docs/extending/flipper-plugin.mdx @@ -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 `` 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 = { + promise: Promise; + resolve: (...res: T extends void ? [] : [T]) => void; + reject: (reason: unknown) => void; + state: 'pending'; + promiseVal: undefined; +} | { + promise: Promise; + resolve: (...res: T extends void ? [] : [T]) => void; + reject: (reason: unknown) => void; + state: 'resolved'; + // Resolved value + promiseVal: T; +} | { + promise: Promise; + 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 The object `TestUtils` as exposed from `flipper-plugin` exposes utilities to write unit tests for Sandy plugins.