Skip to content

Commit 1d7d585

Browse files
committed
Subscription executor wrap up, add some more tests
* The delete subscription request now has identity model, similar to the Create subscription request * The update subscription request is used only for the push sub, and it does not use User JWT, only a push token header * The "Device-Auth-Push-Token" header has to be base 64 encoded * Move some auth helpers into the JWT extension, and move execute request methods into an extension to address swiflint type_body_length violation
1 parent 0104d1f commit 1d7d585

File tree

6 files changed

+257
-67
lines changed

6 files changed

+257
-67
lines changed

iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalCommonDefines.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ typedef enum {GET, POST, HEAD, PUT, DELETE, OPTIONS, CONNECT, TRACE, PATCH} HTTP
363363
#define OS_PROPERTIES_EXECUTOR_PENDING_QUEUE_KEY @"OS_PROPERTIES_EXECUTOR_PENDING_QUEUE_KEY"
364364

365365
// Subscription Executor
366+
#define OS_SUBSCRIPTION_EXECUTOR @"OS_SUBSCRIPTION_EXECUTOR"
366367
#define OS_SUBSCRIPTION_EXECUTOR_DELTA_QUEUE_KEY @"OS_SUBSCRIPTION_EXECUTOR_DELTA_QUEUE_KEY"
367368
#define OS_SUBSCRIPTION_EXECUTOR_ADD_REQUEST_QUEUE_KEY @"OS_SUBSCRIPTION_EXECUTOR_ADD_REQUEST_QUEUE_KEY"
368369
#define OS_SUBSCRIPTION_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY @"OS_SUBSCRIPTION_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY"

iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSSubscriptionOperationExecutor.swift

Lines changed: 100 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor {
4747
init(newRecordsState: OSNewRecordsState, jwtConfig: OSUserJwtConfig) {
4848
self.newRecordsState = newRecordsState
4949
self.jwtConfig = jwtConfig
50-
// Read unfinished deltas and requests from cache, if any...
50+
self.jwtConfig.subscribe(self, key: OS_SUBSCRIPTION_EXECUTOR)
5151
uncacheDeltas()
5252
uncacheRequests()
5353
}
@@ -141,17 +141,30 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor {
141141
private func linkDeleteSubscriptionRequests(requests: inout [OSRequestDeleteSubscription]) {
142142
// Hook each uncached Request to the model in the store
143143
for (index, request) in requests.enumerated().reversed() {
144+
// 1. Hook up the subscription model
144145
if let subscriptionModel = getSubscriptionModelFromStores(modelId: request.subscriptionModel.modelId) {
145-
// 1. The model exists in the store, set it to be the Request's model
146+
// a. The model exists in the store, set it to be the Request's model
146147
request.subscriptionModel = subscriptionModel
147148
} else if let subscriptionModel = subscriptionModels[request.subscriptionModel.modelId] {
148-
// 2. The model exists in the dict of seen subscription models
149+
// b. The model exists in the dict of seen subscription models
149150
request.subscriptionModel = subscriptionModel
150151
} else if !request.prepareForExecution(newRecordsState: newRecordsState) {
151-
// 3. The model does not exist AND this request cannot be sent, drop this Request
152+
// c. The model does not exist AND this request cannot be sent, drop this Request
152153
OneSignalLog.onesignalLog(.LL_ERROR, message: "OSSubscriptionOperationExecutor.init dropped \(request)")
153154
requests.remove(at: index)
154155
}
156+
// 2. Hook up the identity model
157+
if let identityModel = OneSignalUserManagerImpl.sharedInstance.getIdentityModel(request.identityModel.modelId) {
158+
// a. The model exist in the repo
159+
request.identityModel = identityModel
160+
} else if request.prepareForExecution(newRecordsState: newRecordsState) {
161+
// b. The request can be sent, add the model to the repo
162+
OneSignalUserManagerImpl.sharedInstance.addIdentityModelToRepo(request.identityModel)
163+
} else {
164+
// c. The model do not exist AND this request cannot be sent, drop this Request
165+
OneSignalLog.onesignalLog(.LL_WARN, message: "OSSubscriptionOperationExecutor.init dropped: \(request)")
166+
requests.remove(at: index)
167+
}
155168
}
156169
self.removeRequestQueue = requests
157170
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_SUBSCRIPTION_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY, withValue: self.removeRequestQueue)
@@ -254,8 +267,20 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor {
254267
self.addRequestQueue.append(request)
255268

256269
case OS_REMOVE_SUBSCRIPTION_DELTA:
270+
// Only create the request if the identity model exists
271+
guard let identityModel = OneSignalUserManagerImpl.sharedInstance.getIdentityModel(delta.identityModelId) else {
272+
OneSignalLog.onesignalLog(.LL_ERROR, message: "OSSubscriptionOperationExecutor.processDeltaQueue dropped \(delta)")
273+
continue
274+
}
275+
276+
// If JWT is on but the external ID does not exist, drop this Delta
277+
if self.jwtConfig.isRequired == true, identityModel.externalId == nil {
278+
print("\(delta) is Invalid with JWT, being dropped")
279+
}
280+
257281
let request = OSRequestDeleteSubscription(
258-
subscriptionModel: subModel
282+
subscriptionModel: subModel,
283+
identityModel: identityModel
259284
)
260285
self.removeRequestQueue.append(request)
261286

@@ -317,31 +342,11 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor {
317342
}
318343
}
319344
}
345+
}
320346

321-
func handleUnauthorizedError(externalId: String, error: NSError, request: OSUserRequest) {
322-
if jwtConfig.isRequired ?? false {
323-
self.pendRequestUntilAuthUpdated(request, externalId: externalId)
324-
OneSignalUserManagerImpl.sharedInstance.invalidateJwtForExternalId(externalId: externalId, error: error)
325-
}
326-
}
327-
328-
func pendRequestUntilAuthUpdated(_ request: OSUserRequest, externalId: String?) {
329-
self.dispatchQueue.async {
330-
self.removeFromRequestQueueAndPersist(request)
331-
guard let externalId = externalId else {
332-
return
333-
}
334-
var requests = self.pendingAuthRequests[externalId] ?? []
335-
let inQueue = requests.contains(where: {$0 == request})
336-
guard !inQueue else {
337-
return
338-
}
339-
requests.append(request)
340-
self.pendingAuthRequests[externalId] = requests
341-
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_SUBSCRIPTION_EXECUTOR_PENDING_QUEUE_KEY, withValue: self.pendingAuthRequests)
342-
}
343-
}
347+
// MARK: - Execution
344348

349+
extension OSSubscriptionOperationExecutor {
345350
func executeCreateSubscriptionRequest(_ request: OSRequestCreateSubscription, inBackground: Bool) {
346351
guard !request.sentToClient else {
347352
return
@@ -416,7 +421,7 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor {
416421
guard !request.sentToClient else {
417422
return
418423
}
419-
// ECM TODO
424+
// ECM TODO - Delete Subscription, not supported on JWT yet (9-23-2024)
420425
// guard request.addJWTHeaderIsValid(identityModel: request.identityModel) else {
421426
// pendRequestUntilAuthUpdated(request, externalId:request.identityModel.externalId)
422427
// return
@@ -448,8 +453,7 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor {
448453
if let nsError = error as? NSError {
449454
let responseType = OSNetworkingUtils.getResponseStatusType(nsError.code)
450455
if responseType == .unauthorized && (self.jwtConfig.isRequired ?? false) {
451-
// ECM The delete subscription request doesn't have an identity model?
452-
if let externalId = OneSignalUserManagerImpl.sharedInstance.user.identityModel.externalId {
456+
if let externalId = request.identityModel.externalId {
453457
self.handleUnauthorizedError(externalId: externalId, error: nsError, request: request)
454458
}
455459
request.sentToClient = false
@@ -470,11 +474,6 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor {
470474
guard !request.sentToClient else {
471475
return
472476
}
473-
// ECM TODO
474-
// guard request.addJWTHeaderIsValid(identityModel: request.identityModel) else {
475-
// pendRequestUntilAuthUpdated(request, externalId:request.identityModel.externalId)
476-
// return
477-
// }
478477
guard request.prepareForExecution(newRecordsState: newRecordsState) else {
479478
return
480479
}
@@ -499,11 +498,7 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor {
499498
if let nsError = error as? NSError {
500499
let responseType = OSNetworkingUtils.getResponseStatusType(nsError.code)
501500
if responseType == .unauthorized && (self.jwtConfig.isRequired ?? false) {
502-
// ECM The update subscription request doesn't have an identity model?
503-
if let externalId = OneSignalUserManagerImpl.sharedInstance.user.identityModel.externalId {
504-
self.handleUnauthorizedError(externalId: externalId, error: nsError, request: request)
505-
}
506-
request.sentToClient = false
501+
// TODO: Jwt, do we need to handle this case, as this request does not use user JWT
507502
} else if responseType != .retryable {
508503
// Fail, no retry, remove from cache and queue
509504
self.removeFromRequestQueueAndPersist(request)
@@ -520,14 +515,40 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor {
520515
extension OSSubscriptionOperationExecutor: OSUserJwtConfigListener {
521516
func onRequiresUserAuthChanged(from: OneSignalOSCore.OSRequiresUserAuth, to: OneSignalOSCore.OSRequiresUserAuth) {
522517
print("❌ OSSubscriptionOperationExecutor onUserAuthChanged from \(String(describing: from)) to \(String(describing: to))")
523-
// ECM TODO If auth changed from false or unknown to true, process requests
518+
if to == .on {
519+
removeInvalidDeltasAndRequests()
520+
}
524521
}
525522

526523
func onJwtUpdated(externalId: String, token: String?) {
527524
print("❌ OSSubscriptionOperationExecutor onJwtUpdated for \(externalId) to \(String(describing: token))")
528525
reQueuePendingRequestsForExternalId(externalId: externalId)
529526
}
530527

528+
func handleUnauthorizedError(externalId: String, error: NSError, request: OSUserRequest) {
529+
if jwtConfig.isRequired ?? false {
530+
self.pendRequestUntilAuthUpdated(request, externalId: externalId)
531+
OneSignalUserManagerImpl.sharedInstance.invalidateJwtForExternalId(externalId: externalId, error: error)
532+
}
533+
}
534+
535+
func pendRequestUntilAuthUpdated(_ request: OSUserRequest, externalId: String?) {
536+
self.dispatchQueue.async {
537+
self.removeFromRequestQueueAndPersist(request)
538+
guard let externalId = externalId else {
539+
return
540+
}
541+
var requests = self.pendingAuthRequests[externalId] ?? []
542+
let inQueue = requests.contains(where: {$0 == request})
543+
guard !inQueue else {
544+
return
545+
}
546+
requests.append(request)
547+
self.pendingAuthRequests[externalId] = requests
548+
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_SUBSCRIPTION_EXECUTOR_PENDING_QUEUE_KEY, withValue: self.pendingAuthRequests)
549+
}
550+
}
551+
531552
private func reQueuePendingRequestsForExternalId(externalId: String) {
532553
self.dispatchQueue.async {
533554
guard let requests = self.pendingAuthRequests[externalId] else {
@@ -538,18 +559,52 @@ extension OSSubscriptionOperationExecutor: OSUserJwtConfigListener {
538559
self.addRequestQueue.append(addRequest)
539560
} else if let removeRequest = request as? OSRequestDeleteSubscription {
540561
self.removeRequestQueue.append(removeRequest)
541-
} else if let updateRequest = request as? OSRequestUpdateSubscription {
542-
self.updateRequestQueue.append(updateRequest)
543562
}
544563
}
545564
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_SUBSCRIPTION_EXECUTOR_ADD_REQUEST_QUEUE_KEY, withValue: self.addRequestQueue)
546565
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_SUBSCRIPTION_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY, withValue: self.removeRequestQueue)
547-
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_SUBSCRIPTION_EXECUTOR_UPDATE_REQUEST_QUEUE_KEY, withValue: self.updateRequestQueue)
548566
self.pendingAuthRequests[externalId] = nil
549567
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_SUBSCRIPTION_EXECUTOR_PENDING_QUEUE_KEY, withValue: self.pendingAuthRequests)
550568
self.processRequestQueue(inBackground: false)
551569
}
552570
}
571+
572+
/**
573+
Drops deltas and requests that add and remove subscriptions on unidentified users.
574+
Subscription updates are used only for push subscriptions, which are kept as they do not use User JWT.
575+
*/
576+
private func removeInvalidDeltasAndRequests() {
577+
self.dispatchQueue.async {
578+
print("❌ OSSubscriptionOperationExecutor.removeInvalidDeltasAndRequests called")
579+
580+
for (index, delta) in self.deltaQueue.enumerated().reversed() {
581+
if delta.name != OS_UPDATE_SUBSCRIPTION_DELTA,
582+
let identityModel = OneSignalUserManagerImpl.sharedInstance.getIdentityModel(delta.identityModelId),
583+
identityModel.externalId == nil
584+
{
585+
print(" \(delta) is Invalid, being removed")
586+
self.deltaQueue.remove(at: index)
587+
}
588+
}
589+
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_SUBSCRIPTION_EXECUTOR_DELTA_QUEUE_KEY, withValue: self.deltaQueue)
590+
591+
for (index, request) in self.addRequestQueue.enumerated().reversed() {
592+
if request.identityModel.externalId == nil {
593+
print(" \(request) is Invalid, being removed")
594+
self.addRequestQueue.remove(at: index)
595+
}
596+
}
597+
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_SUBSCRIPTION_EXECUTOR_ADD_REQUEST_QUEUE_KEY, withValue: self.updateRequestQueue)
598+
599+
for (index, request) in self.removeRequestQueue.enumerated().reversed() {
600+
if request.identityModel.externalId == nil {
601+
print(" \(request) is Invalid, being removed")
602+
self.removeRequestQueue.remove(at: index)
603+
}
604+
}
605+
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_SUBSCRIPTION_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY, withValue: self.updateRequestQueue)
606+
}
607+
}
553608
}
554609

555610
extension OSSubscriptionOperationExecutor: OSLoggable {

iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestDeleteSubscription.swift

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,43 +41,50 @@ class OSRequestDeleteSubscription: OneSignalRequest, OSUserRequest {
4141
}
4242

4343
var subscriptionModel: OSSubscriptionModel
44+
var identityModel: OSIdentityModel
4445

45-
// Need the subscription_id
4646
func prepareForExecution(newRecordsState: OSNewRecordsState) -> Bool {
47-
if let subscriptionId = subscriptionModel.subscriptionId,
48-
newRecordsState.canAccess(subscriptionId),
49-
let appId = OneSignalConfigManager.getAppId()
50-
{
51-
self.path = "apps/\(appId)/subscriptions/\(subscriptionId)"
52-
return true
53-
} else {
47+
guard
48+
let subscriptionId = subscriptionModel.subscriptionId,
49+
let token = subscriptionModel.address,
50+
newRecordsState.canAccess(subscriptionId),
51+
let appId = OneSignalConfigManager.getAppId(),
52+
let _ = checkUserRequirementsAndReturnAlias(identityModel, newRecordsState)
53+
else {
5454
return false
5555
}
56+
57+
self.path = "apps/\(appId)/subscriptions/by/type/\(subscriptionModel.type)/token/\(token)"
58+
return true
5659
}
5760

58-
init(subscriptionModel: OSSubscriptionModel) {
61+
init(subscriptionModel: OSSubscriptionModel, identityModel: OSIdentityModel) {
5962
self.subscriptionModel = subscriptionModel
63+
self.identityModel = identityModel
6064
self.stringDescription = "<OSRequestDeleteSubscription with subscriptionModel: \(subscriptionModel.address ?? "nil")>"
6165
super.init()
6266
self.method = DELETE
6367
}
6468

6569
func encode(with coder: NSCoder) {
6670
coder.encode(subscriptionModel, forKey: "subscriptionModel")
71+
coder.encode(identityModel, forKey: "identityModel")
6772
coder.encode(method.rawValue, forKey: "method") // Encodes as String
6873
coder.encode(timestamp, forKey: "timestamp")
6974
}
7075

7176
required init?(coder: NSCoder) {
7277
guard
7378
let subscriptionModel = coder.decodeObject(forKey: "subscriptionModel") as? OSSubscriptionModel,
79+
let identityModel = coder.decodeObject(forKey: "identityModel") as? OSIdentityModel,
7480
let rawMethod = coder.decodeObject(forKey: "method") as? UInt32,
7581
let timestamp = coder.decodeObject(forKey: "timestamp") as? Date
7682
else {
7783
// Log error
7884
return nil
7985
}
8086
self.subscriptionModel = subscriptionModel
87+
self.identityModel = identityModel
8188
self.stringDescription = "<OSRequestDeleteSubscription with subscriptionModel: \(subscriptionModel.address ?? "nil")>"
8289
super.init()
8390
self.method = HTTPMethod(rawValue: rawMethod)

iOS_SDK/OneSignalSDK/OneSignalUser/Source/Support/OSUserUtils.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,11 @@ class OSUserUtils {
6363
if let pushSubscriptionId = OneSignalUserManagerImpl.sharedInstance.pushSubscriptionId {
6464
headers["OneSignal-Subscription-Id"] = pushSubscriptionId
6565
}
66-
if let token = OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModel?.address {
67-
headers["Device-Auth-Push-Token"] = "Basic \(token)"
66+
if let token = OneSignalUserManagerImpl.sharedInstance.pushSubscriptionModel?.address,
67+
let data = token.data(using: .utf8)
68+
{
69+
let base64String = data.base64EncodedString()
70+
headers["Device-Auth-Push-Token"] = "Basic \(base64String)"
6871
}
6972
return headers
7073
}

0 commit comments

Comments
 (0)