Skip to content

Commit

Permalink
Modify getting IAM to use alias and jwt
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
nan-li committed Aug 30, 2024
1 parent 90a08f7 commit c9b0a94
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#import "OSInAppMessageInternal.h"
#import "OSInAppMessageViewController.h"
#import "OSTriggerController.h"
#import <OneSignalOSCore/OneSignalOSCore.h>
#import <OneSignalUser/OneSignalUser.h>

NS_ASSUME_NONNULL_BEGIN
Expand All @@ -39,7 +40,7 @@ NS_ASSUME_NONNULL_BEGIN

@end

@interface OSMessagingController : NSObject <OSInAppMessageViewControllerDelegate, OSTriggerControllerDelegate, OSMessagingControllerDelegate, OSPushSubscriptionObserver>
@interface OSMessagingController : NSObject <OSInAppMessageViewControllerDelegate, OSTriggerControllerDelegate, OSMessagingControllerDelegate, OSPushSubscriptionObserver, OSUserStateObserver, OSUserJwtConfigListener>

@property (class, readonly) BOOL isInAppMessagingPaused;

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand Down Expand Up @@ -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<NSString *, NSString*> *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"];
Expand All @@ -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];
}];
}
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

+ (Class<OSInAppMessages>_Nonnull)InAppMessages;
+ (void)start;
+ (void)getInAppMessagesFromServer:(NSString * _Nullable)subscriptionId;
+ (void)getInAppMessagesFromServer;
+ (void)onApplicationDidBecomeActive;
+ (void)migrate;
@end
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ + (void)start {
[OSMessagingController start];
}

+ (void)getInAppMessagesFromServer:(NSString * _Nullable)subscriptionId {
[OSMessagingController.sharedInstance getInAppMessagesFromServer:subscriptionId];
+ (void)getInAppMessagesFromServer {
[OSMessagingController.sharedInstance getInAppMessagesFromServer];
}

+ (void)onApplicationDidBecomeActive {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<NSString *, NSString *> * _Nonnull)header;
@end

@interface OSRequestInAppMessageViewed : OneSignalRequest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<NSString *, NSString *> * _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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 9 additions & 10 deletions iOS_SDK/OneSignalSDK/Source/OneSignal.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)]) {
Expand Down

0 comments on commit c9b0a94

Please sign in to comment.