Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
## XX.XX.XX
* Added support for SDK behavior settings that control the SDK's automatic session tracking, automatic view tracking, automatic crash reporting, and Journey Trigger Views.
* Added a new user properties functions on `CountlyUserDetails`:
* `setProperty:value:` for setting a single predefined or custom user property.
* `setProperties:` for setting multiple predefined and custom properties in one call.
Expand Down
87 changes: 62 additions & 25 deletions Countly.m
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,8 @@ - (void)startWithConfig:(CountlyConfig *)config
[CountlyLocationManager.sharedInstance updateLocation:config.location city:config.city ISOCountryCode:config.ISOCountryCode IP:config.IP];
}

if (!CountlyCommon.sharedInstance.manualSessionHandling)
// Automatic session tracking is resolved through the SBS precedence chain (server can override the developer's manual session control choice)
if (CountlyServerConfig.sharedInstance.automaticSessionTrackingEnabled)
[CountlyConnectionManager.sharedInstance beginSession];
else
[CountlyCommon.sharedInstance recordOrientation];
Expand Down Expand Up @@ -233,21 +234,23 @@ - (void)startWithConfig:(CountlyConfig *)config
if ([config.features containsObject:CLYCrashReporting])
{
CountlyCrashReporter.sharedInstance.isEnabledOnInitialConfig = YES;
if (CountlyServerConfig.sharedInstance.crashReportingEnabled)
{
}
// Automatic crash reporting is resolved through the SBS precedence chain, so the server can enable it even when the developer did not
if (CountlyServerConfig.sharedInstance.crashReportingEnabled && CountlyServerConfig.sharedInstance.automaticCrashReportingEnabled)
{
[CountlyCrashReporter.sharedInstance startCrashReporting];
}
}

#if (TARGET_OS_IOS || TARGET_OS_TV )
if (config.enableAutomaticViewTracking || [config.features containsObject:CLYAutoViewTracking])
{
// Print deprecation flag for feature
CountlyViewTrackingInternal.sharedInstance.isEnabledOnInitialConfig = YES;
if (CountlyServerConfig.sharedInstance.viewTrackingEnabled)
{
[CountlyViewTrackingInternal.sharedInstance startAutoViewTracking];
}
}
// Automatic view tracking is resolved through the SBS precedence chain, so the server can enable it even when the developer did not
if (CountlyServerConfig.sharedInstance.viewTrackingEnabled && CountlyServerConfig.sharedInstance.automaticViewTrackingEnabled)
{
[CountlyViewTrackingInternal.sharedInstance startAutoViewTracking];
}
if (config.automaticViewTrackingExclusionList) {
[CountlyViewTrackingInternal.sharedInstance addAutoViewTrackingExclutionList:config.automaticViewTrackingExclusionList];
Expand Down Expand Up @@ -394,11 +397,11 @@ - (void)onTimer:(NSTimer *)timer
if (isSuspended)
return;

if (!CountlyCommon.sharedInstance.manualSessionHandling)
if (CountlyServerConfig.sharedInstance.automaticSessionTrackingEnabled)
{
[CountlyConnectionManager.sharedInstance updateSession];
}
// this condtion is called only when both manual session handling and hybrid mode is enabled.
// this condtion is called only when automatic session tracking is not active and hybrid mode is enabled.
else if (CountlyCommon.sharedInstance.enableManualSessionControlHybridMode)
{
[CountlyConnectionManager.sharedInstance updateSession];
Expand Down Expand Up @@ -427,9 +430,9 @@ - (void)suspend

[CountlyConnectionManager.sharedInstance sendEventsWithSaveIfNeeded];

if (!CountlyCommon.sharedInstance.manualSessionHandling)
if (CountlyServerConfig.sharedInstance.automaticSessionTrackingEnabled)
[CountlyConnectionManager.sharedInstance endSession];

[CountlyPersistency.sharedInstance saveToFile];
}

Expand All @@ -455,9 +458,9 @@ - (void)resume

CLY_LOG_D(@"%s manualSessions: [%d]", __FUNCTION__, CountlyCommon.sharedInstance.manualSessionHandling);

if (!CountlyCommon.sharedInstance.manualSessionHandling)
if (CountlyServerConfig.sharedInstance.automaticSessionTrackingEnabled)
[CountlyConnectionManager.sharedInstance beginSession];

[CountlyViewTrackingInternal.sharedInstance applicationWillEnterForeground];

isSuspended = NO;
Expand Down Expand Up @@ -615,28 +618,41 @@ - (void)addCustomNetworkRequestHeaders:(NSDictionary<NSString *, NSString *> *_N
- (void)beginSession
{
CLY_LOG_I(@"%s", __FUNCTION__);

if (CountlyCommon.sharedInstance.manualSessionHandling)
[CountlyConnectionManager.sharedInstance beginSession];

if (CountlyServerConfig.sharedInstance.automaticSessionTrackingEnabled)
{
CLY_LOG_W(@"%s 'beginSession' will be ignored since automatic session tracking is active", __FUNCTION__);
return;
}

[CountlyConnectionManager.sharedInstance beginSession];
}

- (void)updateSession
{
CLY_LOG_I(@"%s", __FUNCTION__);

if (CountlyCommon.sharedInstance.manualSessionHandling)
[CountlyConnectionManager.sharedInstance updateSession];

if (CountlyServerConfig.sharedInstance.automaticSessionTrackingEnabled)
{
CLY_LOG_W(@"%s 'updateSession' will be ignored since automatic session tracking is active", __FUNCTION__);
return;
}

[CountlyConnectionManager.sharedInstance updateSession];
}

- (void)endSession
{
CLY_LOG_I(@"%s", __FUNCTION__);
if (CountlyCommon.sharedInstance.manualSessionHandling)

if (CountlyServerConfig.sharedInstance.automaticSessionTrackingEnabled)
{
[CountlyConnectionManager.sharedInstance sendEventsWithSaveIfNeeded];
[CountlyConnectionManager.sharedInstance endSession];
CLY_LOG_W(@"%s 'endSession' will be ignored since automatic session tracking is active", __FUNCTION__);
return;
}

[CountlyConnectionManager.sharedInstance sendEventsWithSaveIfNeeded];
[CountlyConnectionManager.sharedInstance endSession];
}


Expand Down Expand Up @@ -1022,7 +1038,28 @@ - (void)recordEvent:(NSString *)key segmentation:(NSDictionary *)segmentation co
{
event.key = key;
event.segmentation = [self processSegmentation:filteredSegmentations eventKey:key];
[CountlyPersistency.sharedInstance recordEvent:event];
id callback = nil;
// Journey trigger views mirror the journey trigger events behavior: a matching view name force-flushes
// the event queue and refreshes the content zone when the request succeeds. The 'name' segmentation value
// is matched as sent (after truncation and filtering), same as the wire format.
if ([key isEqualToString:kCountlyReservedEventView])
{
NSString* viewName = event.segmentation[kCountlyVTKeyName];
if ([viewName isKindOfClass:NSString.class] && [CountlyServerConfig.sharedInstance isJourneyTriggerView:viewName])
{
callback = ^(NSString *response, BOOL success) {
if (success)
{
#if (TARGET_OS_IOS)
dispatch_async(dispatch_get_main_queue(), ^{
[CountlyContentBuilderInternal.sharedInstance refreshContentZoneJTE];
});
#endif
}
};
}
}
[CountlyPersistency.sharedInstance recordEvent:event callback:callback];
}
}

Expand Down
8 changes: 4 additions & 4 deletions CountlyConnectionManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -596,17 +596,17 @@ - (void)beginSession
}

#if TARGET_OS_IOS || TARGET_OS_TV
if (!CountlyCommon.sharedInstance.manualSessionHandling && [UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
if (CountlyServerConfig.sharedInstance.automaticSessionTrackingEnabled && [UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
CLY_LOG_W(@"%s App is in the background, 'beginSession' will be ignored", __FUNCTION__);
return;
}
#elif TARGET_OS_OSX
if (!CountlyCommon.sharedInstance.manualSessionHandling && ![NSApplication sharedApplication].isActive) {
if (CountlyServerConfig.sharedInstance.automaticSessionTrackingEnabled && ![NSApplication sharedApplication].isActive) {
CLY_LOG_W(@"%s App is not active, 'beginSession' will be ignored", __FUNCTION__);
return;
}
#elif TARGET_OS_WATCH
if (!CountlyCommon.sharedInstance.manualSessionHandling && [WKExtension sharedExtension].applicationState == WKApplicationStateBackground) {
if (CountlyServerConfig.sharedInstance.automaticSessionTrackingEnabled && [WKExtension sharedExtension].applicationState == WKApplicationStateBackground) {
CLY_LOG_W(@"%s App is in the background, 'beginSession' will be ignored", __FUNCTION__);
return;
}
Expand Down Expand Up @@ -823,7 +823,7 @@ - (void)sendCrashReport:(NSString *)report immediately:(BOOL)immediately;

[self sendEventsWithSaveIfNeeded];

if (!CountlyCommon.sharedInstance.manualSessionHandling)
if (CountlyServerConfig.sharedInstance.automaticSessionTrackingEnabled)
[self endSession];

if (CountlyDeviceInfo.sharedInstance.isDeviceIDTemporary)
Expand Down
2 changes: 1 addition & 1 deletion CountlyConsentManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ - (void)setConsentForSessions:(BOOL)consentForSessions
{
CLY_LOG_D(@"Consent for Session is given.");

if (!CountlyCommon.sharedInstance.manualSessionHandling)
if (CountlyServerConfig.sharedInstance.automaticSessionTrackingEnabled)
[CountlyConnectionManager.sharedInstance beginSession];
}
else
Expand Down
28 changes: 25 additions & 3 deletions CountlyCrashReporter.m
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@


@interface CountlyCrashReporter ()
// Tracks whether the unhandled crash handlers have been installed, so they are installed only once
// and can be removed even when they were force-enabled by the server instead of the developer config
@property (nonatomic) BOOL unhandledCrashHandlerInstalled;
@property (nonatomic) NSMutableArray* customCrashLogs;
@property (nonatomic) NSDateFormatter* dateFormatter;
@property (nonatomic) NSString* buildUUID;
Expand Down Expand Up @@ -91,12 +94,22 @@ - (instancetype)init

- (void)startCrashReporting
{
if (!self.isEnabledOnInitialConfig)
// For the default handler path the live handler is checked instead of only the flag, since the
// global handler can be replaced externally while the singleton (and its flag) lives on
BOOL alreadyInstalled = self.unhandledCrashHandlerInstalled && (self.shouldUsePLCrashReporter || NSGetUncaughtExceptionHandler() == &CountlyUncaughtExceptionHandler);
if (alreadyInstalled)
return;

// Gated on the resolved 'acr' value (seeded from the developer config, overridable by the server),
// so the server can enable automatic crash reporting even when the developer did not opt in
if (!CountlyServerConfig.sharedInstance.automaticCrashReportingEnabled)
return;

if (!CountlyConsentManager.sharedInstance.consentForCrashReporting)
return;

self.unhandledCrashHandlerInstalled = YES;

if (self.shouldUsePLCrashReporter)
{
#ifdef COUNTLY_PLCRASHREPORTER_EXISTS
Expand All @@ -123,9 +136,11 @@ - (void)startCrashReporting

- (void)stopCrashReporting
{
if (!self.isEnabledOnInitialConfig)
if (!self.unhandledCrashHandlerInstalled)
return;

self.unhandledCrashHandlerInstalled = NO;

NSSetUncaughtExceptionHandler(NULL);

#if (TARGET_OS_IOS || TARGET_OS_VISION || TARGET_OS_TV || TARGET_OS_OSX)
Expand Down Expand Up @@ -246,7 +261,14 @@ void CountlyExceptionHandler(NSException *exception, bool isFatal, bool isAutoDe
{
if (!CountlyServerConfig.sharedInstance.crashReportingEnabled)
return;


// Automatically detected crashes are additionally gated on the resolved 'acr' value, so a runtime
// 'acr' = false from the server disables them without uninstalling the handler. Manually recorded
// exceptions stay governed by 'crt' only.
if (isAutoDetect && !CountlyServerConfig.sharedInstance.automaticCrashReportingEnabled)
return;


NSArray* stackTrace = exception.userInfo[kCountlyExceptionUserInfoBacktraceKey];
if (!stackTrace)
stackTrace = exception.callStackSymbols;
Expand Down
4 changes: 4 additions & 0 deletions CountlyServerConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ extern NSString* const kCountlySCKeySC;
- (NSInteger)sessionInterval;
- (NSInteger)eventQueueSize;
- (BOOL)crashReportingEnabled;
- (BOOL)automaticSessionTrackingEnabled;
- (BOOL)automaticViewTrackingEnabled;
- (BOOL)automaticCrashReportingEnabled;
- (BOOL)loggingEnabled;
- (NSInteger)limitKeyLength;
- (NSInteger)limitValueSize;
Expand Down Expand Up @@ -53,6 +56,7 @@ extern NSString* const kCountlySCKeySC;
- (BOOL)shouldRecordUserProperty:(NSString *)propertyKey;
- (NSDictionary *)filterSegmentation:(NSDictionary *)segmentation eventKey:(NSString *)eventKey;
- (BOOL)isJourneyTriggerEvent:(NSString *)eventKey;
- (BOOL)isJourneyTriggerView:(NSString *)viewName;
- (NSInteger)userPropertyCacheLimit;

@end
Expand Down
Loading
Loading