zapo-js 1.5.1 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/WaClient.d.ts +1 -1
- package/dist/client/WaClient.js +1 -1
- package/dist/client/WaClientFactory.js +3 -1
- package/dist/client/coordinators/WaMessageCoordinator.d.ts +69 -1
- package/dist/client/coordinators/WaMessageCoordinator.js +76 -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/client/media.d.ts +1 -0
- package/dist/client/media.js +1 -0
- package/dist/client/messaging/messages.d.ts +45 -1
- package/dist/client/messaging/messages.js +57 -45
- package/dist/esm/client/WaClient.js +1 -1
- package/dist/esm/client/WaClientFactory.js +3 -1
- package/dist/esm/client/coordinators/WaMessageCoordinator.js +78 -3
- package/dist/esm/client/coordinators/WaMessageDispatchCoordinator.js +6 -36
- package/dist/esm/client/coordinators/WaPrivacyCoordinator.js +30 -5
- package/dist/esm/client/media.js +1 -1
- package/dist/esm/client/messaging/messages.js +56 -45
- package/dist/esm/index.js +2 -0
- package/dist/esm/signal/api/SignalDeviceSyncApi.js +62 -1
- package/dist/esm/transport/WaComms.js +7 -0
- package/dist/esm/transport/keepalive/WaKeepAlive.js +24 -7
- package/dist/esm/transport/node/builders/privacy.js +32 -2
- package/dist/index.d.ts +4 -1
- package/dist/index.js +6 -2
- package/dist/signal/api/SignalDeviceSyncApi.d.ts +27 -0
- package/dist/signal/api/SignalDeviceSyncApi.js +61 -0
- package/dist/transport/WaComms.d.ts +3 -0
- package/dist/transport/WaComms.js +7 -0
- package/dist/transport/keepalive/WaKeepAlive.d.ts +8 -2
- package/dist/transport/keepalive/WaKeepAlive.js +24 -7
- package/dist/transport/node/builders/privacy.d.ts +26 -1
- package/dist/transport/node/builders/privacy.js +34 -3
- package/package.json +1 -1
|
@@ -131,7 +131,7 @@ declare class WaClientImpl extends EventEmitter {
|
|
|
131
131
|
ignoreKey(input: WaIgnoreKey | WaIgnoreKeyPredicate): () => void;
|
|
132
132
|
/** Auth client: pairing, credentials, registration state. */
|
|
133
133
|
get auth(): WaAuthClient;
|
|
134
|
-
/** Message coordinator: send/receive, receipts, addons, media download. */
|
|
134
|
+
/** Message coordinator: send/receive, receipts, addons, media upload/download. */
|
|
135
135
|
get message(): WaMessageCoordinator;
|
|
136
136
|
/** Presence coordinator: own/peer presence subscriptions. */
|
|
137
137
|
get presence(): WaPresenceCoordinator;
|
package/dist/client/WaClient.js
CHANGED
|
@@ -414,7 +414,7 @@ class WaClientImpl extends node_events_1.EventEmitter {
|
|
|
414
414
|
get auth() {
|
|
415
415
|
return this.deps.authClient;
|
|
416
416
|
}
|
|
417
|
-
/** Message coordinator: send/receive, receipts, addons, media download. */
|
|
417
|
+
/** Message coordinator: send/receive, receipts, addons, media upload/download. */
|
|
418
418
|
get message() {
|
|
419
419
|
return this.deps.messageCoordinator;
|
|
420
420
|
}
|
|
@@ -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,
|
|
@@ -504,6 +505,7 @@ function buildWaClientDependencies(input) {
|
|
|
504
505
|
const messageCoordinator = new WaMessageCoordinator_1.WaMessageCoordinator({
|
|
505
506
|
messageDispatch,
|
|
506
507
|
mediaTransfer,
|
|
508
|
+
mediaUploadOptions: mediaMessageBuildOptions,
|
|
507
509
|
logger,
|
|
508
510
|
messageStore: sessionStore.messages,
|
|
509
511
|
messageSecretStore: sessionStore.messageSecret,
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { Readable } from 'node:stream';
|
|
2
2
|
import type { WaMessageDispatchCoordinator } from '../coordinators/WaMessageDispatchCoordinator';
|
|
3
3
|
import type { WaTrustedContactTokenCoordinator } from '../coordinators/WaTrustedContactTokenCoordinator';
|
|
4
|
+
import { type WaUploadMediaSource } from '../media';
|
|
5
|
+
import { type UploadResult, type WaMediaMessageOptions } from '../messaging/messages';
|
|
4
6
|
import type { WaDownloadMediaOptions, WaIncomingAddonEvent, WaIncomingMessageEvent, WaSendMessageOptions } from '../types';
|
|
5
7
|
import type { Logger } from '../../infra/log/types';
|
|
6
8
|
import type { WaMediaTransferClient } from '../../media/transfer/WaMediaTransferClient';
|
|
9
|
+
import type { MediaKind } from '../../media/types';
|
|
7
10
|
import type { PeerDataOperationRequester } from '../../message/primitives/peer-data-operation';
|
|
8
11
|
import type { WaMessagePublishResult, WaSendMessageContent, WaSendReceiptEventOptions, WaSendReceiptOptions } from '../../message/types';
|
|
9
12
|
import { type Proto } from '../../proto';
|
|
@@ -13,6 +16,8 @@ import { type WaMexQuerySocket } from '../../transport/node/mex/client';
|
|
|
13
16
|
export interface WaMessageCoordinatorDeps {
|
|
14
17
|
readonly messageDispatch: WaMessageDispatchCoordinator;
|
|
15
18
|
readonly mediaTransfer: WaMediaTransferClient;
|
|
19
|
+
/** Media upload wiring shared with the send path; backs {@link WaMessageCoordinator.upload}. */
|
|
20
|
+
readonly mediaUploadOptions: WaMediaMessageOptions;
|
|
16
21
|
readonly logger: Logger;
|
|
17
22
|
readonly messageStore: WaMessageStore;
|
|
18
23
|
readonly messageSecretStore: WaMessageSecretStore;
|
|
@@ -58,14 +63,47 @@ export interface WaMessageCappingInfo {
|
|
|
58
63
|
readonly mvStatus: string | null;
|
|
59
64
|
readonly cappingStatus: string | null;
|
|
60
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Media kinds accepted by {@link WaMessageCoordinator.upload}. `gif` is a
|
|
68
|
+
* GIF-playback video, `ptt` a voice note, `ptv` a round video note.
|
|
69
|
+
*/
|
|
70
|
+
export type WaUploadMediaType = MediaKind | 'gif' | 'ptt';
|
|
71
|
+
/** Options for {@link WaMessageCoordinator.upload}. */
|
|
72
|
+
export interface WaUploadMediaOptions {
|
|
73
|
+
/** Media type: sets the encryption context and CDN upload path. */
|
|
74
|
+
readonly type: WaUploadMediaType;
|
|
75
|
+
/** `Content-Type` for the upload and the mimetype for the message proto. */
|
|
76
|
+
readonly mimetype?: string;
|
|
77
|
+
/** Reuse a 32-byte media key instead of generating one. */
|
|
78
|
+
readonly mediaKey?: Uint8Array;
|
|
79
|
+
/** Override the streaming sidecar (default on for video/ptv/audio/gif/ptt). */
|
|
80
|
+
readonly sidecar?: boolean;
|
|
81
|
+
/** Animated-sticker first-frame length for the first-frame sidecar. */
|
|
82
|
+
readonly firstFrameLength?: number;
|
|
83
|
+
/** Per-upload transfer timeout override (ms). */
|
|
84
|
+
readonly timeoutMs?: number;
|
|
85
|
+
/** Cancellation signal forwarded to the CDN request. */
|
|
86
|
+
readonly signal?: AbortSignal;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Reusable descriptor returned by {@link WaMessageCoordinator.upload}: the
|
|
90
|
+
* {@link UploadResult} fields plus the media-key timestamp and echoed mimetype.
|
|
91
|
+
*/
|
|
92
|
+
export interface WaMediaUploadResult extends UploadResult {
|
|
93
|
+
/** Unix seconds the media key was minted; belongs on the message proto. */
|
|
94
|
+
readonly mediaKeyTimestamp: number;
|
|
95
|
+
/** Echo of the `mimetype` option when provided. */
|
|
96
|
+
readonly mimetype?: string;
|
|
97
|
+
}
|
|
61
98
|
/**
|
|
62
99
|
* Coordinates outbound message sending, receipts, addon decryption, media
|
|
63
|
-
* download, and the related MEX account queries. Accessed via
|
|
100
|
+
* upload/download, and the related MEX account queries. Accessed via
|
|
64
101
|
* {@link WaClient.message}.
|
|
65
102
|
*/
|
|
66
103
|
export declare class WaMessageCoordinator {
|
|
67
104
|
private readonly messageDispatch;
|
|
68
105
|
private readonly mediaTransfer;
|
|
106
|
+
private readonly mediaUploadOptions;
|
|
69
107
|
private readonly logger;
|
|
70
108
|
private readonly messageStore;
|
|
71
109
|
private readonly messageSecretStore;
|
|
@@ -183,6 +221,36 @@ export declare class WaMessageCoordinator {
|
|
|
183
221
|
*/
|
|
184
222
|
sendReceipt(target: WaIncomingMessageEvent | readonly WaIncomingMessageEvent[], options?: WaSendReceiptEventOptions): Promise<void>;
|
|
185
223
|
sendReceipt(jid: string, ids: string | readonly string[], options?: WaSendReceiptOptions): Promise<void>;
|
|
224
|
+
/**
|
|
225
|
+
* Encrypts and uploads standalone media to the WhatsApp CDN and returns the
|
|
226
|
+
* reusable descriptor, without sending a message - pre-upload once and
|
|
227
|
+
* reference it across sends, or build custom protos. Needs a connected
|
|
228
|
+
* session (the host token comes from a `media_conn` IQ). `source` is bytes,
|
|
229
|
+
* a file path, or a `Readable`. To send the result, spread its fields onto
|
|
230
|
+
* the matching proto message and pass that to {@link send}.
|
|
231
|
+
*
|
|
232
|
+
* @throws when `source` is unsupported, the file is unreadable, or the upload fails.
|
|
233
|
+
* @example
|
|
234
|
+
* ```ts
|
|
235
|
+
* const media = await client.message.upload(await readFile('photo.jpg'), {
|
|
236
|
+
* type: 'image',
|
|
237
|
+
* mimetype: 'image/jpeg'
|
|
238
|
+
* })
|
|
239
|
+
* await client.message.send(jid, {
|
|
240
|
+
* imageMessage: {
|
|
241
|
+
* url: media.url,
|
|
242
|
+
* directPath: media.directPath,
|
|
243
|
+
* mediaKey: media.mediaKey,
|
|
244
|
+
* fileSha256: media.fileSha256,
|
|
245
|
+
* fileEncSha256: media.fileEncSha256,
|
|
246
|
+
* fileLength: media.fileLength,
|
|
247
|
+
* mediaKeyTimestamp: media.mediaKeyTimestamp,
|
|
248
|
+
* mimetype: media.mimetype
|
|
249
|
+
* }
|
|
250
|
+
* })
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
upload(source: WaUploadMediaSource, options: WaUploadMediaOptions): Promise<WaMediaUploadResult>;
|
|
186
254
|
/**
|
|
187
255
|
* Resolves the media payload inside `source` and returns a `Readable`
|
|
188
256
|
* stream of the decrypted bytes. Throws when the message has no
|
|
@@ -5,6 +5,8 @@ const node_fs_1 = require("node:fs");
|
|
|
5
5
|
const promises_1 = require("node:stream/promises");
|
|
6
6
|
const receipt_1 = require("../events/receipt");
|
|
7
7
|
const media_1 = require("../media");
|
|
8
|
+
const messages_1 = require("../messaging/messages");
|
|
9
|
+
const constants_1 = require("../../media/constants");
|
|
8
10
|
const addon_crypto_1 = require("../../message/crypto/addon-crypto");
|
|
9
11
|
const content_1 = require("../../message/encode/content");
|
|
10
12
|
const _proto_1 = require("../../proto");
|
|
@@ -34,15 +36,36 @@ function parseMessageCappingMexResponse(data) {
|
|
|
34
36
|
cappingStatus: (0, coercion_1.tryAsString)(root?.capping_status)
|
|
35
37
|
};
|
|
36
38
|
}
|
|
39
|
+
const SIDECAR_UPLOAD_TYPES = new Set([
|
|
40
|
+
'video',
|
|
41
|
+
'ptv',
|
|
42
|
+
'audio',
|
|
43
|
+
'gif',
|
|
44
|
+
'ptt'
|
|
45
|
+
]);
|
|
46
|
+
async function normalizeUploadSource(source) {
|
|
47
|
+
if (source instanceof Uint8Array) {
|
|
48
|
+
return source;
|
|
49
|
+
}
|
|
50
|
+
if (typeof source === 'string') {
|
|
51
|
+
await (0, media_1.assertReadableFile)(source);
|
|
52
|
+
return (0, node_fs_1.createReadStream)(source);
|
|
53
|
+
}
|
|
54
|
+
if ((0, media_1.isReadableStream)(source)) {
|
|
55
|
+
return source;
|
|
56
|
+
}
|
|
57
|
+
throw new Error('media upload received unsupported source type');
|
|
58
|
+
}
|
|
37
59
|
/**
|
|
38
60
|
* Coordinates outbound message sending, receipts, addon decryption, media
|
|
39
|
-
* download, and the related MEX account queries. Accessed via
|
|
61
|
+
* upload/download, and the related MEX account queries. Accessed via
|
|
40
62
|
* {@link WaClient.message}.
|
|
41
63
|
*/
|
|
42
64
|
class WaMessageCoordinator {
|
|
43
65
|
constructor(deps) {
|
|
44
66
|
this.messageDispatch = deps.messageDispatch;
|
|
45
67
|
this.mediaTransfer = deps.mediaTransfer;
|
|
68
|
+
this.mediaUploadOptions = deps.mediaUploadOptions;
|
|
46
69
|
this.logger = deps.logger;
|
|
47
70
|
this.messageStore = deps.messageStore;
|
|
48
71
|
this.messageSecretStore = deps.messageSecretStore;
|
|
@@ -222,6 +245,58 @@ class WaMessageCoordinator {
|
|
|
222
245
|
});
|
|
223
246
|
}
|
|
224
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Encrypts and uploads standalone media to the WhatsApp CDN and returns the
|
|
250
|
+
* reusable descriptor, without sending a message - pre-upload once and
|
|
251
|
+
* reference it across sends, or build custom protos. Needs a connected
|
|
252
|
+
* session (the host token comes from a `media_conn` IQ). `source` is bytes,
|
|
253
|
+
* a file path, or a `Readable`. To send the result, spread its fields onto
|
|
254
|
+
* the matching proto message and pass that to {@link send}.
|
|
255
|
+
*
|
|
256
|
+
* @throws when `source` is unsupported, the file is unreadable, or the upload fails.
|
|
257
|
+
* @example
|
|
258
|
+
* ```ts
|
|
259
|
+
* const media = await client.message.upload(await readFile('photo.jpg'), {
|
|
260
|
+
* type: 'image',
|
|
261
|
+
* mimetype: 'image/jpeg'
|
|
262
|
+
* })
|
|
263
|
+
* await client.message.send(jid, {
|
|
264
|
+
* imageMessage: {
|
|
265
|
+
* url: media.url,
|
|
266
|
+
* directPath: media.directPath,
|
|
267
|
+
* mediaKey: media.mediaKey,
|
|
268
|
+
* fileSha256: media.fileSha256,
|
|
269
|
+
* fileEncSha256: media.fileEncSha256,
|
|
270
|
+
* fileLength: media.fileLength,
|
|
271
|
+
* mediaKeyTimestamp: media.mediaKeyTimestamp,
|
|
272
|
+
* mimetype: media.mimetype
|
|
273
|
+
* }
|
|
274
|
+
* })
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
async upload(source, options) {
|
|
278
|
+
const uploadPath = constants_1.MEDIA_UPLOAD_PATHS[options.type];
|
|
279
|
+
if (!uploadPath) {
|
|
280
|
+
throw new Error(`unknown media upload type: ${String(options.type)}`);
|
|
281
|
+
}
|
|
282
|
+
const result = await (0, messages_1.uploadMedia)(this.mediaUploadOptions, {
|
|
283
|
+
source: await normalizeUploadSource(source),
|
|
284
|
+
cryptoType: options.type,
|
|
285
|
+
uploadPath,
|
|
286
|
+
contentType: options.mimetype,
|
|
287
|
+
mediaKey: options.mediaKey,
|
|
288
|
+
sidecar: options.sidecar ?? SIDECAR_UPLOAD_TYPES.has(options.type),
|
|
289
|
+
firstFrameLength: options.firstFrameLength,
|
|
290
|
+
timeoutMs: options.timeoutMs,
|
|
291
|
+
signal: options.signal,
|
|
292
|
+
logLabel: 'user media upload'
|
|
293
|
+
});
|
|
294
|
+
return {
|
|
295
|
+
...result,
|
|
296
|
+
mediaKeyTimestamp: this.mediaUploadOptions.serverClock.nowSeconds(),
|
|
297
|
+
mimetype: options.mimetype
|
|
298
|
+
};
|
|
299
|
+
}
|
|
225
300
|
/**
|
|
226
301
|
* Resolves the media payload inside `source` and returns a `Readable`
|
|
227
302
|
* stream of the decrypted bytes. Throws when the message has no
|
|
@@ -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
|
};
|
package/dist/client/media.d.ts
CHANGED
|
@@ -57,6 +57,7 @@ export interface ResolvedMediaInputs {
|
|
|
57
57
|
readonly uploadMedia: Uint8Array | Readable;
|
|
58
58
|
readonly tempFilePath?: string;
|
|
59
59
|
}
|
|
60
|
+
export declare function assertReadableFile(filePath: string): Promise<void>;
|
|
60
61
|
export declare function resolveMediaInputs(shouldProcess: boolean, raw: Uint8Array | ArrayBuffer | Readable | string): Promise<ResolvedMediaInputs>;
|
|
61
62
|
export declare function shouldNormalizeVoiceNote(media: WaMediaOptions | undefined, content: WaSendMediaMessage): content is WaSendMediaMessage & {
|
|
62
63
|
type: 'audio';
|
package/dist/client/media.js
CHANGED
|
@@ -9,6 +9,7 @@ exports.buildMediaUploadUrl = buildMediaUploadUrl;
|
|
|
9
9
|
exports.assertMediaUploadStatus = assertMediaUploadStatus;
|
|
10
10
|
exports.parseMediaUploadJsonBody = parseMediaUploadJsonBody;
|
|
11
11
|
exports.performPlaintextMediaUpload = performPlaintextMediaUpload;
|
|
12
|
+
exports.assertReadableFile = assertReadableFile;
|
|
12
13
|
exports.resolveMediaInputs = resolveMediaInputs;
|
|
13
14
|
exports.shouldNormalizeVoiceNote = shouldNormalizeVoiceNote;
|
|
14
15
|
exports.hasMediaProcessingTasks = hasMediaProcessingTasks;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
1
2
|
import type { ResolvedLinkPreviewResult } from '../messaging/link-preview';
|
|
2
3
|
import type { WaMediaOptions } from '../types';
|
|
3
4
|
import type { Logger } from '../../infra/log/types';
|
|
4
5
|
import type { WaMediaTransferClient } from '../../media/transfer/WaMediaTransferClient';
|
|
5
|
-
import type { WaMediaConn } from '../../media/types';
|
|
6
|
+
import type { MediaCryptoType, WaMediaConn } from '../../media/types';
|
|
6
7
|
import type { WaMessageBuildResult, WaSendMessageContent, WaSendTextMessage } from '../../message/types';
|
|
7
8
|
import type { BinaryNode } from '../../transport/types';
|
|
8
9
|
import type { ServerClock } from '../../util/clock';
|
|
@@ -26,3 +27,46 @@ export interface WaBuildMessageContext {
|
|
|
26
27
|
}
|
|
27
28
|
export declare function buildMediaMessageContent(options: WaMediaMessageOptions, content: WaSendMessageContent, ctx?: WaBuildMessageContext): Promise<WaMessageBuildResult>;
|
|
28
29
|
export declare function getMediaConn(options: WaMediaMessageOptions, forceRefresh?: boolean): Promise<WaMediaConn>;
|
|
30
|
+
/** Descriptor produced by {@link uploadMedia}: CDN location, media key, hashes, and optional sidecars. */
|
|
31
|
+
export interface UploadResult {
|
|
32
|
+
/** Absolute CDN URL of the uploaded ciphertext. */
|
|
33
|
+
readonly url: string;
|
|
34
|
+
/** Host-relative path; pairs with a media host or feeds `downloadMediaMessage`. */
|
|
35
|
+
readonly directPath: string;
|
|
36
|
+
/** 32-byte media key used to encrypt (and later decrypt) the payload. */
|
|
37
|
+
readonly mediaKey: Uint8Array;
|
|
38
|
+
/** SHA-256 of the plaintext. */
|
|
39
|
+
readonly fileSha256: Uint8Array;
|
|
40
|
+
/** SHA-256 of the encrypted `ciphertext||mac`. */
|
|
41
|
+
readonly fileEncSha256: Uint8Array;
|
|
42
|
+
/** Plaintext byte length. */
|
|
43
|
+
readonly fileLength: number;
|
|
44
|
+
/** Server metadata URL, when the CDN returns one (video). */
|
|
45
|
+
readonly metadataUrl?: string;
|
|
46
|
+
/** Streaming sidecar for seekable playback, when computed. */
|
|
47
|
+
readonly streamingSidecar?: Uint8Array;
|
|
48
|
+
/** First-frame sidecar for animated stickers, when computed. */
|
|
49
|
+
readonly firstFrameSidecar?: Uint8Array;
|
|
50
|
+
/** First-frame length echoed back for animated stickers. */
|
|
51
|
+
readonly firstFrameLength?: number;
|
|
52
|
+
}
|
|
53
|
+
/** Fully-resolved input for {@link uploadMedia} (crypto type, path, sidecar already decided). */
|
|
54
|
+
export interface WaMediaUploadInput {
|
|
55
|
+
readonly source: Uint8Array | Readable;
|
|
56
|
+
readonly cryptoType: MediaCryptoType;
|
|
57
|
+
readonly uploadPath: string;
|
|
58
|
+
readonly contentType?: string;
|
|
59
|
+
readonly mediaKey?: Uint8Array;
|
|
60
|
+
readonly sidecar: boolean;
|
|
61
|
+
readonly firstFrameLength?: number;
|
|
62
|
+
readonly logLabel?: string;
|
|
63
|
+
readonly timeoutMs?: number;
|
|
64
|
+
readonly signal?: AbortSignal;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Shared media upload primitive: derives (or reuses) a media key, encrypts,
|
|
68
|
+
* fetches a media connection, and POSTs the ciphertext. Bytes take a
|
|
69
|
+
* zero-temp-file fast path; streams stage to a temp file first. Backs both the
|
|
70
|
+
* send path and `WaMessageCoordinator.upload`.
|
|
71
|
+
*/
|
|
72
|
+
export declare function uploadMedia(options: WaMediaMessageOptions, input: WaMediaUploadInput): Promise<UploadResult>;
|