zapo-js 1.6.2 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/README.md +26 -12
  2. package/dist/abprops-spec.d.ts +10 -0
  3. package/dist/abprops-spec.js +25 -0
  4. package/dist/appstate/crypto/WaAppStateCrypto.js +4 -6
  5. package/dist/auth/WaAuthClient.d.ts +14 -1
  6. package/dist/auth/WaAuthClient.js +22 -1
  7. package/dist/auth/credentials-flow.d.ts +3 -1
  8. package/dist/auth/credentials-flow.js +3 -0
  9. package/dist/auth/pairing/WaPairingFlow.d.ts +5 -0
  10. package/dist/auth/pairing/WaPairingFlow.js +66 -14
  11. package/dist/auth/types.d.ts +13 -0
  12. package/dist/client/WaClient.d.ts +11 -0
  13. package/dist/client/WaClient.js +60 -0
  14. package/dist/client/WaClientFactory.d.ts +2 -2
  15. package/dist/client/WaClientFactory.js +53 -13
  16. package/dist/client/coordinators/WaAbPropsCoordinator.d.ts +2 -2
  17. package/dist/client/coordinators/WaAbPropsCoordinator.js +16 -4
  18. package/dist/client/coordinators/WaIncomingNodeCoordinator.d.ts +2 -1
  19. package/dist/client/coordinators/WaIncomingNodeCoordinator.js +4 -0
  20. package/dist/client/coordinators/WaMessageCoordinator.d.ts +96 -0
  21. package/dist/client/coordinators/WaMessageCoordinator.js +163 -8
  22. package/dist/client/coordinators/WaMessageDispatchCoordinator.d.ts +64 -0
  23. package/dist/client/coordinators/WaMessageDispatchCoordinator.js +252 -57
  24. package/dist/client/coordinators/WaPrivacyCoordinator.d.ts +97 -19
  25. package/dist/client/coordinators/WaPrivacyCoordinator.js +152 -101
  26. package/dist/client/coordinators/WaRetryCoordinator.d.ts +22 -0
  27. package/dist/client/coordinators/WaRetryCoordinator.js +74 -11
  28. package/dist/client/events/dirty.d.ts +3 -0
  29. package/dist/client/events/dirty.js +12 -7
  30. package/dist/client/events/offline.d.ts +9 -0
  31. package/dist/client/events/offline.js +95 -0
  32. package/dist/client/events/privacy.d.ts +45 -0
  33. package/dist/client/events/privacy.js +130 -0
  34. package/dist/client/messaging/group-metadata.d.ts +12 -0
  35. package/dist/client/messaging/group-metadata.js +24 -8
  36. package/dist/client/messaging/link-preview.d.ts +7 -0
  37. package/dist/client/messaging/link-preview.js +27 -0
  38. package/dist/client/messaging/messages.js +16 -12
  39. package/dist/client/newsletter/content.d.ts +4 -1
  40. package/dist/client/newsletter/content.js +43 -18
  41. package/dist/client/newsletter/discovery.js +4 -2
  42. package/dist/client/newsletter/messaging.d.ts +2 -0
  43. package/dist/client/newsletter/messaging.js +2 -1
  44. package/dist/client/newsletter/mex.d.ts +2 -2
  45. package/dist/client/persistence/WriteBehindPersistence.js +2 -1
  46. package/dist/client/persistence/ephemeral-setting.d.ts +42 -0
  47. package/dist/client/persistence/ephemeral-setting.js +120 -0
  48. package/dist/client/persistence/group-history.d.ts +29 -0
  49. package/dist/client/persistence/group-history.js +149 -0
  50. package/dist/client/persistence/history-blob.d.ts +25 -0
  51. package/dist/client/persistence/history-blob.js +38 -0
  52. package/dist/client/persistence/history-sync.d.ts +20 -0
  53. package/dist/client/persistence/history-sync.js +165 -71
  54. package/dist/client/plugins/types.d.ts +10 -2
  55. package/dist/client/types.d.ts +150 -16
  56. package/dist/crypto/core/index.d.ts +1 -1
  57. package/dist/crypto/core/index.js +2 -1
  58. package/dist/crypto/core/primitives.d.ts +19 -0
  59. package/dist/crypto/core/primitives.js +48 -0
  60. package/dist/crypto/core/xeddsa.js +35 -4
  61. package/dist/crypto/curves/X25519.js +29 -2
  62. package/dist/crypto/nativeBackend.d.ts +20 -0
  63. package/dist/crypto/nativeBackend.js +95 -0
  64. package/dist/esm/abprops-spec.js +15 -0
  65. package/dist/esm/appstate/crypto/WaAppStateCrypto.js +4 -6
  66. package/dist/esm/auth/WaAuthClient.js +22 -1
  67. package/dist/esm/auth/credentials-flow.js +3 -0
  68. package/dist/esm/auth/pairing/WaPairingFlow.js +67 -15
  69. package/dist/esm/client/WaClient.js +60 -0
  70. package/dist/esm/client/WaClientFactory.js +54 -14
  71. package/dist/esm/client/coordinators/WaAbPropsCoordinator.js +17 -5
  72. package/dist/esm/client/coordinators/WaIncomingNodeCoordinator.js +4 -0
  73. package/dist/esm/client/coordinators/WaMessageCoordinator.js +166 -11
  74. package/dist/esm/client/coordinators/WaMessageDispatchCoordinator.js +254 -60
  75. package/dist/esm/client/coordinators/WaPrivacyCoordinator.js +145 -94
  76. package/dist/esm/client/coordinators/WaRetryCoordinator.js +74 -11
  77. package/dist/esm/client/events/dirty.js +12 -7
  78. package/dist/esm/client/events/offline.js +92 -0
  79. package/dist/esm/client/events/privacy.js +123 -0
  80. package/dist/esm/client/messaging/group-metadata.js +25 -9
  81. package/dist/esm/client/messaging/link-preview.js +29 -2
  82. package/dist/esm/client/messaging/messages.js +16 -12
  83. package/dist/esm/client/newsletter/content.js +44 -19
  84. package/dist/esm/client/newsletter/discovery.js +4 -2
  85. package/dist/esm/client/newsletter/messaging.js +2 -1
  86. package/dist/esm/client/persistence/WriteBehindPersistence.js +2 -1
  87. package/dist/esm/client/persistence/ephemeral-setting.js +116 -0
  88. package/dist/esm/client/persistence/group-history.js +145 -0
  89. package/dist/esm/client/persistence/history-blob.js +34 -0
  90. package/dist/esm/client/persistence/history-sync.js +157 -65
  91. package/dist/esm/crypto/core/index.js +1 -1
  92. package/dist/esm/crypto/core/primitives.js +47 -0
  93. package/dist/esm/crypto/core/xeddsa.js +36 -5
  94. package/dist/esm/crypto/curves/X25519.js +29 -2
  95. package/dist/esm/crypto/nativeBackend.js +93 -0
  96. package/dist/esm/index.js +3 -1
  97. package/dist/esm/media/crypto/WaMediaCrypto.js +38 -19
  98. package/dist/esm/message/context-info.js +39 -0
  99. package/dist/esm/message/crypto/reporting-token.js +38 -216
  100. package/dist/esm/message/encode/content.js +73 -12
  101. package/dist/esm/message/encode/media-payload.js +3 -1
  102. package/dist/esm/message/kinds/group-history.js +36 -0
  103. package/dist/esm/message/primitives/incoming.js +32 -13
  104. package/dist/esm/protocol/abprops.js +18 -162
  105. package/dist/esm/protocol/constants.js +4 -3
  106. package/dist/esm/protocol/defaults.js +7 -0
  107. package/dist/esm/protocol/device-identity.js +10 -5
  108. package/dist/esm/protocol/jid.js +83 -36
  109. package/dist/esm/protocol/media.js +1 -0
  110. package/dist/esm/protocol/message.js +8 -0
  111. package/dist/esm/protocol/notification.js +1 -0
  112. package/dist/esm/protocol/privacy.js +70 -4
  113. package/dist/esm/signal/group/SenderKeyChain.js +1 -2
  114. package/dist/esm/signal/group/SenderKeyManager.js +1 -7
  115. package/dist/esm/signal/session/SignalProtocol.js +22 -21
  116. package/dist/esm/signal/session/SignalRatchet.js +20 -17
  117. package/dist/esm/signal/session/encoding.js +25 -3
  118. package/dist/esm/signal/session/resolver.js +11 -5
  119. package/dist/esm/store/contracts/chat-metadata.store.js +1 -0
  120. package/dist/esm/store/createStore.js +19 -2
  121. package/dist/esm/store/index.js +1 -0
  122. package/dist/esm/store/memory/chat-metadata.store.js +66 -0
  123. package/dist/esm/store/noop.store.js +8 -0
  124. package/dist/esm/transport/binary/constants.js +19 -0
  125. package/dist/esm/transport/binary/decoder.js +17 -19
  126. package/dist/esm/transport/binary/encoder.js +61 -18
  127. package/dist/esm/transport/node/builders/global.js +20 -0
  128. package/dist/esm/transport/node/builders/message.js +32 -15
  129. package/dist/esm/transport/node/builders/privacy.js +63 -4
  130. package/dist/esm/transport/noise/WaClientPayload.js +43 -22
  131. package/dist/esm/transport/noise/WaFrameCodec.js +14 -4
  132. package/dist/esm/util/coercion.js +12 -4
  133. package/dist/esm/util/index.js +1 -1
  134. package/dist/esm/util/protoscan.js +181 -0
  135. package/dist/esm/util/runtime.js +34 -0
  136. package/dist/index.d.ts +11 -6
  137. package/dist/index.js +15 -2
  138. package/dist/media/crypto/WaMediaCrypto.js +36 -17
  139. package/dist/media/types.d.ts +1 -1
  140. package/dist/message/context-info.d.ts +10 -0
  141. package/dist/message/context-info.js +40 -0
  142. package/dist/message/crypto/reporting-token.d.ts +3 -1
  143. package/dist/message/crypto/reporting-token.js +38 -216
  144. package/dist/message/encode/content.d.ts +24 -4
  145. package/dist/message/encode/content.js +74 -12
  146. package/dist/message/encode/media-payload.d.ts +5 -2
  147. package/dist/message/encode/media-payload.js +3 -1
  148. package/dist/message/kinds/group-history.d.ts +30 -0
  149. package/dist/message/kinds/group-history.js +40 -0
  150. package/dist/message/primitives/incoming.d.ts +5 -0
  151. package/dist/message/primitives/incoming.js +32 -13
  152. package/dist/message/types.d.ts +10 -0
  153. package/dist/protocol/abprops.d.ts +27 -140
  154. package/dist/protocol/abprops.js +18 -163
  155. package/dist/protocol/constants.d.ts +4 -3
  156. package/dist/protocol/constants.js +10 -3
  157. package/dist/protocol/defaults.d.ts +7 -0
  158. package/dist/protocol/defaults.js +7 -0
  159. package/dist/protocol/device-identity.d.ts +14 -4
  160. package/dist/protocol/device-identity.js +9 -4
  161. package/dist/protocol/jid.d.ts +4 -1
  162. package/dist/protocol/jid.js +83 -36
  163. package/dist/protocol/media.d.ts +1 -0
  164. package/dist/protocol/media.js +1 -0
  165. package/dist/protocol/message.d.ts +6 -0
  166. package/dist/protocol/message.js +9 -0
  167. package/dist/protocol/notification.d.ts +1 -0
  168. package/dist/protocol/notification.js +1 -0
  169. package/dist/protocol/privacy.d.ts +52 -0
  170. package/dist/protocol/privacy.js +71 -5
  171. package/dist/signal/group/SenderKeyChain.d.ts +1 -1
  172. package/dist/signal/group/SenderKeyChain.js +1 -2
  173. package/dist/signal/group/SenderKeyManager.js +1 -7
  174. package/dist/signal/session/SignalProtocol.d.ts +13 -0
  175. package/dist/signal/session/SignalProtocol.js +22 -21
  176. package/dist/signal/session/SignalRatchet.d.ts +4 -4
  177. package/dist/signal/session/SignalRatchet.js +19 -16
  178. package/dist/signal/session/SignalSession.d.ts +8 -1
  179. package/dist/signal/session/encoding.d.ts +5 -0
  180. package/dist/signal/session/encoding.js +25 -3
  181. package/dist/signal/session/resolver.js +11 -5
  182. package/dist/store/contracts/chat-metadata.store.d.ts +34 -0
  183. package/dist/store/contracts/chat-metadata.store.js +2 -0
  184. package/dist/store/contracts/group-metadata.store.d.ts +7 -0
  185. package/dist/store/contracts/thread.store.d.ts +6 -0
  186. package/dist/store/createStore.d.ts +10 -2
  187. package/dist/store/createStore.js +18 -1
  188. package/dist/store/index.d.ts +3 -1
  189. package/dist/store/index.js +3 -1
  190. package/dist/store/memory/chat-metadata.store.d.ts +26 -0
  191. package/dist/store/memory/chat-metadata.store.js +70 -0
  192. package/dist/store/noop.store.d.ts +2 -0
  193. package/dist/store/noop.store.js +9 -1
  194. package/dist/store/types.d.ts +190 -39
  195. package/dist/transport/binary/constants.d.ts +8 -0
  196. package/dist/transport/binary/constants.js +20 -1
  197. package/dist/transport/binary/decoder.js +17 -19
  198. package/dist/transport/binary/encoder.js +60 -17
  199. package/dist/transport/node/builders/global.d.ts +8 -0
  200. package/dist/transport/node/builders/global.js +21 -0
  201. package/dist/transport/node/builders/message.js +32 -15
  202. package/dist/transport/node/builders/privacy.d.ts +26 -2
  203. package/dist/transport/node/builders/privacy.js +63 -3
  204. package/dist/transport/noise/WaClientPayload.js +43 -22
  205. package/dist/transport/noise/WaFrameCodec.d.ts +1 -0
  206. package/dist/transport/noise/WaFrameCodec.js +14 -4
  207. package/dist/transport/noise/types.d.ts +17 -0
  208. package/dist/util/coercion.d.ts +6 -0
  209. package/dist/util/coercion.js +13 -4
  210. package/dist/util/index.d.ts +1 -1
  211. package/dist/util/index.js +2 -1
  212. package/dist/util/protoscan.d.ts +33 -0
  213. package/dist/util/protoscan.js +186 -0
  214. package/dist/util/runtime.d.ts +12 -0
  215. package/dist/util/runtime.js +35 -0
  216. package/package.json +3 -1
  217. package/spec/abprops/index.d.ts +4402 -0
  218. package/spec/abprops/index.js +4372 -0
@@ -17,11 +17,13 @@ import type { SenderKeyManager } from '../../signal/group/SenderKeyManager';
17
17
  import type { SignalSessionResolver } from '../../signal/session/resolver';
18
18
  import type { SignalProtocol } from '../../signal/session/SignalProtocol';
19
19
  import type { SignalAddress } from '../../signal/types';
20
+ import type { WaChatMetadataStore } from '../../store/contracts/chat-metadata.store';
20
21
  import type { WaDeviceListStore } from '../../store/contracts/device-list.store';
21
22
  import type { WaIdentityStore } from '../../store/contracts/identity.store';
22
23
  import type { WaMessageSecretStore } from '../../store/contracts/message-secret.store';
23
24
  import type { WaSessionStore } from '../../store/contracts/session.store';
24
25
  import type { WaSignalStore } from '../../store/contracts/signal.store';
26
+ import type { WaThreadStore } from '../../store/contracts/thread.store';
25
27
  import type { BinaryNode } from '../../transport/types';
26
28
  import type { ServerClock } from '../../util/clock';
27
29
  interface WaMessageDispatchCoordinatorOptions {
@@ -45,6 +47,8 @@ interface WaMessageDispatchCoordinatorOptions {
45
47
  readonly sessionStore: WaSessionStore;
46
48
  readonly identityStore: WaIdentityStore;
47
49
  readonly deviceListStore: WaDeviceListStore;
50
+ readonly threadStore: WaThreadStore;
51
+ readonly chatMetadataStore: WaChatMetadataStore;
48
52
  readonly signalDeviceSync: SignalDeviceSyncApi;
49
53
  readonly messageSecretStore: WaMessageSecretStore;
50
54
  /**
@@ -66,6 +70,32 @@ interface WaMessageDispatchCoordinatorOptions {
66
70
  readonly mobileMessageIdFormat?: () => boolean;
67
71
  readonly serverClock: ServerClock;
68
72
  }
73
+ /** Membership split behind a group-history share, in the group's addressing mode. */
74
+ export interface WaGroupHistoryAudience {
75
+ /** Members the bundle is addressed to. */
76
+ readonly historyReceivers: readonly string[];
77
+ /** Members in the group who are not receiving it. */
78
+ readonly nonHistoryReceivers: readonly string[];
79
+ /** Requested JIDs that are not current members of the group. */
80
+ readonly unknownJids: readonly string[];
81
+ /** Whether the caller asked to share with this account itself. */
82
+ readonly requestedSelf: boolean;
83
+ readonly addressingMode: 'pn' | 'lid';
84
+ }
85
+ /**
86
+ * Narrows a group's participant list to the members a group-history bundle is
87
+ * addressed to, plus the sender so its own devices keep a copy. Receivers are
88
+ * intersected with the live participant list, so a stale receiver entry cannot
89
+ * pull a non-member into the fanout. Any other message keeps the full list.
90
+ *
91
+ * Receivers must be written in the group's addressing mode - a PN entry for a
92
+ * LID-addressed group does not match and is reported through `onDropped`.
93
+ *
94
+ * @throws when no receiver matches a current member. Sending to the sender's
95
+ * own devices alone counts as no match: it would look like a delivered share
96
+ * while nobody received anything.
97
+ */
98
+ export declare function restrictGroupHistoryTargets(message: Proto.IMessage, participantUserJids: readonly string[], senderJid: string, onDropped?: (droppedJids: readonly string[], totalExpected: number) => void): readonly string[];
69
99
  export declare class WaMessageDispatchCoordinator {
70
100
  private readonly deps;
71
101
  private readonly mobileMessageIdFormat;
@@ -74,6 +104,24 @@ export declare class WaMessageDispatchCoordinator {
74
104
  private readonly privacyTokenDedup;
75
105
  private readonly distributionDedup;
76
106
  constructor(options: WaMessageDispatchCoordinatorOptions);
107
+ /**
108
+ * Per-chat disappearing settings from the cache, falling back to the
109
+ * durable thread record on a cold miss and warming the cache with it. A
110
+ * chat with no disappearing mode is cached as expiration `0` rather than
111
+ * left absent, so the archive is not re-read on every send to it; the
112
+ * incoming writers overwrite that entry the moment the chat turns it on.
113
+ * A failing archive degrades the send to "no ephemeral fields" instead of
114
+ * rejecting it - the store is opt-in persistence, not a send dependency.
115
+ */
116
+ private resolveChatMetadata;
117
+ /**
118
+ * Disappearing-message fields for a send into a chat with the mode on, or
119
+ * `{}` when nothing resolves. Both chat kinds report initiator
120
+ * `CHANGED_IN_CHAT` when nothing more specific is stored; the trigger comes
121
+ * from the group metadata for a group and defaults to `CHAT_SETTING` for
122
+ * 1:1, and is omitted when a group reports none.
123
+ */
124
+ private resolveChatEphemeral;
77
125
  publishMessageNode(node: BinaryNode, options?: WaMessagePublishOptions): Promise<WaMessagePublishResult>;
78
126
  publishEncryptedMessage(input: WaEncryptedMessageInput, options?: WaMessagePublishOptions): Promise<WaMessagePublishResult>;
79
127
  publishSignalMessage(input: WaSignalMessagePublishInput, options?: WaMessagePublishOptions): Promise<WaMessagePublishResult>;
@@ -119,6 +167,22 @@ export declare class WaMessageDispatchCoordinator {
119
167
  requestAppStateSyncKeys(keyIds: readonly Uint8Array[]): Promise<readonly string[]>;
120
168
  sendAppStateSyncKeyShare(toDeviceJid: string, keys: readonly WaAppStateSyncKey[], missingKeyIds?: readonly Uint8Array[]): Promise<void>;
121
169
  mutateGroupMetadataCacheFromGroupEvent(event: WaGroupEvent): Promise<void>;
170
+ /**
171
+ * Splits a group's members into the ones a history bundle is being shared
172
+ * with and the rest, normalizing everything to the group's addressing mode
173
+ * so the receiver list written into the proto matches what the fanout can
174
+ * actually resolve. This account is excluded from both lists.
175
+ *
176
+ * Requested JIDs that are not current members come back in `unknownJids`
177
+ * instead of being silently dropped - sharing history with a non-member is
178
+ * a caller mistake worth surfacing.
179
+ */
180
+ resolveGroupHistoryAudience(groupJid: string, requestedJids: readonly string[]): Promise<WaGroupHistoryAudience>;
181
+ /**
182
+ * A group-history bundle goes to the members named in `historyReceivers`
183
+ * rather than to the whole group, so it takes the per-device direct fanout
184
+ * instead of the group sender key.
185
+ */
122
186
  private shouldUseGroupDirectPath;
123
187
  private publishGroupDirectMessage;
124
188
  private buildBotSidecarParticipant;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WaMessageDispatchCoordinator = void 0;
4
+ exports.restrictGroupHistoryTargets = restrictGroupHistoryTargets;
4
5
  const _crypto_1 = require("../../crypto");
5
6
  const primitives_1 = require("../../crypto/core/primitives");
6
7
  const PromiseDedup_1 = require("../../infra/perf/PromiseDedup");
@@ -20,6 +21,58 @@ const binary_1 = require("../../transport/binary");
20
21
  const message_1 = require("../../transport/node/builders/message");
21
22
  const bytes_1 = require("../../util/bytes");
22
23
  const primitives_2 = require("../../util/primitives");
24
+ /**
25
+ * Members a group-history bundle is addressed to. Empty for every other
26
+ * message, which is what keeps the restricted fanout scoped to bundles.
27
+ */
28
+ function resolveGroupHistoryReceivers(message) {
29
+ const metadata = (0, content_1.unwrapMessage)(message).messageHistoryBundle?.messageHistoryMetadata;
30
+ return metadata?.historyReceivers ?? [];
31
+ }
32
+ /**
33
+ * Narrows a group's participant list to the members a group-history bundle is
34
+ * addressed to, plus the sender so its own devices keep a copy. Receivers are
35
+ * intersected with the live participant list, so a stale receiver entry cannot
36
+ * pull a non-member into the fanout. Any other message keeps the full list.
37
+ *
38
+ * Receivers must be written in the group's addressing mode - a PN entry for a
39
+ * LID-addressed group does not match and is reported through `onDropped`.
40
+ *
41
+ * @throws when no receiver matches a current member. Sending to the sender's
42
+ * own devices alone counts as no match: it would look like a delivered share
43
+ * while nobody received anything.
44
+ */
45
+ function restrictGroupHistoryTargets(message, participantUserJids, senderJid, onDropped) {
46
+ const receivers = resolveGroupHistoryReceivers(message);
47
+ if (receivers.length === 0) {
48
+ return participantUserJids;
49
+ }
50
+ const pending = new Set();
51
+ for (let index = 0; index < receivers.length; index += 1) {
52
+ pending.add((0, jid_1.toUserJid)(receivers[index]));
53
+ }
54
+ const senderUserJid = (0, jid_1.toUserJid)(senderJid);
55
+ const targets = [];
56
+ let matchedReceivers = 0;
57
+ for (let index = 0; index < participantUserJids.length; index += 1) {
58
+ const participant = participantUserJids[index];
59
+ const participantUserJid = (0, jid_1.toUserJid)(participant);
60
+ if (pending.delete(participantUserJid)) {
61
+ matchedReceivers += 1;
62
+ targets[targets.length] = participant;
63
+ }
64
+ else if (participantUserJid === senderUserJid) {
65
+ targets[targets.length] = participant;
66
+ }
67
+ }
68
+ if (pending.size > 0 && onDropped) {
69
+ onDropped([...pending], receivers.length);
70
+ }
71
+ if (matchedReceivers === 0) {
72
+ throw new Error('group history bundle resolved no receivers in the group');
73
+ }
74
+ return targets;
75
+ }
23
76
  class WaMessageDispatchCoordinator {
24
77
  constructor(options) {
25
78
  this.icdcDedup = new PromiseDedup_1.PromiseDedup();
@@ -29,6 +82,82 @@ class WaMessageDispatchCoordinator {
29
82
  this.mobileMessageIdFormat = options.mobileMessageIdFormat ?? (() => false);
30
83
  this.serverClock = options.serverClock;
31
84
  }
85
+ /**
86
+ * Per-chat disappearing settings from the cache, falling back to the
87
+ * durable thread record on a cold miss and warming the cache with it. A
88
+ * chat with no disappearing mode is cached as expiration `0` rather than
89
+ * left absent, so the archive is not re-read on every send to it; the
90
+ * incoming writers overwrite that entry the moment the chat turns it on.
91
+ * A failing archive degrades the send to "no ephemeral fields" instead of
92
+ * rejecting it - the store is opt-in persistence, not a send dependency.
93
+ */
94
+ async resolveChatMetadata(chatJid) {
95
+ try {
96
+ const cached = await this.deps.chatMetadataStore.getChatMetadata(chatJid);
97
+ if (cached) {
98
+ return cached;
99
+ }
100
+ const thread = await this.deps.threadStore.getByJid(chatJid);
101
+ const expiration = thread?.ephemeralExpiration ?? 0;
102
+ const settingTimestamp = expiration > 0 ? thread?.ephemeralSettingTimestamp : undefined;
103
+ const snapshot = {
104
+ chatJid,
105
+ ephemeralExpiration: expiration,
106
+ ...(settingTimestamp !== undefined
107
+ ? { ephemeralSettingTimestamp: settingTimestamp }
108
+ : {}),
109
+ updatedAtMs: Date.now()
110
+ };
111
+ await this.deps.chatMetadataStore.upsertChatMetadata(snapshot);
112
+ return snapshot;
113
+ }
114
+ catch (error) {
115
+ this.deps.logger.debug('chat metadata lookup failed, sending without ephemeral fields', {
116
+ jid: chatJid,
117
+ message: (0, primitives_2.toError)(error).message
118
+ });
119
+ return null;
120
+ }
121
+ }
122
+ /**
123
+ * Disappearing-message fields for a send into a chat with the mode on, or
124
+ * `{}` when nothing resolves. Both chat kinds report initiator
125
+ * `CHANGED_IN_CHAT` when nothing more specific is stored; the trigger comes
126
+ * from the group metadata for a group and defaults to `CHAT_SETTING` for
127
+ * 1:1, and is omitted when a group reports none.
128
+ */
129
+ async resolveChatEphemeral(recipientJid) {
130
+ if ((0, jid_1.isGroupJid)(recipientJid)) {
131
+ const settings = await this.deps.groupMetadataCache.resolveEphemeralSettings(recipientJid);
132
+ if (!settings) {
133
+ return {};
134
+ }
135
+ return {
136
+ expirationSeconds: settings.expirationSeconds,
137
+ disappearingModeInitiator: _proto_1.proto.DisappearingMode.Initiator.CHANGED_IN_CHAT,
138
+ ...(settings.trigger !== undefined
139
+ ? {
140
+ disappearingModeTrigger: settings.trigger
141
+ }
142
+ : {})
143
+ };
144
+ }
145
+ const chat = await this.resolveChatMetadata(recipientJid);
146
+ const expiration = chat?.ephemeralExpiration;
147
+ if (expiration === undefined || expiration <= 0) {
148
+ return {};
149
+ }
150
+ const stored = chat?.ephemeralSettingTimestamp;
151
+ const settingTimestamp = stored !== undefined ? (0, constants_1.normalizeEphemeralSettingSeconds)(stored) : undefined;
152
+ return {
153
+ expirationSeconds: expiration,
154
+ disappearingModeInitiator: _proto_1.proto.DisappearingMode.Initiator.CHANGED_IN_CHAT,
155
+ disappearingModeTrigger: _proto_1.proto.DisappearingMode.Trigger.CHAT_SETTING,
156
+ ...(settingTimestamp !== undefined
157
+ ? { ephemeralSettingTimestamp: settingTimestamp }
158
+ : {})
159
+ };
160
+ }
32
161
  async publishMessageNode(node, options = {}) {
33
162
  this.deps.logger.trace('wa client publish message node', {
34
163
  tag: node.tag,
@@ -149,17 +278,34 @@ class WaMessageDispatchCoordinator {
149
278
  this.withResolvedMessageId(options)
150
279
  ]);
151
280
  }
281
+ const isGroupRecipient = (0, jid_1.isGroupJid)(recipientJid);
282
+ const directRecipientJid = isGroupRecipient
283
+ ? recipientJid
284
+ : await this.resolveDirectRecipientLid((0, jid_1.toUserJid)(recipientJid));
152
285
  let optionsCtx = options.contextInfo;
153
286
  if (options.expirationSeconds !== undefined) {
154
287
  optionsCtx = { ...optionsCtx, expirationSeconds: options.expirationSeconds };
155
288
  }
156
- if ((0, jid_1.isGroupJid)(recipientJid) &&
157
- optionsCtx?.expirationSeconds === undefined &&
158
- !options.disableGroupEphemeralAutoInject) {
159
- const cachedEphemeral = await this.deps.groupMetadataCache.resolveEphemeral(recipientJid);
160
- if (cachedEphemeral !== null && cachedEphemeral > 0) {
161
- optionsCtx = { ...optionsCtx, expirationSeconds: cachedEphemeral };
162
- }
289
+ if (options.ephemeralSettingTimestamp !== undefined) {
290
+ optionsCtx = {
291
+ ...optionsCtx,
292
+ ephemeralSettingTimestamp: options.ephemeralSettingTimestamp
293
+ };
294
+ }
295
+ if (options.disappearingModeTrigger !== undefined) {
296
+ optionsCtx = {
297
+ ...optionsCtx,
298
+ disappearingModeTrigger: options.disappearingModeTrigger
299
+ };
300
+ }
301
+ const autoInjectDisabled = isGroupRecipient
302
+ ? options.disableGroupEphemeralAutoInject
303
+ : options.disableDirectEphemeralAutoInject;
304
+ if (optionsCtx?.expirationSeconds === undefined && !autoInjectDisabled) {
305
+ optionsCtx = {
306
+ ...(await this.resolveChatEphemeral(directRecipientJid)),
307
+ ...optionsCtx
308
+ };
163
309
  }
164
310
  const ctx = (0, context_info_1.resolveSendContextInfo)({
165
311
  contentLevel: pickContentContextInfo(content),
@@ -252,17 +398,16 @@ class WaMessageDispatchCoordinator {
252
398
  ]);
253
399
  const messageWithIcdc = (0, icdc_1.injectDeviceListMetadata)(messageWithSecret, senderIcdc, recipientIcdc);
254
400
  const plaintext = await (0, padding_1.writeRandomPadMax16)(_proto_1.proto.Message.encode(messageWithIcdc).finish());
255
- const buttonAddonKind = (0, content_1.resolveButtonAddonKind)(messageWithIcdc);
401
+ const outboundAttrs = (0, content_1.resolveOutboundMessageAttrs)(messageWithIcdc);
402
+ const buttonAddonKind = outboundAttrs.buttonAddonKind;
256
403
  const buttonAddonNode = buttonAddonKind ? (0, message_1.buildButtonAddonNode)(buttonAddonKind) : undefined;
257
404
  // when a <biz> companion is attached the stanza must advertise type=text and
258
405
  // omit enc.mediatype; sending type=media + mediatype=list/button alongside the
259
406
  // companion is rejected by the server as SMAX_INVALID (479).
260
- const type = buttonAddonKind ? 'text' : (0, content_1.resolveMessageTypeAttr)(messageWithIcdc);
261
- const edit = (0, content_1.resolveEditAttr)(messageWithIcdc) ?? undefined;
262
- const mediatype = buttonAddonKind
263
- ? undefined
264
- : ((0, content_1.resolveEncMediaType)(messageWithIcdc) ?? undefined);
265
- const metaAttrs = (0, content_1.resolveMetaAttrs)(messageWithIcdc);
407
+ const type = buttonAddonKind ? 'text' : outboundAttrs.typeAttr;
408
+ const edit = outboundAttrs.edit ?? undefined;
409
+ const mediatype = buttonAddonKind ? undefined : (outboundAttrs.mediatype ?? undefined);
410
+ const metaAttrs = outboundAttrs.metaAttrs;
266
411
  const metaNode = metaAttrs ? (0, message_1.buildMetaNode)(metaAttrs) : undefined;
267
412
  const customNodes = [];
268
413
  if (metaNode)
@@ -274,7 +419,7 @@ class WaMessageDispatchCoordinator {
274
419
  customNodes.push(node);
275
420
  }
276
421
  }
277
- const decryptFail = (0, content_1.resolveDecryptFailAttr)(messageWithIcdc);
422
+ const decryptFail = outboundAttrs.decryptFail;
278
423
  const envelope = {
279
424
  message: messageWithIcdc,
280
425
  plaintext,
@@ -285,9 +430,6 @@ class WaMessageDispatchCoordinator {
285
430
  customNodes: customNodes.length > 0 ? customNodes : undefined,
286
431
  sendOptions
287
432
  };
288
- const directRecipientJid = isGroup
289
- ? recipientJid
290
- : await this.resolveDirectRecipientLid((0, jid_1.toUserJid)(recipientJid));
291
433
  const peerRecipientPn = isGroup
292
434
  ? undefined
293
435
  : await this.resolvePeerRecipientPn((0, jid_1.toUserJid)(recipientJid), directRecipientJid);
@@ -397,7 +539,7 @@ class WaMessageDispatchCoordinator {
397
539
  replayStatusSetting: statusSetting,
398
540
  // Bare `<to jid=user>` ack hints route the skmsg through primary
399
541
  // devices that already hold the sender key.
400
- customize: async ({ fanoutDeviceJids, distributionParticipants, messageWithSecret, sendOptions }) => {
542
+ customize: ({ fanoutDeviceJids, distributionParticipants, messageWithSecret, sendOptions }) => {
401
543
  const distributedAddressKeys = new Set();
402
544
  for (let i = 0; i < distributionParticipants.length; i += 1) {
403
545
  distributedAddressKeys.add((0, jid_1.signalAddressKey)(distributionParticipants[i].address));
@@ -417,7 +559,7 @@ class WaMessageDispatchCoordinator {
417
559
  seenAck.add(userJid);
418
560
  ackHints.push({ jid: userJid });
419
561
  }
420
- const reportingArtifacts = await this.tryBuildReportingTokenArtifacts({
562
+ const reportingArtifacts = this.tryBuildReportingTokenArtifacts({
421
563
  message: messageWithSecret,
422
564
  stanzaId: sendOptions.id,
423
565
  senderUserJid: (0, jid_1.toUserJid)(senderJid),
@@ -495,14 +637,15 @@ class WaMessageDispatchCoordinator {
495
637
  participants.push(entry);
496
638
  }
497
639
  }
640
+ const outboundAttrs = (0, content_1.resolveOutboundMessageAttrs)(messageWithSecret);
498
641
  const messageNode = (0, message_1.buildGroupSenderKeyMessageNode)({
499
642
  to: input.groupJid,
500
- type: (0, content_1.resolveMessageTypeAttr)(messageWithSecret),
643
+ type: outboundAttrs.typeAttr,
501
644
  id: sendOptions.id,
502
645
  phash: extras.phash,
503
- edit: (0, content_1.resolveEditAttr)(messageWithSecret) ?? undefined,
504
- mediatype: (0, content_1.resolveEncMediaType)(messageWithSecret) ?? undefined,
505
- decryptFail: (0, content_1.resolveDecryptFailAttr)(messageWithSecret),
646
+ edit: outboundAttrs.edit ?? undefined,
647
+ mediatype: outboundAttrs.mediatype ?? undefined,
648
+ decryptFail: outboundAttrs.decryptFail,
506
649
  groupCiphertext: groupCiphertext.ciphertext,
507
650
  participants,
508
651
  deviceIdentity: shouldAttachDeviceIdentity
@@ -550,9 +693,59 @@ class WaMessageDispatchCoordinator {
550
693
  async mutateGroupMetadataCacheFromGroupEvent(event) {
551
694
  await this.deps.groupMetadataCache.mutateFromGroupEvent(event);
552
695
  }
553
- // noop for now
696
+ /**
697
+ * Splits a group's members into the ones a history bundle is being shared
698
+ * with and the rest, normalizing everything to the group's addressing mode
699
+ * so the receiver list written into the proto matches what the fanout can
700
+ * actually resolve. This account is excluded from both lists.
701
+ *
702
+ * Requested JIDs that are not current members come back in `unknownJids`
703
+ * instead of being silently dropped - sharing history with a non-member is
704
+ * a caller mistake worth surfacing.
705
+ */
706
+ async resolveGroupHistoryAudience(groupJid, requestedJids) {
707
+ const participants = await this.deps.groupMetadataCache.resolveParticipantUsers(groupJid);
708
+ const meJid = this.requireCurrentMeJid('shareGroupHistory');
709
+ const addressingMode = this.resolveGroupAddressingMode(participants, groupJid);
710
+ const meUserJid = (0, jid_1.toUserJid)(this.resolveSenderForAddressingMode(addressingMode, meJid));
711
+ const pending = new Set();
712
+ for (let index = 0; index < requestedJids.length; index += 1) {
713
+ pending.add((0, jid_1.toUserJid)((0, jid_1.normalizeRecipientJid)(requestedJids[index])));
714
+ }
715
+ const historyReceivers = [];
716
+ const nonHistoryReceivers = [];
717
+ for (let index = 0; index < participants.length; index += 1) {
718
+ const participantUserJid = (0, jid_1.toUserJid)(participants[index]);
719
+ if (participantUserJid === meUserJid) {
720
+ continue;
721
+ }
722
+ if (pending.delete(participantUserJid)) {
723
+ historyReceivers[historyReceivers.length] = participantUserJid;
724
+ }
725
+ else {
726
+ nonHistoryReceivers[nonHistoryReceivers.length] = participantUserJid;
727
+ }
728
+ }
729
+ const requestedSelf = pending.delete(meUserJid);
730
+ const unknownJids = [];
731
+ for (const jid of pending) {
732
+ unknownJids[unknownJids.length] = jid;
733
+ }
734
+ return {
735
+ historyReceivers,
736
+ nonHistoryReceivers,
737
+ unknownJids,
738
+ requestedSelf,
739
+ addressingMode
740
+ };
741
+ }
742
+ /**
743
+ * A group-history bundle goes to the members named in `historyReceivers`
744
+ * rather than to the whole group, so it takes the per-device direct fanout
745
+ * instead of the group sender key.
746
+ */
554
747
  shouldUseGroupDirectPath(message) {
555
- return false;
748
+ return resolveGroupHistoryReceivers(message).length > 0;
556
749
  }
557
750
  async publishGroupDirectMessage(groupJid, envelope, retryContext = {}) {
558
751
  const { message, plaintext, type, edit, mediatype, sendOptions } = envelope;
@@ -562,8 +755,16 @@ class WaMessageDispatchCoordinator {
562
755
  : await this.deps.groupMetadataCache.resolveParticipantUsers(groupJid);
563
756
  const addressingMode = retryContext.forceAddressingMode ??
564
757
  this.resolveGroupAddressingMode(participantUserJids, groupJid);
565
- const senderForPhash = this.resolveSenderForAddressingMode(addressingMode, meJid);
566
- const fanoutDeviceJids = await this.deps.fanoutResolver.resolveGroupParticipantDeviceJids(participantUserJids);
758
+ const senderJid = this.resolveSenderForAddressingMode(addressingMode, meJid);
759
+ const targetUserJids = restrictGroupHistoryTargets(message, participantUserJids, senderJid, (droppedJids, totalExpected) => {
760
+ this.deps.logger.warn('group history receivers missing from the participant list', {
761
+ groupJid,
762
+ droppedCount: droppedJids.length,
763
+ totalExpected,
764
+ sample: droppedJids.slice(0, 3)
765
+ });
766
+ });
767
+ const fanoutDeviceJids = await this.deps.fanoutResolver.resolveGroupParticipantDeviceJids(targetUserJids);
567
768
  if (fanoutDeviceJids.length === 0) {
568
769
  throw new Error('group direct send resolved no target devices');
569
770
  }
@@ -618,22 +819,11 @@ class WaMessageDispatchCoordinator {
618
819
  break;
619
820
  }
620
821
  }
621
- const phashTargets = new Array(resolvedFanoutTargets.length + 1);
622
- let phashTargetCount = 0;
623
- for (let index = 0; index < resolvedFanoutTargets.length; index += 1) {
624
- const candidate = resolvedFanoutTargets[index].jid;
625
- if ((0, jid_1.isHostedDeviceJid)(candidate))
626
- continue;
627
- phashTargets[phashTargetCount] = candidate;
628
- phashTargetCount += 1;
629
- }
630
- phashTargets[phashTargetCount] = senderForPhash;
631
- phashTargets.length = phashTargetCount + 1;
632
- const localPhash = (0, phash_1.computePhashV2)(phashTargets);
633
- const reportingArtifacts = await this.tryBuildReportingTokenArtifacts({
822
+ const reportingArtifacts = this.tryBuildReportingTokenArtifacts({
634
823
  message,
824
+ messageBytes: (0, padding_1.unpadPkcs7)(plaintext),
635
825
  stanzaId: sendOptions.id,
636
- senderUserJid: (0, jid_1.toUserJid)(senderForPhash),
826
+ senderUserJid: (0, jid_1.toUserJid)(senderJid),
637
827
  remoteJid: groupJid,
638
828
  context: 'group_direct'
639
829
  });
@@ -645,7 +835,6 @@ class WaMessageDispatchCoordinator {
645
835
  type,
646
836
  id: sendOptions.id,
647
837
  edit,
648
- phash: localPhash,
649
838
  addressingMode,
650
839
  participants,
651
840
  deviceIdentity: shouldAttachDeviceIdentity
@@ -671,7 +860,7 @@ class WaMessageDispatchCoordinator {
671
860
  const ackError = result.ack.error;
672
861
  const serverPhash = result.ack.phash;
673
862
  const serverAddressingMode = result.ack.addressingMode;
674
- const hasPhashMismatch = !!serverPhash && serverPhash !== localPhash;
863
+ const hasPhashMismatch = !!serverPhash;
675
864
  const hasAddressingMismatch = !!serverAddressingMode && serverAddressingMode !== addressingMode;
676
865
  const hasAddressingError = ackError === constants_1.WA_NACK_REASONS.STALE_GROUP_ADDRESSING_MODE;
677
866
  if (!retryContext.retried &&
@@ -679,7 +868,6 @@ class WaMessageDispatchCoordinator {
679
868
  this.deps.logger.warn('group direct publish acknowledged with mismatch metadata', {
680
869
  id: result.id,
681
870
  groupJid,
682
- localPhash,
683
871
  serverPhash,
684
872
  localAddressingMode: addressingMode,
685
873
  serverAddressingMode,
@@ -788,8 +976,9 @@ class WaMessageDispatchCoordinator {
788
976
  phashTargets[phashTargetCount] = senderJid;
789
977
  phashTargets.length = phashTargetCount + 1;
790
978
  const localPhash = (0, phash_1.computePhashV2)(phashTargets);
791
- const reportingArtifacts = await this.tryBuildReportingTokenArtifacts({
979
+ const reportingArtifacts = this.tryBuildReportingTokenArtifacts({
792
980
  message,
981
+ messageBytes: (0, padding_1.unpadPkcs7)(plaintext),
793
982
  stanzaId: sendOptions.id,
794
983
  senderUserJid: (0, jid_1.toUserJid)(senderJid),
795
984
  remoteJid: groupJid,
@@ -901,9 +1090,6 @@ class WaMessageDispatchCoordinator {
901
1090
  return (0, jid_1.normalizeDeviceJid)(meJid);
902
1091
  }
903
1092
  async encryptGroupDistributionParticipants(groupJid, senderKeyId, senderKeyDistributionMessage, participantUserJids) {
904
- const distributionPayload = await (0, padding_1.writeRandomPadMax16)(_proto_1.proto.Message.encode({
905
- senderKeyDistributionMessage
906
- }).finish());
907
1093
  const fanoutDeviceJids = await this.deps.fanoutResolver.resolveGroupParticipantDeviceJids(participantUserJids);
908
1094
  if (fanoutDeviceJids.length === 0) {
909
1095
  return {
@@ -911,13 +1097,9 @@ class WaMessageDispatchCoordinator {
911
1097
  distributionParticipants: []
912
1098
  };
913
1099
  }
914
- const fanoutTargetsByAddressKey = new Map();
915
1100
  const fanoutAddresses = new Array(fanoutDeviceJids.length);
916
1101
  for (let index = 0; index < fanoutDeviceJids.length; index += 1) {
917
- const jid = fanoutDeviceJids[index];
918
- const address = (0, jid_1.parseSignalAddressFromJid)(jid);
919
- fanoutAddresses[index] = address;
920
- fanoutTargetsByAddressKey.set((0, jid_1.signalAddressKey)(address), { jid, address });
1102
+ fanoutAddresses[index] = (0, jid_1.parseSignalAddressFromJid)(fanoutDeviceJids[index]);
921
1103
  }
922
1104
  const pendingAddresses = await this.deps.senderKeyManager.filterParticipantsNeedingDistribution(groupJid, senderKeyId, fanoutAddresses);
923
1105
  if (pendingAddresses.length === 0) {
@@ -926,6 +1108,14 @@ class WaMessageDispatchCoordinator {
926
1108
  distributionParticipants: []
927
1109
  };
928
1110
  }
1111
+ const fanoutTargetsByAddressKey = new Map();
1112
+ for (let index = 0; index < fanoutAddresses.length; index += 1) {
1113
+ const address = fanoutAddresses[index];
1114
+ fanoutTargetsByAddressKey.set((0, jid_1.signalAddressKey)(address), {
1115
+ jid: fanoutDeviceJids[index],
1116
+ address
1117
+ });
1118
+ }
929
1119
  const pendingAddressKeys = new Set();
930
1120
  const pendingTargets = [];
931
1121
  for (let index = 0; index < pendingAddresses.length; index += 1) {
@@ -985,6 +1175,9 @@ class WaMessageDispatchCoordinator {
985
1175
  distributionParticipants: []
986
1176
  };
987
1177
  }
1178
+ const distributionPayload = await (0, padding_1.writeRandomPadMax16)(_proto_1.proto.Message.encode({
1179
+ senderKeyDistributionMessage
1180
+ }).finish());
988
1181
  const distributionEncryptRequests = new Array(availableTargets.length);
989
1182
  for (let index = 0; index < availableTargets.length; index += 1) {
990
1183
  const target = availableTargets[index];
@@ -1137,8 +1330,9 @@ class WaMessageDispatchCoordinator {
1137
1330
  const deviceIdentity = shouldAttachDeviceIdentity
1138
1331
  ? this.getEncodedSignedDeviceIdentity()
1139
1332
  : undefined;
1140
- const reportingArtifacts = await this.tryBuildReportingTokenArtifacts({
1333
+ const reportingArtifacts = this.tryBuildReportingTokenArtifacts({
1141
1334
  message,
1335
+ messageBytes: (0, padding_1.unpadPkcs7)(plaintext),
1142
1336
  stanzaId: sendOptions.id,
1143
1337
  senderUserJid: meUserJid,
1144
1338
  remoteJid: recipientUserJid,
@@ -1241,13 +1435,14 @@ class WaMessageDispatchCoordinator {
1241
1435
  return `3EB0${(0, bytes_1.bytesToHex)(await (0, _crypto_1.randomBytesAsync)(8)).toUpperCase()}`;
1242
1436
  }
1243
1437
  }
1244
- async tryBuildReportingTokenArtifacts(input) {
1438
+ tryBuildReportingTokenArtifacts(input) {
1245
1439
  if (!input.stanzaId) {
1246
1440
  return null;
1247
1441
  }
1248
1442
  try {
1249
- return await (0, reporting_token_1.buildReportingTokenArtifacts)({
1443
+ return (0, reporting_token_1.buildReportingTokenArtifacts)({
1250
1444
  message: input.message,
1445
+ messageBytes: input.messageBytes,
1251
1446
  stanzaId: input.stanzaId,
1252
1447
  senderUserJid: input.senderUserJid,
1253
1448
  remoteJid: input.remoteJid