beta.24: read lastActiveOrg cookie via JS, expose bootstrap body and lastActiveOrg in diagnostics

This commit is contained in:
superdooper86
2026-05-11 13:15:08 +02:00
parent 2ab2d913ce
commit 9ed8916075
3 changed files with 50 additions and 6 deletions
+22 -2
View File
@@ -48,6 +48,7 @@ struct DiagnosticsView: View {
DiagRow("Signed in", vm.isSignedIn ? "Yes" : "No") DiagRow("Signed in", vm.isSignedIn ? "Yes" : "No")
DiagRow("Email", vm.userEmail.isEmpty ? "(none)" : vm.userEmail) DiagRow("Email", vm.userEmail.isEmpty ? "(none)" : vm.userEmail)
DiagRow("Org ID", UserDefaults.standard.string(forKey: "claude_org_id") ?? "(none)") DiagRow("Org ID", UserDefaults.standard.string(forKey: "claude_org_id") ?? "(none)")
DiagRow("lastActiveOrg", vm.diagLastActiveOrg.isEmpty ? "(not read)" : vm.diagLastActiveOrg)
DiagRow("Error", vm.errorMessage ?? "(none)") DiagRow("Error", vm.errorMessage ?? "(none)")
} }
@@ -65,9 +66,24 @@ struct DiagnosticsView: View {
Divider() Divider()
// Bootstrap body
if !vm.diagBootstrapBody.isEmpty {
DiagSection(title: "Bootstrap Response (first 500 chars)") {
Text(vm.diagBootstrapBody)
.font(.system(size: 10, design: .monospaced))
.foregroundColor(.secondary)
.textSelection(.enabled)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(8)
.background(Color.primary.opacity(0.04))
.cornerRadius(5)
}
Divider()
}
// Response body // Response body
if !vm.diagLastBody.isEmpty { if !vm.diagLastBody.isEmpty {
DiagSection(title: "Response Body (first 500 chars)") { DiagSection(title: "Last API Response (first 500 chars)") {
Text(vm.diagLastBody) Text(vm.diagLastBody)
.font(.system(size: 10, design: .monospaced)) .font(.system(size: 10, design: .monospaced))
.foregroundColor(.secondary) .foregroundColor(.secondary)
@@ -164,6 +180,7 @@ struct DiagnosticsView: View {
lines.append("Signed in: \(vm.isSignedIn ? "Yes" : "No")") lines.append("Signed in: \(vm.isSignedIn ? "Yes" : "No")")
lines.append("Email: \(vm.userEmail.isEmpty ? "(none)" : vm.userEmail)") lines.append("Email: \(vm.userEmail.isEmpty ? "(none)" : vm.userEmail)")
lines.append("Org ID: \(UserDefaults.standard.string(forKey: "claude_org_id") ?? "(none)")") lines.append("Org ID: \(UserDefaults.standard.string(forKey: "claude_org_id") ?? "(none)")")
lines.append("lastActiveOrg: \(vm.diagLastActiveOrg.isEmpty ? "(not read)" : vm.diagLastActiveOrg)")
lines.append("Error: \(vm.errorMessage ?? "(none)")") lines.append("Error: \(vm.errorMessage ?? "(none)")")
lines.append("") lines.append("")
lines.append("Last path: \(vm.diagLastPath)") lines.append("Last path: \(vm.diagLastPath)")
@@ -173,7 +190,10 @@ struct DiagnosticsView: View {
lines.append("Claude cookies: \(vm.diagClaudeCookieCount)") lines.append("Claude cookies: \(vm.diagClaudeCookieCount)")
lines.append("Domains: \(vm.diagCookieDomains.joined(separator: ", "))") lines.append("Domains: \(vm.diagCookieDomains.joined(separator: ", "))")
lines.append("") lines.append("")
lines.append("Response body:") lines.append("Bootstrap body:")
lines.append(vm.diagBootstrapBody)
lines.append("")
lines.append("Last API response:")
lines.append(vm.diagLastBody) lines.append(vm.diagLastBody)
lines.append("") lines.append("")
lines.append("Live cookies:") lines.append("Live cookies:")
+2 -2
View File
@@ -15,9 +15,9 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.2.1-beta.23</string> <string>1.2.1-beta.24</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>71</string> <string>72</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>13.0</string> <string>13.0</string>
<key>LSUIElement</key> <key>LSUIElement</key>
+26 -2
View File
@@ -34,7 +34,7 @@ class UsageViewModel: ObservableObject {
private var apiWebView: WKWebView? private var apiWebView: WKWebView?
private var navWaiter: WKNavWaiter? private var navWaiter: WKNavWaiter?
// Diagnostics populated on every urlFetch call // Diagnostics
@Published var diagCookieCount: Int = 0 @Published var diagCookieCount: Int = 0
@Published var diagClaudeCookieCount: Int = 0 @Published var diagClaudeCookieCount: Int = 0
@Published var diagCookieDomains: [String] = [] @Published var diagCookieDomains: [String] = []
@@ -42,6 +42,8 @@ class UsageViewModel: ObservableObject {
@Published var diagLastStatus: Int = 0 @Published var diagLastStatus: Int = 0
@Published var diagLastBody: String = "" @Published var diagLastBody: String = ""
@Published var diagLastError: String = "" @Published var diagLastError: String = ""
@Published var diagLastActiveOrg: String = "" // lastActiveOrg cookie value from JS
@Published var diagBootstrapBody: String = "" // raw bootstrap response (first 500 chars)
@Published var diagLastFetch: Date? = nil @Published var diagLastFetch: Date? = nil
init() { init() {
@@ -225,10 +227,31 @@ class UsageViewModel: ObservableObject {
defer { isLoading = false } defer { isLoading = false }
do { do {
// Read lastActiveOrg cookie from JS most reliable org source since it's
// set by the claude.ai frontend to whichever org is currently active.
var cookieOrgId: String? = nil
if let wv = apiWebView, wv.url?.host?.hasSuffix("claude.ai") == true {
let js = """
return document.cookie.split(';')
.map(c => c.trim().split('='))
.filter(p => p[0] === 'lastActiveOrg')
.map(p => p.slice(1).join('='))[0] || null;
"""
let raw: Any? = try? await withCheckedThrowingContinuation { cont in
wv.callAsyncJavaScript(js, arguments: [:], in: nil, in: .defaultClient) { r in
cont.resume(returning: (try? r.get()) ?? nil)
}
}
if let v = raw as? String, !v.isEmpty {
cookieOrgId = v
diagLastActiveOrg = v
}
}
let (fetchedOrgId, fetchedEmail, fetchedPlan) = try await fetchBootstrap() let (fetchedOrgId, fetchedEmail, fetchedPlan) = try await fetchBootstrap()
let orgId: String let orgId: String
if let id = fetchedOrgId { if let id = cookieOrgId ?? fetchedOrgId {
UserDefaults.standard.set(id, forKey: "claude_org_id") UserDefaults.standard.set(id, forKey: "claude_org_id")
orgId = id orgId = id
} else if let cached = UserDefaults.standard.string(forKey: "claude_org_id") { } else if let cached = UserDefaults.standard.string(forKey: "claude_org_id") {
@@ -292,6 +315,7 @@ class UsageViewModel: ObservableObject {
guard body.trimmingCharacters(in: .whitespacesAndNewlines).hasPrefix("{") else { guard body.trimmingCharacters(in: .whitespacesAndNewlines).hasPrefix("{") else {
throw AppError.detail("Non-JSON response (HTML?): \(body.prefix(80))") throw AppError.detail("Non-JSON response (HTML?): \(body.prefix(80))")
} }
diagBootstrapBody = String(body.prefix(500))
guard let data = body.data(using: .utf8), guard let data = body.data(using: .utf8),
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
return (nil, nil, nil) return (nil, nil, nil)