@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
@@ -1,14 +1,16 @@
1
1
  #!/usr/bin/env node
2
- "use strict";
3
2
  /** Standalone CLI for `@zapo-js/fake-server`. */
4
- Object.defineProperty(exports, "__esModule", { value: true });
5
- const FakeWaServer_1 = require("./api/FakeWaServer");
3
+ import { createInterface } from 'node:readline';
4
+ import { FakeWaServer } from './api/FakeWaServer.js';
5
+ import { parsePairingQrString } from './protocol/auth/pair-device.js';
6
+ import { bytesToHex, toError } from './transport/util.js';
6
7
  function parseArgs(argv) {
7
8
  let host = '127.0.0.1';
8
9
  let port = 0;
9
10
  let path = '/ws/chat';
10
11
  const peerJids = [];
11
12
  const groupSpecs = [];
13
+ let pairJid = null;
12
14
  let log = false;
13
15
  let quiet = false;
14
16
  let json = false;
@@ -46,6 +48,9 @@ function parseArgs(argv) {
46
48
  case '--group':
47
49
  groupSpecs.push(next());
48
50
  break;
51
+ case '--pair':
52
+ pairJid = next();
53
+ break;
49
54
  case '--log':
50
55
  log = true;
51
56
  break;
@@ -59,10 +64,10 @@ function parseArgs(argv) {
59
64
  throw new Error(`unknown flag: ${arg}`);
60
65
  }
61
66
  }
62
- return { host, port, path, peerJids, groupSpecs, log, quiet, json, help };
67
+ return { host, port, path, peerJids, groupSpecs, pairJid, log, quiet, json, help };
63
68
  }
64
69
  function printHelp() {
65
- process.stdout.write(`@zapo-js/fake-server — standalone fake WhatsApp Web server
70
+ process.stdout.write(`@zapo-js/fake-server – standalone fake WhatsApp Web server
66
71
 
67
72
  USAGE
68
73
  npx @zapo-js/fake-server [flags]
@@ -75,6 +80,12 @@ FLAGS
75
80
  --group <spec> Pre-create a fake group; spec format
76
81
  <group-jid>=<peer-jid>,<peer-jid>,...
77
82
  All peers must already be passed via --peer.
83
+ --pair <jid> Drive QR pairing for unregistered clients: the server
84
+ sends pair-device refs, then prompts on stdin for the
85
+ QR payload the client displays
86
+ (ref,noisePubB64,identityPubB64,advSecretB64,platform)
87
+ and answers pair-success assigning <jid> as the device
88
+ jid. Without this flag the CLI is login-only.
78
89
  --log Print every captured inbound stanza
79
90
  --quiet Suppress the startup info banner
80
91
  --json Print connection info as JSON
@@ -105,7 +116,7 @@ async function main() {
105
116
  args = parseArgs(process.argv.slice(2));
106
117
  }
107
118
  catch (error) {
108
- process.stderr.write(`error: ${error instanceof Error ? error.message : String(error)}\n`);
119
+ process.stderr.write(`error: ${toError(error).message}\n`);
109
120
  process.stderr.write('run with --help to see usage.\n');
110
121
  process.exit(2);
111
122
  }
@@ -122,16 +133,44 @@ async function main() {
122
133
  }
123
134
  }
124
135
  }
125
- const server = await FakeWaServer_1.FakeWaServer.start({
136
+ const server = await FakeWaServer.start({
126
137
  host: args.host,
127
138
  port: args.port,
128
139
  path: args.path
129
140
  });
141
+ if (args.pairJid !== null) {
142
+ const pairJid = args.pairJid;
143
+ let pairingStarted = false;
144
+ server.onAuthenticatedPipeline(async (pipeline) => {
145
+ if (pairingStarted)
146
+ return;
147
+ if (pipeline.clientPayload?.kind !== 'registration')
148
+ return;
149
+ pairingStarted = true;
150
+ try {
151
+ await server.runPairing(pipeline, { deviceJid: pairJid }, async () => {
152
+ const qr = await readStdinLine('[fake-server] paste the QR payload displayed by the client and press Enter:\n');
153
+ const parsed = parsePairingQrString(qr.trim());
154
+ return {
155
+ advSecretKey: parsed.advSecretKey,
156
+ identityPublicKey: parsed.identityPublicKey
157
+ };
158
+ });
159
+ process.stdout.write(`[fake-server] pair-success sent for ${pairJid}; the client will now reconnect and log in\n`);
160
+ }
161
+ catch (error) {
162
+ pairingStarted = false;
163
+ process.stderr.write(`pairing failed: ${toError(error).message}\n`);
164
+ }
165
+ });
166
+ }
130
167
  let setupComplete = false;
131
168
  const setupPromise = new Promise((resolve, reject) => {
132
169
  server.onAuthenticatedPipeline(async (pipeline) => {
133
170
  if (setupComplete)
134
171
  return;
172
+ if (pipeline.clientPayload?.kind !== 'login')
173
+ return;
135
174
  setupComplete = true;
136
175
  try {
137
176
  const peers = new Map();
@@ -157,7 +196,7 @@ async function main() {
157
196
  resolve();
158
197
  }
159
198
  catch (error) {
160
- reject(error instanceof Error ? error : new Error(String(error)));
199
+ reject(toError(error));
161
200
  }
162
201
  });
163
202
  });
@@ -166,12 +205,11 @@ async function main() {
166
205
  process.exitCode = 1;
167
206
  });
168
207
  if (args.log) {
169
- server.onPipeline((pipeline) => {
170
- pipeline.setEvents({
171
- onStanza: (node) => {
172
- process.stdout.write(`[wire] ${node.tag}${node.attrs.id ? ` id=${node.attrs.id}` : ''}${node.attrs.type ? ` type=${node.attrs.type}` : ''}\n`);
173
- }
174
- });
208
+ // onCapturedStanza taps the server's own capture stream; overriding
209
+ // pipeline.setEvents here would replace the server's handlers and
210
+ // silently break --peer setup and stanza capture.
211
+ server.onCapturedStanza((node) => {
212
+ process.stdout.write(`[wire] ${node.tag}${node.attrs.id ? ` id=${node.attrs.id}` : ''}${node.attrs.type ? ` type=${node.attrs.type}` : ''}\n`);
175
213
  });
176
214
  }
177
215
  if (!args.quiet) {
@@ -187,7 +225,8 @@ async function main() {
187
225
  publicKeyHex: bytesToHex(noiseRoot.publicKey)
188
226
  },
189
227
  peers: args.peerJids,
190
- groups: parsedGroups
228
+ groups: parsedGroups,
229
+ pair: args.pairJid
191
230
  }, null, 2) + '\n');
192
231
  }
193
232
  else {
@@ -203,6 +242,7 @@ async function main() {
203
242
  `│ noise root ca serial=${noiseRoot.serial} pub=${bytesToHex(noiseRoot.publicKey).slice(0, 32)}…`,
204
243
  `│ peers ${args.peerJids.length === 0 ? '(none)' : args.peerJids.join(', ')}`,
205
244
  `│ groups ${parsedGroups.length === 0 ? '(none)' : parsedGroups.map((g) => g.groupJid).join(', ')}`,
245
+ `│ pair mode ${args.pairJid ?? '(off – login only)'}`,
206
246
  '├─────────────────────────────────────────────────────────────',
207
247
  '│ Wire your WaClient with:',
208
248
  '│ chatSocketUrls: [server.url]',
@@ -226,7 +266,7 @@ async function main() {
226
266
  process.exit(0);
227
267
  }
228
268
  catch (error) {
229
- process.stderr.write(`error during shutdown: ${error instanceof Error ? error.message : String(error)}\n`);
269
+ process.stderr.write(`error during shutdown: ${toError(error).message}\n`);
230
270
  process.exit(1);
231
271
  }
232
272
  };
@@ -234,16 +274,21 @@ async function main() {
234
274
  process.on('SIGTERM', () => void shutdown('SIGTERM'));
235
275
  await new Promise(() => undefined);
236
276
  }
237
- function bytesToHex(bytes) {
238
- let out = '';
239
- for (let index = 0; index < bytes.byteLength; index += 1) {
240
- const value = bytes[index];
241
- out += value < 16 ? `0${value.toString(16)}` : value.toString(16);
242
- }
243
- return out;
277
+ function readStdinLine(prompt) {
278
+ process.stdout.write(prompt);
279
+ return new Promise((resolve, reject) => {
280
+ const rl = createInterface({ input: process.stdin });
281
+ // resolve before close(): rl.close() emits 'close' synchronously,
282
+ // which would otherwise reject the still-pending promise first.
283
+ rl.once('line', (line) => {
284
+ resolve(line);
285
+ rl.close();
286
+ });
287
+ rl.once('close', () => reject(new Error('stdin closed before a QR payload was provided')));
288
+ });
244
289
  }
245
290
  main().catch((error) => {
246
- process.stderr.write(`fatal: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`);
291
+ const normalized = toError(error);
292
+ process.stderr.write(`fatal: ${normalized.stack ?? normalized.message}\n`);
247
293
  process.exit(1);
248
294
  });
249
- //# sourceMappingURL=cli.js.map
@@ -0,0 +1,26 @@
1
+ export { FakePairingDriver } from './api/FakePairingDriver.js';
2
+ export { FakePeer } from './api/FakePeer.js';
3
+ export { FakeServerSession, FakeWaServer } from './api/FakeWaServer.js';
4
+ export { IqExpectation, Scenario } from './api/Scenario.js';
5
+ export { WaFakeConnection } from './infra/WaFakeConnection.js';
6
+ export { buildAppStateSyncFullResult, buildAppStateSyncResult, buildServerSyncNotification, parseAppStateSyncRequest } from './protocol/iq/appstate-sync.js';
7
+ export { buildIqError, buildIqResult } from './protocol/iq/router.js';
8
+ export { buildCall, buildFailure } from './protocol/push/call-failure.js';
9
+ export { buildChatstate } from './protocol/push/chatstate.js';
10
+ export { buildIncomingErrorStanza } from './protocol/push/error-stanza.js';
11
+ export { buildAppStateSyncKeyShareMessage } from './protocol/push/app-state-key-share.js';
12
+ export { buildHistorySyncExternalMessage, buildHistorySyncMessage, encodeHistorySyncPlaintext } from './protocol/push/history-sync.js';
13
+ export { APP_STATE_EMPTY_LT_HASH, FakeAppStateCrypto } from './protocol/signal/fake-app-state-crypto.js';
14
+ export { FakeAppStateCollection } from './state/fake-app-state-collection.js';
15
+ export { FakeMediaStore } from './state/fake-media-store.js';
16
+ export { buildMessage } from './protocol/push/message.js';
17
+ export { buildGroupNotification, buildNotification } from './protocol/push/notification.js';
18
+ export { buildIncomingPresence } from './protocol/push/presence.js';
19
+ export { buildReceipt } from './protocol/push/receipt.js';
20
+ export { generateFakePeerIdentity } from './protocol/signal/fake-peer-identity.js';
21
+ export { FakePeerDoubleRatchet } from './protocol/signal/fake-peer-double-ratchet.js';
22
+ export { FakeSenderKey } from './protocol/signal/fake-sender-key.js';
23
+ export { buildAdvSignedDeviceIdentity, generateFakePrimaryDevice } from './protocol/auth/fake-primary-device.js';
24
+ export { buildPairDeviceIq, buildPairSuccessIq, parsePairingQrString } from './protocol/auth/pair-device.js';
25
+ export { parsePreKeyUploadIq, PreKeyUploadParseError } from './protocol/signal/prekey-upload.js';
26
+ export { buildStreamErrorAck, buildStreamErrorCode, buildStreamErrorDeviceRemoved, buildStreamErrorReplaced, buildStreamErrorXmlNotWellFormed } from './protocol/stream/stream-error.js';
@@ -0,0 +1,49 @@
1
+ export class WaFakeConnection {
2
+ constructor(id, socket) {
3
+ this.handlers = {};
4
+ this.currentState = 'open';
5
+ this.id = id;
6
+ this.socket = socket;
7
+ this.bindSocketEvents();
8
+ }
9
+ get state() {
10
+ return this.currentState;
11
+ }
12
+ setHandlers(handlers) {
13
+ this.handlers = handlers;
14
+ }
15
+ sendFrame(frame) {
16
+ if (this.currentState !== 'open') {
17
+ throw new Error(`cannot send frame on connection in state "${this.currentState}"`);
18
+ }
19
+ this.socket.send(frame);
20
+ }
21
+ close(code = 1000, reason = '') {
22
+ if (this.currentState === 'closed' || this.currentState === 'closing') {
23
+ return;
24
+ }
25
+ this.currentState = 'closing';
26
+ this.socket.close(code, reason);
27
+ }
28
+ bindSocketEvents() {
29
+ this.socket.on('message', (data, isBinary) => {
30
+ if (!isBinary) {
31
+ this.handlers.onError?.(new Error('received unexpected text frame'));
32
+ return;
33
+ }
34
+ const frame = data instanceof Uint8Array
35
+ ? data
36
+ : Array.isArray(data)
37
+ ? new Uint8Array(Buffer.concat(data))
38
+ : new Uint8Array(data);
39
+ this.handlers.onFrame?.(frame);
40
+ });
41
+ this.socket.on('close', (code, reasonBuf) => {
42
+ this.currentState = 'closed';
43
+ this.handlers.onClose?.({ code, reason: reasonBuf.toString('utf8') });
44
+ });
45
+ this.socket.on('error', (error) => {
46
+ this.handlers.onError?.(error);
47
+ });
48
+ }
49
+ }
@@ -0,0 +1,235 @@
1
+ /** Per-connection lifecycle pipeline (Noise handshake + stanza loop). */
2
+ import { buildFakeCertChain } from '../protocol/auth/cert-chain.js';
3
+ import { parseClientPayload } from '../protocol/auth/client-payload-validate.js';
4
+ import { buildSuccessNode } from '../protocol/auth/success-node.js';
5
+ import { decodeBinaryNodeStanza, encodeBinaryNodeStanza } from '../transport/codec.js';
6
+ import { X25519 } from '../transport/crypto.js';
7
+ import { proto } from '../transport/protos.js';
8
+ import { WaFakeFrameSocket } from './WaFakeFrameSocket.js';
9
+ import { WaFakeNoiseHandshake } from './WaFakeNoiseHandshake.js';
10
+ import { WaFakeTransport } from './WaFakeTransport.js';
11
+ const NOISE_XX_NAME = new TextEncoder().encode('Noise_XX_25519_AESGCM_SHA256\0\0\0\0');
12
+ const NOISE_IK_NAME = new TextEncoder().encode('Noise_IK_25519_AESGCM_SHA256\0\0\0\0');
13
+ const PROLOGUE = new Uint8Array([0x57, 0x41, 0x06, 0x03]);
14
+ export class WaFakeConnectionPipeline {
15
+ constructor(config) {
16
+ this.events = {};
17
+ this.state = { kind: 'awaiting_prologue' };
18
+ this.chain = Promise.resolve();
19
+ this.authenticatedPayload = null;
20
+ this.config = config;
21
+ this.frameSocket = new WaFakeFrameSocket(config.connection);
22
+ this.frameSocket.setHandlers({
23
+ onPrologue: () => this.onPrologue(),
24
+ onFrame: (frame) => this.scheduleFrame(frame),
25
+ onClose: (info) => {
26
+ this.state = { kind: 'closed' };
27
+ this.events.onClose?.(info);
28
+ },
29
+ onError: (error) => this.events.onError?.(error)
30
+ });
31
+ }
32
+ setEvents(events) {
33
+ this.events = events;
34
+ }
35
+ isAuthenticated() {
36
+ return this.state.kind === 'authenticated';
37
+ }
38
+ /** Parsed ClientPayload of the authenticated connection; null before auth. */
39
+ get clientPayload() {
40
+ return this.authenticatedPayload;
41
+ }
42
+ async sendStanza(node) {
43
+ if (this.state.kind !== 'authenticated') {
44
+ throw new Error(`cannot send stanza while pipeline is in state "${this.state.kind}"`);
45
+ }
46
+ const stanza = encodeBinaryNodeStanza(node);
47
+ // Keep outbound encrypted frames in FIFO order across concurrent callers.
48
+ const ciphertext = await this.state.transport.encryptFrame(stanza);
49
+ this.frameSocket.sendFrame(ciphertext);
50
+ }
51
+ onPrologue() {
52
+ if (this.state.kind !== 'awaiting_prologue') {
53
+ this.events.onError?.(new Error('received second prologue from client'));
54
+ return;
55
+ }
56
+ this.state = { kind: 'awaiting_client_hello' };
57
+ }
58
+ scheduleFrame(frame) {
59
+ this.chain = this.chain.then(() => this.handleFrame(frame));
60
+ }
61
+ async handleFrame(frame) {
62
+ try {
63
+ switch (this.state.kind) {
64
+ case 'awaiting_client_hello':
65
+ await this.handleClientHello(frame);
66
+ return;
67
+ case 'awaiting_client_finish':
68
+ await this.handleClientFinish(frame, this.state.handshake, this.state.serverEphemeralKeyPair);
69
+ return;
70
+ case 'authenticated':
71
+ await this.handleAuthenticatedFrame(frame, this.state.transport);
72
+ return;
73
+ default:
74
+ this.events.onError?.(new Error(`unexpected frame in state "${this.state.kind}"`));
75
+ }
76
+ }
77
+ catch (error) {
78
+ this.fail(error);
79
+ }
80
+ }
81
+ async handleClientHello(frame) {
82
+ const parsed = proto.HandshakeMessage.decode(frame);
83
+ const clientHello = parsed.clientHello;
84
+ if (!clientHello?.ephemeral) {
85
+ throw new Error('ClientHello missing ephemeral');
86
+ }
87
+ // IK includes encrypted static+payload in ClientHello; XX does not.
88
+ if (clientHello.static && clientHello.payload) {
89
+ await this.handleIkClientHello(clientHello);
90
+ }
91
+ else {
92
+ await this.handleXxClientHello(clientHello.ephemeral);
93
+ }
94
+ }
95
+ async handleXxClientHello(clientEphemeralPub) {
96
+ const handshake = new WaFakeNoiseHandshake();
97
+ handshake.start(NOISE_XX_NAME, PROLOGUE);
98
+ handshake.authenticate(clientEphemeralPub);
99
+ const serverEphemeral = await X25519.generateKeyPair();
100
+ handshake.authenticate(serverEphemeral.pubKey);
101
+ handshake.mixIntoKey(await X25519.scalarMult(serverEphemeral.privKey, clientEphemeralPub));
102
+ const encryptedServerStatic = handshake.encrypt(this.config.serverStaticKeyPair.pubKey);
103
+ handshake.mixIntoKey(await X25519.scalarMult(this.config.serverStaticKeyPair.privKey, clientEphemeralPub));
104
+ const certChain = await buildFakeCertChain({
105
+ root: this.config.rootCa,
106
+ leafKey: this.config.serverStaticKeyPair.pubKey
107
+ });
108
+ const encryptedCertPayload = handshake.encrypt(certChain.encoded);
109
+ const serverHello = proto.HandshakeMessage.encode({
110
+ serverHello: {
111
+ ephemeral: serverEphemeral.pubKey,
112
+ static: encryptedServerStatic,
113
+ payload: encryptedCertPayload
114
+ }
115
+ }).finish();
116
+ this.frameSocket.sendFrame(serverHello);
117
+ this.state = {
118
+ kind: 'awaiting_client_finish',
119
+ handshake,
120
+ serverEphemeralKeyPair: serverEphemeral,
121
+ clientEphemeralPub
122
+ };
123
+ }
124
+ /** Server-side IK path used when the client already cached our static key. */
125
+ async handleIkClientHello(clientHello) {
126
+ if (!clientHello.ephemeral || !clientHello.static || !clientHello.payload) {
127
+ throw new Error('IK ClientHello missing ephemeral/static/payload');
128
+ }
129
+ const clientEphemeralPub = clientHello.ephemeral;
130
+ const encryptedClientStatic = clientHello.static;
131
+ const encryptedClientPayload = clientHello.payload;
132
+ const handshake = new WaFakeNoiseHandshake();
133
+ handshake.start(NOISE_IK_NAME, PROLOGUE);
134
+ handshake.authenticate(this.config.serverStaticKeyPair.pubKey);
135
+ handshake.authenticate(clientEphemeralPub);
136
+ handshake.mixIntoKey(await X25519.scalarMult(this.config.serverStaticKeyPair.privKey, clientEphemeralPub));
137
+ const clientStaticKey = handshake.decrypt(encryptedClientStatic);
138
+ handshake.mixIntoKey(await X25519.scalarMult(this.config.serverStaticKeyPair.privKey, clientStaticKey));
139
+ const clientPayloadBytes = handshake.decrypt(encryptedClientPayload);
140
+ const clientPayload = parseClientPayload(clientPayloadBytes);
141
+ const serverEphemeral = await X25519.generateKeyPair();
142
+ handshake.authenticate(serverEphemeral.pubKey);
143
+ const [dhEphem, dhStatic] = await Promise.all([
144
+ X25519.scalarMult(serverEphemeral.privKey, clientEphemeralPub),
145
+ X25519.scalarMult(serverEphemeral.privKey, clientStaticKey)
146
+ ]);
147
+ handshake.mixIntoKey(dhEphem);
148
+ handshake.mixIntoKey(dhStatic);
149
+ const certChain = await buildFakeCertChain({
150
+ root: this.config.rootCa,
151
+ leafKey: this.config.serverStaticKeyPair.pubKey
152
+ });
153
+ const encryptedCertPayload = handshake.encrypt(certChain.encoded);
154
+ const serverHello = proto.HandshakeMessage.encode({
155
+ serverHello: {
156
+ ephemeral: serverEphemeral.pubKey,
157
+ payload: encryptedCertPayload
158
+ }
159
+ }).finish();
160
+ this.frameSocket.sendFrame(serverHello);
161
+ await this.completeAuthentication(handshake, clientPayload, clientStaticKey);
162
+ }
163
+ async handleClientFinish(frame, handshake, serverEphemeralKeyPair) {
164
+ const parsed = proto.HandshakeMessage.decode(frame);
165
+ const clientFinish = parsed.clientFinish;
166
+ if (!clientFinish?.static || !clientFinish.payload) {
167
+ throw new Error('ClientFinish missing static/payload');
168
+ }
169
+ const clientStaticKey = handshake.decrypt(clientFinish.static);
170
+ handshake.mixIntoKey(await X25519.scalarMult(serverEphemeralKeyPair.privKey, clientStaticKey));
171
+ const clientPayloadBytes = handshake.decrypt(clientFinish.payload);
172
+ const clientPayload = parseClientPayload(clientPayloadBytes);
173
+ await this.completeAuthentication(handshake, clientPayload, clientStaticKey);
174
+ }
175
+ async completeAuthentication(handshake, clientPayload, clientStaticKey) {
176
+ const keys = handshake.finish();
177
+ const transport = new WaFakeTransport({
178
+ recvKey: keys.recvKey,
179
+ sendKey: keys.sendKey
180
+ });
181
+ this.state = { kind: 'authenticated', transport };
182
+ this.authenticatedPayload = clientPayload;
183
+ await this.sendStanza(buildSuccessNode(this.config.successNodeAttributes));
184
+ if (clientPayload.kind === 'login') {
185
+ // Real WA drains offline messages after login and closes the
186
+ // drain with this bulletin. Baileys-family clients buffer their
187
+ // events until it arrives, so send it eagerly with count=0.
188
+ await this.sendStanza({
189
+ tag: 'ib',
190
+ attrs: { from: 's.whatsapp.net' },
191
+ content: [{ tag: 'offline', attrs: { count: '0' } }]
192
+ });
193
+ }
194
+ this.events.onAuthenticated?.({ clientPayload, clientStaticKey });
195
+ }
196
+ async handleAuthenticatedFrame(frame, transport) {
197
+ const stanzaBytes = await transport.decryptFrame(frame);
198
+ const node = await decodeBinaryNodeStanza(stanzaBytes);
199
+ this.events.onStanza?.(node);
200
+ if (node.tag === 'iq') {
201
+ const response = await this.config.routeIq(node);
202
+ if (response !== null) {
203
+ await this.sendStanza(response);
204
+ }
205
+ else {
206
+ this.events.onUnhandledStanza?.(node);
207
+ }
208
+ return;
209
+ }
210
+ // Mirror the minimal ack shape that resolves the lib pending publish.
211
+ if (node.tag === 'message' && node.attrs.id) {
212
+ const messageType = node.attrs.type ?? 'text';
213
+ const from = node.attrs.to ?? 's.whatsapp.net';
214
+ const ackAttrs = {
215
+ id: node.attrs.id,
216
+ class: 'receipt',
217
+ type: messageType,
218
+ from
219
+ };
220
+ if (node.attrs.participant) {
221
+ ackAttrs.participant = node.attrs.participant;
222
+ }
223
+ await this.sendStanza({
224
+ tag: 'ack',
225
+ attrs: ackAttrs
226
+ });
227
+ }
228
+ }
229
+ fail(error) {
230
+ const err = error instanceof Error ? error : new Error(String(error));
231
+ this.events.onError?.(err);
232
+ this.state = { kind: 'closed' };
233
+ this.config.connection.close(1011, err.message.slice(0, 120));
234
+ }
235
+ }
@@ -0,0 +1,117 @@
1
+ /** WhatsApp frame codec (prologue + 3-byte length-prefixed frames). */
2
+ const PROLOGUE_W = 0x57;
3
+ const PROLOGUE_A = 0x41;
4
+ const ROUTING_MAGIC = 0xed;
5
+ const VERSION_HEADER_LENGTH = 4;
6
+ const FRAME_LENGTH_PREFIX_BYTES = 3;
7
+ const MAX_FRAME_LENGTH = 1 << 24;
8
+ export class WaFakeFrameSocket {
9
+ constructor(connection) {
10
+ this.handlers = {};
11
+ this.inboundBuffer = new Uint8Array(0);
12
+ this.prologueParsed = false;
13
+ this.connection = connection;
14
+ this.connection.setHandlers({
15
+ onFrame: (bytes) => this.handleInboundBytes(bytes),
16
+ onClose: (info) => this.handlers.onClose?.(info),
17
+ onError: (error) => this.handlers.onError?.(error)
18
+ });
19
+ }
20
+ setHandlers(handlers) {
21
+ this.handlers = handlers;
22
+ }
23
+ sendFrame(body) {
24
+ if (body.byteLength >= MAX_FRAME_LENGTH) {
25
+ throw new Error(`frame too large: ${body.byteLength} bytes`);
26
+ }
27
+ const out = new Uint8Array(FRAME_LENGTH_PREFIX_BYTES + body.byteLength);
28
+ out[0] = (body.byteLength >> 16) & 0xff;
29
+ out[1] = (body.byteLength >> 8) & 0xff;
30
+ out[2] = body.byteLength & 0xff;
31
+ out.set(body, FRAME_LENGTH_PREFIX_BYTES);
32
+ this.connection.sendFrame(out);
33
+ }
34
+ handleInboundBytes(chunk) {
35
+ try {
36
+ this.inboundBuffer = concatBytes(this.inboundBuffer, chunk);
37
+ if (!this.prologueParsed) {
38
+ if (!this.tryParsePrologue()) {
39
+ return;
40
+ }
41
+ }
42
+ this.drainFrames();
43
+ }
44
+ catch (error) {
45
+ this.handlers.onError?.(error instanceof Error ? error : new Error(String(error)));
46
+ }
47
+ }
48
+ tryParsePrologue() {
49
+ const buffer = this.inboundBuffer;
50
+ let offset = 0;
51
+ let routingInfo = null;
52
+ if (buffer.byteLength === 0) {
53
+ return false;
54
+ }
55
+ if (buffer[0] === ROUTING_MAGIC) {
56
+ if (buffer.byteLength < 4) {
57
+ return false;
58
+ }
59
+ const routingLen = (buffer[1] << 16) | (buffer[2] << 8) | buffer[3];
60
+ if (buffer.byteLength < 4 + routingLen + VERSION_HEADER_LENGTH) {
61
+ return false;
62
+ }
63
+ routingInfo = buffer.slice(4, 4 + routingLen);
64
+ offset = 4 + routingLen;
65
+ }
66
+ else if (buffer.byteLength < VERSION_HEADER_LENGTH) {
67
+ return false;
68
+ }
69
+ const versionStart = offset;
70
+ const w = buffer[versionStart];
71
+ const a = buffer[versionStart + 1];
72
+ if (w !== PROLOGUE_W || a !== PROLOGUE_A) {
73
+ throw new Error(`invalid prologue magic: expected "WA", got ${hex(w)} ${hex(a)}`);
74
+ }
75
+ const protocolVersion = buffer[versionStart + 2];
76
+ const dictVersion = buffer[versionStart + 3];
77
+ offset = versionStart + VERSION_HEADER_LENGTH;
78
+ this.inboundBuffer = buffer.slice(offset);
79
+ this.prologueParsed = true;
80
+ this.handlers.onPrologue?.({ protocolVersion, dictVersion, routingInfo });
81
+ return true;
82
+ }
83
+ drainFrames() {
84
+ while (true) {
85
+ const buffer = this.inboundBuffer;
86
+ if (buffer.byteLength < FRAME_LENGTH_PREFIX_BYTES) {
87
+ return;
88
+ }
89
+ const length = (buffer[0] << 16) | (buffer[1] << 8) | buffer[2];
90
+ if (length >= MAX_FRAME_LENGTH) {
91
+ throw new Error(`frame too large: ${length} bytes`);
92
+ }
93
+ const total = FRAME_LENGTH_PREFIX_BYTES + length;
94
+ if (buffer.byteLength < total) {
95
+ return;
96
+ }
97
+ const body = buffer.slice(FRAME_LENGTH_PREFIX_BYTES, total);
98
+ this.inboundBuffer = buffer.slice(total);
99
+ this.handlers.onFrame?.(body);
100
+ }
101
+ }
102
+ }
103
+ function concatBytes(a, b) {
104
+ if (a.byteLength === 0)
105
+ return b;
106
+ if (b.byteLength === 0)
107
+ return a;
108
+ const out = new Uint8Array(a.byteLength + b.byteLength);
109
+ out.set(a, 0);
110
+ out.set(b, a.byteLength);
111
+ return out;
112
+ }
113
+ function hex(byte) {
114
+ if (byte === undefined)
115
+ return 'undefined';
116
+ return `0x${byte.toString(16).padStart(2, '0')}`;
117
+ }