zapo-js 1.7.1 → 1.8.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 -0
- package/dist/client/WaClientFactory.d.ts +2 -0
- package/dist/client/WaClientFactory.js +26 -2
- package/dist/client/coordinators/WaIncomingNodeCoordinator.d.ts +1 -0
- package/dist/client/coordinators/WaIncomingNodeCoordinator.js +2 -1
- package/dist/client/coordinators/WaMessageCoordinator.d.ts +50 -0
- package/dist/client/coordinators/WaMessageCoordinator.js +39 -9
- package/dist/client/coordinators/WaMessageDispatchCoordinator.d.ts +8 -0
- package/dist/client/coordinators/WaMessageDispatchCoordinator.js +39 -2
- package/dist/client/coordinators/WaMobileCoordinator.js +1 -2
- package/dist/client/coordinators/WaPrivacyCoordinator.d.ts +18 -2
- package/dist/client/coordinators/WaPrivacyCoordinator.js +36 -3
- package/dist/client/coordinators/WaProfileCoordinator.d.ts +36 -4
- package/dist/client/coordinators/WaProfileCoordinator.js +80 -2
- package/dist/client/events/business.js +1 -1
- package/dist/client/events/incoming.d.ts +2 -0
- package/dist/client/events/incoming.js +7 -0
- package/dist/client/events/privacy.d.ts +9 -0
- package/dist/client/events/privacy.js +30 -26
- package/dist/client/events/username.d.ts +8 -0
- package/dist/client/events/username.js +26 -0
- package/dist/client/persistence/WriteBehindPersistence.js +1 -0
- package/dist/client/persistence/group-history.js +57 -45
- package/dist/client/persistence/history-blob.d.ts +21 -0
- package/dist/client/persistence/history-blob.js +41 -0
- package/dist/client/persistence/history-sync.d.ts +25 -30
- package/dist/client/persistence/history-sync.js +297 -267
- package/dist/client/persistence/mailbox.js +1 -1
- package/dist/client/types.d.ts +45 -0
- package/dist/esm/client/WaClient.js +1 -0
- package/dist/esm/client/WaClientFactory.js +26 -2
- package/dist/esm/client/coordinators/WaIncomingNodeCoordinator.js +2 -1
- package/dist/esm/client/coordinators/WaMessageCoordinator.js +40 -10
- package/dist/esm/client/coordinators/WaMessageDispatchCoordinator.js +39 -2
- package/dist/esm/client/coordinators/WaMobileCoordinator.js +2 -3
- package/dist/esm/client/coordinators/WaPrivacyCoordinator.js +36 -3
- package/dist/esm/client/coordinators/WaProfileCoordinator.js +81 -3
- package/dist/esm/client/events/business.js +2 -2
- package/dist/esm/client/events/incoming.js +7 -0
- package/dist/esm/client/events/privacy.js +30 -26
- package/dist/esm/client/events/username.js +23 -0
- package/dist/esm/client/persistence/WriteBehindPersistence.js +1 -0
- package/dist/esm/client/persistence/group-history.js +59 -47
- package/dist/esm/client/persistence/history-blob.js +40 -0
- package/dist/esm/client/persistence/history-sync.js +299 -268
- package/dist/esm/client/persistence/mailbox.js +1 -1
- package/dist/esm/media/crypto/media-retry.js +40 -0
- package/dist/esm/message/crypto/addon-crypto.js +114 -0
- package/dist/esm/message/encode/content.js +3 -1
- package/dist/esm/message/kinds/group-history.js +32 -0
- package/dist/esm/message/primitives/incoming.js +48 -6
- package/dist/esm/message/primitives/media-retry.js +198 -0
- package/dist/esm/protocol/constants.js +1 -0
- package/dist/esm/protocol/defaults.js +8 -0
- package/dist/esm/protocol/notification.js +1 -0
- package/dist/esm/protocol/username.js +127 -0
- package/dist/esm/store/memory/contact.store.js +1 -0
- package/dist/esm/transport/index.js +1 -1
- package/dist/esm/transport/node/builders/privacy.js +32 -9
- package/dist/esm/transport/node/builders/profile.js +34 -1
- package/dist/esm/transport/node/builders/usync.js +1 -1
- package/dist/esm/transport/node/helpers.js +9 -0
- package/dist/esm/transport/noise/WaMobileClientPayload.js +3 -1
- package/dist/esm/util/proto-stream.js +325 -0
- package/dist/index.d.ts +4 -3
- package/dist/media/crypto/media-retry.d.ts +29 -0
- package/dist/media/crypto/media-retry.js +45 -0
- package/dist/message/crypto/addon-crypto.d.ts +49 -0
- package/dist/message/crypto/addon-crypto.js +118 -0
- package/dist/message/encode/content.js +3 -1
- package/dist/message/kinds/group-history.d.ts +24 -0
- package/dist/message/kinds/group-history.js +34 -0
- package/dist/message/primitives/incoming.d.ts +2 -1
- package/dist/message/primitives/incoming.js +48 -6
- package/dist/message/primitives/media-retry.d.ts +43 -0
- package/dist/message/primitives/media-retry.js +202 -0
- package/dist/protocol/constants.d.ts +2 -0
- package/dist/protocol/constants.js +12 -2
- package/dist/protocol/defaults.d.ts +8 -0
- package/dist/protocol/defaults.js +8 -0
- package/dist/protocol/notification.d.ts +1 -0
- package/dist/protocol/notification.js +1 -0
- package/dist/protocol/username.d.ts +45 -0
- package/dist/protocol/username.js +137 -0
- package/dist/store/contracts/contact.store.d.ts +2 -0
- package/dist/store/memory/contact.store.js +1 -0
- package/dist/transport/index.d.ts +1 -1
- package/dist/transport/index.js +2 -1
- package/dist/transport/node/builders/privacy.d.ts +12 -7
- package/dist/transport/node/builders/privacy.js +32 -9
- package/dist/transport/node/builders/profile.d.ts +16 -0
- package/dist/transport/node/builders/profile.js +35 -0
- package/dist/transport/node/builders/usync.d.ts +2 -1
- package/dist/transport/node/builders/usync.js +1 -1
- package/dist/transport/node/helpers.d.ts +7 -0
- package/dist/transport/node/helpers.js +10 -0
- package/dist/transport/noise/WaMobileClientPayload.d.ts +1 -0
- package/dist/transport/noise/WaMobileClientPayload.js +3 -1
- package/dist/util/proto-stream.d.ts +48 -0
- package/dist/util/proto-stream.js +329 -0
- package/package.json +1 -1
package/dist/client/WaClient.js
CHANGED
|
@@ -61,6 +61,7 @@ class WaClientImpl extends node_events_1.EventEmitter {
|
|
|
61
61
|
syncAppState: () => this.deps.chatCoordinator.sync().then(() => { }),
|
|
62
62
|
syncAppStateWithOptions: (syncOptions) => this.deps.chatCoordinator.sync(syncOptions),
|
|
63
63
|
emitEvent: this.emit.bind(this),
|
|
64
|
+
hasEventListener: (event) => this.listenerCount(event) > 0,
|
|
64
65
|
handleIncomingMessageEvent: this.handleIncomingMessageEvent.bind(this),
|
|
65
66
|
handleError: this.handleError.bind(this),
|
|
66
67
|
handleIncomingFrame: this.handleIncomingFrame.bind(this),
|
|
@@ -60,6 +60,8 @@ interface WaClientBuildRuntime {
|
|
|
60
60
|
readonly syncAppState: () => Promise<void>;
|
|
61
61
|
readonly syncAppStateWithOptions: (options?: WaAppStateSyncOptions) => Promise<WaAppStateSyncResult>;
|
|
62
62
|
readonly emitEvent: <K extends keyof WaClientEventMap>(event: K, ...args: Parameters<WaClientEventMap[K]>) => void;
|
|
63
|
+
/** Whether anything is subscribed, for events whose payload costs something to build. */
|
|
64
|
+
readonly hasEventListener: (event: keyof WaClientEventMap) => boolean;
|
|
63
65
|
readonly handleIncomingMessageEvent: (event: WaIncomingMessageEvent) => Promise<void>;
|
|
64
66
|
readonly handleError: (error: Error) => void;
|
|
65
67
|
readonly handleIncomingFrame: (frame: Uint8Array) => Promise<void>;
|
|
@@ -32,6 +32,7 @@ const devices_1 = require("./events/devices");
|
|
|
32
32
|
const dirty_1 = require("./events/dirty");
|
|
33
33
|
const identity_1 = require("./events/identity");
|
|
34
34
|
const privacy_token_1 = require("./events/privacy-token");
|
|
35
|
+
const username_1 = require("./events/username");
|
|
35
36
|
const fanout_1 = require("./messaging/fanout");
|
|
36
37
|
const group_metadata_1 = require("./messaging/group-metadata");
|
|
37
38
|
const key_protocol_1 = require("./messaging/key-protocol");
|
|
@@ -41,6 +42,7 @@ const WaMediaTransferClient_1 = require("../media/transfer/WaMediaTransferClient
|
|
|
41
42
|
const fetcher_1 = require("../message/addons/link-preview/fetcher");
|
|
42
43
|
const newsletter_1 = require("../message/kinds/newsletter");
|
|
43
44
|
const incoming_1 = require("../message/primitives/incoming");
|
|
45
|
+
const media_retry_1 = require("../message/primitives/media-retry");
|
|
44
46
|
const peer_data_operation_1 = require("../message/primitives/peer-data-operation");
|
|
45
47
|
const WaMessageClient_1 = require("../message/WaMessageClient");
|
|
46
48
|
const constants_1 = require("../protocol/constants");
|
|
@@ -122,7 +124,7 @@ function resolveWaClientBase(options, logger) {
|
|
|
122
124
|
};
|
|
123
125
|
}
|
|
124
126
|
function createIncomingNodeRuntime(input) {
|
|
125
|
-
const { logger, emitEvent, authClient, connectionManager, nodeOrchestrator, streamControl, mediaMessageBuildOptions, retryCoordinator, messageDispatch, sendNode, syncAppState, disconnect, clearStoredCredentials, getCurrentCredentials, handleClientDirtyBits, incomingMessageAckOptions } = input;
|
|
127
|
+
const { logger, emitEvent, authClient, connectionManager, nodeOrchestrator, streamControl, mediaMessageBuildOptions, retryCoordinator, mediaRetryRequester, messageDispatch, sendNode, syncAppState, disconnect, clearStoredCredentials, getCurrentCredentials, handleClientDirtyBits, incomingMessageAckOptions } = input;
|
|
126
128
|
return {
|
|
127
129
|
handleStreamControlResult: streamControl.handleStreamControlResult,
|
|
128
130
|
persistSuccessAttributes: (attributes) => authClient.persistSuccessAttributes(attributes, { countsAsLogin: true }),
|
|
@@ -141,6 +143,7 @@ function createIncomingNodeRuntime(input) {
|
|
|
141
143
|
handleIncomingMessageNode: (node) => (0, incoming_1.handleIncomingMessageAck)(node, incomingMessageAckOptions),
|
|
142
144
|
sendNode,
|
|
143
145
|
handleIncomingRetryReceipt: (node) => retryCoordinator.handleIncomingRetryReceipt(node),
|
|
146
|
+
handleMediaRetryNotification: (node) => mediaRetryRequester.handleNotification(node),
|
|
144
147
|
trackOutboundReceipt: (node) => retryCoordinator.trackOutboundReceipt(node),
|
|
145
148
|
emitIncomingReceipt: (event) => emitEvent('receipt', event),
|
|
146
149
|
emitIncomingPresence: (event) => emitEvent('presence', event),
|
|
@@ -382,6 +385,8 @@ function buildWaClientDependencies(input) {
|
|
|
382
385
|
});
|
|
383
386
|
const privacyCoordinator = (0, WaPrivacyCoordinator_1.createPrivacyCoordinator)({
|
|
384
387
|
logger,
|
|
388
|
+
contactStore: sessionStore.contacts,
|
|
389
|
+
isUsernamePrivacyListIdentifierEnabled: () => abPropsCoordinator.getConfigValue('username_contact_privacy_setting_allow_uncontact_set_enable'),
|
|
385
390
|
queryWithContext: runtime.queryWithContext,
|
|
386
391
|
resolveUserJidPair: (userJid) => signalDeviceSync.resolveUserJidPair(userJid),
|
|
387
392
|
getSelfLid: () => getCurrentCredentials()?.meLid ?? null,
|
|
@@ -514,9 +519,15 @@ function buildWaClientDependencies(input) {
|
|
|
514
519
|
generateOutgoingMessageId: () => messageDispatch.generateOutgoingMessageId(),
|
|
515
520
|
subscribeToProtocolMessage: runtime.subscribeProtocolMessage
|
|
516
521
|
});
|
|
522
|
+
const mediaRetryRequester = (0, media_retry_1.createMediaRetryRequester)({
|
|
523
|
+
logger,
|
|
524
|
+
sendNode: (node) => nodeOrchestrator.sendNode(node, false),
|
|
525
|
+
getCurrentCredentials
|
|
526
|
+
});
|
|
517
527
|
const messageCoordinator = new WaMessageCoordinator_1.WaMessageCoordinator({
|
|
518
528
|
messageDispatch,
|
|
519
529
|
mediaTransfer,
|
|
530
|
+
mediaRetry: mediaRetryRequester,
|
|
520
531
|
mediaUploadOptions: mediaMessageBuildOptions,
|
|
521
532
|
logger,
|
|
522
533
|
messageStore: sessionStore.messages,
|
|
@@ -719,7 +730,15 @@ function buildWaClientDependencies(input) {
|
|
|
719
730
|
},
|
|
720
731
|
emitNewsletterMessageUpdate: (event) => runtime.emitEvent('newsletter_message_update', event),
|
|
721
732
|
emitUnavailableMessage: (event) => runtime.emitEvent('message_unavailable', event),
|
|
722
|
-
emitUnhandledStanza: (event) => runtime.emitEvent('debug_unhandled_stanza', event)
|
|
733
|
+
emitUnhandledStanza: (event) => runtime.emitEvent('debug_unhandled_stanza', event),
|
|
734
|
+
emitDecryptedPayload: (build) => {
|
|
735
|
+
// Built only if someone is subscribed: the payload carries a copy of
|
|
736
|
+
// the plaintext and a redacted node, and this runs per `<enc>`.
|
|
737
|
+
if (!runtime.hasEventListener('debug_decrypted_payload')) {
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
runtime.emitEvent('debug_decrypted_payload', build());
|
|
741
|
+
}
|
|
723
742
|
};
|
|
724
743
|
const handleClientDirtyBits = (dirtyBits) => (0, dirty_1.handleDirtyBits)({
|
|
725
744
|
logger,
|
|
@@ -752,6 +771,7 @@ function buildWaClientDependencies(input) {
|
|
|
752
771
|
streamControl,
|
|
753
772
|
mediaMessageBuildOptions,
|
|
754
773
|
retryCoordinator,
|
|
774
|
+
mediaRetryRequester,
|
|
755
775
|
messageDispatch,
|
|
756
776
|
sendNode: runtime.sendNode,
|
|
757
777
|
syncAppState: runtime.syncAppState,
|
|
@@ -928,6 +948,10 @@ function buildWaClientDependencies(input) {
|
|
|
928
948
|
if ((0, helpers_1.findNodeChild)(node, constants_1.WA_NODE_TAGS.PRIVACY)) {
|
|
929
949
|
privacyCoordinator.scheduleAccountSyncRefresh();
|
|
930
950
|
}
|
|
951
|
+
const usernameEvent = (0, username_1.parseOwnUsernameNotification)(node);
|
|
952
|
+
if (usernameEvent) {
|
|
953
|
+
runtime.emitEvent('own_username', usernameEvent);
|
|
954
|
+
}
|
|
931
955
|
return false;
|
|
932
956
|
}
|
|
933
957
|
});
|
|
@@ -22,6 +22,7 @@ interface WaIncomingNodeRuntime {
|
|
|
22
22
|
readonly handleIncomingMessageNode: (node: BinaryNode) => Promise<boolean>;
|
|
23
23
|
readonly sendNode: (node: BinaryNode) => Promise<void>;
|
|
24
24
|
readonly handleIncomingRetryReceipt: (node: BinaryNode) => Promise<void>;
|
|
25
|
+
readonly handleMediaRetryNotification: (node: BinaryNode) => void;
|
|
25
26
|
readonly trackOutboundReceipt: (node: BinaryNode) => Promise<void>;
|
|
26
27
|
readonly emitIncomingReceipt: (event: WaIncomingReceiptEvent) => void;
|
|
27
28
|
readonly emitIncomingPresence: (event: WaIncomingPresenceEvent) => void;
|
|
@@ -238,7 +238,8 @@ class WaIncomingNodeCoordinator {
|
|
|
238
238
|
emitIncomingNotification: runtime.emitIncomingNotification,
|
|
239
239
|
emitMexNotification: runtime.emitMexNotification,
|
|
240
240
|
emitUnhandledStanza: runtime.emitUnhandledIncomingNode,
|
|
241
|
-
syncAppState: runtime.syncAppState
|
|
241
|
+
syncAppState: runtime.syncAppState,
|
|
242
|
+
handleMediaRetryNotification: runtime.handleMediaRetryNotification
|
|
242
243
|
})
|
|
243
244
|
});
|
|
244
245
|
this.registerIncomingHandler({
|
|
@@ -8,6 +8,7 @@ import type { WaDownloadMediaOptions, WaIncomingAddonEvent, WaIncomingMessageEve
|
|
|
8
8
|
import type { Logger } from '../../infra/log/types';
|
|
9
9
|
import type { WaMediaTransferClient } from '../../media/transfer/WaMediaTransferClient';
|
|
10
10
|
import type { MediaKind } from '../../media/types';
|
|
11
|
+
import type { WaMediaRetryRequest, WaMediaRetryRequester, WaMediaRetryResult } from '../../message/primitives/media-retry';
|
|
11
12
|
import type { PeerDataOperationRequester } from '../../message/primitives/peer-data-operation';
|
|
12
13
|
import type { WaMessagePublishResult, WaSendMessageContent, WaSendReceiptEventOptions, WaSendReceiptOptions } from '../../message/types';
|
|
13
14
|
import { type Proto } from '../../proto';
|
|
@@ -17,6 +18,8 @@ import { type WaMexQuerySocket } from '../../transport/node/mex/client';
|
|
|
17
18
|
export interface WaMessageCoordinatorDeps {
|
|
18
19
|
readonly messageDispatch: WaMessageDispatchCoordinator;
|
|
19
20
|
readonly mediaTransfer: WaMediaTransferClient;
|
|
21
|
+
/** Backs {@link WaMessageCoordinator.requestMediaReupload}. */
|
|
22
|
+
readonly mediaRetry: WaMediaRetryRequester;
|
|
20
23
|
/** Media upload wiring shared with the send path; backs {@link WaMessageCoordinator.upload}. */
|
|
21
24
|
readonly mediaUploadOptions: WaMediaMessageOptions;
|
|
22
25
|
readonly logger: Logger;
|
|
@@ -163,6 +166,7 @@ export interface WaMediaUploadResult extends UploadResult {
|
|
|
163
166
|
export declare class WaMessageCoordinator {
|
|
164
167
|
private readonly messageDispatch;
|
|
165
168
|
private readonly mediaTransfer;
|
|
169
|
+
private readonly mediaRetry;
|
|
166
170
|
private readonly mediaUploadOptions;
|
|
167
171
|
private readonly logger;
|
|
168
172
|
private readonly messageStore;
|
|
@@ -373,6 +377,52 @@ export declare class WaMessageCoordinator {
|
|
|
373
377
|
* `options.maxBytes`.
|
|
374
378
|
*/
|
|
375
379
|
downloadBytes(source: WaIncomingMessageEvent | Proto.IMessage, options?: WaDownloadMediaOptions): Promise<Uint8Array>;
|
|
380
|
+
/**
|
|
381
|
+
* Asks the sender's primary device to re-upload a message's media, for when
|
|
382
|
+
* the CDN answers `404`/`410` because the blob expired - typically old
|
|
383
|
+
* media surfaced by a history sync. Sends a `server-error` receipt carrying
|
|
384
|
+
* the sealed request and resolves once the server answers with a
|
|
385
|
+
* `mediaretry` notification, usually within a couple of seconds.
|
|
386
|
+
*
|
|
387
|
+
* On `result: 'success'` only the `directPath` changes - the media key,
|
|
388
|
+
* hashes, and length of the original message stay valid, so patch the path
|
|
389
|
+
* into the message and download again. A second call for a message already
|
|
390
|
+
* in flight joins the first request instead of sending another receipt.
|
|
391
|
+
*
|
|
392
|
+
* The other three `result` values are answers too, not thrown errors:
|
|
393
|
+
* `not_found` means the sender no longer holds the file and nothing can
|
|
394
|
+
* recover it, `decryption_error` that it could not open its own copy, and
|
|
395
|
+
* `general_error` anything else including a result code this version does
|
|
396
|
+
* not recognize.
|
|
397
|
+
*
|
|
398
|
+
* Requires the message's media key, so it only works for media you received
|
|
399
|
+
* or sent, never for a bare message id. Newsletter messages are rejected -
|
|
400
|
+
* channel media has no per-message key to seal the request with. Pass an
|
|
401
|
+
* explicit `WaMediaRetryRequest` instead of an event when you hold the
|
|
402
|
+
* message id, chat jid, and media key but not the decoded message.
|
|
403
|
+
*
|
|
404
|
+
* @throws when the message carries no downloadable media, is a newsletter
|
|
405
|
+
* message, the session is not paired, the stanza cannot be sent, or no
|
|
406
|
+
* notification arrives before `options.timeoutMs`. It does **not** throw on
|
|
407
|
+
* a `not_found` / `general_error` answer - check `result`.
|
|
408
|
+
* @example
|
|
409
|
+
* ```ts
|
|
410
|
+
* const image = event.message?.imageMessage
|
|
411
|
+
* try {
|
|
412
|
+
* return await client.message.downloadBytes(event)
|
|
413
|
+
* } catch {
|
|
414
|
+
* const retry = await client.message.requestMediaReupload(event)
|
|
415
|
+
* if (retry.result !== 'success') throw new Error(`reupload ${retry.result}`)
|
|
416
|
+
* return await client.message.downloadBytes({
|
|
417
|
+
* imageMessage: { ...image, directPath: retry.directPath }
|
|
418
|
+
* })
|
|
419
|
+
* }
|
|
420
|
+
* ```
|
|
421
|
+
*/
|
|
422
|
+
requestMediaReupload(event: WaIncomingMessageEvent, options?: {
|
|
423
|
+
readonly timeoutMs?: number;
|
|
424
|
+
}): Promise<WaMediaRetryResult>;
|
|
425
|
+
requestMediaReupload(request: WaMediaRetryRequest): Promise<WaMediaRetryResult>;
|
|
376
426
|
/**
|
|
377
427
|
* Attempts to decrypt an addon payload (poll vote, reaction, edit, ...)
|
|
378
428
|
* attached to `event` and, on success, emits a typed
|
|
@@ -9,6 +9,7 @@ const messages_1 = require("../messaging/messages");
|
|
|
9
9
|
const constants_1 = require("../../media/constants");
|
|
10
10
|
const addon_crypto_1 = require("../../message/crypto/addon-crypto");
|
|
11
11
|
const content_1 = require("../../message/encode/content");
|
|
12
|
+
const media_payload_1 = require("../../message/encode/media-payload");
|
|
12
13
|
const group_history_1 = require("../../message/kinds/group-history");
|
|
13
14
|
const _proto_1 = require("../../proto");
|
|
14
15
|
const jid_1 = require("../../protocol/jid");
|
|
@@ -79,6 +80,7 @@ class WaMessageCoordinator {
|
|
|
79
80
|
constructor(deps) {
|
|
80
81
|
this.messageDispatch = deps.messageDispatch;
|
|
81
82
|
this.mediaTransfer = deps.mediaTransfer;
|
|
83
|
+
this.mediaRetry = deps.mediaRetry;
|
|
82
84
|
this.mediaUploadOptions = deps.mediaUploadOptions;
|
|
83
85
|
this.logger = deps.logger;
|
|
84
86
|
this.messageStore = deps.messageStore;
|
|
@@ -486,6 +488,30 @@ class WaMessageCoordinator {
|
|
|
486
488
|
const stream = await this.download(source, options);
|
|
487
489
|
return (0, bytes_1.readAllBytes)(stream, { maxBytes: options.maxBytes });
|
|
488
490
|
}
|
|
491
|
+
requestMediaReupload(source, options) {
|
|
492
|
+
if (!('key' in source)) {
|
|
493
|
+
return this.mediaRetry.request(source);
|
|
494
|
+
}
|
|
495
|
+
const key = source.key;
|
|
496
|
+
if (!key.id || !key.remoteJid) {
|
|
497
|
+
throw new Error('requestMediaReupload event is missing key.remoteJid or key.id');
|
|
498
|
+
}
|
|
499
|
+
if (key.isNewsletter) {
|
|
500
|
+
throw new Error('requestMediaReupload is not supported on newsletter messages');
|
|
501
|
+
}
|
|
502
|
+
const payload = (0, media_payload_1.resolveMediaPayload)(source.message);
|
|
503
|
+
if (!payload) {
|
|
504
|
+
throw new Error('message has no downloadable media');
|
|
505
|
+
}
|
|
506
|
+
return this.mediaRetry.request({
|
|
507
|
+
messageId: key.id,
|
|
508
|
+
chatJid: key.remoteJid,
|
|
509
|
+
mediaKey: payload.mediaKey,
|
|
510
|
+
fromMe: key.fromMe,
|
|
511
|
+
participant: key.participant,
|
|
512
|
+
timeoutMs: options?.timeoutMs
|
|
513
|
+
});
|
|
514
|
+
}
|
|
489
515
|
/**
|
|
490
516
|
* Attempts to decrypt an addon payload (poll vote, reaction, edit, ...)
|
|
491
517
|
* attached to `event` and, on success, emits a typed
|
|
@@ -529,19 +555,23 @@ class WaMessageCoordinator {
|
|
|
529
555
|
}
|
|
530
556
|
return;
|
|
531
557
|
}
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
558
|
+
const modificationSenderRaw = event.key.participant ?? event.key.remoteJid;
|
|
559
|
+
if (!modificationSenderRaw)
|
|
560
|
+
return;
|
|
561
|
+
const modificationSenderCandidates = (0, addon_crypto_1.collectUniqueUserJids)(event.key.fromMe ? event.rawNode.attrs.from : undefined, modificationSenderRaw, event.key.participantAlt, event.key.remoteJidAlt, event.rawNode.attrs.participant_pn, event.rawNode.attrs.sender_pn, event.rawNode.attrs.participant, event.key.isGroup ? undefined : event.rawNode.attrs.from);
|
|
562
|
+
const keyParentSender = (0, addon_crypto_1.resolveAddonParentSenderFromKey)(addon.targetMessageKey, event.key.isGroup);
|
|
563
|
+
const parentMsgOriginalSenderCandidates = (0, addon_crypto_1.collectUniqueUserJids)(keyParentSender, parentEntry.senderJid);
|
|
564
|
+
const senderPairs = (0, addon_crypto_1.buildAddonSenderPairs)({
|
|
565
|
+
parentCandidates: parentMsgOriginalSenderCandidates,
|
|
566
|
+
modificationCandidates: modificationSenderCandidates
|
|
567
|
+
});
|
|
568
|
+
const plaintext = await (0, addon_crypto_1.decryptAddonPayloadWithSenderFallback)({
|
|
535
569
|
messageSecret: parentEntry.secret,
|
|
536
570
|
stanzaId: targetMessageId,
|
|
537
|
-
|
|
538
|
-
modificationSender,
|
|
571
|
+
senderPairs,
|
|
539
572
|
modificationType: addon.modificationType,
|
|
540
573
|
ciphertext: addon.encPayload,
|
|
541
|
-
iv: addon.encIv
|
|
542
|
-
additionalData: (0, addon_crypto_1.shouldUseAddonAdditionalData)(addon.modificationType)
|
|
543
|
-
? (0, addon_crypto_1.buildAddonAdditionalData)(targetMessageId, modificationSender)
|
|
544
|
-
: undefined
|
|
574
|
+
iv: addon.encIv
|
|
545
575
|
});
|
|
546
576
|
let decrypted = (0, addon_crypto_1.decodeAddonPlaintext)(addon.kind, plaintext);
|
|
547
577
|
if (decrypted.kind === 'poll_vote' && decrypted.pollVote.selectedOptions) {
|
|
@@ -188,6 +188,14 @@ export declare class WaMessageDispatchCoordinator {
|
|
|
188
188
|
private buildBotSidecarParticipant;
|
|
189
189
|
private publishGroupSenderKeyMessage;
|
|
190
190
|
private resolveGroupAddressingMode;
|
|
191
|
+
/**
|
|
192
|
+
* Parent-author JID stored with the outgoing message secret. Peers address
|
|
193
|
+
* us by LID in `pollCreationMessageKey` during the PN→LID migration, so a
|
|
194
|
+
* valid `meLid` wins. A malformed LID must not abort the send: fall back to
|
|
195
|
+
* a normalized `meJid`, then to the empty sender (same recoverable path as
|
|
196
|
+
* {@link resolveSenderForAddressingMode}).
|
|
197
|
+
*/
|
|
198
|
+
private resolveOutgoingSecretSenderJid;
|
|
191
199
|
private resolveSenderForAddressingMode;
|
|
192
200
|
private encryptGroupDistributionParticipants;
|
|
193
201
|
private publishDirectSignalMessageWithFanout;
|
|
@@ -374,9 +374,11 @@ class WaMessageDispatchCoordinator {
|
|
|
374
374
|
rawSecret.length > 0 &&
|
|
375
375
|
sendOptions.id &&
|
|
376
376
|
(this.deps.persistAllMessageSecrets || (0, content_1.needsSecretPersistence)(messageWithSecret))) {
|
|
377
|
-
const meJid = this.deps.getCurrentCredentials()?.meJid ?? '';
|
|
378
377
|
void this.deps.messageSecretStore
|
|
379
|
-
.set(sendOptions.id, {
|
|
378
|
+
.set(sendOptions.id, {
|
|
379
|
+
secret: rawSecret,
|
|
380
|
+
senderJid: this.resolveOutgoingSecretSenderJid()
|
|
381
|
+
})
|
|
380
382
|
.catch((error) => {
|
|
381
383
|
this.deps.logger.warn('failed to persist outgoing message secret', {
|
|
382
384
|
id: sendOptions.id,
|
|
@@ -1072,6 +1074,41 @@ class WaMessageDispatchCoordinator {
|
|
|
1072
1074
|
});
|
|
1073
1075
|
return constants_1.WA_ADDRESSING_MODES.PN;
|
|
1074
1076
|
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Parent-author JID stored with the outgoing message secret. Peers address
|
|
1079
|
+
* us by LID in `pollCreationMessageKey` during the PN→LID migration, so a
|
|
1080
|
+
* valid `meLid` wins. A malformed LID must not abort the send: fall back to
|
|
1081
|
+
* a normalized `meJid`, then to the empty sender (same recoverable path as
|
|
1082
|
+
* {@link resolveSenderForAddressingMode}).
|
|
1083
|
+
*/
|
|
1084
|
+
resolveOutgoingSecretSenderJid() {
|
|
1085
|
+
const credentials = this.deps.getCurrentCredentials();
|
|
1086
|
+
const meLid = credentials?.meLid;
|
|
1087
|
+
if (meLid && meLid.includes('@')) {
|
|
1088
|
+
try {
|
|
1089
|
+
return (0, jid_1.toUserJid)(meLid);
|
|
1090
|
+
}
|
|
1091
|
+
catch (error) {
|
|
1092
|
+
this.deps.logger.trace('ignoring malformed me lid jid', {
|
|
1093
|
+
meLid,
|
|
1094
|
+
message: (0, primitives_2.toError)(error).message
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
const meJid = credentials?.meJid;
|
|
1099
|
+
if (meJid && meJid.includes('@')) {
|
|
1100
|
+
try {
|
|
1101
|
+
return (0, jid_1.toUserJid)(meJid);
|
|
1102
|
+
}
|
|
1103
|
+
catch (error) {
|
|
1104
|
+
this.deps.logger.trace('ignoring malformed me jid', {
|
|
1105
|
+
meJid,
|
|
1106
|
+
message: (0, primitives_2.toError)(error).message
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
return '';
|
|
1111
|
+
}
|
|
1075
1112
|
resolveSenderForAddressingMode(addressingMode, meJid) {
|
|
1076
1113
|
if (addressingMode === constants_1.WA_ADDRESSING_MODES.LID) {
|
|
1077
1114
|
const meLid = this.deps.getCurrentCredentials()?.meLid;
|
|
@@ -607,8 +607,7 @@ class WaMobileCoordinator {
|
|
|
607
607
|
}
|
|
608
608
|
}
|
|
609
609
|
nodeBytes(parent, tag) {
|
|
610
|
-
|
|
611
|
-
return content instanceof Uint8Array ? content : null;
|
|
610
|
+
return (0, _transport_1.getNodeBytesContent)((0, _transport_1.findNodeChild)(parent, tag)) ?? null;
|
|
612
611
|
}
|
|
613
612
|
requirePrimaryIdentityKeyPair() {
|
|
614
613
|
if (!this.isMobilePrimary()) {
|
|
@@ -2,6 +2,7 @@ import { type WaBlocklistResult, type WaPrivacyDisallowedListResult, type WaPriv
|
|
|
2
2
|
import type { Logger } from '../../infra/log/types';
|
|
3
3
|
import { type WaPrivacyDisallowedListSettingName, type WaPrivacySettingName, type WaPrivacySettingValueMap } from '../../protocol/privacy';
|
|
4
4
|
import type { SignalUserJidPair } from '../../signal/api/SignalDeviceSyncApi';
|
|
5
|
+
import type { WaContactStore } from '../../store/contracts/contact.store';
|
|
5
6
|
import type { BinaryNode } from '../../transport/types';
|
|
6
7
|
/** JIDs to add to / remove from a category's disallowed list. */
|
|
7
8
|
export interface WaPrivacyDisallowedListInput {
|
|
@@ -47,7 +48,9 @@ export interface WaPrivacyCoordinator {
|
|
|
47
48
|
/**
|
|
48
49
|
* Fetches the per-category disallowed list (the JIDs explicitly excluded
|
|
49
50
|
* while the category sits on `'contact_blacklist'`). Returns an empty
|
|
50
|
-
* list when the category is on any other value.
|
|
51
|
+
* list when the category is on any other value. `entries` carries the same
|
|
52
|
+
* membership as `jids` plus each entry's handle, when the server
|
|
53
|
+
* identified it that way.
|
|
51
54
|
*/
|
|
52
55
|
readonly getDisallowedList: (category: WaPrivacyDisallowedListSettingName) => Promise<WaPrivacyDisallowedListResult>;
|
|
53
56
|
/**
|
|
@@ -83,7 +86,9 @@ export interface WaPrivacyCoordinator {
|
|
|
83
86
|
/**
|
|
84
87
|
* Returns the current account-wide blocklist. Blocks/unblocks performed
|
|
85
88
|
* on another device are refetched through the same dirty-bit path as
|
|
86
|
-
* {@link getPrivacySettings} and re-emitted as `blocklist`.
|
|
89
|
+
* {@link getPrivacySettings} and re-emitted as `blocklist`. `entries`
|
|
90
|
+
* carries the same membership as `jids` plus each entry's handle, when the
|
|
91
|
+
* server identified it that way.
|
|
87
92
|
*/
|
|
88
93
|
readonly getBlocklist: () => Promise<WaBlocklistResult>;
|
|
89
94
|
/**
|
|
@@ -98,6 +103,10 @@ export interface WaPrivacyCoordinator {
|
|
|
98
103
|
* phone-number input is resolved to its LID first (device-list cache,
|
|
99
104
|
* then a usync query). Non-migrated accounts fall back to the plain
|
|
100
105
|
* phone-jid form.
|
|
106
|
+
*
|
|
107
|
+
* A migrated entry also carries one identifier attribute, read from the
|
|
108
|
+
* stored contact: the phone jid, the username handle, or the display name.
|
|
109
|
+
* Entries with none of them are sent as `unknown_identifier`.
|
|
101
110
|
*/
|
|
102
111
|
readonly blockUser: (jid: string) => Promise<void>;
|
|
103
112
|
/**
|
|
@@ -138,6 +147,13 @@ interface WaPrivacyCoordinatorOptions {
|
|
|
138
147
|
*/
|
|
139
148
|
readonly getSelfLid: () => string | null;
|
|
140
149
|
readonly emitPrivacy: (event: WaPrivacyAccountSyncResult) => void;
|
|
150
|
+
/** Source of the username / display-name identifiers a migrated write carries. */
|
|
151
|
+
readonly contactStore: WaContactStore;
|
|
152
|
+
/**
|
|
153
|
+
* Server-synced `username_contact_privacy_setting_allow_uncontact_set_enable`
|
|
154
|
+
* AB-prop, off by default - entries then fall back to `pn_jid`.
|
|
155
|
+
*/
|
|
156
|
+
readonly isUsernamePrivacyListIdentifierEnabled: () => boolean;
|
|
141
157
|
}
|
|
142
158
|
/** Builds a {@link WaPrivacyCoordinator} backed by the given IQ query function. */
|
|
143
159
|
export declare function createPrivacyCoordinator(options: WaPrivacyCoordinatorOptions): WaPrivacyCoordinatorRuntime;
|
|
@@ -10,6 +10,30 @@ const privacy_2 = require("../../protocol/privacy");
|
|
|
10
10
|
const privacy_3 = require("../../transport/node/builders/privacy");
|
|
11
11
|
const query_1 = require("../../transport/node/query");
|
|
12
12
|
const primitives_1 = require("../../util/primitives");
|
|
13
|
+
/**
|
|
14
|
+
* Adds the identifier fields a migrated write may carry. Only the writes that
|
|
15
|
+
* consume them call it - an unblock addresses by jid alone, and a
|
|
16
|
+
* disallowed-list write with the AB gate closed falls back to `pn_jid`.
|
|
17
|
+
*/
|
|
18
|
+
async function withContactIdentifiers(options, target) {
|
|
19
|
+
if (target.lidJid === null)
|
|
20
|
+
return target;
|
|
21
|
+
try {
|
|
22
|
+
const contact = await options.contactStore.getByJid(target.lidJid);
|
|
23
|
+
return {
|
|
24
|
+
...target,
|
|
25
|
+
username: contact?.username ?? null,
|
|
26
|
+
displayName: contact?.displayName ?? null
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
options.logger.debug('contact identifier lookup failed', {
|
|
31
|
+
jid: target.lidJid,
|
|
32
|
+
message: (0, primitives_1.toError)(error).message
|
|
33
|
+
});
|
|
34
|
+
return target;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
13
37
|
/**
|
|
14
38
|
* Resolves a blocklist input into both addressing forms via
|
|
15
39
|
* `resolveUserJidPair` (device-list cache first, usync fallback for phone
|
|
@@ -50,10 +74,19 @@ async function resolveDisallowedListEntries(options, input) {
|
|
|
50
74
|
{ action: privacy_2.WA_PRIVACY_LIST_ACTIONS.ADD, jids: input.add ?? [] },
|
|
51
75
|
{ action: privacy_2.WA_PRIVACY_LIST_ACTIONS.REMOVE, jids: input.remove ?? [] }
|
|
52
76
|
];
|
|
77
|
+
const usernameIdentifierAllowed = options.isUsernamePrivacyListIdentifierEnabled();
|
|
53
78
|
for (const { action, jids } of actions) {
|
|
54
79
|
for (const jid of jids) {
|
|
55
|
-
const
|
|
56
|
-
|
|
80
|
+
const resolved = await resolveBlocklistTarget(options, jid);
|
|
81
|
+
const target = usernameIdentifierAllowed
|
|
82
|
+
? await withContactIdentifiers(options, resolved)
|
|
83
|
+
: resolved;
|
|
84
|
+
entries.push({
|
|
85
|
+
action,
|
|
86
|
+
lidJid: target.lidJid,
|
|
87
|
+
pnJid: target.pnJid,
|
|
88
|
+
username: target.username ?? null
|
|
89
|
+
});
|
|
57
90
|
}
|
|
58
91
|
}
|
|
59
92
|
if (entries.length === 0) {
|
|
@@ -192,7 +225,7 @@ function createPrivacyCoordinator(options) {
|
|
|
192
225
|
return (0, privacy_1.parseBlocklist)(result);
|
|
193
226
|
},
|
|
194
227
|
blockUser: async (jid) => {
|
|
195
|
-
const target = await resolveBlocklistTarget(options, jid);
|
|
228
|
+
const target = await withContactIdentifiers(options, await resolveBlocklistTarget(options, jid));
|
|
196
229
|
const node = (0, privacy_3.buildBlocklistBlockIq)(target);
|
|
197
230
|
const result = await queryWithContext('privacy.blockUser', node, undefined, {
|
|
198
231
|
jid: target.lidJid ?? target.pnJid
|
|
@@ -54,6 +54,24 @@ export interface WaUsernameAvailabilityResult {
|
|
|
54
54
|
readonly available: boolean;
|
|
55
55
|
readonly suggestions: readonly string[];
|
|
56
56
|
}
|
|
57
|
+
export type WaUsernameLookupResult = {
|
|
58
|
+
readonly status: 'found';
|
|
59
|
+
readonly jid: string;
|
|
60
|
+
readonly username: string | null;
|
|
61
|
+
readonly isBusiness: boolean;
|
|
62
|
+
readonly pnJid: string | null;
|
|
63
|
+
} | {
|
|
64
|
+
readonly status: 'key-required';
|
|
65
|
+
readonly username: string | null;
|
|
66
|
+
} | {
|
|
67
|
+
readonly status: 'not-found';
|
|
68
|
+
};
|
|
69
|
+
export interface WaResolveUsernameInput {
|
|
70
|
+
/** A leading `@` and a `:1234` key suffix are accepted. */
|
|
71
|
+
readonly username: string;
|
|
72
|
+
/** Overrides a key parsed out of {@link username}. */
|
|
73
|
+
readonly usernameKey?: string;
|
|
74
|
+
}
|
|
57
75
|
/**
|
|
58
76
|
* Coordinates own/peer profile queries and mutations: picture, status, text
|
|
59
77
|
* status, username, disappearing mode, and LID lookup. Accessed via
|
|
@@ -109,13 +127,23 @@ export interface WaProfileCoordinator {
|
|
|
109
127
|
readonly setTextStatus: (input: WaSetTextStatusInput) => Promise<void>;
|
|
110
128
|
/** Batched fetch of username per JID. */
|
|
111
129
|
readonly getUsernames: (jids: readonly string[]) => Promise<readonly WaUsernameResult[]>;
|
|
130
|
+
/**
|
|
131
|
+
* Resolves a username handle to the account's LID, so a message can be
|
|
132
|
+
* addressed to `@handle`. `'key-required'` means the server withheld the
|
|
133
|
+
* JID - repeat the call with `usernameKey`.
|
|
134
|
+
*
|
|
135
|
+
* @throws when the handle fails local validation, before any round-trip.
|
|
136
|
+
*/
|
|
137
|
+
readonly resolveUsername: (input: WaResolveUsernameInput) => Promise<WaUsernameLookupResult>;
|
|
112
138
|
/** Fetches the current account's username record (value, state, recovery pin). */
|
|
113
139
|
readonly getOwnUsername: () => Promise<WaOwnUsernameResult>;
|
|
114
140
|
/**
|
|
115
141
|
* Reserves/sets a username on the current account. Returns `true` only
|
|
116
|
-
* when the server reports `'SUCCESS'`; on any other outcome (taken,
|
|
117
|
-
*
|
|
118
|
-
*
|
|
142
|
+
* when the server reports `'SUCCESS'`; on any other server outcome (taken,
|
|
143
|
+
* rate-limited) it returns `false` without throwing - check the value and
|
|
144
|
+
* consult {@link checkUsernameAvailability} for suggestions.
|
|
145
|
+
*
|
|
146
|
+
* @throws when the handle fails local validation, before any round-trip.
|
|
119
147
|
*/
|
|
120
148
|
readonly setUsername: (input: WaSetUsernameInput) => Promise<boolean>;
|
|
121
149
|
/** Deletes the current account's username. Returns `true` on success. */
|
|
@@ -124,7 +152,11 @@ export interface WaProfileCoordinator {
|
|
|
124
152
|
readonly getAboutStatus: (jid: string) => Promise<string | null>;
|
|
125
153
|
/** Checks whether a username is available and returns server suggestions. */
|
|
126
154
|
readonly checkUsernameAvailability: (username: string) => Promise<WaUsernameAvailabilityResult>;
|
|
127
|
-
/**
|
|
155
|
+
/**
|
|
156
|
+
* Sets the username recovery PIN. Returns `true` on success.
|
|
157
|
+
*
|
|
158
|
+
* @throws when `pin` is not exactly 4 digits, before any round-trip.
|
|
159
|
+
*/
|
|
128
160
|
readonly setUsernameKey: (pin: string) => Promise<boolean>;
|
|
129
161
|
/** Resolves LIDs for a list of phone numbers (handles normalization). */
|
|
130
162
|
readonly getLidsByPhoneNumbers: (phoneNumbers: readonly string[]) => Promise<readonly SignalLidSyncResult[]>;
|