Compare commits
13 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
b40f0ac185 | |
|
|
dbcad92ff0 | |
|
|
605e1aeeef | |
|
|
6e712bf664 | |
|
|
0b860372e9 | |
|
|
c8f4fb213e | |
|
|
7ba0aff437 | |
|
|
b3994f932e | |
|
|
1d178f7154 | |
|
|
784187e911 | |
|
|
31a64e5967 | |
|
|
ce8acad6a2 | |
|
|
0e47c7264f |
|
|
@ -1,14 +1,13 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
<!--
|
||||
## Is it really a bug?
|
||||
Before opening an issue, check the following:
|
||||
1. You are using the **Client side integration** key
|
||||
1. You are using the **SITE** key
|
||||
2. The correct domain, with protocol, is setup.
|
||||
3. You are using an **Invisible** reCAPTCHA key.
|
||||
3. You are using an **Invisible** reCAPTCHA v2 key.
|
||||
4. If the widget doesn't appear, that is expected since the library will try to resolve the challenge _invisibly_.
|
||||
|
||||
https://www.google.com/recaptcha/admin#site
|
||||
|
|
@ -20,7 +19,7 @@ A clear and concise description of what the bug is.
|
|||
## To Reproduce
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
2. Click on '...'
|
||||
3. ...
|
||||
4. Profit (jk See error)
|
||||
|
||||
|
|
|
|||
2
Cartfile
2
Cartfile
|
|
@ -1 +1 @@
|
|||
github "ReactiveX/RxSwift" ~> 4.3
|
||||
binary "https://raw.github.com/TouchInstinct/CarthageBinaries/master/RxSwift/RxSwift.json"
|
||||
|
|
@ -1 +1 @@
|
|||
github "ReactiveX/RxSwift" "4.4.0"
|
||||
binary "https://raw.github.com/TouchInstinct/CarthageBinaries/master/RxSwift/RxSwift.json" "4.5.0"
|
||||
|
|
|
|||
15
README.md
15
README.md
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
-----
|
||||
|
||||
Add Google's [Invisible ReCaptcha](https://developers.google.com/recaptcha/docs/invisible) to your project. This library
|
||||
Add Google's [Invisible ReCaptcha v2](https://developers.google.com/recaptcha/docs/invisible) to your project. This library
|
||||
automatically handles ReCaptcha's events and retrieves the validation token or notifies you to present the challenge if
|
||||
invisibility is not possible.
|
||||
|
||||
|
|
@ -17,8 +17,15 @@ invisibility is not possible.
|
|||
|
||||
#### _Warning_ ⚠️
|
||||
|
||||
Beware that this library only works for Invisible ReCaptcha keys! Make sure to check the Invisible reCAPTCHA option
|
||||
when creating your [API Key](https://www.google.com/recaptcha/admin).
|
||||
Beware that this library only works for ReCaptcha v2 Invisible keys! Make sure to check the reCAPTCHA
|
||||
v2 Invisible badge option when creating your [API Key](https://www.google.com/recaptcha/admin/create).
|
||||
|
||||

|
||||
|
||||
You won't be able to use a ReCaptcha v3 key because it requires server-side validation. On v3, all
|
||||
challenges succeed into a token which is then validated in the server for a score. For this reason,
|
||||
a frontend app can't know on its own wether or not a user is valid since the challenge will always
|
||||
result in a valid token.
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
@ -42,7 +49,7 @@ extension for the ReCaptcha framework.
|
|||
|
||||
## Usage
|
||||
|
||||
Simply add `ReCaptchaKey` and `ReCaptchaDomain` (with a protocol) to your Info.plist and run:
|
||||
Simply add `ReCaptchaKey` and `ReCaptchaDomain` (with a protocol ex. http:// or https://) to your Info.plist and run:
|
||||
|
||||
``` swift
|
||||
let recaptcha = try? ReCaptcha()
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import WebKit
|
|||
/**
|
||||
*/
|
||||
public class ReCaptcha {
|
||||
public typealias BoolParameterClosure = (Bool) -> ()
|
||||
|
||||
fileprivate struct Constants {
|
||||
struct InfoDictKeys {
|
||||
static let APIKey = "ReCaptchaKey"
|
||||
|
|
@ -101,6 +103,12 @@ public class ReCaptcha {
|
|||
}
|
||||
}
|
||||
|
||||
/// Callback for WebView loading state changing
|
||||
public var onLoadingChanged: BoolParameterClosure? {
|
||||
get { return manager.onLoadingChanged }
|
||||
set { manager.onLoadingChanged = newValue }
|
||||
}
|
||||
|
||||
/// The worker that handles webview events and communication
|
||||
let manager: ReCaptchaWebViewManager
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,13 @@ internal class ReCaptchaWebViewManager {
|
|||
static let ExecuteJSCommand = "execute();"
|
||||
static let ResetCommand = "reset();"
|
||||
static let BotUserAgent = "Googlebot/2.1"
|
||||
static let NumberOfDivsCommand = "document.getElementsByTagName(\"div\").length"
|
||||
|
||||
static let MinNumberOfDivs = 5
|
||||
static let NumberOfDivsFinishedLoadingAttempts = 10
|
||||
|
||||
// A page doesn't have enough time to load on old devices
|
||||
static let NumberOfDivsLoadingDelay = 0.5
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
|
@ -37,6 +44,9 @@ internal class ReCaptchaWebViewManager {
|
|||
public var shouldSkipForTests = false
|
||||
#endif
|
||||
|
||||
/// Callback for loading state changing
|
||||
var onLoadingChanged: ReCaptcha.BoolParameterClosure?
|
||||
|
||||
/// Sends the result message
|
||||
var completion: ((ReCaptchaResult) -> Void)?
|
||||
|
||||
|
|
@ -99,6 +109,7 @@ internal class ReCaptchaWebViewManager {
|
|||
*/
|
||||
init(html: String, apiKey: String, baseURL: URL, endpoint: String) {
|
||||
self.endpoint = endpoint
|
||||
|
||||
self.decoder = ReCaptchaDecoder { [weak self] result in
|
||||
self?.handle(result: result)
|
||||
}
|
||||
|
|
@ -126,6 +137,7 @@ internal class ReCaptchaWebViewManager {
|
|||
Starts the challenge validation
|
||||
*/
|
||||
func validate(on view: UIView) {
|
||||
onLoadingChanged?(true)
|
||||
#if DEBUG
|
||||
guard !shouldSkipForTests else {
|
||||
completion?(.token(""))
|
||||
|
|
@ -166,7 +178,7 @@ internal class ReCaptchaWebViewManager {
|
|||
/** Private methods for ReCaptchaWebViewManager
|
||||
*/
|
||||
fileprivate extension ReCaptchaWebViewManager {
|
||||
/** Executes the JS command that loads the ReCaptcha challenge.
|
||||
/** Executes the JS command that loads the ReCaptcha challenge after a page finished loading.
|
||||
This method has no effect if the webview hasn't finished loading.
|
||||
*/
|
||||
func execute() {
|
||||
|
|
@ -175,10 +187,65 @@ fileprivate extension ReCaptchaWebViewManager {
|
|||
return
|
||||
}
|
||||
|
||||
evaluateExecuteWhenLoadingFinished(count: 0)
|
||||
}
|
||||
|
||||
/**
|
||||
- parameter count: Number of checks of number of divs
|
||||
|
||||
Executes the JS command that returns number of divs.
|
||||
*/
|
||||
func evaluateExecuteWhenLoadingFinished(count: Int) {
|
||||
webView.evaluateJavaScript(Constants.NumberOfDivsCommand) { [weak self] (result, error) -> Void in
|
||||
if let error = error {
|
||||
self?.decoder.send(error: .unexpected(error))
|
||||
self?.onLoadingChanged?(false)
|
||||
} else {
|
||||
self?.handleNumberOfDivs(result: result, count: count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
- parameters:
|
||||
- result: Result of number of divs command evaluation
|
||||
- count: Number of checks of divs count
|
||||
|
||||
Handles number of divs command result.
|
||||
*/
|
||||
func handleNumberOfDivs(result: Any?, count: Int) {
|
||||
if let result = result as? Int, result >= Constants.MinNumberOfDivs {
|
||||
evaluateExecute()
|
||||
} else {
|
||||
handleInvalidNumberOfDivsResult(count: count)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
- parameter count: Number of checks of number of divs
|
||||
|
||||
Handles invalid number of divs.
|
||||
*/
|
||||
func handleInvalidNumberOfDivsResult(count: Int) {
|
||||
if count < Constants.NumberOfDivsFinishedLoadingAttempts {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + Constants.NumberOfDivsLoadingDelay) { [weak self] in
|
||||
self?.evaluateExecuteWhenLoadingFinished(count: count + 1)
|
||||
}
|
||||
} else {
|
||||
decoder.send(error: .htmlLoadError)
|
||||
onLoadingChanged?(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Executes the JS command that loads the ReCaptcha challenge.
|
||||
*/
|
||||
func evaluateExecute() {
|
||||
webView.evaluateJavaScript(Constants.ExecuteJSCommand) { [weak self] _, error in
|
||||
if let error = error {
|
||||
self?.decoder.send(error: .unexpected(error))
|
||||
}
|
||||
self?.onLoadingChanged?(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@
|
|||
import RxSwift
|
||||
import UIKit
|
||||
|
||||
public enum ReCaptchaRxError: Error {
|
||||
case baseWasReleased
|
||||
}
|
||||
|
||||
/// Makes ReCaptcha compatible with RxSwift extensions
|
||||
extension ReCaptcha: ReactiveCompatible {}
|
||||
|
||||
|
|
@ -64,4 +68,20 @@ public extension Reactive where Base: ReCaptcha {
|
|||
base?.reset()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Observable of loading state
|
||||
(will not work if someone changes onLoadingChanged variable; current onLodinglChanged will not work after subscription)
|
||||
*/
|
||||
var loadingObservable: Observable<Bool> {
|
||||
return .create { [weak base] observer -> Disposable in
|
||||
guard let base = base else {
|
||||
observer.onError(ReCaptchaRxError.baseWasReleased)
|
||||
return Disposables.create()
|
||||
}
|
||||
|
||||
base.onLoadingChanged = { observer.onNext($0) }
|
||||
return Disposables.create { [weak base] in base?.onLoadingChanged = nil }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
Loading…
Reference in New Issue