diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..a4a9c78 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,33 @@ +--- +name: Bug report +about: Create a report to help us improve +--- + + +## 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. diff --git a/.gitignore b/.gitignore index 0ac9602..8cf9e54 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ build/ DerivedData logs/ ReCaptcha.framework.zip +bin/ ## Various settings *.pbxuser @@ -51,3 +52,6 @@ fastlane/Preview.html fastlane/screenshots fastlane/README.md fastlane/test_output + +## VS Code +.vscode diff --git a/.swift-version b/.swift-version deleted file mode 100644 index 5186d07..0000000 --- a/.swift-version +++ /dev/null @@ -1 +0,0 @@ -4.0 diff --git a/.swiftlint.yml b/.swiftlint.yml index fc5bcc9..de09b8b 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -10,7 +10,7 @@ disabled_rules: - weak_delegate opt_in_rules: -# - array_init # is causing a false positive on `.map { !$0 }` + - array_init - closure_end_indentation - closure_spacing - contains_over_first_not_nil @@ -31,7 +31,8 @@ opt_in_rules: - redundant_nil_coalescing - single_test_class - sorted_imports -# - trailing_closure # causing a false positive on `.subscribe(onNext:)` + - trailing_closure: + - only_single_muted_parameter - unneeded_parentheses_in_closure_argument vertical_whitespace: @@ -44,5 +45,5 @@ file_header: \/\/ ReCaptcha \/\/ \/\/ Created by .*? on \d{1,2}\/\d{1,2}\/\d{2}\. - \/\/ Copyright © 2018 ReCaptcha\. All rights reserved\. + \/\/ Copyright © 2021 ReCaptcha\. All rights reserved\. \/\/ diff --git a/.travis.yml b/.travis.yml index a0c4e9b..7f45e04 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -osx_image: xcode9.2 +osx_image: xcode12.2 language: objective-c cache: diff --git a/CHANGELOG.md b/CHANGELOG.md index f577769..f498473 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,30 @@ +# 1.6.0 + +- RxSwift 6.0.0 support (#101) +- Feature: added 2 new cases to ReCaptchaError (`.responseExpired` and `.failedRender`) (#79) + +- Fix: retire JS arrow functions in favor of standard functions (#78) + +# 1.5.0 + +- Swift 5.0 support +- Feature: `didFinishLoading` callback notifier + +- Fix: Resources loading validation (#72 #56 #60) + +# 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 diff --git a/Cartfile b/Cartfile index 4cb3fb3..bcb3591 100644 --- a/Cartfile +++ b/Cartfile @@ -1 +1 @@ -github "ReactiveX/RxSwift" ~> 4.0 +github "ReactiveX/RxSwift" ~> 6.0 diff --git a/Cartfile.resolved b/Cartfile.resolved index 00b43a8..c07923a 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1 +1 @@ -github "ReactiveX/RxSwift" "4.2.0" +github "ReactiveX/RxSwift" "6.0.0" diff --git a/Example/Podfile b/Example/Podfile index df9bc4a..20ff1b9 100644 --- a/Example/Podfile +++ b/Example/Podfile @@ -1,18 +1,18 @@ -platform :ios, 8.3 +platform :ios, 9.0 use_frameworks! inhibit_all_warnings! target 'ReCaptcha_Example' do pod 'ReCaptcha/RxSwift', :path => '../' - pod 'RxCocoa', '~> 4.0' - pod 'SwiftLint', '~> 0.24' + pod 'RxCocoa', '~> 6.0' + pod 'SwiftLint', '~> 0.33' target 'ReCaptcha_Tests' do inherit! :search_paths pod 'AppSwizzle', '~> 1.3' - pod 'RxBlocking', '~> 4.0' + pod 'RxBlocking', '~> 6.0' end target 'ReCaptcha_UITests' do diff --git a/Example/Podfile.lock b/Example/Podfile.lock index fbd7458..e957acb 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,35 +1,48 @@ PODS: - AppSwizzle (1.3.1) - - ReCaptcha/Core (1.2) - - ReCaptcha/RxSwift (1.2): + - ReCaptcha/Core (1.6.0) + - ReCaptcha/RxSwift (1.6.0): - ReCaptcha/Core - - RxSwift (~> 4.0) - - RxBlocking (4.2.0): - - RxSwift (~> 4.0) - - RxCocoa (4.2.0): - - RxSwift (~> 4.0) - - RxSwift (4.2.0) - - SwiftLint (0.26.0) + - RxSwift (~> 6.0) + - RxBlocking (6.0.0): + - RxSwift (= 6.0.0) + - RxCocoa (6.0.0): + - RxRelay (= 6.0.0) + - RxSwift (= 6.0.0) + - RxRelay (6.0.0): + - RxSwift (= 6.0.0) + - RxSwift (6.0.0) + - SwiftLint (0.33.0) DEPENDENCIES: - AppSwizzle (~> 1.3) - ReCaptcha/RxSwift (from `../`) - - RxBlocking (~> 4.0) - - RxCocoa (~> 4.0) - - SwiftLint (~> 0.24) + - RxBlocking (~> 6.0) + - RxCocoa (~> 6.0) + - SwiftLint (~> 0.33) + +SPEC REPOS: + trunk: + - AppSwizzle + - RxBlocking + - RxCocoa + - RxRelay + - RxSwift + - SwiftLint EXTERNAL SOURCES: ReCaptcha: - :path: ../ + :path: "../" SPEC CHECKSUMS: AppSwizzle: db36e436f56110d93e5ae0147683435df593cabc - ReCaptcha: ffa108380b3eaca6c1448dcfe38eb5dab8083a67 - RxBlocking: e339d8a6e752e25ade95ff858466c55436668f59 - RxCocoa: 0b54909c902e1e581212a03e690bbd94032d8baa - RxSwift: 99e10317ddfcc7fbe01356aafd118fde4a0be104 - SwiftLint: f6b83e8d95ee1e91e11932d843af4fdcbf3fc764 + ReCaptcha: d493ed256054a7b6a7eabcaf0b47ec9a8f42a16e + RxBlocking: c025b8c6fe08c5b18c039b5b11d41ec413f8a59e + RxCocoa: 3f79328fafa3645b34600f37c31e64c73ae3a80e + RxRelay: 8d593be109c06ea850df027351beba614b012ffb + RxSwift: c14e798c59b9f6e9a2df8fd235602e85cc044295 + SwiftLint: fed9c66336e41fc74dc48a73678380718f0c8b0e -PODFILE CHECKSUM: 0b5282c7433d7f669094e5e24684866bae899e4b +PODFILE CHECKSUM: 927f56f754e86345b695231d8b5f252ec7c18b35 -COCOAPODS: 1.4.0 +COCOAPODS: 1.10.0 diff --git a/Example/ReCaptcha.xcodeproj/project.pbxproj b/Example/ReCaptcha.xcodeproj/project.pbxproj index 2151e6c..cc4a8ce 100644 --- a/Example/ReCaptcha.xcodeproj/project.pbxproj +++ b/Example/ReCaptcha.xcodeproj/project.pbxproj @@ -262,7 +262,6 @@ 607FACCD1AFB9204008FA782 /* Frameworks */, 607FACCE1AFB9204008FA782 /* Resources */, 8F03FFB3F5C55E873C23C682 /* [CP] Embed Pods Frameworks */, - ED1C0E07490C9C4B4A401061 /* [CP] Copy Pods Resources */, F231B3981FEC3B7F00F82943 /* SwiftLint */, ); buildRules = ( @@ -282,8 +281,6 @@ F28FAC98200E425600E14987 /* Sources */, F28FAC99200E425600E14987 /* Frameworks */, F28FAC9A200E425600E14987 /* Resources */, - BF2909E4520B29BFFBC94AD6 /* [CP] Embed Pods Frameworks */, - DCB1D99E77E589CD76CD3186 /* [CP] Copy Pods Resources */, F28FACA6200E447600E14987 /* ShellScript */, ); buildRules = ( @@ -305,7 +302,6 @@ F2ECCF731E9FC47B0097B199 /* Frameworks */, F2ECCF741E9FC47B0097B199 /* Resources */, 77003100630E7783A936C451 /* [CP] Embed Pods Frameworks */, - 44209D7CDDDE3A11075B8104 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -329,16 +325,19 @@ TargetAttributes = { 607FACCF1AFB9204008FA782 = { CreatedOnToolsVersion = 6.3.1; - LastSwiftMigration = 0900; + DevelopmentTeam = 58EEZG76L8; + LastSwiftMigration = 1020; + ProvisioningStyle = Manual; }; F28FAC9B200E425600E14987 = { CreatedOnToolsVersion = 9.1; + LastSwiftMigration = 1020; ProvisioningStyle = Automatic; TestTargetID = 607FACCF1AFB9204008FA782; }; F2ECCF751E9FC47B0097B199 = { CreatedOnToolsVersion = 8.3; - LastSwiftMigration = 0900; + LastSwiftMigration = 1020; ProvisioningStyle = Automatic; TestTargetID = 607FACCF1AFB9204008FA782; }; @@ -349,6 +348,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, ); @@ -394,21 +394,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 44209D7CDDDE3A11075B8104 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ReCaptcha_Tests/Pods-ReCaptcha_Tests-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; 51299F67A8756E2B3EAE411A /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -433,7 +418,7 @@ files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-ReCaptcha_Tests/Pods-ReCaptcha_Tests-frameworks.sh", + "${PODS_ROOT}/Target Support Files/Pods-ReCaptcha_Tests/Pods-ReCaptcha_Tests-frameworks.sh", "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework", "${BUILT_PRODUCTS_DIR}/AppSwizzle/AppSwizzle.framework", "${BUILT_PRODUCTS_DIR}/RxBlocking/RxBlocking.framework", @@ -446,7 +431,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ReCaptcha_Tests/Pods-ReCaptcha_Tests-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReCaptcha_Tests/Pods-ReCaptcha_Tests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 8F03FFB3F5C55E873C23C682 /* [CP] Embed Pods Frameworks */ = { @@ -455,20 +440,22 @@ files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-ReCaptcha_Example/Pods-ReCaptcha_Example-frameworks.sh", + "${PODS_ROOT}/Target Support Files/Pods-ReCaptcha_Example/Pods-ReCaptcha_Example-frameworks.sh", "${BUILT_PRODUCTS_DIR}/ReCaptcha/ReCaptcha.framework", "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework", + "${BUILT_PRODUCTS_DIR}/RxRelay/RxRelay.framework", "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReCaptcha.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxRelay.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ReCaptcha_Example/Pods-ReCaptcha_Example-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReCaptcha_Example/Pods-ReCaptcha_Example-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; B8A66872166B84DAD39A3E1F /* [CP] Check Pods Manifest.lock */ = { @@ -489,36 +476,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - BF2909E4520B29BFFBC94AD6 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ReCaptcha_UITests/Pods-ReCaptcha_UITests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - DCB1D99E77E589CD76CD3186 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ReCaptcha_UITests/Pods-ReCaptcha_UITests-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; DDB47454887253730AB35230 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -537,21 +494,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - ED1C0E07490C9C4B4A401061 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ReCaptcha_Example/Pods-ReCaptcha_Example-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; F231B3981FEC3B7F00F82943 /* SwiftLint */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -564,7 +506,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/SwiftLint/swiftlint\" --path \"${PROJECT_DIR}/..\""; + shellScript = "\"${PODS_ROOT}/SwiftLint/swiftlint\" --path \"${PROJECT_DIR}/..\"\n"; }; F28FACA6200E447600E14987 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; @@ -756,14 +698,16 @@ baseConfigurationReference = 80FF4E03D71AACBD81A36301 /* Pods-ReCaptcha_Example.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = ""; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = 58EEZG76L8; INFOPLIST_FILE = ReCaptcha/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MODULE_NAME = ExampleApp; PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-Example"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + PROVISIONING_PROFILE_SPECIFIER = "Development Wildcard"; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -772,14 +716,16 @@ baseConfigurationReference = C2A0BDD35B5E219129E9BC65 /* Pods-ReCaptcha_Example.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = ReCaptcha/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MODULE_NAME = ExampleApp; PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-Example"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; }; name = Release; }; @@ -802,7 +748,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "ReCaptcha.ReCaptcha-UITests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = ReCaptcha_Example; }; @@ -825,7 +771,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "ReCaptcha.ReCaptcha-UITests"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = ReCaptcha_Example; }; @@ -847,8 +793,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG UNIT_TESTS"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ReCaptcha_Example.app/ReCaptcha_Example"; }; name = Debug; @@ -867,8 +812,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ReCaptcha_Example.app/ReCaptcha_Example"; }; name = Release; diff --git a/Example/ReCaptcha.xcodeproj/xcshareddata/xcschemes/ReCaptcha-Example.xcscheme b/Example/ReCaptcha.xcodeproj/xcshareddata/xcschemes/ReCaptcha-Example.xcscheme index 38715b9..f31bfd0 100644 --- a/Example/ReCaptcha.xcodeproj/xcshareddata/xcschemes/ReCaptcha-Example.xcscheme +++ b/Example/ReCaptcha.xcodeproj/xcshareddata/xcschemes/ReCaptcha-Example.xcscheme @@ -54,7 +54,7 @@ + skipped = "YES"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/ReCaptcha/AppDelegate.swift b/Example/ReCaptcha/AppDelegate.swift index 299ae8d..86054b4 100644 --- a/Example/ReCaptcha/AppDelegate.swift +++ b/Example/ReCaptcha/AppDelegate.swift @@ -16,7 +16,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application( _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { // Override point for customization after application launch. return true diff --git a/Example/ReCaptcha/Images.xcassets/AppIcon.appiconset/Contents.json b/Example/ReCaptcha/Images.xcassets/AppIcon.appiconset/Contents.json index b8236c6..19882d5 100644 --- a/Example/ReCaptcha/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/Example/ReCaptcha/Images.xcassets/AppIcon.appiconset/Contents.json @@ -39,6 +39,11 @@ "idiom" : "iphone", "size" : "60x60", "scale" : "3x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" } ], "info" : { diff --git a/Example/ReCaptcha/ViewController.swift b/Example/ReCaptcha/ViewController.swift index 8fbe858..67b25ad 100644 --- a/Example/ReCaptcha/ViewController.swift +++ b/Example/ReCaptcha/ViewController.swift @@ -60,7 +60,15 @@ class ViewController: UIViewController { @IBAction private func didPressButton(button: UIButton) { disposeBag = DisposeBag() - let validate = recaptcha.rx.validate(on: view) + recaptcha.rx.didFinishLoading + .debug("did finish loading") + .subscribe() + .disposed(by: disposeBag) + + let validate = recaptcha.rx.validate(on: view, resetOnError: false) + .catch { error in + return .just("Error \(error)") + } .debug("validate") .share() @@ -75,7 +83,7 @@ class ViewController: UIViewController { let isEnabled = isLoading .map { !$0 } - .catchErrorJustReturn(false) + .catchAndReturn(false) .share(replay: 1) isEnabled diff --git a/Example/ReCaptcha_Tests/.swiftlint.yml b/Example/ReCaptcha_Tests/.swiftlint.yml index 8967e41..777177c 100644 --- a/Example/ReCaptcha_Tests/.swiftlint.yml +++ b/Example/ReCaptcha_Tests/.swiftlint.yml @@ -5,3 +5,5 @@ disabled_rules: - explicit_top_level_acl - function_body_length - identifier_name + - file_length + - type_body_length diff --git a/Example/ReCaptcha_Tests/Core/ReCaptchaDecoder__Tests.swift b/Example/ReCaptcha_Tests/Core/ReCaptchaDecoder__Tests.swift index 2f1e75d..bb08513 100644 --- a/Example/ReCaptcha_Tests/Core/ReCaptchaDecoder__Tests.swift +++ b/Example/ReCaptcha_Tests/Core/ReCaptchaDecoder__Tests.swift @@ -164,4 +164,80 @@ class ReCaptchaDecoder__Tests: XCTestCase { // Check XCTAssertEqual(result, .didLoad) } + + func test__Decode__Error_Setup_Failed() { + let exp = expectation(description: "send error") + var result: Result? + + assertResult = { res in + result = res + exp.fulfill() + } + + // Send + let message = MockMessage(message: ["error": 27]) + decoder.send(message: message) + + waitForExpectations(timeout: 1) + + // Check + XCTAssertEqual(result, .error(.failedSetup)) + } + + func test__Decode__Error_Response_Expired() { + let exp = expectation(description: "send error") + var result: Result? + + assertResult = { res in + result = res + exp.fulfill() + } + + // Send + let message = MockMessage(message: ["error": 28]) + decoder.send(message: message) + + waitForExpectations(timeout: 1) + + // Check + XCTAssertEqual(result, .error(.responseExpired)) + } + + func test__Decode__Error_Render_Failed() { + let exp = expectation(description: "send error") + var result: Result? + + assertResult = { res in + result = res + exp.fulfill() + } + + // Send + let message = MockMessage(message: ["error": 29]) + decoder.send(message: message) + + waitForExpectations(timeout: 1) + + // Check + XCTAssertEqual(result, .error(.failedRender)) + } + + func test__Decode__Error_Wrong_Format() { + let exp = expectation(description: "send error") + var result: Result? + + assertResult = { res in + result = res + exp.fulfill() + } + + // Send + let message = MockMessage(message: ["error": 26]) + decoder.send(message: message) + + waitForExpectations(timeout: 1) + + // Check + XCTAssertEqual(result, .error(.wrongMessageFormat)) + } } diff --git a/Example/ReCaptcha_Tests/Core/ReCaptchaWebViewManager__Tests.swift b/Example/ReCaptcha_Tests/Core/ReCaptchaWebViewManager__Tests.swift index d690a0f..2a9e5b6 100644 --- a/Example/ReCaptcha_Tests/Core/ReCaptchaWebViewManager__Tests.swift +++ b/Example/ReCaptcha_Tests/Core/ReCaptchaWebViewManager__Tests.swift @@ -366,6 +366,22 @@ class ReCaptchaWebViewManager__Tests: XCTestCase { XCTAssertEqual(result?.token, apiKey) } + func test__Validate__Should_Skip_For_Tests() { + let exp = expectation(description: "did skip validation") + + let manager = ReCaptchaWebViewManager() + manager.shouldSkipForTests = true + + manager.completion = { result in + XCTAssertEqual(result.token, "") + exp.fulfill() + } + + manager.validate(on: presenterView) + + waitForExpectations(timeout: 1) + } + // MARK: Force Challenge Visible func test__Force_Visible_Challenge() { @@ -382,4 +398,38 @@ class ReCaptchaWebViewManager__Tests: XCTestCase { manager.forceVisibleChallenge = false XCTAssertNotEqual(manager.webView.customUserAgent?.isEmpty, false) } + + // MARK: On Did Finish Loading + + func test__Did_Finish_Loading__Immediate() { + let exp = expectation(description: "did finish loading") + + let manager = ReCaptchaWebViewManager() + + /// Should call closure immediately since it's already loaded + manager.onDidFinishLoading = { + manager.onDidFinishLoading = exp.fulfill + } + + waitForExpectations(timeout: 1) + } + + func test__Did_Finish_Loading__Delayed() { + let exp = expectation(description: "did finish loading") + + let manager = ReCaptchaWebViewManager(shouldFail: true) + + var called = false + manager.onDidFinishLoading = { + called = true + } + + XCTAssertFalse(called) + + // Reset + manager.onDidFinishLoading = exp.fulfill + manager.reset() + + waitForExpectations(timeout: 3) + } } diff --git a/Example/ReCaptcha_Tests/Helpers/ReCaptchaError+Equatable.swift b/Example/ReCaptcha_Tests/Helpers/ReCaptchaError+Equatable.swift index 7e768fe..264edfa 100644 --- a/Example/ReCaptcha_Tests/Helpers/ReCaptchaError+Equatable.swift +++ b/Example/ReCaptcha_Tests/Helpers/ReCaptchaError+Equatable.swift @@ -15,7 +15,10 @@ extension ReCaptchaError: Equatable { case (.htmlLoadError, .htmlLoadError), (.apiKeyNotFound, .apiKeyNotFound), (.baseURLNotFound, .baseURLNotFound), - (.wrongMessageFormat, .wrongMessageFormat): + (.wrongMessageFormat, .wrongMessageFormat), + (.failedSetup, .failedSetup), + (.responseExpired, .responseExpired), + (.failedRender, .failedRender): return true case (.unexpected(let lhe as NSError), .unexpected(let rhe as NSError)): return lhe == rhe @@ -25,11 +28,14 @@ extension ReCaptchaError: Equatable { } static func random() -> ReCaptchaError { - switch arc4random_uniform(4) { + switch arc4random_uniform(7) { case 0: return .htmlLoadError case 1: return .apiKeyNotFound case 2: return .baseURLNotFound case 3: return .wrongMessageFormat + case 4: return .failedSetup + case 5: return .responseExpired + case 6: return .failedRender default: return .unexpected(NSError()) } } diff --git a/Example/ReCaptcha_Tests/RxSwift/ReCaptcha+Rx__Tests.swift b/Example/ReCaptcha_Tests/RxSwift/ReCaptcha+Rx__Tests.swift index 8f96b63..9e6c43c 100644 --- a/Example/ReCaptcha_Tests/RxSwift/ReCaptcha+Rx__Tests.swift +++ b/Example/ReCaptcha_Tests/RxSwift/ReCaptcha+Rx__Tests.swift @@ -69,8 +69,7 @@ class ReCaptcha_Rx__Tests: XCTestCase { do { // Validate _ = try recaptcha.rx.validate(on: presenterView) - .timeout(2, scheduler: MainScheduler.instance) - .toBlocking() + .toBlocking(timeout: 2) .single() XCTFail("should have thrown exception") @@ -101,13 +100,92 @@ class ReCaptcha_Rx__Tests: XCTestCase { } } - // MARK: Dispose + // MARK: - Did Finish Loading + + func test__Did_Finish_Loading__Immediate() { + let manager = ReCaptchaWebViewManager() + let recaptcha = ReCaptcha(manager: manager) + + manager.onDidFinishLoading = { + do { + try recaptcha.rx.didFinishLoading + .toBlocking() + .first() + } + catch let error { + XCTFail(error.localizedDescription) + } + } + } + + func test__Did_Finish_Loading__Multiple() { + let recaptcha = ReCaptcha(manager: ReCaptchaWebViewManager()) + + do { + let obs = recaptcha.rx.didFinishLoading + .take(2) + .share() + + let reset = obs.do(onNext: recaptcha.reset).subscribe() + + let result = try obs + .toBlocking() + .toArray() + + XCTAssertEqual(result.count, 2) + reset.dispose() + } + catch let error { + XCTFail(error.localizedDescription) + } + } + + func test__Did_Finish_Loading__Delayed() { + let recaptcha = ReCaptcha(manager: ReCaptchaWebViewManager(shouldFail: true)) + + do { + _ = try recaptcha.rx.didFinishLoading + .toBlocking(timeout: 0.1) + .first() + + XCTFail("should have timed out") + } + catch let error { + XCTAssertEqual(String(describing: error), RxError.timeout.debugDescription) + } + + do { + recaptcha.reset() + + try recaptcha.rx.didFinishLoading + .toBlocking() + .first() + } + catch let error { + XCTFail(error.localizedDescription) + } + } + + func test__Did_Finish_Loading__Dispose() { + let manager = ReCaptchaWebViewManager() + let recaptcha = ReCaptcha(manager: manager) + + let obs = recaptcha.rx.didFinishLoading + .subscribe() + + XCTAssertNotNil(manager.onDidFinishLoading) + + obs.dispose() + XCTAssertNil(manager.onDidFinishLoading) + } + + // MARK: - Dispose func test__Dispose() { let exp = expectation(description: "stop loading") // Stop - let recaptcha = ReCaptcha(manager: ReCaptchaWebViewManager(messageBody: "{action: \"showReCaptcha\"}")) + let recaptcha = ReCaptcha(manager: ReCaptchaWebViewManager(messageBody: "{log: \"foo\"}")) recaptcha.configureWebView { _ in XCTFail("should not ask to configure the webview") } @@ -118,14 +196,12 @@ class ReCaptcha_Rx__Tests: XCTestCase { XCTFail("should not validate") } - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - disposable.dispose() - } + DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: disposable.dispose) waitForExpectations(timeout: 10) } - // MARK: Reset + // MARK: - Reset func test__Reset() { // Validate diff --git a/Example/ReCaptcha_Tests/mock.html b/Example/ReCaptcha_Tests/mock.html index 4c05cf8..09b3c17 100644 --- a/Example/ReCaptcha_Tests/mock.html +++ b/Example/ReCaptcha_Tests/mock.html @@ -1,35 +1,33 @@ - - - - - - - + post({ action: "didLoad" }); + + + + + diff --git a/Gemfile b/Gemfile index b13ca5a..1549a90 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,5 @@ source 'https://rubygems.org' -gem 'fastlane', '~> 2.75' -gem 'cocoapods', '~> 1.4' +gem 'fastlane', '~> 2.163.0' +gem 'cocoapods', '~> 1.10.0' +gem 'jazzy', '~> 0.13' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 85412af..36611f7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,196 +1,266 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (2.3.6) - activesupport (4.2.10) - i18n (~> 0.7) + CFPropertyList (3.0.3) + activesupport (5.2.4.4) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - addressable (2.5.2) - public_suffix (>= 2.0.2, < 4.0) - babosa (1.0.2) - claide (1.0.2) - cocoapods (1.4.0) - activesupport (>= 4.0.2, < 5) + addressable (2.8.0) + public_suffix (>= 2.0.2, < 5.0) + algoliasearch (1.27.5) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) + atomos (0.1.3) + aws-eventstream (1.1.0) + aws-partitions (1.414.0) + aws-sdk-core (3.110.0) + aws-eventstream (~> 1, >= 1.0.2) + aws-partitions (~> 1, >= 1.239.0) + aws-sigv4 (~> 1.1) + jmespath (~> 1.0) + aws-sdk-kms (1.40.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-s3 (1.87.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.1) + aws-sigv4 (1.2.2) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + claide (1.0.3) + cocoapods (1.10.0) + addressable (~> 2.6) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.4.0) - cocoapods-deintegrate (>= 1.0.2, < 2.0) - cocoapods-downloader (>= 1.1.3, < 2.0) + cocoapods-core (= 1.10.0) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 1.4.0, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-stats (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.3.0, < 2.0) + cocoapods-trunk (>= 1.4.0, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) - fourflusher (~> 2.0.1) + fourflusher (>= 2.3.0, < 3.0) gh_inspector (~> 1.0) - molinillo (~> 0.6.4) + molinillo (~> 0.6.6) nap (~> 1.0) - ruby-macho (~> 1.1) - xcodeproj (>= 1.5.4, < 2.0) - cocoapods-core (1.4.0) - activesupport (>= 4.0.2, < 6) + ruby-macho (~> 1.4) + xcodeproj (>= 1.19.0, < 2.0) + cocoapods-core (1.10.0) + activesupport (> 5.0, < 6) + addressable (~> 2.6) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) fuzzy_match (~> 2.0.4) nap (~> 1.0) - cocoapods-deintegrate (1.0.2) - cocoapods-downloader (1.1.3) + netrc (~> 0.11) + public_suffix + typhoeus (~> 1.0) + cocoapods-deintegrate (1.0.4) + cocoapods-downloader (1.4.0) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) - cocoapods-stats (1.0.0) - cocoapods-trunk (1.3.0) + cocoapods-trunk (1.5.0) nap (>= 0.8, < 2.0) netrc (~> 0.11) - cocoapods-try (1.1.0) + cocoapods-try (1.2.0) colored (1.2) colored2 (3.1.2) - commander-fastlane (4.4.5) + commander-fastlane (4.4.6) highline (~> 1.7.2) - concurrent-ruby (1.0.5) - declarative (0.0.10) + concurrent-ruby (1.1.7) + declarative (0.0.20) declarative-option (0.1.0) - domain_name (0.5.20170404) + digest-crc (0.6.3) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) - dotenv (2.2.1) + dotenv (2.7.6) + emoji_regex (3.2.1) escape (0.0.4) - excon (0.60.0) - faraday (0.13.1) + ethon (0.12.0) + ffi (>= 1.3.0) + excon (0.78.1) + faraday (1.3.0) + faraday-net_http (~> 1.0) multipart-post (>= 1.2, < 3) - faraday-cookie_jar (0.0.6) - faraday (>= 0.7.4) + ruby2_keywords + faraday-cookie_jar (0.0.7) + faraday (>= 0.8.0) http-cookie (~> 1.0.0) - faraday_middleware (0.12.2) - faraday (>= 0.7.4, < 1.0) - fastimage (2.1.1) - fastlane (2.75.1) - CFPropertyList (>= 2.3, < 3.0.0) + faraday-net_http (1.0.0) + faraday_middleware (1.0.0) + faraday (~> 1.0) + fastimage (2.2.1) + fastlane (2.163.0) + CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) - babosa (>= 1.0.2, < 2.0.0) - bundler (>= 1.12.0, < 2.0.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.3, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) colored - commander-fastlane (>= 4.4.5, < 5.0.0) + commander-fastlane (>= 4.4.6, < 5.0.0) dotenv (>= 2.1.1, < 3.0.0) - excon (>= 0.45.0, < 1.0.0) - faraday (~> 0.9) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 1.0) faraday-cookie_jar (~> 0.0.6) - faraday_middleware (~> 0.9) + faraday_middleware (~> 1.0) fastimage (>= 2.1.0, < 3.0.0) - gh_inspector (>= 1.0.1, < 2.0.0) - google-api-client (>= 0.13.1, < 0.14.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-api-client (>= 0.37.0, < 0.39.0) + google-cloud-storage (>= 1.15.0, < 2.0.0) highline (>= 1.7.2, < 2.0.0) json (< 3.0.0) - mini_magick (~> 4.5.1) - multi_json - multi_xml (~> 0.5) + jwt (>= 2.1.0, < 3) + mini_magick (>= 4.9.4, < 5.0.0) multipart-post (~> 2.0.0) plist (>= 3.1.0, < 4.0.0) - public_suffix (~> 2.0.0) - rubyzip (>= 1.1.0, < 2.0.0) + rubyzip (>= 2.0.0, < 3.0.0) security (= 0.1.3) - slack-notifier (>= 1.3, < 2.0.0) - terminal-notifier (>= 1.6.2, < 2.0.0) + simctl (~> 1.6.3) + slack-notifier (>= 2.0.0, < 3.0.0) + terminal-notifier (>= 2.0.0, < 3.0.0) terminal-table (>= 1.4.5, < 2.0.0) tty-screen (>= 0.6.3, < 1.0.0) - tty-spinner (>= 0.7.0, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) word_wrap (~> 1.0.0) - xcodeproj (>= 1.5.2, < 2.0.0) - xcpretty (>= 0.2.4, < 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) - fourflusher (2.0.1) + ffi (1.14.2) + fourflusher (2.3.1) fuzzy_match (2.0.4) - gh_inspector (1.0.3) - google-api-client (0.13.6) + gh_inspector (1.1.3) + google-api-client (0.38.0) addressable (~> 2.5, >= 2.5.1) - googleauth (~> 0.5) + googleauth (~> 0.9) httpclient (>= 2.8.1, < 3.0) - mime-types (~> 3.0) + mini_mime (~> 1.0) representable (~> 3.0) retriable (>= 2.0, < 4.0) - googleauth (0.6.2) - faraday (~> 0.12) + signet (~> 0.12) + google-cloud-core (1.5.0) + google-cloud-env (~> 1.0) + google-cloud-errors (~> 1.0) + google-cloud-env (1.4.0) + faraday (>= 0.17.3, < 2.0) + google-cloud-errors (1.0.1) + google-cloud-storage (1.29.2) + addressable (~> 2.5) + digest-crc (~> 0.4) + google-api-client (~> 0.33) + google-cloud-core (~> 1.2) + googleauth (~> 0.9) + mini_mime (~> 1.0) + googleauth (0.14.0) + faraday (>= 0.17.3, < 2.0) jwt (>= 1.4, < 3.0) - logging (~> 2.0) - memoist (~> 0.12) + memoist (~> 0.16) multi_json (~> 1.11) - os (~> 0.9) - signet (~> 0.7) + os (>= 0.9, < 2.0) + signet (~> 0.14) highline (1.7.10) http-cookie (1.0.3) domain_name (~> 0.5) httpclient (2.8.3) - i18n (0.9.1) + i18n (1.8.7) concurrent-ruby (~> 1.0) - json (2.1.0) - jwt (2.1.0) - little-plugger (1.1.4) - logging (2.2.2) - little-plugger (~> 1.1) - multi_json (~> 1.10) - memoist (0.16.0) - mime-types (3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2016.0521) - mini_magick (4.5.1) - minitest (5.11.1) - molinillo (0.6.4) - multi_json (1.13.0) - multi_xml (0.6.0) + jazzy (0.13.6) + cocoapods (~> 1.5) + mustache (~> 1.1) + open4 + redcarpet (~> 3.4) + rouge (>= 2.0.6, < 4.0) + sassc (~> 2.1) + sqlite3 (~> 1.3) + xcinvoke (~> 0.3.0) + jmespath (1.4.0) + json (2.5.1) + jwt (2.2.2) + liferaft (0.0.6) + memoist (0.16.2) + mini_magick (4.11.0) + mini_mime (1.0.2) + minitest (5.14.2) + molinillo (0.6.6) + multi_json (1.15.0) multipart-post (2.0.0) - nanaimo (0.2.3) + mustache (1.1.1) + nanaimo (0.3.0) nap (1.1.0) + naturally (2.2.0) netrc (0.11.0) - os (0.9.6) - plist (3.4.0) - public_suffix (2.0.5) + open4 (1.3.4) + os (1.1.1) + plist (3.6.0) + public_suffix (4.0.6) + rake (13.0.3) + redcarpet (3.5.1) representable (3.0.4) declarative (< 0.1.0) declarative-option (< 0.2.0) uber (< 0.2.0) - retriable (3.1.1) + retriable (3.1.2) rouge (2.0.7) - ruby-macho (1.1.0) - rubyzip (1.2.1) + ruby-macho (1.4.0) + ruby2_keywords (0.0.2) + rubyzip (2.3.0) + sassc (2.4.0) + ffi (~> 1.9) security (0.1.3) - signet (0.8.1) + signet (0.14.0) addressable (~> 2.3) - faraday (~> 0.9) + faraday (>= 0.17.3, < 2.0) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - slack-notifier (1.5.1) - terminal-notifier (1.8.0) + simctl (1.6.8) + CFPropertyList + naturally + slack-notifier (2.3.2) + sqlite3 (1.4.2) + terminal-notifier (2.0.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) thread_safe (0.3.6) - tty-cursor (0.5.0) - tty-screen (0.6.4) - tty-spinner (0.7.0) - tty-cursor (>= 0.5.0) - tzinfo (1.2.4) + tty-cursor (0.7.1) + tty-screen (0.8.1) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + typhoeus (1.4.0) + ethon (>= 0.9.0) + tzinfo (1.2.9) thread_safe (~> 0.1) uber (0.1.0) unf (0.1.4) unf_ext - unf_ext (0.0.7.4) - unicode-display_width (1.3.0) + unf_ext (0.0.7.7) + unicode-display_width (1.7.0) word_wrap (1.0.0) - xcodeproj (1.5.4) - CFPropertyList (~> 2.3.3) + xcinvoke (0.3.0) + liferaft (~> 0.0.6) + xcodeproj (1.19.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.2.3) - xcpretty (0.2.8) + nanaimo (~> 0.3.0) + xcpretty (0.3.0) rouge (~> 2.0.7) - xcpretty-travis-formatter (1.0.0) + xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.4) - fastlane (~> 2.75) + cocoapods (~> 1.10.0) + fastlane (~> 2.163.0) + jazzy (~> 0.13) BUNDLED WITH - 1.16.0 + 1.17.3 diff --git a/LICENSE b/LICENSE index f3e034c..e29326a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright © 2018 Flávio Caetano +Copyright © Flávio Caetano Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 1ce8b18..d0fd1c9 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,16 @@ # ReCaptcha [![Build Status](https://travis-ci.org/fjcaetano/ReCaptcha.svg?branch=master)](https://travis-ci.org/fjcaetano/ReCaptcha) +[![codecov](https://codecov.io/gh/fjcaetano/ReCaptcha/branch/master/graph/badge.svg)](https://codecov.io/gh/fjcaetano/ReCaptcha) +[![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/fjcaetano/ReCaptcha/pulls) +[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-orange.svg)](https://github.com/Carthage/Carthage) [![Version](https://img.shields.io/cocoapods/v/ReCaptcha.svg?style=flat)](http://cocoapods.org/pods/ReCaptcha) [![License](https://img.shields.io/cocoapods/l/ReCaptcha.svg?style=flat)](http://cocoapods.org/pods/ReCaptcha) [![Platform](https://img.shields.io/cocoapods/p/ReCaptcha.svg?style=flat)](http://cocoapods.org/pods/ReCaptcha) -[![codecov](https://codecov.io/gh/fjcaetano/ReCaptcha/branch/master/graph/badge.svg)](https://codecov.io/gh/fjcaetano/ReCaptcha) -[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) ----- -Add Google's [Invisible ReCaptcha](https://developers.google.com/recaptcha/docs/invisible) to your project. This library +Add Google's [Invisible ReCaptcha v2](https://developers.google.com/recaptcha/docs/invisible) to your project. This library automatically handles ReCaptcha's events and retrieves the validation token or notifies you to present the challenge if invisibility is not possible. @@ -17,8 +18,15 @@ invisibility is not possible. #### _Warning_ ⚠️ -Beware that this library only works for Invisible ReCaptcha keys! Make sure to check the Invisible reCAPTCHA option -when creating your [API Key](https://www.google.com/recaptcha/admin). +Beware that this library only works for ReCaptcha v2 Invisible keys! Make sure to check the reCAPTCHA +v2 Invisible badge option when creating your [API Key](https://www.google.com/recaptcha/admin/create). + +![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 @@ -42,7 +50,9 @@ extension for the ReCaptcha framework. ## Usage -Simply add `ReCaptchaKey` and `ReCaptchaDomain` (with a protocol) to your Info.plist and run: +The reCAPTCHA keys can be specified as Info.plist keys or can be passed as parameters when instantiating ReCaptcha(). + +For the Info.plist configuration, add `ReCaptchaKey` and `ReCaptchaDomain` (with a protocol ex. http:// or https://) to your Info.plist and run: ``` swift let recaptcha = try? ReCaptcha() @@ -63,6 +73,16 @@ func validate() { } ``` +If instead you prefer to keep the information out of the Info.plist, you can use: +``` swift +let recaptcha = try? ReCaptcha( + apiKey: "YOUR_RECAPTCHA_KEY", + baseURL: URL(string: "YOUR_RECAPTCHA_DOMAIN")! +) + +... +``` + You can also install the reactive subpod and use it with RxSwift: ``` swift diff --git a/ReCaptcha-Carthage.xcodeproj/project.pbxproj b/ReCaptcha-Carthage.xcodeproj/project.pbxproj index d348d10..36e4ac3 100644 --- a/ReCaptcha-Carthage.xcodeproj/project.pbxproj +++ b/ReCaptcha-Carthage.xcodeproj/project.pbxproj @@ -248,6 +248,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = F206BAA61F8D3DE900A25807; @@ -367,6 +368,7 @@ SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -419,6 +421,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -440,11 +443,12 @@ ); INFOPLIST_FILE = "ReCaptcha-Carthage/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.flaviocaetano.ReCaptcha; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -462,11 +466,12 @@ ); INFOPLIST_FILE = "ReCaptcha-Carthage/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.flaviocaetano.ReCaptcha; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -485,12 +490,13 @@ ); INFOPLIST_FILE = ReCaptcha_RxSwift/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; OTHER_SWIFT_FLAGS = "-D IMPORT_RECAPTCHA"; PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-RxSwift"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -509,12 +515,13 @@ ); INFOPLIST_FILE = ReCaptcha_RxSwift/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; OTHER_SWIFT_FLAGS = "-D IMPORT_RECAPTCHA"; PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-RxSwift"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/ReCaptcha.podspec b/ReCaptcha.podspec index 11cd7d2..f00daf4 100644 --- a/ReCaptcha.podspec +++ b/ReCaptcha.podspec @@ -1,9 +1,10 @@ Pod::Spec.new do |s| s.name = 'ReCaptcha' - s.version = '1.3.1' + s.version = '1.6.0' s.summary = 'ReCaptcha for iOS' - + s.swift_version = '5.0' + s.description = <<-DESC Add Google's [Invisible ReCaptcha](https://developers.google.com/recaptcha/docs/invisible) to your project. This library automatically handles ReCaptcha's events and retrieves the validation token or notifies you to present the challenge if @@ -17,7 +18,7 @@ invisibility is not possible. 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 = '9.0' s.default_subspecs = 'Core' s.subspec 'Core' do |core| @@ -32,6 +33,6 @@ invisibility is not possible. s.subspec 'RxSwift' do |rx| rx.source_files = 'ReCaptcha/Classes/Rx/**/*' rx.dependency 'ReCaptcha/Core' - rx.dependency 'RxSwift', '~> 4.0' + rx.dependency 'RxSwift', '~> 6.0' end end diff --git a/ReCaptcha/Assets/recaptcha.html b/ReCaptcha/Assets/recaptcha.html index 6a43791..78320bf 100644 --- a/ReCaptcha/Assets/recaptcha.html +++ b/ReCaptcha/Assets/recaptcha.html @@ -1,67 +1,108 @@ - - - - - - - - + const reset = function() { + console.log("resetting"); + grecaptcha.reset(); + grecaptcha.ready(function() { + post({ action: "didLoad" }); + }); + }; + + var onloadCallback = function() { + grecaptcha.render("submit", { + sitekey: "${apiKey}", + callback: function(token) { + console.log(token); + post({ token }); + clearObservers(); + }, + "expired-callback": function() { + post({ error: 28 }); + clearObservers(); + }, + "error-callback": function() { + post({ error: 29 }); + clearObservers(); + }, + size: "invisible" + }); + + grecaptcha.ready(function() { + observeDOM(document.getElementById("body"), function(mut) { + const success = !!mut.find(function({ addedNodes }) { + return Array.from( + addedNodes.values ? addedNodes.values() : addedNodes + ).find(function({ nodeName, name }) { + return nodeName === "IFRAME" && !!name; + }); + }); + + if (success) { + post({ action: "didLoad" }); + } + }); + }); + }; + + + + + + + diff --git a/ReCaptcha/Classes/ReCaptcha.swift b/ReCaptcha/Classes/ReCaptcha.swift index 8276185..d552175 100644 --- a/ReCaptcha/Classes/ReCaptcha.swift +++ b/ReCaptcha/Classes/ReCaptcha.swift @@ -107,9 +107,8 @@ public class ReCaptcha { /** - parameters: - apiKey: The API key sent to the ReCaptcha init - - infoPlistKey: The API key retrived from the application's Info.plist - baseURL: The base URL sent to the ReCaptcha init - - infoPlistURL: The base URL retrieved from the application's Info.plist + - endpoint: The ReCaptcha endpoint to be used. - locale: A locale value to translate ReCaptcha into a different language Initializes a ReCaptcha object @@ -199,12 +198,39 @@ public class ReCaptcha { manager.reset() } + /** + - parameter closure: A closure that is called when the JS bundle finishes loading. + + Provides a closure to be notified when the webview finishes loading JS resources. + + The closure may be called multiple times since the resources may also be loaded multiple times + in case of error or reset. This may also be immediately called if the resources have already + finished loading when you set the closure. + */ + public func didFinishLoading(_ closure: (() -> Void)?) { + manager.onDidFinishLoading = closure + } + + // MARK: - Development + #if DEBUG - /// Forces the challenge to be explicitly displayed. + /// Forces the challenge widget to be explicitly displayed. public var forceVisibleChallenge: Bool { get { return manager.forceVisibleChallenge } set { manager.forceVisibleChallenge = newValue } } + + /** + Allows validation stubbing for testing + + When this property is set to `true`, every call to `validate()` will immediately be resolved with `.token("")`. + + Use only when testing your application. + */ + public var shouldSkipForTests: Bool { + get { return manager.shouldSkipForTests } + set { manager.shouldSkipForTests = newValue } + } #endif } diff --git a/ReCaptcha/Classes/ReCaptchaDecoder.swift b/ReCaptcha/Classes/ReCaptchaDecoder.swift index bb205a9..876d5b4 100644 --- a/ReCaptcha/Classes/ReCaptchaDecoder.swift +++ b/ReCaptcha/Classes/ReCaptchaDecoder.swift @@ -89,6 +89,12 @@ fileprivate extension ReCaptchaDecoder.Result { if let token = response["token"] as? String { return .token(token) } + else if let message = response["log"] as? String { + return .log(message) + } + else if let error = response["error"] as? Int { + return from(error) + } if let action = response["action"] as? String { switch action { @@ -109,4 +115,20 @@ fileprivate extension ReCaptchaDecoder.Result { return .error(.wrongMessageFormat) } + + private static func from(_ error: Int) -> ReCaptchaDecoder.Result { + switch error { + case 27: + return .error(.failedSetup) + + case 28: + return .error(.responseExpired) + + case 29: + return .error(.failedRender) + + default: + return .error(.wrongMessageFormat) + } + } } diff --git a/ReCaptcha/Classes/ReCaptchaError.swift b/ReCaptcha/Classes/ReCaptchaError.swift index d311d06..7c2ea6d 100644 --- a/ReCaptcha/Classes/ReCaptchaError.swift +++ b/ReCaptcha/Classes/ReCaptchaError.swift @@ -25,6 +25,14 @@ public enum ReCaptchaError: Error, CustomStringConvertible { /// Received an unexpected message from javascript case wrongMessageFormat + /// ReCaptcha setup failed + case failedSetup + + /// ReCaptcha response expired + case responseExpired + + /// ReCaptcha render failed + case failedRender /// A human-readable description for each error public var description: String { @@ -43,6 +51,20 @@ public enum ReCaptchaError: Error, CustomStringConvertible { case .wrongMessageFormat: return "Unexpected message from javascript" + + case .failedSetup: + // swiftlint:disable line_length + return """ + ⚠️ WARNING! ReCaptcha wasn't successfully configured. Please double check your ReCaptchaKey and ReCaptchaDomain. + Also check that you're using ReCaptcha's **SITE KEY** for client side integration. + """ + // swiftlint:enable line_length + + case .responseExpired: + return "Response expired and need to re-verify" + + case .failedRender: + return "Recaptha encountered an error in execution" } } } diff --git a/ReCaptcha/Classes/ReCaptchaWebViewManager.swift b/ReCaptcha/Classes/ReCaptchaWebViewManager.swift index f8e3f7d..b808040 100644 --- a/ReCaptcha/Classes/ReCaptchaWebViewManager.swift +++ b/ReCaptcha/Classes/ReCaptchaWebViewManager.swift @@ -13,87 +13,9 @@ import WebKit /** Handles comunications with the webview containing the ReCaptcha challenge. */ internal class ReCaptchaWebViewManager { - /** The `webView` delegate object that performs execution uppon script loading - */ - fileprivate class WebViewDelegate: NSObject, WKNavigationDelegate { - struct Constants { - /// The host that loaded requests should have - static let apiURLHost = "www.google.com" - } - - /// The parent manager - private weak var manager: ReCaptchaWebViewManager? - - /// The active requests' urls - private var activeRequests = Set(minimumCapacity: 0) - - /// - parameter manager: The parent manager - init(manager: ReCaptchaWebViewManager) { - self.manager = manager - } - - /** - - parameters: - - webView: The web view invoking the delegate method. - - navigationAction: Descriptive information about the action triggering the navigation request. - - decisionHandler: The decision handler to call to allow or cancel the navigation. The argument is one of - the constants of the enumerated type WKNavigationActionPolicy. - - Decides whether to allow or cancel a navigation. - */ - func webView( - _ webView: WKWebView, - decidePolicyFor navigationAction: WKNavigationAction, - decisionHandler: @escaping (WKNavigationActionPolicy - ) -> Void) { - defer { decisionHandler(.allow) } - - if let url = navigationAction.request.url, url.host == Constants.apiURLHost { - activeRequests.insert(url.absoluteString) - } - } - - /** - - parameters: - - webView: The web view invoking the delegate method. - - navigationResponse: Descriptive information about the navigation response. - - decisionHandler: A block to be called when your app has decided whether to allow or cancel the navigation - - Decides whether to allow or cancel a navigation after its response is known. - */ - func webView( - _ webView: WKWebView, - decidePolicyFor navigationResponse: WKNavigationResponse, - decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void - ) { - defer { decisionHandler(.allow) } - guard let url = navigationResponse.response.url?.absoluteString, - activeRequests.remove(url) != nil, activeRequests.isEmpty else { - return - } - - execute() - } - - /// Flag the requests as finished and call ReCaptcha execution if necessary - func execute() { - guard manager?.didFinishLoading != true else { return } - - DispatchQueue.main.throttle(deadline: .now() + 1, context: self) { [weak self] in - // Did finish loading the ReCaptcha JS source - self?.manager?.didFinishLoading = true - - if self?.manager?.completion != nil { - // User has requested for validation - self?.manager?.execute() - } - } - } - - /// Flags all requests as finished - func reset() { - activeRequests.removeAll() - } + enum JSCommand: String { + case execute = "execute();" + case reset = "reset();" } fileprivate struct Constants { @@ -114,11 +36,23 @@ internal class ReCaptchaWebViewManager { ) } } + + /// Allows validation stubbing for testing + public var shouldSkipForTests = false #endif /// Sends the result message var completion: ((ReCaptchaResult) -> Void)? + /// Notifies the JS bundle has finished loading + var onDidFinishLoading: (() -> Void)? { + didSet { + if didFinishLoading { + onDidFinishLoading?() + } + } + } + /// Configures the webview for display when required var configureWebView: ((WKWebView) -> Void)? @@ -132,7 +66,13 @@ internal class ReCaptchaWebViewManager { fileprivate var decoder: ReCaptchaDecoder! /// Indicates if the script has already been loaded by the `webView` - fileprivate var didFinishLoading = false // webView.isLoading does not work in this case + fileprivate var didFinishLoading = false { + didSet { + if didFinishLoading { + onDidFinishLoading?() + } + } + } /// The observer for `.UIWindowDidBecomeVisible` fileprivate var observer: NSObjectProtocol? @@ -140,20 +80,14 @@ internal class ReCaptchaWebViewManager { /// The endpoint url being used fileprivate var endpoint: String - /// The `webView` delegate implementation - fileprivate lazy var webviewDelegate: WebViewDelegate = { - WebViewDelegate(manager: self) - }() - /// The webview that executes JS code lazy var webView: WKWebView = { let webview = WKWebView( frame: CGRect(x: 0, y: 0, width: 1, height: 1), configuration: self.buildConfiguration() ) - webview.navigationDelegate = self.webviewDelegate webview.accessibilityIdentifier = "webview" - webview.accessibilityTraits = UIAccessibilityTraitLink + webview.accessibilityTraits = UIAccessibilityTraits.link webview.isHidden = true return webview @@ -179,7 +113,7 @@ internal class ReCaptchaWebViewManager { } else { observer = NotificationCenter.default.addObserver( - forName: .UIWindowDidBecomeVisible, + forName: UIWindow.didBecomeVisibleNotification, object: nil, queue: nil ) { [weak self] notification in @@ -195,10 +129,16 @@ internal class ReCaptchaWebViewManager { Starts the challenge validation */ func validate(on view: UIView) { +#if DEBUG + guard !shouldSkipForTests else { + completion?(.token("")) + return + } +#endif webView.isHidden = false view.addSubview(webView) - execute() + executeJS(command: .execute) } @@ -213,15 +153,9 @@ internal class ReCaptchaWebViewManager { The reset is achieved by calling `grecaptcha.reset()` on the JS API. */ func reset() { - didFinishLoading = false configureWebViewDispatchToken = UUID() - webviewDelegate.reset() - - webView.evaluateJavaScript(Constants.ResetCommand) { [weak self] _, error in - if let error = error { - self?.decoder.send(error: .unexpected(error)) - } - } + executeJS(command: .reset) + didFinishLoading = false } } @@ -230,22 +164,6 @@ internal class ReCaptchaWebViewManager { /** Private methods for ReCaptchaWebViewManager */ fileprivate extension ReCaptchaWebViewManager { - /** Executes the JS command that loads the ReCaptcha challenge. - This method has no effect if the webview hasn't finished loading. - */ - func execute() { - guard didFinishLoading else { - // Hasn't finished loading the HTML yet - return - } - - webView.evaluateJavaScript(Constants.ExecuteJSCommand) { [weak self] _, error in - if let error = error { - self?.decoder.send(error: .unexpected(error)) - } - } - } - /** - returns: An instance of `WKWebViewConfiguration` @@ -287,12 +205,14 @@ fileprivate extension ReCaptchaWebViewManager { } case .didLoad: - // For testing purposes - webviewDelegate.execute() + didFinishLoading = true + if completion != nil { + executeJS(command: .execute) + } case .log(let message): #if DEBUG - print("[JS LOG]:", message) + print("[JS LOG]:", message) #endif } } @@ -313,4 +233,24 @@ fileprivate extension ReCaptchaWebViewManager { NotificationCenter.default.removeObserver(observer) } } + + /** + - parameters: + - command: The JavaScript command to be executed + + Executes the JS command that loads the ReCaptcha challenge. This method has no effect if the webview hasn't + finished loading. + */ + func executeJS(command: JSCommand) { + guard didFinishLoading else { + // Hasn't finished loading all the resources + return + } + + webView.evaluateJavaScript(command.rawValue) { [weak self] _, error in + if let error = error { + self?.decoder.send(error: .unexpected(error)) + } + } + } } diff --git a/ReCaptcha/Classes/Rx/ReCaptcha+Rx.swift b/ReCaptcha/Classes/Rx/ReCaptcha+Rx.swift index 824367c..100d792 100644 --- a/ReCaptcha/Classes/Rx/ReCaptcha+Rx.swift +++ b/ReCaptcha/Classes/Rx/ReCaptcha+Rx.swift @@ -37,7 +37,7 @@ public extension Reactive where Base: ReCaptcha { single(.success(token)) case .error(let error): - single(.error(error)) + single(.failure(error)) } } @@ -64,4 +64,21 @@ public extension Reactive where Base: ReCaptcha { base?.reset() } } + + /** + Notifies when the webview finishes loading all JS resources + + This Observable may produce multiple events since the resources may be loaded multiple times in + case of error or reset. This may also immediately produce an event if the resources have + already finished loading when you subscribe to this Observable. + */ + var didFinishLoading: Observable { + return Observable.create { [weak base] (observer: AnyObserver) in + base?.didFinishLoading { observer.onNext(()) } + + return Disposables.create { [weak base] in + base?.didFinishLoading(nil) + } + } + } } diff --git a/carthage.sh b/carthage.sh new file mode 100755 index 0000000..fa44975 --- /dev/null +++ b/carthage.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +# carthage.sh +# Usage example: ./carthage.sh build --platform iOS + +set -euo pipefail + +xcconfig=$(mktemp /tmp/static.xcconfig.XXXXXX) +trap 'rm -f "$xcconfig"' INT TERM HUP EXIT + +# For Xcode 12 make sure EXCLUDED_ARCHS is set to arm architectures otherwise +# the build will fail on lipo due to duplicate architectures. +echo 'EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200 = arm64 arm64e armv7 armv7s armv6 armv8' >> $xcconfig +echo 'EXCLUDED_ARCHS = $(inherited) $(EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_$(EFFECTIVE_PLATFORM_SUFFIX)__NATIVE_ARCH_64_BIT_$(NATIVE_ARCH_64_BIT)__XCODE_$(XCODE_VERSION_MAJOR))' >> $xcconfig + +export XCODE_XCCONFIG_FILE="$xcconfig" +carthage "$@" \ No newline at end of file diff --git a/example-v2-key.png b/example-v2-key.png new file mode 100644 index 0000000..77d877d Binary files /dev/null and b/example-v2-key.png differ diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 4cd59d4..a947148 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -1,18 +1,14 @@ -# If you want to automatically update fastlane if a new version is available: -# update_fastlane - -# This is the minimum version number required. -# Update this, if you use features of a newer version -fastlane_version "2.62.1" +fastlane_version "2.126" default_platform :ios platform :ios do skip_docs - devices = ["iPhone X (~> 11)"] - devices << "iPhone 7 (~> 10)" if !Helper.is_ci? - devices << "iPhone 5s (~> 9)" if !Helper.is_ci? + devices = ["iPhone X (~> 12)"] + # devices << "iPhone X (~> 11)" if !Helper.is_ci? + # devices << "iPhone 7 (~> 10)" if !Helper.is_ci? + # devices << "iPhone 6s (~> 9)" if !Helper.is_ci? desc "Runs the following lanes:\n- test\n- pod_lint\n- carthage_lint" lane :ci do @@ -31,6 +27,7 @@ platform :ios do swiftlint( executable: "Example/Pods/Swiftlint/swiftlint", strict: true, + reporter: "emoji", ) # The problem lies in the fact (or rather: serious bug in xcodebuild) that @@ -55,35 +52,58 @@ platform :ios do code_coverage: true, ) - if is_ci - codecov( - project_name: 'ReCaptcha', - use_xcodeplist: true, + codecov( + project_name: 'ReCaptcha', + use_xcodeplist: true, + ) + + unless Helper.is_ci? + puts "Running UI Tests" + scan( + test_without_building: true, + devices: self.select_similar_simulator(devices), + scheme: "ReCaptcha_UITests", + workspace: "Example/ReCaptcha.xcworkspace", + code_coverage: true, ) - else + puts "Not CI: Skipping coverage files upload" end end desc "Lint Cocoapods Lib" lane :pod_lint do - pod_lib_lint + pod_lib_lint( + allow_warnings: true, # Temporarily necessary while this issue isn't fixed: https://github.com/CocoaPods/CocoaPods/issues/10291 + ) end desc "Lint Carthage lib" lane :carthage_lint do - carthage( - command: "update", - platform: "iOS", - cache_builds: true, - ) - carthage( - command: "build", - platform: "iOS", - cache_builds: true, - no_skip_current: true, - ) + # This is a temporary fix while Carthage is broken with Xcode 12 + # https://github.com/Carthage/Carthage/issues/3019#issuecomment-665136323 + # + # The default Fastlane action can be used when a new Carthage version with a fix has been released + # and the ./carthage.sh script can be removed + Dir.chdir("..") do + sh("./carthage.sh", "update", "--platform", "iOS", "--cache-builds") + + sh("./carthage.sh", "build", "--no-skip-current", "--platform", "iOS", "--cache-builds") + end + + # carthage( + # command: "update", + # platform: "iOS", + # cache_builds: true, + # ) + + # carthage( + # command: "build", + # platform: "iOS", + # cache_builds: true, + # no_skip_current: true, + # ) end desc "Deploy a new version to Github and Cocoapods" @@ -100,9 +120,18 @@ platform :ios do UI.user_error! "Podspec version different than tag name" end - carthage( - command: "archive", - ) + # This is a temporary fix while Carthage is broken with Xcode 12 + # https://github.com/Carthage/Carthage/issues/3019#issuecomment-665136323 + # + # The default Fastlane action can be used when a new Carthage version with a fix has been released + # and the ./carthage.sh script can be removed + Dir.chdir("..") do + sh("./carthage.sh", "archive") + end + + # carthage( + # command: "archive", + # ) pod_push( path: "ReCaptcha.podspec", diff --git a/fastlane/actions/codecov b/fastlane/actions/codecov index 2f03a68..86af438 160000 --- a/fastlane/actions/codecov +++ b/fastlane/actions/codecov @@ -1 +1 @@ -Subproject commit 2f03a689732adc3a6d1ccc4143b57a5de206d131 +Subproject commit 86af4384bb211fcb3194522e40aa9274af00fc08