@zapo-js/fake-server 0.3.0 → 1.0.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 (298) hide show
  1. package/README.md +3 -3
  2. package/dist/api/AppStateSyncManager.d.ts +0 -1
  3. package/dist/api/AppStateSyncManager.js +0 -1
  4. package/dist/api/FakePairingDriver.d.ts +0 -1
  5. package/dist/api/FakePairingDriver.js +0 -1
  6. package/dist/api/FakePeer.d.ts +77 -4
  7. package/dist/api/FakePeer.js +201 -1
  8. package/dist/api/FakeWaServer.d.ts +11 -1
  9. package/dist/api/FakeWaServer.js +34 -2
  10. package/dist/api/PreKeyDispenser.d.ts +0 -1
  11. package/dist/api/PreKeyDispenser.js +0 -1
  12. package/dist/api/Scenario.d.ts +0 -1
  13. package/dist/api/Scenario.js +0 -1
  14. package/dist/api/ServerRegistries.d.ts +0 -1
  15. package/dist/api/ServerRegistries.js +0 -1
  16. package/dist/api/iq-handlers.d.ts +0 -1
  17. package/dist/api/iq-handlers.js +0 -1
  18. package/dist/{cli.d.ts → bin.d.ts} +0 -1
  19. package/dist/{cli.js → bin.js} +1 -2
  20. package/dist/esm/api/AppStateSyncManager.js +146 -0
  21. package/dist/esm/api/FakePairingDriver.js +31 -0
  22. package/dist/esm/api/FakePeer.js +604 -0
  23. package/dist/esm/api/FakeWaServer.js +680 -0
  24. package/dist/esm/api/PreKeyDispenser.js +93 -0
  25. package/dist/esm/api/Scenario.js +32 -0
  26. package/dist/esm/api/ServerRegistries.js +261 -0
  27. package/dist/esm/api/iq-handlers.js +492 -0
  28. package/dist/esm/bin.js +246 -0
  29. package/dist/esm/index.js +26 -0
  30. package/dist/esm/infra/WaFakeConnection.js +49 -0
  31. package/dist/esm/infra/WaFakeConnectionPipeline.js +223 -0
  32. package/dist/esm/infra/WaFakeFrameSocket.js +117 -0
  33. package/dist/esm/infra/WaFakeMediaHttpsServer.js +126 -0
  34. package/dist/esm/infra/WaFakeNoiseHandshake.js +67 -0
  35. package/dist/esm/infra/WaFakeTransport.js +31 -0
  36. package/dist/esm/infra/WaFakeWsServer.js +96 -0
  37. package/dist/esm/package.json +3 -0
  38. package/dist/esm/protocol/auth/cert-chain.js +39 -0
  39. package/dist/esm/protocol/auth/client-payload-validate.js +55 -0
  40. package/dist/esm/protocol/auth/fake-primary-device.js +47 -0
  41. package/dist/esm/protocol/auth/pair-device.js +87 -0
  42. package/dist/esm/protocol/auth/success-node.js +26 -0
  43. package/dist/esm/protocol/iq/abprops.js +30 -0
  44. package/dist/esm/protocol/iq/appstate-sync.js +146 -0
  45. package/dist/esm/protocol/iq/business.js +59 -0
  46. package/dist/esm/protocol/iq/dirty-bits.js +19 -0
  47. package/dist/esm/protocol/iq/group-ops.js +154 -0
  48. package/dist/esm/protocol/iq/newsletter.js +14 -0
  49. package/dist/esm/protocol/iq/prekey-fetch.js +88 -0
  50. package/dist/esm/protocol/iq/privacy-token.js +26 -0
  51. package/dist/esm/protocol/iq/privacy.js +123 -0
  52. package/dist/esm/protocol/iq/profile.js +72 -0
  53. package/dist/esm/protocol/iq/router.js +111 -0
  54. package/dist/esm/protocol/iq/usync.js +64 -0
  55. package/dist/esm/protocol/push/app-state-key-share.js +26 -0
  56. package/dist/esm/protocol/push/call-failure.js +27 -0
  57. package/dist/esm/protocol/push/chatstate.js +23 -0
  58. package/dist/esm/protocol/push/error-stanza.js +13 -0
  59. package/dist/esm/protocol/push/history-sync.js +111 -0
  60. package/dist/esm/protocol/push/message.js +38 -0
  61. package/dist/esm/protocol/push/notification.js +29 -0
  62. package/dist/esm/protocol/push/presence.js +14 -0
  63. package/dist/esm/protocol/push/receipt.js +21 -0
  64. package/dist/esm/protocol/signal/fake-app-state-crypto.js +196 -0
  65. package/dist/esm/protocol/signal/fake-peer-double-ratchet.js +398 -0
  66. package/dist/esm/protocol/signal/fake-peer-group-recv-session.js +120 -0
  67. package/dist/esm/protocol/signal/fake-peer-identity.js +8 -0
  68. package/dist/esm/protocol/signal/fake-peer-key-bundle.js +25 -0
  69. package/dist/esm/protocol/signal/fake-sender-key.js +83 -0
  70. package/dist/esm/protocol/signal/prekey-upload.js +107 -0
  71. package/dist/esm/protocol/stream/stream-error.js +56 -0
  72. package/dist/esm/state/fake-app-state-collection.js +134 -0
  73. package/dist/esm/state/fake-media-store.js +53 -0
  74. package/dist/esm/transport/codec.js +8 -0
  75. package/dist/esm/transport/crypto.js +8 -0
  76. package/dist/esm/transport/protos.js +7 -0
  77. package/dist/esm/transport/util.js +8 -0
  78. package/dist/index.d.ts +0 -1
  79. package/dist/index.js +0 -1
  80. package/dist/infra/WaFakeConnection.d.ts +0 -1
  81. package/dist/infra/WaFakeConnection.js +0 -1
  82. package/dist/infra/WaFakeConnectionPipeline.d.ts +0 -1
  83. package/dist/infra/WaFakeConnectionPipeline.js +27 -24
  84. package/dist/infra/WaFakeFrameSocket.d.ts +0 -1
  85. package/dist/infra/WaFakeFrameSocket.js +0 -1
  86. package/dist/infra/WaFakeMediaHttpsServer.d.ts +0 -1
  87. package/dist/infra/WaFakeMediaHttpsServer.js +0 -1
  88. package/dist/infra/WaFakeNoiseHandshake.d.ts +8 -10
  89. package/dist/infra/WaFakeNoiseHandshake.js +19 -24
  90. package/dist/infra/WaFakeTransport.d.ts +2 -4
  91. package/dist/infra/WaFakeTransport.js +2 -1
  92. package/dist/infra/WaFakeWsServer.d.ts +0 -1
  93. package/dist/infra/WaFakeWsServer.js +0 -1
  94. package/dist/protocol/auth/cert-chain.d.ts +0 -1
  95. package/dist/protocol/auth/cert-chain.js +0 -1
  96. package/dist/protocol/auth/client-payload-validate.d.ts +0 -1
  97. package/dist/protocol/auth/client-payload-validate.js +0 -1
  98. package/dist/protocol/auth/fake-primary-device.d.ts +0 -1
  99. package/dist/protocol/auth/fake-primary-device.js +1 -3
  100. package/dist/protocol/auth/pair-device.d.ts +0 -1
  101. package/dist/protocol/auth/pair-device.js +4 -16
  102. package/dist/protocol/auth/success-node.d.ts +0 -1
  103. package/dist/protocol/auth/success-node.js +0 -1
  104. package/dist/protocol/iq/abprops.d.ts +0 -1
  105. package/dist/protocol/iq/abprops.js +0 -1
  106. package/dist/protocol/iq/appstate-sync.d.ts +0 -1
  107. package/dist/protocol/iq/appstate-sync.js +0 -1
  108. package/dist/protocol/iq/business.d.ts +0 -1
  109. package/dist/protocol/iq/business.js +0 -1
  110. package/dist/protocol/iq/dirty-bits.d.ts +0 -1
  111. package/dist/protocol/iq/dirty-bits.js +0 -1
  112. package/dist/protocol/iq/group-ops.d.ts +0 -1
  113. package/dist/protocol/iq/group-ops.js +0 -1
  114. package/dist/protocol/iq/newsletter.d.ts +0 -1
  115. package/dist/protocol/iq/newsletter.js +0 -1
  116. package/dist/protocol/iq/prekey-fetch.d.ts +0 -1
  117. package/dist/protocol/iq/prekey-fetch.js +0 -1
  118. package/dist/protocol/iq/privacy-token.d.ts +0 -1
  119. package/dist/protocol/iq/privacy-token.js +0 -1
  120. package/dist/protocol/iq/privacy.d.ts +0 -1
  121. package/dist/protocol/iq/privacy.js +15 -16
  122. package/dist/protocol/iq/profile.d.ts +0 -1
  123. package/dist/protocol/iq/profile.js +0 -1
  124. package/dist/protocol/iq/router.d.ts +0 -1
  125. package/dist/protocol/iq/router.js +0 -1
  126. package/dist/protocol/iq/usync.d.ts +0 -1
  127. package/dist/protocol/iq/usync.js +0 -1
  128. package/dist/protocol/push/app-state-key-share.d.ts +0 -1
  129. package/dist/protocol/push/app-state-key-share.js +0 -1
  130. package/dist/protocol/push/call-failure.d.ts +0 -1
  131. package/dist/protocol/push/call-failure.js +0 -1
  132. package/dist/protocol/push/chatstate.d.ts +0 -1
  133. package/dist/protocol/push/chatstate.js +0 -1
  134. package/dist/protocol/push/error-stanza.d.ts +0 -1
  135. package/dist/protocol/push/error-stanza.js +0 -1
  136. package/dist/protocol/push/history-sync.d.ts +0 -1
  137. package/dist/protocol/push/history-sync.js +0 -1
  138. package/dist/protocol/push/message.d.ts +0 -1
  139. package/dist/protocol/push/message.js +0 -1
  140. package/dist/protocol/push/notification.d.ts +0 -1
  141. package/dist/protocol/push/notification.js +0 -1
  142. package/dist/protocol/push/presence.d.ts +0 -1
  143. package/dist/protocol/push/presence.js +0 -1
  144. package/dist/protocol/push/receipt.d.ts +0 -1
  145. package/dist/protocol/push/receipt.js +0 -1
  146. package/dist/protocol/signal/fake-app-state-crypto.d.ts +7 -9
  147. package/dist/protocol/signal/fake-app-state-crypto.js +31 -33
  148. package/dist/protocol/signal/fake-peer-double-ratchet.d.ts +0 -1
  149. package/dist/protocol/signal/fake-peer-double-ratchet.js +23 -28
  150. package/dist/protocol/signal/fake-peer-group-recv-session.d.ts +0 -1
  151. package/dist/protocol/signal/fake-peer-group-recv-session.js +6 -10
  152. package/dist/protocol/signal/fake-peer-identity.d.ts +0 -1
  153. package/dist/protocol/signal/fake-peer-identity.js +0 -1
  154. package/dist/protocol/signal/fake-peer-key-bundle.d.ts +0 -1
  155. package/dist/protocol/signal/fake-peer-key-bundle.js +0 -1
  156. package/dist/protocol/signal/fake-sender-key.d.ts +0 -1
  157. package/dist/protocol/signal/fake-sender-key.js +5 -9
  158. package/dist/protocol/signal/prekey-upload.d.ts +0 -1
  159. package/dist/protocol/signal/prekey-upload.js +0 -1
  160. package/dist/protocol/stream/stream-error.d.ts +0 -1
  161. package/dist/protocol/stream/stream-error.js +0 -1
  162. package/dist/state/fake-app-state-collection.d.ts +0 -1
  163. package/dist/state/fake-app-state-collection.js +0 -1
  164. package/dist/state/fake-media-store.d.ts +0 -1
  165. package/dist/state/fake-media-store.js +2 -2
  166. package/dist/transport/codec.d.ts +2 -3
  167. package/dist/transport/codec.js +2 -3
  168. package/dist/transport/crypto.d.ts +5 -7
  169. package/dist/transport/crypto.js +6 -11
  170. package/dist/transport/protos.d.ts +2 -4
  171. package/dist/transport/protos.js +2 -4
  172. package/dist/transport/util.d.ts +8 -0
  173. package/dist/transport/util.js +15 -0
  174. package/package.json +21 -6
  175. package/dist/api/AppStateSyncManager.d.ts.map +0 -1
  176. package/dist/api/AppStateSyncManager.js.map +0 -1
  177. package/dist/api/FakePairingDriver.d.ts.map +0 -1
  178. package/dist/api/FakePairingDriver.js.map +0 -1
  179. package/dist/api/FakePeer.d.ts.map +0 -1
  180. package/dist/api/FakePeer.js.map +0 -1
  181. package/dist/api/FakeWaServer.d.ts.map +0 -1
  182. package/dist/api/FakeWaServer.js.map +0 -1
  183. package/dist/api/PreKeyDispenser.d.ts.map +0 -1
  184. package/dist/api/PreKeyDispenser.js.map +0 -1
  185. package/dist/api/Scenario.d.ts.map +0 -1
  186. package/dist/api/Scenario.js.map +0 -1
  187. package/dist/api/ServerRegistries.d.ts.map +0 -1
  188. package/dist/api/ServerRegistries.js.map +0 -1
  189. package/dist/api/iq-handlers.d.ts.map +0 -1
  190. package/dist/api/iq-handlers.js.map +0 -1
  191. package/dist/cli.d.ts.map +0 -1
  192. package/dist/cli.js.map +0 -1
  193. package/dist/index.d.ts.map +0 -1
  194. package/dist/index.js.map +0 -1
  195. package/dist/infra/WaFakeConnection.d.ts.map +0 -1
  196. package/dist/infra/WaFakeConnection.js.map +0 -1
  197. package/dist/infra/WaFakeConnectionPipeline.d.ts.map +0 -1
  198. package/dist/infra/WaFakeConnectionPipeline.js.map +0 -1
  199. package/dist/infra/WaFakeFrameSocket.d.ts.map +0 -1
  200. package/dist/infra/WaFakeFrameSocket.js.map +0 -1
  201. package/dist/infra/WaFakeMediaHttpsServer.d.ts.map +0 -1
  202. package/dist/infra/WaFakeMediaHttpsServer.js.map +0 -1
  203. package/dist/infra/WaFakeNoiseHandshake.d.ts.map +0 -1
  204. package/dist/infra/WaFakeNoiseHandshake.js.map +0 -1
  205. package/dist/infra/WaFakeTransport.d.ts.map +0 -1
  206. package/dist/infra/WaFakeTransport.js.map +0 -1
  207. package/dist/infra/WaFakeWsServer.d.ts.map +0 -1
  208. package/dist/infra/WaFakeWsServer.js.map +0 -1
  209. package/dist/protocol/auth/cert-chain.d.ts.map +0 -1
  210. package/dist/protocol/auth/cert-chain.js.map +0 -1
  211. package/dist/protocol/auth/client-payload-validate.d.ts.map +0 -1
  212. package/dist/protocol/auth/client-payload-validate.js.map +0 -1
  213. package/dist/protocol/auth/fake-primary-device.d.ts.map +0 -1
  214. package/dist/protocol/auth/fake-primary-device.js.map +0 -1
  215. package/dist/protocol/auth/pair-device.d.ts.map +0 -1
  216. package/dist/protocol/auth/pair-device.js.map +0 -1
  217. package/dist/protocol/auth/success-node.d.ts.map +0 -1
  218. package/dist/protocol/auth/success-node.js.map +0 -1
  219. package/dist/protocol/iq/abprops.d.ts.map +0 -1
  220. package/dist/protocol/iq/abprops.js.map +0 -1
  221. package/dist/protocol/iq/appstate-sync.d.ts.map +0 -1
  222. package/dist/protocol/iq/appstate-sync.js.map +0 -1
  223. package/dist/protocol/iq/business.d.ts.map +0 -1
  224. package/dist/protocol/iq/business.js.map +0 -1
  225. package/dist/protocol/iq/dirty-bits.d.ts.map +0 -1
  226. package/dist/protocol/iq/dirty-bits.js.map +0 -1
  227. package/dist/protocol/iq/group-ops.d.ts.map +0 -1
  228. package/dist/protocol/iq/group-ops.js.map +0 -1
  229. package/dist/protocol/iq/newsletter.d.ts.map +0 -1
  230. package/dist/protocol/iq/newsletter.js.map +0 -1
  231. package/dist/protocol/iq/prekey-fetch.d.ts.map +0 -1
  232. package/dist/protocol/iq/prekey-fetch.js.map +0 -1
  233. package/dist/protocol/iq/privacy-token.d.ts.map +0 -1
  234. package/dist/protocol/iq/privacy-token.js.map +0 -1
  235. package/dist/protocol/iq/privacy.d.ts.map +0 -1
  236. package/dist/protocol/iq/privacy.js.map +0 -1
  237. package/dist/protocol/iq/profile.d.ts.map +0 -1
  238. package/dist/protocol/iq/profile.js.map +0 -1
  239. package/dist/protocol/iq/router.d.ts.map +0 -1
  240. package/dist/protocol/iq/router.js.map +0 -1
  241. package/dist/protocol/iq/usync.d.ts.map +0 -1
  242. package/dist/protocol/iq/usync.js.map +0 -1
  243. package/dist/protocol/push/app-state-key-share.d.ts.map +0 -1
  244. package/dist/protocol/push/app-state-key-share.js.map +0 -1
  245. package/dist/protocol/push/call-failure.d.ts.map +0 -1
  246. package/dist/protocol/push/call-failure.js.map +0 -1
  247. package/dist/protocol/push/chatstate.d.ts.map +0 -1
  248. package/dist/protocol/push/chatstate.js.map +0 -1
  249. package/dist/protocol/push/error-stanza.d.ts.map +0 -1
  250. package/dist/protocol/push/error-stanza.js.map +0 -1
  251. package/dist/protocol/push/history-sync.d.ts.map +0 -1
  252. package/dist/protocol/push/history-sync.js.map +0 -1
  253. package/dist/protocol/push/message.d.ts.map +0 -1
  254. package/dist/protocol/push/message.js.map +0 -1
  255. package/dist/protocol/push/notification.d.ts.map +0 -1
  256. package/dist/protocol/push/notification.js.map +0 -1
  257. package/dist/protocol/push/presence.d.ts.map +0 -1
  258. package/dist/protocol/push/presence.js.map +0 -1
  259. package/dist/protocol/push/receipt.d.ts.map +0 -1
  260. package/dist/protocol/push/receipt.js.map +0 -1
  261. package/dist/protocol/signal/fake-app-state-crypto.d.ts.map +0 -1
  262. package/dist/protocol/signal/fake-app-state-crypto.js.map +0 -1
  263. package/dist/protocol/signal/fake-peer-double-ratchet.d.ts.map +0 -1
  264. package/dist/protocol/signal/fake-peer-double-ratchet.js.map +0 -1
  265. package/dist/protocol/signal/fake-peer-group-recv-session.d.ts.map +0 -1
  266. package/dist/protocol/signal/fake-peer-group-recv-session.js.map +0 -1
  267. package/dist/protocol/signal/fake-peer-identity.d.ts.map +0 -1
  268. package/dist/protocol/signal/fake-peer-identity.js.map +0 -1
  269. package/dist/protocol/signal/fake-peer-key-bundle.d.ts.map +0 -1
  270. package/dist/protocol/signal/fake-peer-key-bundle.js.map +0 -1
  271. package/dist/protocol/signal/fake-peer-recv-session.d.ts +0 -60
  272. package/dist/protocol/signal/fake-peer-recv-session.d.ts.map +0 -1
  273. package/dist/protocol/signal/fake-peer-recv-session.js +0 -284
  274. package/dist/protocol/signal/fake-peer-recv-session.js.map +0 -1
  275. package/dist/protocol/signal/fake-peer-session.d.ts +0 -87
  276. package/dist/protocol/signal/fake-peer-session.d.ts.map +0 -1
  277. package/dist/protocol/signal/fake-peer-session.js +0 -207
  278. package/dist/protocol/signal/fake-peer-session.js.map +0 -1
  279. package/dist/protocol/signal/fake-sender-key.d.ts.map +0 -1
  280. package/dist/protocol/signal/fake-sender-key.js.map +0 -1
  281. package/dist/protocol/signal/prekey-upload.d.ts.map +0 -1
  282. package/dist/protocol/signal/prekey-upload.js.map +0 -1
  283. package/dist/protocol/stream/stream-error.d.ts.map +0 -1
  284. package/dist/protocol/stream/stream-error.js.map +0 -1
  285. package/dist/state/fake-app-state-collection.d.ts.map +0 -1
  286. package/dist/state/fake-app-state-collection.js.map +0 -1
  287. package/dist/state/fake-media-store.d.ts.map +0 -1
  288. package/dist/state/fake-media-store.js.map +0 -1
  289. package/dist/transport/codec.d.ts.map +0 -1
  290. package/dist/transport/codec.js.map +0 -1
  291. package/dist/transport/crypto.d.ts.map +0 -1
  292. package/dist/transport/crypto.js.map +0 -1
  293. package/dist/transport/index.d.ts +0 -4
  294. package/dist/transport/index.d.ts.map +0 -1
  295. package/dist/transport/index.js +0 -20
  296. package/dist/transport/index.js.map +0 -1
  297. package/dist/transport/protos.d.ts.map +0 -1
  298. package/dist/transport/protos.js.map +0 -1
@@ -0,0 +1,680 @@
1
+ import { Agent as HttpsAgent } from 'node:https';
2
+ import { WaFakeConnectionPipeline } from '../infra/WaFakeConnectionPipeline.js';
3
+ import { WaFakeMediaHttpsServer } from '../infra/WaFakeMediaHttpsServer.js';
4
+ import { WaFakeWsServer } from '../infra/WaFakeWsServer.js';
5
+ import { generateFakeNoiseRootCa } from '../protocol/auth/cert-chain.js';
6
+ import { WaFakeIqRouter } from '../protocol/iq/router.js';
7
+ import { FakeMediaStore } from '../state/fake-media-store.js';
8
+ import { X25519 } from '../transport/crypto.js';
9
+ import { AppStateSyncManager } from './AppStateSyncManager.js';
10
+ import { FakePairingDriver } from './FakePairingDriver.js';
11
+ import { FakePeer } from './FakePeer.js';
12
+ import { registerDefaultIqHandlers } from './iq-handlers.js';
13
+ import { PreKeyDispenser } from './PreKeyDispenser.js';
14
+ import { Scenario } from './Scenario.js';
15
+ import { ServerRegistries } from './ServerRegistries.js';
16
+ export class FakeWaServer {
17
+ constructor(options = {}) {
18
+ this.registries = new ServerRegistries();
19
+ this.preKeyDispenser = new PreKeyDispenser();
20
+ this.appStateSync = new AppStateSyncManager();
21
+ this.pipelines = new Set();
22
+ this.iqRouter = new WaFakeIqRouter();
23
+ this.capturedStanzas = [];
24
+ this.pendingIqExpectations = new Set();
25
+ this.pendingStanzaExpectations = new Set();
26
+ this.authenticatedListeners = new Set();
27
+ this.inboundStanzaListeners = new Set();
28
+ this.capturedStanzaListeners = new Set();
29
+ this.rootCa = null;
30
+ this.serverStaticKeyPair = null;
31
+ this.listenInfo = null;
32
+ this.pipelineListener = null;
33
+ this.rejectMode = null;
34
+ this.mediaStore = new FakeMediaStore();
35
+ this.mediaHttpsServer = new WaFakeMediaHttpsServer();
36
+ this.capturedMediaUploads = [];
37
+ this.nextUploadCounter = 0;
38
+ this.cachedMediaProxyAgent = null;
39
+ this.wsServer = new WaFakeWsServer(options);
40
+ this.wsServer.onConnection((connection) => this.handleConnection(connection));
41
+ registerDefaultIqHandlers(this.iqRouter, this.buildIqHandlerDeps());
42
+ }
43
+ buildIqHandlerDeps() {
44
+ const reg = this.registries;
45
+ const preKey = this.preKeyDispenser;
46
+ const appState = this.appStateSync;
47
+ return {
48
+ get peerRegistry() {
49
+ return reg.peerRegistry;
50
+ },
51
+ get groupRegistry() {
52
+ return reg.groupRegistry;
53
+ },
54
+ get privacySettings() {
55
+ return reg.privacySettings;
56
+ },
57
+ get blocklistJids() {
58
+ return reg.blocklistJids;
59
+ },
60
+ get profilePicturesByJid() {
61
+ return reg.profilePicturesByJid;
62
+ },
63
+ get businessProfilesByJid() {
64
+ return reg.businessProfilesByJid;
65
+ },
66
+ get abPropsInput() {
67
+ return reg.abPropsInput;
68
+ },
69
+ get issuedPrivacyTokens() {
70
+ return reg.issuedPrivacyTokens;
71
+ },
72
+ get latestStatusText() {
73
+ return reg.latestStatusText;
74
+ },
75
+ setLatestStatusText: (text) => {
76
+ reg.latestStatusText = text;
77
+ },
78
+ lookupDeviceIdsForUser: (userJid) => reg.lookupDeviceIdsForUser(userJid),
79
+ notifyGroupOp: (op) => reg.notifyGroupOp(op),
80
+ mutatePrivacySettings: (category, value) => reg.mutatePrivacySettings(category, value),
81
+ mutateBlocklist: (action, jid) => reg.mutateBlocklist(action, jid),
82
+ notifyProfilePictureSet: (op) => reg.notifyProfilePictureSet(op),
83
+ handleProfilePictureSet: (targetJid, newId) => reg.handleProfilePictureSet(targetJid, newId),
84
+ notifyStatusSet: (text) => reg.notifyStatusSet(text),
85
+ notifyLogout: () => reg.notifyLogout(),
86
+ notifyPrivacyTokenIssue: (token) => reg.notifyPrivacyTokenIssue(token),
87
+ notifyDirtyBitsClear: (op) => reg.notifyDirtyBitsClear(op),
88
+ notifyPrivacySet: (change) => {
89
+ for (const listener of reg.privacySetListeners) {
90
+ try {
91
+ listener(change);
92
+ }
93
+ catch (error) {
94
+ void error;
95
+ }
96
+ }
97
+ },
98
+ notifyBlocklistChange: (change) => {
99
+ for (const listener of reg.blocklistChangeListeners) {
100
+ try {
101
+ listener(change);
102
+ }
103
+ catch (error) {
104
+ void error;
105
+ }
106
+ }
107
+ },
108
+ capturePreKeyBundle: (bundle) => preKey.captureBundle(bundle),
109
+ consumeOutboundAppStatePatches: (iq) => appState.consumeOutboundAppStatePatches(iq),
110
+ get appStateCollectionProviders() {
111
+ return appState.appStateCollectionProviders;
112
+ },
113
+ requireMediaHttpsInfo: () => this.requireMediaHttpsInfo()
114
+ };
115
+ }
116
+ registerIqHandler(matcher, respond, label) {
117
+ const handler = { matcher, respond, label };
118
+ return this.iqRouter.register(handler, { priority: 'high' });
119
+ }
120
+ async routeIqForTest(iq) {
121
+ return this.iqRouter.route(iq);
122
+ }
123
+ onOutboundGroupOp(listener) {
124
+ return this.registries.onOutboundGroupOp(listener);
125
+ }
126
+ onOutboundPrivacySet(listener) {
127
+ return this.registries.onOutboundPrivacySet(listener);
128
+ }
129
+ onOutboundBlocklistChange(listener) {
130
+ return this.registries.onOutboundBlocklistChange(listener);
131
+ }
132
+ onOutboundProfilePictureSet(listener) {
133
+ return this.registries.onOutboundProfilePictureSet(listener);
134
+ }
135
+ onOutboundStatusSet(listener) {
136
+ return this.registries.onOutboundStatusSet(listener);
137
+ }
138
+ onLogout(listener) {
139
+ return this.registries.onLogout(listener);
140
+ }
141
+ onOutboundPrivacyTokenIssue(listener) {
142
+ return this.registries.onOutboundPrivacyTokenIssue(listener);
143
+ }
144
+ onOutboundDirtyBitsClear(listener) {
145
+ return this.registries.onOutboundDirtyBitsClear(listener);
146
+ }
147
+ privacyTokensIssuedSnapshot() {
148
+ return this.registries.privacyTokensIssuedSnapshot();
149
+ }
150
+ privacySettingsSnapshot() {
151
+ return this.registries.privacySettingsSnapshot();
152
+ }
153
+ blocklistSnapshot() {
154
+ return this.registries.blocklistSnapshot();
155
+ }
156
+ latestStatusSnapshot() {
157
+ return this.registries.latestStatusSnapshot();
158
+ }
159
+ groupRegistrySnapshot() {
160
+ return this.registries.groupRegistrySnapshot();
161
+ }
162
+ setAbProps(input) {
163
+ this.registries.setAbProps(input);
164
+ }
165
+ setPrivacyDisallowedList(category, jids) {
166
+ this.registries.setPrivacyDisallowedList(category, jids);
167
+ }
168
+ setProfilePictureRecord(jid, picture) {
169
+ this.registries.setProfilePictureRecord(jid, picture);
170
+ }
171
+ setBusinessProfileRecord(jid, profile) {
172
+ this.registries.setBusinessProfileRecord(jid, profile);
173
+ }
174
+ createFakeGroup(input) {
175
+ return this.registries.createFakeGroup(input);
176
+ }
177
+ async triggerPreKeyUpload(pipeline, options = {}) {
178
+ return this.preKeyDispenser.triggerPreKeyUpload(pipeline, options);
179
+ }
180
+ awaitPreKeyBundle(timeoutMs = 15000) {
181
+ return this.preKeyDispenser.awaitPreKeyBundle(timeoutMs);
182
+ }
183
+ capturedPreKeyBundleSnapshot() {
184
+ return this.preKeyDispenser.capturedPreKeyBundleSnapshot();
185
+ }
186
+ dispenseOneTimePreKey() {
187
+ return this.preKeyDispenser.dispenseOneTimePreKey();
188
+ }
189
+ preKeyDispenserMissesSnapshot() {
190
+ return this.preKeyDispenser.preKeyDispenserMissesSnapshot();
191
+ }
192
+ preKeysAvailable() {
193
+ return this.preKeyDispenser.preKeysAvailable();
194
+ }
195
+ registerAppStateSyncKey(keyId, keyData) {
196
+ this.appStateSync.registerAppStateSyncKey(keyId, keyData);
197
+ }
198
+ onOutboundAppStateMutation(listener) {
199
+ return this.appStateSync.onOutboundAppStateMutation(listener);
200
+ }
201
+ expectAppStateMutation(predicate, timeoutMs = 15000) {
202
+ return this.appStateSync.expectAppStateMutation(predicate, timeoutMs);
203
+ }
204
+ provideAppStateCollection(name, provider) {
205
+ return this.appStateSync.provideAppStateCollection(name, provider);
206
+ }
207
+ async pushServerSyncNotification(pipeline, input) {
208
+ return this.appStateSync.pushServerSyncNotification(pipeline, input);
209
+ }
210
+ onAuthenticatedPipeline(listener) {
211
+ this.authenticatedListeners.add(listener);
212
+ return () => this.authenticatedListeners.delete(listener);
213
+ }
214
+ scenario(configure) {
215
+ configure(new Scenario(this));
216
+ }
217
+ expectIq(matcher, options = {}) {
218
+ const timeoutMs = options.timeoutMs ?? 2000;
219
+ for (const captured of this.capturedStanzas) {
220
+ if (matchesIq(captured, matcher)) {
221
+ return Promise.resolve(captured);
222
+ }
223
+ }
224
+ return new Promise((resolve, reject) => {
225
+ const expectation = {
226
+ matcher,
227
+ resolve: (iq) => {
228
+ this.pendingIqExpectations.delete(expectation);
229
+ clearTimeout(expectation.timer);
230
+ resolve(iq);
231
+ },
232
+ reject: (error) => {
233
+ this.pendingIqExpectations.delete(expectation);
234
+ clearTimeout(expectation.timer);
235
+ reject(error);
236
+ },
237
+ timer: setTimeout(() => {
238
+ this.pendingIqExpectations.delete(expectation);
239
+ reject(new Error(`expectIq timed out after ${timeoutMs}ms (${describeMatcher(matcher)})`));
240
+ }, timeoutMs)
241
+ };
242
+ this.pendingIqExpectations.add(expectation);
243
+ });
244
+ }
245
+ capturedStanzaSnapshot() {
246
+ return this.capturedStanzas.slice();
247
+ }
248
+ /**
249
+ * Subscribes to every stanza captured from the client side (the lib).
250
+ * The listener is called synchronously for each new stanza as it
251
+ * arrives, in addition to `capturedStanzas` being appended. Returns
252
+ * an unsubscribe function. Useful for benches that need to count or
253
+ * react to a stream of receipts/messages without paying the O(N²)
254
+ * cost of polling `capturedStanzaSnapshot()` or queuing one
255
+ * `expectStanza(...)` per iteration.
256
+ */
257
+ onCapturedStanza(listener) {
258
+ this.capturedStanzaListeners.add(listener);
259
+ return () => {
260
+ this.capturedStanzaListeners.delete(listener);
261
+ };
262
+ }
263
+ expectStanza(matcher, options = {}) {
264
+ const timeoutMs = options.timeoutMs ?? 2000;
265
+ for (const captured of this.capturedStanzas) {
266
+ if (matchesStanza(captured, matcher)) {
267
+ return Promise.resolve(captured);
268
+ }
269
+ }
270
+ return new Promise((resolve, reject) => {
271
+ const expectation = {
272
+ matcher,
273
+ resolve: (node) => {
274
+ this.pendingStanzaExpectations.delete(expectation);
275
+ clearTimeout(expectation.timer);
276
+ resolve(node);
277
+ },
278
+ reject: (error) => {
279
+ this.pendingStanzaExpectations.delete(expectation);
280
+ clearTimeout(expectation.timer);
281
+ reject(error);
282
+ },
283
+ timer: setTimeout(() => {
284
+ this.pendingStanzaExpectations.delete(expectation);
285
+ reject(new Error(`expectStanza timed out after ${timeoutMs}ms (${describeStanzaMatcher(matcher)})`));
286
+ }, timeoutMs)
287
+ };
288
+ this.pendingStanzaExpectations.add(expectation);
289
+ });
290
+ }
291
+ async broadcastStanza(node) {
292
+ const tasks = [];
293
+ for (const pipeline of this.pipelines) {
294
+ tasks.push(pipeline.sendStanza(node).catch(() => undefined));
295
+ }
296
+ await Promise.all(tasks);
297
+ return tasks.length;
298
+ }
299
+ waitForAuthenticatedPipeline(timeoutMs = 60000) {
300
+ for (const pipeline of this.pipelines) {
301
+ if (pipeline.isAuthenticated()) {
302
+ return Promise.resolve(pipeline);
303
+ }
304
+ }
305
+ return this.waitForNextAuthenticatedPipeline(timeoutMs);
306
+ }
307
+ waitForNextAuthenticatedPipeline(timeoutMs = 60000) {
308
+ return new Promise((resolve, reject) => {
309
+ const timer = setTimeout(() => reject(new Error(`waitForNextAuthenticatedPipeline timed out after ${timeoutMs}ms`)), timeoutMs);
310
+ const unregister = this.onAuthenticatedPipeline((pipeline) => {
311
+ clearTimeout(timer);
312
+ unregister();
313
+ resolve(pipeline);
314
+ });
315
+ });
316
+ }
317
+ async publishMediaBlob(input) {
318
+ return this.mediaStore.publish(input);
319
+ }
320
+ mediaUrl(path) {
321
+ const info = this.requireMediaHttpsInfo();
322
+ const normalized = path.startsWith('/') ? path : `/${path}`;
323
+ return `https://${info.host}:${info.port}${normalized}`;
324
+ }
325
+ requireMediaHttpsInfo() {
326
+ const info = this.mediaHttpsServer.info;
327
+ if (!info) {
328
+ throw new Error('fake media https server is not listening');
329
+ }
330
+ return info;
331
+ }
332
+ get mediaProxyAgent() {
333
+ if (!this.cachedMediaProxyAgent) {
334
+ // keepAlive is required for representative perf measurements
335
+ // and matches what real-world WhatsApp clients do – every
336
+ // media upload/download would otherwise pay a full TLS
337
+ // handshake. The `src/media/__tests__/media.test.ts` fixtures
338
+ // and a production-quality `proxy.mediaUpload` configuration
339
+ // all set this; mirroring it here keeps benches in line.
340
+ this.cachedMediaProxyAgent = new HttpsAgent({
341
+ rejectUnauthorized: false,
342
+ keepAlive: true
343
+ });
344
+ }
345
+ return this.cachedMediaProxyAgent;
346
+ }
347
+ capturedMediaUploadSnapshot() {
348
+ return this.capturedMediaUploads.slice();
349
+ }
350
+ async createFakePeer(options, pipeline) {
351
+ const peer = await FakePeer.create(options, this.buildFakePeerDeps(pipeline));
352
+ this.registries.registerPeer(peer);
353
+ return peer;
354
+ }
355
+ async createFakePeerWithDevices(input, pipeline) {
356
+ if (input.deviceIds.length === 0) {
357
+ throw new Error('createFakePeerWithDevices requires at least one deviceId');
358
+ }
359
+ const atIdx = input.userJid.indexOf('@');
360
+ if (atIdx < 0) {
361
+ throw new Error(`invalid userJid ${input.userJid}`);
362
+ }
363
+ const userPart = input.userJid.slice(0, atIdx);
364
+ const server = input.userJid.slice(atIdx + 1);
365
+ const peers = [];
366
+ for (const deviceId of input.deviceIds) {
367
+ const deviceJid = deviceId === 0 ? input.userJid : `${userPart}:${deviceId}@${server}`;
368
+ const peer = await FakePeer.create({
369
+ jid: deviceJid,
370
+ displayName: input.displayName,
371
+ skipOneTimePreKey: input.skipOneTimePreKey
372
+ }, this.buildFakePeerDeps(pipeline));
373
+ this.registries.registerPeer(peer);
374
+ peers.push(peer);
375
+ }
376
+ return peers;
377
+ }
378
+ buildFakePeerDeps(pipeline) {
379
+ return {
380
+ bundleResolver: () => this.preKeyDispenser.awaitPreKeyBundle(),
381
+ reserveOneTimePreKey: () => this.preKeyDispenser.dispenseOneTimePreKey(),
382
+ pushStanza: (stanza) => pipeline.sendStanza(stanza),
383
+ subscribeInboundMessages: (listener) => {
384
+ const wrapped = (node) => {
385
+ if (node.tag !== 'message')
386
+ return;
387
+ listener(node);
388
+ };
389
+ this.inboundStanzaListeners.add(wrapped);
390
+ return () => {
391
+ this.inboundStanzaListeners.delete(wrapped);
392
+ };
393
+ }
394
+ };
395
+ }
396
+ async runPairing(pipeline, options, companionMaterialResolver) {
397
+ const driver = new FakePairingDriver(options, {
398
+ pipeline,
399
+ companionMaterialResolver,
400
+ waitForPairDeviceAck: async (pairDeviceIqId) => {
401
+ await this.expectIq({
402
+ id: pairDeviceIqId
403
+ }, {
404
+ timeoutMs: 15000
405
+ });
406
+ }
407
+ });
408
+ await driver.run();
409
+ }
410
+ setRejectMode(info) {
411
+ if (info === null) {
412
+ this.rejectMode = null;
413
+ return;
414
+ }
415
+ this.rejectMode = {
416
+ code: info.code ?? 1011,
417
+ reason: info.reason ?? 'fake-server reject mode'
418
+ };
419
+ }
420
+ static async start(options = {}) {
421
+ const server = new FakeWaServer(options);
422
+ await server.listen();
423
+ return server;
424
+ }
425
+ get url() {
426
+ return this.requireListening().url;
427
+ }
428
+ get host() {
429
+ return this.requireListening().host;
430
+ }
431
+ get port() {
432
+ return this.requireListening().port;
433
+ }
434
+ get noiseRootCa() {
435
+ const root = this.requireRootCa();
436
+ return { publicKey: root.publicKey, serial: root.serial };
437
+ }
438
+ onPipeline(listener) {
439
+ this.pipelineListener = listener;
440
+ }
441
+ async listen() {
442
+ if (this.listenInfo) {
443
+ return;
444
+ }
445
+ ;
446
+ [this.rootCa, this.serverStaticKeyPair] = await Promise.all([
447
+ generateFakeNoiseRootCa(),
448
+ X25519.generateKeyPair()
449
+ ]);
450
+ const mediaHandler = this.buildMediaRequestHandler();
451
+ this.wsServer.setHttpRequestHandler(mediaHandler);
452
+ this.mediaHttpsServer.setRequestHandler(mediaHandler);
453
+ this.listenInfo = await this.wsServer.listen();
454
+ await this.mediaHttpsServer.listen('127.0.0.1');
455
+ }
456
+ async stop() {
457
+ this.pipelines.clear();
458
+ await this.wsServer.close();
459
+ await this.mediaHttpsServer.close();
460
+ if (this.cachedMediaProxyAgent) {
461
+ this.cachedMediaProxyAgent.destroy();
462
+ this.cachedMediaProxyAgent = null;
463
+ }
464
+ this.listenInfo = null;
465
+ this.rootCa = null;
466
+ this.serverStaticKeyPair = null;
467
+ }
468
+ handleConnection(connection) {
469
+ if (this.rejectMode) {
470
+ connection.close(this.rejectMode.code, this.rejectMode.reason);
471
+ return;
472
+ }
473
+ if (!this.rootCa || !this.serverStaticKeyPair) {
474
+ connection.close(1011, 'fake server not initialized');
475
+ return;
476
+ }
477
+ const pipeline = new WaFakeConnectionPipeline({
478
+ connection,
479
+ rootCa: this.rootCa,
480
+ serverStaticKeyPair: this.serverStaticKeyPair,
481
+ iqRouter: this.iqRouter
482
+ });
483
+ this.pipelines.add(pipeline);
484
+ pipeline.setEvents({
485
+ onAuthenticated: () => {
486
+ for (const listener of this.authenticatedListeners) {
487
+ void listener(pipeline);
488
+ }
489
+ },
490
+ onStanza: (node) => this.handleCapturedStanza(node),
491
+ onClose: () => this.pipelines.delete(pipeline)
492
+ });
493
+ this.pipelineListener?.(pipeline);
494
+ }
495
+ handleCapturedStanza(node) {
496
+ this.capturedStanzas.push(node);
497
+ for (const listener of this.capturedStanzaListeners) {
498
+ try {
499
+ listener(node);
500
+ }
501
+ catch (error) {
502
+ void error;
503
+ }
504
+ }
505
+ for (const listener of this.inboundStanzaListeners) {
506
+ try {
507
+ listener(node);
508
+ }
509
+ catch (error) {
510
+ void error;
511
+ }
512
+ }
513
+ for (const expectation of this.pendingStanzaExpectations) {
514
+ if (matchesStanza(node, expectation.matcher)) {
515
+ expectation.resolve(node);
516
+ break;
517
+ }
518
+ }
519
+ if (node.tag !== 'iq') {
520
+ return;
521
+ }
522
+ for (const expectation of this.pendingIqExpectations) {
523
+ if (matchesIq(node, expectation.matcher)) {
524
+ expectation.resolve(node);
525
+ return;
526
+ }
527
+ }
528
+ }
529
+ buildMediaRequestHandler() {
530
+ return (req, res) => {
531
+ const rawUrl = req.url ?? '';
532
+ const [path, query] = rawUrl.split('?');
533
+ const method = (req.method ?? 'GET').toUpperCase();
534
+ if (method === 'POST') {
535
+ this.handleMediaUpload(req, res, path, query);
536
+ return;
537
+ }
538
+ const blob = this.mediaStore.get(path);
539
+ if (!blob) {
540
+ res.statusCode = 404;
541
+ res.end();
542
+ return;
543
+ }
544
+ res.statusCode = 200;
545
+ res.setHeader('content-type', 'application/octet-stream');
546
+ res.setHeader('content-length', String(blob.encryptedBytes.byteLength));
547
+ res.end(Buffer.from(blob.encryptedBytes));
548
+ };
549
+ }
550
+ handleMediaUpload(req, res, path, query) {
551
+ const chunks = [];
552
+ req.on('data', (chunk) => {
553
+ chunks.push(chunk);
554
+ });
555
+ req.on('end', () => {
556
+ const encryptedBytes = new Uint8Array(Buffer.concat(chunks));
557
+ const segments = path.split('/').filter(Boolean);
558
+ const mediaType = segments[1] ?? 'unknown';
559
+ const auth = parseQueryParam(query, 'auth');
560
+ const upload = {
561
+ path,
562
+ mediaType,
563
+ encryptedBytes,
564
+ contentType: req.headers['content-type'],
565
+ auth,
566
+ receivedAtMs: Date.now()
567
+ };
568
+ this.capturedMediaUploads.push(upload);
569
+ this.mediaStore.setRaw(path, encryptedBytes, mediaType);
570
+ this.nextUploadCounter += 1;
571
+ const downloadUrl = this.mediaUrl(path);
572
+ const responseBody = JSON.stringify({
573
+ url: downloadUrl,
574
+ direct_path: path
575
+ });
576
+ res.statusCode = 200;
577
+ res.setHeader('content-type', 'application/json');
578
+ res.setHeader('content-length', String(Buffer.byteLength(responseBody)));
579
+ res.end(responseBody);
580
+ });
581
+ req.on('error', (error) => {
582
+ if (!res.headersSent) {
583
+ res.statusCode = 500;
584
+ }
585
+ res.end(error.message);
586
+ });
587
+ }
588
+ requireListening() {
589
+ if (!this.listenInfo) {
590
+ throw new Error('fake server is not listening');
591
+ }
592
+ return this.listenInfo;
593
+ }
594
+ requireRootCa() {
595
+ if (!this.rootCa) {
596
+ throw new Error('fake server is not listening');
597
+ }
598
+ return this.rootCa;
599
+ }
600
+ }
601
+ function parseQueryParam(query, name) {
602
+ if (!query)
603
+ return undefined;
604
+ for (const pair of query.split('&')) {
605
+ const eq = pair.indexOf('=');
606
+ if (eq < 0)
607
+ continue;
608
+ const key = decodeURIComponent(pair.slice(0, eq));
609
+ if (key !== name)
610
+ continue;
611
+ return decodeURIComponent(pair.slice(eq + 1));
612
+ }
613
+ return undefined;
614
+ }
615
+ function matchesIq(iq, matcher) {
616
+ if (iq.tag !== 'iq')
617
+ return false;
618
+ if (matcher.id !== undefined && iq.attrs.id !== matcher.id)
619
+ return false;
620
+ if (matcher.type !== undefined && iq.attrs.type !== matcher.type)
621
+ return false;
622
+ if (matcher.xmlns !== undefined && iq.attrs.xmlns !== matcher.xmlns)
623
+ return false;
624
+ if (matcher.childTag !== undefined) {
625
+ const children = Array.isArray(iq.content) ? iq.content : null;
626
+ if (!children || children.length === 0)
627
+ return false;
628
+ if (children[0].tag !== matcher.childTag)
629
+ return false;
630
+ }
631
+ return true;
632
+ }
633
+ function describeMatcher(matcher) {
634
+ const parts = [];
635
+ if (matcher.id !== undefined)
636
+ parts.push(`id=${matcher.id}`);
637
+ if (matcher.type !== undefined)
638
+ parts.push(`type=${matcher.type}`);
639
+ if (matcher.xmlns !== undefined)
640
+ parts.push(`xmlns=${matcher.xmlns}`);
641
+ if (matcher.childTag !== undefined)
642
+ parts.push(`childTag=${matcher.childTag}`);
643
+ return parts.length > 0 ? parts.join(', ') : 'any iq';
644
+ }
645
+ function matchesStanza(node, matcher) {
646
+ if (matcher.tag !== undefined && node.tag !== matcher.tag)
647
+ return false;
648
+ if (matcher.type !== undefined && node.attrs.type !== matcher.type)
649
+ return false;
650
+ if (matcher.xmlns !== undefined && node.attrs.xmlns !== matcher.xmlns)
651
+ return false;
652
+ if (matcher.from !== undefined && node.attrs.from !== matcher.from)
653
+ return false;
654
+ if (matcher.to !== undefined && node.attrs.to !== matcher.to)
655
+ return false;
656
+ if (matcher.childTag !== undefined) {
657
+ const children = Array.isArray(node.content) ? node.content : null;
658
+ if (!children || children.length === 0)
659
+ return false;
660
+ if (children[0].tag !== matcher.childTag)
661
+ return false;
662
+ }
663
+ return true;
664
+ }
665
+ function describeStanzaMatcher(matcher) {
666
+ const parts = [];
667
+ if (matcher.tag !== undefined)
668
+ parts.push(`tag=${matcher.tag}`);
669
+ if (matcher.type !== undefined)
670
+ parts.push(`type=${matcher.type}`);
671
+ if (matcher.xmlns !== undefined)
672
+ parts.push(`xmlns=${matcher.xmlns}`);
673
+ if (matcher.from !== undefined)
674
+ parts.push(`from=${matcher.from}`);
675
+ if (matcher.to !== undefined)
676
+ parts.push(`to=${matcher.to}`);
677
+ if (matcher.childTag !== undefined)
678
+ parts.push(`childTag=${matcher.childTag}`);
679
+ return parts.length > 0 ? parts.join(', ') : 'any stanza';
680
+ }