@arbidocs/client 0.5.76 → 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.js CHANGED
@@ -1518,6 +1518,88 @@ function createAutoReloginMiddleware(config) {
1518
1518
  };
1519
1519
  }
1520
1520
 
1521
+ // src/middleware/request-dedup.ts
1522
+ function authTokenOf(input, init) {
1523
+ if (input instanceof Request) {
1524
+ const fromRequest = input.headers.get("authorization");
1525
+ if (fromRequest) return fromRequest;
1526
+ }
1527
+ if (init?.headers) {
1528
+ return new Headers(init.headers).get("authorization") ?? "";
1529
+ }
1530
+ return "";
1531
+ }
1532
+ function createDedupFetch(originalFetch) {
1533
+ const inFlightRequests = /* @__PURE__ */ new Map();
1534
+ return function dedupFetch(input, init) {
1535
+ const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
1536
+ if (method !== "GET") {
1537
+ return originalFetch(input, init);
1538
+ }
1539
+ const url = input instanceof Request ? input.url : String(input);
1540
+ const key = `GET:${url}\0${authTokenOf(input, init)}`;
1541
+ const existing = inFlightRequests.get(key);
1542
+ if (existing) {
1543
+ return existing.then((r) => r.clone());
1544
+ }
1545
+ const promise = originalFetch(input, init).finally(() => {
1546
+ inFlightRequests.delete(key);
1547
+ });
1548
+ inFlightRequests.set(key, promise);
1549
+ return promise.then((r) => r.clone());
1550
+ };
1551
+ }
1552
+
1553
+ // src/middleware/settings-write-breaker.ts
1554
+ var WINDOW_MS = 6e4;
1555
+ var MAX_WRITES_PER_WINDOW = 30;
1556
+ var SETTINGS_WRITE_BREAKER_CODE = "CLIENT_SETTINGS_WRITE_BREAKER";
1557
+ function isSettingsWrite(input, init) {
1558
+ const method = (input instanceof Request ? input.method : init?.method ?? "GET").toUpperCase();
1559
+ if (method !== "PATCH") return false;
1560
+ const url = input instanceof Request ? input.url : String(input);
1561
+ try {
1562
+ const base = typeof location !== "undefined" ? location.origin : "http://localhost";
1563
+ return new URL(url, base).pathname.endsWith("/v1/user/settings");
1564
+ } catch {
1565
+ return false;
1566
+ }
1567
+ }
1568
+ function breakerResponse() {
1569
+ return new Response(
1570
+ JSON.stringify({
1571
+ detail: {
1572
+ code: SETTINGS_WRITE_BREAKER_CODE,
1573
+ message: "Too many settings updates from this tab \u2014 write dropped by the client circuit breaker."
1574
+ }
1575
+ }),
1576
+ { status: 429, headers: { "Content-Type": "application/json" } }
1577
+ );
1578
+ }
1579
+ function createSettingsWriteBreakerFetch(originalFetch) {
1580
+ let acceptedAt = [];
1581
+ let trippedThisEpisode = false;
1582
+ return function settingsWriteBreakerFetch(input, init) {
1583
+ if (!isSettingsWrite(input, init)) {
1584
+ return originalFetch(input, init);
1585
+ }
1586
+ const now = Date.now();
1587
+ acceptedAt = acceptedAt.filter((t) => now - t < WINDOW_MS);
1588
+ if (acceptedAt.length >= MAX_WRITES_PER_WINDOW) {
1589
+ if (!trippedThisEpisode) {
1590
+ trippedThisEpisode = true;
1591
+ console.error(
1592
+ `[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.`
1593
+ );
1594
+ }
1595
+ return Promise.resolve(breakerResponse());
1596
+ }
1597
+ trippedThisEpisode = false;
1598
+ acceptedAt.push(now);
1599
+ return originalFetch(input, init);
1600
+ };
1601
+ }
1602
+
1521
1603
  // src/relogin/handler.ts
1522
1604
  var RELOGIN_COOLDOWN_MS = 5e3;
1523
1605
  function createReloginHandler(deps) {
@@ -2058,11 +2140,12 @@ function createTransport(options) {
2058
2140
  const baseUrlOption = options.baseUrl;
2059
2141
  const base = typeof baseUrlOption === "function" ? baseUrlOption : () => baseUrlOption;
2060
2142
  const transport = createHttpTransport(options.transport);
2061
- const retryFetch = createRetryFetch(transport.fetch);
2143
+ const guardedFetch = createSettingsWriteBreakerFetch(createDedupFetch(transport.fetch));
2144
+ const retryFetch = createRetryFetch(guardedFetch);
2062
2145
  const gate = createSessionGate();
2063
2146
  const sessionFetch = gatedFetch(gate, retryFetch);
2064
2147
  const http = {
2065
- fetch: gatedFetch(gate, transport.fetch),
2148
+ fetch: gatedFetch(gate, guardedFetch),
2066
2149
  customFetch: transport.customFetch,
2067
2150
  withFetch: (adapter) => gatedFetch(gate, transport.withFetch(adapter))
2068
2151
  };
@@ -2423,6 +2506,6 @@ var logger = {
2423
2506
  }
2424
2507
  };
2425
2508
 
2426
- export { ACCESS_TOKEN_STORAGE_KEY, API_PREFIX, CALL_KEY_BYTES, CHAT_KEY_BYTES, ENCRYPTED_RECOVERY_KEY_SIZE, RECOVERY_KEY_ERRORS, SECURE_MAIL_KEY_BYTES, UNENCRYPTED_RECOVERY_KEY_SIZE, authRequestError, base64Decode, base64Encode, base64ToBytes, base64UrlToBytes, browserSessionStateStorage, buildCallFragment, buildCallLink, buildWebSocketUrl, bytesToBase64, bytesToBase64Url, canPersistSession, changePassword, clearAllData, clearSession, computeSharedSecret, configureLogger, createAllWorkspacesOpener, createArbiClient, createAuthMessage, createAutoReloginMiddleware, createBearerAuthMiddleware, createContentHash, createHttpTransport, createMemorySessionPersistence, createOfflineSealer, createRecoveryFile, createRecoveryUrl, createReloginHandler, createSessionManager, createWorkspaceKeys, createWorkspaceOpens, createWorkspaceReopener, decodeCallKey, decodeSecureMailKey, decryptCallTitle, decryptFile, decryptMessage, decryptMessageWithSharedSecret, decryptRecoveryKey, decryptWithChatKey, deriveDirectChatKey, deriveEncryptionKeypairFromSigning, derivePublicKey, disconnectSso, encodeCallKey, encryptCallTitle, encryptFile, encryptMessage, encryptMessageWithSharedSecret, encryptRecoveryKey, encryptWithChatKey, encryptionKeypairFromSigningKey, generateCallKey, generateChatKey, generateIdentityKeypair, generateKeyPairs, generateLoginCredentials, generateLoginCredentialsFromKey, generatePasswordChangeCredentials, generateRandomSigningKeypair, generateRegistrationCredentials, generateUserKeypairs, getRecoveryKeyFilename, getSession, getUserEncryptionKeypair, hasSession, identityFromLoginResponse, initSodium, initializeDatabase, isAbortError, isEncryptedRecoveryKey, isMessageType, logger, openCallKeyOf, openSealedCallKey, openSealedChatKey, openSecureMail, parseCallFragment, parseRecoveryUrl, parseServerMessage, passwordChangeErrorMessage, prewarmKeyPairs, readSessionClaims, requestSsoDisconnectCode, saveSession, sealCallKey, sealCallKeyForPeers, sealChatKey, sealKeyForSession, sealSecureMail, sealedBoxDecrypt, sealedBoxEncrypt, secureMailClaim, signMessage, unlockIdentityWithPassword, unwrapIdentityKey, unwrapWorkspaceKey, updateWrappedIdentityKey, validateRecoveryKeyFile, workspaceKeyRequiredOf, wrapIdentityKey };
2509
+ export { ACCESS_TOKEN_STORAGE_KEY, API_PREFIX, CALL_KEY_BYTES, CHAT_KEY_BYTES, ENCRYPTED_RECOVERY_KEY_SIZE, RECOVERY_KEY_ERRORS, SECURE_MAIL_KEY_BYTES, SETTINGS_WRITE_BREAKER_CODE, UNENCRYPTED_RECOVERY_KEY_SIZE, authRequestError, base64Decode, base64Encode, base64ToBytes, base64UrlToBytes, browserSessionStateStorage, buildCallFragment, buildCallLink, buildWebSocketUrl, bytesToBase64, bytesToBase64Url, canPersistSession, changePassword, clearAllData, clearSession, computeSharedSecret, configureLogger, createAllWorkspacesOpener, createArbiClient, createAuthMessage, createAutoReloginMiddleware, createBearerAuthMiddleware, createContentHash, createHttpTransport, createMemorySessionPersistence, createOfflineSealer, createRecoveryFile, createRecoveryUrl, createReloginHandler, createSessionManager, createWorkspaceKeys, createWorkspaceOpens, createWorkspaceReopener, decodeCallKey, decodeSecureMailKey, decryptCallTitle, decryptFile, decryptMessage, decryptMessageWithSharedSecret, decryptRecoveryKey, decryptWithChatKey, deriveDirectChatKey, deriveEncryptionKeypairFromSigning, derivePublicKey, disconnectSso, encodeCallKey, encryptCallTitle, encryptFile, encryptMessage, encryptMessageWithSharedSecret, encryptRecoveryKey, encryptWithChatKey, encryptionKeypairFromSigningKey, generateCallKey, generateChatKey, generateIdentityKeypair, generateKeyPairs, generateLoginCredentials, generateLoginCredentialsFromKey, generatePasswordChangeCredentials, generateRandomSigningKeypair, generateRegistrationCredentials, generateUserKeypairs, getRecoveryKeyFilename, getSession, getUserEncryptionKeypair, hasSession, identityFromLoginResponse, initSodium, initializeDatabase, isAbortError, isEncryptedRecoveryKey, isMessageType, logger, openCallKeyOf, openSealedCallKey, openSealedChatKey, openSecureMail, parseCallFragment, parseRecoveryUrl, parseServerMessage, passwordChangeErrorMessage, prewarmKeyPairs, readSessionClaims, requestSsoDisconnectCode, saveSession, sealCallKey, sealCallKeyForPeers, sealChatKey, sealKeyForSession, sealSecureMail, sealedBoxDecrypt, sealedBoxEncrypt, secureMailClaim, signMessage, unlockIdentityWithPassword, unwrapIdentityKey, unwrapWorkspaceKey, updateWrappedIdentityKey, validateRecoveryKeyFile, workspaceKeyRequiredOf, wrapIdentityKey };
2427
2510
  //# sourceMappingURL=index.js.map
2428
2511
  //# sourceMappingURL=index.js.map