zapo-js 1.1.3 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/WaClient.js +1 -7
- package/dist/client/WaClientFactory.js +2 -0
- package/dist/client/coordinators/WaMessageDispatchCoordinator.d.ts +16 -0
- package/dist/client/coordinators/WaMessageDispatchCoordinator.js +36 -3
- package/dist/client/persistence/mailbox.d.ts +6 -0
- package/dist/client/persistence/mailbox.js +2 -2
- package/dist/client/types.d.ts +49 -0
- package/dist/esm/client/WaClient.js +1 -7
- package/dist/esm/client/WaClientFactory.js +2 -0
- package/dist/esm/client/coordinators/WaMessageDispatchCoordinator.js +37 -4
- package/dist/esm/client/persistence/mailbox.js +2 -2
- package/dist/esm/message/primitives/incoming.js +36 -0
- package/dist/esm/transport/node/builders/message.js +3 -0
- package/dist/index.d.ts +1 -1
- package/dist/message/primitives/incoming.d.ts +2 -1
- package/dist/message/primitives/incoming.js +36 -0
- package/dist/transport/node/builders/message.d.ts +1 -0
- package/dist/transport/node/builders/message.js +3 -0
- package/package.json +1 -1
package/dist/client/WaClient.js
CHANGED
|
@@ -12,7 +12,6 @@ const _proto_1 = require("../proto");
|
|
|
12
12
|
const constants_1 = require("../protocol/constants");
|
|
13
13
|
const jid_1 = require("../protocol/jid");
|
|
14
14
|
const stream_1 = require("../protocol/stream");
|
|
15
|
-
const noop_store_1 = require("../store/noop.store");
|
|
16
15
|
const device_1 = require("../transport/node/builders/device");
|
|
17
16
|
const query_1 = require("../transport/node/query");
|
|
18
17
|
const wa_web_version_fetcher_1 = require("../transport/wa-web-version-fetcher");
|
|
@@ -91,12 +90,6 @@ class WaClient extends node_events_1.EventEmitter {
|
|
|
91
90
|
threadStore: this.stores.threads,
|
|
92
91
|
contactStore: this.stores.contacts
|
|
93
92
|
}, this.logger, this.options.writeBehind);
|
|
94
|
-
if (this.options.addons?.autoDecrypt !== false &&
|
|
95
|
-
this.stores.messageSecret === noop_store_1.NOOP_MESSAGE_SECRET_STORE) {
|
|
96
|
-
this.logger.warn('addons.autoDecrypt is on (default) but messageSecret cache is noop – ' +
|
|
97
|
-
'addon decryption will only work if secrets are in the message store. ' +
|
|
98
|
-
'Set addons.autoDecrypt: false to silence this warning.');
|
|
99
|
-
}
|
|
100
93
|
const dependencies = (0, WaClientFactory_1.buildWaClientDependencies)({
|
|
101
94
|
base,
|
|
102
95
|
runtime: {
|
|
@@ -212,6 +205,7 @@ class WaClient extends node_events_1.EventEmitter {
|
|
|
212
205
|
logger: this.logger,
|
|
213
206
|
writeBehind: this.writeBehind,
|
|
214
207
|
messageSecretStore: this.stores.messageSecret,
|
|
208
|
+
persistAllSecrets: this.options.addons?.persistAllSecrets === true,
|
|
215
209
|
event
|
|
216
210
|
});
|
|
217
211
|
if (this.options.addons?.autoDecrypt !== false && event.message) {
|
|
@@ -468,6 +468,7 @@ function buildWaClientDependencies(input) {
|
|
|
468
468
|
deviceListStore: sessionStore.deviceList,
|
|
469
469
|
signalDeviceSync,
|
|
470
470
|
messageSecretStore: sessionStore.messageSecret,
|
|
471
|
+
persistAllMessageSecrets: options.addons?.persistAllSecrets === true,
|
|
471
472
|
getCurrentCredentials,
|
|
472
473
|
resolvePrivacyTokenNode: (recipientJid) => trustedContactToken.resolveTokenForMessage(recipientJid),
|
|
473
474
|
onDirectMessageSent: (recipientJid) => {
|
|
@@ -694,6 +695,7 @@ function buildWaClientDependencies(input) {
|
|
|
694
695
|
.catch((err) => runtime.handleError((0, primitives_1.toError)(err)));
|
|
695
696
|
},
|
|
696
697
|
emitNewsletterMessageUpdate: (event) => runtime.emitEvent('newsletter_message_update', event),
|
|
698
|
+
emitUnavailableMessage: (event) => runtime.emitEvent('message_unavailable', event),
|
|
697
699
|
emitUnhandledStanza: (event) => runtime.emitEvent('debug_unhandled_stanza', event)
|
|
698
700
|
};
|
|
699
701
|
const handleClientDirtyBits = (dirtyBits) => (0, dirty_1.handleDirtyBits)({
|
|
@@ -45,6 +45,12 @@ interface WaMessageDispatchCoordinatorOptions {
|
|
|
45
45
|
readonly deviceListStore: WaDeviceListStore;
|
|
46
46
|
readonly signalDeviceSync: SignalDeviceSyncApi;
|
|
47
47
|
readonly messageSecretStore: WaMessageSecretStore;
|
|
48
|
+
/**
|
|
49
|
+
* When `true`, persist the message secret for every outgoing message, not
|
|
50
|
+
* only the poll / event / bot prompts that `needsSecretPersistence` flags.
|
|
51
|
+
* Wired from the client-level `addons.persistAllSecrets` option.
|
|
52
|
+
*/
|
|
53
|
+
readonly persistAllMessageSecrets?: boolean;
|
|
48
54
|
readonly getCurrentCredentials: () => WaAuthCredentials | null;
|
|
49
55
|
readonly resolvePrivacyTokenNode: (recipientJid: string) => Promise<BinaryNode | null>;
|
|
50
56
|
readonly onDirectMessageSent: (recipientJid: string) => void;
|
|
@@ -79,6 +85,16 @@ export declare class WaMessageDispatchCoordinator {
|
|
|
79
85
|
* no LID is known/resolvable. Inputs already in LID form pass through.
|
|
80
86
|
*/
|
|
81
87
|
private resolveDirectRecipientLid;
|
|
88
|
+
/**
|
|
89
|
+
* Resolves the `peer_recipient_pn` cross-reference for a 1:1 send, or
|
|
90
|
+
* `undefined` to drop the attribute. Only set when the envelope is
|
|
91
|
+
* LID-addressed (`directRecipientJid` is a LID): the PN is the caller's own
|
|
92
|
+
* JID when they passed a PN (zapo switched it to LID for sending), else the
|
|
93
|
+
* device-list snapshot counterpart for a recipient passed in LID form.
|
|
94
|
+
* Mirrors wa-web, which sets `peer_recipient_pn = getPhoneNumber($)` for a
|
|
95
|
+
* LID destination.
|
|
96
|
+
*/
|
|
97
|
+
private resolvePeerRecipientPn;
|
|
82
98
|
syncSignalSession(jid: string, reasonIdentity?: boolean): Promise<void>;
|
|
83
99
|
sendReceipt(input: WaSendReceiptInput): Promise<void>;
|
|
84
100
|
publishProtocolMessageToDevice(deviceJid: string, protocolMessage: Proto.Message.IProtocolMessage, options?: {
|
|
@@ -217,7 +217,7 @@ class WaMessageDispatchCoordinator {
|
|
|
217
217
|
if (rawSecret &&
|
|
218
218
|
rawSecret.length > 0 &&
|
|
219
219
|
sendOptions.id &&
|
|
220
|
-
(0, content_1.needsSecretPersistence)(messageWithSecret)) {
|
|
220
|
+
(this.deps.persistAllMessageSecrets || (0, content_1.needsSecretPersistence)(messageWithSecret))) {
|
|
221
221
|
const meJid = this.deps.getCurrentCredentials()?.meJid ?? '';
|
|
222
222
|
void this.deps.messageSecretStore
|
|
223
223
|
.set(sendOptions.id, { secret: rawSecret, senderJid: meJid })
|
|
@@ -278,11 +278,14 @@ class WaMessageDispatchCoordinator {
|
|
|
278
278
|
const directRecipientJid = isGroup
|
|
279
279
|
? recipientJid
|
|
280
280
|
: await this.resolveDirectRecipientLid((0, jid_1.toUserJid)(recipientJid));
|
|
281
|
+
const peerRecipientPn = isGroup
|
|
282
|
+
? undefined
|
|
283
|
+
: await this.resolvePeerRecipientPn((0, jid_1.toUserJid)(recipientJid), directRecipientJid);
|
|
281
284
|
const publishResult = isGroup
|
|
282
285
|
? this.shouldUseGroupDirectPath(messageWithIcdc)
|
|
283
286
|
? await this.publishGroupDirectMessage(recipientJid, envelope)
|
|
284
287
|
: await this.publishGroupSenderKeyMessage(recipientJid, envelope)
|
|
285
|
-
: await this.publishDirectSignalMessageWithFanout(directRecipientJid, envelope);
|
|
288
|
+
: await this.publishDirectSignalMessageWithFanout(directRecipientJid, envelope, peerRecipientPn);
|
|
286
289
|
return upload ? { ...publishResult, upload } : publishResult;
|
|
287
290
|
}
|
|
288
291
|
/**
|
|
@@ -317,6 +320,35 @@ class WaMessageDispatchCoordinator {
|
|
|
317
320
|
}
|
|
318
321
|
return pnUserJid;
|
|
319
322
|
}
|
|
323
|
+
/**
|
|
324
|
+
* Resolves the `peer_recipient_pn` cross-reference for a 1:1 send, or
|
|
325
|
+
* `undefined` to drop the attribute. Only set when the envelope is
|
|
326
|
+
* LID-addressed (`directRecipientJid` is a LID): the PN is the caller's own
|
|
327
|
+
* JID when they passed a PN (zapo switched it to LID for sending), else the
|
|
328
|
+
* device-list snapshot counterpart for a recipient passed in LID form.
|
|
329
|
+
* Mirrors wa-web, which sets `peer_recipient_pn = getPhoneNumber($)` for a
|
|
330
|
+
* LID destination.
|
|
331
|
+
*/
|
|
332
|
+
async resolvePeerRecipientPn(recipientUserJid, directRecipientJid) {
|
|
333
|
+
if (!(0, jid_1.isLidJid)(directRecipientJid))
|
|
334
|
+
return undefined;
|
|
335
|
+
if ((0, jid_1.isUserJid)(recipientUserJid))
|
|
336
|
+
return recipientUserJid;
|
|
337
|
+
try {
|
|
338
|
+
const snapshot = await this.deps.deviceListStore.findByAnyUserJid(directRecipientJid);
|
|
339
|
+
if (snapshot?.userJid && (0, jid_1.isUserJid)(snapshot.userJid))
|
|
340
|
+
return snapshot.userJid;
|
|
341
|
+
if (snapshot?.altUserJid && (0, jid_1.isUserJid)(snapshot.altUserJid))
|
|
342
|
+
return snapshot.altUserJid;
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
this.deps.logger.trace('peer_recipient_pn store lookup failed', {
|
|
346
|
+
lid: directRecipientJid,
|
|
347
|
+
message: (0, primitives_2.toError)(error).message
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
return undefined;
|
|
351
|
+
}
|
|
320
352
|
async syncSignalSession(jid, reasonIdentity = false) {
|
|
321
353
|
const address = (0, jid_1.parseSignalAddressFromJid)(jid);
|
|
322
354
|
if (address.server === constants_1.WA_DEFAULTS.GROUP_SERVER) {
|
|
@@ -997,7 +1029,7 @@ class WaMessageDispatchCoordinator {
|
|
|
997
1029
|
distributionParticipants
|
|
998
1030
|
};
|
|
999
1031
|
}
|
|
1000
|
-
async publishDirectSignalMessageWithFanout(recipientJid, envelope) {
|
|
1032
|
+
async publishDirectSignalMessageWithFanout(recipientJid, envelope, peerRecipientPn) {
|
|
1001
1033
|
const { message, plaintext, type, edit, mediatype, sendOptions } = envelope;
|
|
1002
1034
|
const meJid = this.requireCurrentMeJid('sendMessage');
|
|
1003
1035
|
const meLid = this.deps.getCurrentCredentials()?.meLid;
|
|
@@ -1160,6 +1192,7 @@ class WaMessageDispatchCoordinator {
|
|
|
1160
1192
|
customNodes: customNodes.length > 0 ? customNodes : undefined,
|
|
1161
1193
|
mediatype,
|
|
1162
1194
|
decryptFail: envelope.decryptFail,
|
|
1195
|
+
peerRecipientPn,
|
|
1163
1196
|
additionalAttributes: sendOptions.additionalAttributes
|
|
1164
1197
|
});
|
|
1165
1198
|
const replayPayload = {
|
|
@@ -6,6 +6,12 @@ interface WaPersistIncomingMailboxOptions {
|
|
|
6
6
|
readonly logger: Logger;
|
|
7
7
|
readonly writeBehind: WriteBehindPersistence;
|
|
8
8
|
readonly messageSecretStore: WaMessageSecretStore;
|
|
9
|
+
/**
|
|
10
|
+
* When `true`, persist the secret of every incoming message, not only the
|
|
11
|
+
* poll / event / bot prompts that `needsSecretPersistence` flags. Wired
|
|
12
|
+
* from the client-level `addons.persistAllSecrets` option.
|
|
13
|
+
*/
|
|
14
|
+
readonly persistAllSecrets?: boolean;
|
|
9
15
|
readonly event: WaIncomingMessageEvent;
|
|
10
16
|
}
|
|
11
17
|
export declare function persistIncomingMailboxEntities(options: WaPersistIncomingMailboxOptions): void;
|
|
@@ -56,7 +56,7 @@ function persistContacts(writeBehind, event, nowMs) {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
function persistIncomingMailboxEntities(options) {
|
|
59
|
-
const { logger, writeBehind, messageSecretStore, event } = options;
|
|
59
|
+
const { logger, writeBehind, messageSecretStore, persistAllSecrets, event } = options;
|
|
60
60
|
const stanzaId = event.key.id;
|
|
61
61
|
const chatJid = event.key.remoteJid;
|
|
62
62
|
if (!stanzaId || !chatJid) {
|
|
@@ -81,7 +81,7 @@ function persistIncomingMailboxEntities(options) {
|
|
|
81
81
|
if (rawSecret &&
|
|
82
82
|
rawSecret.length > 0 &&
|
|
83
83
|
event.message &&
|
|
84
|
-
(0, content_1.needsSecretPersistence)(event.message)) {
|
|
84
|
+
(persistAllSecrets || (0, content_1.needsSecretPersistence)(event.message))) {
|
|
85
85
|
const rawSender = event.key.participant ?? event.rawNode.attrs.participant ?? event.key.remoteJid;
|
|
86
86
|
const senderJid = rawSender ? (0, jid_1.toUserJid)(rawSender) : '';
|
|
87
87
|
void messageSecretStore
|
package/dist/client/types.d.ts
CHANGED
|
@@ -241,6 +241,26 @@ export interface WaAddonOptions {
|
|
|
241
241
|
* poll votes, event responses, comments).
|
|
242
242
|
*/
|
|
243
243
|
readonly autoDecrypt?: boolean;
|
|
244
|
+
/**
|
|
245
|
+
* Persist the 32-byte message secret of every sent and received message,
|
|
246
|
+
* not just the poll / event / bot-prompt messages the library knows will
|
|
247
|
+
* get a follow-up. Off by default.
|
|
248
|
+
*
|
|
249
|
+
* Encrypted addons whose parent can be any message type - reactions,
|
|
250
|
+
* comments, and `secretEncryptedMessage` edits - need the parent's secret
|
|
251
|
+
* to decrypt. Without this, those parents stay decryptable after a restart
|
|
252
|
+
* only when the full `messages` archive is persistent. Enable this to keep
|
|
253
|
+
* them decryptable while storing only the secret, not the message body
|
|
254
|
+
* (e.g. with `messages: 'none'`).
|
|
255
|
+
*
|
|
256
|
+
* Has no effect when the `messageSecret` cache is `'none'`: every write
|
|
257
|
+
* lands in the noop store and is silently discarded. With the default
|
|
258
|
+
* `'memory'` provider it works for the lifetime of the process but is lost
|
|
259
|
+
* on restart and bounded by the cache's LRU and `messageSecretMs` TTL;
|
|
260
|
+
* point `messageSecret` at a persistent backend to keep the secrets across
|
|
261
|
+
* restarts.
|
|
262
|
+
*/
|
|
263
|
+
readonly persistAllSecrets?: boolean;
|
|
244
264
|
}
|
|
245
265
|
export interface WaPrivacyTokenOptions {
|
|
246
266
|
readonly tcTokenDurationS?: number;
|
|
@@ -721,6 +741,27 @@ export interface WaIncomingUnhandledStanzaEvent extends WaIncomingBaseEvent {
|
|
|
721
741
|
/** Short reason describing why the dispatcher did not match a typed handler. */
|
|
722
742
|
readonly reason: string;
|
|
723
743
|
}
|
|
744
|
+
/**
|
|
745
|
+
* Why an incoming message arrived as a content-less placeholder. `view_once`:
|
|
746
|
+
* a view-once already consumed elsewhere. `hosted`: a hosted/bot message that
|
|
747
|
+
* could not be fanned out. `other`: an `unavailable` marker the lib does not
|
|
748
|
+
* categorize yet.
|
|
749
|
+
*/
|
|
750
|
+
export type WaUnavailableMessageKind = 'view_once' | 'hosted' | 'other';
|
|
751
|
+
export interface WaIncomingUnavailableMessageEvent extends Omit<WaIncomingBaseEvent, 'chatJid' | 'stanzaId'> {
|
|
752
|
+
/** Which flavour of content the server signalled as unavailable. */
|
|
753
|
+
readonly kind: WaUnavailableMessageKind;
|
|
754
|
+
/**
|
|
755
|
+
* The message key (chat, stanza id, author, addressing metadata) – same
|
|
756
|
+
* shape the `message` event carries, so it can be stored or correlated. There
|
|
757
|
+
* is no decrypted `message`: the payload is unavailable and cannot be fetched.
|
|
758
|
+
*/
|
|
759
|
+
readonly key: WaIncomingMessageKey;
|
|
760
|
+
/** Stanza `t` attr (seconds since epoch). */
|
|
761
|
+
readonly timestampSeconds?: number;
|
|
762
|
+
/** Sender's display name from the stanza's `notify` attr. */
|
|
763
|
+
readonly pushName?: string;
|
|
764
|
+
}
|
|
724
765
|
export interface WaIncomingErrorStanzaEvent extends WaIncomingBaseEvent {
|
|
725
766
|
readonly code?: number;
|
|
726
767
|
readonly text?: string;
|
|
@@ -1057,6 +1098,14 @@ export interface WaClientEventMap {
|
|
|
1057
1098
|
* typed protocol payload directly.
|
|
1058
1099
|
*/
|
|
1059
1100
|
readonly message_protocol: (event: WaIncomingProtocolMessageEvent) => void;
|
|
1101
|
+
/**
|
|
1102
|
+
* A message the server delivered as a content-less placeholder: the payload
|
|
1103
|
+
* is unavailable and cannot be recovered (a view-once already consumed, or a
|
|
1104
|
+
* hosted/bot message that could not be fanned out). The lib acks it and emits
|
|
1105
|
+
* this instead of a `message` event for the same stanza. Branch on
|
|
1106
|
+
* `event.kind`.
|
|
1107
|
+
*/
|
|
1108
|
+
readonly message_unavailable: (event: WaIncomingUnavailableMessageEvent) => void;
|
|
1060
1109
|
/**
|
|
1061
1110
|
* Inbound `<receipt>` for an outgoing message – delivery, read, played,
|
|
1062
1111
|
* server, etc. Use this to track message ACK progression.
|
|
@@ -9,7 +9,6 @@ import { proto } from '../proto.js';
|
|
|
9
9
|
import { WA_DEFAULTS, WA_MESSAGE_TYPES } from '../protocol/constants.js';
|
|
10
10
|
import { normalizeDeviceJid } from '../protocol/jid.js';
|
|
11
11
|
import { WA_DISCONNECT_REASONS, WA_LOGOUT_REASONS } from '../protocol/stream.js';
|
|
12
|
-
import { NOOP_MESSAGE_SECRET_STORE } from '../store/noop.store.js';
|
|
13
12
|
import { buildRemoveCompanionDeviceIq } from '../transport/node/builders/device.js';
|
|
14
13
|
import { assertIqResult, queryWithContext as queryNodeWithContext } from '../transport/node/query.js';
|
|
15
14
|
import { fetchLatestWaWebVersion } from '../transport/wa-web-version-fetcher.js';
|
|
@@ -88,12 +87,6 @@ export class WaClient extends EventEmitter {
|
|
|
88
87
|
threadStore: this.stores.threads,
|
|
89
88
|
contactStore: this.stores.contacts
|
|
90
89
|
}, this.logger, this.options.writeBehind);
|
|
91
|
-
if (this.options.addons?.autoDecrypt !== false &&
|
|
92
|
-
this.stores.messageSecret === NOOP_MESSAGE_SECRET_STORE) {
|
|
93
|
-
this.logger.warn('addons.autoDecrypt is on (default) but messageSecret cache is noop – ' +
|
|
94
|
-
'addon decryption will only work if secrets are in the message store. ' +
|
|
95
|
-
'Set addons.autoDecrypt: false to silence this warning.');
|
|
96
|
-
}
|
|
97
90
|
const dependencies = buildWaClientDependencies({
|
|
98
91
|
base,
|
|
99
92
|
runtime: {
|
|
@@ -209,6 +202,7 @@ export class WaClient extends EventEmitter {
|
|
|
209
202
|
logger: this.logger,
|
|
210
203
|
writeBehind: this.writeBehind,
|
|
211
204
|
messageSecretStore: this.stores.messageSecret,
|
|
205
|
+
persistAllSecrets: this.options.addons?.persistAllSecrets === true,
|
|
212
206
|
event
|
|
213
207
|
});
|
|
214
208
|
if (this.options.addons?.autoDecrypt !== false && event.message) {
|
|
@@ -464,6 +464,7 @@ export function buildWaClientDependencies(input) {
|
|
|
464
464
|
deviceListStore: sessionStore.deviceList,
|
|
465
465
|
signalDeviceSync,
|
|
466
466
|
messageSecretStore: sessionStore.messageSecret,
|
|
467
|
+
persistAllMessageSecrets: options.addons?.persistAllSecrets === true,
|
|
467
468
|
getCurrentCredentials,
|
|
468
469
|
resolvePrivacyTokenNode: (recipientJid) => trustedContactToken.resolveTokenForMessage(recipientJid),
|
|
469
470
|
onDirectMessageSent: (recipientJid) => {
|
|
@@ -690,6 +691,7 @@ export function buildWaClientDependencies(input) {
|
|
|
690
691
|
.catch((err) => runtime.handleError(toError(err)));
|
|
691
692
|
},
|
|
692
693
|
emitNewsletterMessageUpdate: (event) => runtime.emitEvent('newsletter_message_update', event),
|
|
694
|
+
emitUnavailableMessage: (event) => runtime.emitEvent('message_unavailable', event),
|
|
693
695
|
emitUnhandledStanza: (event) => runtime.emitEvent('debug_unhandled_stanza', event)
|
|
694
696
|
};
|
|
695
697
|
const handleClientDirtyBits = (dirtyBits) => handleDirtyBits({
|
|
@@ -12,7 +12,7 @@ import { writeRandomPadMax16 } from '../../message/encode/padding.js';
|
|
|
12
12
|
import { buildBotInvokeProtoCopy, extractInvokedBotJid, genBotMsgSecret } from '../../message/kinds/bot.js';
|
|
13
13
|
import { proto } from '../../proto.js';
|
|
14
14
|
import { WA_DEFAULTS, WA_NACK_REASONS } from '../../protocol/constants.js';
|
|
15
|
-
import { canonicalizeOwnAccountJid, isBotJid, isGroupJid, isHostedDeviceJid, isLidJid, isNewsletterJid, normalizeDeviceJid, normalizeRecipientJid, parseJidFull, parseSignalAddressFromJid, signalAddressKey, toUserJid } from '../../protocol/jid.js';
|
|
15
|
+
import { canonicalizeOwnAccountJid, isBotJid, isGroupJid, isHostedDeviceJid, isLidJid, isNewsletterJid, isUserJid, normalizeDeviceJid, normalizeRecipientJid, parseJidFull, parseSignalAddressFromJid, signalAddressKey, toUserJid } from '../../protocol/jid.js';
|
|
16
16
|
import { encodeBinaryNode } from '../../transport/binary/index.js';
|
|
17
17
|
import { buildButtonAddonNode, buildDirectMessageFanoutNode, buildGroupSenderKeyMessageNode, buildMetaNode } from '../../transport/node/builders/message.js';
|
|
18
18
|
import { bytesToHex, TEXT_ENCODER } from '../../util/bytes.js';
|
|
@@ -214,7 +214,7 @@ export class WaMessageDispatchCoordinator {
|
|
|
214
214
|
if (rawSecret &&
|
|
215
215
|
rawSecret.length > 0 &&
|
|
216
216
|
sendOptions.id &&
|
|
217
|
-
needsSecretPersistence(messageWithSecret)) {
|
|
217
|
+
(this.deps.persistAllMessageSecrets || needsSecretPersistence(messageWithSecret))) {
|
|
218
218
|
const meJid = this.deps.getCurrentCredentials()?.meJid ?? '';
|
|
219
219
|
void this.deps.messageSecretStore
|
|
220
220
|
.set(sendOptions.id, { secret: rawSecret, senderJid: meJid })
|
|
@@ -275,11 +275,14 @@ export class WaMessageDispatchCoordinator {
|
|
|
275
275
|
const directRecipientJid = isGroup
|
|
276
276
|
? recipientJid
|
|
277
277
|
: await this.resolveDirectRecipientLid(toUserJid(recipientJid));
|
|
278
|
+
const peerRecipientPn = isGroup
|
|
279
|
+
? undefined
|
|
280
|
+
: await this.resolvePeerRecipientPn(toUserJid(recipientJid), directRecipientJid);
|
|
278
281
|
const publishResult = isGroup
|
|
279
282
|
? this.shouldUseGroupDirectPath(messageWithIcdc)
|
|
280
283
|
? await this.publishGroupDirectMessage(recipientJid, envelope)
|
|
281
284
|
: await this.publishGroupSenderKeyMessage(recipientJid, envelope)
|
|
282
|
-
: await this.publishDirectSignalMessageWithFanout(directRecipientJid, envelope);
|
|
285
|
+
: await this.publishDirectSignalMessageWithFanout(directRecipientJid, envelope, peerRecipientPn);
|
|
283
286
|
return upload ? { ...publishResult, upload } : publishResult;
|
|
284
287
|
}
|
|
285
288
|
/**
|
|
@@ -314,6 +317,35 @@ export class WaMessageDispatchCoordinator {
|
|
|
314
317
|
}
|
|
315
318
|
return pnUserJid;
|
|
316
319
|
}
|
|
320
|
+
/**
|
|
321
|
+
* Resolves the `peer_recipient_pn` cross-reference for a 1:1 send, or
|
|
322
|
+
* `undefined` to drop the attribute. Only set when the envelope is
|
|
323
|
+
* LID-addressed (`directRecipientJid` is a LID): the PN is the caller's own
|
|
324
|
+
* JID when they passed a PN (zapo switched it to LID for sending), else the
|
|
325
|
+
* device-list snapshot counterpart for a recipient passed in LID form.
|
|
326
|
+
* Mirrors wa-web, which sets `peer_recipient_pn = getPhoneNumber($)` for a
|
|
327
|
+
* LID destination.
|
|
328
|
+
*/
|
|
329
|
+
async resolvePeerRecipientPn(recipientUserJid, directRecipientJid) {
|
|
330
|
+
if (!isLidJid(directRecipientJid))
|
|
331
|
+
return undefined;
|
|
332
|
+
if (isUserJid(recipientUserJid))
|
|
333
|
+
return recipientUserJid;
|
|
334
|
+
try {
|
|
335
|
+
const snapshot = await this.deps.deviceListStore.findByAnyUserJid(directRecipientJid);
|
|
336
|
+
if (snapshot?.userJid && isUserJid(snapshot.userJid))
|
|
337
|
+
return snapshot.userJid;
|
|
338
|
+
if (snapshot?.altUserJid && isUserJid(snapshot.altUserJid))
|
|
339
|
+
return snapshot.altUserJid;
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
this.deps.logger.trace('peer_recipient_pn store lookup failed', {
|
|
343
|
+
lid: directRecipientJid,
|
|
344
|
+
message: toError(error).message
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
return undefined;
|
|
348
|
+
}
|
|
317
349
|
async syncSignalSession(jid, reasonIdentity = false) {
|
|
318
350
|
const address = parseSignalAddressFromJid(jid);
|
|
319
351
|
if (address.server === WA_DEFAULTS.GROUP_SERVER) {
|
|
@@ -994,7 +1026,7 @@ export class WaMessageDispatchCoordinator {
|
|
|
994
1026
|
distributionParticipants
|
|
995
1027
|
};
|
|
996
1028
|
}
|
|
997
|
-
async publishDirectSignalMessageWithFanout(recipientJid, envelope) {
|
|
1029
|
+
async publishDirectSignalMessageWithFanout(recipientJid, envelope, peerRecipientPn) {
|
|
998
1030
|
const { message, plaintext, type, edit, mediatype, sendOptions } = envelope;
|
|
999
1031
|
const meJid = this.requireCurrentMeJid('sendMessage');
|
|
1000
1032
|
const meLid = this.deps.getCurrentCredentials()?.meLid;
|
|
@@ -1157,6 +1189,7 @@ export class WaMessageDispatchCoordinator {
|
|
|
1157
1189
|
customNodes: customNodes.length > 0 ? customNodes : undefined,
|
|
1158
1190
|
mediatype,
|
|
1159
1191
|
decryptFail: envelope.decryptFail,
|
|
1192
|
+
peerRecipientPn,
|
|
1160
1193
|
additionalAttributes: sendOptions.additionalAttributes
|
|
1161
1194
|
});
|
|
1162
1195
|
const replayPayload = {
|
|
@@ -53,7 +53,7 @@ function persistContacts(writeBehind, event, nowMs) {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
export function persistIncomingMailboxEntities(options) {
|
|
56
|
-
const { logger, writeBehind, messageSecretStore, event } = options;
|
|
56
|
+
const { logger, writeBehind, messageSecretStore, persistAllSecrets, event } = options;
|
|
57
57
|
const stanzaId = event.key.id;
|
|
58
58
|
const chatJid = event.key.remoteJid;
|
|
59
59
|
if (!stanzaId || !chatJid) {
|
|
@@ -78,7 +78,7 @@ export function persistIncomingMailboxEntities(options) {
|
|
|
78
78
|
if (rawSecret &&
|
|
79
79
|
rawSecret.length > 0 &&
|
|
80
80
|
event.message &&
|
|
81
|
-
needsSecretPersistence(event.message)) {
|
|
81
|
+
(persistAllSecrets || needsSecretPersistence(event.message))) {
|
|
82
82
|
const rawSender = event.key.participant ?? event.rawNode.attrs.participant ?? event.key.remoteJid;
|
|
83
83
|
const senderJid = rawSender ? toUserJid(rawSender) : '';
|
|
84
84
|
void messageSecretStore
|
|
@@ -496,6 +496,42 @@ export async function handleIncomingMessageAck(node, options) {
|
|
|
496
496
|
await options.sendNode(ackNode);
|
|
497
497
|
return true;
|
|
498
498
|
}
|
|
499
|
+
const unavailableNode = findNodeChild(node, 'unavailable');
|
|
500
|
+
if (unavailableNode) {
|
|
501
|
+
const kind = unavailableNode.attrs.hosted === 'true'
|
|
502
|
+
? 'hosted'
|
|
503
|
+
: unavailableNode.attrs.type === 'view_once'
|
|
504
|
+
? 'view_once'
|
|
505
|
+
: 'other';
|
|
506
|
+
const senderJid = node.attrs.participant ?? node.attrs.from;
|
|
507
|
+
const sender = senderJid ? parseJidFull(senderJid) : null;
|
|
508
|
+
const { key, pushName } = buildIncomingMessageKey(node, sender ? { userJid: sender.userJid, device: sender.address.device } : null, options);
|
|
509
|
+
options.emitUnavailableMessage?.({
|
|
510
|
+
rawNode: buildIncomingEventRawNode(node),
|
|
511
|
+
key,
|
|
512
|
+
kind,
|
|
513
|
+
stanzaType: node.attrs.type,
|
|
514
|
+
offline: node.attrs.offline !== undefined,
|
|
515
|
+
timestampSeconds: parseOptionalInt(node.attrs.t),
|
|
516
|
+
pushName
|
|
517
|
+
});
|
|
518
|
+
const ackNode = buildAckNode({
|
|
519
|
+
kind: 'message',
|
|
520
|
+
node,
|
|
521
|
+
id,
|
|
522
|
+
to: from,
|
|
523
|
+
from: options.getMeJid?.()
|
|
524
|
+
});
|
|
525
|
+
options.logger.trace('acking unavailable incoming message', {
|
|
526
|
+
id,
|
|
527
|
+
to: from,
|
|
528
|
+
type: ackNode.attrs.type,
|
|
529
|
+
participant: ackNode.attrs.participant,
|
|
530
|
+
unavailableKind: kind
|
|
531
|
+
});
|
|
532
|
+
await options.sendNode(ackNode);
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
499
535
|
if (!shouldSendStandardReceipt) {
|
|
500
536
|
return true;
|
|
501
537
|
}
|
|
@@ -32,6 +32,9 @@ function buildMessageAttrs(input) {
|
|
|
32
32
|
if (input.addressingMode) {
|
|
33
33
|
attrs.addressing_mode = input.addressingMode;
|
|
34
34
|
}
|
|
35
|
+
if (input.peerRecipientPn) {
|
|
36
|
+
attrs.peer_recipient_pn = input.peerRecipientPn;
|
|
37
|
+
}
|
|
35
38
|
if (input.additionalAttributes) {
|
|
36
39
|
Object.assign(attrs, input.additionalAttributes);
|
|
37
40
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { WaClient } from './client';
|
|
2
2
|
export type { WaClientEventMap, WaClientOptions, WaClientProxyOptions, WaDownloadMediaOptions, WaHistorySyncChunkEvent, WaHistorySyncOptions, WaWriteBehindOptions } from './client/types';
|
|
3
3
|
export type { WaMessageCoordinator } from './client/coordinators/WaMessageCoordinator';
|
|
4
|
-
export type { WaAccountTakeoverNoticeEvent, WaAppStateMutationEvent, WaAppStateMutationSource, WaBusinessEvent, WaBusinessEventAction, WaBusinessProfileResult, WaConnectionEvent, WaGroupEvent, WaGroupEventAction, WaGroupEventLinkedGroup, WaGroupEventMembershipRequest, WaGroupEventParticipant, WaGroupEventSubgroupSuggestion, WaIgnoreKey, WaIgnoreKeyContext, WaIgnoreKeyPredicate, WaIgnoreStanzaKind, WaIncomingAddonEvent, WaIncomingBaseEvent, WaIncomingBotChunkEvent, WaIncomingCallEvent, WaIncomingChatstateEvent, WaIncomingErrorStanzaEvent, WaIncomingFailureEvent, WaIncomingMessageEvent, WaIncomingMessageKey, WaIncomingNewsletterEvent, WaIncomingNewsletterMessageUpdateEvent, WaIncomingNodeHandler, WaIncomingNodeHandlerRegistration, WaIncomingNotificationEvent, WaIncomingPresenceEvent, WaIncomingProtocolMessageEvent, WaIncomingReceiptEvent, WaIncomingStanzaFilter, WaIncomingUnhandledStanzaEvent, WaMexLidChangeEvent, WaMexMessageCappingEvent, WaMexMessageCappingStatus, WaMexNotificationEvent, WaMexNotificationGraphQlError, WaMexNotificationOperationName, WaMexNotificationUnknownEvent, WaMexOwnUsernameSyncEvent, WaMexTextStatusUpdateEvent, WaMexTextStatusUpdateHintEvent, WaMexUsernameDeleteEvent, WaMexUsernameSetEvent, WaMexUsernameUpdateHintEvent, WaOfflineResumeEvent, WaPictureEvent, WaPictureEventAction, WaPrivacyTokenUpdateEvent, WaReceiptStatus, WaRegistrationCodeEvent, WaSendMessageOptions, WaVerifiedNameResult, WaAddonKind, WaNewsletterEventAction, WaNewsletterMessageUpdate, WaNewsletterPollVoteEntry, WaNewsletterReactionEntry } from './client/types';
|
|
4
|
+
export type { WaAccountTakeoverNoticeEvent, WaAppStateMutationEvent, WaAppStateMutationSource, WaBusinessEvent, WaBusinessEventAction, WaBusinessProfileResult, WaConnectionEvent, WaGroupEvent, WaGroupEventAction, WaGroupEventLinkedGroup, WaGroupEventMembershipRequest, WaGroupEventParticipant, WaGroupEventSubgroupSuggestion, WaIgnoreKey, WaIgnoreKeyContext, WaIgnoreKeyPredicate, WaIgnoreStanzaKind, WaIncomingAddonEvent, WaIncomingBaseEvent, WaIncomingBotChunkEvent, WaIncomingCallEvent, WaIncomingChatstateEvent, WaIncomingErrorStanzaEvent, WaIncomingFailureEvent, WaIncomingMessageEvent, WaIncomingMessageKey, WaIncomingNewsletterEvent, WaIncomingNewsletterMessageUpdateEvent, WaIncomingNodeHandler, WaIncomingNodeHandlerRegistration, WaIncomingNotificationEvent, WaIncomingPresenceEvent, WaIncomingProtocolMessageEvent, WaIncomingReceiptEvent, WaIncomingStanzaFilter, WaIncomingUnavailableMessageEvent, WaIncomingUnhandledStanzaEvent, WaMexLidChangeEvent, WaMexMessageCappingEvent, WaMexMessageCappingStatus, WaMexNotificationEvent, WaMexNotificationGraphQlError, WaMexNotificationOperationName, WaMexNotificationUnknownEvent, WaMexOwnUsernameSyncEvent, WaMexTextStatusUpdateEvent, WaMexTextStatusUpdateHintEvent, WaMexUsernameDeleteEvent, WaMexUsernameSetEvent, WaMexUsernameUpdateHintEvent, WaOfflineResumeEvent, WaPictureEvent, WaPictureEventAction, WaPrivacyTokenUpdateEvent, WaReceiptStatus, WaRegistrationCodeEvent, WaSendMessageOptions, WaUnavailableMessageKind, WaVerifiedNameResult, WaAddonKind, WaNewsletterEventAction, WaNewsletterMessageUpdate, WaNewsletterPollVoteEntry, WaNewsletterReactionEntry } from './client/types';
|
|
5
5
|
export type { WaAppStateMutationCoordinator, WaBroadcastListParticipant, WaSetBroadcastListInput, WaSetStatusPrivacyInput } from './client/coordinators/WaAppStateMutationCoordinator';
|
|
6
6
|
export type { WaBotCoordinator, WaBotInfo, WaBotPosingAsProfessional, WaBotProfileCommand, WaBotProfilePrompt, WaBotProfileResult, WaBotPromptOptions, WaGetBotProfileOptions } from './client/coordinators/WaBotCoordinator';
|
|
7
7
|
export type { WaBroadcastListCoordinator, WaSendBroadcastListMessageInput } from './client/coordinators/WaBroadcastListCoordinator';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WaIncomingMessageEvent, WaIncomingNewsletterMessageUpdateEvent, WaIncomingUnhandledStanzaEvent } from '../../client/types';
|
|
1
|
+
import type { WaIncomingMessageEvent, WaIncomingNewsletterMessageUpdateEvent, WaIncomingUnavailableMessageEvent, WaIncomingUnhandledStanzaEvent } from '../../client/types';
|
|
2
2
|
import type { Logger } from '../../infra/log/types';
|
|
3
3
|
import { proto } from '../../proto';
|
|
4
4
|
import type { WaRetryDecryptFailureContext } from '../../retry/types';
|
|
@@ -15,6 +15,7 @@ interface WaIncomingMessageAckHandlerOptions {
|
|
|
15
15
|
readonly onDecryptFailure?: (context: WaRetryDecryptFailureContext, error: unknown) => Promise<boolean>;
|
|
16
16
|
readonly emitIncomingMessage?: (event: WaIncomingMessageEvent) => void;
|
|
17
17
|
readonly emitNewsletterMessageUpdate?: (event: WaIncomingNewsletterMessageUpdateEvent) => void;
|
|
18
|
+
readonly emitUnavailableMessage?: (event: WaIncomingUnavailableMessageEvent) => void;
|
|
18
19
|
readonly emitUnhandledStanza?: (event: WaIncomingUnhandledStanzaEvent) => void;
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
@@ -500,6 +500,42 @@ async function handleIncomingMessageAck(node, options) {
|
|
|
500
500
|
await options.sendNode(ackNode);
|
|
501
501
|
return true;
|
|
502
502
|
}
|
|
503
|
+
const unavailableNode = (0, helpers_1.findNodeChild)(node, 'unavailable');
|
|
504
|
+
if (unavailableNode) {
|
|
505
|
+
const kind = unavailableNode.attrs.hosted === 'true'
|
|
506
|
+
? 'hosted'
|
|
507
|
+
: unavailableNode.attrs.type === 'view_once'
|
|
508
|
+
? 'view_once'
|
|
509
|
+
: 'other';
|
|
510
|
+
const senderJid = node.attrs.participant ?? node.attrs.from;
|
|
511
|
+
const sender = senderJid ? (0, jid_1.parseJidFull)(senderJid) : null;
|
|
512
|
+
const { key, pushName } = buildIncomingMessageKey(node, sender ? { userJid: sender.userJid, device: sender.address.device } : null, options);
|
|
513
|
+
options.emitUnavailableMessage?.({
|
|
514
|
+
rawNode: buildIncomingEventRawNode(node),
|
|
515
|
+
key,
|
|
516
|
+
kind,
|
|
517
|
+
stanzaType: node.attrs.type,
|
|
518
|
+
offline: node.attrs.offline !== undefined,
|
|
519
|
+
timestampSeconds: (0, primitives_1.parseOptionalInt)(node.attrs.t),
|
|
520
|
+
pushName
|
|
521
|
+
});
|
|
522
|
+
const ackNode = (0, global_1.buildAckNode)({
|
|
523
|
+
kind: 'message',
|
|
524
|
+
node,
|
|
525
|
+
id,
|
|
526
|
+
to: from,
|
|
527
|
+
from: options.getMeJid?.()
|
|
528
|
+
});
|
|
529
|
+
options.logger.trace('acking unavailable incoming message', {
|
|
530
|
+
id,
|
|
531
|
+
to: from,
|
|
532
|
+
type: ackNode.attrs.type,
|
|
533
|
+
participant: ackNode.attrs.participant,
|
|
534
|
+
unavailableKind: kind
|
|
535
|
+
});
|
|
536
|
+
await options.sendNode(ackNode);
|
|
537
|
+
return true;
|
|
538
|
+
}
|
|
503
539
|
if (!shouldSendStandardReceipt) {
|
|
504
540
|
return true;
|
|
505
541
|
}
|
|
@@ -15,6 +15,7 @@ type DirectMessageFanoutInput = {
|
|
|
15
15
|
readonly customNodes?: readonly BinaryNode[];
|
|
16
16
|
readonly mediatype?: string;
|
|
17
17
|
readonly decryptFail?: string;
|
|
18
|
+
readonly peerRecipientPn?: string;
|
|
18
19
|
readonly additionalAttributes?: Readonly<Record<string, string>>;
|
|
19
20
|
};
|
|
20
21
|
type GroupMessageFanoutInput = DirectMessageFanoutInput & {
|
|
@@ -39,6 +39,9 @@ function buildMessageAttrs(input) {
|
|
|
39
39
|
if (input.addressingMode) {
|
|
40
40
|
attrs.addressing_mode = input.addressingMode;
|
|
41
41
|
}
|
|
42
|
+
if (input.peerRecipientPn) {
|
|
43
|
+
attrs.peer_recipient_pn = input.peerRecipientPn;
|
|
44
|
+
}
|
|
42
45
|
if (input.additionalAttributes) {
|
|
43
46
|
Object.assign(attrs, input.additionalAttributes);
|
|
44
47
|
}
|