From 8f5539835587e52543ab523cf731a935c609834e Mon Sep 17 00:00:00 2001 From: Nikola Zagorchev Date: Wed, 3 Aug 2022 20:09:39 +0300 Subject: [PATCH 1/6] Fix observer --- .../LeanplumSDK/ClassesSwift/Actions/ActionManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager.swift b/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager.swift index 5b20279a..361059e9 100644 --- a/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager.swift +++ b/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager.swift @@ -53,7 +53,7 @@ import Foundation NotificationCenter .default .addObserver(forName: UIApplication.didBecomeActiveNotification, - object: self, + object: nil, queue: .main) { [weak self] _ in guard let `self` = self else { return } if self.configuration.resumeOnEnterForeground { From b7504099d28a7363e6e2d3b947c290c75b235da7 Mon Sep 17 00:00:00 2001 From: Nikola Zagorchev Date: Wed, 3 Aug 2022 20:31:34 +0300 Subject: [PATCH 2/6] Enforce local caps in executor --- LeanplumSDK/LeanplumSDK/Classes/Internal/Leanplum.m | 6 ------ LeanplumSDK/LeanplumSDK/Classes/Leanplum.h | 7 +++++++ .../ClassesSwift/Actions/ActionManager+Executor.swift | 8 ++++++++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/LeanplumSDK/LeanplumSDK/Classes/Internal/Leanplum.m b/LeanplumSDK/LeanplumSDK/Classes/Internal/Leanplum.m index ae3cb547..a166ebc2 100644 --- a/LeanplumSDK/LeanplumSDK/Classes/Internal/Leanplum.m +++ b/LeanplumSDK/LeanplumSDK/Classes/Internal/Leanplum.m @@ -2564,12 +2564,6 @@ + (LPSecuredVars *)securedVars return [[LPVarCache sharedCache] securedVars];; } - -/** - * Checks if message should be suppressed based on the local IAM caps. - * @param context The message context to check. - * @return True if message should be suppressed, false otherwise. -*/ + (BOOL)shouldSuppressMessage:(LPActionContext *)context { if([LP_PUSH_NOTIFICATION_ACTION isEqualToString:[context actionName]]) { diff --git a/LeanplumSDK/LeanplumSDK/Classes/Leanplum.h b/LeanplumSDK/LeanplumSDK/Classes/Leanplum.h index 63956625..2b89048c 100644 --- a/LeanplumSDK/LeanplumSDK/Classes/Leanplum.h +++ b/LeanplumSDK/LeanplumSDK/Classes/Leanplum.h @@ -371,6 +371,13 @@ NS_SWIFT_NAME(start(userId:attributes:completion:)); dismissHandler:(nullable LeanplumActionBlock)dismissHandler NS_SWIFT_NAME(defineAction(name:kind:args:options:present:dismiss:)); +/** + * Checks if message should be suppressed based on the local IAM caps. + * @param context The message context to check. + * @return True if message should be suppressed, false otherwise. +*/ ++ (BOOL)shouldSuppressMessage:(LPActionContext *)context; + + (void)applicationDidFinishLaunchingWithOptions:(NSDictionary *)launchOptions; + (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)token; + (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error; diff --git a/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager+Executor.swift b/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager+Executor.swift index 7215d4ae..e849dbf8 100644 --- a/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager+Executor.swift +++ b/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager+Executor.swift @@ -37,6 +37,14 @@ extension ActionManager { } Log.debug("[ActionManager]: running action with name: \(action.context).") + + if action.type == .single, + Leanplum.shouldSuppressMessage(action.context) { + Log.info("[ActionManager]: local IAM caps reached, suppressing \(action.context).") + state.currentAction = nil + performAvailableActions() + return + } // decide if we are going to display the message // by calling delegate and let it decide what are we supposed to do From a980a59c0548fcfc51eb52f1e2ac8a16440e3080 Mon Sep 17 00:00:00 2001 From: Nikola Zagorchev Date: Wed, 3 Aug 2022 20:33:27 +0300 Subject: [PATCH 3/6] Add OpenURL completion --- .../MessageTemplates/LPOpenUrlMessageTemplate.m | 12 ++++++------ LeanplumSDK/LeanplumSDK/Classes/Utilities/LPUtils.h | 7 ++++++- LeanplumSDK/LeanplumSDK/Classes/Utilities/LPUtils.m | 10 +++++++++- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/LeanplumSDK/LeanplumSDK/Classes/MessageTemplates/LPOpenUrlMessageTemplate.m b/LeanplumSDK/LeanplumSDK/Classes/MessageTemplates/LPOpenUrlMessageTemplate.m index bdd124af..447c324a 100644 --- a/LeanplumSDK/LeanplumSDK/Classes/MessageTemplates/LPOpenUrlMessageTemplate.m +++ b/LeanplumSDK/LeanplumSDK/Classes/MessageTemplates/LPOpenUrlMessageTemplate.m @@ -24,10 +24,11 @@ +(void)defineAction @try { LPOpenUrlMessageTemplate *template = [[LPOpenUrlMessageTemplate alloc] init]; template.context = context; - [template openURL]; - - [context actionDismissed]; + [template openURLWithCompletion:^(BOOL success) { + [context actionDismissed]; + }]; + return YES; } @catch (NSException *exception) { @@ -40,14 +41,13 @@ +(void)defineAction }]; } -- (void) openURL +- (void)openURLWithCompletion:(void (^ __nonnull)(BOOL success))completion { dispatch_async(dispatch_get_main_queue(), ^{ NSString *encodedURLString = [self urlEncodedStringFromString:[self.context stringNamed:LPMT_ARG_URL]]; NSURL *url = [NSURL URLWithString: encodedURLString]; - [LPUtils openURL:url]; + [LPUtils openURL:url completionHandler:completion]; }); - } - (NSString *)urlEncodedStringFromString:(NSString *)urlString { diff --git a/LeanplumSDK/LeanplumSDK/Classes/Utilities/LPUtils.h b/LeanplumSDK/LeanplumSDK/Classes/Utilities/LPUtils.h index c3d1c18a..24d09d94 100644 --- a/LeanplumSDK/LeanplumSDK/Classes/Utilities/LPUtils.h +++ b/LeanplumSDK/LeanplumSDK/Classes/Utilities/LPUtils.h @@ -70,7 +70,12 @@ /** * Open URLs from SDK */ -+ (void)openURL:(NSURL*)url; ++ (void)openURL:(NSURL *)url; + +/** + * Open URLs from SDK and calls the completionHandler + */ ++ (void)openURL:(NSURL *)url completionHandler:(void (^ __nullable)(BOOL success))completion; /** * Checks if given value is a NSNumber with bool value diff --git a/LeanplumSDK/LeanplumSDK/Classes/Utilities/LPUtils.m b/LeanplumSDK/LeanplumSDK/Classes/Utilities/LPUtils.m index 7d50a138..13deaf53 100644 --- a/LeanplumSDK/LeanplumSDK/Classes/Utilities/LPUtils.m +++ b/LeanplumSDK/LeanplumSDK/Classes/Utilities/LPUtils.m @@ -111,13 +111,21 @@ + (NSBundle *)leanplumBundle } + (void)openURL:(NSURL *)url +{ + [self openURL:url completionHandler:nil]; +} + ++ (void)openURL:(NSURL *)url completionHandler:(void (^ __nullable)(BOOL success))completion { if (@available(iOS 10.0, *)) { - [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; + [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:completion]; } else { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" [[UIApplication sharedApplication] openURL:url]; + if (completion) { + completion(YES); + } #pragma clang diagnostic pop } } From a359822482b36361a163a575050f45b0d222eab0 Mon Sep 17 00:00:00 2001 From: Nikola Zagorchev Date: Thu, 4 Aug 2022 19:24:46 +0300 Subject: [PATCH 4/6] open url dismiss --- .../LPOpenUrlMessageTemplate.m | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/LeanplumSDK/LeanplumSDK/Classes/MessageTemplates/LPOpenUrlMessageTemplate.m b/LeanplumSDK/LeanplumSDK/Classes/MessageTemplates/LPOpenUrlMessageTemplate.m index 447c324a..cefd687e 100644 --- a/LeanplumSDK/LeanplumSDK/Classes/MessageTemplates/LPOpenUrlMessageTemplate.m +++ b/LeanplumSDK/LeanplumSDK/Classes/MessageTemplates/LPOpenUrlMessageTemplate.m @@ -26,7 +26,25 @@ +(void)defineAction template.context = context; [template openURLWithCompletion:^(BOOL success) { - [context actionDismissed]; + /** + * When the action is dismissed, the ActionManager queue continues to perform actions. + * If the URL opens an external app or browser, there is a delay + * before UIApplication.willResignActiveNotification or UIScene.willDeactivateNotification are executed. + * This delay causes next actions in the queue to execute before the app resigns or application state changes. + * If there are other OpenURL actions in the queue, those actions will be perfomed by the ActionManager + * until the application resigns active (which pauses the main queue respectively the ActionManager queue). + * However, the application:openURL will fail to open them hence they will not be presented. + * + * This happens in the edge case where there are multiple Open URL actions executed one after another, likely when the queue was paused and actions were opened. + * Since real use case implications should be extremely minimal, the implementation is left as is. + * If a workaround should be added, dispatch the actionDismissed after a delay - pausing the queue when app willResign will not work due to the delay explained above. + * dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + * [context actionDismissed]; + * }); + */ + dispatch_async(dispatch_get_main_queue(), ^{ + [context actionDismissed]; + }); }]; return YES; From b3aaf935f288f686ddb937624fa7da9e1b35b1d4 Mon Sep 17 00:00:00 2001 From: Nikola Zagorchev Date: Thu, 4 Aug 2022 19:32:44 +0300 Subject: [PATCH 5/6] fix app rating dismiss --- .../LPAppRatingMessageTemplate.m | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/LeanplumSDK/LeanplumSDK/Classes/MessageTemplates/LPAppRatingMessageTemplate.m b/LeanplumSDK/LeanplumSDK/Classes/MessageTemplates/LPAppRatingMessageTemplate.m index e5ca1c80..d804cccf 100644 --- a/LeanplumSDK/LeanplumSDK/Classes/MessageTemplates/LPAppRatingMessageTemplate.m +++ b/LeanplumSDK/LeanplumSDK/Classes/MessageTemplates/LPAppRatingMessageTemplate.m @@ -23,6 +23,24 @@ +(void)defineAction [appRatingMessageTemplate appStorePrompt]; + /** + * There is no completion handler for the requestReview. + * No information is returned if the prompt has been shown or not. + * It could be possible to check if a window is presented by comparing the windows count but this is not reliable. + * + * Action is marked as dismissed so the queue can continue executing. + * The app request is shown on a separate window, + * so even if an alert message is presented while the App Review is present, + * it will show underneath and not break the UI. + * + * If this behavior is undesired, then dispatch after a delay, + * to provide some time to the user to rate the app, then dismiss the action. + */ + + dispatch_async(dispatch_get_main_queue(), ^{ + [context actionDismissed]; + }); + return YES; } @catch (NSException *exception) { LOG_LP_MESSAGE_EXCEPTION; @@ -38,7 +56,21 @@ - (void)appStorePrompt { dispatch_async(dispatch_get_main_queue(), ^{ if (NSClassFromString(@"SKStoreReviewController")) { - if (@available(iOS 10.3, *)) { + if (@available(iOS 14.0, *)) { + // Find active scene + __block UIScene *scene; + [[[UIApplication sharedApplication] connectedScenes] enumerateObjectsUsingBlock:^(UIScene * _Nonnull obj, BOOL * _Nonnull stop) { + if (obj.activationState == UISceneActivationStateForegroundActive) { + scene = obj; + *stop = YES; + } + }]; + // Present using scene + UIWindowScene *windowScene = (UIWindowScene*)scene; + if (windowScene) { + [SKStoreReviewController requestReviewInScene:windowScene]; + } + } else if (@available(iOS 10.3, *)) { [SKStoreReviewController requestReview]; } } From 704c4176551dc0b855d8703125b92dff2a82e165 Mon Sep 17 00:00:00 2001 From: Nikola Zagorchev Date: Fri, 5 Aug 2022 14:53:49 +0300 Subject: [PATCH 6/6] Fix tracking for embedded actions --- .../Actions/ActionManager+Definition.swift | 8 -------- .../Actions/ActionManager+Tracking.swift | 19 ++++++++++++++----- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager+Definition.swift b/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager+Definition.swift index 24d41eeb..241e22f2 100644 --- a/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager+Definition.swift +++ b/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager+Definition.swift @@ -16,12 +16,4 @@ extension ActionManager { @objc public func definition(withName name: String) -> ActionDefinition? { return self.definitions.first(where: { $0.name == name }) } - - func getActionDefinitionType(name: String) -> UInt { - let definition = definition(withName: name) - if let definition = definition { - return definition.kind.rawValue - } - return 0 - } } diff --git a/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager+Tracking.swift b/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager+Tracking.swift index 6e4a80f2..4760a8c6 100644 --- a/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager+Tracking.swift +++ b/LeanplumSDK/LeanplumSDK/ClassesSwift/Actions/ActionManager+Tracking.swift @@ -10,22 +10,31 @@ import Foundation extension ActionManager { func recordImpression(action: Action) { typealias Kind = Leanplum.ActionKind - if action.type == .chained { + + switch action.type { + + case .single: + LPActionTriggerManager.shared().recordMessageImpression(action.context.messageId) + + case .chained: // We do not want to count occurrences for action kind, because in multi message // campaigns the Open URL action is not a message. Also if the user has defined // actions of type Action we do not want to count them. + guard let actionKind = definition(withName: action.context.name)?.kind else { + break + } - let actionKind: Kind = .init(rawValue: getActionDefinitionType(name: action.context.name)) switch actionKind { case .action: LPActionTriggerManager.shared().recordChainedActionImpression(action.context.messageId) - case .message: + case .message, [.action, .message]: LPActionTriggerManager.shared().recordMessageImpression(action.context.messageId) default: break } - } else { - LPActionTriggerManager.shared().recordMessageImpression(action.context.messageId) + + case .embedded: + break } } }