@antzsoft/chat-core 1.4.5 → 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/README.md +82 -5
- package/dist/chat.store-TA6G7PD6.js +7 -0
- package/dist/chunk-QHELYVNT.js +109 -0
- package/dist/chunk-QHELYVNT.js.map +1 -0
- package/dist/{chunk-WUNH3UTE.js → chunk-U637W5MD.js} +62 -13
- package/dist/chunk-U637W5MD.js.map +1 -0
- package/dist/index.cjs +182 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +60 -3
- package/dist/index.d.ts +60 -3
- package/dist/index.js +70 -9
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.js +1 -1
- package/docs/integration-guide.html +170 -9
- package/package.json +1 -1
- package/dist/chat.store-UVTDBPEC.js +0 -7
- package/dist/chunk-UIYJAOGL.js +0 -62
- package/dist/chunk-UIYJAOGL.js.map +0 -1
- package/dist/chunk-WUNH3UTE.js.map +0 -1
- /package/dist/{chat.store-UVTDBPEC.js.map → chat.store-TA6G7PD6.js.map} +0 -0
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;
|
|
@@ -395,17 +399,66 @@ declare function encryptPayload(data: unknown, sessionKey: AnySessionKey): Promi
|
|
|
395
399
|
declare function decryptPayload(envelope: TransitEnvelope, sessionKey: AnySessionKey): Promise<unknown>;
|
|
396
400
|
declare function isTransitEnvelope(v: unknown): v is TransitEnvelope;
|
|
397
401
|
|
|
402
|
+
/**
|
|
403
|
+
* Caller identity attached to the pre-auth transit handshake requests.
|
|
404
|
+
*
|
|
405
|
+
* GET /crypto/pubkey and POST /crypto/session run BEFORE a transit session (and
|
|
406
|
+
* therefore before the authenticated axios client) exists, so they bypass the
|
|
407
|
+
* request interceptor that normally adds these headers. Without them the server
|
|
408
|
+
* can only rate-limit these two routes by client IP — which means every user
|
|
409
|
+
* behind one NAT, office proxy or ALB shares a single bucket, and one client's
|
|
410
|
+
* reload loop 429s everyone else.
|
|
411
|
+
*
|
|
412
|
+
* These values are NOT used for authentication: the endpoints are unauthenticated
|
|
413
|
+
* by design and the server treats the headers as a fairness hint only, with a
|
|
414
|
+
* per-IP ceiling underneath as the real abuse limit. Sending them is therefore
|
|
415
|
+
* safe, optional, and backward compatible — an older SDK that omits them simply
|
|
416
|
+
* falls back to the shared per-IP bucket.
|
|
417
|
+
*/
|
|
418
|
+
interface TransitIdentity {
|
|
419
|
+
/** External user id — same value sent as x-user-id on authenticated requests. */
|
|
420
|
+
userId?: string;
|
|
421
|
+
/** Tenant id — same value sent as X-Tenant-ID on authenticated requests. */
|
|
422
|
+
tenantId?: string;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* How long the server asked us to wait, in ms, from a 429's Retry-After.
|
|
426
|
+
*
|
|
427
|
+
* The chat server runs TWO named rate-limit layers, and @nestjs/throttler
|
|
428
|
+
* suffixes its headers with the throttler name unless that name is literally
|
|
429
|
+
* "default". So a 429 carries `Retry-After-identity` or `Retry-After-ip`, not a
|
|
430
|
+
* bare `Retry-After`. Older servers and intermediary proxies may still send the
|
|
431
|
+
* bare name, so all three are read; when more than one is present the LONGER
|
|
432
|
+
* wait wins, since retrying before the slower bucket drains just earns another
|
|
433
|
+
* 429.
|
|
434
|
+
*
|
|
435
|
+
* Returns undefined when no variant is readable — in a browser that also
|
|
436
|
+
* happens when the server omits these from Access-Control-Expose-Headers, in
|
|
437
|
+
* which case callers must fall back to their own backoff.
|
|
438
|
+
*/
|
|
439
|
+
declare function readRetryAfterMs(headers: Headers): number | undefined;
|
|
440
|
+
/**
|
|
441
|
+
* Thrown when the transit handshake is rate-limited (HTTP 429).
|
|
442
|
+
*
|
|
443
|
+
* Distinct from a generic failure because the correct response differs: a 429
|
|
444
|
+
* means "wait", not "this is broken", so callers must not burn their retry
|
|
445
|
+
* budget on it and should honour `retryAfterMs` when the server supplied it.
|
|
446
|
+
*/
|
|
447
|
+
declare class TransitRateLimitedError extends Error {
|
|
448
|
+
readonly retryAfterMs?: number;
|
|
449
|
+
constructor(retryAfterMs?: number);
|
|
450
|
+
}
|
|
398
451
|
interface ServerPublicKeys {
|
|
399
452
|
x25519: string;
|
|
400
453
|
p256: string;
|
|
401
454
|
enabled: boolean;
|
|
402
455
|
}
|
|
403
|
-
declare function fetchServerKeys(apiUrl: string): Promise<ServerPublicKeys>;
|
|
456
|
+
declare function fetchServerKeys(apiUrl: string, identity?: TransitIdentity): Promise<ServerPublicKeys>;
|
|
404
457
|
declare function generateEphemeralKey(algo: TransitAlgo, serverKeys: ServerPublicKeys): Promise<{
|
|
405
458
|
ephemeralPubB64: string;
|
|
406
459
|
deriveSessionKey: (sessionId: string) => Promise<unknown>;
|
|
407
460
|
}>;
|
|
408
|
-
declare function createRestTransitSession(apiUrl: string): Promise<{
|
|
461
|
+
declare function createRestTransitSession(apiUrl: string, identity?: TransitIdentity): Promise<{
|
|
409
462
|
sessionId: string;
|
|
410
463
|
sessionKey: CryptoKey | Uint8Array;
|
|
411
464
|
} | null>;
|
|
@@ -535,6 +588,10 @@ interface ChatState {
|
|
|
535
588
|
} | null) => void;
|
|
536
589
|
addTypingUser: (conversationId: string, user: TypingUser) => void;
|
|
537
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;
|
|
538
595
|
setUserOnline: (userId: string) => void;
|
|
539
596
|
setUserOffline: (userId: string) => void;
|
|
540
597
|
setOnlineUsers: (userIds: string[]) => void;
|
|
@@ -799,4 +856,4 @@ declare class AntzChatClient {
|
|
|
799
856
|
uploadIcon(conversationId: string, file: UploadableFile): Promise<Conversation>;
|
|
800
857
|
}
|
|
801
858
|
|
|
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 };
|
|
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;
|
|
@@ -395,17 +399,66 @@ declare function encryptPayload(data: unknown, sessionKey: AnySessionKey): Promi
|
|
|
395
399
|
declare function decryptPayload(envelope: TransitEnvelope, sessionKey: AnySessionKey): Promise<unknown>;
|
|
396
400
|
declare function isTransitEnvelope(v: unknown): v is TransitEnvelope;
|
|
397
401
|
|
|
402
|
+
/**
|
|
403
|
+
* Caller identity attached to the pre-auth transit handshake requests.
|
|
404
|
+
*
|
|
405
|
+
* GET /crypto/pubkey and POST /crypto/session run BEFORE a transit session (and
|
|
406
|
+
* therefore before the authenticated axios client) exists, so they bypass the
|
|
407
|
+
* request interceptor that normally adds these headers. Without them the server
|
|
408
|
+
* can only rate-limit these two routes by client IP — which means every user
|
|
409
|
+
* behind one NAT, office proxy or ALB shares a single bucket, and one client's
|
|
410
|
+
* reload loop 429s everyone else.
|
|
411
|
+
*
|
|
412
|
+
* These values are NOT used for authentication: the endpoints are unauthenticated
|
|
413
|
+
* by design and the server treats the headers as a fairness hint only, with a
|
|
414
|
+
* per-IP ceiling underneath as the real abuse limit. Sending them is therefore
|
|
415
|
+
* safe, optional, and backward compatible — an older SDK that omits them simply
|
|
416
|
+
* falls back to the shared per-IP bucket.
|
|
417
|
+
*/
|
|
418
|
+
interface TransitIdentity {
|
|
419
|
+
/** External user id — same value sent as x-user-id on authenticated requests. */
|
|
420
|
+
userId?: string;
|
|
421
|
+
/** Tenant id — same value sent as X-Tenant-ID on authenticated requests. */
|
|
422
|
+
tenantId?: string;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* How long the server asked us to wait, in ms, from a 429's Retry-After.
|
|
426
|
+
*
|
|
427
|
+
* The chat server runs TWO named rate-limit layers, and @nestjs/throttler
|
|
428
|
+
* suffixes its headers with the throttler name unless that name is literally
|
|
429
|
+
* "default". So a 429 carries `Retry-After-identity` or `Retry-After-ip`, not a
|
|
430
|
+
* bare `Retry-After`. Older servers and intermediary proxies may still send the
|
|
431
|
+
* bare name, so all three are read; when more than one is present the LONGER
|
|
432
|
+
* wait wins, since retrying before the slower bucket drains just earns another
|
|
433
|
+
* 429.
|
|
434
|
+
*
|
|
435
|
+
* Returns undefined when no variant is readable — in a browser that also
|
|
436
|
+
* happens when the server omits these from Access-Control-Expose-Headers, in
|
|
437
|
+
* which case callers must fall back to their own backoff.
|
|
438
|
+
*/
|
|
439
|
+
declare function readRetryAfterMs(headers: Headers): number | undefined;
|
|
440
|
+
/**
|
|
441
|
+
* Thrown when the transit handshake is rate-limited (HTTP 429).
|
|
442
|
+
*
|
|
443
|
+
* Distinct from a generic failure because the correct response differs: a 429
|
|
444
|
+
* means "wait", not "this is broken", so callers must not burn their retry
|
|
445
|
+
* budget on it and should honour `retryAfterMs` when the server supplied it.
|
|
446
|
+
*/
|
|
447
|
+
declare class TransitRateLimitedError extends Error {
|
|
448
|
+
readonly retryAfterMs?: number;
|
|
449
|
+
constructor(retryAfterMs?: number);
|
|
450
|
+
}
|
|
398
451
|
interface ServerPublicKeys {
|
|
399
452
|
x25519: string;
|
|
400
453
|
p256: string;
|
|
401
454
|
enabled: boolean;
|
|
402
455
|
}
|
|
403
|
-
declare function fetchServerKeys(apiUrl: string): Promise<ServerPublicKeys>;
|
|
456
|
+
declare function fetchServerKeys(apiUrl: string, identity?: TransitIdentity): Promise<ServerPublicKeys>;
|
|
404
457
|
declare function generateEphemeralKey(algo: TransitAlgo, serverKeys: ServerPublicKeys): Promise<{
|
|
405
458
|
ephemeralPubB64: string;
|
|
406
459
|
deriveSessionKey: (sessionId: string) => Promise<unknown>;
|
|
407
460
|
}>;
|
|
408
|
-
declare function createRestTransitSession(apiUrl: string): Promise<{
|
|
461
|
+
declare function createRestTransitSession(apiUrl: string, identity?: TransitIdentity): Promise<{
|
|
409
462
|
sessionId: string;
|
|
410
463
|
sessionKey: CryptoKey | Uint8Array;
|
|
411
464
|
} | null>;
|
|
@@ -535,6 +588,10 @@ interface ChatState {
|
|
|
535
588
|
} | null) => void;
|
|
536
589
|
addTypingUser: (conversationId: string, user: TypingUser) => void;
|
|
537
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;
|
|
538
595
|
setUserOnline: (userId: string) => void;
|
|
539
596
|
setUserOffline: (userId: string) => void;
|
|
540
597
|
setOnlineUsers: (userIds: string[]) => void;
|
|
@@ -799,4 +856,4 @@ declare class AntzChatClient {
|
|
|
799
856
|
uploadIcon(conversationId: string, file: UploadableFile): Promise<Conversation>;
|
|
800
857
|
}
|
|
801
858
|
|
|
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 };
|
|
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
|
@@ -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,16 +31,17 @@ 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-
|
|
41
|
+
} from "./chunk-U637W5MD.js";
|
|
40
42
|
import {
|
|
41
43
|
useChatStore
|
|
42
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-QHELYVNT.js";
|
|
43
45
|
|
|
44
46
|
// src/config/types.ts
|
|
45
47
|
function resolveConfig(config) {
|
|
@@ -403,6 +405,19 @@ function secureOn(socket, event, handler) {
|
|
|
403
405
|
handler(raw);
|
|
404
406
|
});
|
|
405
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
|
+
}
|
|
406
421
|
var _joinedRooms = /* @__PURE__ */ new Set();
|
|
407
422
|
function trackRoomJoin(conversationId) {
|
|
408
423
|
_joinedRooms.add(conversationId);
|
|
@@ -456,7 +471,7 @@ async function _doConnect(config, getToken) {
|
|
|
456
471
|
if (existingSession?.enabled && existingSession.sessionId) {
|
|
457
472
|
httpsSession = { sessionId: existingSession.sessionId, sessionKey: existingSession.sessionKey };
|
|
458
473
|
} else {
|
|
459
|
-
httpsSession = await createRestTransitSession(config.apiUrl);
|
|
474
|
+
httpsSession = await createRestTransitSession(config.apiUrl, { userId: config.userId, tenantId: config.tenantId });
|
|
460
475
|
if (httpsSession) {
|
|
461
476
|
const algo = globalThis.crypto?.subtle ? await detectTransitAlgo() : "x25519";
|
|
462
477
|
setTransitSession({ sessionKey: httpsSession.sessionKey, algo, sessionId: httpsSession.sessionId, enabled: true });
|
|
@@ -467,7 +482,7 @@ async function _doConnect(config, getToken) {
|
|
|
467
482
|
boundDeriveSessionKey = null;
|
|
468
483
|
} else {
|
|
469
484
|
try {
|
|
470
|
-
const serverKeys = await fetchServerKeys(config.apiUrl);
|
|
485
|
+
const serverKeys = await fetchServerKeys(config.apiUrl, { userId: config.userId, tenantId: config.tenantId });
|
|
471
486
|
if (!serverKeys.enabled) {
|
|
472
487
|
throw new AntzChatError(
|
|
473
488
|
"TRANSIT_MISMATCH",
|
|
@@ -484,7 +499,7 @@ async function _doConnect(config, getToken) {
|
|
|
484
499
|
}
|
|
485
500
|
} else {
|
|
486
501
|
try {
|
|
487
|
-
const serverKeys = await fetchServerKeys(config.apiUrl);
|
|
502
|
+
const serverKeys = await fetchServerKeys(config.apiUrl, { userId: config.userId, tenantId: config.tenantId });
|
|
488
503
|
if (serverKeys.enabled) {
|
|
489
504
|
throw new AntzChatError(
|
|
490
505
|
"TRANSIT_MISMATCH",
|
|
@@ -546,13 +561,13 @@ async function _doConnect(config, getToken) {
|
|
|
546
561
|
});
|
|
547
562
|
}
|
|
548
563
|
secureOn(_socket, "read_receipt", (event) => {
|
|
549
|
-
import("./chat.store-
|
|
564
|
+
import("./chat.store-TA6G7PD6.js").then(({ useChatStore: useChatStore2 }) => {
|
|
550
565
|
const e = event;
|
|
551
566
|
useChatStore2.getState().setLastRead(e.conversationId, e.messageId, e.readAt);
|
|
552
567
|
});
|
|
553
568
|
});
|
|
554
569
|
secureOn(_socket, "user_online", (event) => {
|
|
555
|
-
import("./chat.store-
|
|
570
|
+
import("./chat.store-TA6G7PD6.js").then(({ useChatStore: useChatStore2 }) => {
|
|
556
571
|
const store = useChatStore2.getState();
|
|
557
572
|
const e = event;
|
|
558
573
|
store.setUserOnline(e.userId);
|
|
@@ -560,7 +575,7 @@ async function _doConnect(config, getToken) {
|
|
|
560
575
|
});
|
|
561
576
|
});
|
|
562
577
|
secureOn(_socket, "user_offline", (event) => {
|
|
563
|
-
import("./chat.store-
|
|
578
|
+
import("./chat.store-TA6G7PD6.js").then(({ useChatStore: useChatStore2 }) => {
|
|
564
579
|
const e = event;
|
|
565
580
|
const store = useChatStore2.getState();
|
|
566
581
|
store.setUserOffline(e.userId);
|
|
@@ -598,6 +613,10 @@ function disconnectSocket() {
|
|
|
598
613
|
}
|
|
599
614
|
clearTransitSession();
|
|
600
615
|
resetAlgoCache();
|
|
616
|
+
runTeardownHooks();
|
|
617
|
+
import("./chat.store-TA6G7PD6.js").then(({ useChatStore: useChatStore2 }) => {
|
|
618
|
+
useChatStore2.getState().clearTypingUsers();
|
|
619
|
+
});
|
|
601
620
|
_getToken = null;
|
|
602
621
|
_userId = void 0;
|
|
603
622
|
_tenantId = void 0;
|
|
@@ -649,6 +668,12 @@ var RECONNECT_WAIT_TIMEOUT = 15e3;
|
|
|
649
668
|
var QUEUE_MAX_SIZE = 100;
|
|
650
669
|
var QUEUE_ENTRY_TTL = 3e4;
|
|
651
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);
|
|
652
677
|
var sendQueueRunning = /* @__PURE__ */ new Map();
|
|
653
678
|
async function drainSendQueue(conversationId) {
|
|
654
679
|
if (sendQueueRunning.get(conversationId)) return;
|
|
@@ -779,8 +804,40 @@ var socketEmit = {
|
|
|
779
804
|
return withAck("unpin_message", { messageId });
|
|
780
805
|
},
|
|
781
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.
|
|
782
830
|
typing(conversationId, isTyping) {
|
|
783
|
-
|
|
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 });
|
|
784
841
|
},
|
|
785
842
|
markRead(conversationId, messageId) {
|
|
786
843
|
fireAndForget("mark_read", { conversationId, ...messageId ? { messageId } : {} });
|
|
@@ -1331,6 +1388,7 @@ export {
|
|
|
1331
1388
|
HIGHLY_FORWARDED_DEPTH_THRESHOLD,
|
|
1332
1389
|
MAX_FORWARD_TARGETS,
|
|
1333
1390
|
MENTION_ALL_ID,
|
|
1391
|
+
TransitRateLimitedError,
|
|
1334
1392
|
appConfigApi,
|
|
1335
1393
|
authApi,
|
|
1336
1394
|
buildMentionText,
|
|
@@ -1363,11 +1421,14 @@ export {
|
|
|
1363
1421
|
onSocketStatus,
|
|
1364
1422
|
parseMentions,
|
|
1365
1423
|
performHandshake,
|
|
1424
|
+
readRetryAfterMs,
|
|
1366
1425
|
reconnectSocket,
|
|
1367
1426
|
refreshSocketAuth,
|
|
1427
|
+
registerTeardownHook,
|
|
1368
1428
|
renderMentionParts,
|
|
1369
1429
|
resetAuthStore,
|
|
1370
1430
|
resetTrackedRooms,
|
|
1431
|
+
resetTypingThrottle,
|
|
1371
1432
|
resolveConfig,
|
|
1372
1433
|
resolveSystemMessageText,
|
|
1373
1434
|
setApiClientInstance,
|