Compare commits

...

59 Commits

Author SHA1 Message Date
Artur b40f0ac185 Update to master branch 2019-10-03 16:09:07 +03:00
Artur Azarau dbcad92ff0 cartfile resolved updated 2019-10-02 09:51:39 +03:00
Artur Azarau 605e1aeeef branch changed 2019-10-02 09:49:50 +03:00
Ivan Babkin 6e712bf664 Carthage update 2019-06-17 19:43:13 +03:00
Ivan Babkin 0b860372e9
Merge pull request #3 from ZharaOo/fix/cartfile
Using RxSwift binary
2019-06-17 19:39:04 +03:00
Ivan Babkin c8f4fb213e Using RxSwift binary 2019-06-17 19:37:56 +03:00
Ivan Babkin 7ba0aff437
Merge pull request #2 from ZharaOo/feature/loading_observable
Added loadingObservable property
2019-06-17 19:32:57 +03:00
Ivan Babkin b3994f932e Added possibility to observe loading state changing 2019-06-17 19:32:23 +03:00
Ivan Babkin 1d178f7154
Merge pull request #1 from ZharaOo/fix/execute
Fixed unexpected error on old devices
2019-06-11 14:16:28 +03:00
Ivan Babkin 784187e911 Fixed unexpected error on old devices 2019-06-03 22:44:24 +03:00
Flávio Caetano 31a64e5967 Update issue template to include ReCaptcha v2 docs 2019-04-04 17:21:28 -03:00
Flávio Caetano ce8acad6a2 Add incompatibility with ReCaptcha v3 to README 2019-04-04 17:17:42 -03:00
Rachit Mishra 0e47c7264f Add warning to uncheck verify origin settings in recaptcha (#68)
* Add warning to uncheck verify origin 

If verify origin is checked it leads to JS error code 4

* Add screenshot for verify domain settings

* Update file name for verify origin settings

* Add example image for verify origin

* Remove redundant space
2019-03-20 10:59:44 -03:00
Flávio Caetano c9afbc22c1 Version Bump (1.4.2) 2018-12-06 16:02:45 -02:00
Flávio Caetano 050ce19425 Fix webview's resource loading detection
fix #56
2018-12-06 15:53:51 -02:00
Flávio Caetano dc4010cc6e Version Bump (1.4.1) 2018-11-12 15:36:58 -02:00
Flávio Caetano d0af0f686b Fix Fastlane test devices
Launching iPhone 5s simulator is unreliable and often causes tests to fail. Upgraded iOS 9 device to iPhone 6s
2018-11-12 15:02:34 -02:00
Flávio Caetano 59c5223580 Updating documentation 2018-11-12 14:15:57 -02:00
Maxim Tsvetkov 484ed94a9b Change Rx dependencies version from '~> 4.3.1' to '~> 4.3' (#59)
* - Change Rx dependencies version from '~> 4.3.1' to '~> 4.3' in podspec and podfile
- Update pods

* Change Rx dependencies version from '~> 4.3.1' to '~> 4.3' in cartfile
2018-11-12 14:15:25 -02:00
Flávio Caetano 7dc2722bb5 Version Bump (1.4) 2018-09-27 16:02:55 -03:00
Flávio Caetano 9f3ac2efa7 Fix test devices on Fastfile 2018-09-27 15:29:48 -03:00
Flávio Caetano ee878521a3 Improve bug_report.md 2018-09-27 15:25:56 -03:00
Flávio Caetano 184be54b90 Add issue template 2018-09-27 15:23:19 -03:00
Przemysław Wosko a88fcb9850 Feature: enable validation to be skipped for testing
* fix visibility of shouldSkipForUITests flag

* fix visibility of shouldSkipForUITests flag

* fix token bypassing

* Add improvements from review

fix #51
2018-09-27 15:00:22 -03:00
manderson420 50046bff25 Updates to support Swift 4.2. (#52)
* Updates to support Swift 4.2.

* Updating osx_image on Travis config

* Add additional 4.2 updates in example project and specify swift 4.2 in Recaptcha and Recaptcha-Carthage projects.

* Update RxCocoa with latest swift 4.2 fixes.

* Bump RxSwift dependency to match podfile version.

* Update podfile.lock.

* Bump fastlane and cocoapods versions, specify swift_version in the podspec and remove the deprecated .swift-version file.

* Update Podfile.lock.
2018-09-26 15:26:52 -03:00
Flávio Caetano 0389c5afa0 Version Bump (1.3.1) 2018-09-07 20:17:47 -03:00
Flávio Caetano a9117cbb7d Fix: Removing Result from Carthage project 2018-09-07 20:17:47 -03:00
Flávio Caetano 11074817f1 Fix: Removing leftover print 2018-09-07 19:17:40 -03:00
Flávio Caetano e540042554 Updating osx_image on Travis config 2018-09-07 19:00:10 -03:00
Flávio Caetano 438bb57c38 Removing Result dependency from Carthage 2018-09-07 18:59:27 -03:00
Flávio Caetano 3821c300b7 Adding documentation_url to podspec 2018-09-06 16:15:06 -03:00
Flávio Caetano 87355c1b13 Adding jazzy config file 2018-09-06 16:14:53 -03:00
Flávio Caetano 3c5cb4fe59 Version Bump (1.3) 2018-09-06 15:03:56 -03:00
Flávio Caetano 66b70da874 Fix: multiple configure calls after app being idle for 10 mins
#40
2018-09-06 14:57:18 -03:00
Flávio Caetano 1c3af347e4 Feature: Enable Locale support
fix #39
2018-07-12 16:06:10 -04:00
Flávio Caetano 0e3028956a Upgrading Dev dependencies
- Fix: upgrade SwiftLint and migrate warnings
- Upgrading RxCocoa to 4.2.0
- Upgrading RxBlocking to 4.2.0
- Upgrading AppSwizzle to 1.3
2018-07-12 13:57:50 -04:00
Flávio Caetano 0642f8d832 Upgrading RxSwift to 4.2.0 2018-07-12 13:22:18 -04:00
Flávio Caetano 9d061aaaff Fix: Reset not flagging ReCaptcha as ready-to-execute
fix #36
2018-07-12 11:17:04 -04:00
Flávio Caetano 5989547f40 Readme: Help wanted 2018-06-14 15:31:48 -03:00
Flávio Caetano 667b78c2f9 Add link to docs on README 2018-03-12 18:07:12 -03:00
Flávio Caetano a329de8859 Version bump (1.2) 2018-03-12 16:48:42 -03:00
Flávio Caetano 4b814c56ab Fix: configureWebView gets called multiple times
fix #31
2018-03-12 16:34:37 -03:00
Flávio Caetano 8160d36ef9 Fix: Better encapsulation with new achitecture 2018-03-12 16:31:35 -03:00
Flávio Caetano 8a91279bd5 Feature: Enable forcing challenge to be visible on DEBUG
fix #19
2018-03-12 12:34:14 -03:00
Flávio Caetano cab34b882f Fix: Completion being called consecutively
fix #29
2018-03-07 20:43:53 -03:00
Flávio Caetano 678da21e63 Fix: Readme with Rx being String 2018-03-07 18:10:09 -03:00
Flávio Caetano 3ec88fcf7e Fix: Using Single for rx.validate
fix #26
2018-03-07 11:25:08 -03:00
Flávio Caetano 55d8988b77 Fix: Test Rx methods using RxBlocking
fix #25
2018-03-07 11:25:08 -03:00
Flávio Caetano ce03959fe9 Fix: Retiring Result lib
fix #24
2018-03-06 18:52:50 -03:00
Flávio Caetano 9212a489a5 Feature: Resettable ReCaptchas
fix #23
2018-03-06 14:59:01 -03:00
Flávio Caetano e2a38dda22 Feature: debug logging 2018-03-05 18:36:56 -03:00
Flávio Caetano c6a00aca8e Fix: updating docs 2018-03-05 17:13:01 -03:00
Flávio Caetano 6a6fc22ae0 Updating docs license year 2018-01-29 12:37:52 -02:00
Flávio Caetano 53aecff93b Version bump (1.1) 2018-01-29 11:32:46 -02:00
Flávio Caetano 3685f7d195 Fix: better logging for when protocol isn't found on 2018-01-29 11:28:33 -02:00
Flávio Caetano 5f1624b9e1 Fix: Better simulator versioning in Fastfile 2018-01-29 08:27:31 -05:00
Flávio Caetano a52b2686cb UI Tests for happy paths 2018-01-29 08:27:31 -05:00
Flávio Caetano 2dd15c0845 Fix: Alternate endpoint not loading
Fix #17
2018-01-29 08:27:31 -05:00
Flávio Caetano 11818ab473 Fix: Prepends a scheme if doesn't have one
Fix #18
2018-01-19 12:33:03 -02:00
51 changed files with 1753 additions and 528 deletions

33
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -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.

3
.gitignore vendored
View File

@ -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

14
.jazzy.yaml Normal file
View File

@ -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

View File

@ -1 +0,0 @@
4.0

View File

@ -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\.
\/\/ \/\/

View File

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

View File

@ -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)

View File

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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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 = (

View File

@ -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"

View File

@ -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>

View File

@ -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

View File

@ -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"/>

View File

@ -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>

View File

@ -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" : {

View File

@ -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)
}
} }
} }

View File

@ -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

View File

@ -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)
}
} }

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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)
} }
} }

View File

@ -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")
}
}

View File

@ -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)
}
} }

View File

@ -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

View File

@ -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

View File

@ -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)
}
} }

View File

@ -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
} }
} }

View File

@ -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)
}
}
} }

View File

@ -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>

View File

@ -0,0 +1,5 @@
disabled_rules:
- type_name
- nesting
- force_unwrapping
- explicit_top_level_acl

View File

@ -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>

View File

@ -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)
}
}

View File

@ -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'

View File

@ -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

View File

@ -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).
![ReCaptcha v2 invisible key example](https://raw.githubusercontent.com/fjcaetano/ReCaptcha/master/example-v2-key.png)
You won't be able to use a ReCaptcha v3 key because it requires server-side validation. On v3, all
challenges succeed into a token which is then validated in the server for a score. For this reason,
a frontend app can't know on its own wether or not a user is valid since the challenge will always
result in a valid token.
## 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.

View File

@ -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;
}; };

View File

@ -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

View File

@ -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>

View File

@ -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()
} }
} }

View File

@ -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
} }
} }

View File

@ -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)
} }
} }

View File

@ -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

View File

@ -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
}
}
}

View File

@ -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
} }
} }

View File

@ -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 }
}
} }
} }

View File

@ -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

BIN
example-v2-key.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -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