@replit/river 0.23.9 → 0.23.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-U4CSF3SQ.js → chunk-B323CECK.js} +473 -15
- package/dist/chunk-B323CECK.js.map +1 -0
- package/dist/{chunk-XUVTMFTJ.js → chunk-FRICSBDW.js} +2 -2
- package/dist/chunk-FRICSBDW.js.map +1 -0
- package/dist/{chunk-YGW5PYGV.js → chunk-KBAZ5TWE.js} +25 -35
- package/dist/chunk-KBAZ5TWE.js.map +1 -0
- package/dist/{chunk-KIWWKSRI.js → chunk-UXQMGZKP.js} +2 -2
- package/dist/{chunk-XKVIPKMR.js → chunk-Z4G27Y2I.js} +2 -2
- package/dist/{connection-afa08cf1.d.ts → connection-700340c4.d.ts} +1 -1
- package/dist/{connection-f5fb7805.d.ts → connection-efcd4e1a.d.ts} +1 -1
- package/dist/router/index.cjs +473 -14
- package/dist/router/index.cjs.map +1 -1
- package/dist/router/index.d.cts +41 -7
- package/dist/router/index.d.ts +41 -7
- package/dist/router/index.js +4 -2
- package/dist/{services-49b1b43d.d.ts → services-409c5545.d.ts} +2 -2
- package/dist/transport/impls/uds/client.cjs +23 -33
- package/dist/transport/impls/uds/client.cjs.map +1 -1
- package/dist/transport/impls/uds/client.d.cts +2 -2
- package/dist/transport/impls/uds/client.d.ts +2 -2
- package/dist/transport/impls/uds/client.js +3 -3
- package/dist/transport/impls/uds/server.cjs +22 -32
- package/dist/transport/impls/uds/server.cjs.map +1 -1
- package/dist/transport/impls/uds/server.d.cts +2 -2
- package/dist/transport/impls/uds/server.d.ts +2 -2
- package/dist/transport/impls/uds/server.js +3 -3
- package/dist/transport/impls/ws/client.cjs +26 -33
- package/dist/transport/impls/ws/client.cjs.map +1 -1
- package/dist/transport/impls/ws/client.d.cts +2 -2
- package/dist/transport/impls/ws/client.d.ts +2 -2
- package/dist/transport/impls/ws/client.js +6 -3
- package/dist/transport/impls/ws/client.js.map +1 -1
- package/dist/transport/impls/ws/server.cjs +22 -32
- package/dist/transport/impls/ws/server.cjs.map +1 -1
- package/dist/transport/impls/ws/server.d.cts +2 -2
- package/dist/transport/impls/ws/server.d.ts +2 -2
- package/dist/transport/impls/ws/server.js +3 -3
- package/dist/transport/index.cjs +24 -34
- package/dist/transport/index.cjs.map +1 -1
- package/dist/transport/index.d.cts +1 -1
- package/dist/transport/index.d.ts +1 -1
- package/dist/transport/index.js +2 -2
- package/dist/{transport-3d0ba1da.d.ts → transport-cf856c41.d.ts} +13 -17
- package/dist/util/testHelpers.cjs +1 -1
- package/dist/util/testHelpers.cjs.map +1 -1
- package/dist/util/testHelpers.d.cts +2 -2
- package/dist/util/testHelpers.d.ts +2 -2
- package/dist/util/testHelpers.js +3 -3
- package/package.json +3 -3
- package/dist/chunk-U4CSF3SQ.js.map +0 -1
- package/dist/chunk-XUVTMFTJ.js.map +0 -1
- package/dist/chunk-YGW5PYGV.js.map +0 -1
- /package/dist/{chunk-KIWWKSRI.js.map → chunk-UXQMGZKP.js.map} +0 -0
- /package/dist/{chunk-XKVIPKMR.js.map → chunk-Z4G27Y2I.js.map} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as Codec } from './types-3e5768ec.js';
|
|
2
1
|
import { a as TelemetryInfo, M as MessageMetadata, T as TransportClientId, b as PropagationContext, L as Logger, P as PartialTransportMessage, c as TransportMessage, O as OpaqueTransportMessage, g as LogFn, h as LoggingLevel } from './index-60f03cb7.js';
|
|
2
|
+
import { C as Codec } from './types-3e5768ec.js';
|
|
3
3
|
import { TSchema, Static } from '@sinclair/typebox';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -162,7 +162,6 @@ type ConnectionStatus = 'connect' | 'disconnect';
|
|
|
162
162
|
declare const ProtocolError: {
|
|
163
163
|
readonly RetriesExceeded: "conn_retry_exceeded";
|
|
164
164
|
readonly HandshakeFailed: "handshake_failed";
|
|
165
|
-
readonly UseAfterDestroy: "use_after_destroy";
|
|
166
165
|
readonly MessageOrderingViolated: "message_ordering_violated";
|
|
167
166
|
};
|
|
168
167
|
type ProtocolErrorType = (typeof ProtocolError)[keyof typeof ProtocolError];
|
|
@@ -180,11 +179,15 @@ interface EventMap {
|
|
|
180
179
|
type: ProtocolErrorType;
|
|
181
180
|
message: string;
|
|
182
181
|
};
|
|
182
|
+
transportStatus: {
|
|
183
|
+
status: TransportStatus;
|
|
184
|
+
};
|
|
183
185
|
}
|
|
184
186
|
type EventTypes = keyof EventMap;
|
|
185
187
|
type EventHandler<K extends EventTypes> = (event: EventMap[K]) => unknown;
|
|
186
188
|
declare class EventDispatcher<T extends EventTypes> {
|
|
187
189
|
private eventListeners;
|
|
190
|
+
removeAllListeners(): void;
|
|
188
191
|
numberOfListeners<K extends T>(eventType: K): number;
|
|
189
192
|
addEventListener<K extends T>(eventType: K, handler: EventHandler<K>): void;
|
|
190
193
|
removeEventListener<K extends T>(eventType: K, handler: EventHandler<K>): void;
|
|
@@ -290,7 +293,7 @@ type ServiceContextWithState<State> = ServiceContext & {
|
|
|
290
293
|
state: State;
|
|
291
294
|
};
|
|
292
295
|
type ServiceContextWithTransportInfo<State> = ServiceContext & {
|
|
293
|
-
state:
|
|
296
|
+
state: State;
|
|
294
297
|
to: TransportClientId;
|
|
295
298
|
from: TransportClientId;
|
|
296
299
|
streamId: string;
|
|
@@ -336,10 +339,9 @@ declare function createServerHandshakeOptions<MetadataSchema extends TSchema = T
|
|
|
336
339
|
/**
|
|
337
340
|
* Represents the possible states of a transport.
|
|
338
341
|
* @property {'open'} open - The transport is open and operational (note that this doesn't mean it is actively connected)
|
|
339
|
-
* @property {'closed'} closed - The transport is closed and
|
|
340
|
-
* @property {'destroyed'} destroyed - The transport is permanently destroyed and cannot be reopened.
|
|
342
|
+
* @property {'closed'} closed - The transport is permanently closed and cannot be reopened.
|
|
341
343
|
*/
|
|
342
|
-
type TransportStatus = 'open' | 'closed'
|
|
344
|
+
type TransportStatus = 'open' | 'closed';
|
|
343
345
|
type TransportOptions = SessionOptions;
|
|
344
346
|
type ProvidedTransportOptions = Partial<TransportOptions>;
|
|
345
347
|
type ClientTransportOptions = TransportOptions & ConnectionRetryOptions;
|
|
@@ -378,10 +380,9 @@ type ProvidedServerTransportOptions = Partial<ServerTransportOptions>;
|
|
|
378
380
|
*/
|
|
379
381
|
declare abstract class Transport<ConnType extends Connection> {
|
|
380
382
|
/**
|
|
381
|
-
*
|
|
382
|
-
* A destroyed transport will not attempt to reconnect and cannot be used again.
|
|
383
|
+
* The status of the transport.
|
|
383
384
|
*/
|
|
384
|
-
|
|
385
|
+
private status;
|
|
385
386
|
/**
|
|
386
387
|
* The {@link Codec} used to encode and decode messages.
|
|
387
388
|
*/
|
|
@@ -482,8 +483,8 @@ declare abstract class Transport<ConnType extends Connection> {
|
|
|
482
483
|
* @param msg The message to send.
|
|
483
484
|
* @returns The ID of the sent message or undefined if it wasn't sent
|
|
484
485
|
*/
|
|
485
|
-
send(to: TransportClientId, msg: PartialTransportMessage): string
|
|
486
|
-
sendCloseStream(to: TransportClientId, streamId: string): string
|
|
486
|
+
send(to: TransportClientId, msg: PartialTransportMessage): string;
|
|
487
|
+
sendCloseStream(to: TransportClientId, streamId: string): string;
|
|
487
488
|
protected protocolError(type: ProtocolErrorType, message: string): void;
|
|
488
489
|
/**
|
|
489
490
|
* Default close implementation for transports. You should override this in the downstream
|
|
@@ -491,12 +492,7 @@ declare abstract class Transport<ConnType extends Connection> {
|
|
|
491
492
|
* Closes the transport. Any messages sent while the transport is closed will be silently discarded.
|
|
492
493
|
*/
|
|
493
494
|
close(): void;
|
|
494
|
-
|
|
495
|
-
* Default destroy implementation for transports. You should override this in the downstream
|
|
496
|
-
* implementation if you need to do any additional cleanup and call super.destroy() at the end.
|
|
497
|
-
* Destroys the transport. Any messages sent while the transport is destroyed will throw an error.
|
|
498
|
-
*/
|
|
499
|
-
destroy(): void;
|
|
495
|
+
getStatus(): TransportStatus;
|
|
500
496
|
}
|
|
501
497
|
declare abstract class ClientTransport<ConnType extends Connection> extends Transport<ConnType> {
|
|
502
498
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../util/testHelpers.ts","../../node_modules/p-defer/index.js","../../node_modules/it-pushable/src/fifo.ts","../../node_modules/it-pushable/src/index.ts","../../router/result.ts","../../tracing/index.ts","../../package.json","../../util/stringify.ts","../../transport/session.ts","../../transport/transport.ts","../../codec/json.ts"],"sourcesContent":["import NodeWs, { WebSocketServer } from 'ws';\nimport http from 'node:http';\nimport { pushable } from 'it-pushable';\nimport {\n Err,\n PayloadType,\n Procedure,\n ServiceContext,\n ServiceContextWithTransportInfo,\n UNCAUGHT_ERROR,\n} from '../router';\nimport { RiverError, Result, RiverUncaughtSchema } from '../router/result';\nimport { Static } from '@sinclair/typebox';\nimport { nanoid } from 'nanoid';\nimport net from 'node:net';\nimport {\n OpaqueTransportMessage,\n PartialTransportMessage,\n} from '../transport/message';\nimport { coerceErrorString } from './stringify';\nimport { Connection, Session, SessionOptions } from '../transport/session';\nimport { Transport, defaultTransportOptions } from '../transport/transport';\nimport { WsLike } from '../transport/impls/ws/wslike';\n\n/**\n * Creates a WebSocket client that connects to a local server at the specified port.\n * This should only be used for testing.\n * @param port - The port number to connect to.\n * @returns A Promise that resolves to a WebSocket instance.\n */\nexport function createLocalWebSocketClient(port: number): WsLike {\n const sock = new NodeWs(`ws://localhost:${port}`);\n sock.binaryType = 'arraybuffer';\n\n return sock;\n}\n\n/**\n * Creates a WebSocket server instance using the provided HTTP server.\n * Only used as helper for testing.\n * @param server - The HTTP server instance to use for the WebSocket server.\n * @returns A Promise that resolves to the created WebSocket server instance.\n */\nexport function createWebSocketServer(server: http.Server) {\n return new WebSocketServer({ server });\n}\n\n/**\n * Starts listening on the given server and returns the automatically allocated port number.\n * This should only be used for testing.\n * @param server - The http server to listen on.\n * @returns A promise that resolves with the allocated port number.\n * @throws An error if a port cannot be allocated.\n */\nexport function onWsServerReady(server: http.Server): Promise<number> {\n return new Promise((resolve, reject) => {\n server.listen(() => {\n const addr = server.address();\n if (typeof addr === 'object' && addr) {\n resolve(addr.port);\n } else {\n reject(new Error(\"couldn't find a port to allocate\"));\n }\n });\n });\n}\n\nexport function onUdsServeReady(\n server: net.Server,\n path: string,\n): Promise<void> {\n return new Promise<void>((resolve) => {\n server.listen(path, resolve);\n });\n}\n\n/**\n * Retrieves the next value from an async iterable iterator.\n * @param iter The async iterable iterator.\n * @returns A promise that resolves to the next value from the iterator.\n */\nexport async function iterNext<T>(iter: AsyncIterableIterator<T>) {\n return await iter.next().then((res) => res.value as T);\n}\n\nexport function payloadToTransportMessage<Payload>(\n payload: Payload,\n): PartialTransportMessage<Payload> {\n return {\n streamId: 'stream',\n controlFlags: 0,\n payload,\n };\n}\n\nexport function createDummyTransportMessage() {\n return payloadToTransportMessage({\n msg: 'cool',\n test: Math.random(),\n });\n}\n\n/**\n * Waits for a message on the transport.\n * @param {Transport} t - The transport to listen to.\n * @param filter - An optional filter function to apply to the received messages.\n * @returns A promise that resolves with the payload of the first message that passes the filter.\n */\nexport async function waitForMessage(\n t: Transport<Connection>,\n filter?: (msg: OpaqueTransportMessage) => boolean,\n rejectMismatch?: boolean,\n) {\n return new Promise((resolve, reject) => {\n function cleanup() {\n t.removeEventListener('message', onMessage);\n }\n\n function onMessage(msg: OpaqueTransportMessage) {\n if (!filter || filter(msg)) {\n cleanup();\n resolve(msg.payload);\n } else if (rejectMismatch) {\n cleanup();\n reject(new Error('message didnt match the filter'));\n }\n }\n\n t.addEventListener('message', onMessage);\n });\n}\n\nfunction catchProcError(err: unknown) {\n const errorMsg = coerceErrorString(err);\n return Err({ code: UNCAUGHT_ERROR, message: errorMsg });\n}\n\nexport const testingSessionOptions: SessionOptions = defaultTransportOptions;\n\nexport function dummySession() {\n return new Session<Connection>(\n undefined,\n 'client',\n 'server',\n testingSessionOptions,\n );\n}\n\nfunction dummyCtx<State>(\n state: State,\n session: Session<Connection>,\n extendedContext?: Omit<ServiceContext, 'state'>,\n): ServiceContextWithTransportInfo<State> {\n return {\n ...extendedContext,\n state,\n to: session.to,\n from: session.from,\n streamId: nanoid(),\n session,\n metadata: {},\n };\n}\n\nexport function asClientRpc<\n State extends object,\n I extends PayloadType,\n O extends PayloadType,\n E extends RiverError,\n Init extends PayloadType | null = null,\n>(\n state: State,\n proc: Procedure<State, 'rpc', I, O, E, Init>,\n extendedContext?: Omit<ServiceContext, 'state'>,\n session: Session<Connection> = dummySession(),\n) {\n return async (\n msg: Static<I>,\n ): Promise<\n Result<Static<O>, Static<E> | Static<typeof RiverUncaughtSchema>>\n > => {\n return await proc\n .handler(dummyCtx(state, session, extendedContext), msg)\n .catch(catchProcError);\n };\n}\n\nexport function asClientStream<\n State extends object,\n I extends PayloadType,\n O extends PayloadType,\n E extends RiverError,\n Init extends PayloadType | null = null,\n>(\n state: State,\n proc: Procedure<State, 'stream', I, O, E, Init>,\n init?: Init extends PayloadType ? Static<Init> : null,\n extendedContext?: Omit<ServiceContext, 'state'>,\n session: Session<Connection> = dummySession(),\n) {\n const input = pushable<Static<I>>({ objectMode: true });\n const output = pushable<Result<Static<O>, Static<E>>>({\n objectMode: true,\n });\n\n void (async () => {\n if (init) {\n const _proc = proc as Procedure<State, 'stream', I, O, E, PayloadType>;\n await _proc\n .handler(dummyCtx(state, session, extendedContext), init, input, output)\n .catch((err: unknown) => output.push(catchProcError(err)));\n } else {\n const _proc = proc as Procedure<State, 'stream', I, O, E>;\n await _proc\n .handler(dummyCtx(state, session, extendedContext), input, output)\n .catch((err: unknown) => output.push(catchProcError(err)));\n }\n })();\n\n return [input, output] as const;\n}\n\nexport function asClientSubscription<\n State extends object,\n I extends PayloadType,\n O extends PayloadType,\n E extends RiverError,\n>(\n state: State,\n proc: Procedure<State, 'subscription', I, O, E>,\n extendedContext?: Omit<ServiceContext, 'state'>,\n session: Session<Connection> = dummySession(),\n) {\n const output = pushable<Result<Static<O>, Static<E>>>({\n objectMode: true,\n });\n\n return (msg: Static<I>) => {\n void (async () => {\n return await proc\n .handler(dummyCtx(state, session, extendedContext), msg, output)\n .catch((err: unknown) => output.push(catchProcError(err)));\n })();\n return output;\n };\n}\n\nexport function asClientUpload<\n State extends object,\n I extends PayloadType,\n O extends PayloadType,\n E extends RiverError,\n Init extends PayloadType | null = null,\n>(\n state: State,\n proc: Procedure<State, 'upload', I, O, E, Init>,\n init?: Init extends PayloadType ? Static<Init> : null,\n extendedContext?: Omit<ServiceContext, 'state'>,\n session: Session<Connection> = dummySession(),\n) {\n const input = pushable<Static<I>>({ objectMode: true });\n if (init) {\n const _proc = proc as Procedure<State, 'upload', I, O, E, PayloadType>;\n const result = _proc\n .handler(dummyCtx(state, session, extendedContext), init, input)\n .catch(catchProcError);\n return [input, result] as const;\n } else {\n const _proc = proc as Procedure<State, 'upload', I, O, E>;\n const result = _proc\n .handler(dummyCtx(state, session, extendedContext), input)\n .catch(catchProcError);\n return [input, result] as const;\n }\n}\n\nexport const getUnixSocketPath = () => {\n // https://nodejs.org/api/net.html#identifying-paths-for-ipc-connections\n return process.platform === 'win32'\n ? `\\\\\\\\?\\\\pipe\\\\${nanoid()}`\n : `/tmp/${nanoid()}.sock`;\n};\n","export default function pDefer() {\n\tconst deferred = {};\n\n\tdeferred.promise = new Promise((resolve, reject) => {\n\t\tdeferred.resolve = resolve;\n\t\tdeferred.reject = reject;\n\t});\n\n\treturn deferred;\n}\n","// ported from https://www.npmjs.com/package/fast-fifo\n\nexport interface Next<T> {\n done?: boolean\n error?: Error\n value?: T\n}\n\nclass FixedFIFO<T> {\n public buffer: Array<Next<T> | undefined>\n private readonly mask: number\n private top: number\n private btm: number\n public next: FixedFIFO<T> | null\n\n constructor (hwm: number) {\n if (!(hwm > 0) || ((hwm - 1) & hwm) !== 0) {\n throw new Error('Max size for a FixedFIFO should be a power of two')\n }\n\n this.buffer = new Array(hwm)\n this.mask = hwm - 1\n this.top = 0\n this.btm = 0\n this.next = null\n }\n\n push (data: Next<T>): boolean {\n if (this.buffer[this.top] !== undefined) {\n return false\n }\n\n this.buffer[this.top] = data\n this.top = (this.top + 1) & this.mask\n\n return true\n }\n\n shift (): Next<T> | undefined {\n const last = this.buffer[this.btm]\n\n if (last === undefined) {\n return undefined\n }\n\n this.buffer[this.btm] = undefined\n this.btm = (this.btm + 1) & this.mask\n return last\n }\n\n isEmpty (): boolean {\n return this.buffer[this.btm] === undefined\n }\n}\n\nexport interface FIFOOptions {\n /**\n * When the queue reaches this size, it will be split into head/tail parts\n */\n splitLimit?: number\n}\n\nexport class FIFO<T> {\n public size: number\n private readonly hwm: number\n private head: FixedFIFO<T>\n private tail: FixedFIFO<T>\n\n constructor (options: FIFOOptions = {}) {\n this.hwm = options.splitLimit ?? 16\n this.head = new FixedFIFO<T>(this.hwm)\n this.tail = this.head\n this.size = 0\n }\n\n calculateSize (obj: any): number {\n if (obj?.byteLength != null) {\n return obj.byteLength\n }\n\n return 1\n }\n\n push (val: Next<T>): void {\n if (val?.value != null) {\n this.size += this.calculateSize(val.value)\n }\n\n if (!this.head.push(val)) {\n const prev = this.head\n this.head = prev.next = new FixedFIFO<T>(2 * this.head.buffer.length)\n this.head.push(val)\n }\n }\n\n shift (): Next<T> | undefined {\n let val = this.tail.shift()\n\n if (val === undefined && (this.tail.next != null)) {\n const next = this.tail.next\n this.tail.next = null\n this.tail = next\n val = this.tail.shift()\n }\n\n if (val?.value != null) {\n this.size -= this.calculateSize(val.value)\n }\n\n return val\n }\n\n isEmpty (): boolean {\n return this.head.isEmpty()\n }\n}\n","/**\n * @packageDocumentation\n *\n * An iterable that you can push values into.\n *\n * @example\n *\n * ```js\n * import { pushable } from 'it-pushable'\n *\n * const source = pushable()\n *\n * setTimeout(() => source.push('hello'), 100)\n * setTimeout(() => source.push('world'), 200)\n * setTimeout(() => source.end(), 300)\n *\n * const start = Date.now()\n *\n * for await (const value of source) {\n * console.log(`got \"${value}\" after ${Date.now() - start}ms`)\n * }\n * console.log(`done after ${Date.now() - start}ms`)\n *\n * // Output:\n * // got \"hello\" after 105ms\n * // got \"world\" after 207ms\n * // done after 309ms\n * ```\n *\n * @example\n *\n * ```js\n * import { pushableV } from 'it-pushable'\n * import all from 'it-all'\n *\n * const source = pushableV()\n *\n * source.push(1)\n * source.push(2)\n * source.push(3)\n * source.end()\n *\n * console.info(await all(source))\n *\n * // Output:\n * // [ [1, 2, 3] ]\n * ```\n */\n\nimport deferred from 'p-defer'\nimport { FIFO, type Next } from './fifo.js'\n\nexport class AbortError extends Error {\n type: string\n code: string\n\n constructor (message?: string, code?: string) {\n super(message ?? 'The operation was aborted')\n this.type = 'aborted'\n this.code = code ?? 'ABORT_ERR'\n }\n}\n\nexport interface AbortOptions {\n signal?: AbortSignal\n}\n\ninterface BasePushable<T> {\n /**\n * End the iterable after all values in the buffer (if any) have been yielded. If an\n * error is passed the buffer is cleared immediately and the next iteration will\n * throw the passed error\n */\n end(err?: Error): this\n\n /**\n * Push a value into the iterable. Values are yielded from the iterable in the order\n * they are pushed. Values not yet consumed from the iterable are buffered.\n */\n push(value: T): this\n\n /**\n * Returns a promise that resolves when the underlying queue becomes empty (e.g.\n * this.readableLength === 0).\n *\n * If an AbortSignal is passed as an option and that signal aborts, it only\n * causes the returned promise to reject - it does not end the pushable.\n */\n onEmpty(options?: AbortOptions): Promise<void>\n\n /**\n * This property contains the number of bytes (or objects) in the queue ready to be read.\n *\n * If `objectMode` is true, this is the number of objects in the queue, if false it's the\n * total number of bytes in the queue.\n */\n readableLength: number\n}\n\n/**\n * An iterable that you can push values into.\n */\nexport interface Pushable<T, R = void, N = unknown> extends AsyncGenerator<T, R, N>, BasePushable<T> {}\n\n/**\n * Similar to `pushable`, except it yields multiple buffered chunks at a time. All values yielded from the iterable will be arrays.\n */\nexport interface PushableV<T, R = void, N = unknown> extends AsyncGenerator<T[], R, N>, BasePushable<T> {}\n\nexport interface Options {\n /**\n * A boolean value that means non-`Uint8Array`s will be passed to `.push`, default: `false`\n */\n objectMode?: boolean\n\n /**\n * A function called after *all* values have been yielded from the iterator (including\n * buffered values). In the case when the iterator is ended with an error it will be\n * passed the error as a parameter.\n */\n onEnd?(err?: Error): void\n}\n\nexport interface DoneResult { done: true }\nexport interface ValueResult<T> { done: false, value: T }\nexport type NextResult<T> = ValueResult<T> | DoneResult\n\ninterface getNext<T, V = T> { (buffer: FIFO<T>): NextResult<V> }\n\nexport interface ObjectPushableOptions extends Options {\n objectMode: true\n}\n\nexport interface BytePushableOptions extends Options {\n objectMode?: false\n}\n\n/**\n * Create a new async iterable. The values yielded from calls to `.next()`\n * or when used in a `for await of`loop are \"pushed\" into the iterable.\n * Returns an async iterable object with additional methods.\n */\nexport function pushable<T extends { byteLength: number } = Uint8Array> (options?: BytePushableOptions): Pushable<T>\nexport function pushable<T> (options: ObjectPushableOptions): Pushable<T>\nexport function pushable<T> (options: Options = {}): Pushable<T> {\n const getNext = (buffer: FIFO<T>): NextResult<T> => {\n const next: Next<T> | undefined = buffer.shift()\n\n if (next == null) {\n return { done: true }\n }\n\n if (next.error != null) {\n throw next.error\n }\n\n return {\n done: next.done === true,\n // @ts-expect-error if done is false, value will be present\n value: next.value\n }\n }\n\n return _pushable<T, T, Pushable<T>>(getNext, options)\n}\n\nexport function pushableV<T extends { byteLength: number } = Uint8Array> (options?: BytePushableOptions): PushableV<T>\nexport function pushableV<T> (options: ObjectPushableOptions): PushableV<T>\nexport function pushableV<T> (options: Options = {}): PushableV<T> {\n const getNext = (buffer: FIFO<T>): NextResult<T[]> => {\n let next: Next<T> | undefined\n const values: T[] = []\n\n while (!buffer.isEmpty()) {\n next = buffer.shift()\n\n if (next == null) {\n break\n }\n\n if (next.error != null) {\n throw next.error\n }\n\n if (next.done === false) {\n // @ts-expect-error if done is false value should be pushed\n values.push(next.value)\n }\n }\n\n if (next == null) {\n return { done: true }\n }\n\n return {\n done: next.done === true,\n value: values\n }\n }\n\n return _pushable<T, T[], PushableV<T>>(getNext, options)\n}\n\nfunction _pushable<PushType, ValueType, ReturnType> (getNext: getNext<PushType, ValueType>, options?: Options): ReturnType {\n options = options ?? {}\n let onEnd = options.onEnd\n let buffer = new FIFO<PushType>()\n let pushable: any\n let onNext: ((next: Next<PushType>) => ReturnType) | null\n let ended: boolean\n let drain = deferred()\n\n const waitNext = async (): Promise<NextResult<ValueType>> => {\n try {\n if (!buffer.isEmpty()) {\n return getNext(buffer)\n }\n\n if (ended) {\n return { done: true }\n }\n\n return await new Promise<NextResult<ValueType>>((resolve, reject) => {\n onNext = (next: Next<PushType>) => {\n onNext = null\n buffer.push(next)\n\n try {\n resolve(getNext(buffer))\n } catch (err) {\n reject(err)\n }\n\n return pushable\n }\n })\n } finally {\n if (buffer.isEmpty()) {\n // settle promise in the microtask queue to give consumers a chance to\n // await after calling .push\n queueMicrotask(() => {\n drain.resolve()\n drain = deferred()\n })\n }\n }\n }\n\n const bufferNext = (next: Next<PushType>): ReturnType => {\n if (onNext != null) {\n return onNext(next)\n }\n\n buffer.push(next)\n return pushable\n }\n\n const bufferError = (err: Error): ReturnType => {\n buffer = new FIFO()\n\n if (onNext != null) {\n return onNext({ error: err })\n }\n\n buffer.push({ error: err })\n return pushable\n }\n\n const push = (value: PushType): ReturnType => {\n if (ended) {\n return pushable\n }\n\n // @ts-expect-error `byteLength` is not declared on PushType\n if (options?.objectMode !== true && value?.byteLength == null) {\n throw new Error('objectMode was not true but tried to push non-Uint8Array value')\n }\n\n return bufferNext({ done: false, value })\n }\n const end = (err?: Error): ReturnType => {\n if (ended) return pushable\n ended = true\n\n return (err != null) ? bufferError(err) : bufferNext({ done: true })\n }\n const _return = (): DoneResult => {\n buffer = new FIFO()\n end()\n\n return { done: true }\n }\n const _throw = (err: Error): DoneResult => {\n end(err)\n\n return { done: true }\n }\n\n pushable = {\n [Symbol.asyncIterator] () { return this },\n next: waitNext,\n return: _return,\n throw: _throw,\n push,\n end,\n get readableLength (): number {\n return buffer.size\n },\n onEmpty: async (options?: AbortOptions) => {\n const signal = options?.signal\n signal?.throwIfAborted()\n\n if (buffer.isEmpty()) {\n return\n }\n\n let cancel: Promise<void> | undefined\n let listener: (() => void) | undefined\n\n if (signal != null) {\n cancel = new Promise((resolve, reject) => {\n listener = () => {\n reject(new AbortError())\n }\n\n signal.addEventListener('abort', listener)\n })\n }\n\n try {\n await Promise.race([\n drain.promise,\n cancel\n ])\n } finally {\n if (listener != null && signal != null) {\n signal?.removeEventListener('abort', listener)\n }\n }\n }\n }\n\n if (onEnd == null) {\n return pushable\n }\n\n const _pushable = pushable\n\n pushable = {\n [Symbol.asyncIterator] () { return this },\n next () {\n return _pushable.next()\n },\n throw (err: Error) {\n _pushable.throw(err)\n\n if (onEnd != null) {\n onEnd(err)\n onEnd = undefined\n }\n\n return { done: true }\n },\n return () {\n _pushable.return()\n\n if (onEnd != null) {\n onEnd()\n onEnd = undefined\n }\n\n return { done: true }\n },\n push,\n end (err: Error) {\n _pushable.end(err)\n\n if (onEnd != null) {\n onEnd(err)\n onEnd = undefined\n }\n\n return pushable\n },\n get readableLength () {\n return _pushable.readableLength\n },\n onEmpty: (opts?: AbortOptions) => {\n return _pushable.onEmpty(opts)\n }\n }\n\n return pushable\n}\n","import {\n TLiteral,\n TNever,\n TObject,\n TSchema,\n TString,\n TUnion,\n Type,\n} from '@sinclair/typebox';\nimport { Client } from './client';\n\ntype TLiteralString = TLiteral<string>;\n\nexport type RiverErrorSchema =\n | TObject<{\n code: TLiteralString | TUnion<Array<TLiteralString>>;\n message: TLiteralString | TString;\n }>\n | TObject<{\n code: TLiteralString | TUnion<Array<TLiteralString>>;\n message: TLiteralString | TString;\n extras: TSchema;\n }>;\n\nexport type RiverError =\n | TUnion<Array<RiverErrorSchema>>\n | RiverErrorSchema\n | TNever;\n\nexport const UNCAUGHT_ERROR = 'UNCAUGHT_ERROR';\nexport const UNEXPECTED_DISCONNECT = 'UNEXPECTED_DISCONNECT';\nexport const RiverUncaughtSchema = Type.Object({\n code: Type.Union([\n Type.Literal(UNCAUGHT_ERROR),\n Type.Literal(UNEXPECTED_DISCONNECT),\n ]),\n message: Type.String(),\n});\n\nexport type Result<T, E> =\n | {\n ok: true;\n payload: T;\n }\n | {\n ok: false;\n payload: E;\n };\n\nexport function Ok<const T extends Array<unknown>, const E>(p: T): Result<T, E>;\nexport function Ok<const T extends ReadonlyArray<unknown>, const E>(\n p: T,\n): Result<T, E>;\nexport function Ok<const T, const E>(payload: T): Result<T, E>;\nexport function Ok<const T, const E>(payload: T): Result<T, E> {\n return {\n ok: true,\n payload,\n };\n}\n\nexport function Err<const T, const E>(error: E): Result<T, E> {\n return {\n ok: false,\n payload: error,\n };\n}\n\n/**\n * Refine a {@link Result} type to its returned payload.\n */\nexport type ResultUnwrapOk<R> = R extends Result<infer T, infer __E>\n ? T\n : never;\n\n/**\n * Refine a {@link Result} type to its error payload.\n */\nexport type ResultUnwrapErr<R> = R extends Result<infer __T, infer E>\n ? E\n : never;\n\n/**\n * Retrieve the output type for a procedure, represented as a {@link Result}\n * type.\n * Example:\n * ```\n * type Message = Output<typeof client, 'serviceName', 'procedureName'>\n * ```\n */\nexport type Output<\n RiverClient,\n ServiceName extends keyof RiverClient,\n ProcedureName extends keyof RiverClient[ServiceName],\n Procedure = RiverClient[ServiceName][ProcedureName],\n Fn extends (...args: never) => unknown = (...args: never) => unknown,\n> = RiverClient extends Client<infer __ServiceSchemaMap>\n ? Procedure extends object\n ? Procedure extends object & { rpc: infer RpcHandler extends Fn }\n ? Awaited<ReturnType<RpcHandler>>\n : Procedure extends object & { upload: infer UploadHandler extends Fn }\n ? Awaited<ReturnType<UploadHandler>> extends [\n infer __UploadInputMessage,\n infer UploadOutputMessage,\n ]\n ? Awaited<UploadOutputMessage>\n : never\n : Procedure extends object & { stream: infer StreamHandler extends Fn }\n ? Awaited<ReturnType<StreamHandler>> extends [\n infer __StreamInputMessage,\n AsyncGenerator<infer StreamOutputMessage>,\n infer __StreamCloseHandle,\n ]\n ? StreamOutputMessage\n : never\n : Procedure extends object & {\n subscribe: infer SubscriptionHandler extends Fn;\n }\n ? Awaited<ReturnType<SubscriptionHandler>> extends [\n AsyncGenerator<infer SubscriptionOutputMessage>,\n infer __SubscriptionCloseHandle,\n ]\n ? SubscriptionOutputMessage\n : never\n : never\n : never\n : never;\n","import {\n Context,\n Span,\n SpanKind,\n context,\n propagation,\n trace,\n} from '@opentelemetry/api';\nimport { version as RIVER_VERSION } from '../package.json';\nimport { ValidProcType } from '../router';\nimport {\n ClientTransport,\n Connection,\n OpaqueTransportMessage,\n Session,\n} from '../transport';\n\nexport interface PropagationContext {\n traceparent: string;\n tracestate: string;\n}\n\nexport interface TelemetryInfo {\n span: Span;\n ctx: Context;\n}\n\nexport function getPropagationContext(\n ctx: Context,\n): PropagationContext | undefined {\n const tracing = {\n traceparent: '',\n tracestate: '',\n };\n propagation.inject(ctx, tracing);\n return tracing;\n}\n\nexport function createSessionTelemetryInfo(\n session: Session<Connection>,\n propagationCtx?: PropagationContext,\n): TelemetryInfo {\n const parentCtx = propagationCtx\n ? propagation.extract(context.active(), propagationCtx)\n : context.active();\n\n const span = tracer.startSpan(\n `session ${session.id}`,\n {\n attributes: {\n component: 'river',\n 'river.session.id': session.id,\n 'river.session.to': session.to,\n 'river.session.from': session.from,\n },\n },\n parentCtx,\n );\n\n const ctx = trace.setSpan(parentCtx, span);\n\n return { span, ctx };\n}\n\nexport function createConnectionTelemetryInfo(\n connection: Connection,\n info: TelemetryInfo,\n): TelemetryInfo {\n const span = tracer.startSpan(\n `connection ${connection.id}`,\n {\n attributes: {\n component: 'river',\n 'river.connection.id': connection.id,\n },\n links: [{ context: info.span.spanContext() }],\n },\n info.ctx,\n );\n\n const ctx = trace.setSpan(info.ctx, span);\n\n return { span, ctx };\n}\n\nexport function createProcTelemetryInfo(\n transport: ClientTransport<Connection>,\n kind: ValidProcType,\n serviceName: string,\n procedureName: string,\n streamId: string,\n): TelemetryInfo {\n const baseCtx = context.active();\n const span = tracer.startSpan(\n `procedure call ${serviceName}.${procedureName}`,\n {\n attributes: {\n component: 'river',\n 'river.method.kind': kind,\n 'river.method.service': serviceName,\n 'river.method.name': procedureName,\n 'river.streamId': streamId,\n 'span.kind': 'client',\n },\n kind: SpanKind.CLIENT,\n },\n baseCtx,\n );\n\n const ctx = trace.setSpan(baseCtx, span);\n\n transport.log?.info(`invoked ${serviceName}.${procedureName}`, {\n clientId: transport.clientId,\n transportMessage: {\n procedureName,\n serviceName,\n },\n telemetry: {\n traceId: span.spanContext().traceId,\n spanId: span.spanContext().spanId,\n },\n });\n return { span, ctx };\n}\n\nexport function createHandlerSpan(\n kind: ValidProcType,\n message: OpaqueTransportMessage,\n fn: (span: Span) => Promise<unknown>,\n) {\n const ctx = message.tracing\n ? propagation.extract(context.active(), message.tracing)\n : context.active();\n\n return tracer.startActiveSpan(\n `procedure handler ${message.serviceName}.${message.procedureName}`,\n {\n attributes: {\n component: 'river',\n 'river.method.kind': kind,\n 'river.method.service': message.serviceName,\n 'river.method.name': message.procedureName,\n 'river.streamId': message.streamId,\n 'span.kind': 'server',\n },\n kind: SpanKind.SERVER,\n },\n ctx,\n fn,\n );\n}\n\nconst tracer = trace.getTracer('river', RIVER_VERSION);\nexport default tracer;\n","{\n \"name\": \"@replit/river\",\n \"description\": \"It's like tRPC but... with JSON Schema Support, duplex streaming and support for service multiplexing. Transport agnostic!\",\n \"version\": \"0.23.9\",\n \"type\": \"module\",\n \"exports\": {\n \".\": {\n \"import\": \"./dist/router/index.js\",\n \"require\": \"./dist/router/index.cjs\"\n },\n \"./logging\": {\n \"import\": \"./dist/logging/index.js\",\n \"require\": \"./dist/logging/index.cjs\"\n },\n \"./codec\": {\n \"import\": \"./dist/codec/index.js\",\n \"require\": \"./dist/codec/index.cjs\"\n },\n \"./transport\": {\n \"import\": \"./dist/transport/index.js\",\n \"require\": \"./dist/transport/index.cjs\"\n },\n \"./transport/ws/client\": {\n \"import\": \"./dist/transport/impls/ws/client.js\",\n \"require\": \"./dist/transport/impls/ws/client.cjs\"\n },\n \"./transport/ws/server\": {\n \"import\": \"./dist/transport/impls/ws/server.js\",\n \"require\": \"./dist/transport/impls/ws/server.cjs\"\n },\n \"./transport/uds/client\": {\n \"import\": \"./dist/transport/impls/uds/client.js\",\n \"require\": \"./dist/transport/impls/uds/client.cjs\"\n },\n \"./transport/uds/server\": {\n \"import\": \"./dist/transport/impls/uds/server.js\",\n \"require\": \"./dist/transport/impls/uds/server.cjs\"\n },\n \"./test-util\": {\n \"import\": \"./dist/util/testHelpers.js\",\n \"require\": \"./dist/util/testHelpers.cjs\"\n }\n },\n \"sideEffects\": [\n \"./dist/logging/index.js\"\n ],\n \"files\": [\n \"dist\"\n ],\n \"dependencies\": {\n \"@msgpack/msgpack\": \"^3.0.0-beta2\",\n \"it-pushable\": \"^3.2.3\",\n \"nanoid\": \"^4.0.2\",\n \"ws\": \"^8.17.0\"\n },\n \"peerDependencies\": {\n \"@opentelemetry/api\": \"^1.7.0\",\n \"@sinclair/typebox\": \"~0.32.8\"\n },\n \"devDependencies\": {\n \"@opentelemetry/sdk-trace-base\": \"^1.24.1\",\n \"@opentelemetry/sdk-trace-web\": \"^1.24.1\",\n \"@opentelemetry/core\": \"^1.7.0\",\n \"@types/ws\": \"^8.5.5\",\n \"@typescript-eslint/eslint-plugin\": \"^7.8.0\",\n \"@typescript-eslint/parser\": \"^7.8.0\",\n \"@vitest/ui\": \"^1.3.1\",\n \"eslint\": \"^8.57.0\",\n \"eslint-config-prettier\": \"^9.1.0\",\n \"eslint-plugin-prettier\": \"^5.1.3\",\n \"prettier\": \"^3.0.0\",\n \"tsup\": \"^7.2.0\",\n \"typescript\": \"^5.4.5\",\n \"vitest\": \"^1.3.1\"\n },\n \"scripts\": {\n \"check\": \"tsc --noEmit && npm run format && npm run lint\",\n \"format\": \"npx prettier . --check\",\n \"format:fix\": \"npx prettier . --write\",\n \"lint\": \"eslint .\",\n \"lint:fix\": \"eslint . --fix\",\n \"fix\": \"npm run format:fix && npm run lint:fix\",\n \"build\": \"rm -rf dist && tsup && du -sh dist\",\n \"prepack\": \"npm run build\",\n \"release\": \"npm publish --access public\",\n \"test:ui\": \"echo \\\"remember to go to /__vitest__ in the webview\\\" && vitest --ui --api.host 0.0.0.0 --api.port 3000\",\n \"test\": \"vitest --test-timeout=500\",\n \"test:single\": \"vitest run --test-timeout=500 --reporter=dot\",\n \"test:flake\": \"./flake.sh\",\n \"bench\": \"vitest bench\"\n },\n \"engines\": {\n \"node\": \">=16\"\n },\n \"keywords\": [\n \"rpc\",\n \"websockets\",\n \"jsonschema\"\n ],\n \"author\": \"Jacky Zhao\",\n \"license\": \"MIT\"\n}\n","export function coerceErrorString(err: unknown): string {\n if (err instanceof Error) {\n return err.message || 'unknown reason';\n }\n\n return `[coerced to error] ${String(err)}`;\n}\n","import { customAlphabet } from 'nanoid';\nimport {\n ControlFlags,\n ControlMessageAckSchema,\n OpaqueTransportMessage,\n PartialTransportMessage,\n TransportClientId,\n TransportMessage,\n} from './message';\nimport { Codec } from '../codec';\nimport { Logger, MessageMetadata } from '../logging/log';\nimport { Static } from '@sinclair/typebox';\nimport {\n PropagationContext,\n TelemetryInfo,\n createSessionTelemetryInfo,\n} from '../tracing';\nimport { SpanStatusCode } from '@opentelemetry/api';\n\nconst nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvxyz', 6);\nexport const unsafeId = () => nanoid();\n\ntype SequenceNumber = number;\n\n/**\n * A connection is the actual raw underlying transport connection.\n * It’s responsible for dispatching to/from the actual connection itself\n * This should be instantiated as soon as the client/server has a connection\n * It’s tied to the lifecycle of the underlying transport connection (i.e. if the WS drops, this connection should be deleted)\n */\nexport abstract class Connection {\n id: string;\n telemetry?: TelemetryInfo;\n constructor() {\n this.id = `conn-${nanoid(12)}`; // for debugging, no collision safety needed\n }\n\n get loggingMetadata(): MessageMetadata {\n const metadata: MessageMetadata = { connId: this.id };\n const spanContext = this.telemetry?.span.spanContext();\n if (spanContext) {\n metadata.telemetry = {\n traceId: spanContext.traceId,\n spanId: spanContext.spanId,\n };\n }\n\n return metadata;\n }\n\n /**\n * Handle adding a callback for when a message is received.\n * @param msg The message that was received.\n */\n abstract addDataListener(cb: (msg: Uint8Array) => void): void;\n abstract removeDataListener(cb: (msg: Uint8Array) => void): void;\n\n /**\n * Handle adding a callback for when the connection is closed.\n * This should also be called if an error happens.\n * @param cb The callback to call when the connection is closed.\n */\n abstract addCloseListener(cb: () => void): void;\n\n /**\n * Handle adding a callback for when an error is received.\n * This should only be used for this.logging errors, all cleanup\n * should be delegated to addCloseListener.\n *\n * The implementer should take care such that the implemented\n * connection will call both the close and error callbacks\n * on an error.\n *\n * @param cb The callback to call when an error is received.\n */\n abstract addErrorListener(cb: (err: Error) => void): void;\n\n /**\n * Sends a message over the connection.\n * @param msg The message to send.\n * @returns true if the message was sent, false otherwise.\n */\n abstract send(msg: Uint8Array): boolean;\n\n /**\n * Closes the connection.\n */\n abstract close(): void;\n}\n\nexport interface SessionOptions {\n /**\n * Frequency at which to send heartbeat acknowledgements\n */\n heartbeatIntervalMs: number;\n /**\n * Number of elapsed heartbeats without a response message before we consider\n * the connection dead.\n */\n heartbeatsUntilDead: number;\n /**\n * Duration to wait between connection disconnect and actual session disconnect\n */\n sessionDisconnectGraceMs: number;\n /**\n * The codec to use for encoding/decoding messages over the wire\n */\n codec: Codec;\n}\n\n/**\n * A session is a higher-level abstraction that operates over the span of potentially multiple transport-level connections\n * - It’s responsible for tracking any metadata for a particular client that might need to be persisted across connections (i.e. the sendBuffer, ack, seq)\n * - This will only be considered disconnected if\n * - the server tells the client that we’ve reconnected but it doesn’t recognize us anymore (server definitely died) or\n * - we hit a grace period after a connection disconnect\n */\nexport class Session<ConnType extends Connection> {\n private codec: Codec;\n private options: SessionOptions;\n readonly telemetry: TelemetryInfo;\n\n /**\n * The buffer of messages that have been sent but not yet acknowledged.\n */\n private sendBuffer: Array<OpaqueTransportMessage> = [];\n\n /**\n * The active connection associated with this session\n */\n connection?: ConnType;\n /**\n * A connection that is currently undergoing handshaking. Used to distinguish between the active\n * connection, but still be able to close it if needed.\n */\n private handshakingConnection?: ConnType;\n readonly from: TransportClientId;\n readonly to: TransportClientId;\n\n /**\n * The unique ID of this session.\n */\n readonly id: string;\n\n /**\n * What the other side advertised as their session ID\n * for this session.\n */\n advertisedSessionId?: string;\n\n /**\n * Number of messages we've sent along this session (excluding handshake and acks)\n */\n private seq: SequenceNumber = 0;\n\n /**\n * Number of unique messages we've received this session (excluding handshake and acks)\n */\n private ack: SequenceNumber = 0;\n\n /**\n * The grace period between when the inner connection is disconnected\n * and when we should consider the entire session disconnected.\n */\n private disconnectionGrace?: ReturnType<typeof setTimeout>;\n\n /**\n * Number of heartbeats we've sent without a response.\n */\n private heartbeatMisses: number;\n\n /**\n * The interval for sending heartbeats.\n */\n private heartbeat: ReturnType<typeof setInterval>;\n private log?: Logger;\n\n constructor(\n conn: ConnType | undefined,\n from: TransportClientId,\n to: TransportClientId,\n options: SessionOptions,\n propagationCtx?: PropagationContext,\n ) {\n this.id = `session-${nanoid(12)}`;\n this.options = options;\n this.from = from;\n this.to = to;\n this.connection = conn;\n this.codec = options.codec;\n\n // setup heartbeat\n this.heartbeatMisses = 0;\n this.heartbeat = setInterval(\n () => this.sendHeartbeat(),\n options.heartbeatIntervalMs,\n );\n this.telemetry = createSessionTelemetryInfo(this, propagationCtx);\n }\n\n bindLogger(log: Logger) {\n this.log = log;\n }\n\n get loggingMetadata(): MessageMetadata {\n const spanContext = this.telemetry.span.spanContext();\n\n return {\n clientId: this.from,\n connectedTo: this.to,\n sessionId: this.id,\n connId: this.connection?.id,\n telemetry: {\n traceId: spanContext.traceId,\n spanId: spanContext.spanId,\n },\n };\n }\n\n /**\n * Sends a message over the session's connection.\n * If the connection is not ready or the message fails to send, the message can be buffered for retry unless skipped.\n *\n * @param msg The partial message to be sent, which will be constructed into a full message.\n * @param addToSendBuff Whether to add the message to the send buffer for retry.\n * @returns The full transport ID of the message that was attempted to be sent.\n */\n send(msg: PartialTransportMessage): string {\n const fullMsg: TransportMessage = this.constructMsg(msg);\n this.log?.debug(`sending msg`, {\n ...this.loggingMetadata,\n transportMessage: fullMsg,\n });\n\n if (this.connection) {\n const ok = this.connection.send(this.codec.toBuffer(fullMsg));\n if (ok) return fullMsg.id;\n this.log?.info(\n `failed to send msg to ${fullMsg.to}, connection is probably dead`,\n {\n ...this.loggingMetadata,\n transportMessage: fullMsg,\n },\n );\n } else {\n this.log?.debug(\n `buffering msg to ${fullMsg.to}, connection not ready yet`,\n { ...this.loggingMetadata, transportMessage: fullMsg },\n );\n }\n\n return fullMsg.id;\n }\n\n sendHeartbeat() {\n const misses = this.heartbeatMisses;\n const missDuration = misses * this.options.heartbeatIntervalMs;\n if (misses > this.options.heartbeatsUntilDead) {\n if (this.connection) {\n this.log?.info(\n `closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,\n this.loggingMetadata,\n );\n this.telemetry.span.addEvent('closing connection due to inactivity');\n this.closeStaleConnection();\n }\n return;\n }\n\n this.send({\n streamId: 'heartbeat',\n controlFlags: ControlFlags.AckBit,\n payload: {\n type: 'ACK',\n } satisfies Static<typeof ControlMessageAckSchema>,\n });\n this.heartbeatMisses++;\n }\n\n resetBufferedMessages() {\n this.sendBuffer = [];\n this.seq = 0;\n this.ack = 0;\n }\n\n sendBufferedMessages(conn: ConnType) {\n this.log?.info(`resending ${this.sendBuffer.length} buffered messages`, {\n ...this.loggingMetadata,\n connId: conn.id,\n });\n for (const msg of this.sendBuffer) {\n this.log?.debug(`resending msg`, {\n ...this.loggingMetadata,\n transportMessage: msg,\n connId: conn.id,\n });\n const ok = conn.send(this.codec.toBuffer(msg));\n if (!ok) {\n // this should never happen unless the transport has an\n // incorrect implementation of `createNewOutgoingConnection`\n const errMsg = `failed to send buffered message to ${this.to} (sus, this is a fresh connection)`;\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: errMsg,\n });\n\n this.log?.error(errMsg, {\n ...this.loggingMetadata,\n transportMessage: msg,\n connId: conn.id,\n tags: ['invariant-violation'],\n });\n conn.close();\n return;\n }\n }\n }\n\n updateBookkeeping(ack: number, seq: number) {\n if (seq + 1 < this.ack) {\n this.log?.error(`received stale seq ${seq} + 1 < ${this.ack}`, {\n ...this.loggingMetadata,\n tags: ['invariant-violation'],\n });\n return;\n }\n\n this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);\n this.ack = seq + 1;\n }\n\n private closeStaleConnection(conn?: ConnType) {\n if (this.connection === undefined || this.connection === conn) return;\n this.log?.info(\n `closing old inner connection from session to ${this.to}`,\n this.loggingMetadata,\n );\n this.connection.close();\n this.connection = undefined;\n }\n\n replaceWithNewConnection(newConn: ConnType) {\n this.closeStaleConnection(newConn);\n this.cancelGrace();\n this.sendBufferedMessages(newConn);\n this.connection = newConn;\n // we only call replaceWithNewConnection after\n // having successfully completed a handshake so we clear\n // it here\n this.handshakingConnection = undefined;\n }\n\n replaceWithNewHandshakingConnection(newConn: ConnType) {\n this.handshakingConnection = newConn;\n }\n\n beginGrace(cb: () => void) {\n this.log?.info(\n `starting ${this.options.sessionDisconnectGraceMs}ms grace period until session to ${this.to} is closed`,\n this.loggingMetadata,\n );\n // Replace any old timeouts to prevent this from firing twice.\n this.cancelGrace({ keepHeartbeatMisses: true });\n this.disconnectionGrace = setTimeout(() => {\n if (this.connection !== undefined) {\n this.log?.warn(\n `grace period for ${this.to} elapsed while connected. not calling callback`,\n {\n ...this.loggingMetadata,\n connId: this.connection.id,\n tags: ['invariant-violation'],\n },\n );\n return;\n }\n this.log?.info(\n `grace period for ${this.to} elapsed`,\n this.loggingMetadata,\n );\n cb();\n }, this.options.sessionDisconnectGraceMs);\n }\n\n // called on reconnect of the underlying session\n cancelGrace(\n { keepHeartbeatMisses }: { keepHeartbeatMisses: boolean } = {\n keepHeartbeatMisses: false,\n },\n ) {\n if (!keepHeartbeatMisses) {\n this.heartbeatMisses = 0;\n }\n if (this.disconnectionGrace === undefined) return;\n clearTimeout(this.disconnectionGrace);\n this.disconnectionGrace = undefined;\n }\n\n /**\n * Used to close the handshaking connection, if set.\n */\n closeHandshakingConnection(expectedHandshakingConn?: ConnType) {\n if (this.handshakingConnection === undefined) return;\n if (\n expectedHandshakingConn !== undefined &&\n this.handshakingConnection === expectedHandshakingConn\n ) {\n // If the handshaking connection is the expected one, don't close it.\n return;\n }\n this.handshakingConnection.close();\n this.handshakingConnection = undefined;\n }\n\n // closed when we want to discard the whole session\n // (i.e. shutdown or session disconnect)\n close() {\n this.closeStaleConnection();\n this.cancelGrace();\n this.resetBufferedMessages();\n clearInterval(this.heartbeat);\n }\n\n get connected() {\n return this.connection !== undefined;\n }\n\n get nextExpectedSeq() {\n return this.ack;\n }\n\n constructMsg<Payload>(\n partialMsg: PartialTransportMessage<Payload>,\n ): TransportMessage<Payload> {\n const msg = {\n ...partialMsg,\n id: unsafeId(),\n to: this.to,\n from: this.from,\n seq: this.seq,\n ack: this.ack,\n };\n\n this.seq++;\n this.sendBuffer.push(msg);\n return msg;\n }\n\n inspectSendBuffer(): ReadonlyArray<OpaqueTransportMessage> {\n return this.sendBuffer;\n }\n}\n","import { Codec } from '../codec/types';\nimport { Value } from '@sinclair/typebox/value';\nimport {\n OpaqueTransportMessage,\n OpaqueTransportMessageSchema,\n TransportClientId,\n ControlMessageHandshakeRequestSchema,\n ControlMessageHandshakeResponseSchema,\n handshakeRequestMessage,\n handshakeResponseMessage,\n PartialTransportMessage,\n ControlFlags,\n ControlMessagePayloadSchema,\n isAck,\n PROTOCOL_VERSION,\n} from './message';\nimport {\n BaseLogger,\n LogFn,\n Logger,\n LoggingLevel,\n createLogProxy,\n} from '../logging/log';\nimport {\n EventDispatcher,\n EventHandler,\n EventTypes,\n ProtocolError,\n ProtocolErrorType,\n} from './events';\nimport { Connection, Session, SessionOptions } from './session';\nimport { Static } from '@sinclair/typebox';\nimport { coerceErrorString } from '../util/stringify';\nimport { ConnectionRetryOptions, LeakyBucketRateLimit } from './rateLimit';\nimport { NaiveJsonCodec } from '../codec';\nimport tracer, {\n PropagationContext,\n createConnectionTelemetryInfo,\n getPropagationContext,\n} from '../tracing';\nimport { SpanStatusCode } from '@opentelemetry/api';\nimport { ParsedMetadata } from '../router/context';\nimport {\n ClientHandshakeOptions,\n ServerHandshakeOptions,\n} from '../router/handshake';\n\n/**\n * Represents the possible states of a transport.\n * @property {'open'} open - The transport is open and operational (note that this doesn't mean it is actively connected)\n * @property {'closed'} closed - The transport is closed and not operational, but can be reopened.\n * @property {'destroyed'} destroyed - The transport is permanently destroyed and cannot be reopened.\n */\nexport type TransportStatus = 'open' | 'closed' | 'destroyed';\n\ntype TransportOptions = SessionOptions;\n\nexport type ProvidedTransportOptions = Partial<TransportOptions>;\n\nexport const defaultTransportOptions: TransportOptions = {\n heartbeatIntervalMs: 1_000,\n heartbeatsUntilDead: 2,\n sessionDisconnectGraceMs: 5_000,\n codec: NaiveJsonCodec,\n};\n\ntype ClientTransportOptions = TransportOptions & ConnectionRetryOptions;\n\nexport type ProvidedClientTransportOptions = Partial<ClientTransportOptions>;\n\nconst defaultConnectionRetryOptions: ConnectionRetryOptions = {\n baseIntervalMs: 250,\n maxJitterMs: 200,\n maxBackoffMs: 32_000,\n attemptBudgetCapacity: 5,\n budgetRestoreIntervalMs: 200,\n};\n\nconst defaultClientTransportOptions: ClientTransportOptions = {\n ...defaultTransportOptions,\n ...defaultConnectionRetryOptions,\n};\n\ntype ServerTransportOptions = TransportOptions;\n\nexport type ProvidedServerTransportOptions = Partial<ServerTransportOptions>;\n\nconst defaultServerTransportOptions: ServerTransportOptions = {\n ...defaultTransportOptions,\n};\n\n/**\n * Transports manage the lifecycle (creation/deletion) of sessions and connections. Its responsibilities include:\n *\n * 1) Constructing a new {@link Session} and {@link Connection} on {@link TransportMessage}s from new clients.\n * After constructing the {@link Connection}, {@link onConnect} is called which adds it to the connection map.\n * 2) Delegating message listening of the connection to the newly created {@link Connection}.\n * From this point on, the {@link Connection} is responsible for *reading* and *writing*\n * messages from the connection.\n * 3) When a connection is closed, the {@link Transport} calls {@link onDisconnect} which closes the\n * connection via {@link Connection.close} and removes it from the {@link connections} map.\n\n *\n * ```plaintext\n * ▲\n * incoming │\n * messages │\n * ▼\n * ┌─────────────┐ 1:N ┌───────────┐ 1:1* ┌────────────┐\n * │ Transport │ ◄─────► │ Session │ ◄─────► │ Connection │\n * └─────────────┘ └───────────┘ └────────────┘\n * ▲ * (may or may not be initialized yet)\n * │\n * ▼\n * ┌───────────┐\n * │ Message │\n * │ Listeners │\n * └───────────┘\n * ```\n * @abstract\n */\nexport abstract class Transport<ConnType extends Connection> {\n /**\n * A flag indicating whether the transport has been destroyed.\n * A destroyed transport will not attempt to reconnect and cannot be used again.\n */\n state: TransportStatus;\n\n /**\n * The {@link Codec} used to encode and decode messages.\n */\n codec: Codec;\n\n /**\n * The client ID of this transport.\n */\n clientId: TransportClientId;\n\n /**\n * The map of {@link Session}s managed by this transport.\n */\n sessions: Map<TransportClientId, Session<ConnType>>;\n\n /**\n * The map of {@link Connection}s managed by this transport.\n */\n get connections() {\n return new Map(\n [...this.sessions]\n .map(([client, session]) => [client, session.connection])\n .filter((entry): entry is [string, ConnType] => entry[1] !== undefined),\n );\n }\n\n /**\n * The event dispatcher for handling events of type EventTypes.\n */\n eventDispatcher: EventDispatcher<EventTypes>;\n\n /**\n * The options for this transport.\n */\n protected options: TransportOptions;\n log?: Logger;\n\n /**\n * Creates a new Transport instance.\n * This should also set up {@link onConnect}, and {@link onDisconnect} listeners.\n * @param codec The codec used to encode and decode messages.\n * @param clientId The client ID of this transport.\n */\n constructor(\n clientId: TransportClientId,\n providedOptions?: ProvidedTransportOptions,\n ) {\n this.options = { ...defaultTransportOptions, ...providedOptions };\n this.eventDispatcher = new EventDispatcher();\n this.sessions = new Map();\n this.codec = this.options.codec;\n this.clientId = clientId;\n this.state = 'open';\n }\n\n bindLogger(fn: LogFn | Logger, level?: LoggingLevel) {\n // construct logger from fn\n if (typeof fn === 'function') {\n this.log = createLogProxy(new BaseLogger(fn, level));\n return;\n }\n\n // object case, just assign\n this.log = createLogProxy(fn);\n }\n\n /**\n * This is called immediately after a new connection is established and we\n * may or may not know the identity of the connected client.\n * It should attach all the necessary listeners to the connection for lifecycle\n * events (i.e. data, close, error)\n *\n * This method is implemented by {@link ClientTransport} and {@link ServerTransport}.\n */\n protected abstract handleConnection(\n conn: ConnType,\n to: TransportClientId,\n ): void;\n\n /**\n * Called when a new connection is established\n * and we know the identity of the connected client.\n * @param conn The connection object.\n */\n protected onConnect(\n conn: ConnType,\n connectedTo: TransportClientId,\n session: Session<ConnType>,\n isReconnect: boolean,\n ) {\n this.eventDispatcher.dispatchEvent('connectionStatus', {\n status: 'connect',\n conn,\n });\n\n conn.telemetry = createConnectionTelemetryInfo(conn, session.telemetry);\n\n if (isReconnect) {\n session.replaceWithNewConnection(conn);\n this.log?.info(`reconnected to ${connectedTo}`, {\n ...conn.loggingMetadata,\n ...session.loggingMetadata,\n clientId: this.clientId,\n connectedTo,\n });\n }\n }\n\n protected createSession(\n to: TransportClientId,\n conn?: ConnType,\n propagationCtx?: PropagationContext,\n ) {\n const session = new Session<ConnType>(\n conn,\n this.clientId,\n to,\n this.options,\n propagationCtx,\n );\n\n if (this.log) {\n session.bindLogger(this.log);\n }\n\n this.sessions.set(session.to, session);\n this.eventDispatcher.dispatchEvent('sessionStatus', {\n status: 'connect',\n session,\n });\n return session;\n }\n\n protected getOrCreateSession({\n to,\n conn,\n handshakingConn,\n sessionId,\n propagationCtx,\n }: {\n to: TransportClientId;\n conn?: ConnType;\n handshakingConn?: ConnType;\n sessionId?: string;\n propagationCtx?: PropagationContext;\n }) {\n let session = this.sessions.get(to);\n let isReconnect = session !== undefined;\n\n if (\n session?.advertisedSessionId !== undefined &&\n sessionId !== undefined &&\n session.advertisedSessionId !== sessionId\n ) {\n this.log?.info(\n `session for ${to} already exists but has a different session id (expected: ${session.advertisedSessionId}, got: ${sessionId}), creating a new one`,\n session.loggingMetadata,\n );\n // note that here we are only interested in closing the handshaking connection if it _does\n // not_ match the current handshaking connection. otherwise we can be in a situation where we\n // can accidentally close the current connection and are never able to establish a full\n // handshake.\n this.deleteSession({\n session,\n closeHandshakingConnection: handshakingConn !== undefined,\n handshakingConn,\n });\n isReconnect = false;\n session = undefined;\n }\n\n if (!session) {\n session = this.createSession(to, conn, propagationCtx);\n this.log?.info(\n `no session for ${to}, created a new one`,\n session.loggingMetadata,\n );\n }\n\n if (sessionId !== undefined) {\n session.advertisedSessionId = sessionId;\n }\n\n if (handshakingConn !== undefined) {\n session.replaceWithNewHandshakingConnection(handshakingConn);\n }\n return { session, isReconnect };\n }\n\n protected deleteSession({\n session,\n closeHandshakingConnection,\n handshakingConn,\n }: {\n session: Session<ConnType>;\n closeHandshakingConnection: boolean;\n handshakingConn?: ConnType;\n }) {\n if (closeHandshakingConnection) {\n session.closeHandshakingConnection(handshakingConn);\n }\n session.close();\n session.telemetry.span.end();\n this.sessions.delete(session.to);\n this.log?.info(\n `session ${session.id} disconnect from ${session.to}`,\n session.loggingMetadata,\n );\n this.eventDispatcher.dispatchEvent('sessionStatus', {\n status: 'disconnect',\n session,\n });\n }\n\n /**\n * The downstream implementation needs to call this when a connection is closed.\n * @param conn The connection object.\n * @param connectedTo The peer we are connected to.\n */\n protected onDisconnect(conn: ConnType, session: Session<ConnType>) {\n conn.telemetry?.span.end();\n this.eventDispatcher.dispatchEvent('connectionStatus', {\n status: 'disconnect',\n conn,\n });\n\n session.connection = undefined;\n session.beginGrace(() => {\n session.telemetry.span.addEvent('session grace period expired');\n this.deleteSession({\n session,\n closeHandshakingConnection: true,\n handshakingConn: conn,\n });\n });\n }\n\n /**\n * Parses a message from a Uint8Array into a {@link OpaqueTransportMessage}.\n * @param msg The message to parse.\n * @returns The parsed message, or null if the message is malformed or invalid.\n */\n protected parseMsg(\n msg: Uint8Array,\n conn: ConnType,\n ): OpaqueTransportMessage | null {\n const parsedMsg = this.codec.fromBuffer(msg);\n\n if (parsedMsg === null) {\n const decodedBuffer = new TextDecoder().decode(Buffer.from(msg));\n this.log?.error(\n `received malformed msg, killing conn: ${decodedBuffer}`,\n {\n clientId: this.clientId,\n ...conn.loggingMetadata,\n },\n );\n return null;\n }\n\n if (!Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {\n this.log?.error(`received invalid msg: ${JSON.stringify(parsedMsg)}`, {\n clientId: this.clientId,\n ...conn.loggingMetadata,\n validationErrors: [\n ...Value.Errors(OpaqueTransportMessageSchema, parsedMsg),\n ],\n });\n return null;\n }\n\n return parsedMsg;\n }\n\n /**\n * Called when a message is received by this transport.\n * You generally shouldn't need to override this in downstream transport implementations.\n * @param msg The received message.\n */\n protected handleMsg(msg: OpaqueTransportMessage, conn: ConnType) {\n if (this.state !== 'open') return;\n const session = this.sessions.get(msg.from);\n if (!session) {\n this.log?.error(`received message for unknown session from ${msg.from}`, {\n clientId: this.clientId,\n transportMessage: msg,\n ...conn.loggingMetadata,\n tags: ['invariant-violation'],\n });\n return;\n }\n\n // got a msg so we know the other end is alive, reset the grace period\n session.cancelGrace();\n\n this.log?.debug(`received msg`, {\n clientId: this.clientId,\n transportMessage: msg,\n ...conn.loggingMetadata,\n });\n if (msg.seq !== session.nextExpectedSeq) {\n if (msg.seq < session.nextExpectedSeq) {\n this.log?.debug(\n `received duplicate msg (got seq: ${msg.seq}, wanted seq: ${session.nextExpectedSeq}), discarding`,\n {\n clientId: this.clientId,\n transportMessage: msg,\n ...conn.loggingMetadata,\n },\n );\n } else {\n const errMsg = `received out-of-order msg (got seq: ${msg.seq}, wanted seq: ${session.nextExpectedSeq})`;\n this.log?.error(`${errMsg}, marking connection as dead`, {\n clientId: this.clientId,\n transportMessage: msg,\n ...conn.loggingMetadata,\n tags: ['invariant-violation'],\n });\n this.protocolError(ProtocolError.MessageOrderingViolated, errMsg);\n session.telemetry.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'message order violated',\n });\n this.deleteSession({ session, closeHandshakingConnection: true });\n }\n\n return;\n }\n\n session.updateBookkeeping(msg.ack, msg.seq);\n\n // don't dispatch explicit acks\n if (!isAck(msg.controlFlags)) {\n this.eventDispatcher.dispatchEvent('message', msg);\n } else {\n this.log?.debug(`discarding msg (ack bit set)`, {\n clientId: this.clientId,\n transportMessage: msg,\n ...conn.loggingMetadata,\n });\n }\n }\n\n /**\n * Adds a listener to this transport.\n * @param the type of event to listen for\n * @param handler The message handler to add.\n */\n addEventListener<K extends EventTypes, T extends EventHandler<K>>(\n type: K,\n handler: T,\n ): void {\n this.eventDispatcher.addEventListener(type, handler);\n }\n\n /**\n * Removes a listener from this transport.\n * @param the type of event to un-listen on\n * @param handler The message handler to remove.\n */\n removeEventListener<K extends EventTypes, T extends EventHandler<K>>(\n type: K,\n handler: T,\n ): void {\n this.eventDispatcher.removeEventListener(type, handler);\n }\n\n /**\n * Sends a message over this transport, delegating to the appropriate connection to actually\n * send the message.\n * @param msg The message to send.\n * @returns The ID of the sent message or undefined if it wasn't sent\n */\n send(\n to: TransportClientId,\n msg: PartialTransportMessage,\n ): string | undefined {\n if (this.state === 'destroyed') {\n const err = 'transport is destroyed, cant send';\n this.log?.error(err, {\n clientId: this.clientId,\n transportMessage: msg,\n tags: ['invariant-violation'],\n });\n this.protocolError(ProtocolError.UseAfterDestroy, err);\n return undefined;\n } else if (this.state === 'closed') {\n this.log?.info(`transport closed when sending, discarding`, {\n clientId: this.clientId,\n transportMessage: msg,\n });\n return undefined;\n }\n\n return this.getOrCreateSession({ to }).session.send(msg);\n }\n\n // control helpers\n sendCloseStream(to: TransportClientId, streamId: string) {\n return this.send(to, {\n streamId: streamId,\n controlFlags: ControlFlags.StreamClosedBit,\n payload: {\n type: 'CLOSE' as const,\n } satisfies Static<typeof ControlMessagePayloadSchema>,\n });\n }\n\n protected protocolError(type: ProtocolErrorType, message: string) {\n this.eventDispatcher.dispatchEvent('protocolError', { type, message });\n }\n\n /**\n * Default close implementation for transports. You should override this in the downstream\n * implementation if you need to do any additional cleanup and call super.close() at the end.\n * Closes the transport. Any messages sent while the transport is closed will be silently discarded.\n */\n close() {\n this.state = 'closed';\n for (const session of this.sessions.values()) {\n this.deleteSession({ session, closeHandshakingConnection: true });\n }\n\n this.log?.info(`manually closed transport`, { clientId: this.clientId });\n }\n\n /**\n * Default destroy implementation for transports. You should override this in the downstream\n * implementation if you need to do any additional cleanup and call super.destroy() at the end.\n * Destroys the transport. Any messages sent while the transport is destroyed will throw an error.\n */\n destroy() {\n this.state = 'destroyed';\n for (const session of this.sessions.values()) {\n this.deleteSession({ session, closeHandshakingConnection: true });\n }\n\n this.log?.info(`manually destroyed transport`, { clientId: this.clientId });\n }\n}\n\nexport abstract class ClientTransport<\n ConnType extends Connection,\n> extends Transport<ConnType> {\n /**\n * The options for this transport.\n */\n protected options: ClientTransportOptions;\n\n /**\n * The map of reconnect promises for each client ID.\n */\n inflightConnectionPromises: Map<TransportClientId, Promise<ConnType>>;\n retryBudget: LeakyBucketRateLimit;\n\n /**\n * A flag indicating whether the transport should automatically reconnect\n * when a connection is dropped.\n * Realistically, this should always be true for clients unless you are writing\n * tests or a special case where you don't want to reconnect.\n */\n reconnectOnConnectionDrop = true;\n\n /**\n * Optional handshake options for this client.\n */\n handshakeExtensions?: ClientHandshakeOptions;\n\n constructor(\n clientId: TransportClientId,\n providedOptions?: ProvidedClientTransportOptions,\n ) {\n super(clientId, providedOptions);\n this.options = {\n ...defaultClientTransportOptions,\n ...providedOptions,\n };\n this.inflightConnectionPromises = new Map();\n this.retryBudget = new LeakyBucketRateLimit(this.options);\n }\n\n extendHandshake(options: ClientHandshakeOptions) {\n this.handshakeExtensions = options;\n }\n\n protected handleConnection(conn: ConnType, to: TransportClientId): void {\n if (this.state !== 'open') return;\n let session: Session<ConnType> | undefined = undefined;\n\n // kill the conn after the grace period if we haven't received a handshake\n const handshakeTimeout = setTimeout(() => {\n if (session) return;\n this.log?.warn(\n `connection to ${to} timed out waiting for handshake, closing`,\n { ...conn.loggingMetadata, clientId: this.clientId, connectedTo: to },\n );\n conn.close();\n }, this.options.sessionDisconnectGraceMs);\n\n const handshakeHandler = (data: Uint8Array) => {\n const maybeSession = this.receiveHandshakeResponseMessage(data, conn);\n clearTimeout(handshakeTimeout);\n if (!maybeSession) {\n conn.close();\n return;\n } else {\n session = maybeSession;\n }\n\n // when we are done handshake sequence,\n // remove handshake listener and use the normal message listener\n conn.removeDataListener(handshakeHandler);\n conn.addDataListener((data) => {\n const parsed = this.parseMsg(data, conn);\n if (!parsed) {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'message parse failure',\n });\n conn.close();\n return;\n }\n\n this.handleMsg(parsed, conn);\n });\n };\n\n conn.addDataListener(handshakeHandler);\n conn.addCloseListener(() => {\n if (session) {\n this.onDisconnect(conn, session);\n }\n this.log?.info(`connection to ${to} disconnected`, {\n ...conn.loggingMetadata,\n ...session?.loggingMetadata,\n clientId: this.clientId,\n connectedTo: to,\n });\n this.inflightConnectionPromises.delete(to);\n if (this.reconnectOnConnectionDrop) {\n void this.connect(to);\n }\n });\n conn.addErrorListener((err) => {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'connection error',\n });\n this.log?.warn(\n `error in connection to ${to}: ${coerceErrorString(err)}`,\n {\n ...conn.loggingMetadata,\n ...session?.loggingMetadata,\n clientId: this.clientId,\n connectedTo: to,\n },\n );\n });\n }\n\n receiveHandshakeResponseMessage(\n data: Uint8Array,\n conn: ConnType,\n ): Session<ConnType> | false {\n const parsed = this.parseMsg(data, conn);\n if (!parsed) {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'non-transport message',\n });\n this.protocolError(\n ProtocolError.HandshakeFailed,\n 'received non-transport message',\n );\n return false;\n }\n\n if (!Value.Check(ControlMessageHandshakeResponseSchema, parsed.payload)) {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'invalid handshake response',\n });\n this.log?.warn(`received invalid handshake resp`, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n connectedTo: parsed.from,\n transportMessage: parsed,\n validationErrors: [\n ...Value.Errors(\n ControlMessageHandshakeResponseSchema,\n parsed.payload,\n ),\n ],\n });\n this.protocolError(\n ProtocolError.HandshakeFailed,\n 'invalid handshake resp',\n );\n return false;\n }\n\n if (!parsed.payload.status.ok) {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'handshake rejected',\n });\n this.log?.warn(`received handshake rejection`, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n connectedTo: parsed.from,\n transportMessage: parsed,\n });\n this.protocolError(\n ProtocolError.HandshakeFailed,\n parsed.payload.status.reason,\n );\n return false;\n }\n\n this.log?.debug(`handshake from ${parsed.from} ok`, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n connectedTo: parsed.from,\n transportMessage: parsed,\n });\n\n const { session, isReconnect } = this.getOrCreateSession({\n to: parsed.from,\n conn,\n sessionId: parsed.payload.status.sessionId,\n });\n\n this.onConnect(conn, parsed.from, session, isReconnect);\n\n // After a successful connection, we start restoring the budget\n // so that the next time we try to connect, we don't hit the client\n // with backoff forever.\n this.retryBudget.startRestoringBudget(parsed.from);\n return session;\n }\n\n /**\n * Abstract method that creates a new {@link Connection} object.\n * This should call {@link handleConnection} when the connection is created.\n * The downstream client implementation needs to implement this.\n *\n * @param to The client ID of the node to connect to.\n * @returns The new connection object.\n */\n protected abstract createNewOutgoingConnection(\n to: TransportClientId,\n ): Promise<ConnType>;\n\n /**\n * Manually attempts to connect to a client.\n * @param to The client ID of the node to connect to.\n */\n async connect(to: TransportClientId): Promise<void> {\n const canProceedWithConnection = () => this.state === 'open';\n if (!canProceedWithConnection()) {\n this.log?.info(\n `transport state is no longer open, cancelling attempt to connect to ${to}`,\n { clientId: this.clientId, connectedTo: to },\n );\n return;\n }\n\n let reconnectPromise = this.inflightConnectionPromises.get(to);\n if (!reconnectPromise) {\n // check budget\n const budgetConsumed = this.retryBudget.getBudgetConsumed(to);\n if (!this.retryBudget.hasBudget(to)) {\n const errMsg = `tried to connect to ${to} but retry budget exceeded (more than ${budgetConsumed} attempts in the last ${this.retryBudget.totalBudgetRestoreTime}ms)`;\n this.log?.error(errMsg, { clientId: this.clientId, connectedTo: to });\n this.protocolError(ProtocolError.RetriesExceeded, errMsg);\n return;\n }\n\n let sleep = Promise.resolve();\n const backoffMs = this.retryBudget.getBackoffMs(to);\n if (backoffMs > 0) {\n sleep = new Promise((resolve) => setTimeout(resolve, backoffMs));\n }\n\n this.log?.info(\n `attempting connection to ${to} (${backoffMs}ms backoff)`,\n {\n clientId: this.clientId,\n connectedTo: to,\n },\n );\n this.retryBudget.consumeBudget(to);\n reconnectPromise = tracer.startActiveSpan('connect', async (span) => {\n try {\n span.addEvent('backoff', { backoffMs });\n await sleep;\n if (!canProceedWithConnection()) {\n throw new Error('transport state is no longer open');\n }\n\n span.addEvent('connecting');\n const conn = await this.createNewOutgoingConnection(to);\n if (!canProceedWithConnection()) {\n this.log?.info(\n `transport state is no longer open, closing pre-handshake connection to ${to}`,\n {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n connectedTo: to,\n },\n );\n conn.close();\n throw new Error('transport state is no longer open');\n }\n\n span.addEvent('sending handshake');\n const ok = await this.sendHandshake(to, conn);\n if (!ok) {\n conn.close();\n throw new Error('failed to send handshake');\n }\n\n return conn;\n } catch (err) {\n // rethrow the error so that the promise is rejected\n // as it was before we wrapped it in a span\n const errStr = coerceErrorString(err);\n span.recordException(errStr);\n span.setStatus({ code: SpanStatusCode.ERROR });\n throw err;\n } finally {\n span.end();\n }\n });\n\n this.inflightConnectionPromises.set(to, reconnectPromise);\n } else {\n this.log?.info(\n `attempting connection to ${to} (reusing previous attempt)`,\n {\n clientId: this.clientId,\n connectedTo: to,\n },\n );\n }\n\n try {\n await reconnectPromise;\n } catch (error: unknown) {\n this.inflightConnectionPromises.delete(to);\n const errStr = coerceErrorString(error);\n\n if (!this.reconnectOnConnectionDrop || !canProceedWithConnection()) {\n this.log?.warn(`connection to ${to} failed (${errStr})`, {\n clientId: this.clientId,\n connectedTo: to,\n });\n } else {\n this.log?.warn(`connection to ${to} failed (${errStr}), retrying`, {\n clientId: this.clientId,\n connectedTo: to,\n });\n return this.connect(to);\n }\n }\n }\n\n protected deleteSession({\n session,\n closeHandshakingConnection,\n handshakingConn,\n }: {\n session: Session<ConnType>;\n closeHandshakingConnection: boolean;\n handshakingConn?: ConnType;\n }) {\n this.inflightConnectionPromises.delete(session.to);\n super.deleteSession({\n session,\n closeHandshakingConnection,\n handshakingConn,\n });\n }\n\n protected async sendHandshake(to: TransportClientId, conn: ConnType) {\n let metadata: unknown = undefined;\n\n if (this.handshakeExtensions) {\n metadata = await this.handshakeExtensions.construct();\n if (!Value.Check(this.handshakeExtensions.schema, metadata)) {\n this.log?.error(`constructed handshake metadata did not match schema`, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n connectedTo: to,\n validationErrors: [\n ...Value.Errors(this.handshakeExtensions.schema, metadata),\n ],\n tags: ['invariant-violation'],\n });\n this.protocolError(\n ProtocolError.HandshakeFailed,\n 'handshake metadata did not match schema',\n );\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'handshake meta mismatch',\n });\n return false;\n }\n }\n\n // dont pass conn here as we dont want the session to start using the conn\n // until we have finished the handshake. Still, let the session know that\n // it is semi-associated with the conn, and it can close it if .close() is called.\n const { session } = this.getOrCreateSession({ to, handshakingConn: conn });\n const requestMsg = handshakeRequestMessage(\n this.clientId,\n to,\n session.id,\n metadata,\n getPropagationContext(session.telemetry.ctx),\n );\n this.log?.debug(`sending handshake request to ${to}`, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n connectedTo: to,\n transportMessage: requestMsg,\n });\n conn.send(this.codec.toBuffer(requestMsg));\n return true;\n }\n\n close() {\n this.retryBudget.close();\n super.close();\n }\n}\n\nexport abstract class ServerTransport<\n ConnType extends Connection,\n> extends Transport<ConnType> {\n /**\n * The options for this transport.\n */\n protected options: ServerTransportOptions;\n\n /**\n * Optional handshake options for the server.\n */\n handshakeExtensions?: ServerHandshakeOptions;\n\n /**\n * A map of session handshake data for each session.\n */\n sessionHandshakeMetadata: WeakMap<Session<ConnType>, ParsedMetadata>;\n\n constructor(\n clientId: TransportClientId,\n providedOptions?: ProvidedServerTransportOptions,\n ) {\n super(clientId, providedOptions);\n this.options = {\n ...defaultServerTransportOptions,\n ...providedOptions,\n };\n this.sessionHandshakeMetadata = new WeakMap();\n this.log?.info(`initiated server transport`, {\n clientId: this.clientId,\n protocolVersion: PROTOCOL_VERSION,\n });\n }\n\n extendHandshake(options: ServerHandshakeOptions) {\n this.handshakeExtensions = options;\n }\n\n protected handleConnection(conn: ConnType) {\n if (this.state !== 'open') return;\n\n this.log?.info(`new incoming connection`, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n });\n\n let session: Session<ConnType> | undefined = undefined;\n const client = () => session?.to ?? 'unknown';\n\n // kill the conn after the grace period if we haven't received a handshake\n const handshakeTimeout = setTimeout(() => {\n if (!session) {\n this.log?.warn(\n `connection to ${client()} timed out waiting for handshake, closing`,\n {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n connectedTo: client(),\n },\n );\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'handshake timeout',\n });\n conn.close();\n }\n }, this.options.sessionDisconnectGraceMs);\n\n const buffer: Array<Uint8Array> = [];\n let receivedHandshakeMessage = false;\n\n const handshakeHandler = (data: Uint8Array) => {\n // if we've already received, just buffer the data\n if (receivedHandshakeMessage) {\n buffer.push(data);\n return;\n }\n\n receivedHandshakeMessage = true;\n clearTimeout(handshakeTimeout);\n\n void this.receiveHandshakeRequestMessage(data, conn).then(\n (maybeSession) => {\n if (!maybeSession) {\n conn.close();\n return;\n }\n\n session = maybeSession;\n\n // when we are done handshake sequence,\n // remove handshake listener and use the normal message listener\n const dataHandler = (data: Uint8Array) => {\n const parsed = this.parseMsg(data, conn);\n if (!parsed) {\n conn.close();\n return;\n }\n\n this.handleMsg(parsed, conn);\n };\n\n // process any data we missed\n for (const data of buffer) {\n dataHandler(data);\n }\n\n conn.removeDataListener(handshakeHandler);\n conn.addDataListener(dataHandler);\n buffer.length = 0;\n },\n );\n };\n\n conn.addDataListener(handshakeHandler);\n conn.addCloseListener(() => {\n if (!session) return;\n this.log?.info(`connection to ${client()} disconnected`, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n });\n this.onDisconnect(conn, session);\n });\n\n conn.addErrorListener((err) => {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'connection error',\n });\n if (!session) return;\n this.log?.warn(\n `connection to ${client()} got an error: ${coerceErrorString(err)}`,\n { ...conn.loggingMetadata, clientId: this.clientId },\n );\n });\n }\n\n private async validateHandshakeMetadata(\n conn: ConnType,\n session: Session<ConnType> | undefined,\n rawMetadata: Static<\n typeof ControlMessageHandshakeRequestSchema\n >['metadata'],\n from: TransportClientId,\n ): Promise<ParsedMetadata | false> {\n let parsedMetadata: ParsedMetadata = {};\n if (this.handshakeExtensions) {\n // check that the metadata that was sent is the correct shape\n if (!Value.Check(this.handshakeExtensions.schema, rawMetadata)) {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'malformed handshake meta',\n });\n const reason = 'received malformed handshake metadata';\n const responseMsg = handshakeResponseMessage(this.clientId, from, {\n ok: false,\n reason,\n });\n conn.send(this.codec.toBuffer(responseMsg));\n this.log?.warn(`received malformed handshake metadata from ${from}`, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n validationErrors: [\n ...Value.Errors(this.handshakeExtensions.schema, rawMetadata),\n ],\n });\n this.protocolError(ProtocolError.HandshakeFailed, reason);\n return false;\n }\n\n const previousParsedMetadata = session\n ? this.sessionHandshakeMetadata.get(session)\n : undefined;\n\n parsedMetadata = await this.handshakeExtensions.validate(\n rawMetadata,\n previousParsedMetadata,\n );\n\n // handler rejected the connection\n if (parsedMetadata === false) {\n const reason = 'rejected by handshake handler';\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: reason,\n });\n const responseMsg = handshakeResponseMessage(this.clientId, from, {\n ok: false,\n reason,\n });\n conn.send(this.codec.toBuffer(responseMsg));\n this.log?.warn(`rejected handshake from ${from}`, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n });\n this.protocolError(ProtocolError.HandshakeFailed, reason);\n return false;\n }\n }\n\n return parsedMetadata;\n }\n\n async receiveHandshakeRequestMessage(\n data: Uint8Array,\n conn: ConnType,\n ): Promise<Session<ConnType> | false> {\n const parsed = this.parseMsg(data, conn);\n if (!parsed) {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'non-transport message',\n });\n this.protocolError(\n ProtocolError.HandshakeFailed,\n 'received non-transport message',\n );\n return false;\n }\n\n if (!Value.Check(ControlMessageHandshakeRequestSchema, parsed.payload)) {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'invalid handshake request',\n });\n const reason = 'received invalid handshake msg';\n const responseMsg = handshakeResponseMessage(this.clientId, parsed.from, {\n ok: false,\n reason,\n });\n conn.send(this.codec.toBuffer(responseMsg));\n this.log?.warn(reason, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n // safe to this.log metadata here as we remove the payload\n // before passing it to user-land\n transportMessage: parsed,\n validationErrors: [\n ...Value.Errors(ControlMessageHandshakeRequestSchema, parsed.payload),\n ],\n });\n this.protocolError(\n ProtocolError.HandshakeFailed,\n 'invalid handshake request',\n );\n return false;\n }\n\n // double check protocol version here\n const gotVersion = parsed.payload.protocolVersion;\n if (gotVersion !== PROTOCOL_VERSION) {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'incorrect protocol version',\n });\n\n const reason = `incorrect version (got: ${gotVersion} wanted ${PROTOCOL_VERSION})`;\n const responseMsg = handshakeResponseMessage(this.clientId, parsed.from, {\n ok: false,\n reason,\n });\n conn.send(this.codec.toBuffer(responseMsg));\n this.log?.warn(\n `received handshake msg with incompatible protocol version (got: ${gotVersion}, expected: ${PROTOCOL_VERSION})`,\n { ...conn.loggingMetadata, clientId: this.clientId },\n );\n this.protocolError(ProtocolError.HandshakeFailed, reason);\n return false;\n }\n\n const oldSession = this.sessions.get(parsed.from);\n const parsedMetadata = await this.validateHandshakeMetadata(\n conn,\n oldSession,\n parsed.payload.metadata,\n parsed.from,\n );\n\n if (parsedMetadata === false) {\n return false;\n }\n\n const { session, isReconnect } = this.getOrCreateSession({\n to: parsed.from,\n conn,\n sessionId: parsed.payload.sessionId,\n propagationCtx: parsed.tracing,\n });\n\n this.sessionHandshakeMetadata.set(session, parsedMetadata);\n\n this.log?.debug(\n `handshake from ${parsed.from} ok, responding with handshake success`,\n conn.loggingMetadata,\n );\n const responseMsg = handshakeResponseMessage(this.clientId, parsed.from, {\n ok: true,\n sessionId: session.id,\n });\n conn.send(this.codec.toBuffer(responseMsg));\n this.onConnect(conn, parsed.from, session, isReconnect);\n\n return session;\n }\n}\n","import { Codec } from './types';\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\n// Convert Uint8Array to base64\nfunction uint8ArrayToBase64(uint8Array: Uint8Array) {\n let binary = '';\n uint8Array.forEach((byte) => {\n binary += String.fromCharCode(byte);\n });\n return btoa(binary);\n}\n\n// Convert base64 to Uint8Array\nfunction base64ToUint8Array(base64: string) {\n const binaryString = atob(base64);\n const uint8Array = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n uint8Array[i] = binaryString.charCodeAt(i);\n }\n return uint8Array;\n}\n\ninterface Base64EncodedValue {\n $t: string;\n}\n\n/**\n * Naive JSON codec implementation using JSON.stringify and JSON.parse.\n * @type {Codec}\n */\nexport const NaiveJsonCodec: Codec = {\n toBuffer: (obj: object) => {\n return encoder.encode(\n JSON.stringify(obj, function replacer<\n T extends object,\n >(this: T, key: keyof T) {\n const val = this[key];\n if (val instanceof Uint8Array) {\n return { $t: uint8ArrayToBase64(val) } satisfies Base64EncodedValue;\n } else {\n return val;\n }\n }),\n );\n },\n fromBuffer: (buff: Uint8Array) => {\n try {\n const parsed = JSON.parse(\n decoder.decode(buff),\n function reviver(_key, val: unknown) {\n if ((val as Base64EncodedValue | undefined)?.$t) {\n return base64ToUint8Array((val as Base64EncodedValue).$t);\n } else {\n return val;\n }\n },\n ) as unknown;\n\n if (typeof parsed === 'object') return parsed;\n return null;\n } catch {\n return null;\n }\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAAwC;;;ACAzB,SAAR,SAA0B;AAChC,QAAM,WAAW,CAAC;AAElB,WAAS,UAAU,IAAI,QAAQ,CAAC,SAAS,WAAW;AACnD,aAAS,UAAU;AACnB,aAAS,SAAS;AAAA,EACnB,CAAC;AAED,SAAO;AACR;;;ACDA,IAAM,YAAN,MAAe;EACN;EACU;EACT;EACA;EACD;EAEP,YAAa,KAAW;AACtB,QAAI,EAAE,MAAM,OAAQ,MAAM,IAAK,SAAS,GAAG;AACzC,YAAM,IAAI,MAAM,mDAAmD;;AAGrE,SAAK,SAAS,IAAI,MAAM,GAAG;AAC3B,SAAK,OAAO,MAAM;AAClB,SAAK,MAAM;AACX,SAAK,MAAM;AACX,SAAK,OAAO;EACd;EAEA,KAAM,MAAa;AACjB,QAAI,KAAK,OAAO,KAAK,GAAG,MAAM,QAAW;AACvC,aAAO;;AAGT,SAAK,OAAO,KAAK,GAAG,IAAI;AACxB,SAAK,MAAO,KAAK,MAAM,IAAK,KAAK;AAEjC,WAAO;EACT;EAEA,QAAK;AACH,UAAM,OAAO,KAAK,OAAO,KAAK,GAAG;AAEjC,QAAI,SAAS,QAAW;AACtB,aAAO;;AAGT,SAAK,OAAO,KAAK,GAAG,IAAI;AACxB,SAAK,MAAO,KAAK,MAAM,IAAK,KAAK;AACjC,WAAO;EACT;EAEA,UAAO;AACL,WAAO,KAAK,OAAO,KAAK,GAAG,MAAM;EACnC;;AAUI,IAAO,OAAP,MAAW;EACR;EACU;EACT;EACA;EAER,YAAa,UAAuB,CAAA,GAAE;AACpC,SAAK,MAAM,QAAQ,cAAc;AACjC,SAAK,OAAO,IAAI,UAAa,KAAK,GAAG;AACrC,SAAK,OAAO,KAAK;AACjB,SAAK,OAAO;EACd;EAEA,cAAe,KAAQ;AACrB,QAAI,KAAK,cAAc,MAAM;AAC3B,aAAO,IAAI;;AAGb,WAAO;EACT;EAEA,KAAM,KAAY;AAChB,QAAI,KAAK,SAAS,MAAM;AACtB,WAAK,QAAQ,KAAK,cAAc,IAAI,KAAK;;AAG3C,QAAI,CAAC,KAAK,KAAK,KAAK,GAAG,GAAG;AACxB,YAAM,OAAO,KAAK;AAClB,WAAK,OAAO,KAAK,OAAO,IAAI,UAAa,IAAI,KAAK,KAAK,OAAO,MAAM;AACpE,WAAK,KAAK,KAAK,GAAG;;EAEtB;EAEA,QAAK;AACH,QAAI,MAAM,KAAK,KAAK,MAAK;AAEzB,QAAI,QAAQ,UAAc,KAAK,KAAK,QAAQ,MAAO;AACjD,YAAM,OAAO,KAAK,KAAK;AACvB,WAAK,KAAK,OAAO;AACjB,WAAK,OAAO;AACZ,YAAM,KAAK,KAAK,MAAK;;AAGvB,QAAI,KAAK,SAAS,MAAM;AACtB,WAAK,QAAQ,KAAK,cAAc,IAAI,KAAK;;AAG3C,WAAO;EACT;EAEA,UAAO;AACL,WAAO,KAAK,KAAK,QAAO;EAC1B;;;;AC9DI,IAAO,aAAP,cAA0B,MAAK;EACnC;EACA;EAEA,YAAa,SAAkB,MAAa;AAC1C,UAAM,WAAW,2BAA2B;AAC5C,SAAK,OAAO;AACZ,SAAK,OAAO,QAAQ;EACtB;;AAoFI,SAAU,SAAa,UAAmB,CAAA,GAAE;AAChD,QAAM,UAAU,CAAC,WAAkC;AACjD,UAAM,OAA4B,OAAO,MAAK;AAE9C,QAAI,QAAQ,MAAM;AAChB,aAAO,EAAE,MAAM,KAAI;;AAGrB,QAAI,KAAK,SAAS,MAAM;AACtB,YAAM,KAAK;;AAGb,WAAO;MACL,MAAM,KAAK,SAAS;;MAEpB,OAAO,KAAK;;EAEhB;AAEA,SAAO,UAA6B,SAAS,OAAO;AACtD;AAuCA,SAAS,UAA4C,SAAuC,SAAiB;AAC3G,YAAU,WAAW,CAAA;AACrB,MAAI,QAAQ,QAAQ;AACpB,MAAI,SAAS,IAAI,KAAI;AACrB,MAAIA;AACJ,MAAI;AACJ,MAAI;AACJ,MAAI,QAAQ,OAAQ;AAEpB,QAAM,WAAW,YAA2C;AAC1D,QAAI;AACF,UAAI,CAAC,OAAO,QAAO,GAAI;AACrB,eAAO,QAAQ,MAAM;;AAGvB,UAAI,OAAO;AACT,eAAO,EAAE,MAAM,KAAI;;AAGrB,aAAO,MAAM,IAAI,QAA+B,CAAC,SAAS,WAAU;AAClE,iBAAS,CAAC,SAAwB;AAChC,mBAAS;AACT,iBAAO,KAAK,IAAI;AAEhB,cAAI;AACF,oBAAQ,QAAQ,MAAM,CAAC;mBAChB,KAAK;AACZ,mBAAO,GAAG;;AAGZ,iBAAOA;QACT;MACF,CAAC;;AAED,UAAI,OAAO,QAAO,GAAI;AAGpB,uBAAe,MAAK;AAClB,gBAAM,QAAO;AACb,kBAAQ,OAAQ;QAClB,CAAC;;;EAGP;AAEA,QAAM,aAAa,CAAC,SAAoC;AACtD,QAAI,UAAU,MAAM;AAClB,aAAO,OAAO,IAAI;;AAGpB,WAAO,KAAK,IAAI;AAChB,WAAOA;EACT;AAEA,QAAM,cAAc,CAAC,QAA0B;AAC7C,aAAS,IAAI,KAAI;AAEjB,QAAI,UAAU,MAAM;AAClB,aAAO,OAAO,EAAE,OAAO,IAAG,CAAE;;AAG9B,WAAO,KAAK,EAAE,OAAO,IAAG,CAAE;AAC1B,WAAOA;EACT;AAEA,QAAM,OAAO,CAAC,UAA+B;AAC3C,QAAI,OAAO;AACT,aAAOA;;AAIT,QAAI,SAAS,eAAe,QAAQ,OAAO,cAAc,MAAM;AAC7D,YAAM,IAAI,MAAM,gEAAgE;;AAGlF,WAAO,WAAW,EAAE,MAAM,OAAO,MAAK,CAAE;EAC1C;AACA,QAAM,MAAM,CAAC,QAA2B;AACtC,QAAI;AAAO,aAAOA;AAClB,YAAQ;AAER,WAAQ,OAAO,OAAQ,YAAY,GAAG,IAAI,WAAW,EAAE,MAAM,KAAI,CAAE;EACrE;AACA,QAAM,UAAU,MAAiB;AAC/B,aAAS,IAAI,KAAI;AACjB,QAAG;AAEH,WAAO,EAAE,MAAM,KAAI;EACrB;AACA,QAAM,SAAS,CAAC,QAA0B;AACxC,QAAI,GAAG;AAEP,WAAO,EAAE,MAAM,KAAI;EACrB;AAEA,EAAAA,YAAW;IACT,CAAC,OAAO,aAAa,IAAC;AAAM,aAAO;IAAK;IACxC,MAAM;IACN,QAAQ;IACR,OAAO;IACP;IACA;IACA,IAAI,iBAAc;AAChB,aAAO,OAAO;IAChB;IACA,SAAS,OAAOC,aAA0B;AACxC,YAAM,SAASA,UAAS;AACxB,cAAQ,eAAc;AAEtB,UAAI,OAAO,QAAO,GAAI;AACpB;;AAGF,UAAI;AACJ,UAAI;AAEJ,UAAI,UAAU,MAAM;AAClB,iBAAS,IAAI,QAAQ,CAAC,SAAS,WAAU;AACvC,qBAAW,MAAK;AACd,mBAAO,IAAI,WAAU,CAAE;UACzB;AAEA,iBAAO,iBAAiB,SAAS,QAAQ;QAC3C,CAAC;;AAGH,UAAI;AACF,cAAM,QAAQ,KAAK;UACjB,MAAM;UACN;SACD;;AAED,YAAI,YAAY,QAAQ,UAAU,MAAM;AACtC,kBAAQ,oBAAoB,SAAS,QAAQ;;;IAGnD;;AAGF,MAAI,SAAS,MAAM;AACjB,WAAOD;;AAGT,QAAME,aAAYF;AAElB,EAAAA,YAAW;IACT,CAAC,OAAO,aAAa,IAAC;AAAM,aAAO;IAAK;IACxC,OAAI;AACF,aAAOE,WAAU,KAAI;IACvB;IACA,MAAO,KAAU;AACf,MAAAA,WAAU,MAAM,GAAG;AAEnB,UAAI,SAAS,MAAM;AACjB,cAAM,GAAG;AACT,gBAAQ;;AAGV,aAAO,EAAE,MAAM,KAAI;IACrB;IACA,SAAM;AACJ,MAAAA,WAAU,OAAM;AAEhB,UAAI,SAAS,MAAM;AACjB,cAAK;AACL,gBAAQ;;AAGV,aAAO,EAAE,MAAM,KAAI;IACrB;IACA;IACA,IAAK,KAAU;AACb,MAAAA,WAAU,IAAI,GAAG;AAEjB,UAAI,SAAS,MAAM;AACjB,cAAM,GAAG;AACT,gBAAQ;;AAGV,aAAOF;IACT;IACA,IAAI,iBAAc;AAChB,aAAOE,WAAU;IACnB;IACA,SAAS,CAAC,SAAuB;AAC/B,aAAOA,WAAU,QAAQ,IAAI;IAC/B;;AAGF,SAAOF;AACT;;;ACzYA,qBAQO;AAqBA,IAAM,iBAAiB;AACvB,IAAM,wBAAwB;AAC9B,IAAM,sBAAsB,oBAAK,OAAO;AAAA,EAC7C,MAAM,oBAAK,MAAM;AAAA,IACf,oBAAK,QAAQ,cAAc;AAAA,IAC3B,oBAAK,QAAQ,qBAAqB;AAAA,EACpC,CAAC;AAAA,EACD,SAAS,oBAAK,OAAO;AACvB,CAAC;AAwBM,SAAS,IAAsB,OAAwB;AAC5D,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,SAAS;AAAA,EACX;AACF;;;AClEA,iBAOO;;;ACJL,cAAW;;;ADmCN,SAAS,2BACd,SACA,gBACe;AACf,QAAM,YAAY,iBACd,uBAAY,QAAQ,mBAAQ,OAAO,GAAG,cAAc,IACpD,mBAAQ,OAAO;AAEnB,QAAM,OAAO,OAAO;AAAA,IAClB,WAAW,QAAQ,EAAE;AAAA,IACrB;AAAA,MACE,YAAY;AAAA,QACV,WAAW;AAAA,QACX,oBAAoB,QAAQ;AAAA,QAC5B,oBAAoB,QAAQ;AAAA,QAC5B,sBAAsB,QAAQ;AAAA,MAChC;AAAA,IACF;AAAA,IACA;AAAA,EACF;AAEA,QAAM,MAAM,iBAAM,QAAQ,WAAW,IAAI;AAEzC,SAAO,EAAE,MAAM,IAAI;AACrB;AA0FA,IAAM,SAAS,iBAAM,UAAU,SAAS,OAAa;;;AExJ9C,SAAS,kBAAkB,KAAsB;AACtD,MAAI,eAAe,OAAO;AACxB,WAAO,IAAI,WAAW;AAAA,EACxB;AAEA,SAAO,sBAAsB,OAAO,GAAG,CAAC;AAC1C;;;APOA,IAAAG,iBAAuB;;;AQbvB,oBAA+B;AAiB/B,IAAAC,cAA+B;AAE/B,IAAM,aAAS,8BAAe,uCAAuC,CAAC;AAC/D,IAAM,WAAW,MAAM,OAAO;AAiG9B,IAAM,UAAN,MAA2C;AAAA,EACxC;AAAA,EACA;AAAA,EACC;AAAA;AAAA;AAAA;AAAA,EAKD,aAA4C,CAAC;AAAA;AAAA;AAAA;AAAA,EAKrD;AAAA;AAAA;AAAA;AAAA;AAAA,EAKQ;AAAA,EACC;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMT;AAAA;AAAA;AAAA;AAAA,EAKQ,MAAsB;AAAA;AAAA;AAAA;AAAA,EAKtB,MAAsB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMtB;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA,EACA;AAAA,EAER,YACE,MACA,MACA,IACA,SACA,gBACA;AACA,SAAK,KAAK,WAAW,OAAO,EAAE,CAAC;AAC/B,SAAK,UAAU;AACf,SAAK,OAAO;AACZ,SAAK,KAAK;AACV,SAAK,aAAa;AAClB,SAAK,QAAQ,QAAQ;AAGrB,SAAK,kBAAkB;AACvB,SAAK,YAAY;AAAA,MACf,MAAM,KAAK,cAAc;AAAA,MACzB,QAAQ;AAAA,IACV;AACA,SAAK,YAAY,2BAA2B,MAAM,cAAc;AAAA,EAClE;AAAA,EAEA,WAAW,KAAa;AACtB,SAAK,MAAM;AAAA,EACb;AAAA,EAEA,IAAI,kBAAmC;AACrC,UAAM,cAAc,KAAK,UAAU,KAAK,YAAY;AAEpD,WAAO;AAAA,MACL,UAAU,KAAK;AAAA,MACf,aAAa,KAAK;AAAA,MAClB,WAAW,KAAK;AAAA,MAChB,QAAQ,KAAK,YAAY;AAAA,MACzB,WAAW;AAAA,QACT,SAAS,YAAY;AAAA,QACrB,QAAQ,YAAY;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,KAAK,KAAsC;AACzC,UAAM,UAA4B,KAAK,aAAa,GAAG;AACvD,SAAK,KAAK,MAAM,eAAe;AAAA,MAC7B,GAAG,KAAK;AAAA,MACR,kBAAkB;AAAA,IACpB,CAAC;AAED,QAAI,KAAK,YAAY;AACnB,YAAM,KAAK,KAAK,WAAW,KAAK,KAAK,MAAM,SAAS,OAAO,CAAC;AAC5D,UAAI;AAAI,eAAO,QAAQ;AACvB,WAAK,KAAK;AAAA,QACR,yBAAyB,QAAQ,EAAE;AAAA,QACnC;AAAA,UACE,GAAG,KAAK;AAAA,UACR,kBAAkB;AAAA,QACpB;AAAA,MACF;AAAA,IACF,OAAO;AACL,WAAK,KAAK;AAAA,QACR,oBAAoB,QAAQ,EAAE;AAAA,QAC9B,EAAE,GAAG,KAAK,iBAAiB,kBAAkB,QAAQ;AAAA,MACvD;AAAA,IACF;AAEA,WAAO,QAAQ;AAAA,EACjB;AAAA,EAEA,gBAAgB;AACd,UAAM,SAAS,KAAK;AACpB,UAAM,eAAe,SAAS,KAAK,QAAQ;AAC3C,QAAI,SAAS,KAAK,QAAQ,qBAAqB;AAC7C,UAAI,KAAK,YAAY;AACnB,aAAK,KAAK;AAAA,UACR,yBAAyB,KAAK,EAAE,8BAA8B,MAAM,wBAAwB,YAAY;AAAA,UACxG,KAAK;AAAA,QACP;AACA,aAAK,UAAU,KAAK,SAAS,sCAAsC;AACnE,aAAK,qBAAqB;AAAA,MAC5B;AACA;AAAA,IACF;AAEA,SAAK,KAAK;AAAA,MACR,UAAU;AAAA,MACV;AAAA,MACA,SAAS;AAAA,QACP,MAAM;AAAA,MACR;AAAA,IACF,CAAC;AACD,SAAK;AAAA,EACP;AAAA,EAEA,wBAAwB;AACtB,SAAK,aAAa,CAAC;AACnB,SAAK,MAAM;AACX,SAAK,MAAM;AAAA,EACb;AAAA,EAEA,qBAAqB,MAAgB;AACnC,SAAK,KAAK,KAAK,aAAa,KAAK,WAAW,MAAM,sBAAsB;AAAA,MACtE,GAAG,KAAK;AAAA,MACR,QAAQ,KAAK;AAAA,IACf,CAAC;AACD,eAAW,OAAO,KAAK,YAAY;AACjC,WAAK,KAAK,MAAM,iBAAiB;AAAA,QAC/B,GAAG,KAAK;AAAA,QACR,kBAAkB;AAAA,QAClB,QAAQ,KAAK;AAAA,MACf,CAAC;AACD,YAAM,KAAK,KAAK,KAAK,KAAK,MAAM,SAAS,GAAG,CAAC;AAC7C,UAAI,CAAC,IAAI;AAGP,cAAM,SAAS,sCAAsC,KAAK,EAAE;AAC5D,aAAK,WAAW,KAAK,UAAU;AAAA,UAC7B,MAAM,2BAAe;AAAA,UACrB,SAAS;AAAA,QACX,CAAC;AAED,aAAK,KAAK,MAAM,QAAQ;AAAA,UACtB,GAAG,KAAK;AAAA,UACR,kBAAkB;AAAA,UAClB,QAAQ,KAAK;AAAA,UACb,MAAM,CAAC,qBAAqB;AAAA,QAC9B,CAAC;AACD,aAAK,MAAM;AACX;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,kBAAkB,KAAa,KAAa;AAC1C,QAAI,MAAM,IAAI,KAAK,KAAK;AACtB,WAAK,KAAK,MAAM,sBAAsB,GAAG,UAAU,KAAK,GAAG,IAAI;AAAA,QAC7D,GAAG,KAAK;AAAA,QACR,MAAM,CAAC,qBAAqB;AAAA,MAC9B,CAAC;AACD;AAAA,IACF;AAEA,SAAK,aAAa,KAAK,WAAW,OAAO,CAAC,YAAY,QAAQ,OAAO,GAAG;AACxE,SAAK,MAAM,MAAM;AAAA,EACnB;AAAA,EAEQ,qBAAqB,MAAiB;AAC5C,QAAI,KAAK,eAAe,UAAa,KAAK,eAAe;AAAM;AAC/D,SAAK,KAAK;AAAA,MACR,gDAAgD,KAAK,EAAE;AAAA,MACvD,KAAK;AAAA,IACP;AACA,SAAK,WAAW,MAAM;AACtB,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,yBAAyB,SAAmB;AAC1C,SAAK,qBAAqB,OAAO;AACjC,SAAK,YAAY;AACjB,SAAK,qBAAqB,OAAO;AACjC,SAAK,aAAa;AAIlB,SAAK,wBAAwB;AAAA,EAC/B;AAAA,EAEA,oCAAoC,SAAmB;AACrD,SAAK,wBAAwB;AAAA,EAC/B;AAAA,EAEA,WAAW,IAAgB;AACzB,SAAK,KAAK;AAAA,MACR,YAAY,KAAK,QAAQ,wBAAwB,oCAAoC,KAAK,EAAE;AAAA,MAC5F,KAAK;AAAA,IACP;AAEA,SAAK,YAAY,EAAE,qBAAqB,KAAK,CAAC;AAC9C,SAAK,qBAAqB,WAAW,MAAM;AACzC,UAAI,KAAK,eAAe,QAAW;AACjC,aAAK,KAAK;AAAA,UACR,oBAAoB,KAAK,EAAE;AAAA,UAC3B;AAAA,YACE,GAAG,KAAK;AAAA,YACR,QAAQ,KAAK,WAAW;AAAA,YACxB,MAAM,CAAC,qBAAqB;AAAA,UAC9B;AAAA,QACF;AACA;AAAA,MACF;AACA,WAAK,KAAK;AAAA,QACR,oBAAoB,KAAK,EAAE;AAAA,QAC3B,KAAK;AAAA,MACP;AACA,SAAG;AAAA,IACL,GAAG,KAAK,QAAQ,wBAAwB;AAAA,EAC1C;AAAA;AAAA,EAGA,YACE,EAAE,oBAAoB,IAAsC;AAAA,IAC1D,qBAAqB;AAAA,EACvB,GACA;AACA,QAAI,CAAC,qBAAqB;AACxB,WAAK,kBAAkB;AAAA,IACzB;AACA,QAAI,KAAK,uBAAuB;AAAW;AAC3C,iBAAa,KAAK,kBAAkB;AACpC,SAAK,qBAAqB;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,2BAA2B,yBAAoC;AAC7D,QAAI,KAAK,0BAA0B;AAAW;AAC9C,QACE,4BAA4B,UAC5B,KAAK,0BAA0B,yBAC/B;AAEA;AAAA,IACF;AACA,SAAK,sBAAsB,MAAM;AACjC,SAAK,wBAAwB;AAAA,EAC/B;AAAA;AAAA;AAAA,EAIA,QAAQ;AACN,SAAK,qBAAqB;AAC1B,SAAK,YAAY;AACjB,SAAK,sBAAsB;AAC3B,kBAAc,KAAK,SAAS;AAAA,EAC9B;AAAA,EAEA,IAAI,YAAY;AACd,WAAO,KAAK,eAAe;AAAA,EAC7B;AAAA,EAEA,IAAI,kBAAkB;AACpB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,aACE,YAC2B;AAC3B,UAAM,MAAM;AAAA,MACV,GAAG;AAAA,MACH,IAAI,SAAS;AAAA,MACb,IAAI,KAAK;AAAA,MACT,MAAM,KAAK;AAAA,MACX,KAAK,KAAK;AAAA,MACV,KAAK,KAAK;AAAA,IACZ;AAEA,SAAK;AACL,SAAK,WAAW,KAAK,GAAG;AACxB,WAAO;AAAA,EACT;AAAA,EAEA,oBAA2D;AACzD,WAAO,KAAK;AAAA,EACd;AACF;;;ACjcA,mBAAsB;;;ACCtB,IAAM,UAAU,IAAI,YAAY;AAChC,IAAM,UAAU,IAAI,YAAY;AAGhC,SAAS,mBAAmB,YAAwB;AAClD,MAAI,SAAS;AACb,aAAW,QAAQ,CAAC,SAAS;AAC3B,cAAU,OAAO,aAAa,IAAI;AAAA,EACpC,CAAC;AACD,SAAO,KAAK,MAAM;AACpB;AAGA,SAAS,mBAAmB,QAAgB;AAC1C,QAAM,eAAe,KAAK,MAAM;AAChC,QAAM,aAAa,IAAI,WAAW,aAAa,MAAM;AACrD,WAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC5C,eAAW,CAAC,IAAI,aAAa,WAAW,CAAC;AAAA,EAC3C;AACA,SAAO;AACT;AAUO,IAAM,iBAAwB;AAAA,EACnC,UAAU,CAAC,QAAgB;AACzB,WAAO,QAAQ;AAAA,MACb,KAAK,UAAU,KAAK,SAAS,SAElB,KAAc;AACvB,cAAM,MAAM,KAAK,GAAG;AACpB,YAAI,eAAe,YAAY;AAC7B,iBAAO,EAAE,IAAI,mBAAmB,GAAG,EAAE;AAAA,QACvC,OAAO;AACL,iBAAO;AAAA,QACT;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EACA,YAAY,CAAC,SAAqB;AAChC,QAAI;AACF,YAAM,SAAS,KAAK;AAAA,QAClB,QAAQ,OAAO,IAAI;AAAA,QACnB,SAAS,QAAQ,MAAM,KAAc;AACnC,cAAK,KAAwC,IAAI;AAC/C,mBAAO,mBAAoB,IAA2B,EAAE;AAAA,UAC1D,OAAO;AACL,mBAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF;AAEA,UAAI,OAAO,WAAW;AAAU,eAAO;AACvC,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;AD1BA,IAAAC,cAA+B;AAmBxB,IAAM,0BAA4C;AAAA,EACvD,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EACrB,0BAA0B;AAAA,EAC1B,OAAO;AACT;AAMA,IAAM,gCAAwD;AAAA,EAC5D,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb,cAAc;AAAA,EACd,uBAAuB;AAAA,EACvB,yBAAyB;AAC3B;AAEA,IAAM,gCAAwD;AAAA,EAC5D,GAAG;AAAA,EACH,GAAG;AACL;AAMA,IAAM,gCAAwD;AAAA,EAC5D,GAAG;AACL;;;AT3DO,SAAS,2BAA2B,MAAsB;AAC/D,QAAM,OAAO,IAAI,UAAAC,QAAO,kBAAkB,IAAI,EAAE;AAChD,OAAK,aAAa;AAElB,SAAO;AACT;AAQO,SAAS,sBAAsB,QAAqB;AACzD,SAAO,IAAI,0BAAgB,EAAE,OAAO,CAAC;AACvC;AASO,SAAS,gBAAgB,QAAsC;AACpE,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAO,OAAO,MAAM;AAClB,YAAM,OAAO,OAAO,QAAQ;AAC5B,UAAI,OAAO,SAAS,YAAY,MAAM;AACpC,gBAAQ,KAAK,IAAI;AAAA,MACnB,OAAO;AACL,eAAO,IAAI,MAAM,kCAAkC,CAAC;AAAA,MACtD;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;AAEO,SAAS,gBACd,QACA,MACe;AACf,SAAO,IAAI,QAAc,CAAC,YAAY;AACpC,WAAO,OAAO,MAAM,OAAO;AAAA,EAC7B,CAAC;AACH;AAOA,eAAsB,SAAY,MAAgC;AAChE,SAAO,MAAM,KAAK,KAAK,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAU;AACvD;AAEO,SAAS,0BACd,SACkC;AAClC,SAAO;AAAA,IACL,UAAU;AAAA,IACV,cAAc;AAAA,IACd;AAAA,EACF;AACF;AAEO,SAAS,8BAA8B;AAC5C,SAAO,0BAA0B;AAAA,IAC/B,KAAK;AAAA,IACL,MAAM,KAAK,OAAO;AAAA,EACpB,CAAC;AACH;AAQA,eAAsB,eACpB,GACA,QACA,gBACA;AACA,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,aAAS,UAAU;AACjB,QAAE,oBAAoB,WAAW,SAAS;AAAA,IAC5C;AAEA,aAAS,UAAU,KAA6B;AAC9C,UAAI,CAAC,UAAU,OAAO,GAAG,GAAG;AAC1B,gBAAQ;AACR,gBAAQ,IAAI,OAAO;AAAA,MACrB,WAAW,gBAAgB;AACzB,gBAAQ;AACR,eAAO,IAAI,MAAM,gCAAgC,CAAC;AAAA,MACpD;AAAA,IACF;AAEA,MAAE,iBAAiB,WAAW,SAAS;AAAA,EACzC,CAAC;AACH;AAEA,SAAS,eAAe,KAAc;AACpC,QAAM,WAAW,kBAAkB,GAAG;AACtC,SAAO,IAAI,EAAE,MAAM,gBAAgB,SAAS,SAAS,CAAC;AACxD;AAEO,IAAM,wBAAwC;AAE9C,SAAS,eAAe;AAC7B,SAAO,IAAI;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,SACP,OACA,SACA,iBACwC;AACxC,SAAO;AAAA,IACL,GAAG;AAAA,IACH;AAAA,IACA,IAAI,QAAQ;AAAA,IACZ,MAAM,QAAQ;AAAA,IACd,cAAU,uBAAO;AAAA,IACjB;AAAA,IACA,UAAU,CAAC;AAAA,EACb;AACF;AAEO,SAAS,YAOd,OACA,MACA,iBACA,UAA+B,aAAa,GAC5C;AACA,SAAO,OACL,QAGG;AACH,WAAO,MAAM,KACV,QAAQ,SAAS,OAAO,SAAS,eAAe,GAAG,GAAG,EACtD,MAAM,cAAc;AAAA,EACzB;AACF;AAEO,SAAS,eAOd,OACA,MACA,MACA,iBACA,UAA+B,aAAa,GAC5C;AACA,QAAM,QAAQ,SAAoB,EAAE,YAAY,KAAK,CAAC;AACtD,QAAM,SAAS,SAAuC;AAAA,IACpD,YAAY;AAAA,EACd,CAAC;AAED,QAAM,YAAY;AAChB,QAAI,MAAM;AACR,YAAM,QAAQ;AACd,YAAM,MACH,QAAQ,SAAS,OAAO,SAAS,eAAe,GAAG,MAAM,OAAO,MAAM,EACtE,MAAM,CAAC,QAAiB,OAAO,KAAK,eAAe,GAAG,CAAC,CAAC;AAAA,IAC7D,OAAO;AACL,YAAM,QAAQ;AACd,YAAM,MACH,QAAQ,SAAS,OAAO,SAAS,eAAe,GAAG,OAAO,MAAM,EAChE,MAAM,CAAC,QAAiB,OAAO,KAAK,eAAe,GAAG,CAAC,CAAC;AAAA,IAC7D;AAAA,EACF,GAAG;AAEH,SAAO,CAAC,OAAO,MAAM;AACvB;AAEO,SAAS,qBAMd,OACA,MACA,iBACA,UAA+B,aAAa,GAC5C;AACA,QAAM,SAAS,SAAuC;AAAA,IACpD,YAAY;AAAA,EACd,CAAC;AAED,SAAO,CAAC,QAAmB;AACzB,UAAM,YAAY;AAChB,aAAO,MAAM,KACV,QAAQ,SAAS,OAAO,SAAS,eAAe,GAAG,KAAK,MAAM,EAC9D,MAAM,CAAC,QAAiB,OAAO,KAAK,eAAe,GAAG,CAAC,CAAC;AAAA,IAC7D,GAAG;AACH,WAAO;AAAA,EACT;AACF;AAEO,SAAS,eAOd,OACA,MACA,MACA,iBACA,UAA+B,aAAa,GAC5C;AACA,QAAM,QAAQ,SAAoB,EAAE,YAAY,KAAK,CAAC;AACtD,MAAI,MAAM;AACR,UAAM,QAAQ;AACd,UAAM,SAAS,MACZ,QAAQ,SAAS,OAAO,SAAS,eAAe,GAAG,MAAM,KAAK,EAC9D,MAAM,cAAc;AACvB,WAAO,CAAC,OAAO,MAAM;AAAA,EACvB,OAAO;AACL,UAAM,QAAQ;AACd,UAAM,SAAS,MACZ,QAAQ,SAAS,OAAO,SAAS,eAAe,GAAG,KAAK,EACxD,MAAM,cAAc;AACvB,WAAO,CAAC,OAAO,MAAM;AAAA,EACvB;AACF;AAEO,IAAM,oBAAoB,MAAM;AAErC,SAAO,QAAQ,aAAa,UACxB,oBAAgB,uBAAO,CAAC,KACxB,YAAQ,uBAAO,CAAC;AACtB;","names":["pushable","options","_pushable","import_nanoid","import_api","import_api","NodeWs"]}
|
|
1
|
+
{"version":3,"sources":["../../util/testHelpers.ts","../../node_modules/p-defer/index.js","../../node_modules/it-pushable/src/fifo.ts","../../node_modules/it-pushable/src/index.ts","../../router/result.ts","../../tracing/index.ts","../../package.json","../../util/stringify.ts","../../transport/session.ts","../../transport/transport.ts","../../codec/json.ts"],"sourcesContent":["import NodeWs, { WebSocketServer } from 'ws';\nimport http from 'node:http';\nimport { pushable } from 'it-pushable';\nimport {\n Err,\n PayloadType,\n Procedure,\n ServiceContext,\n ServiceContextWithTransportInfo,\n UNCAUGHT_ERROR,\n} from '../router';\nimport { RiverError, Result, RiverUncaughtSchema } from '../router/result';\nimport { Static } from '@sinclair/typebox';\nimport { nanoid } from 'nanoid';\nimport net from 'node:net';\nimport {\n OpaqueTransportMessage,\n PartialTransportMessage,\n} from '../transport/message';\nimport { coerceErrorString } from './stringify';\nimport { Connection, Session, SessionOptions } from '../transport/session';\nimport { Transport, defaultTransportOptions } from '../transport/transport';\nimport { WsLike } from '../transport/impls/ws/wslike';\n\n/**\n * Creates a WebSocket client that connects to a local server at the specified port.\n * This should only be used for testing.\n * @param port - The port number to connect to.\n * @returns A Promise that resolves to a WebSocket instance.\n */\nexport function createLocalWebSocketClient(port: number): WsLike {\n const sock = new NodeWs(`ws://localhost:${port}`);\n sock.binaryType = 'arraybuffer';\n\n return sock;\n}\n\n/**\n * Creates a WebSocket server instance using the provided HTTP server.\n * Only used as helper for testing.\n * @param server - The HTTP server instance to use for the WebSocket server.\n * @returns A Promise that resolves to the created WebSocket server instance.\n */\nexport function createWebSocketServer(server: http.Server) {\n return new WebSocketServer({ server });\n}\n\n/**\n * Starts listening on the given server and returns the automatically allocated port number.\n * This should only be used for testing.\n * @param server - The http server to listen on.\n * @returns A promise that resolves with the allocated port number.\n * @throws An error if a port cannot be allocated.\n */\nexport function onWsServerReady(server: http.Server): Promise<number> {\n return new Promise((resolve, reject) => {\n server.listen(() => {\n const addr = server.address();\n if (typeof addr === 'object' && addr) {\n resolve(addr.port);\n } else {\n reject(new Error(\"couldn't find a port to allocate\"));\n }\n });\n });\n}\n\nexport function onUdsServeReady(\n server: net.Server,\n path: string,\n): Promise<void> {\n return new Promise<void>((resolve) => {\n server.listen(path, resolve);\n });\n}\n\n/**\n * Retrieves the next value from an async iterable iterator.\n * @param iter The async iterable iterator.\n * @returns A promise that resolves to the next value from the iterator.\n */\nexport async function iterNext<T>(iter: AsyncIterableIterator<T>) {\n return await iter.next().then((res) => res.value as T);\n}\n\nexport function payloadToTransportMessage<Payload>(\n payload: Payload,\n): PartialTransportMessage<Payload> {\n return {\n streamId: 'stream',\n controlFlags: 0,\n payload,\n };\n}\n\nexport function createDummyTransportMessage() {\n return payloadToTransportMessage({\n msg: 'cool',\n test: Math.random(),\n });\n}\n\n/**\n * Waits for a message on the transport.\n * @param {Transport} t - The transport to listen to.\n * @param filter - An optional filter function to apply to the received messages.\n * @returns A promise that resolves with the payload of the first message that passes the filter.\n */\nexport async function waitForMessage(\n t: Transport<Connection>,\n filter?: (msg: OpaqueTransportMessage) => boolean,\n rejectMismatch?: boolean,\n) {\n return new Promise((resolve, reject) => {\n function cleanup() {\n t.removeEventListener('message', onMessage);\n }\n\n function onMessage(msg: OpaqueTransportMessage) {\n if (!filter || filter(msg)) {\n cleanup();\n resolve(msg.payload);\n } else if (rejectMismatch) {\n cleanup();\n reject(new Error('message didnt match the filter'));\n }\n }\n\n t.addEventListener('message', onMessage);\n });\n}\n\nfunction catchProcError(err: unknown) {\n const errorMsg = coerceErrorString(err);\n return Err({ code: UNCAUGHT_ERROR, message: errorMsg });\n}\n\nexport const testingSessionOptions: SessionOptions = defaultTransportOptions;\n\nexport function dummySession() {\n return new Session<Connection>(\n undefined,\n 'client',\n 'server',\n testingSessionOptions,\n );\n}\n\nfunction dummyCtx<State>(\n state: State,\n session: Session<Connection>,\n extendedContext?: Omit<ServiceContext, 'state'>,\n): ServiceContextWithTransportInfo<State> {\n return {\n ...extendedContext,\n state,\n to: session.to,\n from: session.from,\n streamId: nanoid(),\n session,\n metadata: {},\n };\n}\n\nexport function asClientRpc<\n State extends object,\n I extends PayloadType,\n O extends PayloadType,\n E extends RiverError,\n Init extends PayloadType | null = null,\n>(\n state: State,\n proc: Procedure<State, 'rpc', I, O, E, Init>,\n extendedContext?: Omit<ServiceContext, 'state'>,\n session: Session<Connection> = dummySession(),\n) {\n return async (\n msg: Static<I>,\n ): Promise<\n Result<Static<O>, Static<E> | Static<typeof RiverUncaughtSchema>>\n > => {\n return await proc\n .handler(dummyCtx(state, session, extendedContext), msg)\n .catch(catchProcError);\n };\n}\n\nexport function asClientStream<\n State extends object,\n I extends PayloadType,\n O extends PayloadType,\n E extends RiverError,\n Init extends PayloadType | null = null,\n>(\n state: State,\n proc: Procedure<State, 'stream', I, O, E, Init>,\n init?: Init extends PayloadType ? Static<Init> : null,\n extendedContext?: Omit<ServiceContext, 'state'>,\n session: Session<Connection> = dummySession(),\n) {\n const input = pushable<Static<I>>({ objectMode: true });\n const output = pushable<Result<Static<O>, Static<E>>>({\n objectMode: true,\n });\n\n void (async () => {\n if (init) {\n const _proc = proc as Procedure<State, 'stream', I, O, E, PayloadType>;\n await _proc\n .handler(dummyCtx(state, session, extendedContext), init, input, output)\n .catch((err: unknown) => output.push(catchProcError(err)));\n } else {\n const _proc = proc as Procedure<State, 'stream', I, O, E>;\n await _proc\n .handler(dummyCtx(state, session, extendedContext), input, output)\n .catch((err: unknown) => output.push(catchProcError(err)));\n }\n })();\n\n return [input, output] as const;\n}\n\nexport function asClientSubscription<\n State extends object,\n I extends PayloadType,\n O extends PayloadType,\n E extends RiverError,\n>(\n state: State,\n proc: Procedure<State, 'subscription', I, O, E>,\n extendedContext?: Omit<ServiceContext, 'state'>,\n session: Session<Connection> = dummySession(),\n) {\n const output = pushable<Result<Static<O>, Static<E>>>({\n objectMode: true,\n });\n\n return (msg: Static<I>) => {\n void (async () => {\n return await proc\n .handler(dummyCtx(state, session, extendedContext), msg, output)\n .catch((err: unknown) => output.push(catchProcError(err)));\n })();\n return output;\n };\n}\n\nexport function asClientUpload<\n State extends object,\n I extends PayloadType,\n O extends PayloadType,\n E extends RiverError,\n Init extends PayloadType | null = null,\n>(\n state: State,\n proc: Procedure<State, 'upload', I, O, E, Init>,\n init?: Init extends PayloadType ? Static<Init> : null,\n extendedContext?: Omit<ServiceContext, 'state'>,\n session: Session<Connection> = dummySession(),\n) {\n const input = pushable<Static<I>>({ objectMode: true });\n if (init) {\n const _proc = proc as Procedure<State, 'upload', I, O, E, PayloadType>;\n const result = _proc\n .handler(dummyCtx(state, session, extendedContext), init, input)\n .catch(catchProcError);\n return [input, result] as const;\n } else {\n const _proc = proc as Procedure<State, 'upload', I, O, E>;\n const result = _proc\n .handler(dummyCtx(state, session, extendedContext), input)\n .catch(catchProcError);\n return [input, result] as const;\n }\n}\n\nexport const getUnixSocketPath = () => {\n // https://nodejs.org/api/net.html#identifying-paths-for-ipc-connections\n return process.platform === 'win32'\n ? `\\\\\\\\?\\\\pipe\\\\${nanoid()}`\n : `/tmp/${nanoid()}.sock`;\n};\n","export default function pDefer() {\n\tconst deferred = {};\n\n\tdeferred.promise = new Promise((resolve, reject) => {\n\t\tdeferred.resolve = resolve;\n\t\tdeferred.reject = reject;\n\t});\n\n\treturn deferred;\n}\n","// ported from https://www.npmjs.com/package/fast-fifo\n\nexport interface Next<T> {\n done?: boolean\n error?: Error\n value?: T\n}\n\nclass FixedFIFO<T> {\n public buffer: Array<Next<T> | undefined>\n private readonly mask: number\n private top: number\n private btm: number\n public next: FixedFIFO<T> | null\n\n constructor (hwm: number) {\n if (!(hwm > 0) || ((hwm - 1) & hwm) !== 0) {\n throw new Error('Max size for a FixedFIFO should be a power of two')\n }\n\n this.buffer = new Array(hwm)\n this.mask = hwm - 1\n this.top = 0\n this.btm = 0\n this.next = null\n }\n\n push (data: Next<T>): boolean {\n if (this.buffer[this.top] !== undefined) {\n return false\n }\n\n this.buffer[this.top] = data\n this.top = (this.top + 1) & this.mask\n\n return true\n }\n\n shift (): Next<T> | undefined {\n const last = this.buffer[this.btm]\n\n if (last === undefined) {\n return undefined\n }\n\n this.buffer[this.btm] = undefined\n this.btm = (this.btm + 1) & this.mask\n return last\n }\n\n isEmpty (): boolean {\n return this.buffer[this.btm] === undefined\n }\n}\n\nexport interface FIFOOptions {\n /**\n * When the queue reaches this size, it will be split into head/tail parts\n */\n splitLimit?: number\n}\n\nexport class FIFO<T> {\n public size: number\n private readonly hwm: number\n private head: FixedFIFO<T>\n private tail: FixedFIFO<T>\n\n constructor (options: FIFOOptions = {}) {\n this.hwm = options.splitLimit ?? 16\n this.head = new FixedFIFO<T>(this.hwm)\n this.tail = this.head\n this.size = 0\n }\n\n calculateSize (obj: any): number {\n if (obj?.byteLength != null) {\n return obj.byteLength\n }\n\n return 1\n }\n\n push (val: Next<T>): void {\n if (val?.value != null) {\n this.size += this.calculateSize(val.value)\n }\n\n if (!this.head.push(val)) {\n const prev = this.head\n this.head = prev.next = new FixedFIFO<T>(2 * this.head.buffer.length)\n this.head.push(val)\n }\n }\n\n shift (): Next<T> | undefined {\n let val = this.tail.shift()\n\n if (val === undefined && (this.tail.next != null)) {\n const next = this.tail.next\n this.tail.next = null\n this.tail = next\n val = this.tail.shift()\n }\n\n if (val?.value != null) {\n this.size -= this.calculateSize(val.value)\n }\n\n return val\n }\n\n isEmpty (): boolean {\n return this.head.isEmpty()\n }\n}\n","/**\n * @packageDocumentation\n *\n * An iterable that you can push values into.\n *\n * @example\n *\n * ```js\n * import { pushable } from 'it-pushable'\n *\n * const source = pushable()\n *\n * setTimeout(() => source.push('hello'), 100)\n * setTimeout(() => source.push('world'), 200)\n * setTimeout(() => source.end(), 300)\n *\n * const start = Date.now()\n *\n * for await (const value of source) {\n * console.log(`got \"${value}\" after ${Date.now() - start}ms`)\n * }\n * console.log(`done after ${Date.now() - start}ms`)\n *\n * // Output:\n * // got \"hello\" after 105ms\n * // got \"world\" after 207ms\n * // done after 309ms\n * ```\n *\n * @example\n *\n * ```js\n * import { pushableV } from 'it-pushable'\n * import all from 'it-all'\n *\n * const source = pushableV()\n *\n * source.push(1)\n * source.push(2)\n * source.push(3)\n * source.end()\n *\n * console.info(await all(source))\n *\n * // Output:\n * // [ [1, 2, 3] ]\n * ```\n */\n\nimport deferred from 'p-defer'\nimport { FIFO, type Next } from './fifo.js'\n\nexport class AbortError extends Error {\n type: string\n code: string\n\n constructor (message?: string, code?: string) {\n super(message ?? 'The operation was aborted')\n this.type = 'aborted'\n this.code = code ?? 'ABORT_ERR'\n }\n}\n\nexport interface AbortOptions {\n signal?: AbortSignal\n}\n\ninterface BasePushable<T> {\n /**\n * End the iterable after all values in the buffer (if any) have been yielded. If an\n * error is passed the buffer is cleared immediately and the next iteration will\n * throw the passed error\n */\n end(err?: Error): this\n\n /**\n * Push a value into the iterable. Values are yielded from the iterable in the order\n * they are pushed. Values not yet consumed from the iterable are buffered.\n */\n push(value: T): this\n\n /**\n * Returns a promise that resolves when the underlying queue becomes empty (e.g.\n * this.readableLength === 0).\n *\n * If an AbortSignal is passed as an option and that signal aborts, it only\n * causes the returned promise to reject - it does not end the pushable.\n */\n onEmpty(options?: AbortOptions): Promise<void>\n\n /**\n * This property contains the number of bytes (or objects) in the queue ready to be read.\n *\n * If `objectMode` is true, this is the number of objects in the queue, if false it's the\n * total number of bytes in the queue.\n */\n readableLength: number\n}\n\n/**\n * An iterable that you can push values into.\n */\nexport interface Pushable<T, R = void, N = unknown> extends AsyncGenerator<T, R, N>, BasePushable<T> {}\n\n/**\n * Similar to `pushable`, except it yields multiple buffered chunks at a time. All values yielded from the iterable will be arrays.\n */\nexport interface PushableV<T, R = void, N = unknown> extends AsyncGenerator<T[], R, N>, BasePushable<T> {}\n\nexport interface Options {\n /**\n * A boolean value that means non-`Uint8Array`s will be passed to `.push`, default: `false`\n */\n objectMode?: boolean\n\n /**\n * A function called after *all* values have been yielded from the iterator (including\n * buffered values). In the case when the iterator is ended with an error it will be\n * passed the error as a parameter.\n */\n onEnd?(err?: Error): void\n}\n\nexport interface DoneResult { done: true }\nexport interface ValueResult<T> { done: false, value: T }\nexport type NextResult<T> = ValueResult<T> | DoneResult\n\ninterface getNext<T, V = T> { (buffer: FIFO<T>): NextResult<V> }\n\nexport interface ObjectPushableOptions extends Options {\n objectMode: true\n}\n\nexport interface BytePushableOptions extends Options {\n objectMode?: false\n}\n\n/**\n * Create a new async iterable. The values yielded from calls to `.next()`\n * or when used in a `for await of`loop are \"pushed\" into the iterable.\n * Returns an async iterable object with additional methods.\n */\nexport function pushable<T extends { byteLength: number } = Uint8Array> (options?: BytePushableOptions): Pushable<T>\nexport function pushable<T> (options: ObjectPushableOptions): Pushable<T>\nexport function pushable<T> (options: Options = {}): Pushable<T> {\n const getNext = (buffer: FIFO<T>): NextResult<T> => {\n const next: Next<T> | undefined = buffer.shift()\n\n if (next == null) {\n return { done: true }\n }\n\n if (next.error != null) {\n throw next.error\n }\n\n return {\n done: next.done === true,\n // @ts-expect-error if done is false, value will be present\n value: next.value\n }\n }\n\n return _pushable<T, T, Pushable<T>>(getNext, options)\n}\n\nexport function pushableV<T extends { byteLength: number } = Uint8Array> (options?: BytePushableOptions): PushableV<T>\nexport function pushableV<T> (options: ObjectPushableOptions): PushableV<T>\nexport function pushableV<T> (options: Options = {}): PushableV<T> {\n const getNext = (buffer: FIFO<T>): NextResult<T[]> => {\n let next: Next<T> | undefined\n const values: T[] = []\n\n while (!buffer.isEmpty()) {\n next = buffer.shift()\n\n if (next == null) {\n break\n }\n\n if (next.error != null) {\n throw next.error\n }\n\n if (next.done === false) {\n // @ts-expect-error if done is false value should be pushed\n values.push(next.value)\n }\n }\n\n if (next == null) {\n return { done: true }\n }\n\n return {\n done: next.done === true,\n value: values\n }\n }\n\n return _pushable<T, T[], PushableV<T>>(getNext, options)\n}\n\nfunction _pushable<PushType, ValueType, ReturnType> (getNext: getNext<PushType, ValueType>, options?: Options): ReturnType {\n options = options ?? {}\n let onEnd = options.onEnd\n let buffer = new FIFO<PushType>()\n let pushable: any\n let onNext: ((next: Next<PushType>) => ReturnType) | null\n let ended: boolean\n let drain = deferred()\n\n const waitNext = async (): Promise<NextResult<ValueType>> => {\n try {\n if (!buffer.isEmpty()) {\n return getNext(buffer)\n }\n\n if (ended) {\n return { done: true }\n }\n\n return await new Promise<NextResult<ValueType>>((resolve, reject) => {\n onNext = (next: Next<PushType>) => {\n onNext = null\n buffer.push(next)\n\n try {\n resolve(getNext(buffer))\n } catch (err) {\n reject(err)\n }\n\n return pushable\n }\n })\n } finally {\n if (buffer.isEmpty()) {\n // settle promise in the microtask queue to give consumers a chance to\n // await after calling .push\n queueMicrotask(() => {\n drain.resolve()\n drain = deferred()\n })\n }\n }\n }\n\n const bufferNext = (next: Next<PushType>): ReturnType => {\n if (onNext != null) {\n return onNext(next)\n }\n\n buffer.push(next)\n return pushable\n }\n\n const bufferError = (err: Error): ReturnType => {\n buffer = new FIFO()\n\n if (onNext != null) {\n return onNext({ error: err })\n }\n\n buffer.push({ error: err })\n return pushable\n }\n\n const push = (value: PushType): ReturnType => {\n if (ended) {\n return pushable\n }\n\n // @ts-expect-error `byteLength` is not declared on PushType\n if (options?.objectMode !== true && value?.byteLength == null) {\n throw new Error('objectMode was not true but tried to push non-Uint8Array value')\n }\n\n return bufferNext({ done: false, value })\n }\n const end = (err?: Error): ReturnType => {\n if (ended) return pushable\n ended = true\n\n return (err != null) ? bufferError(err) : bufferNext({ done: true })\n }\n const _return = (): DoneResult => {\n buffer = new FIFO()\n end()\n\n return { done: true }\n }\n const _throw = (err: Error): DoneResult => {\n end(err)\n\n return { done: true }\n }\n\n pushable = {\n [Symbol.asyncIterator] () { return this },\n next: waitNext,\n return: _return,\n throw: _throw,\n push,\n end,\n get readableLength (): number {\n return buffer.size\n },\n onEmpty: async (options?: AbortOptions) => {\n const signal = options?.signal\n signal?.throwIfAborted()\n\n if (buffer.isEmpty()) {\n return\n }\n\n let cancel: Promise<void> | undefined\n let listener: (() => void) | undefined\n\n if (signal != null) {\n cancel = new Promise((resolve, reject) => {\n listener = () => {\n reject(new AbortError())\n }\n\n signal.addEventListener('abort', listener)\n })\n }\n\n try {\n await Promise.race([\n drain.promise,\n cancel\n ])\n } finally {\n if (listener != null && signal != null) {\n signal?.removeEventListener('abort', listener)\n }\n }\n }\n }\n\n if (onEnd == null) {\n return pushable\n }\n\n const _pushable = pushable\n\n pushable = {\n [Symbol.asyncIterator] () { return this },\n next () {\n return _pushable.next()\n },\n throw (err: Error) {\n _pushable.throw(err)\n\n if (onEnd != null) {\n onEnd(err)\n onEnd = undefined\n }\n\n return { done: true }\n },\n return () {\n _pushable.return()\n\n if (onEnd != null) {\n onEnd()\n onEnd = undefined\n }\n\n return { done: true }\n },\n push,\n end (err: Error) {\n _pushable.end(err)\n\n if (onEnd != null) {\n onEnd(err)\n onEnd = undefined\n }\n\n return pushable\n },\n get readableLength () {\n return _pushable.readableLength\n },\n onEmpty: (opts?: AbortOptions) => {\n return _pushable.onEmpty(opts)\n }\n }\n\n return pushable\n}\n","import {\n TLiteral,\n TNever,\n TObject,\n TSchema,\n TString,\n TUnion,\n Type,\n} from '@sinclair/typebox';\nimport { Client } from './client';\n\ntype TLiteralString = TLiteral<string>;\n\nexport type RiverErrorSchema =\n | TObject<{\n code: TLiteralString | TUnion<Array<TLiteralString>>;\n message: TLiteralString | TString;\n }>\n | TObject<{\n code: TLiteralString | TUnion<Array<TLiteralString>>;\n message: TLiteralString | TString;\n extras: TSchema;\n }>;\n\nexport type RiverError =\n | TUnion<Array<RiverErrorSchema>>\n | RiverErrorSchema\n | TNever;\n\nexport const UNCAUGHT_ERROR = 'UNCAUGHT_ERROR';\nexport const UNEXPECTED_DISCONNECT = 'UNEXPECTED_DISCONNECT';\nexport const RiverUncaughtSchema = Type.Object({\n code: Type.Union([\n Type.Literal(UNCAUGHT_ERROR),\n Type.Literal(UNEXPECTED_DISCONNECT),\n ]),\n message: Type.String(),\n});\n\nexport type Result<T, E> =\n | {\n ok: true;\n payload: T;\n }\n | {\n ok: false;\n payload: E;\n };\n\nexport function Ok<const T extends Array<unknown>, const E>(p: T): Result<T, E>;\nexport function Ok<const T extends ReadonlyArray<unknown>, const E>(\n p: T,\n): Result<T, E>;\nexport function Ok<const T, const E>(payload: T): Result<T, E>;\nexport function Ok<const T, const E>(payload: T): Result<T, E> {\n return {\n ok: true,\n payload,\n };\n}\n\nexport function Err<const T, const E>(error: E): Result<T, E> {\n return {\n ok: false,\n payload: error,\n };\n}\n\n/**\n * Refine a {@link Result} type to its returned payload.\n */\nexport type ResultUnwrapOk<R> = R extends Result<infer T, infer __E>\n ? T\n : never;\n\n/**\n * Refine a {@link Result} type to its error payload.\n */\nexport type ResultUnwrapErr<R> = R extends Result<infer __T, infer E>\n ? E\n : never;\n\n/**\n * Retrieve the output type for a procedure, represented as a {@link Result}\n * type.\n * Example:\n * ```\n * type Message = Output<typeof client, 'serviceName', 'procedureName'>\n * ```\n */\nexport type Output<\n RiverClient,\n ServiceName extends keyof RiverClient,\n ProcedureName extends keyof RiverClient[ServiceName],\n Procedure = RiverClient[ServiceName][ProcedureName],\n Fn extends (...args: never) => unknown = (...args: never) => unknown,\n> = RiverClient extends Client<infer __ServiceSchemaMap>\n ? Procedure extends object\n ? Procedure extends object & { rpc: infer RpcHandler extends Fn }\n ? Awaited<ReturnType<RpcHandler>>\n : Procedure extends object & { upload: infer UploadHandler extends Fn }\n ? Awaited<ReturnType<UploadHandler>> extends [\n infer __UploadInputMessage,\n infer UploadOutputMessage,\n ]\n ? Awaited<UploadOutputMessage>\n : never\n : Procedure extends object & { stream: infer StreamHandler extends Fn }\n ? Awaited<ReturnType<StreamHandler>> extends [\n infer __StreamInputMessage,\n AsyncGenerator<infer StreamOutputMessage>,\n infer __StreamCloseHandle,\n ]\n ? StreamOutputMessage\n : never\n : Procedure extends object & {\n subscribe: infer SubscriptionHandler extends Fn;\n }\n ? Awaited<ReturnType<SubscriptionHandler>> extends [\n AsyncGenerator<infer SubscriptionOutputMessage>,\n infer __SubscriptionCloseHandle,\n ]\n ? SubscriptionOutputMessage\n : never\n : never\n : never\n : never;\n","import {\n Context,\n Span,\n SpanKind,\n context,\n propagation,\n trace,\n} from '@opentelemetry/api';\nimport { version as RIVER_VERSION } from '../package.json';\nimport { ValidProcType } from '../router';\nimport {\n ClientTransport,\n Connection,\n OpaqueTransportMessage,\n Session,\n} from '../transport';\n\nexport interface PropagationContext {\n traceparent: string;\n tracestate: string;\n}\n\nexport interface TelemetryInfo {\n span: Span;\n ctx: Context;\n}\n\nexport function getPropagationContext(\n ctx: Context,\n): PropagationContext | undefined {\n const tracing = {\n traceparent: '',\n tracestate: '',\n };\n propagation.inject(ctx, tracing);\n return tracing;\n}\n\nexport function createSessionTelemetryInfo(\n session: Session<Connection>,\n propagationCtx?: PropagationContext,\n): TelemetryInfo {\n const parentCtx = propagationCtx\n ? propagation.extract(context.active(), propagationCtx)\n : context.active();\n\n const span = tracer.startSpan(\n `session ${session.id}`,\n {\n attributes: {\n component: 'river',\n 'river.session.id': session.id,\n 'river.session.to': session.to,\n 'river.session.from': session.from,\n },\n },\n parentCtx,\n );\n\n const ctx = trace.setSpan(parentCtx, span);\n\n return { span, ctx };\n}\n\nexport function createConnectionTelemetryInfo(\n connection: Connection,\n info: TelemetryInfo,\n): TelemetryInfo {\n const span = tracer.startSpan(\n `connection ${connection.id}`,\n {\n attributes: {\n component: 'river',\n 'river.connection.id': connection.id,\n },\n links: [{ context: info.span.spanContext() }],\n },\n info.ctx,\n );\n\n const ctx = trace.setSpan(info.ctx, span);\n\n return { span, ctx };\n}\n\nexport function createProcTelemetryInfo(\n transport: ClientTransport<Connection>,\n kind: ValidProcType,\n serviceName: string,\n procedureName: string,\n streamId: string,\n): TelemetryInfo {\n const baseCtx = context.active();\n const span = tracer.startSpan(\n `procedure call ${serviceName}.${procedureName}`,\n {\n attributes: {\n component: 'river',\n 'river.method.kind': kind,\n 'river.method.service': serviceName,\n 'river.method.name': procedureName,\n 'river.streamId': streamId,\n 'span.kind': 'client',\n },\n kind: SpanKind.CLIENT,\n },\n baseCtx,\n );\n\n const ctx = trace.setSpan(baseCtx, span);\n\n transport.log?.info(`invoked ${serviceName}.${procedureName}`, {\n clientId: transport.clientId,\n transportMessage: {\n procedureName,\n serviceName,\n },\n telemetry: {\n traceId: span.spanContext().traceId,\n spanId: span.spanContext().spanId,\n },\n });\n return { span, ctx };\n}\n\nexport function createHandlerSpan(\n kind: ValidProcType,\n message: OpaqueTransportMessage,\n fn: (span: Span) => Promise<unknown>,\n) {\n const ctx = message.tracing\n ? propagation.extract(context.active(), message.tracing)\n : context.active();\n\n return tracer.startActiveSpan(\n `procedure handler ${message.serviceName}.${message.procedureName}`,\n {\n attributes: {\n component: 'river',\n 'river.method.kind': kind,\n 'river.method.service': message.serviceName,\n 'river.method.name': message.procedureName,\n 'river.streamId': message.streamId,\n 'span.kind': 'server',\n },\n kind: SpanKind.SERVER,\n },\n ctx,\n fn,\n );\n}\n\nconst tracer = trace.getTracer('river', RIVER_VERSION);\nexport default tracer;\n","{\n \"name\": \"@replit/river\",\n \"description\": \"It's like tRPC but... with JSON Schema Support, duplex streaming and support for service multiplexing. Transport agnostic!\",\n \"version\": \"0.23.10\",\n \"type\": \"module\",\n \"exports\": {\n \".\": {\n \"import\": \"./dist/router/index.js\",\n \"require\": \"./dist/router/index.cjs\"\n },\n \"./logging\": {\n \"import\": \"./dist/logging/index.js\",\n \"require\": \"./dist/logging/index.cjs\"\n },\n \"./codec\": {\n \"import\": \"./dist/codec/index.js\",\n \"require\": \"./dist/codec/index.cjs\"\n },\n \"./transport\": {\n \"import\": \"./dist/transport/index.js\",\n \"require\": \"./dist/transport/index.cjs\"\n },\n \"./transport/ws/client\": {\n \"import\": \"./dist/transport/impls/ws/client.js\",\n \"require\": \"./dist/transport/impls/ws/client.cjs\"\n },\n \"./transport/ws/server\": {\n \"import\": \"./dist/transport/impls/ws/server.js\",\n \"require\": \"./dist/transport/impls/ws/server.cjs\"\n },\n \"./transport/uds/client\": {\n \"import\": \"./dist/transport/impls/uds/client.js\",\n \"require\": \"./dist/transport/impls/uds/client.cjs\"\n },\n \"./transport/uds/server\": {\n \"import\": \"./dist/transport/impls/uds/server.js\",\n \"require\": \"./dist/transport/impls/uds/server.cjs\"\n },\n \"./test-util\": {\n \"import\": \"./dist/util/testHelpers.js\",\n \"require\": \"./dist/util/testHelpers.cjs\"\n }\n },\n \"sideEffects\": [\n \"./dist/logging/index.js\"\n ],\n \"files\": [\n \"dist\"\n ],\n \"dependencies\": {\n \"@msgpack/msgpack\": \"^3.0.0-beta2\",\n \"it-pushable\": \"^3.2.3\",\n \"nanoid\": \"^4.0.2\",\n \"ws\": \"^8.17.0\"\n },\n \"peerDependencies\": {\n \"@opentelemetry/api\": \"^1.7.0\",\n \"@sinclair/typebox\": \"~0.32.8\"\n },\n \"devDependencies\": {\n \"@opentelemetry/sdk-trace-base\": \"^1.24.1\",\n \"@opentelemetry/sdk-trace-web\": \"^1.24.1\",\n \"@opentelemetry/core\": \"^1.7.0\",\n \"@types/ws\": \"^8.5.5\",\n \"@typescript-eslint/eslint-plugin\": \"^7.8.0\",\n \"@typescript-eslint/parser\": \"^7.8.0\",\n \"@vitest/ui\": \"^1.3.1\",\n \"eslint\": \"^8.57.0\",\n \"eslint-config-prettier\": \"^9.1.0\",\n \"eslint-plugin-prettier\": \"^5.1.3\",\n \"prettier\": \"^3.0.0\",\n \"tsup\": \"^7.2.0\",\n \"typescript\": \"^5.4.5\",\n \"vitest\": \"^1.3.1\"\n },\n \"scripts\": {\n \"check\": \"tsc --noEmit && npm run format && npm run lint\",\n \"format\": \"npx prettier . --check\",\n \"format:fix\": \"npx prettier . --write\",\n \"lint\": \"eslint .\",\n \"lint:fix\": \"eslint . --fix\",\n \"fix\": \"npm run format:fix && npm run lint:fix\",\n \"build\": \"rm -rf dist && tsup && du -sh dist\",\n \"prepack\": \"npm run build\",\n \"release\": \"npm publish --access public\",\n \"test:ui\": \"echo \\\"remember to go to /__vitest__ in the webview\\\" && vitest --ui --api.host 0.0.0.0 --api.port 3000\",\n \"test\": \"vitest\",\n \"test:single\": \"vitest run --reporter=dot\",\n \"test:flake\": \"./flake.sh\",\n \"bench\": \"vitest bench\"\n },\n \"engines\": {\n \"node\": \">=16\"\n },\n \"keywords\": [\n \"rpc\",\n \"websockets\",\n \"jsonschema\"\n ],\n \"author\": \"Jacky Zhao\",\n \"license\": \"MIT\"\n}\n","export function coerceErrorString(err: unknown): string {\n if (err instanceof Error) {\n return err.message || 'unknown reason';\n }\n\n return `[coerced to error] ${String(err)}`;\n}\n","import { customAlphabet } from 'nanoid';\nimport {\n ControlFlags,\n ControlMessageAckSchema,\n OpaqueTransportMessage,\n PartialTransportMessage,\n TransportClientId,\n TransportMessage,\n} from './message';\nimport { Codec } from '../codec';\nimport { Logger, MessageMetadata } from '../logging/log';\nimport { Static } from '@sinclair/typebox';\nimport {\n PropagationContext,\n TelemetryInfo,\n createSessionTelemetryInfo,\n} from '../tracing';\nimport { SpanStatusCode } from '@opentelemetry/api';\n\nconst nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvxyz', 6);\nexport const unsafeId = () => nanoid();\n\ntype SequenceNumber = number;\n\n/**\n * A connection is the actual raw underlying transport connection.\n * It’s responsible for dispatching to/from the actual connection itself\n * This should be instantiated as soon as the client/server has a connection\n * It’s tied to the lifecycle of the underlying transport connection (i.e. if the WS drops, this connection should be deleted)\n */\nexport abstract class Connection {\n id: string;\n telemetry?: TelemetryInfo;\n constructor() {\n this.id = `conn-${nanoid(12)}`; // for debugging, no collision safety needed\n }\n\n get loggingMetadata(): MessageMetadata {\n const metadata: MessageMetadata = { connId: this.id };\n const spanContext = this.telemetry?.span.spanContext();\n\n if (this.telemetry?.span.isRecording() && spanContext) {\n metadata.telemetry = {\n traceId: spanContext.traceId,\n spanId: spanContext.spanId,\n };\n }\n\n return metadata;\n }\n\n /**\n * Handle adding a callback for when a message is received.\n * @param msg The message that was received.\n */\n abstract addDataListener(cb: (msg: Uint8Array) => void): void;\n abstract removeDataListener(cb: (msg: Uint8Array) => void): void;\n\n /**\n * Handle adding a callback for when the connection is closed.\n * This should also be called if an error happens.\n * @param cb The callback to call when the connection is closed.\n */\n abstract addCloseListener(cb: () => void): void;\n\n /**\n * Handle adding a callback for when an error is received.\n * This should only be used for this.logging errors, all cleanup\n * should be delegated to addCloseListener.\n *\n * The implementer should take care such that the implemented\n * connection will call both the close and error callbacks\n * on an error.\n *\n * @param cb The callback to call when an error is received.\n */\n abstract addErrorListener(cb: (err: Error) => void): void;\n\n /**\n * Sends a message over the connection.\n * @param msg The message to send.\n * @returns true if the message was sent, false otherwise.\n */\n abstract send(msg: Uint8Array): boolean;\n\n /**\n * Closes the connection.\n */\n abstract close(): void;\n}\n\nexport interface SessionOptions {\n /**\n * Frequency at which to send heartbeat acknowledgements\n */\n heartbeatIntervalMs: number;\n /**\n * Number of elapsed heartbeats without a response message before we consider\n * the connection dead.\n */\n heartbeatsUntilDead: number;\n /**\n * Duration to wait between connection disconnect and actual session disconnect\n */\n sessionDisconnectGraceMs: number;\n /**\n * The codec to use for encoding/decoding messages over the wire\n */\n codec: Codec;\n}\n\n/**\n * A session is a higher-level abstraction that operates over the span of potentially multiple transport-level connections\n * - It’s responsible for tracking any metadata for a particular client that might need to be persisted across connections (i.e. the sendBuffer, ack, seq)\n * - This will only be considered disconnected if\n * - the server tells the client that we’ve reconnected but it doesn’t recognize us anymore (server definitely died) or\n * - we hit a grace period after a connection disconnect\n */\nexport class Session<ConnType extends Connection> {\n private codec: Codec;\n private options: SessionOptions;\n readonly telemetry: TelemetryInfo;\n\n /**\n * The buffer of messages that have been sent but not yet acknowledged.\n */\n private sendBuffer: Array<OpaqueTransportMessage> = [];\n\n /**\n * The active connection associated with this session\n */\n connection?: ConnType;\n /**\n * A connection that is currently undergoing handshaking. Used to distinguish between the active\n * connection, but still be able to close it if needed.\n */\n private handshakingConnection?: ConnType;\n readonly from: TransportClientId;\n readonly to: TransportClientId;\n\n /**\n * The unique ID of this session.\n */\n readonly id: string;\n\n /**\n * What the other side advertised as their session ID\n * for this session.\n */\n advertisedSessionId?: string;\n\n /**\n * Number of messages we've sent along this session (excluding handshake and acks)\n */\n private seq: SequenceNumber = 0;\n\n /**\n * Number of unique messages we've received this session (excluding handshake and acks)\n */\n private ack: SequenceNumber = 0;\n\n /**\n * The grace period between when the inner connection is disconnected\n * and when we should consider the entire session disconnected.\n */\n private disconnectionGrace?: ReturnType<typeof setTimeout>;\n\n /**\n * Number of heartbeats we've sent without a response.\n */\n private heartbeatMisses: number;\n\n /**\n * The interval for sending heartbeats.\n */\n private heartbeat: ReturnType<typeof setInterval>;\n private log?: Logger;\n\n constructor(\n conn: ConnType | undefined,\n from: TransportClientId,\n to: TransportClientId,\n options: SessionOptions,\n propagationCtx?: PropagationContext,\n ) {\n this.id = `session-${nanoid(12)}`;\n this.options = options;\n this.from = from;\n this.to = to;\n this.connection = conn;\n this.codec = options.codec;\n\n // setup heartbeat\n this.heartbeatMisses = 0;\n this.heartbeat = setInterval(\n () => this.sendHeartbeat(),\n options.heartbeatIntervalMs,\n );\n this.telemetry = createSessionTelemetryInfo(this, propagationCtx);\n }\n\n bindLogger(log: Logger) {\n this.log = log;\n }\n\n get loggingMetadata(): MessageMetadata {\n const spanContext = this.telemetry.span.spanContext();\n\n return {\n clientId: this.from,\n connectedTo: this.to,\n sessionId: this.id,\n connId: this.connection?.id,\n telemetry: {\n traceId: spanContext.traceId,\n spanId: spanContext.spanId,\n },\n };\n }\n\n /**\n * Sends a message over the session's connection.\n * If the connection is not ready or the message fails to send, the message can be buffered for retry unless skipped.\n *\n * @param msg The partial message to be sent, which will be constructed into a full message.\n * @param addToSendBuff Whether to add the message to the send buffer for retry.\n * @returns The full transport ID of the message that was attempted to be sent.\n */\n send(msg: PartialTransportMessage): string {\n const fullMsg: TransportMessage = this.constructMsg(msg);\n this.log?.debug(`sending msg`, {\n ...this.loggingMetadata,\n transportMessage: fullMsg,\n });\n\n if (this.connection) {\n const ok = this.connection.send(this.codec.toBuffer(fullMsg));\n if (ok) return fullMsg.id;\n this.log?.info(\n `failed to send msg to ${fullMsg.to}, connection is probably dead`,\n {\n ...this.loggingMetadata,\n transportMessage: fullMsg,\n },\n );\n } else {\n this.log?.debug(\n `buffering msg to ${fullMsg.to}, connection not ready yet`,\n { ...this.loggingMetadata, transportMessage: fullMsg },\n );\n }\n\n return fullMsg.id;\n }\n\n sendHeartbeat() {\n const misses = this.heartbeatMisses;\n const missDuration = misses * this.options.heartbeatIntervalMs;\n if (misses > this.options.heartbeatsUntilDead) {\n if (this.connection) {\n this.log?.info(\n `closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,\n this.loggingMetadata,\n );\n this.telemetry.span.addEvent('closing connection due to inactivity');\n this.closeStaleConnection();\n }\n return;\n }\n\n this.send({\n streamId: 'heartbeat',\n controlFlags: ControlFlags.AckBit,\n payload: {\n type: 'ACK',\n } satisfies Static<typeof ControlMessageAckSchema>,\n });\n this.heartbeatMisses++;\n }\n\n resetBufferedMessages() {\n this.sendBuffer = [];\n this.seq = 0;\n this.ack = 0;\n }\n\n sendBufferedMessages(conn: ConnType) {\n this.log?.info(`resending ${this.sendBuffer.length} buffered messages`, {\n ...this.loggingMetadata,\n connId: conn.id,\n });\n for (const msg of this.sendBuffer) {\n this.log?.debug(`resending msg`, {\n ...this.loggingMetadata,\n transportMessage: msg,\n connId: conn.id,\n });\n const ok = conn.send(this.codec.toBuffer(msg));\n if (!ok) {\n // this should never happen unless the transport has an\n // incorrect implementation of `createNewOutgoingConnection`\n const errMsg = `failed to send buffered message to ${this.to} (sus, this is a fresh connection)`;\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: errMsg,\n });\n\n this.log?.error(errMsg, {\n ...this.loggingMetadata,\n transportMessage: msg,\n connId: conn.id,\n tags: ['invariant-violation'],\n });\n conn.close();\n return;\n }\n }\n }\n\n updateBookkeeping(ack: number, seq: number) {\n if (seq + 1 < this.ack) {\n this.log?.error(`received stale seq ${seq} + 1 < ${this.ack}`, {\n ...this.loggingMetadata,\n tags: ['invariant-violation'],\n });\n return;\n }\n\n this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);\n this.ack = seq + 1;\n }\n\n private closeStaleConnection(conn?: ConnType) {\n if (this.connection === undefined || this.connection === conn) return;\n this.log?.info(\n `closing old inner connection from session to ${this.to}`,\n this.loggingMetadata,\n );\n this.connection.close();\n this.connection = undefined;\n }\n\n replaceWithNewConnection(newConn: ConnType) {\n this.closeStaleConnection(newConn);\n this.cancelGrace();\n this.sendBufferedMessages(newConn);\n this.connection = newConn;\n // we only call replaceWithNewConnection after\n // having successfully completed a handshake so we clear\n // it here\n this.handshakingConnection = undefined;\n }\n\n replaceWithNewHandshakingConnection(newConn: ConnType) {\n this.handshakingConnection = newConn;\n }\n\n beginGrace(cb: () => void) {\n this.log?.info(\n `starting ${this.options.sessionDisconnectGraceMs}ms grace period until session to ${this.to} is closed`,\n this.loggingMetadata,\n );\n // Replace any old timeouts to prevent this from firing twice.\n this.cancelGrace({ keepHeartbeatMisses: true });\n this.disconnectionGrace = setTimeout(() => {\n if (this.connection !== undefined) {\n this.log?.warn(\n `grace period for ${this.to} elapsed while connected. not calling callback`,\n {\n ...this.loggingMetadata,\n connId: this.connection.id,\n tags: ['invariant-violation'],\n },\n );\n return;\n }\n this.log?.info(\n `grace period for ${this.to} elapsed`,\n this.loggingMetadata,\n );\n cb();\n }, this.options.sessionDisconnectGraceMs);\n }\n\n // called on reconnect of the underlying session\n cancelGrace(\n { keepHeartbeatMisses }: { keepHeartbeatMisses: boolean } = {\n keepHeartbeatMisses: false,\n },\n ) {\n if (!keepHeartbeatMisses) {\n this.heartbeatMisses = 0;\n }\n if (this.disconnectionGrace === undefined) return;\n clearTimeout(this.disconnectionGrace);\n this.disconnectionGrace = undefined;\n }\n\n /**\n * Used to close the handshaking connection, if set.\n */\n closeHandshakingConnection(expectedHandshakingConn?: ConnType) {\n if (this.handshakingConnection === undefined) return;\n if (\n expectedHandshakingConn !== undefined &&\n this.handshakingConnection === expectedHandshakingConn\n ) {\n // If the handshaking connection is the expected one, don't close it.\n return;\n }\n this.handshakingConnection.close();\n this.handshakingConnection = undefined;\n }\n\n // closed when we want to discard the whole session\n // (i.e. shutdown or session disconnect)\n close() {\n this.closeStaleConnection();\n this.cancelGrace();\n this.resetBufferedMessages();\n clearInterval(this.heartbeat);\n }\n\n get connected() {\n return this.connection !== undefined;\n }\n\n get nextExpectedSeq() {\n return this.ack;\n }\n\n constructMsg<Payload>(\n partialMsg: PartialTransportMessage<Payload>,\n ): TransportMessage<Payload> {\n const msg = {\n ...partialMsg,\n id: unsafeId(),\n to: this.to,\n from: this.from,\n seq: this.seq,\n ack: this.ack,\n };\n\n this.seq++;\n this.sendBuffer.push(msg);\n return msg;\n }\n\n inspectSendBuffer(): ReadonlyArray<OpaqueTransportMessage> {\n return this.sendBuffer;\n }\n}\n","import { Codec } from '../codec/types';\nimport { Value } from '@sinclair/typebox/value';\nimport {\n OpaqueTransportMessage,\n OpaqueTransportMessageSchema,\n TransportClientId,\n ControlMessageHandshakeRequestSchema,\n ControlMessageHandshakeResponseSchema,\n handshakeRequestMessage,\n handshakeResponseMessage,\n PartialTransportMessage,\n ControlFlags,\n ControlMessagePayloadSchema,\n isAck,\n PROTOCOL_VERSION,\n} from './message';\nimport {\n BaseLogger,\n LogFn,\n Logger,\n LoggingLevel,\n createLogProxy,\n} from '../logging/log';\nimport {\n EventDispatcher,\n EventHandler,\n EventTypes,\n ProtocolError,\n ProtocolErrorType,\n} from './events';\nimport { Connection, Session, SessionOptions } from './session';\nimport { Static } from '@sinclair/typebox';\nimport { coerceErrorString } from '../util/stringify';\nimport { ConnectionRetryOptions, LeakyBucketRateLimit } from './rateLimit';\nimport { NaiveJsonCodec } from '../codec';\nimport tracer, {\n PropagationContext,\n createConnectionTelemetryInfo,\n getPropagationContext,\n} from '../tracing';\nimport { SpanStatusCode } from '@opentelemetry/api';\nimport { ParsedMetadata } from '../router/context';\nimport {\n ClientHandshakeOptions,\n ServerHandshakeOptions,\n} from '../router/handshake';\n\n/**\n * Represents the possible states of a transport.\n * @property {'open'} open - The transport is open and operational (note that this doesn't mean it is actively connected)\n * @property {'closed'} closed - The transport is permanently closed and cannot be reopened.\n */\nexport type TransportStatus = 'open' | 'closed';\n\ntype TransportOptions = SessionOptions;\n\nexport type ProvidedTransportOptions = Partial<TransportOptions>;\n\nexport const defaultTransportOptions: TransportOptions = {\n heartbeatIntervalMs: 1_000,\n heartbeatsUntilDead: 2,\n sessionDisconnectGraceMs: 5_000,\n codec: NaiveJsonCodec,\n};\n\ntype ClientTransportOptions = TransportOptions & ConnectionRetryOptions;\n\nexport type ProvidedClientTransportOptions = Partial<ClientTransportOptions>;\n\nconst defaultConnectionRetryOptions: ConnectionRetryOptions = {\n baseIntervalMs: 250,\n maxJitterMs: 200,\n maxBackoffMs: 32_000,\n attemptBudgetCapacity: 5,\n budgetRestoreIntervalMs: 200,\n};\n\nconst defaultClientTransportOptions: ClientTransportOptions = {\n ...defaultTransportOptions,\n ...defaultConnectionRetryOptions,\n};\n\ntype ServerTransportOptions = TransportOptions;\n\nexport type ProvidedServerTransportOptions = Partial<ServerTransportOptions>;\n\nconst defaultServerTransportOptions: ServerTransportOptions = {\n ...defaultTransportOptions,\n};\n\n/**\n * Transports manage the lifecycle (creation/deletion) of sessions and connections. Its responsibilities include:\n *\n * 1) Constructing a new {@link Session} and {@link Connection} on {@link TransportMessage}s from new clients.\n * After constructing the {@link Connection}, {@link onConnect} is called which adds it to the connection map.\n * 2) Delegating message listening of the connection to the newly created {@link Connection}.\n * From this point on, the {@link Connection} is responsible for *reading* and *writing*\n * messages from the connection.\n * 3) When a connection is closed, the {@link Transport} calls {@link onDisconnect} which closes the\n * connection via {@link Connection.close} and removes it from the {@link connections} map.\n\n *\n * ```plaintext\n * ▲\n * incoming │\n * messages │\n * ▼\n * ┌─────────────┐ 1:N ┌───────────┐ 1:1* ┌────────────┐\n * │ Transport │ ◄─────► │ Session │ ◄─────► │ Connection │\n * └─────────────┘ └───────────┘ └────────────┘\n * ▲ * (may or may not be initialized yet)\n * │\n * ▼\n * ┌───────────┐\n * │ Message │\n * │ Listeners │\n * └───────────┘\n * ```\n * @abstract\n */\nexport abstract class Transport<ConnType extends Connection> {\n /**\n * The status of the transport.\n */\n private status: TransportStatus;\n\n /**\n * The {@link Codec} used to encode and decode messages.\n */\n codec: Codec;\n\n /**\n * The client ID of this transport.\n */\n clientId: TransportClientId;\n\n /**\n * The map of {@link Session}s managed by this transport.\n */\n sessions: Map<TransportClientId, Session<ConnType>>;\n\n /**\n * The map of {@link Connection}s managed by this transport.\n */\n get connections() {\n return new Map(\n [...this.sessions]\n .map(([client, session]) => [client, session.connection])\n .filter((entry): entry is [string, ConnType] => entry[1] !== undefined),\n );\n }\n\n /**\n * The event dispatcher for handling events of type EventTypes.\n */\n eventDispatcher: EventDispatcher<EventTypes>;\n\n /**\n * The options for this transport.\n */\n protected options: TransportOptions;\n log?: Logger;\n\n /**\n * Creates a new Transport instance.\n * This should also set up {@link onConnect}, and {@link onDisconnect} listeners.\n * @param codec The codec used to encode and decode messages.\n * @param clientId The client ID of this transport.\n */\n constructor(\n clientId: TransportClientId,\n providedOptions?: ProvidedTransportOptions,\n ) {\n this.options = { ...defaultTransportOptions, ...providedOptions };\n this.eventDispatcher = new EventDispatcher();\n this.sessions = new Map();\n this.codec = this.options.codec;\n this.clientId = clientId;\n this.status = 'open';\n }\n\n bindLogger(fn: LogFn | Logger, level?: LoggingLevel) {\n // construct logger from fn\n if (typeof fn === 'function') {\n this.log = createLogProxy(new BaseLogger(fn, level));\n return;\n }\n\n // object case, just assign\n this.log = createLogProxy(fn);\n }\n\n /**\n * This is called immediately after a new connection is established and we\n * may or may not know the identity of the connected client.\n * It should attach all the necessary listeners to the connection for lifecycle\n * events (i.e. data, close, error)\n *\n * This method is implemented by {@link ClientTransport} and {@link ServerTransport}.\n */\n protected abstract handleConnection(\n conn: ConnType,\n to: TransportClientId,\n ): void;\n\n /**\n * Called when a new connection is established\n * and we know the identity of the connected client.\n * @param conn The connection object.\n */\n protected onConnect(\n conn: ConnType,\n connectedTo: TransportClientId,\n session: Session<ConnType>,\n isReconnect: boolean,\n ) {\n this.eventDispatcher.dispatchEvent('connectionStatus', {\n status: 'connect',\n conn,\n });\n\n conn.telemetry = createConnectionTelemetryInfo(conn, session.telemetry);\n\n if (isReconnect) {\n session.replaceWithNewConnection(conn);\n this.log?.info(`reconnected to ${connectedTo}`, {\n ...conn.loggingMetadata,\n ...session.loggingMetadata,\n clientId: this.clientId,\n connectedTo,\n });\n }\n }\n\n protected createSession(\n to: TransportClientId,\n conn?: ConnType,\n propagationCtx?: PropagationContext,\n ) {\n const session = new Session<ConnType>(\n conn,\n this.clientId,\n to,\n this.options,\n propagationCtx,\n );\n\n if (this.log) {\n session.bindLogger(this.log);\n }\n\n this.sessions.set(session.to, session);\n this.eventDispatcher.dispatchEvent('sessionStatus', {\n status: 'connect',\n session,\n });\n return session;\n }\n\n protected getOrCreateSession({\n to,\n conn,\n handshakingConn,\n sessionId,\n propagationCtx,\n }: {\n to: TransportClientId;\n conn?: ConnType;\n handshakingConn?: ConnType;\n sessionId?: string;\n propagationCtx?: PropagationContext;\n }) {\n let session = this.sessions.get(to);\n let isReconnect = session !== undefined;\n\n if (\n session?.advertisedSessionId !== undefined &&\n sessionId !== undefined &&\n session.advertisedSessionId !== sessionId\n ) {\n this.log?.info(\n `session for ${to} already exists but has a different session id (expected: ${session.advertisedSessionId}, got: ${sessionId}), creating a new one`,\n session.loggingMetadata,\n );\n // note that here we are only interested in closing the handshaking connection if it _does\n // not_ match the current handshaking connection. otherwise we can be in a situation where we\n // can accidentally close the current connection and are never able to establish a full\n // handshake.\n this.deleteSession({\n session,\n closeHandshakingConnection: handshakingConn !== undefined,\n handshakingConn,\n });\n isReconnect = false;\n session = undefined;\n }\n\n if (!session) {\n session = this.createSession(to, conn, propagationCtx);\n this.log?.info(\n `no session for ${to}, created a new one`,\n session.loggingMetadata,\n );\n }\n\n if (sessionId !== undefined) {\n session.advertisedSessionId = sessionId;\n }\n\n if (handshakingConn !== undefined) {\n session.replaceWithNewHandshakingConnection(handshakingConn);\n }\n return { session, isReconnect };\n }\n\n protected deleteSession({\n session,\n closeHandshakingConnection,\n handshakingConn,\n }: {\n session: Session<ConnType>;\n closeHandshakingConnection: boolean;\n handshakingConn?: ConnType;\n }) {\n if (closeHandshakingConnection) {\n session.closeHandshakingConnection(handshakingConn);\n }\n session.close();\n session.telemetry.span.end();\n this.sessions.delete(session.to);\n this.log?.info(\n `session ${session.id} disconnect from ${session.to}`,\n session.loggingMetadata,\n );\n this.eventDispatcher.dispatchEvent('sessionStatus', {\n status: 'disconnect',\n session,\n });\n }\n\n /**\n * The downstream implementation needs to call this when a connection is closed.\n * @param conn The connection object.\n * @param connectedTo The peer we are connected to.\n */\n protected onDisconnect(conn: ConnType, session: Session<ConnType>) {\n conn.telemetry?.span.end();\n this.eventDispatcher.dispatchEvent('connectionStatus', {\n status: 'disconnect',\n conn,\n });\n\n session.connection = undefined;\n session.beginGrace(() => {\n session.telemetry.span.addEvent('session grace period expired');\n this.deleteSession({\n session,\n closeHandshakingConnection: true,\n handshakingConn: conn,\n });\n });\n }\n\n /**\n * Parses a message from a Uint8Array into a {@link OpaqueTransportMessage}.\n * @param msg The message to parse.\n * @returns The parsed message, or null if the message is malformed or invalid.\n */\n protected parseMsg(\n msg: Uint8Array,\n conn: ConnType,\n ): OpaqueTransportMessage | null {\n const parsedMsg = this.codec.fromBuffer(msg);\n\n if (parsedMsg === null) {\n const decodedBuffer = new TextDecoder().decode(Buffer.from(msg));\n this.log?.error(\n `received malformed msg, killing conn: ${decodedBuffer}`,\n {\n clientId: this.clientId,\n ...conn.loggingMetadata,\n },\n );\n return null;\n }\n\n if (!Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {\n this.log?.error(`received invalid msg: ${JSON.stringify(parsedMsg)}`, {\n clientId: this.clientId,\n ...conn.loggingMetadata,\n validationErrors: [\n ...Value.Errors(OpaqueTransportMessageSchema, parsedMsg),\n ],\n });\n return null;\n }\n\n return parsedMsg;\n }\n\n /**\n * Called when a message is received by this transport.\n * You generally shouldn't need to override this in downstream transport implementations.\n * @param msg The received message.\n */\n protected handleMsg(msg: OpaqueTransportMessage, conn: ConnType) {\n if (this.getStatus() !== 'open') return;\n const session = this.sessions.get(msg.from);\n if (!session) {\n this.log?.error(`received message for unknown session from ${msg.from}`, {\n clientId: this.clientId,\n transportMessage: msg,\n ...conn.loggingMetadata,\n tags: ['invariant-violation'],\n });\n return;\n }\n\n // got a msg so we know the other end is alive, reset the grace period\n session.cancelGrace();\n\n this.log?.debug(`received msg`, {\n clientId: this.clientId,\n transportMessage: msg,\n ...conn.loggingMetadata,\n });\n if (msg.seq !== session.nextExpectedSeq) {\n if (msg.seq < session.nextExpectedSeq) {\n this.log?.debug(\n `received duplicate msg (got seq: ${msg.seq}, wanted seq: ${session.nextExpectedSeq}), discarding`,\n {\n clientId: this.clientId,\n transportMessage: msg,\n ...conn.loggingMetadata,\n },\n );\n } else {\n const errMsg = `received out-of-order msg (got seq: ${msg.seq}, wanted seq: ${session.nextExpectedSeq})`;\n this.log?.error(`${errMsg}, marking connection as dead`, {\n clientId: this.clientId,\n transportMessage: msg,\n ...conn.loggingMetadata,\n tags: ['invariant-violation'],\n });\n this.protocolError(ProtocolError.MessageOrderingViolated, errMsg);\n session.telemetry.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'message order violated',\n });\n this.deleteSession({ session, closeHandshakingConnection: true });\n }\n\n return;\n }\n\n session.updateBookkeeping(msg.ack, msg.seq);\n\n // don't dispatch explicit acks\n if (!isAck(msg.controlFlags)) {\n this.eventDispatcher.dispatchEvent('message', msg);\n } else {\n this.log?.debug(`discarding msg (ack bit set)`, {\n clientId: this.clientId,\n transportMessage: msg,\n ...conn.loggingMetadata,\n });\n }\n }\n\n /**\n * Adds a listener to this transport.\n * @param the type of event to listen for\n * @param handler The message handler to add.\n */\n addEventListener<K extends EventTypes, T extends EventHandler<K>>(\n type: K,\n handler: T,\n ): void {\n this.eventDispatcher.addEventListener(type, handler);\n }\n\n /**\n * Removes a listener from this transport.\n * @param the type of event to un-listen on\n * @param handler The message handler to remove.\n */\n removeEventListener<K extends EventTypes, T extends EventHandler<K>>(\n type: K,\n handler: T,\n ): void {\n this.eventDispatcher.removeEventListener(type, handler);\n }\n\n /**\n * Sends a message over this transport, delegating to the appropriate connection to actually\n * send the message.\n * @param msg The message to send.\n * @returns The ID of the sent message or undefined if it wasn't sent\n */\n\n send(to: TransportClientId, msg: PartialTransportMessage): string {\n if (this.getStatus() === 'closed') {\n const err = 'transport is closed, cant send';\n this.log?.error(err, {\n clientId: this.clientId,\n transportMessage: msg,\n tags: ['invariant-violation'],\n });\n\n throw new Error(err);\n }\n\n return this.getOrCreateSession({ to }).session.send(msg);\n }\n\n // control helpers\n sendCloseStream(to: TransportClientId, streamId: string) {\n return this.send(to, {\n streamId: streamId,\n controlFlags: ControlFlags.StreamClosedBit,\n payload: {\n type: 'CLOSE' as const,\n } satisfies Static<typeof ControlMessagePayloadSchema>,\n });\n }\n\n protected protocolError(type: ProtocolErrorType, message: string) {\n this.eventDispatcher.dispatchEvent('protocolError', { type, message });\n }\n\n /**\n * Default close implementation for transports. You should override this in the downstream\n * implementation if you need to do any additional cleanup and call super.close() at the end.\n * Closes the transport. Any messages sent while the transport is closed will be silently discarded.\n */\n close() {\n this.status = 'closed';\n\n for (const session of this.sessions.values()) {\n this.deleteSession({ session, closeHandshakingConnection: true });\n }\n\n this.eventDispatcher.dispatchEvent('transportStatus', {\n status: this.status,\n });\n\n this.eventDispatcher.removeAllListeners();\n\n this.log?.info(`manually closed transport`, { clientId: this.clientId });\n }\n\n getStatus(): TransportStatus {\n return this.status;\n }\n}\n\nexport abstract class ClientTransport<\n ConnType extends Connection,\n> extends Transport<ConnType> {\n /**\n * The options for this transport.\n */\n protected options: ClientTransportOptions;\n\n /**\n * The map of reconnect promises for each client ID.\n */\n inflightConnectionPromises: Map<TransportClientId, Promise<ConnType>>;\n retryBudget: LeakyBucketRateLimit;\n\n /**\n * A flag indicating whether the transport should automatically reconnect\n * when a connection is dropped.\n * Realistically, this should always be true for clients unless you are writing\n * tests or a special case where you don't want to reconnect.\n */\n reconnectOnConnectionDrop = true;\n\n /**\n * Optional handshake options for this client.\n */\n handshakeExtensions?: ClientHandshakeOptions;\n\n constructor(\n clientId: TransportClientId,\n providedOptions?: ProvidedClientTransportOptions,\n ) {\n super(clientId, providedOptions);\n this.options = {\n ...defaultClientTransportOptions,\n ...providedOptions,\n };\n this.inflightConnectionPromises = new Map();\n this.retryBudget = new LeakyBucketRateLimit(this.options);\n }\n\n extendHandshake(options: ClientHandshakeOptions) {\n this.handshakeExtensions = options;\n }\n\n protected handleConnection(conn: ConnType, to: TransportClientId): void {\n if (this.getStatus() !== 'open') return;\n let session: Session<ConnType> | undefined = undefined;\n\n // kill the conn after the grace period if we haven't received a handshake\n const handshakeTimeout = setTimeout(() => {\n if (session) return;\n this.log?.warn(\n `connection to ${to} timed out waiting for handshake, closing`,\n { ...conn.loggingMetadata, clientId: this.clientId, connectedTo: to },\n );\n conn.close();\n }, this.options.sessionDisconnectGraceMs);\n\n const handshakeHandler = (data: Uint8Array) => {\n const maybeSession = this.receiveHandshakeResponseMessage(data, conn);\n clearTimeout(handshakeTimeout);\n if (!maybeSession) {\n conn.close();\n return;\n } else {\n session = maybeSession;\n }\n\n // when we are done handshake sequence,\n // remove handshake listener and use the normal message listener\n conn.removeDataListener(handshakeHandler);\n conn.addDataListener((data) => {\n const parsed = this.parseMsg(data, conn);\n if (!parsed) {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'message parse failure',\n });\n conn.close();\n return;\n }\n\n this.handleMsg(parsed, conn);\n });\n };\n\n conn.addDataListener(handshakeHandler);\n conn.addCloseListener(() => {\n if (session) {\n this.onDisconnect(conn, session);\n }\n this.log?.info(`connection to ${to} disconnected`, {\n ...conn.loggingMetadata,\n ...session?.loggingMetadata,\n clientId: this.clientId,\n connectedTo: to,\n });\n this.inflightConnectionPromises.delete(to);\n if (this.reconnectOnConnectionDrop) {\n void this.connect(to);\n }\n });\n conn.addErrorListener((err) => {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'connection error',\n });\n this.log?.warn(\n `error in connection to ${to}: ${coerceErrorString(err)}`,\n {\n ...conn.loggingMetadata,\n ...session?.loggingMetadata,\n clientId: this.clientId,\n connectedTo: to,\n },\n );\n });\n }\n\n receiveHandshakeResponseMessage(\n data: Uint8Array,\n conn: ConnType,\n ): Session<ConnType> | false {\n const parsed = this.parseMsg(data, conn);\n if (!parsed) {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'non-transport message',\n });\n this.protocolError(\n ProtocolError.HandshakeFailed,\n 'received non-transport message',\n );\n return false;\n }\n\n if (!Value.Check(ControlMessageHandshakeResponseSchema, parsed.payload)) {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'invalid handshake response',\n });\n this.log?.warn(`received invalid handshake resp`, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n connectedTo: parsed.from,\n transportMessage: parsed,\n validationErrors: [\n ...Value.Errors(\n ControlMessageHandshakeResponseSchema,\n parsed.payload,\n ),\n ],\n });\n this.protocolError(\n ProtocolError.HandshakeFailed,\n 'invalid handshake resp',\n );\n return false;\n }\n\n if (!parsed.payload.status.ok) {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'handshake rejected',\n });\n this.log?.warn(`received handshake rejection`, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n connectedTo: parsed.from,\n transportMessage: parsed,\n });\n this.protocolError(\n ProtocolError.HandshakeFailed,\n parsed.payload.status.reason,\n );\n return false;\n }\n\n this.log?.debug(`handshake from ${parsed.from} ok`, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n connectedTo: parsed.from,\n transportMessage: parsed,\n });\n\n const { session, isReconnect } = this.getOrCreateSession({\n to: parsed.from,\n conn,\n sessionId: parsed.payload.status.sessionId,\n });\n\n this.onConnect(conn, parsed.from, session, isReconnect);\n\n // After a successful connection, we start restoring the budget\n // so that the next time we try to connect, we don't hit the client\n // with backoff forever.\n this.retryBudget.startRestoringBudget(parsed.from);\n return session;\n }\n\n /**\n * Abstract method that creates a new {@link Connection} object.\n * This should call {@link handleConnection} when the connection is created.\n * The downstream client implementation needs to implement this.\n *\n * @param to The client ID of the node to connect to.\n * @returns The new connection object.\n */\n protected abstract createNewOutgoingConnection(\n to: TransportClientId,\n ): Promise<ConnType>;\n\n /**\n * Manually attempts to connect to a client.\n * @param to The client ID of the node to connect to.\n */\n async connect(to: TransportClientId): Promise<void> {\n const canProceedWithConnection = () => this.getStatus() === 'open';\n if (!canProceedWithConnection()) {\n this.log?.info(\n `transport state is no longer open, cancelling attempt to connect to ${to}`,\n { clientId: this.clientId, connectedTo: to },\n );\n return;\n }\n\n let reconnectPromise = this.inflightConnectionPromises.get(to);\n if (!reconnectPromise) {\n // check budget\n const budgetConsumed = this.retryBudget.getBudgetConsumed(to);\n if (!this.retryBudget.hasBudget(to)) {\n const errMsg = `tried to connect to ${to} but retry budget exceeded (more than ${budgetConsumed} attempts in the last ${this.retryBudget.totalBudgetRestoreTime}ms)`;\n this.log?.error(errMsg, { clientId: this.clientId, connectedTo: to });\n this.protocolError(ProtocolError.RetriesExceeded, errMsg);\n return;\n }\n\n let sleep = Promise.resolve();\n const backoffMs = this.retryBudget.getBackoffMs(to);\n if (backoffMs > 0) {\n sleep = new Promise((resolve) => setTimeout(resolve, backoffMs));\n }\n\n this.log?.info(\n `attempting connection to ${to} (${backoffMs}ms backoff)`,\n {\n clientId: this.clientId,\n connectedTo: to,\n },\n );\n this.retryBudget.consumeBudget(to);\n reconnectPromise = tracer.startActiveSpan('connect', async (span) => {\n try {\n span.addEvent('backoff', { backoffMs });\n await sleep;\n if (!canProceedWithConnection()) {\n throw new Error('transport state is no longer open');\n }\n\n span.addEvent('connecting');\n const conn = await this.createNewOutgoingConnection(to);\n if (!canProceedWithConnection()) {\n this.log?.info(\n `transport state is no longer open, closing pre-handshake connection to ${to}`,\n {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n connectedTo: to,\n },\n );\n conn.close();\n throw new Error('transport state is no longer open');\n }\n\n span.addEvent('sending handshake');\n const ok = await this.sendHandshake(to, conn);\n if (!ok) {\n conn.close();\n throw new Error('failed to send handshake');\n }\n\n return conn;\n } catch (err) {\n // rethrow the error so that the promise is rejected\n // as it was before we wrapped it in a span\n const errStr = coerceErrorString(err);\n span.recordException(errStr);\n span.setStatus({ code: SpanStatusCode.ERROR });\n throw err;\n } finally {\n span.end();\n }\n });\n\n this.inflightConnectionPromises.set(to, reconnectPromise);\n } else {\n this.log?.info(\n `attempting connection to ${to} (reusing previous attempt)`,\n {\n clientId: this.clientId,\n connectedTo: to,\n },\n );\n }\n\n try {\n await reconnectPromise;\n } catch (error: unknown) {\n this.inflightConnectionPromises.delete(to);\n const errStr = coerceErrorString(error);\n\n if (!this.reconnectOnConnectionDrop || !canProceedWithConnection()) {\n this.log?.warn(`connection to ${to} failed (${errStr})`, {\n clientId: this.clientId,\n connectedTo: to,\n });\n } else {\n this.log?.warn(`connection to ${to} failed (${errStr}), retrying`, {\n clientId: this.clientId,\n connectedTo: to,\n });\n return this.connect(to);\n }\n }\n }\n\n protected deleteSession({\n session,\n closeHandshakingConnection,\n handshakingConn,\n }: {\n session: Session<ConnType>;\n closeHandshakingConnection: boolean;\n handshakingConn?: ConnType;\n }) {\n this.inflightConnectionPromises.delete(session.to);\n super.deleteSession({\n session,\n closeHandshakingConnection,\n handshakingConn,\n });\n }\n\n protected async sendHandshake(to: TransportClientId, conn: ConnType) {\n let metadata: unknown = undefined;\n\n if (this.handshakeExtensions) {\n metadata = await this.handshakeExtensions.construct();\n if (!Value.Check(this.handshakeExtensions.schema, metadata)) {\n this.log?.error(`constructed handshake metadata did not match schema`, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n connectedTo: to,\n validationErrors: [\n ...Value.Errors(this.handshakeExtensions.schema, metadata),\n ],\n tags: ['invariant-violation'],\n });\n this.protocolError(\n ProtocolError.HandshakeFailed,\n 'handshake metadata did not match schema',\n );\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'handshake meta mismatch',\n });\n return false;\n }\n }\n\n // dont pass conn here as we dont want the session to start using the conn\n // until we have finished the handshake. Still, let the session know that\n // it is semi-associated with the conn, and it can close it if .close() is called.\n const { session } = this.getOrCreateSession({ to, handshakingConn: conn });\n const requestMsg = handshakeRequestMessage(\n this.clientId,\n to,\n session.id,\n metadata,\n getPropagationContext(session.telemetry.ctx),\n );\n this.log?.debug(`sending handshake request to ${to}`, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n connectedTo: to,\n transportMessage: requestMsg,\n });\n conn.send(this.codec.toBuffer(requestMsg));\n return true;\n }\n\n close() {\n this.retryBudget.close();\n super.close();\n }\n}\n\nexport abstract class ServerTransport<\n ConnType extends Connection,\n> extends Transport<ConnType> {\n /**\n * The options for this transport.\n */\n protected options: ServerTransportOptions;\n\n /**\n * Optional handshake options for the server.\n */\n handshakeExtensions?: ServerHandshakeOptions;\n\n /**\n * A map of session handshake data for each session.\n */\n sessionHandshakeMetadata: WeakMap<Session<ConnType>, ParsedMetadata>;\n\n constructor(\n clientId: TransportClientId,\n providedOptions?: ProvidedServerTransportOptions,\n ) {\n super(clientId, providedOptions);\n this.options = {\n ...defaultServerTransportOptions,\n ...providedOptions,\n };\n this.sessionHandshakeMetadata = new WeakMap();\n this.log?.info(`initiated server transport`, {\n clientId: this.clientId,\n protocolVersion: PROTOCOL_VERSION,\n });\n }\n\n extendHandshake(options: ServerHandshakeOptions) {\n this.handshakeExtensions = options;\n }\n\n protected handleConnection(conn: ConnType) {\n if (this.getStatus() !== 'open') return;\n\n this.log?.info(`new incoming connection`, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n });\n\n let session: Session<ConnType> | undefined = undefined;\n const client = () => session?.to ?? 'unknown';\n\n // kill the conn after the grace period if we haven't received a handshake\n const handshakeTimeout = setTimeout(() => {\n if (!session) {\n this.log?.warn(\n `connection to ${client()} timed out waiting for handshake, closing`,\n {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n connectedTo: client(),\n },\n );\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'handshake timeout',\n });\n conn.close();\n }\n }, this.options.sessionDisconnectGraceMs);\n\n const buffer: Array<Uint8Array> = [];\n let receivedHandshakeMessage = false;\n\n const handshakeHandler = (data: Uint8Array) => {\n // if we've already received, just buffer the data\n if (receivedHandshakeMessage) {\n buffer.push(data);\n return;\n }\n\n receivedHandshakeMessage = true;\n clearTimeout(handshakeTimeout);\n\n void this.receiveHandshakeRequestMessage(data, conn).then(\n (maybeSession) => {\n if (!maybeSession) {\n conn.close();\n return;\n }\n\n session = maybeSession;\n\n // when we are done handshake sequence,\n // remove handshake listener and use the normal message listener\n const dataHandler = (data: Uint8Array) => {\n const parsed = this.parseMsg(data, conn);\n if (!parsed) {\n conn.close();\n return;\n }\n\n this.handleMsg(parsed, conn);\n };\n\n // process any data we missed\n for (const data of buffer) {\n dataHandler(data);\n }\n\n conn.removeDataListener(handshakeHandler);\n conn.addDataListener(dataHandler);\n buffer.length = 0;\n },\n );\n };\n\n conn.addDataListener(handshakeHandler);\n conn.addCloseListener(() => {\n if (!session) return;\n this.log?.info(`connection to ${client()} disconnected`, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n });\n this.onDisconnect(conn, session);\n });\n\n conn.addErrorListener((err) => {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'connection error',\n });\n if (!session) return;\n this.log?.warn(\n `connection to ${client()} got an error: ${coerceErrorString(err)}`,\n { ...conn.loggingMetadata, clientId: this.clientId },\n );\n });\n }\n\n private async validateHandshakeMetadata(\n conn: ConnType,\n session: Session<ConnType> | undefined,\n rawMetadata: Static<\n typeof ControlMessageHandshakeRequestSchema\n >['metadata'],\n from: TransportClientId,\n ): Promise<ParsedMetadata | false> {\n let parsedMetadata: ParsedMetadata = {};\n if (this.handshakeExtensions) {\n // check that the metadata that was sent is the correct shape\n if (!Value.Check(this.handshakeExtensions.schema, rawMetadata)) {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'malformed handshake meta',\n });\n const reason = 'received malformed handshake metadata';\n const responseMsg = handshakeResponseMessage(this.clientId, from, {\n ok: false,\n reason,\n });\n conn.send(this.codec.toBuffer(responseMsg));\n this.log?.warn(`received malformed handshake metadata from ${from}`, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n validationErrors: [\n ...Value.Errors(this.handshakeExtensions.schema, rawMetadata),\n ],\n });\n this.protocolError(ProtocolError.HandshakeFailed, reason);\n return false;\n }\n\n const previousParsedMetadata = session\n ? this.sessionHandshakeMetadata.get(session)\n : undefined;\n\n parsedMetadata = await this.handshakeExtensions.validate(\n rawMetadata,\n previousParsedMetadata,\n );\n\n // handler rejected the connection\n if (parsedMetadata === false) {\n const reason = 'rejected by handshake handler';\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: reason,\n });\n const responseMsg = handshakeResponseMessage(this.clientId, from, {\n ok: false,\n reason,\n });\n conn.send(this.codec.toBuffer(responseMsg));\n this.log?.warn(`rejected handshake from ${from}`, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n });\n this.protocolError(ProtocolError.HandshakeFailed, reason);\n return false;\n }\n }\n\n return parsedMetadata;\n }\n\n async receiveHandshakeRequestMessage(\n data: Uint8Array,\n conn: ConnType,\n ): Promise<Session<ConnType> | false> {\n const parsed = this.parseMsg(data, conn);\n if (!parsed) {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'non-transport message',\n });\n this.protocolError(\n ProtocolError.HandshakeFailed,\n 'received non-transport message',\n );\n return false;\n }\n\n if (!Value.Check(ControlMessageHandshakeRequestSchema, parsed.payload)) {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'invalid handshake request',\n });\n const reason = 'received invalid handshake msg';\n const responseMsg = handshakeResponseMessage(this.clientId, parsed.from, {\n ok: false,\n reason,\n });\n conn.send(this.codec.toBuffer(responseMsg));\n this.log?.warn(reason, {\n ...conn.loggingMetadata,\n clientId: this.clientId,\n // safe to this.log metadata here as we remove the payload\n // before passing it to user-land\n transportMessage: parsed,\n validationErrors: [\n ...Value.Errors(ControlMessageHandshakeRequestSchema, parsed.payload),\n ],\n });\n this.protocolError(\n ProtocolError.HandshakeFailed,\n 'invalid handshake request',\n );\n return false;\n }\n\n // double check protocol version here\n const gotVersion = parsed.payload.protocolVersion;\n if (gotVersion !== PROTOCOL_VERSION) {\n conn.telemetry?.span.setStatus({\n code: SpanStatusCode.ERROR,\n message: 'incorrect protocol version',\n });\n\n const reason = `incorrect version (got: ${gotVersion} wanted ${PROTOCOL_VERSION})`;\n const responseMsg = handshakeResponseMessage(this.clientId, parsed.from, {\n ok: false,\n reason,\n });\n conn.send(this.codec.toBuffer(responseMsg));\n this.log?.warn(\n `received handshake msg with incompatible protocol version (got: ${gotVersion}, expected: ${PROTOCOL_VERSION})`,\n { ...conn.loggingMetadata, clientId: this.clientId },\n );\n this.protocolError(ProtocolError.HandshakeFailed, reason);\n return false;\n }\n\n const oldSession = this.sessions.get(parsed.from);\n const parsedMetadata = await this.validateHandshakeMetadata(\n conn,\n oldSession,\n parsed.payload.metadata,\n parsed.from,\n );\n\n if (parsedMetadata === false) {\n return false;\n }\n\n const { session, isReconnect } = this.getOrCreateSession({\n to: parsed.from,\n conn,\n sessionId: parsed.payload.sessionId,\n propagationCtx: parsed.tracing,\n });\n\n this.sessionHandshakeMetadata.set(session, parsedMetadata);\n\n this.log?.debug(\n `handshake from ${parsed.from} ok, responding with handshake success`,\n conn.loggingMetadata,\n );\n const responseMsg = handshakeResponseMessage(this.clientId, parsed.from, {\n ok: true,\n sessionId: session.id,\n });\n conn.send(this.codec.toBuffer(responseMsg));\n this.onConnect(conn, parsed.from, session, isReconnect);\n\n return session;\n }\n}\n","import { Codec } from './types';\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\n// Convert Uint8Array to base64\nfunction uint8ArrayToBase64(uint8Array: Uint8Array) {\n let binary = '';\n uint8Array.forEach((byte) => {\n binary += String.fromCharCode(byte);\n });\n return btoa(binary);\n}\n\n// Convert base64 to Uint8Array\nfunction base64ToUint8Array(base64: string) {\n const binaryString = atob(base64);\n const uint8Array = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n uint8Array[i] = binaryString.charCodeAt(i);\n }\n return uint8Array;\n}\n\ninterface Base64EncodedValue {\n $t: string;\n}\n\n/**\n * Naive JSON codec implementation using JSON.stringify and JSON.parse.\n * @type {Codec}\n */\nexport const NaiveJsonCodec: Codec = {\n toBuffer: (obj: object) => {\n return encoder.encode(\n JSON.stringify(obj, function replacer<\n T extends object,\n >(this: T, key: keyof T) {\n const val = this[key];\n if (val instanceof Uint8Array) {\n return { $t: uint8ArrayToBase64(val) } satisfies Base64EncodedValue;\n } else {\n return val;\n }\n }),\n );\n },\n fromBuffer: (buff: Uint8Array) => {\n try {\n const parsed = JSON.parse(\n decoder.decode(buff),\n function reviver(_key, val: unknown) {\n if ((val as Base64EncodedValue | undefined)?.$t) {\n return base64ToUint8Array((val as Base64EncodedValue).$t);\n } else {\n return val;\n }\n },\n ) as unknown;\n\n if (typeof parsed === 'object') return parsed;\n return null;\n } catch {\n return null;\n }\n },\n};\n"],"mappings":"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","names":["pushable","options","_pushable","import_nanoid","import_api","import_api","NodeWs"]}
|