@antzsoft/chat-core 1.4.6 → 1.4.7

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
@@ -339,6 +339,7 @@ declare function getSocket(): Socket;
339
339
  declare function tryGetSocket(): Socket | null;
340
340
  declare function getSocketStatus(): SocketStatus;
341
341
  declare function onSocketStatus(listener: StatusListener): () => void;
342
+ declare function registerTeardownHook(hook: () => void): () => void;
342
343
  /** Drop all tracked rooms. Call on a genuine identity change (user/tenant
343
344
  * switch) so the reconnect re-flush can't re-join the previous user's rooms.
344
345
  * NOT called from disconnectSocket() — a token refresh is not an intent change. */
@@ -353,6 +354,9 @@ declare function reconnectSocket(token: string, userId?: string, tenantId?: stri
353
354
  */
354
355
  declare function refreshSocketAuth(): boolean;
355
356
 
357
+ /** Clears the typing throttle state. Call on teardown/identity change so a new
358
+ * session cannot inherit the previous one's suppression window. */
359
+ declare function resetTypingThrottle(): void;
356
360
  declare const socketEmit: {
357
361
  joinRoom(conversationId: string): void;
358
362
  leaveRoom(conversationId: string): void;
@@ -584,6 +588,10 @@ interface ChatState {
584
588
  } | null) => void;
585
589
  addTypingUser: (conversationId: string, user: TypingUser) => void;
586
590
  removeTypingUser: (conversationId: string, userId: string) => void;
591
+ /** Drops every typing indicator and cancels their expiry timers. For teardown
592
+ * and identity change — a socket drop no longer needs it, since indicators
593
+ * expire on their own. */
594
+ clearTypingUsers: () => void;
587
595
  setUserOnline: (userId: string) => void;
588
596
  setUserOffline: (userId: string) => void;
589
597
  setOnlineUsers: (userIds: string[]) => void;
@@ -848,4 +856,4 @@ declare class AntzChatClient {
848
856
  uploadIcon(conversationId: string, file: UploadableFile): Promise<Conversation>;
849
857
  }
850
858
 
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 };
859
+ 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, registerTeardownHook, renderMentionParts, resetAuthStore, resetTrackedRooms, resetTypingThrottle, resolveSystemMessageText, setApiClientInstance, setAuthReadyPromise, setTransitSession, socketEmit, syncApi, tryGetSocket, useChatStore, usersApi };
package/dist/index.d.ts CHANGED
@@ -339,6 +339,7 @@ declare function getSocket(): Socket;
339
339
  declare function tryGetSocket(): Socket | null;
340
340
  declare function getSocketStatus(): SocketStatus;
341
341
  declare function onSocketStatus(listener: StatusListener): () => void;
342
+ declare function registerTeardownHook(hook: () => void): () => void;
342
343
  /** Drop all tracked rooms. Call on a genuine identity change (user/tenant
343
344
  * switch) so the reconnect re-flush can't re-join the previous user's rooms.
344
345
  * NOT called from disconnectSocket() — a token refresh is not an intent change. */
@@ -353,6 +354,9 @@ declare function reconnectSocket(token: string, userId?: string, tenantId?: stri
353
354
  */
354
355
  declare function refreshSocketAuth(): boolean;
355
356
 
357
+ /** Clears the typing throttle state. Call on teardown/identity change so a new
358
+ * session cannot inherit the previous one's suppression window. */
359
+ declare function resetTypingThrottle(): void;
356
360
  declare const socketEmit: {
357
361
  joinRoom(conversationId: string): void;
358
362
  leaveRoom(conversationId: string): void;
@@ -584,6 +588,10 @@ interface ChatState {
584
588
  } | null) => void;
585
589
  addTypingUser: (conversationId: string, user: TypingUser) => void;
586
590
  removeTypingUser: (conversationId: string, userId: string) => void;
591
+ /** Drops every typing indicator and cancels their expiry timers. For teardown
592
+ * and identity change — a socket drop no longer needs it, since indicators
593
+ * expire on their own. */
594
+ clearTypingUsers: () => void;
587
595
  setUserOnline: (userId: string) => void;
588
596
  setUserOffline: (userId: string) => void;
589
597
  setOnlineUsers: (userIds: string[]) => void;
@@ -848,4 +856,4 @@ declare class AntzChatClient {
848
856
  uploadIcon(conversationId: string, file: UploadableFile): Promise<Conversation>;
849
857
  }
850
858
 
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 };
859
+ 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, registerTeardownHook, renderMentionParts, resetAuthStore, resetTrackedRooms, resetTypingThrottle, resolveSystemMessageText, setApiClientInstance, setAuthReadyPromise, setTransitSession, socketEmit, syncApi, tryGetSocket, useChatStore, usersApi };
package/dist/index.js CHANGED
@@ -41,7 +41,7 @@ import {
41
41
  } from "./chunk-U637W5MD.js";
42
42
  import {
43
43
  useChatStore
44
- } from "./chunk-UIYJAOGL.js";
44
+ } from "./chunk-QHELYVNT.js";
45
45
 
46
46
  // src/config/types.ts
47
47
  function resolveConfig(config) {
@@ -405,6 +405,19 @@ function secureOn(socket, event, handler) {
405
405
  handler(raw);
406
406
  });
407
407
  }
408
+ var _teardownHooks = /* @__PURE__ */ new Set();
409
+ function registerTeardownHook(hook) {
410
+ _teardownHooks.add(hook);
411
+ return () => _teardownHooks.delete(hook);
412
+ }
413
+ function runTeardownHooks() {
414
+ _teardownHooks.forEach((hook) => {
415
+ try {
416
+ hook();
417
+ } catch {
418
+ }
419
+ });
420
+ }
408
421
  var _joinedRooms = /* @__PURE__ */ new Set();
409
422
  function trackRoomJoin(conversationId) {
410
423
  _joinedRooms.add(conversationId);
@@ -548,13 +561,13 @@ async function _doConnect(config, getToken) {
548
561
  });
549
562
  }
550
563
  secureOn(_socket, "read_receipt", (event) => {
551
- import("./chat.store-UVTDBPEC.js").then(({ useChatStore: useChatStore2 }) => {
564
+ import("./chat.store-TA6G7PD6.js").then(({ useChatStore: useChatStore2 }) => {
552
565
  const e = event;
553
566
  useChatStore2.getState().setLastRead(e.conversationId, e.messageId, e.readAt);
554
567
  });
555
568
  });
556
569
  secureOn(_socket, "user_online", (event) => {
557
- import("./chat.store-UVTDBPEC.js").then(({ useChatStore: useChatStore2 }) => {
570
+ import("./chat.store-TA6G7PD6.js").then(({ useChatStore: useChatStore2 }) => {
558
571
  const store = useChatStore2.getState();
559
572
  const e = event;
560
573
  store.setUserOnline(e.userId);
@@ -562,7 +575,7 @@ async function _doConnect(config, getToken) {
562
575
  });
563
576
  });
564
577
  secureOn(_socket, "user_offline", (event) => {
565
- import("./chat.store-UVTDBPEC.js").then(({ useChatStore: useChatStore2 }) => {
578
+ import("./chat.store-TA6G7PD6.js").then(({ useChatStore: useChatStore2 }) => {
566
579
  const e = event;
567
580
  const store = useChatStore2.getState();
568
581
  store.setUserOffline(e.userId);
@@ -600,6 +613,10 @@ function disconnectSocket() {
600
613
  }
601
614
  clearTransitSession();
602
615
  resetAlgoCache();
616
+ runTeardownHooks();
617
+ import("./chat.store-TA6G7PD6.js").then(({ useChatStore: useChatStore2 }) => {
618
+ useChatStore2.getState().clearTypingUsers();
619
+ });
603
620
  _getToken = null;
604
621
  _userId = void 0;
605
622
  _tenantId = void 0;
@@ -651,6 +668,12 @@ var RECONNECT_WAIT_TIMEOUT = 15e3;
651
668
  var QUEUE_MAX_SIZE = 100;
652
669
  var QUEUE_ENTRY_TTL = 3e4;
653
670
  var sendQueues = /* @__PURE__ */ new Map();
671
+ var TYPING_THROTTLE_MS = 3e3;
672
+ var _lastTypingSentAt = /* @__PURE__ */ new Map();
673
+ function resetTypingThrottle() {
674
+ _lastTypingSentAt.clear();
675
+ }
676
+ registerTeardownHook(resetTypingThrottle);
654
677
  var sendQueueRunning = /* @__PURE__ */ new Map();
655
678
  async function drainSendQueue(conversationId) {
656
679
  if (sendQueueRunning.get(conversationId)) return;
@@ -781,8 +804,40 @@ var socketEmit = {
781
804
  return withAck("unpin_message", { messageId });
782
805
  },
783
806
  // markRead and typing are best-effort — silently dropped if socket not ready
807
+ //
808
+ // Typing is additionally LEADING-THROTTLED here rather than in the UI layer,
809
+ // so every consumer (both UI SDKs and any host calling socketEmit directly)
810
+ // gets the same emit budget and none can bypass it.
811
+ //
812
+ // The composer calls this on EVERY keystroke. A 40-word message is ~200 calls
813
+ // of which exactly one carries information: "this person started typing".
814
+ // Each one previously cost a round trip plus real server work, making typing
815
+ // the single most expensive event in the system per unit of user value.
816
+ //
817
+ // Shape of the throttle:
818
+ // isTyping:true — passed through at most once per TYPING_THROTTLE_MS per
819
+ // conversation. A continuous typist emits ~20/min instead
820
+ // of ~200/min.
821
+ // isTyping:false — ALWAYS passed through, and resets the window. It is the
822
+ // edge that clears the indicator on every peer, it is
823
+ // already debounced by the composer, and dropping it is
824
+ // exactly the failure that leaves "is typing…" stuck.
825
+ //
826
+ // The server refreshes its typing key on each true edge (10s TTL) and
827
+ // receivers expire their own indicators after TYPING_EXPIRY_MS, both of which
828
+ // are comfortably longer than TYPING_THROTTLE_MS — so a throttled-away event
829
+ // never lets an indicator lapse mid-typing.
784
830
  typing(conversationId, isTyping) {
785
- fireAndForget("typing", { conversationId, isTyping });
831
+ if (!isTyping) {
832
+ _lastTypingSentAt.delete(conversationId);
833
+ fireAndForget("typing", { conversationId, isTyping: false });
834
+ return;
835
+ }
836
+ const now = Date.now();
837
+ const last = _lastTypingSentAt.get(conversationId) ?? 0;
838
+ if (now - last < TYPING_THROTTLE_MS) return;
839
+ _lastTypingSentAt.set(conversationId, now);
840
+ fireAndForget("typing", { conversationId, isTyping: true });
786
841
  },
787
842
  markRead(conversationId, messageId) {
788
843
  fireAndForget("mark_read", { conversationId, ...messageId ? { messageId } : {} });
@@ -1369,9 +1424,11 @@ export {
1369
1424
  readRetryAfterMs,
1370
1425
  reconnectSocket,
1371
1426
  refreshSocketAuth,
1427
+ registerTeardownHook,
1372
1428
  renderMentionParts,
1373
1429
  resetAuthStore,
1374
1430
  resetTrackedRooms,
1431
+ resetTypingThrottle,
1375
1432
  resolveConfig,
1376
1433
  resolveSystemMessageText,
1377
1434
  setApiClientInstance,