whalibmob 5.19.2 → 5.19.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/.env.example CHANGED
@@ -144,6 +144,18 @@
144
144
  # Only if `socks` lives somewhere require() cannot find it:
145
145
  # WA_SOCKS_LIB=/absolute/path/to/socks/build/index.js
146
146
 
147
+ # ─── Push token ──────────────────────────────────────────────────────────────
148
+ # Every WhatsApp on a real phone holds a Firebase push token — it is how the
149
+ # server wakes the app. Registration fetches one from Google and ships it as
150
+ # push_token, so the request looks like the install it claims to be.
151
+ #
152
+ # On by default. Three plain HTTPS calls to Google, once per number, cached with
153
+ # the session afterwards. Any failure is silent: the field is dropped and
154
+ # registration proceeds exactly as it would without it.
155
+ #
156
+ # Set to 0 to skip it entirely.
157
+ # WA_FCM_PUSH=0
158
+
147
159
 
148
160
  # ─── Registration funnel telemetry ───────────────────────────────────────────
149
161
  # Registration reports the screens it passes through to WhatsApp's /client_log,
package/README.md CHANGED
@@ -152,6 +152,7 @@ npm install -g whalibmob
152
152
  - [Two Sessions on One Number](#two-sessions-on-one-number)
153
153
  - [The Number WhatsApp Files Your Account Under](#the-number-whatsapp-files-your-account-under)
154
154
  - [When Registration Is Refused for Consent](#when-registration-is-refused-for-consent)
155
+ - [The Push Token](#the-push-token)
155
156
  - [Routing Traffic Through a Proxy](#routing-traffic-through-a-proxy)
156
157
  - [Saving & Restoring Sessions](#saving--restoring-sessions)
157
158
  - [Signal Store Utilities](#signal-store-utilities)
@@ -2488,6 +2489,26 @@ If that is refused too, the number has to go through the real app once, on a pho
2488
2489
  > [!NOTE]
2489
2490
  > The `login` field in that reply is worth reading. Brazilian mobiles gained a ninth digit that WhatsApp never adopted, so `+5571976034186` is filed as `+557176034186`. whalibmob adopts the server's form automatically on a successful registration and saves the session under it — the digit difference is not itself the failure.
2490
2491
 
2492
+ ## The Push Token
2493
+
2494
+ Every WhatsApp on a real phone holds a Firebase push token. It is the address Google uses to wake the app when a message arrives, and no install exists without one — so a registration that ships no `push_token` describes a WhatsApp that cannot be notified.
2495
+
2496
+ Registration fetches a real one and sends it, in three plain HTTPS calls to Google:
2497
+
2498
+ | Step | Endpoint | Yields |
2499
+ |---|---|---|
2500
+ | 1 | `android.clients.google.com/checkin` | an android id and security token — the device's identity with Google |
2501
+ | 2 | `firebaseinstallations.googleapis.com` | a Firebase installation for WhatsApp's project |
2502
+ | 3 | `android.clients.google.com/c2dm/register3` | the FCM token itself |
2503
+
2504
+ No root, no Frida, no phone. This is unrelated to Play Integrity attestation — the two travel in the same request but come from different places, and one works without the other.
2505
+
2506
+ It runs once per number. The Firebase identity is cached on the session, because Google issues an android id once and expects it back; fetching a new one per registration step would mint a fresh phantom device each time.
2507
+
2508
+ **Failure is silent by design.** A blocked network, a refusal from Google, a malformed answer — all end with the field omitted and registration proceeding exactly as it did before push tokens were wired up. A push token helps; it is never a prerequisite.
2509
+
2510
+ Turn it off with `WA_FCM_PUSH=0`.
2511
+
2491
2512
  ## Routing Traffic Through a Proxy
2492
2513
 
2493
2514
  Two reasons to want this. Registration is the part of the protocol most likely to be refused from a datacenter IP, so a residential exit helps with security blocks and undeterminable number statuses. And on a network that cannot reach WhatsApp at all, nothing works without one.
@@ -1257,7 +1257,13 @@ async function buildPayload(store, waVersion, useToken, extraPairs) {
1257
1257
  .update(Buffer.concat([store.identityId, stripKeyPrefix(store.noiseKeyPair.public)]))
1258
1258
  .digest()
1259
1259
  );
1260
- const attestFields = await attestation.attestationFields(device, nonceB64);
1260
+ // The push token a real install always has. Acquired from Google once and
1261
+ // then cached on the store, so the two registration steps share one. Returns
1262
+ // null on any failure, which drops the field and leaves the body exactly as
1263
+ // it was before push tokens were wired up.
1264
+ const pushToken = await require('./fcm').getPushToken(store, device);
1265
+
1266
+ const attestFields = await attestation.attestationFields(device, nonceB64, { pushToken });
1261
1267
 
1262
1268
  return buildForm([
1263
1269
  'cc', cc,
package/lib/Store.js CHANGED
@@ -201,6 +201,10 @@ function storeToJson(store) {
201
201
  // call it has to know, since that code is read off the caller ID rather
202
202
  // than out of a message.
203
203
  codeMethod: store.codeMethod || null,
204
+ // Firebase identity behind the push_token. Google issues an android id once
205
+ // and expects it back; discarding it would mint a new phantom device on
206
+ // every registration step, which is worse than carrying one.
207
+ fcm: store.fcm || null,
204
208
  name,
205
209
  version,
206
210
  device,
@@ -244,6 +248,9 @@ function storeFromJson(obj) {
244
248
  registered: !!obj.registered,
245
249
  codePending: obj.codePending || false,
246
250
  codeMethod: obj.codeMethod || null,
251
+ // Absent on stores written before push tokens existed; null simply means
252
+ // the first registration step will fetch one.
253
+ fcm: obj.fcm || null,
247
254
  name,
248
255
  version,
249
256
  device,
package/lib/fcm.js ADDED
@@ -0,0 +1,445 @@
1
+ 'use strict';
2
+
3
+ // Firebase Cloud Messaging registration — the push_token a real WhatsApp
4
+ // install carries.
5
+ //
6
+ // Every WhatsApp on a real phone holds a push token, because that is how the
7
+ // server wakes it when a message arrives. A registration that ships no
8
+ // push_token describes a WhatsApp that cannot be notified, which is a device
9
+ // that does not exist. The field was already wired through
10
+ // Attestation.attestationFields; nothing ever filled it, so buildForm dropped
11
+ // it and every registration went out without one.
12
+ //
13
+ // Three steps, all plain HTTPS with Google — no root, no Frida, no phone:
14
+ //
15
+ // 1. checkin android.clients.google.com/checkin
16
+ // protobuf in, protobuf out; yields an android id and a
17
+ // security token, which together are the device's identity
18
+ // with Google.
19
+ // 2. FIS install firebaseinstallations.googleapis.com/v1/projects/…
20
+ // registers a Firebase installation for WhatsApp's project
21
+ // and returns a short-lived auth token.
22
+ // 3. register3 android.clients.google.com/c2dm/register3
23
+ // trades the two for the actual FCM token.
24
+ //
25
+ // Everything here fails soft. A blocked network, a refusal from Google, a
26
+ // malformed answer — all of them return null, the field is omitted, and
27
+ // registration proceeds exactly as it did before this file existed. A push
28
+ // token is a signal that helps; it is not a prerequisite, and it must never be
29
+ // the reason a registration cannot be attempted.
30
+ //
31
+ // The session is persisted with the account. Google hands out an android id
32
+ // once and expects it to keep coming back; re-running checkin on every
33
+ // registration step would mint a new device each time, which is itself a
34
+ // pattern no real phone produces.
35
+
36
+ const https = require('https');
37
+ const zlib = require('zlib');
38
+ const crypto = require('crypto');
39
+
40
+ const { dbg: _whaDbg } = require('./logger');
41
+ const { proxyAgent } = require('./socks');
42
+
43
+ // WhatsApp's own Firebase project. These are public client-side identifiers,
44
+ // the same ones the APK ships in google-services.json.
45
+ const FCM_CONFIG = {
46
+ personal: {
47
+ projectId: 'whatsapp-messenger',
48
+ appId: '1:293955441834:android:7373a2d0bdfa3228',
49
+ apiKey: 'AIzaSyCGOJbGQ95SWrXxl8wk-_cRQZcJl42bvDU',
50
+ senderId: '293955441834',
51
+ packageName: 'com.whatsapp',
52
+ certSha1: '38a0f7d505fe18fec64fbf343ecaaaf310dbd799'
53
+ },
54
+ business: {
55
+ projectId: 'whatsapp-messenger',
56
+ appId: '1:293955441834:android:7373a2d0bdfa3228',
57
+ apiKey: 'AIzaSyCGOJbGQ95SWrXxl8wk-_cRQZcJl42bvDU',
58
+ senderId: '293955441834',
59
+ packageName: 'com.whatsapp.w4b',
60
+ certSha1: '38a0f7d505fe18fec64fbf343ecaaaf310dbd799'
61
+ }
62
+ };
63
+
64
+ const CHECKIN_URL = 'https://android.clients.google.com/checkin';
65
+ const REGISTER_URL = 'https://android.clients.google.com/c2dm/register3';
66
+ const FIS_URL = 'https://firebaseinstallations.googleapis.com/v1/projects/%s/installations';
67
+
68
+ const HTTP_TIMEOUT_MS = 15000;
69
+
70
+ // A Firebase installation id: 17 random bytes with the top nibble pinned to
71
+ // 0b0111 per the FIS spec, url-safe base64, truncated to 22 characters.
72
+ const FID_BYTES = 17;
73
+ const FID_LENGTH = 22;
74
+
75
+ // ─── minimal protobuf writer ─────────────────────────────────────────────────
76
+ //
77
+ // The checkin message is the only protobuf this file has to produce, and it is
78
+ // a handful of scalar fields. A writer for exactly those is smaller than a
79
+ // schema plus a runtime, and it keeps the wire format visible next to the code
80
+ // that depends on it.
81
+
82
+ function varint(value) {
83
+ let v = typeof value === 'bigint' ? value : BigInt(Math.trunc(value));
84
+ if (v < 0n) v += 1n << 64n; // two's complement, as protobuf does
85
+ const out = [];
86
+ do {
87
+ let byte = Number(v & 0x7fn);
88
+ v >>= 7n;
89
+ if (v > 0n) byte |= 0x80;
90
+ out.push(byte);
91
+ } while (v > 0n);
92
+ return Buffer.from(out);
93
+ }
94
+
95
+ const pbTag = (field, wire) => varint((field << 3) | wire);
96
+ const pbInt = (field, n) => Buffer.concat([pbTag(field, 0), varint(n)]);
97
+ const pbBool = (field, b) => Buffer.concat([pbTag(field, 0), varint(b ? 1 : 0)]);
98
+
99
+ function pbBytes(field, buf) {
100
+ const b = Buffer.isBuffer(buf) ? buf : Buffer.from(String(buf), 'utf8');
101
+ return Buffer.concat([pbTag(field, 2), varint(b.length), b]);
102
+ }
103
+ const pbStr = pbBytes;
104
+ const pbMsg = pbBytes;
105
+
106
+ // ─── checkin request / response ──────────────────────────────────────────────
107
+
108
+ function encodeCheckinRequest() {
109
+ const now = Date.now();
110
+
111
+ // The device this claims to be. A fixed, unremarkable build: Google's own
112
+ // reference hardware, which is what every FCM client library has announced
113
+ // for a decade. Randomising it would make each registration a different
114
+ // phantom device, which reads worse than one ordinary repeated one.
115
+ const build = Buffer.concat([
116
+ pbStr(1, 'google/razor/flo:5.0.1/LRX22C/1602158:user/release-keys'),
117
+ pbStr(2, 'flo'),
118
+ pbStr(3, 'google'),
119
+ pbStr(6, 'android-google'),
120
+ pbInt(7, Math.floor(now / 1000)),
121
+ pbInt(10, 30),
122
+ pbStr(11, 'Nexus 7'),
123
+ pbStr(12, 'asus'),
124
+ pbStr(13, 'razor'),
125
+ pbBool(14, false)
126
+ ]);
127
+
128
+ const event = Buffer.concat([
129
+ pbStr(1, 'event_log_start'),
130
+ pbInt(3, now)
131
+ ]);
132
+
133
+ const checkin = Buffer.concat([
134
+ pbMsg(1, build),
135
+ pbInt(2, 0),
136
+ pbMsg(3, event),
137
+ pbInt(9, 0)
138
+ ]);
139
+
140
+ // 53 bits of randomness rather than a full 63: it stays an exact JS integer,
141
+ // and Google only needs the value to be unlikely to collide.
142
+ const loggingId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
143
+
144
+ return Buffer.concat([
145
+ pbInt(2, 0),
146
+ pbMsg(4, checkin),
147
+ pbStr(6, 'en_US'),
148
+ pbInt(7, loggingId),
149
+ pbStr(12, 'UTC'),
150
+ pbInt(14, 3),
151
+ pbInt(20, 0),
152
+ pbInt(22, 0)
153
+ ]);
154
+ }
155
+
156
+ // Only two fields matter in the answer, both fixed64: 7 = android id,
157
+ // 8 = security token. They are 64-bit and are echoed back to Google verbatim,
158
+ // so they are kept as BigInt and only ever stringified — a Number would round
159
+ // them and the Authorization header would be silently wrong.
160
+ function decodeCheckinResponse(buf) {
161
+ let i = 0;
162
+ const out = { androidId: null, securityToken: null };
163
+
164
+ const readVarint = () => {
165
+ let result = 0n, shift = 0n;
166
+ while (i < buf.length) {
167
+ const b = buf[i++];
168
+ result |= BigInt(b & 0x7f) << shift;
169
+ shift += 7n;
170
+ if (!(b & 0x80)) break;
171
+ }
172
+ return result;
173
+ };
174
+
175
+ while (i < buf.length) {
176
+ const key = Number(readVarint());
177
+ const field = key >> 3;
178
+ const wire = key & 7;
179
+
180
+ if (wire === 0) { readVarint(); }
181
+ else if (wire === 1) {
182
+ if (i + 8 > buf.length) break;
183
+ const v = buf.readBigUInt64LE(i);
184
+ i += 8;
185
+ if (field === 7) out.androidId = v;
186
+ if (field === 8) out.securityToken = v;
187
+ }
188
+ else if (wire === 2) {
189
+ const len = Number(readVarint());
190
+ i += len;
191
+ }
192
+ else if (wire === 5) { i += 4; }
193
+ else break;
194
+ }
195
+ return out;
196
+ }
197
+
198
+ // ─── HTTP ────────────────────────────────────────────────────────────────────
199
+ //
200
+ // Routed through the SOCKS agent when one is configured, like every other
201
+ // outbound call in the library — a machine that needs a proxy to reach WhatsApp
202
+ // generally needs one to reach Google too.
203
+
204
+ function httpPost(url, body, headers) {
205
+ return new Promise((resolve, reject) => {
206
+ const u = new URL(url);
207
+ const agent = proxyAgent();
208
+ const req = https.request({
209
+ hostname: u.hostname,
210
+ port: u.port || 443,
211
+ path: u.pathname + u.search,
212
+ method: 'POST',
213
+ headers: Object.assign({ 'Content-Length': body.length }, headers),
214
+ agent: agent || undefined
215
+ }, res => {
216
+ const chunks = [];
217
+ res.on('data', c => chunks.push(c));
218
+ res.on('end', () => {
219
+ let raw = Buffer.concat(chunks);
220
+ if (String(res.headers['content-encoding'] || '').includes('gzip')) {
221
+ try { raw = zlib.gunzipSync(raw); } catch (_) {}
222
+ }
223
+ resolve({ status: res.statusCode || 0, body: raw });
224
+ });
225
+ res.on('error', reject);
226
+ });
227
+ req.on('error', reject);
228
+ req.setTimeout(HTTP_TIMEOUT_MS, () => req.destroy(new Error('timed out')));
229
+ req.write(body);
230
+ req.end();
231
+ });
232
+ }
233
+
234
+ // ─── the three steps ─────────────────────────────────────────────────────────
235
+
236
+ async function checkin() {
237
+ const body = zlib.gzipSync(encodeCheckinRequest());
238
+ const res = await httpPost(CHECKIN_URL, body, {
239
+ 'Content-Type': 'application/x-protobuffer',
240
+ 'Content-Encoding': 'gzip',
241
+ 'Accept-Encoding': 'gzip',
242
+ 'User-Agent': 'Android-Checkin/2.0 (generic JLS36G); gzip'
243
+ });
244
+ if (res.status < 200 || res.status >= 300) {
245
+ throw new Error('checkin HTTP ' + res.status);
246
+ }
247
+ const { androidId, securityToken } = decodeCheckinResponse(res.body);
248
+ if (!androidId || !securityToken) {
249
+ throw new Error('checkin returned no android id (' + res.body.length + ' B)');
250
+ }
251
+ return { androidId: androidId.toString(), securityToken: securityToken.toString() };
252
+ }
253
+
254
+ function generateFid() {
255
+ const raw = crypto.randomBytes(FID_BYTES);
256
+ raw[0] = 0b01110000 | (raw[0] & 0b00001111);
257
+ return raw.toString('base64url').slice(0, FID_LENGTH);
258
+ }
259
+
260
+ async function fisInstall(config, existingFid) {
261
+ const fid = existingFid || generateFid();
262
+ const body = Buffer.from(JSON.stringify({
263
+ fid,
264
+ appId: config.appId,
265
+ authVersion: 'FIS_v2',
266
+ sdkVersion: 'a:17.0.0'
267
+ }), 'utf8');
268
+
269
+ const res = await httpPost(FIS_URL.replace('%s', config.projectId), body, {
270
+ 'Content-Type': 'application/json',
271
+ 'Accept': 'application/json',
272
+ 'Cache-Control': 'no-cache',
273
+ 'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 11; Pixel 2 Build/RP1A.201005.004) FirebaseInstallations/17.0.0',
274
+ 'X-Firebase-Client': Buffer.from('{"heartbeats":[],"version":2}', 'utf8').toString('base64url'),
275
+ 'x-goog-api-key': config.apiKey,
276
+ 'X-Android-Package': config.packageName,
277
+ 'X-Android-Cert': config.certSha1.replace(/:/g, '').toUpperCase()
278
+ });
279
+ if (res.status < 200 || res.status >= 300) {
280
+ throw new Error('FIS install HTTP ' + res.status + ': ' + res.body.toString('utf8').slice(0, 200));
281
+ }
282
+
283
+ let parsed;
284
+ try { parsed = JSON.parse(res.body.toString('utf8')); }
285
+ catch (_) { throw new Error('FIS install returned non-JSON'); }
286
+
287
+ const auth = parsed.authToken || {};
288
+ return {
289
+ fid: parsed.fid || fid,
290
+ authToken: auth.token || '',
291
+ refreshToken: parsed.refreshToken || '',
292
+ expiresAt: Math.floor(Date.now() / 1000) + parseDurationSeconds(auth.expiresIn)
293
+ };
294
+ }
295
+
296
+ // Google writes durations as "604800s". A missing or malformed value becomes 0,
297
+ // which reads as already expired and simply forces a refresh next time.
298
+ function parseDurationSeconds(text) {
299
+ const m = /^(\d+)s$/.exec(String(text || ''));
300
+ return m ? parseInt(m[1], 10) : 0;
301
+ }
302
+
303
+ async function register3(config, session) {
304
+ const form = {
305
+ app: config.packageName,
306
+ sender: config.senderId,
307
+ device: session.androidId,
308
+ cert: config.certSha1.replace(/:/g, '').toLowerCase(),
309
+ app_ver: '1',
310
+ target_ver: '30',
311
+ info: '',
312
+ 'X-subtype': config.senderId,
313
+ 'X-subscription': config.senderId,
314
+ 'X-app_ver': '1',
315
+ 'X-osv': '30',
316
+ 'X-cliv': 'fiid-21.1.0',
317
+ 'X-gmsv': '240913000',
318
+ 'X-scope': '*',
319
+ 'X-appid': session.fid,
320
+ 'X-gmp_app_id': config.appId,
321
+ 'X-Goog-Firebase-Installations-Auth': session.fisAuthToken,
322
+ 'X-firebase-app-name-hash': ''
323
+ };
324
+
325
+ const body = Buffer.from(
326
+ Object.keys(form)
327
+ .map(k => encodeURIComponent(k) + '=' + encodeURIComponent(form[k]))
328
+ .join('&'),
329
+ 'utf8'
330
+ );
331
+
332
+ const res = await httpPost(REGISTER_URL, body, {
333
+ 'Authorization': 'AidLogin ' + session.androidId + ':' + session.securityToken,
334
+ 'Content-Type': 'application/x-www-form-urlencoded',
335
+ 'User-Agent': 'Android-GCM/1.5 (generic LRX22C)',
336
+ 'app': config.packageName
337
+ });
338
+ if (res.status < 200 || res.status >= 300) {
339
+ throw new Error('register3 HTTP ' + res.status + ': ' + res.body.toString('utf8').slice(0, 200));
340
+ }
341
+
342
+ // The answer is a flat key=value list; anything other than a token line is
343
+ // an error Google chose to report with a 200.
344
+ const text = res.body.toString('utf8').trim();
345
+ for (const line of text.split('\n')) {
346
+ const eq = line.indexOf('=');
347
+ if (eq > 0 && line.slice(0, eq).trim() === 'token') {
348
+ const token = line.slice(eq + 1).trim();
349
+ if (token) return token;
350
+ }
351
+ }
352
+ throw new Error('register3 returned no token: ' + text.slice(0, 200));
353
+ }
354
+
355
+ // ─── public entry point ──────────────────────────────────────────────────────
356
+
357
+ /** Whether push-token acquisition is switched on. Opt out with WA_FCM_PUSH=0. */
358
+ function enabled() {
359
+ const v = process.env.WA_FCM_PUSH;
360
+ return v === undefined || !(v === '0' || v.toLowerCase() === 'false' || v === 'off');
361
+ }
362
+
363
+ // A token in hand is reused. Google mints an android id once and expects it
364
+ // back; running checkin again per registration step would present a new phantom
365
+ // device every time.
366
+ function cached(store) {
367
+ const s = store && store.fcm;
368
+ return s && s.token ? s.token : null;
369
+ }
370
+
371
+ /**
372
+ * The FCM push token for this account, or null.
373
+ *
374
+ * Never throws and never rejects: every failure path ends as null, which
375
+ * attestationFields turns into an omitted field and buildForm leaves out of the
376
+ * body entirely — byte for byte what registration sent before this existed.
377
+ *
378
+ * @param {object} store the account store; the session is cached on store.fcm
379
+ * @param {object} device device config; device.business picks the w4b package
380
+ * @returns {Promise<string|null>}
381
+ */
382
+ async function getPushToken(store, device) {
383
+ if (!enabled()) return null;
384
+ if (!store) return null;
385
+
386
+ const hit = cached(store);
387
+ if (hit) {
388
+ _whaDbg('[DBG] FCM push token from cache, length=' + hit.length);
389
+ return hit;
390
+ }
391
+
392
+ const config = (device && device.business) ? FCM_CONFIG.business : FCM_CONFIG.personal;
393
+ const prev = store.fcm || {};
394
+
395
+ try {
396
+ let androidId = prev.androidId;
397
+ let securityToken = prev.securityToken;
398
+
399
+ if (!androidId || !securityToken) {
400
+ _whaDbg('[DBG] FCM step 1/3 checkin');
401
+ const ids = await checkin();
402
+ androidId = ids.androidId;
403
+ securityToken = ids.securityToken;
404
+ }
405
+
406
+ _whaDbg('[DBG] FCM step 2/3 FIS install (' + config.packageName + ')');
407
+ const fis = await fisInstall(config, prev.fid);
408
+
409
+ _whaDbg('[DBG] FCM step 3/3 register3');
410
+ const token = await register3(config, {
411
+ androidId, securityToken,
412
+ fid: fis.fid,
413
+ fisAuthToken: fis.authToken
414
+ });
415
+
416
+ store.fcm = {
417
+ androidId, securityToken,
418
+ fid: fis.fid,
419
+ fisAuthToken: fis.authToken,
420
+ fisRefreshToken: fis.refreshToken,
421
+ fisExpiresAt: fis.expiresAt,
422
+ token
423
+ };
424
+ _whaDbg('[DBG] FCM push token acquired, length=' + token.length);
425
+ return token;
426
+ } catch (err) {
427
+ // Keep whatever identity was established: a checkin that succeeded before a
428
+ // later step failed is worth carrying into the next attempt rather than
429
+ // minting a second android id.
430
+ _whaDbg('[DBG] FCM push token unavailable: ' + (err && err.message) +
431
+ ' — registering without one');
432
+ return null;
433
+ }
434
+ }
435
+
436
+ module.exports = {
437
+ getPushToken,
438
+ enabled,
439
+ FCM_CONFIG,
440
+ // exported for tests
441
+ encodeCheckinRequest,
442
+ decodeCheckinResponse,
443
+ generateFid,
444
+ parseDurationSeconds
445
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whalibmob",
3
- "version": "5.19.2",
3
+ "version": "5.19.3",
4
4
  "description": "WhatsApp library for interaction with WhatsApp Mobile API and web ",
5
5
  "author": "Kunboruto20",
6
6
  "main": "index.js",