codex-multi-auth 0.1.8 → 0.1.9
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 +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +36 -63
- package/dist/index.js.map +1 -1
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/accounts.js +29 -10
- package/dist/lib/accounts.js.map +1 -1
- package/dist/lib/codex-manager.d.ts.map +1 -1
- package/dist/lib/codex-manager.js +238 -209
- package/dist/lib/codex-manager.js.map +1 -1
- package/dist/lib/entitlement-cache.d.ts +8 -5
- package/dist/lib/entitlement-cache.d.ts.map +1 -1
- package/dist/lib/entitlement-cache.js +24 -9
- package/dist/lib/entitlement-cache.js.map +1 -1
- package/dist/lib/oc-chatgpt-import-adapter.d.ts.map +1 -1
- package/dist/lib/oc-chatgpt-import-adapter.js +49 -33
- package/dist/lib/oc-chatgpt-import-adapter.js.map +1 -1
- package/dist/lib/storage.d.ts +25 -13
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.js +303 -143
- package/dist/lib/storage.js.map +1 -1
- package/package.json +132 -132
|
@@ -14,7 +14,7 @@ import { MODEL_FAMILIES } from "./prompts/codex.js";
|
|
|
14
14
|
import { fetchCodexQuotaSnapshot, formatQuotaSnapshotLine, } from "./quota-probe.js";
|
|
15
15
|
import { queuedRefresh } from "./refresh-queue.js";
|
|
16
16
|
import { loadQuotaCache, saveQuotaCache, } from "./quota-cache.js";
|
|
17
|
-
import { clearAccounts, getStoragePath, loadFlaggedAccounts, loadAccounts, saveFlaggedAccounts, saveAccounts, setStoragePath, } from "./storage.js";
|
|
17
|
+
import { clearAccounts, findMatchingAccountIndex, getStoragePath, loadFlaggedAccounts, loadAccounts, saveFlaggedAccounts, saveAccounts, setStoragePath, withAccountAndFlaggedStorageTransaction, withAccountStorageTransaction, } from "./storage.js";
|
|
18
18
|
import { getCodexCliAuthPath, getCodexCliConfigPath, loadCodexCliState, } from "./codex-cli/state.js";
|
|
19
19
|
import { setCodexCliActiveSelection } from "./codex-cli/writer.js";
|
|
20
20
|
import { ANSI } from "./ui/ansi.js";
|
|
@@ -322,6 +322,26 @@ function normalizeQuotaEmail(email) {
|
|
|
322
322
|
const normalized = sanitizeEmail(email);
|
|
323
323
|
return normalized && normalized.length > 0 ? normalized : null;
|
|
324
324
|
}
|
|
325
|
+
function normalizeQuotaAccountId(accountId) {
|
|
326
|
+
if (typeof accountId !== "string")
|
|
327
|
+
return null;
|
|
328
|
+
const trimmed = accountId.trim();
|
|
329
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
330
|
+
}
|
|
331
|
+
function hasUniqueQuotaAccountId(accounts, account) {
|
|
332
|
+
const accountId = normalizeQuotaAccountId(account.accountId);
|
|
333
|
+
if (!accountId)
|
|
334
|
+
return false;
|
|
335
|
+
let matchCount = 0;
|
|
336
|
+
for (const candidate of accounts) {
|
|
337
|
+
if (normalizeQuotaAccountId(candidate.accountId) !== accountId)
|
|
338
|
+
continue;
|
|
339
|
+
matchCount += 1;
|
|
340
|
+
if (matchCount > 1)
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
return matchCount === 1;
|
|
344
|
+
}
|
|
325
345
|
function quotaCacheEntryToSnapshot(entry) {
|
|
326
346
|
return {
|
|
327
347
|
status: entry.status,
|
|
@@ -389,17 +409,20 @@ function formatAccountQuotaSummary(entry) {
|
|
|
389
409
|
}
|
|
390
410
|
return formatQuotaSnapshotLine(quotaCacheEntryToSnapshot(entry));
|
|
391
411
|
}
|
|
392
|
-
function getQuotaCacheEntryForAccount(cache, account) {
|
|
393
|
-
if (account.accountId && cache.byAccountId[account.accountId]) {
|
|
394
|
-
return cache.byAccountId[account.accountId] ?? null;
|
|
395
|
-
}
|
|
412
|
+
function getQuotaCacheEntryForAccount(cache, account, accounts) {
|
|
396
413
|
const email = normalizeQuotaEmail(account.email);
|
|
397
414
|
if (email && cache.byEmail[email]) {
|
|
398
415
|
return cache.byEmail[email] ?? null;
|
|
399
416
|
}
|
|
417
|
+
const accountId = normalizeQuotaAccountId(account.accountId);
|
|
418
|
+
if (accountId &&
|
|
419
|
+
hasUniqueQuotaAccountId(accounts, account) &&
|
|
420
|
+
cache.byAccountId[accountId]) {
|
|
421
|
+
return cache.byAccountId[accountId] ?? null;
|
|
422
|
+
}
|
|
400
423
|
return null;
|
|
401
424
|
}
|
|
402
|
-
function updateQuotaCacheForAccount(cache, account, snapshot) {
|
|
425
|
+
function updateQuotaCacheForAccount(cache, account, snapshot, accounts) {
|
|
403
426
|
const nextEntry = {
|
|
404
427
|
updatedAt: Date.now(),
|
|
405
428
|
status: snapshot.status,
|
|
@@ -417,25 +440,27 @@ function updateQuotaCacheForAccount(cache, account, snapshot) {
|
|
|
417
440
|
},
|
|
418
441
|
};
|
|
419
442
|
let changed = false;
|
|
420
|
-
if (account.accountId) {
|
|
421
|
-
cache.byAccountId[account.accountId] = nextEntry;
|
|
422
|
-
changed = true;
|
|
423
|
-
}
|
|
424
443
|
const email = normalizeQuotaEmail(account.email);
|
|
425
444
|
if (email) {
|
|
426
445
|
cache.byEmail[email] = nextEntry;
|
|
427
446
|
changed = true;
|
|
447
|
+
return changed;
|
|
448
|
+
}
|
|
449
|
+
const accountId = normalizeQuotaAccountId(account.accountId);
|
|
450
|
+
if (accountId && hasUniqueQuotaAccountId(accounts, account)) {
|
|
451
|
+
cache.byAccountId[accountId] = nextEntry;
|
|
452
|
+
changed = true;
|
|
428
453
|
}
|
|
429
454
|
return changed;
|
|
430
455
|
}
|
|
431
456
|
const DEFAULT_MENU_QUOTA_REFRESH_TTL_MS = 5 * 60_000;
|
|
432
457
|
const MENU_QUOTA_REFRESH_MODEL = "gpt-5-codex";
|
|
433
|
-
function resolveMenuQuotaProbeInput(account, cache, maxAgeMs, now) {
|
|
458
|
+
function resolveMenuQuotaProbeInput(account, cache, maxAgeMs, now, accounts) {
|
|
434
459
|
if (account.enabled === false)
|
|
435
460
|
return null;
|
|
436
461
|
if (!hasUsableAccessToken(account, now))
|
|
437
462
|
return null;
|
|
438
|
-
const existing = getQuotaCacheEntryForAccount(cache, account);
|
|
463
|
+
const existing = getQuotaCacheEntryForAccount(cache, account, accounts);
|
|
439
464
|
if (existing &&
|
|
440
465
|
typeof existing.updatedAt === "number" &&
|
|
441
466
|
Number.isFinite(existing.updatedAt) &&
|
|
@@ -453,7 +478,7 @@ function resolveMenuQuotaProbeInput(account, cache, maxAgeMs, now) {
|
|
|
453
478
|
function collectMenuQuotaRefreshTargets(storage, cache, maxAgeMs, now = Date.now()) {
|
|
454
479
|
const targets = [];
|
|
455
480
|
for (const account of storage.accounts) {
|
|
456
|
-
const probeInput = resolveMenuQuotaProbeInput(account, cache, maxAgeMs, now);
|
|
481
|
+
const probeInput = resolveMenuQuotaProbeInput(account, cache, maxAgeMs, now, storage.accounts);
|
|
457
482
|
if (!probeInput)
|
|
458
483
|
continue;
|
|
459
484
|
targets.push({
|
|
@@ -467,7 +492,7 @@ function collectMenuQuotaRefreshTargets(storage, cache, maxAgeMs, now = Date.now
|
|
|
467
492
|
function countMenuQuotaRefreshTargets(storage, cache, maxAgeMs, now = Date.now()) {
|
|
468
493
|
let count = 0;
|
|
469
494
|
for (const account of storage.accounts) {
|
|
470
|
-
if (resolveMenuQuotaProbeInput(account, cache, maxAgeMs, now)) {
|
|
495
|
+
if (resolveMenuQuotaProbeInput(account, cache, maxAgeMs, now, storage.accounts)) {
|
|
471
496
|
count += 1;
|
|
472
497
|
}
|
|
473
498
|
}
|
|
@@ -492,7 +517,9 @@ async function refreshQuotaCacheForMenu(storage, cache, maxAgeMs, onProgress) {
|
|
|
492
517
|
accessToken: target.accessToken,
|
|
493
518
|
model: MENU_QUOTA_REFRESH_MODEL,
|
|
494
519
|
});
|
|
495
|
-
changed =
|
|
520
|
+
changed =
|
|
521
|
+
updateQuotaCacheForAccount(cache, target.account, snapshot, storage.accounts) ||
|
|
522
|
+
changed;
|
|
496
523
|
}
|
|
497
524
|
catch {
|
|
498
525
|
// Keep existing cached values if probing fails.
|
|
@@ -615,7 +642,9 @@ function toExistingAccountInfo(storage, quotaCache, displaySettings) {
|
|
|
615
642
|
const activeIndex = resolveActiveIndex(storage, "codex");
|
|
616
643
|
const layoutMode = resolveMenuLayoutMode(displaySettings);
|
|
617
644
|
const baseAccounts = storage.accounts.map((account, index) => {
|
|
618
|
-
const entry = quotaCache
|
|
645
|
+
const entry = quotaCache
|
|
646
|
+
? getQuotaCacheEntryForAccount(quotaCache, account, storage.accounts)
|
|
647
|
+
: null;
|
|
619
648
|
return {
|
|
620
649
|
index,
|
|
621
650
|
sourceIndex: index,
|
|
@@ -1058,118 +1087,83 @@ async function runOAuthFlow(forceNewLogin) {
|
|
|
1058
1087
|
async function persistAccountPool(results, replaceAll) {
|
|
1059
1088
|
if (results.length === 0)
|
|
1060
1089
|
return;
|
|
1061
|
-
|
|
1062
|
-
? null
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
indexByRefreshToken.set(account.refreshToken, i);
|
|
1076
|
-
if (account.accountId)
|
|
1077
|
-
indexByAccountId.set(account.accountId, i);
|
|
1078
|
-
if (account.email)
|
|
1079
|
-
indexByEmail.set(account.email, i);
|
|
1080
|
-
}
|
|
1081
|
-
for (const result of results) {
|
|
1082
|
-
const tokenAccountId = extractAccountId(result.access);
|
|
1083
|
-
const accountId = resolveRequestAccountId(result.accountIdOverride, result.accountIdSource, tokenAccountId);
|
|
1084
|
-
const accountIdSource = accountId
|
|
1085
|
-
? (result.accountIdSource ?? (result.accountIdOverride ? "manual" : "token"))
|
|
1086
|
-
: undefined;
|
|
1087
|
-
const accountLabel = result.accountLabel;
|
|
1088
|
-
const accountEmail = sanitizeEmail(extractAccountEmail(result.access, result.idToken));
|
|
1089
|
-
const existingByEmail = accountEmail && indexByEmail.has(accountEmail)
|
|
1090
|
-
? indexByEmail.get(accountEmail)
|
|
1091
|
-
: undefined;
|
|
1092
|
-
const existingById = accountId && indexByAccountId.has(accountId)
|
|
1093
|
-
? indexByAccountId.get(accountId)
|
|
1094
|
-
: undefined;
|
|
1095
|
-
const existingByToken = indexByRefreshToken.get(result.refresh);
|
|
1096
|
-
const existingIndex = existingById ?? existingByEmail ?? existingByToken;
|
|
1097
|
-
if (existingIndex === undefined) {
|
|
1098
|
-
const newIndex = accounts.length;
|
|
1099
|
-
accounts.push({
|
|
1090
|
+
await withAccountStorageTransaction(async (loadedStorage, persist) => {
|
|
1091
|
+
const stored = replaceAll ? null : loadedStorage;
|
|
1092
|
+
const now = Date.now();
|
|
1093
|
+
const accounts = stored?.accounts ? [...stored.accounts] : [];
|
|
1094
|
+
let selectedAccountIndex = null;
|
|
1095
|
+
for (const result of results) {
|
|
1096
|
+
const tokenAccountId = extractAccountId(result.access);
|
|
1097
|
+
const accountId = resolveRequestAccountId(result.accountIdOverride, result.accountIdSource, tokenAccountId);
|
|
1098
|
+
const accountIdSource = accountId
|
|
1099
|
+
? (result.accountIdSource ?? (result.accountIdOverride ? "manual" : "token"))
|
|
1100
|
+
: undefined;
|
|
1101
|
+
const accountLabel = result.accountLabel;
|
|
1102
|
+
const accountEmail = sanitizeEmail(extractAccountEmail(result.access, result.idToken));
|
|
1103
|
+
const existingIndex = findMatchingAccountIndex(accounts, {
|
|
1100
1104
|
accountId,
|
|
1101
|
-
accountIdSource,
|
|
1102
|
-
accountLabel,
|
|
1103
1105
|
email: accountEmail,
|
|
1104
1106
|
refreshToken: result.refresh,
|
|
1107
|
+
}, {
|
|
1108
|
+
allowUniqueAccountIdFallbackWithoutEmail: true,
|
|
1109
|
+
});
|
|
1110
|
+
if (existingIndex === undefined) {
|
|
1111
|
+
const newIndex = accounts.length;
|
|
1112
|
+
accounts.push({
|
|
1113
|
+
accountId,
|
|
1114
|
+
accountIdSource,
|
|
1115
|
+
accountLabel,
|
|
1116
|
+
email: accountEmail,
|
|
1117
|
+
refreshToken: result.refresh,
|
|
1118
|
+
accessToken: result.access,
|
|
1119
|
+
expiresAt: result.expires,
|
|
1120
|
+
enabled: true,
|
|
1121
|
+
addedAt: now,
|
|
1122
|
+
lastUsed: now,
|
|
1123
|
+
});
|
|
1124
|
+
selectedAccountIndex = newIndex;
|
|
1125
|
+
continue;
|
|
1126
|
+
}
|
|
1127
|
+
const existing = accounts[existingIndex];
|
|
1128
|
+
if (!existing)
|
|
1129
|
+
continue;
|
|
1130
|
+
const nextEmail = accountEmail ?? existing.email;
|
|
1131
|
+
const nextAccountId = accountId ?? existing.accountId;
|
|
1132
|
+
const nextAccountIdSource = accountId
|
|
1133
|
+
? (accountIdSource ?? existing.accountIdSource)
|
|
1134
|
+
: existing.accountIdSource;
|
|
1135
|
+
accounts[existingIndex] = {
|
|
1136
|
+
...existing,
|
|
1137
|
+
accountId: nextAccountId,
|
|
1138
|
+
accountIdSource: nextAccountIdSource,
|
|
1139
|
+
accountLabel: accountLabel ?? existing.accountLabel,
|
|
1140
|
+
email: nextEmail,
|
|
1141
|
+
refreshToken: result.refresh,
|
|
1105
1142
|
accessToken: result.access,
|
|
1106
1143
|
expiresAt: result.expires,
|
|
1107
1144
|
enabled: true,
|
|
1108
|
-
addedAt: now,
|
|
1109
1145
|
lastUsed: now,
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
const
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
:
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
accountId: nextAccountId,
|
|
1132
|
-
accountIdSource: nextAccountIdSource,
|
|
1133
|
-
accountLabel: accountLabel ?? existing.accountLabel,
|
|
1134
|
-
email: nextEmail,
|
|
1135
|
-
refreshToken: result.refresh,
|
|
1136
|
-
accessToken: result.access,
|
|
1137
|
-
expiresAt: result.expires,
|
|
1138
|
-
enabled: true,
|
|
1139
|
-
lastUsed: now,
|
|
1140
|
-
};
|
|
1141
|
-
if (oldToken !== result.refresh) {
|
|
1142
|
-
indexByRefreshToken.delete(oldToken);
|
|
1143
|
-
indexByRefreshToken.set(result.refresh, existingIndex);
|
|
1144
|
-
}
|
|
1145
|
-
if (nextAccountId) {
|
|
1146
|
-
indexByAccountId.set(nextAccountId, existingIndex);
|
|
1147
|
-
}
|
|
1148
|
-
if (oldEmail && oldEmail !== nextEmail) {
|
|
1149
|
-
indexByEmail.delete(oldEmail);
|
|
1150
|
-
}
|
|
1151
|
-
if (nextEmail) {
|
|
1152
|
-
indexByEmail.set(nextEmail, existingIndex);
|
|
1153
|
-
}
|
|
1154
|
-
selectedAccountIndex = existingIndex;
|
|
1155
|
-
}
|
|
1156
|
-
const fallbackActiveIndex = accounts.length === 0
|
|
1157
|
-
? 0
|
|
1158
|
-
: Math.max(0, Math.min(loadedStorage?.activeIndex ?? 0, accounts.length - 1));
|
|
1159
|
-
const nextActiveIndex = accounts.length === 0
|
|
1160
|
-
? 0
|
|
1161
|
-
: selectedAccountIndex === null
|
|
1162
|
-
? fallbackActiveIndex
|
|
1163
|
-
: Math.max(0, Math.min(selectedAccountIndex, accounts.length - 1));
|
|
1164
|
-
const activeIndexByFamily = {};
|
|
1165
|
-
for (const family of MODEL_FAMILIES) {
|
|
1166
|
-
activeIndexByFamily[family] = nextActiveIndex;
|
|
1167
|
-
}
|
|
1168
|
-
await saveAccounts({
|
|
1169
|
-
version: 3,
|
|
1170
|
-
accounts,
|
|
1171
|
-
activeIndex: nextActiveIndex,
|
|
1172
|
-
activeIndexByFamily,
|
|
1146
|
+
};
|
|
1147
|
+
selectedAccountIndex = existingIndex;
|
|
1148
|
+
}
|
|
1149
|
+
const fallbackActiveIndex = accounts.length === 0
|
|
1150
|
+
? 0
|
|
1151
|
+
: Math.max(0, Math.min(stored?.activeIndex ?? 0, accounts.length - 1));
|
|
1152
|
+
const nextActiveIndex = accounts.length === 0
|
|
1153
|
+
? 0
|
|
1154
|
+
: selectedAccountIndex === null
|
|
1155
|
+
? fallbackActiveIndex
|
|
1156
|
+
: Math.max(0, Math.min(selectedAccountIndex, accounts.length - 1));
|
|
1157
|
+
const activeIndexByFamily = {};
|
|
1158
|
+
for (const family of MODEL_FAMILIES) {
|
|
1159
|
+
activeIndexByFamily[family] = nextActiveIndex;
|
|
1160
|
+
}
|
|
1161
|
+
await persist({
|
|
1162
|
+
version: 3,
|
|
1163
|
+
accounts,
|
|
1164
|
+
activeIndex: nextActiveIndex,
|
|
1165
|
+
activeIndexByFamily,
|
|
1166
|
+
});
|
|
1173
1167
|
});
|
|
1174
1168
|
}
|
|
1175
1169
|
async function syncSelectionToCodex(tokens) {
|
|
@@ -1279,7 +1273,7 @@ async function runHealthCheck(options = {}) {
|
|
|
1279
1273
|
});
|
|
1280
1274
|
if (quotaCache) {
|
|
1281
1275
|
quotaCacheChanged =
|
|
1282
|
-
updateQuotaCacheForAccount(quotaCache, account, snapshot) || quotaCacheChanged;
|
|
1276
|
+
updateQuotaCacheForAccount(quotaCache, account, snapshot, storage.accounts) || quotaCacheChanged;
|
|
1283
1277
|
}
|
|
1284
1278
|
healthDetail = formatQuotaSnapshotForDashboard(snapshot, display);
|
|
1285
1279
|
}
|
|
@@ -1349,7 +1343,7 @@ async function runHealthCheck(options = {}) {
|
|
|
1349
1343
|
});
|
|
1350
1344
|
if (quotaCache) {
|
|
1351
1345
|
quotaCacheChanged =
|
|
1352
|
-
updateQuotaCacheForAccount(quotaCache, account, snapshot) || quotaCacheChanged;
|
|
1346
|
+
updateQuotaCacheForAccount(quotaCache, account, snapshot, storage.accounts) || quotaCacheChanged;
|
|
1353
1347
|
}
|
|
1354
1348
|
healthyMessage = formatQuotaSnapshotForDashboard(snapshot, display);
|
|
1355
1349
|
}
|
|
@@ -1751,7 +1745,7 @@ async function runForecast(args) {
|
|
|
1751
1745
|
const account = storage.accounts[i];
|
|
1752
1746
|
if (account) {
|
|
1753
1747
|
quotaCacheChanged =
|
|
1754
|
-
updateQuotaCacheForAccount(quotaCache, account, liveQuota) || quotaCacheChanged;
|
|
1748
|
+
updateQuotaCacheForAccount(quotaCache, account, liveQuota, storage.accounts) || quotaCacheChanged;
|
|
1755
1749
|
}
|
|
1756
1750
|
}
|
|
1757
1751
|
}
|
|
@@ -2007,24 +2001,24 @@ function findExistingAccountIndexForFlagged(storage, flagged, nextRefreshToken,
|
|
|
2007
2001
|
const flaggedEmail = sanitizeEmail(flagged.email);
|
|
2008
2002
|
const candidateAccountId = nextAccountId ?? flagged.accountId;
|
|
2009
2003
|
const candidateEmail = sanitizeEmail(nextEmail) ?? flaggedEmail;
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
}
|
|
2027
|
-
return -1;
|
|
2004
|
+
const nextMatchIndex = findMatchingAccountIndex(storage.accounts, {
|
|
2005
|
+
accountId: candidateAccountId,
|
|
2006
|
+
email: candidateEmail,
|
|
2007
|
+
refreshToken: nextRefreshToken,
|
|
2008
|
+
}, {
|
|
2009
|
+
allowUniqueAccountIdFallbackWithoutEmail: true,
|
|
2010
|
+
});
|
|
2011
|
+
if (nextMatchIndex !== undefined) {
|
|
2012
|
+
return nextMatchIndex;
|
|
2013
|
+
}
|
|
2014
|
+
const flaggedMatchIndex = findMatchingAccountIndex(storage.accounts, {
|
|
2015
|
+
accountId: candidateAccountId,
|
|
2016
|
+
email: candidateEmail,
|
|
2017
|
+
refreshToken: flagged.refreshToken,
|
|
2018
|
+
}, {
|
|
2019
|
+
allowUniqueAccountIdFallbackWithoutEmail: true,
|
|
2020
|
+
});
|
|
2021
|
+
return flaggedMatchIndex ?? -1;
|
|
2028
2022
|
}
|
|
2029
2023
|
function upsertRecoveredFlaggedAccount(storage, flagged, refreshResult, now) {
|
|
2030
2024
|
const nextEmail = sanitizeEmail(extractAccountEmail(refreshResult.access, refreshResult.idToken)) ?? flagged.email;
|
|
@@ -2129,24 +2123,65 @@ async function runVerifyFlagged(args) {
|
|
|
2129
2123
|
console.log("No flagged accounts to check.");
|
|
2130
2124
|
return 0;
|
|
2131
2125
|
}
|
|
2132
|
-
let storage = await loadAccounts();
|
|
2133
|
-
if (!storage) {
|
|
2134
|
-
storage = createEmptyAccountStorage();
|
|
2135
|
-
}
|
|
2136
2126
|
let storageChanged = false;
|
|
2137
2127
|
let flaggedChanged = false;
|
|
2138
2128
|
const reports = [];
|
|
2139
2129
|
const nextFlaggedAccounts = [];
|
|
2140
2130
|
const now = Date.now();
|
|
2131
|
+
const refreshChecks = [];
|
|
2141
2132
|
for (let i = 0; i < flaggedStorage.accounts.length; i += 1) {
|
|
2142
2133
|
const flagged = flaggedStorage.accounts[i];
|
|
2143
2134
|
if (!flagged)
|
|
2144
2135
|
continue;
|
|
2145
2136
|
const label = formatAccountLabel(flagged, i);
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2137
|
+
refreshChecks.push({
|
|
2138
|
+
index: i,
|
|
2139
|
+
flagged,
|
|
2140
|
+
label,
|
|
2141
|
+
result: await queuedRefresh(flagged.refreshToken),
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2144
|
+
const applyRefreshChecks = (storage) => {
|
|
2145
|
+
for (const check of refreshChecks) {
|
|
2146
|
+
const { index: i, flagged, label, result } = check;
|
|
2147
|
+
if (result.type === "success") {
|
|
2148
|
+
if (!options.restore) {
|
|
2149
|
+
const nextFlagged = {
|
|
2150
|
+
...flagged,
|
|
2151
|
+
refreshToken: result.refresh,
|
|
2152
|
+
accessToken: result.access,
|
|
2153
|
+
expiresAt: result.expires,
|
|
2154
|
+
accountId: extractAccountId(result.access) ?? flagged.accountId,
|
|
2155
|
+
accountIdSource: extractAccountId(result.access) ? "token" : flagged.accountIdSource,
|
|
2156
|
+
email: sanitizeEmail(extractAccountEmail(result.access, result.idToken)) ?? flagged.email,
|
|
2157
|
+
lastUsed: now,
|
|
2158
|
+
lastError: undefined,
|
|
2159
|
+
};
|
|
2160
|
+
nextFlaggedAccounts.push(nextFlagged);
|
|
2161
|
+
if (JSON.stringify(nextFlagged) !== JSON.stringify(flagged)) {
|
|
2162
|
+
flaggedChanged = true;
|
|
2163
|
+
}
|
|
2164
|
+
reports.push({
|
|
2165
|
+
index: i,
|
|
2166
|
+
label,
|
|
2167
|
+
outcome: "healthy-flagged",
|
|
2168
|
+
message: "session is healthy (left in flagged list due to --no-restore)",
|
|
2169
|
+
});
|
|
2170
|
+
continue;
|
|
2171
|
+
}
|
|
2172
|
+
const upsertResult = upsertRecoveredFlaggedAccount(storage, flagged, result, now);
|
|
2173
|
+
if (upsertResult.restored) {
|
|
2174
|
+
storageChanged = storageChanged || upsertResult.changed;
|
|
2175
|
+
flaggedChanged = true;
|
|
2176
|
+
reports.push({
|
|
2177
|
+
index: i,
|
|
2178
|
+
label,
|
|
2179
|
+
outcome: "restored",
|
|
2180
|
+
message: upsertResult.message,
|
|
2181
|
+
});
|
|
2182
|
+
continue;
|
|
2183
|
+
}
|
|
2184
|
+
const updatedFlagged = {
|
|
2150
2185
|
...flagged,
|
|
2151
2186
|
refreshToken: result.refresh,
|
|
2152
2187
|
accessToken: result.access,
|
|
@@ -2155,87 +2190,71 @@ async function runVerifyFlagged(args) {
|
|
|
2155
2190
|
accountIdSource: extractAccountId(result.access) ? "token" : flagged.accountIdSource,
|
|
2156
2191
|
email: sanitizeEmail(extractAccountEmail(result.access, result.idToken)) ?? flagged.email,
|
|
2157
2192
|
lastUsed: now,
|
|
2158
|
-
lastError:
|
|
2193
|
+
lastError: upsertResult.message,
|
|
2159
2194
|
};
|
|
2160
|
-
nextFlaggedAccounts.push(
|
|
2161
|
-
if (JSON.stringify(
|
|
2195
|
+
nextFlaggedAccounts.push(updatedFlagged);
|
|
2196
|
+
if (JSON.stringify(updatedFlagged) !== JSON.stringify(flagged)) {
|
|
2162
2197
|
flaggedChanged = true;
|
|
2163
2198
|
}
|
|
2164
2199
|
reports.push({
|
|
2165
2200
|
index: i,
|
|
2166
2201
|
label,
|
|
2167
|
-
outcome: "
|
|
2168
|
-
message: "session is healthy (left in flagged list due to --no-restore)",
|
|
2169
|
-
});
|
|
2170
|
-
continue;
|
|
2171
|
-
}
|
|
2172
|
-
const upsertResult = upsertRecoveredFlaggedAccount(storage, flagged, result, now);
|
|
2173
|
-
if (upsertResult.restored) {
|
|
2174
|
-
storageChanged = storageChanged || upsertResult.changed;
|
|
2175
|
-
flaggedChanged = true;
|
|
2176
|
-
reports.push({
|
|
2177
|
-
index: i,
|
|
2178
|
-
label,
|
|
2179
|
-
outcome: "restored",
|
|
2202
|
+
outcome: "restore-skipped",
|
|
2180
2203
|
message: upsertResult.message,
|
|
2181
2204
|
});
|
|
2182
2205
|
continue;
|
|
2183
2206
|
}
|
|
2184
|
-
const
|
|
2207
|
+
const detail = normalizeFailureDetail(result.message, result.reason);
|
|
2208
|
+
const failedFlagged = {
|
|
2185
2209
|
...flagged,
|
|
2186
|
-
|
|
2187
|
-
accessToken: result.access,
|
|
2188
|
-
expiresAt: result.expires,
|
|
2189
|
-
accountId: extractAccountId(result.access) ?? flagged.accountId,
|
|
2190
|
-
accountIdSource: extractAccountId(result.access) ? "token" : flagged.accountIdSource,
|
|
2191
|
-
email: sanitizeEmail(extractAccountEmail(result.access, result.idToken)) ?? flagged.email,
|
|
2192
|
-
lastUsed: now,
|
|
2193
|
-
lastError: upsertResult.message,
|
|
2210
|
+
lastError: detail,
|
|
2194
2211
|
};
|
|
2195
|
-
nextFlaggedAccounts.push(
|
|
2196
|
-
if (
|
|
2212
|
+
nextFlaggedAccounts.push(failedFlagged);
|
|
2213
|
+
if ((flagged.lastError ?? "") !== detail) {
|
|
2197
2214
|
flaggedChanged = true;
|
|
2198
2215
|
}
|
|
2199
2216
|
reports.push({
|
|
2200
2217
|
index: i,
|
|
2201
2218
|
label,
|
|
2202
|
-
outcome: "
|
|
2203
|
-
message:
|
|
2219
|
+
outcome: "still-flagged",
|
|
2220
|
+
message: detail,
|
|
2204
2221
|
});
|
|
2205
|
-
continue;
|
|
2206
2222
|
}
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
};
|
|
2212
|
-
nextFlaggedAccounts.push(failedFlagged);
|
|
2213
|
-
if ((flagged.lastError ?? "") !== detail) {
|
|
2214
|
-
flaggedChanged = true;
|
|
2223
|
+
};
|
|
2224
|
+
if (options.restore) {
|
|
2225
|
+
if (options.dryRun) {
|
|
2226
|
+
applyRefreshChecks((await loadAccounts()) ?? createEmptyAccountStorage());
|
|
2215
2227
|
}
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2228
|
+
else {
|
|
2229
|
+
await withAccountAndFlaggedStorageTransaction(async (loadedStorage, persist) => {
|
|
2230
|
+
const nextStorage = loadedStorage
|
|
2231
|
+
? structuredClone(loadedStorage)
|
|
2232
|
+
: createEmptyAccountStorage();
|
|
2233
|
+
applyRefreshChecks(nextStorage);
|
|
2234
|
+
if (!storageChanged) {
|
|
2235
|
+
return;
|
|
2236
|
+
}
|
|
2237
|
+
normalizeDoctorIndexes(nextStorage);
|
|
2238
|
+
await persist(nextStorage, {
|
|
2239
|
+
version: 1,
|
|
2240
|
+
accounts: nextFlaggedAccounts,
|
|
2241
|
+
});
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
else {
|
|
2246
|
+
applyRefreshChecks(createEmptyAccountStorage());
|
|
2222
2247
|
}
|
|
2223
2248
|
const remainingFlagged = nextFlaggedAccounts.length;
|
|
2224
2249
|
const restored = reports.filter((report) => report.outcome === "restored").length;
|
|
2225
2250
|
const healthyFlagged = reports.filter((report) => report.outcome === "healthy-flagged").length;
|
|
2226
2251
|
const stillFlagged = reports.filter((report) => report.outcome === "still-flagged").length;
|
|
2227
2252
|
const changed = storageChanged || flaggedChanged;
|
|
2228
|
-
if (!options.dryRun) {
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
}
|
|
2233
|
-
if (flaggedChanged) {
|
|
2234
|
-
await saveFlaggedAccounts({
|
|
2235
|
-
version: 1,
|
|
2236
|
-
accounts: nextFlaggedAccounts,
|
|
2237
|
-
});
|
|
2238
|
-
}
|
|
2253
|
+
if (!options.dryRun && flaggedChanged && (!options.restore || !storageChanged)) {
|
|
2254
|
+
await saveFlaggedAccounts({
|
|
2255
|
+
version: 1,
|
|
2256
|
+
accounts: nextFlaggedAccounts,
|
|
2257
|
+
});
|
|
2239
2258
|
}
|
|
2240
2259
|
if (options.json) {
|
|
2241
2260
|
console.log(JSON.stringify({
|
|
@@ -2340,7 +2359,7 @@ async function runFix(args) {
|
|
|
2340
2359
|
});
|
|
2341
2360
|
if (quotaCache) {
|
|
2342
2361
|
quotaCacheChanged =
|
|
2343
|
-
updateQuotaCacheForAccount(quotaCache, account, snapshot) || quotaCacheChanged;
|
|
2362
|
+
updateQuotaCacheForAccount(quotaCache, account, snapshot, storage.accounts) || quotaCacheChanged;
|
|
2344
2363
|
}
|
|
2345
2364
|
reports.push({
|
|
2346
2365
|
index: i,
|
|
@@ -2413,7 +2432,7 @@ async function runFix(args) {
|
|
|
2413
2432
|
});
|
|
2414
2433
|
if (quotaCache) {
|
|
2415
2434
|
quotaCacheChanged =
|
|
2416
|
-
updateQuotaCacheForAccount(quotaCache, account, snapshot) || quotaCacheChanged;
|
|
2435
|
+
updateQuotaCacheForAccount(quotaCache, account, snapshot, storage.accounts) || quotaCacheChanged;
|
|
2417
2436
|
}
|
|
2418
2437
|
reports.push({
|
|
2419
2438
|
index: i,
|
|
@@ -2615,6 +2634,12 @@ function normalizeDoctorIndexes(storage) {
|
|
|
2615
2634
|
}
|
|
2616
2635
|
return changed;
|
|
2617
2636
|
}
|
|
2637
|
+
function getDoctorRefreshTokenKey(refreshToken) {
|
|
2638
|
+
if (typeof refreshToken !== "string")
|
|
2639
|
+
return undefined;
|
|
2640
|
+
const trimmed = refreshToken.trim();
|
|
2641
|
+
return trimmed || undefined;
|
|
2642
|
+
}
|
|
2618
2643
|
function applyDoctorFixes(storage) {
|
|
2619
2644
|
let changed = false;
|
|
2620
2645
|
const actions = [];
|
|
@@ -2630,7 +2655,9 @@ function applyDoctorFixes(storage) {
|
|
|
2630
2655
|
const account = storage.accounts[i];
|
|
2631
2656
|
if (!account)
|
|
2632
2657
|
continue;
|
|
2633
|
-
const refreshToken = account.refreshToken
|
|
2658
|
+
const refreshToken = getDoctorRefreshTokenKey(account.refreshToken);
|
|
2659
|
+
if (!refreshToken)
|
|
2660
|
+
continue;
|
|
2634
2661
|
const existingTokenIndex = seenRefreshTokens.get(refreshToken);
|
|
2635
2662
|
if (typeof existingTokenIndex === "number") {
|
|
2636
2663
|
if (account.enabled !== false) {
|
|
@@ -2879,7 +2906,9 @@ async function runDoctor(args) {
|
|
|
2879
2906
|
const seenRefreshTokens = new Set();
|
|
2880
2907
|
let duplicateTokenCount = 0;
|
|
2881
2908
|
for (const account of storage.accounts) {
|
|
2882
|
-
const token = account.refreshToken
|
|
2909
|
+
const token = getDoctorRefreshTokenKey(account.refreshToken);
|
|
2910
|
+
if (!token)
|
|
2911
|
+
continue;
|
|
2883
2912
|
if (seenRefreshTokens.has(token)) {
|
|
2884
2913
|
duplicateTokenCount += 1;
|
|
2885
2914
|
}
|