{"version":3,"file":"messages.mjs","names":[],"sources":["../../../src/common/msg/messages.ts"],"sourcesContent":["import type { LogLevelAliasType } from '../log/log-base'\nimport type { Json } from '../types'\nimport type { Channel } from './channel'\nimport type { Encoder } from './encoder'\nimport { valueToString } from '../data/convert'\nimport { isPromise, tryTimeout } from '../exec/promise'\nimport { DefaultLogger } from '../log/log'\nimport { uname, uuid } from '../uuid'\nimport { JsonEncoder } from './encoder'\n\nexport interface MessageAction {\n  name: string\n  id: string\n  args?: Json[]\n}\n\nexport interface MessageResult {\n  id: string\n  result?: Json\n  error?: { stack?: string, name: string, message: string }\n}\n\nexport type Message = MessageAction | MessageResult\n\nexport interface MessagesOptions {\n  timeout?: number\n}\n\nexport interface MessagesDefaultMethods<L> {\n  dispose: () => void\n  connect?: (channel: Channel) => void\n  options: (opt: MessagesOptions) => L\n}\n\nexport type MessagesMethods<L> = L & MessagesDefaultMethods<L>\n\n// export type MessageDefinitions = {\n//   [key: string]: (...args: any) => Promise<any>\n// }\n\nexport type MessageDefinitions = Record<any, (...args: any) => Promise<any>>\n\nexport interface MessageHub {\n  dispose: () => void\n  connect: (newChannel: Channel) => void\n  listen: <L extends MessageDefinitions>(newHandlers: L) => void\n  send: <L extends MessageDefinitions>() => MessagesMethods<L>\n}\n\n// The async proxy, waiting for a response\nexport function createPromiseProxy<P extends object>(fn: (name: string, args: any[], opt: any) => Promise<unknown>, opt: MessagesOptions, predefinedMethods: any = {}): P {\n  return new Proxy<P>(predefinedMethods, {\n    get: (target: any, name: any) => {\n      if (name in target)\n        return target[name]\n      return (...args: any): any => fn(name, args, opt)\n    },\n  })\n}\n\n/**\n * RPC\n *\n * Features:\n * - Waits for connection\n * - Retries after fail\n * - Timeouts\n */\nexport function useMessageHub(\n  opt: {\n    name?: string\n    channel?: Channel\n    encoder?: Encoder\n    retryAfter?: number\n    ignoreUnhandled?: boolean\n    debug?: boolean\n    logLevel?: LogLevelAliasType\n  } = {},\n): MessageHub {\n  const {\n    name = uname('hub'),\n    encoder = new JsonEncoder(),\n    retryAfter = 1000,\n    ignoreUnhandled = true,\n    logLevel = false,\n  } = opt\n\n  const log = DefaultLogger(name, logLevel)\n\n  const handlers = {}\n  let channel: Channel | undefined\n  const queue: Message[] = []\n  let queueRetryTimer: any\n  const waitingForResponse: Record<string, [\n    (result?: any) => any, // resolve\n    (result?: any) => any, // reject\n  ]> = {}\n\n  const dispose = () => {\n    clearTimeout(queueRetryTimer)\n  }\n\n  const postNext = async () => {\n    clearTimeout(queueRetryTimer)\n    if (channel) {\n      if (channel.isConnected) {\n        while (queue.length) {\n          const message = queue[0]\n          try {\n            channel.postMessage(await encoder.encode(message))\n            queue.shift() // remove from queue when done\n          }\n          catch (err) {\n            log.warn('postMessage', err)\n            break\n          }\n        }\n      }\n      if (queue.length > 0 && retryAfter > 0)\n        queueRetryTimer = setTimeout(postNext, retryAfter)\n    }\n  }\n\n  const postMessage = async (message: Message) => {\n    log('enqueue postMessage', message)\n    queue.push(message)\n    await postNext()\n  }\n\n  const connect = async (newChannel: Channel) => {\n    channel = newChannel\n\n    channel.on('connect', postNext)\n\n    channel.on('message', async (msg: any) => {\n      log('onmessage', typeof msg)\n      const { name, args, id, result, error } = await encoder.decode(msg.data)\n\n      // Incoming new message\n      if (name) {\n        log(`name ${name} id ${id}`)\n        try {\n          // @ts-expect-error xxx\n          if (handlers[name] == null)\n            throw new Error(`handler for ${name} was not found`)\n\n          // @ts-expect-error xxx\n          let result = handlers[name](...args)\n          if (isPromise(result))\n            result = await result\n          log(`result ${result}`)\n          if (id)\n            await postMessage({ id, result })\n        }\n        catch (error) {\n          const err = error instanceof Error ? error : new Error(valueToString(error))\n          log.warn('execution error', err.name)\n          await postMessage({\n            id,\n            error: {\n              message: err.message,\n              stack: err.stack,\n              name: err.name,\n            },\n          })\n        }\n      }\n\n      // Incoming new response\n      else if (id) {\n        log(`response for id=${id}: result=${result}, error=${error}`)\n        if (waitingForResponse[id] == null) {\n          if (result === undefined)\n            log(`skip response for ${id}`)\n          else\n            log.warn(`no response hook for ${id}`)\n        }\n        else {\n          const [resolve, reject] = waitingForResponse[id]\n          if (resolve && reject) {\n            delete waitingForResponse[id]\n            if (error) {\n              const err = new Error(error.message)\n              err.stack = error.stack\n              err.name = error.name\n              log.warn('reject', err.name)\n              reject(err)\n            }\n            else {\n              log('resolve', result)\n              resolve(result)\n            }\n          }\n        }\n      }\n\n      // Don't know what to do with it\n      else if (!ignoreUnhandled) {\n        log.warn('Unhandled message', msg)\n      }\n    })\n\n    await postNext()\n  }\n\n  const fetchMessage = async (\n    name: string,\n    args: any[],\n    opt: MessagesOptions = {},\n  ): Promise<unknown> => {\n    const { timeout = 5000 } = opt\n    const id = uuid()\n    await postMessage({\n      name,\n      args,\n      id,\n    })\n    return tryTimeout(\n      new Promise(\n        (resolve, reject) => (waitingForResponse[id] = [resolve, reject]),\n      ),\n      timeout,\n    )\n  }\n\n  if (opt.channel)\n    void connect(opt.channel) // todo async\n\n  return {\n    dispose,\n\n    connect,\n    listen<L extends object>(newHandlers: L) {\n      Object.assign(handlers, newHandlers)\n    },\n    send<L extends object>() {\n      // The regular proxy without responding, just send\n      return createPromiseProxy<L>(fetchMessage, {}, {\n        options(perCallopt: MessagesOptions) {\n          return createPromiseProxy<L>(fetchMessage, { ...perCallopt })\n        },\n      } as MessagesDefaultMethods<L>) as MessagesMethods<L>\n    },\n  }\n}\n"],"mappings":"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"}