zapo-js 0.1.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 (585) hide show
  1. package/README.md +235 -0
  2. package/dist/appstate/WaAppStateCrypto.js +202 -0
  3. package/dist/appstate/WaAppStateSyncClient.js +808 -0
  4. package/dist/appstate/WaAppStateSyncResponseParser.js +71 -0
  5. package/dist/appstate/constants.js +23 -0
  6. package/dist/appstate/index.js +28 -0
  7. package/dist/appstate/store/sqlite.js +55 -0
  8. package/dist/appstate/types.js +2 -0
  9. package/dist/appstate/utils.js +84 -0
  10. package/dist/auth/WaAuthClient.js +266 -0
  11. package/dist/auth/flow/WaAuthCredentialsFlow.js +123 -0
  12. package/dist/auth/index.js +27 -0
  13. package/dist/auth/pairing/WaPairingCodeCrypto.js +75 -0
  14. package/dist/auth/pairing/WaPairingFlow.js +328 -0
  15. package/dist/auth/pairing/WaQrFlow.js +86 -0
  16. package/dist/auth/pairing/constants.js +5 -0
  17. package/dist/auth/types.js +2 -0
  18. package/dist/client/WaClient.js +749 -0
  19. package/dist/client/WaClientFactory.js +381 -0
  20. package/dist/client/coordinators/WaGroupCoordinator.js +191 -0
  21. package/dist/client/coordinators/WaIncomingNodeCoordinator.js +315 -0
  22. package/dist/client/coordinators/WaMessageDispatchCoordinator.js +1061 -0
  23. package/dist/client/coordinators/WaPassiveTasksCoordinator.js +200 -0
  24. package/dist/client/coordinators/WaRetryCoordinator.js +494 -0
  25. package/dist/client/coordinators/WaStreamControlCoordinator.js +123 -0
  26. package/dist/client/dirty.js +254 -0
  27. package/dist/client/events/chat.js +226 -0
  28. package/dist/client/events/group.js +410 -0
  29. package/dist/client/history-sync.js +122 -0
  30. package/dist/client/incoming.js +236 -0
  31. package/dist/client/index.js +5 -0
  32. package/dist/client/mailbox.js +49 -0
  33. package/dist/client/messages.js +152 -0
  34. package/dist/client/types.js +2 -0
  35. package/dist/crypto/core/constants.js +4 -0
  36. package/dist/crypto/core/encoding.js +29 -0
  37. package/dist/crypto/core/hkdf.js +26 -0
  38. package/dist/crypto/core/index.js +43 -0
  39. package/dist/crypto/core/keys.js +73 -0
  40. package/dist/crypto/core/nonce.js +18 -0
  41. package/dist/crypto/core/primitives.js +121 -0
  42. package/dist/crypto/core/random.js +32 -0
  43. package/dist/crypto/curves/Ed25519.js +42 -0
  44. package/dist/crypto/curves/X25519.js +64 -0
  45. package/dist/crypto/curves/constants.js +6 -0
  46. package/dist/crypto/curves/types.js +9 -0
  47. package/dist/crypto/index.js +22 -0
  48. package/dist/crypto/math/constants.js +44 -0
  49. package/dist/crypto/math/edwards.js +64 -0
  50. package/dist/crypto/math/le.js +20 -0
  51. package/dist/crypto/math/mod.js +38 -0
  52. package/dist/crypto/math/types.js +2 -0
  53. package/dist/esm/appstate/WaAppStateCrypto.js +198 -0
  54. package/dist/esm/appstate/WaAppStateSyncClient.js +803 -0
  55. package/dist/esm/appstate/WaAppStateSyncResponseParser.js +67 -0
  56. package/dist/esm/appstate/constants.js +20 -0
  57. package/dist/esm/appstate/index.js +6 -0
  58. package/dist/esm/appstate/store/sqlite.js +49 -0
  59. package/dist/esm/appstate/types.js +1 -0
  60. package/dist/esm/appstate/utils.js +75 -0
  61. package/dist/esm/auth/WaAuthClient.js +262 -0
  62. package/dist/esm/auth/flow/WaAuthCredentialsFlow.js +118 -0
  63. package/dist/esm/auth/index.js +5 -0
  64. package/dist/esm/auth/pairing/WaPairingCodeCrypto.js +71 -0
  65. package/dist/esm/auth/pairing/WaPairingFlow.js +324 -0
  66. package/dist/esm/auth/pairing/WaQrFlow.js +82 -0
  67. package/dist/esm/auth/pairing/constants.js +2 -0
  68. package/dist/esm/auth/types.js +1 -0
  69. package/dist/esm/client/WaClient.js +745 -0
  70. package/dist/esm/client/WaClientFactory.js +377 -0
  71. package/dist/esm/client/coordinators/WaGroupCoordinator.js +188 -0
  72. package/dist/esm/client/coordinators/WaIncomingNodeCoordinator.js +311 -0
  73. package/dist/esm/client/coordinators/WaMessageDispatchCoordinator.js +1057 -0
  74. package/dist/esm/client/coordinators/WaPassiveTasksCoordinator.js +196 -0
  75. package/dist/esm/client/coordinators/WaRetryCoordinator.js +490 -0
  76. package/dist/esm/client/coordinators/WaStreamControlCoordinator.js +120 -0
  77. package/dist/esm/client/dirty.js +250 -0
  78. package/dist/esm/client/events/chat.js +223 -0
  79. package/dist/esm/client/events/group.js +407 -0
  80. package/dist/esm/client/history-sync.js +119 -0
  81. package/dist/esm/client/incoming.js +227 -0
  82. package/dist/esm/client/index.js +1 -0
  83. package/dist/esm/client/mailbox.js +46 -0
  84. package/dist/esm/client/messages.js +148 -0
  85. package/dist/esm/client/types.js +1 -0
  86. package/dist/esm/crypto/core/constants.js +1 -0
  87. package/dist/esm/crypto/core/encoding.js +25 -0
  88. package/dist/esm/crypto/core/hkdf.js +22 -0
  89. package/dist/esm/crypto/core/index.js +11 -0
  90. package/dist/esm/crypto/core/keys.js +66 -0
  91. package/dist/esm/crypto/core/nonce.js +15 -0
  92. package/dist/esm/crypto/core/primitives.js +102 -0
  93. package/dist/esm/crypto/core/random.js +28 -0
  94. package/dist/esm/crypto/curves/Ed25519.js +38 -0
  95. package/dist/esm/crypto/curves/X25519.js +58 -0
  96. package/dist/esm/crypto/curves/constants.js +3 -0
  97. package/dist/esm/crypto/curves/types.js +6 -0
  98. package/dist/esm/crypto/index.js +3 -0
  99. package/dist/esm/crypto/math/constants.js +41 -0
  100. package/dist/esm/crypto/math/edwards.js +60 -0
  101. package/dist/esm/crypto/math/le.js +16 -0
  102. package/dist/esm/crypto/math/mod.js +31 -0
  103. package/dist/esm/crypto/math/types.js +1 -0
  104. package/dist/esm/index.js +6 -0
  105. package/dist/esm/infra/log/ConsoleLogger.js +40 -0
  106. package/dist/esm/infra/log/PinoLogger.js +73 -0
  107. package/dist/esm/infra/log/types.js +1 -0
  108. package/dist/esm/infra/perf/BoundedTaskQueue.js +62 -0
  109. package/dist/esm/media/WaMediaCrypto.js +224 -0
  110. package/dist/esm/media/WaMediaTransferClient.js +361 -0
  111. package/dist/esm/media/conn.js +33 -0
  112. package/dist/esm/media/constants.js +18 -0
  113. package/dist/esm/media/index.js +3 -0
  114. package/dist/esm/media/types.js +1 -0
  115. package/dist/esm/message/WaMessageClient.js +210 -0
  116. package/dist/esm/message/ack.js +46 -0
  117. package/dist/esm/message/content.js +20 -0
  118. package/dist/esm/message/device-sent.js +49 -0
  119. package/dist/esm/message/incoming.js +318 -0
  120. package/dist/esm/message/index.js +2 -0
  121. package/dist/esm/message/padding.js +20 -0
  122. package/dist/esm/message/phash.js +25 -0
  123. package/dist/esm/message/types.js +1 -0
  124. package/dist/esm/package.json +3 -0
  125. package/dist/esm/proto.js +3 -0
  126. package/dist/esm/protocol/appstate.js +34 -0
  127. package/dist/esm/protocol/auth.js +12 -0
  128. package/dist/esm/protocol/browser.js +41 -0
  129. package/dist/esm/protocol/constants.js +11 -0
  130. package/dist/esm/protocol/defaults.js +27 -0
  131. package/dist/esm/protocol/dirty.js +26 -0
  132. package/dist/esm/protocol/group.js +5 -0
  133. package/dist/esm/protocol/index.js +11 -0
  134. package/dist/esm/protocol/jid.js +94 -0
  135. package/dist/esm/protocol/media.js +20 -0
  136. package/dist/esm/protocol/message.js +16 -0
  137. package/dist/esm/protocol/nodes.js +83 -0
  138. package/dist/esm/protocol/notification.js +50 -0
  139. package/dist/esm/protocol/stream.js +60 -0
  140. package/dist/esm/retry/constants.js +20 -0
  141. package/dist/esm/retry/index.js +5 -0
  142. package/dist/esm/retry/outbound.js +83 -0
  143. package/dist/esm/retry/parse.js +130 -0
  144. package/dist/esm/retry/reason.js +50 -0
  145. package/dist/esm/retry/replay.js +177 -0
  146. package/dist/esm/retry/types.js +1 -0
  147. package/dist/esm/signal/api/SignalDeviceSyncApi.js +185 -0
  148. package/dist/esm/signal/api/SignalDigestSyncApi.js +179 -0
  149. package/dist/esm/signal/api/SignalIdentitySyncApi.js +111 -0
  150. package/dist/esm/signal/api/SignalMissingPreKeysSyncApi.js +141 -0
  151. package/dist/esm/signal/api/SignalRotateKeyApi.js +59 -0
  152. package/dist/esm/signal/api/SignalSessionSyncApi.js +187 -0
  153. package/dist/esm/signal/api/codec.js +23 -0
  154. package/dist/esm/signal/api/constants.js +9 -0
  155. package/dist/esm/signal/api/prekeys.js +9 -0
  156. package/dist/esm/signal/constants.js +16 -0
  157. package/dist/esm/signal/crypto/WaAdvSignature.js +60 -0
  158. package/dist/esm/signal/crypto/constants.js +8 -0
  159. package/dist/esm/signal/group/SenderKeyChain.js +97 -0
  160. package/dist/esm/signal/group/SenderKeyCodec.js +46 -0
  161. package/dist/esm/signal/group/SenderKeyManager.js +176 -0
  162. package/dist/esm/signal/index.js +11 -0
  163. package/dist/esm/signal/registration/keygen.js +31 -0
  164. package/dist/esm/signal/registration/utils.js +16 -0
  165. package/dist/esm/signal/session/SignalProtocol.js +122 -0
  166. package/dist/esm/signal/session/SignalRatchet.js +260 -0
  167. package/dist/esm/signal/session/SignalSerializer.js +63 -0
  168. package/dist/esm/signal/session/SignalSession.js +153 -0
  169. package/dist/esm/signal/store/sqlite.js +310 -0
  170. package/dist/esm/signal/types.js +1 -0
  171. package/dist/esm/store/contracts/appstate.store.js +1 -0
  172. package/dist/esm/store/contracts/auth.store.js +1 -0
  173. package/dist/esm/store/contracts/contact.store.js +1 -0
  174. package/dist/esm/store/contracts/device-list.store.js +1 -0
  175. package/dist/esm/store/contracts/message.store.js +1 -0
  176. package/dist/esm/store/contracts/participants.store.js +1 -0
  177. package/dist/esm/store/contracts/retry.store.js +1 -0
  178. package/dist/esm/store/contracts/sender-key.store.js +1 -0
  179. package/dist/esm/store/contracts/signal.store.js +1 -0
  180. package/dist/esm/store/contracts/thread.store.js +1 -0
  181. package/dist/esm/store/createStore.js +278 -0
  182. package/dist/esm/store/index.js +20 -0
  183. package/dist/esm/store/noop.store.js +43 -0
  184. package/dist/esm/store/providers/memory/appstate.store.js +101 -0
  185. package/dist/esm/store/providers/memory/contact.store.js +23 -0
  186. package/dist/esm/store/providers/memory/device-list.store.js +86 -0
  187. package/dist/esm/store/providers/memory/message.store.js +40 -0
  188. package/dist/esm/store/providers/memory/participants.store.js +61 -0
  189. package/dist/esm/store/providers/memory/retry.store.js +71 -0
  190. package/dist/esm/store/providers/memory/sender-key.store.js +88 -0
  191. package/dist/esm/store/providers/memory/signal.store.js +170 -0
  192. package/dist/esm/store/providers/memory/thread.store.js +34 -0
  193. package/dist/esm/store/providers/sqlite/BaseSqliteStore.js +37 -0
  194. package/dist/esm/store/providers/sqlite/appstate.store.js +169 -0
  195. package/dist/esm/store/providers/sqlite/auth.store.js +176 -0
  196. package/dist/esm/store/providers/sqlite/connection.js +240 -0
  197. package/dist/esm/store/providers/sqlite/contact.store.js +61 -0
  198. package/dist/esm/store/providers/sqlite/device-list.store.js +155 -0
  199. package/dist/esm/store/providers/sqlite/message.store.js +119 -0
  200. package/dist/esm/store/providers/sqlite/migrations.js +347 -0
  201. package/dist/esm/store/providers/sqlite/participants.store.js +85 -0
  202. package/dist/esm/store/providers/sqlite/retry.store.js +144 -0
  203. package/dist/esm/store/providers/sqlite/sender-key.store.js +203 -0
  204. package/dist/esm/store/providers/sqlite/signal.store.js +353 -0
  205. package/dist/esm/store/providers/sqlite/thread.store.js +72 -0
  206. package/dist/esm/store/types.js +1 -0
  207. package/dist/esm/transport/WaComms.js +527 -0
  208. package/dist/esm/transport/WaWebSocket.js +361 -0
  209. package/dist/esm/transport/binary/constants.js +96 -0
  210. package/dist/esm/transport/binary/decoder.js +275 -0
  211. package/dist/esm/transport/binary/encoder.js +210 -0
  212. package/dist/esm/transport/binary/index.js +4 -0
  213. package/dist/esm/transport/binary/tokens.js +1280 -0
  214. package/dist/esm/transport/index.js +6 -0
  215. package/dist/esm/transport/keepalive/WaKeepAlive.js +141 -0
  216. package/dist/esm/transport/node/WaNodeOrchestrator.js +143 -0
  217. package/dist/esm/transport/node/WaNodeTransport.js +64 -0
  218. package/dist/esm/transport/node/builders/accountSync.js +101 -0
  219. package/dist/esm/transport/node/builders/group.js +47 -0
  220. package/dist/esm/transport/node/builders/index.js +7 -0
  221. package/dist/esm/transport/node/builders/media.js +10 -0
  222. package/dist/esm/transport/node/builders/message.js +317 -0
  223. package/dist/esm/transport/node/builders/pairing.js +130 -0
  224. package/dist/esm/transport/node/builders/prekeys.js +102 -0
  225. package/dist/esm/transport/node/builders/retry.js +116 -0
  226. package/dist/esm/transport/node/helpers.js +37 -0
  227. package/dist/esm/transport/node/query.js +53 -0
  228. package/dist/esm/transport/node/xml.js +39 -0
  229. package/dist/esm/transport/noise/WaClientPayload.js +162 -0
  230. package/dist/esm/transport/noise/WaFrameCodec.js +121 -0
  231. package/dist/esm/transport/noise/WaNoiseCert.js +74 -0
  232. package/dist/esm/transport/noise/WaNoiseHandshake.js +57 -0
  233. package/dist/esm/transport/noise/WaNoiseSession.js +322 -0
  234. package/dist/esm/transport/noise/WaNoiseSocket.js +17 -0
  235. package/dist/esm/transport/noise/constants.js +8 -0
  236. package/dist/esm/transport/noise/types.js +1 -0
  237. package/dist/esm/transport/stream/parse.js +91 -0
  238. package/dist/esm/transport/types.js +1 -0
  239. package/dist/esm/util/async.js +5 -0
  240. package/dist/esm/util/base64.js +18 -0
  241. package/dist/esm/util/bytes.js +275 -0
  242. package/dist/esm/util/coercion.js +56 -0
  243. package/dist/esm/util/collections.js +27 -0
  244. package/dist/esm/util/primitives.js +32 -0
  245. package/dist/esm/util/runtime.js +15 -0
  246. package/dist/esm/util/signal-address.js +5 -0
  247. package/dist/index.js +52 -0
  248. package/dist/infra/log/ConsoleLogger.js +44 -0
  249. package/dist/infra/log/PinoLogger.js +111 -0
  250. package/dist/infra/log/types.js +2 -0
  251. package/dist/infra/perf/BoundedTaskQueue.js +67 -0
  252. package/dist/media/WaMediaCrypto.js +228 -0
  253. package/dist/media/WaMediaTransferClient.js +365 -0
  254. package/dist/media/conn.js +36 -0
  255. package/dist/media/constants.js +21 -0
  256. package/dist/media/index.js +9 -0
  257. package/dist/media/types.js +2 -0
  258. package/dist/message/WaMessageClient.js +214 -0
  259. package/dist/message/ack.js +52 -0
  260. package/dist/message/content.js +24 -0
  261. package/dist/message/device-sent.js +53 -0
  262. package/dist/message/incoming.js +321 -0
  263. package/dist/message/index.js +20 -0
  264. package/dist/message/padding.js +24 -0
  265. package/dist/message/phash.js +28 -0
  266. package/dist/message/types.js +2 -0
  267. package/dist/proto.js +5 -0
  268. package/dist/protocol/appstate.js +37 -0
  269. package/dist/protocol/auth.js +15 -0
  270. package/dist/protocol/browser.js +45 -0
  271. package/dist/protocol/constants.js +46 -0
  272. package/dist/protocol/defaults.js +30 -0
  273. package/dist/protocol/dirty.js +29 -0
  274. package/dist/protocol/group.js +8 -0
  275. package/dist/protocol/index.js +53 -0
  276. package/dist/protocol/jid.js +107 -0
  277. package/dist/protocol/media.js +24 -0
  278. package/dist/protocol/message.js +19 -0
  279. package/dist/protocol/nodes.js +86 -0
  280. package/dist/protocol/notification.js +53 -0
  281. package/dist/protocol/stream.js +63 -0
  282. package/dist/retry/constants.js +23 -0
  283. package/dist/retry/index.js +19 -0
  284. package/dist/retry/outbound.js +88 -0
  285. package/dist/retry/parse.js +133 -0
  286. package/dist/retry/reason.js +53 -0
  287. package/dist/retry/replay.js +181 -0
  288. package/dist/retry/types.js +2 -0
  289. package/dist/signal/api/SignalDeviceSyncApi.js +189 -0
  290. package/dist/signal/api/SignalDigestSyncApi.js +183 -0
  291. package/dist/signal/api/SignalIdentitySyncApi.js +115 -0
  292. package/dist/signal/api/SignalMissingPreKeysSyncApi.js +145 -0
  293. package/dist/signal/api/SignalRotateKeyApi.js +63 -0
  294. package/dist/signal/api/SignalSessionSyncApi.js +191 -0
  295. package/dist/signal/api/codec.js +27 -0
  296. package/dist/signal/api/constants.js +12 -0
  297. package/dist/signal/api/prekeys.js +16 -0
  298. package/dist/signal/constants.js +19 -0
  299. package/dist/signal/crypto/WaAdvSignature.js +72 -0
  300. package/dist/signal/crypto/constants.js +11 -0
  301. package/dist/signal/group/SenderKeyChain.js +101 -0
  302. package/dist/signal/group/SenderKeyCodec.js +50 -0
  303. package/dist/signal/group/SenderKeyManager.js +180 -0
  304. package/dist/signal/index.js +29 -0
  305. package/dist/signal/registration/keygen.js +37 -0
  306. package/dist/signal/registration/utils.js +19 -0
  307. package/dist/signal/session/SignalProtocol.js +126 -0
  308. package/dist/signal/session/SignalRatchet.js +268 -0
  309. package/dist/signal/session/SignalSerializer.js +69 -0
  310. package/dist/signal/session/SignalSession.js +165 -0
  311. package/dist/signal/store/sqlite.js +324 -0
  312. package/dist/signal/types.js +2 -0
  313. package/dist/store/contracts/appstate.store.js +2 -0
  314. package/dist/store/contracts/auth.store.js +2 -0
  315. package/dist/store/contracts/contact.store.js +2 -0
  316. package/dist/store/contracts/device-list.store.js +2 -0
  317. package/dist/store/contracts/message.store.js +2 -0
  318. package/dist/store/contracts/participants.store.js +2 -0
  319. package/dist/store/contracts/retry.store.js +2 -0
  320. package/dist/store/contracts/sender-key.store.js +2 -0
  321. package/dist/store/contracts/signal.store.js +2 -0
  322. package/dist/store/contracts/thread.store.js +2 -0
  323. package/dist/store/createStore.js +281 -0
  324. package/dist/store/index.js +43 -0
  325. package/dist/store/noop.store.js +46 -0
  326. package/dist/store/providers/memory/appstate.store.js +105 -0
  327. package/dist/store/providers/memory/contact.store.js +27 -0
  328. package/dist/store/providers/memory/device-list.store.js +90 -0
  329. package/dist/store/providers/memory/message.store.js +44 -0
  330. package/dist/store/providers/memory/participants.store.js +65 -0
  331. package/dist/store/providers/memory/retry.store.js +75 -0
  332. package/dist/store/providers/memory/sender-key.store.js +92 -0
  333. package/dist/store/providers/memory/signal.store.js +174 -0
  334. package/dist/store/providers/memory/thread.store.js +38 -0
  335. package/dist/store/providers/sqlite/BaseSqliteStore.js +41 -0
  336. package/dist/store/providers/sqlite/appstate.store.js +173 -0
  337. package/dist/store/providers/sqlite/auth.store.js +180 -0
  338. package/dist/store/providers/sqlite/connection.js +276 -0
  339. package/dist/store/providers/sqlite/contact.store.js +65 -0
  340. package/dist/store/providers/sqlite/device-list.store.js +159 -0
  341. package/dist/store/providers/sqlite/message.store.js +123 -0
  342. package/dist/store/providers/sqlite/migrations.js +350 -0
  343. package/dist/store/providers/sqlite/participants.store.js +89 -0
  344. package/dist/store/providers/sqlite/retry.store.js +148 -0
  345. package/dist/store/providers/sqlite/sender-key.store.js +207 -0
  346. package/dist/store/providers/sqlite/signal.store.js +357 -0
  347. package/dist/store/providers/sqlite/thread.store.js +76 -0
  348. package/dist/store/types.js +2 -0
  349. package/dist/transport/WaComms.js +531 -0
  350. package/dist/transport/WaWebSocket.js +365 -0
  351. package/dist/transport/binary/constants.js +99 -0
  352. package/dist/transport/binary/decoder.js +279 -0
  353. package/dist/transport/binary/encoder.js +214 -0
  354. package/dist/transport/binary/index.js +23 -0
  355. package/dist/transport/binary/tokens.js +1283 -0
  356. package/dist/transport/index.js +18 -0
  357. package/dist/transport/keepalive/WaKeepAlive.js +145 -0
  358. package/dist/transport/node/WaNodeOrchestrator.js +147 -0
  359. package/dist/transport/node/WaNodeTransport.js +68 -0
  360. package/dist/transport/node/builders/accountSync.js +110 -0
  361. package/dist/transport/node/builders/group.js +52 -0
  362. package/dist/transport/node/builders/index.js +39 -0
  363. package/dist/transport/node/builders/media.js +13 -0
  364. package/dist/transport/node/builders/message.js +328 -0
  365. package/dist/transport/node/builders/pairing.js +137 -0
  366. package/dist/transport/node/builders/prekeys.js +107 -0
  367. package/dist/transport/node/builders/retry.js +119 -0
  368. package/dist/transport/node/helpers.js +46 -0
  369. package/dist/transport/node/query.js +59 -0
  370. package/dist/transport/node/xml.js +42 -0
  371. package/dist/transport/noise/WaClientPayload.js +166 -0
  372. package/dist/transport/noise/WaFrameCodec.js +125 -0
  373. package/dist/transport/noise/WaNoiseCert.js +77 -0
  374. package/dist/transport/noise/WaNoiseHandshake.js +61 -0
  375. package/dist/transport/noise/WaNoiseSession.js +326 -0
  376. package/dist/transport/noise/WaNoiseSocket.js +21 -0
  377. package/dist/transport/noise/constants.js +11 -0
  378. package/dist/transport/noise/types.js +2 -0
  379. package/dist/transport/stream/parse.js +97 -0
  380. package/dist/transport/types.js +2 -0
  381. package/dist/types/appstate/WaAppStateCrypto.d.ts +59 -0
  382. package/dist/types/appstate/WaAppStateSyncClient.d.ts +63 -0
  383. package/dist/types/appstate/WaAppStateSyncResponseParser.d.ts +12 -0
  384. package/dist/types/appstate/constants.d.ts +14 -0
  385. package/dist/types/appstate/index.d.ts +7 -0
  386. package/dist/types/appstate/store/sqlite.d.ts +21 -0
  387. package/dist/types/appstate/types.d.ts +66 -0
  388. package/dist/types/appstate/utils.d.ts +10 -0
  389. package/dist/types/auth/WaAuthClient.d.ts +61 -0
  390. package/dist/types/auth/flow/WaAuthCredentialsFlow.d.ts +14 -0
  391. package/dist/types/auth/index.d.ts +6 -0
  392. package/dist/types/auth/pairing/WaPairingCodeCrypto.d.ts +17 -0
  393. package/dist/types/auth/pairing/WaPairingFlow.d.ts +48 -0
  394. package/dist/types/auth/pairing/WaQrFlow.d.ts +23 -0
  395. package/dist/types/auth/pairing/constants.d.ts +2 -0
  396. package/dist/types/auth/types.d.ts +48 -0
  397. package/dist/types/client/WaClient.d.ts +97 -0
  398. package/dist/types/client/WaClientFactory.d.ts +83 -0
  399. package/dist/types/client/coordinators/WaGroupCoordinator.d.ts +48 -0
  400. package/dist/types/client/coordinators/WaIncomingNodeCoordinator.d.ts +60 -0
  401. package/dist/types/client/coordinators/WaMessageDispatchCoordinator.d.ts +90 -0
  402. package/dist/types/client/coordinators/WaPassiveTasksCoordinator.d.ts +43 -0
  403. package/dist/types/client/coordinators/WaRetryCoordinator.d.ts +61 -0
  404. package/dist/types/client/coordinators/WaStreamControlCoordinator.d.ts +17 -0
  405. package/dist/types/client/dirty.d.ts +17 -0
  406. package/dist/types/client/events/chat.d.ts +3 -0
  407. package/dist/types/client/events/group.d.ts +7 -0
  408. package/dist/types/client/history-sync.d.ts +17 -0
  409. package/dist/types/client/incoming.d.ts +35 -0
  410. package/dist/types/client/index.d.ts +2 -0
  411. package/dist/types/client/mailbox.d.ts +12 -0
  412. package/dist/types/client/messages.d.ts +17 -0
  413. package/dist/types/client/types.d.ts +235 -0
  414. package/dist/types/crypto/core/constants.d.ts +1 -0
  415. package/dist/types/crypto/core/encoding.d.ts +11 -0
  416. package/dist/types/crypto/core/hkdf.d.ts +8 -0
  417. package/dist/types/crypto/core/index.d.ts +11 -0
  418. package/dist/types/crypto/core/keys.d.ts +20 -0
  419. package/dist/types/crypto/core/nonce.d.ts +5 -0
  420. package/dist/types/crypto/core/primitives.d.ts +25 -0
  421. package/dist/types/crypto/core/random.d.ts +8 -0
  422. package/dist/types/crypto/curves/Ed25519.d.ts +7 -0
  423. package/dist/types/crypto/curves/X25519.d.ts +8 -0
  424. package/dist/types/crypto/curves/constants.d.ts +2 -0
  425. package/dist/types/crypto/curves/types.d.ts +10 -0
  426. package/dist/types/crypto/index.d.ts +3 -0
  427. package/dist/types/crypto/math/constants.d.ts +7 -0
  428. package/dist/types/crypto/math/edwards.d.ts +3 -0
  429. package/dist/types/crypto/math/le.d.ts +2 -0
  430. package/dist/types/crypto/math/mod.d.ts +5 -0
  431. package/dist/types/crypto/math/types.d.ts +6 -0
  432. package/dist/types/index.d.ts +10 -0
  433. package/dist/types/infra/log/ConsoleLogger.d.ts +11 -0
  434. package/dist/types/infra/log/PinoLogger.d.ts +30 -0
  435. package/dist/types/infra/log/types.d.ts +9 -0
  436. package/dist/types/infra/perf/BoundedTaskQueue.d.ts +19 -0
  437. package/dist/types/media/WaMediaCrypto.d.ts +12 -0
  438. package/dist/types/media/WaMediaTransferClient.d.ts +81 -0
  439. package/dist/types/media/conn.d.ts +3 -0
  440. package/dist/types/media/constants.d.ts +10 -0
  441. package/dist/types/media/index.d.ts +4 -0
  442. package/dist/types/media/types.d.ts +56 -0
  443. package/dist/types/message/WaMessageClient.d.ts +29 -0
  444. package/dist/types/message/ack.d.ts +5 -0
  445. package/dist/types/message/content.d.ts +4 -0
  446. package/dist/types/message/device-sent.d.ts +3 -0
  447. package/dist/types/message/incoming.d.ts +18 -0
  448. package/dist/types/message/index.d.ts +2 -0
  449. package/dist/types/message/padding.d.ts +2 -0
  450. package/dist/types/message/phash.d.ts +1 -0
  451. package/dist/types/message/types.d.ts +58 -0
  452. package/dist/types/proto.d.ts +2 -0
  453. package/dist/types/protocol/appstate.d.ts +34 -0
  454. package/dist/types/protocol/auth.d.ts +12 -0
  455. package/dist/types/protocol/browser.d.ts +22 -0
  456. package/dist/types/protocol/constants.d.ts +11 -0
  457. package/dist/types/protocol/defaults.d.ts +26 -0
  458. package/dist/types/protocol/dirty.d.ts +15 -0
  459. package/dist/types/protocol/group.d.ts +6 -0
  460. package/dist/types/protocol/index.d.ts +11 -0
  461. package/dist/types/protocol/jid.d.ts +19 -0
  462. package/dist/types/protocol/media.d.ts +15 -0
  463. package/dist/types/protocol/message.d.ts +16 -0
  464. package/dist/types/protocol/nodes.d.ts +83 -0
  465. package/dist/types/protocol/notification.d.ts +50 -0
  466. package/dist/types/protocol/stream.d.ts +60 -0
  467. package/dist/types/retry/constants.d.ts +21 -0
  468. package/dist/types/retry/index.d.ts +7 -0
  469. package/dist/types/retry/outbound.d.ts +4 -0
  470. package/dist/types/retry/parse.d.ts +3 -0
  471. package/dist/types/retry/reason.d.ts +2 -0
  472. package/dist/types/retry/replay.d.ts +30 -0
  473. package/dist/types/retry/types.d.ts +70 -0
  474. package/dist/types/signal/api/SignalDeviceSyncApi.d.ts +31 -0
  475. package/dist/types/signal/api/SignalDigestSyncApi.d.ts +27 -0
  476. package/dist/types/signal/api/SignalIdentitySyncApi.d.ts +26 -0
  477. package/dist/types/signal/api/SignalMissingPreKeysSyncApi.d.ts +39 -0
  478. package/dist/types/signal/api/SignalRotateKeyApi.d.ts +22 -0
  479. package/dist/types/signal/api/SignalSessionSyncApi.d.ts +38 -0
  480. package/dist/types/signal/api/codec.d.ts +3 -0
  481. package/dist/types/signal/api/constants.d.ts +9 -0
  482. package/dist/types/signal/api/prekeys.d.ts +6 -0
  483. package/dist/types/signal/constants.d.ts +14 -0
  484. package/dist/types/signal/crypto/WaAdvSignature.d.ts +7 -0
  485. package/dist/types/signal/crypto/constants.d.ts +5 -0
  486. package/dist/types/signal/group/SenderKeyChain.d.ts +11 -0
  487. package/dist/types/signal/group/SenderKeyCodec.d.ts +14 -0
  488. package/dist/types/signal/group/SenderKeyManager.d.ts +22 -0
  489. package/dist/types/signal/index.d.ts +12 -0
  490. package/dist/types/signal/registration/keygen.d.ts +5 -0
  491. package/dist/types/signal/registration/utils.d.ts +9 -0
  492. package/dist/types/signal/session/SignalProtocol.d.ts +22 -0
  493. package/dist/types/signal/session/SignalRatchet.d.ts +25 -0
  494. package/dist/types/signal/session/SignalSerializer.d.ts +6 -0
  495. package/dist/types/signal/session/SignalSession.d.ts +43 -0
  496. package/dist/types/signal/store/sqlite.d.ts +72 -0
  497. package/dist/types/signal/types.d.ts +110 -0
  498. package/dist/types/store/contracts/appstate.store.d.ts +22 -0
  499. package/dist/types/store/contracts/auth.store.d.ts +6 -0
  500. package/dist/types/store/contracts/contact.store.d.ts +14 -0
  501. package/dist/types/store/contracts/device-list.store.d.ts +16 -0
  502. package/dist/types/store/contracts/message.store.d.ts +18 -0
  503. package/dist/types/store/contracts/participants.store.d.ts +14 -0
  504. package/dist/types/store/contracts/retry.store.d.ts +11 -0
  505. package/dist/types/store/contracts/sender-key.store.d.ts +16 -0
  506. package/dist/types/store/contracts/signal.store.d.ts +31 -0
  507. package/dist/types/store/contracts/thread.store.d.ts +17 -0
  508. package/dist/types/store/createStore.d.ts +2 -0
  509. package/dist/types/store/index.d.ts +31 -0
  510. package/dist/types/store/noop.store.d.ts +10 -0
  511. package/dist/types/store/providers/memory/appstate.store.d.ts +21 -0
  512. package/dist/types/store/providers/memory/contact.store.d.ts +13 -0
  513. package/dist/types/store/providers/memory/device-list.store.d.ts +20 -0
  514. package/dist/types/store/providers/memory/message.store.d.ts +14 -0
  515. package/dist/types/store/providers/memory/participants.store.d.ts +18 -0
  516. package/dist/types/store/providers/memory/retry.store.d.ts +18 -0
  517. package/dist/types/store/providers/memory/sender-key.store.d.ts +28 -0
  518. package/dist/types/store/providers/memory/signal.store.d.ts +51 -0
  519. package/dist/types/store/providers/memory/thread.store.d.ts +14 -0
  520. package/dist/types/store/providers/sqlite/BaseSqliteStore.d.ts +12 -0
  521. package/dist/types/store/providers/sqlite/appstate.store.d.ts +15 -0
  522. package/dist/types/store/providers/sqlite/auth.store.d.ts +10 -0
  523. package/dist/types/store/providers/sqlite/connection.d.ts +10 -0
  524. package/dist/types/store/providers/sqlite/contact.store.d.ts +10 -0
  525. package/dist/types/store/providers/sqlite/device-list.store.d.ts +18 -0
  526. package/dist/types/store/providers/sqlite/message.store.d.ts +11 -0
  527. package/dist/types/store/providers/sqlite/migrations.d.ts +3 -0
  528. package/dist/types/store/providers/sqlite/participants.store.d.ts +13 -0
  529. package/dist/types/store/providers/sqlite/retry.store.d.ts +16 -0
  530. package/dist/types/store/providers/sqlite/sender-key.store.d.ts +25 -0
  531. package/dist/types/store/providers/sqlite/signal.store.d.ts +46 -0
  532. package/dist/types/store/providers/sqlite/thread.store.d.ts +11 -0
  533. package/dist/types/store/types.d.ts +103 -0
  534. package/dist/types/transport/WaComms.d.ts +61 -0
  535. package/dist/types/transport/WaWebSocket.d.ts +36 -0
  536. package/dist/types/transport/binary/constants.d.ts +49 -0
  537. package/dist/types/transport/binary/decoder.d.ts +3 -0
  538. package/dist/types/transport/binary/encoder.d.ts +3 -0
  539. package/dist/types/transport/binary/index.d.ts +4 -0
  540. package/dist/types/transport/binary/tokens.d.ts +11 -0
  541. package/dist/types/transport/index.d.ts +7 -0
  542. package/dist/types/transport/keepalive/WaKeepAlive.d.ts +39 -0
  543. package/dist/types/transport/node/WaNodeOrchestrator.d.ts +28 -0
  544. package/dist/types/transport/node/WaNodeTransport.d.ts +22 -0
  545. package/dist/types/transport/node/builders/accountSync.d.ts +11 -0
  546. package/dist/types/transport/node/builders/group.d.ts +16 -0
  547. package/dist/types/transport/node/builders/index.d.ts +7 -0
  548. package/dist/types/transport/node/builders/media.d.ts +2 -0
  549. package/dist/types/transport/node/builders/message.d.ts +52 -0
  550. package/dist/types/transport/node/builders/pairing.d.ts +18 -0
  551. package/dist/types/transport/node/builders/prekeys.d.ts +5 -0
  552. package/dist/types/transport/node/builders/retry.d.ts +18 -0
  553. package/dist/types/transport/node/helpers.d.ts +8 -0
  554. package/dist/types/transport/node/query.d.ts +10 -0
  555. package/dist/types/transport/node/xml.d.ts +2 -0
  556. package/dist/types/transport/noise/WaClientPayload.d.ts +3 -0
  557. package/dist/types/transport/noise/WaFrameCodec.d.ts +9 -0
  558. package/dist/types/transport/noise/WaNoiseCert.d.ts +1 -0
  559. package/dist/types/transport/noise/WaNoiseHandshake.d.ts +14 -0
  560. package/dist/types/transport/noise/WaNoiseSession.d.ts +33 -0
  561. package/dist/types/transport/noise/WaNoiseSocket.d.ts +10 -0
  562. package/dist/types/transport/noise/constants.d.ts +7 -0
  563. package/dist/types/transport/noise/types.d.ts +23 -0
  564. package/dist/types/transport/stream/parse.d.ts +23 -0
  565. package/dist/types/transport/types.d.ts +71 -0
  566. package/dist/types/util/async.d.ts +1 -0
  567. package/dist/types/util/base64.d.ts +4 -0
  568. package/dist/types/util/bytes.d.ts +28 -0
  569. package/dist/types/util/coercion.d.ts +8 -0
  570. package/dist/types/util/collections.d.ts +3 -0
  571. package/dist/types/util/primitives.d.ts +7 -0
  572. package/dist/types/util/runtime.d.ts +2 -0
  573. package/dist/types/util/signal-address.d.ts +2 -0
  574. package/dist/util/async.js +8 -0
  575. package/dist/util/base64.js +24 -0
  576. package/dist/util/bytes.js +291 -0
  577. package/dist/util/coercion.js +66 -0
  578. package/dist/util/collections.js +32 -0
  579. package/dist/util/primitives.js +37 -0
  580. package/dist/util/runtime.js +19 -0
  581. package/dist/util/signal-address.js +8 -0
  582. package/package.json +150 -0
  583. package/proto/index.d.ts +10861 -0
  584. package/proto/index.js +1 -0
  585. package/scripts/check-node-version.cjs +55 -0
@@ -0,0 +1,1057 @@
1
+ import { toSerializedPubKey } from '../../crypto/core/keys.js';
2
+ import { resolveMessageTypeAttr } from '../../message/content.js';
3
+ import { wrapDeviceSentMessage } from '../../message/device-sent.js';
4
+ import { writeRandomPadMax16 } from '../../message/padding.js';
5
+ import { computePhashV2 } from '../../message/phash.js';
6
+ import { proto } from '../../proto.js';
7
+ 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';
11
+ 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';
14
+ import { toError } from '../../util/primitives.js';
15
+ import { signalAddressKey } from '../../util/signal-address.js';
16
+ export class WaMessageDispatchCoordinator {
17
+ constructor(options) {
18
+ this.logger = options.logger;
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;
23
+ this.buildMessageContent = options.buildMessageContent;
24
+ this.queryGroupParticipantJids = options.queryGroupParticipantJids;
25
+ this.senderKeyManager = options.senderKeyManager;
26
+ 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
+ this.getCurrentMeJid = options.getCurrentMeJid;
32
+ this.getCurrentMeLid = options.getCurrentMeLid;
33
+ this.getCurrentSignedIdentity = options.getCurrentSignedIdentity;
34
+ }
35
+ async publishMessageNode(node, options = {}) {
36
+ this.logger.debug('wa client publish message node', {
37
+ tag: node.tag,
38
+ type: node.attrs.type,
39
+ to: node.attrs.to
40
+ });
41
+ const messageType = node.attrs.type ?? 'text';
42
+ const replayPayload = {
43
+ mode: 'opaque_node',
44
+ node: encodeBinaryNode(node)
45
+ };
46
+ return this.publishWithRetryTracking({
47
+ messageIdHint: node.attrs.id,
48
+ toJid: node.attrs.to,
49
+ participantJid: node.attrs.participant,
50
+ recipientJid: node.attrs.recipient,
51
+ messageType,
52
+ replayPayload
53
+ }, async () => this.messageClient.publishNode(node, options));
54
+ }
55
+ async publishEncryptedMessage(input, options = {}) {
56
+ this.logger.debug('wa client publish encrypted message', {
57
+ to: input.to,
58
+ type: input.type,
59
+ encType: input.encType
60
+ });
61
+ const replayPayload = {
62
+ mode: 'encrypted',
63
+ to: input.to,
64
+ type: input.type ?? 'text',
65
+ encType: input.encType,
66
+ ciphertext: input.ciphertext,
67
+ participant: input.participant
68
+ };
69
+ return this.publishWithRetryTracking({
70
+ messageIdHint: input.id,
71
+ toJid: input.to,
72
+ participantJid: input.participant,
73
+ messageType: input.type ?? 'text',
74
+ replayPayload
75
+ }, async () => this.messageClient.publishEncrypted(input, options));
76
+ }
77
+ async publishSignalMessage(input, options = {}) {
78
+ this.requireCurrentMeJid('publishSignalMessage');
79
+ const address = parseSignalAddressFromJid(input.to);
80
+ if (address.server === WA_DEFAULTS.GROUP_SERVER) {
81
+ throw new Error('publishSignalMessage currently supports only direct chats; use sender-key flow for groups');
82
+ }
83
+ this.logger.debug('wa client publish signal message', {
84
+ to: input.to,
85
+ type: input.type
86
+ });
87
+ const [paddedPlaintext] = await Promise.all([
88
+ writeRandomPadMax16(input.plaintext),
89
+ this.ensureSignalSession(address, input.to, input.expectedIdentity)
90
+ ]);
91
+ const encrypted = await this.signalProtocol.encryptMessage(address, paddedPlaintext, input.expectedIdentity);
92
+ const messageType = input.type ?? 'text';
93
+ const replayPayload = {
94
+ mode: 'plaintext',
95
+ to: input.to,
96
+ type: messageType,
97
+ plaintext: paddedPlaintext
98
+ };
99
+ return this.publishWithRetryTracking({
100
+ messageIdHint: input.id,
101
+ toJid: input.to,
102
+ participantJid: input.participant,
103
+ messageType,
104
+ replayPayload
105
+ }, async () => this.messageClient.publishEncrypted({
106
+ to: input.to,
107
+ encType: encrypted.type,
108
+ ciphertext: encrypted.ciphertext,
109
+ id: input.id,
110
+ type: input.type,
111
+ category: input.category,
112
+ pushPriority: input.pushPriority,
113
+ participant: input.participant,
114
+ deviceFanout: input.deviceFanout
115
+ }, options));
116
+ }
117
+ async sendMessage(to, content, options = {}) {
118
+ 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
+ }
128
+ const directRecipientJid = toUserJid(recipientJid);
129
+ return this.publishDirectSignalMessageWithFanout(directRecipientJid, message, plaintext, type, options);
130
+ }
131
+ async syncSignalSession(jid, reasonIdentity = false) {
132
+ const address = parseSignalAddressFromJid(jid);
133
+ if (address.server === WA_DEFAULTS.GROUP_SERVER) {
134
+ throw new Error('syncSignalSession supports only direct chats');
135
+ }
136
+ await this.ensureSignalSession(address, jid, undefined, reasonIdentity);
137
+ }
138
+ async sendReceipt(input) {
139
+ await this.messageClient.sendReceipt(input);
140
+ }
141
+ 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;
168
+ }
169
+ 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
+ });
202
+ }
203
+ 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;
399
+ }
400
+ shouldUseGroupDirectPath(message) {
401
+ const protocolType = message.protocolMessage?.type;
402
+ if (protocolType === proto.Message.ProtocolMessage.Type.REVOKE ||
403
+ protocolType === proto.Message.ProtocolMessage.Type.MESSAGE_EDIT) {
404
+ return true;
405
+ }
406
+ return message.keepInChatMessage?.keepType === proto.KeepType.UNDO_KEEP_FOR_ALL;
407
+ }
408
+ async publishGroupDirectMessage(groupJid, plaintext, type, options, retryContext = {}) {
409
+ const meJid = this.requireCurrentMeJid('sendMessage');
410
+ const participantUserJids = retryContext.forceRefreshParticipants
411
+ ? await this.refreshGroupParticipantUsers(groupJid)
412
+ : await this.resolveGroupParticipantUsers(groupJid);
413
+ const addressingMode = retryContext.forceAddressingMode ??
414
+ this.resolveGroupAddressingMode(participantUserJids, groupJid);
415
+ const senderForPhash = this.resolveSenderForAddressingMode(addressingMode, meJid);
416
+ const fanoutDeviceJids = await this.resolveGroupParticipantDeviceJids(participantUserJids);
417
+ if (fanoutDeviceJids.length === 0) {
418
+ throw new Error('group direct send resolved no target devices');
419
+ }
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
+ };
430
+ }));
431
+ const shouldAttachDeviceIdentity = participants.some((participant) => participant.encType === 'pkmsg');
432
+ const localPhash = await computePhashV2([...fanoutDeviceJids, senderForPhash]);
433
+ const messageNode = buildGroupDirectMessageNode({
434
+ to: groupJid,
435
+ type,
436
+ id: options.id,
437
+ phash: localPhash,
438
+ addressingMode,
439
+ participants,
440
+ deviceIdentity: shouldAttachDeviceIdentity
441
+ ? this.getEncodedSignedDeviceIdentity()
442
+ : undefined
443
+ });
444
+ const replayPayload = {
445
+ mode: 'plaintext',
446
+ to: groupJid,
447
+ type,
448
+ plaintext
449
+ };
450
+ const result = await this.publishWithRetryTracking({
451
+ messageIdHint: options.id ?? messageNode.attrs.id,
452
+ toJid: groupJid,
453
+ messageType: type,
454
+ replayPayload
455
+ }, async () => this.messageClient.publishNode(messageNode, options));
456
+ const ackError = result.ack.error;
457
+ const serverPhash = result.ack.phash;
458
+ const serverAddressingMode = result.ack.addressingMode;
459
+ const hasPhashMismatch = !!serverPhash && serverPhash !== localPhash;
460
+ const hasAddressingMismatch = !!serverAddressingMode && serverAddressingMode !== addressingMode;
461
+ const hasAddressingError = ackError === 421;
462
+ if (!retryContext.retried &&
463
+ (hasPhashMismatch || hasAddressingMismatch || hasAddressingError)) {
464
+ this.logger.warn('group direct publish acknowledged with mismatch metadata', {
465
+ id: result.id,
466
+ groupJid,
467
+ localPhash,
468
+ serverPhash,
469
+ localAddressingMode: addressingMode,
470
+ serverAddressingMode,
471
+ ackError
472
+ });
473
+ return this.publishGroupDirectMessage(groupJid, plaintext, type, {
474
+ ...options,
475
+ id: result.id
476
+ }, {
477
+ retried: true,
478
+ forceRefreshParticipants: true,
479
+ forceAddressingMode: serverAddressingMode
480
+ });
481
+ }
482
+ return result;
483
+ }
484
+ async publishGroupSenderKeyMessage(groupJid, plaintext, type, options, retryContext = {}) {
485
+ const meJid = this.requireCurrentMeJid('sendMessage');
486
+ const participantUserJids = retryContext.forceRefreshParticipants
487
+ ? await this.refreshGroupParticipantUsers(groupJid)
488
+ : await this.resolveGroupParticipantUsers(groupJid);
489
+ const addressingMode = retryContext.forceAddressingMode ??
490
+ this.resolveGroupAddressingMode(participantUserJids, groupJid);
491
+ const senderJid = this.resolveSenderForAddressingMode(addressingMode, meJid);
492
+ 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);
496
+ const { fanoutDeviceJids, distributionParticipants } = distributionData;
497
+ const shouldAttachDeviceIdentity = distributionParticipants.some((participant) => participant.encType === 'pkmsg');
498
+ const localPhash = await computePhashV2([...fanoutDeviceJids, senderJid]);
499
+ const messageNode = buildGroupSenderKeyMessageNode({
500
+ to: groupJid,
501
+ type,
502
+ id: options.id,
503
+ phash: localPhash,
504
+ addressingMode,
505
+ groupCiphertext: groupCiphertext.ciphertext,
506
+ participants: distributionParticipants,
507
+ deviceIdentity: shouldAttachDeviceIdentity
508
+ ? this.getEncodedSignedDeviceIdentity()
509
+ : undefined
510
+ });
511
+ const replayPayload = {
512
+ mode: 'plaintext',
513
+ to: groupJid,
514
+ type,
515
+ plaintext
516
+ };
517
+ const result = await this.publishWithRetryTracking({
518
+ messageIdHint: options.id ?? messageNode.attrs.id,
519
+ toJid: groupJid,
520
+ messageType: type,
521
+ replayPayload
522
+ }, async () => this.messageClient.publishNode(messageNode, options));
523
+ const distributedAddresses = distributionParticipants.map((participant) => participant.address);
524
+ try {
525
+ await this.senderKeyManager.markSenderKeyDistributed(groupJid, sender, distributedAddresses);
526
+ }
527
+ catch (error) {
528
+ this.logger.warn('failed to mark sender key distribution targets', {
529
+ groupJid,
530
+ participants: distributedAddresses.length,
531
+ message: toError(error).message
532
+ });
533
+ }
534
+ const ackError = result.ack.error;
535
+ const serverPhash = result.ack.phash;
536
+ const serverAddressingMode = result.ack.addressingMode;
537
+ const hasPhashMismatch = !!serverPhash && serverPhash !== localPhash;
538
+ const hasAddressingMismatch = !!serverAddressingMode && serverAddressingMode !== addressingMode;
539
+ const hasAddressingError = ackError === 421;
540
+ if (!retryContext.retried &&
541
+ (hasPhashMismatch || hasAddressingMismatch || hasAddressingError)) {
542
+ this.logger.warn('group message publish acknowledged with mismatch metadata', {
543
+ id: result.id,
544
+ groupJid,
545
+ localPhash,
546
+ serverPhash,
547
+ localAddressingMode: addressingMode,
548
+ serverAddressingMode,
549
+ ackError
550
+ });
551
+ return this.publishGroupSenderKeyMessage(groupJid, plaintext, type, {
552
+ ...options,
553
+ id: result.id
554
+ }, {
555
+ retried: true,
556
+ forceRefreshParticipants: true,
557
+ forceAddressingMode: serverAddressingMode
558
+ });
559
+ }
560
+ return result;
561
+ }
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
+ resolveGroupAddressingMode(participantUserJids, groupJid) {
694
+ for (const participantJid of participantUserJids) {
695
+ try {
696
+ if (splitJid(participantJid).server === 'lid') {
697
+ return 'lid';
698
+ }
699
+ }
700
+ catch (error) {
701
+ this.logger.trace('ignoring malformed participant jid in addressing mode resolution', { participantJid, message: toError(error).message });
702
+ }
703
+ }
704
+ this.logger.trace('group addressing mode resolved to pn (default)', {
705
+ groupJid,
706
+ participants: participantUserJids.length
707
+ });
708
+ return 'pn';
709
+ }
710
+ resolveSenderForAddressingMode(addressingMode, meJid) {
711
+ if (addressingMode === 'lid') {
712
+ const meLid = this.getCurrentMeLid();
713
+ if (meLid && meLid.includes('@')) {
714
+ try {
715
+ return normalizeDeviceJid(meLid);
716
+ }
717
+ catch (error) {
718
+ this.logger.trace('ignoring malformed me lid jid', {
719
+ meLid,
720
+ message: toError(error).message
721
+ });
722
+ }
723
+ }
724
+ }
725
+ return normalizeDeviceJid(meJid);
726
+ }
727
+ async encryptGroupDistributionParticipants(groupJid, sender, senderKeyDistributionMessage, participantUserJids) {
728
+ const distributionPayload = await writeRandomPadMax16(proto.Message.encode({
729
+ senderKeyDistributionMessage
730
+ }).finish());
731
+ const fanoutDeviceJids = await this.resolveGroupParticipantDeviceJids(participantUserJids);
732
+ if (fanoutDeviceJids.length === 0) {
733
+ return {
734
+ fanoutDeviceJids,
735
+ distributionParticipants: []
736
+ };
737
+ }
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));
743
+ if (pendingAddresses.length === 0) {
744
+ return {
745
+ fanoutDeviceJids,
746
+ distributionParticipants: []
747
+ };
748
+ }
749
+ const pendingAddressKeys = new Set(pendingAddresses.map(signalAddressKey));
750
+ const pendingTargets = fanoutTargets.filter((target) => pendingAddressKeys.has(signalAddressKey(target.address)));
751
+ if (pendingTargets.length === 0) {
752
+ return {
753
+ fanoutDeviceJids,
754
+ distributionParticipants: []
755
+ };
756
+ }
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 [];
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) {
832
+ const meJid = this.requireCurrentMeJid('sendMessage');
833
+ const meLid = this.getCurrentMeLid();
834
+ const selfDeviceJidForRecipient = this.resolveSelfDeviceJidForRecipient(recipientJid, meJid, meLid);
835
+ const deviceJids = await this.resolveDirectFanoutDeviceJids(recipientJid, selfDeviceJidForRecipient);
836
+ const recipientUserJid = toUserJid(recipientJid);
837
+ const meUserJid = toUserJid(selfDeviceJidForRecipient);
838
+ this.logger.debug('wa client publish signal fanout', {
839
+ to: recipientJid,
840
+ devices: deviceJids.length,
841
+ type
842
+ });
843
+ 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);
849
+ }
850
+ }
851
+ }
852
+ await this.ensureSignalSessionsBatch(deviceJids, expectedIdentityByJid);
853
+ const hasSelfDeviceFanout = deviceJids.some((targetJid) => toUserJid(targetJid) === meUserJid);
854
+ const selfDevicePlaintext = hasSelfDeviceFanout
855
+ ? await writeRandomPadMax16(proto.Message.encode(wrapDeviceSentMessage(message, recipientUserJid)).finish())
856
+ : 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
870
+ };
871
+ }));
872
+ const shouldAttachDeviceIdentity = participants.some((participant) => participant.encType === 'pkmsg');
873
+ const deviceIdentity = shouldAttachDeviceIdentity
874
+ ? this.getEncodedSignedDeviceIdentity()
875
+ : undefined;
876
+ const messageNode = buildDirectMessageFanoutNode({
877
+ to: recipientJid,
878
+ type,
879
+ id: options.id,
880
+ participants,
881
+ deviceIdentity
882
+ });
883
+ const replayPayload = {
884
+ mode: 'plaintext',
885
+ to: recipientJid,
886
+ type,
887
+ plaintext
888
+ };
889
+ return this.publishWithRetryTracking({
890
+ messageIdHint: options.id ?? messageNode.attrs.id,
891
+ toJid: recipientJid,
892
+ messageType: type,
893
+ replayPayload
894
+ }, async () => this.messageClient.publishNode(messageNode, options));
895
+ }
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;
909
+ }
910
+ 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
+ }
951
+ }
952
+ 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
960
+ });
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
+ }
965
+ }
966
+ }
967
+ async resolveDirectFanoutDeviceJids(recipientJid, selfDeviceJidForRecipient) {
968
+ const recipientUserJid = toUserJid(recipientJid);
969
+ const meUserJid = toUserJid(selfDeviceJidForRecipient);
970
+ const targets = recipientUserJid === meUserJid ? [recipientUserJid] : [recipientUserJid, meUserJid];
971
+ 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];
994
+ }
995
+ 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
1000
+ });
1001
+ return [recipientUserJid];
1002
+ }
1003
+ }
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
+ getEncodedSignedDeviceIdentity() {
1014
+ const signedIdentity = this.getCurrentSignedIdentity();
1015
+ if (!signedIdentity) {
1016
+ throw new Error('missing signed identity for pkmsg fanout');
1017
+ }
1018
+ return proto.ADVSignedDeviceIdentity.encode(signedIdentity).finish();
1019
+ }
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
+ requireCurrentMeJid(context) {
1051
+ const meJid = this.getCurrentMeJid();
1052
+ if (meJid) {
1053
+ return meJid;
1054
+ }
1055
+ throw new Error(`${context} requires registered meJid`);
1056
+ }
1057
+ }