From 658663c69e2d5ef2663e9ffac24654cc40729b13 Mon Sep 17 00:00:00 2001 From: Nan Date: Wed, 24 Jul 2024 12:32:19 -0700 Subject: [PATCH 01/10] Deprecate Transfer Subscription * The SDK will longer make Transfer Subscription calls now that requests via external ID are removed, and all requests are expected to use OneSignal ID. * The Transfer Subscription request was previously used in an Identify User 409 conflict to transfer the push subscription to that External ID. * Instead these will translate to Create User which will contain the External ID and the push subscription in the payload. * Deprecate this request class but keep a skeleton in order to uncache them --- .../Source/Executors/OSUserExecutor.swift | 109 ++++-------------- .../Source/OneSignalUserManagerImpl.swift | 8 ++ .../OSRequestTransferSubscription.swift | 60 ++-------- 3 files changed, 41 insertions(+), 136 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift index c5eb2a5c4..2fbb3d65d 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift @@ -31,11 +31,10 @@ import OneSignalOSCore /** Involved in the login process and responsible for Identify User and Create User. - Can execute `OSRequestCreateUser`, `OSRequestIdentifyUser`, `OSRequestTransferSubscription`, `OSRequestFetchUser`, `OSRequestFetchIdentityBySubscription`. + Can execute `OSRequestCreateUser`, `OSRequestIdentifyUser`, `OSRequestFetchUser`, `OSRequestFetchIdentityBySubscription`. */ class OSUserExecutor { static var userRequestQueue: [OSUserRequest] = [] - static var transferSubscriptionRequestQueue: [OSRequestTransferSubscription] = [] // The User executor dispatch queue, serial. This synchronizes access to the request queues. private static let dispatchQueue = DispatchQueue(label: "OneSignal.OSUserExecutor", target: .global()) @@ -99,30 +98,28 @@ class OSUserExecutor { } self.userRequestQueue = userRequestQueue OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_USER_EXECUTOR_USER_REQUEST_QUEUE_KEY, withValue: self.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() dropped: \(request)") - self.transferSubscriptionRequestQueue = [] - } - } - } else { - OneSignalLog.onesignalLog(.LL_ERROR, message: "OSUserExecutor error encountered reading from cache for \(OS_USER_EXECUTOR_TRANSFER_SUBSCRIPTION_REQUEST_QUEUE_KEY)") - } - OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_USER_EXECUTOR_TRANSFER_SUBSCRIPTION_REQUEST_QUEUE_KEY, withValue: self.transferSubscriptionRequestQueue) + migrateTransferSubscriptionRequests() executePendingRequests() } } + /** + Read Transfer Subscription requests from cache, if any. + As of `5.2.3`, the SDK will no longer send Transfer Subscription requests, so migrate the request into an equivalent Create User request. + */ + static private func migrateTransferSubscriptionRequests() { + if let transferSubscriptionRequestQueue = OneSignalUserDefaults.initShared().getSavedCodeableData(forKey: OS_USER_EXECUTOR_TRANSFER_SUBSCRIPTION_REQUEST_QUEUE_KEY, defaultValue: nil) as? [OSRequestTransferSubscription] { + OneSignalUserDefaults.initShared().removeValue(forKey: OS_USER_EXECUTOR_TRANSFER_SUBSCRIPTION_REQUEST_QUEUE_KEY) + + // Translate the last request into a Create User request, if the current user is the same + if let request = transferSubscriptionRequestQueue.last, + OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.aliasId) { + createUser(OneSignalUserManagerImpl.sharedInstance.user) + } + } + } + static private func getIdentityModel(_ modelId: String) -> OSIdentityModel? { return OneSignalUserManagerImpl.sharedInstance.getIdentityModel(modelId) } @@ -133,41 +130,27 @@ class OSUserExecutor { static func appendToQueue(_ request: OSUserRequest) { self.dispatchQueue.async { - 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) - } + self.userRequestQueue.append(request) + OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_USER_EXECUTOR_USER_REQUEST_QUEUE_KEY, withValue: self.userRequestQueue) } } static func removeFromQueue(_ request: OSUserRequest) { self.dispatchQueue.async { - 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) - } + userRequestQueue.removeAll(where: { $0 == request}) + OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_USER_EXECUTOR_USER_REQUEST_QUEUE_KEY, withValue: self.userRequestQueue) } } static func executePendingRequests() { self.dispatchQueue.async { - let requestQueue: [OSUserRequest] = userRequestQueue + transferSubscriptionRequestQueue - OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSUserExecutor.executePendingRequests called with queue \(requestQueue)") + OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSUserExecutor.executePendingRequests called with queue \(userRequestQueue)") - if requestQueue.isEmpty { + if userRequestQueue.isEmpty { return } - // Sort the requestQueue by timestamp - for request in requestQueue.sorted(by: { first, second in - return first.timestamp < second.timestamp - }) { + for request in userRequestQueue { // Return as soon as we reach an un-executable request if !request.prepareForExecution() { OneSignalLog.onesignalLog(.LL_WARN, message: "OSUserExecutor.executePendingRequests() is blocked by unexecutable request \(request)") @@ -183,9 +166,6 @@ class OSUserExecutor { } 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 @@ -390,45 +370,6 @@ extension OSUserExecutor { } } - 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 - OneSignalCoreImpl.sharedClient().execute(request) { _ in - removeFromQueue(request) - 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) diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUserManagerImpl.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUserManagerImpl.swift index d06b8493c..e5e7f8ead 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUserManagerImpl.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUserManagerImpl.swift @@ -354,6 +354,14 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager { return self.identityModelStore.getModel(modelId: identityModel.modelId) != nil } + func isCurrentUser(_ externalId: String) -> Bool { + guard !externalId.isEmpty else { + OneSignalLog.onesignalLog(.LL_ERROR, message: "isCurrentUser called with empty externalId") + return false + } + + return user.identityModel.externalId == externalId + } /** Clears the existing user's data in preparation for hydration via a fetch user call. */ diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestTransferSubscription.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestTransferSubscription.swift index e9040e5ae..854f12ce9 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestTransferSubscription.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestTransferSubscription.swift @@ -28,80 +28,36 @@ import OneSignalCore /** + Deprecated as of `5.2.3`. Use CreateUser instead. This class skeleton remains due to potentially cached requests. + When this request is uncached, it will be translated to a CreateUser request, if appropriate. + ------- 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. */ +@available(*, deprecated, message: "Replaced by Create User") 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 - } + 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 = "" - 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") - } + func encode(with coder: NSCoder) { } + /// All cached instances should have External ID as the alias 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 + let aliasId = coder.decodeObject(forKey: "aliasId") as? String else { // Log error return nil } - self.subscriptionModel = subscriptionModel self.aliasLabel = aliasLabel self.aliasId = aliasId - self.stringDescription = "" - super.init() - self.parameters = parameters - self.method = HTTPMethod(rawValue: rawMethod) - self.timestamp = timestamp - _ = prepareForExecution() } } From e656417928526e85134603b8e0551dd5730a7058 Mon Sep 17 00:00:00 2001 From: Nan Date: Wed, 24 Jul 2024 12:19:54 -0700 Subject: [PATCH 02/10] Use Create User to hydrate OneSignal ID * The initializer for the Request has optional PropertiesModel and optional SubscriptionModel * This use case will send an external ID in the payload to retrieve the OneSignal ID. --- .../Source/Executors/OSUserExecutor.swift | 19 ++++++++++-- .../Source/Requests/OSRequestCreateUser.swift | 29 ++++++++++++------- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift index 2fbb3d65d..122cd018f 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift @@ -190,6 +190,20 @@ extension OSUserExecutor { executePendingRequests() } + /** + This Create User call expects an Identity Model with external ID to hydrate the OneSignal ID + */ + static func createUser(identityModel: OSIdentityModel) { + guard identityModel.externalId != nil else { + OneSignalLog.onesignalLog(.LL_ERROR, message: "createUser(identityModel) called with missing external ID") + return + } + + let request = OSRequestCreateUser(identityModel: identityModel, propertiesModel: nil, pushSubscriptionModel: nil, originalPushToken: nil) + appendToQueue(request) + executePendingRequests() + } + static func executeCreateUserRequest(_ request: OSRequestCreateUser) { guard !request.sentToClient else { return @@ -200,8 +214,9 @@ extension OSUserExecutor { } 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) { + // Hook up push subscription model if exists, it may be updated with a subscription_id, etc. + if let modelId = request.pushSubscriptionModel?.modelId, + let pushSubscriptionModel = OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModelStore.getModel(modelId: modelId) { request.pushSubscriptionModel = pushSubscriptionModel request.updatePushSubscriptionModel(pushSubscriptionModel) } diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestCreateUser.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestCreateUser.swift index dbf8ed914..720676fbf 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestCreateUser.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestCreateUser.swift @@ -30,7 +30,8 @@ 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. + This request is used for typical User Create, which will include properties and the push subscription, + or to hydrate OneSignal ID for a given External ID, which will only contain the Identity object in the payload. */ class OSRequestCreateUser: OneSignalRequest, OSUserRequest { var sentToClient = false @@ -40,7 +41,7 @@ class OSRequestCreateUser: OneSignalRequest, OSUserRequest { } var identityModel: OSIdentityModel - var pushSubscriptionModel: OSSubscriptionModel + var pushSubscriptionModel: OSSubscriptionModel? var originalPushToken: String? func prepareForExecution() -> Bool { @@ -55,14 +56,17 @@ class OSRequestCreateUser: OneSignalRequest, OSUserRequest { return true } - // When reading from the cache, update the push subscription model + // When reading from the cache, update the push subscription model, if appropriate func updatePushSubscriptionModel(_ pushSubscriptionModel: OSSubscriptionModel) { + guard self.pushSubscriptionModel != nil else { + return + } self.pushSubscriptionModel = pushSubscriptionModel self.parameters?["subscriptions"] = [pushSubscriptionModel.jsonRepresentation()] self.originalPushToken = pushSubscriptionModel.address } - init(identityModel: OSIdentityModel, propertiesModel: OSPropertiesModel, pushSubscriptionModel: OSSubscriptionModel, originalPushToken: String?) { + init(identityModel: OSIdentityModel, propertiesModel: OSPropertiesModel?, pushSubscriptionModel: OSSubscriptionModel?, originalPushToken: String?) { self.identityModel = identityModel self.pushSubscriptionModel = pushSubscriptionModel self.originalPushToken = originalPushToken @@ -78,14 +82,18 @@ class OSRequestCreateUser: OneSignalRequest, OSUserRequest { } // Properties Object - var propertiesObject: [String: Any] = [:] - propertiesObject["language"] = propertiesModel.language - propertiesObject["timezone_id"] = propertiesModel.timezoneId - params["properties"] = propertiesObject + if let propertiesModel = propertiesModel { + var propertiesObject: [String: Any] = [:] + propertiesObject["language"] = propertiesModel.language + propertiesObject["timezone_id"] = propertiesModel.timezoneId + params["properties"] = propertiesObject + } params["refresh_device_metadata"] = true self.parameters = params - self.updatePushSubscriptionModel(pushSubscriptionModel) + if let pushSub = pushSubscriptionModel { + self.updatePushSubscriptionModel(pushSub) + } self.method = POST } @@ -102,7 +110,6 @@ class OSRequestCreateUser: OneSignalRequest, OSUserRequest { 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, @@ -112,7 +119,7 @@ class OSRequestCreateUser: OneSignalRequest, OSUserRequest { return nil } self.identityModel = identityModel - self.pushSubscriptionModel = pushSubscriptionModel + self.pushSubscriptionModel = coder.decodeObject(forKey: "pushSubscriptionModel") as? OSSubscriptionModel self.originalPushToken = coder.decodeObject(forKey: "originalPushToken") as? String self.stringDescription = "" super.init() From 454909dfc6ab777d9ce7ee850ad14890c931c377 Mon Sep 17 00:00:00 2001 From: Nan Date: Wed, 24 Jul 2024 12:43:03 -0700 Subject: [PATCH 03/10] Fetch User by OSID, not external ID * After a successful `CreateUser` or `IdentifyUser` without conflict, fetch the user for hydration via OneSignal ID instead of External ID. --- .../Source/Executors/OSUserExecutor.swift | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift index 122cd018f..1e33550ea 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift @@ -232,11 +232,12 @@ extension OSUserExecutor { // 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 + // Fetch the user only if its the current user and non-anonymous 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) + let onesignalId = identity[OS_ONESIGNAL_ID], + identity[OS_EXTERNAL_ID] != nil { + fetchUser(aliasLabel: OS_ONESIGNAL_ID, aliasId: onesignalId, identityModel: request.identityModel) } else { executePendingRequests() } @@ -337,12 +338,8 @@ extension OSUserExecutor { // 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) + fetchUser(aliasLabel: OS_ONESIGNAL_ID, aliasId: onesignalId, identityModel: request.identityModelToUpdate) } else { - // Need to hydrate the identity model for any pending requests - if let osid = request.identityModelToIdentify.onesignalId { - request.identityModelToUpdate.hydrate([OS_ONESIGNAL_ID: osid]) - } executePendingRequests() } } onFailure: { error in From 20e9bb9a157b74b0d760aef1ea5b719f24e5784c Mon Sep 17 00:00:00 2001 From: Nan Date: Wed, 24 Jul 2024 13:33:13 -0700 Subject: [PATCH 04/10] Change handling of Identify User 409 conflict * On Identify User 409 conflict response, no longer fetch user by External ID nor transfer subscription. * Instead, make a call to CreateUser with the push subscription in the payload. * If the user has changed since then, make a CreateUser call with just the Identity Model to hydrate the OneSignal ID for any pending updates that need to be sent for the past user. * Nit: remove an extraneous error log. The SDK already logs that it handles the 409 response. And the Client will already log this error as well. --- .../Source/Executors/OSUserExecutor.swift | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift index 1e33550ea..a05982306 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift @@ -343,7 +343,6 @@ extension OSUserExecutor { 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 { @@ -351,14 +350,13 @@ extension OSUserExecutor { OneSignalLog.onesignalLog(.LL_DEBUG, message: "executeIdentifyUserRequest returned error code user-2. Now handling user-2 error response... switch to this user.") removeFromQueue(request) - // Transfer the push subscription, and fetch only if it's the current user + if OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModelToUpdate) { - fetchUser(aliasLabel: OS_EXTERNAL_ID, aliasId: request.aliasId, identityModel: request.identityModelToUpdate) - transferPushSubscriptionTo(aliasLabel: request.aliasLabel, aliasId: request.aliasId) + // Generate a Create User request, if it's still the current user + createUser(OneSignalUserManagerImpl.sharedInstance.user) } else { - // Use external_id for any pending requests, avoiding a fetch to hydrate onesignal_id - request.identityModelToUpdate.primaryAliasLabel = .external_id - executePendingRequests() + // This will hydrate the OneSignal ID for any pending requests + createUser(identityModel: request.identityModelToUpdate) } } else if responseType == .invalid || responseType == .unauthorized { // Failed, no retry From 051f29b3086e8e824b51791090484c73ff18a1e9 Mon Sep 17 00:00:00 2001 From: Nan Date: Wed, 24 Jul 2024 13:24:58 -0700 Subject: [PATCH 05/10] Identify User success will hydrate OSID + EUID * On Identify User successful, the SDK was only hydrating the OneSignal ID for past users (not the current user) and fetching the current user (which means OneSignal ID will be missing locally until that response) * Let's hydrate both OneSignal ID and External ID for all cases so that the local state can be up to date (instead of relying on the Fetch User to hydrate back the OneSignal and External IDs) --- .../Source/Executors/OSUserExecutor.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift index a05982306..8fce87276 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift @@ -335,6 +335,19 @@ extension OSUserExecutor { OneSignalCoreImpl.sharedClient().execute(request) { _ in removeFromQueue(request) + guard let onesignalId = request.identityModelToIdentify.onesignalId else { + OneSignalLog.onesignalLog(.LL_ERROR, message: "executeIdentifyUserRequest succeeded but is now missing OneSignal ID!") + executePendingRequests() + return + } + + // Need to hydrate the identity model for current user or past user with pending requests + let aliases = [ + OS_ONESIGNAL_ID: onesignalId, + request.aliasLabel: request.aliasId + ] + request.identityModelToUpdate.hydrate(aliases) + // 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) { From 5f43d4fa12b999e86a7a149e48e72287a008f9b1 Mon Sep 17 00:00:00 2001 From: Nan Date: Tue, 23 Jul 2024 07:54:31 -0700 Subject: [PATCH 06/10] All requests will only use OneSignal ID All user-update related requests will also only use OneSignal ID in the path * OSRequestUpdateProperties * OSRequestIdentifyUser * OSRequestAddAliases * OSRequestRemoveAlias * OSRequestCreateSubscription --- .../Source/Requests/OSRequestAddAliases.swift | 5 ++--- .../Source/Requests/OSRequestCreateSubscription.swift | 5 ++--- .../OneSignalUser/Source/Requests/OSRequestFetchUser.swift | 4 ++-- .../Source/Requests/OSRequestIdentifyUser.swift | 7 +++---- .../Source/Requests/OSRequestRemoveAlias.swift | 5 ++--- .../Source/Requests/OSRequestUpdateProperties.swift | 5 ++--- 6 files changed, 13 insertions(+), 18 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestAddAliases.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestAddAliases.swift index ace4ce0bc..52d4d0cfa 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestAddAliases.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestAddAliases.swift @@ -39,10 +39,9 @@ class OSRequestAddAliases: OneSignalRequest, OSUserRequest { // requires a `onesignal_id` to send this request func prepareForExecution() -> Bool { - let aliasLabel = identityModel.primaryAliasLabel - if let aliasId = identityModel.primaryAliasId, let appId = OneSignalConfigManager.getAppId() { + if let onesignalId = identityModel.onesignalId, let appId = OneSignalConfigManager.getAppId() { self.addJWTHeader(identityModel: identityModel) - self.path = "apps/\(appId)/users/by/\(aliasLabel)/\(aliasId)/identity" + self.path = "apps/\(appId)/users/by/\(OS_ONESIGNAL_ID)/\(onesignalId)/identity" return true } else { // self.path is non-nil, so set to empty string diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestCreateSubscription.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestCreateSubscription.swift index 49986ae36..e46870b2c 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestCreateSubscription.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestCreateSubscription.swift @@ -44,10 +44,9 @@ class OSRequestCreateSubscription: OneSignalRequest, OSUserRequest { // Need the onesignal_id of the user func prepareForExecution() -> Bool { - let aliasLabel = identityModel.primaryAliasLabel - if let aliasId = identityModel.primaryAliasId, let appId = OneSignalConfigManager.getAppId() { + if let onesignalId = identityModel.onesignalId, let appId = OneSignalConfigManager.getAppId() { self.addJWTHeader(identityModel: identityModel) - self.path = "apps/\(appId)/users/by/\(aliasLabel)/\(aliasId)/subscriptions" + 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 diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestFetchUser.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestFetchUser.swift index e8911ca5b..cd12416b3 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestFetchUser.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestFetchUser.swift @@ -28,8 +28,8 @@ 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. + Fetch the user by the provided alias. This is expected to be `onesignal_id` in most cases. + The `identityModel` is used to reference the user that is updated with the response. */ class OSRequestFetchUser: OneSignalRequest, OSUserRequest { var sentToClient = false diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestIdentifyUser.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestIdentifyUser.swift index 320d78b6e..5572efe05 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestIdentifyUser.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestIdentifyUser.swift @@ -48,15 +48,14 @@ class OSRequestIdentifyUser: OneSignalRequest, OSUserRequest { // requires a onesignal_id to send this request func prepareForExecution() -> Bool { - let aliasLabel = identityModelToIdentify.primaryAliasLabel - if let aliasId = identityModelToIdentify.primaryAliasId, let appId = OneSignalConfigManager.getAppId() { + if let onesignalId = identityModelToIdentify.onesignalId, let appId = OneSignalConfigManager.getAppId() { self.addJWTHeader(identityModel: identityModelToIdentify) - self.path = "apps/\(appId)/users/by/\(aliasLabel)/\(aliasId)/identity" + 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 = "" - OneSignalLog.onesignalLog(.LL_DEBUG, message: "Cannot generate the Identify User request due to null app ID or null \(aliasLabel) ID.") + OneSignalLog.onesignalLog(.LL_DEBUG, message: "Cannot generate the Identify User request due to null app ID or null OneSignal ID.") return false } } diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestRemoveAlias.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestRemoveAlias.swift index ac4343dde..49e6ea691 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestRemoveAlias.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestRemoveAlias.swift @@ -38,10 +38,9 @@ class OSRequestRemoveAlias: OneSignalRequest, OSUserRequest { var identityModel: OSIdentityModel func prepareForExecution() -> Bool { - let aliasLabel = identityModel.primaryAliasLabel - if let aliasId = identityModel.primaryAliasId, let appId = OneSignalConfigManager.getAppId() { + if let onesignalId = identityModel.onesignalId, let appId = OneSignalConfigManager.getAppId() { self.addJWTHeader(identityModel: identityModel) - self.path = "apps/\(appId)/users/by/\(aliasLabel)/\(aliasId)/identity/\(labelToRemove)" + 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 diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestUpdateProperties.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestUpdateProperties.swift index 307967111..83146ab33 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestUpdateProperties.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestUpdateProperties.swift @@ -39,12 +39,11 @@ class OSRequestUpdateProperties: OneSignalRequest, OSUserRequest { // TODO: Decide if addPushSubscriptionIdToAdditionalHeadersIfNeeded should block. // Note Android adds it to requests, if the push sub ID exists func prepareForExecution() -> Bool { - let aliasLabel = identityModel.primaryAliasLabel - if let aliasId = identityModel.primaryAliasId, + if let onesignalId = identityModel.onesignalId, let appId = OneSignalConfigManager.getAppId() { _ = self.addPushSubscriptionIdToAdditionalHeaders() self.addJWTHeader(identityModel: identityModel) - self.path = "apps/\(appId)/users/by/\(aliasLabel)/\(aliasId)" + self.path = "apps/\(appId)/users/by/\(OS_ONESIGNAL_ID)/\(onesignalId)" return true } else { // self.path is non-nil, so set to empty string From 4ed7a643dec68fb5c2221e9bbf41a79d32ae93c0 Mon Sep 17 00:00:00 2001 From: Nan Date: Wed, 24 Jul 2024 12:35:15 -0700 Subject: [PATCH 07/10] Remove concept of primary alias * Now that requests will always use OneSignal ID, remove the concept of a primary alias for an Identity Model (ie: OneSignal ID or External ID) --- .../Source/OSIdentityModel.swift | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityModel.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityModel.swift index 639e47c50..6e70b5057 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityModel.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityModel.swift @@ -29,23 +29,7 @@ import Foundation import OneSignalCore import OneSignalOSCore -// By matching the enum name to the raw value, it will always stringify correctly -enum OSDefaultAlias: String { - // swiftlint:disable identifier_name - case onesignal_id = "onesignal_id" - case external_id = "external_id" - // swiftlint:enable identifier_name -} - class OSIdentityModel: OSModel { - /** - Set either `onesignal_id` or `external_id`, representing the alias that will be used in requests. - */ - var primaryAliasLabel: OSDefaultAlias = .onesignal_id - var primaryAliasId: String? { - return if primaryAliasLabel == .external_id { externalId } else { onesignalId } - } - var onesignalId: String? { return internalGetAlias(OS_ONESIGNAL_ID) } @@ -73,7 +57,6 @@ class OSIdentityModel: OSModel { aliasesLock.withLock { super.encode(with: coder) coder.encode(aliases, forKey: "aliases") - coder.encode(primaryAliasLabel.rawValue, forKey: "primaryAliasLabel") // Encodes as String } } @@ -83,12 +66,6 @@ class OSIdentityModel: OSModel { // log error return nil } - if let rawType = coder.decodeObject(forKey: "primaryAliasLabel") as? String, - let label = OSDefaultAlias(rawValue: rawType) { - self.primaryAliasLabel = label - } else { - self.primaryAliasLabel = .onesignal_id - } self.aliases = aliases } From 589bc349470cba4758c7c18890ed8e7d27ec8665 Mon Sep 17 00:00:00 2001 From: Nan Date: Tue, 23 Jul 2024 09:58:11 -0700 Subject: [PATCH 08/10] [tests] Update tests * No more transfer subscription * All requests using EUID previously now use OSID * After a 409 conflict, there can be a follow-up CreateUser request (whose purpose is to retrieve the OneSignal ID) and a Fetch User request --- .../OneSignalUserMocks/MockUserRequests.swift | 31 ++++++++++--------- .../OneSignalUserMocks.swift | 1 - .../OneSignalUserTests.swift | 2 +- .../SwitchUserIntegrationTests.swift | 14 ++++----- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalUserMocks/MockUserRequests.swift b/iOS_SDK/OneSignalSDK/OneSignalUserMocks/MockUserRequests.swift index 10b38f4e8..403fe3dd4 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUserMocks/MockUserRequests.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUserMocks/MockUserRequests.swift @@ -94,7 +94,7 @@ extension MockUserRequests { response: userResponse ) client.setMockResponseForRequest( - request: "", + request: "", response: userResponse ) } @@ -111,6 +111,16 @@ extension MockUserRequests { request: "", error: NSError(domain: "not-important", code: 409) ) + // 2. Set the response for the subsequent Create User request + let userResponse = MockUserRequests.testIdentityPayload(onesignalId: osid, externalId: externalId) + client.setMockResponseForRequest( + request: "", + response: userResponse) + // 3. Set the response for the subsequent Fetch User request + client.setMockResponseForRequest( + request: "", + response: fetchResponse + ) } else { // The Identify User is successful, the OSID is unchanged osid = anonUserOSID @@ -119,12 +129,12 @@ extension MockUserRequests { request: "", response: fetchResponse ) + // 2. Set the response for the subsequent Fetch User request + client.setMockResponseForRequest( + request: "", + response: fetchResponse + ) } - // 2. Set the response for the subsequent Fetch User request - client.setMockResponseForRequest( - request: "", - response: fetchResponse - ) } /** @@ -147,7 +157,7 @@ extension MockUserRequests { ] ] client.setMockResponseForRequest( - request: "", + request: "", response: fetchResponse ) } @@ -207,11 +217,4 @@ extension MockUserRequests { response: response ) } - - public static func setTransferSubscriptionResponse(with client: MockOneSignalClient, externalId: String) { - client.setMockResponseForRequest( - request: "", - response: [:] // The SDK does not use the response - ) - } } diff --git a/iOS_SDK/OneSignalSDK/OneSignalUserMocks/OneSignalUserMocks.swift b/iOS_SDK/OneSignalSDK/OneSignalUserMocks/OneSignalUserMocks.swift index 72782423a..ae30a48c1 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUserMocks/OneSignalUserMocks.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUserMocks/OneSignalUserMocks.swift @@ -43,7 +43,6 @@ public class OneSignalUserMocks: NSObject { public static func resetStaticUserExecutor() { OSUserExecutor.userRequestQueue.removeAll() - OSUserExecutor.transferSubscriptionRequestQueue.removeAll() } } diff --git a/iOS_SDK/OneSignalSDK/OneSignalUserTests/OneSignalUserTests.swift b/iOS_SDK/OneSignalSDK/OneSignalUserTests/OneSignalUserTests.swift index 7d2cd01a7..8769dc3cf 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUserTests/OneSignalUserTests.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUserTests/OneSignalUserTests.swift @@ -312,7 +312,7 @@ final class OneSignalUserTests: XCTestCase { // Increase flush interval to allow all the updates to batch OSOperationRepo.sharedInstance.pollIntervalMilliseconds = 300 - + /* When */ OneSignalUserManagerImpl.sharedInstance.sendSessionTime(100) diff --git a/iOS_SDK/OneSignalSDK/OneSignalUserTests/SwitchUserIntegrationTests.swift b/iOS_SDK/OneSignalSDK/OneSignalUserTests/SwitchUserIntegrationTests.swift index 64073e7c2..f4ef0a632 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUserTests/SwitchUserIntegrationTests.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUserTests/SwitchUserIntegrationTests.swift @@ -105,7 +105,6 @@ final class SwitchUserIntegrationTests: XCTestCase { MockUserRequests.setAddTagsAndLanguageResponse(with: client, tags: tagsUserA, language: "lang_a") MockUserRequests.setAddAliasesResponse(with: client, aliases: ["alias_a": "id_a"]) MockUserRequests.setAddEmailResponse(with: client, email: "email_a@example.com") - MockUserRequests.setTransferSubscriptionResponse(with: client, externalId: userA_EUID) // Returns mocked user data to test hydration MockUserRequests.setDefaultFetchUserResponseForHydration(with: client, externalId: userA_EUID) @@ -160,7 +159,6 @@ final class SwitchUserIntegrationTests: XCTestCase { contains: ["subscription": ["token": "email_a@example.com"]]) ) XCTAssertTrue(client.hasExecutedRequestOfType(OSRequestFetchUser.self)) - XCTAssertTrue(client.hasExecutedRequestOfType(OSRequestTransferSubscription.self)) // 3. Asserts for User A - local data is updated via hydration XCTAssertEqual("remote_language", OneSignalUserManagerImpl.sharedInstance.user.propertiesModel.language) @@ -262,15 +260,15 @@ final class SwitchUserIntegrationTests: XCTestCase { // 2. Asserts for User A XCTAssertTrue(client.onlyOneRequest( // Tag + Language - contains: "apps/test-app-id/users/by/external_id/\(userA_EUID)", + contains: "apps/test-app-id/users/by/onesignal_id/\(userA_OSID)", contains: ["properties": ["language": "lang_a", "tags": tagsUserA]]) ) XCTAssertTrue(client.onlyOneRequest( // Alias - contains: "apps/test-app-id/users/by/external_id/\(userA_EUID)/identity", + contains: "apps/test-app-id/users/by/onesignal_id/\(userA_OSID)/identity", contains: ["identity": ["alias_a": "id_a"]]) ) XCTAssertTrue(client.onlyOneRequest( // Email - contains: "apps/test-app-id/users/by/external_id/\(userA_EUID)/subscriptions", + contains: "apps/test-app-id/users/by/onesignal_id/\(userA_OSID)/subscriptions", contains: ["subscription": ["token": "email_a@example.com"]]) ) @@ -388,15 +386,15 @@ final class SwitchUserIntegrationTests: XCTestCase { // 2. Asserts for User A XCTAssertTrue(client.onlyOneRequest( // Tag + Language - contains: "apps/test-app-id/users/by/external_id/\(userA_EUID)", + contains: "apps/test-app-id/users/by/onesignal_id/\(userA_OSID)", contains: ["properties": ["language": "lang_a", "tags": tagsUserA]]) ) XCTAssertTrue(client.onlyOneRequest( // Alias - contains: "apps/test-app-id/users/by/external_id/\(userA_EUID)/identity", + contains: "apps/test-app-id/users/by/onesignal_id/\(userA_OSID)/identity", contains: ["identity": ["alias_a": "id_a"]]) ) XCTAssertTrue(client.onlyOneRequest( // Email - contains: "apps/test-app-id/users/by/external_id/\(userA_EUID)/subscriptions", + contains: "apps/test-app-id/users/by/onesignal_id/\(userA_OSID)/subscriptions", contains: ["subscription": ["token": "email_a@example.com"]]) ) From 0b6ddc87d5d06f40caca967318669c3cd3ec854b Mon Sep 17 00:00:00 2001 From: Nan Date: Thu, 25 Jul 2024 16:53:00 -0700 Subject: [PATCH 09/10] Refactor create user to make the alias explicit * The 2nd use of create user is passing an external ID to hydrate the OSID. Let's make it explicit by using the alias as a parameter and creating an additional initializer for the Create User Request. * Here I also removed encoding and decoding of the `path` property. This should not be decoded as it may not be encoded yet. The `prepareForExecution` will always generate the path. Other requests follow this pattern already. --- .../Source/Executors/OSUserExecutor.swift | 15 ++++------ .../Source/Requests/OSRequestCreateUser.swift | 30 +++++++++++-------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift index 8fce87276..b7a9e7f24 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift @@ -191,15 +191,10 @@ extension OSUserExecutor { } /** - This Create User call expects an Identity Model with external ID to hydrate the OneSignal ID + This Create User call expects an external ID and the Identity Model to hydrate with the OneSignal ID */ - static func createUser(identityModel: OSIdentityModel) { - guard identityModel.externalId != nil else { - OneSignalLog.onesignalLog(.LL_ERROR, message: "createUser(identityModel) called with missing external ID") - return - } - - let request = OSRequestCreateUser(identityModel: identityModel, propertiesModel: nil, pushSubscriptionModel: nil, originalPushToken: nil) + static func createUser(aliasLabel: String, aliasId: String, identityModel: OSIdentityModel) { + let request = OSRequestCreateUser(aliasLabel: aliasLabel, aliasId: aliasId, identityModel: identityModel) appendToQueue(request) executePendingRequests() } @@ -209,7 +204,7 @@ extension OSUserExecutor { return } guard request.prepareForExecution() else { - // Currently there are no requirements needed before sending this request + // Currently there are no requirements needed before sending this request, so this will set the path return } request.sentToClient = true @@ -369,7 +364,7 @@ extension OSUserExecutor { createUser(OneSignalUserManagerImpl.sharedInstance.user) } else { // This will hydrate the OneSignal ID for any pending requests - createUser(identityModel: request.identityModelToUpdate) + createUser(aliasLabel: request.aliasLabel, aliasId: request.aliasId, identityModel: request.identityModelToUpdate) } } else if responseType == .invalid || responseType == .unauthorized { // Failed, no retry diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestCreateUser.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestCreateUser.swift index 720676fbf..4532f44e5 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestCreateUser.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestCreateUser.swift @@ -66,7 +66,7 @@ class OSRequestCreateUser: OneSignalRequest, OSUserRequest { self.originalPushToken = pushSubscriptionModel.address } - init(identityModel: OSIdentityModel, propertiesModel: OSPropertiesModel?, pushSubscriptionModel: OSSubscriptionModel?, originalPushToken: String?) { + init(identityModel: OSIdentityModel, propertiesModel: OSPropertiesModel, pushSubscriptionModel: OSSubscriptionModel, originalPushToken: String?) { self.identityModel = identityModel self.pushSubscriptionModel = pushSubscriptionModel self.originalPushToken = originalPushToken @@ -82,18 +82,25 @@ class OSRequestCreateUser: OneSignalRequest, OSUserRequest { } // Properties Object - if let propertiesModel = propertiesModel { - var propertiesObject: [String: Any] = [:] - propertiesObject["language"] = propertiesModel.language - propertiesObject["timezone_id"] = propertiesModel.timezoneId - params["properties"] = propertiesObject - } + var propertiesObject: [String: Any] = [:] + propertiesObject["language"] = propertiesModel.language + propertiesObject["timezone_id"] = propertiesModel.timezoneId + params["properties"] = propertiesObject params["refresh_device_metadata"] = true self.parameters = params - if let pushSub = pushSubscriptionModel { - self.updatePushSubscriptionModel(pushSub) - } + self.updatePushSubscriptionModel(pushSubscriptionModel) + self.method = POST + } + + init(aliasLabel: String, aliasId: String, identityModel: OSIdentityModel) { + self.identityModel = identityModel + self.stringDescription = "" + super.init() + self.parameters = [ + "identity": [aliasLabel: aliasId], + "refresh_device_metadata": true, + ] self.method = POST } @@ -103,7 +110,6 @@ class OSRequestCreateUser: OneSignalRequest, OSUserRequest { 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") } @@ -112,7 +118,6 @@ class OSRequestCreateUser: OneSignalRequest, OSUserRequest { let identityModel = coder.decodeObject(forKey: "identityModel") as? OSIdentityModel, 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 @@ -125,7 +130,6 @@ class OSRequestCreateUser: OneSignalRequest, OSUserRequest { super.init() self.parameters = parameters self.method = HTTPMethod(rawValue: rawMethod) - self.path = path self.timestamp = timestamp } } From bc9112c394bc9382c0f451b6c17a2bc901bc949a Mon Sep 17 00:00:00 2001 From: Nan Date: Thu, 25 Jul 2024 16:55:10 -0700 Subject: [PATCH 10/10] Fix a bug to fetch user after create user is made * After a Create User is successful, we should fetch the user for hydration. There was a bug introduced in an earlier commit that checked the request payload for the OSID, when it should be retrieved from the server response --- .../OneSignalUser/Source/Executors/OSUserExecutor.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift index b7a9e7f24..7577310d3 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift @@ -230,7 +230,7 @@ extension OSUserExecutor { // Fetch the user only if its the current user and non-anonymous if OneSignalUserManagerImpl.sharedInstance.isCurrentUser(request.identityModel), let identity = request.parameters?["identity"] as? [String: String], - let onesignalId = identity[OS_ONESIGNAL_ID], + let onesignalId = request.identityModel.onesignalId, identity[OS_EXTERNAL_ID] != nil { fetchUser(aliasLabel: OS_ONESIGNAL_ID, aliasId: onesignalId, identityModel: request.identityModel) } else {