@antzsoft/chat-core 1.0.3 → 1.0.4
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 +123 -21
- package/dist/index.cjs +20 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -3
- package/dist/index.d.ts +16 -3
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -1
- package/docs/integration-guide.html +290 -42
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -787,7 +787,8 @@ import { conversationsApi } from '@antzsoft/chat-core';
|
|
|
787
787
|
| `get` | `(conversationId: string) => Promise<Conversation>` | Fetch a single conversation. |
|
|
788
788
|
| `createGroup` | `(data: CreateGroupData) => Promise<Conversation>` | Create a group conversation. |
|
|
789
789
|
| `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
|
|
790
|
+
| `update` | `(conversationId: string, data: UpdateConversationData) => Promise<Conversation>` | Update group name or description. |
|
|
791
|
+
| `uploadIcon` | `(conversationId: string, file: File \| { uri: string; name: string; type: string }) => Promise<Conversation>` | Upload or replace the group icon (admin only). Deletes previous icon from storage. Returns conversation with fresh `iconUrl`. |
|
|
791
792
|
| `delete` | `(conversationId: string) => Promise<void>` | Delete a conversation (admin only). |
|
|
792
793
|
| `addParticipants` | `(conversationId: string, userIds: string[]) => Promise<Conversation>` | Add one or more participants. |
|
|
793
794
|
| `removeParticipant` | `(conversationId: string, userId: string) => Promise<Conversation>` | Remove a participant. |
|
|
@@ -829,7 +830,6 @@ interface ConversationListParams {
|
|
|
829
830
|
interface CreateGroupData {
|
|
830
831
|
name: string;
|
|
831
832
|
description?: string;
|
|
832
|
-
icon?: string; // Emoji or short string used as the group avatar
|
|
833
833
|
participantIds: string[];
|
|
834
834
|
}
|
|
835
835
|
|
|
@@ -840,7 +840,6 @@ interface CreateDirectData {
|
|
|
840
840
|
interface UpdateConversationData {
|
|
841
841
|
name?: string;
|
|
842
842
|
description?: string;
|
|
843
|
-
icon?: string;
|
|
844
843
|
}
|
|
845
844
|
```
|
|
846
845
|
|
|
@@ -935,6 +934,48 @@ socket?.on('unread_count_changed', ({ conversationId, unreadCount }) => {
|
|
|
935
934
|
|
|
936
935
|
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
936
|
|
|
937
|
+
#### Chat icon badge — complete pattern (headless / custom UI)
|
|
938
|
+
|
|
939
|
+
When building a custom UI using `chat-core` directly (no web/RN SDK), maintain your own unread state and update it on socket events:
|
|
940
|
+
|
|
941
|
+
```typescript
|
|
942
|
+
import { conversationsApi, tryGetSocket } from '@antzsoft/chat-core';
|
|
943
|
+
|
|
944
|
+
// 1. Cold start — fetch accurate counts from DB
|
|
945
|
+
const summary = await conversationsApi.getUnreadSummary();
|
|
946
|
+
let totalUnread = summary.totalUnread;
|
|
947
|
+
updateBadge(totalUnread); // your UI function
|
|
948
|
+
|
|
949
|
+
// 2. While socket is connected — update on every event
|
|
950
|
+
const socket = tryGetSocket();
|
|
951
|
+
|
|
952
|
+
socket?.on('conversation_updated', ({ conversationId, unreadCount }) => {
|
|
953
|
+
// Server sends accurate DB count per conversation — recalculate total
|
|
954
|
+
summary.byConversation = summary.byConversation
|
|
955
|
+
.filter(c => c.conversationId !== conversationId)
|
|
956
|
+
.concat({ conversationId, unreadCount });
|
|
957
|
+
totalUnread = summary.byConversation.reduce((s, c) => s + c.unreadCount, 0);
|
|
958
|
+
updateBadge(totalUnread);
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
socket?.on('unread_count_changed', ({ conversationId }) => {
|
|
962
|
+
// User read a conversation — clear it from the map
|
|
963
|
+
summary.byConversation = summary.byConversation
|
|
964
|
+
.filter(c => c.conversationId !== conversationId);
|
|
965
|
+
totalUnread = summary.byConversation.reduce((s, c) => s + c.unreadCount, 0);
|
|
966
|
+
updateBadge(totalUnread);
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
// 3. On foreground / socket reconnect — resync from DB
|
|
970
|
+
async function onForeground() {
|
|
971
|
+
const fresh = await conversationsApi.getUnreadSummary();
|
|
972
|
+
totalUnread = fresh.totalUnread;
|
|
973
|
+
updateBadge(totalUnread);
|
|
974
|
+
}
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
> **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.
|
|
978
|
+
|
|
938
979
|
---
|
|
939
980
|
|
|
940
981
|
### Storage API (`storageApi` and `uploadBatch`)
|
|
@@ -1306,6 +1347,60 @@ unsubscribe();
|
|
|
1306
1347
|
disconnectSocket();
|
|
1307
1348
|
```
|
|
1308
1349
|
|
|
1350
|
+
#### Room membership — auto-join and `joinRoom`
|
|
1351
|
+
|
|
1352
|
+
**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.
|
|
1353
|
+
|
|
1354
|
+
This means:
|
|
1355
|
+
- You do **not** need to call `joinRoom` when opening a chat screen.
|
|
1356
|
+
- `new_message`, `typing_indicator`, `read_receipt`, and all other conversation events are received for all conversations from the moment the socket connects.
|
|
1357
|
+
- Calling `joinRoom` on an already-joined room is safe (idempotent) but causes an unnecessary DB access check — avoid it in hot paths.
|
|
1358
|
+
|
|
1359
|
+
**When to call `joinRoom`:**
|
|
1360
|
+
|
|
1361
|
+
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.
|
|
1362
|
+
|
|
1363
|
+
```typescript
|
|
1364
|
+
client.socket.on('conversation_created', (conv) => {
|
|
1365
|
+
// Fires when you're added to a new or existing conversation at runtime
|
|
1366
|
+
client.socket.emit.joinRoom(conv.id);
|
|
1367
|
+
});
|
|
1368
|
+
```
|
|
1369
|
+
|
|
1370
|
+
**Where to add the `new_message` listener:**
|
|
1371
|
+
|
|
1372
|
+
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.
|
|
1373
|
+
|
|
1374
|
+
```typescript
|
|
1375
|
+
await client.connect();
|
|
1376
|
+
|
|
1377
|
+
client.socket.on('new_message', (event: NewMessageEvent) => {
|
|
1378
|
+
const { message } = event;
|
|
1379
|
+
|
|
1380
|
+
// Update the open chat view if this conversation is active
|
|
1381
|
+
if (message.conversationId === activeConversationId) {
|
|
1382
|
+
appendMessageToView(message);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
// Always update the conversation list (last message + unread badge)
|
|
1386
|
+
updateConversationList(message.conversationId, {
|
|
1387
|
+
lastMessage: message,
|
|
1388
|
+
// Don't increment unread if the user sent it or is currently viewing that chat
|
|
1389
|
+
incrementUnread: message.conversationId !== activeConversationId
|
|
1390
|
+
&& message.senderId !== currentUserId,
|
|
1391
|
+
});
|
|
1392
|
+
});
|
|
1393
|
+
|
|
1394
|
+
// Only case where joinRoom is needed — new conversation created at runtime
|
|
1395
|
+
client.socket.on('conversation_created', (conv) => {
|
|
1396
|
+
client.socket.emit.joinRoom(conv.id);
|
|
1397
|
+
});
|
|
1398
|
+
```
|
|
1399
|
+
|
|
1400
|
+
**`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.
|
|
1401
|
+
|
|
1402
|
+
---
|
|
1403
|
+
|
|
1309
1404
|
#### `socketEmit` — outbound events
|
|
1310
1405
|
|
|
1311
1406
|
```typescript
|
|
@@ -1320,7 +1415,7 @@ All emit methods that have server responses use a 5-second ack timeout and retur
|
|
|
1320
1415
|
| `leaveRoom` | `(conversationId: string) => void` | Leave a conversation room. Fire-and-forget. |
|
|
1321
1416
|
| `sendMessage` | `(payload: SendMessagePayload) => Promise<unknown>` | Send a message. Ack-based. |
|
|
1322
1417
|
| `updateMessage` | `(messageId: string, text: string) => Promise<unknown>` | Edit a message. Ack-based. |
|
|
1323
|
-
| `deleteMessage` | `(messageId: string) => Promise<unknown>` | Delete a message for everyone
|
|
1418
|
+
| `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
1419
|
| `deleteMessageForMe` | `(messageId: string) => Promise<unknown>` | Hide a message for the current user only. Ack-based. |
|
|
1325
1420
|
| `addReaction` | `(messageId: string, emoji: string) => Promise<unknown>` | Add a reaction. Ack-based. |
|
|
1326
1421
|
| `removeReaction` | `(messageId: string, emoji: string) => Promise<unknown>` | Remove a reaction. Ack-based. |
|
|
@@ -1360,21 +1455,29 @@ console.log('Online:', onlineIds); // ['user-a', 'user-c']
|
|
|
1360
1455
|
|
|
1361
1456
|
Subscribe using `client.socket.on(event, handler)` (headless) or directly on the Socket.IO socket via `getSocket().on(event, handler)`.
|
|
1362
1457
|
|
|
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
|
-
| `
|
|
1458
|
+
| Event | Payload type | Description | Where to listen / unlisten |
|
|
1459
|
+
|---|---|---|---|
|
|
1460
|
+
| `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. |
|
|
1461
|
+
| `message_updated` | `MessageUpdatedEvent` | A message was edited. | **Chat detail screen** — add on mount, remove on unmount. |
|
|
1462
|
+
| `message_deleted` | `MessageDeletedEvent` | A message was deleted for everyone. | **Chat detail screen** — add on mount, remove on unmount. |
|
|
1463
|
+
| `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. |
|
|
1464
|
+
| `reaction_updated` | `ReactionUpdatedEvent` | Reactions on a message changed (full reaction array). | **Chat detail screen** — add on mount, remove on unmount. |
|
|
1465
|
+
| `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. |
|
|
1466
|
+
| `typing_indicator` | `TypingIndicatorEvent` | A user started or stopped typing. | **Chat detail screen** — add on mount, remove on unmount. |
|
|
1467
|
+
| `user_online` | `{ userId: string }` | A participant came online — auto-updates `useChatStore.onlineUsers`. | **App root** — drives online indicators everywhere. Keep for full session. |
|
|
1468
|
+
| `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. |
|
|
1469
|
+
| `user_status` | `UserStatusEvent` | Global presence broadcast (online/offline/away) to all connected clients. | **App root** — keep for full session. |
|
|
1470
|
+
| `online_users` | `string[]` | Full list of online user IDs — sent on initial room join. | **App root** — keep for full session. |
|
|
1471
|
+
| `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). |
|
|
1472
|
+
| `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. |
|
|
1473
|
+
| `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. |
|
|
1474
|
+
| `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. |
|
|
1475
|
+
| `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. |
|
|
1476
|
+
| `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. |
|
|
1477
|
+
| `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. |
|
|
1478
|
+
| `conversation_deleted` | `{ conversationId: string }` | A conversation was deleted. | **App root / conversation list screen** — remove from state and navigate away if it was open. |
|
|
1479
|
+
| `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. |
|
|
1480
|
+
| `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
1481
|
|
|
1379
1482
|
```typescript
|
|
1380
1483
|
import type {
|
|
@@ -1713,8 +1816,7 @@ interface Conversation {
|
|
|
1713
1816
|
conversationType: 'direct' | 'group';
|
|
1714
1817
|
name?: string;
|
|
1715
1818
|
description?: string;
|
|
1716
|
-
|
|
1717
|
-
iconUrl?: string;
|
|
1819
|
+
iconUrl?: string; // Fresh signed URL generated on each response — never stored directly
|
|
1718
1820
|
participants: Participant[];
|
|
1719
1821
|
participantCount?: number;
|
|
1720
1822
|
settings?: ConversationSettings;
|
package/dist/index.cjs
CHANGED
|
@@ -514,6 +514,26 @@ var conversationsApi = {
|
|
|
514
514
|
async getUnreadSummary() {
|
|
515
515
|
const { data } = await getApiClient().get("/conversations/unread");
|
|
516
516
|
return data;
|
|
517
|
+
},
|
|
518
|
+
/**
|
|
519
|
+
* Upload or replace the group icon (admin only).
|
|
520
|
+
* Accepts a File (web) or { uri, name, type } object (React Native).
|
|
521
|
+
* The server stores the image in its own storage, deletes the previous icon,
|
|
522
|
+
* and returns a fresh signed iconUrl on every subsequent response.
|
|
523
|
+
*/
|
|
524
|
+
async uploadIcon(conversationId, file) {
|
|
525
|
+
const formData = new FormData();
|
|
526
|
+
if (file instanceof File) {
|
|
527
|
+
formData.append("icon", file);
|
|
528
|
+
} else {
|
|
529
|
+
formData.append("icon", { uri: file.uri, name: file.name, type: file.type });
|
|
530
|
+
}
|
|
531
|
+
const { data } = await getApiClient().put(
|
|
532
|
+
`/conversations/${conversationId}/icon`,
|
|
533
|
+
formData,
|
|
534
|
+
{ headers: { "Content-Type": "multipart/form-data" } }
|
|
535
|
+
);
|
|
536
|
+
return normalizeConversation(data);
|
|
517
537
|
}
|
|
518
538
|
};
|
|
519
539
|
|