@arbidocs/client 0.5.77 → 0.5.78
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 +86 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +275 -31
- package/dist/index.d.ts +275 -31
- package/dist/index.js +86 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1525,6 +1525,88 @@ function createAutoReloginMiddleware(config) {
|
|
|
1525
1525
|
};
|
|
1526
1526
|
}
|
|
1527
1527
|
|
|
1528
|
+
// src/middleware/request-dedup.ts
|
|
1529
|
+
function authTokenOf(input, init) {
|
|
1530
|
+
if (input instanceof Request) {
|
|
1531
|
+
const fromRequest = input.headers.get("authorization");
|
|
1532
|
+
if (fromRequest) return fromRequest;
|
|
1533
|
+
}
|
|
1534
|
+
if (init?.headers) {
|
|
1535
|
+
return new Headers(init.headers).get("authorization") ?? "";
|
|
1536
|
+
}
|
|
1537
|
+
return "";
|
|
1538
|
+
}
|
|
1539
|
+
function createDedupFetch(originalFetch) {
|
|
1540
|
+
const inFlightRequests = /* @__PURE__ */ new Map();
|
|
1541
|
+
return function dedupFetch(input, init) {
|
|
1542
|
+
const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
1543
|
+
if (method !== "GET") {
|
|
1544
|
+
return originalFetch(input, init);
|
|
1545
|
+
}
|
|
1546
|
+
const url = input instanceof Request ? input.url : String(input);
|
|
1547
|
+
const key = `GET:${url}\0${authTokenOf(input, init)}`;
|
|
1548
|
+
const existing = inFlightRequests.get(key);
|
|
1549
|
+
if (existing) {
|
|
1550
|
+
return existing.then((r) => r.clone());
|
|
1551
|
+
}
|
|
1552
|
+
const promise = originalFetch(input, init).finally(() => {
|
|
1553
|
+
inFlightRequests.delete(key);
|
|
1554
|
+
});
|
|
1555
|
+
inFlightRequests.set(key, promise);
|
|
1556
|
+
return promise.then((r) => r.clone());
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
// src/middleware/settings-write-breaker.ts
|
|
1561
|
+
var WINDOW_MS = 6e4;
|
|
1562
|
+
var MAX_WRITES_PER_WINDOW = 30;
|
|
1563
|
+
var SETTINGS_WRITE_BREAKER_CODE = "CLIENT_SETTINGS_WRITE_BREAKER";
|
|
1564
|
+
function isSettingsWrite(input, init) {
|
|
1565
|
+
const method = (input instanceof Request ? input.method : init?.method ?? "GET").toUpperCase();
|
|
1566
|
+
if (method !== "PATCH") return false;
|
|
1567
|
+
const url = input instanceof Request ? input.url : String(input);
|
|
1568
|
+
try {
|
|
1569
|
+
const base = typeof location !== "undefined" ? location.origin : "http://localhost";
|
|
1570
|
+
return new URL(url, base).pathname.endsWith("/v1/user/settings");
|
|
1571
|
+
} catch {
|
|
1572
|
+
return false;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
function breakerResponse() {
|
|
1576
|
+
return new Response(
|
|
1577
|
+
JSON.stringify({
|
|
1578
|
+
detail: {
|
|
1579
|
+
code: SETTINGS_WRITE_BREAKER_CODE,
|
|
1580
|
+
message: "Too many settings updates from this tab \u2014 write dropped by the client circuit breaker."
|
|
1581
|
+
}
|
|
1582
|
+
}),
|
|
1583
|
+
{ status: 429, headers: { "Content-Type": "application/json" } }
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
function createSettingsWriteBreakerFetch(originalFetch) {
|
|
1587
|
+
let acceptedAt = [];
|
|
1588
|
+
let trippedThisEpisode = false;
|
|
1589
|
+
return function settingsWriteBreakerFetch(input, init) {
|
|
1590
|
+
if (!isSettingsWrite(input, init)) {
|
|
1591
|
+
return originalFetch(input, init);
|
|
1592
|
+
}
|
|
1593
|
+
const now = Date.now();
|
|
1594
|
+
acceptedAt = acceptedAt.filter((t) => now - t < WINDOW_MS);
|
|
1595
|
+
if (acceptedAt.length >= MAX_WRITES_PER_WINDOW) {
|
|
1596
|
+
if (!trippedThisEpisode) {
|
|
1597
|
+
trippedThisEpisode = true;
|
|
1598
|
+
console.error(
|
|
1599
|
+
`[settings-write-breaker] over ${MAX_WRITES_PER_WINDOW} PATCH /v1/user/settings in ${WINDOW_MS / 1e3}s \u2014 dropping further writes. This indicates a client-side write loop.`
|
|
1600
|
+
);
|
|
1601
|
+
}
|
|
1602
|
+
return Promise.resolve(breakerResponse());
|
|
1603
|
+
}
|
|
1604
|
+
trippedThisEpisode = false;
|
|
1605
|
+
acceptedAt.push(now);
|
|
1606
|
+
return originalFetch(input, init);
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1528
1610
|
// src/relogin/handler.ts
|
|
1529
1611
|
var RELOGIN_COOLDOWN_MS = 5e3;
|
|
1530
1612
|
function createReloginHandler(deps) {
|
|
@@ -2065,11 +2147,12 @@ function createTransport(options) {
|
|
|
2065
2147
|
const baseUrlOption = options.baseUrl;
|
|
2066
2148
|
const base = typeof baseUrlOption === "function" ? baseUrlOption : () => baseUrlOption;
|
|
2067
2149
|
const transport = createHttpTransport(options.transport);
|
|
2068
|
-
const
|
|
2150
|
+
const guardedFetch = createSettingsWriteBreakerFetch(createDedupFetch(transport.fetch));
|
|
2151
|
+
const retryFetch = createRetryFetch(guardedFetch);
|
|
2069
2152
|
const gate = createSessionGate();
|
|
2070
2153
|
const sessionFetch = gatedFetch(gate, retryFetch);
|
|
2071
2154
|
const http = {
|
|
2072
|
-
fetch: gatedFetch(gate,
|
|
2155
|
+
fetch: gatedFetch(gate, guardedFetch),
|
|
2073
2156
|
customFetch: transport.customFetch,
|
|
2074
2157
|
withFetch: (adapter) => gatedFetch(gate, transport.withFetch(adapter))
|
|
2075
2158
|
};
|
|
@@ -2437,6 +2520,7 @@ exports.CHAT_KEY_BYTES = CHAT_KEY_BYTES;
|
|
|
2437
2520
|
exports.ENCRYPTED_RECOVERY_KEY_SIZE = ENCRYPTED_RECOVERY_KEY_SIZE;
|
|
2438
2521
|
exports.RECOVERY_KEY_ERRORS = RECOVERY_KEY_ERRORS;
|
|
2439
2522
|
exports.SECURE_MAIL_KEY_BYTES = SECURE_MAIL_KEY_BYTES;
|
|
2523
|
+
exports.SETTINGS_WRITE_BREAKER_CODE = SETTINGS_WRITE_BREAKER_CODE;
|
|
2440
2524
|
exports.UNENCRYPTED_RECOVERY_KEY_SIZE = UNENCRYPTED_RECOVERY_KEY_SIZE;
|
|
2441
2525
|
exports.authRequestError = authRequestError;
|
|
2442
2526
|
exports.base64Decode = base64Decode;
|