zapo-js 1.6.0 → 1.6.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/WaClientFactory.js +2 -1
- package/dist/client/coordinators/WaMessageDispatchCoordinator.d.ts +2 -2
- package/dist/client/coordinators/WaMessageDispatchCoordinator.js +6 -36
- package/dist/client/coordinators/WaPrivacyCoordinator.d.ts +18 -5
- package/dist/client/coordinators/WaPrivacyCoordinator.js +29 -4
- package/dist/esm/client/WaClientFactory.js +2 -1
- package/dist/esm/client/coordinators/WaMessageDispatchCoordinator.js +6 -36
- package/dist/esm/client/coordinators/WaPrivacyCoordinator.js +30 -5
- package/dist/esm/signal/api/SignalDeviceSyncApi.js +62 -1
- package/dist/esm/transport/WaComms.js +7 -0
- package/dist/esm/transport/keepalive/WaKeepAlive.js +24 -7
- package/dist/esm/transport/node/builders/privacy.js +32 -2
- package/dist/signal/api/SignalDeviceSyncApi.d.ts +27 -0
- package/dist/signal/api/SignalDeviceSyncApi.js +61 -0
- package/dist/transport/WaComms.d.ts +3 -0
- package/dist/transport/WaComms.js +7 -0
- package/dist/transport/keepalive/WaKeepAlive.d.ts +8 -2
- package/dist/transport/keepalive/WaKeepAlive.js +24 -7
- package/dist/transport/node/builders/privacy.d.ts +26 -1
- package/dist/transport/node/builders/privacy.js +34 -3
- package/package.json +1 -1
|
@@ -375,7 +375,8 @@ function buildWaClientDependencies(input) {
|
|
|
375
375
|
logger
|
|
376
376
|
});
|
|
377
377
|
const privacyCoordinator = (0, WaPrivacyCoordinator_1.createPrivacyCoordinator)({
|
|
378
|
-
queryWithContext: runtime.queryWithContext
|
|
378
|
+
queryWithContext: runtime.queryWithContext,
|
|
379
|
+
resolveUserJidPair: (userJid) => signalDeviceSync.resolveUserJidPair(userJid)
|
|
379
380
|
});
|
|
380
381
|
const businessCoordinator = (0, WaBusinessCoordinator_1.createBusinessCoordinator)({
|
|
381
382
|
queryWithContext: runtime.queryWithContext,
|
|
@@ -80,8 +80,8 @@ export declare class WaMessageDispatchCoordinator {
|
|
|
80
80
|
sendMessage(to: string, content: WaSendMessageContent, options?: WaSendMessageOptions): Promise<WaMessagePublishResult>;
|
|
81
81
|
/**
|
|
82
82
|
* For a 1:1 recipient passed in PN form, returns the LID-addressed user JID
|
|
83
|
-
* (
|
|
84
|
-
*
|
|
83
|
+
* (via {@link SignalDeviceSyncApi.resolveUserJidPair}). Switching to LID
|
|
84
|
+
* before fanout ensures the envelope, eligible-requester list, and
|
|
85
85
|
* retry-receipt addressing all agree, which keeps the retry tracker from
|
|
86
86
|
* rejecting receipts that arrive in LID form. Returns the original PN if
|
|
87
87
|
* no LID is known/resolvable. Inputs already in LID form pass through.
|
|
@@ -300,8 +300,8 @@ class WaMessageDispatchCoordinator {
|
|
|
300
300
|
}
|
|
301
301
|
/**
|
|
302
302
|
* For a 1:1 recipient passed in PN form, returns the LID-addressed user JID
|
|
303
|
-
* (
|
|
304
|
-
*
|
|
303
|
+
* (via {@link SignalDeviceSyncApi.resolveUserJidPair}). Switching to LID
|
|
304
|
+
* before fanout ensures the envelope, eligible-requester list, and
|
|
305
305
|
* retry-receipt addressing all agree, which keeps the retry tracker from
|
|
306
306
|
* rejecting receipts that arrive in LID form. Returns the original PN if
|
|
307
307
|
* no LID is known/resolvable. Inputs already in LID form pass through.
|
|
@@ -309,26 +309,8 @@ class WaMessageDispatchCoordinator {
|
|
|
309
309
|
async resolveDirectRecipientLid(pnUserJid) {
|
|
310
310
|
if ((0, jid_1.isLidJid)(pnUserJid))
|
|
311
311
|
return pnUserJid;
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
if ((0, jid_1.isLidJid)(cached.userJid))
|
|
315
|
-
return cached.userJid;
|
|
316
|
-
if (cached.altUserJid && (0, jid_1.isLidJid)(cached.altUserJid))
|
|
317
|
-
return cached.altUserJid;
|
|
318
|
-
}
|
|
319
|
-
try {
|
|
320
|
-
const results = await this.deps.signalDeviceSync.queryLidsByPhoneJids([pnUserJid]);
|
|
321
|
-
const match = results.find((entry) => entry.queriedJid === pnUserJid);
|
|
322
|
-
if (match?.lidJid)
|
|
323
|
-
return match.lidJid;
|
|
324
|
-
}
|
|
325
|
-
catch (error) {
|
|
326
|
-
this.deps.logger.debug('lid resolution failed for direct recipient', {
|
|
327
|
-
pnUserJid,
|
|
328
|
-
message: (0, primitives_2.toError)(error).message
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
return pnUserJid;
|
|
312
|
+
const pair = await this.deps.signalDeviceSync.resolveUserJidPair(pnUserJid);
|
|
313
|
+
return pair.lidJid ?? pnUserJid;
|
|
332
314
|
}
|
|
333
315
|
/**
|
|
334
316
|
* Resolves the `peer_recipient_pn` cross-reference for a 1:1 send, or
|
|
@@ -344,20 +326,8 @@ class WaMessageDispatchCoordinator {
|
|
|
344
326
|
return undefined;
|
|
345
327
|
if ((0, jid_1.isUserJid)(recipientUserJid))
|
|
346
328
|
return recipientUserJid;
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
if (snapshot?.userJid && (0, jid_1.isUserJid)(snapshot.userJid))
|
|
350
|
-
return snapshot.userJid;
|
|
351
|
-
if (snapshot?.altUserJid && (0, jid_1.isUserJid)(snapshot.altUserJid))
|
|
352
|
-
return snapshot.altUserJid;
|
|
353
|
-
}
|
|
354
|
-
catch (error) {
|
|
355
|
-
this.deps.logger.trace('peer_recipient_pn store lookup failed', {
|
|
356
|
-
lid: directRecipientJid,
|
|
357
|
-
message: (0, primitives_2.toError)(error).message
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
|
-
return undefined;
|
|
329
|
+
const pair = await this.deps.signalDeviceSync.resolveUserJidPair(directRecipientJid);
|
|
330
|
+
return pair.pnJid ?? undefined;
|
|
361
331
|
}
|
|
362
332
|
async syncSignalSession(jid, reasonIdentity = false) {
|
|
363
333
|
const address = (0, jid_1.parseSignalAddressFromJid)(jid);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type WaPrivacyDisallowedListSettingName, type WaPrivacySettingName, type WaPrivacySettingValueMap } from '../../protocol/privacy';
|
|
2
|
+
import type { SignalUserJidPair } from '../../signal/api/SignalDeviceSyncApi';
|
|
2
3
|
import type { BinaryNode } from '../../transport/types';
|
|
3
4
|
export type WaPrivacySettings = {
|
|
4
5
|
readonly [K in WaPrivacySettingName]?: WaPrivacySettingValueMap[K];
|
|
@@ -36,17 +37,29 @@ export interface WaPrivacyCoordinator {
|
|
|
36
37
|
/** Returns the current account-wide blocklist. */
|
|
37
38
|
readonly getBlocklist: () => Promise<WaBlocklistResult>;
|
|
38
39
|
/**
|
|
39
|
-
* Blocks
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
40
|
+
* Blocks a user (account-wide blocklist). Accepts a phone-number jid, a
|
|
41
|
+
* LID jid, or a bare phone number (digits only). After this, the peer can
|
|
42
|
+
* no longer
|
|
43
|
+
* message/call you and cannot see your last seen/online/photo/status. The
|
|
44
|
+
* block is symmetric only from the peer's read perspective - they don't
|
|
45
|
+
* get an explicit "you were blocked" notification.
|
|
46
|
+
*
|
|
47
|
+
* The server keys blocklist entries by LID for migrated accounts, so a
|
|
48
|
+
* phone-number input is resolved to its LID first (device-list cache,
|
|
49
|
+
* then a usync query). Non-migrated accounts fall back to the plain
|
|
50
|
+
* phone-jid form.
|
|
43
51
|
*/
|
|
44
52
|
readonly blockUser: (jid: string) => Promise<void>;
|
|
45
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* Removes a user from the blocklist. Accepts the same inputs as
|
|
55
|
+
* {@link blockUser} and performs the same LID resolution - unblocking a
|
|
56
|
+
* migrated entry by phone jid is rejected by the server.
|
|
57
|
+
*/
|
|
46
58
|
readonly unblockUser: (jid: string) => Promise<void>;
|
|
47
59
|
}
|
|
48
60
|
interface WaPrivacyCoordinatorOptions {
|
|
49
61
|
readonly queryWithContext: (context: string, node: BinaryNode, timeoutMs?: number, contextData?: Readonly<Record<string, unknown>>) => Promise<BinaryNode>;
|
|
62
|
+
readonly resolveUserJidPair: (userJid: string) => Promise<SignalUserJidPair>;
|
|
50
63
|
}
|
|
51
64
|
/** Builds a {@link WaPrivacyCoordinator} backed by the given IQ query function. */
|
|
52
65
|
export declare function createPrivacyCoordinator(options: WaPrivacyCoordinatorOptions): WaPrivacyCoordinator;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createPrivacyCoordinator = createPrivacyCoordinator;
|
|
4
|
+
const jid_1 = require("../../protocol/jid");
|
|
4
5
|
const nodes_1 = require("../../protocol/nodes");
|
|
5
6
|
const privacy_1 = require("../../protocol/privacy");
|
|
6
7
|
const privacy_2 = require("../../transport/node/builders/privacy");
|
|
@@ -88,6 +89,23 @@ function parseBlocklist(result) {
|
|
|
88
89
|
jids.length = jidsCount;
|
|
89
90
|
return { jids, dhash };
|
|
90
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Resolves a blocklist input into both addressing forms via
|
|
94
|
+
* `resolveUserJidPair` (device-list cache first, usync fallback for phone
|
|
95
|
+
* jids). Resolution failures degrade to the single known form instead of
|
|
96
|
+
* throwing - the server then decides whether that form is acceptable.
|
|
97
|
+
*/
|
|
98
|
+
async function resolveBlocklistTarget(options, jid) {
|
|
99
|
+
const normalized = (0, jid_1.normalizeRecipientJid)(jid);
|
|
100
|
+
if (!(0, jid_1.isLidJid)(normalized) && !(0, jid_1.isUserJid)(normalized)) {
|
|
101
|
+
throw new Error(`blocklist target must be a user jid: ${jid}`);
|
|
102
|
+
}
|
|
103
|
+
const pair = await options.resolveUserJidPair(normalized);
|
|
104
|
+
if (pair.lidJid !== null) {
|
|
105
|
+
return { lidJid: pair.lidJid, pnJid: pair.pnJid };
|
|
106
|
+
}
|
|
107
|
+
return { lidJid: null, pnJid: pair.pnJid ?? normalized };
|
|
108
|
+
}
|
|
91
109
|
/** Builds a {@link WaPrivacyCoordinator} backed by the given IQ query function. */
|
|
92
110
|
function createPrivacyCoordinator(options) {
|
|
93
111
|
const { queryWithContext } = options;
|
|
@@ -123,13 +141,20 @@ function createPrivacyCoordinator(options) {
|
|
|
123
141
|
return parseBlocklist(result);
|
|
124
142
|
},
|
|
125
143
|
blockUser: async (jid) => {
|
|
126
|
-
const
|
|
127
|
-
const
|
|
144
|
+
const target = await resolveBlocklistTarget(options, jid);
|
|
145
|
+
const node = (0, privacy_2.buildBlocklistBlockIq)(target);
|
|
146
|
+
const result = await queryWithContext('privacy.blockUser', node, undefined, {
|
|
147
|
+
jid: target.lidJid ?? target.pnJid
|
|
148
|
+
});
|
|
128
149
|
(0, query_1.assertIqResult)(result, 'privacy.blockUser');
|
|
129
150
|
},
|
|
130
151
|
unblockUser: async (jid) => {
|
|
131
|
-
const
|
|
132
|
-
const
|
|
152
|
+
const target = await resolveBlocklistTarget(options, jid);
|
|
153
|
+
const unblockJid = target.lidJid ?? target.pnJid;
|
|
154
|
+
const node = (0, privacy_2.buildBlocklistUnblockIq)(unblockJid);
|
|
155
|
+
const result = await queryWithContext('privacy.unblockUser', node, undefined, {
|
|
156
|
+
jid: unblockJid
|
|
157
|
+
});
|
|
133
158
|
(0, query_1.assertIqResult)(result, 'privacy.unblockUser');
|
|
134
159
|
}
|
|
135
160
|
};
|
|
@@ -371,7 +371,8 @@ export function buildWaClientDependencies(input) {
|
|
|
371
371
|
logger
|
|
372
372
|
});
|
|
373
373
|
const privacyCoordinator = createPrivacyCoordinator({
|
|
374
|
-
queryWithContext: runtime.queryWithContext
|
|
374
|
+
queryWithContext: runtime.queryWithContext,
|
|
375
|
+
resolveUserJidPair: (userJid) => signalDeviceSync.resolveUserJidPair(userJid)
|
|
375
376
|
});
|
|
376
377
|
const businessCoordinator = createBusinessCoordinator({
|
|
377
378
|
queryWithContext: runtime.queryWithContext,
|
|
@@ -297,8 +297,8 @@ export class WaMessageDispatchCoordinator {
|
|
|
297
297
|
}
|
|
298
298
|
/**
|
|
299
299
|
* For a 1:1 recipient passed in PN form, returns the LID-addressed user JID
|
|
300
|
-
* (
|
|
301
|
-
*
|
|
300
|
+
* (via {@link SignalDeviceSyncApi.resolveUserJidPair}). Switching to LID
|
|
301
|
+
* before fanout ensures the envelope, eligible-requester list, and
|
|
302
302
|
* retry-receipt addressing all agree, which keeps the retry tracker from
|
|
303
303
|
* rejecting receipts that arrive in LID form. Returns the original PN if
|
|
304
304
|
* no LID is known/resolvable. Inputs already in LID form pass through.
|
|
@@ -306,26 +306,8 @@ export class WaMessageDispatchCoordinator {
|
|
|
306
306
|
async resolveDirectRecipientLid(pnUserJid) {
|
|
307
307
|
if (isLidJid(pnUserJid))
|
|
308
308
|
return pnUserJid;
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
if (isLidJid(cached.userJid))
|
|
312
|
-
return cached.userJid;
|
|
313
|
-
if (cached.altUserJid && isLidJid(cached.altUserJid))
|
|
314
|
-
return cached.altUserJid;
|
|
315
|
-
}
|
|
316
|
-
try {
|
|
317
|
-
const results = await this.deps.signalDeviceSync.queryLidsByPhoneJids([pnUserJid]);
|
|
318
|
-
const match = results.find((entry) => entry.queriedJid === pnUserJid);
|
|
319
|
-
if (match?.lidJid)
|
|
320
|
-
return match.lidJid;
|
|
321
|
-
}
|
|
322
|
-
catch (error) {
|
|
323
|
-
this.deps.logger.debug('lid resolution failed for direct recipient', {
|
|
324
|
-
pnUserJid,
|
|
325
|
-
message: toError(error).message
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
return pnUserJid;
|
|
309
|
+
const pair = await this.deps.signalDeviceSync.resolveUserJidPair(pnUserJid);
|
|
310
|
+
return pair.lidJid ?? pnUserJid;
|
|
329
311
|
}
|
|
330
312
|
/**
|
|
331
313
|
* Resolves the `peer_recipient_pn` cross-reference for a 1:1 send, or
|
|
@@ -341,20 +323,8 @@ export class WaMessageDispatchCoordinator {
|
|
|
341
323
|
return undefined;
|
|
342
324
|
if (isUserJid(recipientUserJid))
|
|
343
325
|
return recipientUserJid;
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if (snapshot?.userJid && isUserJid(snapshot.userJid))
|
|
347
|
-
return snapshot.userJid;
|
|
348
|
-
if (snapshot?.altUserJid && isUserJid(snapshot.altUserJid))
|
|
349
|
-
return snapshot.altUserJid;
|
|
350
|
-
}
|
|
351
|
-
catch (error) {
|
|
352
|
-
this.deps.logger.trace('peer_recipient_pn store lookup failed', {
|
|
353
|
-
lid: directRecipientJid,
|
|
354
|
-
message: toError(error).message
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
return undefined;
|
|
326
|
+
const pair = await this.deps.signalDeviceSync.resolveUserJidPair(directRecipientJid);
|
|
327
|
+
return pair.pnJid ?? undefined;
|
|
358
328
|
}
|
|
359
329
|
async syncSignalSession(jid, reasonIdentity = false) {
|
|
360
330
|
const address = parseSignalAddressFromJid(jid);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { isLidJid, isUserJid, normalizeRecipientJid } from '../../protocol/jid.js';
|
|
1
2
|
import { WA_NODE_TAGS } from '../../protocol/nodes.js';
|
|
2
3
|
import { WA_PRIVACY_CATEGORY_TO_SETTING, WA_PRIVACY_SETTING_TO_CATEGORY, WA_PRIVACY_TAGS, WA_PRIVACY_VALUES } from '../../protocol/privacy.js';
|
|
3
|
-
import {
|
|
4
|
+
import { buildBlocklistBlockIq, buildBlocklistUnblockIq, buildGetBlocklistIq, buildGetPrivacyDisallowedListIq, buildGetPrivacySettingsIq, buildSetPrivacyCategoryIq } from '../../transport/node/builders/privacy.js';
|
|
4
5
|
import { findNodeChild, getNodeChildren, getNodeChildrenByTag } from '../../transport/node/helpers.js';
|
|
5
6
|
import { assertIqResult } from '../../transport/node/query.js';
|
|
6
7
|
const IGNORED_SERVER_CATEGORIES = new Set([
|
|
@@ -85,6 +86,23 @@ function parseBlocklist(result) {
|
|
|
85
86
|
jids.length = jidsCount;
|
|
86
87
|
return { jids, dhash };
|
|
87
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Resolves a blocklist input into both addressing forms via
|
|
91
|
+
* `resolveUserJidPair` (device-list cache first, usync fallback for phone
|
|
92
|
+
* jids). Resolution failures degrade to the single known form instead of
|
|
93
|
+
* throwing - the server then decides whether that form is acceptable.
|
|
94
|
+
*/
|
|
95
|
+
async function resolveBlocklistTarget(options, jid) {
|
|
96
|
+
const normalized = normalizeRecipientJid(jid);
|
|
97
|
+
if (!isLidJid(normalized) && !isUserJid(normalized)) {
|
|
98
|
+
throw new Error(`blocklist target must be a user jid: ${jid}`);
|
|
99
|
+
}
|
|
100
|
+
const pair = await options.resolveUserJidPair(normalized);
|
|
101
|
+
if (pair.lidJid !== null) {
|
|
102
|
+
return { lidJid: pair.lidJid, pnJid: pair.pnJid };
|
|
103
|
+
}
|
|
104
|
+
return { lidJid: null, pnJid: pair.pnJid ?? normalized };
|
|
105
|
+
}
|
|
88
106
|
/** Builds a {@link WaPrivacyCoordinator} backed by the given IQ query function. */
|
|
89
107
|
export function createPrivacyCoordinator(options) {
|
|
90
108
|
const { queryWithContext } = options;
|
|
@@ -120,13 +138,20 @@ export function createPrivacyCoordinator(options) {
|
|
|
120
138
|
return parseBlocklist(result);
|
|
121
139
|
},
|
|
122
140
|
blockUser: async (jid) => {
|
|
123
|
-
const
|
|
124
|
-
const
|
|
141
|
+
const target = await resolveBlocklistTarget(options, jid);
|
|
142
|
+
const node = buildBlocklistBlockIq(target);
|
|
143
|
+
const result = await queryWithContext('privacy.blockUser', node, undefined, {
|
|
144
|
+
jid: target.lidJid ?? target.pnJid
|
|
145
|
+
});
|
|
125
146
|
assertIqResult(result, 'privacy.blockUser');
|
|
126
147
|
},
|
|
127
148
|
unblockUser: async (jid) => {
|
|
128
|
-
const
|
|
129
|
-
const
|
|
149
|
+
const target = await resolveBlocklistTarget(options, jid);
|
|
150
|
+
const unblockJid = target.lidJid ?? target.pnJid;
|
|
151
|
+
const node = buildBlocklistUnblockIq(unblockJid);
|
|
152
|
+
const result = await queryWithContext('privacy.unblockUser', node, undefined, {
|
|
153
|
+
jid: unblockJid
|
|
154
|
+
});
|
|
130
155
|
assertIqResult(result, 'privacy.unblockUser');
|
|
131
156
|
}
|
|
132
157
|
};
|
|
@@ -1,10 +1,11 @@
|
|
|
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, parsePhoneJid, splitJid, toUserJid } from '../../protocol/jid.js';
|
|
3
|
+
import { buildDeviceJid, isHostedDeviceId, isLidJid, isUserJid, parsePhoneJid, splitJid, toUserJid } from '../../protocol/jid.js';
|
|
4
4
|
import { buildUsyncIq, iterateUsyncUsers, parseUsyncResultEnvelope } from '../../transport/node/builders/usync.js';
|
|
5
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
|
+
import { toError } from '../../util/primitives.js';
|
|
8
9
|
/**
|
|
9
10
|
* Resolves the device list and LID mapping for a set of users via the `usync`
|
|
10
11
|
* protocol. Concurrent calls for the same JIDs are deduplicated.
|
|
@@ -139,6 +140,66 @@ export class SignalDeviceSyncApi {
|
|
|
139
140
|
await this.propagateAltUserJids(result);
|
|
140
141
|
return result;
|
|
141
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Resolves both addressing forms for a 1:1 user jid (PN or LID input),
|
|
145
|
+
* cache-first via the device-list store (`userJid`/`altUserJid`) with a
|
|
146
|
+
* one-shot {@link queryLidsByPhoneJids} fallback for PN inputs. LID
|
|
147
|
+
* inputs have no reverse lookup - their PN side stays `null` on a cache
|
|
148
|
+
* miss. Store/usync failures are logged at debug and degrade to the
|
|
149
|
+
* forms already known. Inputs that are neither PN nor LID user jids
|
|
150
|
+
* resolve to `{ lidJid: null, pnJid: null }`.
|
|
151
|
+
*/
|
|
152
|
+
async resolveUserJidPair(userJid, timeoutMs = this.defaultTimeoutMs) {
|
|
153
|
+
if (isLidJid(userJid)) {
|
|
154
|
+
return { lidJid: userJid, pnJid: await this.findCachedAltForm(userJid, isUserJid) };
|
|
155
|
+
}
|
|
156
|
+
if (!isUserJid(userJid)) {
|
|
157
|
+
return { lidJid: null, pnJid: null };
|
|
158
|
+
}
|
|
159
|
+
const cachedLid = await this.findCachedAltForm(userJid, isLidJid);
|
|
160
|
+
if (cachedLid) {
|
|
161
|
+
return { lidJid: cachedLid, pnJid: userJid };
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const results = await this.queryLidsByPhoneJids([userJid], timeoutMs);
|
|
165
|
+
const match = results.find((entry) => entry.queriedJid === userJid);
|
|
166
|
+
if (match?.lidJid) {
|
|
167
|
+
return { lidJid: match.lidJid, pnJid: match.phoneJid };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
this.logger.debug('lid usync resolution failed for jid pair', {
|
|
172
|
+
jid: userJid,
|
|
173
|
+
message: toError(error).message
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return { lidJid: null, pnJid: userJid };
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Returns the device-list snapshot form of `userJid` that satisfies
|
|
180
|
+
* `matches` (checked against `userJid` then `altUserJid`), or `null` when
|
|
181
|
+
* the store is absent, misses, or fails.
|
|
182
|
+
*/
|
|
183
|
+
async findCachedAltForm(userJid, matches) {
|
|
184
|
+
if (!this.deviceListStore)
|
|
185
|
+
return null;
|
|
186
|
+
try {
|
|
187
|
+
const snapshot = await this.deviceListStore.findByAnyUserJid(userJid);
|
|
188
|
+
if (snapshot) {
|
|
189
|
+
if (matches(snapshot.userJid))
|
|
190
|
+
return snapshot.userJid;
|
|
191
|
+
if (snapshot.altUserJid && matches(snapshot.altUserJid))
|
|
192
|
+
return snapshot.altUserJid;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
this.logger.debug('device-list lookup failed for jid pair', {
|
|
197
|
+
jid: userJid,
|
|
198
|
+
message: toError(error).message
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
142
203
|
/**
|
|
143
204
|
* Enriches existing device-list snapshots with the resolved LID equivalents
|
|
144
205
|
* so {@link WaDeviceListStore.findByAnyUserJid} can later match a retry
|
|
@@ -78,6 +78,7 @@ export class WaComms {
|
|
|
78
78
|
this.usedResumeHandshake = false;
|
|
79
79
|
this.noiseSession = null;
|
|
80
80
|
this.lastServerStaticKey = null;
|
|
81
|
+
this.lastInboundAtMs = 0;
|
|
81
82
|
this.frameProcessingQueue = Promise.resolve();
|
|
82
83
|
this.frameHandlerQueue = new BoundedTaskQueue(WA_FRAME_HANDLER_QUEUE_MAX_SIZE, WA_FRAME_HANDLER_MAX_CONCURRENCY);
|
|
83
84
|
}
|
|
@@ -205,6 +206,10 @@ export class WaComms {
|
|
|
205
206
|
const wire = await this.noiseSession.encryptFrame(payload);
|
|
206
207
|
await this.socket.send(wire);
|
|
207
208
|
}
|
|
209
|
+
/** Timestamp (`Date.now()` ms) of the last raw payload received from the socket; `0` before any inbound. */
|
|
210
|
+
getLastInboundAtMs() {
|
|
211
|
+
return this.lastInboundAtMs;
|
|
212
|
+
}
|
|
208
213
|
getCommsState() {
|
|
209
214
|
return {
|
|
210
215
|
started: this.started,
|
|
@@ -285,6 +290,7 @@ export class WaComms {
|
|
|
285
290
|
});
|
|
286
291
|
}
|
|
287
292
|
onSocketMessage(payload) {
|
|
293
|
+
this.lastInboundAtMs = Date.now();
|
|
288
294
|
this.frameProcessingQueue = this.frameProcessingQueue.then(() => this.processSocketPayload(payload), () => this.processSocketPayload(payload));
|
|
289
295
|
}
|
|
290
296
|
async processSocketPayload(payload) {
|
|
@@ -522,6 +528,7 @@ export class WaComms {
|
|
|
522
528
|
this.resumeHandshakeFailures = 0;
|
|
523
529
|
}
|
|
524
530
|
this.noiseSession = null;
|
|
531
|
+
this.lastInboundAtMs = 0;
|
|
525
532
|
this.clearPendingFrames();
|
|
526
533
|
this.pendingFramesOverflowClosing = false;
|
|
527
534
|
if (options.clearHandlers) {
|
|
@@ -5,8 +5,13 @@ const KEEPALIVE_DEFAULT_MIN_JITTER_MS = 250;
|
|
|
5
5
|
const KEEPALIVE_MAX_JITTER_RATIO = 0.5;
|
|
6
6
|
/**
|
|
7
7
|
* Periodically pings the server with a ping IQ to detect dead sockets and
|
|
8
|
-
* measure clock skew.
|
|
9
|
-
*
|
|
8
|
+
* measure clock skew. A ping is skipped while another ping is in flight, or
|
|
9
|
+
* while other queries are pending and the socket shows recent inbound
|
|
10
|
+
* activity. A socket silent past the dead-socket timeout is pinged even with
|
|
11
|
+
* queries pending: pending queries on a silent socket are evidence of a dead
|
|
12
|
+
* connection, so steady outbound traffic must not starve detection. On ping
|
|
13
|
+
* failure it rejects pending queries and resumes the socket via the
|
|
14
|
+
* configured `getComms()`.
|
|
10
15
|
*/
|
|
11
16
|
export class WaKeepAlive {
|
|
12
17
|
constructor(options) {
|
|
@@ -68,14 +73,25 @@ export class WaKeepAlive {
|
|
|
68
73
|
this.schedule(generation);
|
|
69
74
|
return;
|
|
70
75
|
}
|
|
71
|
-
if (this.inFlight
|
|
72
|
-
this.logger.trace('keepalive skipped:
|
|
73
|
-
inFlight: this.inFlight,
|
|
74
|
-
pendingQueries: this.nodeOrchestrator.hasPending()
|
|
75
|
-
});
|
|
76
|
+
if (this.inFlight) {
|
|
77
|
+
this.logger.trace('keepalive skipped: ping already in flight');
|
|
76
78
|
this.schedule(generation);
|
|
77
79
|
return;
|
|
78
80
|
}
|
|
81
|
+
if (this.nodeOrchestrator.hasPending()) {
|
|
82
|
+
const inboundIdleMs = Date.now() - comms.getLastInboundAtMs();
|
|
83
|
+
if (inboundIdleMs < this.timeoutMs) {
|
|
84
|
+
this.logger.trace('keepalive skipped: pending queries with recent inbound', {
|
|
85
|
+
inboundIdleMs
|
|
86
|
+
});
|
|
87
|
+
this.schedule(generation);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
this.logger.debug('keepalive pinging despite pending queries: inbound is silent', {
|
|
91
|
+
inboundIdleMs,
|
|
92
|
+
timeoutMs: this.timeoutMs
|
|
93
|
+
});
|
|
94
|
+
}
|
|
79
95
|
this.inFlight = true;
|
|
80
96
|
const startedAt = Date.now();
|
|
81
97
|
try {
|
|
@@ -108,6 +124,7 @@ export class WaKeepAlive {
|
|
|
108
124
|
this.logger.warn('keepalive ping failed, reconnecting socket', {
|
|
109
125
|
message: toError(error).message
|
|
110
126
|
});
|
|
127
|
+
this.nodeOrchestrator.clearPending(new Error('socket resume requested by keepalive ping failure'));
|
|
111
128
|
try {
|
|
112
129
|
await comms.closeSocketAndResume();
|
|
113
130
|
}
|
|
@@ -38,11 +38,41 @@ export function buildGetPrivacyDisallowedListIq(category) {
|
|
|
38
38
|
export function buildGetBlocklistIq() {
|
|
39
39
|
return buildIqNode(WA_IQ_TYPES.GET, WA_DEFAULTS.HOST_DOMAIN, WA_XMLNS.BLOCKLIST);
|
|
40
40
|
}
|
|
41
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Builds the blocklist `set` IQ for a block action. LID-migrated targets are
|
|
43
|
+
* addressed by the LID jid plus an identifier attribute: `pn_jid` when the
|
|
44
|
+
* phone jid is known, else `unknown_identifier="true"`. Non-migrated targets
|
|
45
|
+
* are addressed by the phone jid alone. The server rejects a block that
|
|
46
|
+
* addresses a migrated account by phone jid or omits the identifier
|
|
47
|
+
* (`400: bad-request`).
|
|
48
|
+
*/
|
|
49
|
+
export function buildBlocklistBlockIq(target) {
|
|
50
|
+
let attrs;
|
|
51
|
+
if (target.lidJid !== null) {
|
|
52
|
+
attrs =
|
|
53
|
+
target.pnJid !== null
|
|
54
|
+
? { action: 'block', jid: target.lidJid, pn_jid: target.pnJid }
|
|
55
|
+
: { action: 'block', jid: target.lidJid, unknown_identifier: 'true' };
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
attrs = { action: 'block', jid: target.pnJid };
|
|
59
|
+
}
|
|
42
60
|
return buildIqNode(WA_IQ_TYPES.SET, WA_DEFAULTS.HOST_DOMAIN, WA_XMLNS.BLOCKLIST, [
|
|
43
61
|
{
|
|
44
62
|
tag: 'item',
|
|
45
|
-
attrs
|
|
63
|
+
attrs
|
|
64
|
+
}
|
|
65
|
+
]);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Builds the blocklist `set` IQ for an unblock action. The server keys
|
|
69
|
+
* migrated entries by LID, so `jid` must be the LID jid when one exists.
|
|
70
|
+
*/
|
|
71
|
+
export function buildBlocklistUnblockIq(jid) {
|
|
72
|
+
return buildIqNode(WA_IQ_TYPES.SET, WA_DEFAULTS.HOST_DOMAIN, WA_XMLNS.BLOCKLIST, [
|
|
73
|
+
{
|
|
74
|
+
tag: 'item',
|
|
75
|
+
attrs: { jid, action: 'unblock' }
|
|
46
76
|
}
|
|
47
77
|
]);
|
|
48
78
|
}
|
|
@@ -29,6 +29,17 @@ export interface SignalLidSyncResult {
|
|
|
29
29
|
*/
|
|
30
30
|
readonly invalid: boolean;
|
|
31
31
|
}
|
|
32
|
+
/** Both addressing forms of a 1:1 user, as far as they could be resolved. */
|
|
33
|
+
export interface SignalUserJidPair {
|
|
34
|
+
/** LID user jid, or `null` when no LID mapping is known. */
|
|
35
|
+
readonly lidJid: string | null;
|
|
36
|
+
/**
|
|
37
|
+
* Phone-number user jid, or `null` when unknown. When resolved through
|
|
38
|
+
* the usync fallback this is the server-canonical form, which may differ
|
|
39
|
+
* from the queried number (e.g. BR 9th digit added).
|
|
40
|
+
*/
|
|
41
|
+
readonly pnJid: string | null;
|
|
42
|
+
}
|
|
32
43
|
/**
|
|
33
44
|
* Resolves the device list and LID mapping for a set of users via the `usync`
|
|
34
45
|
* protocol. Concurrent calls for the same JIDs are deduplicated.
|
|
@@ -56,6 +67,22 @@ export declare class SignalDeviceSyncApi {
|
|
|
56
67
|
* one entry per input JID with `exists` indicating server-side presence.
|
|
57
68
|
*/
|
|
58
69
|
queryLidsByPhoneJids(phoneJids: readonly string[], timeoutMs?: number): Promise<readonly SignalLidSyncResult[]>;
|
|
70
|
+
/**
|
|
71
|
+
* Resolves both addressing forms for a 1:1 user jid (PN or LID input),
|
|
72
|
+
* cache-first via the device-list store (`userJid`/`altUserJid`) with a
|
|
73
|
+
* one-shot {@link queryLidsByPhoneJids} fallback for PN inputs. LID
|
|
74
|
+
* inputs have no reverse lookup - their PN side stays `null` on a cache
|
|
75
|
+
* miss. Store/usync failures are logged at debug and degrade to the
|
|
76
|
+
* forms already known. Inputs that are neither PN nor LID user jids
|
|
77
|
+
* resolve to `{ lidJid: null, pnJid: null }`.
|
|
78
|
+
*/
|
|
79
|
+
resolveUserJidPair(userJid: string, timeoutMs?: number): Promise<SignalUserJidPair>;
|
|
80
|
+
/**
|
|
81
|
+
* Returns the device-list snapshot form of `userJid` that satisfies
|
|
82
|
+
* `matches` (checked against `userJid` then `altUserJid`), or `null` when
|
|
83
|
+
* the store is absent, misses, or fails.
|
|
84
|
+
*/
|
|
85
|
+
private findCachedAltForm;
|
|
59
86
|
/**
|
|
60
87
|
* Enriches existing device-list snapshots with the resolved LID equivalents
|
|
61
88
|
* so {@link WaDeviceListStore.findByAnyUserJid} can later match a retry
|
|
@@ -8,6 +8,7 @@ const usync_1 = require("../../transport/node/builders/usync");
|
|
|
8
8
|
const helpers_1 = require("../../transport/node/helpers");
|
|
9
9
|
const query_1 = require("../../transport/node/query");
|
|
10
10
|
const usync_2 = require("../../transport/node/usync");
|
|
11
|
+
const primitives_1 = require("../../util/primitives");
|
|
11
12
|
/**
|
|
12
13
|
* Resolves the device list and LID mapping for a set of users via the `usync`
|
|
13
14
|
* protocol. Concurrent calls for the same JIDs are deduplicated.
|
|
@@ -142,6 +143,66 @@ class SignalDeviceSyncApi {
|
|
|
142
143
|
await this.propagateAltUserJids(result);
|
|
143
144
|
return result;
|
|
144
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Resolves both addressing forms for a 1:1 user jid (PN or LID input),
|
|
148
|
+
* cache-first via the device-list store (`userJid`/`altUserJid`) with a
|
|
149
|
+
* one-shot {@link queryLidsByPhoneJids} fallback for PN inputs. LID
|
|
150
|
+
* inputs have no reverse lookup - their PN side stays `null` on a cache
|
|
151
|
+
* miss. Store/usync failures are logged at debug and degrade to the
|
|
152
|
+
* forms already known. Inputs that are neither PN nor LID user jids
|
|
153
|
+
* resolve to `{ lidJid: null, pnJid: null }`.
|
|
154
|
+
*/
|
|
155
|
+
async resolveUserJidPair(userJid, timeoutMs = this.defaultTimeoutMs) {
|
|
156
|
+
if ((0, jid_1.isLidJid)(userJid)) {
|
|
157
|
+
return { lidJid: userJid, pnJid: await this.findCachedAltForm(userJid, jid_1.isUserJid) };
|
|
158
|
+
}
|
|
159
|
+
if (!(0, jid_1.isUserJid)(userJid)) {
|
|
160
|
+
return { lidJid: null, pnJid: null };
|
|
161
|
+
}
|
|
162
|
+
const cachedLid = await this.findCachedAltForm(userJid, jid_1.isLidJid);
|
|
163
|
+
if (cachedLid) {
|
|
164
|
+
return { lidJid: cachedLid, pnJid: userJid };
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
const results = await this.queryLidsByPhoneJids([userJid], timeoutMs);
|
|
168
|
+
const match = results.find((entry) => entry.queriedJid === userJid);
|
|
169
|
+
if (match?.lidJid) {
|
|
170
|
+
return { lidJid: match.lidJid, pnJid: match.phoneJid };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
this.logger.debug('lid usync resolution failed for jid pair', {
|
|
175
|
+
jid: userJid,
|
|
176
|
+
message: (0, primitives_1.toError)(error).message
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return { lidJid: null, pnJid: userJid };
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Returns the device-list snapshot form of `userJid` that satisfies
|
|
183
|
+
* `matches` (checked against `userJid` then `altUserJid`), or `null` when
|
|
184
|
+
* the store is absent, misses, or fails.
|
|
185
|
+
*/
|
|
186
|
+
async findCachedAltForm(userJid, matches) {
|
|
187
|
+
if (!this.deviceListStore)
|
|
188
|
+
return null;
|
|
189
|
+
try {
|
|
190
|
+
const snapshot = await this.deviceListStore.findByAnyUserJid(userJid);
|
|
191
|
+
if (snapshot) {
|
|
192
|
+
if (matches(snapshot.userJid))
|
|
193
|
+
return snapshot.userJid;
|
|
194
|
+
if (snapshot.altUserJid && matches(snapshot.altUserJid))
|
|
195
|
+
return snapshot.altUserJid;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
this.logger.debug('device-list lookup failed for jid pair', {
|
|
200
|
+
jid: userJid,
|
|
201
|
+
message: (0, primitives_1.toError)(error).message
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
145
206
|
/**
|
|
146
207
|
* Enriches existing device-list snapshots with the resolved LID equivalents
|
|
147
208
|
* so {@link WaDeviceListStore.findByAnyUserJid} can later match a retry
|
|
@@ -30,6 +30,7 @@ export declare class WaComms {
|
|
|
30
30
|
private usedResumeHandshake;
|
|
31
31
|
private noiseSession;
|
|
32
32
|
private lastServerStaticKey;
|
|
33
|
+
private lastInboundAtMs;
|
|
33
34
|
private frameProcessingQueue;
|
|
34
35
|
private readonly frameHandlerQueue;
|
|
35
36
|
constructor(config: WaCommsConfig, logger?: Logger);
|
|
@@ -41,6 +42,8 @@ export declare class WaComms {
|
|
|
41
42
|
closeSocketAndResume(): Promise<void>;
|
|
42
43
|
closeSocketAndPreventRetry(): Promise<void>;
|
|
43
44
|
sendFrame(payload: Uint8Array): Promise<void>;
|
|
45
|
+
/** Timestamp (`Date.now()` ms) of the last raw payload received from the socket; `0` before any inbound. */
|
|
46
|
+
getLastInboundAtMs(): number;
|
|
44
47
|
getCommsState(): Readonly<WaCommsState>;
|
|
45
48
|
getServerStaticKey(): Uint8Array | null;
|
|
46
49
|
private openSocket;
|
|
@@ -81,6 +81,7 @@ class WaComms {
|
|
|
81
81
|
this.usedResumeHandshake = false;
|
|
82
82
|
this.noiseSession = null;
|
|
83
83
|
this.lastServerStaticKey = null;
|
|
84
|
+
this.lastInboundAtMs = 0;
|
|
84
85
|
this.frameProcessingQueue = Promise.resolve();
|
|
85
86
|
this.frameHandlerQueue = new BoundedTaskQueue_1.BoundedTaskQueue(WA_FRAME_HANDLER_QUEUE_MAX_SIZE, WA_FRAME_HANDLER_MAX_CONCURRENCY);
|
|
86
87
|
}
|
|
@@ -208,6 +209,10 @@ class WaComms {
|
|
|
208
209
|
const wire = await this.noiseSession.encryptFrame(payload);
|
|
209
210
|
await this.socket.send(wire);
|
|
210
211
|
}
|
|
212
|
+
/** Timestamp (`Date.now()` ms) of the last raw payload received from the socket; `0` before any inbound. */
|
|
213
|
+
getLastInboundAtMs() {
|
|
214
|
+
return this.lastInboundAtMs;
|
|
215
|
+
}
|
|
211
216
|
getCommsState() {
|
|
212
217
|
return {
|
|
213
218
|
started: this.started,
|
|
@@ -288,6 +293,7 @@ class WaComms {
|
|
|
288
293
|
});
|
|
289
294
|
}
|
|
290
295
|
onSocketMessage(payload) {
|
|
296
|
+
this.lastInboundAtMs = Date.now();
|
|
291
297
|
this.frameProcessingQueue = this.frameProcessingQueue.then(() => this.processSocketPayload(payload), () => this.processSocketPayload(payload));
|
|
292
298
|
}
|
|
293
299
|
async processSocketPayload(payload) {
|
|
@@ -525,6 +531,7 @@ class WaComms {
|
|
|
525
531
|
this.resumeHandshakeFailures = 0;
|
|
526
532
|
}
|
|
527
533
|
this.noiseSession = null;
|
|
534
|
+
this.lastInboundAtMs = 0;
|
|
528
535
|
this.clearPendingFrames();
|
|
529
536
|
this.pendingFramesOverflowClosing = false;
|
|
530
537
|
if (options.clearHandlers) {
|
|
@@ -5,6 +5,7 @@ interface WaKeepAliveOptions {
|
|
|
5
5
|
readonly logger: Logger;
|
|
6
6
|
readonly nodeOrchestrator: {
|
|
7
7
|
hasPending(): boolean;
|
|
8
|
+
clearPending(reason: Error): void;
|
|
8
9
|
query(node: BinaryNode, timeoutMs?: number): Promise<BinaryNode>;
|
|
9
10
|
};
|
|
10
11
|
readonly getComms: () => WaComms | null;
|
|
@@ -18,8 +19,13 @@ interface WaKeepAliveOptions {
|
|
|
18
19
|
}
|
|
19
20
|
/**
|
|
20
21
|
* Periodically pings the server with a ping IQ to detect dead sockets and
|
|
21
|
-
* measure clock skew.
|
|
22
|
-
*
|
|
22
|
+
* measure clock skew. A ping is skipped while another ping is in flight, or
|
|
23
|
+
* while other queries are pending and the socket shows recent inbound
|
|
24
|
+
* activity. A socket silent past the dead-socket timeout is pinged even with
|
|
25
|
+
* queries pending: pending queries on a silent socket are evidence of a dead
|
|
26
|
+
* connection, so steady outbound traffic must not starve detection. On ping
|
|
27
|
+
* failure it rejects pending queries and resumes the socket via the
|
|
28
|
+
* configured `getComms()`.
|
|
23
29
|
*/
|
|
24
30
|
export declare class WaKeepAlive {
|
|
25
31
|
private readonly logger;
|
|
@@ -8,8 +8,13 @@ const KEEPALIVE_DEFAULT_MIN_JITTER_MS = 250;
|
|
|
8
8
|
const KEEPALIVE_MAX_JITTER_RATIO = 0.5;
|
|
9
9
|
/**
|
|
10
10
|
* Periodically pings the server with a ping IQ to detect dead sockets and
|
|
11
|
-
* measure clock skew.
|
|
12
|
-
*
|
|
11
|
+
* measure clock skew. A ping is skipped while another ping is in flight, or
|
|
12
|
+
* while other queries are pending and the socket shows recent inbound
|
|
13
|
+
* activity. A socket silent past the dead-socket timeout is pinged even with
|
|
14
|
+
* queries pending: pending queries on a silent socket are evidence of a dead
|
|
15
|
+
* connection, so steady outbound traffic must not starve detection. On ping
|
|
16
|
+
* failure it rejects pending queries and resumes the socket via the
|
|
17
|
+
* configured `getComms()`.
|
|
13
18
|
*/
|
|
14
19
|
class WaKeepAlive {
|
|
15
20
|
constructor(options) {
|
|
@@ -71,14 +76,25 @@ class WaKeepAlive {
|
|
|
71
76
|
this.schedule(generation);
|
|
72
77
|
return;
|
|
73
78
|
}
|
|
74
|
-
if (this.inFlight
|
|
75
|
-
this.logger.trace('keepalive skipped:
|
|
76
|
-
inFlight: this.inFlight,
|
|
77
|
-
pendingQueries: this.nodeOrchestrator.hasPending()
|
|
78
|
-
});
|
|
79
|
+
if (this.inFlight) {
|
|
80
|
+
this.logger.trace('keepalive skipped: ping already in flight');
|
|
79
81
|
this.schedule(generation);
|
|
80
82
|
return;
|
|
81
83
|
}
|
|
84
|
+
if (this.nodeOrchestrator.hasPending()) {
|
|
85
|
+
const inboundIdleMs = Date.now() - comms.getLastInboundAtMs();
|
|
86
|
+
if (inboundIdleMs < this.timeoutMs) {
|
|
87
|
+
this.logger.trace('keepalive skipped: pending queries with recent inbound', {
|
|
88
|
+
inboundIdleMs
|
|
89
|
+
});
|
|
90
|
+
this.schedule(generation);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
this.logger.debug('keepalive pinging despite pending queries: inbound is silent', {
|
|
94
|
+
inboundIdleMs,
|
|
95
|
+
timeoutMs: this.timeoutMs
|
|
96
|
+
});
|
|
97
|
+
}
|
|
82
98
|
this.inFlight = true;
|
|
83
99
|
const startedAt = Date.now();
|
|
84
100
|
try {
|
|
@@ -111,6 +127,7 @@ class WaKeepAlive {
|
|
|
111
127
|
this.logger.warn('keepalive ping failed, reconnecting socket', {
|
|
112
128
|
message: (0, primitives_1.toError)(error).message
|
|
113
129
|
});
|
|
130
|
+
this.nodeOrchestrator.clearPending(new Error('socket resume requested by keepalive ping failure'));
|
|
114
131
|
try {
|
|
115
132
|
await comms.closeSocketAndResume();
|
|
116
133
|
}
|
|
@@ -4,4 +4,29 @@ export declare function buildGetPrivacySettingsIq(): BinaryNode;
|
|
|
4
4
|
export declare function buildSetPrivacyCategoryIq(category: WaPrivacyCategory, value: WaPrivacyValue): BinaryNode;
|
|
5
5
|
export declare function buildGetPrivacyDisallowedListIq(category: WaPrivacyCategory): BinaryNode;
|
|
6
6
|
export declare function buildGetBlocklistIq(): BinaryNode;
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Blocklist target in both addressing forms. At least one side is always
|
|
9
|
+
* present: LID-migrated accounts carry `lidJid` (plus `pnJid` when known),
|
|
10
|
+
* non-migrated accounts carry only `pnJid`.
|
|
11
|
+
*/
|
|
12
|
+
export type WaBlocklistTarget = {
|
|
13
|
+
readonly lidJid: string;
|
|
14
|
+
readonly pnJid: string | null;
|
|
15
|
+
} | {
|
|
16
|
+
readonly lidJid: null;
|
|
17
|
+
readonly pnJid: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Builds the blocklist `set` IQ for a block action. LID-migrated targets are
|
|
21
|
+
* addressed by the LID jid plus an identifier attribute: `pn_jid` when the
|
|
22
|
+
* phone jid is known, else `unknown_identifier="true"`. Non-migrated targets
|
|
23
|
+
* are addressed by the phone jid alone. The server rejects a block that
|
|
24
|
+
* addresses a migrated account by phone jid or omits the identifier
|
|
25
|
+
* (`400: bad-request`).
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildBlocklistBlockIq(target: WaBlocklistTarget): BinaryNode;
|
|
28
|
+
/**
|
|
29
|
+
* Builds the blocklist `set` IQ for an unblock action. The server keys
|
|
30
|
+
* migrated entries by LID, so `jid` must be the LID jid when one exists.
|
|
31
|
+
*/
|
|
32
|
+
export declare function buildBlocklistUnblockIq(jid: string): BinaryNode;
|
|
@@ -4,7 +4,8 @@ exports.buildGetPrivacySettingsIq = buildGetPrivacySettingsIq;
|
|
|
4
4
|
exports.buildSetPrivacyCategoryIq = buildSetPrivacyCategoryIq;
|
|
5
5
|
exports.buildGetPrivacyDisallowedListIq = buildGetPrivacyDisallowedListIq;
|
|
6
6
|
exports.buildGetBlocklistIq = buildGetBlocklistIq;
|
|
7
|
-
exports.
|
|
7
|
+
exports.buildBlocklistBlockIq = buildBlocklistBlockIq;
|
|
8
|
+
exports.buildBlocklistUnblockIq = buildBlocklistUnblockIq;
|
|
8
9
|
const defaults_1 = require("../../../protocol/defaults");
|
|
9
10
|
const nodes_1 = require("../../../protocol/nodes");
|
|
10
11
|
const privacy_1 = require("../../../protocol/privacy");
|
|
@@ -45,11 +46,41 @@ function buildGetPrivacyDisallowedListIq(category) {
|
|
|
45
46
|
function buildGetBlocklistIq() {
|
|
46
47
|
return (0, query_1.buildIqNode)(nodes_1.WA_IQ_TYPES.GET, defaults_1.WA_DEFAULTS.HOST_DOMAIN, nodes_1.WA_XMLNS.BLOCKLIST);
|
|
47
48
|
}
|
|
48
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Builds the blocklist `set` IQ for a block action. LID-migrated targets are
|
|
51
|
+
* addressed by the LID jid plus an identifier attribute: `pn_jid` when the
|
|
52
|
+
* phone jid is known, else `unknown_identifier="true"`. Non-migrated targets
|
|
53
|
+
* are addressed by the phone jid alone. The server rejects a block that
|
|
54
|
+
* addresses a migrated account by phone jid or omits the identifier
|
|
55
|
+
* (`400: bad-request`).
|
|
56
|
+
*/
|
|
57
|
+
function buildBlocklistBlockIq(target) {
|
|
58
|
+
let attrs;
|
|
59
|
+
if (target.lidJid !== null) {
|
|
60
|
+
attrs =
|
|
61
|
+
target.pnJid !== null
|
|
62
|
+
? { action: 'block', jid: target.lidJid, pn_jid: target.pnJid }
|
|
63
|
+
: { action: 'block', jid: target.lidJid, unknown_identifier: 'true' };
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
attrs = { action: 'block', jid: target.pnJid };
|
|
67
|
+
}
|
|
49
68
|
return (0, query_1.buildIqNode)(nodes_1.WA_IQ_TYPES.SET, defaults_1.WA_DEFAULTS.HOST_DOMAIN, nodes_1.WA_XMLNS.BLOCKLIST, [
|
|
50
69
|
{
|
|
51
70
|
tag: 'item',
|
|
52
|
-
attrs
|
|
71
|
+
attrs
|
|
72
|
+
}
|
|
73
|
+
]);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Builds the blocklist `set` IQ for an unblock action. The server keys
|
|
77
|
+
* migrated entries by LID, so `jid` must be the LID jid when one exists.
|
|
78
|
+
*/
|
|
79
|
+
function buildBlocklistUnblockIq(jid) {
|
|
80
|
+
return (0, query_1.buildIqNode)(nodes_1.WA_IQ_TYPES.SET, defaults_1.WA_DEFAULTS.HOST_DOMAIN, nodes_1.WA_XMLNS.BLOCKLIST, [
|
|
81
|
+
{
|
|
82
|
+
tag: 'item',
|
|
83
|
+
attrs: { jid, action: 'unblock' }
|
|
53
84
|
}
|
|
54
85
|
]);
|
|
55
86
|
}
|