zapo-js 0.1.1 → 0.2.0

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 (589) hide show
  1. package/README.md +20 -4
  2. package/dist/appstate/WaAppStateCrypto.js +19 -26
  3. package/dist/appstate/WaAppStateSyncClient.js +293 -181
  4. package/dist/appstate/WaAppStateSyncResponseParser.js +16 -5
  5. package/dist/appstate/constants.js +4 -3
  6. package/dist/appstate/{store/sqlite.js → encoding.js} +13 -8
  7. package/dist/appstate/index.js +8 -6
  8. package/dist/appstate/utils.js +9 -34
  9. package/dist/auth/WaAuthClient.js +43 -61
  10. package/dist/auth/flow/WaAuthCredentialsFlow.js +22 -15
  11. package/dist/auth/index.js +1 -8
  12. package/dist/auth/pairing/WaPairingCodeCrypto.js +6 -4
  13. package/dist/auth/pairing/WaPairingFlow.js +34 -26
  14. package/dist/auth/pairing/WaQrFlow.js +37 -24
  15. package/dist/client/WaClient.js +275 -324
  16. package/dist/client/WaClientFactory.js +500 -133
  17. package/dist/client/connection/WaConnectionManager.js +301 -0
  18. package/dist/client/connection/WaKeyShareCoordinator.js +63 -0
  19. package/dist/client/connection/WaReceiptQueue.js +51 -0
  20. package/dist/client/coordinators/WaAppStateMutationCoordinator.js +471 -0
  21. package/dist/client/coordinators/WaBusinessCoordinator.js +241 -0
  22. package/dist/client/coordinators/WaGroupCoordinator.js +30 -16
  23. package/dist/client/coordinators/WaIncomingNodeCoordinator.js +21 -27
  24. package/dist/client/coordinators/WaMessageDispatchCoordinator.js +439 -701
  25. package/dist/client/coordinators/WaPassiveTasksCoordinator.js +74 -31
  26. package/dist/client/coordinators/WaPrivacyCoordinator.js +134 -0
  27. package/dist/client/coordinators/WaProfileCoordinator.js +212 -0
  28. package/dist/client/coordinators/WaRetryCoordinator.js +242 -57
  29. package/dist/client/coordinators/WaStreamControlCoordinator.js +18 -11
  30. package/dist/client/coordinators/WaTrustedContactTokenCoordinator.js +166 -0
  31. package/dist/client/dirty.js +74 -48
  32. package/dist/client/events/chat.js +4 -3
  33. package/dist/client/events/devices.js +72 -0
  34. package/dist/client/events/group.js +62 -47
  35. package/dist/client/events/identity.js +22 -0
  36. package/dist/client/events/privacy-token.js +39 -0
  37. package/dist/client/history-sync.js +94 -63
  38. package/dist/client/incoming.js +60 -27
  39. package/dist/client/mailbox.js +24 -23
  40. package/dist/client/messages.js +107 -31
  41. package/dist/client/messaging/fanout.js +199 -0
  42. package/dist/client/messaging/key-protocol.js +130 -0
  43. package/dist/client/messaging/participants.js +193 -0
  44. package/dist/client/persistence/WriteBehindPersistence.js +129 -0
  45. package/dist/client/tokens/cs-token.js +50 -0
  46. package/dist/client/tokens/tc-token.js +25 -0
  47. package/dist/crypto/core/hkdf.js +3 -8
  48. package/dist/crypto/core/index.js +2 -5
  49. package/dist/crypto/core/keys.js +6 -7
  50. package/dist/crypto/core/nonce.js +2 -0
  51. package/dist/crypto/core/primitives.js +12 -23
  52. package/dist/crypto/core/random.js +26 -23
  53. package/dist/crypto/curves/Ed25519.js +7 -8
  54. package/dist/crypto/curves/X25519.js +38 -22
  55. package/dist/crypto/index.js +1 -3
  56. package/dist/crypto/math/constants.js +13 -36
  57. package/dist/crypto/math/edwards.js +171 -44
  58. package/dist/crypto/math/fe.js +706 -0
  59. package/dist/crypto/math/mod.js +10 -3
  60. package/dist/esm/appstate/WaAppStateCrypto.js +7 -14
  61. package/dist/esm/appstate/WaAppStateSyncClient.js +284 -172
  62. package/dist/esm/appstate/WaAppStateSyncResponseParser.js +17 -6
  63. package/dist/esm/appstate/constants.js +3 -2
  64. package/dist/esm/appstate/{store/sqlite.js → encoding.js} +13 -8
  65. package/dist/esm/appstate/index.js +2 -2
  66. package/dist/esm/appstate/utils.js +8 -30
  67. package/dist/esm/auth/WaAuthClient.js +43 -61
  68. package/dist/esm/auth/flow/WaAuthCredentialsFlow.js +22 -15
  69. package/dist/esm/auth/index.js +0 -3
  70. package/dist/esm/auth/pairing/WaPairingCodeCrypto.js +6 -4
  71. package/dist/esm/auth/pairing/WaPairingFlow.js +28 -20
  72. package/dist/esm/auth/pairing/WaQrFlow.js +37 -24
  73. package/dist/esm/client/WaClient.js +275 -324
  74. package/dist/esm/client/WaClientFactory.js +501 -134
  75. package/dist/esm/client/connection/WaConnectionManager.js +297 -0
  76. package/dist/esm/client/connection/WaKeyShareCoordinator.js +59 -0
  77. package/dist/esm/client/connection/WaReceiptQueue.js +47 -0
  78. package/dist/esm/client/coordinators/WaAppStateMutationCoordinator.js +467 -0
  79. package/dist/esm/client/coordinators/WaBusinessCoordinator.js +238 -0
  80. package/dist/esm/client/coordinators/WaGroupCoordinator.js +23 -9
  81. package/dist/esm/client/coordinators/WaIncomingNodeCoordinator.js +21 -27
  82. package/dist/esm/client/coordinators/WaMessageDispatchCoordinator.js +443 -705
  83. package/dist/esm/client/coordinators/WaPassiveTasksCoordinator.js +74 -31
  84. package/dist/esm/client/coordinators/WaPrivacyCoordinator.js +131 -0
  85. package/dist/esm/client/coordinators/WaProfileCoordinator.js +209 -0
  86. package/dist/esm/client/coordinators/WaRetryCoordinator.js +244 -59
  87. package/dist/esm/client/coordinators/WaStreamControlCoordinator.js +19 -12
  88. package/dist/esm/client/coordinators/WaTrustedContactTokenCoordinator.js +162 -0
  89. package/dist/esm/client/dirty.js +69 -43
  90. package/dist/esm/client/events/chat.js +4 -3
  91. package/dist/esm/client/events/devices.js +68 -0
  92. package/dist/esm/client/events/group.js +53 -39
  93. package/dist/esm/client/events/identity.js +19 -0
  94. package/dist/esm/client/events/privacy-token.js +36 -0
  95. package/dist/esm/client/history-sync.js +91 -60
  96. package/dist/esm/client/incoming.js +61 -28
  97. package/dist/esm/client/mailbox.js +24 -23
  98. package/dist/esm/client/messages.js +108 -32
  99. package/dist/esm/client/messaging/fanout.js +196 -0
  100. package/dist/esm/client/messaging/key-protocol.js +127 -0
  101. package/dist/esm/client/messaging/participants.js +190 -0
  102. package/dist/esm/client/persistence/WriteBehindPersistence.js +125 -0
  103. package/dist/esm/client/tokens/cs-token.js +46 -0
  104. package/dist/esm/client/tokens/tc-token.js +18 -0
  105. package/dist/esm/crypto/core/hkdf.js +3 -8
  106. package/dist/esm/crypto/core/index.js +2 -3
  107. package/dist/esm/crypto/core/keys.js +3 -4
  108. package/dist/esm/crypto/core/nonce.js +2 -0
  109. package/dist/esm/crypto/core/primitives.js +12 -22
  110. package/dist/esm/crypto/core/random.js +25 -23
  111. package/dist/esm/crypto/curves/Ed25519.js +4 -5
  112. package/dist/esm/crypto/curves/X25519.js +35 -19
  113. package/dist/esm/crypto/index.js +0 -1
  114. package/dist/esm/crypto/math/constants.js +12 -35
  115. package/dist/esm/crypto/math/edwards.js +174 -47
  116. package/dist/esm/crypto/math/fe.js +691 -0
  117. package/dist/esm/crypto/math/mod.js +10 -1
  118. package/dist/esm/index.js +1 -1
  119. package/dist/esm/infra/log/ConsoleLogger.js +18 -17
  120. package/dist/esm/infra/log/PinoLogger.js +15 -9
  121. package/dist/esm/infra/log/types.js +11 -1
  122. package/dist/esm/infra/perf/BackgroundQueue.js +478 -0
  123. package/dist/esm/infra/perf/BoundedTaskQueue.js +16 -18
  124. package/dist/esm/infra/perf/PromiseDedup.js +20 -0
  125. package/dist/esm/infra/perf/SharedExclusiveGate.js +109 -0
  126. package/dist/esm/infra/perf/StoreLock.js +77 -0
  127. package/dist/esm/media/WaMediaCrypto.js +96 -16
  128. package/dist/esm/media/WaMediaTransferClient.js +251 -91
  129. package/dist/esm/media/conn.js +10 -6
  130. package/dist/esm/media/constants.js +6 -2
  131. package/dist/esm/message/WaMessageClient.js +30 -32
  132. package/dist/esm/message/ack.js +6 -6
  133. package/dist/esm/message/addon-crypto.js +59 -0
  134. package/dist/esm/message/content.js +195 -9
  135. package/dist/esm/message/icdc.js +76 -0
  136. package/dist/esm/message/incoming.js +129 -122
  137. package/dist/esm/message/index.js +2 -0
  138. package/dist/esm/message/phash.js +3 -1
  139. package/dist/esm/message/reporting-token.js +425 -0
  140. package/dist/esm/message/use-case-secret.js +49 -0
  141. package/dist/esm/protocol/appstate.js +27 -0
  142. package/dist/esm/protocol/browser.js +10 -18
  143. package/dist/esm/protocol/constants.js +6 -3
  144. package/dist/esm/protocol/defaults.js +6 -0
  145. package/dist/esm/protocol/index.js +2 -11
  146. package/dist/esm/protocol/jid.js +133 -52
  147. package/dist/esm/protocol/media.js +3 -3
  148. package/dist/esm/protocol/message.js +61 -1
  149. package/dist/esm/protocol/nodes.js +4 -0
  150. package/dist/esm/protocol/notification.js +3 -1
  151. package/dist/esm/protocol/privacy-token.js +17 -0
  152. package/dist/esm/protocol/privacy.js +55 -0
  153. package/dist/esm/protocol/stream.js +26 -1
  154. package/dist/esm/protocol/usync.js +11 -0
  155. package/dist/esm/retry/codec.js +216 -0
  156. package/dist/esm/retry/constants.js +1 -1
  157. package/dist/esm/retry/index.js +3 -2
  158. package/dist/esm/retry/parse.js +88 -86
  159. package/dist/esm/retry/replay.js +54 -51
  160. package/dist/esm/retry/tracker.js +94 -0
  161. package/dist/esm/signal/api/SignalDeviceSyncApi.js +276 -92
  162. package/dist/esm/signal/api/SignalDigestSyncApi.js +17 -8
  163. package/dist/esm/signal/api/SignalIdentitySyncApi.js +67 -37
  164. package/dist/esm/signal/api/SignalMissingPreKeysSyncApi.js +86 -67
  165. package/dist/esm/signal/api/SignalRotateKeyApi.js +4 -2
  166. package/dist/esm/signal/api/SignalSessionSyncApi.js +36 -34
  167. package/dist/esm/signal/api/result-map.js +10 -0
  168. package/dist/esm/signal/constants.js +0 -4
  169. package/dist/esm/signal/crypto/WaAdvSignature.js +13 -9
  170. package/dist/esm/signal/{store/sqlite.js → encoding.js} +93 -60
  171. package/dist/esm/signal/group/SenderKeyChain.js +28 -23
  172. package/dist/esm/signal/group/SenderKeyCodec.js +5 -6
  173. package/dist/esm/signal/group/SenderKeyManager.js +144 -115
  174. package/dist/esm/signal/index.js +2 -0
  175. package/dist/esm/signal/registration/keygen.js +6 -2
  176. package/dist/esm/signal/registration/utils.js +1 -0
  177. package/dist/esm/signal/session/SignalProtocol.js +164 -53
  178. package/dist/esm/signal/session/SignalRatchet.js +24 -15
  179. package/dist/esm/signal/session/SignalSession.js +14 -9
  180. package/dist/esm/signal/session/resolver.js +221 -0
  181. package/dist/esm/store/contracts/privacy-token.store.js +1 -0
  182. package/dist/esm/store/createStore.js +100 -188
  183. package/dist/esm/store/index.js +1 -10
  184. package/dist/esm/store/locks/appstate.lock.js +26 -0
  185. package/dist/esm/store/locks/auth.lock.js +15 -0
  186. package/dist/esm/store/locks/contact.lock.js +20 -0
  187. package/dist/esm/store/locks/device-list.lock.js +20 -0
  188. package/dist/esm/store/locks/message.lock.js +21 -0
  189. package/dist/esm/store/locks/participants.lock.js +20 -0
  190. package/dist/esm/store/locks/privacy-token.lock.js +18 -0
  191. package/dist/esm/store/locks/retry.lock.js +29 -0
  192. package/dist/esm/store/locks/sender-key.lock.js +52 -0
  193. package/dist/esm/store/locks/signal.lock.js +63 -0
  194. package/dist/esm/store/locks/thread.lock.js +21 -0
  195. package/dist/esm/store/noop.store.js +4 -7
  196. package/dist/esm/store/providers/memory/appstate.store.js +38 -16
  197. package/dist/esm/store/providers/memory/contact.store.js +5 -0
  198. package/dist/esm/store/providers/memory/device-list.store.js +12 -34
  199. package/dist/esm/store/providers/memory/message.store.js +11 -5
  200. package/dist/esm/store/providers/memory/participants.store.js +1 -8
  201. package/dist/esm/store/providers/memory/privacy-token.store.js +43 -0
  202. package/dist/esm/store/providers/memory/retry.store.js +77 -2
  203. package/dist/esm/store/providers/memory/sender-key.store.js +11 -8
  204. package/dist/esm/store/providers/memory/signal.store.js +47 -18
  205. package/dist/esm/store/providers/memory/thread.store.js +5 -0
  206. package/dist/esm/transport/WaComms.js +28 -24
  207. package/dist/esm/transport/WaWebSocket.js +115 -18
  208. package/dist/esm/transport/binary/constants.js +0 -30
  209. package/dist/esm/transport/binary/decoder.js +8 -8
  210. package/dist/esm/transport/binary/encoder.js +10 -9
  211. package/dist/esm/transport/binary/index.js +0 -1
  212. package/dist/esm/transport/index.js +1 -0
  213. package/dist/esm/transport/keepalive/WaKeepAlive.js +2 -8
  214. package/dist/esm/transport/node/WaNodeOrchestrator.js +25 -21
  215. package/dist/esm/transport/node/WaNodeTransport.js +0 -3
  216. package/dist/esm/transport/node/builders/{accountSync.js → account-sync.js} +16 -36
  217. package/dist/esm/transport/node/builders/business.js +129 -0
  218. package/dist/esm/transport/node/builders/global.js +370 -0
  219. package/dist/esm/transport/node/builders/index.js +7 -3
  220. package/dist/esm/transport/node/builders/message.js +63 -230
  221. package/dist/esm/transport/node/builders/pairing.js +2 -27
  222. package/dist/esm/transport/node/builders/privacy-token.js +41 -0
  223. package/dist/esm/transport/node/builders/privacy.js +48 -0
  224. package/dist/esm/transport/node/builders/profile.js +70 -0
  225. package/dist/esm/transport/node/builders/retry.js +10 -22
  226. package/dist/esm/transport/node/builders/usync.js +45 -0
  227. package/dist/esm/transport/node/helpers.js +125 -5
  228. package/dist/esm/transport/node/usync.js +5 -0
  229. package/dist/esm/transport/node/xml.js +35 -14
  230. package/dist/esm/transport/noise/WaClientPayload.js +10 -10
  231. package/dist/esm/transport/noise/WaFrameCodec.js +48 -33
  232. package/dist/esm/transport/noise/WaNoiseCert.js +4 -7
  233. package/dist/esm/transport/noise/WaNoiseSession.js +77 -29
  234. package/dist/esm/transport/noise/WaNoiseSocket.js +8 -4
  235. package/dist/esm/transport/proxy.js +27 -0
  236. package/dist/esm/transport/stream/parse.js +17 -48
  237. package/dist/esm/util/bytes.js +67 -45
  238. package/dist/esm/util/coercion.js +6 -14
  239. package/dist/esm/util/index.js +5 -0
  240. package/dist/esm/util/primitives.js +40 -14
  241. package/dist/index.js +7 -1
  242. package/dist/infra/log/ConsoleLogger.js +18 -17
  243. package/dist/infra/log/PinoLogger.js +15 -9
  244. package/dist/infra/log/types.js +12 -0
  245. package/dist/infra/perf/BackgroundQueue.js +482 -0
  246. package/dist/infra/perf/BoundedTaskQueue.js +16 -18
  247. package/dist/infra/perf/PromiseDedup.js +24 -0
  248. package/dist/infra/perf/SharedExclusiveGate.js +113 -0
  249. package/dist/infra/perf/StoreLock.js +81 -0
  250. package/dist/media/WaMediaCrypto.js +95 -15
  251. package/dist/media/WaMediaTransferClient.js +284 -91
  252. package/dist/media/conn.js +10 -6
  253. package/dist/media/constants.js +6 -2
  254. package/dist/message/WaMessageClient.js +31 -33
  255. package/dist/message/ack.js +6 -6
  256. package/dist/message/addon-crypto.js +65 -0
  257. package/dist/message/content.js +198 -9
  258. package/dist/message/icdc.js +81 -0
  259. package/dist/message/incoming.js +127 -120
  260. package/dist/message/index.js +2 -0
  261. package/dist/message/phash.js +3 -1
  262. package/dist/message/reporting-token.js +429 -0
  263. package/dist/message/use-case-secret.js +55 -0
  264. package/dist/protocol/appstate.js +28 -1
  265. package/dist/protocol/browser.js +10 -18
  266. package/dist/protocol/constants.js +26 -1
  267. package/dist/protocol/defaults.js +6 -0
  268. package/dist/protocol/index.js +23 -42
  269. package/dist/protocol/jid.js +140 -52
  270. package/dist/protocol/media.js +3 -3
  271. package/dist/protocol/message.js +62 -2
  272. package/dist/protocol/nodes.js +4 -0
  273. package/dist/protocol/notification.js +3 -1
  274. package/dist/protocol/privacy-token.js +20 -0
  275. package/dist/protocol/privacy.js +58 -0
  276. package/dist/protocol/stream.js +27 -2
  277. package/dist/protocol/usync.js +14 -0
  278. package/dist/retry/codec.js +220 -0
  279. package/dist/retry/constants.js +1 -1
  280. package/dist/retry/index.js +7 -5
  281. package/dist/retry/parse.js +88 -85
  282. package/dist/retry/replay.js +52 -49
  283. package/dist/retry/tracker.js +97 -0
  284. package/dist/signal/api/SignalDeviceSyncApi.js +273 -89
  285. package/dist/signal/api/SignalDigestSyncApi.js +17 -8
  286. package/dist/signal/api/SignalIdentitySyncApi.js +66 -36
  287. package/dist/signal/api/SignalMissingPreKeysSyncApi.js +82 -63
  288. package/dist/signal/api/SignalRotateKeyApi.js +4 -2
  289. package/dist/signal/api/SignalSessionSyncApi.js +36 -34
  290. package/dist/signal/api/result-map.js +13 -0
  291. package/dist/signal/constants.js +1 -5
  292. package/dist/signal/crypto/WaAdvSignature.js +11 -7
  293. package/dist/signal/{store/sqlite.js → encoding.js} +94 -61
  294. package/dist/signal/group/SenderKeyChain.js +27 -22
  295. package/dist/signal/group/SenderKeyCodec.js +5 -6
  296. package/dist/signal/group/SenderKeyManager.js +144 -115
  297. package/dist/signal/index.js +15 -1
  298. package/dist/signal/registration/keygen.js +6 -2
  299. package/dist/signal/registration/utils.js +1 -0
  300. package/dist/signal/session/SignalProtocol.js +164 -53
  301. package/dist/signal/session/SignalRatchet.js +24 -15
  302. package/dist/signal/session/SignalSession.js +14 -9
  303. package/dist/signal/session/resolver.js +224 -0
  304. package/dist/store/contracts/privacy-token.store.js +2 -0
  305. package/dist/store/createStore.js +100 -188
  306. package/dist/store/index.js +15 -33
  307. package/dist/store/locks/appstate.lock.js +29 -0
  308. package/dist/store/locks/auth.lock.js +18 -0
  309. package/dist/store/locks/contact.lock.js +23 -0
  310. package/dist/store/locks/device-list.lock.js +23 -0
  311. package/dist/store/locks/message.lock.js +24 -0
  312. package/dist/store/locks/participants.lock.js +23 -0
  313. package/dist/store/locks/privacy-token.lock.js +21 -0
  314. package/dist/store/locks/retry.lock.js +32 -0
  315. package/dist/store/locks/sender-key.lock.js +55 -0
  316. package/dist/store/locks/signal.lock.js +66 -0
  317. package/dist/store/locks/thread.lock.js +24 -0
  318. package/dist/store/noop.store.js +4 -7
  319. package/dist/store/providers/memory/appstate.store.js +36 -14
  320. package/dist/store/providers/memory/contact.store.js +5 -0
  321. package/dist/store/providers/memory/device-list.store.js +12 -34
  322. package/dist/store/providers/memory/message.store.js +11 -5
  323. package/dist/store/providers/memory/participants.store.js +1 -8
  324. package/dist/store/providers/memory/privacy-token.store.js +47 -0
  325. package/dist/store/providers/memory/retry.store.js +77 -2
  326. package/dist/store/providers/memory/sender-key.store.js +14 -11
  327. package/dist/store/providers/memory/signal.store.js +54 -25
  328. package/dist/store/providers/memory/thread.store.js +5 -0
  329. package/dist/transport/WaComms.js +30 -26
  330. package/dist/transport/WaWebSocket.js +148 -18
  331. package/dist/transport/binary/constants.js +1 -31
  332. package/dist/transport/binary/decoder.js +8 -8
  333. package/dist/transport/binary/encoder.js +10 -9
  334. package/dist/transport/binary/index.js +0 -4
  335. package/dist/transport/index.js +7 -1
  336. package/dist/transport/keepalive/WaKeepAlive.js +1 -7
  337. package/dist/transport/node/WaNodeOrchestrator.js +25 -21
  338. package/dist/transport/node/WaNodeTransport.js +0 -3
  339. package/dist/transport/node/builders/{accountSync.js → account-sync.js} +15 -35
  340. package/dist/transport/node/builders/business.js +137 -0
  341. package/dist/transport/node/builders/global.js +375 -0
  342. package/dist/transport/node/builders/index.js +29 -17
  343. package/dist/transport/node/builders/message.js +64 -236
  344. package/dist/transport/node/builders/pairing.js +2 -29
  345. package/dist/transport/node/builders/privacy-token.js +46 -0
  346. package/dist/transport/node/builders/privacy.js +55 -0
  347. package/dist/transport/node/builders/profile.js +78 -0
  348. package/dist/transport/node/builders/retry.js +9 -21
  349. package/dist/transport/node/builders/usync.js +49 -0
  350. package/dist/transport/node/helpers.js +131 -4
  351. package/dist/transport/node/usync.js +8 -0
  352. package/dist/transport/node/xml.js +35 -14
  353. package/dist/transport/noise/WaClientPayload.js +13 -13
  354. package/dist/transport/noise/WaFrameCodec.js +47 -32
  355. package/dist/transport/noise/WaNoiseCert.js +5 -8
  356. package/dist/transport/noise/WaNoiseSession.js +77 -29
  357. package/dist/transport/noise/WaNoiseSocket.js +8 -4
  358. package/dist/transport/proxy.js +34 -0
  359. package/dist/transport/stream/parse.js +20 -52
  360. package/dist/types/appstate/WaAppStateCrypto.d.ts +0 -1
  361. package/dist/types/appstate/WaAppStateSyncClient.d.ts +5 -2
  362. package/dist/types/appstate/constants.d.ts +1 -0
  363. package/dist/types/appstate/encoding.d.ts +7 -0
  364. package/dist/types/appstate/index.d.ts +3 -3
  365. package/dist/types/appstate/utils.d.ts +0 -3
  366. package/dist/types/auth/WaAuthClient.d.ts +10 -12
  367. package/dist/types/auth/flow/WaAuthCredentialsFlow.d.ts +1 -1
  368. package/dist/types/auth/index.d.ts +0 -4
  369. package/dist/types/auth/pairing/WaQrFlow.d.ts +1 -1
  370. package/dist/types/auth/types.d.ts +7 -9
  371. package/dist/types/client/WaClient.d.ts +42 -25
  372. package/dist/types/client/WaClientFactory.d.ts +33 -26
  373. package/dist/types/client/connection/WaConnectionManager.d.ts +66 -0
  374. package/dist/types/client/connection/WaKeyShareCoordinator.d.ts +14 -0
  375. package/dist/types/client/connection/WaReceiptQueue.d.ts +13 -0
  376. package/dist/types/client/coordinators/WaAppStateMutationCoordinator.d.ts +46 -0
  377. package/dist/types/client/coordinators/WaBusinessCoordinator.d.ts +57 -0
  378. package/dist/types/client/coordinators/WaIncomingNodeCoordinator.d.ts +3 -2
  379. package/dist/types/client/coordinators/WaMessageDispatchCoordinator.d.ts +29 -38
  380. package/dist/types/client/coordinators/WaPassiveTasksCoordinator.d.ts +4 -0
  381. package/dist/types/client/coordinators/WaPrivacyCoordinator.d.ts +26 -0
  382. package/dist/types/client/coordinators/WaProfileCoordinator.d.ts +36 -0
  383. package/dist/types/client/coordinators/WaRetryCoordinator.d.ts +8 -0
  384. package/dist/types/client/coordinators/WaStreamControlCoordinator.d.ts +3 -2
  385. package/dist/types/client/coordinators/WaTrustedContactTokenCoordinator.d.ts +45 -0
  386. package/dist/types/client/dirty.d.ts +1 -0
  387. package/dist/types/client/events/devices.d.ts +20 -0
  388. package/dist/types/client/events/group.d.ts +2 -1
  389. package/dist/types/client/events/identity.d.ts +9 -0
  390. package/dist/types/client/events/privacy-token.d.ts +7 -0
  391. package/dist/types/client/history-sync.d.ts +9 -6
  392. package/dist/types/client/incoming.d.ts +3 -1
  393. package/dist/types/client/index.d.ts +1 -1
  394. package/dist/types/client/mailbox.d.ts +3 -5
  395. package/dist/types/client/messages.d.ts +1 -2
  396. package/dist/types/client/messaging/fanout.d.ts +14 -0
  397. package/dist/types/client/messaging/key-protocol.d.ts +18 -0
  398. package/dist/types/client/messaging/participants.d.ts +13 -0
  399. package/dist/types/client/persistence/WriteBehindPersistence.d.ts +34 -0
  400. package/dist/types/client/tokens/cs-token.d.ts +10 -0
  401. package/dist/types/client/tokens/tc-token.d.ts +5 -0
  402. package/dist/types/client/types.d.ts +75 -4
  403. package/dist/types/crypto/core/hkdf.d.ts +0 -6
  404. package/dist/types/crypto/core/index.d.ts +2 -3
  405. package/dist/types/crypto/core/nonce.d.ts +2 -0
  406. package/dist/types/crypto/core/primitives.d.ts +0 -1
  407. package/dist/types/crypto/core/random.d.ts +2 -7
  408. package/dist/types/crypto/index.d.ts +0 -1
  409. package/dist/types/crypto/math/constants.d.ts +4 -2
  410. package/dist/types/crypto/math/fe.d.ts +30 -0
  411. package/dist/types/crypto/math/mod.d.ts +0 -2
  412. package/dist/types/crypto/math/types.d.ts +11 -4
  413. package/dist/types/index.d.ts +5 -3
  414. package/dist/types/infra/log/ConsoleLogger.d.ts +2 -1
  415. package/dist/types/infra/log/PinoLogger.d.ts +1 -1
  416. package/dist/types/infra/log/types.d.ts +1 -0
  417. package/dist/types/infra/perf/BackgroundQueue.d.ts +58 -0
  418. package/dist/types/infra/perf/BoundedTaskQueue.d.ts +1 -1
  419. package/dist/types/infra/perf/PromiseDedup.d.ts +4 -0
  420. package/dist/types/infra/perf/SharedExclusiveGate.d.ts +17 -0
  421. package/dist/types/infra/perf/StoreLock.d.ts +10 -0
  422. package/dist/types/media/WaMediaCrypto.d.ts +3 -2
  423. package/dist/types/media/WaMediaTransferClient.d.ts +16 -15
  424. package/dist/types/media/constants.d.ts +1 -1
  425. package/dist/types/media/index.d.ts +1 -1
  426. package/dist/types/media/types.d.ts +15 -2
  427. package/dist/types/message/addon-crypto.d.ts +25 -0
  428. package/dist/types/message/content.d.ts +8 -0
  429. package/dist/types/message/icdc.d.ts +13 -0
  430. package/dist/types/message/index.d.ts +2 -0
  431. package/dist/types/message/reporting-token.d.ts +18 -0
  432. package/dist/types/message/types.d.ts +45 -6
  433. package/dist/types/message/use-case-secret.d.ts +20 -0
  434. package/dist/types/protocol/appstate.d.ts +47 -0
  435. package/dist/types/protocol/constants.d.ts +8 -3
  436. package/dist/types/protocol/defaults.d.ts +6 -0
  437. package/dist/types/protocol/index.d.ts +2 -11
  438. package/dist/types/protocol/jid.d.ts +22 -5
  439. package/dist/types/protocol/message.d.ts +60 -0
  440. package/dist/types/protocol/nodes.d.ts +4 -0
  441. package/dist/types/protocol/notification.d.ts +2 -0
  442. package/dist/types/protocol/privacy-token.d.ts +17 -0
  443. package/dist/types/protocol/privacy.d.ts +75 -0
  444. package/dist/types/protocol/stream.d.ts +30 -0
  445. package/dist/types/protocol/usync.d.ts +11 -0
  446. package/dist/types/retry/codec.d.ts +3 -0
  447. package/dist/types/retry/index.d.ts +4 -3
  448. package/dist/types/retry/parse.d.ts +5 -2
  449. package/dist/types/retry/replay.d.ts +0 -4
  450. package/dist/types/retry/tracker.d.ts +20 -0
  451. package/dist/types/retry/types.d.ts +10 -4
  452. package/dist/types/signal/api/SignalDeviceSyncApi.d.ts +15 -2
  453. package/dist/types/signal/api/SignalDigestSyncApi.d.ts +6 -0
  454. package/dist/types/signal/api/SignalIdentitySyncApi.d.ts +2 -0
  455. package/dist/types/signal/api/SignalRotateKeyApi.d.ts +4 -5
  456. package/dist/types/signal/api/SignalSessionSyncApi.d.ts +8 -6
  457. package/dist/types/signal/api/result-map.d.ts +1 -0
  458. package/dist/types/signal/constants.d.ts +0 -3
  459. package/dist/types/signal/{store/sqlite.d.ts → encoding.d.ts} +3 -3
  460. package/dist/types/signal/group/SenderKeyCodec.d.ts +4 -6
  461. package/dist/types/signal/group/SenderKeyManager.d.ts +10 -5
  462. package/dist/types/signal/index.d.ts +3 -0
  463. package/dist/types/signal/session/SignalProtocol.d.ts +19 -4
  464. package/dist/types/signal/session/resolver.d.ts +22 -0
  465. package/dist/types/store/contracts/appstate.store.d.ts +4 -1
  466. package/dist/types/store/contracts/contact.store.d.ts +1 -0
  467. package/dist/types/store/contracts/device-list.store.d.ts +0 -3
  468. package/dist/types/store/contracts/message.store.d.ts +1 -0
  469. package/dist/types/store/contracts/participants.store.d.ts +0 -1
  470. package/dist/types/store/contracts/privacy-token.store.d.ts +16 -0
  471. package/dist/types/store/contracts/retry.store.d.ts +7 -0
  472. package/dist/types/store/contracts/sender-key.store.d.ts +0 -1
  473. package/dist/types/store/contracts/signal.store.d.ts +13 -0
  474. package/dist/types/store/contracts/thread.store.d.ts +1 -0
  475. package/dist/types/store/createStore.d.ts +1 -1
  476. package/dist/types/store/index.d.ts +5 -13
  477. package/dist/types/store/locks/appstate.lock.d.ts +3 -0
  478. package/dist/types/store/locks/auth.lock.d.ts +3 -0
  479. package/dist/types/store/locks/contact.lock.d.ts +3 -0
  480. package/dist/types/store/locks/device-list.lock.d.ts +2 -0
  481. package/dist/types/store/locks/message.lock.d.ts +3 -0
  482. package/dist/types/store/locks/participants.lock.d.ts +2 -0
  483. package/dist/types/store/locks/privacy-token.lock.d.ts +2 -0
  484. package/dist/types/store/locks/retry.lock.d.ts +2 -0
  485. package/dist/types/store/locks/sender-key.lock.d.ts +3 -0
  486. package/dist/types/store/locks/signal.lock.d.ts +3 -0
  487. package/dist/types/store/locks/thread.lock.d.ts +3 -0
  488. package/dist/types/store/providers/memory/appstate.store.d.ts +3 -1
  489. package/dist/types/store/providers/memory/contact.store.d.ts +1 -0
  490. package/dist/types/store/providers/memory/device-list.store.d.ts +0 -3
  491. package/dist/types/store/providers/memory/message.store.d.ts +1 -0
  492. package/dist/types/store/providers/memory/participants.store.d.ts +0 -1
  493. package/dist/types/store/providers/memory/privacy-token.store.d.ts +13 -0
  494. package/dist/types/store/providers/memory/retry.store.d.ts +8 -0
  495. package/dist/types/store/providers/memory/sender-key.store.d.ts +0 -1
  496. package/dist/types/store/providers/memory/signal.store.d.ts +8 -1
  497. package/dist/types/store/providers/memory/thread.store.d.ts +1 -0
  498. package/dist/types/store/types.d.ts +49 -58
  499. package/dist/types/transport/WaWebSocket.d.ts +3 -1
  500. package/dist/types/transport/binary/constants.d.ts +0 -30
  501. package/dist/types/transport/binary/index.d.ts +0 -1
  502. package/dist/types/transport/index.d.ts +2 -1
  503. package/dist/types/transport/keepalive/WaKeepAlive.d.ts +0 -1
  504. package/dist/types/transport/node/WaNodeOrchestrator.d.ts +3 -4
  505. package/dist/types/transport/node/WaNodeTransport.d.ts +0 -9
  506. package/dist/types/transport/node/builders/business.d.ts +29 -0
  507. package/dist/types/transport/node/builders/global.d.ts +102 -0
  508. package/dist/types/transport/node/builders/group.d.ts +4 -6
  509. package/dist/types/transport/node/builders/index.d.ts +7 -3
  510. package/dist/types/transport/node/builders/message.d.ts +20 -30
  511. package/dist/types/transport/node/builders/pairing.d.ts +0 -2
  512. package/dist/types/transport/node/builders/privacy-token.d.ts +9 -0
  513. package/dist/types/transport/node/builders/privacy.d.ts +7 -0
  514. package/dist/types/transport/node/builders/profile.d.ts +8 -0
  515. package/dist/types/transport/node/builders/retry.d.ts +2 -5
  516. package/dist/types/transport/node/builders/usync.d.ts +21 -0
  517. package/dist/types/transport/node/helpers.d.ts +13 -0
  518. package/dist/types/transport/node/usync.d.ts +2 -0
  519. package/dist/types/transport/noise/WaFrameCodec.d.ts +3 -0
  520. package/dist/types/transport/noise/WaNoiseSession.d.ts +4 -2
  521. package/dist/types/transport/noise/WaNoiseSocket.d.ts +4 -2
  522. package/dist/types/transport/proxy.d.ts +6 -0
  523. package/dist/types/transport/stream/parse.d.ts +0 -1
  524. package/dist/types/transport/types.d.ts +18 -1
  525. package/dist/types/util/bytes.d.ts +5 -0
  526. package/dist/types/util/index.d.ts +5 -0
  527. package/dist/types/util/primitives.d.ts +2 -0
  528. package/dist/util/bytes.js +72 -46
  529. package/dist/util/coercion.js +6 -14
  530. package/dist/util/index.js +23 -0
  531. package/dist/util/primitives.js +42 -14
  532. package/package.json +52 -9
  533. package/proto/index.js +1 -1
  534. package/dist/crypto/core/constants.js +0 -4
  535. package/dist/crypto/core/encoding.js +0 -29
  536. package/dist/esm/crypto/core/constants.js +0 -1
  537. package/dist/esm/crypto/core/encoding.js +0 -25
  538. package/dist/esm/retry/outbound.js +0 -83
  539. package/dist/esm/store/providers/sqlite/BaseSqliteStore.js +0 -37
  540. package/dist/esm/store/providers/sqlite/appstate.store.js +0 -169
  541. package/dist/esm/store/providers/sqlite/auth.store.js +0 -176
  542. package/dist/esm/store/providers/sqlite/connection.js +0 -240
  543. package/dist/esm/store/providers/sqlite/contact.store.js +0 -61
  544. package/dist/esm/store/providers/sqlite/device-list.store.js +0 -155
  545. package/dist/esm/store/providers/sqlite/message.store.js +0 -119
  546. package/dist/esm/store/providers/sqlite/migrations.js +0 -347
  547. package/dist/esm/store/providers/sqlite/participants.store.js +0 -85
  548. package/dist/esm/store/providers/sqlite/retry.store.js +0 -144
  549. package/dist/esm/store/providers/sqlite/sender-key.store.js +0 -203
  550. package/dist/esm/store/providers/sqlite/signal.store.js +0 -353
  551. package/dist/esm/store/providers/sqlite/thread.store.js +0 -72
  552. package/dist/esm/util/base64.js +0 -18
  553. package/dist/esm/util/signal-address.js +0 -5
  554. package/dist/retry/outbound.js +0 -88
  555. package/dist/store/providers/sqlite/BaseSqliteStore.js +0 -41
  556. package/dist/store/providers/sqlite/appstate.store.js +0 -173
  557. package/dist/store/providers/sqlite/auth.store.js +0 -180
  558. package/dist/store/providers/sqlite/connection.js +0 -276
  559. package/dist/store/providers/sqlite/contact.store.js +0 -65
  560. package/dist/store/providers/sqlite/device-list.store.js +0 -159
  561. package/dist/store/providers/sqlite/message.store.js +0 -123
  562. package/dist/store/providers/sqlite/migrations.js +0 -350
  563. package/dist/store/providers/sqlite/participants.store.js +0 -89
  564. package/dist/store/providers/sqlite/retry.store.js +0 -148
  565. package/dist/store/providers/sqlite/sender-key.store.js +0 -207
  566. package/dist/store/providers/sqlite/signal.store.js +0 -357
  567. package/dist/store/providers/sqlite/thread.store.js +0 -76
  568. package/dist/types/appstate/store/sqlite.d.ts +0 -21
  569. package/dist/types/crypto/core/constants.d.ts +0 -1
  570. package/dist/types/crypto/core/encoding.d.ts +0 -11
  571. package/dist/types/retry/outbound.d.ts +0 -4
  572. package/dist/types/store/providers/sqlite/BaseSqliteStore.d.ts +0 -12
  573. package/dist/types/store/providers/sqlite/appstate.store.d.ts +0 -15
  574. package/dist/types/store/providers/sqlite/auth.store.d.ts +0 -10
  575. package/dist/types/store/providers/sqlite/connection.d.ts +0 -10
  576. package/dist/types/store/providers/sqlite/contact.store.d.ts +0 -10
  577. package/dist/types/store/providers/sqlite/device-list.store.d.ts +0 -18
  578. package/dist/types/store/providers/sqlite/message.store.d.ts +0 -11
  579. package/dist/types/store/providers/sqlite/migrations.d.ts +0 -3
  580. package/dist/types/store/providers/sqlite/participants.store.d.ts +0 -13
  581. package/dist/types/store/providers/sqlite/retry.store.d.ts +0 -16
  582. package/dist/types/store/providers/sqlite/sender-key.store.d.ts +0 -25
  583. package/dist/types/store/providers/sqlite/signal.store.d.ts +0 -46
  584. package/dist/types/store/providers/sqlite/thread.store.d.ts +0 -11
  585. package/dist/types/util/base64.d.ts +0 -4
  586. package/dist/types/util/signal-address.d.ts +0 -2
  587. package/dist/util/base64.js +0 -24
  588. package/dist/util/signal-address.js +0 -8
  589. /package/dist/types/transport/node/builders/{accountSync.d.ts → account-sync.d.ts} +0 -0
@@ -1,36 +1,42 @@
1
- import { toSerializedPubKey } from '../../crypto/core/keys.js';
2
- import { resolveMessageTypeAttr } from '../../message/content.js';
1
+ import { randomBytesAsync, sha256 } from '../../crypto/index.js';
2
+ import { PromiseDedup } from '../../infra/perf/PromiseDedup.js';
3
+ import { ensureMessageSecret } from '../../message/index.js';
4
+ import { resolveEditAttr, resolveEncMediaType, resolveMessageTypeAttr, resolveMetaAttrs } from '../../message/content.js';
3
5
  import { wrapDeviceSentMessage } from '../../message/device-sent.js';
6
+ import { injectDeviceListMetadata, resolveIcdcMeta } from '../../message/icdc.js';
4
7
  import { writeRandomPadMax16 } from '../../message/padding.js';
5
8
  import { computePhashV2 } from '../../message/phash.js';
9
+ import { buildReportingTokenArtifacts } from '../../message/reporting-token.js';
6
10
  import { proto } from '../../proto.js';
7
11
  import { WA_DEFAULTS } from '../../protocol/constants.js';
8
- import { isGroupJid, normalizeDeviceJid, normalizeRecipientJid, parseSignalAddressFromJid, splitJid, toUserJid } from '../../protocol/jid.js';
9
- import { RETRY_OUTBOUND_TTL_MS } from '../../retry/constants.js';
10
- import { encodeRetryReplayPayload } from '../../retry/outbound.js';
12
+ import { isGroupJid, normalizeDeviceJid, normalizeRecipientJid, parseJidFull, parseSignalAddressFromJid, splitJid, toUserJid } from '../../protocol/jid.js';
13
+ import { signalAddressKey } from '../../protocol/jid.js';
11
14
  import { encodeBinaryNode } from '../../transport/binary/index.js';
12
- import { buildDirectMessageFanoutNode, buildGroupDirectMessageNode, buildGroupSenderKeyMessageNode } from '../../transport/node/builders/message.js';
13
- import { bytesToHex, uint8Equal } from '../../util/bytes.js';
15
+ import { buildDirectMessageFanoutNode, buildGroupSenderKeyMessageNode, buildMetaNode } from '../../transport/node/builders/message.js';
16
+ import { bytesToHex, concatBytes, TEXT_ENCODER } from '../../util/bytes.js';
14
17
  import { toError } from '../../util/primitives.js';
15
- import { signalAddressKey } from '../../util/signal-address.js';
16
18
  export class WaMessageDispatchCoordinator {
17
19
  constructor(options) {
20
+ this.icdcDedup = new PromiseDedup();
21
+ this.privacyTokenDedup = new PromiseDedup();
22
+ this.distributionDedup = new PromiseDedup();
18
23
  this.logger = options.logger;
19
24
  this.messageClient = options.messageClient;
20
- this.retryStore = options.retryStore;
21
- this.participantsStore = options.participantsStore;
22
- this.retryTtlMs = this.retryStore.getTtlMs?.() ?? RETRY_OUTBOUND_TTL_MS;
25
+ this.retryTracker = options.retryTracker;
26
+ this.sessionResolver = options.sessionResolver;
27
+ this.fanoutResolver = options.fanoutResolver;
28
+ this.participantsCache = options.participantsCache;
29
+ this.appStateSyncKeyProtocol = options.appStateSyncKeyProtocol;
23
30
  this.buildMessageContent = options.buildMessageContent;
24
- this.queryGroupParticipantJids = options.queryGroupParticipantJids;
25
31
  this.senderKeyManager = options.senderKeyManager;
26
32
  this.signalProtocol = options.signalProtocol;
27
33
  this.signalStore = options.signalStore;
28
- this.signalDeviceSync = options.signalDeviceSync;
29
- this.signalIdentitySync = options.signalIdentitySync;
30
- this.signalSessionSync = options.signalSessionSync;
34
+ this.deviceListStore = options.deviceListStore;
31
35
  this.getCurrentMeJid = options.getCurrentMeJid;
32
36
  this.getCurrentMeLid = options.getCurrentMeLid;
33
37
  this.getCurrentSignedIdentity = options.getCurrentSignedIdentity;
38
+ this.resolvePrivacyTokenNode = options.resolvePrivacyTokenNode;
39
+ this.onDirectMessageSent = options.onDirectMessageSent;
34
40
  }
35
41
  async publishMessageNode(node, options = {}) {
36
42
  this.logger.debug('wa client publish message node', {
@@ -43,13 +49,13 @@ export class WaMessageDispatchCoordinator {
43
49
  mode: 'opaque_node',
44
50
  node: encodeBinaryNode(node)
45
51
  };
46
- return this.publishWithRetryTracking({
52
+ return this.retryTracker.track({
47
53
  messageIdHint: node.attrs.id,
48
54
  toJid: node.attrs.to,
55
+ type: messageType,
56
+ replayPayload,
49
57
  participantJid: node.attrs.participant,
50
- recipientJid: node.attrs.recipient,
51
- messageType,
52
- replayPayload
58
+ recipientJid: node.attrs.recipient
53
59
  }, async () => this.messageClient.publishNode(node, options));
54
60
  }
55
61
  async publishEncryptedMessage(input, options = {}) {
@@ -66,12 +72,13 @@ export class WaMessageDispatchCoordinator {
66
72
  ciphertext: input.ciphertext,
67
73
  participant: input.participant
68
74
  };
69
- return this.publishWithRetryTracking({
75
+ return this.retryTracker.track({
70
76
  messageIdHint: input.id,
71
77
  toJid: input.to,
78
+ type: input.type ?? 'text',
79
+ replayPayload,
72
80
  participantJid: input.participant,
73
- messageType: input.type ?? 'text',
74
- replayPayload
81
+ eligibleRequesterDeviceJids: [input.to]
75
82
  }, async () => this.messageClient.publishEncrypted(input, options));
76
83
  }
77
84
  async publishSignalMessage(input, options = {}) {
@@ -86,7 +93,7 @@ export class WaMessageDispatchCoordinator {
86
93
  });
87
94
  const [paddedPlaintext] = await Promise.all([
88
95
  writeRandomPadMax16(input.plaintext),
89
- this.ensureSignalSession(address, input.to, input.expectedIdentity)
96
+ this.sessionResolver.ensureSession(address, input.to, input.expectedIdentity)
90
97
  ]);
91
98
  const encrypted = await this.signalProtocol.encryptMessage(address, paddedPlaintext, input.expectedIdentity);
92
99
  const messageType = input.type ?? 'text';
@@ -96,12 +103,13 @@ export class WaMessageDispatchCoordinator {
96
103
  type: messageType,
97
104
  plaintext: paddedPlaintext
98
105
  };
99
- return this.publishWithRetryTracking({
106
+ return this.retryTracker.track({
100
107
  messageIdHint: input.id,
101
108
  toJid: input.to,
109
+ type: messageType,
110
+ replayPayload,
102
111
  participantJid: input.participant,
103
- messageType,
104
- replayPayload
112
+ eligibleRequesterDeviceJids: [input.to]
105
113
  }, async () => this.messageClient.publishEncrypted({
106
114
  to: input.to,
107
115
  encType: encrypted.type,
@@ -116,286 +124,56 @@ export class WaMessageDispatchCoordinator {
116
124
  }
117
125
  async sendMessage(to, content, options = {}) {
118
126
  const recipientJid = normalizeRecipientJid(to);
119
- const message = await this.buildMessageContent(content);
120
- const plaintext = await writeRandomPadMax16(proto.Message.encode(message).finish());
121
- const type = resolveMessageTypeAttr(message);
122
- if (isGroupJid(recipientJid)) {
123
- if (this.shouldUseGroupDirectPath(message)) {
124
- return this.publishGroupDirectMessage(recipientJid, plaintext, type, options);
125
- }
126
- return this.publishGroupSenderKeyMessage(recipientJid, plaintext, type, options);
127
+ const [message, sendOptions] = await Promise.all([
128
+ this.buildMessageContent(content),
129
+ this.withResolvedMessageId(options)
130
+ ]);
131
+ const messageWithSecret = await ensureMessageSecret(message);
132
+ const meJid = this.getCurrentMeJid();
133
+ const regInfo = meJid ? await this.signalStore.getRegistrationInfo() : null;
134
+ const localPubKey = regInfo?.identityKeyPair.pubKey;
135
+ const meParsed = meJid ? parseJidFull(meJid) : undefined;
136
+ const meUserJid = meParsed?.userJid;
137
+ const localIdentity = meParsed && localPubKey ? { address: meParsed.address, pubKey: localPubKey } : undefined;
138
+ const isGroup = isGroupJid(recipientJid);
139
+ const [senderIcdc, recipientIcdc] = await Promise.all([
140
+ meUserJid ? this.resolveUserIcdc(meUserJid, localIdentity) : null,
141
+ !isGroup ? this.resolveUserIcdc(toUserJid(recipientJid)) : null
142
+ ]);
143
+ const messageWithIcdc = injectDeviceListMetadata(messageWithSecret, senderIcdc, recipientIcdc);
144
+ const plaintext = await writeRandomPadMax16(proto.Message.encode(messageWithIcdc).finish());
145
+ const type = resolveMessageTypeAttr(messageWithIcdc);
146
+ const edit = resolveEditAttr(messageWithIcdc, sendOptions.subtype) ?? undefined;
147
+ const mediatype = resolveEncMediaType(messageWithIcdc) ?? undefined;
148
+ const metaAttrs = resolveMetaAttrs(messageWithIcdc);
149
+ const metaNode = metaAttrs ? buildMetaNode(metaAttrs) : undefined;
150
+ if (isGroup) {
151
+ if (this.shouldUseGroupDirectPath(messageWithIcdc)) {
152
+ return this.publishGroupDirectMessage(recipientJid, messageWithIcdc, plaintext, type, sendOptions, {}, edit, mediatype, metaNode);
153
+ }
154
+ return this.publishGroupSenderKeyMessage(recipientJid, messageWithIcdc, plaintext, type, sendOptions, {}, edit, mediatype, metaNode);
127
155
  }
128
156
  const directRecipientJid = toUserJid(recipientJid);
129
- return this.publishDirectSignalMessageWithFanout(directRecipientJid, message, plaintext, type, options);
157
+ return this.publishDirectSignalMessageWithFanout(directRecipientJid, messageWithIcdc, plaintext, type, sendOptions, edit, mediatype, metaNode);
130
158
  }
131
159
  async syncSignalSession(jid, reasonIdentity = false) {
132
160
  const address = parseSignalAddressFromJid(jid);
133
161
  if (address.server === WA_DEFAULTS.GROUP_SERVER) {
134
162
  throw new Error('syncSignalSession supports only direct chats');
135
163
  }
136
- await this.ensureSignalSession(address, jid, undefined, reasonIdentity);
164
+ await this.sessionResolver.ensureSession(address, jid, undefined, reasonIdentity);
137
165
  }
138
166
  async sendReceipt(input) {
139
167
  await this.messageClient.sendReceipt(input);
140
168
  }
141
169
  async requestAppStateSyncKeys(keyIds) {
142
- const normalizedKeyIds = this.normalizeKeyIds(keyIds);
143
- if (normalizedKeyIds.length === 0) {
144
- return [];
145
- }
146
- const peerDeviceJids = await this.resolveOwnPeerDeviceJids();
147
- if (peerDeviceJids.length === 0) {
148
- this.logger.warn('app-state sync key request skipped: no peer devices available', {
149
- keys: normalizedKeyIds.length
150
- });
151
- return [];
152
- }
153
- const protocolMessage = {
154
- type: proto.Message.ProtocolMessage.Type.APP_STATE_SYNC_KEY_REQUEST,
155
- appStateSyncKeyRequest: {
156
- keyIds: normalizedKeyIds.map((keyId) => ({
157
- keyId
158
- }))
159
- }
160
- };
161
- await Promise.all(peerDeviceJids.map((deviceJid) => this.publishProtocolMessageToDevice(deviceJid, protocolMessage)));
162
- this.logger.info('app-state sync key request sent to peer devices', {
163
- devices: peerDeviceJids.length,
164
- keys: normalizedKeyIds.length,
165
- keyIds: normalizedKeyIds.map((keyId) => bytesToHex(keyId)).join(',')
166
- });
167
- return peerDeviceJids;
170
+ return this.appStateSyncKeyProtocol.requestKeys(keyIds);
168
171
  }
169
172
  async sendAppStateSyncKeyShare(toDeviceJid, keys, missingKeyIds = []) {
170
- const normalizedTo = normalizeDeviceJid(toDeviceJid);
171
- const dedupedKeysById = new Map();
172
- for (const key of keys) {
173
- dedupedKeysById.set(bytesToHex(key.keyId), key);
174
- }
175
- const dedupedKeys = [...dedupedKeysById.values()];
176
- const dedupedMissingKeyIds = this.normalizeKeyIds(missingKeyIds).filter((keyId) => !dedupedKeysById.has(bytesToHex(keyId)));
177
- const keyShareEntries = [
178
- ...dedupedKeys.map((key) => ({
179
- keyId: { keyId: key.keyId },
180
- keyData: {
181
- keyData: key.keyData,
182
- timestamp: key.timestamp,
183
- ...(key.fingerprint ? { fingerprint: key.fingerprint } : {})
184
- }
185
- })),
186
- ...dedupedMissingKeyIds.map((keyId) => ({
187
- keyId: { keyId }
188
- }))
189
- ];
190
- const protocolMessage = {
191
- type: proto.Message.ProtocolMessage.Type.APP_STATE_SYNC_KEY_SHARE,
192
- appStateSyncKeyShare: {
193
- keys: keyShareEntries
194
- }
195
- };
196
- await this.publishProtocolMessageToDevice(normalizedTo, protocolMessage);
197
- this.logger.info('app-state sync key share sent', {
198
- to: normalizedTo,
199
- keys: dedupedKeys.length,
200
- orphanKeys: dedupedMissingKeyIds.length
201
- });
173
+ await this.appStateSyncKeyProtocol.sendKeyShare(toDeviceJid, keys, missingKeyIds);
202
174
  }
203
175
  async mutateParticipantsCacheFromGroupEvent(event) {
204
- const groupJid = this.resolveGroupJidForParticipantCacheEvent(event);
205
- if (!groupJid) {
206
- return;
207
- }
208
- if (event.action === 'delete') {
209
- await this.participantsStore.deleteGroupParticipants(groupJid);
210
- return;
211
- }
212
- const participantUsers = this.extractParticipantUsersFromGroupEvent(event);
213
- if (event.action === 'create') {
214
- if (participantUsers.length === 0) {
215
- return;
216
- }
217
- await this.participantsStore.upsertGroupParticipants({
218
- groupJid,
219
- participants: participantUsers,
220
- updatedAtMs: Date.now()
221
- });
222
- return;
223
- }
224
- const cached = await this.participantsStore.getGroupParticipants(groupJid);
225
- if (!cached || cached.participants.length === 0) {
226
- return;
227
- }
228
- const cachedParticipants = this.sanitizeParticipantUsers(cached.participants);
229
- if (cachedParticipants.length === 0) {
230
- return;
231
- }
232
- if (event.action === 'add' ||
233
- event.action === 'promote' ||
234
- event.action === 'demote' ||
235
- event.action === 'linked_group_promote' ||
236
- event.action === 'linked_group_demote') {
237
- await this.mergeParticipantUsersIntoCache(groupJid, cachedParticipants, participantUsers);
238
- return;
239
- }
240
- if (event.action === 'remove') {
241
- await this.removeParticipantUsersFromCache(groupJid, cachedParticipants, participantUsers);
242
- return;
243
- }
244
- if (event.action === 'modify') {
245
- const authorUsers = event.authorJid
246
- ? this.sanitizeParticipantUsers([event.authorJid])
247
- : [];
248
- await this.replaceParticipantUsersInCache(groupJid, cachedParticipants, authorUsers, participantUsers);
249
- }
250
- }
251
- async publishProtocolMessageToDevice(deviceJid, protocolMessage) {
252
- const plaintext = await writeRandomPadMax16(proto.Message.encode({
253
- protocolMessage
254
- }).finish());
255
- await this.publishSignalMessage({
256
- to: deviceJid,
257
- plaintext,
258
- type: 'protocol',
259
- category: 'peer',
260
- pushPriority: 'high'
261
- });
262
- }
263
- async resolveOwnPeerDeviceJids() {
264
- const meJid = this.requireCurrentMeJid('resolveOwnPeerDeviceJids');
265
- const meUserJid = toUserJid(meJid);
266
- const meDevices = new Set();
267
- meDevices.add(normalizeDeviceJid(meJid));
268
- const meLid = this.getCurrentMeLid();
269
- if (meLid && meLid.includes('@')) {
270
- try {
271
- meDevices.add(normalizeDeviceJid(meLid));
272
- }
273
- catch (error) {
274
- this.logger.trace('ignoring malformed me lid jid while resolving peer devices', {
275
- meLid,
276
- message: toError(error).message
277
- });
278
- }
279
- }
280
- try {
281
- const synced = await this.signalDeviceSync.syncDeviceList([meUserJid]);
282
- const peerDevices = new Set();
283
- for (const entry of synced) {
284
- const sourceDevices = entry.deviceJids.length > 0 ? entry.deviceJids : [entry.jid];
285
- for (const deviceJid of sourceDevices) {
286
- try {
287
- const normalized = normalizeDeviceJid(deviceJid);
288
- if (meDevices.has(normalized)) {
289
- continue;
290
- }
291
- peerDevices.add(normalized);
292
- }
293
- catch (error) {
294
- this.logger.trace('ignoring malformed peer device jid while resolving app-state peers', {
295
- deviceJid,
296
- message: toError(error).message
297
- });
298
- }
299
- }
300
- }
301
- return [...peerDevices];
302
- }
303
- catch (error) {
304
- this.logger.warn('failed to resolve peer devices for app-state key request', {
305
- message: toError(error).message
306
- });
307
- return [];
308
- }
309
- }
310
- normalizeKeyIds(keyIds) {
311
- const deduped = new Map();
312
- for (const keyId of keyIds) {
313
- if (keyId.byteLength === 0) {
314
- continue;
315
- }
316
- const keyHex = bytesToHex(keyId);
317
- if (deduped.has(keyHex)) {
318
- continue;
319
- }
320
- deduped.set(keyHex, keyId);
321
- }
322
- return [...deduped.values()];
323
- }
324
- async publishWithRetryTracking(args, publish) {
325
- const nowMs = Date.now();
326
- const expiresAtMs = nowMs + this.retryTtlMs;
327
- const hintedMessageId = args.messageIdHint?.trim();
328
- const resolvedToJid = args.toJid ?? (args.replayPayload.mode === 'opaque_node' ? '' : args.replayPayload.to);
329
- let hintedPersisted = false;
330
- if (hintedMessageId) {
331
- hintedPersisted = await this.safeUpsertRetryOutboundRecord(this.createRetryOutboundRecord({
332
- messageId: hintedMessageId,
333
- toJid: resolvedToJid,
334
- participantJid: args.participantJid,
335
- recipientJid: args.recipientJid,
336
- messageType: args.messageType,
337
- replayPayload: args.replayPayload,
338
- createdAtMs: nowMs,
339
- updatedAtMs: nowMs,
340
- expiresAtMs
341
- }));
342
- }
343
- const result = await publish();
344
- if (hintedPersisted && hintedMessageId && result.id === hintedMessageId) {
345
- // Hint and final message id matched; avoid a second equivalent upsert on the hot path.
346
- return result;
347
- }
348
- const persistedNowMs = Date.now();
349
- await this.safeUpsertRetryOutboundRecord(this.createRetryOutboundRecord({
350
- messageId: result.id,
351
- toJid: resolvedToJid,
352
- participantJid: args.participantJid,
353
- recipientJid: args.recipientJid,
354
- messageType: args.messageType,
355
- replayPayload: args.replayPayload,
356
- createdAtMs: hintedMessageId ? nowMs : persistedNowMs,
357
- updatedAtMs: persistedNowMs,
358
- expiresAtMs: persistedNowMs + this.retryTtlMs
359
- }));
360
- return result;
361
- }
362
- createRetryOutboundRecord(input) {
363
- return {
364
- messageId: input.messageId,
365
- toJid: input.toJid,
366
- participantJid: input.participantJid,
367
- recipientJid: input.recipientJid,
368
- messageType: input.messageType,
369
- replayMode: input.replayPayload.mode,
370
- replayPayload: encodeRetryReplayPayload(input.replayPayload),
371
- state: 'pending',
372
- createdAtMs: input.createdAtMs,
373
- updatedAtMs: input.updatedAtMs,
374
- expiresAtMs: input.expiresAtMs
375
- };
376
- }
377
- async safeUpsertRetryOutboundRecord(record) {
378
- try {
379
- await this.retryStore.upsertOutboundMessage(record);
380
- }
381
- catch (error) {
382
- this.logger.warn('failed to persist retry outbound message record', {
383
- messageId: record.messageId,
384
- to: record.toJid,
385
- mode: record.replayMode,
386
- message: toError(error).message
387
- });
388
- return false;
389
- }
390
- try {
391
- await this.retryStore.cleanupExpired(Date.now());
392
- }
393
- catch (error) {
394
- this.logger.warn('failed to cleanup retry records after outbound persist', {
395
- message: toError(error).message
396
- });
397
- }
398
- return true;
176
+ await this.participantsCache.mutateFromGroupEvent(event);
399
177
  }
400
178
  shouldUseGroupDirectPath(message) {
401
179
  const protocolType = message.protocolMessage?.type;
@@ -405,41 +183,81 @@ export class WaMessageDispatchCoordinator {
405
183
  }
406
184
  return message.keepInChatMessage?.keepType === proto.KeepType.UNDO_KEEP_FOR_ALL;
407
185
  }
408
- async publishGroupDirectMessage(groupJid, plaintext, type, options, retryContext = {}) {
186
+ async publishGroupDirectMessage(groupJid, message, plaintext, type, options, retryContext = {}, edit, mediatype, metaNode) {
187
+ const sendOptions = await this.withResolvedMessageId(options);
409
188
  const meJid = this.requireCurrentMeJid('sendMessage');
410
189
  const participantUserJids = retryContext.forceRefreshParticipants
411
- ? await this.refreshGroupParticipantUsers(groupJid)
412
- : await this.resolveGroupParticipantUsers(groupJid);
190
+ ? await this.participantsCache.refreshParticipantUsers(groupJid)
191
+ : await this.participantsCache.resolveParticipantUsers(groupJid);
413
192
  const addressingMode = retryContext.forceAddressingMode ??
414
193
  this.resolveGroupAddressingMode(participantUserJids, groupJid);
415
194
  const senderForPhash = this.resolveSenderForAddressingMode(addressingMode, meJid);
416
- const fanoutDeviceJids = await this.resolveGroupParticipantDeviceJids(participantUserJids);
195
+ const fanoutDeviceJids = await this.fanoutResolver.resolveGroupParticipantDeviceJids(participantUserJids);
417
196
  if (fanoutDeviceJids.length === 0) {
418
197
  throw new Error('group direct send resolved no target devices');
419
198
  }
420
- await this.ensureSignalSessionsBatch(fanoutDeviceJids);
421
- const participants = await Promise.all(fanoutDeviceJids.map(async (targetJid) => {
422
- const address = parseSignalAddressFromJid(targetJid);
423
- await this.ensureSignalSession(address, targetJid);
424
- const encrypted = await this.signalProtocol.encryptMessage(address, plaintext);
425
- return {
426
- jid: targetJid,
427
- encType: encrypted.type,
428
- ciphertext: encrypted.ciphertext
199
+ const resolvedFanoutTargets = await this.sessionResolver.ensureSessionsBatch(fanoutDeviceJids);
200
+ const uniqueNormalizedFanoutJids = new Set();
201
+ for (let index = 0; index < fanoutDeviceJids.length; index += 1) {
202
+ uniqueNormalizedFanoutJids.add(normalizeDeviceJid(fanoutDeviceJids[index]));
203
+ }
204
+ if (resolvedFanoutTargets.length !== uniqueNormalizedFanoutJids.size) {
205
+ throw new Error('group direct send resolved incomplete signal sessions');
206
+ }
207
+ const participantEncryptRequests = new Array(resolvedFanoutTargets.length);
208
+ for (let index = 0; index < resolvedFanoutTargets.length; index += 1) {
209
+ const target = resolvedFanoutTargets[index];
210
+ participantEncryptRequests[index] = {
211
+ address: target.address,
212
+ plaintext
213
+ };
214
+ }
215
+ const encryptedParticipants = await this.signalProtocol.encryptMessagesBatch(participantEncryptRequests, resolvedFanoutTargets);
216
+ const participants = new Array(resolvedFanoutTargets.length);
217
+ for (let index = 0; index < resolvedFanoutTargets.length; index += 1) {
218
+ const target = resolvedFanoutTargets[index];
219
+ participants[index] = {
220
+ jid: target.jid,
221
+ encType: encryptedParticipants[index].type,
222
+ ciphertext: encryptedParticipants[index].ciphertext
429
223
  };
430
- }));
431
- const shouldAttachDeviceIdentity = participants.some((participant) => participant.encType === 'pkmsg');
432
- const localPhash = await computePhashV2([...fanoutDeviceJids, senderForPhash]);
433
- const messageNode = buildGroupDirectMessageNode({
224
+ }
225
+ let shouldAttachDeviceIdentity = false;
226
+ for (let index = 0; index < participants.length; index += 1) {
227
+ if (participants[index].encType === 'pkmsg') {
228
+ shouldAttachDeviceIdentity = true;
229
+ break;
230
+ }
231
+ }
232
+ const phashTargets = new Array(resolvedFanoutTargets.length + 1);
233
+ for (let index = 0; index < resolvedFanoutTargets.length; index += 1) {
234
+ phashTargets[index] = resolvedFanoutTargets[index].jid;
235
+ }
236
+ phashTargets[resolvedFanoutTargets.length] = senderForPhash;
237
+ const [localPhash, reportingArtifacts] = await Promise.all([
238
+ computePhashV2(phashTargets),
239
+ this.tryBuildReportingTokenArtifacts({
240
+ message,
241
+ stanzaId: sendOptions.id,
242
+ senderUserJid: toUserJid(senderForPhash),
243
+ remoteJid: groupJid,
244
+ context: 'group_direct'
245
+ })
246
+ ]);
247
+ const messageNode = buildDirectMessageFanoutNode({
434
248
  to: groupJid,
435
249
  type,
436
- id: options.id,
250
+ id: sendOptions.id,
251
+ edit,
437
252
  phash: localPhash,
438
253
  addressingMode,
439
254
  participants,
440
255
  deviceIdentity: shouldAttachDeviceIdentity
441
256
  ? this.getEncodedSignedDeviceIdentity()
442
- : undefined
257
+ : undefined,
258
+ reportingNode: reportingArtifacts?.node ?? undefined,
259
+ metaNode,
260
+ mediatype
443
261
  });
444
262
  const replayPayload = {
445
263
  mode: 'plaintext',
@@ -447,12 +265,13 @@ export class WaMessageDispatchCoordinator {
447
265
  type,
448
266
  plaintext
449
267
  };
450
- const result = await this.publishWithRetryTracking({
451
- messageIdHint: options.id ?? messageNode.attrs.id,
268
+ const result = await this.retryTracker.track({
269
+ messageIdHint: sendOptions.id ?? messageNode.attrs.id,
452
270
  toJid: groupJid,
453
- messageType: type,
454
- replayPayload
455
- }, async () => this.messageClient.publishNode(messageNode, options));
271
+ type,
272
+ replayPayload,
273
+ eligibleRequesterDeviceJids: undefined
274
+ }, async () => this.messageClient.publishNode(messageNode, sendOptions));
456
275
  const ackError = result.ack.error;
457
276
  const serverPhash = result.ack.phash;
458
277
  const serverAddressingMode = result.ack.addressingMode;
@@ -470,43 +289,67 @@ export class WaMessageDispatchCoordinator {
470
289
  serverAddressingMode,
471
290
  ackError
472
291
  });
473
- return this.publishGroupDirectMessage(groupJid, plaintext, type, {
474
- ...options,
292
+ return this.publishGroupDirectMessage(groupJid, message, plaintext, type, {
293
+ ...sendOptions,
475
294
  id: result.id
476
295
  }, {
477
296
  retried: true,
478
297
  forceRefreshParticipants: true,
479
298
  forceAddressingMode: serverAddressingMode
480
- });
299
+ }, edit, mediatype, metaNode);
481
300
  }
482
301
  return result;
483
302
  }
484
- async publishGroupSenderKeyMessage(groupJid, plaintext, type, options, retryContext = {}) {
303
+ async publishGroupSenderKeyMessage(groupJid, message, plaintext, type, options, retryContext = {}, edit, mediatype, metaNode) {
304
+ const sendOptions = await this.withResolvedMessageId(options);
485
305
  const meJid = this.requireCurrentMeJid('sendMessage');
486
306
  const participantUserJids = retryContext.forceRefreshParticipants
487
- ? await this.refreshGroupParticipantUsers(groupJid)
488
- : await this.resolveGroupParticipantUsers(groupJid);
307
+ ? await this.participantsCache.refreshParticipantUsers(groupJid)
308
+ : await this.participantsCache.resolveParticipantUsers(groupJid);
489
309
  const addressingMode = retryContext.forceAddressingMode ??
490
310
  this.resolveGroupAddressingMode(participantUserJids, groupJid);
491
311
  const senderJid = this.resolveSenderForAddressingMode(addressingMode, meJid);
492
312
  const sender = parseSignalAddressFromJid(senderJid);
493
- const senderKeyDistributionMessage = await this.senderKeyManager.createSenderKeyDistributionMessage(groupJid, sender);
494
- const groupCiphertext = await this.senderKeyManager.encryptGroupMessage(groupJid, sender, plaintext);
495
- const distributionData = await this.encryptGroupDistributionParticipants(groupJid, sender, senderKeyDistributionMessage, participantUserJids);
313
+ const { distributionMessage: senderKeyDistributionMessage, ciphertext: groupCiphertext, keyId: senderKeyId } = await this.senderKeyManager.prepareGroupEncryption(groupJid, sender, plaintext);
314
+ const distributionData = await this.distributionDedup.run(`dist:${groupJid}:${senderKeyId}`, () => this.encryptGroupDistributionParticipants(groupJid, senderKeyId, senderKeyDistributionMessage, participantUserJids));
496
315
  const { fanoutDeviceJids, distributionParticipants } = distributionData;
497
- const shouldAttachDeviceIdentity = distributionParticipants.some((participant) => participant.encType === 'pkmsg');
498
- const localPhash = await computePhashV2([...fanoutDeviceJids, senderJid]);
316
+ let shouldAttachDeviceIdentity = false;
317
+ for (let index = 0; index < distributionParticipants.length; index += 1) {
318
+ if (distributionParticipants[index].encType === 'pkmsg') {
319
+ shouldAttachDeviceIdentity = true;
320
+ break;
321
+ }
322
+ }
323
+ const phashTargets = new Array(fanoutDeviceJids.length + 1);
324
+ for (let index = 0; index < fanoutDeviceJids.length; index += 1) {
325
+ phashTargets[index] = fanoutDeviceJids[index];
326
+ }
327
+ phashTargets[fanoutDeviceJids.length] = senderJid;
328
+ const [localPhash, reportingArtifacts] = await Promise.all([
329
+ computePhashV2(phashTargets),
330
+ this.tryBuildReportingTokenArtifacts({
331
+ message,
332
+ stanzaId: sendOptions.id,
333
+ senderUserJid: toUserJid(senderJid),
334
+ remoteJid: groupJid,
335
+ context: 'group_sender_key'
336
+ })
337
+ ]);
499
338
  const messageNode = buildGroupSenderKeyMessageNode({
500
339
  to: groupJid,
501
340
  type,
502
- id: options.id,
341
+ id: sendOptions.id,
342
+ edit,
503
343
  phash: localPhash,
504
344
  addressingMode,
505
345
  groupCiphertext: groupCiphertext.ciphertext,
506
346
  participants: distributionParticipants,
507
347
  deviceIdentity: shouldAttachDeviceIdentity
508
348
  ? this.getEncodedSignedDeviceIdentity()
509
- : undefined
349
+ : undefined,
350
+ reportingNode: reportingArtifacts?.node ?? undefined,
351
+ metaNode,
352
+ mediatype
510
353
  });
511
354
  const replayPayload = {
512
355
  mode: 'plaintext',
@@ -514,15 +357,19 @@ export class WaMessageDispatchCoordinator {
514
357
  type,
515
358
  plaintext
516
359
  };
517
- const result = await this.publishWithRetryTracking({
518
- messageIdHint: options.id ?? messageNode.attrs.id,
360
+ const result = await this.retryTracker.track({
361
+ messageIdHint: sendOptions.id ?? messageNode.attrs.id,
519
362
  toJid: groupJid,
520
- messageType: type,
521
- replayPayload
522
- }, async () => this.messageClient.publishNode(messageNode, options));
523
- const distributedAddresses = distributionParticipants.map((participant) => participant.address);
363
+ type,
364
+ replayPayload,
365
+ eligibleRequesterDeviceJids: undefined
366
+ }, async () => this.messageClient.publishNode(messageNode, sendOptions));
367
+ const distributedAddresses = new Array(distributionParticipants.length);
368
+ for (let index = 0; index < distributionParticipants.length; index += 1) {
369
+ distributedAddresses[index] = distributionParticipants[index].address;
370
+ }
524
371
  try {
525
- await this.senderKeyManager.markSenderKeyDistributed(groupJid, sender, distributedAddresses);
372
+ await this.senderKeyManager.markSenderKeyDistributed(groupJid, senderKeyId, distributedAddresses);
526
373
  }
527
374
  catch (error) {
528
375
  this.logger.warn('failed to mark sender key distribution targets', {
@@ -548,150 +395,20 @@ export class WaMessageDispatchCoordinator {
548
395
  serverAddressingMode,
549
396
  ackError
550
397
  });
551
- return this.publishGroupSenderKeyMessage(groupJid, plaintext, type, {
552
- ...options,
398
+ return this.publishGroupSenderKeyMessage(groupJid, message, plaintext, type, {
399
+ ...sendOptions,
553
400
  id: result.id
554
401
  }, {
555
402
  retried: true,
556
403
  forceRefreshParticipants: true,
557
404
  forceAddressingMode: serverAddressingMode
558
- });
405
+ }, edit, mediatype, metaNode);
559
406
  }
560
407
  return result;
561
408
  }
562
- async resolveGroupParticipantUsers(groupJid) {
563
- const cached = await this.participantsStore.getGroupParticipants(groupJid);
564
- if (cached && cached.participants.length > 0) {
565
- return this.sanitizeParticipantUsers(cached.participants);
566
- }
567
- return this.refreshGroupParticipantUsers(groupJid);
568
- }
569
- resolveGroupJidForParticipantCacheEvent(event) {
570
- if (event.action === 'linked_group_promote' || event.action === 'linked_group_demote') {
571
- return event.contextGroupJid ?? event.groupJid ?? null;
572
- }
573
- return event.groupJid ?? null;
574
- }
575
- extractParticipantUsersFromGroupEvent(event) {
576
- const candidates = [];
577
- for (const participant of event.participants ?? []) {
578
- if (participant.jid) {
579
- candidates.push(participant.jid);
580
- }
581
- if (participant.lidJid) {
582
- candidates.push(participant.lidJid);
583
- }
584
- if (participant.phoneJid) {
585
- candidates.push(participant.phoneJid);
586
- }
587
- }
588
- return this.sanitizeParticipantUsers(candidates);
589
- }
590
- async mergeParticipantUsersIntoCache(groupJid, cachedParticipants, participantsToAdd) {
591
- if (participantsToAdd.length === 0) {
592
- return;
593
- }
594
- const nextParticipants = [...cachedParticipants];
595
- const existing = new Set(cachedParticipants);
596
- for (const participant of participantsToAdd) {
597
- if (existing.has(participant)) {
598
- continue;
599
- }
600
- existing.add(participant);
601
- nextParticipants.push(participant);
602
- }
603
- if (nextParticipants.length === cachedParticipants.length) {
604
- return;
605
- }
606
- await this.participantsStore.upsertGroupParticipants({
607
- groupJid,
608
- participants: nextParticipants,
609
- updatedAtMs: Date.now()
610
- });
611
- }
612
- async removeParticipantUsersFromCache(groupJid, cachedParticipants, participantsToRemove) {
613
- if (participantsToRemove.length === 0) {
614
- return;
615
- }
616
- const removed = new Set(participantsToRemove);
617
- const nextParticipants = cachedParticipants.filter((participant) => !removed.has(participant));
618
- if (nextParticipants.length === cachedParticipants.length) {
619
- return;
620
- }
621
- if (nextParticipants.length === 0) {
622
- await this.participantsStore.deleteGroupParticipants(groupJid);
623
- return;
624
- }
625
- await this.participantsStore.upsertGroupParticipants({
626
- groupJid,
627
- participants: nextParticipants,
628
- updatedAtMs: Date.now()
629
- });
630
- }
631
- async replaceParticipantUsersInCache(groupJid, cachedParticipants, participantsToReplace, replacementParticipants) {
632
- const toReplace = new Set(participantsToReplace);
633
- const nextParticipants = cachedParticipants.filter((participant) => !toReplace.has(participant));
634
- const existing = new Set(nextParticipants);
635
- for (const participant of replacementParticipants) {
636
- if (existing.has(participant)) {
637
- continue;
638
- }
639
- existing.add(participant);
640
- nextParticipants.push(participant);
641
- }
642
- if (this.areParticipantListsEqual(cachedParticipants, nextParticipants)) {
643
- return;
644
- }
645
- if (nextParticipants.length === 0) {
646
- await this.participantsStore.deleteGroupParticipants(groupJid);
647
- return;
648
- }
649
- await this.participantsStore.upsertGroupParticipants({
650
- groupJid,
651
- participants: nextParticipants,
652
- updatedAtMs: Date.now()
653
- });
654
- }
655
- areParticipantListsEqual(left, right) {
656
- if (left.length !== right.length) {
657
- return false;
658
- }
659
- for (let index = 0; index < left.length; index += 1) {
660
- if (left[index] !== right[index]) {
661
- return false;
662
- }
663
- }
664
- return true;
665
- }
666
- async refreshGroupParticipantUsers(groupJid) {
667
- const queried = await this.queryGroupParticipantJids(groupJid);
668
- const participants = this.sanitizeParticipantUsers(queried);
669
- await this.participantsStore.upsertGroupParticipants({
670
- groupJid,
671
- participants,
672
- updatedAtMs: Date.now()
673
- });
674
- return participants;
675
- }
676
- sanitizeParticipantUsers(participants) {
677
- const deduped = new Set();
678
- for (const participant of participants) {
679
- if (!participant || !participant.includes('@'))
680
- continue;
681
- try {
682
- deduped.add(toUserJid(participant));
683
- }
684
- catch (error) {
685
- this.logger.trace('ignoring malformed participant jid', {
686
- participant,
687
- message: toError(error).message
688
- });
689
- }
690
- }
691
- return [...deduped];
692
- }
693
409
  resolveGroupAddressingMode(participantUserJids, groupJid) {
694
- for (const participantJid of participantUserJids) {
410
+ for (let index = 0; index < participantUserJids.length; index += 1) {
411
+ const participantJid = participantUserJids[index];
695
412
  try {
696
413
  if (splitJid(participantJid).server === 'lid') {
697
414
  return 'lid';
@@ -724,115 +441,131 @@ export class WaMessageDispatchCoordinator {
724
441
  }
725
442
  return normalizeDeviceJid(meJid);
726
443
  }
727
- async encryptGroupDistributionParticipants(groupJid, sender, senderKeyDistributionMessage, participantUserJids) {
444
+ async encryptGroupDistributionParticipants(groupJid, senderKeyId, senderKeyDistributionMessage, participantUserJids) {
728
445
  const distributionPayload = await writeRandomPadMax16(proto.Message.encode({
729
446
  senderKeyDistributionMessage
730
447
  }).finish());
731
- const fanoutDeviceJids = await this.resolveGroupParticipantDeviceJids(participantUserJids);
448
+ const fanoutDeviceJids = await this.fanoutResolver.resolveGroupParticipantDeviceJids(participantUserJids);
732
449
  if (fanoutDeviceJids.length === 0) {
733
450
  return {
734
451
  fanoutDeviceJids,
735
452
  distributionParticipants: []
736
453
  };
737
454
  }
738
- const fanoutTargets = fanoutDeviceJids.map((jid) => ({
739
- jid,
740
- address: parseSignalAddressFromJid(jid)
741
- }));
742
- const pendingAddresses = await this.senderKeyManager.filterParticipantsNeedingDistribution(groupJid, sender, fanoutTargets.map((target) => target.address));
455
+ const fanoutTargetsByAddressKey = new Map();
456
+ const fanoutAddresses = new Array(fanoutDeviceJids.length);
457
+ for (let index = 0; index < fanoutDeviceJids.length; index += 1) {
458
+ const jid = fanoutDeviceJids[index];
459
+ const address = parseSignalAddressFromJid(jid);
460
+ fanoutAddresses[index] = address;
461
+ fanoutTargetsByAddressKey.set(signalAddressKey(address), { jid, address });
462
+ }
463
+ const pendingAddresses = await this.senderKeyManager.filterParticipantsNeedingDistribution(groupJid, senderKeyId, fanoutAddresses);
743
464
  if (pendingAddresses.length === 0) {
744
465
  return {
745
466
  fanoutDeviceJids,
746
467
  distributionParticipants: []
747
468
  };
748
469
  }
749
- const pendingAddressKeys = new Set(pendingAddresses.map(signalAddressKey));
750
- const pendingTargets = fanoutTargets.filter((target) => pendingAddressKeys.has(signalAddressKey(target.address)));
470
+ const pendingAddressKeys = new Set();
471
+ const pendingTargets = [];
472
+ for (let index = 0; index < pendingAddresses.length; index += 1) {
473
+ const key = signalAddressKey(pendingAddresses[index]);
474
+ if (pendingAddressKeys.has(key)) {
475
+ continue;
476
+ }
477
+ pendingAddressKeys.add(key);
478
+ const target = fanoutTargetsByAddressKey.get(key);
479
+ if (target) {
480
+ pendingTargets.push(target);
481
+ }
482
+ }
751
483
  if (pendingTargets.length === 0) {
752
484
  return {
753
485
  fanoutDeviceJids,
754
486
  distributionParticipants: []
755
487
  };
756
488
  }
757
- await this.ensureSignalSessionsBatch(pendingTargets.map((target) => target.jid));
758
- const distributionParticipants = await Promise.all(pendingTargets.map(async (target) => {
759
- await this.ensureSignalSession(target.address, target.jid);
760
- const encrypted = await this.signalProtocol.encryptMessage(target.address, distributionPayload);
489
+ const pendingTargetJids = new Array(pendingTargets.length);
490
+ for (let index = 0; index < pendingTargets.length; index += 1) {
491
+ pendingTargetJids[index] = pendingTargets[index].jid;
492
+ }
493
+ let availableTargets = [];
494
+ let prefetchedAvailableTargets;
495
+ try {
496
+ const resolvedTargets = await this.sessionResolver.ensureSessionsBatch(pendingTargetJids);
497
+ availableTargets = resolvedTargets;
498
+ prefetchedAvailableTargets = resolvedTargets;
499
+ }
500
+ catch (error) {
501
+ const normalized = toError(error);
502
+ if (normalized.message === 'identity mismatch') {
503
+ throw normalized;
504
+ }
505
+ this.logger.warn('group sender-key distribution session sync failed, continuing with available sessions', {
506
+ groupJid,
507
+ requested: pendingTargetJids.length,
508
+ message: normalized.message
509
+ });
510
+ const pendingTargetAddresses = new Array(pendingTargets.length);
511
+ for (let index = 0; index < pendingTargets.length; index += 1) {
512
+ pendingTargetAddresses[index] = pendingTargets[index].address;
513
+ }
514
+ const hasPendingSessions = await this.signalStore.hasSessions(pendingTargetAddresses);
515
+ const nextAvailableTargets = [];
516
+ for (let index = 0; index < pendingTargets.length; index += 1) {
517
+ if (hasPendingSessions[index]) {
518
+ nextAvailableTargets.push(pendingTargets[index]);
519
+ }
520
+ }
521
+ availableTargets = nextAvailableTargets;
522
+ }
523
+ if (availableTargets.length === 0) {
761
524
  return {
525
+ fanoutDeviceJids,
526
+ distributionParticipants: []
527
+ };
528
+ }
529
+ const distributionEncryptRequests = new Array(availableTargets.length);
530
+ for (let index = 0; index < availableTargets.length; index += 1) {
531
+ const target = availableTargets[index];
532
+ distributionEncryptRequests[index] = {
533
+ address: target.address,
534
+ plaintext: distributionPayload
535
+ };
536
+ }
537
+ const encryptedDistributionParticipants = await this.signalProtocol.encryptMessagesBatch(distributionEncryptRequests, prefetchedAvailableTargets);
538
+ const distributionParticipants = new Array(availableTargets.length);
539
+ for (let index = 0; index < availableTargets.length; index += 1) {
540
+ const target = availableTargets[index];
541
+ distributionParticipants[index] = {
762
542
  jid: target.jid,
763
543
  address: target.address,
764
- encType: encrypted.type,
765
- ciphertext: encrypted.ciphertext
544
+ encType: encryptedDistributionParticipants[index].type,
545
+ ciphertext: encryptedDistributionParticipants[index].ciphertext
766
546
  };
767
- }));
547
+ }
768
548
  return {
769
549
  fanoutDeviceJids,
770
550
  distributionParticipants
771
551
  };
772
552
  }
773
- async resolveGroupParticipantDeviceJids(participantUserJids) {
774
- const meDeviceJids = new Set();
775
- const meJid = this.getCurrentMeJid();
776
- if (meJid) {
777
- try {
778
- meDeviceJids.add(normalizeDeviceJid(meJid));
779
- }
780
- catch (error) {
781
- this.logger.trace('ignoring malformed me jid', {
782
- meJid,
783
- message: toError(error).message
784
- });
785
- }
786
- }
787
- const meLid = this.getCurrentMeLid();
788
- if (meLid && meLid.includes('@')) {
789
- try {
790
- meDeviceJids.add(normalizeDeviceJid(meLid));
791
- }
792
- catch (error) {
793
- this.logger.trace('ignoring malformed me lid jid', {
794
- meLid,
795
- message: toError(error).message
796
- });
797
- }
798
- }
799
- const candidateUsers = [...new Set(participantUserJids)];
800
- if (candidateUsers.length === 0) {
801
- return [];
802
- }
803
- try {
804
- const synced = await this.signalDeviceSync.syncDeviceList(candidateUsers);
805
- const fanout = new Set();
806
- for (const entry of synced) {
807
- if (entry.deviceJids.length === 0) {
808
- const normalizedEntryJid = normalizeDeviceJid(entry.jid);
809
- if (meDeviceJids.has(normalizedEntryJid))
810
- continue;
811
- fanout.add(normalizedEntryJid);
812
- continue;
813
- }
814
- for (const deviceJid of entry.deviceJids) {
815
- const normalizedDeviceJid = normalizeDeviceJid(deviceJid);
816
- if (meDeviceJids.has(normalizedDeviceJid))
817
- continue;
818
- fanout.add(normalizedDeviceJid);
819
- }
820
- }
821
- return [...fanout];
822
- }
823
- catch (error) {
824
- this.logger.warn('group participant device sync failed, falling back to participant user jids', {
825
- participants: candidateUsers.length,
826
- message: toError(error).message
827
- });
828
- return [...new Set(candidateUsers.map((jid) => normalizeDeviceJid(jid)))].filter((jid) => !meDeviceJids.has(jid));
829
- }
830
- }
831
- async publishDirectSignalMessageWithFanout(recipientJid, message, plaintext, type, options) {
553
+ async publishDirectSignalMessageWithFanout(recipientJid, message, plaintext, type, options, edit, mediatype, metaNode) {
554
+ const sendOptions = await this.withResolvedMessageId(options);
832
555
  const meJid = this.requireCurrentMeJid('sendMessage');
833
556
  const meLid = this.getCurrentMeLid();
834
- const selfDeviceJidForRecipient = this.resolveSelfDeviceJidForRecipient(recipientJid, meJid, meLid);
835
- const deviceJids = await this.resolveDirectFanoutDeviceJids(recipientJid, selfDeviceJidForRecipient);
557
+ const selfDeviceJidForRecipient = this.fanoutResolver.resolveSelfDeviceJidForRecipient(recipientJid, meJid, meLid);
558
+ const deviceJids = await this.fanoutResolver.resolveDirectFanoutDeviceJids(recipientJid, selfDeviceJidForRecipient);
559
+ const targets = new Array(deviceJids.length);
560
+ for (let index = 0; index < deviceJids.length; index += 1) {
561
+ const jid = deviceJids[index];
562
+ const parsed = parseJidFull(jid);
563
+ targets[index] = {
564
+ jid,
565
+ normalizedJid: parsed.normalizedJid,
566
+ userJid: parsed.userJid
567
+ };
568
+ }
836
569
  const recipientUserJid = toUserJid(recipientJid);
837
570
  const meUserJid = toUserJid(selfDeviceJidForRecipient);
838
571
  this.logger.debug('wa client publish signal fanout', {
@@ -841,44 +574,115 @@ export class WaMessageDispatchCoordinator {
841
574
  type
842
575
  });
843
576
  const expectedIdentityByJid = new Map();
844
- if (options.expectedIdentity) {
845
- for (let index = 0; index < deviceJids.length; index += 1) {
846
- const targetJid = deviceJids[index];
847
- if (toUserJid(targetJid) === recipientUserJid) {
848
- expectedIdentityByJid.set(normalizeDeviceJid(targetJid), options.expectedIdentity);
577
+ if (sendOptions.expectedIdentity) {
578
+ for (let index = 0; index < targets.length; index += 1) {
579
+ const target = targets[index];
580
+ if (target.userJid === recipientUserJid) {
581
+ expectedIdentityByJid.set(target.normalizedJid, sendOptions.expectedIdentity);
849
582
  }
850
583
  }
851
584
  }
852
- await this.ensureSignalSessionsBatch(deviceJids, expectedIdentityByJid);
853
- const hasSelfDeviceFanout = deviceJids.some((targetJid) => toUserJid(targetJid) === meUserJid);
585
+ const resolvedFanoutTargets = await this.sessionResolver.ensureSessionsBatch(deviceJids, expectedIdentityByJid);
586
+ const resolvedFanoutTargetsByJid = new Map();
587
+ for (let index = 0; index < resolvedFanoutTargets.length; index += 1) {
588
+ const target = resolvedFanoutTargets[index];
589
+ resolvedFanoutTargetsByJid.set(normalizeDeviceJid(target.jid), target);
590
+ }
591
+ for (let index = 0; index < targets.length; index += 1) {
592
+ if (!resolvedFanoutTargetsByJid.has(targets[index].normalizedJid)) {
593
+ throw new Error('direct fanout missing signal sessions for one or more targets');
594
+ }
595
+ }
596
+ let hasSelfDeviceFanout = false;
597
+ for (let index = 0; index < targets.length; index += 1) {
598
+ if (targets[index].userJid === meUserJid) {
599
+ hasSelfDeviceFanout = true;
600
+ break;
601
+ }
602
+ }
854
603
  const selfDevicePlaintext = hasSelfDeviceFanout
855
604
  ? await writeRandomPadMax16(proto.Message.encode(wrapDeviceSentMessage(message, recipientUserJid)).finish())
856
605
  : null;
857
- const participants = await Promise.all(deviceJids.map(async (targetJid) => {
858
- const address = parseSignalAddressFromJid(targetJid);
859
- const targetUserJid = toUserJid(targetJid);
860
- const expectedIdentity = targetUserJid === recipientUserJid ? options.expectedIdentity : undefined;
861
- const plaintextForTarget = selfDevicePlaintext && targetUserJid === meUserJid
862
- ? selfDevicePlaintext
863
- : plaintext;
864
- await this.ensureSignalSession(address, targetJid, expectedIdentity);
865
- const encrypted = await this.signalProtocol.encryptMessage(address, plaintextForTarget, expectedIdentity);
866
- return {
867
- jid: targetJid,
868
- encType: encrypted.type,
869
- ciphertext: encrypted.ciphertext
606
+ const participantRequests = new Array(targets.length);
607
+ for (let index = 0; index < targets.length; index += 1) {
608
+ const target = targets[index];
609
+ const resolvedTarget = resolvedFanoutTargetsByJid.get(target.normalizedJid);
610
+ if (!resolvedTarget) {
611
+ throw new Error('direct fanout missing signal session for target');
612
+ }
613
+ participantRequests[index] = {
614
+ target,
615
+ address: resolvedTarget.address,
616
+ session: resolvedTarget.session,
617
+ expectedIdentity: target.userJid === recipientUserJid ? sendOptions.expectedIdentity : undefined,
618
+ plaintext: selfDevicePlaintext && target.userJid === meUserJid
619
+ ? selfDevicePlaintext
620
+ : plaintext
621
+ };
622
+ }
623
+ const encryptRequests = new Array(participantRequests.length);
624
+ const prefetchedSessions = new Array(participantRequests.length);
625
+ for (let index = 0; index < participantRequests.length; index += 1) {
626
+ const request = participantRequests[index];
627
+ encryptRequests[index] = {
628
+ address: request.address,
629
+ plaintext: request.plaintext,
630
+ expectedIdentity: request.expectedIdentity
631
+ };
632
+ prefetchedSessions[index] = {
633
+ address: request.address,
634
+ session: request.session
870
635
  };
871
- }));
872
- const shouldAttachDeviceIdentity = participants.some((participant) => participant.encType === 'pkmsg');
636
+ }
637
+ const encryptedParticipants = await this.signalProtocol.encryptMessagesBatch(encryptRequests, prefetchedSessions);
638
+ const participants = new Array(participantRequests.length);
639
+ for (let index = 0; index < participantRequests.length; index += 1) {
640
+ const request = participantRequests[index];
641
+ participants[index] = {
642
+ jid: request.target.jid,
643
+ encType: encryptedParticipants[index].type,
644
+ ciphertext: encryptedParticipants[index].ciphertext
645
+ };
646
+ }
647
+ let shouldAttachDeviceIdentity = false;
648
+ for (let index = 0; index < participants.length; index += 1) {
649
+ if (participants[index].encType === 'pkmsg') {
650
+ shouldAttachDeviceIdentity = true;
651
+ break;
652
+ }
653
+ }
873
654
  const deviceIdentity = shouldAttachDeviceIdentity
874
655
  ? this.getEncodedSignedDeviceIdentity()
875
656
  : undefined;
657
+ const reportingArtifacts = await this.tryBuildReportingTokenArtifacts({
658
+ message,
659
+ stanzaId: sendOptions.id,
660
+ senderUserJid: meUserJid,
661
+ remoteJid: recipientUserJid,
662
+ context: 'direct_fanout'
663
+ });
664
+ let privacyTokenNode;
665
+ try {
666
+ privacyTokenNode =
667
+ (await this.privacyTokenDedup.run(`pt:${recipientUserJid}`, () => this.resolvePrivacyTokenNode(recipientUserJid))) ?? undefined;
668
+ }
669
+ catch (error) {
670
+ this.logger.warn('privacy token resolution failed', {
671
+ to: recipientUserJid,
672
+ message: toError(error).message
673
+ });
674
+ }
876
675
  const messageNode = buildDirectMessageFanoutNode({
877
676
  to: recipientJid,
878
677
  type,
879
- id: options.id,
678
+ id: sendOptions.id,
679
+ edit,
880
680
  participants,
881
- deviceIdentity
681
+ deviceIdentity,
682
+ reportingNode: reportingArtifacts?.node ?? undefined,
683
+ privacyTokenNode,
684
+ metaNode,
685
+ mediatype
882
686
  });
883
687
  const replayPayload = {
884
688
  mode: 'plaintext',
@@ -886,130 +690,75 @@ export class WaMessageDispatchCoordinator {
886
690
  type,
887
691
  plaintext
888
692
  };
889
- return this.publishWithRetryTracking({
890
- messageIdHint: options.id ?? messageNode.attrs.id,
693
+ const result = await this.retryTracker.track({
694
+ messageIdHint: sendOptions.id ?? messageNode.attrs.id,
891
695
  toJid: recipientJid,
892
- messageType: type,
893
- replayPayload
894
- }, async () => this.messageClient.publishNode(messageNode, options));
696
+ type,
697
+ replayPayload,
698
+ eligibleRequesterDeviceJids: deviceJids
699
+ }, async () => this.messageClient.publishNode(messageNode, sendOptions));
700
+ this.onDirectMessageSent(recipientUserJid);
701
+ return result;
895
702
  }
896
- async ensureSignalSessionsBatch(targetJids, expectedIdentityByJid = new Map()) {
897
- const normalizedTargetJids = [...new Set(targetJids.map((jid) => normalizeDeviceJid(jid)))];
898
- if (normalizedTargetJids.length === 0) {
899
- return;
900
- }
901
- const normalizedTargets = normalizedTargetJids.map((jid) => ({
902
- jid,
903
- address: parseSignalAddressFromJid(jid)
904
- }));
905
- const hasSessions = await this.signalProtocol.hasSessions(normalizedTargets.map((target) => target.address));
906
- const missingTargets = normalizedTargets.filter((_, index) => !hasSessions[index]);
907
- if (missingTargets.length === 0) {
908
- return;
703
+ async withResolvedMessageId(options) {
704
+ const normalizedId = options.id?.trim();
705
+ if (normalizedId) {
706
+ if (normalizedId === options.id) {
707
+ return options;
708
+ }
709
+ return {
710
+ ...options,
711
+ id: normalizedId
712
+ };
909
713
  }
714
+ return {
715
+ ...options,
716
+ id: await this.generateOutgoingMessageId()
717
+ };
718
+ }
719
+ async generateOutgoingMessageId() {
910
720
  try {
911
- const batchResults = await this.signalSessionSync.fetchKeyBundles(missingTargets.map((target) => ({ jid: target.jid })));
912
- const resultByJid = new Map(batchResults.map((result) => [normalizeDeviceJid(result.jid), result]));
913
- const fallbackJids = [];
914
- const establishPromises = [];
915
- for (let index = 0; index < missingTargets.length; index += 1) {
916
- const target = missingTargets[index];
917
- const result = resultByJid.get(target.jid);
918
- if (!result || !('bundle' in result)) {
919
- fallbackJids.push(target.jid);
920
- continue;
921
- }
922
- const expectedIdentity = expectedIdentityByJid.get(target.jid);
923
- const remoteIdentity = toSerializedPubKey(result.bundle.identity);
924
- if (expectedIdentity &&
925
- !uint8Equal(remoteIdentity, toSerializedPubKey(expectedIdentity))) {
926
- throw new Error('identity mismatch');
927
- }
928
- establishPromises.push(this.signalProtocol
929
- .establishOutgoingSession(target.address, result.bundle)
930
- .then(() => {
931
- this.logger.debug('signal session synchronized from batch key fetch', {
932
- jid: target.jid,
933
- regId: result.bundle.regId,
934
- hasOneTimeKey: result.bundle.oneTimeKey !== undefined
935
- });
936
- }));
937
- }
938
- await Promise.all(establishPromises);
939
- if (fallbackJids.length === 0) {
940
- return;
941
- }
942
- this.logger.warn('signal batch key fetch returned partial errors, falling back to single requests', {
943
- requested: missingTargets.length,
944
- fallbackTargets: fallbackJids.length
945
- });
946
- for (let index = 0; index < fallbackJids.length; index += 1) {
947
- const jid = fallbackJids[index];
948
- const address = parseSignalAddressFromJid(jid);
949
- await this.ensureSignalSession(address, jid, expectedIdentityByJid.get(jid));
950
- }
721
+ const meUserJid = toUserJid(this.requireCurrentMeJid('sendMessage'));
722
+ const timestampSeconds = Math.floor(Date.now() / 1000);
723
+ const timestampBytes = new Uint8Array(8);
724
+ new DataView(timestampBytes.buffer, timestampBytes.byteOffset, timestampBytes.byteLength).setBigUint64(0, BigInt(timestampSeconds), false);
725
+ const entropy = concatBytes([
726
+ timestampBytes,
727
+ TEXT_ENCODER.encode(meUserJid),
728
+ await randomBytesAsync(8)
729
+ ]);
730
+ const digest = await sha256(entropy);
731
+ return `3EB0${bytesToHex(digest.subarray(0, 9)).toUpperCase()}`;
951
732
  }
952
733
  catch (error) {
953
- const normalized = toError(error);
954
- if (normalized.message === 'identity mismatch') {
955
- throw normalized;
956
- }
957
- this.logger.warn('signal batch key fetch failed, falling back to single requests', {
958
- requested: missingTargets.length,
959
- message: normalized.message
734
+ this.logger.warn('failed to generate sha256 message id, falling back to random id', {
735
+ message: toError(error).message
960
736
  });
961
- for (let index = 0; index < missingTargets.length; index += 1) {
962
- const target = missingTargets[index];
963
- await this.ensureSignalSession(target.address, target.jid, expectedIdentityByJid.get(target.jid));
964
- }
737
+ return `3EB0${bytesToHex(await randomBytesAsync(8)).toUpperCase()}`;
965
738
  }
966
739
  }
967
- async resolveDirectFanoutDeviceJids(recipientJid, selfDeviceJidForRecipient) {
968
- const recipientUserJid = toUserJid(recipientJid);
969
- const meUserJid = toUserJid(selfDeviceJidForRecipient);
970
- const targets = recipientUserJid === meUserJid ? [recipientUserJid] : [recipientUserJid, meUserJid];
740
+ async tryBuildReportingTokenArtifacts(input) {
741
+ if (!input.stanzaId) {
742
+ return null;
743
+ }
971
744
  try {
972
- const synced = await this.signalDeviceSync.syncDeviceList(targets);
973
- const byUser = new Map(synced.map((entry) => [toUserJid(entry.jid), entry.deviceJids]));
974
- const fanout = new Set();
975
- const recipientDevices = byUser.get(recipientUserJid) ?? [];
976
- if (recipientDevices.length === 0) {
977
- fanout.add(recipientUserJid);
978
- }
979
- else {
980
- for (let index = 0; index < recipientDevices.length; index += 1) {
981
- fanout.add(recipientDevices[index]);
982
- }
983
- }
984
- const meDevices = byUser.get(meUserJid) ?? [];
985
- const normalizedMeJid = normalizeDeviceJid(selfDeviceJidForRecipient);
986
- for (let index = 0; index < meDevices.length; index += 1) {
987
- const deviceJid = meDevices[index];
988
- if (normalizeDeviceJid(deviceJid) === normalizedMeJid) {
989
- continue;
990
- }
991
- fanout.add(deviceJid);
992
- }
993
- return [...fanout];
745
+ return await buildReportingTokenArtifacts({
746
+ message: input.message,
747
+ stanzaId: input.stanzaId,
748
+ senderUserJid: input.senderUserJid,
749
+ remoteJid: input.remoteJid
750
+ });
994
751
  }
995
752
  catch (error) {
996
- const message = error instanceof Error ? error.message : String(error);
997
- this.logger.warn('signal device fanout sync failed, falling back to direct recipient', {
998
- to: recipientJid,
999
- message
753
+ this.logger.warn('failed to generate reporting token', {
754
+ context: input.context,
755
+ id: input.stanzaId,
756
+ remoteJid: input.remoteJid,
757
+ message: toError(error).message
1000
758
  });
1001
- return [recipientUserJid];
759
+ return null;
1002
760
  }
1003
761
  }
1004
- resolveSelfDeviceJidForRecipient(recipientJid, meJid, meLid) {
1005
- if (splitJid(recipientJid).server !== 'lid') {
1006
- return meJid;
1007
- }
1008
- if (!meLid || !meLid.includes('@')) {
1009
- return meJid;
1010
- }
1011
- return meLid;
1012
- }
1013
762
  getEncodedSignedDeviceIdentity() {
1014
763
  const signedIdentity = this.getCurrentSignedIdentity();
1015
764
  if (!signedIdentity) {
@@ -1017,34 +766,23 @@ export class WaMessageDispatchCoordinator {
1017
766
  }
1018
767
  return proto.ADVSignedDeviceIdentity.encode(signedIdentity).finish();
1019
768
  }
1020
- async ensureSignalSession(address, jid, expectedIdentity, reasonIdentity = false) {
1021
- this.requireCurrentMeJid('ensureSignalSession');
1022
- if (reasonIdentity) {
1023
- await this.signalIdentitySync.syncIdentityKeys([jid]);
1024
- }
1025
- if (await this.signalProtocol.hasSession(address)) {
1026
- return;
1027
- }
1028
- this.logger.info('signal session missing, fetching remote key bundle', { jid });
1029
- const fetched = await this.signalSessionSync.fetchKeyBundle({
1030
- jid,
1031
- reasonIdentity
1032
- });
1033
- const remoteIdentity = toSerializedPubKey(fetched.bundle.identity);
1034
- if (reasonIdentity) {
1035
- const storedIdentity = await this.signalStore.getRemoteIdentity(address);
1036
- if (storedIdentity && !uint8Equal(remoteIdentity, storedIdentity)) {
1037
- throw new Error('identity mismatch');
769
+ resolveUserIcdc(userJid, localIdentity) {
770
+ return this.icdcDedup.run(`icdc:${userJid}:${localIdentity ? '1' : '0'}`, async () => {
771
+ try {
772
+ const snapshots = await this.deviceListStore.getUserDevicesBatch([userJid]);
773
+ const snapshot = snapshots[0];
774
+ if (!snapshot || snapshot.deviceJids.length === 0) {
775
+ return null;
776
+ }
777
+ return resolveIcdcMeta(snapshot.deviceJids, this.signalStore, snapshot.updatedAtMs, localIdentity);
778
+ }
779
+ catch (error) {
780
+ this.logger.trace('icdc resolution failed', {
781
+ userJid,
782
+ message: toError(error).message
783
+ });
784
+ return null;
1038
785
  }
1039
- }
1040
- if (expectedIdentity && !uint8Equal(remoteIdentity, toSerializedPubKey(expectedIdentity))) {
1041
- throw new Error('identity mismatch');
1042
- }
1043
- await this.signalProtocol.establishOutgoingSession(address, fetched.bundle);
1044
- this.logger.info('signal session synchronized', {
1045
- jid,
1046
- regId: fetched.bundle.regId,
1047
- hasOneTimeKey: fetched.bundle.oneTimeKey !== undefined
1048
786
  });
1049
787
  }
1050
788
  requireCurrentMeJid(context) {