From f34cb7fbb58186f7dea1edf889cc0514ada8eeb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C3=A1vio=20Caetano?= Date: Tue, 11 Apr 2017 18:27:10 -0300 Subject: [PATCH] Documentation --- .gitignore | 11 +---- README.md | 10 +++-- ReCaptcha.podspec | 3 +- ReCaptcha/Classes/NSError+ReCaptcha.swift | 33 ++++++++++++--- ReCaptcha/Classes/ReCaptcha.swift | 18 ++++++++ ReCaptcha/Classes/ReCaptchaDecoder.swift | 30 +++++++++++++ .../Classes/ReCaptchaWebViewManager.swift | 42 +++++++++++++++++++ ReCaptcha/Classes/Rx/ReCaptcha+Rx.swift | 10 +++++ 8 files changed, 137 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index 83e5729..1710495 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/README.md b/README.md index d63775c..6ecb877 100644 --- a/README.md +++ b/README.md @@ -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()) } } diff --git a/ReCaptcha.podspec b/ReCaptcha.podspec index d29e3a1..316ab74 100644 --- a/ReCaptcha.podspec +++ b/ReCaptcha.podspec @@ -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' diff --git a/ReCaptcha/Classes/NSError+ReCaptcha.swift b/ReCaptcha/Classes/NSError+ReCaptcha.swift index 18f686e..ea2ace2 100644 --- a/ReCaptcha/Classes/NSError+ReCaptcha.swift +++ b/ReCaptcha/Classes/NSError+ReCaptcha.swift @@ -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) } } diff --git a/ReCaptcha/Classes/ReCaptcha.swift b/ReCaptcha/Classes/ReCaptcha.swift index fa16896..6329679 100644 --- a/ReCaptcha/Classes/ReCaptcha.swift +++ b/ReCaptcha/Classes/ReCaptcha.swift @@ -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) diff --git a/ReCaptcha/Classes/ReCaptchaDecoder.swift b/ReCaptcha/Classes/ReCaptchaDecoder.swift index dbbd6bc..50dad55 100644 --- a/ReCaptcha/Classes/ReCaptchaDecoder.swift +++ b/ReCaptcha/Classes/ReCaptchaDecoder.swift @@ -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) diff --git a/ReCaptcha/Classes/ReCaptchaWebViewManager.swift b/ReCaptcha/Classes/ReCaptchaWebViewManager.swift index c58ea96..9a41812 100644 --- a/ReCaptcha/Classes/ReCaptchaWebViewManager.swift +++ b/ReCaptcha/Classes/ReCaptchaWebViewManager.swift @@ -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 @@ -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 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): diff --git a/ReCaptcha/Classes/Rx/ReCaptcha+Rx.swift b/ReCaptcha/Classes/Rx/ReCaptcha+Rx.swift index 4174e78..828826d 100644 --- a/ReCaptcha/Classes/Rx/ReCaptcha+Rx.swift +++ b/ReCaptcha/Classes/Rx/ReCaptcha+Rx.swift @@ -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` that may contain a valid token. + + - See: + [ReCaptchaWebViewManager.validate](../Classes/ReCaptchaWebViewManager.html#/s:FC9ReCaptcha23ReCaptchaWebViewManager8validateFT10completionFGO6Result6ResultSSCSo7NSError_T__T_) + */ public func validate() -> Observable { return Observable.create { [weak base] (observer: AnyObserver) in base?.validate { response in