Compare commits
40 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
b40f0ac185 | |
|
|
dbcad92ff0 | |
|
|
605e1aeeef | |
|
|
6e712bf664 | |
|
|
0b860372e9 | |
|
|
c8f4fb213e | |
|
|
7ba0aff437 | |
|
|
b3994f932e | |
|
|
1d178f7154 | |
|
|
784187e911 | |
|
|
31a64e5967 | |
|
|
ce8acad6a2 | |
|
|
0e47c7264f | |
|
|
c9afbc22c1 | |
|
|
050ce19425 | |
|
|
dc4010cc6e | |
|
|
d0af0f686b | |
|
|
59c5223580 | |
|
|
484ed94a9b | |
|
|
7dc2722bb5 | |
|
|
9f3ac2efa7 | |
|
|
ee878521a3 | |
|
|
184be54b90 | |
|
|
a88fcb9850 | |
|
|
50046bff25 | |
|
|
0389c5afa0 | |
|
|
a9117cbb7d | |
|
|
11074817f1 | |
|
|
e540042554 | |
|
|
438bb57c38 | |
|
|
3821c300b7 | |
|
|
87355c1b13 | |
|
|
3c5cb4fe59 | |
|
|
66b70da874 | |
|
|
1c3af347e4 | |
|
|
0e3028956a | |
|
|
0642f8d832 | |
|
|
9d061aaaff | |
|
|
5989547f40 | |
|
|
667b78c2f9 |
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
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 **SITE** key
|
||||
2. The correct domain, with protocol, is setup.
|
||||
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
|
||||
-->
|
||||
|
||||
## Bug description
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
## To Reproduce
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. ...
|
||||
4. Profit (jk See error)
|
||||
|
||||
## Expected behavior
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
## Logs
|
||||
Please add as many logs as you feel necessary. Be mindful of your application and remove any sensitive data.
|
||||
|
||||
## Additional context
|
||||
Add any other context about the problem here.
|
||||
|
|
@ -51,3 +51,6 @@ fastlane/Preview.html
|
|||
fastlane/screenshots
|
||||
fastlane/README.md
|
||||
fastlane/test_output
|
||||
|
||||
## VS Code
|
||||
.vscode
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
author: Flávio Caetano (@fjcaetano)
|
||||
author_url: https://github.com/fjcaetano
|
||||
github_url: https://github.com/fjcaetano/ReCaptcha
|
||||
|
||||
readme: README.md
|
||||
|
||||
module: ReCaptcha
|
||||
xcodebuild_arguments:
|
||||
- -workspace
|
||||
- Example/ReCaptcha.xcworkspace
|
||||
- -scheme
|
||||
- ReCaptcha-Example
|
||||
- -configuration
|
||||
- Release
|
||||
|
|
@ -1 +0,0 @@
|
|||
4.0
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
osx_image: xcode9
|
||||
osx_image: xcode10
|
||||
language: objective-c
|
||||
|
||||
cache:
|
||||
|
|
|
|||
25
CHANGELOG.md
25
CHANGELOG.md
|
|
@ -1,3 +1,28 @@
|
|||
# 1.4.2
|
||||
|
||||
- Fix: Webview's resource loading detection (#56 #60)
|
||||
|
||||
# 1.4.1
|
||||
|
||||
- Fix RxSwift dependency version (#58)
|
||||
|
||||
# 1.4
|
||||
|
||||
- Feature: Support Swift 4.2
|
||||
- Feature: enable validation to be skipped for testing
|
||||
|
||||
# 1.3.1
|
||||
|
||||
- Fix: Removing leftover print
|
||||
- Fix: Removing Result dependency from Carthage
|
||||
|
||||
# 1.3
|
||||
|
||||
- Feature: Locale support (#39)
|
||||
|
||||
- Fix: Reset not flagging ReCaptha as ready-to-execute (#36)
|
||||
- Fix: Multiple configure calls after app being idle (#40)
|
||||
|
||||
# 1.2
|
||||
|
||||
- Feature: Resettable ReCaptchas. (#23)
|
||||
|
|
|
|||
3
Cartfile
3
Cartfile
|
|
@ -1,2 +1 @@
|
|||
github "antitypical/Result" ~> 3.0
|
||||
github "ReactiveX/RxSwift" ~> 4.0
|
||||
binary "https://raw.github.com/TouchInstinct/CarthageBinaries/master/RxSwift/RxSwift.json"
|
||||
|
|
@ -1,2 +1 @@
|
|||
github "ReactiveX/RxSwift" "4.1.2"
|
||||
github "antitypical/Result" "3.2.4"
|
||||
binary "https://raw.github.com/TouchInstinct/CarthageBinaries/master/RxSwift/RxSwift.json" "4.5.0"
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ inhibit_all_warnings!
|
|||
|
||||
target 'ReCaptcha_Example' do
|
||||
pod 'ReCaptcha/RxSwift', :path => '../'
|
||||
pod 'RxCocoa', '~> 4.0'
|
||||
pod 'SwiftLint', '~> 0.24'
|
||||
pod 'RxCocoa', '~> 4.3'
|
||||
pod 'SwiftLint', '~> 0.27'
|
||||
|
||||
target 'ReCaptcha_Tests' do
|
||||
inherit! :search_paths
|
||||
|
||||
pod 'AppSwizzle', :git => 'https://github.com/fjcaetano/AppSwizzle.git', :branch => 'swift4'
|
||||
pod 'AppSwizzle', '~> 1.3'
|
||||
pod 'RxBlocking', '~> 4.0'
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,43 +1,48 @@
|
|||
PODS:
|
||||
- AppSwizzle (1.2)
|
||||
- ReCaptcha/Core (1.1)
|
||||
- ReCaptcha/RxSwift (1.1):
|
||||
- AppSwizzle (1.3.1)
|
||||
- ReCaptcha/Core (1.4.1)
|
||||
- ReCaptcha/RxSwift (1.4.1):
|
||||
- ReCaptcha/Core
|
||||
- RxSwift (~> 4.3)
|
||||
- RxAtomic (4.4.0)
|
||||
- RxBlocking (4.4.0):
|
||||
- RxAtomic (~> 4.4)
|
||||
- RxSwift (~> 4.0)
|
||||
- RxBlocking (4.1.1):
|
||||
- RxCocoa (4.4.0):
|
||||
- RxSwift (~> 4.0)
|
||||
- RxCocoa (4.1.1):
|
||||
- RxSwift (~> 4.0)
|
||||
- RxSwift (4.1.1)
|
||||
- SwiftLint (0.24.1)
|
||||
- RxSwift (4.4.0):
|
||||
- RxAtomic (~> 4.4)
|
||||
- SwiftLint (0.27.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- AppSwizzle (from `https://github.com/fjcaetano/AppSwizzle.git`, branch `swift4`)
|
||||
- AppSwizzle (~> 1.3)
|
||||
- ReCaptcha/RxSwift (from `../`)
|
||||
- RxBlocking (~> 4.0)
|
||||
- RxCocoa (~> 4.0)
|
||||
- SwiftLint (~> 0.24)
|
||||
- RxCocoa (~> 4.3)
|
||||
- SwiftLint (~> 0.27)
|
||||
|
||||
SPEC REPOS:
|
||||
https://github.com/cocoapods/specs.git:
|
||||
- AppSwizzle
|
||||
- RxAtomic
|
||||
- RxBlocking
|
||||
- RxCocoa
|
||||
- RxSwift
|
||||
- SwiftLint
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
AppSwizzle:
|
||||
:branch: swift4
|
||||
:git: https://github.com/fjcaetano/AppSwizzle.git
|
||||
ReCaptcha:
|
||||
:path: ../
|
||||
|
||||
CHECKOUT OPTIONS:
|
||||
AppSwizzle:
|
||||
:commit: c432a73e43779d20ef8e8a589aabd9622b7b6a5d
|
||||
:git: https://github.com/fjcaetano/AppSwizzle.git
|
||||
:path: "../"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
AppSwizzle: bbd3782652fc426ce59c045a92ec61d36f261984
|
||||
ReCaptcha: b94a673f3827e9b9cf7e77db0395694549241247
|
||||
RxBlocking: 22e7d7b86a1c0c42164a30d64f56f97128b4ffab
|
||||
RxCocoa: fd0862fd2df95fa55562ad28ffd2522c25eb4a85
|
||||
RxSwift: c6e3b1c7b325c7d121cd4327e9d98b7ed746b570
|
||||
SwiftLint: 2e4b89feed5909c42c3735bbd6745f4345c4b772
|
||||
AppSwizzle: db36e436f56110d93e5ae0147683435df593cabc
|
||||
ReCaptcha: 520a707a38dfbb1e5de812aa3c041df60bd31827
|
||||
RxAtomic: eacf60db868c96bfd63320e28619fe29c179656f
|
||||
RxBlocking: 138ad53217434444d6eeeb4fb406a45431d92e31
|
||||
RxCocoa: df63ebf7b9a70d6b4eeea407ed5dd4efc8979749
|
||||
RxSwift: 5976ecd04fc2fefd648827c23de5e11157faa973
|
||||
SwiftLint: 3207c1faa2240bf8973b191820a116113cd11073
|
||||
|
||||
PODFILE CHECKSUM: 3ae211ad7c1b9c86749b013587aa4b63661f345e
|
||||
PODFILE CHECKSUM: 63f50401dee6a885ae0eea927fb966553c6d0f33
|
||||
|
||||
COCOAPODS: 1.4.0
|
||||
COCOAPODS: 1.5.3
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
BD850CB2DF4C9C94FC51226C /* Pods_ReCaptcha_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62BEEA62161F672468CCFD64 /* Pods_ReCaptcha_Example.framework */; };
|
||||
D091B6E053FD250B4757E34C /* Pods_ReCaptcha_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9417A28DC340FF0BC1627B3F /* Pods_ReCaptcha_Tests.framework */; };
|
||||
F206BAD51F8D3FEB00A25807 /* Cartfile in Resources */ = {isa = PBXBuildFile; fileRef = F206BAD41F8D3FEB00A25807 /* Cartfile */; };
|
||||
F211C22220F7E0B100709B26 /* ReCaptcha_Endpoint__Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F211C22120F7E0B100709B26 /* ReCaptcha_Endpoint__Tests.swift */; };
|
||||
F231B3971FEC325A00F82943 /* DispatchQueue__Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F231B3961FEC325A00F82943 /* DispatchQueue__Tests.swift */; };
|
||||
F231B39F1FED4A8C00F82943 /* ReCaptchaDecoder+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F231B39E1FED4A8C00F82943 /* ReCaptchaDecoder+Helper.swift */; };
|
||||
F288E9451F9537760018688D /* ReCaptchaError+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F288E9441F9537760018688D /* ReCaptchaError+Equatable.swift */; };
|
||||
|
|
@ -68,6 +69,7 @@
|
|||
C2A0BDD35B5E219129E9BC65 /* Pods-ReCaptcha_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReCaptcha_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-ReCaptcha_Example/Pods-ReCaptcha_Example.release.xcconfig"; sourceTree = "<group>"; };
|
||||
C8537003ECC47117AF54DCA9 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
|
||||
F206BAD41F8D3FEB00A25807 /* Cartfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Cartfile; path = ../Cartfile; sourceTree = "<group>"; };
|
||||
F211C22120F7E0B100709B26 /* ReCaptcha_Endpoint__Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReCaptcha_Endpoint__Tests.swift; sourceTree = "<group>"; };
|
||||
F21901D91F98D62F00D8E2C9 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = CHANGELOG.md; path = ../CHANGELOG.md; sourceTree = "<group>"; };
|
||||
F231B3961FEC325A00F82943 /* DispatchQueue__Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DispatchQueue__Tests.swift; sourceTree = "<group>"; };
|
||||
F231B39E1FED4A8C00F82943 /* ReCaptchaDecoder+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReCaptchaDecoder+Helper.swift"; sourceTree = "<group>"; };
|
||||
|
|
@ -214,6 +216,7 @@
|
|||
F2E2685D1F7AEE3400CD876D /* ReCaptcha__Tests.swift */,
|
||||
F231B3961FEC325A00F82943 /* DispatchQueue__Tests.swift */,
|
||||
F2AE8611204F3430002E28D7 /* ReCaptchaResult__Tests.swift */,
|
||||
F211C22120F7E0B100709B26 /* ReCaptcha_Endpoint__Tests.swift */,
|
||||
);
|
||||
path = Core;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -259,7 +262,6 @@
|
|||
607FACCD1AFB9204008FA782 /* Frameworks */,
|
||||
607FACCE1AFB9204008FA782 /* Resources */,
|
||||
8F03FFB3F5C55E873C23C682 /* [CP] Embed Pods Frameworks */,
|
||||
ED1C0E07490C9C4B4A401061 /* [CP] Copy Pods Resources */,
|
||||
F231B3981FEC3B7F00F82943 /* SwiftLint */,
|
||||
);
|
||||
buildRules = (
|
||||
|
|
@ -279,8 +281,6 @@
|
|||
F28FAC98200E425600E14987 /* Sources */,
|
||||
F28FAC99200E425600E14987 /* Frameworks */,
|
||||
F28FAC9A200E425600E14987 /* Resources */,
|
||||
BF2909E4520B29BFFBC94AD6 /* [CP] Embed Pods Frameworks */,
|
||||
DCB1D99E77E589CD76CD3186 /* [CP] Copy Pods Resources */,
|
||||
F28FACA6200E447600E14987 /* ShellScript */,
|
||||
);
|
||||
buildRules = (
|
||||
|
|
@ -302,7 +302,6 @@
|
|||
F2ECCF731E9FC47B0097B199 /* Frameworks */,
|
||||
F2ECCF741E9FC47B0097B199 /* Resources */,
|
||||
77003100630E7783A936C451 /* [CP] Embed Pods Frameworks */,
|
||||
44209D7CDDDE3A11075B8104 /* [CP] Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
|
|
@ -391,21 +390,6 @@
|
|||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
44209D7CDDDE3A11075B8104 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ReCaptcha_Tests/Pods-ReCaptcha_Tests-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
51299F67A8756E2B3EAE411A /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
|
@ -431,12 +415,14 @@
|
|||
);
|
||||
inputPaths = (
|
||||
"${SRCROOT}/Pods/Target Support Files/Pods-ReCaptcha_Tests/Pods-ReCaptcha_Tests-frameworks.sh",
|
||||
"${BUILT_PRODUCTS_DIR}/RxAtomic/RxAtomic.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/AppSwizzle/AppSwizzle.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/RxBlocking/RxBlocking.framework",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxAtomic.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AppSwizzle.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxBlocking.framework",
|
||||
|
|
@ -454,12 +440,14 @@
|
|||
inputPaths = (
|
||||
"${SRCROOT}/Pods/Target Support Files/Pods-ReCaptcha_Example/Pods-ReCaptcha_Example-frameworks.sh",
|
||||
"${BUILT_PRODUCTS_DIR}/ReCaptcha/ReCaptcha.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/RxAtomic/RxAtomic.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReCaptcha.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxAtomic.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework",
|
||||
);
|
||||
|
|
@ -486,36 +474,6 @@
|
|||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
BF2909E4520B29BFFBC94AD6 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ReCaptcha_UITests/Pods-ReCaptcha_UITests-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
DCB1D99E77E589CD76CD3186 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ReCaptcha_UITests/Pods-ReCaptcha_UITests-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
DDB47454887253730AB35230 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
|
@ -534,21 +492,6 @@
|
|||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
ED1C0E07490C9C4B4A401061 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ReCaptcha_Example/Pods-ReCaptcha_Example-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
F231B3981FEC3B7F00F82943 /* SwiftLint */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
|
@ -561,7 +504,7 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/SwiftLint/swiftlint\" --path \"${PROJECT_DIR}/..\"";
|
||||
shellScript = "\"${PODS_ROOT}/SwiftLint/swiftlint\" --path \"${PROJECT_DIR}/..\"\n";
|
||||
};
|
||||
F28FACA6200E447600E14987 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
|
|
@ -606,6 +549,7 @@
|
|||
F2ECCF981EA011370097B199 /* Result+Helpers.swift in Sources */,
|
||||
F231B3971FEC325A00F82943 /* DispatchQueue__Tests.swift in Sources */,
|
||||
F231B39F1FED4A8C00F82943 /* ReCaptchaDecoder+Helper.swift in Sources */,
|
||||
F211C22220F7E0B100709B26 /* ReCaptcha_Endpoint__Tests.swift in Sources */,
|
||||
F2E2685E1F7AEE3400CD876D /* ReCaptcha__Tests.swift in Sources */,
|
||||
F2ECCF931EA009360097B199 /* ReCaptcha+Rx__Tests.swift in Sources */,
|
||||
F288E9451F9537760018688D /* ReCaptchaError+Equatable.swift in Sources */,
|
||||
|
|
@ -759,7 +703,7 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-Example";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 4.2;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
|
|
@ -775,7 +719,7 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-Example";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 4.2;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
|
@ -798,7 +742,7 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = "ReCaptcha.ReCaptcha-UITests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 4.2;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_TARGET_NAME = ReCaptcha_Example;
|
||||
};
|
||||
|
|
@ -821,7 +765,7 @@
|
|||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "ReCaptcha.ReCaptcha-UITests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 4.2;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_TARGET_NAME = ReCaptcha_Example;
|
||||
};
|
||||
|
|
@ -844,7 +788,7 @@
|
|||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG UNIT_TESTS";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 4.2;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ReCaptcha_Example.app/ReCaptcha_Example";
|
||||
};
|
||||
name = Debug;
|
||||
|
|
@ -864,7 +808,7 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-Tests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 4.2;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ReCaptcha_Example.app/ReCaptcha_Example";
|
||||
};
|
||||
name = Release;
|
||||
|
|
|
|||
|
|
@ -40,9 +40,8 @@
|
|||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
codeCoverageEnabled = "YES"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
|
|
@ -78,10 +77,9 @@
|
|||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Release"
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -16,7 +16,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
|
||||
func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="vXZ-lx-hvc">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14109" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="vXZ-lx-hvc">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="RDW-bD-rSo">
|
||||
<rect key="frame" x="50" y="498" width="275" height="50"/>
|
||||
<rect key="frame" x="50" y="450" width="275" height="50"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="50" id="Bt8-Ou-ht2"/>
|
||||
</constraints>
|
||||
|
|
@ -35,20 +35,20 @@
|
|||
<rect key="frame" x="177" y="323" width="20" height="20"/>
|
||||
</activityIndicatorView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="249" text="" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="o6f-cL-1PF">
|
||||
<rect key="frame" x="20" y="199" width="335" height="269"/>
|
||||
<rect key="frame" x="20" y="247" width="335" height="173"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="resultLabel"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="X8E-G9-9IV">
|
||||
<rect key="frame" x="117" y="568" width="141" height="29"/>
|
||||
<rect key="frame" x="71" y="568" width="233" height="29"/>
|
||||
<segments>
|
||||
<segment title="Default"/>
|
||||
<segment title="Default Endpoint"/>
|
||||
<segment title="Alternate"/>
|
||||
</segments>
|
||||
<connections>
|
||||
<action selector="didPressSegmentedControl:" destination="vXZ-lx-hvc" eventType="valueChanged" id="e5Z-W5-Q1d"/>
|
||||
<action selector="didPressEndpointSegmentedControl:" destination="vXZ-lx-hvc" eventType="valueChanged" id="Sdm-dO-doL"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Uyt-0M-CR7">
|
||||
|
|
@ -76,27 +76,40 @@
|
|||
<constraint firstItem="mGh-Ox-cFf" firstAttribute="leading" secondItem="awK-8H-OCQ" secondAttribute="trailing" constant="10" id="wzo-g0-VEj"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<segmentedControl clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="kq2-ci-l1u">
|
||||
<rect key="frame" x="114" y="520" width="147" height="29"/>
|
||||
<segments>
|
||||
<segment title="Nil Locale"/>
|
||||
<segment title="Chinese"/>
|
||||
</segments>
|
||||
<connections>
|
||||
<action selector="didPressLocaleSegmentedControl:" destination="vXZ-lx-hvc" eventType="valueChanged" id="nSA-f3-8eS"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="o6f-cL-1PF" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leading" constant="20" id="0dV-t0-W0Y"/>
|
||||
<constraint firstAttribute="trailing" secondItem="RDW-bD-rSo" secondAttribute="trailing" constant="50" id="1Lj-Ox-Kl4"/>
|
||||
<constraint firstItem="X8E-G9-9IV" firstAttribute="top" secondItem="kq2-ci-l1u" secondAttribute="bottom" constant="20" id="8ye-XO-bpK"/>
|
||||
<constraint firstItem="X8E-G9-9IV" firstAttribute="centerX" secondItem="kh9-bI-dsS" secondAttribute="centerX" id="92w-cw-lR2"/>
|
||||
<constraint firstItem="2fi-mo-0CV" firstAttribute="top" secondItem="Uyt-0M-CR7" secondAttribute="bottom" constant="20" id="Bfs-p5-IBM"/>
|
||||
<constraint firstItem="o6f-cL-1PF" firstAttribute="centerY" secondItem="kh9-bI-dsS" secondAttribute="centerY" priority="750" id="NMD-ir-PXe"/>
|
||||
<constraint firstItem="X8E-G9-9IV" firstAttribute="top" secondItem="RDW-bD-rSo" secondAttribute="bottom" constant="20" id="Qbw-Jp-xGV"/>
|
||||
<constraint firstItem="RDW-bD-rSo" firstAttribute="top" secondItem="o6f-cL-1PF" secondAttribute="bottom" constant="30" id="TZe-z9-MZS"/>
|
||||
<constraint firstItem="jHc-GP-v1Z" firstAttribute="centerY" secondItem="kh9-bI-dsS" secondAttribute="centerY" id="VOe-WJ-FKo"/>
|
||||
<constraint firstItem="jHc-GP-v1Z" firstAttribute="centerX" secondItem="kh9-bI-dsS" secondAttribute="centerX" id="XkT-zr-eUO"/>
|
||||
<constraint firstItem="kq2-ci-l1u" firstAttribute="top" secondItem="RDW-bD-rSo" secondAttribute="bottom" constant="20" id="Y4H-6k-gDx"/>
|
||||
<constraint firstAttribute="trailing" secondItem="o6f-cL-1PF" secondAttribute="trailing" constant="20" id="c74-nm-rgi"/>
|
||||
<constraint firstAttribute="trailing" secondItem="RDW-bD-rSo" secondAttribute="trailing" constant="50" id="c7q-Rw-n0F"/>
|
||||
<constraint firstItem="RDW-bD-rSo" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leading" constant="50" id="iXO-hP-XZ7"/>
|
||||
<constraint firstItem="kq2-ci-l1u" firstAttribute="centerX" secondItem="kh9-bI-dsS" secondAttribute="centerX" id="ice-pp-mVe"/>
|
||||
<constraint firstItem="Uyt-0M-CR7" firstAttribute="top" secondItem="X8E-G9-9IV" secondAttribute="bottom" constant="20" id="nIE-QT-RpQ"/>
|
||||
<constraint firstItem="Uyt-0M-CR7" firstAttribute="centerX" secondItem="kh9-bI-dsS" secondAttribute="centerX" id="qfG-68-ySO"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="endpointSegmentedControl" destination="X8E-G9-9IV" id="jAb-fU-Yu5"/>
|
||||
<outlet property="label" destination="o6f-cL-1PF" id="KQV-3X-RKr"/>
|
||||
<outlet property="segmentedControl" destination="X8E-G9-9IV" id="sS0-3k-Alu"/>
|
||||
<outlet property="localeSegmentedControl" destination="kq2-ci-l1u" id="gL7-du-K6l"/>
|
||||
<outlet property="spinner" destination="jHc-GP-v1Z" id="gRn-JW-FIK"/>
|
||||
<outlet property="visibleChallengeSwitch" destination="mGh-Ox-cFf" id="R13-nD-EXL"/>
|
||||
</connections>
|
||||
|
|
|
|||
|
|
@ -39,6 +39,11 @@
|
|||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"size" : "1024x1024",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
|
|
|||
|
|
@ -21,23 +21,40 @@ class ViewController: UIViewController {
|
|||
private var recaptcha: ReCaptcha!
|
||||
private var disposeBag = DisposeBag()
|
||||
|
||||
private var locale: Locale?
|
||||
private var endpoint = ReCaptcha.Endpoint.default
|
||||
|
||||
@IBOutlet private weak var label: UILabel!
|
||||
@IBOutlet private weak var spinner: UIActivityIndicatorView!
|
||||
@IBOutlet private weak var segmentedControl: UISegmentedControl!
|
||||
@IBOutlet private weak var localeSegmentedControl: UISegmentedControl!
|
||||
@IBOutlet private weak var endpointSegmentedControl: UISegmentedControl!
|
||||
@IBOutlet private weak var visibleChallengeSwitch: UISwitch!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
setupReCaptcha(endpoint: .default)
|
||||
setupReCaptcha()
|
||||
}
|
||||
|
||||
@IBAction func didPressSegmentedControl(_ sender: UISegmentedControl) {
|
||||
@IBAction func didPressEndpointSegmentedControl(_ sender: UISegmentedControl) {
|
||||
label.text = ""
|
||||
switch sender.selectedSegmentIndex {
|
||||
case 0: setupReCaptcha(endpoint: .default)
|
||||
case 1: setupReCaptcha(endpoint: .alternate)
|
||||
case 0: endpoint = .default
|
||||
case 1: endpoint = .alternate
|
||||
default: assertionFailure("invalid index")
|
||||
}
|
||||
|
||||
setupReCaptcha()
|
||||
}
|
||||
|
||||
@IBAction func didPressLocaleSegmentedControl(_ sender: UISegmentedControl) {
|
||||
label.text = ""
|
||||
switch sender.selectedSegmentIndex {
|
||||
case 0: locale = nil
|
||||
case 1: locale = Locale(identifier: "zh-CN")
|
||||
default: assertionFailure("invalid index")
|
||||
}
|
||||
|
||||
setupReCaptcha()
|
||||
}
|
||||
|
||||
@IBAction private func didPressButton(button: UIButton) {
|
||||
|
|
@ -66,7 +83,7 @@ class ViewController: UIViewController {
|
|||
.disposed(by: disposeBag)
|
||||
|
||||
isEnabled
|
||||
.bind(to: segmentedControl.rx.isEnabled)
|
||||
.bind(to: endpointSegmentedControl.rx.isEnabled)
|
||||
.disposed(by: disposeBag)
|
||||
|
||||
validate
|
||||
|
|
@ -89,9 +106,9 @@ class ViewController: UIViewController {
|
|||
.disposed(by: disposeBag)
|
||||
}
|
||||
|
||||
private func setupReCaptcha(endpoint: ReCaptcha.Endpoint) {
|
||||
private func setupReCaptcha() {
|
||||
// swiftlint:disable:next force_try
|
||||
recaptcha = try! ReCaptcha(endpoint: endpoint)
|
||||
recaptcha = try! ReCaptcha(endpoint: endpoint, locale: locale)
|
||||
|
||||
recaptcha.configureWebView { [weak self] webview in
|
||||
webview.frame = self?.view.bounds ?? CGRect.zero
|
||||
|
|
|
|||
|
|
@ -4,3 +4,6 @@ disabled_rules:
|
|||
- force_unwrapping
|
||||
- explicit_top_level_acl
|
||||
- function_body_length
|
||||
- identifier_name
|
||||
- file_length
|
||||
- type_body_length
|
||||
|
|
|
|||
|
|
@ -151,4 +151,47 @@ class DispatchQueue__Tests: XCTestCase {
|
|||
|
||||
waitForExpectations(timeout: 5)
|
||||
}
|
||||
|
||||
// MARK: Once
|
||||
|
||||
func test__Once__Single_Dispatch() {
|
||||
let token = 3
|
||||
var dispatchCount = 0
|
||||
|
||||
// Does dispatch the given action
|
||||
DispatchQueue.once(token: token) {
|
||||
dispatchCount = 1
|
||||
}
|
||||
|
||||
XCTAssertEqual(dispatchCount, 1)
|
||||
|
||||
// Does not dispatch again for the same token
|
||||
DispatchQueue.once(token: token) {
|
||||
dispatchCount = 2
|
||||
}
|
||||
|
||||
XCTAssertEqual(dispatchCount, 1)
|
||||
}
|
||||
|
||||
func test__Once__Multiple_Dispatches() {
|
||||
let token1 = 4
|
||||
var didDispatch1 = false
|
||||
|
||||
// Does dispatch the given action
|
||||
DispatchQueue.once(token: token1) {
|
||||
didDispatch1 = true
|
||||
}
|
||||
|
||||
XCTAssertTrue(didDispatch1)
|
||||
|
||||
// Dispatch for a different token
|
||||
let token2 = 6
|
||||
var didDispatch2 = false
|
||||
|
||||
DispatchQueue.once(token: token2) {
|
||||
didDispatch2 = true
|
||||
}
|
||||
|
||||
XCTAssertTrue(didDispatch2)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -210,6 +210,34 @@ class ReCaptchaWebViewManager__Tests: XCTestCase {
|
|||
XCTAssertEqual(count, 1)
|
||||
}
|
||||
|
||||
func test__Configure_Web_View__Called_Again_With_Reset() {
|
||||
let exp0 = expectation(description: "configure webview 0")
|
||||
|
||||
let manager = ReCaptchaWebViewManager(messageBody: "{action: \"showReCaptcha\"}")
|
||||
manager.validate(on: presenterView) { _ in
|
||||
XCTFail("should not call completion")
|
||||
}
|
||||
|
||||
// Configure Webview
|
||||
manager.configureWebView { _ in
|
||||
manager.webView.evaluateJavaScript("execute();") { XCTAssertNil($1) }
|
||||
exp0.fulfill()
|
||||
}
|
||||
|
||||
waitForExpectations(timeout: 10)
|
||||
|
||||
// Reset and ensure it calls again
|
||||
let exp1 = expectation(description: "configure webview 1")
|
||||
|
||||
manager.configureWebView { _ in
|
||||
manager.webView.evaluateJavaScript("execute();") { XCTAssertNil($1) }
|
||||
exp1.fulfill()
|
||||
}
|
||||
|
||||
manager.reset()
|
||||
waitForExpectations(timeout: 10)
|
||||
}
|
||||
|
||||
// MARK: Stop
|
||||
|
||||
func test__Stop() {
|
||||
|
|
@ -259,7 +287,7 @@ class ReCaptchaWebViewManager__Tests: XCTestCase {
|
|||
|
||||
func test__Endpoint_Setup() {
|
||||
let exp = expectation(description: "setup endpoint")
|
||||
let endpoint = ReCaptcha.Endpoint.alternate.url
|
||||
let endpoint = ReCaptcha.Endpoint.alternate.getURL(locale: nil)
|
||||
var result: ReCaptchaResult?
|
||||
|
||||
let manager = ReCaptchaWebViewManager(messageBody: "{token: endpoint}", endpoint: endpoint)
|
||||
|
|
@ -338,6 +366,22 @@ class ReCaptchaWebViewManager__Tests: XCTestCase {
|
|||
XCTAssertEqual(result?.token, apiKey)
|
||||
}
|
||||
|
||||
func test__Validate__Should_Skip_For_Tests() {
|
||||
let exp = expectation(description: "did skip validation")
|
||||
|
||||
let manager = ReCaptchaWebViewManager()
|
||||
manager.shouldSkipForTests = true
|
||||
|
||||
manager.completion = { result in
|
||||
XCTAssertEqual(result.token, "")
|
||||
exp.fulfill()
|
||||
}
|
||||
|
||||
manager.validate(on: presenterView)
|
||||
|
||||
waitForExpectations(timeout: 1)
|
||||
}
|
||||
|
||||
// MARK: Force Challenge Visible
|
||||
|
||||
func test__Force_Visible_Challenge() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// ReCaptcha_Endpoint__.swift
|
||||
// ReCaptcha
|
||||
//
|
||||
// Created by Flávio Caetano on 12/07/18.
|
||||
// Copyright © 2018 ReCaptcha. All rights reserved.
|
||||
//
|
||||
|
||||
@testable import ReCaptcha
|
||||
import XCTest
|
||||
|
||||
class ReCaptcha_Endpoint__Tests: XCTestCase {
|
||||
|
||||
private let endpoint = ReCaptcha.Endpoint.default
|
||||
private let endpointURL = "https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit"
|
||||
|
||||
// MARK: - Locale
|
||||
|
||||
func test__Locale__Nil() {
|
||||
XCTAssertEqual(endpoint.getURL(locale: nil), endpointURL)
|
||||
}
|
||||
|
||||
func test__Locale__Valid() {
|
||||
let locale = Locale(identifier: "pt-BR")
|
||||
XCTAssertEqual(endpoint.getURL(locale: locale), "\(endpointURL)&hl=pt-BR")
|
||||
}
|
||||
}
|
||||
|
|
@ -107,7 +107,7 @@ class ReCaptcha_Rx__Tests: XCTestCase {
|
|||
let exp = expectation(description: "stop loading")
|
||||
|
||||
// Stop
|
||||
let recaptcha = ReCaptcha(manager: ReCaptchaWebViewManager(messageBody: "{action: \"showReCaptcha\"}"))
|
||||
let recaptcha = ReCaptcha(manager: ReCaptchaWebViewManager(messageBody: "{log: \"foo\"}"))
|
||||
recaptcha.configureWebView { _ in
|
||||
XCTFail("should not ask to configure the webview")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,10 +19,6 @@
|
|||
}
|
||||
};
|
||||
|
||||
window.onload = function() {
|
||||
post({action: "didLoad"});
|
||||
};
|
||||
|
||||
var reset = function() {
|
||||
shouldFail = false;
|
||||
post({action: "didLoad"});
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class ReCaptcha_UITests: XCTestCase {
|
|||
|
||||
func test__Validate__Default_Endpoint() {
|
||||
let app = XCUIApplication()
|
||||
app.segmentedControls.buttons["Default"].tap()
|
||||
app.segmentedControls.buttons["Default Endpoint"].tap()
|
||||
app.switches["Switch"].tap()
|
||||
app.buttons["Validate"].tap()
|
||||
|
||||
|
|
|
|||
4
Gemfile
4
Gemfile
|
|
@ -1,4 +1,4 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gem 'fastlane', '~> 2.75'
|
||||
gem 'cocoapods', '~> 1.4'
|
||||
gem 'fastlane', '~> 2.105.2'
|
||||
gem 'cocoapods', '~> 1.5.3'
|
||||
|
|
|
|||
127
Gemfile.lock
127
Gemfile.lock
|
|
@ -1,7 +1,7 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (2.3.6)
|
||||
CFPropertyList (3.0.0)
|
||||
activesupport (4.2.10)
|
||||
i18n (~> 0.7)
|
||||
minitest (~> 5.1)
|
||||
|
|
@ -9,14 +9,15 @@ GEM
|
|||
tzinfo (~> 1.1)
|
||||
addressable (2.5.2)
|
||||
public_suffix (>= 2.0.2, < 4.0)
|
||||
atomos (0.1.3)
|
||||
babosa (1.0.2)
|
||||
claide (1.0.2)
|
||||
cocoapods (1.4.0)
|
||||
cocoapods (1.5.3)
|
||||
activesupport (>= 4.0.2, < 5)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
cocoapods-core (= 1.4.0)
|
||||
cocoapods-core (= 1.5.3)
|
||||
cocoapods-deintegrate (>= 1.0.2, < 2.0)
|
||||
cocoapods-downloader (>= 1.1.3, < 2.0)
|
||||
cocoapods-downloader (>= 1.2.0, < 2.0)
|
||||
cocoapods-plugins (>= 1.0.0, < 2.0)
|
||||
cocoapods-search (>= 1.0.0, < 2.0)
|
||||
cocoapods-stats (>= 1.0.0, < 2.0)
|
||||
|
|
@ -26,59 +27,61 @@ GEM
|
|||
escape (~> 0.0.4)
|
||||
fourflusher (~> 2.0.1)
|
||||
gh_inspector (~> 1.0)
|
||||
molinillo (~> 0.6.4)
|
||||
molinillo (~> 0.6.5)
|
||||
nap (~> 1.0)
|
||||
ruby-macho (~> 1.1)
|
||||
xcodeproj (>= 1.5.4, < 2.0)
|
||||
cocoapods-core (1.4.0)
|
||||
xcodeproj (>= 1.5.7, < 2.0)
|
||||
cocoapods-core (1.5.3)
|
||||
activesupport (>= 4.0.2, < 6)
|
||||
fuzzy_match (~> 2.0.4)
|
||||
nap (~> 1.0)
|
||||
cocoapods-deintegrate (1.0.2)
|
||||
cocoapods-downloader (1.1.3)
|
||||
cocoapods-downloader (1.2.1)
|
||||
cocoapods-plugins (1.0.0)
|
||||
nap
|
||||
cocoapods-search (1.0.0)
|
||||
cocoapods-stats (1.0.0)
|
||||
cocoapods-trunk (1.3.0)
|
||||
cocoapods-trunk (1.3.1)
|
||||
nap (>= 0.8, < 2.0)
|
||||
netrc (~> 0.11)
|
||||
cocoapods-try (1.1.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander-fastlane (4.4.5)
|
||||
commander-fastlane (4.4.6)
|
||||
highline (~> 1.7.2)
|
||||
concurrent-ruby (1.0.5)
|
||||
declarative (0.0.10)
|
||||
declarative-option (0.1.0)
|
||||
domain_name (0.5.20170404)
|
||||
domain_name (0.5.20180417)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.2.1)
|
||||
dotenv (2.5.0)
|
||||
emoji_regex (0.1.1)
|
||||
escape (0.0.4)
|
||||
excon (0.60.0)
|
||||
faraday (0.13.1)
|
||||
excon (0.62.0)
|
||||
faraday (0.15.3)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday-cookie_jar (0.0.6)
|
||||
faraday (>= 0.7.4)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday_middleware (0.12.2)
|
||||
faraday (>= 0.7.4, < 1.0)
|
||||
fastimage (2.1.1)
|
||||
fastlane (2.75.1)
|
||||
CFPropertyList (>= 2.3, < 3.0.0)
|
||||
fastimage (2.1.4)
|
||||
fastlane (2.105.2)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.3, < 3.0.0)
|
||||
babosa (>= 1.0.2, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 2.0.0)
|
||||
colored
|
||||
commander-fastlane (>= 4.4.5, < 5.0.0)
|
||||
commander-fastlane (>= 4.4.6, < 5.0.0)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (~> 0.1)
|
||||
excon (>= 0.45.0, < 1.0.0)
|
||||
faraday (~> 0.9)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 0.9)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
gh_inspector (>= 1.0.1, < 2.0.0)
|
||||
google-api-client (>= 0.13.1, < 0.14.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-api-client (>= 0.21.2, < 0.24.0)
|
||||
highline (>= 1.7.2, < 2.0.0)
|
||||
json (< 3.0.0)
|
||||
mini_magick (~> 4.5.1)
|
||||
|
|
@ -87,100 +90,102 @@ GEM
|
|||
multipart-post (~> 2.0.0)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
public_suffix (~> 2.0.0)
|
||||
rubyzip (>= 1.1.0, < 2.0.0)
|
||||
rubyzip (>= 1.2.2, < 2.0.0)
|
||||
security (= 0.1.3)
|
||||
slack-notifier (>= 1.3, < 2.0.0)
|
||||
simctl (~> 1.6.3)
|
||||
slack-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-notifier (>= 1.6.2, < 2.0.0)
|
||||
terminal-table (>= 1.4.5, < 2.0.0)
|
||||
tty-screen (>= 0.6.3, < 1.0.0)
|
||||
tty-spinner (>= 0.7.0, < 1.0.0)
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.5.2, < 2.0.0)
|
||||
xcpretty (>= 0.2.4, < 1.0.0)
|
||||
xcodeproj (>= 1.6.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
fourflusher (2.0.1)
|
||||
fuzzy_match (2.0.4)
|
||||
gh_inspector (1.0.3)
|
||||
google-api-client (0.13.6)
|
||||
gh_inspector (1.1.3)
|
||||
google-api-client (0.23.9)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (~> 0.5)
|
||||
googleauth (>= 0.5, < 0.7.0)
|
||||
httpclient (>= 2.8.1, < 3.0)
|
||||
mime-types (~> 3.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.0)
|
||||
googleauth (0.6.2)
|
||||
signet (~> 0.9)
|
||||
googleauth (0.6.6)
|
||||
faraday (~> 0.12)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
logging (~> 2.0)
|
||||
memoist (~> 0.12)
|
||||
multi_json (~> 1.11)
|
||||
os (~> 0.9)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (~> 0.7)
|
||||
highline (1.7.10)
|
||||
http-cookie (1.0.3)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
i18n (0.9.1)
|
||||
i18n (0.9.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
json (2.1.0)
|
||||
jwt (2.1.0)
|
||||
little-plugger (1.1.4)
|
||||
logging (2.2.2)
|
||||
little-plugger (~> 1.1)
|
||||
multi_json (~> 1.10)
|
||||
memoist (0.16.0)
|
||||
mime-types (3.1)
|
||||
mime-types (3.2.2)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2016.0521)
|
||||
mime-types-data (3.2018.0812)
|
||||
mini_magick (4.5.1)
|
||||
minitest (5.11.1)
|
||||
molinillo (0.6.4)
|
||||
multi_json (1.13.0)
|
||||
minitest (5.11.3)
|
||||
molinillo (0.6.6)
|
||||
multi_json (1.13.1)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.0.0)
|
||||
nanaimo (0.2.3)
|
||||
nanaimo (0.2.6)
|
||||
nap (1.1.0)
|
||||
naturally (2.2.0)
|
||||
netrc (0.11.0)
|
||||
os (0.9.6)
|
||||
os (1.0.0)
|
||||
plist (3.4.0)
|
||||
public_suffix (2.0.5)
|
||||
representable (3.0.4)
|
||||
declarative (< 0.1.0)
|
||||
declarative-option (< 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.1)
|
||||
retriable (3.1.2)
|
||||
rouge (2.0.7)
|
||||
ruby-macho (1.1.0)
|
||||
rubyzip (1.2.1)
|
||||
ruby-macho (1.2.0)
|
||||
rubyzip (1.2.2)
|
||||
security (0.1.3)
|
||||
signet (0.8.1)
|
||||
signet (0.10.0)
|
||||
addressable (~> 2.3)
|
||||
faraday (~> 0.9)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
slack-notifier (1.5.1)
|
||||
simctl (1.6.5)
|
||||
CFPropertyList
|
||||
naturally
|
||||
slack-notifier (2.3.2)
|
||||
terminal-notifier (1.8.0)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
thread_safe (0.3.6)
|
||||
tty-cursor (0.5.0)
|
||||
tty-screen (0.6.4)
|
||||
tty-spinner (0.7.0)
|
||||
tty-cursor (0.6.0)
|
||||
tty-screen (0.6.5)
|
||||
tty-spinner (0.8.0)
|
||||
tty-cursor (>= 0.5.0)
|
||||
tzinfo (1.2.4)
|
||||
tzinfo (1.2.5)
|
||||
thread_safe (~> 0.1)
|
||||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.4)
|
||||
unicode-display_width (1.3.0)
|
||||
unf_ext (0.0.7.5)
|
||||
unicode-display_width (1.4.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.5.4)
|
||||
CFPropertyList (~> 2.3.3)
|
||||
xcodeproj (1.6.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.2.3)
|
||||
xcpretty (0.2.8)
|
||||
nanaimo (~> 0.2.6)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
xcpretty-travis-formatter (1.0.0)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
|
@ -189,8 +194,8 @@ PLATFORMS
|
|||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
cocoapods (~> 1.4)
|
||||
fastlane (~> 2.75)
|
||||
cocoapods (~> 1.5.3)
|
||||
fastlane (~> 2.105.2)
|
||||
|
||||
BUNDLED WITH
|
||||
1.16.0
|
||||
1.16.1
|
||||
|
|
|
|||
22
README.md
22
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()
|
||||
|
|
@ -85,6 +92,13 @@ public enum Endpoint {
|
|||
let recaptcha = try? ReCaptcha(endpoint: .alternate) // Defaults to `default` when unset
|
||||
```
|
||||
|
||||
## Help Wanted
|
||||
|
||||
Do you love ReCaptcha and work actively on apps that use it? We'd love if you could help us keep improving it!
|
||||
Feel free to message us or to start contributing right away!
|
||||
|
||||
## [Full Documentation](http://fjcaetano.github.io/ReCaptcha)
|
||||
|
||||
## License
|
||||
|
||||
ReCaptcha is available under the MIT license. See the LICENSE file for more info.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
/* Begin PBXBuildFile section */
|
||||
F206BAB51F8D3DE900A25807 /* ReCaptcha-Carthage.h in Headers */ = {isa = PBXBuildFile; fileRef = F206BAB31F8D3DE900A25807 /* ReCaptcha-Carthage.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
F206BAD31F8D3E7600A25807 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F206BAD21F8D3E7600A25807 /* Result.framework */; };
|
||||
F206BB1D1F8D4DBC00A25807 /* ReCaptcha_RxSwift.h in Headers */ = {isa = PBXBuildFile; fileRef = F206BB1B1F8D4DBC00A25807 /* ReCaptcha_RxSwift.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
F206BB221F8D4DDF00A25807 /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F206BB121F8D4D1400A25807 /* RxSwift.framework */; };
|
||||
F231B39A1FEC51C800F82943 /* DispatchQueue+Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F231B3991FEC51C800F82943 /* DispatchQueue+Throttle.swift */; };
|
||||
|
|
@ -19,7 +18,6 @@
|
|||
F24EA1E51F968403001DEC17 /* ReCaptchaError.swift in Sources */ = {isa = PBXBuildFile; fileRef = F24EA1DB1F9683F5001DEC17 /* ReCaptchaError.swift */; };
|
||||
F24EA1E61F968403001DEC17 /* ReCaptcha.swift in Sources */ = {isa = PBXBuildFile; fileRef = F24EA1DE1F9683F5001DEC17 /* ReCaptcha.swift */; };
|
||||
F24EA1E71F968406001DEC17 /* recaptcha.html in Resources */ = {isa = PBXBuildFile; fileRef = F24EA1E01F9683F5001DEC17 /* recaptcha.html */; };
|
||||
F255FBEC1F8D5654002F5FA8 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F255FBEB1F8D5654002F5FA8 /* Result.framework */; };
|
||||
F2AE8614204F3B42002E28D7 /* ReCaptchaResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2AE8613204F3B41002E28D7 /* ReCaptchaResult.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
|
|
@ -38,7 +36,6 @@
|
|||
F206BAB31F8D3DE900A25807 /* ReCaptcha-Carthage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ReCaptcha-Carthage.h"; sourceTree = "<group>"; };
|
||||
F206BAB41F8D3DE900A25807 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
F206BACF1F8D3E2800A25807 /* Cartfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Cartfile; sourceTree = "<group>"; };
|
||||
F206BAD21F8D3E7600A25807 /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Result.framework; path = Carthage/Build/iOS/Result.framework; sourceTree = "<group>"; };
|
||||
F206BB121F8D4D1400A25807 /* RxSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxSwift.framework; path = Carthage/Build/iOS/RxSwift.framework; sourceTree = "<group>"; };
|
||||
F206BB191F8D4DBC00A25807 /* ReCaptcha_RxSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReCaptcha_RxSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
F206BB1B1F8D4DBC00A25807 /* ReCaptcha_RxSwift.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReCaptcha_RxSwift.h; sourceTree = "<group>"; };
|
||||
|
|
@ -51,7 +48,6 @@
|
|||
F24EA1DD1F9683F5001DEC17 /* ReCaptcha+Rx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReCaptcha+Rx.swift"; sourceTree = "<group>"; };
|
||||
F24EA1DE1F9683F5001DEC17 /* ReCaptcha.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReCaptcha.swift; sourceTree = "<group>"; };
|
||||
F24EA1E01F9683F5001DEC17 /* recaptcha.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = recaptcha.html; sourceTree = "<group>"; };
|
||||
F255FBEB1F8D5654002F5FA8 /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Result.framework; path = "../ReCaptcha Carthage Test/Carthage/Build/iOS/Result.framework"; sourceTree = "<group>"; };
|
||||
F2AE8613204F3B41002E28D7 /* ReCaptchaResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReCaptchaResult.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
|
|
@ -60,7 +56,6 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F206BAD31F8D3E7600A25807 /* Result.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -69,7 +64,6 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F206BB221F8D4DDF00A25807 /* RxSwift.framework in Frameworks */,
|
||||
F255FBEC1F8D5654002F5FA8 /* Result.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -110,9 +104,7 @@
|
|||
F206BAD11F8D3E7600A25807 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F255FBEB1F8D5654002F5FA8 /* Result.framework */,
|
||||
F206BB121F8D4D1400A25807 /* RxSwift.framework */,
|
||||
F206BAD21F8D3E7600A25807 /* Result.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -375,6 +367,7 @@
|
|||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.2;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
|
|
@ -427,6 +420,7 @@
|
|||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 4.2;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
|
|
@ -452,7 +446,7 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = com.flaviocaetano.ReCaptcha;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 3.0;
|
||||
SWIFT_VERSION = 4.2;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
|
|
@ -474,7 +468,7 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = com.flaviocaetano.ReCaptcha;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 3.0;
|
||||
SWIFT_VERSION = 4.2;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
|
@ -498,7 +492,7 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-RxSwift";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 3.0;
|
||||
SWIFT_VERSION = 4.2;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
|
|
@ -522,7 +516,7 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-RxSwift";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 3.0;
|
||||
SWIFT_VERSION = 4.2;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,20 +1,22 @@
|
|||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'ReCaptcha'
|
||||
s.version = '1.2'
|
||||
s.version = '1.4.2'
|
||||
s.summary = 'ReCaptcha for iOS'
|
||||
|
||||
s.swift_version = '4.2'
|
||||
|
||||
s.description = <<-DESC
|
||||
Add Google's [Invisible ReCaptcha](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.
|
||||
DESC
|
||||
|
||||
s.homepage = 'https://github.com/fjcaetano/ReCaptcha'
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
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'
|
||||
s.homepage = 'https://github.com/fjcaetano/ReCaptcha'
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
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'
|
||||
s.documentation_url = 'http://fjcaetano.github.io/ReCaptcha'
|
||||
|
||||
s.ios.deployment_target = '8.0'
|
||||
s.default_subspecs = 'Core'
|
||||
|
|
@ -31,6 +33,6 @@ invisibility is not possible.
|
|||
s.subspec 'RxSwift' do |rx|
|
||||
rx.source_files = 'ReCaptcha/Classes/Rx/**/*'
|
||||
rx.dependency 'ReCaptcha/Core'
|
||||
rx.dependency 'RxSwift', '~> 4.0'
|
||||
rx.dependency 'RxSwift', '~> 4.3'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ extension DispatchQueue {
|
|||
/// Stores the last call times for a given context
|
||||
private static var lastDebounceCallTimes = [AnyHashable: DispatchTime]()
|
||||
|
||||
/// Dispatched actions' token storage
|
||||
private static var onceTokenStorage = Set<AnyHashable>()
|
||||
|
||||
/// An object representing a context if none is given
|
||||
private static let nilContext = UUID()
|
||||
|
||||
|
|
@ -61,4 +64,21 @@ extension DispatchQueue {
|
|||
DispatchQueue.lastDebounceCallTimes.removeValue(forKey: context)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
- parameters:
|
||||
- token: The control token for each dispatched action
|
||||
- action: The closure to be executed
|
||||
|
||||
Dispatch the action only once for each given token
|
||||
*/
|
||||
static func once(token: AnyHashable, action: () -> Void) {
|
||||
guard !onceTokenStorage.contains(token) else { return }
|
||||
|
||||
defer { objc_sync_exit(self) }
|
||||
objc_sync_enter(self)
|
||||
|
||||
onceTokenStorage.insert(token)
|
||||
action()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import WebKit
|
|||
/**
|
||||
*/
|
||||
public class ReCaptcha {
|
||||
public typealias BoolParameterClosure = (Bool) -> ()
|
||||
|
||||
fileprivate struct Constants {
|
||||
struct InfoDictKeys {
|
||||
static let APIKey = "ReCaptchaKey"
|
||||
|
|
@ -30,10 +32,14 @@ public class ReCaptcha {
|
|||
/// Alternate endpoint. Points to https://www.recaptcha.net/recaptcha/api.js
|
||||
case alternate
|
||||
|
||||
internal var url: String {
|
||||
internal func getURL(locale: Locale?) -> String {
|
||||
let localeAppendix = locale.map { "&hl=\($0.identifier)" } ?? ""
|
||||
switch self {
|
||||
case .default: return "https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit"
|
||||
case .alternate: return "https://www.recaptcha.net/recaptcha/api.js?onload=onloadCallback&render=explicit"
|
||||
case .default:
|
||||
return "https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" + localeAppendix
|
||||
case .alternate:
|
||||
return "https://www.recaptcha.net/recaptcha/api.js?onload=onloadCallback&render=explicit"
|
||||
+ localeAppendix
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -97,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
|
||||
|
||||
|
|
@ -106,6 +118,7 @@ public class ReCaptcha {
|
|||
- infoPlistKey: The API key retrived from the application's Info.plist
|
||||
- baseURL: The base URL sent to the ReCaptcha init
|
||||
- infoPlistURL: The base URL retrieved from the application's Info.plist
|
||||
- locale: A locale value to translate ReCaptcha into a different language
|
||||
|
||||
Initializes a ReCaptcha object
|
||||
|
||||
|
|
@ -121,7 +134,12 @@ public class ReCaptcha {
|
|||
Info.plist.
|
||||
- Throws: Rethrows any exceptions thrown by `String(contentsOfFile:)`
|
||||
*/
|
||||
public convenience init(apiKey: String? = nil, baseURL: URL? = nil, endpoint: Endpoint = .default) throws {
|
||||
public convenience init(
|
||||
apiKey: String? = nil,
|
||||
baseURL: URL? = nil,
|
||||
endpoint: Endpoint = .default,
|
||||
locale: Locale? = nil
|
||||
) throws {
|
||||
let infoDict = Bundle.main.infoDictionary
|
||||
|
||||
let plistApiKey = infoDict?[Constants.InfoDictKeys.APIKey] as? String
|
||||
|
|
@ -133,7 +151,7 @@ public class ReCaptcha {
|
|||
html: config.html,
|
||||
apiKey: config.apiKey,
|
||||
baseURL: config.baseURL,
|
||||
endpoint: endpoint.url
|
||||
endpoint: endpoint.getURL(locale: locale)
|
||||
))
|
||||
}
|
||||
|
||||
|
|
@ -189,12 +207,26 @@ public class ReCaptcha {
|
|||
manager.reset()
|
||||
}
|
||||
|
||||
// MARK: - Development
|
||||
|
||||
#if DEBUG
|
||||
/// Forces the challenge to be explicitly displayed.
|
||||
/// Forces the challenge widget to be explicitly displayed.
|
||||
public var forceVisibleChallenge: Bool {
|
||||
get { return manager.forceVisibleChallenge }
|
||||
set { manager.forceVisibleChallenge = newValue }
|
||||
}
|
||||
|
||||
/**
|
||||
Allows validation stubbing for testing
|
||||
|
||||
When this property is set to `true`, every call to `validate()` will immediately be resolved with `.token("")`.
|
||||
|
||||
Use only when testing your application.
|
||||
*/
|
||||
public var shouldSkipForTests: Bool {
|
||||
get { return manager.shouldSkipForTests }
|
||||
set { manager.shouldSkipForTests = newValue }
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,88 +13,18 @@ import WebKit
|
|||
/** Handles comunications with the webview containing the ReCaptcha challenge.
|
||||
*/
|
||||
internal class ReCaptchaWebViewManager {
|
||||
/** The `webView` delegate object that performs execution uppon script loading
|
||||
*/
|
||||
fileprivate class WebViewDelegate: NSObject, WKNavigationDelegate {
|
||||
struct Constants {
|
||||
/// The host that loaded requests should have
|
||||
static let apiURLHost = "www.google.com"
|
||||
}
|
||||
|
||||
/// The parent manager
|
||||
private weak var manager: ReCaptchaWebViewManager?
|
||||
|
||||
/// The active requests' urls
|
||||
private var activeRequests = Set<String>(minimumCapacity: 0)
|
||||
|
||||
/// - parameter manager: The parent manager
|
||||
init(manager: ReCaptchaWebViewManager) {
|
||||
self.manager = manager
|
||||
}
|
||||
|
||||
/**
|
||||
- parameters:
|
||||
- webView: The web view invoking the delegate method.
|
||||
- navigationAction: Descriptive information about the action triggering the navigation request.
|
||||
- decisionHandler: The decision handler to call to allow or cancel the navigation. The argument is one of
|
||||
the constants of the enumerated type WKNavigationActionPolicy.
|
||||
|
||||
Decides whether to allow or cancel a navigation.
|
||||
*/
|
||||
func webView(
|
||||
_ webView: WKWebView,
|
||||
decidePolicyFor navigationAction: WKNavigationAction,
|
||||
decisionHandler: @escaping (WKNavigationActionPolicy
|
||||
) -> Void) {
|
||||
defer { decisionHandler(.allow) }
|
||||
|
||||
if let url = navigationAction.request.url, url.host == Constants.apiURLHost {
|
||||
activeRequests.insert(url.absoluteString)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
- parameters:
|
||||
- webView: The web view invoking the delegate method.
|
||||
- navigationResponse: Descriptive information about the navigation response.
|
||||
- decisionHandler: A block to be called when your app has decided whether to allow or cancel the navigation
|
||||
|
||||
Decides whether to allow or cancel a navigation after its response is known.
|
||||
*/
|
||||
func webView(
|
||||
_ webView: WKWebView,
|
||||
decidePolicyFor navigationResponse: WKNavigationResponse,
|
||||
decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void
|
||||
) {
|
||||
defer { decisionHandler(.allow) }
|
||||
guard let url = navigationResponse.response.url?.absoluteString,
|
||||
activeRequests.remove(url) != nil, activeRequests.isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
execute()
|
||||
}
|
||||
|
||||
/// Flag the requests as finished and call ReCaptcha execution if necessary
|
||||
func execute() {
|
||||
guard manager?.didFinishLoading != true else { return }
|
||||
|
||||
DispatchQueue.main.throttle(deadline: .now() + 1, context: self) { [weak self] in
|
||||
// Did finish loading the ReCaptcha JS source
|
||||
self?.manager?.didFinishLoading = true
|
||||
|
||||
if self?.manager?.completion != nil {
|
||||
// User has requested for validation
|
||||
self?.manager?.execute()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -109,14 +39,23 @@ internal class ReCaptchaWebViewManager {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows validation stubbing for testing
|
||||
public var shouldSkipForTests = false
|
||||
#endif
|
||||
|
||||
/// Callback for loading state changing
|
||||
var onLoadingChanged: ReCaptcha.BoolParameterClosure?
|
||||
|
||||
/// Sends the result message
|
||||
var completion: ((ReCaptchaResult) -> Void)?
|
||||
|
||||
/// Configures the webview for display when required
|
||||
var configureWebView: ((WKWebView) -> Void)?
|
||||
|
||||
/// The dispatch token used to ensure `configureWebView` is only called once.
|
||||
var configureWebViewDispatchToken = UUID()
|
||||
|
||||
/// If the ReCaptcha should be reset when it errors
|
||||
var shouldResetOnError = true
|
||||
|
||||
|
|
@ -124,29 +63,39 @@ internal class ReCaptchaWebViewManager {
|
|||
fileprivate var decoder: ReCaptchaDecoder!
|
||||
|
||||
/// Indicates if the script has already been loaded by the `webView`
|
||||
fileprivate var didFinishLoading = false // webView.isLoading does not work in this case
|
||||
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
|
||||
|
||||
/// The `webView` delegate implementation
|
||||
fileprivate lazy var webviewDelegate: WebViewDelegate = {
|
||||
WebViewDelegate(manager: self)
|
||||
}()
|
||||
|
||||
/// The webview that executes JS code
|
||||
lazy var webView: WKWebView = {
|
||||
let webview = WKWebView(
|
||||
frame: CGRect(x: 0, y: 0, width: 1, height: 1),
|
||||
configuration: self.buildConfiguration()
|
||||
)
|
||||
webview.navigationDelegate = self.webviewDelegate
|
||||
webview.accessibilityIdentifier = "webview"
|
||||
webview.accessibilityTraits = UIAccessibilityTraitLink
|
||||
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
|
||||
}()
|
||||
|
|
@ -160,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)
|
||||
}
|
||||
|
|
@ -171,7 +121,7 @@ internal class ReCaptchaWebViewManager {
|
|||
}
|
||||
else {
|
||||
observer = NotificationCenter.default.addObserver(
|
||||
forName: .UIWindowDidBecomeVisible,
|
||||
forName: UIWindow.didBecomeVisibleNotification,
|
||||
object: nil,
|
||||
queue: nil
|
||||
) { [weak self] notification in
|
||||
|
|
@ -187,6 +137,13 @@ internal class ReCaptchaWebViewManager {
|
|||
Starts the challenge validation
|
||||
*/
|
||||
func validate(on view: UIView) {
|
||||
onLoadingChanged?(true)
|
||||
#if DEBUG
|
||||
guard !shouldSkipForTests else {
|
||||
completion?(.token(""))
|
||||
return
|
||||
}
|
||||
#endif
|
||||
webView.isHidden = false
|
||||
view.addSubview(webView)
|
||||
|
||||
|
|
@ -206,6 +163,7 @@ internal class ReCaptchaWebViewManager {
|
|||
*/
|
||||
func reset() {
|
||||
didFinishLoading = false
|
||||
configureWebViewDispatchToken = UUID()
|
||||
|
||||
webView.evaluateJavaScript(Constants.ResetCommand) { [weak self] _, error in
|
||||
if let error = error {
|
||||
|
|
@ -220,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() {
|
||||
|
|
@ -229,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -271,15 +284,14 @@ fileprivate extension ReCaptchaWebViewManager {
|
|||
}
|
||||
|
||||
case .showReCaptcha:
|
||||
// Ensures `configureWebView` won't get called multiple times in a short period
|
||||
DispatchQueue.main.debounce(interval: 1) { [weak self] in
|
||||
DispatchQueue.once(token: configureWebViewDispatchToken) { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
self.configureWebView?(self.webView)
|
||||
}
|
||||
|
||||
case .didLoad:
|
||||
// For testing purposes
|
||||
webviewDelegate.execute()
|
||||
didFinishLoading = true
|
||||
|
||||
case .log(let message):
|
||||
#if DEBUG
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -10,9 +10,10 @@ default_platform :ios
|
|||
platform :ios do
|
||||
skip_docs
|
||||
|
||||
devices = ["iPhone X (~> 11)"]
|
||||
devices = ["iPhone XR (~> 12)"]
|
||||
devices << "iPhone X (~> 11)" if !Helper.is_ci?
|
||||
devices << "iPhone 7 (~> 10)" if !Helper.is_ci?
|
||||
devices << "iPhone 5s (~> 9)" 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
|
||||
|
|
@ -31,6 +32,7 @@ platform :ios do
|
|||
swiftlint(
|
||||
executable: "Example/Pods/Swiftlint/swiftlint",
|
||||
strict: true,
|
||||
reporter: "emoji",
|
||||
)
|
||||
|
||||
# The problem lies in the fact (or rather: serious bug in xcodebuild) that
|
||||
|
|
|
|||
Loading…
Reference in New Issue