Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,12 @@
#define OSUD_PERMISSION_EPHEMERAL_FROM @"OSUD_PERMISSION_EPHEMERAL_FROM" // * OSUD_PERMISSION_EPHEMERAL_FROM
#define OSUD_LANGUAGE @"OSUD_LANGUAGE" // * OSUD_LANGUAGE
#define DEFAULT_LANGUAGE @"en" // * OSUD_LANGUAGE
// Push Subscription
#define OSUD_PUSH_SUBSCRIPTION_ID @"GT_PLAYER_ID" // * OSUD_PUSH_SUBSCRIPTION_ID
#define OSUD_PUSH_TOKEN @"GT_DEVICE_TOKEN" // * OSUD_PUSH_TOKEN

/* Push Subscription */
#define OSUD_LEGACY_PLAYER_ID @"GT_PLAYER_ID" // The legacy player ID from SDKs prior to 5.x.x
#define OSUD_PUSH_SUBSCRIPTION_ID @"OSUD_PUSH_SUBSCRIPTION_ID"
#define OSUD_PUSH_TOKEN @"GT_DEVICE_TOKEN"

// Notification
#define OSUD_LAST_MESSAGE_OPENED @"GT_LAST_MESSAGE_OPENED_" // * OSUD_MOST_RECENT_NOTIFICATION_OPENED
#define OSUD_NOTIFICATION_OPEN_LAUNCH_URL @"ONESIGNAL_INAPP_LAUNCH_URL" // * OSUD_NOTIFICATION_OPEN_LAUNCH_URL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,8 @@ class OSSubscriptionModel: OSModel {
guard self.type == .push && _isDisabled != oldValue else {
return
}
notificationTypes = -2
firePushSubscriptionChanged(.isDisabled(oldValue))
notificationTypes = -2
}
}

Expand Down
153 changes: 142 additions & 11 deletions iOS_SDK/OneSignalSDK/OneSignalUser/Source/OSUserRequests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import OneSignalOSCore

/**
Involved in the login process and responsible for Identify User and Create User.
Can execute `OSRequestCreateUser`, `OSRequestIdentifyUser`, `OSRequestTransferSubscription`, `OSRequestFetchUser`.
Can execute `OSRequestCreateUser`, `OSRequestIdentifyUser`, `OSRequestTransferSubscription`, `OSRequestFetchUser`, `OSRequestFetchIdentityBySubscription`.
*/
class OSUserExecutor {
static var userRequestQueue: [OSUserRequest] = []
Expand All @@ -42,11 +42,24 @@ class OSUserExecutor {
static func start() {
var userRequestQueue: [OSUserRequest] = []

// Read unfinished Create User + Identify User requests from cache, if any...
// 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: OSRequestCreateUser.self), let req = request as? OSRequestCreateUser {
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
Expand Down Expand Up @@ -144,8 +157,11 @@ class OSUserExecutor {
if !request.prepareForExecution() {
return
}

if request.isKind(of: OSRequestCreateUser.self), let createUserRequest = request as? OSRequestCreateUser {

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 {
Expand Down Expand Up @@ -262,8 +278,8 @@ class OSUserExecutor {
return response?["properties"] as? [String: Any]
}

static func parseIdentityObjectResponse(_ response: [AnyHashable: Any]?) -> [String: Any]? {
return response?["identity"] 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
Expand Down Expand Up @@ -304,7 +320,7 @@ class 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
if let _ = OneSignalUserManagerImpl.sharedInstance.identityModelStore.getModel(modelId: request.identityModel.modelId),
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)
Expand All @@ -323,7 +339,60 @@ class OSUserExecutor {
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)")

// TODO: Differentiate error cases

// If the error is not retryable, remove from cache and queue
if let nsError = error as? NSError,
nsError.code < 500 && nsError.code != 0 {
// remove the subscription_id but keep the same push subscription model
OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModelStore.getModels()[OS_PUSH_SUBSCRIPTION_MODEL_KEY]?.subscriptionId = nil
removeFromQueue(request)
}
// Otherwise it is a retryable error
executePendingRequests()
}
}

static func identifyUser(externalId: String, identityModelToIdentify: OSIdentityModel, identityModelToUpdate: OSIdentityModel) {
let request = OSRequestIdentifyUser(
aliasLabel: OS_EXTERNAL_ID,
Expand Down Expand Up @@ -352,22 +421,21 @@ class 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 let _ = OneSignalUserManagerImpl.sharedInstance.identityModelStore.getModel(modelId: request.identityModelToUpdate.modelId) {
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)")
removeFromQueue(request)
// Returns 409 if any provided (label, id) pair exists on another User, so the SDK will switch to this user.
if let nsError = error as? NSError {
if nsError.code == 409 {
OneSignalLog.onesignalLog(.LL_VERBOSE, message: "executeIdentifyUserRequest returned 409, failed due to alias already assigned to a different user. Now switch to this user.")

removeFromQueue(request)
// Fetch the user only if its the current user
if let _ = OneSignalUserManagerImpl.sharedInstance.identityModelStore.getModel(modelId: request.identityModelToUpdate.modelId) {
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?
}
Expand Down Expand Up @@ -581,6 +649,69 @@ class OSRequestCreateUser: OneSignalRequest, OSUserRequest {
}
}

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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager {

OSNotificationsManager.delegate = self

// Load user from cache, if any
// Path 1. Load user from cache, if any
// Corrupted state if any of these models exist without the others
if let identityModel = identityModelStore.getModels()[OS_IDENTITY_MODEL_KEY],
let propertiesModel = propertiesModelStore.getModels()[OS_PROPERTIES_MODEL_KEY],
Expand All @@ -211,8 +211,16 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager {
OSOperationRepo.sharedInstance.addExecutor(propertyExecutor)
OSOperationRepo.sharedInstance.addExecutor(subscriptionExecutor)

// Creates an anonymous user if there isn't one in the cache
createUserIfNil()
// Path 2. There is a legacy player to migrate
if let legacyPlayerId = OneSignalUserDefaults.initShared().getSavedString(forKey: OSUD_LEGACY_PLAYER_ID, defaultValue: nil) {
OneSignalLog.onesignalLog(.LL_DEBUG, message: "OneSignalUserManager: creating user linked to legacy subscription \(legacyPlayerId)")
createUserFromLegacyPlayer(legacyPlayerId)
OneSignalUserDefaults.initStandard().removeValue(forKey: OSUD_LEGACY_PLAYER_ID)
OneSignalUserDefaults.initShared().removeValue(forKey: OSUD_LEGACY_PLAYER_ID)
} else {
// Path 3. Creates an anonymous user if there isn't one in the cache nor a legacy player
createUserIfNil()
}

// Model store listeners subscribe to their models
identityModelStoreListener.start()
Expand All @@ -235,6 +243,20 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager {
_ = _login(externalId: externalId, token: token)
}

/**
Converting a 3.x player to a 5.x user. There is a cached legacy player, so we will create the user based on the legacy player ID.
*/
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
Expand Down Expand Up @@ -289,6 +311,13 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager {
)
}

/**
Returns if the OSIdentityModel passed in belongs to the current user. This method is used in deciding whether or not to hydrate via a server response, for example.
*/
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.
*/
Expand Down Expand Up @@ -378,7 +407,7 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager {

// TODO: We will have to save subscription_id and push_token to user defaults when we get them

let pushSubscription = pushSubscriptionModel ?? createDefaultPushSubscription()
let pushSubscription = pushSubscriptionModel ?? createDefaultPushSubscription(subscriptionId: nil)

// Add pushSubscription to store if not present
if !pushSubscriptionModelStore.getModels().keys.contains(OS_PUSH_SUBSCRIPTION_MODEL_KEY) {
Expand All @@ -389,11 +418,14 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager {
return self.user
}

func createDefaultPushSubscription() -> OSSubscriptionModel {
/**
Creates a default Push Subscription Model using the optionally passed in subscriptionId. An scenario where the subscriptionId will be passed in is when we are converting a legacy player's information from 3.x into a Push Subscription Model.
*/
func createDefaultPushSubscription(subscriptionId: String?) -> OSSubscriptionModel {
let sharedUserDefaults = OneSignalUserDefaults.initShared()
let reachable = OSNotificationsManager.currentPermissionState.reachable
let token = sharedUserDefaults.getSavedString(forKey: OSUD_PUSH_TOKEN, defaultValue: nil)
let subscriptionId = sharedUserDefaults.getSavedString(forKey: OSUD_PUSH_SUBSCRIPTION_ID, defaultValue: nil)
let subscriptionId = subscriptionId ?? sharedUserDefaults.getSavedString(forKey: OSUD_PUSH_SUBSCRIPTION_ID, defaultValue: nil)

return OSSubscriptionModel(type: .push,
address: token,
Expand Down
2 changes: 2 additions & 0 deletions iOS_SDK/OneSignalSDK/Source/OneSignal.m
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,8 @@ + (void)handleAppIdChange:(NSString*)appId {
// Remove player_id from both standard and shared NSUserDefaults
[standardUserDefaults removeValueForKey:OSUD_PUSH_SUBSCRIPTION_ID];
[sharedUserDefaults removeValueForKey:OSUD_PUSH_SUBSCRIPTION_ID];
[standardUserDefaults removeValueForKey:OSUD_LEGACY_PLAYER_ID];
[sharedUserDefaults removeValueForKey:OSUD_LEGACY_PLAYER_ID];

// Clear all cached data, does not start User Module nor call logout.
[OneSignalUserManagerImpl.sharedInstance clearAllModelsFromStores];
Expand Down