hono-rate-limiter 0.5.2 → 0.5.4
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/index.cjs +66 -13
- package/dist/index.d.cts +37 -1
- package/dist/index.d.ts +37 -1
- package/dist/index.js +66 -13
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -360,6 +360,19 @@ const scripts = {
|
|
|
360
360
|
local timeToExpire = redis.call("PTTL", KEYS[1])
|
|
361
361
|
|
|
362
362
|
return { totalHits, timeToExpire }
|
|
363
|
+
`.replaceAll(/^\s+/gm, "").trim(),
|
|
364
|
+
// Mirrors the `MemoryStore` behaviour: never go below zero, and never
|
|
365
|
+
// disturb the expiry. A raw DECR on a missing key (e.g. the window expired
|
|
366
|
+
// while a slow handler ran) would create it at -1 with no TTL, and that
|
|
367
|
+
// negative value would leak into the next window, letting a client exceed
|
|
368
|
+
// the configured limit. Reading the value first and only decrementing when
|
|
369
|
+
// it is a positive number avoids that. A nil GET means the window already
|
|
370
|
+
// ended, so the correct action is to do nothing.
|
|
371
|
+
decrement: `
|
|
372
|
+
local current = tonumber(redis.call("GET", KEYS[1]))
|
|
373
|
+
if current and current > 0 then
|
|
374
|
+
redis.call("DECR", KEYS[1])
|
|
375
|
+
end
|
|
363
376
|
`.replaceAll(/^\s+/gm, "").trim()
|
|
364
377
|
};
|
|
365
378
|
|
|
@@ -389,6 +402,13 @@ class RedisStore {
|
|
|
389
402
|
this.resetExpiryOnChange = options.resetExpiryOnChange ?? false;
|
|
390
403
|
this.incrementScriptSha = this.loadIncrementScript();
|
|
391
404
|
this.getScriptSha = this.loadGetScript();
|
|
405
|
+
this.decrementScriptSha = this.loadDecrementScript();
|
|
406
|
+
this.incrementScriptSha.catch(() => {
|
|
407
|
+
});
|
|
408
|
+
this.getScriptSha.catch(() => {
|
|
409
|
+
});
|
|
410
|
+
this.decrementScriptSha.catch(() => {
|
|
411
|
+
});
|
|
392
412
|
}
|
|
393
413
|
/**
|
|
394
414
|
* Loads the script used to increment a client's hit count.
|
|
@@ -410,6 +430,16 @@ class RedisStore {
|
|
|
410
430
|
}
|
|
411
431
|
return result;
|
|
412
432
|
}
|
|
433
|
+
/**
|
|
434
|
+
* Loads the script used to decrement a client's hit count.
|
|
435
|
+
*/
|
|
436
|
+
async loadDecrementScript() {
|
|
437
|
+
const result = await this.client.scriptLoad(scripts.decrement);
|
|
438
|
+
if (typeof result !== "string") {
|
|
439
|
+
throw new TypeError("unexpected reply from redis client");
|
|
440
|
+
}
|
|
441
|
+
return result;
|
|
442
|
+
}
|
|
413
443
|
/**
|
|
414
444
|
* Runs the increment command, and retries it if the script is not loaded.
|
|
415
445
|
*/
|
|
@@ -427,6 +457,23 @@ class RedisStore {
|
|
|
427
457
|
return evalCommand();
|
|
428
458
|
}
|
|
429
459
|
}
|
|
460
|
+
/**
|
|
461
|
+
* Runs the decrement command, and retries it if the script is not loaded.
|
|
462
|
+
*/
|
|
463
|
+
async retryableDecrement(key) {
|
|
464
|
+
const evalCommand = async () => this.client.evalsha(
|
|
465
|
+
await this.decrementScriptSha,
|
|
466
|
+
[this.prefixKey(key)],
|
|
467
|
+
[]
|
|
468
|
+
);
|
|
469
|
+
try {
|
|
470
|
+
const result = await evalCommand();
|
|
471
|
+
return result;
|
|
472
|
+
} catch {
|
|
473
|
+
this.decrementScriptSha = this.loadDecrementScript();
|
|
474
|
+
return evalCommand();
|
|
475
|
+
}
|
|
476
|
+
}
|
|
430
477
|
/**
|
|
431
478
|
* Method to prefix the keys with the given text.
|
|
432
479
|
*
|
|
@@ -474,10 +521,16 @@ class RedisStore {
|
|
|
474
521
|
/**
|
|
475
522
|
* Method to decrement a client's hit counter.
|
|
476
523
|
*
|
|
524
|
+
* Uses a Lua script so the read-and-decrement is atomic. It never takes the
|
|
525
|
+
* counter below zero and never touches the key's expiry, mirroring the
|
|
526
|
+
* `MemoryStore`. This prevents a decrement that lands after the window has
|
|
527
|
+
* expired from creating a negative, TTL-less key that would leak extra
|
|
528
|
+
* requests into the next window.
|
|
529
|
+
*
|
|
477
530
|
* @param key {string} - The identifier for a client
|
|
478
531
|
*/
|
|
479
532
|
async decrement(key) {
|
|
480
|
-
await this.
|
|
533
|
+
await this.retryableDecrement(key);
|
|
481
534
|
}
|
|
482
535
|
/**
|
|
483
536
|
* Method to reset a client's hit counter.
|
|
@@ -525,15 +578,23 @@ class UnstorageStore {
|
|
|
525
578
|
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
|
|
526
579
|
*/
|
|
527
580
|
async get(key) {
|
|
528
|
-
const result = await this.storage.get(this.prefixKey(key)).then(
|
|
529
|
-
(
|
|
530
|
-
|
|
581
|
+
const result = await this.storage.get(this.prefixKey(key)).then((value) => {
|
|
582
|
+
if (typeof value === "object") {
|
|
583
|
+
return value;
|
|
584
|
+
}
|
|
585
|
+
return value ? JSON.parse(String(value)) : void 0;
|
|
586
|
+
});
|
|
531
587
|
return result;
|
|
532
588
|
}
|
|
533
589
|
/**
|
|
534
590
|
* Method to increment a client's hit counter. If the current time is within an active window,
|
|
535
591
|
* it increments the existing hit count. Otherwise, it starts a new window with a hit count of 1.
|
|
536
592
|
*
|
|
593
|
+
* @remarks
|
|
594
|
+
* This read-modify-write is not atomic. Concurrent calls for the same key
|
|
595
|
+
* may under-count and let a client exceed the limit. See the class-level
|
|
596
|
+
* remarks for details and alternatives.
|
|
597
|
+
*
|
|
537
598
|
* @param key {string} - The identifier for a client
|
|
538
599
|
*
|
|
539
600
|
* @returns {ClientRateLimitInfo} - An object containing:
|
|
@@ -656,26 +717,18 @@ function webSocketLimiter(config) {
|
|
|
656
717
|
decremented = true;
|
|
657
718
|
}
|
|
658
719
|
};
|
|
659
|
-
const shouldSkipRequest = async () => {
|
|
660
|
-
if (skipSuccessfulRequests) await decrementKey();
|
|
661
|
-
};
|
|
662
720
|
if (totalHits > _limit) {
|
|
663
|
-
await shouldSkipRequest();
|
|
664
721
|
return handler(event, ws, options);
|
|
665
722
|
}
|
|
666
723
|
try {
|
|
667
724
|
await events.onMessage?.(event, ws);
|
|
668
|
-
await
|
|
725
|
+
if (skipSuccessfulRequests) await decrementKey();
|
|
669
726
|
} catch (error) {
|
|
670
727
|
if (skipFailedRequests) await decrementKey();
|
|
671
728
|
throw error;
|
|
672
729
|
}
|
|
673
730
|
},
|
|
674
731
|
onError: async (event, ws) => {
|
|
675
|
-
if (skipFailedRequests) {
|
|
676
|
-
const key = await keyGenerator(c);
|
|
677
|
-
await store.decrement(key);
|
|
678
|
-
}
|
|
679
732
|
events.onError?.(event, ws);
|
|
680
733
|
}
|
|
681
734
|
};
|
package/dist/index.d.cts
CHANGED
|
@@ -143,7 +143,7 @@ type HonoConfigType<E extends Env = Env, P extends string = string, I extends In
|
|
|
143
143
|
*/
|
|
144
144
|
skipSuccessfulRequests: boolean;
|
|
145
145
|
/**
|
|
146
|
-
* Method to determine whether or not the request counts as '
|
|
146
|
+
* Method to determine whether or not the request counts as 'successful'. Used
|
|
147
147
|
* when either `skipSuccessfulRequests` or `skipFailedRequests` is set to true.
|
|
148
148
|
*
|
|
149
149
|
* By default, requests with a response status code less than 400 are considered
|
|
@@ -439,6 +439,7 @@ declare class RedisStore<E extends Env$1 = Env$1, P extends string = string, I e
|
|
|
439
439
|
*/
|
|
440
440
|
incrementScriptSha: Promise<string>;
|
|
441
441
|
getScriptSha: Promise<string>;
|
|
442
|
+
decrementScriptSha: Promise<string>;
|
|
442
443
|
/**
|
|
443
444
|
* @constructor for `RedisStore`.
|
|
444
445
|
*
|
|
@@ -453,10 +454,18 @@ declare class RedisStore<E extends Env$1 = Env$1, P extends string = string, I e
|
|
|
453
454
|
* Loads the script used to fetch a client's hit count and expiry time.
|
|
454
455
|
*/
|
|
455
456
|
loadGetScript(): Promise<string>;
|
|
457
|
+
/**
|
|
458
|
+
* Loads the script used to decrement a client's hit count.
|
|
459
|
+
*/
|
|
460
|
+
loadDecrementScript(): Promise<string>;
|
|
456
461
|
/**
|
|
457
462
|
* Runs the increment command, and retries it if the script is not loaded.
|
|
458
463
|
*/
|
|
459
464
|
retryableIncrement(key: string): Promise<RedisReply>;
|
|
465
|
+
/**
|
|
466
|
+
* Runs the decrement command, and retries it if the script is not loaded.
|
|
467
|
+
*/
|
|
468
|
+
retryableDecrement(key: string): Promise<RedisReply>;
|
|
460
469
|
/**
|
|
461
470
|
* Method to prefix the keys with the given text.
|
|
462
471
|
*
|
|
@@ -490,6 +499,12 @@ declare class RedisStore<E extends Env$1 = Env$1, P extends string = string, I e
|
|
|
490
499
|
/**
|
|
491
500
|
* Method to decrement a client's hit counter.
|
|
492
501
|
*
|
|
502
|
+
* Uses a Lua script so the read-and-decrement is atomic. It never takes the
|
|
503
|
+
* counter below zero and never touches the key's expiry, mirroring the
|
|
504
|
+
* `MemoryStore`. This prevents a decrement that lands after the window has
|
|
505
|
+
* expired from creating a negative, TTL-less key that would leak extra
|
|
506
|
+
* requests into the next window.
|
|
507
|
+
*
|
|
493
508
|
* @param key {string} - The identifier for a client
|
|
494
509
|
*/
|
|
495
510
|
decrement(key: string): Promise<void>;
|
|
@@ -510,6 +525,22 @@ type UnstorageInstance = {
|
|
|
510
525
|
* A `Store` that stores the hit count for each client using Unstorage
|
|
511
526
|
*
|
|
512
527
|
* {@link https://unstorage.unjs.io/}
|
|
528
|
+
*
|
|
529
|
+
* @remarks
|
|
530
|
+
* **Not safe under high concurrency.** Unstorage exposes no atomic
|
|
531
|
+
* increment or compare-and-swap primitive, so `increment()` and
|
|
532
|
+
* `decrement()` perform a non-atomic read-modify-write against the backing
|
|
533
|
+
* driver. When many requests for the same key arrive at once, they can all
|
|
534
|
+
* read the same hit count before any of them writes it back, so the counter
|
|
535
|
+
* under-counts and a client can exceed the configured limit. The wider the
|
|
536
|
+
* driver's read/write latency (e.g. Vercel KV, Cloudflare KV, S3), the wider
|
|
537
|
+
* this window. The same applies across multiple server instances sharing one
|
|
538
|
+
* backend.
|
|
539
|
+
*
|
|
540
|
+
* If you need correct counting under concurrent load, use a store backed by
|
|
541
|
+
* an atomic counter such as `RedisStore` (which does all its work inside a
|
|
542
|
+
* Lua script). `MemoryStore` is also race-free within a single process, but
|
|
543
|
+
* does not share state across instances.
|
|
513
544
|
*/
|
|
514
545
|
declare class UnstorageStore<E extends Env$1 = Env$1, P extends string = string, I extends Input$1 = Input$1> implements Store<E, P, I> {
|
|
515
546
|
/**
|
|
@@ -559,6 +590,11 @@ declare class UnstorageStore<E extends Env$1 = Env$1, P extends string = string,
|
|
|
559
590
|
* Method to increment a client's hit counter. If the current time is within an active window,
|
|
560
591
|
* it increments the existing hit count. Otherwise, it starts a new window with a hit count of 1.
|
|
561
592
|
*
|
|
593
|
+
* @remarks
|
|
594
|
+
* This read-modify-write is not atomic. Concurrent calls for the same key
|
|
595
|
+
* may under-count and let a client exceed the limit. See the class-level
|
|
596
|
+
* remarks for details and alternatives.
|
|
597
|
+
*
|
|
562
598
|
* @param key {string} - The identifier for a client
|
|
563
599
|
*
|
|
564
600
|
* @returns {ClientRateLimitInfo} - An object containing:
|
package/dist/index.d.ts
CHANGED
|
@@ -143,7 +143,7 @@ type HonoConfigType<E extends Env = Env, P extends string = string, I extends In
|
|
|
143
143
|
*/
|
|
144
144
|
skipSuccessfulRequests: boolean;
|
|
145
145
|
/**
|
|
146
|
-
* Method to determine whether or not the request counts as '
|
|
146
|
+
* Method to determine whether or not the request counts as 'successful'. Used
|
|
147
147
|
* when either `skipSuccessfulRequests` or `skipFailedRequests` is set to true.
|
|
148
148
|
*
|
|
149
149
|
* By default, requests with a response status code less than 400 are considered
|
|
@@ -439,6 +439,7 @@ declare class RedisStore<E extends Env$1 = Env$1, P extends string = string, I e
|
|
|
439
439
|
*/
|
|
440
440
|
incrementScriptSha: Promise<string>;
|
|
441
441
|
getScriptSha: Promise<string>;
|
|
442
|
+
decrementScriptSha: Promise<string>;
|
|
442
443
|
/**
|
|
443
444
|
* @constructor for `RedisStore`.
|
|
444
445
|
*
|
|
@@ -453,10 +454,18 @@ declare class RedisStore<E extends Env$1 = Env$1, P extends string = string, I e
|
|
|
453
454
|
* Loads the script used to fetch a client's hit count and expiry time.
|
|
454
455
|
*/
|
|
455
456
|
loadGetScript(): Promise<string>;
|
|
457
|
+
/**
|
|
458
|
+
* Loads the script used to decrement a client's hit count.
|
|
459
|
+
*/
|
|
460
|
+
loadDecrementScript(): Promise<string>;
|
|
456
461
|
/**
|
|
457
462
|
* Runs the increment command, and retries it if the script is not loaded.
|
|
458
463
|
*/
|
|
459
464
|
retryableIncrement(key: string): Promise<RedisReply>;
|
|
465
|
+
/**
|
|
466
|
+
* Runs the decrement command, and retries it if the script is not loaded.
|
|
467
|
+
*/
|
|
468
|
+
retryableDecrement(key: string): Promise<RedisReply>;
|
|
460
469
|
/**
|
|
461
470
|
* Method to prefix the keys with the given text.
|
|
462
471
|
*
|
|
@@ -490,6 +499,12 @@ declare class RedisStore<E extends Env$1 = Env$1, P extends string = string, I e
|
|
|
490
499
|
/**
|
|
491
500
|
* Method to decrement a client's hit counter.
|
|
492
501
|
*
|
|
502
|
+
* Uses a Lua script so the read-and-decrement is atomic. It never takes the
|
|
503
|
+
* counter below zero and never touches the key's expiry, mirroring the
|
|
504
|
+
* `MemoryStore`. This prevents a decrement that lands after the window has
|
|
505
|
+
* expired from creating a negative, TTL-less key that would leak extra
|
|
506
|
+
* requests into the next window.
|
|
507
|
+
*
|
|
493
508
|
* @param key {string} - The identifier for a client
|
|
494
509
|
*/
|
|
495
510
|
decrement(key: string): Promise<void>;
|
|
@@ -510,6 +525,22 @@ type UnstorageInstance = {
|
|
|
510
525
|
* A `Store` that stores the hit count for each client using Unstorage
|
|
511
526
|
*
|
|
512
527
|
* {@link https://unstorage.unjs.io/}
|
|
528
|
+
*
|
|
529
|
+
* @remarks
|
|
530
|
+
* **Not safe under high concurrency.** Unstorage exposes no atomic
|
|
531
|
+
* increment or compare-and-swap primitive, so `increment()` and
|
|
532
|
+
* `decrement()` perform a non-atomic read-modify-write against the backing
|
|
533
|
+
* driver. When many requests for the same key arrive at once, they can all
|
|
534
|
+
* read the same hit count before any of them writes it back, so the counter
|
|
535
|
+
* under-counts and a client can exceed the configured limit. The wider the
|
|
536
|
+
* driver's read/write latency (e.g. Vercel KV, Cloudflare KV, S3), the wider
|
|
537
|
+
* this window. The same applies across multiple server instances sharing one
|
|
538
|
+
* backend.
|
|
539
|
+
*
|
|
540
|
+
* If you need correct counting under concurrent load, use a store backed by
|
|
541
|
+
* an atomic counter such as `RedisStore` (which does all its work inside a
|
|
542
|
+
* Lua script). `MemoryStore` is also race-free within a single process, but
|
|
543
|
+
* does not share state across instances.
|
|
513
544
|
*/
|
|
514
545
|
declare class UnstorageStore<E extends Env$1 = Env$1, P extends string = string, I extends Input$1 = Input$1> implements Store<E, P, I> {
|
|
515
546
|
/**
|
|
@@ -559,6 +590,11 @@ declare class UnstorageStore<E extends Env$1 = Env$1, P extends string = string,
|
|
|
559
590
|
* Method to increment a client's hit counter. If the current time is within an active window,
|
|
560
591
|
* it increments the existing hit count. Otherwise, it starts a new window with a hit count of 1.
|
|
561
592
|
*
|
|
593
|
+
* @remarks
|
|
594
|
+
* This read-modify-write is not atomic. Concurrent calls for the same key
|
|
595
|
+
* may under-count and let a client exceed the limit. See the class-level
|
|
596
|
+
* remarks for details and alternatives.
|
|
597
|
+
*
|
|
562
598
|
* @param key {string} - The identifier for a client
|
|
563
599
|
*
|
|
564
600
|
* @returns {ClientRateLimitInfo} - An object containing:
|
package/dist/index.js
CHANGED
|
@@ -358,6 +358,19 @@ const scripts = {
|
|
|
358
358
|
local timeToExpire = redis.call("PTTL", KEYS[1])
|
|
359
359
|
|
|
360
360
|
return { totalHits, timeToExpire }
|
|
361
|
+
`.replaceAll(/^\s+/gm, "").trim(),
|
|
362
|
+
// Mirrors the `MemoryStore` behaviour: never go below zero, and never
|
|
363
|
+
// disturb the expiry. A raw DECR on a missing key (e.g. the window expired
|
|
364
|
+
// while a slow handler ran) would create it at -1 with no TTL, and that
|
|
365
|
+
// negative value would leak into the next window, letting a client exceed
|
|
366
|
+
// the configured limit. Reading the value first and only decrementing when
|
|
367
|
+
// it is a positive number avoids that. A nil GET means the window already
|
|
368
|
+
// ended, so the correct action is to do nothing.
|
|
369
|
+
decrement: `
|
|
370
|
+
local current = tonumber(redis.call("GET", KEYS[1]))
|
|
371
|
+
if current and current > 0 then
|
|
372
|
+
redis.call("DECR", KEYS[1])
|
|
373
|
+
end
|
|
361
374
|
`.replaceAll(/^\s+/gm, "").trim()
|
|
362
375
|
};
|
|
363
376
|
|
|
@@ -387,6 +400,13 @@ class RedisStore {
|
|
|
387
400
|
this.resetExpiryOnChange = options.resetExpiryOnChange ?? false;
|
|
388
401
|
this.incrementScriptSha = this.loadIncrementScript();
|
|
389
402
|
this.getScriptSha = this.loadGetScript();
|
|
403
|
+
this.decrementScriptSha = this.loadDecrementScript();
|
|
404
|
+
this.incrementScriptSha.catch(() => {
|
|
405
|
+
});
|
|
406
|
+
this.getScriptSha.catch(() => {
|
|
407
|
+
});
|
|
408
|
+
this.decrementScriptSha.catch(() => {
|
|
409
|
+
});
|
|
390
410
|
}
|
|
391
411
|
/**
|
|
392
412
|
* Loads the script used to increment a client's hit count.
|
|
@@ -408,6 +428,16 @@ class RedisStore {
|
|
|
408
428
|
}
|
|
409
429
|
return result;
|
|
410
430
|
}
|
|
431
|
+
/**
|
|
432
|
+
* Loads the script used to decrement a client's hit count.
|
|
433
|
+
*/
|
|
434
|
+
async loadDecrementScript() {
|
|
435
|
+
const result = await this.client.scriptLoad(scripts.decrement);
|
|
436
|
+
if (typeof result !== "string") {
|
|
437
|
+
throw new TypeError("unexpected reply from redis client");
|
|
438
|
+
}
|
|
439
|
+
return result;
|
|
440
|
+
}
|
|
411
441
|
/**
|
|
412
442
|
* Runs the increment command, and retries it if the script is not loaded.
|
|
413
443
|
*/
|
|
@@ -425,6 +455,23 @@ class RedisStore {
|
|
|
425
455
|
return evalCommand();
|
|
426
456
|
}
|
|
427
457
|
}
|
|
458
|
+
/**
|
|
459
|
+
* Runs the decrement command, and retries it if the script is not loaded.
|
|
460
|
+
*/
|
|
461
|
+
async retryableDecrement(key) {
|
|
462
|
+
const evalCommand = async () => this.client.evalsha(
|
|
463
|
+
await this.decrementScriptSha,
|
|
464
|
+
[this.prefixKey(key)],
|
|
465
|
+
[]
|
|
466
|
+
);
|
|
467
|
+
try {
|
|
468
|
+
const result = await evalCommand();
|
|
469
|
+
return result;
|
|
470
|
+
} catch {
|
|
471
|
+
this.decrementScriptSha = this.loadDecrementScript();
|
|
472
|
+
return evalCommand();
|
|
473
|
+
}
|
|
474
|
+
}
|
|
428
475
|
/**
|
|
429
476
|
* Method to prefix the keys with the given text.
|
|
430
477
|
*
|
|
@@ -472,10 +519,16 @@ class RedisStore {
|
|
|
472
519
|
/**
|
|
473
520
|
* Method to decrement a client's hit counter.
|
|
474
521
|
*
|
|
522
|
+
* Uses a Lua script so the read-and-decrement is atomic. It never takes the
|
|
523
|
+
* counter below zero and never touches the key's expiry, mirroring the
|
|
524
|
+
* `MemoryStore`. This prevents a decrement that lands after the window has
|
|
525
|
+
* expired from creating a negative, TTL-less key that would leak extra
|
|
526
|
+
* requests into the next window.
|
|
527
|
+
*
|
|
475
528
|
* @param key {string} - The identifier for a client
|
|
476
529
|
*/
|
|
477
530
|
async decrement(key) {
|
|
478
|
-
await this.
|
|
531
|
+
await this.retryableDecrement(key);
|
|
479
532
|
}
|
|
480
533
|
/**
|
|
481
534
|
* Method to reset a client's hit counter.
|
|
@@ -523,15 +576,23 @@ class UnstorageStore {
|
|
|
523
576
|
* @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client.
|
|
524
577
|
*/
|
|
525
578
|
async get(key) {
|
|
526
|
-
const result = await this.storage.get(this.prefixKey(key)).then(
|
|
527
|
-
(
|
|
528
|
-
|
|
579
|
+
const result = await this.storage.get(this.prefixKey(key)).then((value) => {
|
|
580
|
+
if (typeof value === "object") {
|
|
581
|
+
return value;
|
|
582
|
+
}
|
|
583
|
+
return value ? JSON.parse(String(value)) : void 0;
|
|
584
|
+
});
|
|
529
585
|
return result;
|
|
530
586
|
}
|
|
531
587
|
/**
|
|
532
588
|
* Method to increment a client's hit counter. If the current time is within an active window,
|
|
533
589
|
* it increments the existing hit count. Otherwise, it starts a new window with a hit count of 1.
|
|
534
590
|
*
|
|
591
|
+
* @remarks
|
|
592
|
+
* This read-modify-write is not atomic. Concurrent calls for the same key
|
|
593
|
+
* may under-count and let a client exceed the limit. See the class-level
|
|
594
|
+
* remarks for details and alternatives.
|
|
595
|
+
*
|
|
535
596
|
* @param key {string} - The identifier for a client
|
|
536
597
|
*
|
|
537
598
|
* @returns {ClientRateLimitInfo} - An object containing:
|
|
@@ -654,26 +715,18 @@ function webSocketLimiter(config) {
|
|
|
654
715
|
decremented = true;
|
|
655
716
|
}
|
|
656
717
|
};
|
|
657
|
-
const shouldSkipRequest = async () => {
|
|
658
|
-
if (skipSuccessfulRequests) await decrementKey();
|
|
659
|
-
};
|
|
660
718
|
if (totalHits > _limit) {
|
|
661
|
-
await shouldSkipRequest();
|
|
662
719
|
return handler(event, ws, options);
|
|
663
720
|
}
|
|
664
721
|
try {
|
|
665
722
|
await events.onMessage?.(event, ws);
|
|
666
|
-
await
|
|
723
|
+
if (skipSuccessfulRequests) await decrementKey();
|
|
667
724
|
} catch (error) {
|
|
668
725
|
if (skipFailedRequests) await decrementKey();
|
|
669
726
|
throw error;
|
|
670
727
|
}
|
|
671
728
|
},
|
|
672
729
|
onError: async (event, ws) => {
|
|
673
|
-
if (skipFailedRequests) {
|
|
674
|
-
const key = await keyGenerator(c);
|
|
675
|
-
await store.decrement(key);
|
|
676
|
-
}
|
|
677
730
|
events.onError?.(event, ws);
|
|
678
731
|
}
|
|
679
732
|
};
|