@@ -35,17 +35,21 @@ import OneSignalOSCore
3535 */
3636class OSUserExecutor {
3737 var userRequestQueue : [ OSUserRequest ] = [ ]
38+ private let newRecordsState : OSNewRecordsState
39+ /// Delay by the "cool down" period plus a buffer of a set amount of milliseconds
40+ private let flushDelayMilliseconds = Int ( OP_REPO_POST_CREATE_DELAY_SECONDS * 1_000 + 200 ) // TODO: This could come from a config, plist, method, remote params
3841
39- // The User executor dispatch queue, serial. This synchronizes access to the request queues.
42+ /// The User executor dispatch queue, serial. This synchronizes access to the request queues.
4043 private let dispatchQueue = DispatchQueue ( label: " OneSignal.OSUserExecutor " , target: . global( ) )
4144
42- init ( ) {
45+ init ( newRecordsState: OSNewRecordsState ) {
46+ self . newRecordsState = newRecordsState
4347 uncacheUserRequests ( )
4448 migrateTransferSubscriptionRequests ( )
4549 executePendingRequests ( )
4650 }
4751
48- // Read in requests from the cache, do not read in FetchUser requests as this is not needed.
52+ /// Read in requests from the cache, do not read in FetchUser requests as this is not needed.
4953 private func uncacheUserRequests( ) {
5054 var userRequestQueue : [ OSUserRequest ] = [ ]
5155
@@ -90,7 +94,7 @@ class OSUserExecutor {
9094 // 3. Both models don't exist yet
9195 // Drop the request if the identityModelToIdentify does not already exist AND the request is missing OSID
9296 // Otherwise, this request will forever fail `prepareForExecution` and block pending requests such as recovery calls to `logout` or `login`
93- guard request. prepareForExecution ( ) else {
97+ guard request. prepareForExecution ( newRecordsState : newRecordsState ) else {
9498 OneSignalLog . onesignalLog ( . LL_ERROR, message: " OSUserExecutor.start() dropped: \( request) " )
9599 continue
96100 }
@@ -143,37 +147,47 @@ class OSUserExecutor {
143147 }
144148 }
145149
146- func executePendingRequests( ) {
147- self . dispatchQueue. async {
148- OneSignalLog . onesignalLog ( . LL_VERBOSE, message: " OSUserExecutor.executePendingRequests called with queue \( self . userRequestQueue) " )
150+ /**
151+ Requests are flushed after a delay when they need to wait for the "cool down" period to access a user or subscription after its creation.
152+ */
153+ func executePendingRequests( withDelay: Bool = false ) {
154+ if withDelay {
155+ self . dispatchQueue. asyncAfter ( deadline: . now( ) + . milliseconds( flushDelayMilliseconds) ) { [ weak self] in
156+ self ? . _executePendingRequests ( )
157+ }
158+ } else {
159+ self . dispatchQueue. async {
160+ self . _executePendingRequests ( )
161+ }
162+ }
163+ }
164+
165+ private func _executePendingRequests( ) {
166+ OneSignalLog . onesignalLog ( . LL_VERBOSE, message: " OSUserExecutor.executePendingRequests called with queue \( self . userRequestQueue) " )
149167
150- if self . userRequestQueue. isEmpty {
168+ for request in self . userRequestQueue {
169+ // Return as soon as we reach an un-executable request
170+ guard request. prepareForExecution ( newRecordsState: self . newRecordsState)
171+ else {
172+ OneSignalLog . onesignalLog ( . LL_WARN, message: " OSUserExecutor.executePendingRequests() is blocked by unexecutable request \( request) " )
173+ executePendingRequests ( withDelay: true )
151174 return
152175 }
153176
154- for request in self . userRequestQueue {
155- // Return as soon as we reach an un-executable request
156- if !request. prepareForExecution ( ) {
157- OneSignalLog . onesignalLog ( . LL_WARN, message: " OSUserExecutor.executePendingRequests() is blocked by unexecutable request \( request) " )
158- return
159- }
160-
161- if request. isKind ( of: OSRequestFetchIdentityBySubscription . self) , let fetchIdentityRequest = request as? OSRequestFetchIdentityBySubscription {
162- self . executeFetchIdentityBySubscriptionRequest ( fetchIdentityRequest)
163- return
164- } else if request. isKind ( of: OSRequestCreateUser . self) , let createUserRequest = request as? OSRequestCreateUser {
165- self . executeCreateUserRequest ( createUserRequest)
166- return
167- } else if request. isKind ( of: OSRequestIdentifyUser . self) , let identifyUserRequest = request as? OSRequestIdentifyUser {
168- self . executeIdentifyUserRequest ( identifyUserRequest)
169- return
170- } else if request. isKind ( of: OSRequestFetchUser . self) , let fetchUserRequest = request as? OSRequestFetchUser {
171- self . executeFetchUserRequest ( fetchUserRequest)
172- return
173- } else {
174- // Log Error
175- OneSignalLog . onesignalLog ( . LL_ERROR, message: " OSUserExecutor met incompatible Request type that cannot be executed. " )
176- }
177+ if request. isKind ( of: OSRequestFetchIdentityBySubscription . self) , let fetchIdentityRequest = request as? OSRequestFetchIdentityBySubscription {
178+ self . executeFetchIdentityBySubscriptionRequest ( fetchIdentityRequest)
179+ return
180+ } else if request. isKind ( of: OSRequestCreateUser . self) , let createUserRequest = request as? OSRequestCreateUser {
181+ self . executeCreateUserRequest ( createUserRequest)
182+ return
183+ } else if request. isKind ( of: OSRequestIdentifyUser . self) , let identifyUserRequest = request as? OSRequestIdentifyUser {
184+ self . executeIdentifyUserRequest ( identifyUserRequest)
185+ return
186+ } else if request. isKind ( of: OSRequestFetchUser . self) , let fetchUserRequest = request as? OSRequestFetchUser {
187+ self . executeFetchUserRequest ( fetchUserRequest)
188+ return
189+ } else {
190+ OneSignalLog . onesignalLog ( . LL_ERROR, message: " OSUserExecutor met incompatible Request type that cannot be executed. " )
177191 }
178192 }
179193 }
@@ -204,11 +218,6 @@ extension OSUserExecutor {
204218 guard !request. sentToClient else {
205219 return
206220 }
207- guard request. prepareForExecution ( ) else {
208- // Currently there are no requirements needed before sending this request, so this will set the path
209- return
210- }
211- request. sentToClient = true
212221
213222 // Hook up push subscription model if exists, it may be updated with a subscription_id, etc.
214223 if let modelId = request. pushSubscriptionModel? . modelId,
@@ -217,17 +226,29 @@ extension OSUserExecutor {
217226 request. updatePushSubscriptionModel ( pushSubscriptionModel)
218227 }
219228
229+ guard request. prepareForExecution ( newRecordsState: newRecordsState)
230+ else {
231+ executePendingRequests ( withDelay: true )
232+ return
233+ }
234+
235+ request. sentToClient = true
236+
220237 OneSignalCoreImpl . sharedClient ( ) . execute ( request) { response in
221238 self . removeFromQueue ( request)
222239
223- // TODO: Differentiate if we need to fetch the user based on response code of 200, 201, 202
224240 // Create User's response won't send us the user's complete info if this user already exists
225241 if let response = response {
242+ let shouldAddNewRecords = request. pushSubscriptionModel != nil
226243 // Parse the response for any data we need to update
227- self . parseFetchUserResponse ( response: response, identityModel: request. identityModel, originalPushToken: request. originalPushToken)
244+ self . parseFetchUserResponse (
245+ response: response,
246+ identityModel: request. identityModel,
247+ originalPushToken: request. originalPushToken,
248+ addNewRecords: shouldAddNewRecords
249+ )
228250
229251 // If this user already exists and we logged into an external_id, fetch the user data
230- // TODO: Only do this if response code is 200 or 202
231252 // Fetch the user only if its the current user and non-anonymous
232253 if OneSignalUserManagerImpl . sharedInstance. isCurrentUser ( request. identityModel) ,
233254 let identity = request. parameters ? [ " identity " ] as? [ String : String ] ,
@@ -270,9 +291,13 @@ extension OSUserExecutor {
270291 guard !request. sentToClient else {
271292 return
272293 }
273- guard request. prepareForExecution ( ) else {
294+
295+ // newRecordsState is unused for this request
296+ guard request. prepareForExecution ( newRecordsState: newRecordsState) else {
297+ executePendingRequests ( withDelay: true )
274298 return
275299 }
300+
276301 request. sentToClient = true
277302
278303 OneSignalCoreImpl . sharedClient ( ) . execute ( request) { response in
@@ -322,10 +347,12 @@ extension OSUserExecutor {
322347 guard !request. sentToClient else {
323348 return
324349 }
325- guard request. prepareForExecution ( ) else {
326- // Missing onesignal_id
350+
351+ guard request. prepareForExecution ( newRecordsState: newRecordsState) else {
352+ executePendingRequests ( withDelay: true )
327353 return
328354 }
355+
329356 request. sentToClient = true
330357
331358 OneSignalCoreImpl . sharedClient ( ) . execute ( request) { _ in
@@ -345,8 +372,9 @@ extension OSUserExecutor {
345372 request. identityModelToUpdate. hydrate ( aliases)
346373
347374 // the anonymous user has been identified, still need to Fetch User as we cleared local data
348- // Fetch the user only if its the current user
349375 if OneSignalUserManagerImpl . sharedInstance. isCurrentUser ( request. identityModelToUpdate) {
376+ // Add onesignal ID to new records because an immediate fetch may not return the newly-applied external ID
377+ self . newRecordsState. add ( onesignalId, true )
350378 self . fetchUser ( aliasLabel: OS_ONESIGNAL_ID, aliasId: onesignalId, identityModel: request. identityModelToUpdate)
351379 } else {
352380 self . executePendingRequests ( )
@@ -394,18 +422,22 @@ extension OSUserExecutor {
394422
395423 appendToQueue ( request)
396424
397- executePendingRequests ( )
425+ // User fetch will always be called after a delay unless it is to refresh the user state on a new session
426+ executePendingRequests ( withDelay: !onNewSession)
398427 }
399428
400429 func executeFetchUserRequest( _ request: OSRequestFetchUser ) {
401430 guard !request. sentToClient else {
402431 return
403432 }
404- guard request. prepareForExecution ( ) else {
405- // This should not happen as we set the alias to use for the request path
433+
434+ guard request. prepareForExecution ( newRecordsState: newRecordsState) else {
435+ executePendingRequests ( withDelay: true )
406436 return
407437 }
438+
408439 request. sentToClient = true
440+
409441 OneSignalCoreImpl . sharedClient ( ) . execute ( request) { response in
410442 self . removeFromQueue ( request)
411443
@@ -465,12 +497,15 @@ extension OSUserExecutor {
465497 /**
466498 Used to parse Create User and Fetch User responses. The `originalPushToken` is the push token when the request was created, which may be different from the push token currently in the SDK. For example, when the request was created, there may be no push token yet, but soon after, the SDK receives a push token. This is used to determine whether or not to hydrate the push subscription.
467499 */
468- func parseFetchUserResponse( response: [ AnyHashable : Any ] , identityModel: OSIdentityModel , originalPushToken: String ? ) {
500+ func parseFetchUserResponse( response: [ AnyHashable : Any ] , identityModel: OSIdentityModel , originalPushToken: String ? , addNewRecords : Bool = false ) {
469501
470502 // If this was a create user, it hydrates the onesignal_id of the request's identityModel
471503 // The model in the store may be different, and it may be waiting on the onesignal_id of this previous model
472504 if let identityObject = parseIdentityObjectResponse ( response) {
473505 identityModel. hydrate ( identityObject)
506+ if addNewRecords, let onesignalId = identityObject [ OS_ONESIGNAL_ID] {
507+ newRecordsState. add ( onesignalId)
508+ }
474509 }
475510
476511 // TODO: Determine how to hydrate the push subscription, which is still faulty.
@@ -480,13 +515,19 @@ extension OSUserExecutor {
480515
481516 // Hydrate the push subscription if we don't already have a subscription ID AND token matches the original request
482517 if OneSignalUserManagerImpl . sharedInstance. pushSubscriptionModel? . subscriptionId == nil ,
483- let subscriptionObject = parseSubscriptionObjectResponse ( response) {
518+ let subscriptionObject = parseSubscriptionObjectResponse ( response)
519+ {
484520 for subModel in subscriptionObject {
485521 if subModel [ " type " ] as? String == " iOSPush " ,
486- areTokensEqual ( tokenA: originalPushToken, tokenB: subModel [ " token " ] as? String ) { // response may have "" token or no token
522+ // response may have "" token or no token
523+ areTokensEqual ( tokenA: originalPushToken, tokenB: subModel [ " token " ] as? String )
524+ {
487525 OneSignalUserManagerImpl . sharedInstance. pushSubscriptionModel? . hydrate ( subModel)
488526 if let subId = subModel [ " id " ] as? String {
489527 OSNotificationsManager . setPushSubscriptionId ( subId)
528+ if addNewRecords {
529+ newRecordsState. add ( subId)
530+ }
490531 }
491532 break
492533 }
@@ -510,7 +551,8 @@ extension OSUserExecutor {
510551 if let address = subModel [ " token " ] as? String ,
511552 let rawType = subModel [ " type " ] as? String ,
512553 rawType != " iOSPush " ,
513- let type = OSSubscriptionType ( rawValue: rawType) {
554+ let type = OSSubscriptionType ( rawValue: rawType)
555+ {
514556 if let model = models [ address] {
515557 // This subscription exists in the store, hydrate
516558 model. hydrate ( subModel)
0 commit comments