@zapo-js/fake-server 1.0.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 +93 -5
- package/dist/api/FakePeer.js +7 -1
- package/dist/api/FakeServerSession.d.ts +67 -0
- package/dist/api/FakeServerSession.js +293 -0
- package/dist/api/FakeWaServer.d.ts +74 -38
- package/dist/api/FakeWaServer.js +125 -276
- package/dist/api/iq-handlers.d.ts +1 -0
- package/dist/api/iq-handlers.js +23 -0
- package/dist/bin.js +69 -21
- package/dist/esm/api/FakePeer.js +7 -1
- package/dist/esm/api/FakeServerSession.js +287 -0
- package/dist/esm/api/FakeWaServer.js +123 -275
- package/dist/esm/api/iq-handlers.js +23 -0
- package/dist/esm/bin.js +67 -19
- package/dist/esm/index.js +1 -1
- package/dist/esm/infra/WaFakeConnectionPipeline.js +21 -9
- package/dist/esm/protocol/auth/cert-chain.js +13 -2
- package/dist/esm/protocol/iq/router.js +8 -2
- package/dist/esm/transport/util.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -1
- package/dist/infra/WaFakeConnectionPipeline.d.ts +10 -2
- package/dist/infra/WaFakeConnectionPipeline.js +21 -9
- package/dist/protocol/auth/cert-chain.d.ts +4 -0
- package/dist/protocol/auth/cert-chain.js +13 -2
- package/dist/protocol/iq/router.d.ts +7 -1
- package/dist/protocol/iq/router.js +8 -2
- package/dist/transport/util.d.ts +1 -1
- package/dist/transport/util.js +2 -1
- package/package.json +6 -1
package/dist/bin.js
CHANGED
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
/** Standalone CLI for `@zapo-js/fake-server`. */
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
const node_readline_1 = require("node:readline");
|
|
5
6
|
const FakeWaServer_1 = require("./api/FakeWaServer");
|
|
7
|
+
const pair_device_1 = require("./protocol/auth/pair-device");
|
|
8
|
+
const util_1 = require("./transport/util");
|
|
6
9
|
function parseArgs(argv) {
|
|
7
10
|
let host = '127.0.0.1';
|
|
8
11
|
let port = 0;
|
|
9
12
|
let path = '/ws/chat';
|
|
10
13
|
const peerJids = [];
|
|
11
14
|
const groupSpecs = [];
|
|
15
|
+
let pairJid = null;
|
|
12
16
|
let log = false;
|
|
13
17
|
let quiet = false;
|
|
14
18
|
let json = false;
|
|
@@ -46,6 +50,9 @@ function parseArgs(argv) {
|
|
|
46
50
|
case '--group':
|
|
47
51
|
groupSpecs.push(next());
|
|
48
52
|
break;
|
|
53
|
+
case '--pair':
|
|
54
|
+
pairJid = next();
|
|
55
|
+
break;
|
|
49
56
|
case '--log':
|
|
50
57
|
log = true;
|
|
51
58
|
break;
|
|
@@ -59,7 +66,7 @@ function parseArgs(argv) {
|
|
|
59
66
|
throw new Error(`unknown flag: ${arg}`);
|
|
60
67
|
}
|
|
61
68
|
}
|
|
62
|
-
return { host, port, path, peerJids, groupSpecs, log, quiet, json, help };
|
|
69
|
+
return { host, port, path, peerJids, groupSpecs, pairJid, log, quiet, json, help };
|
|
63
70
|
}
|
|
64
71
|
function printHelp() {
|
|
65
72
|
process.stdout.write(`@zapo-js/fake-server – standalone fake WhatsApp Web server
|
|
@@ -75,6 +82,12 @@ FLAGS
|
|
|
75
82
|
--group <spec> Pre-create a fake group; spec format
|
|
76
83
|
<group-jid>=<peer-jid>,<peer-jid>,...
|
|
77
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.
|
|
78
91
|
--log Print every captured inbound stanza
|
|
79
92
|
--quiet Suppress the startup info banner
|
|
80
93
|
--json Print connection info as JSON
|
|
@@ -105,7 +118,7 @@ async function main() {
|
|
|
105
118
|
args = parseArgs(process.argv.slice(2));
|
|
106
119
|
}
|
|
107
120
|
catch (error) {
|
|
108
|
-
process.stderr.write(`error: ${
|
|
121
|
+
process.stderr.write(`error: ${(0, util_1.toError)(error).message}\n`);
|
|
109
122
|
process.stderr.write('run with --help to see usage.\n');
|
|
110
123
|
process.exit(2);
|
|
111
124
|
}
|
|
@@ -127,11 +140,39 @@ async function main() {
|
|
|
127
140
|
port: args.port,
|
|
128
141
|
path: args.path
|
|
129
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
|
+
}
|
|
130
169
|
let setupComplete = false;
|
|
131
170
|
const setupPromise = new Promise((resolve, reject) => {
|
|
132
171
|
server.onAuthenticatedPipeline(async (pipeline) => {
|
|
133
172
|
if (setupComplete)
|
|
134
173
|
return;
|
|
174
|
+
if (pipeline.clientPayload?.kind !== 'login')
|
|
175
|
+
return;
|
|
135
176
|
setupComplete = true;
|
|
136
177
|
try {
|
|
137
178
|
const peers = new Map();
|
|
@@ -157,7 +198,7 @@ async function main() {
|
|
|
157
198
|
resolve();
|
|
158
199
|
}
|
|
159
200
|
catch (error) {
|
|
160
|
-
reject(
|
|
201
|
+
reject((0, util_1.toError)(error));
|
|
161
202
|
}
|
|
162
203
|
});
|
|
163
204
|
});
|
|
@@ -166,12 +207,11 @@ async function main() {
|
|
|
166
207
|
process.exitCode = 1;
|
|
167
208
|
});
|
|
168
209
|
if (args.log) {
|
|
169
|
-
server
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
});
|
|
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`);
|
|
175
215
|
});
|
|
176
216
|
}
|
|
177
217
|
if (!args.quiet) {
|
|
@@ -184,10 +224,11 @@ async function main() {
|
|
|
184
224
|
path: args.path,
|
|
185
225
|
noiseRootCa: {
|
|
186
226
|
serial: noiseRoot.serial,
|
|
187
|
-
publicKeyHex: bytesToHex(noiseRoot.publicKey)
|
|
227
|
+
publicKeyHex: (0, util_1.bytesToHex)(noiseRoot.publicKey)
|
|
188
228
|
},
|
|
189
229
|
peers: args.peerJids,
|
|
190
|
-
groups: parsedGroups
|
|
230
|
+
groups: parsedGroups,
|
|
231
|
+
pair: args.pairJid
|
|
191
232
|
}, null, 2) + '\n');
|
|
192
233
|
}
|
|
193
234
|
else {
|
|
@@ -200,9 +241,10 @@ async function main() {
|
|
|
200
241
|
`│ host ${server.host}`,
|
|
201
242
|
`│ port ${server.port}`,
|
|
202
243
|
`│ path ${args.path}`,
|
|
203
|
-
`│ noise root ca serial=${noiseRoot.serial} pub=${bytesToHex(noiseRoot.publicKey).slice(0, 32)}…`,
|
|
244
|
+
`│ noise root ca serial=${noiseRoot.serial} pub=${(0, util_1.bytesToHex)(noiseRoot.publicKey).slice(0, 32)}…`,
|
|
204
245
|
`│ peers ${args.peerJids.length === 0 ? '(none)' : args.peerJids.join(', ')}`,
|
|
205
246
|
`│ groups ${parsedGroups.length === 0 ? '(none)' : parsedGroups.map((g) => g.groupJid).join(', ')}`,
|
|
247
|
+
`│ pair mode ${args.pairJid ?? '(off – login only)'}`,
|
|
206
248
|
'├─────────────────────────────────────────────────────────────',
|
|
207
249
|
'│ Wire your WaClient with:',
|
|
208
250
|
'│ chatSocketUrls: [server.url]',
|
|
@@ -226,7 +268,7 @@ async function main() {
|
|
|
226
268
|
process.exit(0);
|
|
227
269
|
}
|
|
228
270
|
catch (error) {
|
|
229
|
-
process.stderr.write(`error during shutdown: ${
|
|
271
|
+
process.stderr.write(`error during shutdown: ${(0, util_1.toError)(error).message}\n`);
|
|
230
272
|
process.exit(1);
|
|
231
273
|
}
|
|
232
274
|
};
|
|
@@ -234,15 +276,21 @@ async function main() {
|
|
|
234
276
|
process.on('SIGTERM', () => void shutdown('SIGTERM'));
|
|
235
277
|
await new Promise(() => undefined);
|
|
236
278
|
}
|
|
237
|
-
function
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
+
});
|
|
244
291
|
}
|
|
245
292
|
main().catch((error) => {
|
|
246
|
-
|
|
293
|
+
const normalized = (0, util_1.toError)(error);
|
|
294
|
+
process.stderr.write(`fatal: ${normalized.stack ?? normalized.message}\n`);
|
|
247
295
|
process.exit(1);
|
|
248
296
|
});
|
package/dist/esm/api/FakePeer.js
CHANGED
|
@@ -547,7 +547,13 @@ export class FakePeer {
|
|
|
547
547
|
}
|
|
548
548
|
nextId() {
|
|
549
549
|
this.nextMessageCounter += 1;
|
|
550
|
-
|
|
550
|
+
// WA-style uppercase hex, no '@': strict binary decoders tokenize an
|
|
551
|
+
// id containing '@' as a JID and reject the stanza. The registration
|
|
552
|
+
// id keeps ids from colliding across peers created in the same ms.
|
|
553
|
+
const peerPart = this.keyBundle.registrationId.toString(16).padStart(4, '0');
|
|
554
|
+
const timePart = Date.now().toString(16);
|
|
555
|
+
const counterPart = this.nextMessageCounter.toString(16).padStart(4, '0');
|
|
556
|
+
return `3EB0${peerPart}${timePart}${counterPart}`.toUpperCase();
|
|
551
557
|
}
|
|
552
558
|
}
|
|
553
559
|
function findEncForPeer(stanza, peerJid) {
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/** Per-session isolated server state: registries, prekeys, app-state, IQ routing, capture. */
|
|
2
|
+
import { WaFakeIqRouter } from '../protocol/iq/router.js';
|
|
3
|
+
import { AppStateSyncManager } from './AppStateSyncManager.js';
|
|
4
|
+
import { registerDefaultIqHandlers } from './iq-handlers.js';
|
|
5
|
+
import { PreKeyDispenser } from './PreKeyDispenser.js';
|
|
6
|
+
import { ServerRegistries } from './ServerRegistries.js';
|
|
7
|
+
/**
|
|
8
|
+
* One isolated logical device's worth of server state. Every `WaClient`
|
|
9
|
+
* connected to a `FakeWaServer` is bound to exactly one session; peers,
|
|
10
|
+
* groups, privacy, prekeys, app-state, and captured stanzas never leak across
|
|
11
|
+
* sessions. A single-client server uses one default session, which is why the
|
|
12
|
+
* legacy `FakeWaServer` surface delegates straight through to it.
|
|
13
|
+
*/
|
|
14
|
+
export class FakeServerSession {
|
|
15
|
+
constructor(id, host, options) {
|
|
16
|
+
this.registries = new ServerRegistries();
|
|
17
|
+
this.preKeyDispenser = new PreKeyDispenser();
|
|
18
|
+
this.appStateSync = new AppStateSyncManager();
|
|
19
|
+
this.iqRouter = new WaFakeIqRouter();
|
|
20
|
+
this.capturedStanzas = [];
|
|
21
|
+
this.pendingIqExpectations = new Set();
|
|
22
|
+
this.pendingStanzaExpectations = new Set();
|
|
23
|
+
this.capturedStanzaListeners = new Set();
|
|
24
|
+
this.inboundStanzaListeners = new Set();
|
|
25
|
+
this.id = id;
|
|
26
|
+
if (options.defaultIqHandlers) {
|
|
27
|
+
registerDefaultIqHandlers(this.iqRouter, this.buildIqHandlerDeps(host));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
registerIqHandler(matcher, respond, label) {
|
|
31
|
+
const handler = { matcher, respond, label };
|
|
32
|
+
return this.iqRouter.register(handler, { priority: 'high' });
|
|
33
|
+
}
|
|
34
|
+
routeIq(iq) {
|
|
35
|
+
return this.iqRouter.route(iq);
|
|
36
|
+
}
|
|
37
|
+
handleCapturedStanza(node) {
|
|
38
|
+
this.capturedStanzas.push(node);
|
|
39
|
+
for (const listener of this.capturedStanzaListeners) {
|
|
40
|
+
try {
|
|
41
|
+
listener(node);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
void error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
for (const listener of this.inboundStanzaListeners) {
|
|
48
|
+
try {
|
|
49
|
+
listener(node);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
void error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
for (const expectation of this.pendingStanzaExpectations) {
|
|
56
|
+
if (matchesStanza(node, expectation.matcher)) {
|
|
57
|
+
expectation.resolve(node);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (node.tag !== 'iq') {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
for (const expectation of this.pendingIqExpectations) {
|
|
65
|
+
if (matchesIq(node, expectation.matcher)) {
|
|
66
|
+
expectation.resolve(node);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
subscribeInboundMessages(listener) {
|
|
72
|
+
const wrapped = (node) => {
|
|
73
|
+
if (node.tag !== 'message')
|
|
74
|
+
return;
|
|
75
|
+
listener(node);
|
|
76
|
+
};
|
|
77
|
+
this.inboundStanzaListeners.add(wrapped);
|
|
78
|
+
return () => {
|
|
79
|
+
this.inboundStanzaListeners.delete(wrapped);
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
onCapturedStanza(listener) {
|
|
83
|
+
this.capturedStanzaListeners.add(listener);
|
|
84
|
+
return () => {
|
|
85
|
+
this.capturedStanzaListeners.delete(listener);
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
capturedStanzaSnapshot() {
|
|
89
|
+
return this.capturedStanzas.slice();
|
|
90
|
+
}
|
|
91
|
+
expectIq(matcher, options = {}) {
|
|
92
|
+
const timeoutMs = options.timeoutMs ?? 2000;
|
|
93
|
+
for (const captured of this.capturedStanzas) {
|
|
94
|
+
if (matchesIq(captured, matcher)) {
|
|
95
|
+
return Promise.resolve(captured);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
const expectation = {
|
|
100
|
+
matcher,
|
|
101
|
+
resolve: (iq) => {
|
|
102
|
+
this.pendingIqExpectations.delete(expectation);
|
|
103
|
+
clearTimeout(expectation.timer);
|
|
104
|
+
resolve(iq);
|
|
105
|
+
},
|
|
106
|
+
reject: (error) => {
|
|
107
|
+
this.pendingIqExpectations.delete(expectation);
|
|
108
|
+
clearTimeout(expectation.timer);
|
|
109
|
+
reject(error);
|
|
110
|
+
},
|
|
111
|
+
timer: setTimeout(() => {
|
|
112
|
+
this.pendingIqExpectations.delete(expectation);
|
|
113
|
+
reject(new Error(`expectIq timed out after ${timeoutMs}ms (${describeMatcher(matcher)})`));
|
|
114
|
+
}, timeoutMs)
|
|
115
|
+
};
|
|
116
|
+
this.pendingIqExpectations.add(expectation);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
expectStanza(matcher, options = {}) {
|
|
120
|
+
const timeoutMs = options.timeoutMs ?? 2000;
|
|
121
|
+
for (const captured of this.capturedStanzas) {
|
|
122
|
+
if (matchesStanza(captured, matcher)) {
|
|
123
|
+
return Promise.resolve(captured);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return new Promise((resolve, reject) => {
|
|
127
|
+
const expectation = {
|
|
128
|
+
matcher,
|
|
129
|
+
resolve: (node) => {
|
|
130
|
+
this.pendingStanzaExpectations.delete(expectation);
|
|
131
|
+
clearTimeout(expectation.timer);
|
|
132
|
+
resolve(node);
|
|
133
|
+
},
|
|
134
|
+
reject: (error) => {
|
|
135
|
+
this.pendingStanzaExpectations.delete(expectation);
|
|
136
|
+
clearTimeout(expectation.timer);
|
|
137
|
+
reject(error);
|
|
138
|
+
},
|
|
139
|
+
timer: setTimeout(() => {
|
|
140
|
+
this.pendingStanzaExpectations.delete(expectation);
|
|
141
|
+
reject(new Error(`expectStanza timed out after ${timeoutMs}ms (${describeStanzaMatcher(matcher)})`));
|
|
142
|
+
}, timeoutMs)
|
|
143
|
+
};
|
|
144
|
+
this.pendingStanzaExpectations.add(expectation);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
buildIqHandlerDeps(host) {
|
|
148
|
+
const reg = this.registries;
|
|
149
|
+
const preKey = this.preKeyDispenser;
|
|
150
|
+
const appState = this.appStateSync;
|
|
151
|
+
return {
|
|
152
|
+
get peerRegistry() {
|
|
153
|
+
return reg.peerRegistry;
|
|
154
|
+
},
|
|
155
|
+
get groupRegistry() {
|
|
156
|
+
return reg.groupRegistry;
|
|
157
|
+
},
|
|
158
|
+
get privacySettings() {
|
|
159
|
+
return reg.privacySettings;
|
|
160
|
+
},
|
|
161
|
+
get blocklistJids() {
|
|
162
|
+
return reg.blocklistJids;
|
|
163
|
+
},
|
|
164
|
+
get profilePicturesByJid() {
|
|
165
|
+
return reg.profilePicturesByJid;
|
|
166
|
+
},
|
|
167
|
+
get businessProfilesByJid() {
|
|
168
|
+
return reg.businessProfilesByJid;
|
|
169
|
+
},
|
|
170
|
+
get abPropsInput() {
|
|
171
|
+
return reg.abPropsInput;
|
|
172
|
+
},
|
|
173
|
+
get issuedPrivacyTokens() {
|
|
174
|
+
return reg.issuedPrivacyTokens;
|
|
175
|
+
},
|
|
176
|
+
get latestStatusText() {
|
|
177
|
+
return reg.latestStatusText;
|
|
178
|
+
},
|
|
179
|
+
setLatestStatusText: (text) => {
|
|
180
|
+
reg.latestStatusText = text;
|
|
181
|
+
},
|
|
182
|
+
lookupDeviceIdsForUser: (userJid) => reg.lookupDeviceIdsForUser(userJid),
|
|
183
|
+
notifyGroupOp: (op) => reg.notifyGroupOp(op),
|
|
184
|
+
mutatePrivacySettings: (category, value) => reg.mutatePrivacySettings(category, value),
|
|
185
|
+
mutateBlocklist: (action, jid) => reg.mutateBlocklist(action, jid),
|
|
186
|
+
notifyProfilePictureSet: (op) => reg.notifyProfilePictureSet(op),
|
|
187
|
+
handleProfilePictureSet: (targetJid, newId) => reg.handleProfilePictureSet(targetJid, newId),
|
|
188
|
+
notifyStatusSet: (text) => reg.notifyStatusSet(text),
|
|
189
|
+
notifyLogout: () => reg.notifyLogout(),
|
|
190
|
+
notifyPrivacyTokenIssue: (token) => reg.notifyPrivacyTokenIssue(token),
|
|
191
|
+
notifyDirtyBitsClear: (op) => reg.notifyDirtyBitsClear(op),
|
|
192
|
+
notifyPrivacySet: (change) => {
|
|
193
|
+
for (const listener of reg.privacySetListeners) {
|
|
194
|
+
try {
|
|
195
|
+
listener(change);
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
void error;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
notifyBlocklistChange: (change) => {
|
|
203
|
+
for (const listener of reg.blocklistChangeListeners) {
|
|
204
|
+
try {
|
|
205
|
+
listener(change);
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
void error;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
capturePreKeyBundle: (bundle) => preKey.captureBundle(bundle),
|
|
213
|
+
countServerPreKeys: () => preKey.preKeysAvailable(),
|
|
214
|
+
consumeOutboundAppStatePatches: (iq) => appState.consumeOutboundAppStatePatches(iq),
|
|
215
|
+
get appStateCollectionProviders() {
|
|
216
|
+
return appState.appStateCollectionProviders;
|
|
217
|
+
},
|
|
218
|
+
requireMediaHttpsInfo: () => host.requireMediaHttpsInfo()
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
export function matchesIq(iq, matcher) {
|
|
223
|
+
if (iq.tag !== 'iq')
|
|
224
|
+
return false;
|
|
225
|
+
if (matcher.id !== undefined && iq.attrs.id !== matcher.id)
|
|
226
|
+
return false;
|
|
227
|
+
if (matcher.type !== undefined && iq.attrs.type !== matcher.type)
|
|
228
|
+
return false;
|
|
229
|
+
if (matcher.xmlns !== undefined && iq.attrs.xmlns !== matcher.xmlns)
|
|
230
|
+
return false;
|
|
231
|
+
if (matcher.childTag !== undefined) {
|
|
232
|
+
const children = Array.isArray(iq.content) ? iq.content : null;
|
|
233
|
+
if (!children || children.length === 0)
|
|
234
|
+
return false;
|
|
235
|
+
if (children[0].tag !== matcher.childTag)
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
function describeMatcher(matcher) {
|
|
241
|
+
const parts = [];
|
|
242
|
+
if (matcher.id !== undefined)
|
|
243
|
+
parts.push(`id=${matcher.id}`);
|
|
244
|
+
if (matcher.type !== undefined)
|
|
245
|
+
parts.push(`type=${matcher.type}`);
|
|
246
|
+
if (matcher.xmlns !== undefined)
|
|
247
|
+
parts.push(`xmlns=${matcher.xmlns}`);
|
|
248
|
+
if (matcher.childTag !== undefined)
|
|
249
|
+
parts.push(`childTag=${matcher.childTag}`);
|
|
250
|
+
return parts.length > 0 ? parts.join(', ') : 'any iq';
|
|
251
|
+
}
|
|
252
|
+
export function matchesStanza(node, matcher) {
|
|
253
|
+
if (matcher.tag !== undefined && node.tag !== matcher.tag)
|
|
254
|
+
return false;
|
|
255
|
+
if (matcher.type !== undefined && node.attrs.type !== matcher.type)
|
|
256
|
+
return false;
|
|
257
|
+
if (matcher.xmlns !== undefined && node.attrs.xmlns !== matcher.xmlns)
|
|
258
|
+
return false;
|
|
259
|
+
if (matcher.from !== undefined && node.attrs.from !== matcher.from)
|
|
260
|
+
return false;
|
|
261
|
+
if (matcher.to !== undefined && node.attrs.to !== matcher.to)
|
|
262
|
+
return false;
|
|
263
|
+
if (matcher.childTag !== undefined) {
|
|
264
|
+
const children = Array.isArray(node.content) ? node.content : null;
|
|
265
|
+
if (!children || children.length === 0)
|
|
266
|
+
return false;
|
|
267
|
+
if (children[0].tag !== matcher.childTag)
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
function describeStanzaMatcher(matcher) {
|
|
273
|
+
const parts = [];
|
|
274
|
+
if (matcher.tag !== undefined)
|
|
275
|
+
parts.push(`tag=${matcher.tag}`);
|
|
276
|
+
if (matcher.type !== undefined)
|
|
277
|
+
parts.push(`type=${matcher.type}`);
|
|
278
|
+
if (matcher.xmlns !== undefined)
|
|
279
|
+
parts.push(`xmlns=${matcher.xmlns}`);
|
|
280
|
+
if (matcher.from !== undefined)
|
|
281
|
+
parts.push(`from=${matcher.from}`);
|
|
282
|
+
if (matcher.to !== undefined)
|
|
283
|
+
parts.push(`to=${matcher.to}`);
|
|
284
|
+
if (matcher.childTag !== undefined)
|
|
285
|
+
parts.push(`childTag=${matcher.childTag}`);
|
|
286
|
+
return parts.length > 0 ? parts.join(', ') : 'any stanza';
|
|
287
|
+
}
|