Skip to content

Commit

Permalink
feat: don't emit sessionTimeout once result.dematerialize is called
Browse files Browse the repository at this point in the history
  • Loading branch information
CAMOBAP committed Apr 16, 2024
1 parent 5d630c5 commit 8ce8926
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 53 deletions.
6 changes: 4 additions & 2 deletions Example/HCaptcha_Tests/Core/HCaptchaResult__Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import XCTest
class HCaptchaResult__Tests: XCTestCase {
func test__Get_Token() {
let token = UUID().uuidString
let result = HCaptchaResult(token: token)
let manager = HCaptchaWebViewManager()
let result = HCaptchaResult(manager, token: token)

do {
let value = try result.dematerialize()
Expand All @@ -26,7 +27,8 @@ class HCaptchaResult__Tests: XCTestCase {

func test__Get_Token__Error() {
let error = HCaptchaError.random()
let result = HCaptchaResult(error: error)
let manager = HCaptchaWebViewManager()
let result = HCaptchaResult(manager, error: error)

do {
_ = try result.dematerialize()
Expand Down
50 changes: 47 additions & 3 deletions Example/HCaptcha_Tests/Core/HCaptchaWebViewManager__Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,41 @@ class HCaptchaWebViewManager__Tests: XCTestCase {
waitForExpectations(timeout: 10)
}

func test__Reset_After_Stop() {
let exp0 = expectation(description: "stop loading")
let exp1 = expectation(description: "configureWebView called")
let exp2 = expectation(description: "token recieved")

// Stop
let manager = HCaptchaWebViewManager(messageBody: "{token: \"some_token\"}")
manager.stop()
manager.configureWebView { _ in
XCTFail("should not ask to configure the webview")
}

manager.validate(on: presenterView) { _ in
XCTFail("should not validate")
}

DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
exp0.fulfill()
}

manager.reset()

manager.configureWebView { _ in
exp1.fulfill()
}

manager.validate(on: presenterView) { result in
let token = try? result.dematerialize()
XCTAssertEqual("some_token", token)
exp2.fulfill()
}

waitForExpectations(timeout: 10)
}

// MARK: Setup

func test__Key_Setup() {
Expand Down Expand Up @@ -383,6 +418,7 @@ class HCaptchaWebViewManager__Tests: XCTestCase {
var exp0Count = 0
let exp1 = expectation(description: "should call onEvent")
let exp2 = expectation(description: "fail on first execution")
let exp3 = expectation(description: "hcaptcha opened")
var result: HCaptchaResult?

// Validate
Expand All @@ -395,9 +431,17 @@ class HCaptchaWebViewManager__Tests: XCTestCase {
}

manager.onEvent = { (event, error) in
XCTAssertEqual(.error, event)
XCTAssertEqual(HCaptchaError.wrongMessageFormat, error as? HCaptchaError)
exp1.fulfill()
XCTAssertTrue([.error, .open].contains(event))
switch event {
case .error:
XCTAssertEqual(.error, event)
XCTAssertEqual(HCaptchaError.wrongMessageFormat, error as? HCaptchaError)
exp1.fulfill()
case .open:
exp3.fulfill()
default:
XCTFail("Unexpected event \(event)")
}
}

// Error
Expand Down
4 changes: 4 additions & 0 deletions HCaptcha-Carthage.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
1F833B7F271DC69C00E4DAB2 /* RxSwift.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F833B7E271DC69C00E4DAB2 /* RxSwift.xcframework */; };
1F833B80271DC69C00E4DAB2 /* RxSwift.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 1F833B7E271DC69C00E4DAB2 /* RxSwift.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E614B37B2BCEBF3400CEA791 /* HCaptchaWebViewManager+WKNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E614B37A2BCEBF3400CEA791 /* HCaptchaWebViewManager+WKNavigationDelegate.swift */; };
E634944A2828856300130AC5 /* HCaptchaEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E63494492828856300130AC5 /* HCaptchaEvent.swift */; };
E64C60082B7DFBE900D203F4 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = E64C60072B7DFBE900D203F4 /* PrivacyInfo.xcprivacy */; };
E65145E327786BDB0079668A /* HCaptchaConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = E65145E227786BDB0079668A /* HCaptchaConfig.swift */; };
Expand Down Expand Up @@ -54,6 +55,7 @@

/* Begin PBXFileReference section */
1F833B7E271DC69C00E4DAB2 /* RxSwift.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = RxSwift.xcframework; path = Carthage/Build/RxSwift.xcframework; sourceTree = "<group>"; };
E614B37A2BCEBF3400CEA791 /* HCaptchaWebViewManager+WKNavigationDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HCaptchaWebViewManager+WKNavigationDelegate.swift"; sourceTree = "<group>"; };
E63494492828856300130AC5 /* HCaptchaEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HCaptchaEvent.swift; sourceTree = "<group>"; };
E64C60072B7DFBE900D203F4 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
E65145E227786BDB0079668A /* HCaptchaConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HCaptchaConfig.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -180,6 +182,7 @@
F24EA1D81F9683F5001DEC17 /* HCaptchaDecoder.swift */,
E6DB9EA727B15954008F0327 /* HCaptchaDebugInfo.swift */,
F24EA1D91F9683F5001DEC17 /* HCaptchaWebViewManager.swift */,
E614B37A2BCEBF3400CEA791 /* HCaptchaWebViewManager+WKNavigationDelegate.swift */,
F24EA1DA1F9683F5001DEC17 /* String+Dict.swift */,
F24EA1DB1F9683F5001DEC17 /* HCaptchaError.swift */,
F2AE8613204F3B41002E28D7 /* HCaptchaResult.swift */,
Expand Down Expand Up @@ -333,6 +336,7 @@
F24EA1E41F968403001DEC17 /* String+Dict.swift in Sources */,
F231B39A1FEC51C800F82943 /* DispatchQueue+Throttle.swift in Sources */,
E6DB9EA827B15954008F0327 /* HCaptchaDebugInfo.swift in Sources */,
E614B37B2BCEBF3400CEA791 /* HCaptchaWebViewManager+WKNavigationDelegate.swift in Sources */,
E683772129053E560021BFD7 /* HCaptchaURLOpener.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
8 changes: 7 additions & 1 deletion HCaptcha/Classes/HCaptchaResult.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ public class HCaptchaResult: NSObject {
/// Result error
let error: HCaptchaError?

public init (token: String? = nil, error: HCaptchaError? = nil) {
/// Manager
let manager: HCaptchaWebViewManager

internal init (_ manager: HCaptchaWebViewManager, token: String? = nil, error: HCaptchaError? = nil) {
self.manager = manager
self.token = token
self.error = error
}
Expand All @@ -35,6 +39,8 @@ public class HCaptchaResult: NSObject {
*/
@objc
public func dematerialize() throws -> String {
manager.resultHandled = true

if let token = self.token {
return token
}
Expand Down
45 changes: 45 additions & 0 deletions HCaptcha/Classes/HCaptchaWebViewManager+WKNavigationDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// HCaptchaWebViewManager+WKNavigationDelegate.swift
// HCaptcha
//
// Copyright © 2024 HCaptcha. All rights reserved.
//

import Foundation
import WebKit

extension HCaptchaWebViewManager: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if navigationAction.targetFrame == nil, let url = navigationAction.request.url, urlOpener.canOpenURL(url) {
urlOpener.openURL(url)
}
decisionHandler(WKNavigationActionPolicy.allow)
}

/// 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)))
}

/// 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)))
}

/// Tells the delegate that the web view’s content process was terminated.
func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
Log.debug("WebViewManager.webViewWebContentProcessDidTerminate")
let kHCaptchaErrorWebViewProcessDidTerminate = -1
let kHCaptchaErrorDomain = "com.hcaptcha.sdk-ios"
let error = NSError(domain: kHCaptchaErrorDomain,
code: kHCaptchaErrorWebViewProcessDidTerminate,
userInfo: [
NSLocalizedDescriptionKey: "WebView web content process did terminate",
NSLocalizedRecoverySuggestionErrorKey: "Call HCaptcha.reset()"])
completion?(HCaptchaResult(self, error: .unexpected(error)))
didFinishLoading = false
}
}
72 changes: 25 additions & 47 deletions HCaptcha/Classes/HCaptchaWebViewManager.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// swiftlint:disable file_length
//
// HCaptchaWebViewManager.swift
// HCaptcha
Expand Down Expand Up @@ -39,6 +38,9 @@ internal class HCaptchaWebViewManager: NSObject {
public var shouldSkipForTests = false
#endif

/// True if validation token was dematerialized
internal var resultHandled: Bool = false

/// Sends the result message
var completion: ((HCaptchaResult) -> Void)?

Expand Down Expand Up @@ -67,7 +69,7 @@ internal class HCaptchaWebViewManager: NSObject {
fileprivate var decoder: HCaptchaDecoder!

/// Indicates if the script has already been loaded by the `webView`
fileprivate var didFinishLoading = false {
internal var didFinishLoading = false {
didSet {
if didFinishLoading {
onDidFinishLoading?()
Expand Down Expand Up @@ -114,7 +116,7 @@ internal class HCaptchaWebViewManager: NSObject {
}()

/// Responsible for external link handling
fileprivate let urlOpener: HCaptchaURLOpener
internal let urlOpener: HCaptchaURLOpener

/**
- parameters:
Expand Down Expand Up @@ -163,9 +165,11 @@ internal class HCaptchaWebViewManager: NSObject {
*/
func validate(on view: UIView) {
Log.debug("WebViewManager.validate on: \(view)")
resultHandled = false

#if DEBUG
guard !shouldSkipForTests else {
completion?(HCaptchaResult(token: ""))
completion?(HCaptchaResult(self, token: ""))
return
}
#endif
Expand All @@ -182,6 +186,7 @@ internal class HCaptchaWebViewManager: NSObject {
Log.debug("WebViewManager.stop")
stopInitWebViewConfiguration = true
webView.stopLoading()
resultHandled = true
}

/**
Expand All @@ -193,6 +198,7 @@ internal class HCaptchaWebViewManager: NSObject {
Log.debug("WebViewManager.reset")
configureWebViewDispatchToken = UUID()
stopInitWebViewConfiguration = false
resultHandled = false
if didFinishLoading {
executeJS(command: .reset)
didFinishLoading = false
Expand Down Expand Up @@ -229,8 +235,15 @@ fileprivate 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(token: token))
case .token(let token):
completion?(HCaptchaResult(self, token: token))
case .error(let error):
handle(error: error)
onEvent?(.error, error)
Expand All @@ -246,11 +259,10 @@ fileprivate extension HCaptchaWebViewManager {

private func handle(error: HCaptchaError) {
switch error {
case HCaptchaError.challengeClosed:
completion?(HCaptchaResult(error: error))
case HCaptchaError.challengeClosed: completion?(HCaptchaResult(self, error: error))
case HCaptchaError.networkError:
if let completion = completion {
completion(HCaptchaResult(error: error))
completion(HCaptchaResult(self, error: error))
} else {
lastError = error
}
Expand All @@ -259,7 +271,7 @@ fileprivate extension HCaptchaWebViewManager {
reset()
validate(on: view)
} else {
completion?(HCaptchaResult(error: error))
completion?(HCaptchaResult(self, error: error))
}
}
}
Expand Down Expand Up @@ -342,9 +354,11 @@ fileprivate extension HCaptchaWebViewManager {
guard didLoad else {
if let error = lastError {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
Log.debug("WebViewManager complete with pendingError: \(error)")
self?.completion?(HCaptchaResult(error: error))
self?.lastError = nil

self.completion?(HCaptchaResult(self, error: error))
self.lastError = nil
}
if error == .networkError {
Log.debug("WebViewManager reloads html after \(error) error")
Expand All @@ -364,39 +378,3 @@ fileprivate extension HCaptchaWebViewManager {
executeJS(command: command, didLoad: self.didFinishLoading)
}
}

extension HCaptchaWebViewManager: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if navigationAction.targetFrame == nil, let url = navigationAction.request.url, urlOpener.canOpenURL(url) {
urlOpener.openURL(url)
}
decisionHandler(WKNavigationActionPolicy.allow)
}

/// 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(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(error: .unexpected(error)))
}

/// Tells the delegate that the web view’s content process was terminated.
func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
Log.debug("WebViewManager.webViewWebContentProcessDidTerminate")
let kHCaptchaErrorWebViewProcessDidTerminate = -1
let kHCaptchaErrorDomain = "com.hcaptcha.sdk-ios"
let error = NSError(domain: kHCaptchaErrorDomain,
code: kHCaptchaErrorWebViewProcessDidTerminate,
userInfo: [
NSLocalizedDescriptionKey: "WebView web content process did terminate",
NSLocalizedRecoverySuggestionErrorKey: "Call HCaptcha.reset()"])
completion?(HCaptchaResult(error: .unexpected(error)))
didFinishLoading = false
}
}

0 comments on commit 8ce8926

Please sign in to comment.