whalibmob 5.33.26 → 5.33.29
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/lib/Registration.js +139 -33
- package/lib/Store.js +7 -2
- package/package.json +1 -1
package/lib/Registration.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const https = require('https');
|
|
5
5
|
const tls = require('tls');
|
|
6
|
+
const zlib = require('zlib');
|
|
6
7
|
const fs = require('fs');
|
|
7
8
|
const os = require('os');
|
|
8
9
|
const path = require('path');
|
|
@@ -40,13 +41,18 @@ function _uaPlatformName(device) {
|
|
|
40
41
|
|
|
41
42
|
function registrationHeaders(device, waVersion) {
|
|
42
43
|
if (device.os === 'android') {
|
|
44
|
+
// Header set and order mirror the native client's registration POST: no
|
|
45
|
+
// Accept, Connection kept alive, gzip advertised (the response is gunzipped
|
|
46
|
+
// on both the direct and SOCKS paths). request_token is an UPPERCASE UUID —
|
|
47
|
+
// the native client never mints a lowercase one.
|
|
43
48
|
return {
|
|
44
|
-
'User-Agent':
|
|
45
|
-
'
|
|
46
|
-
'Accept': 'text/json',
|
|
47
|
-
'WaMsysRequest': '1',
|
|
49
|
+
'User-Agent': `WhatsApp/${waVersion} ${_uaPlatformName(device)}/${device.osVersion} Device/${device.manufacturer}-${device.modelId}`,
|
|
50
|
+
'WaMsysRequest': '1',
|
|
48
51
|
// A fresh one per request, the way the native client mints it.
|
|
49
|
-
'request_token':
|
|
52
|
+
'request_token': uuidv4().toUpperCase(),
|
|
53
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
54
|
+
'Connection': 'Keep-Alive',
|
|
55
|
+
'Accept-Encoding': 'gzip'
|
|
50
56
|
};
|
|
51
57
|
}
|
|
52
58
|
return {
|
|
@@ -66,7 +72,7 @@ const {
|
|
|
66
72
|
parseSocksProxy, socksProxyUrl, socksConnect, proxyAgent
|
|
67
73
|
} = require('./socks');
|
|
68
74
|
|
|
69
|
-
async function httpPostViaSocks(path, body, waVersion, proxyUrl, authHeader, device) {
|
|
75
|
+
async function httpPostViaSocks(path, body, waVersion, proxyUrl, authHeader, device, query) {
|
|
70
76
|
const dHost = 'v.whatsapp.net';
|
|
71
77
|
const dPort = 443;
|
|
72
78
|
|
|
@@ -88,23 +94,27 @@ async function httpPostViaSocks(path, body, waVersion, proxyUrl, authHeader, dev
|
|
|
88
94
|
});
|
|
89
95
|
|
|
90
96
|
const headers = registrationHeaders(device || getDeviceConfig(), waVersion);
|
|
97
|
+
// The native client submits the parameters twice: once in the clear as the
|
|
98
|
+
// URL query, once encrypted inside ENC. The query string here is the exact
|
|
99
|
+
// plaintext that was encrypted, byte for byte.
|
|
100
|
+
const target = query ? `/v2${path}?${query}` : `/v2${path}`;
|
|
91
101
|
const reqLines = [
|
|
92
|
-
`POST
|
|
102
|
+
`POST ${target} HTTP/1.1`,
|
|
93
103
|
`Host: ${dHost}`,
|
|
94
104
|
...Object.keys(headers).map(k => `${k}: ${headers[k]}`),
|
|
95
105
|
`Content-Length: ${Buffer.byteLength(body)}`
|
|
96
106
|
];
|
|
97
107
|
if (authHeader) reqLines.push(`Authorization: ${authHeader}`);
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
reqLines.push(
|
|
108
|
+
// Connection: Keep-Alive rides in from registrationHeaders, matching the
|
|
109
|
+
// native client; the response is still delimited by its own length and the
|
|
110
|
+
// socket is destroyed once it is read.
|
|
111
|
+
reqLines.push('', body);
|
|
102
112
|
const req = reqLines.join('\r\n');
|
|
103
113
|
|
|
104
114
|
tlsSocket.write(req);
|
|
105
115
|
|
|
106
|
-
const { status, body: bodyBuf } = await readHttpResponse(tlsSocket, path);
|
|
107
|
-
const bodyStr = bodyBuf.toString('utf8');
|
|
116
|
+
const { status, headers: resHeaders, body: bodyBuf } = await readHttpResponse(tlsSocket, path);
|
|
117
|
+
const bodyStr = decodeBody(bodyBuf, resHeaders).toString('utf8');
|
|
108
118
|
|
|
109
119
|
if (status !== 200) throw new Error(`HTTP ${status} ${path}: ${bodyStr}`);
|
|
110
120
|
try { return JSON.parse(bodyStr); } catch (_) { return { raw: bodyStr }; }
|
|
@@ -172,6 +182,17 @@ function readHttpResponse(socket, label, timeoutMs) {
|
|
|
172
182
|
});
|
|
173
183
|
}
|
|
174
184
|
|
|
185
|
+
// The registration POST advertises `Accept-Encoding: gzip`, so the server may
|
|
186
|
+
// answer gzip-compressed. Decode by the response's own content-encoding; fall
|
|
187
|
+
// back to the raw bytes if the header lies or the stream will not inflate, so a
|
|
188
|
+
// plain reply still parses. `headers` is the raw header block (any case).
|
|
189
|
+
function decodeBody(bodyBuf, headers) {
|
|
190
|
+
if (/^content-encoding:[ \t]*gzip/im.test(headers || '')) {
|
|
191
|
+
try { return zlib.gunzipSync(bodyBuf); } catch (_) { /* fall through */ }
|
|
192
|
+
}
|
|
193
|
+
return bodyBuf;
|
|
194
|
+
}
|
|
195
|
+
|
|
175
196
|
/**
|
|
176
197
|
* Parse what has arrived so far.
|
|
177
198
|
*
|
|
@@ -1221,6 +1242,61 @@ function toUrlHex(buf) {
|
|
|
1221
1242
|
// IMPORTANT: do NOT use URLSearchParams here — it would double-encode the
|
|
1222
1243
|
// percent-hex sequences in the 'id' field (%AA → %25AA).
|
|
1223
1244
|
|
|
1245
|
+
// The native client inserts registration fields into a LinkedHashMap in a
|
|
1246
|
+
// fixed order; the wire sequence is that insertion order, not a sort. These are
|
|
1247
|
+
// the live orders captured from WhatsApp Android (WA_REVERSE_2026): 50 fields
|
|
1248
|
+
// for /v2/code, 49 for /v2/register. gpia/token/vname that this build omits
|
|
1249
|
+
// simply drop out — their slots collapse.
|
|
1250
|
+
const CODE_PARAM_ORDER = [
|
|
1251
|
+
'method', 'backup_token', '_gs', 'sim_mnc', 'id', 'mnc', '_gg',
|
|
1252
|
+
'network_radio_type', 'lg', 'rc', 'pid', '_ge', 'cellular_strength', 'gpia',
|
|
1253
|
+
'hasinrc', 'aid', 'mcc', '_gp', 'hasav', 'device_ram', 'db', 'sim_type',
|
|
1254
|
+
'prefer_sms_over_flash', 'access_session_id', 'sim_mcc', 'roaming_type',
|
|
1255
|
+
'mistyped', 'airplane_mode_type', 'e_ident', 'e_skey_sig', 'entrypoint',
|
|
1256
|
+
'token', 'in', 'expid', 'simnum', 'cc', 'education_screen_displayed',
|
|
1257
|
+
'e_skey_val', 'authkey', 'e_keytype', 'e_regid', 'client_metrics', '_gi',
|
|
1258
|
+
'e_skey_id', 'feo2_query_status', 'reason', 'fdid', 'recaptcha', '_ga', 'lc'
|
|
1259
|
+
];
|
|
1260
|
+
|
|
1261
|
+
const REGISTER_PARAM_ORDER = [
|
|
1262
|
+
'code', 'backup_token', 'sim_mnc', '_gs', 'id', 'device_ram', 'db', '_gg',
|
|
1263
|
+
'sim_operator_name', 'passkey_login_status', 'lg', 'rc', 'network_radio_type',
|
|
1264
|
+
'pid', '_ge', 'cellular_strength', 'gpia', 'hasinrc', 'aid',
|
|
1265
|
+
'network_operator_name', 'mcc', '_gp', 'mnc', 'entered', 'access_session_id',
|
|
1266
|
+
'sim_mcc', 'roaming_type', 'mistyped', 'airplane_mode_type', 'has_play_store',
|
|
1267
|
+
'sim_type', 'e_ident', 'e_skey_sig', 'entrypoint', 'in', 'expid', 'simnum',
|
|
1268
|
+
'cc', 'e_skey_val', 'authkey', 'e_keytype', 'e_regid', '_gi', 'client_metrics',
|
|
1269
|
+
'e_skey_id', 'fdid', 'recaptcha', '_ga', 'lc'
|
|
1270
|
+
];
|
|
1271
|
+
|
|
1272
|
+
// Reorder a flat [k, v, k, v, …] list to the native insertion order for the
|
|
1273
|
+
// endpoint. Keys outside the reference order (push_token and any build-specific
|
|
1274
|
+
// extras) keep their relative order and trail the known fields, so nothing is
|
|
1275
|
+
// dropped. Only /v2/code and /v2/register have a reference order; every other
|
|
1276
|
+
// endpoint (client_log, exist, …) and every non-Android device passes through
|
|
1277
|
+
// untouched.
|
|
1278
|
+
function reorderRegistrationPairs(pairs, path, device) {
|
|
1279
|
+
const order = path === '/code' ? CODE_PARAM_ORDER
|
|
1280
|
+
: path === '/register' ? REGISTER_PARAM_ORDER
|
|
1281
|
+
: null;
|
|
1282
|
+
if (!order || !device || device.os !== 'android') return pairs;
|
|
1283
|
+
|
|
1284
|
+
const orderSet = new Set(order);
|
|
1285
|
+
const seen = new Map();
|
|
1286
|
+
const extras = [];
|
|
1287
|
+
for (let i = 0; i < pairs.length; i += 2) {
|
|
1288
|
+
const k = pairs[i];
|
|
1289
|
+
if (seen.has(k)) continue;
|
|
1290
|
+
seen.set(k, pairs[i + 1]);
|
|
1291
|
+
if (!orderSet.has(k)) extras.push(k);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
const out = [];
|
|
1295
|
+
for (const k of order) if (seen.has(k)) out.push(k, seen.get(k));
|
|
1296
|
+
for (const k of extras) out.push(k, seen.get(k));
|
|
1297
|
+
return out;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1224
1300
|
function buildForm(pairs, extraPairs) {
|
|
1225
1301
|
const parts = [];
|
|
1226
1302
|
for (let i = 0; i < pairs.length; i += 2) {
|
|
@@ -1517,6 +1593,16 @@ function toBase64Url(buf) {
|
|
|
1517
1593
|
return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_');
|
|
1518
1594
|
}
|
|
1519
1595
|
|
|
1596
|
+
// The native client encodes the Signal-key and expid wire parameters with
|
|
1597
|
+
// Base64.encodeToString(bytes, 11) = URL_SAFE | NO_WRAP | NO_PADDING: authkey
|
|
1598
|
+
// (43 chars), e_ident/e_skey_val (43), e_skey_sig (86), e_regid (6), e_keytype,
|
|
1599
|
+
// e_skey_id (4), expid (22) — never a trailing '='. The padded form still
|
|
1600
|
+
// decodes server-side, but the bare '=' is a wire tell no real device emits.
|
|
1601
|
+
// The ENC envelope and the vname certificate keep their own encoder above.
|
|
1602
|
+
function toBase64UrlNoPad(buf) {
|
|
1603
|
+
return toBase64Url(buf).replace(/=+$/, '');
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1520
1606
|
// ---------- vname: the Business verified-name certificate ----------
|
|
1521
1607
|
//
|
|
1522
1608
|
// A Business registration carries one extra form field, `vname`, and a consumer
|
|
@@ -1585,7 +1671,7 @@ function buildVerifiedNameCertificate(store, device) {
|
|
|
1585
1671
|
]));
|
|
1586
1672
|
}
|
|
1587
1673
|
|
|
1588
|
-
async function buildPayload(store, waVersion, useToken, extraPairs) {
|
|
1674
|
+
async function buildPayload(store, waVersion, useToken, extraPairs, path) {
|
|
1589
1675
|
const { cc, national } = parsePhone(store.phoneNumber);
|
|
1590
1676
|
const meta = getCountryMeta(cc, store);
|
|
1591
1677
|
const device = store.device || getDeviceConfig();
|
|
@@ -1630,29 +1716,34 @@ async function buildPayload(store, waVersion, useToken, extraPairs) {
|
|
|
1630
1716
|
ga: store._gaToken
|
|
1631
1717
|
});
|
|
1632
1718
|
|
|
1633
|
-
|
|
1719
|
+
const basePairs = [
|
|
1634
1720
|
'cc', cc,
|
|
1635
1721
|
'in', national,
|
|
1636
1722
|
'rc', String(RELEASE_CHANNEL),
|
|
1637
1723
|
'lg', meta.lg,
|
|
1638
1724
|
'lc', meta.lc,
|
|
1639
|
-
'authkey',
|
|
1725
|
+
'authkey', toBase64UrlNoPad(stripKeyPrefix(store.noiseKeyPair.public)),
|
|
1640
1726
|
// Business only; buildForm drops a null, so a consumer registration sends
|
|
1641
1727
|
// the same body it always has.
|
|
1642
1728
|
'vname', buildVerifiedNameCertificate(store, device),
|
|
1643
|
-
'e_regid',
|
|
1644
|
-
'e_keytype',
|
|
1645
|
-
'e_ident',
|
|
1646
|
-
'e_skey_id',
|
|
1647
|
-
'e_skey_val',
|
|
1648
|
-
'e_skey_sig',
|
|
1729
|
+
'e_regid', toBase64UrlNoPad(intToBytes(store.registrationId, 4)),
|
|
1730
|
+
'e_keytype', toBase64UrlNoPad(Buffer.from([SIGNAL_KEY_TYPE])),
|
|
1731
|
+
'e_ident', toBase64UrlNoPad(stripKeyPrefix(store.identityKeyPair.public)),
|
|
1732
|
+
'e_skey_id', toBase64UrlNoPad(intToBytes(store.signedPreKey.id, 3)),
|
|
1733
|
+
'e_skey_val', toBase64UrlNoPad(stripKeyPrefix(store.signedPreKey.public)),
|
|
1734
|
+
'e_skey_sig', toBase64UrlNoPad(store.signedPreKey.signature),
|
|
1649
1735
|
'fdid', fdid,
|
|
1650
|
-
'expid',
|
|
1736
|
+
'expid', toBase64UrlNoPad(store.deviceId),
|
|
1651
1737
|
'id', toUrlHex(store.identityId),
|
|
1652
1738
|
'access_session_id', getAccessSessionId(store),
|
|
1653
1739
|
'token', token,
|
|
1654
1740
|
...attestFields
|
|
1655
|
-
]
|
|
1741
|
+
];
|
|
1742
|
+
const allPairs = extraPairs ? basePairs.concat(extraPairs) : basePairs;
|
|
1743
|
+
// Emit the fields in the native client's fixed insertion order for /code and
|
|
1744
|
+
// /register. The order is part of the wire fingerprint and, with the query
|
|
1745
|
+
// now mirrored into the URL, is visible to the server in the clear.
|
|
1746
|
+
return buildForm(reorderRegistrationPairs(allPairs, path, device));
|
|
1656
1747
|
}
|
|
1657
1748
|
|
|
1658
1749
|
// ---------- Encryption (AES-256-GCM, ephemeral X25519) ----------
|
|
@@ -1672,9 +1763,9 @@ function encryptPayload(plaintext) {
|
|
|
1672
1763
|
|
|
1673
1764
|
// ---------- HTTP ----------
|
|
1674
1765
|
|
|
1675
|
-
function httpPost(path, body, waVersion, authHeader, device) {
|
|
1766
|
+
function httpPost(path, body, waVersion, authHeader, device, query) {
|
|
1676
1767
|
const proxy = socksProxyUrl();
|
|
1677
|
-
if (proxy) return httpPostViaSocks(path, body, waVersion, proxy, authHeader, device);
|
|
1768
|
+
if (proxy) return httpPostViaSocks(path, body, waVersion, proxy, authHeader, device, query);
|
|
1678
1769
|
|
|
1679
1770
|
return new Promise((resolve, reject) => {
|
|
1680
1771
|
const bodyBuf = Buffer.from(body, 'utf8');
|
|
@@ -1685,10 +1776,13 @@ function httpPost(path, body, waVersion, authHeader, device) {
|
|
|
1685
1776
|
// Attestation cert-chain / App Attest header (Android cert chain, iOS
|
|
1686
1777
|
// <attestation>|<keyId>). Only present when body attestation is available.
|
|
1687
1778
|
if (authHeader) headers['Authorization'] = authHeader;
|
|
1779
|
+
// The native client submits the parameters twice: once in the clear as the
|
|
1780
|
+
// URL query, once encrypted inside ENC. The query is the exact ENC
|
|
1781
|
+
// plaintext, byte for byte.
|
|
1688
1782
|
const opts = {
|
|
1689
1783
|
hostname: 'v.whatsapp.net',
|
|
1690
1784
|
port: 443,
|
|
1691
|
-
path: '/v2' + path,
|
|
1785
|
+
path: query ? '/v2' + path + '?' + query : '/v2' + path,
|
|
1692
1786
|
method: 'POST',
|
|
1693
1787
|
timeout: 20000,
|
|
1694
1788
|
headers
|
|
@@ -1697,7 +1791,12 @@ function httpPost(path, body, waVersion, authHeader, device) {
|
|
|
1697
1791
|
const chunks = [];
|
|
1698
1792
|
res.on('data', d => chunks.push(d));
|
|
1699
1793
|
res.on('end', () => {
|
|
1700
|
-
const
|
|
1794
|
+
const encoding = String(res.headers['content-encoding'] || '');
|
|
1795
|
+
let buf = Buffer.concat(chunks);
|
|
1796
|
+
if (/gzip/i.test(encoding)) {
|
|
1797
|
+
try { buf = zlib.gunzipSync(buf); } catch (_) { /* keep raw */ }
|
|
1798
|
+
}
|
|
1799
|
+
const text = buf.toString('utf8');
|
|
1701
1800
|
if (res.statusCode !== 200) {
|
|
1702
1801
|
return reject(new Error(`HTTP ${res.statusCode} ${path}: ${text}`));
|
|
1703
1802
|
}
|
|
@@ -1713,7 +1812,7 @@ function httpPost(path, body, waVersion, authHeader, device) {
|
|
|
1713
1812
|
}
|
|
1714
1813
|
|
|
1715
1814
|
async function sendRequest(path, store, waVersion, useToken, extraPairs) {
|
|
1716
|
-
const plaintext = await buildPayload(store, waVersion, useToken, extraPairs);
|
|
1815
|
+
const plaintext = await buildPayload(store, waVersion, useToken, extraPairs, path);
|
|
1717
1816
|
return sendEncrypted(path, plaintext, store, waVersion);
|
|
1718
1817
|
}
|
|
1719
1818
|
|
|
@@ -1741,7 +1840,14 @@ async function sendEncrypted(path, plaintext, store, waVersion) {
|
|
|
1741
1840
|
|
|
1742
1841
|
_whaDbg('[DBG] REG → ' + path + ' (' + body.length + ' bytes' +
|
|
1743
1842
|
(bodyAtt.bodyAttestation ? ', attested' : '') + ')');
|
|
1744
|
-
|
|
1843
|
+
// The ENC plaintext is also the URL query: the native client submits every
|
|
1844
|
+
// parameter twice, in the clear and encrypted, byte for byte identical. This
|
|
1845
|
+
// is documented and verified only for the Android /code and /register
|
|
1846
|
+
// requests, so the mirror is scoped to them; iOS and the funnel endpoints
|
|
1847
|
+
// keep posting to a bare path, exactly as before.
|
|
1848
|
+
const mirrorQuery = device.os === 'android' && (path === '/code' || path === '/register');
|
|
1849
|
+
const result = await httpPost(path, body, waVersion, bodyAtt.authorizationHeader, device,
|
|
1850
|
+
mirrorQuery ? plaintext : undefined);
|
|
1745
1851
|
_whaDbg('[DBG] REG ← ' + path + ' ' +
|
|
1746
1852
|
(result && result.status ? result.status : JSON.stringify(result).slice(0, 120)) +
|
|
1747
1853
|
(result && result.reason ? ' reason=' + result.reason : ''));
|
|
@@ -1824,7 +1930,7 @@ async function sendFunnelLog(store, waVersion, currentScreen, actionTaken, event
|
|
|
1824
1930
|
'in', national,
|
|
1825
1931
|
'lg', 'en',
|
|
1826
1932
|
'lc', 'US',
|
|
1827
|
-
'expid',
|
|
1933
|
+
'expid', toBase64UrlNoPad(store.deviceId),
|
|
1828
1934
|
'fdid', fdid,
|
|
1829
1935
|
'id', toUrlHex(store.identityId),
|
|
1830
1936
|
'current_screen', currentScreen,
|
|
@@ -1856,7 +1962,7 @@ async function sendPrePnFunnelLog(store, waVersion, actionTaken, eventName) {
|
|
|
1856
1962
|
const body = buildForm([
|
|
1857
1963
|
'lg', 'en',
|
|
1858
1964
|
'lc', 'US',
|
|
1859
|
-
'expid',
|
|
1965
|
+
'expid', toBase64UrlNoPad(store.deviceId),
|
|
1860
1966
|
'fdid', fdid,
|
|
1861
1967
|
'id', toUrlHex(store.identityId),
|
|
1862
1968
|
'current_screen', 'enter_number',
|
|
@@ -2588,7 +2694,7 @@ async function verifyCode(store, code, opts) {
|
|
|
2588
2694
|
module.exports = { checkIfRegistered, checkNumberStatus, requestSmsCode, verifyCode, fetchIosVersion, fetchAndroidVersion, fetchPlayVersion, fetchAndroidVersionLive, fetchWaVersion, currentVersionFor, refreshSessionVersion, clearVersionCache, tryLoadAndroidMaterial, compareVersions, parsePhone, getCountryMeta, assertRegistrationKeys, parseSocksProxy, socksProxyUrl };
|
|
2589
2695
|
|
|
2590
2696
|
// The HTTP reader, exposed for tests. Not part of the public API.
|
|
2591
|
-
module.exports._http = { readHttpResponse, parseHttpResponse, decodeChunkedBody };
|
|
2697
|
+
module.exports._http = { readHttpResponse, parseHttpResponse, decodeChunkedBody, decodeBody, reorderRegistrationPairs };
|
|
2592
2698
|
|
|
2593
2699
|
// Token computation, exposed for tests. Not part of the public API.
|
|
2594
2700
|
module.exports._token = { computeToken, androidMaterialPath, registrationHeaders, ensureAndroidMaterial, deviceForRegistration, buildVerifiedNameCertificate, buildPayload };
|
package/lib/Store.js
CHANGED
|
@@ -82,7 +82,11 @@ function createNewStore(phoneNumber, opts) {
|
|
|
82
82
|
|
|
83
83
|
const registrationId = (crypto.randomBytes(2).readUInt16BE(0) & 0x3fff) + 1;
|
|
84
84
|
const fdid = uuidv4();
|
|
85
|
-
|
|
85
|
+
// expid goes on the wire as a UUID and WhatsApp reads its version/variant
|
|
86
|
+
// nibbles (the native client builds it with UUID.nameUUIDFromBytes/randomUUID).
|
|
87
|
+
// 16 fully random bytes land on a valid UUID shape only ~1 time in 16, so the
|
|
88
|
+
// server sees a malformed UUID; take the 16 bytes of a real v4 UUID instead.
|
|
89
|
+
const deviceId = Buffer.from(uuidv4().replace(/-/g, ''), 'hex');
|
|
86
90
|
const identityId = crypto.randomBytes(16);
|
|
87
91
|
// Device fingerprint fields the native client sends on /code (see
|
|
88
92
|
// Registration.js): advertising id (UUID) and the 20-byte backup token.
|
|
@@ -358,7 +362,8 @@ function fromSixParts(sixParts) {
|
|
|
358
362
|
},
|
|
359
363
|
registrationId: (crypto.randomBytes(2).readUInt16BE(0) & 0x3fff) + 1,
|
|
360
364
|
fdid: uuidv4(),
|
|
361
|
-
|
|
365
|
+
// expid must be a valid UUID on the wire — see createNewStore above.
|
|
366
|
+
deviceId: Buffer.from(uuidv4().replace(/-/g, ''), 'hex'),
|
|
362
367
|
identityId,
|
|
363
368
|
advertisingId: uuidv4(),
|
|
364
369
|
backupToken: crypto.randomBytes(20),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whalibmob",
|
|
3
|
-
"version": "5.33.
|
|
3
|
+
"version": "5.33.29",
|
|
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",
|