diff --git a/Sources/CodexBar/SettingsStore.swift b/Sources/CodexBar/SettingsStore.swift index 09f3e3caa..c99c3a5a2 100644 --- a/Sources/CodexBar/SettingsStore.swift +++ b/Sources/CodexBar/SettingsStore.swift @@ -157,8 +157,12 @@ final class SettingsStore { extension SettingsStore { private static func loadDefaultsState(userDefaults: UserDefaults) -> SettingsDefaultsState { - let refreshRaw = userDefaults.string(forKey: "refreshFrequency") ?? RefreshFrequency.fiveMinutes.rawValue - let refreshFrequency = RefreshFrequency(rawValue: refreshRaw) ?? .fiveMinutes + let refreshDefault = userDefaults.string(forKey: "refreshFrequency") + .flatMap(RefreshFrequency.init(rawValue:)) + let refreshFrequency = refreshDefault ?? .fiveMinutes + if refreshDefault == nil { + userDefaults.set(refreshFrequency.rawValue, forKey: "refreshFrequency") + } let launchAtLogin = userDefaults.object(forKey: "launchAtLogin") as? Bool ?? false let debugMenuEnabled = userDefaults.object(forKey: "debugMenuEnabled") as? Bool ?? false let debugDisableKeychainAccess: Bool = { diff --git a/Sources/CodexBarCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift b/Sources/CodexBarCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift index 8f211c5c3..a6761ff75 100644 --- a/Sources/CodexBarCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift +++ b/Sources/CodexBarCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift @@ -267,66 +267,27 @@ public struct OpenAIDashboardBrowserCookieImporter { } } - private func tryChrome( + /// Generic cookie loader for any non-Safari browser (Chrome, Edge, Firefox, Brave, Arc, etc.). + /// SweetCookieKit handles engine-specific decryption internally. + private func tryBrowser( + _ browser: Browser, targetEmail: String?, allowAnyAccount: Bool, log: @escaping (String) -> Void, diagnostics: inout ImportDiagnostics) async -> ImportResult? { - // Chrome fallback: may trigger Keychain prompt. Only do this if Safari didn't match. do { let query = BrowserCookieQuery(domains: Self.cookieDomains) - let chromeSources = try Self.cookieClient.records( + let sources = try Self.cookieClient.records( matching: query, - in: .chrome) - for source in chromeSources { - let cookies = BrowserCookieClient.makeHTTPCookies(source.records, origin: query.origin) - if cookies.isEmpty { - log("\(source.label) produced 0 HTTPCookies.") - continue - } - diagnostics.foundAnyCookies = true - log("Loaded \(cookies.count) cookies from \(source.label) (\(self.cookieSummary(cookies)))") - let candidate = Candidate(label: source.label, cookies: cookies) - if let match = await self.applyCandidate( - candidate, - targetEmail: targetEmail, - allowAnyAccount: allowAnyAccount, - log: log, - diagnostics: &diagnostics) - { - return match - } - } - return nil - } catch let error as BrowserCookieError { - BrowserCookieAccessGate.recordIfNeeded(error) - if let hint = error.accessDeniedHint { - diagnostics.accessDeniedHints.append(hint) + in: browser) + guard !sources.isEmpty else { + log("\(browser.displayName) contained 0 matching records.") + return nil } - log("Chrome cookie load failed: \(error.localizedDescription)") - return nil - } catch { - log("Chrome cookie load failed: \(error.localizedDescription)") - return nil - } - } - - private func tryFirefox( - targetEmail: String?, - allowAnyAccount: Bool, - log: @escaping (String) -> Void, - diagnostics: inout ImportDiagnostics) async -> ImportResult? - { - // Firefox fallback: no Keychain, but still only after Safari/Chrome. - do { - let query = BrowserCookieQuery(domains: Self.cookieDomains) - let firefoxSources = try Self.cookieClient.records( - matching: query, - in: .firefox) - for source in firefoxSources { + for source in sources { let cookies = BrowserCookieClient.makeHTTPCookies(source.records, origin: query.origin) - if cookies.isEmpty { + guard !cookies.isEmpty else { log("\(source.label) produced 0 HTTPCookies.") continue } @@ -349,10 +310,10 @@ public struct OpenAIDashboardBrowserCookieImporter { if let hint = error.accessDeniedHint { diagnostics.accessDeniedHints.append(hint) } - log("Firefox cookie load failed: \(error.localizedDescription)") + log("\(browser.displayName) cookie load failed: \(error.localizedDescription)") return nil } catch { - log("Firefox cookie load failed: \(error.localizedDescription)") + log("\(browser.displayName) cookie load failed: \(error.localizedDescription)") return nil } } @@ -371,20 +332,15 @@ public struct OpenAIDashboardBrowserCookieImporter { allowAnyAccount: allowAnyAccount, log: log, diagnostics: &diagnostics) - case .chrome: - await self.tryChrome( - targetEmail: targetEmail, - allowAnyAccount: allowAnyAccount, - log: log, - diagnostics: &diagnostics) - case .firefox: - await self.tryFirefox( + default: + // All non-Safari browsers (Chrome, Edge, Firefox, Brave, Arc, etc.) + // share the same cookie loading path via SweetCookieKit. + await self.tryBrowser( + source, targetEmail: targetEmail, allowAnyAccount: allowAnyAccount, log: log, diagnostics: &diagnostics) - default: - nil } } @@ -679,7 +635,7 @@ public struct OpenAIDashboardBrowserCookieImporter { cookies.map { "\($0.name)=\($0.value)" }.joined(separator: "; ") } - private struct Candidate: Sendable { + private struct Candidate { let label: String let cookies: [HTTPCookie] } diff --git a/Tests/CodexBarTests/SettingsStoreTests.swift b/Tests/CodexBarTests/SettingsStoreTests.swift index 04ca55516..989393e53 100644 --- a/Tests/CodexBarTests/SettingsStoreTests.swift +++ b/Tests/CodexBarTests/SettingsStoreTests.swift @@ -22,6 +22,25 @@ struct SettingsStoreTests { #expect(store.refreshFrequency == .fiveMinutes) #expect(store.refreshFrequency.seconds == 300) + #expect(defaults.string(forKey: "refreshFrequency") == RefreshFrequency.fiveMinutes.rawValue) + } + + @Test + func repairsUnrecognizedRefreshFrequencyRawValue() throws { + let suite = "SettingsStoreTests-invalid-refresh" + let defaults = try #require(UserDefaults(suiteName: suite)) + defaults.removePersistentDomain(forName: suite) + defaults.set("legacyValue", forKey: "refreshFrequency") + let configStore = testConfigStore(suiteName: suite) + + let store = SettingsStore( + userDefaults: defaults, + configStore: configStore, + zaiTokenStore: NoopZaiTokenStore(), + syntheticTokenStore: NoopSyntheticTokenStore()) + + #expect(store.refreshFrequency == .fiveMinutes) + #expect(defaults.string(forKey: "refreshFrequency") == RefreshFrequency.fiveMinutes.rawValue) } @Test