whalibmob 5.14.6 → 5.14.8
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/Client.js +241 -17
- package/lib/Registration.js +161 -16
- package/package.json +1 -1
package/lib/Client.js
CHANGED
|
@@ -171,6 +171,17 @@ const PING_INTERVAL_MS = 25000;
|
|
|
171
171
|
const KEEPALIVE_INTERVAL = 20000;
|
|
172
172
|
const RECONNECT_BACKOFF = [1000, 2000, 4000, 8000, 15000, 30000];
|
|
173
173
|
|
|
174
|
+
// When the server is down to this many one-time keys or fewer, send it more.
|
|
175
|
+
// The number is small on purpose: the server tells us when it is running low,
|
|
176
|
+
// and this is the floor for the case where that notice never arrives.
|
|
177
|
+
const PRE_KEY_SERVER_MIN = 5;
|
|
178
|
+
|
|
179
|
+
// How often to ask the server how many it has left. The drain is invisible from
|
|
180
|
+
// here — every bundle handed out is a key gone, and none of that reaches this
|
|
181
|
+
// side — so on a session that stays up for days this is the only thing that
|
|
182
|
+
// notices before the pool is empty and the account stops being reachable.
|
|
183
|
+
const PRE_KEY_CHECK_INTERVAL = 30 * 60 * 1000; // 30 minutes
|
|
184
|
+
|
|
174
185
|
// ─── Key helpers ──────────────────────────────────────────────────────────────
|
|
175
186
|
|
|
176
187
|
function intToBytes(n, byteLen) {
|
|
@@ -1234,7 +1245,12 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1234
1245
|
this.setOnline(true);
|
|
1235
1246
|
|
|
1236
1247
|
this._requestMediaConnection();
|
|
1237
|
-
|
|
1248
|
+
// Ask what the server still has rather than uploading blind. It answers
|
|
1249
|
+
// with a number this side has no other way of learning, and then with the
|
|
1250
|
+
// bundle it is serving, which is the thing that goes quietly wrong and
|
|
1251
|
+
// takes the account's reachability with it.
|
|
1252
|
+
this._ensureServerPreKeys('login')
|
|
1253
|
+
.then(() => this._verifyServerKeyBundle('login'));
|
|
1238
1254
|
|
|
1239
1255
|
// Same telemetry ping the web client sends on every completed login.
|
|
1240
1256
|
this._sendUnifiedSession();
|
|
@@ -1500,9 +1516,23 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1500
1516
|
}
|
|
1501
1517
|
|
|
1502
1518
|
if (collections.size === 0) return;
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1519
|
+
|
|
1520
|
+
// The bit has to be cleared whatever came of the sync. A session that
|
|
1521
|
+
// cannot sync at all — no key has ever been shared with it, which is every
|
|
1522
|
+
// session that registered its own number — would otherwise leave it set
|
|
1523
|
+
// forever and be told about the same change on every single connection.
|
|
1524
|
+
const clearAll = () => {
|
|
1525
|
+
for (const type of dirtyTypes) {
|
|
1526
|
+
if (COLLECTION_MAP[type]) this._cleanDirtyBits(type);
|
|
1527
|
+
}
|
|
1528
|
+
};
|
|
1529
|
+
|
|
1530
|
+
this.syncAppState([...collections])
|
|
1531
|
+
.then(clearAll)
|
|
1532
|
+
.catch(err => {
|
|
1533
|
+
_whaDbg('[DBG] APP_STATE sync failed: ' + (err && err.message));
|
|
1534
|
+
clearAll();
|
|
1535
|
+
});
|
|
1506
1536
|
}
|
|
1507
1537
|
|
|
1508
1538
|
/**
|
|
@@ -1920,11 +1950,20 @@ class WhalibmobClient extends EventEmitter {
|
|
|
1920
1950
|
}, [new BinaryNode('ping', {}, null)]));
|
|
1921
1951
|
}
|
|
1922
1952
|
}, KEEPALIVE_INTERVAL);
|
|
1953
|
+
|
|
1954
|
+
// The server is supposed to say when it is running out of one-time keys,
|
|
1955
|
+
// and mostly does. This is for when it does not: a session that stays up
|
|
1956
|
+
// long enough can be drained without a word, and the first sign of it is
|
|
1957
|
+
// that nobody can reach the account any more.
|
|
1958
|
+
this._preKeyTimer = setInterval(() => {
|
|
1959
|
+
if (this._socket && this._connected) this._ensureServerPreKeys('periodic');
|
|
1960
|
+
}, PRE_KEY_CHECK_INTERVAL);
|
|
1923
1961
|
}
|
|
1924
1962
|
|
|
1925
1963
|
_stopTimers() {
|
|
1926
|
-
if (this._pingTimer)
|
|
1927
|
-
if (this._keepTimer)
|
|
1964
|
+
if (this._pingTimer) { clearInterval(this._pingTimer); this._pingTimer = null; }
|
|
1965
|
+
if (this._keepTimer) { clearInterval(this._keepTimer); this._keepTimer = null; }
|
|
1966
|
+
if (this._preKeyTimer) { clearInterval(this._preKeyTimer); this._preKeyTimer = null; }
|
|
1928
1967
|
}
|
|
1929
1968
|
|
|
1930
1969
|
// ─── Node dispatch ────────────────────────────────────────────────────────
|
|
@@ -3005,15 +3044,28 @@ class WhalibmobClient extends EventEmitter {
|
|
|
3005
3044
|
// whole dispatch down with it, so every notification behind it in the
|
|
3006
3045
|
// offline queue was dropped too.
|
|
3007
3046
|
const fromJid = String(attrs.from || '');
|
|
3008
|
-
const
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3047
|
+
const fromUser = fromJid.split('@')[0].split(':')[0];
|
|
3048
|
+
// The user segment of a LID is not a phone number, and usync only takes
|
|
3049
|
+
// phone numbers — asking it about a LID comes back "invalid" and the
|
|
3050
|
+
// device list is never actually refreshed. Translate first when we know
|
|
3051
|
+
// the mapping, and when we do not, invalidate the cache and stop there
|
|
3052
|
+
// rather than spending an IQ on a question that cannot be answered.
|
|
3053
|
+
const isLid = fromJid.endsWith('@lid');
|
|
3054
|
+
const mappedPn = isLid && this._lidToPn ? this._lidToPn.get(fromUser) : null;
|
|
3055
|
+
const fromPhone = isLid ? mappedPn : fromUser;
|
|
3056
|
+
if (fromUser && this._devMgr) {
|
|
3057
|
+
this._devMgr._dcDel(isLid ? [fromUser, 'lid:' + fromUser] : [fromUser]);
|
|
3058
|
+
if (mappedPn) this._devMgr._dcDel([mappedPn]);
|
|
3059
|
+
_whaDbg('[DBG] NOTIF_DEVICES invalidated cache for ' + fromUser +
|
|
3060
|
+
(isLid ? (mappedPn ? ' (lid → ' + mappedPn + ')' : ' (lid, no phone known)') : ''));
|
|
3012
3061
|
// Also process any inline <devices> list the server included
|
|
3013
3062
|
const devicesNode = findChildDeep(node, 'devices');
|
|
3014
3063
|
if (devicesNode) this._processDeviceUpdate(devicesNode);
|
|
3015
|
-
// Background re-usync so the cache is warm before the next send
|
|
3016
|
-
|
|
3064
|
+
// Background re-usync so the cache is warm before the next send. Only
|
|
3065
|
+
// when there is a phone number to ask about — the cache is already
|
|
3066
|
+
// cleared, so a LID with no known number simply gets looked up the next
|
|
3067
|
+
// time something sends to it.
|
|
3068
|
+
if (this._signal && fromPhone) {
|
|
3017
3069
|
setImmediate(() => {
|
|
3018
3070
|
if (!this._connected || !this._devMgr) return;
|
|
3019
3071
|
this._devMgr.bulkEnsureSessions([fromPhone], this._signal, false)
|
|
@@ -3320,15 +3372,187 @@ class WhalibmobClient extends EventEmitter {
|
|
|
3320
3372
|
]));
|
|
3321
3373
|
}
|
|
3322
3374
|
|
|
3323
|
-
|
|
3375
|
+
// How many one-time keys the server is still holding for us.
|
|
3376
|
+
//
|
|
3377
|
+
// This is the number that matters and the one we could not see. The local
|
|
3378
|
+
// store only loses a key when a pkmsg using it is actually decrypted, while
|
|
3379
|
+
// the server hands one out to everybody who asks for a bundle — including the
|
|
3380
|
+
// ones who then never write. So the two drift apart, and the local count can
|
|
3381
|
+
// sit at its full 800 while the server has none left. With none left nobody
|
|
3382
|
+
// can open a session with this account at all: the messages are not delayed,
|
|
3383
|
+
// they are never sent, which is why nothing arrives and nothing is queued.
|
|
3384
|
+
//
|
|
3385
|
+
// The two client families ask differently, so each is asked its own way. A
|
|
3386
|
+
// companion asks for a count and is told a number. A primary asks for the
|
|
3387
|
+
// digest and is given the ids it holds, which is the same answer with the
|
|
3388
|
+
// arithmetic left to us.
|
|
3389
|
+
async _queryServerPreKeyCount() {
|
|
3390
|
+
if (!this._socket || !this._connected) return null;
|
|
3391
|
+
|
|
3392
|
+
const web = this._mode === 'web';
|
|
3393
|
+
const resp = await this._sendIq(new BinaryNode('iq', {
|
|
3394
|
+
id: this._genMsgId(),
|
|
3395
|
+
to: 's.whatsapp.net',
|
|
3396
|
+
type: 'get',
|
|
3397
|
+
xmlns: 'encrypt'
|
|
3398
|
+
}, [new BinaryNode(web ? 'count' : 'digest', {}, null)]));
|
|
3399
|
+
|
|
3400
|
+
if (!resp) return null; // timed out
|
|
3401
|
+
if (resp.attrs && resp.attrs.type === 'error') return null;
|
|
3402
|
+
|
|
3403
|
+
if (web) {
|
|
3404
|
+
const countNode = findChild(resp, 'count');
|
|
3405
|
+
const value = countNode && countNode.attrs && countNode.attrs.value;
|
|
3406
|
+
const n = value === undefined ? NaN : Number(value);
|
|
3407
|
+
return Number.isFinite(n) ? n : null;
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3410
|
+
// <digest><list><...one child per key we still have there...></list></digest>
|
|
3411
|
+
const digest = findChild(resp, 'digest');
|
|
3412
|
+
const list = digest && findChild(digest, 'list');
|
|
3413
|
+
if (!list) return null;
|
|
3414
|
+
return Array.isArray(list.content) ? list.content.length : 0;
|
|
3415
|
+
}
|
|
3416
|
+
|
|
3417
|
+
// What the server believes this account's key bundle is.
|
|
3418
|
+
//
|
|
3419
|
+
// The digest is the only way to see it. Everything else here is a guess about
|
|
3420
|
+
// what was uploaded once and assumed to have stuck — and when the server's
|
|
3421
|
+
// idea of the bundle and ours part company, nobody can open a session with
|
|
3422
|
+
// this account and there is no error anywhere to say so. It is the state that
|
|
3423
|
+
// registering the number again clears, which is why registering again is what
|
|
3424
|
+
// has been fixing it.
|
|
3425
|
+
async _queryKeyDigest() {
|
|
3426
|
+
if (!this._socket || !this._connected) return null;
|
|
3427
|
+
|
|
3428
|
+
const resp = await this._sendIq(new BinaryNode('iq', {
|
|
3429
|
+
id: this._genMsgId(),
|
|
3430
|
+
to: 's.whatsapp.net',
|
|
3431
|
+
type: 'get',
|
|
3432
|
+
xmlns: 'encrypt'
|
|
3433
|
+
}, [new BinaryNode('digest', {}, null)]));
|
|
3434
|
+
|
|
3435
|
+
if (!resp) return null;
|
|
3436
|
+
if (resp.attrs && resp.attrs.type === 'error') return null;
|
|
3437
|
+
|
|
3438
|
+
const digest = findChild(resp, 'digest');
|
|
3439
|
+
if (!digest) return { missing: true };
|
|
3440
|
+
|
|
3441
|
+
const bytesOf = (parent, tag) => {
|
|
3442
|
+
const n = parent && findChild(parent, tag);
|
|
3443
|
+
if (!n) return null;
|
|
3444
|
+
if (Buffer.isBuffer(n.content)) return n.content;
|
|
3445
|
+
return n.content ? Buffer.from(n.content) : null;
|
|
3446
|
+
};
|
|
3447
|
+
|
|
3448
|
+
const skey = findChild(digest, 'skey');
|
|
3449
|
+
const list = findChild(digest, 'list');
|
|
3450
|
+
return {
|
|
3451
|
+
missing: false,
|
|
3452
|
+
identity: bytesOf(digest, 'identity'),
|
|
3453
|
+
skeyId: bytesOf(skey, 'id'),
|
|
3454
|
+
skeyValue: bytesOf(skey, 'value'),
|
|
3455
|
+
preKeyCount: list && Array.isArray(list.content) ? list.content.length : 0
|
|
3456
|
+
};
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3459
|
+
// Compare that bundle against the one we hold, and re-upload when they differ.
|
|
3460
|
+
//
|
|
3461
|
+
// Only values the server states outright are compared — the identity key and
|
|
3462
|
+
// the signed pre-key it is serving — so nothing here depends on guessing how
|
|
3463
|
+
// it hashes them. A mismatch means the bundle other people are handed is not
|
|
3464
|
+
// the one this session can answer for, and no amount of waiting fixes that.
|
|
3465
|
+
async _verifyServerKeyBundle(reason) {
|
|
3324
3466
|
if (!this._signal) return;
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3467
|
+
try {
|
|
3468
|
+
const digest = await this._queryKeyDigest();
|
|
3469
|
+
if (!digest) return; // no answer; nothing to conclude
|
|
3470
|
+
|
|
3471
|
+
if (digest.missing) {
|
|
3472
|
+
_whaDbg('[DBG] KEYBUNDLE server returned no digest (' + reason + ') — re-uploading');
|
|
3473
|
+
this._replenishLocalPreKeys();
|
|
3474
|
+
this._uploadPreKeys();
|
|
3475
|
+
return;
|
|
3476
|
+
}
|
|
3477
|
+
|
|
3478
|
+
const bare = (b) => (b && b.length === 33 && b[0] === 0x05) ? b.slice(1) : b;
|
|
3479
|
+
const ourIdent = bare(this._signal.getIdentityKey());
|
|
3480
|
+
const ourSpk = this._signal.getSignedPreKeyForUpload();
|
|
3481
|
+
const ourSpkPub = ourSpk && bare(ourSpk.keyPair.pubKey);
|
|
3482
|
+
|
|
3483
|
+
const mismatches = [];
|
|
3484
|
+
if (digest.identity && ourIdent &&
|
|
3485
|
+
Buffer.compare(digest.identity, ourIdent) !== 0) {
|
|
3486
|
+
mismatches.push('identity key');
|
|
3487
|
+
}
|
|
3488
|
+
if (digest.skeyValue && ourSpkPub &&
|
|
3489
|
+
Buffer.compare(digest.skeyValue, ourSpkPub) !== 0) {
|
|
3490
|
+
mismatches.push('signed pre-key');
|
|
3491
|
+
}
|
|
3492
|
+
if (digest.skeyId && ourSpk) {
|
|
3493
|
+
let served = 0;
|
|
3494
|
+
for (const b of digest.skeyId) served = (served << 8) | b;
|
|
3495
|
+
if (served !== ourSpk.keyId) mismatches.push('signed pre-key id (' + served +
|
|
3496
|
+
' vs ' + ourSpk.keyId + ')');
|
|
3497
|
+
}
|
|
3498
|
+
|
|
3499
|
+
if (!mismatches.length) {
|
|
3500
|
+
_whaDbg('[DBG] KEYBUNDLE matches the server (' + reason + '), ' +
|
|
3501
|
+
digest.preKeyCount + ' one-time key(s) there');
|
|
3502
|
+
return;
|
|
3503
|
+
}
|
|
3504
|
+
|
|
3505
|
+
_whaDbg('[DBG] KEYBUNDLE server disagrees on ' + mismatches.join(' and ') +
|
|
3506
|
+
' (' + reason + ') — re-uploading');
|
|
3507
|
+
this._replenishLocalPreKeys();
|
|
3508
|
+
this._uploadPreKeys();
|
|
3509
|
+
} catch (err) {
|
|
3510
|
+
_whaDbg('[DBG] KEYBUNDLE check failed (' + reason + '): ' + (err && err.message));
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
|
|
3514
|
+
// Top the server back up when it is running out. Never throws: a session that
|
|
3515
|
+
// cannot ask is left exactly as it was rather than losing its connection over
|
|
3516
|
+
// a bookkeeping query.
|
|
3517
|
+
async _ensureServerPreKeys(reason) {
|
|
3518
|
+
if (!this._signal) return;
|
|
3519
|
+
try {
|
|
3520
|
+
const serverCount = await this._queryServerPreKeyCount();
|
|
3521
|
+
if (serverCount === null) {
|
|
3522
|
+
// No usable answer. Fall back to the old behaviour — an upload we may
|
|
3523
|
+
// not have needed costs nothing next to an account nobody can reach.
|
|
3524
|
+
_whaDbg('[DBG] PREKEY server count unavailable (' + reason + ') — uploading anyway');
|
|
3525
|
+
this._replenishLocalPreKeys();
|
|
3526
|
+
this._uploadPreKeys();
|
|
3527
|
+
return;
|
|
3528
|
+
}
|
|
3529
|
+
|
|
3530
|
+
_whaDbg('[DBG] PREKEY server holds ' + serverCount + ' (' + reason + ')');
|
|
3531
|
+
if (serverCount > PRE_KEY_SERVER_MIN) return;
|
|
3532
|
+
|
|
3533
|
+
_whaDbg('[DBG] PREKEY server low (' + serverCount + ' <= ' + PRE_KEY_SERVER_MIN +
|
|
3534
|
+
') — uploading');
|
|
3535
|
+
this._replenishLocalPreKeys();
|
|
3536
|
+
this._uploadPreKeys();
|
|
3537
|
+
} catch (err) {
|
|
3538
|
+
_whaDbg('[DBG] PREKEY check failed (' + reason + '): ' + (err && err.message));
|
|
3329
3539
|
}
|
|
3330
3540
|
}
|
|
3331
3541
|
|
|
3542
|
+
// Make sure there is something to upload before uploading.
|
|
3543
|
+
_replenishLocalPreKeys() {
|
|
3544
|
+
if (!this._signal) return;
|
|
3545
|
+
if (this._signal.preKeyCount() < 20) this._signal.replenishPreKeys();
|
|
3546
|
+
}
|
|
3547
|
+
|
|
3548
|
+
_checkAndReplenishPreKeys() {
|
|
3549
|
+
if (!this._signal) return;
|
|
3550
|
+
// The server said it is running low, so ask it how low and act on that
|
|
3551
|
+
// rather than on the local count, which does not move when the server
|
|
3552
|
+
// hands a key out.
|
|
3553
|
+
this._ensureServerPreKeys('server asked');
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3332
3556
|
// ─── IQ helper (send + await response) ────────────────────────────────────
|
|
3333
3557
|
|
|
3334
3558
|
_sendIq(node) {
|
package/lib/Registration.js
CHANGED
|
@@ -418,6 +418,149 @@ const COUNTRY_META = {
|
|
|
418
418
|
'971': { mcc: '424', mnc: '02', lg: 'ar', lc: 'AE' }, // UAE
|
|
419
419
|
'972': { mcc: '425', mnc: '01', lg: 'he', lc: 'IL' }, // Israel
|
|
420
420
|
'880': { mcc: '470', mnc: '01', lg: 'bn', lc: 'BD' }, // Bangladesh
|
|
421
|
+
// Filled in from the ITU mobile-network tables so a number from anywhere
|
|
422
|
+
// reports the carrier its country actually has. Without an entry a session
|
|
423
|
+
// falls back to 000/000, which is what a handset with no SIM card reports.
|
|
424
|
+
// One operational national operator per country; the point is a plausible
|
|
425
|
+
// home network, not a particular one.
|
|
426
|
+
'53': { mcc: '368', mnc: '01', lg: 'es', lc: 'CU' }, // Cuba (CUBACEL)
|
|
427
|
+
'211': { mcc: '659', mnc: '02', lg: 'en', lc: 'SS' }, // South Sudan (MTN)
|
|
428
|
+
'220': { mcc: '607', mnc: '01', lg: 'en', lc: 'GM' }, // Gambia (Gamcel)
|
|
429
|
+
'221': { mcc: '608', mnc: '01', lg: 'fr', lc: 'SN' }, // Senegal (Orange)
|
|
430
|
+
'222': { mcc: '609', mnc: '01', lg: 'ar', lc: 'MR' }, // Mauritania (Mattel)
|
|
431
|
+
'223': { mcc: '610', mnc: '01', lg: 'fr', lc: 'ML' }, // Mali (Malitel)
|
|
432
|
+
'224': { mcc: '611', mnc: '01', lg: 'fr', lc: 'GN' }, // Guinea (Orange)
|
|
433
|
+
'225': { mcc: '612', mnc: '02', lg: 'fr', lc: 'CI' }, // Ivory Coast (Moov)
|
|
434
|
+
'226': { mcc: '613', mnc: '01', lg: 'fr', lc: 'BF' }, // Burkina Faso (Telmob)
|
|
435
|
+
'227': { mcc: '614', mnc: '01', lg: 'fr', lc: 'NE' }, // Niger (SahelCom)
|
|
436
|
+
'228': { mcc: '615', mnc: '01', lg: 'fr', lc: 'TG' }, // Togo (Togo Cell)
|
|
437
|
+
'229': { mcc: '616', mnc: '01', lg: 'fr', lc: 'BJ' }, // Benin (Benin Telecoms Mob)
|
|
438
|
+
'230': { mcc: '617', mnc: '01', lg: 'en', lc: 'MU' }, // Mauritius (my.t)
|
|
439
|
+
'231': { mcc: '618', mnc: '01', lg: 'en', lc: 'LR' }, // Liberia (Lonestar Cell MTN)
|
|
440
|
+
'232': { mcc: '619', mnc: '01', lg: 'en', lc: 'SL' }, // Sierra Leone (Orange)
|
|
441
|
+
'233': { mcc: '620', mnc: '01', lg: 'en', lc: 'GH' }, // Ghana (MTN)
|
|
442
|
+
'235': { mcc: '622', mnc: '01', lg: 'ar', lc: 'TD' }, // Chad (Airtel)
|
|
443
|
+
'236': { mcc: '623', mnc: '01', lg: 'fr', lc: 'CF' }, // Central African Republic (Moov)
|
|
444
|
+
'237': { mcc: '624', mnc: '01', lg: 'en', lc: 'CM' }, // Cameroon (MTN Cameroon)
|
|
445
|
+
'238': { mcc: '625', mnc: '01', lg: 'pt', lc: 'CV' }, // Cape Verde (CVMOVEL)
|
|
446
|
+
'239': { mcc: '626', mnc: '01', lg: 'pt', lc: 'ST' }, // São Tomé and Príncipe (CSTmovel)
|
|
447
|
+
'240': { mcc: '627', mnc: '01', lg: 'fr', lc: 'GQ' }, // Equatorial Guinea (Orange GQ)
|
|
448
|
+
'241': { mcc: '628', mnc: '01', lg: 'fr', lc: 'GA' }, // Gabon (Libertis)
|
|
449
|
+
'242': { mcc: '629', mnc: '01', lg: 'fr', lc: 'CG' }, // Republic of the Congo (Airtel)
|
|
450
|
+
'243': { mcc: '630', mnc: '01', lg: 'fr', lc: 'CD' }, // DR Congo (Vodacom)
|
|
451
|
+
'244': { mcc: '631', mnc: '02', lg: 'pt', lc: 'AO' }, // Angola (UNITEL)
|
|
452
|
+
'245': { mcc: '632', mnc: '01', lg: 'pt', lc: 'GW' }, // Guinea-Bissau (Guinetel)
|
|
453
|
+
'248': { mcc: '633', mnc: '01', lg: 'en', lc: 'SC' }, // Seychelles (Cable & Wireless)
|
|
454
|
+
'249': { mcc: '634', mnc: '01', lg: 'ar', lc: 'SD' }, // Sudan (Zain SD)
|
|
455
|
+
'250': { mcc: '635', mnc: '10', lg: 'en', lc: 'RW' }, // Rwanda (MTN)
|
|
456
|
+
'251': { mcc: '636', mnc: '01', lg: 'am', lc: 'ET' }, // Ethiopia (MTN)
|
|
457
|
+
'252': { mcc: '637', mnc: '01', lg: 'ar', lc: 'SO' }, // Somalia (Telesom)
|
|
458
|
+
'253': { mcc: '638', mnc: '01', lg: 'ar', lc: 'DJ' }, // Djibouti (Evatis)
|
|
459
|
+
'257': { mcc: '642', mnc: '01', lg: 'fr', lc: 'BI' }, // Burundi (econet Leo)
|
|
460
|
+
'258': { mcc: '643', mnc: '01', lg: 'pt', lc: 'MZ' }, // Mozambique (mCel)
|
|
461
|
+
'260': { mcc: '645', mnc: '01', lg: 'en', lc: 'ZM' }, // Zambia (Airtel)
|
|
462
|
+
'261': { mcc: '646', mnc: '01', lg: 'fr', lc: 'MG' }, // Madagascar (Airtel)
|
|
463
|
+
'263': { mcc: '648', mnc: '01', lg: 'en', lc: 'ZW' }, // Zimbabwe (Net*One)
|
|
464
|
+
'264': { mcc: '649', mnc: '01', lg: 'af', lc: 'NA' }, // Namibia (MTC)
|
|
465
|
+
'265': { mcc: '650', mnc: '01', lg: 'en', lc: 'MW' }, // Malawi (TNM)
|
|
466
|
+
'266': { mcc: '651', mnc: '01', lg: 'en', lc: 'LS' }, // Lesotho (Vodacom)
|
|
467
|
+
'267': { mcc: '652', mnc: '01', lg: 'en', lc: 'BW' }, // Botswana (Mascom)
|
|
468
|
+
'268': { mcc: '653', mnc: '02', lg: 'en', lc: 'SZ' }, // Eswatini (Eswatini Mobile Li)
|
|
469
|
+
'269': { mcc: '654', mnc: '01', lg: 'ar', lc: 'KM' }, // Comoros (HURI)
|
|
470
|
+
'290': { mcc: '658', mnc: '01', lg: 'en', lc: 'SH' }, // Saint Helena, Ascension and Tristan da Cunha (Sure)
|
|
471
|
+
'291': { mcc: '657', mnc: '01', lg: 'ar', lc: 'ER' }, // Eritrea (Eritel)
|
|
472
|
+
'297': { mcc: '363', mnc: '01', lg: 'nl', lc: 'AW' }, // Aruba (SETAR)
|
|
473
|
+
'298': { mcc: '288', mnc: '01', lg: 'da', lc: 'FO' }, // Faroe Islands (Føroya Tele)
|
|
474
|
+
'299': { mcc: '290', mnc: '01', lg: 'kl', lc: 'GL' }, // Greenland (tusass)
|
|
475
|
+
'350': { mcc: '266', mnc: '01', lg: 'en', lc: 'GI' }, // Gibraltar (GibTel)
|
|
476
|
+
'352': { mcc: '270', mnc: '01', lg: 'de', lc: 'LU' }, // Luxembourg (POST)
|
|
477
|
+
'354': { mcc: '274', mnc: '01', lg: 'is', lc: 'IS' }, // Iceland (Síminn)
|
|
478
|
+
'355': { mcc: '276', mnc: '01', lg: 'sq', lc: 'AL' }, // Albania (ONE)
|
|
479
|
+
'356': { mcc: '278', mnc: '01', lg: 'en', lc: 'MT' }, // Malta (Epic)
|
|
480
|
+
'357': { mcc: '280', mnc: '01', lg: 'el', lc: 'CY' }, // Cyprus (Cytamobile-Vodafon)
|
|
481
|
+
'359': { mcc: '284', mnc: '01', lg: 'bg', lc: 'BG' }, // Bulgaria (A1 BG)
|
|
482
|
+
'370': { mcc: '246', mnc: '01', lg: 'lt', lc: 'LT' }, // Lithuania (Telia)
|
|
483
|
+
'371': { mcc: '247', mnc: '01', lg: 'lv', lc: 'LV' }, // Latvia (LMT)
|
|
484
|
+
'372': { mcc: '248', mnc: '01', lg: 'et', lc: 'EE' }, // Estonia (Telia)
|
|
485
|
+
'373': { mcc: '255', mnc: '00', lg: 'ro', lc: 'MD' }, // Moldova (IDC)
|
|
486
|
+
'374': { mcc: '283', mnc: '01', lg: 'hy', lc: 'AM' }, // Armenia (Beeline)
|
|
487
|
+
'375': { mcc: '257', mnc: '01', lg: 'be', lc: 'BY' }, // Belarus (A1)
|
|
488
|
+
'376': { mcc: '213', mnc: '03', lg: 'ca', lc: 'AD' }, // Andorra (Som, Mobiland)
|
|
489
|
+
'377': { mcc: '212', mnc: '10', lg: 'fr', lc: 'MC' }, // Monaco (Office des Telepho)
|
|
490
|
+
'378': { mcc: '292', mnc: '01', lg: 'it', lc: 'SM' }, // San Marino (PRIMA)
|
|
491
|
+
'381': { mcc: '220', mnc: '01', lg: 'sr', lc: 'RS' }, // Serbia (Yettel)
|
|
492
|
+
'382': { mcc: '297', mnc: '01', lg: 'en', lc: 'ME' }, // Montenegro (One)
|
|
493
|
+
'383': { mcc: '221', mnc: '01', lg: 'sq', lc: 'XK' }, // Kosovo (Vala)
|
|
494
|
+
'385': { mcc: '219', mnc: '01', lg: 'hr', lc: 'HR' }, // Croatia (HT HR)
|
|
495
|
+
'386': { mcc: '293', mnc: '10', lg: 'sl', lc: 'SI' }, // Slovenia (SŽ - Infrastruktur)
|
|
496
|
+
'387': { mcc: '218', mnc: '03', lg: 'bs', lc: 'BA' }, // Bosnia and Herzegovina (HT-ERONET)
|
|
497
|
+
'389': { mcc: '294', mnc: '01', lg: 'mk', lc: 'MK' }, // North Macedonia (Telekom.mk)
|
|
498
|
+
'421': { mcc: '231', mnc: '01', lg: 'sk', lc: 'SK' }, // Slovakia (Orange)
|
|
499
|
+
'423': { mcc: '295', mnc: '01', lg: 'de', lc: 'LI' }, // Liechtenstein (Swisscom)
|
|
500
|
+
'500': { mcc: '750', mnc: '001', lg: 'en', lc: 'FK' }, // Falkland Islands (Sure)
|
|
501
|
+
'501': { mcc: '702', mnc: '67', lg: 'en', lc: 'BZ' }, // Belize (DigiCell)
|
|
502
|
+
'502': { mcc: '704', mnc: '?', lg: 'es', lc: 'GT' }, // Guatemala (RED/INTELFON)
|
|
503
|
+
'503': { mcc: '706', mnc: '01', lg: 'es', lc: 'SV' }, // El Salvador (Claro)
|
|
504
|
+
'504': { mcc: '708', mnc: '001', lg: 'es', lc: 'HN' }, // Honduras (Claro)
|
|
505
|
+
'505': { mcc: '710', mnc: '21', lg: 'es', lc: 'NI' }, // Nicaragua (Claro)
|
|
506
|
+
'506': { mcc: '712', mnc: '01', lg: 'es', lc: 'CR' }, // Costa Rica (Kölbi ICE)
|
|
507
|
+
'507': { mcc: '714', mnc: '01', lg: 'es', lc: 'PA' }, // Panama (Cable & Wireless)
|
|
508
|
+
'508': { mcc: '308', mnc: '01', lg: 'fr', lc: 'PM' }, // Saint Pierre and Miquelon (Ameris)
|
|
509
|
+
'509': { mcc: '372', mnc: '02', lg: 'fr', lc: 'HT' }, // Haiti (Digicel)
|
|
510
|
+
'591': { mcc: '736', mnc: '01', lg: 'ay', lc: 'BO' }, // Bolivia (Viva)
|
|
511
|
+
'592': { mcc: '738', mnc: '00', lg: 'en', lc: 'GY' }, // Guyana (E-Networks)
|
|
512
|
+
'593': { mcc: '740', mnc: '00', lg: 'es', lc: 'EC' }, // Ecuador (Movistar)
|
|
513
|
+
'594': { mcc: '340', mnc: '01', lg: 'fr', lc: 'GF' }, // French Guiana (Orange)
|
|
514
|
+
'595': { mcc: '744', mnc: '01', lg: 'gn', lc: 'PY' }, // Paraguay (VOX)
|
|
515
|
+
'597': { mcc: '746', mnc: '02', lg: 'nl', lc: 'SR' }, // Suriname (Telesur)
|
|
516
|
+
'598': { mcc: '748', mnc: '01', lg: 'es', lc: 'UY' }, // Uruguay (Antel)
|
|
517
|
+
'670': { mcc: '514', mnc: '01', lg: 'pt', lc: 'TL' }, // Timor-Leste (Telkomcel)
|
|
518
|
+
'672': { mcc: '505', mnc: '10', lg: 'en', lc: 'NF' }, // Norfolk Island (Norfolk Telecom)
|
|
519
|
+
'673': { mcc: '528', mnc: '02', lg: 'ms', lc: 'BN' }, // Brunei (PCSB)
|
|
520
|
+
'674': { mcc: '536', mnc: '02', lg: 'en', lc: 'NR' }, // Nauru (Digicel)
|
|
521
|
+
'675': { mcc: '537', mnc: '01', lg: 'en', lc: 'PG' }, // Papua New Guinea (bmobile)
|
|
522
|
+
'676': { mcc: '539', mnc: '01', lg: 'en', lc: 'TO' }, // Tonga (U-Call)
|
|
523
|
+
'677': { mcc: '540', mnc: '01', lg: 'en', lc: 'SB' }, // Solomon Islands (BREEZE)
|
|
524
|
+
'678': { mcc: '541', mnc: '00', lg: 'bi', lc: 'VU' }, // Vanuatu (AIL)
|
|
525
|
+
'679': { mcc: '542', mnc: '01', lg: 'en', lc: 'FJ' }, // Fiji (Vodafone)
|
|
526
|
+
'680': { mcc: '552', mnc: '01', lg: 'en', lc: 'PW' }, // Palau (PNCC)
|
|
527
|
+
'681': { mcc: '543', mnc: '01', lg: 'fr', lc: 'WF' }, // Wallis and Futuna (Manuia)
|
|
528
|
+
'682': { mcc: '548', mnc: '01', lg: 'en', lc: 'CK' }, // Cook Islands (Vodafone)
|
|
529
|
+
'683': { mcc: '555', mnc: '01', lg: 'en', lc: 'NU' }, // Niue (Telecom Niue)
|
|
530
|
+
'685': { mcc: '549', mnc: '01', lg: 'en', lc: 'WS' }, // Samoa (Digicel)
|
|
531
|
+
'686': { mcc: '545', mnc: '01', lg: 'en', lc: 'KI' }, // Kiribati (Kiribati - ATH)
|
|
532
|
+
'687': { mcc: '546', mnc: '01', lg: 'fr', lc: 'NC' }, // New Caledonia (Mobilis)
|
|
533
|
+
'688': { mcc: '553', mnc: '01', lg: 'en', lc: 'TV' }, // Tuvalu (TTC)
|
|
534
|
+
'689': { mcc: '547', mnc: '05', lg: 'fr', lc: 'PF' }, // French Polynesia (Ora)
|
|
535
|
+
'690': { mcc: '554', mnc: '01', lg: 'en', lc: 'TK' }, // Tokelau (Teletok)
|
|
536
|
+
'691': { mcc: '550', mnc: '01', lg: 'en', lc: 'FM' }, // Micronesia (FSMTC)
|
|
537
|
+
'692': { mcc: '551', mnc: '01', lg: 'en', lc: 'MH' }, // Marshall Islands (Marshall Islands N)
|
|
538
|
+
'850': { mcc: '467', mnc: '05', lg: 'ko', lc: 'KP' }, // North Korea (Koryolink)
|
|
539
|
+
'852': { mcc: '454', mnc: '00', lg: 'en', lc: 'HK' }, // Hong Kong (1O1O / One2Free / )
|
|
540
|
+
'853': { mcc: '455', mnc: '00', lg: 'pt', lc: 'MO' }, // Macau (SmarTone)
|
|
541
|
+
'855': { mcc: '456', mnc: '01', lg: 'km', lc: 'KH' }, // Cambodia (Cellcard)
|
|
542
|
+
'856': { mcc: '457', mnc: '01', lg: 'lo', lc: 'LA' }, // Laos (LaoTel)
|
|
543
|
+
'886': { mcc: '466', mnc: '01', lg: 'zh', lc: 'TW' }, // Taiwan (FarEasTone)
|
|
544
|
+
'960': { mcc: '472', mnc: '01', lg: 'dv', lc: 'MV' }, // Maldives (Dhiraagu)
|
|
545
|
+
'961': { mcc: '415', mnc: '01', lg: 'ar', lc: 'LB' }, // Lebanon (Alfa)
|
|
546
|
+
'962': { mcc: '416', mnc: '01', lg: 'ar', lc: 'JO' }, // Jordan (zain JO)
|
|
547
|
+
'963': { mcc: '417', mnc: '01', lg: 'ar', lc: 'SY' }, // Syria (Syriatel)
|
|
548
|
+
'964': { mcc: '418', mnc: '00', lg: 'ar', lc: 'IQ' }, // Iraq (Asia Cell)
|
|
549
|
+
'965': { mcc: '419', mnc: '02', lg: 'ar', lc: 'KW' }, // Kuwait (zain KW)
|
|
550
|
+
'967': { mcc: '421', mnc: '01', lg: 'ar', lc: 'YE' }, // Yemen (SabaFon)
|
|
551
|
+
'968': { mcc: '422', mnc: '02', lg: 'ar', lc: 'OM' }, // Oman (Omantel)
|
|
552
|
+
'970': { mcc: '425', mnc: '05', lg: 'ar', lc: 'PS' }, // Palestine (Jawwal)
|
|
553
|
+
'973': { mcc: '426', mnc: '01', lg: 'ar', lc: 'BH' }, // Bahrain (Batelco)
|
|
554
|
+
'974': { mcc: '427', mnc: '01', lg: 'ar', lc: 'QA' }, // Qatar (Ooredoo)
|
|
555
|
+
'975': { mcc: '402', mnc: '11', lg: 'dz', lc: 'BT' }, // Bhutan (B-Mobile)
|
|
556
|
+
'976': { mcc: '428', mnc: '88', lg: 'mn', lc: 'MN' }, // Mongolia (Unitel)
|
|
557
|
+
'977': { mcc: '429', mnc: '01', lg: 'ne', lc: 'NP' }, // Nepal (Namaste / NT Mobil)
|
|
558
|
+
'992': { mcc: '436', mnc: '01', lg: 'ru', lc: 'TJ' }, // Tajikistan (Tcell)
|
|
559
|
+
'993': { mcc: '438', mnc: '02', lg: 'ru', lc: 'TM' }, // Turkmenistan (TM-Cell)
|
|
560
|
+
'994': { mcc: '400', mnc: '01', lg: 'az', lc: 'AZ' }, // Azerbaijan (Azercell)
|
|
561
|
+
'995': { mcc: '282', mnc: '01', lg: 'ka', lc: 'GE' }, // Georgia (Geocell)
|
|
562
|
+
'996': { mcc: '437', mnc: '01', lg: 'ky', lc: 'KG' }, // Kyrgyzstan (Beeline)
|
|
563
|
+
'998': { mcc: '434', mnc: '03', lg: 'ru', lc: 'UZ' }, // Uzbekistan (UzMobile)
|
|
421
564
|
};
|
|
422
565
|
|
|
423
566
|
function getCountryMeta(cc) {
|
|
@@ -431,17 +574,20 @@ function getCountryMeta(cc) {
|
|
|
431
574
|
// (Java long conversion removes them automatically)
|
|
432
575
|
|
|
433
576
|
const THREE_DIGIT_CCS = new Set([
|
|
434
|
-
'
|
|
435
|
-
'
|
|
436
|
-
'
|
|
437
|
-
'
|
|
438
|
-
'
|
|
439
|
-
'
|
|
440
|
-
'
|
|
441
|
-
'
|
|
442
|
-
'
|
|
443
|
-
'
|
|
444
|
-
'
|
|
577
|
+
'211','212','213','216','218','220','221','222','223','224','225','226',
|
|
578
|
+
'227','228','229','230','231','232','233','234','235','236','237','238',
|
|
579
|
+
'239','240','241','242','243','244','245','246','247','248','249','250',
|
|
580
|
+
'251','252','253','254','255','256','257','258','260','261','262','263',
|
|
581
|
+
'264','265','266','267','268','269','290','291','297','298','299','350',
|
|
582
|
+
'351','352','353','354','355','356','357','358','359','370','371','372',
|
|
583
|
+
'373','374','375','376','377','378','380','381','382','383','385','386',
|
|
584
|
+
'387','389','420','421','423','500','501','502','503','504','505','506',
|
|
585
|
+
'507','508','509','590','591','592','593','594','595','596','597','598',
|
|
586
|
+
'599','670','672','673','674','675','676','677','678','679','680','681',
|
|
587
|
+
'682','683','685','686','687','688','689','690','691','692','850','852',
|
|
588
|
+
'853','855','856','880','886','960','961','962','963','964','965','966',
|
|
589
|
+
'967','968','970','971','972','973','974','975','976','977','992','993',
|
|
590
|
+
'994','995','996','998'
|
|
445
591
|
]);
|
|
446
592
|
|
|
447
593
|
const TWO_DIGIT_CCS = new Set([
|
|
@@ -451,11 +597,10 @@ const TWO_DIGIT_CCS = new Set([
|
|
|
451
597
|
'95','98'
|
|
452
598
|
]);
|
|
453
599
|
|
|
454
|
-
//
|
|
455
|
-
//
|
|
456
|
-
//
|
|
457
|
-
//
|
|
458
|
-
// country table itself gets the last word.
|
|
600
|
+
// The country table only covers the places we carry carrier metadata for, which
|
|
601
|
+
// is far fewer than the places that can register. Anything it names still has to
|
|
602
|
+
// be splittable, so it is folded in — a no-op while the lists above are complete,
|
|
603
|
+
// and a backstop if a code is ever added to the table and not to them.
|
|
459
604
|
for (const cc of Object.keys(COUNTRY_META)) {
|
|
460
605
|
if (cc.length === 3) THREE_DIGIT_CCS.add(cc);
|
|
461
606
|
else if (cc.length === 2) TWO_DIGIT_CCS.add(cc);
|