{"version":3,"sources":["../src/remote.ts"],"sourcesContent":["import {\r\n  RemoteError,\r\n  RemoteNotFoundError,\r\n  RemoteTimeoutError,\r\n  response,\r\n} from './response'\r\n\r\nimport type { Adaptor, AdaptorCallback, AdaptorPackageData } from './adaptor'\r\n\r\nconst RESPONSE_PREFIX = '_response_'\r\n\r\ntype LogFunc = (...data: unknown[]) => void\r\n\r\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\r\ntype RemoteCallableFunc = (data: any) => Promise<any>\r\n\r\ninterface RemoteFuncRecords {\r\n  [key: string]: RemoteCallableFunc\r\n}\r\n\r\ntype FuncMapWithConfig<T extends RemoteFuncRecords> = {\r\n  [K in keyof T]: T[K] extends (data: infer Arg) => Promise<infer Ret>\r\n    ? (\r\n        data: Arg,\r\n        config?: {\r\n          timeoutMs?: number\r\n          targetDeviceId?: string\r\n        }\r\n      ) => Promise<Ret>\r\n    : never\r\n}\r\n\r\ntype RegisteredFunc<T extends RemoteCallableFunc> = T extends (\r\n  data: infer Arg\r\n) => infer Ret\r\n  ? (\r\n      data: Arg,\r\n      ctx: {\r\n        /**\r\n         * 对方的设备 id\r\n         */\r\n        deviceId: string\r\n      }\r\n    ) => Ret\r\n  : never\r\n\r\nfunction defaultLog(\r\n  this: Remote<\r\n    {\r\n      [key: string]: RemoteCallableFunc\r\n    },\r\n    {\r\n      [key: string]: RemoteCallableFunc\r\n    }\r\n  >,\r\n  ...data: unknown[]\r\n) {\r\n  if (!this.debug) {\r\n    return\r\n  }\r\n  console.log(`[remote of ${this.deviceId}]`, ...data)\r\n}\r\n\r\nexport class Remote<\r\n  /**\r\n   * MF means my functions\r\n   */\r\n  MF extends RemoteFuncRecords,\r\n  /**\r\n   * OF means others functions\r\n   */\r\n  OF extends RemoteFuncRecords,\r\n> {\r\n  debug = false\r\n\r\n  private log: LogFunc\r\n\r\n  /**\r\n   * （调用对方函数时的）默认超时时间，单位 ms\r\n   * @default 30000\r\n   */\r\n  private defaultTimeoutMs = 30000\r\n\r\n  private map: {\r\n    [key: string]: {\r\n      callback: RegisteredFunc<RemoteCallableFunc>\r\n    }\r\n  } = {}\r\n\r\n  private deviceIdValue = ''\r\n\r\n  /**\r\n   * 设备 id 应该唯一，用于区分不同设备。\r\n   * 你可以在任何时候修改（更新）它。\r\n   * @default ''\r\n   */\r\n  get deviceId() {\r\n    return this.deviceIdValue\r\n  }\r\n\r\n  set deviceId(deviceId: string) {\r\n    this.log(`deviceId set: from \"${this.deviceIdValue}\" to \"${deviceId}\"`)\r\n    this.deviceIdValue = deviceId\r\n  }\r\n\r\n  constructor(\r\n    private adaptor: Adaptor,\r\n    config?: {\r\n      /**\r\n       * 设备 id 应该唯一，用于区分不同设备。\r\n       * 你可以在任何时候修改（更新）它。\r\n       * @default ''\r\n       */\r\n      deviceId?: string\r\n      /**\r\n       * （调用对方函数时的）默认超时时间，单位 ms\r\n       * @default 30000\r\n       */\r\n      defaultTimeoutMs?: number\r\n      debug?: boolean\r\n      /**\r\n       * 格式化 AdaptorPackageData 的函数，\r\n       * 用于调试时输出日志。\r\n       * @default JSON.stringify\r\n       */\r\n      log?: LogFunc\r\n    }\r\n  ) {\r\n    this.debug = config?.debug ?? this.debug\r\n    this.defaultTimeoutMs = config?.defaultTimeoutMs ?? this.defaultTimeoutMs\r\n    this.log = config?.log ?? defaultLog.bind(this)\r\n    this.deviceId = config?.deviceId ?? this.deviceId\r\n\r\n    adaptor.every(async (e) => {\r\n      const { deviceId: selfDeviceId } = this\r\n      const { name, data, deviceId: targetDeviceId, callbackName } = e\r\n      const callback = this.map[name]?.callback\r\n      if (!callback) {\r\n        if (name.startsWith(RESPONSE_PREFIX)) {\r\n          // 这是响应，会在 callAsync once 中处理，这儿不用处理\r\n          this.log('[every] response received: ', e)\r\n          return\r\n        }\r\n        this.log('callback not found: ', name)\r\n        if (callbackName) {\r\n          adaptor.emit({\r\n            deviceId: selfDeviceId,\r\n            targetDeviceId,\r\n            name: callbackName,\r\n            data: response.error(\r\n              new RemoteNotFoundError(`callback not found: ${name}`)\r\n            ),\r\n          })\r\n        }\r\n        return\r\n      }\r\n      if (!callbackName) {\r\n        this.log('should not respond: ', e)\r\n        void callback(data, { deviceId: targetDeviceId })\r\n        return\r\n      }\r\n      this.log(`callback: ${name}; respondName: ${callbackName}; data: `, data)\r\n      try {\r\n        const ret = await callback(data, { deviceId: targetDeviceId })\r\n        this.log('callback return: ', ret)\r\n        adaptor.emit({\r\n          deviceId: selfDeviceId,\r\n          targetDeviceId,\r\n          name: callbackName,\r\n          data: response.success(ret),\r\n        })\r\n      } catch (error) {\r\n        this.log('callback error: ', error)\r\n        adaptor.emit({\r\n          deviceId: selfDeviceId,\r\n          targetDeviceId,\r\n          name: callbackName,\r\n          data: response.error(RemoteError.fromError(error)),\r\n        })\r\n      }\r\n    })\r\n  }\r\n\r\n  /**\r\n   * 调用其他端的方法；会等待对方响应。\r\n   * 不能直接使用该方法，应该使用 proxy。\r\n   * @WARNING 不能用于响应其他端。\r\n   */\r\n  private callAsync(\r\n    name: string,\r\n    data: unknown,\r\n    config?: {\r\n      timeoutMs?: number\r\n      targetDeviceId?: string\r\n    }\r\n  ) {\r\n    const timeoutMs = config?.timeoutMs ?? this.defaultTimeoutMs\r\n    const { deviceId } = this\r\n    const randomStr = Math.random().toString(36).slice(2)\r\n    // 本条消息的响应名\r\n    const responseName = `${RESPONSE_PREFIX}-${name}-${deviceId}-${randomStr}`\r\n    return new Promise((resolve, reject) => {\r\n      let timer: NodeJS.Timeout | undefined\r\n      this.log(`callAsync ${name}: waiting for response: ${responseName}`)\r\n      const callback: AdaptorCallback = (e) => {\r\n        clearTimeout(timer)\r\n        this.log(`response received: ${responseName}`, e)\r\n        if (RemoteError.isRemoteError(e.data)) {\r\n          reject(RemoteError.fromError(e.data))\r\n        } else {\r\n          resolve((e.data as ReturnType<typeof response.success>)?.data)\r\n        }\r\n      }\r\n      this.adaptor.once(responseName, callback)\r\n      timer = setTimeout(() => {\r\n        this.log(`timeout: ${responseName}`)\r\n        this.adaptor.off(responseName, callback)\r\n        reject(new RemoteTimeoutError(`timeout: ${name}`))\r\n      }, timeoutMs)\r\n      this.adaptor.emit({\r\n        deviceId,\r\n        targetDeviceId: config?.targetDeviceId ?? '',\r\n        name,\r\n        data,\r\n        callbackName: responseName,\r\n      })\r\n    })\r\n  }\r\n\r\n  /**\r\n   * 注册方法，供对方调用；\r\n   */\r\n  register<K extends keyof MF>(name: K, callback: RegisteredFunc<MF[K]>): void {\r\n    this.map[name as string] = { callback }\r\n  }\r\n\r\n  _ = new Proxy<FuncMapWithConfig<OF>>({} as FuncMapWithConfig<OF>, {\r\n    get: (_, k) => this.callAsync.bind(this, k as string),\r\n  })\r\n\r\n  self = new Proxy<MF>({} as MF, {\r\n    get: (_, k) => (data: unknown) =>\r\n      this.map[k as string]?.callback.apply(null, [\r\n        data,\r\n        {\r\n          deviceId: this.deviceId,\r\n        },\r\n      ]),\r\n  })\r\n}\r\n\r\nexport function isRemoteAdaptorData(data: unknown): data is AdaptorPackageData {\r\n  return (\r\n    !!data &&\r\n    typeof data === 'object' &&\r\n    'deviceId' in data &&\r\n    'name' in data &&\r\n    typeof data.deviceId === 'string' &&\r\n    typeof data.name === 'string' &&\r\n    !!data.deviceId &&\r\n    !!data.name\r\n  )\r\n}\r\n"],"mappings":"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","names":["RemoteError","RemoteNotFoundError","RemoteTimeoutError","response","RESPONSE_PREFIX","defaultLog","data","Remote","adaptor","config","e","_a","selfDeviceId","name","targetDeviceId","callbackName","callback","ret","error","deviceId","timeoutMs","randomStr","responseName","resolve","reject","timer","_","k","isRemoteAdaptorData"]}