@antzsoft/chat-core 1.3.1 → 1.3.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 +268 -23
- package/dist/index.cjs +4 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/internal.d.cts +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/{storage-Df9ya7um.d.cts → storage-DdHsneZR.d.cts} +33 -2
- package/dist/{storage-Df9ya7um.d.ts → storage-DdHsneZR.d.ts} +33 -2
- package/docs/integration-guide.html +301 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -759,6 +759,7 @@ import { messagesApi } from '@antzsoft/chat-core';
|
|
|
759
759
|
| `getLastRead` | `(conversationId: string) => Promise<{ lastReadMessageId: string \| null; lastReadAt: string \| null }>` | Fetch the current user's last-read pointer for a conversation. Use on initial load; after that the store is kept live by socket events. |
|
|
760
760
|
| `markAsRead` | `(conversationId: string, messageId?: string) => Promise<void>` | Mark messages as read via REST. |
|
|
761
761
|
| `getReceipts` | `(messageId: string) => Promise<MessageReceiptsResponse>` | Fetch per-user read and delivery receipts for a single message, with resolved user profiles (name, avatar). Use as the initial load for a message info / "Read by" detail screen. |
|
|
762
|
+
| `getReactions` | `(messageId: string) => Promise<MessageReactionsResponse>` | Fetch the full reaction breakdown for a single message — grouped by emoji with per-user details and `didIReact`. Use for a reactions detail sheet. |
|
|
762
763
|
| `pin` | `(messageId: string) => Promise<Message>` | Pin a message. |
|
|
763
764
|
| `unpin` | `(messageId: string) => Promise<Message>` | Unpin a message. |
|
|
764
765
|
| `getPinned` | `(conversationId: string) => Promise<Message[]>` | List pinned messages in a conversation. |
|
|
@@ -1710,13 +1711,14 @@ import { syncApi } from '@antzsoft/chat-core';
|
|
|
1710
1711
|
import type {
|
|
1711
1712
|
CrossConversationSyncResponse,
|
|
1712
1713
|
ConversationSyncResponse,
|
|
1714
|
+
SyncDeliveredReceipt,
|
|
1713
1715
|
} from '@antzsoft/chat-core';
|
|
1714
1716
|
```
|
|
1715
1717
|
|
|
1716
1718
|
| Method | Signature | Description |
|
|
1717
1719
|
|---|---|---|
|
|
1718
1720
|
| `pull` | `(since: string) => Promise<CrossConversationSyncResponse>` | Cross-conversation delta sync. Call on every socket reconnect. `since` is the `syncedAt` value returned by the previous call (server clock — avoids client clock skew). |
|
|
1719
|
-
| `pullConversation` | `(conversationId: string, since?: string) => Promise<ConversationSyncResponse>` | Per-conversation
|
|
1721
|
+
| `pullConversation` | `(conversationId: string, opts?: { since?: string; afterSeq?: number; beforeSeq?: number }) => Promise<ConversationSyncResponse>` | Per-conversation sync. Prefer `afterSeq` (forward catch-up) or `beforeSeq` (backward scroll). Falls back to `since` timestamp for older servers. |
|
|
1720
1722
|
|
|
1721
1723
|
#### When to call each
|
|
1722
1724
|
|
|
@@ -1724,21 +1726,26 @@ import type {
|
|
|
1724
1726
|
|---|---|
|
|
1725
1727
|
| Socket reconnects (within stale threshold) | `syncApi.pull(lastSyncedAt)` — returns all cross-conversation changes |
|
|
1726
1728
|
| Socket reconnects (gap > stale threshold) | `pull()` returns `stale: true` — mark all conversations `needs_refresh`, lazy-sync on open |
|
|
1727
|
-
| User opens a stale conversation | `syncApi.pullConversation(convId,
|
|
1728
|
-
|
|
|
1729
|
+
| User opens a stale conversation (catch-up) | `syncApi.pullConversation(convId, { afterSeq: lastKnownSeq })` |
|
|
1730
|
+
| User scrolls back into unloaded history | `syncApi.pullConversation(convId, { beforeSeq: oldestLocalSeq })` |
|
|
1731
|
+
| Resume interrupted sync | Pass the `nextSeq` from the previous page as the same cursor type |
|
|
1732
|
+
| First sync / old server | `syncApi.pullConversation(convId, { since: lastSyncedAt })` |
|
|
1729
1733
|
|
|
1730
1734
|
#### `CrossConversationSyncResponse`
|
|
1731
1735
|
|
|
1732
|
-
Returned by `syncApi.pull(since)`.
|
|
1736
|
+
Returned by `syncApi.pull(since)`. As of v1.3.1, also includes `reactions` and `stars`.
|
|
1733
1737
|
|
|
1734
1738
|
```typescript
|
|
1735
1739
|
interface CrossConversationSyncResponse {
|
|
1736
|
-
syncedAt: string;
|
|
1737
|
-
stale: boolean;
|
|
1738
|
-
messages: Message[];
|
|
1740
|
+
syncedAt: string; // server-stamped ISO timestamp — store as new lastSyncedAt
|
|
1741
|
+
stale: boolean; // true when gap > CHAT_SYNC_STALE_DAYS (default 30)
|
|
1742
|
+
messages: Message[]; // edits, deletes, pins, unpins — any message with updatedAt > since
|
|
1739
1743
|
deletedForMe: SyncDeletedForMe[];
|
|
1740
1744
|
participantChanges: SyncParticipantChange[];
|
|
1741
1745
|
readReceipts: SyncReadReceipt[];
|
|
1746
|
+
deliveredReceipts: SyncDeliveredReceipt[]; // delivery receipts since `since`
|
|
1747
|
+
reactions: SyncReactions; // v1.3.1+ — full current state for messages with reaction activity
|
|
1748
|
+
stars: SyncStarEntry[]; // v1.3.1+ — star/unstar records since `since`
|
|
1742
1749
|
}
|
|
1743
1750
|
|
|
1744
1751
|
interface SyncDeletedForMe {
|
|
@@ -1763,25 +1770,44 @@ interface SyncReadReceipt {
|
|
|
1763
1770
|
userId: string;
|
|
1764
1771
|
readAt: string | null;
|
|
1765
1772
|
}
|
|
1773
|
+
|
|
1774
|
+
interface SyncDeliveredReceipt {
|
|
1775
|
+
messageId: string;
|
|
1776
|
+
conversationId: string;
|
|
1777
|
+
userId: string;
|
|
1778
|
+
deliveredAt: string | null;
|
|
1779
|
+
}
|
|
1766
1780
|
```
|
|
1767
1781
|
|
|
1768
1782
|
#### `ConversationSyncResponse`
|
|
1769
1783
|
|
|
1770
|
-
Returned by `syncApi.pullConversation(convId,
|
|
1784
|
+
Returned by `syncApi.pullConversation(convId, opts?)`. Supports seq-based pagination in both directions — call repeatedly following `hasMore` / `nextSeq` until `hasMore` is `false`.
|
|
1771
1785
|
|
|
1772
1786
|
```typescript
|
|
1773
1787
|
interface ConversationSyncResponse {
|
|
1774
1788
|
syncedAt: string;
|
|
1775
|
-
|
|
1789
|
+
/** Cursor for the next page — pass as afterSeq or beforeSeq matching what you sent */
|
|
1790
|
+
nextSeq?: number;
|
|
1791
|
+
/** True when more pages are available — call again with nextSeq as the same cursor type */
|
|
1792
|
+
hasMore?: boolean;
|
|
1793
|
+
messages: Message[];
|
|
1776
1794
|
deletedForMe: SyncDeletedForMe[];
|
|
1777
|
-
reactions: SyncReactions;
|
|
1778
|
-
stars: SyncStarEntry[];
|
|
1795
|
+
reactions: SyncReactions; // full current state for affected messages
|
|
1796
|
+
stars: SyncStarEntry[]; // isActive: false = unstarred
|
|
1779
1797
|
participantChanges: SyncParticipantChange[];
|
|
1780
1798
|
readReceipts: SyncReadReceipt[];
|
|
1799
|
+
deliveredReceipts: SyncDeliveredReceipt[];
|
|
1781
1800
|
}
|
|
1782
1801
|
|
|
1783
|
-
// keyed by messageId —
|
|
1784
|
-
|
|
1802
|
+
// keyed by messageId — bounded shape: emoji, count, didIReact only (v1.3.2+)
|
|
1803
|
+
// Full user list: call messagesApi.getReactions(messageId) on demand
|
|
1804
|
+
type SyncReactions = Record<string, SyncReactionEntry[]>;
|
|
1805
|
+
|
|
1806
|
+
interface SyncReactionEntry {
|
|
1807
|
+
emoji: string;
|
|
1808
|
+
count: number;
|
|
1809
|
+
didIReact: boolean; // true when the calling user has reacted with this emoji
|
|
1810
|
+
}
|
|
1785
1811
|
|
|
1786
1812
|
interface SyncStarEntry {
|
|
1787
1813
|
messageId: string;
|
|
@@ -1791,16 +1817,50 @@ interface SyncStarEntry {
|
|
|
1791
1817
|
}
|
|
1792
1818
|
```
|
|
1793
1819
|
|
|
1820
|
+
#### Seq-based pagination
|
|
1821
|
+
|
|
1822
|
+
`pullConversation` supports two directions. Always upsert each page to SQLite before requesting the next — this gives the user visible messages immediately and provides a safe resume point.
|
|
1823
|
+
|
|
1824
|
+
**Forward (catch-up) — oldest → newest:**
|
|
1825
|
+
```typescript
|
|
1826
|
+
// Use Conversation.lastSeq from the conversation list as the starting cursor
|
|
1827
|
+
let afterSeq = conversation.lastSeq ?? 0;
|
|
1828
|
+
|
|
1829
|
+
while (true) {
|
|
1830
|
+
const res = await syncApi.pullConversation(conversationId, { afterSeq });
|
|
1831
|
+
await upsertPageToSQLite(res);
|
|
1832
|
+
if (!res.hasMore || res.nextSeq == null) break;
|
|
1833
|
+
afterSeq = res.nextSeq;
|
|
1834
|
+
}
|
|
1835
|
+
```
|
|
1836
|
+
|
|
1837
|
+
**Backward (scroll-back) — newest → oldest:**
|
|
1838
|
+
```typescript
|
|
1839
|
+
let beforeSeq: number | null = null; // null = start from the latest messages
|
|
1840
|
+
|
|
1841
|
+
while (true) {
|
|
1842
|
+
const res = await syncApi.pullConversation(conversationId,
|
|
1843
|
+
beforeSeq != null ? { beforeSeq } : undefined,
|
|
1844
|
+
);
|
|
1845
|
+
await upsertPageToSQLite(res);
|
|
1846
|
+
if (!res.hasMore || res.nextSeq == null) break;
|
|
1847
|
+
beforeSeq = res.nextSeq;
|
|
1848
|
+
}
|
|
1849
|
+
```
|
|
1850
|
+
|
|
1851
|
+
The built-in `SyncService.syncConversation()` in the RN app uses the backward direction and handles the loop, cancellation on unmount, and cursor persistence automatically.
|
|
1852
|
+
|
|
1794
1853
|
#### What each field covers
|
|
1795
1854
|
|
|
1796
|
-
| Field |
|
|
1797
|
-
|
|
1798
|
-
| `messages` |
|
|
1799
|
-
| `deletedForMe` | Messages this user hid
|
|
1800
|
-
| `reactions` |
|
|
1801
|
-
| `stars` |
|
|
1802
|
-
| `participantChanges` | Role changes, mute/unmute, removals (`isActive: false`) |
|
|
1803
|
-
| `readReceipts` | Read receipts
|
|
1855
|
+
| Field | `pull()` | `pullConversation()` | What it captures |
|
|
1856
|
+
|---|---|---|---|
|
|
1857
|
+
| `messages` | ✅ | ✅ | Edits, deletes, pins/unpins. Each message includes `metadata`, `sender`, and signed attachment `url`s |
|
|
1858
|
+
| `deletedForMe` | ✅ | ✅ | Messages this user hid — remove from local DB |
|
|
1859
|
+
| `reactions` | ✅ v1.3.1 | ✅ | Full current `{ emoji, count, didIReact }` for affected messages. **Replace, never merge** |
|
|
1860
|
+
| `stars` | ✅ v1.3.1 | ✅ | `isActive: false` = unstarred — delete local star record |
|
|
1861
|
+
| `participantChanges` | ✅ | ✅ | Role changes, mute/unmute, removals (`isActive: false`) |
|
|
1862
|
+
| `readReceipts` | ✅ | ✅ | Read receipts — merge into `msg.readBy[]`, recompute status |
|
|
1863
|
+
| `deliveredReceipts` | ✅ | ✅ | Delivery receipts — merge into `msg.deliveredTo[]`, recompute status |
|
|
1804
1864
|
|
|
1805
1865
|
#### Stale handling
|
|
1806
1866
|
|
|
@@ -1861,9 +1921,10 @@ useEffect(() => {
|
|
|
1861
1921
|
| Message deleted (everyone) | ✅ | ✅ |
|
|
1862
1922
|
| Message deleted for me | ✅ | ✅ |
|
|
1863
1923
|
| Message pinned / unpinned | ✅ | ✅ |
|
|
1864
|
-
| Reaction added / removed |
|
|
1865
|
-
| Star / unstar |
|
|
1924
|
+
| Reaction added / removed | ✅ v1.3.1+ | ✅ |
|
|
1925
|
+
| Star / unstar | ✅ v1.3.1+ | ✅ with `isActive` |
|
|
1866
1926
|
| Read receipt | ✅ | ✅ |
|
|
1927
|
+
| Delivery receipt | ✅ | ✅ |
|
|
1867
1928
|
| Role change / mute / removal | ✅ | ✅ |
|
|
1868
1929
|
|
|
1869
1930
|
---
|
|
@@ -2532,6 +2593,7 @@ interface Message {
|
|
|
2532
2593
|
pinnedBy?: string;
|
|
2533
2594
|
pinnedAt?: string;
|
|
2534
2595
|
uploadProgress?: number; // 0–100, present on optimistic messages
|
|
2596
|
+
seq?: number; // v1.3.0+ — monotonic per-conversation counter; use as afterSeq cursor
|
|
2535
2597
|
sentAt: string;
|
|
2536
2598
|
createdAt: string;
|
|
2537
2599
|
sender?: User;
|
|
@@ -2614,6 +2676,41 @@ const receipts = await messagesApi.getReceipts(messageId);
|
|
|
2614
2676
|
|
|
2615
2677
|
---
|
|
2616
2678
|
|
|
2679
|
+
### `MessageReactionsResponse`
|
|
2680
|
+
|
|
2681
|
+
Returned by `messagesApi.getReactions()`. Use as the data source for a reactions detail sheet (list of who reacted with each emoji).
|
|
2682
|
+
|
|
2683
|
+
```typescript
|
|
2684
|
+
interface ReactionUser {
|
|
2685
|
+
userId: string;
|
|
2686
|
+
displayName: string;
|
|
2687
|
+
avatarUrl?: string;
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
interface ReactionGroup {
|
|
2691
|
+
emoji: string;
|
|
2692
|
+
userIds: string[]; // all user IDs who reacted with this emoji
|
|
2693
|
+
count: number;
|
|
2694
|
+
users: ReactionUser[]; // resolved displayName + avatarUrl
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
interface MessageReactionsResponse {
|
|
2698
|
+
messageId: string;
|
|
2699
|
+
reactions: ReactionGroup[];
|
|
2700
|
+
}
|
|
2701
|
+
```
|
|
2702
|
+
|
|
2703
|
+
```typescript
|
|
2704
|
+
// Load reaction breakdown for a detail sheet
|
|
2705
|
+
const data = await messagesApi.getReactions(messageId);
|
|
2706
|
+
for (const group of data.reactions) {
|
|
2707
|
+
// group.emoji, group.count, group.users (with displayName + avatarUrl)
|
|
2708
|
+
renderReactionSheet(group);
|
|
2709
|
+
}
|
|
2710
|
+
```
|
|
2711
|
+
|
|
2712
|
+
---
|
|
2713
|
+
|
|
2617
2714
|
### `MessageContent`
|
|
2618
2715
|
|
|
2619
2716
|
```typescript
|
|
@@ -2819,6 +2916,7 @@ interface MessageAckEvent {
|
|
|
2819
2916
|
tempId: string; // the client-generated ID you sent
|
|
2820
2917
|
messageId: string; // the server-assigned real ID
|
|
2821
2918
|
status: MessageStatus;
|
|
2919
|
+
seq?: number; // v1.3.0+ — seq assigned by the server; persist as last known seq for this conversation
|
|
2822
2920
|
}
|
|
2823
2921
|
|
|
2824
2922
|
interface MessageDeliveredEvent {
|
|
@@ -2956,6 +3054,153 @@ document.querySelectorAll('[data-conv-id]').forEach((el) => {
|
|
|
2956
3054
|
|
|
2957
3055
|
## Changelog
|
|
2958
3056
|
|
|
3057
|
+
### v1.3.3
|
|
3058
|
+
|
|
3059
|
+
- **New: `metadata` and `sender` fields in all sync message responses** — Messages returned by all three sync paths (`crossConversationSync`, `conversationSync`, `conversationSyncBySeq`) now include `metadata` (system message metadata — `actorUserId`, `targetUserId`, `action`) and `sender` (displayName, avatarUrl snapshot at send time). Both fields were already optional on the core SDK `Message` type so no SDK type changes were required. Previously absent from sync responses, meaning system message text could not be rendered and sender avatars were blank after an offline sync. The server batch-fetches sender profiles in a single `findByIds` call per sync request — one extra DB round trip per sync, not one per message. **No integration changes required.**
|
|
3060
|
+
|
|
3061
|
+
### v1.3.2
|
|
3062
|
+
|
|
3063
|
+
- **New: Bounded sync reaction payloads — `SyncReactionEntry` type replaces `MessageReaction[]`** — Previously, `SyncReactions` (the `reactions` field in `ConversationSyncResponse` and `CrossConversationSyncResponse`) was `Record<string, MessageReaction[]>` where `MessageReaction.userIds` was an unbounded string array. On high-traffic messages with hundreds of reactions, this could blow past the 4 MB adaptive page limit. The type is now `Record<string, SyncReactionEntry[]>` where each entry carries only `emoji`, `count`, and `didIReact` (whether the calling user reacted). Full user lists are fetched on demand via `messagesApi.getReactions()`. **Sync payload size is now O(messages × emojis) regardless of reaction count.**
|
|
3064
|
+
|
|
3065
|
+
```typescript
|
|
3066
|
+
// Old shape (removed)
|
|
3067
|
+
// reactions: Record<string, MessageReaction[]> // MessageReaction had userIds: string[]
|
|
3068
|
+
|
|
3069
|
+
// New shape
|
|
3070
|
+
interface SyncReactionEntry {
|
|
3071
|
+
emoji: string;
|
|
3072
|
+
count: number;
|
|
3073
|
+
didIReact: boolean; // true when the calling user has reacted with this emoji
|
|
3074
|
+
}
|
|
3075
|
+
type SyncReactions = Record<string, SyncReactionEntry[]>; // keyed by messageId
|
|
3076
|
+
```
|
|
3077
|
+
|
|
3078
|
+
**Migration** — clients reading `reactions[msgId][n].userIds` must switch to `reactions[msgId][n].count` and `didIReact`. For a full user list (e.g. "Who reacted" sheet), call `messagesApi.getReactions(messageId)`.
|
|
3079
|
+
|
|
3080
|
+
- **New: `messagesApi.getReactions(messageId)` — reactions detail sheet API** — New `POST /messages/:id/reactions/list` endpoint (GET mirror also available) returns the full per-emoji breakdown for a single message: counts, all reacting user IDs, and resolved user profiles. Use as the data source for a "Who reacted" bottom sheet. **New exported types: `MessageReactionsResponse`, `ReactionGroup`, `ReactionUser`.**
|
|
3081
|
+
|
|
3082
|
+
```typescript
|
|
3083
|
+
interface ReactionGroup {
|
|
3084
|
+
emoji: string;
|
|
3085
|
+
userIds: string[]; // all user IDs who reacted with this emoji
|
|
3086
|
+
count: number;
|
|
3087
|
+
users: ReactionUser[]; // resolved displayName + avatarUrl for each userId
|
|
3088
|
+
}
|
|
3089
|
+
|
|
3090
|
+
interface MessageReactionsResponse {
|
|
3091
|
+
messageId: string;
|
|
3092
|
+
reactions: ReactionGroup[];
|
|
3093
|
+
}
|
|
3094
|
+
```
|
|
3095
|
+
|
|
3096
|
+
- **Improvement: Adaptive page-halving in `conversationSyncBySeq`** — The seq-based sync endpoint now estimates response size before returning and halves the page limit (min 25) when the estimated payload would exceed 4 MB. Triggered only for pathological cases (message with hundreds of reactions). `SYNC_PAGE_LIMIT` reduced from 200 to 100. **No integration changes required.**
|
|
3097
|
+
|
|
3098
|
+
- **No breaking changes** — `SyncReactions` shape change is the only client-visible change; see migration note above.
|
|
3099
|
+
|
|
3100
|
+
### v1.3.1
|
|
3101
|
+
|
|
3102
|
+
- **New: Bidirectional seq pagination in `conversationSyncBySeq`** — The per-conversation seq-based sync endpoint now supports both directions:
|
|
3103
|
+
|
|
3104
|
+
| Direction | Param | Order | Use case |
|
|
3105
|
+
|---|---|---|---|
|
|
3106
|
+
| Forward (default) | `afterSeq` | oldest → newest | catch-up / sync on reconnect |
|
|
3107
|
+
| Backward | `beforeSeq` | newest → oldest | scroll-back / load older history |
|
|
3108
|
+
|
|
3109
|
+
Pass `beforeSeq` (exclusive upper bound) to page backward. The server validates mutual exclusivity — passing both is a `400 Bad Request`. The `nextSeq` in the response is the cursor for the next page in the same direction.
|
|
3110
|
+
|
|
3111
|
+
```typescript
|
|
3112
|
+
// Forward catch-up (default behavior, unchanged)
|
|
3113
|
+
const res = await syncApi.pullConversation(conversationId, { afterSeq: lastKnownSeq });
|
|
3114
|
+
|
|
3115
|
+
// Backward scroll (new)
|
|
3116
|
+
const res = await syncApi.pullConversation(conversationId, { beforeSeq: oldestLocalSeq });
|
|
3117
|
+
if (res.hasMore) {
|
|
3118
|
+
const nextPage = await syncApi.pullConversation(conversationId, { beforeSeq: res.nextSeq! });
|
|
3119
|
+
}
|
|
3120
|
+
```
|
|
3121
|
+
|
|
3122
|
+
- **New: `reactions` and `stars` in `CrossConversationSyncResponse`** — `syncApi.pull(since)` now returns `reactions: SyncReactions` and `stars: SyncStarEntry[]` alongside the existing fields. These were previously only available from `pullConversation`. Cross-conversation catch-up now covers the full delta including reaction and star changes since the cursor.
|
|
3123
|
+
|
|
3124
|
+
```typescript
|
|
3125
|
+
// CrossConversationSyncResponse now includes:
|
|
3126
|
+
reactions: SyncReactions; // full current state for messages with reaction activity > since
|
|
3127
|
+
stars: SyncStarEntry[]; // star/unstar records since since; isActive: false = unstarred
|
|
3128
|
+
```
|
|
3129
|
+
|
|
3130
|
+
**Shape consistency** — stale responses and empty-conversations early returns now also include `reactions: {}` and `stars: []` so the response shape is always uniform.
|
|
3131
|
+
|
|
3132
|
+
- **New: `useConversationSync` direction option (RN SDK)** — The `@antzsoft/chat-rn-sdk` `useConversationSync` hook accepts a `direction` option (`'forward' | 'backward'`, default `'forward'`). Pass `direction: 'backward'` with `beforeSeq` to trigger a scroll-back page load. The hook automatically uses the stored seq cursor for forward syncs.
|
|
3133
|
+
|
|
3134
|
+
- **No breaking changes** — `pullConversation` opts are additive. `CrossConversationSyncResponse` gains two new fields that clients should merge the same way as `ConversationSyncResponse`.
|
|
3135
|
+
|
|
3136
|
+
### v1.3.0
|
|
3137
|
+
|
|
3138
|
+
- **New: `seq` and `lastSeq` — per-conversation monotonic message sequence numbers** — Every message now carries a `seq` field: a per-conversation monotonically increasing integer assigned at send time. The server maintains an atomic Redis counter per conversation (`chat:seq:{conversationId}`), flushed to MongoDB every 60 s. `seq` appears on all message responses (REST and socket), on `Message.seq`, and on the `MessageAckEvent.seq` field.
|
|
3139
|
+
|
|
3140
|
+
`Conversation.lastSeq` is the highest `seq` seen in that conversation — returned by all conversation list/get endpoints and updated on every `conversation_updated` socket event via the new `ConversationUpdatedEvent.lastSeq` field. Use it as the `afterSeq` cursor for `pullConversation` to catch up on exactly the messages you missed.
|
|
3141
|
+
|
|
3142
|
+
```typescript
|
|
3143
|
+
// New fields on existing types:
|
|
3144
|
+
interface Message {
|
|
3145
|
+
seq?: number; // monotonic per-conversation counter
|
|
3146
|
+
// ...
|
|
3147
|
+
}
|
|
3148
|
+
|
|
3149
|
+
interface Conversation {
|
|
3150
|
+
lastSeq?: number; // highest seq in this conversation — use as afterSeq cursor
|
|
3151
|
+
// ...
|
|
3152
|
+
}
|
|
3153
|
+
|
|
3154
|
+
interface MessageAckEvent {
|
|
3155
|
+
seq?: number; // seq assigned to this message by the server
|
|
3156
|
+
// ...
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
interface ConversationUpdatedEvent { // NEW type (v1.3.0)
|
|
3160
|
+
id: string;
|
|
3161
|
+
lastMessage?: Partial<Message> | null;
|
|
3162
|
+
unreadCount?: number;
|
|
3163
|
+
updatedAt?: string;
|
|
3164
|
+
lastSeq?: number; // advance your afterSeq cursor on every message
|
|
3165
|
+
participants?: Participant[];
|
|
3166
|
+
}
|
|
3167
|
+
```
|
|
3168
|
+
|
|
3169
|
+
- **New: `conversationSyncBySeq` server endpoint** — `GET /conversations/:id/sync?afterSeq=N` returns the next page of messages starting from seq N+1 (forward direction). Prefer this over timestamp-based sync when `Conversation.lastSeq` is available — seq cursors are collision-safe, never require a clock comparison, and enable exact-page pagination. Returns `hasMore: boolean` and `nextSeq: number | null` for looping.
|
|
3170
|
+
|
|
3171
|
+
- **New: `useConvSeq` / `setConvSeq` (RN SDK)** — `@antzsoft/chat-rn-sdk` exports `getConvSeq(conversationId)` and `setConvSeq(conversationId, seq)` for persisting the per-conversation seq cursor in AsyncStorage. `useConversationSync` in the RN SDK now automatically uses the stored seq for forward syncs — no code changes needed in the host app.
|
|
3172
|
+
|
|
3173
|
+
- **Migration 006** — `006_backfill_message_seq` runs automatically on first deploy. Assigns sequential `seq` values to all existing messages in each conversation (ordered by `createdAt`). Idempotent — safe to run multiple times.
|
|
3174
|
+
|
|
3175
|
+
- **New exported type: `ConversationUpdatedEvent`.**
|
|
3176
|
+
|
|
3177
|
+
- **No breaking changes** — `seq` and `lastSeq` are additive optional fields. Clients that don't use them are unaffected.
|
|
3178
|
+
|
|
3179
|
+
### v1.2.9
|
|
3180
|
+
|
|
3181
|
+
- **New: `deliveredReceipts[]` in sync responses — delivery tick marks now survive offline gaps** — Both `CrossConversationSyncResponse` (from `syncApi.pull()`) and `ConversationSyncResponse` (from `syncApi.pullConversation()`) now include a `deliveredReceipts: SyncDeliveredReceipt[]` field. This mirrors the existing `readReceipts[]` pattern and is sourced from the `chat_message_deliveries` time-series collection. Mobile apps merge these into each message's `deliveredTo[]` array and recompute `status` via `resolveMessageStatus` — single-tick → double-tick transitions that occurred while offline are now correctly replayed on reconnect.
|
|
3182
|
+
|
|
3183
|
+
```typescript
|
|
3184
|
+
interface SyncDeliveredReceipt {
|
|
3185
|
+
messageId: string;
|
|
3186
|
+
conversationId: string;
|
|
3187
|
+
userId: string;
|
|
3188
|
+
deliveredAt: string | null;
|
|
3189
|
+
}
|
|
3190
|
+
```
|
|
3191
|
+
|
|
3192
|
+
**No integration changes required** — the `SyncService.applyShared()` in the RN app already handles `deliveredReceipts` via `ChatRepository.applyDeliveredReceipts()`. The field is absent on older server versions and is safely ignored by any older client. **New exported type: `SyncDeliveredReceipt`.**
|
|
3193
|
+
|
|
3194
|
+
- **Fix: Attachment URLs in sync messages are now properly signed** — Previously, sync responses (`pullAll` / `pullConversation`) returned raw `storageKey`, `provider`, and `bucket` fields for attachment records. These fields are internal storage identifiers — clients cannot use them to render images or play audio. The sync service now generates signed, short-lived `url`s for all attachments (same Redis-cached signing logic as the message list API, 2400 s cache / 3600 s expiry). The raw storage fields are stripped from the sync response. **Clients that were silently ignoring `storageKey` now receive a working `url`.**
|
|
3195
|
+
|
|
3196
|
+
- **Fix: `replyTo.attachmentSnapshot.url` now signed in sync** — The `replyTo.attachmentSnapshot` object (thumbnail of the quoted attachment shown in reply bubbles) was also missing a signed `url` in sync responses. The attachment snapshot now carries a properly signed `url` alongside its `type`, `filename`, `mimeType`, `size`, `duration`, and `dimensions`. `storageKey` / `provider` / `bucket` are not included in the snapshot output.
|
|
3197
|
+
|
|
3198
|
+
- **Fix: `replyTo.messageId` now serialized as a string in sync** — `replyTo.messageId` was returned as a MongoDB `ObjectId` object in sync responses due to a missing `.toString()` call on the lean document. It is now always a plain string, consistent with all other ID fields. No client-side workaround needed.
|
|
3199
|
+
|
|
3200
|
+
- **Fix: `applyReadReceipts` now recomputes `status` after merge** — The mobile `ChatRepository.applyReadReceipts()` function was merging the new read entries into `msg.readBy[]` but not re-running `resolveMessageStatus`. Messages whose `readBy` grew to include all recipients after a sync were not being promoted to `status: 'read'` — tick marks stayed at double-tick even when all recipients had read the message. Fixed by calling `resolveMessageStatus(updated)` and persisting the result after every merge.
|
|
3201
|
+
|
|
3202
|
+
- **No breaking changes** — all additions are purely additive. `deliveredReceipts` is absent on older server versions and treated as an empty array by any client. Existing sync handling, socket events, and API contracts are unchanged.
|
|
3203
|
+
|
|
2959
3204
|
### v1.2.8
|
|
2960
3205
|
|
|
2961
3206
|
- **New: `deliveryStatus` on `Conversation.lastMessage`** — The conversation list API (`GET /conversations`) and the `conversation_updated` socket event now include `deliveryStatus` on the `lastMessage` object. This is the tick state (`'sent'` | `'delivered'` | `'read'`) of the last message as seen by the sender. It is computed and stored directly on the conversation document — no extra queries at list time.
|
package/dist/index.cjs
CHANGED
|
@@ -1055,6 +1055,10 @@ var messagesApi = {
|
|
|
1055
1055
|
);
|
|
1056
1056
|
return data;
|
|
1057
1057
|
},
|
|
1058
|
+
async getReactions(messageId) {
|
|
1059
|
+
const { data } = await getApiClient().post(`/messages/${messageId}/reactions/list`);
|
|
1060
|
+
return data;
|
|
1061
|
+
},
|
|
1058
1062
|
async star(messageId) {
|
|
1059
1063
|
await getApiClient().post(`/messages/${messageId}/star`);
|
|
1060
1064
|
},
|