zapo-js 1.6.0 → 1.6.2
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/store/createStore.js +119 -57
- 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/store/createStore.js +119 -57
- package/dist/store/types.d.ts +31 -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
|
|
@@ -34,6 +34,7 @@ import { WaSignalMemoryStore } from './memory/signal.store.js';
|
|
|
34
34
|
import { WaThreadMemoryStore } from './memory/thread.store.js';
|
|
35
35
|
import { NOOP_CONTACT_STORE, NOOP_DEVICE_LIST_STORE, NOOP_GROUP_METADATA_STORE, NOOP_MESSAGE_SECRET_STORE, NOOP_MESSAGE_STORE, NOOP_RETRY_STORE, NOOP_THREAD_STORE } from './noop.store.js';
|
|
36
36
|
import { resolvePositive } from '../util/coercion.js';
|
|
37
|
+
import { toError } from '../util/primitives.js';
|
|
37
38
|
const DEFAULT_CACHE_TTLS_MS = Object.freeze({
|
|
38
39
|
retryMs: 60 * 1000,
|
|
39
40
|
groupMetadataMs: 5 * 60 * 1000,
|
|
@@ -109,6 +110,7 @@ export function createStore(options) {
|
|
|
109
110
|
messageSecret: resolvePositive(options.memory?.cacheTtlMs?.messageSecretMs, DEFAULT_CACHE_TTLS_MS.messageSecretMs, 'memory.cacheTtlMs.messageSecretMs')
|
|
110
111
|
});
|
|
111
112
|
const sessions = new Map();
|
|
113
|
+
const pendingSessionDestroys = new Set();
|
|
112
114
|
let storeDestroyed = false;
|
|
113
115
|
return {
|
|
114
116
|
session(sessionId) {
|
|
@@ -148,37 +150,48 @@ export function createStore(options) {
|
|
|
148
150
|
? new WaContactMemoryStore({ maxContacts: ml.contacts })
|
|
149
151
|
: NOOP_CONTACT_STORE);
|
|
150
152
|
const rawPrivacyToken = resolveStore(id, backends, providers.privacyToken ?? 'memory', 'privacyToken', 'stores', () => new WaPrivacyTokenMemoryStore(ml.privacyTokens));
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
maxGroups: ml.groupMetadataGroups,
|
|
161
|
-
logger: memoryLogger?.child({
|
|
162
|
-
domain: 'groupMetadata',
|
|
163
|
-
sessionId: id
|
|
153
|
+
const buildCaches = () => ({
|
|
154
|
+
retry: withRetryLock(resolveStore(id, backends, cacheProviders.retry ?? 'memory', 'retry', 'caches', () => cacheProviders.retry === 'memory' || !cacheProviders.retry
|
|
155
|
+
? new WaRetryMemoryStore(cacheTtlsMs.retry, {
|
|
156
|
+
maxOutboundMessages: ml.retryOutboundMessages,
|
|
157
|
+
maxInboundCounters: ml.retryInboundCounters,
|
|
158
|
+
logger: memoryLogger?.child({
|
|
159
|
+
domain: 'retry',
|
|
160
|
+
sessionId: id
|
|
161
|
+
})
|
|
164
162
|
})
|
|
165
|
-
|
|
166
|
-
:
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
? new WaMessageSecretMemoryStore(cacheTtlsMs.messageSecret, {
|
|
175
|
-
maxSecrets: ml.messageSecrets,
|
|
176
|
-
logger: memoryLogger?.child({
|
|
177
|
-
domain: 'messageSecret',
|
|
178
|
-
sessionId: id
|
|
163
|
+
: NOOP_RETRY_STORE)),
|
|
164
|
+
groupMetadata: withGroupMetadataLock(resolveStore(id, backends, cacheProviders.groupMetadata ?? 'memory', 'groupMetadata', 'caches', () => cacheProviders.groupMetadata === 'memory' ||
|
|
165
|
+
!cacheProviders.groupMetadata
|
|
166
|
+
? new WaGroupMetadataMemoryStore(cacheTtlsMs.groupMetadata, {
|
|
167
|
+
maxGroups: ml.groupMetadataGroups,
|
|
168
|
+
logger: memoryLogger?.child({
|
|
169
|
+
domain: 'groupMetadata',
|
|
170
|
+
sessionId: id
|
|
171
|
+
})
|
|
179
172
|
})
|
|
180
|
-
|
|
181
|
-
:
|
|
173
|
+
: NOOP_GROUP_METADATA_STORE)),
|
|
174
|
+
deviceList: withDeviceListLock(resolveStore(id, backends, cacheProviders.deviceList ?? 'memory', 'deviceList', 'caches', () => cacheProviders.deviceList === 'memory' || !cacheProviders.deviceList
|
|
175
|
+
? new WaDeviceListMemoryStore(cacheTtlsMs.deviceList, {
|
|
176
|
+
maxUsers: ml.deviceListUsers,
|
|
177
|
+
logger: memoryLogger?.child({
|
|
178
|
+
domain: 'deviceList',
|
|
179
|
+
sessionId: id
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
: NOOP_DEVICE_LIST_STORE)),
|
|
183
|
+
messageSecret: withMessageSecretLock(resolveStore(id, backends, cacheProviders.messageSecret ?? 'memory', 'messageSecret', 'caches', () => cacheProviders.messageSecret === 'memory' ||
|
|
184
|
+
!cacheProviders.messageSecret
|
|
185
|
+
? new WaMessageSecretMemoryStore(cacheTtlsMs.messageSecret, {
|
|
186
|
+
maxSecrets: ml.messageSecrets,
|
|
187
|
+
logger: memoryLogger?.child({
|
|
188
|
+
domain: 'messageSecret',
|
|
189
|
+
sessionId: id
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
: NOOP_MESSAGE_SECRET_STORE))
|
|
193
|
+
});
|
|
194
|
+
let caches = buildCaches();
|
|
182
195
|
const authStore = withAuthLock(rawAuth);
|
|
183
196
|
const signalStore = withSignalLock(rawSignal);
|
|
184
197
|
const preKeyStore = withPreKeyLock(rawPreKey);
|
|
@@ -192,41 +205,72 @@ export function createStore(options) {
|
|
|
192
205
|
? withSenderKeyCache(rawSenderKey, cacheLayer.limits?.senderKey)
|
|
193
206
|
: rawSenderKey);
|
|
194
207
|
const appStateStore = withAppStateLock(rawAppState);
|
|
195
|
-
const retryStore = withRetryLock(rawRetry);
|
|
196
|
-
const groupMetadataStore = withGroupMetadataLock(rawGroupMetadata);
|
|
197
|
-
const deviceListStore = withDeviceListLock(rawDeviceList);
|
|
198
208
|
const messageStore = withMessageLock(rawMessages);
|
|
199
|
-
const messageSecretStore = withMessageSecretLock(rawMessageSecret);
|
|
200
209
|
const threadStore = withThreadLock(rawThreads);
|
|
201
210
|
const contactStore = withContactLock(rawContacts);
|
|
202
211
|
const privacyTokenStore = withPrivacyTokenLock(cacheLayer.privacyToken && usesBackend(providers.privacyToken)
|
|
203
212
|
? withPrivacyTokenCache(rawPrivacyToken, cacheLayer.limits?.privacyToken)
|
|
204
213
|
: rawPrivacyToken);
|
|
205
|
-
let cachesDestroyed = false;
|
|
206
214
|
let sessionDestroyed = false;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
messageSecretStore.clear()
|
|
215
|
+
let destroyPromise = null;
|
|
216
|
+
let cacheLifecycle = Promise.resolve();
|
|
217
|
+
const teardownCaches = async (target) => {
|
|
218
|
+
const cleared = await Promise.allSettled([
|
|
219
|
+
target.retry.clear(),
|
|
220
|
+
target.groupMetadata.clear(),
|
|
221
|
+
target.deviceList.clear(),
|
|
222
|
+
target.messageSecret.clear()
|
|
216
223
|
]);
|
|
217
|
-
await Promise.
|
|
218
|
-
destroyIfSupported(
|
|
219
|
-
destroyIfSupported(
|
|
220
|
-
destroyIfSupported(
|
|
221
|
-
destroyIfSupported(
|
|
224
|
+
const destroyed = await Promise.allSettled([
|
|
225
|
+
destroyIfSupported(target.retry),
|
|
226
|
+
destroyIfSupported(target.groupMetadata),
|
|
227
|
+
destroyIfSupported(target.deviceList),
|
|
228
|
+
destroyIfSupported(target.messageSecret)
|
|
222
229
|
]);
|
|
230
|
+
const failures = [...cleared, ...destroyed].filter((result) => result.status === 'rejected');
|
|
231
|
+
if (failures.length > 0) {
|
|
232
|
+
storeLogger?.warn('cache teardown had failures', {
|
|
233
|
+
sessionId: id,
|
|
234
|
+
droppedCount: failures.length,
|
|
235
|
+
totalExpected: cleared.length + destroyed.length,
|
|
236
|
+
sample: toError(failures[0].reason).message
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
return failures.length;
|
|
240
|
+
};
|
|
241
|
+
const destroyCaches = () => {
|
|
242
|
+
const run = cacheLifecycle.then(async () => {
|
|
243
|
+
if (sessionDestroyed)
|
|
244
|
+
return;
|
|
245
|
+
const failureCount = await teardownCaches(caches);
|
|
246
|
+
caches = buildCaches();
|
|
247
|
+
if (failureCount > 0) {
|
|
248
|
+
throw new Error(`cache reset finished with ${failureCount} teardown failure(s); ` +
|
|
249
|
+
'fresh caches are in place but old persistent entries may remain');
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
cacheLifecycle = run.then(() => undefined, () => undefined);
|
|
253
|
+
return run;
|
|
223
254
|
};
|
|
224
|
-
const destroy =
|
|
225
|
-
if (
|
|
226
|
-
|
|
255
|
+
const destroy = () => {
|
|
256
|
+
if (!destroyPromise) {
|
|
257
|
+
const pending = destroyInternal().finally(() => pendingSessionDestroys.delete(pending));
|
|
258
|
+
destroyPromise = pending;
|
|
259
|
+
pendingSessionDestroys.add(pending);
|
|
260
|
+
}
|
|
261
|
+
return destroyPromise;
|
|
262
|
+
};
|
|
263
|
+
const destroyInternal = async () => {
|
|
227
264
|
sessionDestroyed = true;
|
|
228
|
-
|
|
229
|
-
|
|
265
|
+
if (sessions.get(id) === storeSession) {
|
|
266
|
+
sessions.delete(id);
|
|
267
|
+
}
|
|
268
|
+
await cacheLifecycle;
|
|
269
|
+
await teardownCaches(caches);
|
|
270
|
+
await destroyPersistentStores();
|
|
271
|
+
};
|
|
272
|
+
const destroyPersistentStores = async () => {
|
|
273
|
+
const results = await Promise.allSettled([
|
|
230
274
|
destroyIfSupported(authStore),
|
|
231
275
|
destroyIfSupported(signalStore),
|
|
232
276
|
destroyIfSupported(preKeyStore),
|
|
@@ -239,6 +283,15 @@ export function createStore(options) {
|
|
|
239
283
|
destroyIfSupported(contactStore),
|
|
240
284
|
destroyIfSupported(privacyTokenStore)
|
|
241
285
|
]);
|
|
286
|
+
const failures = results.filter((result) => result.status === 'rejected');
|
|
287
|
+
if (failures.length > 0) {
|
|
288
|
+
storeLogger?.warn('persistent store teardown had failures', {
|
|
289
|
+
sessionId: id,
|
|
290
|
+
droppedCount: failures.length,
|
|
291
|
+
totalExpected: results.length,
|
|
292
|
+
sample: toError(failures[0].reason).message
|
|
293
|
+
});
|
|
294
|
+
}
|
|
242
295
|
};
|
|
243
296
|
const storeSession = {
|
|
244
297
|
auth: authStore,
|
|
@@ -248,11 +301,19 @@ export function createStore(options) {
|
|
|
248
301
|
identity: identityStore,
|
|
249
302
|
senderKey: senderKeyStore,
|
|
250
303
|
appState: appStateStore,
|
|
251
|
-
retry
|
|
252
|
-
|
|
253
|
-
|
|
304
|
+
get retry() {
|
|
305
|
+
return caches.retry;
|
|
306
|
+
},
|
|
307
|
+
get groupMetadata() {
|
|
308
|
+
return caches.groupMetadata;
|
|
309
|
+
},
|
|
310
|
+
get deviceList() {
|
|
311
|
+
return caches.deviceList;
|
|
312
|
+
},
|
|
254
313
|
messages: messageStore,
|
|
255
|
-
messageSecret
|
|
314
|
+
get messageSecret() {
|
|
315
|
+
return caches.messageSecret;
|
|
316
|
+
},
|
|
256
317
|
threads: threadStore,
|
|
257
318
|
contacts: contactStore,
|
|
258
319
|
privacyToken: privacyTokenStore,
|
|
@@ -273,6 +334,7 @@ export function createStore(options) {
|
|
|
273
334
|
const list = Array.from(sessions.values());
|
|
274
335
|
sessions.clear();
|
|
275
336
|
await Promise.all(list.map((s) => s.destroy()));
|
|
337
|
+
await Promise.all(Array.from(pendingSessionDestroys));
|
|
276
338
|
const uniqueBackends = new Set(Object.values(backends));
|
|
277
339
|
await Promise.all(Array.from(uniqueBackends, (backend) => destroyIfSupported(backend)));
|
|
278
340
|
}
|