codex-multi-auth 0.1.8 → 1.1.10

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.
@@ -713,107 +713,224 @@ function selectNewestAccount(current, candidate) {
713
713
  const candidateAddedAt = candidate.addedAt || 0;
714
714
  return candidateAddedAt >= currentAddedAt ? candidate : current;
715
715
  }
716
- function deduplicateAccountsByKey(accounts) {
717
- const keyToIndex = new Map();
718
- const indicesToKeep = new Set();
716
+ function normalizeAccountIdKey(accountId) {
717
+ if (!accountId)
718
+ return undefined;
719
+ const trimmed = accountId.trim();
720
+ return trimmed || undefined;
721
+ }
722
+ /**
723
+ * Normalize email keys for case-insensitive account identity matching.
724
+ */
725
+ export function normalizeEmailKey(email) {
726
+ if (!email)
727
+ return undefined;
728
+ const trimmed = email.trim();
729
+ if (!trimmed)
730
+ return undefined;
731
+ return trimmed.toLowerCase();
732
+ }
733
+ function normalizeRefreshTokenKey(refreshToken) {
734
+ if (!refreshToken)
735
+ return undefined;
736
+ const trimmed = refreshToken.trim();
737
+ return trimmed || undefined;
738
+ }
739
+ function toAccountIdentityRef(account) {
740
+ return {
741
+ accountId: normalizeAccountIdKey(account?.accountId),
742
+ emailKey: normalizeEmailKey(account?.email),
743
+ refreshToken: normalizeRefreshTokenKey(account?.refreshToken),
744
+ };
745
+ }
746
+ function collectDistinctIdentityValues(values) {
747
+ const distinct = new Set();
748
+ for (const value of values) {
749
+ if (value)
750
+ distinct.add(value);
751
+ }
752
+ return distinct;
753
+ }
754
+ export function getAccountIdentityKey(account) {
755
+ const ref = toAccountIdentityRef(account);
756
+ if (ref.accountId && ref.emailKey) {
757
+ return `account:${ref.accountId}::email:${ref.emailKey}`;
758
+ }
759
+ if (ref.accountId)
760
+ return `account:${ref.accountId}`;
761
+ if (ref.emailKey)
762
+ return `email:${ref.emailKey}`;
763
+ if (ref.refreshToken)
764
+ return `refresh:${ref.refreshToken}`;
765
+ return undefined;
766
+ }
767
+ function findNewestMatchingIndex(accounts, predicate) {
768
+ let matchIndex;
769
+ let match;
719
770
  for (let i = 0; i < accounts.length; i += 1) {
720
771
  const account = accounts[i];
721
772
  if (!account)
722
773
  continue;
723
- const key = account.accountId || account.refreshToken;
724
- if (!key)
774
+ const ref = toAccountIdentityRef(account);
775
+ if (!predicate(ref))
725
776
  continue;
726
- const existingIndex = keyToIndex.get(key);
727
- if (existingIndex === undefined) {
728
- keyToIndex.set(key, i);
777
+ if (matchIndex === undefined) {
778
+ matchIndex = i;
779
+ match = account;
729
780
  continue;
730
781
  }
731
- const existing = accounts[existingIndex];
732
- const newest = selectNewestAccount(existing, account);
733
- keyToIndex.set(key, newest === account ? i : existingIndex);
734
- }
735
- for (const idx of keyToIndex.values()) {
736
- indicesToKeep.add(idx);
737
- }
738
- const result = [];
739
- for (let i = 0; i < accounts.length; i += 1) {
740
- if (indicesToKeep.has(i)) {
741
- const account = accounts[i];
742
- if (account)
743
- result.push(account);
782
+ const newest = selectNewestAccount(match, account);
783
+ if (newest === account) {
784
+ matchIndex = i;
785
+ match = account;
744
786
  }
745
787
  }
746
- return result;
788
+ return matchIndex;
747
789
  }
748
- /**
749
- * Removes duplicate accounts, keeping the most recently used entry for each unique key.
750
- * Deduplication is based on accountId or refreshToken.
751
- * @param accounts - Array of accounts to deduplicate
752
- * @returns New array with duplicates removed
753
- */
754
- export function deduplicateAccounts(accounts) {
755
- return deduplicateAccountsByKey(accounts);
790
+ function findCompositeAccountMatchIndex(accounts, candidateRef) {
791
+ if (!candidateRef.accountId || !candidateRef.emailKey)
792
+ return undefined;
793
+ return findNewestMatchingIndex(accounts, (ref) => ref.accountId === candidateRef.accountId &&
794
+ ref.emailKey === candidateRef.emailKey);
756
795
  }
757
- /**
758
- * Removes duplicate accounts by email, keeping the most recently used entry.
759
- * Accounts without email are always preserved.
760
- * @param accounts - Array of accounts to deduplicate
761
- * @returns New array with email duplicates removed
762
- */
763
- export function normalizeEmailKey(email) {
764
- if (!email)
796
+ function findSafeEmailMatchIndex(accounts, candidateRef) {
797
+ if (!candidateRef.emailKey)
765
798
  return undefined;
766
- const trimmed = email.trim();
767
- if (!trimmed)
799
+ const emailAccountIds = [candidateRef.accountId];
800
+ let foundAny = false;
801
+ for (let i = 0; i < accounts.length; i += 1) {
802
+ const account = accounts[i];
803
+ if (!account)
804
+ continue;
805
+ const ref = toAccountIdentityRef(account);
806
+ if (ref.emailKey !== candidateRef.emailKey)
807
+ continue;
808
+ foundAny = true;
809
+ emailAccountIds.push(ref.accountId);
810
+ }
811
+ if (!foundAny)
768
812
  return undefined;
769
- return trimmed.toLowerCase();
813
+ if (collectDistinctIdentityValues(emailAccountIds).size > 1) {
814
+ return undefined;
815
+ }
816
+ return findNewestMatchingIndex(accounts, (ref) => ref.emailKey === candidateRef.emailKey);
770
817
  }
771
- export function deduplicateAccountsByEmail(accounts) {
772
- const emailToNewestIndex = new Map();
773
- const indicesToKeep = new Set();
818
+ function findCompatibleRefreshTokenMatchIndex(accounts, candidateRef) {
819
+ if (!candidateRef.refreshToken)
820
+ return undefined;
821
+ let matchingIndex;
822
+ let matchingAccount = null;
774
823
  for (let i = 0; i < accounts.length; i += 1) {
775
824
  const account = accounts[i];
776
825
  if (!account)
777
826
  continue;
778
- const email = normalizeEmailKey(account.email);
779
- if (!email) {
780
- indicesToKeep.add(i);
781
- continue;
782
- }
783
- const existingIndex = emailToNewestIndex.get(email);
784
- if (existingIndex === undefined) {
785
- emailToNewestIndex.set(email, i);
827
+ const ref = toAccountIdentityRef(account);
828
+ if (ref.refreshToken !== candidateRef.refreshToken)
786
829
  continue;
787
- }
788
- const existing = accounts[existingIndex];
789
- // istanbul ignore next -- defensive code: existingIndex always refers to valid account
790
- if (!existing) {
791
- emailToNewestIndex.set(email, i);
830
+ if ((candidateRef.accountId &&
831
+ ref.accountId &&
832
+ ref.accountId !== candidateRef.accountId) ||
833
+ (candidateRef.emailKey &&
834
+ ref.emailKey &&
835
+ ref.emailKey !== candidateRef.emailKey)) {
836
+ return undefined;
837
+ }
838
+ if (matchingIndex !== undefined &&
839
+ !candidateRef.accountId &&
840
+ !candidateRef.emailKey) {
841
+ return undefined;
842
+ }
843
+ if (matchingIndex === undefined || matchingAccount === null) {
844
+ matchingIndex = i;
845
+ matchingAccount = account;
792
846
  continue;
793
847
  }
794
- const existingLastUsed = existing.lastUsed || 0;
795
- const candidateLastUsed = account.lastUsed || 0;
796
- const existingAddedAt = existing.addedAt || 0;
797
- const candidateAddedAt = account.addedAt || 0;
798
- const isNewer = candidateLastUsed > existingLastUsed ||
799
- (candidateLastUsed === existingLastUsed &&
800
- candidateAddedAt > existingAddedAt);
801
- if (isNewer) {
802
- emailToNewestIndex.set(email, i);
848
+ const newest = selectNewestAccount(matchingAccount ?? undefined, account);
849
+ if (newest === account) {
850
+ matchingIndex = i;
851
+ matchingAccount = account;
803
852
  }
804
853
  }
805
- for (const idx of emailToNewestIndex.values()) {
806
- indicesToKeep.add(idx);
854
+ return matchingIndex;
855
+ }
856
+ function findUniqueAccountIdMatchIndex(accounts, candidateRef, options) {
857
+ if (!candidateRef.accountId)
858
+ return undefined;
859
+ if (!candidateRef.emailKey &&
860
+ !options.allowUniqueAccountIdFallbackWithoutEmail) {
861
+ return undefined;
807
862
  }
808
- const result = [];
863
+ let matchingIndex;
864
+ let matchingEmailKey;
809
865
  for (let i = 0; i < accounts.length; i += 1) {
810
- if (indicesToKeep.has(i)) {
811
- const account = accounts[i];
812
- if (account)
813
- result.push(account);
866
+ const account = accounts[i];
867
+ if (!account)
868
+ continue;
869
+ const ref = toAccountIdentityRef(account);
870
+ if (ref.accountId !== candidateRef.accountId)
871
+ continue;
872
+ if (matchingIndex !== undefined) {
873
+ return undefined;
874
+ }
875
+ matchingIndex = i;
876
+ matchingEmailKey = ref.emailKey;
877
+ }
878
+ if (matchingIndex !== undefined &&
879
+ matchingEmailKey &&
880
+ candidateRef.emailKey &&
881
+ matchingEmailKey !== candidateRef.emailKey) {
882
+ return undefined;
883
+ }
884
+ return matchingIndex;
885
+ }
886
+ export function findMatchingAccountIndex(accounts, candidate, options = {}) {
887
+ const candidateRef = toAccountIdentityRef(candidate);
888
+ const byComposite = findCompositeAccountMatchIndex(accounts, candidateRef);
889
+ if (byComposite !== undefined)
890
+ return byComposite;
891
+ const byEmail = findSafeEmailMatchIndex(accounts, candidateRef);
892
+ if (byEmail !== undefined)
893
+ return byEmail;
894
+ if (candidateRef.refreshToken) {
895
+ const byRefresh = findCompatibleRefreshTokenMatchIndex(accounts, candidateRef);
896
+ if (byRefresh !== undefined)
897
+ return byRefresh;
898
+ }
899
+ return findUniqueAccountIdMatchIndex(accounts, candidateRef, options);
900
+ }
901
+ export function resolveAccountSelectionIndex(accounts, candidate, fallbackIndex = 0) {
902
+ if (accounts.length === 0)
903
+ return 0;
904
+ const matchedIndex = findMatchingAccountIndex(accounts, candidate, {
905
+ allowUniqueAccountIdFallbackWithoutEmail: true,
906
+ });
907
+ if (matchedIndex !== undefined)
908
+ return matchedIndex;
909
+ return clampIndex(fallbackIndex, accounts.length);
910
+ }
911
+ function deduplicateAccountsByIdentity(accounts) {
912
+ const deduplicated = [];
913
+ for (const account of accounts) {
914
+ if (!account)
915
+ continue;
916
+ const existingIndex = findMatchingAccountIndex(deduplicated, account);
917
+ if (existingIndex === undefined) {
918
+ deduplicated.push(account);
919
+ continue;
814
920
  }
921
+ deduplicated[existingIndex] = selectNewestAccount(deduplicated[existingIndex], account);
815
922
  }
816
- return result;
923
+ return deduplicated;
924
+ }
925
+ /**
926
+ * Removes duplicate accounts, keeping the most recently used entry for each
927
+ * safely matched identity.
928
+ */
929
+ export function deduplicateAccounts(accounts) {
930
+ return deduplicateAccountsByIdentity(accounts);
931
+ }
932
+ export function deduplicateAccountsByEmail(accounts) {
933
+ return deduplicateAccountsByIdentity(accounts);
817
934
  }
818
935
  function isRecord(value) {
819
936
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -823,20 +940,17 @@ function clampIndex(index, length) {
823
940
  return 0;
824
941
  return Math.max(0, Math.min(index, length - 1));
825
942
  }
826
- function toAccountKey(account) {
827
- return account.accountId || account.refreshToken;
828
- }
829
- function extractActiveKey(accounts, activeIndex) {
943
+ function extractActiveAccountRef(accounts, activeIndex) {
830
944
  const candidate = accounts[activeIndex];
831
945
  if (!isRecord(candidate))
832
- return undefined;
833
- const accountId = typeof candidate.accountId === "string" && candidate.accountId.trim()
834
- ? candidate.accountId
835
- : undefined;
836
- const refreshToken = typeof candidate.refreshToken === "string" && candidate.refreshToken.trim()
837
- ? candidate.refreshToken
838
- : undefined;
839
- return accountId || refreshToken;
946
+ return {};
947
+ return toAccountIdentityRef({
948
+ accountId: typeof candidate.accountId === "string" ? candidate.accountId : undefined,
949
+ email: typeof candidate.email === "string" ? candidate.email : undefined,
950
+ refreshToken: typeof candidate.refreshToken === "string"
951
+ ? candidate.refreshToken
952
+ : undefined,
953
+ });
840
954
  }
841
955
  /**
842
956
  * Normalizes and validates account storage data, migrating from v1 to v3 if needed.
@@ -864,7 +978,7 @@ export function normalizeAccountStorage(data) {
864
978
  ? data.activeIndex
865
979
  : 0;
866
980
  const rawActiveIndex = clampIndex(activeIndexValue, rawAccounts.length);
867
- const activeKey = extractActiveKey(rawAccounts, rawActiveIndex);
981
+ const activeRef = extractActiveAccountRef(rawAccounts, rawActiveIndex);
868
982
  const fromVersion = data.version;
869
983
  const baseStorage = fromVersion === 1
870
984
  ? migrateV1ToV3(data)
@@ -872,16 +986,15 @@ export function normalizeAccountStorage(data) {
872
986
  const validAccounts = rawAccounts.filter((account) => isRecord(account) &&
873
987
  typeof account.refreshToken === "string" &&
874
988
  !!account.refreshToken.trim());
875
- const deduplicatedAccounts = deduplicateAccountsByEmail(deduplicateAccountsByKey(validAccounts));
989
+ const deduplicatedAccounts = deduplicateAccounts(validAccounts);
876
990
  const activeIndex = (() => {
877
991
  if (deduplicatedAccounts.length === 0)
878
992
  return 0;
879
- if (activeKey) {
880
- const mappedIndex = deduplicatedAccounts.findIndex((account) => toAccountKey(account) === activeKey);
881
- if (mappedIndex >= 0)
882
- return mappedIndex;
883
- }
884
- return clampIndex(rawActiveIndex, deduplicatedAccounts.length);
993
+ return resolveAccountSelectionIndex(deduplicatedAccounts, {
994
+ accountId: activeRef.accountId,
995
+ email: activeRef.emailKey,
996
+ refreshToken: activeRef.refreshToken,
997
+ }, rawActiveIndex);
885
998
  })();
886
999
  const activeIndexByFamily = {};
887
1000
  const rawFamilyIndices = isRecord(baseStorage.activeIndexByFamily)
@@ -893,15 +1006,12 @@ export function normalizeAccountStorage(data) {
893
1006
  ? rawIndexValue
894
1007
  : rawActiveIndex;
895
1008
  const clampedRawIndex = clampIndex(rawIndex, rawAccounts.length);
896
- const familyKey = extractActiveKey(rawAccounts, clampedRawIndex);
897
- let mappedIndex = clampIndex(rawIndex, deduplicatedAccounts.length);
898
- if (familyKey && deduplicatedAccounts.length > 0) {
899
- const idx = deduplicatedAccounts.findIndex((account) => toAccountKey(account) === familyKey);
900
- if (idx >= 0) {
901
- mappedIndex = idx;
902
- }
903
- }
904
- activeIndexByFamily[family] = mappedIndex;
1009
+ const familyRef = extractActiveAccountRef(rawAccounts, clampedRawIndex);
1010
+ activeIndexByFamily[family] = resolveAccountSelectionIndex(deduplicatedAccounts, {
1011
+ accountId: familyRef.accountId,
1012
+ email: familyRef.emailKey,
1013
+ refreshToken: familyRef.refreshToken,
1014
+ }, rawIndex);
905
1015
  }
906
1016
  return {
907
1017
  version: 3,
@@ -1314,6 +1424,17 @@ async function saveAccountsUnlocked(storage) {
1314
1424
  throw new StorageError(`Failed to save accounts: ${err?.message || "Unknown error"}`, code, path, hint, err instanceof Error ? err : undefined);
1315
1425
  }
1316
1426
  }
1427
+ function cloneAccountStorageForPersistence(storage) {
1428
+ return {
1429
+ version: 3,
1430
+ accounts: structuredClone(storage?.accounts ?? []),
1431
+ activeIndex: typeof storage?.activeIndex === "number" &&
1432
+ Number.isFinite(storage.activeIndex)
1433
+ ? storage.activeIndex
1434
+ : 0,
1435
+ activeIndexByFamily: structuredClone(storage?.activeIndexByFamily ?? {}),
1436
+ };
1437
+ }
1317
1438
  export async function withAccountStorageTransaction(handler) {
1318
1439
  return withStorageLock(async () => {
1319
1440
  const state = {
@@ -1328,6 +1449,40 @@ export async function withAccountStorageTransaction(handler) {
1328
1449
  return transactionSnapshotContext.run(state, () => handler(current, persist));
1329
1450
  });
1330
1451
  }
1452
+ export async function withAccountAndFlaggedStorageTransaction(handler) {
1453
+ return withStorageLock(async () => {
1454
+ const state = {
1455
+ snapshot: await loadAccountsInternal(saveAccountsUnlocked),
1456
+ active: true,
1457
+ };
1458
+ const current = state.snapshot;
1459
+ const persist = async (accountStorage, flaggedStorage) => {
1460
+ const previousAccounts = cloneAccountStorageForPersistence(state.snapshot);
1461
+ const nextAccounts = cloneAccountStorageForPersistence(accountStorage);
1462
+ await saveAccountsUnlocked(nextAccounts);
1463
+ try {
1464
+ await saveFlaggedAccountsUnlocked(flaggedStorage);
1465
+ state.snapshot = nextAccounts;
1466
+ }
1467
+ catch (error) {
1468
+ try {
1469
+ await saveAccountsUnlocked(previousAccounts);
1470
+ state.snapshot = previousAccounts;
1471
+ }
1472
+ catch (rollbackError) {
1473
+ const combinedError = new AggregateError([error, rollbackError], "Flagged save failed and account storage rollback also failed");
1474
+ log.error("Failed to rollback account storage after flagged save failure", {
1475
+ error: String(error),
1476
+ rollbackError: String(rollbackError),
1477
+ });
1478
+ throw combinedError;
1479
+ }
1480
+ throw error;
1481
+ }
1482
+ };
1483
+ return transactionSnapshotContext.run(state, () => handler(current, persist));
1484
+ });
1485
+ }
1331
1486
  /**
1332
1487
  * Persists account storage to disk using atomic write (temp file + rename).
1333
1488
  * Creates the Codex multi-auth storage directory if it doesn't exist.
@@ -1397,7 +1552,10 @@ function normalizeFlaggedStorage(data) {
1397
1552
  value === "id_token" ||
1398
1553
  value === "org" ||
1399
1554
  value === "manual";
1400
- const isSwitchReason = (value) => value === "rate-limit" || value === "initial" || value === "rotation";
1555
+ const isSwitchReason = (value) => value === "rate-limit" ||
1556
+ value === "initial" ||
1557
+ value === "rotation" ||
1558
+ value === "best";
1401
1559
  const isCooldownReason = (value) => value === "auth-failure" ||
1402
1560
  value === "network-error" ||
1403
1561
  value === "rate-limit";
@@ -1516,48 +1674,53 @@ export async function loadFlaggedAccounts() {
1516
1674
  return empty;
1517
1675
  }
1518
1676
  }
1519
- export async function saveFlaggedAccounts(storage) {
1520
- return withStorageLock(async () => {
1521
- const path = getFlaggedAccountsPath();
1522
- const markerPath = getIntentionalResetMarkerPath(path);
1523
- const uniqueSuffix = `${Date.now()}.${Math.random().toString(36).slice(2, 8)}`;
1524
- const tempPath = `${path}.${uniqueSuffix}.tmp`;
1525
- try {
1526
- await fs.mkdir(dirname(path), { recursive: true });
1527
- if (existsSync(path)) {
1528
- try {
1529
- await fs.copyFile(path, `${path}.bak`);
1530
- }
1531
- catch (backupError) {
1532
- log.warn("Failed to create flagged backup snapshot", {
1533
- path,
1534
- error: String(backupError),
1535
- });
1536
- }
1537
- }
1538
- const content = JSON.stringify(normalizeFlaggedStorage(storage), null, 2);
1539
- await fs.writeFile(tempPath, content, { encoding: "utf-8", mode: 0o600 });
1540
- await fs.rename(tempPath, path);
1677
+ async function saveFlaggedAccountsUnlocked(storage) {
1678
+ const path = getFlaggedAccountsPath();
1679
+ const markerPath = getIntentionalResetMarkerPath(path);
1680
+ const uniqueSuffix = `${Date.now()}.${Math.random().toString(36).slice(2, 8)}`;
1681
+ const tempPath = `${path}.${uniqueSuffix}.tmp`;
1682
+ try {
1683
+ await fs.mkdir(dirname(path), { recursive: true });
1684
+ if (existsSync(path)) {
1541
1685
  try {
1542
- await fs.unlink(markerPath);
1686
+ await copyFileWithRetry(path, `${path}.bak`, {
1687
+ allowMissingSource: true,
1688
+ });
1543
1689
  }
1544
- catch {
1545
- // Best effort cleanup.
1690
+ catch (backupError) {
1691
+ log.warn("Failed to create flagged backup snapshot", {
1692
+ path,
1693
+ error: String(backupError),
1694
+ });
1546
1695
  }
1547
1696
  }
1548
- catch (error) {
1549
- try {
1550
- await fs.unlink(tempPath);
1551
- }
1552
- catch {
1553
- // Ignore cleanup failures.
1554
- }
1555
- log.error("Failed to save flagged account storage", {
1556
- path,
1557
- error: String(error),
1558
- });
1559
- throw error;
1697
+ const content = JSON.stringify(normalizeFlaggedStorage(storage), null, 2);
1698
+ await fs.writeFile(tempPath, content, { encoding: "utf-8", mode: 0o600 });
1699
+ await renameFileWithRetry(tempPath, path);
1700
+ try {
1701
+ await fs.unlink(markerPath);
1702
+ }
1703
+ catch {
1704
+ // Best effort cleanup.
1705
+ }
1706
+ }
1707
+ catch (error) {
1708
+ try {
1709
+ await fs.unlink(tempPath);
1560
1710
  }
1711
+ catch {
1712
+ // Ignore cleanup failures.
1713
+ }
1714
+ log.error("Failed to save flagged account storage", {
1715
+ path,
1716
+ error: String(error),
1717
+ });
1718
+ throw error;
1719
+ }
1720
+ }
1721
+ export async function saveFlaggedAccounts(storage) {
1722
+ return withStorageLock(async () => {
1723
+ await saveFlaggedAccountsUnlocked(storage);
1561
1724
  });
1562
1725
  }
1563
1726
  export async function clearFlaggedAccounts() {
@@ -1635,7 +1798,7 @@ export async function exportAccounts(filePath, force = true, beforeCommit) {
1635
1798
  }
1636
1799
  /**
1637
1800
  * Imports accounts from a JSON file, merging with existing accounts.
1638
- * Deduplicates by accountId/email, preserving most recently used entries.
1801
+ * Deduplicates by safe account identity, preserving most recently used entries.
1639
1802
  * @param filePath - Source file path
1640
1803
  * @throws Error if file is invalid or would exceed MAX_ACCOUNTS
1641
1804
  */
@@ -1662,12 +1825,12 @@ export async function importAccounts(filePath) {
1662
1825
  const existingActiveIndex = existing?.activeIndex ?? 0;
1663
1826
  const merged = [...existingAccounts, ...normalized.accounts];
1664
1827
  if (merged.length > ACCOUNT_LIMITS.MAX_ACCOUNTS) {
1665
- const deduped = deduplicateAccountsByEmail(deduplicateAccounts(merged));
1828
+ const deduped = deduplicateAccounts(merged);
1666
1829
  if (deduped.length > ACCOUNT_LIMITS.MAX_ACCOUNTS) {
1667
1830
  throw new Error(`Import would exceed maximum of ${ACCOUNT_LIMITS.MAX_ACCOUNTS} accounts (would have ${deduped.length})`);
1668
1831
  }
1669
1832
  }
1670
- const deduplicatedAccounts = deduplicateAccountsByEmail(deduplicateAccounts(merged));
1833
+ const deduplicatedAccounts = deduplicateAccounts(merged);
1671
1834
  const newStorage = {
1672
1835
  version: 3,
1673
1836
  accounts: deduplicatedAccounts,