diff --git a/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalCommonDefines.h b/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalCommonDefines.h index 2f2fd8f0f..bb329444d 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalCommonDefines.h +++ b/iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalCommonDefines.h @@ -184,9 +184,13 @@ typedef enum {ATTRIBUTED, NOT_ATTRIBUTED} FocusAttributionState; #define focusAttributionStateString(enum) [@[@"ATTRIBUTED", @"NOT_ATTRIBUTED"] objectAtIndex:enum] // OneSignal Background Task Identifiers -#define ATTRIBUTED_FOCUS_TASK @"ATTRIBUTED_FOCUS_TASK" -#define UNATTRIBUTED_FOCUS_TASK @"UNATTRIBUTED_FOCUS_TASK" -#define USER_MANAGER_BACKGROUND_TASK @"USER_MANAGER_BACKGROUND_TASK" +#define ATTRIBUTED_FOCUS_TASK @"ATTRIBUTED_FOCUS_TASK" +#define UNATTRIBUTED_FOCUS_TASK @"UNATTRIBUTED_FOCUS_TASK" +#define SEND_SESSION_TIME_TO_USER_TASK @"SEND_SESSION_TIME_TO_USER_TASK" +#define OPERATION_REPO_BACKGROUND_TASK @"OPERATION_REPO_BACKGROUND_TASK" +#define IDENTITY_EXECUTOR_BACKGROUND_TASK @"IDENTITY_EXECUTOR_BACKGROUND_TASK_" +#define PROPERTIES_EXECUTOR_BACKGROUND_TASK @"PROPERTIES_EXECUTOR_BACKGROUND_TASK_" +#define SUBSCRIPTION_EXECUTOR_BACKGROUND_TASK @"SUBSCRIPTION_EXECUTOR_BACKGROUND_TASK_" // OneSignal constants #define OS_PUSH @"push" diff --git a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Controller/OSMessagingController.m b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Controller/OSMessagingController.m index f7330e89e..7778341ea 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Controller/OSMessagingController.m +++ b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Controller/OSMessagingController.m @@ -448,6 +448,7 @@ - (void)presentInAppPreviewMessage:(OSInAppMessageInternal *)message { - (void)displayMessage:(OSInAppMessageInternal *)message { // Check if the app disabled IAMs for this device before showing an IAM if (_isInAppMessagingPaused && !message.isPreview) { + [self cleanUpInAppWindow]; [OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:@"In app messages will not show while paused"]; return; } @@ -749,6 +750,19 @@ - (void)messageViewControllerWasDismissed:(OSInAppMessageInternal *)message disp } } +- (void)cleanUpInAppWindow { + self.viewController = nil; + if (self.window) { + /* + Hide the top level IAM window + After the IAM window is hidden, iOS will automatically promote the main window + This also re-shows the keyboard automatically if it had focus in a text input + */ + self.window.hidden = true; + self.window = nil; + } +} + - (void)setAndPersistTimeSinceLastMessage { NSDate *timeSinceLastMessage = [NSDate new]; [self.triggerController timeSinceLastMessage:timeSinceLastMessage]; @@ -768,21 +782,12 @@ - (void)evaluateMessageDisplayQueue { [self displayMessage:self.messageDisplayQueue.firstObject]; return; } else { - [self hideWindow]; + [self cleanUpInAppWindow]; // Evaulate any IAMs (could be new IAM or added trigger conditions) [self evaluateMessages]; } } -/* - Hide the top level IAM window - After the IAM window is hidden, iOS will automatically promote the main window - This also re-shows the keyboard automatically if it had focus in a text input -*/ -- (void)hideWindow { - self.window.hidden = true; -} - - (void)persistInAppMessageForRedisplay:(OSInAppMessageInternal *)message { // If the IAM doesn't have the re display prop or is a preview IAM there is no need to save it if (![message.displayStats isRedisplayEnabled] || message.isPreview) { diff --git a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/UI/OSInAppMessageViewController.m b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/UI/OSInAppMessageViewController.m index 3c034d22a..9645bb8d9 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/UI/OSInAppMessageViewController.m +++ b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/UI/OSInAppMessageViewController.m @@ -95,12 +95,15 @@ @interface OSInAppMessageViewController () @implementation OSInAppMessageViewController +OSInAppMessageInternal *_dismissingMessage = nil; + - (instancetype _Nonnull)initWithMessage:(OSInAppMessageInternal *)inAppMessage delegate:(id)delegate { if (self = [super init]) { self.message = inAppMessage; self.delegate = delegate; self.useHeightMargin = YES; self.useWidthMargin = YES; + _dismissingMessage = nil; } return self; @@ -490,6 +493,10 @@ - (void)dismissCurrentInAppMessage:(BOOL)up withVelocity:(double)velocity { [self.delegate messageViewControllerWasDismissed:self.message displayed:NO]; return; } + + if (_dismissingMessage == self.message) { + return; + } [self.delegate messageViewControllerWillDismiss:self.message]; @@ -521,7 +528,7 @@ - (void)dismissCurrentInAppMessage:(BOOL)up withVelocity:(double)velocity { animationOption = UIViewAnimationOptionCurveEaseIn; dismissAnimationDuration = MIN_DISMISSAL_ANIMATION_DURATION; } - + _dismissingMessage = self.message; [UIView animateWithDuration:dismissAnimationDuration delay:0.0f options:animationOption animations:^{ self.view.backgroundColor = [UIColor clearColor]; self.view.alpha = 0.0f; diff --git a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSEventProducer.swift b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSEventProducer.swift index c14aabb70..fd84d34c2 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSEventProducer.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSEventProducer.swift @@ -33,7 +33,6 @@ public class OSEventProducer: NSObject { var subscriber: THandler? public func subscribe(_ handler: THandler) { - OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSEventProducer.subscribe() called with handler: \(handler)") // TODO: UM do we want to synchronize on subscribers subscriber = handler // TODO: UM style, implicit or explicit self? } diff --git a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSModelStore.swift b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSModelStore.swift index 2a4bf6e20..64a2f832c 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSModelStore.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSModelStore.swift @@ -87,7 +87,6 @@ open class OSModelStore: NSObject { } public func add(id: String, model: TModel, hydrating: Bool) { - OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSModelStore add() called with model \(model)") // TODO: Check if we are adding the same model? Do we replace? // For example, calling addEmail multiple times with the same email // Check API endpoint for behavior @@ -127,6 +126,8 @@ open class OSModelStore: NSObject { self.changeSubscription.fire { modelStoreListener in modelStoreListener.onRemoved(model) } + } else { + OneSignalLog.onesignalLog(.LL_ERROR, message: "OSModelStore cannot remove \(id) because it doesn't exist in the store.") } } @@ -151,8 +152,6 @@ open class OSModelStore: NSObject { extension OSModelStore: OSModelChangedHandler { public func onModelUpdated(args: OSModelChangedArgs, hydrating: Bool) { - OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSModelStore.onChanged() called with OSModelChangedArgs: \(args)") - // persist the changed models to storage OneSignalUserDefaults.initShared().saveCodeableData(forKey: self.storeKey, withValue: self.models) diff --git a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSModelStoreListener.swift b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSModelStoreListener.swift index b4c217306..d6297fbd3 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSModelStoreListener.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSModelStoreListener.swift @@ -52,7 +52,6 @@ extension OSModelStoreListener { } public func onAdded(_ model: OSModel) { - OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSModelStoreListener.onAdded() with model \(model)") guard let addedModel = model as? Self.TModel else { // log error return @@ -63,7 +62,6 @@ extension OSModelStoreListener { } public func onUpdated(_ args: OSModelChangedArgs) { - OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSModelStoreListener.onUpdated() with args \(args)") if let delta = getUpdateModelDelta(args) { OSOperationRepo.sharedInstance.enqueueDelta(delta) } diff --git a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSOperationExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSOperationExecutor.swift index d6d39c4dd..db3aee576 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSOperationExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSOperationExecutor.swift @@ -35,7 +35,7 @@ public protocol OSOperationExecutor { func enqueueDelta(_ delta: OSDelta) func cacheDeltaQueue() - func processDeltaQueue() + func processDeltaQueue(inBackground: Bool) - func processRequestQueue() + func processRequestQueue(inBackground: Bool) } diff --git a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSOperationRepo.swift b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSOperationRepo.swift index e3f952002..8f9e8f77b 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSOperationRepo.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSOperationRepo.swift @@ -43,6 +43,7 @@ public class OSOperationRepo: NSObject { // TODO: This should come from a config, plist, method, remote params var pollIntervalSeconds = 5 + public var paused = false /** Initilize this Operation Repo. Read from the cache. Executors may not be available by this time. @@ -107,13 +108,23 @@ public class OSOperationRepo: NSObject { OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_OPERATION_REPO_DELTA_QUEUE_KEY, withValue: self.deltaQueue) } - @objc public func flushDeltaQueue() { + @objc public func flushDeltaQueue(inBackground: Bool = false) { + guard !paused else { + OneSignalLog.onesignalLog(.LL_DEBUG, message: "OSOperationRepo not flushing queue due to being paused") + return + } + guard !OneSignalConfigManager.shouldAwaitAppIdAndLogMissingPrivacyConsent(forMethod: nil) else { return } + + if (inBackground) { + OSBackgroundTaskManager.beginBackgroundTask(OPERATION_REPO_BACKGROUND_TASK) + } + start() if !deltaQueue.isEmpty { - OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSOperationRepo flushDeltaQueue with queue: \(deltaQueue)") + OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSOperationRepo flushDeltaQueue in background: \(inBackground) with queue: \(deltaQueue)") } var index = 0 @@ -135,7 +146,12 @@ public class OSOperationRepo: NSObject { } for executor in executors { - executor.processDeltaQueue() + executor.processDeltaQueue(inBackground: inBackground) + } + + if (inBackground) { + OSBackgroundTaskManager.endBackgroundTask(OPERATION_REPO_BACKGROUND_TASK) } + } } diff --git a/iOS_SDK/OneSignalSDK/OneSignalOutcomes/Source/OutcomeEvents/OneSignalOutcomeEventsController.h b/iOS_SDK/OneSignalSDK/OneSignalOutcomes/Source/OutcomeEvents/OneSignalOutcomeEventsController.h index 3f1aac2ca..b49257d26 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalOutcomes/Source/OutcomeEvents/OneSignalOutcomeEventsController.h +++ b/iOS_SDK/OneSignalSDK/OneSignalOutcomes/Source/OutcomeEvents/OneSignalOutcomeEventsController.h @@ -52,6 +52,8 @@ appId:(NSString * _Nonnull)appId pushSubscriptionId:(NSString * _Nonnull)pushSubscriptionId onesignalId:(NSString * _Nonnull)onesignalId - influenceParams:(NSArray *_Nonnull)influenceParams; + influenceParams:(NSArray * _Nonnull)influenceParams + onSuccess:(OSResultSuccessBlock _Nonnull)successBlock + onFailure:(OSFailureBlock _Nonnull)failureBlock; @end diff --git a/iOS_SDK/OneSignalSDK/OneSignalOutcomes/Source/OutcomeEvents/OneSignalOutcomeEventsController.m b/iOS_SDK/OneSignalSDK/OneSignalOutcomes/Source/OutcomeEvents/OneSignalOutcomeEventsController.m index e64d4550d..4d319474c 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalOutcomes/Source/OutcomeEvents/OneSignalOutcomeEventsController.m +++ b/iOS_SDK/OneSignalSDK/OneSignalOutcomes/Source/OutcomeEvents/OneSignalOutcomeEventsController.m @@ -108,22 +108,24 @@ - (void)sendSessionEndOutcomes:(NSNumber * _Nonnull)timeElapsed appId:(NSString * _Nonnull)appId pushSubscriptionId:(NSString * _Nonnull)pushSubscriptionId onesignalId:(NSString * _Nonnull)onesignalId - influenceParams:(NSArray * _Nonnull)influenceParams { - // Don't send influenced session with time < 1 seconds - if ([timeElapsed intValue] < 1) { - [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:[NSString stringWithFormat:@"sendSessionEndOutcomes not sending active time %@", timeElapsed]]; - return; - } - // TODO: What to do onSuccess and onFailure + influenceParams:(NSArray * _Nonnull)influenceParams + onSuccess:(OSResultSuccessBlock _Nonnull)successBlock + onFailure:(OSFailureBlock _Nonnull)failureBlock { [OneSignalClient.sharedClient executeRequest:[OSRequestSendSessionEndOutcomes withActiveTime:timeElapsed appId:appId pushSubscriptionId:pushSubscriptionId onesignalId:onesignalId influenceParams:influenceParams] onSuccess:^(NSDictionary *result) { - [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:@"sendSessionEndOutcomes attributed succeed"]; + [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:@"OneSignalOutcomeEventsController:sendSessionEndOutcomes attributed succeed"]; + if (successBlock) { + successBlock(result); + } } onFailure:^(NSError *error) { - [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:@"sendSessionEndOutcomes attributed failed"]; + [OneSignalLog onesignalLog:ONE_S_LL_ERROR message:@"OneSignalOutcomeEventsController:sendSessionEndOutcomes attributed failed"]; + if (failureBlock) { + failureBlock(error); + } }]; } diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityOperationExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityOperationExecutor.swift index bfeb61db7..c49ef5c7b 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityOperationExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityOperationExecutor.swift @@ -103,7 +103,7 @@ class OSIdentityOperationExecutor: OSOperationExecutor { OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_DELTA_QUEUE_KEY, withValue: self.deltaQueue) } - func processDeltaQueue() { + func processDeltaQueue(inBackground: Bool) { if !deltaQueue.isEmpty { OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSIdentityOperationExecutor processDeltaQueue with queue: \(deltaQueue)") } @@ -139,10 +139,10 @@ class OSIdentityOperationExecutor: OSOperationExecutor { OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_DELTA_QUEUE_KEY, withValue: self.deltaQueue) // This should be empty, can remove instead? - processRequestQueue() + processRequestQueue(inBackground: inBackground) } - func processRequestQueue() { + func processRequestQueue(inBackground: Bool) { let requestQueue: [OneSignalRequest] = addRequestQueue + removeRequestQueue if requestQueue.isEmpty { @@ -154,16 +154,16 @@ class OSIdentityOperationExecutor: OSOperationExecutor { return first.timestamp < second.timestamp }) { if request.isKind(of: OSRequestAddAliases.self), let addAliasesRequest = request as? OSRequestAddAliases { - executeAddAliasesRequest(addAliasesRequest) + executeAddAliasesRequest(addAliasesRequest, inBackground: inBackground) } else if request.isKind(of: OSRequestRemoveAlias.self), let removeAliasRequest = request as? OSRequestRemoveAlias { - executeRemoveAliasRequest(removeAliasRequest) + executeRemoveAliasRequest(removeAliasRequest, inBackground: inBackground) } else { OneSignalLog.onesignalLog(.LL_DEBUG, message: "OSIdentityOperationExecutor.processRequestQueue met incompatible OneSignalRequest type: \(request).") } } } - func executeAddAliasesRequest(_ request: OSRequestAddAliases) { + func executeAddAliasesRequest(_ request: OSRequestAddAliases, inBackground: Bool) { guard !request.sentToClient else { return } @@ -174,11 +174,19 @@ class OSIdentityOperationExecutor: OSOperationExecutor { OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSIdentityOperationExecutor: executeAddAliasesRequest making request: \(request)") + let backgroundTaskIdentifier = IDENTITY_EXECUTOR_BACKGROUND_TASK + UUID().uuidString + if (inBackground) { + OSBackgroundTaskManager.beginBackgroundTask(backgroundTaskIdentifier) + } + OneSignalClient.shared().execute(request) { _ in // No hydration from response // On success, remove request from cache self.addRequestQueue.removeAll(where: { $0 == request}) OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_ADD_REQUEST_QUEUE_KEY, withValue: self.addRequestQueue) + if (inBackground) { + OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) + } } onFailure: { error in OneSignalLog.onesignalLog(.LL_ERROR, message: "OSIdentityOperationExecutor add aliases request failed with error: \(error.debugDescription)") if let nsError = error as? NSError { @@ -190,30 +198,27 @@ class OSIdentityOperationExecutor: OSOperationExecutor { // Logout if the user in the SDK is the same guard OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModel) else { + if (inBackground) { + OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) + } return } // The subscription has been deleted along with the user, so remove the subscription_id but keep the same push subscription model - OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModelStore.getModels()[OS_PUSH_SUBSCRIPTION_MODEL_KEY]?.subscriptionId = nil + OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModel?.subscriptionId = nil OneSignalUserManagerImpl.sharedInstance._logout() - } else if responseType == .conflict { - self.addRequestQueue.removeAll(where: { $0 == request}) - OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_ADD_REQUEST_QUEUE_KEY, withValue: self.addRequestQueue) - guard OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModel) - else { - return - } - // Alias(es) already exists on another user, remove from identity model - OneSignalUserManagerImpl.sharedInstance.user.identityModel.removeAliases(Array(request.aliases.keys)) } else if responseType != .retryable { // Fail, no retry, remove from cache and queue self.addRequestQueue.removeAll(where: { $0 == request}) OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_ADD_REQUEST_QUEUE_KEY, withValue: self.addRequestQueue) } } + if (inBackground) { + OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) + } } } - func executeRemoveAliasRequest(_ request: OSRequestRemoveAlias) { + func executeRemoveAliasRequest(_ request: OSRequestRemoveAlias, inBackground: Bool) { guard !request.sentToClient else { return } @@ -224,11 +229,19 @@ class OSIdentityOperationExecutor: OSOperationExecutor { OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSIdentityOperationExecutor: executeRemoveAliasRequest making request: \(request)") + let backgroundTaskIdentifier = IDENTITY_EXECUTOR_BACKGROUND_TASK + UUID().uuidString + if (inBackground) { + OSBackgroundTaskManager.beginBackgroundTask(backgroundTaskIdentifier) + } + OneSignalClient.shared().execute(request) { _ in // There is nothing to hydrate // On success, remove request from cache self.removeRequestQueue.removeAll(where: { $0 == request}) OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY, withValue: self.removeRequestQueue) + if (inBackground) { + OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) + } } onFailure: { error in OneSignalLog.onesignalLog(.LL_ERROR, message: "OSIdentityOperationExecutor remove alias request failed with error: \(error.debugDescription)") @@ -241,6 +254,9 @@ class OSIdentityOperationExecutor: OSOperationExecutor { OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY, withValue: self.removeRequestQueue) } } + if (inBackground) { + OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) + } } } } diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSPropertiesModel.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSPropertiesModel.swift index 8ab2b47bc..ce35d9eb2 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSPropertiesModel.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSPropertiesModel.swift @@ -78,7 +78,9 @@ class OSPropertiesModel: OSModel { self.set(property: "location", newValue: location) } } - + + var timezoneId = TimeZone.current.identifier + var tags: [String: String] = [:] // MARK: - Initialization diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSPropertyOperationExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSPropertyOperationExecutor.swift index 6d2062108..3c3e5b2b9 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSPropertyOperationExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSPropertyOperationExecutor.swift @@ -86,7 +86,7 @@ class OSPropertyOperationExecutor: OSOperationExecutor { OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_PROPERTIES_EXECUTOR_DELTA_QUEUE_KEY, withValue: self.deltaQueue) } - func processDeltaQueue() { + func processDeltaQueue(inBackground: Bool) { if !deltaQueue.isEmpty { OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSPropertyOperationExecutor processDeltaQueue with queue: \(deltaQueue)") } @@ -111,20 +111,20 @@ class OSPropertyOperationExecutor: OSOperationExecutor { OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_PROPERTIES_EXECUTOR_UPDATE_REQUEST_QUEUE_KEY, withValue: self.updateRequestQueue) OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_PROPERTIES_EXECUTOR_DELTA_QUEUE_KEY, withValue: self.deltaQueue) // This should be empty, can remove instead? - processRequestQueue() + processRequestQueue(inBackground: inBackground) } - func processRequestQueue() { + func processRequestQueue(inBackground: Bool) { if updateRequestQueue.isEmpty { return } for request in updateRequestQueue { - executeUpdatePropertiesRequest(request) + executeUpdatePropertiesRequest(request, inBackground: inBackground) } } - func executeUpdatePropertiesRequest(_ request: OSRequestUpdateProperties) { + func executeUpdatePropertiesRequest(_ request: OSRequestUpdateProperties, inBackground: Bool) { guard !request.sentToClient else { return } @@ -132,12 +132,20 @@ class OSPropertyOperationExecutor: OSOperationExecutor { return } request.sentToClient = true - + + let backgroundTaskIdentifier = PROPERTIES_EXECUTOR_BACKGROUND_TASK + UUID().uuidString + if (inBackground) { + OSBackgroundTaskManager.beginBackgroundTask(backgroundTaskIdentifier) + } + OneSignalClient.shared().execute(request) { _ in // On success, remove request from cache, and we do need to hydrate // TODO: We need to hydrate after all ? What why ? self.updateRequestQueue.removeAll(where: { $0 == request}) OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_PROPERTIES_EXECUTOR_UPDATE_REQUEST_QUEUE_KEY, withValue: self.updateRequestQueue) + if (inBackground) { + OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) + } } onFailure: { error in OneSignalLog.onesignalLog(.LL_ERROR, message: "OSPropertyOperationExecutor update properties request failed with error: \(error.debugDescription)") if let nsError = error as? NSError { @@ -149,10 +157,13 @@ class OSPropertyOperationExecutor: OSOperationExecutor { // Logout if the user in the SDK is the same guard OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModel) else { + if (inBackground) { + OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) + } return } // The subscription has been deleted along with the user, so remove the subscription_id but keep the same push subscription model - OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModelStore.getModels()[OS_PUSH_SUBSCRIPTION_MODEL_KEY]?.subscriptionId = nil + OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModel?.subscriptionId = nil OneSignalUserManagerImpl.sharedInstance._logout() } else if responseType != .retryable { // Fail, no retry, remove from cache and queue @@ -160,13 +171,16 @@ class OSPropertyOperationExecutor: OSOperationExecutor { OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_PROPERTIES_EXECUTOR_UPDATE_REQUEST_QUEUE_KEY, withValue: self.updateRequestQueue) } } + if (inBackground) { + OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) + } } } } extension OSPropertyOperationExecutor { // TODO: We can make this go through the operation repo - func updateProperties(propertiesDeltas: OSPropertiesDeltas, refreshDeviceMetadata: Bool?, propertiesModel: OSPropertiesModel, identityModel: OSIdentityModel) { + func updateProperties(propertiesDeltas: OSPropertiesDeltas, refreshDeviceMetadata: Bool, propertiesModel: OSPropertiesModel, identityModel: OSIdentityModel, sendImmediately: Bool = false, onSuccess: (() -> Void)? = nil, onFailure: (() -> Void)? = nil) { let request = OSRequestUpdateProperties( properties: [:], @@ -175,7 +189,20 @@ extension OSPropertyOperationExecutor { modelToUpdate: propertiesModel, identityModel: identityModel) - updateRequestQueue.append(request) - OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_PROPERTIES_EXECUTOR_UPDATE_REQUEST_QUEUE_KEY, withValue: self.updateRequestQueue) + if (sendImmediately) { + // Bypass the request queues + OneSignalClient.shared().execute(request) { _ in + if let onSuccess = onSuccess { + onSuccess() + } + } onFailure: { _ in + if let onFailure = onFailure { + onFailure() + } + } + } else { + updateRequestQueue.append(request) + OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_PROPERTIES_EXECUTOR_UPDATE_REQUEST_QUEUE_KEY, withValue: self.updateRequestQueue) + } } } diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSSubscriptionModel.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSSubscriptionModel.swift index 4bfdbbcfa..a99272a02 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSSubscriptionModel.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSSubscriptionModel.swift @@ -195,12 +195,59 @@ class OSSubscriptionModel: OSModel { } // Properties for push subscription - var testType: Int? - let deviceOs = UIDevice.current.systemVersion - let sdk = ONESIGNAL_VERSION - let deviceModel: String? = OSDeviceUtils.getDeviceVariant() - let appVersion: String? = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String - let netType: Int? = OSNetworkingUtils.getNetType() as? Int + var testType: Int? { + didSet { + guard testType != oldValue else { + return + } + self.set(property: "testType", newValue: testType) + } + } + + var deviceOs = UIDevice.current.systemVersion { + didSet { + guard deviceOs != oldValue else { + return + } + self.set(property: "deviceOs", newValue: deviceOs) + } + } + + var sdk = ONESIGNAL_VERSION { + didSet { + guard sdk != oldValue else { + return + } + self.set(property: "sdk", newValue: sdk) + } + } + + var deviceModel: String? = OSDeviceUtils.getDeviceVariant() { + didSet { + guard deviceModel != oldValue else { + return + } + self.set(property: "deviceModel", newValue: deviceModel) + } + } + + var appVersion: String? = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String { + didSet { + guard appVersion != oldValue else { + return + } + self.set(property: "appVersion", newValue: appVersion) + } + } + + var netType: Int? = OSNetworkingUtils.getNetType() as? Int { + didSet { + guard netType != oldValue else { + return + } + self.set(property: "netType", newValue: netType) + } + } // When a Subscription is initialized, it may not have a subscriptionId until a request to the backend is made. init(type: OSSubscriptionType, @@ -215,7 +262,7 @@ class OSSubscriptionModel: OSModel { _reachable = reachable _isDisabled = isDisabled - // Set test_type if subscription model is PUSH + // Set test_type if subscription model is PUSH, and update notificationTypes if type == .push { let releaseMode: OSUIApplicationReleaseMode = OneSignalMobileProvision.releaseMode() // Workaround to unsure how to extract the Int value in 1 step... @@ -228,6 +275,7 @@ class OSSubscriptionModel: OSModel { if releaseMode == .UIApplicationReleaseWildcard { self.testType = OSUIApplicationReleaseMode.UIApplicationReleaseWildcard.rawValue } + notificationTypes = Int(OSNotificationsManager.getNotificationTypes(_isDisabled)) } super.init(changeNotifier: changeNotifier) @@ -242,6 +290,11 @@ class OSSubscriptionModel: OSModel { coder.encode(_isDisabled, forKey: "_isDisabled") coder.encode(notificationTypes, forKey: "notificationTypes") coder.encode(testType, forKey: "testType") + coder.encode(deviceOs, forKey: "deviceOs") + coder.encode(sdk, forKey: "sdk") + coder.encode(deviceModel, forKey: "deviceModel") + coder.encode(appVersion, forKey: "appVersion") + coder.encode(netType, forKey: "netType") } required init?(coder: NSCoder) { @@ -259,6 +312,12 @@ class OSSubscriptionModel: OSModel { self._isDisabled = coder.decodeBool(forKey: "_isDisabled") self.notificationTypes = coder.decodeInteger(forKey: "notificationTypes") self.testType = coder.decodeObject(forKey: "testType") as? Int + self.deviceOs = coder.decodeObject(forKey: "deviceOs") as? String ?? UIDevice.current.systemVersion + self.sdk = coder.decodeObject(forKey: "sdk") as? String ?? ONESIGNAL_VERSION + self.deviceModel = coder.decodeObject(forKey: "deviceModel") as? String + self.appVersion = coder.decodeObject(forKey: "appVersion") as? String + self.netType = coder.decodeObject(forKey: "netType") as? Int + super.init(coder: coder) } @@ -290,6 +349,26 @@ class OSSubscriptionModel: OSModel { } } } + + // Using snake_case so we can use this in request bodies + public func jsonRepresentation() -> [String: Any] { + var json: [String: Any] = [:] + json["id"] = self.subscriptionId + json["type"] = self.type.rawValue + json["token"] = self.address + json["enabled"] = self.enabled + json["test_type"] = self.testType + json["device_os"] = self.deviceOs + json["sdk"] = self.sdk + json["device_model"] = self.deviceModel + json["app_version"] = self.appVersion + json["net_type"] = self.netType + // notificationTypes defaults to -1 instead of nil, don't send if it's -1 + if self.notificationTypes != -1 { + json["notification_types"] = self.notificationTypes + } + return json + } } // Push Subscription related @@ -317,7 +396,32 @@ extension OSSubscriptionModel { func updateNotificationTypes() { notificationTypes = Int(OSNotificationsManager.getNotificationTypes(_isDisabled)) } - + + func updateTestType() { + let releaseMode: OSUIApplicationReleaseMode = OneSignalMobileProvision.releaseMode() + // Workaround to unsure how to extract the Int value in 1 step... + if releaseMode == .UIApplicationReleaseDev { + self.testType = OSUIApplicationReleaseMode.UIApplicationReleaseDev.rawValue + } + if releaseMode == .UIApplicationReleaseAdHoc { + self.testType = OSUIApplicationReleaseMode.UIApplicationReleaseAdHoc.rawValue + } + if releaseMode == .UIApplicationReleaseWildcard { + self.testType = OSUIApplicationReleaseMode.UIApplicationReleaseWildcard.rawValue + } + } + + func update() { + updateTestType() + deviceOs = UIDevice.current.systemVersion + sdk = ONESIGNAL_VERSION + deviceModel = OSDeviceUtils.getDeviceVariant() + appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String + netType = OSNetworkingUtils.getNetType() as? Int + // sdkType ?? + // isRooted ?? + } + enum OSPushPropertyChanged { case subscriptionId(String?) case reachable(Bool) @@ -325,7 +429,6 @@ extension OSSubscriptionModel { case address(String?) } - // TODO: Fix when isDisabled is set to true, the push subscription observer is not fired due to known bug. func firePushSubscriptionChanged(_ changedProperty: OSPushPropertyChanged) { var prevIsOptedIn = true var prevIsEnabled = true diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSSubscriptionOperationExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSSubscriptionOperationExecutor.swift index f79ced992..0541ba27c 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSSubscriptionOperationExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSSubscriptionOperationExecutor.swift @@ -152,7 +152,7 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_SUBSCRIPTION_EXECUTOR_DELTA_QUEUE_KEY, withValue: self.deltaQueue) } - func processDeltaQueue() { + func processDeltaQueue(inBackground: Bool) { if !deltaQueue.isEmpty { OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSSubscriptionOperationExecutor processDeltaQueue with queue: \(deltaQueue)") } @@ -198,10 +198,17 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_SUBSCRIPTION_EXECUTOR_DELTA_QUEUE_KEY, withValue: self.deltaQueue) // This should be empty, can remove instead? - processRequestQueue() + processRequestQueue(inBackground: inBackground) } - func processRequestQueue() { + // Bypasses the operation repo to create a push subscription request + func createPushSubscription(subscriptionModel: OSSubscriptionModel, identityModel: OSIdentityModel) { + let request = OSRequestCreateSubscription(subscriptionModel: subscriptionModel, identityModel: identityModel) + addRequestQueue.append(request) + OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_SUBSCRIPTION_EXECUTOR_ADD_REQUEST_QUEUE_KEY, withValue: self.addRequestQueue) + } + + func processRequestQueue(inBackground: Bool) { let requestQueue: [OneSignalRequest] = addRequestQueue + removeRequestQueue + updateRequestQueue if requestQueue.isEmpty { @@ -213,18 +220,18 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { return first.timestamp < second.timestamp }) { if request.isKind(of: OSRequestCreateSubscription.self), let createSubscriptionRequest = request as? OSRequestCreateSubscription { - executeCreateSubscriptionRequest(createSubscriptionRequest) + executeCreateSubscriptionRequest(createSubscriptionRequest, inBackground: inBackground) } else if request.isKind(of: OSRequestDeleteSubscription.self), let deleteSubscriptionRequest = request as? OSRequestDeleteSubscription { - executeDeleteSubscriptionRequest(deleteSubscriptionRequest) + executeDeleteSubscriptionRequest(deleteSubscriptionRequest, inBackground: inBackground) } else if request.isKind(of: OSRequestUpdateSubscription.self), let updateSubscriptionRequest = request as? OSRequestUpdateSubscription { - executeUpdateSubscriptionRequest(updateSubscriptionRequest) + executeUpdateSubscriptionRequest(updateSubscriptionRequest, inBackground: inBackground) } else { OneSignalLog.onesignalLog(.LL_DEBUG, message: "OSSubscriptionOperationExecutor.processRequestQueue met incompatible OneSignalRequest type: \(request).") } } } - func executeCreateSubscriptionRequest(_ request: OSRequestCreateSubscription) { + func executeCreateSubscriptionRequest(_ request: OSRequestCreateSubscription, inBackground: Bool) { guard !request.sentToClient else { return } @@ -233,6 +240,11 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { } request.sentToClient = true + let backgroundTaskIdentifier = SUBSCRIPTION_EXECUTOR_BACKGROUND_TASK + UUID().uuidString + if (inBackground) { + OSBackgroundTaskManager.beginBackgroundTask(backgroundTaskIdentifier) + } + OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSSubscriptionOperationExecutor: executeCreateSubscriptionRequest making request: \(request)") OneSignalClient.shared().execute(request) { result in // On success, remove request from cache (even if not hydrating model), and hydrate model @@ -241,9 +253,15 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { guard let response = result?["subscription"] as? [String: Any] else { OneSignalLog.onesignalLog(.LL_ERROR, message: "Unabled to parse response to create subscription request") + if (inBackground) { + OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) + } return } request.subscriptionModel.hydrate(response) + if (inBackground) { + OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) + } } onFailure: { error in OneSignalLog.onesignalLog(.LL_ERROR, message: "OSSubscriptionOperationExecutor create subscription request failed with error: \(error.debugDescription)") if let nsError = error as? NSError { @@ -254,10 +272,13 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { // Logout if the user in the SDK is the same guard OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModel) else { + if (inBackground) { + OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) + } return } // The subscription has been deleted along with the user, so remove the subscription_id but keep the same push subscription model - OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModelStore.getModels()[OS_PUSH_SUBSCRIPTION_MODEL_KEY]?.subscriptionId = nil + OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModel?.subscriptionId = nil OneSignalUserManagerImpl.sharedInstance._logout() } else if responseType != .retryable { // Fail, no retry, remove from cache and queue @@ -265,10 +286,13 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_SUBSCRIPTION_EXECUTOR_ADD_REQUEST_QUEUE_KEY, withValue: self.addRequestQueue) } } + if (inBackground) { + OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) + } } } - func executeDeleteSubscriptionRequest(_ request: OSRequestDeleteSubscription) { + func executeDeleteSubscriptionRequest(_ request: OSRequestDeleteSubscription, inBackground: Bool) { guard !request.sentToClient else { return } @@ -276,16 +300,22 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { return } request.sentToClient = true - + + let backgroundTaskIdentifier = SUBSCRIPTION_EXECUTOR_BACKGROUND_TASK + UUID().uuidString + if (inBackground) { + OSBackgroundTaskManager.beginBackgroundTask(backgroundTaskIdentifier) + } + // This request can be executed as-is. OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSSubscriptionOperationExecutor: executeDeleteSubscriptionRequest making request: \(request)") OneSignalClient.shared().execute(request) { _ in - // On success, remove request from cache. No model hydration occurs. // For example, if app restarts and we read in operations between sending this off and getting the response self.removeRequestQueue.removeAll(where: { $0 == request}) OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_SUBSCRIPTION_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY, withValue: self.removeRequestQueue) - + if (inBackground) { + OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) + } } onFailure: { error in OneSignalLog.onesignalLog(.LL_ERROR, message: "OSSubscriptionOperationExecutor delete subscription request failed with error: \(error.debugDescription)") if let nsError = error as? NSError { @@ -297,10 +327,13 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_SUBSCRIPTION_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY, withValue: self.removeRequestQueue) } } + if (inBackground) { + OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) + } } } - func executeUpdateSubscriptionRequest(_ request: OSRequestUpdateSubscription) { + func executeUpdateSubscriptionRequest(_ request: OSRequestUpdateSubscription, inBackground: Bool) { guard !request.sentToClient else { return } @@ -309,15 +342,19 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { } request.sentToClient = true - OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSSubscriptionOperationExecutor: executeUpdateSubscriptionRequest making request: \(request)") - + let backgroundTaskIdentifier = SUBSCRIPTION_EXECUTOR_BACKGROUND_TASK + UUID().uuidString + if (inBackground) { + OSBackgroundTaskManager.beginBackgroundTask(backgroundTaskIdentifier) + } + OneSignalClient.shared().execute(request) { _ in - // On success, remove request from cache. No model hydration occurs. // For example, if app restarts and we read in operations between sending this off and getting the response self.updateRequestQueue.removeAll(where: { $0 == request}) OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_SUBSCRIPTION_EXECUTOR_UPDATE_REQUEST_QUEUE_KEY, withValue: self.updateRequestQueue) - + if (inBackground) { + OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) + } } onFailure: { error in OneSignalLog.onesignalLog(.LL_ERROR, message: "OSSubscriptionOperationExecutor update subscription request failed with error: \(error.debugDescription)") if let nsError = error as? NSError { @@ -328,6 +365,9 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_SUBSCRIPTION_EXECUTOR_UPDATE_REQUEST_QUEUE_KEY, withValue: self.updateRequestQueue) } } + if (inBackground) { + OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) + } } } } diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSUserRequests.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSUserRequests.swift index b1ce9af89..0c4f3f332 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSUserRequests.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSUserRequests.swift @@ -191,22 +191,18 @@ class OSUserExecutor { identityModel.hydrate(identityObject) } - // On success, check if the current user is the same as the one in the request - // If user has changed, don't hydrate, except for push subscription - let modelInStore = OneSignalUserManagerImpl.sharedInstance.identityModelStore.getModel(key: OS_IDENTITY_MODEL_KEY) - // TODO: Determine how to hydrate the push subscription, which is still faulty. // Hydrate by token if sub_id exists? // Problem: a user can have multiple iOS push subscription, and perhaps missing token // Ideally we only get push subscription for this device in the response, not others // Hydrate the push subscription if we don't already have a subscription ID AND token matches the original request - if (OneSignalUserManagerImpl.sharedInstance.user.pushSubscriptionModel.subscriptionId == nil), + if (OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModel?.subscriptionId == nil), let subscriptionObject = parseSubscriptionObjectResponse(response) { for subModel in subscriptionObject { if subModel["type"] as? String == "iOSPush", areTokensEqual(tokenA: originalPushToken, tokenB: subModel["token"] as? String) { // response may have "" token or no token - OneSignalUserManagerImpl.sharedInstance.user.pushSubscriptionModel.hydrate(subModel) + OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModel?.hydrate(subModel) if let subId = subModel["id"] as? String { OSNotificationsManager.setPushSubscriptionId(subId) } @@ -214,8 +210,10 @@ class OSUserExecutor { } } } - - guard modelInStore?.modelId == identityModel.modelId else { + + // Check if the current user is the same as the one in the request + // If user has changed, don't hydrate, except for push subscription above + guard OneSignalUserManagerImpl.sharedInstance.isCurrentUser(identityModel) else { return } @@ -328,17 +326,21 @@ class OSUserExecutor { executePendingRequests() } } + OSOperationRepo.sharedInstance.paused = false } onFailure: { error in OneSignalLog.onesignalLog(.LL_ERROR, message: "OSUserExecutor create user request failed with error: \(error.debugDescription)") if let nsError = error as? NSError { let responseType = OSNetworkingUtils.getResponseStatusType(nsError.code) if responseType != .retryable { - // Fail, no retry, remove from cache and queue - // TODO: This leaves the SDK in a bad state, revisit why this can happen - removeFromQueue(request) + // A failed create user request would leave the SDK in a bad state + // Don't remove the request from cache and pause the operation repo + // We will retry this request on a new session + OSOperationRepo.sharedInstance.paused = true + request.sentToClient = false } + } else { + executePendingRequests() } - executePendingRequests() } } @@ -384,7 +386,7 @@ class OSUserExecutor { let responseType = OSNetworkingUtils.getResponseStatusType(nsError.code) if responseType != .retryable { // Fail, no retry, remove the subscription_id but keep the same push subscription model - OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModelStore.getModels()[OS_PUSH_SUBSCRIPTION_MODEL_KEY]?.subscriptionId = nil + OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModel?.subscriptionId = nil removeFromQueue(request) } } @@ -453,7 +455,7 @@ class OSUserExecutor { return } // The subscription has been deleted along with the user, so remove the subscription_id but keep the same push subscription model - OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModelStore.getModels()[OS_PUSH_SUBSCRIPTION_MODEL_KEY]?.subscriptionId = nil + OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModel?.subscriptionId = nil OneSignalUserManagerImpl.sharedInstance._logout() } } else { @@ -504,8 +506,8 @@ class OSUserExecutor { } } - static func fetchUser(aliasLabel: String, aliasId: String, identityModel: OSIdentityModel) { - let request = OSRequestFetchUser(identityModel: identityModel, aliasLabel: aliasLabel, aliasId: aliasId) + static func fetchUser(aliasLabel: String, aliasId: String, identityModel: OSIdentityModel, onNewSession: Bool = false) { + let request = OSRequestFetchUser(identityModel: identityModel, aliasLabel: aliasLabel, aliasId: aliasId, onNewSession: onNewSession) appendToQueue(request) @@ -528,6 +530,28 @@ class OSUserExecutor { // Clear local data in preparation for hydration OneSignalUserManagerImpl.sharedInstance.clearUserData() parseFetchUserResponse(response: response, identityModel: request.identityModel, originalPushToken: OneSignalUserManagerImpl.sharedInstance.token) + + // If this is a on-new-session's fetch user call, check that the subscription still exists + if request.onNewSession, + OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModel), + let subId = OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModel?.subscriptionId, + let subscriptionObjects = parseSubscriptionObjectResponse(response) + { + var subscriptionExists = false + for subModel in subscriptionObjects { + if subModel["id"] as? String == subId { + subscriptionExists = true + break + } + } + + if !subscriptionExists { + // This subscription probably has been deleted + OneSignalLog.onesignalLog(.LL_ERROR, message: "OSUserExecutor.executeFetchUserRequest found this device's push subscription gone, now send the push subscription to server.") + OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModel?.subscriptionId = nil + OneSignalUserManagerImpl.sharedInstance.createPushSubscriptionRequest() + } + } } executePendingRequests() } onFailure: { error in @@ -542,7 +566,7 @@ class OSUserExecutor { return } // The subscription has been deleted along with the user, so remove the subscription_id but keep the same push subscription model - OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModelStore.getModels()[OS_PUSH_SUBSCRIPTION_MODEL_KEY]?.subscriptionId = nil + OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModel?.subscriptionId = nil OneSignalUserManagerImpl.sharedInstance._logout() } else if responseType != .retryable { // If the error is not retryable, remove from cache and queue @@ -578,7 +602,7 @@ class OSRequestCreateUser: OneSignalRequest, OSUserRequest { var identityModel: OSIdentityModel var pushSubscriptionModel: OSSubscriptionModel - let originalPushToken: String? + var originalPushToken: String? func prepareForExecution() -> Bool { guard let appId = OneSignalConfigManager.getAppId() else { @@ -594,23 +618,8 @@ class OSRequestCreateUser: OneSignalRequest, OSUserRequest { // When reading from the cache, update the push subscription model func updatePushSubscriptionModel(_ pushSubscriptionModel: OSSubscriptionModel) { self.pushSubscriptionModel = pushSubscriptionModel - // Push Subscription Object - var pushSubscriptionObject: [String: Any] = [:] - pushSubscriptionObject["id"] = pushSubscriptionModel.subscriptionId - pushSubscriptionObject["type"] = pushSubscriptionModel.type.rawValue - pushSubscriptionObject["token"] = pushSubscriptionModel.address - pushSubscriptionObject["enabled"] = pushSubscriptionModel.enabled - pushSubscriptionObject["test_type"] = pushSubscriptionModel.testType - pushSubscriptionObject["device_os"] = pushSubscriptionModel.deviceOs - pushSubscriptionObject["sdk"] = pushSubscriptionModel.sdk - pushSubscriptionObject["device_model"] = pushSubscriptionModel.deviceModel - pushSubscriptionObject["app_version"] = pushSubscriptionModel.appVersion - pushSubscriptionObject["net_type"] = pushSubscriptionModel.netType - // notificationTypes defaults to -1 instead of nil, don't send if it's -1 - if pushSubscriptionModel.notificationTypes != -1 { - pushSubscriptionObject["notification_types"] = pushSubscriptionModel.notificationTypes - } - self.parameters?["subscriptions"] = [pushSubscriptionObject] + self.parameters?["subscriptions"] = [pushSubscriptionModel.jsonRepresentation()] + self.originalPushToken = pushSubscriptionModel.address } init(identityModel: OSIdentityModel, propertiesModel: OSPropertiesModel, pushSubscriptionModel: OSSubscriptionModel, originalPushToken: String?) { @@ -631,6 +640,7 @@ class OSRequestCreateUser: OneSignalRequest, OSUserRequest { // Properties Object var propertiesObject: [String: Any] = [:] propertiesObject["language"] = propertiesModel.language + propertiesObject["timezone_id"] = propertiesModel.timezoneId params["properties"] = propertiesObject self.parameters = params @@ -836,7 +846,8 @@ class OSRequestFetchUser: OneSignalRequest, OSUserRequest { let identityModel: OSIdentityModel let aliasLabel: String let aliasId: String - + let onNewSession: Bool + func prepareForExecution() -> Bool { guard let appId = OneSignalConfigManager.getAppId() else { OneSignalLog.onesignalLog(.LL_DEBUG, message: "Cannot generate the fetch user request due to null app ID.") @@ -847,10 +858,11 @@ class OSRequestFetchUser: OneSignalRequest, OSUserRequest { return true } - init(identityModel: OSIdentityModel, aliasLabel: String, aliasId: String) { + init(identityModel: OSIdentityModel, aliasLabel: String, aliasId: String, onNewSession: Bool) { self.identityModel = identityModel self.aliasLabel = aliasLabel self.aliasId = aliasId + self.onNewSession = onNewSession self.stringDescription = "OSRequestFetchUser with aliasLabel: \(aliasLabel) aliasId: \(aliasId)" super.init() self.method = GET @@ -861,6 +873,7 @@ class OSRequestFetchUser: OneSignalRequest, OSUserRequest { coder.encode(aliasLabel, forKey: "aliasLabel") coder.encode(aliasId, forKey: "aliasId") coder.encode(identityModel, forKey: "identityModel") + coder.encode(onNewSession, forKey: "onNewSession") coder.encode(method.rawValue, forKey: "method") // Encodes as String coder.encode(timestamp, forKey: "timestamp") } @@ -879,6 +892,7 @@ class OSRequestFetchUser: OneSignalRequest, OSUserRequest { self.identityModel = identityModel self.aliasLabel = aliasLabel self.aliasId = aliasId + self.onNewSession = coder.decodeBool(forKey: "onNewSession") self.stringDescription = "OSRequestFetchUser with aliasLabel: \(aliasLabel) aliasId: \(aliasId)" super.init() self.method = HTTPMethod(rawValue: rawMethod) @@ -1107,8 +1121,9 @@ class OSRequestUpdateProperties: OneSignalRequest, OSUserRequest { } /** - Current uses of this request are for adding Email and SMS subscriptions. Push subscriptions won't be created using - this request because they will be created with ``OSRequestCreateUser``. + Primary uses of this request are for adding Email and SMS subscriptions. Push subscriptions typically won't be created using + this request because they will be created with ``OSRequestCreateUser``. However, if we detect that this device's + push subscription is ever deleted, we will make a request to create it again. */ class OSRequestCreateSubscription: OneSignalRequest, OSUserRequest { var sentToClient = false @@ -1137,15 +1152,7 @@ class OSRequestCreateSubscription: OneSignalRequest, OSUserRequest { self.identityModel = identityModel self.stringDescription = "OSRequestCreateSubscription with subscriptionModel: \(subscriptionModel.address ?? "nil")" super.init() - - var subscriptionParams: [String: Any] = [:] - subscriptionParams["type"] = subscriptionModel.type.rawValue - subscriptionParams["token"] = subscriptionModel.address - // 1/5/2023: For email and SMS, either send `enabled` AND `notification_types` or don't send either - // TODO: ^ Backend changes may require us to come back and change this request's payload. - // TODO: Since this is not used for push, don't send either of those. Revisit if we ever create push subscriptions with this request. - - self.parameters = ["subscription": subscriptionParams] + self.parameters = ["subscription": subscriptionModel.jsonRepresentation()] self.method = POST _ = prepareForExecution() // sets the path property } diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUserManagerImpl.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUserManagerImpl.swift index b84760068..cf694a1b4 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUserManagerImpl.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUserManagerImpl.swift @@ -94,6 +94,15 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager { return _user?.identityModel.onesignalId } + /** + Convenience accessor. We access the push subscription model via the model store instead of via`user.pushSubscriptionModel`. + If privacy consent is set in a wrong order, we may have sent requests, but hydrate on a mock user. + However, we want to set tokens and subscription ID on the actual push subscription model. + */ + var pushSubscriptionModel: OSSubscriptionModel? { + return pushSubscriptionModelStore.getModel(key: OS_PUSH_SUBSCRIPTION_MODEL_KEY) + } + @objc public var pushSubscriptionId: String? { return _user?.pushSubscriptionModel.subscriptionId } @@ -185,11 +194,14 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager { OSNotificationsManager.delegate = self + var hasCachedUser = false + // Path 1. Load user from cache, if any // Corrupted state if any of these models exist without the others if let identityModel = identityModelStore.getModels()[OS_IDENTITY_MODEL_KEY], let propertiesModel = propertiesModelStore.getModels()[OS_PROPERTIES_MODEL_KEY], let pushSubscription = pushSubscriptionModelStore.getModels()[OS_PUSH_SUBSCRIPTION_MODEL_KEY] { + hasCachedUser = true _user = OSUserInternalImpl(identityModel: identityModel, propertiesModel: propertiesModel, pushSubscriptionModel: pushSubscription) OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OneSignalUserManager.start called, loaded the user from cache.") } @@ -227,6 +239,9 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager { propertiesModelStoreListener.start() subscriptionModelStoreListener.start() pushSubscriptionModelStoreListener.start() + if hasCachedUser { + _user?.pushSubscriptionModel.update() + } } @objc @@ -439,6 +454,17 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager { changeNotifier: OSEventProducer()) } + func createPushSubscriptionRequest() { + // subscriptionExecutor should exist as this should be called after `start()` has been called + if let subscriptionExecutor = self.subscriptionExecutor, + let subscriptionModel = pushSubscriptionModel + { + subscriptionExecutor.createPushSubscription(subscriptionModel: subscriptionModel, identityModel: user.identityModel) + } else { + OneSignalLog.onesignalLog(.LL_ERROR, message: "OneSignalUserManagerImpl.createPushSubscriptionRequest cannot be executed due to missing subscriptionExecutor.") + } + } + @objc public func getTags() -> [String: String]? { guard let user = _user else { @@ -509,19 +535,24 @@ extension OneSignalUserManagerImpl { return } start() - + + OSUserExecutor.executePendingRequests() + OSOperationRepo.sharedInstance.paused = false updateSession(sessionCount: 1, sessionTime: nil, refreshDeviceMetadata: true) // Fetch the user's data if there is a onesignal_id // TODO: What if onesignal_id is missing, because we may init a user from cache but it may be missing onesignal_id. Is this ok. if let onesignalId = onesignalId { - OSUserExecutor.fetchUser(aliasLabel: OS_ONESIGNAL_ID, aliasId: onesignalId, identityModel: user.identityModel) + OSUserExecutor.fetchUser(aliasLabel: OS_ONESIGNAL_ID, aliasId: onesignalId, identityModel: user.identityModel, onNewSession: true) } } @objc - public func updateSession(sessionCount: NSNumber?, sessionTime: NSNumber?, refreshDeviceMetadata: Bool) { + public func updateSession(sessionCount: NSNumber?, sessionTime: NSNumber?, refreshDeviceMetadata: Bool, sendImmediately: Bool = false, onSuccess: (() -> Void)? = nil, onFailure: (() -> Void)? = nil) { guard !OneSignalConfigManager.shouldAwaitAppIdAndLogMissingPrivacyConsent(forMethod: nil) else { + if let onFailure = onFailure { + onFailure() + } return } @@ -536,10 +567,16 @@ extension OneSignalUserManagerImpl { propertiesDeltas: propertiesDeltas, refreshDeviceMetadata: refreshDeviceMetadata, propertiesModel: propertiesModel, - identityModel: identityModel + identityModel: identityModel, + sendImmediately: sendImmediately, + onSuccess: onSuccess, + onFailure: onFailure ) } else { OneSignalLog.onesignalLog(.LL_ERROR, message: "OneSignalUserManagerImpl.updateSession with sessionCount: \(String(describing: sessionCount)) sessionTime: \(String(describing: sessionTime)) cannot be executed due to missing property executor.") + if let onFailure = onFailure { + onFailure() + } } } @@ -549,12 +586,7 @@ extension OneSignalUserManagerImpl { */ @objc public func runBackgroundTasks() { - // TODO: Test background behavior - // Can't end background task until the server calls return - OSBackgroundTaskManager.beginBackgroundTask(USER_MANAGER_BACKGROUND_TASK) - // dispatch_async ? - OSOperationRepo.sharedInstance.flushDeltaQueue() - OSBackgroundTaskManager.endBackgroundTask(USER_MANAGER_BACKGROUND_TASK) + OSOperationRepo.sharedInstance.flushDeltaQueue(inBackground: true) } } diff --git a/iOS_SDK/OneSignalSDK/Source/OSAttributedFocusTimeProcessor.m b/iOS_SDK/OneSignalSDK/Source/OSAttributedFocusTimeProcessor.m index f9904643c..1efd0e931 100644 --- a/iOS_SDK/OneSignalSDK/Source/OSAttributedFocusTimeProcessor.m +++ b/iOS_SDK/OneSignalSDK/Source/OSAttributedFocusTimeProcessor.m @@ -28,9 +28,10 @@ #import #import "OneSignalFramework.h" #import "OSAttributedFocusTimeProcessor.h" +#import @interface OneSignal () -+ (BOOL)sendSessionEndOutcomes:(NSNumber*)totalTimeActive params:(OSFocusCallParams *)params; ++ (void)sendSessionEndOutcomes:(NSNumber*)totalTimeActive params:(OSFocusCallParams *)params onSuccess:(OSResultSuccessBlock _Nonnull)successBlock onFailure:(OSFailureBlock _Nonnull)failureBlock; @end @implementation OSAttributedFocusTimeProcessor { @@ -43,6 +44,8 @@ @implementation OSAttributedFocusTimeProcessor { - (instancetype)init { self = [super init]; [OSBackgroundTaskManager setTaskInvalid:ATTRIBUTED_FOCUS_TASK]; + [OSBackgroundTaskManager setTaskInvalid:SEND_SESSION_TIME_TO_USER_TASK]; + return self; } @@ -73,38 +76,58 @@ - (void)sendUnsentActiveTime:(OSFocusCallParams *)params { } - (void)sendOnFocusCallWithParams:(OSFocusCallParams *)params totalTimeActive:(NSTimeInterval)totalTimeActive { + // Don't send influenced session with time < 1 seconds + if (totalTimeActive < 1) { + [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:[NSString stringWithFormat:@"sendSessionEndOutcomes not sending active time %f", totalTimeActive]]; + return; + } + [OSBackgroundTaskManager beginBackgroundTask:ATTRIBUTED_FOCUS_TASK]; + [OSBackgroundTaskManager beginBackgroundTask:SEND_SESSION_TIME_TO_USER_TASK]; if (params.onSessionEnded) { - [self sendBackgroundAttributedFocusPingWithParams:params withTotalTimeActive:@(totalTimeActive)]; + [self sendBackgroundAttributedSessionTimeWithParams:params withTotalTimeActive:@(totalTimeActive)]; return; } restCallTimer = [NSTimer scheduledTimerWithTimeInterval:DELAY_TIME target:self - selector:@selector(sendBackgroundAttributedFocusPingWithNSTimer:) + selector:@selector(sendBackgroundAttributedSessionTimeWithNSTimer:) userInfo:@{@"params": params, @"time": @(totalTimeActive)} repeats:false]; } -- (void)sendBackgroundAttributedFocusPingWithNSTimer:(NSTimer*)timer { +- (void)sendBackgroundAttributedSessionTimeWithNSTimer:(NSTimer*)timer { let userInfo = (NSDictionary*)timer.userInfo; let params = (OSFocusCallParams*)userInfo[@"params"]; let totalTimeActive = (NSNumber*)userInfo[@"time"]; - [self sendBackgroundAttributedFocusPingWithParams:params withTotalTimeActive:totalTimeActive]; + [self sendBackgroundAttributedSessionTimeWithParams:params withTotalTimeActive:totalTimeActive]; } -- (void)sendBackgroundAttributedFocusPingWithParams:(OSFocusCallParams *)params withTotalTimeActive:(NSNumber*)totalTimeActive { - - [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:@"OSAttributedFocusTimeProcessor:sendBackgroundAttributedFocusPingWithParams start"]; - // TODO: Can we get wait for onSuccess to call [super saveUnsentActiveTime:0] - // Need on failure an success to end background task - if ([OneSignal sendSessionEndOutcomes:totalTimeActive params:params]) { - [super saveUnsentActiveTime:0]; - } +- (void)sendBackgroundAttributedSessionTimeWithParams:(OSFocusCallParams *)params withTotalTimeActive:(NSNumber*)totalTimeActive { + [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:@"OSAttributedFocusTimeProcessor:sendBackgroundAttributedSessionTimeWithParams start"]; - [OSBackgroundTaskManager endBackgroundTask:ATTRIBUTED_FOCUS_TASK]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [OneSignalUserManagerImpl.sharedInstance updateSessionWithSessionCount:nil sessionTime:totalTimeActive refreshDeviceMetadata:false sendImmediately:true onSuccess:^{ + [super saveUnsentActiveTime:0]; + [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:@"sendBackgroundAttributed session time succeed, saveUnsentActiveTime with 0"]; + [OSBackgroundTaskManager endBackgroundTask:SEND_SESSION_TIME_TO_USER_TASK]; + } onFailure:^{ + [OneSignalLog onesignalLog:ONE_S_LL_ERROR message:@"sendBackgroundAttributed session time failed, will retry on next open"]; + [OSBackgroundTaskManager endBackgroundTask:SEND_SESSION_TIME_TO_USER_TASK]; + }]; + }); + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [OneSignal sendSessionEndOutcomes:totalTimeActive params:params onSuccess:^(NSDictionary *result) { + [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:@"sendBackgroundAttributed succeed"]; + [OSBackgroundTaskManager endBackgroundTask:ATTRIBUTED_FOCUS_TASK]; + } onFailure:^(NSError *error) { + [OneSignalLog onesignalLog:ONE_S_LL_ERROR message:@"sendBackgroundAttributed failed, will retry on next open"]; + [OSBackgroundTaskManager endBackgroundTask:ATTRIBUTED_FOCUS_TASK]; + }]; + }); } - (void)cancelDelayedJob { @@ -114,6 +137,8 @@ - (void)cancelDelayedJob { [restCallTimer invalidate]; restCallTimer = nil; [OSBackgroundTaskManager endBackgroundTask:ATTRIBUTED_FOCUS_TASK]; + [OSBackgroundTaskManager endBackgroundTask:SEND_SESSION_TIME_TO_USER_TASK]; + } @end diff --git a/iOS_SDK/OneSignalSDK/Source/OSBackgroundTaskHandlerImpl.m b/iOS_SDK/OneSignalSDK/Source/OSBackgroundTaskHandlerImpl.m index f9411e180..79b2dac43 100644 --- a/iOS_SDK/OneSignalSDK/Source/OSBackgroundTaskHandlerImpl.m +++ b/iOS_SDK/OneSignalSDK/Source/OSBackgroundTaskHandlerImpl.m @@ -45,6 +45,9 @@ - (void)beginBackgroundTask:(NSString * _Nonnull)taskIdentifier { message:[NSString stringWithFormat: @"OSBackgroundTaskManagerImpl:beginBackgroundTask: %@", taskIdentifier]]; UIBackgroundTaskIdentifier uiIdentifier = [UIApplication.sharedApplication beginBackgroundTaskWithExpirationHandler:^{ + [OneSignalLog onesignalLog:ONE_S_LL_DEBUG + message:[NSString stringWithFormat: + @"OSBackgroundTaskManagerImpl: expirationHandler called for %@", taskIdentifier]]; [self endBackgroundTask:taskIdentifier]; }]; tasks[taskIdentifier] = [NSNumber numberWithUnsignedLong:uiIdentifier]; diff --git a/iOS_SDK/OneSignalSDK/Source/OSUnattributedFocusTimeProcessor.m b/iOS_SDK/OneSignalSDK/Source/OSUnattributedFocusTimeProcessor.m index 5b96b124a..e10da24eb 100644 --- a/iOS_SDK/OneSignalSDK/Source/OSUnattributedFocusTimeProcessor.m +++ b/iOS_SDK/OneSignalSDK/Source/OSUnattributedFocusTimeProcessor.m @@ -77,15 +77,14 @@ - (void)sendOnFocusCallWithParams:(OSFocusCallParams *)params totalTimeActive:(N [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:@"OSUnattributedFocusTimeProcessor:sendOnFocusCallWithParams start"]; - // updateSession can have a success fail block - [OneSignalUserManagerImpl.sharedInstance updateSessionWithSessionCount:nil sessionTime:@(totalTimeActive) refreshDeviceMetadata:false]; - - // TODO: Can we get wait for onSuccess to call [super saveUnsentActiveTime:0] - // TODO: Revisit when we also test op repo flushing on backgrounding - // We could have callbacks from user module updateSessionTime and set to 0 when we get that callback - [super saveUnsentActiveTime:0]; - - [OSBackgroundTaskManager endBackgroundTask:UNATTRIBUTED_FOCUS_TASK]; + [OneSignalUserManagerImpl.sharedInstance updateSessionWithSessionCount:nil sessionTime:@(totalTimeActive) refreshDeviceMetadata:false sendImmediately:true onSuccess:^{ + [super saveUnsentActiveTime:0]; + [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:@"sendOnFocusCallWithParams unattributed succeed, saveUnsentActiveTime with 0"]; + [OSBackgroundTaskManager endBackgroundTask:UNATTRIBUTED_FOCUS_TASK]; + } onFailure:^{ + [OneSignalLog onesignalLog:ONE_S_LL_WARN message:@"sendOnFocusCallWithParams unattributed failed, will retry on next open"]; + [OSBackgroundTaskManager endBackgroundTask:UNATTRIBUTED_FOCUS_TASK]; + }]; }); } diff --git a/iOS_SDK/OneSignalSDK/Source/OneSignal.m b/iOS_SDK/OneSignalSDK/Source/OneSignal.m index 060d77f3d..ce3bc56e9 100755 --- a/iOS_SDK/OneSignalSDK/Source/OneSignal.m +++ b/iOS_SDK/OneSignalSDK/Source/OneSignal.m @@ -698,26 +698,33 @@ + (UNMutableNotificationContent*)serviceExtensionTimeWillExpireRequest:(UNNotifi Start of outcome module */ -// Returns if we can send this, meaning we have a subscription_id and onesignal_id -+ (BOOL)sendSessionEndOutcomes:(NSNumber*)totalTimeActive params:(OSFocusCallParams *)params { ++ (void)sendSessionEndOutcomes:(NSNumber*)totalTimeActive params:(OSFocusCallParams *)params onSuccess:(OSResultSuccessBlock _Nonnull)successBlock onFailure:(OSFailureBlock _Nonnull)failureBlock { if (![OSOutcomes sharedController]) { [OneSignalLog onesignalLog:ONE_S_LL_ERROR message:@"Make sure OneSignal init is called first"]; - return false; + if (failureBlock) { + failureBlock([NSError errorWithDomain:@"onesignal" code:0 userInfo:@{@"error" : @"Missing outcomes controller."}]); + } + return; } NSString* onesignalId = OneSignalUserManagerImpl.sharedInstance.onesignalId; NSString* pushSubscriptionId = OneSignalUserManagerImpl.sharedInstance.pushSubscriptionId; if (!onesignalId || !pushSubscriptionId) { - return false; + if (failureBlock) { + failureBlock([NSError errorWithDomain:@"onesignal" code:0 userInfo:@{@"error" : @"Missing onesignalId or pushSubscriptionId."}]); + } + return; + } [OSOutcomes.sharedController sendSessionEndOutcomes:totalTimeActive - appId:appId - pushSubscriptionId:pushSubscriptionId - onesignalId:onesignalId - influenceParams:params.influenceParams]; - return true; + appId:appId + pushSubscriptionId:pushSubscriptionId + onesignalId:onesignalId + influenceParams:params.influenceParams + onSuccess:successBlock + onFailure:failureBlock]; } @end