Compare commits
10 Commits
fix/resour
...
master
| Author | SHA1 | Date |
|---|---|---|
|
|
b40f0ac185 | |
|
|
dbcad92ff0 | |
|
|
605e1aeeef | |
|
|
6e712bf664 | |
|
|
0b860372e9 | |
|
|
c8f4fb213e | |
|
|
7ba0aff437 | |
|
|
b3994f932e | |
|
|
1d178f7154 | |
|
|
784187e911 |
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"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
PODS:
|
||||
- AppSwizzle (1.3.1)
|
||||
- ReCaptcha/Core (1.4.2)
|
||||
- ReCaptcha/RxSwift (1.4.2):
|
||||
- ReCaptcha/Core (1.4.1)
|
||||
- ReCaptcha/RxSwift (1.4.1):
|
||||
- ReCaptcha/Core
|
||||
- RxSwift (~> 4.3)
|
||||
- RxAtomic (4.4.0)
|
||||
|
|
@ -36,7 +36,7 @@ EXTERNAL SOURCES:
|
|||
|
||||
SPEC CHECKSUMS:
|
||||
AppSwizzle: db36e436f56110d93e5ae0147683435df593cabc
|
||||
ReCaptcha: 9a0e1c02a9db9dface31cca63515e28fc3ed6ba8
|
||||
ReCaptcha: 520a707a38dfbb1e5de812aa3c041df60bd31827
|
||||
RxAtomic: eacf60db868c96bfd63320e28619fe29c179656f
|
||||
RxBlocking: 138ad53217434444d6eeeb4fb406a45431d92e31
|
||||
RxCocoa: df63ebf7b9a70d6b4eeea407ed5dd4efc8979749
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@
|
|||
</BuildableReference>
|
||||
</TestableReference>
|
||||
<TestableReference
|
||||
skipped = "YES">
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "F28FAC9B200E425600E14987"
|
||||
|
|
|
|||
|
|
@ -1,90 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0910"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "F28FAC9B200E425600E14987"
|
||||
BuildableName = "ReCaptcha_UITests.xctest"
|
||||
BlueprintName = "ReCaptcha_UITests"
|
||||
ReferencedContainer = "container:ReCaptcha.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "F28FAC9B200E425600E14987"
|
||||
BuildableName = "ReCaptcha_UITests.xctest"
|
||||
BlueprintName = "ReCaptcha_UITests"
|
||||
ReferencedContainer = "container:ReCaptcha.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "F28FAC9B200E425600E14987"
|
||||
BuildableName = "ReCaptcha_UITests.xctest"
|
||||
BlueprintName = "ReCaptcha_UITests"
|
||||
ReferencedContainer = "container:ReCaptcha.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "F28FAC9B200E425600E14987"
|
||||
BuildableName = "ReCaptcha_UITests.xctest"
|
||||
BlueprintName = "ReCaptcha_UITests"
|
||||
ReferencedContainer = "container:ReCaptcha.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
|
|
@ -164,23 +164,4 @@ class ReCaptchaDecoder__Tests: XCTestCase {
|
|||
// Check
|
||||
XCTAssertEqual(result, .didLoad)
|
||||
}
|
||||
|
||||
func test__Decode__Error_Setup_Failed() {
|
||||
let exp = expectation(description: "send error")
|
||||
var result: Result?
|
||||
|
||||
assertResult = { res in
|
||||
result = res
|
||||
exp.fulfill()
|
||||
}
|
||||
|
||||
// Send
|
||||
let message = MockMessage(message: ["error": 27])
|
||||
decoder.send(message: message)
|
||||
|
||||
waitForExpectations(timeout: 1)
|
||||
|
||||
// Check
|
||||
XCTAssertEqual(result, .error(.failedSetup))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,7 @@ extension ReCaptchaError: Equatable {
|
|||
case (.htmlLoadError, .htmlLoadError),
|
||||
(.apiKeyNotFound, .apiKeyNotFound),
|
||||
(.baseURLNotFound, .baseURLNotFound),
|
||||
(.wrongMessageFormat, .wrongMessageFormat),
|
||||
(.failedSetup, .failedSetup):
|
||||
(.wrongMessageFormat, .wrongMessageFormat):
|
||||
return true
|
||||
case (.unexpected(let lhe as NSError), .unexpected(let rhe as NSError)):
|
||||
return lhe == rhe
|
||||
|
|
@ -26,12 +25,11 @@ extension ReCaptchaError: Equatable {
|
|||
}
|
||||
|
||||
static func random() -> ReCaptchaError {
|
||||
switch arc4random_uniform(5) {
|
||||
switch arc4random_uniform(4) {
|
||||
case 0: return .htmlLoadError
|
||||
case 1: return .apiKeyNotFound
|
||||
case 2: return .baseURLNotFound
|
||||
case 3: return .wrongMessageFormat
|
||||
case 4: return .failedSetup
|
||||
default: return .unexpected(NSError())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@
|
|||
var endpoint = "${endpoint}";
|
||||
var shouldFail = ${shouldFail};
|
||||
|
||||
var post = (value) => {
|
||||
var post = function(value) {
|
||||
window.webkit.messageHandlers.recaptcha.postMessage(value);
|
||||
};
|
||||
|
||||
var execute = () => {
|
||||
var execute = function() {
|
||||
if (shouldFail) {
|
||||
post("error");
|
||||
}
|
||||
|
|
@ -19,12 +19,10 @@
|
|||
}
|
||||
};
|
||||
|
||||
var reset = () => {
|
||||
var reset = function() {
|
||||
shouldFail = false;
|
||||
post({ action: "didLoad" });
|
||||
post({action: "didLoad"});
|
||||
};
|
||||
|
||||
post({ action: "didLoad" });
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -2,81 +2,61 @@
|
|||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<script type="text/javascript">
|
||||
const post = (value) => window.webkit.messageHandlers.recaptcha.postMessage(value);
|
||||
console.log = (message) => post({ log: message });
|
||||
|
||||
let observers = []
|
||||
const observeDOM = (element, completion) => {
|
||||
const obs = new MutationObserver(completion);
|
||||
obs.observe(element, {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributeFilter: ['style'],
|
||||
});
|
||||
|
||||
observers.push(obs);
|
||||
var post = function(value) {
|
||||
window.webkit.messageHandlers.recaptcha.postMessage(value);
|
||||
};
|
||||
|
||||
const clearObservers = () => {
|
||||
observers.forEach(o => o.disconnect());
|
||||
observers = [];
|
||||
console.log = function(message) {
|
||||
post({log: message});
|
||||
};
|
||||
|
||||
const execute = () => {
|
||||
console.log('executing');
|
||||
var showReCaptcha = function() {
|
||||
console.log("showReCaptcha");
|
||||
post({action: "showReCaptcha"});
|
||||
};
|
||||
|
||||
var observeDOM = function(element, completion) {
|
||||
new MutationObserver(function(mutations) {
|
||||
mutations.forEach(function(mutationRecord) {
|
||||
completion();
|
||||
});
|
||||
})
|
||||
.observe(element, {attributes: true, attributeFilter: ['style']})
|
||||
};
|
||||
|
||||
var execute = function() {
|
||||
console.log("executing");
|
||||
|
||||
// Removes ReCaptcha dismissal when clicking outside div area
|
||||
try {
|
||||
document.getElementsByTagName('div')[4].outerHTML = ''
|
||||
document.getElementsByTagName("div")[4].outerHTML = ""
|
||||
}
|
||||
catch(e) {
|
||||
}
|
||||
|
||||
try {
|
||||
// Listens to changes on the div element that presents the ReCaptcha challenge
|
||||
observeDOM(document.getElementsByTagName('div')[3], () => {
|
||||
post({ action: 'showReCaptcha' });
|
||||
});
|
||||
} catch(e) {
|
||||
post({ error: 27 })
|
||||
}
|
||||
// Listens to changes on the div element that presents the ReCaptcha challenge
|
||||
observeDOM(document.getElementsByTagName("div")[3], showReCaptcha);
|
||||
|
||||
grecaptcha.execute();
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
console.log('resetting');
|
||||
grecaptcha.reset();
|
||||
grecaptcha.ready(() => post({ action: 'didLoad' }));
|
||||
};
|
||||
var onSubmit = function(token) {
|
||||
console.log(token);
|
||||
post({token: token});
|
||||
};
|
||||
|
||||
var onloadCallback = () => {
|
||||
var onloadCallback = function() {
|
||||
console.log("did load");
|
||||
grecaptcha.render('submit', {
|
||||
sitekey: '${apiKey}',
|
||||
callback: (token) => {
|
||||
console.log(token);
|
||||
post({ token });
|
||||
clearObservers();
|
||||
},
|
||||
size: 'invisible'
|
||||
'sitekey' : '${apiKey}',
|
||||
'callback' : onSubmit,
|
||||
'size': 'invisible'
|
||||
});
|
||||
};
|
||||
|
||||
grecaptcha.ready(() => {
|
||||
observeDOM(document.getElementById('body'), mut => {
|
||||
const success = !!mut.find(
|
||||
({ addedNodes }) =>
|
||||
Array.from(addedNodes.values())
|
||||
.find(({ nodeName, name }) =>
|
||||
nodeName === 'IFRAME' && !!name
|
||||
)
|
||||
);
|
||||
|
||||
if (success) {
|
||||
post({ action: 'didLoad' });
|
||||
}
|
||||
});
|
||||
});
|
||||
var reset = function() {
|
||||
console.log("resetting");
|
||||
grecaptcha.reset();
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -89,12 +89,6 @@ fileprivate extension ReCaptchaDecoder.Result {
|
|||
if let token = response["token"] as? String {
|
||||
return .token(token)
|
||||
}
|
||||
else if let message = response["log"] as? String {
|
||||
return .log(message)
|
||||
}
|
||||
else if let message = response["error"] as? Int {
|
||||
return .error(.failedSetup)
|
||||
}
|
||||
|
||||
if let action = response["action"] as? String {
|
||||
switch action {
|
||||
|
|
|
|||
|
|
@ -25,8 +25,6 @@ public enum ReCaptchaError: Error, CustomStringConvertible {
|
|||
/// Received an unexpected message from javascript
|
||||
case wrongMessageFormat
|
||||
|
||||
/// ReCaptcha setup failed
|
||||
case failedSetup
|
||||
|
||||
/// A human-readable description for each error
|
||||
public var description: String {
|
||||
|
|
@ -45,14 +43,6 @@ public enum ReCaptchaError: Error, CustomStringConvertible {
|
|||
|
||||
case .wrongMessageFormat:
|
||||
return "Unexpected message from javascript"
|
||||
|
||||
case .failedSetup:
|
||||
// swiftlint:disable line_length
|
||||
return """
|
||||
⚠️ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,15 +13,18 @@ import WebKit
|
|||
/** Handles comunications with the webview containing the ReCaptcha challenge.
|
||||
*/
|
||||
internal class ReCaptchaWebViewManager {
|
||||
enum JSCommand: String {
|
||||
case execute = "execute();"
|
||||
case reset = "reset();"
|
||||
}
|
||||
|
||||
fileprivate struct Constants {
|
||||
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
|
||||
|
|
@ -41,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)?
|
||||
|
||||
|
|
@ -57,11 +63,24 @@ internal class ReCaptchaWebViewManager {
|
|||
fileprivate var decoder: ReCaptchaDecoder!
|
||||
|
||||
/// Indicates if the script has already been loaded by the `webView`
|
||||
fileprivate var didFinishLoading = false
|
||||
fileprivate var didFinishLoading = false { // webView.isLoading does not work for WKWebview.loadHTMLString
|
||||
didSet {
|
||||
if didFinishLoading && completion != nil {
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The observer for `.UIWindowDidBecomeVisible`
|
||||
fileprivate var observer: NSObjectProtocol?
|
||||
|
||||
/// The observer for `\WKWebView.estimatedProgress`
|
||||
fileprivate var loadingObservation: NSKeyValueObservation?
|
||||
|
||||
/// The endpoint url being used
|
||||
fileprivate var endpoint: String
|
||||
|
||||
|
|
@ -74,6 +93,9 @@ internal class ReCaptchaWebViewManager {
|
|||
webview.accessibilityIdentifier = "webview"
|
||||
webview.accessibilityTraits = UIAccessibilityTraits.link
|
||||
webview.isHidden = true
|
||||
self.loadingObservation = webview.observe(\.estimatedProgress, options: [.new]) { [weak self] _, change in
|
||||
self?.didFinishLoading = change.newValue == 1
|
||||
}
|
||||
|
||||
return webview
|
||||
}()
|
||||
|
|
@ -87,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)
|
||||
}
|
||||
|
|
@ -114,6 +137,7 @@ internal class ReCaptchaWebViewManager {
|
|||
Starts the challenge validation
|
||||
*/
|
||||
func validate(on view: UIView) {
|
||||
onLoadingChanged?(true)
|
||||
#if DEBUG
|
||||
guard !shouldSkipForTests else {
|
||||
completion?(.token(""))
|
||||
|
|
@ -123,7 +147,7 @@ internal class ReCaptchaWebViewManager {
|
|||
webView.isHidden = false
|
||||
view.addSubview(webView)
|
||||
|
||||
executeJS(command: .execute)
|
||||
execute()
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -138,9 +162,14 @@ internal class ReCaptchaWebViewManager {
|
|||
The reset is achieved by calling `grecaptcha.reset()` on the JS API.
|
||||
*/
|
||||
func reset() {
|
||||
configureWebViewDispatchToken = UUID()
|
||||
executeJS(command: .reset)
|
||||
didFinishLoading = false
|
||||
configureWebViewDispatchToken = UUID()
|
||||
|
||||
webView.evaluateJavaScript(Constants.ResetCommand) { [weak self] _, error in
|
||||
if let error = error {
|
||||
self?.decoder.send(error: .unexpected(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -149,6 +178,77 @@ internal class ReCaptchaWebViewManager {
|
|||
/** Private methods for ReCaptchaWebViewManager
|
||||
*/
|
||||
fileprivate extension ReCaptchaWebViewManager {
|
||||
/** 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() {
|
||||
guard didFinishLoading else {
|
||||
// Hasn't finished loading the HTML yet
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
- returns: An instance of `WKWebViewConfiguration`
|
||||
|
||||
|
|
@ -190,14 +290,12 @@ fileprivate extension ReCaptchaWebViewManager {
|
|||
}
|
||||
|
||||
case .didLoad:
|
||||
// For testing purposes
|
||||
didFinishLoading = true
|
||||
if completion != nil {
|
||||
executeJS(command: .execute)
|
||||
}
|
||||
|
||||
case .log(let message):
|
||||
#if DEBUG
|
||||
print("[JS LOG]:", message)
|
||||
print("[JS LOG]:", message)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
@ -218,24 +316,4 @@ fileprivate extension ReCaptchaWebViewManager {
|
|||
NotificationCenter.default.removeObserver(observer)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
- parameters:
|
||||
- command: The JavaScript command to be executed
|
||||
|
||||
Executes the JS command that loads the ReCaptcha challenge. This method has no effect if the webview hasn't
|
||||
finished loading.
|
||||
*/
|
||||
func executeJS(command: JSCommand) {
|
||||
guard didFinishLoading else {
|
||||
// Hasn't finished loading all the resources
|
||||
return
|
||||
}
|
||||
|
||||
webView.evaluateJavaScript(command.rawValue) { [weak self] _, error in
|
||||
if let error = error {
|
||||
self?.decoder.send(error: .unexpected(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ default_platform :ios
|
|||
platform :ios do
|
||||
skip_docs
|
||||
|
||||
devices = ["iPhone X (~> 12)"]
|
||||
# devices << "iPhone X (~> 11)" if !Helper.is_ci?
|
||||
# devices << "iPhone 7 (~> 10)" if !Helper.is_ci?
|
||||
# devices << "iPhone 6s (~> 9)" if !Helper.is_ci?
|
||||
devices = ["iPhone XR (~> 12)"]
|
||||
devices << "iPhone X (~> 11)" if !Helper.is_ci?
|
||||
devices << "iPhone 7 (~> 10)" if !Helper.is_ci?
|
||||
devices << "iPhone 6s (~> 9)" if !Helper.is_ci?
|
||||
|
||||
desc "Runs the following lanes:\n- test\n- pod_lint\n- carthage_lint"
|
||||
lane :ci do
|
||||
|
|
@ -57,21 +57,12 @@ platform :ios do
|
|||
code_coverage: true,
|
||||
)
|
||||
|
||||
if Helper.is_ci?
|
||||
if is_ci
|
||||
codecov(
|
||||
project_name: 'ReCaptcha',
|
||||
use_xcodeplist: true,
|
||||
)
|
||||
else
|
||||
puts "Running UI Tests"
|
||||
scan(
|
||||
test_without_building: true,
|
||||
devices: self.select_similar_simulator(devices),
|
||||
scheme: "ReCaptcha_UITests",
|
||||
workspace: "Example/ReCaptcha.xcworkspace",
|
||||
code_coverage: true,
|
||||
)
|
||||
|
||||
puts "Not CI: Skipping coverage files upload"
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue