From 9f189a014c53806b21e3717eb43d2be6f11870c8 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 13 Oct 2020 17:56:46 +0300 Subject: [PATCH 01/11] Use the delta plugin data to show the delta in the clocks --- lib/client/clock-client.js | 41 ++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/lib/client/clock-client.js b/lib/client/clock-client.js index 891f10d57d5..0d2d615300b 100644 --- a/lib/client/clock-client.js +++ b/lib/client/clock-client.js @@ -3,6 +3,8 @@ const browserSettings = require('./browser-settings'); var client = {}; +var latestEntries = {}; +var latestProperties = {}; client.settings = browserSettings(client, window.serverSettings, $); @@ -20,34 +22,39 @@ client.query = function query () { }); var secret = localStorage.getItem('apisecrethash'); - var src = '/api/v1/entries.json?find[type]=sgv&count=3&t=' + new Date().getTime(); + var src = '/api/v1/entries/sgv.json?&count=1'; + var src2 = '/api/v2/properties?'; if (secret) { - src += '&secret=' + secret; + var s = '&secret=' + secret; + src += s; + src2 += s; } else if (token) { - src += '&token=' + token; + var s = '&token=' + token; + src += s; + src2 += 2; } $.ajax(src, { - success: client.render + success: function gotData(data) { + latestEntries = data; + $.ajax(src2, { + success: function gotData(data) { + latestProperties = data; + client.render(); + } + }); + } }); }; -client.render = function render (xhr) { - console.log('got data', xhr); - - let rec; +client.render = function render () { + let rec = latestEntries[0]; let delta; - // Get SGV, calculate DELTA - xhr.forEach(element => { - if (element.sgv && !rec) { - rec = element; - } - else if (element.sgv && rec && delta==null) { - delta = (rec.sgv - element.sgv)/((rec.date - element.date)/(5*60*1000)); - } - }); + if (latestProperties.delta) { + delta = latestProperties.delta.mgdl; + } let $errorMessage = $('#errorMessage'); let $inner = $('#inner'); From 2cb4113672cb590890e5832f0795ac4d0903f7dc Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Wed, 14 Oct 2020 18:55:02 +0300 Subject: [PATCH 02/11] * Update Node checks * Fix disabling the BG alarms * Load battery and other rare events up to two months back --- lib/client/clock-client.js | 6 +++--- lib/data/dataloader.js | 2 +- lib/plugins/ar2.js | 6 ++++-- lib/plugins/simplealarms.js | 21 +++++++++++++-------- lib/server/bootevent.js | 7 +++---- package.json | 4 ++-- 6 files changed, 26 insertions(+), 20 deletions(-) diff --git a/lib/client/clock-client.js b/lib/client/clock-client.js index 0d2d615300b..356c606ba48 100644 --- a/lib/client/clock-client.js +++ b/lib/client/clock-client.js @@ -30,9 +30,9 @@ client.query = function query () { src += s; src2 += s; } else if (token) { - var s = '&token=' + token; - src += s; - src2 += 2; + var s2 = '&token=' + token; + src += s2; + src2 += 22; } $.ajax(src, { diff --git a/lib/data/dataloader.js b/lib/data/dataloader.js index 7074b18e1fb..985ea259de5 100644 --- a/lib/data/dataloader.js +++ b/lib/data/dataloader.js @@ -392,7 +392,7 @@ function loadSensorAndInsulinTreatments(ddata, ctx, callback) { function loadLatestSingle(ddata, ctx, dataType, callback) { var dateRange = { - $gte: new Date(ddata.lastUpdated - (constants.ONE_DAY * 32)).toISOString() + $gte: new Date(ddata.lastUpdated - (constants.ONE_DAY * 62)).toISOString() }; if (ddata.page && ddata.page.frame) { diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 133c5b8d3c9..ddce1308302 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -67,6 +67,8 @@ function init (ctx) { var prop = sbx.properties.ar2; + console.log('ar2', prop); + if (prop) { sbx.notifications.requestNotify({ level: prop.level @@ -223,9 +225,9 @@ function selectEventType (prop, sbx) { var eventName = ''; if (in20mins !== undefined) { - if (in20mins > sbx.scaleMgdl(sbx.settings.thresholds.bgTargetTop)) { + if (sbx.settings.alarmHigh && in20mins > sbx.scaleMgdl(sbx.settings.thresholds.bgTargetTop)) { eventName = 'high'; - } else if (in20mins < sbx.scaleMgdl(sbx.settings.thresholds.bgTargetBottom)) { + } else if (sbx.settings.alarmLow && in20mins < sbx.scaleMgdl(sbx.settings.thresholds.bgTargetBottom)) { eventName = 'low'; } } diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 9b975dddebe..17529eabee8 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -12,6 +12,7 @@ function init() { }; simplealarms.checkNotifications = function checkNotifications(sbx) { + var lastSGVEntry = sbx.lastSGVEntry() , scaledSGV = sbx.scaleEntry(lastSGVEntry) ; @@ -37,27 +38,31 @@ function init() { simplealarms.compareBGToTresholds = function compareBGToTresholds(scaledSGV, sbx) { var result = { level: levels.INFO }; - if (scaledSGV > sbx.scaleMgdl(sbx.settings.thresholds.bgHigh)) { + if (sbx.settings.alarmUrgentHigh && scaledSGV > sbx.scaleMgdl(sbx.settings.thresholds.bgHigh)) { result.level = levels.URGENT; result.title = levels.toDisplay(levels.URGENT) + ' HIGH'; result.pushoverSound = 'persistent'; result.eventName = 'high'; - } else if (scaledSGV > sbx.scaleMgdl(sbx.settings.thresholds.bgTargetTop)) { - result.level = levels.WARN; - result.title = levels.toDisplay(levels.WARN) + ' HIGH'; - result.pushoverSound = 'climb'; - result.eventName = 'high'; - } else if (scaledSGV < sbx.scaleMgdl(sbx.settings.thresholds.bgLow)) { + } else + if (sbx.settings.alarmHigh && scaledSGV > sbx.scaleMgdl(sbx.settings.thresholds.bgTargetTop)) { + result.level = levels.WARN; + result.title = levels.toDisplay(levels.WARN) + ' HIGH'; + result.pushoverSound = 'climb'; + result.eventName = 'high'; + } + + if (sbx.settings.alarmUrgentLow && scaledSGV < sbx.scaleMgdl(sbx.settings.thresholds.bgLow)) { result.level = levels.URGENT; result.title = levels.toDisplay(levels.URGENT) + ' LOW'; result.pushoverSound = 'persistent'; result.eventName = 'low'; - } else if (scaledSGV < sbx.scaleMgdl(sbx.settings.thresholds.bgTargetBottom)) { + } else if (sbx.settings.alarmLow && scaledSGV < sbx.scaleMgdl(sbx.settings.thresholds.bgTargetBottom)) { result.level = levels.WARN; result.title = levels.toDisplay(levels.WARN) + ' LOW'; result.pushoverSound = 'falling'; result.eventName = 'low'; } + return result; }; diff --git a/lib/server/bootevent.js b/lib/server/bootevent.js index 6247645b3d2..075eaaf01ca 100644 --- a/lib/server/bootevent.js +++ b/lib/server/bootevent.js @@ -25,9 +25,8 @@ function boot (env, language) { var semver = require('semver'); var nodeVersion = process.version; - if ( semver.satisfies(nodeVersion, '^8.15.1') || semver.satisfies(nodeVersion, '^10.16.0')) { - //Latest Node 8 LTS and Latest Node 10 LTS are recommended and supported. - //Require at least Node 8 LTS and Node 10 LTS without known security issues + if ( semver.satisfies(nodeVersion, '^10.22.1') || semver.satisfies(nodeVersion, '^12.19.0')) { + //Latest Node 10 LTS and Latest Node 12 LTS are recommended and supported. console.debug('Node LTS version ' + nodeVersion + ' is supported'); next(); } @@ -37,7 +36,7 @@ function boot (env, language) { console.log('WARNING: Please migrate to another hosting provider. Your Node version is outdated and insecure'); next(); } - else if ( semver.satisfies(nodeVersion, '^12.6.0')) { + else if ( semver.satisfies(nodeVersion, '^14.0.0')) { //Latest Node version console.debug('Node version ' + nodeVersion + ' is not a LTS version. Not recommended. Not supported'); next(); diff --git a/package.json b/package.json index a2694ae0297..c19b2829bb5 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,8 @@ } }, "engines": { - "node": "^10.15.2 || ^8.15.1", - "npm": "^6.4.1" + "node": "^10.22.1 || ^12.19.0", + "npm": "^6.14.6" }, "dependencies": { "@babel/core": "^7.11.1", From 9a851e115e2fe64028ffb005c3d5797dc3c17535 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Wed, 14 Oct 2020 19:04:40 +0300 Subject: [PATCH 03/11] Lower 12 --- lib/server/bootevent.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/server/bootevent.js b/lib/server/bootevent.js index 075eaaf01ca..1bf9d644623 100644 --- a/lib/server/bootevent.js +++ b/lib/server/bootevent.js @@ -25,7 +25,7 @@ function boot (env, language) { var semver = require('semver'); var nodeVersion = process.version; - if ( semver.satisfies(nodeVersion, '^10.22.1') || semver.satisfies(nodeVersion, '^12.19.0')) { + if ( semver.satisfies(nodeVersion, '^10.22.0') || semver.satisfies(nodeVersion, '^12.18.4')) { //Latest Node 10 LTS and Latest Node 12 LTS are recommended and supported. console.debug('Node LTS version ' + nodeVersion + ' is supported'); next(); diff --git a/package.json b/package.json index c19b2829bb5..94abf73b6c0 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ } }, "engines": { - "node": "^10.22.1 || ^12.19.0", + "node": "^10.22.0 || ^12.18.4", "npm": "^6.14.6" }, "dependencies": { From 29e2753a87a9ca7a42166fbb60413bed1267811f Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Fri, 16 Oct 2020 18:59:08 +0300 Subject: [PATCH 04/11] * Possibly fixes compatibility with ios9 * Unify black and color clocks * Really use delta from backend --- lib/client/clock-client.js | 20 +++++-------- lib/server/bootevent.js | 28 +++++++----------- npm-shrinkwrap.json | 58 +++++++++++++++++++++++++++++++++++--- package.json | 12 ++++---- views/index.html | 2 +- 5 files changed, 78 insertions(+), 42 deletions(-) diff --git a/lib/client/clock-client.js b/lib/client/clock-client.js index 356c606ba48..28c923e264c 100644 --- a/lib/client/clock-client.js +++ b/lib/client/clock-client.js @@ -50,10 +50,11 @@ client.query = function query () { client.render = function render () { let rec = latestEntries[0]; - let delta; + let deltaDisplayValue; if (latestProperties.delta) { - delta = latestProperties.delta.mgdl; + console.log(latestProperties.delta); + deltaDisplayValue = latestProperties.delta.display; } let $errorMessage = $('#errorMessage'); @@ -78,13 +79,13 @@ client.render = function render () { // Backward compatible if (face === 'clock-color') { - face = 'c' + (window.serverSettings.settings.showClockLastTime ? 'y' : 'n') + '13-sg40-' + (window.serverSettings.settings.showClockDelta ? 'dt14-' : '') + 'nl-ar30-nl-ag6'; + face = 'c' + (window.serverSettings.settings.showClockLastTime ? 'y' : 'n') + '13-sg40-' + (window.serverSettings.settings.showClockDelta ? 'dt14-' : '') + 'nl-ar25-nl-ag6'; } else if (face === 'clock') { face = 'bn0-sg40'; } else if (face === 'bgclock') { - face = 'bn0-sg30-ar18-nl-nl-tm26'; + face = 'by13-sg30-'+ (window.serverSettings.settings.showClockDelta ? 'dt14-' : '') + 'nl-ar25-nl-ag6'; } else if (face === 'config') { face = $inner.attr('data-face-config'); @@ -111,18 +112,11 @@ client.render = function render () { // Convert BG to mmol/L if necessary. let displayValue; - let deltaDisplayValue; if (window.serverSettings.settings.units === 'mmol') { displayValue = window.Nightscout.units.mgdlToMMOL(rec.sgv); - deltaDisplayValue = window.Nightscout.units.mgdlToMMOL(delta); } else { displayValue = rec.sgv; - deltaDisplayValue = Math.round(delta); - } - - if (deltaDisplayValue > 0) { - deltaDisplayValue = '+' + deltaDisplayValue; } // Insert the delta value text. @@ -187,7 +181,7 @@ client.render = function render () { let last = new Date(rec.date); let now = new Date(); - let elapsedMins = Math.round(((now - last) / 1000) / 60); + let elapsedMins = Math.floor(((now - last) / 1000) / 60); let thresholdReached = (now - last > threshold) && threshold > 0; @@ -228,7 +222,7 @@ client.render = function render () { client.init = function init () { console.log('init'); client.query(); - setInterval(client.query, 1 * 60 * 1000); + setInterval(client.query, 30 * 1000); // update every 30 seconds }; module.exports = client; diff --git a/lib/server/bootevent.js b/lib/server/bootevent.js index 1bf9d644623..1c7d9f58f58 100644 --- a/lib/server/bootevent.js +++ b/lib/server/bootevent.js @@ -25,28 +25,20 @@ function boot (env, language) { var semver = require('semver'); var nodeVersion = process.version; - if ( semver.satisfies(nodeVersion, '^10.22.0') || semver.satisfies(nodeVersion, '^12.18.4')) { - //Latest Node 10 LTS and Latest Node 12 LTS are recommended and supported. - console.debug('Node LTS version ' + nodeVersion + ' is supported'); - next(); - } - else if ( semver.eq(nodeVersion, '10.15.2')) { - //Latest Node version on Azure is tolerated, but not recommended - console.log('WARNING: Node version v10.15.2 and Microsoft Azure are not recommended.'); - console.log('WARNING: Please migrate to another hosting provider. Your Node version is outdated and insecure'); - next(); - } - else if ( semver.satisfies(nodeVersion, '^14.0.0')) { - //Latest Node version - console.debug('Node version ' + nodeVersion + ' is not a LTS version. Not recommended. Not supported'); - next(); - } else { - // Other versions will not start + const isLTS = process.release.lts ? true : false; + + if (!isLTS) { console.log( 'ERROR: Node version ' + nodeVersion + ' is not supported. Please use a secure LTS version or upgrade your Node'); process.exit(1); } - } + if (semver.satisfies(nodeVersion, '^12.0.0') || semver.satisfies(nodeVersion, '^10.0.0')) { + //Latest Node 10 LTS and Node 12 LTS are recommended and supported. + //Require at least Node 8 LTS and Node 10 LTS without known security issues + console.debug('Node LTS version ' + nodeVersion + ' is supported'); + next(); + } + } function checkEnv (ctx, next) { ctx.language = language; diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 4e143615a3d..a137dcd1533 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -3405,12 +3405,62 @@ } }, "env-cmd": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/env-cmd/-/env-cmd-8.0.2.tgz", - "integrity": "sha512-gHX8MnQXw1iS7dc2KeJdBdxca7spIkxkNwIuORLwm8kDg6xHh5wWnv1Yv3pc64nLZR6kufQSCmwTz16sRmd/rg==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/env-cmd/-/env-cmd-10.1.0.tgz", + "integrity": "sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==", "dev": true, "requires": { - "cross-spawn": "^6.0.5" + "commander": "^4.0.0", + "cross-spawn": "^7.0.0" + }, + "dependencies": { + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "errno": { diff --git a/package.json b/package.json index 94abf73b6c0..e26dfa2787c 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,9 @@ }, "scripts": { "start": "node server.js", - "test": "env-cmd ./my.test.env mocha --exit tests/*.test.js", - "test-single": "env-cmd ./my.test.env mocha --exit tests/$TEST.test.js", - "test-ci": "env-cmd ./ci.test.env nyc --reporter=lcov --reporter=text-summary mocha --exit tests/*.test.js", + "test": "env-cmd -f ./my.test.env mocha --exit tests/*.test.js", + "test-single": "env-cmd -f ./my.test.env mocha --exit tests/$TEST.test.js", + "test-ci": "env-cmd -f ./ci.test.env nyc --reporter=lcov --reporter=text-summary mocha --exit tests/*.test.js", "env": "env", "postinstall": "webpack --mode production --config webpack.config.js && npm run-script update-buster", "bundle": "webpack --mode production --config webpack.config.js && npm run-script update-buster", @@ -37,8 +37,8 @@ "bundle-analyzer": "webpack --mode development --config webpack.config.js --profile --json > stats.json && webpack-bundle-analyzer stats.json", "update-buster": "node bin/generateCacheBuster.js >tmp/cacheBusterToken", "coverage": "cat ./coverage/lcov.info | env-cmd ./ci.test.env codacy-coverage", - "dev": "env-cmd ./my.env nodemon server.js 0.0.0.0", - "prod": "env-cmd ./my.prod.env node server.js 0.0.0.0", + "dev": "env-cmd -f ./my.env nodemon server.js 0.0.0.0", + "prod": "env-cmd -f ./my.prod.env node server.js 0.0.0.0", "lint": "eslint lib" }, "main": "server.js", @@ -126,7 +126,7 @@ "benv": "^3.3.0", "codacy-coverage": "^3.4.0", "csv-parse": "^4.12.0", - "env-cmd": "^8.0.2", + "env-cmd": "^10.1.0", "eslint": "^6.8.0", "eslint-loader": "^2.2.1", "mocha": "^8.1.1", diff --git a/views/index.html b/views/index.html index dbff801392f..8e60b30f471 100644 --- a/views/index.html +++ b/views/index.html @@ -730,7 +730,7 @@ reg.addEventListener('updatefound', () => { console.log('Service worker update detected'); reg.update(); - const newWorker = reg.installing; + var newWorker = reg.installing; newWorker.addEventListener('statechange', (state) => { console.log('New worker state change', state); window.location.reload(true); From 468abca53aaa37756d33820ecfcbb24ad52c0e26 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sat, 17 Oct 2020 09:38:04 +0300 Subject: [PATCH 05/11] * Unified black and color clock layouts * Clock BG data now fully uses precalculated data from backend so data is guaranteed to look the same as main view * Update clock data every 20 seconds * Update clock time every second --- lib/authorization/storage.js | 27 +++++--- lib/client/clock-client.js | 131 ++++++++++++++++------------------- 2 files changed, 77 insertions(+), 81 deletions(-) diff --git a/lib/authorization/storage.js b/lib/authorization/storage.js index c032018d170..00521127b36 100644 --- a/lib/authorization/storage.js +++ b/lib/authorization/storage.js @@ -210,16 +210,27 @@ function init (env, ctx) { if (!accessToken) return null; - var split_token = accessToken.split('-'); - var prefix = split_token ? _.last(split_token) : ''; - if (prefix.length < 16) { - return null; - } + function checkToken(accessToken) { + var split_token = accessToken.split('-'); + var prefix = split_token ? _.last(split_token) : ''; - return _.find(storage.subjects, function matches (subject) { - return subject.accessTokenDigest.indexOf(accessToken) === 0 || subject.digest.indexOf(prefix) === 0; - }); + if (prefix.length < 16) { + return null; + } + + return _.find(storage.subjects, function matches (subject) { + return subject.accessTokenDigest.indexOf(accessToken) === 0 || subject.digest.indexOf(prefix) === 0; + }); + } + + if (!Array.isArray(accessToken)) accessToken = [accessToken]; + + for (let i=0; i < accessToken.length; i++) { + if (checkToken(accessToken[i])) return true; + } + + return false; }; storage.doesAccessTokenExist = function doesAccessTokenExist(accessToken) { diff --git a/lib/client/clock-client.js b/lib/client/clock-client.js index 28c923e264c..73ac9c3baca 100644 --- a/lib/client/clock-client.js +++ b/lib/client/clock-client.js @@ -1,59 +1,53 @@ 'use strict'; -const browserSettings = require('./browser-settings'); - +var browserSettings = require('./browser-settings'); var client = {}; -var latestEntries = {}; var latestProperties = {}; client.settings = browserSettings(client, window.serverSettings, $); -//console.log('settings', client.settings); -// client.settings now contains all settings - client.query = function query () { - console.log('query'); var parts = (location.search || '?').substring(1).split('&'); var token = ''; - parts.forEach(function (val) { + parts.forEach(function(val) { if (val.startsWith('token=')) { token = val.substring('token='.length); } }); var secret = localStorage.getItem('apisecrethash'); - var src = '/api/v1/entries/sgv.json?&count=1'; - var src2 = '/api/v2/properties?'; + var src = '/api/v2/properties'; // Use precalculated data from the backend if (secret) { - var s = '&secret=' + secret; + var s = '?secret=' + secret; src += s; - src2 += s; } else if (token) { - var s2 = '&token=' + token; + var s2 = '?token=' + token; src += s2; - src2 += 22; } $.ajax(src, { - success: function gotData(data) { - latestEntries = data; - $.ajax(src2, { - success: function gotData(data) { - latestProperties = data; - client.render(); - } - }); + error: function gotError (err) { + console.error(err); + } + , success: function gotData (data) { + latestProperties = data; + client.render(); } }); }; client.render = function render () { - let rec = latestEntries[0]; + + if (!latestProperties.bgnow && !latestProperties.bgnow.sgvs) { + console.error('BG data not available'); + return; + } + + let rec = latestProperties.bgnow.sgvs[0]; let deltaDisplayValue; if (latestProperties.delta) { - console.log(latestProperties.delta); deltaDisplayValue = latestProperties.delta.display; } @@ -79,15 +73,12 @@ client.render = function render () { // Backward compatible if (face === 'clock-color') { - face = 'c' + (window.serverSettings.settings.showClockLastTime ? 'y' : 'n') + '13-sg40-' + (window.serverSettings.settings.showClockDelta ? 'dt14-' : '') + 'nl-ar25-nl-ag6'; - } - else if (face === 'clock') { + face = 'c' + (window.serverSettings.settings.showClockLastTime ? 'y' : 'n') + '13-sg35-' + (window.serverSettings.settings.showClockDelta ? 'dt14-' : '') + 'nl-ar25-nl-ag6'; + } else if (face === 'clock') { face = 'bn0-sg40'; - } - else if (face === 'bgclock') { - face = 'by13-sg30-'+ (window.serverSettings.settings.showClockDelta ? 'dt14-' : '') + 'nl-ar25-nl-ag6'; - } - else if (face === 'config') { + } else if (face === 'bgclock') { + face = 'b' + (window.serverSettings.settings.showClockLastTime ? 'y' : 'n') + '13-sg35-' + (window.serverSettings.settings.showClockDelta ? 'dt14-' : '') + 'nl-ar25-nl-ag6'; + } else if (face === 'config') { face = $inner.attr('data-face-config'); $inner.empty(); } @@ -103,39 +94,19 @@ client.render = function render () { if (param === '0') { bgColor = (faceParams[param].substr(0, 1) === 'c'); // do we want colorful background? alwaysShowTime = (faceParams[param].substr(1, 1) === 'y'); // always show "stale time" text? - staleMinutes = (faceParams[param].substr(2,2) - 0 >= 0) ? faceParams[param].substr(2,2) : 13; // threshold value (0=never) - } else if (!clockCreated){ - let div = '
0) ? ' style="' + ((faceParams[param].substr(0,2) === 'ar') ? 'height' : 'font-size') + ':' + faceParams[param].substr(2,2) + 'vmin"' : '') + '>
'; + staleMinutes = (faceParams[param].substr(2, 2) - 0 >= 0) ? faceParams[param].substr(2, 2) : 13; // threshold value (0=never) + } else if (!clockCreated) { + let div = '
0) ? ' style="' + ((faceParams[param].substr(0, 2) === 'ar') ? 'height' : 'font-size') + ':' + faceParams[param].substr(2, 2) + 'vmin"' : '') + '>
'; $inner.append(div); } } // Convert BG to mmol/L if necessary. - let displayValue; - - if (window.serverSettings.settings.units === 'mmol') { - displayValue = window.Nightscout.units.mgdlToMMOL(rec.sgv); - } else { - displayValue = rec.sgv; - } + let displayValue = rec.scaled; // Insert the delta value text. $('.dt').html(deltaDisplayValue); - // Generate and insert the clock. - let timeDivisor = parseInt(client.settings.timeFormat ? client.settings.timeFormat : 12, 10); - let today = new Date() - , h = today.getHours() % timeDivisor; - if (timeDivisor === 12) { - h = (h === 0) ? 12 : h; // In the case of 00:xx, change to 12:xx for 12h time - } - if (timeDivisor === 24) { - h = (h < 10) ? ("0" + h) : h; // Pad the hours with a 0 in 24h time - } - let m = today.getMinutes(); - if (m < 10) m = "0" + m; - $('.tm').html(h + ":" + m); - // Color background if (bgColor) { @@ -151,7 +122,7 @@ client.render = function render () { let bgTargetBottom = client.settings.thresholds.bgTargetBottom; let bgTargetTop = client.settings.thresholds.bgTargetTop; - let bgNum = parseFloat(rec.sgv); + let bgNum = parseFloat(rec.mgdl); // Threshold background coloring. if (bgNum < bgLow) { @@ -170,20 +141,16 @@ client.render = function render () { $('body').css('background-color', red); } - } - else { + } else { $('body').css('background-color', 'black'); } // Time before data considered stale. let threshold = 1000 * 60 * staleMinutes; - let last = new Date(rec.date); - let now = new Date(); - - let elapsedMins = Math.floor(((now - last) / 1000) / 60); - - let thresholdReached = (now - last > threshold) && threshold > 0; + var elapsedms = Date.now() - rec.mills; + let elapsedMins = Math.floor((elapsedms / 1000) / 60); + let thresholdReached = (elapsedms > threshold) && threshold > 0; // Insert the BG value text, toggle stale if necessary. $('.sg').toggleClass('stale', thresholdReached).html(displayValue); @@ -192,17 +159,14 @@ client.render = function render () { let staleTimeText; if (elapsedMins === 0) { staleTimeText = 'Just now'; - } - else if (elapsedMins === 1) { + } else if (elapsedMins === 1) { staleTimeText = '1 minute ago'; - } - else { + } else { staleTimeText = elapsedMins + ' minutes ago'; } $('.ag').html(staleTimeText); - } - else { + } else { $('.ag').html(''); } @@ -217,12 +181,33 @@ client.render = function render () { $('body').css('color', bgColor ? 'white' : 'grey'); $('.ar').css('filter', bgColor ? 'brightness(100%)' : 'brightness(50%)').html(arrow); } + + updateClock(); + }; +function updateClock () { + let timeDivisor = parseInt(client.settings.timeFormat ? client.settings.timeFormat : 12, 10); + let today = new Date() + , h = today.getHours() % timeDivisor; + if (timeDivisor === 12) { + h = (h === 0) ? 12 : h; // In the case of 00:xx, change to 12:xx for 12h time + } + if (timeDivisor === 24) { + h = (h < 10) ? ("0" + h) : h; // Pad the hours with a 0 in 24h time + } + let m = today.getMinutes(); + if (m < 10) m = "0" + m; + $('.tm').html(h + ":" + m); +} + client.init = function init () { - console.log('init'); + console.log('Initializing clock'); client.query(); - setInterval(client.query, 30 * 1000); // update every 30 seconds + setInterval(client.query, 20 * 1000); // update every 20 seconds + + // time update + setInterval(updateClock, 1000); }; module.exports = client; From 616e8339e57963aa4c7741262f6cd16a0d26dc9d Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sat, 17 Oct 2020 11:24:11 +0300 Subject: [PATCH 06/11] Fix how CSP policy is set for Helmet, fixes #6260 --- app.js | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/app.js b/app.js index c6528819bb9..b9103007345 100644 --- a/app.js +++ b/app.js @@ -29,19 +29,7 @@ function create (env, ctx) { const enableCSP = env.secureCsp ? true : false; - console.info('Enabled SECURE_HSTS_HEADER (HTTP Strict Transport Security)'); - const helmet = require('helmet'); - var includeSubDomainsValue = env.secureHstsHeaderIncludeSubdomains; - var preloadValue = env.secureHstsHeaderPreload; - app.use(helmet({ - hsts: { - maxAge: 31536000 - , includeSubDomains: includeSubDomainsValue - , preload: preloadValue - } - , frameguard: false - , contentSecurityPolicy: enableCSP - })); + let cspPolicy = false; if (enableCSP) { var secureCspReportOnly = env.secureCspReportOnly; @@ -60,7 +48,7 @@ function create (env, ctx) { } } - app.use(helmet.contentSecurityPolicy({ //TODO make NS work without 'unsafe-inline' + cspPolicy = { //TODO make NS work without 'unsafe-inline' directives: { defaultSrc: ["'self'"] , styleSrc: ["'self'", 'https://fonts.googleapis.com/', 'https://fonts.gstatic.com/', "'unsafe-inline'"] @@ -76,7 +64,26 @@ function create (env, ctx) { , frameAncestors: frameAncestors } , reportOnly: secureCspReportOnly - })); + }; + } + + + console.info('Enabled SECURE_HSTS_HEADER (HTTP Strict Transport Security)'); + const helmet = require('helmet'); + var includeSubDomainsValue = env.secureHstsHeaderIncludeSubdomains; + var preloadValue = env.secureHstsHeaderPreload; + app.use(helmet({ + hsts: { + maxAge: 31536000 + , includeSubDomains: includeSubDomainsValue + , preload: preloadValue + } + , frameguard: false + , contentSecurityPolicy: cspPolicy + })); + + if (enableCSP) { + app.use(helmet.referrerPolicy({ policy: 'no-referrer' })); app.use(bodyParser.json({ type: ['json', 'application/csp-report'] })); app.post('/report-violation', (req, res) => { From eb702a272c30440b4e9c254abdd7f771432b1311 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sat, 17 Oct 2020 11:42:21 +0300 Subject: [PATCH 07/11] Fix authorization --- lib/authorization/storage.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/authorization/storage.js b/lib/authorization/storage.js index 00521127b36..d602470add6 100644 --- a/lib/authorization/storage.js +++ b/lib/authorization/storage.js @@ -227,10 +227,11 @@ function init (env, ctx) { if (!Array.isArray(accessToken)) accessToken = [accessToken]; for (let i=0; i < accessToken.length; i++) { - if (checkToken(accessToken[i])) return true; + const subject = checkToken(accessToken[i]); + if (subject) return subject; } - return false; + return null; }; storage.doesAccessTokenExist = function doesAccessTokenExist(accessToken) { From c13af9c5344e74c07dc295bc792c4fb908e1368f Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sat, 17 Oct 2020 11:47:57 +0300 Subject: [PATCH 08/11] Fix coverage --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e26dfa2787c..bbc6f063262 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "bundle-dev": "webpack --mode development --config webpack.config.js && npm run-script update-buster", "bundle-analyzer": "webpack --mode development --config webpack.config.js --profile --json > stats.json && webpack-bundle-analyzer stats.json", "update-buster": "node bin/generateCacheBuster.js >tmp/cacheBusterToken", - "coverage": "cat ./coverage/lcov.info | env-cmd ./ci.test.env codacy-coverage", + "coverage": "cat ./coverage/lcov.info | env-cmd -f ./ci.test.env codacy-coverage", "dev": "env-cmd -f ./my.env nodemon server.js 0.0.0.0", "prod": "env-cmd -f ./my.prod.env node server.js 0.0.0.0", "lint": "eslint lib" From 664747a5c00db3aea80d85a4cbe6bc59308a8d60 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Wed, 21 Oct 2020 15:35:22 +0300 Subject: [PATCH 09/11] * Added unit test for batch upload of CGM entries * Improved / removed some logging * Another fix for high alarm disabling --- lib/plugins/ar2.js | 7 +++-- lib/plugins/bgnow.js | 4 +-- lib/server/bootevent.js | 2 +- tests/api.entries.test.js | 62 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 6 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index ddce1308302..e808dc9b690 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -69,8 +69,8 @@ function init (ctx) { console.log('ar2', prop); - if (prop) { - sbx.notifications.requestNotify({ + if (prop && prop.level) { + const notify = { level: prop.level , title: buildTitle(prop, sbx) , message: sbx.buildDefaultMessage() @@ -78,7 +78,8 @@ function init (ctx) { , pushoverSound: pushoverSound(prop, sbx.levels) , plugin: ar2 , debug: buildDebug(prop, sbx) - }); + }; + sbx.notifications.requestNotify(notify); } }; diff --git a/lib/plugins/bgnow.js b/lib/plugins/bgnow.js index 512a23805c2..f3d835788bf 100644 --- a/lib/plugins/bgnow.js +++ b/lib/plugins/bgnow.js @@ -146,12 +146,12 @@ function init (ctx) { bgnow.calcDelta = function calcDelta (recent, previous, sbx) { if (_.isEmpty(recent)) { - console.info('all buckets are empty'); + //console.info('No recent CGM data is available'); return null; } if (_.isEmpty(previous)) { - console.info('previous bucket not found, not calculating delta'); + //console.info('previous bucket not found, not calculating delta'); return null; } diff --git a/lib/server/bootevent.js b/lib/server/bootevent.js index 1c7d9f58f58..4ee45e054d5 100644 --- a/lib/server/bootevent.js +++ b/lib/server/bootevent.js @@ -246,7 +246,7 @@ function boot (env, language) { }); ctx.bus.on('data-loaded', function updatePlugins ( ) { - // console.info('reloading sandbox data'); + console.info('data loaded: reloading sandbox data and updating plugins'); var sbx = require('../sandbox')().serverInit(env, ctx); ctx.plugins.setProperties(sbx); ctx.notifications.initRequests(); diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index 098b5c45663..6c0c3f14e45 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -307,4 +307,66 @@ describe('Entries REST api', function ( ) { }); }); + it('post multipole entries, query, delete, verify gone', function (done) { + // insert a glucose entry - needs to be unique from example data + console.log('Inserting glucose entry') + request(self.app) + .post('/entries/') + .set('api-secret', self.env.api_secret || '') + .send([{ + "type": "sgv", "sgv": "199", "dateString": "2014-07-20T00:44:15.000-07:00" + , "date": 1405791855000, "device": "dexcom", "direction": "NOT COMPUTABLE" + }, { + "type": "sgv", "sgv": "200", "dateString": "2014-07-20T00:44:15.001-07:00" + , "date": 1405791855001, "device": "dexcom", "direction": "NOT COMPUTABLE" + }]) + .expect(200) + .end(function (err) { + if (err) { + done(err); + } else { + // make sure treatment was inserted successfully + console.log('Ensuring glucose entry was inserted successfully'); + request(self.app) + .get('/entries.json?find[dateString][$gte]=2014-07-20&count=100') + .set('api-secret', self.env.api_secret || '') + .expect(200) + .expect(function (response) { + var entry = response.body[0]; + response.body.length.should.equal(2); + entry.sgv.should.equal('200'); + entry.utcOffset.should.equal(-420); + }) + .end(function (err) { + if (err) { + done(err); + } else { + // delete the glucose entry + console.log('Deleting test glucose entry'); + request(self.app) + .delete('/entries.json?find[dateString][$gte]=2014-07-20&count=100') + .set('api-secret', self.env.api_secret || '') + .expect(200) + .end(function (err) { + if (err) { + done(err); + } else { + // make sure it was deleted + console.log('Testing if glucose entries were deleted'); + request(self.app) + .get('/entries.json?find[dateString][$gte]=2014-07-20&count=100') + .set('api-secret', self.env.api_secret || '') + .expect(200) + .expect(function (response) { + response.body.length.should.equal(0); + }) + .end(done); + } + }); + } + }); + } + }); + }); + }); From 54dd4deecb3e1ff4cc69b510dd180feb8c3865ed Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 10 Nov 2020 17:12:30 +0200 Subject: [PATCH 10/11] Test if user is in read only mode when Nightscout starts and give an error if so --- lib/storage/mongo-storage.js | 101 ++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 44 deletions(-) diff --git a/lib/storage/mongo-storage.js b/lib/storage/mongo-storage.js index 39c1a81e641..ce7e6a75d31 100644 --- a/lib/storage/mongo-storage.js +++ b/lib/storage/mongo-storage.js @@ -36,59 +36,72 @@ function init (env, cb, forceNewConnection) { MongoClient.connect(env.storageURI, options) .then(client => { - console.log('Successfully established a connected to MongoDB'); + console.log('Successfully established a connected to MongoDB'); - var dbName = env.storageURI.split('/').pop().split('?'); - dbName = dbName[0]; // drop Connection Options - mongo.db = client.db(dbName); - connection = mongo.db; - mongo.client = client; - // If there is a valid callback, then invoke the function to perform the callback + var dbName = env.storageURI.split('/').pop().split('?'); + dbName = dbName[0]; // drop Connection Options + mongo.db = client.db(dbName); + connection = mongo.db; + mongo.client = client; - if (cb && cb.call) { - cb(null, mongo); - } - }) - .catch(err => { - if (err.message && err.message.includes('AuthenticationFailed')) { - console.log('Authentication to Mongo failed'); - cb(new Error('MongoDB authentication failed! Double check the URL has the right username and password in MONGODB_URI.'), null); - return; - } + mongo.db.command({ connectionStatus: 1 }).then( + result => { + const role = result.authInfo.authenticatedUserRoles[0].role; + + if (role == 'read') { + console.error('Mongo user is read only'); + cb(new Error('MongoDB connection is in read only mode! Go back to MongoDB configuration and check your database user has read and write access.'), null); + } + + console.log('Mongo user role seems ok:', role); - if (err.name && err.name === "MongoNetworkError") { - var timeout = (i > 15) ? 60000 : i * 3000; - console.log('Error connecting to MongoDB: %j - retrying in ' + timeout / 1000 + ' sec', err); - setTimeout(connect_with_retry, timeout, i + 1); - if (i == 1) cb(new Error('MongoDB connection failed! Double check the MONGODB_URI setting in Heroku.'), null); - } else { - cb(new Error('MONGODB_URI ' + env.storageURI + ' seems invalid: ' + err.message)); + // If there is a valid callback, then invoke the function to perform the callback + if (cb && cb.call) { + cb(null, mongo); + } } - }); + ); + }) + .catch(err => { + if (err.message && err.message.includes('AuthenticationFailed')) { + console.log('Authentication to Mongo failed'); + cb(new Error('MongoDB authentication failed! Double check the URL has the right username and password in MONGODB_URI.'), null); + return; + } + + if (err.name && err.name === "MongoNetworkError") { + var timeout = (i > 15) ? 60000 : i * 3000; + console.log('Error connecting to MongoDB: %j - retrying in ' + timeout / 1000 + ' sec', err); + setTimeout(connect_with_retry, timeout, i + 1); + if (i == 1) cb(new Error('MongoDB connection failed! Double check the MONGODB_URI setting in Heroku.'), null); + } else { + cb(new Error('MONGODB_URI ' + env.storageURI + ' seems invalid: ' + err.message)); + } + }); - }; + }; - return connect_with_retry(1); + return connect_with_retry(1); - } } + } - mongo.collection = function get_collection (name) { - return connection.collection(name); - }; - - mongo.ensureIndexes = function ensureIndexes (collection, fields) { - fields.forEach(function(field) { - console.info('ensuring index for: ' + field); - collection.createIndex(field, { 'background': true }, function(err) { - if (err) { - console.error('unable to ensureIndex for: ' + field + ' - ' + err); - } - }); + mongo.collection = function get_collection (name) { + return connection.collection(name); + }; + + mongo.ensureIndexes = function ensureIndexes (collection, fields) { + fields.forEach(function(field) { + console.info('ensuring index for: ' + field); + collection.createIndex(field, { 'background': true }, function(err) { + if (err) { + console.error('unable to ensureIndex for: ' + field + ' - ' + err); + } }); - }; + }); + }; - return maybe_connect(cb); - } + return maybe_connect(cb); +} - module.exports = init; +module.exports = init; From bfee6ae685e5d26137207ac5d857d0148c026986 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 10 Nov 2020 17:42:54 +0200 Subject: [PATCH 11/11] Fix the role check so this works with unit tests --- lib/storage/mongo-storage.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/storage/mongo-storage.js b/lib/storage/mongo-storage.js index ce7e6a75d31..28dea49ac75 100644 --- a/lib/storage/mongo-storage.js +++ b/lib/storage/mongo-storage.js @@ -46,14 +46,13 @@ function init (env, cb, forceNewConnection) { mongo.db.command({ connectionStatus: 1 }).then( result => { - const role = result.authInfo.authenticatedUserRoles[0].role; - - if (role == 'read') { + const roles = result.authInfo.authenticatedUserRoles; + if (roles.lenght > 0 && roles[0].role == 'read') { console.error('Mongo user is read only'); cb(new Error('MongoDB connection is in read only mode! Go back to MongoDB configuration and check your database user has read and write access.'), null); } - console.log('Mongo user role seems ok:', role); + console.log('Mongo user role seems ok:', roles); // If there is a valid callback, then invoke the function to perform the callback if (cb && cb.call) {