@antzsoft/chat-core 1.0.9 → 1.1.1
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 +78 -17
- package/dist/index.cjs +13 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -3
- package/dist/index.d.ts +23 -3
- package/dist/index.js +12 -10
- package/dist/index.js.map +1 -1
- package/docs/integration-guide.html +224 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -733,7 +733,7 @@ import { messagesApi } from '@antzsoft/chat-core';
|
|
|
733
733
|
| `unpin` | `(messageId: string) => Promise<Message>` | Unpin a message. |
|
|
734
734
|
| `getPinned` | `(conversationId: string) => Promise<Message[]>` | List pinned messages in a conversation. |
|
|
735
735
|
|
|
736
|
-
> **Delete permissions** — `delete()` (for everyone) requires either: the message belongs to the current user AND was sent within the delete window, OR the current user is a group admin. The server default window is **
|
|
736
|
+
> **Delete permissions** — `delete()` (for everyone) requires either: the message belongs to the current user AND was sent within the delete window, OR the current user is a group admin. The server default window is **216,000 s (60 hours)** when `conversation.settings.messageConfig.deleteWindowSeconds` is not set. DMs have no admin role — only the sender can delete for everyone in a DM. `deleteForMe()` is always allowed for any message. See the [integration guide — Edit & Delete section](docs/integration-guide.html#step-edit) for a ready-to-use `getDeleteOptions()` helper.
|
|
737
737
|
|
|
738
738
|
```typescript
|
|
739
739
|
interface ListMessagesParams {
|
|
@@ -847,12 +847,12 @@ import { conversationsApi } from '@antzsoft/chat-core';
|
|
|
847
847
|
| `update` | `(conversationId: string, data: UpdateConversationData) => Promise<Conversation>` | Update group name or description. |
|
|
848
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`. |
|
|
849
849
|
| `delete` | `(conversationId: string) => Promise<void>` | Delete a conversation (admin only). |
|
|
850
|
-
| `addParticipants` | `(conversationId: string, userIds: string[]) => Promise<Conversation>` | Add one or more participants. |
|
|
850
|
+
| `addParticipants` | `(conversationId: string, userIds: string[], role?: 'admin' \| 'member') => Promise<Conversation>` | Add one or more participants. `role` defaults to `'member'`. Previously removed members who are re-added always receive the specified role — a former admin re-added without `role: 'admin'` comes back as a member. |
|
|
851
851
|
| `removeParticipant` | `(conversationId: string, userId: string) => Promise<Conversation>` | Remove a participant. |
|
|
852
852
|
| `updateParticipantRole` | `(conversationId: string, userId: string, role: 'admin' \| 'member') => Promise<Conversation>` | Promote or demote a participant. |
|
|
853
853
|
| `mute` | `(conversationId: string, mutedUntil?: string) => Promise<void>` | Mute notifications. Pass an ISO date string to mute until a specific time. |
|
|
854
854
|
| `unmute` | `(conversationId: string) => Promise<void>` | Unmute a conversation. |
|
|
855
|
-
| `pin` | `(conversationId: string) => Promise<void>` | Pin a conversation to the top of the list. |
|
|
855
|
+
| `pin` | `(conversationId: string) => Promise<void>` | Pin a conversation to the top of the list. Max 5 pins — server returns `400` if the limit is reached. |
|
|
856
856
|
| `unpin` | `(conversationId: string) => Promise<void>` | Unpin a conversation. |
|
|
857
857
|
| `leave` | `(conversationId: string) => Promise<void>` | Leave a group conversation. |
|
|
858
858
|
| `getMembers` | `(conversationId: string) => Promise<User[]>` | Fetch full user profiles for all participants. |
|
|
@@ -971,9 +971,12 @@ console.log(updated.iconUrl); // fresh signed URL, regenerated on every response
|
|
|
971
971
|
// - Previous icon deleted from storage automatically on replace
|
|
972
972
|
// - Non-admins get 403 Forbidden
|
|
973
973
|
|
|
974
|
-
// Add members
|
|
974
|
+
// Add members (default role: member)
|
|
975
975
|
await conversationsApi.addParticipants(group.id, ['user-d', 'user-e']);
|
|
976
976
|
|
|
977
|
+
// Add members as admins
|
|
978
|
+
await conversationsApi.addParticipants(group.id, ['user-f'], 'admin');
|
|
979
|
+
|
|
977
980
|
// Mute for 8 hours
|
|
978
981
|
const mutedUntil = new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString();
|
|
979
982
|
await conversationsApi.mute(group.id, mutedUntil);
|
|
@@ -1554,6 +1557,33 @@ console.log(user.displayName, user.externalId);
|
|
|
1554
1557
|
|
|
1555
1558
|
---
|
|
1556
1559
|
|
|
1560
|
+
### App Config API (`appConfigApi`)
|
|
1561
|
+
|
|
1562
|
+
Returns server-side configuration constants. Call once on init and cache the result.
|
|
1563
|
+
|
|
1564
|
+
```typescript
|
|
1565
|
+
import { appConfigApi } from '@antzsoft/chat-core';
|
|
1566
|
+
```
|
|
1567
|
+
|
|
1568
|
+
| Method | Signature | Description |
|
|
1569
|
+
|---|---|---|
|
|
1570
|
+
| `get` | `() => Promise<AppConfig>` | Fetch app-level config from the server. |
|
|
1571
|
+
|
|
1572
|
+
```typescript
|
|
1573
|
+
interface AppConfig {
|
|
1574
|
+
maxPinnedConversations: number; // currently 5
|
|
1575
|
+
}
|
|
1576
|
+
```
|
|
1577
|
+
|
|
1578
|
+
```typescript
|
|
1579
|
+
const config = await appConfigApi.get();
|
|
1580
|
+
// Use config.maxPinnedConversations to gate the pin UI
|
|
1581
|
+
```
|
|
1582
|
+
|
|
1583
|
+
> The SDK caches this under the `['app-config']` React Query key with `staleTime: Infinity` — it is only fetched once per session. `useConversations()` exposes `maxPinnedConversations` directly.
|
|
1584
|
+
|
|
1585
|
+
---
|
|
1586
|
+
|
|
1557
1587
|
### Socket
|
|
1558
1588
|
|
|
1559
1589
|
#### Connection management
|
|
@@ -1606,12 +1636,14 @@ This means:
|
|
|
1606
1636
|
|
|
1607
1637
|
**When to call `joinRoom`:**
|
|
1608
1638
|
|
|
1609
|
-
|
|
1639
|
+
When the **user is added to a conversation while their socket is already connected**, the server now automatically joins their active sockets into the new room and emits `conversation_created` to their personal room. No `joinRoom` call is required — the user will receive `new_message`, `typing_indicator`, and all other room events immediately.
|
|
1640
|
+
|
|
1641
|
+
Calling `joinRoom` on `conversation_created` is still safe (idempotent) and can be kept for defensive compatibility, but it is no longer necessary:
|
|
1610
1642
|
|
|
1611
1643
|
```typescript
|
|
1644
|
+
// Optional — server already handles this automatically
|
|
1612
1645
|
client.socket.on('conversation_created', (conv) => {
|
|
1613
|
-
//
|
|
1614
|
-
client.socket.emit.joinRoom(conv.id);
|
|
1646
|
+
client.socket.emit.joinRoom(conv.id); // safe no-op if already in room
|
|
1615
1647
|
});
|
|
1616
1648
|
```
|
|
1617
1649
|
|
|
@@ -1663,7 +1695,7 @@ All emit methods that have server responses use a 5-second ack timeout and retur
|
|
|
1663
1695
|
| `leaveRoom` | `(conversationId: string) => void` | Leave a conversation room. Fire-and-forget. |
|
|
1664
1696
|
| `sendMessage` | `(payload: SendMessagePayload) => Promise<unknown>` | Send a message. Ack-based. |
|
|
1665
1697
|
| `updateMessage` | `(messageId: string, text: string) => Promise<unknown>` | Edit a message. Ack-based. |
|
|
1666
|
-
| `deleteMessage` | `(messageId: string) => Promise<unknown>` | Delete a message for everyone. Own messages must be within the delete window (default
|
|
1698
|
+
| `deleteMessage` | `(messageId: string) => Promise<unknown>` | Delete a message for everyone. Own messages must be within the delete window (default 60 hours); group admins can delete any message with no time restriction. Ack-based. |
|
|
1667
1699
|
| `deleteMessageForMe` | `(messageId: string) => Promise<unknown>` | Hide a message for the current user only. Ack-based. |
|
|
1668
1700
|
| `addReaction` | `(messageId: string, emoji: string) => Promise<unknown>` | Add a reaction. Ack-based. |
|
|
1669
1701
|
| `removeReaction` | `(messageId: string, emoji: string) => Promise<unknown>` | Remove a reaction. Ack-based. |
|
|
@@ -2045,10 +2077,21 @@ interface MessageReaction {
|
|
|
2045
2077
|
### `MessageReplyReference`
|
|
2046
2078
|
|
|
2047
2079
|
```typescript
|
|
2080
|
+
interface ReplyAttachmentSnapshot {
|
|
2081
|
+
type: FileType; // 'image' | 'video' | 'document' | 'audio'
|
|
2082
|
+
filename: string;
|
|
2083
|
+
mimeType: string;
|
|
2084
|
+
size: number;
|
|
2085
|
+
duration?: number; // seconds — audio/video only
|
|
2086
|
+
dimensions?: { width: number; height: number };
|
|
2087
|
+
url: string; // signed URL for the first attachment of the quoted message
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2048
2090
|
interface MessageReplyReference {
|
|
2049
2091
|
messageId?: string;
|
|
2050
|
-
contentPreview?: string;
|
|
2092
|
+
contentPreview?: string; // text snippet, filename, or "3 Photos" / "3 Attachments" etc.
|
|
2051
2093
|
senderName?: string;
|
|
2094
|
+
attachmentSnapshot?: ReplyAttachmentSnapshot; // present only when quoted message had attachments
|
|
2052
2095
|
// Present on optimistic messages before server confirmation
|
|
2053
2096
|
id?: string;
|
|
2054
2097
|
content?: MessageContent;
|
|
@@ -2056,6 +2099,8 @@ interface MessageReplyReference {
|
|
|
2056
2099
|
}
|
|
2057
2100
|
```
|
|
2058
2101
|
|
|
2102
|
+
`attachmentSnapshot` is a denormalized copy of the first attachment from the quoted message, stored at write time. This means the reply bubble can render an attachment preview (thumbnail, filename, type icon) without fetching the original message — even if it has scrolled far out of the paginated window. The `url` is a signed URL regenerated on every response, identical to how `content.attachments[].url` works.
|
|
2103
|
+
|
|
2059
2104
|
### `Conversation`
|
|
2060
2105
|
|
|
2061
2106
|
```typescript
|
|
@@ -2356,19 +2401,35 @@ document.querySelectorAll('[data-conv-id]').forEach((el) => {
|
|
|
2356
2401
|
|
|
2357
2402
|
## Changelog
|
|
2358
2403
|
|
|
2404
|
+
### v1.1.1
|
|
2405
|
+
- **New: `attachmentSnapshot` on reply messages** — `replyTo` now includes a snapshot of the first attachment from the quoted message (`type`, `filename`, `mimeType`, `size`, `duration`, `dimensions`, signed `url`). Stored at write time so reply bubbles render without fetching the original message.
|
|
2406
|
+
- **Fix: `contentPreview` for multi-attachment replies** — Now reflects attachment count: `"photo.jpg +2 more"`, `"3 Photos"`, `"3 Attachments"` (mixed types). Previously always showed only the first filename.
|
|
2407
|
+
- **New: exported `ReplyAttachmentSnapshot` type.**
|
|
2408
|
+
|
|
2409
|
+
### v1.1.0
|
|
2410
|
+
- **Fix: 500 error on device token registration** — Registering a push token that was previously registered under a different user or device ID (e.g. after app reinstall, account switch, or UUID rotation) now succeeds. The stale token record is removed before the upsert, preventing a duplicate key violation on the global `token_unique` index.
|
|
2411
|
+
- **Fix: 500 error on remove participant** — Removing a participant no longer errors for the removed user's subsequent actions. Message stops and socket events now correctly target only remaining active members.
|
|
2412
|
+
- **Fix: Leave group now works for removed members** — Calling `conversationsApi.leave()` after being removed from a group (kicked by admin) now hides the conversation from the user's list instead of returning a 403. Previously, removed members (`isActive: false`) were blocked by the active-participant guard.
|
|
2413
|
+
- **Fix: Group auto-disbands when last member leaves or is removed** — When the last active participant leaves or is removed, the conversation is now marked inactive (`isActive: false`) automatically. Previously the group persisted as an orphan with zero members.
|
|
2414
|
+
- **Fix: `lastMessage` content not updating after message edit** — Editing a message now correctly updates `conversation.lastMessage.contentPreview` if the edited message is the current last message.
|
|
2415
|
+
- **Fix: Reply attachment preview shows `[Attachment]`** — When replying to a message that contains an attachment, the quoted preview now returns the filename if available, otherwise the file type (e.g. `image`, `video`). Clients should use the parent message's attachment data to render a visual preview for previewable types.
|
|
2416
|
+
- **Fix: Remove reaction returns error** — Removing a reaction that does not exist no longer throws a 500. The operation is now idempotent.
|
|
2417
|
+
- **New: Pin limit — max 5 pinned conversations** — Server enforces a limit of 5 pinned conversations per user and returns `400` if exceeded. A new `GET /app/config` endpoint and `appConfigApi.get()` return `{ maxPinnedConversations: 5 }` so clients can gate the UI before hitting the API. `useConversations()` (Web + RN) fetches config automatically, blocks the pin mutation if the limit is reached, and exposes `maxPinnedConversations`. RN `ConversationList` now supports long-press to pin/unpin.
|
|
2418
|
+
|
|
2359
2419
|
### v1.0.9
|
|
2360
|
-
- **Socket reconnect resilience for `sendMessage`** — On Android, the OS suspends idle WebSocket connections during long audio recordings. `sendMessage` now waits up to 15 seconds for Socket.IO to auto-reconnect before sending, instead of immediately failing with "Socket not connected". Audio messages go through without error after upload.
|
|
2361
|
-
- **`settings` and `participantCount` now returned in conversation responses** — All conversation API responses and socket events (`conversation_created`, `conversation_updated`, etc.) now include `settings` (`onlyAdminsCanMessage`, `onlyAdminsCanAddMembers`, `messageConfig.editWindowSeconds`, `messageConfig.deleteWindowSeconds`) and `participantCount`. Previously these were server-side only.
|
|
2362
|
-
- **Fix:
|
|
2363
|
-
- **Fix:
|
|
2364
|
-
- **Fix:
|
|
2420
|
+
- **Socket reconnect resilience for `sendMessage`** — On Android, the OS suspends idle WebSocket connections during long audio recordings. `sendMessage` now waits up to 15 seconds for Socket.IO to auto-reconnect before sending, instead of immediately failing with "Socket not connected". Audio messages go through without error after upload.
|
|
2421
|
+
- **`settings` and `participantCount` now returned in conversation responses** — All conversation API responses and socket events (`conversation_created`, `conversation_updated`, etc.) now include `settings` (`onlyAdminsCanMessage`, `onlyAdminsCanAddMembers`, `messageConfig.editWindowSeconds`, `messageConfig.deleteWindowSeconds`) and `participantCount`. Previously these were server-side only. Fields were already in the `Conversation` type as optional.
|
|
2422
|
+
- **Fix: `participant_left` not received by users outside the open conversation** — The event was only sent to users who had the conversation room joined. Users on the conversation-list screen now receive it via their personal socket room. **No client action required.**
|
|
2423
|
+
- **Fix: Removed user could re-enter conversation room via `joinRoom`** — The socket access check was missing `isActive`, letting removed users slip back into the room and receive messages. Fixed server-side. **No client action required.**
|
|
2424
|
+
- **Fix: Re-added user's conversation missing from list after deletion** — If a user deleted a conversation and was re-added by an admin, it never reappeared. The server now resets `isHidden`, auto-joins the socket room, and pushes `conversation_created` to the user's personal room. **No client action required.** Existing `conversation_created → joinRoom` listeners are safe (idempotent).
|
|
2425
|
+
- **New: `role` parameter on `addParticipants`** — Optional third argument (`'admin' | 'member'`, default `'member'`). Re-added users always receive the specified role — a former admin re-added without `role: 'admin'` comes back as a member. **Backward compatible.**
|
|
2365
2426
|
|
|
2366
2427
|
### v1.0.8
|
|
2367
2428
|
- **`duration` field in `SendMessageAttachment`** — Pass `duration` (seconds) when sending audio or video. Server now stores and returns it in `new_message` and message list responses. **Action required (RN):** Omitting it on React Native can crash native audio player libraries on the receiver side; always pass it for audio/video. Web is unaffected.
|
|
2368
2429
|
- **File compression** — `uploadBatch` now accepts optional `platformCompressFn` + `compressionConfig` args. Fully backward compatible — existing callers unchanged. Web/RN SDKs wire this in automatically; Node.js users can supply `nodeCompressFn` manually. No action required unless opting in on Node.
|
|
2369
|
-
- **Removed members keep read-only access** — Server behavior change. Removed participants stay in their conversation list and can read history but cannot write. Socket room membership ends immediately on removal.
|
|
2370
|
-
- **Admin delete is hide-only** — Server behavior change. Deleting a conversation sets `isHidden` for the requester only; other participants are unaffected.
|
|
2371
|
-
- **Fix: `lastMessage.status` stuck as `deleted`** — Server now explicitly resets `status: 'active'` on both REST and WebSocket paths when a new message is sent.
|
|
2430
|
+
- **Removed members keep read-only access** — Server behavior change. Removed participants stay in their conversation list and can read history but cannot write. Socket room membership ends immediately on removal.
|
|
2431
|
+
- **Admin delete is hide-only** — Server behavior change. Deleting a conversation sets `isHidden` for the requester only; other participants are unaffected.
|
|
2432
|
+
- **Fix: `lastMessage.status` stuck as `deleted`** — Server now explicitly resets `status: 'active'` on both REST and WebSocket paths when a new message is sent.
|
|
2372
2433
|
- **Fix: `duration` stored from sender payload** — `SendMessageAttachment.duration` is now persisted and echoed back. The `SendMessageAttachment` interface in this package is unchanged (field was already present as optional).
|
|
2373
2434
|
|
|
2374
2435
|
### v1.0.7
|
package/dist/index.cjs
CHANGED
|
@@ -94,6 +94,7 @@ var init_chat_store = __esm({
|
|
|
94
94
|
var src_exports = {};
|
|
95
95
|
__export(src_exports, {
|
|
96
96
|
AntzChatClient: () => AntzChatClient,
|
|
97
|
+
appConfigApi: () => appConfigApi,
|
|
97
98
|
authApi: () => authApi,
|
|
98
99
|
connectSocket: () => connectSocket,
|
|
99
100
|
conversationsApi: () => conversationsApi,
|
|
@@ -372,6 +373,14 @@ var authApi = {
|
|
|
372
373
|
}
|
|
373
374
|
};
|
|
374
375
|
|
|
376
|
+
// src/api/app-config.ts
|
|
377
|
+
var appConfigApi = {
|
|
378
|
+
async get() {
|
|
379
|
+
const { data } = await getApiClient().get("/app/config");
|
|
380
|
+
return data;
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
|
|
375
384
|
// src/api/messages.ts
|
|
376
385
|
var messagesApi = {
|
|
377
386
|
async list(conversationId, params = {}) {
|
|
@@ -526,10 +535,10 @@ var conversationsApi = {
|
|
|
526
535
|
async delete(conversationId) {
|
|
527
536
|
await getApiClient().delete(`/conversations/${conversationId}`);
|
|
528
537
|
},
|
|
529
|
-
async addParticipants(conversationId, userIds) {
|
|
538
|
+
async addParticipants(conversationId, userIds, role) {
|
|
530
539
|
const { data } = await getApiClient().post(
|
|
531
540
|
`/conversations/${conversationId}/participants`,
|
|
532
|
-
{ userIds }
|
|
541
|
+
{ userIds, ...role && { role } }
|
|
533
542
|
);
|
|
534
543
|
return normalizeConversation(data);
|
|
535
544
|
},
|
|
@@ -649,14 +658,7 @@ async function uploadBatch(files, platformUploadFn, conversationId, onProgress,
|
|
|
649
658
|
filename: f.name,
|
|
650
659
|
mimeType: f.type,
|
|
651
660
|
size: f.size,
|
|
652
|
-
conversationId
|
|
653
|
-
...f.compressed && {
|
|
654
|
-
metadata: {
|
|
655
|
-
compressed: true,
|
|
656
|
-
originalSize: f.originalSize,
|
|
657
|
-
compressionAlgorithm: f.compressionAlgorithm
|
|
658
|
-
}
|
|
659
|
-
}
|
|
661
|
+
conversationId
|
|
660
662
|
}));
|
|
661
663
|
const { urls, errors: requestErrors } = await storageApi.requestPresignedUrlBatch(requests);
|
|
662
664
|
const progressMap = {};
|
|
@@ -1108,6 +1110,7 @@ var AntzChatClient = class {
|
|
|
1108
1110
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1109
1111
|
0 && (module.exports = {
|
|
1110
1112
|
AntzChatClient,
|
|
1113
|
+
appConfigApi,
|
|
1111
1114
|
authApi,
|
|
1112
1115
|
connectSocket,
|
|
1113
1116
|
conversationsApi,
|