diff --git a/Example/ReCaptcha.xcodeproj/project.pbxproj b/Example/ReCaptcha.xcodeproj/project.pbxproj index 75b89a1..bfce56a 100644 --- a/Example/ReCaptcha.xcodeproj/project.pbxproj +++ b/Example/ReCaptcha.xcodeproj/project.pbxproj @@ -254,7 +254,7 @@ attributes = { LastSwiftUpdateCheck = 0830; LastUpgradeCheck = 0820; - ORGANIZATIONNAME = CocoaPods; + ORGANIZATIONNAME = ReCaptcha; TargetAttributes = { 607FACCF1AFB9204008FA782 = { CreatedOnToolsVersion = 6.3.1; diff --git a/Example/ReCaptcha_Tests/Core/ReCaptchaWebViewManager__Tests.swift b/Example/ReCaptcha_Tests/Core/ReCaptchaWebViewManager__Tests.swift index 5254c31..8da042a 100644 --- a/Example/ReCaptcha_Tests/Core/ReCaptchaWebViewManager__Tests.swift +++ b/Example/ReCaptcha_Tests/Core/ReCaptchaWebViewManager__Tests.swift @@ -198,4 +198,50 @@ class ReCaptchaWebViewManager__Tests: XCTestCase { waitForExpectations(timeout: 3) } + + // MARK: Setup + + func test__Key_Setup() { + let exp = expectation(description: "setup key") + var result: ReCaptchaWebViewManager.Response? + + // Validate + let manager = ReCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey) + manager.configureWebView { _ in + XCTFail("should not ask to configure the webview") + } + + manager.validate(on: presenterView) { response in + result = response + exp.fulfill() + } + + waitForExpectations(timeout: 3) + + XCTAssertNotNil(result) + XCTAssertNil(result?.error) + XCTAssertEqual(result?.value, apiKey) + } + + func test__Endpoint_Setup() { + let exp = expectation(description: "setup endpoint") + let endpoint = String(describing: arc4random()) + var result: ReCaptchaWebViewManager.Response? + + let manager = ReCaptchaWebViewManager(messageBody: "{token: endpoint}", endpoint: endpoint) + manager.configureWebView { _ in + XCTFail("should not ask to configure the webview") + } + + manager.validate(on: presenterView) { response in + result = response + exp.fulfill() + } + + waitForExpectations(timeout: 3) + + XCTAssertNotNil(result) + XCTAssertNil(result?.error) + XCTAssertEqual(result?.value, endpoint) + } } diff --git a/Example/ReCaptcha_Tests/Helpers/ReCaptchaWebViewManager+Helpers.swift b/Example/ReCaptcha_Tests/Helpers/ReCaptchaWebViewManager+Helpers.swift index 1a2f9bf..2a4cca7 100644 --- a/Example/ReCaptcha_Tests/Helpers/ReCaptchaWebViewManager+Helpers.swift +++ b/Example/ReCaptcha_Tests/Helpers/ReCaptchaWebViewManager+Helpers.swift @@ -12,12 +12,18 @@ import Foundation extension ReCaptchaWebViewManager { - convenience init(messageBody: String, apiKey: String? = nil) { + convenience init(messageBody: String, apiKey: String? = nil, endpoint: String? = nil) { + let localhost = URL(string: "http://localhost")! let html = Bundle(for: ReCaptchaWebViewManager__Tests.self) .path(forResource: "mock", ofType: "html") .flatMap { try? String(contentsOfFile: $0) } - .map { String(format: $0, "%@", messageBody) } + .map { String(format: $0, arguments: ["message": messageBody]) } - self.init(html: html!, apiKey: apiKey ?? String(arc4random()), baseURL: URL(string: "http://localhost")!) + self.init( + html: html!, + apiKey: apiKey ?? String(arc4random()), + baseURL: localhost, + endpoint: endpoint ?? localhost.absoluteString + ) } } diff --git a/Example/ReCaptcha_Tests/mock.html b/Example/ReCaptcha_Tests/mock.html index b315536..1e741ba 100644 --- a/Example/ReCaptcha_Tests/mock.html +++ b/Example/ReCaptcha_Tests/mock.html @@ -2,10 +2,11 @@ diff --git a/README.md b/README.md index 98b5750..0ee200c 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,19 @@ recaptcha.rx.validate(on: view) }) ``` +#### Alternte endpoint + +If your app has firewall limitations that may be blocking Google's API, the JS endpoint may be changed on initialization. +It'll then point to `https://www.recaptcha.net/recaptcha/api.js`: + +``` swift +public enum Endpoint { + case default, alternate +} + +let recaptcha = try? ReCaptcha(endpoint: .alternate) // Defaults to `default` when unset +``` + ## License ReCaptcha is available under the MIT license. See the LICENSE file for more info. diff --git a/ReCaptcha/Assets/recaptcha.html b/ReCaptcha/Assets/recaptcha.html index 0237452..a6361f2 100644 --- a/ReCaptcha/Assets/recaptcha.html +++ b/ReCaptcha/Assets/recaptcha.html @@ -26,7 +26,7 @@ var onloadCallback = function() { grecaptcha.render('submit', { - 'sitekey' : '%@', + 'sitekey' : '${apiKey}', 'callback' : onSubmit, 'size': 'invisible' }); @@ -35,6 +35,6 @@ - + diff --git a/ReCaptcha/Classes/NSError+ReCaptcha.swift b/ReCaptcha/Classes/NSError+ReCaptcha.swift index 593211f..b79b70f 100644 --- a/ReCaptcha/Classes/NSError+ReCaptcha.swift +++ b/ReCaptcha/Classes/NSError+ReCaptcha.swift @@ -1,6 +1,6 @@ // // NSError+ReCaptcha.swift -// Pods +// ReCaptcha // // Created by Flávio Caetano on 22/03/17. // diff --git a/ReCaptcha/Classes/ReCaptcha.swift b/ReCaptcha/Classes/ReCaptcha.swift index d95738e..db84580 100644 --- a/ReCaptcha/Classes/ReCaptcha.swift +++ b/ReCaptcha/Classes/ReCaptcha.swift @@ -1,6 +1,6 @@ // // ReCaptcha.swift -// Pods +// ReCaptcha // // Created by Flávio Caetano on 22/03/17. // @@ -20,6 +20,22 @@ open class ReCaptcha: ReCaptchaWebViewManager { } } + /** The JS API endpoint to be loaded onto the HTML file. + + - default: Google's default endpoint. Points to https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit + - alternate: Alternate endpoint. Points to https://www.recaptcha.net/recaptcha/api.js + */ + public enum Endpoint { + case `default`, alternate + + fileprivate var url: String { + switch self { + case .default: return "https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" + case .alternate: return "https://www.recaptcha.net/recaptcha/api.js" + } + } + } + /** Internal data model for DI in unit tests */ struct Config { @@ -73,8 +89,9 @@ open class ReCaptcha: ReCaptchaWebViewManager { A key may be aquired here: https://www.google.com/recaptcha/admin#list - - parameter apiKey: The API key to be provided to Google's ReCaptcha. Overrides the Info.plist entry. - - parameter baseURL: A url domain to be load onto the webview. Overrides the Info.plist entry. + - parameter apiKey: The API key to be provided to Google's ReCaptcha. Overrides the Info.plist entry. Defaults to `nil`. + - parameter baseURL: A url domain to be load onto the webview. Overrides the Info.plist entry. Defaults to `nil`. + - parameter endpoint: The JS API endpoint to be loaded onto the HTML file. Defaults to `.default`. - Throws: - `NSError.ReCaptchaCode.htmlLoadError` if is unable to load the HTML embedded in the bundle. @@ -82,13 +99,13 @@ open class ReCaptcha: ReCaptchaWebViewManager { - `NSError.ReCaptchaCode.baseURLNotFound` if a `baseURL` is not provided and can't find one in the project's Info.plist. - Rethrows any exceptions thrown by `String(contentsOfFile:)` */ - public init(apiKey: String? = nil, baseURL: URL? = nil) throws { + public 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) + super.init(html: config.html, apiKey: config.apiKey, baseURL: config.baseURL, endpoint: endpoint.url) } } diff --git a/ReCaptcha/Classes/ReCaptchaDecoder.swift b/ReCaptcha/Classes/ReCaptchaDecoder.swift index 50dad55..deda628 100644 --- a/ReCaptcha/Classes/ReCaptchaDecoder.swift +++ b/ReCaptcha/Classes/ReCaptchaDecoder.swift @@ -1,6 +1,6 @@ // // ReCaptchaDecoder.swift -// Pods +// ReCaptcha // // Created by Flávio Caetano on 22/03/17. // diff --git a/ReCaptcha/Classes/ReCaptchaWebViewManager.swift b/ReCaptcha/Classes/ReCaptchaWebViewManager.swift index 0226fb6..46614d4 100644 --- a/ReCaptcha/Classes/ReCaptchaWebViewManager.swift +++ b/ReCaptcha/Classes/ReCaptchaWebViewManager.swift @@ -1,6 +1,6 @@ // // ReCaptchaWebViewManager.swift -// Pods +// ReCaptcha // // Created by Flávio Caetano on 22/03/17. // @@ -39,15 +39,16 @@ open class ReCaptchaWebViewManager: NSObject { - 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) { + init(html: String, apiKey: String, baseURL: URL, endpoint: String) { super.init() decoder = ReCaptchaDecoder { [weak self] result in self?.handle(result: result) } - let formattedHTML = String(format: html, apiKey) + let formattedHTML = String(format: html, arguments: ["apiKey": apiKey, "endpoint": endpoint]) if let window = UIApplication.shared.keyWindow { setupWebview(on: window, html: formattedHTML, url: baseURL) diff --git a/ReCaptcha/Classes/Rx/ReCaptcha+Rx.swift b/ReCaptcha/Classes/Rx/ReCaptcha+Rx.swift index 74ff201..4d4d519 100644 --- a/ReCaptcha/Classes/Rx/ReCaptcha+Rx.swift +++ b/ReCaptcha/Classes/Rx/ReCaptcha+Rx.swift @@ -1,6 +1,6 @@ // // ReCaptcha+Rx.swift -// Pods +// ReCaptcha // // Created by Flávio Caetano on 11/04/17. // diff --git a/ReCaptcha/Classes/String+Dict.swift b/ReCaptcha/Classes/String+Dict.swift new file mode 100644 index 0000000..38c3521 --- /dev/null +++ b/ReCaptcha/Classes/String+Dict.swift @@ -0,0 +1,19 @@ +// +// String+Dict.swift +// ReCaptcha +// +// Created by Flávio Caetano on 10/10/17. +// +// + +import Foundation + + +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) + }) + } +}