@eiannone/tesla-api 1.9.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/TeslaApi.js CHANGED
@@ -47,10 +47,17 @@ class TeslaApi {
47
47
  }
48
48
  }
49
49
 
50
- async #apiCall(path, method = 'GET') {
51
- return new Promise((resolve, reject) => {
50
+ async #apiCall(path, method = 'GET', params = undefined) {
51
+ return new Promise((resolve, reject) => {
52
+ const postData = (typeof params != 'undefined')? JSON.stringify(params) : '';
53
+ let headers = { 'user-agent': "TeslaEma", 'Authorization': "Bearer " + this.token };
54
+ if (postData.length > 0) {
55
+ headers['Content-Type'] = 'application/json';
56
+ headers['Content-Length'] = postData.length;
57
+ }
58
+
52
59
  const req = request(BASE_URL + "/api/1/vehicles/" + path, {
53
- headers: { 'user-agent': "TeslaEma", 'Authorization': "Bearer " + this.token },
60
+ headers: headers,
54
61
  timeout: this.timeout,
55
62
  method: method
56
63
  }, res => {
@@ -89,6 +96,7 @@ class TeslaApi {
89
96
  // - ENOTFOUND
90
97
  reject(new ApiError(e.message + " ("+e.code+")", ApiError.NETWORK));
91
98
  });
99
+ if (postData.length > 0) req.write(postData);
92
100
  req.end();
93
101
  });
94
102
  }
@@ -116,19 +124,61 @@ class TeslaApi {
116
124
  return this.#apiCall(vid + "/wake_up", "POST");
117
125
  }
118
126
 
119
- async command(command, vehicle_id = null) {
127
+ async command(command, params = undefined, vehicle_id = null) {
120
128
  const vid = (vehicle_id == null)? this.vid : vehicle_id;
121
- return this.#apiCall(vid + "/command/" + command, "POST");
129
+ return this.#apiCall(vid + "/command/" + command, "POST", params);
122
130
  }
123
131
 
124
- async #oauthCall(params) {
125
- const post_data = JSON.stringify(params);
132
+ async #oauthCall(params, bearer_token) {
126
133
  return new Promise((resolve, reject) => {
134
+ const postData = JSON.stringify(params);
127
135
  const req = request(BASE_URL + '/oauth/token', {
136
+ headers: {
137
+ 'user-agent': "TeslaEma",
138
+ 'Authorization': "Bearer " + bearer_token,
139
+ 'Content-Type': 'application/json',
140
+ 'Content-Length': postData.length
141
+ },
142
+ timeout: 30000,
143
+ method: 'POST'
144
+ }, res => {
145
+ if (res.statusCode > 199 && res.statusCode < 300) {
146
+ res.setEncoding('utf8');
147
+ let rawData = '';
148
+ res.on('data', chunk => { rawData += chunk; });
149
+ res.on('end', () => {
150
+ try {
151
+ resolve(JSON.parse(rawData));
152
+ } catch(err) {
153
+ reject(new ApiError(err));
154
+ }
155
+ });
156
+ } else {
157
+ let errMsg = res.statusMessage + " ("+res.statusCode+")";
158
+ reject(new ApiError(errMsg, this.#decodeStatus(res.statusCode)));
159
+ }
160
+ });
161
+ req.on('error', e => {
162
+ // Error code examples:
163
+ // - EAI_AGAIN (DNS lookup timeout)
164
+ // - ECONNRESET
165
+ // - ECONNREFUSED
166
+ // - ENOTFOUND
167
+ reject(new ApiError(e.message + " ("+e.code+")", ApiError.NETWORK));
168
+ });
169
+ req.write(postData);
170
+ req.end();
171
+ });
172
+ }
173
+
174
+ async #oauthCall2(params) {
175
+ return new Promise((resolve, reject) => {
176
+ const postData = JSON.stringify(params);
177
+ const req = request('https://auth.tesla.com/oauth2/v3/token', {
128
178
  headers: {
129
179
  'user-agent': "TeslaEma",
130
180
  'Content-Type': 'application/json',
131
- 'Content-Length': post_data.length
181
+ 'Content-Length': postData.length
132
182
  },
133
183
  timeout: 30000,
134
184
  method: 'POST'
@@ -157,9 +207,9 @@ class TeslaApi {
157
207
  // - ENOTFOUND
158
208
  reject(new ApiError(e.message + " ("+e.code+")", ApiError.NETWORK));
159
209
  });
160
- req.write(post_data);
210
+ req.write(postData);
161
211
  req.end();
162
- });s
212
+ });
163
213
  }
164
214
 
165
215
  onTokenRefreh(callback) {
@@ -167,29 +217,30 @@ class TeslaApi {
167
217
  }
168
218
 
169
219
  async refreshToken(refresh_token) {
170
- return this.#oauthCall({'grant_type': "refresh_token", 'refresh_token': refresh_token})
171
- .then(async (resp) => {
172
- this.token = resp.access_token;
173
- this.refresh_token = resp.refresh_token;
174
- if (typeof this.cb_refreshToken == 'function') {
175
- this.cb_refreshToken(this.token, this.refresh_token);
176
- }
177
- return resp;
178
- })
179
- .catch(error => {
180
- if (error instanceof Error) error.message += " - Unable to refresh Token";
181
- throw error;
182
- });
183
- }
184
-
185
- async getTokens(email, password) {
186
- return this.#oauthCall({
187
- 'grant_type': "password",
188
- 'email': email,
189
- 'password': password,
190
- 'client_id': "81527cff06843c8634fdc09e8ac0abefb46ac849f38fe1e431c2ef2106796384",
191
- 'client_secret': "c7257eb71a564034f9419ee651c7d0e5f7aa6bfbd18bafb5c5c033b093bb2fa3"
192
- });
220
+ const payLoad = {
221
+ grant_type: 'refresh_token',
222
+ client_id: 'ownerapi',
223
+ refresh_token,
224
+ scope: 'openid email offline_access'
225
+ };
226
+ try {
227
+ let resp = await this.#oauthCall2(payLoad);
228
+ this.refresh_token = resp.refresh_token;
229
+ let oauth = await this.#oauthCall({
230
+ grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
231
+ client_id: '81527cff06843c8634fdc09e8ac0abefb46ac849f38fe1e431c2ef2106796384',
232
+ client_secret: 'c7257eb71a564034f9419ee651c7d0e5f7aa6bfbd18bafb5c5c033b093bb2fa3'
233
+ }, resp.access_token);
234
+ this.token = oauth.access_token;
235
+ if (typeof this.cb_refreshToken == 'function') {
236
+ this.cb_refreshToken(this.token, this.refresh_token);
237
+ }
238
+ return oauth;
239
+ }
240
+ catch(error) {
241
+ if (error instanceof Error) error.message += " - Unable to refresh Token";
242
+ throw error;
243
+ }
193
244
  }
194
245
  }
195
246
 
package/TeslaStream.js CHANGED
@@ -164,7 +164,7 @@ export default class TeslaStream extends EventEmitter {
164
164
  case "client_error":
165
165
  this.log("Client error: " + d.value, "error");
166
166
  this.emit('error', d.value);
167
- this.#reconnect();
167
+ if (d.value != "Can't validate token. ") this.#reconnect();
168
168
  break;
169
169
  default:
170
170
  this.log("Stream API error ["+d.error_type+"]: " + data, "error");
@@ -180,7 +180,7 @@ export default class TeslaStream extends EventEmitter {
180
180
  this.log("Websocket error: " + errMsg, "error");
181
181
  });
182
182
  this.ws.on('close', (code, reason) => {
183
- this.log("Websocket closed ("+ code + (reason? ': ' + reason : '') + ").");
183
+ this.log("Websocket closed ("+ code + ((reason != '')? `: ${reason}` : '') + ").");
184
184
  if (code == 1006 && this.state != CLOSING) this.reconnect = true; // Abnormal close
185
185
  if (this.checkTimeout != null) clearTimeout(this.checkTimeout);
186
186
  this.ws = null;
@@ -0,0 +1,11 @@
1
+ import {TeslaApi, ApiError} from '../TeslaApi.js';
2
+
3
+ const api = new TeslaApi();
4
+ api.refreshToken('1234')
5
+ .then(result => console.log(JSON.stringify(result)))
6
+ .catch(err => {
7
+ let reason = (err instanceof ApiError)? err.reason : ApiError.UNKNOWN;
8
+ const errMsg = (err instanceof Error)? err.message : err;
9
+ console.error("ApiError " + reason + ": " + errMsg);
10
+ process.exit(-1);
11
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eiannone/tesla-api",
3
- "version": "1.9.0",
3
+ "version": "1.12.0",
4
4
  "description": "Nodejs Tesla API",
5
5
  "type": "module",
6
6
  "main": "index.js",