{"version":3,"file":"rpc.mjs","names":[],"sources":["../../../src/common/msg/rpc.ts"],"sourcesContent":["// From https://github.com/antfu/birpc/blob/main/src/index.ts MIT\n\nimport type { UseStringHashPool } from '../data/string-hash-pool'\nimport type { LoggerInterface } from '../log/log-base'\nimport type { Pipe } from './pipe'\nimport { createPromise } from '../exec/promise'\n\nexport type ArgumentsType<T> = T extends (...args: infer A) => any ? A : never\nexport type ReturnType<T> = T extends (...args: any) => infer R ? R : never\n\nexport interface RPCOptionsBasic extends Pipe {\n  /** No return values expected */\n  onlyEvents?: boolean\n  /** Maximum timeout for waiting for response, in milliseconds */\n  timeout?: number\n  /** Custom logger */\n  log?: LoggerInterface\n  /** Custom error handler */\n  onError?: (error: Error, functionName: string, args: any[]) => boolean | void\n  /** Custom error handler for timeouts */\n  onTimeoutError?: (functionName: string, args: any[]) => boolean | void\n  /** Throw execptions. Default: true */\n  exceptions?: boolean\n  /**  */\n  stringHashPool?: UseStringHashPool\n}\n\nexport interface RPCOptions<Remote> extends RPCOptionsBasic {\n  // /** No return values expected */\n  // onlyEvents?: boolean\n  /** Names of remote functions that do not need response. */\n  eventNames?: (keyof Remote)[]\n}\n\nexport interface RPCFn<T> {\n  /** Call the remote function and wait for the result. */\n  (...args: ArgumentsType<T>): Promise<Awaited<ReturnType<T>>>\n  /** Send event without asking for response */\n  asEvent: (...args: ArgumentsType<T>) => void\n}\n\nexport type RPCReturn<RemoteFunctions> = {\n  [K in keyof RemoteFunctions]: RPCFn<RemoteFunctions[K]>\n}\n\nlet rpcCounter = 1\n\nexport enum RPCMode {\n  request = 1,\n  event = 2,\n  resolve = 3,\n  reject = 4,\n}\n\nexport type RPCMessage = [\n  mode: RPCMode,\n  id: number,\n  method: string | number | any,\n  ...any,\n]\n\nconst defaultSerialize = (i: any) => i\nconst defaultDeserialize = defaultSerialize\n\nfunction setupRPCBasic(options: RPCOptionsBasic, functions: Record<string, (...args: any) => Promise<any>>, eventNames: string[] = []) {\n  const {\n    log,\n    post,\n    on,\n    serialize = defaultSerialize,\n    deserialize = defaultDeserialize,\n    timeout = 60e3,\n    onError,\n    onTimeoutError,\n    onlyEvents = false,\n    exceptions = true,\n    stringHashPool,\n  } = options\n\n  if (stringHashPool) {\n    Object.keys(functions).forEach(stringHashPool.hash)\n  }\n\n  function checkEventNames(eventNames: string[]) {\n    // eventNames.forEach((n) => {\n    //   if (functions[n] == null)\n    //     throw new Error(`event name ${n} has no registered function`)\n    // })\n  }\n\n  checkEventNames(eventNames)\n\n  function registerFunctions(additionalFunctions: any) {\n    Object.assign(functions, additionalFunctions ?? {})\n    if (stringHashPool)\n      Object.keys(additionalFunctions).forEach(stringHashPool.hash)\n  }\n\n  function registerEventNames(additionalEventNames: string[]) {\n    checkEventNames(additionalEventNames)\n    eventNames.push(...additionalEventNames)\n  }\n\n  const rpcPromiseMap = new Map<number, {\n    resolve: (...args: any) => any\n    reject: (...args: any) => any\n    timeoutId: Parameters<typeof clearTimeout>[0]\n  }>()\n\n  on(async (data) => {\n    try {\n      const msg = await deserialize(data) as RPCMessage\n      const mode = msg?.[0]\n      const id = mode === RPCMode.event ? 0 : msg?.[1]\n      const [method, ...args] = msg.slice(mode === RPCMode.event ? 1 : 2)\n      const methodName = stringHashPool?.stringForHash(method) ?? method\n      if (mode === RPCMode.request || mode === RPCMode.event) {\n        let result, error: any\n        if (method != null) {\n          try {\n            const fn = functions[methodName]\n            result = await fn(...args)\n          }\n          catch (e) {\n            error = String(e)\n          }\n        }\n        else {\n          error = 'Method implementation missing'\n        }\n        if (error) {\n          log?.warn('error', msg, error)\n          onError?.(error, method ?? '', args)\n        }\n        if (id > 0) {\n          const data = await serialize(error\n            ? [RPCMode.reject, id, error]\n            : [RPCMode.resolve, id, result])\n          await post(data)\n        }\n      }\n      else if (id) {\n        const promise = rpcPromiseMap.get(id)\n        if (promise != null) {\n          clearTimeout(promise.timeoutId)\n          if (mode === RPCMode.reject && exceptions === true)\n            promise.reject(method)\n          else\n            promise.resolve(method)\n        }\n        rpcPromiseMap.delete(id)\n      }\n    }\n    catch (err) {\n      log?.warn('Error on handling RPC data. Invalid?', err, data)\n    }\n  })\n\n  const proxyHandler = {\n    get(_: any, methodName: string) {\n      const method = stringHashPool?.hash(methodName) ?? methodName\n      const sendEvent = async (...args: any[]) => await post(await serialize([RPCMode.event, method, ...args]))\n\n      if (onlyEvents || eventNames.includes(methodName)) {\n        sendEvent.asEvent = sendEvent\n        return sendEvent\n      }\n\n      const sendCall = async (...args: any[]) => {\n        const [promise, resolve, reject] = createPromise()\n        const id = rpcCounter++\n\n        let timeoutId: any\n        if (timeout >= 0) {\n          timeoutId = setTimeout(() => {\n            try {\n              // Custom onTimeoutError handler can throw its own error too\n              onTimeoutError?.(methodName, args)\n              throw new Error(`rpc timeout on calling \"${methodName}\"`)\n            }\n            catch (e) {\n              if (exceptions === true)\n                reject(e)\n              else\n                resolve(undefined)\n            }\n            rpcPromiseMap.delete(id)\n\n            // Garbage Collection https://jakearchibald.com/2024/garbage-collection-and-closures/\n            clearTimeout(timeoutId)\n            timeoutId = undefined\n          }, timeout).unref?.()\n        }\n\n        rpcPromiseMap.set(id, { resolve, reject, timeoutId })\n        const data = await serialize([RPCMode.request, id, method, ...args])\n        await post(data)\n        return promise\n      }\n      sendCall.asEvent = sendEvent\n      return sendCall\n    },\n  }\n\n  return {\n    post,\n    serialize,\n    rpcPromiseMap,\n    proxyHandler,\n    registerFunctions,\n    registerEventNames,\n  }\n}\n\nexport function useRPC<LocalFunctions, RemoteFunctions = LocalFunctions>(\n  functions: LocalFunctions,\n  options: RPCOptions<RemoteFunctions>,\n): RPCReturn<RemoteFunctions> {\n  const { eventNames = [] } = options\n\n  const { proxyHandler } = setupRPCBasic(options, functions as any, eventNames as any)\n\n  return new Proxy({}, proxyHandler)\n}\n\nexport function useRPCHub(options: RPCOptionsBasic) {\n  const eventNames: string[] = []\n  const functions: Record<string, any> = {}\n\n  const {\n    proxyHandler,\n    registerFunctions,\n    registerEventNames,\n  } = setupRPCBasic(options, functions, eventNames)\n\n  function createRPCProxy() {\n    return new Proxy({}, proxyHandler)\n  }\n\n  return function<LocalFunctions, RemoteFunctions = LocalFunctions>(\n    additionalFunctions?: LocalFunctions,\n    additionalEventNames: string[] = [],\n  ): RPCReturn<RemoteFunctions> {\n    registerFunctions(additionalFunctions ?? {})\n    registerEventNames(additionalEventNames)\n    return createRPCProxy()\n  }\n}\n\nexport type UseRPCHubType = ReturnType<typeof useRPCHub>\n\n// Syntax test case\n// async function _demo() {\n//   const hub: UseRPCHubType = {} as any\n//   const x = hub({\n//     test(name: string): string {\n//       return name\n//     },\n//   })\n//   await x.test('dsd')\n// }\n"],"mappings":"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"}