@antzsoft/chat-core 1.4.4 → 1.4.6

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/README.md CHANGED
@@ -380,9 +380,48 @@ interface AntzChatConfig {
380
380
  * Optional — sensible defaults are applied for all sub-fields.
381
381
  */
382
382
  upload?: UploadConfig;
383
+
384
+ /**
385
+ * Called when sending a message ultimately fails, after the SDK has already
386
+ * updated the timeline (removed the bubble for permanent failures, flagged it
387
+ * retryable otherwise). Use it to surface a toast or error banner. v1.4.5+.
388
+ * Optional.
389
+ */
390
+ onSendError?: (error: Error, context: SendErrorContext) => void;
383
391
  }
384
392
  ```
385
393
 
394
+ ### `SendErrorContext` (v1.4.5+)
395
+
396
+ Passed as the second argument to `onSendError`. The callback fires *after* the SDK has reconciled the timeline, so it is for user-facing notification only — you do not need to remove or flag the bubble yourself.
397
+
398
+ ```typescript
399
+ interface SendErrorContext {
400
+ conversationId: string;
401
+ /** tempId of the optimistic message the failure belongs to. */
402
+ tempId: string;
403
+ /**
404
+ * True when resending the identical payload cannot succeed (file size/type
405
+ * limits, message too long, no longer a participant, ...). The SDK has removed
406
+ * the optimistic message from the timeline. False for transient failures
407
+ * (network / socket / generic server error), where a "! Retry" bubble stays.
408
+ */
409
+ permanent: boolean;
410
+ }
411
+ ```
412
+
413
+ ```typescript
414
+ const client = new AntzChatClient({
415
+ apiUrl, persistStorage, platformUploadFn,
416
+ onSendError: (error, { permanent }) => {
417
+ if (permanent) toast.error(error.message);
418
+ else toast.warn('Message not sent — tap Retry on the message.');
419
+ },
420
+ });
421
+ ```
422
+
423
+ Pair `permanent: false` with `useChat().retrySendMessage(messageId)` (v1.4.4+) if you want a retry affordance outside the built-in `MessageItem`.
424
+
386
425
  ### `PersistStorage`
387
426
 
388
427
  Supports both synchronous (localStorage) and asynchronous (AsyncStorage) storage backends.
@@ -3244,6 +3283,28 @@ document.querySelectorAll('[data-conv-id]').forEach((el) => {
3244
3283
 
3245
3284
  ## Changelog
3246
3285
 
3286
+ > **Full history lives in [CHANGELOG.md](CHANGELOG.md).** That file is the source of truth and covers every release. This section carries only the headline notes for recent versions; v1.4.2, v1.4.3 and v1.4.4 (message forwarding, per-attachment forwarding, and send/forward retry-safety) are documented there rather than duplicated here.
3287
+
3288
+ ### v1.4.5
3289
+
3290
+ Published as `1.4.5`; built and reviewed internally as `1.4.7` (same code — there is no `1.4.6`, and no separate `1.4.7` will be published).
3291
+
3292
+ - **Fix: transit encryption no longer degrades to plaintext or wedges after a socket drop.** Six related fixes, all of them closing a path where the SDK either sent an unencrypted request the server was certain to reject with `403 "Transit encryption required"`, or blocked forever on a handshake nothing was driving:
3293
+ - The socket `disconnect` handler cleared the REST transit session on *every* websocket blip (network hiccup, backgrounded tab, server redeploy), after which every chat REST call 403'd until a full page reload. The REST session is independent of the socket transport and is now torn down only on an explicit `disconnectSocket()` or auth change.
3294
+ - `clearTransitSession()` left `sessionEverEstablished = true`, making `waitForTransitReady()` ("don't block") and `isTransitEnabled()` ("don't encrypt") permanently disagree — requests went out unencrypted and 403'd with no recovery path. The flag now resets with the session.
3295
+ - The 5s `transit_session` safety timeout called `configureTransit(false)`, turning a transient stall into plaintext for the rest of the session. A timeout now leaves transit required; only `GET /crypto/pubkey` returning `enabled: false` can authoritatively disable it.
3296
+ - The interceptor gated its transit wait on the presence of an auth token, so pre-auth calls (`GET /app/config` before an async `authProvider` resolves) raced ahead and 403'd. It now gates on whether transit is configured.
3297
+ - A `join_room` dropped during the handshake gap left the client outside the server-side room with no error and no retry, silently killing `new_message` delivery for that conversation. Joined rooms are now tracked and re-emitted whenever a transit session (re-)establishes or the socket reconnects.
3298
+ - The per-conversation send queue awaited the full server ack before starting the next message. It now awaits only the emit phase, preserving on-the-wire ordering without paying round-trip latency.
3299
+
3300
+ - **Changed: requests fail loudly instead of hanging or downgrading.** With transit required and no session key, a REST request waits up to 30s for an in-flight handshake, then throws a **retryable** `AntzChatNetworkError` with code `TRANSIT_NOT_READY`; socket emits do the same on a 4s budget. Neither ever falls through to plaintext. The REST handshake retries itself up to 5 times with exponential backoff.
3301
+
3302
+ - **New: `onSendError`.** A config callback — `(error, { conversationId, tempId, permanent })` — fired when a send ultimately fails, after the SDK has updated the timeline, so you can raise a toast. `permanent: true` means a retry of the identical payload cannot succeed and the optimistic bubble was removed; `false` leaves a retryable "! Retry" bubble. New type: `SendErrorContext`.
3303
+
3304
+ - **New exports:** `setAuthReadyPromise()` (gate the interceptor until your auth token resolves), `resetTrackedRooms()` (call on a user/tenant switch so the reconnect re-flush cannot re-join the previous user's rooms — a token refresh is *not* an identity change), and `isApiClientConfigured()`.
3305
+
3306
+ **Backward compatible at the API level** — additive exports only, no signature changes, no call-site updates required. **One behavioral change needs your attention:** a call that previously went out as plaintext and returned `403` now throws a retryable error instead. Audit any REST call you fire and forget during startup — `getMe().then(...).catch(() => {})` will swallow `TRANSIT_NOT_READY`, leaving auth unresolved, the socket unconnected, and the app stuck on a loading screen with no visible cause. See [CHANGELOG.md](CHANGELOG.md) → *Upgrading from 1.4.4* for the retry patterns.
3307
+
3247
3308
  ### v1.4.1
3248
3309
 
3249
3310
  - **New: manual "mark as unread."** A conversation can now be flagged unread independently of `unreadCount` — the same UX as WhatsApp/Telegram's "mark as unread": re-flag a conversation you've already read so it stands out again, without fabricating unread messages.
@@ -3258,7 +3319,9 @@ document.querySelectorAll('[data-conv-id]').forEach((el) => {
3258
3319
 
3259
3320
  - **Fix: stale transit encryption key after token refresh, causing continuous `"Transit decryption failed for event: user_online"` spam.** `reconnectSocket()` called `socket.connect()` to apply a refreshed token while preserving the transit session — but socket.io-client's `connect()` is a no-op on an already-connected socket. A token refresh fires while the user is actively on a chat screen (socket still connected), so the new auth was never actually sent; the socket kept running on the stale token until the server eventually dropped it, and the disconnect handler then cleared the transit session, desyncing the client's key from the server's.
3260
3321
 
3261
- **Fix:** in the transit branch of `reconnectSocket()`, force a real transport re-cycle (disconnect → connect) so the fresh auth (token + `transitSessionId`) is actually sent and the server re-links the *same* session (same key), instead of silently continuing on stale auth. A new internal `_preservingSession` guard stops the disconnect handler from wiping the key being deliberately carried forward. Non-transit and already-disconnected paths are unchanged.
3322
+ **Fix:** in the transit branch of `reconnectSocket()`, force a real transport re-cycle (disconnect → connect) so the fresh auth (token + `transitSessionId`) is actually sent and the server re-links the *same* session (same key), instead of silently continuing on stale auth. Non-transit and already-disconnected paths are unchanged.
3323
+
3324
+ > **Superseded in v1.4.5.** This fix originally added an internal `_preservingSession` guard to stop the disconnect handler from wiping the key being carried through the re-cycle. v1.4.5 removed that guard: the disconnect handler no longer clears the transit session on a transient drop at all, so there is nothing to guard against. The transport re-cycle described above is unchanged.
3262
3325
 
3263
3326
  **Backward compatible.** Only affects transit-encryption deployments that refresh auth tokens while the socket is connected. **No integration changes required** — the fix is entirely internal to `reconnectSocket()`.
3264
3327
 
@@ -161,7 +161,8 @@ function getState() {
161
161
  sessionEverEstablished: false,
162
162
  readyResolve: null,
163
163
  readyPromise: null,
164
- transitConfigured: null
164
+ transitConfigured: null,
165
+ readyListeners: /* @__PURE__ */ new Set()
165
166
  };
166
167
  }
167
168
  return g[_KEY];
@@ -174,11 +175,13 @@ function configureTransit(enabled) {
174
175
  s.readyResolve = null;
175
176
  }
176
177
  }
178
+ function configureTransitIfUnset(enabled) {
179
+ if (getState().transitConfigured === null) configureTransit(enabled);
180
+ }
177
181
  function waitForTransitReady() {
178
182
  const s = getState();
179
183
  if (!s.transitConfigured) return Promise.resolve();
180
184
  if (s.session) return Promise.resolve();
181
- if (s.sessionEverEstablished) return Promise.resolve();
182
185
  if (!s.readyPromise) {
183
186
  s.readyPromise = new Promise((resolve) => {
184
187
  s.readyResolve = resolve;
@@ -192,6 +195,29 @@ function setTransitSession(session) {
192
195
  s.sessionEverEstablished = true;
193
196
  s.readyResolve?.();
194
197
  s.readyResolve = null;
198
+ s.readyListeners.forEach((fn) => {
199
+ try {
200
+ fn();
201
+ } catch {
202
+ }
203
+ });
204
+ }
205
+ function onTransitReady(listener) {
206
+ const s = getState();
207
+ s.readyListeners.add(listener);
208
+ return () => {
209
+ s.readyListeners.delete(listener);
210
+ };
211
+ }
212
+ async function awaitTransitReadyOr(timeoutMs) {
213
+ const s = getState();
214
+ if (!s.transitConfigured || s.session) return true;
215
+ await Promise.race([
216
+ waitForTransitReady(),
217
+ new Promise((r) => setTimeout(r, timeoutMs))
218
+ ]);
219
+ const now = getState();
220
+ return Boolean(now.session) || now.transitConfigured !== true;
195
221
  }
196
222
  function getTransitSession() {
197
223
  return getState().session;
@@ -199,12 +225,16 @@ function getTransitSession() {
199
225
  function clearTransitSession() {
200
226
  const s = getState();
201
227
  s.session = null;
228
+ s.sessionEverEstablished = false;
202
229
  s.readyPromise = null;
203
230
  s.readyResolve = null;
204
231
  }
205
232
  function isTransitEnabled() {
206
233
  return getState().session?.enabled === true;
207
234
  }
235
+ function isTransitRequired() {
236
+ return getState().transitConfigured === true;
237
+ }
208
238
  function getSessionKey() {
209
239
  return getState().session?.sessionKey ?? null;
210
240
  }
@@ -233,11 +263,36 @@ function resetAlgoCache() {
233
263
  }
234
264
 
235
265
  // src/crypto/handshake.ts
266
+ function readRetryAfterMs(headers) {
267
+ const parse = (raw) => {
268
+ if (!raw) return void 0;
269
+ const secs = Number(raw);
270
+ if (Number.isFinite(secs)) return Math.max(0, secs * 1e3);
271
+ const when = Date.parse(raw);
272
+ return Number.isNaN(when) ? void 0 : Math.max(0, when - Date.now());
273
+ };
274
+ const found = ["Retry-After-identity", "Retry-After-ip", "Retry-After"].map((name) => parse(headers.get(name))).filter((ms) => ms != null);
275
+ return found.length > 0 ? Math.max(...found) : void 0;
276
+ }
277
+ var TransitRateLimitedError = class extends Error {
278
+ constructor(retryAfterMs) {
279
+ super("[AntzChat] transit handshake rate-limited (429)");
280
+ this.name = "TransitRateLimitedError";
281
+ this.retryAfterMs = retryAfterMs;
282
+ }
283
+ };
284
+ function identityHeaders(identity) {
285
+ const headers = {};
286
+ if (identity?.userId) headers["x-user-id"] = identity.userId;
287
+ if (identity?.tenantId) headers["X-Tenant-ID"] = identity.tenantId;
288
+ return headers;
289
+ }
236
290
  function hasWebCrypto2() {
237
291
  return typeof globalThis.crypto?.subtle !== "undefined";
238
292
  }
239
- async function fetchServerKeys(apiUrl) {
240
- const res = await fetch(`${apiUrl}/crypto/pubkey`);
293
+ async function fetchServerKeys(apiUrl, identity) {
294
+ const res = await fetch(`${apiUrl}/crypto/pubkey`, { headers: identityHeaders(identity) });
295
+ if (res.status === 429) throw new TransitRateLimitedError(readRetryAfterMs(res.headers));
241
296
  if (!res.ok) throw new Error(`[AntzChat] Failed to fetch server public key: ${res.status}`);
242
297
  const body = await res.json();
243
298
  return body?.data ?? body;
@@ -248,24 +303,26 @@ async function generateEphemeralKey(algo, serverKeys) {
248
303
  }
249
304
  return generateNobleEphemeralKey(serverKeys);
250
305
  }
251
- async function createRestTransitSession(apiUrl) {
306
+ async function createRestTransitSession(apiUrl, identity) {
252
307
  try {
253
- const serverKeys = await fetchServerKeys(apiUrl);
308
+ const serverKeys = await fetchServerKeys(apiUrl, identity);
254
309
  if (!serverKeys.enabled) return null;
255
310
  const algo = hasWebCrypto2() ? await detectTransitAlgo() : "x25519";
256
311
  const { ephemeralPubB64, deriveSessionKey } = await generateEphemeralKey(algo, serverKeys);
257
312
  const res = await fetch(`${apiUrl}/crypto/session`, {
258
313
  method: "POST",
259
- headers: { "Content-Type": "application/json" },
314
+ headers: { "Content-Type": "application/json", ...identityHeaders(identity) },
260
315
  body: JSON.stringify({ ephemeralPub: ephemeralPubB64, algo })
261
316
  });
317
+ if (res.status === 429) throw new TransitRateLimitedError(readRetryAfterMs(res.headers));
262
318
  if (!res.ok) return null;
263
319
  const body = await res.json();
264
320
  const sessionId = (body?.data ?? body)?.sessionId ?? body?.sessionId;
265
321
  if (!sessionId) return null;
266
322
  const sessionKey = await deriveSessionKey(sessionId);
267
323
  return { sessionId, sessionKey };
268
- } catch {
324
+ } catch (err) {
325
+ if (err instanceof TransitRateLimitedError) throw err;
269
326
  return null;
270
327
  }
271
328
  }
@@ -444,13 +501,72 @@ function normalizeAxiosError(error) {
444
501
 
445
502
  // src/api/client.ts
446
503
  import axios from "axios";
504
+ var TRANSIT_GATE_MAX_WAIT_MS = 3e4;
447
505
  var _tokenStore = null;
448
506
  var _config = null;
449
507
  var _avatarSent = false;
450
508
  var _transitHandshakePromise = null;
509
+ var _authReadyPromise = null;
451
510
  function getTransitHandshakePromise() {
452
511
  return _transitHandshakePromise;
453
512
  }
513
+ function setAuthReadyPromise(promise) {
514
+ _authReadyPromise = promise;
515
+ }
516
+ function isApiClientConfigured() {
517
+ return _config !== null;
518
+ }
519
+ function ensureRestTransitHandshake() {
520
+ if (!_config?.transitEncryption || getTransitSession() || _transitHandshakePromise) return;
521
+ const apiUrl = _config.apiUrl;
522
+ _transitHandshakePromise = (async () => {
523
+ try {
524
+ const MAX_FAILURES = 5;
525
+ const BACKSTOP_MS = 2 * 6e4;
526
+ const deadline = Date.now() + BACKSTOP_MS;
527
+ let failures = 0;
528
+ let rateLimitHits = 0;
529
+ while (failures < MAX_FAILURES && Date.now() < deadline) {
530
+ if (getTransitSession()) return;
531
+ let waitMs;
532
+ try {
533
+ const identity = { userId: _config?.userId, tenantId: _config?.tenantId };
534
+ const keys = await fetchServerKeys(apiUrl, identity);
535
+ if (!keys?.enabled) {
536
+ configureTransit(false);
537
+ return;
538
+ }
539
+ const session = await createRestTransitSession(apiUrl, identity);
540
+ if (session && !getTransitSession()) {
541
+ const algo = typeof globalThis.crypto?.subtle !== "undefined" ? await detectTransitAlgo() : "x25519";
542
+ setTransitSession({ sessionKey: session.sessionKey, algo, sessionId: session.sessionId, enabled: true });
543
+ return;
544
+ }
545
+ failures++;
546
+ waitMs = Math.min(500 * 2 ** failures, 8e3);
547
+ } catch (err) {
548
+ if (err instanceof TransitRateLimitedError) {
549
+ const blind = Math.min(15e3 * 2 ** rateLimitHits, 6e4);
550
+ waitMs = err.retryAfterMs != null ? Math.min(Math.max(err.retryAfterMs, 1e3), 6e4) : blind;
551
+ rateLimitHits++;
552
+ console.warn(
553
+ `[AntzChat] transit handshake rate-limited (429) \u2014 retrying in ${Math.round(waitMs / 1e3)}s${err.retryAfterMs != null ? " (per Retry-After)" : ""}.`
554
+ );
555
+ } else {
556
+ failures++;
557
+ waitMs = Math.min(500 * 2 ** failures, 8e3);
558
+ }
559
+ }
560
+ await new Promise((r) => setTimeout(r, waitMs));
561
+ }
562
+ console.error(
563
+ "[AntzChat] transit handshake could not establish a session \u2014 chat requests stay gated until one succeeds (server requires transit)."
564
+ );
565
+ } finally {
566
+ _transitHandshakePromise = null;
567
+ }
568
+ })();
569
+ }
454
570
  function initApiClient(config, tokenStore) {
455
571
  _config = config;
456
572
  _tokenStore = tokenStore;
@@ -460,24 +576,9 @@ function initApiClient(config, tokenStore) {
460
576
  headers: { "Content-Type": "application/json" }
461
577
  });
462
578
  configureTransit(config.transitEncryption);
463
- if (config.transitEncryption && !getTransitSession() && !_transitHandshakePromise) {
464
- _transitHandshakePromise = (async () => {
465
- try {
466
- const session = await createRestTransitSession(config.apiUrl);
467
- if (session && !getTransitSession()) {
468
- const algo = typeof globalThis.crypto?.subtle !== "undefined" ? await detectTransitAlgo() : "x25519";
469
- setTransitSession({ sessionKey: session.sessionKey, algo, sessionId: session.sessionId, enabled: true });
470
- } else if (!session) {
471
- configureTransit(false);
472
- }
473
- } catch {
474
- configureTransit(false);
475
- } finally {
476
- _transitHandshakePromise = null;
477
- }
478
- })();
479
- }
579
+ ensureRestTransitHandshake();
480
580
  client.interceptors.request.use(async (req) => {
581
+ if (_authReadyPromise) await _authReadyPromise;
481
582
  const token = _tokenStore?.getAccessToken();
482
583
  if (token) req.headers["Authorization"] = `Bearer ${token}`;
483
584
  if (_config?.userId) req.headers["x-user-id"] = _config.userId;
@@ -487,7 +588,17 @@ function initApiClient(config, tokenStore) {
487
588
  else if (_config.avatar.url) req.headers["x-avatar-url"] = _config.avatar.url;
488
589
  _avatarSent = true;
489
590
  }
490
- if (token) await waitForTransitReady();
591
+ if (_config?.transitEncryption) {
592
+ if (!getTransitSession()) ensureRestTransitHandshake();
593
+ const ready = await awaitTransitReadyOr(TRANSIT_GATE_MAX_WAIT_MS);
594
+ if (!ready) {
595
+ throw new AntzChatNetworkError(
596
+ "Secure channel to chat server not established \u2014 request not sent. It will retry automatically.",
597
+ "TRANSIT_NOT_READY",
598
+ { url: req.url }
599
+ );
600
+ }
601
+ }
491
602
  if (isTransitEnabled()) {
492
603
  const sessionId = getSessionId();
493
604
  const key = getSessionKey();
@@ -766,15 +877,20 @@ export {
766
877
  encryptPayload,
767
878
  decryptPayload,
768
879
  isTransitEnvelope,
769
- configureTransit,
880
+ configureTransitIfUnset,
770
881
  setTransitSession,
882
+ onTransitReady,
883
+ awaitTransitReadyOr,
771
884
  getTransitSession,
772
885
  clearTransitSession,
773
886
  isTransitEnabled,
887
+ isTransitRequired,
774
888
  getSessionKey,
775
889
  getSessionId,
776
890
  detectTransitAlgo,
777
891
  resetAlgoCache,
892
+ readRetryAfterMs,
893
+ TransitRateLimitedError,
778
894
  fetchServerKeys,
779
895
  generateEphemeralKey,
780
896
  createRestTransitSession,
@@ -787,6 +903,9 @@ export {
787
903
  AntzChatServerError,
788
904
  normalizeAxiosError,
789
905
  getTransitHandshakePromise,
906
+ setAuthReadyPromise,
907
+ isApiClientConfigured,
908
+ ensureRestTransitHandshake,
790
909
  initApiClient,
791
910
  setApiClientInstance,
792
911
  getApiClient,
@@ -795,4 +914,4 @@ export {
795
914
  uploadBatch,
796
915
  uploadBatchWithSlots
797
916
  };
798
- //# sourceMappingURL=chunk-XTQYF5HU.js.map
917
+ //# sourceMappingURL=chunk-U637W5MD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/compression/compress.ts","../src/crypto/transit.ts","../src/crypto/session.ts","../src/crypto/detect.ts","../src/crypto/handshake.ts","../src/errors.ts","../src/api/client.ts","../src/crypto/uuid.ts","../src/api/storage.ts"],"sourcesContent":["import type { UploadableFile, CompressedFile, CompressionAlgorithm } from '../types/index.js';\nimport type { PlatformCompressFn, ResolvedCompressionConfig } from '../config/types.js';\n\n// MIME types that benefit from gzip (text-based, not already compressed)\nconst GZIP_MIME_TYPES = new Set([\n 'text/plain', 'text/csv', 'text/markdown', 'text/x-markdown',\n 'text/xml', 'application/xml', 'text/yaml', 'text/x-yaml',\n 'application/x-yaml', 'application/rtf', 'text/rtf',\n 'application/json', 'image/svg+xml',\n]);\n\nconst IMAGE_MIME_TYPES = new Set([\n 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/bmp', 'image/tiff',\n]);\n\n// Already-compressed formats — no gain from recompressing\nconst SKIP_MIME_TYPES = new Set([\n 'video/mp4', 'video/webm', 'video/quicktime',\n 'audio/mpeg', 'audio/wav', 'audio/ogg', 'audio/webm', 'audio/mp4',\n 'application/zip', 'application/pdf',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n]);\n\nexport type CompressionStrategy = 'image' | 'gzip' | 'skip';\n\nexport function getCompressionStrategy(\n mimeType: string,\n config: ResolvedCompressionConfig,\n): CompressionStrategy {\n if (SKIP_MIME_TYPES.has(mimeType)) return 'skip';\n if (IMAGE_MIME_TYPES.has(mimeType)) return 'image';\n if (config.compressDocuments && GZIP_MIME_TYPES.has(mimeType)) return 'gzip';\n return 'skip';\n}\n\n/**\n * Attempt to compress a file using the platform-provided compressor.\n * Returns the original file unchanged (as a CompressedFile with compressed=false)\n * if compression is disabled, no compressor is provided, or the strategy is 'skip'.\n */\nexport async function compressFile(\n file: UploadableFile,\n platformCompressFn: PlatformCompressFn | undefined,\n config: ResolvedCompressionConfig,\n): Promise<CompressedFile> {\n const noop: CompressedFile = {\n ...file,\n originalSize: file.size,\n compressed: false,\n compressionAlgorithm: 'none' as CompressionAlgorithm,\n };\n\n if (!config.enabled || !platformCompressFn) return noop;\n\n const strategy = getCompressionStrategy(file.type, config);\n if (strategy === 'skip') return noop;\n\n try {\n return await platformCompressFn(file, config);\n } catch {\n // Compression failure is non-fatal — fall back to original\n return noop;\n }\n}\n","export interface TransitEnvelope {\n v: 1;\n iv: string; // base64, 12 bytes\n tag: string; // base64, 16 bytes\n ct: string; // base64, ciphertext\n}\n\n// sessionKey is CryptoKey on Web Crypto path, Uint8Array on noble/RN path.\ntype AnySessionKey = CryptoKey | Uint8Array;\n\nfunction hasWebCrypto(): boolean {\n return typeof globalThis.crypto?.subtle !== 'undefined';\n}\n\n// ─── Encrypt ─────────────────────────────────────────────────────────────────\n\nexport async function encryptPayload(\n data: unknown,\n sessionKey: AnySessionKey,\n): Promise<TransitEnvelope> {\n const plaintext = new TextEncoder().encode(JSON.stringify(data));\n\n if (!hasWebCrypto() || sessionKey instanceof Uint8Array) {\n return encryptNoble(plaintext, sessionKey as Uint8Array);\n }\n return encryptWebCrypto(plaintext, sessionKey as CryptoKey);\n}\n\nasync function encryptWebCrypto(plaintext: Uint8Array, sessionKey: CryptoKey): Promise<TransitEnvelope> {\n const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));\n const encrypted = await globalThis.crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv as Uint8Array<ArrayBuffer> }, sessionKey, plaintext as Uint8Array<ArrayBuffer>);\n const ct = encrypted.slice(0, encrypted.byteLength - 16);\n const tag = encrypted.slice(encrypted.byteLength - 16);\n return { v: 1, iv: bufToB64(iv), tag: bufToB64(tag), ct: bufToB64(ct) };\n}\n\nasync function encryptNoble(plaintext: Uint8Array, sessionKey: Uint8Array): Promise<TransitEnvelope> {\n const { gcm } = await import('@noble/ciphers/aes');\n const { randomBytes } = await import('@noble/hashes/utils');\n const iv = randomBytes(12);\n const cipher = gcm(sessionKey, iv);\n const encrypted = cipher.encrypt(plaintext); // ct + 16-byte tag appended\n const ct = encrypted.slice(0, encrypted.length - 16);\n const tag = encrypted.slice(encrypted.length - 16);\n return { v: 1, iv: uint8ToB64(iv), tag: uint8ToB64(tag), ct: uint8ToB64(ct) };\n}\n\n// ─── Decrypt ─────────────────────────────────────────────────────────────────\n\nexport async function decryptPayload(\n envelope: TransitEnvelope,\n sessionKey: AnySessionKey,\n): Promise<unknown> {\n if (!hasWebCrypto() || sessionKey instanceof Uint8Array) {\n return decryptNoble(envelope, sessionKey as Uint8Array);\n }\n return decryptWebCrypto(envelope, sessionKey as CryptoKey);\n}\n\nasync function decryptWebCrypto(envelope: TransitEnvelope, sessionKey: CryptoKey): Promise<unknown> {\n const iv = b64ToBuf(envelope.iv);\n const tag = b64ToBuf(envelope.tag);\n const ct = b64ToBuf(envelope.ct);\n const combined = new Uint8Array(ct.byteLength + tag.byteLength);\n combined.set(new Uint8Array(ct), 0);\n combined.set(new Uint8Array(tag), ct.byteLength);\n const decrypted = await globalThis.crypto.subtle.decrypt(\n { name: 'AES-GCM', iv: new Uint8Array(iv) },\n sessionKey,\n combined,\n );\n return JSON.parse(new TextDecoder().decode(decrypted));\n}\n\nasync function decryptNoble(envelope: TransitEnvelope, sessionKey: Uint8Array): Promise<unknown> {\n const { gcm } = await import('@noble/ciphers/aes');\n const iv = base64ToUint8(envelope.iv);\n const tag = base64ToUint8(envelope.tag);\n const ct = base64ToUint8(envelope.ct);\n const combined = new Uint8Array(ct.length + tag.length);\n combined.set(ct, 0);\n combined.set(tag, ct.length);\n const cipher = gcm(sessionKey, iv);\n const decrypted = cipher.decrypt(combined);\n return JSON.parse(new TextDecoder().decode(decrypted));\n}\n\nexport function isTransitEnvelope(v: unknown): v is TransitEnvelope {\n return (\n typeof v === 'object' &&\n v !== null &&\n (v as any).v === 1 &&\n typeof (v as any).iv === 'string' &&\n typeof (v as any).tag === 'string' &&\n typeof (v as any).ct === 'string'\n );\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction bufToB64(buf: ArrayBuffer | Uint8Array): string {\n const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);\n let str = '';\n bytes.forEach(b => { str += String.fromCharCode(b); });\n return btoa(str);\n}\n\nfunction uint8ToB64(bytes: Uint8Array): string {\n let str = '';\n bytes.forEach(b => { str += String.fromCharCode(b); });\n return btoa(str);\n}\n\nfunction b64ToBuf(b64: string): ArrayBuffer {\n const bin = atob(b64);\n const buf = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);\n return buf.buffer;\n}\n\nfunction base64ToUint8(b64: string): Uint8Array {\n const bin = atob(b64);\n const buf = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);\n return buf;\n}\n","import type { TransitAlgo } from './detect.js';\n\ninterface TransitSession {\n sessionKey: CryptoKey | Uint8Array; // CryptoKey on Web Crypto, Uint8Array on noble/RN\n algo: TransitAlgo;\n sessionId: string;\n enabled: boolean;\n}\n\n// All state lives on globalThis so Turbopack <locals> module splits and any\n// other bundler that creates multiple instances of this module still share a\n// single source of truth. The symbol key prevents accidental collisions.\nconst _KEY = Symbol.for('__antz_chat_transit__');\n\ninterface TransitState {\n session: TransitSession | null;\n sessionEverEstablished: boolean;\n readyResolve: (() => void) | null;\n readyPromise: Promise<void> | null;\n transitConfigured: boolean | null;\n /** Edge-triggered listeners fired each time a session (re-)establishes. Used\n * by the socket emitters to re-send fire-and-forget state (join_room) that\n * was dropped while the handshake was settling. On globalThis like the rest\n * of transit state so bundler module-duplication can't split the set. */\n readyListeners: Set<() => void>;\n}\n\nfunction getState(): TransitState {\n const g = globalThis as any;\n if (!g[_KEY]) {\n g[_KEY] = {\n session: null,\n sessionEverEstablished: false,\n readyResolve: null,\n readyPromise: null,\n transitConfigured: null,\n readyListeners: new Set<() => void>(),\n } satisfies TransitState;\n }\n return g[_KEY] as TransitState;\n}\n\nexport function configureTransit(enabled: boolean): void {\n const s = getState();\n s.transitConfigured = enabled;\n if (!enabled) {\n // Transit disabled — resolve immediately so HTTP requests don't block\n s.readyResolve?.();\n s.readyResolve = null;\n }\n}\n\n/**\n * Set the transit-required flag ONLY if nothing has configured it yet. Used by\n * connectSocket() for socket-only consumers that never call initApiClient().\n * Must not override an explicit configureTransit(false) — that flag can carry\n * the authoritative \"server reported transit disabled\" signal, and re-gating\n * after it would wedge every request.\n */\nexport function configureTransitIfUnset(enabled: boolean): void {\n if (getState().transitConfigured === null) configureTransit(enabled);\n}\n\nexport function waitForTransitReady(): Promise<void> {\n const s = getState();\n // Not configured yet or disabled — resolve immediately\n if (!s.transitConfigured) return Promise.resolve();\n // Already have an active session — resolve immediately\n if (s.session) return Promise.resolve();\n // Transit is required but there is no session (first startup, or a socket\n // disconnect cleared it). Block until the handshake (re-)establishes one.\n // Resolving early here would let the request go out as plaintext and the\n // server rejects it with 403 \"Transit encryption required\". sessionEverEstablished\n // is deliberately NOT consulted — a stale `true` from a prior session must not\n // unblock a now-sessionless request. The request interceptor kicks a fresh\n // handshake before awaiting this, so the promise is guaranteed a resolver.\n if (!s.readyPromise) {\n s.readyPromise = new Promise<void>((resolve) => {\n s.readyResolve = resolve;\n });\n }\n return s.readyPromise;\n}\n\nexport function setTransitSession(session: TransitSession): void {\n const s = getState();\n s.session = session;\n s.sessionEverEstablished = true;\n // Resolve any pending HTTP requests waiting for the session key\n s.readyResolve?.();\n s.readyResolve = null;\n // Notify edge-triggered listeners (join_room re-flush, etc.)\n s.readyListeners.forEach((fn) => { try { fn(); } catch { /* listener must not break transit */ } });\n}\n\n/**\n * Register a listener fired every time a transit session (re-)establishes via\n * setTransitSession(). For re-sending fire-and-forget socket state that\n * secureEmit dropped during the handshake gap. Returns an unsubscribe fn.\n */\nexport function onTransitReady(listener: () => void): () => void {\n const s = getState();\n s.readyListeners.add(listener);\n return () => { s.readyListeners.delete(listener); };\n}\n\n/**\n * Wait until a transit session is available OR `timeoutMs` elapses, whichever\n * comes first. Never rejects. Returns true if it is now safe to proceed\n * (session present, or transit not required), false if it timed out with\n * transit still required and no session — the caller decides what a false\n * means (REST: fail the request loudly; socket emit: throw), so that a\n * handshake that never completes surfaces as a retryable error instead of an\n * infinite pending request that react-query can never recover.\n */\nexport async function awaitTransitReadyOr(timeoutMs: number): Promise<boolean> {\n const s = getState();\n if (!s.transitConfigured || s.session) return true;\n await Promise.race([\n waitForTransitReady(),\n new Promise<void>((r) => setTimeout(r, timeoutMs)),\n ]);\n const now = getState();\n return Boolean(now.session) || now.transitConfigured !== true;\n}\n\nexport function getTransitSession(): TransitSession | null {\n return getState().session;\n}\n\nexport function clearTransitSession(): void {\n const s = getState();\n s.session = null;\n // Reset sessionEverEstablished too. Leaving it `true` made waitForTransitReady()\n // (which trusted the flag) and isTransitEnabled() (which checks the live session)\n // permanently disagree after any clear-following-success: requests then went out\n // unencrypted and 403'd (\"Transit encryption required\") forever, with no path to\n // recovery. The ready promise is recreated lazily on the next waitForTransitReady().\n s.sessionEverEstablished = false;\n s.readyPromise = null;\n s.readyResolve = null;\n}\n\nexport function isTransitEnabled(): boolean {\n return getState().session?.enabled === true;\n}\n\n/**\n * True when transit encryption is *required* — i.e. the SDK was configured with\n * transitEncryption and the server has NOT authoritatively told us it is off\n * (which is the only thing that sets transitConfigured back to false).\n *\n * This is the correct signal for \"must this payload be encrypted?\". It is\n * deliberately distinct from isTransitEnabled() (which is \"is a live session\n * key available right now?\"): the gap between the two — required but no key —\n * is a handshake-in-progress / reconnect window where callers must WAIT or\n * FAIL, never fall through to plaintext.\n */\nexport function isTransitRequired(): boolean {\n return getState().transitConfigured === true;\n}\n\nexport function getSessionKey(): CryptoKey | Uint8Array | null {\n return getState().session?.sessionKey ?? null;\n}\n\n// Returns sessionId for the x-transit-session header sent with HTTP requests.\nexport function getSessionId(): string | null {\n return getState().session?.sessionId ?? null;\n}\n","export type TransitAlgo = 'x25519' | 'p256';\n\nlet _cached: TransitAlgo | null = null;\n\n// Probes Web Crypto API for X25519 support once; caches result for the session.\n// RN and Node callers never call this — they always get X25519 via @noble.\nexport async function detectTransitAlgo(): Promise<TransitAlgo> {\n if (_cached) return _cached;\n\n try {\n await globalThis.crypto.subtle.generateKey(\n { name: 'X25519' } as any,\n false,\n ['deriveKey'],\n );\n _cached = 'x25519';\n } catch {\n _cached = 'p256';\n }\n\n return _cached;\n}\n\nexport function getCachedAlgo(): TransitAlgo | null {\n return _cached;\n}\n\nexport function resetAlgoCache(): void {\n _cached = null;\n}\n","import type { TransitAlgo } from './detect.js';\nimport { detectTransitAlgo } from './detect.js';\n\n/**\n * Caller identity attached to the pre-auth transit handshake requests.\n *\n * GET /crypto/pubkey and POST /crypto/session run BEFORE a transit session (and\n * therefore before the authenticated axios client) exists, so they bypass the\n * request interceptor that normally adds these headers. Without them the server\n * can only rate-limit these two routes by client IP — which means every user\n * behind one NAT, office proxy or ALB shares a single bucket, and one client's\n * reload loop 429s everyone else.\n *\n * These values are NOT used for authentication: the endpoints are unauthenticated\n * by design and the server treats the headers as a fairness hint only, with a\n * per-IP ceiling underneath as the real abuse limit. Sending them is therefore\n * safe, optional, and backward compatible — an older SDK that omits them simply\n * falls back to the shared per-IP bucket.\n */\nexport interface TransitIdentity {\n /** External user id — same value sent as x-user-id on authenticated requests. */\n userId?: string;\n /** Tenant id — same value sent as X-Tenant-ID on authenticated requests. */\n tenantId?: string;\n}\n\n/**\n * How long the server asked us to wait, in ms, from a 429's Retry-After.\n *\n * The chat server runs TWO named rate-limit layers, and @nestjs/throttler\n * suffixes its headers with the throttler name unless that name is literally\n * \"default\". So a 429 carries `Retry-After-identity` or `Retry-After-ip`, not a\n * bare `Retry-After`. Older servers and intermediary proxies may still send the\n * bare name, so all three are read; when more than one is present the LONGER\n * wait wins, since retrying before the slower bucket drains just earns another\n * 429.\n *\n * Returns undefined when no variant is readable — in a browser that also\n * happens when the server omits these from Access-Control-Expose-Headers, in\n * which case callers must fall back to their own backoff.\n */\nexport function readRetryAfterMs(headers: Headers): number | undefined {\n const parse = (raw: string | null): number | undefined => {\n if (!raw) return undefined;\n const secs = Number(raw);\n if (Number.isFinite(secs)) return Math.max(0, secs * 1000);\n const when = Date.parse(raw);\n return Number.isNaN(when) ? undefined : Math.max(0, when - Date.now());\n };\n const found = ['Retry-After-identity', 'Retry-After-ip', 'Retry-After']\n .map((name) => parse(headers.get(name)))\n .filter((ms): ms is number => ms != null);\n return found.length > 0 ? Math.max(...found) : undefined;\n}\n\n/**\n * Thrown when the transit handshake is rate-limited (HTTP 429).\n *\n * Distinct from a generic failure because the correct response differs: a 429\n * means \"wait\", not \"this is broken\", so callers must not burn their retry\n * budget on it and should honour `retryAfterMs` when the server supplied it.\n */\nexport class TransitRateLimitedError extends Error {\n readonly retryAfterMs?: number;\n constructor(retryAfterMs?: number) {\n super('[AntzChat] transit handshake rate-limited (429)');\n this.name = 'TransitRateLimitedError';\n this.retryAfterMs = retryAfterMs;\n }\n}\n\n/** Build the identity headers, omitting whichever values the host app did not configure. */\nfunction identityHeaders(identity?: TransitIdentity): Record<string, string> {\n const headers: Record<string, string> = {};\n if (identity?.userId) headers['x-user-id'] = identity.userId;\n if (identity?.tenantId) headers['X-Tenant-ID'] = identity.tenantId;\n return headers;\n}\n\nexport interface ServerPublicKeys {\n x25519: string; // base64\n p256: string; // base64\n enabled: boolean;\n}\n\n// Returns true when the Web Crypto API is available (browser, Node 18+).\n// Hermes (React Native) does not expose crypto.subtle — use noble fallback.\nfunction hasWebCrypto(): boolean {\n return typeof globalThis.crypto?.subtle !== 'undefined';\n}\n\n// ─── Fetch ───────────────────────────────────────────────────────────────────\n\n// Fetches server public keys + enabled flag. Called once per init.\n// Unwraps the server's standard { success, data } envelope if present.\n// `identity` is optional and only affects server-side rate-limit bucketing —\n// see TransitIdentity. Omitting it preserves the previous (per-IP) behaviour.\nexport async function fetchServerKeys(\n apiUrl: string,\n identity?: TransitIdentity,\n): Promise<ServerPublicKeys> {\n const res = await fetch(`${apiUrl}/crypto/pubkey`, { headers: identityHeaders(identity) });\n if (res.status === 429) throw new TransitRateLimitedError(readRetryAfterMs(res.headers));\n if (!res.ok) throw new Error(`[AntzChat] Failed to fetch server public key: ${res.status}`);\n const body = await res.json() as any;\n return (body?.data ?? body) as ServerPublicKeys;\n}\n\n// ─── Core key generation ──────────────────────────────────────────────────────\n\n// Generates an ephemeral key pair and returns the public key (base64) plus a\n// bound deriveSessionKey closure that captures the private key.\n// Used by both the HTTPS handshake path and the socket handshake path.\nexport async function generateEphemeralKey(\n algo: TransitAlgo,\n serverKeys: ServerPublicKeys,\n): Promise<{ ephemeralPubB64: string; deriveSessionKey: (sessionId: string) => Promise<unknown> }> {\n if (hasWebCrypto()) {\n return generateWebCryptoEphemeralKey(algo, serverKeys);\n }\n return generateNobleEphemeralKey(serverKeys);\n}\n\n// ─── New HTTPS handshake (browser, Node 18+, React Native via noble) ─────────\n\n// Self-contained REST key exchange — no socket required.\n// 1. Fetch server public keys\n// 2. Generate ephemeral key pair\n// 3. POST /crypto/session { ephemeralPub, algo } → { sessionId }\n// 4. Derive session key locally via HKDF\n// Returns null when the server doesn't support the endpoint (old server) so\n// callers can fall back to the socket handshake path gracefully.\nexport async function createRestTransitSession(\n apiUrl: string,\n identity?: TransitIdentity,\n): Promise<{ sessionId: string; sessionKey: CryptoKey | Uint8Array } | null> {\n try {\n const serverKeys = await fetchServerKeys(apiUrl, identity);\n if (!serverKeys.enabled) return null;\n\n const algo = hasWebCrypto() ? await detectTransitAlgo() : 'x25519';\n const { ephemeralPubB64, deriveSessionKey } = await generateEphemeralKey(algo, serverKeys);\n\n const res = await fetch(`${apiUrl}/crypto/session`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...identityHeaders(identity) },\n body: JSON.stringify({ ephemeralPub: ephemeralPubB64, algo }),\n });\n // A 429 must NOT collapse into `null`: null means \"old server, fall back to\n // the socket handshake\", whereas a rate limit means \"this endpoint is fine,\n // wait and retry\". Conflating them makes the caller abandon a working path.\n if (res.status === 429) throw new TransitRateLimitedError(readRetryAfterMs(res.headers));\n if (!res.ok) return null; // old server without the endpoint — caller falls back\n\n const body = await res.json() as any;\n const sessionId = (body?.data ?? body)?.sessionId ?? body?.sessionId;\n if (!sessionId) return null;\n\n const sessionKey = await deriveSessionKey(sessionId) as CryptoKey | Uint8Array;\n return { sessionId, sessionKey };\n } catch (err) {\n // Rate limiting is a distinct, actionable condition — rethrow so the caller\n // can wait the requested interval. Everything else stays a soft null.\n if (err instanceof TransitRateLimitedError) throw err;\n return null;\n }\n}\n\n// ─── Socket handshake entry point (backward compat) ──────────────────────────\n\n// Performs the client side of the ECDH handshake via socket auth.\n// Injects ephemeralPub + algo into socketHandshakeAuth (mutates in place).\n// Returns a bound deriveSessionKey closure called after transit_session arrives.\n// Kept for old-server compatibility — new path uses createRestTransitSession.\nexport async function performHandshake(\n algo: TransitAlgo,\n serverKeys: ServerPublicKeys,\n socketHandshakeAuth: Record<string, unknown>,\n): Promise<(sessionId: string) => Promise<unknown>> {\n const { ephemeralPubB64, deriveSessionKey } = await generateEphemeralKey(algo, serverKeys);\n socketHandshakeAuth['transitEphemeralPub'] = ephemeralPubB64;\n socketHandshakeAuth['transitAlgo'] = algo;\n return deriveSessionKey;\n}\n\n// ─── Web Crypto path (browser, Node 18+) ─────────────────────────────────────\n\nasync function generateWebCryptoEphemeralKey(\n algo: TransitAlgo,\n serverKeys: ServerPublicKeys,\n): Promise<{ ephemeralPubB64: string; deriveSessionKey: (sessionId: string) => Promise<CryptoKey> }> {\n const ephemeral = await globalThis.crypto.subtle.generateKey(\n algo === 'x25519'\n ? { name: 'X25519' }\n : { name: 'ECDH', namedCurve: 'P-256' } as any,\n false,\n ['deriveBits'],\n );\n\n const pubRaw = await globalThis.crypto.subtle.exportKey('raw', (ephemeral as CryptoKeyPair).publicKey);\n const ephemeralPriv = (ephemeral as CryptoKeyPair).privateKey;\n\n return {\n ephemeralPubB64: bufToB64(pubRaw),\n deriveSessionKey: (sessionId: string) =>\n deriveWebCryptoSessionKey(ephemeralPriv, algo, serverKeys, sessionId),\n };\n}\n\nasync function deriveWebCryptoSessionKey(\n ephemeralPriv: CryptoKey,\n algo: TransitAlgo,\n serverKeys: ServerPublicKeys,\n sessionId: string,\n): Promise<CryptoKey> {\n const serverPubRaw = b64ToBuf(algo === 'x25519' ? serverKeys.x25519 : serverKeys.p256);\n const keyAlgoParams = algo === 'x25519' ? { name: 'X25519' } : { name: 'ECDH', namedCurve: 'P-256' };\n\n const serverPubKey = await globalThis.crypto.subtle.importKey('raw', serverPubRaw, keyAlgoParams as any, false, []);\n const sharedBits = await globalThis.crypto.subtle.deriveBits(\n { name: algo === 'x25519' ? 'X25519' : 'ECDH', public: serverPubKey } as any,\n ephemeralPriv,\n 256,\n );\n const hkdfKey = await globalThis.crypto.subtle.importKey('raw', sharedBits, 'HKDF', false, ['deriveKey']);\n const salt = new TextEncoder().encode(sessionId);\n const info = new TextEncoder().encode('antz-transit-v1');\n\n return globalThis.crypto.subtle.deriveKey(\n { name: 'HKDF', hash: 'SHA-256', salt, info },\n hkdfKey,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt', 'decrypt'],\n );\n}\n\n// ─── Noble path (React Native / Hermes) ──────────────────────────────────────\n\nasync function generateNobleEphemeralKey(\n serverKeys: ServerPublicKeys,\n): Promise<{ ephemeralPubB64: string; deriveSessionKey: (sessionId: string) => Promise<Uint8Array> }> {\n const { x25519 } = await import('@noble/curves/ed25519');\n const { hkdf } = await import('@noble/hashes/hkdf');\n const { sha256 } = await import('@noble/hashes/sha256');\n const { randomBytes } = await import('@noble/hashes/utils');\n\n const ephemeralPriv = randomBytes(32);\n const ephemeralPub = x25519.getPublicKey(ephemeralPriv);\n const serverPubBytes = base64ToUint8(serverKeys.x25519);\n\n return {\n ephemeralPubB64: uint8ToBase64(ephemeralPub),\n deriveSessionKey: (sessionId: string): Promise<Uint8Array> => {\n const sharedSecret = x25519.getSharedSecret(ephemeralPriv, serverPubBytes);\n const salt = new TextEncoder().encode(sessionId);\n const info = new TextEncoder().encode('antz-transit-v1');\n return Promise.resolve(hkdf(sha256, sharedSecret, salt, info, 32) as Uint8Array);\n },\n };\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction bufToB64(buf: ArrayBuffer): string {\n const bytes = new Uint8Array(buf);\n let str = '';\n bytes.forEach(b => { str += String.fromCharCode(b); });\n return btoa(str);\n}\n\nfunction b64ToBuf(b64: string): ArrayBuffer {\n const bin = atob(b64);\n const buf = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);\n return buf.buffer;\n}\n\nfunction uint8ToBase64(bytes: Uint8Array): string {\n let str = '';\n bytes.forEach(b => { str += String.fromCharCode(b); });\n return btoa(str);\n}\n\nfunction base64ToUint8(b64: string): Uint8Array {\n const bin = atob(b64);\n const buf = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);\n return buf;\n}\n","import { isAxiosError } from 'axios';\nimport { isTransitEnvelope } from './crypto/transit.js';\n\n// ─── Base error class ─────────────────────────────────────────────────────────\n\nexport class AntzChatError extends Error {\n readonly code: string;\n readonly retryable: boolean;\n readonly context?: Record<string, unknown>;\n\n constructor(\n code: string,\n message: string,\n retryable = false,\n context?: Record<string, unknown>,\n ) {\n super(message);\n this.name = 'AntzChatError';\n this.code = code;\n this.retryable = retryable;\n this.context = context;\n // Maintain proper prototype chain in transpiled environments\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n// ─── Semantic subclasses ──────────────────────────────────────────────────────\n\n/** Thrown on 401 (after refresh also fails) or when no refresh token exists. */\nexport class AntzChatAuthError extends AntzChatError {\n constructor(message: string, code = 'AUTH_FAILED', context?: Record<string, unknown>) {\n super(code, message, false, context);\n this.name = 'AntzChatAuthError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown on 400 / 422 — bad input, validation failure. */\nexport class AntzChatValidationError extends AntzChatError {\n /** Server-returned field error array (when the server sends message as string[]). */\n readonly fields?: string[];\n\n constructor(message: string | string[], context?: Record<string, unknown>) {\n const msg = Array.isArray(message) ? message.join('; ') : message;\n super('VALIDATION_ERROR', msg, false, context);\n this.name = 'AntzChatValidationError';\n this.fields = Array.isArray(message) ? message : undefined;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown on network failures, timeouts, socket disconnections, and queue overflow. retryable = true. */\nexport class AntzChatNetworkError extends AntzChatError {\n constructor(message: string, code = 'NETWORK_ERROR', context?: Record<string, unknown>) {\n super(code, message, true, context);\n this.name = 'AntzChatNetworkError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown on 403 — insufficient permissions. */\nexport class AntzChatPermissionError extends AntzChatError {\n constructor(message: string, context?: Record<string, unknown>) {\n super('PERMISSION_DENIED', message, false, context);\n this.name = 'AntzChatPermissionError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown on 5xx or other unexpected server errors. retryable = true. */\nexport class AntzChatServerError extends AntzChatError {\n readonly httpStatus?: number;\n\n constructor(message: string, httpStatus?: number, context?: Record<string, unknown>) {\n super('SERVER_ERROR', message, true, context);\n this.name = 'AntzChatServerError';\n this.httpStatus = httpStatus;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n// ─── Error code reference ─────────────────────────────────────────────────────\n//\n// Code Class Source\n// ───────────────────── ──────────────────────── ─────────────────────────\n// AUTH_FAILED AntzChatAuthError 401 after refresh fails\n// SESSION_EXPIRED AntzChatAuthError 401, no refresh token\n// PERMISSION_DENIED AntzChatPermissionError 403\n// VALIDATION_ERROR AntzChatValidationError 400 / 422\n// NOT_FOUND AntzChatServerError 404\n// RATE_LIMITED AntzChatNetworkError 429\n// NETWORK_ERROR AntzChatNetworkError No response / conn failure\n// SOCKET_TIMEOUT AntzChatNetworkError ACK timeout / reconnect timeout\n// SOCKET_NOT_CONNECTED AntzChatNetworkError withAck when socket is down\n// SEND_QUEUE_FULL AntzChatNetworkError Queue overflow (>100 msgs)\n// MESSAGE_DROPPED AntzChatNetworkError Queue TTL expired (30s)\n// TRANSIT_MISMATCH AntzChatError SDK/server encryption config mismatch\n// SERVER_ERROR AntzChatServerError 5xx or unknown HTTP error\n\n// ─── REST error normaliser ────────────────────────────────────────────────────\n\n/**\n * Converts a raw axios error (or any unknown throw) into a typed AntzChatError.\n *\n * Call site: client.ts response interceptor — runs AFTER transit decryption,\n * so error.response.data is always plaintext by the time this function sees it.\n * If decryption itself failed, error.response.data remains the raw encrypted\n * envelope — detected via isTransitEnvelope() and noted in context.\n */\nexport function normalizeAxiosError(error: unknown): AntzChatError {\n if (error instanceof AntzChatError) return error;\n\n if (isAxiosError(error)) {\n const status = error.response?.status;\n const body = error.response?.data;\n\n // Detect if decryption failed — body is still an encrypted envelope\n const decryptionFailed = body != null && isTransitEnvelope(body);\n\n const rawMessage: string | string[] | undefined = decryptionFailed\n ? undefined\n : (body?.message ?? undefined);\n\n const message: string =\n (Array.isArray(rawMessage) ? rawMessage.join('; ') : rawMessage) ||\n error.message ||\n 'Request failed';\n\n const ctx: Record<string, unknown> = {\n ...(status != null && { httpStatus: status }),\n ...(body?.path != null && { path: body.path }),\n ...(body?.error != null && { serverError: body.error }),\n ...(error.code != null && { axiosCode: error.code }),\n ...(decryptionFailed && { decryptionFailed: true, note: 'Transit decryption failed — server error body is an encrypted envelope' }),\n };\n\n // No response at all — network/timeout failure\n if (!error.response) {\n return new AntzChatNetworkError(message || 'Network error', 'NETWORK_ERROR', ctx);\n }\n\n if (status === 401) {\n // AUTH_FAILED is used when a refresh was attempted but failed (set by interceptor).\n // SESSION_EXPIRED is the default: 401 with no prior retry = token simply expired.\n const code = (error.config as any)?._retry ? 'AUTH_FAILED' : 'SESSION_EXPIRED';\n return new AntzChatAuthError(message, code, ctx);\n }\n if (status === 403) return new AntzChatPermissionError(message, ctx);\n if (status === 400 || status === 422) {\n return new AntzChatValidationError(\n Array.isArray(rawMessage) ? rawMessage : message,\n ctx,\n );\n }\n if (status === 404) return new AntzChatServerError(message, 404, ctx);\n if (status === 429) return new AntzChatNetworkError(message, 'RATE_LIMITED', ctx);\n if (status != null && status >= 500) return new AntzChatServerError(message, status, ctx);\n\n return new AntzChatServerError(message, status, ctx);\n }\n\n const msg = error instanceof Error ? error.message : String(error);\n return new AntzChatError('UNKNOWN_ERROR', msg, false);\n}\n","import axios, {\n AxiosInstance,\n InternalAxiosRequestConfig,\n} from 'axios';\nimport type { ResolvedConfig } from '../config/types.js';\nimport type { AuthTokens } from '../types/index.js';\nimport { encryptPayload, decryptPayload, isTransitEnvelope } from '../crypto/transit.js';\nimport { getSessionKey, getSessionId, isTransitEnabled, awaitTransitReadyOr, configureTransit, setTransitSession, getTransitSession } from '../crypto/session.js';\nimport { createRestTransitSession, fetchServerKeys, TransitRateLimitedError } from '../crypto/handshake.js';\nimport { detectTransitAlgo } from '../crypto/detect.js';\nimport { normalizeAxiosError, AntzChatNetworkError } from '../errors.js';\n\n// Hard ceiling on how long a single request will block waiting for the transit\n// handshake. A legitimate cold-start handshake resolves in well under this even\n// on a slow link (TransitGate already spent ~6s, establishTransit keeps\n// retrying). Past this we FAIL the request with a retryable error rather than\n// leave it pending forever — react-query cannot retry / refetch-on-focus a\n// request that never settles, so an unbounded wait here is an unrecoverable\n// silent hang. Each failed+retried request also re-arms ensureRestTransitHandshake.\nconst TRANSIT_GATE_MAX_WAIT_MS = 30_000;\n\nexport type TokenStore = {\n getAccessToken: () => string | null | undefined;\n getRefreshToken: () => string | null | undefined;\n setTokens: (tokens: AuthTokens) => void;\n clearTokens: () => void;\n};\n\nlet _tokenStore: TokenStore | null = null;\nlet _config: ResolvedConfig | null = null;\nlet _avatarSent = false;\n// In-flight transit handshake promise — shared between initApiClient and connectSocket\n// so they never fire two concurrent HTTPS handshakes for the same session.\nlet _transitHandshakePromise: Promise<void> | null = null;\n// Gate the request interceptor until the SDK has resolved a token (async\n// authProvider) and, where wired, the transit handshake. Set by the SDK\n// provider; the interceptor awaits it before attaching the Authorization\n// header so early requests (e.g. useConversations' initial fetch) don't race\n// ahead unauthenticated.\nlet _authReadyPromise: Promise<unknown> | null = null;\n\nexport function getTransitHandshakePromise(): Promise<void> | null {\n return _transitHandshakePromise;\n}\n\nexport function setAuthReadyPromise(promise: Promise<unknown> | null): void {\n _authReadyPromise = promise;\n}\n\n// True once initApiClient() has run and its config has not been torn down by a\n// subsequent disconnectSocket(). Lets the SDK provider detect the case where a\n// React remount skipped re-init (its key was unchanged) but disconnectSocket()\n// had nulled _config in between — leaving the request interceptor unable to see\n// transitEncryption and firing every request as unencrypted plaintext.\nexport function isApiClientConfigured(): boolean {\n return _config !== null;\n}\n\n// (Re-)kick the HTTPS transit handshake. Idempotent: no-ops when transit is\n// disabled, a session already exists, or an attempt is already in flight.\n// Called both at init and from the request interceptor when a request is about\n// to block on waitForTransitReady() with no session — e.g. after a socket\n// disconnect cleared the session and nothing else re-established it.\n//\n// It only calls configureTransit(false) — which un-gates the interceptor and\n// lets requests go out as PLAINTEXT — when the server itself reports transit\n// disabled (GET /crypto/pubkey → enabled:false, i.e. an old server). A transient\n// failure of POST /crypto/session (network blip, rate limit, 5xx) must NOT\n// disable transit: the server still requires it, so plaintext would just 403.\n// Instead we retry with backoff; waitForTransitReady() keeps requests pending\n// and they dispatch the moment a retry sets the session.\nexport function ensureRestTransitHandshake(): void {\n if (!_config?.transitEncryption || getTransitSession() || _transitHandshakePromise) return;\n const apiUrl = _config.apiUrl;\n _transitHandshakePromise = (async () => {\n try {\n // A 429 is \"wait\", not \"broken\", so it must NOT consume the attempt\n // budget — otherwise a rate-limited client exhausts 5 attempts in a few\n // seconds and gives up on an endpoint that was working fine. Failures are\n // counted separately from rate-limit hits, and the loop is additionally\n // bounded by wall-clock time so a persistently limited server cannot keep\n // it running forever.\n const MAX_FAILURES = 5;\n const BACKSTOP_MS = 2 * 60_000;\n const deadline = Date.now() + BACKSTOP_MS;\n let failures = 0;\n let rateLimitHits = 0;\n\n while (failures < MAX_FAILURES && Date.now() < deadline) {\n if (getTransitSession()) return;\n let waitMs: number;\n try {\n // Identity is sent so the server can rate-limit these pre-auth routes\n // per user rather than per IP (see TransitIdentity in handshake.ts).\n const identity = { userId: _config?.userId, tenantId: _config?.tenantId };\n const keys = await fetchServerKeys(apiUrl, identity);\n if (!keys?.enabled) {\n configureTransit(false); // server genuinely doesn't want transit\n return;\n }\n const session = await createRestTransitSession(apiUrl, identity);\n if (session && !getTransitSession()) {\n const algo = typeof globalThis.crypto?.subtle !== 'undefined'\n ? await detectTransitAlgo()\n : 'x25519';\n setTransitSession({ sessionKey: session.sessionKey as CryptoKey, algo, sessionId: session.sessionId, enabled: true });\n return;\n }\n // Reachable when the server returned no session but did not throw\n // (e.g. an old server without the endpoint) — treat as a failure.\n failures++;\n waitMs = Math.min(500 * 2 ** failures, 8_000);\n } catch (err) {\n if (err instanceof TransitRateLimitedError) {\n // Honour the server's own figure when it sent one (clamped to a\n // sane 1-60s), else escalate blind since the window is unknown.\n const blind = Math.min(15_000 * 2 ** rateLimitHits, 60_000);\n waitMs = err.retryAfterMs != null\n ? Math.min(Math.max(err.retryAfterMs, 1_000), 60_000)\n : blind;\n rateLimitHits++;\n console.warn(\n `[AntzChat] transit handshake rate-limited (429) — retrying in ${Math.round(waitMs / 1000)}s` +\n `${err.retryAfterMs != null ? ' (per Retry-After)' : ''}.`,\n );\n } else {\n failures++;\n waitMs = Math.min(500 * 2 ** failures, 8_000);\n }\n }\n await new Promise((r) => setTimeout(r, waitMs));\n }\n console.error(\n '[AntzChat] transit handshake could not establish a session — ' +\n \"chat requests stay gated until one succeeds (server requires transit).\",\n );\n } finally {\n _transitHandshakePromise = null;\n }\n })();\n}\n\nexport function initApiClient(config: ResolvedConfig, tokenStore: TokenStore): AxiosInstance {\n _config = config;\n _tokenStore = tokenStore;\n _avatarSent = false; // reset on re-init (new session / authToken change)\n\n const client = axios.create({\n baseURL: config.apiUrl,\n headers: { 'Content-Type': 'application/json' },\n });\n\n // Configure transit as early as possible — before any requests fire —\n // so waitForTransitReady() in the interceptor knows whether to block or not.\n configureTransit(config.transitEncryption);\n\n // Kick off the HTTPS transit handshake immediately so REST calls that fire\n // before connectSocket (e.g. getMe() right after initApiClient) are not\n // blocked indefinitely. Store the promise so connectSocket can await it\n // instead of firing a duplicate handshake.\n ensureRestTransitHandshake();\n\n // ── Request interceptor ──────────────────────────────────────────────────\n client.interceptors.request.use(async (req: InternalAxiosRequestConfig) => {\n // Wait for the SDK's auth (and, where wired, transit) gate before reading\n // the token — otherwise a request fired during boot goes out with no\n // Authorization header.\n if (_authReadyPromise) await _authReadyPromise;\n\n const token = _tokenStore?.getAccessToken();\n if (token) req.headers['Authorization'] = `Bearer ${token}`;\n if (_config?.userId) req.headers['x-user-id'] = _config.userId;\n if (_config?.tenantId) req.headers['X-Tenant-ID'] = _config.tenantId;\n // Send avatar on the first request only — server hashes and deduplicates\n if (token && !_avatarSent && _config?.avatar) {\n if (_config.avatar.base64) req.headers['x-avatar-base64'] = _config.avatar.base64;\n else if (_config.avatar.url) req.headers['x-avatar-url'] = _config.avatar.url;\n _avatarSent = true;\n }\n\n // Wait for the transit session key before sending any request. The server\n // enforces transit encryption independent of auth (e.g. GET /app/config\n // fires before the async authProvider token resolves) — gating this on\n // `token` let those pre-auth requests race ahead of the handshake and get\n // rejected with 403 \"Transit encryption required\".\n if (_config?.transitEncryption) {\n // If the session is gone (socket disconnect cleared it, first boot still\n // pending), make sure a handshake is running before we block — otherwise\n // the wait could hang with nothing to resolve it.\n if (!getTransitSession()) ensureRestTransitHandshake();\n const ready = await awaitTransitReadyOr(TRANSIT_GATE_MAX_WAIT_MS);\n if (!ready) {\n // Handshake still hasn't produced a session. Do NOT send plaintext\n // (server requires transit); fail loudly instead so the error surfaces\n // in the UI and react-query's retry re-drives the handshake.\n throw new AntzChatNetworkError(\n 'Secure channel to chat server not established — request not sent. It will retry automatically.',\n 'TRANSIT_NOT_READY',\n { url: req.url },\n );\n }\n }\n\n if (isTransitEnabled()) {\n const sessionId = getSessionId();\n const key = getSessionKey();\n if (sessionId && key) {\n req.headers['x-transit-session'] = sessionId;\n if (req.data !== undefined && req.data !== null) {\n const envelope = await encryptPayload(req.data, key);\n req.data = envelope;\n req.headers['x-transit-encrypted'] = '1';\n }\n }\n }\n\n return req;\n });\n\n let isRefreshing = false;\n let refreshQueue: Array<(token: string) => void> = [];\n\n // ── Response interceptor ─────────────────────────────────────────────────\n client.interceptors.response.use(\n async (response) => {\n // Transit decryption — server wraps encrypted payload inside { success, data: <envelope> }.\n // Decrypt the inner envelope, then let the standard unwrap below handle { success, data }.\n if (isTransitEnabled()) {\n const key = getSessionKey();\n if (key) {\n // Case 1: entire response is the envelope (unlikely but handle it)\n if (isTransitEnvelope(response.data)) {\n response.data = await decryptPayload(response.data, key);\n }\n // Case 2: envelope is nested inside { success, data: <envelope> }\n else if (response.data?.data && isTransitEnvelope(response.data.data)) {\n response.data.data = await decryptPayload(response.data.data, key);\n }\n }\n }\n\n // Standard { success, data } unwrap\n if (\n response.data &&\n typeof response.data === 'object' &&\n 'success' in response.data &&\n 'data' in response.data\n ) {\n response.data = response.data.data;\n }\n return response;\n },\n async (error) => {\n // Decrypt error response body — error filter encrypts it too\n if (isTransitEnabled() && error.response?.data) {\n const key = getSessionKey();\n if (key) {\n try {\n if (isTransitEnvelope(error.response.data)) {\n error.response.data = await decryptPayload(error.response.data, key);\n } else if (error.response.data?.data && isTransitEnvelope(error.response.data.data)) {\n error.response.data.data = await decryptPayload(error.response.data.data, key);\n }\n } catch { /* decryption failed — leave as-is */ }\n }\n }\n\n const original = error.config as InternalAxiosRequestConfig & { _retry?: boolean };\n\n if (error.response?.status === 401 && !original._retry) {\n const refreshToken = _tokenStore?.getRefreshToken();\n if (!refreshToken) {\n _tokenStore?.clearTokens();\n return Promise.reject(normalizeAxiosError(error));\n }\n\n if (isRefreshing) {\n return new Promise((resolve) => {\n refreshQueue.push((newToken) => {\n original.headers['Authorization'] = `Bearer ${newToken}`;\n resolve(client(original));\n });\n });\n }\n\n original._retry = true;\n isRefreshing = true;\n\n try {\n const { data } = await axios.post<{ data: AuthTokens }>(\n `${_config!.apiUrl}/auth/refresh`,\n { refreshToken },\n );\n const tokens: AuthTokens = (data as any).data ?? data;\n _tokenStore?.setTokens(tokens);\n refreshQueue.forEach((cb) => cb(tokens.accessToken));\n refreshQueue = [];\n original.headers['Authorization'] = `Bearer ${tokens.accessToken}`;\n return client(original);\n } catch {\n _tokenStore?.clearTokens();\n return Promise.reject(normalizeAxiosError(error));\n } finally {\n isRefreshing = false;\n }\n }\n\n return Promise.reject(normalizeAxiosError(error));\n },\n );\n\n return client;\n}\n\nlet _instance: AxiosInstance | null = null;\n\nexport function setApiClientInstance(instance: AxiosInstance) {\n _instance = instance;\n}\n\nexport function getApiClient(): AxiosInstance {\n if (!_instance) throw new Error('[AntzChat] API client not initialized. Call initApiClient first.');\n return _instance;\n}\n","// crypto.randomUUID() doesn't exist in React Native's Hermes engine.\n// Fall back to a RFC 4122 v4 UUID built from Math.random() when unavailable.\nexport function generateUUID(): string {\n if (typeof globalThis.crypto?.randomUUID === 'function') {\n return globalThis.crypto.randomUUID();\n }\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);\n });\n}\n","import type {\n BatchUploadResult,\n FileResponse,\n PaginatedResponse,\n PresignedUrlRequest,\n PresignedUrlResponse,\n FileType,\n UploadableFile,\n CompletedPart,\n} from '../types/index.js';\nimport type { PlatformUploadFn, PlatformCompressFn, PlatformUploadPartFn, ResolvedCompressionConfig } from '../config/types.js';\nimport { compressFile } from '../compression/compress.js';\nimport { getApiClient } from './client.js';\nimport { generateUUID } from '../crypto/uuid.js';\n\nexport const storageApi = {\n async requestPresignedUrl(payload: PresignedUrlRequest): Promise<PresignedUrlResponse> {\n const { data } = await getApiClient().post<PresignedUrlResponse>('/storage/presigned-url', payload);\n return data;\n },\n\n async requestPresignedUrlBatch(files: PresignedUrlRequest[]): Promise<{\n urls: PresignedUrlResponse[];\n errors: Array<{ filename: string; error: string; clientIndex?: number }>;\n }> {\n const { data } = await getApiClient().post('/storage/presigned-url/batch', { files });\n return data;\n },\n\n async confirmUpload(fileId: string): Promise<FileResponse> {\n const { data } = await getApiClient().post<FileResponse>(`/storage/confirm/${fileId}`);\n return data;\n },\n\n async getFile(fileId: string): Promise<FileResponse> {\n const { data } = await getApiClient().get<FileResponse>(`/storage/files/${fileId}`);\n return data;\n },\n\n async getFileUrl(fileId: string, expiresIn?: number): Promise<{ url: string; expiresAt: string }> {\n const { data } = await getApiClient().get(`/storage/files/${fileId}/url`, {\n params: expiresIn ? { expiresIn } : {},\n });\n return data;\n },\n\n async deleteFile(fileId: string): Promise<void> {\n await getApiClient().post(`/storage/files/${fileId}/delete`);\n },\n\n async completeMultipartUpload(\n fileId: string,\n uploadId: string,\n parts: CompletedPart[],\n ): Promise<FileResponse> {\n const { data } = await getApiClient().post<FileResponse>(\n `/storage/multipart/complete/${fileId}`,\n { uploadId, parts },\n );\n return data;\n },\n\n async getConversationFiles(\n conversationId: string,\n params: { page?: number; limit?: number; type?: FileType } = {},\n ): Promise<PaginatedResponse<FileResponse>> {\n const { data } = await getApiClient().get(\n `/storage/conversations/${conversationId}/files`,\n { params },\n );\n return data;\n },\n\n async getMyFiles(params: { page?: number; limit?: number } = {}): Promise<PaginatedResponse<FileResponse>> {\n const { data } = await getApiClient().get('/storage/my-files', { params });\n return data;\n },\n};\n\nasync function runMultipartUpload(\n presigned: PresignedUrlResponse,\n file: UploadableFile,\n platformUploadPartFn: PlatformUploadPartFn,\n onProgress?: (pct: number) => void,\n): Promise<FileResponse> {\n const { multipart } = presigned;\n if (!multipart) throw new Error('No multipart info on presigned response');\n\n const CONCURRENCY = 3;\n const completedParts: CompletedPart[] = [];\n const partProgress: Record<number, number> = {};\n\n multipart.partUrls.forEach(({ partNumber }) => { partProgress[partNumber] = 0; });\n\n const reportProgress = () => {\n if (!onProgress) return;\n const vals = Object.values(partProgress);\n const avg = vals.reduce((s, v) => s + v, 0) / Math.max(vals.length, 1);\n onProgress(Math.round(avg * 0.95));\n };\n\n const uploadPart = async (partNumber: number, uploadUrl: string, method: 'PUT' | 'POST'): Promise<void> => {\n const offset = (partNumber - 1) * multipart.chunkSize;\n const end = Math.min(offset + multipart.chunkSize, file.size);\n const blob = await fetch(file.uri).then((r) => r.blob());\n const slice = blob.slice(offset, end);\n\n const etag = await platformUploadPartFn(uploadUrl, slice, (pct) => {\n partProgress[partNumber] = pct;\n reportProgress();\n }, method);\n\n completedParts.push({ partNumber, etag });\n partProgress[partNumber] = 100;\n reportProgress();\n };\n\n for (let i = 0; i < multipart.partUrls.length; i += CONCURRENCY) {\n const batch = multipart.partUrls.slice(i, i + CONCURRENCY);\n const results = await Promise.allSettled(\n batch.map(({ partNumber, uploadUrl, method }) => uploadPart(partNumber, uploadUrl, method ?? 'PUT')),\n );\n const failed = results.find((r) => r.status === 'rejected') as PromiseRejectedResult | undefined;\n if (failed) throw failed.reason;\n }\n\n completedParts.sort((a, b) => a.partNumber - b.partNumber);\n\n const fileResponse = await storageApi.completeMultipartUpload(\n presigned.fileId,\n multipart.uploadId,\n completedParts,\n );\n onProgress?.(100);\n return fileResponse;\n}\n\n/**\n * Core upload implementation. Returns the public BatchUploadResult plus a\n * slotId → FileResponse map that useChat hooks use internally to match\n * confirmed uploads back to optimistic UI slots by position rather than\n * filename. The slotToFile map is never part of the public API.\n */\nasync function runUploadBatch(\n files: UploadableFile[],\n platformUploadFn: PlatformUploadFn,\n slotIds: string[],\n conversationId?: string,\n onProgress?: (pct: number) => void,\n platformCompressFn?: PlatformCompressFn,\n compressionConfig?: ResolvedCompressionConfig,\n platformUploadPartFn?: PlatformUploadPartFn,\n): Promise<{ result: BatchUploadResult; slotToFile: Map<string, FileResponse> }> {\n // Compress all files first (no-ops for unsupported types or when disabled)\n const compressedFiles = await Promise.all(\n files.map((f) => compressFile(f, platformCompressFn, compressionConfig ?? { enabled: false, imageQuality: 0.85, imageMaxDimension: 1920, compressDocuments: true })),\n );\n\n // Pair each compressed file with its slot ID and a clientIndex.\n // clientIndex is sent to the server and echoed back in both urls and errors,\n // giving us a reliable position mapping regardless of which files fail.\n const slotted = compressedFiles.map((f, i) => ({ file: f, slotId: slotIds[i], clientIndex: i }));\n\n const requests: PresignedUrlRequest[] = slotted.map(({ file: f, clientIndex }) => ({\n filename: f.name,\n mimeType: f.type,\n size: f.size,\n conversationId,\n clientIndex,\n ...(f.compressed && {\n metadata: {\n compressed: f.compressed,\n originalSize: f.originalSize,\n compressionAlgorithm: f.compressionAlgorithm,\n },\n }),\n }));\n\n const { urls, errors: requestErrors } = await storageApi.requestPresignedUrlBatch(requests);\n\n // Use the echoed clientIndex to identify which original slots failed.\n // This is reliable even for same-named files and any failure pattern.\n const failedSlotIds = new Set<string>();\n const failed: Array<{ filename: string; error: string }> = requestErrors.map((e) => {\n const idx = e.clientIndex ?? slotted.findIndex((s) => s.file.name === e.filename);\n const slotId = slotted[idx]?.slotId;\n if (slotId) failedSlotIds.add(slotId);\n return { filename: e.filename, error: e.error };\n });\n\n // Map each presigned URL back to its original slot via clientIndex.\n const progressMap: Record<number, number> = {};\n const reportProgress = () => {\n if (!onProgress) return;\n const vals = Object.values(progressMap);\n const avg = vals.reduce((s, v) => s + v, 0) / Math.max(vals.length, 1);\n onProgress(Math.round(avg));\n };\n\n const successful: FileResponse[] = [];\n const slotToFile = new Map<string, FileResponse>();\n\n await Promise.all(\n urls.map(async (presigned, idx) => {\n // Resolve the original slot via echoed clientIndex; fall back to position\n // in urls[] only if the server didn't echo it (older server version).\n const originalIdx = presigned.clientIndex ?? idx;\n const { file, slotId } = slotted[originalIdx];\n progressMap[originalIdx] = 0;\n try {\n let fileResponse: FileResponse;\n if (presigned.multipart && platformUploadPartFn) {\n fileResponse = await runMultipartUpload(presigned, file, platformUploadPartFn, (pct) => {\n progressMap[originalIdx] = pct;\n reportProgress();\n });\n } else {\n await platformUploadFn(presigned, file, (pct) => {\n progressMap[originalIdx] = Math.round(pct * 0.9);\n reportProgress();\n });\n fileResponse = await storageApi.confirmUpload(presigned.fileId);\n }\n progressMap[originalIdx] = 100;\n reportProgress();\n successful.push(fileResponse);\n slotToFile.set(slotId, fileResponse);\n } catch (err) {\n failed.push({ filename: file.name, error: (err as Error).message });\n }\n }),\n );\n\n return { result: { successful, failed }, slotToFile };\n}\n\n/** Public API — returns standard BatchUploadResult, slot tracking is internal. */\nexport async function uploadBatch(\n files: UploadableFile[],\n platformUploadFn: PlatformUploadFn,\n conversationId?: string,\n onProgress?: (pct: number) => void,\n platformCompressFn?: PlatformCompressFn,\n compressionConfig?: ResolvedCompressionConfig,\n platformUploadPartFn?: PlatformUploadPartFn,\n): Promise<BatchUploadResult> {\n const slotIds = files.map(() => generateUUID());\n const { result } = await runUploadBatch(files, platformUploadFn, slotIds, conversationId, onProgress, platformCompressFn, compressionConfig, platformUploadPartFn);\n return result;\n}\n\n/**\n * Used only by useChat hooks (web + RN) to get the slotId → FileResponse map\n * for matching confirmed uploads back to optimistic UI slots.\n * Not exported from the package index — internal SDK use only.\n */\nexport async function uploadBatchWithSlots(\n files: UploadableFile[],\n platformUploadFn: PlatformUploadFn,\n slotIds: string[],\n conversationId?: string,\n onProgress?: (pct: number) => void,\n platformCompressFn?: PlatformCompressFn,\n compressionConfig?: ResolvedCompressionConfig,\n platformUploadPartFn?: PlatformUploadPartFn,\n): Promise<{ result: BatchUploadResult; slotToFile: Map<string, FileResponse> }> {\n return runUploadBatch(files, platformUploadFn, slotIds, conversationId, onProgress, platformCompressFn, compressionConfig, platformUploadPartFn);\n}\n"],"mappings":"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","names":["hasWebCrypto","bufToB64","b64ToBuf","base64ToUint8"]}