diff --git a/Example/HCaptcha_Tests/Core/HCaptchaWebViewManager__Tests.swift b/Example/HCaptcha_Tests/Core/HCaptchaWebViewManager__Tests.swift index 38307fc..49c717a 100644 --- a/Example/HCaptcha_Tests/Core/HCaptchaWebViewManager__Tests.swift +++ b/Example/HCaptcha_Tests/Core/HCaptchaWebViewManager__Tests.swift @@ -77,6 +77,27 @@ class HCaptchaWebViewManager__Tests: XCTestCase { XCTAssertEqual(result2?.token, apiKey) } + func test__Validate__Ignores_Late_Termination_After_Token() { + let exp = expectation(description: "load token") + var results = [HCaptchaResult]() + + let manager = HCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey) + manager.validate(on: presenterView) { response in + results.append(response) + if results.count == 1 { + exp.fulfill() + } + } + + waitForExpectations(timeout: TestTimeouts.standard) + + manager.webViewWebContentProcessDidTerminate(manager.webView) + + XCTAssertEqual(results.count, 1) + XCTAssertNil(results.first?.error) + XCTAssertEqual(results.first?.token, apiKey) + } + func test__Validate__Show_HCaptcha() { let exp = expectation(description: "show hcaptcha") diff --git a/HCaptcha/Classes/HCaptchaWebViewManager+Private.swift b/HCaptcha/Classes/HCaptchaWebViewManager+Private.swift index 9bca5b6..3ddee62 100644 --- a/HCaptcha/Classes/HCaptchaWebViewManager+Private.swift +++ b/HCaptcha/Classes/HCaptchaWebViewManager+Private.swift @@ -37,17 +37,9 @@ extension HCaptchaWebViewManager { func handle(result: HCaptchaDecoder.Result) { Log.debug("WebViewManager.handleResult: \(result)") - guard !resultHandled else { - Log.debug("WebViewManager.handleResult skip as handled") - return - } - switch result { - case .token(let token): - completion?(HCaptchaResult(self, token: token)) - case .error(let error): - handle(error: error) - onEvent?(.error, error) + case .token(let token): handleToken(token) + case .error(let error): handleDecoderError(error) case .showHCaptcha: webView.isHidden = false case .didLoad: didLoad() case .onOpen: onEvent?(.open, nil) @@ -58,6 +50,23 @@ extension HCaptchaWebViewManager { } } + private func handleToken(_ token: String) { + guard !resultHandled else { + Log.debug("WebViewManager.handleResult skip token as handled") + return + } + complete(HCaptchaResult(self, token: token)) + } + + private func handleDecoderError(_ error: HCaptchaError) { + guard !resultHandled else { + Log.debug("WebViewManager.handleResult skip error as handled") + return + } + handle(error: error) + onEvent?(.error, error) + } + private func handle(error: HCaptchaError) { loadingTimer?.invalidate() loadingTimer = nil @@ -66,11 +75,11 @@ extension HCaptchaWebViewManager { reset() validate(on: view) } else { - completion?(HCaptchaResult(self, error: error)) + complete(HCaptchaResult(self, error: error)) } } else { - if let completion = completion { - completion(HCaptchaResult(self, error: error)) + if completion != nil { + complete(HCaptchaResult(self, error: error)) } else { lastError = error } @@ -172,8 +181,8 @@ extension HCaptchaWebViewManager { guard let self = self else { return } Log.debug("WebViewManager complete with pendingError: \(error)") - self.completion?(HCaptchaResult(self, error: error)) self.lastError = nil + self.complete(HCaptchaResult(self, error: error)) } if error == .networkError { Log.debug("WebViewManager reloads html after \(error) error") @@ -196,4 +205,10 @@ extension HCaptchaWebViewManager { func executeJS(command: JSCommand) { executeJS(command: command, didLoad: self.didFinishLoading) } + + func complete(_ result: HCaptchaResult) { + let completion = self.completion + self.completion = nil + completion?(result) + } } diff --git a/HCaptcha/Classes/HCaptchaWebViewManager+WKNavigationDelegate.swift b/HCaptcha/Classes/HCaptchaWebViewManager+WKNavigationDelegate.swift index ae4afac..a3cb7bb 100644 --- a/HCaptcha/Classes/HCaptchaWebViewManager+WKNavigationDelegate.swift +++ b/HCaptcha/Classes/HCaptchaWebViewManager+WKNavigationDelegate.swift @@ -30,13 +30,13 @@ extension HCaptchaWebViewManager: WKNavigationDelegate, WKUIDelegate { /// Tells the delegate that an error occurred during navigation. func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { Log.debug("WebViewManager.webViewDidFail with \(error)") - completion?(HCaptchaResult(self, error: .unexpected(error))) + complete(HCaptchaResult(self, error: .unexpected(error))) } /// Tells the delegate that an error occurred during the early navigation process. func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { Log.debug("WebViewManager.webViewDidFailProvisionalNavigation with \(error)") - completion?(HCaptchaResult(self, error: .unexpected(error))) + complete(HCaptchaResult(self, error: .unexpected(error))) } /// Tells the delegate that the web view’s content process was terminated. @@ -49,7 +49,7 @@ extension HCaptchaWebViewManager: WKNavigationDelegate, WKUIDelegate { userInfo: [ NSLocalizedDescriptionKey: "WebView web content process did terminate", NSLocalizedRecoverySuggestionErrorKey: "Call HCaptcha.reset()"]) - completion?(HCaptchaResult(self, error: .unexpected(error))) didFinishLoading = false + complete(HCaptchaResult(self, error: .unexpected(error))) } } diff --git a/HCaptcha/Classes/HCaptchaWebViewManager.swift b/HCaptcha/Classes/HCaptchaWebViewManager.swift index f7076a0..bfef5d8 100644 --- a/HCaptcha/Classes/HCaptchaWebViewManager.swift +++ b/HCaptcha/Classes/HCaptchaWebViewManager.swift @@ -154,7 +154,7 @@ internal class HCaptchaWebViewManager: NSObject { if !passiveApiKey { guard let view = view else { - completion?(HCaptchaResult(self, error: .failedSetup)) + complete(HCaptchaResult(self, error: .failedSetup)) return } @@ -171,6 +171,7 @@ internal class HCaptchaWebViewManager: NSObject { func stop() { Log.debug("WebViewManager.stop") stopInitWebViewConfiguration = true + completion = nil webView.stopLoading() resultHandled = true loadingTimer?.invalidate()