beta.17: adopt login WebView for API calls, add diagnostics

This commit is contained in:
SuperDooper
2026-05-11 12:00:28 +02:00
parent 377888a427
commit f4a83940b1
+29 -19
View File
@@ -98,16 +98,17 @@ class UsageViewModel: ObservableObject {
} }
} }
// Called after login: reloads the background WebView to pick up the new session. // Called after login: adopts the proven-authenticated login WebView for all API calls.
func reloadAPIWebViewAndRefresh() async { // This avoids the background WebView potentially missing auth tokens that only exist
guard let wv = apiWebView else { // in the login WebView's JS context (localStorage, Service Workers, etc.).
await refresh() func adoptAndRefresh(_ loginWebView: WKWebView) async {
return loginWebView.removeFromSuperview()
} loginWebView.frame = NSRect(x: 0, y: 0, width: 1, height: 1)
apiWebViewLoaded = false apiWindow?.contentView?.addSubview(loginWebView)
wv.load(URLRequest(url: URL(string: "https://claude.ai")!)) apiWebView = loginWebView
await waitForAPIWebViewReady() apiWebViewLoaded = true
try? await Task.sleep(nanoseconds: 500_000_000) resumeAPIReadyContinuations()
try? await Task.sleep(nanoseconds: 400_000_000)
await refresh() await refresh()
} }
@@ -115,15 +116,22 @@ class UsageViewModel: ObservableObject {
// This includes all credentials the page has (cookies, localStorage tokens, etc.), // This includes all credentials the page has (cookies, localStorage tokens, etc.),
// which URLSession cannot access hence using callAsyncJavaScript instead. // which URLSession cannot access hence using callAsyncJavaScript instead.
private func webViewFetch(_ path: String) async throws -> (statusCode: Int, body: String) { private func webViewFetch(_ path: String) async throws -> (statusCode: Int, body: String) {
guard let wv = apiWebView else { throw AppError.networkError } guard let wv = apiWebView else { throw AppError.detail("no webview") }
await waitForAPIWebViewReady() await waitForAPIWebViewReady()
let js = "const r = await fetch(path, {credentials:'include'}); return {s: r.status, b: await r.text()};" let js = "const r = await fetch(path, {credentials:'include'}); return {s: r.status, b: await r.text()};"
let result = try await wv.callAsyncJavaScript( do {
js, arguments: ["path": path], in: nil, in: .page) let result = try await wv.callAsyncJavaScript(js, arguments: ["path": path], in: nil, in: .page)
guard let d = result as? [String: Any], guard let d = result as? [String: Any],
let s = (d["s"] as? NSNumber)?.intValue, let s = (d["s"] as? NSNumber)?.intValue,
let b = d["b"] as? String else { throw AppError.networkError } let b = d["b"] as? String else {
return (s, b) throw AppError.detail("bad result: \(String(describing: result).prefix(120))")
}
return (s, b)
} catch let e as AppError { throw e }
catch {
let loc = wv.url?.absoluteString ?? "?"
throw AppError.detail("JS err @ \(loc): \(error.localizedDescription.prefix(100))")
}
} }
private func checkInitialSignInState() async { private func checkInitialSignInState() async {
@@ -409,10 +417,12 @@ private class APIWebViewDelegate: NSObject, WKNavigationDelegate {
enum AppError: LocalizedError { enum AppError: LocalizedError {
case notAuthenticated case notAuthenticated
case networkError case networkError
case detail(String)
var errorDescription: String? { var errorDescription: String? {
switch self { switch self {
case .notAuthenticated: return "Not signed into claude.ai — open claude.ai in your browser first." case .notAuthenticated: return "Not signed into claude.ai — open claude.ai in your browser first."
case .networkError: return "Network error fetching usage data." case .networkError: return "Network error fetching usage data."
case .detail(let msg): return msg
} }
} }
} }