From 91bedbe51691a065a5c44fe85f6b4fc571782a0f Mon Sep 17 00:00:00 2001 From: Yuxin Qiao <104957188+Yuxin-Qiao@users.noreply.github.com> Date: Fri, 19 Jun 2026 15:51:19 +0800 Subject: [PATCH 1/2] Fix MiMo auth redirect fallback --- .../Providers/MiMo/MiMoUsageFetcher.swift | 3 + Tests/CodexBarTests/MiMoProviderTests.swift | 86 +++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/Sources/CodexBarCore/Providers/MiMo/MiMoUsageFetcher.swift b/Sources/CodexBarCore/Providers/MiMo/MiMoUsageFetcher.swift index 7d2a077072..b39cbb46f0 100644 --- a/Sources/CodexBarCore/Providers/MiMo/MiMoUsageFetcher.swift +++ b/Sources/CodexBarCore/Providers/MiMo/MiMoUsageFetcher.swift @@ -168,6 +168,9 @@ public enum MiMoUsageFetcher { switch response.statusCode { case 200: break + // Expired browser sessions can redirect API requests to the login flow. + case 300..<400: + throw MiMoUsageError.loginRequired case 401: throw MiMoUsageError.loginRequired case 403: diff --git a/Tests/CodexBarTests/MiMoProviderTests.swift b/Tests/CodexBarTests/MiMoProviderTests.swift index 8065c7f469..4d23d3da13 100644 --- a/Tests/CodexBarTests/MiMoProviderTests.swift +++ b/Tests/CodexBarTests/MiMoProviderTests.swift @@ -561,6 +561,25 @@ struct MiMoProviderTests { #expect(elapsed < .seconds(1), "Required failure was delayed by optional requests: \(elapsed)") } + + @Test + func `fetch usage treats auth redirect as login required`() async throws { + let transport = ProviderHTTPTransportStub { request in + let url = try #require(request.url) + let (response, data) = Self.makeResponse(url: url, body: "", statusCode: 302) + return (data, response) + } + + do { + _ = try await MiMoUsageFetcher.fetchUsage( + cookieHeader: "userId=123; api-platform_serviceToken=expired-token", + environment: ["MIMO_API_URL": "https://mimo.test/api/v1"], + session: transport) + Issue.record("Expected MiMo auth redirect to require login") + } catch MiMoUsageError.loginRequired { + // Expected. + } + } } private actor MiMoOptionalRequestGate { @@ -942,6 +961,73 @@ extension MiMoProviderTests { #expect(CookieHeaderCache.load(provider: .mimo)?.sourceLabel == "Active Chrome") } + @Test + func `mimo web strategy retries safari after stale chrome auth redirect`() async throws { + KeychainCacheStore.setTestStoreForTesting(true) + defer { KeychainCacheStore.setTestStoreForTesting(false) } + let registered = URLProtocol.registerClass(MiMoStubURLProtocol.self) + defer { + if registered { + URLProtocol.unregisterClass(MiMoStubURLProtocol.self) + } + MiMoStubURLProtocol.handler = nil + MiMoCookieImporter.importSessionsOverrideForTesting = nil + CookieHeaderCache.clear(provider: .mimo) + } + + CookieHeaderCache.clear(provider: .mimo) + CookieHeaderCache.store( + provider: .mimo, + cookieHeader: "api-platform_serviceToken=stale-chrome-token; userId=111", + sourceLabel: "Chrome") + + MiMoCookieImporter.importSessionsOverrideForTesting = { _, _ in + [ + .init( + cookieHeader: "api-platform_serviceToken=stale-chrome-token; userId=111", + sourceLabel: "Chrome"), + .init( + cookieHeader: "api-platform_serviceToken=valid-safari-token; userId=222", + sourceLabel: "Safari"), + ] + } + + let lock = NSLock() + var requestedCookies: [String] = [] + MiMoStubURLProtocol.handler = { request in + guard let url = request.url else { throw URLError(.badURL) } + let cookie = request.value(forHTTPHeaderField: "Cookie") ?? "" + lock.withLock { + requestedCookies.append(cookie) + } + + if cookie.contains("stale-chrome-token") { + return Self.makeResponse(url: url, body: "", statusCode: 302) + } + + let body = """ + { + "code": 0, + "message": "", + "data": { + "balance": "25.51", + "currency": "USD" + } + } + """ + return Self.makeResponse(url: url, body: body) + } + + let strategy = MiMoWebFetchStrategy() + let result = try await strategy + .fetch(self.makeContext(environment: ["MIMO_API_URL": "https://mimo.test/api/v1"])) + + #expect(requestedCookies.contains(where: { $0.contains("stale-chrome-token") })) + #expect(requestedCookies.contains(where: { $0.contains("valid-safari-token") })) + #expect(result.usage.mimoUsage?.balanceDetail == "$25.51") + #expect(CookieHeaderCache.load(provider: .mimo)?.sourceLabel == "Safari") + } + #if os(macOS) @Test func `mimo importer merges profile stores before validating auth cookies`() { From 215584c29306bb016f35662a235fe48cf703ca7f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 19 Jun 2026 09:01:02 +0100 Subject: [PATCH 2/2] docs: note MiMo browser fallback fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2969a5a519..1b21f43bee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - LiteLLM: show personal and team spend amounts directly on budget rows while suppressing duplicate budget sections. Thanks @hololee! ### Fixed +- Xiaomi MiMo: retry another imported browser session when a stale session redirects API requests to login. Thanks @Yuxin-Qiao! - Kiro: keep parsed usage available when the optional account probe times out or fails. Thanks @Yuxin-Qiao! - Memory: release idle OpenAI WebViews under system pressure without blocking the main thread. Thanks @ProspectOre! - Memory: trim rebuildable menu and OpenAI debug caches under system pressure. Thanks @ProspectOre!