From c9b0a94a5d9acca8e4425cadd8d45f2d382d3f0c Mon Sep 17 00:00:00 2001 From: Nan Date: Fri, 30 Aug 2024 09:14:16 -0700 Subject: [PATCH] Modify getting IAM to use alias and jwt * All methods that trigger fetching IAM from server will no longer pass the push subscription ID. * The method to fetch IAM will itself handle the requirements. * To get IAMs from the server, the following requirements are necessary: - A subscription ID - An appropriate alias (depending on Identity Verification enabled) for the subscription - A valid JWT token for the user if Identity Verification is enabled * OSMessagingController will observe user and jwt --- .../Controller/OSMessagingController.h | 5 +- .../Controller/OSMessagingController.m | 86 +++++++++++++++++-- .../OSInAppMessagingDefines.h | 2 + .../OneSignalInAppMessages.h | 2 +- .../OneSignalInAppMessages.m | 4 +- .../Requests/OSInAppMessagingRequests.h | 2 +- .../Requests/OSInAppMessagingRequests.m | 8 +- .../Source/Jwt/OSAliasPair.swift | 4 +- iOS_SDK/OneSignalSDK/Source/OneSignal.m | 19 ++-- 9 files changed, 107 insertions(+), 25 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Controller/OSMessagingController.h b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Controller/OSMessagingController.h index d37e3577e..089a7be59 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Controller/OSMessagingController.h +++ b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Controller/OSMessagingController.h @@ -29,6 +29,7 @@ #import "OSInAppMessageInternal.h" #import "OSInAppMessageViewController.h" #import "OSTriggerController.h" +#import #import NS_ASSUME_NONNULL_BEGIN @@ -39,7 +40,7 @@ NS_ASSUME_NONNULL_BEGIN @end -@interface OSMessagingController : NSObject +@interface OSMessagingController : NSObject @property (class, readonly) BOOL isInAppMessagingPaused; @@ -52,7 +53,7 @@ NS_ASSUME_NONNULL_BEGIN + (void)removeInstance; - (void)presentInAppMessage:(OSInAppMessageInternal *)message; - (void)updateInAppMessagesFromCache; -- (void)getInAppMessagesFromServer:(NSString * _Nullable)subscriptionId; +- (void)getInAppMessagesFromServer; - (void)messageViewImpressionRequest:(OSInAppMessageInternal *)message; - (void)messageViewPageImpressionRequest:(OSInAppMessageInternal *)message withPageId:(NSString *)pageId; diff --git a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Controller/OSMessagingController.m b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Controller/OSMessagingController.m index 47bc312ff..629a51b13 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Controller/OSMessagingController.m +++ b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Controller/OSMessagingController.m @@ -145,6 +145,21 @@ @implementation OSMessagingController @dynamic isInAppMessagingPaused; // Maximum time decided to save IAM with redisplay on cache - current value: six months in seconds static long OS_IAM_MAX_CACHE_TIME = 6 * 30 * 24 * 60 * 60; + +/** + If an attempt to get IAMs from the server returns an Unauthorized response, + the controller should re-attempt once the JWT token is updated. + */ +static BOOL shouldRetryGetInAppMessagesOnJwtUpdated = false; + +/** + If an attempt to get IAMs from the server is blocked by incomplete alias information, + the controller should re-attempt once the user state changes. + An example of when this can happen occurs when users are switching with Identity Verification turned off - + the SDK has a push subscription ID but no onesignal ID for the current user. + */ +static BOOL shouldRetryGetInAppMessagesOnUserChange = false; + static OSMessagingController *sharedInstance = nil; static dispatch_once_t once; + (OSMessagingController *)sharedInstance { @@ -167,6 +182,8 @@ + (void)removeInstance { + (void)start { OSMessagingController *shared = OSMessagingController.sharedInstance; [OneSignalUserManagerImpl.sharedInstance.pushSubscriptionImpl addObserver:shared]; + [OneSignalUserManagerImpl.sharedInstance addObserver:shared]; + [OneSignalUserManagerImpl.sharedInstance subscribeToJwtConfig:shared key:OS_MESSAGING_CONTROLLER]; } static BOOL _isInAppMessagingPaused = false; @@ -236,15 +253,48 @@ - (void)updateInAppMessagesFromCache { [self evaluateMessages]; } -- (void)getInAppMessagesFromServer:(NSString *)subscriptionId { - [OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:@"getInAppMessagesFromServer"]; - +/** + To get IAMs from the server, the following requirements are necessary: + - A subscription ID + - An appropriate alias (depending on Identity Verification enabled) for the subscription + - A valid JWT token for the user if Identity Verification is enabled + + This current implementation is not completely correct, as it will always use the current subscription ID and the current user. + The SDK would need to consider if the current user owns the subscription on the server. + */ +- (void)getInAppMessagesFromServer { + [OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:@"getInAppMessagesFromServer attempted"]; + + NSString *subscriptionId = OneSignalUserManagerImpl.sharedInstance.pushSubscriptionId; if (!subscriptionId) { + // When the subscription observer fires, it will drive a re-fetch + [OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:@"getInAppMessagesFromServer blocked by null subscriptionId"]; + [self updateInAppMessagesFromCache]; + return; + } + + OSAliasPair *alias = [OneSignalUserManagerImpl.sharedInstance getAliasForCurrentUser]; + if (!alias) { + // When the user observer fires, it will drive a re-fetch + [OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:@"getInAppMessagesFromServer blocked by null alias"]; + shouldRetryGetInAppMessagesOnUserChange = true; + [self updateInAppMessagesFromCache]; + return; + } + + NSDictionary *header = [OneSignalUserManagerImpl.sharedInstance getCurrentUserFullHeader]; + if (!header) { + // When the JWT updated listener fires, it will drive a re-fetch + [OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:@"getInAppMessagesFromServer blocked by missing header"]; + shouldRetryGetInAppMessagesOnJwtUpdated = true; [self updateInAppMessagesFromCache]; return; } - OSRequestGetInAppMessages *request = [OSRequestGetInAppMessages withSubscriptionId:subscriptionId]; + OSRequestGetInAppMessages *request = [OSRequestGetInAppMessages withSubscriptionId:subscriptionId + withAliasLabel:alias.label + withAliasId:alias.id + withHeader:header]; [OneSignalCoreImpl.sharedClient executeRequest:request onSuccess:^(NSDictionary *result) { dispatch_async(dispatch_get_main_queue(), ^{ [OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:@"getInAppMessagesFromServer success"]; @@ -270,6 +320,10 @@ - (void)getInAppMessagesFromServer:(NSString *)subscriptionId { }); } onFailure:^(NSError *error) { [OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"getInAppMessagesFromServer failure: %@", error.localizedDescription]]; + OSResponseStatusType responseType = [OSNetworkingUtils getResponseStatusType:error.code]; + if (responseType == OSResponseStatusUnauthorized) { + shouldRetryGetInAppMessagesOnJwtUpdated = true; + } [self updateInAppMessagesFromCache]; }]; } @@ -1087,7 +1141,29 @@ - (void)onPushSubscriptionDidChangeWithState:(OSPushSubscriptionChangedState * _ // Pull new IAMs when the subscription id changes to a new valid subscription id [OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:@"OSMessagingController onPushSubscriptionDidChange: changed to new valid subscription id"]; - [self getInAppMessagesFromServer:state.current.id]; + [self getInAppMessagesFromServer]; +} + +#pragma mark OSUserStateObserver Methods +- (void)onUserStateDidChangeWithState:(OSUserChangedState * _Nonnull)state { + NSLog(@"❌ OSMessagingController onUserStateDidChangeWithState: %@", [state jsonRepresentation]); + if (state.current.onesignalId && shouldRetryGetInAppMessagesOnUserChange) { + shouldRetryGetInAppMessagesOnUserChange = false; + [self getInAppMessagesFromServer]; + } +} + +#pragma mark OSUserJwtConfigListener Methods +- (void)onRequiresUserAuthChangedFrom:(enum OSRequiresUserAuth)from to:(enum OSRequiresUserAuth)to { + // This callback is unused, the controller will fetch when subscription ID changes +} + +- (void)onJwtUpdatedWithExternalId:(NSString *)externalId token:(NSString *)token { + NSLog(@"❌ OSMessagingController onJwtUpdatedWithExternalId: %@ token: %@", externalId, token); + if (![token isEqual: OS_JWT_TOKEN_INVALID] && shouldRetryGetInAppMessagesOnJwtUpdated) { + shouldRetryGetInAppMessagesOnJwtUpdated = false; + [self getInAppMessagesFromServer]; + } } - (void)dealloc { diff --git a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/OSInAppMessagingDefines.h b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/OSInAppMessagingDefines.h index 9bda4a68b..8ee3db3b9 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/OSInAppMessagingDefines.h +++ b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/OSInAppMessagingDefines.h @@ -28,6 +28,8 @@ #ifndef OSInAppMessagingDefines_h #define OSInAppMessagingDefines_h +// OSMessagingController name +#define OS_MESSAGING_CONTROLLER @"OSMessagingController" // IAM display position enums typedef NS_ENUM(NSUInteger, OSInAppMessageDisplayPosition) { diff --git a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/OneSignalInAppMessages.h b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/OneSignalInAppMessages.h index 11d0faa67..fda5f4def 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/OneSignalInAppMessages.h +++ b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/OneSignalInAppMessages.h @@ -32,7 +32,7 @@ + (Class_Nonnull)InAppMessages; + (void)start; -+ (void)getInAppMessagesFromServer:(NSString * _Nullable)subscriptionId; ++ (void)getInAppMessagesFromServer; + (void)onApplicationDidBecomeActive; + (void)migrate; @end diff --git a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/OneSignalInAppMessages.m b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/OneSignalInAppMessages.m index f9540813c..27fef03df 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/OneSignalInAppMessages.m +++ b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/OneSignalInAppMessages.m @@ -40,8 +40,8 @@ + (void)start { [OSMessagingController start]; } -+ (void)getInAppMessagesFromServer:(NSString * _Nullable)subscriptionId { - [OSMessagingController.sharedInstance getInAppMessagesFromServer:subscriptionId]; ++ (void)getInAppMessagesFromServer { + [OSMessagingController.sharedInstance getInAppMessagesFromServer]; } + (void)onApplicationDidBecomeActive { diff --git a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Requests/OSInAppMessagingRequests.h b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Requests/OSInAppMessagingRequests.h index c37b4bbf1..2b1f2a381 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Requests/OSInAppMessagingRequests.h +++ b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Requests/OSInAppMessagingRequests.h @@ -29,7 +29,7 @@ #import "OSInAppMessageClickResult.h" @interface OSRequestGetInAppMessages : OneSignalRequest -+ (instancetype _Nonnull)withSubscriptionId:(NSString * _Nonnull)subscriptionId; ++ (instancetype _Nonnull)withSubscriptionId:(NSString * _Nonnull)subscriptionId withAliasLabel:(NSString * _Nonnull)aliasLabel withAliasId:(NSString * _Nonnull)aliasId withHeader:(NSDictionary * _Nonnull)header; @end @interface OSRequestInAppMessageViewed : OneSignalRequest diff --git a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Requests/OSInAppMessagingRequests.m b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Requests/OSInAppMessagingRequests.m index 6f837a243..f0b2defe6 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Requests/OSInAppMessagingRequests.m +++ b/iOS_SDK/OneSignalSDK/OneSignalInAppMessages/Requests/OSInAppMessagingRequests.m @@ -28,11 +28,15 @@ of this software and associated documentation files (the "Software"), to deal #import "OSInAppMessagingRequests.h" @implementation OSRequestGetInAppMessages -+ (instancetype _Nonnull)withSubscriptionId:(NSString * _Nonnull)subscriptionId { ++ (instancetype _Nonnull)withSubscriptionId:(NSString * _Nonnull)subscriptionId + withAliasLabel:(NSString * _Nonnull)aliasLabel + withAliasId:(NSString * _Nonnull)aliasId + withHeader:(NSDictionary * _Nonnull)header { let request = [OSRequestGetInAppMessages new]; request.method = GET; NSString *appId = [OneSignalConfigManager getAppId]; - request.path = [NSString stringWithFormat:@"apps/%@/subscriptions/%@/iams", appId, subscriptionId]; + request.additionalHeaders = header; + request.path = [NSString stringWithFormat:@"apps/%@/users/by/%@/%@/subscriptions/%@/iams", appId, aliasLabel, aliasId, subscriptionId]; return request; } @end diff --git a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/Jwt/OSAliasPair.swift b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/Jwt/OSAliasPair.swift index b5cb9e051..c0f9b161b 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/Jwt/OSAliasPair.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/Jwt/OSAliasPair.swift @@ -29,8 +29,8 @@ An alias label and alias ID pair to represent a user. */ @objc public class OSAliasPair: NSObject { - public let label: String - public let id: String + @objc public let label: String + @objc public let id: String public init(_ label: String, _ id: String) { self.label = label diff --git a/iOS_SDK/OneSignalSDK/Source/OneSignal.m b/iOS_SDK/OneSignalSDK/Source/OneSignal.m index 5b49733bf..db2745a86 100755 --- a/iOS_SDK/OneSignalSDK/Source/OneSignal.m +++ b/iOS_SDK/OneSignalSDK/Source/OneSignal.m @@ -416,16 +416,8 @@ + (void)startNewSessionInternal { // TODO: Figure out if Create User also sets session_count automatically on backend [OneSignalUserManagerImpl.sharedInstance startNewSession]; - - // This is almost always going to be nil the first time. - // The OSMessagingController is an OSPushSubscriptionObserver so that we pull IAMs once we have the sub id - NSString *subscriptionId = OneSignalUserManagerImpl.sharedInstance.pushSubscriptionId; - if (subscriptionId) { - let oneSignalInAppMessages = NSClassFromString(ONE_SIGNAL_IN_APP_MESSAGES_CLASS_NAME); - if (oneSignalInAppMessages != nil && [oneSignalInAppMessages respondsToSelector:@selector(getInAppMessagesFromServer:)]) { - [oneSignalInAppMessages performSelector:@selector(getInAppMessagesFromServer:) withObject:subscriptionId]; - } - } + + [self fetchInAppMessages]; // The below means there are NO IAMs until on_session returns // because they can be ended, paused, or deleted from the server, or your segment has changed and you're no longer eligible @@ -440,6 +432,13 @@ + (void)startNewSessionInternal { // [OSMessagingController.sharedInstance updateInAppMessagesFromCache]; // go to controller } ++ (void)fetchInAppMessages { + let oneSignalInAppMessages = NSClassFromString(ONE_SIGNAL_IN_APP_MESSAGES_CLASS_NAME); + if (oneSignalInAppMessages != nil && [oneSignalInAppMessages respondsToSelector:@selector(getInAppMessagesFromServer)]) { + [oneSignalInAppMessages performSelector:@selector(getInAppMessagesFromServer)]; + } +} + + (void)startInAppMessages { let oneSignalInAppMessages = NSClassFromString(ONE_SIGNAL_IN_APP_MESSAGES_CLASS_NAME); if (oneSignalInAppMessages != nil && [oneSignalInAppMessages respondsToSelector:@selector(start)]) {