├── .github └── stale.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── activity-monitor.js ├── activity-monitor.min.js ├── bower.json ├── karma.conf.js ├── package.json └── test └── activity-monitor.spec.js /.github/stale.yml: -------------------------------------------------------------------------------- 1 | daysUntilStale: 60 2 | daysUntilClose: 14 3 | exemptLabels: 4 | - Critical 5 | - Serve 6 | staleLabel: Stale 7 | markComment: > 8 | This issue has been automatically marked as stale because it has not had 9 | recent activity. It will be closed if no further activity occurs. Thank you 10 | for your contributions. 11 | closeComment: > 12 | This issue has been automatically closed as stale because it has not had 13 | recent activity. 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented here. 3 | 4 | ## 1.1.0 - 2016-08-23 5 | ### Features 6 | - Added CHANGELOG.md 7 | - Added `disableOnInactive` option (default: `true`). (#10) 8 | - This allows the service to resume _after_ a user has been considered inactive. 9 | - Exposed `ActivityMonitor.enable()` and `ActivityMonitor.disable()` for manual control of service. (#10) 10 | 11 | ### Bug Fix 12 | - `DOMevents` is no longer "pre-compiled", allowing consumers to pass their own events (via options) 13 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Sean Wragg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Angular Activity Monitor 2 | This is a simple service that will emit a couple of events based on the users' DOM activity. It also allows you to "keep items alive" in the background so long as the user is considered "active". 3 | 4 | #### Installation: 5 | ```bash 6 | $ [npm|bower] install --save angular-activity-monitor 7 | ``` 8 | 9 | #### Usage: 10 | ```js 11 | // with bower (or without packaging) 12 | angular.module('myModule', ['ActivityMonitor']); 13 | 14 | // with npm (via webpack or Browserify) 15 | angular.module('myModule', [require('angular-activity-monitor')]); 16 | 17 | MyController.$inject = ['ActivityMonitor']; 18 | function MyController(ActivityMonitor) { 19 | ActivityMonitor.on('inactive', function() { 20 | alert("y0, you're inactive!"); 21 | }); 22 | } 23 | ``` 24 | 25 | ##### `ActivityMonitor.options` (configuration): 26 | * `enabled`: whether to regularly check for inactivity (default: `false`) [bool] 27 | * `keepAlive`: background execution frequency (default: `800`) [seconds] 28 | * `inactive`: how long until user is considered inactive (default: `900`) [seconds] 29 | * `warning`: when user is nearing inactive state (deducted from inactive) (default: `60`) [seconds] 30 | * `disableOnInactive`: Once user is inactive, all event listeners are detached and activity monitoring is discontinued (default: `true`) [bool] 31 | * `DOMevents`: array of events on the DOM that count as user activity (default: `['mousemove', 'mousedown', 'mouseup', 'keypress', 'wheel', 'touchstart', 'scroll']`) 32 | 33 | ##### `ActivityMonitor.user` (information about the user): 34 | * `action`: timestamp of the users' last action (default: `Date.now()`) [milliseconds] 35 | * `active`: is the user considered active? (default: `true`) [bool] 36 | * `warning`: is the user nearing inactivity? (default: `false`) [bool] 37 | 38 | ##### `ActivityMonitor` Methods: 39 | * `on(event, callback)` (alias `bind`): subsribe to a particular event 40 | * `off(event[, callback])` (alias `unbind`): unsubscribe to a particular event. If no `callback` or `namespace` provided, all subscribers for the given `event` will be cleared. 41 | * `activity()`: manually invoke user activity (this updates the `User` object above) 42 | 43 | ##### `ActivityMonitor` Events: 44 | * `keepAlive`: anything to execute (at the `Options.keepAlive` interval) so long as the user is active. 45 | * `warning`: when user is approaching inactive state 46 | * `inactive`: when user is officially considered inactive 47 | 48 | #### How long until user is inactive? 49 | This can be configured by setting the `ActivityMonitor.options.inactive` property to the desired timeout (in seconds). 50 | 51 | #### When is the user considered active? 52 | Everytime one of the follow DOM events occur, the `action` and `active` properties on the `User` object is updated accordingly. 53 | ```js 54 | var DOMevents = ['mousemove', 'mousedown', 'keypress', 'wheel', 'touchstart', 'scroll']; 55 | ``` 56 | 57 | #### (Un)subscribing and Event namespacing 58 | If you've ever used [`jQuery.unbind()`](http://api.jquery.com/unbind/), you're in luck. This subscription model works _almost_ exactly like that. Subscribing is pretty straight forward using `.on()` or `.bind()` as described above but, unsubscribing gets a little weird. You essentially have two options: 59 | - Pass the same callback argument to `.unbind()` or `.off()` 60 | - Subscribe and unsubscribe using event namespacing. 61 | 62 | _same callback example_ 63 | ```js 64 | var foo = function() { 65 | alert("y0, you're inactive!"); 66 | }; 67 | ActivityMonitor.on('inactive', foo); /* subscribe */ 68 | ActivityMonitor.off('inactive', foo); /* unsubscribe */ 69 | ``` 70 | 71 | _event namespace example_ 72 | 73 | Instead of maintaining references to callbacks in order to unbind them, we can namespace the events and use this capability to easily unbind our actions. Namespaces are defined by using a period (`.`) character when binding to an event: 74 | ```js 75 | ActivityMonitor.on('inactive.myEvent', function foo() { 76 | alert("y0, you're inactive!"); 77 | }); 78 | ActivityMonitor.off('inactive.myEvent'); 79 | ``` 80 | 81 | If there's something missing or some quirk you've found. _FIX OR UPDATE ME!!!_ 82 | -------------------------------------------------------------------------------- /activity-monitor.js: -------------------------------------------------------------------------------- 1 | /*jshint -W116, -W030, latedef: false */ 2 | 'use strict'; 3 | 4 | (function (root, factory) { 5 | if (typeof module !== 'undefined' && module.exports) { 6 | // CommonJS 7 | if (typeof angular === 'undefined') { 8 | factory(require('angular')); 9 | } else { 10 | factory(angular); 11 | } 12 | module.exports = 'ActivityMonitor'; 13 | } else if (typeof define === 'function' && define.amd) { 14 | // AMD 15 | define(['angular'], factory); 16 | } else { 17 | // Global variables 18 | factory(root.angular); 19 | } 20 | }(this, function (angular) { 21 | var m = angular 22 | .module('ActivityMonitor', []) 23 | .service('ActivityMonitor', ActivityMonitor); 24 | 25 | var MILLISECOND = 1000; 26 | var EVENT_KEEPALIVE = 'keepAlive'; 27 | var EVENT_INACTIVE = 'inactive'; 28 | var EVENT_WARNING = 'warning'; 29 | var EVENT_ACTIVITY = 'activity'; 30 | 31 | ActivityMonitor.$inject = ['$document']; 32 | function ActivityMonitor($document) { 33 | var service = this; 34 | 35 | /* configuration */ 36 | service.options = { 37 | enabled: false, /* is the ActivityMonitor enabled? */ 38 | keepAlive: 800, /* keepAlive ping invterval (seconds) */ 39 | inactive: 900, /* how long until user is considered inactive? (seconds) */ 40 | warning: 60, /* when to warn user when nearing inactive state (deducted from inactive in seconds) */ 41 | monitor: 3, /* how frequently to check if the user is inactive (seconds) */ 42 | disableOnInactive: true, /* by default, once user becomes inactive, all listeners are detached */ 43 | DOMevents: ['mousemove', 'mousedown', 'mouseup', 'keypress', 'wheel', 'touchstart', 'scroll'] /* list of DOM events to determine user's activity */ 44 | }; 45 | 46 | /* user activity */ 47 | service.user = { 48 | action: Date.now(), /* timestamp of the users' last action */ 49 | active: true, /* is the user considered active? */ 50 | warning: false /* is the user in warning state? */ 51 | }; 52 | 53 | service.activity = activity; /* method consumers can use to supply activity */ 54 | service.on = service.bind = subscribe; /* expose method to subscribe to events */ 55 | service.off = service.unbind = unsubscribe; /* expose method to unsubscribe from events */ 56 | 57 | var events = {}; 58 | events[EVENT_KEEPALIVE] = {}; /* functions to invoke along with ping (options.frequency) */ 59 | events[EVENT_INACTIVE] = {}; /* functions to invoke when user goes inactive (options.threshold) */ 60 | events[EVENT_WARNING] = {}; /* functions to invoke when warning user about inactivity (options.warning) */ 61 | events[EVENT_ACTIVITY] = {}; /* functions to invoke any time a user makes a move */ 62 | 63 | var timer = { 64 | inactivity: null, /* setInterval handle to determine whether the user is inactive */ 65 | keepAlive: null /* setInterval handle for ping handler (options.frequency) */ 66 | }; 67 | 68 | enable.timer = timer; 69 | service.enable = enable; 70 | service.disable = disable; 71 | 72 | return service; 73 | 74 | /////////////// 75 | 76 | function disable() { 77 | service.options.enabled = false; 78 | 79 | disableIntervals() 80 | 81 | $document.off(service.options.DOMevents.join(' '), activity); 82 | } 83 | 84 | function disableIntervals(){ 85 | clearInterval(timer.inactivity); 86 | clearInterval(timer.keepAlive); 87 | delete timer.inactivity; 88 | delete timer.keepAlive; 89 | } 90 | 91 | function enable() { 92 | $document.on(service.options.DOMevents.join(' '), activity); 93 | service.options.enabled = true; 94 | service.user.warning = false; 95 | 96 | enableIntervals(); 97 | } 98 | 99 | function enableIntervals(){ 100 | timer.keepAlive = setInterval(function () { 101 | publish(EVENT_KEEPALIVE); 102 | }, service.options.keepAlive * MILLISECOND); 103 | 104 | timer.inactivity = setInterval(function () { 105 | var now = Date.now(); 106 | var warning = now - (service.options.inactive - service.options.warning) * MILLISECOND; 107 | var inactive = now - service.options.inactive * MILLISECOND; 108 | 109 | /* should we display warning */ 110 | if (!service.user.warning && service.user.action <= warning) { 111 | service.user.warning = true; 112 | publish(EVENT_WARNING); 113 | } 114 | 115 | /* should user be considered inactive? */ 116 | if (service.user.active && service.user.action <= inactive) { 117 | service.user.active = false; 118 | publish(EVENT_INACTIVE); 119 | 120 | if(service.options.disableOnInactive){ 121 | disable(); 122 | }else{ 123 | disableIntervals();//user inactive is known, lets stop checking, for now 124 | dynamicActivity = reactivate;//hot swap method that handles document event watching 125 | } 126 | } 127 | }, service.options.monitor * MILLISECOND); 128 | } 129 | 130 | /* function that lives in memory with the intention of being swapped out */ 131 | function dynamicActivity(){ 132 | regularActivityMonitor(); 133 | } 134 | 135 | /* after user inactive, this method is hot swapped as the dynamicActivity method in-which the next user activity reactivates monitors */ 136 | function reactivate() { 137 | enableIntervals(); 138 | dynamicActivity = regularActivityMonitor; 139 | } 140 | 141 | /* invoked on every user action */ 142 | function activity(){ 143 | dynamicActivity() 144 | } 145 | 146 | /* during a users active state the following method is called */ 147 | function regularActivityMonitor() { 148 | service.user.active = true; 149 | service.user.action = Date.now(); 150 | 151 | publish(EVENT_ACTIVITY); 152 | 153 | if (service.user.warning) { 154 | service.user.warning = false; 155 | publish(EVENT_KEEPALIVE); 156 | } 157 | } 158 | 159 | function publish(event) { 160 | if (!service.options.enabled) return; 161 | var spaces = Object.keys(events[event]); 162 | if (!event || !spaces.length) return; 163 | spaces.forEach(function (space) { 164 | events[event][space] && events[event][space](); 165 | }); 166 | } 167 | 168 | function subscribe(event, callback) { 169 | if (!event || typeof callback !== 'function') return; 170 | event = _namespace(event, callback); 171 | events[event.name][event.space] = callback; 172 | !service.options.enabled && enable(); 173 | } 174 | 175 | function unsubscribe(event, callback) { 176 | event = _namespace(event, callback); 177 | 178 | if (!event.space) { 179 | events[event.name] = {}; 180 | return; 181 | } 182 | 183 | events[event.name][event.space] = null; 184 | } 185 | 186 | /* method to return event namespace */ 187 | function _namespace(event, callback) { 188 | event = event.split('.'); 189 | 190 | if (!event[1] && typeof callback === 'function') { 191 | /* if no namespace, use callback and strip all linebreaks and spaces */ 192 | event[1] = callback.toString().substr(0, 150).replace(/\r?\n|\r|\s+/gm, ''); 193 | } 194 | 195 | return { 196 | name: event[0], 197 | space: event[1] 198 | }; 199 | } 200 | } 201 | 202 | return m; 203 | })); -------------------------------------------------------------------------------- /activity-monitor.min.js: -------------------------------------------------------------------------------- 1 | "use strict";!function(n,e){"undefined"!=typeof module&&module.exports?(e("undefined"==typeof angular?require("angular"):angular),module.exports="ActivityMonitor"):"function"==typeof define&&define.amd?define(["angular"],e):e(n.angular)}(this,function(n){function e(n){function e(){w.options.enabled=!1,i(),n.off(w.options.DOMevents.join(" "),f)}function i(){clearInterval(A.inactivity),clearInterval(A.keepAlive),delete A.inactivity,delete A.keepAlive}function c(){n.on(w.options.DOMevents.join(" "),f),w.options.enabled=!0,w.user.warning=!1,s()}function s(){A.keepAlive=setInterval(function(){d(o)},w.options.keepAlive*t),A.inactivity=setInterval(function(){var n=Date.now(),o=n-(w.options.inactive-w.options.warning)*t,u=n-w.options.inactive*t;!w.user.warning&&w.user.action<=o&&(w.user.warning=!0,d(r)),w.user.active&&w.user.action<=u&&(w.user.active=!1,d(a),w.options.disableOnInactive?e():(i(),l=v))},w.options.monitor*t)}function l(){p()}function v(){s(),l=p}function f(){l()}function p(){w.user.active=!0,w.user.action=Date.now(),d(u),w.user.warning&&(w.user.warning=!1,d(o))}function d(n){if(w.options.enabled){var e=Object.keys(b[n]);n&&e.length&&e.forEach(function(e){b[n][e]&&b[n][e]()})}}function m(n,e){n&&"function"==typeof e&&(n=y(n,e),b[n.name][n.space]=e,!w.options.enabled&&c())}function g(n,e){return n=y(n,e),n.space?void(b[n.name][n.space]=null):void(b[n.name]={})}function y(n,e){return n=n.split("."),n[1]||"function"!=typeof e||(n[1]=e.toString().substr(0,150).replace(/\r?\n|\r|\s+/gm,"")),{name:n[0],space:n[1]}}var w=this;w.options={enabled:!1,keepAlive:800,inactive:900,warning:60,monitor:3,disableOnInactive:!0,DOMevents:["mousemove","mousedown","mouseup","keypress","wheel","touchstart","scroll"]},w.user={action:Date.now(),active:!0,warning:!1},w.activity=f,w.on=w.bind=m,w.off=w.unbind=g;var b={};b[o]={},b[a]={},b[r]={},b[u]={};var A={inactivity:null,keepAlive:null};return c.timer=A,w.enable=c,w.disable=e,w}var i=n.module("ActivityMonitor",[]).service("ActivityMonitor",e),t=1e3,o="keepAlive",a="inactive",r="warning",u="activity";return e.$inject=["$document"],i}); -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-activity-monitor", 3 | "version": "1.1.0", 4 | "homepage": "https://github.com/sean3z/angular-activity-monitor", 5 | "authors": [ 6 | "Sean Wragg " 7 | ], 8 | "description": "An Angular service that tracks users' DOM activity", 9 | "main": "activity-monitor.js", 10 | "moduleType": [ 11 | "globals" 12 | ], 13 | "keywords": [ 14 | "angular", 15 | "activity-monitor", 16 | "keepalive", 17 | "timeout", 18 | "idle", 19 | "user" 20 | ], 21 | "license": "MIT", 22 | "ignore": [ 23 | "**/.*", 24 | "node_modules", 25 | "bower_components", 26 | "test", 27 | "tests" 28 | ], 29 | "dependencies": { 30 | "angular": ">=1.4" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(config) { 4 | config.set({ 5 | 6 | // base path that will be used to resolve all patterns (eg. files, exclude) 7 | basePath: '', 8 | 9 | // frameworks to use 10 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 11 | frameworks: ['mocha', 'chai'], 12 | 13 | // list of files / patterns to load in the browser 14 | files: [ 15 | 'node_modules/angular/angular.min.js', 16 | 'node_modules/angular-mocks/angular-mocks.js', 17 | 'activity-monitor.js', 18 | 'test/*.js' 19 | ], 20 | 21 | // list of files to exclude 22 | exclude: [], 23 | 24 | // preprocess matching files before serving them to the browser 25 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 26 | preprocessors: {}, 27 | 28 | // test results reporter to use 29 | // possible values: 'dots', 'progress' 30 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 31 | reporters: ['progress'], 32 | 33 | // web server port 34 | port: 9876, 35 | 36 | // enable / disable colors in the output (reporters and logs) 37 | colors: true, 38 | 39 | // level of logging 40 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 41 | logLevel: config.LOG_INFO, 42 | 43 | // enable / disable watching file and executing tests whenever any file changes 44 | autoWatch: false, 45 | 46 | // start these browsers 47 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 48 | browsers: ['PhantomJS'], 49 | 50 | // Continuous Integration mode 51 | // if true, Karma captures browsers, runs the tests and exits 52 | singleRun: true, 53 | 54 | // Concurrency level 55 | // how many browser should be started simultaneous 56 | concurrency: Infinity 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-activity-monitor", 3 | "version": "1.1.1", 4 | "description": "An Angular service that tracks users' DOM activity", 5 | "main": "activity-monitor.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "karma start", 11 | "build": "uglifyjs activity-monitor.js -o activity-monitor.min.js --screw-ie8 -c -m" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+ssh://git@github.com/sean3z/angular-activity-monitor.git" 16 | }, 17 | "keywords": [ 18 | "angular", 19 | "activity-monitor", 20 | "keepalive", 21 | "timeout", 22 | "idle", 23 | "user" 24 | ], 25 | "author": "Sean Wragg ", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/sean3z/angular-activity-monitor/issues" 29 | }, 30 | "homepage": "http://sean3z.github.io/angular-activity-monitor", 31 | "devDependencies": { 32 | "angular": "~1.6", 33 | "angular-mocks": "~1.4.9", 34 | "chai": "~3.5.0", 35 | "eslint": ">=4.18.2", 36 | "karma": "~4.2", 37 | "karma-chai": "~0.1.0", 38 | "karma-mocha": "~0.2.1", 39 | "karma-phantomjs-launcher": "~1.0.0", 40 | "mocha": "~6.2", 41 | "phantomjs-prebuilt": "~2.1.3", 42 | "uglify-js": "~2.6.1" 43 | }, 44 | "dependencies": {} 45 | } 46 | -------------------------------------------------------------------------------- /test/activity-monitor.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | 'use strict'; 3 | 4 | describe('Angular Activity Monitor', function() { 5 | 6 | beforeEach(module('ActivityMonitor')); 7 | 8 | var ActivityMonitor; 9 | var noop = function() {}; 10 | var options; 11 | 12 | beforeEach(inject(function(_ActivityMonitor_) { 13 | ActivityMonitor = _ActivityMonitor_; 14 | 15 | ActivityMonitor.options.keepAlive = 0.3; /* ping invterval (seconds) */ 16 | ActivityMonitor.options.inactive = 0.4; /* user is considered inactive (seconds) */ 17 | ActivityMonitor.options.warning = 0.1; /* warn user about inactivity (deducted from inactive in seconds) */ 18 | ActivityMonitor.options.monitor = 0.2; /* how frequently to check if the user is inactive (seconds) */ 19 | 20 | options = ActivityMonitor.options; 21 | })); 22 | 23 | describe('when invoked', function() { 24 | it('should have predefined options', function() { 25 | expect(options).to.be.an.object; 26 | }); 27 | 28 | it('should be disabled', function() { 29 | expect(options.enabled).to.equal(false); 30 | }); 31 | 32 | it('should allow users to override options', function() { 33 | expect(options.keepAlive).to.not.equal(800); 34 | }); 35 | 36 | it('should contain an object about the users\' activity', function() { 37 | expect(ActivityMonitor.user).to.be.an.object; 38 | }); 39 | 40 | it('should consider the user active by default', function() { 41 | expect(ActivityMonitor.user.active).to.equal(true); 42 | }); 43 | 44 | it('should allow consumer to subsribe to events', function() { 45 | expect(ActivityMonitor.on).to.be.a.function; 46 | expect(ActivityMonitor.bind).to.be.a.function; 47 | ActivityMonitor.on('keepAlive', noop); 48 | ActivityMonitor.on(); /* no error when missing event or callback */ 49 | }); 50 | 51 | it('should allow consumer to unsubsribe to events', function() { 52 | expect(ActivityMonitor.off).to.be.a.function; 53 | expect(ActivityMonitor.unbind).to.be.a.function; 54 | ActivityMonitor.off('keepAlive', noop); 55 | ActivityMonitor.off('keepAlive'); /* clears entire namespace */ 56 | }); 57 | 58 | it('should allow consumers to invoke activity', function(done) { 59 | expect(ActivityMonitor.activity).to.be.a.function; 60 | 61 | setTimeout(function() { 62 | ActivityMonitor.activity(); 63 | expect(ActivityMonitor.user.action).to.equal(Date.now()); 64 | done(); 65 | }, 2); 66 | }); 67 | }); 68 | 69 | describe('when subscribing to any event', function() { 70 | it('should enable the service', function() { 71 | expect(options.enabled).to.equal(false); 72 | ActivityMonitor.on('keepAlive', noop); 73 | expect(options.enabled).to.equal(true); 74 | }); 75 | 76 | it('should broadcast events', function(done) { 77 | var now = Date.now(); 78 | var threshold = options.keepAlive * 1000; 79 | 80 | ActivityMonitor.on('keepAlive', function() { 81 | expect(Date.now() - now).to.be.at.least(threshold); 82 | done(); 83 | }); 84 | }); 85 | }); 86 | 87 | describe('when broadcasting warning events', function() { 88 | it('should note that the user is inactive', function(done) { 89 | var now = Date.now(); 90 | var threshold = (options.inactive - options.warning) * 1000; 91 | 92 | ActivityMonitor.on('warning', function() { 93 | expect(Date.now() - now).to.be.at.least(threshold); 94 | expect(ActivityMonitor.user.warning).to.equal(true); 95 | 96 | setTimeout(function() { 97 | ActivityMonitor.activity(); 98 | expect(ActivityMonitor.user.warning).to.equal(false); 99 | done(); 100 | }, 1); 101 | }); 102 | }); 103 | }); 104 | 105 | describe('when broadcasting inactive events', function() { 106 | it('should note that the user is inactive', function(done) { 107 | var now = Date.now(); 108 | var threshold = options.inactive * 1000; 109 | 110 | ActivityMonitor.on('inactive', function() { 111 | expect(Date.now() - now).to.be.at.least(threshold); 112 | expect(ActivityMonitor.user.active).to.equal(false); 113 | done(); 114 | }); 115 | }); 116 | 117 | it('should not broadcast inactive events during activity', function(done) { 118 | var now = Date.now(); 119 | var threshold = (options.inactive * 1000) * 2; 120 | 121 | setTimeout(function() { 122 | ActivityMonitor.activity(); 123 | }, (options.inactive - 0.1) * 1000); 124 | 125 | ActivityMonitor.on('inactive', function() { 126 | expect(Date.now() - now).to.be.at.least(threshold); 127 | done(); 128 | }); 129 | }); 130 | }); 131 | 132 | describe('when not disableOnInactive', function() { 133 | beforeEach(function(){ 134 | ActivityMonitor.options.disableOnInactive = false 135 | }) 136 | 137 | it('should be enabled',function(done){ 138 | ActivityMonitor.on('keepAlive', noop); 139 | expect(options.enabled).to.equal(true); 140 | 141 | setTimeout(function() { 142 | expect(options.enabled).to.equal(true); 143 | done(); 144 | }, 410); 145 | }) 146 | 147 | it('should reactivate',function(done){ 148 | ActivityMonitor.on('keepAlive', noop); 149 | expect(options.enabled).to.equal(true); 150 | 151 | setTimeout(function() { 152 | expect(options.enabled).to.equal(true); 153 | 154 | expect(typeof ActivityMonitor.enable.timer.inactivity).to.equal('undefined'); 155 | expect(typeof ActivityMonitor.enable.timer.keepAlive).to.equal('undefined'); 156 | 157 | ActivityMonitor.activity(); 158 | 159 | expect(typeof ActivityMonitor.enable.timer.inactivity).to.equal('number'); 160 | expect(typeof ActivityMonitor.enable.timer.keepAlive).to.equal('number'); 161 | 162 | done(); 163 | }, 410); 164 | }) 165 | }); 166 | 167 | }); 168 | --------------------------------------------------------------------------------