@atproto/xrpc-server 0.7.18 → 0.8.0
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 +24 -0
- package/LICENSE.txt +1 -1
- package/dist/logger.d.ts +1 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +3 -2
- package/dist/logger.js.map +1 -1
- package/dist/rate-limiter.d.ts +48 -16
- package/dist/rate-limiter.d.ts.map +1 -1
- package/dist/rate-limiter.js +130 -53
- package/dist/rate-limiter.js.map +1 -1
- package/dist/server.d.ts +5 -5
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +106 -129
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +7 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/util.d.ts +3 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +12 -1
- package/dist/util.js.map +1 -1
- package/package.json +1 -1
- package/src/logger.ts +3 -1
- package/src/rate-limiter.ts +125 -51
- package/src/server.ts +154 -164
- package/src/types.ts +12 -2
- package/src/util.ts +15 -1
- package/tests/rate-limiter.test.ts +2 -5
- package/tsconfig.tests.tsbuildinfo +1 -1
package/src/server.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
1
2
|
import { Readable } from 'node:stream'
|
|
2
3
|
import { pipeline } from 'node:stream/promises'
|
|
3
4
|
import express, {
|
|
@@ -21,20 +22,18 @@ import {
|
|
|
21
22
|
Lexicons,
|
|
22
23
|
lexToJson,
|
|
23
24
|
} from '@atproto/lexicon'
|
|
24
|
-
import log from './logger'
|
|
25
|
-
import {
|
|
25
|
+
import log, { LOGGER_NAME } from './logger'
|
|
26
|
+
import { RouteRateLimiter, WrappedRateLimiter } from './rate-limiter'
|
|
26
27
|
import { ErrorFrame, Frame, MessageFrame, XrpcStreamServer } from './stream'
|
|
27
28
|
import {
|
|
28
29
|
AuthVerifier,
|
|
29
30
|
HandlerAuth,
|
|
30
|
-
HandlerPipeThrough,
|
|
31
31
|
HandlerSuccess,
|
|
32
32
|
InternalServerError,
|
|
33
33
|
InvalidRequestError,
|
|
34
34
|
MethodNotImplementedError,
|
|
35
35
|
Options,
|
|
36
36
|
Params,
|
|
37
|
-
RateLimitExceededError,
|
|
38
37
|
RateLimiterI,
|
|
39
38
|
XRPCError,
|
|
40
39
|
XRPCHandler,
|
|
@@ -48,8 +47,10 @@ import {
|
|
|
48
47
|
isShared,
|
|
49
48
|
} from './types'
|
|
50
49
|
import {
|
|
50
|
+
asArray,
|
|
51
51
|
decodeQueryParams,
|
|
52
52
|
getQueryParams,
|
|
53
|
+
setHeaders,
|
|
53
54
|
validateInput,
|
|
54
55
|
validateOutput,
|
|
55
56
|
} from './util'
|
|
@@ -65,9 +66,8 @@ export class Server {
|
|
|
65
66
|
lex = new Lexicons()
|
|
66
67
|
options: Options
|
|
67
68
|
middleware: Record<'json' | 'text', RequestHandler>
|
|
68
|
-
|
|
69
|
-
sharedRateLimiters
|
|
70
|
-
routeRateLimiters: Record<string, RateLimiterI[]>
|
|
69
|
+
globalRateLimiter?: RouteRateLimiter
|
|
70
|
+
sharedRateLimiters?: Map<string, RateLimiterI>
|
|
71
71
|
|
|
72
72
|
constructor(lexicons?: LexiconDoc[], opts: Options = {}) {
|
|
73
73
|
if (lexicons) {
|
|
@@ -84,25 +84,26 @@ export class Server {
|
|
|
84
84
|
json: jsonParser({ limit: opts?.payload?.jsonLimit }),
|
|
85
85
|
text: textParser({ limit: opts?.payload?.textLimit }),
|
|
86
86
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
...limit
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
87
|
+
|
|
88
|
+
if (opts.rateLimits) {
|
|
89
|
+
const { global, shared, creator, bypass } = opts.rateLimits
|
|
90
|
+
|
|
91
|
+
if (global) {
|
|
92
|
+
this.globalRateLimiter = RouteRateLimiter.from(
|
|
93
|
+
global.map(({ name, ...limit }) =>
|
|
94
|
+
creator({ ...limit, keyPrefix: `rl-${name}` }),
|
|
95
|
+
),
|
|
96
|
+
{ bypass },
|
|
97
|
+
)
|
|
97
98
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
99
|
+
|
|
100
|
+
if (shared) {
|
|
101
|
+
this.sharedRateLimiters = new Map(
|
|
102
|
+
shared.map(({ name, ...limit }) => [
|
|
103
|
+
name,
|
|
104
|
+
creator({ ...limit, keyPrefix: `rl-${name}` }),
|
|
105
|
+
]),
|
|
106
|
+
)
|
|
106
107
|
}
|
|
107
108
|
}
|
|
108
109
|
}
|
|
@@ -177,7 +178,6 @@ export class Server {
|
|
|
177
178
|
middleware.push(this.middleware.json)
|
|
178
179
|
middleware.push(this.middleware.text)
|
|
179
180
|
}
|
|
180
|
-
this.setupRouteRateLimits(nsid, config)
|
|
181
181
|
this.routes[verb](
|
|
182
182
|
`/xrpc/${nsid}`,
|
|
183
183
|
...middleware,
|
|
@@ -186,52 +186,43 @@ export class Server {
|
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
async catchall(req: Request, res: Response, next: NextFunction) {
|
|
189
|
-
if (this.
|
|
189
|
+
if (this.globalRateLimiter) {
|
|
190
190
|
try {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
},
|
|
200
|
-
this.globalRateLimiters.map(
|
|
201
|
-
(rl) => (ctx: XRPCReqContext) => rl.consume(ctx),
|
|
202
|
-
),
|
|
203
|
-
)
|
|
204
|
-
if (rlRes instanceof RateLimitExceededError) {
|
|
205
|
-
return next(rlRes)
|
|
206
|
-
}
|
|
191
|
+
await this.globalRateLimiter.handle({
|
|
192
|
+
req,
|
|
193
|
+
res,
|
|
194
|
+
auth: undefined,
|
|
195
|
+
params: {},
|
|
196
|
+
input: undefined,
|
|
197
|
+
async resetRouteRateLimits() {},
|
|
198
|
+
})
|
|
207
199
|
} catch (err) {
|
|
208
200
|
return next(err)
|
|
209
201
|
}
|
|
210
202
|
}
|
|
211
203
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
204
|
+
// Ensure that known XRPC methods are only called with the correct HTTP
|
|
205
|
+
// method.
|
|
216
206
|
const def = this.lex.getDef(req.params.methodId)
|
|
217
|
-
if (
|
|
218
|
-
|
|
207
|
+
if (def) {
|
|
208
|
+
const expectedMethod =
|
|
209
|
+
def.type === 'procedure' ? 'POST' : def.type === 'query' ? 'GET' : null
|
|
210
|
+
if (expectedMethod != null && expectedMethod !== req.method) {
|
|
211
|
+
return next(
|
|
212
|
+
new InvalidRequestError(
|
|
213
|
+
`Incorrect HTTP method (${req.method}) expected ${expectedMethod}`,
|
|
214
|
+
),
|
|
215
|
+
)
|
|
216
|
+
}
|
|
219
217
|
}
|
|
220
|
-
|
|
221
|
-
if (
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
)
|
|
227
|
-
} else if (def.type === 'procedure' && req.method !== 'POST') {
|
|
228
|
-
return next(
|
|
229
|
-
new InvalidRequestError(
|
|
230
|
-
`Incorrect HTTP method (${req.method}) expected POST`,
|
|
231
|
-
),
|
|
232
|
-
)
|
|
218
|
+
|
|
219
|
+
if (this.options.catchall) {
|
|
220
|
+
this.options.catchall.call(null, req, res, next)
|
|
221
|
+
} else if (!def) {
|
|
222
|
+
next(new MethodNotImplementedError())
|
|
223
|
+
} else {
|
|
224
|
+
next()
|
|
233
225
|
}
|
|
234
|
-
return next()
|
|
235
226
|
}
|
|
236
227
|
|
|
237
228
|
createHandler(
|
|
@@ -251,18 +242,8 @@ export class Server {
|
|
|
251
242
|
validateOutput(nsid, def, output, this.lex)
|
|
252
243
|
const assertValidXrpcParams = (params: unknown) =>
|
|
253
244
|
this.lex.assertValidXrpcParams(nsid, params)
|
|
254
|
-
|
|
255
|
-
const
|
|
256
|
-
consumeMany(
|
|
257
|
-
reqCtx,
|
|
258
|
-
rls.map((rl) => (ctx: XRPCReqContext) => rl.consume(ctx)),
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
const resetRateLimit = (reqCtx: XRPCReqContext) =>
|
|
262
|
-
resetMany(
|
|
263
|
-
reqCtx,
|
|
264
|
-
rls.map((rl) => (ctx: XRPCReqContext) => rl.reset(ctx)),
|
|
265
|
-
)
|
|
245
|
+
|
|
246
|
+
const routeLimiter = this.createRouteRateLimiter(nsid, routeCfg)
|
|
266
247
|
|
|
267
248
|
return async function (req, res, next) {
|
|
268
249
|
try {
|
|
@@ -283,14 +264,11 @@ export class Server {
|
|
|
283
264
|
auth: locals.auth,
|
|
284
265
|
req,
|
|
285
266
|
res,
|
|
286
|
-
resetRouteRateLimits: async () =>
|
|
267
|
+
resetRouteRateLimits: async () => routeLimiter?.reset(reqCtx),
|
|
287
268
|
}
|
|
288
269
|
|
|
289
270
|
// handle rate limits
|
|
290
|
-
|
|
291
|
-
if (result instanceof RateLimitExceededError) {
|
|
292
|
-
return next(result)
|
|
293
|
-
}
|
|
271
|
+
if (routeLimiter) await routeLimiter.handle(reqCtx)
|
|
294
272
|
|
|
295
273
|
// run the handler
|
|
296
274
|
const output = await routeCfg.handler(reqCtx)
|
|
@@ -300,12 +278,12 @@ export class Server {
|
|
|
300
278
|
res.status(200)
|
|
301
279
|
res.end()
|
|
302
280
|
} else if (isHandlerPipeThroughStream(output)) {
|
|
303
|
-
setHeaders(res, output)
|
|
281
|
+
setHeaders(res, output.headers)
|
|
304
282
|
res.status(200)
|
|
305
283
|
res.header('Content-Type', output.encoding)
|
|
306
284
|
await pipeline(output.stream, res)
|
|
307
285
|
} else if (isHandlerPipeThroughBuffer(output)) {
|
|
308
|
-
setHeaders(res, output)
|
|
286
|
+
setHeaders(res, output.headers)
|
|
309
287
|
res.status(200)
|
|
310
288
|
res.header('Content-Type', output.encoding)
|
|
311
289
|
res.end(output.buffer)
|
|
@@ -315,7 +293,7 @@ export class Server {
|
|
|
315
293
|
validateResOutput?.(output)
|
|
316
294
|
|
|
317
295
|
res.status(200)
|
|
318
|
-
setHeaders(res, output)
|
|
296
|
+
setHeaders(res, output.headers)
|
|
319
297
|
|
|
320
298
|
if (
|
|
321
299
|
output.encoding === 'application/json' ||
|
|
@@ -432,76 +410,43 @@ export class Server {
|
|
|
432
410
|
}
|
|
433
411
|
}
|
|
434
412
|
|
|
435
|
-
private
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
})
|
|
442
|
-
}
|
|
413
|
+
private createRouteRateLimiter(
|
|
414
|
+
nsid: string,
|
|
415
|
+
config: XRPCHandlerConfig,
|
|
416
|
+
): RouteRateLimiter | undefined {
|
|
417
|
+
// No route specific rate limiting configured, use the global rate limiter.
|
|
418
|
+
if (!config.rateLimit) return this.globalRateLimiter
|
|
443
419
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
calcKey,
|
|
464
|
-
}),
|
|
465
|
-
})
|
|
466
|
-
}
|
|
467
|
-
} else {
|
|
468
|
-
const { durationMs, points } = limit
|
|
469
|
-
const rateLimiter = this.options.rateLimits?.creator({
|
|
470
|
-
keyPrefix: `nsid-${i}`,
|
|
471
|
-
durationMs,
|
|
472
|
-
points,
|
|
473
|
-
calcKey,
|
|
474
|
-
calcPoints,
|
|
475
|
-
})
|
|
476
|
-
if (rateLimiter) {
|
|
477
|
-
this.sharedRateLimiters[nsid] = rateLimiter
|
|
478
|
-
this.routeRateLimiters[nsid].push({
|
|
479
|
-
consume: (ctx: XRPCReqContext) =>
|
|
480
|
-
rateLimiter.consume(ctx, {
|
|
481
|
-
calcKey,
|
|
482
|
-
calcPoints,
|
|
483
|
-
}),
|
|
484
|
-
reset: (ctx: XRPCReqContext) =>
|
|
485
|
-
rateLimiter.reset(ctx, {
|
|
486
|
-
calcKey,
|
|
487
|
-
}),
|
|
488
|
-
})
|
|
489
|
-
}
|
|
490
|
-
}
|
|
420
|
+
const { rateLimits } = this.options
|
|
421
|
+
|
|
422
|
+
// @NOTE Silently ignore creation of route specific rate limiter if the
|
|
423
|
+
// `rateLimits` options was not provided to the constructor.
|
|
424
|
+
if (!rateLimits) return this.globalRateLimiter
|
|
425
|
+
|
|
426
|
+
const { creator, bypass } = rateLimits
|
|
427
|
+
|
|
428
|
+
const rateLimiters = asArray(config.rateLimit).map((options, i) => {
|
|
429
|
+
if (isShared(options)) {
|
|
430
|
+
const rateLimiter = this.sharedRateLimiters?.get(options.name)
|
|
431
|
+
|
|
432
|
+
// The route config references a shared rate limiter that does not
|
|
433
|
+
// exist. This is a configuration error.
|
|
434
|
+
assert(rateLimiter, `Shared rate limiter "${options.name}" not defined`)
|
|
435
|
+
|
|
436
|
+
return WrappedRateLimiter.from(rateLimiter, options)
|
|
437
|
+
} else {
|
|
438
|
+
return creator({ ...options, keyPrefix: `${nsid}-${i}` })
|
|
491
439
|
}
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
}
|
|
440
|
+
})
|
|
495
441
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
)
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
}
|
|
442
|
+
// If the route config contains an empty array, use global rate limiter.
|
|
443
|
+
if (!rateLimiters.length) return this.globalRateLimiter
|
|
444
|
+
|
|
445
|
+
// The global rate limiter (if present) should be applied in addition to
|
|
446
|
+
// the route specific rate limiters.
|
|
447
|
+
if (this.globalRateLimiter) rateLimiters.push(this.globalRateLimiter)
|
|
448
|
+
|
|
449
|
+
return RouteRateLimiter.from(rateLimiters, { bypass })
|
|
505
450
|
}
|
|
506
451
|
}
|
|
507
452
|
|
|
@@ -542,24 +487,69 @@ function createErrorMiddleware({
|
|
|
542
487
|
return (err, req, res, next) => {
|
|
543
488
|
const locals: RequestLocals | undefined = req[kRequestLocals]
|
|
544
489
|
const methodSuffix = locals ? ` method ${locals.nsid}` : ''
|
|
490
|
+
|
|
545
491
|
const xrpcError = errorParser(err)
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
492
|
+
|
|
493
|
+
// Use the request's logger (if available) to benefit from request context
|
|
494
|
+
// (id, timing) and logging configuration (serialization, etc.).
|
|
495
|
+
const logger = isPinoHttpRequest(req) ? req.log : log
|
|
496
|
+
|
|
497
|
+
const isInternalError = xrpcError instanceof InternalServerError
|
|
498
|
+
|
|
499
|
+
logger.error(
|
|
500
|
+
{
|
|
501
|
+
// @NOTE Computation of error stack is an expensive operation, so
|
|
502
|
+
// we strip it for expected errors.
|
|
503
|
+
err:
|
|
504
|
+
isInternalError || process.env.NODE_ENV === 'development'
|
|
505
|
+
? err
|
|
506
|
+
: toSimplifiedErrorLike(err),
|
|
507
|
+
|
|
508
|
+
// XRPC specific properties, for easier browsing of logs
|
|
509
|
+
nsid: locals?.nsid,
|
|
510
|
+
type: xrpcError.type,
|
|
511
|
+
status: xrpcError.statusCode,
|
|
512
|
+
payload: xrpcError.payload,
|
|
513
|
+
|
|
514
|
+
// Ensure that the logged item's name is set to LOGGER_NAME, instead of
|
|
515
|
+
// the name of the pino-http logger, to ensure consistency across logs.
|
|
516
|
+
name: LOGGER_NAME,
|
|
517
|
+
},
|
|
518
|
+
isInternalError
|
|
519
|
+
? `unhandled exception in xrpc${methodSuffix}`
|
|
520
|
+
: `error in xrpc${methodSuffix}`,
|
|
521
|
+
)
|
|
522
|
+
|
|
560
523
|
if (res.headersSent) {
|
|
561
524
|
return next(err)
|
|
562
525
|
}
|
|
526
|
+
|
|
563
527
|
return res.status(xrpcError.statusCode).json(xrpcError.payload)
|
|
564
528
|
}
|
|
565
529
|
}
|
|
530
|
+
|
|
531
|
+
function isPinoHttpRequest(req: Request): req is Request & {
|
|
532
|
+
log: { error: (obj: unknown, msg: string) => void }
|
|
533
|
+
} {
|
|
534
|
+
return typeof (req as { log?: any }).log?.error === 'function'
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function toSimplifiedErrorLike(err: unknown): unknown {
|
|
538
|
+
if (err instanceof Error) {
|
|
539
|
+
// Transform into an "ErrorLike" for pino's std "err" serializer
|
|
540
|
+
return {
|
|
541
|
+
...err,
|
|
542
|
+
// Carry over non-enumerable properties
|
|
543
|
+
message: err.message,
|
|
544
|
+
name:
|
|
545
|
+
!Object.hasOwn(err, 'name') &&
|
|
546
|
+
Object.prototype.toString.call(err.constructor) === '[object Function]'
|
|
547
|
+
? err.constructor.name // extract the class name for sub-classes of Error
|
|
548
|
+
: err.name,
|
|
549
|
+
// @NOTE Error.stack, Error.cause and AggregateError.error are non
|
|
550
|
+
// enumerable properties so they won't be spread to the ErrorLike
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return err
|
|
555
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -29,6 +29,7 @@ export type Options = {
|
|
|
29
29
|
creator: RateLimiterCreator
|
|
30
30
|
global?: ServerRateLimitDescription[]
|
|
31
31
|
shared?: ServerRateLimitDescription[]
|
|
32
|
+
bypass?: (ctx: XRPCReqContext) => boolean
|
|
32
33
|
}
|
|
33
34
|
/**
|
|
34
35
|
* By default, errors are converted to {@link XRPCError} using
|
|
@@ -148,14 +149,23 @@ export interface RateLimiterI {
|
|
|
148
149
|
reset: RateLimiterReset
|
|
149
150
|
}
|
|
150
151
|
|
|
152
|
+
export type RateLimiterConsumeOptions = {
|
|
153
|
+
calcKey?: CalcKeyFn
|
|
154
|
+
calcPoints?: CalcPointsFn
|
|
155
|
+
}
|
|
156
|
+
|
|
151
157
|
export type RateLimiterConsume = (
|
|
152
158
|
ctx: XRPCReqContext,
|
|
153
|
-
opts?:
|
|
159
|
+
opts?: RateLimiterConsumeOptions,
|
|
154
160
|
) => Promise<RateLimiterStatus | RateLimitExceededError | null>
|
|
155
161
|
|
|
162
|
+
export type RateLimiterResetOptions = {
|
|
163
|
+
calcKey?: CalcKeyFn
|
|
164
|
+
}
|
|
165
|
+
|
|
156
166
|
export type RateLimiterReset = (
|
|
157
167
|
ctx: XRPCReqContext,
|
|
158
|
-
opts?:
|
|
168
|
+
opts?: RateLimiterResetOptions,
|
|
159
169
|
) => Promise<void>
|
|
160
170
|
|
|
161
171
|
export type RateLimiterCreator = (opts: {
|
package/src/util.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from 'node:assert'
|
|
2
|
-
import { IncomingMessage } from 'node:http'
|
|
2
|
+
import { IncomingMessage, OutgoingMessage } from 'node:http'
|
|
3
3
|
import { Duplex, Readable, pipeline } from 'node:stream'
|
|
4
4
|
import express from 'express'
|
|
5
5
|
import mime from 'mime-types'
|
|
@@ -24,6 +24,20 @@ import {
|
|
|
24
24
|
handlerSuccess,
|
|
25
25
|
} from './types'
|
|
26
26
|
|
|
27
|
+
export const asArray = <T>(arr: T | T[]): T[] =>
|
|
28
|
+
Array.isArray(arr) ? arr : [arr]
|
|
29
|
+
|
|
30
|
+
export function setHeaders(
|
|
31
|
+
res: OutgoingMessage,
|
|
32
|
+
headers?: Record<string, string | number>,
|
|
33
|
+
) {
|
|
34
|
+
if (headers) {
|
|
35
|
+
for (const [name, val] of Object.entries(headers)) {
|
|
36
|
+
if (val != null) res.setHeader(name, val)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
27
41
|
export function decodeQueryParams(
|
|
28
42
|
def: LexXrpcProcedure | LexXrpcQuery | LexXrpcSubscription,
|
|
29
43
|
params: UndecodedParams,
|
|
@@ -132,11 +132,8 @@ describe('Parameters', () => {
|
|
|
132
132
|
let s: http.Server
|
|
133
133
|
const server = xrpcServer.createServer(LEXICONS, {
|
|
134
134
|
rateLimits: {
|
|
135
|
-
creator: (opts
|
|
136
|
-
|
|
137
|
-
bypassSecret: 'bypass',
|
|
138
|
-
...opts,
|
|
139
|
-
}),
|
|
135
|
+
creator: (opts) => RateLimiter.memory(opts),
|
|
136
|
+
bypass: ({ req }) => req.headers['x-ratelimit-bypass'] === 'bypass',
|
|
140
137
|
shared: [
|
|
141
138
|
{
|
|
142
139
|
name: 'shared-limit',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./tests/_util.ts","./tests/auth.test.ts","./tests/bodies.test.ts","./tests/errors.test.ts","./tests/frames.test.ts","./tests/ipld.test.ts","./tests/parameters.test.ts","./tests/parsing.test.ts","./tests/procedures.test.ts","./tests/queries.test.ts","./tests/rate-limiter.test.ts","./tests/responses.test.ts","./tests/stream.test.ts","./tests/subscriptions.test.ts"],"version":"5.8.
|
|
1
|
+
{"root":["./tests/_util.ts","./tests/auth.test.ts","./tests/bodies.test.ts","./tests/errors.test.ts","./tests/frames.test.ts","./tests/ipld.test.ts","./tests/parameters.test.ts","./tests/parsing.test.ts","./tests/procedures.test.ts","./tests/queries.test.ts","./tests/rate-limiter.test.ts","./tests/responses.test.ts","./tests/stream.test.ts","./tests/subscriptions.test.ts"],"version":"5.8.3"}
|