diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b5a63af..ec54172d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +## 24.7.9 +* Improved view tracking capabilities + +## 24.7.8 +* Added support for localization of content blocks. + +* Mitigated an issue where visibility could have been wrongly assigned if a view was closed while going to background. (Experimental!) +* Mitigated an issue where the user provided URLSessionConfiguration was not applied to direct requests +* Mitigated an issue where a concurrent modification error could have happen when starting multiple stopped views +* Mitigated an issue that parsing internal content event segmentation. + +## 24.7.7 +* Changed the visibility tracking segmentation values to binary + ## 24.7.6 * Mitigated an issue with experimental visibility tracking and previous name recording, ensuring they’re included even when no segmentation is provided in event or view recording. diff --git a/Countly-PL.podspec b/Countly-PL.podspec index 8631363a..2d85adbd 100644 --- a/Countly-PL.podspec +++ b/Countly-PL.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Countly-PL' - s.version = '24.7.6' + s.version = '24.7.9' s.license = { :type => 'MIT', :file => 'LICENSE' } s.summary = 'Countly is an innovative, real-time, open source mobile analytics platform.' s.homepage = 'https://github.com/Countly/countly-sdk-ios' diff --git a/Countly.m b/Countly.m index 7cf1d60f..6c51a817 100644 --- a/Countly.m +++ b/Countly.m @@ -932,19 +932,26 @@ - (void)recordEvent:(NSString *)key segmentation:(NSDictionary *)segmentation co [CountlyPersistency.sharedInstance recordEvent:event]; } -- (NSDictionary*) processSegmentation:(NSMutableDictionary *) segmentation eventKey:(NSString *)eventKey -{ - if(CountlyViewTrackingInternal.sharedInstance.enablePreviousNameRecording) { - if([eventKey isEqualToString:kCountlyReservedEventView]) { - segmentation[kCountlyPreviousView] = CountlyViewTrackingInternal.sharedInstance.previousViewName ?: @""; - } +- (NSDictionary *)processSegmentation:(NSMutableDictionary *)segmentation eventKey:(NSString *)eventKey { + BOOL isViewEvent = [eventKey isEqualToString:kCountlyReservedEventView]; + + // Add previous view name if enabled and the event is a view event + if (isViewEvent && CountlyViewTrackingInternal.sharedInstance.enablePreviousNameRecording) { + segmentation[kCountlyPreviousView] = CountlyViewTrackingInternal.sharedInstance.previousViewName ?: @""; } - if(CountlyCommon.sharedInstance.enableVisibiltyTracking) { - segmentation[kCountlyVisibility] = @([self isAppInForeground]); + // Add visibility tracking information if enabled + if (CountlyCommon.sharedInstance.enableVisibiltyTracking) { + BOOL isViewStart = [segmentation[kCountlyVTKeyVisit] isEqual:@1]; + + // Add visibility if it's not a view event or it's a view start event + if (!isViewEvent || isViewStart) { + segmentation[kCountlyVisibility] = @([self isAppInForeground] ? 1 : 0); + } } - return segmentation.count == 0 ? nil : segmentation; + // Return segmentation dictionary if not empty, otherwise return nil + return segmentation.count > 0 ? segmentation : nil; } - (BOOL)isAppInForeground { diff --git a/Countly.podspec b/Countly.podspec index fe20e91b..41dd3402 100644 --- a/Countly.podspec +++ b/Countly.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Countly' - s.version = '24.7.6' + s.version = '24.7.9' s.license = { :type => 'MIT', :file => 'LICENSE' } s.summary = 'Countly is an innovative, real-time, open source mobile analytics platform.' s.homepage = 'https://github.com/Countly/countly-sdk-ios' diff --git a/Countly.xcodeproj/project.pbxproj b/Countly.xcodeproj/project.pbxproj index c03ad326..071f99d1 100644 --- a/Countly.xcodeproj/project.pbxproj +++ b/Countly.xcodeproj/project.pbxproj @@ -738,7 +738,7 @@ "@loader_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.14; - MARKETING_VERSION = 24.7.6; + MARKETING_VERSION = 24.7.9; PRODUCT_BUNDLE_IDENTIFIER = ly.count.CountlyiOSSDK; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -770,7 +770,7 @@ "@loader_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.14; - MARKETING_VERSION = 24.7.6; + MARKETING_VERSION = 24.7.9; PRODUCT_BUNDLE_IDENTIFIER = ly.count.CountlyiOSSDK; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/CountlyCommon.h b/CountlyCommon.h index 2aaf4e66..08ad66f5 100644 --- a/CountlyCommon.h +++ b/CountlyCommon.h @@ -122,6 +122,8 @@ void CountlyPrint(NSString *stringToPrint); - (void)recordOrientation; - (BOOL)hasStarted_; + +- (NSURLSession *)URLSession; @end diff --git a/CountlyCommon.m b/CountlyCommon.m index 893b488d..000094f4 100644 --- a/CountlyCommon.m +++ b/CountlyCommon.m @@ -29,7 +29,7 @@ @interface CountlyCommon () #endif @end -NSString* const kCountlySDKVersion = @"24.7.6"; +NSString* const kCountlySDKVersion = @"24.7.9"; NSString* const kCountlySDKName = @"objc-native-ios"; NSString* const kCountlyErrorDomain = @"ly.count.ErrorDomain"; @@ -317,6 +317,18 @@ - (void)tryPresentingViewController:(UIViewController *)viewController withCompl } #endif +- (NSURLSession *)URLSession +{ + if (CountlyConnectionManager.sharedInstance.URLSessionConfiguration) + { + return [NSURLSession sessionWithConfiguration:CountlyConnectionManager.sharedInstance.URLSessionConfiguration]; + } + else + { + return NSURLSession.sharedSession; + } +} + @end diff --git a/CountlyConnectionManager.h b/CountlyConnectionManager.h index 273621b5..53cff0c9 100644 --- a/CountlyConnectionManager.h +++ b/CountlyConnectionManager.h @@ -69,4 +69,6 @@ extern const NSInteger kCountlyGETRequestMaxLength; - (NSString *)queryEssentials; - (NSString *)appendChecksum:(NSString *)queryString; +- (BOOL)isSessionStarted; + @end diff --git a/CountlyConnectionManager.m b/CountlyConnectionManager.m index 1368f748..4bc1f207 100644 --- a/CountlyConnectionManager.m +++ b/CountlyConnectionManager.m @@ -16,6 +16,7 @@ @interface CountlyConnectionManager () @property (nonatomic) NSURLSession* URLSession; @property (nonatomic, strong) NSDate *startTime; + @end NSString* const kCountlyQSKeyAppKey = @"app_key"; @@ -105,6 +106,11 @@ - (instancetype)init return self; } + +- (BOOL)isSessionStarted { + return isSessionStarted; +} + - (void)resetInstance { CLY_LOG_I(@"%s", __FUNCTION__); onceToken = 0; diff --git a/CountlyContentBuilderInternal.m b/CountlyContentBuilderInternal.m index ec0bbcb0..196edb8a 100644 --- a/CountlyContentBuilderInternal.m +++ b/CountlyContentBuilderInternal.m @@ -137,6 +137,11 @@ - (NSURLRequest *)fetchContentsRequest queryString = [CountlyConnectionManager.sharedInstance appendChecksum:queryString]; + NSArray *components = [CountlyDeviceInfo.locale componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"_-"]]; + + queryString = [queryString stringByAppendingFormat:@"&%@=%@", + @"la", components.firstObject]; + NSString* URLString = [NSString stringWithFormat:@"%@%@?%@", CountlyConnectionManager.sharedInstance.host, kCountlyEndpointContent, diff --git a/CountlyFeedbackWidget.m b/CountlyFeedbackWidget.m index 7631dd69..258f265a 100644 --- a/CountlyFeedbackWidget.m +++ b/CountlyFeedbackWidget.m @@ -115,7 +115,7 @@ - (void)getWidgetData:(void (^)(NSDictionary * __nullable widgetData, NSError * return; } - NSURLSessionTask* task = [NSURLSession.sharedSession dataTaskWithRequest:[self dataRequest] completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) + NSURLSessionTask* task = [CountlyCommon.sharedInstance.URLSession dataTaskWithRequest:[self dataRequest] completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) { NSDictionary *widgetData = nil; diff --git a/CountlyFeedbacksInternal.m b/CountlyFeedbacksInternal.m index 4a7bb9e2..6b320811 100644 --- a/CountlyFeedbacksInternal.m +++ b/CountlyFeedbacksInternal.m @@ -252,7 +252,7 @@ - (void)presentRatingWidgetWithID:(NSString *)widgetID closeButtonText:(NSString return; NSURLRequest* feedbackWidgetCheckRequest = [self widgetCheckURLRequest:widgetID]; - NSURLSessionTask* task = [NSURLSession.sharedSession dataTaskWithRequest:feedbackWidgetCheckRequest completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) + NSURLSessionTask* task = [CountlyCommon.sharedInstance.URLSession dataTaskWithRequest:feedbackWidgetCheckRequest completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) { NSDictionary* widgetInfo = nil; @@ -425,7 +425,7 @@ - (void)getFeedbackWidgets:(void (^)(NSArray *feedback if (CountlyDeviceInfo.sharedInstance.isDeviceIDTemporary) return; - NSURLSessionTask* task = [NSURLSession.sharedSession dataTaskWithRequest:[self feedbacksRequest] completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) + NSURLSessionTask* task = [CountlyCommon.sharedInstance.URLSession dataTaskWithRequest:[self feedbacksRequest] completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) { NSDictionary *feedbacksResponse = nil; diff --git a/CountlyRemoteConfigInternal.m b/CountlyRemoteConfigInternal.m index dd2ed7a8..5e0aee79 100644 --- a/CountlyRemoteConfigInternal.m +++ b/CountlyRemoteConfigInternal.m @@ -197,7 +197,7 @@ - (void)fetchRemoteConfigForKeys:(NSArray *)keys omitKeys:(NSArray *)omitKeys i return; NSURLRequest* request = [self remoteConfigRequestForKeys:keys omitKeys:omitKeys isLegacy:isLegacy]; - NSURLSessionTask* task = [NSURLSession.sharedSession dataTaskWithRequest:request completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) + NSURLSessionTask* task = [CountlyCommon.sharedInstance.URLSession dataTaskWithRequest:request completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) { NSDictionary* remoteConfig = nil; @@ -496,7 +496,7 @@ - (void)testingDownloadAllVariantsInternal:(void (^)(CLYRequestResult response, return; NSURLRequest* request = [self downloadVariantsRequest]; - NSURLSessionTask* task = [NSURLSession.sharedSession dataTaskWithRequest:request completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) + NSURLSessionTask* task = [CountlyCommon.sharedInstance.URLSession dataTaskWithRequest:request completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) { NSMutableDictionary* variants = NSMutableDictionary.new; @@ -607,7 +607,7 @@ - (void)testingEnrollIntoVariantInternal:(NSString *)key variantName:(NSString * } NSURLRequest* request = [self enrollInVarianRequestForKey:key variantName:variantName]; - NSURLSessionTask* task = [NSURLSession.sharedSession dataTaskWithRequest:request completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) + NSURLSessionTask* task = [CountlyCommon.sharedInstance.URLSession dataTaskWithRequest:request completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) { NSDictionary* variants = nil; [self clearCachedRemoteConfig]; @@ -721,7 +721,7 @@ - (void)testingDownloaExperimentInfoInternal:(void (^)(CLYRequestResult response return; NSURLRequest* request = [self downloadExperimentInfoRequest]; - NSURLSessionTask* task = [NSURLSession.sharedSession dataTaskWithRequest:request completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) + NSURLSessionTask* task = [CountlyCommon.sharedInstance.URLSession dataTaskWithRequest:request completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) { NSMutableDictionary * experiments = NSMutableDictionary.new; diff --git a/CountlyServerConfig.m b/CountlyServerConfig.m index 300f14e7..c8c93623 100644 --- a/CountlyServerConfig.m +++ b/CountlyServerConfig.m @@ -84,7 +84,7 @@ - (void)fetchServerConfig if (CountlyDeviceInfo.sharedInstance.isDeviceIDTemporary) return; - NSURLSessionTask* task = [NSURLSession.sharedSession dataTaskWithRequest:[self serverConfigRequest] completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) + NSURLSessionTask* task = [CountlyCommon.sharedInstance.URLSession dataTaskWithRequest:[self serverConfigRequest] completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) { NSDictionary *serverConfigResponse = nil; diff --git a/CountlyViewTrackingInternal.h b/CountlyViewTrackingInternal.h index b2260209..b21e2e1e 100644 --- a/CountlyViewTrackingInternal.h +++ b/CountlyViewTrackingInternal.h @@ -11,6 +11,7 @@ extern NSString* const kCountlyReservedEventView; extern NSString* const kCountlyCurrentView; extern NSString* const kCountlyPreviousView; extern NSString* const kCountlyPreviousEventName; +extern NSString* const kCountlyVTKeyVisit; @interface CountlyViewTrackingInternal : NSObject @property (nonatomic) BOOL isEnabledOnInitialConfig; diff --git a/CountlyViewTrackingInternal.m b/CountlyViewTrackingInternal.m index 6b74a0c2..53f9b319 100644 --- a/CountlyViewTrackingInternal.m +++ b/CountlyViewTrackingInternal.m @@ -436,7 +436,7 @@ - (NSString*)startViewInternal:(NSString *)viewName customSegmentation:(NSDictio segmentation[kCountlyVTKeySegment] = CountlyDeviceInfo.osName; segmentation[kCountlyVTKeyVisit] = @1; - if (self.isFirstView) + if (self.isFirstView && [CountlyConnectionManager.sharedInstance isSessionStarted]) { self.isFirstView = NO; segmentation[kCountlyVTKeyStart] = @1; @@ -545,27 +545,33 @@ - (void)startStoppedViewsInternal { // Create an array to store keys for views that need to be removed NSMutableArray *keysToRemove = [NSMutableArray array]; - + NSMutableArray *keysToStart = [NSMutableArray array]; + + // Collect keys without modifying the dictionary [self.viewDataDictionary enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, CountlyViewData * _Nonnull viewData, BOOL * _Nonnull stop) { if (viewData.willStartAgain) { - NSString *viewID = [self startViewInternal:viewData.viewName customSegmentation:viewData.startSegmentation isAutoStoppedView:viewData.isAutoStoppedView]; - - // Retrieve the newly created viewData for the viewID - CountlyViewData* viewDataNew = self.viewDataDictionary[viewID]; - - // Copy the segmentation data from the old view to the new view - viewDataNew.segmentation = viewData.segmentation.mutableCopy; - - // Add the old view's ID to the array for removal later + [keysToStart addObject:key]; [keysToRemove addObject:viewData.viewID]; } }]; + // Start the collected views after enumeration + for (NSString *key in keysToStart) + { + CountlyViewData *viewData = self.viewDataDictionary[key]; + NSString *viewID = [self startViewInternal:viewData.viewName customSegmentation:viewData.startSegmentation isAutoStoppedView:viewData.isAutoStoppedView]; + + // Retrieve and update the newly created viewData + CountlyViewData *viewDataNew = self.viewDataDictionary[viewID]; + viewDataNew.segmentation = viewData.segmentation.mutableCopy; + } + // Remove the entries from the dictionary [self.viewDataDictionary removeObjectsForKeys:keysToRemove]; } + - (void)stopAllViewsInternal:(NSDictionary *)segmentation { // TODO: Should apply all the segmenation operations here at one place instead of doing it for individual view @@ -759,7 +765,7 @@ - (void)applicationWillTerminate { - (void)resetFirstView { - self.isFirstView = NO; + self.isFirstView = YES; } diff --git a/CountlyWebViewManager.m b/CountlyWebViewManager.m index 258588a0..14a954ff 100644 --- a/CountlyWebViewManager.m +++ b/CountlyWebViewManager.m @@ -208,11 +208,15 @@ - (void)recordEventsWithJSONString:(NSString *)jsonString { for (NSDictionary *event in events) { NSString *key = event[@"key"]; - NSDictionary *segmentation = event[@"sg"]; + NSDictionary *segmentation = event[@"segmentation"]; + NSDictionary *sg = event[@"sg"]; if(!key) { CLY_LOG_I(@"Skipping the event due to key is empty or nil"); continue; } + if(sg) { + segmentation = sg; + } if(!segmentation) { CLY_LOG_I(@"Skipping the event due to missing segmentation"); continue;