Skip to content
Merged
163 changes: 61 additions & 102 deletions iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift

Large diffs are not rendered by default.

23 changes: 0 additions & 23 deletions iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSIdentityModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will probably want to use this concept for JWT, since we will make requests with external_id when it is enabled. I am still ok with removing this code in this PR to if you want to, just making a note in-case this didn't come up.

Copy link
Copy Markdown
Contributor Author

@nan-li nan-li Jul 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jkasten2 I thought about JWT before removing this. The issue with the primaryAliasLabel as it exists here is that every Identity Model needs to manage it. Therefore, when the SDK first detects JWT is on, it needs to go and update every Identity Model. Instead, some flag can be centralized.

The reason this primaryAliasLabel existed on the Identity Models is that some models can use external ID instead of onesignal ID.

var primaryAliasId: String? {
return if primaryAliasLabel == .external_id { externalId } else { onesignalId }
}

var onesignalId: String? {
return internalGetAlias(OS_ONESIGNAL_ID)
}
Expand Down Expand Up @@ -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
}
}

Expand All @@ -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
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -40,7 +41,7 @@ class OSRequestCreateUser: OneSignalRequest, OSUserRequest {
}

var identityModel: OSIdentityModel
var pushSubscriptionModel: OSSubscriptionModel
var pushSubscriptionModel: OSSubscriptionModel?
var originalPushToken: String?

func prepareForExecution() -> Bool {
Expand All @@ -55,8 +56,11 @@ 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
Expand Down Expand Up @@ -89,36 +93,43 @@ class OSRequestCreateUser: OneSignalRequest, OSUserRequest {
self.method = POST
}

init(aliasLabel: String, aliasId: String, identityModel: OSIdentityModel) {
self.identityModel = identityModel
self.stringDescription = "<OSRequestCreateUser with alias \(aliasLabel): \(aliasId)>"
super.init()
self.parameters = [
"identity": [aliasLabel: aliasId],
"refresh_device_metadata": true,
]
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.pushSubscriptionModel = coder.decodeObject(forKey: "pushSubscriptionModel") as? OSSubscriptionModel
self.originalPushToken = coder.decodeObject(forKey: "originalPushToken") as? String
self.stringDescription = "<OSRequestCreateUser with externalId: \(identityModel.externalId ?? "nil")>"
super.init()
self.parameters = parameters
self.method = HTTPMethod(rawValue: rawMethod)
self.path = path
self.timestamp = timestamp
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "<OSRequestTransferSubscription to \(aliasLabel): \(aliasId)>"
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 = "<OSRequestTransferSubscription to \(aliasLabel): \(aliasId)>"
super.init()
self.parameters = parameters
self.method = HTTPMethod(rawValue: rawMethod)
self.timestamp = timestamp
_ = prepareForExecution()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 17 additions & 14 deletions iOS_SDK/OneSignalSDK/OneSignalUserMocks/MockUserRequests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ extension MockUserRequests {
response: userResponse
)
client.setMockResponseForRequest(
request: "<OSRequestFetchUser with external_id: \(externalId)>",
request: "<OSRequestFetchUser with onesignal_id: \(osid)>",
response: userResponse
)
}
Expand All @@ -111,6 +111,16 @@ extension MockUserRequests {
request: "<OSRequestIdentifyUser with external_id: \(externalId)>",
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: "<OSRequestCreateUser with externalId: \(externalId)>",
response: userResponse)
// 3. Set the response for the subsequent Fetch User request
client.setMockResponseForRequest(
request: "<OSRequestFetchUser with onesignal_id: \(osid)>",
response: fetchResponse
)
} else {
// The Identify User is successful, the OSID is unchanged
osid = anonUserOSID
Expand All @@ -119,12 +129,12 @@ extension MockUserRequests {
request: "<OSRequestIdentifyUser with external_id: \(externalId)>",
response: fetchResponse
)
// 2. Set the response for the subsequent Fetch User request
client.setMockResponseForRequest(
request: "<OSRequestFetchUser with onesignalId: \(osid)>",
response: fetchResponse
)
}
// 2. Set the response for the subsequent Fetch User request
client.setMockResponseForRequest(
request: "<OSRequestFetchUser with external_id: \(externalId)>",
response: fetchResponse
)
}

/**
Expand All @@ -147,7 +157,7 @@ extension MockUserRequests {
]
]
client.setMockResponseForRequest(
request: "<OSRequestFetchUser with external_id: \(externalId)>",
request: "<OSRequestFetchUser with onesignal_id: \(osid)>",
response: fetchResponse
)
}
Expand Down Expand Up @@ -207,11 +217,4 @@ extension MockUserRequests {
response: response
)
}

public static func setTransferSubscriptionResponse(with client: MockOneSignalClient, externalId: String) {
client.setMockResponseForRequest(
request: "<OSRequestTransferSubscription to external_id: \(externalId)>",
response: [:] // The SDK does not use the response
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ public class OneSignalUserMocks: NSObject {

public static func resetStaticUserExecutor() {
OSUserExecutor.userRequestQueue.removeAll()
OSUserExecutor.transferSubscriptionRequestQueue.removeAll()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading