@antzsoft/chat-core 1.0.0 → 1.0.3
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 +518 -31
- package/dist/chat.store-PY3YVYGN.js +7 -0
- package/dist/chat.store-PY3YVYGN.js.map +1 -0
- package/dist/chunk-TB52RCSF.js +54 -0
- package/dist/chunk-TB52RCSF.js.map +1 -0
- package/dist/index.cjs +252 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +217 -13
- package/dist/index.d.ts +217 -13
- package/dist/index.js +186 -55
- package/dist/index.js.map +1 -1
- package/docs/integration-guide.html +2076 -0
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useChatStore
|
|
3
|
+
} from "./chunk-TB52RCSF.js";
|
|
4
|
+
|
|
1
5
|
// src/config/types.ts
|
|
2
6
|
function resolveConfig(config) {
|
|
3
7
|
const socketUrl = config.socketUrl ?? config.apiUrl.replace(/\/api\/v\d+\/?$/, "").replace(/\/$/, "");
|
|
8
|
+
let socketOrigin = socketUrl;
|
|
9
|
+
let socketPath = "/socket.io";
|
|
10
|
+
try {
|
|
11
|
+
const parsed = new URL(socketUrl);
|
|
12
|
+
socketOrigin = parsed.origin;
|
|
13
|
+
const pathname = parsed.pathname.replace(/\/$/, "");
|
|
14
|
+
if (pathname && pathname !== "/") socketPath = `${pathname}/socket.io`;
|
|
15
|
+
} catch {
|
|
16
|
+
}
|
|
4
17
|
const raw = config.upload?.maxFileSizeMB;
|
|
5
18
|
let limits;
|
|
6
19
|
if (typeof raw === "number") {
|
|
@@ -18,10 +31,13 @@ function resolveConfig(config) {
|
|
|
18
31
|
return {
|
|
19
32
|
apiUrl: config.apiUrl.replace(/\/$/, ""),
|
|
20
33
|
socketUrl,
|
|
34
|
+
socketOrigin,
|
|
35
|
+
socketPath,
|
|
21
36
|
authToken: config.authToken,
|
|
22
37
|
authProvider: config.authProvider,
|
|
23
38
|
userId: config.userId,
|
|
24
39
|
tenantId: config.tenantId,
|
|
40
|
+
avatar: config.avatar,
|
|
25
41
|
encryptionMode: config.encryptionMode ?? "none",
|
|
26
42
|
upload: {
|
|
27
43
|
maxFileSizeMB: limits,
|
|
@@ -31,7 +47,10 @@ function resolveConfig(config) {
|
|
|
31
47
|
onProgress: config.upload?.onProgress
|
|
32
48
|
},
|
|
33
49
|
platformUploadFn: config.platformUploadFn,
|
|
34
|
-
persistStorage: config.persistStorage
|
|
50
|
+
persistStorage: config.persistStorage,
|
|
51
|
+
messagePageSize: config.messagePageSize ?? 40,
|
|
52
|
+
starredMessagePageSize: config.starredMessagePageSize ?? 30,
|
|
53
|
+
searchPageSize: config.searchPageSize ?? 50
|
|
35
54
|
};
|
|
36
55
|
}
|
|
37
56
|
|
|
@@ -39,9 +58,11 @@ function resolveConfig(config) {
|
|
|
39
58
|
import axios from "axios";
|
|
40
59
|
var _tokenStore = null;
|
|
41
60
|
var _config = null;
|
|
61
|
+
var _avatarSent = false;
|
|
42
62
|
function initApiClient(config, tokenStore) {
|
|
43
63
|
_config = config;
|
|
44
64
|
_tokenStore = tokenStore;
|
|
65
|
+
_avatarSent = false;
|
|
45
66
|
const client = axios.create({
|
|
46
67
|
baseURL: config.apiUrl,
|
|
47
68
|
headers: { "Content-Type": "application/json" }
|
|
@@ -51,6 +72,11 @@ function initApiClient(config, tokenStore) {
|
|
|
51
72
|
if (token) req.headers["Authorization"] = `Bearer ${token}`;
|
|
52
73
|
if (_config?.userId) req.headers["x-user-id"] = _config.userId;
|
|
53
74
|
if (_config?.tenantId) req.headers["X-Tenant-ID"] = _config.tenantId;
|
|
75
|
+
if (token && !_avatarSent && _config?.avatar) {
|
|
76
|
+
if (_config.avatar.base64) req.headers["x-avatar-base64"] = _config.avatar.base64;
|
|
77
|
+
else if (_config.avatar.url) req.headers["x-avatar-url"] = _config.avatar.url;
|
|
78
|
+
_avatarSent = true;
|
|
79
|
+
}
|
|
54
80
|
return req;
|
|
55
81
|
});
|
|
56
82
|
let isRefreshing = false;
|
|
@@ -135,15 +161,37 @@ var authApi = {
|
|
|
135
161
|
async getMe() {
|
|
136
162
|
const { data } = await getApiClient().get("/users/me");
|
|
137
163
|
return data;
|
|
164
|
+
},
|
|
165
|
+
// Upload a new avatar file (builtin mode — multipart)
|
|
166
|
+
async uploadAvatar(file, mimeType) {
|
|
167
|
+
const form = new FormData();
|
|
168
|
+
form.append("avatar", file instanceof File ? file : new File([file], "avatar.jpg", { type: mimeType ?? "image/jpeg" }));
|
|
169
|
+
const { data } = await getApiClient().put("/users/me/avatar", form, {
|
|
170
|
+
headers: { "Content-Type": "multipart/form-data" }
|
|
171
|
+
});
|
|
172
|
+
return data;
|
|
173
|
+
},
|
|
174
|
+
// Sync avatar from URL or base64 (non-builtin modes — or post-init update)
|
|
175
|
+
async syncAvatar(source) {
|
|
176
|
+
const headers = {};
|
|
177
|
+
if (source.base64) headers["x-avatar-base64"] = source.base64;
|
|
178
|
+
else if (source.url) headers["x-avatar-url"] = source.url;
|
|
179
|
+
const { data } = await getApiClient().post("/users/me/avatar/sync", {}, { headers });
|
|
180
|
+
return data;
|
|
138
181
|
}
|
|
139
182
|
};
|
|
140
183
|
|
|
141
184
|
// src/api/messages.ts
|
|
142
185
|
var messagesApi = {
|
|
143
186
|
async list(conversationId, params = {}) {
|
|
187
|
+
const { cursor, direction, ...rest } = params;
|
|
188
|
+
const serverParams = { ...rest };
|
|
189
|
+
if (cursor) {
|
|
190
|
+
serverParams[direction === "after" ? "after" : "before"] = cursor;
|
|
191
|
+
}
|
|
144
192
|
const { data } = await getApiClient().get(
|
|
145
193
|
`/conversations/${conversationId}/messages`,
|
|
146
|
-
{ params }
|
|
194
|
+
{ params: serverParams }
|
|
147
195
|
);
|
|
148
196
|
return data;
|
|
149
197
|
},
|
|
@@ -165,6 +213,9 @@ var messagesApi = {
|
|
|
165
213
|
async delete(messageId) {
|
|
166
214
|
await getApiClient().delete(`/messages/${messageId}`);
|
|
167
215
|
},
|
|
216
|
+
async deleteForMe(messageId) {
|
|
217
|
+
await getApiClient().delete(`/messages/${messageId}/for-me`);
|
|
218
|
+
},
|
|
168
219
|
async addReaction(messageId, emoji) {
|
|
169
220
|
const { data } = await getApiClient().post(`/messages/${messageId}/reactions`, { emoji });
|
|
170
221
|
return data;
|
|
@@ -189,6 +240,12 @@ var messagesApi = {
|
|
|
189
240
|
const { data } = await getApiClient().get("/messages/search", { params });
|
|
190
241
|
return data;
|
|
191
242
|
},
|
|
243
|
+
async getLastRead(conversationId) {
|
|
244
|
+
const { data } = await getApiClient().get(
|
|
245
|
+
`/conversations/${conversationId}/read-receipt`
|
|
246
|
+
);
|
|
247
|
+
return data;
|
|
248
|
+
},
|
|
192
249
|
async markAsRead(conversationId, messageId) {
|
|
193
250
|
await getApiClient().post(`/conversations/${conversationId}/read`, messageId ? { messageId } : {});
|
|
194
251
|
},
|
|
@@ -213,6 +270,7 @@ function normalizeParticipant(p) {
|
|
|
213
270
|
userId: p.userId,
|
|
214
271
|
role: p.role,
|
|
215
272
|
joinedAt: p.joinedAt,
|
|
273
|
+
isActive: p.isActive,
|
|
216
274
|
user: hasUserDetails ? {
|
|
217
275
|
id: p.userId,
|
|
218
276
|
tenantId: "",
|
|
@@ -239,7 +297,7 @@ function normalizeLastMessage(lastMsg) {
|
|
|
239
297
|
text: lastMsg.contentPreview
|
|
240
298
|
},
|
|
241
299
|
reactions: [],
|
|
242
|
-
status: "sent",
|
|
300
|
+
status: lastMsg.status ?? "sent",
|
|
243
301
|
isEdited: false,
|
|
244
302
|
sentAt: lastMsg.sentAt ?? "",
|
|
245
303
|
createdAt: lastMsg.sentAt ?? ""
|
|
@@ -316,9 +374,25 @@ var conversationsApi = {
|
|
|
316
374
|
const { data } = await getApiClient().get(`/conversations/${conversationId}/participants`);
|
|
317
375
|
return data;
|
|
318
376
|
},
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
377
|
+
/**
|
|
378
|
+
* Get unread message count for a single conversation.
|
|
379
|
+
* Use this after app foreground or socket reconnect to refresh a specific count.
|
|
380
|
+
*/
|
|
381
|
+
async getUnreadCount(conversationId) {
|
|
382
|
+
const { data } = await getApiClient().get(
|
|
383
|
+
`/conversations/${conversationId}/unread`
|
|
384
|
+
);
|
|
385
|
+
return data;
|
|
386
|
+
},
|
|
387
|
+
/**
|
|
388
|
+
* Get total unread count across all conversations + per-conversation breakdown.
|
|
389
|
+
* Use on app cold start, foreground resume, or after socket reconnect.
|
|
390
|
+
* The socket keeps counts live while connected — this is the source of truth
|
|
391
|
+
* when the socket was down.
|
|
392
|
+
*/
|
|
393
|
+
async getUnreadSummary() {
|
|
394
|
+
const { data } = await getApiClient().get("/conversations/unread");
|
|
395
|
+
return data;
|
|
322
396
|
}
|
|
323
397
|
};
|
|
324
398
|
|
|
@@ -423,11 +497,52 @@ var devicesApi = {
|
|
|
423
497
|
}
|
|
424
498
|
};
|
|
425
499
|
|
|
500
|
+
// src/api/users.ts
|
|
501
|
+
var usersApi = {
|
|
502
|
+
async list(params = {}) {
|
|
503
|
+
const { data } = await getApiClient().get("/users", { params });
|
|
504
|
+
return data;
|
|
505
|
+
},
|
|
506
|
+
async getById(userId) {
|
|
507
|
+
const { data } = await getApiClient().get(`/users/${userId}`);
|
|
508
|
+
return data;
|
|
509
|
+
},
|
|
510
|
+
async getLastSeen(userId) {
|
|
511
|
+
const { data } = await getApiClient().get(`/users/${userId}`);
|
|
512
|
+
return { lastSeenAt: data.lastSeenAt ?? null };
|
|
513
|
+
},
|
|
514
|
+
/**
|
|
515
|
+
* Update notification preferences for the current user.
|
|
516
|
+
* Partial update — only send fields you want to change.
|
|
517
|
+
* A prefs record is automatically created with defaults when a device
|
|
518
|
+
* token is first registered, so this never fails with "not found".
|
|
519
|
+
*/
|
|
520
|
+
async updatePreferences(prefs) {
|
|
521
|
+
const { data } = await getApiClient().put("/users/me/preferences", prefs);
|
|
522
|
+
return data;
|
|
523
|
+
},
|
|
524
|
+
/**
|
|
525
|
+
* Fetch current notification preferences for the current user.
|
|
526
|
+
* Returns null if no prefs record exists yet (all defaults apply).
|
|
527
|
+
*/
|
|
528
|
+
async getPreferences() {
|
|
529
|
+
try {
|
|
530
|
+
const { data } = await getApiClient().get("/users/me/preferences");
|
|
531
|
+
return data;
|
|
532
|
+
} catch {
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
|
|
426
538
|
// src/socket/socket.ts
|
|
427
539
|
import { io } from "socket.io-client";
|
|
428
540
|
var _socket = null;
|
|
429
541
|
var _status = "disconnected";
|
|
430
542
|
var _statusListeners = /* @__PURE__ */ new Set();
|
|
543
|
+
var _getToken = null;
|
|
544
|
+
var _userId;
|
|
545
|
+
var _tenantId;
|
|
431
546
|
function setStatus(s) {
|
|
432
547
|
_status = s;
|
|
433
548
|
_statusListeners.forEach((l) => l(s));
|
|
@@ -447,10 +562,23 @@ function onSocketStatus(listener) {
|
|
|
447
562
|
return () => _statusListeners.delete(listener);
|
|
448
563
|
}
|
|
449
564
|
async function connectSocket(config, getToken) {
|
|
450
|
-
if (_socket
|
|
565
|
+
if (_socket && !_socket.disconnected) return _socket;
|
|
566
|
+
_getToken = getToken;
|
|
567
|
+
_userId = config.userId;
|
|
568
|
+
_tenantId = config.tenantId;
|
|
451
569
|
const token = getToken();
|
|
452
|
-
_socket = io(`${config.
|
|
453
|
-
auth: {
|
|
570
|
+
_socket = io(`${config.socketOrigin}/chat`, {
|
|
571
|
+
auth: {
|
|
572
|
+
token: token ? `Bearer ${token}` : "",
|
|
573
|
+
...config.userId && { userId: config.userId },
|
|
574
|
+
...config.tenantId && { tenantId: config.tenantId },
|
|
575
|
+
...config.avatar?.url && { avatarUrl: config.avatar.url },
|
|
576
|
+
...config.avatar?.base64 && { avatarBase64: config.avatar.base64 }
|
|
577
|
+
},
|
|
578
|
+
// path must match SOCKET_IO_PATH on the server (default '/socket.io').
|
|
579
|
+
// Set socketPath in SDK config when the server is behind a reverse proxy
|
|
580
|
+
// that adds a path prefix (e.g. '/chat-api/socket.io' for UAT).
|
|
581
|
+
path: config.socketPath,
|
|
454
582
|
transports: ["websocket", "polling"],
|
|
455
583
|
reconnection: true,
|
|
456
584
|
reconnectionAttempts: 10,
|
|
@@ -461,9 +589,30 @@ async function connectSocket(config, getToken) {
|
|
|
461
589
|
setStatus("connecting");
|
|
462
590
|
_socket.on("connect", () => setStatus("connected"));
|
|
463
591
|
_socket.on("disconnect", () => setStatus("disconnected"));
|
|
464
|
-
_socket.on("connect_error", () =>
|
|
592
|
+
_socket.on("connect_error", (err) => {
|
|
593
|
+
console.error("[AntzChat] Socket connect_error:", err?.message, err?.data);
|
|
594
|
+
setStatus("error");
|
|
595
|
+
});
|
|
465
596
|
_socket.on("reconnecting", () => setStatus("reconnecting"));
|
|
466
597
|
_socket.on("reconnect", () => setStatus("connected"));
|
|
598
|
+
_socket.on("read_receipt", (event) => {
|
|
599
|
+
import("./chat.store-PY3YVYGN.js").then(({ useChatStore: useChatStore2 }) => {
|
|
600
|
+
useChatStore2.getState().setLastRead(event.conversationId, event.messageId, event.readAt);
|
|
601
|
+
});
|
|
602
|
+
});
|
|
603
|
+
_socket.on("user_online", (event) => {
|
|
604
|
+
import("./chat.store-PY3YVYGN.js").then(({ useChatStore: useChatStore2 }) => {
|
|
605
|
+
const store = useChatStore2.getState();
|
|
606
|
+
store.setUserOnline(event.userId);
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
_socket.on("user_offline", (event) => {
|
|
610
|
+
import("./chat.store-PY3YVYGN.js").then(({ useChatStore: useChatStore2 }) => {
|
|
611
|
+
const store = useChatStore2.getState();
|
|
612
|
+
store.setUserOffline(event.userId);
|
|
613
|
+
if (event.lastSeenAt) store.setLastSeen(event.userId, event.lastSeenAt);
|
|
614
|
+
});
|
|
615
|
+
});
|
|
467
616
|
return _socket;
|
|
468
617
|
}
|
|
469
618
|
function disconnectSocket() {
|
|
@@ -472,13 +621,31 @@ function disconnectSocket() {
|
|
|
472
621
|
_socket = null;
|
|
473
622
|
setStatus("disconnected");
|
|
474
623
|
}
|
|
624
|
+
_getToken = null;
|
|
625
|
+
_userId = void 0;
|
|
626
|
+
_tenantId = void 0;
|
|
475
627
|
}
|
|
476
|
-
function reconnectSocket(token) {
|
|
628
|
+
function reconnectSocket(token, userId, tenantId) {
|
|
477
629
|
if (_socket) {
|
|
478
|
-
_socket.auth = {
|
|
630
|
+
_socket.auth = {
|
|
631
|
+
token: `Bearer ${token}`,
|
|
632
|
+
...userId && { userId },
|
|
633
|
+
...tenantId && { tenantId }
|
|
634
|
+
};
|
|
479
635
|
_socket.connect();
|
|
480
636
|
}
|
|
481
637
|
}
|
|
638
|
+
function refreshSocketAuth() {
|
|
639
|
+
if (!_socket || !_getToken) return false;
|
|
640
|
+
const fresh = _getToken();
|
|
641
|
+
if (!fresh) return false;
|
|
642
|
+
_socket.auth = {
|
|
643
|
+
token: `Bearer ${fresh}`,
|
|
644
|
+
..._userId && { userId: _userId },
|
|
645
|
+
..._tenantId && { tenantId: _tenantId }
|
|
646
|
+
};
|
|
647
|
+
return true;
|
|
648
|
+
}
|
|
482
649
|
|
|
483
650
|
// src/socket/emitters.ts
|
|
484
651
|
var ACK_TIMEOUT = 5e3;
|
|
@@ -514,6 +681,9 @@ var socketEmit = {
|
|
|
514
681
|
deleteMessage(messageId) {
|
|
515
682
|
return withAck("delete_message", { messageId });
|
|
516
683
|
},
|
|
684
|
+
deleteMessageForMe(messageId) {
|
|
685
|
+
return withAck("delete_message_for_me", { messageId });
|
|
686
|
+
},
|
|
517
687
|
addReaction(messageId, emoji) {
|
|
518
688
|
return withAck("add_reaction", { messageId, emoji });
|
|
519
689
|
},
|
|
@@ -643,48 +813,6 @@ function resetAuthStore() {
|
|
|
643
813
|
_authStore = null;
|
|
644
814
|
}
|
|
645
815
|
|
|
646
|
-
// src/stores/chat.store.ts
|
|
647
|
-
import { create as create2 } from "zustand";
|
|
648
|
-
var useChatStore = create2((set) => ({
|
|
649
|
-
activeConversationId: null,
|
|
650
|
-
pendingTarget: null,
|
|
651
|
-
typingUsers: {},
|
|
652
|
-
onlineUsers: [],
|
|
653
|
-
replyingTo: null,
|
|
654
|
-
editingMessage: null,
|
|
655
|
-
isSidebarOpen: true,
|
|
656
|
-
isGroupInfoOpen: false,
|
|
657
|
-
isStarredPanelOpen: false,
|
|
658
|
-
setActiveConversation: (id) => set({ activeConversationId: id, replyingTo: null, editingMessage: null }),
|
|
659
|
-
setPendingTarget: (target) => set({ pendingTarget: target }),
|
|
660
|
-
addTypingUser: (conversationId, user) => set((state) => {
|
|
661
|
-
const existing = state.typingUsers[conversationId] ?? [];
|
|
662
|
-
const deduped = existing.filter((u) => u.userId !== user.userId);
|
|
663
|
-
return { typingUsers: { ...state.typingUsers, [conversationId]: [...deduped, user] } };
|
|
664
|
-
}),
|
|
665
|
-
removeTypingUser: (conversationId, userId) => set((state) => ({
|
|
666
|
-
typingUsers: {
|
|
667
|
-
...state.typingUsers,
|
|
668
|
-
[conversationId]: (state.typingUsers[conversationId] ?? []).filter(
|
|
669
|
-
(u) => u.userId !== userId
|
|
670
|
-
)
|
|
671
|
-
}
|
|
672
|
-
})),
|
|
673
|
-
setUserOnline: (userId) => set((state) => ({
|
|
674
|
-
onlineUsers: state.onlineUsers.includes(userId) ? state.onlineUsers : [...state.onlineUsers, userId]
|
|
675
|
-
})),
|
|
676
|
-
setUserOffline: (userId) => set((state) => ({ onlineUsers: state.onlineUsers.filter((id) => id !== userId) })),
|
|
677
|
-
setOnlineUsers: (userIds) => set({ onlineUsers: userIds }),
|
|
678
|
-
setReplyingTo: (message) => set({ replyingTo: message, editingMessage: null }),
|
|
679
|
-
setEditingMessage: (message) => set({ editingMessage: message, replyingTo: null }),
|
|
680
|
-
toggleSidebar: () => set((state) => ({ isSidebarOpen: !state.isSidebarOpen })),
|
|
681
|
-
setSidebarOpen: (open) => set({ isSidebarOpen: open }),
|
|
682
|
-
toggleGroupInfo: () => set((state) => ({ isGroupInfoOpen: !state.isGroupInfoOpen })),
|
|
683
|
-
setGroupInfoOpen: (open) => set({ isGroupInfoOpen: open }),
|
|
684
|
-
toggleStarredPanel: () => set((state) => ({ isStarredPanelOpen: !state.isStarredPanelOpen })),
|
|
685
|
-
setStarredPanelOpen: (open) => set({ isStarredPanelOpen: open })
|
|
686
|
-
}));
|
|
687
|
-
|
|
688
816
|
// src/client-facade.ts
|
|
689
817
|
var AntzChatClient = class {
|
|
690
818
|
constructor(rawConfig) {
|
|
@@ -692,6 +820,7 @@ var AntzChatClient = class {
|
|
|
692
820
|
this.messages = messagesApi;
|
|
693
821
|
this.conversations = conversationsApi;
|
|
694
822
|
this.storage = storageApi;
|
|
823
|
+
this.users = usersApi;
|
|
695
824
|
this.socket = {
|
|
696
825
|
emit: socketEmit,
|
|
697
826
|
on: (event, handler) => getSocket().on(event, handler),
|
|
@@ -738,6 +867,7 @@ export {
|
|
|
738
867
|
normalizeConversation,
|
|
739
868
|
onSocketStatus,
|
|
740
869
|
reconnectSocket,
|
|
870
|
+
refreshSocketAuth,
|
|
741
871
|
resetAuthStore,
|
|
742
872
|
resolveConfig,
|
|
743
873
|
setApiClientInstance,
|
|
@@ -745,6 +875,7 @@ export {
|
|
|
745
875
|
storageApi,
|
|
746
876
|
tryGetSocket,
|
|
747
877
|
uploadBatch,
|
|
748
|
-
useChatStore
|
|
878
|
+
useChatStore,
|
|
879
|
+
usersApi
|
|
749
880
|
};
|
|
750
881
|
//# sourceMappingURL=index.js.map
|