nightscout
Version:
Nightscout acts as a web-based CGM (Continuous Glucose Monitor) to allow multiple caregivers to remotely view a patients glucose data in realtime.
408 lines (347 loc) • 13.1 kB
JavaScript
;
var _ = require('lodash');
var constants = require('./constants.json');
function init () {
var settings = {
units: 'mg/dl'
, timeFormat: 12
, dayStart: 7.0
, dayEnd: 21.0
, nightMode: false
, editMode: true
, showRawbg: 'never'
, customTitle: 'Nightscout'
, theme: 'default'
, alarmUrgentHigh: true
, alarmUrgentHighMins: [30, 60, 90, 120]
, alarmHigh: true
, alarmHighMins: [30, 60, 90, 120]
, alarmLow: true
, alarmLowMins: [15, 30, 45, 60]
, alarmUrgentLow: true
, alarmUrgentLowMins: [15, 30, 45]
, alarmUrgentMins: [30, 60, 90, 120]
, alarmWarnMins: [30, 60, 90, 120]
, alarmTimeagoWarn: true
, alarmTimeagoWarnMins: 15
, alarmTimeagoUrgent: true
, alarmTimeagoUrgentMins: 30
, alarmPumpBatteryLow: false
, language: 'en'
, scaleY: 'log'
, showPlugins: 'dbsize'
, showForecast: 'ar2'
, focusHours: 3
, heartbeat: 60
, baseURL: ''
, authDefaultRoles: 'readable'
, thresholds: {
bgHigh: 260
, bgTargetTop: 180
, bgTargetBottom: 80
, bgLow: 55
}
, insecureUseHttp: false
, secureHstsHeader: true
, secureHstsHeaderIncludeSubdomains: false
, secureHstsHeaderPreload: false
, secureCsp: false
, deNormalizeDates: false
, showClockDelta: false
, showClockLastTime: false
, frameUrl1: ''
, frameUrl2: ''
, frameUrl3: ''
, frameUrl4: ''
, frameUrl5: ''
, frameUrl6: ''
, frameUrl7: ''
, frameUrl8: ''
, frameName1: ''
, frameName2: ''
, frameName3: ''
, frameName4: ''
, frameName5: ''
, frameName6: ''
, frameName7: ''
, frameName8: ''
, authFailDelay: 5000
, adminNotifiesEnabled: true
};
var secureSettings = [
'apnsKey'
, 'apnsKeyId'
, 'developerTeamId'
, 'userName'
, 'password'
];
var valueMappers = {
nightMode: mapTruthy
, alarmUrgentHigh: mapTruthy
, alarmUrgentHighMins: mapNumberArray
, alarmHigh: mapTruthy
, alarmHighMins: mapNumberArray
, alarmLow: mapTruthy
, alarmLowMins: mapNumberArray
, alarmUrgentLow: mapTruthy
, alarmUrgentLowMins: mapNumberArray
, alarmUrgentMins: mapNumberArray
, alarmTimeagoWarn: mapTruthy
, alarmTimeagoUrgent: mapTruthy
, alarmWarnMins: mapNumberArray
, timeFormat: mapNumber
, insecureUseHttp: mapTruthy
, secureHstsHeader: mapTruthy
, secureCsp: mapTruthy
, deNormalizeDates: mapTruthy
, showClockDelta: mapTruthy
, showClockLastTime: mapTruthy
, bgHigh: mapNumber
, bgLow: mapNumber
, bgTargetTop: mapNumber
, bgTargetBottom: mapNumber
, authFailDelay: mapNumber
, adminNotifiesEnabled: mapTruthy
};
function filterObj(obj, secureKeys) {
if (obj && typeof obj === 'object') {
var allKeys = Object.keys(obj);
for (var i = 0 ; i < allKeys.length ; i++) {
var k = allKeys[i];
if (secureKeys.includes(k)) {
delete obj[k];
} else {
var value = obj[k];
if ( typeof value === 'object') {
filterObj(value, secureKeys);
}
}
}
}
return obj;
}
function filteredSettings(settingsObject) {
let so = _.cloneDeep(settingsObject);
return filterObj(so, secureSettings);
}
function mapNumberArray (value) {
if (!value || _.isArray(value)) {
return value;
}
if (isNaN(value)) {
var rawValues = value && value.split(' ') || [];
return _.map(rawValues, function(num) {
return isNaN(num) ? null : Number(num);
});
} else {
return [Number(value)];
}
}
function mapNumber (value) {
if (!value) {
return value;
}
if (typeof value === 'string' && isNaN(value)) {
const decommaed = value.replace(',','.');
if (!isNaN(decommaed)) { value = decommaed; }
}
if (isNaN(value)) {
return value;
} else {
return Number(value);
}
}
function mapTruthy (value) {
if (typeof value === 'string' && (value.toLowerCase() === 'on' || value.toLowerCase() === 'true')) { value = true; }
if (typeof value === 'string' && (value.toLowerCase() === 'off' || value.toLowerCase() === 'false')) { value = false; }
return value;
}
//TODO: getting sent in status.json, shouldn't be
settings.DEFAULT_FEATURES = ['bgnow', 'delta', 'direction', 'timeago', 'devicestatus', 'upbat', 'errorcodes', 'profile', 'bolus', 'dbsize', 'runtimestate', 'basal', 'careportal'];
var wasSet = [];
function isSimple (value) {
return _.isArray(value) || (typeof value !== 'function' && typeof value !== 'object');
}
function nameFromKey (key, nameType) {
return nameType === 'env' ? _.snakeCase(key).toUpperCase() : key;
}
function eachSettingAs (nameType) {
function mapKeys (accessor, keys) {
_.forIn(keys, function each (value, key) {
if (isSimple(value)) {
var newValue = accessor(nameFromKey(key, nameType));
if (newValue !== undefined) {
var mapper = valueMappers[key];
wasSet.push(key);
keys[key] = mapper ? mapper(newValue) : newValue;
}
}
});
}
return function allKeys (accessor) {
mapKeys(accessor, settings);
mapKeys(accessor, settings.thresholds);
enableAndDisableFeatures(accessor, nameType);
};
}
function enableAndDisableFeatures (accessor, nameType) {
function getAndPrepare (key) {
var raw = accessor(nameFromKey(key, nameType)) || '';
var cleaned = decodeURIComponent(raw).toLowerCase();
cleaned = cleaned ? cleaned.split(' ') : [];
cleaned = _.filter(cleaned, function(e) { return e !== ""; } );
return cleaned;
}
function enableIf (feature, condition) {
if (condition) {
enable.push(feature);
}
}
function anyEnabled (features) {
return _.findIndex(features, function(feature) {
return enable.indexOf(feature) > -1;
}) > -1;
}
function prepareAlarmTypes () {
var alarmTypes = _.filter(getAndPrepare('alarmTypes'), function onlyKnownTypes (type) {
return type === 'predict' || type === 'simple';
});
if (alarmTypes.length === 0) {
var thresholdWasSet = _.findIndex(wasSet, function(name) {
return name.indexOf('bg') === 0;
}) > -1;
alarmTypes = thresholdWasSet ? ['simple'] : ['predict'];
}
return alarmTypes;
}
var enable = getAndPrepare('enable');
var disable = getAndPrepare('disable');
settings.alarmTypes = prepareAlarmTypes();
//don't require pushover to be enabled to preserve backwards compatibility if there are extendedSettings for it
enableIf('pushover', accessor(nameFromKey('pushoverApiToken', nameType)));
enableIf('treatmentnotify', anyEnabled(['careportal', 'pushover', 'maker']));
_.each(settings.DEFAULT_FEATURES, function eachDefault (feature) {
enableIf(feature, enable.indexOf(feature) < 0);
});
//TODO: maybe get rid of ALARM_TYPES and only use enable?
enableIf('simplealarms', settings.alarmTypes.indexOf('simple') > -1);
enableIf('ar2', settings.alarmTypes.indexOf('predict') > -1);
if (disable.length > 0) {
console.info('disabling', disable);
}
//all enabled feature, without any that have been disabled
settings.enable = _.difference(enable, disable);
var thresholds = settings.thresholds;
thresholds.bgHigh = Number(thresholds.bgHigh);
thresholds.bgTargetTop = Number(thresholds.bgTargetTop);
thresholds.bgTargetBottom = Number(thresholds.bgTargetBottom);
thresholds.bgLow = Number(thresholds.bgLow);
// Do not convert for old installs that have these set in mg/dl
if (settings.units.toLowerCase().includes('mmol') && thresholds.bgHigh < 50) {
thresholds.bgHigh = Math.round(thresholds.bgHigh * constants.MMOL_TO_MGDL);
thresholds.bgTargetTop = Math.round(thresholds.bgTargetTop * constants.MMOL_TO_MGDL);
thresholds.bgTargetBottom = Math.round(thresholds.bgTargetBottom * constants.MMOL_TO_MGDL);
thresholds.bgLow = Math.round(thresholds.bgLow * constants.MMOL_TO_MGDL);
}
verifyThresholds();
adjustShownPlugins();
}
function verifyThresholds () {
var thresholds = settings.thresholds;
if (thresholds.bgTargetBottom >= thresholds.bgTargetTop) {
console.warn('BG_TARGET_BOTTOM(' + thresholds.bgTargetBottom + ') was >= BG_TARGET_TOP(' + thresholds.bgTargetTop + ')');
thresholds.bgTargetBottom = thresholds.bgTargetTop - 1;
console.warn('BG_TARGET_BOTTOM is now ' + thresholds.bgTargetBottom);
}
if (thresholds.bgTargetTop <= thresholds.bgTargetBottom) {
console.warn('BG_TARGET_TOP(' + thresholds.bgTargetTop + ') was <= BG_TARGET_BOTTOM(' + thresholds.bgTargetBottom + ')');
thresholds.bgTargetTop = thresholds.bgTargetBottom + 1;
console.warn('BG_TARGET_TOP is now ' + thresholds.bgTargetTop);
}
if (thresholds.bgLow >= thresholds.bgTargetBottom) {
console.warn('BG_LOW(' + thresholds.bgLow + ') was >= BG_TARGET_BOTTOM(' + thresholds.bgTargetBottom + ')');
thresholds.bgLow = thresholds.bgTargetBottom - 1;
console.warn('BG_LOW is now ' + thresholds.bgLow);
}
if (thresholds.bgHigh <= thresholds.bgTargetTop) {
console.warn('BG_HIGH(' + thresholds.bgHigh + ') was <= BG_TARGET_TOP(' + thresholds.bgTargetTop + ')');
thresholds.bgHigh = thresholds.bgTargetTop + 1;
console.warn('BG_HIGH is now ' + thresholds.bgHigh);
}
}
function adjustShownPlugins () {
var showPluginsUnset = settings.showPlugins && 0 === settings.showPlugins.length;
settings.showPlugins += ' delta direction upbat';
if (settings.showRawbg === 'always' || settings.showRawbg === 'noise') {
settings.showPlugins += ' rawbg';
}
if (showPluginsUnset) {
//assume all enabled features are plugins and they should be shown for now
//it would be better to use the registered plugins, but it's not loaded yet...
_.forEach(settings.enable, function showFeature (feature) {
if (isEnabled(feature)) {
settings.showPlugins += ' ' + feature;
}
});
}
}
function isEnabled (feature) {
var enabled = false;
if (settings.enable && typeof feature === 'object' && feature.length !== undefined) {
enabled = _.find(feature, function eachFeature (f) {
return settings.enable.indexOf(f) > -1;
}) !== undefined;
} else {
enabled = settings.enable && settings.enable.indexOf(feature) > -1;
}
return enabled;
}
function isUrgentHighAlarmEnabled(notify) {
return notify.eventName === 'high' && notify.level === constants.LEVEL_URGENT && settings.alarmUrgentHigh;
}
function isHighAlarmEnabled(notify) {
return notify.eventName === 'high' && settings.alarmHigh;
}
function isUrgentLowAlarmEnabled(notify) {
return notify.eventName === 'low' && notify.level === constants.LEVEL_URGENT && settings.alarmUrgentLow;
}
function isLowAlarmEnabled(notify) {
return notify.eventName === 'low' && settings.alarmLow;
}
function isAlarmEventEnabled (notify) {
return ('high' !== notify.eventName && 'low' !== notify.eventName)
|| isUrgentHighAlarmEnabled(notify)
|| isHighAlarmEnabled(notify)
|| isUrgentLowAlarmEnabled(notify)
|| isLowAlarmEnabled(notify);
}
function snoozeMinsForAlarmEvent (notify) {
var snoozeTime;
if (isUrgentHighAlarmEnabled(notify)) {
snoozeTime = settings.alarmUrgentHighMins;
} else if (isHighAlarmEnabled(notify)) {
snoozeTime = settings.alarmHighMins;
} else if (isUrgentLowAlarmEnabled(notify)) {
snoozeTime = settings.alarmUrgentLowMins;
} else if (isLowAlarmEnabled(notify)) {
snoozeTime = settings.alarmLowMins;
} else if (notify.level === constants.LEVEL_URGENT) {
snoozeTime = settings.alarmUrgentMins;
} else {
snoozeTime = settings.alarmWarnMins;
}
return snoozeTime;
}
function snoozeFirstMinsForAlarmEvent (notify) {
return _.first(snoozeMinsForAlarmEvent(notify));
}
settings.eachSetting = eachSettingAs();
settings.eachSettingAsEnv = eachSettingAs('env');
settings.isEnabled = isEnabled;
settings.isAlarmEventEnabled = isAlarmEventEnabled;
settings.snoozeMinsForAlarmEvent = snoozeMinsForAlarmEvent;
settings.snoozeFirstMinsForAlarmEvent = snoozeFirstMinsForAlarmEvent;
settings.filteredSettings = filteredSettings;
return settings;
}
module.exports = init;