我对我发现的一些方法(包括一些 npm 包)并不感到兴奋,所以我为 angular 设计了一些非常简单的方法。您只需在 app.component 中导入服务并调用它的 init() 方法。唯一棘手的部分是处理跨选项卡操作对话框的关闭。请务必注意,用于存储的 windoweventlisteners 仅在当前文档(也称为另一个窗口或选项卡)之外的存储发生更改时才会做出反应。
import { Injectable, NgZone, OnDestroy } from '@angular/core';
import { MatDialog, MatDialogConfig, MatDialogRef } from '@angular/material/dialog';
import { Router } from '@angular/router';
import { UserService } from '@common/services/user.service';
import { takeWhile } from 'rxjs/operators';
import { IdleTimeoutActions, IDLE_LOGOUT_TIME, IDLE_TIMEOUT_ACTION, IDLE_TIMEOUT_TIME } from './IdleTimeoutActions';
import { InactivityTimeoutModalComponent } from './inactivity-timeout-modal/inactivity-timeout-modal.component';
@Injectable({
providedIn: 'root'
})
export class IdleTimeoutService implements OnDestroy {
alive = true;
interval;
timeoutSetExpiredTime;
boundUpdateExpiredTime;
boundOnIdleTimeoutAction;
inactivityDialogRef: MatDialogRef<InactivityTimeoutModalComponent>;
dialogConfig: MatDialogConfig = {
panelClass: ['confirmation-dialog', 'l-w400'],
disableClose: true
};
dialogOpen = false;
currentStatus;
constructor(private dialog: MatDialog,
private userService: UserService,
private zone: NgZone,
private router: Router) {}
init() {
this.boundUpdateExpiredTime = this.updateExpiredTime.bind(this);
this.boundOnIdleTimeoutAction = this.onIdleTimeoutAction.bind(this);
this.userService.isLoggedIn().pipe(takeWhile(x => this.alive)).subscribe(userIsLoggedIn=> {
if(userIsLoggedIn) {
this.currentStatus = window.localStorage.getItem(IDLE_TIMEOUT_ACTION);
if(this.currentStatus === IdleTimeoutActions.LOGOUT) { // if the user is logged in, reset the idletimeoutactions to null
window.localStorage.setItem(IDLE_TIMEOUT_ACTION, null);
}
window.addEventListener('storage', this.boundOnIdleTimeoutAction); // handle dialog action events from other tabs
this.startTrackingIdleTime();
}
});
}
/**
* Starts the interval that checks localstorage to determine if the idle expired time is at it's limit
*/
startTrackingIdleTime() {
this.addListeners();
if(window.localStorage.getItem(IDLE_TIMEOUT_ACTION) !== IdleTimeoutActions.IDLE_TRIGGERED) {
this.updateExpiredTime(0);
}
if(this.interval) {
clearInterval(this.interval);
}
this.interval = setInterval(() => {
const expiredTime = parseInt(localStorage.getItem('_expiredTime'), 10);
if(expiredTime + (IDLE_LOGOUT_TIME * 1000) < Date.now()) {
this.triggerLogout();
} else if (expiredTime < Date.now()) {
if(!this.dialogOpen) {
window.localStorage.setItem(IDLE_TIMEOUT_ACTION, IdleTimeoutActions.IDLE_TRIGGERED);
this.openIdleDialog();
}
}
}, 1000);
}
triggerLogout() {
this.removeListeners();
// triggers other tabs to logout
window.localStorage.setItem(IDLE_TIMEOUT_ACTION, IdleTimeoutActions.LOGOUT);
this.dialog.closeAll();
this.userService.logout();
localStorage.setItem(IDLE_TIMEOUT_ACTION, null);
}
/**
* Update the _exporedTime localStorage variable with a new time (timeout used to throttle)
*/
updateExpiredTime(timeout = 300) {
if(window.localStorage.getItem(IDLE_TIMEOUT_ACTION) !== IdleTimeoutActions.IDLE_TRIGGERED) {
if (this.timeoutSetExpiredTime) {
clearTimeout(this.timeoutSetExpiredTime);
}
this.timeoutSetExpiredTime = setTimeout(() => {
this.zone.run(() => {
localStorage.setItem('_expiredTime', '' + (Date.now() + (IDLE_TIMEOUT_TIME * 1000)));
});
}, timeout);
}
}
addListeners() {
this.zone.runOutsideAngular(() => {
window.addEventListener('mousemove', this.boundUpdateExpiredTime);
window.addEventListener('scroll', this.boundUpdateExpiredTime);
window.addEventListener('keydown', this.boundUpdateExpiredTime);
});
}
removeListeners() {
window.removeEventListener('mousemove', this.boundUpdateExpiredTime);
window.removeEventListener('scroll', this.boundUpdateExpiredTime);
window.removeEventListener('keydown', this.boundUpdateExpiredTime);
window.removeEventListener('storage', this.boundOnIdleTimeoutAction);
clearInterval(this.interval);
}
openIdleDialog() {
this.dialogOpen = true;
this.inactivityDialogRef = this.dialog.open(InactivityTimeoutModalComponent, this.dialogConfig);
this.inactivityDialogRef.afterClosed().subscribe(action => {
if(action === IdleTimeoutActions.CONTINUE) {
this.updateExpiredTime(0);
// trigger other tabs to close the modal
localStorage.setItem(IDLE_TIMEOUT_ACTION, IdleTimeoutActions.CONTINUE);
localStorage.setItem(IDLE_TIMEOUT_ACTION, null);
} else if(action === IdleTimeoutActions.LOGOUT){
this.triggerLogout();
}
this.dialogOpen = false;
});
}
onIdleTimeoutAction = (event) => {
if (event.storageArea === localStorage) {
if(this.dialogOpen) {
const action = localStorage.getItem(IDLE_TIMEOUT_ACTION);
if(action === IdleTimeoutActions.LOGOUT) {
this.removeListeners();
this.dialog.closeAll();
this.router.navigate(['login']);
} else if (action === IdleTimeoutActions.CONTINUE) {
this.updateExpiredTime(0);
this.inactivityDialogRef?.close(IdleTimeoutActions.CONTINUE);
}
}
}
}
ngOnDestroy() {
this.removeListeners();
this.alive = false;
}
}