Skip to content

Commit 73e7a92

Browse files
authored
Merge pull request #848 from Iterable/evan/MOB-9996
[MOB-9996] User update rearchitecting
2 parents 28f1524 + 761154b commit 73e7a92

File tree

8 files changed

+120
-79
lines changed

8 files changed

+120
-79
lines changed

swift-sdk/Constants.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ enum Const {
7171
static let sdkVersion = "itbl_sdk_version"
7272
static let offlineMode = "itbl_offline_mode"
7373
static let anonymousUserEvents = "itbl_anonymous_user_events"
74+
static let anonymousUserUpdate = "itbl_anonymous_user_update"
7475
static let criteriaData = "itbl_criteria_data"
7576
static let anonymousSessions = "itbl_anon_sessions"
7677
static let matchedCriteria = "itbl_matched_criteria"

swift-sdk/Internal/AnonymousUserManager.swift

Lines changed: 86 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -92,44 +92,26 @@ public class AnonymousUserManager: AnonymousUserManagerProtocol {
9292
anonSessions[JsonKey.matchedCriteriaId] = Int(criteriaId)
9393
let appName = Bundle.main.appPackageName ?? ""
9494
notificationStateProvider.isNotificationsEnabled { isEnabled in
95-
if (!appName.isEmpty && isEnabled) {
95+
if !appName.isEmpty && isEnabled {
9696
anonSessions[JsonKey.mobilePushOptIn] = appName
9797
}
98-
99-
// store last update user event
100-
var updateUserEventIndex : Int?
101-
var dataFields: [AnyHashable:Any]?
102-
if let events = self.localStorage.anonymousUserEvents {
103-
// if there is an update user event, find the index of the last one
104-
if let eventIndex = events.lastIndex(where: { dict in
105-
if let eventType = dict[JsonKey.eventType] as? String, eventType == EventType.updateUser {
106-
return true
107-
}
108-
return false
109-
}) {
110-
updateUserEventIndex = eventIndex
111-
var updateUserEvent = events[eventIndex]
112-
updateUserEvent.removeValue(forKey: JsonKey.eventType)
113-
//save update user event to data fields removing the event type
114-
dataFields = updateUserEvent
115-
}
116-
}
11798

11899
//track anon session for new user
119-
IterableAPI.implementation?.apiClient.trackAnonSession(createdAt: IterableUtil.secondsFromEpoch(for: self.dateProvider.currentDate), withUserId: userId, dataFields: dataFields,requestJson: anonSessions).onError { error in
120-
if (error.httpStatusCode == 409) {
100+
IterableAPI.implementation?.apiClient.trackAnonSession(
101+
createdAt: IterableUtil.secondsFromEpoch(for: self.dateProvider.currentDate),
102+
withUserId: userId,
103+
dataFields: self.localStorage.anonymousUserUpdate,
104+
requestJson: anonSessions
105+
).onError { error in
106+
if error.httpStatusCode == 409 {
121107
self.getAnonCriteria() // refetch the criteria
122108
}
123109
}.onSuccess { success in
124-
//remove the update user event from local storage
125-
if var events = self.localStorage.anonymousUserEvents, let index = updateUserEventIndex {
126-
events.remove(at: index)
127-
self.localStorage.anonymousUserEvents = events
128-
}
129-
130110
self.localStorage.userIdAnnon = userId
131111
self.config.anonUserDelegate?.onAnonUserCreated(userId: userId)
132-
IterableAPI.implementation?.setUserId(userId, authToken: nil, successHandler: nil, failureHandler: nil, isAnon: true, identityResolution: nil)
112+
113+
IterableAPI.implementation?.setUserId(userId, isAnon: true)
114+
133115
self.syncNonSyncedEvents()
134116
}
135117
}
@@ -144,37 +126,32 @@ public class AnonymousUserManager: AnonymousUserManagerProtocol {
144126

145127
// Syncs locally saved data through track APIs
146128
public func syncEvents() {
147-
let events = localStorage.anonymousUserEvents
148-
var successfulSyncedData: [Int] = []
149-
150-
if let _events = events {
151-
for var eventData in _events {
129+
if let events = localStorage.anonymousUserEvents {
130+
for var eventData in events {
152131
if let eventType = eventData[JsonKey.eventType] as? String {
153132
eventData.removeValue(forKey: JsonKey.eventType)
154133
switch eventType {
155134
case EventType.customEvent:
156-
IterableAPI.implementation?.track(eventData[JsonKey.eventName] as? String ?? "", withBody: eventData, onSuccess: {result in
157-
successfulSyncedData.append(eventData[JsonKey.eventTimeStamp] as? Int ?? 0)
158-
})
135+
IterableAPI.implementation?.track(eventData[JsonKey.eventName] as? String ?? "", withBody: eventData)
159136
break
160137
case EventType.purchase:
161138
var total = NSNumber(value: 0)
162139
if let _total = NumberFormatter().number(from: eventData[JsonKey.Commerce.total] as! String) {
163140
total = _total
164141
}
165142

166-
IterableAPI.implementation?.trackPurchase(total, items: convertCommerceItems(from: eventData[JsonKey.Commerce.items] as! [[AnyHashable: Any]]), dataFields: eventData[JsonKey.dataFields] as? [AnyHashable : Any], createdAt: eventData[JsonKey.Body.createdAt] as? Int ?? 0, onSuccess: {result in
167-
successfulSyncedData.append(eventData[JsonKey.eventTimeStamp] as? Int ?? 0)
168-
})
143+
IterableAPI.implementation?.trackPurchase(
144+
total,
145+
items: convertCommerceItems(from: eventData[JsonKey.Commerce.items] as! [[AnyHashable: Any]]),
146+
dataFields: eventData[JsonKey.dataFields] as? [AnyHashable : Any],
147+
createdAt: eventData[JsonKey.Body.createdAt] as? Int ?? 0
148+
)
169149
break
170150
case EventType.updateCart:
171-
IterableAPI.implementation?.updateCart(items: convertCommerceItems(from: eventData[JsonKey.Commerce.items] as! [[AnyHashable: Any]]), createdAt: eventData[JsonKey.Body.createdAt] as? Int ?? 0,
172-
onSuccess: {result in
173-
successfulSyncedData.append(eventData[JsonKey.eventTimeStamp] as? Int ?? 0)
174-
})
175-
break
176-
case EventType.updateUser:
177-
IterableAPI.implementation?.updateUser(eventData, mergeNestedObjects: false)
151+
IterableAPI.implementation?.updateCart(
152+
items: convertCommerceItems(from: eventData[JsonKey.Commerce.items] as! [[AnyHashable: Any]]),
153+
createdAt: eventData[JsonKey.Body.createdAt] as? Int ?? 0
154+
)
178155
break
179156
default:
180157
break
@@ -193,15 +170,36 @@ public class AnonymousUserManager: AnonymousUserManagerProtocol {
193170
localStorage.anonymousUserEvents = nil
194171
localStorage.anonymousSessions = nil
195172
}
173+
174+
if var userUpdate = localStorage.anonymousUserUpdate {
175+
if userUpdate[JsonKey.eventType] is String {
176+
userUpdate.removeValue(forKey: JsonKey.eventType)
177+
}
178+
179+
IterableAPI.implementation?.updateUser(userUpdate, mergeNestedObjects: false)
180+
181+
localStorage.anonymousUserUpdate = nil
182+
}
183+
196184
}
197185

198186
// Checks if criterias are being met and returns criteriaId if it matches the criteria.
199187
private func evaluateCriteriaAndReturnID() -> String? {
200-
guard let events = localStorage.anonymousUserEvents, let criteriaData = localStorage.criteriaData else {
201-
return nil
188+
guard let criteriaData = localStorage.criteriaData else { return nil }
189+
190+
var events = [[AnyHashable: Any]]()
191+
192+
if let anonymousUserEvents = localStorage.anonymousUserEvents {
193+
events.append(contentsOf: anonymousUserEvents)
194+
}
195+
196+
if let userUpdate = localStorage.anonymousUserUpdate {
197+
events.append(userUpdate)
202198
}
203-
let matchedCriteriaId = CriteriaCompletionChecker(anonymousCriteria: criteriaData, anonymousEvents: events).getMatchedCriteria()
204-
return matchedCriteriaId
199+
200+
guard events.count > 0 else { return nil }
201+
202+
return CriteriaCompletionChecker(anonymousCriteria: criteriaData, anonymousEvents: events).getMatchedCriteria()
205203
}
206204

207205
// Gets the anonymous criteria
@@ -212,41 +210,51 @@ public class AnonymousUserManager: AnonymousUserManagerProtocol {
212210
}
213211

214212
// Stores event data locally
215-
private func storeEventData(type: String, data: [AnyHashable: Any], shouldOverWrite: Bool? = false) {
213+
private func storeEventData(type: String, data: [AnyHashable: Any], shouldOverWrite: Bool = false) {
214+
// Early return if no AUT consent was given
216215
if !self.localStorage.anonymousUsageTrack {
217216
ITBInfo("AUT CONSENT NOT GIVEN - no events being stored")
218217
return
219218
}
220-
221-
let storedData = localStorage.anonymousUserEvents
222-
var eventsDataObjects: [[AnyHashable: Any]] = []
223-
224-
if let _storedData = storedData {
225-
eventsDataObjects = _storedData
226-
}
227-
var appendData = data
228-
appendData.setValue(for: JsonKey.eventType, value: type)
229-
appendData.setValue(for: JsonKey.eventTimeStamp, value:IterableUtil.secondsFromEpoch(for: dateProvider.currentDate)) // this we use as unique idenfier too
230-
231-
if shouldOverWrite == true {
232-
let trackingType = type
233-
if let indexToUpdate = eventsDataObjects.firstIndex(where: { $0[JsonKey.eventType] as? String == trackingType }) {
234-
let dataToUpdate = eventsDataObjects[indexToUpdate]
235-
eventsDataObjects[indexToUpdate] = dataToUpdate.merging(data) { (_, new) in new }
236-
} else {
237-
eventsDataObjects.append(appendData)
238-
}
219+
220+
if type == EventType.updateUser {
221+
processAndStoreUserUpdate(data: data)
239222
} else {
240-
eventsDataObjects.append(appendData)
241-
}
242-
243-
let eventDataCount = eventsDataObjects.count
244-
if eventDataCount > config.eventThresholdLimit {
245-
eventsDataObjects = eventsDataObjects.suffix(config.eventThresholdLimit)
223+
processAndStoreEvent(type: type, data: data)
246224
}
247-
localStorage.anonymousUserEvents = eventsDataObjects
225+
248226
if let criteriaId = evaluateCriteriaAndReturnID() {
249227
createKnownUserIfCriteriaMatched(criteriaId)
250228
}
251229
}
230+
231+
// Stores User Update data
232+
private func processAndStoreUserUpdate(data: [AnyHashable: Any]) {
233+
var userUpdate = localStorage.anonymousUserUpdate ?? [:]
234+
235+
// Merge new data into userUpdate
236+
userUpdate.merge(data) { (_, new) in new }
237+
238+
userUpdate.setValue(for: JsonKey.eventType, value: EventType.updateUser)
239+
userUpdate.setValue(for: JsonKey.eventTimeStamp, value: IterableUtil.secondsFromEpoch(for: dateProvider.currentDate))
240+
241+
localStorage.anonymousUserUpdate = userUpdate
242+
}
243+
244+
// Stores all other event data
245+
private func processAndStoreEvent(type: String, data: [AnyHashable: Any]) {
246+
var eventsDataObjects: [[AnyHashable: Any]] = localStorage.anonymousUserEvents ?? []
247+
248+
var newEventData = data
249+
newEventData.setValue(for: JsonKey.eventType, value: type)
250+
newEventData.setValue(for: JsonKey.eventTimeStamp, value: IterableUtil.secondsFromEpoch(for: dateProvider.currentDate)) // this we use as unique idenfier too
251+
252+
eventsDataObjects.append(newEventData)
253+
254+
if eventsDataObjects.count > config.eventThresholdLimit {
255+
eventsDataObjects = eventsDataObjects.suffix(config.eventThresholdLimit)
256+
}
257+
258+
localStorage.anonymousUserEvents = eventsDataObjects
259+
}
252260
}

swift-sdk/Internal/AuthManager.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ class AuthManager: IterableAuthManagerProtocol {
9393
if localStorage.email != nil || localStorage.userId != nil || localStorage.userIdAnnon != nil {
9494
localStorage.anonymousUserEvents = nil
9595
localStorage.anonymousSessions = nil
96+
localStorage.anonymousUserUpdate = nil
9697
}
9798

9899
isLastAuthTokenValid = false

swift-sdk/Internal/InternalIterableAPI.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,8 @@ final class InternalIterableAPI: NSObject, PushTrackerProtocol, AuthProvider {
232232
self.localStorage.anonymousUsageTrack = isAnonymousUsageTracked
233233
self.localStorage.anonymousUserEvents = nil
234234
self.localStorage.anonymousSessions = nil
235+
self.localStorage.anonymousUserUpdate = nil
236+
235237
if isAnonymousUsageTracked && config.enableAnonTracking {
236238
ITBInfo("CONSENT GIVEN and ANON TRACKING ENABLED - Criteria fetched")
237239
self.anonymousUserManager.getAnonCriteria()

swift-sdk/Internal/IterableUserDefaults.swift

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ class IterableUserDefaults {
8686
}
8787
}
8888

89+
var anonymousUserUpdate: [AnyHashable: Any]? {
90+
get {
91+
return userUpdateData(withKey: .anonymousUserUpdate)
92+
} set {
93+
saveUserUpdate(newValue, withKey: .anonymousUserUpdate)
94+
}
95+
}
96+
8997
var criteriaData: Data? {
9098
get {
9199
return getCriteriaData(withKey: .criteriaData)
@@ -134,6 +142,10 @@ class IterableUserDefaults {
134142
userDefaults.set(anonymousUserEvents, forKey: key.value)
135143
}
136144

145+
private func saveUserUpdate(_ update: [AnyHashable: Any]?, withKey key: UserDefaultsKey) {
146+
userDefaults.set(update, forKey: key.value)
147+
}
148+
137149
func getAttributionInfo(currentDate: Date) -> IterableAttributionInfo? {
138150
(try? codable(withKey: .attributionInfo, currentDate: currentDate)) ?? nil
139151
}
@@ -206,6 +218,10 @@ class IterableUserDefaults {
206218
userDefaults.array(forKey: key.value) as? [[AnyHashable: Any]]
207219
}
208220

221+
private func userUpdateData(withKey key: UserDefaultsKey) -> [AnyHashable: Any]? {
222+
userDefaults.object(forKey: key.value) as? [AnyHashable: Any]
223+
}
224+
209225
private func getCriteriaData(withKey key: UserDefaultsKey) -> Data? {
210226
userDefaults.object(forKey: key.value) as? Data
211227
}
@@ -289,7 +305,8 @@ class IterableUserDefaults {
289305
static let deviceId = UserDefaultsKey(value: Const.UserDefault.deviceId)
290306
static let sdkVersion = UserDefaultsKey(value: Const.UserDefault.sdkVersion)
291307
static let offlineMode = UserDefaultsKey(value: Const.UserDefault.offlineMode)
292-
static let anonymousUserEvents = UserDefaultsKey(value: Const.UserDefault.offlineMode)
308+
static let anonymousUserEvents = UserDefaultsKey(value: Const.UserDefault.anonymousUserEvents)
309+
static let anonymousUserUpdate = UserDefaultsKey(value: Const.UserDefault.anonymousUserUpdate)
293310
static let criteriaData = UserDefaultsKey(value: Const.UserDefault.criteriaData)
294311
static let anonymousSessions = UserDefaultsKey(value: Const.UserDefault.anonymousSessions)
295312
static let anonymousUsageTrack = UserDefaultsKey(value: Const.UserDefault.anonymousUsageTrack)

swift-sdk/Internal/LocalStorage.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ struct LocalStorage: LocalStorageProtocol {
8383
iterableUserDefaults.anonymousUserEvents = newValue
8484
}
8585
}
86+
87+
var anonymousUserUpdate: [AnyHashable: Any]? {
88+
get {
89+
iterableUserDefaults.anonymousUserUpdate
90+
} set {
91+
iterableUserDefaults.anonymousUserUpdate = newValue
92+
}
93+
}
8694

8795
var anonymousSessions: IterableAnonSessionsWrapper? {
8896
get {

swift-sdk/Internal/LocalStorageProtocol.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ protocol LocalStorageProtocol {
2424
var anonymousUsageTrack: Bool { get set }
2525

2626
var anonymousUserEvents: [[AnyHashable: Any]]? { get set }
27+
28+
var anonymousUserUpdate: [AnyHashable: Any]? { get set }
2729

2830
var criteriaData: Data? { get set }
2931

tests/common/MockLocalStorage.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ class MockLocalStorage: LocalStorageProtocol {
3131
var offlineMode: Bool = false
3232

3333
var anonymousUsageTrack: Bool = true
34+
35+
var anonymousUserUpdate: [AnyHashable : Any]?
3436

3537
func getAttributionInfo(currentDate: Date) -> IterableAttributionInfo? {
3638
guard !MockLocalStorage.isExpired(expiration: attributionInfoExpiration, currentDate: currentDate) else {

0 commit comments

Comments
 (0)