session-expiration-alert
Version:
An Angular module to time session expiration. When user session idle time reaches a threshold, then pop up a modal dialog to let user choose to continue session or log out the system. When user session is expired, timer will stop and user will be logged o
1 lines • 15.6 kB
Source Map (JSON)
{"version":3,"file":"session-expiration-alert.mjs","sources":["../../../projects/session-expiration-alert/src/lib/services/session-interrupt.service.ts","../../../projects/session-expiration-alert/src/lib/services/session-timer.service.ts","../../../projects/session-expiration-alert/src/lib/services/session-timer-http-interceptor.ts","../../../projects/session-expiration-alert/src/lib/services/session-expiration-config.ts","../../../projects/session-expiration-alert/src/lib/session-expiration-alert/session-expiration-alert.ts","../../../projects/session-expiration-alert/src/lib/session-expiration-alert/session-expiration-alert.html","../../../projects/session-expiration-alert/src/public-api.ts","../../../projects/session-expiration-alert/src/session-expiration-alert.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\r\n\r\nexport const SEA_INTERRUPT_SERVICE = new InjectionToken<SessionInterruptService>(\r\n 'SessionInterruptService'\r\n);\r\n\r\nexport interface SessionInterruptService {\r\n /**\r\n * Method to refresh session cookie. Normally, this method issue a quick API call to server.\r\n *\r\n * @memberof SessionInterruptService\r\n */\r\n continueSession(): void;\r\n\r\n /**\r\n * Method to remove session cookie. Normally, this method redirect to website logout endpoint.\r\n *\r\n * @memberof SessionInterruptService\r\n */\r\n stopSession(): void;\r\n\r\n /**\r\n * Method to handle expiration event. Can be empty.\r\n */\r\n onExpire(): void;\r\n}\r\n","import { inject, Injectable } from '@angular/core';\r\nimport { interval, Observable, shareReplay, Subject, Subscription } from 'rxjs';\r\nimport { ConfigToken } from './session-expiration-config';\r\n\r\n@Injectable({ providedIn: 'root' })\r\nexport class SessionTimerService {\r\n private readonly config = inject(ConfigToken);\r\n private readonly _timeoutSeconds = this.config.totalMinutes * 60;\r\n private timerSubscription?: Subscription;\r\n private timer: Observable<number> = interval(1000);\r\n private _remainSeconds = new Subject<number>();\r\n /**\r\n * Observable to get session remaining time (in seconds).\r\n *\r\n * Subscribers need to unsubscribe to it before hosting element is destroyed.\r\n *\r\n * @memberof SessionTimerService\r\n */\r\n remainSeconds$ = this._remainSeconds.asObservable().pipe(shareReplay(1));\r\n\r\n startTimer() {\r\n this.stopTimer();\r\n this.timerSubscription = this.timer.subscribe((n) => {\r\n if (n <= this._timeoutSeconds) {\r\n this._remainSeconds.next(this._timeoutSeconds - n);\r\n }\r\n });\r\n }\r\n\r\n stopTimer() {\r\n this.timerSubscription?.unsubscribe();\r\n }\r\n\r\n resetTimer() {\r\n this.startTimer();\r\n }\r\n}\r\n","import { HttpEvent, HttpHandlerFn, HttpRequest } from '@angular/common/http';\r\nimport { inject } from '@angular/core';\r\nimport { Observable } from 'rxjs';\r\nimport { SessionTimerService } from '../services/session-timer.service';\r\n\r\nexport function sessionTimerHttpInterceptor(\r\n req: HttpRequest<unknown>,\r\n next: HttpHandlerFn\r\n): Observable<HttpEvent<unknown>> {\r\n const timerService = inject(SessionTimerService);\r\n timerService.resetTimer();\r\n return next(req);\r\n}\r\n","import { provideHttpClient, withInterceptors } from '@angular/common/http';\r\nimport { EnvironmentProviders, InjectionToken, Provider, Type } from '@angular/core';\r\nimport { SEA_INTERRUPT_SERVICE, SessionInterruptService } from './session-interrupt.service';\r\nimport { sessionTimerHttpInterceptor } from './session-timer-http-interceptor';\r\n\r\nexport interface SessionExpirationConfig {\r\n totalMinutes: number;\r\n}\r\n\r\nexport const ConfigToken = new InjectionToken<SessionExpirationConfig>('config');\r\n\r\nexport function provideSessionExpirationServices(\r\n interrupter: Type<SessionInterruptService>,\r\n config?: SessionExpirationConfig\r\n): (Provider | EnvironmentProviders)[] {\r\n return [\r\n {\r\n provide: ConfigToken,\r\n useValue: config || { totalMinutes: 20 },\r\n },\r\n { provide: SEA_INTERRUPT_SERVICE, useClass: interrupter },\r\n provideHttpClient(withInterceptors([sessionTimerHttpInterceptor])),\r\n ];\r\n}\r\n","import { AsyncPipe } from '@angular/common';\r\nimport {\r\n ChangeDetectionStrategy,\r\n Component,\r\n ElementRef,\r\n inject,\r\n input,\r\n OnChanges,\r\n OnDestroy,\r\n OnInit,\r\n signal,\r\n SimpleChanges,\r\n} from '@angular/core';\r\nimport { Subscription } from 'rxjs';\r\nimport { SEA_INTERRUPT_SERVICE } from '../services/session-interrupt.service';\r\nimport { SessionTimerService } from '../services/session-timer.service';\r\n\r\n@Component({\r\n selector: 'session-expiration-alert',\r\n templateUrl: './session-expiration-alert.html',\r\n styleUrls: ['./session-expiration-alert.css', './btn.css'],\r\n imports: [AsyncPipe],\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n host: {\r\n '(document:keydown.tab)': 'handleTabKey($event)',\r\n '(document:keydown.shift.tab)': 'handleShiftTabKey($event)',\r\n },\r\n})\r\nexport class SessionExpirationAlert implements OnInit, OnChanges, OnDestroy {\r\n /**\r\n * Should start the timer or not. Usually, you can set it to true if a user is authenticated.\r\n */\r\n startTimer = input(true);\r\n\r\n /**\r\n * Count down seconds.\r\n */\r\n alertAt = input(60);\r\n\r\n showModal = signal(false);\r\n expired = signal(false);\r\n private sessionTimerSubscription?: Subscription;\r\n private el = inject(ElementRef);\r\n private sessionInterrupter = inject(SEA_INTERRUPT_SERVICE);\r\n public sessionTimer = inject(SessionTimerService);\r\n\r\n ngOnInit() {\r\n if (!this.sessionTimerSubscription && this.startTimer()) {\r\n this.trackSessionTime();\r\n }\r\n document.body.appendChild(this.el.nativeElement);\r\n }\r\n ngOnChanges(changes: SimpleChanges<{ startTimer: boolean }>): void {\r\n if (changes.startTimer) {\r\n this.cleanUp();\r\n if (changes.startTimer.currentValue) {\r\n this.trackSessionTime();\r\n }\r\n }\r\n }\r\n\r\n private trackSessionTime() {\r\n this.sessionTimer.startTimer();\r\n this.expired.set(false);\r\n this.sessionTimerSubscription = this.sessionTimer.remainSeconds$.subscribe((t) => {\r\n if (t === this.alertAt()) {\r\n this.open();\r\n }\r\n if (t === 0) {\r\n this.expired.set(true);\r\n this.cleanUp();\r\n this.sessionInterrupter.onExpire();\r\n }\r\n });\r\n }\r\n continue() {\r\n this.sessionInterrupter.continueSession();\r\n this.sessionTimer.resetTimer();\r\n this.close();\r\n }\r\n logout() {\r\n this.sessionTimer.stopTimer();\r\n this.close();\r\n this.sessionInterrupter.stopSession();\r\n }\r\n\r\n open(): void {\r\n this.showModal.set(true);\r\n document.body.classList.add('sea-modal-open');\r\n }\r\n\r\n close(): void {\r\n this.showModal.set(false);\r\n document.body.classList.remove('sea-modal-open');\r\n }\r\n\r\n cleanUp() {\r\n this.sessionTimer.stopTimer();\r\n this.sessionTimerSubscription?.unsubscribe();\r\n }\r\n reload() {\r\n this.close();\r\n location.reload();\r\n }\r\n\r\n ngOnDestroy(): void {\r\n this.el.nativeElement.remove();\r\n this.cleanUp();\r\n }\r\n\r\n handleTabKey(event: Event) {\r\n const modal = document.querySelector('#session-expiration-alert');\r\n if (modal) {\r\n const btn1 = modal.querySelector<HTMLButtonElement>('button.btn-primary');\r\n const btn2 = modal.querySelector<HTMLButtonElement>('button.btn-secondary');\r\n if (document.activeElement === btn1) {\r\n btn2?.focus();\r\n event.preventDefault();\r\n }\r\n }\r\n }\r\n handleShiftTabKey(event: Event) {\r\n const modal = document.querySelector('#session-expiration-alert');\r\n if (modal) {\r\n const btn1 = modal.querySelector<HTMLButtonElement>('button.btn-primary');\r\n const btn2 = modal.querySelector<HTMLButtonElement>('button.btn-secondary');\r\n if (document.activeElement === btn2) {\r\n btn1?.focus();\r\n event.preventDefault();\r\n }\r\n }\r\n }\r\n}\r\n","<div\r\n class=\"sea-modal\"\r\n id=\"session-expiration-alert\"\r\n role=\"dialog\"\r\n tabindex=\"-1\"\r\n aria-modal=\"true\"\r\n aria-label=\"session-expiration-alert\"\r\n [hidden]=\"!showModal()\"\r\n>\r\n @if(expired()) {\r\n <div role=\"document\" class=\"sea-modal-content\" id=\"expired\">\r\n <div class=\"sea-modal-header\">Session expired</div>\r\n <div class=\"sea-modal-body\">\r\n <p>\r\n The current page is idle due to lack of activity. You can try to reload\r\n this page or log out of the website.\r\n </p>\r\n </div>\r\n <div class=\"sea-modal-footer\">\r\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"logout()\">\r\n Logout\r\n </button>\r\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"reload()\">\r\n Reload Page\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <div role=\"document\" class=\"sea-modal-content\" id=\"expiring\">\r\n <div class=\"sea-modal-header\">Session is about to expire</div>\r\n <div class=\"sea-modal-body\">\r\n <p>\r\n Your session will expire in\r\n {{ sessionTimer.remainSeconds$ | async }} seconds.\r\n </p>\r\n </div>\r\n <div class=\"sea-modal-footer\">\r\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"logout()\">\r\n Logout\r\n </button>\r\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"continue()\">\r\n Continue Session\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n</div>\r\n\r\n<div class=\"sea-modal-background\" [hidden]=\"!showModal()\"></div>\r\n","/*\r\n * Public API Surface of session-expiration-alert\r\n */\r\n\r\nexport * from './lib/services/session-expiration-config';\r\nexport * from './lib/services/session-interrupt.service';\r\nexport * from './lib/services/session-timer-http-interceptor';\r\nexport * from './lib/services/session-timer.service';\r\nexport * from './lib/session-expiration-alert/session-expiration-alert';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}