diff --git a/Package.swift b/Package.swift index afac3e9f..f63ae3bf 100644 --- a/Package.swift +++ b/Package.swift @@ -66,9 +66,16 @@ let package = Package( .target(name: "TISwiftUICore", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TISwiftUICore/Sources"), // MARK: - Utils + .target(name: "TISwiftUtils", path: "TISwiftUtils/Sources"), .target(name: "TIFoundationUtils", dependencies: ["TISwiftUtils"], path: "TIFoundationUtils", exclude: ["TIFoundationUtils.app"]), - .target(name: "TIKeychainUtils", dependencies: ["TIFoundationUtils", "KeychainAccess"], path: "TIKeychainUtils/Sources"), + + .target(name: "TIKeychainUtils", + dependencies: ["TIFoundationUtils", "KeychainAccess"], + path: "TIKeychainUtils/Sources", + exclude: ["../TIKeychainUtils.app"], + plugins: [.plugin(name: "TISwiftLintPlugin")]), + .target(name: "TITableKitUtils", dependencies: ["TIUIElements", "TableKit"], path: "TITableKitUtils/Sources"), .target(name: "TIDeeplink", dependencies: ["TIFoundationUtils"], path: "TIDeeplink", exclude: ["TIDeeplink.app"]), .target(name: "TIDeveloperUtils", dependencies: ["TISwiftUtils", "TIUIKitCore", "TIUIElements"], path: "TIDeveloperUtils/Sources"), @@ -80,7 +87,11 @@ let package = Package( path: "TINetworking/Sources", plugins: [.plugin(name: "TISwiftLintPlugin")]), - .target(name: "TIMoyaNetworking", dependencies: ["TINetworking", "TIFoundationUtils", "Moya"], path: "TIMoyaNetworking"), + .target(name: "TIMoyaNetworking", + dependencies: ["TINetworking", "TIFoundationUtils", "Moya"], + path: "TIMoyaNetworking", + plugins: [.plugin(name: "TISwiftLintPlugin")]), + .target(name: "TINetworkingCache", dependencies: ["TIFoundationUtils", "TINetworking", "Cache"], path: "TINetworkingCache/Sources"), // MARK: - Maps @@ -96,7 +107,7 @@ let package = Package( .target(name: "TITextProcessing", dependencies: [.product(name: "Antlr4", package: "antlr4")], path: "TITextProcessing/Sources", - exclude: ["TITextProcessing.app"]), + exclude: ["../TITextProcessing.app"]), .binaryTarget(name: "SwiftLintBinary", url: "https://github.com/realm/SwiftLint/releases/download/0.52.2/SwiftLintBinary-macos.artifactbundle.zip", diff --git a/README.md b/README.md index a83e9cf2..9b4f48f6 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,8 @@ LICENSE - [TIFoundationUtils](docs/tifoundationutils) * [AsyncOperation](docs/tifoundationutils/asyncoperation.md) +- [TIKeychainUtils](docs/tikeychainutils) + * [SingleValueStorage](docs/tikeychainutils/singlevaluestorage.md) - [TIUIElements](docs/tiuielements) * [Skeletons](docs/tiuielements/skeletons.md) * [Placeholders](docs/tiuielements/placeholder.md) diff --git a/TIAuth/Sources/TokenStorage/DefaultAuthSettingsStorage.swift b/TIAuth/Sources/TokenStorage/DefaultAuthSettingsStorage.swift index ce72a5ea..a12f2a56 100644 --- a/TIAuth/Sources/TokenStorage/DefaultAuthSettingsStorage.swift +++ b/TIAuth/Sources/TokenStorage/DefaultAuthSettingsStorage.swift @@ -25,8 +25,8 @@ import Foundation open class DefaultAuthSettingsStorage: AuthSettingsStorage { public enum Defaults { - public static var shouldResetAuthDataKey: String { - "shouldResetAuthData" + public static var shouldResetAuthDataKey: StorageKey { + .init(rawValue: "shouldResetAuthData") } } @@ -44,7 +44,7 @@ open class DefaultAuthSettingsStorage: AuthSettingsStorage { } public init(defaultsStorage: UserDefaults = .standard, - storageKey: String = Defaults.shouldResetAuthDataKey) { + storageKey: StorageKey = Defaults.shouldResetAuthDataKey) { self.reinstallChecker = AppReinstallChecker(defaultsStorage: defaultsStorage, storageKey: storageKey) diff --git a/TIKeychainUtils/PlaygroundPodfile b/TIKeychainUtils/PlaygroundPodfile new file mode 100644 index 00000000..a39cbfee --- /dev/null +++ b/TIKeychainUtils/PlaygroundPodfile @@ -0,0 +1,11 @@ +ENV["DEVELOPMENT_INSTALL"] = "true" + +target 'TIModuleName' do + platform :ios, 11 + use_frameworks! + + pod 'TIFoundationUtils', :path => '../../../../TIFoundationUtils/TIFoundationUtils.podspec' + pod 'TISwiftUtils', :path => '../../../../TISwiftUtils/TISwiftUtils.podspec' + pod 'TIKeychainUtils', :path => '../../../../TIKeychainUtils/TIKeychainUtils.podspec' + pod 'KeychainAccess' +end diff --git a/TIKeychainUtils/Sources/AppInstallKeychainSingleValueStorage/AppInstallLifetimeSingleValueStorage.swift b/TIKeychainUtils/Sources/AppInstallKeychainSingleValueStorage/AppInstallLifetimeSingleValueStorage.swift index 781b4322..eb8fca53 100644 --- a/TIKeychainUtils/Sources/AppInstallKeychainSingleValueStorage/AppInstallLifetimeSingleValueStorage.swift +++ b/TIKeychainUtils/Sources/AppInstallKeychainSingleValueStorage/AppInstallLifetimeSingleValueStorage.swift @@ -22,7 +22,9 @@ import TIFoundationUtils -open class AppInstallLifetimeSingleValueStorage: SingleValueStorage where Storage.ErrorType == StorageError { +open class AppInstallLifetimeSingleValueStorage: SingleValueStorage +where Storage.ErrorType == StorageError { + public let appReinstallChecker: AppReinstallChecker public let wrappedStorage: Storage @@ -77,6 +79,6 @@ public extension SingleValueStorage { where Self.ErrorType == ErrorType { AppInstallLifetimeSingleValueStorage(storage: self, - appReinstallChecker: reinstallChecker) + appReinstallChecker: reinstallChecker) } } diff --git a/TIKeychainUtils/Sources/SingleValueKeychainStorage/BaseSingleValueKeychainStorage.swift b/TIKeychainUtils/Sources/SingleValueKeychainStorage/BaseSingleValueKeychainStorage.swift index f9159ae7..fff11f9c 100644 --- a/TIKeychainUtils/Sources/SingleValueKeychainStorage/BaseSingleValueKeychainStorage.swift +++ b/TIKeychainUtils/Sources/SingleValueKeychainStorage/BaseSingleValueKeychainStorage.swift @@ -33,7 +33,6 @@ open class BaseSingleValueKeychainStorage: BaseSingleValueStorage { public init(keychain: Keychain, storageKey: StorageKey) { - let getValueClosure: BaseSingleValueKeychainStorage.GetValueClosure = { keychain, storageKey in + let getValueClosure: GetValueClosure = { keychain, storageKey in do { guard let value = try keychain.get(storageKey.rawValue) else { return .failure(.valueNotFound) @@ -37,7 +37,7 @@ public final class StringValueKeychainStorage: BaseSingleValueKeychainStorage.SetValueClosure = { keychain, value, storageKey in + let setValueClosure: SetValueClosure = { keychain, value, storageKey in do { return .success(try keychain.set(value, key: storageKey.rawValue)) } catch { diff --git a/TIKeychainUtils/TIKeychainUtils.app/.gitignore b/TIKeychainUtils/TIKeychainUtils.app/.gitignore new file mode 100644 index 00000000..b7fe13ce --- /dev/null +++ b/TIKeychainUtils/TIKeychainUtils.app/.gitignore @@ -0,0 +1,4 @@ +# gitignore nef files +**/build/ +**/nef/ +LICENSE \ No newline at end of file diff --git a/TIKeychainUtils/TIKeychainUtils.app/Contents/Info.plist b/TIKeychainUtils/TIKeychainUtils.app/Contents/Info.plist new file mode 100644 index 00000000..831ea97a --- /dev/null +++ b/TIKeychainUtils/TIKeychainUtils.app/Contents/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + launcher + CFBundleIconFile + AppIcon + CFBundleIconName + AppIcon + CFBundleIdentifier + com.fortysevendeg.nef + CFBundleInfoDictionaryVersion + 6.0 + CFBundleSupportedPlatforms + + MacOSX + + LSApplicationCategoryType + public.app-category.developer-tools + LSMinimumSystemVersion + 10.14 + NSHumanReadableCopyright + Copyright © 2019 The nef Authors. All rights reserved. + + diff --git a/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/.gitignore b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/.gitignore new file mode 100644 index 00000000..18bd1f3b --- /dev/null +++ b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/.gitignore @@ -0,0 +1,26 @@ +## gitignore nef files +**/build/ +**/nef/ +LICENSE + +## User data +**/xcuserdata/ +podfile.lock +**.DS_Store + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +## CocoaPods +**Pods** + +## Carthage +**Carthage** + +## SPM +.build +.swiftpm +swiftpm diff --git a/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/Podfile b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/Podfile new file mode 100644 index 00000000..f0a332cf --- /dev/null +++ b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/Podfile @@ -0,0 +1,11 @@ +ENV["DEVELOPMENT_INSTALL"] = "true" + +target 'TIKeychainUtils' do + platform :ios, 11 + use_frameworks! + + pod 'TIFoundationUtils', :path => '../../../../TIFoundationUtils/TIFoundationUtils.podspec' + pod 'TISwiftUtils', :path => '../../../../TISwiftUtils/TISwiftUtils.podspec' + pod 'TIKeychainUtils', :path => '../../../../TIKeychainUtils/TIKeychainUtils.podspec' + pod 'KeychainAccess' +end diff --git a/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.playground/Pages/SingleValueStorage.xcplaygroundpage/Contents.swift b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.playground/Pages/SingleValueStorage.xcplaygroundpage/Contents.swift new file mode 100644 index 00000000..3d6b2e97 --- /dev/null +++ b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.playground/Pages/SingleValueStorage.xcplaygroundpage/Contents.swift @@ -0,0 +1,71 @@ +/*: + # `SingleValueStorage` - протокол для доступа к значению которое может храниться в Keychain, UserDefaults или ещё где-то. + +Позволяет: + +- инкапсулировать внутри себя логику получения, записи и удаления значения +- добавлять дополнительную логику для получения или изменения значений через композицию или наследование +- ограничить доступ к данным в UserDefaults или Keychain в разных частях приложения + + */ + +/*: + ### `StringValueKeychainStorage` + + Класс для работы со строковым значением нахоящимся в keychain (самый частый кейс) +*/ + +import TIKeychainUtils +import TIFoundationUtils +import KeychainAccess + +extension StorageKey { + static var apiToken: StorageKey { + .init(rawValue: "apiToken") + } + + static var deleteApiToken: StorageKey { + .init(rawValue: "deleteApiToken") + } +} + +let apiTokenKeychainStorage = StringValueKeychainStorage(keychain: keychain, storageKey: .apiToken) + +if apiTokenKeychainStorage.hasStoredValue() { + // app wasn't reinstalled, open auth user flow, perform requests +} else { + // show login screen + // ... + + // login + +// switch await userService.login() { +// case .success: +// // open auth user flow, perform requests +// case .failure: +// // show login screen +// } +} + +/*: + ### `AppInstallLifetimeSingleValueStorage` + + Класс позволяющий добавить дополнительную функциональность очистки значения по конкретному ключу в keychain + после переустановки приложения +*/ + +import Foundation + +let defaults = UserDefaults.standard // or AppGroup defaults + +let appReinstallChecker = AppReinstallChecker(defaultsStorage: defaults, + storageKey: .deleteApiToken) + +let appInstallAwareTokenStorage = apiTokenKeychainStorage.appInstallLifetimeStorage(reinstallChecker: appReinstallChecker) + +if appInstallAwareTokenStorage.hasStoredValue() { + // app wasn't reinstalled, token is exist +} else { + // app was reinstalled or token is empty + // ... +} diff --git a/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.playground/Sources/NefPlaygroundSupport.swift b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.playground/Sources/NefPlaygroundSupport.swift new file mode 100644 index 00000000..2ffea80a --- /dev/null +++ b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.playground/Sources/NefPlaygroundSupport.swift @@ -0,0 +1,30 @@ +import UIKit + +public protocol NefPlaygroundLiveViewable {} +extension UIView: NefPlaygroundLiveViewable {} +extension UIViewController: NefPlaygroundLiveViewable {} + +#if NOT_IN_PLAYGROUND +public enum Nef { + public enum Playground { + public static func liveView(_ view: NefPlaygroundLiveViewable) {} + public static func needsIndefiniteExecution(_ state: Bool) {} + } +} + +#else +import PlaygroundSupport + +public enum Nef { + public enum Playground { + public static func liveView(_ view: NefPlaygroundLiveViewable) { + PlaygroundPage.current.liveView = (view as! PlaygroundLiveViewable) + } + + public static func needsIndefiniteExecution(_ state: Bool) { + PlaygroundPage.current.needsIndefiniteExecution = state + } + } +} + +#endif diff --git a/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.playground/contents.xcplayground b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.playground/contents.xcplayground new file mode 100644 index 00000000..00daa653 --- /dev/null +++ b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.playground/contents.xcplayground @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.xcodeproj/project.pbxproj b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.xcodeproj/project.pbxproj new file mode 100644 index 00000000..838e90d3 --- /dev/null +++ b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.xcodeproj/project.pbxproj @@ -0,0 +1,396 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 419F8E81EC23E596305C14C1 /* Pods_TIKeychainUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A9558DC3B75CF88D5A98670 /* Pods_TIKeychainUtils.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 02F7E6D0F4B151F4585D0961 /* Pods-TIKeychainUtils.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TIKeychainUtils.debug.xcconfig"; path = "Target Support Files/Pods-TIKeychainUtils/Pods-TIKeychainUtils.debug.xcconfig"; sourceTree = ""; }; + 1A9558DC3B75CF88D5A98670 /* Pods_TIKeychainUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TIKeychainUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7D43492919D876D7B5F60316 /* Pods-TIKeychainUtils.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TIKeychainUtils.release.xcconfig"; path = "Target Support Files/Pods-TIKeychainUtils/Pods-TIKeychainUtils.release.xcconfig"; sourceTree = ""; }; + 8BACBE8322576CAD00266845 /* TIKeychainUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TIKeychainUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8BACBE8622576CAD00266845 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8BACBE8022576CAD00266845 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 419F8E81EC23E596305C14C1 /* Pods_TIKeychainUtils.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 75FB9D9E19767EA711BCA3E2 /* Pods */ = { + isa = PBXGroup; + children = ( + 02F7E6D0F4B151F4585D0961 /* Pods-TIKeychainUtils.debug.xcconfig */, + 7D43492919D876D7B5F60316 /* Pods-TIKeychainUtils.release.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 8B39A26221D40F8700DE2643 = { + isa = PBXGroup; + children = ( + 8BACBE8422576CAD00266845 /* TIKeychainUtils */, + 8B39A26C21D40F8700DE2643 /* Products */, + 75FB9D9E19767EA711BCA3E2 /* Pods */, + B96CA711C514498357DF4109 /* Frameworks */, + ); + sourceTree = ""; + }; + 8B39A26C21D40F8700DE2643 /* Products */ = { + isa = PBXGroup; + children = ( + 8BACBE8322576CAD00266845 /* TIKeychainUtils.framework */, + ); + name = Products; + sourceTree = ""; + }; + 8BACBE8422576CAD00266845 /* TIKeychainUtils */ = { + isa = PBXGroup; + children = ( + 8BACBE8622576CAD00266845 /* Info.plist */, + ); + path = TIKeychainUtils; + sourceTree = ""; + }; + B96CA711C514498357DF4109 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 1A9558DC3B75CF88D5A98670 /* Pods_TIKeychainUtils.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 8BACBE7E22576CAD00266845 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 8BACBE8222576CAD00266845 /* TIKeychainUtils */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8BACBE8A22576CAD00266845 /* Build configuration list for PBXNativeTarget "TIKeychainUtils" */; + buildPhases = ( + FB020BFF1242B09050D0D379 /* [CP] Check Pods Manifest.lock */, + 8BACBE7E22576CAD00266845 /* Headers */, + 8BACBE7F22576CAD00266845 /* Sources */, + 8BACBE8022576CAD00266845 /* Frameworks */, + 8BACBE8122576CAD00266845 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = TIKeychainUtils; + productName = TIKeychainUtils2; + productReference = 8BACBE8322576CAD00266845 /* TIKeychainUtils.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 8B39A26321D40F8700DE2643 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1010; + LastUpgradeCheck = 1200; + ORGANIZATIONNAME = "47 Degrees"; + TargetAttributes = { + 8BACBE8222576CAD00266845 = { + CreatedOnToolsVersion = 10.1; + }; + }; + }; + buildConfigurationList = 8B39A26621D40F8700DE2643 /* Build configuration list for PBXProject "TIKeychainUtils" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 8B39A26221D40F8700DE2643; + productRefGroup = 8B39A26C21D40F8700DE2643 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8BACBE8222576CAD00266845 /* TIKeychainUtils */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8BACBE8122576CAD00266845 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + FB020BFF1242B09050D0D379 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-TIKeychainUtils-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; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 8BACBE7F22576CAD00266845 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 8B39A27721D40F8800DE2643 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_TESTING_SEARCH_PATHS = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 8B39A27821D40F8800DE2643 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTING_SEARCH_PATHS = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 8BACBE8822576CAD00266845 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 02F7E6D0F4B151F4585D0961 /* Pods-TIKeychainUtils.debug.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + CURRENT_TIKeychainUtils_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "$(SRCROOT)/TIKeychainUtils/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.47deg.ios.TIKeychainUtils; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 8BACBE8922576CAD00266845 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7D43492919D876D7B5F60316 /* Pods-TIKeychainUtils.release.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + CURRENT_TIKeychainUtils_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "$(SRCROOT)/TIKeychainUtils/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.47deg.ios.TIKeychainUtils; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 8B39A26621D40F8700DE2643 /* Build configuration list for PBXProject "TIKeychainUtils" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8B39A27721D40F8800DE2643 /* Debug */, + 8B39A27821D40F8800DE2643 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8BACBE8A22576CAD00266845 /* Build configuration list for PBXNativeTarget "TIKeychainUtils" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8BACBE8822576CAD00266845 /* Debug */, + 8BACBE8922576CAD00266845 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 8B39A26321D40F8700DE2643 /* Project object */; +} diff --git a/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..423fc503 --- /dev/null +++ b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.xcodeproj/xcshareddata/xcschemes/TIKeychainUtils.xcscheme b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.xcodeproj/xcshareddata/xcschemes/TIKeychainUtils.xcscheme new file mode 100644 index 00000000..065c0e82 --- /dev/null +++ b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.xcodeproj/xcshareddata/xcschemes/TIKeychainUtils.xcscheme @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.xcworkspace/contents.xcworkspacedata b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..530d439c --- /dev/null +++ b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils/Info.plist b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils/Info.plist new file mode 100644 index 00000000..98d14f60 --- /dev/null +++ b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + NSHumanReadableCopyright + Copyright © 2019. The nef authors. + + diff --git a/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/launcher b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/launcher new file mode 100755 index 00000000..fd9aba4b --- /dev/null +++ b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/launcher @@ -0,0 +1,6 @@ +#!/bin/bash + +workspace="TIKeychainUtils.xcworkspace" +workspacePath=$(echo "$0" | rev | cut -f2- -d '/' | rev) + +open "`pwd`/$workspacePath/$workspace" diff --git a/TIKeychainUtils/TIKeychainUtils.app/Contents/Resources/AppIcon.icns b/TIKeychainUtils/TIKeychainUtils.app/Contents/Resources/AppIcon.icns new file mode 100644 index 00000000..32814f1c Binary files /dev/null and b/TIKeychainUtils/TIKeychainUtils.app/Contents/Resources/AppIcon.icns differ diff --git a/TIKeychainUtils/TIKeychainUtils.app/Contents/Resources/Assets.car b/TIKeychainUtils/TIKeychainUtils.app/Contents/Resources/Assets.car new file mode 100644 index 00000000..79d9ea89 Binary files /dev/null and b/TIKeychainUtils/TIKeychainUtils.app/Contents/Resources/Assets.car differ diff --git a/TIKeychainUtils/TIKeychainUtils.playground b/TIKeychainUtils/TIKeychainUtils.playground new file mode 120000 index 00000000..c1284cc1 --- /dev/null +++ b/TIKeychainUtils/TIKeychainUtils.playground @@ -0,0 +1 @@ +TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.playground \ No newline at end of file diff --git a/TIMoyaNetworking/Sources/NetworkService/DefaultJsonNetworkService.swift b/TIMoyaNetworking/Sources/NetworkService/DefaultJsonNetworkService.swift index f7da59ff..dca3a6cb 100644 --- a/TIMoyaNetworking/Sources/NetworkService/DefaultJsonNetworkService.swift +++ b/TIMoyaNetworking/Sources/NetworkService/DefaultJsonNetworkService.swift @@ -69,10 +69,10 @@ open class DefaultJsonNetworkService: ApiInteractor { } open func process(request: EndpointRequest, - mapSuccess: @escaping Closure, - mapFailure: @escaping Closure, R>, - mapNetworkError: @escaping Closure, - completion: @escaping ParameterClosure) -> TIFoundationUtils.Cancellable { + mapSuccess: @escaping Closure, + mapFailure: @escaping Closure, R>, + mapNetworkError: @escaping Closure, + completion: @escaping ParameterClosure) -> TIFoundationUtils.Cancellable { let cancellableBag = BaseCancellableBag() @@ -94,7 +94,7 @@ open class DefaultJsonNetworkService: ApiInteractor { mapFailure: mapFailure, mapNetworkError: mapNetworkError, completion: completion) - .add(to: cancellableBag) + .add(to: cancellableBag) } catch { callbackQueue.async { completion(mapNetworkError(.encodableMapping(error))) @@ -116,10 +116,10 @@ open class DefaultJsonNetworkService: ApiInteractor { } open func process(request: SerializedRequest, - mapSuccess: @escaping Closure, - mapFailure: @escaping Closure, R>, - mapNetworkError: @escaping Closure, - completion: @escaping ParameterClosure) -> TIFoundationUtils.Cancellable { + mapSuccess: @escaping Closure, + mapFailure: @escaping Closure, R>, + mapNetworkError: @escaping Closure, + completion: @escaping ParameterClosure) -> TIFoundationUtils.Cancellable { createProvider().request(request) { [jsonDecoder, callbackQueue, @@ -195,12 +195,12 @@ open class DefaultJsonNetworkService: ApiInteractor { } private static func preprocess(request: EndpointRequest, - preprocessors: P, - cancellableBag: BaseCancellableBag, - completion: @escaping (Result, Error>) -> Void) + preprocessors: P, + cancellableBag: BaseCancellableBag, + completion: @escaping (Result, Error>) -> Void) where P.Element == EndpointRequestPreprocessor { - guard let preprocessor = preprocessors.first else { + guard let preprocessor = preprocessors.first, !cancellableBag.isCancelled else { completion(.success(request)) return } diff --git a/TIMoyaNetworking/Sources/RecoverableNetworkService/DefaultRecoverableJsonNetworkService.swift b/TIMoyaNetworking/Sources/RecoverableNetworkService/DefaultRecoverableJsonNetworkService.swift index 150fcb4e..5430b716 100644 --- a/TIMoyaNetworking/Sources/RecoverableNetworkService/DefaultRecoverableJsonNetworkService.swift +++ b/TIMoyaNetworking/Sources/RecoverableNetworkService/DefaultRecoverableJsonNetworkService.swift @@ -23,6 +23,7 @@ import TINetworking import TISwiftUtils import TIFoundationUtils +import Alamofire open class DefaultRecoverableJsonNetworkService: DefaultJsonNetworkService { public typealias EndpointResponse = EndpointRecoverableRequestResult @@ -61,13 +62,19 @@ open class DefaultRecoverableJsonNetworkService: De completion: @escaping ParameterClosure>) -> Cancellable { Cancellables.scoped { cancellableBag in - process(request: recoverableRequest) { (result: RequestResult) in + process(request: recoverableRequest) { [weak self] (result: RequestResult) in if case let .failure(errorResponse) = result { - Self.validateAndRepair(recoverableRequest: recoverableRequest, - errors: [errorResponse], - retriers: errorHandlers, - cancellableBag: cancellableBag, - completion: completion) + guard let self, !cancellableBag.isCancelled else { + completion(result.mapError { .init(failures: [$0]) }) + return + } + + self.recover(request: recoverableRequest, + errorHandlers: errorHandlers, + errorResponse: errorResponse, + originalResult: result, + cancellableBag: cancellableBag, + completion: completion) } else { completion(result.mapError { .init(failures: [$0]) }) } @@ -86,44 +93,27 @@ open class DefaultRecoverableJsonNetworkService: De } } - private static func validateAndRepair(recoverableRequest: EndpointRequest, - errors: [ErrorType], - retriers: R, - cancellableBag: BaseCancellableBag, - completion: @escaping ParameterClosure>) - where R.Element == RequestRetrier { + open func recover(request: EndpointRequest, + errorHandlers: [RequestRetrier], + errorResponse: ErrorType, + originalResult: RequestResult, + cancellableBag: BaseCancellableBag, + completion: @escaping ParameterClosure>) { - guard let retrier = retriers.first, !cancellableBag.isCancelled else { - completion(.failure(.init(failures: errors))) - return - } - - retrier.validateAndRepair(errorResults: errors) { handlerResult in - switch handlerResult { - case let .success(retryResult): - switch retryResult { - case .retry, .retryWithDelay: - validateAndRepair(recoverableRequest: recoverableRequest, - errors: errors, - retriers: retriers, - cancellableBag: cancellableBag, - completion: completion) - case .doNotRetry, .doNotRetryWithError: - validateAndRepair(recoverableRequest: recoverableRequest, - errors: errors, - retriers: retriers.dropFirst(), - cancellableBag: cancellableBag, - completion: completion) + Self.validateAndRepair(request: request, + errors: [errorResponse], + retriers: errorHandlers, + cancellableBag: cancellableBag) { + switch $0 { + case .retry, .retryWithDelay: + self.process(request: request) { + completion($0.mapError { .init(failures: [$0]) }) } - case let .failure(error): - validateAndRepair(recoverableRequest: recoverableRequest, - errors: errors + [error], - retriers: retriers.dropFirst(), - cancellableBag: cancellableBag, - completion: completion) + .add(to: cancellableBag) + case .doNotRetry, .doNotRetryWithError: + completion(originalResult.mapError { .init(failures: [$0]) }) } } - .add(to: cancellableBag) } public func register(defaultRequestRetrier: RequestRetrier) @@ -137,4 +127,40 @@ open class DefaultRecoverableJsonNetworkService: De self.defaultRequestRetriers = defaultRequestRetriers.map { $0.asAnyEndpointRequestRetrier() } } + + private static func validateAndRepair(request: EndpointRequest, + errors: [ErrorType], + retriers: R, + cancellableBag: BaseCancellableBag, + completion: @escaping ParameterClosure) + where R.Element == RequestRetrier { + + guard let retrier = retriers.first, !cancellableBag.isCancelled else { + completion(.doNotRetry) + return + } + + retrier.validateAndRepair(errorResults: errors) { handlerResult in + switch handlerResult { + case let .success(retryResult): + switch retryResult { + case .retry, .retryWithDelay: + completion(.retry) + case .doNotRetry, .doNotRetryWithError: + validateAndRepair(request: request, + errors: errors, + retriers: retriers.dropFirst(), + cancellableBag: cancellableBag, + completion: completion) + } + case let .failure(error): + validateAndRepair(request: request, + errors: errors + [error], + retriers: retriers.dropFirst(), + cancellableBag: cancellableBag, + completion: completion) + } + } + .add(to: cancellableBag) + } } diff --git a/TINetworking/Sources/Alamofire/FingerprintsTrustEvaluation/DefaultFingerprintsSettingsStorage.swift b/TINetworking/Sources/Alamofire/FingerprintsTrustEvaluation/DefaultFingerprintsSettingsStorage.swift index 6926f1eb..50d833ba 100644 --- a/TINetworking/Sources/Alamofire/FingerprintsTrustEvaluation/DefaultFingerprintsSettingsStorage.swift +++ b/TINetworking/Sources/Alamofire/FingerprintsTrustEvaluation/DefaultFingerprintsSettingsStorage.swift @@ -25,8 +25,8 @@ import Foundation open class DefaultFingerprintsSettingsStorage: FingerprintsSettingsStorage { public enum Defaults { - public static var shouldResetFingerprintsKey: String { - "shouldResetFingerprints" + public static var shouldResetFingerprintsKey: StorageKey { + .init(rawValue: "shouldResetFingerprints") } } @@ -44,7 +44,7 @@ open class DefaultFingerprintsSettingsStorage: FingerprintsSettingsStorage { } public init(defaultsStorage: UserDefaults = .standard, - storageKey: String = Defaults.shouldResetFingerprintsKey) { + storageKey: StorageKey = Defaults.shouldResetFingerprintsKey) { self.reinstallChecker = AppReinstallChecker(defaultsStorage: defaultsStorage, storageKey: storageKey) } diff --git a/TINetworking/Sources/ApiInteractor/ApiInteractor.swift b/TINetworking/Sources/ApiInteractor/ApiInteractor.swift index 6afad2af..ffe1c485 100644 --- a/TINetworking/Sources/ApiInteractor/ApiInteractor.swift +++ b/TINetworking/Sources/ApiInteractor/ApiInteractor.swift @@ -65,7 +65,8 @@ public extension ApiInteractor { process(request: request, mapSuccess: mapSuccess, mapFailure: mapFailure, - mapNetworkError: mapNetworkError, completion: $0) + mapNetworkError: mapNetworkError, + completion: $0) } } } diff --git a/TINetworking/Sources/RequestPreprocessors/DefaultEndpointSecurityRequestPreprocessor.swift b/TINetworking/Sources/RequestPreprocessors/DefaultEndpointSecurityRequestPreprocessor.swift index 7e81d427..cabd5c80 100644 --- a/TINetworking/Sources/RequestPreprocessors/DefaultEndpointSecurityRequestPreprocessor.swift +++ b/TINetworking/Sources/RequestPreprocessors/DefaultEndpointSecurityRequestPreprocessor.swift @@ -41,6 +41,7 @@ open class DefaultSecuritySchemePreprocessor: SecuritySchemePreprocessor { completion(.failure(ValueNotProvidedError())) return } + completion(.success(value)) } } diff --git a/build-scripts b/build-scripts index 39109c6e..318e0ce0 160000 --- a/build-scripts +++ b/build-scripts @@ -1 +1 @@ -Subproject commit 39109c6e6032b2a59f4cdd7b80ac06c4dc8b33c0 +Subproject commit 318e0ce0215da8c790f9a4ea945d1773cb35687f diff --git a/docs/tikeychainutils/singlevaluestorage.md b/docs/tikeychainutils/singlevaluestorage.md new file mode 100644 index 00000000..33600ad0 --- /dev/null +++ b/docs/tikeychainutils/singlevaluestorage.md @@ -0,0 +1,70 @@ + +# `SingleValueStorage` - протокол для доступа к значению которое может храниться в Keychain, UserDefaults или ещё где-то. + +Позволяет: + +- инкапсулировать внутри себя логику получения, записи и удаления значения +- добавлять дополнительную логику для получения или изменения значений через композицию или наследование +- ограничить доступ к данным в UserDefaults или Keychain в разных частях приложения + + +### `StringValueKeychainStorage` + + Класс для работы со строковым значением нахоящимся в keychain (самый частый кейс) + +```swift +import TIKeychainUtils +import TIFoundationUtils +import KeychainAccess + +extension StorageKey { + static var apiToken: StorageKey { + .init(rawValue: "apiToken") + } + + static var deleteApiToken: StorageKey { + .init(rawValue: "deleteApiToken") + } +} + +let apiTokenKeychainStorage = StringValueKeychainStorage(keychain: keychain, storageKey: .apiToken) + +if apiTokenKeychainStorage.hasStoredValue() { + // app wasn't reinstalled, open auth user flow, perform requests +} else { + // show login screen + // ... + + // login + +// switch await userService.login() { +// case .success: +// // open auth user flow, perform requests +// case .failure: +// // show login screen +// } +} +``` + +### `AppInstallLifetimeSingleValueStorage` + + Класс позволяющий добавить дополнительную функциональность очистки значения по конкретному ключу в keychain + после переустановки приложения + +```swift +import Foundation + +let defaults = UserDefaults.standard // or AppGroup defaults + +let appReinstallChecker = AppReinstallChecker(defaultsStorage: defaults, + storageKey: .deleteApiToken) + +let appInstallAwareTokenStorage = apiTokenKeychainStorage.appInstallLifetimeStorage(reinstallChecker: appReinstallChecker) + +if appInstallAwareTokenStorage.hasStoredValue() { + // app wasn't reinstalled, token is exist +} else { + // app was reinstalled or token is empty + // ... +} +```