···1111 updatedAt: number;
1212};
13131414+/**
1515+ * DID cache using memory, recommended for low scale
1616+ * use cases.
1717+ *
1818+ * Not compatible with Cloudflare Workers or serverless/edge
1919+ * setups
2020+ */
1421export class MemoryCache implements DidCache {
1522 public staleTTL: number;
1623 public maxTTL: number;
+4
identity/handle/index.ts
···44const SUBDOMAIN = "_atproto";
55const PREFIX = "did=";
6677+/**
88+ * Identity resolver for resolving handles to DIDs
99+ * using DNS
1010+ */
711export class HandleResolver {
812 public timeout: number;
913 private backupNameservers: string[] | undefined;
+57-13
xrpc-server/auth.ts
···44import * as crypto from "@atp/crypto";
55import { AuthRequiredError } from "./errors.ts";
6677-type ServiceJwtParams = {
77+/**
88+ * Parameters for service JWT creation
99+ * @prop iss The issuer of the key (corresponds to user DID)
1010+ * @prop aud The intended audience of the key, the service it's intended for
1111+ * @prop iat When the key was issued at
1212+ * @prop exp When the key expires
1313+ * @prop lxm Lexicon (XRPC) endpoints the key is allowed to be used for
1414+ * @prop keypair Signing key to be used to create the JWT token
1515+ */
1616+export type ServiceJwtParams = {
817 iss: string;
918 aud: string;
1019 iat?: number;
···1322 keypair: crypto.Keypair;
1423};
15241616-type ServiceJwtHeaders = {
2525+/**
2626+ * Headers of a service JWT token
2727+ * @prop alg Algorithm used for the JWT token's encoding
2828+ */
2929+export type ServiceJwtHeaders = {
1730 alg: string;
1831} & Record<string, unknown>;
19322020-type ServiceJwtPayload = {
3333+/**
3434+ * Parameters for service JWT creation
3535+ * @prop iss The issuer of the token (corresponds to user DID)
3636+ * @prop aud The intended audience of the token, the service it's intended for
3737+ * @prop exp When the key expires
3838+ * @prop lxm Lexicon (XRPC) endpoints the token is allowed to be used for
3939+ * @prop jti JWT Identifier
4040+ */
4141+export type ServiceJwtPayload = {
2142 iss: string;
2243 aud: string;
2344 exp: number;
···2546 jti?: string;
2647};
27482828-export const createServiceJwt = async (
4949+/**
5050+ * Create a JWT token string for service auth
5151+ * @param params Information and permissions given to the service JWT token
5252+ */
5353+export const createServiceJwt = (
2954 params: ServiceJwtParams,
3030-): Promise<string> => {
5555+): string => {
3156 const { iss, aud, keypair } = params;
3257 const iat = params.iat ?? Math.floor(Date.now() / 1e3);
3358 const exp = params.exp ?? iat + MINUTE / 1e3;
···4772 });
4873 const toSignStr = `${jsonToB64Url(header)}.${jsonToB64Url(payload)}`;
4974 const toSign = ui8.fromString(toSignStr, "utf8");
5050- const sig = await keypair.sign(toSign);
7575+ const sig = keypair.sign(toSign);
5176 return `${toSignStr}.${ui8.toString(sig, "base64url")}`;
5277};
5378···6893 * fetch(url, { headers: auth.headers });
6994 * ```
7095 */
7171-export const createServiceAuthHeaders = async (
9696+export const createServiceAuthHeaders = (
7297 params: ServiceJwtParams,
7373-): Promise<{ headers: { authorization: string } }> => {
7474- const jwt = await createServiceJwt(params);
9898+): { headers: { authorization: string } } => {
9999+ const jwt = createServiceJwt(params);
75100 return {
76101 headers: { authorization: `Bearer ${jwt}` },
77102 };
···81106 return common.utf8ToB64Url(JSON.stringify(json));
82107};
83108109109+/** Verify a message signature against a key */
84110export type VerifySignatureWithKeyFn = (
85111 key: string,
86112 msgBytes: Uint8Array,
···88114 alg: string,
89115) => boolean;
90116117117+/**
118118+ * Verify a JWT token is valid against the context in which
119119+ * it's being used, including the lxm matching the current endpoint,
120120+ * the aud matching the service DID, and the key itself matching
121121+ * the signing key of the DID who claims to have issued it
122122+ * @param jwtStr The JWT token being used
123123+ * @param ownDid The DID of the current service, null indicates to skip the audience check
124124+ * @param lxm The lexicon permissions of the JWT token, null indicates to skip the lxm check
125125+ * @param getSigningKey A function to get the signing key of the issuer
126126+ * @param verifySignatureWithKey A method to verify the signature with the JWT token,
127127+ */
91128export const verifyJwt = async (
92129 jwtStr: string,
9393- ownDid: string | null, // null indicates to skip the audience check
9494- lxm: string | null, // null indicates to skip the lxm check
130130+ ownDid: string | null,
131131+ lxm: string | null,
95132 getSigningKey: (
96133 iss: string,
97134 forceRefresh: boolean,
···153190154191 let validSig: boolean;
155192 try {
156156- validSig = await verifySignatureWithKey(
193193+ validSig = verifySignatureWithKey(
157194 signingKey,
158195 msgBytes,
159196 sigBytes,
···171208 const freshSigningKey = await getSigningKey(payload.iss, true);
172209 try {
173210 validSig = freshSigningKey !== signingKey
174174- ? await verifySignatureWithKey(
211211+ ? verifySignatureWithKey(
175212 freshSigningKey,
176213 msgBytes,
177214 sigBytes,
···196233 return payload;
197234};
198235236236+/**
237237+ * Default method to verify a JWT signature against a key.
238238+ * @param key to verify JWT token against
239239+ * @param msgBytes Corresponding message
240240+ * @param sigBytes JWT signature bytes to verify
241241+ * @param alg Encoding algorithm for JWT signature
242242+ */
199243export const cryptoVerifySignatureWithKey: VerifySignatureWithKeyFn = (
200244 key: string,
201245 msgBytes: Uint8Array,
+47
xrpc-server/rate-limiter.ts
···77import { ResponseType, XRPCError } from "./errors.ts";
88import { logger } from "./logger.ts";
991010+/** Context about the request and response */
1011export interface RateLimiterContext {
1112 req: Request;
1213 res?: Response;
1314}
14151616+/** Method type to calculate the group key of the request */
1517export type CalcKeyFn<C extends RateLimiterContext = RateLimiterContext> = (
1618 ctx: C,
1719) => string | null;
2020+2121+/** Method function to calculate the amount of points at a given time for a duration */
1822export type CalcPointsFn<C extends RateLimiterContext = RateLimiterContext> = (
1923 ctx: C,
2024) => number;
21252626+/** Generic rate limiter interface used for all rate limiters */
2227export interface RateLimiterI<
2328 C extends RateLimiterContext = RateLimiterContext,
2429> {
···2631 reset: RateLimiterReset<C>;
2732}
28333434+/** Method options for {@link RateLimiterConsume} */
2935export type RateLimiterConsumeOptions<
3036 C extends RateLimiterContext = RateLimiterContext,
3137> = {
···3339 calcPoints?: CalcPointsFn<C>;
3440};
35414242+/** Generic rate limiter request consume method type */
3643export type RateLimiterConsume<
3744 C extends RateLimiterContext = RateLimiterContext,
3845> = (
···4047 opts?: RateLimiterConsumeOptions<C>,
4148) => Promise<RateLimiterStatus | RateLimitExceededError | null>;
42495050+/** Information about the current status of the rate limiter
5151+ * @prop limit Current point limit
5252+ * @prop duration Duration limit is applied
5353+ * @prop remainingPoints The remaining points before the limit is reached
5454+ * @prop msBeforeNext Milliseconds before the next duration begins
5555+ * @prop consumedPoints Amount of points consumed in this duration
5656+ * @prop isFirstInDuration Is this the first point in this duration?
5757+ */
4358export type RateLimiterStatus = {
4459 limit: number;
4560 duration: number;
···4964 isFirstInDuration: boolean;
5065};
51666767+/** Options for {@link RateLimiterReset} */
5268export type RateLimiterResetOptions<
5369 C extends RateLimiterContext = RateLimiterContext,
5470> = {
5571 calcKey?: CalcKeyFn<C>;
5672};
57737474+/** Generic method type to reset any rate limiter **/
5875export type RateLimiterReset<
5976 C extends RateLimiterContext = RateLimiterContext,
6077> = (ctx: C, opts?: RateLimiterResetOptions<C>) => Promise<void>;
61787979+/**
8080+ * Generic options for {@link RateLimiter}
8181+ * @prop keyPrefix A prefix to differentiate multiple rate limiters
8282+ * @prop durationMs Millisecond duration of the period the limit is applied on
8383+ * @prop points Amount of points allowed over the duration
8484+ * @prop calcKey Method to calculate what rate limits apply to the request
8585+ * @prop calcPoints Method to calculate the group key for the request
8686+ * @prop failClosed Should all requests be rejected if the rate limiter fails?
8787+ */
6288export type RateLimiterOptions<
6389 C extends RateLimiterContext = RateLimiterContext,
6490> = {
···140166 }
141167}
142168169169+/**
170170+ * Rate limiter implementation using memory,
171171+ * recommended for low-scale uses.
172172+ *
173173+ * Should not be used with Cloudflare Workers
174174+ * or serverless/edge use cases.
175175+ */
143176export class MemoryRateLimiter<
144177 C extends RateLimiterContext = RateLimiterContext,
145178> extends RateLimiter<C> {
···153186 }
154187}
155188189189+/**
190190+ * Rate limiter implementation using Redis,
191191+ * recommended for medium or high scale
192192+ * or any serverless/edge use case.
193193+ */
156194export class RedisRateLimiter<
157195 C extends RateLimiterContext = RateLimiterContext,
158196> extends RateLimiter<C> {
···167205 }
168206}
169207208208+/**
209209+ * Method type to get rate limiter status
210210+ * from a response and a rate limiter instance
211211+ * @param limiter Rate limiter instance
212212+ * @param res Response returned by rate limiter
213213+ */
170214export const formatLimiterStatus = (
171215 limiter: RateLimiterAbstract,
172216 res: RateLimiterRes,
···181225 };
182226};
183227228228+/** Options for {@link WrappedRateLimiter} */
184229export type WrappedRateLimiterOptions<
185230 C extends RateLimiterContext = RateLimiterContext,
186231> = {
···276321 return lowest;
277322};
278323324324+/** Options for {@link RouteRateLimiter} */
279325export type RouteRateLimiterOptions<
280326 C extends RateLimiterContext = RateLimiterContext,
281327> = {
···349395 );
350396}
351397398398+/** XRPC error returned by rate limiter when rate limit has been exceeded */
352399export class RateLimitExceededError extends XRPCError {
353400 constructor(
354401 public status: RateLimiterStatus,
+2-1
xrpc-server/server.ts
···80808181/**
8282 * XRPC server implementation that handles HTTP and WebSocket requests.
8383- * Manages method registration, authentication, rate limiting, and streaming.
8383+ * Manages method registration, authentication, rate limiting, and streaming
8484+ * with automatic schema validation.
8485 */
8586export class Server {
8687 /** The underlying Hono HTTP server instance */
+3
xrpc-server/stream/stream.ts
···129129 }
130130}
131131132132+/** Ensure that a chunk adheres to the message frame type,
133133+ * and isn't an error or unknown frame type
134134+ */
132135export function ensureChunkIsMessage(chunk: Uint8Array): MessageFrame<unknown> {
133136 const frame = Frame.fromBytes(chunk);
134137 if (frame.isMessage()) {
+1
xrpc-server/stream/types.ts
···106106 }
107107}
108108109109+/** Different closure codes for normal, abnormal and policy violation closures. */
109110export enum CloseCode {
110111 /** Normal closure, meaning the purpose for which the connection was established has been fulfilled */
111112 Normal = 1000,
+17-6
xrpc-server/stream/websocket-keepalive.ts
···55import { CloseCode, DisconnectError } from "./types.ts";
66import { iterateBinary } from "./stream.ts";
7788-// Public options are web-standard and protocol-safe.
88+/**
99+ * Options for a {@link WebSocketKeepAlive} instance
1010+ *
1111+ * @prop getUrl Method to get the current URL of the websocket endpoint
1212+ * @prop maxReconnectSeconds Maximum time a request can take to reconnect
1313+ * @prop signal Abort signal to send when aborting connection
1414+ *
1515+ * @prop heartbeatIntervalMs Interval to send provided heartbeatPayload on,
1616+ * @prop heartbeatPayload Method to create payload to send for heartbeat
1717+ * @prop isPong If provided, we mark alive only when it returns true for a message
1818+ * if omitted, *any* message is considered proof of life
1919+ *
2020+ * @prop onReconnectError Reconnect hook
2121+ *
2222+ * @prop createSocket Socket factory override (lets you use custom client if needed)
2323+ * @prop protocols Override value for accepted protocols
2424+ */
925export type KeepAliveOptions = {
1026 getUrl: () => Promise<string>;
1127 maxReconnectSeconds?: number;
1228 signal?: AbortSignal;
13291414- // Heartbeat (optional, protocol-safe):
1515- // - If provided, we'll send this payload periodically.
1616- // - If `isPong` is provided, we mark alive only when it returns true for a message.
1717- // - If omitted, we consider *any* incoming message as proof of life.
1830 heartbeatIntervalMs?: number; // default 10 * SECOND
1931 heartbeatPayload?: () => string | ArrayBuffer | Uint8Array | Blob;
2032 isPong?: (data: unknown) => boolean;
···2234 // Reconnect hook
2335 onReconnectError?: (error: unknown, n: number, initialSetup: boolean) => void;
24362525- // Socket factory override (lets you use custom client if needed)
2637 createSocket?: (url: string, protocols?: string | string[]) => WebSocket;
2738 protocols?: string | string[];
2839};
+12-33
xrpc-server/types.ts
···8080 body: unknown;
8181};
82828383-/**
8484- * Result of successful authentication.
8585- */
8383+/** Result of successful authentication. */
8684export type AuthResult = {
8785 /** Authentication credentials (e.g., user info, tokens) */
8886 credentials: unknown;
···9088 artifacts?: unknown;
9189};
92909191+/** Zod Schema for request headers. */
9392export const headersSchema: z.ZodRecord<z.ZodString, z.ZodString> = z.record(
9493 z.string(),
9594 z.string(),
9695);
97969898-/**
9999- * HTTP headers as a record of string key-value pairs.
100100- */
9797+/** HTTP headers as a record of string key-value pairs. */
10198export type Headers = z.infer<typeof headersSchema>;
10299103100export const handlerSuccess: z.ZodObject<{
···110107 headers: headersSchema.optional(),
111108});
112109113113-/**
114114- * Successful response from a method handler.
115115- */
110110+/** Successful response from a method handler. */
116111export type HandlerSuccess = z.infer<typeof handlerSuccess>;
117112118118-/**
119119- * Handler response that pipes through a buffer.
120120- */
113113+/** Handler response that pipes through a buffer. */
121114export type HandlerPipeThroughBuffer = {
122115 /** Content encoding of the response */
123116 encoding: string;
···127120 headers?: Headers;
128121};
129122130130-/**
131131- * Handler response that pipes through a stream.
132132- */
123123+/** Handler response that pipes through a stream. */
133124export type HandlerPipeThroughStream = {
134125 /** Content encoding of the response */
135126 encoding: string;
···139130 headers?: Headers;
140131};
141132142142-/**
143143- * Union type for handler responses that pipe data through either a buffer or stream.
144144- */
133133+/** Union type for handler responses that pipe data through either a buffer or stream. */
145134export type HandlerPipeThrough =
146135 | HandlerPipeThroughBuffer
147136 | HandlerPipeThroughStream;
148137149149-/**
150150- * Authentication state for a handler context.
151151- */
138138+/** Authentication state for a handler context. */
152139export type Auth = void | AuthResult;
153140154154-/**
155155- * Input data for a handler context.
156156- */
141141+/** Input data for a handler context. */
157142export type Input = void | HandlerInput;
158143159159-/**
160160- * Output data from a handler.
161161- */
144144+/** Output data from a handler. */
162145export type Output = void | HandlerSuccess | ErrorResult;
163146164147/**
···364347 return "name" in opts && typeof opts.name === "string";
365348}
366349367367-/**
368368- * Options for configuring payload size limits by content type.
369369- */
350350+/** Options for configuring payload size limits by content type. */
370351export type RouteOptions = {
371352 /** Maximum size for binary/blob payloads in bytes */
372353 blobLimit?: number;
···376357 textLimit?: number;
377358};
378359379379-/**
380380- * Simplified route options with only blob limit configuration.
381381- */
360360+/** Simplified route options with only blob limit configuration. */
382361export type RouteOpts = {
383362 /** Maximum size for binary/blob payloads in bytes */
384363 blobLimit?: number;