diff --git a/CHANGELOG.md b/CHANGELOG.md index b13776c9..d39dfcae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,20 @@ # Change Log All notable changes to this project will be documented in this file. +### [Version 7.0.3](https://github.com/CleverTap/clevertap-ios-sdk/releases/tag/7.0.3) (November 29, 2024) + +#### Added +- Adds a method `setCredentials` for setting custom handshake domains. +- Adds support for previewing in-apps created through the new dashboard advanced builder. +- Adds parsing of urls for `open-url` action to track parameters in the url for `Notification Clicked` events in HTML in-app messages. +- Adds support for `promptForPushPermission` method in JS Interface and HTML in-apps. + +#### Fixed +- Mitigates a potential crash related to the `CTValidationResultStack` class. +- Mitigates a potential crash on `[CTInAppHTMLViewController hideFromWindow:]`. +- Changes campaign triggering evaluation of event names, event properties, and profile properties to ignore letter case and whitespace. +- Fixes an issue where the `wzrk_c2a` value is passed as null to backend when we receive null for `callToAction` value in a webView message handler. + ### [Version 7.0.2](https://github.com/CleverTap/clevertap-ios-sdk/releases/tag/7.0.2) (October 10, 2024) #### Added diff --git a/CleverTapSDK.xcodeproj/project.pbxproj b/CleverTapSDK.xcodeproj/project.pbxproj index 46b1ca5e..968b6a99 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,8 @@ 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 */; }; + 6B453EFE2CF74BE2003C7A89 /* CTEventAdapterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B453EFD2CF74BE2003C7A89 /* CTEventAdapterTest.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 +772,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 +915,9 @@ 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 = ""; }; + 6B453EFD2CF74BE2003C7A89 /* CTEventAdapterTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTEventAdapterTest.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 +1478,10 @@ 6BA3B2E72B07E207004E834B /* CTTriggersMatcher+Tests.m */, 6B4A0F902B45EF6D00A42C6D /* CTInAppTriggerManagerTest.m */, 6BB778CF2BEE4C3400A41628 /* CTNotificationActionTest.m */, + 6B453EFD2CF74BE2003C7A89 /* CTEventAdapterTest.m */, + 4806346E2CEB620400E39E9B /* CTInAppDisplayViewControllerTests.m */, + 6B453EF72CF621E3003C7A89 /* CTInAppDisplayViewControllerMock.h */, + 6B453EF82CF621E3003C7A89 /* CTInAppDisplayViewControllerMock.m */, ); path = InApps; sourceTree = ""; @@ -2525,6 +2536,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,10 +2544,12 @@ 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 */, 6BD334F02AF545C80099E33E /* CTInAppStoreTest.m in Sources */, + 6B453EFE2CF74BE2003C7A89 /* CTEventAdapterTest.m in Sources */, 32394C1F29FA251E00956058 /* CTEventBuilderTest.m in Sources */, 6B12F7692C9466460045D743 /* CTJsonTemplateProducerTest.m in Sources */, 6BB778D02BEE4C3400A41628 /* CTNotificationActionTest.m in Sources */, diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index c1f91ae1..f2a2eba4 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -231,10 +231,13 @@ 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" +#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 2c699215..ca1027d4 100644 --- a/CleverTapSDK/CTInAppDisplayViewController.m +++ b/CleverTapSDK/CTInAppDisplayViewController.m @@ -196,17 +196,30 @@ - (void)showFromWindow:(BOOL)animated { } - (void)hideFromWindow:(BOOL)animated { + [self hideFromWindow:animated withCompletion:nil]; +} + +- (void)hideFromWindow:(BOOL)animated withCompletion:(void (^)(void))completion { + __weak typeof(self) weakSelf = self; void (^completionBlock)(void) = ^ { - [self.window removeFromSuperview]; - self.window = nil; - if (self.delegate && [self.delegate respondsToSelector:@selector(notificationDidDismiss:fromViewController:)]) { - [self.delegate notificationDidDismiss:self.notification fromViewController:self]; + if (!weakSelf) { + return; + } + if (weakSelf.window) { + [weakSelf.window removeFromSuperview]; + weakSelf.window = nil; + } + if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(notificationDidDismiss:fromViewController:)]) { + [weakSelf.delegate notificationDidDismiss:weakSelf.notification fromViewController:weakSelf]; + } + if (completion) { + completion(); } }; if (animated) { [UIView animateWithDuration:0.25 animations:^{ - self.window.alpha = 0; + weakSelf.window.alpha = 0; } completion:^(BOOL finished) { completionBlock(); }]; @@ -216,7 +229,6 @@ - (void)hideFromWindow:(BOOL)animated { } } - #pragma mark - CTInAppPassThroughViewDelegate - (void)viewWillPassThroughTouch { @@ -303,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/CTInAppDisplayViewControllerPrivate.h b/CleverTapSDK/CTInAppDisplayViewControllerPrivate.h index 6cb54f76..3d09ffba 100644 --- a/CleverTapSDK/CTInAppDisplayViewControllerPrivate.h +++ b/CleverTapSDK/CTInAppDisplayViewControllerPrivate.h @@ -22,6 +22,7 @@ - (void)showFromWindow:(BOOL)animated; - (void)hideFromWindow:(BOOL)animated; +- (void)hideFromWindow:(BOOL)animated withCompletion:(void (^)(void))completion; - (void)tappedDismiss; - (void)buttonTapped:(UIButton*)button; 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/CTPlistInfo.h b/CleverTapSDK/CTPlistInfo.h index 3c2b322e..44347ffc 100644 --- a/CleverTapSDK/CTPlistInfo.h +++ b/CleverTapSDK/CTPlistInfo.h @@ -21,5 +21,5 @@ - (void)setCredentialsWithAccountID:(NSString * _Nonnull)accountID token:(NSString * _Nonnull)token region:(NSString * _Nullable)region; - (void)setCredentialsWithAccountID:(NSString * _Nonnull)accountID token:(NSString * _Nonnull)token proxyDomain:(NSString * _Nonnull)proxyDomain; - (void)setCredentialsWithAccountID:(NSString * _Nonnull)accountID token:(NSString * _Nonnull)token proxyDomain:(NSString * _Nonnull)proxyDomain spikyProxyDomain:(NSString * _Nullable)spikyProxyDomain; - +- (void)setCredentialsWithAccountID:(NSString * _Nonnull)accountID token:(NSString * _Nonnull)token proxyDomain:(NSString * _Nonnull)proxyDomain spikyProxyDomain:(NSString * _Nullable)spikyProxyDomain handshakeDomain:(NSString* _Nonnull)handshakeDomain; @end diff --git a/CleverTapSDK/CTPlistInfo.m b/CleverTapSDK/CTPlistInfo.m index 5c67dfa2..ce0ceff7 100644 --- a/CleverTapSDK/CTPlistInfo.m +++ b/CleverTapSDK/CTPlistInfo.m @@ -121,6 +121,14 @@ - (void)setCredentialsWithAccountID:(NSString * _Nonnull)accountID token:(NSStri _spikyProxyDomain = spikyProxyDomain; } +- (void)setCredentialsWithAccountID:(NSString * _Nonnull)accountID token:(NSString * _Nonnull)token proxyDomain:(NSString * _Nonnull)proxyDomain spikyProxyDomain:(NSString * _Nullable)spikyProxyDomain handshakeDomain:(NSString*)handshakeDomain { + _accountId = accountID; + _accountToken = token; + _proxyDomain = proxyDomain; + _spikyProxyDomain = spikyProxyDomain; + _handshakeDomain = handshakeDomain; +} + - (void)setEncryption:(NSString *)encryptionLevel { if (encryptionLevel && [encryptionLevel isEqualToString:@"0"]) { _encryptionLevel = CleverTapEncryptionNone; diff --git a/CleverTapSDK/CTUtils.h b/CleverTapSDK/CTUtils.h index 26a1b247..6e3c1f7a 100644 --- a/CleverTapSDK/CTUtils.h +++ b/CleverTapSDK/CTUtils.h @@ -15,4 +15,14 @@ + (NSNumber * _Nullable)numberFromString:(NSString * _Nullable)string; + (NSNumber * _Nullable)numberFromString:(NSString * _Nullable)string withLocale:(NSLocale * _Nullable)locale; +/** + * Get the CT normalized version of an event or a property name. + */ ++ (NSString * _Nullable)getNormalizedName:(NSString * _Nullable)name; + +/** + * Check if two event/property names are equal with applied CT normalization + */ ++ (BOOL)areEqualNormalizedName:(NSString * _Nullable)firstName andName:(NSString * _Nullable)secondName; + @end diff --git a/CleverTapSDK/CTUtils.m b/CleverTapSDK/CTUtils.m index 14f49047..92500f14 100644 --- a/CleverTapSDK/CTUtils.m +++ b/CleverTapSDK/CTUtils.m @@ -142,4 +142,34 @@ + (NSNumber * _Nullable)numberFromString:(NSString * _Nullable)string withLocale return nil; } ++ (NSString * _Nullable)getNormalizedName:(NSString * _Nullable)name { + if (name) { + // Lowercase with English locale for consistent behavior with the backend + // and across different device locales. + NSString *normalizedName = [name stringByReplacingOccurrencesOfString:@" " withString:@""]; + NSLocale *englishLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; + normalizedName = [normalizedName lowercaseStringWithLocale:englishLocale]; + normalizedName = [normalizedName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + return normalizedName; + } + + return nil; +} + ++ (BOOL)areEqualNormalizedName:(NSString * _Nullable)firstName + andName:(NSString * _Nullable)secondName { + if (firstName == nil && secondName == nil) { + return YES; + } + + if (firstName == nil || secondName == nil) { + return NO; + } + + NSString *normalizedFirstName = [CTUtils getNormalizedName:firstName]; + NSString *normalizedSecondName = [CTUtils getNormalizedName:secondName]; + + return [normalizedFirstName isEqualToString:normalizedSecondName]; +} + @end 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 diff --git a/CleverTapSDK/CTValidator.m b/CleverTapSDK/CTValidator.m index a5329611..c3c5c070 100644 --- a/CleverTapSDK/CTValidator.m +++ b/CleverTapSDK/CTValidator.m @@ -2,6 +2,7 @@ #import "CTValidationResult.h" #import "CTConstants.h" #import "CTKnownProfileFields.h" +#import "CTUtils.h" static const int kMaxKeyChars = 120; static const int kMaxValueChars = 1024; @@ -231,7 +232,7 @@ + (BOOL)isRestrictedEventName:(NSString *)name { NSArray *restrictedNames = @[@"Notification Sent", @"Notification Viewed", @"Notification Clicked", @"UTM Visited", @"App Launched", @"Stayed", @"App Uninstalled", @"wzrk_d", @"wzrk_fetch", @"SCCampaignOptOut", CLTAP_GEOFENCE_ENTERED_EVENT_NAME, CLTAP_GEOFENCE_EXITED_EVENT_NAME]; for (NSString *x in restrictedNames) - if ([name.lowercaseString isEqualToString:x.lowercaseString]) { + if ([CTUtils areEqualNormalizedName:name andName:x]) { // The event name is restricted CTValidationResult *error = [[CTValidationResult alloc] init]; [error setErrorCode:513]; @@ -244,7 +245,7 @@ + (BOOL)isRestrictedEventName:(NSString *)name { + (BOOL)isDiscaredEventName:(NSString *)name { for (NSString *x in discardedEvents) - if ([name.lowercaseString isEqualToString:x.lowercaseString]) { + if ([CTUtils areEqualNormalizedName:name andName:x]) { // The event name is discarded CTValidationResult *error = [[CTValidationResult alloc] init]; [error setErrorCode:513]; diff --git a/CleverTapSDK/CleverTap.h b/CleverTapSDK/CleverTap.h index d3a4a100..3ea4e882 100644 --- a/CleverTapSDK/CleverTap.h +++ b/CleverTapSDK/CleverTap.h @@ -297,6 +297,24 @@ typedef void (^CleverTapFetchInAppsBlock)(BOOL success); */ + (void)setCredentialsWithAccountID:(NSString * _Nonnull)accountID token:(NSString * _Nonnull)token proxyDomain:(NSString * _Nonnull)proxyDomain spikyProxyDomain:(NSString * _Nonnull)spikyProxyDomain; +/*! + @method + + @abstract + Sets the CleverTap AccountID, token, proxy domain URL for APIs and spiky proxy domain URL for push impression APIs + +@discussion +Sets the CleverTap account credentials and proxy domain URL. Once the default shared instance is intialized subsequent calls will be ignored. +Only has effect on the default shared instance. + +@param accountID the CleverTap account id +@param token the CleverTap account token +@param proxyDomain the domain of the proxy server eg: example.com or subdomain.example.com +@param spikyProxyDomain the domain of the proxy server for push impression eg: example.com or subdomain.example.com +@param handshakeDomain the domain to be used for clevertap handshake + */ ++ (void)setCredentialsWithAccountID:(NSString * _Nonnull)accountID token:(NSString * _Nonnull)token proxyDomain:(NSString * _Nonnull)proxyDomain spikyProxyDomain:(NSString * _Nonnull)spikyProxyDomain handshakeDomain:(NSString * _Nonnull)handshakeDomain; + /*! @method diff --git a/CleverTapSDK/CleverTap.m b/CleverTapSDK/CleverTap.m index 806a7244..064e8b47 100644 --- a/CleverTapSDK/CleverTap.m +++ b/CleverTapSDK/CleverTap.m @@ -393,6 +393,8 @@ + (nullable instancetype)_sharedInstanceWithCleverTapID:(NSString *)cleverTapID _defaultInstanceConfig = [[CleverTapInstanceConfig alloc] initWithAccountId:_plistInfo.accountId accountToken:_plistInfo.accountToken proxyDomain:_plistInfo.proxyDomain spikyProxyDomain:_plistInfo.spikyProxyDomain isDefaultInstance:YES]; } else if (_plistInfo.proxyDomain.length > 0) { _defaultInstanceConfig = [[CleverTapInstanceConfig alloc] initWithAccountId:_plistInfo.accountId accountToken:_plistInfo.accountToken proxyDomain:_plistInfo.proxyDomain isDefaultInstance:YES]; + } else if (_plistInfo.handshakeDomain.length > 0) { + _defaultInstanceConfig = [[CleverTapInstanceConfig alloc] initWithAccountId:_plistInfo.accountId accountToken:_plistInfo.accountToken handshakeDomain:_plistInfo.handshakeDomain isDefaultInstance:YES]; } else { _defaultInstanceConfig = [[CleverTapInstanceConfig alloc] initWithAccountId:_plistInfo.accountId accountToken:_plistInfo.accountToken accountRegion:_plistInfo.accountRegion isDefaultInstance:YES]; } @@ -3298,6 +3300,19 @@ + (void)setCredentialsWithAccountID:(NSString *)accountID token:(NSString *)toke [_plistInfo setCredentialsWithAccountID:accountID token:token proxyDomain:proxyDomain spikyProxyDomain:finalSpikyProxyDomain]; } ++ (void)setCredentialsWithAccountID:(NSString *)accountID token:(NSString *)token proxyDomain:(NSString *)proxyDomain spikyProxyDomain:(NSString *)spikyProxyDomain handshakeDomain:(NSString *)handshakeDomain { + [self _setCredentialsWithAccountID:accountID token:token proxyDomain:proxyDomain]; + + NSString *finalSpikyProxyDomain; + if (spikyProxyDomain != nil && ![spikyProxyDomain isEqualToString:@""]) { + finalSpikyProxyDomain = [spikyProxyDomain stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + if (finalSpikyProxyDomain.length <= 0) { + finalSpikyProxyDomain = nil; + } + } + [_plistInfo setCredentialsWithAccountID:accountID token:token proxyDomain:proxyDomain spikyProxyDomain:finalSpikyProxyDomain handshakeDomain:handshakeDomain]; +} + + (void)enablePersonalization { [self setPersonalizationEnabled:true]; } diff --git a/CleverTapSDK/CleverTapBuildInfo.h b/CleverTapSDK/CleverTapBuildInfo.h index 2c056645..211764c8 100644 --- a/CleverTapSDK/CleverTapBuildInfo.h +++ b/CleverTapSDK/CleverTapBuildInfo.h @@ -1 +1 @@ -#define WR_SDK_REVISION @"70002" +#define WR_SDK_REVISION @"70003" diff --git a/CleverTapSDK/CleverTapInstanceConfig.m b/CleverTapSDK/CleverTapInstanceConfig.m index 4bd9c770..895b471e 100644 --- a/CleverTapSDK/CleverTapInstanceConfig.m +++ b/CleverTapSDK/CleverTapInstanceConfig.m @@ -139,6 +139,24 @@ - (instancetype)initWithAccountId:(NSString *)accountId return self; } +- (instancetype)initWithAccountId:(NSString *)accountId + accountToken:(NSString *)accountToken + handshakeDomain:(NSString *)handshakeDomain + isDefaultInstance:(BOOL)isDefault { + [self checkIfAvailableAccountId:accountId accountToken:accountToken]; + + if (self = [super init]) { + _accountId = accountId; + _accountToken = accountToken; + _handshakeDomain = handshakeDomain; + _isDefaultInstance = isDefault; + _queueLabel = [NSString stringWithFormat:@"com.clevertap.serialQueue:%@",accountId]; + + [self setupPlistData:isDefault]; + } + return self; +} + - (instancetype)initWithAccountId:(NSString *)accountId accountToken:(NSString *)accountToken proxyDomain:(NSString *)proxyDomain diff --git a/CleverTapSDK/CleverTapInstanceConfigPrivate.h b/CleverTapSDK/CleverTapInstanceConfigPrivate.h index 4d60db85..094a7e84 100644 --- a/CleverTapSDK/CleverTapInstanceConfigPrivate.h +++ b/CleverTapSDK/CleverTapInstanceConfigPrivate.h @@ -26,5 +26,10 @@ spikyProxyDomain:(NSString* _Nonnull)spikyProxyDomain isDefaultInstance:(BOOL)isDefault; +- (instancetype _Nonnull)initWithAccountId:(NSString * _Nonnull)accountId + accountToken:(NSString * _Nonnull)accountToken + handshakeDomain:(NSString * _Nonnull)handshakeDomain + isDefaultInstance:(BOOL)isDefault; + + (NSString* _Nonnull)dataArchiveFileNameWithAccountId:(NSString* _Nonnull)accountId; @end diff --git a/CleverTapSDK/CleverTapJSInterface.m b/CleverTapSDK/CleverTapJSInterface.m index 4609998e..3abeede8 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,11 @@ - (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"]) { + if (self.controller) { + [self.controller hide:NO]; + } + [cleverTap promptForPushPermission:message[@"showFallbackSettings"]]; } } @@ -100,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/CTInAppDisplayManager.m b/CleverTapSDK/InApps/CTInAppDisplayManager.m index 61c23d95..09caf2d1 100644 --- a/CleverTapSDK/InApps/CTInAppDisplayManager.m +++ b/CleverTapSDK/InApps/CTInAppDisplayManager.m @@ -738,45 +738,29 @@ - (BOOL)didHandleInAppTestFromPushNotificaton:(NSDictionary * _Nullable)notifica NSMutableDictionary *inapp = [[NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil] mutableCopy]; - - // Handle Image Interstitial InApp Test - if (inapp && [notification[CLTAP_INAPP_PREVIEW_TYPE] isEqualToString:CLTAP_INAPP_IMAGE_INTERSTITIAL_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 (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 { + if (!inapp) { CleverTapLogDebug(self.config.logLevel, @"%@: Failed to parse the inapp notification as JSON", self); return YES; } + // Handle Image Interstitial and Advanced Builder InApp Test (Preview) + NSString *inAppPreviewType = notification[CLTAP_INAPP_PREVIEW_TYPE]; + 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; + } + + 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; @@ -800,11 +784,36 @@ - (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; } +- (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 diff --git a/CleverTapSDK/InApps/CTInAppHTMLViewController.m b/CleverTapSDK/InApps/CTInAppHTMLViewController.m index a0d36943..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); @@ -510,29 +494,6 @@ - (void)showFromWindow:(BOOL)animated { } } -- (void)hideFromWindow:(BOOL)animated { - void (^completionBlock)(void) = ^ { - [self->webView.configuration.userContentController removeScriptMessageHandlerForName:@"clevertap"]; - [self.window removeFromSuperview]; - self.window = nil; - if (self.delegate && [self.delegate respondsToSelector:@selector(notificationDidDismiss:fromViewController:)]) { - [self.delegate notificationDidDismiss:self.notification fromViewController:self]; - } - }; - - if (animated) { - [UIView animateWithDuration:0.25 animations:^{ - self.window.alpha = 0; - } completion:^(BOOL finished) { - completionBlock(); - }]; - } - else { - completionBlock(); - } -} - - #pragma mark - Public - (void)show:(BOOL)animated { @@ -541,7 +502,14 @@ - (void)show:(BOOL)animated { } - (void)hide:(BOOL)animated { - [self hideFromWindow:animated]; + __weak typeof(self) weakSelf = self; + [self hideFromWindow:animated withCompletion:^{ + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + [strongSelf->webView.configuration.userContentController removeScriptMessageHandlerForName:@"clevertap"]; + }]; } @end diff --git a/CleverTapSDK/InApps/Matchers/CTEventAdapter.m b/CleverTapSDK/InApps/Matchers/CTEventAdapter.m index 301f551f..fc961ac6 100644 --- a/CleverTapSDK/InApps/Matchers/CTEventAdapter.m +++ b/CleverTapSDK/InApps/Matchers/CTEventAdapter.m @@ -8,6 +8,7 @@ #import "CTEventAdapter.h" #import "CTConstants.h" +#import "CTUtils.h" static NSDictionary *systemPropToKey; @@ -95,19 +96,41 @@ - (CTTriggerValue *)propertyValueNamed:(NSString *)name { } - (id)getActualPropertyValue:(NSString *)propertyName { - id value = self.eventProperties[propertyName]; + id value = [self getPropertyValue:propertyName]; + if (value == nil) { if ([propertyName isEqualToString:CLTAP_PROP_CAMPAIGN_ID]) { - value = self.eventProperties[CLTAP_PROP_WZRK_ID]; + value = [self getPropertyValue:CLTAP_PROP_WZRK_ID]; } else if ([propertyName isEqualToString:CLTAP_PROP_WZRK_ID]) { - value = self.eventProperties[CLTAP_PROP_CAMPAIGN_ID]; + value = [self getPropertyValue:CLTAP_PROP_CAMPAIGN_ID]; } else if ([propertyName isEqualToString:CLTAP_PROP_VARIANT]) { - value = self.eventProperties[CLTAP_PROP_WZRK_PIVOT]; + value = [self getPropertyValue:CLTAP_PROP_WZRK_PIVOT]; } else if ([propertyName isEqualToString:CLTAP_PROP_WZRK_PIVOT]) { - value = self.eventProperties[CLTAP_PROP_VARIANT]; + value = [self getPropertyValue:CLTAP_PROP_VARIANT]; } else if (systemPropToKey[propertyName]) { // Map App Fields - value = self.eventProperties[systemPropToKey[propertyName]]; + value = [self getPropertyValue:systemPropToKey[propertyName]]; + } + } + return value; +} + +- (id)getPropertyValue:(NSString *)propertyName { + id value = self.eventProperties[propertyName]; + + if (value == nil) { + // Normalize the property name + propertyName = [CTUtils getNormalizedName:propertyName]; + value = self.eventProperties[propertyName]; + } + + if (value == nil) { + // Check if event properties name are normalized equal + for (NSString *key in self.eventProperties) { + if ([CTUtils areEqualNormalizedName:key andName:propertyName]) { + value = self.eventProperties[key]; + break; + } } } return value; @@ -120,6 +143,20 @@ - (CTTriggerValue *)itemValueNamed:(NSString *)name { NSMutableArray *itemValues = [NSMutableArray new]; for (NSDictionary *item in self.items) { id value = item[name]; + if (value == nil) { + NSString *normalizedName = [CTUtils getNormalizedName:name]; + value = item[normalizedName]; + } + if (value == nil) { + // Normalize the keys in the dictionary + NSMutableDictionary *normalizedItem = [NSMutableDictionary dictionary]; + for (NSString *key in item) { + NSString *normalizedKey = [CTUtils getNormalizedName:key]; + normalizedItem[normalizedKey] = item[key]; + } + value = normalizedItem[[CTUtils getNormalizedName:name]]; + } + if (value) { [itemValues addObject:value]; } diff --git a/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m b/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m index b6366b13..7f68301a 100644 --- a/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m +++ b/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m @@ -11,6 +11,7 @@ #import "CTTriggerValue.h" #import "CTConstants.h" #import "CTTriggerEvaluator.h" +#import "CTUtils.h" @implementation CTTriggersMatcher @@ -30,9 +31,8 @@ - (BOOL)matchEventWhenTriggers:(NSArray *)whenTriggers event:(CTEventAdapter *)e } - (BOOL)match:(CTTriggerAdapter *)trigger event:(CTEventAdapter *)event { - - BOOL eventNameMatch = [[event eventName] isEqualToString:[trigger eventName]]; - BOOL profileAttrNameMatch = [event profileAttrName] != nil && [[event profileAttrName] isEqualToString:[trigger profileAttrName]]; + BOOL eventNameMatch = [CTUtils areEqualNormalizedName:[event eventName] andName:[trigger eventName]]; + BOOL profileAttrNameMatch = [event profileAttrName] != nil && [CTUtils areEqualNormalizedName:[event profileAttrName] andName:[trigger profileAttrName]]; if (!eventNameMatch && !profileAttrNameMatch) { return NO; } diff --git a/CleverTapSDK/InApps/resources/image_interstitial.html b/CleverTapSDK/InApps/resources/image_interstitial.html index 3b2d49c8..bf59a29d 100644 --- a/CleverTapSDK/InApps/resources/image_interstitial.html +++ b/CleverTapSDK/InApps/resources/image_interstitial.html @@ -1 +1,28 @@ -
+ + + + + + + +
+
+
+ +
+
+
+ + + diff --git a/CleverTapSDKTests/CTUtilsTest.m b/CleverTapSDKTests/CTUtilsTest.m index 16f12e0c..71fe4103 100644 --- a/CleverTapSDKTests/CTUtilsTest.m +++ b/CleverTapSDKTests/CTUtilsTest.m @@ -176,4 +176,23 @@ - (void)test_numberFromStringWithLocale { XCTAssertNil([CTUtils numberFromString:@"12.3" withLocale:locale]); } +- (void)testGetNormalizedName { + XCTAssertNil([CTUtils getNormalizedName:nil]); + XCTAssertEqualObjects(@"", [CTUtils getNormalizedName:@""]); + XCTAssertEqualObjects(@"event1", [CTUtils getNormalizedName:@"Event 1"]); + XCTAssertEqualObjects(@"event1", [CTUtils getNormalizedName:@"EVENT 1"]); + XCTAssertEqualObjects(@"event1", [CTUtils getNormalizedName:@"event1"]); +} + +- (void)testAreEqualNormalizedNames { + XCTAssertTrue([CTUtils areEqualNormalizedName:nil andName:nil]); + XCTAssertTrue([CTUtils areEqualNormalizedName:@"" andName:@""]); + XCTAssertTrue([CTUtils areEqualNormalizedName:@"Event 1" andName:@"Event1"]); + XCTAssertTrue([CTUtils areEqualNormalizedName:@"Event 1" andName:@"event1"]); + XCTAssertTrue([CTUtils areEqualNormalizedName:@"Event 1" andName:@"EVENT 1"]); + XCTAssertFalse([CTUtils areEqualNormalizedName:@"" andName:nil]); + XCTAssertFalse([CTUtils areEqualNormalizedName:@"Event 1" andName:nil]); + XCTAssertFalse([CTUtils areEqualNormalizedName:@"Event 1" andName:@"Event 2"]); +} + @end diff --git a/CleverTapSDKTests/InApps/CTEventAdapterTest.m b/CleverTapSDKTests/InApps/CTEventAdapterTest.m new file mode 100644 index 00000000..0d218ae5 --- /dev/null +++ b/CleverTapSDKTests/InApps/CTEventAdapterTest.m @@ -0,0 +1,195 @@ +// +// CTEventAdapterTest.m +// CleverTapSDKTests +// +// Created by Nikola Zagorchev on 27.11.24. +// Copyright © 2024 CleverTap. All rights reserved. +// + +#import +#import +#import "CTEventAdapter.h" +#import "CTConstants.h" + +@interface CTEventAdapterTest : XCTestCase + +@end + +@implementation CTEventAdapterTest + +- (void)testPropertyValueNamed { + NSString *value = @"value"; + NSDictionary *eventProperties = @{ + @"prop1": value + }; + CTEventAdapter *eventAdapter = [self eventAdapterWithProperties:eventProperties]; + XCTAssertEqualObjects(value, [[eventAdapter propertyValueNamed:@"prop1"] stringValue]); + XCTAssertEqualObjects(value, [[eventAdapter propertyValueNamed:@" prop 1 "] stringValue]); + XCTAssertEqualObjects(value, [[eventAdapter propertyValueNamed:@"Prop1"] stringValue]); + XCTAssertEqualObjects(value, [[eventAdapter propertyValueNamed:@"Prop 1"] stringValue]); + XCTAssertNil([eventAdapter propertyValueNamed:@"Prop 1 1"]); + + eventProperties = @{ + @"prop 1": value + }; + eventAdapter = [self eventAdapterWithProperties:eventProperties]; + XCTAssertEqualObjects(value, [[eventAdapter propertyValueNamed:@"prop1"] stringValue]); + XCTAssertEqualObjects(value, [[eventAdapter propertyValueNamed:@" prop 1 "] stringValue]); + XCTAssertEqualObjects(value, [[eventAdapter propertyValueNamed:@"Prop1"] stringValue]); + XCTAssertEqualObjects(value, [[eventAdapter propertyValueNamed:@"Prop 1"] stringValue]); + + eventProperties = @{ + @"prop 1": @"value1", + @"prop1": value, + @"Prop 1": @"value2" + }; + eventAdapter = [self eventAdapterWithProperties:eventProperties]; + XCTAssertEqualObjects(value, [[eventAdapter propertyValueNamed:@"prop1"] stringValue]); + XCTAssertEqualObjects(eventProperties[@"Prop 1"], [[eventAdapter propertyValueNamed:@"Prop 1"] stringValue]); + // The dictionary is unordered - the order is not the same as defined in code + NSString *firstPropertyKey = eventAdapter.eventProperties.allKeys[0]; + NSString *expectedValue = eventAdapter.eventProperties[firstPropertyKey]; + XCTAssertEqualObjects(expectedValue, [[eventAdapter propertyValueNamed:@" prop 1"] stringValue]); + + eventProperties = @{ + @"prop 1": @"value1", + @"prop 1": @"value2", + @"Prop 1": @"value3", + }; + eventAdapter = [self eventAdapterWithProperties:eventProperties]; + // The dictionary is unordered - the order is not the same as defined in code + firstPropertyKey = eventAdapter.eventProperties.allKeys[0]; + expectedValue = eventAdapter.eventProperties[firstPropertyKey]; + XCTAssertEqualObjects(expectedValue, [[eventAdapter propertyValueNamed:@"prop1"] stringValue]); +} + +- (void)testSystemPropertyValueNamed { + NSDictionary *eventProperties = @{ + CLTAP_PROP_WZRK_ID: @"wzrk_id value", + CLTAP_APP_VERSION: @"Version value", + CLTAP_SDK_VERSION: @"SDK Version value" + }; + CTEventAdapter *eventAdapter = [self eventAdapterWithProperties:eventProperties]; + XCTAssertEqualObjects(@"wzrk_id value", [[eventAdapter propertyValueNamed:CLTAP_PROP_CAMPAIGN_ID] stringValue]); + XCTAssertEqualObjects(@"Version value", [[eventAdapter propertyValueNamed:@"CT App Version"] stringValue]); + XCTAssertEqualObjects(@"SDK Version value", [[eventAdapter propertyValueNamed:@"CT SDK Version"] stringValue]); + + eventProperties = @{ + CLTAP_PROP_CAMPAIGN_ID: @"Campaign id value", + }; + eventAdapter = [self eventAdapterWithProperties:eventProperties]; + XCTAssertEqualObjects(@"Campaign id value", [[eventAdapter propertyValueNamed:CLTAP_PROP_WZRK_ID] stringValue]); + + eventProperties = @{ + CLTAP_PROP_VARIANT: @"Variant value" + }; + eventAdapter = [self eventAdapterWithProperties:eventProperties]; + XCTAssertEqualObjects(@"Variant value", [[eventAdapter propertyValueNamed:CLTAP_PROP_WZRK_PIVOT] stringValue]); + + eventProperties = @{ + CLTAP_PROP_WZRK_PIVOT: @"wzrk_pivot value", + }; + eventAdapter = [self eventAdapterWithProperties:eventProperties]; + XCTAssertEqualObjects(@"wzrk_pivot value", [[eventAdapter propertyValueNamed:CLTAP_PROP_VARIANT] stringValue]); +} + +- (void)testSystemPropertyValueNamedNormalized { + NSDictionary *eventProperties = @{ + CLTAP_PROP_WZRK_ID: @"wzrk_id value", + CLTAP_APP_VERSION: @"Version value", + }; + CTEventAdapter *eventAdapter = [self eventAdapterWithProperties:eventProperties]; + XCTAssertEqualObjects(@"wzrk_id value", [[eventAdapter propertyValueNamed:CLTAP_PROP_CAMPAIGN_ID] stringValue]); + XCTAssertEqualObjects(@"Version value", [[eventAdapter propertyValueNamed:@"CT App Version"] stringValue]); + // The system property names must be exact match + XCTAssertNil([eventAdapter propertyValueNamed:CLTAP_PROP_CAMPAIGN_ID.lowercaseString]); + XCTAssertNil([eventAdapter propertyValueNamed:@"CT App Version".lowercaseString]); + XCTAssertNil([eventAdapter propertyValueNamed:@"CTApp Version"]); + + // The property name is normalized if it matches the system property name evaluated + eventProperties = @{ + // CLTAP_PROP_WZRK_ID @"wzrk_id" + @"wzrk_ID": @"wzrk_id value", + // CLTAP_APP_VERSION @"Version" + @"version": @"Version value" + }; + eventAdapter = [self eventAdapterWithProperties:eventProperties]; + XCTAssertEqualObjects(@"wzrk_id value", [[eventAdapter propertyValueNamed:CLTAP_PROP_CAMPAIGN_ID] stringValue]); + XCTAssertEqualObjects(@"Version value", [[eventAdapter propertyValueNamed:@"CT App Version"] stringValue]); +} + +- (void)testItemValueNamed { + NSArray *items = @[ + @{ + @"productName": @"product 1", + @"price": @5.99 + }, + @{ + @"productName": @"product 2", + @"price": @5.50 + }]; + CTEventAdapter *eventAdapter = [self eventAdapterWithItems:items]; + XCTAssertEqualObjects((@[@"product 1", @"product 2"]), [[eventAdapter itemValueNamed:@"productName"] arrayValue]); + XCTAssertEqualObjects((@[@5.99, @5.50]), [[eventAdapter itemValueNamed:@"price"] arrayValue]); + XCTAssertNil([eventAdapter itemValueNamed:@"none"]); + + // Nil Items + CTEventAdapter *eventAdapterNilItems = [self eventAdapterWithItems:nil]; + XCTAssertNil([eventAdapterNilItems itemValueNamed:@"none"]); +} + +- (void)testItemValueNamedNormalized { + NSArray *items = @[ + @{ + @"productName": @"product 1" + }, + @{ + @"productName": @"product 2" + }]; + NSArray *expectedProductNames = @[@"product 1", @"product 2"]; + + CTEventAdapter *eventAdapter = [self eventAdapterWithItems:items]; + XCTAssertEqualObjects(expectedProductNames, [[eventAdapter itemValueNamed:@"productName"] arrayValue]); + XCTAssertEqualObjects(expectedProductNames, [[eventAdapter itemValueNamed:@"ProductName"] arrayValue]); + XCTAssertEqualObjects(expectedProductNames, [[eventAdapter itemValueNamed:@"product Name"] arrayValue]); + XCTAssertEqualObjects(expectedProductNames, [[eventAdapter itemValueNamed:@"Product Name"] arrayValue]); +} + +- (void)testItemValueNamedNormalizedItem { + NSArray *items = @[ + @{ + @"product Name": @"product 1" + }, + @{ + @"ProductName": @"product 2" + }, + @{ + @"Product Name": @"product 3" + }, + @{ + @"product name": @"product 4" + }, + @{ + @"product_name": @"product 5" + }]; + NSArray *expectedProductNames = @[@"product 1", @"product 2", @"product 3", @"product 4"]; + + CTEventAdapter *eventAdapter = [self eventAdapterWithItems:items]; + XCTAssertEqualObjects(expectedProductNames, [[eventAdapter itemValueNamed:@"productName"] arrayValue]); + XCTAssertEqualObjects(expectedProductNames, [[eventAdapter itemValueNamed:@"ProductName"] arrayValue]); + XCTAssertEqualObjects(expectedProductNames, [[eventAdapter itemValueNamed:@"product Name"] arrayValue]); + XCTAssertEqualObjects(expectedProductNames, [[eventAdapter itemValueNamed:@"Product Name"] arrayValue]); + + XCTAssertEqualObjects((@[@"product 5"]), [[eventAdapter itemValueNamed:@"product_name"] arrayValue]); + XCTAssertEqualObjects((@[@"product 5"]), [[eventAdapter itemValueNamed:@"Product_Name"] arrayValue]); +} + +- (CTEventAdapter *)eventAdapterWithProperties:(NSDictionary *)eventProperties { + return [[CTEventAdapter alloc] initWithEventName:@"event" eventProperties:eventProperties andLocation:kCLLocationCoordinate2DInvalid]; +} + +- (CTEventAdapter *)eventAdapterWithItems:(NSArray *)items { + return [[CTEventAdapter alloc] initWithEventName:@"event" eventProperties:@{} location:kCLLocationCoordinate2DInvalid andItems:items]; +} + +@end 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 diff --git a/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m b/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m index e66f4280..87718b0e 100644 --- a/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m +++ b/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m @@ -322,7 +322,6 @@ - (void)testEvaluateWithInApps { } - (void)testEvaluateUserAttribute { - self.helper.inAppStore.serverSideInApps = @[ @{ @"ti": @1, @@ -343,19 +342,79 @@ - (void)testEvaluateUserAttribute { }], @"profileAttrName": @"Customer Type", }] - }, - ]; + }]; + NSDictionary *profile = @{ @"Customer Type": @{ @"newValue": @"Gold", @"oldValue": @"Premium" } }; + [self.evaluationManager evaluateOnUserAttributeChange:profile]; + XCTAssertEqualObjects((@[@1]), self.evaluationManager.evaluatedServerSideInAppIdsForProfile); +} - +- (void)testEvaluateUserAttributeNormalized { + self.helper.inAppStore.serverSideInApps = @[ + @{ + @"ti": @1, + @"whenTriggers": @[@{ + @"eventProperties": @[@{ + @"propertyName": @"newValue", + @"propertyValue": @"Gold", + }], + @"profileAttrName": @"Customer Type", + }] + }]; + + NSDictionary *profile = @{ + @"CustomerType": @{ + @"newValue": @"Gold", + @"oldValue": @"Premium" + } + }; [self.evaluationManager evaluateOnUserAttributeChange:profile]; XCTAssertEqualObjects((@[@1]), self.evaluationManager.evaluatedServerSideInAppIdsForProfile); - XCTAssertNotEqualObjects((@[@2]), self.evaluationManager.evaluatedServerSideInAppIdsForProfile); + + profile = @{ + @"customer type": @{ + @"newValue": @"Gold", + @"oldValue": @"Premium" + } + }; + [self.evaluationManager evaluateOnUserAttributeChange:profile]; + XCTAssertEqualObjects((@[@1, @1]), self.evaluationManager.evaluatedServerSideInAppIdsForProfile); +} + +- (void)testEvaluateUserAttributeNormalizedMultiple { + self.helper.inAppStore.serverSideInApps = @[ + @{ + @"ti": @1, + @"whenTriggers": @[@{ + @"eventProperties": @[@{ + @"propertyName": @"newValue", + @"propertyValue": @"Gold", + }], + @"profileAttrName": @"Customer Type", + }] + }]; + + NSDictionary *profile = @{ + @"CustomerType": @{ + @"newValue": @"Gold", + @"oldValue": @"Premium" + }, + @"customer type": @{ + @"newValue": @"Gold", + @"oldValue": @"Premium" + }, + @"customerType": @{ + @"newValue": @"Gold", + @"oldValue": @"Premium" + } + }; + [self.evaluationManager evaluateOnUserAttributeChange:profile]; + XCTAssertEqualObjects((@[@1, @1, @1]), self.evaluationManager.evaluatedServerSideInAppIdsForProfile); } - (void)testEvaluateCharged { diff --git a/CleverTapSDKTests/InApps/CTTriggersMatcherTest.m b/CleverTapSDKTests/InApps/CTTriggersMatcherTest.m index df26c716..81c976af 100644 --- a/CleverTapSDKTests/InApps/CTTriggersMatcherTest.m +++ b/CleverTapSDKTests/InApps/CTTriggersMatcherTest.m @@ -146,6 +146,72 @@ - (void)testMatchEventWithoutProps { XCTAssertFalse(matchNoProps); } +- (void)testMatchEventWithNormalizedName { + NSArray *whenTriggers = @[ + @{ + @"eventName": @"event1", + @"eventProperties": @[ + ] + } + ]; + + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"Event 1" eventProperties:@{ + @"prop1": @"clevertap" + }]; + XCTAssertTrue(match); +} + +#pragma mark Profile Event + +- (void)testMatchProfileEvent { + NSArray *whenTriggers = @[ + @{ + @"eventName": @"profile1 changed", + @"profileAttrName": @"profile1", + @"eventProperties": @[ + @{ + @"propertyName": @"newValue", + @"operator": @0, + @"propertyValue": @150 + }, + @{ + @"propertyName": @"oldValue", + @"operator": @1, + @"propertyValue": @"Equals" + } + ] + } + ]; + + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + + NSDictionary *eventProperties = @{ + @"newValue": @160, + @"oldValue": @"Equals" + }; + + CTEventAdapter *eventAdapter = [[CTEventAdapter alloc] initWithEventName:@"profile1_changed" profileAttrName:@"profile1" eventProperties:eventProperties andLocation:kCLLocationCoordinate2DInvalid]; + BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers event:eventAdapter]; + XCTAssertTrue(match); + + eventAdapter = [[CTEventAdapter alloc] initWithEventName:@"profile 1_changed" profileAttrName:@"profile 1" eventProperties:eventProperties andLocation:kCLLocationCoordinate2DInvalid]; + match = [triggerMatcher matchEventWhenTriggers:whenTriggers event:eventAdapter]; + XCTAssertTrue(match); + + eventAdapter = [[CTEventAdapter alloc] initWithEventName:@"Profile 1_changed" profileAttrName:@"Profile 1" eventProperties:eventProperties andLocation:kCLLocationCoordinate2DInvalid]; + match = [triggerMatcher matchEventWhenTriggers:whenTriggers event:eventAdapter]; + XCTAssertTrue(match); + + eventAdapter = [[CTEventAdapter alloc] initWithEventName:@"profile 1_changed" profileAttrName:@"profile 1" eventProperties:eventProperties andLocation:kCLLocationCoordinate2DInvalid]; + match = [triggerMatcher matchEventWhenTriggers:whenTriggers event:eventAdapter]; + XCTAssertTrue(match); + + eventAdapter = [[CTEventAdapter alloc] initWithEventName:@"Profile_1_changed" profileAttrName:@"Profile_1" eventProperties:eventProperties andLocation:kCLLocationCoordinate2DInvalid]; + match = [triggerMatcher matchEventWhenTriggers:whenTriggers event:eventAdapter]; + XCTAssertFalse(match); +} + #pragma mark Charged Event - (void)testMatchChargedEvent { @@ -289,6 +355,44 @@ - (void)testMatchChargedEventItemArrayEquals { XCTAssertTrue(match); } +- (void)testMatchChargedEventItemArrayEqualsNormalized { + NSArray *whenTriggers = @[ + @{ + @"eventName": @"Charged", + @"eventProperties": @[ + @{ + @"propertyName": @"prop1", + @"operator": @1, + @"propertyValue": @150 + }], + @"itemProperties": @[ + @{ + @"propertyName": @"product name", + @"operator": @1, + @"propertyValue": @[@"product 1"] + }, + @{ + @"propertyName": @"price", + @"operator": @1, + @"propertyValue": @[@5.99] + }] + } + ]; + + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + + BOOL match = [triggerMatcher matchChargedEventWhenTriggers:whenTriggers details:@{ + @"Prop 1": @150, + } items:@[ + @{ + @"ProductName": @"product 1", + @"Price": @5.99 + } + ]]; + + XCTAssertTrue(match); +} + - (void)testMatchChargedEventItemArrayContains { NSArray *whenTriggers = @[ @{ @@ -326,6 +430,32 @@ - (void)testMatchChargedEventItemArrayContains { XCTAssertTrue(match); } +- (void)testMatchChargedEventItemArrayContainsNormalized { + NSArray *whenTriggers = @[ + @{ + @"eventName": @"Charged", + @"eventProperties": @[], + @"itemProperties": @[ + @{ + @"propertyName": @"product name", + @"operator": @3, + @"propertyValue": @[@"product 1", @"product 2"] + }] + } + ]; + + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + + BOOL match = [triggerMatcher matchChargedEventWhenTriggers:whenTriggers details:@{} items:@[ + @{ + @"Product Name": @"product 1", + @"price": @5.50 + } + ]]; + + XCTAssertTrue(match); +} + #pragma mark Equals - (void)testMatchEqualsPrimitives { @@ -757,6 +887,56 @@ - (void)testMatchEqualsExtectedStringWithActualString { XCTAssertFalse(match); } +- (void)testMatchEqualsPropertyNameWithNormalization { + NSArray *whenTriggers = @[ + @{ + @"eventName": @"event1", + @"eventProperties": @[ + @{ + @"propertyName": @"prop1", + @"operator": @1, + @"propertyValue": @"test" + } + ] + } + ]; + + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + + BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ + @"prop 1": @"test" + }]; + XCTAssertTrue(match); + + match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ + @"Prop 1": @"test" + }]; + XCTAssertTrue(match); + + match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"E vent1" eventProperties:@{ + @"Prop 1": @"test" + }]; + XCTAssertTrue(match); + + match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ + @"Prop.1": @"test" + }]; + XCTAssertFalse(match); + + match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ + @"Prop 1": @"test1", + @"Prop1": @"test", + }]; + XCTAssertFalse(match); + + match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ + @"Prop1": @"test1", + @"prop 1": @"test2", + @"prop1": @"test", + }]; + XCTAssertTrue(match); +} + - (void)testMatchEqualsExtectedNumberWithActualString { NSArray *whenTriggers = @[ @{ 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



+ +

diff --git a/sdk-version.txt b/sdk-version.txt index 2f963cd6..5febf260 100644 --- a/sdk-version.txt +++ b/sdk-version.txt @@ -1 +1 @@ -7.0.2 \ No newline at end of file +7.0.3 \ No newline at end of file