@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/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as ResolvedCompressionConfig, L as LoginCredentials, A as AuthResponse, a as RegisterData, b as AuthTokens, U as User, C as CrossConversationSyncResponse, c as ConversationSyncResponse, d as AppConfig, S as SendMessagePayload, M as Message, e as CursorPaginatedResponse, f as MessageReactionsResponse, P as PaginatedResponse, g as MessageReceiptsResponse, h as ConversationListParams, i as Conversation, j as Participant, k as ConversationUnreadCount, l as UnreadSummary, m as UserPreferences, n as ResolvedConfig, F as ForwardMessagePayload, o as ForwardAckPayload, p as PersistStorage, q as PresignedUrlRequest, r as PresignedUrlResponse, s as FileResponse, t as CompletedPart, u as FileType, v as AntzChatConfig, w as UploadableFile, B as BatchUploadResult } from './storage-C8V7aVum.cjs';
2
- export { x as Attachment, y as CompressedFile, z as CompressionAlgorithm, D as CompressionConfig, E as ConversationType, G as ConversationUpdatedEvent, H as FileSizeLimits, I as ForwardResultPayload, J as LastReaction, K as MENTION_ALL_ID, N as MessageAckEvent, O as MessageContent, Q as MessageDeletedEvent, T as MessageDeletedForMeEvent, V as MessageDeliveredEvent, W as MessageForwardReference, X as MessageMetadata, Y as MessageReaction, Z as MessageReceiptEntry, _ as MessageReplyReference, $ as MessageStarUpdatedEvent, a0 as MessageUpdatedEvent, a1 as MessagesDeliveredEvent, a2 as MultipartPartUrl, a3 as MultipartUploadInfo, a4 as NewMessageEvent, a5 as OptimisticAttachment, a6 as PlatformCompressFn, a7 as PlatformUploadFn, a8 as PlatformUploadPartFn, a9 as QuietHours, aa as ReactionGroup, ab as ReactionUpdatedEvent, ac as ReactionUser, ad as ReadReceiptEvent, ae as ReplyAttachmentSnapshot, af as ResolvedFileSizeLimits, ag as SendMessageAttachment, ah as SyncDeletedForMe, ai as SyncDeliveredReceipt, aj as SyncParticipantChange, ak as SyncReactionEntry, al as SyncReactions, am as SyncReadReceipt, an as SyncStarEntry, ao as SystemMessageMetadata, ap as TypingIndicatorEvent, aq as UploadConfig, ar as UploadProgress, as as UserStatusEvent, at as resolveConfig, au as storageApi, av as uploadBatch } from './storage-C8V7aVum.cjs';
1
+ import { R as ResolvedCompressionConfig, L as LoginCredentials, A as AuthResponse, a as RegisterData, b as AuthTokens, U as User, C as CrossConversationSyncResponse, c as ConversationSyncResponse, d as AppConfig, S as SendMessagePayload, M as Message, e as CursorPaginatedResponse, f as MessageReactionsResponse, P as PaginatedResponse, g as MessageReceiptsResponse, h as ConversationListParams, i as Conversation, j as Participant, k as ConversationUnreadCount, l as UnreadSummary, m as UserPreferences, n as ResolvedConfig, F as ForwardMessagePayload, o as ForwardAckPayload, p as PersistStorage, q as PresignedUrlRequest, r as PresignedUrlResponse, s as FileResponse, t as CompletedPart, u as FileType, v as AntzChatConfig, w as UploadableFile, B as BatchUploadResult } from './storage-DlxfLVqx.cjs';
2
+ export { x as Attachment, y as CompressedFile, z as CompressionAlgorithm, D as CompressionConfig, E as ConversationType, G as ConversationUpdatedEvent, H as FileSizeLimits, I as ForwardResultPayload, J as LastReaction, K as MENTION_ALL_ID, N as MessageAckEvent, O as MessageContent, Q as MessageDeletedEvent, T as MessageDeletedForMeEvent, V as MessageDeliveredEvent, W as MessageForwardReference, X as MessageMetadata, Y as MessageReaction, Z as MessageReceiptEntry, _ as MessageReplyReference, $ as MessageStarUpdatedEvent, a0 as MessageUpdatedEvent, a1 as MessagesDeliveredEvent, a2 as MultipartPartUrl, a3 as MultipartUploadInfo, a4 as NewMessageEvent, a5 as OptimisticAttachment, a6 as PlatformCompressFn, a7 as PlatformUploadFn, a8 as PlatformUploadPartFn, a9 as QuietHours, aa as ReactionGroup, ab as ReactionUpdatedEvent, ac as ReactionUser, ad as ReadReceiptEvent, ae as ReplyAttachmentSnapshot, af as ResolvedFileSizeLimits, ag as SendErrorContext, ah as SendMessageAttachment, ai as SyncDeletedForMe, aj as SyncDeliveredReceipt, ak as SyncParticipantChange, al as SyncReactionEntry, am as SyncReactions, an as SyncReadReceipt, ao as SyncStarEntry, ap as SystemMessageMetadata, aq as TypingIndicatorEvent, ar as UploadConfig, as as UploadProgress, at as UserStatusEvent, au as resolveConfig, av as storageApi, aw as uploadBatch } from './storage-DlxfLVqx.cjs';
3
3
  import { AxiosInstance } from 'axios';
4
4
  import { Socket } from 'socket.io-client';
5
5
  import * as zustand_middleware from 'zustand/middleware';
@@ -325,6 +325,8 @@ type TokenStore = {
325
325
  setTokens: (tokens: AuthTokens) => void;
326
326
  clearTokens: () => void;
327
327
  };
328
+ declare function setAuthReadyPromise(promise: Promise<unknown> | null): void;
329
+ declare function isApiClientConfigured(): boolean;
328
330
  declare function initApiClient(config: ResolvedConfig, tokenStore: TokenStore): AxiosInstance;
329
331
  declare function setApiClientInstance(instance: AxiosInstance): void;
330
332
  declare function getApiClient(): AxiosInstance;
@@ -337,6 +339,10 @@ declare function getSocket(): Socket;
337
339
  declare function tryGetSocket(): Socket | null;
338
340
  declare function getSocketStatus(): SocketStatus;
339
341
  declare function onSocketStatus(listener: StatusListener): () => void;
342
+ /** Drop all tracked rooms. Call on a genuine identity change (user/tenant
343
+ * switch) so the reconnect re-flush can't re-join the previous user's rooms.
344
+ * NOT called from disconnectSocket() — a token refresh is not an intent change. */
345
+ declare function resetTrackedRooms(): void;
340
346
  declare function connectSocket(config: ResolvedConfig, getToken: () => string | null | undefined): Promise<Socket>;
341
347
  declare function disconnectSocket(): void;
342
348
  declare function reconnectSocket(token: string, userId?: string, tenantId?: string): void;
@@ -389,17 +395,66 @@ declare function encryptPayload(data: unknown, sessionKey: AnySessionKey): Promi
389
395
  declare function decryptPayload(envelope: TransitEnvelope, sessionKey: AnySessionKey): Promise<unknown>;
390
396
  declare function isTransitEnvelope(v: unknown): v is TransitEnvelope;
391
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
+ }
392
447
  interface ServerPublicKeys {
393
448
  x25519: string;
394
449
  p256: string;
395
450
  enabled: boolean;
396
451
  }
397
- declare function fetchServerKeys(apiUrl: string): Promise<ServerPublicKeys>;
452
+ declare function fetchServerKeys(apiUrl: string, identity?: TransitIdentity): Promise<ServerPublicKeys>;
398
453
  declare function generateEphemeralKey(algo: TransitAlgo, serverKeys: ServerPublicKeys): Promise<{
399
454
  ephemeralPubB64: string;
400
455
  deriveSessionKey: (sessionId: string) => Promise<unknown>;
401
456
  }>;
402
- declare function createRestTransitSession(apiUrl: string): Promise<{
457
+ declare function createRestTransitSession(apiUrl: string, identity?: TransitIdentity): Promise<{
403
458
  sessionId: string;
404
459
  sessionKey: CryptoKey | Uint8Array;
405
460
  } | null>;
@@ -793,4 +848,4 @@ declare class AntzChatClient {
793
848
  uploadIcon(conversationId: string, file: UploadableFile): Promise<Conversation>;
794
849
  }
795
850
 
796
- 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, isMentionAll, isTransitEnvelope, messagesApi, normalizeAxiosError, normalizeConversation, onSocketStatus, parseMentions, performHandshake, reconnectSocket, refreshSocketAuth, renderMentionParts, resetAuthStore, resolveSystemMessageText, setApiClientInstance, 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
@@ -1,5 +1,5 @@
1
- import { R as ResolvedCompressionConfig, L as LoginCredentials, A as AuthResponse, a as RegisterData, b as AuthTokens, U as User, C as CrossConversationSyncResponse, c as ConversationSyncResponse, d as AppConfig, S as SendMessagePayload, M as Message, e as CursorPaginatedResponse, f as MessageReactionsResponse, P as PaginatedResponse, g as MessageReceiptsResponse, h as ConversationListParams, i as Conversation, j as Participant, k as ConversationUnreadCount, l as UnreadSummary, m as UserPreferences, n as ResolvedConfig, F as ForwardMessagePayload, o as ForwardAckPayload, p as PersistStorage, q as PresignedUrlRequest, r as PresignedUrlResponse, s as FileResponse, t as CompletedPart, u as FileType, v as AntzChatConfig, w as UploadableFile, B as BatchUploadResult } from './storage-C8V7aVum.js';
2
- export { x as Attachment, y as CompressedFile, z as CompressionAlgorithm, D as CompressionConfig, E as ConversationType, G as ConversationUpdatedEvent, H as FileSizeLimits, I as ForwardResultPayload, J as LastReaction, K as MENTION_ALL_ID, N as MessageAckEvent, O as MessageContent, Q as MessageDeletedEvent, T as MessageDeletedForMeEvent, V as MessageDeliveredEvent, W as MessageForwardReference, X as MessageMetadata, Y as MessageReaction, Z as MessageReceiptEntry, _ as MessageReplyReference, $ as MessageStarUpdatedEvent, a0 as MessageUpdatedEvent, a1 as MessagesDeliveredEvent, a2 as MultipartPartUrl, a3 as MultipartUploadInfo, a4 as NewMessageEvent, a5 as OptimisticAttachment, a6 as PlatformCompressFn, a7 as PlatformUploadFn, a8 as PlatformUploadPartFn, a9 as QuietHours, aa as ReactionGroup, ab as ReactionUpdatedEvent, ac as ReactionUser, ad as ReadReceiptEvent, ae as ReplyAttachmentSnapshot, af as ResolvedFileSizeLimits, ag as SendMessageAttachment, ah as SyncDeletedForMe, ai as SyncDeliveredReceipt, aj as SyncParticipantChange, ak as SyncReactionEntry, al as SyncReactions, am as SyncReadReceipt, an as SyncStarEntry, ao as SystemMessageMetadata, ap as TypingIndicatorEvent, aq as UploadConfig, ar as UploadProgress, as as UserStatusEvent, at as resolveConfig, au as storageApi, av as uploadBatch } from './storage-C8V7aVum.js';
1
+ import { R as ResolvedCompressionConfig, L as LoginCredentials, A as AuthResponse, a as RegisterData, b as AuthTokens, U as User, C as CrossConversationSyncResponse, c as ConversationSyncResponse, d as AppConfig, S as SendMessagePayload, M as Message, e as CursorPaginatedResponse, f as MessageReactionsResponse, P as PaginatedResponse, g as MessageReceiptsResponse, h as ConversationListParams, i as Conversation, j as Participant, k as ConversationUnreadCount, l as UnreadSummary, m as UserPreferences, n as ResolvedConfig, F as ForwardMessagePayload, o as ForwardAckPayload, p as PersistStorage, q as PresignedUrlRequest, r as PresignedUrlResponse, s as FileResponse, t as CompletedPart, u as FileType, v as AntzChatConfig, w as UploadableFile, B as BatchUploadResult } from './storage-DlxfLVqx.js';
2
+ export { x as Attachment, y as CompressedFile, z as CompressionAlgorithm, D as CompressionConfig, E as ConversationType, G as ConversationUpdatedEvent, H as FileSizeLimits, I as ForwardResultPayload, J as LastReaction, K as MENTION_ALL_ID, N as MessageAckEvent, O as MessageContent, Q as MessageDeletedEvent, T as MessageDeletedForMeEvent, V as MessageDeliveredEvent, W as MessageForwardReference, X as MessageMetadata, Y as MessageReaction, Z as MessageReceiptEntry, _ as MessageReplyReference, $ as MessageStarUpdatedEvent, a0 as MessageUpdatedEvent, a1 as MessagesDeliveredEvent, a2 as MultipartPartUrl, a3 as MultipartUploadInfo, a4 as NewMessageEvent, a5 as OptimisticAttachment, a6 as PlatformCompressFn, a7 as PlatformUploadFn, a8 as PlatformUploadPartFn, a9 as QuietHours, aa as ReactionGroup, ab as ReactionUpdatedEvent, ac as ReactionUser, ad as ReadReceiptEvent, ae as ReplyAttachmentSnapshot, af as ResolvedFileSizeLimits, ag as SendErrorContext, ah as SendMessageAttachment, ai as SyncDeletedForMe, aj as SyncDeliveredReceipt, ak as SyncParticipantChange, al as SyncReactionEntry, am as SyncReactions, an as SyncReadReceipt, ao as SyncStarEntry, ap as SystemMessageMetadata, aq as TypingIndicatorEvent, ar as UploadConfig, as as UploadProgress, at as UserStatusEvent, au as resolveConfig, av as storageApi, aw as uploadBatch } from './storage-DlxfLVqx.js';
3
3
  import { AxiosInstance } from 'axios';
4
4
  import { Socket } from 'socket.io-client';
5
5
  import * as zustand_middleware from 'zustand/middleware';
@@ -325,6 +325,8 @@ type TokenStore = {
325
325
  setTokens: (tokens: AuthTokens) => void;
326
326
  clearTokens: () => void;
327
327
  };
328
+ declare function setAuthReadyPromise(promise: Promise<unknown> | null): void;
329
+ declare function isApiClientConfigured(): boolean;
328
330
  declare function initApiClient(config: ResolvedConfig, tokenStore: TokenStore): AxiosInstance;
329
331
  declare function setApiClientInstance(instance: AxiosInstance): void;
330
332
  declare function getApiClient(): AxiosInstance;
@@ -337,6 +339,10 @@ declare function getSocket(): Socket;
337
339
  declare function tryGetSocket(): Socket | null;
338
340
  declare function getSocketStatus(): SocketStatus;
339
341
  declare function onSocketStatus(listener: StatusListener): () => void;
342
+ /** Drop all tracked rooms. Call on a genuine identity change (user/tenant
343
+ * switch) so the reconnect re-flush can't re-join the previous user's rooms.
344
+ * NOT called from disconnectSocket() — a token refresh is not an intent change. */
345
+ declare function resetTrackedRooms(): void;
340
346
  declare function connectSocket(config: ResolvedConfig, getToken: () => string | null | undefined): Promise<Socket>;
341
347
  declare function disconnectSocket(): void;
342
348
  declare function reconnectSocket(token: string, userId?: string, tenantId?: string): void;
@@ -389,17 +395,66 @@ declare function encryptPayload(data: unknown, sessionKey: AnySessionKey): Promi
389
395
  declare function decryptPayload(envelope: TransitEnvelope, sessionKey: AnySessionKey): Promise<unknown>;
390
396
  declare function isTransitEnvelope(v: unknown): v is TransitEnvelope;
391
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
+ }
392
447
  interface ServerPublicKeys {
393
448
  x25519: string;
394
449
  p256: string;
395
450
  enabled: boolean;
396
451
  }
397
- declare function fetchServerKeys(apiUrl: string): Promise<ServerPublicKeys>;
452
+ declare function fetchServerKeys(apiUrl: string, identity?: TransitIdentity): Promise<ServerPublicKeys>;
398
453
  declare function generateEphemeralKey(algo: TransitAlgo, serverKeys: ServerPublicKeys): Promise<{
399
454
  ephemeralPubB64: string;
400
455
  deriveSessionKey: (sessionId: string) => Promise<unknown>;
401
456
  }>;
402
- declare function createRestTransitSession(apiUrl: string): Promise<{
457
+ declare function createRestTransitSession(apiUrl: string, identity?: TransitIdentity): Promise<{
403
458
  sessionId: string;
404
459
  sessionKey: CryptoKey | Uint8Array;
405
460
  } | null>;
@@ -793,4 +848,4 @@ declare class AntzChatClient {
793
848
  uploadIcon(conversationId: string, file: UploadableFile): Promise<Conversation>;
794
849
  }
795
850
 
796
- 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, isMentionAll, isTransitEnvelope, messagesApi, normalizeAxiosError, normalizeConversation, onSocketStatus, parseMentions, performHandshake, reconnectSocket, refreshSocketAuth, renderMentionParts, resetAuthStore, resolveSystemMessageText, setApiClientInstance, 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,12 +5,15 @@ import {
5
5
  AntzChatPermissionError,
6
6
  AntzChatServerError,
7
7
  AntzChatValidationError,
8
+ TransitRateLimitedError,
9
+ awaitTransitReadyOr,
8
10
  clearTransitSession,
9
- configureTransit,
11
+ configureTransitIfUnset,
10
12
  createRestTransitSession,
11
13
  decryptPayload,
12
14
  detectTransitAlgo,
13
15
  encryptPayload,
16
+ ensureRestTransitHandshake,
14
17
  fetchServerKeys,
15
18
  generateEphemeralKey,
16
19
  generateUUID,
@@ -21,16 +24,21 @@ import {
21
24
  getTransitHandshakePromise,
22
25
  getTransitSession,
23
26
  initApiClient,
27
+ isApiClientConfigured,
24
28
  isTransitEnabled,
25
29
  isTransitEnvelope,
30
+ isTransitRequired,
26
31
  normalizeAxiosError,
32
+ onTransitReady,
27
33
  performHandshake,
34
+ readRetryAfterMs,
28
35
  resetAlgoCache,
29
36
  setApiClientInstance,
37
+ setAuthReadyPromise,
30
38
  setTransitSession,
31
39
  storageApi,
32
40
  uploadBatch
33
- } from "./chunk-XTQYF5HU.js";
41
+ } from "./chunk-U637W5MD.js";
34
42
  import {
35
43
  useChatStore
36
44
  } from "./chunk-UIYJAOGL.js";
@@ -89,6 +97,7 @@ function resolveConfig(config) {
89
97
  compressDocuments: config.compression?.compressDocuments ?? true
90
98
  },
91
99
  persistStorage: config.persistStorage,
100
+ onSendError: config.onSendError,
92
101
  messagePageSize: config.messagePageSize ?? 40,
93
102
  starredMessagePageSize: config.starredMessagePageSize ?? 30,
94
103
  searchPageSize: config.searchPageSize ?? 50
@@ -311,7 +320,6 @@ var _getToken = null;
311
320
  var _userId;
312
321
  var _tenantId;
313
322
  var _config = null;
314
- var _preservingSession = false;
315
323
  function setStatus(s) {
316
324
  _status = s;
317
325
  _statusListeners.forEach((l) => l(s));
@@ -330,29 +338,48 @@ function onSocketStatus(listener) {
330
338
  _statusListeners.add(listener);
331
339
  return () => _statusListeners.delete(listener);
332
340
  }
333
- async function secureEmit(socket, event, payload, ack) {
334
- if (isTransitEnabled()) {
335
- const key = getSessionKey();
336
- if (key) {
337
- const envelope = await encryptPayload(payload, key);
338
- if (ack) {
339
- socket.emit(event, envelope, async (encryptedResponse) => {
340
- if (isTransitEnvelope(encryptedResponse)) {
341
- try {
342
- const decrypted = await decryptPayload(encryptedResponse, key);
343
- ack(decrypted);
344
- } catch {
345
- ack(encryptedResponse);
346
- }
347
- } else {
348
- ack(encryptedResponse);
349
- }
350
- });
341
+ var SECURE_EMIT_WAIT_MS = 4e3;
342
+ function emitEncrypted(socket, event, envelope, key, ack) {
343
+ if (ack) {
344
+ socket.emit(event, envelope, async (encryptedResponse) => {
345
+ if (isTransitEnvelope(encryptedResponse)) {
346
+ try {
347
+ ack(await decryptPayload(encryptedResponse, key));
348
+ } catch {
349
+ ack(encryptedResponse);
350
+ }
351
351
  } else {
352
- socket.emit(event, envelope);
352
+ ack(encryptedResponse);
353
353
  }
354
+ });
355
+ } else {
356
+ socket.emit(event, envelope);
357
+ }
358
+ }
359
+ async function secureEmit(socket, event, payload, ack) {
360
+ let key = getSessionKey();
361
+ if (key && isTransitEnabled()) {
362
+ emitEncrypted(socket, event, await encryptPayload(payload, key), key, ack);
363
+ return;
364
+ }
365
+ if (isTransitRequired()) {
366
+ try {
367
+ ensureRestTransitHandshake();
368
+ } catch {
369
+ }
370
+ const ok = await awaitTransitReadyOr(SECURE_EMIT_WAIT_MS);
371
+ key = getSessionKey();
372
+ if (ok && key && isTransitEnabled()) {
373
+ emitEncrypted(socket, event, await encryptPayload(payload, key), key, ack);
354
374
  return;
355
375
  }
376
+ if (isTransitRequired()) {
377
+ throw new AntzChatNetworkError(
378
+ `Transit encryption required but no session established \u2014 "${event}" not sent`,
379
+ "TRANSIT_NOT_READY",
380
+ { event }
381
+ );
382
+ }
356
383
  }
357
384
  if (ack) {
358
385
  socket.emit(event, payload, ack);
@@ -378,6 +405,28 @@ function secureOn(socket, event, handler) {
378
405
  handler(raw);
379
406
  });
380
407
  }
408
+ var _joinedRooms = /* @__PURE__ */ new Set();
409
+ function trackRoomJoin(conversationId) {
410
+ _joinedRooms.add(conversationId);
411
+ }
412
+ function trackRoomLeave(conversationId) {
413
+ _joinedRooms.delete(conversationId);
414
+ }
415
+ function resetTrackedRooms() {
416
+ _joinedRooms.clear();
417
+ }
418
+ function flushJoinedRooms() {
419
+ const socket = tryGetSocket();
420
+ if (!socket || _joinedRooms.size === 0) return;
421
+ for (const conversationId of _joinedRooms) {
422
+ void secureEmit(socket, "join_room", { conversationId }).catch(() => {
423
+ });
424
+ }
425
+ }
426
+ onTransitReady(flushJoinedRooms);
427
+ onSocketStatus((status) => {
428
+ if (status === "connected") flushJoinedRooms();
429
+ });
381
430
  async function connectSocket(config, getToken) {
382
431
  if (_socket && !_socket.disconnected) return _socket;
383
432
  if (_connectingPromise) return _connectingPromise;
@@ -399,6 +448,7 @@ async function _doConnect(config, getToken) {
399
448
  ...config.avatar?.url && { avatarUrl: config.avatar.url },
400
449
  ...config.avatar?.base64 && { avatarBase64: config.avatar.base64 }
401
450
  };
451
+ configureTransitIfUnset(config.transitEncryption);
402
452
  let boundDeriveSessionKey = null;
403
453
  if (config.transitEncryption) {
404
454
  const inFlight = getTransitHandshakePromise();
@@ -408,7 +458,7 @@ async function _doConnect(config, getToken) {
408
458
  if (existingSession?.enabled && existingSession.sessionId) {
409
459
  httpsSession = { sessionId: existingSession.sessionId, sessionKey: existingSession.sessionKey };
410
460
  } else {
411
- httpsSession = await createRestTransitSession(config.apiUrl);
461
+ httpsSession = await createRestTransitSession(config.apiUrl, { userId: config.userId, tenantId: config.tenantId });
412
462
  if (httpsSession) {
413
463
  const algo = globalThis.crypto?.subtle ? await detectTransitAlgo() : "x25519";
414
464
  setTransitSession({ sessionKey: httpsSession.sessionKey, algo, sessionId: httpsSession.sessionId, enabled: true });
@@ -419,7 +469,7 @@ async function _doConnect(config, getToken) {
419
469
  boundDeriveSessionKey = null;
420
470
  } else {
421
471
  try {
422
- const serverKeys = await fetchServerKeys(config.apiUrl);
472
+ const serverKeys = await fetchServerKeys(config.apiUrl, { userId: config.userId, tenantId: config.tenantId });
423
473
  if (!serverKeys.enabled) {
424
474
  throw new AntzChatError(
425
475
  "TRANSIT_MISMATCH",
@@ -436,7 +486,7 @@ async function _doConnect(config, getToken) {
436
486
  }
437
487
  } else {
438
488
  try {
439
- const serverKeys = await fetchServerKeys(config.apiUrl);
489
+ const serverKeys = await fetchServerKeys(config.apiUrl, { userId: config.userId, tenantId: config.tenantId });
440
490
  if (serverKeys.enabled) {
441
491
  throw new AntzChatError(
442
492
  "TRANSIT_MISMATCH",
@@ -463,7 +513,6 @@ async function _doConnect(config, getToken) {
463
513
  _socket.on("connect", () => setStatus("connected"));
464
514
  _socket.on("disconnect", () => {
465
515
  setStatus("disconnected");
466
- if (!_preservingSession) clearTransitSession();
467
516
  });
468
517
  _socket.on("connect_error", (err) => {
469
518
  console.error("[AntzChat] Socket connect_error:", err?.message, err?.data);
@@ -478,8 +527,9 @@ async function _doConnect(config, getToken) {
478
527
  resolve();
479
528
  };
480
529
  const timeout = setTimeout(() => {
481
- console.warn("[AntzChat] transit_session timeout \u2014 unblocking HTTP without transit encryption");
482
- configureTransit(false);
530
+ console.error(
531
+ "[AntzChat] transit_session did not arrive within 5s. Transit stays REQUIRED \u2014 chat requests are gated (not downgraded to plaintext) until the handshake succeeds via REST retry or socket reconnect. Persistent failure = check POST /crypto/session (rate limit / 5xx) and the socket transit middleware."
532
+ );
483
533
  done();
484
534
  }, 5e3);
485
535
  _socket.on("transit_session", async ({ sessionId }) => {
@@ -566,13 +616,8 @@ function reconnectSocket(token, userId, tenantId) {
566
616
  ...tenantId && { tenantId },
567
617
  transitSessionId: existing.sessionId
568
618
  };
569
- _preservingSession = true;
570
- try {
571
- if (_socket.connected) _socket.disconnect();
572
- _socket.connect();
573
- } finally {
574
- _preservingSession = false;
575
- }
619
+ if (_socket.connected) _socket.disconnect();
620
+ _socket.connect();
576
621
  return;
577
622
  }
578
623
  if (_getToken) {
@@ -617,7 +662,15 @@ async function drainSendQueue(conversationId) {
617
662
  entry.reject(new AntzChatNetworkError("Message dropped: queued too long", "MESSAGE_DROPPED"));
618
663
  continue;
619
664
  }
620
- entry.run().then(entry.resolve).catch(entry.reject);
665
+ let acked;
666
+ try {
667
+ ({ acked } = await emitWithAck("send_message", entry.payload));
668
+ } catch (err) {
669
+ entry.reject(err);
670
+ while (queue.length > 0) queue.shift().reject(err);
671
+ break;
672
+ }
673
+ acked.then(entry.resolve).catch(entry.reject);
621
674
  }
622
675
  sendQueues.delete(conversationId);
623
676
  sendQueueRunning.delete(conversationId);
@@ -630,7 +683,7 @@ function queueSendMessage(payload) {
630
683
  return Promise.reject(new AntzChatNetworkError("Send queue full: too many messages in flight", "SEND_QUEUE_FULL", { conversationId }));
631
684
  }
632
685
  return new Promise((resolve, reject) => {
633
- queue.push({ run: () => withAck("send_message", payload), resolve, reject, enqueuedAt: Date.now() });
686
+ queue.push({ payload, resolve, reject, enqueuedAt: Date.now() });
634
687
  drainSendQueue(conversationId);
635
688
  });
636
689
  }
@@ -653,33 +706,44 @@ function waitForReconnect() {
653
706
  });
654
707
  });
655
708
  }
656
- async function withAck(event, payload) {
709
+ async function emitWithAck(event, payload) {
657
710
  let socket = tryGetSocket();
658
711
  if (!socket) {
659
712
  await waitForReconnect();
660
713
  socket = tryGetSocket();
661
714
  }
662
- if (!socket) return Promise.reject(new AntzChatNetworkError(`Socket not connected (event: ${event})`, "SOCKET_NOT_CONNECTED", { event }));
663
- return new Promise((resolve, reject) => {
664
- let timer;
665
- secureEmit(socket, event, payload, (response) => {
666
- clearTimeout(timer);
667
- resolve(response);
668
- }).then(() => {
669
- timer = setTimeout(() => reject(new AntzChatNetworkError(`Socket ack timeout: ${event}`, "SOCKET_TIMEOUT", { event })), ACK_TIMEOUT);
670
- }).catch(reject);
715
+ if (!socket) throw new AntzChatNetworkError(`Socket not connected (event: ${event})`, "SOCKET_NOT_CONNECTED", { event });
716
+ let resolveAck;
717
+ let rejectAck;
718
+ const acked = new Promise((res, rej) => {
719
+ resolveAck = res;
720
+ rejectAck = rej;
671
721
  });
722
+ await secureEmit(socket, event, payload, (response) => resolveAck(response));
723
+ const timer = setTimeout(
724
+ () => rejectAck(new AntzChatNetworkError(`Socket ack timeout: ${event}`, "SOCKET_TIMEOUT", { event })),
725
+ ACK_TIMEOUT
726
+ );
727
+ void acked.then(() => clearTimeout(timer), () => clearTimeout(timer));
728
+ return { acked };
729
+ }
730
+ async function withAck(event, payload) {
731
+ const { acked } = await emitWithAck(event, payload);
732
+ return acked;
672
733
  }
673
734
  function fireAndForget(event, payload) {
674
735
  const socket = tryGetSocket();
675
736
  if (!socket) return;
676
- secureEmit(socket, event, payload);
737
+ secureEmit(socket, event, payload).catch(() => {
738
+ });
677
739
  }
678
740
  var socketEmit = {
679
741
  joinRoom(conversationId) {
742
+ trackRoomJoin(conversationId);
680
743
  fireAndForget("join_room", { conversationId });
681
744
  },
682
745
  leaveRoom(conversationId) {
746
+ trackRoomLeave(conversationId);
683
747
  fireAndForget("leave_room", { conversationId });
684
748
  },
685
749
  sendMessage(payload) {
@@ -1269,6 +1333,7 @@ export {
1269
1333
  HIGHLY_FORWARDED_DEPTH_THRESHOLD,
1270
1334
  MAX_FORWARD_TARGETS,
1271
1335
  MENTION_ALL_ID,
1336
+ TransitRateLimitedError,
1272
1337
  appConfigApi,
1273
1338
  authApi,
1274
1339
  buildMentionText,
@@ -1292,6 +1357,7 @@ export {
1292
1357
  getSocketStatus,
1293
1358
  initApiClient,
1294
1359
  initAuthStore,
1360
+ isApiClientConfigured,
1295
1361
  isMentionAll,
1296
1362
  isTransitEnvelope,
1297
1363
  messagesApi,
@@ -1300,13 +1366,16 @@ export {
1300
1366
  onSocketStatus,
1301
1367
  parseMentions,
1302
1368
  performHandshake,
1369
+ readRetryAfterMs,
1303
1370
  reconnectSocket,
1304
1371
  refreshSocketAuth,
1305
1372
  renderMentionParts,
1306
1373
  resetAuthStore,
1374
+ resetTrackedRooms,
1307
1375
  resolveConfig,
1308
1376
  resolveSystemMessageText,
1309
1377
  setApiClientInstance,
1378
+ setAuthReadyPromise,
1310
1379
  setTransitSession,
1311
1380
  socketEmit,
1312
1381
  storageApi,