@antzsoft/chat-core 1.1.0 → 1.1.2
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 +55 -20
- package/dist/index.cjs +381 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -31
- package/dist/index.d.ts +37 -31
- package/dist/index.js +380 -29
- package/dist/index.js.map +1 -1
- package/docs/integration-guide.html +222 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -320,10 +320,14 @@ interface AntzChatConfig {
|
|
|
320
320
|
tenantId?: string;
|
|
321
321
|
|
|
322
322
|
/**
|
|
323
|
-
*
|
|
324
|
-
*
|
|
323
|
+
* Enable payload-level transit encryption for all HTTP and socket traffic.
|
|
324
|
+
* Uses ECDH key exchange (X25519/P-256) + AES-256-GCM to encrypt every
|
|
325
|
+
* request, response, and socket event on the wire — independent of TLS.
|
|
326
|
+
* Server must have TRANSIT_ENCRYPTION_ENABLED=true (default).
|
|
327
|
+
* Default: true. Set false only for local development or debugging.
|
|
328
|
+
* Safe to toggle anytime — no data migration needed (wire-only, never stored).
|
|
325
329
|
*/
|
|
326
|
-
|
|
330
|
+
transitEncryption?: boolean;
|
|
327
331
|
|
|
328
332
|
/**
|
|
329
333
|
* The user's ID in the external auth system.
|
|
@@ -747,7 +751,6 @@ interface SendData {
|
|
|
747
751
|
attachments?: SendMessageAttachment[];
|
|
748
752
|
replyTo?: string; // messageId of the message being replied to
|
|
749
753
|
tempId?: string; // Client-generated ID for optimistic UI
|
|
750
|
-
isEncrypted?: boolean;
|
|
751
754
|
}
|
|
752
755
|
|
|
753
756
|
interface SearchParams {
|
|
@@ -847,7 +850,7 @@ import { conversationsApi } from '@antzsoft/chat-core';
|
|
|
847
850
|
| `update` | `(conversationId: string, data: UpdateConversationData) => Promise<Conversation>` | Update group name or description. |
|
|
848
851
|
| `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
852
|
| `delete` | `(conversationId: string) => Promise<void>` | Delete a conversation (admin only). |
|
|
850
|
-
| `addParticipants` | `(conversationId: string, userIds: string[]) => Promise<Conversation>` | Add one or more participants. |
|
|
853
|
+
| `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
854
|
| `removeParticipant` | `(conversationId: string, userId: string) => Promise<Conversation>` | Remove a participant. |
|
|
852
855
|
| `updateParticipantRole` | `(conversationId: string, userId: string, role: 'admin' \| 'member') => Promise<Conversation>` | Promote or demote a participant. |
|
|
853
856
|
| `mute` | `(conversationId: string, mutedUntil?: string) => Promise<void>` | Mute notifications. Pass an ISO date string to mute until a specific time. |
|
|
@@ -971,9 +974,12 @@ console.log(updated.iconUrl); // fresh signed URL, regenerated on every response
|
|
|
971
974
|
// - Previous icon deleted from storage automatically on replace
|
|
972
975
|
// - Non-admins get 403 Forbidden
|
|
973
976
|
|
|
974
|
-
// Add members
|
|
977
|
+
// Add members (default role: member)
|
|
975
978
|
await conversationsApi.addParticipants(group.id, ['user-d', 'user-e']);
|
|
976
979
|
|
|
980
|
+
// Add members as admins
|
|
981
|
+
await conversationsApi.addParticipants(group.id, ['user-f'], 'admin');
|
|
982
|
+
|
|
977
983
|
// Mute for 8 hours
|
|
978
984
|
const mutedUntil = new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString();
|
|
979
985
|
await conversationsApi.mute(group.id, mutedUntil);
|
|
@@ -1105,6 +1111,8 @@ const presigned = await storageApi.requestPresignedUrl({
|
|
|
1105
1111
|
mimeType: 'application/pdf',
|
|
1106
1112
|
size: 512000,
|
|
1107
1113
|
conversationId: 'conv-abc',
|
|
1114
|
+
// optional — stored on the server file record
|
|
1115
|
+
metadata: { compressed: true, originalSize: 900000, compressionAlgorithm: 'gzip' },
|
|
1108
1116
|
});
|
|
1109
1117
|
await platformUploadFn(presigned, file, (pct) => console.log(`${pct * 100}%`));
|
|
1110
1118
|
const fileRecord = await storageApi.confirmUpload(presigned.fileId);
|
|
@@ -1133,9 +1141,9 @@ When `platformCompressFn` is provided and `compression.enabled` is `true` (the d
|
|
|
1133
1141
|
1. Determine strategy per file (`image` → WebP/JPEG resize+encode, `gzip` → text/doc compression, `skip` → no-op)
|
|
1134
1142
|
2. Run `platformCompressFn(file, compressionConfig)` — returns a `CompressedFile`
|
|
1135
1143
|
3. If compressed result is **larger** than the original, the original is used instead (automatic fallback)
|
|
1136
|
-
4. Request presigned URL with the compressed size and
|
|
1144
|
+
4. Request presigned URL with the compressed size, MIME type, and compression metadata (`compressed`, `originalSize`, `compressionAlgorithm`)
|
|
1137
1145
|
5. Upload the compressed bytes
|
|
1138
|
-
6.
|
|
1146
|
+
6. Server persists `metadata.compressed`, `metadata.originalSize`, `metadata.compressionAlgorithm` on the file record alongside system fields (`userId`, `tenantId`)
|
|
1139
1147
|
|
|
1140
1148
|
### Strategy by file type
|
|
1141
1149
|
|
|
@@ -1633,12 +1641,14 @@ This means:
|
|
|
1633
1641
|
|
|
1634
1642
|
**When to call `joinRoom`:**
|
|
1635
1643
|
|
|
1636
|
-
|
|
1644
|
+
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.
|
|
1645
|
+
|
|
1646
|
+
Calling `joinRoom` on `conversation_created` is still safe (idempotent) and can be kept for defensive compatibility, but it is no longer necessary:
|
|
1637
1647
|
|
|
1638
1648
|
```typescript
|
|
1649
|
+
// Optional — server already handles this automatically
|
|
1639
1650
|
client.socket.on('conversation_created', (conv) => {
|
|
1640
|
-
//
|
|
1641
|
-
client.socket.emit.joinRoom(conv.id);
|
|
1651
|
+
client.socket.emit.joinRoom(conv.id); // safe no-op if already in room
|
|
1642
1652
|
});
|
|
1643
1653
|
```
|
|
1644
1654
|
|
|
@@ -2043,9 +2053,6 @@ interface Message {
|
|
|
2043
2053
|
sender?: User;
|
|
2044
2054
|
readBy?: Array<{ userId: string; readAt: string }>;
|
|
2045
2055
|
deliveredTo?: Array<{ userId: string; deliveredAt: string }>;
|
|
2046
|
-
isEncrypted?: boolean;
|
|
2047
|
-
encryptionMode?: 'none' | 'server' | 'e2ee';
|
|
2048
|
-
encryptedContent?: EncryptedContent;
|
|
2049
2056
|
}
|
|
2050
2057
|
```
|
|
2051
2058
|
|
|
@@ -2072,10 +2079,21 @@ interface MessageReaction {
|
|
|
2072
2079
|
### `MessageReplyReference`
|
|
2073
2080
|
|
|
2074
2081
|
```typescript
|
|
2082
|
+
interface ReplyAttachmentSnapshot {
|
|
2083
|
+
type: FileType; // 'image' | 'video' | 'document' | 'audio'
|
|
2084
|
+
filename: string;
|
|
2085
|
+
mimeType: string;
|
|
2086
|
+
size: number;
|
|
2087
|
+
duration?: number; // seconds — audio/video only
|
|
2088
|
+
dimensions?: { width: number; height: number };
|
|
2089
|
+
url: string; // signed URL for the first attachment of the quoted message
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2075
2092
|
interface MessageReplyReference {
|
|
2076
2093
|
messageId?: string;
|
|
2077
|
-
contentPreview?: string;
|
|
2094
|
+
contentPreview?: string; // text snippet, filename, or "3 Photos" / "3 Attachments" etc.
|
|
2078
2095
|
senderName?: string;
|
|
2096
|
+
attachmentSnapshot?: ReplyAttachmentSnapshot; // present only when quoted message had attachments
|
|
2079
2097
|
// Present on optimistic messages before server confirmation
|
|
2080
2098
|
id?: string;
|
|
2081
2099
|
content?: MessageContent;
|
|
@@ -2083,6 +2101,8 @@ interface MessageReplyReference {
|
|
|
2083
2101
|
}
|
|
2084
2102
|
```
|
|
2085
2103
|
|
|
2104
|
+
`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.
|
|
2105
|
+
|
|
2086
2106
|
### `Conversation`
|
|
2087
2107
|
|
|
2088
2108
|
```typescript
|
|
@@ -2105,9 +2125,6 @@ interface Conversation {
|
|
|
2105
2125
|
isPinned?: boolean;
|
|
2106
2126
|
isMuted?: boolean;
|
|
2107
2127
|
mutedUntil?: string;
|
|
2108
|
-
encryptionMode?: 'none' | 'server' | 'e2ee';
|
|
2109
|
-
isEncryptionEnabled?: boolean;
|
|
2110
|
-
encryptionKey?: string;
|
|
2111
2128
|
}
|
|
2112
2129
|
|
|
2113
2130
|
interface ConversationSettings {
|
|
@@ -2174,8 +2191,6 @@ interface SendMessagePayload {
|
|
|
2174
2191
|
attachments?: SendMessageAttachment[];
|
|
2175
2192
|
replyTo?: string; // messageId
|
|
2176
2193
|
tempId: string; // client-generated; echoed back in message_ack
|
|
2177
|
-
encryptedContent?: EncryptedContent;
|
|
2178
|
-
isEncrypted?: boolean;
|
|
2179
2194
|
}
|
|
2180
2195
|
|
|
2181
2196
|
interface SendMessageAttachment {
|
|
@@ -2383,6 +2398,22 @@ document.querySelectorAll('[data-conv-id]').forEach((el) => {
|
|
|
2383
2398
|
|
|
2384
2399
|
## Changelog
|
|
2385
2400
|
|
|
2401
|
+
### v1.1.2
|
|
2402
|
+
- **New: Participants stored in a dedicated collection** — Participants are no longer embedded inside conversation documents. Each participant is now its own document in a separate collection, removing the MongoDB 16 MB document size ceiling for large groups and eliminating write contention on high-membership conversations. No client API changes.
|
|
2403
|
+
- **New: Transit encryption** — All socket payloads are now encrypted end-to-end using a per-session symmetric key negotiated on connect. Transparent to callers — no SDK API changes required.
|
|
2404
|
+
- **Fix: Disbanded groups now visible in read-only mode for removed members** — Previously removed members' views of disbanded groups were inconsistent. Removed participants now always see the conversation in read-only mode until they explicitly call `leave()`.
|
|
2405
|
+
- **Fix: Removed participant access hardened** — Removed users can no longer view messages sent after their removal via the REST API. Message history is now capped at the exact moment of removal. Removed users were also incorrectly able to search messages, view starred messages, and fetch unread counts from conversations they were removed from — all three are now blocked.
|
|
2406
|
+
- **Fix: Unread badge drops to zero immediately on removal** — When an admin removes a user from a group, all unread messages in that conversation are automatically marked as read for the removed user at the moment of removal. Previously the badge stayed non-zero until the user manually opened the conversation.
|
|
2407
|
+
- **Fix: Compression metadata now stored on the server** — When a compressed image is uploaded, the file record now correctly saves `compressed`, `originalSize`, and `compressionAlgorithm`. Previously this metadata was silently dropped by DTO validation. No changes needed on the client — handled automatically by `uploadBatch`.
|
|
2408
|
+
- **Fix: Delete for me also removes the message from your starred list** — Calling `deleteForMe` on a starred message now removes the star. Previously the message remained in the starred list even after being hidden.
|
|
2409
|
+
|
|
2410
|
+
### v1.1.1
|
|
2411
|
+
- **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.
|
|
2412
|
+
- **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.
|
|
2413
|
+
- **New: exported `ReplyAttachmentSnapshot` type.**
|
|
2414
|
+
- **Fix: Pin, unpin, and unmute operations** — Server-side issues causing pin, unpin, and unmute to fail in certain states are resolved.
|
|
2415
|
+
- **Fix: Various backend fixes** — Stability and correctness improvements across multiple server-side paths.
|
|
2416
|
+
|
|
2386
2417
|
### v1.1.0
|
|
2387
2418
|
- **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.
|
|
2388
2419
|
- **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.
|
|
@@ -2396,6 +2427,10 @@ document.querySelectorAll('[data-conv-id]').forEach((el) => {
|
|
|
2396
2427
|
### v1.0.9
|
|
2397
2428
|
- **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.
|
|
2398
2429
|
- **`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.
|
|
2430
|
+
- **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.**
|
|
2431
|
+
- **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.**
|
|
2432
|
+
- **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).
|
|
2433
|
+
- **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.**
|
|
2399
2434
|
|
|
2400
2435
|
### v1.0.8
|
|
2401
2436
|
- **`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.
|