66 * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
77 */
88
9- import { Injectable , OnDestroy , NgZone , signal , computed , inject , PLATFORM_ID } from '@angular/core' ;
9+ import { Injectable , OnDestroy , signal , computed , inject , PLATFORM_ID } from '@angular/core' ;
1010import { isPlatformBrowser } from '@angular/common' ;
11- import { fromEvent , Subject } from 'rxjs' ;
12- import { debounceTime , takeUntil } from 'rxjs/operators' ;
1311
1412/**
1513 * Service to monitor user activity in an Angular application.
@@ -22,19 +20,15 @@ import { debounceTime, takeUntil } from 'rxjs/operators';
2220 */
2321@Injectable ( )
2422export class UserActivityService implements OnDestroy {
25- private ngZone = inject ( NgZone ) ;
26-
2723 /**
2824 * Signal to store the timestamp of the last user activity.
2925 * The timestamp is represented as the number of milliseconds since epoch.
3026 */
3127 private lastActivity = signal < number > ( Date . now ( ) ) ;
32-
33- /**
34- * Subject to signal the destruction of the service.
35- * Used to clean up RxJS subscriptions.
36- */
37- private destroy$ = new Subject < void > ( ) ;
28+ private isBrowser = isPlatformBrowser ( inject ( PLATFORM_ID ) ) ;
29+ private eventListeners : Array < ( ) => void > = [ ] ;
30+ private debounceTimeoutId : any = null ;
31+ private readonly debounceTime = 300 ;
3832
3933 /**
4034 * Computed signal to expose the last user activity as a read-only signal.
@@ -43,34 +37,34 @@ export class UserActivityService implements OnDestroy {
4337
4438 /**
4539 * Starts monitoring user activity events (`mousemove`, `touchstart`, `keydown`, `click`, `scroll`)
46- * and updates the last activity timestamp using RxJS with debounce.
47- * The events are processed outside Angular zone for performance optimization.
40+ * and updates the last activity timestamp using debouncing for performance optimization.
4841 */
4942 startMonitoring ( ) : void {
50- const isBrowser = isPlatformBrowser ( inject ( PLATFORM_ID ) ) ;
51- if ( ! isBrowser ) {
43+ if ( ! this . isBrowser ) {
5244 return ;
5345 }
5446
55- this . ngZone . runOutsideAngular ( ( ) => {
56- const events = [ 'mousemove' , 'touchstart' , 'keydown' , 'click' , 'scroll' ] ;
47+ const events : Array < keyof WindowEventMap > = [ 'mousemove' , 'touchstart' , 'keydown' , 'click' , 'scroll' ] ;
5748
58- events . forEach ( ( event ) => {
59- fromEvent ( window , event )
60- . pipe ( debounceTime ( 300 ) , takeUntil ( this . destroy$ ) )
61- . subscribe ( ( ) => this . updateLastActivity ( ) ) ;
62- } ) ;
49+ const handler = ( ) => this . debouncedUpdate ( ) ;
50+
51+ events . forEach ( ( event ) => {
52+ window . addEventListener ( event , handler , { passive : true } ) ;
53+ this . eventListeners . push ( ( ) => window . removeEventListener ( event , handler ) ) ;
6354 } ) ;
6455 }
6556
6657 /**
67- * Updates the last activity timestamp to the current time.
68- * This method runs inside Angular's zone to ensure reactivity with Angular signals.
58+ * Updates the last activity timestamp with debounce.
6959 */
70- private updateLastActivity ( ) : void {
71- this . ngZone . run ( ( ) => {
60+ private debouncedUpdate ( ) : void {
61+ if ( this . debounceTimeoutId !== null ) {
62+ clearTimeout ( this . debounceTimeoutId ) ;
63+ }
64+ this . debounceTimeoutId = setTimeout ( ( ) => {
7265 this . lastActivity . set ( Date . now ( ) ) ;
73- } ) ;
66+ this . debounceTimeoutId = null ;
67+ } , this . debounceTime ) ;
7468 }
7569
7670 /**
@@ -82,7 +76,7 @@ export class UserActivityService implements OnDestroy {
8276 }
8377
8478 /**
85- * Determines whether the user interacted with the application, meaning it is activily using the application, based on
79+ * Determines whether the user interacted with the application, meaning it is actively using the application, based on
8680 * the specified duration.
8781 * @param timeout - The inactivity timeout in milliseconds.
8882 * @returns {boolean } `true` if the user is inactive, otherwise `false`.
@@ -92,11 +86,15 @@ export class UserActivityService implements OnDestroy {
9286 }
9387
9488 /**
95- * Cleans up RxJS subscriptions and resources when the service is destroyed .
89+ * Cleans up event listeners and debouncing timer on destroy .
9690 * This method is automatically called by Angular when the service is removed.
9791 */
9892 ngOnDestroy ( ) : void {
99- this . destroy$ . next ( ) ;
100- this . destroy$ . complete ( ) ;
93+ this . eventListeners . forEach ( ( remove ) => remove ( ) ) ;
94+ this . eventListeners = [ ] ;
95+ if ( this . debounceTimeoutId !== null ) {
96+ clearTimeout ( this . debounceTimeoutId ) ;
97+ this . debounceTimeoutId = null ;
98+ }
10199 }
102100}
0 commit comments