From 0ca65eaa7157fdf7dca4eac052f849cd6c3c9fd6 Mon Sep 17 00:00:00 2001 From: Fabrizio Demaria Date: Tue, 30 Apr 2024 17:16:47 +0200 Subject: [PATCH] feat: Add visitorID context (#106) * feat: Add visitorID context * refactor: visitorId in UserDefaults * Update VisitorUtil key --- .../Cache => Common}/DefaultStorage.swift | 20 +------- .../Cache => Common}/Storage.swift | 0 Sources/Confidence/Confidence.swift | 19 ++++++-- Sources/Confidence/VisitorUtil.swift | 18 +++++++ .../ConfidenceFeatureProvider.swift | 14 ++++++ .../ConfidenceIntegrationTest.swift | 1 + .../DefaultStorageTest.swift | 1 + .../Helpers/StorageMock.swift | 1 + .../PersistentProviderCacheTest.swift | 1 + Tests/ConfidenceTests/ConfidenceTests.swift | 48 +++++++++++++++++++ 10 files changed, 101 insertions(+), 22 deletions(-) rename Sources/{ConfidenceProvider/Cache => Common}/DefaultStorage.swift (85%) rename Sources/{ConfidenceProvider/Cache => Common}/Storage.swift (100%) create mode 100644 Sources/Confidence/VisitorUtil.swift diff --git a/Sources/ConfidenceProvider/Cache/DefaultStorage.swift b/Sources/Common/DefaultStorage.swift similarity index 85% rename from Sources/ConfidenceProvider/Cache/DefaultStorage.swift rename to Sources/Common/DefaultStorage.swift index 800678c6..20a4406b 100644 --- a/Sources/ConfidenceProvider/Cache/DefaultStorage.swift +++ b/Sources/Common/DefaultStorage.swift @@ -1,13 +1,11 @@ import Foundation -import Common -import Confidence public class DefaultStorage: Storage { private let storageQueue = DispatchQueue(label: "com.confidence.storage") private let resolverCacheBundleId = "com.confidence.cache" private let filePath: String - init(filePath: String) { + public init(filePath: String) { self.filePath = filePath } @@ -83,7 +81,7 @@ public class DefaultStorage: Storage { } } - func getConfigUrl() throws -> URL { + public func getConfigUrl() throws -> URL { guard let applicationSupportUrl: URL = FileManager.default.urls( for: .applicationSupportDirectory, @@ -101,17 +99,3 @@ public class DefaultStorage: Storage { components: resolverCacheBundleId, "\(bundleIdentifier)", filePath) } } - -extension DefaultStorage { - public static func resolverFlagsCache() -> DefaultStorage { - DefaultStorage(filePath: "resolver.flags.cache") - } - - public static func resolverApplyCache() -> DefaultStorage { - DefaultStorage(filePath: "resolver.apply.cache") - } - - public static func applierFlagsCache() -> DefaultStorage { - DefaultStorage(filePath: "applier.flags.cache") - } -} diff --git a/Sources/ConfidenceProvider/Cache/Storage.swift b/Sources/Common/Storage.swift similarity index 100% rename from Sources/ConfidenceProvider/Cache/Storage.swift rename to Sources/Common/Storage.swift diff --git a/Sources/Confidence/Confidence.swift b/Sources/Confidence/Confidence.swift index a36e034a..2b66b028 100644 --- a/Sources/Confidence/Confidence.swift +++ b/Sources/Confidence/Confidence.swift @@ -2,12 +2,12 @@ import Foundation import Combine public class Confidence: ConfidenceEventSender { - private let parent: ConfidenceContextProvider? public let clientSecret: String public var timeout: TimeInterval public var region: ConfidenceRegion - let eventSenderEngine: EventSenderEngine public var initializationStrategy: InitializationStrategy + private let parent: ConfidenceContextProvider? + private let eventSenderEngine: EventSenderEngine private let contextFlow = CurrentValueSubject([:]) private var removedContextKeys: Set = Set() private let confidenceQueue = DispatchQueue(label: "com.confidence.queue") @@ -19,7 +19,8 @@ public class Confidence: ConfidenceEventSender { eventSenderEngine: EventSenderEngine, initializationStrategy: InitializationStrategy, context: ConfidenceStruct = [:], - parent: ConfidenceEventSender? = nil + parent: ConfidenceEventSender? = nil, + visitorId: String? = nil ) { self.eventSenderEngine = eventSenderEngine self.clientSecret = clientSecret @@ -28,6 +29,9 @@ public class Confidence: ConfidenceEventSender { self.initializationStrategy = initializationStrategy self.contextFlow.value = context self.parent = parent + if let visitorId { + putContext(context: ["visitorId": ConfidenceValue.init(string: visitorId)]) + } } public func contextChanges() -> AnyPublisher { @@ -120,6 +124,7 @@ extension Confidence { var region: ConfidenceRegion = .global var initializationStrategy: InitializationStrategy = .fetchAndActivate let eventStorage: EventStorage + var visitorId: String? public init(clientSecret: String) { self.clientSecret = clientSecret @@ -146,6 +151,11 @@ extension Confidence { return self } + public func withVisitorId() -> Builder { + self.visitorId = VisitorUtil().getId() + return self + } + public func build() -> Confidence { let uploader = RemoteConfidenceClient( options: ConfidenceClientOptions( @@ -168,7 +178,8 @@ extension Confidence { eventSenderEngine: eventSenderEngine, initializationStrategy: initializationStrategy, context: [:], - parent: nil + parent: nil, + visitorId: visitorId ) } } diff --git a/Sources/Confidence/VisitorUtil.swift b/Sources/Confidence/VisitorUtil.swift new file mode 100644 index 00000000..b376a113 --- /dev/null +++ b/Sources/Confidence/VisitorUtil.swift @@ -0,0 +1,18 @@ +import Foundation +import Common + +class VisitorUtil { + let defaults = UserDefaults.standard + let userDefaultsKey = "confidence.visitor_id" + func getId() -> String { + let id = defaults.string(forKey: userDefaultsKey) ?? "" + if id.isEmpty { + let newId = UUID.init().uuidString + defaults.set(newId, forKey: userDefaultsKey) + defaults.synchronize() + return newId + } else { + return id + } + } +} diff --git a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift index 6c0cc187..fc1ea53e 100644 --- a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift +++ b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift @@ -676,5 +676,19 @@ extension ConfidenceFeatureProvider { } } } + +extension DefaultStorage { + public static func resolverFlagsCache() -> DefaultStorage { + DefaultStorage(filePath: "resolver.flags.cache") + } + + public static func resolverApplyCache() -> DefaultStorage { + DefaultStorage(filePath: "resolver.apply.cache") + } + + public static func applierFlagsCache() -> DefaultStorage { + DefaultStorage(filePath: "applier.flags.cache") + } +} // swiftlint:enable type_body_length // swiftlint:enable file_length diff --git a/Tests/ConfidenceProviderTests/ConfidenceIntegrationTest.swift b/Tests/ConfidenceProviderTests/ConfidenceIntegrationTest.swift index baf1c85b..8b46e4c6 100644 --- a/Tests/ConfidenceProviderTests/ConfidenceIntegrationTest.swift +++ b/Tests/ConfidenceProviderTests/ConfidenceIntegrationTest.swift @@ -1,4 +1,5 @@ import Foundation +import Common import OpenFeature import XCTest diff --git a/Tests/ConfidenceProviderTests/DefaultStorageTest.swift b/Tests/ConfidenceProviderTests/DefaultStorageTest.swift index 1ef9fdc0..8e37b69d 100644 --- a/Tests/ConfidenceProviderTests/DefaultStorageTest.swift +++ b/Tests/ConfidenceProviderTests/DefaultStorageTest.swift @@ -1,4 +1,5 @@ import Foundation +import Common import OpenFeature import XCTest diff --git a/Tests/ConfidenceProviderTests/Helpers/StorageMock.swift b/Tests/ConfidenceProviderTests/Helpers/StorageMock.swift index a6276bc1..961dfcf5 100644 --- a/Tests/ConfidenceProviderTests/Helpers/StorageMock.swift +++ b/Tests/ConfidenceProviderTests/Helpers/StorageMock.swift @@ -1,4 +1,5 @@ import Foundation +import Common import OpenFeature import XCTest diff --git a/Tests/ConfidenceProviderTests/PersistentProviderCacheTest.swift b/Tests/ConfidenceProviderTests/PersistentProviderCacheTest.swift index 1ca77bbe..ee43ddf7 100644 --- a/Tests/ConfidenceProviderTests/PersistentProviderCacheTest.swift +++ b/Tests/ConfidenceProviderTests/PersistentProviderCacheTest.swift @@ -1,4 +1,5 @@ import Foundation +import Common import OpenFeature import XCTest diff --git a/Tests/ConfidenceTests/ConfidenceTests.swift b/Tests/ConfidenceTests/ConfidenceTests.swift index cd0035d6..9281a3ce 100644 --- a/Tests/ConfidenceTests/ConfidenceTests.swift +++ b/Tests/ConfidenceTests/ConfidenceTests.swift @@ -197,4 +197,52 @@ final class ConfidenceTests: XCTestCase { ] XCTAssertEqual(confidenceChild.getContext(), expected) } + + func testVisitorId() { + let confidence = Confidence.init( + clientSecret: "", + timeout: TimeInterval(), + region: .europe, + eventSenderEngine: EventSenderEngineMock(), + initializationStrategy: .activateAndFetchAsync, + context: ["k1": ConfidenceValue(string: "v1")], + visitorId: "uuid" + ) + let expected = [ + "k1": ConfidenceValue(string: "v1"), + "visitorId": ConfidenceValue(string: "uuid") + ] + XCTAssertEqual(confidence.getContext(), expected) + } + + func testWithVisitorId() throws { + let userDefaults = UserDefaults.standard + userDefaults.removeObject(forKey: "confidence.visitor_id") + let confidence = Confidence.Builder(clientSecret: "") + .withVisitorId() + .build() + let visitorId = try XCTUnwrap(confidence.getContext()["visitorId"]?.asString()) + XCTAssertNotEqual(visitorId, "") + XCTAssertNotEqual(visitorId, "storage-error") + let newConfidence = Confidence.Builder(clientSecret: "") + .withVisitorId() + .build() + XCTAssertEqual(visitorId, try XCTUnwrap(newConfidence.getContext()["visitorId"]?.asString())) + userDefaults.removeObject(forKey: "confidence.visitor_id") + let veryNewConfidence = Confidence.Builder(clientSecret: "") + .withVisitorId() + .build() + let newVisitorId = try XCTUnwrap(veryNewConfidence.getContext()["visitorId"]?.asString()) + XCTAssertNotEqual(newVisitorId, "") + XCTAssertNotEqual(newVisitorId, "storage-error") + XCTAssertNotEqual(newVisitorId, visitorId) + } + + func testWithoutVisitorId() throws { + let userDefaults = UserDefaults.standard + userDefaults.removeObject(forKey: "confidence.visitor_id") + let confidence = Confidence.Builder(clientSecret: "") + .build() + XCTAssertNil(confidence.getContext()["visitorId"]) + } }