Compare commits

..

No commits in common. "master" and "1.2" have entirely different histories.
master ... 1.2

35 changed files with 324 additions and 600 deletions

View File

@ -1,33 +0,0 @@
---
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.

3
.gitignore vendored
View File

@ -51,6 +51,3 @@ fastlane/Preview.html
fastlane/screenshots
fastlane/README.md
fastlane/test_output
## VS Code
.vscode

View File

@ -1,14 +0,0 @@
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
.swift-version Normal file
View File

@ -0,0 +1 @@
4.0

View File

@ -1,4 +1,4 @@
osx_image: xcode10
osx_image: xcode9
language: objective-c
cache:

View File

@ -1,28 +1,3 @@
# 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)

View File

@ -1 +1,2 @@
binary "https://raw.github.com/TouchInstinct/CarthageBinaries/master/RxSwift/RxSwift.json"
github "antitypical/Result" ~> 3.0
github "ReactiveX/RxSwift" ~> 4.0

View File

@ -1 +1,2 @@
binary "https://raw.github.com/TouchInstinct/CarthageBinaries/master/RxSwift/RxSwift.json" "4.5.0"
github "ReactiveX/RxSwift" "4.1.2"
github "antitypical/Result" "3.2.4"

View File

@ -5,13 +5,13 @@ inhibit_all_warnings!
target 'ReCaptcha_Example' do
pod 'ReCaptcha/RxSwift', :path => '../'
pod 'RxCocoa', '~> 4.3'
pod 'SwiftLint', '~> 0.27'
pod 'RxCocoa', '~> 4.0'
pod 'SwiftLint', '~> 0.24'
target 'ReCaptcha_Tests' do
inherit! :search_paths
pod 'AppSwizzle', '~> 1.3'
pod 'AppSwizzle', :git => 'https://github.com/fjcaetano/AppSwizzle.git', :branch => 'swift4'
pod 'RxBlocking', '~> 4.0'
end

View File

@ -1,48 +1,43 @@
PODS:
- AppSwizzle (1.3.1)
- ReCaptcha/Core (1.4.1)
- ReCaptcha/RxSwift (1.4.1):
- AppSwizzle (1.2)
- ReCaptcha/Core (1.1)
- ReCaptcha/RxSwift (1.1):
- ReCaptcha/Core
- RxSwift (~> 4.3)
- RxAtomic (4.4.0)
- RxBlocking (4.4.0):
- RxAtomic (~> 4.4)
- RxSwift (~> 4.0)
- RxCocoa (4.4.0):
- RxBlocking (4.1.1):
- RxSwift (~> 4.0)
- RxSwift (4.4.0):
- RxAtomic (~> 4.4)
- SwiftLint (0.27.0)
- RxCocoa (4.1.1):
- RxSwift (~> 4.0)
- RxSwift (4.1.1)
- SwiftLint (0.24.1)
DEPENDENCIES:
- AppSwizzle (~> 1.3)
- AppSwizzle (from `https://github.com/fjcaetano/AppSwizzle.git`, branch `swift4`)
- ReCaptcha/RxSwift (from `../`)
- RxBlocking (~> 4.0)
- RxCocoa (~> 4.3)
- SwiftLint (~> 0.27)
SPEC REPOS:
https://github.com/cocoapods/specs.git:
- AppSwizzle
- RxAtomic
- RxBlocking
- RxCocoa
- RxSwift
- SwiftLint
- RxCocoa (~> 4.0)
- SwiftLint (~> 0.24)
EXTERNAL SOURCES:
AppSwizzle:
:branch: swift4
:git: https://github.com/fjcaetano/AppSwizzle.git
ReCaptcha:
:path: "../"
:path: ../
CHECKOUT OPTIONS:
AppSwizzle:
:commit: c432a73e43779d20ef8e8a589aabd9622b7b6a5d
:git: https://github.com/fjcaetano/AppSwizzle.git
SPEC CHECKSUMS:
AppSwizzle: db36e436f56110d93e5ae0147683435df593cabc
ReCaptcha: 520a707a38dfbb1e5de812aa3c041df60bd31827
RxAtomic: eacf60db868c96bfd63320e28619fe29c179656f
RxBlocking: 138ad53217434444d6eeeb4fb406a45431d92e31
RxCocoa: df63ebf7b9a70d6b4eeea407ed5dd4efc8979749
RxSwift: 5976ecd04fc2fefd648827c23de5e11157faa973
SwiftLint: 3207c1faa2240bf8973b191820a116113cd11073
AppSwizzle: bbd3782652fc426ce59c045a92ec61d36f261984
ReCaptcha: b94a673f3827e9b9cf7e77db0395694549241247
RxBlocking: 22e7d7b86a1c0c42164a30d64f56f97128b4ffab
RxCocoa: fd0862fd2df95fa55562ad28ffd2522c25eb4a85
RxSwift: c6e3b1c7b325c7d121cd4327e9d98b7ed746b570
SwiftLint: 2e4b89feed5909c42c3735bbd6745f4345c4b772
PODFILE CHECKSUM: 63f50401dee6a885ae0eea927fb966553c6d0f33
PODFILE CHECKSUM: 3ae211ad7c1b9c86749b013587aa4b63661f345e
COCOAPODS: 1.5.3
COCOAPODS: 1.4.0

View File

@ -16,7 +16,6 @@
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 */; };
@ -69,7 +68,6 @@
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>"; };
@ -216,7 +214,6 @@
F2E2685D1F7AEE3400CD876D /* ReCaptcha__Tests.swift */,
F231B3961FEC325A00F82943 /* DispatchQueue__Tests.swift */,
F2AE8611204F3430002E28D7 /* ReCaptchaResult__Tests.swift */,
F211C22120F7E0B100709B26 /* ReCaptcha_Endpoint__Tests.swift */,
);
path = Core;
sourceTree = "<group>";
@ -262,6 +259,7 @@
607FACCD1AFB9204008FA782 /* Frameworks */,
607FACCE1AFB9204008FA782 /* Resources */,
8F03FFB3F5C55E873C23C682 /* [CP] Embed Pods Frameworks */,
ED1C0E07490C9C4B4A401061 /* [CP] Copy Pods Resources */,
F231B3981FEC3B7F00F82943 /* SwiftLint */,
);
buildRules = (
@ -281,6 +279,8 @@
F28FAC98200E425600E14987 /* Sources */,
F28FAC99200E425600E14987 /* Frameworks */,
F28FAC9A200E425600E14987 /* Resources */,
BF2909E4520B29BFFBC94AD6 /* [CP] Embed Pods Frameworks */,
DCB1D99E77E589CD76CD3186 /* [CP] Copy Pods Resources */,
F28FACA6200E447600E14987 /* ShellScript */,
);
buildRules = (
@ -302,6 +302,7 @@
F2ECCF731E9FC47B0097B199 /* Frameworks */,
F2ECCF741E9FC47B0097B199 /* Resources */,
77003100630E7783A936C451 /* [CP] Embed Pods Frameworks */,
44209D7CDDDE3A11075B8104 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@ -390,6 +391,21 @@
/* 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;
@ -415,14 +431,12 @@
);
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",
@ -440,14 +454,12 @@
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",
);
@ -474,6 +486,36 @@
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;
@ -492,6 +534,21 @@
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;
@ -504,7 +561,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/SwiftLint/swiftlint\" --path \"${PROJECT_DIR}/..\"\n";
shellScript = "\"${PODS_ROOT}/SwiftLint/swiftlint\" --path \"${PROJECT_DIR}/..\"";
};
F28FACA6200E447600E14987 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
@ -549,7 +606,6 @@
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 */,
@ -703,7 +759,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-Example";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 4.0;
};
name = Debug;
};
@ -719,7 +775,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-Example";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 4.0;
};
name = Release;
};
@ -742,7 +798,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "ReCaptcha.ReCaptcha-UITests";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = ReCaptcha_Example;
};
@ -765,7 +821,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.2;
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = ReCaptcha_Example;
};
@ -788,7 +844,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG UNIT_TESTS";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 4.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ReCaptcha_Example.app/ReCaptcha_Example";
};
name = Debug;
@ -808,7 +864,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-Tests";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 4.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ReCaptcha_Example.app/ReCaptcha_Example";
};
name = Release;

View File

@ -40,8 +40,9 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
codeCoverageEnabled = "YES"
shouldUseLaunchSchemeArgsEnv = "YES">
language = ""
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
<Testables>
<TestableReference
skipped = "NO">
@ -77,9 +78,10 @@
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"

View File

@ -1,8 +0,0 @@
<?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>

View File

@ -16,7 +16,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
) -> Bool {
// Override point for customization after application launch.
return true

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<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">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<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="450" width="275" height="50"/>
<rect key="frame" x="50" y="498" 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="247" width="335" height="173"/>
<rect key="frame" x="20" y="199" width="335" height="269"/>
<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="71" y="568" width="233" height="29"/>
<rect key="frame" x="117" y="568" width="141" height="29"/>
<segments>
<segment title="Default Endpoint"/>
<segment title="Default"/>
<segment title="Alternate"/>
</segments>
<connections>
<action selector="didPressEndpointSegmentedControl:" destination="vXZ-lx-hvc" eventType="valueChanged" id="Sdm-dO-doL"/>
<action selector="didPressSegmentedControl:" destination="vXZ-lx-hvc" eventType="valueChanged" id="e5Z-W5-Q1d"/>
</connections>
</segmentedControl>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Uyt-0M-CR7">
@ -76,40 +76,27 @@
<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="localeSegmentedControl" destination="kq2-ci-l1u" id="gL7-du-K6l"/>
<outlet property="segmentedControl" destination="X8E-G9-9IV" id="sS0-3k-Alu"/>
<outlet property="spinner" destination="jHc-GP-v1Z" id="gRn-JW-FIK"/>
<outlet property="visibleChallengeSwitch" destination="mGh-Ox-cFf" id="R13-nD-EXL"/>
</connections>

View File

@ -39,11 +39,6 @@
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
}
],
"info" : {

View File

@ -21,40 +21,23 @@ 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 localeSegmentedControl: UISegmentedControl!
@IBOutlet private weak var endpointSegmentedControl: UISegmentedControl!
@IBOutlet private weak var segmentedControl: UISegmentedControl!
@IBOutlet private weak var visibleChallengeSwitch: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
setupReCaptcha()
setupReCaptcha(endpoint: .default)
}
@IBAction func didPressEndpointSegmentedControl(_ sender: UISegmentedControl) {
@IBAction func didPressSegmentedControl(_ sender: UISegmentedControl) {
label.text = ""
switch sender.selectedSegmentIndex {
case 0: endpoint = .default
case 1: endpoint = .alternate
case 0: setupReCaptcha(endpoint: .default)
case 1: setupReCaptcha(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) {
@ -83,7 +66,7 @@ class ViewController: UIViewController {
.disposed(by: disposeBag)
isEnabled
.bind(to: endpointSegmentedControl.rx.isEnabled)
.bind(to: segmentedControl.rx.isEnabled)
.disposed(by: disposeBag)
validate
@ -106,9 +89,9 @@ class ViewController: UIViewController {
.disposed(by: disposeBag)
}
private func setupReCaptcha() {
private func setupReCaptcha(endpoint: ReCaptcha.Endpoint) {
// swiftlint:disable:next force_try
recaptcha = try! ReCaptcha(endpoint: endpoint, locale: locale)
recaptcha = try! ReCaptcha(endpoint: endpoint)
recaptcha.configureWebView { [weak self] webview in
webview.frame = self?.view.bounds ?? CGRect.zero

View File

@ -4,6 +4,3 @@ disabled_rules:
- force_unwrapping
- explicit_top_level_acl
- function_body_length
- identifier_name
- file_length
- type_body_length

View File

@ -151,47 +151,4 @@ 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)
}
}

View File

@ -210,34 +210,6 @@ 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() {
@ -287,7 +259,7 @@ class ReCaptchaWebViewManager__Tests: XCTestCase {
func test__Endpoint_Setup() {
let exp = expectation(description: "setup endpoint")
let endpoint = ReCaptcha.Endpoint.alternate.getURL(locale: nil)
let endpoint = ReCaptcha.Endpoint.alternate.url
var result: ReCaptchaResult?
let manager = ReCaptchaWebViewManager(messageBody: "{token: endpoint}", endpoint: endpoint)
@ -366,22 +338,6 @@ 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() {

View File

@ -1,27 +0,0 @@
//
// 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")
}
}

View File

@ -107,7 +107,7 @@ class ReCaptcha_Rx__Tests: XCTestCase {
let exp = expectation(description: "stop loading")
// Stop
let recaptcha = ReCaptcha(manager: ReCaptchaWebViewManager(messageBody: "{log: \"foo\"}"))
let recaptcha = ReCaptcha(manager: ReCaptchaWebViewManager(messageBody: "{action: \"showReCaptcha\"}"))
recaptcha.configureWebView { _ in
XCTFail("should not ask to configure the webview")
}

View File

@ -19,6 +19,10 @@
}
};
window.onload = function() {
post({action: "didLoad"});
};
var reset = function() {
shouldFail = false;
post({action: "didLoad"});

View File

@ -23,7 +23,7 @@ class ReCaptcha_UITests: XCTestCase {
func test__Validate__Default_Endpoint() {
let app = XCUIApplication()
app.segmentedControls.buttons["Default Endpoint"].tap()
app.segmentedControls.buttons["Default"].tap()
app.switches["Switch"].tap()
app.buttons["Validate"].tap()

View File

@ -1,4 +1,4 @@
source 'https://rubygems.org'
gem 'fastlane', '~> 2.105.2'
gem 'cocoapods', '~> 1.5.3'
gem 'fastlane', '~> 2.75'
gem 'cocoapods', '~> 1.4'

View File

@ -1,7 +1,7 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.0)
CFPropertyList (2.3.6)
activesupport (4.2.10)
i18n (~> 0.7)
minitest (~> 5.1)
@ -9,15 +9,14 @@ 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.5.3)
cocoapods (1.4.0)
activesupport (>= 4.0.2, < 5)
claide (>= 1.0.2, < 2.0)
cocoapods-core (= 1.5.3)
cocoapods-core (= 1.4.0)
cocoapods-deintegrate (>= 1.0.2, < 2.0)
cocoapods-downloader (>= 1.2.0, < 2.0)
cocoapods-downloader (>= 1.1.3, < 2.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
cocoapods-search (>= 1.0.0, < 2.0)
cocoapods-stats (>= 1.0.0, < 2.0)
@ -27,61 +26,59 @@ GEM
escape (~> 0.0.4)
fourflusher (~> 2.0.1)
gh_inspector (~> 1.0)
molinillo (~> 0.6.5)
molinillo (~> 0.6.4)
nap (~> 1.0)
ruby-macho (~> 1.1)
xcodeproj (>= 1.5.7, < 2.0)
cocoapods-core (1.5.3)
xcodeproj (>= 1.5.4, < 2.0)
cocoapods-core (1.4.0)
activesupport (>= 4.0.2, < 6)
fuzzy_match (~> 2.0.4)
nap (~> 1.0)
cocoapods-deintegrate (1.0.2)
cocoapods-downloader (1.2.1)
cocoapods-downloader (1.1.3)
cocoapods-plugins (1.0.0)
nap
cocoapods-search (1.0.0)
cocoapods-stats (1.0.0)
cocoapods-trunk (1.3.1)
cocoapods-trunk (1.3.0)
nap (>= 0.8, < 2.0)
netrc (~> 0.11)
cocoapods-try (1.1.0)
colored (1.2)
colored2 (3.1.2)
commander-fastlane (4.4.6)
commander-fastlane (4.4.5)
highline (~> 1.7.2)
concurrent-ruby (1.0.5)
declarative (0.0.10)
declarative-option (0.1.0)
domain_name (0.5.20180417)
domain_name (0.5.20170404)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.5.0)
emoji_regex (0.1.1)
dotenv (2.2.1)
escape (0.0.4)
excon (0.62.0)
faraday (0.15.3)
excon (0.60.0)
faraday (0.13.1)
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.4)
fastlane (2.105.2)
CFPropertyList (>= 2.3, < 4.0.0)
fastimage (2.1.1)
fastlane (2.75.1)
CFPropertyList (>= 2.3, < 3.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.6, < 5.0.0)
commander-fastlane (>= 4.4.5, < 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.1.2, < 2.0.0)
google-api-client (>= 0.21.2, < 0.24.0)
gh_inspector (>= 1.0.1, < 2.0.0)
google-api-client (>= 0.13.1, < 0.14.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
mini_magick (~> 4.5.1)
@ -90,102 +87,100 @@ GEM
multipart-post (~> 2.0.0)
plist (>= 3.1.0, < 4.0.0)
public_suffix (~> 2.0.0)
rubyzip (>= 1.2.2, < 2.0.0)
rubyzip (>= 1.1.0, < 2.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
slack-notifier (>= 2.0.0, < 3.0.0)
slack-notifier (>= 1.3, < 2.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.8.0, < 1.0.0)
tty-spinner (>= 0.7.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.6.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcodeproj (>= 1.5.2, < 2.0.0)
xcpretty (>= 0.2.4, < 1.0.0)
xcpretty-travis-formatter (>= 0.0.3)
fourflusher (2.0.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.3)
google-api-client (0.23.9)
gh_inspector (1.0.3)
google-api-client (0.13.6)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.5, < 0.7.0)
googleauth (~> 0.5)
httpclient (>= 2.8.1, < 3.0)
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
signet (~> 0.9)
googleauth (0.6.6)
googleauth (0.6.2)
faraday (~> 0.12)
jwt (>= 1.4, < 3.0)
logging (~> 2.0)
memoist (~> 0.12)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
os (~> 0.9)
signet (~> 0.7)
highline (1.7.10)
http-cookie (1.0.3)
domain_name (~> 0.5)
httpclient (2.8.3)
i18n (0.9.5)
i18n (0.9.1)
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.2.2)
mime-types (3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2018.0812)
mime-types-data (3.2016.0521)
mini_magick (4.5.1)
minitest (5.11.3)
molinillo (0.6.6)
multi_json (1.13.1)
minitest (5.11.1)
molinillo (0.6.4)
multi_json (1.13.0)
multi_xml (0.6.0)
multipart-post (2.0.0)
nanaimo (0.2.6)
nanaimo (0.2.3)
nap (1.1.0)
naturally (2.2.0)
netrc (0.11.0)
os (1.0.0)
os (0.9.6)
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.2)
retriable (3.1.1)
rouge (2.0.7)
ruby-macho (1.2.0)
rubyzip (1.2.2)
ruby-macho (1.1.0)
rubyzip (1.2.1)
security (0.1.3)
signet (0.10.0)
signet (0.8.1)
addressable (~> 2.3)
faraday (~> 0.9)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.5)
CFPropertyList
naturally
slack-notifier (2.3.2)
slack-notifier (1.5.1)
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.6.0)
tty-screen (0.6.5)
tty-spinner (0.8.0)
tty-cursor (0.5.0)
tty-screen (0.6.4)
tty-spinner (0.7.0)
tty-cursor (>= 0.5.0)
tzinfo (1.2.5)
tzinfo (1.2.4)
thread_safe (~> 0.1)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.5)
unicode-display_width (1.4.0)
unf_ext (0.0.7.4)
unicode-display_width (1.3.0)
word_wrap (1.0.0)
xcodeproj (1.6.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
xcodeproj (1.5.4)
CFPropertyList (~> 2.3.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.2.6)
xcpretty (0.3.0)
nanaimo (~> 0.2.3)
xcpretty (0.2.8)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.0)
xcpretty (~> 0.2, >= 0.0.7)
@ -194,8 +189,8 @@ PLATFORMS
ruby
DEPENDENCIES
cocoapods (~> 1.5.3)
fastlane (~> 2.105.2)
cocoapods (~> 1.4)
fastlane (~> 2.75)
BUNDLED WITH
1.16.1
1.16.0

View File

@ -9,7 +9,7 @@
-----
Add Google's [Invisible ReCaptcha v2](https://developers.google.com/recaptcha/docs/invisible) to your project. This library
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.
@ -17,15 +17,8 @@ invisibility is not possible.
#### _Warning_ ⚠️
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).
![ReCaptcha v2 invisible key example](https://raw.githubusercontent.com/fjcaetano/ReCaptcha/master/example-v2-key.png)
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.
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).
## Installation
@ -49,7 +42,7 @@ extension for the ReCaptcha framework.
## Usage
Simply add `ReCaptchaKey` and `ReCaptchaDomain` (with a protocol ex. http:// or https://) to your Info.plist and run:
Simply add `ReCaptchaKey` and `ReCaptchaDomain` (with a protocol) to your Info.plist and run:
``` swift
let recaptcha = try? ReCaptcha()
@ -92,13 +85,6 @@ 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.

View File

@ -8,6 +8,7 @@
/* 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 */; };
@ -18,6 +19,7 @@
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 */
@ -36,6 +38,7 @@
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>"; };
@ -48,6 +51,7 @@
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 */
@ -56,6 +60,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
F206BAD31F8D3E7600A25807 /* Result.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -64,6 +69,7 @@
buildActionMask = 2147483647;
files = (
F206BB221F8D4DDF00A25807 /* RxSwift.framework in Frameworks */,
F255FBEC1F8D5654002F5FA8 /* Result.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -104,7 +110,9 @@
F206BAD11F8D3E7600A25807 /* Frameworks */ = {
isa = PBXGroup;
children = (
F255FBEB1F8D5654002F5FA8 /* Result.framework */,
F206BB121F8D4D1400A25807 /* RxSwift.framework */,
F206BAD21F8D3E7600A25807 /* Result.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -367,7 +375,6 @@
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 = "";
@ -420,7 +427,6 @@
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";
@ -446,7 +452,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.flaviocaetano.ReCaptcha;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 3.0;
};
name = Debug;
};
@ -468,7 +474,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.flaviocaetano.ReCaptcha;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 3.0;
};
name = Release;
};
@ -492,7 +498,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-RxSwift";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 3.0;
};
name = Debug;
};
@ -516,7 +522,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-RxSwift";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 3.0;
};
name = Release;
};

View File

@ -1,22 +1,20 @@
Pod::Spec.new do |s|
s.name = 'ReCaptcha'
s.version = '1.4.2'
s.version = '1.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.documentation_url = 'http://fjcaetano.github.io/ReCaptcha'
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.ios.deployment_target = '8.0'
s.default_subspecs = 'Core'
@ -33,6 +31,6 @@ invisibility is not possible.
s.subspec 'RxSwift' do |rx|
rx.source_files = 'ReCaptcha/Classes/Rx/**/*'
rx.dependency 'ReCaptcha/Core'
rx.dependency 'RxSwift', '~> 4.3'
rx.dependency 'RxSwift', '~> 4.0'
end
end

View File

@ -16,9 +16,6 @@ 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()
@ -64,21 +61,4 @@ 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()
}
}

View File

@ -13,8 +13,6 @@ import WebKit
/**
*/
public class ReCaptcha {
public typealias BoolParameterClosure = (Bool) -> ()
fileprivate struct Constants {
struct InfoDictKeys {
static let APIKey = "ReCaptchaKey"
@ -32,14 +30,10 @@ public class ReCaptcha {
/// Alternate endpoint. Points to https://www.recaptcha.net/recaptcha/api.js
case alternate
internal func getURL(locale: Locale?) -> String {
let localeAppendix = locale.map { "&hl=\($0.identifier)" } ?? ""
internal var url: String {
switch self {
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
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"
}
}
}
@ -103,12 +97,6 @@ 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
@ -118,7 +106,6 @@ 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
@ -134,12 +121,7 @@ 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,
locale: Locale? = nil
) throws {
public convenience init(apiKey: String? = nil, baseURL: URL? = nil, endpoint: Endpoint = .default) throws {
let infoDict = Bundle.main.infoDictionary
let plistApiKey = infoDict?[Constants.InfoDictKeys.APIKey] as? String
@ -151,7 +133,7 @@ public class ReCaptcha {
html: config.html,
apiKey: config.apiKey,
baseURL: config.baseURL,
endpoint: endpoint.getURL(locale: locale)
endpoint: endpoint.url
))
}
@ -207,26 +189,12 @@ public class ReCaptcha {
manager.reset()
}
// MARK: - Development
#if DEBUG
/// Forces the challenge widget to be explicitly displayed.
/// Forces the challenge 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
}

View File

@ -13,18 +13,88 @@ 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
@ -39,23 +109,14 @@ 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
@ -63,39 +124,29 @@ 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 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()
}
}
}
}
fileprivate var didFinishLoading = false // webView.isLoading does not work in this case
/// 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 = UIAccessibilityTraits.link
webview.accessibilityTraits = UIAccessibilityTraitLink
webview.isHidden = true
self.loadingObservation = webview.observe(\.estimatedProgress, options: [.new]) { [weak self] _, change in
self?.didFinishLoading = change.newValue == 1
}
return webview
}()
@ -109,7 +160,6 @@ 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)
}
@ -121,7 +171,7 @@ internal class ReCaptchaWebViewManager {
}
else {
observer = NotificationCenter.default.addObserver(
forName: UIWindow.didBecomeVisibleNotification,
forName: .UIWindowDidBecomeVisible,
object: nil,
queue: nil
) { [weak self] notification in
@ -137,13 +187,6 @@ 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)
@ -163,7 +206,6 @@ internal class ReCaptchaWebViewManager {
*/
func reset() {
didFinishLoading = false
configureWebViewDispatchToken = UUID()
webView.evaluateJavaScript(Constants.ResetCommand) { [weak self] _, error in
if let error = error {
@ -178,7 +220,7 @@ internal class ReCaptchaWebViewManager {
/** Private methods for ReCaptchaWebViewManager
*/
fileprivate extension ReCaptchaWebViewManager {
/** Executes the JS command that loads the ReCaptcha challenge after a page finished loading.
/** Executes the JS command that loads the ReCaptcha challenge.
This method has no effect if the webview hasn't finished loading.
*/
func execute() {
@ -187,65 +229,10 @@ 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)
}
}
@ -284,14 +271,15 @@ fileprivate extension ReCaptchaWebViewManager {
}
case .showReCaptcha:
DispatchQueue.once(token: configureWebViewDispatchToken) { [weak self] in
// Ensures `configureWebView` won't get called multiple times in a short period
DispatchQueue.main.debounce(interval: 1) { [weak self] in
guard let `self` = self else { return }
self.configureWebView?(self.webView)
}
case .didLoad:
// For testing purposes
didFinishLoading = true
webviewDelegate.execute()
case .log(let message):
#if DEBUG

View File

@ -9,10 +9,6 @@
import RxSwift
import UIKit
public enum ReCaptchaRxError: Error {
case baseWasReleased
}
/// Makes ReCaptcha compatible with RxSwift extensions
extension ReCaptcha: ReactiveCompatible {}
@ -68,20 +64,4 @@ 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.

Before

Width:  |  Height:  |  Size: 54 KiB

View File

@ -10,10 +10,9 @@ default_platform :ios
platform :ios do
skip_docs
devices = ["iPhone XR (~> 12)"]
devices << "iPhone X (~> 11)" if !Helper.is_ci?
devices = ["iPhone X (~> 11)"]
devices << "iPhone 7 (~> 10)" if !Helper.is_ci?
devices << "iPhone 6s (~> 9)" if !Helper.is_ci?
devices << "iPhone 5s (~> 9)" if !Helper.is_ci?
desc "Runs the following lanes:\n- test\n- pod_lint\n- carthage_lint"
lane :ci do
@ -32,7 +31,6 @@ 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