Skip to content

Commit 3dd9104

Browse files
committed
Apply auth snapshot only if newer
Refactor account auth updates to avoid overwriting newer state with stale snapshots. Introduces shouldApplyAuthSnapshot(_:over:) which decides when to apply an incoming CodexAccount snapshot (e.g. when tokens are missing, snapshot has newer successful/last refresh timestamps, or tokens changed while existing state needs reauth). Only updates planType/accountId when meaningful, updates tokens and timestamps conservatively, and resets refresh-failure counters when tokens actually change or the account needed reauth. Persist and return the updated account after applying the safe merge.
1 parent 169b05b commit 3dd9104

1 file changed

Lines changed: 58 additions & 10 deletions

File tree

CodexAccounts/ViewModels/AccountsViewModel.swift

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -548,18 +548,66 @@ final class AccountsViewModel {
548548
return authAccount
549549
}
550550

551-
accounts[idx].accessToken = authAccount.accessToken
552-
accounts[idx].refreshToken = authAccount.refreshToken
553-
accounts[idx].idToken = authAccount.idToken
554-
accounts[idx].planType = authAccount.planType
555-
accounts[idx].accountId = authAccount.accountId
556-
accounts[idx].lastTokenRefresh = authAccount.lastTokenRefresh
557-
accounts[idx].lastSuccessfulTokenRefreshAt = authAccount.lastSuccessfulTokenRefreshAt ?? accounts[idx].lastSuccessfulTokenRefreshAt
558-
if accounts[idx].authState != .needsReauth {
559-
accounts[idx].authState = .healthy
551+
var existing = accounts[idx]
552+
let snapshotIsNewer = shouldApplyAuthSnapshot(authAccount, over: existing)
553+
554+
if authAccount.planType.lowercased() != "unknown" {
555+
existing.planType = authAccount.planType
556+
}
557+
if let accountId = authAccount.accountId, !accountId.isEmpty {
558+
existing.accountId = accountId
559+
}
560+
561+
if snapshotIsNewer {
562+
let tokensChanged = authAccount.accessToken != existing.accessToken
563+
|| authAccount.refreshToken != existing.refreshToken
564+
|| authAccount.idToken != existing.idToken
565+
566+
existing.accessToken = authAccount.accessToken
567+
existing.refreshToken = authAccount.refreshToken
568+
existing.idToken = authAccount.idToken
569+
existing.lastTokenRefresh = authAccount.lastTokenRefresh ?? existing.lastTokenRefresh
570+
existing.lastSuccessfulTokenRefreshAt = authAccount.lastSuccessfulTokenRefreshAt
571+
?? authAccount.lastTokenRefresh
572+
?? existing.lastSuccessfulTokenRefreshAt
573+
574+
if tokensChanged || existing.authState == .needsReauth {
575+
existing.authState = .healthy
576+
existing.lastRefreshFailureAt = nil
577+
existing.consecutiveRefreshFailures = 0
578+
}
560579
}
580+
581+
accounts[idx] = existing
561582
persistAccounts()
562-
return accounts[idx]
583+
return existing
584+
}
585+
586+
private func shouldApplyAuthSnapshot(_ snapshot: CodexAccount, over existing: CodexAccount) -> Bool {
587+
if existing.accessToken.isEmpty || existing.refreshToken.isEmpty {
588+
return true
589+
}
590+
591+
let tokensChanged = snapshot.accessToken != existing.accessToken
592+
|| snapshot.refreshToken != existing.refreshToken
593+
|| snapshot.idToken != existing.idToken
594+
595+
let snapshotRefreshAt = snapshot.lastSuccessfulTokenRefreshAt ?? snapshot.lastTokenRefresh
596+
let existingRefreshAt = existing.lastSuccessfulTokenRefreshAt ?? existing.lastTokenRefresh
597+
598+
switch (snapshotRefreshAt, existingRefreshAt) {
599+
case let (snapshotDate?, existingDate?):
600+
if snapshotDate >= existingDate {
601+
return true
602+
}
603+
return existing.authState == .needsReauth && tokensChanged
604+
case (_?, nil):
605+
return true
606+
case (nil, _?):
607+
return existing.authState == .needsReauth && tokensChanged
608+
case (nil, nil):
609+
return existing.authState == .needsReauth && tokensChanged
610+
}
563611
}
564612

565613
// MARK: - Untracked Account Detection

0 commit comments

Comments
 (0)