forked from nightscout/cgm-remote-monitor
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathnotifications.js
276 lines (224 loc) · 7.93 KB
/
notifications.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
'use strict';
var _ = require('lodash');
var THIRTY_MINUTES = 30 * 60 * 1000;
var DEFAULT_GROUPS = ['default'];
var Alarm = function(level, group, label) {
this.level = level;
this.group = group;
this.label = label;
this.silenceTime = THIRTY_MINUTES;
this.lastAckTime = 0;
};
// list of alarms with their thresholds
var alarms = {};
function init (env, ctx) {
function notifications () {
return notifications;
}
function getAlarm (level, group) {
var key = level + '-' + group;
var alarm = alarms[key];
if (!alarm) {
var display = group === 'default' ? ctx.levels.toDisplay(level) : group + ':' + level;
alarm = new Alarm(level, group, display);
alarms[key] = alarm;
}
return alarm;
}
//should only be used when auto acking the alarms after going back in range or when an error corrects
//setting the silence time to 1ms so the alarm will be re-triggered as soon as the condition changes
//since this wasn't ack'd by a user action
function autoAckAlarms (group) {
var sendClear = false;
for (var level = 1; level <= 2; level++) {
var alarm = getAlarm(level, group);
if (alarm.lastEmitTime) {
console.info('auto acking ' + alarm.level, ' - ', group);
notifications.ack(alarm.level, group, 1);
sendClear = true;
}
}
if (sendClear) {
var notify = { clear: true, title: 'All Clear', message: 'Auto ack\'d alarm(s)', group: group };
ctx.bus.emit('notification', notify);
logEmitEvent(notify);
}
}
function emitNotification (notify) {
var alarm = getAlarm(notify.level, notify.group);
if (ctx.ddata.lastUpdated > alarm.lastAckTime + alarm.silenceTime) {
ctx.bus.emit('notification', notify);
alarm.lastEmitTime = ctx.ddata.lastUpdated;
logEmitEvent(notify);
} else {
console.log(alarm.label + ' alarm is silenced for ' + Math.floor((alarm.silenceTime - (ctx.ddata.lastUpdated - alarm.lastAckTime)) / 60000) + ' minutes more');
}
}
var requests = {};
notifications.initRequests = function initRequests () {
requests = { notifies: [], snoozes: [] };
};
notifications.initRequests();
/**
* Find the first URGENT or first WARN
* @returns a notification or undefined
*/
notifications.findHighestAlarm = function findHighestAlarm (group) {
group = group || 'default';
var filtered = _.filter(requests.notifies, { group: group });
return _.find(filtered, { level: ctx.levels.URGENT }) || _.find(filtered, { level: ctx.levels.WARN });
};
notifications.findUnSnoozeable = function findUnSnoozeable () {
return _.filter(requests.notifies, function(notify) {
return notify.level <= ctx.levels.INFO || notify.isAnnouncement;
});
};
notifications.snoozedBy = function snoozedBy (notify) {
if (notify.isAnnouncement) { return false; }
var filtered = _.filter(requests.snoozes, { group: notify.group });
if (_.isEmpty(filtered)) { return false; }
var byLevel = _.filter(filtered, function checkSnooze (snooze) {
return snooze.level >= notify.level;
});
var sorted = _.sortBy(byLevel, 'lengthMills');
return _.last(sorted);
};
notifications.requestNotify = function requestNotify (notify) {
if (!Object.prototype.hasOwnProperty.call(notify, 'level') || !notify.title || !notify.message || !notify.plugin) {
console.error(new Error('Unable to request notification, since the notify isn\'t complete: ' + JSON.stringify(notify)));
return;
}
notify.group = notify.group || 'default';
requests.notifies.push(notify);
};
notifications.requestSnooze = function requestSnooze (snooze) {
if (!snooze.level || !snooze.title || !snooze.message || !snooze.lengthMills) {
console.error(new Error('Unable to request snooze, since the snooze isn\'t complete: ' + JSON.stringify(snooze)));
return;
}
snooze.group = snooze.group || 'default';
requests.snoozes.push(snooze);
};
notifications.process = function process () {
var notifyGroups = _.map(requests.notifies, function eachNotify (notify) {
return notify.group;
});
var alarmGroups = _.map(_.values(alarms), function eachAlarm (alarm) {
return alarm.group;
});
var groups = _.uniq(notifyGroups.concat(alarmGroups));
if (_.isEmpty(groups)) {
groups = DEFAULT_GROUPS.slice();
}
_.each(groups, function eachGroup (group) {
var highestAlarm = notifications.findHighestAlarm(group);
if (highestAlarm) {
var snoozedBy = notifications.snoozedBy(highestAlarm, group);
if (snoozedBy) {
logSnoozingEvent(highestAlarm, snoozedBy);
notifications.ack(snoozedBy.level, group, snoozedBy.lengthMills, true);
} else {
emitNotification(highestAlarm);
}
} else {
autoAckAlarms(group);
}
});
notifications.findUnSnoozeable().forEach(function eachInfo (notify) {
emitNotification(notify);
});
};
notifications.ack = function ack (level, group, time, sendClear) {
var alarm = getAlarm(level, group);
if (!alarm) {
console.warn('Got an ack for an unknown alarm time, level:', level, ', group:', group);
return;
}
if (Date.now() < alarm.lastAckTime + alarm.silenceTime) {
console.warn('Alarm has already been snoozed, don\'t snooze it again, level:', level, ', group:', group);
return;
}
alarm.lastAckTime = Date.now();
alarm.silenceTime = time ? time : THIRTY_MINUTES;
delete alarm.lastEmitTime;
if (level === 2) {
notifications.ack(1, group, time);
}
/*
* TODO: modify with a local clear, this will clear all connected clients,
* globally
*/
if (sendClear) {
var notify = {
clear: true
, title: 'All Clear'
, message: group + ' - ' + ctx.levels.toDisplay(level) + ' was ack\'d'
, group: group
};
// When web client sends ack, this translates the websocket message into
// an event on our internal bus.
ctx.bus.emit('notification', notify);
logEmitEvent(notify);
}
};
function ifTestModeThen (callback) {
if (env.testMode) {
return callback();
} else {
throw 'Test only function was called = while not in test mode';
}
}
notifications.resetStateForTests = function resetStateForTests () {
ifTestModeThen(function doResetStateForTests () {
console.info('resetting notifications state for tests');
alarms = {};
});
};
notifications.getAlarmForTests = function getAlarmForTests (level, group) {
return ifTestModeThen(function doResetStateForTests () {
group = group || 'default';
var alarm = getAlarm(level, group);
console.info('got alarm for tests: ', alarm);
return alarm;
});
};
function notifyToView (notify) {
return {
level: ctx.levels.toDisplay(notify.level)
, title: notify.title
, message: notify.message
, group: notify.group
, plugin: notify.plugin ? notify.plugin.name : '<none>'
, debug: notify.debug
};
}
function snoozeToView (snooze) {
return {
level: ctx.levels.toDisplay(snooze.level)
, title: snooze.title
, message: snooze.message
, group: snooze.group
};
}
function logEmitEvent (notify) {
var type = notify.level >= ctx.levels.WARN ? 'ALARM' : (notify.clear ? 'ALL CLEAR' : 'NOTIFICATION');
console.info([
logTimestamp() + '\tEMITTING ' + type + ':'
, ' ' + JSON.stringify(notifyToView(notify))
].join('\n'));
}
function logSnoozingEvent (highestAlarm, snoozedBy) {
console.info([
logTimestamp() + '\tSNOOZING ALARM:'
, ' ' + JSON.stringify(notifyToView(highestAlarm))
, ' BECAUSE:'
, ' ' + JSON.stringify(snoozeToView(snoozedBy))
].join('\n'));
}
//TODO: we need a common logger, but until then...
function logTimestamp () {
return (new Date).toISOString();
}
return notifications();
}
module.exports = init;