Compare commits
59 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 | |
|
|
a329de8859 | |
|
|
4b814c56ab | |
|
|
8160d36ef9 | |
|
|
8a91279bd5 | |
|
|
cab34b882f | |
|
|
678da21e63 | |
|
|
3ec88fcf7e | |
|
|
55d8988b77 | |
|
|
ce03959fe9 | |
|
|
9212a489a5 | |
|
|
e2a38dda22 | |
|
|
c6a00aca8e | |
|
|
6a6fc22ae0 | |
|
|
53aecff93b | |
|
|
3685f7d195 | |
|
|
5f1624b9e1 | |
|
|
a52b2686cb | |
|
|
2dd15c0845 | |
|
|
11818ab473 |
|
|
@ -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/screenshots
|
||||||
fastlane/README.md
|
fastlane/README.md
|
||||||
fastlane/test_output
|
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
|
|
||||||
|
|
@ -44,5 +44,5 @@ file_header:
|
||||||
\/\/ ReCaptcha
|
\/\/ ReCaptcha
|
||||||
\/\/
|
\/\/
|
||||||
\/\/ Created by .*? on \d{1,2}\/\d{1,2}\/\d{2}\.
|
\/\/ Created by .*? on \d{1,2}\/\d{1,2}\/\d{2}\.
|
||||||
\/\/ Copyright © \d{4} ReCaptcha\. All rights reserved\.
|
\/\/ Copyright © 2018 ReCaptcha\. All rights reserved\.
|
||||||
\/\/
|
\/\/
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
osx_image: xcode9
|
osx_image: xcode10
|
||||||
language: objective-c
|
language: objective-c
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
|
|
|
||||||
41
CHANGELOG.md
41
CHANGELOG.md
|
|
@ -1,3 +1,44 @@
|
||||||
|
# 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)
|
||||||
|
- Feature: Forcing visible challenge on DEBUG. (#19)
|
||||||
|
|
||||||
|
- Fix: Better encapsulation architecture.
|
||||||
|
- Fix: Retiring Result dependency. (#24)
|
||||||
|
- Fix: `validate` completion closure being called consecutively. (#29)
|
||||||
|
- Fix: `configureWebView` being called multiple times. (#31)
|
||||||
|
|
||||||
|
# 1.1
|
||||||
|
|
||||||
|
- Fix: better logging for when protocol isn't found on
|
||||||
|
- Fix: Alternate endpoint not loading
|
||||||
|
- Fix: Prepends a scheme if `baseURL` doesn't have one
|
||||||
|
|
||||||
# 1.0.2
|
# 1.0.2
|
||||||
|
|
||||||
- Fix: Better detection of resources loading end (#16)
|
- Fix: Better detection of resources loading end (#16)
|
||||||
|
|
|
||||||
3
Cartfile
3
Cartfile
|
|
@ -1,2 +1 @@
|
||||||
github "antitypical/Result" ~> 3.0
|
binary "https://raw.github.com/TouchInstinct/CarthageBinaries/master/RxSwift/RxSwift.json"
|
||||||
github "ReactiveX/RxSwift" ~> 4.0
|
|
||||||
|
|
@ -1,2 +1 @@
|
||||||
github "ReactiveX/RxSwift" "4.0.0"
|
binary "https://raw.github.com/TouchInstinct/CarthageBinaries/master/RxSwift/RxSwift.json" "4.5.0"
|
||||||
github "antitypical/Result" "3.2.4"
|
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,18 @@ inhibit_all_warnings!
|
||||||
|
|
||||||
target 'ReCaptcha_Example' do
|
target 'ReCaptcha_Example' do
|
||||||
pod 'ReCaptcha/RxSwift', :path => '../'
|
pod 'ReCaptcha/RxSwift', :path => '../'
|
||||||
pod 'RxCocoa', '~> 4.0'
|
pod 'RxCocoa', '~> 4.3'
|
||||||
pod 'SwiftLint', '~> 0.24'
|
pod 'SwiftLint', '~> 0.27'
|
||||||
|
|
||||||
target 'ReCaptcha_Tests' do
|
target 'ReCaptcha_Tests' do
|
||||||
inherit! :search_paths
|
inherit! :search_paths
|
||||||
|
|
||||||
pod 'AppSwizzle', :git => 'https://github.com/fjcaetano/AppSwizzle.git', :branch => 'swift4'
|
pod 'AppSwizzle', '~> 1.3'
|
||||||
|
pod 'RxBlocking', '~> 4.0'
|
||||||
|
end
|
||||||
|
|
||||||
|
target 'ReCaptcha_UITests' do
|
||||||
|
inherit! :search_paths
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,48 @@
|
||||||
PODS:
|
PODS:
|
||||||
- AppSwizzle (1.2)
|
- AppSwizzle (1.3.1)
|
||||||
- ReCaptcha/Core (1.0.1):
|
- ReCaptcha/Core (1.4.1)
|
||||||
- Result (~> 3.0)
|
- ReCaptcha/RxSwift (1.4.1):
|
||||||
- ReCaptcha/RxSwift (1.0.1):
|
|
||||||
- ReCaptcha/Core
|
- ReCaptcha/Core
|
||||||
|
- RxSwift (~> 4.3)
|
||||||
|
- RxAtomic (4.4.0)
|
||||||
|
- RxBlocking (4.4.0):
|
||||||
|
- RxAtomic (~> 4.4)
|
||||||
- RxSwift (~> 4.0)
|
- RxSwift (~> 4.0)
|
||||||
- Result (3.2.4)
|
- RxCocoa (4.4.0):
|
||||||
- RxCocoa (4.1.1):
|
|
||||||
- RxSwift (~> 4.0)
|
- RxSwift (~> 4.0)
|
||||||
- RxSwift (4.1.1)
|
- RxSwift (4.4.0):
|
||||||
- SwiftLint (0.24.1)
|
- RxAtomic (~> 4.4)
|
||||||
|
- SwiftLint (0.27.0)
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- AppSwizzle (from `https://github.com/fjcaetano/AppSwizzle.git`, branch `swift4`)
|
- AppSwizzle (~> 1.3)
|
||||||
- ReCaptcha/RxSwift (from `../`)
|
- ReCaptcha/RxSwift (from `../`)
|
||||||
- RxCocoa (~> 4.0)
|
- RxBlocking (~> 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:
|
EXTERNAL SOURCES:
|
||||||
AppSwizzle:
|
|
||||||
:branch: swift4
|
|
||||||
:git: https://github.com/fjcaetano/AppSwizzle.git
|
|
||||||
ReCaptcha:
|
ReCaptcha:
|
||||||
:path: ../
|
:path: "../"
|
||||||
|
|
||||||
CHECKOUT OPTIONS:
|
|
||||||
AppSwizzle:
|
|
||||||
:commit: c432a73e43779d20ef8e8a589aabd9622b7b6a5d
|
|
||||||
:git: https://github.com/fjcaetano/AppSwizzle.git
|
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
AppSwizzle: bbd3782652fc426ce59c045a92ec61d36f261984
|
AppSwizzle: db36e436f56110d93e5ae0147683435df593cabc
|
||||||
ReCaptcha: f281cd074be9b282f528c6dda9337476f13bd776
|
ReCaptcha: 520a707a38dfbb1e5de812aa3c041df60bd31827
|
||||||
Result: d2d07204ce72856f1fd9130bbe42c35a7b0fea10
|
RxAtomic: eacf60db868c96bfd63320e28619fe29c179656f
|
||||||
RxCocoa: fd0862fd2df95fa55562ad28ffd2522c25eb4a85
|
RxBlocking: 138ad53217434444d6eeeb4fb406a45431d92e31
|
||||||
RxSwift: c6e3b1c7b325c7d121cd4327e9d98b7ed746b570
|
RxCocoa: df63ebf7b9a70d6b4eeea407ed5dd4efc8979749
|
||||||
SwiftLint: 2e4b89feed5909c42c3735bbd6745f4345c4b772
|
RxSwift: 5976ecd04fc2fefd648827c23de5e11157faa973
|
||||||
|
SwiftLint: 3207c1faa2240bf8973b191820a116113cd11073
|
||||||
|
|
||||||
PODFILE CHECKSUM: 354a8d5b36f1deae996d24aea6382d1ca091466f
|
PODFILE CHECKSUM: 63f50401dee6a885ae0eea927fb966553c6d0f33
|
||||||
|
|
||||||
COCOAPODS: 1.3.1
|
COCOAPODS: 1.5.3
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,16 @@
|
||||||
607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; };
|
607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; };
|
||||||
607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; };
|
607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; };
|
||||||
607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; };
|
607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; };
|
||||||
|
9D85E832734B73CFBD0156E0 /* Pods_ReCaptcha_UITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B4C17A11F39B6DC940891AE0 /* Pods_ReCaptcha_UITests.framework */; };
|
||||||
BD850CB2DF4C9C94FC51226C /* Pods_ReCaptcha_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62BEEA62161F672468CCFD64 /* Pods_ReCaptcha_Example.framework */; };
|
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 */; };
|
D091B6E053FD250B4757E34C /* Pods_ReCaptcha_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9417A28DC340FF0BC1627B3F /* Pods_ReCaptcha_Tests.framework */; };
|
||||||
F206BAD51F8D3FEB00A25807 /* Cartfile in Resources */ = {isa = PBXBuildFile; fileRef = F206BAD41F8D3FEB00A25807 /* Cartfile */; };
|
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 */; };
|
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 */; };
|
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 */; };
|
F288E9451F9537760018688D /* ReCaptchaError+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F288E9441F9537760018688D /* ReCaptchaError+Equatable.swift */; };
|
||||||
|
F28FAC9F200E425600E14987 /* ReCaptcha_UITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F28FAC9E200E425600E14987 /* ReCaptcha_UITests.swift */; };
|
||||||
|
F2AE8612204F3430002E28D7 /* ReCaptchaResult__Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2AE8611204F3430002E28D7 /* ReCaptchaResult__Tests.swift */; };
|
||||||
F2E2685E1F7AEE3400CD876D /* ReCaptcha__Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E2685D1F7AEE3400CD876D /* ReCaptcha__Tests.swift */; };
|
F2E2685E1F7AEE3400CD876D /* ReCaptcha__Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E2685D1F7AEE3400CD876D /* ReCaptcha__Tests.swift */; };
|
||||||
F2ECCF8A1E9FCEFE0097B199 /* ReCaptchaDecoder__Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2ECCF891E9FCEFE0097B199 /* ReCaptchaDecoder__Tests.swift */; };
|
F2ECCF8A1E9FCEFE0097B199 /* ReCaptchaDecoder__Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2ECCF891E9FCEFE0097B199 /* ReCaptchaDecoder__Tests.swift */; };
|
||||||
F2ECCF8C1E9FE37C0097B199 /* mock.html in Resources */ = {isa = PBXBuildFile; fileRef = F2ECCF8B1E9FE37C0097B199 /* mock.html */; };
|
F2ECCF8C1E9FE37C0097B199 /* mock.html in Resources */ = {isa = PBXBuildFile; fileRef = F2ECCF8B1E9FE37C0097B199 /* mock.html */; };
|
||||||
|
|
@ -28,6 +32,13 @@
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
F28FACA1200E425600E14987 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 607FACC81AFB9204008FA782 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 607FACCF1AFB9204008FA782;
|
||||||
|
remoteInfo = ReCaptcha_Example;
|
||||||
|
};
|
||||||
F2ECCF7B1E9FC47B0097B199 /* PBXContainerItemProxy */ = {
|
F2ECCF7B1E9FC47B0097B199 /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = 607FACC81AFB9204008FA782 /* Project object */;
|
containerPortal = 607FACC81AFB9204008FA782 /* Project object */;
|
||||||
|
|
@ -40,6 +51,7 @@
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
0A2D0E5B4C6E445BF971488B /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
|
0A2D0E5B4C6E445BF971488B /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
|
||||||
44568771DD76CFBDF2D1C83D /* Pods-ReCaptcha_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReCaptcha_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ReCaptcha_Tests/Pods-ReCaptcha_Tests.release.xcconfig"; sourceTree = "<group>"; };
|
44568771DD76CFBDF2D1C83D /* Pods-ReCaptcha_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReCaptcha_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ReCaptcha_Tests/Pods-ReCaptcha_Tests.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
4A242A5E18CBBFA095B66558 /* Pods-ReCaptcha_UITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReCaptcha_UITests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ReCaptcha_UITests/Pods-ReCaptcha_UITests.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
4FED8267564AACFFEE83DB15 /* Pods-ReCaptcha_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReCaptcha_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ReCaptcha_Tests/Pods-ReCaptcha_Tests.debug.xcconfig"; sourceTree = "<group>"; };
|
4FED8267564AACFFEE83DB15 /* Pods-ReCaptcha_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReCaptcha_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ReCaptcha_Tests/Pods-ReCaptcha_Tests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
607FACD01AFB9204008FA782 /* ReCaptcha_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ReCaptcha_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
607FACD01AFB9204008FA782 /* ReCaptcha_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ReCaptcha_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
|
@ -49,16 +61,23 @@
|
||||||
607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||||
607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
|
607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
|
||||||
62BEEA62161F672468CCFD64 /* Pods_ReCaptcha_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ReCaptcha_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
62BEEA62161F672468CCFD64 /* Pods_ReCaptcha_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ReCaptcha_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
62C1DD0E80E9920845E5DA51 /* ReCaptcha.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = ReCaptcha.podspec; path = ../ReCaptcha.podspec; sourceTree = "<group>"; };
|
62C1DD0E80E9920845E5DA51 /* ReCaptcha.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = ReCaptcha.podspec; path = ../ReCaptcha.podspec; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
|
||||||
80FF4E03D71AACBD81A36301 /* Pods-ReCaptcha_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReCaptcha_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ReCaptcha_Example/Pods-ReCaptcha_Example.debug.xcconfig"; sourceTree = "<group>"; };
|
80FF4E03D71AACBD81A36301 /* Pods-ReCaptcha_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReCaptcha_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ReCaptcha_Example/Pods-ReCaptcha_Example.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
930BD5ACA20B973070B89ACF /* Pods-ReCaptcha_UITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReCaptcha_UITests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ReCaptcha_UITests/Pods-ReCaptcha_UITests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
9417A28DC340FF0BC1627B3F /* Pods_ReCaptcha_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ReCaptcha_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
9417A28DC340FF0BC1627B3F /* Pods_ReCaptcha_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ReCaptcha_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
B4C17A11F39B6DC940891AE0 /* Pods_ReCaptcha_UITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ReCaptcha_UITests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
F231B39E1FED4A8C00F82943 /* ReCaptchaDecoder+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReCaptchaDecoder+Helper.swift"; sourceTree = "<group>"; };
|
||||||
F288E9441F9537760018688D /* ReCaptchaError+Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReCaptchaError+Equatable.swift"; sourceTree = "<group>"; };
|
F288E9441F9537760018688D /* ReCaptchaError+Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReCaptchaError+Equatable.swift"; sourceTree = "<group>"; };
|
||||||
|
F28FAC9C200E425600E14987 /* ReCaptcha_UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReCaptcha_UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
F28FAC9E200E425600E14987 /* ReCaptcha_UITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReCaptcha_UITests.swift; sourceTree = "<group>"; };
|
||||||
|
F28FACA0200E425600E14987 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
F2AE8611204F3430002E28D7 /* ReCaptchaResult__Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReCaptchaResult__Tests.swift; sourceTree = "<group>"; };
|
||||||
F2E2685D1F7AEE3400CD876D /* ReCaptcha__Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReCaptcha__Tests.swift; sourceTree = "<group>"; };
|
F2E2685D1F7AEE3400CD876D /* ReCaptcha__Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReCaptcha__Tests.swift; sourceTree = "<group>"; };
|
||||||
F2ECCF761E9FC47B0097B199 /* ReCaptcha_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReCaptcha_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
F2ECCF761E9FC47B0097B199 /* ReCaptcha_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReCaptcha_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
F2ECCF7A1E9FC47B0097B199 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
F2ECCF7A1E9FC47B0097B199 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
|
@ -79,6 +98,14 @@
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
F28FAC99200E425600E14987 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
9D85E832734B73CFBD0156E0 /* Pods_ReCaptcha_UITests.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
F2ECCF731E9FC47B0097B199 /* Frameworks */ = {
|
F2ECCF731E9FC47B0097B199 /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
|
@ -96,6 +123,7 @@
|
||||||
607FACF51AFB993E008FA782 /* Podspec Metadata */,
|
607FACF51AFB993E008FA782 /* Podspec Metadata */,
|
||||||
607FACD21AFB9204008FA782 /* Example for ReCaptcha */,
|
607FACD21AFB9204008FA782 /* Example for ReCaptcha */,
|
||||||
F2ECCF771E9FC47B0097B199 /* ReCaptcha_Tests */,
|
F2ECCF771E9FC47B0097B199 /* ReCaptcha_Tests */,
|
||||||
|
F28FAC9D200E425600E14987 /* ReCaptcha_UITests */,
|
||||||
607FACD11AFB9204008FA782 /* Products */,
|
607FACD11AFB9204008FA782 /* Products */,
|
||||||
716E2370DBF48D5E2C86E802 /* Pods */,
|
716E2370DBF48D5E2C86E802 /* Pods */,
|
||||||
FDC29111B59FAB9F0F44DADB /* Frameworks */,
|
FDC29111B59FAB9F0F44DADB /* Frameworks */,
|
||||||
|
|
@ -107,6 +135,7 @@
|
||||||
children = (
|
children = (
|
||||||
607FACD01AFB9204008FA782 /* ReCaptcha_Example.app */,
|
607FACD01AFB9204008FA782 /* ReCaptcha_Example.app */,
|
||||||
F2ECCF761E9FC47B0097B199 /* ReCaptcha_Tests.xctest */,
|
F2ECCF761E9FC47B0097B199 /* ReCaptcha_Tests.xctest */,
|
||||||
|
F28FAC9C200E425600E14987 /* ReCaptcha_UITests.xctest */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -152,10 +181,21 @@
|
||||||
C2A0BDD35B5E219129E9BC65 /* Pods-ReCaptcha_Example.release.xcconfig */,
|
C2A0BDD35B5E219129E9BC65 /* Pods-ReCaptcha_Example.release.xcconfig */,
|
||||||
4FED8267564AACFFEE83DB15 /* Pods-ReCaptcha_Tests.debug.xcconfig */,
|
4FED8267564AACFFEE83DB15 /* Pods-ReCaptcha_Tests.debug.xcconfig */,
|
||||||
44568771DD76CFBDF2D1C83D /* Pods-ReCaptcha_Tests.release.xcconfig */,
|
44568771DD76CFBDF2D1C83D /* Pods-ReCaptcha_Tests.release.xcconfig */,
|
||||||
|
930BD5ACA20B973070B89ACF /* Pods-ReCaptcha_UITests.debug.xcconfig */,
|
||||||
|
4A242A5E18CBBFA095B66558 /* Pods-ReCaptcha_UITests.release.xcconfig */,
|
||||||
);
|
);
|
||||||
name = Pods;
|
name = Pods;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
F28FAC9D200E425600E14987 /* ReCaptcha_UITests */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
F28FAC9E200E425600E14987 /* ReCaptcha_UITests.swift */,
|
||||||
|
F28FACA0200E425600E14987 /* Info.plist */,
|
||||||
|
);
|
||||||
|
path = ReCaptcha_UITests;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
F2ECCF771E9FC47B0097B199 /* ReCaptcha_Tests */ = {
|
F2ECCF771E9FC47B0097B199 /* ReCaptcha_Tests */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
|
@ -175,6 +215,8 @@
|
||||||
F2ECCF8D1E9FE68C0097B199 /* ReCaptchaWebViewManager__Tests.swift */,
|
F2ECCF8D1E9FE68C0097B199 /* ReCaptchaWebViewManager__Tests.swift */,
|
||||||
F2E2685D1F7AEE3400CD876D /* ReCaptcha__Tests.swift */,
|
F2E2685D1F7AEE3400CD876D /* ReCaptcha__Tests.swift */,
|
||||||
F231B3961FEC325A00F82943 /* DispatchQueue__Tests.swift */,
|
F231B3961FEC325A00F82943 /* DispatchQueue__Tests.swift */,
|
||||||
|
F2AE8611204F3430002E28D7 /* ReCaptchaResult__Tests.swift */,
|
||||||
|
F211C22120F7E0B100709B26 /* ReCaptcha_Endpoint__Tests.swift */,
|
||||||
);
|
);
|
||||||
path = Core;
|
path = Core;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -203,6 +245,7 @@
|
||||||
children = (
|
children = (
|
||||||
62BEEA62161F672468CCFD64 /* Pods_ReCaptcha_Example.framework */,
|
62BEEA62161F672468CCFD64 /* Pods_ReCaptcha_Example.framework */,
|
||||||
9417A28DC340FF0BC1627B3F /* Pods_ReCaptcha_Tests.framework */,
|
9417A28DC340FF0BC1627B3F /* Pods_ReCaptcha_Tests.framework */,
|
||||||
|
B4C17A11F39B6DC940891AE0 /* Pods_ReCaptcha_UITests.framework */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -219,7 +262,6 @@
|
||||||
607FACCD1AFB9204008FA782 /* Frameworks */,
|
607FACCD1AFB9204008FA782 /* Frameworks */,
|
||||||
607FACCE1AFB9204008FA782 /* Resources */,
|
607FACCE1AFB9204008FA782 /* Resources */,
|
||||||
8F03FFB3F5C55E873C23C682 /* [CP] Embed Pods Frameworks */,
|
8F03FFB3F5C55E873C23C682 /* [CP] Embed Pods Frameworks */,
|
||||||
ED1C0E07490C9C4B4A401061 /* [CP] Copy Pods Resources */,
|
|
||||||
F231B3981FEC3B7F00F82943 /* SwiftLint */,
|
F231B3981FEC3B7F00F82943 /* SwiftLint */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
|
|
@ -231,6 +273,26 @@
|
||||||
productReference = 607FACD01AFB9204008FA782 /* ReCaptcha_Example.app */;
|
productReference = 607FACD01AFB9204008FA782 /* ReCaptcha_Example.app */;
|
||||||
productType = "com.apple.product-type.application";
|
productType = "com.apple.product-type.application";
|
||||||
};
|
};
|
||||||
|
F28FAC9B200E425600E14987 /* ReCaptcha_UITests */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = F28FACA5200E425600E14987 /* Build configuration list for PBXNativeTarget "ReCaptcha_UITests" */;
|
||||||
|
buildPhases = (
|
||||||
|
B8A66872166B84DAD39A3E1F /* [CP] Check Pods Manifest.lock */,
|
||||||
|
F28FAC98200E425600E14987 /* Sources */,
|
||||||
|
F28FAC99200E425600E14987 /* Frameworks */,
|
||||||
|
F28FAC9A200E425600E14987 /* Resources */,
|
||||||
|
F28FACA6200E447600E14987 /* ShellScript */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
F28FACA2200E425600E14987 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
name = ReCaptcha_UITests;
|
||||||
|
productName = ReCaptcha_UITests;
|
||||||
|
productReference = F28FAC9C200E425600E14987 /* ReCaptcha_UITests.xctest */;
|
||||||
|
productType = "com.apple.product-type.bundle.ui-testing";
|
||||||
|
};
|
||||||
F2ECCF751E9FC47B0097B199 /* ReCaptcha_Tests */ = {
|
F2ECCF751E9FC47B0097B199 /* ReCaptcha_Tests */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = F2ECCF7D1E9FC47B0097B199 /* Build configuration list for PBXNativeTarget "ReCaptcha_Tests" */;
|
buildConfigurationList = F2ECCF7D1E9FC47B0097B199 /* Build configuration list for PBXNativeTarget "ReCaptcha_Tests" */;
|
||||||
|
|
@ -240,7 +302,6 @@
|
||||||
F2ECCF731E9FC47B0097B199 /* Frameworks */,
|
F2ECCF731E9FC47B0097B199 /* Frameworks */,
|
||||||
F2ECCF741E9FC47B0097B199 /* Resources */,
|
F2ECCF741E9FC47B0097B199 /* Resources */,
|
||||||
77003100630E7783A936C451 /* [CP] Embed Pods Frameworks */,
|
77003100630E7783A936C451 /* [CP] Embed Pods Frameworks */,
|
||||||
44209D7CDDDE3A11075B8104 /* [CP] Copy Pods Resources */,
|
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
|
|
@ -258,7 +319,7 @@
|
||||||
607FACC81AFB9204008FA782 /* Project object */ = {
|
607FACC81AFB9204008FA782 /* Project object */ = {
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastSwiftUpdateCheck = 0830;
|
LastSwiftUpdateCheck = 0910;
|
||||||
LastUpgradeCheck = 0900;
|
LastUpgradeCheck = 0900;
|
||||||
ORGANIZATIONNAME = ReCaptcha;
|
ORGANIZATIONNAME = ReCaptcha;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
|
|
@ -266,9 +327,13 @@
|
||||||
CreatedOnToolsVersion = 6.3.1;
|
CreatedOnToolsVersion = 6.3.1;
|
||||||
LastSwiftMigration = 0900;
|
LastSwiftMigration = 0900;
|
||||||
};
|
};
|
||||||
|
F28FAC9B200E425600E14987 = {
|
||||||
|
CreatedOnToolsVersion = 9.1;
|
||||||
|
ProvisioningStyle = Automatic;
|
||||||
|
TestTargetID = 607FACCF1AFB9204008FA782;
|
||||||
|
};
|
||||||
F2ECCF751E9FC47B0097B199 = {
|
F2ECCF751E9FC47B0097B199 = {
|
||||||
CreatedOnToolsVersion = 8.3;
|
CreatedOnToolsVersion = 8.3;
|
||||||
DevelopmentTeam = 58EEZG76L8;
|
|
||||||
LastSwiftMigration = 0900;
|
LastSwiftMigration = 0900;
|
||||||
ProvisioningStyle = Automatic;
|
ProvisioningStyle = Automatic;
|
||||||
TestTargetID = 607FACCF1AFB9204008FA782;
|
TestTargetID = 607FACCF1AFB9204008FA782;
|
||||||
|
|
@ -290,6 +355,7 @@
|
||||||
targets = (
|
targets = (
|
||||||
607FACCF1AFB9204008FA782 /* ReCaptcha_Example */,
|
607FACCF1AFB9204008FA782 /* ReCaptcha_Example */,
|
||||||
F2ECCF751E9FC47B0097B199 /* ReCaptcha_Tests */,
|
F2ECCF751E9FC47B0097B199 /* ReCaptcha_Tests */,
|
||||||
|
F28FAC9B200E425600E14987 /* ReCaptcha_UITests */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
|
|
@ -306,6 +372,13 @@
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
F28FAC9A200E425600E14987 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
F2ECCF741E9FC47B0097B199 /* Resources */ = {
|
F2ECCF741E9FC47B0097B199 /* Resources */ = {
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
|
@ -317,21 +390,6 @@
|
||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXShellScriptBuildPhase 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 */ = {
|
51299F67A8756E2B3EAE411A /* [CP] Check Pods Manifest.lock */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
|
@ -357,11 +415,17 @@
|
||||||
);
|
);
|
||||||
inputPaths = (
|
inputPaths = (
|
||||||
"${SRCROOT}/Pods/Target Support Files/Pods-ReCaptcha_Tests/Pods-ReCaptcha_Tests-frameworks.sh",
|
"${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}/AppSwizzle/AppSwizzle.framework",
|
||||||
|
"${BUILT_PRODUCTS_DIR}/RxBlocking/RxBlocking.framework",
|
||||||
);
|
);
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputPaths = (
|
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}/AppSwizzle.framework",
|
||||||
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxBlocking.framework",
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
|
|
@ -376,14 +440,14 @@
|
||||||
inputPaths = (
|
inputPaths = (
|
||||||
"${SRCROOT}/Pods/Target Support Files/Pods-ReCaptcha_Example/Pods-ReCaptcha_Example-frameworks.sh",
|
"${SRCROOT}/Pods/Target Support Files/Pods-ReCaptcha_Example/Pods-ReCaptcha_Example-frameworks.sh",
|
||||||
"${BUILT_PRODUCTS_DIR}/ReCaptcha/ReCaptcha.framework",
|
"${BUILT_PRODUCTS_DIR}/ReCaptcha/ReCaptcha.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/Result/Result.framework",
|
"${BUILT_PRODUCTS_DIR}/RxAtomic/RxAtomic.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework",
|
"${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework",
|
"${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework",
|
||||||
);
|
);
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputPaths = (
|
outputPaths = (
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReCaptcha.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReCaptcha.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Result.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxAtomic.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework",
|
||||||
);
|
);
|
||||||
|
|
@ -392,6 +456,24 @@
|
||||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ReCaptcha_Example/Pods-ReCaptcha_Example-frameworks.sh\"\n";
|
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ReCaptcha_Example/Pods-ReCaptcha_Example-frameworks.sh\"\n";
|
||||||
showEnvVarsInLog = 0;
|
showEnvVarsInLog = 0;
|
||||||
};
|
};
|
||||||
|
B8A66872166B84DAD39A3E1F /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-ReCaptcha_UITests-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
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;
|
||||||
|
};
|
||||||
DDB47454887253730AB35230 /* [CP] Check Pods Manifest.lock */ = {
|
DDB47454887253730AB35230 /* [CP] Check Pods Manifest.lock */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
|
@ -410,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";
|
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;
|
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 */ = {
|
F231B3981FEC3B7F00F82943 /* SwiftLint */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
|
@ -437,7 +504,20 @@
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/SwiftLint/swiftlint\" --path \"${PROJECT_DIR}/..\"";
|
shellScript = "\"${PODS_ROOT}/SwiftLint/swiftlint\" --path \"${PROJECT_DIR}/..\"\n";
|
||||||
|
};
|
||||||
|
F28FACA6200E447600E14987 /* ShellScript */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "# Unfortuantely, Xcode 7.3 and Cocoapods are not yet fully compatible. The location of the xctest bundle has changed and so we need to manually copy some things ...\n# this fix is necesary - tested on Cocoapods 1.1.rc2 that still have the issue.\n# ver 0.2\n\n# set variables\nBUILD_ROOT=\"${BUILT_PRODUCTS_DIR}\"\n\nMAIN_TARGET_NAME=\"ReCaptcha_Example.app\" #if you know how to get this from here do let me know!\nMAIN_TARGET_FRAMEWORKS=\"${BUILT_PRODUCTS_DIR}/${MAIN_TARGET_NAME}/Frameworks\"\n\nTEST_TARGET_NAME=\"ReCaptcha_UITests\"\nTEST_RUNNER_TARGET_FRAMEWORKS=\"${BUILT_PRODUCTS_DIR}/${TEST_TARGET_NAME}-Runner.app\"\n\n# COPY FRAMEWORKS FROM MAIN TARGET TO TEST TARGET if framework folder exists\nif [[ -d \"${MAIN_TARGET_FRAMEWORKS}\" ]]; then\necho \"**** COPYING FRAMEWORKS FROM \\\"${MAIN_TARGET_FRAMEWORKS}\\\" TO \\\"${TEST_RUNNER_TARGET_FRAMEWORKS}\\\" ...\"\ncp -R \"${MAIN_TARGET_FRAMEWORKS}\" \"${TEST_RUNNER_TARGET_FRAMEWORKS}\"\nfi\n\n# You don't really need this section below if your script works, otherwise it is quite helpful to troubleshoot any problems with paths.\n\n# DEBUG SECTION\necho \"BUILD_ROOT : ${BUILD_ROOT}\"\necho \"MAIN_TARGET_NAME : ${MAIN_TARGET_NAME}\"\necho \"MAIN_TARGET_FRAMEWORKS ${MAIN_TARGET_FRAMEWORKS}\"\necho \"TEST_TARGET_NAME : ${TEST_TARGET_NAME}\"\necho \"TEST_RUNNER_TARGET_FRAMEWORKS : ${TEST_TARGET_FRAMEWORKS}\"\necho \"CONTENT: ${CONTENTS_FOLDER_PATH}\"\necho \"FRAMEWORK PATH: ${FRAMEWORKS_FOLDER_PATH}\"\necho \"TARGET BUILD DIR: ${TARGET_BUILD_DIR}\"";
|
||||||
};
|
};
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
|
@ -451,15 +531,25 @@
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
F28FAC98200E425600E14987 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
F28FAC9F200E425600E14987 /* ReCaptcha_UITests.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
F2ECCF721E9FC47B0097B199 /* Sources */ = {
|
F2ECCF721E9FC47B0097B199 /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
F2AE8612204F3430002E28D7 /* ReCaptchaResult__Tests.swift in Sources */,
|
||||||
F2ECCF961EA00A5B0097B199 /* ReCaptchaWebViewManager+Helpers.swift in Sources */,
|
F2ECCF961EA00A5B0097B199 /* ReCaptchaWebViewManager+Helpers.swift in Sources */,
|
||||||
F2ECCF8E1E9FE68C0097B199 /* ReCaptchaWebViewManager__Tests.swift in Sources */,
|
F2ECCF8E1E9FE68C0097B199 /* ReCaptchaWebViewManager__Tests.swift in Sources */,
|
||||||
F2ECCF981EA011370097B199 /* Result+Helpers.swift in Sources */,
|
F2ECCF981EA011370097B199 /* Result+Helpers.swift in Sources */,
|
||||||
F231B3971FEC325A00F82943 /* DispatchQueue__Tests.swift in Sources */,
|
F231B3971FEC325A00F82943 /* DispatchQueue__Tests.swift in Sources */,
|
||||||
F231B39F1FED4A8C00F82943 /* ReCaptchaDecoder+Helper.swift in Sources */,
|
F231B39F1FED4A8C00F82943 /* ReCaptchaDecoder+Helper.swift in Sources */,
|
||||||
|
F211C22220F7E0B100709B26 /* ReCaptcha_Endpoint__Tests.swift in Sources */,
|
||||||
F2E2685E1F7AEE3400CD876D /* ReCaptcha__Tests.swift in Sources */,
|
F2E2685E1F7AEE3400CD876D /* ReCaptcha__Tests.swift in Sources */,
|
||||||
F2ECCF931EA009360097B199 /* ReCaptcha+Rx__Tests.swift in Sources */,
|
F2ECCF931EA009360097B199 /* ReCaptcha+Rx__Tests.swift in Sources */,
|
||||||
F288E9451F9537760018688D /* ReCaptchaError+Equatable.swift in Sources */,
|
F288E9451F9537760018688D /* ReCaptchaError+Equatable.swift in Sources */,
|
||||||
|
|
@ -470,6 +560,11 @@
|
||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXTargetDependency section */
|
/* Begin PBXTargetDependency section */
|
||||||
|
F28FACA2200E425600E14987 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 607FACCF1AFB9204008FA782 /* ReCaptcha_Example */;
|
||||||
|
targetProxy = F28FACA1200E425600E14987 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
F2ECCF7C1E9FC47B0097B199 /* PBXTargetDependency */ = {
|
F2ECCF7C1E9FC47B0097B199 /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
target = 607FACCF1AFB9204008FA782 /* ReCaptcha_Example */;
|
target = 607FACCF1AFB9204008FA782 /* ReCaptcha_Example */;
|
||||||
|
|
@ -608,7 +703,7 @@
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-Example";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-Example";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||||
SWIFT_VERSION = 4.0;
|
SWIFT_VERSION = 4.2;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
|
@ -624,7 +719,55 @@
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-Example";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-Example";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||||
SWIFT_VERSION = 4.0;
|
SWIFT_VERSION = 4.2;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
F28FACA3200E425600E14987 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 930BD5ACA20B973070B89ACF /* Pods-ReCaptcha_UITests.debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
INFOPLIST_FILE = ReCaptcha_UITests/Info.plist;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = "ReCaptcha.ReCaptcha-UITests";
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
|
SWIFT_VERSION = 4.2;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
TEST_TARGET_NAME = ReCaptcha_Example;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
F28FACA4200E425600E14987 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 4A242A5E18CBBFA095B66558 /* Pods-ReCaptcha_UITests.release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
INFOPLIST_FILE = ReCaptcha_UITests/Info.plist;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
|
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;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
TEST_TARGET_NAME = ReCaptcha_Example;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
|
@ -637,15 +780,15 @@
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
DEVELOPMENT_TEAM = 58EEZG76L8;
|
DEVELOPMENT_TEAM = "";
|
||||||
INFOPLIST_FILE = ReCaptcha_Tests/Info.plist;
|
INFOPLIST_FILE = ReCaptcha_Tests/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
|
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-Tests";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-Tests";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG UNIT_TESTS";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG UNIT_TESTS";
|
||||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||||
SWIFT_VERSION = 4.0;
|
SWIFT_VERSION = 4.2;
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ReCaptcha_Example.app/ReCaptcha_Example";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ReCaptcha_Example.app/ReCaptcha_Example";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
|
|
@ -658,14 +801,14 @@
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
DEVELOPMENT_TEAM = 58EEZG76L8;
|
DEVELOPMENT_TEAM = "";
|
||||||
INFOPLIST_FILE = ReCaptcha_Tests/Info.plist;
|
INFOPLIST_FILE = ReCaptcha_Tests/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
|
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-Tests";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-Tests";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||||
SWIFT_VERSION = 4.0;
|
SWIFT_VERSION = 4.2;
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ReCaptcha_Example.app/ReCaptcha_Example";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ReCaptcha_Example.app/ReCaptcha_Example";
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
|
|
@ -691,6 +834,15 @@
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
|
F28FACA5200E425600E14987 /* Build configuration list for PBXNativeTarget "ReCaptcha_UITests" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
F28FACA3200E425600E14987 /* Debug */,
|
||||||
|
F28FACA4200E425600E14987 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
F2ECCF7D1E9FC47B0097B199 /* Build configuration list for PBXNativeTarget "ReCaptcha_Tests" */ = {
|
F2ECCF7D1E9FC47B0097B199 /* Build configuration list for PBXNativeTarget "ReCaptcha_Tests" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,8 @@
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
language = ""
|
codeCoverageEnabled = "YES"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
codeCoverageEnabled = "YES">
|
|
||||||
<Testables>
|
<Testables>
|
||||||
<TestableReference
|
<TestableReference
|
||||||
skipped = "NO">
|
skipped = "NO">
|
||||||
|
|
@ -54,6 +53,16 @@
|
||||||
ReferencedContainer = "container:ReCaptcha.xcodeproj">
|
ReferencedContainer = "container:ReCaptcha.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</TestableReference>
|
</TestableReference>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "F28FAC9B200E425600E14987"
|
||||||
|
BuildableName = "ReCaptcha_UITests.xctest"
|
||||||
|
BlueprintName = "ReCaptcha_UITests"
|
||||||
|
ReferencedContainer = "container:ReCaptcha.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
</Testables>
|
</Testables>
|
||||||
<MacroExpansion>
|
<MacroExpansion>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
|
|
@ -71,7 +80,6 @@
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
language = ""
|
|
||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
ignoresPersistentStateOnLaunch = "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>
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
// ReCaptcha
|
// ReCaptcha
|
||||||
//
|
//
|
||||||
// Created by Flávio Caetano on 03/22/17.
|
// Created by Flávio Caetano on 03/22/17.
|
||||||
// Copyright © 2017 ReCaptcha. All rights reserved.
|
// Copyright © 2018 ReCaptcha. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
@ -16,7 +16,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
func application(
|
func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
// Override point for customization after application launch.
|
// Override point for customization after application launch.
|
||||||
return true
|
return true
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12120" systemVersion="17A365" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
|
||||||
<device id="retina4_7" orientation="portrait">
|
|
||||||
<adaptation id="fullscreen"/>
|
|
||||||
</device>
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12088"/>
|
|
||||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
@ -16,7 +12,7 @@
|
||||||
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
|
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2017 Flávio Caetano. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
|
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2018 Flávio Caetano. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
|
||||||
<rect key="frame" x="20" y="439" width="441" height="21"/>
|
<rect key="frame" x="20" y="439" width="441" height="21"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12118" systemVersion="16D32" 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">
|
<device id="retina4_7" orientation="portrait">
|
||||||
<adaptation id="fullscreen"/>
|
<adaptation id="fullscreen"/>
|
||||||
</device>
|
</device>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12086"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<scenes>
|
<scenes>
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="RDW-bD-rSo">
|
<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="505" width="275" height="50"/>
|
<rect key="frame" x="50" y="450" width="275" height="50"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="height" constant="50" id="Bt8-Ou-ht2"/>
|
<constraint firstAttribute="height" constant="50" id="Bt8-Ou-ht2"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
|
|
@ -34,33 +34,89 @@
|
||||||
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="jHc-GP-v1Z">
|
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="jHc-GP-v1Z">
|
||||||
<rect key="frame" x="177" y="323" width="20" height="20"/>
|
<rect key="frame" x="177" y="323" width="20" height="20"/>
|
||||||
</activityIndicatorView>
|
</activityIndicatorView>
|
||||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="o6f-cL-1PF">
|
<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="192" width="335" height="283"/>
|
<rect key="frame" x="20" y="247" width="335" height="173"/>
|
||||||
|
<accessibility key="accessibilityConfiguration" identifier="resultLabel"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</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"/>
|
||||||
|
<segments>
|
||||||
|
<segment title="Default Endpoint"/>
|
||||||
|
<segment title="Alternate"/>
|
||||||
|
</segments>
|
||||||
|
<connections>
|
||||||
|
<action selector="didPressEndpointSegmentedControl:" destination="vXZ-lx-hvc" eventType="valueChanged" id="Sdm-dO-doL"/>
|
||||||
|
</connections>
|
||||||
|
</segmentedControl>
|
||||||
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Uyt-0M-CR7">
|
||||||
|
<rect key="frame" x="77" y="616" width="221" height="31"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Force visible captcha" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="awK-8H-OCQ">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="162" height="31"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<nil key="textColor"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="mGh-Ox-cFf">
|
||||||
|
<rect key="frame" x="172" y="0.0" width="51" height="31"/>
|
||||||
|
<accessibility key="accessibilityConfiguration" identifier="Switch"/>
|
||||||
|
</switch>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="awK-8H-OCQ" secondAttribute="bottom" id="0Wx-IJ-Kwg"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="mGh-Ox-cFf" secondAttribute="trailing" id="8Xc-zN-tVW"/>
|
||||||
|
<constraint firstItem="awK-8H-OCQ" firstAttribute="top" secondItem="Uyt-0M-CR7" secondAttribute="top" id="IbZ-YP-G0R"/>
|
||||||
|
<constraint firstItem="mGh-Ox-cFf" firstAttribute="top" secondItem="Uyt-0M-CR7" secondAttribute="top" id="P27-Ua-aX7"/>
|
||||||
|
<constraint firstItem="awK-8H-OCQ" firstAttribute="leading" secondItem="Uyt-0M-CR7" secondAttribute="leading" id="fna-4f-wRD"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="mGh-Ox-cFf" secondAttribute="bottom" id="vnT-b0-5h3"/>
|
||||||
|
<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>
|
</subviews>
|
||||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="o6f-cL-1PF" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leading" constant="20" id="0dV-t0-W0Y"/>
|
<constraint firstItem="o6f-cL-1PF" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leading" constant="20" id="0dV-t0-W0Y"/>
|
||||||
<constraint firstItem="2fi-mo-0CV" firstAttribute="top" secondItem="RDW-bD-rSo" secondAttribute="bottom" constant="112" id="E4K-Vp-ZL2"/>
|
<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="o6f-cL-1PF" firstAttribute="centerY" secondItem="kh9-bI-dsS" secondAttribute="centerY" priority="750" id="NMD-ir-PXe"/>
|
||||||
<constraint firstItem="RDW-bD-rSo" firstAttribute="top" secondItem="o6f-cL-1PF" secondAttribute="bottom" constant="30" id="TZe-z9-MZS"/>
|
<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="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="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="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="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>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
<connections>
|
<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="label" destination="o6f-cL-1PF" id="KQV-3X-RKr"/>
|
||||||
|
<outlet property="localeSegmentedControl" destination="kq2-ci-l1u" id="gL7-du-K6l"/>
|
||||||
<outlet property="spinner" destination="jHc-GP-v1Z" id="gRn-JW-FIK"/>
|
<outlet property="spinner" destination="jHc-GP-v1Z" id="gRn-JW-FIK"/>
|
||||||
|
<outlet property="visibleChallengeSwitch" destination="mGh-Ox-cFf" id="R13-nD-EXL"/>
|
||||||
</connections>
|
</connections>
|
||||||
</viewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="x5A-6p-PRh" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="x5A-6p-PRh" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
|
<point key="canvasLocation" x="117.59999999999999" y="117.39130434782609"/>
|
||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
</document>
|
</document>
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,11 @@
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
"size" : "60x60",
|
"size" : "60x60",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ios-marketing",
|
||||||
|
"size" : "1024x1024",
|
||||||
|
"scale" : "1x"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
|
|
|
||||||
|
|
@ -3,38 +3,61 @@
|
||||||
// ReCaptcha
|
// ReCaptcha
|
||||||
//
|
//
|
||||||
// Created by Flávio Caetano on 03/22/17.
|
// Created by Flávio Caetano on 03/22/17.
|
||||||
// Copyright © 2017 ReCaptcha. All rights reserved.
|
// Copyright © 2018 ReCaptcha. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import ReCaptcha
|
import ReCaptcha
|
||||||
import Result
|
|
||||||
import RxCocoa
|
import RxCocoa
|
||||||
import RxSwift
|
import RxSwift
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
|
||||||
class ViewController: UIViewController {
|
class ViewController: UIViewController {
|
||||||
fileprivate static let webViewTag = 123
|
private struct Constants {
|
||||||
|
static let webViewTag = 123
|
||||||
|
static let testLabelTag = 321
|
||||||
|
}
|
||||||
|
|
||||||
// swiftlint:disable:next force_try
|
private var recaptcha: ReCaptcha!
|
||||||
fileprivate let recaptcha = try! ReCaptcha()
|
private var disposeBag = DisposeBag()
|
||||||
fileprivate var disposeBag = DisposeBag()
|
|
||||||
|
|
||||||
@IBOutlet weak var label: UILabel!
|
private var locale: Locale?
|
||||||
@IBOutlet weak var spinner: UIActivityIndicatorView!
|
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 visibleChallengeSwitch: UISwitch!
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
setupReCaptcha()
|
||||||
recaptcha.configureWebView { [weak self] webview in
|
|
||||||
webview.frame = self?.view.bounds ?? CGRect.zero
|
|
||||||
webview.tag = ViewController.webViewTag
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@IBAction func didPressEndpointSegmentedControl(_ sender: UISegmentedControl) {
|
||||||
|
label.text = ""
|
||||||
|
switch sender.selectedSegmentIndex {
|
||||||
|
case 0: endpoint = .default
|
||||||
|
case 1: endpoint = .alternate
|
||||||
|
default: assertionFailure("invalid index")
|
||||||
|
}
|
||||||
|
|
||||||
@IBAction func didPressButton(button: UIButton) {
|
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) {
|
||||||
disposeBag = DisposeBag()
|
disposeBag = DisposeBag()
|
||||||
|
|
||||||
let validate = recaptcha.rx.validate(on: view)
|
let validate = recaptcha.rx.validate(on: view)
|
||||||
|
|
@ -44,20 +67,28 @@ class ViewController: UIViewController {
|
||||||
let isLoading = validate
|
let isLoading = validate
|
||||||
.map { _ in false }
|
.map { _ in false }
|
||||||
.startWith(true)
|
.startWith(true)
|
||||||
|
.share(replay: 1)
|
||||||
|
|
||||||
isLoading
|
isLoading
|
||||||
.bind(to: spinner.rx.isAnimating)
|
.bind(to: spinner.rx.isAnimating)
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
isLoading
|
let isEnabled = isLoading
|
||||||
.map { !$0 }
|
.map { !$0 }
|
||||||
.catchErrorJustReturn(false)
|
.catchErrorJustReturn(false)
|
||||||
|
.share(replay: 1)
|
||||||
|
|
||||||
|
isEnabled
|
||||||
.bind(to: button.rx.isEnabled)
|
.bind(to: button.rx.isEnabled)
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
|
isEnabled
|
||||||
|
.bind(to: endpointSegmentedControl.rx.isEnabled)
|
||||||
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
validate
|
validate
|
||||||
.map { [weak self] _ in
|
.map { [weak self] _ in
|
||||||
self?.view.viewWithTag(ViewController.webViewTag)
|
self?.view.viewWithTag(Constants.webViewTag)
|
||||||
}
|
}
|
||||||
.subscribe(onNext: { subview in
|
.subscribe(onNext: { subview in
|
||||||
subview?.removeFromSuperview()
|
subview?.removeFromSuperview()
|
||||||
|
|
@ -65,8 +96,31 @@ class ViewController: UIViewController {
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
validate
|
validate
|
||||||
.map { try $0.dematerialize() }
|
|
||||||
.bind(to: label.rx.text)
|
.bind(to: label.rx.text)
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
|
visibleChallengeSwitch.rx.value
|
||||||
|
.subscribe(onNext: { [weak recaptcha] value in
|
||||||
|
recaptcha?.forceVisibleChallenge = value
|
||||||
|
})
|
||||||
|
.disposed(by: disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupReCaptcha() {
|
||||||
|
// swiftlint:disable:next force_try
|
||||||
|
recaptcha = try! ReCaptcha(endpoint: endpoint, locale: locale)
|
||||||
|
|
||||||
|
recaptcha.configureWebView { [weak self] webview in
|
||||||
|
webview.frame = self?.view.bounds ?? CGRect.zero
|
||||||
|
webview.tag = Constants.webViewTag
|
||||||
|
|
||||||
|
// For testing purposes
|
||||||
|
// If the webview requires presentation, this should work as a way of detecting the webview in UI tests
|
||||||
|
self?.view.viewWithTag(Constants.testLabelTag)?.removeFromSuperview()
|
||||||
|
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
|
||||||
|
label.tag = Constants.testLabelTag
|
||||||
|
label.accessibilityLabel = "webview"
|
||||||
|
self?.view.addSubview(label)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,7 @@ disabled_rules:
|
||||||
- nesting
|
- nesting
|
||||||
- force_unwrapping
|
- force_unwrapping
|
||||||
- explicit_top_level_acl
|
- explicit_top_level_acl
|
||||||
|
- function_body_length
|
||||||
|
- identifier_name
|
||||||
|
- file_length
|
||||||
|
- type_body_length
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
// ReCaptcha
|
// ReCaptcha
|
||||||
//
|
//
|
||||||
// Created by Flávio Caetano on 21/12/17.
|
// Created by Flávio Caetano on 21/12/17.
|
||||||
// Copyright © 2017 ReCaptcha. All rights reserved.
|
// Copyright © 2018 ReCaptcha. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
@testable import ReCaptcha
|
@testable import ReCaptcha
|
||||||
|
|
@ -20,6 +20,8 @@ class DispatchQueue__Tests: XCTestCase {
|
||||||
super.tearDown()
|
super.tearDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Throttle
|
||||||
|
|
||||||
func test__Throttle_Nil_Context() {
|
func test__Throttle_Nil_Context() {
|
||||||
// Execute closure called once
|
// Execute closure called once
|
||||||
let exp0 = expectation(description: "did call single closure")
|
let exp0 = expectation(description: "did call single closure")
|
||||||
|
|
@ -31,14 +33,15 @@ class DispatchQueue__Tests: XCTestCase {
|
||||||
waitForExpectations(timeout: 1)
|
waitForExpectations(timeout: 1)
|
||||||
|
|
||||||
// Does not execute first closure
|
// Does not execute first closure
|
||||||
let exp1 = expectation(description: "")
|
let exp1 = expectation(description: "did call last closure")
|
||||||
DispatchQueue.main.throttle(deadline: .now() + 0.1) {
|
DispatchQueue.main.throttle(deadline: .now() + 0.1) {
|
||||||
XCTFail("Shouldn't be called")
|
XCTFail("Shouldn't be called")
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.throttle(deadline: .now() + 0.1) {
|
DispatchQueue.main.throttle(
|
||||||
exp1.fulfill()
|
deadline: .now() + 0.1,
|
||||||
}
|
action: exp1.fulfill
|
||||||
|
)
|
||||||
|
|
||||||
waitForExpectations(timeout: 1)
|
waitForExpectations(timeout: 1)
|
||||||
}
|
}
|
||||||
|
|
@ -48,9 +51,11 @@ class DispatchQueue__Tests: XCTestCase {
|
||||||
let exp0 = expectation(description: "did call single closure")
|
let exp0 = expectation(description: "did call single closure")
|
||||||
let c0 = UUID()
|
let c0 = UUID()
|
||||||
|
|
||||||
DispatchQueue.main.throttle(deadline: .now() + 0.1, context: c0) {
|
DispatchQueue.main.throttle(
|
||||||
exp0.fulfill()
|
deadline: .now() + 0.1,
|
||||||
}
|
context: c0,
|
||||||
|
action: exp0.fulfill
|
||||||
|
)
|
||||||
|
|
||||||
waitForExpectations(timeout: 1)
|
waitForExpectations(timeout: 1)
|
||||||
|
|
||||||
|
|
@ -61,17 +66,132 @@ class DispatchQueue__Tests: XCTestCase {
|
||||||
XCTFail("Shouldn't be called")
|
XCTFail("Shouldn't be called")
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.throttle(deadline: .now() + 0.1, context: c1) {
|
DispatchQueue.main.throttle(
|
||||||
exp1.fulfill()
|
deadline: .now() + 0.1,
|
||||||
}
|
context: c1,
|
||||||
|
action: exp1.fulfill
|
||||||
|
)
|
||||||
|
|
||||||
// Execute in a different context
|
// Execute in a different context
|
||||||
let exp2 = expectation(description: "execute on different context")
|
let exp2 = expectation(description: "execute on different context")
|
||||||
let c2 = UUID()
|
let c2 = UUID()
|
||||||
DispatchQueue.main.throttle(deadline: .now() + 0.1, context: c2) {
|
DispatchQueue.main.throttle(
|
||||||
exp2.fulfill()
|
deadline: .now() + 0.1,
|
||||||
}
|
context: c2,
|
||||||
|
action: exp2.fulfill
|
||||||
|
)
|
||||||
|
|
||||||
waitForExpectations(timeout: 1)
|
waitForExpectations(timeout: 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Debounce
|
||||||
|
|
||||||
|
func test__Debounce_Nil_Context() {
|
||||||
|
// Does not execute sequenced closures
|
||||||
|
let exp0 = expectation(description: "did call first closure")
|
||||||
|
|
||||||
|
DispatchQueue.main.debounce(
|
||||||
|
interval: 0.1,
|
||||||
|
action: exp0.fulfill
|
||||||
|
)
|
||||||
|
|
||||||
|
DispatchQueue.main.debounce(interval: 0) {
|
||||||
|
XCTFail("Shouldn't be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForExpectations(timeout: 1)
|
||||||
|
|
||||||
|
// Executes closure after previous has timed out
|
||||||
|
let exp1 = expectation(description: "did call closure")
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||||
|
DispatchQueue.main.debounce(
|
||||||
|
interval: 0.1,
|
||||||
|
action: exp1.fulfill
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForExpectations(timeout: 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func test__Debounce_Context() {
|
||||||
|
// Does not execute sequenced closures
|
||||||
|
let exp0 = expectation(description: "did call first closure")
|
||||||
|
let c0 = UUID()
|
||||||
|
|
||||||
|
DispatchQueue.main.debounce(
|
||||||
|
interval: 0.1,
|
||||||
|
context: c0,
|
||||||
|
action: exp0.fulfill
|
||||||
|
)
|
||||||
|
|
||||||
|
DispatchQueue.main.debounce(interval: 0, context: c0) {
|
||||||
|
XCTFail("Shouldn't be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute in a different context
|
||||||
|
let c1 = UUID()
|
||||||
|
let exp1 = expectation(description: "executes in different context")
|
||||||
|
DispatchQueue.main.debounce(
|
||||||
|
interval: 0,
|
||||||
|
context: c1,
|
||||||
|
action: exp1.fulfill
|
||||||
|
)
|
||||||
|
|
||||||
|
waitForExpectations(timeout: 1)
|
||||||
|
|
||||||
|
// Executes closure after previous has timed out
|
||||||
|
let exp2 = expectation(description: "did call closure")
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
||||||
|
DispatchQueue.main.debounce(
|
||||||
|
interval: 0.1,
|
||||||
|
context: c0,
|
||||||
|
action: exp2.fulfill
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
// ReCaptcha
|
// ReCaptcha
|
||||||
//
|
//
|
||||||
// Created by Flávio Caetano on 13/04/17.
|
// Created by Flávio Caetano on 13/04/17.
|
||||||
// Copyright © 2017 ReCaptcha. All rights reserved.
|
// Copyright © 2018 ReCaptcha. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
@testable import ReCaptcha
|
@testable import ReCaptcha
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
//
|
||||||
|
// ReCaptchaResult__Tests.swift
|
||||||
|
// ReCaptcha
|
||||||
|
//
|
||||||
|
// Created by Flávio Caetano on 06/03/18.
|
||||||
|
// Copyright © 2018 ReCaptcha. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
@testable import ReCaptcha
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
|
||||||
|
class ReCaptchaResult__Tests: XCTestCase {
|
||||||
|
func test__Get_Token() {
|
||||||
|
let token = UUID().uuidString
|
||||||
|
let result = ReCaptchaResult.token(token)
|
||||||
|
|
||||||
|
do {
|
||||||
|
let value = try result.dematerialize()
|
||||||
|
XCTAssertEqual(value, token)
|
||||||
|
}
|
||||||
|
catch let err {
|
||||||
|
XCTFail(err.localizedDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func test__Get_Token__Error() {
|
||||||
|
let error = ReCaptchaError.random()
|
||||||
|
let result = ReCaptchaResult.error(error)
|
||||||
|
|
||||||
|
do {
|
||||||
|
_ = try result.dematerialize()
|
||||||
|
XCTFail("Shouldn't have completed")
|
||||||
|
}
|
||||||
|
catch let err {
|
||||||
|
XCTAssertEqual(err as? ReCaptchaError, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,12 +3,11 @@
|
||||||
// ReCaptcha
|
// ReCaptcha
|
||||||
//
|
//
|
||||||
// Created by Flávio Caetano on 13/04/17.
|
// Created by Flávio Caetano on 13/04/17.
|
||||||
// Copyright © 2017 ReCaptcha. All rights reserved.
|
// Copyright © 2018 ReCaptcha. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
@testable import ReCaptcha
|
@testable import ReCaptcha
|
||||||
|
|
||||||
import Result
|
|
||||||
import WebKit
|
import WebKit
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
|
|
@ -36,7 +35,7 @@ class ReCaptchaWebViewManager__Tests: XCTestCase {
|
||||||
|
|
||||||
func test__Validate__Token() {
|
func test__Validate__Token() {
|
||||||
let exp1 = expectation(description: "load token")
|
let exp1 = expectation(description: "load token")
|
||||||
var result1: ReCaptchaWebViewManager.Response?
|
var result1: ReCaptchaResult?
|
||||||
|
|
||||||
// Validate
|
// Validate
|
||||||
let manager = ReCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey)
|
let manager = ReCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey)
|
||||||
|
|
@ -55,12 +54,12 @@ class ReCaptchaWebViewManager__Tests: XCTestCase {
|
||||||
// Verify
|
// Verify
|
||||||
XCTAssertNotNil(result1)
|
XCTAssertNotNil(result1)
|
||||||
XCTAssertNil(result1?.error)
|
XCTAssertNil(result1?.error)
|
||||||
XCTAssertEqual(result1?.value, apiKey)
|
XCTAssertEqual(result1?.token, apiKey)
|
||||||
|
|
||||||
|
|
||||||
// Validate again
|
// Validate again
|
||||||
let exp2 = expectation(description: "reload token")
|
let exp2 = expectation(description: "reload token")
|
||||||
var result2: ReCaptchaWebViewManager.Response?
|
var result2: ReCaptchaResult?
|
||||||
|
|
||||||
// Validate
|
// Validate
|
||||||
manager.validate(on: presenterView) { response in
|
manager.validate(on: presenterView) { response in
|
||||||
|
|
@ -74,7 +73,7 @@ class ReCaptchaWebViewManager__Tests: XCTestCase {
|
||||||
// Verify
|
// Verify
|
||||||
XCTAssertNotNil(result2)
|
XCTAssertNotNil(result2)
|
||||||
XCTAssertNil(result2?.error)
|
XCTAssertNil(result2?.error)
|
||||||
XCTAssertEqual(result2?.value, apiKey)
|
XCTAssertEqual(result2?.token, apiKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -96,7 +95,7 @@ class ReCaptchaWebViewManager__Tests: XCTestCase {
|
||||||
|
|
||||||
|
|
||||||
func test__Validate__Message_Error() {
|
func test__Validate__Message_Error() {
|
||||||
var result: ReCaptchaWebViewManager.Response?
|
var result: ReCaptchaResult?
|
||||||
let exp = expectation(description: "message error")
|
let exp = expectation(description: "message error")
|
||||||
|
|
||||||
// Validate
|
// Validate
|
||||||
|
|
@ -105,7 +104,7 @@ class ReCaptchaWebViewManager__Tests: XCTestCase {
|
||||||
XCTFail("should not ask to configure the webview")
|
XCTFail("should not ask to configure the webview")
|
||||||
}
|
}
|
||||||
|
|
||||||
manager.validate(on: presenterView) { response in
|
manager.validate(on: presenterView, resetOnError: false) { response in
|
||||||
result = response
|
result = response
|
||||||
exp.fulfill()
|
exp.fulfill()
|
||||||
}
|
}
|
||||||
|
|
@ -115,11 +114,11 @@ class ReCaptchaWebViewManager__Tests: XCTestCase {
|
||||||
// Verify
|
// Verify
|
||||||
XCTAssertNotNil(result)
|
XCTAssertNotNil(result)
|
||||||
XCTAssertEqual(result?.error, .wrongMessageFormat)
|
XCTAssertEqual(result?.error, .wrongMessageFormat)
|
||||||
XCTAssertNil(result?.value)
|
XCTAssertNil(result?.token)
|
||||||
}
|
}
|
||||||
|
|
||||||
func test__Validate__JS_Error() {
|
func test__Validate__JS_Error() {
|
||||||
var result: ReCaptchaWebViewManager.Response?
|
var result: ReCaptchaResult?
|
||||||
let exp = expectation(description: "js error")
|
let exp = expectation(description: "js error")
|
||||||
|
|
||||||
// Validate
|
// Validate
|
||||||
|
|
@ -128,7 +127,7 @@ class ReCaptchaWebViewManager__Tests: XCTestCase {
|
||||||
XCTFail("should not ask to configure the webview")
|
XCTFail("should not ask to configure the webview")
|
||||||
}
|
}
|
||||||
|
|
||||||
manager.validate(on: presenterView) { response in
|
manager.validate(on: presenterView, resetOnError: false) { response in
|
||||||
result = response
|
result = response
|
||||||
exp.fulfill()
|
exp.fulfill()
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +137,7 @@ class ReCaptchaWebViewManager__Tests: XCTestCase {
|
||||||
// Verify
|
// Verify
|
||||||
XCTAssertNotNil(result)
|
XCTAssertNotNil(result)
|
||||||
XCTAssertNotNil(result?.error)
|
XCTAssertNotNil(result?.error)
|
||||||
XCTAssertNil(result?.value)
|
XCTAssertNil(result?.token)
|
||||||
|
|
||||||
switch result!.error! {
|
switch result!.error! {
|
||||||
case .unexpected(let error as NSError):
|
case .unexpected(let error as NSError):
|
||||||
|
|
@ -183,6 +182,62 @@ class ReCaptchaWebViewManager__Tests: XCTestCase {
|
||||||
waitForExpectations(timeout: 10)
|
waitForExpectations(timeout: 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func test__Configure_Web_View__Called_Once() {
|
||||||
|
var count = 0
|
||||||
|
let exp0 = expectation(description: "configure webview")
|
||||||
|
|
||||||
|
// Configure WebView
|
||||||
|
let manager = ReCaptchaWebViewManager(messageBody: "{action: \"showReCaptcha\"}")
|
||||||
|
manager.configureWebView { _ in
|
||||||
|
if count < 3 {
|
||||||
|
manager.webView.evaluateJavaScript("execute();") { XCTAssertNil($1) }
|
||||||
|
}
|
||||||
|
|
||||||
|
count += 1
|
||||||
|
exp0.fulfill()
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.validate(on: presenterView) { _ in
|
||||||
|
XCTFail("should not call completion")
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForExpectations(timeout: 10)
|
||||||
|
|
||||||
|
let exp1 = expectation(description: "waiting for extra calls")
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: exp1.fulfill)
|
||||||
|
waitForExpectations(timeout: 2)
|
||||||
|
|
||||||
|
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
|
// MARK: Stop
|
||||||
|
|
||||||
func test__Stop() {
|
func test__Stop() {
|
||||||
|
|
@ -210,7 +265,7 @@ class ReCaptchaWebViewManager__Tests: XCTestCase {
|
||||||
|
|
||||||
func test__Key_Setup() {
|
func test__Key_Setup() {
|
||||||
let exp = expectation(description: "setup key")
|
let exp = expectation(description: "setup key")
|
||||||
var result: ReCaptchaWebViewManager.Response?
|
var result: ReCaptchaResult?
|
||||||
|
|
||||||
// Validate
|
// Validate
|
||||||
let manager = ReCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey)
|
let manager = ReCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey)
|
||||||
|
|
@ -227,13 +282,13 @@ class ReCaptchaWebViewManager__Tests: XCTestCase {
|
||||||
|
|
||||||
XCTAssertNotNil(result)
|
XCTAssertNotNil(result)
|
||||||
XCTAssertNil(result?.error)
|
XCTAssertNil(result?.error)
|
||||||
XCTAssertEqual(result?.value, apiKey)
|
XCTAssertEqual(result?.token, apiKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func test__Endpoint_Setup() {
|
func test__Endpoint_Setup() {
|
||||||
let exp = expectation(description: "setup endpoint")
|
let exp = expectation(description: "setup endpoint")
|
||||||
let endpoint = String(describing: arc4random())
|
let endpoint = ReCaptcha.Endpoint.alternate.getURL(locale: nil)
|
||||||
var result: ReCaptchaWebViewManager.Response?
|
var result: ReCaptchaResult?
|
||||||
|
|
||||||
let manager = ReCaptchaWebViewManager(messageBody: "{token: endpoint}", endpoint: endpoint)
|
let manager = ReCaptchaWebViewManager(messageBody: "{token: endpoint}", endpoint: endpoint)
|
||||||
manager.configureWebView { _ in
|
manager.configureWebView { _ in
|
||||||
|
|
@ -249,6 +304,98 @@ class ReCaptchaWebViewManager__Tests: XCTestCase {
|
||||||
|
|
||||||
XCTAssertNotNil(result)
|
XCTAssertNotNil(result)
|
||||||
XCTAssertNil(result?.error)
|
XCTAssertNil(result?.error)
|
||||||
XCTAssertEqual(result?.value, endpoint)
|
XCTAssertEqual(result?.token, endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Reset
|
||||||
|
|
||||||
|
func test__Reset() {
|
||||||
|
let exp1 = expectation(description: "fail on first execution")
|
||||||
|
var result1: ReCaptchaResult?
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
let manager = ReCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey, shouldFail: true)
|
||||||
|
manager.configureWebView { _ in
|
||||||
|
XCTFail("should not ask to configure the webview")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error
|
||||||
|
manager.validate(on: presenterView, resetOnError: false) { result in
|
||||||
|
result1 = result
|
||||||
|
exp1.fulfill()
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForExpectations(timeout: 10)
|
||||||
|
XCTAssertEqual(result1?.error, .wrongMessageFormat)
|
||||||
|
|
||||||
|
// Resets and tries again
|
||||||
|
let exp2 = expectation(description: "validates after reset")
|
||||||
|
var result2: ReCaptchaResult?
|
||||||
|
|
||||||
|
manager.reset()
|
||||||
|
manager.validate(on: presenterView, resetOnError: false) { result in
|
||||||
|
result2 = result
|
||||||
|
exp2.fulfill()
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForExpectations(timeout: 10)
|
||||||
|
|
||||||
|
XCTAssertNil(result2?.error)
|
||||||
|
XCTAssertEqual(result2?.token, apiKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func test__Validate__Reset_On_Error() {
|
||||||
|
let exp = expectation(description: "fail on first execution")
|
||||||
|
var result: ReCaptchaResult?
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
let manager = ReCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey, shouldFail: true)
|
||||||
|
manager.configureWebView { _ in
|
||||||
|
XCTFail("should not ask to configure the webview")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error
|
||||||
|
manager.validate(on: presenterView, resetOnError: true) { response in
|
||||||
|
result = response
|
||||||
|
exp.fulfill()
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForExpectations(timeout: 10)
|
||||||
|
|
||||||
|
XCTAssertNil(result?.error)
|
||||||
|
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() {
|
||||||
|
let manager = ReCaptchaWebViewManager()
|
||||||
|
|
||||||
|
// Initial value
|
||||||
|
XCTAssertFalse(manager.forceVisibleChallenge)
|
||||||
|
|
||||||
|
// Set True
|
||||||
|
manager.forceVisibleChallenge = true
|
||||||
|
XCTAssertEqual(manager.webView.customUserAgent, "Googlebot/2.1")
|
||||||
|
|
||||||
|
// Set False
|
||||||
|
manager.forceVisibleChallenge = false
|
||||||
|
XCTAssertNotEqual(manager.webView.customUserAgent?.isEmpty, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
// ReCaptcha
|
// ReCaptcha
|
||||||
//
|
//
|
||||||
// Created by Flávio Caetano on 26/09/17.
|
// Created by Flávio Caetano on 26/09/17.
|
||||||
// Copyright © 2017 ReCaptcha. All rights reserved.
|
// Copyright © 2018 ReCaptcha. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import AppSwizzle
|
import AppSwizzle
|
||||||
|
|
@ -26,7 +26,6 @@ class ReCaptcha__Tests: XCTestCase {
|
||||||
toAlterSelector: #selector(Bundle.failHTMLLoad(_:type:))
|
toAlterSelector: #selector(Bundle.failHTMLLoad(_:type:))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
_ = try ReCaptcha()
|
_ = try ReCaptcha()
|
||||||
XCTFail("Should have failed")
|
XCTFail("Should have failed")
|
||||||
|
|
@ -57,16 +56,28 @@ class ReCaptcha__Tests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensures plist url if nil key
|
// Ensures plist url if nil key
|
||||||
let plistURL = URL(string: "bar")!
|
let plistURL = URL(string: "https://bar")!
|
||||||
let config1 = try? ReCaptcha.Config(apiKey: "", infoPlistKey: nil, baseURL: nil, infoPlistURL: plistURL)
|
let config1 = try? ReCaptcha.Config(apiKey: "", infoPlistKey: nil, baseURL: nil, infoPlistURL: plistURL)
|
||||||
XCTAssertEqual(config1?.baseURL, plistURL)
|
XCTAssertEqual(config1?.baseURL, plistURL)
|
||||||
|
|
||||||
// Ensures preference of given url over plist entry
|
// Ensures preference of given url over plist entry
|
||||||
let url = URL(string: "foo")!
|
let url = URL(string: "ftp://foo")!
|
||||||
let config2 = try? ReCaptcha.Config(apiKey: "", infoPlistKey: nil, baseURL: url, infoPlistURL: plistURL)
|
let config2 = try? ReCaptcha.Config(apiKey: "", infoPlistKey: nil, baseURL: url, infoPlistURL: plistURL)
|
||||||
XCTAssertEqual(config2?.baseURL, url)
|
XCTAssertEqual(config2?.baseURL, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func test__Base_URL_Without_Scheme() {
|
||||||
|
// Ignores URL with scheme
|
||||||
|
let goodURL = URL(string: "https://foo.bar")!
|
||||||
|
let config0 = try? ReCaptcha.Config(apiKey: "", infoPlistKey: nil, baseURL: goodURL, infoPlistURL: nil)
|
||||||
|
XCTAssertEqual(config0?.baseURL, goodURL)
|
||||||
|
|
||||||
|
// Fixes URL without scheme
|
||||||
|
let badURL = URL(string: "foo")!
|
||||||
|
let config = try? ReCaptcha.Config(apiKey: "", infoPlistKey: nil, baseURL: badURL, infoPlistURL: nil)
|
||||||
|
XCTAssertEqual(config?.baseURL.absoluteString, "http://" + badURL.absoluteString)
|
||||||
|
}
|
||||||
|
|
||||||
func test__API_Key() {
|
func test__API_Key() {
|
||||||
// Ensures key failure when nil
|
// Ensures key failure when nil
|
||||||
do {
|
do {
|
||||||
|
|
@ -99,6 +110,17 @@ class ReCaptcha__Tests: XCTestCase {
|
||||||
)
|
)
|
||||||
XCTAssertEqual(config2?.apiKey, key)
|
XCTAssertEqual(config2?.apiKey, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func test__Force_Visible_Challenge() {
|
||||||
|
let recaptcha = ReCaptcha(manager: ReCaptchaWebViewManager())
|
||||||
|
|
||||||
|
// Initial value
|
||||||
|
XCTAssertFalse(recaptcha.forceVisibleChallenge)
|
||||||
|
|
||||||
|
// Set true
|
||||||
|
recaptcha.forceVisibleChallenge = true
|
||||||
|
XCTAssertTrue(recaptcha.forceVisibleChallenge)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
// ReCaptcha
|
// ReCaptcha
|
||||||
//
|
//
|
||||||
// Created by Flávio Caetano on 22/12/17.
|
// Created by Flávio Caetano on 22/12/17.
|
||||||
// Copyright © 2017 ReCaptcha. All rights reserved.
|
// Copyright © 2018 ReCaptcha. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
// ReCaptcha
|
// ReCaptcha
|
||||||
//
|
//
|
||||||
// Created by Flávio Caetano on 16/10/17.
|
// Created by Flávio Caetano on 16/10/17.
|
||||||
// Copyright © 2017 ReCaptcha. All rights reserved.
|
// Copyright © 2018 ReCaptcha. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
|
||||||
|
|
@ -3,26 +3,48 @@
|
||||||
// ReCaptcha
|
// ReCaptcha
|
||||||
//
|
//
|
||||||
// Created by Flávio Caetano on 13/04/17.
|
// Created by Flávio Caetano on 13/04/17.
|
||||||
// Copyright © 2017 ReCaptcha. All rights reserved.
|
// Copyright © 2018 ReCaptcha. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
@testable import ReCaptcha
|
@testable import ReCaptcha
|
||||||
|
import WebKit
|
||||||
|
|
||||||
extension ReCaptchaWebViewManager {
|
extension ReCaptchaWebViewManager {
|
||||||
convenience init(messageBody: String, apiKey: String? = nil, endpoint: String? = nil) {
|
private static let unformattedHTML: String! = {
|
||||||
let localhost = URL(string: "http://localhost")!
|
Bundle(for: ReCaptchaWebViewManager__Tests.self)
|
||||||
let html = Bundle(for: ReCaptchaWebViewManager__Tests.self)
|
|
||||||
.path(forResource: "mock", ofType: "html")
|
.path(forResource: "mock", ofType: "html")
|
||||||
.flatMap { try? String(contentsOfFile: $0) }
|
.flatMap { try? String(contentsOfFile: $0) }
|
||||||
.map { String(format: $0, arguments: ["message": messageBody]) }
|
}()
|
||||||
|
|
||||||
|
convenience init(
|
||||||
|
messageBody: String = "",
|
||||||
|
apiKey: String? = nil,
|
||||||
|
endpoint: String? = nil,
|
||||||
|
shouldFail: Bool = false
|
||||||
|
) {
|
||||||
|
let localhost = URL(string: "http://localhost")!
|
||||||
|
let html = String(format: ReCaptchaWebViewManager.unformattedHTML, arguments: [
|
||||||
|
"message": messageBody,
|
||||||
|
"shouldFail": shouldFail.description
|
||||||
|
])
|
||||||
|
|
||||||
self.init(
|
self.init(
|
||||||
html: html!,
|
html: html,
|
||||||
apiKey: apiKey ?? String(arc4random()),
|
apiKey: apiKey ?? String(arc4random()),
|
||||||
baseURL: localhost,
|
baseURL: localhost,
|
||||||
endpoint: endpoint ?? localhost.absoluteString
|
endpoint: endpoint ?? localhost.absoluteString
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func configureWebView(_ configure: @escaping (WKWebView) -> Void) {
|
||||||
|
configureWebView = configure
|
||||||
|
}
|
||||||
|
|
||||||
|
func validate(on view: UIView, resetOnError: Bool = true, completion: @escaping (ReCaptchaResult) -> Void) {
|
||||||
|
self.shouldResetOnError = resetOnError
|
||||||
|
self.completion = completion
|
||||||
|
|
||||||
|
validate(on: view)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,20 +3,20 @@
|
||||||
// ReCaptcha
|
// ReCaptcha
|
||||||
//
|
//
|
||||||
// Created by Flávio Caetano on 13/04/17.
|
// Created by Flávio Caetano on 13/04/17.
|
||||||
// Copyright © 2017 ReCaptcha. All rights reserved.
|
// Copyright © 2018 ReCaptcha. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Result
|
@testable import ReCaptcha
|
||||||
|
|
||||||
|
|
||||||
extension Result {
|
extension ReCaptchaResult {
|
||||||
var value: T? {
|
var token: String? {
|
||||||
guard case .success(let value) = self else { return nil }
|
guard case .token(let value) = self else { return nil }
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
var error: Error? {
|
var error: ReCaptchaError? {
|
||||||
guard case .failure(let error) = self else { return nil }
|
guard case .error(let error) = self else { return nil }
|
||||||
return error
|
return error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,31 +3,30 @@
|
||||||
// ReCaptcha
|
// ReCaptcha
|
||||||
//
|
//
|
||||||
// Created by Flávio Caetano on 13/04/17.
|
// Created by Flávio Caetano on 13/04/17.
|
||||||
// Copyright © 2017 ReCaptcha. All rights reserved.
|
// Copyright © 2018 ReCaptcha. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
@testable import ReCaptcha
|
@testable import ReCaptcha
|
||||||
|
|
||||||
|
import RxBlocking
|
||||||
|
import RxCocoa
|
||||||
import RxSwift
|
import RxSwift
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
|
|
||||||
class ReCaptcha_Rx__Tests: XCTestCase {
|
class ReCaptcha_Rx__Tests: XCTestCase {
|
||||||
|
|
||||||
fileprivate var disposeBag: DisposeBag!
|
|
||||||
fileprivate var apiKey: String!
|
fileprivate var apiKey: String!
|
||||||
fileprivate var presenterView: UIView!
|
fileprivate var presenterView: UIView!
|
||||||
|
|
||||||
override func setUp() {
|
override func setUp() {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
|
|
||||||
disposeBag = DisposeBag()
|
|
||||||
presenterView = UIApplication.shared.keyWindow!
|
presenterView = UIApplication.shared.keyWindow!
|
||||||
apiKey = String(arc4random())
|
apiKey = String(arc4random())
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tearDown() {
|
override func tearDown() {
|
||||||
disposeBag = nil
|
|
||||||
presenterView = nil
|
presenterView = nil
|
||||||
apiKey = nil
|
apiKey = nil
|
||||||
|
|
||||||
|
|
@ -36,102 +35,70 @@ class ReCaptcha_Rx__Tests: XCTestCase {
|
||||||
|
|
||||||
|
|
||||||
func test__Validate__Token() {
|
func test__Validate__Token() {
|
||||||
let manager = ReCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey)
|
let recaptcha = ReCaptcha(manager: ReCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey))
|
||||||
manager.configureWebView { _ in
|
recaptcha.configureWebView { _ in
|
||||||
XCTFail("should not ask to configure the webview")
|
XCTFail("should not ask to configure the webview")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
// Validate
|
||||||
|
let result = try recaptcha.rx.validate(on: presenterView)
|
||||||
|
.toBlocking()
|
||||||
|
.single()
|
||||||
|
|
||||||
var result: ReCaptchaWebViewManager.Response?
|
// Verify
|
||||||
let exp = expectation(description: "validate token")
|
XCTAssertEqual(result, apiKey)
|
||||||
|
}
|
||||||
// Validate
|
catch let error {
|
||||||
manager.rx.validate(on: presenterView)
|
XCTFail(error.localizedDescription)
|
||||||
.subscribe { event in
|
}
|
||||||
switch event {
|
|
||||||
case .next(let value):
|
|
||||||
result = value
|
|
||||||
|
|
||||||
case .error(let error):
|
|
||||||
XCTFail(error.localizedDescription)
|
|
||||||
|
|
||||||
case .completed:
|
|
||||||
exp.fulfill()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.disposed(by: disposeBag)
|
|
||||||
|
|
||||||
waitForExpectations(timeout: 10)
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
XCTAssertNotNil(result)
|
|
||||||
XCTAssertEqual(result?.value, apiKey)
|
|
||||||
XCTAssertNil(result?.error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func test__Validate__Show_ReCaptcha() {
|
func test__Validate__Show_ReCaptcha() {
|
||||||
let manager = ReCaptchaWebViewManager(messageBody: "{action: \"showReCaptcha\"}", apiKey: apiKey)
|
let recaptcha = ReCaptcha(
|
||||||
let exp = expectation(description: "show recaptcha")
|
manager: ReCaptchaWebViewManager(messageBody: "{action: \"showReCaptcha\"}", apiKey: apiKey)
|
||||||
|
)
|
||||||
|
|
||||||
manager.configureWebView { _ in
|
var didConfigureWebView = false
|
||||||
exp.fulfill()
|
|
||||||
|
recaptcha.configureWebView { _ in
|
||||||
|
didConfigureWebView = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate
|
do {
|
||||||
manager.rx.validate(on: presenterView)
|
// Validate
|
||||||
.timeout(2, scheduler: MainScheduler.instance)
|
_ = try recaptcha.rx.validate(on: presenterView)
|
||||||
.subscribe { event in
|
.timeout(2, scheduler: MainScheduler.instance)
|
||||||
switch event {
|
.toBlocking()
|
||||||
case .next:
|
.single()
|
||||||
XCTFail("should not have validated")
|
|
||||||
|
|
||||||
case .error(let error):
|
XCTFail("should have thrown exception")
|
||||||
XCTAssertEqual(String(describing: error), RxError.timeout.debugDescription)
|
}
|
||||||
|
catch let error {
|
||||||
case .completed:
|
XCTAssertEqual(String(describing: error), RxError.timeout.debugDescription)
|
||||||
XCTFail("should not have completed")
|
XCTAssertTrue(didConfigureWebView)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.disposed(by: disposeBag)
|
|
||||||
|
|
||||||
waitForExpectations(timeout: 10)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func test__Validate__Error() {
|
func test__Validate__Error() {
|
||||||
let manager = ReCaptchaWebViewManager(messageBody: "\"foobar\"", apiKey: apiKey)
|
let recaptcha = ReCaptcha(manager: ReCaptchaWebViewManager(messageBody: "\"foobar\"", apiKey: apiKey))
|
||||||
manager.configureWebView { _ in
|
recaptcha.configureWebView { _ in
|
||||||
XCTFail("should not ask to configure the webview")
|
XCTFail("should not ask to configure the webview")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
// Validate
|
||||||
|
_ = try recaptcha.rx.validate(on: presenterView, resetOnError: false)
|
||||||
|
.toBlocking()
|
||||||
|
.single()
|
||||||
|
|
||||||
var result: ReCaptchaWebViewManager.Response?
|
XCTFail("should have thrown exception")
|
||||||
let exp = expectation(description: "validate token")
|
}
|
||||||
|
catch let error {
|
||||||
// Validate
|
XCTAssertEqual(error as? ReCaptchaError, .wrongMessageFormat)
|
||||||
manager.rx.validate(on: presenterView)
|
}
|
||||||
.subscribe { event in
|
|
||||||
switch event {
|
|
||||||
case .next(let value):
|
|
||||||
result = value
|
|
||||||
|
|
||||||
case .error(let error):
|
|
||||||
XCTFail(error.localizedDescription)
|
|
||||||
|
|
||||||
case .completed:
|
|
||||||
exp.fulfill()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.disposed(by: disposeBag)
|
|
||||||
|
|
||||||
waitForExpectations(timeout: 10)
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
XCTAssertNotNil(result)
|
|
||||||
XCTAssertNil(result?.value)
|
|
||||||
XCTAssertNotNil(result?.error)
|
|
||||||
XCTAssertEqual(result?.error, .wrongMessageFormat)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Dispose
|
// MARK: Dispose
|
||||||
|
|
@ -140,21 +107,83 @@ class ReCaptcha_Rx__Tests: XCTestCase {
|
||||||
let exp = expectation(description: "stop loading")
|
let exp = expectation(description: "stop loading")
|
||||||
|
|
||||||
// Stop
|
// Stop
|
||||||
let manager = ReCaptchaWebViewManager(messageBody: "{action: \"showReCaptcha\"}")
|
let recaptcha = ReCaptcha(manager: ReCaptchaWebViewManager(messageBody: "{log: \"foo\"}"))
|
||||||
manager.configureWebView { _ in
|
recaptcha.configureWebView { _ in
|
||||||
XCTFail("should not ask to configure the webview")
|
XCTFail("should not ask to configure the webview")
|
||||||
}
|
}
|
||||||
|
|
||||||
let disposable = manager.rx.validate(on: presenterView)
|
let disposable = recaptcha.rx.validate(on: presenterView)
|
||||||
|
.do(onDispose: exp.fulfill)
|
||||||
.subscribe { _ in
|
.subscribe { _ in
|
||||||
XCTFail("should not validate")
|
XCTFail("should not validate")
|
||||||
}
|
}
|
||||||
disposable.dispose()
|
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||||
exp.fulfill()
|
disposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForExpectations(timeout: 10)
|
waitForExpectations(timeout: 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Reset
|
||||||
|
|
||||||
|
func test__Reset() {
|
||||||
|
// Validate
|
||||||
|
let recaptcha = ReCaptcha(
|
||||||
|
manager: ReCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey, shouldFail: true)
|
||||||
|
)
|
||||||
|
|
||||||
|
recaptcha.configureWebView { _ in
|
||||||
|
XCTFail("should not ask to configure the webview")
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
// Error
|
||||||
|
_ = try recaptcha.rx.validate(on: presenterView, resetOnError: false)
|
||||||
|
.toBlocking()
|
||||||
|
.single()
|
||||||
|
}
|
||||||
|
catch let error {
|
||||||
|
XCTAssertEqual(error as? ReCaptchaError, .wrongMessageFormat)
|
||||||
|
|
||||||
|
// Resets after failure
|
||||||
|
_ = Observable<Void>.just(())
|
||||||
|
.bind(to: recaptcha.rx.reset)
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
// Resets and tries again
|
||||||
|
let result = try recaptcha.rx.validate(on: presenterView, resetOnError: false)
|
||||||
|
.toBlocking()
|
||||||
|
.single()
|
||||||
|
|
||||||
|
XCTAssertEqual(result, apiKey)
|
||||||
|
}
|
||||||
|
catch let error {
|
||||||
|
XCTFail(error.localizedDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func test__Validate__Reset_On_Error() {
|
||||||
|
// Validate
|
||||||
|
let recaptcha = ReCaptcha(
|
||||||
|
manager: ReCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey, shouldFail: true)
|
||||||
|
)
|
||||||
|
|
||||||
|
recaptcha.configureWebView { _ in
|
||||||
|
XCTFail("should not ask to configure the webview")
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
// Error
|
||||||
|
let result = try recaptcha.rx.validate(on: presenterView, resetOnError: true)
|
||||||
|
.toBlocking()
|
||||||
|
.single()
|
||||||
|
|
||||||
|
XCTAssertEqual(result, apiKey)
|
||||||
|
}
|
||||||
|
catch let error {
|
||||||
|
XCTFail(error.localizedDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,25 @@
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var key = "${apiKey}";
|
var key = "${apiKey}";
|
||||||
var endpoint = "${endpoint}";
|
var endpoint = "${endpoint}";
|
||||||
|
var shouldFail = ${shouldFail};
|
||||||
|
|
||||||
|
var post = function(value) {
|
||||||
|
window.webkit.messageHandlers.recaptcha.postMessage(value);
|
||||||
|
};
|
||||||
|
|
||||||
var execute = function() {
|
var execute = function() {
|
||||||
window.webkit.messageHandlers.recaptcha.postMessage(${message});
|
if (shouldFail) {
|
||||||
}
|
post("error");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
post(${message});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
window.onload = function() {
|
var reset = function() {
|
||||||
window.webkit.messageHandlers.recaptcha.postMessage({action: "didLoad"});
|
shouldFail = false;
|
||||||
}
|
post({action: "didLoad"});
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
disabled_rules:
|
||||||
|
- type_name
|
||||||
|
- nesting
|
||||||
|
- force_unwrapping
|
||||||
|
- explicit_top_level_acl
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?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>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>BNDL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
//
|
||||||
|
// ReCaptcha_UITests.swift
|
||||||
|
// ReCaptcha
|
||||||
|
//
|
||||||
|
// Created by Flávio Caetano on 16/01/18.
|
||||||
|
// Copyright © 2018 ReCaptcha. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
@testable import ReCaptcha
|
||||||
|
@testable import ReCaptcha_Example
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
class ReCaptcha_UITests: XCTestCase {
|
||||||
|
|
||||||
|
override func setUp() {
|
||||||
|
super.setUp()
|
||||||
|
|
||||||
|
// In UI tests it is usually best to stop immediately when a failure occurs.
|
||||||
|
continueAfterFailure = false
|
||||||
|
XCUIApplication().launch()
|
||||||
|
}
|
||||||
|
|
||||||
|
func test__Validate__Default_Endpoint() {
|
||||||
|
let app = XCUIApplication()
|
||||||
|
app.segmentedControls.buttons["Default Endpoint"].tap()
|
||||||
|
app.switches["Switch"].tap()
|
||||||
|
app.buttons["Validate"].tap()
|
||||||
|
|
||||||
|
verifyValidation()
|
||||||
|
}
|
||||||
|
|
||||||
|
func test__Validate__Alternate_Endpoint() {
|
||||||
|
let app = XCUIApplication()
|
||||||
|
app.segmentedControls.buttons["Alternate"].tap()
|
||||||
|
app.switches["Switch"].tap()
|
||||||
|
app.buttons["Validate"].tap()
|
||||||
|
|
||||||
|
verifyValidation()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Private Methods
|
||||||
|
|
||||||
|
private func verifyValidation() {
|
||||||
|
let app = XCUIApplication()
|
||||||
|
let webview = app.staticTexts.element(matching: .any, identifier: "webview")
|
||||||
|
let webviewExists = webview.waitForExistence(timeout: 10)
|
||||||
|
|
||||||
|
XCTAssertTrue(webviewExists)
|
||||||
|
}
|
||||||
|
}
|
||||||
4
Gemfile
4
Gemfile
|
|
@ -1,4 +1,4 @@
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
gem 'fastlane', '~> 2.75'
|
gem 'fastlane', '~> 2.105.2'
|
||||||
gem 'cocoapods', '~> 1.3.1'
|
gem 'cocoapods', '~> 1.5.3'
|
||||||
|
|
|
||||||
133
Gemfile.lock
133
Gemfile.lock
|
|
@ -1,7 +1,7 @@
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
CFPropertyList (2.3.6)
|
CFPropertyList (3.0.0)
|
||||||
activesupport (4.2.10)
|
activesupport (4.2.10)
|
||||||
i18n (~> 0.7)
|
i18n (~> 0.7)
|
||||||
minitest (~> 5.1)
|
minitest (~> 5.1)
|
||||||
|
|
@ -9,76 +9,79 @@ GEM
|
||||||
tzinfo (~> 1.1)
|
tzinfo (~> 1.1)
|
||||||
addressable (2.5.2)
|
addressable (2.5.2)
|
||||||
public_suffix (>= 2.0.2, < 4.0)
|
public_suffix (>= 2.0.2, < 4.0)
|
||||||
|
atomos (0.1.3)
|
||||||
babosa (1.0.2)
|
babosa (1.0.2)
|
||||||
claide (1.0.2)
|
claide (1.0.2)
|
||||||
cocoapods (1.3.1)
|
cocoapods (1.5.3)
|
||||||
activesupport (>= 4.0.2, < 5)
|
activesupport (>= 4.0.2, < 5)
|
||||||
claide (>= 1.0.2, < 2.0)
|
claide (>= 1.0.2, < 2.0)
|
||||||
cocoapods-core (= 1.3.1)
|
cocoapods-core (= 1.5.3)
|
||||||
cocoapods-deintegrate (>= 1.0.1, < 2.0)
|
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-plugins (>= 1.0.0, < 2.0)
|
||||||
cocoapods-search (>= 1.0.0, < 2.0)
|
cocoapods-search (>= 1.0.0, < 2.0)
|
||||||
cocoapods-stats (>= 1.0.0, < 2.0)
|
cocoapods-stats (>= 1.0.0, < 2.0)
|
||||||
cocoapods-trunk (>= 1.2.0, < 2.0)
|
cocoapods-trunk (>= 1.3.0, < 2.0)
|
||||||
cocoapods-try (>= 1.1.0, < 2.0)
|
cocoapods-try (>= 1.1.0, < 2.0)
|
||||||
colored2 (~> 3.1)
|
colored2 (~> 3.1)
|
||||||
escape (~> 0.0.4)
|
escape (~> 0.0.4)
|
||||||
fourflusher (~> 2.0.1)
|
fourflusher (~> 2.0.1)
|
||||||
gh_inspector (~> 1.0)
|
gh_inspector (~> 1.0)
|
||||||
molinillo (~> 0.5.7)
|
molinillo (~> 0.6.5)
|
||||||
nap (~> 1.0)
|
nap (~> 1.0)
|
||||||
ruby-macho (~> 1.1)
|
ruby-macho (~> 1.1)
|
||||||
xcodeproj (>= 1.5.1, < 2.0)
|
xcodeproj (>= 1.5.7, < 2.0)
|
||||||
cocoapods-core (1.3.1)
|
cocoapods-core (1.5.3)
|
||||||
activesupport (>= 4.0.2, < 6)
|
activesupport (>= 4.0.2, < 6)
|
||||||
fuzzy_match (~> 2.0.4)
|
fuzzy_match (~> 2.0.4)
|
||||||
nap (~> 1.0)
|
nap (~> 1.0)
|
||||||
cocoapods-deintegrate (1.0.1)
|
cocoapods-deintegrate (1.0.2)
|
||||||
cocoapods-downloader (1.1.3)
|
cocoapods-downloader (1.2.1)
|
||||||
cocoapods-plugins (1.0.0)
|
cocoapods-plugins (1.0.0)
|
||||||
nap
|
nap
|
||||||
cocoapods-search (1.0.0)
|
cocoapods-search (1.0.0)
|
||||||
cocoapods-stats (1.0.0)
|
cocoapods-stats (1.0.0)
|
||||||
cocoapods-trunk (1.3.0)
|
cocoapods-trunk (1.3.1)
|
||||||
nap (>= 0.8, < 2.0)
|
nap (>= 0.8, < 2.0)
|
||||||
netrc (~> 0.11)
|
netrc (~> 0.11)
|
||||||
cocoapods-try (1.1.0)
|
cocoapods-try (1.1.0)
|
||||||
colored (1.2)
|
colored (1.2)
|
||||||
colored2 (3.1.2)
|
colored2 (3.1.2)
|
||||||
commander-fastlane (4.4.5)
|
commander-fastlane (4.4.6)
|
||||||
highline (~> 1.7.2)
|
highline (~> 1.7.2)
|
||||||
concurrent-ruby (1.0.5)
|
concurrent-ruby (1.0.5)
|
||||||
declarative (0.0.10)
|
declarative (0.0.10)
|
||||||
declarative-option (0.1.0)
|
declarative-option (0.1.0)
|
||||||
domain_name (0.5.20170404)
|
domain_name (0.5.20180417)
|
||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
dotenv (2.2.1)
|
dotenv (2.5.0)
|
||||||
|
emoji_regex (0.1.1)
|
||||||
escape (0.0.4)
|
escape (0.0.4)
|
||||||
excon (0.60.0)
|
excon (0.62.0)
|
||||||
faraday (0.13.1)
|
faraday (0.15.3)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
faraday-cookie_jar (0.0.6)
|
faraday-cookie_jar (0.0.6)
|
||||||
faraday (>= 0.7.4)
|
faraday (>= 0.7.4)
|
||||||
http-cookie (~> 1.0.0)
|
http-cookie (~> 1.0.0)
|
||||||
faraday_middleware (0.12.2)
|
faraday_middleware (0.12.2)
|
||||||
faraday (>= 0.7.4, < 1.0)
|
faraday (>= 0.7.4, < 1.0)
|
||||||
fastimage (2.1.1)
|
fastimage (2.1.4)
|
||||||
fastlane (2.75.1)
|
fastlane (2.105.2)
|
||||||
CFPropertyList (>= 2.3, < 3.0.0)
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
addressable (>= 2.3, < 3.0.0)
|
addressable (>= 2.3, < 3.0.0)
|
||||||
babosa (>= 1.0.2, < 2.0.0)
|
babosa (>= 1.0.2, < 2.0.0)
|
||||||
bundler (>= 1.12.0, < 2.0.0)
|
bundler (>= 1.12.0, < 2.0.0)
|
||||||
colored
|
colored
|
||||||
commander-fastlane (>= 4.4.5, < 5.0.0)
|
commander-fastlane (>= 4.4.6, < 5.0.0)
|
||||||
dotenv (>= 2.1.1, < 3.0.0)
|
dotenv (>= 2.1.1, < 3.0.0)
|
||||||
|
emoji_regex (~> 0.1)
|
||||||
excon (>= 0.45.0, < 1.0.0)
|
excon (>= 0.45.0, < 1.0.0)
|
||||||
faraday (~> 0.9)
|
faraday (~> 0.9)
|
||||||
faraday-cookie_jar (~> 0.0.6)
|
faraday-cookie_jar (~> 0.0.6)
|
||||||
faraday_middleware (~> 0.9)
|
faraday_middleware (~> 0.9)
|
||||||
fastimage (>= 2.1.0, < 3.0.0)
|
fastimage (>= 2.1.0, < 3.0.0)
|
||||||
gh_inspector (>= 1.0.1, < 2.0.0)
|
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||||
google-api-client (>= 0.13.1, < 0.14.0)
|
google-api-client (>= 0.21.2, < 0.24.0)
|
||||||
highline (>= 1.7.2, < 2.0.0)
|
highline (>= 1.7.2, < 2.0.0)
|
||||||
json (< 3.0.0)
|
json (< 3.0.0)
|
||||||
mini_magick (~> 4.5.1)
|
mini_magick (~> 4.5.1)
|
||||||
|
|
@ -87,100 +90,102 @@ GEM
|
||||||
multipart-post (~> 2.0.0)
|
multipart-post (~> 2.0.0)
|
||||||
plist (>= 3.1.0, < 4.0.0)
|
plist (>= 3.1.0, < 4.0.0)
|
||||||
public_suffix (~> 2.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)
|
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-notifier (>= 1.6.2, < 2.0.0)
|
||||||
terminal-table (>= 1.4.5, < 2.0.0)
|
terminal-table (>= 1.4.5, < 2.0.0)
|
||||||
tty-screen (>= 0.6.3, < 1.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)
|
word_wrap (~> 1.0.0)
|
||||||
xcodeproj (>= 1.5.2, < 2.0.0)
|
xcodeproj (>= 1.6.0, < 2.0.0)
|
||||||
xcpretty (>= 0.2.4, < 1.0.0)
|
xcpretty (~> 0.3.0)
|
||||||
xcpretty-travis-formatter (>= 0.0.3)
|
xcpretty-travis-formatter (>= 0.0.3)
|
||||||
fourflusher (2.0.1)
|
fourflusher (2.0.1)
|
||||||
fuzzy_match (2.0.4)
|
fuzzy_match (2.0.4)
|
||||||
gh_inspector (1.0.3)
|
gh_inspector (1.1.3)
|
||||||
google-api-client (0.13.6)
|
google-api-client (0.23.9)
|
||||||
addressable (~> 2.5, >= 2.5.1)
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
googleauth (~> 0.5)
|
googleauth (>= 0.5, < 0.7.0)
|
||||||
httpclient (>= 2.8.1, < 3.0)
|
httpclient (>= 2.8.1, < 3.0)
|
||||||
mime-types (~> 3.0)
|
mime-types (~> 3.0)
|
||||||
representable (~> 3.0)
|
representable (~> 3.0)
|
||||||
retriable (>= 2.0, < 4.0)
|
retriable (>= 2.0, < 4.0)
|
||||||
googleauth (0.6.2)
|
signet (~> 0.9)
|
||||||
|
googleauth (0.6.6)
|
||||||
faraday (~> 0.12)
|
faraday (~> 0.12)
|
||||||
jwt (>= 1.4, < 3.0)
|
jwt (>= 1.4, < 3.0)
|
||||||
logging (~> 2.0)
|
|
||||||
memoist (~> 0.12)
|
memoist (~> 0.12)
|
||||||
multi_json (~> 1.11)
|
multi_json (~> 1.11)
|
||||||
os (~> 0.9)
|
os (>= 0.9, < 2.0)
|
||||||
signet (~> 0.7)
|
signet (~> 0.7)
|
||||||
highline (1.7.10)
|
highline (1.7.10)
|
||||||
http-cookie (1.0.3)
|
http-cookie (1.0.3)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
i18n (0.9.0)
|
i18n (0.9.5)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
json (2.1.0)
|
json (2.1.0)
|
||||||
jwt (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)
|
memoist (0.16.0)
|
||||||
mime-types (3.1)
|
mime-types (3.2.2)
|
||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
mime-types-data (3.2016.0521)
|
mime-types-data (3.2018.0812)
|
||||||
mini_magick (4.5.1)
|
mini_magick (4.5.1)
|
||||||
minitest (5.10.3)
|
minitest (5.11.3)
|
||||||
molinillo (0.5.7)
|
molinillo (0.6.6)
|
||||||
multi_json (1.13.0)
|
multi_json (1.13.1)
|
||||||
multi_xml (0.6.0)
|
multi_xml (0.6.0)
|
||||||
multipart-post (2.0.0)
|
multipart-post (2.0.0)
|
||||||
nanaimo (0.2.3)
|
nanaimo (0.2.6)
|
||||||
nap (1.1.0)
|
nap (1.1.0)
|
||||||
|
naturally (2.2.0)
|
||||||
netrc (0.11.0)
|
netrc (0.11.0)
|
||||||
os (0.9.6)
|
os (1.0.0)
|
||||||
plist (3.4.0)
|
plist (3.4.0)
|
||||||
public_suffix (2.0.5)
|
public_suffix (2.0.5)
|
||||||
representable (3.0.4)
|
representable (3.0.4)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
declarative-option (< 0.2.0)
|
declarative-option (< 0.2.0)
|
||||||
uber (< 0.2.0)
|
uber (< 0.2.0)
|
||||||
retriable (3.1.1)
|
retriable (3.1.2)
|
||||||
rouge (2.0.7)
|
rouge (2.0.7)
|
||||||
ruby-macho (1.1.0)
|
ruby-macho (1.2.0)
|
||||||
rubyzip (1.2.1)
|
rubyzip (1.2.2)
|
||||||
security (0.1.3)
|
security (0.1.3)
|
||||||
signet (0.8.1)
|
signet (0.10.0)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
faraday (~> 0.9)
|
faraday (~> 0.9)
|
||||||
jwt (>= 1.5, < 3.0)
|
jwt (>= 1.5, < 3.0)
|
||||||
multi_json (~> 1.10)
|
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-notifier (1.8.0)
|
||||||
terminal-table (1.8.0)
|
terminal-table (1.8.0)
|
||||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||||
thread_safe (0.3.6)
|
thread_safe (0.3.6)
|
||||||
tty-cursor (0.5.0)
|
tty-cursor (0.6.0)
|
||||||
tty-screen (0.6.4)
|
tty-screen (0.6.5)
|
||||||
tty-spinner (0.7.0)
|
tty-spinner (0.8.0)
|
||||||
tty-cursor (>= 0.5.0)
|
tty-cursor (>= 0.5.0)
|
||||||
tzinfo (1.2.4)
|
tzinfo (1.2.5)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
uber (0.1.0)
|
uber (0.1.0)
|
||||||
unf (0.1.4)
|
unf (0.1.4)
|
||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.7.4)
|
unf_ext (0.0.7.5)
|
||||||
unicode-display_width (1.3.0)
|
unicode-display_width (1.4.0)
|
||||||
word_wrap (1.0.0)
|
word_wrap (1.0.0)
|
||||||
xcodeproj (1.5.4)
|
xcodeproj (1.6.0)
|
||||||
CFPropertyList (~> 2.3.3)
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
|
atomos (~> 0.1.3)
|
||||||
claide (>= 1.0.2, < 2.0)
|
claide (>= 1.0.2, < 2.0)
|
||||||
colored2 (~> 3.1)
|
colored2 (~> 3.1)
|
||||||
nanaimo (~> 0.2.3)
|
nanaimo (~> 0.2.6)
|
||||||
xcpretty (0.2.8)
|
xcpretty (0.3.0)
|
||||||
rouge (~> 2.0.7)
|
rouge (~> 2.0.7)
|
||||||
xcpretty-travis-formatter (1.0.0)
|
xcpretty-travis-formatter (1.0.0)
|
||||||
xcpretty (~> 0.2, >= 0.0.7)
|
xcpretty (~> 0.2, >= 0.0.7)
|
||||||
|
|
@ -189,8 +194,8 @@ PLATFORMS
|
||||||
ruby
|
ruby
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
cocoapods (~> 1.3.1)
|
cocoapods (~> 1.5.3)
|
||||||
fastlane (~> 2.75)
|
fastlane (~> 2.105.2)
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
1.16.0
|
1.16.1
|
||||||
|
|
|
||||||
27
README.md
27
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
|
automatically handles ReCaptcha's events and retrieves the validation token or notifies you to present the challenge if
|
||||||
invisibility is not possible.
|
invisibility is not possible.
|
||||||
|
|
||||||
|
|
@ -17,13 +17,20 @@ invisibility is not possible.
|
||||||
|
|
||||||
#### _Warning_ ⚠️
|
#### _Warning_ ⚠️
|
||||||
|
|
||||||
Beware that this library only works for Invisible ReCaptcha keys! Make sure to check the Invisible reCAPTCHA option
|
Beware that this library only works for ReCaptcha v2 Invisible keys! Make sure to check the reCAPTCHA
|
||||||
when creating your [API Key](https://www.google.com/recaptcha/admin).
|
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
|
## Installation
|
||||||
|
|
||||||
ReCaptcha is available through [CocoaPods](http://cocoapods.org) and [Carthage](https://github.com/Carthage/Carthage).
|
ReCaptcha is available through [CocoaPods](http://cocoapods.org) and [Carthage](https://github.com/Carthage/Carthage).
|
||||||
To install it, simply add the following line to your depedencies file:
|
To install it, simply add the following line to your dependencies file:
|
||||||
|
|
||||||
#### Cocoapods
|
#### Cocoapods
|
||||||
``` ruby
|
``` ruby
|
||||||
|
|
@ -42,7 +49,7 @@ extension for the ReCaptcha framework.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Simply add `ReCaptchaKey` and `ReCaptchaDomain` 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
|
``` swift
|
||||||
let recaptcha = try? ReCaptcha()
|
let recaptcha = try? ReCaptcha()
|
||||||
|
|
@ -57,7 +64,7 @@ override func viewDidLoad() {
|
||||||
|
|
||||||
|
|
||||||
func validate() {
|
func validate() {
|
||||||
recaptcha?.validate(on: view) { [weak self] result in
|
recaptcha?.validate(on: view) { [weak self] (result: ReCaptchaResult) in
|
||||||
print(try? result.dematerialize())
|
print(try? result.dematerialize())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +74,6 @@ You can also install the reactive subpod and use it with RxSwift:
|
||||||
|
|
||||||
``` swift
|
``` swift
|
||||||
recaptcha.rx.validate(on: view)
|
recaptcha.rx.validate(on: view)
|
||||||
.map { try $0.dematerialize() }
|
|
||||||
.subscribe(onNext: { (token: String) in
|
.subscribe(onNext: { (token: String) in
|
||||||
// Do something
|
// Do something
|
||||||
})
|
})
|
||||||
|
|
@ -86,6 +92,13 @@ public enum Endpoint {
|
||||||
let recaptcha = try? ReCaptcha(endpoint: .alternate) // Defaults to `default` when unset
|
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
|
## License
|
||||||
|
|
||||||
ReCaptcha is available under the MIT license. See the LICENSE file for more info.
|
ReCaptcha is available under the MIT license. See the LICENSE file for more info.
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
F206BAB51F8D3DE900A25807 /* ReCaptcha-Carthage.h in Headers */ = {isa = PBXBuildFile; fileRef = F206BAB31F8D3DE900A25807 /* ReCaptcha-Carthage.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
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, ); }; };
|
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 */; };
|
F206BB221F8D4DDF00A25807 /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F206BB121F8D4D1400A25807 /* RxSwift.framework */; };
|
||||||
F231B39A1FEC51C800F82943 /* DispatchQueue+Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F231B3991FEC51C800F82943 /* DispatchQueue+Throttle.swift */; };
|
F231B39A1FEC51C800F82943 /* DispatchQueue+Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F231B3991FEC51C800F82943 /* DispatchQueue+Throttle.swift */; };
|
||||||
|
|
@ -19,7 +18,7 @@
|
||||||
F24EA1E51F968403001DEC17 /* ReCaptchaError.swift in Sources */ = {isa = PBXBuildFile; fileRef = F24EA1DB1F9683F5001DEC17 /* ReCaptchaError.swift */; };
|
F24EA1E51F968403001DEC17 /* ReCaptchaError.swift in Sources */ = {isa = PBXBuildFile; fileRef = F24EA1DB1F9683F5001DEC17 /* ReCaptchaError.swift */; };
|
||||||
F24EA1E61F968403001DEC17 /* ReCaptcha.swift in Sources */ = {isa = PBXBuildFile; fileRef = F24EA1DE1F9683F5001DEC17 /* ReCaptcha.swift */; };
|
F24EA1E61F968403001DEC17 /* ReCaptcha.swift in Sources */ = {isa = PBXBuildFile; fileRef = F24EA1DE1F9683F5001DEC17 /* ReCaptcha.swift */; };
|
||||||
F24EA1E71F968406001DEC17 /* recaptcha.html in Resources */ = {isa = PBXBuildFile; fileRef = F24EA1E01F9683F5001DEC17 /* recaptcha.html */; };
|
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 */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
|
@ -37,7 +36,6 @@
|
||||||
F206BAB31F8D3DE900A25807 /* ReCaptcha-Carthage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ReCaptcha-Carthage.h"; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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; };
|
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>"; };
|
F206BB1B1F8D4DBC00A25807 /* ReCaptcha_RxSwift.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReCaptcha_RxSwift.h; sourceTree = "<group>"; };
|
||||||
|
|
@ -50,7 +48,7 @@
|
||||||
F24EA1DD1F9683F5001DEC17 /* ReCaptcha+Rx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReCaptcha+Rx.swift"; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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 */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
|
@ -58,7 +56,6 @@
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
F206BAD31F8D3E7600A25807 /* Result.framework in Frameworks */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
@ -67,7 +64,6 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
F206BB221F8D4DDF00A25807 /* RxSwift.framework in Frameworks */,
|
F206BB221F8D4DDF00A25807 /* RxSwift.framework in Frameworks */,
|
||||||
F255FBEC1F8D5654002F5FA8 /* Result.framework in Frameworks */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
@ -108,9 +104,7 @@
|
||||||
F206BAD11F8D3E7600A25807 /* Frameworks */ = {
|
F206BAD11F8D3E7600A25807 /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
F255FBEB1F8D5654002F5FA8 /* Result.framework */,
|
|
||||||
F206BB121F8D4D1400A25807 /* RxSwift.framework */,
|
F206BB121F8D4D1400A25807 /* RxSwift.framework */,
|
||||||
F206BAD21F8D3E7600A25807 /* Result.framework */,
|
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -147,6 +141,7 @@
|
||||||
F24EA1D91F9683F5001DEC17 /* ReCaptchaWebViewManager.swift */,
|
F24EA1D91F9683F5001DEC17 /* ReCaptchaWebViewManager.swift */,
|
||||||
F24EA1DA1F9683F5001DEC17 /* String+Dict.swift */,
|
F24EA1DA1F9683F5001DEC17 /* String+Dict.swift */,
|
||||||
F24EA1DB1F9683F5001DEC17 /* ReCaptchaError.swift */,
|
F24EA1DB1F9683F5001DEC17 /* ReCaptchaError.swift */,
|
||||||
|
F2AE8613204F3B41002E28D7 /* ReCaptchaResult.swift */,
|
||||||
F231B3991FEC51C800F82943 /* DispatchQueue+Throttle.swift */,
|
F231B3991FEC51C800F82943 /* DispatchQueue+Throttle.swift */,
|
||||||
F24EA1DC1F9683F5001DEC17 /* Rx */,
|
F24EA1DC1F9683F5001DEC17 /* Rx */,
|
||||||
F24EA1DE1F9683F5001DEC17 /* ReCaptcha.swift */,
|
F24EA1DE1F9683F5001DEC17 /* ReCaptcha.swift */,
|
||||||
|
|
@ -291,6 +286,7 @@
|
||||||
files = (
|
files = (
|
||||||
F24EA1E51F968403001DEC17 /* ReCaptchaError.swift in Sources */,
|
F24EA1E51F968403001DEC17 /* ReCaptchaError.swift in Sources */,
|
||||||
F24EA1E21F968403001DEC17 /* ReCaptchaDecoder.swift in Sources */,
|
F24EA1E21F968403001DEC17 /* ReCaptchaDecoder.swift in Sources */,
|
||||||
|
F2AE8614204F3B42002E28D7 /* ReCaptchaResult.swift in Sources */,
|
||||||
F24EA1E61F968403001DEC17 /* ReCaptcha.swift in Sources */,
|
F24EA1E61F968403001DEC17 /* ReCaptcha.swift in Sources */,
|
||||||
F24EA1E31F968403001DEC17 /* ReCaptchaWebViewManager.swift in Sources */,
|
F24EA1E31F968403001DEC17 /* ReCaptchaWebViewManager.swift in Sources */,
|
||||||
F24EA1E41F968403001DEC17 /* String+Dict.swift in Sources */,
|
F24EA1E41F968403001DEC17 /* String+Dict.swift in Sources */,
|
||||||
|
|
@ -371,6 +367,7 @@
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 4.2;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
VERSION_INFO_PREFIX = "";
|
VERSION_INFO_PREFIX = "";
|
||||||
|
|
@ -423,6 +420,7 @@
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||||
|
SWIFT_VERSION = 4.2;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
VALIDATE_PRODUCT = YES;
|
VALIDATE_PRODUCT = YES;
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
|
@ -448,7 +446,7 @@
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.flaviocaetano.ReCaptcha;
|
PRODUCT_BUNDLE_IDENTIFIER = com.flaviocaetano.ReCaptcha;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_VERSION = 3.0;
|
SWIFT_VERSION = 4.2;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
|
@ -470,7 +468,7 @@
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.flaviocaetano.ReCaptcha;
|
PRODUCT_BUNDLE_IDENTIFIER = com.flaviocaetano.ReCaptcha;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_VERSION = 3.0;
|
SWIFT_VERSION = 4.2;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
|
@ -494,7 +492,7 @@
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-RxSwift";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-RxSwift";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_VERSION = 3.0;
|
SWIFT_VERSION = 4.2;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
|
@ -518,7 +516,7 @@
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-RxSwift";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-RxSwift";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_VERSION = 3.0;
|
SWIFT_VERSION = 4.2;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,22 @@
|
||||||
|
|
||||||
Pod::Spec.new do |s|
|
Pod::Spec.new do |s|
|
||||||
s.name = 'ReCaptcha'
|
s.name = 'ReCaptcha'
|
||||||
s.version = '1.0.2'
|
s.version = '1.4.2'
|
||||||
s.summary = 'ReCaptcha for iOS'
|
s.summary = 'ReCaptcha for iOS'
|
||||||
|
s.swift_version = '4.2'
|
||||||
|
|
||||||
s.description = <<-DESC
|
s.description = <<-DESC
|
||||||
Add Google's [Invisible ReCaptcha](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
|
automatically handles ReCaptcha's events and retrieves the validation token or notifies you to present the challenge if
|
||||||
invisibility is not possible.
|
invisibility is not possible.
|
||||||
DESC
|
DESC
|
||||||
|
|
||||||
s.homepage = 'https://github.com/fjcaetano/ReCaptcha'
|
s.homepage = 'https://github.com/fjcaetano/ReCaptcha'
|
||||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||||
s.author = { 'Flávio Caetano' => 'flavio@vieiracaetano.com' }
|
s.author = { 'Flávio Caetano' => 'flavio@vieiracaetano.com' }
|
||||||
s.source = { :git => 'https://github.com/fjcaetano/ReCaptcha.git', :tag => s.version.to_s }
|
s.source = { :git => 'https://github.com/fjcaetano/ReCaptcha.git', :tag => s.version.to_s }
|
||||||
s.social_media_url = 'https://twitter.com/flavio_caetano'
|
s.social_media_url = 'https://twitter.com/flavio_caetano'
|
||||||
|
s.documentation_url = 'http://fjcaetano.github.io/ReCaptcha'
|
||||||
|
|
||||||
s.ios.deployment_target = '8.0'
|
s.ios.deployment_target = '8.0'
|
||||||
s.default_subspecs = 'Core'
|
s.default_subspecs = 'Core'
|
||||||
|
|
@ -22,7 +24,6 @@ invisibility is not possible.
|
||||||
s.subspec 'Core' do |core|
|
s.subspec 'Core' do |core|
|
||||||
core.source_files = 'ReCaptcha/Classes/*'
|
core.source_files = 'ReCaptcha/Classes/*'
|
||||||
core.frameworks = 'WebKit'
|
core.frameworks = 'WebKit'
|
||||||
core.dependency 'Result', '~> 3.0'
|
|
||||||
|
|
||||||
core.resource_bundles = {
|
core.resource_bundles = {
|
||||||
'ReCaptcha' => ['ReCaptcha/Assets/**/*']
|
'ReCaptcha' => ['ReCaptcha/Assets/**/*']
|
||||||
|
|
@ -32,6 +33,6 @@ invisibility is not possible.
|
||||||
s.subspec 'RxSwift' do |rx|
|
s.subspec 'RxSwift' do |rx|
|
||||||
rx.source_files = 'ReCaptcha/Classes/Rx/**/*'
|
rx.source_files = 'ReCaptcha/Classes/Rx/**/*'
|
||||||
rx.dependency 'ReCaptcha/Core'
|
rx.dependency 'ReCaptcha/Core'
|
||||||
rx.dependency 'RxSwift', '~> 4.0'
|
rx.dependency 'RxSwift', '~> 4.3'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,19 @@
|
||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
var post = function(value) {
|
||||||
|
window.webkit.messageHandlers.recaptcha.postMessage(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log = function(message) {
|
||||||
|
post({log: message});
|
||||||
|
};
|
||||||
|
|
||||||
|
var showReCaptcha = function() {
|
||||||
|
console.log("showReCaptcha");
|
||||||
|
post({action: "showReCaptcha"});
|
||||||
|
};
|
||||||
|
|
||||||
var observeDOM = function(element, completion) {
|
var observeDOM = function(element, completion) {
|
||||||
new MutationObserver(function(mutations) {
|
new MutationObserver(function(mutations) {
|
||||||
mutations.forEach(function(mutationRecord) {
|
mutations.forEach(function(mutationRecord) {
|
||||||
|
|
@ -12,6 +25,8 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
var execute = function() {
|
var execute = function() {
|
||||||
|
console.log("executing");
|
||||||
|
|
||||||
// Removes ReCaptcha dismissal when clicking outside div area
|
// Removes ReCaptcha dismissal when clicking outside div area
|
||||||
try {
|
try {
|
||||||
document.getElementsByTagName("div")[4].outerHTML = ""
|
document.getElementsByTagName("div")[4].outerHTML = ""
|
||||||
|
|
@ -20,26 +35,29 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listens to changes on the div element that presents the ReCaptcha challenge
|
// Listens to changes on the div element that presents the ReCaptcha challenge
|
||||||
observeDOM(document.getElementsByTagName("div")[3], function() {
|
observeDOM(document.getElementsByTagName("div")[3], showReCaptcha);
|
||||||
console.log({action: "showReCaptcha"});
|
|
||||||
window.webkit.messageHandlers.recaptcha.postMessage({action: "showReCaptcha"});
|
|
||||||
});
|
|
||||||
|
|
||||||
grecaptcha.execute();
|
grecaptcha.execute();
|
||||||
}
|
};
|
||||||
|
|
||||||
var onSubmit = function(token) {
|
var onSubmit = function(token) {
|
||||||
console.log(token);
|
console.log(token);
|
||||||
window.webkit.messageHandlers.recaptcha.postMessage({token: token});
|
post({token: token});
|
||||||
};
|
};
|
||||||
|
|
||||||
var onloadCallback = function() {
|
var onloadCallback = function() {
|
||||||
|
console.log("did load");
|
||||||
grecaptcha.render('submit', {
|
grecaptcha.render('submit', {
|
||||||
'sitekey' : '${apiKey}',
|
'sitekey' : '${apiKey}',
|
||||||
'callback' : onSubmit,
|
'callback' : onSubmit,
|
||||||
'size': 'invisible'
|
'size': 'invisible'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var reset = function() {
|
||||||
|
console.log("resetting");
|
||||||
|
grecaptcha.reset();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,25 @@
|
||||||
// ReCaptcha
|
// ReCaptcha
|
||||||
//
|
//
|
||||||
// Created by Flávio Caetano on 21/12/17.
|
// Created by Flávio Caetano on 21/12/17.
|
||||||
// Copyright © 2017 ReCaptcha. All rights reserved.
|
// Copyright © 2018 ReCaptcha. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
private var workItems = [AnyHashable: DispatchWorkItem]()
|
/// Adds throttling to dispatch queues
|
||||||
private let nilContext = UUID()
|
|
||||||
|
|
||||||
extension DispatchQueue {
|
extension DispatchQueue {
|
||||||
|
/// Stores a throttle DispatchWorkItem instance for a given context
|
||||||
|
private static var workItems = [AnyHashable: DispatchWorkItem]()
|
||||||
|
|
||||||
|
/// 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()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
- parameters:
|
- parameters:
|
||||||
- deadline: The timespan to delay a closure execution
|
- deadline: The timespan to delay a closure execution
|
||||||
|
|
@ -22,13 +32,53 @@ extension DispatchQueue {
|
||||||
*/
|
*/
|
||||||
func throttle(deadline: DispatchTime, context: AnyHashable = nilContext, action: @escaping () -> Void) {
|
func throttle(deadline: DispatchTime, context: AnyHashable = nilContext, action: @escaping () -> Void) {
|
||||||
let worker = DispatchWorkItem {
|
let worker = DispatchWorkItem {
|
||||||
defer { workItems[context] = nil }
|
defer { DispatchQueue.workItems.removeValue(forKey: context) }
|
||||||
action()
|
action()
|
||||||
}
|
}
|
||||||
|
|
||||||
asyncAfter(deadline: deadline, execute: worker)
|
asyncAfter(deadline: deadline, execute: worker)
|
||||||
|
|
||||||
workItems[context]?.cancel()
|
DispatchQueue.workItems[context]?.cancel()
|
||||||
workItems[context] = worker
|
DispatchQueue.workItems[context] = worker
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
- parameters:
|
||||||
|
- interval: The interval in which new calls will be ignored
|
||||||
|
- context: The context in which the debounce should be executed
|
||||||
|
- action: The closure to be executed
|
||||||
|
|
||||||
|
Executes a closure and ensures no other executions will be made during the interval.
|
||||||
|
*/
|
||||||
|
func debounce(interval: Double, context: AnyHashable = nilContext, action: @escaping () -> Void) {
|
||||||
|
let now = DispatchTime.now()
|
||||||
|
if let last = DispatchQueue.lastDebounceCallTimes[context], last + interval > now {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue.lastDebounceCallTimes[context] = now + interval
|
||||||
|
async(execute: action)
|
||||||
|
|
||||||
|
// Cleanup & release context
|
||||||
|
throttle(deadline: now + interval) {
|
||||||
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,18 @@
|
||||||
// ReCaptcha
|
// ReCaptcha
|
||||||
//
|
//
|
||||||
// Created by Flávio Caetano on 22/03/17.
|
// Created by Flávio Caetano on 22/03/17.
|
||||||
// Copyright © 2017 ReCaptcha. All rights reserved.
|
// Copyright © 2018 ReCaptcha. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import WebKit
|
import WebKit
|
||||||
|
|
||||||
|
|
||||||
/** The public facade of ReCaptcha
|
/**
|
||||||
*/
|
*/
|
||||||
open class ReCaptcha: ReCaptchaWebViewManager {
|
public class ReCaptcha {
|
||||||
|
public typealias BoolParameterClosure = (Bool) -> ()
|
||||||
|
|
||||||
fileprivate struct Constants {
|
fileprivate struct Constants {
|
||||||
struct InfoDictKeys {
|
struct InfoDictKeys {
|
||||||
static let APIKey = "ReCaptchaKey"
|
static let APIKey = "ReCaptchaKey"
|
||||||
|
|
@ -23,22 +25,26 @@ open class ReCaptcha: ReCaptchaWebViewManager {
|
||||||
/// The JS API endpoint to be loaded onto the HTML file.
|
/// The JS API endpoint to be loaded onto the HTML file.
|
||||||
public enum Endpoint {
|
public enum Endpoint {
|
||||||
/** Google's default endpoint. Points to
|
/** Google's default endpoint. Points to
|
||||||
https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit
|
https://www.google.com/recaptcha/api.js
|
||||||
*/
|
*/
|
||||||
case `default`
|
case `default`
|
||||||
|
|
||||||
/// Alternate endpoint. Points to https://www.recaptcha.net/recaptcha/api.js
|
/// Alternate endpoint. Points to https://www.recaptcha.net/recaptcha/api.js
|
||||||
case alternate
|
case alternate
|
||||||
|
|
||||||
fileprivate var url: String {
|
internal func getURL(locale: Locale?) -> String {
|
||||||
|
let localeAppendix = locale.map { "&hl=\($0.identifier)" } ?? ""
|
||||||
switch self {
|
switch self {
|
||||||
case .default: return "https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit"
|
case .default:
|
||||||
case .alternate: return "https://www.recaptcha.net/recaptcha/api.js"
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Internal data model for DI in unit tests
|
/** Internal data model for CI in unit tests
|
||||||
*/
|
*/
|
||||||
struct Config {
|
struct Config {
|
||||||
/// The raw unformated HTML file content
|
/// The raw unformated HTML file content
|
||||||
|
|
@ -93,16 +99,26 @@ open class ReCaptcha: ReCaptchaWebViewManager {
|
||||||
|
|
||||||
self.html = rawHTML
|
self.html = rawHTML
|
||||||
self.apiKey = apiKey
|
self.apiKey = apiKey
|
||||||
self.baseURL = domain
|
self.baseURL = Config.fixSchemeIfNeeded(for: domain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
- parameters:
|
- parameters:
|
||||||
- apiKey: The API key sent to the ReCaptcha init
|
- apiKey: The API key sent to the ReCaptcha init
|
||||||
- infoPlistKey: The API key retrived from the application's Info.plist
|
- infoPlistKey: The API key retrived from the application's Info.plist
|
||||||
- baseURL: The base URL sent to the ReCaptcha init
|
- baseURL: The base URL sent to the ReCaptcha init
|
||||||
- infoPlistURL: The base URL retrieved from the application's Info.plist
|
- 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
|
Initializes a ReCaptcha object
|
||||||
|
|
||||||
|
|
@ -118,13 +134,124 @@ open class ReCaptcha: ReCaptchaWebViewManager {
|
||||||
Info.plist.
|
Info.plist.
|
||||||
- Throws: Rethrows any exceptions thrown by `String(contentsOfFile:)`
|
- Throws: Rethrows any exceptions thrown by `String(contentsOfFile:)`
|
||||||
*/
|
*/
|
||||||
public 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 infoDict = Bundle.main.infoDictionary
|
||||||
|
|
||||||
let plistApiKey = infoDict?[Constants.InfoDictKeys.APIKey] as? String
|
let plistApiKey = infoDict?[Constants.InfoDictKeys.APIKey] as? String
|
||||||
let plistDomain = (infoDict?[Constants.InfoDictKeys.Domain] as? String).flatMap(URL.init(string:))
|
let plistDomain = (infoDict?[Constants.InfoDictKeys.Domain] as? String).flatMap(URL.init(string:))
|
||||||
|
|
||||||
let config = try Config(apiKey: apiKey, infoPlistKey: plistApiKey, baseURL: baseURL, infoPlistURL: plistDomain)
|
let config = try Config(apiKey: apiKey, infoPlistKey: plistApiKey, baseURL: baseURL, infoPlistURL: plistDomain)
|
||||||
super.init(html: config.html, apiKey: config.apiKey, baseURL: config.baseURL, endpoint: endpoint.url)
|
|
||||||
|
self.init(manager: ReCaptchaWebViewManager(
|
||||||
|
html: config.html,
|
||||||
|
apiKey: config.apiKey,
|
||||||
|
baseURL: config.baseURL,
|
||||||
|
endpoint: endpoint.getURL(locale: locale)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
- parameter manager: A ReCaptchaWebViewManager instance.
|
||||||
|
|
||||||
|
Initializes ReCaptcha with the given manager
|
||||||
|
*/
|
||||||
|
init(manager: ReCaptchaWebViewManager) {
|
||||||
|
self.manager = manager
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
- parameters:
|
||||||
|
- view: The view that should present the webview.
|
||||||
|
- resetOnError: If ReCaptcha should be reset if it errors. Defaults to `true`.
|
||||||
|
- completion: A closure that receives a ReCaptchaResult which may contain a valid result token.
|
||||||
|
|
||||||
|
Starts the challenge validation
|
||||||
|
*/
|
||||||
|
public func validate(on view: UIView, resetOnError: Bool = true, completion: @escaping (ReCaptchaResult) -> Void) {
|
||||||
|
manager.shouldResetOnError = resetOnError
|
||||||
|
manager.completion = completion
|
||||||
|
|
||||||
|
manager.validate(on: view)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Stops the execution of the webview
|
||||||
|
public func stop() {
|
||||||
|
manager.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
- parameter configure: A closure that receives an instance of `WKWebView` for configuration.
|
||||||
|
|
||||||
|
Provides a closure to configure the webview for presentation if necessary.
|
||||||
|
|
||||||
|
If presentation is required, the webview will already be a subview of `presenterView` if one is provided. Otherwise
|
||||||
|
it might need to be added in a view currently visible.
|
||||||
|
*/
|
||||||
|
public func configureWebView(_ configure: @escaping (WKWebView) -> Void) {
|
||||||
|
manager.configureWebView = configure
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Resets the ReCaptcha.
|
||||||
|
|
||||||
|
The reset is achieved by calling `grecaptcha.reset()` on the JS API.
|
||||||
|
*/
|
||||||
|
public func reset() {
|
||||||
|
manager.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Development
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private Methods
|
||||||
|
|
||||||
|
private extension ReCaptcha.Config {
|
||||||
|
/**
|
||||||
|
- parameter url: The URL to be fixed
|
||||||
|
- returns: An URL with scheme
|
||||||
|
|
||||||
|
If the given URL has no scheme, prepends `http://` to it and return the fixed URL.
|
||||||
|
*/
|
||||||
|
static func fixSchemeIfNeeded(for url: URL) -> URL {
|
||||||
|
guard url.scheme?.isEmpty != false else {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
print("⚠️ WARNING! Protocol not found for ReCaptcha domain (\(url))! You should add http:// or https:// to it!")
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if let fixedURL = URL(string: "http://" + url.absoluteString) {
|
||||||
|
return fixedURL
|
||||||
|
}
|
||||||
|
|
||||||
|
return url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
// ReCaptcha
|
// ReCaptcha
|
||||||
//
|
//
|
||||||
// Created by Flávio Caetano on 22/03/17.
|
// Created by Flávio Caetano on 22/03/17.
|
||||||
// Copyright © 2017 ReCaptcha. All rights reserved.
|
// Copyright © 2018 ReCaptcha. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
@ -27,6 +27,9 @@ internal class ReCaptchaDecoder: NSObject {
|
||||||
|
|
||||||
/// Did finish loading resources
|
/// Did finish loading resources
|
||||||
case didLoad
|
case didLoad
|
||||||
|
|
||||||
|
/// Logs a string onto the console
|
||||||
|
case log(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The closure that receives messages
|
/// The closure that receives messages
|
||||||
|
|
@ -100,6 +103,10 @@ fileprivate extension ReCaptchaDecoder.Result {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let message = response["log"] as? String {
|
||||||
|
return .log(message)
|
||||||
|
}
|
||||||
|
|
||||||
return .error(.wrongMessageFormat)
|
return .error(.wrongMessageFormat)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
// ReCaptcha
|
// ReCaptcha
|
||||||
//
|
//
|
||||||
// Created by Flávio Caetano on 22/03/17.
|
// Created by Flávio Caetano on 22/03/17.
|
||||||
// Copyright © 2017 ReCaptcha. All rights reserved.
|
// Copyright © 2018 ReCaptcha. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
@ -22,7 +22,7 @@ public enum ReCaptchaError: Error, CustomStringConvertible {
|
||||||
/// ReCaptchaDomain was not provided
|
/// ReCaptchaDomain was not provided
|
||||||
case baseURLNotFound
|
case baseURLNotFound
|
||||||
|
|
||||||
/// Received an unexpeted message from javascript
|
/// Received an unexpected message from javascript
|
||||||
case wrongMessageFormat
|
case wrongMessageFormat
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
//
|
||||||
|
// ReCaptchaWebViewManager.swift
|
||||||
|
// ReCaptcha
|
||||||
|
//
|
||||||
|
// Created by Flávio Caetano on 06/03/17.
|
||||||
|
// Copyright © 2018 ReCaptcha. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/** The ReCaptcha result.
|
||||||
|
|
||||||
|
This may contain the validation token on success, or an error that may have occurred.
|
||||||
|
*/
|
||||||
|
public enum ReCaptchaResult {
|
||||||
|
/// The validation token.
|
||||||
|
case token(String)
|
||||||
|
|
||||||
|
/// An error that may have occurred.
|
||||||
|
case error(ReCaptchaError)
|
||||||
|
|
||||||
|
/**
|
||||||
|
- returns: The validation token uppon success.
|
||||||
|
|
||||||
|
Tries to unwrap the Result and retrieve the token if it's successful.
|
||||||
|
|
||||||
|
- Throws: `ReCaptchaError`
|
||||||
|
*/
|
||||||
|
public func dematerialize() throws -> String {
|
||||||
|
switch self {
|
||||||
|
case .token(let token):
|
||||||
|
return token
|
||||||
|
|
||||||
|
case .error(let error):
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,126 +3,99 @@
|
||||||
// ReCaptcha
|
// ReCaptcha
|
||||||
//
|
//
|
||||||
// Created by Flávio Caetano on 22/03/17.
|
// Created by Flávio Caetano on 22/03/17.
|
||||||
// Copyright © 2017 ReCaptcha. All rights reserved.
|
// Copyright © 2018 ReCaptcha. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Result
|
|
||||||
import WebKit
|
import WebKit
|
||||||
|
|
||||||
|
|
||||||
/** Handles comunications with the webview containing the ReCaptcha challenge.
|
/** Handles comunications with the webview containing the ReCaptcha challenge.
|
||||||
*/
|
*/
|
||||||
open class ReCaptchaWebViewManager {
|
internal class ReCaptchaWebViewManager {
|
||||||
public typealias Response = Result<String, ReCaptchaError>
|
|
||||||
|
|
||||||
/** The `webView` delegate object that performs execution uppon script loading
|
|
||||||
*/
|
|
||||||
fileprivate class WebViewDelegate: NSObject, WKNavigationDelegate {
|
|
||||||
/// 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, let host = url.host, let endpoint = manager?.endpoint,
|
|
||||||
endpoint.range(of: host) != nil {
|
|
||||||
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() {
|
|
||||||
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 {
|
fileprivate struct Constants {
|
||||||
static let ExecuteJSCommand = "execute();"
|
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
|
||||||
|
/// Forces the challenge to be explicitly displayed.
|
||||||
|
var forceVisibleChallenge = false {
|
||||||
|
didSet {
|
||||||
|
// Also works on iOS < 9
|
||||||
|
webView.performSelector(
|
||||||
|
onMainThread: "_setCustomUserAgent:",
|
||||||
|
with: forceVisibleChallenge ? Constants.BotUserAgent : nil,
|
||||||
|
waitUntilDone: true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allows validation stubbing for testing
|
||||||
|
public var shouldSkipForTests = false
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Callback for loading state changing
|
||||||
|
var onLoadingChanged: ReCaptcha.BoolParameterClosure?
|
||||||
|
|
||||||
/// Sends the result message
|
/// Sends the result message
|
||||||
fileprivate var completion: ((Response) -> Void)?
|
var completion: ((ReCaptchaResult) -> Void)?
|
||||||
|
|
||||||
/// Configures the webview for display when required
|
/// Configures the webview for display when required
|
||||||
fileprivate var configureWebView: ((WKWebView) -> Void)?
|
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
|
||||||
|
|
||||||
/// The JS message recoder
|
/// The JS message recoder
|
||||||
fileprivate var decoder: ReCaptchaDecoder!
|
fileprivate var decoder: ReCaptchaDecoder!
|
||||||
|
|
||||||
/// Indicates if the script has already been loaded by the `webView`
|
/// 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`
|
/// The observer for `.UIWindowDidBecomeVisible`
|
||||||
fileprivate var observer: NSObjectProtocol?
|
fileprivate var observer: NSObjectProtocol?
|
||||||
|
|
||||||
|
/// The observer for `\WKWebView.estimatedProgress`
|
||||||
|
fileprivate var loadingObservation: NSKeyValueObservation?
|
||||||
|
|
||||||
/// The endpoint url being used
|
/// The endpoint url being used
|
||||||
fileprivate var endpoint: String
|
fileprivate var endpoint: String
|
||||||
|
|
||||||
/// The `webView` delegate implementation
|
|
||||||
fileprivate lazy var webviewDelegate: WebViewDelegate = {
|
|
||||||
WebViewDelegate(manager: self)
|
|
||||||
}()
|
|
||||||
|
|
||||||
/// The webview that executes JS code
|
/// The webview that executes JS code
|
||||||
fileprivate lazy var webView: WKWebView = {
|
lazy var webView: WKWebView = {
|
||||||
let webview = WKWebView(
|
let webview = WKWebView(
|
||||||
frame: CGRect(x: 0, y: 0, width: 1, height: 1),
|
frame: CGRect(x: 0, y: 0, width: 1, height: 1),
|
||||||
configuration: self.buildConfiguration()
|
configuration: self.buildConfiguration()
|
||||||
)
|
)
|
||||||
webview.navigationDelegate = self.webviewDelegate
|
webview.accessibilityIdentifier = "webview"
|
||||||
|
webview.accessibilityTraits = UIAccessibilityTraits.link
|
||||||
webview.isHidden = true
|
webview.isHidden = true
|
||||||
|
self.loadingObservation = webview.observe(\.estimatedProgress, options: [.new]) { [weak self] _, change in
|
||||||
|
self?.didFinishLoading = change.newValue == 1
|
||||||
|
}
|
||||||
|
|
||||||
return webview
|
return webview
|
||||||
}()
|
}()
|
||||||
|
|
@ -136,6 +109,7 @@ open class ReCaptchaWebViewManager {
|
||||||
*/
|
*/
|
||||||
init(html: String, apiKey: String, baseURL: URL, endpoint: String) {
|
init(html: String, apiKey: String, baseURL: URL, endpoint: String) {
|
||||||
self.endpoint = endpoint
|
self.endpoint = endpoint
|
||||||
|
|
||||||
self.decoder = ReCaptchaDecoder { [weak self] result in
|
self.decoder = ReCaptchaDecoder { [weak self] result in
|
||||||
self?.handle(result: result)
|
self?.handle(result: result)
|
||||||
}
|
}
|
||||||
|
|
@ -147,7 +121,7 @@ open class ReCaptchaWebViewManager {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
observer = NotificationCenter.default.addObserver(
|
observer = NotificationCenter.default.addObserver(
|
||||||
forName: .UIWindowDidBecomeVisible,
|
forName: UIWindow.didBecomeVisibleNotification,
|
||||||
object: nil,
|
object: nil,
|
||||||
queue: nil
|
queue: nil
|
||||||
) { [weak self] notification in
|
) { [weak self] notification in
|
||||||
|
|
@ -157,19 +131,20 @@ open class ReCaptchaWebViewManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
- parameters:
|
- parameter view: The view that should present the webview.
|
||||||
- view: The view that should present the webview.
|
|
||||||
- completion: A closure that receives a Result<String, NSError> which may contain a valid result token.
|
|
||||||
|
|
||||||
Starts the challenge validation
|
Starts the challenge validation
|
||||||
*/
|
*/
|
||||||
open func validate(on view: UIView, completion: @escaping (Response) -> Void) {
|
func validate(on view: UIView) {
|
||||||
self.completion = completion
|
onLoadingChanged?(true)
|
||||||
|
#if DEBUG
|
||||||
|
guard !shouldSkipForTests else {
|
||||||
|
completion?(.token(""))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
#endif
|
||||||
webView.isHidden = false
|
webView.isHidden = false
|
||||||
webView.removeFromSuperview()
|
|
||||||
view.addSubview(webView)
|
view.addSubview(webView)
|
||||||
|
|
||||||
execute()
|
execute()
|
||||||
|
|
@ -177,21 +152,24 @@ open class ReCaptchaWebViewManager {
|
||||||
|
|
||||||
|
|
||||||
/// Stops the execution of the webview
|
/// Stops the execution of the webview
|
||||||
open func stop() {
|
func stop() {
|
||||||
webView.stopLoading()
|
webView.stopLoading()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
- parameter configure: A closure that receives an instance of `WKWebView` for configuration.
|
Resets the ReCaptcha.
|
||||||
|
|
||||||
Provides a closure to configure the webview for presentation if necessary.
|
The reset is achieved by calling `grecaptcha.reset()` on the JS API.
|
||||||
|
|
||||||
If presentation is required, the webview will already be a subview of `presenterView` if one is provided. Otherwise
|
|
||||||
it might need to be added in a view currently visible.
|
|
||||||
*/
|
*/
|
||||||
open func configureWebView(_ configure: @escaping (WKWebView) -> Void) {
|
func reset() {
|
||||||
self.configureWebView = configure
|
didFinishLoading = false
|
||||||
|
configureWebViewDispatchToken = UUID()
|
||||||
|
|
||||||
|
webView.evaluateJavaScript(Constants.ResetCommand) { [weak self] _, error in
|
||||||
|
if let error = error {
|
||||||
|
self?.decoder.send(error: .unexpected(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -200,8 +178,7 @@ open class ReCaptchaWebViewManager {
|
||||||
/** Private methods for ReCaptchaWebViewManager
|
/** Private methods for ReCaptchaWebViewManager
|
||||||
*/
|
*/
|
||||||
fileprivate extension 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.
|
This method has no effect if the webview hasn't finished loading.
|
||||||
*/
|
*/
|
||||||
func execute() {
|
func execute() {
|
||||||
|
|
@ -210,10 +187,65 @@ fileprivate extension ReCaptchaWebViewManager {
|
||||||
return
|
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
|
webView.evaluateJavaScript(Constants.ExecuteJSCommand) { [weak self] _, error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
self?.decoder.send(error: .unexpected(error))
|
self?.decoder.send(error: .unexpected(error))
|
||||||
}
|
}
|
||||||
|
self?.onLoadingChanged?(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -240,17 +272,31 @@ fileprivate extension ReCaptchaWebViewManager {
|
||||||
func handle(result: ReCaptchaDecoder.Result) {
|
func handle(result: ReCaptchaDecoder.Result) {
|
||||||
switch result {
|
switch result {
|
||||||
case .token(let token):
|
case .token(let token):
|
||||||
completion?(.success(token))
|
completion?(.token(token))
|
||||||
|
|
||||||
case .error(let error):
|
case .error(let error):
|
||||||
completion?(.failure(error))
|
if shouldResetOnError, let view = webView.superview {
|
||||||
|
reset()
|
||||||
|
validate(on: view)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
completion?(.error(error))
|
||||||
|
}
|
||||||
|
|
||||||
case .showReCaptcha:
|
case .showReCaptcha:
|
||||||
configureWebView?(webView)
|
DispatchQueue.once(token: configureWebViewDispatchToken) { [weak self] in
|
||||||
|
guard let `self` = self else { return }
|
||||||
|
self.configureWebView?(self.webView)
|
||||||
|
}
|
||||||
|
|
||||||
case .didLoad:
|
case .didLoad:
|
||||||
// For testing purposes
|
// For testing purposes
|
||||||
webviewDelegate.execute()
|
didFinishLoading = true
|
||||||
|
|
||||||
|
case .log(let message):
|
||||||
|
#if DEBUG
|
||||||
|
print("[JS LOG]:", message)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,40 +3,85 @@
|
||||||
// ReCaptcha
|
// ReCaptcha
|
||||||
//
|
//
|
||||||
// Created by Flávio Caetano on 11/04/17.
|
// Created by Flávio Caetano on 11/04/17.
|
||||||
// Copyright © 2017 ReCaptcha. All rights reserved.
|
// Copyright © 2018 ReCaptcha. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import RxSwift
|
import RxSwift
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
/// Makes ReCaptchaWebViewManager compatible with RxSwift extensions
|
public enum ReCaptchaRxError: Error {
|
||||||
extension ReCaptchaWebViewManager: ReactiveCompatible {}
|
case baseWasReleased
|
||||||
|
}
|
||||||
|
|
||||||
/// Provides a public extension on ReCaptchaWebViewManager that makes it reactive.
|
/// Makes ReCaptcha compatible with RxSwift extensions
|
||||||
public extension Reactive where Base: ReCaptchaWebViewManager {
|
extension ReCaptcha: ReactiveCompatible {}
|
||||||
|
|
||||||
|
/// Provides a public extension on ReCaptcha that makes it reactive.
|
||||||
|
public extension Reactive where Base: ReCaptcha {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
- parameter view: The view that should present the webview.
|
- parameters:
|
||||||
|
- view: The view that should present the webview.
|
||||||
|
- resetOnError: If ReCaptcha should be reset if it errors. Defaults to `true`
|
||||||
|
|
||||||
Starts the challenge validation uppon subscription.
|
Starts the challenge validation uppon subscription.
|
||||||
|
|
||||||
The stream's element is a `Result<String, ReCaptchaError>` that may contain a valid token.
|
The stream's element is a String with the validation token.
|
||||||
|
|
||||||
Sends `stop()` uppon disposal.
|
Sends `stop()` uppon disposal.
|
||||||
|
|
||||||
- See: `ReCaptchaWebViewManager.validate(on:completion:)`
|
- See: `ReCaptcha.validate(on:resetOnError:completion:)`
|
||||||
- See: `ReCaptchaWebViewManager.stop()`
|
- See: `ReCaptcha.stop()`
|
||||||
*/
|
*/
|
||||||
func validate(on view: UIView) -> Observable<Base.Response> {
|
func validate(on view: UIView, resetOnError: Bool = true) -> Observable<String> {
|
||||||
return Observable<Base.Response>.create { [weak base] (observer: AnyObserver<Base.Response>) in
|
return Single<String>.create { [weak base] single in
|
||||||
base?.validate(on: view) { response in
|
base?.validate(on: view, resetOnError: resetOnError) { result in
|
||||||
observer.onNext(response)
|
switch result {
|
||||||
observer.onCompleted()
|
case .token(let token):
|
||||||
|
single(.success(token))
|
||||||
|
|
||||||
|
case .error(let error):
|
||||||
|
single(.error(error))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Disposables.create { [weak base] in
|
return Disposables.create { [weak base] in
|
||||||
base?.stop()
|
base?.stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Resets the ReCaptcha.
|
||||||
|
|
||||||
|
The reset is achieved by calling `grecaptcha.reset()` on the JS API.
|
||||||
|
|
||||||
|
- See: `ReCaptcha.reset()`
|
||||||
|
*/
|
||||||
|
var reset: AnyObserver<Void> {
|
||||||
|
return AnyObserver { [weak base] event in
|
||||||
|
guard case .next = event else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
// ReCaptcha
|
// ReCaptcha
|
||||||
//
|
//
|
||||||
// Created by Flávio Caetano on 10/10/17.
|
// Created by Flávio Caetano on 10/10/17.
|
||||||
// Copyright © 2017 ReCaptcha. All rights reserved.
|
// Copyright © 2018 ReCaptcha. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
|
|
@ -10,10 +10,10 @@ default_platform :ios
|
||||||
platform :ios do
|
platform :ios do
|
||||||
skip_docs
|
skip_docs
|
||||||
|
|
||||||
devices = ["iPhone X (11.0)"]
|
devices = ["iPhone XR (~> 12)"]
|
||||||
devices << "iPhone 7 (10.0)" if !Helper.is_ci?
|
devices << "iPhone X (~> 11)" if !Helper.is_ci?
|
||||||
devices << "iPhone 6 (9.0)" if !Helper.is_ci?
|
devices << "iPhone 7 (~> 10)" if !Helper.is_ci?
|
||||||
devices << "iPhone 5s (8.1)" 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"
|
desc "Runs the following lanes:\n- test\n- pod_lint\n- carthage_lint"
|
||||||
lane :ci do
|
lane :ci do
|
||||||
|
|
@ -32,6 +32,7 @@ platform :ios do
|
||||||
swiftlint(
|
swiftlint(
|
||||||
executable: "Example/Pods/Swiftlint/swiftlint",
|
executable: "Example/Pods/Swiftlint/swiftlint",
|
||||||
strict: true,
|
strict: true,
|
||||||
|
reporter: "emoji",
|
||||||
)
|
)
|
||||||
|
|
||||||
# The problem lies in the fact (or rather: serious bug in xcodebuild) that
|
# The problem lies in the fact (or rather: serious bug in xcodebuild) that
|
||||||
|
|
@ -42,7 +43,7 @@ platform :ios do
|
||||||
# https://stackoverflow.com/questions/37922146/xctests-failing-on-physical-device-canceling-tests-due-to-timeout/40790171#40790171
|
# https://stackoverflow.com/questions/37922146/xctests-failing-on-physical-device-canceling-tests-due-to-timeout/40790171#40790171
|
||||||
scan(
|
scan(
|
||||||
build_for_testing: true,
|
build_for_testing: true,
|
||||||
devices: devices,
|
devices: self.select_similar_simulator(devices),
|
||||||
scheme: "ReCaptcha-Example",
|
scheme: "ReCaptcha-Example",
|
||||||
workspace: "Example/ReCaptcha.xcworkspace",
|
workspace: "Example/ReCaptcha.xcworkspace",
|
||||||
code_coverage: true,
|
code_coverage: true,
|
||||||
|
|
@ -50,7 +51,7 @@ platform :ios do
|
||||||
|
|
||||||
scan(
|
scan(
|
||||||
test_without_building: true,
|
test_without_building: true,
|
||||||
devices: devices,
|
devices: self.select_similar_simulator(devices),
|
||||||
scheme: "ReCaptcha-Example",
|
scheme: "ReCaptcha-Example",
|
||||||
workspace: "Example/ReCaptcha.xcworkspace",
|
workspace: "Example/ReCaptcha.xcworkspace",
|
||||||
code_coverage: true,
|
code_coverage: true,
|
||||||
|
|
@ -125,18 +126,17 @@ platform :ios do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
after_all do |lane|
|
# Private
|
||||||
# This block is called, only if the executed lane was successful
|
|
||||||
|
|
||||||
# slack(
|
def select_similar_simulator(args)
|
||||||
# message: "Successfully deployed new App Update."
|
args.map { |device_string|
|
||||||
# )
|
pieces = device_string.split(' (')
|
||||||
end
|
FastlaneCore::Simulator.all
|
||||||
|
.select { |s| s.name == pieces.first }
|
||||||
error do |lane, exception|
|
.sort_by { |s| Gem::Version.create(s.os_version) }
|
||||||
# slack(
|
.detect { |s| Gem::Requirement.new(pieces[1].tr('()', '')).satisfied_by?(Gem::Version.create(s.os_version)) }
|
||||||
# message: exception.message,
|
}
|
||||||
# success: false
|
.compact
|
||||||
# )
|
.map { |s| "#{s.name} (#{s.ios_version})"}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue