@atproto/lex-server 0.0.11 → 0.0.13
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/CHANGELOG.md +27 -0
- package/README.md +38 -21
- package/dist/errors.d.ts +28 -58
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +72 -72
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -4
- package/dist/index.js.map +1 -1
- package/dist/{lex-server.d.ts → lex-router.d.ts} +55 -21
- package/dist/lex-router.d.ts.map +1 -0
- package/dist/{lex-server.js → lex-router.js} +169 -73
- package/dist/lex-router.js.map +1 -0
- package/dist/lib/drain-websocket.d.ts +7 -0
- package/dist/lib/drain-websocket.d.ts.map +1 -1
- package/dist/lib/drain-websocket.js +11 -0
- package/dist/lib/drain-websocket.js.map +1 -1
- package/dist/lib/www-authenticate.d.ts +4 -3
- package/dist/lib/www-authenticate.d.ts.map +1 -1
- package/dist/lib/www-authenticate.js +29 -16
- package/dist/lib/www-authenticate.js.map +1 -1
- package/dist/nodejs.d.ts +1 -1
- package/dist/nodejs.d.ts.map +1 -1
- package/dist/nodejs.js +1 -1
- package/dist/nodejs.js.map +1 -1
- package/dist/service-auth.d.ts +1 -1
- package/dist/service-auth.d.ts.map +1 -1
- package/dist/service-auth.js.map +1 -1
- package/package.json +9 -8
- package/src/errors.test.ts +262 -0
- package/src/errors.ts +103 -78
- package/src/index.ts +1 -7
- package/src/{lex-server.test.ts → lex-router.test.ts} +591 -24
- package/src/{lex-server.ts → lex-router.ts} +275 -119
- package/src/lib/drain-websocket.ts +11 -0
- package/src/lib/www-authenticate.test.ts +134 -0
- package/src/lib/www-authenticate.ts +36 -17
- package/src/nodejs.ts +2 -2
- package/src/service-auth.ts +1 -1
- package/dist/lex-server.d.ts.map +0 -1
- package/dist/lex-server.js.map +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { InferMethodInput, InferMethodMessage, InferMethodOutput, InferMethodOutputBody, InferMethodOutputEncoding, InferMethodParams, Main, NsidString, Procedure, Query, Subscription } from '@atproto/lex-schema';
|
|
1
|
+
import { DidString, InferMethodInput, InferMethodMessage, InferMethodOutput, InferMethodOutputBody, InferMethodOutputEncoding, InferMethodParams, Main, NsidString, Procedure, Query, Subscription } from '@atproto/lex-schema';
|
|
2
|
+
import { LexServerError } from './errors.js';
|
|
2
3
|
type Awaitable<T> = T | Promise<T>;
|
|
3
4
|
/**
|
|
4
5
|
* Union type representing the supported Lexicon method types.
|
|
@@ -270,7 +271,7 @@ export type LexRouterSubscriptionConfig<Method extends Subscription = Subscripti
|
|
|
270
271
|
* ```typescript
|
|
271
272
|
* const authHandler: LexRouterAuth<UserCredentials> = async (ctx) => {
|
|
272
273
|
* const token = ctx.request.headers.get('authorization')
|
|
273
|
-
* if (!token) throw new
|
|
274
|
+
* if (!token) throw new LexServerAuthError('AuthenticationRequired', 'Missing token')
|
|
274
275
|
* return { userId: await verifyToken(token) }
|
|
275
276
|
* }
|
|
276
277
|
* ```
|
|
@@ -299,8 +300,9 @@ export type LexRouterAuthContext<Method extends LexMethod = LexMethod> = {
|
|
|
299
300
|
* // Simple token-based auth
|
|
300
301
|
* const tokenAuth: LexRouterAuth<{ userId: string }> = async ({ request }) => {
|
|
301
302
|
* const token = request.headers.get('authorization')?.replace('Bearer ', '')
|
|
302
|
-
* if (!token) throw new
|
|
303
|
+
* if (!token) throw new LexServerAuthError('AuthenticationRequired', 'Token required')
|
|
303
304
|
* const userId = await verifyToken(token)
|
|
305
|
+
* if (!userId) throw new LexServerAuthError('AuthenticationRequired', 'Invalid token')
|
|
304
306
|
* return { userId }
|
|
305
307
|
* }
|
|
306
308
|
*
|
|
@@ -315,14 +317,18 @@ export type LexRouterAuth<Credentials = unknown, Method extends LexMethod = LexM
|
|
|
315
317
|
*
|
|
316
318
|
* Used for logging and monitoring errors that occur during request handling.
|
|
317
319
|
*/
|
|
318
|
-
export type
|
|
319
|
-
/** The error that was thrown during handling. */
|
|
320
|
-
error: unknown;
|
|
321
|
-
/** The original HTTP request that triggered the error. */
|
|
320
|
+
export type HandlerErrorContext = {
|
|
322
321
|
request: Request;
|
|
323
|
-
/** The Lexicon method that was being executed. */
|
|
324
322
|
method: LexMethod;
|
|
323
|
+
error: LexServerError;
|
|
324
|
+
};
|
|
325
|
+
export type HandlerErrorHook = (ctx: HandlerErrorContext) => void | Promise<void>;
|
|
326
|
+
export type SocketErrorContext = {
|
|
327
|
+
request: Request;
|
|
328
|
+
method: Subscription;
|
|
329
|
+
error: unknown;
|
|
325
330
|
};
|
|
331
|
+
export type SocketErrorHook = (ctx: SocketErrorContext) => void | Promise<void>;
|
|
326
332
|
/**
|
|
327
333
|
* Function that upgrades an HTTP request to a WebSocket connection.
|
|
328
334
|
*
|
|
@@ -346,6 +352,10 @@ export type UpgradeWebSocket = (request: Request) => {
|
|
|
346
352
|
/** The HTTP response to return (101 Switching Protocols). */
|
|
347
353
|
response: Response;
|
|
348
354
|
};
|
|
355
|
+
export type HealthCheckHandler = (request: Request) => Awaitable<{
|
|
356
|
+
[x: string]: unknown;
|
|
357
|
+
status: 'ok';
|
|
358
|
+
}>;
|
|
349
359
|
/**
|
|
350
360
|
* Configuration options for the {@link LexRouter}.
|
|
351
361
|
*
|
|
@@ -364,25 +374,45 @@ export type UpgradeWebSocket = (request: Request) => {
|
|
|
364
374
|
*/
|
|
365
375
|
export type LexRouterOptions = {
|
|
366
376
|
/**
|
|
367
|
-
* Function to upgrade HTTP requests to WebSocket connections.
|
|
368
|
-
*
|
|
369
|
-
* upgradeWebSocket if available.
|
|
377
|
+
* Function to upgrade HTTP requests to WebSocket connections. Required for
|
|
378
|
+
* subscription methods. Defaults to Deno's built-in
|
|
379
|
+
* {@link globalThis.upgradeWebSocket} if available. For NodeJS, use the
|
|
380
|
+
* homonymous export from `@atproto/lex-server/nodejs`.
|
|
370
381
|
*/
|
|
371
382
|
upgradeWebSocket?: UpgradeWebSocket;
|
|
372
383
|
/**
|
|
373
|
-
* Callback invoked when an error occurs during request handling.
|
|
374
|
-
*
|
|
375
|
-
*
|
|
384
|
+
* Callback invoked when an error occurs during request handling. Useful for
|
|
385
|
+
* logging and error reporting. Not called for client-induced errors (e.g.,
|
|
386
|
+
* request abortion).
|
|
387
|
+
*/
|
|
388
|
+
onHandlerError?: HandlerErrorHook;
|
|
389
|
+
/**
|
|
390
|
+
* Optional hook for handling errors during generation of WebSocket messages.
|
|
376
391
|
*/
|
|
377
|
-
|
|
392
|
+
onSocketError?: SocketErrorHook;
|
|
378
393
|
/**
|
|
379
|
-
*
|
|
380
|
-
*
|
|
394
|
+
* Optional health check handler. If provided, this function will be called
|
|
395
|
+
* for requests to the /xrpc/_health endpoint, allowing for custom health
|
|
396
|
+
* check logic and responses.
|
|
397
|
+
*
|
|
398
|
+
* If not provided, the server will respond to /xrpc/_health requests with a
|
|
399
|
+
* default JSON response of `{ status: 'ok' }`.
|
|
400
|
+
*/
|
|
401
|
+
healthCheck?: HealthCheckHandler;
|
|
402
|
+
/**
|
|
403
|
+
* Optional fallback handler for requests that are not /xrpc/ paths. Can be
|
|
404
|
+
* used to serve static files or other routes. If not provided, non-/xrpc/
|
|
405
|
+
* requests will return 404 responses.
|
|
406
|
+
*/
|
|
407
|
+
fallback?: FetchHandler;
|
|
408
|
+
/**
|
|
409
|
+
* High water mark for WebSocket backpressure (in bytes). When buffered data
|
|
410
|
+
* exceeds this, the handler will wait before sending more.
|
|
381
411
|
*/
|
|
382
412
|
highWaterMark?: number;
|
|
383
413
|
/**
|
|
384
|
-
* Low water mark for WebSocket backpressure (in bytes).
|
|
385
|
-
*
|
|
414
|
+
* Low water mark for WebSocket backpressure (in bytes). The handler resumes
|
|
415
|
+
* sending when buffered data drops below this.
|
|
386
416
|
*/
|
|
387
417
|
lowWaterMark?: number;
|
|
388
418
|
};
|
|
@@ -527,7 +557,7 @@ export declare class LexRouter {
|
|
|
527
557
|
add<M extends LexMethod, Credentials = unknown>(ns: Main<M>, config: M extends Subscription ? LexRouterSubscriptionHandler<M, Credentials> | LexRouterSubscriptionConfig<M, Credentials> : M extends Query | Procedure ? LexRouterMethodHandler<M, Credentials> | LexRouterMethodConfig<M, Credentials> : never): this;
|
|
528
558
|
private buildMethodHandler;
|
|
529
559
|
private buildSubscriptionHandler;
|
|
530
|
-
private
|
|
560
|
+
private handlerError;
|
|
531
561
|
/**
|
|
532
562
|
* The main fetch handler for processing XRPC requests.
|
|
533
563
|
*
|
|
@@ -559,5 +589,9 @@ export declare class LexRouter {
|
|
|
559
589
|
*/
|
|
560
590
|
fetch: FetchHandler;
|
|
561
591
|
}
|
|
592
|
+
export type ServiceProxyInfo = {
|
|
593
|
+
did: DidString;
|
|
594
|
+
serviceId: string;
|
|
595
|
+
};
|
|
562
596
|
export {};
|
|
563
|
-
//# sourceMappingURL=lex-
|
|
597
|
+
//# sourceMappingURL=lex-router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lex-router.d.ts","sourceRoot":"","sources":["../src/lex-router.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,7 +5,10 @@ const lex_cbor_1 = require("@atproto/lex-cbor");
|
|
|
5
5
|
const lex_data_1 = require("@atproto/lex-data");
|
|
6
6
|
const lex_json_1 = require("@atproto/lex-json");
|
|
7
7
|
const lex_schema_1 = require("@atproto/lex-schema");
|
|
8
|
+
const errors_js_1 = require("./errors.js");
|
|
8
9
|
const drain_websocket_js_1 = require("./lib/drain-websocket.js");
|
|
10
|
+
const XRPC_PATH_PREFIX = '/xrpc/';
|
|
11
|
+
const XRPC_HEALTH_CHECK_PATH = '/xrpc/_health';
|
|
9
12
|
/**
|
|
10
13
|
* XRPC router for handling AT Protocol Lexicon methods.
|
|
11
14
|
*
|
|
@@ -88,16 +91,17 @@ class LexRouter {
|
|
|
88
91
|
}
|
|
89
92
|
add(ns, config) {
|
|
90
93
|
const method = (0, lex_schema_1.getMain)(ns);
|
|
91
|
-
|
|
94
|
+
const nsid = normalizeNsid(method.nsid);
|
|
95
|
+
if (this.handlers.has(nsid)) {
|
|
92
96
|
throw new TypeError(`Method ${method.nsid} already registered`);
|
|
93
97
|
}
|
|
94
98
|
const methodConfig = typeof config === 'function'
|
|
95
99
|
? { handler: config, auth: undefined }
|
|
96
100
|
: config;
|
|
97
|
-
const
|
|
101
|
+
const handler = method.type === 'subscription'
|
|
98
102
|
? this.buildSubscriptionHandler(method, methodConfig.handler, methodConfig.auth)
|
|
99
103
|
: this.buildMethodHandler(method, methodConfig.handler, methodConfig.auth);
|
|
100
|
-
this.handlers.set(
|
|
104
|
+
this.handlers.set(nsid, handler);
|
|
101
105
|
return this;
|
|
102
106
|
}
|
|
103
107
|
buildMethodHandler(method, methodHandler, auth) {
|
|
@@ -111,7 +115,7 @@ class LexRouter {
|
|
|
111
115
|
(method.type === 'query' &&
|
|
112
116
|
request.method !== 'GET' &&
|
|
113
117
|
request.method !== 'HEAD')) {
|
|
114
|
-
return
|
|
118
|
+
return invalidRequestResponse('Method not allowed', 405);
|
|
115
119
|
}
|
|
116
120
|
try {
|
|
117
121
|
const url = new URL(request.url);
|
|
@@ -149,34 +153,28 @@ class LexRouter {
|
|
|
149
153
|
});
|
|
150
154
|
}
|
|
151
155
|
catch (error) {
|
|
152
|
-
return this.
|
|
156
|
+
return this.handlerError(request, method, error);
|
|
153
157
|
}
|
|
154
158
|
};
|
|
155
159
|
}
|
|
156
160
|
buildSubscriptionHandler(method, methodHandler, auth) {
|
|
157
|
-
const {
|
|
161
|
+
const { onSocketError, upgradeWebSocket = globalThis.Deno?.upgradeWebSocket, } = this.options;
|
|
158
162
|
if (!upgradeWebSocket) {
|
|
159
163
|
throw new TypeError('WebSocket upgrade not supported in this environment. Please provide an upgradeWebSocket option when creating the LexRouter.');
|
|
160
164
|
}
|
|
161
165
|
return async (request, connection) => {
|
|
162
166
|
if (request.method !== 'GET') {
|
|
163
|
-
return
|
|
167
|
+
return invalidRequestResponse('Method not allowed', 405);
|
|
164
168
|
}
|
|
165
169
|
if (request.headers.get('connection')?.toLowerCase() !== 'upgrade' ||
|
|
166
170
|
request.headers.get('upgrade')?.toLowerCase() !== 'websocket') {
|
|
167
|
-
return
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}, {
|
|
171
|
-
status: 426,
|
|
172
|
-
headers: {
|
|
173
|
-
Connection: 'Upgrade',
|
|
174
|
-
Upgrade: 'websocket',
|
|
175
|
-
},
|
|
171
|
+
return invalidRequestResponse('XRPC subscriptions are only available over WebSocket', 426, {
|
|
172
|
+
Connection: 'Upgrade',
|
|
173
|
+
Upgrade: 'websocket',
|
|
176
174
|
});
|
|
177
175
|
}
|
|
178
176
|
if (request.signal.aborted) {
|
|
179
|
-
return
|
|
177
|
+
return invalidRequestResponse('Request aborted', 499);
|
|
180
178
|
}
|
|
181
179
|
try {
|
|
182
180
|
const { response, socket } = upgradeWebSocket(request);
|
|
@@ -186,11 +184,6 @@ class LexRouter {
|
|
|
186
184
|
const abortController = new AbortController();
|
|
187
185
|
const { signal } = abortController;
|
|
188
186
|
const abort = () => abortController.abort();
|
|
189
|
-
const onMessage = (event) => {
|
|
190
|
-
const error = new lex_data_1.LexError('InvalidRequest', 'XRPC subscriptions do not accept messages', { cause: event });
|
|
191
|
-
socket.send(encodeErrorFrame(error));
|
|
192
|
-
socket.close(1008, error.error);
|
|
193
|
-
};
|
|
194
187
|
const onOpen = async () => {
|
|
195
188
|
try {
|
|
196
189
|
const url = new URL(request.url);
|
|
@@ -208,10 +201,23 @@ class LexRouter {
|
|
|
208
201
|
signal,
|
|
209
202
|
});
|
|
210
203
|
const iterator = iterable[Symbol.asyncIterator]();
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
204
|
+
if (iterator.return) {
|
|
205
|
+
signal.addEventListener('abort', () => {
|
|
206
|
+
// @NOTE if iterator.return() throws, and no onSocketError is
|
|
207
|
+
// provided, or if onSocketError itself throws, the error will
|
|
208
|
+
// be unhandled, causing the process to crash. This is
|
|
209
|
+
// intentional, as it surfaces critical errors that occur
|
|
210
|
+
// during cleanup of the subscription.
|
|
211
|
+
void new Promise((resolve) => {
|
|
212
|
+
// Wrapping in new Promise to catch any potential sync errors thrown by iterator.return()
|
|
213
|
+
resolve(iterator.return());
|
|
214
|
+
}).catch(onSocketError
|
|
215
|
+
? (error) => onSocketError({ request, method, error })
|
|
216
|
+
: null);
|
|
217
|
+
}, {
|
|
218
|
+
once: true,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
215
221
|
while (!signal.aborted && socket.readyState === 1) {
|
|
216
222
|
const result = await iterator.next();
|
|
217
223
|
if (result.done)
|
|
@@ -230,17 +236,24 @@ class LexRouter {
|
|
|
230
236
|
catch (error) {
|
|
231
237
|
// If the socket is still open, send an error frame before closing
|
|
232
238
|
if (socket.readyState === 1) {
|
|
233
|
-
const
|
|
234
|
-
? error
|
|
235
|
-
: new lex_data_1.LexError('InternalError', 'An internal error occurred');
|
|
236
|
-
socket.send(encodeErrorFrame(lexError));
|
|
237
|
-
socket.close(
|
|
239
|
+
const isLexError = error instanceof lex_data_1.LexError;
|
|
238
240
|
// https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1
|
|
239
|
-
|
|
241
|
+
const code = isLexError && method.errors?.includes(error.error)
|
|
242
|
+
? 1008 // Policy Violation for known LexErrors
|
|
243
|
+
: 1011; // Internal Error for unexpected errors
|
|
244
|
+
if (isLexError) {
|
|
245
|
+
socket.send(encodeErrorFrame(error.toJSON()));
|
|
246
|
+
socket.close(code, error.error);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
const error = 'InternalServerError';
|
|
250
|
+
const message = 'An internal error occurred';
|
|
251
|
+
socket.send(encodeErrorFrame({ error, message }));
|
|
252
|
+
socket.close(code, error);
|
|
253
|
+
}
|
|
240
254
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
await onHandlerError({ error, request, method });
|
|
255
|
+
if (onSocketError && !isAbortReason(signal, error)) {
|
|
256
|
+
await onSocketError({ request, method, error });
|
|
244
257
|
}
|
|
245
258
|
}
|
|
246
259
|
finally {
|
|
@@ -254,20 +267,20 @@ class LexRouter {
|
|
|
254
267
|
return response;
|
|
255
268
|
}
|
|
256
269
|
catch (error) {
|
|
257
|
-
return this.
|
|
270
|
+
return this.handlerError(request, method, error);
|
|
258
271
|
}
|
|
259
272
|
};
|
|
260
273
|
}
|
|
261
|
-
async
|
|
274
|
+
async handlerError(request, method, cause) {
|
|
262
275
|
// Only report unexpected processing errors
|
|
276
|
+
if (isAbortReason(request.signal, cause)) {
|
|
277
|
+
return Response.json({ error: 'RequestAborted' }, { status: 499 });
|
|
278
|
+
}
|
|
279
|
+
const error = errors_js_1.LexServerError.from(cause);
|
|
263
280
|
const { onHandlerError } = this.options;
|
|
264
|
-
if (onHandlerError
|
|
281
|
+
if (onHandlerError)
|
|
265
282
|
await onHandlerError({ error, request, method });
|
|
266
|
-
|
|
267
|
-
if (error instanceof lex_data_1.LexError) {
|
|
268
|
-
return error.toResponse();
|
|
269
|
-
}
|
|
270
|
-
return Response.json({ error: 'InternalError', message: 'An internal error occurred' }, { status: 500 });
|
|
283
|
+
return error.toResponse();
|
|
271
284
|
}
|
|
272
285
|
/**
|
|
273
286
|
* The main fetch handler for processing XRPC requests.
|
|
@@ -299,17 +312,47 @@ class LexRouter {
|
|
|
299
312
|
* ```
|
|
300
313
|
*/
|
|
301
314
|
fetch = async (request, connection) => {
|
|
302
|
-
const
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
return Response
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
315
|
+
const { pathname } = new URL(request.url);
|
|
316
|
+
const atprotoProxy = request.headers.get('atproto-proxy');
|
|
317
|
+
if (!pathname.startsWith(XRPC_PATH_PREFIX)) {
|
|
318
|
+
// Handle non XRPC paths
|
|
319
|
+
const { fallback } = this.options;
|
|
320
|
+
if (fallback)
|
|
321
|
+
return fallback(request, connection);
|
|
322
|
+
return new Response('Not Found', { status: 404 });
|
|
323
|
+
}
|
|
324
|
+
if (pathname === XRPC_HEALTH_CHECK_PATH) {
|
|
325
|
+
if (request.method !== 'GET') {
|
|
326
|
+
return invalidRequestResponse('Method not allowed', 405);
|
|
327
|
+
}
|
|
328
|
+
if (atprotoProxy != null) {
|
|
329
|
+
return invalidRequestResponse('atproto-proxy header is not allowed on health check endpoint');
|
|
330
|
+
}
|
|
331
|
+
const { healthCheck } = this.options;
|
|
332
|
+
const data = healthCheck ? await healthCheck(request) : { status: 'ok' };
|
|
333
|
+
return Response.json(data);
|
|
334
|
+
}
|
|
335
|
+
const subPath = pathname.slice(XRPC_PATH_PREFIX.length);
|
|
336
|
+
if (!(0, lex_schema_1.isNsidString)(subPath)) {
|
|
337
|
+
return invalidRequestResponse('Invalid NSID in URL path');
|
|
338
|
+
}
|
|
339
|
+
const nsid = normalizeNsid(subPath);
|
|
340
|
+
if (atprotoProxy == null) {
|
|
341
|
+
const handler = this.handlers.get(nsid);
|
|
342
|
+
if (handler)
|
|
343
|
+
return handler(request, connection);
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
// Handle service proxying logic.
|
|
347
|
+
const proxyInfo = parseAtprotoProxyHeader(atprotoProxy);
|
|
348
|
+
if (!proxyInfo) {
|
|
349
|
+
return invalidRequestResponse(`Invalid atproto-proxy header value: ${atprotoProxy}`);
|
|
350
|
+
}
|
|
351
|
+
// @TODO actually implement service proxying logic here. The reason it was
|
|
352
|
+
// not done already is because we want to perform all the heavy lifting
|
|
353
|
+
// here, while still allowing the possibility to override the endpoint
|
|
354
|
+
// resolution, etc.
|
|
355
|
+
// @NOTE see ./service-auth.ts for potential common code (did resolver, etc.)
|
|
313
356
|
}
|
|
314
357
|
return Response.json({
|
|
315
358
|
error: 'MethodNotImplemented',
|
|
@@ -318,16 +361,6 @@ class LexRouter {
|
|
|
318
361
|
};
|
|
319
362
|
}
|
|
320
363
|
exports.LexRouter = LexRouter;
|
|
321
|
-
function extractMethodNsid(request) {
|
|
322
|
-
const { pathname } = new URL(request.url);
|
|
323
|
-
if (!pathname.startsWith('/xrpc/'))
|
|
324
|
-
return null;
|
|
325
|
-
if (pathname.includes('/', 6))
|
|
326
|
-
return null;
|
|
327
|
-
// We don't really need to validate the NSID here, the existence of the route
|
|
328
|
-
// (which is looked up based on an NSID) is sufficient.
|
|
329
|
-
return pathname.slice(6);
|
|
330
|
-
}
|
|
331
364
|
async function getProcedureInput(request) {
|
|
332
365
|
const encodingRaw = request.headers
|
|
333
366
|
.get('content-type')
|
|
@@ -341,7 +374,10 @@ async function getProcedureInput(request) {
|
|
|
341
374
|
? 'application/octet-stream'
|
|
342
375
|
: undefined);
|
|
343
376
|
if (!this.input.matchesEncoding(encoding)) {
|
|
344
|
-
throw new
|
|
377
|
+
throw new errors_js_1.LexServerError(400, {
|
|
378
|
+
error: 'InvalidRequest',
|
|
379
|
+
message: `Invalid content-type: ${encoding}`,
|
|
380
|
+
});
|
|
345
381
|
}
|
|
346
382
|
if (this.input.encoding === 'application/json') {
|
|
347
383
|
// @TODO limit size?
|
|
@@ -361,14 +397,27 @@ async function getQueryInput(request) {
|
|
|
361
397
|
if (request.body ||
|
|
362
398
|
request.headers.has('content-type') ||
|
|
363
399
|
request.headers.has('content-length')) {
|
|
364
|
-
throw new
|
|
400
|
+
throw new errors_js_1.LexServerError(400, {
|
|
401
|
+
error: 'InvalidRequest',
|
|
402
|
+
message: 'GET requests must not have a body',
|
|
403
|
+
});
|
|
365
404
|
}
|
|
366
405
|
return undefined;
|
|
367
406
|
}
|
|
407
|
+
function onMessage(_event) {
|
|
408
|
+
const error = 'InvalidRequest';
|
|
409
|
+
const message = 'XRPC subscriptions do not accept messages';
|
|
410
|
+
this.send(encodeErrorFrame({ error, message }));
|
|
411
|
+
// 1003 indicates that an endpoint is terminating the connection
|
|
412
|
+
// because it has received a type of data it cannot accept (e.g., an
|
|
413
|
+
// endpoint that understands only text data MAY send this if it
|
|
414
|
+
// receives a binary message).
|
|
415
|
+
this.close(1003, error);
|
|
416
|
+
}
|
|
368
417
|
// Pre-encoded frame header for error frames
|
|
369
418
|
const ERROR_FRAME_HEADER = /*#__PURE__*/ (0, lex_cbor_1.encode)({ op: -1 });
|
|
370
|
-
function encodeErrorFrame(
|
|
371
|
-
return (0, lex_data_1.ui8Concat)([ERROR_FRAME_HEADER, (0, lex_cbor_1.encode)(
|
|
419
|
+
function encodeErrorFrame(errorData) {
|
|
420
|
+
return (0, lex_data_1.ui8Concat)([ERROR_FRAME_HEADER, (0, lex_cbor_1.encode)(errorData)]);
|
|
372
421
|
}
|
|
373
422
|
// Pre-encoded frame header for message frames with unknown type
|
|
374
423
|
const UNKNOWN_MESSAGE_FRAME_HEADER = /*#__PURE__*/ (0, lex_cbor_1.encode)({ op: 1 });
|
|
@@ -392,9 +441,56 @@ function encodeMessageFrame(method, value) {
|
|
|
392
441
|
return (0, lex_data_1.ui8Concat)([UNKNOWN_MESSAGE_FRAME_HEADER, (0, lex_cbor_1.encode)(value)]);
|
|
393
442
|
}
|
|
394
443
|
function isAbortReason(signal, error) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
(error
|
|
444
|
+
return (signal.aborted &&
|
|
445
|
+
signal.reason != null &&
|
|
446
|
+
error instanceof Error &&
|
|
447
|
+
(error === signal.reason || error.cause === signal.reason));
|
|
448
|
+
}
|
|
449
|
+
function parseAtprotoProxyHeader(value) {
|
|
450
|
+
// /!\ Hot path
|
|
451
|
+
// (fast) sanity check to avoid unnecessary parsing for non-DID values
|
|
452
|
+
if (!value.startsWith('did:'))
|
|
453
|
+
return null;
|
|
454
|
+
// The format is expected to be `did:example:service#serviceId`
|
|
455
|
+
const hashIndex = value.indexOf('#');
|
|
456
|
+
if (hashIndex === -1)
|
|
457
|
+
return null;
|
|
458
|
+
const fragmentIndex = hashIndex + 1;
|
|
459
|
+
// Basic validation if the fragment
|
|
460
|
+
if (fragmentIndex === value.length)
|
|
461
|
+
return null;
|
|
462
|
+
if (value.includes('#', fragmentIndex))
|
|
463
|
+
return null;
|
|
464
|
+
if (value.includes(' ', fragmentIndex))
|
|
465
|
+
return null;
|
|
466
|
+
const did = value.slice(0, hashIndex);
|
|
467
|
+
if (!(0, lex_schema_1.isDidString)(did))
|
|
468
|
+
return null;
|
|
469
|
+
const serviceId = value.slice(fragmentIndex);
|
|
470
|
+
return { did, serviceId };
|
|
471
|
+
}
|
|
472
|
+
function normalizeNsid(nsid) {
|
|
473
|
+
const lastDotIdx = nsid.lastIndexOf('.');
|
|
474
|
+
// The domain name part of the NSID is case-insensitive, but the last part is
|
|
475
|
+
// case-sensitive. Normalize the domain part to lowercase.
|
|
476
|
+
if (lastDotIdx !== -1 && hasUpperCase(nsid, 0, lastDotIdx)) {
|
|
477
|
+
return `${nsid.slice(0, lastDotIdx).toLowerCase()}.${nsid.slice(lastDotIdx + 1)}`;
|
|
478
|
+
}
|
|
479
|
+
return nsid;
|
|
480
|
+
}
|
|
481
|
+
function hasUpperCase(str, start = 0, end = str.length) {
|
|
482
|
+
for (let i = start; i < end; i++) {
|
|
483
|
+
const code = str.charCodeAt(i);
|
|
484
|
+
if (code >= 0x41 && code <= 0x5a) {
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
function invalidRequestResponse(message, status = 400, headers) {
|
|
491
|
+
return Response.json({
|
|
492
|
+
error: 'InvalidRequest',
|
|
493
|
+
message,
|
|
494
|
+
}, { status, headers });
|
|
399
495
|
}
|
|
400
|
-
//# sourceMappingURL=lex-
|
|
496
|
+
//# sourceMappingURL=lex-router.js.map
|