diff --git a/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj b/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj index 957917efb..54def7124 100644 --- a/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj +++ b/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj @@ -87,7 +87,19 @@ 3C8E6DF928A6D89E0031E48A /* OSOperationExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C8E6DF828A6D89E0031E48A /* OSOperationExecutor.swift */; }; 3C8E6DFF28AB09AE0031E48A /* OSPropertyOperationExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C8E6DFE28AB09AE0031E48A /* OSPropertyOperationExecutor.swift */; }; 3C8E6E0128AC0BA10031E48A /* OSIdentityOperationExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C8E6E0028AC0BA10031E48A /* OSIdentityOperationExecutor.swift */; }; - 3CA6CE0A28E4F19B00CA0585 /* OSUserRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA6CE0928E4F19B00CA0585 /* OSUserRequests.swift */; }; + 3C9AD6BC2B2285FB00BC1540 /* OSUserExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C9AD6BB2B2285FB00BC1540 /* OSUserExecutor.swift */; }; + 3C9AD6BF2B22881D00BC1540 /* OSRequestFetchIdentityBySubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C9AD6BE2B22881D00BC1540 /* OSRequestFetchIdentityBySubscription.swift */; }; + 3C9AD6C12B22886600BC1540 /* OSRequestUpdateSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C9AD6C02B22886600BC1540 /* OSRequestUpdateSubscription.swift */; }; + 3C9AD6C32B22887700BC1540 /* OSRequestCreateUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C9AD6C22B22887700BC1540 /* OSRequestCreateUser.swift */; }; + 3C9AD6C52B228A7300BC1540 /* OSRequestDeleteSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C9AD6C42B228A7300BC1540 /* OSRequestDeleteSubscription.swift */; }; + 3C9AD6C72B228A9800BC1540 /* OSRequestTransferSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C9AD6C62B228A9800BC1540 /* OSRequestTransferSubscription.swift */; }; + 3C9AD6C92B228AB200BC1540 /* OSRequestCreateSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C9AD6C82B228AB200BC1540 /* OSRequestCreateSubscription.swift */; }; + 3C9AD6CB2B228B5200BC1540 /* OSRequestIdentifyUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C9AD6CA2B228B5200BC1540 /* OSRequestIdentifyUser.swift */; }; + 3C9AD6CD2B228B6300BC1540 /* OSRequestFetchUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C9AD6CC2B228B6300BC1540 /* OSRequestFetchUser.swift */; }; + 3C9AD6CF2B228B7800BC1540 /* OSRequestAddAliases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C9AD6CE2B228B7800BC1540 /* OSRequestAddAliases.swift */; }; + 3C9AD6D12B228B9200BC1540 /* OSRequestRemoveAlias.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C9AD6D02B228B9200BC1540 /* OSRequestRemoveAlias.swift */; }; + 3C9AD6D32B228BB000BC1540 /* OSRequestUpdateProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C9AD6D22B228BB000BC1540 /* OSRequestUpdateProperties.swift */; }; + 3CA6CE0A28E4F19B00CA0585 /* OSUserRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA6CE0928E4F19B00CA0585 /* OSUserRequest.swift */; }; 3CC9A6342AFA1FDE008F68FD /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 3CC9A6332AFA1FDD008F68FD /* PrivacyInfo.xcprivacy */; }; 3CC9A6362AFA26E7008F68FD /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 3CC9A6352AFA26E7008F68FD /* PrivacyInfo.xcprivacy */; }; 3CCF44BE299B17290021964D /* OneSignalWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 3CCF44BC299B17290021964D /* OneSignalWrapper.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -749,7 +761,19 @@ 3C8E6DF828A6D89E0031E48A /* OSOperationExecutor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSOperationExecutor.swift; sourceTree = ""; }; 3C8E6DFE28AB09AE0031E48A /* OSPropertyOperationExecutor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSPropertyOperationExecutor.swift; sourceTree = ""; }; 3C8E6E0028AC0BA10031E48A /* OSIdentityOperationExecutor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSIdentityOperationExecutor.swift; sourceTree = ""; }; - 3CA6CE0928E4F19B00CA0585 /* OSUserRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSUserRequests.swift; sourceTree = ""; }; + 3C9AD6BB2B2285FB00BC1540 /* OSUserExecutor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSUserExecutor.swift; sourceTree = ""; }; + 3C9AD6BE2B22881D00BC1540 /* OSRequestFetchIdentityBySubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSRequestFetchIdentityBySubscription.swift; sourceTree = ""; }; + 3C9AD6C02B22886600BC1540 /* OSRequestUpdateSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSRequestUpdateSubscription.swift; sourceTree = ""; }; + 3C9AD6C22B22887700BC1540 /* OSRequestCreateUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSRequestCreateUser.swift; sourceTree = ""; }; + 3C9AD6C42B228A7300BC1540 /* OSRequestDeleteSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSRequestDeleteSubscription.swift; sourceTree = ""; }; + 3C9AD6C62B228A9800BC1540 /* OSRequestTransferSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSRequestTransferSubscription.swift; sourceTree = ""; }; + 3C9AD6C82B228AB200BC1540 /* OSRequestCreateSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSRequestCreateSubscription.swift; sourceTree = ""; }; + 3C9AD6CA2B228B5200BC1540 /* OSRequestIdentifyUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSRequestIdentifyUser.swift; sourceTree = ""; }; + 3C9AD6CC2B228B6300BC1540 /* OSRequestFetchUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSRequestFetchUser.swift; sourceTree = ""; }; + 3C9AD6CE2B228B7800BC1540 /* OSRequestAddAliases.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSRequestAddAliases.swift; sourceTree = ""; }; + 3C9AD6D02B228B9200BC1540 /* OSRequestRemoveAlias.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSRequestRemoveAlias.swift; sourceTree = ""; }; + 3C9AD6D22B228BB000BC1540 /* OSRequestUpdateProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSRequestUpdateProperties.swift; sourceTree = ""; }; + 3CA6CE0928E4F19B00CA0585 /* OSUserRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSUserRequest.swift; sourceTree = ""; }; 3CC9A6332AFA1FDD008F68FD /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 3CC9A6352AFA26E7008F68FD /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 3CCF44BC299B17290021964D /* OneSignalWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OneSignalWrapper.h; sourceTree = ""; }; @@ -1371,6 +1395,36 @@ path = Source; sourceTree = ""; }; + 3C9AD6BA2B2284AB00BC1540 /* Executors */ = { + isa = PBXGroup; + children = ( + 3C8E6E0028AC0BA10031E48A /* OSIdentityOperationExecutor.swift */, + 3C8E6DFE28AB09AE0031E48A /* OSPropertyOperationExecutor.swift */, + 3CE795FA28DBDCE700736BD4 /* OSSubscriptionOperationExecutor.swift */, + 3C9AD6BB2B2285FB00BC1540 /* OSUserExecutor.swift */, + ); + path = Executors; + sourceTree = ""; + }; + 3C9AD6BD2B22877600BC1540 /* Requests */ = { + isa = PBXGroup; + children = ( + 3CA6CE0928E4F19B00CA0585 /* OSUserRequest.swift */, + 3C9AD6C22B22887700BC1540 /* OSRequestCreateUser.swift */, + 3C9AD6BE2B22881D00BC1540 /* OSRequestFetchIdentityBySubscription.swift */, + 3C9AD6CA2B228B5200BC1540 /* OSRequestIdentifyUser.swift */, + 3C9AD6CC2B228B6300BC1540 /* OSRequestFetchUser.swift */, + 3C9AD6CE2B228B7800BC1540 /* OSRequestAddAliases.swift */, + 3C9AD6D02B228B9200BC1540 /* OSRequestRemoveAlias.swift */, + 3C9AD6D22B228BB000BC1540 /* OSRequestUpdateProperties.swift */, + 3C9AD6C82B228AB200BC1540 /* OSRequestCreateSubscription.swift */, + 3C9AD6C62B228A9800BC1540 /* OSRequestTransferSubscription.swift */, + 3C9AD6C02B22886600BC1540 /* OSRequestUpdateSubscription.swift */, + 3C9AD6C42B228A7300BC1540 /* OSRequestDeleteSubscription.swift */, + ); + path = Requests; + sourceTree = ""; + }; 3E2400391D4FFC31008BDE70 /* OneSignalFramework */ = { isa = PBXGroup; children = ( @@ -1584,6 +1638,7 @@ DE69E19C282ED8060090BB3D /* OneSignalUser */ = { isa = PBXGroup; children = ( + DE69E19D282ED8060090BB3D /* OneSignalUser.h */, DE69E1A8282ED8360090BB3D /* Source */, DE69E19E282ED8060090BB3D /* OneSignalUser.docc */, ); @@ -1593,20 +1648,17 @@ DE69E1A8282ED8360090BB3D /* Source */ = { isa = PBXGroup; children = ( - DE69E19D282ED8060090BB3D /* OneSignalUser.h */, + 3C9AD6BA2B2284AB00BC1540 /* Executors */, + 3C9AD6BD2B22877600BC1540 /* Requests */, + DE69E1A9282ED8790090BB3D /* UnitTestApp-Bridging-Header.h */, 3C0EF49D28A1DBCB00E5434B /* OSUserInternalImpl.swift */, DE69E1AA282ED8790090BB3D /* OneSignalUserManagerImpl.swift */, - DE69E1A9282ED8790090BB3D /* UnitTestApp-Bridging-Header.h */, 3C2C7DC7288F3C020020F9AE /* OSSubscriptionModel.swift */, 3CE92279289FA88B001B1062 /* OSIdentityModelStoreListener.swift */, 3CF8629D28A183F900776CA4 /* OSIdentityModel.swift */, 3CF862A128A197D200776CA4 /* OSPropertiesModelStoreListener.swift */, 3CF8629F28A1964F00776CA4 /* OSPropertiesModel.swift */, - 3C8E6DFE28AB09AE0031E48A /* OSPropertyOperationExecutor.swift */, - 3C8E6E0028AC0BA10031E48A /* OSIdentityOperationExecutor.swift */, 3CE795F828DB99B500736BD4 /* OSSubscriptionModelStoreListener.swift */, - 3CE795FA28DBDCE700736BD4 /* OSSubscriptionOperationExecutor.swift */, - 3CA6CE0928E4F19B00CA0585 /* OSUserRequests.swift */, 3C5117162B15C31E00563465 /* OSUserState.swift */, ); path = Source; @@ -2930,18 +2982,30 @@ files = ( 3CE795F928DB99B500736BD4 /* OSSubscriptionModelStoreListener.swift in Sources */, DE69E1AC282ED87A0090BB3D /* OneSignalUserManagerImpl.swift in Sources */, + 3C9AD6CF2B228B7800BC1540 /* OSRequestAddAliases.swift in Sources */, + 3C9AD6D32B228BB000BC1540 /* OSRequestUpdateProperties.swift in Sources */, + 3C9AD6CD2B228B6300BC1540 /* OSRequestFetchUser.swift in Sources */, 3CF862A028A1964F00776CA4 /* OSPropertiesModel.swift in Sources */, 3C8E6E0128AC0BA10031E48A /* OSIdentityOperationExecutor.swift in Sources */, 3CF862A228A197D200776CA4 /* OSPropertiesModelStoreListener.swift in Sources */, + 3C9AD6C12B22886600BC1540 /* OSRequestUpdateSubscription.swift in Sources */, 3C0EF49E28A1DBCB00E5434B /* OSUserInternalImpl.swift in Sources */, 3C8E6DFF28AB09AE0031E48A /* OSPropertyOperationExecutor.swift in Sources */, + 3C9AD6CB2B228B5200BC1540 /* OSRequestIdentifyUser.swift in Sources */, + 3C9AD6BC2B2285FB00BC1540 /* OSUserExecutor.swift in Sources */, + 3C9AD6C32B22887700BC1540 /* OSRequestCreateUser.swift in Sources */, + 3C9AD6D12B228B9200BC1540 /* OSRequestRemoveAlias.swift in Sources */, + 3C9AD6C92B228AB200BC1540 /* OSRequestCreateSubscription.swift in Sources */, + 3C9AD6C52B228A7300BC1540 /* OSRequestDeleteSubscription.swift in Sources */, + 3C9AD6C72B228A9800BC1540 /* OSRequestTransferSubscription.swift in Sources */, 3C2C7DC8288F3C020020F9AE /* OSSubscriptionModel.swift in Sources */, 3CF8629E28A183F900776CA4 /* OSIdentityModel.swift in Sources */, 3CE795FB28DBDCE700736BD4 /* OSSubscriptionOperationExecutor.swift in Sources */, 3C5117172B15C31E00563465 /* OSUserState.swift in Sources */, + 3C9AD6BF2B22881D00BC1540 /* OSRequestFetchIdentityBySubscription.swift in Sources */, 3CE9227A289FA88B001B1062 /* OSIdentityModelStoreListener.swift in Sources */, DE69E19F282ED8060090BB3D /* OneSignalUser.docc in Sources */, - 3CA6CE0A28E4F19B00CA0585 /* OSUserRequests.swift in Sources */, + 3CA6CE0A28E4F19B00CA0585 /* OSUserRequest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSOperationRepo.swift b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSOperationRepo.swift index 8f9e8f77b..25ef3b00f 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSOperationRepo.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSOperationRepo.swift @@ -113,12 +113,12 @@ public class OSOperationRepo: NSObject { OneSignalLog.onesignalLog(.LL_DEBUG, message: "OSOperationRepo not flushing queue due to being paused") return } - + guard !OneSignalConfigManager.shouldAwaitAppIdAndLogMissingPrivacyConsent(forMethod: nil) else { return } - - if (inBackground) { + + if inBackground { OSBackgroundTaskManager.beginBackgroundTask(OPERATION_REPO_BACKGROUND_TASK) } @@ -148,8 +148,8 @@ public class OSOperationRepo: NSObject { for executor in executors { executor.processDeltaQueue(inBackground: inBackground) } - - if (inBackground) { + + if inBackground { OSBackgroundTaskManager.endBackgroundTask(OPERATION_REPO_BACKGROUND_TASK) } diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUser.h b/iOS_SDK/OneSignalSDK/OneSignalUser/OneSignalUser.h similarity index 100% rename from iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUser.h rename to iOS_SDK/OneSignalSDK/OneSignalUser/OneSignalUser.h diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityOperationExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSIdentityOperationExecutor.swift similarity index 98% rename from iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityOperationExecutor.swift rename to iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSIdentityOperationExecutor.swift index c49ef5c7b..475ddffde 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityOperationExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSIdentityOperationExecutor.swift @@ -175,16 +175,16 @@ class OSIdentityOperationExecutor: OSOperationExecutor { OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSIdentityOperationExecutor: executeAddAliasesRequest making request: \(request)") let backgroundTaskIdentifier = IDENTITY_EXECUTOR_BACKGROUND_TASK + UUID().uuidString - if (inBackground) { + 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) { + if inBackground { OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) } } onFailure: { error in @@ -198,7 +198,7 @@ class OSIdentityOperationExecutor: OSOperationExecutor { // Logout if the user in the SDK is the same guard OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModel) else { - if (inBackground) { + if inBackground { OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) } return @@ -212,7 +212,7 @@ class OSIdentityOperationExecutor: OSOperationExecutor { OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_ADD_REQUEST_QUEUE_KEY, withValue: self.addRequestQueue) } } - if (inBackground) { + if inBackground { OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) } } @@ -230,21 +230,21 @@ class OSIdentityOperationExecutor: OSOperationExecutor { OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSIdentityOperationExecutor: executeRemoveAliasRequest making request: \(request)") let backgroundTaskIdentifier = IDENTITY_EXECUTOR_BACKGROUND_TASK + UUID().uuidString - if (inBackground) { + 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) { + if inBackground { OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) } } onFailure: { error in OneSignalLog.onesignalLog(.LL_ERROR, message: "OSIdentityOperationExecutor remove alias request failed with error: \(error.debugDescription)") - + if let nsError = error as? NSError { let responseType = OSNetworkingUtils.getResponseStatusType(nsError.code) if responseType != .retryable { @@ -254,7 +254,7 @@ class OSIdentityOperationExecutor: OSOperationExecutor { OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY, withValue: self.removeRequestQueue) } } - if (inBackground) { + if inBackground { OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) } } diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSPropertyOperationExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSPropertyOperationExecutor.swift similarity index 98% rename from iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSPropertyOperationExecutor.swift rename to iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSPropertyOperationExecutor.swift index 3c3e5b2b9..9f219f6dd 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSPropertyOperationExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSPropertyOperationExecutor.swift @@ -132,18 +132,18 @@ class OSPropertyOperationExecutor: OSOperationExecutor { return } request.sentToClient = true - + let backgroundTaskIdentifier = PROPERTIES_EXECUTOR_BACKGROUND_TASK + UUID().uuidString - if (inBackground) { + 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) { + if inBackground { OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) } } onFailure: { error in @@ -157,7 +157,7 @@ class OSPropertyOperationExecutor: OSOperationExecutor { // Logout if the user in the SDK is the same guard OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModel) else { - if (inBackground) { + if inBackground { OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) } return @@ -171,7 +171,7 @@ class OSPropertyOperationExecutor: OSOperationExecutor { OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_PROPERTIES_EXECUTOR_UPDATE_REQUEST_QUEUE_KEY, withValue: self.updateRequestQueue) } } - if (inBackground) { + if inBackground { OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) } } @@ -189,7 +189,7 @@ extension OSPropertyOperationExecutor { modelToUpdate: propertiesModel, identityModel: identityModel) - if (sendImmediately) { + if sendImmediately { // Bypass the request queues OneSignalClient.shared().execute(request) { _ in if let onSuccess = onSuccess { diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSSubscriptionOperationExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSSubscriptionOperationExecutor.swift similarity index 98% rename from iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSSubscriptionOperationExecutor.swift rename to iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSSubscriptionOperationExecutor.swift index 0541ba27c..8188663ef 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSSubscriptionOperationExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSSubscriptionOperationExecutor.swift @@ -241,10 +241,10 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { request.sentToClient = true let backgroundTaskIdentifier = SUBSCRIPTION_EXECUTOR_BACKGROUND_TASK + UUID().uuidString - if (inBackground) { + 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 @@ -253,13 +253,13 @@ 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) { + if inBackground { OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) } return } request.subscriptionModel.hydrate(response) - if (inBackground) { + if inBackground { OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) } } onFailure: { error in @@ -272,7 +272,7 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { // Logout if the user in the SDK is the same guard OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModel) else { - if (inBackground) { + if inBackground { OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) } return @@ -286,7 +286,7 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_SUBSCRIPTION_EXECUTOR_ADD_REQUEST_QUEUE_KEY, withValue: self.addRequestQueue) } } - if (inBackground) { + if inBackground { OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) } } @@ -300,12 +300,12 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { return } request.sentToClient = true - + let backgroundTaskIdentifier = SUBSCRIPTION_EXECUTOR_BACKGROUND_TASK + UUID().uuidString - if (inBackground) { + 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 @@ -313,7 +313,7 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { // 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) { + if inBackground { OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) } } onFailure: { error in @@ -327,7 +327,7 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_SUBSCRIPTION_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY, withValue: self.removeRequestQueue) } } - if (inBackground) { + if inBackground { OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) } } @@ -343,16 +343,16 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { request.sentToClient = true let backgroundTaskIdentifier = SUBSCRIPTION_EXECUTOR_BACKGROUND_TASK + UUID().uuidString - if (inBackground) { + 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) { + if inBackground { OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) } } onFailure: { error in @@ -365,7 +365,7 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_SUBSCRIPTION_EXECUTOR_UPDATE_REQUEST_QUEUE_KEY, withValue: self.updateRequestQueue) } } - if (inBackground) { + if inBackground { OSBackgroundTaskManager.endBackgroundTask(backgroundTaskIdentifier) } } diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift new file mode 100644 index 000000000..da719ee64 --- /dev/null +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift @@ -0,0 +1,580 @@ +/* + Modified MIT License + + Copyright 2023 OneSignal + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + 1. The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 2. All copies of substantial portions of the Software may only be used in connection + with services provided by OneSignal. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +import OneSignalCore +import OneSignalNotifications +import OneSignalOSCore + +/** + Involved in the login process and responsible for Identify User and Create User. + Can execute `OSRequestCreateUser`, `OSRequestIdentifyUser`, `OSRequestTransferSubscription`, `OSRequestFetchUser`, `OSRequestFetchIdentityBySubscription`. + */ +class OSUserExecutor { + static var userRequestQueue: [OSUserRequest] = [] + static var transferSubscriptionRequestQueue: [OSRequestTransferSubscription] = [] + static var identityModels: [String: OSIdentityModel] = [:] + + // Read in requests from the cache, do not read in FetchUser requests as this is not needed. + static func start() { + var userRequestQueue: [OSUserRequest] = [] + + // Read unfinished Create User + Identify User + Get Identity By Subscription requests from cache, if any... + if let cachedRequestQueue = OneSignalUserDefaults.initShared().getSavedCodeableData(forKey: OS_USER_EXECUTOR_USER_REQUEST_QUEUE_KEY, defaultValue: []) as? [OSUserRequest] { + // Hook each uncached Request to the right model reference + for request in cachedRequestQueue { + if request.isKind(of: OSRequestFetchIdentityBySubscription.self), let req = request as? OSRequestFetchIdentityBySubscription { + if let identityModel = OneSignalUserManagerImpl.sharedInstance.identityModelStore.getModel(modelId: req.identityModel.modelId) { + // 1. The model exist in the store, set it to be the Request's model + req.identityModel = identityModel + } else if let identityModel = identityModels[req.identityModel.modelId] { + // 2. The model exists in the dict of identityModels already processed to use + req.identityModel = identityModel + } else { + // 3. The models do not exist, use the model on the request, and add to dict. + identityModels[req.identityModel.modelId] = req.identityModel + } + userRequestQueue.append(req) + + } else if request.isKind(of: OSRequestCreateUser.self), let req = request as? OSRequestCreateUser { + if let identityModel = OneSignalUserManagerImpl.sharedInstance.identityModelStore.getModel(modelId: req.identityModel.modelId) { + // 1. The model exist in the store, set it to be the Request's model + req.identityModel = identityModel + } else if let identityModel = identityModels[req.identityModel.modelId] { + // 2. The model exists in the dict of identityModels already processed to use + req.identityModel = identityModel + } else { + // 3. The models do not exist, use the model on the request, and add to dict. + identityModels[req.identityModel.modelId] = req.identityModel + } + userRequestQueue.append(req) + + } else if request.isKind(of: OSRequestIdentifyUser.self), let req = request as? OSRequestIdentifyUser { + + if let identityModelToIdentify = identityModels[req.identityModelToIdentify.modelId], + let identityModelToUpdate = OneSignalUserManagerImpl.sharedInstance.identityModelStore.getModel(modelId: req.identityModelToUpdate.modelId) { + // 1. A model exist in the dict and a model exist in the store, set it to be the Request's models + req.identityModelToIdentify = identityModelToIdentify + req.identityModelToUpdate = identityModelToUpdate + } else if let identityModelToIdentify = identityModels[req.identityModelToIdentify.modelId], + let identityModelToUpdate = identityModels[req.identityModelToUpdate.modelId] { + // 2. The two models exist in the dict, set it to be the Request's models + req.identityModelToIdentify = identityModelToIdentify + req.identityModelToUpdate = identityModelToUpdate + } else if let identityModelToIdentify = identityModels[req.identityModelToIdentify.modelId], + identityModels[req.identityModelToUpdate.modelId] == nil { + // 3. A model is in the dict, the other model does not exist + req.identityModelToIdentify = identityModelToIdentify + identityModels[req.identityModelToUpdate.modelId] = req.identityModelToUpdate + } else { + // 4. Both models don't exist yet + identityModels[req.identityModelToIdentify.modelId] = req.identityModelToIdentify + identityModels[req.identityModelToUpdate.modelId] = req.identityModelToUpdate + } + userRequestQueue.append(req) + } + } + } + self.userRequestQueue = userRequestQueue + + // Read unfinished Transfer Subscription requests from cache, if any... + if let transferSubscriptionRequestQueue = OneSignalUserDefaults.initShared().getSavedCodeableData(forKey: OS_USER_EXECUTOR_TRANSFER_SUBSCRIPTION_REQUEST_QUEUE_KEY, defaultValue: []) as? [OSRequestTransferSubscription] { + // We only care about the last transfer subscription request + if let request = transferSubscriptionRequestQueue.last { + // Hook the uncached Request to the model in the store + if request.subscriptionModel.modelId == OneSignalUserManagerImpl.sharedInstance.user.pushSubscriptionModel.modelId { + // The model exist, set it to be the Request's model + request.subscriptionModel = OneSignalUserManagerImpl.sharedInstance.user.pushSubscriptionModel + self.transferSubscriptionRequestQueue = [request] + } else if !request.prepareForExecution() { + // The model do not exist AND this request cannot be sent, drop this Request + OneSignalLog.onesignalLog(.LL_ERROR, message: "OSUserExecutor.start() reading request \(request) from cache failed. Dropping request.") + self.transferSubscriptionRequestQueue = [] + } + } + } else { + OneSignalLog.onesignalLog(.LL_ERROR, message: "OSUserExecutor error encountered reading from cache for \(OS_USER_EXECUTOR_TRANSFER_SUBSCRIPTION_REQUEST_QUEUE_KEY)") + } + + executePendingRequests() + } + + static func appendToQueue(_ request: OSUserRequest) { + if request.isKind(of: OSRequestTransferSubscription.self), let req = request as? OSRequestTransferSubscription { + self.transferSubscriptionRequestQueue.append(req) + OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_USER_EXECUTOR_TRANSFER_SUBSCRIPTION_REQUEST_QUEUE_KEY, withValue: self.transferSubscriptionRequestQueue) + } else { + self.userRequestQueue.append(request) + OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_USER_EXECUTOR_USER_REQUEST_QUEUE_KEY, withValue: self.userRequestQueue) + } + } + + static func removeFromQueue(_ request: OSUserRequest) { + if request.isKind(of: OSRequestTransferSubscription.self), let req = request as? OSRequestTransferSubscription { + transferSubscriptionRequestQueue.removeAll(where: { $0 == req}) + OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_USER_EXECUTOR_TRANSFER_SUBSCRIPTION_REQUEST_QUEUE_KEY, withValue: self.transferSubscriptionRequestQueue) + } else { + userRequestQueue.removeAll(where: { $0 == request}) + OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_USER_EXECUTOR_USER_REQUEST_QUEUE_KEY, withValue: self.userRequestQueue) + } + } + + static func executePendingRequests() { + let requestQueue: [OSUserRequest] = userRequestQueue + transferSubscriptionRequestQueue + + if requestQueue.isEmpty { + return + } + + // Sort the requestQueue by timestamp + for request in requestQueue.sorted(by: { first, second in + return first.timestamp < second.timestamp + }) { + // Return as soon as we reach an un-executable request + if !request.prepareForExecution() { + return + } + + if request.isKind(of: OSRequestFetchIdentityBySubscription.self), let fetchIdentityRequest = request as? OSRequestFetchIdentityBySubscription { + executeFetchIdentityBySubscriptionRequest(fetchIdentityRequest) + return + } else if request.isKind(of: OSRequestCreateUser.self), let createUserRequest = request as? OSRequestCreateUser { + executeCreateUserRequest(createUserRequest) + return + } else if request.isKind(of: OSRequestIdentifyUser.self), let identifyUserRequest = request as? OSRequestIdentifyUser { + executeIdentifyUserRequest(identifyUserRequest) + return + } else if request.isKind(of: OSRequestTransferSubscription.self), let transferSubscriptionRequest = request as? OSRequestTransferSubscription { + executeTransferPushSubscriptionRequest(transferSubscriptionRequest) + return + } else if request.isKind(of: OSRequestFetchUser.self), let fetchUserRequest = request as? OSRequestFetchUser { + executeFetchUserRequest(fetchUserRequest) + return + } else { + // Log Error + OneSignalLog.onesignalLog(.LL_ERROR, message: "OSUserExecutor met incompatible Request type that cannot be executed.") + } + } + } + + // We will pass minimal properties to this request + static func createUser(_ user: OSUserInternal) { + let originalPushToken = user.pushSubscriptionModel.address + let request = OSRequestCreateUser(identityModel: user.identityModel, propertiesModel: user.propertiesModel, pushSubscriptionModel: user.pushSubscriptionModel, originalPushToken: originalPushToken) + + appendToQueue(request) + + executePendingRequests() + } + + static func executeCreateUserRequest(_ request: OSRequestCreateUser) { + guard !request.sentToClient else { + return + } + guard request.prepareForExecution() else { + // Currently there are no requirements needed before sending this request + return + } + request.sentToClient = true + + // Hook up push subscription model, it may be updated with a subscription_id, etc. + if let pushSubscriptionModel = OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModelStore.getModel(modelId: request.pushSubscriptionModel.modelId) { + request.pushSubscriptionModel = pushSubscriptionModel + request.updatePushSubscriptionModel(pushSubscriptionModel) + } + + OneSignalClient.shared().execute(request) { response in + removeFromQueue(request) + + // TODO: Differentiate if we need to fetch the user based on response code of 200, 201, 202 + // Create User's response won't send us the user's complete info if this user already exists + if let response = response { + // Parse the response for any data we need to update + parseFetchUserResponse(response: response, identityModel: request.identityModel, originalPushToken: request.originalPushToken) + + // If this user already exists and we logged into an external_id, fetch the user data + // TODO: Only do this if response code is 200 or 202 + // Fetch the user only if its the current user + if OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModel), + let identity = request.parameters?["identity"] as? [String: String], + let externalId = identity[OS_EXTERNAL_ID] { + fetchUser(aliasLabel: OS_EXTERNAL_ID, aliasId: externalId, identityModel: request.identityModel) + } else { + 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 { + // 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() + } + } + } + + static func fetchIdentityBySubscription(_ user: OSUserInternal) { + let request = OSRequestFetchIdentityBySubscription(identityModel: user.identityModel, pushSubscriptionModel: user.pushSubscriptionModel) + + appendToQueue(request) + executePendingRequests() + } + + /** + For migrating legacy players from 3.x to 5.x. This request will fetch the identity object for a subscription ID, and we will use the returned onesignalId to fetch and hydrate the local user. + */ + static func executeFetchIdentityBySubscriptionRequest(_ request: OSRequestFetchIdentityBySubscription) { + guard !request.sentToClient else { + return + } + guard request.prepareForExecution() else { + return + } + request.sentToClient = true + + OneSignalClient.shared().execute(request) { response in + removeFromQueue(request) + + if let identityObject = parseIdentityObjectResponse(response), + let onesignalId = identityObject[OS_ONESIGNAL_ID] { + request.identityModel.hydrate(identityObject) + + // Fetch this user's data if it is the current user + guard OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModel) + else { + executePendingRequests() + return + } + + fetchUser(aliasLabel: OS_ONESIGNAL_ID, aliasId: onesignalId, identityModel: request.identityModel) + } + } onFailure: { error in + OneSignalLog.onesignalLog(.LL_ERROR, message: "OSUserExecutor executeFetchIdentityBySubscriptionRequest failed with error: \(error.debugDescription)") + if let nsError = error as? NSError { + 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.pushSubscriptionModel?.subscriptionId = nil + removeFromQueue(request) + } + } + executePendingRequests() + } + } + + static func identifyUser(externalId: String, identityModelToIdentify: OSIdentityModel, identityModelToUpdate: OSIdentityModel) { + let request = OSRequestIdentifyUser( + aliasLabel: OS_EXTERNAL_ID, + aliasId: externalId, + identityModelToIdentify: identityModelToIdentify, + identityModelToUpdate: identityModelToUpdate + ) + + appendToQueue(request) + + executePendingRequests() + } + + static func executeIdentifyUserRequest(_ request: OSRequestIdentifyUser) { + guard !request.sentToClient else { + return + } + guard request.prepareForExecution() else { + // Missing onesignal_id + return + } + request.sentToClient = true + + OneSignalClient.shared().execute(request) { _ in + removeFromQueue(request) + + // the anonymous user has been identified, still need to Fetch User as we cleared local data + // Fetch the user only if its the current user + if OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModelToUpdate) { + fetchUser(aliasLabel: OS_EXTERNAL_ID, aliasId: request.aliasId, identityModel: request.identityModelToUpdate) + } else { + executePendingRequests() + } + } onFailure: { error in + OneSignalLog.onesignalLog(.LL_VERBOSE, message: "executeIdentifyUserRequest failed with error \(error.debugDescription)") + if let nsError = error as? NSError { + let responseType = OSNetworkingUtils.getResponseStatusType(nsError.code) + if responseType == .conflict { + // Returns 409 if any provided (label, id) pair exists on another User, so the SDK will switch to this user. + OneSignalLog.onesignalLog(.LL_DEBUG, message: "executeIdentifyUserRequest returned error code user-2. Now handling user-2 error response... switch to this user.") + + removeFromQueue(request) + // Fetch the user only if its the current user + if OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModelToUpdate) { + fetchUser(aliasLabel: OS_EXTERNAL_ID, aliasId: request.aliasId, identityModel: request.identityModelToUpdate) + // TODO: Link ^ to the new user... what was this todo for? + } + transferPushSubscriptionTo(aliasLabel: request.aliasLabel, aliasId: request.aliasId) + } else if responseType == .invalid || responseType == .unauthorized { + // Failed, no retry + removeFromQueue(request) + executePendingRequests() + } else if responseType == .missing { + removeFromQueue(request) + executePendingRequests() + // Logout if the user in the SDK is the same + guard OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModelToUpdate) + else { + return + } + // The subscription has been deleted along with the user, so remove the subscription_id but keep the same push subscription model + OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModel?.subscriptionId = nil + OneSignalUserManagerImpl.sharedInstance._logout() + } + } else { + executePendingRequests() + } + } + } + + static func transferPushSubscriptionTo(aliasLabel: String, aliasId: String) { + // TODO: Where to get pushSubscriptionModel for this request + let request = OSRequestTransferSubscription( + subscriptionModel: OneSignalUserManagerImpl.sharedInstance.user.pushSubscriptionModel, + aliasLabel: aliasLabel, + aliasId: aliasId + ) + + appendToQueue(request) + + executePendingRequests() + } + + static func executeTransferPushSubscriptionRequest(_ request: OSRequestTransferSubscription) { + guard !request.sentToClient else { + return + } + guard request.prepareForExecution() else { + // Missing subscriptionId + OneSignalLog.onesignalLog(.LL_DEBUG, message: "OSUserExecutor.executeTransferPushSubscriptionRequest with request \(request) cannot be executed due to failing prepareForExecution()") + return + } + request.sentToClient = true + OneSignalClient.shared().execute(request) { _ in + removeFromQueue(request) + + // TODO: ... hydrate with returned identity object? + executePendingRequests() + + } onFailure: { error in + OneSignalLog.onesignalLog(.LL_ERROR, message: "OSUserExecutor executeTransferPushSubscriptionRequest 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 + removeFromQueue(request) + } + } + executePendingRequests() + } + } + + 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) + + executePendingRequests() + } + + static func executeFetchUserRequest(_ request: OSRequestFetchUser) { + guard !request.sentToClient else { + return + } + guard request.prepareForExecution() else { + // This should not happen as we set the alias to use for the request path + return + } + request.sentToClient = true + OneSignalClient.shared().execute(request) { response in + removeFromQueue(request) + + if let response = response { + // Clear local data in preparation for hydration + OneSignalUserManagerImpl.sharedInstance.clearUserData() + parseFetchUserResponse(response: response, identityModel: request.identityModel, originalPushToken: OneSignalUserManagerImpl.sharedInstance.pushSubscriptionImpl.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 + OneSignalLog.onesignalLog(.LL_ERROR, message: "OSUserExecutor executeFetchUserRequest failed with error: \(error.debugDescription)") + if let nsError = error as? NSError { + let responseType = OSNetworkingUtils.getResponseStatusType(nsError.code) + if responseType == .missing { + removeFromQueue(request) + // Logout if the user in the SDK is the same + guard OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModel) + else { + return + } + // The subscription has been deleted along with the user, so remove the subscription_id but keep the same push subscription model + OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModel?.subscriptionId = nil + OneSignalUserManagerImpl.sharedInstance._logout() + } else if responseType != .retryable { + // If the error is not retryable, remove from cache and queue + removeFromQueue(request) + } + } + executePendingRequests() + } + } +} + +// MARK: - Parsing +extension OSUserExecutor { + /** + 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. + */ + static func parseFetchUserResponse(response: [AnyHashable: Any], identityModel: OSIdentityModel, originalPushToken: String?) { + + // If this was a create user, it hydrates the onesignal_id of the request's identityModel + // The model in the store may be different, and it may be waiting on the onesignal_id of this previous model + if let identityObject = parseIdentityObjectResponse(response) { + identityModel.hydrate(identityObject) + } + + // 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.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.pushSubscriptionModel?.hydrate(subModel) + if let subId = subModel["id"] as? String { + OSNotificationsManager.setPushSubscriptionId(subId) + } + break + } + } + } + + // 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 + } + + if let identityObject = parseIdentityObjectResponse(response) { + OneSignalUserManagerImpl.sharedInstance.user.identityModel.hydrate(identityObject) + } + + if let propertiesObject = parsePropertiesObjectResponse(response) { + OneSignalUserManagerImpl.sharedInstance.user.propertiesModel.hydrate(propertiesObject) + } + + // Now parse email and sms subscriptions + if let subscriptionObject = parseSubscriptionObjectResponse(response) { + let models = OneSignalUserManagerImpl.sharedInstance.subscriptionModelStore.getModels() + for subModel in subscriptionObject { + if let address = subModel["token"] as? String, + let rawType = subModel["type"] as? String, + rawType != "iOSPush", + let type = OSSubscriptionType(rawValue: rawType) { + if let model = models[address] { + // This subscription exists in the store, hydrate + model.hydrate(subModel) + + } else { + // This subscription does not exist in the store, add + OneSignalUserManagerImpl.sharedInstance.subscriptionModelStore.add(id: address, model: OSSubscriptionModel( + type: type, + address: address, + subscriptionId: subModel["id"] as? String, + reachable: true, + isDisabled: false, + changeNotifier: OSEventProducer()), hydrating: true + ) + } + } + } + } + } + + /** + Returns if 2 tokens are equal. This is needed as a nil token is equal to the empty string "". + */ + static func areTokensEqual(tokenA: String?, tokenB: String?) -> Bool { + // They are both strings or both nil + if tokenA == tokenB { + return true + } + // One is nil and the other is "" + if (tokenA == nil && tokenB == "") || (tokenA == "" && tokenB == nil) { + return true + } + return false + } + + static func parseSubscriptionObjectResponse(_ response: [AnyHashable: Any]?) -> [[String: Any]]? { + return response?["subscriptions"] as? [[String: Any]] + } + + static func parsePropertiesObjectResponse(_ response: [AnyHashable: Any]?) -> [String: Any]? { + return response?["properties"] as? [String: Any] + } + + static func parseIdentityObjectResponse(_ response: [AnyHashable: Any]?) -> [String: String]? { + return response?["identity"] as? [String: String] + } +} diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityModel.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityModel.swift index 63b239368..b915030cc 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityModel.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityModel.swift @@ -92,7 +92,7 @@ class OSIdentityModel: OSModel { OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSIdentityModel hydrateModel()") var newOnesignalId: String? var newExternalId: String? - + for property in response { switch property.key { case "external_id": @@ -108,18 +108,18 @@ class OSIdentityModel: OSModel { } fireUserStateChanged(newOnesignalId: newOnesignalId, newExternalId: newExternalId) } - + /** Fires the user observer if `onesignal_id` OR `external_id` has changed from the previous snapshot (previous hydration). */ private func fireUserStateChanged(newOnesignalId: String?, newExternalId: String?) { let prevOnesignalId = OneSignalUserDefaults.initShared().getSavedString(forKey: OS_SNAPSHOT_ONESIGNAL_ID, defaultValue: nil) let prevExternalId = OneSignalUserDefaults.initShared().getSavedString(forKey: OS_SNAPSHOT_EXTERNAL_ID, defaultValue: nil) - + guard prevOnesignalId != newOnesignalId || prevExternalId != newExternalId else { return } - + OneSignalUserDefaults.initShared().saveString(forKey: OS_SNAPSHOT_ONESIGNAL_ID, withValue: newOnesignalId) OneSignalUserDefaults.initShared().saveString(forKey: OS_SNAPSHOT_EXTERNAL_ID, withValue: newExternalId) diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSPropertiesModel.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSPropertiesModel.swift index ce35d9eb2..eb9e59bc1 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSPropertiesModel.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSPropertiesModel.swift @@ -78,9 +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/OSSubscriptionModel.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSSubscriptionModel.swift index 805e224b7..89471da2c 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSSubscriptionModel.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSSubscriptionModel.swift @@ -203,7 +203,7 @@ class OSSubscriptionModel: OSModel { self.set(property: "testType", newValue: testType) } } - + var deviceOs = UIDevice.current.systemVersion { didSet { guard deviceOs != oldValue else { @@ -212,7 +212,7 @@ class OSSubscriptionModel: OSModel { self.set(property: "deviceOs", newValue: deviceOs) } } - + var sdk = ONESIGNAL_VERSION { didSet { guard sdk != oldValue else { @@ -221,7 +221,7 @@ class OSSubscriptionModel: OSModel { self.set(property: "sdk", newValue: sdk) } } - + var deviceModel: String? = OSDeviceUtils.getDeviceVariant() { didSet { guard deviceModel != oldValue else { @@ -230,7 +230,7 @@ class OSSubscriptionModel: OSModel { self.set(property: "deviceModel", newValue: deviceModel) } } - + var appVersion: String? = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String { didSet { guard appVersion != oldValue else { @@ -239,7 +239,7 @@ class OSSubscriptionModel: OSModel { self.set(property: "appVersion", newValue: appVersion) } } - + var netType: Int? = OSNetworkingUtils.getNetType() as? Int { didSet { guard netType != oldValue else { @@ -266,7 +266,7 @@ class OSSubscriptionModel: OSModel { if type == .push { let releaseMode: OSUIApplicationReleaseMode = OneSignalMobileProvision.releaseMode() #if targetEnvironment(simulator) - if (releaseMode == OSUIApplicationReleaseMode.UIApplicationReleaseUnknown) { + if releaseMode == OSUIApplicationReleaseMode.UIApplicationReleaseUnknown { self.testType = OSUIApplicationReleaseMode.UIApplicationReleaseDev.rawValue } #endif @@ -322,7 +322,7 @@ class OSSubscriptionModel: OSModel { 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) } @@ -354,7 +354,7 @@ class OSSubscriptionModel: OSModel { } } } - + // Using snake_case so we can use this in request bodies public func jsonRepresentation() -> [String: Any] { var json: [String: Any] = [:] @@ -401,7 +401,7 @@ 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... @@ -415,7 +415,7 @@ extension OSSubscriptionModel { self.testType = OSUIApplicationReleaseMode.UIApplicationReleaseWildcard.rawValue } } - + func update() { updateTestType() deviceOs = UIDevice.current.systemVersion @@ -429,7 +429,7 @@ extension OSSubscriptionModel { OneSignalUserDefaults.initShared().saveString(forKey: OSUD_PUSH_SUBSCRIPTION_ID, withValue: subscriptionId) } } - + enum OSPushPropertyChanged { case subscriptionId(String?) case reachable(Bool) diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSUserRequests.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSUserRequests.swift deleted file mode 100644 index 9c901c5a0..000000000 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSUserRequests.swift +++ /dev/null @@ -1,1416 +0,0 @@ -/* - Modified MIT License - - Copyright 2022 OneSignal - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - 1. The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - 2. All copies of substantial portions of the Software may only be used in connection - with services provided by OneSignal. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - */ - -import OneSignalCore -import OneSignalNotifications -import OneSignalOSCore - -/** - Involved in the login process and responsible for Identify User and Create User. - Can execute `OSRequestCreateUser`, `OSRequestIdentifyUser`, `OSRequestTransferSubscription`, `OSRequestFetchUser`, `OSRequestFetchIdentityBySubscription`. - */ -class OSUserExecutor { - static var userRequestQueue: [OSUserRequest] = [] - static var transferSubscriptionRequestQueue: [OSRequestTransferSubscription] = [] - static var identityModels: [String: OSIdentityModel] = [:] - - // Read in requests from the cache, do not read in FetchUser requests as this is not needed. - static func start() { - var userRequestQueue: [OSUserRequest] = [] - - // Read unfinished Create User + Identify User + Get Identity By Subscription requests from cache, if any... - if let cachedRequestQueue = OneSignalUserDefaults.initShared().getSavedCodeableData(forKey: OS_USER_EXECUTOR_USER_REQUEST_QUEUE_KEY, defaultValue: []) as? [OSUserRequest] { - // Hook each uncached Request to the right model reference - for request in cachedRequestQueue { - if request.isKind(of: OSRequestFetchIdentityBySubscription.self), let req = request as? OSRequestFetchIdentityBySubscription { - if let identityModel = OneSignalUserManagerImpl.sharedInstance.identityModelStore.getModel(modelId: req.identityModel.modelId) { - // 1. The model exist in the store, set it to be the Request's model - req.identityModel = identityModel - } else if let identityModel = identityModels[req.identityModel.modelId] { - // 2. The model exists in the dict of identityModels already processed to use - req.identityModel = identityModel - } else { - // 3. The models do not exist, use the model on the request, and add to dict. - identityModels[req.identityModel.modelId] = req.identityModel - } - userRequestQueue.append(req) - - } else if request.isKind(of: OSRequestCreateUser.self), let req = request as? OSRequestCreateUser { - if let identityModel = OneSignalUserManagerImpl.sharedInstance.identityModelStore.getModel(modelId: req.identityModel.modelId) { - // 1. The model exist in the store, set it to be the Request's model - req.identityModel = identityModel - } else if let identityModel = identityModels[req.identityModel.modelId] { - // 2. The model exists in the dict of identityModels already processed to use - req.identityModel = identityModel - } else { - // 3. The models do not exist, use the model on the request, and add to dict. - identityModels[req.identityModel.modelId] = req.identityModel - } - userRequestQueue.append(req) - - } else if request.isKind(of: OSRequestIdentifyUser.self), let req = request as? OSRequestIdentifyUser { - - if let identityModelToIdentify = identityModels[req.identityModelToIdentify.modelId], - let identityModelToUpdate = OneSignalUserManagerImpl.sharedInstance.identityModelStore.getModel(modelId: req.identityModelToUpdate.modelId) { - // 1. A model exist in the dict and a model exist in the store, set it to be the Request's models - req.identityModelToIdentify = identityModelToIdentify - req.identityModelToUpdate = identityModelToUpdate - } else if let identityModelToIdentify = identityModels[req.identityModelToIdentify.modelId], - let identityModelToUpdate = identityModels[req.identityModelToUpdate.modelId] { - // 2. The two models exist in the dict, set it to be the Request's models - req.identityModelToIdentify = identityModelToIdentify - req.identityModelToUpdate = identityModelToUpdate - } else if let identityModelToIdentify = identityModels[req.identityModelToIdentify.modelId], - identityModels[req.identityModelToUpdate.modelId] == nil { - // 3. A model is in the dict, the other model does not exist - req.identityModelToIdentify = identityModelToIdentify - identityModels[req.identityModelToUpdate.modelId] = req.identityModelToUpdate - } else { - // 4. Both models don't exist yet - identityModels[req.identityModelToIdentify.modelId] = req.identityModelToIdentify - identityModels[req.identityModelToUpdate.modelId] = req.identityModelToUpdate - } - userRequestQueue.append(req) - } - } - } - self.userRequestQueue = userRequestQueue - - // Read unfinished Transfer Subscription requests from cache, if any... - if let transferSubscriptionRequestQueue = OneSignalUserDefaults.initShared().getSavedCodeableData(forKey: OS_USER_EXECUTOR_TRANSFER_SUBSCRIPTION_REQUEST_QUEUE_KEY, defaultValue: []) as? [OSRequestTransferSubscription] { - // We only care about the last transfer subscription request - if let request = transferSubscriptionRequestQueue.last { - // Hook the uncached Request to the model in the store - if request.subscriptionModel.modelId == OneSignalUserManagerImpl.sharedInstance.user.pushSubscriptionModel.modelId { - // The model exist, set it to be the Request's model - request.subscriptionModel = OneSignalUserManagerImpl.sharedInstance.user.pushSubscriptionModel - self.transferSubscriptionRequestQueue = [request] - } else if !request.prepareForExecution() { - // The model do not exist AND this request cannot be sent, drop this Request - OneSignalLog.onesignalLog(.LL_ERROR, message: "OSUserExecutor.start() reading request \(request) from cache failed. Dropping request.") - self.transferSubscriptionRequestQueue = [] - } - } - } else { - OneSignalLog.onesignalLog(.LL_ERROR, message: "OSUserExecutor error encountered reading from cache for \(OS_USER_EXECUTOR_TRANSFER_SUBSCRIPTION_REQUEST_QUEUE_KEY)") - } - - executePendingRequests() - } - - static func appendToQueue(_ request: OSUserRequest) { - if request.isKind(of: OSRequestTransferSubscription.self), let req = request as? OSRequestTransferSubscription { - self.transferSubscriptionRequestQueue.append(req) - OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_USER_EXECUTOR_TRANSFER_SUBSCRIPTION_REQUEST_QUEUE_KEY, withValue: self.transferSubscriptionRequestQueue) - } else { - self.userRequestQueue.append(request) - OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_USER_EXECUTOR_USER_REQUEST_QUEUE_KEY, withValue: self.userRequestQueue) - } - } - - static func removeFromQueue(_ request: OSUserRequest) { - if request.isKind(of: OSRequestTransferSubscription.self), let req = request as? OSRequestTransferSubscription { - transferSubscriptionRequestQueue.removeAll(where: { $0 == req}) - OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_USER_EXECUTOR_TRANSFER_SUBSCRIPTION_REQUEST_QUEUE_KEY, withValue: self.transferSubscriptionRequestQueue) - } else { - userRequestQueue.removeAll(where: { $0 == request}) - OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_USER_EXECUTOR_USER_REQUEST_QUEUE_KEY, withValue: self.userRequestQueue) - } - } - - static func executePendingRequests() { - let requestQueue: [OSUserRequest] = userRequestQueue + transferSubscriptionRequestQueue - - if requestQueue.isEmpty { - return - } - - // Sort the requestQueue by timestamp - for request in requestQueue.sorted(by: { first, second in - return first.timestamp < second.timestamp - }) { - // Return as soon as we reach an un-executable request - if !request.prepareForExecution() { - return - } - - if request.isKind(of: OSRequestFetchIdentityBySubscription.self), let fetchIdentityRequest = request as? OSRequestFetchIdentityBySubscription { - executeFetchIdentityBySubscriptionRequest(fetchIdentityRequest) - return - } else if request.isKind(of: OSRequestCreateUser.self), let createUserRequest = request as? OSRequestCreateUser { - executeCreateUserRequest(createUserRequest) - return - } else if request.isKind(of: OSRequestIdentifyUser.self), let identifyUserRequest = request as? OSRequestIdentifyUser { - executeIdentifyUserRequest(identifyUserRequest) - return - } else if request.isKind(of: OSRequestTransferSubscription.self), let transferSubscriptionRequest = request as? OSRequestTransferSubscription { - executeTransferPushSubscriptionRequest(transferSubscriptionRequest) - return - } else if request.isKind(of: OSRequestFetchUser.self), let fetchUserRequest = request as? OSRequestFetchUser { - executeFetchUserRequest(fetchUserRequest) - return - } else { - // Log Error - OneSignalLog.onesignalLog(.LL_ERROR, message: "OSUserExecutor met incompatible Request type that cannot be executed.") - } - } - } - - /** - 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. - */ - static func parseFetchUserResponse(response: [AnyHashable: Any], identityModel: OSIdentityModel, originalPushToken: String?) { - - // If this was a create user, it hydrates the onesignal_id of the request's identityModel - // The model in the store may be different, and it may be waiting on the onesignal_id of this previous model - if let identityObject = parseIdentityObjectResponse(response) { - identityModel.hydrate(identityObject) - } - - // 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.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.pushSubscriptionModel?.hydrate(subModel) - if let subId = subModel["id"] as? String { - OSNotificationsManager.setPushSubscriptionId(subId) - } - break - } - } - } - - // 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 - } - - if let identityObject = parseIdentityObjectResponse(response) { - OneSignalUserManagerImpl.sharedInstance.user.identityModel.hydrate(identityObject) - } - - if let propertiesObject = parsePropertiesObjectResponse(response) { - OneSignalUserManagerImpl.sharedInstance.user.propertiesModel.hydrate(propertiesObject) - } - - // Now parse email and sms subscriptions - if let subscriptionObject = parseSubscriptionObjectResponse(response) { - let models = OneSignalUserManagerImpl.sharedInstance.subscriptionModelStore.getModels() - for subModel in subscriptionObject { - if let address = subModel["token"] as? String, - let rawType = subModel["type"] as? String, - rawType != "iOSPush", - let type = OSSubscriptionType(rawValue: rawType) { - if let model = models[address] { - // This subscription exists in the store, hydrate - model.hydrate(subModel) - - } else { - // This subscription does not exist in the store, add - OneSignalUserManagerImpl.sharedInstance.subscriptionModelStore.add(id: address, model: OSSubscriptionModel( - type: type, - address: address, - subscriptionId: subModel["id"] as? String, - reachable: true, - isDisabled: false, - changeNotifier: OSEventProducer()), hydrating: true - ) - } - } - } - } - } - - /** - Returns if 2 tokens are equal. This is needed as a nil token is equal to the empty string "". - */ - static func areTokensEqual(tokenA: String?, tokenB: String?) -> Bool { - // They are both strings or both nil - if tokenA == tokenB { - return true - } - // One is nil and the other is "" - if (tokenA == nil && tokenB == "") || (tokenA == "" && tokenB == nil) { - return true - } - return false - } - - static func parseSubscriptionObjectResponse(_ response: [AnyHashable: Any]?) -> [[String: Any]]? { - return response?["subscriptions"] as? [[String: Any]] - } - - static func parsePropertiesObjectResponse(_ response: [AnyHashable: Any]?) -> [String: Any]? { - return response?["properties"] as? [String: Any] - } - - static func parseIdentityObjectResponse(_ response: [AnyHashable: Any]?) -> [String: String]? { - return response?["identity"] as? [String: String] - } - - // We will pass minimal properties to this request - static func createUser(_ user: OSUserInternal) { - let originalPushToken = user.pushSubscriptionModel.address - let request = OSRequestCreateUser(identityModel: user.identityModel, propertiesModel: user.propertiesModel, pushSubscriptionModel: user.pushSubscriptionModel, originalPushToken: originalPushToken) - - appendToQueue(request) - - executePendingRequests() - } - - static func executeCreateUserRequest(_ request: OSRequestCreateUser) { - guard !request.sentToClient else { - return - } - guard request.prepareForExecution() else { - // Currently there are no requirements needed before sending this request - return - } - request.sentToClient = true - - // Hook up push subscription model, it may be updated with a subscription_id, etc. - if let pushSubscriptionModel = OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModelStore.getModel(modelId: request.pushSubscriptionModel.modelId) { - request.pushSubscriptionModel = pushSubscriptionModel - request.updatePushSubscriptionModel(pushSubscriptionModel) - } - - OneSignalClient.shared().execute(request) { response in - removeFromQueue(request) - - // TODO: Differentiate if we need to fetch the user based on response code of 200, 201, 202 - // Create User's response won't send us the user's complete info if this user already exists - if let response = response { - // Parse the response for any data we need to update - parseFetchUserResponse(response: response, identityModel: request.identityModel, originalPushToken: request.originalPushToken) - - // If this user already exists and we logged into an external_id, fetch the user data - // TODO: Only do this if response code is 200 or 202 - // Fetch the user only if its the current user - if OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModel), - let identity = request.parameters?["identity"] as? [String: String], - let externalId = identity[OS_EXTERNAL_ID] { - fetchUser(aliasLabel: OS_EXTERNAL_ID, aliasId: externalId, identityModel: request.identityModel) - } else { - 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 { - // 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() - } - } - } - - static func fetchIdentityBySubscription(_ user: OSUserInternal) { - let request = OSRequestFetchIdentityBySubscription(identityModel: user.identityModel, pushSubscriptionModel: user.pushSubscriptionModel) - - appendToQueue(request) - executePendingRequests() - } - - /** - For migrating legacy players from 3.x to 5.x. This request will fetch the identity object for a subscription ID, and we will use the returned onesignalId to fetch and hydrate the local user. - */ - static func executeFetchIdentityBySubscriptionRequest(_ request: OSRequestFetchIdentityBySubscription) { - guard !request.sentToClient else { - return - } - guard request.prepareForExecution() else { - return - } - request.sentToClient = true - - OneSignalClient.shared().execute(request) { response in - removeFromQueue(request) - - if let identityObject = parseIdentityObjectResponse(response), - let onesignalId = identityObject[OS_ONESIGNAL_ID] - { - request.identityModel.hydrate(identityObject) - - // Fetch this user's data if it is the current user - guard OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModel) - else { - executePendingRequests() - return - } - - fetchUser(aliasLabel: OS_ONESIGNAL_ID, aliasId: onesignalId, identityModel: request.identityModel) - } - } onFailure: { error in - OneSignalLog.onesignalLog(.LL_ERROR, message: "OSUserExecutor executeFetchIdentityBySubscriptionRequest failed with error: \(error.debugDescription)") - if let nsError = error as? NSError { - 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.pushSubscriptionModel?.subscriptionId = nil - removeFromQueue(request) - } - } - executePendingRequests() - } - } - - static func identifyUser(externalId: String, identityModelToIdentify: OSIdentityModel, identityModelToUpdate: OSIdentityModel) { - let request = OSRequestIdentifyUser( - aliasLabel: OS_EXTERNAL_ID, - aliasId: externalId, - identityModelToIdentify: identityModelToIdentify, - identityModelToUpdate: identityModelToUpdate - ) - - appendToQueue(request) - - executePendingRequests() - } - - static func executeIdentifyUserRequest(_ request: OSRequestIdentifyUser) { - guard !request.sentToClient else { - return - } - guard request.prepareForExecution() else { - // Missing onesignal_id - return - } - request.sentToClient = true - - OneSignalClient.shared().execute(request) { _ in - removeFromQueue(request) - - // the anonymous user has been identified, still need to Fetch User as we cleared local data - // Fetch the user only if its the current user - if OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModelToUpdate) { - fetchUser(aliasLabel: OS_EXTERNAL_ID, aliasId: request.aliasId, identityModel: request.identityModelToUpdate) - } else { - executePendingRequests() - } - } onFailure: { error in - OneSignalLog.onesignalLog(.LL_VERBOSE, message: "executeIdentifyUserRequest failed with error \(error.debugDescription)") - if let nsError = error as? NSError { - let responseType = OSNetworkingUtils.getResponseStatusType(nsError.code) - if responseType == .conflict { - // Returns 409 if any provided (label, id) pair exists on another User, so the SDK will switch to this user. - OneSignalLog.onesignalLog(.LL_DEBUG, message: "executeIdentifyUserRequest returned error code user-2. Now handling user-2 error response... switch to this user.") - - removeFromQueue(request) - // Fetch the user only if its the current user - if OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModelToUpdate) { - fetchUser(aliasLabel: OS_EXTERNAL_ID, aliasId: request.aliasId, identityModel: request.identityModelToUpdate) - // TODO: Link ^ to the new user... what was this todo for? - } - transferPushSubscriptionTo(aliasLabel: request.aliasLabel, aliasId: request.aliasId) - } else if responseType == .invalid || responseType == .unauthorized { - // Failed, no retry - removeFromQueue(request) - executePendingRequests() - } else if responseType == .missing { - removeFromQueue(request) - executePendingRequests() - // Logout if the user in the SDK is the same - guard OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModelToUpdate) - else { - return - } - // The subscription has been deleted along with the user, so remove the subscription_id but keep the same push subscription model - OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModel?.subscriptionId = nil - OneSignalUserManagerImpl.sharedInstance._logout() - } - } else { - executePendingRequests() - } - } - } - - static func transferPushSubscriptionTo(aliasLabel: String, aliasId: String) { - // TODO: Where to get pushSubscriptionModel for this request - let request = OSRequestTransferSubscription( - subscriptionModel: OneSignalUserManagerImpl.sharedInstance.user.pushSubscriptionModel, - aliasLabel: aliasLabel, - aliasId: aliasId - ) - - appendToQueue(request) - - executePendingRequests() - } - - static func executeTransferPushSubscriptionRequest(_ request: OSRequestTransferSubscription) { - guard !request.sentToClient else { - return - } - guard request.prepareForExecution() else { - // Missing subscriptionId - OneSignalLog.onesignalLog(.LL_DEBUG, message: "OSUserExecutor.executeTransferPushSubscriptionRequest with request \(request) cannot be executed due to failing prepareForExecution()") - return - } - request.sentToClient = true - OneSignalClient.shared().execute(request) { _ in - removeFromQueue(request) - - // TODO: ... hydrate with returned identity object? - executePendingRequests() - - } onFailure: { error in - OneSignalLog.onesignalLog(.LL_ERROR, message: "OSUserExecutor executeTransferPushSubscriptionRequest 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 - removeFromQueue(request) - } - } - executePendingRequests() - } - } - - 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) - - executePendingRequests() - } - - static func executeFetchUserRequest(_ request: OSRequestFetchUser) { - guard !request.sentToClient else { - return - } - guard request.prepareForExecution() else { - // This should not happen as we set the alias to use for the request path - return - } - request.sentToClient = true - OneSignalClient.shared().execute(request) { response in - removeFromQueue(request) - - if let response = response { - // Clear local data in preparation for hydration - OneSignalUserManagerImpl.sharedInstance.clearUserData() - parseFetchUserResponse(response: response, identityModel: request.identityModel, originalPushToken: OneSignalUserManagerImpl.sharedInstance.pushSubscriptionImpl.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 - OneSignalLog.onesignalLog(.LL_ERROR, message: "OSUserExecutor executeFetchUserRequest failed with error: \(error.debugDescription)") - if let nsError = error as? NSError { - let responseType = OSNetworkingUtils.getResponseStatusType(nsError.code) - if responseType == .missing { - removeFromQueue(request) - // Logout if the user in the SDK is the same - guard OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModel) - else { - return - } - // The subscription has been deleted along with the user, so remove the subscription_id but keep the same push subscription model - OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModel?.subscriptionId = nil - OneSignalUserManagerImpl.sharedInstance._logout() - } else if responseType != .retryable { - // If the error is not retryable, remove from cache and queue - removeFromQueue(request) - } - } - executePendingRequests() - } - } -} - -// MARK: - User Request Classes - -protocol OSUserRequest: OneSignalRequest, NSCoding { - var sentToClient: Bool { get set } - func prepareForExecution() -> Bool -} - -// TODO: Confirm the type of the things in the parameters field -// TODO: Don't hardcode the strings? - -/** - This request will be made with the minimum information needed. The payload will contain an externalId or no identities. - The push subscription may or may not have a token or suscriptionId already. - There will be no properties sent. - */ -class OSRequestCreateUser: OneSignalRequest, OSUserRequest { - var sentToClient = false - let stringDescription: String - override var description: String { - return stringDescription - } - - var identityModel: OSIdentityModel - var pushSubscriptionModel: OSSubscriptionModel - var originalPushToken: String? - - func prepareForExecution() -> Bool { - guard let appId = OneSignalConfigManager.getAppId() else { - OneSignalLog.onesignalLog(.LL_DEBUG, message: "Cannot generate the create user request due to null app ID.") - return false - } - self.addJWTHeader(identityModel: identityModel) - self.path = "apps/\(appId)/users" - // The pushSub doesn't need to have a token. - return true - } - - // When reading from the cache, update the push subscription model - func updatePushSubscriptionModel(_ pushSubscriptionModel: OSSubscriptionModel) { - self.pushSubscriptionModel = pushSubscriptionModel - self.parameters?["subscriptions"] = [pushSubscriptionModel.jsonRepresentation()] - self.originalPushToken = pushSubscriptionModel.address - } - - init(identityModel: OSIdentityModel, propertiesModel: OSPropertiesModel, pushSubscriptionModel: OSSubscriptionModel, originalPushToken: String?) { - self.identityModel = identityModel - self.pushSubscriptionModel = pushSubscriptionModel - self.originalPushToken = originalPushToken - self.stringDescription = "OSRequestCreateUser" - super.init() - - var params: [String: Any] = [:] - - // Identity Object - params["identity"] = [:] - if let externalId = identityModel.externalId { - params["identity"] = [OS_EXTERNAL_ID: externalId] - } - - // Properties Object - var propertiesObject: [String: Any] = [:] - propertiesObject["language"] = propertiesModel.language - propertiesObject["timezone_id"] = propertiesModel.timezoneId - params["properties"] = propertiesObject - - self.parameters = params - self.updatePushSubscriptionModel(pushSubscriptionModel) - self.method = POST - } - - func encode(with coder: NSCoder) { - coder.encode(identityModel, forKey: "identityModel") - coder.encode(pushSubscriptionModel, forKey: "pushSubscriptionModel") - coder.encode(originalPushToken, forKey: "originalPushToken") - coder.encode(parameters, forKey: "parameters") - coder.encode(method.rawValue, forKey: "method") // Encodes as String - coder.encode(path, forKey: "path") - coder.encode(timestamp, forKey: "timestamp") - } - - required init?(coder: NSCoder) { - guard - let identityModel = coder.decodeObject(forKey: "identityModel") as? OSIdentityModel, - let pushSubscriptionModel = coder.decodeObject(forKey: "pushSubscriptionModel") as? OSSubscriptionModel, - let parameters = coder.decodeObject(forKey: "parameters") as? [String: Any], - let rawMethod = coder.decodeObject(forKey: "method") as? UInt32, - let path = coder.decodeObject(forKey: "path") as? String, - let timestamp = coder.decodeObject(forKey: "timestamp") as? Date - else { - // Log error - return nil - } - self.identityModel = identityModel - self.pushSubscriptionModel = pushSubscriptionModel - self.originalPushToken = coder.decodeObject(forKey: "originalPushToken") as? String - self.stringDescription = "OSRequestCreateUser" - super.init() - self.parameters = parameters - self.method = HTTPMethod(rawValue: rawMethod) - self.path = path - self.timestamp = timestamp - } -} - -class OSRequestFetchIdentityBySubscription: OneSignalRequest, OSUserRequest { - var sentToClient = false - let stringDescription: String - - override var description: String { - return stringDescription - } - - var identityModel: OSIdentityModel - var pushSubscriptionModel: OSSubscriptionModel - - func prepareForExecution() -> Bool { - guard let appId = OneSignalConfigManager.getAppId() else { - OneSignalLog.onesignalLog(.LL_DEBUG, message: "Cannot generate the FetchIdentityBySubscription request due to null app ID.") - return false - } - - if let subscriptionId = pushSubscriptionModel.subscriptionId { - self.path = "apps/\(appId)/subscriptions/\(subscriptionId)/user/identity" - return true - } else { - // This is an error, and should never happen - OneSignalLog.onesignalLog(.LL_ERROR, message: "Cannot generate the FetchIdentityBySubscription request due to null subscriptionId.") - self.path = "" - return false - } - } - - init(identityModel: OSIdentityModel, pushSubscriptionModel: OSSubscriptionModel) { - self.identityModel = identityModel - self.pushSubscriptionModel = pushSubscriptionModel - self.stringDescription = "OSRequestFetchIdentityBySubscription with subscriptionId: \(pushSubscriptionModel.subscriptionId ?? "nil")" - super.init() - self.method = GET - } - - func encode(with coder: NSCoder) { - coder.encode(identityModel, forKey: "identityModel") - coder.encode(pushSubscriptionModel, forKey: "pushSubscriptionModel") - coder.encode(method.rawValue, forKey: "method") // Encodes as String - coder.encode(timestamp, forKey: "timestamp") - } - - required init?(coder: NSCoder) { - guard - let identityModel = coder.decodeObject(forKey: "identityModel") as? OSIdentityModel, - let pushSubscriptionModel = coder.decodeObject(forKey: "pushSubscriptionModel") as? OSSubscriptionModel, - let rawMethod = coder.decodeObject(forKey: "method") as? UInt32, - let timestamp = coder.decodeObject(forKey: "timestamp") as? Date - else { - // Log error - return nil - } - self.identityModel = identityModel - self.pushSubscriptionModel = pushSubscriptionModel - - self.stringDescription = "OSRequestFetchIdentityBySubscription with subscriptionId: \(pushSubscriptionModel.subscriptionId ?? "nil")" - super.init() - self.method = HTTPMethod(rawValue: rawMethod) - self.timestamp = timestamp - } -} - -/** - The `identityModelToIdentify` is used for the `onesignal_id` of the user we want to associate with this alias. - This request will tell us if we should continue with the previous user who is now identitfied, or to change users to the one this alias already exists on. - - Note: The SDK needs an user to operate on before this request returns. However, at the time of this request's creation, the SDK does not know if there is already an user associated with this alias. So, it creates a blank new user (whose identity model is passed in as `identityModelToUpdate`, - which is the model used to make a subsequent ``OSRequestFetchUser``). - */ -class OSRequestIdentifyUser: OneSignalRequest, OSUserRequest { - var sentToClient = false - let stringDescription: String - override var description: String { - return stringDescription - } - - var identityModelToIdentify: OSIdentityModel - var identityModelToUpdate: OSIdentityModel - let aliasLabel: String - let aliasId: String - - // requires a onesignal_id to send this request - func prepareForExecution() -> Bool { - if let onesignalId = identityModelToIdentify.onesignalId, let appId = OneSignalConfigManager.getAppId() { - self.addJWTHeader(identityModel: identityModelToIdentify) - self.path = "apps/\(appId)/users/by/\(OS_ONESIGNAL_ID)/\(onesignalId)/identity" - return true - } else { - // self.path is non-nil, so set to empty string - self.path = "" - return false - } - } - - /** - - Parameters: - - aliasLabel: The alias label we want to identify this user with. - - aliasId: The alias ID we want to identify this user with. - - identityModelToIdentify: Belongs to the user we want to identify with this alias. - - identityModelToUpdate: Belongs to the user we want to send in the subsequent ``OSRequestFetchUser`` that is made when this request returns. - */ - init(aliasLabel: String, aliasId: String, identityModelToIdentify: OSIdentityModel, identityModelToUpdate: OSIdentityModel) { - self.identityModelToIdentify = identityModelToIdentify - self.identityModelToUpdate = identityModelToUpdate - self.aliasLabel = aliasLabel - self.aliasId = aliasId - self.stringDescription = "OSRequestIdentifyUser with aliasLabel: \(aliasLabel) aliasId: \(aliasId)" - super.init() - self.parameters = ["identity": [aliasLabel: aliasId]] - self.method = PATCH - _ = prepareForExecution() // sets the path property - } - - func encode(with coder: NSCoder) { - coder.encode(identityModelToIdentify, forKey: "identityModelToIdentify") - coder.encode(identityModelToUpdate, forKey: "identityModelToUpdate") - coder.encode(aliasLabel, forKey: "aliasLabel") - coder.encode(aliasId, forKey: "aliasId") - coder.encode(parameters, forKey: "parameters") - coder.encode(method.rawValue, forKey: "method") // Encodes as String - coder.encode(timestamp, forKey: "timestamp") - } - - required init?(coder: NSCoder) { - guard - let identityModelToIdentify = coder.decodeObject(forKey: "identityModelToIdentify") as? OSIdentityModel, - let identityModelToUpdate = coder.decodeObject(forKey: "identityModelToUpdate") as? OSIdentityModel, - let aliasLabel = coder.decodeObject(forKey: "aliasLabel") as? String, - let aliasId = coder.decodeObject(forKey: "aliasId") as? String, - let parameters = coder.decodeObject(forKey: "parameters") as? [String: [String: String]], - let rawMethod = coder.decodeObject(forKey: "method") as? UInt32, - let timestamp = coder.decodeObject(forKey: "timestamp") as? Date - else { - // Log error - return nil - } - self.identityModelToIdentify = identityModelToIdentify - self.identityModelToUpdate = identityModelToUpdate - self.aliasLabel = aliasLabel - self.aliasId = aliasId - self.stringDescription = "OSRequestIdentifyUser with aliasLabel: \(aliasLabel) aliasId: \(aliasId)" - super.init() - self.timestamp = timestamp - self.parameters = parameters - self.method = HTTPMethod(rawValue: rawMethod) - _ = prepareForExecution() - } -} - -/** - If an alias is passed in, it will be used to fetch the user. If not, then by default, use the `onesignal_id` in the `identityModel` to fetch the user. - The `identityModel` is also used to reference the user that is updated with the response. - */ -class OSRequestFetchUser: OneSignalRequest, OSUserRequest { - var sentToClient = false - let stringDescription: String - override var description: String { - return stringDescription - } - - 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.") - return false - } - self.addJWTHeader(identityModel: identityModel) - self.path = "apps/\(appId)/users/by/\(aliasLabel)/\(aliasId)" - return true - } - - 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 - _ = prepareForExecution() // sets the path property - } - - func encode(with coder: NSCoder) { - 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") - } - - required init?(coder: NSCoder) { - guard - let identityModel = coder.decodeObject(forKey: "identityModel") as? OSIdentityModel, - let aliasLabel = coder.decodeObject(forKey: "aliasLabel") as? String, - let aliasId = coder.decodeObject(forKey: "aliasId") as? String, - let rawMethod = coder.decodeObject(forKey: "method") as? UInt32, - let timestamp = coder.decodeObject(forKey: "timestamp") as? Date - else { - // Log error - return nil - } - 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) - self.timestamp = timestamp - _ = prepareForExecution() - } -} - -class OSRequestAddAliases: OneSignalRequest, OSUserRequest { - var sentToClient = false - let stringDescription: String - override var description: String { - return stringDescription - } - - var identityModel: OSIdentityModel - let aliases: [String: String] - - // requires a `onesignal_id` to send this request - func prepareForExecution() -> Bool { - if let onesignalId = identityModel.onesignalId, let appId = OneSignalConfigManager.getAppId() { - self.addJWTHeader(identityModel: identityModel) - self.path = "apps/\(appId)/users/by/\(OS_ONESIGNAL_ID)/\(onesignalId)/identity" - return true - } else { - // self.path is non-nil, so set to empty string - self.path = "" - return false - } - } - - init(aliases: [String: String], identityModel: OSIdentityModel) { - self.identityModel = identityModel - self.aliases = aliases - self.stringDescription = "OSRequestAddAliases with aliases: \(aliases)" - super.init() - self.parameters = ["identity": aliases] - self.method = PATCH - _ = prepareForExecution() // sets the path property - } - - func encode(with coder: NSCoder) { - coder.encode(identityModel, forKey: "identityModel") - coder.encode(aliases, forKey: "aliases") - coder.encode(parameters, forKey: "parameters") - coder.encode(method.rawValue, forKey: "method") // Encodes as String - coder.encode(timestamp, forKey: "timestamp") - } - - required init?(coder: NSCoder) { - guard - let identityModel = coder.decodeObject(forKey: "identityModel") as? OSIdentityModel, - let aliases = coder.decodeObject(forKey: "aliases") as? [String: String], - let rawMethod = coder.decodeObject(forKey: "method") as? UInt32, - let parameters = coder.decodeObject(forKey: "parameters") as? [String: [String: String]], - let timestamp = coder.decodeObject(forKey: "timestamp") as? Date - else { - // Log error - return nil - } - self.identityModel = identityModel - self.aliases = aliases - self.stringDescription = "OSRequestAddAliases with parameters: \(parameters)" - super.init() - self.parameters = parameters - self.method = HTTPMethod(rawValue: rawMethod) - self.timestamp = timestamp - _ = prepareForExecution() - } -} - -class OSRequestRemoveAlias: OneSignalRequest, OSUserRequest { - var sentToClient = false - let stringDescription: String - override var description: String { - return stringDescription - } - - let labelToRemove: String - var identityModel: OSIdentityModel - - func prepareForExecution() -> Bool { - if let onesignalId = identityModel.onesignalId, let appId = OneSignalConfigManager.getAppId() { - self.addJWTHeader(identityModel: identityModel) - self.path = "apps/\(appId)/users/by/\(OS_ONESIGNAL_ID)/\(onesignalId)/identity/\(labelToRemove)" - return true - } else { - // self.path is non-nil, so set to empty string - self.path = "" - return false - } - } - - init(labelToRemove: String, identityModel: OSIdentityModel) { - self.labelToRemove = labelToRemove - self.identityModel = identityModel - self.stringDescription = "OSRequestRemoveAlias with aliasLabel: \(labelToRemove)" - super.init() - self.method = DELETE - _ = prepareForExecution() // sets the path property - } - - func encode(with coder: NSCoder) { - coder.encode(labelToRemove, forKey: "labelToRemove") - coder.encode(identityModel, forKey: "identityModel") - coder.encode(method.rawValue, forKey: "method") // Encodes as String - coder.encode(timestamp, forKey: "timestamp") - } - - required init?(coder: NSCoder) { - guard - let labelToRemove = coder.decodeObject(forKey: "labelToRemove") as? String, - let identityModel = coder.decodeObject(forKey: "identityModel") as? OSIdentityModel, - let rawMethod = coder.decodeObject(forKey: "method") as? UInt32, - let timestamp = coder.decodeObject(forKey: "timestamp") as? Date - else { - // Log error - return nil - } - self.labelToRemove = labelToRemove - self.identityModel = identityModel - self.stringDescription = "OSRequestRemoveAlias with aliasLabel: \(labelToRemove)" - super.init() - self.method = HTTPMethod(rawValue: rawMethod) - self.timestamp = timestamp - _ = prepareForExecution() - } -} - -class OSRequestUpdateProperties: OneSignalRequest, OSUserRequest { - var sentToClient = false - let stringDescription: String - override var description: String { - return stringDescription - } - - // TODO: does updating properties even have a response in which we need to hydrate from? Then we can get rid of modelToUpdate - // Yes we may, if we cleared local state - var modelToUpdate: OSPropertiesModel - var identityModel: OSIdentityModel - - // TODO: Decide if addPushSubscriptionIdToAdditionalHeadersIfNeeded should block. - // Note Android adds it to requests, if the push sub ID exists - func prepareForExecution() -> Bool { - if let onesignalId = identityModel.onesignalId, - let appId = OneSignalConfigManager.getAppId(), - addPushSubscriptionIdToAdditionalHeadersIfNeeded() { - self.addJWTHeader(identityModel: identityModel) - self.path = "apps/\(appId)/users/by/\(OS_ONESIGNAL_ID)/\(onesignalId)" - return true - } else { - // self.path is non-nil, so set to empty string - self.path = "" - return false - } - } - - func addPushSubscriptionIdToAdditionalHeadersIfNeeded() -> Bool { - guard let parameters = self.parameters else { - return true - } - if parameters["deltas"] != nil { // , !parameters["deltas"].isEmpty - if let pushSubscriptionId = OneSignalUserManagerImpl.sharedInstance.pushSubscriptionId { - var additionalHeaders = self.additionalHeaders ?? [String: String]() - additionalHeaders["OneSignal-Subscription-Id"] = pushSubscriptionId - self.additionalHeaders = additionalHeaders - return true - } else { - return false - } - } - return true - } - - init(properties: [String: Any], deltas: [String: Any]?, refreshDeviceMetadata: Bool?, modelToUpdate: OSPropertiesModel, identityModel: OSIdentityModel) { - self.modelToUpdate = modelToUpdate - self.identityModel = identityModel - self.stringDescription = "OSRequestUpdateProperties with properties: \(properties) deltas: \(String(describing: deltas)) refreshDeviceMetadata: \(String(describing: refreshDeviceMetadata))" - super.init() - - var propertiesObject = properties - if let location = propertiesObject["location"] as? OSLocationPoint { - propertiesObject["lat"] = location.lat - propertiesObject["long"] = location.long - propertiesObject.removeValue(forKey: "location") - } - var params: [String: Any] = [:] - params["properties"] = propertiesObject - params["refresh_device_metadata"] = refreshDeviceMetadata - if let deltas = deltas { - params["deltas"] = deltas - } - self.parameters = params - self.method = PATCH - _ = prepareForExecution() // sets the path property - } - - func encode(with coder: NSCoder) { - coder.encode(modelToUpdate, forKey: "modelToUpdate") - coder.encode(identityModel, forKey: "identityModel") - coder.encode(parameters, forKey: "parameters") - coder.encode(method.rawValue, forKey: "method") // Encodes as String - coder.encode(timestamp, forKey: "timestamp") - } - - required init?(coder: NSCoder) { - guard - let modelToUpdate = coder.decodeObject(forKey: "modelToUpdate") as? OSPropertiesModel, - let identityModel = coder.decodeObject(forKey: "identityModel") as? OSIdentityModel, - let rawMethod = coder.decodeObject(forKey: "method") as? UInt32, - let parameters = coder.decodeObject(forKey: "parameters") as? [String: Any], - let timestamp = coder.decodeObject(forKey: "timestamp") as? Date - else { - // Log error - return nil - } - self.modelToUpdate = modelToUpdate - self.identityModel = identityModel - self.stringDescription = "OSRequestUpdateProperties with parameters: \(parameters)" - super.init() - self.parameters = parameters - self.method = HTTPMethod(rawValue: rawMethod) - self.timestamp = timestamp - _ = prepareForExecution() - } -} - -/** - 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 - let stringDescription: String - override var description: String { - return stringDescription - } - - var subscriptionModel: OSSubscriptionModel - var identityModel: OSIdentityModel - - // Need the onesignal_id of the user - func prepareForExecution() -> Bool { - if let onesignalId = identityModel.onesignalId, let appId = OneSignalConfigManager.getAppId() { - self.addJWTHeader(identityModel: identityModel) - self.path = "apps/\(appId)/users/by/\(OS_ONESIGNAL_ID)/\(onesignalId)/subscriptions" - return true - } else { - self.path = "" // self.path is non-nil, so set to empty string - return false - } - } - - init(subscriptionModel: OSSubscriptionModel, identityModel: OSIdentityModel) { - self.subscriptionModel = subscriptionModel - self.identityModel = identityModel - self.stringDescription = "OSRequestCreateSubscription with subscriptionModel: \(subscriptionModel.address ?? "nil")" - super.init() - self.parameters = ["subscription": subscriptionModel.jsonRepresentation()] - self.method = POST - _ = prepareForExecution() // sets the path property - } - - func encode(with coder: NSCoder) { - coder.encode(subscriptionModel, forKey: "subscriptionModel") - coder.encode(identityModel, forKey: "identityModel") - coder.encode(parameters, forKey: "parameters") - coder.encode(method.rawValue, forKey: "method") // Encodes as String - coder.encode(timestamp, forKey: "timestamp") - } - - required init?(coder: NSCoder) { - guard - let subscriptionModel = coder.decodeObject(forKey: "subscriptionModel") as? OSSubscriptionModel, - let identityModel = coder.decodeObject(forKey: "identityModel") as? OSIdentityModel, - let rawMethod = coder.decodeObject(forKey: "method") as? UInt32, - let parameters = coder.decodeObject(forKey: "parameters") as? [String: Any], - let timestamp = coder.decodeObject(forKey: "timestamp") as? Date - else { - // Log error - return nil - } - self.subscriptionModel = subscriptionModel - self.identityModel = identityModel - self.stringDescription = "OSRequestCreateSubscription with subscriptionModel: \(subscriptionModel.address ?? "nil")" - super.init() - self.parameters = parameters - self.method = HTTPMethod(rawValue: rawMethod) - self.timestamp = timestamp - _ = prepareForExecution() - } -} - -/** - Transfers the Subscription specified by the subscriptionId to the User identified by the identity in the payload. - Only one entry is allowed, `onesignal_id` or an Alias. We will use the alias specified. - The anticipated usage of this request is only for push subscriptions. - */ -class OSRequestTransferSubscription: OneSignalRequest, OSUserRequest { - var sentToClient = false - let stringDescription: String - override var description: String { - return stringDescription - } - - var subscriptionModel: OSSubscriptionModel - let aliasLabel: String - let aliasId: String - - // Need an alias and subscription_id - func prepareForExecution() -> Bool { - if let subscriptionId = subscriptionModel.subscriptionId, let appId = OneSignalConfigManager.getAppId() { - self.path = "apps/\(appId)/subscriptions/\(subscriptionId)/owner" - // TODO: self.addJWTHeader(identityModel: identityModel) ?? - return true - } else { - self.path = "" // self.path is non-nil, so set to empty string - return false - } - } - - /** - Must pass an Alias pair to identify the User. - */ - init( - subscriptionModel: OSSubscriptionModel, - aliasLabel: String, - aliasId: String - ) { - self.subscriptionModel = subscriptionModel - self.aliasLabel = aliasLabel - self.aliasId = aliasId - self.stringDescription = "OSRequestTransferSubscription" - super.init() - self.parameters = ["identity": [aliasLabel: aliasId]] - self.method = PATCH - _ = prepareForExecution() // sets the path property - } - - func encode(with coder: NSCoder) { - coder.encode(subscriptionModel, forKey: "subscriptionModel") - coder.encode(aliasLabel, forKey: "aliasLabel") - coder.encode(aliasId, forKey: "aliasId") - coder.encode(parameters, forKey: "parameters") - coder.encode(method.rawValue, forKey: "method") // Encodes as String - coder.encode(timestamp, forKey: "timestamp") - } - - required init?(coder: NSCoder) { - guard - let subscriptionModel = coder.decodeObject(forKey: "subscriptionModel") as? OSSubscriptionModel, - let aliasLabel = coder.decodeObject(forKey: "aliasLabel") as? String, - let aliasId = coder.decodeObject(forKey: "aliasId") as? String, - let rawMethod = coder.decodeObject(forKey: "method") as? UInt32, - let parameters = coder.decodeObject(forKey: "parameters") as? [String: Any], - let timestamp = coder.decodeObject(forKey: "timestamp") as? Date - else { - // Log error - return nil - } - self.subscriptionModel = subscriptionModel - self.aliasLabel = aliasLabel - self.aliasId = aliasId - self.stringDescription = "OSRequestTransferSubscription" - super.init() - self.parameters = parameters - self.method = HTTPMethod(rawValue: rawMethod) - self.timestamp = timestamp - _ = prepareForExecution() - } -} - -/** - Currently, only the Push Subscription will make this Update Request. - */ -class OSRequestUpdateSubscription: OneSignalRequest, OSUserRequest { - var sentToClient = false - let stringDescription: String - override var description: String { - return stringDescription - } - - var subscriptionModel: OSSubscriptionModel - - // Need the subscription_id - func prepareForExecution() -> Bool { - if let subscriptionId = subscriptionModel.subscriptionId, let appId = OneSignalConfigManager.getAppId() { - self.path = "apps/\(appId)/subscriptions/\(subscriptionId)" - return true - } else { - self.path = "" // self.path is non-nil, so set to empty string - return false - } - } - - // TODO: just need the sub model and send it - // But the model may be outdated or not sync with the subscriptionObject - init(subscriptionObject: [String: Any], subscriptionModel: OSSubscriptionModel) { - self.subscriptionModel = subscriptionModel - self.stringDescription = "OSRequestUpdateSubscription with subscriptionObject: \(subscriptionObject)" - super.init() - - // Rename "address" key as "token", if it exists - var subscriptionParams = subscriptionObject - subscriptionParams.removeValue(forKey: "address") - subscriptionParams.removeValue(forKey: "notificationTypes") - subscriptionParams["token"] = subscriptionModel.address - subscriptionParams["device_os"] = subscriptionModel.deviceOs - subscriptionParams["sdk"] = subscriptionModel.sdk - subscriptionParams["app_version"] = subscriptionModel.appVersion - - // notificationTypes defaults to -1 instead of nil, don't send if it's -1 - if subscriptionModel.notificationTypes != -1 { - subscriptionParams["notification_types"] = subscriptionModel.notificationTypes - } - - subscriptionParams["enabled"] = subscriptionModel.enabled - // TODO: The above is not quite right. If we hydrate, we will over-write any pending updates - // May use subscriptionObject, but enabled and notification_types should be sent together... - - self.parameters = ["subscription": subscriptionParams] - self.method = PATCH - _ = prepareForExecution() // sets the path property - } - - func encode(with coder: NSCoder) { - coder.encode(subscriptionModel, forKey: "subscriptionModel") - coder.encode(parameters, forKey: "parameters") - coder.encode(method.rawValue, forKey: "method") // Encodes as String - coder.encode(timestamp, forKey: "timestamp") - } - - required init?(coder: NSCoder) { - guard - let subscriptionModel = coder.decodeObject(forKey: "subscriptionModel") as? OSSubscriptionModel, - let rawMethod = coder.decodeObject(forKey: "method") as? UInt32, - let parameters = coder.decodeObject(forKey: "parameters") as? [String: Any], - let timestamp = coder.decodeObject(forKey: "timestamp") as? Date - else { - // Log error - return nil - } - self.subscriptionModel = subscriptionModel - self.stringDescription = "OSRequestUpdateSubscription with parameters: \(parameters)" - super.init() - self.parameters = parameters - self.method = HTTPMethod(rawValue: rawMethod) - self.timestamp = timestamp - _ = prepareForExecution() - } -} - -/** - Delete the subscription specified by the `subscriptionId` in the `subscriptionModel`. - Prior to the creation of this request, this model has already been removed from the model store. - - Remark: If this model did not already exist in the store, no request is created. - */ -class OSRequestDeleteSubscription: OneSignalRequest, OSUserRequest { - var sentToClient = false - let stringDescription: String - override var description: String { - return stringDescription - } - - var subscriptionModel: OSSubscriptionModel - - // Need the subscription_id - func prepareForExecution() -> Bool { - if let subscriptionId = subscriptionModel.subscriptionId, let appId = OneSignalConfigManager.getAppId() { - self.path = "apps/\(appId)/subscriptions/\(subscriptionId)" - return true - } else { - // self.path is non-nil, so set to empty string - self.path = "" - return false - } - } - - init(subscriptionModel: OSSubscriptionModel) { - self.subscriptionModel = subscriptionModel - self.stringDescription = "OSRequestDeleteSubscription with subscriptionModel: \(subscriptionModel.address ?? "nil")" - super.init() - self.method = DELETE - _ = prepareForExecution() // sets the path property - } - - func encode(with coder: NSCoder) { - coder.encode(subscriptionModel, forKey: "subscriptionModel") - coder.encode(method.rawValue, forKey: "method") // Encodes as String - coder.encode(timestamp, forKey: "timestamp") - } - - required init?(coder: NSCoder) { - guard - let subscriptionModel = coder.decodeObject(forKey: "subscriptionModel") as? OSSubscriptionModel, - let rawMethod = coder.decodeObject(forKey: "method") as? UInt32, - let timestamp = coder.decodeObject(forKey: "timestamp") as? Date - else { - // Log error - return nil - } - self.subscriptionModel = subscriptionModel - self.stringDescription = "OSRequestDeleteSubscription with subscriptionModel: \(subscriptionModel.address ?? "nil")" - super.init() - self.method = HTTPMethod(rawValue: rawMethod) - self.timestamp = timestamp - _ = prepareForExecution() - } -} - -internal extension OneSignalRequest { - func addJWTHeader(identityModel: OSIdentityModel) { -// guard let token = identityModel.jwtBearerToken else { -// return -// } -// var additionalHeaders = self.additionalHeaders ?? [String:String]() -// additionalHeaders["Authorization"] = "Bearer \(token)" -// self.additionalHeaders = additionalHeaders - } -} diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUserManagerImpl.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUserManagerImpl.swift index 411f0b844..36d8ceaff 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUserManagerImpl.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUserManagerImpl.swift @@ -107,7 +107,7 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager { var pushSubscriptionModel: OSSubscriptionModel? { return pushSubscriptionModelStore.getModel(key: OS_PUSH_SUBSCRIPTION_MODEL_KEY) } - + @objc public var pushSubscriptionId: String? { return _user?.pushSubscriptionModel.subscriptionId } @@ -115,7 +115,7 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager { @objc public var language: String? { return _user?.propertiesModel.language } - + @objc public let pushSubscriptionImpl: OSPushSubscriptionImpl private var hasCalledStart = false @@ -203,7 +203,7 @@ 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], @@ -273,14 +273,14 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager { private func createUserFromLegacyPlayer(_ playerId: String) { // 1. Create the Push Subscription Model let pushSubscriptionModel = createDefaultPushSubscription(subscriptionId: playerId) - + // 2. Set the internal user let newUser = setNewInternalUser(externalId: nil, pushSubscriptionModel: pushSubscriptionModel) // 3. Make the request OSUserExecutor.fetchIdentityBySubscription(newUser) } - + private func createNewUser(externalId: String?, token: String?) -> OSUserInternal { guard !OneSignalConfigManager.shouldAwaitAppIdAndLogMissingPrivacyConsent(forMethod: nil) else { return _mockUser @@ -341,7 +341,7 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager { func isCurrentUser(_ identityModel: OSIdentityModel) -> Bool { return self.identityModelStore.getModel(modelId: identityModel.modelId) != nil } - + /** Clears the existing user's data in preparation for hydration via a fetch user call. */ @@ -466,14 +466,13 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager { func createPushSubscriptionRequest() { // subscriptionExecutor should exist as this should be called after `start()` has been called if let subscriptionExecutor = self.subscriptionExecutor, - let subscriptionModel = pushSubscriptionModel - { + 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 getTagsInternal() -> [String: String]? { guard let user = _user else { @@ -544,7 +543,7 @@ extension OneSignalUserManagerImpl { return } start() - + OSUserExecutor.executePendingRequests() OSOperationRepo.sharedInstance.paused = false updateSession(sessionCount: 1, sessionTime: nil, refreshDeviceMetadata: true) @@ -613,26 +612,26 @@ extension OneSignalUserManagerImpl: OSUser { start() return pushSubscriptionImpl } - + public var externalId: String? { guard !OneSignalConfigManager.shouldAwaitAppIdAndLogMissingPrivacyConsent(forMethod: "externalId") else { return nil } return _user?.identityModel.externalId } - + public var onesignalId: String? { guard !OneSignalConfigManager.shouldAwaitAppIdAndLogMissingPrivacyConsent(forMethod: "onesignalId") else { return nil } return _user?.identityModel.onesignalId } - + public func addObserver(_ observer: OSUserStateObserver) { // This is a method in the User namespace that doesn't require privacy consent first self.userStateChangesObserver.addObserver(observer) } - + public func removeObserver(_ observer: OSUserStateObserver) { self.userStateChangesObserver.removeObserver(observer) } @@ -783,7 +782,7 @@ extension OneSignalUserManagerImpl { public class OSPushSubscriptionImpl: NSObject, OSPushSubscription { let pushSubscriptionModelStore: OSModelStore - + private var _pushSubscriptionStateChangesObserver: OSObservable? var pushSubscriptionStateChangesObserver: OSObservable { if let observer = _pushSubscriptionStateChangesObserver { @@ -794,16 +793,16 @@ extension OneSignalUserManagerImpl { return pushSubscriptionStateChangesObserver } - + init(pushSubscriptionModelStore: OSModelStore) { self.pushSubscriptionModelStore = pushSubscriptionModelStore } - + public func addObserver(_ observer: OSPushSubscriptionObserver) { // This is a method in the User namespace that doesn't require privacy consent first self.pushSubscriptionStateChangesObserver.addObserver(observer) } - + public func removeObserver(_ observer: OSPushSubscriptionObserver) { self.pushSubscriptionStateChangesObserver.removeObserver(observer) } @@ -828,7 +827,7 @@ extension OneSignalUserManagerImpl { } return pushSubscriptionModelStore.getModel(key: OS_PUSH_SUBSCRIPTION_MODEL_KEY)?.optedIn ?? false } - + /** Enable the push subscription, and prompts if needed. `optedIn` can still be `false` after `optIn()` is called if permission is not granted. */ diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestAddAliases.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestAddAliases.swift new file mode 100644 index 000000000..d70ca8a6f --- /dev/null +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestAddAliases.swift @@ -0,0 +1,91 @@ +/* + Modified MIT License + + Copyright 2023 OneSignal + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + 1. The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 2. All copies of substantial portions of the Software may only be used in connection + with services provided by OneSignal. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +import OneSignalCore + +class OSRequestAddAliases: OneSignalRequest, OSUserRequest { + var sentToClient = false + let stringDescription: String + override var description: String { + return stringDescription + } + + var identityModel: OSIdentityModel + let aliases: [String: String] + + // requires a `onesignal_id` to send this request + func prepareForExecution() -> Bool { + if let onesignalId = identityModel.onesignalId, let appId = OneSignalConfigManager.getAppId() { + self.addJWTHeader(identityModel: identityModel) + self.path = "apps/\(appId)/users/by/\(OS_ONESIGNAL_ID)/\(onesignalId)/identity" + return true + } else { + // self.path is non-nil, so set to empty string + self.path = "" + return false + } + } + + init(aliases: [String: String], identityModel: OSIdentityModel) { + self.identityModel = identityModel + self.aliases = aliases + self.stringDescription = "OSRequestAddAliases with aliases: \(aliases)" + super.init() + self.parameters = ["identity": aliases] + self.method = PATCH + _ = prepareForExecution() // sets the path property + } + + func encode(with coder: NSCoder) { + coder.encode(identityModel, forKey: "identityModel") + coder.encode(aliases, forKey: "aliases") + coder.encode(parameters, forKey: "parameters") + coder.encode(method.rawValue, forKey: "method") // Encodes as String + coder.encode(timestamp, forKey: "timestamp") + } + + required init?(coder: NSCoder) { + guard + let identityModel = coder.decodeObject(forKey: "identityModel") as? OSIdentityModel, + let aliases = coder.decodeObject(forKey: "aliases") as? [String: String], + let rawMethod = coder.decodeObject(forKey: "method") as? UInt32, + let parameters = coder.decodeObject(forKey: "parameters") as? [String: [String: String]], + let timestamp = coder.decodeObject(forKey: "timestamp") as? Date + else { + // Log error + return nil + } + self.identityModel = identityModel + self.aliases = aliases + self.stringDescription = "OSRequestAddAliases with parameters: \(parameters)" + super.init() + self.parameters = parameters + self.method = HTTPMethod(rawValue: rawMethod) + self.timestamp = timestamp + _ = prepareForExecution() + } +} diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestCreateSubscription.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestCreateSubscription.swift new file mode 100644 index 000000000..6128c475b --- /dev/null +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestCreateSubscription.swift @@ -0,0 +1,95 @@ +/* + Modified MIT License + + Copyright 2023 OneSignal + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + 1. The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 2. All copies of substantial portions of the Software may only be used in connection + with services provided by OneSignal. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +import OneSignalCore + +/** + 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 + let stringDescription: String + override var description: String { + return stringDescription + } + + var subscriptionModel: OSSubscriptionModel + var identityModel: OSIdentityModel + + // Need the onesignal_id of the user + func prepareForExecution() -> Bool { + if let onesignalId = identityModel.onesignalId, let appId = OneSignalConfigManager.getAppId() { + self.addJWTHeader(identityModel: identityModel) + self.path = "apps/\(appId)/users/by/\(OS_ONESIGNAL_ID)/\(onesignalId)/subscriptions" + return true + } else { + self.path = "" // self.path is non-nil, so set to empty string + return false + } + } + + init(subscriptionModel: OSSubscriptionModel, identityModel: OSIdentityModel) { + self.subscriptionModel = subscriptionModel + self.identityModel = identityModel + self.stringDescription = "OSRequestCreateSubscription with subscriptionModel: \(subscriptionModel.address ?? "nil")" + super.init() + self.parameters = ["subscription": subscriptionModel.jsonRepresentation()] + self.method = POST + _ = prepareForExecution() // sets the path property + } + + func encode(with coder: NSCoder) { + coder.encode(subscriptionModel, forKey: "subscriptionModel") + coder.encode(identityModel, forKey: "identityModel") + coder.encode(parameters, forKey: "parameters") + coder.encode(method.rawValue, forKey: "method") // Encodes as String + coder.encode(timestamp, forKey: "timestamp") + } + + required init?(coder: NSCoder) { + guard + let subscriptionModel = coder.decodeObject(forKey: "subscriptionModel") as? OSSubscriptionModel, + let identityModel = coder.decodeObject(forKey: "identityModel") as? OSIdentityModel, + let rawMethod = coder.decodeObject(forKey: "method") as? UInt32, + let parameters = coder.decodeObject(forKey: "parameters") as? [String: Any], + let timestamp = coder.decodeObject(forKey: "timestamp") as? Date + else { + // Log error + return nil + } + self.subscriptionModel = subscriptionModel + self.identityModel = identityModel + self.stringDescription = "OSRequestCreateSubscription with subscriptionModel: \(subscriptionModel.address ?? "nil")" + super.init() + self.parameters = parameters + self.method = HTTPMethod(rawValue: rawMethod) + self.timestamp = timestamp + _ = prepareForExecution() + } +} diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestCreateUser.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestCreateUser.swift new file mode 100644 index 000000000..b59824d4c --- /dev/null +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestCreateUser.swift @@ -0,0 +1,122 @@ +/* + Modified MIT License + + Copyright 2023 OneSignal + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + 1. The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 2. All copies of substantial portions of the Software may only be used in connection + with services provided by OneSignal. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +import OneSignalCore + +/** + This request will be made with the minimum information needed. The payload will contain an externalId or no identities. + The push subscription may or may not have a token or suscriptionId already. + There will be no properties sent. + */ +class OSRequestCreateUser: OneSignalRequest, OSUserRequest { + var sentToClient = false + let stringDescription: String + override var description: String { + return stringDescription + } + + var identityModel: OSIdentityModel + var pushSubscriptionModel: OSSubscriptionModel + var originalPushToken: String? + + func prepareForExecution() -> Bool { + guard let appId = OneSignalConfigManager.getAppId() else { + OneSignalLog.onesignalLog(.LL_DEBUG, message: "Cannot generate the create user request due to null app ID.") + return false + } + self.addJWTHeader(identityModel: identityModel) + self.path = "apps/\(appId)/users" + // The pushSub doesn't need to have a token. + return true + } + + // When reading from the cache, update the push subscription model + func updatePushSubscriptionModel(_ pushSubscriptionModel: OSSubscriptionModel) { + self.pushSubscriptionModel = pushSubscriptionModel + self.parameters?["subscriptions"] = [pushSubscriptionModel.jsonRepresentation()] + self.originalPushToken = pushSubscriptionModel.address + } + + init(identityModel: OSIdentityModel, propertiesModel: OSPropertiesModel, pushSubscriptionModel: OSSubscriptionModel, originalPushToken: String?) { + self.identityModel = identityModel + self.pushSubscriptionModel = pushSubscriptionModel + self.originalPushToken = originalPushToken + self.stringDescription = "OSRequestCreateUser" + super.init() + + var params: [String: Any] = [:] + + // Identity Object + params["identity"] = [:] + if let externalId = identityModel.externalId { + params["identity"] = [OS_EXTERNAL_ID: externalId] + } + + // Properties Object + var propertiesObject: [String: Any] = [:] + propertiesObject["language"] = propertiesModel.language + propertiesObject["timezone_id"] = propertiesModel.timezoneId + params["properties"] = propertiesObject + + self.parameters = params + self.updatePushSubscriptionModel(pushSubscriptionModel) + self.method = POST + } + + func encode(with coder: NSCoder) { + coder.encode(identityModel, forKey: "identityModel") + coder.encode(pushSubscriptionModel, forKey: "pushSubscriptionModel") + coder.encode(originalPushToken, forKey: "originalPushToken") + coder.encode(parameters, forKey: "parameters") + coder.encode(method.rawValue, forKey: "method") // Encodes as String + coder.encode(path, forKey: "path") + coder.encode(timestamp, forKey: "timestamp") + } + + required init?(coder: NSCoder) { + guard + let identityModel = coder.decodeObject(forKey: "identityModel") as? OSIdentityModel, + let pushSubscriptionModel = coder.decodeObject(forKey: "pushSubscriptionModel") as? OSSubscriptionModel, + let parameters = coder.decodeObject(forKey: "parameters") as? [String: Any], + let rawMethod = coder.decodeObject(forKey: "method") as? UInt32, + let path = coder.decodeObject(forKey: "path") as? String, + let timestamp = coder.decodeObject(forKey: "timestamp") as? Date + else { + // Log error + return nil + } + self.identityModel = identityModel + self.pushSubscriptionModel = pushSubscriptionModel + self.originalPushToken = coder.decodeObject(forKey: "originalPushToken") as? String + self.stringDescription = "OSRequestCreateUser" + super.init() + self.parameters = parameters + self.method = HTTPMethod(rawValue: rawMethod) + self.path = path + self.timestamp = timestamp + } +} diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestDeleteSubscription.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestDeleteSubscription.swift new file mode 100644 index 000000000..11ed44c86 --- /dev/null +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestDeleteSubscription.swift @@ -0,0 +1,86 @@ +/* + Modified MIT License + + Copyright 2023 OneSignal + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + 1. The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 2. All copies of substantial portions of the Software may only be used in connection + with services provided by OneSignal. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +import OneSignalCore + +/** + Delete the subscription specified by the `subscriptionId` in the `subscriptionModel`. + Prior to the creation of this request, this model has already been removed from the model store. + - Remark: If this model did not already exist in the store, no request is created. + */ +class OSRequestDeleteSubscription: OneSignalRequest, OSUserRequest { + var sentToClient = false + let stringDescription: String + override var description: String { + return stringDescription + } + + var subscriptionModel: OSSubscriptionModel + + // Need the subscription_id + func prepareForExecution() -> Bool { + if let subscriptionId = subscriptionModel.subscriptionId, let appId = OneSignalConfigManager.getAppId() { + self.path = "apps/\(appId)/subscriptions/\(subscriptionId)" + return true + } else { + // self.path is non-nil, so set to empty string + self.path = "" + return false + } + } + + init(subscriptionModel: OSSubscriptionModel) { + self.subscriptionModel = subscriptionModel + self.stringDescription = "OSRequestDeleteSubscription with subscriptionModel: \(subscriptionModel.address ?? "nil")" + super.init() + self.method = DELETE + _ = prepareForExecution() // sets the path property + } + + func encode(with coder: NSCoder) { + coder.encode(subscriptionModel, forKey: "subscriptionModel") + coder.encode(method.rawValue, forKey: "method") // Encodes as String + coder.encode(timestamp, forKey: "timestamp") + } + + required init?(coder: NSCoder) { + guard + let subscriptionModel = coder.decodeObject(forKey: "subscriptionModel") as? OSSubscriptionModel, + let rawMethod = coder.decodeObject(forKey: "method") as? UInt32, + let timestamp = coder.decodeObject(forKey: "timestamp") as? Date + else { + // Log error + return nil + } + self.subscriptionModel = subscriptionModel + self.stringDescription = "OSRequestDeleteSubscription with subscriptionModel: \(subscriptionModel.address ?? "nil")" + super.init() + self.method = HTTPMethod(rawValue: rawMethod) + self.timestamp = timestamp + _ = prepareForExecution() + } +} diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestFetchIdentityBySubscription.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestFetchIdentityBySubscription.swift new file mode 100644 index 000000000..25dd80be0 --- /dev/null +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestFetchIdentityBySubscription.swift @@ -0,0 +1,91 @@ +/* + Modified MIT License + + Copyright 2023 OneSignal + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + 1. The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 2. All copies of substantial portions of the Software may only be used in connection + with services provided by OneSignal. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +import OneSignalCore + +class OSRequestFetchIdentityBySubscription: OneSignalRequest, OSUserRequest { + var sentToClient = false + let stringDescription: String + + override var description: String { + return stringDescription + } + + var identityModel: OSIdentityModel + var pushSubscriptionModel: OSSubscriptionModel + + func prepareForExecution() -> Bool { + guard let appId = OneSignalConfigManager.getAppId() else { + OneSignalLog.onesignalLog(.LL_DEBUG, message: "Cannot generate the FetchIdentityBySubscription request due to null app ID.") + return false + } + + if let subscriptionId = pushSubscriptionModel.subscriptionId { + self.path = "apps/\(appId)/subscriptions/\(subscriptionId)/user/identity" + return true + } else { + // This is an error, and should never happen + OneSignalLog.onesignalLog(.LL_ERROR, message: "Cannot generate the FetchIdentityBySubscription request due to null subscriptionId.") + self.path = "" + return false + } + } + + init(identityModel: OSIdentityModel, pushSubscriptionModel: OSSubscriptionModel) { + self.identityModel = identityModel + self.pushSubscriptionModel = pushSubscriptionModel + self.stringDescription = "OSRequestFetchIdentityBySubscription with subscriptionId: \(pushSubscriptionModel.subscriptionId ?? "nil")" + super.init() + self.method = GET + } + + func encode(with coder: NSCoder) { + coder.encode(identityModel, forKey: "identityModel") + coder.encode(pushSubscriptionModel, forKey: "pushSubscriptionModel") + coder.encode(method.rawValue, forKey: "method") // Encodes as String + coder.encode(timestamp, forKey: "timestamp") + } + + required init?(coder: NSCoder) { + guard + let identityModel = coder.decodeObject(forKey: "identityModel") as? OSIdentityModel, + let pushSubscriptionModel = coder.decodeObject(forKey: "pushSubscriptionModel") as? OSSubscriptionModel, + let rawMethod = coder.decodeObject(forKey: "method") as? UInt32, + let timestamp = coder.decodeObject(forKey: "timestamp") as? Date + else { + // Log error + return nil + } + self.identityModel = identityModel + self.pushSubscriptionModel = pushSubscriptionModel + + self.stringDescription = "OSRequestFetchIdentityBySubscription with subscriptionId: \(pushSubscriptionModel.subscriptionId ?? "nil")" + super.init() + self.method = HTTPMethod(rawValue: rawMethod) + self.timestamp = timestamp + } +} diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestFetchUser.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestFetchUser.swift new file mode 100644 index 000000000..92c7ddd0b --- /dev/null +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestFetchUser.swift @@ -0,0 +1,97 @@ +/* + Modified MIT License + + Copyright 2023 OneSignal + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + 1. The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 2. All copies of substantial portions of the Software may only be used in connection + with services provided by OneSignal. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +import OneSignalCore + +/** + If an alias is passed in, it will be used to fetch the user. If not, then by default, use the `onesignal_id` in the `identityModel` to fetch the user. + The `identityModel` is also used to reference the user that is updated with the response. + */ +class OSRequestFetchUser: OneSignalRequest, OSUserRequest { + var sentToClient = false + let stringDescription: String + override var description: String { + return stringDescription + } + + 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.") + return false + } + self.addJWTHeader(identityModel: identityModel) + self.path = "apps/\(appId)/users/by/\(aliasLabel)/\(aliasId)" + return true + } + + 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 + _ = prepareForExecution() // sets the path property + } + + func encode(with coder: NSCoder) { + 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") + } + + required init?(coder: NSCoder) { + guard + let identityModel = coder.decodeObject(forKey: "identityModel") as? OSIdentityModel, + let aliasLabel = coder.decodeObject(forKey: "aliasLabel") as? String, + let aliasId = coder.decodeObject(forKey: "aliasId") as? String, + let rawMethod = coder.decodeObject(forKey: "method") as? UInt32, + let timestamp = coder.decodeObject(forKey: "timestamp") as? Date + else { + // Log error + return nil + } + 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) + self.timestamp = timestamp + _ = prepareForExecution() + } +} diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestIdentifyUser.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestIdentifyUser.swift new file mode 100644 index 000000000..02aa0e318 --- /dev/null +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestIdentifyUser.swift @@ -0,0 +1,115 @@ +/* + Modified MIT License + + Copyright 2023 OneSignal + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + 1. The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 2. All copies of substantial portions of the Software may only be used in connection + with services provided by OneSignal. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +import OneSignalCore + +/** + The `identityModelToIdentify` is used for the `onesignal_id` of the user we want to associate with this alias. + This request will tell us if we should continue with the previous user who is now identitfied, or to change users to the one this alias already exists on. + + Note: The SDK needs an user to operate on before this request returns. However, at the time of this request's creation, the SDK does not know if there is already an user associated with this alias. So, it creates a blank new user (whose identity model is passed in as `identityModelToUpdate`, + which is the model used to make a subsequent ``OSRequestFetchUser``). + */ +class OSRequestIdentifyUser: OneSignalRequest, OSUserRequest { + var sentToClient = false + let stringDescription: String + override var description: String { + return stringDescription + } + + var identityModelToIdentify: OSIdentityModel + var identityModelToUpdate: OSIdentityModel + let aliasLabel: String + let aliasId: String + + // requires a onesignal_id to send this request + func prepareForExecution() -> Bool { + if let onesignalId = identityModelToIdentify.onesignalId, let appId = OneSignalConfigManager.getAppId() { + self.addJWTHeader(identityModel: identityModelToIdentify) + self.path = "apps/\(appId)/users/by/\(OS_ONESIGNAL_ID)/\(onesignalId)/identity" + return true + } else { + // self.path is non-nil, so set to empty string + self.path = "" + return false + } + } + + /** + - Parameters: + - aliasLabel: The alias label we want to identify this user with. + - aliasId: The alias ID we want to identify this user with. + - identityModelToIdentify: Belongs to the user we want to identify with this alias. + - identityModelToUpdate: Belongs to the user we want to send in the subsequent ``OSRequestFetchUser`` that is made when this request returns. + */ + init(aliasLabel: String, aliasId: String, identityModelToIdentify: OSIdentityModel, identityModelToUpdate: OSIdentityModel) { + self.identityModelToIdentify = identityModelToIdentify + self.identityModelToUpdate = identityModelToUpdate + self.aliasLabel = aliasLabel + self.aliasId = aliasId + self.stringDescription = "OSRequestIdentifyUser with aliasLabel: \(aliasLabel) aliasId: \(aliasId)" + super.init() + self.parameters = ["identity": [aliasLabel: aliasId]] + self.method = PATCH + _ = prepareForExecution() // sets the path property + } + + func encode(with coder: NSCoder) { + coder.encode(identityModelToIdentify, forKey: "identityModelToIdentify") + coder.encode(identityModelToUpdate, forKey: "identityModelToUpdate") + coder.encode(aliasLabel, forKey: "aliasLabel") + coder.encode(aliasId, forKey: "aliasId") + coder.encode(parameters, forKey: "parameters") + coder.encode(method.rawValue, forKey: "method") // Encodes as String + coder.encode(timestamp, forKey: "timestamp") + } + + required init?(coder: NSCoder) { + guard + let identityModelToIdentify = coder.decodeObject(forKey: "identityModelToIdentify") as? OSIdentityModel, + let identityModelToUpdate = coder.decodeObject(forKey: "identityModelToUpdate") as? OSIdentityModel, + let aliasLabel = coder.decodeObject(forKey: "aliasLabel") as? String, + let aliasId = coder.decodeObject(forKey: "aliasId") as? String, + let parameters = coder.decodeObject(forKey: "parameters") as? [String: [String: String]], + let rawMethod = coder.decodeObject(forKey: "method") as? UInt32, + let timestamp = coder.decodeObject(forKey: "timestamp") as? Date + else { + // Log error + return nil + } + self.identityModelToIdentify = identityModelToIdentify + self.identityModelToUpdate = identityModelToUpdate + self.aliasLabel = aliasLabel + self.aliasId = aliasId + self.stringDescription = "OSRequestIdentifyUser with aliasLabel: \(aliasLabel) aliasId: \(aliasId)" + super.init() + self.timestamp = timestamp + self.parameters = parameters + self.method = HTTPMethod(rawValue: rawMethod) + _ = prepareForExecution() + } +} diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestRemoveAlias.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestRemoveAlias.swift new file mode 100644 index 000000000..49e6ea691 --- /dev/null +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestRemoveAlias.swift @@ -0,0 +1,86 @@ +/* + Modified MIT License + + Copyright 2023 OneSignal + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + 1. The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 2. All copies of substantial portions of the Software may only be used in connection + with services provided by OneSignal. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +import OneSignalCore + +class OSRequestRemoveAlias: OneSignalRequest, OSUserRequest { + var sentToClient = false + let stringDescription: String + override var description: String { + return stringDescription + } + + let labelToRemove: String + var identityModel: OSIdentityModel + + func prepareForExecution() -> Bool { + if let onesignalId = identityModel.onesignalId, let appId = OneSignalConfigManager.getAppId() { + self.addJWTHeader(identityModel: identityModel) + self.path = "apps/\(appId)/users/by/\(OS_ONESIGNAL_ID)/\(onesignalId)/identity/\(labelToRemove)" + return true + } else { + // self.path is non-nil, so set to empty string + self.path = "" + return false + } + } + + init(labelToRemove: String, identityModel: OSIdentityModel) { + self.labelToRemove = labelToRemove + self.identityModel = identityModel + self.stringDescription = "OSRequestRemoveAlias with aliasLabel: \(labelToRemove)" + super.init() + self.method = DELETE + _ = prepareForExecution() // sets the path property + } + + func encode(with coder: NSCoder) { + coder.encode(labelToRemove, forKey: "labelToRemove") + coder.encode(identityModel, forKey: "identityModel") + coder.encode(method.rawValue, forKey: "method") // Encodes as String + coder.encode(timestamp, forKey: "timestamp") + } + + required init?(coder: NSCoder) { + guard + let labelToRemove = coder.decodeObject(forKey: "labelToRemove") as? String, + let identityModel = coder.decodeObject(forKey: "identityModel") as? OSIdentityModel, + let rawMethod = coder.decodeObject(forKey: "method") as? UInt32, + let timestamp = coder.decodeObject(forKey: "timestamp") as? Date + else { + // Log error + return nil + } + self.labelToRemove = labelToRemove + self.identityModel = identityModel + self.stringDescription = "OSRequestRemoveAlias with aliasLabel: \(labelToRemove)" + super.init() + self.method = HTTPMethod(rawValue: rawMethod) + self.timestamp = timestamp + _ = prepareForExecution() + } +} diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestTransferSubscription.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestTransferSubscription.swift new file mode 100644 index 000000000..57bacec4e --- /dev/null +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestTransferSubscription.swift @@ -0,0 +1,107 @@ +/* + Modified MIT License + + Copyright 2023 OneSignal + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + 1. The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 2. All copies of substantial portions of the Software may only be used in connection + with services provided by OneSignal. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +import OneSignalCore + +/** + Transfers the Subscription specified by the subscriptionId to the User identified by the identity in the payload. + Only one entry is allowed, `onesignal_id` or an Alias. We will use the alias specified. + The anticipated usage of this request is only for push subscriptions. + */ +class OSRequestTransferSubscription: OneSignalRequest, OSUserRequest { + var sentToClient = false + let stringDescription: String + override var description: String { + return stringDescription + } + + var subscriptionModel: OSSubscriptionModel + let aliasLabel: String + let aliasId: String + + // Need an alias and subscription_id + func prepareForExecution() -> Bool { + if let subscriptionId = subscriptionModel.subscriptionId, let appId = OneSignalConfigManager.getAppId() { + self.path = "apps/\(appId)/subscriptions/\(subscriptionId)/owner" + // TODO: self.addJWTHeader(identityModel: identityModel) ?? + return true + } else { + self.path = "" // self.path is non-nil, so set to empty string + return false + } + } + + /** + Must pass an Alias pair to identify the User. + */ + init( + subscriptionModel: OSSubscriptionModel, + aliasLabel: String, + aliasId: String + ) { + self.subscriptionModel = subscriptionModel + self.aliasLabel = aliasLabel + self.aliasId = aliasId + self.stringDescription = "OSRequestTransferSubscription" + super.init() + self.parameters = ["identity": [aliasLabel: aliasId]] + self.method = PATCH + _ = prepareForExecution() // sets the path property + } + + func encode(with coder: NSCoder) { + coder.encode(subscriptionModel, forKey: "subscriptionModel") + coder.encode(aliasLabel, forKey: "aliasLabel") + coder.encode(aliasId, forKey: "aliasId") + coder.encode(parameters, forKey: "parameters") + coder.encode(method.rawValue, forKey: "method") // Encodes as String + coder.encode(timestamp, forKey: "timestamp") + } + + required init?(coder: NSCoder) { + guard + let subscriptionModel = coder.decodeObject(forKey: "subscriptionModel") as? OSSubscriptionModel, + let aliasLabel = coder.decodeObject(forKey: "aliasLabel") as? String, + let aliasId = coder.decodeObject(forKey: "aliasId") as? String, + let rawMethod = coder.decodeObject(forKey: "method") as? UInt32, + let parameters = coder.decodeObject(forKey: "parameters") as? [String: Any], + let timestamp = coder.decodeObject(forKey: "timestamp") as? Date + else { + // Log error + return nil + } + self.subscriptionModel = subscriptionModel + self.aliasLabel = aliasLabel + self.aliasId = aliasId + self.stringDescription = "OSRequestTransferSubscription" + super.init() + self.parameters = parameters + self.method = HTTPMethod(rawValue: rawMethod) + self.timestamp = timestamp + _ = prepareForExecution() + } +} diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestUpdateProperties.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestUpdateProperties.swift new file mode 100644 index 000000000..50dd02b1f --- /dev/null +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestUpdateProperties.swift @@ -0,0 +1,126 @@ +/* + Modified MIT License + + Copyright 2023 OneSignal + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + 1. The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 2. All copies of substantial portions of the Software may only be used in connection + with services provided by OneSignal. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +import OneSignalCore + +class OSRequestUpdateProperties: OneSignalRequest, OSUserRequest { + var sentToClient = false + let stringDescription: String + override var description: String { + return stringDescription + } + + // TODO: does updating properties even have a response in which we need to hydrate from? Then we can get rid of modelToUpdate + // Yes we may, if we cleared local state + var modelToUpdate: OSPropertiesModel + var identityModel: OSIdentityModel + + // TODO: Decide if addPushSubscriptionIdToAdditionalHeadersIfNeeded should block. + // Note Android adds it to requests, if the push sub ID exists + func prepareForExecution() -> Bool { + if let onesignalId = identityModel.onesignalId, + let appId = OneSignalConfigManager.getAppId(), + addPushSubscriptionIdToAdditionalHeadersIfNeeded() { + self.addJWTHeader(identityModel: identityModel) + self.path = "apps/\(appId)/users/by/\(OS_ONESIGNAL_ID)/\(onesignalId)" + return true + } else { + // self.path is non-nil, so set to empty string + self.path = "" + return false + } + } + + func addPushSubscriptionIdToAdditionalHeadersIfNeeded() -> Bool { + guard let parameters = self.parameters else { + return true + } + if parameters["deltas"] != nil { // , !parameters["deltas"].isEmpty + if let pushSubscriptionId = OneSignalUserManagerImpl.sharedInstance.pushSubscriptionId { + var additionalHeaders = self.additionalHeaders ?? [String: String]() + additionalHeaders["OneSignal-Subscription-Id"] = pushSubscriptionId + self.additionalHeaders = additionalHeaders + return true + } else { + return false + } + } + return true + } + + init(properties: [String: Any], deltas: [String: Any]?, refreshDeviceMetadata: Bool?, modelToUpdate: OSPropertiesModel, identityModel: OSIdentityModel) { + self.modelToUpdate = modelToUpdate + self.identityModel = identityModel + self.stringDescription = "OSRequestUpdateProperties with properties: \(properties) deltas: \(String(describing: deltas)) refreshDeviceMetadata: \(String(describing: refreshDeviceMetadata))" + super.init() + + var propertiesObject = properties + if let location = propertiesObject["location"] as? OSLocationPoint { + propertiesObject["lat"] = location.lat + propertiesObject["long"] = location.long + propertiesObject.removeValue(forKey: "location") + } + var params: [String: Any] = [:] + params["properties"] = propertiesObject + params["refresh_device_metadata"] = refreshDeviceMetadata + if let deltas = deltas { + params["deltas"] = deltas + } + self.parameters = params + self.method = PATCH + _ = prepareForExecution() // sets the path property + } + + func encode(with coder: NSCoder) { + coder.encode(modelToUpdate, forKey: "modelToUpdate") + coder.encode(identityModel, forKey: "identityModel") + coder.encode(parameters, forKey: "parameters") + coder.encode(method.rawValue, forKey: "method") // Encodes as String + coder.encode(timestamp, forKey: "timestamp") + } + + required init?(coder: NSCoder) { + guard + let modelToUpdate = coder.decodeObject(forKey: "modelToUpdate") as? OSPropertiesModel, + let identityModel = coder.decodeObject(forKey: "identityModel") as? OSIdentityModel, + let rawMethod = coder.decodeObject(forKey: "method") as? UInt32, + let parameters = coder.decodeObject(forKey: "parameters") as? [String: Any], + let timestamp = coder.decodeObject(forKey: "timestamp") as? Date + else { + // Log error + return nil + } + self.modelToUpdate = modelToUpdate + self.identityModel = identityModel + self.stringDescription = "OSRequestUpdateProperties with parameters: \(parameters)" + super.init() + self.parameters = parameters + self.method = HTTPMethod(rawValue: rawMethod) + self.timestamp = timestamp + _ = prepareForExecution() + } +} diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestUpdateSubscription.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestUpdateSubscription.swift new file mode 100644 index 000000000..3dfe05581 --- /dev/null +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestUpdateSubscription.swift @@ -0,0 +1,108 @@ +/* + Modified MIT License + + Copyright 2023 OneSignal + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + 1. The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 2. All copies of substantial portions of the Software may only be used in connection + with services provided by OneSignal. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +import OneSignalCore + +/** + Currently, only the Push Subscription will make this Update Request. + */ +class OSRequestUpdateSubscription: OneSignalRequest, OSUserRequest { + var sentToClient = false + let stringDescription: String + override var description: String { + return stringDescription + } + + var subscriptionModel: OSSubscriptionModel + + // Need the subscription_id + func prepareForExecution() -> Bool { + if let subscriptionId = subscriptionModel.subscriptionId, let appId = OneSignalConfigManager.getAppId() { + self.path = "apps/\(appId)/subscriptions/\(subscriptionId)" + return true + } else { + self.path = "" // self.path is non-nil, so set to empty string + return false + } + } + + // TODO: just need the sub model and send it + // But the model may be outdated or not sync with the subscriptionObject + init(subscriptionObject: [String: Any], subscriptionModel: OSSubscriptionModel) { + self.subscriptionModel = subscriptionModel + self.stringDescription = "OSRequestUpdateSubscription with subscriptionObject: \(subscriptionObject)" + super.init() + + // Rename "address" key as "token", if it exists + var subscriptionParams = subscriptionObject + subscriptionParams.removeValue(forKey: "address") + subscriptionParams.removeValue(forKey: "notificationTypes") + subscriptionParams["token"] = subscriptionModel.address + subscriptionParams["device_os"] = subscriptionModel.deviceOs + subscriptionParams["sdk"] = subscriptionModel.sdk + subscriptionParams["app_version"] = subscriptionModel.appVersion + + // notificationTypes defaults to -1 instead of nil, don't send if it's -1 + if subscriptionModel.notificationTypes != -1 { + subscriptionParams["notification_types"] = subscriptionModel.notificationTypes + } + + subscriptionParams["enabled"] = subscriptionModel.enabled + // TODO: The above is not quite right. If we hydrate, we will over-write any pending updates + // May use subscriptionObject, but enabled and notification_types should be sent together... + + self.parameters = ["subscription": subscriptionParams] + self.method = PATCH + _ = prepareForExecution() // sets the path property + } + + func encode(with coder: NSCoder) { + coder.encode(subscriptionModel, forKey: "subscriptionModel") + coder.encode(parameters, forKey: "parameters") + coder.encode(method.rawValue, forKey: "method") // Encodes as String + coder.encode(timestamp, forKey: "timestamp") + } + + required init?(coder: NSCoder) { + guard + let subscriptionModel = coder.decodeObject(forKey: "subscriptionModel") as? OSSubscriptionModel, + let rawMethod = coder.decodeObject(forKey: "method") as? UInt32, + let parameters = coder.decodeObject(forKey: "parameters") as? [String: Any], + let timestamp = coder.decodeObject(forKey: "timestamp") as? Date + else { + // Log error + return nil + } + self.subscriptionModel = subscriptionModel + self.stringDescription = "OSRequestUpdateSubscription with parameters: \(parameters)" + super.init() + self.parameters = parameters + self.method = HTTPMethod(rawValue: rawMethod) + self.timestamp = timestamp + _ = prepareForExecution() + } +} diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSUserRequest.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSUserRequest.swift new file mode 100644 index 000000000..19ac164ce --- /dev/null +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSUserRequest.swift @@ -0,0 +1,44 @@ +/* + Modified MIT License + + Copyright 2022 OneSignal + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + 1. The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 2. All copies of substantial portions of the Software may only be used in connection + with services provided by OneSignal. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +import OneSignalCore + +protocol OSUserRequest: OneSignalRequest, NSCoding { + var sentToClient: Bool { get set } + func prepareForExecution() -> Bool +} + +internal extension OneSignalRequest { + func addJWTHeader(identityModel: OSIdentityModel) { +// guard let token = identityModel.jwtBearerToken else { +// return +// } +// var additionalHeaders = self.additionalHeaders ?? [String:String]() +// additionalHeaders["Authorization"] = "Bearer \(token)" +// self.additionalHeaders = additionalHeaders + } +} diff --git a/iOS_SDK/OneSignalSDK/Source/OneSignalSwiftInterface.swift b/iOS_SDK/OneSignalSDK/Source/OneSignalSwiftInterface.swift index 7a048ef4a..b29c525cb 100644 --- a/iOS_SDK/OneSignalSDK/Source/OneSignalSwiftInterface.swift +++ b/iOS_SDK/OneSignalSDK/Source/OneSignalSwiftInterface.swift @@ -56,10 +56,10 @@ public extension OneSignal { static var Location: OSLocation.Type { return __location() } - + static var LiveActivities: OSLiveActivities.Type { return __liveActivities() - } + } } public extension OSDebug { @@ -77,19 +77,19 @@ public extension OSInAppMessages { __paused(newValue) } } - + static func addLifecycleListener(_ listener: OSInAppMessageLifecycleListener) { __add(listener) } - + static func removeLifecycleListener(_ listener: OSInAppMessageLifecycleListener) { __remove(listener) } - + static func addClickListener(_ listener: OSInAppMessageClickListener) { __add(listener) } - + static func removeClickListener(_ listener: OSInAppMessageClickListener) { __remove(listener) } @@ -113,7 +113,7 @@ public extension OSNotifications { static var permissionNative: OSNotificationPermission { return __permissionNative() } - + static func registerForProvisionalAuthorization(_ block: OSUserResponseBlock?) { return __register(forProvisionalAuthorization: block) } @@ -125,11 +125,11 @@ public extension OSNotifications { static func removePermissionObserver(_ observer: OSNotificationPermissionObserver) { return __remove(observer) } - + static func addClickListener(_ listener: OSNotificationClickListener) { return __add(listener) } - + static func removeClickListener(_ listener: OSNotificationClickListener) { return __remove(listener) }