@antzsoft/chat-core 1.4.2 → 1.4.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 +34 -5
- package/dist/index.cjs +670 -625
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -5
- package/dist/index.d.ts +46 -5
- package/dist/index.js +449 -404
- package/dist/index.js.map +1 -1
- package/dist/internal.d.cts +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/{storage-BqygfDAK.d.cts → storage-C8V7aVum.d.cts} +26 -2
- package/dist/{storage-BqygfDAK.d.ts → storage-C8V7aVum.d.ts} +26 -2
- package/docs/integration-guide.html +187 -3
- package/package.json +1 -1
package/dist/internal.d.cts
CHANGED
package/dist/internal.d.ts
CHANGED
|
@@ -136,7 +136,9 @@ interface MessageReplyReference {
|
|
|
136
136
|
* back (the message actually forwarded), never the root of a longer forward chain — the
|
|
137
137
|
* true original author/conversation is intentionally not reconstructable after more than
|
|
138
138
|
* one hop, matching WhatsApp's forwarding privacy model. `forwardDepth` still accumulates
|
|
139
|
-
* across the whole chain, so "Forwarded many times" (depth >=
|
|
139
|
+
* across the whole chain, so "Forwarded many times" (depth >= HIGHLY_FORWARDED_DEPTH_THRESHOLD,
|
|
140
|
+
* exported from '../api/messages.js', currently 5 — the same value that triggers the
|
|
141
|
+
* server's fan-out-to-1 cap) keeps working.
|
|
140
142
|
*/
|
|
141
143
|
interface MessageForwardReference {
|
|
142
144
|
originalMessageId: string;
|
|
@@ -600,6 +602,28 @@ interface SendMessagePayload {
|
|
|
600
602
|
/** Mentioned userIds (use `"all"` / MENTION_ALL_ID for @all). Derived from the tokens in `text`. */
|
|
601
603
|
mentions?: string[];
|
|
602
604
|
}
|
|
605
|
+
interface ForwardMessagePayload {
|
|
606
|
+
messageId: string;
|
|
607
|
+
targetConversationIds: string[];
|
|
608
|
+
/** Omit to forward the whole source message, including all its attachments, unchanged. */
|
|
609
|
+
attachmentIds?: string[];
|
|
610
|
+
/** Client-generated idempotency key — see ForwardData.tempId (api/messages.ts) for the full contract. */
|
|
611
|
+
tempId: string;
|
|
612
|
+
}
|
|
613
|
+
/** Per-target result for a 'forward_message' socket call. Mirrors the REST ForwardResult shape. */
|
|
614
|
+
interface ForwardResultPayload {
|
|
615
|
+
conversationId: string;
|
|
616
|
+
success: boolean;
|
|
617
|
+
message?: Message;
|
|
618
|
+
error?: string;
|
|
619
|
+
}
|
|
620
|
+
/** Ack for the 'forward_message' socket event. */
|
|
621
|
+
interface ForwardAckPayload {
|
|
622
|
+
tempId?: string;
|
|
623
|
+
results: ForwardResultPayload[];
|
|
624
|
+
/** Present only when the server-side forward() call itself threw (e.g. source message not found) — results is [] in that case. */
|
|
625
|
+
error?: string;
|
|
626
|
+
}
|
|
603
627
|
interface QuietHours {
|
|
604
628
|
enabled: boolean;
|
|
605
629
|
/** HH:MM — e.g. "22:00" */
|
|
@@ -866,4 +890,4 @@ declare function uploadBatchWithSlots(files: UploadableFile[], platformUploadFn:
|
|
|
866
890
|
slotToFile: Map<string, FileResponse>;
|
|
867
891
|
}>;
|
|
868
892
|
|
|
869
|
-
export { type
|
|
893
|
+
export { type MessageStarUpdatedEvent as $, type AuthResponse as A, type BatchUploadResult as B, type CrossConversationSyncResponse as C, type CompressionConfig as D, type ConversationType as E, type ForwardMessagePayload as F, type ConversationUpdatedEvent as G, type FileSizeLimits as H, type ForwardResultPayload as I, type LastReaction as J, MENTION_ALL_ID as K, type LoginCredentials as L, type Message as M, type MessageAckEvent as N, type MessageContent as O, type PaginatedResponse as P, type MessageDeletedEvent as Q, type ResolvedCompressionConfig as R, type SendMessagePayload as S, type MessageDeletedForMeEvent as T, type User as U, type MessageDeliveredEvent as V, type MessageForwardReference as W, type MessageMetadata as X, type MessageReaction as Y, type MessageReceiptEntry as Z, type MessageReplyReference as _, type RegisterData as a, type MessageUpdatedEvent as a0, type MessagesDeliveredEvent as a1, type MultipartPartUrl as a2, type MultipartUploadInfo as a3, type NewMessageEvent as a4, type OptimisticAttachment as a5, type PlatformCompressFn as a6, type PlatformUploadFn as a7, type PlatformUploadPartFn as a8, type QuietHours as a9, type ReactionGroup as aa, type ReactionUpdatedEvent as ab, type ReactionUser as ac, type ReadReceiptEvent as ad, type ReplyAttachmentSnapshot as ae, type ResolvedFileSizeLimits as af, type SendMessageAttachment as ag, type SyncDeletedForMe as ah, type SyncDeliveredReceipt as ai, type SyncParticipantChange as aj, type SyncReactionEntry as ak, type SyncReactions as al, type SyncReadReceipt as am, type SyncStarEntry as an, type SystemMessageMetadata as ao, type TypingIndicatorEvent as ap, type UploadConfig as aq, type UploadProgress as ar, type UserStatusEvent as as, resolveConfig as at, storageApi as au, uploadBatch as av, uploadBatchWithSlots as aw, type AuthTokens as b, type ConversationSyncResponse as c, type AppConfig as d, type CursorPaginatedResponse as e, type MessageReactionsResponse as f, type MessageReceiptsResponse as g, type ConversationListParams as h, type Conversation as i, type Participant as j, type ConversationUnreadCount as k, type UnreadSummary as l, type UserPreferences as m, type ResolvedConfig as n, type ForwardAckPayload as o, type PersistStorage as p, type PresignedUrlRequest as q, type PresignedUrlResponse as r, type FileResponse as s, type CompletedPart as t, type FileType as u, type AntzChatConfig as v, type UploadableFile as w, type Attachment as x, type CompressedFile as y, type CompressionAlgorithm as z };
|
|
@@ -136,7 +136,9 @@ interface MessageReplyReference {
|
|
|
136
136
|
* back (the message actually forwarded), never the root of a longer forward chain — the
|
|
137
137
|
* true original author/conversation is intentionally not reconstructable after more than
|
|
138
138
|
* one hop, matching WhatsApp's forwarding privacy model. `forwardDepth` still accumulates
|
|
139
|
-
* across the whole chain, so "Forwarded many times" (depth >=
|
|
139
|
+
* across the whole chain, so "Forwarded many times" (depth >= HIGHLY_FORWARDED_DEPTH_THRESHOLD,
|
|
140
|
+
* exported from '../api/messages.js', currently 5 — the same value that triggers the
|
|
141
|
+
* server's fan-out-to-1 cap) keeps working.
|
|
140
142
|
*/
|
|
141
143
|
interface MessageForwardReference {
|
|
142
144
|
originalMessageId: string;
|
|
@@ -600,6 +602,28 @@ interface SendMessagePayload {
|
|
|
600
602
|
/** Mentioned userIds (use `"all"` / MENTION_ALL_ID for @all). Derived from the tokens in `text`. */
|
|
601
603
|
mentions?: string[];
|
|
602
604
|
}
|
|
605
|
+
interface ForwardMessagePayload {
|
|
606
|
+
messageId: string;
|
|
607
|
+
targetConversationIds: string[];
|
|
608
|
+
/** Omit to forward the whole source message, including all its attachments, unchanged. */
|
|
609
|
+
attachmentIds?: string[];
|
|
610
|
+
/** Client-generated idempotency key — see ForwardData.tempId (api/messages.ts) for the full contract. */
|
|
611
|
+
tempId: string;
|
|
612
|
+
}
|
|
613
|
+
/** Per-target result for a 'forward_message' socket call. Mirrors the REST ForwardResult shape. */
|
|
614
|
+
interface ForwardResultPayload {
|
|
615
|
+
conversationId: string;
|
|
616
|
+
success: boolean;
|
|
617
|
+
message?: Message;
|
|
618
|
+
error?: string;
|
|
619
|
+
}
|
|
620
|
+
/** Ack for the 'forward_message' socket event. */
|
|
621
|
+
interface ForwardAckPayload {
|
|
622
|
+
tempId?: string;
|
|
623
|
+
results: ForwardResultPayload[];
|
|
624
|
+
/** Present only when the server-side forward() call itself threw (e.g. source message not found) — results is [] in that case. */
|
|
625
|
+
error?: string;
|
|
626
|
+
}
|
|
603
627
|
interface QuietHours {
|
|
604
628
|
enabled: boolean;
|
|
605
629
|
/** HH:MM — e.g. "22:00" */
|
|
@@ -866,4 +890,4 @@ declare function uploadBatchWithSlots(files: UploadableFile[], platformUploadFn:
|
|
|
866
890
|
slotToFile: Map<string, FileResponse>;
|
|
867
891
|
}>;
|
|
868
892
|
|
|
869
|
-
export { type
|
|
893
|
+
export { type MessageStarUpdatedEvent as $, type AuthResponse as A, type BatchUploadResult as B, type CrossConversationSyncResponse as C, type CompressionConfig as D, type ConversationType as E, type ForwardMessagePayload as F, type ConversationUpdatedEvent as G, type FileSizeLimits as H, type ForwardResultPayload as I, type LastReaction as J, MENTION_ALL_ID as K, type LoginCredentials as L, type Message as M, type MessageAckEvent as N, type MessageContent as O, type PaginatedResponse as P, type MessageDeletedEvent as Q, type ResolvedCompressionConfig as R, type SendMessagePayload as S, type MessageDeletedForMeEvent as T, type User as U, type MessageDeliveredEvent as V, type MessageForwardReference as W, type MessageMetadata as X, type MessageReaction as Y, type MessageReceiptEntry as Z, type MessageReplyReference as _, type RegisterData as a, type MessageUpdatedEvent as a0, type MessagesDeliveredEvent as a1, type MultipartPartUrl as a2, type MultipartUploadInfo as a3, type NewMessageEvent as a4, type OptimisticAttachment as a5, type PlatformCompressFn as a6, type PlatformUploadFn as a7, type PlatformUploadPartFn as a8, type QuietHours as a9, type ReactionGroup as aa, type ReactionUpdatedEvent as ab, type ReactionUser as ac, type ReadReceiptEvent as ad, type ReplyAttachmentSnapshot as ae, type ResolvedFileSizeLimits as af, type SendMessageAttachment as ag, type SyncDeletedForMe as ah, type SyncDeliveredReceipt as ai, type SyncParticipantChange as aj, type SyncReactionEntry as ak, type SyncReactions as al, type SyncReadReceipt as am, type SyncStarEntry as an, type SystemMessageMetadata as ao, type TypingIndicatorEvent as ap, type UploadConfig as aq, type UploadProgress as ar, type UserStatusEvent as as, resolveConfig as at, storageApi as au, uploadBatch as av, uploadBatchWithSlots as aw, type AuthTokens as b, type ConversationSyncResponse as c, type AppConfig as d, type CursorPaginatedResponse as e, type MessageReactionsResponse as f, type MessageReceiptsResponse as g, type ConversationListParams as h, type Conversation as i, type Participant as j, type ConversationUnreadCount as k, type UnreadSummary as l, type UserPreferences as m, type ResolvedConfig as n, type ForwardAckPayload as o, type PersistStorage as p, type PresignedUrlRequest as q, type PresignedUrlResponse as r, type FileResponse as s, type CompletedPart as t, type FileType as u, type AntzChatConfig as v, type UploadableFile as w, type Attachment as x, type CompressedFile as y, type CompressionAlgorithm as z };
|
|
@@ -155,7 +155,7 @@ section.sec>h2:hover{color:#fff}
|
|
|
155
155
|
|
|
156
156
|
<div class="section-label">What's New</div>
|
|
157
157
|
<ul>
|
|
158
|
-
<li><a href="#whats-new">v1.4.
|
|
158
|
+
<li><a href="#whats-new">v1.4.4 Release Notes</a></li>
|
|
159
159
|
</ul>
|
|
160
160
|
|
|
161
161
|
<div class="section-label">Getting Started</div>
|
|
@@ -196,6 +196,7 @@ section.sec>h2:hover{color:#fff}
|
|
|
196
196
|
<li><a href="#step-read">12. Read Receipts & Last Seen</a></li>
|
|
197
197
|
<li><a href="#step-message-info">12b. Message Info Screen</a></li>
|
|
198
198
|
<li><a href="#step-edit">13. Edit & Delete</a></li>
|
|
199
|
+
<li><a href="#step-forward">13b. Forward a Message</a></li>
|
|
199
200
|
<li><a href="#step-search">14. Search Messages</a></li>
|
|
200
201
|
<li><a href="#step-reactions">15. Reactions, Pin, Star</a></li>
|
|
201
202
|
</ul>
|
|
@@ -250,12 +251,149 @@ section.sec>h2:hover{color:#fff}
|
|
|
250
251
|
<h2>What's New</h2>
|
|
251
252
|
<p style="color:var(--muted);font-size:13px;margin-bottom:20px">Version history and release notes. Click a version to expand.</p>
|
|
252
253
|
|
|
253
|
-
<!-- ── v1.4.
|
|
254
|
+
<!-- ── v1.4.4 (current) ── -->
|
|
255
|
+
<div class="wn-version open" id="wn-144">
|
|
256
|
+
<div class="wn-header" onclick="toggleVersion('wn-144')">
|
|
257
|
+
<div class="wn-title">
|
|
258
|
+
<span class="wn-ver">v1.4.4</span>
|
|
259
|
+
<span class="wn-badge current">Current</span>
|
|
260
|
+
<span class="wn-date">July 2026</span>
|
|
261
|
+
</div>
|
|
262
|
+
<span class="wn-chevron">▲</span>
|
|
263
|
+
</div>
|
|
264
|
+
<div class="wn-body">
|
|
265
|
+
<div class="wn-item" id="wn-144-forwardsocket">
|
|
266
|
+
<div class="wn-item-header" onclick="toggleItem('wn-144-forwardsocket')">
|
|
267
|
+
<span class="wn-tag new">New</span>
|
|
268
|
+
<span class="wn-item-title">Forward now goes over the socket, not just REST</span>
|
|
269
|
+
<span class="wn-chevron-sm">▾</span>
|
|
270
|
+
</div>
|
|
271
|
+
<div class="wn-item-body">
|
|
272
|
+
<p>New server-side <code>forward_message</code> socket event, acked via the standard socket.io callback (the same request/response ack pattern as <code>update_message</code>/<code>pin_message</code> — not a separate broadcast event), plus a matching <code>socketEmit.forwardMessage(payload): Promise<ForwardAckPayload></code> in this package.</p>
|
|
273
|
+
<p><code>messagesApi.forward()</code> uses the socket transport automatically when a socket is connected — the exact same server-side <code>MessagesService.forward()</code> code path either way, so <code>new_message</code> broadcast / <code>conversation_updated</code> / push-notification behavior is byte-for-byte identical, just lower latency — and transparently falls back to REST when no socket is connected.</p>
|
|
274
|
+
<p><strong>New exported types:</strong> <code>ForwardMessagePayload</code>, <code>ForwardResultPayload</code>, <code>ForwardAckPayload</code>. <code>ForwardResultPayload.message</code> is the same fully-shaped message object either transport returns (server-side <code>MessageResponseDto</code>, matching this package's <code>Message</code> type) — not the lighter <code>new_message</code> broadcast shape.</p>
|
|
275
|
+
<p><strong>Backward compatible, additive-only. No integration changes required</strong> — existing <code>messagesApi.forward()</code> / <code>useChat().forwardMessage()</code> call sites in both UI SDKs pick this up automatically.</p>
|
|
276
|
+
<p class="wn-ref">→ <a href="#step-forward">Step 13b — Forward a Message</a> for the full transport note and usage.</p>
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
<div class="wn-item" id="wn-144-forwardretry">
|
|
280
|
+
<div class="wn-item-header" onclick="toggleItem('wn-144-forwardretry')">
|
|
281
|
+
<span class="wn-tag new">New</span>
|
|
282
|
+
<span class="wn-item-title">Forward retry safety — client-generated tempId prevents duplicate forwards</span>
|
|
283
|
+
<span class="wn-chevron-sm">▾</span>
|
|
284
|
+
</div>
|
|
285
|
+
<div class="wn-item-body">
|
|
286
|
+
<p><code>messagesApi.forward()</code> gained a fourth optional parameter, <code>tempId?: string</code> — a client-generated key that makes retrying a timed-out forward safe. Generate one per forward <em>action</em> (not per target), reuse the same value on retry, and the server recognizes a matching <code>(targetConversationId, senderId, tempId)</code> and returns the already-created message for that target instead of creating a duplicate, while still retrying any target that genuinely failed.</p>
|
|
287
|
+
<p>Auto-generated if omitted, but an omitted/regenerated key means retries get <strong>no dedup protection</strong>. The built-in <code>ForwardPicker</code> in both UI SDKs already does this correctly with zero extra wiring (one <code>tempId</code> per picker session, reused across retries).</p>
|
|
288
|
+
<p><code>Message.tempId?: string</code> — present on a message created via <code>forward()</code> (or <code>send()</code>) when a <code>tempId</code> was supplied. Purely a server-side dedup key; not intended for UI rendering.</p>
|
|
289
|
+
<p class="wn-ref">→ <a href="#step-forward">Step 13b — Forward a Message</a> (Retry safety)</p>
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
<div class="wn-item" id="wn-144-sendretry">
|
|
293
|
+
<div class="wn-item-header" onclick="toggleItem('wn-144-sendretry')">
|
|
294
|
+
<span class="wn-tag new">New</span>
|
|
295
|
+
<span class="wn-item-title">Send retry safety — regular sends get the same dedup protection forward already had</span>
|
|
296
|
+
<span class="wn-chevron-sm">▾</span>
|
|
297
|
+
</div>
|
|
298
|
+
<div class="wn-item-body">
|
|
299
|
+
<p>The server now checks <code>(conversationId, senderId, tempId)</code> before creating a message on <strong>both</strong> the REST path (<code>messagesApi.send()</code>) and the socket path (<code>socketEmit.sendMessage</code>, which already required <code>tempId</code> on every call — this was previously ignored server-side). A retry with the same <code>tempId</code> returns the already-created message instead of duplicating it.</p>
|
|
300
|
+
<p>On the socket path, a retry correctly suppresses a second <code>new_message</code> broadcast and a second push notification (the original successful attempt already delivered both) — only the retrying client's own <code>message_ack</code> is (re-)sent, so their optimistic bubble still reconciles.</p>
|
|
301
|
+
<p>Unlike <code>forward()</code>, <code>send()</code> does <strong>not</strong> auto-generate a <code>tempId</code> if you omit it — omitting it, or generating a fresh one on every retry, gets no dedup protection.</p>
|
|
302
|
+
<p><strong><code>useChat().retrySendMessage(messageId)</code></strong> — new in both <code>@antzsoft/chat-web-sdk</code> and <code>@antzsoft/chat-rn-sdk</code>. Resends a message whose <code>deliveryStatus</code> is <code>'failed'</code>, reusing the exact original payload (same <code>tempId</code>, same already-uploaded attachment <code>fileId</code>s) — never re-uploads files, never mints a new <code>tempId</code>. The built-in <code>MessageItem</code> in both UI SDKs now renders a tappable "Retry" affordance on the failed-delivery indicator and a "Retry" entry in the message action menu.</p>
|
|
303
|
+
<p class="wn-ref">→ <a href="#step-send">Step 9 — Send a Message</a></p>
|
|
304
|
+
</div>
|
|
305
|
+
</div>
|
|
306
|
+
<div class="wn-item" id="wn-144-race">
|
|
307
|
+
<div class="wn-item-header" onclick="toggleItem('wn-144-race')">
|
|
308
|
+
<span class="wn-tag fix">Fix</span>
|
|
309
|
+
<span class="wn-item-title">Closed a race window in the dedup check for concurrent duplicate sends/forwards</span>
|
|
310
|
+
<span class="wn-chevron-sm">▾</span>
|
|
311
|
+
</div>
|
|
312
|
+
<div class="wn-item-body">
|
|
313
|
+
<p>Two concurrent deliveries of the same send or forward request (e.g. a network-layer duplicate, or a client double-submit) could previously both pass the "does this already exist" check before either had written, producing two messages for one logical action. The server's <code>(conversationId, senderId, tempId)</code> index is now a unique constraint (partial — only enforced when <code>tempId</code> is present), and the loser of that race resolves to the winner's document instead of erroring.</p>
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
</div><!-- /.wn-version -->
|
|
318
|
+
|
|
319
|
+
<!-- ── v1.4.3 ── -->
|
|
320
|
+
<div class="wn-version open" id="wn-143">
|
|
321
|
+
<div class="wn-header" onclick="toggleVersion('wn-143')">
|
|
322
|
+
<div class="wn-title">
|
|
323
|
+
<span class="wn-ver">v1.4.3</span>
|
|
324
|
+
<span class="wn-date">July 2026</span>
|
|
325
|
+
</div>
|
|
326
|
+
<span class="wn-chevron">▲</span>
|
|
327
|
+
</div>
|
|
328
|
+
<div class="wn-body">
|
|
329
|
+
<div class="wn-item" id="wn-143-partialforward">
|
|
330
|
+
<div class="wn-item-header" onclick="toggleItem('wn-143-partialforward')">
|
|
331
|
+
<span class="wn-tag new">New</span>
|
|
332
|
+
<span class="wn-item-title">Forward a subset of a message's attachments instead of always forwarding all of them</span>
|
|
333
|
+
<span class="wn-chevron-sm">▾</span>
|
|
334
|
+
</div>
|
|
335
|
+
<div class="wn-item-body">
|
|
336
|
+
<p><code>messagesApi.forward()</code> gained an optional third parameter, <code>attachmentIds?: string[]</code>, to forward only a subset of the source message's attachments (e.g. one image out of a multi-image message). Omit it for the previous whole-message behavior; an ID not present on the source message is ignored server-side.</p>
|
|
337
|
+
<p><strong><code>HIGHLY_FORWARDED_DEPTH_THRESHOLD</code></strong> constant, exported from the package root — the single source of truth for the <code>forwardDepth</code> value (<code>5</code>) that triggers both the "Forwarded many times" label and the server's reduced fan-out cap. The built-in <code>MessageItem</code> components in both UI SDKs now import this instead of hardcoding a threshold.</p>
|
|
338
|
+
<p class="wn-ref">→ <a href="#step-forward">Step 13b — Forward a Message</a></p>
|
|
339
|
+
</div>
|
|
340
|
+
</div>
|
|
341
|
+
<div class="wn-item" id="wn-143-lastmessage">
|
|
342
|
+
<div class="wn-item-header" onclick="toggleItem('wn-143-lastmessage')">
|
|
343
|
+
<span class="wn-tag fix">Fix</span>
|
|
344
|
+
<span class="wn-item-title"><code>conversation_updated</code>'s <code>lastMessage.forwardedFrom</code> was missing</span>
|
|
345
|
+
<span class="wn-chevron-sm">▾</span>
|
|
346
|
+
</div>
|
|
347
|
+
<div class="wn-item-body">
|
|
348
|
+
<p>Every other place a <code>Message</code> is serialized (list/get, <code>new_message</code>, <code>syncApi</code>) already included <code>forwardedFrom</code>, but the denormalized <code>lastMessage</code> preview stored on <code>Conversation</code> (and broadcast via <code>conversation_updated</code>) had no such field at all — so a conversation-list preview of a forwarded message could never show a "Forwarded" label. Fixed by adding <code>forwardedFrom</code> to the stored <code>lastMessage</code> snapshot and every DTO that projects it.</p>
|
|
349
|
+
</div>
|
|
350
|
+
</div>
|
|
351
|
+
<div class="wn-item" id="wn-143-fanout">
|
|
352
|
+
<div class="wn-item-header" onclick="toggleItem('wn-143-fanout')">
|
|
353
|
+
<span class="wn-tag improvement">Documented</span>
|
|
354
|
+
<span class="wn-item-title">Reduced fan-out for highly-forwarded content (server behavior, no SDK code change)</span>
|
|
355
|
+
<span class="wn-chevron-sm">▾</span>
|
|
356
|
+
</div>
|
|
357
|
+
<div class="wn-item-body">
|
|
358
|
+
<p>Once a message's <code>forwardDepth</code> reaches 5, the server caps <code>forward()</code> to 1 target per call instead of 5, silently truncating any additional target IDs and reporting each dropped one back as a normal <code>{ success: false, error }</code> entry in <code>ForwardResult[]</code> — not a validation error. This matches WhatsApp's actual behavior (throttled fan-out, never a forwarding ban): <strong>there is no hard cap on how many times a message can be forwarded.</strong></p>
|
|
359
|
+
<p>Forwarding your own message is not special-cased — <code>forwardedFrom</code> is stamped unconditionally regardless of who sent the source message. Forward requires a fully-persisted, active message — an unsent message simply isn't forwardable yet.</p>
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
</div><!-- /.wn-version -->
|
|
364
|
+
|
|
365
|
+
<!-- ── v1.4.2 ── -->
|
|
366
|
+
<div class="wn-version open" id="wn-142">
|
|
367
|
+
<div class="wn-header" onclick="toggleVersion('wn-142')">
|
|
368
|
+
<div class="wn-title">
|
|
369
|
+
<span class="wn-ver">v1.4.2</span>
|
|
370
|
+
<span class="wn-date">July 2026</span>
|
|
371
|
+
</div>
|
|
372
|
+
<span class="wn-chevron">▲</span>
|
|
373
|
+
</div>
|
|
374
|
+
<div class="wn-body">
|
|
375
|
+
<div class="wn-item" id="wn-142-forward">
|
|
376
|
+
<div class="wn-item-header" onclick="toggleItem('wn-142-forward')">
|
|
377
|
+
<span class="wn-tag new">New</span>
|
|
378
|
+
<span class="wn-item-title">Message forwarding</span>
|
|
379
|
+
<span class="wn-chevron-sm">▾</span>
|
|
380
|
+
</div>
|
|
381
|
+
<div class="wn-item-body">
|
|
382
|
+
<p><code>messagesApi.forward(messageId, targetConversationIds)</code> forwards a message into up to <code>MAX_FORWARD_TARGETS</code> (5) conversations in a single call. Each target conversation receives an independent copy of the message — its own ID, sequence number, and delete lifecycle — never a shared row. Attachments are never re-uploaded; a forwarded attachment references the same underlying storage object as the source message.</p>
|
|
383
|
+
<p><code>Message.forwardedFrom?: MessageForwardReference</code> — present on a message created via forwarding. Points one hop back (the immediate message forwarded), never the root of a longer chain, matching WhatsApp's forwarding privacy model: the true original author/conversation is intentionally not reconstructable after more than one hop. <code>forwardDepth</code> still accumulates across the whole chain so a "Forwarded many times" UI treatment remains possible.</p>
|
|
384
|
+
<p><code>MessageForwardReference</code> type and <code>MAX_FORWARD_TARGETS</code> constant, exported from the package root. <code>ForwardResult</code> type — the per-target success/failure shape returned by <code>messagesApi.forward()</code>.</p>
|
|
385
|
+
<p><code>useChatStore</code> gained <code>forwardingMessage</code>/<code>setForwardingMessage</code> — UI state for staging a message in a forward picker, analogous to <code>replyingTo</code>/<code>setReplyingTo</code> but not folded into the next composer send.</p>
|
|
386
|
+
<p class="wn-ref">→ <a href="#step-forward">Step 13b — Forward a Message</a> for full API details and rendering guidance.</p>
|
|
387
|
+
</div>
|
|
388
|
+
</div>
|
|
389
|
+
</div>
|
|
390
|
+
</div><!-- /.wn-version -->
|
|
391
|
+
|
|
392
|
+
<!-- ── v1.4.1 ── -->
|
|
254
393
|
<div class="wn-version open" id="wn-141">
|
|
255
394
|
<div class="wn-header" onclick="toggleVersion('wn-141')">
|
|
256
395
|
<div class="wn-title">
|
|
257
396
|
<span class="wn-ver">v1.4.1</span>
|
|
258
|
-
<span class="wn-badge current">Current</span>
|
|
259
397
|
<span class="wn-date">July 2026</span>
|
|
260
398
|
</div>
|
|
261
399
|
<span class="wn-chevron">▲</span>
|
|
@@ -4272,6 +4410,52 @@ socket?.<span class="fn">on</span>(<span class="str">'message_delivered'</span>,
|
|
|
4272
4410
|
<span class="cm">// REST: messagesApi.delete(id) / messagesApi.deleteForMe(id)</span></code></pre>
|
|
4273
4411
|
</section>
|
|
4274
4412
|
|
|
4413
|
+
<!-- ─── STEP 13b: FORWARD A MESSAGE ───────────────────────────────────── -->
|
|
4414
|
+
<section id="step-forward">
|
|
4415
|
+
<h2><span class="step">STEP 13b</span> Forward a Message</h2>
|
|
4416
|
+
|
|
4417
|
+
<p>Forwarding creates an <strong>independent copy</strong> of a message in each target conversation — its own ID, sequence number, read receipts, and delete lifecycle. Deleting one copy never affects another, and none of them affect the original. Attachments are never re-uploaded — a forwarded attachment references the same underlying storage object as the source message.</p>
|
|
4418
|
+
|
|
4419
|
+
<pre><code><span class="kw">const</span> results = <span class="kw">await</span> messagesApi.<span class="fn">forward</span>(messageId, [convA, convB, convC]);
|
|
4420
|
+
<span class="kw">const</span> failed = results.<span class="fn">filter</span>(r => !r.success);
|
|
4421
|
+
<span class="kw">if</span> (failed.length > <span class="num">0</span>) {
|
|
4422
|
+
console.<span class="fn">warn</span>(<span class="str">`Forward failed for ${failed.length}/${results.length} conversations`</span>, failed);
|
|
4423
|
+
}
|
|
4424
|
+
|
|
4425
|
+
<span class="cm">// Forward only one attachment out of a multi-attachment message (e.g. image 2 of 3)</span>
|
|
4426
|
+
<span class="kw">await</span> messagesApi.<span class="fn">forward</span>(messageId, [convA], [message.content.attachments![<span class="num">1</span>].id]);</code></pre>
|
|
4427
|
+
|
|
4428
|
+
<table>
|
|
4429
|
+
<thead><tr><th>Field / method</th><th>Type</th><th>Description</th></tr></thead>
|
|
4430
|
+
<tbody>
|
|
4431
|
+
<tr><td><code>messagesApi.forward(messageId, targetConversationIds, attachmentIds?, tempId?)</code></td><td><code>Promise<ForwardResult[]></code></td><td>Forwards into up to <code>MAX_FORWARD_TARGETS</code> (5) conversations in one call — fewer if the source message is highly forwarded, see Limits below. Pass <code>attachmentIds</code> to forward only a subset of the source message's attachments; omit it to forward the whole message unchanged. <code>tempId</code> makes retries safe — see below; auto-generated if omitted. Uses the socket transport when connected, REST otherwise.</td></tr>
|
|
4432
|
+
<tr><td><code>Message.forwardedFrom</code></td><td><code>MessageForwardReference | undefined</code></td><td>Present on a message created via forwarding. Points one hop back only — never the root of a longer chain (WhatsApp's privacy model).</td></tr>
|
|
4433
|
+
<tr><td><code>MAX_FORWARD_TARGETS</code></td><td><code>number</code></td><td><code>5</code> — matches the server's normal-case cap.</td></tr>
|
|
4434
|
+
<tr><td><code>HIGHLY_FORWARDED_DEPTH_THRESHOLD</code></td><td><code>number</code></td><td><code>5</code> — matches the server's <code>forwardDepth</code> value that triggers both the "Forwarded many times" label and the reduced fan-out cap.</td></tr>
|
|
4435
|
+
</tbody>
|
|
4436
|
+
</table>
|
|
4437
|
+
|
|
4438
|
+
<div class="callout info">
|
|
4439
|
+
<strong>Limits</strong> — 5 targets per call normally (client-side <code>ForwardPicker</code> disables further selection; server rejects if bypassed). Once <code>forwardDepth >= HIGHLY_FORWARDED_DEPTH_THRESHOLD</code> (5), the server caps fan-out to <strong>1</strong> target per call, silently truncating extras and reporting each dropped one back as a normal <code>{ success: false, error }</code> entry — never a validation error. There is <strong>no hard cap</strong> on how many times a message can be forwarded, ever — only fan-out width shrinks at high depth.
|
|
4440
|
+
</div>
|
|
4441
|
+
|
|
4442
|
+
<h3>Rendering</h3>
|
|
4443
|
+
<p>Show a "Forwarded" (or "Forwarded many times" at <code>forwardDepth >= HIGHLY_FORWARDED_DEPTH_THRESHOLD</code>) label when <code>message.forwardedFrom</code> is present — do not render a sender name or content preview for it (unlike <code>replyTo</code>), since <code>content</code> already holds what was forwarded. The built-in <code>MessageItem</code> component in both UI SDKs already renders this using the exported constant and exposes a "Forward" action in the message menu.</p>
|
|
4444
|
+
|
|
4445
|
+
<h3>Retry safety</h3>
|
|
4446
|
+
<p>The <code>tempId</code> parameter makes a retry safe:</p>
|
|
4447
|
+
<ul>
|
|
4448
|
+
<li>Generate <strong>one</strong> <code>tempId</code> per forward <em>action</em> (one user tap on "Forward," covering all its target conversations) — not one per target.</li>
|
|
4449
|
+
<li>Reuse the <strong>exact same</strong> <code>tempId</code> if you retry that same action. Never mint a new one for a retry — only for a genuinely new forward.</li>
|
|
4450
|
+
<li>The server checks <code>(targetConversationId, senderId, tempId)</code> per target before creating anything — a retry with a matching <code>tempId</code> returns the message that already exists for that target instead of duplicating it; targets that failed the first time are retried normally.</li>
|
|
4451
|
+
<li>The built-in <code>ForwardPicker</code> component in both UI SDKs already does this correctly (one <code>tempId</code> per picker session, reused across retries) — no extra wiring needed if you're using the prebuilt UI.</li>
|
|
4452
|
+
</ul>
|
|
4453
|
+
|
|
4454
|
+
<div class="callout tip">
|
|
4455
|
+
<strong>Transport (v1.4.4+)</strong> — <code>forward()</code> sends over the <code>forward_message</code> socket event (acked via the standard socket.io callback, not a separate broadcast event) whenever a socket is connected, and transparently falls back to REST otherwise — <code>messagesApi.forward()</code> picks the transport for you automatically, no code change needed. Either way it runs through the exact same server-side <code>MessagesService.forward()</code> path, so <code>new_message</code> broadcast / <code>conversation_updated</code> / push-notification behavior is identical regardless of which transport was actually used.
|
|
4456
|
+
</div>
|
|
4457
|
+
</section>
|
|
4458
|
+
|
|
4275
4459
|
<!-- ─── STEP 14: SEARCH ───────────────────────────────────────────────── -->
|
|
4276
4460
|
<section id="step-search">
|
|
4277
4461
|
<h2><span class="step">STEP 14</span> Search Messages</h2>
|
package/package.json
CHANGED