From 345f661f997c139a63518cce3bbe58bb43abedc0 Mon Sep 17 00:00:00 2001 From: Boyko Mihail Date: Wed, 28 Apr 2021 15:06:28 +0300 Subject: [PATCH] feat: Add TIKeychainUtils --- LeadKit.podspec | 2 +- Package.swift | 2 + TIFoundationUtils/TIFoundationUtils.podspec | 2 +- TIKeychainUtils/README.md | 1 + .../CodableKeyValueStorage+BackingStore.swift | 49 ++++++++++ .../CodableKeyValueStorage.swift | 91 +++++++++++++++++++ .../Decoders/CodableKeyValueDecoder.swift | 27 ++++++ .../Decoders/JSONKeyValueDecoder.swift | 39 ++++++++ .../Decoders/UnarchiverKeyValueDecoder.swift | 47 ++++++++++ .../Encoders/ArchiverKeyValueEncoder.swift | 41 +++++++++ .../Encoders/CodableKeyValueEncoder.swift | 27 ++++++ .../Encoders/JSONKeyValueEncoder.swift | 39 ++++++++ .../CodableKeyValueStorage/StorageError.swift | 29 ++++++ .../CodableKeyValueStorage/StorageKey.swift | 29 ++++++ .../UserDefaults+CodableKeyValueStorage.swift | 52 +++++++++++ .../UserDefaultsCodableBackingStore.swift | 25 +++++ .../Keychain+CodableKeyValueStorage.swift | 43 +++++++++ .../KeychainAccess/Keychain+Factory.swift | 18 ++++ .../KeychainCodableBackingStore.swift | 4 + .../UserDefaultsBackingStore.swift | 69 ++++++++++++++ TIKeychainUtils/TIKeychainUtils.podspec | 17 ++++ TISwiftUtils/TISwiftUtils.podspec | 2 +- TITableKitUtils/TITableKitUtils.podspec | 2 +- TITransitions/TITransitions.podspec | 2 +- TIUIElements/TIUIElements.podspec | 2 +- TIUIKitCore/TIUIKitCore.podspec | 2 +- 26 files changed, 656 insertions(+), 7 deletions(-) create mode 100644 TIKeychainUtils/README.md create mode 100644 TIKeychainUtils/Sources/CodableKeyValueStorage/CodableKeyValueStorage+BackingStore.swift create mode 100644 TIKeychainUtils/Sources/CodableKeyValueStorage/CodableKeyValueStorage.swift create mode 100644 TIKeychainUtils/Sources/CodableKeyValueStorage/Decoders/CodableKeyValueDecoder.swift create mode 100644 TIKeychainUtils/Sources/CodableKeyValueStorage/Decoders/JSONKeyValueDecoder.swift create mode 100644 TIKeychainUtils/Sources/CodableKeyValueStorage/Decoders/UnarchiverKeyValueDecoder.swift create mode 100644 TIKeychainUtils/Sources/CodableKeyValueStorage/Encoders/ArchiverKeyValueEncoder.swift create mode 100644 TIKeychainUtils/Sources/CodableKeyValueStorage/Encoders/CodableKeyValueEncoder.swift create mode 100644 TIKeychainUtils/Sources/CodableKeyValueStorage/Encoders/JSONKeyValueEncoder.swift create mode 100644 TIKeychainUtils/Sources/CodableKeyValueStorage/StorageError.swift create mode 100644 TIKeychainUtils/Sources/CodableKeyValueStorage/StorageKey.swift create mode 100644 TIKeychainUtils/Sources/CodableKeyValueStorage/UserDefaults+CodableKeyValueStorage.swift create mode 100644 TIKeychainUtils/Sources/CodableKeyValueStorage/UserDefaultsCodableBackingStore.swift create mode 100644 TIKeychainUtils/Sources/KeychainAccess/Keychain+CodableKeyValueStorage.swift create mode 100644 TIKeychainUtils/Sources/KeychainAccess/Keychain+Factory.swift create mode 100644 TIKeychainUtils/Sources/KeychainAccess/KeychainCodableBackingStore.swift create mode 100644 TIKeychainUtils/Sources/UserDefaultsBackingStore/UserDefaultsBackingStore.swift create mode 100644 TIKeychainUtils/TIKeychainUtils.podspec diff --git a/LeadKit.podspec b/LeadKit.podspec index 4d7ffe43..0899b713 100644 --- a/LeadKit.podspec +++ b/LeadKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "LeadKit" - s.version = "1.1.1" + s.version = "1.1.2" s.summary = "iOS framework with a bunch of tools for rapid development" s.homepage = "https://github.com/TouchInstinct/LeadKit" s.license = "Apache License, Version 2.0" diff --git a/Package.swift b/Package.swift index 6c5640d1..df0f148c 100644 --- a/Package.swift +++ b/Package.swift @@ -11,6 +11,7 @@ let package = Package( .library(name: "TIUIKitCore", targets: ["TIUIKitCore"]), .library(name: "TISwiftUtils", targets: ["TISwiftUtils"]), .library(name: "TIFoundationUtils", targets: ["TIFoundationUtils"]), + .library(name: "TIKeychainUtils", targets: ["TIKeychainUtils"]), .library(name: "TIUIElements", targets: ["TIUIElements"]), .library(name: "TITableKitUtils", targets: ["TITableKitUtils"]), .library(name: "OTPSwiftView", targets: ["OTPSwiftView"]) @@ -23,6 +24,7 @@ let package = Package( .target(name: "TIUIKitCore", path: "TIUIKitCore/Sources"), .target(name: "TISwiftUtils", path: "TISwiftUtils/Sources"), .target(name: "TIFoundationUtils", dependencies: ["TISwiftUtils"], path: "TIFoundationUtils/Sources"), + .target(name: "TIKeychainUtils", dependencies: ["TISwiftUtils"], path: "TIKeychainUtils/Sources"), .target(name: "TIUIElements", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TIUIElements/Sources"), .target(name: "TITableKitUtils", dependencies: ["TIUIElements", "TableKit"], path: "TITableKitUtils/Sources"), .target(name: "OTPSwiftView", dependencies: ["TIUIElements"], path: "OTPSwiftView/Sources") diff --git a/TIFoundationUtils/TIFoundationUtils.podspec b/TIFoundationUtils/TIFoundationUtils.podspec index 89acf69b..c63f8958 100644 --- a/TIFoundationUtils/TIFoundationUtils.podspec +++ b/TIFoundationUtils/TIFoundationUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIFoundationUtils' - s.version = '1.1.1' + s.version = '1.1.2' s.summary = 'Set of helpers for Foundation framework classes.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIKeychainUtils/README.md b/TIKeychainUtils/README.md new file mode 100644 index 00000000..40e63bd6 --- /dev/null +++ b/TIKeychainUtils/README.md @@ -0,0 +1 @@ +# TIKeychainUtils diff --git a/TIKeychainUtils/Sources/CodableKeyValueStorage/CodableKeyValueStorage+BackingStore.swift b/TIKeychainUtils/Sources/CodableKeyValueStorage/CodableKeyValueStorage+BackingStore.swift new file mode 100644 index 00000000..9824afbc --- /dev/null +++ b/TIKeychainUtils/Sources/CodableKeyValueStorage/CodableKeyValueStorage+BackingStore.swift @@ -0,0 +1,49 @@ +// +// Copyright (c) 2020 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import TISwiftUtils + +public typealias CodableKeyValueBackingStore = BackingStore + +public extension BackingStore where Store: CodableKeyValueStorage, StoreContent: Codable { + init(key: StorageKey, + codableKeyValueStorage: Store, + decoder: CodableKeyValueDecoder = JSONKeyValueDecoder(), + encoder: CodableKeyValueEncoder = JSONKeyValueEncoder()) + where StoreContent == Value? { + + self.init(store: codableKeyValueStorage, + getClosure: { try? $0.codableObject(forKey: key, decoder: decoder) }, + setClosure: { try? $0.setOrRemove(codableObject: $1, forKey: key, encoder: encoder) }) + } + + init(wrappedValue: StoreContent, + key: StorageKey, + codableKeyValueStorage: Store, + decoder: CodableKeyValueDecoder = JSONKeyValueDecoder(), + encoder: CodableKeyValueEncoder = JSONKeyValueEncoder()) { + + self.init(store: codableKeyValueStorage, + getClosure: { $0.codableObject(forKey: key, defaultValue: wrappedValue, decoder: decoder) }, + setClosure: { try? $0.setOrRemove(codableObject: $1, forKey: key, encoder: encoder) }) + } +} diff --git a/TIKeychainUtils/Sources/CodableKeyValueStorage/CodableKeyValueStorage.swift b/TIKeychainUtils/Sources/CodableKeyValueStorage/CodableKeyValueStorage.swift new file mode 100644 index 00000000..f1e4f8d4 --- /dev/null +++ b/TIKeychainUtils/Sources/CodableKeyValueStorage/CodableKeyValueStorage.swift @@ -0,0 +1,91 @@ +// +// Copyright (c) 2020 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public protocol CodableKeyValueStorage { + /// Returns the object with specified type associated with the first occurrence of the specified key. + /// - Parameters: + /// - key: A key in the storage. + /// - decoder: CodableKeyValueDecoder to decode stored data. + /// - Returns: The object with specified type associated with the specified key, + /// or throw exception if the key was not found. + /// - Throws: CodableStorageError + func codableObject(forKey key: StorageKey, + decoder: CodableKeyValueDecoder) throws -> Value + + /// Set or remove the value of the specified key in the storage. + /// - Parameters: + /// - object: The object with specified type to store. + /// - key: The key with which to associate with the value. + /// - encoder: CodableKeyValueEncoder to encode to encode passed object. + /// - Throws: EncodingError if error is occured during passed object encoding. + func set(encodableObject: Value, + forKey key: StorageKey, + encoder: CodableKeyValueEncoder) throws + + /// Removes value for specific key + /// - Parameter key: The key with which to associate with the value. + /// - Throws: EncodingError if error is occured during reading/writing. + func removeCodableValue(forKey key: StorageKey) throws +} + +public extension CodableKeyValueStorage { + + /// Returns the object with specified type associated with the first occurrence of the specified key. + /// - Parameters: + /// - key: A key in the storage. + /// - defaultValue: A default value that will be used if there is no such value for specified key, + /// - decoder: CodableKeyValueDecoder to decode stored data. + /// or if error occurred during decoding + /// - Returns: The object with specified type associated with the specified key, or passed default value + /// if there is no such value for specified key or if error occurred during mapping. + func codableObject(forKey key: StorageKey, + defaultValue: Value, + decoder: CodableKeyValueDecoder = JSONKeyValueDecoder()) -> Value { + + (try? codableObject(forKey: key, decoder: decoder)) ?? defaultValue + } + + func setOrRemove(codableObject: Value?, + forKey key: StorageKey, + encoder: CodableKeyValueEncoder = JSONKeyValueEncoder()) throws { + + if let codableObject = codableObject { + try set(encodableObject: codableObject, forKey: key, encoder: encoder) + } else { + try? removeCodableValue(forKey: key) + } + } + + subscript(key: StorageKey, + decoder: CodableKeyValueDecoder = JSONKeyValueDecoder(), + encoder: CodableKeyValueEncoder = JSONKeyValueEncoder()) -> Value? { + + get { + try? codableObject(forKey: key, decoder: decoder) + } + set { + try? setOrRemove(codableObject: newValue, forKey: key, encoder: encoder) + } + } +} diff --git a/TIKeychainUtils/Sources/CodableKeyValueStorage/Decoders/CodableKeyValueDecoder.swift b/TIKeychainUtils/Sources/CodableKeyValueStorage/Decoders/CodableKeyValueDecoder.swift new file mode 100644 index 00000000..54e4cf99 --- /dev/null +++ b/TIKeychainUtils/Sources/CodableKeyValueStorage/Decoders/CodableKeyValueDecoder.swift @@ -0,0 +1,27 @@ +// +// Copyright (c) 2020 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public protocol CodableKeyValueDecoder { + func decodeDecodable(from data: Data, for key: StorageKey) throws -> Value +} diff --git a/TIKeychainUtils/Sources/CodableKeyValueStorage/Decoders/JSONKeyValueDecoder.swift b/TIKeychainUtils/Sources/CodableKeyValueStorage/Decoders/JSONKeyValueDecoder.swift new file mode 100644 index 00000000..406f89b9 --- /dev/null +++ b/TIKeychainUtils/Sources/CodableKeyValueStorage/Decoders/JSONKeyValueDecoder.swift @@ -0,0 +1,39 @@ +// +// Copyright (c) 2020 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +open class JSONKeyValueDecoder: CodableKeyValueDecoder { + private let jsonDecoder: JSONDecoder + + public init(jsonDecoder: JSONDecoder = JSONDecoder()) { + self.jsonDecoder = jsonDecoder + } + + open func decodeDecodable(from data: Data, for key: StorageKey) throws -> Value { + do { + return try jsonDecoder.decode(Value.self, from: data) + } catch { + throw StorageError.unableToDecode(underlyingError: error) + } + } +} diff --git a/TIKeychainUtils/Sources/CodableKeyValueStorage/Decoders/UnarchiverKeyValueDecoder.swift b/TIKeychainUtils/Sources/CodableKeyValueStorage/Decoders/UnarchiverKeyValueDecoder.swift new file mode 100644 index 00000000..49e0d5a7 --- /dev/null +++ b/TIKeychainUtils/Sources/CodableKeyValueStorage/Decoders/UnarchiverKeyValueDecoder.swift @@ -0,0 +1,47 @@ +// +// Copyright (c) 2020 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +open class UnarchiverKeyValueDecoder: CodableKeyValueDecoder { + public init() {} + + open func decodeDecodable(from data: Data, for key: StorageKey) throws -> Value { + let unarchiver: NSKeyedUnarchiver + + do { + unarchiver = try NSKeyedUnarchiver(forReadingFrom: data) + } catch { + throw StorageError.unableToDecode(underlyingError: error) + } + + defer { + unarchiver.finishDecoding() + } + + guard let decodableObject = unarchiver.decodeDecodable(Value.self, forKey: key.rawValue) else { + throw StorageError.valueNotFound + } + + return decodableObject + } +} diff --git a/TIKeychainUtils/Sources/CodableKeyValueStorage/Encoders/ArchiverKeyValueEncoder.swift b/TIKeychainUtils/Sources/CodableKeyValueStorage/Encoders/ArchiverKeyValueEncoder.swift new file mode 100644 index 00000000..80a8a954 --- /dev/null +++ b/TIKeychainUtils/Sources/CodableKeyValueStorage/Encoders/ArchiverKeyValueEncoder.swift @@ -0,0 +1,41 @@ +// +// Copyright (c) 2020 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +open class ArchiverKeyValueEncoder: CodableKeyValueEncoder { + public init() {} + + open func encodeEncodable(value: Value, for key: StorageKey) throws -> Data { + let archiver = NSKeyedArchiver(requiringSecureCoding: true) + + do { + try archiver.encodeEncodable(value, forKey: key.rawValue) + } catch { + throw StorageError.unableToEncode(underlyingError: error) + } + + archiver.finishEncoding() + + return archiver.encodedData + } +} diff --git a/TIKeychainUtils/Sources/CodableKeyValueStorage/Encoders/CodableKeyValueEncoder.swift b/TIKeychainUtils/Sources/CodableKeyValueStorage/Encoders/CodableKeyValueEncoder.swift new file mode 100644 index 00000000..30bf3fb0 --- /dev/null +++ b/TIKeychainUtils/Sources/CodableKeyValueStorage/Encoders/CodableKeyValueEncoder.swift @@ -0,0 +1,27 @@ +// +// Copyright (c) 2020 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public protocol CodableKeyValueEncoder { + func encodeEncodable(value: Value, for key: StorageKey) throws -> Data +} diff --git a/TIKeychainUtils/Sources/CodableKeyValueStorage/Encoders/JSONKeyValueEncoder.swift b/TIKeychainUtils/Sources/CodableKeyValueStorage/Encoders/JSONKeyValueEncoder.swift new file mode 100644 index 00000000..0aacb1ae --- /dev/null +++ b/TIKeychainUtils/Sources/CodableKeyValueStorage/Encoders/JSONKeyValueEncoder.swift @@ -0,0 +1,39 @@ +// +// Copyright (c) 2020 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +open class JSONKeyValueEncoder: CodableKeyValueEncoder { + private let jsonEncoder: JSONEncoder + + public init(jsonEncoder: JSONEncoder = JSONEncoder()) { + self.jsonEncoder = jsonEncoder + } + + open func encodeEncodable(value: Value, for key: StorageKey) throws -> Data { + do { + return try jsonEncoder.encode(value) + } catch { + throw StorageError.unableToEncode(underlyingError: error) + } + } +} diff --git a/TIKeychainUtils/Sources/CodableKeyValueStorage/StorageError.swift b/TIKeychainUtils/Sources/CodableKeyValueStorage/StorageError.swift new file mode 100644 index 00000000..0e64dc35 --- /dev/null +++ b/TIKeychainUtils/Sources/CodableKeyValueStorage/StorageError.swift @@ -0,0 +1,29 @@ +// +// Copyright (c) 2020 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +enum StorageError: Error { + case valueNotFound + case unableToExtractData(underlyingError: Error) + case unableToDecode(underlyingError: Error) + case unableToEncode(underlyingError: Error) + case unableToWriteData(underlyingError: Error) +} diff --git a/TIKeychainUtils/Sources/CodableKeyValueStorage/StorageKey.swift b/TIKeychainUtils/Sources/CodableKeyValueStorage/StorageKey.swift new file mode 100644 index 00000000..8c9cc837 --- /dev/null +++ b/TIKeychainUtils/Sources/CodableKeyValueStorage/StorageKey.swift @@ -0,0 +1,29 @@ +// +// Copyright (c) 2020 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public struct StorageKey: RawRepresentable { + public let rawValue: String + + public init(rawValue: String) { + self.rawValue = rawValue + } +} diff --git a/TIKeychainUtils/Sources/CodableKeyValueStorage/UserDefaults+CodableKeyValueStorage.swift b/TIKeychainUtils/Sources/CodableKeyValueStorage/UserDefaults+CodableKeyValueStorage.swift new file mode 100644 index 00000000..e3a3ac64 --- /dev/null +++ b/TIKeychainUtils/Sources/CodableKeyValueStorage/UserDefaults+CodableKeyValueStorage.swift @@ -0,0 +1,52 @@ +// +// Copyright (c) 2020 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +extension UserDefaults: CodableKeyValueStorage { + public func codableObject(forKey key: StorageKey, + decoder: CodableKeyValueDecoder) throws -> Value { + + guard let storedData = data(forKey: key.rawValue) else { + throw StorageError.valueNotFound + } + + return try decoder.decodeDecodable(from: storedData, for: key) + } + + public func set(encodableObject: Value, + forKey key: StorageKey, + encoder: CodableKeyValueEncoder) throws { + + let encodedData = try encoder.encodeEncodable(value: encodableObject, for: key) + + set(encodedData, forKey: key.rawValue) + } + + public func removeCodableValue(forKey key: StorageKey) throws { + guard data(forKey: key.rawValue) != nil else { + throw StorageError.valueNotFound + } + + removeObject(forKey: key.rawValue) + } +} diff --git a/TIKeychainUtils/Sources/CodableKeyValueStorage/UserDefaultsCodableBackingStore.swift b/TIKeychainUtils/Sources/CodableKeyValueStorage/UserDefaultsCodableBackingStore.swift new file mode 100644 index 00000000..cfd9fbac --- /dev/null +++ b/TIKeychainUtils/Sources/CodableKeyValueStorage/UserDefaultsCodableBackingStore.swift @@ -0,0 +1,25 @@ +// +// Copyright (c) 2020 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public typealias UserDefaultsCodableBackingStore = CodableKeyValueBackingStore diff --git a/TIKeychainUtils/Sources/KeychainAccess/Keychain+CodableKeyValueStorage.swift b/TIKeychainUtils/Sources/KeychainAccess/Keychain+CodableKeyValueStorage.swift new file mode 100644 index 00000000..2213acac --- /dev/null +++ b/TIKeychainUtils/Sources/KeychainAccess/Keychain+CodableKeyValueStorage.swift @@ -0,0 +1,43 @@ +import Foundation +import KeychainAccess + +extension Keychain: CodableKeyValueStorage { + public func codableObject(forKey key: StorageKey, + decoder: CodableKeyValueDecoder) throws -> Value { + + let unwrappedStoredData: Data? + + do { + unwrappedStoredData = try getData(key.rawValue) + } catch { + throw StorageError.unableToExtractData(underlyingError: error) + } + + guard let storedData = unwrappedStoredData else { + throw StorageError.valueNotFound + } + + return try decoder.decodeDecodable(from: storedData, for: key) + } + + public func set(encodableObject: Value, + forKey key: StorageKey, + encoder: CodableKeyValueEncoder) throws { + + let objectData = try encoder.encodeEncodable(value: encodableObject, for: key) + + do { + try set(objectData, key: key.rawValue) + } catch { + throw StorageError.unableToWriteData(underlyingError: error) + } + } + + public func removeCodableValue(forKey key: StorageKey) throws { + do { + try remove(key.rawValue) + } catch { + throw StorageError.unableToWriteData(underlyingError: error) + } + } +} diff --git a/TIKeychainUtils/Sources/KeychainAccess/Keychain+Factory.swift b/TIKeychainUtils/Sources/KeychainAccess/Keychain+Factory.swift new file mode 100644 index 00000000..d22d80cd --- /dev/null +++ b/TIKeychainUtils/Sources/KeychainAccess/Keychain+Factory.swift @@ -0,0 +1,18 @@ +import Foundation +import KeychainAccess + +extension Bundle { + var mirBundleId: String { + Bundle.main.bundleIdentifier ?? .empty + } +} + +extension Keychain { + static var mirKeychain: Keychain { + .init(service: Bundle.main.mirBundleId) + } +} + +private extension String { + static let empty = "" +} diff --git a/TIKeychainUtils/Sources/KeychainAccess/KeychainCodableBackingStore.swift b/TIKeychainUtils/Sources/KeychainAccess/KeychainCodableBackingStore.swift new file mode 100644 index 00000000..2be86a80 --- /dev/null +++ b/TIKeychainUtils/Sources/KeychainAccess/KeychainCodableBackingStore.swift @@ -0,0 +1,4 @@ +import KeychainAccess +import TIFoundationUtils + +typealias KeychainCodableBackingStore = CodableKeyValueBackingStore diff --git a/TIKeychainUtils/Sources/UserDefaultsBackingStore/UserDefaultsBackingStore.swift b/TIKeychainUtils/Sources/UserDefaultsBackingStore/UserDefaultsBackingStore.swift new file mode 100644 index 00000000..487f9686 --- /dev/null +++ b/TIKeychainUtils/Sources/UserDefaultsBackingStore/UserDefaultsBackingStore.swift @@ -0,0 +1,69 @@ +// +// Copyright (c) 2021 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import TISwiftUtils +import Foundation + +public typealias UserDefaultsBackingStore = BackingStore + +public extension BackingStore where Store: UserDefaults { + typealias GetValueByRawKeyClosure = (Store, String) -> StoreContent + typealias SetValueByRawKeyClosure = (Store, StoreContent, String) -> Void + + init(key: StorageKey, + userDefaultsStorage: Store, + getClosure: @escaping GetValueByRawKeyClosure, + setClosure: @escaping SetValueByRawKeyClosure) + where StoreContent == Value? { + + self.init(store: userDefaultsStorage, + getClosure: { + guard $0.object(forKey: key.rawValue) != nil else { + return nil + } + + return getClosure($0, key.rawValue) + }, + setClosure: { + setClosure($0, $1, key.rawValue) + }) + } + + init(wrappedValue: StoreContent, + key: StorageKey, + userDefaultsStorage: Store, + getClosure: @escaping GetValueByRawKeyClosure, + setClosure: @escaping SetValueByRawKeyClosure) { + + self.init(store: userDefaultsStorage, + getClosure: { + guard $0.object(forKey: key.rawValue) != nil else { + return wrappedValue + } + + return getClosure($0, key.rawValue) + }, + setClosure: { + setClosure($0, $1, key.rawValue) + }) + } +} diff --git a/TIKeychainUtils/TIKeychainUtils.podspec b/TIKeychainUtils/TIKeychainUtils.podspec new file mode 100644 index 00000000..9cf05d9a --- /dev/null +++ b/TIKeychainUtils/TIKeychainUtils.podspec @@ -0,0 +1,17 @@ +Pod::Spec.new do |s| + s.name = 'TIKeychainUtils' + s.version = '1.1.2' + s.summary = 'Set of helpers for Keychain classes.' + s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru' } + s.source = { :git => 'https://github.com/TouchInstinct/LeadKit.git', :tag => s.version.to_s } + + s.ios.deployment_target = '11.0' + s.swift_versions = ['5.3'] + + s.source_files = s.name + '/Sources/**/*' + + s.dependency 'TISwiftUtils', s.version.to_s + s.framework = 'Foundation' +end diff --git a/TISwiftUtils/TISwiftUtils.podspec b/TISwiftUtils/TISwiftUtils.podspec index 1cd832d3..b5a911ff 100644 --- a/TISwiftUtils/TISwiftUtils.podspec +++ b/TISwiftUtils/TISwiftUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TISwiftUtils' - s.version = '1.1.1' + s.version = '1.1.2' s.summary = 'Bunch of useful helpers for Swift development.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TITableKitUtils/TITableKitUtils.podspec b/TITableKitUtils/TITableKitUtils.podspec index d8bd1cf1..eaffd093 100644 --- a/TITableKitUtils/TITableKitUtils.podspec +++ b/TITableKitUtils/TITableKitUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TITableKitUtils' - s.version = '1.1.1' + s.version = '1.1.2' s.summary = 'Set of helpers for TableKit classes.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TITransitions/TITransitions.podspec b/TITransitions/TITransitions.podspec index eaee010e..bba696f5 100644 --- a/TITransitions/TITransitions.podspec +++ b/TITransitions/TITransitions.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TITransitions' - s.version = '1.1.1' + s.version = '1.1.2' s.summary = 'Set of custom transitions to present controller. ' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIUIElements/TIUIElements.podspec b/TIUIElements/TIUIElements.podspec index 9c7f3e18..832ef76c 100644 --- a/TIUIElements/TIUIElements.podspec +++ b/TIUIElements/TIUIElements.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIUIElements' - s.version = '1.1.1' + s.version = '1.1.2' s.summary = 'Bunch of useful protocols and views.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIUIKitCore/TIUIKitCore.podspec b/TIUIKitCore/TIUIKitCore.podspec index 68d308f7..7387c9fb 100644 --- a/TIUIKitCore/TIUIKitCore.podspec +++ b/TIUIKitCore/TIUIKitCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIUIKitCore' - s.version = '1.1.1' + s.version = '1.1.2' s.summary = 'Core UI elements: protocols, views and helpers.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' }