@antzsoft/chat-core 1.1.1 → 1.1.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 +91 -19
- package/dist/index.cjs +445 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -31
- package/dist/index.d.ts +58 -31
- package/dist/index.js +444 -30
- package/dist/index.js.map +1 -1
- package/docs/integration-guide.html +354 -14
- 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.
|
|
@@ -621,6 +625,13 @@ class AntzChatClient {
|
|
|
621
625
|
* Same presigned URL pipeline as message attachments — platformUploadFn is handled automatically.
|
|
622
626
|
*/
|
|
623
627
|
uploadIcon(conversationId: string, file: UploadableFile): Promise<Conversation>;
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Remove the group icon (admin only).
|
|
631
|
+
* Deletes the asset from storage and clears iconMeta on the conversation.
|
|
632
|
+
* Returns the updated conversation with iconUrl: undefined.
|
|
633
|
+
*/
|
|
634
|
+
removeIcon(conversationId: string): Promise<Conversation>;
|
|
624
635
|
}
|
|
625
636
|
```
|
|
626
637
|
|
|
@@ -747,7 +758,6 @@ interface SendData {
|
|
|
747
758
|
attachments?: SendMessageAttachment[];
|
|
748
759
|
replyTo?: string; // messageId of the message being replied to
|
|
749
760
|
tempId?: string; // Client-generated ID for optimistic UI
|
|
750
|
-
isEncrypted?: boolean;
|
|
751
761
|
}
|
|
752
762
|
|
|
753
763
|
interface SearchParams {
|
|
@@ -846,15 +856,16 @@ import { conversationsApi } from '@antzsoft/chat-core';
|
|
|
846
856
|
| `createDirect` | `(data: CreateDirectData) => Promise<Conversation>` | Start or retrieve a direct conversation with another user. |
|
|
847
857
|
| `update` | `(conversationId: string, data: UpdateConversationData) => Promise<Conversation>` | Update group name or description. |
|
|
848
858
|
| `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
|
-
| `
|
|
850
|
-
| `
|
|
851
|
-
| `
|
|
859
|
+
| `removeIcon` | `(conversationId: string) => Promise<Conversation>` | Remove the group icon (admin only). Deletes the asset from storage, clears `iconMeta` on the conversation, and returns the updated conversation with `iconUrl: undefined`. Non-admins receive `403 Forbidden`. |
|
|
860
|
+
| `delete` | `(conversationId: string) => Promise<void>` | Hide a conversation from the caller's list. Works for any participant (active or inactive) on both DMs and groups — no admin role required. For DMs this is "Delete Chat"; for groups this is "Delete Group" (after already exiting). Other participants are completely unaffected. |
|
|
861
|
+
| `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. Message visibility on re-add depends on whether the user previously deleted the conversation (see [Message History & Re-add](#message-history--re-add)). |
|
|
862
|
+
| `removeParticipant` | `(conversationId: string, userId: string) => Promise<Conversation>` | Remove a participant (admin only). |
|
|
852
863
|
| `updateParticipantRole` | `(conversationId: string, userId: string, role: 'admin' \| 'member') => Promise<Conversation>` | Promote or demote a participant. |
|
|
853
864
|
| `mute` | `(conversationId: string, mutedUntil?: string) => Promise<void>` | Mute notifications. Pass an ISO date string to mute until a specific time. |
|
|
854
865
|
| `unmute` | `(conversationId: string) => Promise<void>` | Unmute a conversation. |
|
|
855
866
|
| `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
867
|
| `unpin` | `(conversationId: string) => Promise<void>` | Unpin a conversation. |
|
|
857
|
-
| `leave` | `(conversationId: string) => Promise<void>` | Leave a group conversation. |
|
|
868
|
+
| `leave` | `(conversationId: string, andDelete?: boolean) => Promise<void>` | Leave a group conversation. Pass `andDelete: true` to also hide it from the caller's list in one atomic operation ("Exit and Delete"). When the last admin calls `leave()`, the server automatically promotes the longest-standing active member to admin before completing the exit — no client action required. |
|
|
858
869
|
| `getMembers` | `(conversationId: string) => Promise<User[]>` | Fetch full user profiles for all participants. |
|
|
859
870
|
|
|
860
871
|
```typescript
|
|
@@ -971,6 +982,11 @@ console.log(updated.iconUrl); // fresh signed URL, regenerated on every response
|
|
|
971
982
|
// - Previous icon deleted from storage automatically on replace
|
|
972
983
|
// - Non-admins get 403 Forbidden
|
|
973
984
|
|
|
985
|
+
// Remove the group icon (admin only).
|
|
986
|
+
// Deletes the asset from storage and clears iconMeta. Returns conversation with iconUrl: undefined.
|
|
987
|
+
const noIcon = await conversationsApi.removeIcon(group.id);
|
|
988
|
+
// noIcon.iconUrl === undefined
|
|
989
|
+
|
|
974
990
|
// Add members (default role: member)
|
|
975
991
|
await conversationsApi.addParticipants(group.id, ['user-d', 'user-e']);
|
|
976
992
|
|
|
@@ -1108,6 +1124,8 @@ const presigned = await storageApi.requestPresignedUrl({
|
|
|
1108
1124
|
mimeType: 'application/pdf',
|
|
1109
1125
|
size: 512000,
|
|
1110
1126
|
conversationId: 'conv-abc',
|
|
1127
|
+
// optional — stored on the server file record
|
|
1128
|
+
metadata: { compressed: true, originalSize: 900000, compressionAlgorithm: 'gzip' },
|
|
1111
1129
|
});
|
|
1112
1130
|
await platformUploadFn(presigned, file, (pct) => console.log(`${pct * 100}%`));
|
|
1113
1131
|
const fileRecord = await storageApi.confirmUpload(presigned.fileId);
|
|
@@ -1136,9 +1154,9 @@ When `platformCompressFn` is provided and `compression.enabled` is `true` (the d
|
|
|
1136
1154
|
1. Determine strategy per file (`image` → WebP/JPEG resize+encode, `gzip` → text/doc compression, `skip` → no-op)
|
|
1137
1155
|
2. Run `platformCompressFn(file, compressionConfig)` — returns a `CompressedFile`
|
|
1138
1156
|
3. If compressed result is **larger** than the original, the original is used instead (automatic fallback)
|
|
1139
|
-
4. Request presigned URL with the compressed size and
|
|
1157
|
+
4. Request presigned URL with the compressed size, MIME type, and compression metadata (`compressed`, `originalSize`, `compressionAlgorithm`)
|
|
1140
1158
|
5. Upload the compressed bytes
|
|
1141
|
-
6.
|
|
1159
|
+
6. Server persists `metadata.compressed`, `metadata.originalSize`, `metadata.compressionAlgorithm` on the file record alongside system fields (`userId`, `tenantId`)
|
|
1142
1160
|
|
|
1143
1161
|
### Strategy by file type
|
|
1144
1162
|
|
|
@@ -1534,6 +1552,9 @@ import { usersApi } from '@antzsoft/chat-core';
|
|
|
1534
1552
|
| `list` | `(params?: { query?: string; page?: number; limit?: number }) => Promise<PaginatedResponse<User>>` | List users, optionally filtered by a search query. Omit `page`/`limit` for all results. |
|
|
1535
1553
|
| `getById` | `(userId: string) => Promise<User>` | Fetch a single user by their chat system ID. |
|
|
1536
1554
|
| `getLastSeen` | `(userId: string) => Promise<{ lastSeenAt: string \| null }>` | Fetch a user's last-seen timestamp. Use on initial load; after that the store is kept live by `user_offline` socket events. |
|
|
1555
|
+
| `updateProfile` | `(payload: UpdateProfilePayload) => Promise<User>` | Update the current user's profile fields. Works in both builtin and non-builtin modes. |
|
|
1556
|
+
| `updatePreferences` | `(prefs: UserPreferences) => Promise<User>` | Partial update of notification preferences. |
|
|
1557
|
+
| `getPreferences` | `() => Promise<UserPreferences \| null>` | Fetch current notification preferences. Returns `null` if no record exists yet. |
|
|
1537
1558
|
|
|
1538
1559
|
All methods return `User` objects that include `externalId` for non-builtin modes.
|
|
1539
1560
|
|
|
@@ -1555,6 +1576,43 @@ const user = await usersApi.getById('64abc...');
|
|
|
1555
1576
|
console.log(user.displayName, user.externalId);
|
|
1556
1577
|
```
|
|
1557
1578
|
|
|
1579
|
+
#### `updateProfile` — immediate profile update
|
|
1580
|
+
|
|
1581
|
+
Use this to push a profile change to the chat server without waiting for the next sync cycle. Useful in **non-builtin (external/SSO) modes** when the host app knows a field just changed in the identity provider — e.g. the user updated their name in the main system.
|
|
1582
|
+
|
|
1583
|
+
```typescript
|
|
1584
|
+
import { usersApi, type UpdateProfilePayload } from '@antzsoft/chat-core';
|
|
1585
|
+
|
|
1586
|
+
await usersApi.updateProfile({
|
|
1587
|
+
firstName: 'Jane',
|
|
1588
|
+
lastName: 'Smith',
|
|
1589
|
+
displayName: 'Jane S.', // optional — auto-derived from firstName+lastName if omitted (builtin)
|
|
1590
|
+
email: 'jane@example.com',
|
|
1591
|
+
phone: '+919900000000',
|
|
1592
|
+
});
|
|
1593
|
+
```
|
|
1594
|
+
|
|
1595
|
+
**Behaviour by mode:**
|
|
1596
|
+
|
|
1597
|
+
| Field | Builtin | Non-builtin (external / SSO / WSO2) |
|
|
1598
|
+
|---|---|---|
|
|
1599
|
+
| `firstName` | Written directly | Written immediately; overwritten on next sync if user-service has a different value |
|
|
1600
|
+
| `lastName` | Written directly | Written immediately; overwritten on next sync if user-service has a different value |
|
|
1601
|
+
| `email` | Written directly | Written immediately; overwritten on next sync if user-service has a different value |
|
|
1602
|
+
| `displayName` | Written. Auto-derived from `firstName`+`lastName` if omitted | Written immediately |
|
|
1603
|
+
| `phone` | Written directly | Written immediately |
|
|
1604
|
+
| `username` | Not allowed | Not allowed |
|
|
1605
|
+
| `status` | Not allowed | Not allowed |
|
|
1606
|
+
|
|
1607
|
+
**Uniqueness constraints (both modes):**
|
|
1608
|
+
- `email` must be unique within the tenant → `409 Conflict` if another user holds it
|
|
1609
|
+
- `phone` must be unique within the tenant → `409 Conflict` if another user holds it
|
|
1610
|
+
|
|
1611
|
+
**Background sync in non-builtin mode:**
|
|
1612
|
+
- On `AntzChatClient` socket connect, if the user's shadow record is stale (> 6 hours since last sync), the server fires a single-user sync from the upstream user-service in the background.
|
|
1613
|
+
- The 2-hour bulk cron also keeps all shadow records up to date.
|
|
1614
|
+
- `updateProfile()` lets the host app push changes immediately rather than waiting for the next sync cycle.
|
|
1615
|
+
|
|
1558
1616
|
---
|
|
1559
1617
|
|
|
1560
1618
|
### App Config API (`appConfigApi`)
|
|
@@ -2048,9 +2106,6 @@ interface Message {
|
|
|
2048
2106
|
sender?: User;
|
|
2049
2107
|
readBy?: Array<{ userId: string; readAt: string }>;
|
|
2050
2108
|
deliveredTo?: Array<{ userId: string; deliveredAt: string }>;
|
|
2051
|
-
isEncrypted?: boolean;
|
|
2052
|
-
encryptionMode?: 'none' | 'server' | 'e2ee';
|
|
2053
|
-
encryptedContent?: EncryptedContent;
|
|
2054
2109
|
}
|
|
2055
2110
|
```
|
|
2056
2111
|
|
|
@@ -2123,9 +2178,6 @@ interface Conversation {
|
|
|
2123
2178
|
isPinned?: boolean;
|
|
2124
2179
|
isMuted?: boolean;
|
|
2125
2180
|
mutedUntil?: string;
|
|
2126
|
-
encryptionMode?: 'none' | 'server' | 'e2ee';
|
|
2127
|
-
isEncryptionEnabled?: boolean;
|
|
2128
|
-
encryptionKey?: string;
|
|
2129
2181
|
}
|
|
2130
2182
|
|
|
2131
2183
|
interface ConversationSettings {
|
|
@@ -2192,8 +2244,6 @@ interface SendMessagePayload {
|
|
|
2192
2244
|
attachments?: SendMessageAttachment[];
|
|
2193
2245
|
replyTo?: string; // messageId
|
|
2194
2246
|
tempId: string; // client-generated; echoed back in message_ack
|
|
2195
|
-
encryptedContent?: EncryptedContent;
|
|
2196
|
-
isEncrypted?: boolean;
|
|
2197
2247
|
}
|
|
2198
2248
|
|
|
2199
2249
|
interface SendMessageAttachment {
|
|
@@ -2401,10 +2451,32 @@ document.querySelectorAll('[data-conv-id]').forEach((el) => {
|
|
|
2401
2451
|
|
|
2402
2452
|
## Changelog
|
|
2403
2453
|
|
|
2454
|
+
### v1.1.3
|
|
2455
|
+
- **New: `conversationsApi.leave(conversationId, andDelete?)` — `andDelete` param** — Pass `andDelete: true` to exit a group and hide it from the caller's list in a single atomic server call ("Exit and Delete"). Previously required two separate calls.
|
|
2456
|
+
- **New: Auto-promote on last-admin exit** — When the only admin leaves a group, the server now automatically promotes the longest-standing active member to admin before completing the exit. Previously the server returned `400 Bad Request` requiring the admin to manually promote someone first.
|
|
2457
|
+
- **Changed: `conversationsApi.delete()` — no longer admin-only** — Any participant (active or inactive) can call `delete()` to hide their own copy of a DM or group. For DMs this is "Delete Chat". For groups this is "Delete Group" (for already-exited members). Other participants are unaffected.
|
|
2458
|
+
- **New: Message visibility windows (`membershipPeriods`)** — Each participant now tracks an array of membership time windows. The message fetch API enforces these windows so gap messages (sent while the user was not a member) are always hidden. Previously re-added users could see all messages including those sent during their absence.
|
|
2459
|
+
- **New: DM reappear on new message** — After a user deletes a DM, if the other participant sends a new message the conversation automatically reappears in the deleted user's list. The user sees only messages from the reappearance point onwards.
|
|
2460
|
+
- **New: `useConversations()` returns `leaveGroup`, `leaveAndDeleteGroup`, `deleteGroup` / `deleteConversation` mutations** — Available in both the web SDK and RN SDK hooks.
|
|
2461
|
+
|
|
2462
|
+
### v1.1.2
|
|
2463
|
+
- **New: `usersApi.updateProfile()`** — Update the current user's profile fields (`firstName`, `lastName`, `email`, `displayName`, `phone`) immediately without waiting for the next sync cycle. Works in both builtin and non-builtin modes. Email and phone are enforced unique per tenant (409 on conflict). In non-builtin modes, fields may be overwritten on the next background sync if user-service returns different values.
|
|
2464
|
+
- **New: On-connect per-user sync** — In non-builtin modes, `AntzChatClient.connect()` now triggers a background sync of the current user's profile from the upstream user-service if the shadow record is stale (> 6 hours). Previously only the 2-hour bulk cron kept shadow records updated.
|
|
2465
|
+
- **New: Phone number uniqueness enforced** — `phone` is now unique per tenant (schema index + pre-check on register and `updateProfile`). A `409 Conflict` is returned if another user in the same tenant already holds the phone number.
|
|
2466
|
+
- **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.
|
|
2467
|
+
- **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.
|
|
2468
|
+
- **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()`.
|
|
2469
|
+
- **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.
|
|
2470
|
+
- **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.
|
|
2471
|
+
- **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`.
|
|
2472
|
+
- **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.
|
|
2473
|
+
|
|
2404
2474
|
### v1.1.1
|
|
2405
2475
|
- **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
2476
|
- **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
2477
|
- **New: exported `ReplyAttachmentSnapshot` type.**
|
|
2478
|
+
- **Fix: Pin, unpin, and unmute operations** — Server-side issues causing pin, unpin, and unmute to fail in certain states are resolved.
|
|
2479
|
+
- **Fix: Various backend fixes** — Stability and correctness improvements across multiple server-side paths.
|
|
2408
2480
|
|
|
2409
2481
|
### v1.1.0
|
|
2410
2482
|
- **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.
|
|
@@ -2445,7 +2517,7 @@ document.querySelectorAll('[data-conv-id]').forEach((el) => {
|
|
|
2445
2517
|
- Fix: attachment last-message content preview
|
|
2446
2518
|
- Fix: avatar upload via `AntzChatClient`
|
|
2447
2519
|
- Fix: `client.connect()` on React Native
|
|
2448
|
-
- Group icon — create &
|
|
2520
|
+
- Group icon — create, update & remove
|
|
2449
2521
|
- Scroll to first unread message
|
|
2450
2522
|
- Push notifications — device token registration (RN & Web)
|
|
2451
2523
|
|