WEB-384: Fix idle timer (#2733)
This commit is contained in:
@@ -3,7 +3,7 @@ import { Injectable } from '@angular/core';
|
|||||||
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
|
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
|
||||||
|
|
||||||
/** rxjs Imports */
|
/** rxjs Imports */
|
||||||
import { Observable, of } from 'rxjs';
|
import { BehaviorSubject, Observable, of } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
/** Custom Services */
|
/** Custom Services */
|
||||||
@@ -30,6 +30,8 @@ export class AuthenticationService {
|
|||||||
}
|
}
|
||||||
// User logged in boolean
|
// User logged in boolean
|
||||||
private userLoggedIn: boolean;
|
private userLoggedIn: boolean;
|
||||||
|
private userLoggedIn$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||||
|
public readonly isAuthenticated$ = this.userLoggedIn$.asObservable();
|
||||||
|
|
||||||
/** Denotes whether the user credentials should persist through sessions. */
|
/** Denotes whether the user credentials should persist through sessions. */
|
||||||
private rememberMe: boolean;
|
private rememberMe: boolean;
|
||||||
@@ -84,6 +86,9 @@ export class AuthenticationService {
|
|||||||
if (twoFactorAccessToken) {
|
if (twoFactorAccessToken) {
|
||||||
authenticationInterceptor.setTwoFactorAccessToken(twoFactorAccessToken.token);
|
authenticationInterceptor.setTwoFactorAccessToken(twoFactorAccessToken.token);
|
||||||
}
|
}
|
||||||
|
// Emit the correct initial state
|
||||||
|
this.userLoggedIn = true;
|
||||||
|
this.userLoggedIn$.next(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,6 +205,7 @@ export class AuthenticationService {
|
|||||||
*/
|
*/
|
||||||
private onLoginSuccess(credentials: Credentials) {
|
private onLoginSuccess(credentials: Credentials) {
|
||||||
this.userLoggedIn = true;
|
this.userLoggedIn = true;
|
||||||
|
this.userLoggedIn$.next(true); // ✅ notify observers
|
||||||
// Ensure the rememberMe value is preserved in credentials
|
// Ensure the rememberMe value is preserved in credentials
|
||||||
credentials.rememberMe = this.rememberMe;
|
credentials.rememberMe = this.rememberMe;
|
||||||
|
|
||||||
@@ -268,6 +274,7 @@ export class AuthenticationService {
|
|||||||
this.setCredentials();
|
this.setCredentials();
|
||||||
this.resetDialog();
|
this.resetDialog();
|
||||||
this.userLoggedIn = false;
|
this.userLoggedIn = false;
|
||||||
|
this.userLoggedIn$.next(false); // ✅ notify observers
|
||||||
return of(true);
|
return of(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import { interval, merge, fromEvent, Observable } from 'rxjs';
|
import { fromEvent, merge, Subject, timer, Observable, Subscription } from 'rxjs';
|
||||||
import { takeUntil, repeat, map } from 'rxjs/operators';
|
import { switchMap, takeUntil, tap, map } from 'rxjs/operators';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Idle timeout service used to track idle user
|
* Idle timeout service used to track idle user
|
||||||
@@ -11,24 +11,59 @@ import { takeUntil, repeat, map } from 'rxjs/operators';
|
|||||||
})
|
})
|
||||||
export class IdleTimeoutService {
|
export class IdleTimeoutService {
|
||||||
// max timeout for an idle user
|
// max timeout for an idle user
|
||||||
readonly timeoutDelay = environment.session.timeout.idleTimeout || 300000; // 5 minutes
|
private readonly timeoutDelay = environment.session.timeout.idleTimeout || 300000;
|
||||||
|
private timeout$ = new Subject<void>();
|
||||||
|
private resetTimer$ = new Subject<void>();
|
||||||
|
private active = false;
|
||||||
|
private timerSubscription?: Subscription;
|
||||||
|
private userActionsSubscription?: Subscription;
|
||||||
|
|
||||||
// observable timeout
|
// observable timeout
|
||||||
readonly $onSessionTimeout: Observable<void>;
|
readonly $onSessionTimeout: Observable<void>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const events = [
|
this.$onSessionTimeout = this.timeout$.asObservable();
|
||||||
'mousemove',
|
|
||||||
'keydown',
|
this.resetTimer$.subscribe(() => {
|
||||||
'wheel',
|
this.timerSubscription?.unsubscribe();
|
||||||
'mousedown',
|
this.timerSubscription = timer(this.timeoutDelay).subscribe(() => {
|
||||||
'scroll'
|
this.timeout$.next();
|
||||||
];
|
this.stop();
|
||||||
const $signal = merge(...events.map((eventName) => fromEvent(document, eventName)));
|
});
|
||||||
this.$onSessionTimeout = interval(this.timeoutDelay).pipe(
|
});
|
||||||
takeUntil($signal),
|
}
|
||||||
map((): void => undefined),
|
|
||||||
repeat()
|
start() {
|
||||||
);
|
if (!this.active) {
|
||||||
|
this.active = true;
|
||||||
|
this.reset();
|
||||||
|
|
||||||
|
// Subscribe to user actions only when active
|
||||||
|
const events = [
|
||||||
|
'mousemove',
|
||||||
|
'keydown',
|
||||||
|
'wheel',
|
||||||
|
'mousedown',
|
||||||
|
'scroll'
|
||||||
|
];
|
||||||
|
const userActions$ = merge(...events.map((e) => fromEvent(document, e)));
|
||||||
|
this.userActionsSubscription = userActions$.subscribe(() => {
|
||||||
|
this.reset();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
if (this.active) {
|
||||||
|
this.active = false;
|
||||||
|
this.timerSubscription?.unsubscribe();
|
||||||
|
this.userActionsSubscription?.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
if (this.active) {
|
||||||
|
this.resetTimer$.next();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
/** Angular Imports */
|
/** Angular Imports */
|
||||||
import { Component, OnInit, HostListener, HostBinding } from '@angular/core';
|
import { Component, OnInit, HostListener, HostBinding, OnDestroy } from '@angular/core';
|
||||||
import { Router, NavigationEnd, ActivatedRoute } from '@angular/router';
|
import { Router, NavigationEnd, ActivatedRoute } from '@angular/router';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
|
||||||
/** rxjs Imports */
|
/** rxjs Imports */
|
||||||
import { merge } from 'rxjs';
|
import { merge, Subscription } from 'rxjs';
|
||||||
import { filter, map, mergeMap } from 'rxjs/operators';
|
import { filter, map, mergeMap } from 'rxjs/operators';
|
||||||
|
|
||||||
/** Translation Imports */
|
/** Translation Imports */
|
||||||
@@ -87,12 +87,12 @@ registerLocaleData(localeSW);
|
|||||||
// eslint-disable-next-line @angular-eslint/prefer-standalone
|
// eslint-disable-next-line @angular-eslint/prefer-standalone
|
||||||
standalone: false
|
standalone: false
|
||||||
})
|
})
|
||||||
export class WebAppComponent implements OnInit {
|
export class WebAppComponent implements OnInit, OnDestroy {
|
||||||
buttonConfig: KeyboardShortcutsConfiguration;
|
buttonConfig: KeyboardShortcutsConfiguration;
|
||||||
|
|
||||||
i18nService: I18nService;
|
i18nService: I18nService;
|
||||||
|
|
||||||
isLoggedIn = false;
|
private authSubscription: Subscription;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Router} router Router for navigation.
|
* @param {Router} router Router for navigation.
|
||||||
@@ -231,6 +231,14 @@ export class WebAppComponent implements OnInit {
|
|||||||
|
|
||||||
// Subscribe to session timeout If IdleTimeout is higher than 0 (zero)
|
// Subscribe to session timeout If IdleTimeout is higher than 0 (zero)
|
||||||
if (environment.session.timeout.idleTimeout > 0) {
|
if (environment.session.timeout.idleTimeout > 0) {
|
||||||
|
this.authSubscription = this.authenticationService.isAuthenticated$.subscribe((loggedIn) => {
|
||||||
|
if (loggedIn) {
|
||||||
|
this.idle.start();
|
||||||
|
} else {
|
||||||
|
this.idle.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.idle.$onSessionTimeout.subscribe(() => {
|
this.idle.$onSessionTimeout.subscribe(() => {
|
||||||
this.alertService.alert({
|
this.alertService.alert({
|
||||||
type: 'Session timeout',
|
type: 'Session timeout',
|
||||||
@@ -248,6 +256,12 @@ export class WebAppComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
if (this.authSubscription) {
|
||||||
|
this.authSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logout() {
|
logout() {
|
||||||
this.authenticationService.logout().subscribe(() => this.router.navigate(['/login'], { replaceUrl: true }));
|
this.authenticationService.logout().subscribe(() => this.router.navigate(['/login'], { replaceUrl: true }));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user