@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
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @zapo-js/fake-server
2
2
 
3
- In-process fake WhatsApp Web server that drives the real `zapo-js` `WaClient` end-to-end — full Noise XX/IK handshake, QR pairing, Signal Protocol (X3DH + Double Ratchet), SenderKey for groups, media upload/download over self-signed HTTPS, app-state sync — all without touching WhatsApp servers.
3
+ In-process fake WhatsApp Web server that drives the real `zapo-js` `WaClient` end-to-end - full Noise XX/IK handshake, QR pairing, Signal Protocol (X3DH + Double Ratchet), SenderKey for groups, media upload/download over self-signed HTTPS, app-state sync - all without touching WhatsApp servers.
4
4
 
5
5
  ## Quick start
6
6
 
@@ -29,8 +29,8 @@ await server.stop()
29
29
  ```text
30
30
  src/
31
31
  ├── api/ # Public-facing API
32
- │ ├── FakeWaServer.ts # Main facade — WS server, IQ router, registries, lifecycle
33
- │ ├── FakePeer.ts # Simulated WhatsApp peer — Signal crypto, send/recv, groups
32
+ │ ├── FakeWaServer.ts # Main facade – WS server, IQ router, registries, lifecycle
33
+ │ ├── FakePeer.ts # Simulated WhatsApp peer – Signal crypto, send/recv, groups
34
34
  │ ├── FakePairingDriver.ts # QR pairing flow orchestrator
35
35
  │ └── Scenario.ts # Declarative test scenario DSL
36
36
  ├── protocol/ # Protocol-layer builders, parsers, crypto
@@ -24,4 +24,3 @@ export declare class AppStateSyncManager {
24
24
  pushServerSyncNotification(pipeline: WaFakeConnectionPipeline, input: BuildServerSyncNotificationInput): Promise<void>;
25
25
  consumeOutboundAppStatePatches(iq: BinaryNode): Promise<void>;
26
26
  }
27
- //# sourceMappingURL=AppStateSyncManager.d.ts.map
@@ -148,4 +148,3 @@ class AppStateSyncManager {
148
148
  }
149
149
  }
150
150
  exports.AppStateSyncManager = AppStateSyncManager;
151
- //# sourceMappingURL=AppStateSyncManager.js.map
@@ -24,4 +24,3 @@ export declare class FakePairingDriver {
24
24
  constructor(options: FakePairingDriverOptions, deps: FakePairingDriverDeps);
25
25
  run(): Promise<void>;
26
26
  }
27
- //# sourceMappingURL=FakePairingDriver.d.ts.map
@@ -33,4 +33,3 @@ class FakePairingDriver {
33
33
  }
34
34
  }
35
35
  exports.FakePairingDriver = FakePairingDriver;
36
- //# sourceMappingURL=FakePairingDriver.js.map
@@ -12,6 +12,13 @@ export interface CreateFakePeerOptions {
12
12
  readonly displayName?: string;
13
13
  readonly keyBundle?: FakePeerKeyBundle;
14
14
  readonly skipOneTimePreKey?: boolean;
15
+ /**
16
+ * Stash every outbound (id → plaintext + pristine ciphertext) so
17
+ * tests / benches can drive replays via {@link FakePeer.replaySentMessage}.
18
+ * Off by default to avoid inflating the fixture's heap on long
19
+ * memory-focused runs that never replay.
20
+ */
21
+ readonly enableReplayCache?: boolean;
15
22
  }
16
23
  export interface SendMessageOptions {
17
24
  readonly id?: string;
@@ -45,20 +52,57 @@ interface FakePeerDeps {
45
52
  export declare class FakePeer {
46
53
  readonly jid: string;
47
54
  readonly displayName?: string;
48
- readonly keyBundle: FakePeerKeyBundle;
55
+ keyBundle: FakePeerKeyBundle;
49
56
  readonly identity: FakePeerIdentity;
50
57
  private readonly deps;
51
- private readonly ratchet;
58
+ private ratchet;
52
59
  private readonly skipOneTimePreKey;
53
- private readonly reservedOneTimePreKey;
60
+ private reservedOneTimePreKey;
54
61
  private ratchetInitiated;
55
62
  private nextMessageCounter;
63
+ private readonly replayCacheEnabled;
64
+ private readonly sentPlaintextById;
65
+ private readonly sentEncryptedById;
56
66
  private readonly senderKeysByGroup;
57
67
  private readonly groupsBootstrapped;
58
68
  private readonly groupRecvSession;
59
69
  private constructor();
60
70
  static create(options: CreateFakePeerOptions, deps: FakePeerDeps): Promise<FakePeer>;
61
71
  sendMessage(message: proto.IMessage, options?: SendMessageOptions): Promise<void>;
72
+ /**
73
+ * Re-encrypts and re-pushes a previously sent message identified by
74
+ * `originalMsgId`. Used by tests / benches that exercise the lib's
75
+ * incoming-retry path: after the lib emits `<receipt type="retry">`
76
+ * back to this peer, the peer is expected to re-send the same
77
+ * plaintext so the lib can decrypt it successfully on the second
78
+ * attempt.
79
+ *
80
+ * The replay reuses the original stanza id by default so the lib's
81
+ * dedup logic (and tests that wait for a specific id) see the resend
82
+ * as a continuation of the same outbound. Pass `resendId` to use a
83
+ * different id.
84
+ */
85
+ replaySentMessage(originalMsgId: string, options?: {
86
+ readonly resendId?: string;
87
+ }): Promise<void>;
88
+ /**
89
+ * Rotates the peer's prekey material and resets the Signal session
90
+ * state so the lib has to re-run X3DH on the next outbound. Keeps
91
+ * the identity key pair (same user) but issues a new signed prekey
92
+ * + new one-time prekey set. After this call:
93
+ * - `keyBundle` reflects the new material;
94
+ * - the underlying `FakePeerDoubleRatchet` is fresh – the next
95
+ * inbound from the lib must be a `pkmsg` (initial signal
96
+ * message), which the new `keyBundle` is wired to decrypt;
97
+ * - any cached one-time prekey reservation in the dispenser is
98
+ * dropped so subsequent retry-receipts include this peer's new
99
+ * keys directly via {@link sendRetryReceipt}.
100
+ *
101
+ * Use this to simulate the "session lost" scenario that real
102
+ * WhatsApp clients trigger when they need the sender to fully
103
+ * re-bootstrap the Signal session via the retry-receipt path.
104
+ */
105
+ rotateForRetry(): Promise<void>;
62
106
  sendConversation(text: string, options?: SendMessageOptions): Promise<void>;
63
107
  sendMessageEdit(input: {
64
108
  readonly targetMessageId: string;
@@ -131,6 +175,36 @@ export declare class FakePeer {
131
175
  sendAppStateSyncKeyShare(input: BuildAppStateSyncKeyShareInput, options?: SendMessageOptions): Promise<void>;
132
176
  sendGroupConversation(groupJid: string, text: string, options?: SendMessageOptions): Promise<void>;
133
177
  sendGroupMessage(groupJid: string, message: proto.IMessage, options?: SendMessageOptions): Promise<void>;
178
+ /**
179
+ * Sends a `<receipt type="retry">` stanza to the lib asking it to
180
+ * re-encrypt and re-send the message identified by `originalMsgId`.
181
+ * Used by benches / tests that exercise the lib's outbound retry
182
+ * replay path.
183
+ *
184
+ * Shape matches WhatsApp Web's `WAWebRetryRequestParser`:
185
+ * `<receipt id type=retry from t><retry id count?/><registration>4B</registration>[<keys>...</keys>]</receipt>`.
186
+ *
187
+ * When `includeKeys` is true (default), the `<keys>` block carries
188
+ * the peer's current identity + signed prekey + one-time prekey.
189
+ * This forces the lib's `WaRetryCoordinator` to tear down the
190
+ * existing session and re-run X3DH against these fresh keys before
191
+ * re-encrypting the original payload, so the resend lands as a
192
+ * `pkmsg` against the peer's *new* prekey state. Pair this with
193
+ * {@link rotateForRetry} to simulate full session loss + recovery.
194
+ *
195
+ * `<device-identity>` is omitted: it's `maybeChild` in wa-web and
196
+ * also optional in the lib's parser. The fake-peer fixture doesn't
197
+ * have an ADV primary-device to sign one, and the lib's outbound
198
+ * retry replay path does not require it.
199
+ */
200
+ sendRetryReceipt(originalMsgId: string, options?: {
201
+ readonly count?: number;
202
+ readonly receiptId?: string;
203
+ readonly t?: number;
204
+ readonly error?: number;
205
+ readonly includeKeys?: boolean;
206
+ }): Promise<void>;
207
+ private buildRetryKeysNode;
134
208
  expectMessage(options?: ExpectMessageOptions): Promise<ReceivedMessage>;
135
209
  expectGroupMessage(groupJid: string, options?: ExpectGroupMessageOptions): Promise<ReceivedMessage>;
136
210
  private decryptPairwise;
@@ -140,4 +214,3 @@ export declare class FakePeer {
140
214
  private nextId;
141
215
  }
142
216
  export {};
143
- //# sourceMappingURL=FakePeer.d.ts.map
@@ -14,6 +14,8 @@ class FakePeer {
14
14
  constructor(keyBundle, options, deps) {
15
15
  this.ratchetInitiated = false;
16
16
  this.nextMessageCounter = 0;
17
+ this.sentPlaintextById = new Map();
18
+ this.sentEncryptedById = new Map();
17
19
  this.senderKeysByGroup = new Map();
18
20
  this.groupsBootstrapped = new Set();
19
21
  this.groupRecvSession = new fake_peer_group_recv_session_1.FakePeerGroupRecvSession();
@@ -26,6 +28,7 @@ class FakePeer {
26
28
  this.displayName = options.displayName;
27
29
  this.deps = deps;
28
30
  this.skipOneTimePreKey = options.skipOneTimePreKey === true;
31
+ this.replayCacheEnabled = options.enableReplayCache === true;
29
32
  this.reservedOneTimePreKey =
30
33
  !this.skipOneTimePreKey && deps.reserveOneTimePreKey
31
34
  ? deps.reserveOneTimePreKey()
@@ -45,6 +48,18 @@ class FakePeer {
45
48
  : ciphertext;
46
49
  const enc = { type, ciphertext: finalCiphertext };
47
50
  const id = options.id ?? this.nextId();
51
+ if (this.replayCacheEnabled) {
52
+ // Stash the plaintext so the bench / a test can replay this
53
+ // exact payload later in response to an incoming retry
54
+ // receipt from the lib (replaySentMessage). Also stash the
55
+ // PRISTINE ciphertext (pre-tamper) + signal type so a
56
+ // replay can put the original frame back on the wire –
57
+ // matches how a real Signal sender re-sends after a
58
+ // recipient asks for retry: same counter + ratchet key,
59
+ // just an uncorrupted body.
60
+ this.sentPlaintextById.set(id, message);
61
+ this.sentEncryptedById.set(id, { type, ciphertext });
62
+ }
48
63
  const stanza = (0, message_1.buildMessage)({
49
64
  id,
50
65
  from: options.from ?? this.jid,
@@ -56,6 +71,76 @@ class FakePeer {
56
71
  });
57
72
  await this.deps.pushStanza(stanza);
58
73
  }
74
+ /**
75
+ * Re-encrypts and re-pushes a previously sent message identified by
76
+ * `originalMsgId`. Used by tests / benches that exercise the lib's
77
+ * incoming-retry path: after the lib emits `<receipt type="retry">`
78
+ * back to this peer, the peer is expected to re-send the same
79
+ * plaintext so the lib can decrypt it successfully on the second
80
+ * attempt.
81
+ *
82
+ * The replay reuses the original stanza id by default so the lib's
83
+ * dedup logic (and tests that wait for a specific id) see the resend
84
+ * as a continuation of the same outbound. Pass `resendId` to use a
85
+ * different id.
86
+ */
87
+ async replaySentMessage(originalMsgId, options = {}) {
88
+ const stored = this.sentEncryptedById.get(originalMsgId);
89
+ if (stored) {
90
+ // Re-send the pristine ciphertext – same Signal counter +
91
+ // ratchet key as the original send, just without the
92
+ // tamper. The lib's recv chain still has the message key
93
+ // for that counter, so this is the recovery path real
94
+ // WhatsApp peers walk after observing a retry receipt for
95
+ // a frame the recipient failed to decrypt.
96
+ const enc = { type: stored.type, ciphertext: stored.ciphertext };
97
+ const id = options.resendId ?? originalMsgId;
98
+ const stanza = (0, message_1.buildMessage)({
99
+ id,
100
+ from: this.jid,
101
+ type: 'text',
102
+ notify: this.displayName,
103
+ enc: [enc]
104
+ });
105
+ await this.deps.pushStanza(stanza);
106
+ return;
107
+ }
108
+ const message = this.sentPlaintextById.get(originalMsgId);
109
+ if (!message) {
110
+ throw new Error(`FakePeer.replaySentMessage: no sent plaintext for id ${originalMsgId}`);
111
+ }
112
+ await this.sendMessage(message, { id: options.resendId ?? originalMsgId });
113
+ }
114
+ /**
115
+ * Rotates the peer's prekey material and resets the Signal session
116
+ * state so the lib has to re-run X3DH on the next outbound. Keeps
117
+ * the identity key pair (same user) but issues a new signed prekey
118
+ * + new one-time prekey set. After this call:
119
+ * - `keyBundle` reflects the new material;
120
+ * - the underlying `FakePeerDoubleRatchet` is fresh – the next
121
+ * inbound from the lib must be a `pkmsg` (initial signal
122
+ * message), which the new `keyBundle` is wired to decrypt;
123
+ * - any cached one-time prekey reservation in the dispenser is
124
+ * dropped so subsequent retry-receipts include this peer's new
125
+ * keys directly via {@link sendRetryReceipt}.
126
+ *
127
+ * Use this to simulate the "session lost" scenario that real
128
+ * WhatsApp clients trigger when they need the sender to fully
129
+ * re-bootstrap the Signal session via the retry-receipt path.
130
+ */
131
+ async rotateForRetry() {
132
+ const refreshed = await (0, fake_peer_key_bundle_1.generateFakePeerKeyBundle)({
133
+ identityKeyPair: this.keyBundle.identityKeyPair,
134
+ registrationId: this.keyBundle.registrationId,
135
+ signedPreKeyId: this.keyBundle.signedPreKey.id + 1,
136
+ firstOneTimePreKeyId: (this.keyBundle.oneTimePreKeys[this.keyBundle.oneTimePreKeys.length - 1]?.id ?? 0) +
137
+ 1
138
+ });
139
+ this.keyBundle = refreshed;
140
+ this.ratchet = new fake_peer_double_ratchet_1.FakePeerDoubleRatchet(refreshed);
141
+ this.ratchetInitiated = false;
142
+ this.reservedOneTimePreKey = null;
143
+ }
59
144
  sendConversation(text, options = {}) {
60
145
  return this.sendMessage({ conversation: text }, options);
61
146
  }
@@ -234,6 +319,112 @@ class FakePeer {
234
319
  });
235
320
  await this.deps.pushStanza(groupStanza);
236
321
  }
322
+ /**
323
+ * Sends a `<receipt type="retry">` stanza to the lib asking it to
324
+ * re-encrypt and re-send the message identified by `originalMsgId`.
325
+ * Used by benches / tests that exercise the lib's outbound retry
326
+ * replay path.
327
+ *
328
+ * Shape matches WhatsApp Web's `WAWebRetryRequestParser`:
329
+ * `<receipt id type=retry from t><retry id count?/><registration>4B</registration>[<keys>...</keys>]</receipt>`.
330
+ *
331
+ * When `includeKeys` is true (default), the `<keys>` block carries
332
+ * the peer's current identity + signed prekey + one-time prekey.
333
+ * This forces the lib's `WaRetryCoordinator` to tear down the
334
+ * existing session and re-run X3DH against these fresh keys before
335
+ * re-encrypting the original payload, so the resend lands as a
336
+ * `pkmsg` against the peer's *new* prekey state. Pair this with
337
+ * {@link rotateForRetry} to simulate full session loss + recovery.
338
+ *
339
+ * `<device-identity>` is omitted: it's `maybeChild` in wa-web and
340
+ * also optional in the lib's parser. The fake-peer fixture doesn't
341
+ * have an ADV primary-device to sign one, and the lib's outbound
342
+ * retry replay path does not require it.
343
+ */
344
+ async sendRetryReceipt(originalMsgId, options = {}) {
345
+ const receiptId = options.receiptId ?? originalMsgId;
346
+ const retryCount = options.count ?? 1;
347
+ const t = options.t ?? Math.floor(Date.now() / 1000);
348
+ const error = options.error;
349
+ const includeKeys = options.includeKeys !== false;
350
+ const registration = new Uint8Array(4);
351
+ const regId = this.keyBundle.registrationId & 0xffffffff;
352
+ registration[0] = (regId >>> 24) & 0xff;
353
+ registration[1] = (regId >>> 16) & 0xff;
354
+ registration[2] = (regId >>> 8) & 0xff;
355
+ registration[3] = regId & 0xff;
356
+ const retryAttrs = {
357
+ v: '1',
358
+ count: String(retryCount),
359
+ id: originalMsgId,
360
+ t: String(t)
361
+ };
362
+ if (error !== undefined && error !== 0) {
363
+ retryAttrs.error = String(error);
364
+ }
365
+ const content = [
366
+ {
367
+ tag: 'retry',
368
+ attrs: retryAttrs
369
+ },
370
+ {
371
+ tag: 'registration',
372
+ attrs: {},
373
+ content: registration
374
+ }
375
+ ];
376
+ if (includeKeys) {
377
+ content.push(this.buildRetryKeysNode());
378
+ }
379
+ const receipt = {
380
+ tag: 'receipt',
381
+ attrs: {
382
+ id: receiptId,
383
+ from: this.jid,
384
+ type: 'retry',
385
+ t: String(t)
386
+ },
387
+ content
388
+ };
389
+ await this.deps.pushStanza(receipt);
390
+ }
391
+ buildRetryKeysNode() {
392
+ const skey = this.keyBundle.signedPreKey;
393
+ const oneTime = this.keyBundle.oneTimePreKeys[0];
394
+ if (!oneTime) {
395
+ throw new Error('FakePeer.sendRetryReceipt: no one-time prekey available');
396
+ }
397
+ const skeyIdBytes = encodeUint3(skey.id);
398
+ const keyIdBytes = encodeUint3(oneTime.id);
399
+ return {
400
+ tag: 'keys',
401
+ attrs: {},
402
+ content: [
403
+ {
404
+ tag: 'identity',
405
+ attrs: {},
406
+ content: this.keyBundle.identityKeyPair.pubKey
407
+ },
408
+ {
409
+ tag: 'skey',
410
+ attrs: {},
411
+ content: [
412
+ { tag: 'id', attrs: {}, content: skeyIdBytes },
413
+ { tag: 'value', attrs: {}, content: skey.keyPair.pubKey },
414
+ { tag: 'signature', attrs: {}, content: skey.signature }
415
+ ]
416
+ },
417
+ {
418
+ tag: 'key',
419
+ attrs: {},
420
+ content: [
421
+ { tag: 'id', attrs: {}, content: keyIdBytes },
422
+ { tag: 'value', attrs: {}, content: oneTime.keyPair.pubKey }
423
+ ]
424
+ }
425
+ ]
426
+ };
427
+ }
237
428
  expectMessage(options = {}) {
238
429
  const timeoutMs = options.timeoutMs ?? 5000;
239
430
  return new Promise((resolve, reject) => {
@@ -395,6 +586,16 @@ function findTopLevelEnc(stanza, type) {
395
586
  }
396
587
  return null;
397
588
  }
589
+ function encodeUint3(value) {
590
+ if (!Number.isInteger(value) || value < 0 || value > 0xffffff) {
591
+ throw new Error(`encodeUint3: value out of range: ${value}`);
592
+ }
593
+ const out = new Uint8Array(3);
594
+ out[0] = (value >>> 16) & 0xff;
595
+ out[1] = (value >>> 8) & 0xff;
596
+ out[2] = value & 0xff;
597
+ return out;
598
+ }
398
599
  function encodePlaintextWithPadding(message) {
399
600
  const encoded = protos_1.proto.Message.encode(message).finish();
400
601
  const padLen = 16 - (encoded.byteLength % 16);
@@ -405,4 +606,3 @@ function encodePlaintextWithPadding(message) {
405
606
  }
406
607
  return out;
407
608
  }
408
- //# sourceMappingURL=FakePeer.js.map
@@ -62,6 +62,7 @@ export declare class FakeWaServer {
62
62
  private readonly pendingStanzaExpectations;
63
63
  private readonly authenticatedListeners;
64
64
  private readonly inboundStanzaListeners;
65
+ private readonly capturedStanzaListeners;
65
66
  private rootCa;
66
67
  private serverStaticKeyPair;
67
68
  private listenInfo;
@@ -122,6 +123,16 @@ export declare class FakeWaServer {
122
123
  scenario(configure: (s: Scenario) => void): void;
123
124
  expectIq(matcher: WaFakeIqMatcher, options?: ExpectIqOptions): Promise<BinaryNode>;
124
125
  capturedStanzaSnapshot(): readonly BinaryNode[];
126
+ /**
127
+ * Subscribes to every stanza captured from the client side (the lib).
128
+ * The listener is called synchronously for each new stanza as it
129
+ * arrives, in addition to `capturedStanzas` being appended. Returns
130
+ * an unsubscribe function. Useful for benches that need to count or
131
+ * react to a stream of receipts/messages without paying the O(N²)
132
+ * cost of polling `capturedStanzaSnapshot()` or queuing one
133
+ * `expectStanza(...)` per iteration.
134
+ */
135
+ onCapturedStanza(listener: (node: BinaryNode) => void): () => void;
125
136
  expectStanza(matcher: StanzaMatcher, options?: ExpectStanzaOptions): Promise<BinaryNode>;
126
137
  broadcastStanza(node: BinaryNode): Promise<number>;
127
138
  waitForAuthenticatedPipeline(timeoutMs?: number): Promise<WaFakeConnectionPipeline>;
@@ -164,4 +175,3 @@ export declare class FakeWaServer {
164
175
  }
165
176
  export type { WaFakeAuthenticatedInfo, WaFakeConnectionPipeline };
166
177
  export type { BinaryNode };
167
- //# sourceMappingURL=FakeWaServer.d.ts.map
@@ -28,6 +28,7 @@ class FakeWaServer {
28
28
  this.pendingStanzaExpectations = new Set();
29
29
  this.authenticatedListeners = new Set();
30
30
  this.inboundStanzaListeners = new Set();
31
+ this.capturedStanzaListeners = new Set();
31
32
  this.rootCa = null;
32
33
  this.serverStaticKeyPair = null;
33
34
  this.listenInfo = null;
@@ -247,6 +248,21 @@ class FakeWaServer {
247
248
  capturedStanzaSnapshot() {
248
249
  return this.capturedStanzas.slice();
249
250
  }
251
+ /**
252
+ * Subscribes to every stanza captured from the client side (the lib).
253
+ * The listener is called synchronously for each new stanza as it
254
+ * arrives, in addition to `capturedStanzas` being appended. Returns
255
+ * an unsubscribe function. Useful for benches that need to count or
256
+ * react to a stream of receipts/messages without paying the O(N²)
257
+ * cost of polling `capturedStanzaSnapshot()` or queuing one
258
+ * `expectStanza(...)` per iteration.
259
+ */
260
+ onCapturedStanza(listener) {
261
+ this.capturedStanzaListeners.add(listener);
262
+ return () => {
263
+ this.capturedStanzaListeners.delete(listener);
264
+ };
265
+ }
250
266
  expectStanza(matcher, options = {}) {
251
267
  const timeoutMs = options.timeoutMs ?? 2000;
252
268
  for (const captured of this.capturedStanzas) {
@@ -318,7 +334,16 @@ class FakeWaServer {
318
334
  }
319
335
  get mediaProxyAgent() {
320
336
  if (!this.cachedMediaProxyAgent) {
321
- this.cachedMediaProxyAgent = new node_https_1.Agent({ rejectUnauthorized: false });
337
+ // keepAlive is required for representative perf measurements
338
+ // and matches what real-world WhatsApp clients do – every
339
+ // media upload/download would otherwise pay a full TLS
340
+ // handshake. The `src/media/__tests__/media.test.ts` fixtures
341
+ // and a production-quality `proxy.mediaUpload` configuration
342
+ // all set this; mirroring it here keeps benches in line.
343
+ this.cachedMediaProxyAgent = new node_https_1.Agent({
344
+ rejectUnauthorized: false,
345
+ keepAlive: true
346
+ });
322
347
  }
323
348
  return this.cachedMediaProxyAgent;
324
349
  }
@@ -472,6 +497,14 @@ class FakeWaServer {
472
497
  }
473
498
  handleCapturedStanza(node) {
474
499
  this.capturedStanzas.push(node);
500
+ for (const listener of this.capturedStanzaListeners) {
501
+ try {
502
+ listener(node);
503
+ }
504
+ catch (error) {
505
+ void error;
506
+ }
507
+ }
475
508
  for (const listener of this.inboundStanzaListeners) {
476
509
  try {
477
510
  listener(node);
@@ -649,4 +682,3 @@ function describeStanzaMatcher(matcher) {
649
682
  parts.push(`childTag=${matcher.childTag}`);
650
683
  return parts.length > 0 ? parts.join(', ') : 'any stanza';
651
684
  }
652
- //# sourceMappingURL=FakeWaServer.js.map
@@ -20,4 +20,3 @@ export declare class PreKeyDispenser {
20
20
  readonly force?: boolean;
21
21
  } | number): Promise<ClientPreKeyBundle>;
22
22
  }
23
- //# sourceMappingURL=PreKeyDispenser.d.ts.map
@@ -95,4 +95,3 @@ class PreKeyDispenser {
95
95
  }
96
96
  }
97
97
  exports.PreKeyDispenser = PreKeyDispenser;
98
- //# sourceMappingURL=PreKeyDispenser.js.map
@@ -21,4 +21,3 @@ export declare class IqExpectation {
21
21
  respond(responder: WaFakeIqResponder): this;
22
22
  respondOnce(response: BinaryNode | WaFakeIqResponder): this;
23
23
  }
24
- //# sourceMappingURL=Scenario.d.ts.map
@@ -35,4 +35,3 @@ class IqExpectation {
35
35
  }
36
36
  }
37
37
  exports.IqExpectation = IqExpectation;
38
- //# sourceMappingURL=Scenario.js.map
@@ -107,4 +107,3 @@ export declare class ServerRegistries {
107
107
  notifyDirtyBitsClear(op: CapturedDirtyBitsClear): void;
108
108
  handleProfilePictureSet(targetJid: string, newId: string): void;
109
109
  }
110
- //# sourceMappingURL=ServerRegistries.d.ts.map
@@ -265,4 +265,3 @@ class ServerRegistries {
265
265
  }
266
266
  }
267
267
  exports.ServerRegistries = ServerRegistries;
268
- //# sourceMappingURL=ServerRegistries.js.map
@@ -44,4 +44,3 @@ export interface IqHandlerDeps {
44
44
  export declare function parseUsyncRequestedUserJids(iq: BinaryNode): readonly string[];
45
45
  export declare function parseRequestedKeyJids(iq: BinaryNode): readonly string[];
46
46
  export declare function registerDefaultIqHandlers(router: WaFakeIqRouter, deps: IqHandlerDeps): void;
47
- //# sourceMappingURL=iq-handlers.d.ts.map
@@ -495,4 +495,3 @@ function registerDefaultIqHandlers(router, deps) {
495
495
  respond: (iq) => (0, router_1.buildIqResult)(iq)
496
496
  });
497
497
  }
498
- //# sourceMappingURL=iq-handlers.js.map
@@ -1,4 +1,3 @@
1
1
  #!/usr/bin/env node
2
2
  /** Standalone CLI for `@zapo-js/fake-server`. */
3
3
  export {};
4
- //# sourceMappingURL=cli.d.ts.map
@@ -62,7 +62,7 @@ function parseArgs(argv) {
62
62
  return { host, port, path, peerJids, groupSpecs, log, quiet, json, help };
63
63
  }
64
64
  function printHelp() {
65
- process.stdout.write(`@zapo-js/fake-server — standalone fake WhatsApp Web server
65
+ process.stdout.write(`@zapo-js/fake-server – standalone fake WhatsApp Web server
66
66
 
67
67
  USAGE
68
68
  npx @zapo-js/fake-server [flags]
@@ -246,4 +246,3 @@ main().catch((error) => {
246
246
  process.stderr.write(`fatal: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`);
247
247
  process.exit(1);
248
248
  });
249
- //# sourceMappingURL=cli.js.map