[MM-19266] User activity monitor updates (#1061)
* user activity monitor updates - re-work of the mechanism for determining user activity status and triggering updates that are passed to the server via the webapp - removing system events (login/out screensaveer on/of etc.) to be re-considered for a future release * add missing descriptions * review tweaks * update tests
This commit is contained in:
@@ -15,25 +15,19 @@ export default class UserActivityMonitor extends EventEmitter {
|
|||||||
super();
|
super();
|
||||||
|
|
||||||
this.isActive = true;
|
this.isActive = true;
|
||||||
this.forceInactive = false;
|
|
||||||
this.idleTime = 0;
|
this.idleTime = 0;
|
||||||
this.lastStatusUpdate = 0;
|
this.lastSetActive = null;
|
||||||
this.systemIdleTimeIntervalID = -1;
|
this.systemIdleTimeIntervalID = -1;
|
||||||
|
|
||||||
this.config = {
|
this.config = {
|
||||||
internalUpdateFrequencyMs: 1 * 1000, // eslint-disable-line no-magic-numbers
|
updateFrequencyMs: 1 * 1000, // eslint-disable-line no-magic-numbers
|
||||||
|
inactiveThresholdMs: 60 * 1000, // eslint-disable-line no-magic-numbers
|
||||||
statusUpdateThresholdMs: 60 * 1000, // eslint-disable-line no-magic-numbers
|
statusUpdateThresholdMs: 60 * 1000, // eslint-disable-line no-magic-numbers
|
||||||
activityTimeoutSec: 5 * 60, // eslint-disable-line no-magic-numbers
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// NOTE: binding needed to prevent error; fat arrow class methods don't work in current setup
|
|
||||||
// Error: "Error: async hook stack has become corrupted (actual: #, expected: #)"
|
|
||||||
this.handleSystemGoingAway = this.handleSystemGoingAway.bind(this);
|
|
||||||
this.handleSystemComingBack = this.handleSystemComingBack.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get userIsActive() {
|
get userIsActive() {
|
||||||
return this.forceInactive ? false : this.isActive;
|
return this.isActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
get userIdleTime() {
|
get userIdleTime() {
|
||||||
@@ -44,9 +38,9 @@ export default class UserActivityMonitor extends EventEmitter {
|
|||||||
* Begin monitoring system events and idle time at defined frequency
|
* Begin monitoring system events and idle time at defined frequency
|
||||||
*
|
*
|
||||||
* @param {Object} config - overide internal configuration defaults
|
* @param {Object} config - overide internal configuration defaults
|
||||||
* @param {nunber} config.internalUpdateFrequencyMs
|
* @param {number} config.updateFrequencyMs - internal update clock frequency for monitoring idleTime
|
||||||
* @param {nunber} config.statusUpdateThresholdMs
|
* @param {number} config.inactiveThresholdMs - the number of milliseconds that idleTime needs to reach to internally be considered inactive
|
||||||
* @param {nunber} config.activityTimeoutSec
|
* @param {number} config.statusUpdateThresholdMs - minimum amount of time before sending a new status update
|
||||||
* @emits {error} emitted when method is called before the app is ready
|
* @emits {error} emitted when method is called before the app is ready
|
||||||
* @emits {error} emitted when this method has previously been called but not subsequently stopped
|
* @emits {error} emitted when this method has previously been called but not subsequently stopped
|
||||||
*/
|
*/
|
||||||
@@ -63,12 +57,6 @@ export default class UserActivityMonitor extends EventEmitter {
|
|||||||
|
|
||||||
this.config = Object.assign({}, this.config, config);
|
this.config = Object.assign({}, this.config, config);
|
||||||
|
|
||||||
// NOTE: electron.powerMonitor cannot be referenced until the app is ready
|
|
||||||
electron.powerMonitor.on('suspend', this.handleSystemGoingAway);
|
|
||||||
electron.powerMonitor.on('resume', this.handleSystemComingBack);
|
|
||||||
electron.powerMonitor.on('lock-screen', this.handleSystemGoingAway);
|
|
||||||
electron.powerMonitor.on('unlock-screen', this.handleSystemComingBack);
|
|
||||||
|
|
||||||
this.systemIdleTimeIntervalID = setInterval(() => {
|
this.systemIdleTimeIntervalID = setInterval(() => {
|
||||||
try {
|
try {
|
||||||
electron.powerMonitor.querySystemIdleTime((idleTime) => {
|
electron.powerMonitor.querySystemIdleTime((idleTime) => {
|
||||||
@@ -77,51 +65,53 @@ export default class UserActivityMonitor extends EventEmitter {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Error getting system idle time:', err);
|
console.log('Error getting system idle time:', err);
|
||||||
}
|
}
|
||||||
}, this.config.internalUpdateFrequencyMs);
|
}, this.config.updateFrequencyMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop monitoring system events and idle time
|
* Stop monitoring system events and idle time
|
||||||
*/
|
*/
|
||||||
stopMonitoring() {
|
stopMonitoring() {
|
||||||
electron.powerMonitor.off('suspend', this.handleSystemGoingAway);
|
|
||||||
electron.powerMonitor.off('resume', this.handleSystemComingBack);
|
|
||||||
electron.powerMonitor.off('lock-screen', this.handleSystemGoingAway);
|
|
||||||
electron.powerMonitor.off('unlock-screen', this.handleSystemComingBack);
|
|
||||||
|
|
||||||
clearInterval(this.systemIdleTimeIntervalID);
|
clearInterval(this.systemIdleTimeIntervalID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates internal idle time properties and conditionally triggers updates to user activity status
|
* Updates internal idle time and sets internal user activity state
|
||||||
*
|
*
|
||||||
* @param {integer} idleTime
|
* @param {integer} idleTime
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
updateIdleTime(idleTime) {
|
updateIdleTime(idleTime) {
|
||||||
this.idleTime = idleTime;
|
this.idleTime = idleTime;
|
||||||
|
if (idleTime * 1000 > this.config.inactiveThresholdMs) { // eslint-disable-line no-magic-numbers
|
||||||
if (this.idleTime > this.config.activityTimeoutSec) {
|
this.setActivityState(false);
|
||||||
this.updateUserActivityStatus(false);
|
} else {
|
||||||
} else if (!this.forceInactive && this.idleTime < this.config.activityTimeoutSec) {
|
this.setActivityState(true);
|
||||||
this.updateUserActivityStatus(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates user activity status if changed and triggers a status update
|
* Updates user active state and conditionally triggers a status update
|
||||||
*
|
*
|
||||||
* @param {boolean} isActive
|
* @param {boolean} isActive
|
||||||
* @param {boolean} isSystemEvent – indicates whether the update was triggered by a system event (log in/out, screesaver on/off etc)
|
* @param {boolean} isSystemEvent – indicates whether the update was triggered by a system event (log in/out, screesaver on/off etc)
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
updateUserActivityStatus(isActive = false, isSystemEvent = false) {
|
setActivityState(isActive = false, isSystemEvent = false) {
|
||||||
|
this.isActive = isActive;
|
||||||
|
|
||||||
|
if (isSystemEvent) {
|
||||||
|
this.sendStatusUpdate(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (isActive !== this.isActive) {
|
|
||||||
this.isActive = isActive;
|
if (isActive && (this.lastSetActive == null || now - this.lastSetActive >= this.config.statusUpdateThresholdMs)) {
|
||||||
this.sendStatusUpdate(now, isSystemEvent);
|
this.sendStatusUpdate(false);
|
||||||
} else if (now - this.lastStatusUpdate > this.config.statusUpdateThresholdMs) {
|
this.lastSetActive = now;
|
||||||
this.sendStatusUpdate(now, isSystemEvent);
|
} else if (!isActive) {
|
||||||
|
this.lastSetActive = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,26 +121,11 @@ export default class UserActivityMonitor extends EventEmitter {
|
|||||||
* @emits {status} emitted at regular, definable intervals providing an update on user active status and idle time
|
* @emits {status} emitted at regular, definable intervals providing an update on user active status and idle time
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
sendStatusUpdate(now = Date.now(), isSystemEvent = false) {
|
sendStatusUpdate(isSystemEvent = false) {
|
||||||
this.lastStatusUpdate = now;
|
|
||||||
this.emit('status', {
|
this.emit('status', {
|
||||||
userIsActive: this.isActive,
|
userIsActive: this.isActive,
|
||||||
idleTime: this.idleTime,
|
idleTime: this.idleTime,
|
||||||
isSystemEvent,
|
isSystemEvent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* System event handlers
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
handleSystemGoingAway() {
|
|
||||||
this.forceInactive = true;
|
|
||||||
this.updateUserActivityStatus(false, true);
|
|
||||||
}
|
|
||||||
handleSystemComingBack() {
|
|
||||||
this.forceInactive = false;
|
|
||||||
this.updateUserActivityStatus(true, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -22,35 +22,15 @@ describe('UserActivityMonitor', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should set user status to active', () => {
|
it('should set user status to active', () => {
|
||||||
userActivityMonitor.updateUserActivityStatus(true);
|
userActivityMonitor.setActivityState(true);
|
||||||
assert.equal(userActivityMonitor.userIsActive, true);
|
assert.equal(userActivityMonitor.userIsActive, true);
|
||||||
});
|
});
|
||||||
it('should set user status to inactive', () => {
|
it('should set user status to inactive', () => {
|
||||||
userActivityMonitor.updateUserActivityStatus(false);
|
userActivityMonitor.setActivityState(false);
|
||||||
assert.equal(userActivityMonitor.userIsActive, false);
|
assert.equal(userActivityMonitor.userIsActive, false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('handleSystemGoingAway', () => {
|
|
||||||
it('should set user status to inactive and forceInactive to true', () => {
|
|
||||||
const userActivityMonitor = new UserActivityMonitor();
|
|
||||||
userActivityMonitor.isActive = true;
|
|
||||||
userActivityMonitor.forceInactive = false;
|
|
||||||
userActivityMonitor.handleSystemGoingAway();
|
|
||||||
assert.equal(!userActivityMonitor.userIsActive && userActivityMonitor.forceInactive, true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('handleSystemComingBack', () => {
|
|
||||||
it('should set user status to active and forceInactive to false', () => {
|
|
||||||
const userActivityMonitor = new UserActivityMonitor();
|
|
||||||
userActivityMonitor.isActive = false;
|
|
||||||
userActivityMonitor.forceInactive = true;
|
|
||||||
userActivityMonitor.handleSystemComingBack();
|
|
||||||
assert.equal(userActivityMonitor.userIsActive && !userActivityMonitor.forceInactive, true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('sendStatusUpdate', () => {
|
describe('sendStatusUpdate', () => {
|
||||||
let userActivityMonitor;
|
let userActivityMonitor;
|
||||||
|
|
||||||
@@ -62,28 +42,28 @@ describe('UserActivityMonitor', () => {
|
|||||||
userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => {
|
userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => {
|
||||||
assert.equal(userIsActive && !isSystemEvent, true);
|
assert.equal(userIsActive && !isSystemEvent, true);
|
||||||
});
|
});
|
||||||
userActivityMonitor.updateUserActivityStatus(true, false);
|
userActivityMonitor.setActivityState(true, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should emit a non-system triggered status event indicating a user is inactive', () => {
|
it('should emit a non-system triggered status event indicating a user is inactive', () => {
|
||||||
userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => {
|
userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => {
|
||||||
assert.equal(!userIsActive && !isSystemEvent, true);
|
assert.equal(!userIsActive && !isSystemEvent, true);
|
||||||
});
|
});
|
||||||
userActivityMonitor.updateUserActivityStatus(false, false);
|
userActivityMonitor.setActivityState(false, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should emit a system triggered status event indicating a user is active', () => {
|
it('should emit a system triggered status event indicating a user is active', () => {
|
||||||
userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => {
|
userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => {
|
||||||
assert.equal(userIsActive && isSystemEvent, true);
|
assert.equal(userIsActive && isSystemEvent, true);
|
||||||
});
|
});
|
||||||
userActivityMonitor.updateUserActivityStatus(true, true);
|
userActivityMonitor.setActivityState(true, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should emit a system triggered status event indicating a user is inactive', () => {
|
it('should emit a system triggered status event indicating a user is inactive', () => {
|
||||||
userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => {
|
userActivityMonitor.on('status', ({userIsActive, isSystemEvent}) => {
|
||||||
assert.equal(!userIsActive && isSystemEvent, true);
|
assert.equal(!userIsActive && isSystemEvent, true);
|
||||||
});
|
});
|
||||||
userActivityMonitor.updateUserActivityStatus(false, true);
|
userActivityMonitor.setActivityState(false, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user