Skip to content

Commit b63b0e1

Browse files
authored
WEB-384: Fix idle timer (#2733)
1 parent 28c8f7b commit b63b0e1

File tree

3 files changed

+77
-21
lines changed

3 files changed

+77
-21
lines changed

src/app/core/authentication/authentication.service.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Injectable } from '@angular/core';
33
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
44

55
/** rxjs Imports */
6-
import { Observable, of } from 'rxjs';
6+
import { BehaviorSubject, Observable, of } from 'rxjs';
77
import { map } from 'rxjs/operators';
88

99
/** Custom Services */
@@ -30,6 +30,8 @@ export class AuthenticationService {
3030
}
3131
// User logged in boolean
3232
private userLoggedIn: boolean;
33+
private userLoggedIn$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
34+
public readonly isAuthenticated$ = this.userLoggedIn$.asObservable();
3335

3436
/** Denotes whether the user credentials should persist through sessions. */
3537
private rememberMe: boolean;
@@ -84,6 +86,9 @@ export class AuthenticationService {
8486
if (twoFactorAccessToken) {
8587
authenticationInterceptor.setTwoFactorAccessToken(twoFactorAccessToken.token);
8688
}
89+
// Emit the correct initial state
90+
this.userLoggedIn = true;
91+
this.userLoggedIn$.next(true);
8792
}
8893
}
8994

@@ -200,6 +205,7 @@ export class AuthenticationService {
200205
*/
201206
private onLoginSuccess(credentials: Credentials) {
202207
this.userLoggedIn = true;
208+
this.userLoggedIn$.next(true); // ✅ notify observers
203209
// Ensure the rememberMe value is preserved in credentials
204210
credentials.rememberMe = this.rememberMe;
205211

@@ -268,6 +274,7 @@ export class AuthenticationService {
268274
this.setCredentials();
269275
this.resetDialog();
270276
this.userLoggedIn = false;
277+
this.userLoggedIn$.next(false); // ✅ notify observers
271278
return of(true);
272279
}
273280

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Injectable } from '@angular/core';
22
import { environment } from '../../../environments/environment';
3-
import { interval, merge, fromEvent, Observable } from 'rxjs';
4-
import { takeUntil, repeat, map } from 'rxjs/operators';
3+
import { fromEvent, merge, Subject, timer, Observable, Subscription } from 'rxjs';
4+
import { switchMap, takeUntil, tap, map } from 'rxjs/operators';
55

66
/**
77
* Idle timeout service used to track idle user
@@ -11,24 +11,59 @@ import { takeUntil, repeat, map } from 'rxjs/operators';
1111
})
1212
export class IdleTimeoutService {
1313
// max timeout for an idle user
14-
readonly timeoutDelay = environment.session.timeout.idleTimeout || 300000; // 5 minutes
14+
private readonly timeoutDelay = environment.session.timeout.idleTimeout || 300000;
15+
private timeout$ = new Subject<void>();
16+
private resetTimer$ = new Subject<void>();
17+
private active = false;
18+
private timerSubscription?: Subscription;
19+
private userActionsSubscription?: Subscription;
1520

1621
// observable timeout
1722
readonly $onSessionTimeout: Observable<void>;
1823

1924
constructor() {
20-
const events = [
21-
'mousemove',
22-
'keydown',
23-
'wheel',
24-
'mousedown',
25-
'scroll'
26-
];
27-
const $signal = merge(...events.map((eventName) => fromEvent(document, eventName)));
28-
this.$onSessionTimeout = interval(this.timeoutDelay).pipe(
29-
takeUntil($signal),
30-
map((): void => undefined),
31-
repeat()
32-
);
25+
this.$onSessionTimeout = this.timeout$.asObservable();
26+
27+
this.resetTimer$.subscribe(() => {
28+
this.timerSubscription?.unsubscribe();
29+
this.timerSubscription = timer(this.timeoutDelay).subscribe(() => {
30+
this.timeout$.next();
31+
this.stop();
32+
});
33+
});
34+
}
35+
36+
start() {
37+
if (!this.active) {
38+
this.active = true;
39+
this.reset();
40+
41+
// Subscribe to user actions only when active
42+
const events = [
43+
'mousemove',
44+
'keydown',
45+
'wheel',
46+
'mousedown',
47+
'scroll'
48+
];
49+
const userActions$ = merge(...events.map((e) => fromEvent(document, e)));
50+
this.userActionsSubscription = userActions$.subscribe(() => {
51+
this.reset();
52+
});
53+
}
54+
}
55+
56+
stop() {
57+
if (this.active) {
58+
this.active = false;
59+
this.timerSubscription?.unsubscribe();
60+
this.userActionsSubscription?.unsubscribe();
61+
}
62+
}
63+
64+
reset() {
65+
if (this.active) {
66+
this.resetTimer$.next();
67+
}
3368
}
3469
}

src/app/web-app.component.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
/** Angular Imports */
2-
import { Component, OnInit, HostListener, HostBinding } from '@angular/core';
2+
import { Component, OnInit, HostListener, HostBinding, OnDestroy } from '@angular/core';
33
import { Router, NavigationEnd, ActivatedRoute } from '@angular/router';
44
import { Title } from '@angular/platform-browser';
55
import { MatSnackBar } from '@angular/material/snack-bar';
66
import { MatDialog } from '@angular/material/dialog';
77

88
/** rxjs Imports */
9-
import { merge } from 'rxjs';
9+
import { merge, Subscription } from 'rxjs';
1010
import { filter, map, mergeMap } from 'rxjs/operators';
1111

1212
/** Translation Imports */
@@ -87,12 +87,12 @@ registerLocaleData(localeSW);
8787
// eslint-disable-next-line @angular-eslint/prefer-standalone
8888
standalone: false
8989
})
90-
export class WebAppComponent implements OnInit {
90+
export class WebAppComponent implements OnInit, OnDestroy {
9191
buttonConfig: KeyboardShortcutsConfiguration;
9292

9393
i18nService: I18nService;
9494

95-
isLoggedIn = false;
95+
private authSubscription: Subscription;
9696

9797
/**
9898
* @param {Router} router Router for navigation.
@@ -231,6 +231,14 @@ export class WebAppComponent implements OnInit {
231231

232232
// Subscribe to session timeout If IdleTimeout is higher than 0 (zero)
233233
if (environment.session.timeout.idleTimeout > 0) {
234+
this.authSubscription = this.authenticationService.isAuthenticated$.subscribe((loggedIn) => {
235+
if (loggedIn) {
236+
this.idle.start();
237+
} else {
238+
this.idle.stop();
239+
}
240+
});
241+
234242
this.idle.$onSessionTimeout.subscribe(() => {
235243
this.alertService.alert({
236244
type: 'Session timeout',
@@ -248,6 +256,12 @@ export class WebAppComponent implements OnInit {
248256
}
249257
}
250258

259+
ngOnDestroy() {
260+
if (this.authSubscription) {
261+
this.authSubscription.unsubscribe();
262+
}
263+
}
264+
251265
logout() {
252266
this.authenticationService.logout().subscribe(() => this.router.navigate(['/login'], { replaceUrl: true }));
253267
}

0 commit comments

Comments
 (0)