Skip to content

Commit 953865f

Browse files
authored
Fix/implement caching for iams pulled on session (#630)
* Added IAM caching from on_session request * Now IAMs from the on_session request response will be persisted and used in the case where an on_session is not called and we have no IAMs to reference * Using the codeable data method in the OneSignalUserDefaults class and required to setup decoder and encoder for the OSInAppMessage and OSTrigger class * Now we try to init IAMs with the cached IAMs just in case Added two unit tests in relation to this added functionality * First test checks that on_session we start using the IAMs and persist them * Second test makes sure that on_session is called, IAMs are cached, and then cold start without a new on_session * This should result in the app using the cached IAMs * Added a little deeper logic into the on_session IAM unit tests * First test now checks for persisted IAMs before and after the test runs * Second test now checks for persisted IAMs at the start, and the network check at the end verifies the IAMs came from cache instead of request response * Fixing unit test request count for 2 on_session IAM tests * Added IAM cache logic down same path as a on_session IAM receive * Comment fix, missing space * Split up on_session IAMs and cache inits * Return early in the case where we use the session for IAMs * Only need to call eval after getting cached IAMs * Fixed unit tests related to pausing IAMs
1 parent 2bce297 commit 953865f

15 files changed

+203
-27
lines changed

iOS_SDK/OneSignalDevApp/OneSignalDevApp/AppDelegate.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
3939

4040
NSLog(@"Bundle URL: %@", [[NSBundle mainBundle] bundleURL]);
4141

42-
[OneSignal setLogLevel:ONE_S_LL_VERBOSE visualLevel:ONE_S_LL_ERROR];
42+
[OneSignal setLogLevel:ONE_S_LL_VERBOSE visualLevel:ONE_S_LL_NONE];
4343

4444
OneSignal.inFocusDisplayType = OSNotificationDisplayTypeInAppAlert;
4545

iOS_SDK/OneSignalSDK/Source/OSInAppMessage.m

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,5 +191,20 @@ - (NSUInteger)hash {
191191
return [self.messageId hash];
192192
}
193193

194+
- (void)encodeWithCoder:(NSCoder *)encoder {
195+
[encoder encodeObject:_messageId forKey:@"messageId"];
196+
[encoder encodeObject:_variants forKey:@"variants"];
197+
[encoder encodeObject:_triggers forKey:@"triggers"];
198+
}
199+
200+
- (id)initWithCoder:(NSCoder *)decoder {
201+
if (self = [super init]) {
202+
_messageId = [decoder decodeObjectForKey:@"messageId"];
203+
_variants = [decoder decodeObjectForKey:@"variants"];
204+
_triggers = [decoder decodeObjectForKey:@"triggers"];
205+
}
206+
return self;
207+
}
208+
194209
@end
195210

iOS_SDK/OneSignalSDK/Source/OSInAppMessagingDefines.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,11 @@ typedef NS_ENUM(NSUInteger, OSTriggerOperatorType) {
6969
#define MIN_DISMISSAL_ANIMATION_DURATION 0.1f
7070
#define MAX_DISMISSAL_ANIMATION_DURATION 0.3f
7171

72-
// Keys for NSUserDefaults
72+
// In-App Messaging NSUserDefaults
7373
#define OS_IAM_SEEN_SET_KEY @"OS_IAM_SEEN_SET"
7474
#define OS_IAM_CLICKED_SET_KEY @"OS_IAM_CLICKED_SET"
7575
#define OS_IAM_IMPRESSIONED_SET_KEY @"OS_IAM_IMPRESSIONED_SET"
76+
#define OS_IAM_MESSAGES_ARRAY @"OS_IAM_MESSAGES_ARRAY"
7677
#define OS_IAM_REDISPLAY_DICTIONARY @"OS_IAM_REDISPLAY_DICTIONARY"
7778

7879
// Dynamic trigger kind types

iOS_SDK/OneSignalSDK/Source/OSMessagingController.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ NS_ASSUME_NONNULL_BEGIN
4545
+ (void)removeInstance;
4646
- (void)presentInAppMessage:(OSInAppMessage *)message;
4747
- (void)presentInAppPreviewMessage:(OSInAppMessage *)message;
48-
- (void)didUpdateMessagesForSession:(NSArray<OSInAppMessage *> *)newMessages;
48+
- (void)updateInAppMessagesFromCache;
49+
- (void)updateInAppMessagesFromOnSession:(NSArray<OSInAppMessage *> *)newMessages;
4950
- (void)messageViewImpressionRequest:(OSInAppMessage *)message;
5051

5152
- (BOOL)isInAppMessagingPaused;

iOS_SDK/OneSignalSDK/Source/OSMessagingController.m

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ - (instancetype)init {
120120
self.dateGenerator = ^ NSTimeInterval {
121121
return [[NSDate date] timeIntervalSince1970];
122122
};
123-
self.messages = [NSArray<OSInAppMessage *> new];
123+
self.messages = [OneSignalUserDefaults.initStandard getSavedCodeableDataForKey:OS_IAM_MESSAGES_ARRAY
124+
defaultValue:[NSArray<OSInAppMessage *> new]];
124125
self.triggerController = [OSTriggerController new];
125126
self.triggerController.delegate = self;
126127
self.messageDisplayQueue = [NSMutableArray new];
@@ -140,8 +141,18 @@ - (instancetype)init {
140141
return self;
141142
}
142143

143-
- (void)didUpdateMessagesForSession:(NSArray<OSInAppMessage *> *)newMessages {
144+
- (void)updateInAppMessagesFromCache {
145+
self.messages = [OneSignalUserDefaults.initStandard getSavedCodeableDataForKey:OS_IAM_MESSAGES_ARRAY defaultValue:[NSArray new]];
146+
[self evaluateMessages];
147+
}
148+
149+
- (void)updateInAppMessagesFromOnSession:(NSArray<OSInAppMessage *> *)newMessages {
144150
self.messages = newMessages;
151+
152+
// Cache if messages passed in are not null, this method is called from on_session for
153+
// new messages and cached when foregrounding app
154+
if (self.messages)
155+
[OneSignalUserDefaults.initStandard saveCodeableDataForKey:OS_IAM_MESSAGES_ARRAY withValue:self.messages];
145156

146157
[self resetRedisplayMessagesBySession];
147158
[self evaluateMessages];
@@ -583,7 +594,7 @@ + (OSMessagingController *)sharedInstance {return nil; }
583594
- (instancetype)init { self = [super init]; return self; }
584595
- (BOOL)isInAppMessagingPaused { return false; }
585596
- (void)setInAppMessagingPaused:(BOOL)pause {}
586-
- (void)didUpdateMessagesForSession:(NSArray<OSInAppMessage *> *)newMessages {}
597+
- (void)updateInAppMessagesFromOnSession:(NSArray<OSInAppMessage *> *)newMessages {}
587598
- (void)setInAppMessageClickHandler:(OSHandleInAppMessageActionClickBlock)actionClickBlock {}
588599
- (void)presentInAppMessage:(OSInAppMessage *)message {}
589600
- (void)presentInAppPreviewMessage:(OSInAppMessage *)message {}

iOS_SDK/OneSignalSDK/Source/OSTrigger.m

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,31 @@ - (NSDictionary *)jsonRepresentation {
9090
}
9191

9292
- (NSString *)description {
93-
return [NSString stringWithFormat: @"OSTrigger: triggerId=%@ property=%@ operatorType=%lu value=%@", _triggerId, _property, (unsigned long)_operatorType, _value];
93+
return [NSString stringWithFormat:@"OSTrigger: triggerId=%@ kind=%@ property=%@ operatorType=%lu value=%@",
94+
_triggerId,
95+
_kind,
96+
_property,
97+
(unsigned long)_operatorType,
98+
_value];
99+
}
100+
101+
- (void)encodeWithCoder:(NSCoder *)encoder {
102+
[encoder encodeObject:_triggerId forKey:@"triggerId"];
103+
[encoder encodeObject:_kind forKey:@"kind"];
104+
[encoder encodeObject:_property forKey:@"property"];
105+
[encoder encodeInt:(int)_operatorType forKey:@"operatorType"];
106+
[encoder encodeObject:_value forKey:@"value"];
107+
}
108+
109+
- (id)initWithCoder:(NSCoder *)decoder {
110+
if (self = [super init]) {
111+
_triggerId = [decoder decodeObjectForKey:@"triggerId"];
112+
_kind = [decoder decodeObjectForKey:@"kind"];
113+
_property = [decoder decodeObjectForKey:@"property"];
114+
_operatorType = (OSTriggerOperatorType)[decoder decodeIntForKey:@"operatorType"];
115+
_value = [decoder decodeObjectForKey:@"value"];
116+
}
117+
return self;
94118
}
95119

96120
@end

iOS_SDK/OneSignalSDK/Source/OneSignal.m

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1779,15 +1779,21 @@ + (void)registerUserInternal {
17791779

17801780
+ (void)receivedInAppMessageJson:(NSArray<NSDictionary *> *)messagesJson {
17811781
let messages = [NSMutableArray new];
1782-
1783-
for (NSDictionary *messageJson in messagesJson) {
1784-
let message = [OSInAppMessage instanceWithJson:messageJson];
1785-
1786-
if (message)
1787-
[messages addObject:message];
1782+
1783+
if (messagesJson) {
1784+
for (NSDictionary *messageJson in messagesJson) {
1785+
let message = [OSInAppMessage instanceWithJson:messageJson];
1786+
1787+
if (message)
1788+
[messages addObject:message];
1789+
}
1790+
1791+
[OSMessagingController.sharedInstance updateInAppMessagesFromOnSession:messages];
1792+
return;
17881793
}
17891794

1790-
[[OSMessagingController sharedInstance] didUpdateMessagesForSession:messages];
1795+
// Default is using cached IAMs in the messaging controller
1796+
[OSMessagingController.sharedInstance updateInAppMessagesFromCache];
17911797
}
17921798

17931799
+ (NSString*)getUsableDeviceToken {

iOS_SDK/OneSignalSDK/Source/OneSignalInternal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
+ (BOOL)shouldPromptToShowURL;
6464
+ (void)setIsOnSessionSuccessfulForCurrentState:(BOOL)value;
6565
+ (BOOL)shouldRegisterNow;
66+
+ (void)receivedInAppMessageJson:(NSArray<NSDictionary *> *)messagesJson;
6667

6768
+ (NSDate *)sessionLaunchTime;
6869

iOS_SDK/OneSignalSDK/Source/OneSignalTracker.m

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#import "OSFocusTimeProcessorFactory.h"
4040
#import "OSBaseFocusTimeProcessor.h"
4141
#import "OSFocusCallParams.h"
42+
#import "OSMessagingController.h"
4243

4344
@interface OneSignal ()
4445

@@ -110,9 +111,10 @@ + (void)applicationForegrounded {
110111
if ([OneSignal shouldRegisterNow])
111112
[OneSignal registerUser];
112113
else {
113-
// This checks if notifiation permissions changed when app was backgrounded
114+
// This checks if notification permissions changed when app was backgrounded
114115
[OneSignal sendNotificationTypesUpdate];
115116
[OneSignal.sessionManager attemptSessionUpgrade];
117+
[OneSignal receivedInAppMessageJson:nil];
116118
}
117119

118120
let wasBadgeSet = [OneSignal clearBadgeCount:false];

iOS_SDK/OneSignalSDK/UnitTests/InAppMessagingIntegrationTests.m

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,101 @@ - (void)setUp {
8282
NSTimerOverrider.shouldScheduleTimers = false;
8383
}
8484

85-
-(void)tearDown {
85+
- (void)tearDown {
8686
OneSignalOverrider.shouldOverrideSessionLaunchTime = false;
8787

8888
[OSMessagingController.sharedInstance resetState];
8989

9090
NSTimerOverrider.shouldScheduleTimers = true;
91+
}
92+
93+
/**
94+
Make sure on_session IAMs are pulled for the specific app_id
95+
For this test we have mocked a single IAM in the on_session request
96+
After first on_session IAMs are setup to be used by controller
97+
*/
98+
- (void)testIAMsAvailable_afterOnSession {
99+
// 1. Make sure 0 IAMs are persisted
100+
NSArray *cachedMessages = [OneSignalUserDefaults.initStandard getSavedCodeableDataForKey:OS_IAM_MESSAGES_ARRAY defaultValue:nil];
101+
XCTAssertNil(cachedMessages);
91102

92-
// Set to false so that we don't interfere with other tests
93-
[OneSignal pauseInAppMessages:false];
103+
// 2. Open app
104+
[UnitTestCommonMethods initOneSignalAndThreadWait];
105+
[UnitTestCommonMethods runBackgroundThreads];
106+
107+
// 3. Kill the app and wait 31 seconds
108+
[UnitTestCommonMethods backgroundApp];
109+
[UnitTestCommonMethods clearStateForAppRestart:self];
110+
[NSDateOverrider advanceSystemTimeBy:31];
111+
[UnitTestCommonMethods runBackgroundThreads];
112+
113+
// 4. Open app
114+
[UnitTestCommonMethods initOneSignalAndThreadWait];
115+
[UnitTestCommonMethods runBackgroundThreads];
116+
117+
// 5. Ensure the last network call is an on_session
118+
// Total calls - 2 ios params + player create + on_session = 4 requests
119+
XCTAssertEqualObjects(OneSignalClientOverrider.lastUrl, serverUrlWithPath(@"players/1234/on_session"));
120+
XCTAssertEqual(OneSignalClientOverrider.networkRequestCount, 4);
121+
122+
// 6. Make sure IAMs are available, but not in queue
123+
XCTAssertTrue([OSMessagingController.sharedInstance getInAppMessages].count > 0);
124+
125+
// 7. Make sure 1 IAM is persisted
126+
cachedMessages = [OneSignalUserDefaults.initStandard getSavedCodeableDataForKey:OS_IAM_MESSAGES_ARRAY defaultValue:nil];
127+
XCTAssertEqual(1, cachedMessages.count);
128+
}
129+
130+
/**
131+
Make sure on_session IAMs are pulled for the specific app_id
132+
For this test we have mocked a single IAM in the on_session request response
133+
After first on_session IAMs will be cached, now force quit app and return in less than 30 seconds to make sure cached IAMs are used instead
134+
*/
135+
- (void)testIAMsCacheAvailable_afterOnSession_andAppRestart {
136+
// 1. Make sure 0 IAMs are persisted
137+
NSArray *cachedMessages = [OneSignalUserDefaults.initStandard getSavedCodeableDataForKey:OS_IAM_MESSAGES_ARRAY defaultValue:nil];
138+
XCTAssertNil(cachedMessages);
139+
140+
// 2. Open app
141+
[UnitTestCommonMethods initOneSignalAndThreadWait];
142+
143+
// 3. Kill the app and wait 31 seconds
144+
[UnitTestCommonMethods backgroundApp];
145+
[NSDateOverrider advanceSystemTimeBy:31];
146+
[UnitTestCommonMethods runBackgroundThreads];
147+
148+
// 4. Open app
149+
[UnitTestCommonMethods initOneSignalAndThreadWait];
150+
151+
// 5. Ensure the last network call is an on_session
152+
// Total calls - ios params + 2 on_session = 3 requests
153+
XCTAssertEqualObjects(OneSignalClientOverrider.lastUrl, serverUrlWithPath(@"players/1234/on_session"));
154+
XCTAssertEqual(OneSignalClientOverrider.networkRequestCount, 3);
155+
156+
// 6. Make sure IAMs are available
157+
XCTAssertTrue([OSMessagingController.sharedInstance getInAppMessages].count > 0);
158+
159+
// 7. Don't make an on_session call if only out of the app for 10 secounds
160+
[UnitTestCommonMethods clearStateForAppRestart:self];
161+
[UnitTestCommonMethods backgroundApp];
162+
[NSDateOverrider advanceSystemTimeBy:10];
163+
[UnitTestCommonMethods runBackgroundThreads];
164+
165+
// 8. Make sure no more IAMs exist
166+
XCTAssertTrue([OSMessagingController.sharedInstance getInAppMessages].count == 0);
167+
168+
// 9. Open app
169+
[UnitTestCommonMethods initOneSignalAndThreadWait];
170+
[UnitTestCommonMethods runBackgroundThreads];
171+
172+
// 10. Make sure 1 IAM is persisted
173+
cachedMessages = [OneSignalUserDefaults.initStandard getSavedCodeableDataForKey:OS_IAM_MESSAGES_ARRAY defaultValue:nil];
174+
XCTAssertEqual(1, cachedMessages.count);
175+
176+
// 11. Make sure IAMs are available
177+
XCTAssertTrue([OSMessagingController.sharedInstance getInAppMessages].count > 0);
178+
// Total calls - ios params + 2 on_session + 1 ios params + 1 on_session = 5 requests
179+
XCTAssertEqual(OneSignalClientOverrider.networkRequestCount, 5);
94180
}
95181

96182
/**
@@ -187,6 +273,8 @@ - (void)testDelaysSettingUpTimers {
187273
}
188274

189275
- (void)testIAMWithRedisplay {
276+
[OneSignal pauseInAppMessages:false];
277+
190278
let limit = 5;
191279
let delay = 60;
192280
let firstTrigger = [OSTrigger customTriggerWithProperty:@"prop1" withOperator:OSTriggerOperatorTypeExists withValue:nil];
@@ -339,6 +427,8 @@ - (void)testRemoveTriggers {
339427
}
340428

341429
- (void)testIAMWithNoTriggersDisplayOnePerSession_Redisplay {
430+
[OneSignal pauseInAppMessages:false];
431+
342432
let limit = 5;
343433
let delay = 60;
344434

@@ -394,6 +484,8 @@ - (void)testIAMWithNoTriggersDisplayOnePerSession_Redisplay {
394484
}
395485

396486
- (void)testIAMShowAfterRemoveTrigger_Redisplay {
487+
[OneSignal pauseInAppMessages:false];
488+
397489
[OSMessagingController.sharedInstance setTriggerWithName:@"prop1" withValue:@2];
398490
let limit = 5;
399491
let delay = 60;
@@ -556,6 +648,8 @@ - (void)testPastButValidExactTimeTrigger {
556648

557649
// when an in-app message is displayed to the user, the SDK should launch an API request
558650
- (void)testIAMViewedLaunchesViewedAPIRequest {
651+
[OneSignal pauseInAppMessages:false];
652+
559653
let message = [OSInAppMessageTestHelper testMessageJsonWithTriggerPropertyName:OS_DYNAMIC_TRIGGER_KIND_SESSION_TIME withId:@"test_id1" withOperator:OSTriggerOperatorTypeLessThan withValue:@10.0];
560654

561655
let registrationResponse = [OSInAppMessageTestHelper testRegistrationJsonWithMessages:@[message]];

0 commit comments

Comments
 (0)