Documentation

This commit is contained in:
Flávio Caetano 2017-04-11 18:27:10 -03:00
parent 20f68d61f7
commit f34cb7fbb5
8 changed files with 137 additions and 20 deletions

11
.gitignore vendored
View File

@ -36,13 +36,6 @@ xcuserdata
.DS_Store
## CocoaPods
**/Pods/
Pods/
html/highwinds_credentials.json
html/node_modules/
html/fb-*.html
html/ios-*.html
html/mp4-*.html
html/player-inject-*.js
.bitrise*
docs/

View File

@ -1,6 +1,6 @@
# ReCaptcha
[![CI Status](http://img.shields.io/travis/fjcaetano/ReCaptcha.svg?style=flat)](https://travis-ci.org/fjcaetano/ReCaptcha)
# [![CI Status](http://img.shields.io/travis/fjcaetano/ReCaptcha.svg?style=flat)](https://travis-ci.org/fjcaetano/ReCaptcha)
[![Version](https://img.shields.io/cocoapods/v/ReCaptcha.svg?style=flat)](http://cocoapods.org/pods/ReCaptcha)
[![License](https://img.shields.io/cocoapods/l/ReCaptcha.svg?style=flat)](http://cocoapods.org/pods/ReCaptcha)
[![Platform](https://img.shields.io/cocoapods/p/ReCaptcha.svg?style=flat)](http://cocoapods.org/pods/ReCaptcha)
@ -25,11 +25,13 @@ pod "ReCaptcha"
Simply add `ReCaptchaKey` and `ReCaptchaDomain` to your Info.plist and run:
``` swift
let recaptcha = try? ReCaptcha()
override func viewDidLoad() {
super.viewDidLoad()
recaptcha.presenterView = view
recaptcha.configureWebView { [weak self] webview in
recaptcha?.presenterView = view
recaptcha?.configureWebView { [weak self] webview in
webview.frame = self?.view.bounds ?? CGRect.zero
webview.tag = ViewController.webViewTag
}
@ -37,7 +39,7 @@ override func viewDidLoad() {
func validate() {
recaptcha.validate { [weak self] result in
recaptcha?.validate { [weak self] result in
print(try? result.dematerialize())
}
}

View File

@ -22,9 +22,8 @@ Add Google invisible ReCaptcha to your app
DESC
s.homepage = 'https://github.com/fjcaetano/ReCaptcha'
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'fjcaetano' => 'flavio@vieiracaetano.com' }
s.author = { 'Flávio Caetano' => 'flavio@vieiracaetano.com' }
s.source = { :git => 'https://github.com/fjcaetano/ReCaptcha.git', :tag => s.version.to_s }
s.social_media_url = 'https://twitter.com/flavio_caetano'

View File

@ -11,24 +11,47 @@ import Foundation
/// The domain for ReCaptcha's errors
fileprivate let kErrorDomain = "com.flaviocaetano.ReCaptcha"
/**
/** Adds enum codes to ReCaptcha's errors
*/
extension NSError {
enum Code: Int {
/** The codes of possible errors thrown by ReCaptcha
- undefined: Any unexpected errors
- htmlLoadError: Could not load the HTML embedded in the bundle
- apiKeyNotFound: ReCaptchaKey was not provided
- baseURLNotFound: ReCaptchaDomain was not provided
- wrongMessageFormat: Received an unexpeted message from javascript
*/
enum ReCaptchaCode: Int {
/// Unexpected error
case undefined
/// Could not load the HTML embedded in the bundle
case htmlLoadError
/// ReCaptchaKey was not provided
case apiKeyNotFound
/// ReCaptchaDomain was not provided
case baseURLNotFound
/// Received an unexpeted message from javascript
case wrongMessageFormat
}
var rc_code: Code? {
return Code(rawValue: code)
/// The error ReCaptchaCode
var rc_code: ReCaptchaCode? {
return ReCaptchaCode(rawValue: code)
}
convenience init(code: Code, userInfo: [AnyHashable: Any]? = nil) {
/** Initializes the error with a Code and an userInfo
- parameter code: A ReCaptchaCode
- parameter userInfo: The error's userInfo
*/
convenience init(code: ReCaptchaCode, userInfo: [AnyHashable: Any]? = nil) {
self.init(domain: kErrorDomain, code: code.rawValue, userInfo: userInfo)
}
}

View File

@ -10,6 +10,8 @@ import Foundation
import WebKit
/** The public facade of ReCaptcha
*/
open class ReCaptcha: ReCaptchaWebViewManager {
fileprivate struct Constants {
struct InfoDictKeys {
@ -18,6 +20,22 @@ open class ReCaptcha: ReCaptchaWebViewManager {
}
}
/** Initializes a ReCaptcha object
Both `apiKey` and `baseURL` may be nil, in which case the lib will look for entries of `ReCaptchaKey` and
`ReCaptchaDomain`, respectively, in the project's Info.plist
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.
- Throws:
- `NSError.ReCaptchaCode.htmlLoadError` if is unable to load the HTML embedded in the bundle.
- `NSError.ReCaptchaCode.apiKeyNotFound` if an `apiKey` is not provided and can't find one in the project's Info.plist.
- `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 {
guard let filePath = Bundle(for: ReCaptcha.self).path(forResource: "recaptcha", ofType: "html") else {
throw NSError(code: .htmlLoadError)

View File

@ -10,15 +10,31 @@ import Foundation
import WebKit
/** The Decoder of javascript messages from the webview
*/
class ReCaptchaDecoder: NSObject {
/** The decoder result.
- token(String): A result token, if any
- showReCaptcha: Indicates that the webview containing the challenge should be displayed.
- error(NSError): Any errors
*/
enum Result {
/// A result token, if any
case token(String)
/// Indicates that the webview containing the challenge should be displayed.
case showReCaptcha
/// Any errors
case error(NSError)
}
fileprivate let sendMessage: ((Result) -> Void)
/** Initializes a decoder with a completion closure.
- parameter didReceiveMessage: A closure that receives a ReCaptchaDecoder.Result
*/
init(didReceiveMessage: @escaping (Result) -> Void) {
sendMessage = didReceiveMessage
@ -26,6 +42,9 @@ class ReCaptchaDecoder: NSObject {
}
/** Sends an error to the completion closure
- parameter error: The error to be sent.
*/
func send(error: NSError) {
sendMessage(.error(error))
}
@ -33,6 +52,9 @@ class ReCaptchaDecoder: NSObject {
// MARK: Script Handler
/** Makes ReCaptchaDecoder conform to `WKScriptMessageHandler`
*/
extension ReCaptchaDecoder: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard let dict = message.body as? [String: Any] else {
@ -45,7 +67,15 @@ extension ReCaptchaDecoder: WKScriptMessageHandler {
// MARK: - Result
/** Private methods on `ReCaptchaDecoder.Result`
*/
fileprivate extension ReCaptchaDecoder.Result {
/** Parses a dict received from the webview onto a `ReCaptchaDecoder.Result`
- parameter response: A dictionary containing the message to be parsed
- returns: A decoded ReCaptchaDecoder.Result
*/
static func from(response: [String: Any]) -> ReCaptchaDecoder.Result {
if let token = response["token"] as? String {
return .token(token)

View File

@ -11,6 +11,8 @@ import WebKit
import Result
/** Handles comunications with the webview containing the ReCaptcha challenge.
*/
open class ReCaptchaWebViewManager: NSObject {
public typealias Response = Result<String, NSError>
@ -18,6 +20,7 @@ open class ReCaptchaWebViewManager: NSObject {
static let ExecuteJSCommand = "execute();"
}
/// The view in which the webview may be presented.
open weak var presenterView: UIView?
@ -34,6 +37,12 @@ open class ReCaptchaWebViewManager: NSObject {
}()
/** Initializes the manager
- 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
*/
init(html: String, apiKey: String, baseURL: URL) {
super.init()
@ -45,6 +54,10 @@ open class ReCaptchaWebViewManager: NSObject {
}
/** Starts the challenge validation
- parameter completion: A closure that receives a Result<String, NSError> which may contain a valid result token.
*/
open func validate(completion: @escaping (Response) -> Void) {
self.completion = completion
@ -52,11 +65,19 @@ open class ReCaptchaWebViewManager: NSObject {
}
/// Stops the execution of the webview
open func stop() {
webView.stopLoading()
}
/** 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.
- parameter configure: A closure that receives an instance of `WKWebView` for configuration.
*/
open func configureWebView(_ configure: @escaping (WKWebView) -> Void) {
self.configureWebView = configure
}
@ -64,7 +85,15 @@ open class ReCaptchaWebViewManager: NSObject {
// MARK: - Navigation
/** Makes ReCaptchaWebViewManager conform to `WKNavigationDelegate`
*/
extension ReCaptchaWebViewManager: WKNavigationDelegate {
/** Called when the navigation is complete.
- parameter webView: The web view invoking the delegate method.
- parameter navigation: The navigation object that finished.
*/
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
didFinishLoading = true
@ -81,7 +110,14 @@ extension ReCaptchaWebViewManager: WKNavigationDelegate {
// MARK: - Private Methods
/** 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.
*/
func execute() {
guard didFinishLoading else {
// Hasn't finished loading the HTML yet
@ -95,6 +131,9 @@ fileprivate extension ReCaptchaWebViewManager {
}
}
/** Creates a `WKWebViewConfiguration` to be added to the `WKWebView` instance.
- returns: An instance of `WKWebViewConfiguration`
*/
func buildConfiguration() -> WKWebViewConfiguration {
let controller = WKUserContentController()
controller.add(decoder, name: "recaptcha")
@ -105,6 +144,9 @@ fileprivate extension ReCaptchaWebViewManager {
return conf
}
/** Handles the decoder results received from the webview
- Parameter result: A `ReCaptchaDecoder.Result` with the decoded message.
*/
func handle(result: ReCaptchaDecoder.Result) {
switch result {
case .token(let token):

View File

@ -9,7 +9,17 @@
import RxSwift
/** Provides a public extension on ReCaptcha that makes it reactive.
*/
public extension Reactive where Base: ReCaptcha {
/** Starts the challenge validation uppon subscription.
The stream's element is a `Result<String, NSError>` that may contain a valid token.
- See:
[ReCaptchaWebViewManager.validate](../Classes/ReCaptchaWebViewManager.html#/s:FC9ReCaptcha23ReCaptchaWebViewManager8validateFT10completionFGO6Result6ResultSSCSo7NSError_T__T_)
*/
public func validate() -> Observable<Base.Response> {
return Observable<Base.Response>.create { [weak base] (observer: AnyObserver<Base.Response>) in
base?.validate { response in