1230 lines · 53.3 KB · Swift
RawDownload
import CodexBarCoreimport Foundation struct OpenAIWebRefreshGateContext {    let force: Bool    let accountDidChange: Bool    let lastError: String?    let lastSnapshotAt: Date?    let lastAttemptAt: Date?    let now: Date    let refreshInterval: TimeInterval} struct OpenAIWebRefreshPolicyContext {    let accessEnabled: Bool    let batterySaverEnabled: Bool    let force: Bool} // MARK: - OpenAI web lifecycle extension UsageStore {    private struct OpenAIDashboardRefreshContext {        let targetEmail: String?        let allowCurrentSnapshotFallback: Bool        let expectedGuard: CodexAccountScopedRefreshGuard?        let refreshTaskToken: UUID        let allowCodexUsageBackfill: Bool    }     private static let openAIWebRefreshMultiplier: TimeInterval = 5    private static let openAIWebPrimaryFetchTimeout: TimeInterval = 15    private static let openAIWebRetryFetchTimeout: TimeInterval = 8    private static let openAIWebPostImportFetchTimeout: TimeInterval = 25     static func openAIWebDashboardFetchTimeout(didImportCookies: Bool) -> TimeInterval {        didImportCookies ? self.openAIWebPostImportFetchTimeout : self.openAIWebPrimaryFetchTimeout    }     static func openAIWebRetryDashboardFetchTimeout(afterCookieImport: Bool) -> TimeInterval {        afterCookieImport ? self.openAIWebPostImportFetchTimeout : self.openAIWebRetryFetchTimeout    }     private func openAIWebRefreshIntervalSeconds() -> TimeInterval {        let base = max(self.settings.refreshFrequency.seconds ?? 0, 120)        return base * Self.openAIWebRefreshMultiplier    }     func requestOpenAIDashboardRefreshIfStale(reason: String) {        guard self.isEnabled(.codex),              self.settings.openAIWebAccessEnabled,              self.settings.codexCookieSource.isEnabled        else { return }        let now = Date()        let refreshInterval = self.openAIWebRefreshIntervalSeconds()        let lastUpdatedAt = self.openAIDashboard?.updatedAt ?? self.lastOpenAIDashboardSnapshot?.updatedAt        if let lastUpdatedAt, now.timeIntervalSince(lastUpdatedAt) < refreshInterval { return }        let stamp = now.formatted(date: .abbreviated, time: .shortened)        self.logOpenAIWeb("[\(stamp)] OpenAI web refresh request: \(reason)")        let forceRefresh = Self.forceOpenAIWebRefreshForStaleRequest(            batterySaverEnabled: self.settings.openAIWebBatterySaverEnabled)        self.openAIWebLogger.debug(            "OpenAI web stale refresh gate",            metadata: [                "reason": reason,                "force": forceRefresh ? "1" : "0",                "batterySaverEnabled": self.settings.openAIWebBatterySaverEnabled ? "1" : "0",                "interaction": ProviderInteractionContext.current == .userInitiated ? "user" : "background",            ])        let expectedGuard = self.currentCodexOpenAIWebRefreshGuard()        Task { await self.refreshOpenAIDashboardIfNeeded(force: forceRefresh, expectedGuard: expectedGuard) }    }     func applyOpenAIDashboard(        _ dash: OpenAIDashboardSnapshot,        targetEmail: String?,        expectedGuard: CodexAccountScopedRefreshGuard? = nil,        refreshTaskToken: UUID? = nil,        allowCodexUsageBackfill: Bool = true) async    {        guard self.shouldApplyOpenAIDashboardRefreshTask(token: refreshTaskToken) else { return }        if let expectedGuard,           !self.shouldApplyOpenAIDashboardRefreshGuard(               expectedGuard: expectedGuard,               routingTargetEmail: targetEmail)        {            return        }         let authority = self.evaluateCodexDashboardAuthority(            dashboard: dash,            sourceKind: .liveWeb,            routingTargetEmail: targetEmail)        let attachedAccountEmail = self.codexDashboardAttachmentEmail(from: authority.input)         await self.applyOpenAIDashboardAuthorityDecision(            authority.decision,            dashboard: dash,            authorityInput: authority.input,            attachedAccountEmail: attachedAccountEmail,            allowCodexUsageBackfill: allowCodexUsageBackfill)    }     func applyOpenAIDashboardFailure(        message: String,        expectedGuard: CodexAccountScopedRefreshGuard? = nil,        refreshTaskToken: UUID? = nil,        routingTargetEmail: String? = nil) async    {        guard self.shouldApplyOpenAIDashboardRefreshTask(token: refreshTaskToken) else { return }        if let expectedGuard,           !self.shouldApplyOpenAIWebNonSuccessResult(               expectedGuard: expectedGuard,               routingTargetEmail: routingTargetEmail)        {            return        }        if self.openAIWebManagedTargetStoreIsUnreadable() {            await self.failClosedRefreshForUnreadableManagedCodexStore()            return        }        if self.openAIWebManagedTargetIsMissing() {            await self.failClosedRefreshForMissingManagedCodexTarget()            return        }         OpenAIDashboardFetcher.evictAllCachedWebViews()        await MainActor.run {            if let cached = self.lastOpenAIDashboardSnapshot {                self.openAIDashboard = cached                self.openAIDashboardAttachmentAuthorized = self.lastOpenAIDashboardAttachmentAuthorized                let stamp = cached.updatedAt.formatted(date: .abbreviated, time: .shortened)                self.lastOpenAIDashboardError =                    "Last OpenAI dashboard refresh failed: \(message). Cached values from \(stamp)."            } else {                self.lastOpenAIDashboardError = message                self.openAIDashboard = nil                self.openAIDashboardAttachmentAuthorized = false            }        }    }     func applyOpenAIDashboardLoginRequiredFailure(        expectedGuard: CodexAccountScopedRefreshGuard? = nil,        refreshTaskToken: UUID? = nil,        routingTargetEmail: String? = nil) async    {        guard self.shouldApplyOpenAIDashboardRefreshTask(token: refreshTaskToken) else { return }        if let expectedGuard,           !self.shouldApplyOpenAIWebNonSuccessResult(               expectedGuard: expectedGuard,               routingTargetEmail: routingTargetEmail)        {            return        }        if self.openAIWebManagedTargetStoreIsUnreadable() {            await self.failClosedRefreshForUnreadableManagedCodexStore()            return        }        if self.openAIWebManagedTargetIsMissing() {            await self.failClosedRefreshForMissingManagedCodexTarget()            return        }         OpenAIDashboardFetcher.evictAllCachedWebViews()        await MainActor.run {            self.lastOpenAIDashboardError = [                "OpenAI web access requires a signed-in chatgpt.com session.",                "Sign in using \(self.codexBrowserCookieOrder.loginHint), " +                    "then update OpenAI cookies in Providers → Codex.",            ].joined(separator: " ")            self.openAIDashboard = self.lastOpenAIDashboardSnapshot            self.openAIDashboardAttachmentAuthorized = self.lastOpenAIDashboardAttachmentAuthorized            self.openAIDashboardRequiresLogin = true        }    }     private func failClosedOpenAIDashboardSnapshot() {        self.applyOpenAIDashboardCleanup(Set(CodexDashboardCleanup.allCases), preserveVisibleDashboard: false)        self.openAIDashboardRequiresLogin = true    }     private func applyOpenAIDashboardAuthorityDecision(        _ decision: CodexDashboardAuthorityDecision,        dashboard: OpenAIDashboardSnapshot,        authorityInput: CodexDashboardAuthorityInput,        attachedAccountEmail: String?,        allowCodexUsageBackfill: Bool) async    {        switch decision.disposition {        case .attach:            self.openAIDashboard = dashboard            self.openAIDashboardAttachmentAuthorized = true            self.lastOpenAIDashboardSnapshot = dashboard            self.lastOpenAIDashboardAttachmentAuthorized = true            self.lastOpenAIDashboardError = nil            self.openAIDashboardRequiresLogin = false             if decision.allowedEffects.contains(.usageBackfill),               allowCodexUsageBackfill,               self.snapshots[.codex] == nil,               let usage = dashboard.toUsageSnapshot(provider: .codex, accountEmail: attachedAccountEmail)            {                self.snapshots[.codex] = usage                self.errors[.codex] = nil                self.failureGates[.codex]?.recordSuccess()                self.lastSourceLabels[.codex] = "openai-web"            }             if decision.allowedEffects.contains(.creditsAttachment),               self.credits == nil,               let credits = dashboard.toCreditsSnapshot()            {                self.credits = credits                self.lastCreditsSnapshot = credits                self.lastCreditsSnapshotAccountKey = Self.normalizeCodexAccountScopedKey(attachedAccountEmail)                self.lastCreditsSource = .dashboardWeb                self.lastCreditsError = nil                self.creditsFailureStreak = 0            }             if decision.allowedEffects.contains(.refreshGuardSeed) {                self.seedCodexAccountScopedRefreshGuard(accountEmail: attachedAccountEmail)            }             if let attachedAccountEmail, !attachedAccountEmail.isEmpty {                OpenAIDashboardCacheStore.save(OpenAIDashboardCache(                    accountEmail: attachedAccountEmail,                    snapshot: dashboard))            }             if decision.allowedEffects.contains(.historicalBackfill) {                self.backfillCodexHistoricalFromDashboardIfNeeded(                    dashboard,                    authorityDecision: decision,                    attachedAccountEmail: attachedAccountEmail)            }         case .displayOnly:            self.applyOpenAIDashboardCleanup(decision.cleanup, preserveVisibleDashboard: true)            self.openAIDashboard = dashboard            self.openAIDashboardAttachmentAuthorized = false            self.lastOpenAIDashboardSnapshot = dashboard            self.lastOpenAIDashboardAttachmentAuthorized = false            self.lastOpenAIDashboardError = nil            self.openAIDashboardRequiresLogin = false         case .failClosed:            self.applyOpenAIDashboardCleanup(decision.cleanup, preserveVisibleDashboard: false)            self.lastOpenAIDashboardError = self.openAIDashboardPolicyFailureMessage(                for: decision,                authorityInput: authorityInput)            self.openAIDashboardRequiresLogin = true        }    }     private func applyOpenAIDashboardCleanup(        _ cleanup: Set<CodexDashboardCleanup>,        preserveVisibleDashboard: Bool)    {        if cleanup.contains(.dashboardDerivedUsage) {            self.clearDashboardDerivedCodexUsageIfNeeded()        }        if cleanup.contains(.dashboardDerivedCredits) {            self.clearDashboardDerivedCreditsIfNeeded()        }        if cleanup.contains(.dashboardRefreshGuardSeed) {            self.clearDashboardRefreshGuardSeedIfNeeded()        }        if cleanup.contains(.dashboardCache) {            OpenAIDashboardCacheStore.clear()        }        if cleanup.contains(.dashboardSnapshot), !preserveVisibleDashboard {            self.openAIDashboard = nil            self.openAIDashboardAttachmentAuthorized = false            self.lastOpenAIDashboardSnapshot = nil            self.lastOpenAIDashboardAttachmentAuthorized = false        }    }     private func clearDashboardDerivedCodexUsageIfNeeded() {        guard self.lastSourceLabels[.codex] == "openai-web" else { return }        self.snapshots.removeValue(forKey: .codex)        self.errors[.codex] = nil        self.lastSourceLabels.removeValue(forKey: .codex)        self.lastFetchAttempts.removeValue(forKey: .codex)        self.accountSnapshots.removeValue(forKey: .codex)        self.failureGates[.codex]?.reset()        self.lastKnownSessionRemaining.removeValue(forKey: .codex)        self.lastKnownSessionWindowSource.removeValue(forKey: .codex)    }     private func clearDashboardDerivedCreditsIfNeeded() {        guard self.lastCreditsSource == .dashboardWeb else { return }        self.credits = nil        self.lastCreditsError = nil        self.lastCreditsSnapshot = nil        self.lastCreditsSnapshotAccountKey = nil        self.lastCreditsSource = .none        self.creditsFailureStreak = 0    }     private func clearDashboardRefreshGuardSeedIfNeeded() {        self.lastCodexAccountScopedRefreshGuard = self.currentCodexAccountScopedRefreshGuard(            preferCurrentSnapshot: false,            allowLastKnownLiveFallback: false)    }     private func openAIDashboardPolicyFailureMessage(        for decision: CodexDashboardAuthorityDecision,        authorityInput: CodexDashboardAuthorityInput) -> String    {        switch decision.reason {        case let .wrongEmail(expected, actual):            [                "OpenAI dashboard signed in as \(actual ?? "unknown"), but Codex uses \(expected ?? "unknown").",                "Switch accounts in your browser and update OpenAI cookies in Providers → Codex.",            ].joined(separator: " ")        case let .sameEmailAmbiguity(email):            "OpenAI dashboard ownership is ambiguous for \(email); Codex will not attach dashboard data."        case .missingDashboardSignedInEmail:            "OpenAI dashboard did not report a signed-in account. Refresh OpenAI cookies and try again."        case .unresolvedWithoutTrustedEvidence:            "OpenAI dashboard ownership could not be verified for the active Codex account."        case .providerAccountMissingScopedEmail:            "Codex account ownership could not be verified because the scoped email is unavailable."        case .providerAccountLacksExactOwnershipProof:            [                "OpenAI dashboard ownership could not be matched to the active Codex account.",                "Refresh Codex account data, then retry OpenAI web access.",            ].joined(separator: " ")        case .exactProviderAccountMatch,             .trustedEmailMatchNoCompetingOwner,             .trustedContinuityNoCompetingOwner:            "OpenAI dashboard ownership policy blocked this dashboard."        }    }     func refreshOpenAIDashboardIfNeeded(        force: Bool = false,        expectedGuard: CodexAccountScopedRefreshGuard? = nil,        bypassCoalescing: Bool = false,        allowCodexUsageBackfill: Bool = true) async    {        self.syncOpenAIWebState()        guard self.isEnabled(.codex),              self.settings.openAIWebAccessEnabled,              self.settings.codexCookieSource.isEnabled        else { return }        if self.openAIWebManagedTargetStoreIsUnreadable() {            await self.failClosedRefreshForUnreadableManagedCodexStore()            return        }        if self.openAIWebManagedTargetIsMissing() {            await self.failClosedRefreshForMissingManagedCodexTarget()            return        }         let allowCurrentSnapshotFallback = expectedGuard?.source == .liveSystem && expectedGuard?            .identity == .unresolved        let targetEmail = self.currentCodexOpenAIWebTargetEmail(            allowCurrentSnapshotFallback: allowCurrentSnapshotFallback,            allowLastKnownLiveFallback: expectedGuard?.identity != .unresolved)        let refreshKey = self.openAIDashboardRefreshKey(targetEmail: targetEmail, expectedGuard: expectedGuard)        if !bypassCoalescing,           let task = self.openAIDashboardRefreshTask,           self.openAIDashboardRefreshTaskKey == refreshKey        {            await task.value            return        }        self.handleOpenAIWebTargetEmailChangeIfNeeded(targetEmail: targetEmail)         let now = Date()        let minInterval = self.openAIWebRefreshIntervalSeconds()        let refreshGate = OpenAIWebRefreshGateContext(            force: force,            accountDidChange: self.openAIWebAccountDidChange,            lastError: self.lastOpenAIDashboardError,            lastSnapshotAt: self.lastOpenAIDashboardSnapshot?.updatedAt,            lastAttemptAt: self.lastOpenAIDashboardAttemptAt,            now: now,            refreshInterval: minInterval)        if Self.shouldSkipOpenAIWebRefresh(refreshGate) {            return        }        self.lastOpenAIDashboardAttemptAt = now         let taskToken = UUID()        let context = OpenAIDashboardRefreshContext(            targetEmail: targetEmail,            allowCurrentSnapshotFallback: allowCurrentSnapshotFallback,            expectedGuard: expectedGuard,            refreshTaskToken: taskToken,            allowCodexUsageBackfill: allowCodexUsageBackfill)        let task = Task { [weak self] in            guard let self else { return }            await self.performOpenAIDashboardRefreshIfNeeded(context)        }        self.openAIDashboardRefreshTask = task        self.openAIDashboardRefreshTaskKey = refreshKey        self.openAIDashboardRefreshTaskToken = taskToken        await task.value        if self.openAIDashboardRefreshTaskToken == taskToken {            self.openAIDashboardRefreshTask = nil            self.openAIDashboardRefreshTaskKey = nil            self.openAIDashboardRefreshTaskToken = nil        }    }     private func performOpenAIDashboardRefreshIfNeeded(_ context: OpenAIDashboardRefreshContext) async {        self.openAIDashboardCookieImportStatus = nil        var latestCookieImportStatus: String?        if self.openAIWebDebugLines.isEmpty {            self.resetOpenAIWebDebugLog(context: "refresh")        } else {            let stamp = Date().formatted(date: .abbreviated, time: .shortened)            self.logOpenAIWeb("[\(stamp)] OpenAI web refresh start")        }        let log: (String) -> Void = { [weak self] line in            guard let self else { return }            self.logOpenAIWeb(line)        }         do {            let normalized = context.targetEmail?                .trimmingCharacters(in: .whitespacesAndNewlines)                .lowercased()            var effectiveEmail = context.targetEmail             // Use a per-email persistent `WKWebsiteDataStore` so multiple dashboard sessions can coexist.            // Strategy:            // - Try the existing per-email WebKit cookie store first (fast; avoids Keychain prompts).            // - On login-required or account mismatch, import cookies from the configured browser order and retry once.            var didImportCookiesForRefresh = false            if self.openAIWebAccountDidChange, let targetEmail = context.targetEmail, !targetEmail.isEmpty {                // On account switches, proactively re-import cookies so we don't show stale data from the previous                // user.                let imported = await self.importOpenAIDashboardCookiesIfNeeded(                    targetEmail: targetEmail,                    force: true)                didImportCookiesForRefresh = true                latestCookieImportStatus = self.currentOpenAIDashboardCookieImportStatus()                if await self.abortOpenAIDashboardRetryAfterImportFailure(                    importedEmail: imported,                    targetEmail: targetEmail,                    expectedGuard: context.expectedGuard,                    cookieImportStatus: latestCookieImportStatus,                    refreshTaskToken: context.refreshTaskToken)                {                    self.openAIWebAccountDidChange = false                    return                }                if let imported {                    effectiveEmail = imported                }                self.openAIWebAccountDidChange = false            }             var dash = try await self.loadLatestOpenAIDashboard(                accountEmail: effectiveEmail,                logger: log,                timeout: Self.openAIWebDashboardFetchTimeout(didImportCookies: didImportCookiesForRefresh))             if self.dashboardEmailMismatch(expected: normalized, actual: dash.signedInEmail) {                if let imported = await self.importOpenAIDashboardCookiesIfNeeded(                    targetEmail: context.targetEmail,                    force: true)                {                    effectiveEmail = imported                }                latestCookieImportStatus = self.currentOpenAIDashboardCookieImportStatus()                dash = try await self.loadLatestOpenAIDashboard(                    accountEmail: effectiveEmail,                    logger: log,                    timeout: Self.openAIWebRetryDashboardFetchTimeout(afterCookieImport: true))            }             await self.applyOpenAIDashboard(                dash,                targetEmail: effectiveEmail,                expectedGuard: context.expectedGuard,                refreshTaskToken: context.refreshTaskToken,                allowCodexUsageBackfill: context.allowCodexUsageBackfill)        } catch let OpenAIDashboardFetcher.FetchError.noDashboardData(body) {            await self.retryOpenAIDashboardAfterNoData(                body: body,                context: context,                latestCookieImportStatus: &latestCookieImportStatus,                logger: log)        } catch OpenAIDashboardFetcher.FetchError.loginRequired {            await self.retryOpenAIDashboardAfterLoginRequired(                context: context,                latestCookieImportStatus: &latestCookieImportStatus,                logger: log)        } catch {            let message = self.preferredOpenAIDashboardFailureMessage(                error: error,                targetEmail: context.targetEmail,                cookieImportStatus: latestCookieImportStatus)

Lines 1–500 of 1230.