@antzsoft/chat-core 1.4.5 → 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/dist/index.d.cts CHANGED
@@ -395,17 +395,66 @@ declare function encryptPayload(data: unknown, sessionKey: AnySessionKey): Promi
395
395
  declare function decryptPayload(envelope: TransitEnvelope, sessionKey: AnySessionKey): Promise<unknown>;
396
396
  declare function isTransitEnvelope(v: unknown): v is TransitEnvelope;
397
397
 
398
+ /**
399
+ * Caller identity attached to the pre-auth transit handshake requests.
400
+ *
401
+ * GET /crypto/pubkey and POST /crypto/session run BEFORE a transit session (and
402
+ * therefore before the authenticated axios client) exists, so they bypass the
403
+ * request interceptor that normally adds these headers. Without them the server
404
+ * can only rate-limit these two routes by client IP — which means every user
405
+ * behind one NAT, office proxy or ALB shares a single bucket, and one client's
406
+ * reload loop 429s everyone else.
407
+ *
408
+ * These values are NOT used for authentication: the endpoints are unauthenticated
409
+ * by design and the server treats the headers as a fairness hint only, with a
410
+ * per-IP ceiling underneath as the real abuse limit. Sending them is therefore
411
+ * safe, optional, and backward compatible — an older SDK that omits them simply
412
+ * falls back to the shared per-IP bucket.
413
+ */
414
+ interface TransitIdentity {
415
+ /** External user id — same value sent as x-user-id on authenticated requests. */
416
+ userId?: string;
417
+ /** Tenant id — same value sent as X-Tenant-ID on authenticated requests. */
418
+ tenantId?: string;
419
+ }
420
+ /**
421
+ * How long the server asked us to wait, in ms, from a 429's Retry-After.
422
+ *
423
+ * The chat server runs TWO named rate-limit layers, and @nestjs/throttler
424
+ * suffixes its headers with the throttler name unless that name is literally
425
+ * "default". So a 429 carries `Retry-After-identity` or `Retry-After-ip`, not a
426
+ * bare `Retry-After`. Older servers and intermediary proxies may still send the
427
+ * bare name, so all three are read; when more than one is present the LONGER
428
+ * wait wins, since retrying before the slower bucket drains just earns another
429
+ * 429.
430
+ *
431
+ * Returns undefined when no variant is readable — in a browser that also
432
+ * happens when the server omits these from Access-Control-Expose-Headers, in
433
+ * which case callers must fall back to their own backoff.
434
+ */
435
+ declare function readRetryAfterMs(headers: Headers): number | undefined;
436
+ /**
437
+ * Thrown when the transit handshake is rate-limited (HTTP 429).
438
+ *
439
+ * Distinct from a generic failure because the correct response differs: a 429
440
+ * means "wait", not "this is broken", so callers must not burn their retry
441
+ * budget on it and should honour `retryAfterMs` when the server supplied it.
442
+ */
443
+ declare class TransitRateLimitedError extends Error {
444
+ readonly retryAfterMs?: number;
445
+ constructor(retryAfterMs?: number);
446
+ }
398
447
  interface ServerPublicKeys {
399
448
  x25519: string;
400
449
  p256: string;
401
450
  enabled: boolean;
402
451
  }
403
- declare function fetchServerKeys(apiUrl: string): Promise<ServerPublicKeys>;
452
+ declare function fetchServerKeys(apiUrl: string, identity?: TransitIdentity): Promise<ServerPublicKeys>;
404
453
  declare function generateEphemeralKey(algo: TransitAlgo, serverKeys: ServerPublicKeys): Promise<{
405
454
  ephemeralPubB64: string;
406
455
  deriveSessionKey: (sessionId: string) => Promise<unknown>;
407
456
  }>;
408
- declare function createRestTransitSession(apiUrl: string): Promise<{
457
+ declare function createRestTransitSession(apiUrl: string, identity?: TransitIdentity): Promise<{
409
458
  sessionId: string;
410
459
  sessionKey: CryptoKey | Uint8Array;
411
460
  } | null>;
@@ -799,4 +848,4 @@ declare class AntzChatClient {
799
848
  uploadIcon(conversationId: string, file: UploadableFile): Promise<Conversation>;
800
849
  }
801
850
 
802
- export { AntzChatAuthError, AntzChatClient, AntzChatConfig, AntzChatError, AntzChatNetworkError, AntzChatPermissionError, AntzChatServerError, AntzChatValidationError, AppConfig, AuthResponse, AuthTokens, BatchUploadResult, CompletedPart, Conversation, ConversationListParams, ConversationSyncResponse, ConversationUnreadCount, type CreateDirectData, type CreateGroupData, CrossConversationSyncResponse, CursorPaginatedResponse, FileResponse, FileType, ForwardAckPayload, ForwardMessagePayload, type ForwardResult, HIGHLY_FORWARDED_DEPTH_THRESHOLD, type LastReadEntry, type ListMessagesParams, LoginCredentials, MAX_FORWARD_TARGETS, type MentionPart, type MentionSegment, Message, MessageReactionsResponse, MessageReceiptsResponse, type MobileDeviceToken, PaginatedResponse, type ParsedMention, Participant, PersistStorage, PresignedUrlRequest, PresignedUrlResponse, RegisterData, type RegisterDeviceTokenPayload, ResolvedCompressionConfig, ResolvedConfig, type SearchParams, type SendData, SendMessagePayload, type SocketStatus, type StatusListener, type TokenStore, UnreadSummary, type UpdateConversationData, type UpdateProfilePayload, UploadableFile, User, UserPreferences, type WebPushDeviceToken, appConfigApi, authApi, buildMentionText, connectSocket, conversationsApi, createAuthStore, createRestTransitSession, decryptPayload, devicesApi, disconnectSocket, encryptPayload, extractMentionIds, fetchServerKeys, generateEphemeralKey, getApiClient, getAuthStore, getCompressionStrategy, getSessionId, getSessionKey, getSocket, getSocketStatus, initApiClient, initAuthStore, isApiClientConfigured, isMentionAll, isTransitEnvelope, messagesApi, normalizeAxiosError, normalizeConversation, onSocketStatus, parseMentions, performHandshake, reconnectSocket, refreshSocketAuth, renderMentionParts, resetAuthStore, resetTrackedRooms, resolveSystemMessageText, setApiClientInstance, setAuthReadyPromise, setTransitSession, socketEmit, syncApi, tryGetSocket, useChatStore, usersApi };
851
+ export { AntzChatAuthError, AntzChatClient, AntzChatConfig, AntzChatError, AntzChatNetworkError, AntzChatPermissionError, AntzChatServerError, AntzChatValidationError, AppConfig, AuthResponse, AuthTokens, BatchUploadResult, CompletedPart, Conversation, ConversationListParams, ConversationSyncResponse, ConversationUnreadCount, type CreateDirectData, type CreateGroupData, CrossConversationSyncResponse, CursorPaginatedResponse, FileResponse, FileType, ForwardAckPayload, ForwardMessagePayload, type ForwardResult, HIGHLY_FORWARDED_DEPTH_THRESHOLD, type LastReadEntry, type ListMessagesParams, LoginCredentials, MAX_FORWARD_TARGETS, type MentionPart, type MentionSegment, Message, MessageReactionsResponse, MessageReceiptsResponse, type MobileDeviceToken, PaginatedResponse, type ParsedMention, Participant, PersistStorage, PresignedUrlRequest, PresignedUrlResponse, RegisterData, type RegisterDeviceTokenPayload, ResolvedCompressionConfig, ResolvedConfig, type SearchParams, type SendData, SendMessagePayload, type SocketStatus, type StatusListener, type TokenStore, type TransitIdentity, TransitRateLimitedError, UnreadSummary, type UpdateConversationData, type UpdateProfilePayload, UploadableFile, User, UserPreferences, type WebPushDeviceToken, appConfigApi, authApi, buildMentionText, connectSocket, conversationsApi, createAuthStore, createRestTransitSession, decryptPayload, devicesApi, disconnectSocket, encryptPayload, extractMentionIds, fetchServerKeys, generateEphemeralKey, getApiClient, getAuthStore, getCompressionStrategy, getSessionId, getSessionKey, getSocket, getSocketStatus, initApiClient, initAuthStore, isApiClientConfigured, isMentionAll, isTransitEnvelope, messagesApi, normalizeAxiosError, normalizeConversation, onSocketStatus, parseMentions, performHandshake, readRetryAfterMs, reconnectSocket, refreshSocketAuth, renderMentionParts, resetAuthStore, resetTrackedRooms, resolveSystemMessageText, setApiClientInstance, setAuthReadyPromise, setTransitSession, socketEmit, syncApi, tryGetSocket, useChatStore, usersApi };
package/dist/index.d.ts CHANGED
@@ -395,17 +395,66 @@ declare function encryptPayload(data: unknown, sessionKey: AnySessionKey): Promi
395
395
  declare function decryptPayload(envelope: TransitEnvelope, sessionKey: AnySessionKey): Promise<unknown>;
396
396
  declare function isTransitEnvelope(v: unknown): v is TransitEnvelope;
397
397
 
398
+ /**
399
+ * Caller identity attached to the pre-auth transit handshake requests.
400
+ *
401
+ * GET /crypto/pubkey and POST /crypto/session run BEFORE a transit session (and
402
+ * therefore before the authenticated axios client) exists, so they bypass the
403
+ * request interceptor that normally adds these headers. Without them the server
404
+ * can only rate-limit these two routes by client IP — which means every user
405
+ * behind one NAT, office proxy or ALB shares a single bucket, and one client's
406
+ * reload loop 429s everyone else.
407
+ *
408
+ * These values are NOT used for authentication: the endpoints are unauthenticated
409
+ * by design and the server treats the headers as a fairness hint only, with a
410
+ * per-IP ceiling underneath as the real abuse limit. Sending them is therefore
411
+ * safe, optional, and backward compatible — an older SDK that omits them simply
412
+ * falls back to the shared per-IP bucket.
413
+ */
414
+ interface TransitIdentity {
415
+ /** External user id — same value sent as x-user-id on authenticated requests. */
416
+ userId?: string;
417
+ /** Tenant id — same value sent as X-Tenant-ID on authenticated requests. */
418
+ tenantId?: string;
419
+ }
420
+ /**
421
+ * How long the server asked us to wait, in ms, from a 429's Retry-After.
422
+ *
423
+ * The chat server runs TWO named rate-limit layers, and @nestjs/throttler
424
+ * suffixes its headers with the throttler name unless that name is literally
425
+ * "default". So a 429 carries `Retry-After-identity` or `Retry-After-ip`, not a
426
+ * bare `Retry-After`. Older servers and intermediary proxies may still send the
427
+ * bare name, so all three are read; when more than one is present the LONGER
428
+ * wait wins, since retrying before the slower bucket drains just earns another
429
+ * 429.
430
+ *
431
+ * Returns undefined when no variant is readable — in a browser that also
432
+ * happens when the server omits these from Access-Control-Expose-Headers, in
433
+ * which case callers must fall back to their own backoff.
434
+ */
435
+ declare function readRetryAfterMs(headers: Headers): number | undefined;
436
+ /**
437
+ * Thrown when the transit handshake is rate-limited (HTTP 429).
438
+ *
439
+ * Distinct from a generic failure because the correct response differs: a 429
440
+ * means "wait", not "this is broken", so callers must not burn their retry
441
+ * budget on it and should honour `retryAfterMs` when the server supplied it.
442
+ */
443
+ declare class TransitRateLimitedError extends Error {
444
+ readonly retryAfterMs?: number;
445
+ constructor(retryAfterMs?: number);
446
+ }
398
447
  interface ServerPublicKeys {
399
448
  x25519: string;
400
449
  p256: string;
401
450
  enabled: boolean;
402
451
  }
403
- declare function fetchServerKeys(apiUrl: string): Promise<ServerPublicKeys>;
452
+ declare function fetchServerKeys(apiUrl: string, identity?: TransitIdentity): Promise<ServerPublicKeys>;
404
453
  declare function generateEphemeralKey(algo: TransitAlgo, serverKeys: ServerPublicKeys): Promise<{
405
454
  ephemeralPubB64: string;
406
455
  deriveSessionKey: (sessionId: string) => Promise<unknown>;
407
456
  }>;
408
- declare function createRestTransitSession(apiUrl: string): Promise<{
457
+ declare function createRestTransitSession(apiUrl: string, identity?: TransitIdentity): Promise<{
409
458
  sessionId: string;
410
459
  sessionKey: CryptoKey | Uint8Array;
411
460
  } | null>;
@@ -799,4 +848,4 @@ declare class AntzChatClient {
799
848
  uploadIcon(conversationId: string, file: UploadableFile): Promise<Conversation>;
800
849
  }
801
850
 
802
- export { AntzChatAuthError, AntzChatClient, AntzChatConfig, AntzChatError, AntzChatNetworkError, AntzChatPermissionError, AntzChatServerError, AntzChatValidationError, AppConfig, AuthResponse, AuthTokens, BatchUploadResult, CompletedPart, Conversation, ConversationListParams, ConversationSyncResponse, ConversationUnreadCount, type CreateDirectData, type CreateGroupData, CrossConversationSyncResponse, CursorPaginatedResponse, FileResponse, FileType, ForwardAckPayload, ForwardMessagePayload, type ForwardResult, HIGHLY_FORWARDED_DEPTH_THRESHOLD, type LastReadEntry, type ListMessagesParams, LoginCredentials, MAX_FORWARD_TARGETS, type MentionPart, type MentionSegment, Message, MessageReactionsResponse, MessageReceiptsResponse, type MobileDeviceToken, PaginatedResponse, type ParsedMention, Participant, PersistStorage, PresignedUrlRequest, PresignedUrlResponse, RegisterData, type RegisterDeviceTokenPayload, ResolvedCompressionConfig, ResolvedConfig, type SearchParams, type SendData, SendMessagePayload, type SocketStatus, type StatusListener, type TokenStore, UnreadSummary, type UpdateConversationData, type UpdateProfilePayload, UploadableFile, User, UserPreferences, type WebPushDeviceToken, appConfigApi, authApi, buildMentionText, connectSocket, conversationsApi, createAuthStore, createRestTransitSession, decryptPayload, devicesApi, disconnectSocket, encryptPayload, extractMentionIds, fetchServerKeys, generateEphemeralKey, getApiClient, getAuthStore, getCompressionStrategy, getSessionId, getSessionKey, getSocket, getSocketStatus, initApiClient, initAuthStore, isApiClientConfigured, isMentionAll, isTransitEnvelope, messagesApi, normalizeAxiosError, normalizeConversation, onSocketStatus, parseMentions, performHandshake, reconnectSocket, refreshSocketAuth, renderMentionParts, resetAuthStore, resetTrackedRooms, resolveSystemMessageText, setApiClientInstance, setAuthReadyPromise, setTransitSession, socketEmit, syncApi, tryGetSocket, useChatStore, usersApi };
851
+ export { AntzChatAuthError, AntzChatClient, AntzChatConfig, AntzChatError, AntzChatNetworkError, AntzChatPermissionError, AntzChatServerError, AntzChatValidationError, AppConfig, AuthResponse, AuthTokens, BatchUploadResult, CompletedPart, Conversation, ConversationListParams, ConversationSyncResponse, ConversationUnreadCount, type CreateDirectData, type CreateGroupData, CrossConversationSyncResponse, CursorPaginatedResponse, FileResponse, FileType, ForwardAckPayload, ForwardMessagePayload, type ForwardResult, HIGHLY_FORWARDED_DEPTH_THRESHOLD, type LastReadEntry, type ListMessagesParams, LoginCredentials, MAX_FORWARD_TARGETS, type MentionPart, type MentionSegment, Message, MessageReactionsResponse, MessageReceiptsResponse, type MobileDeviceToken, PaginatedResponse, type ParsedMention, Participant, PersistStorage, PresignedUrlRequest, PresignedUrlResponse, RegisterData, type RegisterDeviceTokenPayload, ResolvedCompressionConfig, ResolvedConfig, type SearchParams, type SendData, SendMessagePayload, type SocketStatus, type StatusListener, type TokenStore, type TransitIdentity, TransitRateLimitedError, UnreadSummary, type UpdateConversationData, type UpdateProfilePayload, UploadableFile, User, UserPreferences, type WebPushDeviceToken, appConfigApi, authApi, buildMentionText, connectSocket, conversationsApi, createAuthStore, createRestTransitSession, decryptPayload, devicesApi, disconnectSocket, encryptPayload, extractMentionIds, fetchServerKeys, generateEphemeralKey, getApiClient, getAuthStore, getCompressionStrategy, getSessionId, getSessionKey, getSocket, getSocketStatus, initApiClient, initAuthStore, isApiClientConfigured, isMentionAll, isTransitEnvelope, messagesApi, normalizeAxiosError, normalizeConversation, onSocketStatus, parseMentions, performHandshake, readRetryAfterMs, reconnectSocket, refreshSocketAuth, renderMentionParts, resetAuthStore, resetTrackedRooms, resolveSystemMessageText, setApiClientInstance, setAuthReadyPromise, setTransitSession, socketEmit, syncApi, tryGetSocket, useChatStore, usersApi };
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  AntzChatPermissionError,
6
6
  AntzChatServerError,
7
7
  AntzChatValidationError,
8
+ TransitRateLimitedError,
8
9
  awaitTransitReadyOr,
9
10
  clearTransitSession,
10
11
  configureTransitIfUnset,
@@ -30,13 +31,14 @@ import {
30
31
  normalizeAxiosError,
31
32
  onTransitReady,
32
33
  performHandshake,
34
+ readRetryAfterMs,
33
35
  resetAlgoCache,
34
36
  setApiClientInstance,
35
37
  setAuthReadyPromise,
36
38
  setTransitSession,
37
39
  storageApi,
38
40
  uploadBatch
39
- } from "./chunk-WUNH3UTE.js";
41
+ } from "./chunk-U637W5MD.js";
40
42
  import {
41
43
  useChatStore
42
44
  } from "./chunk-UIYJAOGL.js";
@@ -456,7 +458,7 @@ async function _doConnect(config, getToken) {
456
458
  if (existingSession?.enabled && existingSession.sessionId) {
457
459
  httpsSession = { sessionId: existingSession.sessionId, sessionKey: existingSession.sessionKey };
458
460
  } else {
459
- httpsSession = await createRestTransitSession(config.apiUrl);
461
+ httpsSession = await createRestTransitSession(config.apiUrl, { userId: config.userId, tenantId: config.tenantId });
460
462
  if (httpsSession) {
461
463
  const algo = globalThis.crypto?.subtle ? await detectTransitAlgo() : "x25519";
462
464
  setTransitSession({ sessionKey: httpsSession.sessionKey, algo, sessionId: httpsSession.sessionId, enabled: true });
@@ -467,7 +469,7 @@ async function _doConnect(config, getToken) {
467
469
  boundDeriveSessionKey = null;
468
470
  } else {
469
471
  try {
470
- const serverKeys = await fetchServerKeys(config.apiUrl);
472
+ const serverKeys = await fetchServerKeys(config.apiUrl, { userId: config.userId, tenantId: config.tenantId });
471
473
  if (!serverKeys.enabled) {
472
474
  throw new AntzChatError(
473
475
  "TRANSIT_MISMATCH",
@@ -484,7 +486,7 @@ async function _doConnect(config, getToken) {
484
486
  }
485
487
  } else {
486
488
  try {
487
- const serverKeys = await fetchServerKeys(config.apiUrl);
489
+ const serverKeys = await fetchServerKeys(config.apiUrl, { userId: config.userId, tenantId: config.tenantId });
488
490
  if (serverKeys.enabled) {
489
491
  throw new AntzChatError(
490
492
  "TRANSIT_MISMATCH",
@@ -1331,6 +1333,7 @@ export {
1331
1333
  HIGHLY_FORWARDED_DEPTH_THRESHOLD,
1332
1334
  MAX_FORWARD_TARGETS,
1333
1335
  MENTION_ALL_ID,
1336
+ TransitRateLimitedError,
1334
1337
  appConfigApi,
1335
1338
  authApi,
1336
1339
  buildMentionText,
@@ -1363,6 +1366,7 @@ export {
1363
1366
  onSocketStatus,
1364
1367
  parseMentions,
1365
1368
  performHandshake,
1369
+ readRetryAfterMs,
1366
1370
  reconnectSocket,
1367
1371
  refreshSocketAuth,
1368
1372
  renderMentionParts,