diff --git a/package.json b/package.json index ef730cf7..4a8437f7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "zeed", "type": "module", - "version": "0.13.19", + "version": "0.13.20", "description": "🌱 Simple foundation library", "author": { "name": "Dirk Holtwick", diff --git a/src/common/msg/rpc.spec.ts b/src/common/msg/rpc.spec.ts index 23fc4cd8..df5e165c 100644 --- a/src/common/msg/rpc.spec.ts +++ b/src/common/msg/rpc.spec.ts @@ -2,6 +2,7 @@ import { MessageChannel } from 'worker_threads' import { useRPC, useRPCHub } from './rpc' import { decodeJson, encodeJson } from '../bin' + let bobCount = 0 const Bob = { @@ -39,14 +40,14 @@ describe('rpc', () => { const serialize = (data: any) => encodeJson(data) const deserialize = (data: any) => decodeJson(data) - const bob = useRPC(Bob, { + const bob = useRPC(Bob, { post: data => channel.port1.postMessage(data), on: data => channel.port1.on('message', data), serialize, deserialize, }) - const alice = useRPC(Alice, { + const alice = useRPC(Alice, { // mark bob's `bump` as an event without response eventNames: ['bump'], post: data => channel.port2.postMessage(data), @@ -82,9 +83,9 @@ describe('rpc', () => { deserialize, }) - let bob = bobHub(Bob) + let bob = bobHub(Bob) - const alice = useRPC(Alice, { + const alice = useRPC(Alice, { // mark bob's `bump` as an event without response eventNames: ['bump'], post: data => channel.port2.postMessage(data), diff --git a/src/common/msg/rpc.ts b/src/common/msg/rpc.ts index 5e2ec62f..71d63a41 100644 --- a/src/common/msg/rpc.ts +++ b/src/common/msg/rpc.ts @@ -1,5 +1,7 @@ // From https://github.com/antfu/birpc/blob/main/src/index.ts MIT +import { LoggerInterface } from "../log/log-base" + export type ArgumentsType = T extends (...args: infer A) => any ? A : never export type ReturnType = T extends (...args: any) => infer R ? R : never @@ -14,6 +16,8 @@ export interface RPCOptionsBasic { serialize?: (data: any) => any /** Custom function to deserialize data */ deserialize?: (data: any) => any + /** Custom logger */ + log?: LoggerInterface } export interface RPCOptions extends RPCOptionsBasic { @@ -53,16 +57,13 @@ type RPCMessage = [ const defaultSerialize = (i: any) => i const defaultDeserialize = defaultSerialize -export function useRPC( - functions: LocalFunctions, - options: RPCOptions, -): RPCReturn { +function setupRPCBasic(options: RPCOptionsBasic, functions: any, eventNames: string[] = []) { const { post, on, - eventNames = [], serialize = defaultSerialize, deserialize = defaultDeserialize, + log, } = options const rpcPromiseMap = new Map< @@ -71,150 +72,109 @@ export function useRPC( >() on(async (data) => { - const msg = deserialize(data) as RPCMessage - const [mode, args, id, method] = msg - if (mode === RPCMode.request || mode === RPCMode.event) { - let result, error: any - if (method != null) { - try { - const fn = (functions as any)[method] // todo - result = await fn(...args) + try { + const msg = deserialize(data) as RPCMessage + const [mode, args, id, method] = msg + if (mode === RPCMode.request || mode === RPCMode.event) { + let result, error: any + if (method != null) { + try { + const fn = functions[method] + result = await fn(...args) + } + catch (e) { + error = String(e) + } } - catch (e) { - error = String(e) + else { + error = 'Method implementation missing' } - } - else { - error = 'Method implementation missing' - } - if (mode === RPCMode.request && id) { if (error) - post(serialize([RPCMode.reject, error, id])) - else - post(serialize([RPCMode.resolve, result, id])) + log?.warn('error', msg, error) + if (mode === RPCMode.request && id) { + if (error) + post(serialize([RPCMode.reject, error, id])) + else + post(serialize([RPCMode.resolve, result, id])) + } } - } - else if (id) { - const promise = rpcPromiseMap.get(id) - if (promise != null) { - if (mode === RPCMode.reject) - promise.reject(args) - else promise.resolve(args) + else if (id) { + const promise = rpcPromiseMap.get(id) + if (promise != null) { + if (mode === RPCMode.reject) + promise.reject(args) + else promise.resolve(args) + } + rpcPromiseMap.delete(id) } - rpcPromiseMap.delete(id) + } + catch (err) { + log?.warn('Error on handling RPC data. Invalid?', err, data) } }) - return new Proxy( - {}, - { - get(_, method) { - const sendEvent = (...args: any[]) => { - post(serialize([RPCMode.event, args, null, method])) - } - if (options.onlyEvents || eventNames.includes(method as any)) { - sendEvent.asEvent = sendEvent - return sendEvent - } - const sendCall = (...args: any[]) => { - return new Promise((resolve, reject) => { - const id = rpcCounter++ - rpcPromiseMap.set(id, { resolve, reject }) - post(serialize([RPCMode.request, args, id, method])) - }) - } - sendCall.asEvent = sendEvent - return sendCall - }, + const proxyHandler = { + get(_: any, method: string) { + const sendEvent = (...args: any[]) => { + post(serialize([RPCMode.event, args, null, method])) + } + if (options.onlyEvents || eventNames.includes(method)) { + sendEvent.asEvent = sendEvent + return sendEvent + } + const sendCall = (...args: any[]) => { + return new Promise((resolve, reject) => { + const id = rpcCounter++ + rpcPromiseMap.set(id, { resolve, reject }) + post(serialize([RPCMode.request, args, id, method])) + }) + } + sendCall.asEvent = sendEvent + return sendCall }, - ) as any + } + + return { post, serialize, rpcPromiseMap, proxyHandler } } -export function useRPCHub(options: RPCOptionsBasic) { - const { - post, - on, - serialize = defaultSerialize, - deserialize = defaultDeserialize, - } = options +export function useRPC( + functions: LocalFunctions, + options: RPCOptions, +): RPCReturn { + const { eventNames = [] } = options + const { proxyHandler } = setupRPCBasic(options, functions, eventNames as any) + return new Proxy({}, proxyHandler) +} + +export function useRPCHub(options: RPCOptionsBasic) { const eventNames: string[] = [] const functions: Record = {} - const rpcPromiseMap = new Map< - number, - { resolve: (...args: any) => any; reject: (...args: any) => any } - >() - - on(async (data) => { - const msg = deserialize(data) as RPCMessage - const [mode, args, id, method] = msg - if (mode === RPCMode.request || mode === RPCMode.event) { - let result, error: any - if (method != null) { - try { - const fn = functions[method] - result = await fn(...args) - } - catch (e) { - error = String(e) - } - } - else { - error = 'Method implementation missing' - } - if (mode === RPCMode.request && id) { - if (error) - post(serialize([RPCMode.reject, error, id])) - else - post(serialize([RPCMode.resolve, result, id])) - } - } - else if (id) { - const promise = rpcPromiseMap.get(id) - if (promise != null) { - if (mode === RPCMode.reject) - promise.reject(args) - else promise.resolve(args) - } - rpcPromiseMap.delete(id) - } - }) + const { proxyHandler } = setupRPCBasic(options, functions) function createRPCProxy() { - return new Proxy( - {}, - { - get(_, method) { - const sendEvent = (...args: any[]) => { - post(serialize([RPCMode.event, args, null, method])) - } - if (options.onlyEvents || eventNames.includes(method as any)) { - sendEvent.asEvent = sendEvent - return sendEvent - } - const sendCall = (...args: any[]) => { - return new Promise((resolve, reject) => { - const id = rpcCounter++ - rpcPromiseMap.set(id, { resolve, reject }) - post(serialize([RPCMode.request, args, id, method])) - }) - } - sendCall.asEvent = sendEvent - return sendCall - }, - }, - ) as any + return new Proxy({}, proxyHandler) } - return function( + return function( additionalFunctions?: LocalFunctions, additionalEventNames: string[] = [], ): RPCReturn { Object.assign(functions, additionalFunctions ?? {}) + // log(`Registered functions:\n${Object.keys(functions).join('\n')}`) eventNames.push(...additionalEventNames) return createRPCProxy() } } export type UseRPCHubType = ReturnType + +// Syntax test case +// const hub: UseRPCHubType = {} as any +// const x = hub({ +// test(name: string): string { +// return name +// }, +// }) +// await x.test('dsd')