From 78248f1b78324d11c9d14ce30950dba23d1def74 Mon Sep 17 00:00:00 2001 From: Matheus Gois Date: Fri, 6 Jan 2023 11:03:15 -0300 Subject: [PATCH 1/7] Improve animation and view frame --- ReCaptcha/Classes/ReCaptchaWebViewManager.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ReCaptcha/Classes/ReCaptchaWebViewManager.swift b/ReCaptcha/Classes/ReCaptchaWebViewManager.swift index b808040..68d41a0 100644 --- a/ReCaptcha/Classes/ReCaptchaWebViewManager.swift +++ b/ReCaptcha/Classes/ReCaptchaWebViewManager.swift @@ -128,14 +128,23 @@ internal class ReCaptchaWebViewManager { Starts the challenge validation */ - func validate(on view: UIView) { + func validate(on view: UIView, animated: Bool = false) { #if DEBUG guard !shouldSkipForTests else { completion?(.token("")) return } #endif + if animated { + webView.alpha = 0 + + UIView.animate(withDuration: 0.2, delay: .zero, options: [.transitionCrossDissolve]) { + self.webView.alpha = 1 + } + } webView.isHidden = false + webView.frame = view.frame + view.addSubview(webView) executeJS(command: .execute) From bbc95618aea6b1b645b066a3d9663ff5fb61c86d Mon Sep 17 00:00:00 2001 From: Matheus Gois Date: Fri, 6 Jan 2023 11:04:21 -0300 Subject: [PATCH 2/7] Improve open webview with animation option --- ReCaptcha/Classes/ReCaptcha.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReCaptcha/Classes/ReCaptcha.swift b/ReCaptcha/Classes/ReCaptcha.swift index d552175..7fb46f9 100644 --- a/ReCaptcha/Classes/ReCaptcha.swift +++ b/ReCaptcha/Classes/ReCaptcha.swift @@ -163,11 +163,11 @@ public class ReCaptcha { Starts the challenge validation */ - public func validate(on view: UIView, resetOnError: Bool = true, completion: @escaping (ReCaptchaResult) -> Void) { + public func validate(on view: UIView, animated: Bool = false, resetOnError: Bool = true, completion: @escaping (ReCaptchaResult) -> Void) { manager.shouldResetOnError = resetOnError manager.completion = completion - manager.validate(on: view) + manager.validate(on: view, animated: animated) } From 37d40e93542523ca56b4e2f330254ddae7896700 Mon Sep 17 00:00:00 2001 From: Matheus Gois Date: Fri, 6 Jan 2023 15:14:32 -0300 Subject: [PATCH 3/7] =?UTF-8?q?feat(Feature/Style-And-Destroy):=20Swift=20?= =?UTF-8?q?Format=20And=20Add=20Option=20To=20Remove=20Webview=20From=20Su?= =?UTF-8?q?per=20View=20=F0=9F=94=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Example/ReCaptcha/AppDelegate.swift | 5 +- Example/ReCaptcha/ViewController.swift | 15 ++-- .../Core/ReCaptchaDecoder__Tests.swift | 19 ---- .../Core/ReCaptchaResult__Tests.swift | 7 +- .../Core/ReCaptchaWebViewManager__Tests.swift | 8 +- .../Core/ReCaptcha__Tests.swift | 12 ++- .../Helpers/ReCaptchaDecoder+Helper.swift | 12 +-- .../Helpers/ReCaptchaError+Equatable.swift | 2 +- .../ReCaptchaWebViewManager+Helpers.swift | 10 +-- .../Helpers/Result+Helpers.swift | 5 +- .../RxSwift/ReCaptcha+Rx__Tests.swift | 34 +++---- .../Classes/DispatchQueue+Throttle.swift | 12 +-- ReCaptcha/Classes/ReCaptcha.swift | 88 ++++++++++--------- ReCaptcha/Classes/ReCaptchaDecoder.swift | 18 ++-- ReCaptcha/Classes/ReCaptchaError.swift | 2 +- ReCaptcha/Classes/ReCaptchaResult.swift | 4 +- .../Classes/ReCaptchaWebViewManager.swift | 56 ++++++------ ReCaptcha/Classes/Rx/ReCaptcha+Rx.swift | 22 ++--- ReCaptcha/Classes/String+Dict.swift | 7 +- 19 files changed, 143 insertions(+), 195 deletions(-) diff --git a/Example/ReCaptcha/AppDelegate.swift b/Example/ReCaptcha/AppDelegate.swift index 86054b4..7ef06da 100644 --- a/Example/ReCaptcha/AppDelegate.swift +++ b/Example/ReCaptcha/AppDelegate.swift @@ -13,12 +13,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { + ) -> Bool { // Override point for customization after application launch. - return true + true } } diff --git a/Example/ReCaptcha/ViewController.swift b/Example/ReCaptcha/ViewController.swift index 67b25ad..6e526ab 100644 --- a/Example/ReCaptcha/ViewController.swift +++ b/Example/ReCaptcha/ViewController.swift @@ -11,9 +11,8 @@ import RxCocoa import RxSwift import UIKit - class ViewController: UIViewController { - private struct Constants { + private enum Constants { static let webViewTag = 123 static let testLabelTag = 321 } @@ -24,11 +23,11 @@ class ViewController: UIViewController { private var locale: Locale? private var endpoint = ReCaptcha.Endpoint.default - @IBOutlet private weak var label: UILabel! - @IBOutlet private weak var spinner: UIActivityIndicatorView! - @IBOutlet private weak var localeSegmentedControl: UISegmentedControl! - @IBOutlet private weak var endpointSegmentedControl: UISegmentedControl! - @IBOutlet private weak var visibleChallengeSwitch: UISwitch! + @IBOutlet private var label: UILabel! + @IBOutlet private var spinner: UIActivityIndicatorView! + @IBOutlet private var localeSegmentedControl: UISegmentedControl! + @IBOutlet private var endpointSegmentedControl: UISegmentedControl! + @IBOutlet private var visibleChallengeSwitch: UISwitch! override func viewDidLoad() { super.viewDidLoad() @@ -67,7 +66,7 @@ class ViewController: UIViewController { let validate = recaptcha.rx.validate(on: view, resetOnError: false) .catch { error in - return .just("Error \(error)") + .just("Error \(error)") } .debug("validate") .share() diff --git a/Example/ReCaptcha_Tests/Core/ReCaptchaDecoder__Tests.swift b/Example/ReCaptcha_Tests/Core/ReCaptchaDecoder__Tests.swift index bb08513..6a983c2 100644 --- a/Example/ReCaptcha_Tests/Core/ReCaptchaDecoder__Tests.swift +++ b/Example/ReCaptcha_Tests/Core/ReCaptchaDecoder__Tests.swift @@ -11,7 +11,6 @@ import WebKit import XCTest - class ReCaptchaDecoder__Tests: XCTestCase { fileprivate typealias Result = ReCaptchaDecoder.Result @@ -31,7 +30,6 @@ class ReCaptchaDecoder__Tests: XCTestCase { super.tearDown() } - func test__Send_Error() { let exp = expectation(description: "send error message") var result: Result? @@ -41,20 +39,17 @@ class ReCaptchaDecoder__Tests: XCTestCase { exp.fulfill() } - // Send let err = ReCaptchaError.random() decoder.send(error: err) waitForExpectations(timeout: 1) - // Check XCTAssertNotNil(result) XCTAssertEqual(result, .error(err)) } - func test__Decode__Wrong_Format() { let exp = expectation(description: "send unsupported message") var result: Result? @@ -64,19 +59,16 @@ class ReCaptchaDecoder__Tests: XCTestCase { exp.fulfill() } - // Send let message = MockMessage(message: "foobar") decoder.send(message: message) waitForExpectations(timeout: 1) - // Check XCTAssertEqual(result, .error(ReCaptchaError.wrongMessageFormat)) } - func test__Decode__Unexpected_Action() { let exp = expectation(description: "send message with unexpected action") var result: Result? @@ -86,19 +78,16 @@ class ReCaptchaDecoder__Tests: XCTestCase { exp.fulfill() } - // Send let message = MockMessage(message: ["action": "bar"]) decoder.send(message: message) waitForExpectations(timeout: 1) - // Check XCTAssertEqual(result, .error(ReCaptchaError.wrongMessageFormat)) } - func test__Decode__ShowReCaptcha() { let exp = expectation(description: "send showReCaptcha message") var result: Result? @@ -108,19 +97,16 @@ class ReCaptchaDecoder__Tests: XCTestCase { exp.fulfill() } - // Send let message = MockMessage(message: ["action": "showReCaptcha"]) decoder.send(message: message) waitForExpectations(timeout: 1) - // Check XCTAssertEqual(result, .showReCaptcha) } - func test__Decode__Token() { let exp = expectation(description: "send token message") var result: Result? @@ -130,7 +116,6 @@ class ReCaptchaDecoder__Tests: XCTestCase { exp.fulfill() } - // Send let token = String(arc4random()) let message = MockMessage(message: ["token": token]) @@ -138,12 +123,10 @@ class ReCaptchaDecoder__Tests: XCTestCase { waitForExpectations(timeout: 1) - // Check XCTAssertEqual(result, .token(token)) } - func test__Decode__DidLoad() { let exp = expectation(description: "send did load message") var result: Result? @@ -153,14 +136,12 @@ class ReCaptchaDecoder__Tests: XCTestCase { exp.fulfill() } - // Send let message = MockMessage(message: ["action": "didLoad"]) decoder.send(message: message) waitForExpectations(timeout: 1) - // Check XCTAssertEqual(result, .didLoad) } diff --git a/Example/ReCaptcha_Tests/Core/ReCaptchaResult__Tests.swift b/Example/ReCaptcha_Tests/Core/ReCaptchaResult__Tests.swift index 7922832..a6333de 100644 --- a/Example/ReCaptcha_Tests/Core/ReCaptchaResult__Tests.swift +++ b/Example/ReCaptcha_Tests/Core/ReCaptchaResult__Tests.swift @@ -9,7 +9,6 @@ @testable import ReCaptcha import XCTest - class ReCaptchaResult__Tests: XCTestCase { func test__Get_Token() { let token = UUID().uuidString @@ -18,8 +17,7 @@ class ReCaptchaResult__Tests: XCTestCase { do { let value = try result.dematerialize() XCTAssertEqual(value, token) - } - catch let err { + } catch let err { XCTFail(err.localizedDescription) } } @@ -31,8 +29,7 @@ class ReCaptchaResult__Tests: XCTestCase { do { _ = try result.dematerialize() XCTFail("Shouldn't have completed") - } - catch let err { + } catch let err { XCTAssertEqual(err as? ReCaptchaError, error) } } diff --git a/Example/ReCaptcha_Tests/Core/ReCaptchaWebViewManager__Tests.swift b/Example/ReCaptcha_Tests/Core/ReCaptchaWebViewManager__Tests.swift index 2a9e5b6..3d66fdc 100644 --- a/Example/ReCaptcha_Tests/Core/ReCaptchaWebViewManager__Tests.swift +++ b/Example/ReCaptcha_Tests/Core/ReCaptchaWebViewManager__Tests.swift @@ -11,7 +11,6 @@ import WebKit import XCTest - class ReCaptchaWebViewManager__Tests: XCTestCase { fileprivate var apiKey: String! @@ -50,13 +49,11 @@ class ReCaptchaWebViewManager__Tests: XCTestCase { waitForExpectations(timeout: 10) - // Verify XCTAssertNotNil(result1) XCTAssertNil(result1?.error) XCTAssertEqual(result1?.token, apiKey) - // Validate again let exp2 = expectation(description: "reload token") var result2: ReCaptchaResult? @@ -69,14 +66,12 @@ class ReCaptchaWebViewManager__Tests: XCTestCase { waitForExpectations(timeout: 10) - // Verify XCTAssertNotNil(result2) XCTAssertNil(result2?.error) XCTAssertEqual(result2?.token, apiKey) } - func test__Validate__Show_ReCaptcha() { let exp = expectation(description: "show recaptcha") @@ -93,7 +88,6 @@ class ReCaptchaWebViewManager__Tests: XCTestCase { waitForExpectations(timeout: 10) } - func test__Validate__Message_Error() { var result: ReCaptchaResult? let exp = expectation(description: "message error") @@ -140,7 +134,7 @@ class ReCaptchaWebViewManager__Tests: XCTestCase { XCTAssertNil(result?.token) switch result!.error! { - case .unexpected(let error as NSError): + case let .unexpected(error as NSError): XCTAssertEqual(error.code, WKError.javaScriptExceptionOccurred.rawValue) default: XCTFail("Unexpected error received") diff --git a/Example/ReCaptcha_Tests/Core/ReCaptcha__Tests.swift b/Example/ReCaptcha_Tests/Core/ReCaptcha__Tests.swift index 232b508..d2c2ae2 100644 --- a/Example/ReCaptcha_Tests/Core/ReCaptcha__Tests.swift +++ b/Example/ReCaptcha_Tests/Core/ReCaptcha__Tests.swift @@ -11,10 +11,9 @@ import AppSwizzle import RxSwift import XCTest - class ReCaptcha__Tests: XCTestCase { - fileprivate struct Constants { - struct InfoDictKeys { + fileprivate enum Constants { + enum InfoDictKeys { static let APIKey = "ReCaptchaKey" static let Domain = "ReCaptchaDomain" } @@ -123,10 +122,9 @@ class ReCaptcha__Tests: XCTestCase { } } - -private extension Bundle { - @objc func failHTMLLoad(_ resource: String, type: String) -> String? { - guard resource == "recaptcha" && type == "html" else { +extension Bundle { + @objc fileprivate func failHTMLLoad(_ resource: String, type: String) -> String? { + guard resource == "recaptcha", type == "html" else { return failHTMLLoad(resource, type: type) } diff --git a/Example/ReCaptcha_Tests/Helpers/ReCaptchaDecoder+Helper.swift b/Example/ReCaptcha_Tests/Helpers/ReCaptchaDecoder+Helper.swift index 63b2a8b..6997342 100644 --- a/Example/ReCaptcha_Tests/Helpers/ReCaptchaDecoder+Helper.swift +++ b/Example/ReCaptcha_Tests/Helpers/ReCaptchaDecoder+Helper.swift @@ -12,17 +12,18 @@ import WebKit class MockMessage: WKScriptMessage { override var body: Any { - return storedBody + storedBody } fileprivate let storedBody: Any init(message: Any) { - storedBody = message + self.storedBody = message } } // MARK: - Decoder Helpers + extension ReCaptchaDecoder { func send(message: MockMessage) { userContentController(WKUserContentController(), didReceive: message) @@ -30,9 +31,10 @@ extension ReCaptchaDecoder { } // MARK: - Result Helpers + extension ReCaptchaDecoder.Result: Equatable { var error: ReCaptchaError? { - guard case .error(let error) = self else { return nil } + guard case let .error(error) = self else { return nil } return error } @@ -42,10 +44,10 @@ extension ReCaptchaDecoder.Result: Equatable { (.didLoad, .didLoad): return true - case (.token(let lht), .token(let rht)): + case let (.token(lht), .token(rht)): return lht == rht - case (.error(let lhe), .error(let rhe)): + case let (.error(lhe), .error(rhe)): return lhe == rhe default: diff --git a/Example/ReCaptcha_Tests/Helpers/ReCaptchaError+Equatable.swift b/Example/ReCaptcha_Tests/Helpers/ReCaptchaError+Equatable.swift index 264edfa..87d0861 100644 --- a/Example/ReCaptcha_Tests/Helpers/ReCaptchaError+Equatable.swift +++ b/Example/ReCaptcha_Tests/Helpers/ReCaptchaError+Equatable.swift @@ -20,7 +20,7 @@ extension ReCaptchaError: Equatable { (.responseExpired, .responseExpired), (.failedRender, .failedRender): return true - case (.unexpected(let lhe as NSError), .unexpected(let rhe as NSError)): + case let (.unexpected(lhe as NSError), .unexpected(rhe as NSError)): return lhe == rhe default: return false diff --git a/Example/ReCaptcha_Tests/Helpers/ReCaptchaWebViewManager+Helpers.swift b/Example/ReCaptcha_Tests/Helpers/ReCaptchaWebViewManager+Helpers.swift index 7d4f35f..e2eeb3d 100644 --- a/Example/ReCaptcha_Tests/Helpers/ReCaptchaWebViewManager+Helpers.swift +++ b/Example/ReCaptcha_Tests/Helpers/ReCaptchaWebViewManager+Helpers.swift @@ -11,11 +11,9 @@ import Foundation import WebKit extension ReCaptchaWebViewManager { - private static let unformattedHTML: String! = { - Bundle(for: ReCaptchaWebViewManager__Tests.self) - .path(forResource: "mock", ofType: "html") - .flatMap { try? String(contentsOfFile: $0) } - }() + private static let unformattedHTML: String! = Bundle(for: ReCaptchaWebViewManager__Tests.self) + .path(forResource: "mock", ofType: "html") + .flatMap { try? String(contentsOfFile: $0) } convenience init( messageBody: String = "", @@ -42,7 +40,7 @@ extension ReCaptchaWebViewManager { } func validate(on view: UIView, resetOnError: Bool = true, completion: @escaping (ReCaptchaResult) -> Void) { - self.shouldResetOnError = resetOnError + shouldResetOnError = resetOnError self.completion = completion validate(on: view) diff --git a/Example/ReCaptcha_Tests/Helpers/Result+Helpers.swift b/Example/ReCaptcha_Tests/Helpers/Result+Helpers.swift index c39a055..fa2969e 100644 --- a/Example/ReCaptcha_Tests/Helpers/Result+Helpers.swift +++ b/Example/ReCaptcha_Tests/Helpers/Result+Helpers.swift @@ -8,15 +8,14 @@ @testable import ReCaptcha - extension ReCaptchaResult { var token: String? { - guard case .token(let value) = self else { return nil } + guard case let .token(value) = self else { return nil } return value } var error: ReCaptchaError? { - guard case .error(let error) = self else { return nil } + guard case let .error(error) = self else { return nil } return error } } diff --git a/Example/ReCaptcha_Tests/RxSwift/ReCaptcha+Rx__Tests.swift b/Example/ReCaptcha_Tests/RxSwift/ReCaptcha+Rx__Tests.swift index 9e6c43c..1ac9ebe 100644 --- a/Example/ReCaptcha_Tests/RxSwift/ReCaptcha+Rx__Tests.swift +++ b/Example/ReCaptcha_Tests/RxSwift/ReCaptcha+Rx__Tests.swift @@ -13,7 +13,6 @@ import RxCocoa import RxSwift import XCTest - class ReCaptcha_Rx__Tests: XCTestCase { fileprivate var apiKey: String! @@ -33,7 +32,6 @@ class ReCaptcha_Rx__Tests: XCTestCase { super.tearDown() } - func test__Validate__Token() { let recaptcha = ReCaptcha(manager: ReCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey)) recaptcha.configureWebView { _ in @@ -48,13 +46,11 @@ class ReCaptcha_Rx__Tests: XCTestCase { // Verify XCTAssertEqual(result, apiKey) - } - catch let error { + } catch { XCTFail(error.localizedDescription) } } - func test__Validate__Show_ReCaptcha() { let recaptcha = ReCaptcha( manager: ReCaptchaWebViewManager(messageBody: "{action: \"showReCaptcha\"}", apiKey: apiKey) @@ -73,14 +69,12 @@ class ReCaptcha_Rx__Tests: XCTestCase { .single() XCTFail("should have thrown exception") - } - catch let error { + } catch { XCTAssertEqual(String(describing: error), RxError.timeout.debugDescription) XCTAssertTrue(didConfigureWebView) } } - func test__Validate__Error() { let recaptcha = ReCaptcha(manager: ReCaptchaWebViewManager(messageBody: "\"foobar\"", apiKey: apiKey)) recaptcha.configureWebView { _ in @@ -94,8 +88,7 @@ class ReCaptcha_Rx__Tests: XCTestCase { .single() XCTFail("should have thrown exception") - } - catch let error { + } catch { XCTAssertEqual(error as? ReCaptchaError, .wrongMessageFormat) } } @@ -111,8 +104,7 @@ class ReCaptcha_Rx__Tests: XCTestCase { try recaptcha.rx.didFinishLoading .toBlocking() .first() - } - catch let error { + } catch { XCTFail(error.localizedDescription) } } @@ -134,8 +126,7 @@ class ReCaptcha_Rx__Tests: XCTestCase { XCTAssertEqual(result.count, 2) reset.dispose() - } - catch let error { + } catch { XCTFail(error.localizedDescription) } } @@ -149,8 +140,7 @@ class ReCaptcha_Rx__Tests: XCTestCase { .first() XCTFail("should have timed out") - } - catch let error { + } catch { XCTAssertEqual(String(describing: error), RxError.timeout.debugDescription) } @@ -160,8 +150,7 @@ class ReCaptcha_Rx__Tests: XCTestCase { try recaptcha.rx.didFinishLoading .toBlocking() .first() - } - catch let error { + } catch { XCTFail(error.localizedDescription) } } @@ -218,8 +207,7 @@ class ReCaptcha_Rx__Tests: XCTestCase { _ = try recaptcha.rx.validate(on: presenterView, resetOnError: false) .toBlocking() .single() - } - catch let error { + } catch { XCTAssertEqual(error as? ReCaptchaError, .wrongMessageFormat) // Resets after failure @@ -234,8 +222,7 @@ class ReCaptcha_Rx__Tests: XCTestCase { .single() XCTAssertEqual(result, apiKey) - } - catch let error { + } catch { XCTFail(error.localizedDescription) } } @@ -257,8 +244,7 @@ class ReCaptcha_Rx__Tests: XCTestCase { .single() XCTAssertEqual(result, apiKey) - } - catch let error { + } catch { XCTFail(error.localizedDescription) } } diff --git a/ReCaptcha/Classes/DispatchQueue+Throttle.swift b/ReCaptcha/Classes/DispatchQueue+Throttle.swift index 64566e5..ae429f2 100644 --- a/ReCaptcha/Classes/DispatchQueue+Throttle.swift +++ b/ReCaptcha/Classes/DispatchQueue+Throttle.swift @@ -27,7 +27,7 @@ extension DispatchQueue { - deadline: The timespan to delay a closure execution - context: The context in which the throttle should be executed - action: The closure to be executed - + Delays a closure execution and ensures no other executions are made during deadline for that context */ func throttle(deadline: DispatchTime, context: AnyHashable = nilContext, action: @escaping () -> Void) { @@ -66,12 +66,12 @@ extension DispatchQueue { } /** - - parameters: - - token: The control token for each dispatched action - - action: The closure to be executed + - parameters: + - token: The control token for each dispatched action + - action: The closure to be executed - Dispatch the action only once for each given token - */ + Dispatch the action only once for each given token + */ static func once(token: AnyHashable, action: () -> Void) { guard !onceTokenStorage.contains(token) else { return } diff --git a/ReCaptcha/Classes/ReCaptcha.swift b/ReCaptcha/Classes/ReCaptcha.swift index 7fb46f9..285b5db 100644 --- a/ReCaptcha/Classes/ReCaptcha.swift +++ b/ReCaptcha/Classes/ReCaptcha.swift @@ -9,12 +9,11 @@ import Foundation import WebKit - /** -*/ + */ public class ReCaptcha { - fileprivate struct Constants { - struct InfoDictKeys { + fileprivate enum Constants { + enum InfoDictKeys { static let APIKey = "ReCaptchaKey" static let Domain = "ReCaptchaDomain" } @@ -60,7 +59,7 @@ public class ReCaptcha { guard let cocoapodsBundle = bundle .path(forResource: "ReCaptcha", ofType: "bundle") .flatMap(Bundle.init(path:)) else { - return bundle + return bundle } return cocoapodsBundle @@ -110,7 +109,7 @@ public class ReCaptcha { - baseURL: The base URL sent to the ReCaptcha init - endpoint: The ReCaptcha endpoint to be used. - locale: A locale value to translate ReCaptcha into a different language - + Initializes a ReCaptcha object Both `apiKey` and `baseURL` may be nil, in which case the lib will look for entries of `ReCaptchaKey` and @@ -147,22 +146,22 @@ public class ReCaptcha { } /** - - parameter manager: A ReCaptchaWebViewManager instance. + - parameter manager: A ReCaptchaWebViewManager instance. - Initializes ReCaptcha with the given manager - */ + Initializes ReCaptcha with the given manager + */ init(manager: ReCaptchaWebViewManager) { self.manager = manager } /** - - parameters: - - view: The view that should present the webview. - - resetOnError: If ReCaptcha should be reset if it errors. Defaults to `true`. - - completion: A closure that receives a ReCaptchaResult which may contain a valid result token. + - parameters: + - view: The view that should present the webview. + - resetOnError: If ReCaptcha should be reset if it errors. Defaults to `true`. + - completion: A closure that receives a ReCaptchaResult which may contain a valid result token. - Starts the challenge validation - */ + Starts the challenge validation + */ public func validate(on view: UIView, animated: Bool = false, resetOnError: Bool = true, completion: @escaping (ReCaptchaResult) -> Void) { manager.shouldResetOnError = resetOnError manager.completion = completion @@ -170,87 +169,90 @@ public class ReCaptcha { manager.validate(on: view, animated: animated) } - /// Stops the execution of the webview public func stop() { manager.stop() } + /// Remove webview from superview + public func destroy() { + manager.destroy() + } /** - - parameter configure: A closure that receives an instance of `WKWebView` for configuration. + - parameter configure: A closure that receives an instance of `WKWebView` for configuration. - Provides a closure to configure the webview for presentation if necessary. + Provides a closure to configure the webview for presentation if necessary. - If presentation is required, the webview will already be a subview of `presenterView` if one is provided. Otherwise - it might need to be added in a view currently visible. - */ + If presentation is required, the webview will already be a subview of `presenterView` if one is provided. Otherwise + it might need to be added in a view currently visible. + */ public func configureWebView(_ configure: @escaping (WKWebView) -> Void) { manager.configureWebView = configure } /** - Resets the ReCaptcha. + Resets the ReCaptcha. - The reset is achieved by calling `grecaptcha.reset()` on the JS API. - */ + The reset is achieved by calling `grecaptcha.reset()` on the JS API. + */ public func reset() { manager.reset() } /** - - parameter closure: A closure that is called when the JS bundle finishes loading. + - parameter closure: A closure that is called when the JS bundle finishes loading. - Provides a closure to be notified when the webview finishes loading JS resources. + Provides a closure to be notified when the webview finishes loading JS resources. - The closure may be called multiple times since the resources may also be loaded multiple times - in case of error or reset. This may also be immediately called if the resources have already - finished loading when you set the closure. - */ + The closure may be called multiple times since the resources may also be loaded multiple times + in case of error or reset. This may also be immediately called if the resources have already + finished loading when you set the closure. + */ public func didFinishLoading(_ closure: (() -> Void)?) { manager.onDidFinishLoading = closure } // MARK: - Development -#if DEBUG + #if DEBUG /// Forces the challenge widget to be explicitly displayed. public var forceVisibleChallenge: Bool { - get { return manager.forceVisibleChallenge } + get { manager.forceVisibleChallenge } set { manager.forceVisibleChallenge = newValue } } /** - Allows validation stubbing for testing + Allows validation stubbing for testing + + When this property is set to `true`, every call to `validate()` will immediately be resolved with `.token("")`. - When this property is set to `true`, every call to `validate()` will immediately be resolved with `.token("")`. - - Use only when testing your application. - */ + Use only when testing your application. + */ public var shouldSkipForTests: Bool { - get { return manager.shouldSkipForTests } + get { manager.shouldSkipForTests } set { manager.shouldSkipForTests = newValue } } -#endif + #endif } // MARK: - Private Methods -private extension ReCaptcha.Config { +extension ReCaptcha.Config { /** - parameter url: The URL to be fixed - returns: An URL with scheme If the given URL has no scheme, prepends `http://` to it and return the fixed URL. */ - static func fixSchemeIfNeeded(for url: URL) -> URL { + fileprivate static func fixSchemeIfNeeded(for url: URL) -> URL { guard url.scheme?.isEmpty != false else { return url } -#if DEBUG + #if DEBUG print("⚠️ WARNING! Protocol not found for ReCaptcha domain (\(url))! You should add http:// or https:// to it!") -#endif + #endif if let fixedURL = URL(string: "http://" + url.absoluteString) { return fixedURL diff --git a/ReCaptcha/Classes/ReCaptchaDecoder.swift b/ReCaptcha/Classes/ReCaptchaDecoder.swift index 876d5b4..ae98091 100644 --- a/ReCaptcha/Classes/ReCaptchaDecoder.swift +++ b/ReCaptcha/Classes/ReCaptchaDecoder.swift @@ -9,7 +9,6 @@ import Foundation import WebKit - /** The Decoder of javascript messages from the webview */ internal class ReCaptchaDecoder: NSObject { @@ -33,7 +32,7 @@ internal class ReCaptchaDecoder: NSObject { } /// The closure that receives messages - fileprivate let sendMessage: ((Result) -> Void) + fileprivate let sendMessage: (Result) -> Void /** - parameter didReceiveMessage: A closure that receives a ReCaptchaDecoder.Result @@ -41,12 +40,11 @@ internal class ReCaptchaDecoder: NSObject { Initializes a decoder with a completion closure. */ init(didReceiveMessage: @escaping (Result) -> Void) { - sendMessage = didReceiveMessage + self.sendMessage = didReceiveMessage super.init() } - /** - parameter error: The error to be sent. @@ -57,7 +55,6 @@ internal class ReCaptchaDecoder: NSObject { } } - // MARK: Script Handler /** Makes ReCaptchaDecoder conform to `WKScriptMessageHandler` @@ -72,12 +69,11 @@ extension ReCaptchaDecoder: WKScriptMessageHandler { } } - // MARK: - Result /** Private methods on `ReCaptchaDecoder.Result` */ -fileprivate extension ReCaptchaDecoder.Result { +extension ReCaptchaDecoder.Result { /** - parameter response: A dictionary containing the message to be parsed @@ -85,14 +81,12 @@ fileprivate extension ReCaptchaDecoder.Result { Parses a dict received from the webview onto a `ReCaptchaDecoder.Result` */ - static func from(response: [String: Any]) -> ReCaptchaDecoder.Result { + fileprivate static func from(response: [String: Any]) -> ReCaptchaDecoder.Result { if let token = response["token"] as? String { return .token(token) - } - else if let message = response["log"] as? String { + } else if let message = response["log"] as? String { return .log(message) - } - else if let error = response["error"] as? Int { + } else if let error = response["error"] as? Int { return from(error) } diff --git a/ReCaptcha/Classes/ReCaptchaError.swift b/ReCaptcha/Classes/ReCaptchaError.swift index 7c2ea6d..d1006f0 100644 --- a/ReCaptcha/Classes/ReCaptchaError.swift +++ b/ReCaptcha/Classes/ReCaptchaError.swift @@ -37,7 +37,7 @@ public enum ReCaptchaError: Error, CustomStringConvertible { /// A human-readable description for each error public var description: String { switch self { - case .unexpected(let error): + case let .unexpected(error): return "Unexpected Error: \(error)" case .htmlLoadError: diff --git a/ReCaptcha/Classes/ReCaptchaResult.swift b/ReCaptcha/Classes/ReCaptchaResult.swift index 48412d5..76b4c78 100644 --- a/ReCaptcha/Classes/ReCaptchaResult.swift +++ b/ReCaptcha/Classes/ReCaptchaResult.swift @@ -28,10 +28,10 @@ public enum ReCaptchaResult { */ public func dematerialize() throws -> String { switch self { - case .token(let token): + case let .token(token): return token - case .error(let error): + case let .error(error): throw error } } diff --git a/ReCaptcha/Classes/ReCaptchaWebViewManager.swift b/ReCaptcha/Classes/ReCaptchaWebViewManager.swift index 68d41a0..5457838 100644 --- a/ReCaptcha/Classes/ReCaptchaWebViewManager.swift +++ b/ReCaptcha/Classes/ReCaptchaWebViewManager.swift @@ -9,7 +9,6 @@ import Foundation import WebKit - /** Handles comunications with the webview containing the ReCaptcha challenge. */ internal class ReCaptchaWebViewManager { @@ -18,13 +17,13 @@ internal class ReCaptchaWebViewManager { case reset = "reset();" } - fileprivate struct Constants { + fileprivate enum Constants { static let ExecuteJSCommand = "execute();" static let ResetCommand = "reset();" static let BotUserAgent = "Googlebot/2.1" } -#if DEBUG + #if DEBUG /// Forces the challenge to be explicitly displayed. var forceVisibleChallenge = false { didSet { @@ -39,7 +38,7 @@ internal class ReCaptchaWebViewManager { /// Allows validation stubbing for testing public var shouldSkipForTests = false -#endif + #endif /// Sends the result message var completion: ((ReCaptchaResult) -> Void)? @@ -63,10 +62,10 @@ internal class ReCaptchaWebViewManager { var shouldResetOnError = true /// The JS message recoder - fileprivate var decoder: ReCaptchaDecoder! + private var decoder: ReCaptchaDecoder! /// Indicates if the script has already been loaded by the `webView` - fileprivate var didFinishLoading = false { + private var didFinishLoading = false { didSet { if didFinishLoading { onDidFinishLoading?() @@ -75,10 +74,10 @@ internal class ReCaptchaWebViewManager { } /// The observer for `.UIWindowDidBecomeVisible` - fileprivate var observer: NSObjectProtocol? + private var observer: NSObjectProtocol? /// The endpoint url being used - fileprivate var endpoint: String + private var endpoint: String /// The webview that executes JS code lazy var webView: WKWebView = { @@ -110,9 +109,8 @@ internal class ReCaptchaWebViewManager { if let window = UIApplication.shared.keyWindow { setupWebview(on: window, html: formattedHTML, url: baseURL) - } - else { - observer = NotificationCenter.default.addObserver( + } else { + self.observer = NotificationCenter.default.addObserver( forName: UIWindow.didBecomeVisibleNotification, object: nil, queue: nil @@ -128,13 +126,13 @@ internal class ReCaptchaWebViewManager { Starts the challenge validation */ - func validate(on view: UIView, animated: Bool = false) { -#if DEBUG + func validate(on view: UIView, animated: Bool = false) { + #if DEBUG guard !shouldSkipForTests else { completion?(.token("")) return } -#endif + #endif if animated { webView.alpha = 0 @@ -150,12 +148,15 @@ internal class ReCaptchaWebViewManager { executeJS(command: .execute) } - - /// Stops the execution of the webview func stop() { webView.stopLoading() } + func destroy() { + stop() + webView.removeFromSuperview() + } + /** Resets the ReCaptcha. @@ -172,13 +173,13 @@ internal class ReCaptchaWebViewManager { /** Private methods for ReCaptchaWebViewManager */ -fileprivate extension ReCaptchaWebViewManager { +extension ReCaptchaWebViewManager { /** - returns: An instance of `WKWebViewConfiguration` Creates a `WKWebViewConfiguration` to be added to the `WKWebView` instance. */ - func buildConfiguration() -> WKWebViewConfiguration { + private func buildConfiguration() -> WKWebViewConfiguration { let controller = WKUserContentController() controller.add(decoder, name: "recaptcha") @@ -193,23 +194,22 @@ fileprivate extension ReCaptchaWebViewManager { Handles the decoder results received from the webview */ - func handle(result: ReCaptchaDecoder.Result) { + private func handle(result: ReCaptchaDecoder.Result) { switch result { - case .token(let token): + case let .token(token): completion?(.token(token)) - case .error(let error): + case let .error(error): if shouldResetOnError, let view = webView.superview { reset() validate(on: view) - } - else { + } else { completion?(.error(error)) } case .showReCaptcha: DispatchQueue.once(token: configureWebViewDispatchToken) { [weak self] in - guard let `self` = self else { return } + guard let self = self else { return } self.configureWebView?(self.webView) } @@ -219,9 +219,9 @@ fileprivate extension ReCaptchaWebViewManager { executeJS(command: .execute) } - case .log(let message): + case let .log(message): #if DEBUG - print("[JS LOG]:", message) + print("[JS LOG]:", message) #endif } } @@ -234,7 +234,7 @@ fileprivate extension ReCaptchaWebViewManager { Adds the webview to a valid UIView and loads the initial HTML file */ - func setupWebview(on window: UIWindow, html: String, url: URL) { + private func setupWebview(on window: UIWindow, html: String, url: URL) { window.addSubview(webView) webView.loadHTMLString(html, baseURL: url) @@ -250,7 +250,7 @@ fileprivate extension ReCaptchaWebViewManager { Executes the JS command that loads the ReCaptcha challenge. This method has no effect if the webview hasn't finished loading. */ - func executeJS(command: JSCommand) { + private func executeJS(command: JSCommand) { guard didFinishLoading else { // Hasn't finished loading all the resources return diff --git a/ReCaptcha/Classes/Rx/ReCaptcha+Rx.swift b/ReCaptcha/Classes/Rx/ReCaptcha+Rx.swift index 100d792..9d7e51d 100644 --- a/ReCaptcha/Classes/Rx/ReCaptcha+Rx.swift +++ b/ReCaptcha/Classes/Rx/ReCaptcha+Rx.swift @@ -13,30 +13,30 @@ import UIKit extension ReCaptcha: ReactiveCompatible {} /// Provides a public extension on ReCaptcha that makes it reactive. -public extension Reactive where Base: ReCaptcha { +extension Reactive where Base: ReCaptcha { /** - parameters: - view: The view that should present the webview. - resetOnError: If ReCaptcha should be reset if it errors. Defaults to `true` - + Starts the challenge validation uppon subscription. The stream's element is a String with the validation token. Sends `stop()` uppon disposal. - + - See: `ReCaptcha.validate(on:resetOnError:completion:)` - See: `ReCaptcha.stop()` */ - func validate(on view: UIView, resetOnError: Bool = true) -> Observable { - return Single.create { [weak base] single in + public func validate(on view: UIView, resetOnError: Bool = true) -> Observable { + Single.create { [weak base] single in base?.validate(on: view, resetOnError: resetOnError) { result in switch result { - case .token(let token): + case let .token(token): single(.success(token)) - case .error(let error): + case let .error(error): single(.failure(error)) } } @@ -55,8 +55,8 @@ public extension Reactive where Base: ReCaptcha { - See: `ReCaptcha.reset()` */ - var reset: AnyObserver { - return AnyObserver { [weak base] event in + public var reset: AnyObserver { + AnyObserver { [weak base] event in guard case .next = event else { return } @@ -72,8 +72,8 @@ public extension Reactive where Base: ReCaptcha { case of error or reset. This may also immediately produce an event if the resources have already finished loading when you subscribe to this Observable. */ - var didFinishLoading: Observable { - return Observable.create { [weak base] (observer: AnyObserver) in + public var didFinishLoading: Observable { + Observable.create { [weak base] (observer: AnyObserver) in base?.didFinishLoading { observer.onNext(()) } return Disposables.create { [weak base] in diff --git a/ReCaptcha/Classes/String+Dict.swift b/ReCaptcha/Classes/String+Dict.swift index 7c1b4e6..86fd355 100644 --- a/ReCaptcha/Classes/String+Dict.swift +++ b/ReCaptcha/Classes/String+Dict.swift @@ -8,7 +8,6 @@ import Foundation - extension String { /** - parameters: @@ -26,8 +25,8 @@ extension String { */ init(format: String, arguments: [String: CustomStringConvertible]) { self.init(describing: arguments.reduce(format) - { (format: String, args: (key: String, value: CustomStringConvertible)) -> String in - format.replacingOccurrences(of: "${\(args.key)}", with: args.value.description) - }) + { (format: String, args: (key: String, value: CustomStringConvertible)) -> String in + format.replacingOccurrences(of: "${\(args.key)}", with: args.value.description) + }) } } From 14fda830ea2915e390de54bc012250d58605c959 Mon Sep 17 00:00:00 2001 From: Matheus Gois Date: Mon, 9 Jan 2023 12:14:14 -0300 Subject: [PATCH 4/7] Update ReCaptchaWebViewManager.swift --- ReCaptcha/Classes/ReCaptchaWebViewManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReCaptcha/Classes/ReCaptchaWebViewManager.swift b/ReCaptcha/Classes/ReCaptchaWebViewManager.swift index 5457838..71d8e8f 100644 --- a/ReCaptcha/Classes/ReCaptchaWebViewManager.swift +++ b/ReCaptcha/Classes/ReCaptchaWebViewManager.swift @@ -141,7 +141,7 @@ internal class ReCaptchaWebViewManager { } } webView.isHidden = false - webView.frame = view.frame + webView.frame = view.bounds view.addSubview(webView) From 01231f5c3e5acacb8c3d423ee5e4b7e2b7dc76e1 Mon Sep 17 00:00:00 2001 From: Matheus Gois Date: Mon, 9 Jan 2023 14:24:47 -0300 Subject: [PATCH 5/7] [TODO] Back code --- ReCaptcha/Classes/ReCaptchaWebViewManager.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReCaptcha/Classes/ReCaptchaWebViewManager.swift b/ReCaptcha/Classes/ReCaptchaWebViewManager.swift index 71d8e8f..5975f1b 100644 --- a/ReCaptcha/Classes/ReCaptchaWebViewManager.swift +++ b/ReCaptcha/Classes/ReCaptchaWebViewManager.swift @@ -128,10 +128,10 @@ internal class ReCaptchaWebViewManager { */ func validate(on view: UIView, animated: Bool = false) { #if DEBUG - guard !shouldSkipForTests else { +// guard !shouldSkipForTests else { completion?(.token("")) return - } +// } #endif if animated { webView.alpha = 0 From 2b434cb1c2b0ce839326d3cda88b38c845ad94ec Mon Sep 17 00:00:00 2001 From: Matheus Gois Date: Mon, 9 Jan 2023 16:27:13 -0300 Subject: [PATCH 6/7] =?UTF-8?q?feat(Master):=20Enable=20Flags=20=E2=9A=97?= =?UTF-8?q?=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReCaptcha/Assets/recaptcha.html | 179 +++++++++--------- .../Classes/ReCaptchaWebViewManager.swift | 20 +- 2 files changed, 101 insertions(+), 98 deletions(-) diff --git a/ReCaptcha/Assets/recaptcha.html b/ReCaptcha/Assets/recaptcha.html index 0f338c3..faa7bd8 100644 --- a/ReCaptcha/Assets/recaptcha.html +++ b/ReCaptcha/Assets/recaptcha.html @@ -1,102 +1,105 @@ - - - - - - - - + if (success) { + post({ action: "didLoad" }); + } else { + post({ error: 29 }) + } + }); + }); + }; + + + + + + diff --git a/ReCaptcha/Classes/ReCaptchaWebViewManager.swift b/ReCaptcha/Classes/ReCaptchaWebViewManager.swift index 5975f1b..e4f7840 100644 --- a/ReCaptcha/Classes/ReCaptchaWebViewManager.swift +++ b/ReCaptcha/Classes/ReCaptchaWebViewManager.swift @@ -94,10 +94,10 @@ internal class ReCaptchaWebViewManager { /** - parameters: - - html: The HTML string to be loaded onto the webview - - apiKey: The Google's ReCaptcha API Key - - baseURL: The URL configured with the API Key - - endpoint: The JS API endpoint to be loaded onto the HTML file. + - html: The HTML string to be loaded onto the webview + - apiKey: The Google's ReCaptcha API Key + - baseURL: The URL configured with the API Key + - endpoint: The JS API endpoint to be loaded onto the HTML file. */ init(html: String, apiKey: String, baseURL: URL, endpoint: String) { self.endpoint = endpoint @@ -128,10 +128,10 @@ internal class ReCaptchaWebViewManager { */ func validate(on view: UIView, animated: Bool = false) { #if DEBUG -// guard !shouldSkipForTests else { + guard !shouldSkipForTests else { completion?(.token("")) return -// } + } #endif if animated { webView.alpha = 0 @@ -228,9 +228,9 @@ extension ReCaptchaWebViewManager { /** - parameters: - - window: The window in which to add the webview - - html: The embedded HTML file - - url: The base URL given to the webview + - window: The window in which to add the webview + - html: The embedded HTML file + - url: The base URL given to the webview Adds the webview to a valid UIView and loads the initial HTML file */ @@ -245,7 +245,7 @@ extension ReCaptchaWebViewManager { /** - parameters: - - command: The JavaScript command to be executed + - command: The JavaScript command to be executed Executes the JS command that loads the ReCaptcha challenge. This method has no effect if the webview hasn't finished loading. From 8211722b509f95b8fce91d26dbc3b6ed8d8d07cc Mon Sep 17 00:00:00 2001 From: Matheus Gois Date: Mon, 9 Jan 2023 16:54:20 -0300 Subject: [PATCH 7/7] =?UTF-8?q?feat(Master):=20Fix=20Errors=20In=20Loader?= =?UTF-8?q?=20=E2=9C=8F=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReCaptcha/Assets/recaptcha.html | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ReCaptcha/Assets/recaptcha.html b/ReCaptcha/Assets/recaptcha.html index faa7bd8..ef8538a 100644 --- a/ReCaptcha/Assets/recaptcha.html +++ b/ReCaptcha/Assets/recaptcha.html @@ -36,10 +36,7 @@ // Removes ReCaptcha dismissal when clicking outside div area try { document.getElementsByTagName("div")[4].outerHTML = ""; - } catch (e) { - console.log(e); - post({ error: 29 }); - } + } catch (e) {} try { // Listens to changes on the div element that presents the ReCaptcha challenge @@ -90,8 +87,6 @@ if (success) { post({ action: "didLoad" }); - } else { - post({ error: 29 }) } }); });