cursedbelt-server 4.46.0 → 4.46.1
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.
|
@@ -417,13 +417,16 @@ export function createBinaryStore(cfg) {
|
|
|
417
417
|
* caller asked for `paced` and the budget allows, otherwise thrown as a typed error that
|
|
418
418
|
* keeps the `[binary-store] <op> <key>: 429` message batch callers already match.
|
|
419
419
|
*/
|
|
420
|
-
const sendRead = async (op, key,
|
|
420
|
+
const sendRead = async (op, key,
|
|
421
|
+
/** Signs the URL. Called for EVERY attempt: a read token lives 60 s and a paced read may wait
|
|
422
|
+
* 120 s, so a URL signed once went out dead after the first long retry-after (forge task 3302). */
|
|
423
|
+
mint, init, opts) => {
|
|
421
424
|
const budget = opts?.paced
|
|
422
425
|
? (typeof opts.paced === 'object' ? opts.paced.maxWaitMs : undefined) ?? BINARY_PACED_MAX_WAIT_MS
|
|
423
426
|
: 0;
|
|
424
427
|
let waited = 0;
|
|
425
428
|
for (let attempt = 0;; attempt++) {
|
|
426
|
-
const res = await send(
|
|
429
|
+
const res = await send(await mint(), init);
|
|
427
430
|
if (res.status !== 429)
|
|
428
431
|
return res;
|
|
429
432
|
const retryAfterSec = retryAfterSeconds(res.headers.get('retry-after'), attempt);
|
|
@@ -548,7 +551,7 @@ export function createBinaryStore(cfg) {
|
|
|
548
551
|
if (!opts?.fresh && knownAbsent(key))
|
|
549
552
|
return null;
|
|
550
553
|
countWireRead(key);
|
|
551
|
-
const res = await sendRead('get', key,
|
|
554
|
+
const res = await sendRead('get', key, () => internalMediaUrl(key), undefined, opts);
|
|
552
555
|
if (res.status === 404) {
|
|
553
556
|
rememberAbsent(key);
|
|
554
557
|
return null;
|
|
@@ -624,7 +627,7 @@ export function createBinaryStore(cfg) {
|
|
|
624
627
|
return null;
|
|
625
628
|
}
|
|
626
629
|
countWireRead(key);
|
|
627
|
-
const res = await sendRead('meta', key,
|
|
630
|
+
const res = await sendRead('meta', key, () => metaUrl(key), undefined, opts);
|
|
628
631
|
if (res.status === 404) {
|
|
629
632
|
rememberAbsent(key);
|
|
630
633
|
return null;
|
|
@@ -671,7 +674,7 @@ export function createBinaryStore(cfg) {
|
|
|
671
674
|
throw why;
|
|
672
675
|
}
|
|
673
676
|
countWireRead(key);
|
|
674
|
-
const res = await sendRead('stat', key,
|
|
677
|
+
const res = await sendRead('stat', key, () => internalMediaUrl(key), { headers: { Range: 'bytes=0-0' } }, opts);
|
|
675
678
|
if (res.status === 404) {
|
|
676
679
|
rememberAbsent(key);
|
|
677
680
|
return null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursedbelt-server",
|
|
3
|
-
"version": "4.46.
|
|
3
|
+
"version": "4.46.1",
|
|
4
4
|
"license": "ISC",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "The app-facing Bun/Hono server tier of the cursedbelt split — storage, sharing, activity, guard, sync. React-free; cursedbelt-core below it.",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* server; the RSA keypair is generated in-process and the signature is verified with
|
|
27
27
|
* `node:crypto`, which is what makes the token assertions real rather than shape-deep.
|
|
28
28
|
*/
|
|
29
|
-
import { afterAll, afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
|
29
|
+
import { afterAll, afterEach, beforeEach, describe, expect, it, setSystemTime } from 'bun:test';
|
|
30
30
|
import { generateKeyPairSync, verify as cryptoVerify } from 'node:crypto';
|
|
31
31
|
import {
|
|
32
32
|
AUTO_CHUNK_THRESHOLD_BYTES,
|
|
@@ -1056,6 +1056,29 @@ describe('🔴 a read the store PACES is waited out when asked, typed when not (
|
|
|
1056
1056
|
expect(calls).toHaveLength(4);
|
|
1057
1057
|
});
|
|
1058
1058
|
|
|
1059
|
+
it('🔴 every attempt goes with a token signed for IT — a 60 s token outlives no 120 s wait (forge task 3302)', async () => {
|
|
1060
|
+
// The URL was signed once, before the first attempt, so a paced read that waited out 30 s
|
|
1061
|
+
// refusals sent a dead token on its third try, and binary-server answered 403 — not 429.
|
|
1062
|
+
try {
|
|
1063
|
+
let n = 0;
|
|
1064
|
+
reply = () => (n++ < 3 ? limited('30') : ok(new Uint8Array([7, 8])));
|
|
1065
|
+
const store = makeStore({
|
|
1066
|
+
sleep: (ms) => {
|
|
1067
|
+
setSystemTime(new Date(Date.now() + ms));
|
|
1068
|
+
return Promise.resolve();
|
|
1069
|
+
},
|
|
1070
|
+
});
|
|
1071
|
+
await store.get('parts/0', { paced: true });
|
|
1072
|
+
expect(calls).toHaveLength(4);
|
|
1073
|
+
// The last attempt went 90 s after the first: its token must still have time left.
|
|
1074
|
+
const token = new URL(String(calls.at(-1)?.url)).searchParams.get('token') ?? '';
|
|
1075
|
+
const { exp } = JSON.parse(Buffer.from(token.split('.')[1] ?? '', 'base64url').toString('utf8')) as { exp: number };
|
|
1076
|
+
expect(exp * 1000).toBeGreaterThan(Date.now());
|
|
1077
|
+
} finally {
|
|
1078
|
+
setSystemTime();
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1059
1082
|
it('without paced, a 429 is the typed error carrying retryAfterSec — and the old message', async () => {
|
|
1060
1083
|
const { store, slept } = pacedStore(1, '7');
|
|
1061
1084
|
const error = await store.get('parts/0').catch((e: unknown) => e);
|
|
@@ -728,7 +728,9 @@ export function createBinaryStore(cfg: BinaryStoreConfig): BinaryStore {
|
|
|
728
728
|
const sendRead = async (
|
|
729
729
|
op: string,
|
|
730
730
|
key: string,
|
|
731
|
-
|
|
731
|
+
/** Signs the URL. Called for EVERY attempt: a read token lives 60 s and a paced read may wait
|
|
732
|
+
* 120 s, so a URL signed once went out dead after the first long retry-after (forge task 3302). */
|
|
733
|
+
mint: () => Promise<string>,
|
|
732
734
|
init: RequestInit | undefined,
|
|
733
735
|
opts: ReadOptions | undefined,
|
|
734
736
|
): Promise<Response> => {
|
|
@@ -737,7 +739,7 @@ export function createBinaryStore(cfg: BinaryStoreConfig): BinaryStore {
|
|
|
737
739
|
: 0;
|
|
738
740
|
let waited = 0;
|
|
739
741
|
for (let attempt = 0; ; attempt++) {
|
|
740
|
-
const res = await send(
|
|
742
|
+
const res = await send(await mint(), init);
|
|
741
743
|
if (res.status !== 429) return res;
|
|
742
744
|
const retryAfterSec = retryAfterSeconds(res.headers.get('retry-after'), attempt);
|
|
743
745
|
const ms = retryAfterSec * 1000;
|
|
@@ -875,7 +877,7 @@ export function createBinaryStore(cfg: BinaryStoreConfig): BinaryStore {
|
|
|
875
877
|
// short-circuit here — we want the bytes, and only the store has them.
|
|
876
878
|
if (!opts?.fresh && knownAbsent(key)) return null;
|
|
877
879
|
countWireRead(key);
|
|
878
|
-
const res = await sendRead('get', key,
|
|
880
|
+
const res = await sendRead('get', key, () => internalMediaUrl(key), undefined, opts);
|
|
879
881
|
if (res.status === 404) {
|
|
880
882
|
rememberAbsent(key);
|
|
881
883
|
return null;
|
|
@@ -952,7 +954,7 @@ export function createBinaryStore(cfg: BinaryStoreConfig): BinaryStore {
|
|
|
952
954
|
if (knownAbsent(key)) return null;
|
|
953
955
|
}
|
|
954
956
|
countWireRead(key);
|
|
955
|
-
const res = await sendRead('meta', key,
|
|
957
|
+
const res = await sendRead('meta', key, () => metaUrl(key), undefined, opts);
|
|
956
958
|
if (res.status === 404) {
|
|
957
959
|
rememberAbsent(key);
|
|
958
960
|
return null;
|
|
@@ -995,7 +997,7 @@ export function createBinaryStore(cfg: BinaryStoreConfig): BinaryStore {
|
|
|
995
997
|
if (why instanceof BinaryStoreRateLimitedError) throw why;
|
|
996
998
|
}
|
|
997
999
|
countWireRead(key);
|
|
998
|
-
const res = await sendRead('stat', key,
|
|
1000
|
+
const res = await sendRead('stat', key, () => internalMediaUrl(key), { headers: { Range: 'bytes=0-0' } }, opts);
|
|
999
1001
|
if (res.status === 404) {
|
|
1000
1002
|
rememberAbsent(key);
|
|
1001
1003
|
return null;
|