From 4779cf1bac7076d7a66397ee000aff2f5cd5eec4 Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 30 Jun 2020 13:52:32 -0700 Subject: [PATCH 01/32] allow specifying server name via environment variable --- carelink.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/carelink.js b/carelink.js index 7fa0367..52606ea 100644 --- a/carelink.js +++ b/carelink.js @@ -7,10 +7,13 @@ var _ = require('lodash'), 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_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'; From d32a7f4946498a1b4b06c8a9756f4f327e25579d Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 30 Jun 2020 16:00:09 -0700 Subject: [PATCH 02/32] pass runtime country and lang values Future-proofing a bit. --- carelink.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/carelink.js b/carelink.js index 52606ea..e7b01f9 100644 --- a/carelink.js +++ b/carelink.js @@ -3,6 +3,8 @@ var _ = require('lodash'), common = require('common'), + qs = require('qs'), + urllib = require('url'), request = require('request'); var logger = require('./logger'); @@ -11,6 +13,10 @@ 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 = MMCONNECT_SERVERNAME || (CARELINK_EU ? "carelink.minimed.eu" : "carelink.minimed.com"); @@ -147,10 +153,14 @@ var Client = exports.Client = function (options) { } function doLoginEu1(next) { - logger.log('GET ' + CARELINKEU_LOGIN1_URL); + let url = urllib.parse(CARELINKEU_LOGIN1_URL); + var query = _.merge(qs.parse(url.query), CARELINKEU_LOGIN_LOCALE); + url = urllib.format(_.merge(url, { search: null, query: query })); + + logger.log('GET ' + url); request.get( - CARELINKEU_LOGIN1_URL, + url, reqOptions({ jar: jar, }), From c5de0aa0d26c872647f51c0006c075cf38f71933 Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 30 Jun 2020 16:12:42 -0700 Subject: [PATCH 03/32] be consistent about using qs and url from stdlib --- carelink.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/carelink.js b/carelink.js index e7b01f9..6492baf 100644 --- a/carelink.js +++ b/carelink.js @@ -183,10 +183,10 @@ var Client = exports.Client = function (options) { } function doLoginEu3(response, next) { - 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')}`; + let uri = response.headers.location; + let url = urllib.parse(uri); + let query = qs.parse(url.query); + url = urllib.format(_.merge(url, {search: null, query: CARELINKEU_LOGIN_LOCALE})); logger.log('POST ' + url); request.post( @@ -195,8 +195,8 @@ var Client = exports.Client = function (options) { jar: jar, gzip: true, form: { - sessionID: uriParam.get('sessionID'), - sessionData: uriParam.get('sessionData'), + sessionID: query.sessionID, + sessionData: query.sessionData, locale: "en", action: "login", username: options.username, From 48978bef9086d736c8149cd8b11045d6b460c838 Mon Sep 17 00:00:00 2001 From: Frigyes Bartha Date: Tue, 30 Jun 2020 20:31:58 +0200 Subject: [PATCH 04/32] Trying to solve ERR_TLS_CERT_ALTNAME_INVALID error --- carelink.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/carelink.js b/carelink.js index 6492baf..dd4b9a8 100644 --- a/carelink.js +++ b/carelink.js @@ -41,10 +41,10 @@ function reqOptions(extra) { var defaults = { jar: true, followRedirect: false, - rejectUnauthorized: false, + //rejectUnauthorized: false, changeOrigin: true, headers: { - Host: carelinkServerAddress, + //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', From 4bb98ae7c6c26710912c6cd7a426c7b90fd26edb Mon Sep 17 00:00:00 2001 From: Frigyes Bartha Date: Tue, 30 Jun 2020 20:43:02 +0200 Subject: [PATCH 05/32] Trying to solve ERR_TLS_CERT_ALTNAME_INVALID error2 --- carelink.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/carelink.js b/carelink.js index dd4b9a8..c315694 100644 --- a/carelink.js +++ b/carelink.js @@ -41,7 +41,7 @@ function reqOptions(extra) { var defaults = { jar: true, followRedirect: false, - //rejectUnauthorized: false, + rejectUnauthorized: false, changeOrigin: true, headers: { //Host: carelinkServerAddress, From 2c5ed825b02aa785ef1620d5ab4fbf2c9459a20c Mon Sep 17 00:00:00 2001 From: Frigyes Bartha Date: Tue, 30 Jun 2020 21:15:59 +0200 Subject: [PATCH 06/32] Trying to solve ERR_TLS_CERT_ALTNAME_INVALID error3 --- carelink.js | 67 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/carelink.js b/carelink.js index c315694..c170fb0 100644 --- a/carelink.js +++ b/carelink.js @@ -22,7 +22,7 @@ var DEFAULT_MAX_RETRY_DURATION = module.exports.defaultMaxRetryDuration = 512; 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'; @@ -99,6 +99,10 @@ var Client = exports.Client = function (options) { var jar = request.jar(); + if (options.maxRetryDuration === undefined) { + options.maxRetryDuration = DEFAULT_MAX_RETRY_DURATION; + } + function getCookies() { return jar.getCookies(CARELINK_EU ? CARELINKEU_SERVER_ADDRESS : CARELINK_SECURITY_URL); } @@ -111,28 +115,41 @@ var Client = exports.Client = function (options) { return _.find(getCookies(), {key: cookieName}); } - if (options.maxRetryDuration === undefined) { - options.maxRetryDuration = DEFAULT_MAX_RETRY_DURATION; + function getHost(url) { + return new URL(url).host; } function doLogin(next) { - logger.log('POST ' + CARELINK_SECURITY_URL); + let url = CARELINK_SECURITY_URL; + logger.log('POST ' + url); + request.post( - CARELINK_SECURITY_URL, + url, reqOptions({ jar: jar, - form: {j_username: options.username, j_password: options.password, j_character_encoding: "UTF-8"} + headers: { + Host: getHost(url), + }, + form: { + j_username: options.username, + j_password: options.password, + j_character_encoding: "UTF-8" + }, }), checkResponseThen(next) ); } function doFetchCookie(response, next) { - logger.log('GET ' + CARELINK_AFTER_LOGIN_URL); + let url = CARELINK_AFTER_LOGIN_URL; + logger.log('GET ' + url); request.get( - CARELINK_AFTER_LOGIN_URL, + url, reqOptions({ - jar: jar + jar: jar, + headers: { + Host: getHost(url), + }, }), checkResponseThen(next) ); @@ -153,7 +170,7 @@ var Client = exports.Client = function (options) { } function doLoginEu1(next) { - let url = urllib.parse(CARELINKEU_LOGIN1_URL); + 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 })); @@ -163,6 +180,9 @@ var Client = exports.Client = function (options) { url, reqOptions({ jar: jar, + headers: { + Host: getHost(url), + }, }), checkResponseThen(next) ); @@ -177,6 +197,9 @@ var Client = exports.Client = function (options) { url, reqOptions({ jar: jar, + headers: { + Host: getHost(url), + }, }), checkResponseThen(next) ); @@ -194,6 +217,9 @@ var Client = exports.Client = function (options) { reqOptions({ jar: jar, gzip: true, + headers: { + host: getHost(url), + }, form: { sessionID: query.sessionID, sessionData: query.sessionData, @@ -223,6 +249,9 @@ var Client = exports.Client = function (options) { url, reqOptions({ jar: jar, + headers: { + Host: getHost(url), + }, form: { action: "consent", sessionID: ps.sessionID, @@ -244,21 +273,26 @@ var Client = exports.Client = function (options) { url, reqOptions({ jar: jar, + headers: { + Host: getHost(url), + }, }), checkResponseThen(next) ); } function refreshTokenEu(next) { + let url = CARELINKEU_REFRESH_TOKEN_URL; logger.log('Refresh auth token'); request.post( - CARELINKEU_REFRESH_TOKEN_URL, + url, reqOptions({ jar: jar, gzip: true, json: true, headers: { + Host: getHost(url), Authorization: "Bearer " + _.get(getCookie(CARELINKEU_TOKEN_COOKIE), 'value', ''), }, }), @@ -282,12 +316,13 @@ var Client = exports.Client = function (options) { var reqO = { jar: jar, - gzip: true + gzip: true, + headers: { + Host: getHost(url), + }, }; if (CARELINK_EU) { - reqO.headers = { - Authorization: "Bearer " + _.get(getCookie(CARELINKEU_TOKEN_COOKIE), 'value', ''), - }; + reqO.headers.Authorization = "Bearer " + _.get(getCookie(CARELINKEU_TOKEN_COOKIE), 'value', ''); } var resp = request.get( @@ -306,7 +341,7 @@ var Client = exports.Client = function (options) { logger.log('Trying again in ' + timeout + ' second(s)...'); setTimeout(function () { if (CARELINK_EU) { - refreshTokenEu(function() { + refreshTokenEu(function () { getConnectData(response, next, retryCount + 1); }); } else { From 7dbfda516d821e0be981fb6cf6d52a096e37bdd5 Mon Sep 17 00:00:00 2001 From: Frigyes Bartha Date: Tue, 30 Jun 2020 21:39:50 +0200 Subject: [PATCH 07/32] test --- carelink.js | 65 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/carelink.js b/carelink.js index c170fb0..31ae258 100644 --- a/carelink.js +++ b/carelink.js @@ -119,6 +119,11 @@ var Client = exports.Client = function (options) { return new URL(url).host; } + function getPath(url) { + let u = new URL(url); + return `${u.pathname}${u.search}`; + } + function doLogin(next) { let url = CARELINK_SECURITY_URL; logger.log('POST ' + url); @@ -127,9 +132,10 @@ var Client = exports.Client = function (options) { url, reqOptions({ jar: jar, - headers: { - Host: getHost(url), - }, + uri: url, + host: getHost(url), + path: getPath(url), + port: 443, form: { j_username: options.username, j_password: options.password, @@ -147,9 +153,10 @@ var Client = exports.Client = function (options) { url, reqOptions({ jar: jar, - headers: { - Host: getHost(url), - }, + uri: url, + host: getHost(url), + path: getPath(url), + port: 443, }), checkResponseThen(next) ); @@ -177,12 +184,12 @@ var Client = exports.Client = function (options) { logger.log('GET ' + url); request.get( - url, reqOptions({ jar: jar, - headers: { - Host: getHost(url), - }, + uri: url, + host: getHost(url), + path: getPath(url), + port: 443, }), checkResponseThen(next) ); @@ -197,9 +204,10 @@ var Client = exports.Client = function (options) { url, reqOptions({ jar: jar, - headers: { - Host: getHost(url), - }, + uri: url, + host: getHost(url), + path: getPath(url), + port: 443, }), checkResponseThen(next) ); @@ -217,9 +225,10 @@ var Client = exports.Client = function (options) { reqOptions({ jar: jar, gzip: true, - headers: { - host: getHost(url), - }, + uri: url, + host: getHost(url), + path: getPath(url), + port: 443, form: { sessionID: query.sessionID, sessionData: query.sessionData, @@ -249,9 +258,10 @@ var Client = exports.Client = function (options) { url, reqOptions({ jar: jar, - headers: { - Host: getHost(url), - }, + uri: url, + host: getHost(url), + path: getPath(url), + port: 443, form: { action: "consent", sessionID: ps.sessionID, @@ -273,9 +283,10 @@ var Client = exports.Client = function (options) { url, reqOptions({ jar: jar, - headers: { - Host: getHost(url), - }, + uri: url, + host: getHost(url), + path: getPath(url), + port: 443, }), checkResponseThen(next) ); @@ -291,8 +302,11 @@ var Client = exports.Client = function (options) { jar: jar, gzip: true, json: true, + uri: url, + host: getHost(url), + path: getPath(url), + port: 443, headers: { - Host: getHost(url), Authorization: "Bearer " + _.get(getCookie(CARELINKEU_TOKEN_COOKIE), 'value', ''), }, }), @@ -317,8 +331,11 @@ var Client = exports.Client = function (options) { var reqO = { jar: jar, gzip: true, + uri: url, + host: getHost(url), + path: getPath(url), + port: 443, headers: { - Host: getHost(url), }, }; if (CARELINK_EU) { From 890371faf0992c628ba8625d24084ce1b1ab36ff Mon Sep 17 00:00:00 2001 From: Frigyes Bartha Date: Tue, 30 Jun 2020 21:45:04 +0200 Subject: [PATCH 08/32] process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0; --- carelink.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/carelink.js b/carelink.js index 31ae258..fac3905 100644 --- a/carelink.js +++ b/carelink.js @@ -9,6 +9,8 @@ var _ = require('lodash'), var logger = require('./logger'); +process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0; + var MMCONNECT_SERVER = process.env['MMCONNECT_SERVER']; var CARELINK_EU = MMCONNECT_SERVER === 'EU'; var MMCONNECT_SERVERNAME = process.env['MMCONNECT_SERVERNAME']; From 37160d0f0e57f9dc6c34966c47cc90f25a7b5562 Mon Sep 17 00:00:00 2001 From: Frigyes Bartha Date: Tue, 30 Jun 2020 21:59:47 +0200 Subject: [PATCH 09/32] test --- carelink.js | 46 ++++++++-------------------------------------- 1 file changed, 8 insertions(+), 38 deletions(-) diff --git a/carelink.js b/carelink.js index fac3905..d55e27a 100644 --- a/carelink.js +++ b/carelink.js @@ -43,8 +43,8 @@ function reqOptions(extra) { var defaults = { jar: true, followRedirect: false, - rejectUnauthorized: false, - changeOrigin: true, + //rejectUnauthorized: false, + //changeOrigin: true, headers: { //Host: carelinkServerAddress, Connection: 'keep-alive', @@ -78,6 +78,11 @@ function responseAsError(response) { function checkResponseThen(fn) { return function (err, response) { err = err || responseAsError(response); + + if (err) { + let x = 1; + } + fn.apply(this, [err].concat(Array.prototype.slice.call(arguments, 1))); }; } @@ -134,10 +139,6 @@ var Client = exports.Client = function (options) { url, reqOptions({ jar: jar, - uri: url, - host: getHost(url), - path: getPath(url), - port: 443, form: { j_username: options.username, j_password: options.password, @@ -155,10 +156,6 @@ var Client = exports.Client = function (options) { url, reqOptions({ jar: jar, - uri: url, - host: getHost(url), - path: getPath(url), - port: 443, }), checkResponseThen(next) ); @@ -186,12 +183,9 @@ var Client = exports.Client = function (options) { logger.log('GET ' + url); request.get( + url, reqOptions({ jar: jar, - uri: url, - host: getHost(url), - path: getPath(url), - port: 443, }), checkResponseThen(next) ); @@ -206,10 +200,6 @@ var Client = exports.Client = function (options) { url, reqOptions({ jar: jar, - uri: url, - host: getHost(url), - path: getPath(url), - port: 443, }), checkResponseThen(next) ); @@ -227,10 +217,6 @@ var Client = exports.Client = function (options) { reqOptions({ jar: jar, gzip: true, - uri: url, - host: getHost(url), - path: getPath(url), - port: 443, form: { sessionID: query.sessionID, sessionData: query.sessionData, @@ -260,10 +246,6 @@ var Client = exports.Client = function (options) { url, reqOptions({ jar: jar, - uri: url, - host: getHost(url), - path: getPath(url), - port: 443, form: { action: "consent", sessionID: ps.sessionID, @@ -285,10 +267,6 @@ var Client = exports.Client = function (options) { url, reqOptions({ jar: jar, - uri: url, - host: getHost(url), - path: getPath(url), - port: 443, }), checkResponseThen(next) ); @@ -304,10 +282,6 @@ var Client = exports.Client = function (options) { jar: jar, gzip: true, json: true, - uri: url, - host: getHost(url), - path: getPath(url), - port: 443, headers: { Authorization: "Bearer " + _.get(getCookie(CARELINKEU_TOKEN_COOKIE), 'value', ''), }, @@ -333,10 +307,6 @@ var Client = exports.Client = function (options) { var reqO = { jar: jar, gzip: true, - uri: url, - host: getHost(url), - path: getPath(url), - port: 443, headers: { }, }; From cd4c3cb19583e7e98295c240105248c7e3e81a1e Mon Sep 17 00:00:00 2001 From: Frigyes Bartha Date: Tue, 30 Jun 2020 22:06:08 +0200 Subject: [PATCH 10/32] test2 --- carelink.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/carelink.js b/carelink.js index d55e27a..6cc6e74 100644 --- a/carelink.js +++ b/carelink.js @@ -43,7 +43,7 @@ function reqOptions(extra) { var defaults = { jar: true, followRedirect: false, - //rejectUnauthorized: false, + rejectUnauthorized: false, //changeOrigin: true, headers: { //Host: carelinkServerAddress, diff --git a/package.json b/package.json index e80fc6c..ea879ae 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ }, "license": "MIT", "engines": { - "node": "^10.14.1 || ^8.15.1", + "node": "^10.15.2 || ^8.15.1", "npm": "^6.4.1" }, "dependencies": { From b1ca72f2fa7edf1b77faab8b5d9d34c43e3f4aa7 Mon Sep 17 00:00:00 2001 From: Frigyes Bartha Date: Tue, 30 Jun 2020 22:10:35 +0200 Subject: [PATCH 11/32] test --- carelink.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/carelink.js b/carelink.js index 6cc6e74..e2e4a5c 100644 --- a/carelink.js +++ b/carelink.js @@ -44,7 +44,8 @@ function reqOptions(extra) { jar: true, followRedirect: false, rejectUnauthorized: false, - //changeOrigin: true, + //secure: false, + changeOrigin: true, headers: { //Host: carelinkServerAddress, Connection: 'keep-alive', From be18fd8049faef8f8d80feb71aedecea3f2fe81f Mon Sep 17 00:00:00 2001 From: Frigyes Bartha Date: Tue, 30 Jun 2020 22:21:04 +0200 Subject: [PATCH 12/32] checkServerIdentity --- carelink.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/carelink.js b/carelink.js index e2e4a5c..d1be10b 100644 --- a/carelink.js +++ b/carelink.js @@ -53,6 +53,9 @@ function reqOptions(extra) { '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' + }, + checkServerIdentity: function (host, cert) { + return undefined; } }; return _.merge(defaults, extra); From d13accbeabe29c921f95285c02da19e52ddb4bbd Mon Sep 17 00:00:00 2001 From: Frigyes Bartha Date: Tue, 30 Jun 2020 22:33:49 +0200 Subject: [PATCH 13/32] why? --- carelink.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/carelink.js b/carelink.js index d1be10b..499ee05 100644 --- a/carelink.js +++ b/carelink.js @@ -43,7 +43,7 @@ function reqOptions(extra) { var defaults = { jar: true, followRedirect: false, - rejectUnauthorized: false, + //rejectUnauthorized: false, //secure: false, changeOrigin: true, headers: { @@ -55,6 +55,8 @@ function reqOptions(extra) { 'Accept-Language': 'en-US,en;q=0.8' }, checkServerIdentity: function (host, cert) { + /*if (host != cert.subject.CN) + return 'Incorrect server identity';// Return error in case of failed checking.*/ return undefined; } }; From 4f41c402088ffea52543ef670ebfdb8dc4f2d6bd Mon Sep 17 00:00:00 2001 From: Frigyes Bartha Date: Tue, 30 Jun 2020 22:38:45 +0200 Subject: [PATCH 14/32] working --- carelink.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/carelink.js b/carelink.js index 499ee05..17f3817 100644 --- a/carelink.js +++ b/carelink.js @@ -54,11 +54,9 @@ function reqOptions(extra) { 'Accept-Encoding': 'gzip,deflate,sdch', 'Accept-Language': 'en-US,en;q=0.8' }, - checkServerIdentity: function (host, cert) { - /*if (host != cert.subject.CN) - return 'Incorrect server identity';// Return error in case of failed checking.*/ + /*checkServerIdentity: function (host, cert) { return undefined; - } + }*/ }; return _.merge(defaults, extra); } @@ -85,10 +83,6 @@ function checkResponseThen(fn) { return function (err, response) { err = err || responseAsError(response); - if (err) { - let x = 1; - } - fn.apply(this, [err].concat(Array.prototype.slice.call(arguments, 1))); }; } From 424e6ae5844f2736dc7739b1140e1a6451c59fe1 Mon Sep 17 00:00:00 2001 From: Frigyes Bartha Date: Tue, 30 Jun 2020 23:12:10 +0200 Subject: [PATCH 15/32] refactor for sessonId --- carelink.js | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/carelink.js b/carelink.js index 17f3817..932067a 100644 --- a/carelink.js +++ b/carelink.js @@ -161,20 +161,6 @@ var Client = exports.Client = function (options) { ); } - 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; - } - function doLoginEu1(next) { let url = urllib.parse(CARELINKEU_LOGIN_URL); var query = _.merge(qs.parse(url.query), CARELINKEU_LOGIN_LOCALE); @@ -232,15 +218,17 @@ var Client = exports.Client = function (options) { } function doLoginEu4(response, next) { - const regexUrl = /(
Date: Wed, 1 Jul 2020 12:41:08 +0200 Subject: [PATCH 16/32] refactor with axios --- carelink.js | 422 +++++++++++++++----------------------------- npm-shrinkwrap.json | 93 ++++++++-- package.json | 5 +- 3 files changed, 224 insertions(+), 296 deletions(-) diff --git a/carelink.js b/carelink.js index 932067a..20e7763 100644 --- a/carelink.js +++ b/carelink.js @@ -2,10 +2,11 @@ "use strict"; var _ = require('lodash'), - common = require('common'), - qs = require('qs'), + axios = require('axios').default, + axiosCookieJarSupport = require('axios-cookiejar-support').default, + tough = require('tough-cookie'), urllib = require('url'), - request = require('request'); + qs = require('qs'); var logger = require('./logger'); @@ -23,7 +24,6 @@ var CARELINKEU_LOGIN_LOCALE = { country: DEFAULT_COUNTRYCODE, lang: DEFAULT_LANG var DEFAULT_MAX_RETRY_DURATION = module.exports.defaultMaxRetryDuration = 512; var carelinkServerAddress = MMCONNECT_SERVERNAME || (CARELINK_EU ? "carelink.minimed.eu" : "carelink.minimed.com"); -var CARELINKEU_SERVER_ADDRESS = 'https://' + carelinkServerAddress; 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='; @@ -39,79 +39,41 @@ 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, - //secure: 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' - }, - /*checkServerIdentity: function (host, cert) { - return undefined; - }*/ - }; - 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; - } -} - -function checkResponseThen(fn) { - return function (err, response) { - err = err || responseAsError(response); - - fn.apply(this, [err].concat(Array.prototype.slice.call(arguments, 1))); - }; -} - -function retryDurationOnAttempt(n) { - return Math.pow(2, n); -} - -function totalDurationAfterNextRetry(n) { - var sum = 0; - for (var i = 0; i <= n; i++) { - sum += retryDurationOnAttempt(i); - } - return sum; -} - var Client = exports.Client = function (options) { if (!(this instanceof Client)) { return new Client(arguments[0]); } - var jar = request.jar(); + axiosCookieJarSupport(axios); + var cookieJar = new tough.CookieJar(); + axios.defaults.jar = new tough.CookieJar(); + axios.defaults.maxRedirects = 0; + axios.defaults.withCredentials = true; + 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); + } + }); if (options.maxRetryDuration === undefined) { options.maxRetryDuration = DEFAULT_MAX_RETRY_DURATION; } 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) { @@ -122,274 +84,174 @@ var Client = exports.Client = function (options) { return _.find(getCookies(), {key: cookieName}); } - function getHost(url) { - return new URL(url).host; + 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 getPath(url) { - let u = new URL(url); - return `${u.pathname}${u.search}`; - } - - function doLogin(next) { - let url = CARELINK_SECURITY_URL; - logger.log('POST ' + url); - - request.post( - url, - reqOptions({ - jar: jar, - form: { - j_username: options.username, - j_password: options.password, - j_character_encoding: "UTF-8" - }, - }), - checkResponseThen(next) - ); + async function doFetchCookie() { + return await axios.get(CARELINK_AFTER_LOGIN_URL); } - function doFetchCookie(response, next) { - let url = CARELINK_AFTER_LOGIN_URL; - logger.log('GET ' + url); - request.get( - url, - reqOptions({ - jar: jar, - }), - checkResponseThen(next) - ); - } - - function doLoginEu1(next) { + 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); + return await axios.get(url); + } - request.get( - url, - reqOptions({ - jar: jar, - }), - checkResponseThen(next) - ); + async function doLoginEu2(response) { + return await axios.get(response.headers.location); } - function doLoginEu2(response, next) { - let url = response.headers.location; + async function doLoginEu3(response) { + let uri = new URL(response.headers.location); + let uriParam = uri.searchParams; - logger.log('GET ' + url); + let url = `${uri.origin}${uri.pathname}?locale=${uriParam.get('locale')}&countrycode=${uriParam.get('countrycode')}`; - request.get( - url, - reqOptions({ - jar: jar, - }), - checkResponseThen(next) - ); - } + response = await axios.post(url, qs.stringify({ + sessionID: uriParam.get('sessionID'), + sessionData: uriParam.get('sessionData'), + locale: "en", + action: "login", + username: options.username, + password: options.password, + actionButton: "Log in", + })); - function doLoginEu3(response, next) { - let uri = response.headers.location; - let url = urllib.parse(uri); - let query = qs.parse(url.query); - url = urllib.format(_.merge(url, {search: null, query: CARELINKEU_LOGIN_LOCALE})); - logger.log('POST ' + url); - - request.post( - url, - reqOptions({ - jar: jar, - gzip: true, - form: { - sessionID: query.sessionID, - sessionData: query.sessionData, - locale: "en", - action: "login", - username: options.username, - password: options.password, - actionButton: "Log in", - } - }), - checkResponseThen(next) - ); + if (_.get(response, 'data', '').includes(uri.pathname)) + throw new Error('Carelink invalid username or password'); + + return response; } - function doLoginEu4(response, next) { + async function doLoginEu4(response) { let regex = /(= 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.get(url, config); } - function parseData(response, next) { - var parsed; - try { - parsed = JSON.parse(response.body); - } catch (e) { - next(e); - } - next(null, parsed); - } - - function checkLogin(next) { + async function checkLogin() { 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 (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() + 10 * 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) { + try { + let maxRetry = 3; + for (let i = 1; i <= maxRetry; i++) { + await checkLogin(); + try { + let response = await getConnectData(); + callback(null, response.data); + return; + } catch (e1) { + if (i === maxRetry) + throw e1; + + if (e1.response && e1.response.status === 401) { + // reauth + cookieJar.removeAllCookiesSync(); + } + + let timeout = retryDurationOnAttempt(i); + await sleep(1000 * timeout); + } + } + + throw new Error('Failed to download Carelink data'); + } catch (e) { + callback(`${e.toString()}\nstack: ${e.stack}`, null); + } } return { diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index a47962e..88726f3 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -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 ea879ae..350ac99 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,12 @@ "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", From aef4bf62176b35e405267edfe6ebdbd589e02801 Mon Sep 17 00:00:00 2001 From: Ben West Date: Thu, 2 Jul 2020 11:56:07 -0700 Subject: [PATCH 17/32] 1.4.0 --- npm-shrinkwrap.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 88726f3..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": { diff --git a/package.json b/package.json index 350ac99..3d09278 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": { From 1e2ef7a50750b336c0a3869eb917c8b05df7b6db Mon Sep 17 00:00:00 2001 From: Frigyes Bartha Date: Sat, 4 Jul 2020 17:13:33 +0200 Subject: [PATCH 18/32] connection test for EU and US server --- test/connectEu.js | 29 +++++++++++++++++++++++++++++ test/connectUs.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 test/connectEu.js create mode 100644 test/connectUs.js 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..b1c4196 --- /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('connectEu()', 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(); + } + }) + }); +}); From 84f1a6fc765db88ea031931554422fda44478a79 Mon Sep 17 00:00:00 2001 From: Frigyes Bartha Date: Sat, 4 Jul 2020 17:14:15 +0200 Subject: [PATCH 19/32] missing function fix --- carelink.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/carelink.js b/carelink.js index 20e7763..7ac9d0f 100644 --- a/carelink.js +++ b/carelink.js @@ -65,6 +65,10 @@ var Client = exports.Client = function (options) { options.maxRetryDuration = DEFAULT_MAX_RETRY_DURATION; } + function retryDurationOnAttempt(n) { + return Math.pow(2, n); + } + function getCookies() { let cookies = []; axios.defaults.jar.store.getAllCookies(function (err, cookieArray) { From 1bf30be6e3c2841bcffed5695e7bf29923198f07 Mon Sep 17 00:00:00 2001 From: Frigyes Bartha Date: Sat, 4 Jul 2020 17:14:37 +0200 Subject: [PATCH 20/32] callback enh --- carelink.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/carelink.js b/carelink.js index 7ac9d0f..dd309e9 100644 --- a/carelink.js +++ b/carelink.js @@ -230,14 +230,15 @@ var Client = exports.Client = function (options) { } async function fetch(callback) { + let data = null; + let error = null; try { let maxRetry = 3; for (let i = 1; i <= maxRetry; i++) { await checkLogin(); try { - let response = await getConnectData(); - callback(null, response.data); - return; + data = (await getConnectData()).data; + break; } catch (e1) { if (i === maxRetry) throw e1; @@ -251,10 +252,10 @@ var Client = exports.Client = function (options) { await sleep(1000 * timeout); } } - - throw new Error('Failed to download Carelink data'); } catch (e) { - callback(`${e.toString()}\nstack: ${e.stack}`, null); + error = `${e.toString()}\nstack: ${e.stack}`; + } finally { + callback(error, data); } } From 2a86281766ba2a34a1736cbe5232f8d33a153353 Mon Sep 17 00:00:00 2001 From: Frigyes Date: Fri, 11 Dec 2020 09:04:38 +0100 Subject: [PATCH 21/32] axious timout --- carelink.js | 1 + 1 file changed, 1 insertion(+) diff --git a/carelink.js b/carelink.js index dd309e9..2c3a637 100644 --- a/carelink.js +++ b/carelink.js @@ -48,6 +48,7 @@ var Client = exports.Client = function (options) { var cookieJar = new tough.CookieJar(); axios.defaults.jar = new tough.CookieJar(); axios.defaults.maxRedirects = 0; + axios.defaults.timeout = 10 * 1000; axios.defaults.withCredentials = true; axios.interceptors.response.use(function (response) { // Do something with response data From 6585f7f4364e009e8f112ca5b77fa635475abaa2 Mon Sep 17 00:00:00 2001 From: Frigyes Date: Fri, 11 Dec 2020 09:04:55 +0100 Subject: [PATCH 22/32] Logging enh --- carelink.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/carelink.js b/carelink.js index 2c3a637..e82eed0 100644 --- a/carelink.js +++ b/carelink.js @@ -108,11 +108,12 @@ var Client = exports.Client = function (options) { var query = _.merge(qs.parse(url.query), CARELINKEU_LOGIN_LOCALE); url = urllib.format(_.merge(url, { search: null, query: query })); - logger.log('GET ' + url); + logger.log('EU login 1'); return await axios.get(url); } async function doLoginEu2(response) { + logger.log(`EU login 2 (url: ${response.headers.location})`); return await axios.get(response.headers.location); } @@ -122,6 +123,7 @@ var Client = exports.Client = function (options) { let url = `${uri.origin}${uri.pathname}?locale=${uriParam.get('locale')}&countrycode=${uriParam.get('countrycode')}`; + logger.log(`EU login 3 (url: ${url})`); response = await axios.post(url, qs.stringify({ sessionID: uriParam.get('sessionID'), sessionData: uriParam.get('sessionData'), @@ -139,6 +141,7 @@ var Client = exports.Client = function (options) { } async function doLoginEu4(response) { + let regex = /( Date: Fri, 11 Dec 2020 13:37:19 +0100 Subject: [PATCH 23/32] User agent not axios (refresh token not working with default) minor changes --- carelink.js | 77 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/carelink.js b/carelink.js index e82eed0..ef5d7a9 100644 --- a/carelink.js +++ b/carelink.js @@ -50,6 +50,9 @@ var Client = exports.Client = function (options) { 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; @@ -89,6 +92,19 @@ var Client = exports.Client = function (options) { return _.find(getCookies(), {key: cookieName}); } + function deleteCookies() { + return axios.defaults.jar.removeAllCookiesSync(); + } + + function removeCookie(domain, path, key) { + return axios.defaults.jar.store.removeCookie(domain, path, key, function () { + }); + } + + function setCookie(domain, path, key, value) { + axios.defaults.jar.setCookieSync(`${key}=${value}`, `https://${domain}${path}`); + } + async function doLogin() { return await axios.post( CARELINK_SECURITY_URL, @@ -108,6 +124,7 @@ var Client = exports.Client = function (options) { var query = _.merge(qs.parse(url.query), CARELINKEU_LOGIN_LOCALE); url = urllib.format(_.merge(url, { search: null, query: query })); + deleteCookies(); logger.log('EU login 1'); return await axios.get(url); } @@ -166,53 +183,47 @@ var Client = exports.Client = function (options) { async function doLoginEu5(response) { logger.log(`EU login 5 (url: ${response.headers.location})`); - return await axios.get(response.headers.location, {maxRedirects: 0}); + await axios.get(response.headers.location, {maxRedirects: 0}); + axios.defaults.headers.common = { + 'Authorization': `Bearer ${_.get(getCookie(CARELINKEU_TOKEN_COOKIE), 'value', '')}`, + 'User-Agent': null, + }; } async function refreshTokenEu() { logger.log('Refresh EU token'); - return await axios.post( - CARELINKEU_REFRESH_TOKEN_URL, - {}, - { - headers: { - Authorization: "Bearer " + _.get(getCookie(CARELINKEU_TOKEN_COOKIE), 'value', ''), - }, - }, - ).catch(async function (error) { - logger.log(`Refresh EU token failed (${error})`); - - if (error.response && error.response.status === 401) { - // Login again - await checkLogin(); - } else { - throw error; - } - }); + + removeCookie('carelink.minimed.eu', '/', 'codeVerifier') + + 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})`); + + await checkLogin(true); + }); } async function getConnectData() { var url = carelinkJsonUrlNow(); - logger.log('GET ' + url); - - var config = { - headers: {}, - }; - if (CARELINK_EU) { - config.headers.Authorization = "Bearer " + _.get(getCookie(CARELINKEU_TOKEN_COOKIE), 'value', ''); - } - - return await axios.get(url, config); + logger.log('GET data ' + url); + return await axios.get(url); } - async function checkLogin() { + async function checkLogin(relogin = false) { if (CARELINK_EU) { // EU - SSO method - if (haveCookie(CARELINKEU_TOKEN_COOKIE) || haveCookie(CARELINKEU_TOKENEXPIRE_COOKIE)) { + if (!relogin && haveCookie(CARELINKEU_TOKEN_COOKIE) || haveCookie(CARELINKEU_TOKENEXPIRE_COOKIE)) { let expire = new Date(Date.parse(_.get(getCookie(CARELINKEU_TOKENEXPIRE_COOKIE), 'value'))); // Refresh token if expires in 10 minutes - if (expire < new Date(Date.now() + 10 * 1000 * 60)) + if (expire < new Date(Date.now() + 6 * 1000 * 60)) await refreshTokenEu(); } else { logger.log('Logging in to CareLink'); @@ -249,6 +260,8 @@ var Client = exports.Client = function (options) { data = (await getConnectData()).data; break; } catch (e1) { + deleteCookies(); + if (i === maxRetry) throw e1; From 2dc48e61643e96808a450f7505a4b381b29ada48 Mon Sep 17 00:00:00 2001 From: Frigyes Date: Fri, 11 Dec 2020 13:37:34 +0100 Subject: [PATCH 24/32] dynamic interval --- run.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/run.js b/run.js index d9576a2..8fc6c7c 100644 --- a/run.js +++ b/run.js @@ -83,9 +83,16 @@ function uploadMaybe(items, endpoint, callback) { // does not do the same for created_at, so we need to de-dupe them here. var newDeviceStatuses = filterDeviceStatus(transformed.devicestatus); + // Calculate interval by the device next upload time + let interval = config.deviceInterval - (data.currentServerTime - data.lastMedicalDeviceDataUpdateServerTime); + if (interval > config.deviceInterval) + interval = config.deviceInterval; + + 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); + setTimeout(requestLoop, interval); }); }); } From 9c31fb36ea454b5e414fa4690463018d98f21299 Mon Sep 17 00:00:00 2001 From: Frigyes Date: Fri, 11 Dec 2020 16:40:47 +0100 Subject: [PATCH 25/32] Heroku debug --- Procfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Procfile b/Procfile index 3f566b6..fbdf8f8 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -worker: node run.js +web: node --inspect ./run.js \ No newline at end of file From 5b2a9a3d69184b8c63ddbdbd3a355bd6a1749de7 Mon Sep 17 00:00:00 2001 From: Frigyes Date: Fri, 11 Dec 2020 17:45:42 +0100 Subject: [PATCH 26/32] heroku debug 2 --- Procfile | 2 +- run.js | 55 +++++++++++++++++++++++++++++++------------------------ 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/Procfile b/Procfile index fbdf8f8..291db96 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: node --inspect ./run.js \ No newline at end of file +worker: node --inspect 9090 run.js \ No newline at end of file diff --git a/run.js b/run.js index 8fc6c7c..1e5c6ca 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,33 +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. - var newSgvs = filterSgvs(transformed.entries); + // 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. - var newDeviceStatuses = filterDeviceStatus(transformed.devicestatus); + // 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); - // Calculate interval by the device next upload time - let interval = config.deviceInterval - (data.currentServerTime - data.lastMedicalDeviceDataUpdateServerTime); - if (interval > config.deviceInterval) - interval = config.deviceInterval; + // Calculate interval by the device next upload time + let interval = config.deviceInterval - (data.currentServerTime - data.lastMedicalDeviceDataUpdateServerTime); + if (interval > config.deviceInterval) + interval = config.deviceInterval; - logger.log(`Next check ${Math.round(interval / 1000)}s later (at ${new Date(Date.now() + interval)})`) + 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, interval); + uploadMaybe(newSgvs, entriesUrl, function() { + uploadMaybe(newDeviceStatuses, devicestatusUrl, function() { + setTimeout(requestLoop, interval); + }); }); - }); - } - }); + } + }); + } catch (error) { + console.error(error); + setTimeout(requestLoop, config.deviceInterval); + } })(); From 2ef7f9ad4774553a67d9916e8cfe73ae9a3a645a Mon Sep 17 00:00:00 2001 From: Frigyes Date: Fri, 11 Dec 2020 19:56:52 +0100 Subject: [PATCH 27/32] revert debug --- Procfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Procfile b/Procfile index 291db96..113b834 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -worker: node --inspect 9090 run.js \ No newline at end of file +worker: node run.js \ No newline at end of file From c07f4eee16cabbb39bebfb35e8a597ae726d0bda Mon Sep 17 00:00:00 2001 From: Frigyes Date: Fri, 11 Dec 2020 22:10:06 +0100 Subject: [PATCH 28/32] fresh token error fix --- carelink.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/carelink.js b/carelink.js index ef5d7a9..9933c0f 100644 --- a/carelink.js +++ b/carelink.js @@ -205,7 +205,7 @@ var Client = exports.Client = function (options) { }) .catch(async function (error) { logger.log(`Refresh EU token failed (${error})`); - + deleteCookies(); await checkLogin(true); }); } @@ -219,7 +219,7 @@ var Client = exports.Client = function (options) { async function checkLogin(relogin = false) { if (CARELINK_EU) { // EU - SSO method - if (!relogin && haveCookie(CARELINKEU_TOKEN_COOKIE) || haveCookie(CARELINKEU_TOKENEXPIRE_COOKIE)) { + if (!relogin && (haveCookie(CARELINKEU_TOKEN_COOKIE) || haveCookie(CARELINKEU_TOKENEXPIRE_COOKIE))) { let expire = new Date(Date.parse(_.get(getCookie(CARELINKEU_TOKENEXPIRE_COOKIE), 'value'))); // Refresh token if expires in 10 minutes From 1ebcdd2f0f99be03a2043081eaa19fa0904be423 Mon Sep 17 00:00:00 2001 From: Frigyes Date: Fri, 11 Dec 2020 22:26:43 +0100 Subject: [PATCH 29/32] Request count safety check --- carelink.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/carelink.js b/carelink.js index 9933c0f..3157767 100644 --- a/carelink.js +++ b/carelink.js @@ -40,12 +40,13 @@ var carelinkJsonUrlNow = function () { }; var Client = exports.Client = function (options) { + let requestCount = 0; + if (!(this instanceof Client)) { return new Client(arguments[0]); } axiosCookieJarSupport(axios); - var cookieJar = new tough.CookieJar(); axios.defaults.jar = new tough.CookieJar(); axios.defaults.maxRedirects = 0; axios.defaults.timeout = 10 * 1000; @@ -65,6 +66,15 @@ var Client = exports.Client = function (options) { } }); + axios.interceptors.request.use((config) => { + requestCount++; + + if (requestCount > 10) + throw new Error("Request count exceeds the maximum in one fetch!"); + + return config; + }); + if (options.maxRetryDuration === undefined) { options.maxRetryDuration = DEFAULT_MAX_RETRY_DURATION; } @@ -250,6 +260,8 @@ var Client = exports.Client = function (options) { } async function fetch(callback) { + requestCount = 0; + let data = null; let error = null; try { From 2bf85cfe12875853815ddd77ded81db806d74f90 Mon Sep 17 00:00:00 2001 From: Frigyes Date: Sat, 12 Dec 2020 07:48:58 +0100 Subject: [PATCH 30/32] interval fix on stale data --- run.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run.js b/run.js index 1e5c6ca..1a49424 100644 --- a/run.js +++ b/run.js @@ -88,7 +88,7 @@ function uploadMaybe(items, endpoint, callback) { // Calculate interval by the device next upload time let interval = config.deviceInterval - (data.currentServerTime - data.lastMedicalDeviceDataUpdateServerTime); - if (interval > config.deviceInterval) + if (interval > config.deviceInterval || interval < 0) interval = config.deviceInterval; logger.log(`Next check ${Math.round(interval / 1000)}s later (at ${new Date(Date.now() + interval)})`) From a95976c2fdcee1178cc22aa7fc7503037486d51d Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 15 Dec 2020 11:35:19 -0800 Subject: [PATCH 31/32] typo in US test name, allow tests time to pass testing against international servers takes longer. --- package.json | 2 +- test/connectUs.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 3d09278..db1ac34 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "main": "index.js", "scripts": { "start": "node run.js", - "test": "node_modules/.bin/mocha" + "test": "node_modules/.bin/mocha --timeout 10000" }, "license": "MIT", "engines": { diff --git a/test/connectUs.js b/test/connectUs.js index b1c4196..f6b0ad3 100644 --- a/test/connectUs.js +++ b/test/connectUs.js @@ -9,7 +9,7 @@ var _ = require('lodash'), var carelink = require('../carelink.js'); -describe('connectEu()', function() { +describe('connectUS()', function() { var client = carelink.Client({ username: "nstestuss", password: "ournightscoutustest", From 7dcb25e6f51eeb925db4f4021c38a0bf389785e0 Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 15 Dec 2020 11:52:57 -0800 Subject: [PATCH 32/32] apparently not needed anymore --- carelink.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/carelink.js b/carelink.js index 3157767..6ec248a 100644 --- a/carelink.js +++ b/carelink.js @@ -10,8 +10,6 @@ var _ = require('lodash'), var logger = require('./logger'); -process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0; - var MMCONNECT_SERVER = process.env['MMCONNECT_SERVER']; var CARELINK_EU = MMCONNECT_SERVER === 'EU'; var MMCONNECT_SERVERNAME = process.env['MMCONNECT_SERVERNAME'];