Skip to content

Commit

Permalink
Merge pull request #1476 from OneSignal/identity_verification_get_iams
Browse files Browse the repository at this point in the history
[JWT] Get In-App Messages from Server
  • Loading branch information
nan-li authored Oct 4, 2024
2 parents 8694a4d + 0bc08eb commit 1fbddcf
Show file tree
Hide file tree
Showing 54 changed files with 2,927 additions and 605 deletions.
2 changes: 1 addition & 1 deletion iOS_SDK/OneSignalDevApp/OneSignalDevApp/AppDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
#import <UIKit/UIKit.h>
#import <OneSignalFramework/OneSignalFramework.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate, OSNotificationPermissionObserver, OSInAppMessageLifecycleListener, OSPushSubscriptionObserver, OSNotificationLifecycleListener, OSInAppMessageClickListener, OSNotificationClickListener, OSUserStateObserver>
@interface AppDelegate : UIResponder <UIApplicationDelegate, OSNotificationPermissionObserver, OSInAppMessageLifecycleListener, OSPushSubscriptionObserver, OSNotificationLifecycleListener, OSInAppMessageClickListener, OSNotificationClickListener, OSUserStateObserver, OSUserJwtInvalidatedListener>

@property (strong, nonatomic) UIWindow *window;

Expand Down
13 changes: 11 additions & 2 deletions iOS_SDK/OneSignalDevApp/OneSignalDevApp/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ @implementation AppDelegate

OneSignalNotificationCenterDelegate *_notificationDelegate;

// ECM Should we ship these typedefs in OneSignalFramework.h to make them available to Objective C customers?
typedef void (^JwtCompletionBlock)(NSString*);
typedef void (^JwtExpiredBlock)(NSString *, JwtCompletionBlock);

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

// [FIRApp configure];
Expand All @@ -72,6 +76,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
[OneSignal.User addObserver:self];
[OneSignal.Notifications addPermissionObserver:self];
[OneSignal.InAppMessages addClickListener:self];
[OneSignal addUserJwtInvalidatedListener:self];

NSLog(@"UNUserNotificationCenter.delegate: %@", UNUserNotificationCenter.currentNotificationCenter.delegate);

Expand All @@ -86,8 +91,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
return YES;
}

#define ONESIGNAL_APP_ID_DEFAULT @"STAGING_APP_HERE"
#define ONESIGNAL_APP_ID_KEY_FOR_TESTING @"YOUR_APP_ID_HERE"
#define ONESIGNAL_APP_ID_DEFAULT @"77e32082-ea27-42e3-a898-c72e141824ef"
#define ONESIGNAL_APP_ID_KEY_FOR_TESTING @"77e32082-ea27-42e3-a898-c72e141824ef"

+ (NSString*)getOneSignalAppId {
NSString* userDefinedAppId = [[NSUserDefaults standardUserDefaults] objectForKey:ONESIGNAL_APP_ID_KEY_FOR_TESTING];
Expand Down Expand Up @@ -121,6 +126,10 @@ - (void)onUserStateDidChangeWithState:(OSUserChangedState * _Nonnull)state {
NSLog(@"Dev App onUserStateDidChangeWithState: %@", [state jsonRepresentation]);
}

- (void)onUserJwtInvalidatedWithEvent:(OSUserJwtInvalidatedEvent * _Nonnull)event {
NSLog(@"Dev App onUserJwtInvalidatedWithEvent: %@", [event jsonRepresentation]);
}

#pragma mark OSInAppMessageDelegate

- (void)onClickInAppMessage:(OSInAppMessageClickEvent * _Nonnull)event {
Expand Down
11 changes: 10 additions & 1 deletion iOS_SDK/OneSignalDevApp/OneSignalDevApp/SwiftTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,19 @@
import Foundation
import OneSignalFramework

class SwiftTest: NSObject {
class SwiftTest: NSObject, OSUserJwtInvalidatedListener {
func onUserJwtInvalidated(event: OSUserJwtInvalidatedEvent) {
print("event: \(event.jsonRepresentation())")
print("externalId: \(event.externalId)")
}

func testSwiftUserModel() {
let token1 = OneSignal.User.pushSubscription.token
let token = OneSignal.User.pushSubscription.token
OneSignal.Debug._dump()
OneSignal.login(externalId: "euid", token: "token")
OneSignal.updateUserJwt(externalId: "euid", token: "token")
OneSignal.addUserJwtInvalidatedListener(self)
OneSignal.removeUserJwtInvalidatedListener(self)
}
}
4 changes: 2 additions & 2 deletions iOS_SDK/OneSignalDevApp/OneSignalDevApp/ViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -197,14 +197,14 @@ - (IBAction)inAppMessagingSegmentedControlValueChanged:(UISegmentedControl *)sen
- (IBAction)loginExternalUserId:(UIButton *)sender {
NSString* externalUserId = self.externalUserIdTextField.text;
NSString* token = self.tokenTextField.text;
NSLog(@"Dev App: Logging in to external user ID %@ and token %@", externalUserId, token);
NSLog(@"Dev App: Logging in to external user ID %@ and token %@", externalUserId, token);
[OneSignal login:externalUserId withToken:token];
}

- (IBAction)updateJwt:(id)sender {
NSString* externalUserId = self.externalUserIdTextField.text;
NSString* token = self.tokenTextField.text;
NSLog(@"Dev App: updating JWT for external user ID %@ and token %@", externalUserId, token);
NSLog(@"Dev App: updating JWT for external user ID %@ and token %@", externalUserId, token);
[OneSignal updateUserJwt:externalUserId withToken:token];
}

Expand Down
227 changes: 221 additions & 6 deletions iOS_SDK/OneSignalDevApp/OneSignalExample.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

72 changes: 64 additions & 8 deletions iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,10 @@ - (double)calculateReattemptDelay:(int)reattemptCount {
}

- (void)prettyPrintDebugStatementWithRequest:(OneSignalRequest *)request {
if (![NSJSONSerialization isValidJSONObject:request.parameters])
if (![NSJSONSerialization isValidJSONObject:request.parameters]) {
[OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"HTTP Request (%@) with URL: %@, with headers: %@", NSStringFromClass([request class]), request.urlRequest.URL.absoluteString, request.additionalHeaders]];
return;
}

NSError *error;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
// Networking
#define OS_API_VERSION @"1"
#define OS_API_ACCEPT_HEADER @"application/vnd.onesignal.v" OS_API_VERSION @"+json"
#define OS_API_SERVER_URL @"https://api.staging.onesignal.com/"
#define OS_IAM_WEBVIEW_BASE_URL @"https://staging.onesignal.com/"
#define OS_API_SERVER_URL @"https://api.onesignal.com/"
#define OS_IAM_WEBVIEW_BASE_URL @"https://onesignal.com/"

// OneSignalUserDefault keys
// String values start with "OSUD_" to maintain a level of uniqueness from other libs and app code
Expand Down Expand Up @@ -79,7 +79,6 @@
#define OSUD_REQUIRES_USER_PRIVACY_CONSENT @"OSUD_REQUIRES_USER_PRIVACY_CONSENT"

/* Identity Verification */
// TODO: JWT 🔐 Figure out the key below and may need to relate to existing key IOS_REQUIRES_USER_ID_AUTHENTICATION
#define OSUD_USE_IDENTITY_VERIFICATION @"OSUD_USE_IDENTITY_VERIFICATION"
#define OS_JWT_BEARER_TOKEN @"OS_JWT_BEARER_TOKEN"
#define OS_JWT_TOKEN_INVALID @"OS_JWT_TOKEN_INVALID"
Expand Down Expand Up @@ -135,7 +134,7 @@
#define IOS_USES_PROVISIONAL_AUTHORIZATION @"uses_provisional_auth"
#define IOS_REQUIRES_EMAIL_AUTHENTICATION @"require_email_auth"
#define IOS_REQUIRES_SMS_AUTHENTICATION @"require_sms_auth"
#define IOS_REQUIRES_USER_ID_AUTHENTICATION @"require_user_id_auth" // TODO: JWT 🔐 Figure out the key, also think about needing to migrate this value
#define IOS_JWT_REQUIRED @"jwt_required" // Returned by remote params
#define IOS_RECEIVE_RECEIPTS_ENABLE @"receive_receipts_enable"
#define IOS_OUTCOMES_V2_SERVICE_ENABLE @"v2_enabled"
#define IOS_LOCATION_SHARED @"location_shared"
Expand Down Expand Up @@ -347,23 +346,28 @@ typedef enum {GET, POST, HEAD, PUT, DELETE, OPTIONS, CONNECT, TRACE, PATCH} HTTP
#define OS_USER_EXECUTOR @"OS_USER_EXECUTOR"
#define OS_USER_EXECUTOR_USER_REQUEST_QUEUE_KEY @"OS_USER_EXECUTOR_USER_REQUEST_QUEUE_KEY"
#define OS_USER_EXECUTOR_TRANSFER_SUBSCRIPTION_REQUEST_QUEUE_KEY @"OS_USER_EXECUTOR_TRANSFER_SUBSCRIPTION_REQUEST_QUEUE_KEY"
#define OS_USER_EXECUTOR_PENDING_QUEUE_KEY @"OS_USER_EXECUTOR_PENDING_QUEUE_KEY"

// Identity Executor
#define OS_IDENTITY_EXECUTOR @"OS_IDENTITY_EXECUTOR"
#define OS_IDENTITY_EXECUTOR_DELTA_QUEUE_KEY @"OS_IDENTITY_EXECUTOR_DELTA_QUEUE_KEY"
#define OS_IDENTITY_EXECUTOR_ADD_REQUEST_QUEUE_KEY @"OS_IDENTITY_EXECUTOR_ADD_REQUEST_QUEUE_KEY"
#define OS_IDENTITY_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY @"OS_IDENTITY_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY"
#define OS_IDENTITY_EXECUTOR_PENDING_QUEUE_KEY @"OS_IDENTITY_EXECUTOR_PENDING_QUEUE_KEY"

// Property Executor
#define OS_PROPERTIES_EXECUTOR @"OS_PROPERTIES_EXECUTOR"
#define OS_PROPERTIES_EXECUTOR_DELTA_QUEUE_KEY @"OS_PROPERTIES_EXECUTOR_DELTA_QUEUE_KEY"
#define OS_PROPERTIES_EXECUTOR_UPDATE_REQUEST_QUEUE_KEY @"OS_PROPERTIES_EXECUTOR_UPDATE_REQUEST_QUEUE_KEY"
#define OS_PROPERTIES_EXECUTOR_PENDING_QUEUE_KEY @"OS_PROPERTIES_EXECUTOR_PENDING_QUEUE_KEY"

// Subscription Executor
#define OS_SUBSCRIPTION_EXECUTOR @"OS_SUBSCRIPTION_EXECUTOR"
#define OS_SUBSCRIPTION_EXECUTOR_DELTA_QUEUE_KEY @"OS_SUBSCRIPTION_EXECUTOR_DELTA_QUEUE_KEY"
#define OS_SUBSCRIPTION_EXECUTOR_ADD_REQUEST_QUEUE_KEY @"OS_SUBSCRIPTION_EXECUTOR_ADD_REQUEST_QUEUE_KEY"
#define OS_SUBSCRIPTION_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY @"OS_SUBSCRIPTION_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY"
#define OS_SUBSCRIPTION_EXECUTOR_UPDATE_REQUEST_QUEUE_KEY @"OS_SUBSCRIPTION_EXECUTOR_UPDATE_REQUEST_QUEUE_KEY"
#define OS_SUBSCRIPTION_EXECUTOR_PENDING_QUEUE_KEY @"OS_SUBSCRIPTION_EXECUTOR_PENDING_QUEUE_KEY"

// Live Activies Executor
#define OS_LIVE_ACTIVITIES_EXECUTOR_UPDATE_TOKENS_KEY @"OS_LIVE_ACTIVITIES_EXECUTOR_UPDATE_TOKENS_KEY"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ + (OSUIApplicationReleaseMode) releaseMode {
NSDictionary *entitlements = nil;
NSDictionary *provision = [self getProvision];
if (provision) {
// [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:[NSString stringWithFormat:@"provision: %@", provision]];
[OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:[NSString stringWithFormat:@"provision: %@", provision]];
entitlements = [provision objectForKey:@"Entitlements"];
}
else
Expand Down
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 @@ -233,18 +250,54 @@ - (void)initializeTriggerController {

- (void)updateInAppMessagesFromCache {
self.messages = [OneSignalUserDefaults.initStandard getSavedCodeableDataForKey:OS_IAM_MESSAGES_ARRAY defaultValue:[NSArray new]];
[self evaluateMessages];
// ECM THIS NEEDS TO RUN ON THE MAIN THREAD
dispatch_async(dispatch_get_main_queue(), ^{
[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,10 +323,19 @@ - (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 handleUnauthroizedError:error externalId:alias.id];
}
[self updateInAppMessagesFromCache];
}];
}

- (void)handleUnauthroizedError:(NSError*)error externalId:(NSString *)externalId {
[OneSignalUserManagerImpl.sharedInstance invalidateJwtForExternalIdWithExternalId:externalId error:error];
}

- (void)updateInAppMessagesFromServer:(NSArray<OSInAppMessageInternal *> *)newMessages {
[OneSignalLog onesignalLog:ONE_S_LL_VERBOSE message:@"updateInAppMessagesFromServer"];
self.messages = newMessages;
Expand Down Expand Up @@ -1087,7 +1149,27 @@ - (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 {
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 {
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
Loading

0 comments on commit 1fbddcf

Please sign in to comment.