diff --git a/Procfile b/Procfile index 3f566b6..113b834 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -worker: node run.js +worker: node run.js \ No newline at end of file diff --git a/carelink.js b/carelink.js index 7fa0367..6ec248a 100644 --- a/carelink.js +++ b/carelink.js @@ -2,18 +2,27 @@ "use strict"; var _ = require('lodash'), - common = require('common'), - request = require('request'); + axios = require('axios').default, + axiosCookieJarSupport = require('axios-cookiejar-support').default, + tough = require('tough-cookie'), + urllib = require('url'), + qs = require('qs'); var logger = require('./logger'); -var CARELINK_EU = process.env['MMCONNECT_SERVER'] === 'EU'; +var MMCONNECT_SERVER = process.env['MMCONNECT_SERVER']; +var CARELINK_EU = MMCONNECT_SERVER === 'EU'; +var MMCONNECT_SERVERNAME = process.env['MMCONNECT_SERVERNAME']; + +var DEFAULT_COUNTRYCODE = process.env['MMCONNECT_COUNTRYCODE'] || 'gb'; +var DEFAULT_LANGCODE = process.env['MMCONNECT_LANGCODE'] || 'en'; + +var CARELINKEU_LOGIN_LOCALE = { country: DEFAULT_COUNTRYCODE, lang: DEFAULT_LANGCODE }; var DEFAULT_MAX_RETRY_DURATION = module.exports.defaultMaxRetryDuration = 512; -var carelinkServerAddress = CARELINK_EU ? "carelink.minimed.eu" : "carelink.minimed.com"; +var carelinkServerAddress = MMCONNECT_SERVERNAME || (CARELINK_EU ? "carelink.minimed.eu" : "carelink.minimed.com"); -var CARELINKEU_SERVER_ADDRESS = 'https://' + carelinkServerAddress; -var CARELINKEU_LOGIN1_URL = 'https://' + carelinkServerAddress + '/patient/sso/login?country=gb&lang=en'; +var CARELINKEU_LOGIN_URL = 'https://' + carelinkServerAddress + '/patient/sso/login?country=gb&lang=en'; var CARELINKEU_REFRESH_TOKEN_URL = 'https://' + carelinkServerAddress + '/patient/sso/reauth'; var CARELINKEU_JSON_BASE_URL = 'https://' + carelinkServerAddress + '/patient/connect/data?cpSerialNumber=NONE&msgType=last24hours&requestTime='; var CARELINKEU_TOKEN_COOKIE = 'auth_tmp_token'; @@ -28,70 +37,59 @@ var carelinkJsonUrlNow = function () { return (CARELINK_EU ? CARELINKEU_JSON_BASE_URL : CARELINK_JSON_BASE_URL) + Date.now(); }; -function reqOptions(extra) { - var defaults = { - jar: true, - followRedirect: false, - rejectUnauthorized: false, - changeOrigin: true, - headers: { - Host: carelinkServerAddress, - Connection: 'keep-alive', - Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', - 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:41.0) Gecko/20100101 Firefox/41.0', - 'Accept-Encoding': 'gzip,deflate,sdch', - 'Accept-Language': 'en-US,en;q=0.8' - } - }; - return _.merge(defaults, extra); -} - -/*function haveLoginCookie(jar) { - if (CARELINK_EU) - return _.some(jar.getCookies(CARELINKEU_SERVER_ADDRESS), {key: CARELINKEU_TOKEN_COOKIE}); - else - return _.some(jar.getCookies(CARELINK_SECURITY_URL), {key: CARELINK_LOGIN_COOKIE}); -}*/ - -function responseAsError(response) { - if (!(response.statusCode >= 200 && response.statusCode < 400)) { - return new Error( - "Bad response from CareLink: " + - JSON.stringify(_.merge(response, {'body': ''})) - ); - } else { - return null; +var Client = exports.Client = function (options) { + let requestCount = 0; + + if (!(this instanceof Client)) { + return new Client(arguments[0]); } -} -function checkResponseThen(fn) { - return function (err, response) { - err = err || responseAsError(response); - fn.apply(this, [err].concat(Array.prototype.slice.call(arguments, 1))); + axiosCookieJarSupport(axios); + axios.defaults.jar = new tough.CookieJar(); + axios.defaults.maxRedirects = 0; + axios.defaults.timeout = 10 * 1000; + axios.defaults.withCredentials = true; + axios.defaults.headers.common = { + 'User-Agent': null, }; -} + axios.interceptors.response.use(function (response) { + // Do something with response data + return response; + }, function (error) { + if (error.response && error.response.status >= 200 && error.response.status < 400) { + return error.response; + } else { + // Do something with response error + return Promise.reject(error); + } + }); -function retryDurationOnAttempt(n) { - return Math.pow(2, n); -} + axios.interceptors.request.use((config) => { + requestCount++; -function totalDurationAfterNextRetry(n) { - var sum = 0; - for (var i = 0; i <= n; i++) { - sum += retryDurationOnAttempt(i); - } - return sum; -} + if (requestCount > 10) + throw new Error("Request count exceeds the maximum in one fetch!"); -var Client = exports.Client = function (options) { - if (!(this instanceof Client)) { - return new Client(arguments[0]); + return config; + }); + + if (options.maxRetryDuration === undefined) { + options.maxRetryDuration = DEFAULT_MAX_RETRY_DURATION; } - var jar = request.jar(); + function retryDurationOnAttempt(n) { + return Math.pow(2, n); + } function getCookies() { - return jar.getCookies(CARELINK_EU ? CARELINKEU_SERVER_ADDRESS : CARELINK_SECURITY_URL); + let cookies = []; + axios.defaults.jar.store.getAllCookies(function (err, cookieArray) { + if (err) + cookies = []; + cookies = cookieArray; + }); + + return cookies.filter(c => c.domain === carelinkServerAddress); } function haveCookie(cookieName) { @@ -102,270 +100,195 @@ var Client = exports.Client = function (options) { return _.find(getCookies(), {key: cookieName}); } - if (options.maxRetryDuration === undefined) { - options.maxRetryDuration = DEFAULT_MAX_RETRY_DURATION; + function deleteCookies() { + return axios.defaults.jar.removeAllCookiesSync(); } - function doLogin(next) { - logger.log('POST ' + CARELINK_SECURITY_URL); - request.post( - CARELINK_SECURITY_URL, - reqOptions({ - jar: jar, - form: {j_username: options.username, j_password: options.password, j_character_encoding: "UTF-8"} - }), - checkResponseThen(next) - ); + function removeCookie(domain, path, key) { + return axios.defaults.jar.store.removeCookie(domain, path, key, function () { + }); } - function doFetchCookie(response, next) { - logger.log('GET ' + CARELINK_AFTER_LOGIN_URL); - request.get( - CARELINK_AFTER_LOGIN_URL, - reqOptions({ - jar: jar - }), - checkResponseThen(next) - ); + function setCookie(domain, path, key, value) { + axios.defaults.jar.setCookieSync(`${key}=${value}`, `https://${domain}${path}`); } - var params = function (url) { - let q = url.split('?'), result = {}; - if (q.length >= 1) { - q[q.length >= 2 ? 1 : 0].split('&').forEach((item) => { - try { - result[item.split('=')[0]] = item.split('=')[1]; - } catch (e) { - result[item.split('=')[0]] = ''; - } - }) - } - return result; + async function doLogin() { + return await axios.post( + CARELINK_SECURITY_URL, + qs.stringify({ + j_username: options.username, + j_password: options.password, + j_character_encoding: "UTF-8" + })); } - function doLoginEu1(next) { - logger.log('GET ' + CARELINKEU_LOGIN1_URL); - - request.get( - CARELINKEU_LOGIN1_URL, - reqOptions({ - jar: jar, - }), - checkResponseThen(next) - ); + async function doFetchCookie() { + return await axios.get(CARELINK_AFTER_LOGIN_URL); } - function doLoginEu2(response, next) { - let url = response.headers.location; + async function doLoginEu1() { + let url = urllib.parse(CARELINKEU_LOGIN_URL); + var query = _.merge(qs.parse(url.query), CARELINKEU_LOGIN_LOCALE); + url = urllib.format(_.merge(url, { search: null, query: query })); - logger.log('GET ' + url); + deleteCookies(); + logger.log('EU login 1'); + return await axios.get(url); + } - request.get( - url, - reqOptions({ - jar: jar, - }), - checkResponseThen(next) - ); + async function doLoginEu2(response) { + logger.log(`EU login 2 (url: ${response.headers.location})`); + return await axios.get(response.headers.location); } - function doLoginEu3(response, next) { + async function doLoginEu3(response) { let uri = new URL(response.headers.location); let uriParam = uri.searchParams; let url = `${uri.origin}${uri.pathname}?locale=${uriParam.get('locale')}&countrycode=${uriParam.get('countrycode')}`; - logger.log('POST ' + url); - - request.post( - url, - reqOptions({ - jar: jar, - gzip: true, - form: { - sessionID: uriParam.get('sessionID'), - sessionData: uriParam.get('sessionData'), - locale: "en", - action: "login", - username: options.username, - password: options.password, - actionButton: "Log in", - } - }), - checkResponseThen(next) - ); - } - - function doLoginEu4(response, next) { - const regexUrl = /(
= options.maxRetryDuration) { - logger.log('Retried for too long (' + totalDurationAfterNextRetry(retryCount - 1) + ' seconds).'); - next(err); - } - var timeout = retryDurationOnAttempt(retryCount); - logger.log('Trying again in ' + timeout + ' second(s)...'); - setTimeout(function () { - if (CARELINK_EU) { - refreshTokenEu(function() { - getConnectData(response, next, retryCount + 1); - }); - } else { - getConnectData(response, next, retryCount + 1); - } - }, 1000 * timeout); - } else { - next(null, response); - } + return await axios + .post(CARELINKEU_REFRESH_TOKEN_URL) + .then(response => { + axios.defaults.headers.common = { + 'Authorization': `Bearer ${_.get(getCookie(CARELINKEU_TOKEN_COOKIE), 'value', '')}`, + 'User-Agent': null, + }; }) - ); + .catch(async function (error) { + logger.log(`Refresh EU token failed (${error})`); + deleteCookies(); + await checkLogin(true); + }); } - function parseData(response, next) { - var parsed; - try { - parsed = JSON.parse(response.body); - } catch (e) { - next(e); - } - next(null, parsed); + async function getConnectData() { + var url = carelinkJsonUrlNow(); + logger.log('GET data ' + url); + return await axios.get(url); } - function checkLogin(next) { + async function checkLogin(relogin = false) { if (CARELINK_EU) { // EU - SSO method - if (haveCookie(CARELINKEU_TOKEN_COOKIE)) { - let expire = new Date(Date.parse(_.get(getCookie(CARELINKEU_TOKENEXPIRE_COOKIE), 'value', '2999-01-01'))); + if (!relogin && (haveCookie(CARELINKEU_TOKEN_COOKIE) || haveCookie(CARELINKEU_TOKENEXPIRE_COOKIE))) { + let expire = new Date(Date.parse(_.get(getCookie(CARELINKEU_TOKENEXPIRE_COOKIE), 'value'))); - if (expire < new Date(Date.now() - 10 * 1000 * 60)) { - refreshTokenEu(next); - } else { - next(null, null); - } + // Refresh token if expires in 10 minutes + if (expire < new Date(Date.now() + 6 * 1000 * 60)) + await refreshTokenEu(); } else { - common.step([ - doLoginEu1, - doLoginEu2, - doLoginEu3, - doLoginEu4, - doLoginEu5, - next.bind(null, null), - ], - ); + logger.log('Logging in to CareLink'); + let response = await doLoginEu1(); + response = await doLoginEu2(response); + response = await doLoginEu3(response); + response = await doLoginEu4(response); + await doLoginEu5(response); } } else { // US - Cookie method - if (haveCookie(CARELINK_LOGIN_COOKIE)) { - next(null); - } else { + if (!haveCookie(CARELINK_LOGIN_COOKIE)) { logger.log('Logging in to CareLink'); - - common.step([ - doLogin, - doFetchCookie, - next.bind(null, null) - ] - ); + let response = await doLogin() + await doFetchCookie(response) } } } - function fetch(callback) { - common.step( - [ - checkLogin, - getConnectData, - parseData, - callback.bind(null, null), - ], - callback - ); + function sleep(ms) { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + } + + async function fetch(callback) { + requestCount = 0; + + let data = null; + let error = null; + try { + let maxRetry = 1; // No retry + for (let i = 1; i <= maxRetry; i++) { + await checkLogin(); + try { + data = (await getConnectData()).data; + break; + } catch (e1) { + deleteCookies(); + + if (i === maxRetry) + throw e1; + + if (e1.response && e1.response.status === 401) { + // reauth + cookieJar.removeAllCookiesSync(); + } + + let timeout = retryDurationOnAttempt(i); + await sleep(1000 * timeout); + } + } + } catch (e) { + error = `${e.toString()}\nstack: ${e.stack}`; + } finally { + callback(error, data); + } } return { diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index a47962e..80aa024 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "minimed-connect-to-nightscout", - "version": "1.3.2", + "version": "1.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -73,6 +73,23 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "requires": { + "follow-redirects": "1.5.10" + } + }, + "axios-cookiejar-support": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/axios-cookiejar-support/-/axios-cookiejar-support-1.0.0.tgz", + "integrity": "sha512-9pBlIU5jfrGZTnUQlt8symShviSTOSlOKGtryHx76lJPnKIXDqUT3JDAjJ1ywOQLyfiWrthIt4iJiVP2L2S4jA==", + "requires": { + "is-redirect": "^1.0.0", + "pify": "^5.0.0" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -368,6 +385,29 @@ "is-buffer": "~2.0.3" } }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -535,6 +575,11 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" + }, "is-regex": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", @@ -898,10 +943,15 @@ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, + "pify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", + "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==" + }, "psl": { - "version": "1.1.31", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", - "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" }, "pump": { "version": "3.0.0", @@ -953,6 +1003,20 @@ "node-uuid": { "version": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz", "integrity": "sha1-baWhdmjEs91ZYjvaEc9/pMH2Cm8=" + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } } } }, @@ -1074,19 +1138,13 @@ } }, "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - } + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" } }, "tunnel-agent": { @@ -1102,6 +1160,11 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", diff --git a/package.json b/package.json index e80fc6c..db1ac34 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minimed-connect-to-nightscout", - "version": "1.3.2", + "version": "1.4.0", "description": "Send Medtronic pump and CGM data to Nightscout.", "author": "Mark Wilson ", "repository": { @@ -13,17 +13,20 @@ "main": "index.js", "scripts": { "start": "node run.js", - "test": "node_modules/.bin/mocha" + "test": "node_modules/.bin/mocha --timeout 10000" }, "license": "MIT", "engines": { - "node": "^10.14.1 || ^8.15.1", + "node": "^10.15.2 || ^8.15.1", "npm": "^6.4.1" }, "dependencies": { + "axios": "^0.19.2", + "axios-cookiejar-support": "^1.0.0", "common": "^0.2.5", "lodash": "^4.17.15", - "request": "^2.88.0" + "request": "^2.88.0", + "tough-cookie": "^4.0.0" }, "devDependencies": { "expect.js": "^0.3.1", diff --git a/run.js b/run.js index d9576a2..1a49424 100644 --- a/run.js +++ b/run.js @@ -25,7 +25,8 @@ var config = { interval: parseInt(readEnv('CARELINK_REQUEST_INTERVAL', 60 * 1000), 10), sgvLimit: parseInt(readEnv('CARELINK_SGV_LIMIT', 24), 10), maxRetryDuration: parseInt(readEnv('CARELINK_MAX_RETRY_DURATION', carelink.defaultMaxRetryDuration), 10), - verbose: !readEnv('CARELINK_QUIET') + verbose: !readEnv('CARELINK_QUIET'), + deviceInterval: 5.1 * 60 * 1000, }; if (!config.username) { @@ -68,26 +69,39 @@ function uploadMaybe(items, endpoint, callback) { } (function requestLoop() { - client.fetch(function(err, data) { - if (err) { - throw new Error(err); - } else { - var transformed = transform(data, config.sgvLimit); + try { + client.fetch(function(err, data) { + if (err) { + console.log(err); + setTimeout(requestLoop, config.deviceInterval); + } else { + let transformed = transform(data, config.sgvLimit); + + // Because of Nightscout's upsert semantics and the fact that CareLink provides trend + // data only for the most recent sgv, we need to filter out sgvs we've already sent. + // Otherwise we'll overwrite existing sgv entries and remove their trend data. + let newSgvs = filterSgvs(transformed.entries); + + // Nightscout's entries collection upserts based on date, but the devicestatus collection + // does not do the same for created_at, so we need to de-dupe them here. + let newDeviceStatuses = filterDeviceStatus(transformed.devicestatus); - // Because of Nightscout's upsert semantics and the fact that CareLink provides trend - // data only for the most recent sgv, we need to filter out sgvs we've already sent. - // Otherwise we'll overwrite existing sgv entries and remove their trend data. - var newSgvs = filterSgvs(transformed.entries); + // Calculate interval by the device next upload time + let interval = config.deviceInterval - (data.currentServerTime - data.lastMedicalDeviceDataUpdateServerTime); + if (interval > config.deviceInterval || interval < 0) + interval = config.deviceInterval; - // Nightscout's entries collection upserts based on date, but the devicestatus collection - // does not do the same for created_at, so we need to de-dupe them here. - var newDeviceStatuses = filterDeviceStatus(transformed.devicestatus); + logger.log(`Next check ${Math.round(interval / 1000)}s later (at ${new Date(Date.now() + interval)})`) - uploadMaybe(newSgvs, entriesUrl, function() { - uploadMaybe(newDeviceStatuses, devicestatusUrl, function() { - setTimeout(requestLoop, config.interval); + uploadMaybe(newSgvs, entriesUrl, function() { + uploadMaybe(newDeviceStatuses, devicestatusUrl, function() { + setTimeout(requestLoop, interval); + }); }); - }); - } - }); + } + }); + } catch (error) { + console.error(error); + setTimeout(requestLoop, config.deviceInterval); + } })(); diff --git a/test/connectEu.js b/test/connectEu.js new file mode 100644 index 0000000..28a5290 --- /dev/null +++ b/test/connectEu.js @@ -0,0 +1,29 @@ +/* jshint node: true */ +/* globals describe, it */ +"use strict"; + +process.env['MMCONNECT_SERVER'] = "EU"; + +var _ = require('lodash'), + expect = require('expect.js'); + +var carelink = require('../carelink.js'); + +describe('connectEu()', function() { + var client = carelink.Client({ + username: "nstesteu", + password: "ournightscouteutest", + }); + + it('should save without error', function (done) { + client.fetch(function (err, data) { + if (err) { + done(err) + } + else { + expect(data).to.have.property('bgunits'); + done(); + } + }) + }); +}); diff --git a/test/connectUs.js b/test/connectUs.js new file mode 100644 index 0000000..f6b0ad3 --- /dev/null +++ b/test/connectUs.js @@ -0,0 +1,29 @@ +/* jshint node: true */ +/* globals describe, it */ +"use strict"; + +process.env['MMCONNECT_SERVER'] = "US"; + +var _ = require('lodash'), + expect = require('expect.js'); + +var carelink = require('../carelink.js'); + +describe('connectUS()', function() { + var client = carelink.Client({ + username: "nstestuss", + password: "ournightscoutustest", + }); + + it('should save without error', function (done) { + client.fetch(function (err, data) { + if (err) { + done(err) + } + else { + expect(data).to.have.property('bgunits'); + done(); + } + }) + }); +});