ng2-idle
Version:
A module for responding to idle users in Angular2 applications.
348 lines (287 loc) • 8.68 kB
text/typescript
/**
* ng2-idle - A module for responding to idle users in Angular2 applications.
# @author Mike Grabski <me@mikegrabski.com> (http://mikegrabski.com/)
* @version v1.0.0-alpha.18
* @link https://github.com/HackedByChinese/ng2-idle.git#readme
* @license MIT
*/
import {EventEmitter, Injectable, OnDestroy, Optional} from '@angular/core';
import {IdleExpiry} from './idleexpiry';
import {Interrupt} from './interrupt';
import {InterruptArgs} from './interruptargs';
import {InterruptSource} from './interruptsource';
import {KeepaliveSvc} from './keepalivesvc';
/*
* Indicates the desired auto resume behavior.
*/
export enum AutoResume {
/*
* Auto resume functionality will be disabled.
*/
disabled,
/*
* Can resume automatically even if they are idle.
*/
idle,
/*
* Can only resume automatically if they are not yet idle.
*/
notIdle
}
/**
* A service for detecting and responding to user idleness.
*/
export class Idle implements OnDestroy {
private idle: number = 20 * 60; // in seconds
private timeoutVal: number = 30; // in seconds
private autoResume: AutoResume = AutoResume.idle;
private interrupts: Array<Interrupt> = new Array;
private running: boolean = false;
private idling: boolean = false;
private idleHandle: any;
private timeoutHandle: any;
private countdown: number;
private keepaliveEnabled: boolean = false;
private keepaliveSvc: KeepaliveSvc;
public onIdleStart: EventEmitter<any> = new EventEmitter;
public onIdleEnd: EventEmitter<any> = new EventEmitter;
public onTimeoutWarning: EventEmitter<number> = new EventEmitter<number>();
public onTimeout: EventEmitter<number> = new EventEmitter<number>();
public onInterrupt: EventEmitter<any> = new EventEmitter;
constructor(private expiry: IdleExpiry, keepaliveSvc?: KeepaliveSvc) {
if (keepaliveSvc) {
this.keepaliveSvc = keepaliveSvc;
this.keepaliveEnabled = true;
}
}
/*
* Returns whether or not keepalive integration is enabled.
* @return True if integration is enabled; otherwise, false.
*/
getKeepaliveEnabled(): boolean { return this.keepaliveEnabled; }
/*
* Sets and returns whether or not keepalive integration is enabled.
* @param True if the integration is enabled; otherwise, false.
* @return The current value.
*/
setKeepaliveEnabled(value: boolean): boolean {
if (!this.keepaliveSvc) {
throw new Error(
'Cannot enable keepalive integration because no KeepaliveSvc has been provided.');
}
return this.keepaliveEnabled = value;
}
/*
* Returns the current timeout value.
* @return The timeout value in seconds.
*/
getTimeout(): number { return this.timeoutVal; }
/*
* Sets the timeout value.
* @param seconds - The timeout value in seconds. 0 or false to disable timeout feature.
* @return The current value. If disabled, the value will be 0.
*/
setTimeout(seconds: number|boolean): number {
if (seconds === false) {
this.timeoutVal = 0;
} else if (typeof seconds === 'number' && seconds >= 0) {
this.timeoutVal = seconds;
} else {
throw new Error('\'seconds\' can only be \'false\' or a positive number.');
}
return this.timeoutVal;
}
/*
* Returns the current idle value.
* @return The idle value in seconds.
*/
getIdle(): number { return this.idle; }
/*
* Sets the idle value.
* @param seconds - The idle value in seconds.
* @return The idle value in seconds.
*/
setIdle(seconds: number): number {
if (seconds <= 0) {
throw new Error('\'seconds\' must be greater zero');
}
return this.idle = seconds;
}
/*
* Returns the current autoresume value.
* @return The current value.
*/
getAutoResume(): AutoResume { return this.autoResume; }
setAutoResume(value: AutoResume): AutoResume { return this.autoResume = value; }
/*
* Sets interrupts from the specified sources.
* @param sources - Interrupt sources.
* @return The resulting interrupts.
*/
setInterrupts(sources: Array<InterruptSource>): Array<Interrupt> {
this.clearInterrupts();
let self = this;
for (let source of sources) {
let sub = new Interrupt(source);
sub.subscribe((args: InterruptArgs) => { self.interrupt(args.force, args.innerArgs); });
this.interrupts.push(sub);
}
return this.interrupts;
}
/*
* Returns the current interrupts.
* @return The current interrupts.
*/
getInterrupts(): Array<Interrupt> { return this.interrupts; }
/*
* Pauses, unsubscribes, and clears the current interrupt subscriptions.
*/
clearInterrupts(): void {
for (let sub of this.interrupts) {
sub.pause();
sub.unsubscribe();
}
this.interrupts.length = 0;
}
/*
* Returns whether or not the service is running i.e. watching for idleness.
* @return True if service is watching; otherwise, false.
*/
isRunning(): boolean { return this.running; }
/*
* Returns whether or not the user is considered idle.
* @return True if the user is in the idle state; otherwise, false.
*/
isIdling(): boolean { return this.idling; }
/*
* Starts watching for inactivity.
*/
watch(skipExpiry?: boolean): void {
this.safeClearInterval('idleHandle');
this.safeClearInterval('timeoutHandle');
let timeout = !this.timeoutVal ? 0 : this.timeoutVal;
if (!skipExpiry) {
let value = new Date(this.expiry.now().getTime() + ((this.idle + timeout) * 1000));
this.expiry.last(value);
}
if (this.idling) {
this.toggleState();
}
if (!this.running) {
this.startKeepalive();
this.toggleInterrupts(true);
}
this.running = true;
this.idleHandle = setInterval(() => { this.toggleState(); }, this.idle * 1000);
}
/*
* Stops watching for inactivity.
*/
stop(): void {
this.stopKeepalive();
this.toggleInterrupts(false);
this.safeClearInterval('idleHandle');
this.safeClearInterval('timeoutHandle');
this.idling = false;
this.running = false;
this.expiry.last(null);
}
/*
* Forces a timeout event and state.
*/
timeout(): void {
this.stopKeepalive();
this.toggleInterrupts(false);
this.safeClearInterval('idleHandle');
this.safeClearInterval('timeoutHandle');
this.idling = true;
this.running = false;
this.countdown = 0;
this.onTimeout.emit(null);
}
/*
* Signals that user activity has occurred.
* @param force - Forces watch to be called, unless they are timed out.
* @param eventArgs - Optional source event arguments.
*/
interrupt(force?: boolean, eventArgs?: any): void {
if (!this.running) {
return;
}
if (this.timeoutVal && this.expiry.isExpired()) {
this.timeout();
return;
}
this.onInterrupt.emit(eventArgs);
if (force === true || this.autoResume === AutoResume.idle ||
(this.autoResume === AutoResume.notIdle && !this.idling)) {
this.watch(force);
}
}
private toggleState(): void {
this.idling = !this.idling;
if (this.idling) {
this.onIdleStart.emit(null);
this.stopKeepalive();
if (this.timeoutVal > 0) {
this.countdown = this.timeoutVal;
this.doCountdown();
this.timeoutHandle = setInterval(() => { this.doCountdown(); }, 1000);
}
} else {
this.toggleInterrupts(true);
this.onIdleEnd.emit(null);
this.startKeepalive();
}
this.safeClearInterval('idleHandle');
}
private toggleInterrupts(resume: boolean): void {
for (let interrupt of this.interrupts) {
if (resume) {
interrupt.resume();
} else {
interrupt.pause();
}
}
}
private doCountdown(): void {
if (!this.idling) {
return;
}
if (this.countdown <= 0) {
this.timeout();
return;
}
this.onTimeoutWarning.emit(this.countdown);
this.countdown--;
}
private safeClearInterval(handleName: string): void {
if (this[handleName]) {
clearInterval(this[handleName]);
this[handleName] = null;
}
}
private startKeepalive(): void {
if (!this.keepaliveSvc || !this.keepaliveEnabled) {
return;
}
if (this.running) {
this.keepaliveSvc.ping();
}
this.keepaliveSvc.start();
}
private stopKeepalive(): void {
if (!this.keepaliveSvc || !this.keepaliveEnabled) {
return;
}
this.keepaliveSvc.stop();
}
/*
* Called by Angular when destroying the instance.
*/
ngOnDestroy(): void {
this.stop();
this.clearInterrupts();
}
}