Skip to content

Commit 2abd05a

Browse files
authored
Migration: Use loggedInUserId to identify previously logged-in users (#572)
* Fixes for CleverTap ID * Identity fixes, allow login with deviceId * Fix tests * Update sample response * Fix setting the loggedInUserId in Leanplum * Refactor IdentityManagerTest * Fix for changing the user while offline before getMigrateState
1 parent c232be8 commit 2abd05a

File tree

9 files changed

+240
-34
lines changed

9 files changed

+240
-34
lines changed

LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/MigrationManager+Constants.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import Foundation
1616
static let RegionCodeKey = "__leanplum_region_code"
1717
static let AttributeMappingsKey = "__leanplum_attribute_mappings"
1818
static let IdentityKeysKey = "__leanplum_identity_keys"
19+
static let LoggedInUserIdKey = "__leanplum_logged_in_user_id"
1920

2021
static let DefaultIdentityKeys = [IdentityManager.Constants.Identity]
2122

LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/MigrationManager+ResponseHandler.swift

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ import Foundation
2020
let ct: CTConfig?
2121
let migrationState: String?
2222
let hash: String?
23+
let loggedInUserId: String?
2324

2425
enum CodingKeys: String, CodingKey {
2526
case hash = "sha256"
2627
case migrationState = "sdk"
2728
case ct
29+
case loggedInUserId
2830
}
2931
}
3032

@@ -80,6 +82,7 @@ import Foundation
8082
// "sdk": "lp+ct",
8183
// "sha256": "31484a565dcd3e1672922c7c4166bfeee0f500b6d6473fc412091304cc162ca8",
8284
// "state": "EVENTS_UPLOAD_STARTED",
85+
// "loggedInUserId": "9da5cdc6-m340-42a8-9110-1d4a1099f157",
8386
// "success": 1,
8487
// }
8588
// ]
@@ -106,12 +109,23 @@ import Foundation
106109
}
107110
}
108111

109-
if let sdk = migrationData.migrationState {
110-
migrationState = MigrationState(stringValue: sdk)
111-
}
112112
if let hash = migrationData.hash {
113113
migrationHash = hash
114114
}
115+
116+
if let loggedInUser = migrationData.loggedInUserId {
117+
loggedInUserId = loggedInUser
118+
Leanplum.onStartResponse { _ in
119+
if Leanplum.userId() == Leanplum.deviceId() {
120+
Leanplum.setUserId(loggedInUser)
121+
}
122+
}
123+
}
124+
125+
// Changing the migrationState value will initialize the Wrapper
126+
if let sdk = migrationData.migrationState {
127+
migrationState = MigrationState(stringValue: sdk)
128+
}
115129
}
116130

117131
// MARK: - Utils

LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/MigrationManager.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ import Foundation
3636
@PropUserDefaults(key: Constants.IdentityKeysKey, defaultValue: Constants.DefaultIdentityKeys)
3737
var identityKeys: [String]
3838

39+
@StringOptionalUserDefaults(key: Constants.LoggedInUserIdKey)
40+
var loggedInUserId: String?
41+
3942
@MigrationStateUserDefaults(key: Constants.MigrationStateKey, defaultValue: .undefined)
4043
var migrationState: MigrationState {
4144
didSet {
@@ -61,10 +64,11 @@ import Foundation
6164
Log.error("[Wrapper] Missing Leanplum userId and deviceId. Cannot initialize CleverTap.")
6265
return
6366
}
64-
67+
6568
wrapper = CTWrapper(accountId: id, accountToken: token,
6669
accountRegion: accountRegion,
67-
userId: user, deviceId: device,
70+
useCustomCleverTapId: state != .cleverTap,
71+
userId: user, deviceId: device, loggedInUserId: loggedInUserId,
6872
callbacks: instanceCallbacks)
6973
wrapper?.launch()
7074
}

LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/Wrapper/CTWrapper.swift

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,28 +35,48 @@ class CTWrapper: Wrapper {
3535
var accountRegion: String
3636
var identityManager: IdentityManager
3737

38+
var useCustomCleverTapId: Bool
39+
3840
public init(accountId: String,
3941
accountToken: String,
4042
accountRegion: String,
43+
useCustomCleverTapId: Bool,
4144
userId: String,
4245
deviceId: String,
46+
loggedInUserId: String?,
4347
callbacks: [CleverTapInstanceCallback]) {
4448
Log.info("[Wrapper] Wrapper Instantiated")
4549
self.accountId = accountId
4650
self.accountToken = accountToken
4751
self.accountRegion = accountRegion
4852
self.instanceCallbacks = callbacks
53+
self.useCustomCleverTapId = useCustomCleverTapId
4954

50-
identityManager = IdentityManager(userId: userId, deviceId: deviceId)
55+
identityManager = IdentityManager(userId: userId, deviceId: deviceId, loggedInUserId: loggedInUserId)
5156
setLogLevel(LPLogManager.logLevel())
5257
}
5358

59+
func onUserLogin() {
60+
if useCustomCleverTapId {
61+
cleverTapInstance?.onUserLogin(identityManager.profile,
62+
withCleverTapID: identityManager.cleverTapID)
63+
} else {
64+
cleverTapInstance?.onUserLogin(identityManager.profile)
65+
}
66+
}
67+
5468
func launch() {
5569
let config = CleverTapInstanceConfig.init(accountId: accountId, accountToken: accountToken, accountRegion: accountRegion)
5670
config.identityKeys = MigrationManager.shared.identityKeys
57-
config.useCustomCleverTapId = true
5871
config.logLevel = CleverTapLogLevel(LPLogManager.logLevel())
59-
cleverTapInstance = CleverTap.instance(with: config, andCleverTapID: identityManager.cleverTapID)
72+
73+
if useCustomCleverTapId {
74+
config.useCustomCleverTapId = true
75+
cleverTapInstance = CleverTap.instance(with: config, andCleverTapID: identityManager.cleverTapID)
76+
} else {
77+
cleverTapInstance = CleverTap.instance(with: config)
78+
}
79+
6080
cleverTapInstance?.setLibrary("Leanplum")
6181
// Track App Launched
6282
cleverTapInstance?.notifyApplicationLaunched(withOptions: [:])
@@ -69,8 +89,7 @@ class CTWrapper: Wrapper {
6989
[Wrapper] will call onUserLogin with \
7090
Identity: \(identityManager.userId) and cleverTapId: \(identityManager.cleverTapID)"
7191
""")
72-
cleverTapInstance?.onUserLogin(identityManager.profile,
73-
withCleverTapID: identityManager.cleverTapID)
92+
onUserLogin()
7493
setDevicesProperty()
7594
} else {
7695
if !identityManager.isValidCleverTapID {
@@ -216,7 +235,7 @@ class CTWrapper: Wrapper {
216235
with identity: \(profile) \
217236
and CleverTapID: \(cleverTapID)")
218237
""")
219-
cleverTapInstance?.onUserLogin(profile, withCleverTapID: cleverTapID)
238+
onUserLogin()
220239

221240
setDevicesProperty()
222241
}

LeanplumSDK/LeanplumSDK/ClassesSwift/Migration/Wrapper/IdentityManager.swift

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// LeanplumSDK
44
//
55
// Created by Nikola Zagorchev on 6.10.22.
6-
// Copyright © 2022 Leanplum. All rights reserved.
6+
// Copyright © 2023 Leanplum. All rights reserved.
77

88
import Foundation
99
// Use @_implementationOnly to *not* expose CleverTapSDK to the Leanplum-Swift header
@@ -28,6 +28,18 @@ import Foundation
2828
* For this userId, the CTID is always set to deviceId.
2929
* Leanplum UserId can be set through Leanplum.start and Leanplum.setUserId
3030
*
31+
* - Note: There are cases where the deviceId can remain the same after app uninstall.
32+
* In this case, the SDK has lost the data on the anonymous and logged in user.
33+
* If there is already logged in user on that device, the API will return the logged in userId with the migration data.
34+
* Use this userId as Identified, so CTID is non-anonymous when the `Wrapper` is created.
35+
* The very first `CleverTap` instance must be created as non-anonymous <CTID=deviceId_userIdHash, Identity=userId>.
36+
* This ensures the data will go to the logged in user according to the Leanplum API.
37+
*
38+
* - Note: Allow setting the userId to the deviceId if user has already logged in - non-anonymous.
39+
* If the user is anonymous, calling `setUserId` with <userId=deviceId> should be a NOOP.
40+
* If user has logged in on the device, and then `setUserId` is called with <userId=deviceId>,
41+
* treat this as a new user login <CTID=deviceId_deviceIdHash, Identity=deviceId>
42+
*
3143
* - Precondition: DeviceId cannot be changed when CT is used,
3244
* since CTID cannot be modified for the same Identity.
3345
*/
@@ -36,6 +48,7 @@ class IdentityManager {
3648
static let Identity = "Identity"
3749
static let AnonymousLoginUserIdKey = "__leanplum_anonymous_login_user_id"
3850
static let IdentityStateKey = "__leanplum_identity_state"
51+
static let LoggedInUserKey = "__leanplum_logged_in_user"
3952

4053
static let DeviceIdLengthLimit = 50
4154
static let IdentityHashLength = 10
@@ -59,15 +72,27 @@ class IdentityManager {
5972
@StringOptionalUserDefaults(key: Constants.IdentityStateKey)
6073
var state: String?
6174

62-
init(userId: String, deviceId: String) {
75+
@StringOptionalUserDefaults(key: Constants.LoggedInUserKey)
76+
var loggedInUserId: String?
77+
78+
convenience init(userId: String, deviceId: String) {
79+
self.init(userId: userId, deviceId: deviceId, loggedInUserId: nil)
80+
}
81+
82+
init(userId: String, deviceId: String, loggedInUserId: String?) {
6383
self.userId = userId
6484
self.deviceId = deviceId
85+
self.loggedInUserId = loggedInUserId
6586

6687
identify()
6788
}
6889

6990
func setUserId(_ userId: String) {
70-
if (state == IdentityState.anonymous()) {
91+
if userId == deviceId && state == IdentityState.anonymous() {
92+
return
93+
}
94+
95+
if state == IdentityState.anonymous() {
7196
if let hash = Utilities.sha256_40(string: userId) {
7297
anonymousLoginUserId = hash
7398
} else {
@@ -81,7 +106,10 @@ class IdentityManager {
81106
}
82107

83108
func identify() {
84-
if isAnonymous {
109+
if isAnonymous, let loggedInUserId = self.loggedInUserId {
110+
self.userId = loggedInUserId
111+
state = IdentityState.identified()
112+
} else if isAnonymous {
85113
state = IdentityState.anonymous()
86114
} else {
87115
identifyNonAnonymous()
@@ -141,10 +169,10 @@ class IdentityManager {
141169
}
142170

143171
var isAnonymous: Bool {
144-
userId == deviceId
172+
userId == deviceId && state != IdentityState.identified()
145173
}
146174

147175
var shouldAppendUserId: Bool {
148-
userIdHash != anonymousLoginUserId && userId != deviceId
176+
userIdHash != anonymousLoginUserId && (userId != deviceId || state == IdentityState.identified())
149177
}
150178
}

LeanplumSDKApp/LeanplumSDKTests/Classes/Migration/CTWrapperTest.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import XCTest
1212
class CTWrapperTest: XCTestCase {
1313

1414
static let attributeMappings = ["lpName": "ctName", "lpName2": "ctName2"]
15-
let wrapper = CTWrapper(accountId: "", accountToken: "", accountRegion: "", userId: "", deviceId: "", callbacks: [])
16-
15+
let wrapper = CTWrapper(accountId: "", accountToken: "", accountRegion: "", useCustomCleverTapId: true,
16+
userId: "", deviceId: "", loggedInUserId: nil, callbacks: [])
1717
override class func setUp() {
1818
MigrationManager.shared.attributeMappings = attributeMappings
1919
}

LeanplumSDKApp/LeanplumSDKTests/Classes/Migration/IdentityManagerMocks.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ class IdentityManagerMockStatic: IdentityManager {
5151
}
5252
}
5353

54-
init(userId: String, deviceId: String, anonymousLoginUserId: String?, state: String?) {
54+
init(userId: String, deviceId: String, anonymousLoginUserId: String?, state: String?, loggedInUserId: String? = nil) {
5555
// Needs to be set before call to super.init
5656
IdentityManagerMockStatic._anonymousLoginUserId = anonymousLoginUserId
5757
IdentityManagerMockStatic._state = state
5858

59-
super.init(userId: userId, deviceId: deviceId)
59+
super.init(userId: userId, deviceId: deviceId, loggedInUserId: loggedInUserId)
6060
}
6161
}

0 commit comments

Comments
 (0)