whalibmob 5.23.1 → 5.23.3
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 +37 -9
- package/cli.js +18 -1
- package/index.js +15 -4
- package/lib/PushClient.js +106 -0
- package/lib/Registration.js +28 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
|
-
<div align=
|
|
2
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# whalibmob
|
|
4
|
+
|
|
5
|
+
**A Node.js library for WhatsApp that can register a phone number of its own.**
|
|
6
|
+
|
|
7
|
+
Every other JavaScript library links to an account that already exists on someone's phone.
|
|
8
|
+
whalibmob does that too — but it can also take a bare phone number, request the SMS or voice
|
|
9
|
+
code, and bring the account into being. Both transports, one API.
|
|
10
|
+
|
|
11
|
+
[](https://www.npmjs.com/package/whalibmob)
|
|
12
|
+
[](https://nodejs.org)
|
|
13
|
+
[](LICENSE)
|
|
14
|
+
|
|
15
|
+
### Start here
|
|
16
|
+
|
|
17
|
+
[](#register-a-new-number)
|
|
18
|
+
[](#linking-to-an-existing-account-pairing-code-or-qr)
|
|
19
|
+
[](#cli--getting-started)
|
|
20
|
+
|
|
21
|
+
[](#library-api)
|
|
22
|
+
[](#sending-messages)
|
|
23
|
+
[](#handling-events)
|
|
24
|
+
|
|
25
|
+
</div>
|
|
3
26
|
|
|
4
27
|
##
|
|
5
28
|
|
|
@@ -7,9 +30,9 @@
|
|
|
7
30
|
|
|
8
31
|
|
|
9
32
|
|
|
10
|
-
CONTACT ME ON TELEGRAM IF YOU WANT TO WORK WITH ME AND IF YOU HAVE PROBLEM WITH WHALIBMOB : @brtyu545
|
|
11
33
|
|
|
12
|
-
|
|
34
|
+
|
|
35
|
+
|
|
13
36
|
|
|
14
37
|
If you want News about whalibmob enter this whalibmob channel: https://t.me/+sHN4MDCyB7U5OWY0
|
|
15
38
|
|
|
@@ -26,9 +49,10 @@ If you want News about whalibmob enter this whalibmob channel: https://t.me/+sHN
|
|
|
26
49
|
> [!IMPORTANT]
|
|
27
50
|
> This project is not affiliated, associated, authorized, endorsed by, or in any way officially connected with WhatsApp or any of its subsidiaries or affiliates. "WhatsApp" and related names are registered trademarks of their respective owners. Use at your own discretion.
|
|
28
51
|
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
52
|
+
- **It registers numbers.** Give it a phone number that has never been on WhatsApp, request the code over SMS, voice call, flash call or an old WhatsApp account, confirm it, and the account exists — as an **Android or iOS device**, on the Mobile API. No phone, no scanning, no existing account to borrow. See [Register a New Number](#register-a-new-number).
|
|
53
|
+
- **It also speaks WhatsApp Web**, over a WebSocket. When a number cannot receive an SMS, or is already live on a phone, whalibmob links itself to that account by **QR code** or an **8-character pairing code** and runs as one of its linked devices — with the full message history and the account's address book. See [Linking to an Existing Account](#linking-to-an-existing-account-pairing-code-or-qr).
|
|
54
|
+
- **The API is identical in both modes.** Everything below — sending, media, groups, events — reads the same whichever way the session was created.
|
|
55
|
+
- No browser, no Selenium, no external runtime. It talks to WhatsApp directly over a **TCP socket** with the **Noise Protocol** handshake.
|
|
32
56
|
- Signal Protocol encryption is **fully inlined** in pure JavaScript — no native binaries, no node-gyp, runs anywhere Node.js runs.
|
|
33
57
|
|
|
34
58
|
## Install
|
|
@@ -1689,7 +1713,7 @@ if (result.status === 'ok') {
|
|
|
1689
1713
|
}
|
|
1690
1714
|
```
|
|
1691
1715
|
|
|
1692
|
-
**Optional — let the code arrive by itself.** The two steps above are the whole flow, and nothing about them changes if you do nothing else. But because registration
|
|
1716
|
+
**Optional — let the code arrive by itself.** The two steps above are the whole flow, and nothing about them changes if you do nothing else. But because an Android registration sends a Firebase push token (see [The Push Token](#the-push-token)), WhatsApp *may* also deliver the six-digit code as a silent push. Open a listener for it before requesting the code, and the code can come back with nothing typed — on an `WA_OS=android` profile; on iOS it resolves `null` straight away, since only Firebase is implemented:
|
|
1693
1717
|
|
|
1694
1718
|
```js
|
|
1695
1719
|
const { receivePushCode } = require('whalibmob')
|
|
@@ -2579,7 +2603,9 @@ If that is refused too, the number has to go through the real app once, on a pho
|
|
|
2579
2603
|
|
|
2580
2604
|
## The Push Token
|
|
2581
2605
|
|
|
2582
|
-
Every WhatsApp on a real phone holds a
|
|
2606
|
+
Every WhatsApp on a real phone holds a push token. It is the address the push network uses to wake the app, and no install exists without one — so a registration that ships no `push_token` describes a WhatsApp that cannot be notified, which is a device that does not exist.
|
|
2607
|
+
|
|
2608
|
+
**This is an Android-profile feature.** Which push network an install uses follows from its platform: Android holds a *Firebase* token and keeps a stream open to Google, iOS holds an *APNs* token and keeps one open to Apple. They are not interchangeable, and the registration server sees both the token and the User-Agent naming the platform that sent it — an iPhone presenting a Firebase token describes a device nobody ships. Only the Firebase side is implemented here, so everything in this section applies when `WA_OS=android`. An iOS registration sends no `push_token` at all, which is the correct thing for a client with no push line, and every other part of the flow is unaffected.
|
|
2583
2609
|
|
|
2584
2610
|
The token does two distinct jobs, and it is easy to conflate them:
|
|
2585
2611
|
|
|
@@ -2613,6 +2639,8 @@ Turn it off with `WA_FCM_PUSH=0`.
|
|
|
2613
2639
|
|
|
2614
2640
|
This is the receiving end of job 2 above. When WhatsApp sends the code as a silent push, something has to be listening on the Firebase line to catch it — the same long-lived connection every Android phone keeps open to Google. `receivePushCode(store, device)` opens it: a TLS stream to `mtalk.google.com:5228` speaking the MCS protocol, logged in with the Firebase identity, resolving with the code the moment a push carrying it arrives.
|
|
2615
2641
|
|
|
2642
|
+
Like the token itself, this is Android-only. On an iOS profile `receivePushCode` resolves `null` immediately rather than holding a listener open on a line no push can reach, and `/reg push` says so and stops instead of waiting out its timeout. Check with `supportsPush(store.device)` if you want to branch on it.
|
|
2643
|
+
|
|
2616
2644
|
The order matters. Open the listener **first**, so the line is live before the code is requested; then request the code by whatever method; then await it.
|
|
2617
2645
|
|
|
2618
2646
|
```js
|
package/cli.js
CHANGED
|
@@ -2431,7 +2431,7 @@ async function handleLine(line) {
|
|
|
2431
2431
|
break;
|
|
2432
2432
|
}
|
|
2433
2433
|
const method = (p[3] && !p[3].startsWith('--')) ? p[3] : 'sms';
|
|
2434
|
-
const {
|
|
2434
|
+
const { pushClientFor } = require('./lib/PushClient');
|
|
2435
2435
|
|
|
2436
2436
|
sessionDirFor(_sessDir, ph, { create: true });
|
|
2437
2437
|
const sessFile = storeFileFor(_sessDir, ph);
|
|
@@ -2439,6 +2439,23 @@ async function handleLine(line) {
|
|
|
2439
2439
|
if (!store) { store = initAuthCreds(ph, { name: regName }); saveStore(store, sessFile); }
|
|
2440
2440
|
if (!store.device) store.device = getDeviceConfig();
|
|
2441
2441
|
|
|
2442
|
+
// Push verification needs a push line, and only Android has one here.
|
|
2443
|
+
// An iOS session holds no Firebase identity, so the listener would sit
|
|
2444
|
+
// for three minutes on a push that can never be routed to it. Say so
|
|
2445
|
+
// now instead, and point at the two things that do work.
|
|
2446
|
+
const pushClient = pushClientFor(store.device);
|
|
2447
|
+
if (!pushClient.supportsPush) {
|
|
2448
|
+
fail('push verification is not available for this device profile (' +
|
|
2449
|
+
(store.device.os || 'unknown') + ')');
|
|
2450
|
+
out(' the code arrives over the push line of the platform being announced,');
|
|
2451
|
+
out(' and only Android has one implemented (Firebase). iOS needs APNs.');
|
|
2452
|
+
out(' either register this number on an Android profile:');
|
|
2453
|
+
out(' WA_OS=android (see /device) and re-run /reg push ' + ph);
|
|
2454
|
+
out(' or use the ordinary path: /reg code ' + ph + ' then /reg confirm ' + ph + ' <code>');
|
|
2455
|
+
break;
|
|
2456
|
+
}
|
|
2457
|
+
const receivePushCode = (s, d, o) => pushClient.receivePushCode(s, d, o);
|
|
2458
|
+
|
|
2442
2459
|
out('opening Firebase push listener (this can take a moment)...');
|
|
2443
2460
|
// Open the listener first so the push has somewhere to land. onReady
|
|
2444
2461
|
// fires once MCS is logged in — only then is it safe to ask for the
|
package/index.js
CHANGED
|
@@ -61,10 +61,21 @@ module.exports = {
|
|
|
61
61
|
checkIfRegistered,
|
|
62
62
|
requestSmsCode,
|
|
63
63
|
verifyCode,
|
|
64
|
-
// Receive the verification code as a silent
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
|
|
64
|
+
// Receive the verification code as a silent push, without an SMS — opens the
|
|
65
|
+
// listener the native client of that platform keeps open. See "Receiving the
|
|
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),
|
|
68
79
|
assertRegistrationKeys,
|
|
69
80
|
// Version fetch — use fetchWaVersion for device-aware (iOS or Android) fetching.
|
|
70
81
|
// fetchIosVersion is kept for backward compatibility.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ─── Push clients ─────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// Every real WhatsApp install can be woken by the server, and which line it is
|
|
6
|
+
// woken on follows from the platform it runs on. An Android install holds a
|
|
7
|
+
// Firebase token and keeps an MCS stream open to Google. An iOS install holds
|
|
8
|
+
// an APNs token and keeps a courier stream open to Apple. The two are not
|
|
9
|
+
// interchangeable, and the registration server sees both halves: the push token
|
|
10
|
+
// in the body, and the User-Agent that says which platform sent it. An iPhone
|
|
11
|
+
// presenting a Firebase token describes a device that does not exist.
|
|
12
|
+
//
|
|
13
|
+
// This module is the seam. `pushClientFor(device)` returns the client that
|
|
14
|
+
// matches the device profile, so no caller has to reach for a transport by
|
|
15
|
+
// name. Two implementations exist today:
|
|
16
|
+
//
|
|
17
|
+
// android → ./fcm (checkin → FIS install → register3, then MCS for the code)
|
|
18
|
+
// ios → NONE (APNs is not implemented; every push field is omitted)
|
|
19
|
+
//
|
|
20
|
+
// The NONE client is not a failure path — it is the correct answer for a
|
|
21
|
+
// platform we cannot speak push for. It supplies nothing, `buildForm` drops the
|
|
22
|
+
// null, and the registration body goes out without a push token rather than
|
|
23
|
+
// with somebody else's.
|
|
24
|
+
|
|
25
|
+
const { dbg: _whaDbg } = require('./logger');
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The push client for a platform we have no transport for.
|
|
29
|
+
*
|
|
30
|
+
* Supplies nothing and never performs I/O. Registration proceeds without any
|
|
31
|
+
* push field, which is what an install with no push line should send.
|
|
32
|
+
*/
|
|
33
|
+
const NONE_PUSH_CLIENT = {
|
|
34
|
+
platform: 'none',
|
|
35
|
+
supportsPush: false,
|
|
36
|
+
|
|
37
|
+
async getPushToken() {
|
|
38
|
+
return null;
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
async receivePushCode() {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The push client backed by Firebase Cloud Messaging, for Android profiles.
|
|
48
|
+
*
|
|
49
|
+
* Delegates to ./fcm, which owns the Google handshake and the MCS stream. The
|
|
50
|
+
* requires are lazy so that loading this module does not pull in the whole FCM
|
|
51
|
+
* stack for a session that never registers.
|
|
52
|
+
*/
|
|
53
|
+
const FCM_PUSH_CLIENT = {
|
|
54
|
+
platform: 'android',
|
|
55
|
+
supportsPush: true,
|
|
56
|
+
|
|
57
|
+
async getPushToken(store, device) {
|
|
58
|
+
return require('./fcm').getPushToken(store, device);
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
async receivePushCode(store, device, opts) {
|
|
62
|
+
return require('./fcm').receivePushCode(store, device, opts);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The push client matching a device profile.
|
|
68
|
+
*
|
|
69
|
+
* Android profiles get the Firebase client; everything else gets NONE. The
|
|
70
|
+
* choice is made on the device rather than on a global setting, because a
|
|
71
|
+
* process can register an Android session and an iOS session in turn and each
|
|
72
|
+
* has to present its own platform's push line — or none at all.
|
|
73
|
+
*
|
|
74
|
+
* @param {object} device a device config; only `device.os` is read
|
|
75
|
+
* @returns {{platform: string, supportsPush: boolean,
|
|
76
|
+
* getPushToken: function, receivePushCode: function}}
|
|
77
|
+
*/
|
|
78
|
+
function pushClientFor(device) {
|
|
79
|
+
if (device && device.os === 'android') return FCM_PUSH_CLIENT;
|
|
80
|
+
|
|
81
|
+
if (device && device.os) {
|
|
82
|
+
_whaDbg('[DBG] no push transport for ' + device.os + ' — push fields omitted');
|
|
83
|
+
}
|
|
84
|
+
return NONE_PUSH_CLIENT;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Whether this device profile can send and receive push at all.
|
|
89
|
+
*
|
|
90
|
+
* Callers that want to offer push verification check this first, so they can
|
|
91
|
+
* say why it is unavailable instead of opening a listener that nothing will
|
|
92
|
+
* ever reach.
|
|
93
|
+
*
|
|
94
|
+
* @param {object} device a device config; only `device.os` is read
|
|
95
|
+
* @returns {boolean}
|
|
96
|
+
*/
|
|
97
|
+
function supportsPush(device) {
|
|
98
|
+
return pushClientFor(device).supportsPush;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = {
|
|
102
|
+
pushClientFor,
|
|
103
|
+
supportsPush,
|
|
104
|
+
NONE_PUSH_CLIENT,
|
|
105
|
+
FCM_PUSH_CLIENT
|
|
106
|
+
};
|
package/lib/Registration.js
CHANGED
|
@@ -11,6 +11,7 @@ const { v4: uuidv4 } = require('uuid');
|
|
|
11
11
|
const { getDeviceConfig } = require('./DeviceConfig');
|
|
12
12
|
const AndroidApk = require('./AndroidApk');
|
|
13
13
|
const attestation = require('./Attestation');
|
|
14
|
+
const { pushClientFor } = require('./PushClient');
|
|
14
15
|
const { dbg: _whaDbg, warn: _whaWarn } = require('./logger');
|
|
15
16
|
|
|
16
17
|
// ---------- Request envelope ----------
|
|
@@ -1207,6 +1208,20 @@ function buildClientMetrics(attempt) {
|
|
|
1207
1208
|
return encodeURIComponent(json);
|
|
1208
1209
|
}
|
|
1209
1210
|
|
|
1211
|
+
// A note on `push_code`, because it looks like an omission and is not.
|
|
1212
|
+
//
|
|
1213
|
+
// It is not the field that makes silent push verification work. The code
|
|
1214
|
+
// arrives over the push line *after* /code has been sent — that is the whole
|
|
1215
|
+
// point of it — so a code cannot be in the request that asks for it. Filling
|
|
1216
|
+
// this field by waiting for a push would hang /code forever on the push it is
|
|
1217
|
+
// itself supposed to trigger. The working flow is the other shape entirely:
|
|
1218
|
+
// open the MCS listener first, send /code, then await the push and hand what
|
|
1219
|
+
// arrives to /register. That is what `receivePushCode` and `/reg push` do.
|
|
1220
|
+
//
|
|
1221
|
+
// So the field stays absent. An empty value is worse than no value: the server
|
|
1222
|
+
// validates the shape of what it receives and answers a blank push_code with
|
|
1223
|
+
// bad_param/bad_format naming that field, while a field that is not there is
|
|
1224
|
+
// not validated at all. buildForm drops nulls, which is exactly what we want.
|
|
1210
1225
|
function getRequestVerificationCodeParameters(store, method, meta, device, attempt) {
|
|
1211
1226
|
if (device && device.os === 'android') {
|
|
1212
1227
|
// Flash call is the one method whose delivery the server only routes when
|
|
@@ -1245,8 +1260,7 @@ function getRequestVerificationCodeParameters(store, method, meta, device, attem
|
|
|
1245
1260
|
'manage_call_permission', wantsFlash ? 'true' : 'false',
|
|
1246
1261
|
'clicked_education_link', 'false',
|
|
1247
1262
|
'aid', '',
|
|
1248
|
-
//
|
|
1249
|
-
// rejected as bad_format, an absent field is not validated at all.
|
|
1263
|
+
// Always absent — see the note above the function.
|
|
1250
1264
|
'push_code', null
|
|
1251
1265
|
];
|
|
1252
1266
|
}
|
|
@@ -1257,7 +1271,7 @@ function getRequestVerificationCodeParameters(store, method, meta, device, attem
|
|
|
1257
1271
|
'sim_mcc', meta.mcc,
|
|
1258
1272
|
'sim_mnc', meta.mnc,
|
|
1259
1273
|
'jailbroken', '0',
|
|
1260
|
-
//
|
|
1274
|
+
// Always absent — see the note above the function.
|
|
1261
1275
|
'push_code', null,
|
|
1262
1276
|
'cellular_strength', '1'
|
|
1263
1277
|
];
|
|
@@ -1357,11 +1371,17 @@ async function buildPayload(store, waVersion, useToken, extraPairs) {
|
|
|
1357
1371
|
.update(Buffer.concat([store.identityId, stripKeyPrefix(store.noiseKeyPair.public)]))
|
|
1358
1372
|
.digest()
|
|
1359
1373
|
);
|
|
1360
|
-
// The push token a real install always has
|
|
1361
|
-
//
|
|
1362
|
-
//
|
|
1363
|
-
//
|
|
1364
|
-
|
|
1374
|
+
// The push token a real install always has, taken from the transport that
|
|
1375
|
+
// matches this device's platform: Firebase for Android, nothing for iOS until
|
|
1376
|
+
// APNs exists. Asking ./fcm directly would have put a Google token in an
|
|
1377
|
+
// iPhone's registration body — a device that keeps an MCS stream to Google
|
|
1378
|
+
// and announces itself as iOS is not a device anyone ships.
|
|
1379
|
+
//
|
|
1380
|
+
// Acquired once and cached on the store, so the two registration steps share
|
|
1381
|
+
// one. Returns null on any failure or on a platform with no transport, which
|
|
1382
|
+
// drops the field and leaves the body exactly as it was before push tokens
|
|
1383
|
+
// were wired up.
|
|
1384
|
+
const pushToken = await pushClientFor(device).getPushToken(store, device);
|
|
1365
1385
|
|
|
1366
1386
|
const attestFields = await attestation.attestationFields(device, nonceB64, { pushToken });
|
|
1367
1387
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whalibmob",
|
|
3
|
-
"version": "5.23.
|
|
3
|
+
"version": "5.23.3",
|
|
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",
|