Add verification for ReCaptcha config
This will verify if ReCaptchaKey and ReCaptchaDomain are correctly configured and the JS lib was able to load correctly
This commit is contained in:
parent
31a64e5967
commit
4e6c022a23
|
|
@ -58,9 +58,11 @@ class ViewController: UIViewController {
|
|||
}
|
||||
|
||||
@IBAction private func didPressButton(button: UIButton) {
|
||||
label.text = ""
|
||||
|
||||
disposeBag = DisposeBag()
|
||||
|
||||
let validate = recaptcha.rx.validate(on: view)
|
||||
let validate = recaptcha.rx.validate(on: view, resetOnError: false)
|
||||
.debug("validate")
|
||||
.share()
|
||||
|
||||
|
|
|
|||
|
|
@ -164,4 +164,25 @@ class ReCaptchaDecoder__Tests: XCTestCase {
|
|||
// Check
|
||||
XCTAssertEqual(result, .didLoad)
|
||||
}
|
||||
|
||||
|
||||
func test__Decode__Verify() {
|
||||
let exp = expectation(description: "send verify")
|
||||
let expectedResult = true
|
||||
var result: Result?
|
||||
|
||||
assertResult = { res in
|
||||
result = res
|
||||
exp.fulfill()
|
||||
}
|
||||
|
||||
// Send
|
||||
let message = MockMessage(message: ["verify": expectedResult])
|
||||
decoder.send(message: message)
|
||||
|
||||
waitForExpectations(timeout: 1)
|
||||
|
||||
// Check
|
||||
XCTAssertEqual(result, .verify(expectedResult))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,9 @@ extension ReCaptchaDecoder.Result: Equatable {
|
|||
case (.error(let lhe), .error(let rhe)):
|
||||
return lhe == rhe
|
||||
|
||||
case (.verify(let lhv), .verify(let rhv)):
|
||||
return lhv == rhv
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<script type="text/javascript">
|
||||
var key = "${apiKey}";
|
||||
var endpoint = "${endpoint}";
|
||||
var key = '${apiKey}';
|
||||
var endpoint = '${endpoint}';
|
||||
var shouldFail = ${shouldFail};
|
||||
|
||||
var post = function(value) {
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
var execute = function() {
|
||||
if (shouldFail) {
|
||||
post("error");
|
||||
post('error');
|
||||
}
|
||||
else {
|
||||
post(${message});
|
||||
|
|
@ -21,7 +21,11 @@
|
|||
|
||||
var reset = function() {
|
||||
shouldFail = false;
|
||||
post({action: "didLoad"});
|
||||
post({ action: 'didLoad' });
|
||||
};
|
||||
|
||||
var verify = function() {
|
||||
post({ verify: shouldFail });
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@
|
|||
};
|
||||
|
||||
console.log = function(message) {
|
||||
post({log: message});
|
||||
post({ log: message });
|
||||
};
|
||||
|
||||
var showReCaptcha = function() {
|
||||
console.log("showReCaptcha");
|
||||
post({action: "showReCaptcha"});
|
||||
console.log('showReCaptcha');
|
||||
post({ action: 'showReCaptcha' });
|
||||
};
|
||||
|
||||
var observeDOM = function(element, completion) {
|
||||
|
|
@ -21,42 +21,49 @@
|
|||
completion();
|
||||
});
|
||||
})
|
||||
.observe(element, {attributes: true, attributeFilter: ['style']})
|
||||
.observe(element, {
|
||||
attributes: true,
|
||||
attributeFilter: ['style'],
|
||||
});
|
||||
};
|
||||
|
||||
var execute = function() {
|
||||
console.log("executing");
|
||||
console.log('executing');
|
||||
|
||||
// Removes ReCaptcha dismissal when clicking outside div area
|
||||
try {
|
||||
document.getElementsByTagName("div")[4].outerHTML = ""
|
||||
}
|
||||
catch(e) {
|
||||
document.getElementsByTagName('div')[4].outerHTML = '';
|
||||
}
|
||||
catch(e) {}
|
||||
|
||||
// Listens to changes on the div element that presents the ReCaptcha challenge
|
||||
observeDOM(document.getElementsByTagName("div")[3], showReCaptcha);
|
||||
observeDOM(document.getElementsByTagName('div')[3], showReCaptcha);
|
||||
|
||||
grecaptcha.execute();
|
||||
};
|
||||
|
||||
var onSubmit = function(token) {
|
||||
console.log(token);
|
||||
post({token: token});
|
||||
post({ token });
|
||||
};
|
||||
|
||||
var onloadCallback = function() {
|
||||
console.log("did load");
|
||||
console.log('did load');
|
||||
grecaptcha.render('submit', {
|
||||
'sitekey' : '${apiKey}',
|
||||
'callback' : onSubmit,
|
||||
'size': 'invisible'
|
||||
sitekey : '${apiKey}',
|
||||
callback : onSubmit,
|
||||
size: 'invisible'
|
||||
});
|
||||
};
|
||||
|
||||
var reset = function() {
|
||||
console.log("resetting");
|
||||
grecaptcha.reset();
|
||||
console.log('resetting');
|
||||
grecaptcha.reset();
|
||||
};
|
||||
|
||||
var verify = function() {
|
||||
const challengeDiv = document.getElementsByTagName('div')[3];
|
||||
post({ verify: challengeDiv !== undefined });
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ internal class ReCaptchaDecoder: NSObject {
|
|||
|
||||
/// Logs a string onto the console
|
||||
case log(String)
|
||||
|
||||
/// Verifies the configuration
|
||||
case verify(Bool)
|
||||
}
|
||||
|
||||
/// The closure that receives messages
|
||||
|
|
@ -89,6 +92,12 @@ fileprivate extension ReCaptchaDecoder.Result {
|
|||
if let token = response["token"] as? String {
|
||||
return .token(token)
|
||||
}
|
||||
else if let success = response["verify"] as? Bool {
|
||||
return .verify(success)
|
||||
}
|
||||
else if let message = response["log"] as? String {
|
||||
return .log(message)
|
||||
}
|
||||
|
||||
if let action = response["action"] as? String {
|
||||
switch action {
|
||||
|
|
@ -103,10 +112,6 @@ fileprivate extension ReCaptchaDecoder.Result {
|
|||
}
|
||||
}
|
||||
|
||||
if let message = response["log"] as? String {
|
||||
return .log(message)
|
||||
}
|
||||
|
||||
return .error(.wrongMessageFormat)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,10 +13,13 @@ import WebKit
|
|||
/** Handles comunications with the webview containing the ReCaptcha challenge.
|
||||
*/
|
||||
internal class ReCaptchaWebViewManager {
|
||||
enum JSCommand: String {
|
||||
case execute = "execute();"
|
||||
case reset = "reset();"
|
||||
case verify = "verify();"
|
||||
}
|
||||
|
||||
fileprivate struct Constants {
|
||||
static let ExecuteJSCommand = "execute();"
|
||||
static let ResetCommand = "reset();"
|
||||
static let BotUserAgent = "Googlebot/2.1"
|
||||
}
|
||||
|
||||
|
|
@ -55,11 +58,15 @@ internal class ReCaptchaWebViewManager {
|
|||
/// Indicates if the script has already been loaded by the `webView`
|
||||
fileprivate var didFinishLoading = false { // webView.isLoading does not work for WKWebview.loadHTMLString
|
||||
didSet {
|
||||
if didFinishLoading && completion != nil {
|
||||
if didFinishLoading {
|
||||
// User has requested for validation
|
||||
// A small delay is necessary to allow JS to wrap its operations and avoid errors.
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in
|
||||
self?.execute()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
|
||||
self?.executeJS(command: .verify)
|
||||
|
||||
if self?.completion != nil {
|
||||
self?.executeJS(command: .execute)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -135,7 +142,7 @@ internal class ReCaptchaWebViewManager {
|
|||
webView.isHidden = false
|
||||
view.addSubview(webView)
|
||||
|
||||
execute()
|
||||
executeJS(command: .execute)
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -150,14 +157,9 @@ internal class ReCaptchaWebViewManager {
|
|||
The reset is achieved by calling `grecaptcha.reset()` on the JS API.
|
||||
*/
|
||||
func reset() {
|
||||
didFinishLoading = false
|
||||
configureWebViewDispatchToken = UUID()
|
||||
|
||||
webView.evaluateJavaScript(Constants.ResetCommand) { [weak self] _, error in
|
||||
if let error = error {
|
||||
self?.decoder.send(error: .unexpected(error))
|
||||
}
|
||||
}
|
||||
executeJS(command: .reset)
|
||||
didFinishLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -166,22 +168,6 @@ internal 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.
|
||||
*/
|
||||
func execute() {
|
||||
guard didFinishLoading else {
|
||||
// Hasn't finished loading the HTML yet
|
||||
return
|
||||
}
|
||||
|
||||
webView.evaluateJavaScript(Constants.ExecuteJSCommand) { [weak self] _, error in
|
||||
if let error = error {
|
||||
self?.decoder.send(error: .unexpected(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
- returns: An instance of `WKWebViewConfiguration`
|
||||
|
||||
|
|
@ -208,6 +194,10 @@ fileprivate extension ReCaptchaWebViewManager {
|
|||
completion?(.token(token))
|
||||
|
||||
case .error(let error):
|
||||
#if DEBUG
|
||||
print("[JS ERROR]:", error)
|
||||
#endif
|
||||
|
||||
if shouldResetOnError, let view = webView.superview {
|
||||
reset()
|
||||
validate(on: view)
|
||||
|
|
@ -228,8 +218,20 @@ fileprivate extension ReCaptchaWebViewManager {
|
|||
|
||||
case .log(let message):
|
||||
#if DEBUG
|
||||
print("[JS LOG]:", message)
|
||||
print("[JS LOG]:", message)
|
||||
#endif
|
||||
|
||||
case .verify(let success):
|
||||
if !success {
|
||||
#if DEBUG
|
||||
// swiftlint:disable line_length
|
||||
print("""
|
||||
⚠️ WARNING! ReCaptcha wasn't successfully configured. Please double check your ReCaptchaKey and ReCaptchaDomain.
|
||||
Also check that you're using ReCaptcha's **SITE KEY** for client side integration.
|
||||
""")
|
||||
// swiftlint:enable line_length
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -249,4 +251,24 @@ fileprivate extension ReCaptchaWebViewManager {
|
|||
NotificationCenter.default.removeObserver(observer)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
- parameters:
|
||||
- command: The JS command to be executed
|
||||
|
||||
Executes the given JS command. If an error happens, it is thrown to the user. This method has no effect if the
|
||||
webview hasn't finished loading.
|
||||
*/
|
||||
func executeJS(command: JSCommand) {
|
||||
guard didFinishLoading else {
|
||||
// Hasn't finished loading the HTML yet
|
||||
return
|
||||
}
|
||||
|
||||
webView.evaluateJavaScript(command.rawValue) { [weak self] _, error in
|
||||
if let error = error {
|
||||
self?.decoder.send(error: .unexpected(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue