whalibmob 5.21.0 → 5.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -1
- package/lib/Client.js +16 -2
- package/lib/Registration.js +80 -1
- package/lib/constants.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -132,6 +132,8 @@ npm install -g whalibmob
|
|
|
132
132
|
- [Client Options](#client-options)
|
|
133
133
|
- [Linking to an Existing Account (Pairing Code or QR)](#linking-to-an-existing-account-pairing-code-or-qr)
|
|
134
134
|
- [Requesting a Pairing Code](#requesting-a-pairing-code)
|
|
135
|
+
- [Linking by QR Code](#linking-by-qr-code)
|
|
136
|
+
- [After the Link — Same for Both Paths](#after-the-link--same-for-both-paths)
|
|
135
137
|
- [Reconnecting a Linked Session](#reconnecting-a-linked-session)
|
|
136
138
|
- [Choosing Your Own Code](#choosing-your-own-code)
|
|
137
139
|
- [Options](#options)
|
|
@@ -153,13 +155,20 @@ npm install -g whalibmob
|
|
|
153
155
|
- [The Number WhatsApp Files Your Account Under](#the-number-whatsapp-files-your-account-under)
|
|
154
156
|
- [When Registration Is Refused for Consent](#when-registration-is-refused-for-consent)
|
|
155
157
|
- [The Push Token](#the-push-token)
|
|
158
|
+
- [Receiving the Code over Push](#receiving-the-code-over-push-without-typing-it)
|
|
156
159
|
- [Routing Traffic Through a Proxy](#routing-traffic-through-a-proxy)
|
|
160
|
+
- [What Goes Through It](#what-goes-through-it)
|
|
157
161
|
- [Saving & Restoring Sessions](#saving--restoring-sessions)
|
|
162
|
+
- [Keeping the Announced Version Current](#keeping-the-announced-version-current)
|
|
163
|
+
- [One-time Pre-keys](#one-time-pre-keys)
|
|
164
|
+
- [Where the Folder Comes From](#where-the-folder-comes-from)
|
|
165
|
+
- [Working Out the Paths Yourself](#working-out-the-paths-yourself)
|
|
158
166
|
- [Signal Store Utilities](#signal-store-utilities)
|
|
159
167
|
- [makeCacheableSignalKeyStore](#makecacheablesignalkeystore)
|
|
160
168
|
- [addTransactionCapability](#addtransactioncapability)
|
|
161
169
|
- [assertMeId](#assertmeid)
|
|
162
170
|
- [initAuthCreds](#initauthcreds)
|
|
171
|
+
- [Recommended Stacking Pattern](#recommended-stacking-pattern)
|
|
163
172
|
- [Handling Events](#handling-events)
|
|
164
173
|
- [Example to Start](#example-to-start)
|
|
165
174
|
- [All Events](#all-events)
|
|
@@ -248,11 +257,13 @@ npm install -g whalibmob
|
|
|
248
257
|
- [Query Metadata](#query-metadata)
|
|
249
258
|
- [Get Request Join List](#get-request-join-list)
|
|
250
259
|
- [Approve / Reject Request Join](#approve--reject-request-join)
|
|
251
|
-
- [Personal Invitations](#personal-invitations)
|
|
260
|
+
- [Personal Invitations](#personal-invitations-1)
|
|
252
261
|
- [Toggle Ephemeral in Group](#toggle-ephemeral-in-group)
|
|
253
262
|
- [WhatsApp IDs](#whatsapp-ids)
|
|
254
263
|
- [Transport](#transport)
|
|
255
264
|
- [Media Encryption](#media-encryption)
|
|
265
|
+
- [Sending (Upload Flow)](#sending-upload-flow)
|
|
266
|
+
- [Receiving (Download + Decrypt Flow)](#receiving-download--decrypt-flow)
|
|
256
267
|
- [Device Emulation](#device-emulation)
|
|
257
268
|
- [Quick Start](#device-quick-start)
|
|
258
269
|
- [iOS Profiles](#ios-profiles)
|
|
@@ -261,6 +272,7 @@ npm install -g whalibmob
|
|
|
261
272
|
- [Version & Token Overrides](#version--token-overrides)
|
|
262
273
|
- [When the server answers 405 on connect](#when-the-server-answers-405-on-connect)
|
|
263
274
|
- [Finding out what a 405 objects to](#finding-out-what-a-405-objects-to)
|
|
275
|
+
- [License](#license)
|
|
264
276
|
|
|
265
277
|
---
|
|
266
278
|
|
|
@@ -1616,6 +1628,10 @@ wa> /quit
|
|
|
1616
1628
|
|
|
1617
1629
|
## Library API
|
|
1618
1630
|
|
|
1631
|
+
Everything the CLI does is available as a Node.js library. The sections below
|
|
1632
|
+
cover connecting an account, sending and receiving every message type, groups,
|
|
1633
|
+
communities, channels, presence, privacy, history sync, and device emulation.
|
|
1634
|
+
|
|
1619
1635
|
## Connecting Account
|
|
1620
1636
|
|
|
1621
1637
|
### Register a New Number
|
package/lib/Client.js
CHANGED
|
@@ -574,10 +574,24 @@ class WhalibmobClient extends EventEmitter {
|
|
|
574
574
|
const source = android ? 'Play Store listing' : 'App Store listing';
|
|
575
575
|
|
|
576
576
|
try {
|
|
577
|
-
const { compareVersions } = require('./Registration');
|
|
577
|
+
const { compareVersions, fetchAndroidVersionLive } = require('./Registration');
|
|
578
578
|
const before = this._store.version;
|
|
579
|
+
// Connecting and registering ask different questions, and they must not
|
|
580
|
+
// share an answer.
|
|
581
|
+
//
|
|
582
|
+
// Registration announces the build its token was computed from, which is
|
|
583
|
+
// the APK material and nothing else — see fetchWaVersion().
|
|
584
|
+
//
|
|
585
|
+
// Connecting announces the build the store is serving *now*, because a
|
|
586
|
+
// version the server no longer recognises is what a 405 is. The material
|
|
587
|
+
// is the wrong source for that: it ages the moment Play moves on, and a
|
|
588
|
+
// session pinned to it would keep announcing an older build forever
|
|
589
|
+
// without anything looking broken.
|
|
590
|
+
//
|
|
591
|
+
// fetchAndroidVersionLive asks Play's own API first and only falls back
|
|
592
|
+
// to the store page, which no longer carries a version worth reading.
|
|
579
593
|
const live = android
|
|
580
|
-
? await
|
|
594
|
+
? await fetchAndroidVersionLive(business)
|
|
581
595
|
: await fetchIosVersion(business);
|
|
582
596
|
if (!live) return;
|
|
583
597
|
|
package/lib/Registration.js
CHANGED
|
@@ -553,6 +553,7 @@ const VERSION_CACHE_TTL_MS = 6 * 60 * 60 * 1000;
|
|
|
553
553
|
|
|
554
554
|
const _cachedIosVersion = {};
|
|
555
555
|
const _cachedAndroidVersion = {};
|
|
556
|
+
const _cachedPlayVersion = {};
|
|
556
557
|
|
|
557
558
|
function _cachedVersion(cache, key) {
|
|
558
559
|
const entry = cache[key];
|
|
@@ -578,6 +579,84 @@ function _cacheVersion(cache, key, value) {
|
|
|
578
579
|
function clearVersionCache() {
|
|
579
580
|
for (const k of Object.keys(_cachedIosVersion)) delete _cachedIosVersion[k];
|
|
580
581
|
for (const k of Object.keys(_cachedAndroidVersion)) delete _cachedAndroidVersion[k];
|
|
582
|
+
for (const k of Object.keys(_cachedPlayVersion)) delete _cachedPlayVersion[k];
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// How long to let the Play lookup run before giving up on it. This is on the
|
|
586
|
+
// connect path, so a slow answer has to become "no answer" rather than a
|
|
587
|
+
// connection that hangs; the caller keeps whatever version it already had.
|
|
588
|
+
const PLAY_VERSION_TIMEOUT_MS = 20000;
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* The version Google Play is actually serving, read through Play's own API.
|
|
592
|
+
*
|
|
593
|
+
* This is the Android counterpart of the iTunes lookup, and the only Android
|
|
594
|
+
* source that can be trusted. The store *page* stopped carrying a version to
|
|
595
|
+
* scrape: fetchAndroidVersion() below now usually returns the pinned fallback,
|
|
596
|
+
* and when it does find a number on the page it can be the wrong one — a live
|
|
597
|
+
* run had it read 2.26.27.85 off a listing that was serving 2.26.31.77, which
|
|
598
|
+
* is older than the fallback it was supposed to improve on.
|
|
599
|
+
*
|
|
600
|
+
* Play's device-facing API answers with the real thing. It needs a Google
|
|
601
|
+
* account, which the Aurora dispenser hands out anonymously — a free
|
|
602
|
+
* third-party service that rate-limits, hence the six-hour memoisation shared
|
|
603
|
+
* with the other lookups and the timeout above. A failure here is not an
|
|
604
|
+
* error: the caller falls back to the scrape and then to the pinned version.
|
|
605
|
+
*
|
|
606
|
+
* @param {boolean} business the w4b package rather than the consumer one
|
|
607
|
+
* @returns {Promise<string|null>} e.g. "2.26.31.77", or null if Play did not answer
|
|
608
|
+
*/
|
|
609
|
+
async function fetchPlayVersion(business) {
|
|
610
|
+
const key = business ? 'business' : 'personal';
|
|
611
|
+
const hit = _cachedVersion(_cachedPlayVersion, key);
|
|
612
|
+
if (hit) return hit;
|
|
613
|
+
|
|
614
|
+
const packageName = business ? WHATSAPP_BUSINESS_PACKAGE : WHATSAPP_PACKAGE;
|
|
615
|
+
const PlayStore = require('./PlayStore');
|
|
616
|
+
|
|
617
|
+
const lookup = (async () => {
|
|
618
|
+
const auth = await PlayStore.fetchAnonymousAuth();
|
|
619
|
+
const headers = PlayStore.buildFdfeHeaders(auth);
|
|
620
|
+
const info = await PlayStore.fetchVersion(packageName, headers);
|
|
621
|
+
return info && info.versionName ? info.versionName : null;
|
|
622
|
+
})();
|
|
623
|
+
|
|
624
|
+
let timer;
|
|
625
|
+
const deadline = new Promise((resolve) => {
|
|
626
|
+
timer = setTimeout(() => resolve(null), PLAY_VERSION_TIMEOUT_MS);
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
let version = null;
|
|
630
|
+
try {
|
|
631
|
+
version = await Promise.race([lookup, deadline]);
|
|
632
|
+
} catch (err) {
|
|
633
|
+
_whaDbg('[DBG] PLAY_VERSION lookup failed: ' + (err && err.message));
|
|
634
|
+
version = null;
|
|
635
|
+
} finally {
|
|
636
|
+
if (timer) clearTimeout(timer);
|
|
637
|
+
}
|
|
638
|
+
// The race leaves the lookup running when the deadline wins; it must not take
|
|
639
|
+
// the process down if it rejects after nobody is waiting on it any more.
|
|
640
|
+
lookup.catch(() => {});
|
|
641
|
+
|
|
642
|
+
if (!version) return null;
|
|
643
|
+
_whaDbg('[DBG] PLAY_VERSION ' + packageName + ' -> ' + version);
|
|
644
|
+
return _cacheVersion(_cachedPlayVersion, key, version);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* The current Android version for a client that is connecting, best source
|
|
649
|
+
* first: Play's API, then the store page, then the pinned fallback.
|
|
650
|
+
*
|
|
651
|
+
* Registration does not come through here — it reads the version off the APK
|
|
652
|
+
* material it computed its token from, and the two must not be confused. See
|
|
653
|
+
* fetchWaVersion().
|
|
654
|
+
*/
|
|
655
|
+
async function fetchAndroidVersionLive(business) {
|
|
656
|
+
const fromPlay = await fetchPlayVersion(business);
|
|
657
|
+
if (fromPlay) return fromPlay;
|
|
658
|
+
_whaDbg('[DBG] PLAY_VERSION unavailable — falling back to the store page');
|
|
659
|
+
return fetchAndroidVersion(business);
|
|
581
660
|
}
|
|
582
661
|
|
|
583
662
|
async function fetchIosVersion(business) {
|
|
@@ -2098,7 +2177,7 @@ async function verifyCode(store, code, opts) {
|
|
|
2098
2177
|
throw new Error(`Verification failed: ${reason || JSON.stringify(result)}`);
|
|
2099
2178
|
}
|
|
2100
2179
|
|
|
2101
|
-
module.exports = { checkIfRegistered, checkNumberStatus, requestSmsCode, verifyCode, fetchIosVersion, fetchAndroidVersion, fetchWaVersion, currentVersionFor, refreshSessionVersion, clearVersionCache, tryLoadAndroidMaterial, compareVersions, parsePhone, getCountryMeta, assertRegistrationKeys, parseSocksProxy, socksProxyUrl };
|
|
2180
|
+
module.exports = { checkIfRegistered, checkNumberStatus, requestSmsCode, verifyCode, fetchIosVersion, fetchAndroidVersion, fetchPlayVersion, fetchAndroidVersionLive, fetchWaVersion, currentVersionFor, refreshSessionVersion, clearVersionCache, tryLoadAndroidMaterial, compareVersions, parsePhone, getCountryMeta, assertRegistrationKeys, parseSocksProxy, socksProxyUrl };
|
|
2102
2181
|
|
|
2103
2182
|
// The HTTP reader, exposed for tests. Not part of the public API.
|
|
2104
2183
|
module.exports._http = { readHttpResponse, parseHttpResponse, decodeChunkedBody };
|
package/lib/constants.js
CHANGED
|
@@ -48,7 +48,7 @@ const IOS_BUSINESS_STATIC_TOKEN = 'USUDuDYDeQhY4RF2fCSp5m3F6kJ1M2J8wS7bbNA2';
|
|
|
48
48
|
// reason nothing in the error names. Keep them within a few releases of what
|
|
49
49
|
// the stores are actually serving; the live lookup is what normally decides.
|
|
50
50
|
const IOS_VERSION_FALLBACK = '2.26.9.75';
|
|
51
|
-
const ANDROID_VERSION_FALLBACK = '2.26.
|
|
51
|
+
const ANDROID_VERSION_FALLBACK = '2.26.31.77';
|
|
52
52
|
|
|
53
53
|
// Safari UA for the iTunes lookup only.
|
|
54
54
|
const IOS_USER_AGENT = 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Mobile/15E148 Safari/604.1';
|