@trpc/server
Version:
1 lines • 38.9 kB
Source Map (JSON)
{"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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"}