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.
- package/lib/fcm.js +107 -26
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
375
|
+
return { error: 'HTTP ' + res.status + ': ' + res.body.toString('utf8').slice(0, 120),
|
|
376
|
+
retryable: false, needsCheckin: false };
|
|
340
377
|
}
|
|
341
378
|
|
|
342
|
-
//
|
|
343
|
-
//
|
|
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
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
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
|
-
|
|
452
|
+
let fis = await fisInstall(config, prev.fid);
|
|
408
453
|
|
|
409
454
|
_whaDbg('[DBG] FCM step 3/3 register3');
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
-
|
|
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
|
-
|
|
425
|
-
|
|
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
|
-
//
|
|
428
|
-
//
|
|
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
|
};
|