diff --git a/CHANGELOG.md b/CHANGELOG.md index c8a853b18a..99c139e1fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Antigravity: exclude model quotas without a remaining fraction from family summaries so they no longer mask tracked usage in the automatic menu-bar metric (#1369). Thanks @Martin-Hausleitner! - Claude: add bundled Fable 5 pricing, account for native 1-hour cache-write usage, and refresh Sonnet 4.6 full-context rates (#1368). Thanks @MoollaMore! - Menu bar: defer data-refresh rebuilds until the tracked menu closes, avoiding multi-second WindowServer stalls with slower providers such as Grok (#1376). Thanks @jangisaac-dev! +- Xiaomi MiMo: import automatic session cookies from Safari, Chrome variants, Firefox, and Edge instead of limiting discovery to Chrome (#1304). Thanks @Yuxin-Qiao! ## 0.32.5 — 2026-06-09 diff --git a/Sources/CodexBar/Providers/MiMo/MiMoProviderImplementation.swift b/Sources/CodexBar/Providers/MiMo/MiMoProviderImplementation.swift index bcb9b68cfb..361f2d519e 100644 --- a/Sources/CodexBar/Providers/MiMo/MiMoProviderImplementation.swift +++ b/Sources/CodexBar/Providers/MiMo/MiMoProviderImplementation.swift @@ -39,7 +39,7 @@ struct MiMoProviderImplementation: ProviderImplementation { ProviderCookieSourceUI.subtitle( source: context.settings.miMoCookieSource, keychainDisabled: context.settings.debugDisableKeychainAccess, - auto: "Automatic imports Chrome browser cookies from Xiaomi MiMo.", + auto: "Automatic imports browser cookies from Xiaomi MiMo.", manual: "Paste a Cookie header from platform.xiaomimimo.com.", off: "Xiaomi MiMo cookies are disabled.") } @@ -48,7 +48,7 @@ struct MiMoProviderImplementation: ProviderImplementation { ProviderSettingsPickerDescriptor( id: "mimo-cookie-source", title: "Cookie source", - subtitle: "Automatic imports Chrome browser cookies from Xiaomi MiMo.", + subtitle: "Automatic imports browser cookies from Xiaomi MiMo.", dynamicSubtitle: cookieSubtitle, binding: cookieBinding, options: cookieOptions, diff --git a/Sources/CodexBar/Resources/ca.lproj/Localizable.strings b/Sources/CodexBar/Resources/ca.lproj/Localizable.strings index 4004139a56..2fe1073332 100644 --- a/Sources/CodexBar/Resources/ca.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/ca.lproj/Localizable.strings @@ -765,7 +765,7 @@ "Antigravity login failed" = "L'inici de sessió d'Antigravity ha fallat"; "Antigravity login timed out" = "L'inici de sessió d'Antigravity ha esgotat el temps"; "Auth source" = "Font d'autenticació"; -"Automatic imports Chrome browser cookies from Xiaomi MiMo." = "Importa automàticament les galetes de Chrome de Xiaomi MiMo."; +"Automatic imports browser cookies from Xiaomi MiMo." = "Importa automàticament les galetes del navegador de Xiaomi MiMo."; "Automatic imports Windsurf session data from Chromium browser localStorage." = "Importa automàticament dades de sessió de Windsurf del localStorage de Chromium."; "Automatic imports browser cookies from Bailian." = "Importa automàticament galetes del navegador de Bailian."; "Automatically imports browser cookies." = "Importa automàticament galetes del navegador."; diff --git a/Sources/CodexBar/Resources/en.lproj/Localizable.strings b/Sources/CodexBar/Resources/en.lproj/Localizable.strings index 5fada87c87..114cfdf983 100644 --- a/Sources/CodexBar/Resources/en.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/en.lproj/Localizable.strings @@ -908,7 +908,7 @@ "Antigravity login failed" = "Antigravity login failed"; "Antigravity login timed out" = "Antigravity login timed out"; "Auth source" = "Auth source"; -"Automatic imports Chrome browser cookies from Xiaomi MiMo." = "Automatic imports Chrome browser cookies from Xiaomi MiMo."; +"Automatic imports browser cookies from Xiaomi MiMo." = "Automatic imports browser cookies from Xiaomi MiMo."; "Automatic imports Windsurf session data from Chromium browser localStorage." = "Automatic imports Windsurf session data from Chromium browser localStorage."; "Automatic imports browser cookies from Bailian." = "Automatic imports browser cookies from Bailian."; "Automatically imports browser cookies." = "Automatically imports browser cookies."; diff --git a/Sources/CodexBar/Resources/es.lproj/Localizable.strings b/Sources/CodexBar/Resources/es.lproj/Localizable.strings index 8da82dde50..308756f6f1 100644 --- a/Sources/CodexBar/Resources/es.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/es.lproj/Localizable.strings @@ -765,7 +765,7 @@ "Antigravity login failed" = "Error al iniciar sesión en Antigravity"; "Antigravity login timed out" = "El inicio de sesión en Antigravity agotó el tiempo"; "Auth source" = "Fuente de autenticación"; -"Automatic imports Chrome browser cookies from Xiaomi MiMo." = "Importa automáticamente las cookies de Chrome desde Xiaomi MiMo."; +"Automatic imports browser cookies from Xiaomi MiMo." = "Importa automáticamente las cookies del navegador desde Xiaomi MiMo."; "Automatic imports Windsurf session data from Chromium browser localStorage." = "Importa automáticamente datos de sesión de Windsurf desde localStorage de Chromium."; "Automatic imports browser cookies from Bailian." = "Importa automáticamente cookies del navegador desde Bailian."; "Automatically imports browser cookies." = "Importa automáticamente cookies del navegador."; diff --git a/Sources/CodexBar/Resources/fr.lproj/Localizable.strings b/Sources/CodexBar/Resources/fr.lproj/Localizable.strings index 8932f84a78..4baa281220 100644 --- a/Sources/CodexBar/Resources/fr.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/fr.lproj/Localizable.strings @@ -909,7 +909,7 @@ "Antigravity login failed" = "La connexion antigravité a échoué"; "Antigravity login timed out" = "La connexion antigravité a expiré"; "Auth source" = "Source d'authentification"; -"Automatic imports Chrome browser cookies from Xiaomi MiMo." = "Importe automatiquement les cookies du navigateur Chrome de Xiaomi MiMo."; +"Automatic imports browser cookies from Xiaomi MiMo." = "Importe automatiquement les cookies du navigateur de Xiaomi MiMo."; "Automatic imports Windsurf session data from Chromium browser localStorage." = "Importe automatiquement les données de session Windsurf à partir du navigateur Chromium localStorage."; "Automatic imports browser cookies from Bailian." = "Importe automatiquement les cookies du navigateur depuis Bailian."; "Automatically imports browser cookies." = "Importe automatiquement les cookies du navigateur."; diff --git a/Sources/CodexBar/Resources/nl.lproj/Localizable.strings b/Sources/CodexBar/Resources/nl.lproj/Localizable.strings index 6034738e25..2d5a860fe4 100644 --- a/Sources/CodexBar/Resources/nl.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/nl.lproj/Localizable.strings @@ -909,7 +909,7 @@ "Antigravity login failed" = "Antigravity-aanmelding mislukt"; "Antigravity login timed out" = "Er is een time-out opgetreden bij het inloggen op anti-zwaartekracht"; "Auth source" = "Authenticatiebron"; -"Automatic imports Chrome browser cookies from Xiaomi MiMo." = "Importeert automatisch Chrome-browsercookies van Xiaomi MiMo."; +"Automatic imports browser cookies from Xiaomi MiMo." = "Importeert automatisch browsercookies van Xiaomi MiMo."; "Automatic imports Windsurf session data from Chromium browser localStorage." = "Automatische import van windsurfsessiegegevens uit de Chromium-browser localStorage."; "Automatic imports browser cookies from Bailian." = "Importeert automatisch browsercookies van Bailian."; "Automatically imports browser cookies." = "Importeert automatisch browsercookies."; diff --git a/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings b/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings index 93304a8945..f58ef465ab 100644 --- a/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings @@ -908,7 +908,7 @@ "Antigravity login failed" = "Falha no login do Antigravity"; "Antigravity login timed out" = "Tempo esgotado no login do Antigravity"; "Auth source" = "Fonte de autenticação"; -"Automatic imports Chrome browser cookies from Xiaomi MiMo." = "Importa automaticamente cookies do Chrome do Xiaomi MiMo."; +"Automatic imports browser cookies from Xiaomi MiMo." = "Importa automaticamente cookies do navegador do Xiaomi MiMo."; "Automatic imports Windsurf session data from Chromium browser localStorage." = "Importa automaticamente dados de sessão do Windsurf do localStorage do Chromium."; "Automatic imports browser cookies from Bailian." = "Importa automaticamente cookies do navegador do Bailian."; "Automatically imports browser cookies." = "Importa automaticamente cookies do navegador."; diff --git a/Sources/CodexBar/Resources/sv.lproj/Localizable.strings b/Sources/CodexBar/Resources/sv.lproj/Localizable.strings index b777ad418f..ca7e2295ac 100644 --- a/Sources/CodexBar/Resources/sv.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/sv.lproj/Localizable.strings @@ -1045,7 +1045,7 @@ "stale data" = "inaktuella data"; "Quota" = "Kvot"; "Auth source" = "Autentiseringskälla"; -"Automatic imports Chrome browser cookies from Xiaomi MiMo." = "Importerar Chrome-cookies från Xiaomi MiMo automatiskt."; +"Automatic imports browser cookies from Xiaomi MiMo." = "Importerar webbläsarcookies från Xiaomi MiMo automatiskt."; "No usage configured." = "Ingen användning konfigurerad."; "Extra usage" = "Extra användning"; "That account is no longer available in CodexBar. Refresh the account list and try again." = "Kontot finns inte längre i CodexBar. Uppdatera kontolistan och försök igen."; diff --git a/Sources/CodexBar/Resources/uk.lproj/Localizable.strings b/Sources/CodexBar/Resources/uk.lproj/Localizable.strings index fbcadb84a4..84322b580e 100644 --- a/Sources/CodexBar/Resources/uk.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/uk.lproj/Localizable.strings @@ -909,7 +909,7 @@ "Antigravity login failed" = "Помилка входу в Antigravity"; "Antigravity login timed out" = "Час очікування входу в антигравітацію минув"; "Auth source" = "Джерело авторизації"; -"Automatic imports Chrome browser cookies from Xiaomi MiMo." = "Автоматично імпортує файли cookie браузера Chrome із Xiaomi MiMo."; +"Automatic imports browser cookies from Xiaomi MiMo." = "Автоматично імпортує файли cookie браузера з Xiaomi MiMo."; "Automatic imports Windsurf session data from Chromium browser localStorage." = "Автоматично імпортує дані сесії Windsurf із локального сховища браузера Chromium."; "Automatic imports browser cookies from Bailian." = "Автоматично імпортує файли cookie браузера з Bailian."; "Automatically imports browser cookies." = "Автоматично імпортує файли cookie браузера."; diff --git a/Sources/CodexBar/Resources/vi.lproj/Localizable.strings b/Sources/CodexBar/Resources/vi.lproj/Localizable.strings index 48749b29c8..fd11165123 100644 --- a/Sources/CodexBar/Resources/vi.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/vi.lproj/Localizable.strings @@ -908,7 +908,7 @@ "Antigravity login failed" = "Đăng nhập chống trọng lực không thành công"; "Antigravity login timed out" = "Đã hết thời gian đăng nhập chống trọng lực"; "Auth source" = "Nguồn xác thực"; -"Automatic imports Chrome browser cookies from Xiaomi MiMo." = "Tự động nhập cookie trình duyệt Chrome từ Xiaomi MiMo."; +"Automatic imports browser cookies from Xiaomi MiMo." = "Tự động nhập cookie trình duyệt từ Xiaomi MiMo."; "Automatic imports Windsurf session data from Chromium browser localStorage." = "Tự động nhập dữ liệu phiên Windsurf từ localStorage của trình duyệt Chrome."; "Automatic imports browser cookies from Bailian." = "Tự động nhập cookie trình duyệt từ Bailian."; "Automatically imports browser cookies." = "Tự động nhập cookie trình duyệt."; diff --git a/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings b/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings index c500c7149b..6b1c03a8e2 100644 --- a/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/zh-Hans.lproj/Localizable.strings @@ -882,7 +882,7 @@ "Antigravity login failed" = "Antigravity 登录失败"; "Antigravity login timed out" = "Antigravity 登录超时"; "Auth source" = "认证来源"; -"Automatic imports Chrome browser cookies from Xiaomi MiMo." = "自动导入 Xiaomi MiMo 的 Chrome 浏览器 Cookie。"; +"Automatic imports browser cookies from Xiaomi MiMo." = "自动导入 Xiaomi MiMo 的浏览器 Cookie。"; "Automatic imports Windsurf session data from Chromium browser localStorage." = "自动从 Chromium 浏览器 localStorage 导入 Windsurf 会话数据。"; "Automatic imports browser cookies from Bailian." = "自动导入 Bailian 的浏览器 Cookie。"; "Automatically imports browser cookies." = "自动导入浏览器 Cookie。"; diff --git a/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings b/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings index efe33c21d4..6b3589c5d0 100644 --- a/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings +++ b/Sources/CodexBar/Resources/zh-Hant.lproj/Localizable.strings @@ -778,7 +778,7 @@ "Antigravity login failed" = "Antigravity 登入失敗"; "Antigravity login timed out" = "Antigravity 登入逾時"; "Auth source" = "認證來源"; -"Automatic imports Chrome browser cookies from Xiaomi MiMo." = "自動匯入 Xiaomi MiMo 的 Chrome 瀏覽器 Cookie。"; +"Automatic imports browser cookies from Xiaomi MiMo." = "自動匯入 Xiaomi MiMo 的瀏覽器 Cookie。"; "Automatic imports Windsurf session data from Chromium browser localStorage." = "自動從 Chromium 瀏覽器 localStorage 匯入 Windsurf 工作階段資料。"; "Automatic imports browser cookies from Bailian." = "自動匯入 Bailian 的瀏覽器 Cookie。"; "Automatically imports browser cookies." = "自動匯入瀏覽器 Cookie。"; diff --git a/Sources/CodexBarCore/Providers/MiMo/MiMoCookieImporter.swift b/Sources/CodexBarCore/Providers/MiMo/MiMoCookieImporter.swift index 6cb2470af1..857fbe10db 100644 --- a/Sources/CodexBarCore/Providers/MiMo/MiMoCookieImporter.swift +++ b/Sources/CodexBarCore/Providers/MiMo/MiMoCookieImporter.swift @@ -129,8 +129,26 @@ public enum MiMoCookieImporter { return try override(browserDetection, logger) } + return try self.importSessions( + browserDetection: browserDetection, + logger: logger, + loadRecords: { browserSource, query, log in + try Self.cookieClient.records( + matching: query, + in: browserSource, + logger: log) + }) + } + + static func importSessions( + browserDetection: BrowserDetection, + logger: ((String) -> Void)? = nil, + loadRecords: (Browser, BrowserCookieQuery, ((String) -> Void)?) throws + -> [BrowserCookieStoreRecords]) throws -> [SessionInfo] + { let log: (String) -> Void = { msg in logger?("[mimo-cookie] \(msg)") } var sessions: [SessionInfo] = [] + var accessDeniedHints: [String] = [] let installed = miMoCookieImportOrder.cookieImportCandidates(using: browserDetection) let labels = installed.map(\.displayName).joined(separator: ", ") log("Cookie import candidates: \(labels)") @@ -138,17 +156,24 @@ public enum MiMoCookieImporter { for browserSource in installed { do { let query = BrowserCookieQuery(domains: self.cookieDomains) - let sources = try Self.cookieClient.records( - matching: query, - in: browserSource, - logger: log) + let sources = try loadRecords(browserSource, query, log) sessions.append(contentsOf: self.sessionInfos(from: sources, origin: query.origin)) + } catch let error as BrowserCookieError { + BrowserCookieAccessGate.recordIfNeeded(error) + if let hint = error.accessDeniedHint { + accessDeniedHints.append(hint) + } + log("\(browserSource.displayName) cookie import failed: \(error.localizedDescription)") } catch { BrowserCookieAccessGate.recordIfNeeded(error) log("\(browserSource.displayName) cookie import failed: \(error.localizedDescription)") } } + if sessions.isEmpty, !accessDeniedHints.isEmpty { + let details = Array(Set(accessDeniedHints)).sorted().joined(separator: " ") + throw MiMoSettingsError.missingCookie(details: details) + } return sessions } diff --git a/Sources/CodexBarCore/Providers/MiMo/MiMoProviderDescriptor.swift b/Sources/CodexBarCore/Providers/MiMo/MiMoProviderDescriptor.swift index 42e3eb6cc1..add4447b85 100644 --- a/Sources/CodexBarCore/Providers/MiMo/MiMoProviderDescriptor.swift +++ b/Sources/CodexBarCore/Providers/MiMo/MiMoProviderDescriptor.swift @@ -1,25 +1,11 @@ import CodexBarMacroSupport import Foundation -#if os(macOS) -import SweetCookieKit -#endif - @ProviderDescriptorRegistration @ProviderDescriptorDefinition public enum MiMoProviderDescriptor { static func makeDescriptor() -> ProviderDescriptor { - #if os(macOS) - let browserOrder: BrowserCookieImportOrder = [ - .chrome, - .chromeBeta, - .chromeCanary, - ] - #else - let browserOrder: BrowserCookieImportOrder? = nil - #endif - - return ProviderDescriptor( + ProviderDescriptor( id: .mimo, metadata: ProviderMetadata( id: .mimo, @@ -35,7 +21,7 @@ public enum MiMoProviderDescriptor { defaultEnabled: false, isPrimaryProvider: false, usesAccountFallback: false, - browserCookieOrder: browserOrder, + browserCookieOrder: ProviderBrowserCookieDefaults.mimoCookieImportOrder, dashboardURL: "https://platform.xiaomimimo.com/#/console/balance", statusPageURL: nil), branding: ProviderBranding( @@ -64,25 +50,13 @@ struct MiMoWebFetchStrategy: ProviderFetchStrategy { if context.settings?.mimo?.cookieSource == .manual { return Self.resolveManualCookieHeader(context: context) != nil } - if Self.resolveManualCookieHeader(context: context) != nil { - return true - } - - #if os(macOS) - if let cached = CookieHeaderCache.load(provider: .mimo), - MiMoCookieHeader.normalizedHeader(from: cached.cookieHeader) != nil - { - return true - } - return MiMoCookieImporter.hasSession(browserDetection: context.browserDetection) - #else - return false - #endif + // Fetch resolves the session so missing-cookie and browser-permission errors stay actionable. + return true } func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult { guard context.settings?.mimo?.cookieSource != .off else { - throw MiMoSettingsError.missingCookie + throw MiMoSettingsError.missingCookie() } if context.settings?.mimo?.cookieSource == .manual { guard let manualCookie = Self.resolveManualCookieHeader(context: context) else { @@ -123,7 +97,7 @@ struct MiMoWebFetchStrategy: ProviderFetchStrategy { let sessions = try MiMoCookieImporter.importSessions(browserDetection: context.browserDetection) guard !sessions.isEmpty else { if let lastError { throw lastError } - throw MiMoSettingsError.missingCookie + throw MiMoSettingsError.missingCookie() } for session in sessions { @@ -146,9 +120,9 @@ struct MiMoWebFetchStrategy: ProviderFetchStrategy { } if let lastError { throw lastError } - throw MiMoSettingsError.missingCookie + throw MiMoSettingsError.missingCookie() #else - throw MiMoSettingsError.missingCookie + throw MiMoSettingsError.missingCookie() #endif } diff --git a/Sources/CodexBarCore/Providers/MiMo/MiMoUsageFetcher.swift b/Sources/CodexBarCore/Providers/MiMo/MiMoUsageFetcher.swift index 7f443d587b..446e429109 100644 --- a/Sources/CodexBarCore/Providers/MiMo/MiMoUsageFetcher.swift +++ b/Sources/CodexBarCore/Providers/MiMo/MiMoUsageFetcher.swift @@ -3,14 +3,19 @@ import Foundation import FoundationNetworking #endif -public enum MiMoSettingsError: LocalizedError, Sendable { - case missingCookie +public enum MiMoSettingsError: LocalizedError, Sendable, Equatable { + case missingCookie(details: String? = nil) case invalidCookie public var errorDescription: String? { switch self { - case .missingCookie: - "No Xiaomi MiMo browser session found. Log in at platform.xiaomimimo.com first." + case let .missingCookie(details): + [ + "No Xiaomi MiMo browser session found. Log in at platform.xiaomimimo.com first.", + details, + ] + .compactMap(\.self) + .joined(separator: " ") case .invalidCookie: "Xiaomi MiMo requires the api-platform_serviceToken and userId cookies." } diff --git a/Sources/CodexBarCore/Providers/Providers.swift b/Sources/CodexBarCore/Providers/Providers.swift index 0727067ab5..aa8d0a3cd9 100644 --- a/Sources/CodexBarCore/Providers/Providers.swift +++ b/Sources/CodexBarCore/Providers/Providers.swift @@ -220,4 +220,15 @@ public enum ProviderBrowserCookieDefaults { nil #endif } + + /// MiMo Auto: Safari first (no Keychain prompt), keep the existing Chrome-family + /// entries from main, and add Firefox/Edge per #1304. Other Chromium forks stay on + /// Manual import to avoid scanning the full SweetCookieKit default order. + public static var mimoCookieImportOrder: BrowserCookieImportOrder? { + #if os(macOS) + [.safari, .chrome, .chromeBeta, .chromeCanary, .firefox, .edge] + #else + nil + #endif + } } diff --git a/Tests/CodexBarTests/BrowserCookieOrderLabelTests.swift b/Tests/CodexBarTests/BrowserCookieOrderLabelTests.swift index ac46fe826b..666007d033 100644 --- a/Tests/CodexBarTests/BrowserCookieOrderLabelTests.swift +++ b/Tests/CodexBarTests/BrowserCookieOrderLabelTests.swift @@ -42,5 +42,17 @@ struct BrowserCookieOrderStatusStringTests { func `opencode automatic cookies keep chrome only default`() { #expect(OpenCodeWebCookieSupport.automaticImportOrder(provider: .opencode) == [.chrome]) } + + @Test + func `mimo cookie import order supports safari firefox and edge`() { + let order = ProviderDefaults.metadata[.mimo]?.browserCookieOrder ?? Browser.defaultImportOrder + #expect(order == ProviderBrowserCookieDefaults.mimoCookieImportOrder) + #expect(order == [.safari, .chrome, .chromeBeta, .chromeCanary, .firefox, .edge]) + #expect(order.first == .safari) + #expect(order.contains(.firefox)) + #expect(order.contains(.edge)) + #expect(!order.contains(.arc)) + } + #endif } diff --git a/Tests/CodexBarTests/MiMoProviderTests.swift b/Tests/CodexBarTests/MiMoProviderTests.swift index 411ed124b6..fe27c44127 100644 --- a/Tests/CodexBarTests/MiMoProviderTests.swift +++ b/Tests/CodexBarTests/MiMoProviderTests.swift @@ -488,6 +488,29 @@ struct MiMoProviderTests { } } + @Test + func `mimo cookie importer surfaces safari access denial`() throws { + let detection = BrowserDetection( + homeDirectory: "/tmp/codexbar-mimo-browser-test", + cacheTTL: 0, + fileExists: { _ in false }, + directoryContents: { _ in nil }) + + do { + _ = try MiMoCookieImporter.importSessions( + browserDetection: detection, + loadRecords: { browser, _, _ in + throw BrowserCookieError.accessDenied( + browser: browser, + details: "Grant CodexBar Full Disk Access to read Safari cookies.") + }) + Issue.record("Expected Safari access denial") + } catch let error as MiMoSettingsError { + #expect(error.localizedDescription.contains("Full Disk Access")) + #expect(error.localizedDescription.contains("Safari")) + } + } + @Test func `mimo web strategy retries imported sessions after decode failure`() async throws { KeychainCacheStore.setTestStoreForTesting(true) diff --git a/docs/mimo.md b/docs/mimo.md index be6fb06362..772f334ff1 100644 --- a/docs/mimo.md +++ b/docs/mimo.md @@ -22,6 +22,10 @@ The Xiaomi MiMo provider tracks your current balance from the Xiaomi MiMo consol 2. Enable **Xiaomi MiMo** 3. Leave **Cookie source** on **Auto** (recommended) +CodexBar imports cookies from these browsers in order: **Safari**, **Chrome** / **Chrome Beta** / **Chrome Canary**, **Firefox**, and **Microsoft Edge**. Switch to **Manual** and paste a `Cookie:` header if your active MiMo session lives in Arc, Brave, or another browser profile CodexBar does not auto-detect. + +Safari cookie import may require granting CodexBar Full Disk Access in **System Settings → Privacy & Security**. + ### Manual cookie import (optional) 1. Open `https://platform.xiaomimimo.com/#/console/balance` @@ -39,12 +43,13 @@ The Xiaomi MiMo provider tracks your current balance from the Xiaomi MiMo consol - MiMo currently exposes **balance only** - Token cost, status polling, debug log output, and widgets are not supported yet +- Auto import covers Safari, Chrome variants, Firefox, and Edge only; other browsers use **Manual** mode ## Troubleshooting ### “No Xiaomi MiMo browser session found” -Log in at `https://platform.xiaomimimo.com/#/console/balance` in Chrome, then refresh CodexBar. +Log in at `https://platform.xiaomimimo.com/#/console/balance` in Safari, Chrome, Firefox, or Edge, then refresh CodexBar. If your session lives in another browser, switch the MiMo provider to **Cookie source → Manual** and paste the `Cookie:` header instead. ### “Xiaomi MiMo requires the api-platform_serviceToken and userId cookies”