Fix: Better encapsulation with new achitecture
This commit is contained in:
parent
8a91279bd5
commit
8160d36ef9
|
|
@ -78,7 +78,7 @@
|
|||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
buildConfiguration = "Release"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
|
|
|
|||
|
|
@ -309,4 +309,21 @@ class ReCaptchaWebViewManager__Tests: XCTestCase {
|
|||
XCTAssertNil(result?.error)
|
||||
XCTAssertEqual(result?.token, apiKey)
|
||||
}
|
||||
|
||||
// MARK: Force Challenge Visible
|
||||
|
||||
func test__Force_Visible_Challenge() {
|
||||
let manager = ReCaptchaWebViewManager()
|
||||
|
||||
// Initial value
|
||||
XCTAssertFalse(manager.forceVisibleChallenge)
|
||||
|
||||
// Set True
|
||||
manager.forceVisibleChallenge = true
|
||||
XCTAssertEqual(manager.webView.customUserAgent, "Googlebot/2.1")
|
||||
|
||||
// Set False
|
||||
manager.forceVisibleChallenge = false
|
||||
XCTAssertNotEqual(manager.webView.customUserAgent?.isEmpty, false)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,6 +110,17 @@ class ReCaptcha__Tests: XCTestCase {
|
|||
)
|
||||
XCTAssertEqual(config2?.apiKey, key)
|
||||
}
|
||||
|
||||
func test__Force_Visible_Challenge() {
|
||||
let recaptcha = ReCaptcha(manager: ReCaptchaWebViewManager())
|
||||
|
||||
// Initial value
|
||||
XCTAssertFalse(recaptcha.forceVisibleChallenge)
|
||||
|
||||
// Set true
|
||||
recaptcha.forceVisibleChallenge = true
|
||||
XCTAssertTrue(recaptcha.forceVisibleChallenge)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import Foundation
|
||||
@testable import ReCaptcha
|
||||
|
||||
import WebKit
|
||||
|
||||
extension ReCaptchaWebViewManager {
|
||||
private static let unformattedHTML: String! = {
|
||||
|
|
@ -18,7 +18,7 @@ extension ReCaptchaWebViewManager {
|
|||
}()
|
||||
|
||||
convenience init(
|
||||
messageBody: String,
|
||||
messageBody: String = "",
|
||||
apiKey: String? = nil,
|
||||
endpoint: String? = nil,
|
||||
shouldFail: Bool = false
|
||||
|
|
@ -36,4 +36,15 @@ extension ReCaptchaWebViewManager {
|
|||
endpoint: endpoint ?? localhost.absoluteString
|
||||
)
|
||||
}
|
||||
|
||||
func configureWebView(_ configure: @escaping (WKWebView) -> Void) {
|
||||
configureWebView = configure
|
||||
}
|
||||
|
||||
func validate(on view: UIView, resetOnError: Bool = true, completion: @escaping (ReCaptchaResult) -> Void) {
|
||||
self.shouldResetOnError = resetOnError
|
||||
self.completion = completion
|
||||
|
||||
validate(on: view)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,14 +35,14 @@ class ReCaptcha_Rx__Tests: XCTestCase {
|
|||
|
||||
|
||||
func test__Validate__Token() {
|
||||
let manager = ReCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey)
|
||||
manager.configureWebView { _ in
|
||||
let recaptcha = ReCaptcha(manager: ReCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey))
|
||||
recaptcha.configureWebView { _ in
|
||||
XCTFail("should not ask to configure the webview")
|
||||
}
|
||||
|
||||
do {
|
||||
// Validate
|
||||
let result = try manager.rx.validate(on: presenterView)
|
||||
let result = try recaptcha.rx.validate(on: presenterView)
|
||||
.toBlocking()
|
||||
.single()
|
||||
|
||||
|
|
@ -56,16 +56,19 @@ class ReCaptcha_Rx__Tests: XCTestCase {
|
|||
|
||||
|
||||
func test__Validate__Show_ReCaptcha() {
|
||||
let manager = ReCaptchaWebViewManager(messageBody: "{action: \"showReCaptcha\"}", apiKey: apiKey)
|
||||
let recaptcha = ReCaptcha(
|
||||
manager: ReCaptchaWebViewManager(messageBody: "{action: \"showReCaptcha\"}", apiKey: apiKey)
|
||||
)
|
||||
|
||||
var didConfigureWebView = false
|
||||
|
||||
manager.configureWebView { _ in
|
||||
recaptcha.configureWebView { _ in
|
||||
didConfigureWebView = true
|
||||
}
|
||||
|
||||
do {
|
||||
// Validate
|
||||
_ = try manager.rx.validate(on: presenterView)
|
||||
_ = try recaptcha.rx.validate(on: presenterView)
|
||||
.timeout(2, scheduler: MainScheduler.instance)
|
||||
.toBlocking()
|
||||
.single()
|
||||
|
|
@ -80,14 +83,14 @@ class ReCaptcha_Rx__Tests: XCTestCase {
|
|||
|
||||
|
||||
func test__Validate__Error() {
|
||||
let manager = ReCaptchaWebViewManager(messageBody: "\"foobar\"", apiKey: apiKey)
|
||||
manager.configureWebView { _ in
|
||||
let recaptcha = ReCaptcha(manager: ReCaptchaWebViewManager(messageBody: "\"foobar\"", apiKey: apiKey))
|
||||
recaptcha.configureWebView { _ in
|
||||
XCTFail("should not ask to configure the webview")
|
||||
}
|
||||
|
||||
do {
|
||||
// Validate
|
||||
_ = try manager.rx.validate(on: presenterView, resetOnError: false)
|
||||
_ = try recaptcha.rx.validate(on: presenterView, resetOnError: false)
|
||||
.toBlocking()
|
||||
.single()
|
||||
|
||||
|
|
@ -104,12 +107,12 @@ class ReCaptcha_Rx__Tests: XCTestCase {
|
|||
let exp = expectation(description: "stop loading")
|
||||
|
||||
// Stop
|
||||
let manager = ReCaptchaWebViewManager(messageBody: "{action: \"showReCaptcha\"}")
|
||||
manager.configureWebView { _ in
|
||||
let recaptcha = ReCaptcha(manager: ReCaptchaWebViewManager(messageBody: "{action: \"showReCaptcha\"}"))
|
||||
recaptcha.configureWebView { _ in
|
||||
XCTFail("should not ask to configure the webview")
|
||||
}
|
||||
|
||||
let disposable = manager.rx.validate(on: presenterView)
|
||||
let disposable = recaptcha.rx.validate(on: presenterView)
|
||||
.do(onDispose: exp.fulfill)
|
||||
.subscribe { _ in
|
||||
XCTFail("should not validate")
|
||||
|
|
@ -126,14 +129,17 @@ class ReCaptcha_Rx__Tests: XCTestCase {
|
|||
|
||||
func test__Reset() {
|
||||
// Validate
|
||||
let manager = ReCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey, shouldFail: true)
|
||||
manager.configureWebView { _ in
|
||||
let recaptcha = ReCaptcha(
|
||||
manager: ReCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey, shouldFail: true)
|
||||
)
|
||||
|
||||
recaptcha.configureWebView { _ in
|
||||
XCTFail("should not ask to configure the webview")
|
||||
}
|
||||
|
||||
do {
|
||||
// Error
|
||||
_ = try manager.rx.validate(on: presenterView, resetOnError: false)
|
||||
_ = try recaptcha.rx.validate(on: presenterView, resetOnError: false)
|
||||
.toBlocking()
|
||||
.single()
|
||||
}
|
||||
|
|
@ -142,12 +148,12 @@ class ReCaptcha_Rx__Tests: XCTestCase {
|
|||
|
||||
// Resets after failure
|
||||
_ = Observable<Void>.just(())
|
||||
.bind(to: manager.rx.reset)
|
||||
.bind(to: recaptcha.rx.reset)
|
||||
}
|
||||
|
||||
do {
|
||||
// Resets and tries again
|
||||
let result = try manager.rx.validate(on: presenterView, resetOnError: false)
|
||||
let result = try recaptcha.rx.validate(on: presenterView, resetOnError: false)
|
||||
.toBlocking()
|
||||
.single()
|
||||
|
||||
|
|
@ -160,14 +166,17 @@ class ReCaptcha_Rx__Tests: XCTestCase {
|
|||
|
||||
func test__Validate__Reset_On_Error() {
|
||||
// Validate
|
||||
let manager = ReCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey, shouldFail: true)
|
||||
manager.configureWebView { _ in
|
||||
let recaptcha = ReCaptcha(
|
||||
manager: ReCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey, shouldFail: true)
|
||||
)
|
||||
|
||||
recaptcha.configureWebView { _ in
|
||||
XCTFail("should not ask to configure the webview")
|
||||
}
|
||||
|
||||
do {
|
||||
// Error
|
||||
let result = try manager.rx.validate(on: presenterView, resetOnError: true)
|
||||
let result = try recaptcha.rx.validate(on: presenterView, resetOnError: true)
|
||||
.toBlocking()
|
||||
.single()
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ import Foundation
|
|||
import WebKit
|
||||
|
||||
|
||||
/** The public facade of ReCaptcha
|
||||
/**
|
||||
*/
|
||||
open class ReCaptcha: ReCaptchaWebViewManager {
|
||||
public class ReCaptcha {
|
||||
fileprivate struct Constants {
|
||||
struct InfoDictKeys {
|
||||
static let APIKey = "ReCaptchaKey"
|
||||
|
|
@ -97,6 +97,9 @@ open class ReCaptcha: ReCaptchaWebViewManager {
|
|||
}
|
||||
}
|
||||
|
||||
/// The worker that handles webview events and communication
|
||||
let manager: ReCaptchaWebViewManager
|
||||
|
||||
/**
|
||||
- parameters:
|
||||
- apiKey: The API key sent to the ReCaptcha init
|
||||
|
|
@ -118,15 +121,81 @@ open class ReCaptcha: ReCaptchaWebViewManager {
|
|||
Info.plist.
|
||||
- Throws: Rethrows any exceptions thrown by `String(contentsOfFile:)`
|
||||
*/
|
||||
public init(apiKey: String? = nil, baseURL: URL? = nil, endpoint: Endpoint = .default) throws {
|
||||
public convenience init(apiKey: String? = nil, baseURL: URL? = nil, endpoint: Endpoint = .default) throws {
|
||||
let infoDict = Bundle.main.infoDictionary
|
||||
|
||||
let plistApiKey = infoDict?[Constants.InfoDictKeys.APIKey] as? String
|
||||
let plistDomain = (infoDict?[Constants.InfoDictKeys.Domain] as? String).flatMap(URL.init(string:))
|
||||
|
||||
let config = try Config(apiKey: apiKey, infoPlistKey: plistApiKey, baseURL: baseURL, infoPlistURL: plistDomain)
|
||||
super.init(html: config.html, apiKey: config.apiKey, baseURL: config.baseURL, endpoint: endpoint.url)
|
||||
|
||||
self.init(manager: ReCaptchaWebViewManager(
|
||||
html: config.html,
|
||||
apiKey: config.apiKey,
|
||||
baseURL: config.baseURL,
|
||||
endpoint: endpoint.url
|
||||
))
|
||||
}
|
||||
|
||||
/**
|
||||
- parameter manager: A ReCaptchaWebViewManager instance.
|
||||
|
||||
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.
|
||||
|
||||
Starts the challenge validation
|
||||
*/
|
||||
public func validate(on view: UIView, resetOnError: Bool = true, completion: @escaping (ReCaptchaResult) -> Void) {
|
||||
manager.shouldResetOnError = resetOnError
|
||||
manager.completion = completion
|
||||
|
||||
manager.validate(on: view)
|
||||
}
|
||||
|
||||
|
||||
/// Stops the execution of the webview
|
||||
public func stop() {
|
||||
manager.stop()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
- parameter configure: A closure that receives an instance of `WKWebView` for configuration.
|
||||
|
||||
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.
|
||||
*/
|
||||
public func configureWebView(_ configure: @escaping (WKWebView) -> Void) {
|
||||
manager.configureWebView = configure
|
||||
}
|
||||
|
||||
/**
|
||||
Resets the ReCaptcha.
|
||||
|
||||
The reset is achieved by calling `grecaptcha.reset()` on the JS API.
|
||||
*/
|
||||
public func reset() {
|
||||
manager.reset()
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
/// Forces the challenge to be explicitly displayed.
|
||||
public var forceVisibleChallenge: Bool {
|
||||
get { return manager.forceVisibleChallenge }
|
||||
set { manager.forceVisibleChallenge = newValue }
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// MARK: - Private Methods
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import WebKit
|
|||
|
||||
/** Handles comunications with the webview containing the ReCaptcha challenge.
|
||||
*/
|
||||
open class ReCaptchaWebViewManager {
|
||||
internal class ReCaptchaWebViewManager {
|
||||
/** The `webView` delegate object that performs execution uppon script loading
|
||||
*/
|
||||
fileprivate class WebViewDelegate: NSObject, WKNavigationDelegate {
|
||||
|
|
@ -99,7 +99,7 @@ open class ReCaptchaWebViewManager {
|
|||
|
||||
#if DEBUG
|
||||
/// Forces the challenge to be explicitly displayed.
|
||||
public var forceVisibleChallenge = false {
|
||||
var forceVisibleChallenge = false {
|
||||
didSet {
|
||||
// Also works on iOS < 9
|
||||
webView.performSelector(
|
||||
|
|
@ -112,10 +112,13 @@ open class ReCaptchaWebViewManager {
|
|||
#endif
|
||||
|
||||
/// Sends the result message
|
||||
fileprivate var completion: ((ReCaptchaResult) -> Void)?
|
||||
var completion: ((ReCaptchaResult) -> Void)?
|
||||
|
||||
/// Configures the webview for display when required
|
||||
fileprivate var configureWebView: ((WKWebView) -> Void)?
|
||||
var configureWebView: ((WKWebView) -> Void)?
|
||||
|
||||
/// If the ReCaptcha should be reset when it errors
|
||||
var shouldResetOnError = true
|
||||
|
||||
/// The JS message recoder
|
||||
fileprivate var decoder: ReCaptchaDecoder!
|
||||
|
|
@ -129,16 +132,13 @@ open class ReCaptchaWebViewManager {
|
|||
/// The endpoint url being used
|
||||
fileprivate var endpoint: String
|
||||
|
||||
/// If the ReCaptcha should be reset when it errors
|
||||
fileprivate var shouldResetOnError = true
|
||||
|
||||
/// The `webView` delegate implementation
|
||||
fileprivate lazy var webviewDelegate: WebViewDelegate = {
|
||||
WebViewDelegate(manager: self)
|
||||
}()
|
||||
|
||||
/// The webview that executes JS code
|
||||
fileprivate lazy var webView: WKWebView = {
|
||||
lazy var webView: WKWebView = {
|
||||
let webview = WKWebView(
|
||||
frame: CGRect(x: 0, y: 0, width: 1, height: 1),
|
||||
configuration: self.buildConfiguration()
|
||||
|
|
@ -181,19 +181,12 @@ open class ReCaptchaWebViewManager {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
- 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.
|
||||
- parameter view: The view that should present the webview.
|
||||
|
||||
Starts the challenge validation
|
||||
*/
|
||||
open func validate(on view: UIView, resetOnError: Bool = true, completion: @escaping (ReCaptchaResult) -> Void) {
|
||||
self.completion = completion
|
||||
self.shouldResetOnError = resetOnError
|
||||
|
||||
func validate(on view: UIView) {
|
||||
webView.isHidden = false
|
||||
view.addSubview(webView)
|
||||
|
||||
|
|
@ -202,29 +195,16 @@ open class ReCaptchaWebViewManager {
|
|||
|
||||
|
||||
/// Stops the execution of the webview
|
||||
open func stop() {
|
||||
func stop() {
|
||||
webView.stopLoading()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
- parameter configure: A closure that receives an instance of `WKWebView` for configuration.
|
||||
|
||||
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.
|
||||
*/
|
||||
open func configureWebView(_ configure: @escaping (WKWebView) -> Void) {
|
||||
self.configureWebView = configure
|
||||
}
|
||||
|
||||
/**
|
||||
Resets the ReCaptcha.
|
||||
|
||||
The reset is achieved by calling `grecaptcha.reset()` on the JS API.
|
||||
*/
|
||||
open func reset() {
|
||||
func reset() {
|
||||
didFinishLoading = false
|
||||
|
||||
webView.evaluateJavaScript(Constants.ResetCommand) { [weak self] _, error in
|
||||
|
|
@ -240,7 +220,6 @@ open class ReCaptchaWebViewManager {
|
|||
/** Private methods for ReCaptchaWebViewManager
|
||||
*/
|
||||
fileprivate extension ReCaptchaWebViewManager {
|
||||
|
||||
/** Executes the JS command that loads the ReCaptcha challenge.
|
||||
This method has no effect if the webview hasn't finished loading.
|
||||
*/
|
||||
|
|
@ -283,9 +262,9 @@ fileprivate extension ReCaptchaWebViewManager {
|
|||
completion?(.token(token))
|
||||
|
||||
case .error(let error):
|
||||
if shouldResetOnError, let view = webView.superview, let completion = completion {
|
||||
if shouldResetOnError, let view = webView.superview {
|
||||
reset()
|
||||
validate(on: view, completion: completion)
|
||||
validate(on: view)
|
||||
}
|
||||
else {
|
||||
completion?(.error(error))
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@
|
|||
import RxSwift
|
||||
import UIKit
|
||||
|
||||
/// Makes ReCaptchaWebViewManager compatible with RxSwift extensions
|
||||
extension ReCaptchaWebViewManager: ReactiveCompatible {}
|
||||
/// Makes ReCaptcha compatible with RxSwift extensions
|
||||
extension ReCaptcha: ReactiveCompatible {}
|
||||
|
||||
/// Provides a public extension on ReCaptchaWebViewManager that makes it reactive.
|
||||
public extension Reactive where Base: ReCaptchaWebViewManager {
|
||||
/// Provides a public extension on ReCaptcha that makes it reactive.
|
||||
public extension Reactive where Base: ReCaptcha {
|
||||
|
||||
/**
|
||||
- parameters:
|
||||
|
|
@ -26,8 +26,8 @@ public extension Reactive where Base: ReCaptchaWebViewManager {
|
|||
|
||||
Sends `stop()` uppon disposal.
|
||||
|
||||
- See: `ReCaptchaWebViewManager.validate(on:resetOnError:completion:)`
|
||||
- See: `ReCaptchaWebViewManager.stop()`
|
||||
- See: `ReCaptcha.validate(on:resetOnError:completion:)`
|
||||
- See: `ReCaptcha.stop()`
|
||||
*/
|
||||
func validate(on view: UIView, resetOnError: Bool = true) -> Observable<String> {
|
||||
return Single<String>.create { [weak base] single in
|
||||
|
|
@ -53,7 +53,7 @@ public extension Reactive where Base: ReCaptchaWebViewManager {
|
|||
|
||||
The reset is achieved by calling `grecaptcha.reset()` on the JS API.
|
||||
|
||||
- See: `ReCaptchaWebViewManager.reset()`
|
||||
- See: `ReCaptcha.reset()`
|
||||
*/
|
||||
var reset: AnyObserver<Void> {
|
||||
return AnyObserver { [weak base] event in
|
||||
|
|
|
|||
Loading…
Reference in New Issue