Skip to content
8 changes: 6 additions & 2 deletions Sources/CodexBar/SettingsStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
}
Expand All @@ -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
}
}

Expand Down Expand Up @@ -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]
}
Expand Down
19 changes: 19 additions & 0 deletions Tests/CodexBarTests/SettingsStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down