From 24c2db4590bbe70cbfa237fa89634e77817cba48 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 10 Nov 2020 18:02:28 +0200 Subject: [PATCH] Fix batch (#6248) * Use the delta plugin data to show the delta in the clock views * Update Node checks * Fix disabling the BG alarms for simple alarms * Load battery and other rare events up to two months back * Possibly fixes compatibility with ios9 - needs testing * Unified black and color clock layouts * Update clock data every 20 seconds * Update clock time every second * Fix how CSP policy is set for Helmet, fixes #6260 * Authorization fix for misformatted URLs * Added unit test for batch upload of CGM entries * Improved / removed some logging * Test if user is in read only mode when Nightscout starts and give an error if so --- app.js | 37 +++++---- lib/authorization/storage.js | 28 +++++-- lib/client/clock-client.js | 146 ++++++++++++++++------------------- lib/data/dataloader.js | 2 +- lib/plugins/ar2.js | 13 ++-- lib/plugins/bgnow.js | 4 +- lib/plugins/simplealarms.js | 21 +++-- lib/server/bootevent.js | 29 +++---- lib/storage/mongo-storage.js | 106 ++++++++++++++----------- npm-shrinkwrap.json | 58 +++++++++++++- package.json | 18 ++--- tests/api.entries.test.js | 62 +++++++++++++++ views/index.html | 2 +- 13 files changed, 327 insertions(+), 199 deletions(-) diff --git a/app.js b/app.js index 90bb372a3157..2a94b1dd3e91 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) => { diff --git a/lib/authorization/storage.js b/lib/authorization/storage.js index c032018d170b..d602470add6c 100644 --- a/lib/authorization/storage.js +++ b/lib/authorization/storage.js @@ -210,16 +210,28 @@ 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++) { + const subject = checkToken(accessToken[i]); + if (subject) return subject; + } + + return null; }; storage.doesAccessTokenExist = function doesAccessTokenExist(accessToken) { diff --git a/lib/client/clock-client.js b/lib/client/clock-client.js index 891f10d57d5d..73ac9c3baca9 100644 --- a/lib/client/clock-client.js +++ b/lib/client/clock-client.js @@ -1,53 +1,55 @@ 'use strict'; -const browserSettings = require('./browser-settings'); - +var browserSettings = require('./browser-settings'); var client = {}; +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.json?find[type]=sgv&count=3&t=' + new Date().getTime(); + var src = '/api/v2/properties'; // Use precalculated data from the backend if (secret) { - src += '&secret=' + secret; + var s = '?secret=' + secret; + src += s; } else if (token) { - src += '&token=' + token; + var s2 = '?token=' + token; + src += s2; } $.ajax(src, { - success: client.render + error: function gotError (err) { + console.error(err); + } + , success: function gotData (data) { + latestProperties = data; + client.render(); + } }); }; -client.render = function render (xhr) { - console.log('got data', xhr); +client.render = function render () { - let rec; - let delta; + if (!latestProperties.bgnow && !latestProperties.bgnow.sgvs) { + console.error('BG data not available'); + return; + } - // 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)); - } - }); + let rec = latestProperties.bgnow.sgvs[0]; + let deltaDisplayValue; + + if (latestProperties.delta) { + deltaDisplayValue = latestProperties.delta.display; + } let $errorMessage = $('#errorMessage'); let $inner = $('#inner'); @@ -71,15 +73,12 @@ client.render = function render (xhr) { // 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'; - } - 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 = 'bn0-sg30-ar18-nl-nl-tm26'; - } - 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(); } @@ -95,46 +94,19 @@ client.render = function render (xhr) { 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; - 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; - } + 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) { @@ -150,7 +122,7 @@ client.render = function render (xhr) { 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) { @@ -169,20 +141,16 @@ client.render = function render (xhr) { $('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.round(((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); @@ -191,17 +159,14 @@ client.render = function render (xhr) { 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(''); } @@ -216,12 +181,33 @@ client.render = function render (xhr) { $('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, 1 * 60 * 1000); + setInterval(client.query, 20 * 1000); // update every 20 seconds + + // time update + setInterval(updateClock, 1000); }; module.exports = client; diff --git a/lib/data/dataloader.js b/lib/data/dataloader.js index 7074b18e1fbd..985ea259de5c 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 ab8675a03424..7a5d5e5dcfa7 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -67,8 +67,10 @@ function init (ctx) { var prop = sbx.properties.ar2; - if (prop) { - sbx.notifications.requestNotify({ + console.log('ar2', prop); + + if (prop && prop.level) { + const notify = { level: prop.level , title: buildTitle(prop, sbx) , message: sbx.buildDefaultMessage() @@ -76,7 +78,8 @@ function init (ctx) { , pushoverSound: pushoverSound(prop, sbx.levels) , plugin: ar2 , debug: buildDebug(prop, sbx) - }); + }; + sbx.notifications.requestNotify(notify); } }; @@ -224,9 +227,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/bgnow.js b/lib/plugins/bgnow.js index 512a23805c25..f3d835788bfa 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/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 9b975dddebed..17529eabee81 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 6247645b3d20..4ee45e054d57 100644 --- a/lib/server/bootevent.js +++ b/lib/server/bootevent.js @@ -25,30 +25,21 @@ 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. + 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(); } - 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, '^12.6.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 - console.log( 'ERROR: Node version ' + nodeVersion + ' is not supported. Please use a secure LTS version or upgrade your Node'); - process.exit(1); - } } - function checkEnv (ctx, next) { ctx.language = language; if (env.err) { @@ -255,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/lib/storage/mongo-storage.js b/lib/storage/mongo-storage.js index 39c1a81e641e..28dea49ac752 100644 --- a/lib/storage/mongo-storage.js +++ b/lib/storage/mongo-storage.js @@ -36,59 +36,71 @@ function init (env, cb, forceNewConnection) { MongoClient.connect(env.storageURI, options) .then(client => { - 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 - - 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)); + 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; + + mongo.db.command({ connectionStatus: 1 }).then( + result => { + 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:', roles); + + // 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; diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 4e143615a3d8..a137dcd15339 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 a2694ae02970..bbc6f063262c 100644 --- a/package.json +++ b/package.json @@ -27,18 +27,18 @@ }, "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", "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", - "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", + "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" }, "main": "server.js", @@ -57,8 +57,8 @@ } }, "engines": { - "node": "^10.15.2 || ^8.15.1", - "npm": "^6.4.1" + "node": "^10.22.0 || ^12.18.4", + "npm": "^6.14.6" }, "dependencies": { "@babel/core": "^7.11.1", @@ -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/tests/api.entries.test.js b/tests/api.entries.test.js index 098b5c456635..6c0c3f14e45b 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); + } + }); + } + }); + } + }); + }); + }); diff --git a/views/index.html b/views/index.html index dbff801392f6..8e60b30f4715 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);