/* eslint-disable no-underscore-dangle */
/* eslint-disable no-unused-expressions */
/* eslint-disable no-param-reassign */
/* eslint-disable no-func-assign */
/* eslint-disable no-use-before-define */
/**
* Copy of Angular Activity Monitor.
* Wrapped in an AngularJS Factory to allow multiple instances.
*
* @class IdleTimer
* @see https://github.com/sean3z/angular-activity-monitor
* @see https://docs.angularjs.org/api/ng/type/angular.Module#factory
*
*/
angular.module('tgaApp')
.factory('IdleTimer', ['$document', '$interval',
function IdleTimerFactory($document, $interval) {
/**
* @constructs
* @lends IdleTimer
*
* @borrows IdleTimer~activity as IdleTimer#activity
* @borrows IdleTimer~subscribe as IdleTimer#on
* @borrows IdleTimer~subscribe as IdleTimer#bind
* @borrows IdleTimer~unsubscribe as IdleTimer#off
* @borrows IdleTimer~unsubscribe as IdleTimer#unbind
*/
const instance = function IdleTimer() {
const MILLISECOND = 1000;
const EVENT_KEEPALIVE = 'keepAlive';
const EVENT_INACTIVE = 'inactive';
const EVENT_WARNING = 'warning';
const EVENT_ACTIVITY = 'activity';
/** @alias IdleTimer# */
const service = this;
/**
* Configuration object.
*
* @property {boolean} enabled - Is the ActivityMonitor enabled?
* @property {number} keepAlive - KeepAlive ping interval (seconds).
* @property {number} inactive - How long until user is considered inactive (seconds)?
* @property {number} warning - When to warn user nearing inactive state (at `inactive - warning` seconds).
* @property {number} monitor - How frequently to check if the user is inactive (seconds).
* @property {boolean} disableOnInactive - Whether to detach all listeners when user goes inactive.
* @property {string[]} DOMevents - List of DOM events to determine user's activity.
* @default
*/
service.options = {
enabled: false,
keepAlive: 800,
inactive: 900,
warning: 60,
monitor: 3,
disableOnInactive: true,
DOMevents: [
'mousemove',
'mousedown',
'mouseup',
'keypress',
'wheel',
'touchstart',
'scroll',
],
};
/**
* User activity.
*
* @property {number} action - Timestamp of the users' last action.
* @property {boolean} active - Is the user considered active?
* @property {boolean} warning - Is the user in warning state?
* @default
*/
service.user = {
action: Date.now(),
active: true,
warning: false,
};
service.activity = activity; /* method consumers can use to supply activity */
service.on = service.bind = subscribe; /* expose method to subscribe to events */
service.off = service.unbind = unsubscribe; /* expose method to unsubscribe from events */
const events = {};
events[EVENT_KEEPALIVE] = {}; /* functions to invoke along with ping (options.frequency) */
events[EVENT_INACTIVE] = {}; /* functions to invoke when user goes inactive (options.threshold) */
events[EVENT_WARNING] = {}; /* functions to invoke when warning user about inactivity (options.warning) */
events[EVENT_ACTIVITY] = {}; /* functions to invoke any time a user makes a move */
const timer = {
inactivity: null, /* setInterval handle to determine whether the user is inactive */
keepAlive: null, /* setInterval handle for ping handler (options.frequency) */
};
enable.timer = timer;
service.enable = enable;
service.disable = disable;
/// ////////////
function disable() {
service.options.enabled = false;
disableIntervals();
$document.off(service.options.DOMevents.join(' '), activity);
}
function disableIntervals() {
if (timer.inactivity) {
$interval.cancel(timer.inactivity);
delete timer.inactivity;
}
if (timer.keepAlive) {
$interval.cancel(timer.keepAlive);
delete timer.keepAlive;
}
}
function enable() {
$document.on(service.options.DOMevents.join(' '), activity);
service.options.enabled = true;
service.user.warning = false;
enableIntervals();
}
function enableIntervals() {
timer.keepAlive = $interval(() => {
publish(EVENT_KEEPALIVE);
}, service.options.keepAlive * MILLISECOND);
timer.inactivity = $interval(() => {
const now = Date.now();
const warning = now - (service.options.inactive - service.options.warning) * MILLISECOND;
const inactive = now - service.options.inactive * MILLISECOND;
/* should we display warning */
if (!service.user.warning && service.user.action <= warning) {
service.user.warning = true;
publish(EVENT_WARNING);
}
/* should user be considered inactive? */
if (service.user.active && service.user.action <= inactive) {
service.user.active = false;
publish(EVENT_INACTIVE);
if (service.options.disableOnInactive) {
disable();
} else {
disableIntervals();// user inactive is known, lets stop checking, for now
dynamicActivity = reactivate;// hot swap method that handles document event watching
}
}
}, service.options.monitor * MILLISECOND);
}
/* function that lives in memory with the intention of being swapped out */
function dynamicActivity() {
regularActivityMonitor();
}
/* after user inactive, this method is hot swapped as the
dynamicActivity method in-which the next user activity reactivates monitors */
function reactivate() {
enableIntervals();
dynamicActivity = regularActivityMonitor;
}
/* invoked on every user action */
/**
* Manually invoke user activity.
*/
function activity() {
dynamicActivity();
}
/* during a users active state the following method is called */
function regularActivityMonitor() {
service.user.active = true;
service.user.action = Date.now();
publish(EVENT_ACTIVITY);
if (service.user.warning) {
service.user.warning = false;
publish(EVENT_KEEPALIVE);
}
}
function publish(event) {
if (!service.options.enabled) return;
const spaces = Object.keys(events[event]);
if (!event || !spaces.length) return;
spaces.forEach((space) => {
events[event][space] && events[event][space]();
});
}
/**
* Subscribe to a particular event.
*
* @param {string} event - Event to subscribe to (`keepAlive`, `inactive`, `warning`, `activity`).
* @param {function} callback - Callback to run on event.
*/
function subscribe(event, callback) {
if (!event || !angular.isFunction(callback)) return;
event = _namespace(event, callback);
events[event.name][event.space] = callback;
!service.options.enabled && enable();
}
/**
* Unsubscribe to a particular event.
*
* @param {string} event - Event to unsubscribe from (`keepAlive`, `inactive`, `warning`, `activity`).
* @param {function} [callback] - If no callback or namespace provided, all subscribers for the given event
* will be cleared.
*/
function unsubscribe(event, callback) {
event = _namespace(event, callback);
if (!event.space) {
events[event.name] = {};
return;
}
events[event.name][event.space] = null;
}
/* method to return event namespace */
function _namespace(event, callback) {
event = event.split('.');
if (!event[1] && angular.isFunction(callback)) {
/* if no namespace, use callback and strip all linebreaks and spaces */
event[1] = callback.toString().substr(0, 150).replace(/\r?\n|\r|\s+/gm, '');
}
return {
name: event[0],
space: event[1],
};
}
return service;
};
return instance;
},
]);