@zapo-js/fake-server 0.3.0 → 1.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 (301) hide show
  1. package/README.md +96 -8
  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 +208 -2
  8. package/dist/api/FakeServerSession.d.ts +67 -0
  9. package/dist/api/FakeServerSession.js +293 -0
  10. package/dist/api/FakeWaServer.d.ts +77 -31
  11. package/dist/api/FakeWaServer.js +140 -259
  12. package/dist/api/PreKeyDispenser.d.ts +0 -1
  13. package/dist/api/PreKeyDispenser.js +0 -1
  14. package/dist/api/Scenario.d.ts +0 -1
  15. package/dist/api/Scenario.js +0 -1
  16. package/dist/api/ServerRegistries.d.ts +0 -1
  17. package/dist/api/ServerRegistries.js +0 -1
  18. package/dist/api/iq-handlers.d.ts +1 -1
  19. package/dist/api/iq-handlers.js +23 -1
  20. package/dist/{cli.d.ts → bin.d.ts} +0 -1
  21. package/dist/bin.js +296 -0
  22. package/dist/esm/api/AppStateSyncManager.js +146 -0
  23. package/dist/esm/api/FakePairingDriver.js +31 -0
  24. package/dist/esm/api/FakePeer.js +610 -0
  25. package/dist/esm/api/FakeServerSession.js +287 -0
  26. package/dist/esm/api/FakeWaServer.js +528 -0
  27. package/dist/esm/api/PreKeyDispenser.js +93 -0
  28. package/dist/esm/api/Scenario.js +32 -0
  29. package/dist/esm/api/ServerRegistries.js +261 -0
  30. package/dist/esm/api/iq-handlers.js +515 -0
  31. package/dist/{cli.js → esm/bin.js} +70 -25
  32. package/dist/esm/index.js +26 -0
  33. package/dist/esm/infra/WaFakeConnection.js +49 -0
  34. package/dist/esm/infra/WaFakeConnectionPipeline.js +235 -0
  35. package/dist/esm/infra/WaFakeFrameSocket.js +117 -0
  36. package/dist/esm/infra/WaFakeMediaHttpsServer.js +126 -0
  37. package/dist/esm/infra/WaFakeNoiseHandshake.js +67 -0
  38. package/dist/esm/infra/WaFakeTransport.js +31 -0
  39. package/dist/esm/infra/WaFakeWsServer.js +96 -0
  40. package/dist/esm/package.json +3 -0
  41. package/dist/esm/protocol/auth/cert-chain.js +50 -0
  42. package/dist/esm/protocol/auth/client-payload-validate.js +55 -0
  43. package/dist/esm/protocol/auth/fake-primary-device.js +47 -0
  44. package/dist/esm/protocol/auth/pair-device.js +87 -0
  45. package/dist/esm/protocol/auth/success-node.js +26 -0
  46. package/dist/esm/protocol/iq/abprops.js +30 -0
  47. package/dist/esm/protocol/iq/appstate-sync.js +146 -0
  48. package/dist/esm/protocol/iq/business.js +59 -0
  49. package/dist/esm/protocol/iq/dirty-bits.js +19 -0
  50. package/dist/esm/protocol/iq/group-ops.js +154 -0
  51. package/dist/esm/protocol/iq/newsletter.js +14 -0
  52. package/dist/esm/protocol/iq/prekey-fetch.js +88 -0
  53. package/dist/esm/protocol/iq/privacy-token.js +26 -0
  54. package/dist/esm/protocol/iq/privacy.js +123 -0
  55. package/dist/esm/protocol/iq/profile.js +72 -0
  56. package/dist/esm/protocol/iq/router.js +117 -0
  57. package/dist/esm/protocol/iq/usync.js +64 -0
  58. package/dist/esm/protocol/push/app-state-key-share.js +26 -0
  59. package/dist/esm/protocol/push/call-failure.js +27 -0
  60. package/dist/esm/protocol/push/chatstate.js +23 -0
  61. package/dist/esm/protocol/push/error-stanza.js +13 -0
  62. package/dist/esm/protocol/push/history-sync.js +111 -0
  63. package/dist/esm/protocol/push/message.js +38 -0
  64. package/dist/esm/protocol/push/notification.js +29 -0
  65. package/dist/esm/protocol/push/presence.js +14 -0
  66. package/dist/esm/protocol/push/receipt.js +21 -0
  67. package/dist/esm/protocol/signal/fake-app-state-crypto.js +196 -0
  68. package/dist/esm/protocol/signal/fake-peer-double-ratchet.js +398 -0
  69. package/dist/esm/protocol/signal/fake-peer-group-recv-session.js +120 -0
  70. package/dist/esm/protocol/signal/fake-peer-identity.js +8 -0
  71. package/dist/esm/protocol/signal/fake-peer-key-bundle.js +25 -0
  72. package/dist/esm/protocol/signal/fake-sender-key.js +83 -0
  73. package/dist/esm/protocol/signal/prekey-upload.js +107 -0
  74. package/dist/esm/protocol/stream/stream-error.js +56 -0
  75. package/dist/esm/state/fake-app-state-collection.js +134 -0
  76. package/dist/esm/state/fake-media-store.js +53 -0
  77. package/dist/esm/transport/codec.js +8 -0
  78. package/dist/esm/transport/crypto.js +8 -0
  79. package/dist/esm/transport/protos.js +7 -0
  80. package/dist/esm/transport/util.js +8 -0
  81. package/dist/index.d.ts +2 -3
  82. package/dist/index.js +2 -2
  83. package/dist/infra/WaFakeConnection.d.ts +0 -1
  84. package/dist/infra/WaFakeConnection.js +0 -1
  85. package/dist/infra/WaFakeConnectionPipeline.d.ts +10 -3
  86. package/dist/infra/WaFakeConnectionPipeline.js +47 -32
  87. package/dist/infra/WaFakeFrameSocket.d.ts +0 -1
  88. package/dist/infra/WaFakeFrameSocket.js +0 -1
  89. package/dist/infra/WaFakeMediaHttpsServer.d.ts +0 -1
  90. package/dist/infra/WaFakeMediaHttpsServer.js +0 -1
  91. package/dist/infra/WaFakeNoiseHandshake.d.ts +8 -10
  92. package/dist/infra/WaFakeNoiseHandshake.js +19 -24
  93. package/dist/infra/WaFakeTransport.d.ts +2 -4
  94. package/dist/infra/WaFakeTransport.js +2 -1
  95. package/dist/infra/WaFakeWsServer.d.ts +0 -1
  96. package/dist/infra/WaFakeWsServer.js +0 -1
  97. package/dist/protocol/auth/cert-chain.d.ts +4 -1
  98. package/dist/protocol/auth/cert-chain.js +13 -3
  99. package/dist/protocol/auth/client-payload-validate.d.ts +0 -1
  100. package/dist/protocol/auth/client-payload-validate.js +0 -1
  101. package/dist/protocol/auth/fake-primary-device.d.ts +0 -1
  102. package/dist/protocol/auth/fake-primary-device.js +1 -3
  103. package/dist/protocol/auth/pair-device.d.ts +0 -1
  104. package/dist/protocol/auth/pair-device.js +4 -16
  105. package/dist/protocol/auth/success-node.d.ts +0 -1
  106. package/dist/protocol/auth/success-node.js +0 -1
  107. package/dist/protocol/iq/abprops.d.ts +0 -1
  108. package/dist/protocol/iq/abprops.js +0 -1
  109. package/dist/protocol/iq/appstate-sync.d.ts +0 -1
  110. package/dist/protocol/iq/appstate-sync.js +0 -1
  111. package/dist/protocol/iq/business.d.ts +0 -1
  112. package/dist/protocol/iq/business.js +0 -1
  113. package/dist/protocol/iq/dirty-bits.d.ts +0 -1
  114. package/dist/protocol/iq/dirty-bits.js +0 -1
  115. package/dist/protocol/iq/group-ops.d.ts +0 -1
  116. package/dist/protocol/iq/group-ops.js +0 -1
  117. package/dist/protocol/iq/newsletter.d.ts +0 -1
  118. package/dist/protocol/iq/newsletter.js +0 -1
  119. package/dist/protocol/iq/prekey-fetch.d.ts +0 -1
  120. package/dist/protocol/iq/prekey-fetch.js +0 -1
  121. package/dist/protocol/iq/privacy-token.d.ts +0 -1
  122. package/dist/protocol/iq/privacy-token.js +0 -1
  123. package/dist/protocol/iq/privacy.d.ts +0 -1
  124. package/dist/protocol/iq/privacy.js +15 -16
  125. package/dist/protocol/iq/profile.d.ts +0 -1
  126. package/dist/protocol/iq/profile.js +0 -1
  127. package/dist/protocol/iq/router.d.ts +7 -2
  128. package/dist/protocol/iq/router.js +8 -3
  129. package/dist/protocol/iq/usync.d.ts +0 -1
  130. package/dist/protocol/iq/usync.js +0 -1
  131. package/dist/protocol/push/app-state-key-share.d.ts +0 -1
  132. package/dist/protocol/push/app-state-key-share.js +0 -1
  133. package/dist/protocol/push/call-failure.d.ts +0 -1
  134. package/dist/protocol/push/call-failure.js +0 -1
  135. package/dist/protocol/push/chatstate.d.ts +0 -1
  136. package/dist/protocol/push/chatstate.js +0 -1
  137. package/dist/protocol/push/error-stanza.d.ts +0 -1
  138. package/dist/protocol/push/error-stanza.js +0 -1
  139. package/dist/protocol/push/history-sync.d.ts +0 -1
  140. package/dist/protocol/push/history-sync.js +0 -1
  141. package/dist/protocol/push/message.d.ts +0 -1
  142. package/dist/protocol/push/message.js +0 -1
  143. package/dist/protocol/push/notification.d.ts +0 -1
  144. package/dist/protocol/push/notification.js +0 -1
  145. package/dist/protocol/push/presence.d.ts +0 -1
  146. package/dist/protocol/push/presence.js +0 -1
  147. package/dist/protocol/push/receipt.d.ts +0 -1
  148. package/dist/protocol/push/receipt.js +0 -1
  149. package/dist/protocol/signal/fake-app-state-crypto.d.ts +7 -9
  150. package/dist/protocol/signal/fake-app-state-crypto.js +31 -33
  151. package/dist/protocol/signal/fake-peer-double-ratchet.d.ts +0 -1
  152. package/dist/protocol/signal/fake-peer-double-ratchet.js +23 -28
  153. package/dist/protocol/signal/fake-peer-group-recv-session.d.ts +0 -1
  154. package/dist/protocol/signal/fake-peer-group-recv-session.js +6 -10
  155. package/dist/protocol/signal/fake-peer-identity.d.ts +0 -1
  156. package/dist/protocol/signal/fake-peer-identity.js +0 -1
  157. package/dist/protocol/signal/fake-peer-key-bundle.d.ts +0 -1
  158. package/dist/protocol/signal/fake-peer-key-bundle.js +0 -1
  159. package/dist/protocol/signal/fake-sender-key.d.ts +0 -1
  160. package/dist/protocol/signal/fake-sender-key.js +5 -9
  161. package/dist/protocol/signal/prekey-upload.d.ts +0 -1
  162. package/dist/protocol/signal/prekey-upload.js +0 -1
  163. package/dist/protocol/stream/stream-error.d.ts +0 -1
  164. package/dist/protocol/stream/stream-error.js +0 -1
  165. package/dist/state/fake-app-state-collection.d.ts +0 -1
  166. package/dist/state/fake-app-state-collection.js +0 -1
  167. package/dist/state/fake-media-store.d.ts +0 -1
  168. package/dist/state/fake-media-store.js +2 -2
  169. package/dist/transport/codec.d.ts +2 -3
  170. package/dist/transport/codec.js +2 -3
  171. package/dist/transport/crypto.d.ts +5 -7
  172. package/dist/transport/crypto.js +6 -11
  173. package/dist/transport/protos.d.ts +2 -4
  174. package/dist/transport/protos.js +2 -4
  175. package/dist/transport/util.d.ts +8 -0
  176. package/dist/transport/util.js +16 -0
  177. package/package.json +26 -6
  178. package/dist/api/AppStateSyncManager.d.ts.map +0 -1
  179. package/dist/api/AppStateSyncManager.js.map +0 -1
  180. package/dist/api/FakePairingDriver.d.ts.map +0 -1
  181. package/dist/api/FakePairingDriver.js.map +0 -1
  182. package/dist/api/FakePeer.d.ts.map +0 -1
  183. package/dist/api/FakePeer.js.map +0 -1
  184. package/dist/api/FakeWaServer.d.ts.map +0 -1
  185. package/dist/api/FakeWaServer.js.map +0 -1
  186. package/dist/api/PreKeyDispenser.d.ts.map +0 -1
  187. package/dist/api/PreKeyDispenser.js.map +0 -1
  188. package/dist/api/Scenario.d.ts.map +0 -1
  189. package/dist/api/Scenario.js.map +0 -1
  190. package/dist/api/ServerRegistries.d.ts.map +0 -1
  191. package/dist/api/ServerRegistries.js.map +0 -1
  192. package/dist/api/iq-handlers.d.ts.map +0 -1
  193. package/dist/api/iq-handlers.js.map +0 -1
  194. package/dist/cli.d.ts.map +0 -1
  195. package/dist/cli.js.map +0 -1
  196. package/dist/index.d.ts.map +0 -1
  197. package/dist/index.js.map +0 -1
  198. package/dist/infra/WaFakeConnection.d.ts.map +0 -1
  199. package/dist/infra/WaFakeConnection.js.map +0 -1
  200. package/dist/infra/WaFakeConnectionPipeline.d.ts.map +0 -1
  201. package/dist/infra/WaFakeConnectionPipeline.js.map +0 -1
  202. package/dist/infra/WaFakeFrameSocket.d.ts.map +0 -1
  203. package/dist/infra/WaFakeFrameSocket.js.map +0 -1
  204. package/dist/infra/WaFakeMediaHttpsServer.d.ts.map +0 -1
  205. package/dist/infra/WaFakeMediaHttpsServer.js.map +0 -1
  206. package/dist/infra/WaFakeNoiseHandshake.d.ts.map +0 -1
  207. package/dist/infra/WaFakeNoiseHandshake.js.map +0 -1
  208. package/dist/infra/WaFakeTransport.d.ts.map +0 -1
  209. package/dist/infra/WaFakeTransport.js.map +0 -1
  210. package/dist/infra/WaFakeWsServer.d.ts.map +0 -1
  211. package/dist/infra/WaFakeWsServer.js.map +0 -1
  212. package/dist/protocol/auth/cert-chain.d.ts.map +0 -1
  213. package/dist/protocol/auth/cert-chain.js.map +0 -1
  214. package/dist/protocol/auth/client-payload-validate.d.ts.map +0 -1
  215. package/dist/protocol/auth/client-payload-validate.js.map +0 -1
  216. package/dist/protocol/auth/fake-primary-device.d.ts.map +0 -1
  217. package/dist/protocol/auth/fake-primary-device.js.map +0 -1
  218. package/dist/protocol/auth/pair-device.d.ts.map +0 -1
  219. package/dist/protocol/auth/pair-device.js.map +0 -1
  220. package/dist/protocol/auth/success-node.d.ts.map +0 -1
  221. package/dist/protocol/auth/success-node.js.map +0 -1
  222. package/dist/protocol/iq/abprops.d.ts.map +0 -1
  223. package/dist/protocol/iq/abprops.js.map +0 -1
  224. package/dist/protocol/iq/appstate-sync.d.ts.map +0 -1
  225. package/dist/protocol/iq/appstate-sync.js.map +0 -1
  226. package/dist/protocol/iq/business.d.ts.map +0 -1
  227. package/dist/protocol/iq/business.js.map +0 -1
  228. package/dist/protocol/iq/dirty-bits.d.ts.map +0 -1
  229. package/dist/protocol/iq/dirty-bits.js.map +0 -1
  230. package/dist/protocol/iq/group-ops.d.ts.map +0 -1
  231. package/dist/protocol/iq/group-ops.js.map +0 -1
  232. package/dist/protocol/iq/newsletter.d.ts.map +0 -1
  233. package/dist/protocol/iq/newsletter.js.map +0 -1
  234. package/dist/protocol/iq/prekey-fetch.d.ts.map +0 -1
  235. package/dist/protocol/iq/prekey-fetch.js.map +0 -1
  236. package/dist/protocol/iq/privacy-token.d.ts.map +0 -1
  237. package/dist/protocol/iq/privacy-token.js.map +0 -1
  238. package/dist/protocol/iq/privacy.d.ts.map +0 -1
  239. package/dist/protocol/iq/privacy.js.map +0 -1
  240. package/dist/protocol/iq/profile.d.ts.map +0 -1
  241. package/dist/protocol/iq/profile.js.map +0 -1
  242. package/dist/protocol/iq/router.d.ts.map +0 -1
  243. package/dist/protocol/iq/router.js.map +0 -1
  244. package/dist/protocol/iq/usync.d.ts.map +0 -1
  245. package/dist/protocol/iq/usync.js.map +0 -1
  246. package/dist/protocol/push/app-state-key-share.d.ts.map +0 -1
  247. package/dist/protocol/push/app-state-key-share.js.map +0 -1
  248. package/dist/protocol/push/call-failure.d.ts.map +0 -1
  249. package/dist/protocol/push/call-failure.js.map +0 -1
  250. package/dist/protocol/push/chatstate.d.ts.map +0 -1
  251. package/dist/protocol/push/chatstate.js.map +0 -1
  252. package/dist/protocol/push/error-stanza.d.ts.map +0 -1
  253. package/dist/protocol/push/error-stanza.js.map +0 -1
  254. package/dist/protocol/push/history-sync.d.ts.map +0 -1
  255. package/dist/protocol/push/history-sync.js.map +0 -1
  256. package/dist/protocol/push/message.d.ts.map +0 -1
  257. package/dist/protocol/push/message.js.map +0 -1
  258. package/dist/protocol/push/notification.d.ts.map +0 -1
  259. package/dist/protocol/push/notification.js.map +0 -1
  260. package/dist/protocol/push/presence.d.ts.map +0 -1
  261. package/dist/protocol/push/presence.js.map +0 -1
  262. package/dist/protocol/push/receipt.d.ts.map +0 -1
  263. package/dist/protocol/push/receipt.js.map +0 -1
  264. package/dist/protocol/signal/fake-app-state-crypto.d.ts.map +0 -1
  265. package/dist/protocol/signal/fake-app-state-crypto.js.map +0 -1
  266. package/dist/protocol/signal/fake-peer-double-ratchet.d.ts.map +0 -1
  267. package/dist/protocol/signal/fake-peer-double-ratchet.js.map +0 -1
  268. package/dist/protocol/signal/fake-peer-group-recv-session.d.ts.map +0 -1
  269. package/dist/protocol/signal/fake-peer-group-recv-session.js.map +0 -1
  270. package/dist/protocol/signal/fake-peer-identity.d.ts.map +0 -1
  271. package/dist/protocol/signal/fake-peer-identity.js.map +0 -1
  272. package/dist/protocol/signal/fake-peer-key-bundle.d.ts.map +0 -1
  273. package/dist/protocol/signal/fake-peer-key-bundle.js.map +0 -1
  274. package/dist/protocol/signal/fake-peer-recv-session.d.ts +0 -60
  275. package/dist/protocol/signal/fake-peer-recv-session.d.ts.map +0 -1
  276. package/dist/protocol/signal/fake-peer-recv-session.js +0 -284
  277. package/dist/protocol/signal/fake-peer-recv-session.js.map +0 -1
  278. package/dist/protocol/signal/fake-peer-session.d.ts +0 -87
  279. package/dist/protocol/signal/fake-peer-session.d.ts.map +0 -1
  280. package/dist/protocol/signal/fake-peer-session.js +0 -207
  281. package/dist/protocol/signal/fake-peer-session.js.map +0 -1
  282. package/dist/protocol/signal/fake-sender-key.d.ts.map +0 -1
  283. package/dist/protocol/signal/fake-sender-key.js.map +0 -1
  284. package/dist/protocol/signal/prekey-upload.d.ts.map +0 -1
  285. package/dist/protocol/signal/prekey-upload.js.map +0 -1
  286. package/dist/protocol/stream/stream-error.d.ts.map +0 -1
  287. package/dist/protocol/stream/stream-error.js.map +0 -1
  288. package/dist/state/fake-app-state-collection.d.ts.map +0 -1
  289. package/dist/state/fake-app-state-collection.js.map +0 -1
  290. package/dist/state/fake-media-store.d.ts.map +0 -1
  291. package/dist/state/fake-media-store.js.map +0 -1
  292. package/dist/transport/codec.d.ts.map +0 -1
  293. package/dist/transport/codec.js.map +0 -1
  294. package/dist/transport/crypto.d.ts.map +0 -1
  295. package/dist/transport/crypto.js.map +0 -1
  296. package/dist/transport/index.d.ts +0 -4
  297. package/dist/transport/index.d.ts.map +0 -1
  298. package/dist/transport/index.js +0 -20
  299. package/dist/transport/index.js.map +0 -1
  300. package/dist/transport/protos.d.ts.map +0 -1
  301. package/dist/transport/protos.js.map +0 -1
package/dist/bin.js ADDED
@@ -0,0 +1,296 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /** Standalone CLI for `@zapo-js/fake-server`. */
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ const node_readline_1 = require("node:readline");
6
+ const FakeWaServer_1 = require("./api/FakeWaServer");
7
+ const pair_device_1 = require("./protocol/auth/pair-device");
8
+ const util_1 = require("./transport/util");
9
+ function parseArgs(argv) {
10
+ let host = '127.0.0.1';
11
+ let port = 0;
12
+ let path = '/ws/chat';
13
+ const peerJids = [];
14
+ const groupSpecs = [];
15
+ let pairJid = null;
16
+ let log = false;
17
+ let quiet = false;
18
+ let json = false;
19
+ let help = false;
20
+ for (let index = 0; index < argv.length; index += 1) {
21
+ const arg = argv[index];
22
+ const next = () => {
23
+ const value = argv[index + 1];
24
+ if (value === undefined) {
25
+ throw new Error(`missing value for ${arg}`);
26
+ }
27
+ index += 1;
28
+ return value;
29
+ };
30
+ switch (arg) {
31
+ case '-h':
32
+ case '--help':
33
+ help = true;
34
+ break;
35
+ case '--host':
36
+ host = next();
37
+ break;
38
+ case '--port':
39
+ port = Number.parseInt(next(), 10);
40
+ if (!Number.isFinite(port) || port < 0 || port > 65535) {
41
+ throw new Error(`invalid --port: ${argv[index]}`);
42
+ }
43
+ break;
44
+ case '--path':
45
+ path = next();
46
+ break;
47
+ case '--peer':
48
+ peerJids.push(next());
49
+ break;
50
+ case '--group':
51
+ groupSpecs.push(next());
52
+ break;
53
+ case '--pair':
54
+ pairJid = next();
55
+ break;
56
+ case '--log':
57
+ log = true;
58
+ break;
59
+ case '--quiet':
60
+ quiet = true;
61
+ break;
62
+ case '--json':
63
+ json = true;
64
+ break;
65
+ default:
66
+ throw new Error(`unknown flag: ${arg}`);
67
+ }
68
+ }
69
+ return { host, port, path, peerJids, groupSpecs, pairJid, log, quiet, json, help };
70
+ }
71
+ function printHelp() {
72
+ process.stdout.write(`@zapo-js/fake-server – standalone fake WhatsApp Web server
73
+
74
+ USAGE
75
+ npx @zapo-js/fake-server [flags]
76
+
77
+ FLAGS
78
+ --host <host> Bind host (default: 127.0.0.1)
79
+ --port <port> WebSocket listener port (default: random)
80
+ --path <path> WebSocket upgrade path (default: /ws/chat)
81
+ --peer <jid> Pre-create a fake peer; can be repeated
82
+ --group <spec> Pre-create a fake group; spec format
83
+ <group-jid>=<peer-jid>,<peer-jid>,...
84
+ All peers must already be passed via --peer.
85
+ --pair <jid> Drive QR pairing for unregistered clients: the server
86
+ sends pair-device refs, then prompts on stdin for the
87
+ QR payload the client displays
88
+ (ref,noisePubB64,identityPubB64,advSecretB64,platform)
89
+ and answers pair-success assigning <jid> as the device
90
+ jid. Without this flag the CLI is login-only.
91
+ --log Print every captured inbound stanza
92
+ --quiet Suppress the startup info banner
93
+ --json Print connection info as JSON
94
+ -h | --help Show this help
95
+
96
+ The server stays up until SIGINT (Ctrl+C).
97
+ `);
98
+ }
99
+ function parseGroupSpec(spec) {
100
+ const eqIdx = spec.indexOf('=');
101
+ if (eqIdx < 0) {
102
+ throw new Error(`invalid --group spec '${spec}': expected '<group-jid>=<peer-jid>,<peer-jid>,...'`);
103
+ }
104
+ const groupJid = spec.slice(0, eqIdx).trim();
105
+ const participantsRaw = spec.slice(eqIdx + 1);
106
+ const participantJids = participantsRaw
107
+ .split(',')
108
+ .map((jid) => jid.trim())
109
+ .filter((jid) => jid.length > 0);
110
+ if (!groupJid || participantJids.length === 0) {
111
+ throw new Error(`invalid --group spec '${spec}': empty group jid or participant list`);
112
+ }
113
+ return { groupJid, participantJids };
114
+ }
115
+ async function main() {
116
+ let args;
117
+ try {
118
+ args = parseArgs(process.argv.slice(2));
119
+ }
120
+ catch (error) {
121
+ process.stderr.write(`error: ${(0, util_1.toError)(error).message}\n`);
122
+ process.stderr.write('run with --help to see usage.\n');
123
+ process.exit(2);
124
+ }
125
+ if (args.help) {
126
+ printHelp();
127
+ process.exit(0);
128
+ }
129
+ const parsedGroups = args.groupSpecs.map(parseGroupSpec);
130
+ for (const group of parsedGroups) {
131
+ for (const jid of group.participantJids) {
132
+ if (!args.peerJids.includes(jid)) {
133
+ process.stderr.write(`error: --group '${group.groupJid}' references peer '${jid}' that was not declared via --peer\n`);
134
+ process.exit(2);
135
+ }
136
+ }
137
+ }
138
+ const server = await FakeWaServer_1.FakeWaServer.start({
139
+ host: args.host,
140
+ port: args.port,
141
+ path: args.path
142
+ });
143
+ if (args.pairJid !== null) {
144
+ const pairJid = args.pairJid;
145
+ let pairingStarted = false;
146
+ server.onAuthenticatedPipeline(async (pipeline) => {
147
+ if (pairingStarted)
148
+ return;
149
+ if (pipeline.clientPayload?.kind !== 'registration')
150
+ return;
151
+ pairingStarted = true;
152
+ try {
153
+ await server.runPairing(pipeline, { deviceJid: pairJid }, async () => {
154
+ const qr = await readStdinLine('[fake-server] paste the QR payload displayed by the client and press Enter:\n');
155
+ const parsed = (0, pair_device_1.parsePairingQrString)(qr.trim());
156
+ return {
157
+ advSecretKey: parsed.advSecretKey,
158
+ identityPublicKey: parsed.identityPublicKey
159
+ };
160
+ });
161
+ process.stdout.write(`[fake-server] pair-success sent for ${pairJid}; the client will now reconnect and log in\n`);
162
+ }
163
+ catch (error) {
164
+ pairingStarted = false;
165
+ process.stderr.write(`pairing failed: ${(0, util_1.toError)(error).message}\n`);
166
+ }
167
+ });
168
+ }
169
+ let setupComplete = false;
170
+ const setupPromise = new Promise((resolve, reject) => {
171
+ server.onAuthenticatedPipeline(async (pipeline) => {
172
+ if (setupComplete)
173
+ return;
174
+ if (pipeline.clientPayload?.kind !== 'login')
175
+ return;
176
+ setupComplete = true;
177
+ try {
178
+ const peers = new Map();
179
+ for (const jid of args.peerJids) {
180
+ const peer = await server.createFakePeer({ jid }, pipeline);
181
+ peers.set(jid, peer);
182
+ process.stdout.write(`[fake-server] created peer ${jid}\n`);
183
+ }
184
+ for (const group of parsedGroups) {
185
+ const participants = group.participantJids.map((jid) => {
186
+ const peer = peers.get(jid);
187
+ if (!peer) {
188
+ throw new Error(`internal: peer ${jid} not found for group ${group.groupJid}`);
189
+ }
190
+ return peer;
191
+ });
192
+ server.createFakeGroup({
193
+ groupJid: group.groupJid,
194
+ participants
195
+ });
196
+ process.stdout.write(`[fake-server] created group ${group.groupJid} with ${participants.length} participants\n`);
197
+ }
198
+ resolve();
199
+ }
200
+ catch (error) {
201
+ reject((0, util_1.toError)(error));
202
+ }
203
+ });
204
+ });
205
+ setupPromise.catch((error) => {
206
+ process.stderr.write(`error: ${error.message}\n`);
207
+ process.exitCode = 1;
208
+ });
209
+ if (args.log) {
210
+ // onCapturedStanza taps the server's own capture stream; overriding
211
+ // pipeline.setEvents here would replace the server's handlers and
212
+ // silently break --peer setup and stanza capture.
213
+ server.onCapturedStanza((node) => {
214
+ process.stdout.write(`[wire] ${node.tag}${node.attrs.id ? ` id=${node.attrs.id}` : ''}${node.attrs.type ? ` type=${node.attrs.type}` : ''}\n`);
215
+ });
216
+ }
217
+ if (!args.quiet) {
218
+ const noiseRoot = server.noiseRootCa;
219
+ if (args.json) {
220
+ process.stdout.write(JSON.stringify({
221
+ url: server.url,
222
+ host: server.host,
223
+ port: server.port,
224
+ path: args.path,
225
+ noiseRootCa: {
226
+ serial: noiseRoot.serial,
227
+ publicKeyHex: (0, util_1.bytesToHex)(noiseRoot.publicKey)
228
+ },
229
+ peers: args.peerJids,
230
+ groups: parsedGroups,
231
+ pair: args.pairJid
232
+ }, null, 2) + '\n');
233
+ }
234
+ else {
235
+ const banner = [
236
+ '',
237
+ '┌─────────────────────────────────────────────────────────────',
238
+ '│ @zapo-js/fake-server is up',
239
+ '├─────────────────────────────────────────────────────────────',
240
+ `│ url ${server.url}`,
241
+ `│ host ${server.host}`,
242
+ `│ port ${server.port}`,
243
+ `│ path ${args.path}`,
244
+ `│ noise root ca serial=${noiseRoot.serial} pub=${(0, util_1.bytesToHex)(noiseRoot.publicKey).slice(0, 32)}…`,
245
+ `│ peers ${args.peerJids.length === 0 ? '(none)' : args.peerJids.join(', ')}`,
246
+ `│ groups ${parsedGroups.length === 0 ? '(none)' : parsedGroups.map((g) => g.groupJid).join(', ')}`,
247
+ `│ pair mode ${args.pairJid ?? '(off – login only)'}`,
248
+ '├─────────────────────────────────────────────────────────────',
249
+ '│ Wire your WaClient with:',
250
+ '│ chatSocketUrls: [server.url]',
251
+ '│ testHooks: { noiseRootCa: <hex above as Uint8Array> }',
252
+ '│ Hit Ctrl+C to stop.',
253
+ '└─────────────────────────────────────────────────────────────',
254
+ ''
255
+ ];
256
+ process.stdout.write(banner.join('\n'));
257
+ }
258
+ }
259
+ let stopping = false;
260
+ const shutdown = async (signal) => {
261
+ if (stopping)
262
+ return;
263
+ stopping = true;
264
+ process.stdout.write(`\n[fake-server] received ${signal}, shutting down…\n`);
265
+ try {
266
+ await server.stop();
267
+ process.stdout.write('[fake-server] stopped cleanly\n');
268
+ process.exit(0);
269
+ }
270
+ catch (error) {
271
+ process.stderr.write(`error during shutdown: ${(0, util_1.toError)(error).message}\n`);
272
+ process.exit(1);
273
+ }
274
+ };
275
+ process.on('SIGINT', () => void shutdown('SIGINT'));
276
+ process.on('SIGTERM', () => void shutdown('SIGTERM'));
277
+ await new Promise(() => undefined);
278
+ }
279
+ function readStdinLine(prompt) {
280
+ process.stdout.write(prompt);
281
+ return new Promise((resolve, reject) => {
282
+ const rl = (0, node_readline_1.createInterface)({ input: process.stdin });
283
+ // resolve before close(): rl.close() emits 'close' synchronously,
284
+ // which would otherwise reject the still-pending promise first.
285
+ rl.once('line', (line) => {
286
+ resolve(line);
287
+ rl.close();
288
+ });
289
+ rl.once('close', () => reject(new Error('stdin closed before a QR payload was provided')));
290
+ });
291
+ }
292
+ main().catch((error) => {
293
+ const normalized = (0, util_1.toError)(error);
294
+ process.stderr.write(`fatal: ${normalized.stack ?? normalized.message}\n`);
295
+ process.exit(1);
296
+ });
@@ -0,0 +1,146 @@
1
+ import { buildServerSyncNotification } from '../protocol/iq/appstate-sync.js';
2
+ import { FakeAppStateCrypto } from '../protocol/signal/fake-app-state-crypto.js';
3
+ import { proto } from '../transport/protos.js';
4
+ function toHex(bytes) {
5
+ let out = '';
6
+ for (let index = 0; index < bytes.byteLength; index += 1) {
7
+ const value = bytes[index];
8
+ out += value < 16 ? `0${value.toString(16)}` : value.toString(16);
9
+ }
10
+ return out;
11
+ }
12
+ export class AppStateSyncManager {
13
+ constructor() {
14
+ this.appStateCollectionProviders = new Map();
15
+ this.appStateSyncKeysByKeyId = new Map();
16
+ this.appStateCrypto = new FakeAppStateCrypto();
17
+ this.outboundMutationListeners = new Set();
18
+ }
19
+ registerAppStateSyncKey(keyId, keyData) {
20
+ this.appStateSyncKeysByKeyId.set(toHex(keyId), keyData);
21
+ }
22
+ onOutboundAppStateMutation(listener) {
23
+ this.outboundMutationListeners.add(listener);
24
+ return () => {
25
+ this.outboundMutationListeners.delete(listener);
26
+ };
27
+ }
28
+ expectAppStateMutation(predicate, timeoutMs = 15000) {
29
+ return new Promise((resolve, reject) => {
30
+ const timer = setTimeout(() => {
31
+ unsubscribe();
32
+ reject(new Error(`expectAppStateMutation timed out after ${timeoutMs}ms`));
33
+ }, timeoutMs);
34
+ const unsubscribe = this.onOutboundAppStateMutation((mutation) => {
35
+ if (!predicate(mutation))
36
+ return;
37
+ clearTimeout(timer);
38
+ unsubscribe();
39
+ resolve(mutation);
40
+ });
41
+ });
42
+ }
43
+ provideAppStateCollection(name, provider) {
44
+ this.appStateCollectionProviders.set(name, provider);
45
+ return () => {
46
+ this.appStateCollectionProviders.delete(name);
47
+ };
48
+ }
49
+ async pushServerSyncNotification(pipeline, input) {
50
+ await pipeline.sendStanza(buildServerSyncNotification(input));
51
+ }
52
+ async consumeOutboundAppStatePatches(iq) {
53
+ if (!Array.isArray(iq.content))
54
+ return;
55
+ const sync = iq.content.find((child) => child.tag === 'sync');
56
+ if (!sync || !Array.isArray(sync.content))
57
+ return;
58
+ for (const collectionNode of sync.content) {
59
+ if (collectionNode.tag !== 'collection')
60
+ continue;
61
+ if (!Array.isArray(collectionNode.content))
62
+ continue;
63
+ const collectionName = collectionNode.attrs.name;
64
+ if (!collectionName)
65
+ continue;
66
+ for (const patchNode of collectionNode.content) {
67
+ if (patchNode.tag !== 'patch')
68
+ continue;
69
+ const patchBytes = patchNode.content;
70
+ if (!(patchBytes instanceof Uint8Array))
71
+ continue;
72
+ try {
73
+ const decoded = proto.SyncdPatch.decode(patchBytes);
74
+ const keyId = decoded.keyId?.id;
75
+ if (!keyId)
76
+ continue;
77
+ const keyData = this.appStateSyncKeysByKeyId.get(toHex(keyId));
78
+ if (!keyData)
79
+ continue;
80
+ const rawVersion = decoded.version?.version;
81
+ let patchVersion = 0;
82
+ if (typeof rawVersion === 'number') {
83
+ patchVersion = rawVersion;
84
+ }
85
+ else if (rawVersion !== null &&
86
+ rawVersion !== undefined &&
87
+ typeof rawVersion.toNumber === 'function') {
88
+ patchVersion = rawVersion.toNumber();
89
+ }
90
+ for (const mutation of decoded.mutations ?? []) {
91
+ const operationCode = mutation.operation;
92
+ if (operationCode === null || operationCode === undefined)
93
+ continue;
94
+ const record = mutation.record;
95
+ if (!record)
96
+ continue;
97
+ const indexMac = record.index?.blob;
98
+ const valueBlob = record.value?.blob;
99
+ if (!indexMac || !valueBlob)
100
+ continue;
101
+ const operation = operationCode === proto.SyncdMutation.SyncdOperation.REMOVE
102
+ ? 'remove'
103
+ : 'set';
104
+ const decrypted = await this.appStateCrypto.decryptMutation({
105
+ operation,
106
+ keyId,
107
+ keyData,
108
+ indexMac,
109
+ valueBlob
110
+ });
111
+ let action;
112
+ try {
113
+ const parsed = JSON.parse(decrypted.index);
114
+ if (Array.isArray(parsed) && typeof parsed[0] === 'string') {
115
+ action = parsed[0];
116
+ }
117
+ }
118
+ catch {
119
+ // Ignore malformed index payloads; mutation capture still proceeds.
120
+ }
121
+ const captured = {
122
+ collection: collectionName,
123
+ operation,
124
+ index: decrypted.index,
125
+ action,
126
+ version: decrypted.version,
127
+ value: decrypted.value,
128
+ patchVersion
129
+ };
130
+ for (const listener of this.outboundMutationListeners) {
131
+ try {
132
+ listener(captured);
133
+ }
134
+ catch {
135
+ // Listener failures are best-effort.
136
+ }
137
+ }
138
+ }
139
+ }
140
+ catch {
141
+ // Ignore malformed/undecryptable patches.
142
+ }
143
+ }
144
+ }
145
+ }
146
+ }
@@ -0,0 +1,31 @@
1
+ /** Runs the server side of the QR pairing flow. */
2
+ import { buildAdvSignedDeviceIdentity, generateFakePrimaryDevice } from '../protocol/auth/fake-primary-device.js';
3
+ import { buildPairDeviceIq, buildPairSuccessIq } from '../protocol/auth/pair-device.js';
4
+ import { randomBytesAsync } from '../transport/crypto.js';
5
+ export class FakePairingDriver {
6
+ constructor(options, deps) {
7
+ this.primary = null;
8
+ this.options = options;
9
+ this.deps = deps;
10
+ }
11
+ async run() {
12
+ this.primary = this.options.primary ?? (await generateFakePrimaryDevice());
13
+ const refs = await Promise.all(Array.from({ length: 6 }, () => randomBytesAsync(16)));
14
+ const pairDeviceIq = buildPairDeviceIq({ refs });
15
+ await this.deps.pipeline.sendStanza(pairDeviceIq);
16
+ const waitForAck = this.deps.waitForPairDeviceAck?.(pairDeviceIq.attrs.id) ?? Promise.resolve();
17
+ const [material] = await Promise.all([this.deps.companionMaterialResolver(), waitForAck]);
18
+ const { deviceIdentityBytes } = await buildAdvSignedDeviceIdentity({
19
+ primary: this.primary,
20
+ advSecretKey: material.advSecretKey,
21
+ companionIdentityPublicKey: material.identityPublicKey,
22
+ companionDeviceId: this.options.companionDeviceId ?? 1
23
+ });
24
+ await this.deps.pipeline.sendStanza(buildPairSuccessIq({
25
+ deviceJid: this.options.deviceJid,
26
+ deviceLid: this.options.deviceLid,
27
+ platform: this.options.platform ?? 'IOS',
28
+ deviceIdentityBytes
29
+ }));
30
+ }
31
+ }