@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/api/FakeWaServer.js
CHANGED
|
@@ -1,127 +1,98 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.FakeWaServer = void 0;
|
|
3
|
+
exports.FakeWaServer = exports.FakeServerSession = void 0;
|
|
4
4
|
const node_https_1 = require("node:https");
|
|
5
5
|
const WaFakeConnectionPipeline_1 = require("../infra/WaFakeConnectionPipeline");
|
|
6
6
|
const WaFakeMediaHttpsServer_1 = require("../infra/WaFakeMediaHttpsServer");
|
|
7
7
|
const WaFakeWsServer_1 = require("../infra/WaFakeWsServer");
|
|
8
8
|
const cert_chain_1 = require("../protocol/auth/cert-chain");
|
|
9
|
-
const router_1 = require("../protocol/iq/router");
|
|
10
9
|
const fake_media_store_1 = require("../state/fake-media-store");
|
|
11
10
|
const crypto_1 = require("../transport/crypto");
|
|
12
|
-
const AppStateSyncManager_1 = require("./AppStateSyncManager");
|
|
13
11
|
const FakePairingDriver_1 = require("./FakePairingDriver");
|
|
14
12
|
const FakePeer_1 = require("./FakePeer");
|
|
15
|
-
const
|
|
16
|
-
const PreKeyDispenser_1 = require("./PreKeyDispenser");
|
|
13
|
+
const FakeServerSession_1 = require("./FakeServerSession");
|
|
17
14
|
const Scenario_1 = require("./Scenario");
|
|
18
|
-
|
|
15
|
+
var FakeServerSession_2 = require("./FakeServerSession");
|
|
16
|
+
Object.defineProperty(exports, "FakeServerSession", { enumerable: true, get: function () { return FakeServerSession_2.FakeServerSession; } });
|
|
19
17
|
class FakeWaServer {
|
|
20
18
|
constructor(options = {}) {
|
|
21
|
-
this.registries = new ServerRegistries_1.ServerRegistries();
|
|
22
|
-
this.preKeyDispenser = new PreKeyDispenser_1.PreKeyDispenser();
|
|
23
|
-
this.appStateSync = new AppStateSyncManager_1.AppStateSyncManager();
|
|
24
19
|
this.pipelines = new Set();
|
|
25
|
-
this.
|
|
26
|
-
this.capturedStanzas = [];
|
|
27
|
-
this.pendingIqExpectations = new Set();
|
|
28
|
-
this.pendingStanzaExpectations = new Set();
|
|
20
|
+
this.pipelineSessions = new WeakMap();
|
|
29
21
|
this.authenticatedListeners = new Set();
|
|
30
|
-
this.
|
|
31
|
-
this.
|
|
22
|
+
this.sessions = new Map();
|
|
23
|
+
this.globalIqHandlers = new Set();
|
|
32
24
|
this.rootCa = null;
|
|
33
25
|
this.serverStaticKeyPair = null;
|
|
34
26
|
this.listenInfo = null;
|
|
35
|
-
this.
|
|
27
|
+
this.pipelineListeners = new Set();
|
|
36
28
|
this.rejectMode = null;
|
|
37
29
|
this.mediaStore = new fake_media_store_1.FakeMediaStore();
|
|
38
30
|
this.mediaHttpsServer = new WaFakeMediaHttpsServer_1.WaFakeMediaHttpsServer();
|
|
39
31
|
this.capturedMediaUploads = [];
|
|
40
32
|
this.nextUploadCounter = 0;
|
|
41
33
|
this.cachedMediaProxyAgent = null;
|
|
34
|
+
this.options = options;
|
|
42
35
|
this.wsServer = new WaFakeWsServer_1.WaFakeWsServer(options);
|
|
43
36
|
this.wsServer.onConnection((connection) => this.handleConnection(connection));
|
|
44
|
-
|
|
37
|
+
this.defaultSession = this.createSession('__default__');
|
|
45
38
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
lookupDeviceIdsForUser: (userJid) => reg.lookupDeviceIdsForUser(userJid),
|
|
82
|
-
notifyGroupOp: (op) => reg.notifyGroupOp(op),
|
|
83
|
-
mutatePrivacySettings: (category, value) => reg.mutatePrivacySettings(category, value),
|
|
84
|
-
mutateBlocklist: (action, jid) => reg.mutateBlocklist(action, jid),
|
|
85
|
-
notifyProfilePictureSet: (op) => reg.notifyProfilePictureSet(op),
|
|
86
|
-
handleProfilePictureSet: (targetJid, newId) => reg.handleProfilePictureSet(targetJid, newId),
|
|
87
|
-
notifyStatusSet: (text) => reg.notifyStatusSet(text),
|
|
88
|
-
notifyLogout: () => reg.notifyLogout(),
|
|
89
|
-
notifyPrivacyTokenIssue: (token) => reg.notifyPrivacyTokenIssue(token),
|
|
90
|
-
notifyDirtyBitsClear: (op) => reg.notifyDirtyBitsClear(op),
|
|
91
|
-
notifyPrivacySet: (change) => {
|
|
92
|
-
for (const listener of reg.privacySetListeners) {
|
|
93
|
-
try {
|
|
94
|
-
listener(change);
|
|
95
|
-
}
|
|
96
|
-
catch (error) {
|
|
97
|
-
void error;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
notifyBlocklistChange: (change) => {
|
|
102
|
-
for (const listener of reg.blocklistChangeListeners) {
|
|
103
|
-
try {
|
|
104
|
-
listener(change);
|
|
105
|
-
}
|
|
106
|
-
catch (error) {
|
|
107
|
-
void error;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
capturePreKeyBundle: (bundle) => preKey.captureBundle(bundle),
|
|
112
|
-
consumeOutboundAppStatePatches: (iq) => appState.consumeOutboundAppStatePatches(iq),
|
|
113
|
-
get appStateCollectionProviders() {
|
|
114
|
-
return appState.appStateCollectionProviders;
|
|
115
|
-
},
|
|
116
|
-
requireMediaHttpsInfo: () => this.requireMediaHttpsInfo()
|
|
117
|
-
};
|
|
39
|
+
/** State of the single default session (the only session for one client). */
|
|
40
|
+
get registries() {
|
|
41
|
+
return this.defaultSession.registries;
|
|
42
|
+
}
|
|
43
|
+
get preKeyDispenser() {
|
|
44
|
+
return this.defaultSession.preKeyDispenser;
|
|
45
|
+
}
|
|
46
|
+
get appStateSync() {
|
|
47
|
+
return this.defaultSession.appStateSync;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Returns the isolated session for `id`, creating it on first use. Every
|
|
51
|
+
* session gets its own registries, prekey dispenser, app-state, IQ router,
|
|
52
|
+
* and stanza capture. Use with the `sessionKey` option to run several
|
|
53
|
+
* `WaClient`s against one server without them sharing any state.
|
|
54
|
+
*/
|
|
55
|
+
session(id) {
|
|
56
|
+
const existing = this.sessions.get(id);
|
|
57
|
+
if (existing) {
|
|
58
|
+
return existing;
|
|
59
|
+
}
|
|
60
|
+
return this.createSession(id);
|
|
61
|
+
}
|
|
62
|
+
/** The session a connection is bound to (default before authentication). */
|
|
63
|
+
sessionFor(pipeline) {
|
|
64
|
+
return this.pipelineSessions.get(pipeline) ?? this.defaultSession;
|
|
65
|
+
}
|
|
66
|
+
createSession(id) {
|
|
67
|
+
const session = new FakeServerSession_1.FakeServerSession(id, { requireMediaHttpsInfo: () => this.requireMediaHttpsInfo() }, { defaultIqHandlers: this.options.defaultIqHandlers !== false });
|
|
68
|
+
this.sessions.set(id, session);
|
|
69
|
+
// Server-level custom handlers apply to every session, present and future.
|
|
70
|
+
for (const handler of this.globalIqHandlers) {
|
|
71
|
+
handler.unregisterBySession.set(session, session.registerIqHandler(handler.matcher, handler.respond, handler.label));
|
|
72
|
+
}
|
|
73
|
+
return session;
|
|
118
74
|
}
|
|
119
75
|
registerIqHandler(matcher, respond, label) {
|
|
120
|
-
const
|
|
121
|
-
|
|
76
|
+
const entry = {
|
|
77
|
+
matcher,
|
|
78
|
+
respond,
|
|
79
|
+
label,
|
|
80
|
+
unregisterBySession: new Map()
|
|
81
|
+
};
|
|
82
|
+
for (const session of this.sessions.values()) {
|
|
83
|
+
entry.unregisterBySession.set(session, session.registerIqHandler(matcher, respond, label));
|
|
84
|
+
}
|
|
85
|
+
this.globalIqHandlers.add(entry);
|
|
86
|
+
return () => {
|
|
87
|
+
for (const unregister of entry.unregisterBySession.values()) {
|
|
88
|
+
unregister();
|
|
89
|
+
}
|
|
90
|
+
entry.unregisterBySession.clear();
|
|
91
|
+
this.globalIqHandlers.delete(entry);
|
|
92
|
+
};
|
|
122
93
|
}
|
|
123
94
|
async routeIqForTest(iq) {
|
|
124
|
-
return this.
|
|
95
|
+
return this.defaultSession.routeIq(iq);
|
|
125
96
|
}
|
|
126
97
|
onOutboundGroupOp(listener) {
|
|
127
98
|
return this.registries.onOutboundGroupOp(listener);
|
|
@@ -178,7 +149,7 @@ class FakeWaServer {
|
|
|
178
149
|
return this.registries.createFakeGroup(input);
|
|
179
150
|
}
|
|
180
151
|
async triggerPreKeyUpload(pipeline, options = {}) {
|
|
181
|
-
return this.preKeyDispenser.triggerPreKeyUpload(pipeline, options);
|
|
152
|
+
return this.sessionFor(pipeline).preKeyDispenser.triggerPreKeyUpload(pipeline, options);
|
|
182
153
|
}
|
|
183
154
|
awaitPreKeyBundle(timeoutMs = 15000) {
|
|
184
155
|
return this.preKeyDispenser.awaitPreKeyBundle(timeoutMs);
|
|
@@ -208,7 +179,7 @@ class FakeWaServer {
|
|
|
208
179
|
return this.appStateSync.provideAppStateCollection(name, provider);
|
|
209
180
|
}
|
|
210
181
|
async pushServerSyncNotification(pipeline, input) {
|
|
211
|
-
return this.appStateSync.pushServerSyncNotification(pipeline, input);
|
|
182
|
+
return this.sessionFor(pipeline).appStateSync.pushServerSyncNotification(pipeline, input);
|
|
212
183
|
}
|
|
213
184
|
onAuthenticatedPipeline(listener) {
|
|
214
185
|
this.authenticatedListeners.add(listener);
|
|
@@ -218,78 +189,24 @@ class FakeWaServer {
|
|
|
218
189
|
configure(new Scenario_1.Scenario(this));
|
|
219
190
|
}
|
|
220
191
|
expectIq(matcher, options = {}) {
|
|
221
|
-
|
|
222
|
-
for (const captured of this.capturedStanzas) {
|
|
223
|
-
if (matchesIq(captured, matcher)) {
|
|
224
|
-
return Promise.resolve(captured);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
return new Promise((resolve, reject) => {
|
|
228
|
-
const expectation = {
|
|
229
|
-
matcher,
|
|
230
|
-
resolve: (iq) => {
|
|
231
|
-
this.pendingIqExpectations.delete(expectation);
|
|
232
|
-
clearTimeout(expectation.timer);
|
|
233
|
-
resolve(iq);
|
|
234
|
-
},
|
|
235
|
-
reject: (error) => {
|
|
236
|
-
this.pendingIqExpectations.delete(expectation);
|
|
237
|
-
clearTimeout(expectation.timer);
|
|
238
|
-
reject(error);
|
|
239
|
-
},
|
|
240
|
-
timer: setTimeout(() => {
|
|
241
|
-
this.pendingIqExpectations.delete(expectation);
|
|
242
|
-
reject(new Error(`expectIq timed out after ${timeoutMs}ms (${describeMatcher(matcher)})`));
|
|
243
|
-
}, timeoutMs)
|
|
244
|
-
};
|
|
245
|
-
this.pendingIqExpectations.add(expectation);
|
|
246
|
-
});
|
|
192
|
+
return this.defaultSession.expectIq(matcher, options);
|
|
247
193
|
}
|
|
248
194
|
capturedStanzaSnapshot() {
|
|
249
|
-
return this.
|
|
195
|
+
return this.defaultSession.capturedStanzaSnapshot();
|
|
250
196
|
}
|
|
251
197
|
/**
|
|
252
|
-
* Subscribes to every stanza captured from the client side (the lib)
|
|
253
|
-
* The listener is called synchronously for each new
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
* `expectStanza(...)` per iteration.
|
|
198
|
+
* Subscribes to every stanza captured from the client side (the lib) of
|
|
199
|
+
* the default session. The listener is called synchronously for each new
|
|
200
|
+
* stanza as it arrives. Returns an unsubscribe function. Useful for
|
|
201
|
+
* benches that need to count or react to a stream of receipts/messages
|
|
202
|
+
* without paying the O(N²) cost of polling `capturedStanzaSnapshot()` or
|
|
203
|
+
* queuing one `expectStanza(...)` per iteration.
|
|
259
204
|
*/
|
|
260
205
|
onCapturedStanza(listener) {
|
|
261
|
-
this.
|
|
262
|
-
return () => {
|
|
263
|
-
this.capturedStanzaListeners.delete(listener);
|
|
264
|
-
};
|
|
206
|
+
return this.defaultSession.onCapturedStanza(listener);
|
|
265
207
|
}
|
|
266
208
|
expectStanza(matcher, options = {}) {
|
|
267
|
-
|
|
268
|
-
for (const captured of this.capturedStanzas) {
|
|
269
|
-
if (matchesStanza(captured, matcher)) {
|
|
270
|
-
return Promise.resolve(captured);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
return new Promise((resolve, reject) => {
|
|
274
|
-
const expectation = {
|
|
275
|
-
matcher,
|
|
276
|
-
resolve: (node) => {
|
|
277
|
-
this.pendingStanzaExpectations.delete(expectation);
|
|
278
|
-
clearTimeout(expectation.timer);
|
|
279
|
-
resolve(node);
|
|
280
|
-
},
|
|
281
|
-
reject: (error) => {
|
|
282
|
-
this.pendingStanzaExpectations.delete(expectation);
|
|
283
|
-
clearTimeout(expectation.timer);
|
|
284
|
-
reject(error);
|
|
285
|
-
},
|
|
286
|
-
timer: setTimeout(() => {
|
|
287
|
-
this.pendingStanzaExpectations.delete(expectation);
|
|
288
|
-
reject(new Error(`expectStanza timed out after ${timeoutMs}ms (${describeStanzaMatcher(matcher)})`));
|
|
289
|
-
}, timeoutMs)
|
|
290
|
-
};
|
|
291
|
-
this.pendingStanzaExpectations.add(expectation);
|
|
292
|
-
});
|
|
209
|
+
return this.defaultSession.expectStanza(matcher, options);
|
|
293
210
|
}
|
|
294
211
|
async broadcastStanza(node) {
|
|
295
212
|
const tasks = [];
|
|
@@ -351,8 +268,9 @@ class FakeWaServer {
|
|
|
351
268
|
return this.capturedMediaUploads.slice();
|
|
352
269
|
}
|
|
353
270
|
async createFakePeer(options, pipeline) {
|
|
354
|
-
const
|
|
355
|
-
|
|
271
|
+
const session = this.sessionFor(pipeline);
|
|
272
|
+
const peer = await FakePeer_1.FakePeer.create(options, this.buildFakePeerDeps(pipeline, session));
|
|
273
|
+
session.registries.registerPeer(peer);
|
|
356
274
|
return peer;
|
|
357
275
|
}
|
|
358
276
|
async createFakePeerWithDevices(input, pipeline) {
|
|
@@ -365,6 +283,7 @@ class FakeWaServer {
|
|
|
365
283
|
}
|
|
366
284
|
const userPart = input.userJid.slice(0, atIdx);
|
|
367
285
|
const server = input.userJid.slice(atIdx + 1);
|
|
286
|
+
const session = this.sessionFor(pipeline);
|
|
368
287
|
const peers = [];
|
|
369
288
|
for (const deviceId of input.deviceIds) {
|
|
370
289
|
const deviceJid = deviceId === 0 ? input.userJid : `${userPart}:${deviceId}@${server}`;
|
|
@@ -372,36 +291,27 @@ class FakeWaServer {
|
|
|
372
291
|
jid: deviceJid,
|
|
373
292
|
displayName: input.displayName,
|
|
374
293
|
skipOneTimePreKey: input.skipOneTimePreKey
|
|
375
|
-
}, this.buildFakePeerDeps(pipeline));
|
|
376
|
-
|
|
294
|
+
}, this.buildFakePeerDeps(pipeline, session));
|
|
295
|
+
session.registries.registerPeer(peer);
|
|
377
296
|
peers.push(peer);
|
|
378
297
|
}
|
|
379
298
|
return peers;
|
|
380
299
|
}
|
|
381
|
-
buildFakePeerDeps(pipeline) {
|
|
300
|
+
buildFakePeerDeps(pipeline, session) {
|
|
382
301
|
return {
|
|
383
|
-
bundleResolver: () =>
|
|
384
|
-
reserveOneTimePreKey: () =>
|
|
302
|
+
bundleResolver: () => session.preKeyDispenser.awaitPreKeyBundle(),
|
|
303
|
+
reserveOneTimePreKey: () => session.preKeyDispenser.dispenseOneTimePreKey(),
|
|
385
304
|
pushStanza: (stanza) => pipeline.sendStanza(stanza),
|
|
386
|
-
subscribeInboundMessages: (listener) =>
|
|
387
|
-
const wrapped = (node) => {
|
|
388
|
-
if (node.tag !== 'message')
|
|
389
|
-
return;
|
|
390
|
-
listener(node);
|
|
391
|
-
};
|
|
392
|
-
this.inboundStanzaListeners.add(wrapped);
|
|
393
|
-
return () => {
|
|
394
|
-
this.inboundStanzaListeners.delete(wrapped);
|
|
395
|
-
};
|
|
396
|
-
}
|
|
305
|
+
subscribeInboundMessages: (listener) => session.subscribeInboundMessages(listener)
|
|
397
306
|
};
|
|
398
307
|
}
|
|
399
308
|
async runPairing(pipeline, options, companionMaterialResolver) {
|
|
309
|
+
const session = this.sessionFor(pipeline);
|
|
400
310
|
const driver = new FakePairingDriver_1.FakePairingDriver(options, {
|
|
401
311
|
pipeline,
|
|
402
312
|
companionMaterialResolver,
|
|
403
313
|
waitForPairDeviceAck: async (pairDeviceIqId) => {
|
|
404
|
-
await
|
|
314
|
+
await session.expectIq({
|
|
405
315
|
id: pairDeviceIqId
|
|
406
316
|
}, {
|
|
407
317
|
timeoutMs: 15000
|
|
@@ -438,8 +348,15 @@ class FakeWaServer {
|
|
|
438
348
|
const root = this.requireRootCa();
|
|
439
349
|
return { publicKey: root.publicKey, serial: root.serial };
|
|
440
350
|
}
|
|
351
|
+
/**
|
|
352
|
+
* Subscribes to every new connection pipeline (pre-auth). Listeners
|
|
353
|
+
* fan out; returns an unsubscribe function.
|
|
354
|
+
*/
|
|
441
355
|
onPipeline(listener) {
|
|
442
|
-
this.
|
|
356
|
+
this.pipelineListeners.add(listener);
|
|
357
|
+
return () => {
|
|
358
|
+
this.pipelineListeners.delete(listener);
|
|
359
|
+
};
|
|
443
360
|
}
|
|
444
361
|
async listen() {
|
|
445
362
|
if (this.listenInfo) {
|
|
@@ -481,53 +398,51 @@ class FakeWaServer {
|
|
|
481
398
|
connection,
|
|
482
399
|
rootCa: this.rootCa,
|
|
483
400
|
serverStaticKeyPair: this.serverStaticKeyPair,
|
|
484
|
-
|
|
401
|
+
routeIq: (node) => this.sessionFor(pipeline).routeIq(node),
|
|
402
|
+
...(this.options.successNodeAttributes !== undefined
|
|
403
|
+
? { successNodeAttributes: this.options.successNodeAttributes }
|
|
404
|
+
: {})
|
|
485
405
|
});
|
|
486
406
|
this.pipelines.add(pipeline);
|
|
407
|
+
this.pipelineSessions.set(pipeline, this.defaultSession);
|
|
487
408
|
pipeline.setEvents({
|
|
488
409
|
onAuthenticated: () => {
|
|
410
|
+
this.bindPipelineSession(pipeline);
|
|
489
411
|
for (const listener of this.authenticatedListeners) {
|
|
490
|
-
|
|
412
|
+
try {
|
|
413
|
+
void Promise.resolve(listener(pipeline)).catch(() => undefined);
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
/* ignore */
|
|
417
|
+
}
|
|
491
418
|
}
|
|
492
419
|
},
|
|
493
|
-
onStanza: (node) => this.handleCapturedStanza(node),
|
|
420
|
+
onStanza: (node) => this.sessionFor(pipeline).handleCapturedStanza(node),
|
|
494
421
|
onClose: () => this.pipelines.delete(pipeline)
|
|
495
422
|
});
|
|
496
|
-
this.
|
|
497
|
-
}
|
|
498
|
-
handleCapturedStanza(node) {
|
|
499
|
-
this.capturedStanzas.push(node);
|
|
500
|
-
for (const listener of this.capturedStanzaListeners) {
|
|
423
|
+
for (const listener of this.pipelineListeners) {
|
|
501
424
|
try {
|
|
502
|
-
listener(
|
|
425
|
+
void Promise.resolve(listener(pipeline)).catch(() => undefined);
|
|
503
426
|
}
|
|
504
|
-
catch
|
|
505
|
-
|
|
427
|
+
catch {
|
|
428
|
+
/* ignore */
|
|
506
429
|
}
|
|
507
430
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
break;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
if (node.tag !== 'iq') {
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Rebinds a connection to its keyed session once authenticated. Runs
|
|
434
|
+
* before user `onAuthenticated` listeners and before any client stanza, so
|
|
435
|
+
* captures and IQ routing land in the right session from the first frame.
|
|
436
|
+
* No-op without a `sessionKey` resolver (single default session).
|
|
437
|
+
*/
|
|
438
|
+
bindPipelineSession(pipeline) {
|
|
439
|
+
const resolveKey = this.options.sessionKey;
|
|
440
|
+
const clientPayload = pipeline.clientPayload;
|
|
441
|
+
if (!resolveKey || !clientPayload) {
|
|
523
442
|
return;
|
|
524
443
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
expectation.resolve(node);
|
|
528
|
-
return;
|
|
529
|
-
}
|
|
530
|
-
}
|
|
444
|
+
const id = resolveKey({ clientPayload, pipeline });
|
|
445
|
+
this.pipelineSessions.set(pipeline, this.session(id));
|
|
531
446
|
}
|
|
532
447
|
buildMediaRequestHandler() {
|
|
533
448
|
return (req, res) => {
|
|
@@ -616,69 +531,3 @@ function parseQueryParam(query, name) {
|
|
|
616
531
|
}
|
|
617
532
|
return undefined;
|
|
618
533
|
}
|
|
619
|
-
function matchesIq(iq, matcher) {
|
|
620
|
-
if (iq.tag !== 'iq')
|
|
621
|
-
return false;
|
|
622
|
-
if (matcher.id !== undefined && iq.attrs.id !== matcher.id)
|
|
623
|
-
return false;
|
|
624
|
-
if (matcher.type !== undefined && iq.attrs.type !== matcher.type)
|
|
625
|
-
return false;
|
|
626
|
-
if (matcher.xmlns !== undefined && iq.attrs.xmlns !== matcher.xmlns)
|
|
627
|
-
return false;
|
|
628
|
-
if (matcher.childTag !== undefined) {
|
|
629
|
-
const children = Array.isArray(iq.content) ? iq.content : null;
|
|
630
|
-
if (!children || children.length === 0)
|
|
631
|
-
return false;
|
|
632
|
-
if (children[0].tag !== matcher.childTag)
|
|
633
|
-
return false;
|
|
634
|
-
}
|
|
635
|
-
return true;
|
|
636
|
-
}
|
|
637
|
-
function describeMatcher(matcher) {
|
|
638
|
-
const parts = [];
|
|
639
|
-
if (matcher.id !== undefined)
|
|
640
|
-
parts.push(`id=${matcher.id}`);
|
|
641
|
-
if (matcher.type !== undefined)
|
|
642
|
-
parts.push(`type=${matcher.type}`);
|
|
643
|
-
if (matcher.xmlns !== undefined)
|
|
644
|
-
parts.push(`xmlns=${matcher.xmlns}`);
|
|
645
|
-
if (matcher.childTag !== undefined)
|
|
646
|
-
parts.push(`childTag=${matcher.childTag}`);
|
|
647
|
-
return parts.length > 0 ? parts.join(', ') : 'any iq';
|
|
648
|
-
}
|
|
649
|
-
function matchesStanza(node, matcher) {
|
|
650
|
-
if (matcher.tag !== undefined && node.tag !== matcher.tag)
|
|
651
|
-
return false;
|
|
652
|
-
if (matcher.type !== undefined && node.attrs.type !== matcher.type)
|
|
653
|
-
return false;
|
|
654
|
-
if (matcher.xmlns !== undefined && node.attrs.xmlns !== matcher.xmlns)
|
|
655
|
-
return false;
|
|
656
|
-
if (matcher.from !== undefined && node.attrs.from !== matcher.from)
|
|
657
|
-
return false;
|
|
658
|
-
if (matcher.to !== undefined && node.attrs.to !== matcher.to)
|
|
659
|
-
return false;
|
|
660
|
-
if (matcher.childTag !== undefined) {
|
|
661
|
-
const children = Array.isArray(node.content) ? node.content : null;
|
|
662
|
-
if (!children || children.length === 0)
|
|
663
|
-
return false;
|
|
664
|
-
if (children[0].tag !== matcher.childTag)
|
|
665
|
-
return false;
|
|
666
|
-
}
|
|
667
|
-
return true;
|
|
668
|
-
}
|
|
669
|
-
function describeStanzaMatcher(matcher) {
|
|
670
|
-
const parts = [];
|
|
671
|
-
if (matcher.tag !== undefined)
|
|
672
|
-
parts.push(`tag=${matcher.tag}`);
|
|
673
|
-
if (matcher.type !== undefined)
|
|
674
|
-
parts.push(`type=${matcher.type}`);
|
|
675
|
-
if (matcher.xmlns !== undefined)
|
|
676
|
-
parts.push(`xmlns=${matcher.xmlns}`);
|
|
677
|
-
if (matcher.from !== undefined)
|
|
678
|
-
parts.push(`from=${matcher.from}`);
|
|
679
|
-
if (matcher.to !== undefined)
|
|
680
|
-
parts.push(`to=${matcher.to}`);
|
|
681
|
-
if (matcher.childTag !== undefined)
|
|
682
|
-
parts.push(`childTag=${matcher.childTag}`);
|
|
683
|
-
return parts.length > 0 ? parts.join(', ') : 'any stanza';
|
|
684
|
-
}
|
|
@@ -34,6 +34,7 @@ export interface IqHandlerDeps {
|
|
|
34
34
|
notifyPrivacySet(change: CapturedPrivacySet): void;
|
|
35
35
|
notifyBlocklistChange(change: CapturedBlocklistChange): void;
|
|
36
36
|
capturePreKeyBundle(bundle: ClientPreKeyBundle): void;
|
|
37
|
+
countServerPreKeys(): number;
|
|
37
38
|
consumeOutboundAppStatePatches(iq: BinaryNode): Promise<void>;
|
|
38
39
|
readonly appStateCollectionProviders: ReadonlyMap<string, () => Promise<FakeAppStateCollectionPayload | null> | FakeAppStateCollectionPayload | null>;
|
|
39
40
|
requireMediaHttpsInfo(): {
|
package/dist/api/iq-handlers.js
CHANGED
|
@@ -98,6 +98,29 @@ function registerDefaultIqHandlers(router, deps) {
|
|
|
98
98
|
matcher: { xmlns: 'encrypt', type: 'get', childTag: 'digest' },
|
|
99
99
|
respond: (iq) => (0, router_1.buildIqError)(iq, { code: 404, text: 'item-not-found' })
|
|
100
100
|
});
|
|
101
|
+
// Baileys-family and whatsmeow clients block on this query before
|
|
102
|
+
// reporting the connection as open; zapo-js uses <digest> instead.
|
|
103
|
+
router.register({
|
|
104
|
+
label: 'prekey-count',
|
|
105
|
+
matcher: { xmlns: 'encrypt', type: 'get', childTag: 'count' },
|
|
106
|
+
respond: (iq) => {
|
|
107
|
+
const result = (0, router_1.buildIqResult)(iq);
|
|
108
|
+
return {
|
|
109
|
+
...result,
|
|
110
|
+
content: [
|
|
111
|
+
{
|
|
112
|
+
tag: 'count',
|
|
113
|
+
attrs: { value: String(deps.countServerPreKeys()) }
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
router.register({
|
|
120
|
+
label: 'passive-mode',
|
|
121
|
+
matcher: { xmlns: 'passive', type: 'set' },
|
|
122
|
+
respond: (iq) => (0, router_1.buildIqResult)(iq)
|
|
123
|
+
});
|
|
101
124
|
router.register({
|
|
102
125
|
label: 'media-conn',
|
|
103
126
|
matcher: { xmlns: 'w:m', type: 'set', childTag: 'media_conn' },
|