Compare commits

..

5 Commits

4 changed files with 158 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
import { createHookRunner, RPCPlugin } from "@/core/RPCPlugin"
import type { CallIncomingCtx } from "@/core/RPCPlugin"
describe('RPCPlugin.test', () => {
const plugin3 = {
onCallIncoming(ctx: CallIncomingCtx) { throw new Error() }
} as RPCPlugin;
const plugin4 = {
async onCallIncoming(ctx: CallIncomingCtx) { throw new Error() }
} as RPCPlugin;
test('should be resolved', async () => {
const plugin1 = {
onCallIncoming: jest.fn(),
} as RPCPlugin;
const plugin2 = {
async onCallIncoming(ctx: CallIncomingCtx) { }
} as RPCPlugin;
const plugins = [plugin1, plugin2];
const hookRunner = createHookRunner(plugins, 'onCallIncoming');
await hookRunner({} as any);
expect(plugin1.onCallIncoming).toHaveBeenCalled()
})
test('should be resolved2', async () => {
const plugins = [] as RPCPlugin[];
const hookRunner = createHookRunner(plugins, 'onCallIncoming');
await hookRunner({} as any);
expect.assertions(0);
})
test('should be rejected1', async () => {
const plugins = [plugin3];
const hookRunner = createHookRunner(plugins, 'onCallIncoming');
await expect(hookRunner({} as any)).rejects.toThrow(Error)
})
test('should be rejected2', async () => {
const plugins = [plugin4];
const hookRunner = createHookRunner(plugins, 'onCallIncoming');
await expect(hookRunner({} as any)).rejects.toThrow(Error)
})
})

View File

@@ -3,6 +3,7 @@ import { RPCClient } from "./RPCClient";
import { RPCServer } from "./RPCServer";
import { RPCProvider } from "./RPCProvider";
import { RPCSession } from "./RPCSession";
import { RPCPlugin } from "./RPCPlugin";
const DefaultListenOptions = {
port: 5201,
@@ -34,6 +35,7 @@ export class RPCHandler extends EventEmitter<RPCHandlerEvents> {
private provider?: RPCProvider;
private accessKey?: string;
private config: RPCConfig;
private plugins: RPCPlugin[] = [];
constructor(
args?: {
@@ -88,6 +90,29 @@ export class RPCHandler extends EventEmitter<RPCHandlerEvents> {
}
}
loadPlugin(plugin: RPCPlugin): boolean {
const plugins = this.plugins;
if (plugins.includes(plugin)) {
return false;
}
plugins.push(plugin);
return true;
}
unloadPlugin(plugin: RPCPlugin): boolean {
const plugins = this.plugins;
const idx = plugins.indexOf(plugin);
if (idx === -1) {
return false;
}
plugins.splice(idx, 1);
return true;
}
getPlugins(): RPCPlugin[] {
return [...this.plugins];
}
async connect(options: {
url?: string;
accessKey?: string;

74
src/core/RPCPlugin.ts Normal file
View File

@@ -0,0 +1,74 @@
import { RPCSession } from "./RPCSession";
// interface BaseHookRuntimeCtx {
// nexts: RPCPlugin[];
// setNextPlugins: (plugins: RPCPlugin[]) => void
// }
export interface BaseHookCtx {
}
export interface CallOutgoingBeforeCtx extends BaseHookCtx {
session: RPCSession;
options: {
fnPath: string;
args: any[];
};
}
export interface CallOutgoingCtx extends CallOutgoingBeforeCtx {
result: any;
setResult: (data: any) => void;
}
export interface CallIncomingBeforeCtx extends BaseHookCtx {
}
export interface CallIncomingCtx extends CallIncomingBeforeCtx {
}
export type NormalMethodReturn = Promise<void> | void;
export type HookFn<Ctx> = (ctx: Ctx) => NormalMethodReturn;
export interface RPCPluginHooksCtx {
onCallOutgoingBefore: CallOutgoingBeforeCtx;
onCallOutgoing: CallOutgoingCtx;
onCallIncomingBefore: CallIncomingBeforeCtx;
onCallIncoming: CallIncomingCtx;
}
export type RPCPluginHooks = {
[K in keyof RPCPluginHooksCtx]?: HookFn<RPCPluginHooksCtx[K]>;
};
export interface RPCPlugin extends RPCPluginHooks {
onInit?(): void;
onDestroy?(): void;
}
export abstract class AbstractRPCPlugin implements RPCPlugin {
abstract onInit?(): void;
abstract onDestroy?(): void;
abstract onCallOutgoingBefore?(ctx: CallOutgoingBeforeCtx): NormalMethodReturn;
abstract onCallOutgoing?(ctx: CallOutgoingCtx): NormalMethodReturn;
abstract onCallIncomingBefore?(ctx: CallIncomingBeforeCtx): NormalMethodReturn;
abstract onCallIncoming?(ctx: CallIncomingCtx): NormalMethodReturn;
}
type HookName = keyof RPCPluginHooksCtx;
type HookRunner<Ctx> = (ctx: Ctx) => Promise<void>;
export function createHookRunner<K extends HookName>(
plugins: RPCPlugin[],
hookName: K,
): HookRunner<RPCPluginHooksCtx[K]> {
return async (ctx: RPCPluginHooksCtx[K]) => {
for (const plugin of plugins) {
await (plugin[hookName] as HookFn<RPCPluginHooksCtx[K]> | undefined)?.(ctx);
}
};
}

View File

@@ -81,4 +81,20 @@ export function markAsPublicMethod<T extends Function | Record<any, unknown> | u
markAs(obj);
return obj;
}
export function createDeferrablePromise<T = unknown>() {
let resolve!: (value: T | PromiseLike<T>) => void;
let reject!: (reason?: unknown) => void;
const promise = new Promise<T>((res, rej) => {
resolve = res;
reject = rej;
});
return {
promise,
resolve,
reject
};
}