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,803 @@
1
+ import { APP_STATE_DEFAULT_COLLECTIONS, APP_STATE_EMPTY_LT_HASH } from './constants.js';
2
+ import { keyIdToHex } from './utils.js';
3
+ import { WaAppStateCrypto } from './WaAppStateCrypto.js';
4
+ import { parseSyncResponse } from './WaAppStateSyncResponseParser.js';
5
+ import { proto } from '../proto.js';
6
+ import { WA_APP_STATE_COLLECTION_STATES, WA_DEFAULTS, WA_IQ_TYPES, WA_NODE_TAGS, WA_XMLNS } from '../protocol/constants.js';
7
+ import { decodeProtoBytes } from '../util/base64.js';
8
+ import { uint8Equal } from '../util/bytes.js';
9
+ import { longToNumber } from '../util/primitives.js';
10
+ export class WaAppStateMissingKeyError extends Error {
11
+ constructor(message, keyId, collection) {
12
+ super(message);
13
+ this.keyId = keyId;
14
+ this.collection = collection;
15
+ this.name = 'WaAppStateMissingKeyError';
16
+ }
17
+ }
18
+ export class WaAppStateSyncClient {
19
+ constructor(options) {
20
+ this.logger = options.logger;
21
+ this.query = options.query;
22
+ this.store = options.store;
23
+ this.hostDomain = options.hostDomain ?? WA_DEFAULTS.HOST_DOMAIN;
24
+ this.defaultTimeoutMs = options.defaultTimeoutMs ?? WA_DEFAULTS.APP_STATE_SYNC_TIMEOUT_MS;
25
+ this.onMissingKeys = options.onMissingKeys;
26
+ this.crypto = new WaAppStateCrypto();
27
+ this.syncContext = null;
28
+ this.syncPromise = null;
29
+ }
30
+ async exportState() {
31
+ this.logger.trace('app-state export requested');
32
+ return this.store.exportData();
33
+ }
34
+ async importSyncKeys(keys) {
35
+ this.logger.debug('app-state importing sync keys', { count: keys.length });
36
+ const inserted = await this.store.upsertSyncKeys(keys);
37
+ if (inserted > 0) {
38
+ this.crypto.clearCache();
39
+ this.logger.info('app-state sync keys persisted', { inserted });
40
+ }
41
+ return inserted;
42
+ }
43
+ async importSyncKeyShare(share) {
44
+ const keys = [];
45
+ for (const item of share.keys ?? []) {
46
+ const keyId = decodeProtoBytes(item.keyId?.keyId, 'appStateSyncKeyShare.keys[].keyId.keyId');
47
+ if (!item.keyData?.keyData) {
48
+ this.logger.debug('app-state sync key share entry missing key data', {
49
+ keyId: keyIdToHex(keyId)
50
+ });
51
+ continue;
52
+ }
53
+ const keyData = decodeProtoBytes(item.keyData?.keyData, 'appStateSyncKeyShare.keys[].keyData.keyData');
54
+ keys.push({
55
+ keyId,
56
+ keyData,
57
+ timestamp: item.keyData?.timestamp === null || item.keyData?.timestamp === undefined
58
+ ? Date.now()
59
+ : this.normalizeProtoLong(item.keyData?.timestamp, 'appStateSyncKeyShare.keys[].keyData.timestamp'),
60
+ fingerprint: item.keyData?.fingerprint ?? undefined
61
+ });
62
+ }
63
+ return this.importSyncKeys(keys);
64
+ }
65
+ async sync(options = {}) {
66
+ if (this.syncPromise) {
67
+ this.logger.debug('app-state sync already in flight, joining existing run');
68
+ return this.syncPromise;
69
+ }
70
+ const inFlight = this.syncOnce(options);
71
+ this.syncPromise = inFlight;
72
+ try {
73
+ return await inFlight;
74
+ }
75
+ finally {
76
+ if (this.syncPromise === inFlight) {
77
+ this.syncPromise = null;
78
+ }
79
+ }
80
+ }
81
+ async syncOnce(options = {}) {
82
+ const context = {
83
+ keys: new Map(),
84
+ collections: new Map(),
85
+ dirtyCollections: new Set()
86
+ };
87
+ this.syncContext = context;
88
+ const collections = [
89
+ ...new Set(options.collections ?? APP_STATE_DEFAULT_COLLECTIONS)
90
+ ];
91
+ try {
92
+ this.logger.info('app-state sync start', {
93
+ collections: collections.length,
94
+ pendingMutations: options.pendingMutations?.length ?? 0
95
+ });
96
+ const pendingByCollection = this.groupPendingMutations(options.pendingMutations ?? []);
97
+ const resultMap = new Map();
98
+ let stateChanged = false;
99
+ let collectionsToSync = [...collections];
100
+ const missingKeysHandler = options.onMissingKeys ?? this.onMissingKeys;
101
+ const maxSyncIterations = 5;
102
+ let syncIteration = 0;
103
+ while (collectionsToSync.length > 0) {
104
+ syncIteration += 1;
105
+ if (syncIteration > maxSyncIterations) {
106
+ this.logger.warn('app-state sync reached max iterations', {
107
+ maxSyncIterations,
108
+ remainingCollections: collectionsToSync
109
+ });
110
+ for (const collection of collectionsToSync) {
111
+ resultMap.set(collection, {
112
+ collection,
113
+ state: WA_APP_STATE_COLLECTION_STATES.ERROR_RETRY
114
+ });
115
+ }
116
+ break;
117
+ }
118
+ const round = await this.syncCollectionsRound(collectionsToSync, pendingByCollection, options);
119
+ stateChanged = stateChanged || round.stateChanged;
120
+ for (const result of round.results) {
121
+ resultMap.set(result.collection, result);
122
+ }
123
+ if (missingKeysHandler &&
124
+ round.missingKeyIds.length > 0 &&
125
+ round.blockedCollections.length > 0) {
126
+ await this.notifyMissingKeys({
127
+ onMissingKeys: missingKeysHandler,
128
+ keyIds: round.missingKeyIds,
129
+ collections: round.blockedCollections
130
+ });
131
+ }
132
+ collectionsToSync = [...round.collectionsToRefetch];
133
+ if (collectionsToSync.length > 0) {
134
+ this.logger.debug('app-state scheduling refetch for collections', {
135
+ iteration: syncIteration,
136
+ collections: collectionsToSync
137
+ });
138
+ }
139
+ }
140
+ if (stateChanged && context.dirtyCollections.size > 0) {
141
+ await this.persistCollectionUpdates();
142
+ this.logger.info('app-state sync persisted updated state');
143
+ }
144
+ const orderedResults = collections.map((collection) => resultMap.get(collection) ?? {
145
+ collection,
146
+ state: WA_APP_STATE_COLLECTION_STATES.ERROR_RETRY
147
+ });
148
+ this.logger.info('app-state sync finished', {
149
+ collections: orderedResults.length,
150
+ stateChanged
151
+ });
152
+ return { collections: orderedResults };
153
+ }
154
+ finally {
155
+ if (this.syncContext === context) {
156
+ this.syncContext = null;
157
+ }
158
+ }
159
+ }
160
+ async syncCollectionsRound(collections, pendingByCollection, options) {
161
+ const prepared = await this.prepareSyncRoundRequest(collections, pendingByCollection);
162
+ const iqNode = this.buildSyncIqNode(prepared.collectionNodes);
163
+ const payloadByCollection = await this.fetchSyncPayloadByCollection(iqNode, options.timeoutMs ?? this.defaultTimeoutMs);
164
+ const collectionOutcomes = await Promise.all(collections.map((collection) => this.processCollectionRound({
165
+ collection,
166
+ payloadByCollection,
167
+ pendingByCollection,
168
+ options,
169
+ outgoingContexts: prepared.outgoingContexts,
170
+ skippedUploadCollections: prepared.skippedUploadCollections
171
+ })));
172
+ return {
173
+ results: collectionOutcomes.map((entry) => entry.result),
174
+ collectionsToRefetch: collectionOutcomes
175
+ .filter((entry) => entry.shouldRefetch)
176
+ .map((entry) => entry.collection),
177
+ stateChanged: collectionOutcomes.some((entry) => entry.stateChanged),
178
+ missingKeyIds: this.collectDistinctMissingKeyIds(collectionOutcomes
179
+ .map((entry) => entry.missingKeyId)
180
+ .filter((value) => value !== null)),
181
+ blockedCollections: collectionOutcomes
182
+ .filter((entry) => entry.result.state === WA_APP_STATE_COLLECTION_STATES.BLOCKED)
183
+ .map((entry) => entry.collection)
184
+ };
185
+ }
186
+ async prepareSyncRoundRequest(collections, pendingByCollection) {
187
+ const requests = await Promise.all(collections.map((collection) => this.buildCollectionSyncRequest(collection, pendingByCollection)));
188
+ const outgoingContexts = new Map();
189
+ const skippedUploadCollections = new Set();
190
+ for (const request of requests) {
191
+ if (request.outgoingContext) {
192
+ outgoingContexts.set(request.collection, request.outgoingContext);
193
+ }
194
+ if (request.skippedUpload) {
195
+ skippedUploadCollections.add(request.collection);
196
+ }
197
+ }
198
+ return {
199
+ collectionNodes: requests.map((request) => request.node),
200
+ outgoingContexts,
201
+ skippedUploadCollections
202
+ };
203
+ }
204
+ async buildCollectionSyncRequest(collection, pendingByCollection) {
205
+ const collectionState = await this.getCollectionState(collection);
206
+ const hasPersistedState = collectionState.version > 0 ||
207
+ collectionState.indexValueMap.size > 0 ||
208
+ !uint8Equal(collectionState.hash, APP_STATE_EMPTY_LT_HASH);
209
+ const attrs = {
210
+ name: collection
211
+ };
212
+ if (hasPersistedState) {
213
+ attrs.version = String(collectionState.version);
214
+ }
215
+ else {
216
+ attrs.return_snapshot = 'true';
217
+ }
218
+ const children = [];
219
+ const pendingMutations = pendingByCollection.get(collection) ?? [];
220
+ let outgoingContext;
221
+ let skippedUpload = false;
222
+ if (pendingMutations.length > 0) {
223
+ if (!hasPersistedState) {
224
+ skippedUpload = true;
225
+ this.logger.debug('app-state skipped outgoing patch upload until snapshot bootstrap', {
226
+ collection,
227
+ pendingMutations: pendingMutations.length
228
+ });
229
+ }
230
+ else {
231
+ const outgoing = await this.buildOutgoingPatch(collection, collectionState, pendingMutations);
232
+ outgoingContext = outgoing.context;
233
+ children.push({
234
+ tag: WA_NODE_TAGS.PATCH,
235
+ attrs: {},
236
+ content: outgoing.encodedPatch
237
+ });
238
+ }
239
+ }
240
+ return {
241
+ collection,
242
+ outgoingContext,
243
+ skippedUpload,
244
+ node: {
245
+ tag: WA_NODE_TAGS.COLLECTION,
246
+ attrs,
247
+ content: children.length > 0 ? children : undefined
248
+ }
249
+ };
250
+ }
251
+ buildSyncIqNode(collectionNodes) {
252
+ return {
253
+ tag: WA_NODE_TAGS.IQ,
254
+ attrs: {
255
+ to: this.hostDomain,
256
+ type: WA_IQ_TYPES.SET,
257
+ xmlns: WA_XMLNS.APP_STATE_SYNC
258
+ },
259
+ content: [
260
+ {
261
+ tag: WA_NODE_TAGS.SYNC,
262
+ attrs: {},
263
+ content: collectionNodes
264
+ }
265
+ ]
266
+ };
267
+ }
268
+ async fetchSyncPayloadByCollection(iqNode, timeoutMs) {
269
+ const responseNode = await this.query(iqNode, timeoutMs);
270
+ this.logger.debug('app-state sync iq response received', {
271
+ tag: responseNode.tag,
272
+ type: responseNode.attrs.type
273
+ });
274
+ const payloads = parseSyncResponse(responseNode);
275
+ this.logger.debug('app-state sync payloads parsed', { count: payloads.length });
276
+ const payloadByCollection = new Map();
277
+ for (const payload of payloads) {
278
+ payloadByCollection.set(payload.collection, payload);
279
+ }
280
+ return payloadByCollection;
281
+ }
282
+ async processCollectionRound({ collection, payloadByCollection, pendingByCollection, options, outgoingContexts, skippedUploadCollections }) {
283
+ const payload = payloadByCollection.get(collection);
284
+ let shouldRefetch = false;
285
+ let collectionStateChanged = false;
286
+ if (!payload) {
287
+ this.logger.warn('app-state sync response missing collection payload', { collection });
288
+ return this.createCollectionOutcome(collection, WA_APP_STATE_COLLECTION_STATES.ERROR_RETRY);
289
+ }
290
+ if (payload.state === WA_APP_STATE_COLLECTION_STATES.ERROR_FATAL ||
291
+ payload.state === WA_APP_STATE_COLLECTION_STATES.ERROR_RETRY) {
292
+ return this.createCollectionOutcome(collection, payload.state, payload.version);
293
+ }
294
+ const pendingMutationsCount = pendingByCollection.get(collection)?.length ?? 0;
295
+ if (payload.state === WA_APP_STATE_COLLECTION_STATES.CONFLICT_HAS_MORE) {
296
+ shouldRefetch = true;
297
+ }
298
+ else if (payload.state === WA_APP_STATE_COLLECTION_STATES.CONFLICT &&
299
+ pendingMutationsCount > 0) {
300
+ shouldRefetch = true;
301
+ }
302
+ if (payload.state === WA_APP_STATE_COLLECTION_STATES.CONFLICT) {
303
+ return this.createCollectionOutcome(collection, pendingMutationsCount > 0
304
+ ? WA_APP_STATE_COLLECTION_STATES.CONFLICT
305
+ : WA_APP_STATE_COLLECTION_STATES.SUCCESS, payload.version, shouldRefetch);
306
+ }
307
+ if (payload.state === WA_APP_STATE_COLLECTION_STATES.CONFLICT_HAS_MORE) {
308
+ return this.createCollectionOutcome(collection, payload.state, payload.version, shouldRefetch);
309
+ }
310
+ try {
311
+ let appliedMutations = [];
312
+ if (payload.snapshotReference) {
313
+ const downloader = options.downloadExternalBlob;
314
+ if (!downloader) {
315
+ throw new Error(`snapshot for ${payload.collection} requires external blob downloader`);
316
+ }
317
+ const snapshotBytes = await downloader(payload.collection, 'snapshot', payload.snapshotReference);
318
+ const snapshot = this.validateSnapshot(payload.collection, proto.SyncdSnapshot.decode(snapshotBytes));
319
+ const snapshotMutations = await this.applySnapshot(payload.collection, snapshot);
320
+ appliedMutations = appliedMutations.concat(snapshotMutations);
321
+ collectionStateChanged = true;
322
+ }
323
+ if (payload.patches.length > 0) {
324
+ const readyPatches = await this.resolveReadyPatches(payload, options);
325
+ for (const readyPatch of readyPatches) {
326
+ const patchMutations = await this.applyPatch(payload.collection, readyPatch);
327
+ appliedMutations = appliedMutations.concat(patchMutations);
328
+ collectionStateChanged = true;
329
+ }
330
+ }
331
+ else {
332
+ const outgoingContext = outgoingContexts.get(payload.collection);
333
+ if (outgoingContext &&
334
+ payload.state === WA_APP_STATE_COLLECTION_STATES.SUCCESS &&
335
+ payload.version === outgoingContext.patchVersion) {
336
+ this.setCollectionState(payload.collection, outgoingContext.patchVersion, outgoingContext.nextHash, outgoingContext.nextIndexValueMap);
337
+ collectionStateChanged = true;
338
+ }
339
+ }
340
+ if (payload.state === WA_APP_STATE_COLLECTION_STATES.SUCCESS_HAS_MORE) {
341
+ shouldRefetch = true;
342
+ }
343
+ if (payload.state === WA_APP_STATE_COLLECTION_STATES.SUCCESS &&
344
+ skippedUploadCollections.has(collection)) {
345
+ shouldRefetch = true;
346
+ }
347
+ this.logger.debug('app-state collection processed', {
348
+ collection: payload.collection,
349
+ state: payload.state,
350
+ version: payload.version,
351
+ appliedMutations: appliedMutations.length
352
+ });
353
+ return this.createCollectionOutcome(collection, payload.state, payload.version, shouldRefetch, collectionStateChanged, appliedMutations);
354
+ }
355
+ catch (error) {
356
+ if (error instanceof WaAppStateMissingKeyError) {
357
+ this.logger.warn('app-state blocked by missing key', {
358
+ collection: payload.collection,
359
+ message: error.message
360
+ });
361
+ return this.createCollectionOutcome(collection, WA_APP_STATE_COLLECTION_STATES.BLOCKED, payload.version, shouldRefetch, collectionStateChanged, undefined, error.keyId);
362
+ }
363
+ const message = error instanceof Error ? error.message : String(error);
364
+ this.logger.warn('app-state collection processing failed', {
365
+ collection: payload.collection,
366
+ message
367
+ });
368
+ return this.createCollectionOutcome(collection, WA_APP_STATE_COLLECTION_STATES.ERROR_RETRY, payload.version, true, collectionStateChanged);
369
+ }
370
+ }
371
+ createCollectionOutcome(collection, state, version, shouldRefetch = false, stateChanged = false, mutations, missingKeyId = null) {
372
+ return {
373
+ collection,
374
+ shouldRefetch,
375
+ stateChanged,
376
+ missingKeyId,
377
+ result: {
378
+ collection,
379
+ state,
380
+ version,
381
+ ...(mutations ? { mutations } : {})
382
+ }
383
+ };
384
+ }
385
+ collectDistinctMissingKeyIds(keyIds) {
386
+ const byHex = new Map();
387
+ for (const keyId of keyIds) {
388
+ const keyHex = keyIdToHex(keyId);
389
+ if (byHex.has(keyHex)) {
390
+ continue;
391
+ }
392
+ byHex.set(keyHex, keyId);
393
+ }
394
+ return [...byHex.values()];
395
+ }
396
+ async notifyMissingKeys(input) {
397
+ const keyIds = this.collectDistinctMissingKeyIds(input.keyIds);
398
+ const collections = [...new Set(input.collections)];
399
+ if (keyIds.length === 0 || collections.length === 0) {
400
+ return;
401
+ }
402
+ const keyIdsHex = keyIds.map((keyId) => keyIdToHex(keyId));
403
+ this.logger.info('app-state requesting missing sync keys', {
404
+ keys: keyIdsHex.length,
405
+ keyIds: keyIdsHex.join(','),
406
+ collections: collections.join(',')
407
+ });
408
+ try {
409
+ await input.onMissingKeys({
410
+ keyIds,
411
+ collections
412
+ });
413
+ }
414
+ catch (error) {
415
+ this.logger.warn('app-state missing key callback failed', {
416
+ keys: keyIdsHex.length,
417
+ collections: collections.join(','),
418
+ message: error instanceof Error ? error.message : String(error)
419
+ });
420
+ }
421
+ }
422
+ async resolveReadyPatches(payload, options) {
423
+ const sortedPatches = payload.patches
424
+ .map((patch) => ({
425
+ patch,
426
+ sortVersion: this.parseCollectionPatchVersion(payload.collection, patch)
427
+ }))
428
+ .sort((left, right) => left.sortVersion - right.sortVersion)
429
+ .map((entry) => entry.patch);
430
+ return Promise.all(sortedPatches.map(async (patch) => {
431
+ let readyPatch = patch;
432
+ if ((!readyPatch.mutations || readyPatch.mutations.length === 0) &&
433
+ readyPatch.externalMutations) {
434
+ const downloader = options.downloadExternalBlob;
435
+ if (!downloader) {
436
+ throw new Error(`external patch for ${payload.collection} requires external blob downloader`);
437
+ }
438
+ const patchBytes = await downloader(payload.collection, 'patch', readyPatch.externalMutations);
439
+ const decodedMutations = proto.SyncdMutations.decode(patchBytes);
440
+ readyPatch = {
441
+ ...readyPatch,
442
+ mutations: decodedMutations.mutations ?? []
443
+ };
444
+ }
445
+ return this.validatePatch(payload.collection, readyPatch);
446
+ }));
447
+ }
448
+ validateSnapshot(collection, snapshot) {
449
+ if (!snapshot.version?.version) {
450
+ throw new Error(`snapshot for ${collection} is missing version`);
451
+ }
452
+ if (!snapshot.mac) {
453
+ throw new Error(`snapshot for ${collection} is missing mac`);
454
+ }
455
+ if (!snapshot.keyId?.id) {
456
+ throw new Error(`snapshot for ${collection} is missing keyId`);
457
+ }
458
+ return snapshot;
459
+ }
460
+ parseCollectionPatchVersion(collection, patch) {
461
+ const parsed = this.normalizeProtoLong(patch.version?.version, `patch.version.version (${collection})`);
462
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
463
+ throw new Error(`patch for ${collection} has invalid version ${parsed}`);
464
+ }
465
+ return parsed;
466
+ }
467
+ validatePatch(collection, patch) {
468
+ if (!patch.version?.version) {
469
+ throw new Error(`patch for ${collection} is missing version`);
470
+ }
471
+ if (!patch.snapshotMac) {
472
+ throw new Error(`patch for ${collection} is missing snapshotMac`);
473
+ }
474
+ if (!patch.patchMac) {
475
+ throw new Error(`patch for ${collection} is missing patchMac`);
476
+ }
477
+ if (!patch.keyId?.id) {
478
+ throw new Error(`patch for ${collection} is missing keyId`);
479
+ }
480
+ if (patch.mutations && patch.mutations.length > 0 && patch.externalMutations) {
481
+ throw new Error(`patch for ${collection} has inline and external mutations together`);
482
+ }
483
+ if (patch.exitCode?.code !== null &&
484
+ patch.exitCode?.code !== undefined &&
485
+ patch.exitCode.code !== 0) {
486
+ throw new Error(`patch for ${collection} has terminal exitCode ${patch.exitCode.code}: ${patch.exitCode.text ?? ''}`);
487
+ }
488
+ return patch;
489
+ }
490
+ async applySnapshot(collection, snapshot) {
491
+ const version = this.normalizeProtoLong(snapshot.version?.version, `snapshot.version.version (${collection})`);
492
+ const keyId = decodeProtoBytes(snapshot.keyId?.id, `snapshot.keyId.id (${collection})`);
493
+ const keyData = await this.getKeyData(keyId);
494
+ if (!keyData) {
495
+ throw new WaAppStateMissingKeyError(`missing snapshot key ${keyIdToHex(keyId)} for ${collection}`, keyId, collection);
496
+ }
497
+ const indexValueMap = new Map();
498
+ const mutations = [];
499
+ const decryptedRecords = await this.decryptSnapshotRecords(collection, snapshot);
500
+ for (const { decrypted, recordKeyId } of decryptedRecords) {
501
+ const indexMacHex = keyIdToHex(decrypted.indexMac);
502
+ indexValueMap.set(indexMacHex, decrypted.valueMac);
503
+ mutations.push({
504
+ collection,
505
+ operation: 'set',
506
+ source: 'snapshot',
507
+ index: decrypted.index,
508
+ value: decrypted.value,
509
+ version: decrypted.version,
510
+ indexMac: decrypted.indexMac,
511
+ valueMac: decrypted.valueMac,
512
+ keyId: recordKeyId,
513
+ timestamp: this.normalizeProtoLong(decrypted.value?.timestamp, `snapshot.record.value.timestamp (${collection})`)
514
+ });
515
+ }
516
+ const ltHash = await this.crypto.ltHashAdd(APP_STATE_EMPTY_LT_HASH, Array.from(indexValueMap.values()));
517
+ const expectedSnapshotMac = await this.crypto.generateSnapshotMac(keyData, ltHash, version, collection);
518
+ if (!uint8Equal(expectedSnapshotMac, snapshot.mac)) {
519
+ throw new Error(`snapshot MAC mismatch for ${collection}`);
520
+ }
521
+ this.setCollectionState(collection, version, ltHash, indexValueMap);
522
+ return mutations;
523
+ }
524
+ async applyPatch(collection, patch) {
525
+ const patchVersion = this.normalizeProtoLong(patch.version?.version, `patch.version.version (${collection})`);
526
+ const current = await this.getCollectionState(collection);
527
+ if (current.version !== patchVersion - 1) {
528
+ throw new Error(`patch version mismatch for ${collection}: local=${current.version}, incoming=${patchVersion}`);
529
+ }
530
+ const patchKeyId = decodeProtoBytes(patch.keyId?.id, `patch.keyId.id (${collection})`);
531
+ const patchKeyData = await this.getKeyData(patchKeyId);
532
+ if (!patchKeyData) {
533
+ throw new WaAppStateMissingKeyError(`missing patch key ${keyIdToHex(patchKeyId)} for ${collection}`, patchKeyId, collection);
534
+ }
535
+ const decryptedMutations = await this.decryptPatchMutations(collection, patch);
536
+ const nextState = await this.computeNextCollectionState(current.hash, current.indexValueMap, decryptedMutations.map((mutation) => ({
537
+ operation: mutation.operationCode,
538
+ indexMac: mutation.indexMac,
539
+ valueMac: mutation.valueMac
540
+ })), collection);
541
+ await this.assertPatchMacsMatch(patch, collection, patchKeyData, patchVersion, nextState.hash, decryptedMutations);
542
+ this.setCollectionState(collection, patchVersion, nextState.hash, nextState.indexValueMap);
543
+ return decryptedMutations.map(({ operationCode, ...mutation }) => {
544
+ void operationCode;
545
+ return mutation;
546
+ });
547
+ }
548
+ async decryptSnapshotRecords(collection, snapshot) {
549
+ return Promise.all((snapshot.records ?? []).map(async (record) => {
550
+ const indexMac = decodeProtoBytes(record.index?.blob, `snapshot.record.index.blob (${collection})`);
551
+ const valueBlob = decodeProtoBytes(record.value?.blob, `snapshot.record.value.blob (${collection})`);
552
+ const recordKeyId = decodeProtoBytes(record.keyId?.id, `snapshot.record.keyId.id (${collection})`);
553
+ const recordKeyData = await this.getKeyData(recordKeyId);
554
+ if (!recordKeyData) {
555
+ throw new WaAppStateMissingKeyError(`missing snapshot mutation key ${keyIdToHex(recordKeyId)} for ${collection}`, recordKeyId, collection);
556
+ }
557
+ const decrypted = await this.crypto.decryptMutation({
558
+ operation: proto.SyncdMutation.SyncdOperation.SET,
559
+ keyId: recordKeyId,
560
+ keyData: recordKeyData,
561
+ indexMac,
562
+ valueBlob
563
+ });
564
+ return {
565
+ decrypted,
566
+ recordKeyId
567
+ };
568
+ }));
569
+ }
570
+ async decryptPatchMutations(collection, patch) {
571
+ return Promise.all((patch.mutations ?? []).map(async (mutation) => {
572
+ const operationCode = mutation.operation;
573
+ if (operationCode === null || operationCode === undefined) {
574
+ throw new Error(`patch mutation is missing operation (${collection})`);
575
+ }
576
+ const record = mutation.record;
577
+ if (!record) {
578
+ throw new Error(`patch mutation is missing record (${collection})`);
579
+ }
580
+ const indexMac = decodeProtoBytes(record.index?.blob, `patch.record.index.blob (${collection})`);
581
+ const valueBlob = decodeProtoBytes(record.value?.blob, `patch.record.value.blob (${collection})`);
582
+ const recordKeyId = decodeProtoBytes(record.keyId?.id, `patch.record.keyId.id (${collection})`);
583
+ const recordKeyData = await this.getKeyData(recordKeyId);
584
+ if (!recordKeyData) {
585
+ throw new WaAppStateMissingKeyError(`missing mutation key ${keyIdToHex(recordKeyId)} for ${collection}`, recordKeyId, collection);
586
+ }
587
+ const decrypted = await this.crypto.decryptMutation({
588
+ operation: operationCode,
589
+ keyId: recordKeyId,
590
+ keyData: recordKeyData,
591
+ indexMac,
592
+ valueBlob
593
+ });
594
+ return {
595
+ collection,
596
+ operation: operationCode === proto.SyncdMutation.SyncdOperation.REMOVE
597
+ ? 'remove'
598
+ : 'set',
599
+ source: 'patch',
600
+ operationCode,
601
+ index: decrypted.index,
602
+ value: decrypted.value,
603
+ version: decrypted.version,
604
+ indexMac: decrypted.indexMac,
605
+ valueMac: decrypted.valueMac,
606
+ keyId: recordKeyId,
607
+ timestamp: this.normalizeProtoLong(decrypted.value?.timestamp, `patch.record.value.timestamp (${collection})`)
608
+ };
609
+ }));
610
+ }
611
+ async assertPatchMacsMatch(patch, collection, patchKeyData, patchVersion, nextHash, decryptedMutations) {
612
+ const snapshotMac = decodeProtoBytes(patch.snapshotMac, `patch.snapshotMac (${collection})`);
613
+ const expectedSnapshotMac = await this.crypto.generateSnapshotMac(patchKeyData, nextHash, patchVersion, collection);
614
+ if (!uint8Equal(expectedSnapshotMac, snapshotMac)) {
615
+ throw new Error(`patch snapshot MAC mismatch for ${collection}`);
616
+ }
617
+ const patchMac = decodeProtoBytes(patch.patchMac, `patch.patchMac (${collection})`);
618
+ const expectedPatchMac = await this.crypto.generatePatchMac(patchKeyData, snapshotMac, decryptedMutations.map((mutation) => mutation.valueMac), patchVersion, collection);
619
+ if (!uint8Equal(expectedPatchMac, patchMac)) {
620
+ throw new Error(`patch MAC mismatch for ${collection}`);
621
+ }
622
+ }
623
+ async buildOutgoingPatch(collection, snapshot, pendingMutations) {
624
+ const activeKey = await this.store.getActiveSyncKey();
625
+ if (!activeKey) {
626
+ throw new WaAppStateMissingKeyError(`no sync key available to upload ${collection}`, null, collection);
627
+ }
628
+ const encryptedResults = await Promise.all(pendingMutations.map(async (mutation) => {
629
+ const value = mutation.operation === 'set' ? mutation.value : mutation.previousValue;
630
+ const operationCode = mutation.operation === 'remove'
631
+ ? proto.SyncdMutation.SyncdOperation.REMOVE
632
+ : proto.SyncdMutation.SyncdOperation.SET;
633
+ const encrypted = await this.crypto.encryptMutation({
634
+ operation: operationCode,
635
+ keyId: activeKey.keyId,
636
+ keyData: activeKey.keyData,
637
+ index: mutation.index,
638
+ value,
639
+ version: mutation.version
640
+ });
641
+ return { operationCode, encrypted };
642
+ }));
643
+ const encryptedMutations = encryptedResults.map(({ operationCode, encrypted }) => ({
644
+ operation: operationCode,
645
+ record: {
646
+ keyId: { id: activeKey.keyId },
647
+ index: { blob: encrypted.indexMac },
648
+ value: { blob: encrypted.valueBlob }
649
+ }
650
+ }));
651
+ const macMutations = encryptedResults.map(({ operationCode, encrypted }) => ({
652
+ operation: operationCode,
653
+ indexMac: encrypted.indexMac,
654
+ valueMac: encrypted.valueMac
655
+ }));
656
+ const nextState = await this.computeNextCollectionState(snapshot.hash, snapshot.indexValueMap, macMutations, collection);
657
+ const patchVersion = snapshot.version + 1;
658
+ const snapshotMac = await this.crypto.generateSnapshotMac(activeKey.keyData, nextState.hash, patchVersion, collection);
659
+ const patchMac = await this.crypto.generatePatchMac(activeKey.keyData, snapshotMac, macMutations.map((item) => item.valueMac), patchVersion, collection);
660
+ const encodedPatch = proto.SyncdPatch.encode({
661
+ version: { version: patchVersion },
662
+ mutations: encryptedMutations,
663
+ snapshotMac,
664
+ patchMac,
665
+ keyId: { id: activeKey.keyId }
666
+ }).finish();
667
+ return {
668
+ encodedPatch,
669
+ context: {
670
+ collection,
671
+ patchVersion,
672
+ nextHash: nextState.hash,
673
+ nextIndexValueMap: nextState.indexValueMap
674
+ }
675
+ };
676
+ }
677
+ async computeNextCollectionState(baseHash, baseMap, mutations, collection) {
678
+ const indexValueMap = new Map(baseMap);
679
+ const addValues = [];
680
+ const removeValues = [];
681
+ let missingRemoveCount = 0;
682
+ for (const mutation of mutations) {
683
+ const indexMacHex = keyIdToHex(mutation.indexMac);
684
+ const existing = indexValueMap.get(indexMacHex);
685
+ if (mutation.operation === proto.SyncdMutation.SyncdOperation.REMOVE) {
686
+ if (!existing) {
687
+ missingRemoveCount += 1;
688
+ continue;
689
+ }
690
+ indexValueMap.delete(indexMacHex);
691
+ removeValues.push(existing);
692
+ continue;
693
+ }
694
+ if (existing) {
695
+ removeValues.push(existing);
696
+ }
697
+ indexValueMap.set(indexMacHex, mutation.valueMac);
698
+ addValues.push(mutation.valueMac);
699
+ }
700
+ if (missingRemoveCount > 0) {
701
+ this.logger.warn('app-state mutation remove index mac not found', {
702
+ collection,
703
+ missingRemoveCount
704
+ });
705
+ }
706
+ const nextHash = await this.crypto.ltHashSubtractThenAdd(baseHash, addValues, removeValues);
707
+ return {
708
+ hash: nextHash.hash,
709
+ indexValueMap
710
+ };
711
+ }
712
+ normalizeProtoLong(value, field) {
713
+ try {
714
+ return longToNumber(value);
715
+ }
716
+ catch (error) {
717
+ const reason = error instanceof Error ? error.message : String(error);
718
+ throw new Error(`invalid ${field}: ${reason}`);
719
+ }
720
+ }
721
+ groupPendingMutations(pendingMutations) {
722
+ const grouped = new Map();
723
+ for (const mutation of pendingMutations) {
724
+ const list = grouped.get(mutation.collection);
725
+ if (list) {
726
+ list.push(mutation);
727
+ }
728
+ else {
729
+ grouped.set(mutation.collection, [mutation]);
730
+ }
731
+ }
732
+ const compacted = new Map();
733
+ for (const [collection, list] of grouped.entries()) {
734
+ const seenIndexes = new Set();
735
+ const reversed = [];
736
+ for (let index = list.length - 1; index >= 0; index -= 1) {
737
+ const mutation = list[index];
738
+ if (seenIndexes.has(mutation.index)) {
739
+ continue;
740
+ }
741
+ seenIndexes.add(mutation.index);
742
+ reversed.push(mutation);
743
+ }
744
+ compacted.set(collection, reversed.reverse());
745
+ }
746
+ return compacted;
747
+ }
748
+ async getKeyData(keyId) {
749
+ const context = this.requireSyncContext();
750
+ const keyHex = keyIdToHex(keyId);
751
+ if (context.keys.has(keyHex)) {
752
+ return context.keys.get(keyHex) ?? null;
753
+ }
754
+ const value = await this.store.getSyncKeyData(keyId);
755
+ context.keys.set(keyHex, value);
756
+ return value;
757
+ }
758
+ async getCollectionState(collection) {
759
+ const context = this.requireSyncContext();
760
+ const cached = context.collections.get(collection);
761
+ if (cached) {
762
+ return cached;
763
+ }
764
+ const state = await this.store.getCollectionState(collection);
765
+ context.collections.set(collection, state);
766
+ return state;
767
+ }
768
+ setCollectionState(collection, version, hash, indexValueMap) {
769
+ const context = this.requireSyncContext();
770
+ context.collections.set(collection, {
771
+ version,
772
+ hash,
773
+ indexValueMap
774
+ });
775
+ context.dirtyCollections.add(collection);
776
+ }
777
+ async persistCollectionUpdates() {
778
+ const context = this.requireSyncContext();
779
+ const updates = [];
780
+ for (const collection of context.dirtyCollections.values()) {
781
+ const state = context.collections.get(collection);
782
+ if (!state) {
783
+ continue;
784
+ }
785
+ updates.push({
786
+ collection,
787
+ version: state.version,
788
+ hash: state.hash,
789
+ indexValueMap: state.indexValueMap
790
+ });
791
+ }
792
+ if (updates.length === 0) {
793
+ return;
794
+ }
795
+ await this.store.setCollectionStates(updates);
796
+ }
797
+ requireSyncContext() {
798
+ if (!this.syncContext) {
799
+ throw new Error('app-state sync context is not initialized');
800
+ }
801
+ return this.syncContext;
802
+ }
803
+ }