{"version":3,"file":"ws-BqEmiHOX.mjs","names":["callTRPCProcedure"],"sources":["../src/adapters/wsEncoder.ts","../src/adapters/ws.ts"],"sourcesContent":["/**\n * Encodes the messages a WebSocket server sends and decodes the ones it\n * gets. The client's `experimental_encoder` must match.\n *\n * @example\n * ```ts\n * const customEncoder: Encoder = {\n *   encode: (data) => myFormat.stringify(data),\n *   decode: (data) => myFormat.parse(data),\n * };\n * ```\n */\nexport interface Encoder {\n  /** Encodes a message to send. */\n  encode(data: unknown): string | Uint8Array<ArrayBuffer>;\n  /** Decodes a received message. */\n  decode(data: string | ArrayBuffer | Uint8Array): unknown;\n}\n\n/**\n * The default encoder: JSON in text frames.\n */\nexport const jsonEncoder: Encoder = {\n  encode: (data) => JSON.stringify(data),\n  decode: (data) => {\n    if (typeof data !== 'string') {\n      throw new Error(\n        'jsonEncoder received binary data. JSON uses text frames. ' +\n          'Use a binary encoder for binary data.',\n      );\n    }\n    return JSON.parse(data);\n  },\n};\n","import type { IncomingMessage } from 'http';\nimport type { WebSocketServer, WebSocket as WsWebSocket } from 'ws';\nimport type {\n  AnyRouter,\n  CreateContextCallback,\n  inferRouterContext,\n} from '../@trpc/server';\nimport {\n  callTRPCProcedure,\n  getErrorShape,\n  getTRPCErrorFromUnknown,\n  transformTRPCResponse,\n  TRPCError,\n} from '../@trpc/server';\nimport type { TRPCRequestInfo } from '../@trpc/server/http';\nimport { type BaseHandlerOptions } from '../@trpc/server/http';\nimport { parseTRPCMessage, TRPC_ERROR_CODES_BY_KEY } from '../@trpc/server/rpc';\n// @trpc/server/rpc\nimport type {\n  TRPCClientOutgoingMessage,\n  TRPCConnectionParamsMessage,\n  TRPCErrorShape,\n  TRPCReconnectNotification,\n  TRPCResponseMessage,\n  TRPCResultMessage,\n} from '../@trpc/server/rpc';\nimport { parseConnectionParamsFromUnknown } from '../http';\nimport { isObservable, observableToAsyncIterable } from '../observable';\n// oxlint-disable-next-line no-restricted-imports\nimport {\n  isAbortError,\n  isAsyncIterable,\n  isObject,\n  isPromise,\n  isTrackedEnvelope,\n  makeResource,\n  run,\n  type MaybePromise,\n} from '../unstable-core-do-not-import';\n// oxlint-disable-next-line no-restricted-imports\nimport type { Result } from '../unstable-core-do-not-import';\n// oxlint-disable-next-line no-restricted-imports\nimport { iteratorResource } from '../unstable-core-do-not-import/stream/utils/asyncIterable';\nimport { Unpromise } from '../vendor/unpromise';\nimport { createURL, type NodeHTTPCreateContextFnOptions } from './node-http';\nimport type { Encoder } from './wsEncoder';\nimport { jsonEncoder } from './wsEncoder';\n\n/**\n * Importing ws causes a build error\n * @see https://github.com/trpc/trpc/pull/5279\n */\nconst WEBSOCKET_OPEN = 1; /* ws.WebSocket.OPEN */\n\nfunction settle<TValue>(promise: Promise<TValue>): Promise<Result<TValue>> {\n  return promise.then(\n    (value): Result<TValue> => ({ ok: true, value }),\n    (error: unknown): Result<TValue> => ({ ok: false, error }),\n  );\n}\n\n/**\n * What `await using` rejects with when disposal fails after the scope threw:\n * `error` is the disposal failure and `suppressed` the original error.\n */\nfunction isSuppressedError(\n  cause: unknown,\n): cause is { error: unknown; suppressed: unknown } {\n  return (\n    isObject(cause) &&\n    cause['name'] === 'SuppressedError' &&\n    'error' in cause &&\n    'suppressed' in cause\n  );\n}\n\n/**\n * What the client gets when its error can't be formatted or encoded. It says\n * nothing about the error, which is what `errorFormatter` would have hidden.\n */\nconst fallbackErrorShape: TRPCErrorShape = {\n  code: TRPC_ERROR_CODES_BY_KEY.INTERNAL_SERVER_ERROR,\n  message: 'Internal server error',\n  data: { code: 'INTERNAL_SERVER_ERROR', httpStatus: 500 },\n};\n\n/**\n * A procedure that gives up because its subscription was cancelled rejects\n * with an `AbortError`, which is wrapped as an INTERNAL_SERVER_ERROR.\n */\nfunction isCancelledSetup(error: TRPCError, signal: AbortSignal) {\n  return (\n    signal.aborted &&\n    error.code === 'INTERNAL_SERVER_ERROR' &&\n    isAbortError(error.cause)\n  );\n}\n\n/**\n * What `createContext` gets in the WebSocket adapter: the upgrade request\n * as `req`, the socket as `res`, and `info`. It's called once per\n * connection.\n * @see https://trpc.io/docs/server/websockets#creating-a-websocket-server\n * @public\n */\nexport type CreateWSSContextFnOptions = NodeHTTPCreateContextFnOptions<\n  IncomingMessage,\n  WsWebSocket\n>;\n\n/**\n * The `createContext` function the WebSocket adapter takes.\n * @public\n */\nexport type CreateWSSContextFn<TRouter extends AnyRouter> = (\n  opts: CreateWSSContextFnOptions,\n) => MaybePromise<inferRouterContext<TRouter>>;\n\n/** The options of `getWSConnectionHandler`. */\nexport type WSConnectionHandlerOptions<TRouter extends AnyRouter> =\n  BaseHandlerOptions<TRouter, IncomingMessage> &\n    CreateContextCallback<\n      inferRouterContext<TRouter>,\n      CreateWSSContextFn<TRouter>\n    >;\n\n/**\n * The options of `applyWSSHandler`.\n * @see https://trpc.io/docs/server/websockets#creating-a-websocket-server\n */\nexport type WSSHandlerOptions<TRouter extends AnyRouter> =\n  WSConnectionHandlerOptions<TRouter> & {\n    /** The `ws` server to serve the router on. */\n    wss: WebSocketServer;\n    /** Only connections whose URL starts with this are served. */\n    prefix?: string;\n    /**\n     * Sends pings to each client and closes connections that stop\n     * answering.\n     */\n    keepAlive?: {\n      /**\n       * Whether to send pings.\n       * @default false\n       */\n      enabled: boolean;\n      /**\n       * How long a connection can be quiet before a ping, in milliseconds.\n       * @default 30_000\n       */\n      pingMs?: number;\n      /**\n       * How long to wait for a pong before closing the connection, in\n       * milliseconds.\n       * @default 5_000\n       */\n      pongWaitMs?: number;\n    };\n    /**\n     * Stops answering clients' pings. Meant for tests.\n     * @default false\n     */\n    dangerouslyDisablePong?: boolean;\n    /**\n     * Encodes and decodes messages, for formats other than JSON.\n     * Experimental: it may change in any release.\n     * @default jsonEncoder\n     */\n    experimental_encoder?: Encoder;\n  };\n\n/**\n * `iterator.next()` for a subscription loop that races it against the\n * subscription's `signal`, with a rejection turned into a `TRPCError`.\n *\n * Once the subscription is stopped, the loop doesn't wait for this call, so a\n * rejection after the stop, such as a teardown that throws, goes to\n * `onErrorAfterStop` and resolves `'abort'`. An `AbortError` is how a stopped\n * subscription is expected to end, and isn't reported.\n */\nfunction nextUnlessStopped<TValue, TReturn>(\n  iterator: AsyncIterator<TValue, TReturn>,\n  signal: AbortSignal,\n  onErrorAfterStop: (error: TRPCError) => void,\n): Promise<IteratorResult<TValue, TReturn> | TRPCError | 'abort'> {\n  return iterator.next().catch((cause: unknown) => {\n    const error = getTRPCErrorFromUnknown(cause);\n    if (!signal.aborted) {\n      return error;\n    }\n    if (!isAbortError(error.cause)) {\n      onErrorAfterStop(error);\n    }\n    return 'abort';\n  });\n}\n\n/**\n * Returns a `ws` connection listener that serves a router. `applyWSSHandler`\n * uses it; call it to attach tRPC to your own WebSocket server.\n */\nexport function getWSConnectionHandler<TRouter extends AnyRouter>(\n  opts: WSSHandlerOptions<TRouter>,\n) {\n  const { createContext, router } = opts;\n  const { transformer } = router._def._config;\n  const encoder = opts.experimental_encoder ?? jsonEncoder;\n\n  return (client: WsWebSocket, req: IncomingMessage) => {\n    type Context = inferRouterContext<TRouter>;\n    type ContextResult = Result<Context>;\n\n    const clientSubscriptions = new Map<number | string, AbortController>();\n    const abortController = new AbortController();\n\n    function registerSubscription(\n      id: number | string,\n      subscription: AbortController,\n    ) {\n      clientSubscriptions.set(id, subscription);\n      return makeResource({}, () => {\n        clientSubscriptions.delete(id);\n      });\n    }\n\n    if (opts.keepAlive?.enabled) {\n      const { pingMs, pongWaitMs } = opts.keepAlive;\n      handleKeepAlive(client, pingMs, pongWaitMs);\n    }\n\n    function respond(untransformedJSON: TRPCResponseMessage) {\n      client.send(\n        encoder.encode(\n          transformTRPCResponse(router._def._config, untransformedJSON),\n        ),\n      );\n    }\n\n    type ErrorInfo = Parameters<NonNullable<typeof opts.onError>>[0];\n\n    /**\n     * `onError` runs in listeners and in promises nothing awaits, where a\n     * throw or a rejection would crash the process, so either goes to\n     * `onFailure` instead.\n     */\n    function guardOnError(\n      info: ErrorInfo,\n      onFailure: (cause: unknown) => void,\n    ) {\n      try {\n        const result: unknown = opts.onError?.(info);\n        if (isPromise(result)) {\n          result.catch(onFailure);\n        }\n      } catch (cause) {\n        onFailure(cause);\n      }\n    }\n\n    /** Reports an error to `onError`, ignoring an `onError` that fails. */\n    function callOnError(info: ErrorInfo) {\n      guardOnError(info, () => {\n        // ignored, see guardOnError\n      });\n    }\n\n    /**\n     * Reports a failure of the adapter's own error handling, such as an\n     * `errorFormatter` that throws or a reply that can't be sent. It's logged\n     * when `onError` is missing or fails, so it's never dropped silently.\n     */\n    function reportInternalError(info: ErrorInfo) {\n      const log = () => {\n        // oxlint-disable-next-line no-console\n        console.error('[@trpc/server/adapters/ws]', info.error);\n      };\n      if (!opts.onError) {\n        log();\n        return;\n      }\n      guardOnError(info, log);\n    }\n\n    /**\n     * The error shape the client gets, or a minimal one when `errorFormatter`\n     * throws, so that the request is still answered.\n     */\n    function errorShape(call: Omit<ErrorInfo, 'ctx' | 'req'>): TRPCErrorShape {\n      try {\n        return getErrorShape({ config: router._def._config, ctx, ...call });\n      } catch (cause) {\n        reportInternalError({\n          ...call,\n          error: getTRPCErrorFromUnknown(cause),\n          ctx,\n          req,\n        });\n        return fallbackErrorShape;\n      }\n    }\n\n    function reportConnectionError(cause: unknown) {\n      reportInternalError({\n        error: getTRPCErrorFromUnknown(cause),\n        path: undefined,\n        type: 'unknown',\n        ctx,\n        req,\n        input: undefined,\n      });\n    }\n\n    /**\n     * Sends a response that isn't tied to a request, reporting it if it\n     * can't be built or sent. One that can't be sent is replaced by the\n     * fallback error, so that a client still learns it was rejected.\n     */\n    function respondOrReport(response: () => TRPCResponseMessage) {\n      let message: TRPCResponseMessage | null = null;\n      try {\n        message = response();\n        respond(message);\n      } catch (cause) {\n        reportConnectionError(cause);\n        if (message === null) {\n          return;\n        }\n        try {\n          respond({\n            id: message.id,\n            jsonrpc: message.jsonrpc,\n            error: fallbackErrorShape,\n          });\n        } catch {\n          // the same failure, and it was reported above\n        }\n      }\n    }\n\n    async function createCtxPromise(\n      getConnectionParams: () => TRPCRequestInfo['connectionParams'],\n    ): Promise<ContextResult> {\n      try {\n        return await run(async (): Promise<ContextResult> => {\n          ctx = await createContext?.({\n            req,\n            res: client,\n            info: {\n              connectionParams: getConnectionParams(),\n              calls: [],\n              isBatchCall: false,\n              accept: null,\n              type: 'unknown',\n              signal: abortController.signal,\n              url: null,\n            },\n          });\n\n          return {\n            ok: true,\n            value: ctx,\n          };\n        });\n      } catch (cause) {\n        const error = getTRPCErrorFromUnknown(cause);\n        callOnError({\n          error,\n          path: undefined,\n          type: 'unknown',\n          ctx,\n          req,\n          input: undefined,\n        });\n        respondOrReport(() => ({\n          id: null,\n          error: errorShape({\n            error,\n            type: 'unknown',\n            path: undefined,\n            input: undefined,\n          }),\n        }));\n\n        // close in next tick\n        (globalThis.setImmediate ?? globalThis.setTimeout)(() => {\n          client.close();\n        });\n        return {\n          ok: false,\n          error,\n        };\n      }\n    }\n\n    let ctx: Context | undefined = undefined;\n\n    /**\n     * promise for initializing the context\n     *\n     * - the context promise will be created immediately on connection if no connectionParams are expected\n     * - if connection params are expected, they will be created once received\n     */\n    let ctxPromise =\n      createURL(req).searchParams.get('connectionParams') === '1'\n        ? null\n        : createCtxPromise(() => null);\n\n    async function handleRequest(\n      msg: TRPCClientOutgoingMessage,\n      batchIndex: number,\n      ctxResult: Promise<ContextResult>,\n    ) {\n      const { id, jsonrpc } = msg;\n\n      if (id === null) {\n        const error = getTRPCErrorFromUnknown(\n          new TRPCError({\n            code: 'PARSE_ERROR',\n            message: '`id` is required',\n          }),\n        );\n        callOnError({\n          error,\n          path: undefined,\n          type: 'unknown',\n          ctx,\n          req,\n          input: undefined,\n        });\n        respond({\n          id,\n          jsonrpc,\n          error: errorShape({\n            error,\n            type: 'unknown',\n            path: undefined,\n            input: undefined,\n          }),\n        });\n        return;\n      }\n      if (msg.method === 'subscription.stop') {\n        clientSubscriptions.get(id)?.abort();\n        return;\n      }\n      const { path, lastEventId } = msg.params;\n      let { input } = msg.params;\n      const type = msg.method;\n\n      if (lastEventId !== undefined) {\n        if (isObject(input)) {\n          input = {\n            ...input,\n            lastEventId: lastEventId,\n          };\n        } else {\n          input ??= {\n            lastEventId: lastEventId,\n          };\n        }\n      }\n      const abortController = new AbortController();\n      // `abort()` builds an AbortError with a stack trace even when the\n      // signal is already aborted, which is the common case on a stop.\n      const abort = () => {\n        if (!abortController.signal.aborted) {\n          abortController.abort();\n        }\n      };\n      const reportError = (cause: unknown) => {\n        const error = getTRPCErrorFromUnknown(cause);\n        callOnError({ error, path, type, ctx, req, input });\n        return error;\n      };\n      const respondWithError = (cause: unknown) => {\n        const error = reportError(cause);\n        respond({\n          id,\n          jsonrpc,\n          error: errorShape({ error, type, path, input }),\n        });\n      };\n\n      if (type === 'subscription' && clientSubscriptions.has(id)) {\n        // duplicate request ids for client\n        respondWithError(\n          new TRPCError({\n            message: `Duplicate id ${id}`,\n            code: 'BAD_REQUEST',\n          }),\n        );\n        return;\n      }\n\n      const execute = async () => {\n        // Registered before any await so that a stop or a disconnect during\n        // context creation or the procedure call cancels the subscription.\n        // The id is freed once this has settled, after the iterator's\n        // disposal and right before the client is told it may reuse it.\n        using _subscription =\n          type === 'subscription'\n            ? registerSubscription(id, abortController)\n            : null;\n\n        const res = await ctxResult;\n        if (!res.ok) {\n          throw res.error;\n        }\n\n        if (abortController.signal.aborted) {\n          return;\n        }\n\n        const result = await callTRPCProcedure({\n          router,\n          path,\n          getRawInput: async () => input,\n          ctx,\n          type,\n          signal: abortController.signal,\n          batchIndex,\n        });\n\n        const isIterableResult =\n          isAsyncIterable(result) || isObservable(result);\n\n        if (type !== 'subscription') {\n          if (isIterableResult) {\n            throw new TRPCError({\n              code: 'UNSUPPORTED_MEDIA_TYPE',\n              message: `Cannot return an async iterable or observable from a ${type} procedure with WebSockets`,\n            });\n          }\n          // send the value as data if the method is not a subscription\n          respond({\n            id,\n            jsonrpc,\n            result: {\n              type: 'data',\n              data: result,\n            },\n          });\n          return;\n        }\n\n        if (!isIterableResult) {\n          throw new TRPCError({\n            message: `Subscription ${path} did not return an observable or a AsyncGenerator`,\n            code: 'INTERNAL_SERVER_ERROR',\n          });\n        }\n\n        const iterable = isObservable(result)\n          ? observableToAsyncIterable(result, abortController.signal)\n          : result;\n\n        // A failed iterator cleanup is reported to the server only: the\n        // client still gets `stopped`, so a subscription that completed or\n        // was stopped doesn't turn into a failed one.\n        if (abortController.signal.aborted) {\n          try {\n            // dispose the iterator without consuming it\n            await using _iterator = iteratorResource(iterable);\n          } catch (cause) {\n            reportError(cause);\n          }\n          return;\n        }\n\n        respond({\n          id,\n          jsonrpc,\n          result: {\n            type: 'started',\n          },\n        });\n\n        let ended = false;\n        try {\n          await using iterator = iteratorResource(iterable);\n          let completed = false;\n          // Disposed before the iterator, so cleanup that waits for\n          // cancellation is woken when iteration ends other than by `done`.\n          using _abortUnlessCompleted = makeResource({}, () => {\n            if (!completed) {\n              abort();\n            }\n          });\n\n          const abortPromise = new Promise<'abort'>((resolve) => {\n            abortController.signal.onabort = () => resolve('abort');\n          });\n          // We need those declarations outside the loop for garbage collection reasons. If they\n          // were declared inside, they would not be freed until the next value is present.\n          let next:\n            | null\n            | TRPCError\n            | Awaited<\n                typeof abortPromise | ReturnType<(typeof iterator)['next']>\n              >;\n          let result: null | TRPCResultMessage<unknown>['result'];\n\n          while (true) {\n            next = await Unpromise.race([\n              nextUnlessStopped(iterator, abortController.signal, (error) =>\n                callOnError({ error, path, type, ctx, req, input }),\n              ),\n              abortPromise,\n            ]);\n\n            if (next === 'abort') {\n              break;\n            }\n            if (next instanceof Error) {\n              // Report the error only after the iterator's disposal settles.\n              throw next;\n            }\n            if (next.done) {\n              completed = true;\n              break;\n            }\n\n            result = {\n              type: 'data',\n              data: next.value,\n            };\n\n            if (isTrackedEnvelope(next.value)) {\n              const [id, data] = next.value;\n              result.id = id;\n              result.data = {\n                id,\n                data,\n              };\n            }\n\n            respond({\n              id,\n              jsonrpc,\n              result,\n            });\n\n            // free up references for garbage collection\n            next = null;\n            result = null;\n          }\n          ended = true;\n        } catch (cause) {\n          abort();\n          if (ended) {\n            reportError(cause);\n          } else if (isSuppressedError(cause)) {\n            // the client gets the iteration error, the server both\n            respondWithError(cause.suppressed);\n            reportError(cause.error);\n          } else {\n            respondWithError(cause);\n          }\n        }\n      };\n\n      const outcome = await settle(execute());\n      if (!outcome.ok) {\n        const error = getTRPCErrorFromUnknown(outcome.error);\n        if (!isCancelledSetup(error, abortController.signal)) {\n          try {\n            respondWithError(error);\n          } catch (cause) {\n            // the error reply couldn't be encoded or sent\n            reportInternalError({\n              error: getTRPCErrorFromUnknown(cause),\n              path,\n              type,\n              ctx,\n              req,\n              input,\n            });\n            respondOrReport(() => ({ id, jsonrpc, error: fallbackErrorShape }));\n          }\n          return;\n        }\n      }\n      if (type === 'subscription') {\n        respond({ id, jsonrpc, result: { type: 'stopped' } });\n      }\n    }\n    client.on('message', (rawData, isBinary) => {\n      // Handle PING/PONG as text regardless of encoder\n      if (!isBinary) {\n        // oxlint-disable-next-line typescript/no-base-to-string\n        const msgStr = rawData.toString();\n        if (msgStr === 'PONG') {\n          return;\n        }\n        if (msgStr === 'PING') {\n          if (!opts.dangerouslyDisablePong) {\n            client.send('PONG');\n          }\n          return;\n        }\n      }\n\n      // Convert rawData to a format our encoder accepts\n      // ws gives us Buffer for both text and binary frames\n      if (!Buffer.isBuffer(rawData)) {\n        const error = new TRPCError({\n          code: 'UNPROCESSABLE_CONTENT',\n          message: 'Unexpected WebSocket message format',\n        });\n        respondOrReport(() => ({\n          id: null,\n          error: errorShape({\n            error,\n            type: 'unknown',\n            path: undefined,\n            input: undefined,\n          }),\n        }));\n        return;\n      }\n      const data: string | Uint8Array = isBinary\n        ? rawData\n        : rawData.toString('utf8');\n\n      if (!ctxPromise) {\n        // If the ctxPromise wasn't created immediately, we're expecting the first message to be a TRPCConnectionParamsMessage\n        ctxPromise = createCtxPromise(() => {\n          let msg;\n          try {\n            msg = encoder.decode(data) as TRPCConnectionParamsMessage;\n\n            if (!isObject(msg)) {\n              throw new Error('Message was not an object');\n            }\n          } catch (cause) {\n            throw new TRPCError({\n              code: 'PARSE_ERROR',\n              message: `Malformed TRPCConnectionParamsMessage`,\n              cause,\n            });\n          }\n\n          const connectionParams = parseConnectionParamsFromUnknown(msg.data);\n\n          return connectionParams;\n        });\n        return;\n      }\n\n      const parsedMsgs = run(() => {\n        try {\n          const msgJSON: unknown = encoder.decode(data);\n          const msgs: unknown[] = Array.isArray(msgJSON) ? msgJSON : [msgJSON];\n\n          if (\n            Array.isArray(msgJSON) &&\n            typeof opts.maxBatchSize === 'number' &&\n            msgs.length > opts.maxBatchSize\n          ) {\n            const error = new TRPCError({\n              code: 'BAD_REQUEST',\n              message: `Batch call exceeds maximum size`,\n            });\n            callOnError({\n              error,\n              path: undefined,\n              type: 'unknown',\n              ctx,\n              req,\n              input: undefined,\n            });\n            const shape = errorShape({\n              error,\n              type: 'unknown',\n              path: undefined,\n              input: undefined,\n            });\n\n            // Checked before parsing so that no input is deserialized.\n            // Every request in the batch gets the error, as a batched HTTP\n            // call does, so that clients do not wait for a response forever.\n            let responded = false;\n            for (const raw of msgs) {\n              if (\n                isObject(raw) &&\n                raw['method'] !== 'subscription.stop' &&\n                (typeof raw['id'] === 'number' || typeof raw['id'] === 'string')\n              ) {\n                const id = raw['id'];\n                const jsonrpc = raw['jsonrpc'] === '2.0' ? '2.0' : undefined;\n                respondOrReport(() => ({ id, jsonrpc, error: shape }));\n                responded = true;\n              }\n            }\n            if (!responded) {\n              respondOrReport(() => ({ id: null, error: shape }));\n            }\n            return [];\n          }\n\n          return msgs.map((raw) => parseTRPCMessage(raw, transformer));\n        } catch (cause) {\n          const error = new TRPCError({\n            code: 'PARSE_ERROR',\n            cause,\n          });\n\n          respondOrReport(() => ({\n            id: null,\n            error: errorShape({\n              error,\n              type: 'unknown',\n              path: undefined,\n              input: undefined,\n            }),\n          }));\n\n          return [];\n        }\n      });\n\n      for (const [index, msg] of parsedMsgs.entries()) {\n        handleRequest(msg, index, ctxPromise).catch(reportConnectionError);\n      }\n    });\n\n    // WebSocket errors should be handled, as otherwise unhandled exceptions will crash Node.js.\n    // This line was introduced after the following error brought down production systems:\n    // \"RangeError: Invalid WebSocket frame: RSV2 and RSV3 must be clear\"\n    // Here is the relevant discussion: https://github.com/websockets/ws/issues/1354#issuecomment-774616962\n    client.on('error', (cause) => {\n      callOnError({\n        ctx,\n        error: getTRPCErrorFromUnknown(cause),\n        input: undefined,\n        path: undefined,\n        type: 'unknown',\n        req,\n      });\n    });\n\n    client.once('close', () => {\n      for (const sub of clientSubscriptions.values()) {\n        sub.abort();\n      }\n      clientSubscriptions.clear();\n      abortController.abort();\n    });\n  };\n}\n\n/**\n * Pings a client after `pingMs` of quiet, and closes the connection if no\n * message comes within `pongWaitMs`.\n */\nexport function handleKeepAlive(\n  client: WsWebSocket,\n  pingMs = 30_000,\n  pongWaitMs = 5_000,\n) {\n  let timeout: NodeJS.Timeout | undefined = undefined;\n  let ping: NodeJS.Timeout | undefined = undefined;\n\n  const schedulePing = () => {\n    const scheduleTimeout = () => {\n      timeout = setTimeout(() => {\n        client.terminate();\n      }, pongWaitMs);\n    };\n    ping = setTimeout(() => {\n      client.send('PING');\n\n      scheduleTimeout();\n    }, pingMs);\n  };\n\n  const onMessage = () => {\n    clearTimeout(ping);\n    clearTimeout(timeout);\n\n    schedulePing();\n  };\n\n  client.on('message', onMessage);\n\n  client.on('close', () => {\n    clearTimeout(ping);\n    clearTimeout(timeout);\n  });\n\n  schedulePing();\n}\n\n/**\n * Serves a router on a `ws` WebSocket server. Returns\n * `broadcastReconnectNotification()`, which tells every client to\n * reconnect, for example before a deploy.\n * @example\n * ```ts\n * const wss = new WebSocketServer({ port: 3001 });\n * const handler = applyWSSHandler({ wss, router: appRouter, createContext });\n * process.on('SIGTERM', () => {\n *   handler.broadcastReconnectNotification();\n *   wss.close();\n * });\n * ```\n * @see https://trpc.io/docs/server/websockets#creating-a-websocket-server\n */\nexport function applyWSSHandler<TRouter extends AnyRouter>(\n  opts: WSSHandlerOptions<TRouter>,\n) {\n  const encoder = opts.experimental_encoder ?? jsonEncoder;\n  const onConnection = getWSConnectionHandler(opts);\n  opts.wss.on('connection', (client, req) => {\n    if (opts.prefix && !req.url?.startsWith(opts.prefix)) {\n      return;\n    }\n\n    onConnection(client, req);\n  });\n\n  return {\n    broadcastReconnectNotification: () => {\n      const response: TRPCReconnectNotification = {\n        id: null,\n        method: 'reconnect',\n      };\n      const data = encoder.encode(response);\n      for (const client of opts.wss.clients) {\n        if (client.readyState === WEBSOCKET_OPEN) {\n          client.send(data);\n        }\n      }\n    },\n  };\n}\n\nexport type { Encoder } from './wsEncoder';\nexport { jsonEncoder } from './wsEncoder';\n"],"mappings":"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"}