@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.
- package/README.md +96 -8
- package/dist/api/AppStateSyncManager.d.ts +0 -1
- package/dist/api/AppStateSyncManager.js +0 -1
- package/dist/api/FakePairingDriver.d.ts +0 -1
- package/dist/api/FakePairingDriver.js +0 -1
- package/dist/api/FakePeer.d.ts +77 -4
- package/dist/api/FakePeer.js +208 -2
- package/dist/api/FakeServerSession.d.ts +67 -0
- package/dist/api/FakeServerSession.js +293 -0
- package/dist/api/FakeWaServer.d.ts +77 -31
- package/dist/api/FakeWaServer.js +140 -259
- package/dist/api/PreKeyDispenser.d.ts +0 -1
- package/dist/api/PreKeyDispenser.js +0 -1
- package/dist/api/Scenario.d.ts +0 -1
- package/dist/api/Scenario.js +0 -1
- package/dist/api/ServerRegistries.d.ts +0 -1
- package/dist/api/ServerRegistries.js +0 -1
- package/dist/api/iq-handlers.d.ts +1 -1
- package/dist/api/iq-handlers.js +23 -1
- package/dist/{cli.d.ts → bin.d.ts} +0 -1
- package/dist/bin.js +296 -0
- package/dist/esm/api/AppStateSyncManager.js +146 -0
- package/dist/esm/api/FakePairingDriver.js +31 -0
- package/dist/esm/api/FakePeer.js +610 -0
- package/dist/esm/api/FakeServerSession.js +287 -0
- package/dist/esm/api/FakeWaServer.js +528 -0
- package/dist/esm/api/PreKeyDispenser.js +93 -0
- package/dist/esm/api/Scenario.js +32 -0
- package/dist/esm/api/ServerRegistries.js +261 -0
- package/dist/esm/api/iq-handlers.js +515 -0
- package/dist/{cli.js → esm/bin.js} +70 -25
- package/dist/esm/index.js +26 -0
- package/dist/esm/infra/WaFakeConnection.js +49 -0
- package/dist/esm/infra/WaFakeConnectionPipeline.js +235 -0
- package/dist/esm/infra/WaFakeFrameSocket.js +117 -0
- package/dist/esm/infra/WaFakeMediaHttpsServer.js +126 -0
- package/dist/esm/infra/WaFakeNoiseHandshake.js +67 -0
- package/dist/esm/infra/WaFakeTransport.js +31 -0
- package/dist/esm/infra/WaFakeWsServer.js +96 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/protocol/auth/cert-chain.js +50 -0
- package/dist/esm/protocol/auth/client-payload-validate.js +55 -0
- package/dist/esm/protocol/auth/fake-primary-device.js +47 -0
- package/dist/esm/protocol/auth/pair-device.js +87 -0
- package/dist/esm/protocol/auth/success-node.js +26 -0
- package/dist/esm/protocol/iq/abprops.js +30 -0
- package/dist/esm/protocol/iq/appstate-sync.js +146 -0
- package/dist/esm/protocol/iq/business.js +59 -0
- package/dist/esm/protocol/iq/dirty-bits.js +19 -0
- package/dist/esm/protocol/iq/group-ops.js +154 -0
- package/dist/esm/protocol/iq/newsletter.js +14 -0
- package/dist/esm/protocol/iq/prekey-fetch.js +88 -0
- package/dist/esm/protocol/iq/privacy-token.js +26 -0
- package/dist/esm/protocol/iq/privacy.js +123 -0
- package/dist/esm/protocol/iq/profile.js +72 -0
- package/dist/esm/protocol/iq/router.js +117 -0
- package/dist/esm/protocol/iq/usync.js +64 -0
- package/dist/esm/protocol/push/app-state-key-share.js +26 -0
- package/dist/esm/protocol/push/call-failure.js +27 -0
- package/dist/esm/protocol/push/chatstate.js +23 -0
- package/dist/esm/protocol/push/error-stanza.js +13 -0
- package/dist/esm/protocol/push/history-sync.js +111 -0
- package/dist/esm/protocol/push/message.js +38 -0
- package/dist/esm/protocol/push/notification.js +29 -0
- package/dist/esm/protocol/push/presence.js +14 -0
- package/dist/esm/protocol/push/receipt.js +21 -0
- package/dist/esm/protocol/signal/fake-app-state-crypto.js +196 -0
- package/dist/esm/protocol/signal/fake-peer-double-ratchet.js +398 -0
- package/dist/esm/protocol/signal/fake-peer-group-recv-session.js +120 -0
- package/dist/esm/protocol/signal/fake-peer-identity.js +8 -0
- package/dist/esm/protocol/signal/fake-peer-key-bundle.js +25 -0
- package/dist/esm/protocol/signal/fake-sender-key.js +83 -0
- package/dist/esm/protocol/signal/prekey-upload.js +107 -0
- package/dist/esm/protocol/stream/stream-error.js +56 -0
- package/dist/esm/state/fake-app-state-collection.js +134 -0
- package/dist/esm/state/fake-media-store.js +53 -0
- package/dist/esm/transport/codec.js +8 -0
- package/dist/esm/transport/crypto.js +8 -0
- package/dist/esm/transport/protos.js +7 -0
- package/dist/esm/transport/util.js +8 -0
- package/dist/index.d.ts +2 -3
- package/dist/index.js +2 -2
- package/dist/infra/WaFakeConnection.d.ts +0 -1
- package/dist/infra/WaFakeConnection.js +0 -1
- package/dist/infra/WaFakeConnectionPipeline.d.ts +10 -3
- package/dist/infra/WaFakeConnectionPipeline.js +47 -32
- package/dist/infra/WaFakeFrameSocket.d.ts +0 -1
- package/dist/infra/WaFakeFrameSocket.js +0 -1
- package/dist/infra/WaFakeMediaHttpsServer.d.ts +0 -1
- package/dist/infra/WaFakeMediaHttpsServer.js +0 -1
- package/dist/infra/WaFakeNoiseHandshake.d.ts +8 -10
- package/dist/infra/WaFakeNoiseHandshake.js +19 -24
- package/dist/infra/WaFakeTransport.d.ts +2 -4
- package/dist/infra/WaFakeTransport.js +2 -1
- package/dist/infra/WaFakeWsServer.d.ts +0 -1
- package/dist/infra/WaFakeWsServer.js +0 -1
- package/dist/protocol/auth/cert-chain.d.ts +4 -1
- package/dist/protocol/auth/cert-chain.js +13 -3
- package/dist/protocol/auth/client-payload-validate.d.ts +0 -1
- package/dist/protocol/auth/client-payload-validate.js +0 -1
- package/dist/protocol/auth/fake-primary-device.d.ts +0 -1
- package/dist/protocol/auth/fake-primary-device.js +1 -3
- package/dist/protocol/auth/pair-device.d.ts +0 -1
- package/dist/protocol/auth/pair-device.js +4 -16
- package/dist/protocol/auth/success-node.d.ts +0 -1
- package/dist/protocol/auth/success-node.js +0 -1
- package/dist/protocol/iq/abprops.d.ts +0 -1
- package/dist/protocol/iq/abprops.js +0 -1
- package/dist/protocol/iq/appstate-sync.d.ts +0 -1
- package/dist/protocol/iq/appstate-sync.js +0 -1
- package/dist/protocol/iq/business.d.ts +0 -1
- package/dist/protocol/iq/business.js +0 -1
- package/dist/protocol/iq/dirty-bits.d.ts +0 -1
- package/dist/protocol/iq/dirty-bits.js +0 -1
- package/dist/protocol/iq/group-ops.d.ts +0 -1
- package/dist/protocol/iq/group-ops.js +0 -1
- package/dist/protocol/iq/newsletter.d.ts +0 -1
- package/dist/protocol/iq/newsletter.js +0 -1
- package/dist/protocol/iq/prekey-fetch.d.ts +0 -1
- package/dist/protocol/iq/prekey-fetch.js +0 -1
- package/dist/protocol/iq/privacy-token.d.ts +0 -1
- package/dist/protocol/iq/privacy-token.js +0 -1
- package/dist/protocol/iq/privacy.d.ts +0 -1
- package/dist/protocol/iq/privacy.js +15 -16
- package/dist/protocol/iq/profile.d.ts +0 -1
- package/dist/protocol/iq/profile.js +0 -1
- package/dist/protocol/iq/router.d.ts +7 -2
- package/dist/protocol/iq/router.js +8 -3
- package/dist/protocol/iq/usync.d.ts +0 -1
- package/dist/protocol/iq/usync.js +0 -1
- package/dist/protocol/push/app-state-key-share.d.ts +0 -1
- package/dist/protocol/push/app-state-key-share.js +0 -1
- package/dist/protocol/push/call-failure.d.ts +0 -1
- package/dist/protocol/push/call-failure.js +0 -1
- package/dist/protocol/push/chatstate.d.ts +0 -1
- package/dist/protocol/push/chatstate.js +0 -1
- package/dist/protocol/push/error-stanza.d.ts +0 -1
- package/dist/protocol/push/error-stanza.js +0 -1
- package/dist/protocol/push/history-sync.d.ts +0 -1
- package/dist/protocol/push/history-sync.js +0 -1
- package/dist/protocol/push/message.d.ts +0 -1
- package/dist/protocol/push/message.js +0 -1
- package/dist/protocol/push/notification.d.ts +0 -1
- package/dist/protocol/push/notification.js +0 -1
- package/dist/protocol/push/presence.d.ts +0 -1
- package/dist/protocol/push/presence.js +0 -1
- package/dist/protocol/push/receipt.d.ts +0 -1
- package/dist/protocol/push/receipt.js +0 -1
- package/dist/protocol/signal/fake-app-state-crypto.d.ts +7 -9
- package/dist/protocol/signal/fake-app-state-crypto.js +31 -33
- package/dist/protocol/signal/fake-peer-double-ratchet.d.ts +0 -1
- package/dist/protocol/signal/fake-peer-double-ratchet.js +23 -28
- package/dist/protocol/signal/fake-peer-group-recv-session.d.ts +0 -1
- package/dist/protocol/signal/fake-peer-group-recv-session.js +6 -10
- package/dist/protocol/signal/fake-peer-identity.d.ts +0 -1
- package/dist/protocol/signal/fake-peer-identity.js +0 -1
- package/dist/protocol/signal/fake-peer-key-bundle.d.ts +0 -1
- package/dist/protocol/signal/fake-peer-key-bundle.js +0 -1
- package/dist/protocol/signal/fake-sender-key.d.ts +0 -1
- package/dist/protocol/signal/fake-sender-key.js +5 -9
- package/dist/protocol/signal/prekey-upload.d.ts +0 -1
- package/dist/protocol/signal/prekey-upload.js +0 -1
- package/dist/protocol/stream/stream-error.d.ts +0 -1
- package/dist/protocol/stream/stream-error.js +0 -1
- package/dist/state/fake-app-state-collection.d.ts +0 -1
- package/dist/state/fake-app-state-collection.js +0 -1
- package/dist/state/fake-media-store.d.ts +0 -1
- package/dist/state/fake-media-store.js +2 -2
- package/dist/transport/codec.d.ts +2 -3
- package/dist/transport/codec.js +2 -3
- package/dist/transport/crypto.d.ts +5 -7
- package/dist/transport/crypto.js +6 -11
- package/dist/transport/protos.d.ts +2 -4
- package/dist/transport/protos.js +2 -4
- package/dist/transport/util.d.ts +8 -0
- package/dist/transport/util.js +16 -0
- package/package.json +26 -6
- package/dist/api/AppStateSyncManager.d.ts.map +0 -1
- package/dist/api/AppStateSyncManager.js.map +0 -1
- package/dist/api/FakePairingDriver.d.ts.map +0 -1
- package/dist/api/FakePairingDriver.js.map +0 -1
- package/dist/api/FakePeer.d.ts.map +0 -1
- package/dist/api/FakePeer.js.map +0 -1
- package/dist/api/FakeWaServer.d.ts.map +0 -1
- package/dist/api/FakeWaServer.js.map +0 -1
- package/dist/api/PreKeyDispenser.d.ts.map +0 -1
- package/dist/api/PreKeyDispenser.js.map +0 -1
- package/dist/api/Scenario.d.ts.map +0 -1
- package/dist/api/Scenario.js.map +0 -1
- package/dist/api/ServerRegistries.d.ts.map +0 -1
- package/dist/api/ServerRegistries.js.map +0 -1
- package/dist/api/iq-handlers.d.ts.map +0 -1
- package/dist/api/iq-handlers.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/infra/WaFakeConnection.d.ts.map +0 -1
- package/dist/infra/WaFakeConnection.js.map +0 -1
- package/dist/infra/WaFakeConnectionPipeline.d.ts.map +0 -1
- package/dist/infra/WaFakeConnectionPipeline.js.map +0 -1
- package/dist/infra/WaFakeFrameSocket.d.ts.map +0 -1
- package/dist/infra/WaFakeFrameSocket.js.map +0 -1
- package/dist/infra/WaFakeMediaHttpsServer.d.ts.map +0 -1
- package/dist/infra/WaFakeMediaHttpsServer.js.map +0 -1
- package/dist/infra/WaFakeNoiseHandshake.d.ts.map +0 -1
- package/dist/infra/WaFakeNoiseHandshake.js.map +0 -1
- package/dist/infra/WaFakeTransport.d.ts.map +0 -1
- package/dist/infra/WaFakeTransport.js.map +0 -1
- package/dist/infra/WaFakeWsServer.d.ts.map +0 -1
- package/dist/infra/WaFakeWsServer.js.map +0 -1
- package/dist/protocol/auth/cert-chain.d.ts.map +0 -1
- package/dist/protocol/auth/cert-chain.js.map +0 -1
- package/dist/protocol/auth/client-payload-validate.d.ts.map +0 -1
- package/dist/protocol/auth/client-payload-validate.js.map +0 -1
- package/dist/protocol/auth/fake-primary-device.d.ts.map +0 -1
- package/dist/protocol/auth/fake-primary-device.js.map +0 -1
- package/dist/protocol/auth/pair-device.d.ts.map +0 -1
- package/dist/protocol/auth/pair-device.js.map +0 -1
- package/dist/protocol/auth/success-node.d.ts.map +0 -1
- package/dist/protocol/auth/success-node.js.map +0 -1
- package/dist/protocol/iq/abprops.d.ts.map +0 -1
- package/dist/protocol/iq/abprops.js.map +0 -1
- package/dist/protocol/iq/appstate-sync.d.ts.map +0 -1
- package/dist/protocol/iq/appstate-sync.js.map +0 -1
- package/dist/protocol/iq/business.d.ts.map +0 -1
- package/dist/protocol/iq/business.js.map +0 -1
- package/dist/protocol/iq/dirty-bits.d.ts.map +0 -1
- package/dist/protocol/iq/dirty-bits.js.map +0 -1
- package/dist/protocol/iq/group-ops.d.ts.map +0 -1
- package/dist/protocol/iq/group-ops.js.map +0 -1
- package/dist/protocol/iq/newsletter.d.ts.map +0 -1
- package/dist/protocol/iq/newsletter.js.map +0 -1
- package/dist/protocol/iq/prekey-fetch.d.ts.map +0 -1
- package/dist/protocol/iq/prekey-fetch.js.map +0 -1
- package/dist/protocol/iq/privacy-token.d.ts.map +0 -1
- package/dist/protocol/iq/privacy-token.js.map +0 -1
- package/dist/protocol/iq/privacy.d.ts.map +0 -1
- package/dist/protocol/iq/privacy.js.map +0 -1
- package/dist/protocol/iq/profile.d.ts.map +0 -1
- package/dist/protocol/iq/profile.js.map +0 -1
- package/dist/protocol/iq/router.d.ts.map +0 -1
- package/dist/protocol/iq/router.js.map +0 -1
- package/dist/protocol/iq/usync.d.ts.map +0 -1
- package/dist/protocol/iq/usync.js.map +0 -1
- package/dist/protocol/push/app-state-key-share.d.ts.map +0 -1
- package/dist/protocol/push/app-state-key-share.js.map +0 -1
- package/dist/protocol/push/call-failure.d.ts.map +0 -1
- package/dist/protocol/push/call-failure.js.map +0 -1
- package/dist/protocol/push/chatstate.d.ts.map +0 -1
- package/dist/protocol/push/chatstate.js.map +0 -1
- package/dist/protocol/push/error-stanza.d.ts.map +0 -1
- package/dist/protocol/push/error-stanza.js.map +0 -1
- package/dist/protocol/push/history-sync.d.ts.map +0 -1
- package/dist/protocol/push/history-sync.js.map +0 -1
- package/dist/protocol/push/message.d.ts.map +0 -1
- package/dist/protocol/push/message.js.map +0 -1
- package/dist/protocol/push/notification.d.ts.map +0 -1
- package/dist/protocol/push/notification.js.map +0 -1
- package/dist/protocol/push/presence.d.ts.map +0 -1
- package/dist/protocol/push/presence.js.map +0 -1
- package/dist/protocol/push/receipt.d.ts.map +0 -1
- package/dist/protocol/push/receipt.js.map +0 -1
- package/dist/protocol/signal/fake-app-state-crypto.d.ts.map +0 -1
- package/dist/protocol/signal/fake-app-state-crypto.js.map +0 -1
- package/dist/protocol/signal/fake-peer-double-ratchet.d.ts.map +0 -1
- package/dist/protocol/signal/fake-peer-double-ratchet.js.map +0 -1
- package/dist/protocol/signal/fake-peer-group-recv-session.d.ts.map +0 -1
- package/dist/protocol/signal/fake-peer-group-recv-session.js.map +0 -1
- package/dist/protocol/signal/fake-peer-identity.d.ts.map +0 -1
- package/dist/protocol/signal/fake-peer-identity.js.map +0 -1
- package/dist/protocol/signal/fake-peer-key-bundle.d.ts.map +0 -1
- package/dist/protocol/signal/fake-peer-key-bundle.js.map +0 -1
- package/dist/protocol/signal/fake-peer-recv-session.d.ts +0 -60
- package/dist/protocol/signal/fake-peer-recv-session.d.ts.map +0 -1
- package/dist/protocol/signal/fake-peer-recv-session.js +0 -284
- package/dist/protocol/signal/fake-peer-recv-session.js.map +0 -1
- package/dist/protocol/signal/fake-peer-session.d.ts +0 -87
- package/dist/protocol/signal/fake-peer-session.d.ts.map +0 -1
- package/dist/protocol/signal/fake-peer-session.js +0 -207
- package/dist/protocol/signal/fake-peer-session.js.map +0 -1
- package/dist/protocol/signal/fake-sender-key.d.ts.map +0 -1
- package/dist/protocol/signal/fake-sender-key.js.map +0 -1
- package/dist/protocol/signal/prekey-upload.d.ts.map +0 -1
- package/dist/protocol/signal/prekey-upload.js.map +0 -1
- package/dist/protocol/stream/stream-error.d.ts.map +0 -1
- package/dist/protocol/stream/stream-error.js.map +0 -1
- package/dist/state/fake-app-state-collection.d.ts.map +0 -1
- package/dist/state/fake-app-state-collection.js.map +0 -1
- package/dist/state/fake-media-store.d.ts.map +0 -1
- package/dist/state/fake-media-store.js.map +0 -1
- package/dist/transport/codec.d.ts.map +0 -1
- package/dist/transport/codec.js.map +0 -1
- package/dist/transport/crypto.d.ts.map +0 -1
- package/dist/transport/crypto.js.map +0 -1
- package/dist/transport/index.d.ts +0 -4
- package/dist/transport/index.d.ts.map +0 -1
- package/dist/transport/index.js +0 -20
- package/dist/transport/index.js.map +0 -1
- package/dist/transport/protos.d.ts.map +0 -1
- 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
|
+
}
|