Suite of AT Protocol TypeScript libraries built on web standards
21
fork

Configure Feed

Select the types of activity you want to include in your feed.

rm bad docs

+38 -291
-1
common/streams.ts
··· 59 59 return concat(chunks); 60 60 }; 61 61 62 - // streamToBuffer identifier name already taken by @atproto/common-web 63 62 export const streamToBuffer = async ( 64 63 stream: 65 64 | Iterable<Uint8Array>
-57
xrpc-server/errors.ts
··· 88 88 get statusCode(): number { 89 89 const { type } = this; 90 90 91 - // Fool-proofing. `new XRPCError(123.5 as number, '')` does not generate a TypeScript error. 92 - // Because of this, we can end-up with any numeric value instead of an actual `ResponseType`. 93 - // For legacy reasons, the `type` argument is not checked in the constructor, so we check it here. 94 91 if (type < 400 || type >= 600 || !Number.isFinite(type)) { 95 92 return 500; 96 93 } ··· 162 159 * Used when the client request is malformed or invalid. 163 160 */ 164 161 export class InvalidRequestError extends XRPCError { 165 - /** 166 - * Creates a new InvalidRequestError. 167 - * @param errorMessage - Optional error message 168 - * @param customErrorName - Optional custom error name 169 - * @param options - Optional error options 170 - */ 171 162 constructor( 172 163 errorMessage?: string, 173 164 customErrorName?: string, ··· 189 180 * Used when the request requires authentication but none was provided or it was invalid. 190 181 */ 191 182 export class AuthRequiredError extends XRPCError { 192 - /** 193 - * Creates a new AuthRequiredError. 194 - * @param errorMessage - Optional error message 195 - * @param customErrorName - Optional custom error name 196 - * @param options - Optional error options 197 - */ 198 183 constructor( 199 184 errorMessage?: string, 200 185 customErrorName?: string, ··· 221 206 * Used when the client is authenticated but doesn't have permission to access the resource. 222 207 */ 223 208 export class ForbiddenError extends XRPCError { 224 - /** 225 - * Creates a new ForbiddenError. 226 - * @param errorMessage - Optional error message 227 - * @param customErrorName - Optional custom error name 228 - * @param options - Optional error options 229 - */ 230 209 constructor( 231 210 errorMessage?: string, 232 211 customErrorName?: string, ··· 247 226 * Used when an unexpected error occurs on the server side. 248 227 */ 249 228 export class InternalServerError extends XRPCError { 250 - /** 251 - * Creates a new InternalServerError. 252 - * @param errorMessage - Optional error message 253 - * @param customErrorName - Optional custom error name 254 - * @param options - Optional error options 255 - */ 256 229 constructor( 257 230 errorMessage?: string, 258 231 customErrorName?: string, ··· 279 252 * Used when a dependent service fails or returns an invalid response. 280 253 */ 281 254 export class UpstreamFailureError extends XRPCError { 282 - /** 283 - * Creates a new UpstreamFailureError. 284 - * @param errorMessage - Optional error message 285 - * @param customErrorName - Optional custom error name 286 - * @param options - Optional error options 287 - */ 288 255 constructor( 289 256 errorMessage?: string, 290 257 customErrorName?: string, ··· 306 273 * Used when the server temporarily cannot handle the request due to resource constraints. 307 274 */ 308 275 export class NotEnoughResourcesError extends XRPCError { 309 - /** 310 - * Creates a new NotEnoughResourcesError. 311 - * @param errorMessage - Optional error message 312 - * @param customErrorName - Optional custom error name 313 - * @param options - Optional error options 314 - */ 315 276 constructor( 316 277 errorMessage?: string, 317 278 customErrorName?: string, ··· 338 299 * Used when a dependent service times out or takes too long to respond. 339 300 */ 340 301 export class UpstreamTimeoutError extends XRPCError { 341 - /** 342 - * Creates a new UpstreamTimeoutError. 343 - * @param errorMessage - Optional error message 344 - * @param customErrorName - Optional custom error name 345 - * @param options - Optional error options 346 - */ 347 302 constructor( 348 303 errorMessage?: string, 349 304 customErrorName?: string, ··· 365 320 * Used when the requested XRPC method is not implemented by the server. 366 321 */ 367 322 export class MethodNotImplementedError extends XRPCError { 368 - /** 369 - * Creates a new MethodNotImplementedError. 370 - * @param errorMessage - Optional error message 371 - * @param customErrorName - Optional custom error name 372 - * @param options - Optional error options 373 - */ 374 323 constructor( 375 324 errorMessage?: string, 376 325 customErrorName?: string, ··· 392 341 } 393 342 } 394 343 395 - /** 396 - * Converts an upstream XRPC client error into a downstream ResponseType. 397 - * Maps client error status codes to appropriate server response types. 398 - * @param error The upstream XRPC client error 399 - * @returns Object containing error details and mapped response type 400 - */ 401 344 function mapFromClientError(error: XRPCClientError): { 402 345 error: string; 403 346 message: string;
-3
xrpc-server/rate-limiter.ts
··· 7 7 import { ResponseType, XRPCError } from "./errors.ts"; 8 8 import { logger } from "./logger.ts"; 9 9 10 - // @NOTE Do not depend (directly or indirectly) on "./types" here, as it would 11 - // create a circular dependency. 12 - 13 10 export interface RateLimiterContext { 14 11 req: Request; 15 12 res?: Response;
+1 -42
xrpc-server/server.ts
··· 58 58 import type { CatchallHandler, RouteOptions } from "./types.ts"; 59 59 60 60 /** 61 - * Creates a new XRPC server instance. 61 + * Creates a new XRPC server instance 62 62 * @param lexicons - Optional array of lexicon documents to initialize the server with 63 63 * @param options - Optional server configuration options 64 - * @returns A new Server instance 65 64 */ 66 65 export function createServer( 67 66 lexicons?: LexiconDoc[], ··· 104 103 this.addLexicons(lexicons); 105 104 } 106 105 107 - // Add global middleware 108 106 this.app.use("*", this.catchall); 109 107 this.app.onError(createErrorHandler(opts)); 110 108 111 - // Add 404 handler to catch unmatched XRPC routes 112 109 this.app.notFound((c) => { 113 110 const nsid = parseUrlNsid(c.req.url); 114 111 if (nsid) { ··· 130 127 throw error; 131 128 } 132 129 } 133 - // For non-XRPC routes, return standard 404 134 130 return c.text("Not Found", 404); 135 131 }); 136 132 ··· 156 152 } 157 153 158 154 // handlers 159 - // = 160 155 161 156 /** 162 157 * Registers a method handler for the specified NSID. ··· 223 218 } 224 219 225 220 // lexicon 226 - // = 227 221 228 222 /** 229 223 * Adds a lexicon document to the server's schema registry. ··· 244 238 } 245 239 246 240 // routes 247 - // = 248 241 249 242 /** 250 243 * Adds an HTTP route for the specified method. ··· 270 263 271 264 /** 272 265 * Catchall handler that processes all XRPC routes and applies global rate limiting. 273 - * Only applies to routes starting with "/xrpc/". 274 266 */ 275 267 catchall: CatchallHandler = async (c, next) => { 276 268 if (!c.req.url.includes("/xrpc/")) { ··· 510 502 }); 511 503 } 512 504 513 - /** 514 - * Creates a route-specific rate limiter based on the method configuration. 515 - * @template A - The authentication type 516 - * @template C - The handler context type 517 - * @param nsid - The namespace identifier for the method 518 - * @param config - The method configuration containing rate limit options 519 - * @returns A route rate limiter or undefined if no rate limiting is configured 520 - * @private 521 - */ 522 505 private createRouteRateLimiter<A extends Auth, C extends HandlerContext>( 523 506 nsid: string, 524 507 config: MethodConfig<A>, ··· 590 573 } 591 574 } 592 575 593 - /** 594 - * Creates an error handler function for the Hono application. 595 - * @param opts - Server options containing optional error parser 596 - * @returns An error handler function that converts errors to XRPC error responses 597 - */ 598 576 function createErrorHandler( 599 577 opts: Options, 600 578 ): (err: Error, c: Context) => Response { ··· 612 590 }; 613 591 } 614 592 615 - /** 616 - * Builds rate limiter options from a server rate limit description. 617 - * @template C - The handler context type 618 - * @param options - The server rate limit description 619 - * @returns Rate limiter options with defaults applied 620 - */ 621 593 function buildRateLimiterOptions<C extends HandlerContext = HandlerContext>({ 622 594 name, 623 595 calcKey = defaultKey, ··· 627 599 return { ...desc, calcKey, calcPoints, keyPrefix: `rl-${name}` }; 628 600 } 629 601 630 - /** 631 - * Default function for calculating rate limit points consumed per request. 632 - * Always returns 1 point per request. 633 - */ 634 602 const defaultPoints: CalcPointsFn = (): number => 1; 635 603 636 - /** 637 - * Default function for calculating rate limit keys based on client IP address. 638 - * Extracts IP from X-Forwarded-For, X-Real-IP headers, or falls back to "unknown". 639 - * 640 - * @note When using a proxy, ensure headers are getting forwarded correctly: 641 - * `app.set('trust proxy', true)` 642 - * 643 - * @see {@link https://expressjs.com/en/guide/behind-proxies.html} 644 - */ 645 604 const defaultKey: CalcKeyFn<HandlerContext> = ({ req }) => { 646 605 const forwarded = req.headers.get("x-forwarded-for"); 647 606 const ip = forwarded
+3
xrpc-server/stream/connection.ts
··· 121 121 122 122 /** 123 123 * Executes the streaming handler and processes yielded data. 124 + * @param params - The request's parameters. 125 + * @param auth - The request's authentication state. 126 + * @param req - The raw request object. 124 127 */ 125 128 private async executeHandler( 126 129 params: Record<string, string>,
+8 -23
xrpc-server/stream/frames.ts
··· 44 44 45 45 /** 46 46 * Type guard to check if this frame is a MessageFrame. 47 - * @returns {boolean} True if this is a MessageFrame 48 47 */ 49 48 isMessage(): this is MessageFrame<unknown> { 50 49 return this.op === FrameType.Message; ··· 52 51 53 52 /** 54 53 * Type guard to check if this frame is an ErrorFrame. 55 - * @returns {boolean} True if this is an ErrorFrame 56 54 */ 57 55 isError(): this is ErrorFrame { 58 56 return this.op === FrameType.Error; ··· 62 60 * Deserializes a frame from its binary representation. 63 61 * Validates the frame structure and creates the appropriate frame type. 64 62 * 65 - * @param {Uint8Array} bytes - The serialized frame bytes 66 - * @returns {Frame} The deserialized frame (either MessageFrame or ErrorFrame) 67 - * @throws {Error} If the frame format is invalid or unknown 63 + * @param bytes - The serialized frame bytes 64 + * @returns The deserialized frame (either MessageFrame or ErrorFrame) 65 + * @throws If the frame format is invalid or unknown 68 66 */ 69 67 static fromBytes(bytes: Uint8Array): Frame { 70 68 let decoded: unknown[]; 71 69 try { 72 70 decoded = cborDecodeMulti(bytes); 73 71 } catch { 74 - // Re-throw CBOR decode errors with a more generic message to match test expectations 75 72 throw new Error("Unexpected end of CBOR data"); 76 73 } 77 74 78 - // Check for empty or invalid decode results 79 75 if (decoded.length === 0 || decoded[0] === undefined) { 80 76 throw new Error("Unexpected end of CBOR data"); 81 77 } ··· 119 115 * Frame type for sending messages/data over an XRPC stream. 120 116 * Can contain any type of payload data and an optional message type identifier. 121 117 * 122 - * @template T - The type of the message body, defaults to Record<string, unknown> 123 - * @extends {Frame} 124 - * @property {MessageFrameHeader} header - Message frame header 125 - * @property {T} body - Message payload data 118 + * @template T - The type of the message body 119 + * @property header - Message frame header 120 + * @property body - Message payload data 126 121 */ 127 122 export class MessageFrame<T = Record<string, unknown>> extends Frame { 128 123 header: MessageFrameHeader; ··· 156 151 * Contains an error code and optional error message. 157 152 * 158 153 * @template T - The type of error code string 159 - * @extends {Frame} 160 - * @property {ErrorFrameHeader} header - Error frame header 161 - * @property {ErrorFrameBody<T>} body - Error details including code and message 154 + * @property header - Error frame header 155 + * @property body - Error details including code and message 162 156 */ 163 157 export class ErrorFrame<T extends string = string> extends Frame { 164 158 header: ErrorFrameHeader; 165 159 override body: ErrorFrameBody<T>; 166 160 167 - /** 168 - * Creates a new ErrorFrame. 169 - * @param {ErrorFrameBody<T>} body - The error details 170 - */ 171 161 constructor(body: ErrorFrameBody<T>) { 172 162 super(); 173 163 this.header = { op: FrameType.Error }; ··· 176 166 177 167 /** 178 168 * Gets the error code. 179 - * @returns {string} The error code 180 169 */ 181 170 get code(): string { 182 171 return this.body.error; ··· 191 180 } 192 181 } 193 182 194 - /** 195 - * Symbol used internally to detect unset frame body. 196 - * @private 197 - */ 198 183 const kUnset = Symbol("unset");
-17
xrpc-server/stream/logger.ts
··· 1 1 import { subsystemLogger } from "@atp/common"; 2 2 3 - /** 4 - * Logger instance for XRPC streaming operations. 5 - * This is a subsystem logger specifically configured for logging events 6 - * related to WebSocket streaming, connection management, and stream processing. 7 - * 8 - * @example 9 - * ```typescript 10 - * import { logger } from './logger'; 11 - * 12 - * logger.info('WebSocket connection established'); 13 - * logger.error(error, 'Stream processing failed'); 14 - * ``` 15 - */ 16 3 export const logger: ReturnType<typeof subsystemLogger> = subsystemLogger( 17 4 "xrpc-stream", 18 5 ); 19 6 20 - /** 21 - * Default export of the XRPC stream logger. 22 - * Same as the named export, provided for convenience. 23 - */ 24 7 export default logger;
+5 -24
xrpc-server/stream/server.ts
··· 11 11 export class XrpcStreamServer { 12 12 wss: WebSocketServer; 13 13 14 - /** 15 - * Creates a new XRPC streaming server instance. 16 - * @constructor 17 - * @param {Object} opts - Server configuration options 18 - * @param {Handler} opts.handler - Function to handle incoming WebSocket connections 19 - * @param {ServerOptions} opts - Additional WebSocket server options 20 - */ 21 14 constructor(opts: ServerOptions & { handler: Handler }) { 22 15 const { handler, ...serverOpts } = opts; 23 16 this.wss = new WebSocketServer(serverOpts); ··· 81 74 /** 82 75 * Handler function type for WebSocket connections. 83 76 * @callback Handler 84 - * @param {Request} req - The incoming WebSocket request 85 - * @param {AbortSignal} signal - Signal for detecting connection abort 86 - * @param {WebSocket} socket - The WebSocket connection 87 - * @param {XrpcStreamServer} server - The server instance 88 - * @returns {AsyncIterable<Frame>} An async iterable of frames to send 77 + * @param req - The incoming WebSocket request 78 + * @param signal - Signal for detecting connection abort 79 + * @param socket - The WebSocket connection 80 + * @param server - The server instance 81 + * @returns An async iterable of frames to send 89 82 */ 90 83 export type Handler = ( 91 84 req: Request, ··· 94 87 server: XrpcStreamServer, 95 88 ) => AsyncIterable<Frame>; 96 89 97 - /** 98 - * Unwraps an AsyncIterable into its AsyncIterator. 99 - * @template T - The type of values being iterated 100 - * @param {AsyncIterable<T>} iterable - The iterable to unwrap 101 - * @returns {AsyncIterator<T>} The unwrapped iterator 102 - */ 103 90 function unwrapIterator<T>(iterable: AsyncIterable<T>): AsyncIterator<T> { 104 91 return iterable[Symbol.asyncIterator](); 105 92 } 106 93 107 - /** 108 - * Wraps an AsyncIterator back into an AsyncIterable. 109 - * @template T - The type of values being iterated 110 - * @param {AsyncIterator<T>} iterator - The iterator to wrap 111 - * @returns {AsyncIterable<T>} The wrapped iterable 112 - */ 113 94 function wrapIterator<T>(iterator: AsyncIterator<T>): AsyncIterable<T> { 114 95 return { 115 96 [Symbol.asyncIterator]() {
+8 -12
xrpc-server/stream/stream.ts
··· 6 6 * Converts a WebSocket connection into an async generator of Frame objects. 7 7 * Handles both message and error frames, with proper error propagation. 8 8 * 9 - * @param {WebSocket} ws - The WebSocket connection to read from 9 + * @param ws - The WebSocket connection to read from 10 10 * @yields {Frame} Each frame received from the WebSocket 11 - * @throws {Error} Any WebSocket error that occurs during communication 11 + * @throws Any WebSocket error that occurs during communication 12 12 * 13 13 * @example 14 14 * ```typescript ··· 67 67 } 68 68 } 69 69 70 - /** 71 - * Waits for the next frame from a WebSocket connection. 72 - * Returns null if the connection closes normally. 73 - */ 74 70 function waitForNextFrame(ws: WebSocket): Promise<Frame | null> { 75 71 return new Promise<Frame | null>((resolve, reject) => { 76 72 const cleanup = () => { ··· 135 131 * Automatically filters and validates frames to ensure they are valid messages. 136 132 * Error frames are converted to exceptions. 137 133 * 138 - * @param {WebSocket} ws - The WebSocket connection to read from 139 - * @yields {MessageFrame<unknown>} Each message frame received from the WebSocket 140 - * @throws {XRPCError} If an error frame is received or an invalid frame type is encountered 134 + * @param ws - The WebSocket connection to read from 135 + * @yields Each message frame received from the WebSocket 136 + * @throws If an error frame is received or an invalid frame type is encountered 141 137 * 142 138 * @example 143 139 * ```typescript ··· 160 156 * Validates that a frame is a MessageFrame and converts it to the appropriate type. 161 157 * If the frame is an error frame, throws an XRPCError with the error details. 162 158 * 163 - * @param {Frame} frame - The frame to validate 164 - * @returns {MessageFrame<unknown>} The frame as a MessageFrame if valid 165 - * @throws {XRPCError} If the frame is an error frame or an invalid type 159 + * @param frame - The frame to validate 160 + * @returns The frame as a MessageFrame if valid 161 + * @throws If the frame is an error frame or an invalid type 166 162 * @internal 167 163 */ 168 164 export function ensureChunkIsMessage(frame: Frame): MessageFrame<unknown> {
+2 -32
xrpc-server/stream/subscription.ts
··· 6 6 /** 7 7 * Represents a message body in a subscription stream. 8 8 * @interface 9 - * @property {string} [$type] - Optional type identifier for the message 10 - * @property {unknown} [key: string] - Additional message properties 9 + * @property $type - Optional type identifier for the message 10 + * @property [key] - Additional message properties 11 11 */ 12 12 interface MessageBody { 13 13 $type?: string; ··· 21 21 * @template T - The type of messages yielded by the subscription 22 22 */ 23 23 export class Subscription<T = unknown> { 24 - /** 25 - * Creates a new subscription instance. 26 - * @constructor 27 - * @param {Object} opts - Subscription configuration options 28 - * @param {string} opts.service - The base URL of the XRPC service 29 - * @param {string} opts.method - The XRPC method to subscribe to 30 - * @param {number} [opts.maxReconnectSeconds] - Maximum time in seconds between reconnection attempts 31 - * @param {number} [opts.heartbeatIntervalMs] - Interval in milliseconds for sending heartbeat messages 32 - * @param {AbortSignal} [opts.signal] - Signal for aborting the subscription 33 - * @param {Function} opts.validate - Function to validate and transform incoming messages 34 - * @param {Function} [opts.onReconnectError] - Callback for handling reconnection errors 35 - * @param {Function} [opts.getParams] - Function to get query parameters for the subscription URL 36 - */ 37 24 constructor( 38 25 public opts: WebSocketOptions & { 39 26 service: string; ··· 54 41 }, 55 42 ) {} 56 43 57 - /** 58 - * Implements the AsyncIterator protocol for the subscription. 59 - * Allows using the subscription in a for-await-of loop. 60 - * @returns {AsyncGenerator<T>} An async generator that yields validated messages 61 - */ 62 44 async *[Symbol.asyncIterator](): AsyncGenerator<T> { 63 45 const ws = new WebSocketKeepAlive({ 64 46 ...this.opts, ··· 88 70 89 71 export default Subscription; 90 72 91 - /** 92 - * Encodes an object of parameters into a URL query string. 93 - * @param {Record<string, unknown>} obj - The parameters to encode 94 - * @returns {string} The encoded query string 95 - */ 96 73 function encodeQueryParams(obj: Record<string, unknown>): string { 97 74 const params = new URLSearchParams(); 98 75 Object.entries(obj).forEach(([key, value]) => { ··· 106 83 return params.toString(); 107 84 } 108 85 109 - /** 110 - * Encodes a single query parameter value into a string or array of strings. 111 - * Handles various types including strings, numbers, booleans, dates, and arrays. 112 - * @param {unknown} value - The value to encode 113 - * @returns {string | string[]} The encoded parameter value(s) 114 - * @throws {Error} If the value cannot be encoded as a query parameter 115 - */ 116 86 function encodeQueryParam(value: unknown): string | string[] { 117 87 if (typeof value === "string") { 118 88 return value;
+11 -16
xrpc-server/stream/types.ts
··· 14 14 /** 15 15 * WebSocket connection options. 16 16 * @interface 17 - * @property {Record<string, string>} [headers] - Additional headers for the WebSocket connection 18 - * @property {string[]} [protocols] - WebSocket subprotocols to use 17 + * @property [headers] - Additional headers for the WebSocket connection 18 + * @property [protocols] - WebSocket subprotocols to use 19 19 */ 20 20 export interface WebSocketOptions { 21 21 headers?: Record<string, string>; ··· 25 25 /** 26 26 * Header for message frames. 27 27 * @interface 28 - * @property {FrameType.Message} op - Operation type, always Message 29 - * @property {string} [t] - Optional message type discriminator 28 + * @property op - Operation type, always Message 29 + * @property [t] - Optional message type discriminator 30 30 */ 31 31 export type MessageFrameHeader = { 32 32 op: FrameType.Message; ··· 41 41 /** 42 42 * Header for error frames. 43 43 * @interface 44 - * @property {FrameType.Error} op - Operation type, always Error 44 + * @property op - Operation type, always Error 45 45 */ 46 46 export type ErrorFrameHeader = { 47 47 op: FrameType.Error; ··· 54 54 /** 55 55 * Base type for error frame bodies. 56 56 * @interface 57 - * @property {string} error - Error code or identifier 58 - * @property {string} [message] - Optional error message 57 + * @property error - Error code or identifier 58 + * @property [message] - Optional error message 59 59 */ 60 60 export type ErrorFrameBodyBase = { 61 61 error: string; ··· 66 66 * Generic error frame body with typed error codes. 67 67 * @template T - The type of error codes allowed 68 68 * @interface 69 - * @property {T} error - Typed error code 70 - * @property {string} [message] - Optional error message 69 + * @property error - Typed error code 70 + * @property [message] - Optional error message 71 71 */ 72 72 export type ErrorFrameBody<T extends string = string> = { 73 73 error: T; ··· 94 94 * Error class for handling WebSocket disconnections. 95 95 * @class 96 96 * @extends Error 97 - * @property {CloseCode} wsCode - WebSocket close code 98 - * @property {string} [xrpcCode] - XRPC-specific error code 97 + * @property wsCode - WebSocket close code 98 + * @property [xrpcCode] - XRPC-specific error code 99 99 */ 100 100 export class DisconnectError extends Error { 101 101 constructor( ··· 106 106 } 107 107 } 108 108 109 - /** 110 - * WebSocket close codes as defined in RFC 6455. 111 - * @see https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1 112 - * @enum {number} 113 - */ 114 109 export enum CloseCode { 115 110 /** Normal closure, meaning the purpose for which the connection was established has been fulfilled */ 116 111 Normal = 1000,
-50
xrpc-server/stream/websocket-keepalive.ts
··· 7 7 * @class 8 8 */ 9 9 export class WebSocketKeepAlive { 10 - /** Current WebSocket connection instance */ 11 10 public ws: WebSocket | null = null; 12 - /** Whether this is the first connection attempt */ 13 11 public initialSetup = true; 14 - /** Number of reconnection attempts made, or null if not reconnecting */ 15 12 public reconnects: number | null = null; 16 13 17 - /** 18 - * Creates a new WebSocket client with keep-alive functionality. 19 - * @constructor 20 - * @param {Object} opts - Client configuration options 21 - * @param {Function} opts.getUrl - Function to get the WebSocket URL 22 - * @param {number} [opts.maxReconnectSeconds] - Maximum backoff time between reconnection attempts 23 - * @param {AbortSignal} [opts.signal] - Signal for aborting the connection 24 - * @param {number} [opts.heartbeatIntervalMs] - Interval between heartbeat messages 25 - * @param {Function} [opts.onReconnectError] - Callback for handling reconnection errors 26 - */ 27 14 constructor( 28 15 public opts: WebSocketOptions & { 29 16 getUrl: () => Promise<string>; ··· 38 25 }, 39 26 ) {} 40 27 41 - /** 42 - * Implements the AsyncIterator protocol for receiving WebSocket messages. 43 - * Handles automatic reconnection and message buffering. 44 - * @returns {AsyncGenerator<Uint8Array>} An async generator that yields received messages 45 - */ 46 28 async *[Symbol.asyncIterator](): AsyncGenerator<Uint8Array> { 47 29 const maxReconnectMs = 1000 * (this.opts.maxReconnectSeconds ?? 64); 48 30 while (true) { ··· 194 176 } 195 177 } 196 178 197 - /** 198 - * Starts the heartbeat mechanism for a WebSocket connection. 199 - * Sends periodic ping messages and monitors for pong responses. 200 - * @param {WebSocket} ws - The WebSocket connection to monitor 201 - */ 202 179 startHeartbeat(ws: WebSocket) { 203 180 let isAlive = true; 204 181 let heartbeatInterval: ReturnType<typeof setInterval> | null = null; ··· 247 224 248 225 export default WebSocketKeepAlive; 249 226 250 - /** 251 - * Error class for abnormal WebSocket closures. 252 - * @class 253 - * @extends Error 254 - */ 255 227 class AbnormalCloseError extends Error { 256 228 code = "EWSABNORMALCLOSE"; 257 229 } ··· 276 248 return err !== null && typeof err === "object" && "code" in err; 277 249 } 278 250 279 - /** 280 - * Checks if an error should trigger a reconnection attempt. 281 - * Network-related errors are typically reconnectable. 282 - * @param {unknown} err - The error to check 283 - * @returns {boolean} True if the error should trigger a reconnection 284 - */ 285 251 function isReconnectable(err: unknown): boolean { 286 - // Network errors are reconnectable. 287 - // AuthenticationRequired and InvalidRequest XRPCErrors are not reconnectable. 288 - // @TODO method-specific XRPCErrors may be reconnectable, need to consider. Receiving 289 - // an invalid message is not current reconnectable, but the user can decide to skip them. 290 252 if (!isErrorWithCode(err)) return false; 291 253 return typeof err.code === "string" && networkErrorCodes.includes(err.code); 292 254 } ··· 305 267 "ECANCELED", 306 268 ]; 307 269 308 - /** 309 - * Calculates the backoff duration for reconnection attempts. 310 - * Uses exponential backoff with random jitter. 311 - * @param {number} n - The number of reconnection attempts so far 312 - * @param {number} maxMs - Maximum backoff duration in milliseconds 313 - * @returns {number} The backoff duration in milliseconds 314 - */ 315 270 function backoffMs(n: number, maxMs: number) { 316 271 const baseSec = Math.pow(2, n); // 1, 2, 4, ... 317 272 const randSec = Math.random() - 0.5; // Random jitter between -.5 and .5 seconds ··· 319 274 return Math.min(ms, maxMs); 320 275 } 321 276 322 - /** 323 - * Forwards abort signals from one AbortController to another. 324 - * @param {AbortSignal} signal - The source abort signal 325 - * @param {AbortController} ac - The target abort controller 326 - */ 327 277 function forwardSignal(signal: AbortSignal, ac: AbortController) { 328 278 if (signal.aborted) { 329 279 return ac.abort(signal.reason);
-14
xrpc-server/util.ts
··· 209 209 return base.trim().toLowerCase(); 210 210 } 211 211 212 - /** 213 - * Checks if an actual encoding matches the expected encoding. 214 - * Supports wildcard matching and JSON aliases. 215 - * @param expected - The expected encoding from the lexicon 216 - * @param actual - The actual encoding from the request 217 - * @returns True if the encodings are compatible 218 - */ 219 212 function isValidEncoding(output: LexXrpcBody, encoding: string) { 220 213 const normalized = normalizeMime(encoding); 221 214 if (!normalized) return false; ··· 226 219 227 220 type BodyPresence = "missing" | "empty" | "present"; 228 221 229 - /** 230 - * Determines if a request body is present or missing. 231 - * Considers empty strings and empty arrays as missing when no content type is provided. 232 - * @param body - The request body 233 - * @param contentType - The Content-Type header value 234 - * @returns "present" if body exists, "missing" otherwise 235 - */ 236 222 function getBodyPresence(req: Request): BodyPresence { 237 223 if (req.headers.get("transfer-encoding") != null) return "present"; 238 224 if (req.headers.get("content-length") === "0") return "empty";