From 89cb56c9d81708434a8ba227506058363644fe19 Mon Sep 17 00:00:00 2001 From: Nikola Zagorchev Date: Thu, 5 Sep 2024 15:44:38 +0300 Subject: [PATCH 01/21] [MC-2003] Custom template dictionary swift bool (#370) * Fix flatten map arguments with booleans in Custom Templates * Add unit tests --- CleverTapSDK/CleverTap.m | 4 +- .../CustomTemplates/CTCustomTemplateBuilder.m | 11 ++- .../CTCustomTemplatesManagerTest.m | 71 +++++++++++++++++++ .../CustomTemplates/CTTemplateContextTest.m | 39 ++++++++++ 4 files changed, 122 insertions(+), 3 deletions(-) diff --git a/CleverTapSDK/CleverTap.m b/CleverTapSDK/CleverTap.m index 041e2a3f..4e92b627 100644 --- a/CleverTapSDK/CleverTap.m +++ b/CleverTapSDK/CleverTap.m @@ -4331,9 +4331,9 @@ - (void)syncCustomTemplates:(BOOL)isProduction { - (void)syncWithBlock:(void(^)(void))syncBlock methodName:(NSString *)methodName isProduction:(BOOL)isProduction { if (isProduction) { #if DEBUG - CleverTapLogInfo(_config.logLevel, @"%@: Calling %@: with isProduction:YES from Debug configuration/build. Use syncVariables in this case", self, methodName); + CleverTapLogInfo(_config.logLevel, @"%@: Calling %@ with isProduction:YES from Debug configuration/build. Do not use isProduction:YES in this case", self, methodName); #else - CleverTapLogInfo(_config.logLevel, @"%@: Calling %@: with isProduction:YES from Release configuration/build. Do not release this build and use with caution", self, methodName); + CleverTapLogInfo(_config.logLevel, @"%@: Calling %@ with isProduction:YES from Release configuration/build. Do not release this build and use with caution", self, methodName); #endif [self runSerialAsyncEnsureHandshake:syncBlock]; } else { diff --git a/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplateBuilder.m b/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplateBuilder.m index d7a8b19c..f7679fda 100644 --- a/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplateBuilder.m +++ b/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplateBuilder.m @@ -130,7 +130,16 @@ - (void)flatten:(NSDictionary *)map name:(NSString *)name { if ([value isKindOfClass:[NSString class]]) { [self addArgument:argName withString:value]; } else if ([value isKindOfClass:[NSNumber class]]) { - [self addArgument:argName withNumber:value]; + // If the NSNumber is a boolean, use addArgument:withBool: + // so the values are correctly mapped to the type. + // This is required so dictionary arguments with booleans defined in Swift + // have correct type and value. + // Booleans are of class __NSCFBoolean, Numbers are of class __NSCFNumber. + if ([value isKindOfClass:NSClassFromString(@"__NSCFBoolean")]) { + [self addArgument:argName withBool:[value boolValue]]; + } else { + [self addArgument:argName withNumber:value]; + } } else if ([value isKindOfClass:[NSDictionary class]]) { [self flatten:value name:argName]; } diff --git a/CleverTapSDKTests/InApps/CustomTemplates/CTCustomTemplatesManagerTest.m b/CleverTapSDKTests/InApps/CustomTemplates/CTCustomTemplatesManagerTest.m index f2c79acd..544cb79f 100644 --- a/CleverTapSDKTests/InApps/CustomTemplates/CTCustomTemplatesManagerTest.m +++ b/CleverTapSDKTests/InApps/CustomTemplates/CTCustomTemplatesManagerTest.m @@ -278,6 +278,77 @@ - (void)testSyncPayload { XCTAssertEqualObjects(syncPayload, expectedPayload); } +- (void)testSyncPayloadDictionaryBooleans { + NSMutableSet *templates = [NSMutableSet set]; + CTInAppTemplateBuilder *templateBuilder = [[CTInAppTemplateBuilder alloc] init]; + [templateBuilder setName:@"Template 1"]; + [templateBuilder addArgument:@"dictionary.booleanYes" withBool:YES]; + [templateBuilder addArgument:@"dictionary.booleanNo" withBool:NO]; + [templateBuilder addArgument:@"dictionary.number" withNumber:@1]; + [templateBuilder addArgument:@"dictionary" withDictionary:@{ + @"double": @1.0, + @"int": @0, + @"boolYes": @(YES), + @"boolNo": @(NO) + }]; + [templateBuilder setPresenter:[CTTemplatePresenterMock new]]; + [templates addObject:[templateBuilder build]]; + + CTTestTemplateProducer *producer = [[CTTestTemplateProducer alloc] initWithTemplates:templates]; + [CTCustomTemplatesManager registerTemplateProducer:producer]; + CTCustomTemplatesManager *manager = [self templatesManager]; + + NSDictionary *syncPayload = [manager syncPayload]; + NSDictionary *expectedPayload = @{ + @"type": @"templatePayload", + @"definitions": @{ + @"Template 1": @{ + @"type": TEMPLATE_TYPE, + @"vars": @{ + @"dictionary.booleanNo": @{ + @"defaultValue": @0, + @"order": @0, + @"type": @"boolean" + }, + @"dictionary.booleanYes": @{ + @"defaultValue": @1, + @"order": @1, + @"type": @"boolean" + }, + @"dictionary.boolNo": @{ + @"defaultValue": @0, + @"order": @2, + @"type": @"boolean" + }, + @"dictionary.boolYes": @{ + @"defaultValue": @1, + @"order": @3, + @"type": @"boolean" + }, + @"dictionary.double": @{ + @"defaultValue": @1.0, + @"order": @4, + @"type": @"number" + }, + @"dictionary.int": @{ + @"defaultValue": @0, + @"order": @5, + @"type": @"number" + }, + @"dictionary.number": @{ + @"defaultValue": @1, + @"order": @6, + @"type": @"number" + } + } + } + } + }; + + XCTAssertEqual([syncPayload[@"definitions"] count], 1); + XCTAssertEqualObjects(syncPayload, expectedPayload); +} + - (void)testTemplatesRegistered { NSMutableSet *templates = [NSMutableSet set]; diff --git a/CleverTapSDKTests/InApps/CustomTemplates/CTTemplateContextTest.m b/CleverTapSDKTests/InApps/CustomTemplates/CTTemplateContextTest.m index 4ce4484f..5b772f6f 100644 --- a/CleverTapSDKTests/InApps/CustomTemplates/CTTemplateContextTest.m +++ b/CleverTapSDKTests/InApps/CustomTemplates/CTTemplateContextTest.m @@ -270,6 +270,45 @@ - (void)testDictionaryArguments { XCTAssertTrue([innermostMap isEqualToDictionary:[self.templateContext dictionaryNamed:@"map.innerMap.innermostMap"]]); } +- (void)testDictionaryBoolArguments { + NSDictionary *notificationJson = @{ + @"templateName": TEMPLATE_NAME_NESTED, + @"type": @"custom-code", + @"vars": @{ + @"map.double": @(0.0), + @"map.boolFalse": @(YES), + @"map.bool": @(NO) + } + }; + CTInAppNotification *notification = [[CTInAppNotification alloc] initWithJSON:notificationJson]; + + CTInAppTemplateBuilder *templateBuilder = [[CTInAppTemplateBuilder alloc] init]; + [templateBuilder setName:TEMPLATE_NAME_NESTED]; + [templateBuilder addArgument:@"map" withDictionary:@{ + @"int": @0, + @"double": @1.0, + @"boolFalse": @(NO), + @"boolTrue": @(YES), + @"bool": @(YES) + }]; + [templateBuilder setPresenter:[CTTemplatePresenterMock new]]; + CTCustomTemplate *template = [templateBuilder build]; + + CTTemplateContext *context = [[CTTemplateContext alloc] initWithTemplate:template notification:notification andFileDownloader:self.fileDownloader]; + + NSDictionary *map = [context dictionaryNamed:@"map"]; + NSDictionary *notificationVars = notificationJson[@"vars"]; + XCTAssertEqualObjects(@0, map[@"int"]); + XCTAssertEqualObjects(notificationVars[@"map.double"], map[@"double"]); + XCTAssertEqualObjects(@YES, map[@"boolTrue"]); + XCTAssertEqualObjects(notificationVars[@"map.boolFalse"], map[@"boolFalse"]); + XCTAssertEqualObjects(@([context boolNamed:@"map.boolFalse"]), map[@"boolFalse"]); + XCTAssertEqualObjects(@([context boolNamed:@"map.boolFalse"]), @(YES)); + XCTAssertEqualObjects(notificationVars[@"map.bool"], map[@"bool"]); + XCTAssertEqualObjects(@([context boolNamed:@"map.bool"]), map[@"bool"]); + XCTAssertEqualObjects(@([context boolNamed:@"map.bool"]), @(NO)); +} + - (void)testActionsValueInDictionary { NSDictionary *actionsMap = [self.templateContext dictionaryNamed:@"map.actions"]; XCTAssertEqualObjects(VARS_ACTION_FUNCTION_NAME, actionsMap[@"function"]); From 8c5a95b98c0e19a165fc1d3b85bf3d604073e440 Mon Sep 17 00:00:00 2001 From: Nikola Zagorchev Date: Thu, 5 Sep 2024 17:45:02 +0300 Subject: [PATCH 02/21] Do not handle push on launch if Leanplum library (#371) --- CleverTapSDK/CleverTap.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CleverTapSDK/CleverTap.m b/CleverTapSDK/CleverTap.m index 4e92b627..84cf963b 100644 --- a/CleverTapSDK/CleverTap.m +++ b/CleverTapSDK/CleverTap.m @@ -1168,7 +1168,11 @@ - (void)_appEnteredForegroundWithLaunchingOptions:(NSDictionary *)launchOptions if (launchOptions && launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) { NSDictionary *notification = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]; CleverTapLogDebug(self.config.logLevel, @"%@: found push notification at launch: %@", self, notification); - [self _handlePushNotification:notification]; + if ([self.deviceInfo.library isEqualToString:@"Leanplum"]) { + CleverTapLogDebug(self.config.logLevel, @"%@: Leanplum will handle the notification: %@", self, notification); + } else { + [self _handlePushNotification:notification]; + } } #endif } From bf236a65b01707e7bf1e2f97a25723d0f7244ce6 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Wed, 11 Sep 2024 10:02:25 +0530 Subject: [PATCH 03/21] added provisions to enable custom handshake url via plists --- CleverTapSDK/CTConstants.h | 2 ++ CleverTapSDK/CTConstants.m | 1 + CleverTapSDK/CTDomainFactory.m | 5 +++++ CleverTapSDK/CTPlistInfo.h | 1 + CleverTapSDK/CTPlistInfo.m | 2 ++ CleverTapSDK/CTRequestFactory.m | 10 +++++++++- CleverTapSDK/CleverTap.m | 1 + CleverTapSDK/CleverTapInstanceConfig.h | 3 +++ CleverTapSDK/CleverTapInstanceConfig.m | 12 ++++++++++++ 9 files changed, 36 insertions(+), 1 deletion(-) diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index f5563c25..4cb2f2ec 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -3,6 +3,7 @@ extern NSString *const kCTApiDomain; extern NSString *const kCTNotifViewedApiDomain; extern NSString *const kHANDSHAKE_URL; +extern NSString *const kHANDSHAKE_DOMAIN_HEADER; extern NSString *const kLastSessionPing; extern NSString *const kLastSessionTime; @@ -31,6 +32,7 @@ extern NSString *const kSessionId; #define CLTAP_USE_CUSTOM_CLEVERTAP_ID_LABEL @"CleverTapUseCustomId" #define CLTAP_DISABLE_IDFV_LABEL @"CleverTapDisableIDFV" #define CLTAP_ENABLE_FILE_PROTECTION @"CleverTapEnableFileProtection" +#define CLTAP_HANDSHAKE_DOMAIN @"CleverTapHandshakeDomain" #define CLTAP_BETA_LABEL @"CleverTapBeta" #define CLTAP_SESSION_LENGTH_MINS 20 #define CLTAP_SESSION_LAST_VC_TRAIL @"last_session_vc_trail" diff --git a/CleverTapSDK/CTConstants.m b/CleverTapSDK/CTConstants.m index 4a61e130..0a7162b7 100644 --- a/CleverTapSDK/CTConstants.m +++ b/CleverTapSDK/CTConstants.m @@ -3,6 +3,7 @@ NSString *const kCTApiDomain = @"clevertap-prod.com"; NSString *const kCTNotifViewedApiDomain = @"spiky.clevertap-prod.com"; NSString *const kHANDSHAKE_URL = @"https://clevertap-prod.com/hello"; +NSString *const kHANDSHAKE_DOMAIN_HEADER =@"X-CleverTap-Handshake-Domain"; NSString *const kLastSessionPing = @"last_session_ping"; NSString *const kLastSessionTime = @"lastSessionTime"; diff --git a/CleverTapSDK/CTDomainFactory.m b/CleverTapSDK/CTDomainFactory.m index 64a3615f..e22e3b01 100644 --- a/CleverTapSDK/CTDomainFactory.m +++ b/CleverTapSDK/CTDomainFactory.m @@ -75,6 +75,11 @@ - (NSString *)loadRedirectDomain { return self.explictEndpointDomain; } } + +// if (self.config.handshakeDomain) { +// return nil; +// } + NSString *domain = nil; if (self.config.isDefaultInstance) { domain = [CTPreferences getStringForKey:[CTPreferences storageKeyWithSuffix:REDIRECT_DOMAIN_KEY config: self.config] withResetValue:[CTPreferences getStringForKey:REDIRECT_DOMAIN_KEY withResetValue:nil]]; diff --git a/CleverTapSDK/CTPlistInfo.h b/CleverTapSDK/CTPlistInfo.h index ffa177a6..3c2b322e 100644 --- a/CleverTapSDK/CTPlistInfo.h +++ b/CleverTapSDK/CTPlistInfo.h @@ -14,6 +14,7 @@ @property (nonatomic, assign, readonly) BOOL beta; @property (nonatomic, assign, readonly) BOOL disableIDFV; @property (nonatomic, assign) BOOL enableFileProtection; +@property (nonatomic, strong, readonly, nullable) NSString *handshakeDomain; @property (nonatomic, readonly) CleverTapEncryptionLevel encryptionLevel; + (instancetype _Nullable)sharedInstance; diff --git a/CleverTapSDK/CTPlistInfo.m b/CleverTapSDK/CTPlistInfo.m index c660c08d..5c67dfa2 100644 --- a/CleverTapSDK/CTPlistInfo.m +++ b/CleverTapSDK/CTPlistInfo.m @@ -94,6 +94,8 @@ - (instancetype)init { NSString *enableFileProtection = [CTPlistInfo getMetaDataForAttribute:CLTAP_ENABLE_FILE_PROTECTION]; _enableFileProtection = (enableFileProtection && [enableFileProtection isEqualToString:@"1"]); + _handshakeDomain = [CTPlistInfo getMetaDataForAttribute:CLTAP_HANDSHAKE_DOMAIN]; + NSString *encryptionLevel = [CTPlistInfo getMetaDataForAttribute:CLTAP_ENCRYPTION_LEVEL]; [self setEncryption:encryptionLevel]; } diff --git a/CleverTapSDK/CTRequestFactory.m b/CleverTapSDK/CTRequestFactory.m index 9edc1c49..251fe57e 100644 --- a/CleverTapSDK/CTRequestFactory.m +++ b/CleverTapSDK/CTRequestFactory.m @@ -12,7 +12,15 @@ @implementation CTRequestFactory + (CTRequest *_Nonnull)helloRequestWithConfig:(CleverTapInstanceConfig *_Nonnull)config { - return [[CTRequest alloc] initWithHttpMethod:@"GET" config:config params:nil url:kHANDSHAKE_URL]; + NSString *helloUrl = kHANDSHAKE_URL; + if (config.handshakeDomain) { + helloUrl = [NSString stringWithFormat:@"https://%@/hello",config.handshakeDomain]; + } + CTRequest *request = [[CTRequest alloc] initWithHttpMethod:@"GET" config:config params:nil url:helloUrl]; + if (config.handshakeDomain) { + [request.urlRequest setValue:config.handshakeDomain forHTTPHeaderField:kHANDSHAKE_DOMAIN_HEADER]; + } + return request; } + (CTRequest *_Nonnull)eventRequestWithConfig:(CleverTapInstanceConfig *_Nonnull)config params:(id _Nullable)params url:(NSString *_Nonnull)url { diff --git a/CleverTapSDK/CleverTap.m b/CleverTapSDK/CleverTap.m index 041e2a3f..51ea2335 100644 --- a/CleverTapSDK/CleverTap.m +++ b/CleverTapSDK/CleverTap.m @@ -403,6 +403,7 @@ + (nullable instancetype)_sharedInstanceWithCleverTapID:(NSString *)cleverTapID _defaultInstanceConfig.enablePersonalization = [CleverTap isPersonalizationEnabled]; _defaultInstanceConfig.logLevel = [self getDebugLevel]; _defaultInstanceConfig.enableFileProtection = _plistInfo.enableFileProtection; + _defaultInstanceConfig.handshakeDomain = _plistInfo.handshakeDomain; NSString *regionLog = (!_plistInfo.accountRegion || _plistInfo.accountRegion.length < 1) ? @"default" : _plistInfo.accountRegion; NSString *proxyDomainLog = (!_plistInfo.proxyDomain || _plistInfo.proxyDomain.length < 1) ? @"" : _plistInfo.proxyDomain; NSString *spikyProxyDomainLog = (!_plistInfo.spikyProxyDomain || _plistInfo.spikyProxyDomain.length < 1) ? @"" : _plistInfo.spikyProxyDomain; diff --git a/CleverTapSDK/CleverTapInstanceConfig.h b/CleverTapSDK/CleverTapInstanceConfig.h index 1f78cbe8..d517e4c5 100644 --- a/CleverTapSDK/CleverTapInstanceConfig.h +++ b/CleverTapSDK/CleverTapInstanceConfig.h @@ -16,6 +16,8 @@ @property (nonatomic, assign) BOOL useCustomCleverTapId; @property (nonatomic, assign) BOOL disableIDFV; @property (nonatomic, assign) BOOL enableFileProtection; +@property (nonatomic, strong, readonly, nullable) NSString *handshakeDomain; + @property (nonatomic, assign) CleverTapLogLevel logLevel; @property (nonatomic, strong, nullable) NSArray *identityKeys; @property (nonatomic, assign) CleverTapEncryptionLevel encryptionLevel; @@ -56,4 +58,5 @@ */ - (void)setEncryptionLevel:(CleverTapEncryptionLevel)encryptionLevel; - (void)setEnableFileProtection:(BOOL)enableFileProtection; +- (void)setHandshakeDomain:(NSString * _Nonnull)handshakeDomain; @end diff --git a/CleverTapSDK/CleverTapInstanceConfig.m b/CleverTapSDK/CleverTapInstanceConfig.m index 8a3144b5..4bd9c770 100644 --- a/CleverTapSDK/CleverTapInstanceConfig.m +++ b/CleverTapSDK/CleverTapInstanceConfig.m @@ -29,6 +29,7 @@ - (void)encodeWithCoder:(NSCoder *)coder [coder encodeInt: _encryptionLevel forKey:@"encryptionLevel"]; [coder encodeObject: _aesCrypt forKey:@"aesCrypt"]; [coder encodeBool:_enableFileProtection forKey:@"enableFileProtection"]; + [coder encodeObject:_handshakeDomain forKey:@"handshakeDomain"]; } - (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { @@ -54,6 +55,7 @@ - (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { _encryptionLevel = [coder decodeIntForKey:@"encryptionLevel"]; _aesCrypt = [coder decodeObjectForKey:@"aesCrypt"]; _enableFileProtection = [coder decodeBoolForKey:@"enableFileProtection"]; + _handshakeDomain = [coder decodeObjectForKey:@"handshakeDomain"]; } return self; } @@ -186,6 +188,7 @@ - (instancetype)copyWithZone:(NSZone*)zone { copy.encryptionLevel = self.encryptionLevel; copy.aesCrypt = self.aesCrypt; copy.enableFileProtection = self.enableFileProtection; + copy.handshakeDomain = self.handshakeDomain; return copy; } @@ -209,6 +212,7 @@ - (void) setupPlistData:(BOOL)isDefault { _beta = plist.beta; _encryptionLevel = isDefault ? plist.encryptionLevel : CleverTapEncryptionNone; _enableFileProtection = isDefault ? plist.enableFileProtection : NO; + _handshakeDomain = isDefault ? plist.handshakeDomain : nil; if (isDefault) { _aesCrypt = [[CTAES alloc] initWithAccountID:_accountId encryptionLevel:_encryptionLevel isDefaultInstance:isDefault]; } @@ -241,4 +245,12 @@ - (void)setEnableFileProtection:(BOOL)enableFileProtection { CleverTapLogStaticInfo("CleverTap enable file protection for default instance can't be updated from setEnableFileProtection method"); } } + +- (void)setHandshakeDomain:(NSString *)handshakeDomain { + if (!_isDefaultInstance) { + _handshakeDomain = handshakeDomain; + } else { + CleverTapLogStaticInfo("CleverTap handshake domain for default instance can't be updated from setHandshakeDomain method"); + } +} @end From 12396042e9fe8d81fd6954e263b99c70df18f830 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Wed, 11 Sep 2024 10:13:35 +0530 Subject: [PATCH 04/21] remove commented code --- CleverTapSDK/CTDomainFactory.m | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CleverTapSDK/CTDomainFactory.m b/CleverTapSDK/CTDomainFactory.m index e22e3b01..59f3c72f 100644 --- a/CleverTapSDK/CTDomainFactory.m +++ b/CleverTapSDK/CTDomainFactory.m @@ -76,10 +76,6 @@ - (NSString *)loadRedirectDomain { } } -// if (self.config.handshakeDomain) { -// return nil; -// } - NSString *domain = nil; if (self.config.isDefaultInstance) { domain = [CTPreferences getStringForKey:[CTPreferences storageKeyWithSuffix:REDIRECT_DOMAIN_KEY config: self.config] withResetValue:[CTPreferences getStringForKey:REDIRECT_DOMAIN_KEY withResetValue:nil]]; From c8f526fbc4cb5166f7e53d5ff2c95e2cad20ca5a Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Wed, 11 Sep 2024 12:04:24 +0530 Subject: [PATCH 05/21] handled a use case where an existing app with cached domain would introduce the newer custom domain tag --- CleverTapSDK/CTDomainFactory.m | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/CleverTapSDK/CTDomainFactory.m b/CleverTapSDK/CTDomainFactory.m index 59f3c72f..0c8b8ea8 100644 --- a/CleverTapSDK/CTDomainFactory.m +++ b/CleverTapSDK/CTDomainFactory.m @@ -11,7 +11,7 @@ #import "CTConstants.h" #import "CleverTapInstanceConfigPrivate.h" - +NSString *const CUSTOM_DOMAIN_KEY = @"CLTAP_CUSTOM_DOMAIN_KEY"; NSString *const REDIRECT_DOMAIN_KEY = @"CLTAP_REDIRECT_DOMAIN_KEY"; NSString *const REDIRECT_NOTIF_VIEWED_DOMAIN_KEY = @"CLTAP_REDIRECT_NOTIF_VIEWED_DOMAIN_KEY"; @@ -76,6 +76,16 @@ - (NSString *)loadRedirectDomain { } } + if (self.config.handshakeDomain) { + NSString *customDomain = nil; + if (self.config.isDefaultInstance) { + customDomain = [CTPreferences getStringForKey:[CTPreferences storageKeyWithSuffix:CUSTOM_DOMAIN_KEY config: self.config] withResetValue:[CTPreferences getStringForKey:CUSTOM_DOMAIN_KEY withResetValue:nil]]; + } else { + customDomain = [CTPreferences getStringForKey:[CTPreferences storageKeyWithSuffix:CUSTOM_DOMAIN_KEY config: self.config] withResetValue:nil]; + } + return customDomain; + } + NSString *domain = nil; if (self.config.isDefaultInstance) { domain = [CTPreferences getStringForKey:[CTPreferences storageKeyWithSuffix:REDIRECT_DOMAIN_KEY config: self.config] withResetValue:[CTPreferences getStringForKey:REDIRECT_DOMAIN_KEY withResetValue:nil]]; @@ -113,13 +123,23 @@ - (NSString *)loadRedirectNotifViewedDomain { - (void)persistRedirectDomain { if (self.redirectDomain != nil) { - [CTPreferences putString:self.redirectDomain forKey:[CTPreferences storageKeyWithSuffix:REDIRECT_DOMAIN_KEY config: self.config]]; + if (self.config.handshakeDomain) { + [CTPreferences putString:self.redirectDomain forKey:[CTPreferences storageKeyWithSuffix:CUSTOM_DOMAIN_KEY config: self.config]]; + } + else { + [CTPreferences putString:self.redirectDomain forKey:[CTPreferences storageKeyWithSuffix:REDIRECT_DOMAIN_KEY config: self.config]]; + } #if CLEVERTAP_SSL_PINNING [self.urlSessionDelegate pinSSLCerts:self.sslCertNames forDomains:@[kCTApiDomain, self.redirectDomain]]; #endif } else { - [CTPreferences removeObjectForKey:REDIRECT_DOMAIN_KEY]; - [CTPreferences removeObjectForKey:[CTPreferences storageKeyWithSuffix:REDIRECT_DOMAIN_KEY config: self.config]]; + if (self.config.handshakeDomain) { + [CTPreferences removeObjectForKey:[CTPreferences storageKeyWithSuffix:CUSTOM_DOMAIN_KEY config: self.config]]; + } + else { + [CTPreferences removeObjectForKey:REDIRECT_DOMAIN_KEY]; + [CTPreferences removeObjectForKey:[CTPreferences storageKeyWithSuffix:REDIRECT_DOMAIN_KEY config: self.config]]; + } } } From d4e367e4817e31e69b13bb4163a6c8df3fb5ffb2 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Wed, 11 Sep 2024 17:21:14 +0530 Subject: [PATCH 06/21] updated versions and changelog --- CHANGELOG.md | 5 +++++ CleverTapSDK/CleverTapBuildInfo.h | 2 +- sdk-version.txt | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3d96eb1..edde3c1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Change Log All notable changes to this project will be documented in this file. +### [Version 7.0.2](https://github.com/CleverTap/clevertap-ios-sdk/releases/tag/7.0.2) (September 16, 2024) + +#### Added +- Adds support for custom handshake domains. + ### [Version 7.0.1](https://github.com/CleverTap/clevertap-ios-sdk/releases/tag/7.0.1) (August 22, 2024) #### Fixed diff --git a/CleverTapSDK/CleverTapBuildInfo.h b/CleverTapSDK/CleverTapBuildInfo.h index ff6f62bb..2c056645 100644 --- a/CleverTapSDK/CleverTapBuildInfo.h +++ b/CleverTapSDK/CleverTapBuildInfo.h @@ -1 +1 @@ -#define WR_SDK_REVISION @"70001" +#define WR_SDK_REVISION @"70002" diff --git a/sdk-version.txt b/sdk-version.txt index 73a86b19..2f963cd6 100644 --- a/sdk-version.txt +++ b/sdk-version.txt @@ -1 +1 @@ -7.0.1 \ No newline at end of file +7.0.2 \ No newline at end of file From 326d4843075444dfce21da4653d6e20bcc244c08 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Thu, 12 Sep 2024 01:07:26 +0530 Subject: [PATCH 07/21] added unit tests --- CleverTapSDK.xcodeproj/project.pbxproj | 10 +++++ CleverTapSDK/CTConstants.h | 5 +++ CleverTapSDK/CTConstants.m | 4 ++ CleverTapSDK/CTDomainFactory.m | 4 -- CleverTapSDKTests/CTDomainFactory+Tests.h | 14 ++++++ CleverTapSDKTests/CTDomainFactory+Tests.m | 13 ++++++ CleverTapSDKTests/CTDomainFactoryTests.m | 52 +++++++++++++++++++++++ 7 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 CleverTapSDKTests/CTDomainFactory+Tests.h create mode 100644 CleverTapSDKTests/CTDomainFactory+Tests.m create mode 100644 CleverTapSDKTests/CTDomainFactoryTests.m diff --git a/CleverTapSDK.xcodeproj/project.pbxproj b/CleverTapSDK.xcodeproj/project.pbxproj index 75193683..eb58c3a3 100644 --- a/CleverTapSDK.xcodeproj/project.pbxproj +++ b/CleverTapSDK.xcodeproj/project.pbxproj @@ -252,6 +252,8 @@ 4E872969277CDF9000A7A618 /* inapp_alert.json in Resources */ = {isa = PBXBuildFile; fileRef = 4E1F1561277090D6009387AE /* inapp_alert.json */; }; 4E87296E277CE8EB00A7A618 /* StubHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E87296D277CE8EB00A7A618 /* StubHelper.m */; }; 4E872973277CEE6700A7A618 /* TestConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E872972277CEE6700A7A618 /* TestConstants.m */; }; + 4E87397B2C92223C00FDFDFD /* CTDomainFactoryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E8739772C921D9000FDFDFD /* CTDomainFactoryTests.m */; }; + 4E87397E2C9223B300FDFDFD /* CTDomainFactory+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E87397D2C9223B300FDFDFD /* CTDomainFactory+Tests.m */; }; 4E8B81662AD2ADAE00714BB4 /* CTSwizzleManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E8B81642AD2ADAE00714BB4 /* CTSwizzleManager.m */; }; 4E8B81672AD2ADAE00714BB4 /* CTSwizzleManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E8B81642AD2ADAE00714BB4 /* CTSwizzleManager.m */; }; 4E8B81682AD2ADAE00714BB4 /* CTSwizzleManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E8B81652AD2ADAE00714BB4 /* CTSwizzleManager.h */; }; @@ -819,6 +821,9 @@ 4E87296D277CE8EB00A7A618 /* StubHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StubHelper.m; sourceTree = ""; }; 4E872971277CEE6700A7A618 /* TestConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestConstants.h; sourceTree = ""; }; 4E872972277CEE6700A7A618 /* TestConstants.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestConstants.m; sourceTree = ""; }; + 4E8739772C921D9000FDFDFD /* CTDomainFactoryTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTDomainFactoryTests.m; sourceTree = ""; }; + 4E87397C2C9223B300FDFDFD /* CTDomainFactory+Tests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CTDomainFactory+Tests.h"; sourceTree = ""; }; + 4E87397D2C9223B300FDFDFD /* CTDomainFactory+Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "CTDomainFactory+Tests.m"; sourceTree = ""; }; 4E8B81642AD2ADAE00714BB4 /* CTSwizzleManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTSwizzleManager.m; sourceTree = ""; }; 4E8B81652AD2ADAE00714BB4 /* CTSwizzleManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTSwizzleManager.h; sourceTree = ""; }; 4E8B816A2AD2B2FD00714BB4 /* CleverTapInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CleverTapInternal.h; sourceTree = ""; }; @@ -1600,6 +1605,9 @@ 6BD851C82B45CD1800FA5298 /* CTMultiDelegateManager+Tests.h */, 0B5564552C25946C00B87284 /* CTUserInfoMigratorTest.m */, 0B995A492C36AEDC00AF6006 /* CTLocalDataStoreTests.m */, + 4E8739772C921D9000FDFDFD /* CTDomainFactoryTests.m */, + 4E87397C2C9223B300FDFDFD /* CTDomainFactory+Tests.h */, + 4E87397D2C9223B300FDFDFD /* CTDomainFactory+Tests.m */, ); path = CleverTapSDKTests; sourceTree = ""; @@ -2500,6 +2508,7 @@ 32394C2529FA272600956058 /* CTValidatorTest.m in Sources */, 6BB778CE2BEE48C300A41628 /* CTCustomTemplateInAppDataTest.m in Sources */, 6BA3B2E82B07E207004E834B /* CTTriggersMatcher+Tests.m in Sources */, + 4E87397E2C9223B300FDFDFD /* CTDomainFactory+Tests.m in Sources */, 6BBF05CE2C58E3FB0047E3D9 /* NSURLSessionMock.m in Sources */, 6B32A0A52B9A0F17009ADC57 /* CTCustomTemplateTest.m in Sources */, 4E1F155B276B662C009387AE /* EventDetail.m in Sources */, @@ -2539,6 +2548,7 @@ 0B995A4A2C36AEDC00AF6006 /* CTLocalDataStoreTests.m in Sources */, 6A4427C52AA6515A0098866F /* CTTriggersMatcherTest.m in Sources */, 6B32A0B02B9DC374009ADC57 /* CTTemplateArgumentTest.m in Sources */, + 4E87397B2C92223C00FDFDFD /* CTDomainFactoryTests.m in Sources */, 0B5564562C25946C00B87284 /* CTUserInfoMigratorTest.m in Sources */, 4E2CF1442AC56D8F00441E8B /* CTEncryptionTests.m in Sources */, 32394C2729FA278C00956058 /* CTUriHelperTest.m in Sources */, diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index 4cb2f2ec..12a1912d 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -5,6 +5,11 @@ extern NSString *const kCTNotifViewedApiDomain; extern NSString *const kHANDSHAKE_URL; extern NSString *const kHANDSHAKE_DOMAIN_HEADER; + +extern NSString *const CUSTOM_DOMAIN_KEY; +extern NSString *const REDIRECT_DOMAIN_KEY; +extern NSString *const REDIRECT_NOTIF_VIEWED_DOMAIN_KEY; + extern NSString *const kLastSessionPing; extern NSString *const kLastSessionTime; extern NSString *const kSessionId; diff --git a/CleverTapSDK/CTConstants.m b/CleverTapSDK/CTConstants.m index 0a7162b7..7923a16e 100644 --- a/CleverTapSDK/CTConstants.m +++ b/CleverTapSDK/CTConstants.m @@ -5,6 +5,10 @@ NSString *const kHANDSHAKE_URL = @"https://clevertap-prod.com/hello"; NSString *const kHANDSHAKE_DOMAIN_HEADER =@"X-CleverTap-Handshake-Domain"; +NSString *const CUSTOM_DOMAIN_KEY = @"CLTAP_CUSTOM_DOMAIN_KEY"; +NSString *const REDIRECT_DOMAIN_KEY = @"CLTAP_REDIRECT_DOMAIN_KEY"; +NSString *const REDIRECT_NOTIF_VIEWED_DOMAIN_KEY = @"CLTAP_REDIRECT_NOTIF_VIEWED_DOMAIN_KEY"; + NSString *const kLastSessionPing = @"last_session_ping"; NSString *const kLastSessionTime = @"lastSessionTime"; NSString *const kSessionId = @"sessionId"; diff --git a/CleverTapSDK/CTDomainFactory.m b/CleverTapSDK/CTDomainFactory.m index 0c8b8ea8..5ada3bec 100644 --- a/CleverTapSDK/CTDomainFactory.m +++ b/CleverTapSDK/CTDomainFactory.m @@ -11,10 +11,6 @@ #import "CTConstants.h" #import "CleverTapInstanceConfigPrivate.h" -NSString *const CUSTOM_DOMAIN_KEY = @"CLTAP_CUSTOM_DOMAIN_KEY"; -NSString *const REDIRECT_DOMAIN_KEY = @"CLTAP_REDIRECT_DOMAIN_KEY"; -NSString *const REDIRECT_NOTIF_VIEWED_DOMAIN_KEY = @"CLTAP_REDIRECT_NOTIF_VIEWED_DOMAIN_KEY"; - @interface CTDomainFactory () @property (nonatomic, strong) CleverTapInstanceConfig *config; diff --git a/CleverTapSDKTests/CTDomainFactory+Tests.h b/CleverTapSDKTests/CTDomainFactory+Tests.h new file mode 100644 index 00000000..4d828936 --- /dev/null +++ b/CleverTapSDKTests/CTDomainFactory+Tests.h @@ -0,0 +1,14 @@ +// +// CTDomainFactory+Tests.h +// CleverTapSDKTests +// +// Created by Akash Malhotra on 12/09/24. +// Copyright © 2024 CleverTap. All rights reserved. +// + +#import "CleverTapInstanceConfig.h" +#import "CTDomainFactory.h" + +@interface CTDomainFactory (Tests) +- (NSString *)loadRedirectDomain; +@end diff --git a/CleverTapSDKTests/CTDomainFactory+Tests.m b/CleverTapSDKTests/CTDomainFactory+Tests.m new file mode 100644 index 00000000..c2991fa5 --- /dev/null +++ b/CleverTapSDKTests/CTDomainFactory+Tests.m @@ -0,0 +1,13 @@ +// +// CTDomainFactory+Tests.m +// CleverTapSDKTests +// +// Created by Akash Malhotra on 12/09/24. +// Copyright © 2024 CleverTap. All rights reserved. +// + +#import "CTDomainFactory+Tests.h" + +@implementation CTDomainFactory (Tests) + +@end diff --git a/CleverTapSDKTests/CTDomainFactoryTests.m b/CleverTapSDKTests/CTDomainFactoryTests.m new file mode 100644 index 00000000..9ed47df6 --- /dev/null +++ b/CleverTapSDKTests/CTDomainFactoryTests.m @@ -0,0 +1,52 @@ +// +// CTDomainFactoryTests.m +// CleverTapSDKTests +// +// Created by Akash Malhotra on 12/09/24. +// Copyright © 2024 CleverTap. All rights reserved. +// +#import +#import +#import "CleverTapInstanceConfig.h" +#import "CTDomainFactory.h" +#import "CTDomainFactory+Tests.h" +#import "CTConstants.h" +#import "CTPreferences.h" + +@interface CTDomainFactoryTests: XCTestCase +@property (nonatomic, strong) NSString *region; +@property (nonatomic, strong) CleverTapInstanceConfig *config; +@property (nonatomic, strong) CTDomainFactory *domainFactory; +@end + +@implementation CTDomainFactoryTests + +- (void)setUp { + [super setUp]; + self.region = @"testRegion"; + self.config = [[CleverTapInstanceConfig alloc] initWithAccountId:@"testAccount" accountToken:@"testToken" accountRegion:self.region]; + self.domainFactory = [[CTDomainFactory alloc]initWithConfig:self.config]; +} + +- (void)tearDown { + [self.domainFactory clearRedirectDomain]; + self.domainFactory = nil; + self.region = nil; + [super tearDown]; +} + +- (void)testLoadRedirectDomainRegion { + NSString *domain = [self.domainFactory loadRedirectDomain]; + NSString *result = [NSString stringWithFormat:@"%@.%@", self.region, kCTApiDomain].lowercaseString; + XCTAssertEqualObjects(domain, result); +} + +- (void)testLoadRedirectDomainCached { + [self.domainFactory persistRedirectDomain]; + + NSString *domain = [CTPreferences getStringForKey:[CTPreferences storageKeyWithSuffix:REDIRECT_DOMAIN_KEY config: self.config] withResetValue:nil]; + NSString *result = [NSString stringWithFormat:@"%@.%@", self.region, kCTApiDomain].lowercaseString; + XCTAssertEqualObjects(domain, result); +} + +@end From 88bedd28b6260a3592b90ebe099e913a8318d5de Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Thu, 12 Sep 2024 12:14:07 +0530 Subject: [PATCH 08/21] added more unit tests --- CleverTapSDKTests/CTDomainFactoryTests.m | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/CleverTapSDKTests/CTDomainFactoryTests.m b/CleverTapSDKTests/CTDomainFactoryTests.m index 9ed47df6..4fcdabb6 100644 --- a/CleverTapSDKTests/CTDomainFactoryTests.m +++ b/CleverTapSDKTests/CTDomainFactoryTests.m @@ -43,10 +43,28 @@ - (void)testLoadRedirectDomainRegion { - (void)testLoadRedirectDomainCached { [self.domainFactory persistRedirectDomain]; - NSString *domain = [CTPreferences getStringForKey:[CTPreferences storageKeyWithSuffix:REDIRECT_DOMAIN_KEY config: self.config] withResetValue:nil]; - NSString *result = [NSString stringWithFormat:@"%@.%@", self.region, kCTApiDomain].lowercaseString; + NSString *result = [self.domainFactory loadRedirectDomain]; XCTAssertEqualObjects(domain, result); } +- (void)testLoadRedirectDomainCustomHandShake { + CleverTapInstanceConfig *config = [[CleverTapInstanceConfig alloc] initWithAccountId:@"testAccount" accountToken:@"testToken"]; + config.handshakeDomain = @"testCustomDomain"; + CTDomainFactory *domainFactory = [[CTDomainFactory alloc]initWithConfig:config]; + domainFactory.redirectDomain = config.handshakeDomain; + [domainFactory persistRedirectDomain]; + + NSString *domain = [domainFactory loadRedirectDomain];; + XCTAssertEqualObjects(domain, config.handshakeDomain); +} + +- (void)testLoadRedirectDomainProxy { + CleverTapInstanceConfig *config = [[CleverTapInstanceConfig alloc] initWithAccountId:@"testAccount" accountToken:@"testToken" proxyDomain:@"testProxydomain"]; + CTDomainFactory *domainFactory = [[CTDomainFactory alloc]initWithConfig:config]; + + NSString *domain = [domainFactory loadRedirectDomain]; + XCTAssertEqualObjects(domain, config.proxyDomain.lowercaseString); +} + @end From cf6cd232af837ffc40ea749107219dc4c8ecbc4c Mon Sep 17 00:00:00 2001 From: Nikola Zagorchev Date: Thu, 12 Sep 2024 09:49:09 +0300 Subject: [PATCH 09/21] Enable internal logs and debugger if log level is Debug (#375) --- CleverTapSDK/CTConstants.h | 4 ++-- CleverTapSDK/CleverTap.m | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index f5563c25..10faab57 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -10,10 +10,10 @@ extern NSString *const kSessionId; #define CleverTapLogInfo(level, fmt, ...) if(level >= 0) { NSLog((@"%@" fmt), @"[CleverTap]: ", ##__VA_ARGS__); } #define CleverTapLogDebug(level, fmt, ...) if(level > 0) { NSLog((@"%@" fmt), @"[CleverTap]: ", ##__VA_ARGS__); } -#define CleverTapLogInternal(level, fmt, ...) if (level > 1) { NSLog((@"%@" fmt), @"[CleverTap]: ", ##__VA_ARGS__); } +#define CleverTapLogInternal(level, fmt, ...) if (level >= 1) { NSLog((@"%@" fmt), @"[CleverTap]: ", ##__VA_ARGS__); } #define CleverTapLogStaticInfo(fmt, ...) if([CTLogger getDebugLevel] >= 0) { NSLog((@"%@" fmt), @"[CleverTap]: ", ##__VA_ARGS__); } #define CleverTapLogStaticDebug(fmt, ...) if([CTLogger getDebugLevel] > 0) { NSLog((@"%@" fmt), @"[CleverTap]: ", ##__VA_ARGS__); } -#define CleverTapLogStaticInternal(fmt, ...) if([CTLogger getDebugLevel] > 1) { NSLog((@"%@" fmt), @"[CleverTap]: ", ##__VA_ARGS__); } +#define CleverTapLogStaticInternal(fmt, ...) if([CTLogger getDebugLevel] >= 1) { NSLog((@"%@" fmt), @"[CleverTap]: ", ##__VA_ARGS__); } #define CT_TRY @try { #define CT_END_TRY }\ diff --git a/CleverTapSDK/CleverTap.m b/CleverTapSDK/CleverTap.m index 84cf963b..de8d1ca8 100644 --- a/CleverTapSDK/CleverTap.m +++ b/CleverTapSDK/CleverTap.m @@ -873,7 +873,7 @@ - (NSDictionary *)batchHeaderForQueue:(CTQueueType)queueType { } // Adds debug flag to show errors and events on the dashboard - integration-debugger when dubug level is set to 3 - if ([CleverTap getDebugLevel] == 3){ + if ([CleverTap getDebugLevel] >= CleverTapLogDebug){ header[@"debug"] = @YES; } From 4517a7b1a764feeff02730f574049a0786b1b929 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Thu, 12 Sep 2024 15:38:47 +0530 Subject: [PATCH 10/21] ctrequest unit tests --- CleverTapSDK/CTConstants.h | 3 ++- CleverTapSDK/CTConstants.m | 2 ++ CleverTapSDK/CTRequest.m | 3 --- CleverTapSDKTests/CTRequestFactoryTests.m | 9 +++++++++ 4 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 CleverTapSDKTests/CTRequestFactoryTests.m diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index 12a1912d..d9b79d40 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -4,7 +4,8 @@ extern NSString *const kCTApiDomain; extern NSString *const kCTNotifViewedApiDomain; extern NSString *const kHANDSHAKE_URL; extern NSString *const kHANDSHAKE_DOMAIN_HEADER; - +extern NSString *const ACCOUNT_ID_HEADER; +extern NSString *const ACCOUNT_TOKEN_HEADER; extern NSString *const CUSTOM_DOMAIN_KEY; extern NSString *const REDIRECT_DOMAIN_KEY; diff --git a/CleverTapSDK/CTConstants.m b/CleverTapSDK/CTConstants.m index 7923a16e..323f69fb 100644 --- a/CleverTapSDK/CTConstants.m +++ b/CleverTapSDK/CTConstants.m @@ -4,6 +4,8 @@ NSString *const kCTNotifViewedApiDomain = @"spiky.clevertap-prod.com"; NSString *const kHANDSHAKE_URL = @"https://clevertap-prod.com/hello"; NSString *const kHANDSHAKE_DOMAIN_HEADER =@"X-CleverTap-Handshake-Domain"; +NSString *const ACCOUNT_ID_HEADER = @"X-CleverTap-Account-Id"; +NSString *const ACCOUNT_TOKEN_HEADER = @"X-CleverTap-Token"; NSString *const CUSTOM_DOMAIN_KEY = @"CLTAP_CUSTOM_DOMAIN_KEY"; NSString *const REDIRECT_DOMAIN_KEY = @"CLTAP_REDIRECT_DOMAIN_KEY"; diff --git a/CleverTapSDK/CTRequest.m b/CleverTapSDK/CTRequest.m index b8734910..e3aed06a 100644 --- a/CleverTapSDK/CTRequest.m +++ b/CleverTapSDK/CTRequest.m @@ -10,9 +10,6 @@ #import "CTConstants.h" #import "CTUtils.h" -NSString *const ACCOUNT_ID_HEADER = @"X-CleverTap-Account-Id"; -NSString *const ACCOUNT_TOKEN_HEADER = @"X-CleverTap-Token"; - @interface CTRequest() @property (nonatomic, strong, nullable) id params; diff --git a/CleverTapSDKTests/CTRequestFactoryTests.m b/CleverTapSDKTests/CTRequestFactoryTests.m new file mode 100644 index 00000000..16a84ecc --- /dev/null +++ b/CleverTapSDKTests/CTRequestFactoryTests.m @@ -0,0 +1,9 @@ +// +// CTRequestFactoryTests.m +// CleverTapSDKTests +// +// Created by Akash Malhotra on 12/09/24. +// Copyright © 2024 CleverTap. All rights reserved. +// + +#import From ba548acdd8cef3395113d3687467d22c732c9e43 Mon Sep 17 00:00:00 2001 From: Nikola Zagorchev Date: Fri, 13 Sep 2024 10:53:41 +0300 Subject: [PATCH 11/21] Check the application state for in-apps (#374) --- CleverTapSDK/InApps/CTInAppDisplayManager.m | 42 +++++++++++++++------ 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/CleverTapSDK/InApps/CTInAppDisplayManager.m b/CleverTapSDK/InApps/CTInAppDisplayManager.m index f5255add..61c23d95 100644 --- a/CleverTapSDK/InApps/CTInAppDisplayManager.m +++ b/CleverTapSDK/InApps/CTInAppDisplayManager.m @@ -157,10 +157,17 @@ - (void)_addInAppNotificationsToQueue:(NSArray *)inappNotifs { @try { NSArray *filteredInAppNotifs = [self filterNonRegisteredTemplates:inappNotifs]; [self.inAppStore enqueueInApps:filteredInAppNotifs]; - - // Fire the first notification, if any - [self.dispatchQueueManager runOnNotificationQueue:^{ - [self _showNotificationIfAvailable]; + + [CTUtils runSyncMainQueue:^{ + if (!self.isAppActiveForeground) { + CleverTapLogInternal(self.config.logLevel, @"%@: Application is not in the foreground, won't try to show in-app if available.", self); + return; + } + + // Fire the first notification, if any + [self.dispatchQueueManager runOnNotificationQueue:^{ + [self _showNotificationIfAvailable]; + }]; }]; } @catch (NSException *e) { CleverTapLogInternal(self.config.logLevel, @"%@: InApp notification handling error: %@", self, e.debugDescription); @@ -172,6 +179,11 @@ - (void)_addInAppNotificationInFrontOfQueue:(CTInAppNotification *)inappNotif { NSString *templateName = inappNotif.customTemplateInAppData.templateName; if ([self.templatesManager isRegisteredTemplateWithName:templateName]) { [self.inAppStore insertInFrontInApp:inappNotif.jsonDescription]; + + if (!self.isAppActiveForeground) { + CleverTapLogInternal(self.config.logLevel, @"%@: Application is not in the foreground, won't try to show in-app if available.", self); + return; + } // Fire the first notification, if any [self.dispatchQueueManager runOnNotificationQueue:^{ [self _showNotificationIfAvailable]; @@ -213,6 +225,11 @@ - (void)_showInAppNotificationIfAny { return; } if (!self.config.analyticsOnly) { + if (!self.isAppActiveForeground) { + CleverTapLogInternal(self.config.logLevel, @"%@: Application is not in the foreground, won't try to show in-app if any.", self); + return; + } + [self.dispatchQueueManager runOnNotificationQueue:^{ [self _showNotificationIfAvailable]; }]; @@ -279,11 +296,6 @@ - (void)prepareNotificationForDisplay:(NSDictionary*)jsonObj { return; } - if (!self.instance.isAppForeground) { - CleverTapLogInternal(self.config.logLevel, @"%@: Application is not in the foreground, won't prepare in-app: %@", self, jsonObj); - return; - } - [self.dispatchQueueManager runOnNotificationQueue:^{ CleverTapLogInternal(self.config.logLevel, @"%@: processing inapp notification: %@", self, jsonObj); __block CTInAppNotification *notification = [[CTInAppNotification alloc] initWithJSON:jsonObj]; @@ -399,7 +411,7 @@ - (void)displayNotification:(CTInAppNotification*)notification { return; } - if (!self.instance.isAppForeground) { + if (!self.isAppActiveForeground) { CleverTapLogInternal(self.config.logLevel, @"%@: Application is not in the foreground, not displaying in-app: %@", self, notification.jsonDescription); return; } @@ -514,6 +526,14 @@ - (void)notifyNotificationDismissed:(CTInAppNotification *)notification { } } +- (BOOL)isAppActiveForeground { + UIApplication *app = [CTUIUtils getSharedApplication]; + if (app != nil) { + return [app applicationState] == UIApplicationStateActive; + } + return NO; +} + #pragma mark - Pending Notification - (void)onDisplayPendingNotification:(NSNotification *)notification { @@ -744,7 +764,7 @@ - (BOOL)didHandleInAppTestFromPushNotificaton:(NSDictionary * _Nullable)notifica } if (inapp) { - float delay = self.instance.isAppForeground ? 0.5 : 2.0; + 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]; From 362041e86bcc3621e784504b0d9b16b85d159f2c Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Wed, 25 Sep 2024 13:02:17 +0530 Subject: [PATCH 12/21] removed custom domain key value in preferences --- CleverTapSDK/CTDomainFactory.m | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/CleverTapSDK/CTDomainFactory.m b/CleverTapSDK/CTDomainFactory.m index 5ada3bec..b18c0b99 100644 --- a/CleverTapSDK/CTDomainFactory.m +++ b/CleverTapSDK/CTDomainFactory.m @@ -72,16 +72,6 @@ - (NSString *)loadRedirectDomain { } } - if (self.config.handshakeDomain) { - NSString *customDomain = nil; - if (self.config.isDefaultInstance) { - customDomain = [CTPreferences getStringForKey:[CTPreferences storageKeyWithSuffix:CUSTOM_DOMAIN_KEY config: self.config] withResetValue:[CTPreferences getStringForKey:CUSTOM_DOMAIN_KEY withResetValue:nil]]; - } else { - customDomain = [CTPreferences getStringForKey:[CTPreferences storageKeyWithSuffix:CUSTOM_DOMAIN_KEY config: self.config] withResetValue:nil]; - } - return customDomain; - } - NSString *domain = nil; if (self.config.isDefaultInstance) { domain = [CTPreferences getStringForKey:[CTPreferences storageKeyWithSuffix:REDIRECT_DOMAIN_KEY config: self.config] withResetValue:[CTPreferences getStringForKey:REDIRECT_DOMAIN_KEY withResetValue:nil]]; @@ -119,23 +109,13 @@ - (NSString *)loadRedirectNotifViewedDomain { - (void)persistRedirectDomain { if (self.redirectDomain != nil) { - if (self.config.handshakeDomain) { - [CTPreferences putString:self.redirectDomain forKey:[CTPreferences storageKeyWithSuffix:CUSTOM_DOMAIN_KEY config: self.config]]; - } - else { - [CTPreferences putString:self.redirectDomain forKey:[CTPreferences storageKeyWithSuffix:REDIRECT_DOMAIN_KEY config: self.config]]; - } + [CTPreferences putString:self.redirectDomain forKey:[CTPreferences storageKeyWithSuffix:REDIRECT_DOMAIN_KEY config: self.config]]; #if CLEVERTAP_SSL_PINNING [self.urlSessionDelegate pinSSLCerts:self.sslCertNames forDomains:@[kCTApiDomain, self.redirectDomain]]; #endif } else { - if (self.config.handshakeDomain) { - [CTPreferences removeObjectForKey:[CTPreferences storageKeyWithSuffix:CUSTOM_DOMAIN_KEY config: self.config]]; - } - else { - [CTPreferences removeObjectForKey:REDIRECT_DOMAIN_KEY]; - [CTPreferences removeObjectForKey:[CTPreferences storageKeyWithSuffix:REDIRECT_DOMAIN_KEY config: self.config]]; - } + [CTPreferences removeObjectForKey:REDIRECT_DOMAIN_KEY]; + [CTPreferences removeObjectForKey:[CTPreferences storageKeyWithSuffix:REDIRECT_DOMAIN_KEY config: self.config]]; } } From 523213c81c03ae4cd451baf081db97a2000d8617 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Wed, 25 Sep 2024 13:10:03 +0530 Subject: [PATCH 13/21] removed custom domain constants --- CleverTapSDK/CTConstants.h | 1 - CleverTapSDK/CTConstants.m | 1 - 2 files changed, 2 deletions(-) diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index d9b79d40..e544d2bd 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -7,7 +7,6 @@ extern NSString *const kHANDSHAKE_DOMAIN_HEADER; extern NSString *const ACCOUNT_ID_HEADER; extern NSString *const ACCOUNT_TOKEN_HEADER; -extern NSString *const CUSTOM_DOMAIN_KEY; extern NSString *const REDIRECT_DOMAIN_KEY; extern NSString *const REDIRECT_NOTIF_VIEWED_DOMAIN_KEY; diff --git a/CleverTapSDK/CTConstants.m b/CleverTapSDK/CTConstants.m index 323f69fb..b7f5ef46 100644 --- a/CleverTapSDK/CTConstants.m +++ b/CleverTapSDK/CTConstants.m @@ -7,7 +7,6 @@ NSString *const ACCOUNT_ID_HEADER = @"X-CleverTap-Account-Id"; NSString *const ACCOUNT_TOKEN_HEADER = @"X-CleverTap-Token"; -NSString *const CUSTOM_DOMAIN_KEY = @"CLTAP_CUSTOM_DOMAIN_KEY"; NSString *const REDIRECT_DOMAIN_KEY = @"CLTAP_REDIRECT_DOMAIN_KEY"; NSString *const REDIRECT_NOTIF_VIEWED_DOMAIN_KEY = @"CLTAP_REDIRECT_NOTIF_VIEWED_DOMAIN_KEY"; From 2cc3e98e26dcee55720cafbd3d0ae2c8cc4882b7 Mon Sep 17 00:00:00 2001 From: Nikola Zagorchev Date: Tue, 8 Oct 2024 14:31:13 +0300 Subject: [PATCH 14/21] Custom templates JSON (#376) * Add JSON template producer * Check presenters and types, add tests * Check template type, use CTTemplateArgument enum, add more tests * Use custom templates exception name * Make CTJsonTemplateProducer public * Add code documentation. * Expose registering custom templates to CleverTap interface --- CleverTap-iOS-SDK.podspec | 2 +- CleverTapSDK.xcodeproj/project.pbxproj | 12 + CleverTapSDK/CTConstants.h | 2 + CleverTapSDK/CleverTap.h | 17 + CleverTapSDK/CleverTap.m | 4 + .../CustomTemplates/CTAppFunctionBuilder.h | 11 + .../InApps/CustomTemplates/CTCustomTemplate.h | 11 + .../CustomTemplates/CTCustomTemplateBuilder.h | 48 +++ .../CustomTemplates/CTCustomTemplateBuilder.m | 23 +- .../CTCustomTemplatesManager.m | 4 +- .../CustomTemplates/CTInAppTemplateBuilder.h | 8 + .../CustomTemplates/CTJsonTemplateProducer.h | 93 +++++ .../CustomTemplates/CTJsonTemplateProducer.m | 192 ++++++++++ .../CustomTemplates/CTTemplateArgument.h | 2 +- .../CustomTemplates/CTTemplateArgument.m | 4 +- .../CustomTemplates/CTTemplateContext.h | 83 ++++- .../CustomTemplates/CTTemplatePresenter.h | 15 + .../CustomTemplates/CTTemplateProducer.h | 7 + .../CTCustomTemplateBuilderTest.m | 68 ++-- .../CTCustomTemplatesManagerTest.m | 3 +- .../CTJsonTemplateProducerTest.m | 350 ++++++++++++++++++ 21 files changed, 901 insertions(+), 58 deletions(-) create mode 100644 CleverTapSDK/InApps/CustomTemplates/CTJsonTemplateProducer.h create mode 100644 CleverTapSDK/InApps/CustomTemplates/CTJsonTemplateProducer.m create mode 100644 CleverTapSDKTests/InApps/CustomTemplates/CTJsonTemplateProducerTest.m diff --git a/CleverTap-iOS-SDK.podspec b/CleverTap-iOS-SDK.podspec index 3cf8a182..448fcbc2 100644 --- a/CleverTap-iOS-SDK.podspec +++ b/CleverTap-iOS-SDK.podspec @@ -15,7 +15,7 @@ s.ios.resource_bundle = {'CleverTapSDK' => ['CleverTapSDK/**/*.{png,xib,ht s.ios.deployment_target = '9.0' s.ios.source_files = 'CleverTapSDK/**/*.{h,m}' s.ios.exclude_files = 'CleverTapSDK/include/**/*.h' -s.ios.public_header_files = 'CleverTapSDK/CleverTap.h', 'CleverTapSDK/CleverTap+SSLPinning.h','CleverTapSDK/CleverTap+Inbox.h', 'CleverTapSDK/CleverTapInstanceConfig.h', 'CleverTapSDK/CleverTapBuildInfo.h', 'CleverTapSDK/CleverTapEventDetail.h', 'CleverTapSDK/CleverTapInAppNotificationDelegate.h', 'CleverTapSDK/CleverTapSyncDelegate.h', 'CleverTapSDK/CleverTapTrackedViewController.h', 'CleverTapSDK/CleverTapUTMDetail.h', 'CleverTapSDK/CleverTapJSInterface.h', 'CleverTapSDK/CleverTap+DisplayUnit.h', 'CleverTapSDK/CleverTap+FeatureFlags.h', 'CleverTapSDK/CleverTap+ProductConfig.h', 'CleverTapSDK/CleverTapPushNotificationDelegate.h', 'CleverTapSDK/CleverTapURLDelegate.h', 'CleverTapSDK/CleverTap+InAppNotifications.h', 'CleverTapSDK/CleverTap+SCDomain.h', 'CleverTapSDK/CleverTap+PushPermission.h', 'CleverTapSDK/InApps/CTLocalInApp.h', 'CleverTapSDK/CleverTap+CTVar.h', 'CleverTapSDK/ProductExperiences/CTVar.h', 'CleverTapSDK/LeanplumCT.h', 'CleverTapSDK/InApps/CustomTemplates/CTInAppTemplateBuilder.h', 'CleverTapSDK/InApps/CustomTemplates/CTAppFunctionBuilder.h', 'CleverTapSDK/InApps/CustomTemplates/CTTemplatePresenter.h', 'CleverTapSDK/InApps/CustomTemplates/CTTemplateProducer.h', 'CleverTapSDK/InApps/CustomTemplates/CTCustomTemplateBuilder.h', 'CleverTapSDK/InApps/CustomTemplates/CTCustomTemplate.h', 'CleverTapSDK/InApps/CustomTemplates/CTTemplateContext.h', 'CleverTapSDK/InApps/CustomTemplates/CTCustomTemplatesManager.h' +s.ios.public_header_files = 'CleverTapSDK/CleverTap.h', 'CleverTapSDK/CleverTap+SSLPinning.h','CleverTapSDK/CleverTap+Inbox.h', 'CleverTapSDK/CleverTapInstanceConfig.h', 'CleverTapSDK/CleverTapBuildInfo.h', 'CleverTapSDK/CleverTapEventDetail.h', 'CleverTapSDK/CleverTapInAppNotificationDelegate.h', 'CleverTapSDK/CleverTapSyncDelegate.h', 'CleverTapSDK/CleverTapTrackedViewController.h', 'CleverTapSDK/CleverTapUTMDetail.h', 'CleverTapSDK/CleverTapJSInterface.h', 'CleverTapSDK/CleverTap+DisplayUnit.h', 'CleverTapSDK/CleverTap+FeatureFlags.h', 'CleverTapSDK/CleverTap+ProductConfig.h', 'CleverTapSDK/CleverTapPushNotificationDelegate.h', 'CleverTapSDK/CleverTapURLDelegate.h', 'CleverTapSDK/CleverTap+InAppNotifications.h', 'CleverTapSDK/CleverTap+SCDomain.h', 'CleverTapSDK/CleverTap+PushPermission.h', 'CleverTapSDK/InApps/CTLocalInApp.h', 'CleverTapSDK/CleverTap+CTVar.h', 'CleverTapSDK/ProductExperiences/CTVar.h', 'CleverTapSDK/LeanplumCT.h', 'CleverTapSDK/InApps/CustomTemplates/CTInAppTemplateBuilder.h', 'CleverTapSDK/InApps/CustomTemplates/CTAppFunctionBuilder.h', 'CleverTapSDK/InApps/CustomTemplates/CTTemplatePresenter.h', 'CleverTapSDK/InApps/CustomTemplates/CTTemplateProducer.h', 'CleverTapSDK/InApps/CustomTemplates/CTCustomTemplateBuilder.h', 'CleverTapSDK/InApps/CustomTemplates/CTCustomTemplate.h', 'CleverTapSDK/InApps/CustomTemplates/CTTemplateContext.h', 'CleverTapSDK/InApps/CustomTemplates/CTCustomTemplatesManager.h', 'CleverTapSDK/InApps/CustomTemplates/CTJsonTemplateProducer.h' s.tvos.deployment_target = '9.0' s.tvos.source_files = 'CleverTapSDK/*.{h,m}', 'CleverTapSDK/FileDownload/*.{h,m}', 'CleverTapSDK/ProductConfig/**/*.{h,m}', 'CleverTapSDK/FeatureFlags/**/*.{h,m}', 'CleverTapSDK/ProductExperiences/*.{h,m}', 'CleverTapSDK/Swizzling/*.{h,m}', 'CleverTapSDK/Session/*.{h,m}' s.tvos.exclude_files = 'CleverTapSDK/include/**/*.h', 'CleverTapSDK/CleverTapJSInterface.{h,m}', 'CleverTapSDK/CTInAppNotification.{h,m}', 'CleverTapSDK/CTNotificationButton.{h,m}', 'CleverTapSDK/CTNotificationAction.{h,m}', 'CleverTapSDK/CTPushPrimerManager.{h,m}', 'CleverTapSDK/InApps/*.{h,m}', 'CleverTapSDK/InApps/**/*.{h,m}', 'CleverTapSDK/CTInAppFCManager.{h,m}', 'CleverTapSDK/CTInAppDisplayViewController.{h,m}' diff --git a/CleverTapSDK.xcodeproj/project.pbxproj b/CleverTapSDK.xcodeproj/project.pbxproj index 75193683..ea4c003c 100644 --- a/CleverTapSDK.xcodeproj/project.pbxproj +++ b/CleverTapSDK.xcodeproj/project.pbxproj @@ -337,6 +337,9 @@ 6AA1357C2A2E467800EFF2C1 /* NSDictionary+Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 6AA135782A2E467800EFF2C1 /* NSDictionary+Extensions.m */; }; 6B0063BB2B18EC9E0063BF79 /* image_interstitial.html in Resources */ = {isa = PBXBuildFile; fileRef = 6B0063BA2B18EC9E0063BF79 /* image_interstitial.html */; }; 6B0063BC2B18EC9E0063BF79 /* image_interstitial.html in Resources */ = {isa = PBXBuildFile; fileRef = 6B0063BA2B18EC9E0063BF79 /* image_interstitial.html */; }; + 6B12F7662C94312D0045D743 /* CTJsonTemplateProducer.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B12F7652C94312D0045D743 /* CTJsonTemplateProducer.m */; }; + 6B12F7672C94312D0045D743 /* CTJsonTemplateProducer.h in Headers */ = {isa = PBXBuildFile; fileRef = 6B12F7642C94312D0045D743 /* CTJsonTemplateProducer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6B12F7692C9466460045D743 /* CTJsonTemplateProducerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B12F7682C9466460045D743 /* CTJsonTemplateProducerTest.m */; }; 6B32A09E2B9901AA009ADC57 /* CTCustomTemplateBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 6B32A09C2B9901AA009ADC57 /* CTCustomTemplateBuilder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6B32A09F2B9901AA009ADC57 /* CTCustomTemplateBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B32A09D2B9901AA009ADC57 /* CTCustomTemplateBuilder.m */; }; 6B32A0A12B99033F009ADC57 /* CTCustomTemplateBuilder-Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 6B32A0A02B99033F009ADC57 /* CTCustomTemplateBuilder-Internal.h */; }; @@ -891,6 +894,9 @@ 6AF6C7FF2A1BDA6A001E38A8 /* LeanplumCT.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LeanplumCT.h; sourceTree = ""; }; 6AF6C8002A1BDA6A001E38A8 /* LeanplumCT.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LeanplumCT.m; sourceTree = ""; }; 6B0063BA2B18EC9E0063BF79 /* image_interstitial.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = image_interstitial.html; sourceTree = ""; }; + 6B12F7642C94312D0045D743 /* CTJsonTemplateProducer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CTJsonTemplateProducer.h; sourceTree = ""; }; + 6B12F7652C94312D0045D743 /* CTJsonTemplateProducer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTJsonTemplateProducer.m; sourceTree = ""; }; + 6B12F7682C9466460045D743 /* CTJsonTemplateProducerTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTJsonTemplateProducerTest.m; sourceTree = ""; }; 6B32A09C2B9901AA009ADC57 /* CTCustomTemplateBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CTCustomTemplateBuilder.h; sourceTree = ""; }; 6B32A09D2B9901AA009ADC57 /* CTCustomTemplateBuilder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTCustomTemplateBuilder.m; sourceTree = ""; }; 6B32A0A02B99033F009ADC57 /* CTCustomTemplateBuilder-Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CTCustomTemplateBuilder-Internal.h"; sourceTree = ""; }; @@ -1500,6 +1506,7 @@ 6B9157B82C10F40C00B1C907 /* CTInAppNotificationDisplayDelegateMock.m */, 6BAFFE9A2C371B4500654CAF /* CTFileDownloaderCustomTemplatesMock.h */, 6BAFFE9B2C371B4500654CAF /* CTFileDownloaderCustomTemplatesMock.m */, + 6B12F7682C9466460045D743 /* CTJsonTemplateProducerTest.m */, ); path = CustomTemplates; sourceTree = ""; @@ -1560,6 +1567,8 @@ 6BB778C62BECEC2700A41628 /* CTCustomTemplateInAppData.m */, 6BB778D82BFD277400A41628 /* CTCustomTemplatesManager-Internal.h */, 6B9157BA2C11D07200B1C907 /* CTCustomTemplateInAppData-Internal.h */, + 6B12F7642C94312D0045D743 /* CTJsonTemplateProducer.h */, + 6B12F7652C94312D0045D743 /* CTJsonTemplateProducer.m */, ); path = CustomTemplates; sourceTree = ""; @@ -1973,6 +1982,7 @@ 6A4427B92AA3903C0098866F /* CTTriggerCondition.h in Headers */, 4E49AE53275D24570074A774 /* CTValidationResultStack.h in Headers */, 07B94544219EA34300D4C542 /* CTInboxController.h in Headers */, + 6B12F7672C94312D0045D743 /* CTJsonTemplateProducer.h in Headers */, 4E838C40299F419900ED0875 /* ContentMerger.h in Headers */, 5709005327FD8E1F0011B89F /* CleverTap+SCDomain.h in Headers */, 6BB727152B8E463C009CE7D0 /* CTCustomTemplate.h in Headers */, @@ -2522,6 +2532,7 @@ 32394C2129FA264B00956058 /* CTPreferencesTest.m in Sources */, 6BD334F02AF545C80099E33E /* CTInAppStoreTest.m in Sources */, 32394C1F29FA251E00956058 /* CTEventBuilderTest.m in Sources */, + 6B12F7692C9466460045D743 /* CTJsonTemplateProducerTest.m in Sources */, 6BB778D02BEE4C3400A41628 /* CTNotificationActionTest.m in Sources */, D02AC2EB2767F4590031C1BE /* BaseTestCase.m in Sources */, 6BAFFE9C2C371B4500654CAF /* CTFileDownloaderCustomTemplatesMock.m in Sources */, @@ -2590,6 +2601,7 @@ D01651AF2097B38400660178 /* CTEventBuilder.m in Sources */, D020C929209006AD0073F61E /* CTUriHelper.m in Sources */, 07B9454C219EA34300D4C542 /* CTMessageMO.m in Sources */, + 6B12F7662C94312D0045D743 /* CTJsonTemplateProducer.m in Sources */, 48F9FD1E2C3D30BF00617770 /* CTFileDownloader.m in Sources */, D0D4C9F32414EE6C0029477E /* CleverTapFeatureFlags.m in Sources */, 4E5A02DE2A4C5FD800DE242A /* LeanplumCT.m in Sources */, diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index 10faab57..8e18b435 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -20,6 +20,8 @@ extern NSString *const kSessionId; @catch (NSException *e) {\ [CTLogger logInternalError:e]; } +#define CLTAP_CUSTOM_TEMPLATE_EXCEPTION @"CleverTapCustomTemplateException" + #pragma mark Constants for General data #define CLTAP_REQUEST_TIME_OUT_INTERVAL 10 #define CLTAP_ACCOUNT_ID_LABEL @"CleverTapAccountID" diff --git a/CleverTapSDK/CleverTap.h b/CleverTapSDK/CleverTap.h index c920566d..d3a4a100 100644 --- a/CleverTapSDK/CleverTap.h +++ b/CleverTapSDK/CleverTap.h @@ -25,6 +25,7 @@ #if !CLEVERTAP_NO_INAPP_SUPPORT @protocol CleverTapInAppNotificationDelegate; @class CTTemplateContext; +@protocol CTTemplateProducer; #endif @protocol CTBatchSentDelegate; @@ -1441,6 +1442,22 @@ extern NSString * _Nonnull const CleverTapProfileDidInitializeNotification; #if !CLEVERTAP_NO_INAPP_SUPPORT #pragma mark Custom Templates and Functions +/*! + Register ``CTCustomTemplate`` templates through a ``CTTemplateProducer``. + See ``CTCustomTemplateBuilder``. Templates must be registered before the ``CleverTap`` instance, that would use + them, is created. + + Typically, this method is called from `UIApplicationDelegate/application:didFinishLaunchingWithOptions:`. + If your application uses multiple ``CleverTap`` instances, use the ``CleverTapInstanceConfig`` within the + ``CTTemplateProducer/defineTemplates:`` method to differentiate which templates should be registered to which instances. + + This method can be called multiple times with different ``CTTemplateProducer`` producers, however all of the + produced templates must have unique names. + + @param producer A ``CTTemplateProducer`` to register and define templates with. + */ ++ (void)registerCustomInAppTemplates:(id _Nonnull)producer; + /*! @method diff --git a/CleverTapSDK/CleverTap.m b/CleverTapSDK/CleverTap.m index de8d1ca8..ac9aaa79 100644 --- a/CleverTapSDK/CleverTap.m +++ b/CleverTapSDK/CleverTap.m @@ -1591,6 +1591,10 @@ - (void)clearInAppResources:(BOOL)expiredOnly { [self.fileDownloader clearFileAssets:expiredOnly]; } ++ (void)registerCustomInAppTemplates:(id _Nonnull)producer { + [CTCustomTemplatesManager registerTemplateProducer:producer]; +} + - (CTTemplateContext * _Nullable)activeContextForTemplate:(NSString * _Nonnull)templateName { return [[self customTemplatesManager] activeContextForTemplate:templateName]; } diff --git a/CleverTapSDK/InApps/CustomTemplates/CTAppFunctionBuilder.h b/CleverTapSDK/InApps/CustomTemplates/CTAppFunctionBuilder.h index ee5647a8..30459c7b 100644 --- a/CleverTapSDK/InApps/CustomTemplates/CTAppFunctionBuilder.h +++ b/CleverTapSDK/InApps/CustomTemplates/CTAppFunctionBuilder.h @@ -11,8 +11,19 @@ NS_ASSUME_NONNULL_BEGIN +/*! + Builder for ``CTCustomTemplate`` functions. See ``CTCustomTemplateBuilder``. + */ @interface CTAppFunctionBuilder : CTCustomTemplateBuilder +/*! + Use `isVisual` to set if the template has UI or not. + If set to `YES` the template is registered as part of the in-apps queue + and must be explicitly dismissed before other in-apps can be shown. + If set to `NO` the template is executed directly and does not require dismissal nor it impedes other in-apps. + + @param isVisual Whether the function will present UI. + */ - (instancetype)initWithIsVisual:(BOOL)isVisual; @end diff --git a/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplate.h b/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplate.h index 2fd46e65..b326efc9 100644 --- a/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplate.h +++ b/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplate.h @@ -10,9 +10,20 @@ NS_ASSUME_NONNULL_BEGIN +/*! + A definition of a custom template. Can be a function or a code template. + Instances are uniquely identified by their name. + */ @interface CTCustomTemplate : NSObject +/*! + The name of the template. + */ @property (nonatomic, strong, readonly) NSString *name; + +/*! + Whether the template has UI or not. + */ @property (nonatomic, readonly) BOOL isVisual; - (instancetype)init NS_UNAVAILABLE; diff --git a/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplateBuilder.h b/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplateBuilder.h index 6a9df22b..a713db48 100644 --- a/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplateBuilder.h +++ b/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplateBuilder.h @@ -15,10 +15,42 @@ NS_ASSUME_NONNULL_BEGIN +/*! + Builder for ``CTCustomTemplate``s creation. Set `name` and `presenter` before calling ``build``. + Arguments can be specified by using one of the `addArgument:` methods. Argument names must be unique. + The "." characters in template arguments' names denote hierarchical structure. + They are treated the same way as the keys within dictionaries passed to ``addArgument:withDictionary:``. + If a higher-level name (to the left of a "." symbol) matches a dictionary argument's name, + it is treated the same as if the argument was part of the dictionary itself. + + For example, the following code snippets define identical arguments: + ``` + [builder addArgument:@"map" withDictionary:@{ + @"a": @5, + @"b": @6 + }]; + ``` + and + ``` + [builder addArgument:@"map.a" withNumber:@5]; + [builder addArgument:@"map.b" withNumber:@6]; + ``` + + Methods of this class throw `NSException` with name `CleverTapCustomTemplateException` + for invalid states or parameters. Defined templates must be correct when the app is running. If such an + exception is thrown the template definition must be corrected instead of handling the error. + */ @interface CTCustomTemplateBuilder : NSObject - (instancetype)init NS_UNAVAILABLE; +/*! + The name for the template. It should be provided exactly once. + It must be unique across template definitions. + Must be non-blank. + + This method throws `NSException` with name `CleverTapCustomTemplateException` if the name is already set or the provided name is blank. + */ - (void)setName:(NSString *)name; - (void)addArgument:(NSString *)name withString:(NSString *)defaultValue @@ -30,13 +62,29 @@ NS_SWIFT_NAME(addArgument(_:number:)); - (void)addArgument:(NSString *)name withBool:(BOOL)defaultValue NS_SWIFT_NAME(addArgument(_:boolean:)); +/*! + Add a dictionary structure to the arguments of the ``CTCustomTemplate``. + The `name` should be unique across all arguments and also + all keys in `defaultValue` should form unique names across all arguments. + + @param defaultValue The dictionary must be non-empty. Values can be of type `NSNumber` or `NSString` or another `NSDictionary` which values can also be of the same types. + */ - (void)addArgument:(nonnull NSString *)name withDictionary:(nonnull NSDictionary *)defaultValue NS_SWIFT_NAME(addArgument(_:dictionary:)); - (void)addFileArgument:(NSString *)name; +/*! + The presenter for this template. See ``CTTemplatePresenter``. + */ - (void)setPresenter:(id)presenter; +/*! + Creates the ``CTCustomTemplate`` with the previously defined name, arguments and presenter. + Name and presenter must be set before calling this method. + + This method throws `NSException` with name `CleverTapCustomTemplateException` if name or presenter were not set. + */ - (CTCustomTemplate *)build; @end diff --git a/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplateBuilder.m b/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplateBuilder.m index f7679fda..ab30ba4b 100644 --- a/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplateBuilder.m +++ b/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplateBuilder.m @@ -10,6 +10,7 @@ #import "CTCustomTemplateBuilder-Internal.h" #import "CTCustomTemplate-Internal.h" #import "CTTemplatePresenter.h" +#import "CTConstants.h" @implementation CTCustomTemplateBuilder @@ -39,21 +40,21 @@ - (void)addArgumentWithName:(NSString *)name type:(CTTemplateArgumentType)type d if (!name || [name isEqualToString:@""]) { @throw([NSException - exceptionWithName:@"CleverTap Error" + exceptionWithName:CLTAP_CUSTOM_TEMPLATE_EXCEPTION reason:@"CleverTap: Argument Name cannot be null or empty." userInfo:nil]); } if ([name hasPrefix:@"."] || [name hasSuffix:@"."] || [name containsString:@".."]) { @throw([NSException - exceptionWithName:@"CleverTap Error" + exceptionWithName:CLTAP_CUSTOM_TEMPLATE_EXCEPTION reason:@"CleverTap: Argument Name cannot start or end with dot \".\" or contain consecutive dots \"..\"." userInfo:nil]); } if ([self.argumentNames containsObject:name]) { @throw([NSException - exceptionWithName:@"CleverTap Error" + exceptionWithName:CLTAP_CUSTOM_TEMPLATE_EXCEPTION reason:[NSString stringWithFormat:@"CleverTap: Argument with the name \"%@\" already defined.", name] userInfo:nil]); } @@ -61,7 +62,7 @@ - (void)addArgumentWithName:(NSString *)name type:(CTTemplateArgumentType)type d if (![self.nullableArgumentTypes containsObject:@(type)] && (defaultValue == nil || [defaultValue isEqual:[NSNull null]])) { @throw([NSException - exceptionWithName:@"CleverTap Error" + exceptionWithName:CLTAP_CUSTOM_TEMPLATE_EXCEPTION reason:[NSString stringWithFormat:@"CleverTap: Default value cannot be nil."] userInfo:nil]); } @@ -86,7 +87,7 @@ - (void)validateParentNames:(NSString *)name { if ([self.argumentNames containsObject:parentName]) { @throw([NSException - exceptionWithName:@"CleverTap Error" + exceptionWithName:CLTAP_CUSTOM_TEMPLATE_EXCEPTION reason:[NSString stringWithFormat:@"CleverTap: Argument with the name \"%@\" already defined.", parentName] userInfo:nil]); } @@ -95,7 +96,7 @@ - (void)validateParentNames:(NSString *)name { if ([self.parentArgumentNames containsObject:name]) { @throw([NSException - exceptionWithName:@"CleverTap Error" + exceptionWithName:CLTAP_CUSTOM_TEMPLATE_EXCEPTION reason:[NSString stringWithFormat:@"CleverTap: Argument with the name \"%@\" already defined.", name] userInfo:nil]); } @@ -116,7 +117,7 @@ - (void)addArgument:(NSString *)name withNumber:(NSNumber *)defaultValue { - (void)addArgument:(nonnull NSString *)name withDictionary:(nonnull NSDictionary *)defaultValue { if (defaultValue == nil || [defaultValue count] == 0) { @throw([NSException - exceptionWithName:@"CleverTap Error" + exceptionWithName:CLTAP_CUSTOM_TEMPLATE_EXCEPTION reason:[NSString stringWithFormat:@"CleverTap: Default value cannot be nil or empty."] userInfo:nil]); } @@ -153,14 +154,14 @@ - (void)addFileArgument:(NSString *)name { - (void)setName:(NSString *)name { if (_name) { @throw([NSException - exceptionWithName:@"CleverTap Error" + exceptionWithName:CLTAP_CUSTOM_TEMPLATE_EXCEPTION reason:@"CleverTap: Name is already set." userInfo:nil]); } if (name == nil || [name isEqualToString:@""]) { @throw([NSException - exceptionWithName:@"CleverTap Error" + exceptionWithName:CLTAP_CUSTOM_TEMPLATE_EXCEPTION reason:@"CleverTap: Name cannot be null or empty." userInfo:nil]); } @@ -175,14 +176,14 @@ - (void)setPresenter:(id)presenter { - (CTCustomTemplate *)build { if (!self.name || [self.name isEqualToString:@""]) { @throw([NSException - exceptionWithName:@"CleverTap Error" + exceptionWithName:CLTAP_CUSTOM_TEMPLATE_EXCEPTION reason:[NSString stringWithFormat:@"CleverTap: Name cannot be null or empty. Use setName to set it."] userInfo:nil]); } if (!self.presenter) { @throw([NSException - exceptionWithName:@"CleverTap Error" + exceptionWithName:CLTAP_CUSTOM_TEMPLATE_EXCEPTION reason:[NSString stringWithFormat:@"CleverTap: Presenter cannot be null. Use setOnPresentWithPresenter to set it."] userInfo:nil]); } diff --git a/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplatesManager.m b/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplatesManager.m index 0dabf21c..274f88f0 100644 --- a/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplatesManager.m +++ b/CleverTapSDK/InApps/CustomTemplates/CTCustomTemplatesManager.m @@ -51,7 +51,7 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig *)instanceConfig { self.templates[template.name] = template; } else { @throw([NSException - exceptionWithName:@"CleverTap Error" + exceptionWithName:CLTAP_CUSTOM_TEMPLATE_EXCEPTION reason:[NSString stringWithFormat:@"CleverTap: Template with name: %@ is already defined.", template.name] userInfo:nil]); } @@ -229,7 +229,7 @@ - (NSMutableDictionary *)argumentPayload:(CTTemplateArgument *)arg order:(int)or if (defaultValue) { argument[@"defaultValue"] = defaultValue; } - NSString *type = [CTTemplateArgument templateArgumentTypeString:arg.type]; + NSString *type = [CTTemplateArgument templateArgumentTypeToString:arg.type]; if (type) { argument[@"type"] = type; } diff --git a/CleverTapSDK/InApps/CustomTemplates/CTInAppTemplateBuilder.h b/CleverTapSDK/InApps/CustomTemplates/CTInAppTemplateBuilder.h index c3e9fff5..e7805d08 100644 --- a/CleverTapSDK/InApps/CustomTemplates/CTInAppTemplateBuilder.h +++ b/CleverTapSDK/InApps/CustomTemplates/CTInAppTemplateBuilder.h @@ -11,10 +11,18 @@ NS_ASSUME_NONNULL_BEGIN +/*! + Builder for ``CTCustomTemplate`` code templates. See ``CTCustomTemplateBuilder``. + */ @interface CTInAppTemplateBuilder : CTCustomTemplateBuilder - (instancetype)init; +/*! + Action arguments are specified by name only. When the ``CTCustomTemplate`` is triggered, the configured action + can be executed through ``CTTemplateContext/triggerActionNamed:``. + Action values could either be a predefined action (like close or open-url) or а registered ``CTCustomTemplate`` function. + */ - (void)addActionArgument:(NSString *)name; @end diff --git a/CleverTapSDK/InApps/CustomTemplates/CTJsonTemplateProducer.h b/CleverTapSDK/InApps/CustomTemplates/CTJsonTemplateProducer.h new file mode 100644 index 00000000..1e6aec23 --- /dev/null +++ b/CleverTapSDK/InApps/CustomTemplates/CTJsonTemplateProducer.h @@ -0,0 +1,93 @@ +// +// CTJsonTemplateProducer.h +// CleverTapSDK +// +// Created by Nikola Zagorchev on 13.09.24. +// Copyright © 2024 CleverTap. All rights reserved. +// + +#import +#import "CTTemplatePresenter.h" +#import "CTTemplateProducer.h" +#import "CTCustomTemplate.h" +#import "CleverTapInstanceConfig.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! + A ``CTTemplateProducer`` that creates templates based on a json definition. + */ +@interface CTJsonTemplateProducer : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +/*! + Creates a template producer with a json definition, template presenter and function presenter. + + See ``CTTemplatePresenter`` for more information on the template/function presenter. + + Invalid definitions throw `NSException` with name `CleverTapCustomTemplateException` when ``defineTemplates:`` is called. + + @param jsonTemplatesDefinition A string with a json definition of templates in the following format: + ``` + { + "TemplateName": { + "type": "template", + "arguments": { + "Argument1": { + "type": "string|number|boolean|file|action|object", + "value": "val" // different type depending on "type", e.g 12.5, true, "str" or {} + }, + "Argument2": { + "type": "object", + "value": { + "Nested1": { + "type": "string|number|boolean|object", // file and action cannot be nested + "value": {} + }, + "Nested2": { + "type": "string|number|boolean|object", + "value": "val" + } + } + } + } + }, + "functionName": { + "type": "function", + "isVisual": true|false, + "arguments": { + "a": { + "type": "string|number|boolean|file|object", // action arguments are not supported for functions + "value": "val" + } + } + } + } + ``` + + @param templatePresenter A presenter for all templates in the json definitions. Required if there + is at least one template with type "template". + + @param functionPresenter A presenter for all functions in the json definitions. Required if there + is at least one template with type "function". + */ +- (nonnull instancetype)initWithJson:(nonnull NSString *)jsonTemplatesDefinition + templatePresenter:(nonnull id)templatePresenter + functionPresenter:(nonnull id)functionPresenter; + + +/*! + Creates ``CTCustomTemplate``s based on the `jsonTemplatesDefinition` this ``CTJsonTemplateProducer`` was initialized with. + + @param instanceConfig The config of the CleverTap instance. + @return A set of the custom templates created. + + This method throws an `NSException` with name `CleverTapCustomTemplateException` if an invalid JSON format or values occur while parsing `jsonTemplatesDefinition`. + See the exception reason for details. + */ +- (NSSet * _Nonnull)defineTemplates:(CleverTapInstanceConfig * _Nonnull)instanceConfig; + +@end + +NS_ASSUME_NONNULL_END diff --git a/CleverTapSDK/InApps/CustomTemplates/CTJsonTemplateProducer.m b/CleverTapSDK/InApps/CustomTemplates/CTJsonTemplateProducer.m new file mode 100644 index 00000000..d17b9e2c --- /dev/null +++ b/CleverTapSDK/InApps/CustomTemplates/CTJsonTemplateProducer.m @@ -0,0 +1,192 @@ +// +// CTJsonTemplateProducer.m +// CleverTapSDK +// +// Created by Nikola Zagorchev on 13.09.24. +// Copyright © 2024 CleverTap. All rights reserved. +// + +#import "CTJsonTemplateProducer.h" +#import "CTCustomTemplateBuilder.h" +#import "CTInAppTemplateBuilder.h" +#import "CTAppFunctionBuilder.h" +#import "CTTemplateArgument.h" +#import "CTConstants.h" + +@interface CTJsonTemplateProducer () + +@property (nonatomic, strong) NSString *jsonTemplatesDefinition; +@property (nonatomic, strong) id templatePresenter; +@property (nonatomic, strong) id functionPresenter; + +@end + +@implementation CTJsonTemplateProducer + +- (nonnull instancetype)initWithJson:(nonnull NSString *)jsonTemplatesDefinition + templatePresenter:(nonnull id)templatePresenter + functionPresenter:(nonnull id)functionPresenter { + if (self = [super init]) { + self.jsonTemplatesDefinition = jsonTemplatesDefinition; + self.templatePresenter = templatePresenter; + self.functionPresenter = functionPresenter; + } + return self; +} + +- (NSSet * _Nonnull)defineTemplates:(CleverTapInstanceConfig * _Nonnull)instanceConfig { + if (!self.jsonTemplatesDefinition || [self.jsonTemplatesDefinition length] == 0) { + @throw([NSException + exceptionWithName:CLTAP_CUSTOM_TEMPLATE_EXCEPTION + reason:@"CleverTap: JSON template definitions cannot be nil or empty." + userInfo:nil]); + } + + NSData *jsonData = [self.jsonTemplatesDefinition dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *jsonDefinitions = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error]; + + if (error) { + @throw([NSException + exceptionWithName:CLTAP_CUSTOM_TEMPLATE_EXCEPTION + reason:[NSString stringWithFormat:@"CleverTap: Error parsing JSON template definitions: %@.", error.localizedDescription] + userInfo:nil]); + } + + NSMutableSet *templates = [NSMutableSet set]; + for (NSString *key in jsonDefinitions) { + NSDictionary *item = jsonDefinitions[key]; + NSString *type = item[@"type"]; + NSDictionary *arguments = item[@"arguments"]; + + CTCustomTemplateBuilder *builder; + if ([type isEqualToString:TEMPLATE_TYPE]) { + if (!self.templatePresenter) { + @throw([NSException + exceptionWithName:CLTAP_CUSTOM_TEMPLATE_EXCEPTION + reason:@"CleverTap: JSON definition contains a template definition and a template presenter is required." + userInfo:nil]); + } + + builder = [CTInAppTemplateBuilder new]; + [builder setPresenter:self.templatePresenter]; + } else if ([type isEqualToString:FUNCTION_TYPE]) { + if (!self.functionPresenter) { + @throw([NSException + exceptionWithName:CLTAP_CUSTOM_TEMPLATE_EXCEPTION + reason:@"CleverTap: JSON definition contains a function definition and a function presenter is required." + userInfo:nil]); + } + + BOOL isVisual = NO; + if (item[@"isVisual"]) { + isVisual = [item[@"isVisual"] boolValue]; + } + builder = [[CTAppFunctionBuilder alloc] initWithIsVisual:isVisual]; + [builder setPresenter:self.functionPresenter]; + } else { + @throw([NSException + exceptionWithName:CLTAP_CUSTOM_TEMPLATE_EXCEPTION + reason:[NSString stringWithFormat:@"Unknown template type: %@ for template: %@.", type, key] + userInfo:nil]); + } + [builder setName:key]; + + [self addJsonArguments:arguments toBuilder:builder]; + + CTCustomTemplate *customTemplate = [builder build]; + [templates addObject:customTemplate]; + } + return templates; +} + +- (void)addJsonArguments:(NSDictionary *)arguments toBuilder:(CTCustomTemplateBuilder *)builder { + for (NSString *argKey in arguments) { + NSDictionary *arg = arguments[argKey]; + NSString *argType = arg[@"type"]; + id value = arg[@"value"]; + + if ([argType isEqualToString:@"object"]) { + NSDictionary *dictValue = [self objectArgumentJsonToDictionary:value]; + [builder addArgument:argKey withDictionary:dictValue]; + continue; + } + + if ([argType isEqualToString:[CTTemplateArgument templateArgumentTypeToString:CTTemplateArgumentTypeString]]) { + NSString *stringValue = value; + [builder addArgument:argKey withString:stringValue]; + } else if ([argType isEqualToString:[CTTemplateArgument templateArgumentTypeToString:CTTemplateArgumentTypeNumber]]) { + NSNumber *numberValue = value; + [builder addArgument:argKey withNumber:numberValue]; + } else if ([argType isEqualToString:[CTTemplateArgument templateArgumentTypeToString:CTTemplateArgumentTypeBool]]) { + BOOL boolValue = [value boolValue]; + [builder addArgument:argKey withBool:boolValue]; + } else if ([argType isEqualToString:[CTTemplateArgument templateArgumentTypeToString:CTTemplateArgumentTypeFile]]) { + if (value) { + @throw([NSException + exceptionWithName:CLTAP_CUSTOM_TEMPLATE_EXCEPTION + reason:[NSString stringWithFormat:@"CleverTap: File arguments should not specify a value. Remove value from argument: %@.", argKey] + userInfo:nil]); + } + [builder addFileArgument:argKey]; + } else if ([argType isEqualToString:[CTTemplateArgument templateArgumentTypeToString:CTTemplateArgumentTypeAction]]) { + if (value) { + @throw([NSException + exceptionWithName:CLTAP_CUSTOM_TEMPLATE_EXCEPTION + reason:[NSString stringWithFormat:@"CleverTap: Action arguments should not specify a value. Remove value from argument: %@.", argKey] + userInfo:nil]); + } + + if (![builder isKindOfClass:CTInAppTemplateBuilder.class]) { + @throw([NSException + exceptionWithName:CLTAP_CUSTOM_TEMPLATE_EXCEPTION + reason:[NSString stringWithFormat:@"Function templates cannot have action arguments. Remove argument: %@.", argKey] + userInfo:nil]); + } + [(CTInAppTemplateBuilder *)builder addActionArgument:argKey]; + } else { + @throw([NSException + exceptionWithName:CLTAP_CUSTOM_TEMPLATE_EXCEPTION + reason:[NSString stringWithFormat:@"Unknown argument type: %@ for argument: %@.", argType, argKey] + userInfo:nil]); + } + } +} + +- (NSDictionary *)objectArgumentJsonToDictionary:(NSDictionary *)objectArgumentJson { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + + NSSet *supportedNestedTypes = [NSSet setWithArray:@[ + [CTTemplateArgument templateArgumentTypeToString:CTTemplateArgumentTypeBool], + [CTTemplateArgument templateArgumentTypeToString:CTTemplateArgumentTypeString], + [CTTemplateArgument templateArgumentTypeToString:CTTemplateArgumentTypeNumber] + ]]; + + for (id argName in objectArgumentJson) { + NSDictionary *argJson = objectArgumentJson[argName]; + NSString *argType = argJson[@"type"]; + if ([argType isEqualToString:@"object"]) { + NSDictionary *value = argJson[@"value"]; + dictionary[argName] = [self objectArgumentJsonToDictionary:value]; + continue; + } + + if (![supportedNestedTypes containsObject:argType]) { + NSString *reason = + [NSString stringWithFormat:@"CleverTap: Not supported argument type: %@, for argument: %@. %@. %@", + argType, + argName, + @"Nesting of file and action arguments within objects is not supported", + @"To define nested file and actions use dot '.' notation in the argument name"]; + @throw([NSException + exceptionWithName:CLTAP_CUSTOM_TEMPLATE_EXCEPTION + reason:reason + userInfo:nil]); + } + + dictionary[argName] = argJson[@"value"]; + } + return dictionary; +} + +@end diff --git a/CleverTapSDK/InApps/CustomTemplates/CTTemplateArgument.h b/CleverTapSDK/InApps/CustomTemplates/CTTemplateArgument.h index 39676d6a..ced6e2c4 100644 --- a/CleverTapSDK/InApps/CustomTemplates/CTTemplateArgument.h +++ b/CleverTapSDK/InApps/CustomTemplates/CTTemplateArgument.h @@ -27,6 +27,6 @@ typedef NS_ENUM(NSInteger, CTTemplateArgumentType) { type:(CTTemplateArgumentType)type defaultValue:(id _Nullable)defaultValue; -+ (NSString * _Nonnull)templateArgumentTypeString:(CTTemplateArgumentType)type; ++ (NSString * _Nonnull)templateArgumentTypeToString:(CTTemplateArgumentType)type; @end diff --git a/CleverTapSDK/InApps/CustomTemplates/CTTemplateArgument.m b/CleverTapSDK/InApps/CustomTemplates/CTTemplateArgument.m index 513318ba..152b9578 100644 --- a/CleverTapSDK/InApps/CustomTemplates/CTTemplateArgument.m +++ b/CleverTapSDK/InApps/CustomTemplates/CTTemplateArgument.m @@ -20,7 +20,7 @@ - (instancetype)initWithName:(NSString *)name type:(CTTemplateArgumentType)type return self; } -+ (NSString *)templateArgumentTypeString:(CTTemplateArgumentType)type { ++ (NSString *)templateArgumentTypeToString:(CTTemplateArgumentType)type { static NSDictionary *enumStringMap = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ @@ -75,7 +75,7 @@ - (NSString *)description { [self class], self, self.name, - [CTTemplateArgument templateArgumentTypeString:self.type], + [CTTemplateArgument templateArgumentTypeToString:self.type], self.defaultValue]; } diff --git a/CleverTapSDK/InApps/CustomTemplates/CTTemplateContext.h b/CleverTapSDK/InApps/CustomTemplates/CTTemplateContext.h index e7221240..8160f168 100644 --- a/CleverTapSDK/InApps/CustomTemplates/CTTemplateContext.h +++ b/CleverTapSDK/InApps/CustomTemplates/CTTemplateContext.h @@ -10,60 +10,129 @@ NS_ASSUME_NONNULL_BEGIN +/*! + Representation of the context around a ``CTCustomTemplate``. Use the `Named:` methods to obtain the + current values of the arguments. Use ``triggerActionNamed:`` to trigger template actions. + Use ``presented`` and ``dismissed`` to notify the SDK of the current state of this InApp context. + */ @interface CTTemplateContext : NSObject - (instancetype)init NS_UNAVAILABLE; +/*! + The name of the template or function. + + @return The name of the ``CTCustomTemplate``. + */ - (NSString *)templateName NS_SWIFT_NAME(name()); +/*! + Retrieve a `NSString` argument by `name`. + + @return The argument value or `nil` if no such argument is defined for the ``CTCustomTemplate``. + */ - (nullable NSString *)stringNamed:(NSString *)name NS_SWIFT_NAME(string(name:)); +/*! + Retrieve a `NSNumber` argument by `name`. + + @return The argument value or `nil` if no such argument is defined for the ``CTCustomTemplate``. + */ - (nullable NSNumber *)numberNamed:(NSString *)name NS_SWIFT_NAME(number(name:)); +/*! + Retrieve a `char` argument by `name`. + + @return The argument value or `0` if no such argument is defined for the ``CTCustomTemplate``. + */ - (int)charNamed:(NSString *)name NS_SWIFT_NAME(char(name:)); +/*! + Retrieve an `int` argument by `name`. + + @return The argument value or `0` if no such argument is defined for the ``CTCustomTemplate``. + */ - (int)intNamed:(NSString *)name NS_SWIFT_NAME(int(name:)); +/*! + Retrieve a `double` argument by `name`. + + @return The argument value or `0` if no such argument is defined for the ``CTCustomTemplate``. + */ - (double)doubleNamed:(NSString *)name NS_SWIFT_NAME(double(name:)); +/*! + Retrieve a `float` argument by `name`. + + @return The argument value or `0` if no such argument is defined for the ``CTCustomTemplate``. + */ - (float)floatNamed:(NSString *)name NS_SWIFT_NAME(float(name:)); +/*! + Retrieve a `long` argument by `name`. + + @return The argument value or `0` if no such argument is defined for the ``CTCustomTemplate``. + */ - (long)longNamed:(NSString *)name NS_SWIFT_NAME(long(name:)); +/*! + Retrieve a `long long` argument by `name`. + + @return The argument value or `0` if no such argument is defined for the ``CTCustomTemplate``. + */ - (long long)longLongNamed:(NSString *)name NS_SWIFT_NAME(longLong(name:)); +/*! + Retrieve a `BOOL` argument by `name`. + + @return The argument value or `NO` if no such argument is defined for the ``CTCustomTemplate``. + */ - (BOOL)boolNamed:(NSString *)name NS_SWIFT_NAME(boolean(name:)); +/*! + Retrieve a dictionary of all arguments under `name`. Dictionary arguments will be combined with dot notation arguments. All + values are converted to their defined type in the ``CTCustomTemplate``. Action arguments are mapped to their + name as `NSString`. Returns `nil` if no arguments are found for the requested map. + + @return A dictionary of all arguments under `name` or `nil`. + */ - (nullable NSDictionary *)dictionaryNamed:(NSString *)name NS_SWIFT_NAME(dictionary(name:)); +/*! + Retrieve an absolute file path argument by `name`. + + @return The argument value or `nil` if no such argument is defined for the ``CTCustomTemplate``. + */ - (nullable NSString *)fileNamed:(NSString *)name NS_SWIFT_NAME(file(name:)); -/** - * Call this method to notify the SDK the template is presented. +/*! + Call this method to notify the SDK the ``CTCustomTemplate`` is presented. */ - (void)presented; -/** - * Executes the action given by the "name" key. - * Records Notification Clicked event. +/*! + Trigger an action argument by `name`. + Records a "Notification Clicked" event. */ - (void)triggerActionNamed:(NSString *)name NS_SWIFT_NAME(triggerAction(name:)); -/** - * Call this method to notify the SDK the template is dismissed. +/*! + Notify the SDK that the current ``CTCustomTemplate`` is dismissed. The current ``CTCustomTemplate`` is considered to be + visible to the user until this method is called. Since the SDK can show only one InApp message at a time, all + other messages will be queued until the current one is dismissed. */ - (void)dismissed; diff --git a/CleverTapSDK/InApps/CustomTemplates/CTTemplatePresenter.h b/CleverTapSDK/InApps/CustomTemplates/CTTemplatePresenter.h index c0bd66c1..bc0db098 100644 --- a/CleverTapSDK/InApps/CustomTemplates/CTTemplatePresenter.h +++ b/CleverTapSDK/InApps/CustomTemplates/CTTemplatePresenter.h @@ -13,11 +13,26 @@ NS_ASSUME_NONNULL_BEGIN +/*! + A handler of custom code templates ``CTCustomTemplate``. Its methods are called when the corresponding InApp + message should be presented to the user or closed. + */ @protocol CTTemplatePresenter +/*! + Called when a ``CTCustomTemplate`` should be presented or a function should be executed. For visual templates + (code templates and functions with ``CTCustomTemplate/isVisual`` equals `YES`) implementing classes should use the provided + ``CTTemplateContext`` methods ``CTTemplateContext/presented`` and + ``CTTemplateContext/dismissed`` to notify the SDK of the state of the template invocation. Only + one visual template or other InApp message can be displayed at a time by the SDK and no new messages can be + shown until the current one is dismissed. + */ - (void)onPresent:(CTTemplateContext *)context NS_SWIFT_NAME(onPresent(context:)); +/*! + Called when a ``CTCustomTemplate`` action Notification Close is executed. Dismiss the custom template InApp and call ``CTTemplateContext/dismissed`` to notify the SDK the template is dismissed. + */ - (void)onCloseClicked:(CTTemplateContext *)context NS_SWIFT_NAME(onCloseClicked(context:)); diff --git a/CleverTapSDK/InApps/CustomTemplates/CTTemplateProducer.h b/CleverTapSDK/InApps/CustomTemplates/CTTemplateProducer.h index 64d679a6..c4e727e7 100644 --- a/CleverTapSDK/InApps/CustomTemplates/CTTemplateProducer.h +++ b/CleverTapSDK/InApps/CustomTemplates/CTTemplateProducer.h @@ -14,6 +14,13 @@ @protocol CTTemplateProducer +/*! + Defines custom templates. + + @param instanceConfig Use the config to decide which instance the templates are defined for. + + @return A set of ``CTCustomTemplate`` definitions. ``CTCustomTemplate``s are uniquely identified by their name. + */ - (NSSet * _Nonnull)defineTemplates:(CleverTapInstanceConfig * _Nonnull)instanceConfig; @end diff --git a/CleverTapSDKTests/InApps/CustomTemplates/CTCustomTemplateBuilderTest.m b/CleverTapSDKTests/InApps/CustomTemplates/CTCustomTemplateBuilderTest.m index cf28f803..152bdc1d 100644 --- a/CleverTapSDKTests/InApps/CustomTemplates/CTCustomTemplateBuilderTest.m +++ b/CleverTapSDKTests/InApps/CustomTemplates/CTCustomTemplateBuilderTest.m @@ -13,6 +13,7 @@ #import "CTAppFunctionBuilder.h" #import "CTCustomTemplate-Internal.h" #import "CTTemplatePresenterMock.h" +#import "CTConstants.h" @interface CTCustomTemplateBuilderTest : XCTestCase @@ -22,33 +23,34 @@ @implementation CTCustomTemplateBuilderTest - (void)testNameNotSetThrows { CTInAppTemplateBuilder *templateBuilder = [[CTInAppTemplateBuilder alloc] init]; - XCTAssertThrows([templateBuilder build]); + XCTAssertThrowsSpecificNamed([templateBuilder build], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); CTAppFunctionBuilder *functionBuilder = [[CTAppFunctionBuilder alloc] initWithIsVisual:NO]; - XCTAssertThrows([functionBuilder build]); + XCTAssertThrowsSpecificNamed([functionBuilder build], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); } - (void)testEmptyNameSetThrows { CTInAppTemplateBuilder *templateBuilder = [[CTInAppTemplateBuilder alloc] init]; - XCTAssertThrows([templateBuilder setName:@""]); + XCTAssertThrowsSpecificNamed([templateBuilder setName:@""], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnonnull" - XCTAssertThrows([templateBuilder setName:nil]); + XCTAssertThrowsSpecificNamed([templateBuilder setName:nil], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); #pragma clang diagnostic pop } - (void)testNameAlreadySetThrows { CTInAppTemplateBuilder *templateBuilder = [[CTInAppTemplateBuilder alloc] init]; [templateBuilder setName:@"template"]; - XCTAssertThrows([templateBuilder setName:@"template"]); + XCTAssertThrowsSpecificNamed([templateBuilder setName:@"template"], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); } - (void)testInvalidArgumentNameThrows { CTInAppTemplateBuilder *templateBuilder = [[CTInAppTemplateBuilder alloc] init]; - XCTAssertThrows([templateBuilder addArgument:@"" withString:@"string"]); - XCTAssertThrows([templateBuilder addArgument:@".start" withString:@"string"]); - XCTAssertThrows([templateBuilder addArgument:@"end." withString:@"string"]); - XCTAssertThrows([templateBuilder addArgument:@"two.." withString:@"string"]); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"" withString:@"string"], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@".start" withString:@"string"], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"end." withString:@"string"], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"two.." withString:@"string"], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); } - (void)testValidArgumentName { @@ -69,61 +71,61 @@ - (void)testInvalidArgumentValueDictionaryThrows { - (void)testArgumentEmptyDictionaryThrows { CTInAppTemplateBuilder *templateBuilder = [[CTInAppTemplateBuilder alloc] init]; - XCTAssertThrows([templateBuilder addArgument:@"dictionary" withDictionary:@{}]); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"dictionary" withDictionary:@{}], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); XCTAssertNoThrow([templateBuilder addArgument:@"dictionary" withDictionary:@{ @"a": @(0) }]); } - (void)testArgumentSameNameThrows { CTInAppTemplateBuilder *templateBuilder = [[CTInAppTemplateBuilder alloc] init]; [templateBuilder addArgument:@"arg" withString:@"value"]; - XCTAssertThrows([templateBuilder addArgument:@"arg" withString:@"value"]); - XCTAssertThrows([templateBuilder addArgument:@"arg" withNumber:@(2)]); - XCTAssertThrows([templateBuilder addArgument:@"arg" withBool:YES]); - XCTAssertThrows([templateBuilder addFileArgument:@"arg"]); - XCTAssertThrows([templateBuilder addActionArgument:@"arg"]); - XCTAssertThrows([templateBuilder addArgument:@"arg" withDictionary:@{ @"a": @(0) }]); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"arg" withString:@"value"], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"arg" withNumber:@(2)], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"arg" withBool:YES], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + XCTAssertThrowsSpecificNamed([templateBuilder addFileArgument:@"arg"], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + XCTAssertThrowsSpecificNamed([templateBuilder addActionArgument:@"arg"], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"arg" withDictionary:@{ @"a": @(0) }], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); } - (void)testArgumentDictionaryName { CTInAppTemplateBuilder *templateBuilder = [[CTInAppTemplateBuilder alloc] init]; [templateBuilder addArgument:@"arg" withDictionary:@{ @"a": @(0) }]; [templateBuilder addArgument:@"arg" withDictionary:@{ @"b": @(0) }]; - XCTAssertThrows([templateBuilder addArgument:@"arg" withDictionary:@{ @"a": @(0) }]); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"arg" withDictionary:@{ @"a": @(0) }], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); } - (void)testNoPresenterThrows { CTInAppTemplateBuilder *templateBuilder = [[CTInAppTemplateBuilder alloc] init]; [templateBuilder setName:@"template"]; - XCTAssertThrows([templateBuilder build]); + XCTAssertThrowsSpecificNamed([templateBuilder build], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); } - (void)testParentArgsAlreadyDefinedThrows { CTInAppTemplateBuilder *templateBuilder = [[CTInAppTemplateBuilder alloc] init]; [templateBuilder addArgument:@"a.b" withString:@""]; - XCTAssertThrows([templateBuilder addArgument:@"a" withString:@""]); - XCTAssertThrows([templateBuilder addArgument:@"a.b" withString:@""]); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"a" withString:@""], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"a.b" withString:@""], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); templateBuilder = [[CTInAppTemplateBuilder alloc] init]; [templateBuilder addArgument:@"a.b.c.d" withString:@""]; - XCTAssertThrows([templateBuilder addArgument:@"a" withString:@""]); - XCTAssertThrows([templateBuilder addArgument:@"a.b" withString:@""]); - XCTAssertThrows([templateBuilder addArgument:@"a.b.c" withString:@""]); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"a" withString:@""], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"a.b" withString:@""], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"a.b.c" withString:@""], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); [templateBuilder addArgument:@"a.b.c.e" withString:@""]; templateBuilder = [[CTInAppTemplateBuilder alloc] init]; [templateBuilder addArgument:@"a.a.a" withString:@""]; [templateBuilder addArgument:@"a.a.b" withString:@""]; - XCTAssertThrows([templateBuilder addArgument:@"a.a.a.d" withString:@""]); - XCTAssertThrows([templateBuilder addArgument:@"a" withString:@""]); - XCTAssertThrows([templateBuilder addArgument:@"a.a" withString:@""]); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"a.a.a.d" withString:@""], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"a" withString:@""], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"a.a" withString:@""], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); templateBuilder = [[CTInAppTemplateBuilder alloc] init]; [templateBuilder addArgument:@"a.a.a" withString:@""]; [templateBuilder addArgument:@"a.a.b" withString:@""]; - XCTAssertThrows([templateBuilder addArgument:@"a.a.a.d" withString:@""]); - XCTAssertThrows([templateBuilder addArgument:@"a" withString:@""]); - XCTAssertThrows([templateBuilder addArgument:@"a.a" withString:@""]); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"a.a.a.d" withString:@""], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"a" withString:@""], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"a.a" withString:@""], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); [templateBuilder addArgument:@"a.a.c" withString:@""]; } @@ -136,10 +138,10 @@ - (void)testArgumentValue { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wincompatible-pointer-types" #pragma clang diagnostic ignored "-Wnonnull" - XCTAssertThrows([templateBuilder addArgument:@"e" withString:nil]); - XCTAssertThrows([templateBuilder addArgument:@"f" withString:[NSNull null]]); - XCTAssertThrows([templateBuilder addArgument:@"g" withNumber:nil]); - XCTAssertThrows([templateBuilder addArgument:@"h" withDictionary:nil]); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"e" withString:nil], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"f" withString:[NSNull null]], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"g" withNumber:nil], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + XCTAssertThrowsSpecificNamed([templateBuilder addArgument:@"h" withDictionary:nil], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); #pragma clang diagnostic pop } diff --git a/CleverTapSDKTests/InApps/CustomTemplates/CTCustomTemplatesManagerTest.m b/CleverTapSDKTests/InApps/CustomTemplates/CTCustomTemplatesManagerTest.m index 544cb79f..725e0502 100644 --- a/CleverTapSDKTests/InApps/CustomTemplates/CTCustomTemplatesManagerTest.m +++ b/CleverTapSDKTests/InApps/CustomTemplates/CTCustomTemplatesManagerTest.m @@ -15,6 +15,7 @@ #import "CTTestTemplateProducer.h" #import "CTInAppNotificationDisplayDelegateMock.h" #import "CTFileDownloaderCustomTemplatesMock.h" +#import "CTConstants.h" @interface CTCustomTemplatesManagerTest : XCTestCase @@ -412,7 +413,7 @@ - (void)testDuplicateTemplateNameThrows { [CTCustomTemplatesManager registerTemplateProducer:producer]; CleverTapInstanceConfig *config = [[CleverTapInstanceConfig alloc] initWithAccountId:@"testAccountId" accountToken:@"testAccountToken"]; - XCTAssertThrows([[CTCustomTemplatesManager alloc] initWithConfig:config]); + XCTAssertThrowsSpecificNamed([[CTCustomTemplatesManager alloc] initWithConfig:config], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); } - (void)testPresenterOnPresent { diff --git a/CleverTapSDKTests/InApps/CustomTemplates/CTJsonTemplateProducerTest.m b/CleverTapSDKTests/InApps/CustomTemplates/CTJsonTemplateProducerTest.m new file mode 100644 index 00000000..e9351a5c --- /dev/null +++ b/CleverTapSDKTests/InApps/CustomTemplates/CTJsonTemplateProducerTest.m @@ -0,0 +1,350 @@ +// +// CTJsonTemplateProducerTest.m +// CleverTapSDKTests +// +// Created by Nikola Zagorchev on 13.09.24. +// Copyright © 2024 CleverTap. All rights reserved. +// + +#import +#import +#import "CTJsonTemplateProducer.h" +#import "CTTemplatePresenterMock.h" +#import "CTCustomTemplate-Internal.h" +#import "CTConstants.h" + +@interface CTJsonTemplateProducerTest : XCTestCase + +@property CTTemplatePresenterMock *presenter; +@property CleverTapInstanceConfig *config; + +@end + +@implementation CTJsonTemplateProducerTest + +- (void)setUp { + self.presenter = [[CTTemplatePresenterMock alloc] init]; + self.config = [[CleverTapInstanceConfig alloc] initWithAccountId:@"testAccountId" accountToken:@"testAccountToken"]; +} + +- (void)testJsonDefinitions { + NSString *json = [NSString stringWithFormat:@"{%@, %@}", self.template1, self.function1]; + + CTJsonTemplateProducer *producer = [[CTJsonTemplateProducer alloc] initWithJson:json templatePresenter:self.presenter functionPresenter:self.presenter]; + + NSSet *templates = [producer defineTemplates:self.config]; + XCTAssertEqual(2, templates.count); + + // Validate template1 + CTCustomTemplate *template1 = [self templateWithName:@"template-1" inSet:templates]; + XCTAssertNotNil(template1); + XCTAssertEqualObjects(TEMPLATE_TYPE, template1.templateType); + + NSArray *template1Keys = [template1.arguments valueForKey:@"name"]; + NSArray *template1Values = [template1.arguments valueForKey:@"defaultValue"]; + NSDictionary *template1ArgsDict = [NSDictionary dictionaryWithObjects:template1Values forKeys:template1Keys]; + + XCTAssertEqualObjects(@"Default", template1ArgsDict[@"string"]); + XCTAssertEqual(0.0, [template1ArgsDict[@"number"] doubleValue]); + XCTAssertEqual(YES, [template1ArgsDict[@"boolean"] boolValue]); + XCTAssertEqualObjects([NSNull null], template1ArgsDict[@"file"]); + XCTAssertEqualObjects([NSNull null], template1ArgsDict[@"action"]); + XCTAssertEqualObjects(@"Inner Default", template1ArgsDict[@"map.innerString"]); + XCTAssertEqual(1.0, [template1ArgsDict[@"map.innerNumber"] doubleValue]); + XCTAssertEqual(NO, [template1ArgsDict[@"map.innerBoolean"] boolValue]); + XCTAssertEqualObjects(@"Innermost Default", template1ArgsDict[@"map.innerMap.innermostString"]); + + // Validate function2 + CTCustomTemplate *function2 = [self templateWithName:@"function-2" inSet:templates]; + XCTAssertNotNil(function2); + XCTAssertEqualObjects(FUNCTION_TYPE, function2.templateType); + XCTAssertEqual(NO, function2.isVisual); + + NSArray *function2Keys = [function2.arguments valueForKey:@"name"]; + NSArray *function2Values = [function2.arguments valueForKey:@"defaultValue"]; + NSDictionary *function2ArgsDict = [NSDictionary dictionaryWithObjects:function2Values forKeys:function2Keys]; + + XCTAssertEqualObjects(@"Default", function2ArgsDict[@"functionString"]); + XCTAssertEqual(0.0, [function2ArgsDict[@"functionNumber"] doubleValue]); + XCTAssertEqual(YES, [function2ArgsDict[@"functionBoolean"] boolValue]); + XCTAssertEqualObjects([NSNull null], function2ArgsDict[@"functionFile"]); + XCTAssertEqualObjects(@"Inner Default", function2ArgsDict[@"functionMap.innerString"]); +} + +- (void)testNoArguments { + NSString *noArgumentsJson = @"{" + @"\"template-1\": {" + " \"type\": \"template\"," + " \"arguments\": {" + " }" + " }" + "}"; + CTJsonTemplateProducer *producer = [[CTJsonTemplateProducer alloc] initWithJson:noArgumentsJson templatePresenter:self.presenter functionPresenter:self.presenter]; + + NSSet *templates = [producer defineTemplates:self.config]; + CTCustomTemplate *template1 = [self templateWithName:@"template-1" inSet:templates]; + XCTAssertNotNil(template1); + XCTAssertEqualObjects(TEMPLATE_TYPE, template1.templateType); + XCTAssertEqual(0, template1.arguments.count); +} + +- (void)testNoJson { + NSString *nilJson = nil; + CTJsonTemplateProducer *producer = [[CTJsonTemplateProducer alloc] initWithJson:nilJson templatePresenter:self.presenter functionPresenter:self.presenter]; + + XCTAssertThrowsSpecificNamed([producer defineTemplates:self.config], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); +} + +- (void)testPresenterNotProvided { +#pragma clang diagnostic ignored "-Wnonnull" + NSString *templateJson = [NSString stringWithFormat:@"{%@}", self.template1]; + CTJsonTemplateProducer *templateJsonProducer = [[CTJsonTemplateProducer alloc] initWithJson:templateJson templatePresenter:nil functionPresenter:self.presenter]; + XCTAssertThrowsSpecificNamed([templateJsonProducer defineTemplates:self.config], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + + NSString *functionJson = [NSString stringWithFormat:@"{%@}", self.function1]; + CTJsonTemplateProducer *functionJsonProducer = [[CTJsonTemplateProducer alloc] initWithJson:functionJson templatePresenter:self.presenter functionPresenter:nil]; + XCTAssertThrowsSpecificNamed([functionJsonProducer defineTemplates:self.config], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); +#pragma clang diagnostic pop +} + +- (void)testInvalidJson { + NSString *invalidJson = @"{[]}"; + CTJsonTemplateProducer *producer = [[CTJsonTemplateProducer alloc] initWithJson:invalidJson templatePresenter:self.presenter functionPresenter:self.presenter]; + + XCTAssertThrowsSpecificNamed([producer defineTemplates:self.config], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); +} + +- (void)testEmptyObjectArgument { + NSString *json = [@"{" + @"\"template-1\": {" + " \"type\": \"template\"," + " \"arguments\": {" + " \"map\": {" + " \"type\": \"object\"," + " \"value\": {" + " }" + " }" + " }" + " }" + "}" stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + + CTJsonTemplateProducer *producer = [[CTJsonTemplateProducer alloc] initWithJson:json templatePresenter:self.presenter functionPresenter:self.presenter]; + XCTAssertThrowsSpecificNamed([producer defineTemplates:self.config], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); +} + +- (void)testInvalidValues { + // Invalid Template Type JSON + NSString *invalidTemplateTypeJson = @"{" + "\"template\": {" + "\"type\": \"string\"," + "\"arguments\": {" + "\"string\": {" + "\"type\": \"string\"," + "\"value\": \"Text\"" + "}" + "}" + "}" + "}"; + + CTJsonTemplateProducer *invalidTemplateTypeProducer = [[CTJsonTemplateProducer alloc] initWithJson:invalidTemplateTypeJson templatePresenter:self.presenter functionPresenter:self.presenter]; + XCTAssertThrowsSpecificNamed([invalidTemplateTypeProducer defineTemplates:self.config], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + + // Invalid Argument Type JSON + NSString *invalidArgumentTypeJson = @"{" + "\"template\": {" + "\"type\": \"template\"," + "\"arguments\": {" + "\"json\": {" + "\"type\": \"json\"," + "\"value\": {}" + "}" + "}" + "}" + "}"; + + CTJsonTemplateProducer *invalidArgumentTypeProducer = [[CTJsonTemplateProducer alloc] initWithJson:invalidArgumentTypeJson templatePresenter:self.presenter functionPresenter:self.presenter]; + XCTAssertThrowsSpecificNamed([invalidArgumentTypeProducer defineTemplates:self.config], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + + // Invalid File Value JSON + NSString *invalidFileValueJson = @"{" + "\"template\": {" + "\"type\": \"template\"," + "\"arguments\": {" + "\"file\": {" + "\"type\": \"file\"," + "\"value\": \"https://files.example.com/file.pdf\"" + "}" + "}" + "}" + "}"; + + CTJsonTemplateProducer *invalidFileValueJsonProducer = [[CTJsonTemplateProducer alloc] initWithJson:invalidFileValueJson templatePresenter:self.presenter functionPresenter:self.presenter]; + XCTAssertThrowsSpecificNamed([invalidFileValueJsonProducer defineTemplates:self.config], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + + // Invalid Function File Value JSON + NSString *invalidFunctionFileValueJson = @"{" + "\"template\": {" + "\"type\": \"function\"," + "\"arguments\": {" + "\"file\": {" + "\"type\": \"file\"," + "\"value\": \"https://files.example.com/file.pdf\"" + "}" + "}" + "}" + "}"; + + CTJsonTemplateProducer *invalidFunctionFileValueJsonProducer = [[CTJsonTemplateProducer alloc] initWithJson:invalidFunctionFileValueJson templatePresenter:self.presenter functionPresenter:self.presenter]; + XCTAssertThrowsSpecificNamed([invalidFunctionFileValueJsonProducer defineTemplates:self.config], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + + // Invalid Action Value JSON + NSString *invalidActionValueJson = @"{" + "\"template\": {" + "\"type\": \"template\"," + "\"arguments\": {" + "\"action\": {" + "\"type\": \"action\"," + "\"value\": \"function1\"" + "}" + "}" + "}" + "}"; + + CTJsonTemplateProducer *invalidActionValueJsonProducer = [[CTJsonTemplateProducer alloc] initWithJson:invalidActionValueJson templatePresenter:self.presenter functionPresenter:self.presenter]; + XCTAssertThrowsSpecificNamed([invalidActionValueJsonProducer defineTemplates:self.config], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + + // Invalid Function Action JSON + NSString *invalidFunctionActionJson = @"{" + "\"template\": {" + "\"type\": \"function\"," + "\"isVisual\": true," + "\"arguments\": {" + "\"action\": {" + "\"type\": \"action\"" + "}" + "}" + "}" + "}"; + + CTJsonTemplateProducer *invalidFunctionActionProducer = [[CTJsonTemplateProducer alloc] initWithJson:invalidFunctionActionJson templatePresenter:self.presenter functionPresenter:self.presenter]; + XCTAssertThrowsSpecificNamed([invalidFunctionActionProducer defineTemplates:self.config], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); + + // Invalid Nested File JSON + NSString *invalidNestedFileJson = @"{" + "\"template\": {" + "\"type\": \"template\"," + "\"arguments\": {" + "\"map\": {" + "\"type\": \"object\"," + "\"value\": {" + "\"file\": {" + "\"type\": \"file\"" + "}" + "}" + "}" + "}" + "}" + "}"; + + CTJsonTemplateProducer *invalidNestedFileJsonProducer = [[CTJsonTemplateProducer alloc] initWithJson:invalidNestedFileJson templatePresenter:self.presenter functionPresenter:self.presenter]; + XCTAssertThrowsSpecificNamed([invalidNestedFileJsonProducer defineTemplates:self.config], NSException, CLTAP_CUSTOM_TEMPLATE_EXCEPTION); +} + +- (CTCustomTemplate * _Nullable)templateWithName:(NSString *)name inSet:(NSSet *)templates { + return [templates objectsPassingTest:^BOOL(CTCustomTemplate * _Nonnull template, BOOL * _Nonnull stop) { + if ([template.name isEqualToString:name]) { + *stop = YES; + return YES; + } + return NO; + }].anyObject; +} + +- (NSString *)template1 { + return [ + @"\"template-1\": {" + " \"type\": \"template\"," + " \"arguments\": {" + " \"string\": {" + " \"type\": \"string\"," + " \"value\": \"Default\"" + " }," + " \"number\": {" + " \"type\": \"number\"," + " \"value\": 0" + " }," + " \"boolean\": {" + " \"type\": \"boolean\"," + " \"value\": true" + " }," + " \"file\": {" + " \"type\": \"file\"" + " }," + " \"action\": {" + " \"type\": \"action\"" + " }," + " \"map\": {" + " \"type\": \"object\"," + " \"value\": {" + " \"innerString\": {" + " \"type\": \"string\"," + " \"value\": \"Inner Default\"" + " }," + " \"innerNumber\": {" + " \"type\": \"number\"," + " \"value\": 1" + " }," + " \"innerBoolean\": {" + " \"type\": \"boolean\"," + " \"value\": false" + " }," + " \"innerMap\": {" + " \"type\": \"object\"," + " \"value\": {" + " \"innermostString\": {" + " \"type\": \"string\"," + " \"value\": \"Innermost Default\"" + " }" + " }" + " }" + " }" + " }" + " }" + "}" stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; +} + +- (NSString *)function1 { + return [ + @"\"function-2\": {" + " \"type\": \"function\"," + " \"isVisual\": false," + " \"arguments\": {" + " \"functionString\": {" + " \"type\": \"string\"," + " \"value\": \"Default\"" + " }," + " \"functionNumber\": {" + " \"type\": \"number\"," + " \"value\": 0" + " }," + " \"functionBoolean\": {" + " \"type\": \"boolean\"," + " \"value\": true" + " }," + " \"functionFile\": {" + " \"type\": \"file\"" + " }," + " \"functionMap\": {" + " \"type\": \"object\"," + " \"value\": {" + " \"innerString\": {" + " \"type\": \"string\"," + " \"value\": \"Inner Default\"" + " }" + " }" + " }" + " }" + "}" stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; +} + +@end From e371d227f1c23bcf4805047cb11512e33a7f5426 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Wed, 9 Oct 2024 17:14:15 +0530 Subject: [PATCH 15/21] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edde3c1d..c343c4d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log All notable changes to this project will be documented in this file. -### [Version 7.0.2](https://github.com/CleverTap/clevertap-ios-sdk/releases/tag/7.0.2) (September 16, 2024) +### [Version 7.0.2](https://github.com/CleverTap/clevertap-ios-sdk/releases/tag/7.0.2) (October 09, 2024) #### Added - Adds support for custom handshake domains. From 94061c70f16d80cd5030297f9355da0deac239af Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Wed, 9 Oct 2024 17:20:37 +0530 Subject: [PATCH 16/21] removed a stray file --- CleverTapSDKTests/CTRequestFactoryTests.m | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 CleverTapSDKTests/CTRequestFactoryTests.m diff --git a/CleverTapSDKTests/CTRequestFactoryTests.m b/CleverTapSDKTests/CTRequestFactoryTests.m deleted file mode 100644 index 16a84ecc..00000000 --- a/CleverTapSDKTests/CTRequestFactoryTests.m +++ /dev/null @@ -1,9 +0,0 @@ -// -// CTRequestFactoryTests.m -// CleverTapSDKTests -// -// Created by Akash Malhotra on 12/09/24. -// Copyright © 2024 CleverTap. All rights reserved. -// - -#import From 344f047b6b60fd325abca8d02fbc6168f465a353 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Wed, 9 Oct 2024 20:55:57 +0530 Subject: [PATCH 17/21] fixed domain property specifier --- CleverTapSDK/CleverTapInstanceConfig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CleverTapSDK/CleverTapInstanceConfig.h b/CleverTapSDK/CleverTapInstanceConfig.h index d517e4c5..c351f444 100644 --- a/CleverTapSDK/CleverTapInstanceConfig.h +++ b/CleverTapSDK/CleverTapInstanceConfig.h @@ -16,7 +16,7 @@ @property (nonatomic, assign) BOOL useCustomCleverTapId; @property (nonatomic, assign) BOOL disableIDFV; @property (nonatomic, assign) BOOL enableFileProtection; -@property (nonatomic, strong, readonly, nullable) NSString *handshakeDomain; +@property (nonatomic, strong, nullable) NSString *handshakeDomain; @property (nonatomic, assign) CleverTapLogLevel logLevel; @property (nonatomic, strong, nullable) NSArray *identityKeys; From cd49f2a07117980a8a1bc602064a0e35b21d0206 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Thu, 10 Oct 2024 10:09:01 +0530 Subject: [PATCH 18/21] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c343c4d9..9454e70b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log All notable changes to this project will be documented in this file. -### [Version 7.0.2](https://github.com/CleverTap/clevertap-ios-sdk/releases/tag/7.0.2) (October 09, 2024) +### [Version 7.0.2](https://github.com/CleverTap/clevertap-ios-sdk/releases/tag/7.0.2) (October 10, 2024) #### Added - Adds support for custom handshake domains. From ab8e4f658dab76af30ed63397861ada2d78c0de3 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Thu, 10 Oct 2024 10:11:31 +0530 Subject: [PATCH 19/21] removed a stray file --- CleverTapSDKTests/CTDomainFactory+Tests.m | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 CleverTapSDKTests/CTDomainFactory+Tests.m diff --git a/CleverTapSDKTests/CTDomainFactory+Tests.m b/CleverTapSDKTests/CTDomainFactory+Tests.m deleted file mode 100644 index c2991fa5..00000000 --- a/CleverTapSDKTests/CTDomainFactory+Tests.m +++ /dev/null @@ -1,13 +0,0 @@ -// -// CTDomainFactory+Tests.m -// CleverTapSDKTests -// -// Created by Akash Malhotra on 12/09/24. -// Copyright © 2024 CleverTap. All rights reserved. -// - -#import "CTDomainFactory+Tests.h" - -@implementation CTDomainFactory (Tests) - -@end From d940eea55d84becf9d1006a66fba4208b5709450 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Thu, 10 Oct 2024 11:49:31 +0530 Subject: [PATCH 20/21] removed stray reference in pbxproj --- CleverTapSDK.xcodeproj/project.pbxproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CleverTapSDK.xcodeproj/project.pbxproj b/CleverTapSDK.xcodeproj/project.pbxproj index 37b8dfed..46b1ca5e 100644 --- a/CleverTapSDK.xcodeproj/project.pbxproj +++ b/CleverTapSDK.xcodeproj/project.pbxproj @@ -253,7 +253,6 @@ 4E87296E277CE8EB00A7A618 /* StubHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E87296D277CE8EB00A7A618 /* StubHelper.m */; }; 4E872973277CEE6700A7A618 /* TestConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E872972277CEE6700A7A618 /* TestConstants.m */; }; 4E87397B2C92223C00FDFDFD /* CTDomainFactoryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E8739772C921D9000FDFDFD /* CTDomainFactoryTests.m */; }; - 4E87397E2C9223B300FDFDFD /* CTDomainFactory+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E87397D2C9223B300FDFDFD /* CTDomainFactory+Tests.m */; }; 4E8B81662AD2ADAE00714BB4 /* CTSwizzleManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E8B81642AD2ADAE00714BB4 /* CTSwizzleManager.m */; }; 4E8B81672AD2ADAE00714BB4 /* CTSwizzleManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E8B81642AD2ADAE00714BB4 /* CTSwizzleManager.m */; }; 4E8B81682AD2ADAE00714BB4 /* CTSwizzleManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E8B81652AD2ADAE00714BB4 /* CTSwizzleManager.h */; }; @@ -826,7 +825,6 @@ 4E872972277CEE6700A7A618 /* TestConstants.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestConstants.m; sourceTree = ""; }; 4E8739772C921D9000FDFDFD /* CTDomainFactoryTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTDomainFactoryTests.m; sourceTree = ""; }; 4E87397C2C9223B300FDFDFD /* CTDomainFactory+Tests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CTDomainFactory+Tests.h"; sourceTree = ""; }; - 4E87397D2C9223B300FDFDFD /* CTDomainFactory+Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "CTDomainFactory+Tests.m"; sourceTree = ""; }; 4E8B81642AD2ADAE00714BB4 /* CTSwizzleManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTSwizzleManager.m; sourceTree = ""; }; 4E8B81652AD2ADAE00714BB4 /* CTSwizzleManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTSwizzleManager.h; sourceTree = ""; }; 4E8B816A2AD2B2FD00714BB4 /* CleverTapInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CleverTapInternal.h; sourceTree = ""; }; @@ -1616,7 +1614,6 @@ 0B995A492C36AEDC00AF6006 /* CTLocalDataStoreTests.m */, 4E8739772C921D9000FDFDFD /* CTDomainFactoryTests.m */, 4E87397C2C9223B300FDFDFD /* CTDomainFactory+Tests.h */, - 4E87397D2C9223B300FDFDFD /* CTDomainFactory+Tests.m */, ); path = CleverTapSDKTests; sourceTree = ""; @@ -2518,7 +2515,6 @@ 32394C2529FA272600956058 /* CTValidatorTest.m in Sources */, 6BB778CE2BEE48C300A41628 /* CTCustomTemplateInAppDataTest.m in Sources */, 6BA3B2E82B07E207004E834B /* CTTriggersMatcher+Tests.m in Sources */, - 4E87397E2C9223B300FDFDFD /* CTDomainFactory+Tests.m in Sources */, 6BBF05CE2C58E3FB0047E3D9 /* NSURLSessionMock.m in Sources */, 6B32A0A52B9A0F17009ADC57 /* CTCustomTemplateTest.m in Sources */, 4E1F155B276B662C009387AE /* EventDetail.m in Sources */, From 73dbdcaa1185852b5557d57b9939ec9e6c966dc6 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Thu, 10 Oct 2024 13:35:43 +0530 Subject: [PATCH 21/21] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9454e70b..b13776c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. #### Added - Adds support for custom handshake domains. +- Adds support for custom code in-app templates definitions through a json scheme. ### [Version 7.0.1](https://github.com/CleverTap/clevertap-ios-sdk/releases/tag/7.0.1) (August 22, 2024)