@antzsoft/chat-core 1.0.3 → 1.0.5
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 +212 -22
- package/dist/index.cjs +18 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -3
- package/dist/index.d.ts +8 -3
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -1
- package/docs/integration-guide.html +589 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -614,6 +614,13 @@ class AntzChatClient {
|
|
|
614
614
|
* to the configured platformUploadFn, and confirms each upload with the server.
|
|
615
615
|
*/
|
|
616
616
|
uploadFiles(files: UploadableFile[], conversationId?: string): Promise<BatchUploadResult>;
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Upload or replace the group icon (admin only).
|
|
620
|
+
* Internally calls uploadFiles() to upload the file, then sets the icon on the conversation.
|
|
621
|
+
* Same presigned URL pipeline as message attachments — platformUploadFn is handled automatically.
|
|
622
|
+
*/
|
|
623
|
+
uploadIcon(conversationId: string, file: UploadableFile): Promise<Conversation>;
|
|
617
624
|
}
|
|
618
625
|
```
|
|
619
626
|
|
|
@@ -773,6 +780,56 @@ await messagesApi.send('conv-abc', {
|
|
|
773
780
|
const results = await messagesApi.search({ query: 'deployment', conversationId: 'conv-abc' });
|
|
774
781
|
```
|
|
775
782
|
|
|
783
|
+
#### Jump to first unread message
|
|
784
|
+
|
|
785
|
+
Use `direction: 'after'` with the user's `lastReadMessageId` as the cursor to fetch only the unread messages. This powers a scroll-to-first-unread experience with an "↑ Unread messages" divider.
|
|
786
|
+
|
|
787
|
+
```typescript
|
|
788
|
+
import { messagesApi, useChatStore } from '@antzsoft/chat-core';
|
|
789
|
+
|
|
790
|
+
// 1. Get the last-read pointer and seed the store
|
|
791
|
+
const { lastReadMessageId, lastReadAt } = await messagesApi.getLastRead(conversationId);
|
|
792
|
+
|
|
793
|
+
if (lastReadMessageId && lastReadAt) {
|
|
794
|
+
useChatStore.getState().setLastRead(conversationId, lastReadMessageId, lastReadAt);
|
|
795
|
+
|
|
796
|
+
// 2. Fetch all messages AFTER the last-read message — these are unread
|
|
797
|
+
const { data: unreadMessages, meta } = await messagesApi.list(conversationId, {
|
|
798
|
+
cursor: lastReadMessageId,
|
|
799
|
+
direction: 'after',
|
|
800
|
+
limit: 50,
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
// unreadMessages[0] is the first unread — scroll the list to this item
|
|
804
|
+
// meta.hasMore = true means there are more than 50 unread messages
|
|
805
|
+
if (unreadMessages.length > 0) {
|
|
806
|
+
scrollToMessage(unreadMessages[0].id);
|
|
807
|
+
}
|
|
808
|
+
} else {
|
|
809
|
+
// No prior read state — load latest messages normally
|
|
810
|
+
const { data: messages } = await messagesApi.list(conversationId, { limit: 30 });
|
|
811
|
+
}
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
Render the divider in your message list by checking `useChatStore.lastRead[conversationId]`:
|
|
815
|
+
|
|
816
|
+
```typescript
|
|
817
|
+
const lastRead = useChatStore((s) => s.lastRead[conversationId]);
|
|
818
|
+
|
|
819
|
+
function MessageRow({ message, prevMessage }) {
|
|
820
|
+
// Insert divider between the last-read message and the next one
|
|
821
|
+
const isFirstUnread = lastRead && prevMessage?.id === lastRead.messageId;
|
|
822
|
+
return (
|
|
823
|
+
<>
|
|
824
|
+
{isFirstUnread && <UnreadDivider />}
|
|
825
|
+
<MessageBubble message={message} />
|
|
826
|
+
</>
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
After the user reads the messages, call `socketEmit.markRead(conversationId)` (see [Read Receipts](#chat-store-usechatstore)) to update the server and broadcast the receipt to other participants.
|
|
832
|
+
|
|
776
833
|
---
|
|
777
834
|
|
|
778
835
|
### Conversations API (`conversationsApi`)
|
|
@@ -787,7 +844,8 @@ import { conversationsApi } from '@antzsoft/chat-core';
|
|
|
787
844
|
| `get` | `(conversationId: string) => Promise<Conversation>` | Fetch a single conversation. |
|
|
788
845
|
| `createGroup` | `(data: CreateGroupData) => Promise<Conversation>` | Create a group conversation. |
|
|
789
846
|
| `createDirect` | `(data: CreateDirectData) => Promise<Conversation>` | Start or retrieve a direct conversation with another user. |
|
|
790
|
-
| `update` | `(conversationId: string, data: UpdateConversationData) => Promise<Conversation>` | Update group name
|
|
847
|
+
| `update` | `(conversationId: string, data: UpdateConversationData) => Promise<Conversation>` | Update group name or description. |
|
|
848
|
+
| `uploadIcon` | `(conversationId: string, fileId: string) => Promise<Conversation>` | Set the group icon from an already-uploaded file (admin only). Call `client.uploadFiles()` first to get the `fileId`, then pass it here. Server copies `storageKey` into `conversation.iconMeta`, deletes the `chat_files` record, and returns the conversation with a fresh `iconUrl`. |
|
|
791
849
|
| `delete` | `(conversationId: string) => Promise<void>` | Delete a conversation (admin only). |
|
|
792
850
|
| `addParticipants` | `(conversationId: string, userIds: string[]) => Promise<Conversation>` | Add one or more participants. |
|
|
793
851
|
| `removeParticipant` | `(conversationId: string, userId: string) => Promise<Conversation>` | Remove a participant. |
|
|
@@ -829,7 +887,6 @@ interface ConversationListParams {
|
|
|
829
887
|
interface CreateGroupData {
|
|
830
888
|
name: string;
|
|
831
889
|
description?: string;
|
|
832
|
-
icon?: string; // Emoji or short string used as the group avatar
|
|
833
890
|
participantIds: string[];
|
|
834
891
|
}
|
|
835
892
|
|
|
@@ -840,7 +897,6 @@ interface CreateDirectData {
|
|
|
840
897
|
interface UpdateConversationData {
|
|
841
898
|
name?: string;
|
|
842
899
|
description?: string;
|
|
843
|
-
icon?: string;
|
|
844
900
|
}
|
|
845
901
|
```
|
|
846
902
|
|
|
@@ -884,6 +940,37 @@ const group = await conversationsApi.createGroup({
|
|
|
884
940
|
// Start a DM
|
|
885
941
|
const dm = await conversationsApi.createDirect({ userId: 'user-b' });
|
|
886
942
|
|
|
943
|
+
// ── Group icon ────────────────────────────────────────────────────────────────
|
|
944
|
+
// Upload or replace the group icon (admin only).
|
|
945
|
+
// Uses the SAME presigned URL pipeline as message attachments — platformUploadFn
|
|
946
|
+
// is handled automatically from config, you never pass it explicitly.
|
|
947
|
+
// Always call AFTER createGroup — the group must exist first.
|
|
948
|
+
|
|
949
|
+
// Using AntzChatClient (headless) — one call, same as client.uploadFiles()
|
|
950
|
+
const updated = await client.uploadIcon(group.id, {
|
|
951
|
+
uri: 'blob:http://...', // URL.createObjectURL(file) on web, file URI on RN
|
|
952
|
+
name: 'icon.jpg',
|
|
953
|
+
type: 'image/jpeg',
|
|
954
|
+
size: file.size,
|
|
955
|
+
});
|
|
956
|
+
console.log(updated.iconUrl); // fresh signed URL, regenerated on every response
|
|
957
|
+
|
|
958
|
+
// What client.uploadIcon() does internally (same as attachment upload):
|
|
959
|
+
// 1. uploadFiles([file], conversationId)
|
|
960
|
+
// → POST /storage/presigned-url (creates temp chat_files record)
|
|
961
|
+
// → platformUploadFn uploads binary directly to S3/Azure/local
|
|
962
|
+
// → POST /storage/confirm/:fileId (marks chat_files active)
|
|
963
|
+
// 2. conversationsApi.uploadIcon(conversationId, fileId)
|
|
964
|
+
// → PUT /conversations/:id/icon { fileId }
|
|
965
|
+
// Server: validateAdmin() → copy storageKey into conversation.iconMeta
|
|
966
|
+
// → delete chat_files record (it was only a transport vehicle)
|
|
967
|
+
// → return conversation with fresh iconUrl
|
|
968
|
+
|
|
969
|
+
// iconUrl behaviour:
|
|
970
|
+
// - Never stored in DB — regenerated fresh from iconMeta.storageKey on every response
|
|
971
|
+
// - Previous icon deleted from storage automatically on replace
|
|
972
|
+
// - Non-admins get 403 Forbidden
|
|
973
|
+
|
|
887
974
|
// Add members
|
|
888
975
|
await conversationsApi.addParticipants(group.id, ['user-d', 'user-e']);
|
|
889
976
|
|
|
@@ -935,6 +1022,48 @@ socket?.on('unread_count_changed', ({ conversationId, unreadCount }) => {
|
|
|
935
1022
|
|
|
936
1023
|
Both events are emitted to the user's **private room** (`user:{tenantId}:{userId}`) so every connected device of the same user receives them simultaneously. This is how reading on your phone automatically clears the badge on your browser tab.
|
|
937
1024
|
|
|
1025
|
+
#### Chat icon badge — complete pattern (headless / custom UI)
|
|
1026
|
+
|
|
1027
|
+
When building a custom UI using `chat-core` directly (no web/RN SDK), maintain your own unread state and update it on socket events:
|
|
1028
|
+
|
|
1029
|
+
```typescript
|
|
1030
|
+
import { conversationsApi, tryGetSocket } from '@antzsoft/chat-core';
|
|
1031
|
+
|
|
1032
|
+
// 1. Cold start — fetch accurate counts from DB
|
|
1033
|
+
const summary = await conversationsApi.getUnreadSummary();
|
|
1034
|
+
let totalUnread = summary.totalUnread;
|
|
1035
|
+
updateBadge(totalUnread); // your UI function
|
|
1036
|
+
|
|
1037
|
+
// 2. While socket is connected — update on every event
|
|
1038
|
+
const socket = tryGetSocket();
|
|
1039
|
+
|
|
1040
|
+
socket?.on('conversation_updated', ({ conversationId, unreadCount }) => {
|
|
1041
|
+
// Server sends accurate DB count per conversation — recalculate total
|
|
1042
|
+
summary.byConversation = summary.byConversation
|
|
1043
|
+
.filter(c => c.conversationId !== conversationId)
|
|
1044
|
+
.concat({ conversationId, unreadCount });
|
|
1045
|
+
totalUnread = summary.byConversation.reduce((s, c) => s + c.unreadCount, 0);
|
|
1046
|
+
updateBadge(totalUnread);
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
socket?.on('unread_count_changed', ({ conversationId }) => {
|
|
1050
|
+
// User read a conversation — clear it from the map
|
|
1051
|
+
summary.byConversation = summary.byConversation
|
|
1052
|
+
.filter(c => c.conversationId !== conversationId);
|
|
1053
|
+
totalUnread = summary.byConversation.reduce((s, c) => s + c.unreadCount, 0);
|
|
1054
|
+
updateBadge(totalUnread);
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1057
|
+
// 3. On foreground / socket reconnect — resync from DB
|
|
1058
|
+
async function onForeground() {
|
|
1059
|
+
const fresh = await conversationsApi.getUnreadSummary();
|
|
1060
|
+
totalUnread = fresh.totalUnread;
|
|
1061
|
+
updateBadge(totalUnread);
|
|
1062
|
+
}
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
> **Using `@antzsoft/chat-web-sdk` or `@antzsoft/chat-rn-sdk`?** You don't need any of this — `useConversations()` handles socket subscriptions internally. Just sum `conversations.reduce((s, c) => s + (c.unreadCount ?? 0), 0)` and it updates automatically.
|
|
1066
|
+
|
|
938
1067
|
---
|
|
939
1068
|
|
|
940
1069
|
### Storage API (`storageApi` and `uploadBatch`)
|
|
@@ -1306,6 +1435,60 @@ unsubscribe();
|
|
|
1306
1435
|
disconnectSocket();
|
|
1307
1436
|
```
|
|
1308
1437
|
|
|
1438
|
+
#### Room membership — auto-join and `joinRoom`
|
|
1439
|
+
|
|
1440
|
+
**On every socket connection, the server automatically joins the user into all their existing conversation rooms.** No client action is needed. The moment `client.connect()` resolves, the user is already subscribed to real-time events for every conversation they belong to.
|
|
1441
|
+
|
|
1442
|
+
This means:
|
|
1443
|
+
- You do **not** need to call `joinRoom` when opening a chat screen.
|
|
1444
|
+
- `new_message`, `typing_indicator`, `read_receipt`, and all other conversation events are received for all conversations from the moment the socket connects.
|
|
1445
|
+
- Calling `joinRoom` on an already-joined room is safe (idempotent) but causes an unnecessary DB access check — avoid it in hot paths.
|
|
1446
|
+
|
|
1447
|
+
**When to call `joinRoom`:**
|
|
1448
|
+
|
|
1449
|
+
There is only one real use case — when the **user is added to a conversation while their socket is already connected** (either someone created a new group and added them, or they were added to an existing group). The server does not auto-join the user's socket to the new room — it only emits `conversation_created` to the user's personal room. The client must call `joinRoom` in response, otherwise the socket will not receive any `new_message`, `typing_indicator`, or other room events for that conversation.
|
|
1450
|
+
|
|
1451
|
+
```typescript
|
|
1452
|
+
client.socket.on('conversation_created', (conv) => {
|
|
1453
|
+
// Fires when you're added to a new or existing conversation at runtime
|
|
1454
|
+
client.socket.emit.joinRoom(conv.id);
|
|
1455
|
+
});
|
|
1456
|
+
```
|
|
1457
|
+
|
|
1458
|
+
**Where to add the `new_message` listener:**
|
|
1459
|
+
|
|
1460
|
+
Register it **once at app root level**, right after `client.connect()`. A single listener handles messages from all conversations — use `message.conversationId` to route to the right place. Never add it inside a screen or component.
|
|
1461
|
+
|
|
1462
|
+
```typescript
|
|
1463
|
+
await client.connect();
|
|
1464
|
+
|
|
1465
|
+
client.socket.on('new_message', (event: NewMessageEvent) => {
|
|
1466
|
+
const { message } = event;
|
|
1467
|
+
|
|
1468
|
+
// Update the open chat view if this conversation is active
|
|
1469
|
+
if (message.conversationId === activeConversationId) {
|
|
1470
|
+
appendMessageToView(message);
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
// Always update the conversation list (last message + unread badge)
|
|
1474
|
+
updateConversationList(message.conversationId, {
|
|
1475
|
+
lastMessage: message,
|
|
1476
|
+
// Don't increment unread if the user sent it or is currently viewing that chat
|
|
1477
|
+
incrementUnread: message.conversationId !== activeConversationId
|
|
1478
|
+
&& message.senderId !== currentUserId,
|
|
1479
|
+
});
|
|
1480
|
+
});
|
|
1481
|
+
|
|
1482
|
+
// Only case where joinRoom is needed — new conversation created at runtime
|
|
1483
|
+
client.socket.on('conversation_created', (conv) => {
|
|
1484
|
+
client.socket.emit.joinRoom(conv.id);
|
|
1485
|
+
});
|
|
1486
|
+
```
|
|
1487
|
+
|
|
1488
|
+
**`leaveRoom`** is only needed if you want to intentionally stop receiving events for a room the user is still a member of (e.g. archiving client-side). It is not needed when navigating away from a screen.
|
|
1489
|
+
|
|
1490
|
+
---
|
|
1491
|
+
|
|
1309
1492
|
#### `socketEmit` — outbound events
|
|
1310
1493
|
|
|
1311
1494
|
```typescript
|
|
@@ -1320,7 +1503,7 @@ All emit methods that have server responses use a 5-second ack timeout and retur
|
|
|
1320
1503
|
| `leaveRoom` | `(conversationId: string) => void` | Leave a conversation room. Fire-and-forget. |
|
|
1321
1504
|
| `sendMessage` | `(payload: SendMessagePayload) => Promise<unknown>` | Send a message. Ack-based. |
|
|
1322
1505
|
| `updateMessage` | `(messageId: string, text: string) => Promise<unknown>` | Edit a message. Ack-based. |
|
|
1323
|
-
| `deleteMessage` | `(messageId: string) => Promise<unknown>` | Delete a message for everyone
|
|
1506
|
+
| `deleteMessage` | `(messageId: string) => Promise<unknown>` | Delete a message for everyone. Own messages must be within the delete window (default 30 min); group admins can delete any message with no time restriction. Ack-based. |
|
|
1324
1507
|
| `deleteMessageForMe` | `(messageId: string) => Promise<unknown>` | Hide a message for the current user only. Ack-based. |
|
|
1325
1508
|
| `addReaction` | `(messageId: string, emoji: string) => Promise<unknown>` | Add a reaction. Ack-based. |
|
|
1326
1509
|
| `removeReaction` | `(messageId: string, emoji: string) => Promise<unknown>` | Remove a reaction. Ack-based. |
|
|
@@ -1360,21 +1543,29 @@ console.log('Online:', onlineIds); // ['user-a', 'user-c']
|
|
|
1360
1543
|
|
|
1361
1544
|
Subscribe using `client.socket.on(event, handler)` (headless) or directly on the Socket.IO socket via `getSocket().on(event, handler)`.
|
|
1362
1545
|
|
|
1363
|
-
| Event | Payload type | Description |
|
|
1364
|
-
|
|
1365
|
-
| `new_message` | `NewMessageEvent` |
|
|
1366
|
-
| `message_updated` | `MessageUpdatedEvent` | A message was edited. |
|
|
1367
|
-
| `message_deleted` | `MessageDeletedEvent` | A message was deleted for everyone. |
|
|
1368
|
-
| `message_deleted_for_me` | `{ messageId: string }` | A message was hidden for the current user only (fired only to that user). |
|
|
1369
|
-
| `reaction_updated` | `ReactionUpdatedEvent` | Reactions on a message changed (full reaction array). |
|
|
1370
|
-
| `
|
|
1371
|
-
| `
|
|
1372
|
-
| `
|
|
1373
|
-
| `
|
|
1374
|
-
| `
|
|
1375
|
-
| `
|
|
1376
|
-
| `
|
|
1377
|
-
| `
|
|
1546
|
+
| Event | Payload type | Description | Where to listen / unlisten |
|
|
1547
|
+
|---|---|---|---|
|
|
1548
|
+
| `new_message` | `NewMessageEvent` | New message in any of the user's conversations — all rooms are auto-joined on connect. | **App root** after `client.connect()`. Never remove — must stay alive for the full app session to keep the conversation list and unread badges up to date. |
|
|
1549
|
+
| `message_updated` | `MessageUpdatedEvent` | A message was edited. | **Chat detail screen** — add on mount, remove on unmount. |
|
|
1550
|
+
| `message_deleted` | `MessageDeletedEvent` | A message was deleted for everyone. | **Chat detail screen** — add on mount, remove on unmount. |
|
|
1551
|
+
| `message_deleted_for_me` | `{ messageId: string; conversationId: string }` | A message was hidden for the current user only (fired only to that user's socket). | **Chat detail screen** — add on mount, remove on unmount. |
|
|
1552
|
+
| `reaction_updated` | `ReactionUpdatedEvent` | Reactions on a message changed (full reaction array). | **Chat detail screen** — add on mount, remove on unmount. |
|
|
1553
|
+
| `message_pin_updated` | `{ messageId: string; conversationId: string; isPinned: boolean; pinnedBy?: string; pinnedAt?: string }` | A message was pinned or unpinned. | **Chat detail screen** — add on mount, remove on unmount. |
|
|
1554
|
+
| `typing_indicator` | `TypingIndicatorEvent` | A user started or stopped typing. | **Chat detail screen** — add on mount, remove on unmount. |
|
|
1555
|
+
| `user_online` | `{ userId: string }` | A participant came online — auto-updates `useChatStore.onlineUsers`. | **App root** — drives online indicators everywhere. Keep for full session. |
|
|
1556
|
+
| `user_offline` | `{ userId: string; lastSeen: string }` | A participant went offline — auto-updates `useChatStore.lastSeen`. | **App root** — drives online indicators and last-seen everywhere. Keep for full session. |
|
|
1557
|
+
| `user_status` | `UserStatusEvent` | Global presence broadcast (online/offline/away) to all connected clients. | **App root** — keep for full session. |
|
|
1558
|
+
| `online_users` | `string[]` | Full list of online user IDs — sent on initial room join. | **App root** — keep for full session. |
|
|
1559
|
+
| `read_receipt` | `ReadReceiptEvent` | A user read messages in a conversation. | **Chat detail screen** (to update tick marks) + **app root** (to clear your own unread count when read on another device). |
|
|
1560
|
+
| `unread_count_changed` | `{ conversationId: string; unreadCount: number; userId: string }` | Your unread count changed for a conversation (fired to your personal room on all devices). | **App root / conversation list screen** — keep alive as long as the list is rendered. |
|
|
1561
|
+
| `message_ack` | `MessageAckEvent` | Server confirmation for a message you sent via socket (maps tempId to the real messageId). | **Chat detail screen** — add on mount, remove on unmount. |
|
|
1562
|
+
| `message_delivered` | `{ messageId: string; conversationId: string; deliveredAt: string }` | A single message you sent was delivered to all active recipients. | **Chat detail screen** — add on mount, remove on unmount. |
|
|
1563
|
+
| `messages_delivered` | `MessagesDeliveredEvent` | Batch delivery catch-up — fired when a recipient comes online and your pending messages are delivered. | **Chat detail screen** — add on mount, remove on unmount. |
|
|
1564
|
+
| `conversation_created` | `Conversation` | A new conversation was created (or you were added to one). | **App root** — call `joinRoom` here for the new conversation. Keep for full session. |
|
|
1565
|
+
| `conversation_updated` | `Conversation` | A conversation's last message or metadata changed — use this to update the conversation list. | **App root** — keep for full session. The server emits this for every message across all conversations; a global listener keeps the in-memory conversation list and unread badge always in sync. |
|
|
1566
|
+
| `conversation_deleted` | `{ conversationId: string }` | A conversation was deleted. | **App root / conversation list screen** — remove from state and navigate away if it was open. |
|
|
1567
|
+
| `participant_joined` | `{ conversationId: string; userId: string; displayName: string; addedBy: string }` | A new participant was added to a group conversation. | **Chat detail screen** — add on mount, remove on unmount. |
|
|
1568
|
+
| `participant_left` | `{ conversationId: string; userId: string; displayName: string; removedBy?: string }` | A participant left or was removed from a group conversation. | **Chat detail screen** — add on mount, remove on unmount. |
|
|
1378
1569
|
|
|
1379
1570
|
```typescript
|
|
1380
1571
|
import type {
|
|
@@ -1541,7 +1732,7 @@ The socket listeners for `read_receipt`, `user_online`, and `user_offline` are w
|
|
|
1541
1732
|
|
|
1542
1733
|
#### Seeding state on initial load
|
|
1543
1734
|
|
|
1544
|
-
The store starts empty. On first render, fetch the initial values from the API and seed the store:
|
|
1735
|
+
The store starts empty. On first render, fetch the initial values from the API and seed the store. Once seeded, `lastReadMessageId` can also be used as a cursor to [jump to the first unread message](#jump-to-first-unread-message) using `messagesApi.list()` with `direction: 'after'`.
|
|
1545
1736
|
|
|
1546
1737
|
```typescript
|
|
1547
1738
|
import { messagesApi, usersApi, useChatStore, type LastReadEntry } from '@antzsoft/chat-core';
|
|
@@ -1713,8 +1904,7 @@ interface Conversation {
|
|
|
1713
1904
|
conversationType: 'direct' | 'group';
|
|
1714
1905
|
name?: string;
|
|
1715
1906
|
description?: string;
|
|
1716
|
-
|
|
1717
|
-
iconUrl?: string;
|
|
1907
|
+
iconUrl?: string; // Fresh signed URL generated on each response — never stored directly
|
|
1718
1908
|
participants: Participant[];
|
|
1719
1909
|
participantCount?: number;
|
|
1720
1910
|
settings?: ConversationSettings;
|
package/dist/index.cjs
CHANGED
|
@@ -514,6 +514,18 @@ var conversationsApi = {
|
|
|
514
514
|
async getUnreadSummary() {
|
|
515
515
|
const { data } = await getApiClient().get("/conversations/unread");
|
|
516
516
|
return data;
|
|
517
|
+
},
|
|
518
|
+
/**
|
|
519
|
+
* Set the group icon from an already-uploaded file (admin only).
|
|
520
|
+
* The fileId comes from uploadBatch() / client.uploadFiles() — same as attachments.
|
|
521
|
+
* Server copies storageKey into conversation.iconMeta and deletes the chat_files record.
|
|
522
|
+
*/
|
|
523
|
+
async uploadIcon(conversationId, fileId) {
|
|
524
|
+
const { data } = await getApiClient().put(
|
|
525
|
+
`/conversations/${conversationId}/icon`,
|
|
526
|
+
{ fileId }
|
|
527
|
+
);
|
|
528
|
+
return normalizeConversation(data);
|
|
517
529
|
}
|
|
518
530
|
};
|
|
519
531
|
|
|
@@ -972,6 +984,12 @@ var AntzChatClient = class {
|
|
|
972
984
|
uploadFiles(files, conversationId) {
|
|
973
985
|
return uploadBatch(files, this._config.platformUploadFn, conversationId, this._config.upload.onProgress);
|
|
974
986
|
}
|
|
987
|
+
async uploadIcon(conversationId, file) {
|
|
988
|
+
const result = await this.uploadFiles([file], conversationId);
|
|
989
|
+
const fileId = result.successful[0]?.id;
|
|
990
|
+
if (!fileId) throw new Error("Icon upload failed");
|
|
991
|
+
return conversationsApi.uploadIcon(conversationId, fileId);
|
|
992
|
+
}
|
|
975
993
|
};
|
|
976
994
|
// Annotate the CommonJS export names for ESM import in node:
|
|
977
995
|
0 && (module.exports = {
|