From c89c47825c92d709f4fc6c07bbb9441997812df1 Mon Sep 17 00:00:00 2001 From: Nan Date: Mon, 23 Mar 2026 22:56:04 -0700 Subject: [PATCH 1/2] Align iOS session duration reporting with Android SDK behavior Both attributed and unattributed focus time processors now behave like the Android SDK: session time accumulates silently across background events, and both the user property update and the outcomes/measure call are sent together only after a 30-second timer fires (confirming the user has left). If the user returns within 30 seconds, the timer is cancelled and no calls are made. Changes to OSAttributedFocusTimeProcessor: - Move sendSessionTime from sendOnFocusCall to the actual send method so it only fires when the 30s timer fires or the session ends, sending the full accumulated total instead of each interval Changes to OSUnattributedFocusTimeProcessor: - Add 30s NSTimer delay before sending to outcomes/measure - Send immediately (no delay) when session ends via influence change - Implement cancelDelayedJob to invalidate timer on foreground return - Clear unsent time only on outcomes success (retry on failure) - Move sendSessionTime to the actual send method (same as attributed) - Reduce min session threshold from 60s to 1s to match attributed Made-with: Cursor --- .../Source/OSAttributedFocusTimeProcessor.m | 8 +-- .../Source/OSUnattributedFocusTimeProcessor.m | 61 ++++++++++++++----- 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/Source/OSAttributedFocusTimeProcessor.m b/iOS_SDK/OneSignalSDK/Source/OSAttributedFocusTimeProcessor.m index 5beb0464a..37ef9a7d9 100644 --- a/iOS_SDK/OneSignalSDK/Source/OSAttributedFocusTimeProcessor.m +++ b/iOS_SDK/OneSignalSDK/Source/OSAttributedFocusTimeProcessor.m @@ -61,11 +61,8 @@ - (void)sendOnFocusCall:(OSFocusCallParams *)params { let totalTimeActive = unsentActive + params.timeElapsed; [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:[NSString stringWithFormat:@"sendOnFocusCall attributed with totalTimeActive %f", totalTimeActive]]; - + [super saveUnsentActiveTime:totalTimeActive]; - - [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:[NSString stringWithFormat:@"OSAttributedFocusTimeProcessor:sendSessionTime of %@", @(params.timeElapsed)]]; - [OneSignalUserManagerImpl.sharedInstance sendSessionTime:@(params.timeElapsed)]; [self sendOnFocusCallWithParams:params totalTimeActive:totalTimeActive]; } @@ -110,6 +107,9 @@ - (void)sendBackgroundAttributedSessionTimeWithNSTimer:(NSTimer*)timer { - (void)sendBackgroundAttributedSessionTimeWithParams:(OSFocusCallParams *)params withTotalTimeActive:(NSNumber*)totalTimeActive { [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:@"OSAttributedFocusTimeProcessor:sendBackgroundAttributedSessionTimeWithParams start"]; + [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:[NSString stringWithFormat:@"OSAttributedFocusTimeProcessor:sendSessionTime of %@", totalTimeActive]]; + [OneSignalUserManagerImpl.sharedInstance sendSessionTime:totalTimeActive]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [OneSignal sendSessionEndOutcomes:totalTimeActive params:params onSuccess:^(NSDictionary *result) { [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:@"sendBackgroundAttributed succeed"]; diff --git a/iOS_SDK/OneSignalSDK/Source/OSUnattributedFocusTimeProcessor.m b/iOS_SDK/OneSignalSDK/Source/OSUnattributedFocusTimeProcessor.m index 8aff9d922..3ccc97840 100644 --- a/iOS_SDK/OneSignalSDK/Source/OSUnattributedFocusTimeProcessor.m +++ b/iOS_SDK/OneSignalSDK/Source/OSUnattributedFocusTimeProcessor.m @@ -36,9 +36,12 @@ @interface OneSignal () + (void)sendSessionEndOutcomes:(NSNumber*)totalTimeActive params:(OSFocusCallParams *)params onSuccess:(OSResultSuccessBlock _Nonnull)successBlock onFailure:(OSFailureBlock _Nonnull)failureBlock; @end -@implementation OSUnattributedFocusTimeProcessor +@implementation OSUnattributedFocusTimeProcessor { + NSTimer* restCallTimer; +} -static let UNATTRIBUTED_MIN_SESSION_TIME_SEC = 60; +static let UNATTRIBUTED_MIN_SESSION_TIME_SEC = 1; +static let DELAY_TIME = 30; - (instancetype)init { self = [super init]; @@ -57,12 +60,12 @@ - (NSString*)unsentActiveTimeUserDefaultsKey { - (void)sendOnFocusCall:(OSFocusCallParams *)params { let unsentActive = [super getUnsentActiveTime]; let totalTimeActive = unsentActive + params.timeElapsed; - + [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:[NSString stringWithFormat:@"sendOnFocusCall unattributed with totalTimeActive %f", totalTimeActive]]; - + + [super saveUnsentActiveTime:totalTimeActive]; + if (![super hasMinSyncTime:totalTimeActive]) { - [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:[NSString stringWithFormat:@"unattributed influence saveUnsentActiveTime %f", totalTimeActive]]; - [super saveUnsentActiveTime:totalTimeActive]; return; } @@ -72,31 +75,59 @@ - (void)sendOnFocusCall:(OSFocusCallParams *)params { - (void)sendUnsentActiveTime:(OSFocusCallParams *)params { let unsentActive = [super getUnsentActiveTime]; [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:[NSString stringWithFormat:@"sendUnsentActiveTime unattributed with unsentActive %f", unsentActive]]; - + [self sendOnFocusCallWithParams:params totalTimeActive:unsentActive]; } - (void)sendOnFocusCallWithParams:(OSFocusCallParams *)params totalTimeActive:(NSTimeInterval)totalTimeActive { - [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:[NSString stringWithFormat:@"OSUnattributedFocusTimeProcessor:sendSessionTime of %@", @(totalTimeActive)]]; - [OneSignalUserManagerImpl.sharedInstance sendSessionTime:@(totalTimeActive)]; - [super saveUnsentActiveTime:0]; - [OSBackgroundTaskManager beginBackgroundTask:SESSION_OUTCOMES_TASK]; + + if (params.onSessionEnded) { + [self sendBackgroundUnattributedSessionTimeWithParams:params withTotalTimeActive:@(totalTimeActive)]; + return; + } + + restCallTimer = [NSTimer + scheduledTimerWithTimeInterval:DELAY_TIME + target:self + selector:@selector(sendBackgroundUnattributedSessionTimeWithNSTimer:) + userInfo:@{@"params": params, @"time": @(totalTimeActive)} + repeats:false]; +} + +- (void)sendBackgroundUnattributedSessionTimeWithNSTimer:(NSTimer*)timer { + let userInfo = (NSDictionary*)timer.userInfo; + let params = (OSFocusCallParams*)userInfo[@"params"]; + let totalTimeActive = (NSNumber*)userInfo[@"time"]; + [self sendBackgroundUnattributedSessionTimeWithParams:params withTotalTimeActive:totalTimeActive]; +} + +- (void)sendBackgroundUnattributedSessionTimeWithParams:(OSFocusCallParams *)params withTotalTimeActive:(NSNumber*)totalTimeActive { + [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:@"OSUnattributedFocusTimeProcessor:sendBackgroundUnattributedSessionTimeWithParams start"]; + + [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:[NSString stringWithFormat:@"OSUnattributedFocusTimeProcessor:sendSessionTime of %@", totalTimeActive]]; + [OneSignalUserManagerImpl.sharedInstance sendSessionTime:totalTimeActive]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [OneSignal sendSessionEndOutcomes:@(totalTimeActive) params:params onSuccess:^(NSDictionary *result) { + [OneSignal sendSessionEndOutcomes:totalTimeActive params:params onSuccess:^(NSDictionary *result) { [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:@"sendUnattributed session end outcomes succeed"]; + [super saveUnsentActiveTime:0]; [OSBackgroundTaskManager endBackgroundTask:SESSION_OUTCOMES_TASK]; } onFailure:^(NSError *error) { - [OneSignalLog onesignalLog:ONE_S_LL_ERROR message:@"sendUnattributed session end outcomes failed"]; + [OneSignalLog onesignalLog:ONE_S_LL_ERROR message:@"sendUnattributed session end outcomes failed, will retry on next open"]; [OSBackgroundTaskManager endBackgroundTask:SESSION_OUTCOMES_TASK]; }]; }); } - (void)cancelDelayedJob { - // No job to cancel, network call is made right away. -} + if (!restCallTimer) + return; + [restCallTimer invalidate]; + restCallTimer = nil; + [OSBackgroundTaskManager endBackgroundTask:SESSION_OUTCOMES_TASK]; +} @end From 4bc508c3ab59ea5dc627d69219ab67e161e244c4 Mon Sep 17 00:00:00 2001 From: Nan Date: Tue, 24 Mar 2026 01:40:22 -0700 Subject: [PATCH 2/2] Remove unused min session time threshold Both processors now use a unified `< 1` guard in sendOnFocusCallWithParams: instead of the old getMinSessionTime/hasMinSyncTime dispatch through the base class. --- .../Source/OSAttributedFocusTimeProcessor.m | 5 ----- .../OneSignalSDK/Source/OSBaseFocusTimeProcessor.h | 2 -- .../OneSignalSDK/Source/OSBaseFocusTimeProcessor.m | 11 ----------- .../Source/OSUnattributedFocusTimeProcessor.m | 14 +++++--------- 4 files changed, 5 insertions(+), 27 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/Source/OSAttributedFocusTimeProcessor.m b/iOS_SDK/OneSignalSDK/Source/OSAttributedFocusTimeProcessor.m index 37ef9a7d9..649d2468b 100644 --- a/iOS_SDK/OneSignalSDK/Source/OSAttributedFocusTimeProcessor.m +++ b/iOS_SDK/OneSignalSDK/Source/OSAttributedFocusTimeProcessor.m @@ -39,7 +39,6 @@ @implementation OSAttributedFocusTimeProcessor { NSTimer* restCallTimer; } -static let ATTRIBUTED_MIN_SESSION_TIME_SEC = 1; static let DELAY_TIME = 30; - (instancetype)init { @@ -48,10 +47,6 @@ - (instancetype)init { return self; } -- (int)getMinSessionTime { - return ATTRIBUTED_MIN_SESSION_TIME_SEC; -} - - (NSString*)unsentActiveTimeUserDefaultsKey { return OSUD_UNSENT_ACTIVE_TIME_ATTRIBUTED; } diff --git a/iOS_SDK/OneSignalSDK/Source/OSBaseFocusTimeProcessor.h b/iOS_SDK/OneSignalSDK/Source/OSBaseFocusTimeProcessor.h index 6f3bbe3d7..c3cd8269d 100644 --- a/iOS_SDK/OneSignalSDK/Source/OSBaseFocusTimeProcessor.h +++ b/iOS_SDK/OneSignalSDK/Source/OSBaseFocusTimeProcessor.h @@ -34,9 +34,7 @@ @property (nonatomic, readonly) BOOL onFocusCallEnabled; -- (int)getMinSessionTime; - (NSString*)unsentActiveTimeUserDefaultsKey; -- (BOOL)hasMinSyncTime:(NSTimeInterval)activeTime; - (void)resetUnsentActiveTime; - (void)sendOnFocusCall:(OSFocusCallParams *)params; diff --git a/iOS_SDK/OneSignalSDK/Source/OSBaseFocusTimeProcessor.m b/iOS_SDK/OneSignalSDK/Source/OSBaseFocusTimeProcessor.m index 2fb3321a9..802071d47 100644 --- a/iOS_SDK/OneSignalSDK/Source/OSBaseFocusTimeProcessor.m +++ b/iOS_SDK/OneSignalSDK/Source/OSBaseFocusTimeProcessor.m @@ -33,17 +33,6 @@ @implementation OSBaseFocusTimeProcessor { NSNumber* unsentActiveTime; } -// Must override -- (int)getMinSessionTime { - @throw [NSException exceptionWithName:NSInternalInconsistencyException - reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)] userInfo:nil]; -} - -- (BOOL)hasMinSyncTime:(NSTimeInterval)activeTime { - [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:[NSString stringWithFormat:@"OSBaseFocusTimeProcessor hasMinSyncTime getMinSessionTime: %d activeTime: %f", [self getMinSessionTime], activeTime]]; - return activeTime >= [self getMinSessionTime]; -} - - (void)resetUnsentActiveTime { unsentActiveTime = nil; } diff --git a/iOS_SDK/OneSignalSDK/Source/OSUnattributedFocusTimeProcessor.m b/iOS_SDK/OneSignalSDK/Source/OSUnattributedFocusTimeProcessor.m index 3ccc97840..714631131 100644 --- a/iOS_SDK/OneSignalSDK/Source/OSUnattributedFocusTimeProcessor.m +++ b/iOS_SDK/OneSignalSDK/Source/OSUnattributedFocusTimeProcessor.m @@ -40,7 +40,6 @@ @implementation OSUnattributedFocusTimeProcessor { NSTimer* restCallTimer; } -static let UNATTRIBUTED_MIN_SESSION_TIME_SEC = 1; static let DELAY_TIME = 30; - (instancetype)init { @@ -49,10 +48,6 @@ - (instancetype)init { return self; } -- (int)getMinSessionTime { - return UNATTRIBUTED_MIN_SESSION_TIME_SEC; -} - - (NSString*)unsentActiveTimeUserDefaultsKey { return OSUD_UNSENT_ACTIVE_TIME; } @@ -65,10 +60,6 @@ - (void)sendOnFocusCall:(OSFocusCallParams *)params { [super saveUnsentActiveTime:totalTimeActive]; - if (![super hasMinSyncTime:totalTimeActive]) { - return; - } - [self sendOnFocusCallWithParams:params totalTimeActive:totalTimeActive]; } @@ -80,6 +71,11 @@ - (void)sendUnsentActiveTime:(OSFocusCallParams *)params { } - (void)sendOnFocusCallWithParams:(OSFocusCallParams *)params totalTimeActive:(NSTimeInterval)totalTimeActive { + if (totalTimeActive < 1) { + [OneSignalLog onesignalLog:ONE_S_LL_DEBUG message:[NSString stringWithFormat:@"sendSessionEndOutcomes not sending active time %f", totalTimeActive]]; + return; + } + [OSBackgroundTaskManager beginBackgroundTask:SESSION_OUTCOMES_TASK]; if (params.onSessionEnded) {