@antzsoft/chat-core 1.4.6 → 1.4.8
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 +86 -7
- package/dist/chat.store-TA6G7PD6.js +7 -0
- package/dist/{chunk-U637W5MD.js → chunk-L537XWRD.js} +12 -4
- package/dist/chunk-L537XWRD.js.map +1 -0
- package/dist/chunk-QHELYVNT.js +109 -0
- package/dist/chunk-QHELYVNT.js.map +1 -0
- package/dist/index.cjs +145 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.js +82 -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 +19 -5
- package/package.json +1 -1
- package/dist/chat.store-UVTDBPEC.js +0 -7
- package/dist/chunk-U637W5MD.js.map +0 -1
- package/dist/chunk-UIYJAOGL.js +0 -62
- package/dist/chunk-UIYJAOGL.js.map +0 -1
- /package/dist/{chat.store-UVTDBPEC.js.map → chat.store-TA6G7PD6.js.map} +0 -0
package/dist/index.d.cts
CHANGED
|
@@ -12,6 +12,15 @@ declare const authApi: {
|
|
|
12
12
|
login(credentials: LoginCredentials): Promise<AuthResponse>;
|
|
13
13
|
register(payload: RegisterData): Promise<AuthResponse>;
|
|
14
14
|
refresh(refreshToken: string): Promise<AuthTokens>;
|
|
15
|
+
/**
|
|
16
|
+
* Ends the session. Never blocks on the transit handshake: logout has to work
|
|
17
|
+
* on a degraded channel, which is exactly when it matters most.
|
|
18
|
+
*
|
|
19
|
+
* The refreshToken is dropped when no transit session exists, so it is never
|
|
20
|
+
* sent in the clear. Losing it costs nothing meaningful — the server revokes
|
|
21
|
+
* against the JWT's own user id either way; passing it only lets the server
|
|
22
|
+
* target that one refresh token instead of the caller's session set.
|
|
23
|
+
*/
|
|
15
24
|
logout(refreshToken?: string): Promise<void>;
|
|
16
25
|
logoutAll(): Promise<void>;
|
|
17
26
|
getMe(): Promise<User>;
|
|
@@ -327,6 +336,28 @@ type TokenStore = {
|
|
|
327
336
|
};
|
|
328
337
|
declare function setAuthReadyPromise(promise: Promise<unknown> | null): void;
|
|
329
338
|
declare function isApiClientConfigured(): boolean;
|
|
339
|
+
/**
|
|
340
|
+
* Per-request opt-out of the transit GATE (not of transit itself).
|
|
341
|
+
*
|
|
342
|
+
* Set via `{ headers: { [TRANSIT_OPTIONAL_HEADER]: '1' } }` on the axios call.
|
|
343
|
+
* A request marked this way still encrypts normally whenever a session happens
|
|
344
|
+
* to be ready — it simply refuses to BLOCK waiting for one, and goes out
|
|
345
|
+
* unencrypted rather than failing when the channel is down.
|
|
346
|
+
*
|
|
347
|
+
* This exists for teardown calls (logout, logout-all, device de-registration).
|
|
348
|
+
* Those must succeed precisely when the secure channel is degraded: a control
|
|
349
|
+
* that prevents a user from ending their own session is inverted. Their server
|
|
350
|
+
* routes are marked @PreTransit() so they accept an unencrypted request, and
|
|
351
|
+
* JwtAuthGuard still authenticates them — transit is a confidentiality layer,
|
|
352
|
+
* never an authenticity one (POST /crypto/session is itself unauthenticated, so
|
|
353
|
+
* holding a transit session proves nothing about the caller).
|
|
354
|
+
*
|
|
355
|
+
* Do NOT add this to ordinary routes: they are not @PreTransit() server-side and
|
|
356
|
+
* would 403 with "Transit encryption required" the moment the gate is skipped.
|
|
357
|
+
*
|
|
358
|
+
* The marker header is stripped before the request leaves the client.
|
|
359
|
+
*/
|
|
360
|
+
declare const TRANSIT_OPTIONAL_HEADER = "x-antz-transit-optional";
|
|
330
361
|
declare function initApiClient(config: ResolvedConfig, tokenStore: TokenStore): AxiosInstance;
|
|
331
362
|
declare function setApiClientInstance(instance: AxiosInstance): void;
|
|
332
363
|
declare function getApiClient(): AxiosInstance;
|
|
@@ -339,6 +370,7 @@ declare function getSocket(): Socket;
|
|
|
339
370
|
declare function tryGetSocket(): Socket | null;
|
|
340
371
|
declare function getSocketStatus(): SocketStatus;
|
|
341
372
|
declare function onSocketStatus(listener: StatusListener): () => void;
|
|
373
|
+
declare function registerTeardownHook(hook: () => void): () => void;
|
|
342
374
|
/** Drop all tracked rooms. Call on a genuine identity change (user/tenant
|
|
343
375
|
* switch) so the reconnect re-flush can't re-join the previous user's rooms.
|
|
344
376
|
* NOT called from disconnectSocket() — a token refresh is not an intent change. */
|
|
@@ -353,6 +385,9 @@ declare function reconnectSocket(token: string, userId?: string, tenantId?: stri
|
|
|
353
385
|
*/
|
|
354
386
|
declare function refreshSocketAuth(): boolean;
|
|
355
387
|
|
|
388
|
+
/** Clears the typing throttle state. Call on teardown/identity change so a new
|
|
389
|
+
* session cannot inherit the previous one's suppression window. */
|
|
390
|
+
declare function resetTypingThrottle(): void;
|
|
356
391
|
declare const socketEmit: {
|
|
357
392
|
joinRoom(conversationId: string): void;
|
|
358
393
|
leaveRoom(conversationId: string): void;
|
|
@@ -584,6 +619,10 @@ interface ChatState {
|
|
|
584
619
|
} | null) => void;
|
|
585
620
|
addTypingUser: (conversationId: string, user: TypingUser) => void;
|
|
586
621
|
removeTypingUser: (conversationId: string, userId: string) => void;
|
|
622
|
+
/** Drops every typing indicator and cancels their expiry timers. For teardown
|
|
623
|
+
* and identity change — a socket drop no longer needs it, since indicators
|
|
624
|
+
* expire on their own. */
|
|
625
|
+
clearTypingUsers: () => void;
|
|
587
626
|
setUserOnline: (userId: string) => void;
|
|
588
627
|
setUserOffline: (userId: string) => void;
|
|
589
628
|
setOnlineUsers: (userIds: string[]) => void;
|
|
@@ -848,4 +887,4 @@ declare class AntzChatClient {
|
|
|
848
887
|
uploadIcon(conversationId: string, file: UploadableFile): Promise<Conversation>;
|
|
849
888
|
}
|
|
850
889
|
|
|
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 };
|
|
890
|
+
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, TRANSIT_OPTIONAL_HEADER, 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
|
@@ -12,6 +12,15 @@ declare const authApi: {
|
|
|
12
12
|
login(credentials: LoginCredentials): Promise<AuthResponse>;
|
|
13
13
|
register(payload: RegisterData): Promise<AuthResponse>;
|
|
14
14
|
refresh(refreshToken: string): Promise<AuthTokens>;
|
|
15
|
+
/**
|
|
16
|
+
* Ends the session. Never blocks on the transit handshake: logout has to work
|
|
17
|
+
* on a degraded channel, which is exactly when it matters most.
|
|
18
|
+
*
|
|
19
|
+
* The refreshToken is dropped when no transit session exists, so it is never
|
|
20
|
+
* sent in the clear. Losing it costs nothing meaningful — the server revokes
|
|
21
|
+
* against the JWT's own user id either way; passing it only lets the server
|
|
22
|
+
* target that one refresh token instead of the caller's session set.
|
|
23
|
+
*/
|
|
15
24
|
logout(refreshToken?: string): Promise<void>;
|
|
16
25
|
logoutAll(): Promise<void>;
|
|
17
26
|
getMe(): Promise<User>;
|
|
@@ -327,6 +336,28 @@ type TokenStore = {
|
|
|
327
336
|
};
|
|
328
337
|
declare function setAuthReadyPromise(promise: Promise<unknown> | null): void;
|
|
329
338
|
declare function isApiClientConfigured(): boolean;
|
|
339
|
+
/**
|
|
340
|
+
* Per-request opt-out of the transit GATE (not of transit itself).
|
|
341
|
+
*
|
|
342
|
+
* Set via `{ headers: { [TRANSIT_OPTIONAL_HEADER]: '1' } }` on the axios call.
|
|
343
|
+
* A request marked this way still encrypts normally whenever a session happens
|
|
344
|
+
* to be ready — it simply refuses to BLOCK waiting for one, and goes out
|
|
345
|
+
* unencrypted rather than failing when the channel is down.
|
|
346
|
+
*
|
|
347
|
+
* This exists for teardown calls (logout, logout-all, device de-registration).
|
|
348
|
+
* Those must succeed precisely when the secure channel is degraded: a control
|
|
349
|
+
* that prevents a user from ending their own session is inverted. Their server
|
|
350
|
+
* routes are marked @PreTransit() so they accept an unencrypted request, and
|
|
351
|
+
* JwtAuthGuard still authenticates them — transit is a confidentiality layer,
|
|
352
|
+
* never an authenticity one (POST /crypto/session is itself unauthenticated, so
|
|
353
|
+
* holding a transit session proves nothing about the caller).
|
|
354
|
+
*
|
|
355
|
+
* Do NOT add this to ordinary routes: they are not @PreTransit() server-side and
|
|
356
|
+
* would 403 with "Transit encryption required" the moment the gate is skipped.
|
|
357
|
+
*
|
|
358
|
+
* The marker header is stripped before the request leaves the client.
|
|
359
|
+
*/
|
|
360
|
+
declare const TRANSIT_OPTIONAL_HEADER = "x-antz-transit-optional";
|
|
330
361
|
declare function initApiClient(config: ResolvedConfig, tokenStore: TokenStore): AxiosInstance;
|
|
331
362
|
declare function setApiClientInstance(instance: AxiosInstance): void;
|
|
332
363
|
declare function getApiClient(): AxiosInstance;
|
|
@@ -339,6 +370,7 @@ declare function getSocket(): Socket;
|
|
|
339
370
|
declare function tryGetSocket(): Socket | null;
|
|
340
371
|
declare function getSocketStatus(): SocketStatus;
|
|
341
372
|
declare function onSocketStatus(listener: StatusListener): () => void;
|
|
373
|
+
declare function registerTeardownHook(hook: () => void): () => void;
|
|
342
374
|
/** Drop all tracked rooms. Call on a genuine identity change (user/tenant
|
|
343
375
|
* switch) so the reconnect re-flush can't re-join the previous user's rooms.
|
|
344
376
|
* NOT called from disconnectSocket() — a token refresh is not an intent change. */
|
|
@@ -353,6 +385,9 @@ declare function reconnectSocket(token: string, userId?: string, tenantId?: stri
|
|
|
353
385
|
*/
|
|
354
386
|
declare function refreshSocketAuth(): boolean;
|
|
355
387
|
|
|
388
|
+
/** Clears the typing throttle state. Call on teardown/identity change so a new
|
|
389
|
+
* session cannot inherit the previous one's suppression window. */
|
|
390
|
+
declare function resetTypingThrottle(): void;
|
|
356
391
|
declare const socketEmit: {
|
|
357
392
|
joinRoom(conversationId: string): void;
|
|
358
393
|
leaveRoom(conversationId: string): void;
|
|
@@ -584,6 +619,10 @@ interface ChatState {
|
|
|
584
619
|
} | null) => void;
|
|
585
620
|
addTypingUser: (conversationId: string, user: TypingUser) => void;
|
|
586
621
|
removeTypingUser: (conversationId: string, userId: string) => void;
|
|
622
|
+
/** Drops every typing indicator and cancels their expiry timers. For teardown
|
|
623
|
+
* and identity change — a socket drop no longer needs it, since indicators
|
|
624
|
+
* expire on their own. */
|
|
625
|
+
clearTypingUsers: () => void;
|
|
587
626
|
setUserOnline: (userId: string) => void;
|
|
588
627
|
setUserOffline: (userId: string) => void;
|
|
589
628
|
setOnlineUsers: (userIds: string[]) => void;
|
|
@@ -848,4 +887,4 @@ declare class AntzChatClient {
|
|
|
848
887
|
uploadIcon(conversationId: string, file: UploadableFile): Promise<Conversation>;
|
|
849
888
|
}
|
|
850
889
|
|
|
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 };
|
|
890
|
+
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, TRANSIT_OPTIONAL_HEADER, 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
|
+
TRANSIT_OPTIONAL_HEADER,
|
|
8
9
|
TransitRateLimitedError,
|
|
9
10
|
awaitTransitReadyOr,
|
|
10
11
|
clearTransitSession,
|
|
@@ -38,10 +39,10 @@ import {
|
|
|
38
39
|
setTransitSession,
|
|
39
40
|
storageApi,
|
|
40
41
|
uploadBatch
|
|
41
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-L537XWRD.js";
|
|
42
43
|
import {
|
|
43
44
|
useChatStore
|
|
44
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-QHELYVNT.js";
|
|
45
46
|
|
|
46
47
|
// src/config/types.ts
|
|
47
48
|
function resolveConfig(config) {
|
|
@@ -105,6 +106,7 @@ function resolveConfig(config) {
|
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
// src/api/auth.ts
|
|
109
|
+
var teardownRequest = { headers: { [TRANSIT_OPTIONAL_HEADER]: "1" } };
|
|
108
110
|
var authApi = {
|
|
109
111
|
async login(credentials) {
|
|
110
112
|
const { data } = await getApiClient().post("/auth/login", credentials);
|
|
@@ -118,11 +120,22 @@ var authApi = {
|
|
|
118
120
|
const { data } = await getApiClient().post("/auth/refresh", { refreshToken });
|
|
119
121
|
return data;
|
|
120
122
|
},
|
|
123
|
+
/**
|
|
124
|
+
* Ends the session. Never blocks on the transit handshake: logout has to work
|
|
125
|
+
* on a degraded channel, which is exactly when it matters most.
|
|
126
|
+
*
|
|
127
|
+
* The refreshToken is dropped when no transit session exists, so it is never
|
|
128
|
+
* sent in the clear. Losing it costs nothing meaningful — the server revokes
|
|
129
|
+
* against the JWT's own user id either way; passing it only lets the server
|
|
130
|
+
* target that one refresh token instead of the caller's session set.
|
|
131
|
+
*/
|
|
121
132
|
async logout(refreshToken) {
|
|
122
|
-
|
|
133
|
+
const encrypted = getTransitSession() != null;
|
|
134
|
+
const body = refreshToken && encrypted ? { refreshToken } : {};
|
|
135
|
+
await getApiClient().post("/auth/logout", body, teardownRequest);
|
|
123
136
|
},
|
|
124
137
|
async logoutAll() {
|
|
125
|
-
await getApiClient().post("/auth/logout-all");
|
|
138
|
+
await getApiClient().post("/auth/logout-all", {}, teardownRequest);
|
|
126
139
|
},
|
|
127
140
|
async getMe() {
|
|
128
141
|
const { data } = await getApiClient().get("/users/me");
|
|
@@ -405,6 +418,19 @@ function secureOn(socket, event, handler) {
|
|
|
405
418
|
handler(raw);
|
|
406
419
|
});
|
|
407
420
|
}
|
|
421
|
+
var _teardownHooks = /* @__PURE__ */ new Set();
|
|
422
|
+
function registerTeardownHook(hook) {
|
|
423
|
+
_teardownHooks.add(hook);
|
|
424
|
+
return () => _teardownHooks.delete(hook);
|
|
425
|
+
}
|
|
426
|
+
function runTeardownHooks() {
|
|
427
|
+
_teardownHooks.forEach((hook) => {
|
|
428
|
+
try {
|
|
429
|
+
hook();
|
|
430
|
+
} catch {
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
}
|
|
408
434
|
var _joinedRooms = /* @__PURE__ */ new Set();
|
|
409
435
|
function trackRoomJoin(conversationId) {
|
|
410
436
|
_joinedRooms.add(conversationId);
|
|
@@ -548,13 +574,13 @@ async function _doConnect(config, getToken) {
|
|
|
548
574
|
});
|
|
549
575
|
}
|
|
550
576
|
secureOn(_socket, "read_receipt", (event) => {
|
|
551
|
-
import("./chat.store-
|
|
577
|
+
import("./chat.store-TA6G7PD6.js").then(({ useChatStore: useChatStore2 }) => {
|
|
552
578
|
const e = event;
|
|
553
579
|
useChatStore2.getState().setLastRead(e.conversationId, e.messageId, e.readAt);
|
|
554
580
|
});
|
|
555
581
|
});
|
|
556
582
|
secureOn(_socket, "user_online", (event) => {
|
|
557
|
-
import("./chat.store-
|
|
583
|
+
import("./chat.store-TA6G7PD6.js").then(({ useChatStore: useChatStore2 }) => {
|
|
558
584
|
const store = useChatStore2.getState();
|
|
559
585
|
const e = event;
|
|
560
586
|
store.setUserOnline(e.userId);
|
|
@@ -562,7 +588,7 @@ async function _doConnect(config, getToken) {
|
|
|
562
588
|
});
|
|
563
589
|
});
|
|
564
590
|
secureOn(_socket, "user_offline", (event) => {
|
|
565
|
-
import("./chat.store-
|
|
591
|
+
import("./chat.store-TA6G7PD6.js").then(({ useChatStore: useChatStore2 }) => {
|
|
566
592
|
const e = event;
|
|
567
593
|
const store = useChatStore2.getState();
|
|
568
594
|
store.setUserOffline(e.userId);
|
|
@@ -600,6 +626,10 @@ function disconnectSocket() {
|
|
|
600
626
|
}
|
|
601
627
|
clearTransitSession();
|
|
602
628
|
resetAlgoCache();
|
|
629
|
+
runTeardownHooks();
|
|
630
|
+
import("./chat.store-TA6G7PD6.js").then(({ useChatStore: useChatStore2 }) => {
|
|
631
|
+
useChatStore2.getState().clearTypingUsers();
|
|
632
|
+
});
|
|
603
633
|
_getToken = null;
|
|
604
634
|
_userId = void 0;
|
|
605
635
|
_tenantId = void 0;
|
|
@@ -651,6 +681,12 @@ var RECONNECT_WAIT_TIMEOUT = 15e3;
|
|
|
651
681
|
var QUEUE_MAX_SIZE = 100;
|
|
652
682
|
var QUEUE_ENTRY_TTL = 3e4;
|
|
653
683
|
var sendQueues = /* @__PURE__ */ new Map();
|
|
684
|
+
var TYPING_THROTTLE_MS = 3e3;
|
|
685
|
+
var _lastTypingSentAt = /* @__PURE__ */ new Map();
|
|
686
|
+
function resetTypingThrottle() {
|
|
687
|
+
_lastTypingSentAt.clear();
|
|
688
|
+
}
|
|
689
|
+
registerTeardownHook(resetTypingThrottle);
|
|
654
690
|
var sendQueueRunning = /* @__PURE__ */ new Map();
|
|
655
691
|
async function drainSendQueue(conversationId) {
|
|
656
692
|
if (sendQueueRunning.get(conversationId)) return;
|
|
@@ -781,8 +817,40 @@ var socketEmit = {
|
|
|
781
817
|
return withAck("unpin_message", { messageId });
|
|
782
818
|
},
|
|
783
819
|
// markRead and typing are best-effort — silently dropped if socket not ready
|
|
820
|
+
//
|
|
821
|
+
// Typing is additionally LEADING-THROTTLED here rather than in the UI layer,
|
|
822
|
+
// so every consumer (both UI SDKs and any host calling socketEmit directly)
|
|
823
|
+
// gets the same emit budget and none can bypass it.
|
|
824
|
+
//
|
|
825
|
+
// The composer calls this on EVERY keystroke. A 40-word message is ~200 calls
|
|
826
|
+
// of which exactly one carries information: "this person started typing".
|
|
827
|
+
// Each one previously cost a round trip plus real server work, making typing
|
|
828
|
+
// the single most expensive event in the system per unit of user value.
|
|
829
|
+
//
|
|
830
|
+
// Shape of the throttle:
|
|
831
|
+
// isTyping:true — passed through at most once per TYPING_THROTTLE_MS per
|
|
832
|
+
// conversation. A continuous typist emits ~20/min instead
|
|
833
|
+
// of ~200/min.
|
|
834
|
+
// isTyping:false — ALWAYS passed through, and resets the window. It is the
|
|
835
|
+
// edge that clears the indicator on every peer, it is
|
|
836
|
+
// already debounced by the composer, and dropping it is
|
|
837
|
+
// exactly the failure that leaves "is typing…" stuck.
|
|
838
|
+
//
|
|
839
|
+
// The server refreshes its typing key on each true edge (10s TTL) and
|
|
840
|
+
// receivers expire their own indicators after TYPING_EXPIRY_MS, both of which
|
|
841
|
+
// are comfortably longer than TYPING_THROTTLE_MS — so a throttled-away event
|
|
842
|
+
// never lets an indicator lapse mid-typing.
|
|
784
843
|
typing(conversationId, isTyping) {
|
|
785
|
-
|
|
844
|
+
if (!isTyping) {
|
|
845
|
+
_lastTypingSentAt.delete(conversationId);
|
|
846
|
+
fireAndForget("typing", { conversationId, isTyping: false });
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
const now = Date.now();
|
|
850
|
+
const last = _lastTypingSentAt.get(conversationId) ?? 0;
|
|
851
|
+
if (now - last < TYPING_THROTTLE_MS) return;
|
|
852
|
+
_lastTypingSentAt.set(conversationId, now);
|
|
853
|
+
fireAndForget("typing", { conversationId, isTyping: true });
|
|
786
854
|
},
|
|
787
855
|
markRead(conversationId, messageId) {
|
|
788
856
|
fireAndForget("mark_read", { conversationId, ...messageId ? { messageId } : {} });
|
|
@@ -1147,7 +1215,9 @@ var devicesApi = {
|
|
|
1147
1215
|
* Call this on logout so the user stops receiving push notifications on this device.
|
|
1148
1216
|
*/
|
|
1149
1217
|
async remove(deviceId) {
|
|
1150
|
-
await getApiClient().post(`/users/me/devices/${deviceId}/remove
|
|
1218
|
+
await getApiClient().post(`/users/me/devices/${deviceId}/remove`, {}, {
|
|
1219
|
+
headers: { [TRANSIT_OPTIONAL_HEADER]: "1" }
|
|
1220
|
+
});
|
|
1151
1221
|
}
|
|
1152
1222
|
};
|
|
1153
1223
|
|
|
@@ -1333,6 +1403,7 @@ export {
|
|
|
1333
1403
|
HIGHLY_FORWARDED_DEPTH_THRESHOLD,
|
|
1334
1404
|
MAX_FORWARD_TARGETS,
|
|
1335
1405
|
MENTION_ALL_ID,
|
|
1406
|
+
TRANSIT_OPTIONAL_HEADER,
|
|
1336
1407
|
TransitRateLimitedError,
|
|
1337
1408
|
appConfigApi,
|
|
1338
1409
|
authApi,
|
|
@@ -1369,9 +1440,11 @@ export {
|
|
|
1369
1440
|
readRetryAfterMs,
|
|
1370
1441
|
reconnectSocket,
|
|
1371
1442
|
refreshSocketAuth,
|
|
1443
|
+
registerTeardownHook,
|
|
1372
1444
|
renderMentionParts,
|
|
1373
1445
|
resetAuthStore,
|
|
1374
1446
|
resetTrackedRooms,
|
|
1447
|
+
resetTypingThrottle,
|
|
1375
1448
|
resolveConfig,
|
|
1376
1449
|
resolveSystemMessageText,
|
|
1377
1450
|
setApiClientInstance,
|