diff --git a/CHANGELOG.md b/CHANGELOG.md index f4887ad4..a62a899a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## 23.2.3 +* Not reporting battery level in the crash handler to prevent hanging in iOS +* Fixing bug that prevented device ID to be changed when there is no consent given in Android +* Updated Underlying android SDK version to 22.09.4 +* Updated Underlying iOS SDK version to 23.02.2 + +## 23.2.3-np +* Not reporting battery level in the crash handler to prevent hanging in iOS +* Fixing bug that prevented device ID to be changed when there is no consent given in Android +* Updated Underlying android SDK version to 22.09.4 +* Updated Underlying iOS SDK version to 23.02.2 + ## 23.2.2 * Added "previous event ID" logic for non-internal events * Session update interval upper limit (10 minutes) has been lifted in Android diff --git a/android/build.gradle b/android/build.gradle index 15211745..be9f5325 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -34,6 +34,6 @@ android { } dependencies { - implementation 'ly.count.android:sdk:22.09.3' + implementation 'ly.count.android:sdk:22.09.4' implementation 'com.google.firebase:firebase-messaging:20.2.1' } diff --git a/example-no-push/android/app/build.gradle b/example-no-push/android/app/build.gradle index a4c6f7dc..ca37083a 100644 --- a/example-no-push/android/app/build.gradle +++ b/example-no-push/android/app/build.gradle @@ -59,6 +59,6 @@ dependencies { testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' - implementation 'ly.count.android:sdk:22.09.3' + implementation 'ly.count.android:sdk:22.09.4' } diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 4aec1a7c..f8623916 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -59,8 +59,9 @@ dependencies { testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' - implementation 'ly.count.android:sdk:22.09.3' + implementation 'ly.count.android:sdk:22.09.4' implementation 'com.google.firebase:firebase-messaging:20.2.1' + implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0")) } apply plugin: 'com.google.gms.google-services' // Google Play services Gradle plugin diff --git a/ios/Classes/CountlyiOS/CHANGELOG.md b/ios/Classes/CountlyiOS/CHANGELOG.md index e5b9e193..19890a3e 100644 --- a/ios/Classes/CountlyiOS/CHANGELOG.md +++ b/ios/Classes/CountlyiOS/CHANGELOG.md @@ -1,3 +1,7 @@ +## 23.02.2 +- Added server configuration functionality. This is an experimental feature. +- Not reporting battery level in the crash handler to prevent hanging + ## 23.02.1 - Added previous event ID and sending it with custom events. - Updated default `maxSegmentationValues` from 30 to 100 diff --git a/ios/Classes/CountlyiOS/Countly-PL.podspec b/ios/Classes/CountlyiOS/Countly-PL.podspec index be8f4edb..189aab4c 100644 --- a/ios/Classes/CountlyiOS/Countly-PL.podspec +++ b/ios/Classes/CountlyiOS/Countly-PL.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Countly-PL' - s.version = '23.02.1' + s.version = '23.02.2' s.license = { :type => 'MIT', :file => 'LICENSE.md' } 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/ios/Classes/CountlyiOS/Countly.m b/ios/Classes/CountlyiOS/Countly.m index b77fd8ce..410f8cea 100644 --- a/ios/Classes/CountlyiOS/Countly.m +++ b/ios/Classes/CountlyiOS/Countly.m @@ -117,6 +117,14 @@ - (void)startWithConfig:(CountlyConfig *)config CountlyDeviceInfo.sharedInstance.customMetrics = [config.customMetrics cly_truncated:@"Custom metric"]; [Countly.user save]; + + CountlyCommon.sharedInstance.enableServerConfiguration = config.enableServerConfiguration; + + // Fetch server configs if 'enableServerConfiguration' is true. + if(config.enableServerConfiguration) + { + [CountlyServerConfig.sharedInstance fetchServerConfig]; + } #if (TARGET_OS_IOS) CountlyFeedbacks.sharedInstance.message = config.starRatingMessage; diff --git a/ios/Classes/CountlyiOS/Countly.podspec b/ios/Classes/CountlyiOS/Countly.podspec index ab6a7cba..49965a22 100644 --- a/ios/Classes/CountlyiOS/Countly.podspec +++ b/ios/Classes/CountlyiOS/Countly.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Countly' - s.version = '23.02.1' + s.version = '23.02.2' s.license = { :type => 'MIT', :file => 'LICENSE.md' } 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/ios/Classes/CountlyiOS/CountlyCommon.h b/ios/Classes/CountlyiOS/CountlyCommon.h index aa7ac44e..f5fc53b7 100644 --- a/ios/Classes/CountlyiOS/CountlyCommon.h +++ b/ios/Classes/CountlyiOS/CountlyCommon.h @@ -22,6 +22,7 @@ #import "CountlyLocationManager.h" #import "CountlyRemoteConfig.h" #import "CountlyPerformanceMonitoring.h" +#import "CountlyServerConfig.h" #define CLY_LOG_E(fmt, ...) CountlyInternalLog(CLYInternalLogLevelError, fmt, ##__VA_ARGS__) #define CLY_LOG_W(fmt, ...) CountlyInternalLog(CLYInternalLogLevelWarning, fmt, ##__VA_ARGS__) @@ -57,6 +58,7 @@ NS_ERROR_ENUM(kCountlyErrorDomain) CLYErrorFeedbackWidgetNotTargetedForDevice = 10002, CLYErrorRemoteConfigGeneralAPIError = 10011, CLYErrorFeedbacksGeneralAPIError = 10012, + CLYErrorServerConfigGeneralAPIError = 10013, }; @interface CountlyCommon : NSObject @@ -72,6 +74,7 @@ NS_ERROR_ENUM(kCountlyErrorDomain) @property (nonatomic, copy) NSString* attributionID; @property (nonatomic) BOOL manualSessionHandling; @property (nonatomic) BOOL enableOrientationTracking; +@property (nonatomic) BOOL enableServerConfiguration; @property (nonatomic) NSUInteger maxKeyLength; diff --git a/ios/Classes/CountlyiOS/CountlyCommon.m b/ios/Classes/CountlyiOS/CountlyCommon.m index 04fe7c2a..5fc10c9a 100644 --- a/ios/Classes/CountlyiOS/CountlyCommon.m +++ b/ios/Classes/CountlyiOS/CountlyCommon.m @@ -26,7 +26,7 @@ @interface CountlyCommon () #endif @end -NSString* const kCountlySDKVersion = @"23.02.1"; +NSString* const kCountlySDKVersion = @"23.02.2"; NSString* const kCountlySDKName = @"objc-native-ios"; NSString* const kCountlyErrorDomain = @"ly.count.ErrorDomain"; diff --git a/ios/Classes/CountlyiOS/CountlyConfig.h b/ios/Classes/CountlyiOS/CountlyConfig.h index bd144c55..65e8bef5 100644 --- a/ios/Classes/CountlyiOS/CountlyConfig.h +++ b/ios/Classes/CountlyiOS/CountlyConfig.h @@ -529,6 +529,13 @@ typedef enum : NSUInteger * @discussion For disabling it, please set this flag to @c NO. */ @property (nonatomic) BOOL enableOrientationTracking; + +/** + * This is an experimental feature + * For enabling fetching and application of server config values. + * @discussion If set, Server Config values from Countly Server will be fetched at the beginning of a session. + */ +@property (nonatomic) BOOL enableServerConfiguration; NS_ASSUME_NONNULL_END @end diff --git a/ios/Classes/CountlyiOS/CountlyConfig.m b/ios/Classes/CountlyiOS/CountlyConfig.m index 7382dfdc..6f89bb1e 100644 --- a/ios/Classes/CountlyiOS/CountlyConfig.m +++ b/ios/Classes/CountlyiOS/CountlyConfig.m @@ -58,6 +58,7 @@ - (instancetype)init self.internalLogLevel = CLYInternalLogLevelDebug; self.enableOrientationTracking = YES; + self.enableServerConfiguration = NO; } return self; diff --git a/ios/Classes/CountlyiOS/CountlyConnectionManager.m b/ios/Classes/CountlyiOS/CountlyConnectionManager.m index 5c562437..830c6236 100644 --- a/ios/Classes/CountlyiOS/CountlyConnectionManager.m +++ b/ios/Classes/CountlyiOS/CountlyConnectionManager.m @@ -123,6 +123,12 @@ - (void)setURLSessionConfiguration:(NSURLSessionConfiguration *)URLSessionConfig - (void)proceedOnQueue { CLY_LOG_D(@"Proceeding on queue..."); + + if (!CountlyServerConfig.sharedInstance.networkingEnabled) + { + CLY_LOG_D(@"Proceeding on queue is aborted: SDK Networking is disabled from server config!"); + return; + } if (self.connection) { @@ -364,6 +370,12 @@ - (void)sendUserDetails:(NSString *)userDetails - (void)sendCrashReport:(NSString *)report immediately:(BOOL)immediately; { + if (!CountlyServerConfig.sharedInstance.networkingEnabled) + { + CLY_LOG_D(@"'sendCrashReport' is aborted: SDK Networking is disabled from server config!"); + return; + } + if (!report) { CLY_LOG_W(@"Crash report is nil. Converting to JSON may have failed due to custom objects in initial config's crashSegmentation property."); diff --git a/ios/Classes/CountlyiOS/CountlyCrashReporter.m b/ios/Classes/CountlyiOS/CountlyCrashReporter.m index 8d63fa15..f5d4ee96 100644 --- a/ios/Classes/CountlyiOS/CountlyCrashReporter.m +++ b/ios/Classes/CountlyiOS/CountlyCrashReporter.m @@ -273,7 +273,7 @@ void CountlyExceptionHandler(NSException *exception, bool isFatal, bool isAutoDe crashReport[kCountlyCRKeyRAMTotal] = @(CountlyDeviceInfo.totalRAM / kCLYMebibit); crashReport[kCountlyCRKeyDiskCurrent] = @((CountlyDeviceInfo.totalDisk - CountlyDeviceInfo.freeDisk) / kCLYMebibit); crashReport[kCountlyCRKeyDiskTotal] = @(CountlyDeviceInfo.totalDisk / kCLYMebibit); - crashReport[kCountlyCRKeyBattery] = @(CountlyDeviceInfo.batteryLevel); + //crashReport[kCountlyCRKeyBattery] = @(CountlyDeviceInfo.batteryLevel);//not reporting this to prevent hanging in the middle of crash reporting crashReport[kCountlyCRKeyOrientation] = CountlyDeviceInfo.orientation; crashReport[kCountlyCRKeyOnline] = @((CountlyDeviceInfo.connectionType) ? 1 : 0 ); crashReport[kCountlyCRKeyRoot] = @(CountlyDeviceInfo.isJailbroken); diff --git a/ios/Classes/CountlyiOS/CountlyFeedbackWidget.m b/ios/Classes/CountlyiOS/CountlyFeedbackWidget.m index c3e7eb17..e40e6e57 100644 --- a/ios/Classes/CountlyiOS/CountlyFeedbackWidget.m +++ b/ios/Classes/CountlyiOS/CountlyFeedbackWidget.m @@ -89,6 +89,12 @@ - (void)presentWithAppearBlock:(void(^ __nullable)(void))appearBlock andDismissB - (void)getWidgetData:(void (^)(NSDictionary * __nullable widgetData, NSError * __nullable error))completionHandler { CLY_LOG_I(@"%s %@", __FUNCTION__, completionHandler); + + if (!CountlyServerConfig.sharedInstance.networkingEnabled) + { + CLY_LOG_D(@"'getWidgetData' is aborted: SDK Networking is disabled from server config!"); + return; + } NSURLSessionTask* task = [NSURLSession.sharedSession dataTaskWithRequest:[self dataRequest] completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) { diff --git a/ios/Classes/CountlyiOS/CountlyFeedbacks.m b/ios/Classes/CountlyiOS/CountlyFeedbacks.m index 5c689138..c667cb03 100644 --- a/ios/Classes/CountlyiOS/CountlyFeedbacks.m +++ b/ios/Classes/CountlyiOS/CountlyFeedbacks.m @@ -236,6 +236,12 @@ - (UIColor *)passiveStarColor - (void)checkFeedbackWidgetWithID:(NSString *)widgetID completionHandler:(void (^)(NSError * error))completionHandler { + if (!CountlyServerConfig.sharedInstance.networkingEnabled) + { + CLY_LOG_D(@"'checkFeedbackWidgetWithID' is aborted: SDK Networking is disabled from server config!"); + return; + } + if (!CountlyConsentManager.sharedInstance.consentForFeedback) return; @@ -401,6 +407,12 @@ - (void)recordRatingWidgetWithID:(NSString *)widgetID rating:(NSInteger)rating e - (void)getFeedbackWidgets:(void (^)(NSArray *feedbackWidgets, NSError *error))completionHandler { + if (!CountlyServerConfig.sharedInstance.networkingEnabled) + { + CLY_LOG_D(@"'getFeedbackWidgets' is aborted: SDK Networking is disabled from server config!"); + return; + } + if (!CountlyConsentManager.sharedInstance.consentForFeedback) return; diff --git a/ios/Classes/CountlyiOS/CountlyPersistency.h b/ios/Classes/CountlyiOS/CountlyPersistency.h index 476fa9d1..1119eee5 100644 --- a/ios/Classes/CountlyiOS/CountlyPersistency.h +++ b/ios/Classes/CountlyiOS/CountlyPersistency.h @@ -56,6 +56,9 @@ - (NSDictionary *)retrieveRemoteConfig; - (void)storeRemoteConfig:(NSDictionary *)remoteConfig; +- (NSDictionary *)retrieveServerConfig; +- (void)storeServerConfig:(NSDictionary *)serverConfig; + @property (nonatomic) NSUInteger eventSendThreshold; @property (nonatomic) NSUInteger storedRequestsLimit; @property (nonatomic, readonly) BOOL isQueueBeingModified; diff --git a/ios/Classes/CountlyiOS/CountlyPersistency.m b/ios/Classes/CountlyiOS/CountlyPersistency.m index 0c9f996b..155db1a6 100644 --- a/ios/Classes/CountlyiOS/CountlyPersistency.m +++ b/ios/Classes/CountlyiOS/CountlyPersistency.m @@ -23,6 +23,8 @@ @implementation CountlyPersistency NSString* const kCountlyNotificationPermissionKey = @"kCountlyNotificationPermissionKey"; NSString* const kCountlyIsCustomDeviceIDKey = @"kCountlyIsCustomDeviceIDKey"; NSString* const kCountlyRemoteConfigPersistencyKey = @"kCountlyRemoteConfigPersistencyKey"; +NSString* const kCountlyServerConfigPersistencyKey = @"kCountlyServerConfigPersistencyKey"; + NSString* const kCountlyCustomCrashLogFileName = @"CountlyCustomCrash.log"; @@ -69,6 +71,12 @@ - (instancetype)init - (void)addToQueue:(NSString *)queryString { + if(!CountlyServerConfig.sharedInstance.trackingEnabled) + { + CLY_LOG_D(@"'addToQueue' is aborted: SDK Tracking is disabled from server config!"); + return; + } + if (!queryString.length || [queryString isEqual:NSNull.null]) return; @@ -500,4 +508,19 @@ - (void)storeRemoteConfig:(NSDictionary *)remoteConfig [NSUserDefaults.standardUserDefaults synchronize]; } +- (NSDictionary *)retrieveServerConfig +{ + NSDictionary* serverConfig = [NSUserDefaults.standardUserDefaults objectForKey:kCountlyServerConfigPersistencyKey]; + if (!serverConfig) + serverConfig = NSDictionary.new; + + return serverConfig; +} + +- (void)storeServerConfig:(NSDictionary *)serverConfig +{ + [NSUserDefaults.standardUserDefaults setObject:serverConfig forKey:kCountlyServerConfigPersistencyKey]; + [NSUserDefaults.standardUserDefaults synchronize]; +} + @end diff --git a/ios/Classes/CountlyiOS/CountlyRemoteConfig.m b/ios/Classes/CountlyiOS/CountlyRemoteConfig.m index 6949a94b..348c230d 100644 --- a/ios/Classes/CountlyiOS/CountlyRemoteConfig.m +++ b/ios/Classes/CountlyiOS/CountlyRemoteConfig.m @@ -125,6 +125,11 @@ - (void)clearCachedRemoteConfig - (void)fetchRemoteConfigForKeys:(NSArray *)keys omitKeys:(NSArray *)omitKeys completionHandler:(void (^)(NSDictionary* remoteConfig, NSError * error))completionHandler { + if (!CountlyServerConfig.sharedInstance.networkingEnabled) + { + CLY_LOG_D(@"'fetchRemoteConfigForKeys' is aborted: SDK Networking is disabled from server config!"); + return; + } if (!completionHandler) return; diff --git a/ios/Classes/CountlyiOS/CountlyServerConfig.h b/ios/Classes/CountlyiOS/CountlyServerConfig.h new file mode 100644 index 00000000..db244796 --- /dev/null +++ b/ios/Classes/CountlyiOS/CountlyServerConfig.h @@ -0,0 +1,19 @@ +// CountlyServerConfig.h +// +// This code is provided under the MIT License. +// +// Please visit www.count.ly for more information. + +#import + +@interface CountlyServerConfig : NSObject +#if (TARGET_OS_IOS) ++ (instancetype)sharedInstance; + +- (void)fetchServerConfig; + +@property (nonatomic) BOOL trackingEnabled; +@property (nonatomic) BOOL networkingEnabled; +#endif +@end + diff --git a/ios/Classes/CountlyiOS/CountlyServerConfig.m b/ios/Classes/CountlyiOS/CountlyServerConfig.m new file mode 100644 index 00000000..5ef43e30 --- /dev/null +++ b/ios/Classes/CountlyiOS/CountlyServerConfig.m @@ -0,0 +1,159 @@ +// CountlyServerConfig.m +// +// This code is provided under the MIT License. +// +// Please visit www.count.ly for more information. + +#import "CountlyCommon.h" +#if (TARGET_OS_IOS) +#import +#endif + +NSString* const kCountlySCKeySC = @"sc"; + +@implementation CountlyServerConfig + +@synthesize trackingEnabled = _trackingEnabled; +@synthesize networkingEnabled = _networkingEnabled; + +#if (TARGET_OS_IOS) ++ (instancetype)sharedInstance +{ + if (!CountlyCommon.sharedInstance.hasStarted) + return nil; + + static CountlyServerConfig* s_sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{s_sharedInstance = self.new;}); + return s_sharedInstance; +} + +- (instancetype)init +{ + if (self = [super init]) + { + self.trackingEnabled = YES; + self.networkingEnabled = YES; + NSDictionary* serverConfigObject = [CountlyPersistency.sharedInstance retrieveServerConfig]; + if(serverConfigObject) { + [self populateServerConfig:serverConfigObject]; + } + } + + return self; +} + +- (BOOL)trackingEnabled +{ + if (!CountlyCommon.sharedInstance.enableServerConfiguration) + return YES; + + return _trackingEnabled; +} + +- (BOOL)networkingEnabled +{ + if (!CountlyCommon.sharedInstance.enableServerConfiguration) + return YES; + + return _networkingEnabled; +} + +- (void)populateServerConfig:(NSDictionary *)dictionary +{ + if(dictionary[@"tracking"]) + { + self.trackingEnabled = [dictionary[@"tracking"] boolValue]; + } + if(dictionary[@"networking"]) + { + self.networkingEnabled = [dictionary[@"networking"] boolValue]; + } + + CLY_LOG_D(@"tracking : %@", self.trackingEnabled ? @"YES" : @"NO"); + CLY_LOG_D(@"networking : %@", self.networkingEnabled ? @"YES" : @"NO"); +} + +- (void)fetchServerConfig +{ + CLY_LOG_D(@"Fetching server configs..."); + if (!CountlyCommon.sharedInstance.enableServerConfiguration) + { + CLY_LOG_D(@"'fetchServerConfig' enable server configuration during init time configuration."); + return; + } + + if (CountlyDeviceInfo.sharedInstance.isDeviceIDTemporary) + return; + + NSURLSessionTask* task = [NSURLSession.sharedSession dataTaskWithRequest:[self serverConfigRequest] completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) + { + NSDictionary *serverConfigResponse = nil; + + if (!error) + { + serverConfigResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; + CLY_LOG_D(@"Server Config Fetched: %@", serverConfigResponse.description); + } + + if (!error) + { + if (((NSHTTPURLResponse*)response).statusCode != 200) + { + NSMutableDictionary* serverConfig = serverConfigResponse.mutableCopy; + serverConfig[NSLocalizedDescriptionKey] = @"Server configuration general API error"; + error = [NSError errorWithDomain:kCountlyErrorDomain code:CLYErrorServerConfigGeneralAPIError userInfo:serverConfig]; + } + } + + if (error) + { + CLY_LOG_E(@"Error while fetching server configs: %@",error.description); + return; + } + + NSDictionary* serverConfigObject = serverConfigResponse[@"c"]; + if(serverConfigObject) { + [self populateServerConfig:serverConfigObject]; + [CountlyPersistency.sharedInstance storeServerConfig:serverConfigObject]; + } + + }]; + + [task resume]; +} + +- (NSURLRequest *)serverConfigRequest +{ + NSString* queryString = [NSString stringWithFormat:@"%@=%@&%@=%@&%@=%@&%@=%@&%@=%@", + kCountlyQSKeyMethod, kCountlySCKeySC, + kCountlyQSKeyAppKey, CountlyConnectionManager.sharedInstance.appKey.cly_URLEscaped, + kCountlyQSKeyDeviceID, CountlyDeviceInfo.sharedInstance.deviceID.cly_URLEscaped, + kCountlyQSKeySDKName, CountlyCommon.sharedInstance.SDKName, + kCountlyQSKeySDKVersion, CountlyCommon.sharedInstance.SDKVersion]; + + queryString = [CountlyConnectionManager.sharedInstance appendChecksum:queryString]; + + NSMutableString* URL = CountlyConnectionManager.sharedInstance.host.mutableCopy; + [URL appendString:kCountlyEndpointO]; + [URL appendString:kCountlyEndpointSDK]; + + if (queryString.length > kCountlyGETRequestMaxLength || CountlyConnectionManager.sharedInstance.alwaysUsePOST) + { + NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:URL]]; + request.HTTPMethod = @"POST"; + request.HTTPBody = [queryString cly_dataUTF8]; + return request.copy; + } + else + { + [URL appendFormat:@"?%@", queryString]; + NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:URL]]; + return request; + } + + CLY_LOG_D(@"serverConfigRequest URL :%@", URL); +} + +#endif +@end diff --git a/scripts/no-push-files/build.gradle b/scripts/no-push-files/build.gradle index 360f2460..86e0073e 100644 --- a/scripts/no-push-files/build.gradle +++ b/scripts/no-push-files/build.gradle @@ -34,5 +34,5 @@ android { } dependencies { - implementation 'ly.count.android:sdk:22.09.3' + implementation 'ly.count.android:sdk:22.09.4' }