diff --git a/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj b/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj index d516eba25..3ed961b75 100644 --- a/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj +++ b/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj @@ -428,6 +428,10 @@ DE20425E24E21C2C00350E4F /* UIApplication+OneSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = DE20425D24E21C2C00350E4F /* UIApplication+OneSignal.m */; }; DE20425F24E21C2C00350E4F /* UIApplication+OneSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = DE20425D24E21C2C00350E4F /* UIApplication+OneSignal.m */; }; DE20426024E21C2C00350E4F /* UIApplication+OneSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = DE20425D24E21C2C00350E4F /* UIApplication+OneSignal.m */; }; + DE367CC724EEF2BE00165207 /* OSInAppMessagePage.m in Sources */ = {isa = PBXBuildFile; fileRef = DE367CC624EEF2BE00165207 /* OSInAppMessagePage.m */; }; + DE367CC824EEF2BE00165207 /* OSInAppMessagePage.m in Sources */ = {isa = PBXBuildFile; fileRef = DE367CC624EEF2BE00165207 /* OSInAppMessagePage.m */; }; + DE367CC924EEF2BE00165207 /* OSInAppMessagePage.m in Sources */ = {isa = PBXBuildFile; fileRef = DE367CC624EEF2BE00165207 /* OSInAppMessagePage.m */; }; + DE367CCA24EEF2C800165207 /* OSInAppMessagePage.h in Headers */ = {isa = PBXBuildFile; fileRef = DE367CC524EEF2A800165207 /* OSInAppMessagePage.h */; }; DE5EFECA24D8DBF70032632D /* OSInAppMessageViewControllerOverrider.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5EFEC924D8DBF70032632D /* OSInAppMessageViewControllerOverrider.m */; }; DE98772B2591656200DE07D5 /* NSDateFormatter+OneSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = DE98772A2591655800DE07D5 /* NSDateFormatter+OneSignal.m */; }; DE9877332591656200DE07D5 /* NSDateFormatter+OneSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = DE98772A2591655800DE07D5 /* NSDateFormatter+OneSignal.m */; }; @@ -730,6 +734,8 @@ DE16C14624D3727200670EFA /* OneSignalLifecycleObserver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OneSignalLifecycleObserver.h; sourceTree = ""; }; DE20425C24E21C1500350E4F /* UIApplication+OneSignal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIApplication+OneSignal.h"; sourceTree = ""; }; DE20425D24E21C2C00350E4F /* UIApplication+OneSignal.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIApplication+OneSignal.m"; sourceTree = ""; }; + DE367CC524EEF2A800165207 /* OSInAppMessagePage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OSInAppMessagePage.h; sourceTree = ""; }; + DE367CC624EEF2BE00165207 /* OSInAppMessagePage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OSInAppMessagePage.m; sourceTree = ""; }; DE5EFEC924D8DBF70032632D /* OSInAppMessageViewControllerOverrider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OSInAppMessageViewControllerOverrider.m; sourceTree = ""; }; DE5EFECB24D8DC0E0032632D /* OSInAppMessageViewControllerOverrider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OSInAppMessageViewControllerOverrider.h; sourceTree = ""; }; DE9877292591654600DE07D5 /* NSDateFormatter+OneSignal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSDateFormatter+OneSignal.h"; sourceTree = ""; }; @@ -1286,6 +1292,8 @@ 7A880F302404AE7B0081F5E8 /* OSInAppMessagePushPrompt.m */, 7AD172362416D52D00A78B19 /* OSInAppMessageLocationPrompt.h */, 7AD172372416D53B00A78B19 /* OSInAppMessageLocationPrompt.m */, + DE367CC524EEF2A800165207 /* OSInAppMessagePage.h */, + DE367CC624EEF2BE00165207 /* OSInAppMessagePage.m */, ); name = Model; sourceTree = ""; @@ -1395,6 +1403,7 @@ 7AF98684244A32D900C36EAE /* OSOutcomeEventsV2Repository.h in Headers */, CA8E18FC2193A1A5009DA223 /* NSTimerOverrider.h in Headers */, 91B6EA451E86555200B5CF01 /* OSObservable.h in Headers */, + DE367CCA24EEF2C800165207 /* OSInAppMessagePage.h in Headers */, CACBAA9C218A6243000ACAA5 /* OSInAppMessageView.h in Headers */, 912412351E73342200E41FD7 /* OneSignalTrackIAP.h in Headers */, 7AF9868A244A556300C36EAE /* OSOutcomeEventsFactory.h in Headers */, @@ -1699,6 +1708,7 @@ CAB269E021B2038B00F8A43C /* OSInAppMessageBridgeEvent.m in Sources */, 91B6EA411E85D38F00B5CF01 /* OSObservable.m in Sources */, 7AF986432444C47400C36EAE /* OSNotificationTracker.m in Sources */, + DE367CC724EEF2BE00165207 /* OSInAppMessagePage.m in Sources */, 4529DF0C1FA932AC00CEAB1D /* OneSignalTrackFirebaseAnalytics.m in Sources */, 7A676BE524981CEC003957CC /* OSDeviceState.m in Sources */, 7AFE856B2368DDB80091D6A5 /* OSFocusCallParams.m in Sources */, @@ -1791,6 +1801,7 @@ 7AF986442444C47400C36EAE /* OSNotificationTracker.m in Sources */, 912412271E73342200E41FD7 /* OneSignalMobileProvision.m in Sources */, 912412331E73342200E41FD7 /* OneSignalTracker.m in Sources */, + DE367CC824EEF2BE00165207 /* OSInAppMessagePage.m in Sources */, 91B6EA421E85D38F00B5CF01 /* OSObservable.m in Sources */, 7AC8D3A924993A0F0023EDE8 /* OSDeviceState.m in Sources */, CA810FD2202BA97600A60FED /* OSEmailSubscription.m in Sources */, @@ -1938,6 +1949,7 @@ 7AECE59823674AB700537907 /* OSUnattributedFocusTimeProcessor.m in Sources */, 7A5A818224897693002E07C8 /* MigrationTests.m in Sources */, 7AECE5A023675F6300537907 /* OSFocusTimeProcessorFactory.m in Sources */, + DE367CC924EEF2BE00165207 /* OSInAppMessagePage.m in Sources */, 9129C6C01E89E7AB009CB6A0 /* OSSubscription.m in Sources */, 7AC8D3A824993A0E0023EDE8 /* OSDeviceState.m in Sources */, CA63AFC52022670A00E340FB /* ReattemptRequest.m in Sources */, diff --git a/iOS_SDK/OneSignalSDK/Source/OSInAppMessage.m b/iOS_SDK/OneSignalSDK/Source/OSInAppMessage.m index e58586228..40bd131d9 100644 --- a/iOS_SDK/OneSignalSDK/Source/OSInAppMessage.m +++ b/iOS_SDK/OneSignalSDK/Source/OSInAppMessage.m @@ -32,6 +32,7 @@ @interface OSInAppMessage () @property (strong, nonatomic, nonnull) NSMutableSet *clickedClickIds; +@property (strong, nonatomic, nonnull) NSMutableSet *viewedPageIds; @end @@ -40,6 +41,7 @@ @implementation OSInAppMessage - (instancetype)init { if (self = [super init]) { self.clickedClickIds = [[NSMutableSet alloc] init]; + self.viewedPageIds = [NSMutableSet new]; self.isTriggerChanged = false; } @@ -61,7 +63,7 @@ - (BOOL)isClickAvailable:(NSString *)clickId { } - (void)clearClickIds { - _clickedClickIds = [[NSMutableSet alloc] init]; + _clickedClickIds = [NSMutableSet new]; } - (void)addClickId:(NSString *)clickId { diff --git a/iOS_SDK/OneSignalSDK/Source/OSInAppMessageAction.m b/iOS_SDK/OneSignalSDK/Source/OSInAppMessageAction.m index 6a7484e74..5b06f3e81 100644 --- a/iOS_SDK/OneSignalSDK/Source/OSInAppMessageAction.m +++ b/iOS_SDK/OneSignalSDK/Source/OSInAppMessageAction.m @@ -63,6 +63,9 @@ + (instancetype _Nullable)instanceWithJson:(NSDictionary *)json { if ([json[@"name"] isKindOfClass:[NSString class]]) action.clickName = json[@"name"]; + if ([json[@"pageId"] isKindOfClass:[NSString class]]) + action.pageId = json[@"pageId"]; + if ([json[@"url_target"] isKindOfClass:[NSString class]] && OS_IS_VALID_URL_ACTION(json[@"url_target"])) action.urlActionType = OS_URL_ACTION_TYPE_FROM_STRING(json[@"url_target"]); else @@ -72,6 +75,8 @@ + (instancetype _Nullable)instanceWithJson:(NSDictionary *)json { action.closesMessage = [json[@"close"] boolValue]; else action.closesMessage = true; // Default behavior + + [OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"OSInAppMessageAction %@", json]]; NSMutableArray *outcomes = [NSMutableArray new]; //TODO: when backend is ready check that key matches diff --git a/iOS_SDK/OneSignalSDK/Source/OSInAppMessageBridgeEvent.h b/iOS_SDK/OneSignalSDK/Source/OSInAppMessageBridgeEvent.h index b05e4008a..cb305cb54 100644 --- a/iOS_SDK/OneSignalSDK/Source/OSInAppMessageBridgeEvent.h +++ b/iOS_SDK/OneSignalSDK/Source/OSInAppMessageBridgeEvent.h @@ -28,6 +28,7 @@ #import #import "OSJSONHandling.h" #import "OSInAppMessageAction.h" +#import "OSInAppMessagePage.h" #import "OSInAppMessagingDefines.h" NS_ASSUME_NONNULL_BEGIN @@ -35,7 +36,8 @@ NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSUInteger, OSInAppMessageBridgeEventType) { OSInAppMessageBridgeEventTypePageRenderingComplete, OSInAppMessageBridgeEventTypeActionTaken, - OSInAppMessageBridgeEventTypePageResize + OSInAppMessageBridgeEventTypePageResize, + OSInAppMessageBridgeEventTypePageChange, }; @interface OSInAppMessageBridgeEventRenderingComplete : NSObject @@ -48,12 +50,16 @@ typedef NS_ENUM(NSUInteger, OSInAppMessageBridgeEventType) { @property (nonatomic) NSNumber *height; @end +@interface OSInAppMessageBridgeEventPageChange : NSObject +@property (nonatomic) OSInAppMessagePage *page; +@end + @interface OSInAppMessageBridgeEvent : NSObject @property (nonatomic) OSInAppMessageBridgeEventType type; @property (nonatomic) OSInAppMessageBridgeEventRenderingComplete *renderingComplete; @property (nonatomic) OSInAppMessageBridgeEventResize *resize; +@property (nonatomic, nullable) OSInAppMessageBridgeEventPageChange *pageChange; @property (strong, nonatomic, nullable) OSInAppMessageAction *userAction; @end - NS_ASSUME_NONNULL_END diff --git a/iOS_SDK/OneSignalSDK/Source/OSInAppMessageBridgeEvent.m b/iOS_SDK/OneSignalSDK/Source/OSInAppMessageBridgeEvent.m index ecb9d27a4..3be9ce669 100644 --- a/iOS_SDK/OneSignalSDK/Source/OSInAppMessageBridgeEvent.m +++ b/iOS_SDK/OneSignalSDK/Source/OSInAppMessageBridgeEvent.m @@ -31,10 +31,9 @@ @implementation OSInAppMessageBridgeEvent -+ (instancetype _Nullable)instanceWithData:(NSData *)data { ++ (instancetype)instanceWithData:(NSData *)data { NSError *error; NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error]; - if (error || !json) { [OneSignal onesignal_Log:ONE_S_LL_WARN message:[NSString stringWithFormat:@"Unable to decode JS-bridge event with error: %@", error.description ?: @"Unknown Error"]]; return nil; @@ -51,26 +50,35 @@ + (instancetype _Nullable)instanceWithJson:(NSDictionary *)json { else return nil; - if (instance.type == OSInAppMessageBridgeEventTypeActionTaken) { - // deserialize the action JSON - if ([json[@"body"] isKindOfClass:[NSDictionary class]]) { - - let action = [OSInAppMessageAction instanceWithJson:json[@"body"]]; - - if (!action) + switch (instance.type) { + case OSInAppMessageBridgeEventTypeActionTaken: { + // deserialize the action JSON + if ([json[@"body"] isKindOfClass:[NSDictionary class]]) { + + let action = [OSInAppMessageAction instanceWithJson:json[@"body"]]; + + if (!action) + return nil; + + instance.userAction = action; + } + else return nil; - - instance.userAction = action; + break; + } + case OSInAppMessageBridgeEventTypePageRenderingComplete: { + instance.renderingComplete = [OSInAppMessageBridgeEventRenderingComplete instanceWithJson:json]; + break; + } + case OSInAppMessageBridgeEventTypePageResize: { + instance.resize = [OSInAppMessageBridgeEventResize instanceWithJson:json]; + break; + } + case OSInAppMessageBridgeEventTypePageChange: { + instance.pageChange = [OSInAppMessageBridgeEventPageChange instanceWithJson:json]; + break; } - else - return nil; - } - else if (instance.type == OSInAppMessageBridgeEventTypePageRenderingComplete) { - instance.renderingComplete = [OSInAppMessageBridgeEventRenderingComplete instanceWithJson:json]; - } else if (instance.type == OSInAppMessageBridgeEventTypePageResize) { - instance.resize = [OSInAppMessageBridgeEventResize instanceWithJson:json]; } - return instance; } @@ -90,7 +98,7 @@ + (instancetype _Nullable)instanceWithData:(NSData *)data { return nil; } -+ (instancetype _Nullable)instanceWithJson:(NSDictionary *)json { ++ (instancetype)instanceWithJson:(NSDictionary *)json { let instance = [OSInAppMessageBridgeEventRenderingComplete new]; if (json[@"displayLocation"]) @@ -140,3 +148,25 @@ - (NSString *)description { return [NSString stringWithFormat:@"OSInAppMessageBridgeEventResize height: %@", _height]; } @end + +@implementation OSInAppMessageBridgeEventPageChange ++ (instancetype)instanceWithData:(NSData *)data { + return nil; +} + ++ (instancetype)instanceWithJson:(NSDictionary *)json { + let instance = [OSInAppMessageBridgeEventPageChange new]; + instance.page = [OSInAppMessagePage instanceWithJson:json]; + return instance; +} + ++ (instancetype _Nullable)instancePreviewFromNotification:(OSNotification * _Nonnull)notification { + return nil; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"OSInAppMessageBridgeEventPageChange pageId: %@ pageIndex: %@", _page.pageId, _page.pageIndex]; +} + +@end + diff --git a/iOS_SDK/OneSignalSDK/Source/OSInAppMessagePage.h b/iOS_SDK/OneSignalSDK/Source/OSInAppMessagePage.h new file mode 100644 index 000000000..e85bf2a67 --- /dev/null +++ b/iOS_SDK/OneSignalSDK/Source/OSInAppMessagePage.h @@ -0,0 +1,36 @@ +/** +* Modified MIT License +* +* Copyright 2020 OneSignal +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* 1. The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* 2. All copies of substantial portions of the Software may only be used in connection +* with services provided by OneSignal. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +*/ + +#import +#import "OSJSONHandling.h" + +@interface OSInAppMessagePage : NSObject + +@property (strong, nonatomic, nonnull) NSString *pageId; +@property (strong, nonatomic, nonnull) NSNumber *pageIndex; + +@end diff --git a/iOS_SDK/OneSignalSDK/Source/OSInAppMessagePage.m b/iOS_SDK/OneSignalSDK/Source/OSInAppMessagePage.m new file mode 100644 index 000000000..a5df71298 --- /dev/null +++ b/iOS_SDK/OneSignalSDK/Source/OSInAppMessagePage.m @@ -0,0 +1,60 @@ +/** +* Modified MIT License +* +* Copyright 2020 OneSignal +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* 1. The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* 2. All copies of substantial portions of the Software may only be used in connection +* with services provided by OneSignal. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +*/ + +#import "OSInAppMessagePage.h" + +@implementation OSInAppMessagePage + ++ (instancetype _Nullable)instanceWithData:(NSData * _Nonnull)data { + NSError *error; + NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; + + if (error || !json) { + [OneSignal onesignalLog:ONE_S_LL_ERROR message:[NSString stringWithFormat:@"OSInAppMessagePage Unable to decode in-app message JSON: %@", error.description ?: @"No Data"]]; + return nil; + } + + return [self instanceWithJson:json]; +} + ++ (instancetype _Nullable)instanceWithJson:(NSDictionary * _Nonnull)json { + OSInAppMessagePage *page = [OSInAppMessagePage new]; + + if ([json[@"pageId"] isKindOfClass:[NSString class]]) + page.pageId = json[@"pageId"]; + + if ([json[@"pageIndex"] isKindOfClass:[NSNumber class]]) + page.pageIndex = json[@"pageIndex"]; + + return page; +} + ++ (instancetype _Nullable)instancePreviewFromNotification:(OSNotification * _Nonnull)notification { + return nil; +} + +@end diff --git a/iOS_SDK/OneSignalSDK/Source/OSInAppMessageViewController.h b/iOS_SDK/OneSignalSDK/Source/OSInAppMessageViewController.h index 1131056a1..f7a1b1fb1 100644 --- a/iOS_SDK/OneSignalSDK/Source/OSInAppMessageViewController.h +++ b/iOS_SDK/OneSignalSDK/Source/OSInAppMessageViewController.h @@ -37,6 +37,7 @@ NS_ASSUME_NONNULL_BEGIN @protocol OSInAppMessageViewControllerDelegate - (void)messageViewDidSelectAction:(OSInAppMessage *)message withAction:(OSInAppMessageAction *)action; +- (void)messageViewDidDisplayPage:(OSInAppMessage *)message withPageId:(NSString *)pageId; - (void)messageViewControllerWasDismissed; - (void)webViewContentFinishedLoading; diff --git a/iOS_SDK/OneSignalSDK/Source/OSInAppMessageViewController.m b/iOS_SDK/OneSignalSDK/Source/OSInAppMessageViewController.m index 9deac184e..40b6ea251 100644 --- a/iOS_SDK/OneSignalSDK/Source/OSInAppMessageViewController.m +++ b/iOS_SDK/OneSignalSDK/Source/OSInAppMessageViewController.m @@ -622,35 +622,44 @@ - (void)jsEventOccurredWithBody:(NSData *)body { [OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:eventMessage]; NSString *eventTypeMessage = [NSString stringWithFormat:@"Action Occured with Event Type: %lu", (unsigned long)event.type]; [OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:eventTypeMessage]; - + if (event) { - if (event.type == OSInAppMessageBridgeEventTypePageRenderingComplete) { - - // BOOL set to true since the JS event fired, meaning the WebView was populated properly with the IAM code - self.didPageRenderingComplete = true; - - self.message.position = event.renderingComplete.displayLocation; - self.message.height = event.renderingComplete.height; - self.message.dragToDismissDisabled = event.renderingComplete.dragToDismissDisabled; - - // The page is fully loaded and should now be displayed - // This is only fired once the javascript on the page sends the "rendering_complete" type event - dispatch_async(dispatch_get_main_queue(), ^{ - [self.delegate webViewContentFinishedLoading]; - [OneSignalHelper performSelector:@selector(displayMessage) onMainThreadOnObject:self withObject:nil afterDelay:0.0f]; - }); - } - else if (event.type == OSInAppMessageBridgeEventTypePageResize) { - // Unused resize event for IAM during actions like orientation changes and displaying an IAM -// self.message.height = event.resize.height; - } - else if (event.type == OSInAppMessageBridgeEventTypeActionTaken) { - if (event.userAction.clickType) - [self.delegate messageViewDidSelectAction:self.message withAction:event.userAction]; - if (event.userAction.urlActionType == OSInAppMessageActionUrlTypeReplaceContent) - [self.messageView loadReplacementURL:event.userAction.clickUrl]; - if (event.userAction.closesMessage) - [self dismissCurrentInAppMessage]; + switch (event.type) { + case OSInAppMessageBridgeEventTypePageRenderingComplete: { + // BOOL set to true since the JS event fired, meaning the WebView was populated properly with the IAM code + self.didPageRenderingComplete = true; + + self.message.position = event.renderingComplete.displayLocation; + self.message.height = event.renderingComplete.height; + + // The page is fully loaded and should now be displayed + // This is only fired once the javascript on the page sends the "rendering_complete" type event + dispatch_async(dispatch_get_main_queue(), ^{ + [self.delegate webViewContentFinishedLoading]; + [OneSignalHelper performSelector:@selector(displayMessage) onMainThreadOnObject:self withObject:nil afterDelay:0.0f]; + }); + break; + } + case OSInAppMessageBridgeEventTypePageResize: { + // Unused resize event for IAM during actions like orientation changes and displaying an IAM + // self.message.height = event.resize.height; + break; + } + case OSInAppMessageBridgeEventTypeActionTaken: { + if (event.userAction.clickType) + [self.delegate messageViewDidSelectAction:self.message withAction:event.userAction]; + if (event.userAction.urlActionType == OSInAppMessageActionUrlTypeReplaceContent) + [self.messageView loadReplacementURL:event.userAction.clickUrl]; + if (event.userAction.closesMessage) + [self dismissCurrentInAppMessage]; + break; + } + case OSInAppMessageBridgeEventTypePageChange: { + [self.delegate messageViewDidDisplayPage:self.message withPageId: event.pageChange.page.pageId]; + break; + } + default: + break; } } } diff --git a/iOS_SDK/OneSignalSDK/Source/OSInAppMessagingDefines.h b/iOS_SDK/OneSignalSDK/Source/OSInAppMessagingDefines.h index f0d404dec..3e35ddffb 100644 --- a/iOS_SDK/OneSignalSDK/Source/OSInAppMessagingDefines.h +++ b/iOS_SDK/OneSignalSDK/Source/OSInAppMessagingDefines.h @@ -73,6 +73,7 @@ typedef NS_ENUM(NSUInteger, OSTriggerOperatorType) { #define OS_IAM_SEEN_SET_KEY @"OS_IAM_SEEN_SET" #define OS_IAM_CLICKED_SET_KEY @"OS_IAM_CLICKED_SET" #define OS_IAM_IMPRESSIONED_SET_KEY @"OS_IAM_IMPRESSIONED_SET" +#define OS_IAM_PAGE_IMPRESSIONED_SET_KEY @"OS_IAM_PAGE_IMPRESSIONED_SET" #define OS_IAM_MESSAGES_ARRAY @"OS_IAM_MESSAGES_ARRAY" #define OS_IAM_REDISPLAY_DICTIONARY @"OS_IAM_REDISPLAY_DICTIONARY" @@ -96,7 +97,7 @@ typedef NS_ENUM(NSUInteger, OSTriggerOperatorType) { #define PREFERRED_VARIANT_ORDER @[@"ios", @"app", @"all"] -#define OS_BRIDGE_EVENT_TYPES @[@"rendering_complete", @"action_taken", @"resize"] +#define OS_BRIDGE_EVENT_TYPES @[@"rendering_complete", @"action_taken", @"resize", @"page_change"] #define OS_IS_VALID_BRIDGE_EVENT_TYPE(string) [OS_BRIDGE_EVENT_TYPES containsObject:string] #define OS_BRIDGE_EVENT_TYPE_FROM_STRING(string) (OSInAppMessageBridgeEventType)[OS_BRIDGE_EVENT_TYPES indexOfObject:string] diff --git a/iOS_SDK/OneSignalSDK/Source/OSMessagingController.h b/iOS_SDK/OneSignalSDK/Source/OSMessagingController.h index a77274e1a..0008afeb3 100644 --- a/iOS_SDK/OneSignalSDK/Source/OSMessagingController.h +++ b/iOS_SDK/OneSignalSDK/Source/OSMessagingController.h @@ -54,6 +54,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)updateInAppMessagesFromCache; - (void)updateInAppMessagesFromOnSession:(NSArray *)newMessages; - (void)messageViewImpressionRequest:(OSInAppMessage *)message; +- (void)messageViewPageImpressionRequest:(OSInAppMessage *)message withPageId:(NSString *)pageId; - (BOOL)isInAppMessagingPaused; - (void)setInAppMessagingPaused:(BOOL)pause; diff --git a/iOS_SDK/OneSignalSDK/Source/OSMessagingController.m b/iOS_SDK/OneSignalSDK/Source/OSMessagingController.m index e94209a44..6efbeda4e 100644 --- a/iOS_SDK/OneSignalSDK/Source/OSMessagingController.m +++ b/iOS_SDK/OneSignalSDK/Source/OSMessagingController.m @@ -63,6 +63,9 @@ @interface OSMessagingController () // Tracking for impessions so that an IAM is only tracked once and not several times if it is reshown @property (strong, nonatomic, nonnull) NSMutableSet *impressionedInAppMessages; +// Tracking for impessions so that an IAM is only tracked once and not several times if it is reshown +@property (strong, nonatomic, nonnull) NSMutableSet *viewedPageIDs; + // Click action block to allow overridden behavior when clicking an IAM @property (strong, nonatomic, nullable) OSInAppMessageClickBlock actionClickBlock; @@ -143,6 +146,7 @@ - (instancetype)init { [OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"init redisplayedInAppMessages with: %@", [_redisplayedInAppMessages description]]]; self.clickedClickIds = [[NSMutableSet alloc] initWithSet:[standardUserDefaults getSavedSetForKey:OS_IAM_CLICKED_SET_KEY defaultValue:nil]]; self.impressionedInAppMessages = [[NSMutableSet alloc] initWithSet:[standardUserDefaults getSavedSetForKey:OS_IAM_IMPRESSIONED_SET_KEY defaultValue:nil]]; + self.viewedPageIDs = [[NSMutableSet alloc] initWithSet:[standardUserDefaults getSavedSetForKey:OS_IAM_PAGE_IMPRESSIONED_SET_KEY defaultValue:nil]]; self.currentPromptAction = nil; self.isAppInactive = NO; // BOOL that controls if in-app messaging is paused or not (false by default) @@ -280,6 +284,54 @@ - (void)showAndImpressMessage:(OSInAppMessage *)message { }); } +- (void)messageViewPageImpressionRequest:(OSInAppMessage *)message withPageId:(NSString *)pageId { + if (message.isPreview) { + [OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"Not sending page impression for preview message. ID: %@",pageId]]; + return; + } + + if (!pageId) { + [OneSignal onesignal_Log:ONE_S_LL_ERROR message:[NSString stringWithFormat:@"Attempting to send page impression for nil page id"]]; + return; + } + + NSString *messagePrefixedPageId = [message.messageId stringByAppendingString:pageId]; + + if ([self.viewedPageIDs containsObject:messagePrefixedPageId]) { + [OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"Page Impression already sent. id: %@",pageId]]; + return; + } + + [self.viewedPageIDs addObject:messagePrefixedPageId]; + + [OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:[NSString stringWithFormat:@"Page Impression Request page id: %@",pageId]]; + // Create the request and attach a payload to it + let metricsRequest = [OSRequestInAppMessagePageViewed withAppId:OneSignal.appId + withPlayerId:OneSignal.currentSubscriptionState.userId + withMessageId:message.messageId + withPageId:pageId + forVariantId:message.variantId]; + + [OneSignalClient.sharedClient executeRequest:metricsRequest + onSuccess:^(NSDictionary *result) { + NSString *successMessage = [NSString stringWithFormat:@"In App Message with message id: %@ and page id: %@, successful POST page impression update with result: %@", message.messageId, pageId, result]; + [OneSignal onesignal_Log:ONE_S_LL_DEBUG message:successMessage]; + // If the post was successful, save the updated viewedPageIds set + [OneSignalUserDefaults.initStandard saveSetForKey:OS_IAM_PAGE_IMPRESSIONED_SET_KEY withValue:self.viewedPageIDs]; + } + onFailure:^(NSError *error) { + NSString *errorMessage = [NSString stringWithFormat:@"In App Message with message id: %@ and page id: %@, failed POST page impression update with error: %@", message.messageId, pageId, error]; + [OneSignal onesignal_Log:ONE_S_LL_ERROR message:errorMessage]; + if (message) { + [self.viewedPageIDs removeObject:messagePrefixedPageId]; + } + }]; +} + +- (BOOL)shouldSendImpression:(OSInAppMessage *)message { + return !(message.isPreview || [self.impressionedInAppMessages containsObject:message.messageId]); +} + /* Make an impression POST to track that the IAM has been Request should only be made for IAMs that are not previews and have not been impressioned yet @@ -287,7 +339,7 @@ - (void)showAndImpressMessage:(OSInAppMessage *)message { - (void)messageViewImpressionRequest:(OSInAppMessage *)message { // Make sure no tracking is performed for previewed IAMs // If the messageId exists in cached impressionedInAppMessages return early so the impression is not tracked again - if (message.isPreview || [self.impressionedInAppMessages containsObject:message.messageId]) + if (![self shouldSendImpression:message]) return; // Add messageId to impressionedInAppMessages @@ -372,6 +424,8 @@ - (void)setDataForRedisplay:(OSInAppMessage *)message { [self.seenInAppMessages removeObject:message.messageId]; [self.impressionedInAppMessages removeObject:message.messageId]; + [self.viewedPageIDs removeAllObjects]; + [OneSignalUserDefaults.initStandard saveSetForKey:OS_IAM_PAGE_IMPRESSIONED_SET_KEY withValue:self.viewedPageIDs]; [message clearClickIds]; return; } @@ -603,6 +657,12 @@ - (void)messageViewDidSelectAction:(OSInAppMessage *)message withAction:(OSInApp [self sendOutcomes:action.outcomes forMessageId:message.messageId]; } +- (void)messageViewDidDisplayPage:(OSInAppMessage *)message withPageId:(NSString *)pageId { + dispatch_async(dispatch_get_main_queue(), ^{ + [self messageViewPageImpressionRequest:message withPageId:pageId]; + }); +} + /* * Show the developer what will happen with a non IAM preview */ diff --git a/iOS_SDK/OneSignalSDK/Source/OneSignal.h b/iOS_SDK/OneSignalSDK/Source/OneSignal.h index 82a5298cd..600065e7b 100755 --- a/iOS_SDK/OneSignalSDK/Source/OneSignal.h +++ b/iOS_SDK/OneSignalSDK/Source/OneSignal.h @@ -185,6 +185,9 @@ typedef NS_ENUM(NSUInteger, OSNotificationActionType) { // The URL (if any) that should be opened when the action occurs @property (strong, nonatomic, nullable) NSURL *clickUrl; +//UUID for the page in an IAM Carousel +@property (strong, nonatomic, nullable) NSString *pageId; + // Whether or not the click action is first click on the IAM @property (nonatomic) BOOL firstClick; diff --git a/iOS_SDK/OneSignalSDK/Source/Requests.h b/iOS_SDK/OneSignalSDK/Source/Requests.h index 86408dee2..eaa25dae5 100644 --- a/iOS_SDK/OneSignalSDK/Source/Requests.h +++ b/iOS_SDK/OneSignalSDK/Source/Requests.h @@ -123,6 +123,10 @@ NS_ASSUME_NONNULL_END + (instancetype _Nonnull)withAppId:(NSString * _Nonnull)appId withPlayerId:(NSString * _Nonnull)playerId withMessageId:(NSString * _Nonnull)messageId forVariantId:(NSString * _Nonnull)variantId; @end +@interface OSRequestInAppMessagePageViewed : OneSignalRequest ++ (instancetype _Nonnull)withAppId:(NSString * _Nonnull)appId withPlayerId:(NSString * _Nonnull)playerId withMessageId:(NSString * _Nonnull)messageId withPageId:(NSString * _Nonnull)pageId forVariantId:(NSString * _Nonnull)variantId; +@end + @interface OSRequestInAppMessageClicked : OneSignalRequest + (instancetype _Nonnull)withAppId:(NSString * _Nonnull)appId withPlayerId:(NSString * _Nonnull)playerId diff --git a/iOS_SDK/OneSignalSDK/Source/Requests.m b/iOS_SDK/OneSignalSDK/Source/Requests.m index f367a46bc..62c74ebe6 100644 --- a/iOS_SDK/OneSignalSDK/Source/Requests.m +++ b/iOS_SDK/OneSignalSDK/Source/Requests.m @@ -420,6 +420,29 @@ + (instancetype _Nonnull)withAppId:(NSString * _Nonnull)appId } @end +@implementation OSRequestInAppMessagePageViewed ++ (instancetype _Nonnull)withAppId:(NSString * _Nonnull)appId + withPlayerId:(NSString * _Nonnull)playerId + withMessageId:(NSString * _Nonnull)messageId + withPageId:(NSString * _Nonnull)pageId + forVariantId:(NSString *)variantId { + let request = [OSRequestInAppMessagePageViewed new]; + + request.parameters = @{ + @"device_type": @0, + @"player_id": playerId, + @"app_id": appId, + @"variant_id": variantId, + @"page_id": pageId + }; + + request.method = POST; + request.path = [NSString stringWithFormat:@"in_app_messages/%@/pageImpression", messageId]; + + return request; +} +@end + @implementation OSRequestInAppMessageClicked + (instancetype _Nonnull)withAppId:(NSString * _Nonnull)appId withPlayerId:(NSString * _Nonnull)playerId diff --git a/iOS_SDK/OneSignalSDK/UnitTests/InAppMessagingTests.m b/iOS_SDK/OneSignalSDK/UnitTests/InAppMessagingTests.m index 97f9cf137..70cefa36c 100644 --- a/iOS_SDK/OneSignalSDK/UnitTests/InAppMessagingTests.m +++ b/iOS_SDK/OneSignalSDK/UnitTests/InAppMessagingTests.m @@ -58,6 +58,7 @@ @implementation InAppMessagingTests { OSInAppMessage *testMessageRedisplay; OSInAppMessageAction *testAction; OSInAppMessageBridgeEvent *testBridgeEvent; + OSInAppMessageBridgeEvent *testPageChangeEvent; } NSInteger const LIMIT = 5; @@ -95,10 +96,17 @@ - (void)setUp { @"id" : @"test_id", @"url" : @"https://www.onesignal.com", @"url_target" : @"browser", - @"close" : @false + @"close" : @false, + @"pageId": @"test_page_id", } }]; + testPageChangeEvent = [OSInAppMessageBridgeEvent instanceWithJson:@{ + @"type" : @"page_change", + @"pageIndex" : @"1", + @"pageId" : @"test_id", + }]; + testAction = testBridgeEvent.userAction; self.triggerController = [OSTriggerController new]; @@ -289,6 +297,12 @@ - (void)testCorrectlyParsedActionClose { - (void)testCorrectlyParsedActionBridgeEvent { XCTAssertEqual(testBridgeEvent.type, OSInAppMessageBridgeEventTypeActionTaken); + XCTAssertEqualObjects(testBridgeEvent.userAction.pageId, @"test_page_id"); +} + +- (void)testCorrectlyParsedPageChangeBridgeEvent { + XCTAssertEqual(testPageChangeEvent.type, OSInAppMessageBridgeEventTypePageChange); + XCTAssertEqualObjects(testPageChangeEvent.pageChange.page.pageId, @"test_id"); } - (void)testCorrectlyParsedRenderingCompleteBridgeEvent { diff --git a/iOS_SDK/OneSignalSDK/UnitTests/RequestTests.m b/iOS_SDK/OneSignalSDK/UnitTests/RequestTests.m index 32fca446e..8570b0af2 100644 --- a/iOS_SDK/OneSignalSDK/UnitTests/RequestTests.m +++ b/iOS_SDK/OneSignalSDK/UnitTests/RequestTests.m @@ -52,6 +52,7 @@ @implementation RequestTests { NSString *testInAppMessageId; NSString *testInAppMessageAppId; NSString *testInAppMessageVariantId; + NSString *testInAppMessagePageId; NSString *testNotificationId; OSOutcomeEvent *testOutcome; NSNumber *testDeviceType; @@ -78,6 +79,7 @@ - (void)setUp { testInAppMessageId = @"test_in_app_message_id"; testInAppMessageAppId = @"test_in_app_message_app_id"; testInAppMessageVariantId = @"test_in_app_message_variant_id"; + testInAppMessagePageId = @"test_in_app_message_page_id"; testNotificationId = @"test_notification_id"; testOutcome = [[OSOutcomeEvent new] initWithSession:UNATTRIBUTED