import AppKit import WebKit @MainActor final class RedditOAuthPanelController: NSObject { static let shared = RedditOAuthPanelController() private var panel: NSPanel? private var webView: WKWebView? private var onComplete: (() -> Void)? private var onAuthError: ((String) -> Void)? private override init() { super.init() } func completeSignIn( callbackURL: URL, onAuthError: @escaping (String) -> Void, onFinished: @escaping () -> Void ) { dismiss() self.onComplete = onFinished self.onAuthError = onAuthError let panel = NSPanel( contentRect: NSRect(x: 0, y: 0, width: 520, height: 640), styleMask: [.titled, .closable, .fullSizeContentView], backing: .buffered, defer: false ) panel.title = "Sign in to Reddit" panel.isReleasedWhenClosed = false panel.center() panel.level = .floating let webView = WKWebView(frame: panel.contentView?.bounds ?? .zero, configuration: RedditWebViewManager.makeConfiguration()) webView.navigationDelegate = self webView.autoresizingMask = [.width, .height] panel.contentView?.addSubview(webView) self.panel = panel self.webView = webView panel.makeKeyAndOrderFront(nil) NSApp.activate(ignoringOtherApps: true) webView.load(URLRequest(url: callbackURL)) } func dismiss() { panel?.close() panel = nil webView = nil onComplete = nil onAuthError = nil } private func finish() { let completion = onComplete dismiss() completion?() } } extension RedditOAuthPanelController: WKNavigationDelegate { func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { guard let url = webView.url, RedditWebAuthHelper.isRedditURL(url) else { return } if shouldFinish(for: url) { finish() } } func webView( _ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void ) { guard let url = navigationAction.request.url else { decisionHandler(.allow) return } if RedditWebAuthHelper.isOAuthProviderURL(url) { decisionHandler(.cancel) OAuthAuthenticationManager.shared.authenticate( url: url, presentationWindow: panel ) { [weak self] result in guard let self else { return } switch result { case .success(let callbackURL): webView.load(URLRequest(url: callbackURL)) case .failure(let error): self.onAuthError?(error.localizedDescription) self.finish() } } return } if shouldFinish(for: url) { decisionHandler(.allow) DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in self?.finish() } return } decisionHandler(.allow) } private func shouldFinish(for url: URL) -> Bool { guard let host = url.host?.lowercased() else { return false } let path = url.path.lowercased() if host == "www.reddit.com" || host == "reddit.com" { return !path.contains("login") && !path.contains("register") } return RedditWebAuthHelper.looksLikeLoggedInURL(url) } }