diff --git a/Example/HCaptcha_Tests/Core/HCaptchaWebViewManager__Tests.swift b/Example/HCaptcha_Tests/Core/HCaptchaWebViewManager__Tests.swift index 7be6707..4220252 100644 --- a/Example/HCaptcha_Tests/Core/HCaptchaWebViewManager__Tests.swift +++ b/Example/HCaptcha_Tests/Core/HCaptchaWebViewManager__Tests.swift @@ -633,4 +633,21 @@ class HCaptchaWebViewManager__Tests: XCTestCase { waitForExpectations(timeout: 10) } + + func test__HTML_Load_Error_Timeout() { + let exp = expectation(description: "didLoad never called") + + let manager = HCaptchaWebViewManager(html: "", apiKey: apiKey, loadingTimeout: 0.5) + manager.shouldResetOnError = false + manager.configureWebView { _ in + XCTFail("should not ask to configure the webview") + } + + manager.validate(on: presenterView, resetOnError: false) { response in + XCTAssertEqual(HCaptchaError.htmlLoadError, response.error) + exp.fulfill() + } + + waitForExpectations(timeout: 10) + } } diff --git a/Example/HCaptcha_Tests/Helpers/HCaptchaConfig+Helpers.swift b/Example/HCaptcha_Tests/Helpers/HCaptchaConfig+Helpers.swift index f0a43f9..194288f 100644 --- a/Example/HCaptcha_Tests/Helpers/HCaptchaConfig+Helpers.swift +++ b/Example/HCaptcha_Tests/Helpers/HCaptchaConfig+Helpers.swift @@ -23,7 +23,8 @@ extension HCaptchaConfig { host: String? = nil, theme: String = "\"light\"", customTheme: String? = nil, - locale: Locale? = nil) throws { + locale: Locale? = nil, + loadingTimeout: TimeInterval = 5.0) throws { try self.init(html: html, apiKey: apiKey, @@ -43,7 +44,8 @@ extension HCaptchaConfig { host: host, theme: theme, customTheme: customTheme, - locale: locale) + locale: locale, + loadingTimeout: loadingTimeout) } init(apiKey: String = "some-api-key", @@ -57,6 +59,5 @@ extension HCaptchaConfig { host: host, customTheme: customTheme, locale: locale) - } } diff --git a/Example/HCaptcha_Tests/Helpers/HCaptchaWebViewManager+Helpers.swift b/Example/HCaptcha_Tests/Helpers/HCaptchaWebViewManager+Helpers.swift index 1990cf4..176515e 100644 --- a/Example/HCaptcha_Tests/Helpers/HCaptchaWebViewManager+Helpers.swift +++ b/Example/HCaptcha_Tests/Helpers/HCaptchaWebViewManager+Helpers.swift @@ -52,13 +52,14 @@ extension HCaptchaWebViewManager { html: String, apiKey: String, passiveApiKey: Bool = false, - endpoint: URL, + endpoint: URL = URL(string: "https://api.hcaptcha.com")!, size: HCaptchaSize = .invisible, orientation: HCaptchaOrientation = .portrait, rqdata: String? = nil, theme: String = "light", customTheme: String? = nil, - urlOpener: HCaptchaURLOpener = HCapchaAppURLOpener() + urlOpener: HCaptchaURLOpener = HCapchaAppURLOpener(), + loadingTimeout: TimeInterval = 5 ) { let localhost = URL(string: "http://localhost")! @@ -72,7 +73,8 @@ extension HCaptchaWebViewManager { rqdata: rqdata, endpoint: endpoint, theme: theme, - customTheme: customTheme) + customTheme: customTheme, + loadingTimeout: loadingTimeout) self.init( config: config, diff --git a/HCaptcha/Classes/HCaptchaConfig.swift b/HCaptcha/Classes/HCaptchaConfig.swift index f7f945e..24e545c 100644 --- a/HCaptcha/Classes/HCaptchaConfig.swift +++ b/HCaptcha/Classes/HCaptchaConfig.swift @@ -131,9 +131,12 @@ struct HCaptchaConfig: CustomDebugStringConvertible { /// Custom theme JSON string. let customTheme: String? - /// locale: A locale value to translate HCaptcha into a different language + /// A locale value to translate HCaptcha into a different language let locale: Locale? + /// A time before throw the `.htmlLoadError` if HCaptcha is not loaded + let loadingTimeout: TimeInterval + /// Return actual theme value based on init params. It must return valid JS object. var actualTheme: String { self.customTheme ?? "\"\(theme)\"" @@ -205,7 +208,8 @@ struct HCaptchaConfig: CustomDebugStringConvertible { host: String?, theme: String, customTheme: String?, - locale: Locale?) throws { + locale: Locale?, + loadingTimeout: TimeInterval = 5.0) throws { guard let apiKey = apiKey ?? infoPlistKey else { throw HCaptchaError.apiKeyNotFound } @@ -243,6 +247,7 @@ struct HCaptchaConfig: CustomDebugStringConvertible { self.theme = theme self.customTheme = customTheme self.locale = locale + self.loadingTimeout = loadingTimeout } /** diff --git a/HCaptcha/Classes/HCaptchaWebViewManager.swift b/HCaptcha/Classes/HCaptchaWebViewManager.swift index 033d513..e18598f 100644 --- a/HCaptcha/Classes/HCaptchaWebViewManager.swift +++ b/HCaptcha/Classes/HCaptchaWebViewManager.swift @@ -97,6 +97,9 @@ internal class HCaptchaWebViewManager: NSObject { /// Keep error If it happens before validate call fileprivate var lastError: HCaptchaError? + /// Timeout to throw `.htmlLoadError` if no `didLoad` called + fileprivate let loadingTimeout: TimeInterval + /// The webview that executes JS code lazy var webView: WKWebView = { let debug = Log.minLevel == .debug @@ -133,6 +136,7 @@ internal class HCaptchaWebViewManager: NSObject { self.urlOpener = urlOpener self.baseURL = config.baseURL self.passiveApiKey = config.passiveApiKey + self.loadingTimeout = config.loadingTimeout super.init() self.decoder = HCaptchaDecoder { [weak self] result in self?.handle(result: result) @@ -260,6 +264,8 @@ fileprivate extension HCaptchaWebViewManager { } private func handle(error: HCaptchaError) { + loadingTimer?.invalidate() + loadingTimer = nil if error == .sessionTimeout { if shouldResetOnError, let view = webView.superview { reset() @@ -338,7 +344,7 @@ fileprivate extension HCaptchaWebViewManager { webView.navigationDelegate = self } loadingTimer?.invalidate() - loadingTimer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false, block: { _ in + loadingTimer = Timer.scheduledTimer(withTimeInterval: self.loadingTimeout, repeats: false, block: { _ in self.handle(error: .htmlLoadError) self.loadingTimer = nil }) @@ -360,6 +366,8 @@ fileprivate extension HCaptchaWebViewManager { Log.debug("WebViewManager.executeJS: \(command)") guard didLoad else { if let error = lastError { + loadingTimer?.invalidate() + loadingTimer = nil DispatchQueue.main.async { [weak self] in guard let self = self else { return } Log.debug("WebViewManager complete with pendingError: \(error)")