zapo-js 1.5.1 → 1.6.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.
@@ -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;
@@ -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
  }
@@ -504,6 +504,7 @@ function buildWaClientDependencies(input) {
504
504
  const messageCoordinator = new WaMessageCoordinator_1.WaMessageCoordinator({
505
505
  messageDispatch,
506
506
  mediaTransfer,
507
+ mediaUploadOptions: mediaMessageBuildOptions,
507
508
  logger,
508
509
  messageStore: sessionStore.messages,
509
510
  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
@@ -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';
@@ -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>;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildMediaMessageContent = buildMediaMessageContent;
4
4
  exports.getMediaConn = getMediaConn;
5
+ exports.uploadMedia = uploadMedia;
5
6
  const node_fs_1 = require("node:fs");
6
7
  const node_stream_1 = require("node:stream");
7
8
  const media_1 = require("../media");
@@ -345,9 +346,15 @@ async function buildMediaMessage(options, content) {
345
346
  const firstFrameLength = content.type === 'sticker'
346
347
  ? (content.firstFrameLength ?? detectedFirstFrameLength)
347
348
  : undefined;
348
- const uploadPromise = (0, media_1.isReadableStream)(resolved.uploadMedia)
349
- ? uploadMediaStream(options, content, resolved.uploadMedia, firstFrameLength, mimetype)
350
- : uploadMediaBytes(options, content, resolved.uploadMedia, firstFrameLength, mimetype);
349
+ const uploadType = resolveUploadType(content);
350
+ const uploadPromise = uploadMedia(options, {
351
+ source: resolved.uploadMedia,
352
+ cryptoType: uploadType,
353
+ uploadPath: resolveUploadPath(uploadType),
354
+ contentType: mimetype,
355
+ sidecar: needsSidecar(content),
356
+ firstFrameLength
357
+ });
351
358
  const processPromise = (0, media_1.runMediaProcessor)(options.media, resolved.processorInput, content, options.logger);
352
359
  const [uploadResult, processResult] = await Promise.allSettled([
353
360
  uploadPromise,
@@ -508,29 +515,42 @@ function parseUploadResponse(body, status) {
508
515
  ...(parsed.metadata_url ? { metadataUrl: parsed.metadata_url } : {})
509
516
  };
510
517
  }
511
- async function uploadMediaBytes(options, content, mediaBytes, firstFrameLength, mimetype) {
512
- const uploadType = resolveUploadType(content);
513
- const mediaKey = await WaMediaCrypto_1.WaMediaCrypto.generateMediaKey();
518
+ /**
519
+ * Shared media upload primitive: derives (or reuses) a media key, encrypts,
520
+ * fetches a media connection, and POSTs the ciphertext. Bytes take a
521
+ * zero-temp-file fast path; streams stage to a temp file first. Backs both the
522
+ * send path and `WaMessageCoordinator.upload`.
523
+ */
524
+ async function uploadMedia(options, input) {
525
+ const mediaKey = input.mediaKey ?? (await WaMediaCrypto_1.WaMediaCrypto.generateMediaKey());
526
+ if (input.source instanceof Uint8Array) {
527
+ return uploadMediaBytes(options, input, mediaKey, input.source);
528
+ }
529
+ return uploadMediaStream(options, input, mediaKey, input.source);
530
+ }
531
+ async function uploadMediaBytes(options, input, mediaKey, mediaBytes) {
514
532
  const [encrypted, mediaConn] = await Promise.all([
515
- WaMediaCrypto_1.WaMediaCrypto.encryptBytes(uploadType, mediaKey, mediaBytes, {
516
- sidecar: needsSidecar(content),
517
- firstFrameLength
533
+ WaMediaCrypto_1.WaMediaCrypto.encryptBytes(input.cryptoType, mediaKey, mediaBytes, {
534
+ sidecar: input.sidecar,
535
+ firstFrameLength: input.firstFrameLength
518
536
  }),
519
537
  getMediaConn(options)
520
538
  ]);
521
539
  const selectedHost = (0, media_1.selectMediaUploadHost)(mediaConn);
522
- const uploadUrl = (0, media_1.buildMediaUploadUrl)(selectedHost, resolveUploadPath(uploadType), mediaConn.auth, encrypted.fileEncSha256);
523
- options.logger.debug('sending media upload request', {
524
- mediaType: content.type,
525
- uploadType,
526
- host: selectedHost
540
+ const uploadUrl = (0, media_1.buildMediaUploadUrl)(selectedHost, input.uploadPath, mediaConn.auth, encrypted.fileEncSha256);
541
+ options.logger.debug(input.logLabel ?? 'sending media upload request', {
542
+ cryptoType: input.cryptoType,
543
+ host: selectedHost,
544
+ size: mediaBytes.byteLength
527
545
  });
528
546
  const uploadResponse = await options.mediaTransfer.uploadStream({
529
547
  url: uploadUrl,
530
548
  method: 'POST',
531
549
  body: encrypted.ciphertextHmac,
532
550
  contentLength: encrypted.ciphertextHmac.byteLength,
533
- contentType: mimetype
551
+ contentType: input.contentType,
552
+ timeoutMs: input.timeoutMs,
553
+ signal: input.signal
534
554
  });
535
555
  const responseBody = await options.mediaTransfer.readResponseBytes(uploadResponse);
536
556
  const parsed = parseUploadResponse(responseBody, uploadResponse.status);
@@ -542,19 +562,22 @@ async function uploadMediaBytes(options, content, mediaBytes, firstFrameLength,
542
562
  fileLength: mediaBytes.byteLength,
543
563
  streamingSidecar: encrypted.streamingSidecar,
544
564
  firstFrameSidecar: encrypted.firstFrameSidecar,
545
- firstFrameLength
565
+ firstFrameLength: input.firstFrameLength
546
566
  };
547
567
  }
548
- async function uploadEncryptedStream(options, input) {
549
- const encResult = await WaMediaCrypto_1.WaMediaCrypto.encryptToFile(input.cryptoType, input.mediaKey, input.plaintext, { sidecar: input.sidecar, firstFrameLength: input.firstFrameLength });
568
+ async function uploadMediaStream(options, input, mediaKey, plaintext) {
569
+ const encResult = await WaMediaCrypto_1.WaMediaCrypto.encryptToFile(input.cryptoType, mediaKey, plaintext, {
570
+ sidecar: input.sidecar,
571
+ firstFrameLength: input.firstFrameLength
572
+ });
550
573
  let readStream;
551
574
  try {
552
575
  const mediaConn = await getMediaConn(options);
553
576
  const selectedHost = (0, media_1.selectMediaUploadHost)(mediaConn);
554
577
  const uploadUrl = (0, media_1.buildMediaUploadUrl)(selectedHost, input.uploadPath, mediaConn.auth, encResult.fileEncSha256);
555
- options.logger.debug(input.logLabel, {
578
+ options.logger.debug(input.logLabel ?? 'sending media stream upload request', {
579
+ cryptoType: input.cryptoType,
556
580
  host: selectedHost,
557
- uploadPath: input.uploadPath,
558
581
  plaintextLength: encResult.plaintextLength,
559
582
  encryptedSize: encResult.fileSize
560
583
  });
@@ -564,13 +587,15 @@ async function uploadEncryptedStream(options, input) {
564
587
  method: 'POST',
565
588
  body: readStream,
566
589
  contentLength: encResult.fileSize,
567
- contentType: input.contentType
590
+ contentType: input.contentType,
591
+ timeoutMs: input.timeoutMs,
592
+ signal: input.signal
568
593
  });
569
594
  const responseBody = await options.mediaTransfer.readResponseBytes(uploadResponse);
570
595
  const parsed = parseUploadResponse(responseBody, uploadResponse.status);
571
596
  return {
572
597
  ...parsed,
573
- mediaKey: input.mediaKey,
598
+ mediaKey,
574
599
  fileSha256: encResult.fileSha256,
575
600
  fileEncSha256: encResult.fileEncSha256,
576
601
  fileLength: encResult.plaintextLength,
@@ -589,19 +614,6 @@ async function uploadEncryptedStream(options, input) {
589
614
  await WaMediaCrypto_1.WaMediaCrypto.cleanupEncryptedFile(encResult.filePath);
590
615
  }
591
616
  }
592
- async function uploadMediaStream(options, content, stream, firstFrameLength, mimetype) {
593
- const cryptoType = resolveUploadType(content);
594
- return uploadEncryptedStream(options, {
595
- plaintext: stream,
596
- mediaKey: await WaMediaCrypto_1.WaMediaCrypto.generateMediaKey(),
597
- cryptoType,
598
- uploadPath: resolveUploadPath(cryptoType),
599
- contentType: mimetype,
600
- logLabel: 'sending media stream upload request',
601
- sidecar: needsSidecar(content),
602
- firstFrameLength
603
- });
604
- }
605
617
  function openStickerPackInputStream(media) {
606
618
  return typeof media === 'string' ? (0, node_fs_1.createReadStream)(media) : node_stream_1.Readable.from([media]);
607
619
  }
@@ -613,23 +625,23 @@ async function buildStickerPackMediaMessage(options, content) {
613
625
  const coverThumbnail = content.coverThumbnail;
614
626
  const mediaKey = await WaMediaCrypto_1.WaMediaCrypto.generateMediaKey();
615
627
  const [bundle, cover] = await Promise.all([
616
- uploadEncryptedStream(options, {
617
- plaintext: (0, sticker_pack_1.createStickerPackZipStream)((0, sticker_pack_2.toStickerPackZipEntries)(content)),
618
- mediaKey,
628
+ uploadMedia(options, {
629
+ source: (0, sticker_pack_1.createStickerPackZipStream)((0, sticker_pack_2.toStickerPackZipEntries)(content)),
619
630
  cryptoType: 'sticker-pack',
620
631
  uploadPath: constants_1.MEDIA_UPLOAD_PATHS['sticker-pack'],
621
632
  contentType: 'application/zip',
622
- logLabel: 'sending sticker pack bundle upload',
623
- sidecar: false
624
- }),
625
- uploadEncryptedStream(options, {
626
- plaintext: openStickerPackInputStream(coverThumbnail),
627
633
  mediaKey,
634
+ sidecar: false,
635
+ logLabel: 'sending sticker pack bundle upload'
636
+ }),
637
+ uploadMedia(options, {
638
+ source: openStickerPackInputStream(coverThumbnail),
628
639
  cryptoType: 'thumbnail-sticker-pack',
629
640
  uploadPath: constants_1.MEDIA_UPLOAD_PATHS['thumbnail-sticker-pack'],
630
641
  contentType: 'image/jpeg',
631
- logLabel: 'sending sticker pack thumbnail upload',
632
- sidecar: false
642
+ mediaKey,
643
+ sidecar: false,
644
+ logLabel: 'sending sticker pack thumbnail upload'
633
645
  })
634
646
  ]);
635
647
  if (cover.fileLength === 0) {
@@ -411,7 +411,7 @@ class WaClientImpl extends EventEmitter {
411
411
  get auth() {
412
412
  return this.deps.authClient;
413
413
  }
414
- /** Message coordinator: send/receive, receipts, addons, media download. */
414
+ /** Message coordinator: send/receive, receipts, addons, media upload/download. */
415
415
  get message() {
416
416
  return this.deps.messageCoordinator;
417
417
  }
@@ -500,6 +500,7 @@ export function buildWaClientDependencies(input) {
500
500
  const messageCoordinator = new WaMessageCoordinator({
501
501
  messageDispatch,
502
502
  mediaTransfer,
503
+ mediaUploadOptions: mediaMessageBuildOptions,
503
504
  logger,
504
505
  messageStore: sessionStore.messages,
505
506
  messageSecretStore: sessionStore.messageSecret,
@@ -1,7 +1,9 @@
1
- import { createWriteStream } from 'node:fs';
1
+ import { createReadStream, createWriteStream } from 'node:fs';
2
2
  import { pipeline } from 'node:stream/promises';
3
3
  import { aggregateReceiptTargets } from '../events/receipt.js';
4
- import { downloadMediaMessage } from '../media.js';
4
+ import { assertReadableFile, downloadMediaMessage, isReadableStream } from '../media.js';
5
+ import { uploadMedia } from '../messaging/messages.js';
6
+ import { MEDIA_UPLOAD_PATHS } from '../../media/constants.js';
5
7
  import { buildAddonAdditionalData, decodeAddonPlaintext, decryptAddonPayload, identifyEncryptedAddon, resolveParentMessageSecret, resolvePollOptionNames, shouldUseAddonAdditionalData } from '../../message/crypto/addon-crypto.js';
6
8
  import { unwrapMessage } from '../../message/encode/content.js';
7
9
  import { proto } from '../../proto.js';
@@ -31,15 +33,36 @@ function parseMessageCappingMexResponse(data) {
31
33
  cappingStatus: tryAsString(root?.capping_status)
32
34
  };
33
35
  }
36
+ const SIDECAR_UPLOAD_TYPES = new Set([
37
+ 'video',
38
+ 'ptv',
39
+ 'audio',
40
+ 'gif',
41
+ 'ptt'
42
+ ]);
43
+ async function normalizeUploadSource(source) {
44
+ if (source instanceof Uint8Array) {
45
+ return source;
46
+ }
47
+ if (typeof source === 'string') {
48
+ await assertReadableFile(source);
49
+ return createReadStream(source);
50
+ }
51
+ if (isReadableStream(source)) {
52
+ return source;
53
+ }
54
+ throw new Error('media upload received unsupported source type');
55
+ }
34
56
  /**
35
57
  * Coordinates outbound message sending, receipts, addon decryption, media
36
- * download, and the related MEX account queries. Accessed via
58
+ * upload/download, and the related MEX account queries. Accessed via
37
59
  * {@link WaClient.message}.
38
60
  */
39
61
  export class WaMessageCoordinator {
40
62
  constructor(deps) {
41
63
  this.messageDispatch = deps.messageDispatch;
42
64
  this.mediaTransfer = deps.mediaTransfer;
65
+ this.mediaUploadOptions = deps.mediaUploadOptions;
43
66
  this.logger = deps.logger;
44
67
  this.messageStore = deps.messageStore;
45
68
  this.messageSecretStore = deps.messageSecretStore;
@@ -219,6 +242,58 @@ export class WaMessageCoordinator {
219
242
  });
220
243
  }
221
244
  }
245
+ /**
246
+ * Encrypts and uploads standalone media to the WhatsApp CDN and returns the
247
+ * reusable descriptor, without sending a message - pre-upload once and
248
+ * reference it across sends, or build custom protos. Needs a connected
249
+ * session (the host token comes from a `media_conn` IQ). `source` is bytes,
250
+ * a file path, or a `Readable`. To send the result, spread its fields onto
251
+ * the matching proto message and pass that to {@link send}.
252
+ *
253
+ * @throws when `source` is unsupported, the file is unreadable, or the upload fails.
254
+ * @example
255
+ * ```ts
256
+ * const media = await client.message.upload(await readFile('photo.jpg'), {
257
+ * type: 'image',
258
+ * mimetype: 'image/jpeg'
259
+ * })
260
+ * await client.message.send(jid, {
261
+ * imageMessage: {
262
+ * url: media.url,
263
+ * directPath: media.directPath,
264
+ * mediaKey: media.mediaKey,
265
+ * fileSha256: media.fileSha256,
266
+ * fileEncSha256: media.fileEncSha256,
267
+ * fileLength: media.fileLength,
268
+ * mediaKeyTimestamp: media.mediaKeyTimestamp,
269
+ * mimetype: media.mimetype
270
+ * }
271
+ * })
272
+ * ```
273
+ */
274
+ async upload(source, options) {
275
+ const uploadPath = MEDIA_UPLOAD_PATHS[options.type];
276
+ if (!uploadPath) {
277
+ throw new Error(`unknown media upload type: ${String(options.type)}`);
278
+ }
279
+ const result = await uploadMedia(this.mediaUploadOptions, {
280
+ source: await normalizeUploadSource(source),
281
+ cryptoType: options.type,
282
+ uploadPath,
283
+ contentType: options.mimetype,
284
+ mediaKey: options.mediaKey,
285
+ sidecar: options.sidecar ?? SIDECAR_UPLOAD_TYPES.has(options.type),
286
+ firstFrameLength: options.firstFrameLength,
287
+ timeoutMs: options.timeoutMs,
288
+ signal: options.signal,
289
+ logLabel: 'user media upload'
290
+ });
291
+ return {
292
+ ...result,
293
+ mediaKeyTimestamp: this.mediaUploadOptions.serverClock.nowSeconds(),
294
+ mimetype: options.mimetype
295
+ };
296
+ }
222
297
  /**
223
298
  * Resolves the media payload inside `source` and returns a `Readable`
224
299
  * stream of the decrypted bytes. Throws when the message has no
@@ -198,7 +198,7 @@ export async function performPlaintextMediaUpload(deps, input) {
198
198
  await prepared.cleanup?.();
199
199
  }
200
200
  }
201
- async function assertReadableFile(filePath) {
201
+ export async function assertReadableFile(filePath) {
202
202
  try {
203
203
  const stats = await stat(filePath);
204
204
  if (!stats.isFile()) {
@@ -341,9 +341,15 @@ async function buildMediaMessage(options, content) {
341
341
  const firstFrameLength = content.type === 'sticker'
342
342
  ? (content.firstFrameLength ?? detectedFirstFrameLength)
343
343
  : undefined;
344
- const uploadPromise = isReadableStream(resolved.uploadMedia)
345
- ? uploadMediaStream(options, content, resolved.uploadMedia, firstFrameLength, mimetype)
346
- : uploadMediaBytes(options, content, resolved.uploadMedia, firstFrameLength, mimetype);
344
+ const uploadType = resolveUploadType(content);
345
+ const uploadPromise = uploadMedia(options, {
346
+ source: resolved.uploadMedia,
347
+ cryptoType: uploadType,
348
+ uploadPath: resolveUploadPath(uploadType),
349
+ contentType: mimetype,
350
+ sidecar: needsSidecar(content),
351
+ firstFrameLength
352
+ });
347
353
  const processPromise = runMediaProcessor(options.media, resolved.processorInput, content, options.logger);
348
354
  const [uploadResult, processResult] = await Promise.allSettled([
349
355
  uploadPromise,
@@ -504,29 +510,42 @@ function parseUploadResponse(body, status) {
504
510
  ...(parsed.metadata_url ? { metadataUrl: parsed.metadata_url } : {})
505
511
  };
506
512
  }
507
- async function uploadMediaBytes(options, content, mediaBytes, firstFrameLength, mimetype) {
508
- const uploadType = resolveUploadType(content);
509
- const mediaKey = await WaMediaCrypto.generateMediaKey();
513
+ /**
514
+ * Shared media upload primitive: derives (or reuses) a media key, encrypts,
515
+ * fetches a media connection, and POSTs the ciphertext. Bytes take a
516
+ * zero-temp-file fast path; streams stage to a temp file first. Backs both the
517
+ * send path and `WaMessageCoordinator.upload`.
518
+ */
519
+ export async function uploadMedia(options, input) {
520
+ const mediaKey = input.mediaKey ?? (await WaMediaCrypto.generateMediaKey());
521
+ if (input.source instanceof Uint8Array) {
522
+ return uploadMediaBytes(options, input, mediaKey, input.source);
523
+ }
524
+ return uploadMediaStream(options, input, mediaKey, input.source);
525
+ }
526
+ async function uploadMediaBytes(options, input, mediaKey, mediaBytes) {
510
527
  const [encrypted, mediaConn] = await Promise.all([
511
- WaMediaCrypto.encryptBytes(uploadType, mediaKey, mediaBytes, {
512
- sidecar: needsSidecar(content),
513
- firstFrameLength
528
+ WaMediaCrypto.encryptBytes(input.cryptoType, mediaKey, mediaBytes, {
529
+ sidecar: input.sidecar,
530
+ firstFrameLength: input.firstFrameLength
514
531
  }),
515
532
  getMediaConn(options)
516
533
  ]);
517
534
  const selectedHost = selectMediaUploadHost(mediaConn);
518
- const uploadUrl = buildMediaUploadUrl(selectedHost, resolveUploadPath(uploadType), mediaConn.auth, encrypted.fileEncSha256);
519
- options.logger.debug('sending media upload request', {
520
- mediaType: content.type,
521
- uploadType,
522
- host: selectedHost
535
+ const uploadUrl = buildMediaUploadUrl(selectedHost, input.uploadPath, mediaConn.auth, encrypted.fileEncSha256);
536
+ options.logger.debug(input.logLabel ?? 'sending media upload request', {
537
+ cryptoType: input.cryptoType,
538
+ host: selectedHost,
539
+ size: mediaBytes.byteLength
523
540
  });
524
541
  const uploadResponse = await options.mediaTransfer.uploadStream({
525
542
  url: uploadUrl,
526
543
  method: 'POST',
527
544
  body: encrypted.ciphertextHmac,
528
545
  contentLength: encrypted.ciphertextHmac.byteLength,
529
- contentType: mimetype
546
+ contentType: input.contentType,
547
+ timeoutMs: input.timeoutMs,
548
+ signal: input.signal
530
549
  });
531
550
  const responseBody = await options.mediaTransfer.readResponseBytes(uploadResponse);
532
551
  const parsed = parseUploadResponse(responseBody, uploadResponse.status);
@@ -538,19 +557,22 @@ async function uploadMediaBytes(options, content, mediaBytes, firstFrameLength,
538
557
  fileLength: mediaBytes.byteLength,
539
558
  streamingSidecar: encrypted.streamingSidecar,
540
559
  firstFrameSidecar: encrypted.firstFrameSidecar,
541
- firstFrameLength
560
+ firstFrameLength: input.firstFrameLength
542
561
  };
543
562
  }
544
- async function uploadEncryptedStream(options, input) {
545
- const encResult = await WaMediaCrypto.encryptToFile(input.cryptoType, input.mediaKey, input.plaintext, { sidecar: input.sidecar, firstFrameLength: input.firstFrameLength });
563
+ async function uploadMediaStream(options, input, mediaKey, plaintext) {
564
+ const encResult = await WaMediaCrypto.encryptToFile(input.cryptoType, mediaKey, plaintext, {
565
+ sidecar: input.sidecar,
566
+ firstFrameLength: input.firstFrameLength
567
+ });
546
568
  let readStream;
547
569
  try {
548
570
  const mediaConn = await getMediaConn(options);
549
571
  const selectedHost = selectMediaUploadHost(mediaConn);
550
572
  const uploadUrl = buildMediaUploadUrl(selectedHost, input.uploadPath, mediaConn.auth, encResult.fileEncSha256);
551
- options.logger.debug(input.logLabel, {
573
+ options.logger.debug(input.logLabel ?? 'sending media stream upload request', {
574
+ cryptoType: input.cryptoType,
552
575
  host: selectedHost,
553
- uploadPath: input.uploadPath,
554
576
  plaintextLength: encResult.plaintextLength,
555
577
  encryptedSize: encResult.fileSize
556
578
  });
@@ -560,13 +582,15 @@ async function uploadEncryptedStream(options, input) {
560
582
  method: 'POST',
561
583
  body: readStream,
562
584
  contentLength: encResult.fileSize,
563
- contentType: input.contentType
585
+ contentType: input.contentType,
586
+ timeoutMs: input.timeoutMs,
587
+ signal: input.signal
564
588
  });
565
589
  const responseBody = await options.mediaTransfer.readResponseBytes(uploadResponse);
566
590
  const parsed = parseUploadResponse(responseBody, uploadResponse.status);
567
591
  return {
568
592
  ...parsed,
569
- mediaKey: input.mediaKey,
593
+ mediaKey,
570
594
  fileSha256: encResult.fileSha256,
571
595
  fileEncSha256: encResult.fileEncSha256,
572
596
  fileLength: encResult.plaintextLength,
@@ -585,19 +609,6 @@ async function uploadEncryptedStream(options, input) {
585
609
  await WaMediaCrypto.cleanupEncryptedFile(encResult.filePath);
586
610
  }
587
611
  }
588
- async function uploadMediaStream(options, content, stream, firstFrameLength, mimetype) {
589
- const cryptoType = resolveUploadType(content);
590
- return uploadEncryptedStream(options, {
591
- plaintext: stream,
592
- mediaKey: await WaMediaCrypto.generateMediaKey(),
593
- cryptoType,
594
- uploadPath: resolveUploadPath(cryptoType),
595
- contentType: mimetype,
596
- logLabel: 'sending media stream upload request',
597
- sidecar: needsSidecar(content),
598
- firstFrameLength
599
- });
600
- }
601
612
  function openStickerPackInputStream(media) {
602
613
  return typeof media === 'string' ? createReadStream(media) : Readable.from([media]);
603
614
  }
@@ -609,23 +620,23 @@ async function buildStickerPackMediaMessage(options, content) {
609
620
  const coverThumbnail = content.coverThumbnail;
610
621
  const mediaKey = await WaMediaCrypto.generateMediaKey();
611
622
  const [bundle, cover] = await Promise.all([
612
- uploadEncryptedStream(options, {
613
- plaintext: createStickerPackZipStream(toStickerPackZipEntries(content)),
614
- mediaKey,
623
+ uploadMedia(options, {
624
+ source: createStickerPackZipStream(toStickerPackZipEntries(content)),
615
625
  cryptoType: 'sticker-pack',
616
626
  uploadPath: MEDIA_UPLOAD_PATHS['sticker-pack'],
617
627
  contentType: 'application/zip',
618
- logLabel: 'sending sticker pack bundle upload',
619
- sidecar: false
620
- }),
621
- uploadEncryptedStream(options, {
622
- plaintext: openStickerPackInputStream(coverThumbnail),
623
628
  mediaKey,
629
+ sidecar: false,
630
+ logLabel: 'sending sticker pack bundle upload'
631
+ }),
632
+ uploadMedia(options, {
633
+ source: openStickerPackInputStream(coverThumbnail),
624
634
  cryptoType: 'thumbnail-sticker-pack',
625
635
  uploadPath: MEDIA_UPLOAD_PATHS['thumbnail-sticker-pack'],
626
636
  contentType: 'image/jpeg',
627
- logLabel: 'sending sticker pack thumbnail upload',
628
- sidecar: false
637
+ mediaKey,
638
+ sidecar: false,
639
+ logLabel: 'sending sticker pack thumbnail upload'
629
640
  })
630
641
  ]);
631
642
  if (cover.fileLength === 0) {
package/dist/esm/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  export { WaClient } from './client/index.js';
2
2
  export { defineWaClientPlugin } from './client/plugins/index.js';
3
3
  export { downloadMediaMessage } from './client/media.js';
4
+ export { WaMediaCrypto } from './media/crypto/WaMediaCrypto.js';
5
+ export { WaMediaTransferClient } from './media/transfer/WaMediaTransferClient.js';
4
6
  export { createFileCompanionHostPersistence } from './client/persistence/companion-host.js';
5
7
  export { parseUsyncResultEnvelope } from './transport/node/builders/usync.js';
6
8
  export { getContentType, resolveEncMediaType, resolveMessageTarget, unwrapMessage } from './message/encode/content.js';
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ export { defineWaClientPlugin } from './client/plugins';
3
3
  export type { WaClientDependencies } from './client/WaClientFactory';
4
4
  export type { WaClientEventMap, WaClientOptions, WaClientProxyOptions, WaDownloadMediaOptions, WaHistorySyncChunkEvent, WaHistorySyncOptions, WaWriteBehindOptions } from './client/types';
5
5
  export type { WaClientPluginContext, WaClientPluginDefinition } from './client/plugins';
6
- export type { WaMessageCoordinator } from './client/coordinators/WaMessageCoordinator';
6
+ export type { WaMediaUploadResult, WaMessageCoordinator, WaUploadMediaOptions, WaUploadMediaType } from './client/coordinators/WaMessageCoordinator';
7
7
  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, WaOutgoingMessageEvent, WaPictureEvent, WaPictureEventAction, WaPrivacyTokenUpdateEvent, WaReceiptStatus, WaRegistrationCodeEvent, WaSendMessageOptions, WaUnavailableMessageKind, WaVerifiedNameResult, WaAddonKind, WaNewsletterEventAction, WaNewsletterMessageUpdate, WaNewsletterPollVoteEntry, WaNewsletterReactionEntry } from './client/types';
8
8
  export type { WaAppStateMutationCoordinator, WaBroadcastListParticipant, WaSetBroadcastListInput, WaSetStatusPrivacyInput } from './client/coordinators/WaAppStateMutationCoordinator';
9
9
  export type { WaBotCoordinator, WaBotInfo, WaBotPosingAsProfessional, WaBotProfileCommand, WaBotProfilePrompt, WaBotProfileResult, WaBotPromptOptions, WaGetBotProfileOptions } from './client/coordinators/WaBotCoordinator';
@@ -11,6 +11,9 @@ export type { WaBroadcastListCoordinator, WaSendBroadcastListMessageInput } from
11
11
  export type { WaBusinessCoordinator, WaVerifiedNameBatchEntry } from './client/coordinators/WaBusinessCoordinator';
12
12
  export { downloadMediaMessage } from './client/media';
13
13
  export type { WaDownloadMediaMessageOptions, WaUploadMediaSource } from './client/media';
14
+ export { WaMediaCrypto } from './media/crypto/WaMediaCrypto';
15
+ export { WaMediaTransferClient } from './media/transfer/WaMediaTransferClient';
16
+ export type { MediaCryptoType, MediaKind, WaMediaConn, WaMediaDecryptReadableOptions, WaMediaDecryptionResult, WaMediaDerivedKeys, WaMediaEncryptionResult, WaMediaFileEncryptionResult, WaMediaReadableDecryptionResult, WaMediaReadableEncryptionResult, WaMediaTransferClientOptions } from './media/types';
14
17
  export type { WaEditBusinessProfileInput } from './transport/node/builders/business';
15
18
  export type { WaEmailCoordinator, WaEmailStatus, WaEmailVerifyCodeResult } from './client/coordinators/WaEmailCoordinator';
16
19
  export type { WaCommunityCreateOptions, WaCommunitySubGroup, WaCommunitySubGroupResult, WaCommunitySubGroupsResult, WaGroupCoordinator, WaGroupCreateOptions, WaGroupMetadata, WaGroupParticipant, WaLinkSubGroupsResult, WaMembershipRequest, WaUnlinkSubGroupsResult } from './client/coordinators/WaGroupCoordinator';
package/dist/index.js CHANGED
@@ -1,13 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.WA_APP_STATE_COLLECTIONS = exports.WA_ACCOUNT_SYNC_PROTOCOLS = exports.toUserJid = exports.splitJid = exports.signalAddressKey = exports.parseSignalAddressFromJid = exports.parsePhoneJid = exports.parseJidFull = exports.normalizeRecipientJid = exports.normalizeDeviceJid = exports.isUserJid = exports.isStatusBroadcastJid = exports.isNewsletterJid = exports.isLidJid = exports.isHostedServer = exports.isHostedDeviceJid = exports.isHostedDeviceId = exports.isGroupOrBroadcastJid = exports.isGroupJid = exports.isBroadcastJid = exports.isBotJid = exports.getWaMediaHkdfInfo = exports.getWaCompanionPlatformId = exports.getLoginIdentity = exports.canonicalizeSignalServer = exports.canonicalizeSignalJid = exports.buildDeviceJid = exports.delay = exports.WaAuthMemoryStore = exports.createStore = exports.WA_VERSION = exports.createNoopLogger = exports.createPinoLogger = exports.PinoLogger = exports.ConsoleLogger = exports.fetchLatestWaMobileVersion = exports.fetchLatestWaWebVersion = exports.writeRandomPadMax16 = exports.unpadPkcs7 = exports.resolveMediaPayload = exports.getContextInfo = exports.unwrapMessage = exports.resolveMessageTarget = exports.resolveEncMediaType = exports.getContentType = exports.parseUsyncResultEnvelope = exports.createFileCompanionHostPersistence = exports.downloadMediaMessage = exports.defineWaClientPlugin = exports.WaClient = void 0;
4
- exports.proto = exports.WA_XMLNS = exports.WA_PRIVACY_VALUES = exports.WA_PRIVACY_TAGS = exports.WA_PRIVACY_SETTING_TO_CATEGORY = exports.WA_PRIVACY_DISALLOWED_LIST_CATEGORIES = exports.WA_PRIVACY_CATEGORY_TO_SETTING = exports.WA_PRIVACY_CATEGORIES = exports.WA_SUPPORTED_DIRTY_TYPES = exports.WA_STREAM_SIGNALING = exports.WA_SIGNALING = exports.WA_RETRYABLE_ACK_CODES = exports.WA_READY_STATES = exports.WA_PREVIEW_MEDIA_HKDF_INFO = exports.WA_PAIRING_KDF_INFO = exports.WA_NODE_TAGS = exports.WA_MEDIA_HKDF_INFO = exports.WA_MESSAGE_TYPES = exports.WA_MESSAGE_TAGS = exports.WA_LOGOUT_REASONS = exports.WA_IQ_TYPES = exports.WA_DISCONNECT_REASONS = exports.WA_DIRTY_TYPES = exports.WA_DIRTY_PROTOCOLS = exports.WA_DEFAULTS = exports.WA_COMPANION_PLATFORM_IDS = exports.WA_BUSINESS_HOURS_MODES = exports.WA_BUSINESS_HOURS_DAYS = exports.WA_BROWSERS = exports.WA_APP_STATE_SYNC_DATA_TYPE = exports.WA_APP_STATE_KEY_TYPES = exports.WA_APP_STATE_KDF_INFO = exports.WA_APP_STATE_ERROR_CODES = exports.WA_APP_STATE_COLLECTION_STATES = void 0;
3
+ exports.toUserJid = exports.splitJid = exports.signalAddressKey = exports.parseSignalAddressFromJid = exports.parsePhoneJid = exports.parseJidFull = exports.normalizeRecipientJid = exports.normalizeDeviceJid = exports.isUserJid = exports.isStatusBroadcastJid = exports.isNewsletterJid = exports.isLidJid = exports.isHostedServer = exports.isHostedDeviceJid = exports.isHostedDeviceId = exports.isGroupOrBroadcastJid = exports.isGroupJid = exports.isBroadcastJid = exports.isBotJid = exports.getWaMediaHkdfInfo = exports.getWaCompanionPlatformId = exports.getLoginIdentity = exports.canonicalizeSignalServer = exports.canonicalizeSignalJid = exports.buildDeviceJid = exports.delay = exports.WaAuthMemoryStore = exports.createStore = exports.WA_VERSION = exports.createNoopLogger = exports.createPinoLogger = exports.PinoLogger = exports.ConsoleLogger = exports.fetchLatestWaMobileVersion = exports.fetchLatestWaWebVersion = exports.writeRandomPadMax16 = exports.unpadPkcs7 = exports.resolveMediaPayload = exports.getContextInfo = exports.unwrapMessage = exports.resolveMessageTarget = exports.resolveEncMediaType = exports.getContentType = exports.parseUsyncResultEnvelope = exports.createFileCompanionHostPersistence = exports.WaMediaTransferClient = exports.WaMediaCrypto = exports.downloadMediaMessage = exports.defineWaClientPlugin = exports.WaClient = void 0;
4
+ exports.proto = exports.WA_XMLNS = exports.WA_PRIVACY_VALUES = exports.WA_PRIVACY_TAGS = exports.WA_PRIVACY_SETTING_TO_CATEGORY = exports.WA_PRIVACY_DISALLOWED_LIST_CATEGORIES = exports.WA_PRIVACY_CATEGORY_TO_SETTING = exports.WA_PRIVACY_CATEGORIES = exports.WA_SUPPORTED_DIRTY_TYPES = exports.WA_STREAM_SIGNALING = exports.WA_SIGNALING = exports.WA_RETRYABLE_ACK_CODES = exports.WA_READY_STATES = exports.WA_PREVIEW_MEDIA_HKDF_INFO = exports.WA_PAIRING_KDF_INFO = exports.WA_NODE_TAGS = exports.WA_MEDIA_HKDF_INFO = exports.WA_MESSAGE_TYPES = exports.WA_MESSAGE_TAGS = exports.WA_LOGOUT_REASONS = exports.WA_IQ_TYPES = exports.WA_DISCONNECT_REASONS = exports.WA_DIRTY_TYPES = exports.WA_DIRTY_PROTOCOLS = exports.WA_DEFAULTS = exports.WA_COMPANION_PLATFORM_IDS = exports.WA_BUSINESS_HOURS_MODES = exports.WA_BUSINESS_HOURS_DAYS = exports.WA_BROWSERS = exports.WA_APP_STATE_SYNC_DATA_TYPE = exports.WA_APP_STATE_KEY_TYPES = exports.WA_APP_STATE_KDF_INFO = exports.WA_APP_STATE_ERROR_CODES = exports.WA_APP_STATE_COLLECTION_STATES = exports.WA_APP_STATE_COLLECTIONS = exports.WA_ACCOUNT_SYNC_PROTOCOLS = void 0;
5
5
  var _client_1 = require("./client");
6
6
  Object.defineProperty(exports, "WaClient", { enumerable: true, get: function () { return _client_1.WaClient; } });
7
7
  var plugins_1 = require("./client/plugins");
8
8
  Object.defineProperty(exports, "defineWaClientPlugin", { enumerable: true, get: function () { return plugins_1.defineWaClientPlugin; } });
9
9
  var media_1 = require("./client/media");
10
10
  Object.defineProperty(exports, "downloadMediaMessage", { enumerable: true, get: function () { return media_1.downloadMediaMessage; } });
11
+ var WaMediaCrypto_1 = require("./media/crypto/WaMediaCrypto");
12
+ Object.defineProperty(exports, "WaMediaCrypto", { enumerable: true, get: function () { return WaMediaCrypto_1.WaMediaCrypto; } });
13
+ var WaMediaTransferClient_1 = require("./media/transfer/WaMediaTransferClient");
14
+ Object.defineProperty(exports, "WaMediaTransferClient", { enumerable: true, get: function () { return WaMediaTransferClient_1.WaMediaTransferClient; } });
11
15
  var companion_host_1 = require("./client/persistence/companion-host");
12
16
  Object.defineProperty(exports, "createFileCompanionHostPersistence", { enumerable: true, get: function () { return companion_host_1.createFileCompanionHostPersistence; } });
13
17
  var usync_1 = require("./transport/node/builders/usync");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zapo-js",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "High-performance WhatsApp Web TypeScript library",
5
5
  "license": "MIT",
6
6
  "author": "vinikjkkj <contact@vinicius.email> (https://github.com/vinikjkkj)",