diff --git a/Example/Podfile.lock b/Example/Podfile.lock index c214aa0..c296c53 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,11 +1,9 @@ PODS: - AppSwizzle (1.2) - - ReCaptcha/Core (1.1): - - Result (~> 3.0) + - ReCaptcha/Core (1.1) - ReCaptcha/RxSwift (1.1): - ReCaptcha/Core - RxSwift (~> 4.0) - - Result (3.2.4) - RxCocoa (4.1.1): - RxSwift (~> 4.0) - RxSwift (4.1.1) @@ -31,8 +29,7 @@ CHECKOUT OPTIONS: SPEC CHECKSUMS: AppSwizzle: bbd3782652fc426ce59c045a92ec61d36f261984 - ReCaptcha: 7a130e2765cf13af3223683400252fe7ae30ca7b - Result: d2d07204ce72856f1fd9130bbe42c35a7b0fea10 + ReCaptcha: b94a673f3827e9b9cf7e77db0395694549241247 RxCocoa: fd0862fd2df95fa55562ad28ffd2522c25eb4a85 RxSwift: c6e3b1c7b325c7d121cd4327e9d98b7ed746b570 SwiftLint: 2e4b89feed5909c42c3735bbd6745f4345c4b772 diff --git a/Example/ReCaptcha.xcodeproj/project.pbxproj b/Example/ReCaptcha.xcodeproj/project.pbxproj index 2f1634f..16b8600 100644 --- a/Example/ReCaptcha.xcodeproj/project.pbxproj +++ b/Example/ReCaptcha.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ 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 */; }; 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 */; }; F2ECCF8A1E9FCEFE0097B199 /* ReCaptchaDecoder__Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2ECCF891E9FCEFE0097B199 /* ReCaptchaDecoder__Tests.swift */; }; F2ECCF8C1E9FE37C0097B199 /* mock.html in Resources */ = {isa = PBXBuildFile; fileRef = F2ECCF8B1E9FE37C0097B199 /* mock.html */; }; @@ -74,6 +75,7 @@ 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 = ""; }; F28FACA0200E425600E14987 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + F2AE8611204F3430002E28D7 /* ReCaptchaResult__Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReCaptchaResult__Tests.swift; sourceTree = ""; }; F2E2685D1F7AEE3400CD876D /* ReCaptcha__Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReCaptcha__Tests.swift; sourceTree = ""; }; 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 = ""; }; @@ -211,6 +213,7 @@ F2ECCF8D1E9FE68C0097B199 /* ReCaptchaWebViewManager__Tests.swift */, F2E2685D1F7AEE3400CD876D /* ReCaptcha__Tests.swift */, F231B3961FEC325A00F82943 /* DispatchQueue__Tests.swift */, + F2AE8611204F3430002E28D7 /* ReCaptchaResult__Tests.swift */, ); path = Core; sourceTree = ""; @@ -447,14 +450,12 @@ inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-ReCaptcha_Example/Pods-ReCaptcha_Example-frameworks.sh", "${BUILT_PRODUCTS_DIR}/ReCaptcha/ReCaptcha.framework", - "${BUILT_PRODUCTS_DIR}/Result/Result.framework", "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework", "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReCaptcha.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Result.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework", ); @@ -595,6 +596,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F2AE8612204F3430002E28D7 /* ReCaptchaResult__Tests.swift in Sources */, F2ECCF961EA00A5B0097B199 /* ReCaptchaWebViewManager+Helpers.swift in Sources */, F2ECCF8E1E9FE68C0097B199 /* ReCaptchaWebViewManager__Tests.swift in Sources */, F2ECCF981EA011370097B199 /* Result+Helpers.swift in Sources */, diff --git a/Example/ReCaptcha.xcodeproj/xcshareddata/xcschemes/ReCaptcha-Example.xcscheme b/Example/ReCaptcha.xcodeproj/xcshareddata/xcschemes/ReCaptcha-Example.xcscheme index 81d7408..332962e 100644 --- a/Example/ReCaptcha.xcodeproj/xcshareddata/xcschemes/ReCaptcha-Example.xcscheme +++ b/Example/ReCaptcha.xcodeproj/xcshareddata/xcschemes/ReCaptcha-Example.xcscheme @@ -55,7 +55,7 @@ + skipped = "YES"> Observable in - switch result { - case .failure: return .just(()) - default: return .empty() - } - } + .catchErrorJustReturn("error") + .filter { $0 == "error" } + .map { _ in () } .take(1) .do(onCompleted: exp2.fulfill) .bind(to: manager.rx.reset) .disposed(by: disposeBag) waitForExpectations(timeout: 10) - XCTAssertEqual(result1?.error, .wrongMessageFormat) // Resets and tries again let exp3 = expectation(description: "validates after reset") - var result2: ReCaptchaWebViewManager.Response? + var result2: String? manager.rx.validate(on: presenterView, resetOnError: false) .subscribe { event in @@ -227,12 +217,12 @@ class ReCaptcha_Rx__Tests: XCTestCase { .disposed(by: disposeBag) waitForExpectations(timeout: 10) - XCTAssertEqual(result2?.value, apiKey) + XCTAssertEqual(result2, apiKey) } func test__Validate__Reset_On_Error() { let exp = expectation(description: "executes after failure on first execution") - var result: ReCaptchaWebViewManager.Response? + var result: String? // Validate let manager = ReCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey, shouldFail: true) @@ -257,6 +247,6 @@ class ReCaptcha_Rx__Tests: XCTestCase { .disposed(by: disposeBag) waitForExpectations(timeout: 10) - XCTAssertEqual(result?.value, apiKey) + XCTAssertEqual(result, apiKey) } } diff --git a/README.md b/README.md index a573f1f..24886a8 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ override func viewDidLoad() { func validate() { - recaptcha?.validate(on: view) { [weak self] result in + recaptcha?.validate(on: view) { [weak self] (result: ReCaptchaResult) in print(try? result.dematerialize()) } } diff --git a/ReCaptcha-Carthage.xcodeproj/project.pbxproj b/ReCaptcha-Carthage.xcodeproj/project.pbxproj index 72520df..a9d4bac 100644 --- a/ReCaptcha-Carthage.xcodeproj/project.pbxproj +++ b/ReCaptcha-Carthage.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ F24EA1E61F968403001DEC17 /* ReCaptcha.swift in Sources */ = {isa = PBXBuildFile; fileRef = F24EA1DE1F9683F5001DEC17 /* ReCaptcha.swift */; }; F24EA1E71F968406001DEC17 /* recaptcha.html in Resources */ = {isa = PBXBuildFile; fileRef = F24EA1E01F9683F5001DEC17 /* recaptcha.html */; }; F255FBEC1F8D5654002F5FA8 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F255FBEB1F8D5654002F5FA8 /* Result.framework */; }; + F2AE8614204F3B42002E28D7 /* ReCaptchaResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2AE8613204F3B41002E28D7 /* ReCaptchaResult.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -51,6 +52,7 @@ F24EA1DE1F9683F5001DEC17 /* ReCaptcha.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReCaptcha.swift; sourceTree = ""; }; F24EA1E01F9683F5001DEC17 /* recaptcha.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = recaptcha.html; sourceTree = ""; }; F255FBEB1F8D5654002F5FA8 /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Result.framework; path = "../ReCaptcha Carthage Test/Carthage/Build/iOS/Result.framework"; sourceTree = ""; }; + F2AE8613204F3B41002E28D7 /* ReCaptchaResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReCaptchaResult.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -147,6 +149,7 @@ F24EA1D91F9683F5001DEC17 /* ReCaptchaWebViewManager.swift */, F24EA1DA1F9683F5001DEC17 /* String+Dict.swift */, F24EA1DB1F9683F5001DEC17 /* ReCaptchaError.swift */, + F2AE8613204F3B41002E28D7 /* ReCaptchaResult.swift */, F231B3991FEC51C800F82943 /* DispatchQueue+Throttle.swift */, F24EA1DC1F9683F5001DEC17 /* Rx */, F24EA1DE1F9683F5001DEC17 /* ReCaptcha.swift */, @@ -291,6 +294,7 @@ files = ( F24EA1E51F968403001DEC17 /* ReCaptchaError.swift in Sources */, F24EA1E21F968403001DEC17 /* ReCaptchaDecoder.swift in Sources */, + F2AE8614204F3B42002E28D7 /* ReCaptchaResult.swift in Sources */, F24EA1E61F968403001DEC17 /* ReCaptcha.swift in Sources */, F24EA1E31F968403001DEC17 /* ReCaptchaWebViewManager.swift in Sources */, F24EA1E41F968403001DEC17 /* String+Dict.swift in Sources */, diff --git a/ReCaptcha.podspec b/ReCaptcha.podspec index 57401a8..738c7a1 100644 --- a/ReCaptcha.podspec +++ b/ReCaptcha.podspec @@ -22,7 +22,6 @@ invisibility is not possible. s.subspec 'Core' do |core| core.source_files = 'ReCaptcha/Classes/*' core.frameworks = 'WebKit' - core.dependency 'Result', '~> 3.0' core.resource_bundles = { 'ReCaptcha' => ['ReCaptcha/Assets/**/*'] diff --git a/ReCaptcha/Classes/ReCaptcha.swift b/ReCaptcha/Classes/ReCaptcha.swift index 762e5e9..7489b67 100644 --- a/ReCaptcha/Classes/ReCaptcha.swift +++ b/ReCaptcha/Classes/ReCaptcha.swift @@ -30,7 +30,7 @@ open class ReCaptcha: ReCaptchaWebViewManager { /// Alternate endpoint. Points to https://www.recaptcha.net/recaptcha/api.js case alternate - fileprivate var url: String { + internal var url: String { switch self { case .default: return "https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" case .alternate: return "https://www.recaptcha.net/recaptcha/api.js?onload=onloadCallback&render=explicit" diff --git a/ReCaptcha/Classes/ReCaptchaResult.swift b/ReCaptcha/Classes/ReCaptchaResult.swift new file mode 100644 index 0000000..48412d5 --- /dev/null +++ b/ReCaptcha/Classes/ReCaptchaResult.swift @@ -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 + } + } +} diff --git a/ReCaptcha/Classes/ReCaptchaWebViewManager.swift b/ReCaptcha/Classes/ReCaptchaWebViewManager.swift index 0b680a2..68607b0 100644 --- a/ReCaptcha/Classes/ReCaptchaWebViewManager.swift +++ b/ReCaptcha/Classes/ReCaptchaWebViewManager.swift @@ -7,15 +7,12 @@ // import Foundation -import Result import WebKit /** Handles comunications with the webview containing the ReCaptcha challenge. */ open class ReCaptchaWebViewManager { - public typealias Response = Result - /** The `webView` delegate object that performs execution uppon script loading */ fileprivate class WebViewDelegate: NSObject, WKNavigationDelegate { @@ -98,7 +95,7 @@ open class ReCaptchaWebViewManager { } /// Sends the result message - fileprivate var completion: ((Response) -> Void)? + fileprivate var completion: ((ReCaptchaResult) -> Void)? /// Configures the webview for display when required fileprivate var configureWebView: ((WKWebView) -> Void)? @@ -172,11 +169,11 @@ open class ReCaptchaWebViewManager { - 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 Result which may contain a valid result token. + - completion: A closure that receives a ReCaptchaResult which may contain a valid result token. Starts the challenge validation */ - open func validate(on view: UIView, resetOnError: Bool = true, completion: @escaping (Response) -> Void) { + open func validate(on view: UIView, resetOnError: Bool = true, completion: @escaping (ReCaptchaResult) -> Void) { self.completion = completion self.shouldResetOnError = resetOnError @@ -266,7 +263,7 @@ fileprivate extension ReCaptchaWebViewManager { func handle(result: ReCaptchaDecoder.Result) { switch result { case .token(let token): - completion?(.success(token)) + completion?(.token(token)) case .error(let error): if shouldResetOnError, let view = webView.superview, let completion = completion { @@ -274,7 +271,7 @@ fileprivate extension ReCaptchaWebViewManager { validate(on: view, completion: completion) } else { - completion?(.failure(error)) + completion?(.error(error)) } case .showReCaptcha: diff --git a/ReCaptcha/Classes/Rx/ReCaptcha+Rx.swift b/ReCaptcha/Classes/Rx/ReCaptcha+Rx.swift index 8970e4b..29d8123 100644 --- a/ReCaptcha/Classes/Rx/ReCaptcha+Rx.swift +++ b/ReCaptcha/Classes/Rx/ReCaptcha+Rx.swift @@ -22,18 +22,25 @@ public extension Reactive where Base: ReCaptchaWebViewManager { Starts the challenge validation uppon subscription. - The stream's element is a `Result` that may contain a valid token. + The stream's element is a String with the validation token. Sends `stop()` uppon disposal. - See: `ReCaptchaWebViewManager.validate(on:resetOnError:completion:)` - See: `ReCaptchaWebViewManager.stop()` */ - func validate(on view: UIView, resetOnError: Bool = true) -> Observable { - return Observable.create { [weak base] (observer: AnyObserver) in - base?.validate(on: view, resetOnError: resetOnError) { response in - observer.onNext(response) - observer.onCompleted() + func validate(on view: UIView, resetOnError: Bool = true) -> Observable { + return Observable.create { [weak base] (observer: AnyObserver) in + base?.validate(on: view, resetOnError: resetOnError) { result in + defer { observer.onCompleted() } + + switch result { + case .token(let token): + observer.onNext(token) + + case .error(let error): + observer.onError(error) + } } return Disposables.create { [weak base] in diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 36e74e2..4cd59d4 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -126,15 +126,15 @@ platform :ios do # Private -def select_similar_simulator(args) - args.map { |device_string| - pieces = device_string.split(' (') - FastlaneCore::Simulator.all - .select { |s| s.name == pieces.first } - .sort_by { |s| Gem::Version.create(s.os_version) } - .detect { |s| Gem::Requirement.new(pieces[1].tr('()', '')).satisfied_by?(Gem::Version.create(s.os_version)) } - } - .compact - .map { |s| "#{s.name} (#{s.ios_version})"} -end + def select_similar_simulator(args) + args.map { |device_string| + pieces = device_string.split(' (') + FastlaneCore::Simulator.all + .select { |s| s.name == pieces.first } + .sort_by { |s| Gem::Version.create(s.os_version) } + .detect { |s| Gem::Requirement.new(pieces[1].tr('()', '')).satisfied_by?(Gem::Version.create(s.os_version)) } + } + .compact + .map { |s| "#{s.name} (#{s.ios_version})"} + end end