diff --git a/package.json b/package.json index 36dde3b..b2dfa22 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,14 @@ "name": "Mark Seminatore", "url": "https://github.com/mseminatore/TeslaJS" }, + "name": "teslajs", + "version": "4.9.8", + "description": "Full-featured Tesla REST API NodeJS package", + "dependencies": { + "promise": "^8.0.3", + "request": "^2.88.2", + "ws": "^7.2.1" + }, "bugs": { "url": "https://github.com/mseminatore/TeslaJS/issues" }, diff --git a/src/auth.js b/src/auth.js index 6f11f7b..0033689 100644 --- a/src/auth.js +++ b/src/auth.js @@ -119,20 +119,7 @@ exports.login = function login(credentials, callback) { redirect_uri: url.protocol + '//' + url.host + url.pathname } }); - }).then(function (result) { - return req({ - method: 'POST', - url: 'https://owner-api.teslamotors.com/oauth/token', - headers: { - Authorization: 'Bearer ' + result.body.access_token - }, - json: true, - body: { - grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', - client_id: _0x2dc0[0] - } - }); - }).then(function (result) { + }).then(bearerForAccessToken).then(function (result) { callback(null, result.response, result.body); }).catch(function (error) { callback(error); @@ -208,6 +195,24 @@ function mfaVerify(transactionId, host, referer, mfaPassCode, mfaDeviceName) { }); } +function bearerForAccessToken(bearerResult) { + return req({ + method: 'POST', + url: 'https://owner-api.teslamotors.com/oauth/token', + headers: { + authorization: 'bearer ' + bearerResult.body.access_token + }, + json: true, + body: { + grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', + client_id: _0x2dc0[0] + } + }).then(function (result) { + result.body.refresh_token = bearerResult.body.refresh_token; + return result; + }); +} + function generateCodeVerifier() { // Tesla might use something more sophisticated, but in my experience it's a 112-char alphanumeric string so let's just do that var chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'; @@ -228,6 +233,24 @@ function generateCodeChallenge(verifier) { .replace(/\//g, '_'); } +exports.refresh = function refresh(refresh_token, callback) { + req({ + method: 'POST', + url: 'https://auth.tesla.com/oauth2/v3/token', + json: true, + body: { + "grant_type": "refresh_token", + "client_id": "ownerapi", + "refresh_token": refresh_token, + "scope": "openid email offline_access" + } + }).then(bearerForAccessToken).then(function (result) { + callback(null, result.response, result.body); + }).catch(function (error) { + callback(error); + }); +} + function req(parameters) { return new Promise(function (resolve, reject) { request(parameters, function (error, response, body) { diff --git a/teslajs.js b/teslajs.js index e5f50a8..9d4c3b2 100644 --- a/teslajs.js +++ b/teslajs.js @@ -404,22 +404,17 @@ exports.refreshToken = function refreshToken(refresh_token, callback) { return; } - var req = { - method: 'POST', - url: portalBaseURI + '/oauth/token', - body: { - "grant_type": "refresh_token", - "refresh_token": refresh_token - } - }; - - log(API_REQUEST_LEVEL, "\nRequest: " + JSON.stringify(req)); - - request(req, function (error, response, body) { + + require('./src/auth').refresh(refresh_token, function (error, response, body) { log(API_RESPONSE_LEVEL, "\nResponse: " + body); - callback(error, { error: error, response: response, body: JSON.stringify(body), authToken: body.access_token, refreshToken: body.refresh_token }); + if (error) { + callback(error) + } + else { + callback(error, { error: error, response: response, body: JSON.stringify(body), authToken: body.access_token, refreshToken: body.refresh_token }); + } log(API_RETURN_LEVEL, "TeslaJS.refreshToken() completed."); }); @@ -2040,6 +2035,72 @@ exports.solarStatus = function solarStatus(options, callback) { exports.solarStatusAsync = Promise.denodeify(exports.solarStatus); + +/** + * Return historical data for solar installation + * @function solarHistory + * @param {optionsType} options - options object + * @param {string} period - time period + * @param {string} kind - kind (i.e. 'energy') + * @param {nodeBack} callback - Node-style callback + * @returns {solarStatus} solarHistory JSON data + */ +exports.solarHistory = function solarHistory(options, period, kind, callback) { + log(API_CALL_LEVEL, "TeslaJS.solarHistory()"); + + // Default Values + callback = callback || function(err, solarHistory) { }; /* do nothing! */ + period = period || "day"; + kind = kind || "energy"; + + var req = { + method: "GET", + url: portalBaseURI + "/api/1/energy_sites/" + options.siteId + "/calendar_history?kind="+kind+"&period="+period, + headers: { + Authorization: "Bearer " + options.authToken, + "Content-Type": "application/json; charset=utf-8" + } + }; + + log(API_REQUEST_LEVEL, "\nRequest: " + JSON.stringify(req)); + + request(req, function(error, response, body) { + if (error) { + log(API_ERR_LEVEL, error); + return callback(error, null); + } + + if (response.statusCode != 200) { + return callback(response.statusMessage, null); + } + + log(API_BODY_LEVEL, "\nBody: " + JSON.stringify(body)); + log(API_RESPONSE_LEVEL, "\nResponse: " + JSON.stringify(response)); + + try { + body = body.response; + + callback(null, body); + } catch (e) { + log(API_ERR_LEVEL, "Error parsing solarHistory response"); + callback(e, null); + } + + log(API_RETURN_LEVEL, "\nGET request: " + "/solarHistory" + " completed."); + }); +}; + +/** + * Return historical data for solar installation + * @function solarHistoryAsync + * @param {optionsType} options - options object + * @param {string} period - time period + * @param {string} kind - kind (i.e. 'energy') + * @param {nodeBack} callback - Node-style callback + * @returns {Promise} solarHistory JSON data + */ +exports.solarHistoryAsync = Promise.denodeify(exports.solarHistory); + /* // // [Alpha impl] Not yet supported