feat: add TILogging module and TINetworking error logging

This commit is contained in:
Ivan Smolin 2023-05-25 11:30:43 +03:00
parent c0189dc7ae
commit ecfb83bafa
22 changed files with 221 additions and 67 deletions

View File

@ -3,7 +3,9 @@
### 1.45.0
- **Added**: `SingleValueStorage` implementations + `AppInstallLifetimeSingleValueStorage` for automatically removing keychain items on app reinstall.
- **Added**: `TILogging` with error logging types
- **Update**: `DefaultRecoverableJsonNetworkService` supports iOS 12.
- **Update**: `DefaultFingerprintsProvider` now uses `SingleValueStorage`
### 1.44.0

View File

@ -68,7 +68,12 @@ let package = Package(
// MARK: - Utils
.target(name: "TISwiftUtils", path: "TISwiftUtils/Sources"),
.target(name: "TIFoundationUtils", dependencies: ["TISwiftUtils"], path: "TIFoundationUtils", exclude: ["TIFoundationUtils.app"]),
.target(name: "TIFoundationUtils",
dependencies: ["TISwiftUtils"],
path: "TIFoundationUtils",
exclude: ["TIFoundationUtils.app"],
plugins: [.plugin(name: "TISwiftLintPlugin")]),
.target(name: "TIKeychainUtils",
dependencies: ["TIFoundationUtils", "KeychainAccess"],
@ -79,11 +84,12 @@ let package = Package(
.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"),
.target(name: "TILogging", path: "TILogging/Sources", plugins: ["TISwiftLintPlugin"]),
// MARK: - Networking
.target(name: "TINetworking",
dependencies: ["TIFoundationUtils", "Alamofire"],
dependencies: ["TIFoundationUtils", "Alamofire", "TILogging"],
path: "TINetworking/Sources",
plugins: [.plugin(name: "TISwiftLintPlugin")]),

View File

@ -30,7 +30,7 @@ struct SwiftLintPlugin: BuildToolPlugin {
let swiftlintExecutablePath = try context.tool(named: "swiftlint").path
return [
.prebuildCommand(displayName: "SwiftLint linting...",
.prebuildCommand(displayName: "SwiftLint linting \(target.name)...",
executable: swiftlintScriptPath,
arguments: [
swiftlintExecutablePath,

View File

@ -55,7 +55,7 @@ open class DefaultEncryptedTokenKeyStorage: SingleValueAuthKeychainStorage<Data>
}
}
let setValueClosure: SetValueClosure = { keychain, value, storageKey in
let storeValueClosure: StoreValueClosure = { keychain, value, storageKey in
do {
return .success(try keychain.set(value, key: storageKey.rawValue))
} catch {
@ -67,6 +67,6 @@ open class DefaultEncryptedTokenKeyStorage: SingleValueAuthKeychainStorage<Data>
settingsStorage: settingsStorage,
storageKey: encryptedTokenKeyStorageKey,
getValueClosure: getValueClosure,
setValueClosure: setValueClosure)
storeValueClosure: storeValueClosure)
}
}

View File

@ -51,7 +51,7 @@ open class DefaultEncryptedTokenStorage: SingleValueAuthKeychainStorage<StringEn
}
}
let setValueClosure: SetValueClosure = { keychain, value, storageKey in
let storeValueClosure: StoreValueClosure = { keychain, value, storageKey in
do {
return .success(try keychain.set(value.asStorableData(), key: storageKey.rawValue))
} catch {
@ -63,6 +63,6 @@ open class DefaultEncryptedTokenStorage: SingleValueAuthKeychainStorage<StringEn
settingsStorage: settingsStorage,
storageKey: encryptedTokenStorageKey,
getValueClosure: getValueClosure,
setValueClosure: setValueClosure)
storeValueClosure: storeValueClosure)
}
}

View File

@ -38,14 +38,14 @@ open class SingleValueAuthKeychainStorage<ValueType>: BaseSingleValueKeychainSto
settingsStorage: AuthSettingsStorage = DefaultAuthSettingsStorage(),
storageKey: StorageKey<ValueType>,
getValueClosure: @escaping GetValueClosure,
setValueClosure: @escaping SetValueClosure) {
storeValueClosure: @escaping StoreValueClosure) {
self.settingsStorage = settingsStorage
super.init(keychain: keychain,
storageKey: storageKey,
getValueClosure: getValueClosure,
setValueClosure: setValueClosure)
storeValueClosure: storeValueClosure)
}
// MARK: - SingleValueStorage

View File

@ -0,0 +1,64 @@
//
// Copyright (c) 2023 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 AnySingleValueStorage<ValueType, ErrorType: Error>: SingleValueStorage {
public typealias HasValueClosure = () -> Bool
public typealias GetValueClosure = () -> Result<ValueType, ErrorType>
public typealias StoreValueClosure = (ValueType) -> Result<Void, ErrorType>
public typealias DeleteValueClosure = () -> Result<Void, ErrorType>
private let hasValueClosure: HasValueClosure
private let deleteValueClosure: DeleteValueClosure
private let getValueClosure: GetValueClosure
private let storeValueClosure: StoreValueClosure
public init<Storage: SingleValueStorage>(storage: Storage)
where Storage.ValueType == ValueType, Storage.ErrorType == ErrorType {
self.hasValueClosure = storage.hasStoredValue
self.deleteValueClosure = storage.deleteValue
self.getValueClosure = storage.getValue
self.storeValueClosure = storage.store
}
public func hasStoredValue() -> Bool {
hasValueClosure()
}
public func store(value: ValueType) -> Result<Void, ErrorType> {
storeValueClosure(value)
}
public func getValue() -> Result<ValueType, ErrorType> {
getValueClosure()
}
public func deleteValue() -> Result<Void, ErrorType> {
deleteValueClosure()
}
}
public extension SingleValueStorage {
func eraseToAnySingleValueStorate() -> AnySingleValueStorage<ValueType, ErrorType> {
AnySingleValueStorage(storage: self)
}
}

View File

@ -20,8 +20,6 @@
// THE SOFTWARE.
//
import TIFoundationUtils
open class AppInstallLifetimeSingleValueStorage<Storage: SingleValueStorage>: SingleValueStorage
where Storage.ErrorType == StorageError {

View File

@ -26,13 +26,13 @@ open class BaseSingleValueDefaultsStorage<ValueType>: BaseSingleValueStorage<Val
public init(defaults: UserDefaults,
storageKey: StorageKey<ValueType>,
getValueClosure: @escaping GetValueClosure,
setValueClosure: @escaping SetValueClosure) {
storeValueClosure: @escaping StoreValueClosure) {
super.init(storage: defaults,
storageKey: storageKey,
hasValueClosure: { .success($0.object(forKey: $1.rawValue) != nil) },
deleteValueClosure: { .success($0.removeObject(forKey: $1.rawValue)) },
getValueClosure: getValueClosure,
setValueClosure: setValueClosure)
storeValueClosure: storeValueClosure)
}
}

View File

@ -23,7 +23,7 @@
open class BaseSingleValueStorage<ValueType, StorageType>: SingleValueStorage {
public typealias HasValueClosure = (StorageType, StorageKey<ValueType>) -> Result<Bool, StorageError>
public typealias GetValueClosure = (StorageType, StorageKey<ValueType>) -> Result<ValueType, StorageError>
public typealias SetValueClosure = (StorageType, ValueType, StorageKey<ValueType>) -> Result<Void, StorageError>
public typealias StoreValueClosure = (StorageType, ValueType, StorageKey<ValueType>) -> Result<Void, StorageError>
public typealias DeleteValueClosure = (StorageType, StorageKey<ValueType>) -> Result<Void, StorageError>
public let storage: StorageType
@ -31,21 +31,21 @@ open class BaseSingleValueStorage<ValueType, StorageType>: SingleValueStorage {
public let hasValueClosure: HasValueClosure
public let deleteValueClosure: DeleteValueClosure
public let getValueClosure: GetValueClosure
public let setValueClosure: SetValueClosure
public let storeValueClosure: StoreValueClosure
public init(storage: StorageType,
storageKey: StorageKey<ValueType>,
hasValueClosure: @escaping HasValueClosure,
deleteValueClosure: @escaping DeleteValueClosure,
getValueClosure: @escaping GetValueClosure,
setValueClosure: @escaping SetValueClosure) {
storeValueClosure: @escaping StoreValueClosure) {
self.storage = storage
self.storageKey = storageKey
self.hasValueClosure = hasValueClosure
self.deleteValueClosure = deleteValueClosure
self.getValueClosure = getValueClosure
self.setValueClosure = setValueClosure
self.storeValueClosure = storeValueClosure
}
// MARK: - SingleValueStorage
@ -55,14 +55,14 @@ open class BaseSingleValueStorage<ValueType, StorageType>: SingleValueStorage {
}
open func store(value: ValueType) -> Result<Void, StorageError> {
setValueClosure(storage, value, storageKey)
storeValueClosure(storage, value, storageKey)
}
open func getValue() -> Result<ValueType, StorageError> {
getValueClosure(storage, storageKey)
}
public func deleteValue() -> Result<Void, StorageError> {
open func deleteValue() -> Result<Void, StorageError> {
deleteValueClosure(storage, storageKey)
}
}

View File

@ -35,6 +35,6 @@ public final class StringValueDefaultsStorage: BaseSingleValueDefaultsStorage<St
super.init(defaults: defaults,
storageKey: storageKey,
getValueClosure: getValueClosure,
setValueClosure: { .success($0.set($1, forKey: $2.rawValue)) })
storeValueClosure: { .success($0.set($1, forKey: $2.rawValue)) })
}
}

View File

@ -27,7 +27,7 @@ open class BaseSingleValueKeychainStorage<ValueType>: BaseSingleValueStorage<Val
public init(keychain: Keychain,
storageKey: StorageKey<ValueType>,
getValueClosure: @escaping GetValueClosure,
setValueClosure: @escaping SetValueClosure) {
storeValueClosure: @escaping StoreValueClosure) {
let hasValueClosure: HasValueClosure = { keychain, storageKey in
Result { try keychain.contains(storageKey.rawValue) }
@ -44,6 +44,6 @@ open class BaseSingleValueKeychainStorage<ValueType>: BaseSingleValueStorage<Val
hasValueClosure: hasValueClosure,
deleteValueClosure: deleteValueClosure,
getValueClosure: getValueClosure,
setValueClosure: setValueClosure)
storeValueClosure: storeValueClosure)
}
}

View File

@ -37,7 +37,7 @@ public final class StringValueKeychainStorage: BaseSingleValueKeychainStorage<St
}
}
let setValueClosure: SetValueClosure = { keychain, value, storageKey in
let storeValueClosure: StoreValueClosure = { keychain, value, storageKey in
do {
return .success(try keychain.set(value, key: storageKey.rawValue))
} catch {
@ -48,6 +48,6 @@ public final class StringValueKeychainStorage: BaseSingleValueKeychainStorage<St
super.init(keychain: keychain,
storageKey: storageKey,
getValueClosure: getValueClosure,
setValueClosure: setValueClosure)
storeValueClosure: storeValueClosure)
}
}

View File

@ -0,0 +1,44 @@
//
// Copyright (c) 2023 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 os
open class DefaultOSLogErrorLogger: ErrorLogger {
public var log: OSLog
public init(log: OSLog) {
self.log = log
}
public convenience init(subsystem: String, category: String) {
self.init(log: OSLog(subsystem: subsystem, category: category))
}
open func log(error: Error, file: StaticString, line: Int) {
os_log("%{public}s:%{public}d %{public}s",
log: log,
type: .error,
file.debugDescription,
line,
error.localizedDescription)
}
}

View File

@ -20,6 +20,8 @@
// THE SOFTWARE.
//
public protocol FingerprintsSecureStorage {
var knownPins: [String: Set<String>] { get set }
import Foundation
public protocol ErrorLogger {
func log(error: Error, file: StaticString, line: Int)
}

View File

@ -0,0 +1,15 @@
Pod::Spec.new do |s|
s.name = 'TILogging'
s.version = '1.45.0'
s.summary = 'Logging for TI libraries.'
s.homepage = 'https://git.svc.touchin.ru/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://git.svc.touchin.ru/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
s.ios.deployment_target = '11.0'
s.swift_versions = ['5.7']
s.source_files = s.name + '/Sources/**/*'
end

View File

@ -45,6 +45,6 @@ public struct CALayerDrawingOperation: DrawingOperation {
context.concatenate(offsetTransform)
layer.render(in: context)
offsetTransform.concatenating(offsetTransform.inverted())
context.concatenate(offsetTransform.inverted())
}
}

View File

@ -20,33 +20,64 @@
// THE SOFTWARE.
//
import TIFoundationUtils
import TILogging
open class DefaultFingerprintsProvider: FingerprintsProvider {
public var secureStorage: FingerprintsSecureStorage
public var settingsStorage: FingerprintsSettingsStorage
public typealias FingerprintsMapping = [String: Set<String>]
public init(secureStorage: FingerprintsSecureStorage,
settingsStorage: FingerprintsSettingsStorage,
bundledFingerprints: [String: Set<String>]) {
public var secureStorage: AnySingleValueStorage<FingerprintsMapping, StorageError>
public var bundledFingerprints: FingerprintsMapping
public var errorLogger: ErrorLogger
self.secureStorage = secureStorage
self.settingsStorage = settingsStorage
public init<Storage: SingleValueStorage>(secureStorage: Storage,
bundledFingerprints: FingerprintsMapping,
errorLogger: ErrorLogger = TINetworkingLogger(category: "Fingerprints"))
where Storage.ValueType == FingerprintsMapping, Storage.ErrorType == StorageError {
if settingsStorage.shouldResetFingerprints {
self.secureStorage.knownPins = bundledFingerprints
self.settingsStorage.shouldResetFingerprints = false
} else {
self.secureStorage.knownPins.merge(bundledFingerprints) { storedFingerprints, bundleFingerprints in
storedFingerprints.union(bundleFingerprints)
self.secureStorage = secureStorage.eraseToAnySingleValueStorate()
self.bundledFingerprints = bundledFingerprints
self.errorLogger = errorLogger
let fingerprintsUpdateResult = secureStorage
.getValue()
.map {
$0.merging(bundledFingerprints) { storedFingerprints, bundleFingerprints in
storedFingerprints.union(bundleFingerprints)
}
}
.flatMap {
secureStorage.store(value: $0)
}
if case let .failure(storageError) = fingerprintsUpdateResult {
errorLogger.log(error: storageError, file: #file, line: #line)
}
}
public func fingerprints(forHost host: String) -> Set<String> {
secureStorage.knownPins[host] ?? []
open func fingerprints(forHost host: String) -> Set<String> {
(try? secureStorage
.getValue()
.flatMapError { _ -> Result<FingerprintsMapping, StorageError> in
.success(bundledFingerprints)
}
.get())?[host] ?? []
}
public func add(fingerprints: [String], forHost host: String) {
let pinsForHost = (secureStorage.knownPins[host] ?? []).union(fingerprints)
secureStorage.knownPins.updateValue(pinsForHost, forKey: host)
open func add(fingerprints: Set<String>, forHost host: String) {
let fingerprintsUpdateResult = secureStorage
.getValue()
.map {
$0.merging([host: fingerprints]) { storedFingerprints, addedFingerprints in
storedFingerprints.union(addedFingerprints)
}
}
.flatMap {
secureStorage.store(value: $0)
}
if case let .failure(storageError) = fingerprintsUpdateResult {
errorLogger.log(error: storageError, file: #file, line: #line)
}
}
}

View File

@ -23,29 +23,16 @@
import TIFoundationUtils
import Foundation
open class DefaultFingerprintsSettingsStorage: FingerprintsSettingsStorage {
open class FingerprintsReinstallChecker: AppReinstallChecker {
public enum Defaults {
public static var shouldResetFingerprintsKey: StorageKey<Bool> {
.init(rawValue: "shouldResetFingerprints")
}
}
private let reinstallChecker: AppReinstallChecker
public override init(defaultsStorage: UserDefaults = .standard,
storageKey: StorageKey<Bool> = Defaults.shouldResetFingerprintsKey) {
// MARK: - PinCodeSettingsStorage
open var shouldResetFingerprints: Bool {
get {
reinstallChecker.isAppFirstRun
}
set {
reinstallChecker.isAppFirstRun = newValue
}
}
public init(defaultsStorage: UserDefaults = .standard,
storageKey: StorageKey<Bool> = Defaults.shouldResetFingerprintsKey) {
self.reinstallChecker = AppReinstallChecker(defaultsStorage: defaultsStorage, storageKey: storageKey)
super.init(defaultsStorage: defaultsStorage, storageKey: storageKey)
}
}

View File

@ -22,5 +22,5 @@
public protocol FingerprintsProvider {
func fingerprints(forHost host: String) -> Set<String>
func add(fingerprints: [String], forHost host: String)
func add(fingerprints: Set<String>, forHost host: String)
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2022 Touch Instinct
// Copyright (c) 2023 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
@ -20,7 +20,11 @@
// THE SOFTWARE.
//
public protocol FingerprintsSettingsStorage {
/// Should be true by default (on app first run)
var shouldResetFingerprints: Bool { get set }
import TILogging
import os
public final class TINetworkingLogger: DefaultOSLogErrorLogger {
public init(category: String) {
super.init(log: OSLog(subsystem: "TINetworking", category: category))
}
}

View File

@ -1,3 +1,4 @@
TILogging
TISwiftUtils
TIPagination
TIFoundationUtils