zapo-js 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (468) hide show
  1. package/README.md +12 -4
  2. package/dist/appstate/WaAppStateCrypto.js +1 -1
  3. package/dist/appstate/WaAppStateSyncClient.js +138 -93
  4. package/dist/appstate/{store/sqlite.js → encoding.js} +13 -8
  5. package/dist/appstate/index.js +8 -6
  6. package/dist/appstate/utils.js +0 -5
  7. package/dist/auth/WaAuthClient.js +36 -47
  8. package/dist/auth/flow/WaAuthCredentialsFlow.js +7 -7
  9. package/dist/auth/index.js +1 -6
  10. package/dist/auth/pairing/WaPairingCodeCrypto.js +6 -4
  11. package/dist/auth/pairing/WaPairingFlow.js +13 -3
  12. package/dist/client/WaClient.js +225 -101
  13. package/dist/client/WaClientFactory.js +294 -44
  14. package/dist/client/connection/WaConnectionManager.js +19 -10
  15. package/dist/client/coordinators/WaBusinessCoordinator.js +241 -0
  16. package/dist/client/coordinators/WaGroupCoordinator.js +11 -7
  17. package/dist/client/coordinators/WaIncomingNodeCoordinator.js +1 -0
  18. package/dist/client/coordinators/WaMessageDispatchCoordinator.js +292 -99
  19. package/dist/client/coordinators/WaPassiveTasksCoordinator.js +74 -31
  20. package/dist/client/coordinators/WaPrivacyCoordinator.js +134 -0
  21. package/dist/client/coordinators/WaProfileCoordinator.js +212 -0
  22. package/dist/client/coordinators/WaRetryCoordinator.js +179 -27
  23. package/dist/client/coordinators/WaStreamControlCoordinator.js +18 -11
  24. package/dist/client/coordinators/WaTrustedContactTokenCoordinator.js +166 -0
  25. package/dist/client/dirty.js +40 -20
  26. package/dist/client/events/devices.js +72 -0
  27. package/dist/client/events/group.js +3 -11
  28. package/dist/client/events/identity.js +22 -0
  29. package/dist/client/events/privacy-token.js +39 -0
  30. package/dist/client/history-sync.js +50 -9
  31. package/dist/client/incoming.js +37 -7
  32. package/dist/client/mailbox.js +24 -23
  33. package/dist/client/messages.js +107 -31
  34. package/dist/client/messaging/fanout.js +21 -11
  35. package/dist/client/messaging/participants.js +6 -4
  36. package/dist/client/persistence/WriteBehindPersistence.js +129 -0
  37. package/dist/client/tokens/cs-token.js +50 -0
  38. package/dist/client/tokens/tc-token.js +25 -0
  39. package/dist/crypto/core/index.js +2 -2
  40. package/dist/crypto/core/keys.js +4 -4
  41. package/dist/crypto/core/nonce.js +2 -0
  42. package/dist/crypto/core/primitives.js +0 -8
  43. package/dist/crypto/core/random.js +22 -0
  44. package/dist/crypto/curves/X25519.js +25 -6
  45. package/dist/crypto/index.js +3 -0
  46. package/dist/crypto/math/constants.js +13 -36
  47. package/dist/crypto/math/edwards.js +171 -44
  48. package/dist/crypto/math/fe.js +706 -0
  49. package/dist/crypto/math/mod.js +10 -3
  50. package/dist/esm/appstate/WaAppStateCrypto.js +1 -1
  51. package/dist/esm/appstate/WaAppStateSyncClient.js +138 -93
  52. package/dist/esm/appstate/{store/sqlite.js → encoding.js} +13 -8
  53. package/dist/esm/appstate/index.js +2 -2
  54. package/dist/esm/appstate/utils.js +2 -5
  55. package/dist/esm/auth/WaAuthClient.js +36 -47
  56. package/dist/esm/auth/flow/WaAuthCredentialsFlow.js +7 -7
  57. package/dist/esm/auth/index.js +0 -2
  58. package/dist/esm/auth/pairing/WaPairingCodeCrypto.js +6 -4
  59. package/dist/esm/auth/pairing/WaPairingFlow.js +14 -4
  60. package/dist/esm/client/WaClient.js +225 -101
  61. package/dist/esm/client/WaClientFactory.js +295 -45
  62. package/dist/esm/client/connection/WaConnectionManager.js +19 -10
  63. package/dist/esm/client/coordinators/WaBusinessCoordinator.js +238 -0
  64. package/dist/esm/client/coordinators/WaGroupCoordinator.js +11 -7
  65. package/dist/esm/client/coordinators/WaIncomingNodeCoordinator.js +1 -0
  66. package/dist/esm/client/coordinators/WaMessageDispatchCoordinator.js +295 -102
  67. package/dist/esm/client/coordinators/WaPassiveTasksCoordinator.js +74 -31
  68. package/dist/esm/client/coordinators/WaPrivacyCoordinator.js +131 -0
  69. package/dist/esm/client/coordinators/WaProfileCoordinator.js +209 -0
  70. package/dist/esm/client/coordinators/WaRetryCoordinator.js +181 -29
  71. package/dist/esm/client/coordinators/WaStreamControlCoordinator.js +19 -12
  72. package/dist/esm/client/coordinators/WaTrustedContactTokenCoordinator.js +162 -0
  73. package/dist/esm/client/dirty.js +40 -20
  74. package/dist/esm/client/events/devices.js +68 -0
  75. package/dist/esm/client/events/group.js +3 -11
  76. package/dist/esm/client/events/identity.js +19 -0
  77. package/dist/esm/client/events/privacy-token.js +36 -0
  78. package/dist/esm/client/history-sync.js +50 -9
  79. package/dist/esm/client/incoming.js +38 -8
  80. package/dist/esm/client/mailbox.js +24 -23
  81. package/dist/esm/client/messages.js +108 -32
  82. package/dist/esm/client/messaging/fanout.js +22 -12
  83. package/dist/esm/client/messaging/participants.js +6 -4
  84. package/dist/esm/client/persistence/WriteBehindPersistence.js +125 -0
  85. package/dist/esm/client/tokens/cs-token.js +46 -0
  86. package/dist/esm/client/tokens/tc-token.js +18 -0
  87. package/dist/esm/crypto/core/index.js +2 -2
  88. package/dist/esm/crypto/core/keys.js +1 -1
  89. package/dist/esm/crypto/core/nonce.js +2 -0
  90. package/dist/esm/crypto/core/primitives.js +0 -7
  91. package/dist/esm/crypto/core/random.js +22 -1
  92. package/dist/esm/crypto/curves/X25519.js +25 -6
  93. package/dist/esm/crypto/index.js +1 -0
  94. package/dist/esm/crypto/math/constants.js +12 -35
  95. package/dist/esm/crypto/math/edwards.js +174 -47
  96. package/dist/esm/crypto/math/fe.js +691 -0
  97. package/dist/esm/crypto/math/mod.js +10 -1
  98. package/dist/esm/index.js +1 -1
  99. package/dist/esm/infra/perf/BackgroundQueue.js +478 -0
  100. package/dist/esm/infra/perf/BoundedTaskQueue.js +3 -1
  101. package/dist/esm/infra/perf/PromiseDedup.js +20 -0
  102. package/dist/esm/infra/perf/SharedExclusiveGate.js +109 -0
  103. package/dist/esm/infra/perf/StoreLock.js +77 -0
  104. package/dist/esm/media/WaMediaCrypto.js +95 -13
  105. package/dist/esm/media/WaMediaTransferClient.js +39 -47
  106. package/dist/esm/media/constants.js +2 -1
  107. package/dist/esm/message/WaMessageClient.js +26 -19
  108. package/dist/esm/message/content.js +195 -9
  109. package/dist/esm/message/icdc.js +76 -0
  110. package/dist/esm/message/incoming.js +24 -12
  111. package/dist/esm/message/phash.js +3 -1
  112. package/dist/esm/message/reporting-token.js +14 -27
  113. package/dist/esm/protocol/appstate.js +9 -40
  114. package/dist/esm/protocol/browser.js +10 -18
  115. package/dist/esm/protocol/constants.js +5 -3
  116. package/dist/esm/protocol/defaults.js +6 -0
  117. package/dist/esm/protocol/index.js +1 -2
  118. package/dist/esm/protocol/jid.js +105 -36
  119. package/dist/esm/protocol/message.js +61 -1
  120. package/dist/esm/protocol/nodes.js +2 -0
  121. package/dist/esm/protocol/notification.js +3 -1
  122. package/dist/esm/protocol/privacy-token.js +17 -0
  123. package/dist/esm/protocol/privacy.js +55 -0
  124. package/dist/esm/protocol/stream.js +26 -1
  125. package/dist/esm/retry/codec.js +216 -0
  126. package/dist/esm/retry/constants.js +1 -1
  127. package/dist/esm/retry/index.js +2 -2
  128. package/dist/esm/retry/parse.js +50 -30
  129. package/dist/esm/retry/replay.js +11 -7
  130. package/dist/esm/retry/tracker.js +50 -12
  131. package/dist/esm/signal/api/SignalDeviceSyncApi.js +49 -32
  132. package/dist/esm/signal/api/SignalDigestSyncApi.js +13 -9
  133. package/dist/esm/signal/api/SignalIdentitySyncApi.js +26 -11
  134. package/dist/esm/signal/api/SignalMissingPreKeysSyncApi.js +18 -7
  135. package/dist/esm/signal/api/SignalRotateKeyApi.js +4 -2
  136. package/dist/esm/signal/api/SignalSessionSyncApi.js +16 -7
  137. package/dist/esm/signal/api/result-map.js +10 -0
  138. package/dist/esm/signal/constants.js +0 -4
  139. package/dist/esm/signal/crypto/WaAdvSignature.js +12 -6
  140. package/dist/esm/signal/{store/sqlite.js → encoding.js} +78 -24
  141. package/dist/esm/signal/group/SenderKeyCodec.js +3 -2
  142. package/dist/esm/signal/group/SenderKeyManager.js +125 -106
  143. package/dist/esm/signal/index.js +1 -0
  144. package/dist/esm/signal/registration/keygen.js +6 -2
  145. package/dist/esm/signal/registration/utils.js +1 -0
  146. package/dist/esm/signal/session/SignalProtocol.js +150 -74
  147. package/dist/esm/signal/session/resolver.js +137 -102
  148. package/dist/esm/store/contracts/privacy-token.store.js +1 -0
  149. package/dist/esm/store/createStore.js +101 -187
  150. package/dist/esm/store/index.js +1 -10
  151. package/dist/esm/store/locks/appstate.lock.js +26 -0
  152. package/dist/esm/store/locks/auth.lock.js +15 -0
  153. package/dist/esm/store/locks/contact.lock.js +20 -0
  154. package/dist/esm/store/locks/device-list.lock.js +20 -0
  155. package/dist/esm/store/locks/message.lock.js +21 -0
  156. package/dist/esm/store/locks/participants.lock.js +20 -0
  157. package/dist/esm/store/locks/privacy-token.lock.js +18 -0
  158. package/dist/esm/store/locks/retry.lock.js +29 -0
  159. package/dist/esm/store/locks/sender-key.lock.js +52 -0
  160. package/dist/esm/store/locks/signal.lock.js +63 -0
  161. package/dist/esm/store/locks/thread.lock.js +21 -0
  162. package/dist/esm/store/noop.store.js +1 -1
  163. package/dist/esm/store/providers/memory/appstate.store.js +22 -24
  164. package/dist/esm/store/providers/memory/device-list.store.js +10 -5
  165. package/dist/esm/store/providers/memory/privacy-token.store.js +43 -0
  166. package/dist/esm/store/providers/memory/retry.store.js +77 -2
  167. package/dist/esm/store/providers/memory/sender-key.store.js +6 -1
  168. package/dist/esm/store/providers/memory/signal.store.js +36 -19
  169. package/dist/esm/transport/WaComms.js +3 -1
  170. package/dist/esm/transport/WaWebSocket.js +0 -6
  171. package/dist/esm/transport/binary/constants.js +0 -30
  172. package/dist/esm/transport/binary/decoder.js +4 -4
  173. package/dist/esm/transport/binary/encoder.js +8 -15
  174. package/dist/esm/transport/binary/index.js +0 -1
  175. package/dist/esm/transport/node/WaNodeOrchestrator.js +25 -19
  176. package/dist/esm/transport/node/builders/business.js +129 -0
  177. package/dist/esm/transport/node/builders/global.js +370 -0
  178. package/dist/esm/transport/node/builders/index.js +5 -2
  179. package/dist/esm/transport/node/builders/message.js +63 -239
  180. package/dist/esm/transport/node/builders/pairing.js +0 -24
  181. package/dist/esm/transport/node/builders/privacy-token.js +41 -0
  182. package/dist/esm/transport/node/builders/privacy.js +48 -0
  183. package/dist/esm/transport/node/builders/profile.js +70 -0
  184. package/dist/esm/transport/node/builders/retry.js +10 -22
  185. package/dist/esm/transport/node/builders/usync.js +6 -2
  186. package/dist/esm/transport/node/helpers.js +19 -1
  187. package/dist/esm/transport/node/usync.js +3 -33
  188. package/dist/esm/transport/node/xml.js +35 -14
  189. package/dist/esm/transport/noise/WaClientPayload.js +10 -10
  190. package/dist/esm/transport/noise/WaNoiseCert.js +3 -3
  191. package/dist/esm/transport/noise/WaNoiseSession.js +64 -23
  192. package/dist/esm/transport/noise/WaNoiseSocket.js +8 -4
  193. package/dist/esm/transport/stream/parse.js +8 -4
  194. package/dist/esm/util/bytes.js +22 -18
  195. package/dist/esm/util/index.js +5 -0
  196. package/dist/esm/util/primitives.js +3 -2
  197. package/dist/index.js +7 -1
  198. package/dist/infra/perf/BackgroundQueue.js +482 -0
  199. package/dist/infra/perf/BoundedTaskQueue.js +3 -1
  200. package/dist/infra/perf/PromiseDedup.js +24 -0
  201. package/dist/infra/perf/SharedExclusiveGate.js +113 -0
  202. package/dist/infra/perf/StoreLock.js +81 -0
  203. package/dist/media/WaMediaCrypto.js +94 -12
  204. package/dist/media/WaMediaTransferClient.js +39 -47
  205. package/dist/media/constants.js +2 -1
  206. package/dist/message/WaMessageClient.js +26 -19
  207. package/dist/message/content.js +198 -9
  208. package/dist/message/icdc.js +81 -0
  209. package/dist/message/incoming.js +24 -12
  210. package/dist/message/phash.js +3 -1
  211. package/dist/message/reporting-token.js +14 -28
  212. package/dist/protocol/appstate.js +10 -41
  213. package/dist/protocol/browser.js +10 -18
  214. package/dist/protocol/constants.js +21 -2
  215. package/dist/protocol/defaults.js +6 -0
  216. package/dist/protocol/index.js +8 -5
  217. package/dist/protocol/jid.js +111 -36
  218. package/dist/protocol/message.js +62 -2
  219. package/dist/protocol/nodes.js +2 -0
  220. package/dist/protocol/notification.js +3 -1
  221. package/dist/protocol/privacy-token.js +20 -0
  222. package/dist/protocol/privacy.js +58 -0
  223. package/dist/protocol/stream.js +27 -2
  224. package/dist/retry/codec.js +220 -0
  225. package/dist/retry/constants.js +1 -1
  226. package/dist/retry/index.js +5 -5
  227. package/dist/retry/parse.js +51 -30
  228. package/dist/retry/replay.js +10 -6
  229. package/dist/retry/tracker.js +50 -12
  230. package/dist/signal/api/SignalDeviceSyncApi.js +48 -31
  231. package/dist/signal/api/SignalDigestSyncApi.js +13 -9
  232. package/dist/signal/api/SignalIdentitySyncApi.js +25 -10
  233. package/dist/signal/api/SignalMissingPreKeysSyncApi.js +17 -6
  234. package/dist/signal/api/SignalRotateKeyApi.js +4 -2
  235. package/dist/signal/api/SignalSessionSyncApi.js +16 -7
  236. package/dist/signal/api/result-map.js +13 -0
  237. package/dist/signal/constants.js +1 -5
  238. package/dist/signal/crypto/WaAdvSignature.js +11 -5
  239. package/dist/signal/{store/sqlite.js → encoding.js} +79 -25
  240. package/dist/signal/group/SenderKeyCodec.js +4 -3
  241. package/dist/signal/group/SenderKeyManager.js +125 -106
  242. package/dist/signal/index.js +13 -1
  243. package/dist/signal/registration/keygen.js +6 -2
  244. package/dist/signal/registration/utils.js +1 -0
  245. package/dist/signal/session/SignalProtocol.js +150 -74
  246. package/dist/signal/session/resolver.js +135 -100
  247. package/dist/store/contracts/privacy-token.store.js +2 -0
  248. package/dist/store/createStore.js +101 -187
  249. package/dist/store/index.js +15 -33
  250. package/dist/store/locks/appstate.lock.js +29 -0
  251. package/dist/store/locks/auth.lock.js +18 -0
  252. package/dist/store/locks/contact.lock.js +23 -0
  253. package/dist/store/locks/device-list.lock.js +23 -0
  254. package/dist/store/locks/message.lock.js +24 -0
  255. package/dist/store/locks/participants.lock.js +23 -0
  256. package/dist/store/locks/privacy-token.lock.js +21 -0
  257. package/dist/store/locks/retry.lock.js +32 -0
  258. package/dist/store/locks/sender-key.lock.js +55 -0
  259. package/dist/store/locks/signal.lock.js +66 -0
  260. package/dist/store/locks/thread.lock.js +24 -0
  261. package/dist/store/noop.store.js +1 -1
  262. package/dist/store/providers/memory/appstate.store.js +22 -24
  263. package/dist/store/providers/memory/device-list.store.js +10 -5
  264. package/dist/store/providers/memory/privacy-token.store.js +47 -0
  265. package/dist/store/providers/memory/retry.store.js +77 -2
  266. package/dist/store/providers/memory/sender-key.store.js +6 -1
  267. package/dist/store/providers/memory/signal.store.js +36 -19
  268. package/dist/transport/WaComms.js +3 -1
  269. package/dist/transport/WaWebSocket.js +0 -6
  270. package/dist/transport/binary/constants.js +1 -31
  271. package/dist/transport/binary/decoder.js +4 -4
  272. package/dist/transport/binary/encoder.js +8 -15
  273. package/dist/transport/binary/index.js +0 -4
  274. package/dist/transport/node/WaNodeOrchestrator.js +24 -18
  275. package/dist/transport/node/builders/business.js +137 -0
  276. package/dist/transport/node/builders/global.js +375 -0
  277. package/dist/transport/node/builders/index.js +18 -9
  278. package/dist/transport/node/builders/message.js +64 -245
  279. package/dist/transport/node/builders/pairing.js +0 -26
  280. package/dist/transport/node/builders/privacy-token.js +46 -0
  281. package/dist/transport/node/builders/privacy.js +55 -0
  282. package/dist/transport/node/builders/profile.js +78 -0
  283. package/dist/transport/node/builders/retry.js +9 -21
  284. package/dist/transport/node/builders/usync.js +6 -2
  285. package/dist/transport/node/helpers.js +20 -1
  286. package/dist/transport/node/usync.js +2 -32
  287. package/dist/transport/node/xml.js +35 -14
  288. package/dist/transport/noise/WaClientPayload.js +13 -13
  289. package/dist/transport/noise/WaNoiseCert.js +2 -2
  290. package/dist/transport/noise/WaNoiseSession.js +64 -23
  291. package/dist/transport/noise/WaNoiseSocket.js +8 -4
  292. package/dist/transport/stream/parse.js +7 -3
  293. package/dist/types/appstate/encoding.d.ts +7 -0
  294. package/dist/types/appstate/index.d.ts +3 -3
  295. package/dist/types/appstate/utils.d.ts +0 -2
  296. package/dist/types/auth/flow/WaAuthCredentialsFlow.d.ts +1 -1
  297. package/dist/types/auth/index.d.ts +0 -2
  298. package/dist/types/auth/types.d.ts +1 -0
  299. package/dist/types/client/WaClient.d.ts +27 -12
  300. package/dist/types/client/WaClientFactory.d.ts +12 -4
  301. package/dist/types/client/connection/WaConnectionManager.d.ts +2 -0
  302. package/dist/types/client/coordinators/WaBusinessCoordinator.d.ts +57 -0
  303. package/dist/types/client/coordinators/WaIncomingNodeCoordinator.d.ts +3 -1
  304. package/dist/types/client/coordinators/WaMessageDispatchCoordinator.d.ts +14 -0
  305. package/dist/types/client/coordinators/WaPassiveTasksCoordinator.d.ts +4 -0
  306. package/dist/types/client/coordinators/WaPrivacyCoordinator.d.ts +26 -0
  307. package/dist/types/client/coordinators/WaProfileCoordinator.d.ts +36 -0
  308. package/dist/types/client/coordinators/WaRetryCoordinator.d.ts +6 -0
  309. package/dist/types/client/coordinators/WaStreamControlCoordinator.d.ts +3 -2
  310. package/dist/types/client/coordinators/WaTrustedContactTokenCoordinator.d.ts +45 -0
  311. package/dist/types/client/events/devices.d.ts +20 -0
  312. package/dist/types/client/events/identity.d.ts +9 -0
  313. package/dist/types/client/events/privacy-token.d.ts +7 -0
  314. package/dist/types/client/history-sync.d.ts +9 -6
  315. package/dist/types/client/incoming.d.ts +3 -1
  316. package/dist/types/client/index.d.ts +1 -1
  317. package/dist/types/client/mailbox.d.ts +3 -5
  318. package/dist/types/client/messages.d.ts +1 -2
  319. package/dist/types/client/persistence/WriteBehindPersistence.d.ts +34 -0
  320. package/dist/types/client/tokens/cs-token.d.ts +10 -0
  321. package/dist/types/client/tokens/tc-token.d.ts +5 -0
  322. package/dist/types/client/types.d.ts +51 -3
  323. package/dist/types/crypto/core/index.d.ts +2 -2
  324. package/dist/types/crypto/core/nonce.d.ts +2 -0
  325. package/dist/types/crypto/core/primitives.d.ts +0 -1
  326. package/dist/types/crypto/core/random.d.ts +1 -0
  327. package/dist/types/crypto/index.d.ts +1 -0
  328. package/dist/types/crypto/math/constants.d.ts +4 -2
  329. package/dist/types/crypto/math/fe.d.ts +30 -0
  330. package/dist/types/crypto/math/mod.d.ts +0 -2
  331. package/dist/types/crypto/math/types.d.ts +11 -4
  332. package/dist/types/index.d.ts +5 -3
  333. package/dist/types/infra/perf/BackgroundQueue.d.ts +58 -0
  334. package/dist/types/infra/perf/PromiseDedup.d.ts +4 -0
  335. package/dist/types/infra/perf/SharedExclusiveGate.d.ts +17 -0
  336. package/dist/types/infra/perf/StoreLock.d.ts +10 -0
  337. package/dist/types/media/WaMediaCrypto.d.ts +3 -2
  338. package/dist/types/media/WaMediaTransferClient.d.ts +3 -12
  339. package/dist/types/media/constants.d.ts +1 -1
  340. package/dist/types/media/index.d.ts +1 -1
  341. package/dist/types/media/types.d.ts +10 -2
  342. package/dist/types/message/content.d.ts +8 -0
  343. package/dist/types/message/icdc.d.ts +13 -0
  344. package/dist/types/message/reporting-token.d.ts +0 -1
  345. package/dist/types/message/types.d.ts +45 -6
  346. package/dist/types/protocol/appstate.d.ts +0 -11
  347. package/dist/types/protocol/constants.d.ts +7 -3
  348. package/dist/types/protocol/defaults.d.ts +6 -0
  349. package/dist/types/protocol/index.d.ts +1 -2
  350. package/dist/types/protocol/jid.d.ts +19 -2
  351. package/dist/types/protocol/message.d.ts +60 -0
  352. package/dist/types/protocol/nodes.d.ts +2 -0
  353. package/dist/types/protocol/notification.d.ts +2 -0
  354. package/dist/types/protocol/privacy-token.d.ts +17 -0
  355. package/dist/types/protocol/privacy.d.ts +75 -0
  356. package/dist/types/protocol/stream.d.ts +30 -0
  357. package/dist/types/retry/codec.d.ts +3 -0
  358. package/dist/types/retry/index.d.ts +3 -3
  359. package/dist/types/retry/parse.d.ts +5 -2
  360. package/dist/types/retry/tracker.d.ts +1 -0
  361. package/dist/types/retry/types.d.ts +6 -1
  362. package/dist/types/signal/api/SignalDeviceSyncApi.d.ts +2 -1
  363. package/dist/types/signal/api/SignalDigestSyncApi.d.ts +6 -0
  364. package/dist/types/signal/api/SignalIdentitySyncApi.d.ts +2 -0
  365. package/dist/types/signal/api/SignalRotateKeyApi.d.ts +4 -5
  366. package/dist/types/signal/api/SignalSessionSyncApi.d.ts +8 -6
  367. package/dist/types/signal/api/result-map.d.ts +1 -0
  368. package/dist/types/signal/constants.d.ts +0 -3
  369. package/dist/types/signal/{store/sqlite.d.ts → encoding.d.ts} +3 -3
  370. package/dist/types/signal/group/SenderKeyManager.d.ts +10 -5
  371. package/dist/types/signal/index.d.ts +2 -0
  372. package/dist/types/signal/session/SignalProtocol.d.ts +10 -4
  373. package/dist/types/signal/session/resolver.d.ts +7 -2
  374. package/dist/types/store/contracts/appstate.store.d.ts +1 -1
  375. package/dist/types/store/contracts/privacy-token.store.d.ts +16 -0
  376. package/dist/types/store/contracts/retry.store.d.ts +7 -0
  377. package/dist/types/store/contracts/signal.store.d.ts +7 -0
  378. package/dist/types/store/createStore.d.ts +1 -1
  379. package/dist/types/store/index.d.ts +5 -13
  380. package/dist/types/store/locks/appstate.lock.d.ts +3 -0
  381. package/dist/types/store/locks/auth.lock.d.ts +3 -0
  382. package/dist/types/store/locks/contact.lock.d.ts +3 -0
  383. package/dist/types/store/locks/device-list.lock.d.ts +2 -0
  384. package/dist/types/store/locks/message.lock.d.ts +3 -0
  385. package/dist/types/store/locks/participants.lock.d.ts +2 -0
  386. package/dist/types/store/locks/privacy-token.lock.d.ts +2 -0
  387. package/dist/types/store/locks/retry.lock.d.ts +2 -0
  388. package/dist/types/store/locks/sender-key.lock.d.ts +3 -0
  389. package/dist/types/store/locks/signal.lock.d.ts +3 -0
  390. package/dist/types/store/locks/thread.lock.d.ts +3 -0
  391. package/dist/types/store/providers/memory/appstate.store.d.ts +1 -1
  392. package/dist/types/store/providers/memory/privacy-token.store.d.ts +13 -0
  393. package/dist/types/store/providers/memory/retry.store.d.ts +8 -0
  394. package/dist/types/store/providers/memory/signal.store.d.ts +2 -1
  395. package/dist/types/store/types.d.ts +49 -61
  396. package/dist/types/transport/WaWebSocket.d.ts +0 -1
  397. package/dist/types/transport/binary/constants.d.ts +0 -30
  398. package/dist/types/transport/binary/index.d.ts +0 -1
  399. package/dist/types/transport/node/WaNodeOrchestrator.d.ts +3 -4
  400. package/dist/types/transport/node/builders/business.d.ts +29 -0
  401. package/dist/types/transport/node/builders/global.d.ts +102 -0
  402. package/dist/types/transport/node/builders/index.d.ts +5 -2
  403. package/dist/types/transport/node/builders/message.d.ts +8 -7
  404. package/dist/types/transport/node/builders/pairing.d.ts +0 -2
  405. package/dist/types/transport/node/builders/privacy-token.d.ts +9 -0
  406. package/dist/types/transport/node/builders/privacy.d.ts +7 -0
  407. package/dist/types/transport/node/builders/profile.d.ts +8 -0
  408. package/dist/types/transport/node/builders/retry.d.ts +0 -1
  409. package/dist/types/transport/node/helpers.d.ts +5 -0
  410. package/dist/types/transport/noise/WaNoiseSession.d.ts +3 -2
  411. package/dist/types/transport/noise/WaNoiseSocket.d.ts +4 -2
  412. package/dist/types/util/bytes.d.ts +1 -1
  413. package/dist/types/util/index.d.ts +5 -0
  414. package/dist/types/util/primitives.d.ts +0 -1
  415. package/dist/util/bytes.js +22 -18
  416. package/dist/util/index.js +23 -0
  417. package/dist/util/primitives.js +2 -2
  418. package/package.json +29 -7
  419. package/proto/index.js +1 -1
  420. package/dist/crypto/core/constants.js +0 -4
  421. package/dist/esm/crypto/core/constants.js +0 -1
  422. package/dist/esm/retry/outbound.js +0 -82
  423. package/dist/esm/store/providers/sqlite/BaseSqliteStore.js +0 -37
  424. package/dist/esm/store/providers/sqlite/appstate.store.js +0 -250
  425. package/dist/esm/store/providers/sqlite/auth.store.js +0 -176
  426. package/dist/esm/store/providers/sqlite/connection.js +0 -245
  427. package/dist/esm/store/providers/sqlite/contact.store.js +0 -74
  428. package/dist/esm/store/providers/sqlite/device-list.store.js +0 -127
  429. package/dist/esm/store/providers/sqlite/message.store.js +0 -132
  430. package/dist/esm/store/providers/sqlite/migrations.js +0 -347
  431. package/dist/esm/store/providers/sqlite/participants.store.js +0 -77
  432. package/dist/esm/store/providers/sqlite/retry.store.js +0 -141
  433. package/dist/esm/store/providers/sqlite/sender-key.store.js +0 -198
  434. package/dist/esm/store/providers/sqlite/signal.store.js +0 -435
  435. package/dist/esm/store/providers/sqlite/table-names.js +0 -107
  436. package/dist/esm/store/providers/sqlite/thread.store.js +0 -85
  437. package/dist/retry/outbound.js +0 -87
  438. package/dist/store/providers/sqlite/BaseSqliteStore.js +0 -41
  439. package/dist/store/providers/sqlite/appstate.store.js +0 -254
  440. package/dist/store/providers/sqlite/auth.store.js +0 -180
  441. package/dist/store/providers/sqlite/connection.js +0 -281
  442. package/dist/store/providers/sqlite/contact.store.js +0 -78
  443. package/dist/store/providers/sqlite/device-list.store.js +0 -131
  444. package/dist/store/providers/sqlite/message.store.js +0 -136
  445. package/dist/store/providers/sqlite/migrations.js +0 -350
  446. package/dist/store/providers/sqlite/participants.store.js +0 -81
  447. package/dist/store/providers/sqlite/retry.store.js +0 -145
  448. package/dist/store/providers/sqlite/sender-key.store.js +0 -202
  449. package/dist/store/providers/sqlite/signal.store.js +0 -439
  450. package/dist/store/providers/sqlite/table-names.js +0 -113
  451. package/dist/store/providers/sqlite/thread.store.js +0 -89
  452. package/dist/types/appstate/store/sqlite.d.ts +0 -7
  453. package/dist/types/crypto/core/constants.d.ts +0 -1
  454. package/dist/types/retry/outbound.d.ts +0 -4
  455. package/dist/types/store/providers/sqlite/BaseSqliteStore.d.ts +0 -12
  456. package/dist/types/store/providers/sqlite/appstate.store.d.ts +0 -17
  457. package/dist/types/store/providers/sqlite/auth.store.d.ts +0 -10
  458. package/dist/types/store/providers/sqlite/connection.d.ts +0 -10
  459. package/dist/types/store/providers/sqlite/contact.store.d.ts +0 -12
  460. package/dist/types/store/providers/sqlite/device-list.store.d.ts +0 -15
  461. package/dist/types/store/providers/sqlite/message.store.d.ts +0 -13
  462. package/dist/types/store/providers/sqlite/migrations.d.ts +0 -3
  463. package/dist/types/store/providers/sqlite/participants.store.d.ts +0 -12
  464. package/dist/types/store/providers/sqlite/retry.store.d.ts +0 -15
  465. package/dist/types/store/providers/sqlite/sender-key.store.d.ts +0 -24
  466. package/dist/types/store/providers/sqlite/signal.store.d.ts +0 -53
  467. package/dist/types/store/providers/sqlite/table-names.d.ts +0 -5
  468. package/dist/types/store/providers/sqlite/thread.store.d.ts +0 -13
@@ -1,16 +1,18 @@
1
1
  import { proto } from '../../proto.js';
2
2
  import { WA_MESSAGE_TAGS } from '../../protocol/constants.js';
3
- import { isGroupOrBroadcastJid, normalizeDeviceJid, parseSignalAddressFromJid } from '../../protocol/jid.js';
3
+ import { isGroupOrBroadcastJid, normalizeDeviceJid, parseJidFull } from '../../protocol/jid.js';
4
4
  import { MAX_RETRY_ATTEMPTS, RETRY_KEYS_MIN_COUNT, RETRY_OUTBOUND_TTL_MS, RETRY_REASON } from '../../retry/constants.js';
5
- import { pickRetryStateMax } from '../../retry/outbound.js';
6
- import { parseRetryReceiptRequest } from '../../retry/parse.js';
5
+ import { parseRetryReceiptRequest, pickRetryStateMax } from '../../retry/parse.js';
7
6
  import { mapRetryReasonFromError } from '../../retry/reason.js';
8
7
  import { WaRetryReplayService } from '../../retry/replay.js';
9
8
  import { generatePreKeyPair } from '../../signal/registration/keygen.js';
10
- import { buildInboundRetryReceiptAckNode } from '../../transport/node/builders/message.js';
9
+ import { buildAckNode } from '../../transport/node/builders/global.js';
11
10
  import { buildRetryReceiptNode } from '../../transport/node/builders/retry.js';
11
+ import { uint8Equal } from '../../util/bytes.js';
12
+ import { setBoundedMapEntry } from '../../util/collections.js';
12
13
  import { toError } from '../../util/primitives.js';
13
14
  const RETRY_CLEANUP_INTERVAL_MS = 30000;
15
+ const RETRY_SESSION_BASE_KEY_CACHE_MAX_ENTRIES = 8192;
14
16
  function getRetryReasonName(code) {
15
17
  if (code === undefined) {
16
18
  return undefined;
@@ -52,6 +54,7 @@ export class WaRetryCoordinator {
52
54
  getCurrentSignedIdentity: this.getCurrentSignedIdentity
53
55
  });
54
56
  this.retryProcessingByMessageId = new Map();
57
+ this.retrySessionBaseKeys = new Map();
55
58
  }
56
59
  async onDecryptFailure(context, error) {
57
60
  try {
@@ -76,12 +79,23 @@ export class WaRetryCoordinator {
76
79
  if (!this.isRetryReceiptNode(receiptNode)) {
77
80
  return;
78
81
  }
82
+ let shouldAck = false;
79
83
  try {
80
84
  await this.maybeCleanupRetryStore(Date.now());
81
- const request = parseRetryReceiptRequest(receiptNode);
85
+ const expectedToJids = [];
86
+ const meJid = this.getCurrentMeJid()?.trim();
87
+ const meLid = this.getCurrentMeLid()?.trim();
88
+ if (meJid) {
89
+ expectedToJids.push(meJid);
90
+ }
91
+ if (meLid) {
92
+ expectedToJids.push(meLid);
93
+ }
94
+ const request = parseRetryReceiptRequest(receiptNode, expectedToJids.length > 0 ? { expectedToJids } : undefined);
82
95
  if (!request) {
83
96
  return;
84
97
  }
98
+ shouldAck = true;
85
99
  await this.handleParsedRetryRequest(receiptNode, request);
86
100
  }
87
101
  catch (error) {
@@ -93,7 +107,9 @@ export class WaRetryCoordinator {
93
107
  });
94
108
  }
95
109
  finally {
96
- await this.sendRetryAckSafe(receiptNode);
110
+ if (shouldAck) {
111
+ await this.sendRetryAckSafe(receiptNode);
112
+ }
97
113
  }
98
114
  }
99
115
  isRetryReceiptNode(node) {
@@ -129,7 +145,6 @@ export class WaRetryCoordinator {
129
145
  to: context.from,
130
146
  participant: context.participant,
131
147
  recipient: context.recipient,
132
- from: this.getCurrentMeJid() ?? undefined,
133
148
  originalMsgId: context.stanzaId,
134
149
  retryCount: prepared.retryCount,
135
150
  t: prepared.timestamp,
@@ -200,8 +215,9 @@ export class WaRetryCoordinator {
200
215
  let requesterAddress;
201
216
  let requesterNormalizedDeviceJid;
202
217
  try {
203
- requesterAddress = parseSignalAddressFromJid(requesterJid);
204
- requesterNormalizedDeviceJid = normalizeDeviceJid(requesterJid);
218
+ const requesterParsed = parseJidFull(requesterJid);
219
+ requesterAddress = requesterParsed.address;
220
+ requesterNormalizedDeviceJid = requesterParsed.normalizedJid;
205
221
  }
206
222
  catch (error) {
207
223
  this.logger.info('retry request rejected: invalid requester jid', {
@@ -277,20 +293,36 @@ export class WaRetryCoordinator {
277
293
  if (!nextState) {
278
294
  return;
279
295
  }
280
- const current = await this.retryStore.getOutboundMessage(messageId);
281
- if (!current) {
282
- return;
283
- }
284
- const merged = pickRetryStateMax(current.state, nextState);
285
- if (merged === current.state) {
286
- return;
287
- }
288
- const nowMs = Date.now();
289
- await this.retryStore.updateOutboundMessageState(messageId, merged, nowMs, nowMs + this.retryTtlMs);
296
+ await this.runRetryTaskSerialized(messageId, async () => {
297
+ const current = await this.retryStore.getOutboundMessage(messageId);
298
+ if (!current) {
299
+ return;
300
+ }
301
+ const nowMs = Date.now();
302
+ const expiresAtMs = nowMs + this.retryTtlMs;
303
+ const merged = pickRetryStateMax(current.state, nextState);
304
+ if (merged !== current.state) {
305
+ await this.retryStore.updateOutboundMessageState(messageId, merged, nowMs, expiresAtMs);
306
+ }
307
+ const requesterJid = receiptNode.attrs.participant ?? receiptNode.attrs.from;
308
+ if (!requesterJid) {
309
+ return;
310
+ }
311
+ try {
312
+ await this.retryStore.markOutboundRequesterDelivered(messageId, normalizeDeviceJid(requesterJid), nowMs, expiresAtMs);
313
+ }
314
+ catch (error) {
315
+ this.logger.warn('failed to update outbound requester delivery state', {
316
+ id: messageId,
317
+ requester: requesterJid,
318
+ message: toError(error).message
319
+ });
320
+ }
321
+ });
290
322
  }
291
323
  async runRetryTaskSerialized(messageId, task) {
292
324
  const previous = this.retryProcessingByMessageId.get(messageId) ?? Promise.resolve();
293
- const current = previous.then(task, task);
325
+ const current = previous.then(task);
294
326
  const tracker = current.then(() => undefined, () => undefined);
295
327
  this.retryProcessingByMessageId.set(messageId, tracker);
296
328
  try {
@@ -331,15 +363,45 @@ export class WaRetryCoordinator {
331
363
  };
332
364
  }
333
365
  async updateLocalSessionFromRetryRequest(request, requesterJid, requesterAddress, requesterNormalizedDeviceJid) {
334
- await this.markRetryRequesterSenderKeyAsStale(request, requesterJid, requesterAddress);
335
- const currentSession = await this.signalStore.getSession(requesterAddress);
336
- if (currentSession && request.regId > 0 && currentSession.remote.regId !== request.regId) {
366
+ const [, currentSession] = await Promise.all([
367
+ this.markRetryRequesterSenderKeyAsStale(request, requesterJid, requesterAddress),
368
+ this.signalStore.getSession(requesterAddress)
369
+ ]);
370
+ const regIdMismatch = !!currentSession && request.regId > 0 && currentSession.remote.regId !== request.regId;
371
+ if (regIdMismatch && !request.keyBundle) {
337
372
  await this.signalStore.deleteSession(requesterAddress);
338
373
  }
339
374
  if (request.keyBundle) {
340
375
  if (!request.keyBundle.key || !request.keyBundle.skey.signature) {
341
376
  return false;
342
377
  }
378
+ if (request.offline) {
379
+ if (!currentSession) {
380
+ this.logger.info('retry request rejected: offline retry missing existing session', {
381
+ id: request.stanzaId,
382
+ originalMsgId: request.originalMsgId,
383
+ requester: requesterJid,
384
+ remoteRetryCount: request.retryCount,
385
+ ...getRemoteRetryReasonLogFields(request.retryReason)
386
+ });
387
+ await this.signalStore.deleteSession(requesterAddress);
388
+ return false;
389
+ }
390
+ if (regIdMismatch) {
391
+ this.logger.info('retry request rejected: offline retry registration id mismatch', {
392
+ id: request.stanzaId,
393
+ originalMsgId: request.originalMsgId,
394
+ requester: requesterJid,
395
+ remoteRetryCount: request.retryCount,
396
+ ...getRemoteRetryReasonLogFields(request.retryReason)
397
+ });
398
+ await this.signalStore.deleteSession(requesterAddress);
399
+ return false;
400
+ }
401
+ }
402
+ else if (regIdMismatch) {
403
+ await this.signalStore.deleteSession(requesterAddress);
404
+ }
343
405
  await this.signalProtocol.establishOutgoingSession(requesterAddress, {
344
406
  regId: request.regId,
345
407
  identity: request.keyBundle.identity,
@@ -353,12 +415,45 @@ export class WaRetryCoordinator {
353
415
  publicKey: request.keyBundle.key.publicKey
354
416
  }
355
417
  });
418
+ return this.applySessionBaseKeyPolicy(request, requesterJid, requesterAddress, requesterNormalizedDeviceJid);
419
+ }
420
+ const sessionStillExists = currentSession !== null && !regIdMismatch;
421
+ if (sessionStillExists) {
422
+ return this.applySessionBaseKeyPolicy(request, requesterJid, requesterAddress, requesterNormalizedDeviceJid);
423
+ }
424
+ const fetched = await this.fetchMissingPreKeysSession(requesterJid, requesterAddress, requesterNormalizedDeviceJid, request.regId);
425
+ if (!fetched) {
426
+ return false;
427
+ }
428
+ await this.signalProtocol.establishOutgoingSession(requesterAddress, fetched);
429
+ return this.applySessionBaseKeyPolicy(request, requesterJid, requesterAddress, requesterNormalizedDeviceJid);
430
+ }
431
+ async applySessionBaseKeyPolicy(request, requesterJid, requesterAddress, requesterNormalizedDeviceJid) {
432
+ if (request.retryCount < 2) {
356
433
  return true;
357
434
  }
358
- const hasSession = await this.signalProtocol.hasSession(requesterAddress);
359
- if (hasSession) {
435
+ const currentSession = await this.signalStore.getSession(requesterAddress);
436
+ const sessionBaseKey = currentSession?.aliceBaseKey ?? null;
437
+ if (!sessionBaseKey) {
360
438
  return true;
361
439
  }
440
+ const expiresAtMs = Date.now() + this.retryTtlMs;
441
+ if (request.retryCount === 2) {
442
+ this.setRetrySessionBaseKey(request.originalMsgId, requesterNormalizedDeviceJid, sessionBaseKey, expiresAtMs);
443
+ return true;
444
+ }
445
+ const saved = this.getRetrySessionBaseKey(request.originalMsgId, requesterNormalizedDeviceJid);
446
+ if (!saved || !uint8Equal(saved.baseKey, sessionBaseKey)) {
447
+ return true;
448
+ }
449
+ await this.signalStore.deleteSession(requesterAddress);
450
+ this.logger.info('retry request forcing session refresh due to repeated base key', {
451
+ id: request.stanzaId,
452
+ originalMsgId: request.originalMsgId,
453
+ requester: requesterJid,
454
+ remoteRetryCount: request.retryCount,
455
+ ...getRemoteRetryReasonLogFields(request.retryReason)
456
+ });
362
457
  const fetched = await this.fetchMissingPreKeysSession(requesterJid, requesterAddress, requesterNormalizedDeviceJid, request.regId);
363
458
  if (!fetched) {
364
459
  return false;
@@ -431,13 +526,35 @@ export class WaRetryCoordinator {
431
526
  if (outbound.state === 'ineligible') {
432
527
  return { authorized: false, reason: `state_${outbound.state}` };
433
528
  }
529
+ let requesterStatus = null;
530
+ try {
531
+ requesterStatus = await this.retryStore.getOutboundRequesterStatus(outbound.messageId, requesterNormalizedDeviceJid);
532
+ }
533
+ catch (error) {
534
+ this.logger.warn('failed to resolve outbound requester status from retry store', {
535
+ id: request.stanzaId,
536
+ originalMsgId: request.originalMsgId,
537
+ requester: requesterJid,
538
+ message: toError(error).message
539
+ });
540
+ }
541
+ if (requesterStatus) {
542
+ if (!requesterStatus.eligible) {
543
+ return { authorized: false, reason: 'requester_device_not_eligible' };
544
+ }
545
+ if (requesterStatus.delivered) {
546
+ return { authorized: false, reason: 'requester_already_delivered' };
547
+ }
548
+ }
434
549
  const isGroupOutbound = isGroupOrBroadcastJid(outbound.toJid);
435
550
  if (!isGroupOutbound && (outbound.state === 'read' || outbound.state === 'played')) {
436
551
  return { authorized: false, reason: `state_${outbound.state}` };
437
552
  }
438
- const requesterAuthorized = await this.isRequesterAuthorizedDevice(requesterJid, requesterAddress, requesterNormalizedDeviceJid);
439
- if (!requesterAuthorized) {
440
- return { authorized: false, reason: 'requester_device_not_authorized' };
553
+ if (!requesterStatus) {
554
+ const requesterAuthorized = await this.isRequesterAuthorizedDevice(requesterJid, requesterAddress, requesterNormalizedDeviceJid);
555
+ if (!requesterAuthorized) {
556
+ return { authorized: false, reason: 'requester_device_not_authorized' };
557
+ }
441
558
  }
442
559
  return { authorized: true };
443
560
  }
@@ -495,7 +612,11 @@ export class WaRetryCoordinator {
495
612
  return;
496
613
  }
497
614
  try {
498
- await this.sendNode(buildInboundRetryReceiptAckNode(receiptNode));
615
+ await this.sendNode(buildAckNode({
616
+ kind: 'receipt',
617
+ node: receiptNode,
618
+ retryType: true
619
+ }));
499
620
  }
500
621
  catch (error) {
501
622
  this.logger.warn('failed to send retry ack', {
@@ -511,6 +632,7 @@ export class WaRetryCoordinator {
511
632
  return;
512
633
  }
513
634
  this.nextRetryCleanupAtMs = nowMs + RETRY_CLEANUP_INTERVAL_MS;
635
+ this.cleanupRetrySessionBaseKeys(nowMs);
514
636
  try {
515
637
  await this.retryStore.cleanupExpired(nowMs);
516
638
  }
@@ -520,4 +642,34 @@ export class WaRetryCoordinator {
520
642
  });
521
643
  }
522
644
  }
645
+ retrySessionBaseKeyMapKey(originalMsgId, requesterNormalizedDeviceJid) {
646
+ return `${originalMsgId}|${requesterNormalizedDeviceJid}`;
647
+ }
648
+ setRetrySessionBaseKey(originalMsgId, requesterNormalizedDeviceJid, baseKey, expiresAtMs) {
649
+ const key = this.retrySessionBaseKeyMapKey(originalMsgId, requesterNormalizedDeviceJid);
650
+ setBoundedMapEntry(this.retrySessionBaseKeys, key, {
651
+ baseKey: Uint8Array.from(baseKey),
652
+ expiresAtMs
653
+ }, RETRY_SESSION_BASE_KEY_CACHE_MAX_ENTRIES);
654
+ }
655
+ getRetrySessionBaseKey(originalMsgId, requesterNormalizedDeviceJid) {
656
+ const key = this.retrySessionBaseKeyMapKey(originalMsgId, requesterNormalizedDeviceJid);
657
+ const entry = this.retrySessionBaseKeys.get(key);
658
+ if (!entry) {
659
+ return null;
660
+ }
661
+ if (entry.expiresAtMs <= Date.now()) {
662
+ this.retrySessionBaseKeys.delete(key);
663
+ return null;
664
+ }
665
+ return entry;
666
+ }
667
+ cleanupRetrySessionBaseKeys(nowMs) {
668
+ for (const [key, entry] of this.retrySessionBaseKeys) {
669
+ if (entry.expiresAtMs > nowMs) {
670
+ continue;
671
+ }
672
+ this.retrySessionBaseKeys.delete(key);
673
+ }
674
+ }
523
675
  }
@@ -1,4 +1,4 @@
1
- import { WA_DISCONNECT_REASONS, WA_STREAM_SIGNALING } from '../../protocol/constants.js';
1
+ import { WA_CONNECTION_REASONS, WA_DISCONNECT_REASONS, WA_STREAM_SIGNALING } from '../../protocol/constants.js';
2
2
  import { toError } from '../../util/primitives.js';
3
3
  export function createStreamControlHandler(options) {
4
4
  const { logger, getComms, clearPendingQueries, clearMediaConnCache, disconnect, clearStoredCredentials, connect } = options;
@@ -16,7 +16,7 @@ export function createStreamControlHandler(options) {
16
16
  const restartBackendAfterStreamControl = async (reason) => {
17
17
  logger.info('restarting backend after stream control', { reason });
18
18
  try {
19
- await connect();
19
+ await connect(WA_CONNECTION_REASONS.RECONNECTED);
20
20
  }
21
21
  catch (error) {
22
22
  logger.warn('failed to restart backend after stream control', {
@@ -43,29 +43,36 @@ export function createStreamControlHandler(options) {
43
43
  });
44
44
  }
45
45
  };
46
+ const stopCommsImmediately = () => {
47
+ void getComms()?.stopComms();
48
+ };
46
49
  const forceLoginDueToStreamError = async (code) => {
47
- await runStreamControlLifecycle(`stream_error_code_${code}`, async () => {
50
+ const reason = WA_DISCONNECT_REASONS.STREAM_ERROR_FORCE_LOGIN;
51
+ stopCommsImmediately();
52
+ await runStreamControlLifecycle(reason, async () => {
48
53
  logger.warn('received forced login stream error; starting login lifecycle', {
49
54
  code
50
55
  });
51
- await disconnect();
56
+ await disconnect(reason, true, code);
52
57
  await clearStoredCredentials();
53
- await restartBackendAfterStreamControl(`stream_error_code_${code}`);
58
+ await restartBackendAfterStreamControl(reason);
54
59
  });
55
60
  };
56
- const disconnectDueToStreamError = async (reason) => {
61
+ const disconnectDueToStreamError = async (reason, code) => {
62
+ stopCommsImmediately();
57
63
  await runStreamControlLifecycle(reason, async () => {
58
64
  logger.warn('disconnecting due to stream control node', { reason });
59
- await disconnect();
65
+ await disconnect(reason, false, code);
60
66
  });
61
67
  };
62
- const logoutDueToStreamError = async (reason, shouldRestartBackend) => {
68
+ const logoutDueToStreamError = async (reason, code, shouldRestartBackend) => {
69
+ stopCommsImmediately();
63
70
  await runStreamControlLifecycle(reason, async () => {
64
71
  logger.warn('logging out due to stream control node', {
65
72
  reason,
66
73
  shouldRestartBackend
67
74
  });
68
- await disconnect();
75
+ await disconnect(reason, true, code);
69
76
  await clearStoredCredentials();
70
77
  if (shouldRestartBackend) {
71
78
  await restartBackendAfterStreamControl(reason);
@@ -86,7 +93,7 @@ export function createStreamControlHandler(options) {
86
93
  return;
87
94
  }
88
95
  if (result.code === WA_STREAM_SIGNALING.FORCE_LOGOUT_CODE) {
89
- await logoutDueToStreamError(`stream_error_code_${WA_STREAM_SIGNALING.FORCE_LOGOUT_CODE}`, true);
96
+ await logoutDueToStreamError(WA_DISCONNECT_REASONS.STREAM_ERROR_FORCE_LOGOUT, result.code, true);
90
97
  return;
91
98
  }
92
99
  }
@@ -94,11 +101,11 @@ export function createStreamControlHandler(options) {
94
101
  return;
95
102
  case 'stream_error_replaced':
96
103
  logger.warn('received stream:error replaced, stopping client');
97
- await disconnectDueToStreamError(WA_DISCONNECT_REASONS.STREAM_ERROR_REPLACED);
104
+ await disconnectDueToStreamError(WA_DISCONNECT_REASONS.STREAM_ERROR_REPLACED, null);
98
105
  return;
99
106
  case 'stream_error_device_removed':
100
107
  logger.warn('received stream:error device removed, logging out');
101
- await logoutDueToStreamError(WA_DISCONNECT_REASONS.STREAM_ERROR_DEVICE_REMOVED, false);
108
+ await logoutDueToStreamError(WA_DISCONNECT_REASONS.STREAM_ERROR_DEVICE_REMOVED, null, false);
102
109
  return;
103
110
  case 'stream_error_ack':
104
111
  logger.warn('received stream:error ack', { id: result.id });
@@ -0,0 +1,162 @@
1
+ import { CsTokenGenerator } from '../tokens/cs-token.js';
2
+ import { isTokenExpired, shouldSendNewToken, clampDuration } from '../tokens/tc-token.js';
3
+ import { PromiseDedup } from '../../infra/perf/PromiseDedup.js';
4
+ import { WA_PRIVACY_TOKEN_TYPES, WA_TC_TOKEN_DEFAULTS } from '../../protocol/privacy-token.js';
5
+ import { buildPrivacyTokenIqNode, buildTcTokenMessageNode, buildCsTokenMessageNode } from '../../transport/node/builders/privacy-token.js';
6
+ import { toError } from '../../util/primitives.js';
7
+ const NCT_SALT_SENTINEL_JID = '__nct_salt__';
8
+ export class WaTrustedContactTokenCoordinator {
9
+ constructor(options) {
10
+ this.logger = options.logger;
11
+ this.store = options.store;
12
+ this.runtime = options.runtime;
13
+ const maxDurationS = options.maxDurationS ?? WA_TC_TOKEN_DEFAULTS.MAX_DURATION_S;
14
+ this.config = {
15
+ durationS: clampDuration(options.durationS ?? WA_TC_TOKEN_DEFAULTS.DURATION_S, maxDurationS),
16
+ numBuckets: options.numBuckets ?? WA_TC_TOKEN_DEFAULTS.NUM_BUCKETS,
17
+ senderDurationS: clampDuration(options.senderDurationS ?? WA_TC_TOKEN_DEFAULTS.SENDER_DURATION_S, maxDurationS),
18
+ senderNumBuckets: options.senderNumBuckets ?? WA_TC_TOKEN_DEFAULTS.SENDER_NUM_BUCKETS,
19
+ maxDurationS
20
+ };
21
+ this.csTokenGenerator = new CsTokenGenerator();
22
+ this.senderTokenDedup = new PromiseDedup();
23
+ this.cachedNctSalt = null;
24
+ this.nctSaltHydrated = false;
25
+ }
26
+ async resolveTokenForMessage(recipientJid) {
27
+ const record = await this.store.getByJid(recipientJid);
28
+ const nowS = Math.floor(Date.now() / 1000);
29
+ if (record?.tcToken &&
30
+ record.tcTokenTimestamp !== undefined &&
31
+ !isTokenExpired(record.tcTokenTimestamp, nowS, this.config.durationS, this.config.numBuckets)) {
32
+ return buildTcTokenMessageNode(record.tcToken);
33
+ }
34
+ const nctSalt = await this.getNctSalt();
35
+ if (!nctSalt) {
36
+ return null;
37
+ }
38
+ const meLid = this.runtime.getCurrentMeLid();
39
+ if (!meLid) {
40
+ return null;
41
+ }
42
+ const hash = await this.csTokenGenerator.generate(nctSalt, meLid);
43
+ return buildCsTokenMessageNode(hash);
44
+ }
45
+ async handleIncomingToken(fromJid, tokens) {
46
+ const nowMs = Date.now();
47
+ for (let i = 0; i < tokens.length; i += 1) {
48
+ const token = tokens[i];
49
+ if (token.type !== WA_PRIVACY_TOKEN_TYPES.TRUSTED_CONTACT) {
50
+ this.logger.warn('ignoring unknown privacy token type', { type: token.type });
51
+ continue;
52
+ }
53
+ await this.store.upsert({
54
+ jid: fromJid,
55
+ tcToken: token.tokenBytes,
56
+ tcTokenTimestamp: token.timestampS,
57
+ updatedAtMs: nowMs
58
+ });
59
+ this.runtime.emitEvent('privacy_token_update', {
60
+ jid: fromJid,
61
+ timestampS: token.timestampS,
62
+ type: token.type,
63
+ source: 'notification'
64
+ });
65
+ }
66
+ }
67
+ async maybeIssueSenderToken(recipientJid) {
68
+ return this.senderTokenDedup.run(recipientJid, async () => {
69
+ const nowS = Math.floor(Date.now() / 1000);
70
+ const record = await this.store.getByJid(recipientJid);
71
+ const senderTimestampS = record?.tcTokenSenderTimestamp;
72
+ if (senderTimestampS !== undefined && senderTimestampS > 0) {
73
+ if (!shouldSendNewToken(senderTimestampS, nowS, this.config.senderDurationS)) {
74
+ return;
75
+ }
76
+ }
77
+ await this.issuePrivacyToken(recipientJid, nowS);
78
+ await this.store.upsert({
79
+ jid: recipientJid,
80
+ tcTokenSenderTimestamp: nowS,
81
+ updatedAtMs: Date.now()
82
+ });
83
+ });
84
+ }
85
+ async reissueOnIdentityChange(jid) {
86
+ const record = await this.store.getByJid(jid);
87
+ if (!record?.tcTokenSenderTimestamp) {
88
+ return;
89
+ }
90
+ const nowS = Math.floor(Date.now() / 1000);
91
+ if (isTokenExpired(record.tcTokenSenderTimestamp, nowS, this.config.senderDurationS, this.config.senderNumBuckets)) {
92
+ return;
93
+ }
94
+ try {
95
+ await this.issuePrivacyToken(jid, record.tcTokenSenderTimestamp);
96
+ }
97
+ catch (error) {
98
+ this.logger.warn('send-tc-token-device-identity-change-failed', {
99
+ jid,
100
+ message: toError(error).message
101
+ });
102
+ }
103
+ }
104
+ async hydrateFromHistorySync(conversations) {
105
+ const nowMs = Date.now();
106
+ const records = [];
107
+ for (let i = 0; i < conversations.length; i += 1) {
108
+ const conv = conversations[i];
109
+ if (!conv.tcToken && !conv.tcTokenTimestamp && !conv.tcTokenSenderTimestamp) {
110
+ continue;
111
+ }
112
+ records[records.length] = {
113
+ jid: conv.jid,
114
+ tcToken: conv.tcToken ?? undefined,
115
+ tcTokenTimestamp: conv.tcTokenTimestamp ?? undefined,
116
+ tcTokenSenderTimestamp: conv.tcTokenSenderTimestamp ?? undefined,
117
+ updatedAtMs: nowMs
118
+ };
119
+ }
120
+ if (records.length > 0) {
121
+ await this.store.upsertBatch(records);
122
+ }
123
+ }
124
+ async handleNctSaltSync(salt) {
125
+ if (salt) {
126
+ await this.store.upsert({
127
+ jid: NCT_SALT_SENTINEL_JID,
128
+ nctSalt: salt,
129
+ updatedAtMs: Date.now()
130
+ });
131
+ this.cachedNctSalt = salt;
132
+ }
133
+ else {
134
+ await this.store.deleteByJid(NCT_SALT_SENTINEL_JID);
135
+ this.cachedNctSalt = null;
136
+ }
137
+ this.nctSaltHydrated = true;
138
+ this.csTokenGenerator.invalidate();
139
+ }
140
+ async hydrateNctSaltFromHistorySync(salt) {
141
+ await this.store.upsert({
142
+ jid: NCT_SALT_SENTINEL_JID,
143
+ nctSalt: salt,
144
+ updatedAtMs: Date.now()
145
+ });
146
+ this.cachedNctSalt = salt;
147
+ this.nctSaltHydrated = true;
148
+ }
149
+ async getNctSalt() {
150
+ if (this.nctSaltHydrated) {
151
+ return this.cachedNctSalt;
152
+ }
153
+ const record = await this.store.getByJid(NCT_SALT_SENTINEL_JID);
154
+ this.cachedNctSalt = record?.nctSalt ?? null;
155
+ this.nctSaltHydrated = true;
156
+ return this.cachedNctSalt;
157
+ }
158
+ async issuePrivacyToken(jid, timestampS) {
159
+ const node = buildPrivacyTokenIqNode({ jid, timestampS });
160
+ await this.runtime.queryWithContext('issue-privacy-token', node);
161
+ }
162
+ }
@@ -24,7 +24,13 @@ function parseDirtyBitNode(node, logger) {
24
24
  };
25
25
  }
26
26
  function resolveAccountSyncProtocols(protocols) {
27
- const selected = protocols.filter((protocol) => ACCOUNT_SYNC_PROTOCOL_SET.has(protocol));
27
+ const selected = [];
28
+ for (let index = 0; index < protocols.length; index += 1) {
29
+ const protocol = protocols[index];
30
+ if (ACCOUNT_SYNC_PROTOCOL_SET.has(protocol)) {
31
+ selected.push(protocol);
32
+ }
33
+ }
28
34
  if (selected.length > 0) {
29
35
  return selected;
30
36
  }
@@ -56,12 +62,24 @@ export async function handleDirtyBits(runtime, dirtyBits) {
56
62
  }
57
63
  unsupported.push(dirtyBit);
58
64
  }
65
+ const supportedTypes = new Array(supported.length);
66
+ for (let index = 0; index < supported.length; index += 1) {
67
+ supportedTypes[index] = supported[index].type;
68
+ }
69
+ const unsupportedTypes = new Array(unsupported.length);
70
+ for (let index = 0; index < unsupported.length; index += 1) {
71
+ unsupportedTypes[index] = unsupported[index].type;
72
+ }
59
73
  runtime.logger.info('handling dirty bits from info bulletin', {
60
- supported: supported.map((entry) => entry.type).join(','),
61
- unsupported: unsupported.map((entry) => entry.type).join(',')
74
+ supported: supportedTypes.join(','),
75
+ unsupported: unsupportedTypes.join(',')
62
76
  });
63
77
  const clearableDirtyBits = [...unsupported];
64
- const settledSupported = await Promise.allSettled(supported.map(async (dirtyBit) => handleDirtyBit(runtime, dirtyBit)));
78
+ const supportedPromises = new Array(supported.length);
79
+ for (let index = 0; index < supported.length; index += 1) {
80
+ supportedPromises[index] = handleDirtyBit(runtime, supported[index]);
81
+ }
82
+ const settledSupported = await Promise.allSettled(supportedPromises);
65
83
  for (let index = 0; index < settledSupported.length; index += 1) {
66
84
  const result = settledSupported[index];
67
85
  if (result.status === 'fulfilled') {
@@ -101,18 +119,23 @@ async function handleAccountSyncDirtyBit(runtime, protocols) {
101
119
  protocols: selectedProtocols.join(',')
102
120
  });
103
121
  const failures = [];
104
- await Promise.all(selectedProtocols.map(async (protocol) => {
105
- try {
106
- await runAccountSyncProtocol(runtime, protocol);
107
- }
108
- catch (error) {
109
- failures.push(protocol);
110
- runtime.logger.warn('account_sync protocol failed', {
111
- protocol,
112
- message: toError(error).message
113
- });
114
- }
115
- }));
122
+ const protocolPromises = new Array(selectedProtocols.length);
123
+ for (let index = 0; index < selectedProtocols.length; index += 1) {
124
+ const protocol = selectedProtocols[index];
125
+ protocolPromises[index] = (async () => {
126
+ try {
127
+ await runAccountSyncProtocol(runtime, protocol);
128
+ }
129
+ catch (error) {
130
+ failures.push(protocol);
131
+ runtime.logger.warn('account_sync protocol failed', {
132
+ protocol,
133
+ message: toError(error).message
134
+ });
135
+ }
136
+ })();
137
+ }
138
+ await Promise.all(protocolPromises);
116
139
  if (failures.length > 0) {
117
140
  throw new Error(`account_sync protocols failed: ${failures.join(',')}`);
118
141
  }
@@ -132,7 +155,7 @@ async function runAccountSyncProtocol(runtime, protocol) {
132
155
  await syncAccountBlocklistDirtyBit(runtime);
133
156
  return;
134
157
  case WA_DIRTY_PROTOCOLS.NOTICE:
135
- await syncAccountNoticeDirtyBit(runtime);
158
+ runtime.logger.info('account_sync notice protocol received (no GraphQL/MEX job configured)');
136
159
  return;
137
160
  default:
138
161
  runtime.logger.debug('unsupported account_sync protocol', {
@@ -207,9 +230,6 @@ async function syncAccountBlocklistDirtyBit(runtime) {
207
230
  logMessage: 'account_sync blocklist synchronized'
208
231
  });
209
232
  }
210
- async function syncAccountNoticeDirtyBit(runtime) {
211
- runtime.logger.info('account_sync notice protocol received (no GraphQL/MEX job configured)');
212
- }
213
233
  async function syncGroupsDirtyBit(runtime) {
214
234
  await runSyncQuery(runtime, {
215
235
  queryContext: 'dirty.groups',