zapo-js 1.5.0 → 1.5.1
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/dist/client/coordinators/WaAppStateMutationCoordinator.js +2 -2
- package/dist/client/coordinators/WaMessageDispatchCoordinator.js +1 -1
- package/dist/esm/client/coordinators/WaAppStateMutationCoordinator.js +2 -2
- package/dist/esm/client/coordinators/WaMessageDispatchCoordinator.js +1 -1
- package/dist/esm/signal/api/SignalDeviceSyncApi.js +133 -63
- package/dist/signal/api/SignalDeviceSyncApi.d.ts +29 -2
- package/dist/signal/api/SignalDeviceSyncApi.js +131 -61
- package/package.json +1 -1
- package/spec/appstate/index.d.ts +5 -4
- package/spec/appstate/index.js +21 -8
- package/spec/mex/index.d.ts +335 -73
- package/spec/mex/index.js +29 -21
- package/spec/proto/index.d.ts +833 -69
- package/spec/proto/index.js +1 -1
- package/spec/version/index.d.ts +1 -1
- package/spec/version/index.js +1 -1
- package/spec/version/version.json +1 -1
|
@@ -761,8 +761,8 @@ class WaAppStateMutationCoordinator {
|
|
|
761
761
|
statusPrivacy: {
|
|
762
762
|
mode: modeValue,
|
|
763
763
|
userJid,
|
|
764
|
-
...(input.shareToFB === undefined ? {} : {
|
|
765
|
-
...(input.shareToIG === undefined ? {} : {
|
|
764
|
+
...(input.shareToFB === undefined ? {} : { shareToFb: input.shareToFB }),
|
|
765
|
+
...(input.shareToIG === undefined ? {} : { shareToIg: input.shareToIG })
|
|
766
766
|
}
|
|
767
767
|
};
|
|
768
768
|
const timestamp = this.serverClock.nowMs();
|
|
@@ -318,7 +318,7 @@ class WaMessageDispatchCoordinator {
|
|
|
318
318
|
}
|
|
319
319
|
try {
|
|
320
320
|
const results = await this.deps.signalDeviceSync.queryLidsByPhoneJids([pnUserJid]);
|
|
321
|
-
const match = results.find((entry) => entry.
|
|
321
|
+
const match = results.find((entry) => entry.queriedJid === pnUserJid);
|
|
322
322
|
if (match?.lidJid)
|
|
323
323
|
return match.lidJid;
|
|
324
324
|
}
|
|
@@ -758,8 +758,8 @@ export class WaAppStateMutationCoordinator {
|
|
|
758
758
|
statusPrivacy: {
|
|
759
759
|
mode: modeValue,
|
|
760
760
|
userJid,
|
|
761
|
-
...(input.shareToFB === undefined ? {} : {
|
|
762
|
-
...(input.shareToIG === undefined ? {} : {
|
|
761
|
+
...(input.shareToFB === undefined ? {} : { shareToFb: input.shareToFB }),
|
|
762
|
+
...(input.shareToIG === undefined ? {} : { shareToIg: input.shareToIG })
|
|
763
763
|
}
|
|
764
764
|
};
|
|
765
765
|
const timestamp = this.serverClock.nowMs();
|
|
@@ -315,7 +315,7 @@ export class WaMessageDispatchCoordinator {
|
|
|
315
315
|
}
|
|
316
316
|
try {
|
|
317
317
|
const results = await this.deps.signalDeviceSync.queryLidsByPhoneJids([pnUserJid]);
|
|
318
|
-
const match = results.find((entry) => entry.
|
|
318
|
+
const match = results.find((entry) => entry.queriedJid === pnUserJid);
|
|
319
319
|
if (match?.lidJid)
|
|
320
320
|
return match.lidJid;
|
|
321
321
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { PromiseDedup } from '../../infra/perf/PromiseDedup.js';
|
|
2
2
|
import { WA_DEFAULTS, WA_NODE_TAGS, WA_USYNC_CONTEXTS } from '../../protocol/constants.js';
|
|
3
|
-
import { buildDeviceJid, isHostedDeviceId, splitJid, toUserJid } from '../../protocol/jid.js';
|
|
3
|
+
import { buildDeviceJid, isHostedDeviceId, parsePhoneJid, splitJid, toUserJid } from '../../protocol/jid.js';
|
|
4
4
|
import { buildUsyncIq, iterateUsyncUsers, parseUsyncResultEnvelope } from '../../transport/node/builders/usync.js';
|
|
5
|
-
import { findNodeChild, getNodeChildrenByTag } from '../../transport/node/helpers.js';
|
|
5
|
+
import { findNodeChild, getNodeChildrenByTag, getNodeTextContent } from '../../transport/node/helpers.js';
|
|
6
6
|
import { assertIqResult } from '../../transport/node/query.js';
|
|
7
7
|
import { createUsyncSidGenerator, logUsyncProtocolErrors } from '../../transport/node/usync.js';
|
|
8
8
|
/**
|
|
@@ -112,22 +112,21 @@ export class SignalDeviceSyncApi {
|
|
|
112
112
|
const parsedByPhoneJid = new Map();
|
|
113
113
|
for (let index = 0; index < parsed.length; index += 1) {
|
|
114
114
|
const entry = parsed[index];
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
phoneJid,
|
|
115
|
+
parsedByPhoneJid.set(entry.jid, {
|
|
116
|
+
phoneJid: entry.phoneJid ?? entry.jid,
|
|
118
117
|
lidJid: entry.lidJid,
|
|
119
|
-
exists: entry.exists
|
|
118
|
+
exists: entry.exists,
|
|
119
|
+
invalid: entry.invalid
|
|
120
120
|
});
|
|
121
121
|
}
|
|
122
122
|
const result = new Array(normalizedPhoneJids.length);
|
|
123
123
|
let found = 0;
|
|
124
124
|
for (let index = 0; index < normalizedPhoneJids.length; index += 1) {
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
exists: false
|
|
130
|
-
};
|
|
125
|
+
const queriedJid = normalizedPhoneJids[index];
|
|
126
|
+
const hit = parsedByPhoneJid.get(queriedJid);
|
|
127
|
+
const resolved = hit
|
|
128
|
+
? { queriedJid, ...hit }
|
|
129
|
+
: { queriedJid, phoneJid: queriedJid, lidJid: null, exists: false, invalid: false };
|
|
131
130
|
if (resolved.exists) {
|
|
132
131
|
found += 1;
|
|
133
132
|
}
|
|
@@ -151,24 +150,23 @@ export class SignalDeviceSyncApi {
|
|
|
151
150
|
if (!this.deviceListStore || results.length === 0)
|
|
152
151
|
return;
|
|
153
152
|
const nowMs = Date.now();
|
|
154
|
-
const
|
|
155
|
-
const
|
|
153
|
+
const lidByQueriedJid = new Map();
|
|
154
|
+
const queriedJids = [];
|
|
156
155
|
for (const entry of results) {
|
|
157
|
-
if (entry.lidJid
|
|
158
|
-
|
|
159
|
-
|
|
156
|
+
if (entry.lidJid) {
|
|
157
|
+
lidByQueriedJid.set(entry.queriedJid, entry.lidJid);
|
|
158
|
+
queriedJids.push(entry.queriedJid);
|
|
160
159
|
}
|
|
161
160
|
}
|
|
162
|
-
if (
|
|
161
|
+
if (queriedJids.length === 0)
|
|
163
162
|
return;
|
|
164
|
-
const existing = await this.deviceListStore.getUserDevicesBatch(
|
|
163
|
+
const existing = await this.deviceListStore.getUserDevicesBatch(queriedJids, nowMs);
|
|
165
164
|
const updates = [];
|
|
166
|
-
for (let index = 0; index <
|
|
165
|
+
for (let index = 0; index < queriedJids.length; index += 1) {
|
|
167
166
|
const snapshot = existing[index];
|
|
168
167
|
if (!snapshot)
|
|
169
168
|
continue;
|
|
170
|
-
const
|
|
171
|
-
const lidJid = lidByPhoneJid.get(phoneJid);
|
|
169
|
+
const lidJid = lidByQueriedJid.get(queriedJids[index]);
|
|
172
170
|
if (!lidJid || snapshot.altUserJid === lidJid)
|
|
173
171
|
continue;
|
|
174
172
|
updates.push({
|
|
@@ -271,7 +269,13 @@ export class SignalDeviceSyncApi {
|
|
|
271
269
|
if (!userJid) {
|
|
272
270
|
continue;
|
|
273
271
|
}
|
|
274
|
-
const normalizedUserJid = this.
|
|
272
|
+
const normalizedUserJid = this.tryNormalizeUserJid(userJid);
|
|
273
|
+
if (normalizedUserJid === null) {
|
|
274
|
+
this.logger.debug('signal device sync skipping user node with invalid jid', {
|
|
275
|
+
jid: userJid
|
|
276
|
+
});
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
275
279
|
if (!requestedSet.has(normalizedUserJid)) {
|
|
276
280
|
continue;
|
|
277
281
|
}
|
|
@@ -292,55 +296,107 @@ export class SignalDeviceSyncApi {
|
|
|
292
296
|
throw new Error('signal lid sync response missing usync envelope');
|
|
293
297
|
}
|
|
294
298
|
const requestedSet = new Set(requestedUsers);
|
|
295
|
-
const parsed =
|
|
296
|
-
|
|
299
|
+
const parsed = [];
|
|
300
|
+
const lidUserErrors = [];
|
|
297
301
|
for (let index = 0; index < userNodes.length; index += 1) {
|
|
298
302
|
const userNode = userNodes[index];
|
|
299
|
-
const
|
|
300
|
-
if (!
|
|
303
|
+
const rawUserJid = userNode.attrs.jid;
|
|
304
|
+
if (!rawUserJid) {
|
|
301
305
|
continue;
|
|
302
306
|
}
|
|
303
|
-
const
|
|
304
|
-
const
|
|
305
|
-
? this.
|
|
307
|
+
const resolvedJid = this.tryNormalizeUserJid(rawUserJid);
|
|
308
|
+
const pnJid = userNode.attrs.pn_jid
|
|
309
|
+
? this.tryNormalizeUserJid(userNode.attrs.pn_jid)
|
|
310
|
+
: null;
|
|
311
|
+
const phoneJid = pnJid ?? resolvedJid;
|
|
312
|
+
const lidNode = findNodeChild(userNode, WA_NODE_TAGS.LID);
|
|
313
|
+
const lidErrorNode = lidNode ? findNodeChild(lidNode, WA_NODE_TAGS.ERROR) : null;
|
|
314
|
+
if (lidErrorNode) {
|
|
315
|
+
lidUserErrors.push({
|
|
316
|
+
jid: resolvedJid ?? rawUserJid,
|
|
317
|
+
code: lidErrorNode.attrs.code,
|
|
318
|
+
text: lidErrorNode.attrs.text
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
const lidJid = !lidErrorNode && lidNode?.attrs.val
|
|
322
|
+
? this.tryNormalizeUserJid(lidNode.attrs.val)
|
|
306
323
|
: null;
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
324
|
+
const contactNodes = getNodeChildrenByTag(userNode, WA_NODE_TAGS.CONTACT);
|
|
325
|
+
let matched = false;
|
|
326
|
+
for (let c = 0; c < contactNodes.length; c += 1) {
|
|
327
|
+
const contactNode = contactNodes[c];
|
|
328
|
+
const inputJid = this.recoverContactJid(contactNode);
|
|
329
|
+
if (inputJid === null || !requestedSet.has(inputJid)) {
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
matched = true;
|
|
333
|
+
const invalid = resolvedJid === null || contactNode.attrs.type === 'invalid';
|
|
334
|
+
parsed.push({
|
|
335
|
+
jid: inputJid,
|
|
336
|
+
phoneJid: phoneJid ?? inputJid,
|
|
337
|
+
lidJid,
|
|
338
|
+
exists: !invalid &&
|
|
339
|
+
this.parseLidSyncContactExists(contactNode, inputJid, lidJid !== null),
|
|
340
|
+
invalid
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
if (matched) {
|
|
310
344
|
continue;
|
|
311
345
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
parsedCount += 1;
|
|
346
|
+
if (resolvedJid === null) {
|
|
347
|
+
this.logger.debug('signal lid sync skipping user node with invalid jid', {
|
|
348
|
+
jid: rawUserJid
|
|
349
|
+
});
|
|
317
350
|
continue;
|
|
318
351
|
}
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
352
|
+
const requestedKey = requestedSet.has(resolvedJid)
|
|
353
|
+
? resolvedJid
|
|
354
|
+
: pnJid !== null && requestedSet.has(pnJid)
|
|
355
|
+
? pnJid
|
|
356
|
+
: null;
|
|
357
|
+
if (requestedKey === null) {
|
|
358
|
+
this.logger.debug('signal lid sync unmatched user (no contact echo)', {
|
|
359
|
+
jid: resolvedJid,
|
|
360
|
+
pnJid
|
|
325
361
|
});
|
|
326
|
-
parsed[parsedCount] = this.buildLidSyncResult(normalizedUserJid, normalizedPhoneJid, contactNode, null);
|
|
327
|
-
parsedCount += 1;
|
|
328
362
|
continue;
|
|
329
363
|
}
|
|
330
|
-
const
|
|
331
|
-
parsed
|
|
332
|
-
|
|
364
|
+
const contactNode = findNodeChild(userNode, WA_NODE_TAGS.CONTACT);
|
|
365
|
+
parsed.push({
|
|
366
|
+
jid: requestedKey,
|
|
367
|
+
phoneJid: phoneJid ?? requestedKey,
|
|
368
|
+
lidJid,
|
|
369
|
+
exists: this.parseLidSyncContactExists(contactNode, requestedKey, lidJid !== null),
|
|
370
|
+
invalid: false
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
if (lidUserErrors.length > 0) {
|
|
374
|
+
this.logger.warn('signal lid sync user errors', {
|
|
375
|
+
droppedCount: lidUserErrors.length,
|
|
376
|
+
totalExpected: requestedUsers.length,
|
|
377
|
+
sample: lidUserErrors.slice(0, 3)
|
|
378
|
+
});
|
|
333
379
|
}
|
|
334
|
-
parsed.length = parsedCount;
|
|
335
380
|
return parsed;
|
|
336
381
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
382
|
+
/**
|
|
383
|
+
* Recovers the queried phone JID from a `<contact>` echo. Each `<contact>` in a
|
|
384
|
+
* usync response echoes the `+<number>` we sent (base64 text content), so it maps
|
|
385
|
+
* a response node back to the exact input - even when the server corrected the
|
|
386
|
+
* `<user jid>` or rejected it as `jid='undefined'`. Returns `null` when there is
|
|
387
|
+
* no decodable phone echo.
|
|
388
|
+
*/
|
|
389
|
+
recoverContactJid(contactNode) {
|
|
390
|
+
const echoed = getNodeTextContent(contactNode);
|
|
391
|
+
if (!echoed) {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
try {
|
|
395
|
+
return parsePhoneJid(echoed);
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
344
400
|
}
|
|
345
401
|
parseLidSyncContactExists(contactNode, userJid, defaultExists) {
|
|
346
402
|
if (!contactNode) {
|
|
@@ -401,7 +457,10 @@ export class SignalDeviceSyncApi {
|
|
|
401
457
|
let normalizedCount = 0;
|
|
402
458
|
const dedup = new Set();
|
|
403
459
|
for (let index = 0; index < userJids.length; index += 1) {
|
|
404
|
-
const normalizedJid =
|
|
460
|
+
const normalizedJid = toUserJid(userJids[index], {
|
|
461
|
+
canonicalizeSignalServer: true,
|
|
462
|
+
hostDomain: this.hostDomain
|
|
463
|
+
});
|
|
405
464
|
if (dedup.has(normalizedJid)) {
|
|
406
465
|
continue;
|
|
407
466
|
}
|
|
@@ -412,10 +471,21 @@ export class SignalDeviceSyncApi {
|
|
|
412
471
|
normalized.length = normalizedCount;
|
|
413
472
|
return normalized;
|
|
414
473
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
474
|
+
/**
|
|
475
|
+
* Canonicalizes a user jid, returning `null` instead of throwing when the input
|
|
476
|
+
* is not a valid jid (for example the literal `'undefined'` the server returns
|
|
477
|
+
* for an invalid contact). Lets a single malformed response node be skipped
|
|
478
|
+
* without discarding the rest of the batch.
|
|
479
|
+
*/
|
|
480
|
+
tryNormalizeUserJid(jid) {
|
|
481
|
+
try {
|
|
482
|
+
return toUserJid(jid, {
|
|
483
|
+
canonicalizeSignalServer: true,
|
|
484
|
+
hostDomain: this.hostDomain
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
catch {
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
420
490
|
}
|
|
421
491
|
}
|
|
@@ -11,9 +11,23 @@ interface SignalDeviceSyncApiOptions {
|
|
|
11
11
|
readonly generateSid?: WaUsyncSidGenerator;
|
|
12
12
|
}
|
|
13
13
|
export interface SignalLidSyncResult {
|
|
14
|
+
/**
|
|
15
|
+
* The phone jid the caller queried (normalized), so results can be correlated
|
|
16
|
+
* back to the input by value rather than by array position. Equals `phoneJid`
|
|
17
|
+
* unless the server corrected the number.
|
|
18
|
+
*/
|
|
19
|
+
readonly queriedJid: string;
|
|
20
|
+
/** The server's canonical/corrected phone jid (e.g. BR 9th digit added). */
|
|
14
21
|
readonly phoneJid: string;
|
|
15
22
|
readonly lidJid: string | null;
|
|
16
23
|
readonly exists: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* `true` when the server rejected the number as malformed (`<user
|
|
26
|
+
* jid='undefined'>` with `<contact type='invalid'>`) - distinct from a
|
|
27
|
+
* well-formed number that simply has no WhatsApp account (`exists: false`,
|
|
28
|
+
* `invalid: false`).
|
|
29
|
+
*/
|
|
30
|
+
readonly invalid: boolean;
|
|
17
31
|
}
|
|
18
32
|
/**
|
|
19
33
|
* Resolves the device list and LID mapping for a set of users via the `usync`
|
|
@@ -55,10 +69,23 @@ export declare class SignalDeviceSyncApi {
|
|
|
55
69
|
private makeLidSyncRequest;
|
|
56
70
|
private parseDeviceSyncResponse;
|
|
57
71
|
private parseLidSyncResponse;
|
|
58
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Recovers the queried phone JID from a `<contact>` echo. Each `<contact>` in a
|
|
74
|
+
* usync response echoes the `+<number>` we sent (base64 text content), so it maps
|
|
75
|
+
* a response node back to the exact input - even when the server corrected the
|
|
76
|
+
* `<user jid>` or rejected it as `jid='undefined'`. Returns `null` when there is
|
|
77
|
+
* no decodable phone echo.
|
|
78
|
+
*/
|
|
79
|
+
private recoverContactJid;
|
|
59
80
|
private parseLidSyncContactExists;
|
|
60
81
|
private parseUserDeviceJids;
|
|
61
82
|
private normalizeUsers;
|
|
62
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Canonicalizes a user jid, returning `null` instead of throwing when the input
|
|
85
|
+
* is not a valid jid (for example the literal `'undefined'` the server returns
|
|
86
|
+
* for an invalid contact). Lets a single malformed response node be skipped
|
|
87
|
+
* without discarding the rest of the batch.
|
|
88
|
+
*/
|
|
89
|
+
private tryNormalizeUserJid;
|
|
63
90
|
}
|
|
64
91
|
export {};
|
|
@@ -115,22 +115,21 @@ class SignalDeviceSyncApi {
|
|
|
115
115
|
const parsedByPhoneJid = new Map();
|
|
116
116
|
for (let index = 0; index < parsed.length; index += 1) {
|
|
117
117
|
const entry = parsed[index];
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
phoneJid,
|
|
118
|
+
parsedByPhoneJid.set(entry.jid, {
|
|
119
|
+
phoneJid: entry.phoneJid ?? entry.jid,
|
|
121
120
|
lidJid: entry.lidJid,
|
|
122
|
-
exists: entry.exists
|
|
121
|
+
exists: entry.exists,
|
|
122
|
+
invalid: entry.invalid
|
|
123
123
|
});
|
|
124
124
|
}
|
|
125
125
|
const result = new Array(normalizedPhoneJids.length);
|
|
126
126
|
let found = 0;
|
|
127
127
|
for (let index = 0; index < normalizedPhoneJids.length; index += 1) {
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
exists: false
|
|
133
|
-
};
|
|
128
|
+
const queriedJid = normalizedPhoneJids[index];
|
|
129
|
+
const hit = parsedByPhoneJid.get(queriedJid);
|
|
130
|
+
const resolved = hit
|
|
131
|
+
? { queriedJid, ...hit }
|
|
132
|
+
: { queriedJid, phoneJid: queriedJid, lidJid: null, exists: false, invalid: false };
|
|
134
133
|
if (resolved.exists) {
|
|
135
134
|
found += 1;
|
|
136
135
|
}
|
|
@@ -154,24 +153,23 @@ class SignalDeviceSyncApi {
|
|
|
154
153
|
if (!this.deviceListStore || results.length === 0)
|
|
155
154
|
return;
|
|
156
155
|
const nowMs = Date.now();
|
|
157
|
-
const
|
|
158
|
-
const
|
|
156
|
+
const lidByQueriedJid = new Map();
|
|
157
|
+
const queriedJids = [];
|
|
159
158
|
for (const entry of results) {
|
|
160
|
-
if (entry.lidJid
|
|
161
|
-
|
|
162
|
-
|
|
159
|
+
if (entry.lidJid) {
|
|
160
|
+
lidByQueriedJid.set(entry.queriedJid, entry.lidJid);
|
|
161
|
+
queriedJids.push(entry.queriedJid);
|
|
163
162
|
}
|
|
164
163
|
}
|
|
165
|
-
if (
|
|
164
|
+
if (queriedJids.length === 0)
|
|
166
165
|
return;
|
|
167
|
-
const existing = await this.deviceListStore.getUserDevicesBatch(
|
|
166
|
+
const existing = await this.deviceListStore.getUserDevicesBatch(queriedJids, nowMs);
|
|
168
167
|
const updates = [];
|
|
169
|
-
for (let index = 0; index <
|
|
168
|
+
for (let index = 0; index < queriedJids.length; index += 1) {
|
|
170
169
|
const snapshot = existing[index];
|
|
171
170
|
if (!snapshot)
|
|
172
171
|
continue;
|
|
173
|
-
const
|
|
174
|
-
const lidJid = lidByPhoneJid.get(phoneJid);
|
|
172
|
+
const lidJid = lidByQueriedJid.get(queriedJids[index]);
|
|
175
173
|
if (!lidJid || snapshot.altUserJid === lidJid)
|
|
176
174
|
continue;
|
|
177
175
|
updates.push({
|
|
@@ -274,7 +272,13 @@ class SignalDeviceSyncApi {
|
|
|
274
272
|
if (!userJid) {
|
|
275
273
|
continue;
|
|
276
274
|
}
|
|
277
|
-
const normalizedUserJid = this.
|
|
275
|
+
const normalizedUserJid = this.tryNormalizeUserJid(userJid);
|
|
276
|
+
if (normalizedUserJid === null) {
|
|
277
|
+
this.logger.debug('signal device sync skipping user node with invalid jid', {
|
|
278
|
+
jid: userJid
|
|
279
|
+
});
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
278
282
|
if (!requestedSet.has(normalizedUserJid)) {
|
|
279
283
|
continue;
|
|
280
284
|
}
|
|
@@ -295,55 +299,107 @@ class SignalDeviceSyncApi {
|
|
|
295
299
|
throw new Error('signal lid sync response missing usync envelope');
|
|
296
300
|
}
|
|
297
301
|
const requestedSet = new Set(requestedUsers);
|
|
298
|
-
const parsed =
|
|
299
|
-
|
|
302
|
+
const parsed = [];
|
|
303
|
+
const lidUserErrors = [];
|
|
300
304
|
for (let index = 0; index < userNodes.length; index += 1) {
|
|
301
305
|
const userNode = userNodes[index];
|
|
302
|
-
const
|
|
303
|
-
if (!
|
|
306
|
+
const rawUserJid = userNode.attrs.jid;
|
|
307
|
+
if (!rawUserJid) {
|
|
304
308
|
continue;
|
|
305
309
|
}
|
|
306
|
-
const
|
|
307
|
-
const
|
|
308
|
-
? this.
|
|
310
|
+
const resolvedJid = this.tryNormalizeUserJid(rawUserJid);
|
|
311
|
+
const pnJid = userNode.attrs.pn_jid
|
|
312
|
+
? this.tryNormalizeUserJid(userNode.attrs.pn_jid)
|
|
313
|
+
: null;
|
|
314
|
+
const phoneJid = pnJid ?? resolvedJid;
|
|
315
|
+
const lidNode = (0, helpers_1.findNodeChild)(userNode, constants_1.WA_NODE_TAGS.LID);
|
|
316
|
+
const lidErrorNode = lidNode ? (0, helpers_1.findNodeChild)(lidNode, constants_1.WA_NODE_TAGS.ERROR) : null;
|
|
317
|
+
if (lidErrorNode) {
|
|
318
|
+
lidUserErrors.push({
|
|
319
|
+
jid: resolvedJid ?? rawUserJid,
|
|
320
|
+
code: lidErrorNode.attrs.code,
|
|
321
|
+
text: lidErrorNode.attrs.text
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
const lidJid = !lidErrorNode && lidNode?.attrs.val
|
|
325
|
+
? this.tryNormalizeUserJid(lidNode.attrs.val)
|
|
309
326
|
: null;
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
327
|
+
const contactNodes = (0, helpers_1.getNodeChildrenByTag)(userNode, constants_1.WA_NODE_TAGS.CONTACT);
|
|
328
|
+
let matched = false;
|
|
329
|
+
for (let c = 0; c < contactNodes.length; c += 1) {
|
|
330
|
+
const contactNode = contactNodes[c];
|
|
331
|
+
const inputJid = this.recoverContactJid(contactNode);
|
|
332
|
+
if (inputJid === null || !requestedSet.has(inputJid)) {
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
matched = true;
|
|
336
|
+
const invalid = resolvedJid === null || contactNode.attrs.type === 'invalid';
|
|
337
|
+
parsed.push({
|
|
338
|
+
jid: inputJid,
|
|
339
|
+
phoneJid: phoneJid ?? inputJid,
|
|
340
|
+
lidJid,
|
|
341
|
+
exists: !invalid &&
|
|
342
|
+
this.parseLidSyncContactExists(contactNode, inputJid, lidJid !== null),
|
|
343
|
+
invalid
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
if (matched) {
|
|
313
347
|
continue;
|
|
314
348
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
parsedCount += 1;
|
|
349
|
+
if (resolvedJid === null) {
|
|
350
|
+
this.logger.debug('signal lid sync skipping user node with invalid jid', {
|
|
351
|
+
jid: rawUserJid
|
|
352
|
+
});
|
|
320
353
|
continue;
|
|
321
354
|
}
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
355
|
+
const requestedKey = requestedSet.has(resolvedJid)
|
|
356
|
+
? resolvedJid
|
|
357
|
+
: pnJid !== null && requestedSet.has(pnJid)
|
|
358
|
+
? pnJid
|
|
359
|
+
: null;
|
|
360
|
+
if (requestedKey === null) {
|
|
361
|
+
this.logger.debug('signal lid sync unmatched user (no contact echo)', {
|
|
362
|
+
jid: resolvedJid,
|
|
363
|
+
pnJid
|
|
328
364
|
});
|
|
329
|
-
parsed[parsedCount] = this.buildLidSyncResult(normalizedUserJid, normalizedPhoneJid, contactNode, null);
|
|
330
|
-
parsedCount += 1;
|
|
331
365
|
continue;
|
|
332
366
|
}
|
|
333
|
-
const
|
|
334
|
-
parsed
|
|
335
|
-
|
|
367
|
+
const contactNode = (0, helpers_1.findNodeChild)(userNode, constants_1.WA_NODE_TAGS.CONTACT);
|
|
368
|
+
parsed.push({
|
|
369
|
+
jid: requestedKey,
|
|
370
|
+
phoneJid: phoneJid ?? requestedKey,
|
|
371
|
+
lidJid,
|
|
372
|
+
exists: this.parseLidSyncContactExists(contactNode, requestedKey, lidJid !== null),
|
|
373
|
+
invalid: false
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
if (lidUserErrors.length > 0) {
|
|
377
|
+
this.logger.warn('signal lid sync user errors', {
|
|
378
|
+
droppedCount: lidUserErrors.length,
|
|
379
|
+
totalExpected: requestedUsers.length,
|
|
380
|
+
sample: lidUserErrors.slice(0, 3)
|
|
381
|
+
});
|
|
336
382
|
}
|
|
337
|
-
parsed.length = parsedCount;
|
|
338
383
|
return parsed;
|
|
339
384
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
385
|
+
/**
|
|
386
|
+
* Recovers the queried phone JID from a `<contact>` echo. Each `<contact>` in a
|
|
387
|
+
* usync response echoes the `+<number>` we sent (base64 text content), so it maps
|
|
388
|
+
* a response node back to the exact input - even when the server corrected the
|
|
389
|
+
* `<user jid>` or rejected it as `jid='undefined'`. Returns `null` when there is
|
|
390
|
+
* no decodable phone echo.
|
|
391
|
+
*/
|
|
392
|
+
recoverContactJid(contactNode) {
|
|
393
|
+
const echoed = (0, helpers_1.getNodeTextContent)(contactNode);
|
|
394
|
+
if (!echoed) {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
try {
|
|
398
|
+
return (0, jid_1.parsePhoneJid)(echoed);
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
347
403
|
}
|
|
348
404
|
parseLidSyncContactExists(contactNode, userJid, defaultExists) {
|
|
349
405
|
if (!contactNode) {
|
|
@@ -404,7 +460,10 @@ class SignalDeviceSyncApi {
|
|
|
404
460
|
let normalizedCount = 0;
|
|
405
461
|
const dedup = new Set();
|
|
406
462
|
for (let index = 0; index < userJids.length; index += 1) {
|
|
407
|
-
const normalizedJid =
|
|
463
|
+
const normalizedJid = (0, jid_1.toUserJid)(userJids[index], {
|
|
464
|
+
canonicalizeSignalServer: true,
|
|
465
|
+
hostDomain: this.hostDomain
|
|
466
|
+
});
|
|
408
467
|
if (dedup.has(normalizedJid)) {
|
|
409
468
|
continue;
|
|
410
469
|
}
|
|
@@ -415,11 +474,22 @@ class SignalDeviceSyncApi {
|
|
|
415
474
|
normalized.length = normalizedCount;
|
|
416
475
|
return normalized;
|
|
417
476
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
477
|
+
/**
|
|
478
|
+
* Canonicalizes a user jid, returning `null` instead of throwing when the input
|
|
479
|
+
* is not a valid jid (for example the literal `'undefined'` the server returns
|
|
480
|
+
* for an invalid contact). Lets a single malformed response node be skipped
|
|
481
|
+
* without discarding the rest of the batch.
|
|
482
|
+
*/
|
|
483
|
+
tryNormalizeUserJid(jid) {
|
|
484
|
+
try {
|
|
485
|
+
return (0, jid_1.toUserJid)(jid, {
|
|
486
|
+
canonicalizeSignalServer: true,
|
|
487
|
+
hostDomain: this.hostDomain
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
catch {
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
423
493
|
}
|
|
424
494
|
}
|
|
425
495
|
exports.SignalDeviceSyncApi = SignalDeviceSyncApi;
|