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/Assets/recaptcha.html b/ReCaptcha/Assets/recaptcha.html index 0f338c3..ef8538a 100644 --- a/ReCaptcha/Assets/recaptcha.html +++ b/ReCaptcha/Assets/recaptcha.html @@ -1,102 +1,100 @@ - - - - - - - - + if (success) { + post({ action: "didLoad" }); + } + }); + }); + }; + + + + + + 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 d552175..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,110 +146,113 @@ 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 - */ - public func validate(on view: UIView, resetOnError: Bool = true, completion: @escaping (ReCaptchaResult) -> Void) { + 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 - manager.validate(on: view) + 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 b808040..e4f7840 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 = { @@ -95,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 @@ -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,25 +126,37 @@ internal class ReCaptchaWebViewManager { Starts the challenge validation */ - func validate(on view: UIView) { -#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 + + UIView.animate(withDuration: 0.2, delay: .zero, options: [.transitionCrossDissolve]) { + self.webView.alpha = 1 + } + } webView.isHidden = false + webView.frame = view.bounds + view.addSubview(webView) executeJS(command: .execute) } - - /// Stops the execution of the webview func stop() { webView.stopLoading() } + func destroy() { + stop() + webView.removeFromSuperview() + } + /** Resets the ReCaptcha. @@ -163,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") @@ -184,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) } @@ -210,22 +219,22 @@ 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 } } /** - 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 */ - 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) @@ -236,12 +245,12 @@ fileprivate 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. */ - 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) + }) } }