@eiannone/tesla-api 1.9.1 → 1.13.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/.vscode/launch.json +17 -0
- package/TeslaApi.js +103 -42
- package/TeslaStream.js +1 -1
- package/examples/get-id.js +4 -0
- package/examples/refresh-token.js +11 -0
- package/package.json +1 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Usare IntelliSense per informazioni sui possibili attributi.
|
|
3
|
+
// Al passaggio del mouse vengono visualizzate le descrizioni degli attributi esistenti.
|
|
4
|
+
// Per altre informazioni, visitare: https://go.microsoft.com/fwlink/?linkid=830387
|
|
5
|
+
"version": "0.2.0",
|
|
6
|
+
"configurations": [
|
|
7
|
+
{
|
|
8
|
+
"type": "pwa-node",
|
|
9
|
+
"request": "launch",
|
|
10
|
+
"name": "Get-id",
|
|
11
|
+
"skipFiles": [
|
|
12
|
+
"<node_internals>/**"
|
|
13
|
+
],
|
|
14
|
+
"program": "${workspaceFolder}\\examples\\get-id.js"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
package/TeslaApi.js
CHANGED
|
@@ -21,8 +21,8 @@ class ApiError extends Error {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
class TeslaApi {
|
|
24
|
-
constructor(access_token = null,
|
|
25
|
-
this.vid =
|
|
24
|
+
constructor(access_token = null, id = null, refresh_token = null) {
|
|
25
|
+
this.vid = id;
|
|
26
26
|
this.token = access_token;
|
|
27
27
|
this.refresh_token = refresh_token;
|
|
28
28
|
this.timeout = 10000;
|
|
@@ -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:
|
|
60
|
+
headers: headers,
|
|
54
61
|
timeout: this.timeout,
|
|
55
62
|
method: method
|
|
56
63
|
}, res => {
|
|
@@ -89,46 +96,48 @@ 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
|
}
|
|
95
103
|
|
|
96
104
|
async getVehicles() {
|
|
97
|
-
return this.#apiCall(
|
|
105
|
+
return this.#apiCall();
|
|
98
106
|
}
|
|
99
107
|
|
|
100
|
-
async getVehicle(
|
|
101
|
-
return this.#apiCall((
|
|
108
|
+
async getVehicle(id = null) {
|
|
109
|
+
return this.#apiCall((id == null)? this.vid : id);
|
|
102
110
|
}
|
|
103
111
|
|
|
104
|
-
async getVehicleData(
|
|
105
|
-
const vid = (
|
|
112
|
+
async getVehicleData(id = null) {
|
|
113
|
+
const vid = (id == null)? this.vid : id;
|
|
106
114
|
return this.#apiCall(vid + "/vehicle_data");
|
|
107
115
|
}
|
|
108
116
|
|
|
109
|
-
async getChargeState(
|
|
110
|
-
const vid = (
|
|
117
|
+
async getChargeState(id = null) {
|
|
118
|
+
const vid = (id == null)? this.vid : id;
|
|
111
119
|
return this.#apiCall(vid + "/data_request/charge_state");
|
|
112
120
|
}
|
|
113
121
|
|
|
114
|
-
async wakeUp(
|
|
115
|
-
const vid = (
|
|
122
|
+
async wakeUp(id = null) {
|
|
123
|
+
const vid = (id == null)? this.vid : id;
|
|
116
124
|
return this.#apiCall(vid + "/wake_up", "POST");
|
|
117
125
|
}
|
|
118
126
|
|
|
119
|
-
async command(command,
|
|
120
|
-
const vid = (
|
|
121
|
-
return this.#apiCall(vid + "/command/" + command, "POST");
|
|
127
|
+
async command(command, params = undefined, id = null) {
|
|
128
|
+
const vid = (id == null)? this.vid : id;
|
|
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', {
|
|
128
136
|
headers: {
|
|
129
137
|
'user-agent': "TeslaEma",
|
|
138
|
+
'Authorization': "Bearer " + bearer_token,
|
|
130
139
|
'Content-Type': 'application/json',
|
|
131
|
-
'Content-Length':
|
|
140
|
+
'Content-Length': postData.length
|
|
132
141
|
},
|
|
133
142
|
timeout: 30000,
|
|
134
143
|
method: 'POST'
|
|
@@ -157,9 +166,50 @@ class TeslaApi {
|
|
|
157
166
|
// - ENOTFOUND
|
|
158
167
|
reject(new ApiError(e.message + " ("+e.code+")", ApiError.NETWORK));
|
|
159
168
|
});
|
|
160
|
-
req.write(
|
|
169
|
+
req.write(postData);
|
|
161
170
|
req.end();
|
|
162
|
-
});
|
|
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', {
|
|
178
|
+
headers: {
|
|
179
|
+
'user-agent': "TeslaEma",
|
|
180
|
+
'Content-Type': 'application/json',
|
|
181
|
+
'Content-Length': postData.length
|
|
182
|
+
},
|
|
183
|
+
timeout: 30000,
|
|
184
|
+
method: 'POST'
|
|
185
|
+
}, res => {
|
|
186
|
+
if (res.statusCode > 199 && res.statusCode < 300) {
|
|
187
|
+
res.setEncoding('utf8');
|
|
188
|
+
let rawData = '';
|
|
189
|
+
res.on('data', chunk => { rawData += chunk; });
|
|
190
|
+
res.on('end', () => {
|
|
191
|
+
try {
|
|
192
|
+
resolve(JSON.parse(rawData));
|
|
193
|
+
} catch(err) {
|
|
194
|
+
reject(new ApiError(err));
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
} else {
|
|
198
|
+
let errMsg = res.statusMessage + " ("+res.statusCode+")";
|
|
199
|
+
reject(new ApiError(errMsg, this.#decodeStatus(res.statusCode)));
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
req.on('error', e => {
|
|
203
|
+
// Error code examples:
|
|
204
|
+
// - EAI_AGAIN (DNS lookup timeout)
|
|
205
|
+
// - ECONNRESET
|
|
206
|
+
// - ECONNREFUSED
|
|
207
|
+
// - ENOTFOUND
|
|
208
|
+
reject(new ApiError(e.message + " ("+e.code+")", ApiError.NETWORK));
|
|
209
|
+
});
|
|
210
|
+
req.write(postData);
|
|
211
|
+
req.end();
|
|
212
|
+
});
|
|
163
213
|
}
|
|
164
214
|
|
|
165
215
|
onTokenRefreh(callback) {
|
|
@@ -167,28 +217,39 @@ class TeslaApi {
|
|
|
167
217
|
}
|
|
168
218
|
|
|
169
219
|
async refreshToken(refresh_token) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
+
}
|
|
183
244
|
}
|
|
184
245
|
|
|
185
|
-
async
|
|
186
|
-
return this.#
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
246
|
+
async getId(vehicle_id) {
|
|
247
|
+
return this.#apiCall().then(vehicles => {
|
|
248
|
+
for (let v = 0; v < vehicles.length; v++) {
|
|
249
|
+
if (!vehicles[v].hasOwnProperty('vehicle_id') || !vehicles[v].hasOwnProperty('id_s')) continue;
|
|
250
|
+
if (vehicles[v].vehicle_id == vehicle_id) return vehicles[v].id_s;
|
|
251
|
+
}
|
|
252
|
+
throw new ApiError("Vehicle not found");
|
|
192
253
|
});
|
|
193
254
|
}
|
|
194
255
|
}
|
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");
|
|
@@ -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
|
+
});
|