whalibmob 5.19.3 → 5.19.4

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.
Files changed (2) hide show
  1. package/lib/fcm.js +107 -26
  2. package/package.json +1 -1
package/lib/fcm.js CHANGED
@@ -300,7 +300,30 @@ function parseDurationSeconds(text) {
300
300
  return m ? parseInt(m[1], 10) : 0;
301
301
  }
302
302
 
303
- async function register3(config, session) {
303
+ // register3 answers a fresh android id with Error=PHONE_REGISTRATION_ERROR far
304
+ // more often than not: Google will not register a device id the same second it
305
+ // minted it, and a real phone's id is always days old by the time it asks. The
306
+ // id has to age. A short backoff between tries is usually enough, and the same
307
+ // id is reused across them precisely so it ages rather than being replaced.
308
+ //
309
+ // These are the errors worth trying again; anything else is a settled no.
310
+ // PHONE_REGISTRATION_ERROR the common transient one — the id is too new
311
+ // SERVICE_NOT_AVAILABLE Google-side hiccup
312
+ // INTERNAL_SERVER_ERROR the same, phrased differently
313
+ // AUTHENTICATION_FAILED the AidLogin is stale; needs a fresh checkin
314
+ const REGISTER3_RETRYABLE = new Set([
315
+ 'PHONE_REGISTRATION_ERROR', 'SERVICE_NOT_AVAILABLE', 'INTERNAL_SERVER_ERROR'
316
+ ]);
317
+
318
+ const REGISTER3_MAX_ATTEMPTS = 4;
319
+ // Waits before attempts 2, 3 and 4. Growing, so the id gets progressively more
320
+ // time to settle without dragging registration out — the whole budget is ~12s,
321
+ // well under one SMS round-trip.
322
+ const REGISTER3_BACKOFF_MS = [1500, 4000, 6500];
323
+
324
+ const sleep = ms => new Promise(r => setTimeout(r, ms));
325
+
326
+ function buildRegister3Body(config, session) {
304
327
  const form = {
305
328
  app: config.packageName,
306
329
  sender: config.senderId,
@@ -321,35 +344,53 @@ async function register3(config, session) {
321
344
  'X-Goog-Firebase-Installations-Auth': session.fisAuthToken,
322
345
  'X-firebase-app-name-hash': ''
323
346
  };
324
-
325
- const body = Buffer.from(
347
+ return Buffer.from(
326
348
  Object.keys(form)
327
349
  .map(k => encodeURIComponent(k) + '=' + encodeURIComponent(form[k]))
328
350
  .join('&'),
329
351
  'utf8'
330
352
  );
353
+ }
331
354
 
332
- const res = await httpPost(REGISTER_URL, body, {
355
+ // One attempt. Never throws for an answer Google actually sent — a token, an
356
+ // Error= line and a 5xx are all outcomes, classified so the caller can decide
357
+ // whether to try again. A network reject still propagates.
358
+ //
359
+ // Returns one of:
360
+ // { token } success
361
+ // { error, retryable, needsCheckin } a refusal
362
+ async function register3Once(config, session) {
363
+ const body = buildRegister3Body(config, session);
364
+ const res = await httpPost(REGISTER_URL, body, {
333
365
  'Authorization': 'AidLogin ' + session.androidId + ':' + session.securityToken,
334
366
  'Content-Type': 'application/x-www-form-urlencoded',
335
367
  'User-Agent': 'Android-GCM/1.5 (generic LRX22C)',
336
368
  'app': config.packageName
337
369
  });
370
+
371
+ if (res.status >= 500) {
372
+ return { error: 'HTTP ' + res.status, retryable: true, needsCheckin: false };
373
+ }
338
374
  if (res.status < 200 || res.status >= 300) {
339
- throw new Error('register3 HTTP ' + res.status + ': ' + res.body.toString('utf8').slice(0, 200));
375
+ return { error: 'HTTP ' + res.status + ': ' + res.body.toString('utf8').slice(0, 120),
376
+ retryable: false, needsCheckin: false };
340
377
  }
341
378
 
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.
379
+ // A flat key=value list. A token line is success; an Error= line names the
380
+ // reason; anything else is treated as an unrecognised refusal.
344
381
  const text = res.body.toString('utf8').trim();
345
382
  for (const line of text.split('\n')) {
346
383
  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;
384
+ if (eq <= 0) continue;
385
+ const key = line.slice(0, eq).trim();
386
+ const val = line.slice(eq + 1).trim();
387
+ if (key === 'token' && val) return { token: val };
388
+ if (key === 'Error') {
389
+ const needsCheckin = val === 'AUTHENTICATION_FAILED';
390
+ return { error: val, retryable: REGISTER3_RETRYABLE.has(val) || needsCheckin, needsCheckin };
350
391
  }
351
392
  }
352
- throw new Error('register3 returned no token: ' + text.slice(0, 200));
393
+ return { error: text.slice(0, 120) || 'empty response', retryable: false, needsCheckin: false };
353
394
  }
354
395
 
355
396
  // ─── public entry point ──────────────────────────────────────────────────────
@@ -401,32 +442,70 @@ async function getPushToken(store, device) {
401
442
  const ids = await checkin();
402
443
  androidId = ids.androidId;
403
444
  securityToken = ids.securityToken;
445
+ // Persist the identity the moment it exists, so a failure in a later step
446
+ // still leaves the aged android id behind for the next attempt rather than
447
+ // throwing it away and minting a second one.
448
+ store.fcm = Object.assign({}, store.fcm, { androidId, securityToken });
404
449
  }
405
450
 
406
451
  _whaDbg('[DBG] FCM step 2/3 FIS install (' + config.packageName + ')');
407
- const fis = await fisInstall(config, prev.fid);
452
+ let fis = await fisInstall(config, prev.fid);
408
453
 
409
454
  _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
- });
455
+ let token = null;
456
+ let checkinRedone = false;
457
+
458
+ for (let attempt = 1; attempt <= REGISTER3_MAX_ATTEMPTS; attempt++) {
459
+ const result = await register3Once(config, { androidId, securityToken, fid: fis.fid, fisAuthToken: fis.authToken });
460
+
461
+ if (result.token) { token = result.token; break; }
462
+
463
+ const last = attempt === REGISTER3_MAX_ATTEMPTS;
464
+ _whaDbg('[DBG] FCM register3 attempt ' + attempt + '/' + REGISTER3_MAX_ATTEMPTS +
465
+ ' → ' + result.error + (result.retryable && !last ? ' (retrying)' : ''));
466
+
467
+ if (!result.retryable || last) break;
468
+
469
+ // A stale AidLogin is not fixed by waiting: the android id itself is no
470
+ // longer accepted, so mint a fresh one — but only once, so a server that
471
+ // keeps saying this cannot spin us forever.
472
+ if (result.needsCheckin && !checkinRedone) {
473
+ checkinRedone = true;
474
+ _whaDbg('[DBG] FCM re-running checkin after AUTHENTICATION_FAILED');
475
+ const ids = await checkin();
476
+ androidId = ids.androidId;
477
+ securityToken = ids.securityToken;
478
+ // The FIS install is bound to nothing device-specific, but refreshing
479
+ // it alongside a new id keeps the pair consistent.
480
+ fis = await fisInstall(config, fis.fid);
481
+ }
482
+
483
+ await sleep(REGISTER3_BACKOFF_MS[Math.min(attempt - 1, REGISTER3_BACKOFF_MS.length - 1)]);
484
+ }
415
485
 
416
- store.fcm = {
486
+ // Persist the Firebase identity even when no token came back. The android id
487
+ // is the valuable part — reused on the next registration attempt, by which
488
+ // time it has aged and register3 is far more likely to accept it. This is
489
+ // what turns a first-run PHONE_REGISTRATION_ERROR into a second-run success.
490
+ store.fcm = Object.assign({}, store.fcm, {
417
491
  androidId, securityToken,
418
492
  fid: fis.fid,
419
493
  fisAuthToken: fis.authToken,
420
494
  fisRefreshToken: fis.refreshToken,
421
495
  fisExpiresAt: fis.expiresAt,
422
- token
423
- };
424
- _whaDbg('[DBG] FCM push token acquired, length=' + token.length);
425
- return token;
496
+ token: token || (store.fcm && store.fcm.token) || null
497
+ });
498
+
499
+ if (token) {
500
+ _whaDbg('[DBG] FCM push token acquired, length=' + token.length);
501
+ return token;
502
+ }
503
+ _whaDbg('[DBG] FCM push token unavailable after ' + REGISTER3_MAX_ATTEMPTS +
504
+ ' attempts — registering without one (android id kept for next time)');
505
+ return null;
426
506
  } 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.
507
+ // A checkin that succeeded before a later step threw has already written
508
+ // its android id to store.fcm above, so it survives into the next attempt.
430
509
  _whaDbg('[DBG] FCM push token unavailable: ' + (err && err.message) +
431
510
  ' — registering without one');
432
511
  return null;
@@ -441,5 +520,7 @@ module.exports = {
441
520
  encodeCheckinRequest,
442
521
  decodeCheckinResponse,
443
522
  generateFid,
444
- parseDurationSeconds
523
+ parseDurationSeconds,
524
+ register3Once,
525
+ REGISTER3_MAX_ATTEMPTS
445
526
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whalibmob",
3
- "version": "5.19.3",
3
+ "version": "5.19.4",
4
4
  "description": "WhatsApp library for interaction with WhatsApp Mobile API and web ",
5
5
  "author": "Kunboruto20",
6
6
  "main": "index.js",