From 3d9748f88ec167b6226f51d49eb16a890c337813 Mon Sep 17 00:00:00 2001 From: Kushagra Date: Tue, 29 Oct 2024 13:41:40 +0530 Subject: [PATCH 1/7] Added push and pop validation in synchronized block --- CleverTapSDK/CTValidationResultStack.m | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/CleverTapSDK/CTValidationResultStack.m b/CleverTapSDK/CTValidationResultStack.m index 17d05d71..27b2953e 100644 --- a/CleverTapSDK/CTValidationResultStack.m +++ b/CleverTapSDK/CTValidationResultStack.m @@ -41,20 +41,26 @@ - (void)pushValidationResult:(CTValidationResult *)vr { CleverTapLogInternal(self.config.logLevel, @"%@: no object in the validation result", self); return; } - [self.pendingValidationResults addObject:vr]; - if (self.pendingValidationResults && [self.pendingValidationResults count] > 50) { - [self.pendingValidationResults removeObjectAtIndex:0]; + + @synchronized (self.pendingValidationResults) { + [self.pendingValidationResults addObject:vr]; + if (self.pendingValidationResults.count > 50) { + [self.pendingValidationResults removeObjectAtIndex:0]; + } } } - (CTValidationResult *)popValidationResult { CTValidationResult *vr = nil; - if (self.pendingValidationResults && [self.pendingValidationResults count] > 0) { - vr = self.pendingValidationResults[0]; - [self.pendingValidationResults removeObjectAtIndex:0]; + + @synchronized (self.pendingValidationResults) { + if (self.pendingValidationResults.count > 0) { + vr = self.pendingValidationResults[0]; + [self.pendingValidationResults removeObjectAtIndex:0]; + } } + return vr; } - @end From 3ebb17f6c2f47a6915154617e927ee72f4fa5fee Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Thu, 7 Nov 2024 13:39:57 +0530 Subject: [PATCH 2/7] chore(MC-2302): Update HTML template for inapp preview. - Added check for inapp advanced builder type also. --- CleverTapSDK/CTConstants.h | 1 + CleverTapSDK/InApps/CTInAppDisplayManager.m | 3 +- .../InApps/resources/image_interstitial.html | 30 ++++++++++++++++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index c1f91ae1..4f6b5099 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -231,6 +231,7 @@ extern NSString *CLTAP_PROFILE_IDENTITY_KEY; #define CLTAP_INAPP_PREVIEW_TYPE @"wzrk_inapp_type" #define CLTAP_INAPP_IMAGE_INTERSTITIAL_TYPE @"image-interstitial" +#define CLTAP_INAPP_ADVANCED_BUILDER_TYPE @"advanced-builder" #define CLTAP_INAPP_IMAGE_INTERSTITIAL_CONFIG @"imageInterstitialConfig" #define CLTAP_INAPP_HTML_SPLIT @"\"##Vars##\"" #define CLTAP_INAPP_IMAGE_INTERSTITIAL_HTML_NAME @"image_interstitial" diff --git a/CleverTapSDK/InApps/CTInAppDisplayManager.m b/CleverTapSDK/InApps/CTInAppDisplayManager.m index 61c23d95..cf01931f 100644 --- a/CleverTapSDK/InApps/CTInAppDisplayManager.m +++ b/CleverTapSDK/InApps/CTInAppDisplayManager.m @@ -740,7 +740,8 @@ - (BOOL)didHandleInAppTestFromPushNotificaton:(NSDictionary * _Nullable)notifica error:nil] mutableCopy]; // Handle Image Interstitial InApp Test - if (inapp && [notification[CLTAP_INAPP_PREVIEW_TYPE] isEqualToString:CLTAP_INAPP_IMAGE_INTERSTITIAL_TYPE]) { + NSString *inAppPreviewType = notification[CLTAP_INAPP_PREVIEW_TYPE]; + if (inapp && ([inAppPreviewType isEqualToString:CLTAP_INAPP_IMAGE_INTERSTITIAL_TYPE] || [inAppPreviewType isEqualToString:CLTAP_INAPP_ADVANCED_BUILDER_TYPE])) { NSString *config = [inapp valueForKeyPath:CLTAP_INAPP_IMAGE_INTERSTITIAL_CONFIG]; NSString *htmlContent = [self wrapImageInterstitialContent:[CTUtils jsonObjectToString:config]]; if (config && htmlContent) { diff --git a/CleverTapSDK/InApps/resources/image_interstitial.html b/CleverTapSDK/InApps/resources/image_interstitial.html index 3b2d49c8..34e97a86 100644 --- a/CleverTapSDK/InApps/resources/image_interstitial.html +++ b/CleverTapSDK/InApps/resources/image_interstitial.html @@ -1 +1,29 @@ -
+ + + + + + + +
+
+
+ +
+
+
+ + + + From b44db16a79fcf11ee8301660b94c5806851c69cf Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Tue, 19 Nov 2024 16:51:26 +0530 Subject: [PATCH 3/7] - Updated to latest HTML preview template. - Updated wrap ImageInterstitial content to replace without quotes. --- CleverTapSDK/InApps/CTInAppDisplayManager.m | 2 +- CleverTapSDK/InApps/resources/image_interstitial.html | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CleverTapSDK/InApps/CTInAppDisplayManager.m b/CleverTapSDK/InApps/CTInAppDisplayManager.m index cf01931f..e81f558d 100644 --- a/CleverTapSDK/InApps/CTInAppDisplayManager.m +++ b/CleverTapSDK/InApps/CTInAppDisplayManager.m @@ -801,7 +801,7 @@ - (NSString *)wrapImageInterstitialContent:(NSString *)content { if (html && content) { NSArray *parts = [html componentsSeparatedByString:CLTAP_INAPP_HTML_SPLIT]; if ([parts count] == 2) { - return [NSString stringWithFormat:@"%@'%@'%@", parts[0], content, parts[1]]; + return [NSString stringWithFormat:@"%@%@%@", parts[0], content, parts[1]]; } } return nil; diff --git a/CleverTapSDK/InApps/resources/image_interstitial.html b/CleverTapSDK/InApps/resources/image_interstitial.html index 34e97a86..bf59a29d 100644 --- a/CleverTapSDK/InApps/resources/image_interstitial.html +++ b/CleverTapSDK/InApps/resources/image_interstitial.html @@ -22,8 +22,7 @@ - From d8773c27c3ff7f9c59e34ff602e8a7e7bad80355 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Fri, 22 Nov 2024 11:34:35 +0530 Subject: [PATCH 4/7] added support for promptForPushPermission method in JS Interface --- CleverTapSDK/CleverTapJSInterface.m | 3 +++ ObjCStarter/ObjCStarter/sampleHTMLCode.html | 14 ++++++++++++++ .../Supporting Files/sampleHTMLCode.html | 14 ++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/CleverTapSDK/CleverTapJSInterface.m b/CleverTapSDK/CleverTapJSInterface.m index 4609998e..7ecd0469 100644 --- a/CleverTapSDK/CleverTapJSInterface.m +++ b/CleverTapSDK/CleverTapJSInterface.m @@ -9,6 +9,7 @@ #import "CTInAppDisplayViewController.h" #import "CleverTapBuildInfo.h" +#import "CleverTap+PushPermission.h" @interface CleverTapJSInterface (){} @@ -87,6 +88,8 @@ - (void)handleMessageFromWebview:(NSDictionary *)message forInsta [cleverTap profileDecrementValueBy: message[@"value"] forKey: message[@"key"]]; } else if ([action isEqual: @"triggerInAppAction"]) { [self triggerInAppAction:message[@"actionJson"] callToAction:message[@"callToAction"] buttonId:message[@"buttonId"]]; + } else if ([action isEqual: @"promptForPushPermission"]) { + [cleverTap promptForPushPermission:message[@"showFallbackSettings"]]; } } diff --git a/ObjCStarter/ObjCStarter/sampleHTMLCode.html b/ObjCStarter/ObjCStarter/sampleHTMLCode.html index 906d9f27..9bf98cd9 100755 --- a/ObjCStarter/ObjCStarter/sampleHTMLCode.html +++ b/ObjCStarter/ObjCStarter/sampleHTMLCode.html @@ -24,6 +24,8 @@

CleverTap Webview



+ +

diff --git a/SwiftStarter/SwiftStarter/Supporting Files/sampleHTMLCode.html b/SwiftStarter/SwiftStarter/Supporting Files/sampleHTMLCode.html index 935a0b1f..a4839ae1 100644 --- a/SwiftStarter/SwiftStarter/Supporting Files/sampleHTMLCode.html +++ b/SwiftStarter/SwiftStarter/Supporting Files/sampleHTMLCode.html @@ -24,6 +24,8 @@

CleverTap Webview



+ +

From 99a149f9d186415ab8758e7991daa859b752f388 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Fri, 22 Nov 2024 15:02:27 +0530 Subject: [PATCH 5/7] dismissed inapp when the prompt method is called from an html in-app --- CleverTapSDK/CleverTapJSInterface.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CleverTapSDK/CleverTapJSInterface.m b/CleverTapSDK/CleverTapJSInterface.m index 7ecd0469..47aa13b0 100644 --- a/CleverTapSDK/CleverTapJSInterface.m +++ b/CleverTapSDK/CleverTapJSInterface.m @@ -89,6 +89,9 @@ - (void)handleMessageFromWebview:(NSDictionary *)message forInsta } else if ([action isEqual: @"triggerInAppAction"]) { [self triggerInAppAction:message[@"actionJson"] callToAction:message[@"callToAction"] buttonId:message[@"buttonId"]]; } else if ([action isEqual: @"promptForPushPermission"]) { + if (self.controller) { + [self.controller hide:NO]; + } [cleverTap promptForPushPermission:message[@"showFallbackSettings"]]; } } From a9ad294990aafc593e1bf916e17af028e60af036 Mon Sep 17 00:00:00 2001 From: Nikola Zagorchev Date: Tue, 26 Nov 2024 13:00:49 +0200 Subject: [PATCH 6/7] Refactor test push inapp handling --- CleverTapSDK/InApps/CTInAppDisplayManager.m | 78 ++++++++++++--------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/CleverTapSDK/InApps/CTInAppDisplayManager.m b/CleverTapSDK/InApps/CTInAppDisplayManager.m index e81f558d..09caf2d1 100644 --- a/CleverTapSDK/InApps/CTInAppDisplayManager.m +++ b/CleverTapSDK/InApps/CTInAppDisplayManager.m @@ -738,46 +738,29 @@ - (BOOL)didHandleInAppTestFromPushNotificaton:(NSDictionary * _Nullable)notifica NSMutableDictionary *inapp = [[NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil] mutableCopy]; + if (!inapp) { + CleverTapLogDebug(self.config.logLevel, @"%@: Failed to parse the inapp notification as JSON", self); + return YES; + } - // Handle Image Interstitial InApp Test + // Handle Image Interstitial and Advanced Builder InApp Test (Preview) NSString *inAppPreviewType = notification[CLTAP_INAPP_PREVIEW_TYPE]; - if (inapp && ([inAppPreviewType isEqualToString:CLTAP_INAPP_IMAGE_INTERSTITIAL_TYPE] || [inAppPreviewType isEqualToString:CLTAP_INAPP_ADVANCED_BUILDER_TYPE])) { - NSString *config = [inapp valueForKeyPath:CLTAP_INAPP_IMAGE_INTERSTITIAL_CONFIG]; - NSString *htmlContent = [self wrapImageInterstitialContent:[CTUtils jsonObjectToString:config]]; - if (config && htmlContent) { - inapp[@"type"] = CLTAP_INAPP_HTML_TYPE; - id data = inapp[CLTAP_INAPP_DATA_TAG]; - if (data && [data isKindOfClass:[NSDictionary class]]) { - data = [data mutableCopy]; - // Update the html - data[CLTAP_INAPP_HTML] = htmlContent; - } else { - // If data key is not present or it is not a dictionary, - // set it and overwrite it - inapp[CLTAP_INAPP_DATA_TAG] = @{ - CLTAP_INAPP_HTML: htmlContent - }; - } - } else { - CleverTapLogDebug(self.config.logLevel, @"%@: Failed to parse the image-interstitial notification", self); - return YES; + if ([inAppPreviewType isEqualToString:CLTAP_INAPP_IMAGE_INTERSTITIAL_TYPE] || [inAppPreviewType isEqualToString:CLTAP_INAPP_ADVANCED_BUILDER_TYPE]) { + NSMutableDictionary *htmlInapp = [self handleHTMLInAppPreview:inapp]; + if (!htmlInapp) { + return YES; // Failed to handle HTML inapp } + inapp = htmlInapp; } - if (inapp) { - float delay = self.isAppActiveForeground ? 0.5 : 2.0; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - @try { - [self prepareNotificationForDisplay:inapp]; - } @catch (NSException *e) { - CleverTapLogDebug(self.config.logLevel, @"%@: Failed to display the inapp notifcation from payload: %@", self, e.debugDescription); - } - }); - } else { - CleverTapLogDebug(self.config.logLevel, @"%@: Failed to parse the inapp notification as JSON", self); - return YES; - } - + float delay = self.isAppActiveForeground ? 0.5 : 2.0; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + @try { + [self prepareNotificationForDisplay:inapp]; + } @catch (NSException *e) { + CleverTapLogDebug(self.config.logLevel, @"%@: Failed to display the inapp notifcation from payload: %@", self, e.debugDescription); + } + }); } @catch (NSException *e) { CleverTapLogDebug(self.config.logLevel, @"%@: Failed to display the inapp notifcation from payload: %@", self, e.debugDescription); return YES; @@ -807,5 +790,30 @@ - (NSString *)wrapImageInterstitialContent:(NSString *)content { return nil; } +- (NSMutableDictionary *)handleHTMLInAppPreview:(NSMutableDictionary *)inapp { + NSMutableDictionary *htmlInapp = [inapp mutableCopy]; + NSString *config = [htmlInapp valueForKeyPath:CLTAP_INAPP_IMAGE_INTERSTITIAL_CONFIG]; + NSString *htmlContent = [self wrapImageInterstitialContent:[CTUtils jsonObjectToString:config]]; + if (config && htmlContent) { + htmlInapp[@"type"] = CLTAP_INAPP_HTML_TYPE; + id data = htmlInapp[CLTAP_INAPP_DATA_TAG]; + if (data && [data isKindOfClass:[NSDictionary class]]) { + data = [data mutableCopy]; + // Update the html + data[CLTAP_INAPP_HTML] = htmlContent; + } else { + // If data key is not present or it is not a dictionary, + // set it and overwrite it + htmlInapp[CLTAP_INAPP_DATA_TAG] = @{ + CLTAP_INAPP_HTML: htmlContent + }; + } + return htmlInapp; + } else { + CleverTapLogDebug(self.config.logLevel, @"%@: Failed to parse the image-interstitial notification", self); + return nil; + } +} + @end From 1e6c80d98d3a67971a22a0fc6a1c6c8a734721ff Mon Sep 17 00:00:00 2001 From: nishant-clevertap <96819882+nishant-clevertap@users.noreply.github.com> Date: Tue, 26 Nov 2024 23:51:01 +0530 Subject: [PATCH 7/7] [MC-2363] Add parsing of url params on open-url action (#386) * feat(MC-2363): Add parsing of url params on open-url action * Fixes wzrk_c2a value passed as null when nil is passed as callToAction value in webView message handler. * Added unit tests for triggerInAppAction method * Added parse of url to utils class to reuse code. * Refactoring --------- Co-authored-by: Nikola Zagorchev --- CleverTapSDK.xcodeproj/project.pbxproj | 10 ++ CleverTapSDK/CTConstants.h | 2 + CleverTapSDK/CTInAppDisplayViewController.m | 17 +++ CleverTapSDK/CTInAppUtils.h | 10 +- CleverTapSDK/CTInAppUtils.m | 31 ++++ CleverTapSDK/CleverTapJSInterface.m | 8 + .../InApps/CTInAppHTMLViewController.m | 28 +--- .../InApps/CTInAppDisplayViewControllerMock.h | 18 +++ .../InApps/CTInAppDisplayViewControllerMock.m | 19 +++ .../CTInAppDisplayViewControllerTests.m | 140 ++++++++++++++++++ 10 files changed, 259 insertions(+), 24 deletions(-) create mode 100644 CleverTapSDKTests/InApps/CTInAppDisplayViewControllerMock.h create mode 100644 CleverTapSDKTests/InApps/CTInAppDisplayViewControllerMock.m create mode 100644 CleverTapSDKTests/InApps/CTInAppDisplayViewControllerTests.m diff --git a/CleverTapSDK.xcodeproj/project.pbxproj b/CleverTapSDK.xcodeproj/project.pbxproj index 46b1ca5e..a23bfc05 100644 --- a/CleverTapSDK.xcodeproj/project.pbxproj +++ b/CleverTapSDK.xcodeproj/project.pbxproj @@ -162,6 +162,7 @@ 32790959299F4B29001FE140 /* CTDeviceInfoTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 32790958299F4B29001FE140 /* CTDeviceInfoTest.m */; }; 4803951B2A7ABAD200C4D254 /* CTAES.m in Sources */ = {isa = PBXBuildFile; fileRef = 480395192A7ABAD200C4D254 /* CTAES.m */; }; 4803951C2A7ABAD200C4D254 /* CTAES.h in Headers */ = {isa = PBXBuildFile; fileRef = 4803951A2A7ABAD200C4D254 /* CTAES.h */; }; + 4806346F2CEB620400E39E9B /* CTInAppDisplayViewControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4806346E2CEB620400E39E9B /* CTInAppDisplayViewControllerTests.m */; }; 4808030E292EB4FB00C06E2F /* CleverTap+PushPermission.h in Headers */ = {isa = PBXBuildFile; fileRef = 4808030D292EB4FB00C06E2F /* CleverTap+PushPermission.h */; }; 48080311292EB50D00C06E2F /* CTLocalInApp.h in Headers */ = {isa = PBXBuildFile; fileRef = 4808030F292EB50D00C06E2F /* CTLocalInApp.h */; settings = {ATTRIBUTES = (Public, ); }; }; 48080312292EB50D00C06E2F /* CTLocalInApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 48080310292EB50D00C06E2F /* CTLocalInApp.m */; }; @@ -349,6 +350,7 @@ 6B32A0AD2B9DBE31009ADC57 /* CTTemplatePresenterMock.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B32A0AC2B9DBE31009ADC57 /* CTTemplatePresenterMock.m */; }; 6B32A0B02B9DC374009ADC57 /* CTTemplateArgumentTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B32A0AF2B9DC374009ADC57 /* CTTemplateArgumentTest.m */; }; 6B32A0B42B9F2E8F009ADC57 /* CTTestTemplateProducer.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B32A0B32B9F2E8F009ADC57 /* CTTestTemplateProducer.m */; }; + 6B453EF92CF621E3003C7A89 /* CTInAppDisplayViewControllerMock.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B453EF82CF621E3003C7A89 /* CTInAppDisplayViewControllerMock.m */; }; 6B4A0F912B45EF6D00A42C6D /* CTInAppTriggerManagerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B4A0F902B45EF6D00A42C6D /* CTInAppTriggerManagerTest.m */; }; 6B535FB62AD56C60002A2663 /* CTMultiDelegateManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 6B535FB42AD56C60002A2663 /* CTMultiDelegateManager.h */; }; 6B535FB72AD56C60002A2663 /* CTMultiDelegateManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 6B535FB42AD56C60002A2663 /* CTMultiDelegateManager.h */; }; @@ -769,6 +771,7 @@ 32790958299F4B29001FE140 /* CTDeviceInfoTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTDeviceInfoTest.m; sourceTree = ""; }; 480395192A7ABAD200C4D254 /* CTAES.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTAES.m; sourceTree = ""; }; 4803951A2A7ABAD200C4D254 /* CTAES.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTAES.h; sourceTree = ""; }; + 4806346E2CEB620400E39E9B /* CTInAppDisplayViewControllerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTInAppDisplayViewControllerTests.m; sourceTree = ""; }; 4808030D292EB4FB00C06E2F /* CleverTap+PushPermission.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CleverTap+PushPermission.h"; sourceTree = ""; }; 4808030F292EB50D00C06E2F /* CTLocalInApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTLocalInApp.h; sourceTree = ""; }; 48080310292EB50D00C06E2F /* CTLocalInApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTLocalInApp.m; sourceTree = ""; }; @@ -911,6 +914,8 @@ 6B32A0B12B9F2A75009ADC57 /* CTCustomTemplatesManager+Tests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CTCustomTemplatesManager+Tests.h"; sourceTree = ""; }; 6B32A0B22B9F2E8F009ADC57 /* CTTestTemplateProducer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CTTestTemplateProducer.h; sourceTree = ""; }; 6B32A0B32B9F2E8F009ADC57 /* CTTestTemplateProducer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTTestTemplateProducer.m; sourceTree = ""; }; + 6B453EF72CF621E3003C7A89 /* CTInAppDisplayViewControllerMock.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CTInAppDisplayViewControllerMock.h; sourceTree = ""; }; + 6B453EF82CF621E3003C7A89 /* CTInAppDisplayViewControllerMock.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTInAppDisplayViewControllerMock.m; sourceTree = ""; }; 6B4A0F902B45EF6D00A42C6D /* CTInAppTriggerManagerTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTInAppTriggerManagerTest.m; sourceTree = ""; }; 6B535FB42AD56C60002A2663 /* CTMultiDelegateManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CTMultiDelegateManager.h; sourceTree = ""; }; 6B535FB52AD56C60002A2663 /* CTMultiDelegateManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTMultiDelegateManager.m; sourceTree = ""; }; @@ -1471,6 +1476,9 @@ 6BA3B2E72B07E207004E834B /* CTTriggersMatcher+Tests.m */, 6B4A0F902B45EF6D00A42C6D /* CTInAppTriggerManagerTest.m */, 6BB778CF2BEE4C3400A41628 /* CTNotificationActionTest.m */, + 4806346E2CEB620400E39E9B /* CTInAppDisplayViewControllerTests.m */, + 6B453EF72CF621E3003C7A89 /* CTInAppDisplayViewControllerMock.h */, + 6B453EF82CF621E3003C7A89 /* CTInAppDisplayViewControllerMock.m */, ); path = InApps; sourceTree = ""; @@ -2525,6 +2533,7 @@ 6B32A0A32B99EA9D009ADC57 /* CTCustomTemplateBuilderTest.m in Sources */, 6B9E95B52C29C2F40002D557 /* NSFileManagerMock.m in Sources */, 6A7BB8DC29E47CFF00651584 /* CTVarTest.m in Sources */, + 6B453EF92CF621E3003C7A89 /* CTInAppDisplayViewControllerMock.m in Sources */, 6B32A0AD2B9DBE31009ADC57 /* CTTemplatePresenterMock.m in Sources */, 6A2E0B9129CCCC8600FCEA5F /* ContentMergerTest.m in Sources */, 6A2E0B9529D49D0200FCEA5F /* CTVariables+Tests.m in Sources */, @@ -2532,6 +2541,7 @@ 6A2E0B9829D49D5100FCEA5F /* CTVarCacheMock.m in Sources */, 4EAF05022A495DD5009D9D61 /* CleverTapInstanceTests.m in Sources */, 6BB778D22BF267B600A41628 /* CTTemplateContextTest.m in Sources */, + 4806346F2CEB620400E39E9B /* CTInAppDisplayViewControllerTests.m in Sources */, 6A2E0B9329D0A5CF00FCEA5F /* CTVariablesTest.m in Sources */, D02AC2DB276044F70031C1BE /* CleverTapSDKTests.m in Sources */, 32394C2129FA264B00956058 /* CTPreferencesTest.m in Sources */, diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index 4f6b5099..f2a2eba4 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -236,6 +236,8 @@ extern NSString *CLTAP_PROFILE_IDENTITY_KEY; #define CLTAP_INAPP_HTML_SPLIT @"\"##Vars##\"" #define CLTAP_INAPP_IMAGE_INTERSTITIAL_HTML_NAME @"image_interstitial" +#define CLTAP_URL_PARAM_DL_SEPARATOR @"__dl__" + #pragma mark Constants for persisting system data #define CLTAP_SYS_CARRIER @"sysCarrier" #define CLTAP_SYS_CC @"sysCountryCode" diff --git a/CleverTapSDK/CTInAppDisplayViewController.m b/CleverTapSDK/CTInAppDisplayViewController.m index d714f5c8..ca1027d4 100644 --- a/CleverTapSDK/CTInAppDisplayViewController.m +++ b/CleverTapSDK/CTInAppDisplayViewController.m @@ -315,6 +315,23 @@ - (void)handleButtonClickFromIndex:(int)index { - (void)triggerInAppAction:(CTNotificationAction *)action callToAction:(NSString *)callToAction buttonId:(NSString *)buttonId { NSMutableDictionary *extras = [NSMutableDictionary new]; + + if (action.type == CTInAppActionTypeOpenURL) { + NSString *urlString = [action.actionURL absoluteString]; + NSMutableDictionary *mutableParams = [CTInAppUtils getParametersFromURL:urlString]; + + if (mutableParams[@"params"]) { + extras = [mutableParams[@"params"] mutableCopy]; + + // Use the url from the deeplink to update the action if such is set + if (mutableParams[@"deeplink"]) { + action = [[CTNotificationAction alloc] initWithOpenURL:mutableParams[@"deeplink"]]; + } + } + } + + // callToAction, buttonId and notification id take precedence over + // the URL parameters if those have been set in the URL if (callToAction) { extras[CLTAP_PROP_WZRK_CTA] = callToAction; } diff --git a/CleverTapSDK/CTInAppUtils.h b/CleverTapSDK/CTInAppUtils.h index 6c67df35..f34165c1 100644 --- a/CleverTapSDK/CTInAppUtils.h +++ b/CleverTapSDK/CTInAppUtils.h @@ -31,7 +31,13 @@ typedef NS_ENUM(NSUInteger, CTInAppActionType){ + (NSString * _Nonnull)inAppTypeString:(CTInAppType)type; + (CTInAppActionType)inAppActionTypeFromString:(NSString *_Nonnull)type; + (NSString * _Nonnull)inAppActionTypeString:(CTInAppActionType)type; -+ (NSBundle *_Nullable)bundle; -+ (NSString *_Nullable)getXibNameForControllerName:(NSString *_Nonnull)controllerName; ++ (NSBundle * _Nullable)bundle; ++ (NSString * _Nullable)getXibNameForControllerName:(NSString * _Nonnull)controllerName; + /** + * Extracts the parameters from the URL and extracts the deeplink from the call to action if applicable. + * @param url The URL to process. + * @return Returns a dictionary with "deeplink" and "params" keys holding the respective values. + */ ++ (NSMutableDictionary * _Nonnull)getParametersFromURL:(NSString * _Nonnull)url; @end diff --git a/CleverTapSDK/CTInAppUtils.m b/CleverTapSDK/CTInAppUtils.m index 8fab2616..d88406a4 100644 --- a/CleverTapSDK/CTInAppUtils.m +++ b/CleverTapSDK/CTInAppUtils.m @@ -124,4 +124,35 @@ + (NSString *)getXibNameForControllerName:(NSString *)controllerName { #endif } ++ (NSMutableDictionary *)getParametersFromURL:(NSString *)urlString { + NSMutableDictionary *mutableParams = [[NSMutableDictionary alloc] init]; + // Try to extract the parameters from the URL and overrite default dl if applicable + NSMutableDictionary *params = [[NSMutableDictionary alloc] init]; + NSArray *comps = [urlString componentsSeparatedByString:@"?"]; + if ([comps count] >= 2) { + // Extract the parameters and store in params dictionary + NSString *query = comps[1]; + for (NSString *param in [query componentsSeparatedByString:@"&"]) { + NSArray *elts = [param componentsSeparatedByString:@"="]; + if ([elts count] < 2) continue; + params[elts[0]] = [elts[1] stringByRemovingPercentEncoding]; + } + + // Check for wzrk_c2a key, if present update its value after parsing with __dl__ + NSString *c2a = params[CLTAP_PROP_WZRK_CTA]; + if (c2a) { + c2a = [c2a stringByRemovingPercentEncoding]; + NSArray *parts = [c2a componentsSeparatedByString:CLTAP_URL_PARAM_DL_SEPARATOR]; + if (parts && [parts count] == 2) { + params[CLTAP_PROP_WZRK_CTA] = parts[0]; + mutableParams[@"deeplink"] = [NSURL URLWithString:parts[1]]; + } + } + + mutableParams[@"params"] = [params mutableCopy]; + } + + return mutableParams; +} + @end diff --git a/CleverTapSDK/CleverTapJSInterface.m b/CleverTapSDK/CleverTapJSInterface.m index 47aa13b0..3abeede8 100644 --- a/CleverTapSDK/CleverTapJSInterface.m +++ b/CleverTapSDK/CleverTapJSInterface.m @@ -106,6 +106,14 @@ - (void)triggerInAppAction:(NSDictionary *)actionJson callToAction:(NSString *)c return; } + // Check for NSNull in case null is passed from the WebView message + if ([callToAction isKindOfClass:[NSNull class]]) { + callToAction = nil; + } + if ([buttonId isKindOfClass:[NSNull class]]) { + buttonId = nil; + } + CTNotificationAction *action = [[CTNotificationAction alloc] initWithJSON:actionJson]; if (action && !action.error) { [self.controller triggerInAppAction:action callToAction:callToAction buttonId:buttonId]; diff --git a/CleverTapSDK/InApps/CTInAppHTMLViewController.m b/CleverTapSDK/InApps/CTInAppHTMLViewController.m index fcdb4859..4f65aca0 100644 --- a/CleverTapSDK/InApps/CTInAppHTMLViewController.m +++ b/CleverTapSDK/InApps/CTInAppHTMLViewController.m @@ -230,34 +230,18 @@ - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigati return; } - NSMutableDictionary *mutableParams = [NSMutableDictionary new]; NSString *urlString = [navigationAction.request.URL absoluteString]; NSURL *dl = [NSURL URLWithString:urlString]; + NSMutableDictionary *mutableParams = [CTInAppUtils getParametersFromURL:urlString]; - // Try to extract the parameters from the URL and overrite default dl if applicable - NSMutableDictionary *params = [[NSMutableDictionary alloc] init]; - NSArray *comps = [urlString componentsSeparatedByString:@"?"]; - if ([comps count] >= 2) { - NSString *query = comps[1]; - for (NSString *param in [query componentsSeparatedByString:@"&"]) { - NSArray *elts = [param componentsSeparatedByString:@"="]; - if ([elts count] < 2) continue; - params[elts[0]] = [elts[1] stringByRemovingPercentEncoding]; - }; - mutableParams = [params mutableCopy]; - NSString *c2a = params[CLTAP_PROP_WZRK_CTA]; - if (c2a) { - c2a = [c2a stringByRemovingPercentEncoding]; - NSArray *parts = [c2a componentsSeparatedByString:@"__dl__"]; - if (parts && [parts count] == 2) { - dl = [NSURL URLWithString:parts[1]]; - mutableParams[CLTAP_PROP_WZRK_CTA] = parts[0]; - } - } + // Use the url from the callToAction param to update action + if (mutableParams[@"deeplink"]) { + dl = mutableParams[@"deeplink"]; } + if (self.delegate && [self.delegate respondsToSelector:@selector(handleNotificationAction:forNotification:withExtras:)]) { CTNotificationAction *action = [[CTNotificationAction alloc] initWithOpenURL:dl]; - [self.delegate handleNotificationAction:action forNotification:self.notification withExtras:mutableParams]; + [self.delegate handleNotificationAction:action forNotification:self.notification withExtras:mutableParams[@"params"]]; } [self hide:YES]; decisionHandler(WKNavigationActionPolicyCancel); diff --git a/CleverTapSDKTests/InApps/CTInAppDisplayViewControllerMock.h b/CleverTapSDKTests/InApps/CTInAppDisplayViewControllerMock.h new file mode 100644 index 00000000..db0a0479 --- /dev/null +++ b/CleverTapSDKTests/InApps/CTInAppDisplayViewControllerMock.h @@ -0,0 +1,18 @@ +// +// CTInAppDisplayViewControllerMock.h +// CleverTapSDKTests +// +// Created by Nikola Zagorchev on 26.11.24. +// Copyright © 2024 CleverTap. All rights reserved. +// + +#import +#import "CTInAppDisplayViewController.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface CTInAppDisplayViewControllerMock : CTInAppDisplayViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/CleverTapSDKTests/InApps/CTInAppDisplayViewControllerMock.m b/CleverTapSDKTests/InApps/CTInAppDisplayViewControllerMock.m new file mode 100644 index 00000000..1fc71833 --- /dev/null +++ b/CleverTapSDKTests/InApps/CTInAppDisplayViewControllerMock.m @@ -0,0 +1,19 @@ +// +// CTInAppDisplayViewControllerMock.m +// CleverTapSDKTests +// +// Created by Nikola Zagorchev on 26.11.24. +// Copyright © 2024 CleverTap. All rights reserved. +// + +#import "CTInAppDisplayViewControllerMock.h" + +@implementation CTInAppDisplayViewControllerMock + +- (void)show:(BOOL)animated { +} + +- (void)hide:(BOOL)animated { +} + +@end diff --git a/CleverTapSDKTests/InApps/CTInAppDisplayViewControllerTests.m b/CleverTapSDKTests/InApps/CTInAppDisplayViewControllerTests.m new file mode 100644 index 00000000..4225c90c --- /dev/null +++ b/CleverTapSDKTests/InApps/CTInAppDisplayViewControllerTests.m @@ -0,0 +1,140 @@ +// +// CTInAppDisplayViewControllerTests.m +// CleverTapSDKTests +// +// Created by Nishant Kumar on 18/11/24. +// Copyright © 2024 CleverTap. All rights reserved. +// + +#import +#import "CTInAppDisplayViewControllerMock.h" +#import "CTInAppNotificationDisplayDelegateMock.h" + +@interface CTInAppDisplayViewControllerTests : XCTestCase + +@property (nonatomic, strong) CTInAppDisplayViewControllerMock *viewController; +@property (nonatomic, strong) CTInAppNotification *inAppNotification; + +@end + +@implementation CTInAppDisplayViewControllerTests + +- (void)setUp { + [super setUp]; + + NSDictionary *inApp = @{ + @"ti": @1 + }; + self.inAppNotification = [[CTInAppNotification alloc] initWithJSON:inApp]; + self.viewController = [[CTInAppDisplayViewControllerMock alloc] initWithNotification:self.inAppNotification]; +} + +- (void)tearDown { + self.viewController = nil; + + [super tearDown]; +} + +#pragma mark triggerInAppAction Tests + +- (void)testAddURLParamsOnly { + // triggerAction should add url parameters only in extras dictionary + // if callToAction and buttonId is not present. + NSURL *url = [NSURL URLWithString:@"https://clevertap.com?param1=value1¶m2=value2"]; + NSDictionary *expectedExtras = @{ + @"wzrk_id": @"", + @"param1": @"value1", + @"param2": @"value2" + }; + + CTNotificationAction *action = [[CTNotificationAction alloc] initWithOpenURL:url]; + + CTInAppNotificationDisplayDelegateMock *delegate = [[CTInAppNotificationDisplayDelegateMock alloc] init]; + [delegate setHandleNotificationAction:^(CTNotificationAction *action, CTInAppNotification *notification, NSDictionary *extras) { + XCTAssertEqualObjects(url, action.actionURL); + XCTAssertEqualObjects(expectedExtras, extras); + }]; + self.viewController.delegate = delegate; + // Trigger the action + [self.viewController triggerInAppAction:action callToAction:nil buttonId:nil]; +} + +- (void)testAddURLParamsAndC2A { + // triggerAction should add url parameters along with callToAction and buttonId + // in extras dictionary. + NSURL *url = [NSURL URLWithString:@"https://clevertap.com?param1=value1¶m2=value2"]; + NSString *callToAction = @"Test CTA"; + NSString *buttonId = @"button1"; + NSDictionary *expectedExtras = @{ + @"wzrk_id": @"", + @"wzrk_c2a": callToAction, + @"button_id": buttonId, + @"param1": @"value1", + @"param2": @"value2" + }; + + CTNotificationAction *action = [[CTNotificationAction alloc] initWithOpenURL:url]; + + CTInAppNotificationDisplayDelegateMock *delegate = [[CTInAppNotificationDisplayDelegateMock alloc] init]; + [delegate setHandleNotificationAction:^(CTNotificationAction *action, CTInAppNotification *notification, NSDictionary *extras) { + XCTAssertEqualObjects(url, action.actionURL); + XCTAssertEqualObjects(expectedExtras, extras); + }]; + self.viewController.delegate = delegate; + // Trigger the action + [self.viewController triggerInAppAction:action callToAction:callToAction buttonId:buttonId]; +} + +- (void)testC2AParamsParseFromDL { + // triggerAction should parse c2a url params with __dl__ data + // when callToAction is not provided. + NSURL *url = [NSURL URLWithString:@"https://clevertap.com?wzrk_c2a=c2aParam__dl__https%3A%2F%2Fdeeplink.com%3Fparam1%3Dasd%26param2%3Dvalue2&asd=value"]; + + NSString *buttonId = @"button1"; + NSDictionary *expectedExtras = @{ + @"wzrk_id": @"", + @"wzrk_c2a": @"c2aParam", + @"button_id": buttonId, + @"asd": @"value" + }; + NSURL *expectedURL = [NSURL URLWithString:@"https://deeplink.com?param1=asd¶m2=value2"]; + + CTNotificationAction *action = [[CTNotificationAction alloc] initWithOpenURL:url]; + + CTInAppNotificationDisplayDelegateMock *delegate = [[CTInAppNotificationDisplayDelegateMock alloc] init]; + [delegate setHandleNotificationAction:^(CTNotificationAction *action, CTInAppNotification *notification, NSDictionary *extras) { + XCTAssertEqualObjects(expectedURL, action.actionURL); + XCTAssertEqualObjects(expectedExtras, extras); + }]; + self.viewController.delegate = delegate; + // Trigger the action + [self.viewController triggerInAppAction:action callToAction:nil buttonId:buttonId]; +} + +- (void)testC2AParamsDoesNotParseFromDL { + // triggerAction does not parse c2a url params with __dl__ data when callToAction + // is provided, wzrk_c2a should have callToAction value only if provided. + NSURL *url = [NSURL URLWithString:@"https://clevertap.com?wzrk_c2a=c2aParam__dl__https%3A%2F%2Fdeeplink.com%3Fparam1%3Dasd%26param2%3Dvalue2&asd=value"]; + NSString *callToAction = @"Test CTA"; + NSString *buttonId = @"button1"; + NSDictionary *expectedExtras = @{ + @"wzrk_id": @"", + @"wzrk_c2a": callToAction, + @"button_id": buttonId, + @"asd": @"value" + }; + NSURL *expectedURL = [NSURL URLWithString:@"https://deeplink.com?param1=asd¶m2=value2"]; + + CTNotificationAction *action = [[CTNotificationAction alloc] initWithOpenURL:url]; + + CTInAppNotificationDisplayDelegateMock *delegate = [[CTInAppNotificationDisplayDelegateMock alloc] init]; + [delegate setHandleNotificationAction:^(CTNotificationAction *action, CTInAppNotification *notification, NSDictionary *extras) { + XCTAssertEqualObjects(expectedURL, action.actionURL); + XCTAssertEqualObjects(expectedExtras, extras); + }]; + self.viewController.delegate = delegate; + // Trigger the action + [self.viewController triggerInAppAction:action callToAction:callToAction buttonId:buttonId]; +} + +@end