Skip to content

Commit

Permalink
[FSSDK-10761] feat: make vuid as opt-in (#556)
Browse files Browse the repository at this point in the history
VUID optln added
  • Loading branch information
muzahidul-opti authored Nov 20, 2024
1 parent 3635373 commit 62ae49b
Show file tree
Hide file tree
Showing 16 changed files with 310 additions and 219 deletions.
78 changes: 39 additions & 39 deletions OptimizelySwiftSDK.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions Sources/ODP/OdpEventManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ open class OdpEventManager {

// MARK: - events

func registerVUID(vuid: String) {
func sendInitializedEvent(vuid: String) {
sendEvent(type: Constants.ODP.eventType,
action: "client_initialized",
identifiers: [
Expand All @@ -58,8 +58,11 @@ open class OdpEventManager {
data: [:])
}

func identifyUser(vuid: String, userId: String?) {
var identifiers = [Constants.ODP.keyForVuid: vuid]
func identifyUser(vuid: String?, userId: String?) {
var identifiers = [String: String]()
if let _vuid = vuid {
identifiers[Constants.ODP.keyForVuid] = _vuid
}
if let userId = userId {
identifiers[Constants.ODP.keyForUserId] = userId
}
Expand Down
32 changes: 11 additions & 21 deletions Sources/ODP/OdpManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,13 @@ import Foundation

public class OdpManager {
var enabled: Bool
var vuidManager: OdpVuidManager

var odpConfig: OdpConfig!
var segmentManager: OdpSegmentManager!
var eventManager: OdpEventManager!

let logger = OPTLoggerFactory.getLogger()

var vuid: String {
return vuidManager.vuid
}
var vuid: String?

/// OdpManager init
/// - Parameters:
Expand All @@ -42,6 +38,7 @@ public class OdpManager {
/// - eventManager: ODPEventManager
public init(sdkKey: String,
disable: Bool,
vuid: String? = nil,
cacheSize: Int,
cacheTimeoutInSecs: Int,
timeoutForSegmentFetchInSecs: Int? = nil,
Expand All @@ -50,8 +47,7 @@ public class OdpManager {
eventManager: OdpEventManager? = nil) {

self.enabled = !disable
self.vuidManager = OdpVuidManager.shared

self.vuid = vuid
guard enabled else {
logger.i(.odpNotEnabled)
return
Expand All @@ -65,8 +61,6 @@ public class OdpManager {
self.odpConfig = OdpConfig()
self.segmentManager.odpConfig = odpConfig
self.eventManager.odpConfig = odpConfig

self.eventManager.registerVUID(vuid: self.vuidManager.vuid)
}

func fetchQualifiedSegments(userId: String,
Expand All @@ -77,7 +71,7 @@ public class OdpManager {
return
}

let userKey = OdpVuidManager.isVuid(userId) ? Constants.ODP.keyForVuid : Constants.ODP.keyForUserId
let userKey = VuidManager.isVuid(userId) ? Constants.ODP.keyForVuid : Constants.ODP.keyForUserId
let userValue = userId

segmentManager.fetchQualifiedSegments(userKey: userKey,
Expand All @@ -97,15 +91,13 @@ public class OdpManager {
return
}

var vuid = vuidManager.vuid
var fsUserId: String? = userId
if OdpVuidManager.isVuid(userId) {
if VuidManager.isVuid(userId) {
// overwrite if userId is vuid (when userContext is created with vuid)
vuid = userId
fsUserId = nil
eventManager.identifyUser(vuid: userId, userId: nil)
} else {
eventManager.identifyUser(vuid: self.vuid, userId: userId)
}

eventManager.identifyUser(vuid: vuid, userId: fsUserId)
}

/// Send an event to the ODP server.
Expand All @@ -125,15 +117,13 @@ public class OdpManager {

let typeUpdated = (type ?? "").isEmpty ? Constants.ODP.eventType : type!

// add vuid to all events by default

var identifiersUpdated = identifiers
if identifiers[Constants.ODP.keyForVuid] == nil {
identifiersUpdated[Constants.ODP.keyForVuid] = vuidManager.vuid

if identifiers[Constants.ODP.keyForVuid] == nil, let _vuid = vuid {
identifiersUpdated[Constants.ODP.keyForVuid] = _vuid
}

// replace aliases (fs-user-id, FS_USER_ID, FS-USER-ID) with "fs_user_id".

for (idKey, idValue) in identifiersUpdated {
if idKey == Constants.ODP.keyForUserId { break }

Expand Down
5 changes: 5 additions & 0 deletions Sources/ODP/OptimizelySdkSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ public struct OptimizelySdkSettings {
let timeoutForOdpEventInSecs: Int
/// ODP features are disabled if this is set to true.
let disableOdp: Bool
/// VUID is enabled if this is set to true.
let enableVuid: Bool

/// Optimizely SDK Settings
///
Expand All @@ -36,20 +38,23 @@ public struct OptimizelySdkSettings {
/// - timeoutForSegmentFetchInSecs: The timeout in seconds of odp segment fetch (optional. default = 10) - OS default timeout will be used if this is set to zero.
/// - timeoutForOdpEventInSecs: The timeout in seconds of odp event dispatch (optional. default = 10) - OS default timeout will be used if this is set to zero.
/// - disableOdp: Set this flag to true (default = false) to disable ODP features
/// - enableVuid: Set this flag to true (default = false) to enable vuid.
/// - sdkName: Set this flag to override sdkName included in events
/// - sdkVersion: Set this flag to override sdkVersion included in events
public init(segmentsCacheSize: Int = 100,
segmentsCacheTimeoutInSecs: Int = 600,
timeoutForSegmentFetchInSecs: Int = 10,
timeoutForOdpEventInSecs: Int = 10,
disableOdp: Bool = false,
enableVuid: Bool = false,
sdkName: String? = nil,
sdkVersion: String? = nil) {
self.segmentsCacheSize = segmentsCacheSize
self.segmentsCacheTimeoutInSecs = segmentsCacheTimeoutInSecs
self.timeoutForSegmentFetchInSecs = timeoutForSegmentFetchInSecs
self.timeoutForOdpEventInSecs = timeoutForOdpEventInSecs
self.disableOdp = disableOdp
self.enableVuid = enableVuid
if let _sdkName = sdkName, _sdkName != "" {
Utils.swiftSdkClientName = _sdkName
}
Expand Down
6 changes: 5 additions & 1 deletion Sources/Optimizely+Decide/OptimizelyClient+Decide.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ extension OptimizelyClient {
///
/// - Parameter attributes: A map of attribute names to current user attribute values.
/// - Returns: An OptimizelyUserContext associated with this OptimizelyClient
public func createUserContext(attributes: [String: Any]? = nil) -> OptimizelyUserContext {
public func createUserContext(attributes: [String: Any]? = nil) -> OptimizelyUserContext? {
guard enableVuid, let vuid = self.vuid else {
logger.e("Vuid is not enabled or invalid VUID. User context not created.")
return nil
}
return OptimizelyUserContext(optimizely: self, userId: vuid, attributes: attributes)
}

Expand Down
1 change: 0 additions & 1 deletion Sources/Optimizely+Decide/OptimizelyUserContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ public class OptimizelyUserContext {
self.atomicAttributes = AtomicProperty(property: attributes, lock: lock)
self.atomicForcedDecisions = AtomicProperty(property: nil, lock: lock)
self.atomicQualifiedSegments = AtomicProperty(property: nil, lock: lock)

if identify {
// async call so event building overhead is not blocking context creation
lock.async {
Expand Down
23 changes: 20 additions & 3 deletions Sources/Optimizely/OptimizelyClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ open class OptimizelyClient: NSObject {
var decisionService: OPTDecisionService!
public var notificationCenter: OPTNotificationCenter?
public var odpManager: OdpManager!
private var vuidManager: VuidManager!
let sdkSettings: OptimizelySdkSettings

// MARK: - Public interfaces
Expand Down Expand Up @@ -91,13 +92,15 @@ open class OptimizelyClient: NSObject {
self.defaultDecideOptions = defaultDecideOptions ?? []

super.init()

self.vuidManager = VuidManager.shared
self.vuidManager.configure(enable: self.sdkSettings.enableVuid)
self.odpManager = odpManager ?? OdpManager(sdkKey: sdkKey,
disable: sdkSettings.disableOdp,
cacheSize: sdkSettings.segmentsCacheSize,
cacheTimeoutInSecs: sdkSettings.segmentsCacheTimeoutInSecs,
timeoutForSegmentFetchInSecs: sdkSettings.timeoutForSegmentFetchInSecs,
timeoutForEventDispatchInSecs: sdkSettings.timeoutForOdpEventInSecs)
self.odpManager.vuid = vuidManager.vuid
let userProfileService = userProfileService ?? DefaultUserProfileService()
let logger = logger ?? DefaultLogger()
type(of: logger).logLevel = defaultLogLevel ?? .info
Expand All @@ -115,6 +118,16 @@ open class OptimizelyClient: NSObject {
self.decisionService = HandlerRegistryService.shared.injectDecisionService(sdkKey: self.sdkKey)
self.notificationCenter = HandlerRegistryService.shared.injectNotificationCenter(sdkKey: self.sdkKey)

if let _vuid = self.vuidManager.vuid {
try? sendOdpEvent(type: Constants.ODP.eventType,
action: "client_initialized",
identifiers: [
Constants.ODP.keyForVuid: _vuid
],
data: [:])
}


logger.d("SDK Version: \(version)")
}

Expand Down Expand Up @@ -971,8 +984,12 @@ extension OptimizelyClient {
}

/// the device vuid (read only)
public var vuid: String {
return odpManager.vuid
public var vuid: String? {
return self.vuidManager.vuid
}

public var enableVuid: Bool {
return self.vuidManager.enable
}

func identifyUserToOdp(userId: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,22 @@

import Foundation

class OdpVuidManager {
var vuid: String = ""
public class VuidManager {
private var _vuid: String = ""
private(set) var enable: Bool = false
let logger = OPTLoggerFactory.getLogger()

// a single vuid should be shared for all SDK instances
static let shared = OdpVuidManager()
public static let shared = VuidManager()

init() {
self.vuid = load()
public func configure(enable: Bool) {
self.enable = enable
if enable {
self._vuid = load()
} else {
self.remove()
self._vuid = ""
}
}

static var newVuid: String {
Expand All @@ -35,16 +42,24 @@ class OdpVuidManager {
let vuid = (vuidFull.count <= maxLength) ? vuidFull : String(vuidFull.prefix(maxLength))
return vuid
}

static func isVuid(_ visitorId: String) -> Bool {
return visitorId.starts(with: "vuid_")
return visitorId.lowercased().starts(with: "vuid_")
}

}

// MARK: - VUID Store

extension OdpVuidManager {
extension VuidManager {
public var vuid: String? {
if self.enable {
return _vuid
} else {
logger.w("VUID is not enabled.")
return nil
}
}

private var keyForVuid: String {
return "optimizely-vuid"
Expand All @@ -55,11 +70,16 @@ extension OdpVuidManager {
return oldVuid
}

let vuid = OdpVuidManager.newVuid
let vuid = VuidManager.newVuid
save(vuid: vuid)
return vuid
}


private func remove() {
UserDefaults.standard.set(nil, forKey: keyForVuid)
UserDefaults.standard.synchronize()
}

private func save(vuid: String) {
UserDefaults.standard.set(vuid, forKey: keyForVuid)
UserDefaults.standard.synchronize()
Expand Down
14 changes: 8 additions & 6 deletions Tests/OptimizelyTests-APIs/OptimizelyClientTests_Decide.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ class OptimizelyClientTests_Decide: XCTestCase {
super.setUp()

let datafile = OTUtils.loadJSONDatafile("api_datafile")!
optimizely = OptimizelyClient(sdkKey: OTUtils.randomSdkKey)
let settings = OptimizelySdkSettings(enableVuid: true)
optimizely = OptimizelyClient(sdkKey: OTUtils.randomSdkKey, settings: settings)

try! optimizely.start(datafile: datafile)
}

Expand Down Expand Up @@ -54,11 +56,11 @@ class OptimizelyClientTests_Decide: XCTestCase {

let user = optimizely.createUserContext(attributes: attributes)

XCTAssert(user.optimizely == optimizely)
XCTAssert(user.userId == optimizely.vuid, "vuid should be used as the default userId when not given")
XCTAssert(user.attributes["country"] as! String == "us")
XCTAssert(user.attributes["age"] as! Int == 100)
XCTAssert(user.attributes["old"] as! Bool == true)
XCTAssert(user?.optimizely == optimizely)
XCTAssert(user?.userId == optimizely.vuid, "vuid should be used as the default userId when not given")
XCTAssert(user?.attributes["country"] as! String == "us")
XCTAssert(user?.attributes["age"] as! Int == 100)
XCTAssert(user?.attributes["old"] as! Bool == true)
}

func testCreateUserContext_multiple() {
Expand Down
13 changes: 11 additions & 2 deletions Tests/OptimizelyTests-APIs/OptimizelyClientTests_ODP.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,19 @@ class OptimizelyClientTests_ODP: XCTestCase {

// MARK: - vuid

func testVuid() {
XCTAssert(optimizely.vuid.starts(with: "vuid_"))
func testVuidEnabled() {
let settings = OptimizelySdkSettings(enableVuid: true)
optimizely = OptimizelyClient(sdkKey: OTUtils.randomSdkKey, settings: settings)
XCTAssertTrue(optimizely.enableVuid)
XCTAssert(optimizely.vuid!.starts(with: "vuid_"))
}

func testVuidDiabled() {
// Default client vuid diabled
XCTAssertFalse(optimizely.enableVuid)
XCTAssertNil(optimizely.vuid)
}

// MARK: - OdpConfig Update

func testUpdateOpdConfigCalled_wheneverProjectConfigUpdated_initialOrPolling() {
Expand Down
8 changes: 4 additions & 4 deletions Tests/OptimizelyTests-Common/OdpEventManagerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class OdpEventManagerTests: XCTestCase {
}

func testRegisterVUID_noApiKey() {
manager.registerVUID(vuid: "v1")
manager.sendInitializedEvent(vuid: "v1")

XCTAssertEqual(1, manager.eventQueue.count)

Expand Down Expand Up @@ -149,7 +149,7 @@ class OdpEventManagerTests: XCTestCase {

XCTAssertTrue(manager.odpConfig.eventQueueingAllowed, "initially datafile not ready and assumed queueing is allowed")

manager.registerVUID(vuid: "v1") // each of these will try to flush
manager.sendInitializedEvent(vuid: "v1") // each of these will try to flush
manager.identifyUser(vuid: "v1", userId: "u1")
manager.sendEvent(type: "t1", action: "a1", identifiers: [:], data: [:])

Expand Down Expand Up @@ -184,7 +184,7 @@ class OdpEventManagerTests: XCTestCase {

XCTAssertTrue(manager.odpConfig.eventQueueingAllowed, "initially datafile not ready and assumed queueing is allowed")

manager.registerVUID(vuid: "v1") // each of these will try to flush
manager.sendInitializedEvent(vuid: "v1") // each of these will try to flush
manager.identifyUser(vuid: "v1", userId: "u1")
manager.sendEvent(type: "t1", action: "a1", identifiers: [:], data: [:])

Expand Down Expand Up @@ -215,7 +215,7 @@ class OdpEventManagerTests: XCTestCase {
func testFlush_maxSize() {
manager.maxQueueSize = 2

manager.registerVUID(vuid: "v1") // each of these will try to flush
manager.sendInitializedEvent(vuid: "v1") // each of these will try to flush
manager.identifyUser(vuid: "v1", userId: "u1")
manager.sendEvent(type: "t1", action: "a1", identifiers: [:], data: [:])

Expand Down
Loading

0 comments on commit 62ae49b

Please sign in to comment.