zapo-js 0.1.0 → 0.1.2

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 (366) hide show
  1. package/README.md +12 -7
  2. package/dist/appstate/WaAppStateCrypto.js +18 -25
  3. package/dist/appstate/WaAppStateSyncClient.js +181 -114
  4. package/dist/appstate/WaAppStateSyncResponseParser.js +16 -5
  5. package/dist/appstate/constants.js +4 -3
  6. package/dist/appstate/utils.js +10 -30
  7. package/dist/auth/WaAuthClient.js +48 -55
  8. package/dist/auth/flow/WaAuthCredentialsFlow.js +21 -14
  9. package/dist/auth/index.js +1 -3
  10. package/dist/auth/pairing/WaPairingFlow.js +21 -23
  11. package/dist/auth/pairing/WaQrFlow.js +37 -24
  12. package/dist/client/WaClient.js +103 -276
  13. package/dist/client/WaClientFactory.js +227 -110
  14. package/dist/client/connection/WaConnectionManager.js +292 -0
  15. package/dist/client/connection/WaKeyShareCoordinator.js +63 -0
  16. package/dist/client/connection/WaReceiptQueue.js +51 -0
  17. package/dist/client/coordinators/WaAppStateMutationCoordinator.js +471 -0
  18. package/dist/client/coordinators/WaGroupCoordinator.js +27 -17
  19. package/dist/client/coordinators/WaIncomingNodeCoordinator.js +20 -27
  20. package/dist/client/coordinators/WaMessageDispatchCoordinator.js +231 -686
  21. package/dist/client/coordinators/WaRetryCoordinator.js +70 -37
  22. package/dist/client/dirty.js +35 -29
  23. package/dist/client/events/chat.js +4 -3
  24. package/dist/client/events/group.js +59 -36
  25. package/dist/client/history-sync.js +53 -63
  26. package/dist/client/incoming.js +23 -20
  27. package/dist/client/mailbox.js +8 -8
  28. package/dist/client/messages.js +4 -4
  29. package/dist/client/messaging/fanout.js +189 -0
  30. package/dist/client/messaging/key-protocol.js +130 -0
  31. package/dist/client/messaging/participants.js +191 -0
  32. package/dist/crypto/core/hkdf.js +3 -8
  33. package/dist/crypto/core/index.js +1 -4
  34. package/dist/crypto/core/keys.js +2 -3
  35. package/dist/crypto/core/primitives.js +12 -15
  36. package/dist/crypto/core/random.js +7 -26
  37. package/dist/crypto/curves/Ed25519.js +7 -8
  38. package/dist/crypto/curves/X25519.js +13 -16
  39. package/dist/crypto/index.js +0 -5
  40. package/dist/esm/appstate/WaAppStateCrypto.js +6 -13
  41. package/dist/esm/appstate/WaAppStateSyncClient.js +174 -107
  42. package/dist/esm/appstate/WaAppStateSyncResponseParser.js +17 -6
  43. package/dist/esm/appstate/constants.js +3 -2
  44. package/dist/esm/appstate/utils.js +8 -27
  45. package/dist/esm/auth/WaAuthClient.js +48 -55
  46. package/dist/esm/auth/flow/WaAuthCredentialsFlow.js +21 -14
  47. package/dist/esm/auth/index.js +0 -1
  48. package/dist/esm/auth/pairing/WaPairingFlow.js +14 -16
  49. package/dist/esm/auth/pairing/WaQrFlow.js +37 -24
  50. package/dist/esm/client/WaClient.js +103 -276
  51. package/dist/esm/client/WaClientFactory.js +227 -110
  52. package/dist/esm/client/connection/WaConnectionManager.js +288 -0
  53. package/dist/esm/client/connection/WaKeyShareCoordinator.js +59 -0
  54. package/dist/esm/client/connection/WaReceiptQueue.js +47 -0
  55. package/dist/esm/client/coordinators/WaAppStateMutationCoordinator.js +467 -0
  56. package/dist/esm/client/coordinators/WaGroupCoordinator.js +20 -10
  57. package/dist/esm/client/coordinators/WaIncomingNodeCoordinator.js +20 -27
  58. package/dist/esm/client/coordinators/WaMessageDispatchCoordinator.js +232 -687
  59. package/dist/esm/client/coordinators/WaRetryCoordinator.js +71 -38
  60. package/dist/esm/client/dirty.js +30 -24
  61. package/dist/esm/client/events/chat.js +4 -3
  62. package/dist/esm/client/events/group.js +50 -28
  63. package/dist/esm/client/history-sync.js +50 -60
  64. package/dist/esm/client/incoming.js +23 -20
  65. package/dist/esm/client/mailbox.js +8 -8
  66. package/dist/esm/client/messages.js +1 -1
  67. package/dist/esm/client/messaging/fanout.js +186 -0
  68. package/dist/esm/client/messaging/key-protocol.js +127 -0
  69. package/dist/esm/client/messaging/participants.js +188 -0
  70. package/dist/esm/crypto/core/hkdf.js +3 -8
  71. package/dist/esm/crypto/core/index.js +0 -1
  72. package/dist/esm/crypto/core/keys.js +2 -3
  73. package/dist/esm/crypto/core/primitives.js +12 -15
  74. package/dist/esm/crypto/core/random.js +6 -25
  75. package/dist/esm/crypto/curves/Ed25519.js +4 -5
  76. package/dist/esm/crypto/curves/X25519.js +10 -13
  77. package/dist/esm/crypto/index.js +0 -2
  78. package/dist/esm/infra/log/ConsoleLogger.js +18 -17
  79. package/dist/esm/infra/log/PinoLogger.js +15 -9
  80. package/dist/esm/infra/log/types.js +11 -1
  81. package/dist/esm/infra/perf/BoundedTaskQueue.js +13 -17
  82. package/dist/esm/media/WaMediaCrypto.js +2 -4
  83. package/dist/esm/media/WaMediaTransferClient.js +226 -58
  84. package/dist/esm/media/conn.js +10 -6
  85. package/dist/esm/media/constants.js +4 -1
  86. package/dist/esm/message/WaMessageClient.js +4 -13
  87. package/dist/esm/message/ack.js +6 -6
  88. package/dist/esm/message/addon-crypto.js +59 -0
  89. package/dist/esm/message/incoming.js +106 -111
  90. package/dist/esm/message/index.js +2 -0
  91. package/dist/esm/message/reporting-token.js +438 -0
  92. package/dist/esm/message/use-case-secret.js +49 -0
  93. package/dist/esm/protocol/appstate.js +58 -0
  94. package/dist/esm/protocol/constants.js +2 -1
  95. package/dist/esm/protocol/index.js +2 -10
  96. package/dist/esm/protocol/jid.js +63 -51
  97. package/dist/esm/protocol/media.js +3 -3
  98. package/dist/esm/protocol/nodes.js +2 -0
  99. package/dist/esm/protocol/usync.js +11 -0
  100. package/dist/esm/retry/index.js +1 -0
  101. package/dist/esm/retry/outbound.js +4 -5
  102. package/dist/esm/retry/parse.js +58 -76
  103. package/dist/esm/retry/replay.js +48 -49
  104. package/dist/esm/retry/tracker.js +56 -0
  105. package/dist/esm/signal/api/SignalDeviceSyncApi.js +249 -82
  106. package/dist/esm/signal/api/SignalDigestSyncApi.js +6 -1
  107. package/dist/esm/signal/api/SignalIdentitySyncApi.js +49 -34
  108. package/dist/esm/signal/api/SignalMissingPreKeysSyncApi.js +70 -62
  109. package/dist/esm/signal/api/SignalSessionSyncApi.js +23 -30
  110. package/dist/esm/signal/crypto/WaAdvSignature.js +3 -5
  111. package/dist/esm/signal/group/SenderKeyChain.js +28 -23
  112. package/dist/esm/signal/group/SenderKeyCodec.js +2 -4
  113. package/dist/esm/signal/group/SenderKeyManager.js +26 -16
  114. package/dist/esm/signal/index.js +1 -0
  115. package/dist/esm/signal/session/SignalProtocol.js +49 -14
  116. package/dist/esm/signal/session/SignalRatchet.js +24 -15
  117. package/dist/esm/signal/session/SignalSession.js +14 -9
  118. package/dist/esm/signal/session/resolver.js +186 -0
  119. package/dist/esm/signal/store/sqlite.js +16 -37
  120. package/dist/esm/store/createStore.js +16 -18
  121. package/dist/esm/store/noop.store.js +3 -6
  122. package/dist/esm/store/providers/memory/appstate.store.js +30 -6
  123. package/dist/esm/store/providers/memory/contact.store.js +5 -0
  124. package/dist/esm/store/providers/memory/device-list.store.js +3 -30
  125. package/dist/esm/store/providers/memory/message.store.js +11 -5
  126. package/dist/esm/store/providers/memory/participants.store.js +1 -8
  127. package/dist/esm/store/providers/memory/sender-key.store.js +5 -7
  128. package/dist/esm/store/providers/memory/signal.store.js +13 -1
  129. package/dist/esm/store/providers/memory/thread.store.js +5 -0
  130. package/dist/esm/store/providers/sqlite/appstate.store.js +82 -1
  131. package/dist/esm/store/providers/sqlite/connection.js +18 -13
  132. package/dist/esm/store/providers/sqlite/contact.store.js +31 -18
  133. package/dist/esm/store/providers/sqlite/device-list.store.js +7 -35
  134. package/dist/esm/store/providers/sqlite/message.store.js +45 -32
  135. package/dist/esm/store/providers/sqlite/migrations.js +1 -1
  136. package/dist/esm/store/providers/sqlite/participants.store.js +1 -9
  137. package/dist/esm/store/providers/sqlite/retry.store.js +8 -11
  138. package/dist/esm/store/providers/sqlite/sender-key.store.js +25 -30
  139. package/dist/esm/store/providers/sqlite/signal.store.js +104 -22
  140. package/dist/esm/store/providers/sqlite/table-names.js +107 -0
  141. package/dist/esm/store/providers/sqlite/thread.store.js +35 -22
  142. package/dist/esm/transport/WaComms.js +25 -23
  143. package/dist/esm/transport/WaWebSocket.js +115 -12
  144. package/dist/esm/transport/binary/decoder.js +4 -4
  145. package/dist/esm/transport/binary/encoder.js +12 -4
  146. package/dist/esm/transport/index.js +1 -0
  147. package/dist/esm/transport/keepalive/WaKeepAlive.js +2 -8
  148. package/dist/esm/transport/node/WaNodeOrchestrator.js +2 -4
  149. package/dist/esm/transport/node/WaNodeTransport.js +0 -3
  150. package/dist/esm/transport/node/builders/{accountSync.js → account-sync.js} +16 -36
  151. package/dist/esm/transport/node/builders/index.js +2 -1
  152. package/dist/esm/transport/node/builders/message.js +9 -0
  153. package/dist/esm/transport/node/builders/pairing.js +4 -5
  154. package/dist/esm/transport/node/builders/usync.js +41 -0
  155. package/dist/esm/transport/node/helpers.js +107 -5
  156. package/dist/esm/transport/node/usync.js +35 -0
  157. package/dist/esm/transport/noise/WaFrameCodec.js +48 -33
  158. package/dist/esm/transport/noise/WaNoiseCert.js +3 -6
  159. package/dist/esm/transport/noise/WaNoiseSession.js +17 -10
  160. package/dist/esm/transport/proxy.js +27 -0
  161. package/dist/esm/transport/stream/parse.js +13 -48
  162. package/dist/esm/util/bytes.js +50 -32
  163. package/dist/esm/util/coercion.js +6 -14
  164. package/dist/esm/util/primitives.js +39 -14
  165. package/dist/infra/log/ConsoleLogger.js +18 -17
  166. package/dist/infra/log/PinoLogger.js +15 -9
  167. package/dist/infra/log/types.js +12 -0
  168. package/dist/infra/perf/BoundedTaskQueue.js +13 -17
  169. package/dist/media/WaMediaCrypto.js +1 -3
  170. package/dist/media/WaMediaTransferClient.js +259 -58
  171. package/dist/media/conn.js +10 -6
  172. package/dist/media/constants.js +4 -1
  173. package/dist/message/WaMessageClient.js +5 -14
  174. package/dist/message/ack.js +6 -6
  175. package/dist/message/addon-crypto.js +65 -0
  176. package/dist/message/incoming.js +104 -109
  177. package/dist/message/index.js +2 -0
  178. package/dist/message/reporting-token.js +443 -0
  179. package/dist/message/use-case-secret.js +55 -0
  180. package/dist/protocol/appstate.js +59 -1
  181. package/dist/protocol/constants.js +7 -1
  182. package/dist/protocol/index.js +20 -42
  183. package/dist/protocol/jid.js +64 -51
  184. package/dist/protocol/media.js +3 -3
  185. package/dist/protocol/nodes.js +2 -0
  186. package/dist/protocol/usync.js +14 -0
  187. package/dist/retry/index.js +3 -1
  188. package/dist/retry/outbound.js +6 -7
  189. package/dist/retry/parse.js +57 -75
  190. package/dist/retry/replay.js +46 -47
  191. package/dist/retry/tracker.js +59 -0
  192. package/dist/signal/api/SignalDeviceSyncApi.js +247 -80
  193. package/dist/signal/api/SignalDigestSyncApi.js +6 -1
  194. package/dist/signal/api/SignalIdentitySyncApi.js +49 -34
  195. package/dist/signal/api/SignalMissingPreKeysSyncApi.js +67 -59
  196. package/dist/signal/api/SignalSessionSyncApi.js +23 -30
  197. package/dist/signal/crypto/WaAdvSignature.js +2 -4
  198. package/dist/signal/group/SenderKeyChain.js +27 -22
  199. package/dist/signal/group/SenderKeyCodec.js +1 -3
  200. package/dist/signal/group/SenderKeyManager.js +26 -16
  201. package/dist/signal/index.js +3 -1
  202. package/dist/signal/session/SignalProtocol.js +49 -14
  203. package/dist/signal/session/SignalRatchet.js +24 -15
  204. package/dist/signal/session/SignalSession.js +14 -9
  205. package/dist/signal/session/resolver.js +189 -0
  206. package/dist/signal/store/sqlite.js +16 -37
  207. package/dist/store/createStore.js +16 -18
  208. package/dist/store/noop.store.js +3 -6
  209. package/dist/store/providers/memory/appstate.store.js +28 -4
  210. package/dist/store/providers/memory/contact.store.js +5 -0
  211. package/dist/store/providers/memory/device-list.store.js +3 -30
  212. package/dist/store/providers/memory/message.store.js +11 -5
  213. package/dist/store/providers/memory/participants.store.js +1 -8
  214. package/dist/store/providers/memory/sender-key.store.js +8 -10
  215. package/dist/store/providers/memory/signal.store.js +21 -9
  216. package/dist/store/providers/memory/thread.store.js +5 -0
  217. package/dist/store/providers/sqlite/appstate.store.js +81 -0
  218. package/dist/store/providers/sqlite/connection.js +18 -13
  219. package/dist/store/providers/sqlite/contact.store.js +31 -18
  220. package/dist/store/providers/sqlite/device-list.store.js +7 -35
  221. package/dist/store/providers/sqlite/message.store.js +45 -32
  222. package/dist/store/providers/sqlite/migrations.js +1 -1
  223. package/dist/store/providers/sqlite/participants.store.js +1 -9
  224. package/dist/store/providers/sqlite/retry.store.js +8 -11
  225. package/dist/store/providers/sqlite/sender-key.store.js +24 -29
  226. package/dist/store/providers/sqlite/signal.store.js +105 -23
  227. package/dist/store/providers/sqlite/table-names.js +113 -0
  228. package/dist/store/providers/sqlite/thread.store.js +35 -22
  229. package/dist/transport/WaComms.js +27 -25
  230. package/dist/transport/WaWebSocket.js +148 -12
  231. package/dist/transport/binary/decoder.js +4 -4
  232. package/dist/transport/binary/encoder.js +12 -4
  233. package/dist/transport/index.js +7 -1
  234. package/dist/transport/keepalive/WaKeepAlive.js +1 -7
  235. package/dist/transport/node/WaNodeOrchestrator.js +2 -4
  236. package/dist/transport/node/WaNodeTransport.js +0 -3
  237. package/dist/transport/node/builders/{accountSync.js → account-sync.js} +15 -35
  238. package/dist/transport/node/builders/index.js +12 -9
  239. package/dist/transport/node/builders/message.js +9 -0
  240. package/dist/transport/node/builders/pairing.js +4 -5
  241. package/dist/transport/node/builders/usync.js +45 -0
  242. package/dist/transport/node/helpers.js +112 -4
  243. package/dist/transport/node/usync.js +38 -0
  244. package/dist/transport/noise/WaFrameCodec.js +47 -32
  245. package/dist/transport/noise/WaNoiseCert.js +5 -8
  246. package/dist/transport/noise/WaNoiseSession.js +17 -10
  247. package/dist/transport/proxy.js +34 -0
  248. package/dist/transport/stream/parse.js +17 -53
  249. package/dist/types/appstate/WaAppStateCrypto.d.ts +0 -1
  250. package/dist/types/appstate/WaAppStateSyncClient.d.ts +5 -2
  251. package/dist/types/appstate/constants.d.ts +1 -0
  252. package/dist/types/appstate/store/sqlite.d.ts +4 -18
  253. package/dist/types/appstate/utils.d.ts +0 -1
  254. package/dist/types/auth/WaAuthClient.d.ts +10 -12
  255. package/dist/types/auth/index.d.ts +0 -2
  256. package/dist/types/auth/pairing/WaQrFlow.d.ts +1 -1
  257. package/dist/types/auth/types.d.ts +6 -9
  258. package/dist/types/client/WaClient.d.ts +27 -25
  259. package/dist/types/client/WaClientFactory.d.ts +22 -23
  260. package/dist/types/client/connection/WaConnectionManager.d.ts +64 -0
  261. package/dist/types/client/connection/WaKeyShareCoordinator.d.ts +14 -0
  262. package/dist/types/client/connection/WaReceiptQueue.d.ts +13 -0
  263. package/dist/types/client/coordinators/WaAppStateMutationCoordinator.d.ts +46 -0
  264. package/dist/types/client/coordinators/WaIncomingNodeCoordinator.d.ts +0 -1
  265. package/dist/types/client/coordinators/WaMessageDispatchCoordinator.d.ts +18 -41
  266. package/dist/types/client/coordinators/WaRetryCoordinator.d.ts +2 -0
  267. package/dist/types/client/dirty.d.ts +1 -0
  268. package/dist/types/client/events/group.d.ts +2 -1
  269. package/dist/types/client/index.d.ts +1 -1
  270. package/dist/types/client/messaging/fanout.d.ts +14 -0
  271. package/dist/types/client/messaging/key-protocol.d.ts +18 -0
  272. package/dist/types/client/messaging/participants.d.ts +13 -0
  273. package/dist/types/client/types.d.ts +24 -1
  274. package/dist/types/crypto/core/hkdf.d.ts +0 -6
  275. package/dist/types/crypto/core/index.d.ts +0 -1
  276. package/dist/types/crypto/core/random.d.ts +1 -7
  277. package/dist/types/crypto/index.d.ts +0 -2
  278. package/dist/types/index.d.ts +1 -1
  279. package/dist/types/infra/log/ConsoleLogger.d.ts +2 -1
  280. package/dist/types/infra/log/PinoLogger.d.ts +1 -1
  281. package/dist/types/infra/log/types.d.ts +1 -0
  282. package/dist/types/infra/perf/BoundedTaskQueue.d.ts +1 -1
  283. package/dist/types/media/WaMediaTransferClient.d.ts +13 -3
  284. package/dist/types/media/types.d.ts +5 -0
  285. package/dist/types/message/addon-crypto.d.ts +25 -0
  286. package/dist/types/message/index.d.ts +2 -0
  287. package/dist/types/message/reporting-token.d.ts +19 -0
  288. package/dist/types/message/use-case-secret.d.ts +20 -0
  289. package/dist/types/protocol/appstate.d.ts +58 -0
  290. package/dist/types/protocol/constants.d.ts +2 -1
  291. package/dist/types/protocol/index.d.ts +2 -10
  292. package/dist/types/protocol/jid.d.ts +3 -3
  293. package/dist/types/protocol/nodes.d.ts +2 -0
  294. package/dist/types/protocol/usync.d.ts +11 -0
  295. package/dist/types/retry/index.d.ts +1 -0
  296. package/dist/types/retry/replay.d.ts +0 -4
  297. package/dist/types/retry/tracker.d.ts +19 -0
  298. package/dist/types/retry/types.d.ts +4 -3
  299. package/dist/types/signal/api/SignalDeviceSyncApi.d.ts +13 -1
  300. package/dist/types/signal/group/SenderKeyCodec.d.ts +4 -6
  301. package/dist/types/signal/index.d.ts +1 -0
  302. package/dist/types/signal/session/SignalProtocol.d.ts +9 -0
  303. package/dist/types/signal/session/resolver.d.ts +17 -0
  304. package/dist/types/store/contracts/appstate.store.d.ts +3 -0
  305. package/dist/types/store/contracts/contact.store.d.ts +1 -0
  306. package/dist/types/store/contracts/device-list.store.d.ts +0 -3
  307. package/dist/types/store/contracts/message.store.d.ts +1 -0
  308. package/dist/types/store/contracts/participants.store.d.ts +0 -1
  309. package/dist/types/store/contracts/sender-key.store.d.ts +0 -1
  310. package/dist/types/store/contracts/signal.store.d.ts +6 -0
  311. package/dist/types/store/contracts/thread.store.d.ts +1 -0
  312. package/dist/types/store/index.d.ts +1 -1
  313. package/dist/types/store/providers/memory/appstate.store.d.ts +2 -0
  314. package/dist/types/store/providers/memory/contact.store.d.ts +1 -0
  315. package/dist/types/store/providers/memory/device-list.store.d.ts +0 -3
  316. package/dist/types/store/providers/memory/message.store.d.ts +1 -0
  317. package/dist/types/store/providers/memory/participants.store.d.ts +0 -1
  318. package/dist/types/store/providers/memory/sender-key.store.d.ts +0 -1
  319. package/dist/types/store/providers/memory/signal.store.d.ts +6 -0
  320. package/dist/types/store/providers/memory/thread.store.d.ts +1 -0
  321. package/dist/types/store/providers/sqlite/appstate.store.d.ts +2 -0
  322. package/dist/types/store/providers/sqlite/contact.store.d.ts +2 -0
  323. package/dist/types/store/providers/sqlite/device-list.store.d.ts +0 -3
  324. package/dist/types/store/providers/sqlite/message.store.d.ts +2 -0
  325. package/dist/types/store/providers/sqlite/participants.store.d.ts +0 -1
  326. package/dist/types/store/providers/sqlite/retry.store.d.ts +0 -1
  327. package/dist/types/store/providers/sqlite/sender-key.store.d.ts +0 -1
  328. package/dist/types/store/providers/sqlite/signal.store.d.ts +7 -0
  329. package/dist/types/store/providers/sqlite/table-names.d.ts +5 -0
  330. package/dist/types/store/providers/sqlite/thread.store.d.ts +2 -0
  331. package/dist/types/store/types.d.ts +3 -0
  332. package/dist/types/transport/WaWebSocket.d.ts +3 -0
  333. package/dist/types/transport/index.d.ts +2 -1
  334. package/dist/types/transport/keepalive/WaKeepAlive.d.ts +0 -1
  335. package/dist/types/transport/node/WaNodeTransport.d.ts +0 -9
  336. package/dist/types/transport/node/builders/group.d.ts +4 -6
  337. package/dist/types/transport/node/builders/index.d.ts +2 -1
  338. package/dist/types/transport/node/builders/message.d.ts +14 -25
  339. package/dist/types/transport/node/builders/retry.d.ts +2 -4
  340. package/dist/types/transport/node/builders/usync.d.ts +21 -0
  341. package/dist/types/transport/node/helpers.d.ts +8 -0
  342. package/dist/types/transport/node/usync.d.ts +2 -0
  343. package/dist/types/transport/noise/WaFrameCodec.d.ts +3 -0
  344. package/dist/types/transport/noise/WaNoiseSession.d.ts +1 -0
  345. package/dist/types/transport/proxy.d.ts +6 -0
  346. package/dist/types/transport/stream/parse.d.ts +0 -1
  347. package/dist/types/transport/types.d.ts +18 -1
  348. package/dist/types/util/bytes.d.ts +5 -0
  349. package/dist/types/util/primitives.d.ts +3 -0
  350. package/dist/util/bytes.js +55 -33
  351. package/dist/util/coercion.js +6 -14
  352. package/dist/util/primitives.js +42 -14
  353. package/package.json +27 -9
  354. package/proto/index.d.ts +1090 -1048
  355. package/proto/index.js +1 -1
  356. package/scripts/check-node-version.cjs +0 -1
  357. package/dist/crypto/core/encoding.js +0 -29
  358. package/dist/esm/crypto/core/encoding.js +0 -25
  359. package/dist/esm/util/base64.js +0 -18
  360. package/dist/esm/util/signal-address.js +0 -5
  361. package/dist/types/crypto/core/encoding.d.ts +0 -11
  362. package/dist/types/util/base64.d.ts +0 -4
  363. package/dist/types/util/signal-address.d.ts +0 -2
  364. package/dist/util/base64.js +0 -24
  365. package/dist/util/signal-address.js +0 -8
  366. /package/dist/types/transport/node/builders/{accountSync.d.ts → account-sync.d.ts} +0 -0
@@ -1,33 +1,30 @@
1
- import { toSerializedPubKey } from '../../crypto/core/keys.js';
1
+ import { randomBytesAsync, sha256 } from '../../crypto/index.js';
2
+ import { ensureMessageSecret } from '../../message/index.js';
2
3
  import { resolveMessageTypeAttr } from '../../message/content.js';
3
4
  import { wrapDeviceSentMessage } from '../../message/device-sent.js';
4
5
  import { writeRandomPadMax16 } from '../../message/padding.js';
5
6
  import { computePhashV2 } from '../../message/phash.js';
7
+ import { buildReportingTokenArtifacts } from '../../message/reporting-token.js';
6
8
  import { proto } from '../../proto.js';
7
9
  import { WA_DEFAULTS } from '../../protocol/constants.js';
8
10
  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';
11
+ import { signalAddressKey } from '../../protocol/jid.js';
11
12
  import { encodeBinaryNode } from '../../transport/binary/index.js';
12
13
  import { buildDirectMessageFanoutNode, buildGroupDirectMessageNode, buildGroupSenderKeyMessageNode } from '../../transport/node/builders/message.js';
13
- import { bytesToHex, uint8Equal } from '../../util/bytes.js';
14
+ import { bytesToHex, concatBytes, TEXT_ENCODER } from '../../util/bytes.js';
14
15
  import { toError } from '../../util/primitives.js';
15
- import { signalAddressKey } from '../../util/signal-address.js';
16
16
  export class WaMessageDispatchCoordinator {
17
17
  constructor(options) {
18
18
  this.logger = options.logger;
19
19
  this.messageClient = options.messageClient;
20
- this.retryStore = options.retryStore;
21
- this.participantsStore = options.participantsStore;
22
- this.retryTtlMs = this.retryStore.getTtlMs?.() ?? RETRY_OUTBOUND_TTL_MS;
20
+ this.retryTracker = options.retryTracker;
21
+ this.sessionResolver = options.sessionResolver;
22
+ this.fanoutResolver = options.fanoutResolver;
23
+ this.participantsCache = options.participantsCache;
24
+ this.appStateSyncKeyProtocol = options.appStateSyncKeyProtocol;
23
25
  this.buildMessageContent = options.buildMessageContent;
24
- this.queryGroupParticipantJids = options.queryGroupParticipantJids;
25
26
  this.senderKeyManager = options.senderKeyManager;
26
27
  this.signalProtocol = options.signalProtocol;
27
- this.signalStore = options.signalStore;
28
- this.signalDeviceSync = options.signalDeviceSync;
29
- this.signalIdentitySync = options.signalIdentitySync;
30
- this.signalSessionSync = options.signalSessionSync;
31
28
  this.getCurrentMeJid = options.getCurrentMeJid;
32
29
  this.getCurrentMeLid = options.getCurrentMeLid;
33
30
  this.getCurrentSignedIdentity = options.getCurrentSignedIdentity;
@@ -43,13 +40,13 @@ export class WaMessageDispatchCoordinator {
43
40
  mode: 'opaque_node',
44
41
  node: encodeBinaryNode(node)
45
42
  };
46
- return this.publishWithRetryTracking({
43
+ return this.retryTracker.track({
47
44
  messageIdHint: node.attrs.id,
48
45
  toJid: node.attrs.to,
46
+ type: messageType,
47
+ replayPayload,
49
48
  participantJid: node.attrs.participant,
50
- recipientJid: node.attrs.recipient,
51
- messageType,
52
- replayPayload
49
+ recipientJid: node.attrs.recipient
53
50
  }, async () => this.messageClient.publishNode(node, options));
54
51
  }
55
52
  async publishEncryptedMessage(input, options = {}) {
@@ -66,12 +63,12 @@ export class WaMessageDispatchCoordinator {
66
63
  ciphertext: input.ciphertext,
67
64
  participant: input.participant
68
65
  };
69
- return this.publishWithRetryTracking({
66
+ return this.retryTracker.track({
70
67
  messageIdHint: input.id,
71
68
  toJid: input.to,
72
- participantJid: input.participant,
73
- messageType: input.type ?? 'text',
74
- replayPayload
69
+ type: input.type ?? 'text',
70
+ replayPayload,
71
+ participantJid: input.participant
75
72
  }, async () => this.messageClient.publishEncrypted(input, options));
76
73
  }
77
74
  async publishSignalMessage(input, options = {}) {
@@ -86,7 +83,7 @@ export class WaMessageDispatchCoordinator {
86
83
  });
87
84
  const [paddedPlaintext] = await Promise.all([
88
85
  writeRandomPadMax16(input.plaintext),
89
- this.ensureSignalSession(address, input.to, input.expectedIdentity)
86
+ this.sessionResolver.ensureSession(address, input.to, input.expectedIdentity)
90
87
  ]);
91
88
  const encrypted = await this.signalProtocol.encryptMessage(address, paddedPlaintext, input.expectedIdentity);
92
89
  const messageType = input.type ?? 'text';
@@ -96,12 +93,12 @@ export class WaMessageDispatchCoordinator {
96
93
  type: messageType,
97
94
  plaintext: paddedPlaintext
98
95
  };
99
- return this.publishWithRetryTracking({
96
+ return this.retryTracker.track({
100
97
  messageIdHint: input.id,
101
98
  toJid: input.to,
102
- participantJid: input.participant,
103
- messageType,
104
- replayPayload
99
+ type: messageType,
100
+ replayPayload,
101
+ participantJid: input.participant
105
102
  }, async () => this.messageClient.publishEncrypted({
106
103
  to: input.to,
107
104
  encType: encrypted.type,
@@ -116,286 +113,40 @@ export class WaMessageDispatchCoordinator {
116
113
  }
117
114
  async sendMessage(to, content, options = {}) {
118
115
  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);
116
+ const [message, sendOptions] = await Promise.all([
117
+ this.buildMessageContent(content),
118
+ this.withResolvedMessageId(options)
119
+ ]);
120
+ const messageWithSecret = await ensureMessageSecret(message);
121
+ const plaintext = await writeRandomPadMax16(proto.Message.encode(messageWithSecret).finish());
122
+ const type = resolveMessageTypeAttr(messageWithSecret);
122
123
  if (isGroupJid(recipientJid)) {
123
- if (this.shouldUseGroupDirectPath(message)) {
124
- return this.publishGroupDirectMessage(recipientJid, plaintext, type, options);
124
+ if (this.shouldUseGroupDirectPath(messageWithSecret)) {
125
+ return this.publishGroupDirectMessage(recipientJid, messageWithSecret, plaintext, type, sendOptions);
125
126
  }
126
- return this.publishGroupSenderKeyMessage(recipientJid, plaintext, type, options);
127
+ return this.publishGroupSenderKeyMessage(recipientJid, messageWithSecret, plaintext, type, sendOptions);
127
128
  }
128
129
  const directRecipientJid = toUserJid(recipientJid);
129
- return this.publishDirectSignalMessageWithFanout(directRecipientJid, message, plaintext, type, options);
130
+ return this.publishDirectSignalMessageWithFanout(directRecipientJid, messageWithSecret, plaintext, type, sendOptions);
130
131
  }
131
132
  async syncSignalSession(jid, reasonIdentity = false) {
132
133
  const address = parseSignalAddressFromJid(jid);
133
134
  if (address.server === WA_DEFAULTS.GROUP_SERVER) {
134
135
  throw new Error('syncSignalSession supports only direct chats');
135
136
  }
136
- await this.ensureSignalSession(address, jid, undefined, reasonIdentity);
137
+ await this.sessionResolver.ensureSession(address, jid, undefined, reasonIdentity);
137
138
  }
138
139
  async sendReceipt(input) {
139
140
  await this.messageClient.sendReceipt(input);
140
141
  }
141
142
  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;
143
+ return this.appStateSyncKeyProtocol.requestKeys(keyIds);
168
144
  }
169
145
  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
- });
146
+ await this.appStateSyncKeyProtocol.sendKeyShare(toDeviceJid, keys, missingKeyIds);
202
147
  }
203
148
  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;
149
+ await this.participantsCache.mutateFromGroupEvent(event);
399
150
  }
400
151
  shouldUseGroupDirectPath(message) {
401
152
  const protocolType = message.protocolMessage?.type;
@@ -405,41 +156,50 @@ export class WaMessageDispatchCoordinator {
405
156
  }
406
157
  return message.keepInChatMessage?.keepType === proto.KeepType.UNDO_KEEP_FOR_ALL;
407
158
  }
408
- async publishGroupDirectMessage(groupJid, plaintext, type, options, retryContext = {}) {
159
+ async publishGroupDirectMessage(groupJid, message, plaintext, type, options, retryContext = {}) {
160
+ const sendOptions = await this.withResolvedMessageId(options);
409
161
  const meJid = this.requireCurrentMeJid('sendMessage');
410
162
  const participantUserJids = retryContext.forceRefreshParticipants
411
- ? await this.refreshGroupParticipantUsers(groupJid)
412
- : await this.resolveGroupParticipantUsers(groupJid);
163
+ ? await this.participantsCache.refreshParticipantUsers(groupJid)
164
+ : await this.participantsCache.resolveParticipantUsers(groupJid);
413
165
  const addressingMode = retryContext.forceAddressingMode ??
414
166
  this.resolveGroupAddressingMode(participantUserJids, groupJid);
415
167
  const senderForPhash = this.resolveSenderForAddressingMode(addressingMode, meJid);
416
- const fanoutDeviceJids = await this.resolveGroupParticipantDeviceJids(participantUserJids);
168
+ const fanoutDeviceJids = await this.fanoutResolver.resolveGroupParticipantDeviceJids(participantUserJids);
417
169
  if (fanoutDeviceJids.length === 0) {
418
170
  throw new Error('group direct send resolved no target devices');
419
171
  }
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
429
- };
172
+ await this.sessionResolver.ensureSessionsBatch(fanoutDeviceJids);
173
+ const participantAddresses = fanoutDeviceJids.map((targetJid) => parseSignalAddressFromJid(targetJid));
174
+ const encryptedParticipants = await this.signalProtocol.encryptMessagesBatch(participantAddresses.map((address) => ({
175
+ address,
176
+ plaintext
177
+ })));
178
+ const participants = fanoutDeviceJids.map((targetJid, index) => ({
179
+ jid: targetJid,
180
+ encType: encryptedParticipants[index].type,
181
+ ciphertext: encryptedParticipants[index].ciphertext
430
182
  }));
431
183
  const shouldAttachDeviceIdentity = participants.some((participant) => participant.encType === 'pkmsg');
432
184
  const localPhash = await computePhashV2([...fanoutDeviceJids, senderForPhash]);
185
+ const reportingArtifacts = await this.tryBuildReportingTokenArtifacts({
186
+ message,
187
+ stanzaId: sendOptions.id,
188
+ senderUserJid: toUserJid(senderForPhash),
189
+ remoteJid: groupJid,
190
+ context: 'group_direct'
191
+ });
433
192
  const messageNode = buildGroupDirectMessageNode({
434
193
  to: groupJid,
435
194
  type,
436
- id: options.id,
195
+ id: sendOptions.id,
437
196
  phash: localPhash,
438
197
  addressingMode,
439
198
  participants,
440
199
  deviceIdentity: shouldAttachDeviceIdentity
441
200
  ? this.getEncodedSignedDeviceIdentity()
442
- : undefined
201
+ : undefined,
202
+ reportingNode: reportingArtifacts?.node ?? undefined
443
203
  });
444
204
  const replayPayload = {
445
205
  mode: 'plaintext',
@@ -447,12 +207,12 @@ export class WaMessageDispatchCoordinator {
447
207
  type,
448
208
  plaintext
449
209
  };
450
- const result = await this.publishWithRetryTracking({
451
- messageIdHint: options.id ?? messageNode.attrs.id,
210
+ const result = await this.retryTracker.track({
211
+ messageIdHint: sendOptions.id ?? messageNode.attrs.id,
452
212
  toJid: groupJid,
453
- messageType: type,
213
+ type,
454
214
  replayPayload
455
- }, async () => this.messageClient.publishNode(messageNode, options));
215
+ }, async () => this.messageClient.publishNode(messageNode, sendOptions));
456
216
  const ackError = result.ack.error;
457
217
  const serverPhash = result.ack.phash;
458
218
  const serverAddressingMode = result.ack.addressingMode;
@@ -470,8 +230,8 @@ export class WaMessageDispatchCoordinator {
470
230
  serverAddressingMode,
471
231
  ackError
472
232
  });
473
- return this.publishGroupDirectMessage(groupJid, plaintext, type, {
474
- ...options,
233
+ return this.publishGroupDirectMessage(groupJid, message, plaintext, type, {
234
+ ...sendOptions,
475
235
  id: result.id
476
236
  }, {
477
237
  retried: true,
@@ -481,11 +241,12 @@ export class WaMessageDispatchCoordinator {
481
241
  }
482
242
  return result;
483
243
  }
484
- async publishGroupSenderKeyMessage(groupJid, plaintext, type, options, retryContext = {}) {
244
+ async publishGroupSenderKeyMessage(groupJid, message, plaintext, type, options, retryContext = {}) {
245
+ const sendOptions = await this.withResolvedMessageId(options);
485
246
  const meJid = this.requireCurrentMeJid('sendMessage');
486
247
  const participantUserJids = retryContext.forceRefreshParticipants
487
- ? await this.refreshGroupParticipantUsers(groupJid)
488
- : await this.resolveGroupParticipantUsers(groupJid);
248
+ ? await this.participantsCache.refreshParticipantUsers(groupJid)
249
+ : await this.participantsCache.resolveParticipantUsers(groupJid);
489
250
  const addressingMode = retryContext.forceAddressingMode ??
490
251
  this.resolveGroupAddressingMode(participantUserJids, groupJid);
491
252
  const senderJid = this.resolveSenderForAddressingMode(addressingMode, meJid);
@@ -496,17 +257,25 @@ export class WaMessageDispatchCoordinator {
496
257
  const { fanoutDeviceJids, distributionParticipants } = distributionData;
497
258
  const shouldAttachDeviceIdentity = distributionParticipants.some((participant) => participant.encType === 'pkmsg');
498
259
  const localPhash = await computePhashV2([...fanoutDeviceJids, senderJid]);
260
+ const reportingArtifacts = await this.tryBuildReportingTokenArtifacts({
261
+ message,
262
+ stanzaId: sendOptions.id,
263
+ senderUserJid: toUserJid(senderJid),
264
+ remoteJid: groupJid,
265
+ context: 'group_sender_key'
266
+ });
499
267
  const messageNode = buildGroupSenderKeyMessageNode({
500
268
  to: groupJid,
501
269
  type,
502
- id: options.id,
270
+ id: sendOptions.id,
503
271
  phash: localPhash,
504
272
  addressingMode,
505
273
  groupCiphertext: groupCiphertext.ciphertext,
506
274
  participants: distributionParticipants,
507
275
  deviceIdentity: shouldAttachDeviceIdentity
508
276
  ? this.getEncodedSignedDeviceIdentity()
509
- : undefined
277
+ : undefined,
278
+ reportingNode: reportingArtifacts?.node ?? undefined
510
279
  });
511
280
  const replayPayload = {
512
281
  mode: 'plaintext',
@@ -514,12 +283,12 @@ export class WaMessageDispatchCoordinator {
514
283
  type,
515
284
  plaintext
516
285
  };
517
- const result = await this.publishWithRetryTracking({
518
- messageIdHint: options.id ?? messageNode.attrs.id,
286
+ const result = await this.retryTracker.track({
287
+ messageIdHint: sendOptions.id ?? messageNode.attrs.id,
519
288
  toJid: groupJid,
520
- messageType: type,
289
+ type,
521
290
  replayPayload
522
- }, async () => this.messageClient.publishNode(messageNode, options));
291
+ }, async () => this.messageClient.publishNode(messageNode, sendOptions));
523
292
  const distributedAddresses = distributionParticipants.map((participant) => participant.address);
524
293
  try {
525
294
  await this.senderKeyManager.markSenderKeyDistributed(groupJid, sender, distributedAddresses);
@@ -548,8 +317,8 @@ export class WaMessageDispatchCoordinator {
548
317
  serverAddressingMode,
549
318
  ackError
550
319
  });
551
- return this.publishGroupSenderKeyMessage(groupJid, plaintext, type, {
552
- ...options,
320
+ return this.publishGroupSenderKeyMessage(groupJid, message, plaintext, type, {
321
+ ...sendOptions,
553
322
  id: result.id
554
323
  }, {
555
324
  retried: true,
@@ -559,137 +328,6 @@ export class WaMessageDispatchCoordinator {
559
328
  }
560
329
  return result;
561
330
  }
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
331
  resolveGroupAddressingMode(participantUserJids, groupJid) {
694
332
  for (const participantJid of participantUserJids) {
695
333
  try {
@@ -728,111 +366,95 @@ export class WaMessageDispatchCoordinator {
728
366
  const distributionPayload = await writeRandomPadMax16(proto.Message.encode({
729
367
  senderKeyDistributionMessage
730
368
  }).finish());
731
- const fanoutDeviceJids = await this.resolveGroupParticipantDeviceJids(participantUserJids);
369
+ const fanoutDeviceJids = await this.fanoutResolver.resolveGroupParticipantDeviceJids(participantUserJids);
732
370
  if (fanoutDeviceJids.length === 0) {
733
371
  return {
734
372
  fanoutDeviceJids,
735
373
  distributionParticipants: []
736
374
  };
737
375
  }
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));
376
+ const fanoutTargetsByAddressKey = new Map();
377
+ const fanoutAddresses = new Array(fanoutDeviceJids.length);
378
+ for (let index = 0; index < fanoutDeviceJids.length; index += 1) {
379
+ const jid = fanoutDeviceJids[index];
380
+ const address = parseSignalAddressFromJid(jid);
381
+ fanoutAddresses[index] = address;
382
+ fanoutTargetsByAddressKey.set(signalAddressKey(address), { jid, address });
383
+ }
384
+ const pendingAddresses = await this.senderKeyManager.filterParticipantsNeedingDistribution(groupJid, sender, fanoutAddresses);
743
385
  if (pendingAddresses.length === 0) {
744
386
  return {
745
387
  fanoutDeviceJids,
746
388
  distributionParticipants: []
747
389
  };
748
390
  }
749
- const pendingAddressKeys = new Set(pendingAddresses.map(signalAddressKey));
750
- const pendingTargets = fanoutTargets.filter((target) => pendingAddressKeys.has(signalAddressKey(target.address)));
391
+ const pendingAddressKeys = new Set();
392
+ const pendingTargets = [];
393
+ for (let index = 0; index < pendingAddresses.length; index += 1) {
394
+ const key = signalAddressKey(pendingAddresses[index]);
395
+ if (pendingAddressKeys.has(key)) {
396
+ continue;
397
+ }
398
+ pendingAddressKeys.add(key);
399
+ const target = fanoutTargetsByAddressKey.get(key);
400
+ if (target) {
401
+ pendingTargets.push(target);
402
+ }
403
+ }
751
404
  if (pendingTargets.length === 0) {
752
405
  return {
753
406
  fanoutDeviceJids,
754
407
  distributionParticipants: []
755
408
  };
756
409
  }
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);
761
- return {
762
- jid: target.jid,
763
- address: target.address,
764
- encType: encrypted.type,
765
- ciphertext: encrypted.ciphertext
766
- };
767
- }));
768
- return {
769
- fanoutDeviceJids,
770
- distributionParticipants
771
- };
772
- }
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 [];
410
+ const pendingTargetJids = new Array(pendingTargets.length);
411
+ for (let index = 0; index < pendingTargets.length; index += 1) {
412
+ pendingTargetJids[index] = pendingTargets[index].jid;
802
413
  }
803
414
  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];
415
+ await this.sessionResolver.ensureSessionsBatch(pendingTargetJids);
822
416
  }
823
417
  catch (error) {
824
- this.logger.warn('group participant device sync failed, falling back to participant user jids', {
825
- participants: candidateUsers.length,
418
+ this.logger.warn('group sender-key distribution session sync failed, continuing with available sessions', {
419
+ groupJid,
420
+ requested: pendingTargetJids.length,
826
421
  message: toError(error).message
827
422
  });
828
- return [...new Set(candidateUsers.map((jid) => normalizeDeviceJid(jid)))].filter((jid) => !meDeviceJids.has(jid));
829
423
  }
424
+ const hasPendingSessions = await this.signalProtocol.hasSessions(pendingTargets.map((target) => target.address));
425
+ const availableTargets = pendingTargets.filter((_target, index) => hasPendingSessions[index]);
426
+ if (availableTargets.length === 0) {
427
+ return {
428
+ fanoutDeviceJids,
429
+ distributionParticipants: []
430
+ };
431
+ }
432
+ const encryptedDistributionParticipants = await this.signalProtocol.encryptMessagesBatch(availableTargets.map((target) => ({
433
+ address: target.address,
434
+ plaintext: distributionPayload
435
+ })));
436
+ const distributionParticipants = availableTargets.map((target, index) => ({
437
+ jid: target.jid,
438
+ address: target.address,
439
+ encType: encryptedDistributionParticipants[index].type,
440
+ ciphertext: encryptedDistributionParticipants[index].ciphertext
441
+ }));
442
+ return {
443
+ fanoutDeviceJids,
444
+ distributionParticipants
445
+ };
830
446
  }
831
447
  async publishDirectSignalMessageWithFanout(recipientJid, message, plaintext, type, options) {
448
+ const sendOptions = await this.withResolvedMessageId(options);
832
449
  const meJid = this.requireCurrentMeJid('sendMessage');
833
450
  const meLid = this.getCurrentMeLid();
834
- const selfDeviceJidForRecipient = this.resolveSelfDeviceJidForRecipient(recipientJid, meJid, meLid);
835
- const deviceJids = await this.resolveDirectFanoutDeviceJids(recipientJid, selfDeviceJidForRecipient);
451
+ const selfDeviceJidForRecipient = this.fanoutResolver.resolveSelfDeviceJidForRecipient(recipientJid, meJid, meLid);
452
+ const deviceJids = await this.fanoutResolver.resolveDirectFanoutDeviceJids(recipientJid, selfDeviceJidForRecipient);
453
+ const targets = deviceJids.map((jid) => ({
454
+ jid,
455
+ normalizedJid: normalizeDeviceJid(jid),
456
+ userJid: toUserJid(jid)
457
+ }));
836
458
  const recipientUserJid = toUserJid(recipientJid);
837
459
  const meUserJid = toUserJid(selfDeviceJidForRecipient);
838
460
  this.logger.debug('wa client publish signal fanout', {
@@ -841,44 +463,55 @@ export class WaMessageDispatchCoordinator {
841
463
  type
842
464
  });
843
465
  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);
466
+ if (sendOptions.expectedIdentity) {
467
+ for (let index = 0; index < targets.length; index += 1) {
468
+ const target = targets[index];
469
+ if (target.userJid === recipientUserJid) {
470
+ expectedIdentityByJid.set(target.normalizedJid, sendOptions.expectedIdentity);
849
471
  }
850
472
  }
851
473
  }
852
- await this.ensureSignalSessionsBatch(deviceJids, expectedIdentityByJid);
853
- const hasSelfDeviceFanout = deviceJids.some((targetJid) => toUserJid(targetJid) === meUserJid);
474
+ await this.sessionResolver.ensureSessionsBatch(deviceJids, expectedIdentityByJid);
475
+ const hasSelfDeviceFanout = targets.some((target) => target.userJid === meUserJid);
854
476
  const selfDevicePlaintext = hasSelfDeviceFanout
855
477
  ? await writeRandomPadMax16(proto.Message.encode(wrapDeviceSentMessage(message, recipientUserJid)).finish())
856
478
  : 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
479
+ const participantRequests = targets.map((target) => ({
480
+ target,
481
+ address: parseSignalAddressFromJid(target.jid),
482
+ expectedIdentity: target.userJid === recipientUserJid ? sendOptions.expectedIdentity : undefined,
483
+ plaintext: selfDevicePlaintext && target.userJid === meUserJid
862
484
  ? 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
870
- };
485
+ : plaintext
486
+ }));
487
+ const encryptedParticipants = await this.signalProtocol.encryptMessagesBatch(participantRequests.map((request) => ({
488
+ address: request.address,
489
+ plaintext: request.plaintext,
490
+ expectedIdentity: request.expectedIdentity
491
+ })));
492
+ const participants = participantRequests.map((request, index) => ({
493
+ jid: request.target.jid,
494
+ encType: encryptedParticipants[index].type,
495
+ ciphertext: encryptedParticipants[index].ciphertext
871
496
  }));
872
497
  const shouldAttachDeviceIdentity = participants.some((participant) => participant.encType === 'pkmsg');
873
498
  const deviceIdentity = shouldAttachDeviceIdentity
874
499
  ? this.getEncodedSignedDeviceIdentity()
875
500
  : undefined;
501
+ const reportingArtifacts = await this.tryBuildReportingTokenArtifacts({
502
+ message,
503
+ stanzaId: sendOptions.id,
504
+ senderUserJid: meUserJid,
505
+ remoteJid: recipientUserJid,
506
+ context: 'direct_fanout'
507
+ });
876
508
  const messageNode = buildDirectMessageFanoutNode({
877
509
  to: recipientJid,
878
510
  type,
879
- id: options.id,
511
+ id: sendOptions.id,
880
512
  participants,
881
- deviceIdentity
513
+ deviceIdentity,
514
+ reportingNode: reportingArtifacts?.node ?? undefined
882
515
  });
883
516
  const replayPayload = {
884
517
  mode: 'plaintext',
@@ -886,130 +519,72 @@ export class WaMessageDispatchCoordinator {
886
519
  type,
887
520
  plaintext
888
521
  };
889
- return this.publishWithRetryTracking({
890
- messageIdHint: options.id ?? messageNode.attrs.id,
522
+ return this.retryTracker.track({
523
+ messageIdHint: sendOptions.id ?? messageNode.attrs.id,
891
524
  toJid: recipientJid,
892
- messageType: type,
525
+ type,
893
526
  replayPayload
894
- }, async () => this.messageClient.publishNode(messageNode, options));
527
+ }, async () => this.messageClient.publishNode(messageNode, sendOptions));
895
528
  }
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;
529
+ async withResolvedMessageId(options) {
530
+ const normalizedId = options.id?.trim();
531
+ if (normalizedId) {
532
+ if (normalizedId === options.id) {
533
+ return options;
534
+ }
535
+ return {
536
+ ...options,
537
+ id: normalizedId
538
+ };
909
539
  }
540
+ return {
541
+ ...options,
542
+ id: await this.generateOutgoingMessageId()
543
+ };
544
+ }
545
+ async generateOutgoingMessageId() {
910
546
  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
- }
547
+ const meUserJid = toUserJid(this.requireCurrentMeJid('sendMessage'));
548
+ const timestampSeconds = Math.floor(Date.now() / 1000);
549
+ const timestampBytes = new Uint8Array(8);
550
+ new DataView(timestampBytes.buffer, timestampBytes.byteOffset, timestampBytes.byteLength).setBigUint64(0, BigInt(timestampSeconds), false);
551
+ const entropy = concatBytes([
552
+ timestampBytes,
553
+ TEXT_ENCODER.encode(meUserJid),
554
+ await randomBytesAsync(8)
555
+ ]);
556
+ const digest = await sha256(entropy);
557
+ return `3EB0${bytesToHex(digest.subarray(0, 9)).toUpperCase()}`;
951
558
  }
952
559
  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
560
+ this.logger.warn('failed to generate sha256 message id, falling back to random id', {
561
+ message: toError(error).message
960
562
  });
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
- }
563
+ return `3EB0${bytesToHex(await randomBytesAsync(8)).toUpperCase()}`;
965
564
  }
966
565
  }
967
- async resolveDirectFanoutDeviceJids(recipientJid, selfDeviceJidForRecipient) {
968
- const recipientUserJid = toUserJid(recipientJid);
969
- const meUserJid = toUserJid(selfDeviceJidForRecipient);
970
- const targets = recipientUserJid === meUserJid ? [recipientUserJid] : [recipientUserJid, meUserJid];
566
+ async tryBuildReportingTokenArtifacts(input) {
567
+ if (!input.stanzaId) {
568
+ return null;
569
+ }
971
570
  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];
571
+ return await buildReportingTokenArtifacts({
572
+ message: input.message,
573
+ stanzaId: input.stanzaId,
574
+ senderUserJid: input.senderUserJid,
575
+ remoteJid: input.remoteJid
576
+ });
994
577
  }
995
578
  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
579
+ this.logger.warn('failed to generate reporting token', {
580
+ context: input.context,
581
+ id: input.stanzaId,
582
+ remoteJid: input.remoteJid,
583
+ message: toError(error).message
1000
584
  });
1001
- return [recipientUserJid];
585
+ return null;
1002
586
  }
1003
587
  }
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
588
  getEncodedSignedDeviceIdentity() {
1014
589
  const signedIdentity = this.getCurrentSignedIdentity();
1015
590
  if (!signedIdentity) {
@@ -1017,36 +592,6 @@ export class WaMessageDispatchCoordinator {
1017
592
  }
1018
593
  return proto.ADVSignedDeviceIdentity.encode(signedIdentity).finish();
1019
594
  }
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');
1038
- }
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
- });
1049
- }
1050
595
  requireCurrentMeJid(context) {
1051
596
  const meJid = this.getCurrentMeJid();
1052
597
  if (meJid) {