#if os(macOS)import Foundationimport WebKit // MARK: - Navigation helper (revived from the old credits scraper) @MainActorfinal class NavigationDelegate: NSObject, WKNavigationDelegate { private let completion: (Result<Void, Error>) -> Void private var hasCompleted: Bool = false private var timeoutWorkItem: DispatchWorkItem? private var postCommitWorkItem: DispatchWorkItem? static var associationKey: UInt8 = 0 nonisolated static let postCommitSuccessDelay: TimeInterval = 0.75 init(completion: @escaping (Result<Void, Error>) -> Void) { self.completion = completion } func armTimeout(seconds: TimeInterval) { self.timeoutWorkItem?.cancel() let delay = max(seconds, 0) let workItem = DispatchWorkItem { [weak self] in MainActor.assumeIsolated { self?.completeOnce(.failure(URLError(.timedOut))) } } self.timeoutWorkItem = workItem DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: workItem) } func cancel() { self.completeOnce(.failure(CancellationError())) } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { self.completeOnce(.success(())) } func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { guard !self.hasCompleted else { return } self.postCommitWorkItem?.cancel() let workItem = DispatchWorkItem { [weak self] in MainActor.assumeIsolated { self?.completeOnce(.success(())) } } self.postCommitWorkItem = workItem DispatchQueue.main.asyncAfter(deadline: .now() + Self.postCommitSuccessDelay, execute: workItem) } func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { if Self.shouldIgnoreNavigationError(error) { return } self.completeOnce(.failure(error)) } func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { if Self.shouldIgnoreNavigationError(error) { return } self.completeOnce(.failure(error)) } nonisolated static func shouldIgnoreNavigationError(_ error: Error) -> Bool { let nsError = error as NSError if nsError.domain == NSURLErrorDomain, nsError.code == NSURLErrorCancelled { return true } if nsError.domain == "WebKitErrorDomain", nsError.code == 102 { return true } return false } private func completeOnce(_ result: Result<Void, Error>) { guard !self.hasCompleted else { return } self.hasCompleted = true self.timeoutWorkItem?.cancel() self.timeoutWorkItem = nil self.postCommitWorkItem?.cancel() self.postCommitWorkItem = nil self.completion(result) }} extension WKWebView { var codexNavigationDelegate: NavigationDelegate? { get { objc_getAssociatedObject(self, &NavigationDelegate.associationKey) as? NavigationDelegate } set { objc_setAssociatedObject( self, &NavigationDelegate.associationKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } }} #endif
#if os(macOS)
import Foundation
import WebKit
// MARK: - Navigation helper (revived from the old credits scraper)
@MainActor
final class NavigationDelegate: NSObject, WKNavigationDelegate {
private let completion: (Result<Void, Error>) -> Void
private var hasCompleted: Bool = false
private var timeoutWorkItem: DispatchWorkItem?
private var postCommitWorkItem: DispatchWorkItem?
static var associationKey: UInt8 = 0
nonisolated static let postCommitSuccessDelay: TimeInterval = 0.75
init(completion: @escaping (Result<Void, Error>) -> Void) {
self.completion = completion
}
func armTimeout(seconds: TimeInterval) {
self.timeoutWorkItem?.cancel()
let delay = max(seconds, 0)
let workItem = DispatchWorkItem { [weak self] in
MainActor.assumeIsolated {
self?.completeOnce(.failure(URLError(.timedOut)))
}
}
self.timeoutWorkItem = workItem
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: workItem)
}
func cancel() {
self.completeOnce(.failure(CancellationError()))
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.completeOnce(.success(()))
}
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
guard !self.hasCompleted else { return }
self.postCommitWorkItem?.cancel()
let workItem = DispatchWorkItem { [weak self] in
MainActor.assumeIsolated {
self?.completeOnce(.success(()))
}
}
self.postCommitWorkItem = workItem
DispatchQueue.main.asyncAfter(deadline: .now() + Self.postCommitSuccessDelay, execute: workItem)
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
if Self.shouldIgnoreNavigationError(error) { return }
self.completeOnce(.failure(error))
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
if Self.shouldIgnoreNavigationError(error) { return }
self.completeOnce(.failure(error))
}
nonisolated static func shouldIgnoreNavigationError(_ error: Error) -> Bool {
let nsError = error as NSError
if nsError.domain == NSURLErrorDomain, nsError.code == NSURLErrorCancelled {
return true
}
if nsError.domain == "WebKitErrorDomain", nsError.code == 102 {
return true
}
return false
}
private func completeOnce(_ result: Result<Void, Error>) {
guard !self.hasCompleted else { return }
self.hasCompleted = true
self.timeoutWorkItem?.cancel()
self.timeoutWorkItem = nil
self.postCommitWorkItem?.cancel()
self.postCommitWorkItem = nil
self.completion(result)
}
}
extension WKWebView {
var codexNavigationDelegate: NavigationDelegate? {
get {
objc_getAssociatedObject(self, &NavigationDelegate.associationKey) as? NavigationDelegate
}
set {
objc_setAssociatedObject(
self,
&NavigationDelegate.associationKey,
newValue,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
#endif