whalibmob 5.24.0 → 5.24.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/README.md +125 -0
- package/index.d.ts +220 -2
- package/index.js +232 -26
- package/lib/messages/MediaRetry.js +8 -2
- package/lib/proto/MessageProto.js +102 -11
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1664,6 +1664,82 @@ Everything the CLI does is available as a Node.js library. The sections below
|
|
|
1664
1664
|
cover connecting an account, sending and receiving every message type, groups,
|
|
1665
1665
|
communities, channels, presence, privacy, history sync, and device emulation.
|
|
1666
1666
|
|
|
1667
|
+
### What the package exports
|
|
1668
|
+
|
|
1669
|
+
Two layers. The first is the flat one the rest of this document uses — the
|
|
1670
|
+
eighty-two names the common paths are built from:
|
|
1671
|
+
|
|
1672
|
+
```js
|
|
1673
|
+
const { WhalibmobClient, createNewStore, requestSmsCode } = require('whalibmob')
|
|
1674
|
+
```
|
|
1675
|
+
|
|
1676
|
+
The second is everything else. The library is ninety-five modules and close to
|
|
1677
|
+
five hundred exported names, and the flat layer picks out under a fifth of
|
|
1678
|
+
them. The rest are reachable as **namespaces**, each one a module of `lib/`
|
|
1679
|
+
under a name of its own:
|
|
1680
|
+
|
|
1681
|
+
```js
|
|
1682
|
+
const wa = require('whalibmob')
|
|
1683
|
+
|
|
1684
|
+
wa.MediaService.getMediaKeyName('ptt') // → 'WhatsApp Audio Keys'
|
|
1685
|
+
wa.MessageProto.decodeMessageContainer(buf) // raw bytes → a decoded message
|
|
1686
|
+
wa.Messages.MediaRetry.mediaRetryKey(key) // the retry-receipt key
|
|
1687
|
+
wa.Signal.libsignal.SessionCipher // the vendored libsignal
|
|
1688
|
+
wa.AppState.SyncdProto // app-state mutation records
|
|
1689
|
+
wa.Image.Jpeg // the JPEG header reader
|
|
1690
|
+
```
|
|
1691
|
+
|
|
1692
|
+
A namespace is named after the module it comes from, and a directory in `lib/`
|
|
1693
|
+
becomes one namespace rather than several — so `Signal` is all of
|
|
1694
|
+
`lib/signal/`, `AppState` all of `lib/appstate/`, `Messages` all of
|
|
1695
|
+
`lib/messages/`. Two names could not be had: **`Devices`** is
|
|
1696
|
+
`lib/DeviceManager.js`, because the flat `DeviceManager` is the class rather
|
|
1697
|
+
than the module, and **`Proto`** is `lib/proto.js` while the message protobufs
|
|
1698
|
+
in `lib/proto/` are **`MessageProto`**.
|
|
1699
|
+
|
|
1700
|
+
Deep requires work as well, and always have — the package declares no `exports`
|
|
1701
|
+
map, so nothing is sealed off:
|
|
1702
|
+
|
|
1703
|
+
```js
|
|
1704
|
+
const MediaService = require('whalibmob/lib/MediaService')
|
|
1705
|
+
// the same object, by a longer name
|
|
1706
|
+
require('whalibmob').MediaService === MediaService // true
|
|
1707
|
+
```
|
|
1708
|
+
|
|
1709
|
+
Adding the namespaces renamed and removed nothing: every name the library
|
|
1710
|
+
exported before is still exported, still holding what it held.
|
|
1711
|
+
|
|
1712
|
+
> [!NOTE]
|
|
1713
|
+
> The namespaces are typed as far as the rest of this document goes — media,
|
|
1714
|
+
> the message protobufs, media retry. Past that they are `any`, which is what
|
|
1715
|
+
> the internals have always been in `index.d.ts`. They are reachable, not
|
|
1716
|
+
> described.
|
|
1717
|
+
|
|
1718
|
+
### ES modules
|
|
1719
|
+
|
|
1720
|
+
The package is CommonJS, and every one of its names can be imported from an ES
|
|
1721
|
+
module — a bot written with `"type": "module"` needs no interop dance:
|
|
1722
|
+
|
|
1723
|
+
```js
|
|
1724
|
+
import { WhalibmobClient, createNewStore, requestSmsCode } from 'whalibmob'
|
|
1725
|
+
import { MediaService, MessageProto } from 'whalibmob'
|
|
1726
|
+
|
|
1727
|
+
// the default import is the whole export object, if you prefer it
|
|
1728
|
+
import wa from 'whalibmob'
|
|
1729
|
+
|
|
1730
|
+
// deep imports work too — ESM wants the extension, CommonJS does not
|
|
1731
|
+
import { downloadMedia } from 'whalibmob/lib/MediaService.js'
|
|
1732
|
+
```
|
|
1733
|
+
|
|
1734
|
+
> [!NOTE]
|
|
1735
|
+
> `import { … }` used to fail for all but five names. Node reads a CommonJS
|
|
1736
|
+
> module's names statically, and the reader gives up at the first property of
|
|
1737
|
+
> `module.exports` whose value is not a plain identifier — an inline arrow
|
|
1738
|
+
> function five entries in cost the other 116. `require()` and the default
|
|
1739
|
+
> import were unaffected, which is why it went unnoticed. Everything is now
|
|
1740
|
+
> bound to a name before it is exported, and a test fails if that stops being
|
|
1741
|
+
> true.
|
|
1742
|
+
|
|
1667
1743
|
## Connecting Account
|
|
1668
1744
|
|
|
1669
1745
|
### Register a New Number
|
|
@@ -3700,6 +3776,55 @@ const bytes = await client.downloadMedia(d, { verify: true })
|
|
|
3700
3776
|
with no media, no CDN location, an unsupported type, or a file that does not
|
|
3701
3777
|
match the message all say so.
|
|
3702
3778
|
|
|
3779
|
+
### View-once and disappearing messages
|
|
3780
|
+
|
|
3781
|
+
Some messages arrive inside an envelope. A photo sent as **view-once** is an
|
|
3782
|
+
ordinary `ImageMessage` wrapped in a `ViewOnceMessage`; the same photo in a
|
|
3783
|
+
chat with **disappearing messages** turned on is one wrapped in an
|
|
3784
|
+
`EphemeralMessage`. Nothing about the photo changes — the envelope only records
|
|
3785
|
+
how it was sent.
|
|
3786
|
+
|
|
3787
|
+
The decoder opens them, so nothing special is needed on your side. `msg.decoded`
|
|
3788
|
+
is the photo, and `downloadMedia()` works on it exactly as it does on any other:
|
|
3789
|
+
|
|
3790
|
+
```js
|
|
3791
|
+
client.on('message', async (msg) => {
|
|
3792
|
+
const d = msg.decoded
|
|
3793
|
+
if (!d || !d.mediaKey) return
|
|
3794
|
+
|
|
3795
|
+
if (d.viewOnce) console.log('sent as view-once')
|
|
3796
|
+
if (d.ephemeral) console.log('from a disappearing chat')
|
|
3797
|
+
|
|
3798
|
+
const bytes = await client.downloadMedia(d) // same call, wrapped or not
|
|
3799
|
+
})
|
|
3800
|
+
```
|
|
3801
|
+
|
|
3802
|
+
Three flags say how the message was sent, and are absent otherwise:
|
|
3803
|
+
|
|
3804
|
+
| flag | meaning |
|
|
3805
|
+
|---|---|
|
|
3806
|
+
| `d.viewOnce` | sent as view-once — `ViewOnceMessage`, `ViewOnceMessageV2` or the V2 extension a voice note uses |
|
|
3807
|
+
| `d.ephemeral` | from a chat with disappearing messages on |
|
|
3808
|
+
| `d.edited` | the new text of an edited message — the payload is the `protocol` message carrying it |
|
|
3809
|
+
|
|
3810
|
+
They stack. A view-once photo in a disappearing chat carries both:
|
|
3811
|
+
|
|
3812
|
+
```js
|
|
3813
|
+
if (d.viewOnce && d.ephemeral) { /* … */ }
|
|
3814
|
+
```
|
|
3815
|
+
|
|
3816
|
+
Documents sent with a caption (`DocumentWithCaptionMessage`) are unwrapped the
|
|
3817
|
+
same way and decode as a plain `document`, with no flag of their own.
|
|
3818
|
+
|
|
3819
|
+
> [!NOTE]
|
|
3820
|
+
> Opening the envelope is what makes this media reachable at all. Before it, a
|
|
3821
|
+
> view-once photo decoded as `{ type: 'unknown' }` — no `mediaKey`, no
|
|
3822
|
+
> `directPath` — and there was nothing for `downloadMedia()` to fetch.
|
|
3823
|
+
|
|
3824
|
+
Nothing here changes when the message came through history sync, or when it is
|
|
3825
|
+
one of your own echoed back to this device from another: those envelopes nest,
|
|
3826
|
+
and are unwrapped down to the message inside.
|
|
3827
|
+
|
|
3703
3828
|
### When the file is gone from the CDN
|
|
3704
3829
|
|
|
3705
3830
|
Media is not carried inside the message — the message carries a URL, a hash and
|
package/index.d.ts
CHANGED
|
@@ -229,6 +229,15 @@ export interface PollSendResult extends SendResult {
|
|
|
229
229
|
|
|
230
230
|
export interface DecodedBase {
|
|
231
231
|
type: string;
|
|
232
|
+
/**
|
|
233
|
+
* The message arrived inside a view-once envelope. The content is decoded
|
|
234
|
+
* normally — media included — this only records how it was sent.
|
|
235
|
+
*/
|
|
236
|
+
viewOnce?: boolean;
|
|
237
|
+
/** The message belongs to a chat with disappearing messages turned on. */
|
|
238
|
+
ephemeral?: boolean;
|
|
239
|
+
/** The message is the new text of an edit. */
|
|
240
|
+
edited?: boolean;
|
|
232
241
|
[key: string]: any;
|
|
233
242
|
}
|
|
234
243
|
|
|
@@ -586,9 +595,22 @@ export interface MediaRetryNotification {
|
|
|
586
595
|
}
|
|
587
596
|
|
|
588
597
|
export interface MediaRetryResult {
|
|
598
|
+
/** `true` only when the phone actually re-uploaded the file. */
|
|
589
599
|
ok: boolean;
|
|
590
|
-
|
|
591
|
-
|
|
600
|
+
/**
|
|
601
|
+
* `MediaRetryNotification.result` — `1` is SUCCESS. `null` when the phone
|
|
602
|
+
* answered with an error rather than a payload.
|
|
603
|
+
*/
|
|
604
|
+
result: number | null;
|
|
605
|
+
/**
|
|
606
|
+
* Where the file was put back. **`null`, not absent**, whenever `ok` is
|
|
607
|
+
* false — check it against `null`, or just read `ok`.
|
|
608
|
+
*/
|
|
609
|
+
directPath: string | null;
|
|
610
|
+
/** The message the retry was for. Absent when the phone answered an error. */
|
|
611
|
+
stanzaId?: string;
|
|
612
|
+
/** Set instead of a location when the phone no longer has the file either. */
|
|
613
|
+
error?: any;
|
|
592
614
|
[key: string]: any;
|
|
593
615
|
}
|
|
594
616
|
|
|
@@ -1112,3 +1134,199 @@ export declare const WEB_EVENTS: any;
|
|
|
1112
1134
|
export declare const WEB_GLOBALS: any;
|
|
1113
1135
|
export declare function decodeArgo(buf: Buffer): any;
|
|
1114
1136
|
export declare function tryDecodeArgo(buf: Buffer): any;
|
|
1137
|
+
|
|
1138
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
1139
|
+
// Namespaces
|
|
1140
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
1141
|
+
|
|
1142
|
+
/**
|
|
1143
|
+
* Everything above this line is a name picked out of a module by hand.
|
|
1144
|
+
* Everything below is the rest of the library: each module of `lib/`, whole,
|
|
1145
|
+
* under a name of its own. Nothing above is renamed or removed by any of it.
|
|
1146
|
+
*
|
|
1147
|
+
* `wa.MediaService` is the object `require('whalibmob/lib/MediaService')`
|
|
1148
|
+
* returns — deep requires work too, and always have. Four namespaces are
|
|
1149
|
+
* gathered from the several modules of a directory rather than being one
|
|
1150
|
+
* module: `Signal`, `Signal.libsignal`, `AppState` and `Image`.
|
|
1151
|
+
*
|
|
1152
|
+
* The members declared on each namespace below are typed. The rest of a module
|
|
1153
|
+
* is reachable and untyped, the same way `MessageSender` and `SignalProtocol`
|
|
1154
|
+
* are above — this file has never claimed to describe the internals, and a
|
|
1155
|
+
* namespace does not change that.
|
|
1156
|
+
*/
|
|
1157
|
+
export interface LibModule {
|
|
1158
|
+
[name: string]: any;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
/**
|
|
1162
|
+
* `whalibmob/lib/MediaService` — the encryption, upload and download beneath
|
|
1163
|
+
* `client.downloadMedia()`. Everything it exports is typed.
|
|
1164
|
+
*/
|
|
1165
|
+
export declare const MediaService: {
|
|
1166
|
+
/** Upload path and HKDF info string per media type. */
|
|
1167
|
+
MEDIA_PATH: Record<string, { path: string; keyName: string }>;
|
|
1168
|
+
encryptMedia(plaintext: Buffer, mediaKey: Buffer, keyName: string): EncryptedMedia;
|
|
1169
|
+
decryptMedia(encryptedWithMac: Buffer, mediaKey: Buffer, keyName: string): Buffer;
|
|
1170
|
+
uploadMedia(...args: any[]): Promise<any>;
|
|
1171
|
+
/** `opts.fileEncSha256` checks the blob as it arrived, before decrypting. */
|
|
1172
|
+
downloadMedia(
|
|
1173
|
+
url: string,
|
|
1174
|
+
mediaKey: Buffer,
|
|
1175
|
+
keyName: string,
|
|
1176
|
+
opts?: { web?: boolean; fileEncSha256?: Buffer }
|
|
1177
|
+
): Promise<Buffer>;
|
|
1178
|
+
/** Plain GET, no decryption — profile pictures are served in the clear. */
|
|
1179
|
+
httpGetBuffer(url: string, opts?: { web?: boolean }): Promise<Buffer>;
|
|
1180
|
+
/** HKDF-SHA256 over the media key: 112 bytes of IV, cipher key and MAC key. */
|
|
1181
|
+
deriveMediaKeyData(mediaKey: Buffer, keyName: string): Buffer;
|
|
1182
|
+
/** `'image'` → `'WhatsApp Image Keys'`. `null` for a type it does not know. */
|
|
1183
|
+
getMediaKeyName(mediaType: string): string | null;
|
|
1184
|
+
/** The absolute URL, built from `directPath` when there is no `url`. */
|
|
1185
|
+
resolveMediaUrl(url: string | null, directPath: string | null): string | null;
|
|
1186
|
+
};
|
|
1187
|
+
|
|
1188
|
+
/** `whalibmob/lib/proto/MessageProto` — the message protobufs. */
|
|
1189
|
+
export declare const MessageProto: LibModule & {
|
|
1190
|
+
/**
|
|
1191
|
+
* Raw decrypted bytes into a `DecodedMessage`. Envelopes — view-once,
|
|
1192
|
+
* ephemeral, deviceSent, documentWithCaption, edited — are opened, so what
|
|
1193
|
+
* comes back is the message inside with `viewOnce` / `ephemeral` / `edited`
|
|
1194
|
+
* recorded on it. `depth` is internal; leave it unset.
|
|
1195
|
+
*/
|
|
1196
|
+
decodeMessageContainer(buf: Buffer, depth?: number): DecodedMessage;
|
|
1197
|
+
/** The generic protobuf field walker, keyed by field number. */
|
|
1198
|
+
decodeFields(buf: Buffer): Record<number, any>;
|
|
1199
|
+
encodeMessage(...args: any[]): Buffer;
|
|
1200
|
+
encodeImageMessage(opts: Record<string, any>): Buffer;
|
|
1201
|
+
encodeVideoMessage(opts: Record<string, any>): Buffer;
|
|
1202
|
+
encodeAudioMessage(opts: Record<string, any>): Buffer;
|
|
1203
|
+
encodeDocumentMessage(opts: Record<string, any>): Buffer;
|
|
1204
|
+
encodeStickerMessage(opts: Record<string, any>): Buffer;
|
|
1205
|
+
encodePollCreationMessage(opts: Record<string, any>):
|
|
1206
|
+
{ payload: Buffer; messageSecret: Buffer; encKey: Buffer };
|
|
1207
|
+
};
|
|
1208
|
+
|
|
1209
|
+
/** `whalibmob/lib/messages/` — sending, and the receipts that go with it. */
|
|
1210
|
+
export declare const Messages: {
|
|
1211
|
+
MessageSender: LibModule;
|
|
1212
|
+
/**
|
|
1213
|
+
* Asking the sender's phone to upload a file again. `client
|
|
1214
|
+
* .requestMediaRetry()` and `client.decryptMediaRetry()` are these, wired up.
|
|
1215
|
+
*/
|
|
1216
|
+
MediaRetry: {
|
|
1217
|
+
/** HKDF-SHA256(mediaKey, "WhatsApp Media Retry Notification"), 32 bytes. */
|
|
1218
|
+
mediaRetryKey(mediaKey: Buffer): Buffer;
|
|
1219
|
+
encryptRetryReceipt(msgId: string, mediaKey: Buffer):
|
|
1220
|
+
{ ciphertext: Buffer; iv: Buffer };
|
|
1221
|
+
decryptRetryNotification(
|
|
1222
|
+
notif: MediaRetryNotification, mediaKey: Buffer): MediaRetryResult;
|
|
1223
|
+
buildRetryReceiptNode(
|
|
1224
|
+
info: { id: string; chatJid: Jid; fromMe: boolean; participant?: Jid },
|
|
1225
|
+
mediaKey: Buffer, ownJid: Jid, BinaryNode: any): any;
|
|
1226
|
+
/** `null` when the node is not a media-retry notification. */
|
|
1227
|
+
parseRetryNotification(node: any, helpers: {
|
|
1228
|
+
findChild: (node: any, tag: string) => any;
|
|
1229
|
+
getContent: (node: any) => Buffer | null;
|
|
1230
|
+
}): MediaRetryNotification | null;
|
|
1231
|
+
RETRY_KEY_INFO: string;
|
|
1232
|
+
};
|
|
1233
|
+
ReportingToken: LibModule;
|
|
1234
|
+
TcTokenStore: LibModule;
|
|
1235
|
+
};
|
|
1236
|
+
|
|
1237
|
+
/**
|
|
1238
|
+
* `whalibmob/lib/signal/` — SignalProtocol.js, SignalStore.js and SenderKey.js
|
|
1239
|
+
* gathered into one namespace, with the two vendored libraries under their own.
|
|
1240
|
+
*/
|
|
1241
|
+
export declare const Signal: LibModule & {
|
|
1242
|
+
SignalProtocol: any;
|
|
1243
|
+
SignalStore: any;
|
|
1244
|
+
SenderKeyStore: any;
|
|
1245
|
+
SenderKeyCrypto: any;
|
|
1246
|
+
/** Group ciphers and sender-key records. */
|
|
1247
|
+
WaSignalGroup: LibModule;
|
|
1248
|
+
/**
|
|
1249
|
+
* The vendored libsignal. Its own barrel leaves six modules out; they are
|
|
1250
|
+
* here — `BaseKeyType`, `ChainType`, `protobufs`, `queueJob`,
|
|
1251
|
+
* `FingerprintGenerator`, `textsecure`.
|
|
1252
|
+
*/
|
|
1253
|
+
libsignal: LibModule;
|
|
1254
|
+
};
|
|
1255
|
+
|
|
1256
|
+
/** `whalibmob/lib/appstate/` — the synced state a companion keeps in step. */
|
|
1257
|
+
export declare const AppState: LibModule & {
|
|
1258
|
+
AppStateStore: any;
|
|
1259
|
+
COLLECTIONS: string[];
|
|
1260
|
+
AppStateSync: LibModule;
|
|
1261
|
+
LTHash: LibModule;
|
|
1262
|
+
Mutations: LibModule;
|
|
1263
|
+
SyncdProto: LibModule;
|
|
1264
|
+
};
|
|
1265
|
+
|
|
1266
|
+
/**
|
|
1267
|
+
* `whalibmob/lib/image/` — the decoders behind the inline thumbnails, and the
|
|
1268
|
+
* JPEG encoder that writes them. No native dependency; sharp or jimp are used
|
|
1269
|
+
* when installed and this is what runs when they are not.
|
|
1270
|
+
*/
|
|
1271
|
+
export declare const Image: LibModule & {
|
|
1272
|
+
/** Width and height from the file header, for every format below. */
|
|
1273
|
+
decodeImage(buf: Buffer): any;
|
|
1274
|
+
canDecode(buf: Buffer): boolean;
|
|
1275
|
+
toSquareJpeg(...args: any[]): Buffer | null;
|
|
1276
|
+
toScaledJpeg(...args: any[]): Buffer | null;
|
|
1277
|
+
encodeJpeg(...args: any[]): Buffer;
|
|
1278
|
+
Jpeg: LibModule;
|
|
1279
|
+
JpegEncoder: LibModule;
|
|
1280
|
+
Png: LibModule;
|
|
1281
|
+
Gif: LibModule;
|
|
1282
|
+
Bmp: LibModule;
|
|
1283
|
+
};
|
|
1284
|
+
|
|
1285
|
+
/** Registration, sessions, and the device a session presents itself as. */
|
|
1286
|
+
export declare const Client: LibModule;
|
|
1287
|
+
export declare const Registration: LibModule;
|
|
1288
|
+
export declare const Store: LibModule;
|
|
1289
|
+
export declare const WebStore: LibModule;
|
|
1290
|
+
export declare const DeviceConfig: LibModule;
|
|
1291
|
+
/**
|
|
1292
|
+
* `whalibmob/lib/DeviceManager` — the module, not the class. The flat
|
|
1293
|
+
* `DeviceManager` export is the class; this is what it comes from, so
|
|
1294
|
+
* `makeDeviceJid`, `phoneFromJid` and `jidStrToObj` can be reached too.
|
|
1295
|
+
*/
|
|
1296
|
+
export declare const Devices: LibModule;
|
|
1297
|
+
export declare const PlayStore: LibModule;
|
|
1298
|
+
export declare const PlayStoreDevice: LibModule;
|
|
1299
|
+
export declare const AndroidApk: LibModule;
|
|
1300
|
+
export declare const Attestation: LibModule;
|
|
1301
|
+
export declare const Tokens: LibModule;
|
|
1302
|
+
export declare const PushClient: LibModule;
|
|
1303
|
+
export declare const Fcm: LibModule;
|
|
1304
|
+
export declare const FcmMcs: LibModule;
|
|
1305
|
+
|
|
1306
|
+
/** Linking to an account that already exists. */
|
|
1307
|
+
export declare const PairingCode: LibModule;
|
|
1308
|
+
export declare const CompanionPairing: LibModule;
|
|
1309
|
+
export declare const QrPairing: LibModule;
|
|
1310
|
+
export declare const WebVersion: LibModule;
|
|
1311
|
+
export declare const WebProto: LibModule;
|
|
1312
|
+
|
|
1313
|
+
/** The wire. */
|
|
1314
|
+
export declare const BinaryNode: LibModule;
|
|
1315
|
+
export declare const Noise: LibModule;
|
|
1316
|
+
export declare const WebSocketStream: LibModule;
|
|
1317
|
+
export declare const Socks: LibModule;
|
|
1318
|
+
export declare const OfflineNodeProcessor: LibModule;
|
|
1319
|
+
export declare const Constants: LibModule;
|
|
1320
|
+
export declare const Logger: LibModule;
|
|
1321
|
+
/**
|
|
1322
|
+
* `whalibmob/lib/proto.js` — the handshake payloads. The message protobufs are
|
|
1323
|
+
* the directory of the same name, and are `MessageProto`.
|
|
1324
|
+
*/
|
|
1325
|
+
export declare const Proto: LibModule;
|
|
1326
|
+
|
|
1327
|
+
export declare const MediaThumbnail: LibModule;
|
|
1328
|
+
export declare const GroupParticipant: LibModule;
|
|
1329
|
+
export declare const HistorySyncHandler: LibModule;
|
|
1330
|
+
export declare const AuthUtils: LibModule;
|
|
1331
|
+
export declare const Argo: LibModule;
|
|
1332
|
+
export declare const WAUSync: LibModule;
|
package/index.js
CHANGED
|
@@ -55,27 +55,159 @@ const {
|
|
|
55
55
|
HistorySyncTypeName
|
|
56
56
|
} = require('./lib/HistorySyncHandler');
|
|
57
57
|
|
|
58
|
+
// ─── Everything below is bound to a name before it is exported ───────────────
|
|
59
|
+
//
|
|
60
|
+
// Not for tidiness. Node reads a CommonJS module's names statically when
|
|
61
|
+
// something imports it as ESM, and the reader it uses gives up at the first
|
|
62
|
+
// property of module.exports whose value is not a plain identifier. There used
|
|
63
|
+
// to be an arrow function five entries in, and everything after it — a hundred
|
|
64
|
+
// and sixteen of the hundred and twenty-one names — could not be imported by
|
|
65
|
+
// name at all:
|
|
66
|
+
//
|
|
67
|
+
// import { createNewStore } from 'whalibmob'
|
|
68
|
+
// SyntaxError: Named export 'createNewStore' not found.
|
|
69
|
+
//
|
|
70
|
+
// The default import always worked and still does, so this broke no CommonJS
|
|
71
|
+
// caller and no `import wa from 'whalibmob'`. It broke `import { … }`, which is
|
|
72
|
+
// how a bot written as an ES module reaches for anything.
|
|
73
|
+
//
|
|
74
|
+
// So: functions defined here get a name first, member accesses get a name
|
|
75
|
+
// first, and the modules below get a name first. The object at the bottom is
|
|
76
|
+
// nothing but identifiers, and test/public-surface.test.js fails if any name
|
|
77
|
+
// stops being importable that way.
|
|
78
|
+
|
|
79
|
+
// Receive the verification code as a silent push, without an SMS — opens the
|
|
80
|
+
// listener the native client of that platform keeps open. See "Receiving the
|
|
81
|
+
// code over push" in the README.
|
|
82
|
+
//
|
|
83
|
+
// Routed through the push client for the device's platform: Android opens the
|
|
84
|
+
// Firebase MCS stream, iOS resolves null because APNs is not implemented and
|
|
85
|
+
// an iOS session holds no Firebase identity to listen with.
|
|
86
|
+
const receivePushCode = (store, device, opts) => {
|
|
87
|
+
const dev = device || (store && store.device);
|
|
88
|
+
return require('./lib/PushClient')
|
|
89
|
+
.pushClientFor(dev)
|
|
90
|
+
.receivePushCode(store, dev, opts);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Whether the device profile can do push verification at all.
|
|
94
|
+
const supportsPush = (device) => require('./lib/PushClient').supportsPush(device);
|
|
95
|
+
|
|
96
|
+
// Where a session's files live: one folder per number inside the
|
|
97
|
+
// authentication folder. The same resolver the CLI uses.
|
|
98
|
+
const {
|
|
99
|
+
defaultBaseDir, sessionDirFor, storeFileFor, webStoreFileFor,
|
|
100
|
+
listSessions, migrateSession
|
|
101
|
+
} = SessionPaths;
|
|
102
|
+
|
|
103
|
+
const { encodeWAM, BinaryInfo, WEB_EVENTS, WEB_GLOBALS } = WAM;
|
|
104
|
+
|
|
105
|
+
// ─── The namespaces ──────────────────────────────────────────────────────────
|
|
106
|
+
//
|
|
107
|
+
// Every module of lib/, whole, under a name of its own — see the note at the
|
|
108
|
+
// foot of module.exports for what the names mean and why they are these.
|
|
109
|
+
|
|
110
|
+
const Client = require('./lib/Client');
|
|
111
|
+
const Registration = require('./lib/Registration');
|
|
112
|
+
const Store = require('./lib/Store');
|
|
113
|
+
const WebStore = require('./lib/WebStore');
|
|
114
|
+
const DeviceConfig = require('./lib/DeviceConfig');
|
|
115
|
+
const Devices = require('./lib/DeviceManager');
|
|
116
|
+
const PlayStore = require('./lib/PlayStore');
|
|
117
|
+
const PlayStoreDevice = require('./lib/playstore-device');
|
|
118
|
+
const AndroidApk = require('./lib/AndroidApk');
|
|
119
|
+
const Attestation = require('./lib/Attestation');
|
|
120
|
+
const Tokens = require('./lib/tokens');
|
|
121
|
+
const PushClient = require('./lib/PushClient');
|
|
122
|
+
const Fcm = require('./lib/fcm');
|
|
123
|
+
const FcmMcs = require('./lib/fcm-mcs');
|
|
124
|
+
|
|
125
|
+
const PairingCode = require('./lib/PairingCode');
|
|
126
|
+
const CompanionPairing = require('./lib/CompanionPairing');
|
|
127
|
+
const QrPairing = require('./lib/QrPairing');
|
|
128
|
+
const WebVersion = require('./lib/WebVersion');
|
|
129
|
+
const WebProto = require('./lib/webproto');
|
|
130
|
+
|
|
131
|
+
const BinaryNode = require('./lib/BinaryNode');
|
|
132
|
+
const Noise = require('./lib/noise');
|
|
133
|
+
const WebSocketStream = require('./lib/WebSocketStream');
|
|
134
|
+
const Socks = require('./lib/socks');
|
|
135
|
+
const OfflineNodeProcessor = require('./lib/OfflineNodeProcessor');
|
|
136
|
+
const Constants = require('./lib/constants');
|
|
137
|
+
const Logger = require('./lib/logger');
|
|
138
|
+
// lib/proto.js — the handshake payloads. The message protobufs are the
|
|
139
|
+
// directory of the same name, and are MessageProto.
|
|
140
|
+
const Proto = require('./lib/proto.js');
|
|
141
|
+
|
|
142
|
+
const MessageProto = require('./lib/proto/MessageProto');
|
|
143
|
+
const MediaService = require('./lib/MediaService');
|
|
144
|
+
const MediaThumbnail = require('./lib/MediaThumbnail');
|
|
145
|
+
const GroupParticipant = require('./lib/GroupParticipant');
|
|
146
|
+
|
|
147
|
+
const Messages = {
|
|
148
|
+
MessageSender: require('./lib/messages/MessageSender'),
|
|
149
|
+
MediaRetry: require('./lib/messages/MediaRetry'),
|
|
150
|
+
ReportingToken: require('./lib/messages/ReportingToken'),
|
|
151
|
+
TcTokenStore: require('./lib/messages/TcTokenStore')
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// The decoders behind the inline thumbnails, and the encoder that makes them.
|
|
155
|
+
// lib/image/index.js is the whole story for most callers; the per-format
|
|
156
|
+
// modules are here for the rest.
|
|
157
|
+
const Image = Object.assign({}, require('./lib/image'), {
|
|
158
|
+
Jpeg: require('./lib/image/Jpeg'),
|
|
159
|
+
JpegEncoder: require('./lib/image/JpegEncoder'),
|
|
160
|
+
Png: require('./lib/image/Png'),
|
|
161
|
+
Gif: require('./lib/image/Gif'),
|
|
162
|
+
Bmp: require('./lib/image/Bmp')
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const HistorySyncHandler = require('./lib/HistorySyncHandler');
|
|
166
|
+
const AuthUtils = require('./lib/auth-utils');
|
|
167
|
+
const Argo = require('./lib/argo/ArgoDecoder');
|
|
168
|
+
const WAUSync = require('./lib/WAUSync');
|
|
169
|
+
|
|
170
|
+
// Signal — lib/signal/. SignalProtocol.js, SignalStore.js and SenderKey.js have
|
|
171
|
+
// no name in common, so the three are one namespace. The two vendored libraries
|
|
172
|
+
// keep theirs, being libraries.
|
|
173
|
+
const Signal = Object.assign({},
|
|
174
|
+
require('./lib/signal/SignalProtocol'),
|
|
175
|
+
require('./lib/signal/SignalStore'),
|
|
176
|
+
require('./lib/signal/SenderKey'),
|
|
177
|
+
{
|
|
178
|
+
WaSignalGroup: require('./lib/signal/WaSignalGroup'),
|
|
179
|
+
// libsignal's own barrel leaves six of its modules out. They are added to a
|
|
180
|
+
// copy of it here rather than written into it, so
|
|
181
|
+
// require('whalibmob/lib/signal/libsignal') stays exactly what it was.
|
|
182
|
+
libsignal: Object.assign({}, require('./lib/signal/libsignal'), {
|
|
183
|
+
BaseKeyType: require('./lib/signal/libsignal/base_key_type'),
|
|
184
|
+
ChainType: require('./lib/signal/libsignal/chain_type'),
|
|
185
|
+
protobufs: require('./lib/signal/libsignal/protobufs'),
|
|
186
|
+
queueJob: require('./lib/signal/libsignal/queue_job'),
|
|
187
|
+
FingerprintGenerator:
|
|
188
|
+
require('./lib/signal/libsignal/numeric_fingerprint').FingerprintGenerator,
|
|
189
|
+
textsecure:
|
|
190
|
+
require('./lib/signal/libsignal/WhisperTextProtocol').textsecure
|
|
191
|
+
})
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// App state — lib/appstate/. AppStateStore.js is spread because the flat
|
|
195
|
+
// AppStateStore is its class; the other three are whole.
|
|
196
|
+
const AppState = Object.assign({}, require('./lib/appstate/AppStateStore'), {
|
|
197
|
+
AppStateSync: require('./lib/appstate/AppStateSync'),
|
|
198
|
+
LTHash: require('./lib/appstate/LTHash'),
|
|
199
|
+
Mutations: require('./lib/appstate/Mutations'),
|
|
200
|
+
SyncdProto: require('./lib/appstate/SyncdProto')
|
|
201
|
+
});
|
|
202
|
+
|
|
58
203
|
module.exports = {
|
|
59
204
|
WhalibmobClient,
|
|
60
205
|
checkNumberStatus,
|
|
61
206
|
checkIfRegistered,
|
|
62
207
|
requestSmsCode,
|
|
63
208
|
verifyCode,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// code over push" in the README.
|
|
67
|
-
//
|
|
68
|
-
// Routed through the push client for the device's platform: Android opens the
|
|
69
|
-
// Firebase MCS stream, iOS resolves null because APNs is not implemented and
|
|
70
|
-
// an iOS session holds no Firebase identity to listen with.
|
|
71
|
-
receivePushCode: (store, device, opts) => {
|
|
72
|
-
const dev = device || (store && store.device);
|
|
73
|
-
return require('./lib/PushClient')
|
|
74
|
-
.pushClientFor(dev)
|
|
75
|
-
.receivePushCode(store, dev, opts);
|
|
76
|
-
},
|
|
77
|
-
// Whether the device profile can do push verification at all.
|
|
78
|
-
supportsPush: (device) => require('./lib/PushClient').supportsPush(device),
|
|
209
|
+
receivePushCode,
|
|
210
|
+
supportsPush,
|
|
79
211
|
assertRegistrationKeys,
|
|
80
212
|
// Version fetch — use fetchWaVersion for device-aware (iOS or Android) fetching.
|
|
81
213
|
// fetchIosVersion is kept for backward compatibility.
|
|
@@ -89,12 +221,12 @@ module.exports = {
|
|
|
89
221
|
// Where a session's files live: one folder per number inside the
|
|
90
222
|
// authentication folder. The same resolver the CLI uses.
|
|
91
223
|
SessionPaths,
|
|
92
|
-
defaultBaseDir
|
|
93
|
-
sessionDirFor
|
|
94
|
-
storeFileFor
|
|
95
|
-
webStoreFileFor
|
|
96
|
-
listSessions
|
|
97
|
-
migrateSession
|
|
224
|
+
defaultBaseDir,
|
|
225
|
+
sessionDirFor,
|
|
226
|
+
storeFileFor,
|
|
227
|
+
webStoreFileFor,
|
|
228
|
+
listSessions,
|
|
229
|
+
migrateSession,
|
|
98
230
|
// Device config — reads WA_OS / WA_DEVICE / WA_DEVICE_* from process.env
|
|
99
231
|
getDeviceConfig,
|
|
100
232
|
// Store helpers
|
|
@@ -123,10 +255,10 @@ module.exports = {
|
|
|
123
255
|
// encoder and the BinaryInfo holder, all as the reference client defines
|
|
124
256
|
// them. See client.wamBuffer / client.sendWAMBuffer().
|
|
125
257
|
WAM,
|
|
126
|
-
encodeWAM
|
|
127
|
-
BinaryInfo
|
|
128
|
-
WEB_EVENTS
|
|
129
|
-
WEB_GLOBALS
|
|
258
|
+
encodeWAM,
|
|
259
|
+
BinaryInfo,
|
|
260
|
+
WEB_EVENTS,
|
|
261
|
+
WEB_GLOBALS,
|
|
130
262
|
// Signal / encryption internals
|
|
131
263
|
SignalProtocol,
|
|
132
264
|
SignalStore,
|
|
@@ -171,5 +303,79 @@ module.exports = {
|
|
|
171
303
|
decryptHistoryBlob,
|
|
172
304
|
deriveHistoryKeys,
|
|
173
305
|
HistorySyncType,
|
|
174
|
-
HistorySyncTypeName
|
|
306
|
+
HistorySyncTypeName,
|
|
307
|
+
|
|
308
|
+
// ─── Namespaces ────────────────────────────────────────────────────────────
|
|
309
|
+
//
|
|
310
|
+
// Everything above is a name picked out of a module by hand — eighty-two of
|
|
311
|
+
// the four hundred and ninety-five the library actually has. What follows is
|
|
312
|
+
// the rest: every module, whole, under a name of its own. Nothing above is
|
|
313
|
+
// renamed, moved or removed by any of it.
|
|
314
|
+
//
|
|
315
|
+
// The rule is that a namespace is named after the module it comes from, and a
|
|
316
|
+
// directory in lib/ becomes one namespace rather than several. Two names
|
|
317
|
+
// could not be had: Devices is lib/DeviceManager.js, because the flat export
|
|
318
|
+
// of that name is the class rather than the module, and Proto is lib/proto.js,
|
|
319
|
+
// because lib/proto/ would want the same name and is MessageProto.
|
|
320
|
+
//
|
|
321
|
+
// Most are the very object a deep require returns —
|
|
322
|
+
// wa.MediaService === require('whalibmob/lib/MediaService') — which is worth
|
|
323
|
+
// knowing because deep requires work and always have: the package declares no
|
|
324
|
+
// `exports` map, so nothing here is the only way in. The four built with
|
|
325
|
+
// Object.assign (Signal, AppState, Image, Signal.libsignal) are new objects
|
|
326
|
+
// holding the same functions, gathered from the several modules of a
|
|
327
|
+
// directory. Nothing is copied out of a module and nothing is written back
|
|
328
|
+
// into one.
|
|
329
|
+
//
|
|
330
|
+
// test/public-surface.test.js walks lib/ and fails if any module has an
|
|
331
|
+
// export that none of this reaches, so the coverage is checked rather than
|
|
332
|
+
// claimed.
|
|
333
|
+
|
|
334
|
+
// Registration, sessions and the device a session presents itself as
|
|
335
|
+
Client,
|
|
336
|
+
Registration,
|
|
337
|
+
Store,
|
|
338
|
+
WebStore,
|
|
339
|
+
DeviceConfig,
|
|
340
|
+
Devices,
|
|
341
|
+
PlayStore,
|
|
342
|
+
PlayStoreDevice,
|
|
343
|
+
AndroidApk,
|
|
344
|
+
Attestation,
|
|
345
|
+
Tokens,
|
|
346
|
+
PushClient,
|
|
347
|
+
Fcm,
|
|
348
|
+
FcmMcs,
|
|
349
|
+
|
|
350
|
+
// Linking to an account that already exists
|
|
351
|
+
PairingCode,
|
|
352
|
+
CompanionPairing,
|
|
353
|
+
QrPairing,
|
|
354
|
+
WebVersion,
|
|
355
|
+
WebProto,
|
|
356
|
+
|
|
357
|
+
// The wire
|
|
358
|
+
BinaryNode,
|
|
359
|
+
Noise,
|
|
360
|
+
WebSocketStream,
|
|
361
|
+
Socks,
|
|
362
|
+
OfflineNodeProcessor,
|
|
363
|
+
Constants,
|
|
364
|
+
Logger,
|
|
365
|
+
Proto,
|
|
366
|
+
|
|
367
|
+
// Messages and media
|
|
368
|
+
MessageProto,
|
|
369
|
+
Messages,
|
|
370
|
+
MediaService,
|
|
371
|
+
MediaThumbnail,
|
|
372
|
+
GroupParticipant,
|
|
373
|
+
Image,
|
|
374
|
+
|
|
375
|
+
HistorySyncHandler,
|
|
376
|
+
AuthUtils,
|
|
377
|
+
Argo,
|
|
378
|
+
WAUSync,
|
|
379
|
+
Signal,
|
|
380
|
+
AppState
|
|
175
381
|
};
|
|
@@ -116,8 +116,14 @@ function encryptRetryReceipt(msgId, mediaKey) {
|
|
|
116
116
|
*
|
|
117
117
|
* @param {object} notif { messageId, ciphertext, iv } off the wire
|
|
118
118
|
* @param {Buffer} mediaKey the same media key the receipt was sent with
|
|
119
|
-
* @returns {{result: number,
|
|
120
|
-
*
|
|
119
|
+
* @returns {{result: number|null, ok: boolean, directPath: string|null,
|
|
120
|
+
* stanzaId: string}}
|
|
121
|
+
* directPath is null rather than absent when the phone declined, so
|
|
122
|
+
* `ok` — or a check against null — is what tells the two apart.
|
|
123
|
+
*
|
|
124
|
+
* This used to promise a url, a handle and a ciphertextSha256. None of
|
|
125
|
+
* the three has ever been returned; the notification does not carry
|
|
126
|
+
* them.
|
|
121
127
|
*/
|
|
122
128
|
function decryptRetryNotification(notif, mediaKey) {
|
|
123
129
|
const { messageId, ciphertext, iv } = notif;
|
|
@@ -534,17 +534,67 @@ function _dbl(buf) {
|
|
|
534
534
|
return buf.readDoubleLE(0);
|
|
535
535
|
}
|
|
536
536
|
|
|
537
|
-
|
|
537
|
+
// ─── Envelopes ───────────────────────────────────────────────────────────────
|
|
538
|
+
//
|
|
539
|
+
// Several entries in Message carry no content of their own. They hold another
|
|
540
|
+
// Message and exist only to say something about it: that a photo may be opened
|
|
541
|
+
// once, that a message belongs to a disappearing chat, that a document was sent
|
|
542
|
+
// with a caption. The inner message is an ordinary ImageMessage or
|
|
543
|
+
// DocumentMessage — nothing about it changes.
|
|
544
|
+
//
|
|
545
|
+
// A decoder that does not open the envelope sees a field it has no branch for
|
|
546
|
+
// and calls the whole thing unknown. That is why view-once and disappearing
|
|
547
|
+
// media could not be downloaded at all: the mediaKey never reached the caller,
|
|
548
|
+
// and downloadMedia had no message to work from.
|
|
549
|
+
//
|
|
550
|
+
// Every one of these but DeviceSentMessage is a FutureProofMessage — one shape,
|
|
551
|
+
// { message = 1 }, under several names.
|
|
552
|
+
//
|
|
553
|
+
// field inner what it is
|
|
554
|
+
// 31 2 DeviceSentMessage { destinationJid=1, message=2, phash=3 }
|
|
555
|
+
// 37 1 viewOnceMessage
|
|
556
|
+
// 55 1 viewOnceMessageV2
|
|
557
|
+
// 59 1 viewOnceMessageV2Extension
|
|
558
|
+
// 40 1 ephemeralMessage
|
|
559
|
+
// 53 1 documentWithCaptionMessage
|
|
560
|
+
// 58 1 editedMessage
|
|
561
|
+
//
|
|
562
|
+
// All three view-once fields are read, not just the current one: V2 is what
|
|
563
|
+
// today's clients produce for a photo or a video and the extension what they
|
|
564
|
+
// produce for a voice note, but the original 37 is still what older senders and
|
|
565
|
+
// history sync deliver.
|
|
566
|
+
const MESSAGE_WRAPPERS = [
|
|
567
|
+
{ field: 31, inner: 2, flag: null },
|
|
568
|
+
{ field: 37, inner: 1, flag: 'viewOnce' },
|
|
569
|
+
{ field: 55, inner: 1, flag: 'viewOnce' },
|
|
570
|
+
{ field: 59, inner: 1, flag: 'viewOnce' },
|
|
571
|
+
{ field: 40, inner: 1, flag: 'ephemeral' },
|
|
572
|
+
{ field: 53, inner: 1, flag: null },
|
|
573
|
+
{ field: 58, inner: 1, flag: 'edited' }
|
|
574
|
+
];
|
|
575
|
+
|
|
576
|
+
// Real messages nest two or three deep at the most — a view-once photo inside a
|
|
577
|
+
// disappearing chat, echoed to our own devices. Each level is a length-
|
|
578
|
+
// delimited field of the one above it, so the buffer strictly shrinks and this
|
|
579
|
+
// cannot run away; the bound is only here so a malformed payload cannot recurse
|
|
580
|
+
// deep enough to exhaust the stack.
|
|
581
|
+
const MAX_WRAPPER_DEPTH = 8;
|
|
582
|
+
|
|
583
|
+
// The Message inside an envelope, or null when the field is absent, is not an
|
|
584
|
+
// envelope after all, or holds nothing.
|
|
585
|
+
function _unwrap(envelope, innerField) {
|
|
586
|
+
if (!envelope || !Buffer.isBuffer(envelope)) return null;
|
|
587
|
+
try {
|
|
588
|
+
const inner = _decodeFields(envelope)[innerField];
|
|
589
|
+
return (Buffer.isBuffer(inner) && inner.length) ? inner : null;
|
|
590
|
+
} catch (_) { return null; }
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function decodeMessageContainer(buf, depth) {
|
|
538
594
|
if (!buf || buf.length === 0) return { type: 'unknown' };
|
|
539
595
|
try {
|
|
540
596
|
const f = _decodeFields(buf);
|
|
541
|
-
|
|
542
|
-
// Field 31: deviceSentMessage — wraps the real message at field 2
|
|
543
|
-
// (sent to our own linked devices). Unwrap and re-decode.
|
|
544
|
-
if (f[31]) {
|
|
545
|
-
const dsm = _decodeFields(f[31]);
|
|
546
|
-
if (dsm[2]) return decodeMessageContainer(dsm[2]);
|
|
547
|
-
}
|
|
597
|
+
const level = depth || 0;
|
|
548
598
|
|
|
549
599
|
// Field 2: senderKeyDistributionMessage — used to set up sender keys.
|
|
550
600
|
// In WhatsApp Multi-Device, SKDM is bundled WITH the first real message in a new
|
|
@@ -578,6 +628,29 @@ function decodeMessageContainer(buf) {
|
|
|
578
628
|
// Decode the actual message payload (independent of SKDM).
|
|
579
629
|
// We use a helper so we can attach skdmInfo to every result.
|
|
580
630
|
let msgResult = null;
|
|
631
|
+
// What the envelopes said about it, raised on the result below.
|
|
632
|
+
let envelopeFlags = null;
|
|
633
|
+
|
|
634
|
+
// An envelope is opened before anything else is looked at: whatever is
|
|
635
|
+
// inside it is the message, and the fields beside it here are the
|
|
636
|
+
// envelope's own. A distribution or a message secret riding on this level
|
|
637
|
+
// still belongs to what comes out, which is why both are read first and
|
|
638
|
+
// carried onto the result — unwrapping used to return straight out of here
|
|
639
|
+
// and lose them.
|
|
640
|
+
if (level < MAX_WRAPPER_DEPTH) {
|
|
641
|
+
for (const w of MESSAGE_WRAPPERS) {
|
|
642
|
+
const inner = _unwrap(f[w.field], w.inner);
|
|
643
|
+
if (!inner) continue;
|
|
644
|
+
const decoded = decodeMessageContainer(inner, level + 1);
|
|
645
|
+
// Nothing this decoder knows was in there. Leave msgResult unset so the
|
|
646
|
+
// branches below still get their turn, rather than reporting an empty
|
|
647
|
+
// envelope as the whole message.
|
|
648
|
+
if (!decoded || decoded.type === 'unknown') continue;
|
|
649
|
+
msgResult = decoded;
|
|
650
|
+
if (w.flag) envelopeFlags = Object.assign({}, envelopeFlags, { [w.flag]: true });
|
|
651
|
+
break;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
581
654
|
|
|
582
655
|
// Field 1: conversation — plain text
|
|
583
656
|
if (!msgResult && f[1] && Buffer.isBuffer(f[1])) {
|
|
@@ -841,10 +914,24 @@ function decodeMessageContainer(buf) {
|
|
|
841
914
|
msgResult = { type: 'pollVote' };
|
|
842
915
|
}
|
|
843
916
|
|
|
844
|
-
// If we found a real message, attach skdmInfo / messageSecret and return
|
|
917
|
+
// If we found a real message, attach skdmInfo / messageSecret and return.
|
|
918
|
+
//
|
|
919
|
+
// Anything the inner message already carries stays as it is: a nested
|
|
920
|
+
// envelope has set its own flags and read its own secret on the way up, and
|
|
921
|
+
// this level only fills in what is still missing.
|
|
845
922
|
if (msgResult) {
|
|
846
|
-
if (
|
|
847
|
-
|
|
923
|
+
if (envelopeFlags) msgResult = Object.assign({}, msgResult, envelopeFlags);
|
|
924
|
+
if (messageSecret && !msgResult.messageSecret) {
|
|
925
|
+
msgResult = Object.assign({}, msgResult, { messageSecret });
|
|
926
|
+
}
|
|
927
|
+
// A poll inside an envelope keeps its messageContextInfo out here, beside
|
|
928
|
+
// the envelope rather than beside the poll. Its votes cannot be decrypted
|
|
929
|
+
// without that secret, so it is taken from whichever level carried it.
|
|
930
|
+
if (msgResult.type === 'poll' && !msgResult.encKey && messageSecret) {
|
|
931
|
+
msgResult = Object.assign({}, msgResult, { encKey: messageSecret });
|
|
932
|
+
}
|
|
933
|
+
if (skdmInfo && !msgResult.skdm) msgResult = Object.assign({}, msgResult, { skdm: skdmInfo });
|
|
934
|
+
return msgResult;
|
|
848
935
|
}
|
|
849
936
|
|
|
850
937
|
// If we only have an SKDM (no other message content), return senderKeyDistribution
|
|
@@ -900,6 +987,10 @@ module.exports = {
|
|
|
900
987
|
encodeVarint,
|
|
901
988
|
// The generic field walker, for the protobufs that live outside this file.
|
|
902
989
|
decodeFields: _decodeFields,
|
|
990
|
+
// Exported so a test can check the bound at its edge rather than guess where
|
|
991
|
+
// the edge is: a test that hardcodes the number goes on passing after the
|
|
992
|
+
// constant moves, which is the same as not testing it.
|
|
993
|
+
MAX_WRAPPER_DEPTH,
|
|
903
994
|
field,
|
|
904
995
|
varint,
|
|
905
996
|
str,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whalibmob",
|
|
3
|
-
"version": "5.24.
|
|
3
|
+
"version": "5.24.1",
|
|
4
4
|
"description": "Node.js library for WhatsApp — register a number over SMS, or link as a companion by QR. Signal E2E encryption, media, groups, channels.",
|
|
5
5
|
"author": "Kunboruto20",
|
|
6
6
|
"main": "index.js",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"type": "commonjs",
|
|
22
22
|
"scripts": {
|
|
23
23
|
"test": "node --test",
|
|
24
|
-
"test:types": "tsc --noEmit --strict --skipLibCheck index.d.ts",
|
|
24
|
+
"test:types": "tsc --noEmit --strict --skipLibCheck index.d.ts && tsc --noEmit --strict --skipLibCheck --types node types/usage-check.ts",
|
|
25
25
|
"test:all": "npm test && npm run test:types"
|
|
26
26
|
},
|
|
27
27
|
"license": "MIT",
|