diff --git a/MVMCore/MVMCore/Session/MVMCoreSessionTimeHandler.h b/MVMCore/MVMCore/Session/MVMCoreSessionTimeHandler.h index d31ae62..99ee577 100644 --- a/MVMCore/MVMCore/Session/MVMCoreSessionTimeHandler.h +++ b/MVMCore/MVMCore/Session/MVMCoreSessionTimeHandler.h @@ -13,50 +13,57 @@ @interface MVMCoreSessionTimeHandler : NSObject -// The time that we started the last session timer. -@property (assign, nonatomic, readonly) NSTimeInterval timeTimerStarted; +/// The number of seconds ago that we started the last session timer. Distant past if it wasn't started. +@property (nonatomic, readonly) NSTimeInterval timeSinceStart; -// Keeps track of if the session is currently being timed. Used for entering from the background. +/// The number of seconds remaining until a warning will occur. +@property (nonatomic, readonly) NSTimeInterval remainingTimeUntilWarning; + +/// The number of seconds remaining until a timeout will occur. +@property (nonatomic, readonly) NSTimeInterval remainingTimeUntiTimeout; + + +/// Keeps track of if the session is currently being timed. Used for entering from the background. @property (assign, nonatomic, readonly) BOOL sessionBeingTimed; -// Keeps track of if the session has already timed out. +/// Keeps track of if the session has already timed out. @property (assign, nonatomic, readonly) BOOL sessionTimedOut; #pragma mark - functions to override -// Can override to provide a time until the warning shows in seconds. Set to 0 if there should be no warning. Default is 0 +/// Can override to provide a time until the warning shows in seconds. Set to 0 if there should be no warning. Default is 0 - (NSTimeInterval)timeUntilWarning; -// Can override to provide a time until the timeout happens in seconds. If there is a warning, then this value is used after the warning happens. Set to 0 if there should be no timeout. Default is 0. +/// Can override to provide a time until the timeout happens in seconds. If there is a warning, then this value is used after the warning happens. Set to 0 if there should be no timeout. Default is 0. - (NSTimeInterval)timeUntilTimeout; -// Starts the timeout timer. Override to handle what happens on timeout warning. Should call super if want the timeout timer going. +/// Starts the timeout timer. Override to handle what happens on timeout warning. Should call super if want the timeout timer going. - (void)sessionTimeoutWarning NS_REQUIRES_SUPER; -// Called when the session has timed out. Override to handle what happens on timeout. Should call super. Can be called to force timeout... should never need to call unless simulating timout. +/// Called when the session has timed out. Override to handle what happens on timeout. Should call super. Can be called to force timeout... should never need to call unless simulating timout. - (void)sessionTimeout:(BOOL)whileInBackground NS_REQUIRES_SUPER; -// Keeps the session alive. A boolean for if we should show the alert if there is an error. Does nothing by default. Can override to do something. +/// Keeps the session alive. A boolean for if we should show the alert if there is an error. Does nothing by default. Can override to do something. - (void)sendKeepAliveToServer:(BOOL)notifyUserIfError; -// Invalidates the server session and then calls the completion handler. Error may or may not populate. By default this only calls the completion handler, override to invalidate your server session as you see fit then call completion. +/// Invalidates the server session and then calls the completion handler. Error may or may not populate. By default this only calls the completion handler, override to invalidate your server session as you see fit then call completion. - (void)invalidateSession:(void (^ __nullable)(MVMCoreErrorObject * _Nullable error))completion; #pragma mark - Session timer functions -// Returns the shared instance of this singleton +/// Returns the shared instance of this singleton + (nullable instancetype)sharedSessionHandler; -// Starts the session timer. Should be called after every response from the server. Happens on the main thread. +/// Starts the session timer. Should be called after every response from the server. Happens on the main thread. - (void)startSessionTimer; -// Should only be used in rare occassions, like on the original wifi screen. +/// Completely stop the session timer. Should only be used in rare occassions, like on the original wifi screen. - (void)stopSessionTimer; -// Returns whether the app is in session. +/// Returns whether the app is in session. - (BOOL)isAppInSession; -// Resets everything. +/// Resets everything. - (void)resetState; @end diff --git a/MVMCore/MVMCore/Session/MVMCoreSessionTimeHandler.m b/MVMCore/MVMCore/Session/MVMCoreSessionTimeHandler.m index f0bf1b5..be90025 100644 --- a/MVMCore/MVMCore/Session/MVMCoreSessionTimeHandler.m +++ b/MVMCore/MVMCore/Session/MVMCoreSessionTimeHandler.m @@ -24,22 +24,22 @@ @property (strong, nonatomic) NSTimer *sessionWarningTimer; @property (strong, nonatomic) NSTimer *sessionTimer; -// The time that we started the last session timer. +/// The time that we started the last session timer. @property (assign, nonatomic, readwrite) NSTimeInterval timeTimerStarted; -// Keeps track of if the session is currently being timed. Used for entering from the background. +/// Keeps track of if the session is currently being timed. Used for entering from the background. @property (assign, nonatomic, readwrite) BOOL sessionBeingTimed; -// Keeps track of if the session has already timed out. +/// Keeps track of if the session has already timed out. @property (assign, nonatomic, readwrite) BOOL sessionTimedOut; @property (assign, nonatomic) NSTimeInterval secondsUntilWarning; @property (assign, nonatomic) NSTimeInterval secondsUntilTimeout; -// Should be called when the app enters the background. +/// Should be called when the app enters the background. - (void)appEnteredBackground; -// Should be called when the app enters the foreground. +/// Should be called when the app enters the foreground. - (void)appEnteredForeground; @end @@ -55,6 +55,7 @@ // Adds notifications for if the app entered the background/foreground. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appEnteredBackground) name:UIApplicationDidEnterBackgroundNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appEnteredForeground) name:UIApplicationWillEnterForegroundNotification object:nil]; + [self resetStartTime]; } return self; } @@ -85,6 +86,28 @@ - (void)sendKeepAliveToServer:(BOOL)notifyUserIfError { } +#pragma mark - State + +- (void)markStartTime { + self.timeTimerStarted = [NSDate timeIntervalSinceReferenceDate]; +} + +- (void)resetStartTime { + self.timeTimerStarted = [[NSDate distantPast] timeIntervalSinceReferenceDate]; +} + +- (NSTimeInterval)timeSinceStart { + return [NSDate timeIntervalSinceReferenceDate] - self.timeTimerStarted; +} + +- (NSTimeInterval)remainingTimeUntilWarning { + return self.secondsUntilWarning - self.timeSinceStart; +} + +- (NSTimeInterval)remainingTimeUntiTimeout { + return self.secondsUntilTimeout - self.timeSinceStart; +} + #pragma mark - Session timer functions - (void)startSessionTimer { @@ -97,22 +120,34 @@ self.secondsUntilWarning = [self timeUntilWarning]; self.secondsUntilTimeout = [self timeUntilTimeout]; + if (!fequal(0, self.secondsUntilTimeout)) { self.sessionBeingTimed = YES; - self.timeTimerStarted = [NSDate timeIntervalSinceReferenceDate]; - } - - // Only start physical timer if active, otherwise will begin once entering foreground. - if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive && !fequal(0, self.secondsUntilTimeout)) { - if (!fequal(0, self.secondsUntilWarning)) { - self.sessionWarningTimer = [NSTimer scheduledTimerWithTimeInterval:self.secondsUntilWarning target:self selector:@selector(sessionTimeoutWarning) userInfo:nil repeats:NO]; + [self markStartTime]; + + // Only start physical timer if active, otherwise will begin once entering foreground. FaceId prompt is considered UIApplicationStateInactive. + if ([UIApplication sharedApplication].applicationState != UIApplicationStateBackground) { + [self resumeSessionTimer]; } - self.sessionTimer = [NSTimer scheduledTimerWithTimeInterval:self.secondsUntilTimeout target:self selector:@selector(sessionTimeout:) userInfo:nil repeats:NO]; } } }); } +/// Resume the session timers if they are stopped. +- (void)resumeSessionTimer { + dispatch_async(dispatch_get_main_queue(), ^(void) { + if (!fequal(0, self.secondsUntilWarning) && ![self.sessionWarningTimer isValid]) { + self.sessionWarningTimer = [NSTimer scheduledTimerWithTimeInterval:[self remainingTimeUntilWarning] target:self selector:@selector(sessionTimeoutWarning) userInfo:nil repeats:NO]; + MVMCoreLog(@"Session warning will fire at: %@", self.sessionWarningTimer.fireDate); + } + if (!fequal(0, self.secondsUntilTimeout) && ![self.sessionTimer isValid]) { + self.sessionTimer = [NSTimer scheduledTimerWithTimeInterval:[self remainingTimeUntiTimeout] target:self selector:@selector(sessionTimeout:) userInfo:nil repeats:NO]; + MVMCoreLog(@"Session timeout will fire at: %@", self.sessionTimer.fireDate); + } + }); +} + - (void)stopSessionTimer { // nil timer, session no longer timed. @@ -137,31 +172,7 @@ } - (void)appEnteredForeground { - - if (self.sessionBeingTimed || self.sessionTimedOut) { - - NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate]; - if ((!fequal(0, self.secondsUntilWarning) && now > self.timeTimerStarted + self.secondsUntilWarning) || (!fequal(0, self.secondsUntilTimeout) && now > self.timeTimerStarted + self.secondsUntilTimeout)) { - - // Timeout if we are passed the warning. - [self sessionTimeout:YES]; - } else { - if (!fequal(0, self.secondsUntilWarning)) { - - // Restart the warning timer! - NSTimeInterval timeLeftTillWarning = self.timeTimerStarted + self.secondsUntilWarning - now; - [self.sessionWarningTimer invalidate]; - self.sessionWarningTimer = [NSTimer scheduledTimerWithTimeInterval:timeLeftTillWarning target:self selector:@selector(sessionTimeoutWarning) userInfo:nil repeats:NO]; - } - if (!fequal(0, self.secondsUntilTimeout)) { - - // Restart the timeout timer! - NSTimeInterval timeLeftTillTimeout = self.timeTimerStarted + self.secondsUntilTimeout - now; - [self.sessionTimer invalidate]; - self.sessionTimer = [NSTimer scheduledTimerWithTimeInterval:timeLeftTillTimeout target:self selector:@selector(sessionTimeout:) userInfo:nil repeats:NO]; - } - } - } + [self revalidateSessionTimestamp]; } - (void)invalidateSession:(void (^ __nullable)(MVMCoreErrorObject * _Nullable error))completion { @@ -170,18 +181,14 @@ // Checks to make sure session is still valid and that the timer is running. - (void)revalidateSessionTimestamp { - if (self.sessionBeingTimed && !self.sessionTimedOut) { - - NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate]; - if ((!fequal(0, self.secondsUntilWarning) && now > self.timeTimerStarted + self.secondsUntilWarning) || (!fequal(0, self.secondsUntilTimeout) && now > self.timeTimerStarted + self.secondsUntilTimeout)) { - self.sessionTimedOut = YES; - [self.sessionWarningTimer invalidate]; - self.sessionWarningTimer = nil; - [self.sessionTimer invalidate]; - self.sessionTimer = nil; - } else if (![self.sessionTimer isValid]) { - // Restart the session timer! - [self startSessionTimer]; + if (self.sessionBeingTimed || self.sessionTimedOut) { + NSTimeInterval timeSinceStart = [self timeSinceStart]; + if ((!fequal(0, self.secondsUntilWarning) && timeSinceStart > self.secondsUntilWarning) || (!fequal(0, self.secondsUntilTimeout) && timeSinceStart > self.secondsUntilTimeout)) { + + // Timeout if we are passed the warning. + [self sessionTimeout:YES]; + } else { + [self resumeSessionTimer]; } } }