fix: add KVO on webView.url to catch SPA pushState navigation

didFinish only fires for cross-document (full page) navigations. After
loading https://claude.ai/login the SPA redirects authenticated users
via history.pushState to /new — this changes the URL visually but never
fires didFinish, so auth was never detected.

KVO on webView.url fires for every URL change including SPA pushState,
covering the case where the app routes client-side after the initial
page load. Both KVO and didFinish now call the same checkCurrentURL
helper so detection is not missed regardless of navigation type.
This commit is contained in:
superdooper86
2026-05-11 10:46:11 +02:00
parent 88252e4d59
commit 58b947e515
2 changed files with 16 additions and 7 deletions
+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.10</string> <string>1.2.1-beta.11</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>58</string> <string>59</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>13.0</string> <string>13.0</string>
<key>LSUIElement</key> <key>LSUIElement</key>
+14 -5
View File
@@ -12,6 +12,12 @@ struct LoginWebView: NSViewRepresentable {
let webView = WKWebView(frame: .zero, configuration: config) let webView = WKWebView(frame: .zero, configuration: config)
webView.navigationDelegate = context.coordinator webView.navigationDelegate = context.coordinator
// KVO on url catches SPA pushState navigations that don't fire didFinish
context.coordinator.urlObservation = webView.observe(\.url, options: [.new]) { [weak coordinator = context.coordinator] wv, _ in
coordinator?.checkCurrentURL(wv.url?.absoluteString)
}
webView.load(URLRequest(url: URL(string: "https://claude.ai/login")!)) webView.load(URLRequest(url: URL(string: "https://claude.ai/login")!))
return webView return webView
} }
@@ -25,22 +31,25 @@ struct LoginWebView: NSViewRepresentable {
class Coordinator: NSObject, WKNavigationDelegate { class Coordinator: NSObject, WKNavigationDelegate {
let onAuthenticated: () -> Void let onAuthenticated: () -> Void
var didAuthenticate = false var didAuthenticate = false
var urlObservation: NSKeyValueObservation?
init(onAuthenticated: @escaping () -> Void) { init(onAuthenticated: @escaping () -> Void) {
self.onAuthenticated = onAuthenticated self.onAuthenticated = onAuthenticated
} }
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { func checkCurrentURL(_ url: String?) {
guard !didAuthenticate else { return } guard !didAuthenticate, let url else { return }
guard let url = webView.url?.absoluteString else { return }
// Stay on login/auth pages user hasn't completed sign-in yet
if url.contains("/login") || url.contains("/auth") { return } if url.contains("/login") || url.contains("/auth") { return }
// Navigated away from login server redirected us, so sign-in completed
didAuthenticate = true didAuthenticate = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.onAuthenticated() self.onAuthenticated()
} }
} }
// Covers full cross-document navigations
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
checkCurrentURL(webView.url?.absoluteString)
}
} }
} }