homey-api 1.10.17 → 3.0.0-rc.2

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.
Files changed (53) hide show
  1. package/README.md +1 -1
  2. package/assets/types/homey-api.d.ts +47 -588
  3. package/assets/types/homey-api.private.d.ts +47 -648
  4. package/index.js +1 -1
  5. package/lib/APIErrorNotFound.js +20 -0
  6. package/lib/AthomCloudAPI/Homey.js +3 -1
  7. package/lib/EventEmitter.js +0 -6
  8. package/lib/HomeyAPI/HomeyAPI.js +49 -5
  9. package/lib/HomeyAPI/HomeyAPIErrorNotFound.js +21 -0
  10. package/lib/HomeyAPI/HomeyAPIV2/Manager.js +2 -575
  11. package/lib/HomeyAPI/HomeyAPIV2/ManagerDevices/Capability.js +20 -0
  12. package/lib/HomeyAPI/HomeyAPIV2/ManagerDevices/Device.js +18 -0
  13. package/lib/HomeyAPI/HomeyAPIV2/ManagerDevices.js +20 -3
  14. package/lib/HomeyAPI/HomeyAPIV2/ManagerDrivers/Driver.js +25 -0
  15. package/lib/HomeyAPI/HomeyAPIV2/ManagerDrivers.js +29 -0
  16. package/lib/HomeyAPI/HomeyAPIV2/ManagerFlow/AdvancedFlow.js +17 -0
  17. package/lib/HomeyAPI/HomeyAPIV2/ManagerFlow/Flow.js +34 -0
  18. package/lib/HomeyAPI/HomeyAPIV2/ManagerFlow/FlowCardAction.js +25 -0
  19. package/lib/HomeyAPI/HomeyAPIV2/ManagerFlow/FlowCardCondition.js +25 -0
  20. package/lib/HomeyAPI/HomeyAPIV2/ManagerFlow/FlowCardTrigger.js +25 -0
  21. package/lib/HomeyAPI/HomeyAPIV2/ManagerFlow.js +104 -0
  22. package/lib/HomeyAPI/HomeyAPIV2/ManagerFlowToken/FlowToken.js +24 -0
  23. package/lib/HomeyAPI/HomeyAPIV2/ManagerFlowToken.js +29 -0
  24. package/lib/HomeyAPI/HomeyAPIV2/ManagerInsights/Log.js +23 -0
  25. package/lib/HomeyAPI/HomeyAPIV2/ManagerInsights.js +29 -0
  26. package/lib/HomeyAPI/HomeyAPIV2.js +12 -716
  27. package/lib/HomeyAPI/HomeyAPIV3/Item.js +173 -2
  28. package/lib/HomeyAPI/HomeyAPIV3/Manager.js +531 -3
  29. package/lib/HomeyAPI/{HomeyAPIV2 → HomeyAPIV3/ManagerApps}/App.js +1 -1
  30. package/lib/HomeyAPI/{HomeyAPIV2 → HomeyAPIV3}/ManagerApps.js +4 -3
  31. package/lib/HomeyAPI/HomeyAPIV3/ManagerDevices/Capability.js +9 -0
  32. package/lib/HomeyAPI/{HomeyAPIV2 → HomeyAPIV3/ManagerDevices}/Device.js +78 -3
  33. package/lib/HomeyAPI/{HomeyAPIV2 → HomeyAPIV3/ManagerDevices}/DeviceCapability.js +3 -3
  34. package/lib/HomeyAPI/HomeyAPIV3/ManagerDevices.js +17 -0
  35. package/lib/HomeyAPI/HomeyAPIV3/ManagerDrivers/Driver.js +9 -0
  36. package/lib/HomeyAPI/HomeyAPIV3/ManagerDrivers.js +15 -0
  37. package/lib/HomeyAPI/HomeyAPIV3/ManagerFlow/AdvancedFlow.js +9 -0
  38. package/lib/HomeyAPI/HomeyAPIV3/ManagerFlow/Flow.js +9 -0
  39. package/lib/HomeyAPI/HomeyAPIV3/ManagerFlow/FlowCard.js +9 -0
  40. package/lib/HomeyAPI/HomeyAPIV3/ManagerFlow/FlowCardAction.js +9 -0
  41. package/lib/HomeyAPI/HomeyAPIV3/ManagerFlow/FlowCardCondition.js +9 -0
  42. package/lib/HomeyAPI/HomeyAPIV3/ManagerFlow/FlowCardTrigger.js +9 -0
  43. package/lib/HomeyAPI/HomeyAPIV3/ManagerFlow.js +12 -23
  44. package/lib/HomeyAPI/HomeyAPIV3/ManagerFlowToken/FlowToken.js +16 -0
  45. package/lib/HomeyAPI/HomeyAPIV3/ManagerFlowToken.js +15 -0
  46. package/lib/HomeyAPI/HomeyAPIV3/ManagerInsights/Log.js +9 -0
  47. package/lib/HomeyAPI/HomeyAPIV3/ManagerInsights.js +15 -0
  48. package/lib/HomeyAPI/HomeyAPIV3.js +728 -4
  49. package/lib/HomeyAPI/HomeyAPIV3Cloud.js +1 -1
  50. package/lib/HomeyAPI/HomeyAPIV3Local.js +1 -1
  51. package/package.json +1 -1
  52. package/lib/HomeyAPI/HomeyAPIApp.js +0 -127
  53. package/lib/HomeyAPI/HomeyAPIV2/Item.js +0 -177
@@ -1,734 +1,30 @@
1
1
  'use strict';
2
2
 
3
- const SocketIOClient = require('socket.io-client');
4
-
5
- const Manager = require('./HomeyAPIV2/Manager');
6
- const ManagerApps = require('./HomeyAPIV2/ManagerApps');
3
+ const HomeyAPIV3 = require('./HomeyAPIV3');
4
+ const ManagerFlow = require('./HomeyAPIV2/ManagerFlow');
5
+ const ManagerFlowToken = require('./HomeyAPIV2/ManagerFlowToken');
7
6
  const ManagerDevices = require('./HomeyAPIV2/ManagerDevices');
8
- const HomeyAPI = require('./HomeyAPI');
9
- const HomeyAPIError = require('./HomeyAPIError');
10
- const APIErrorHomeyOffline = require('../APIErrorHomeyOffline');
11
- const Util = require('../Util');
7
+ const ManagerDrivers = require('./HomeyAPIV2/ManagerDrivers');
8
+ const ManagerInsights = require('./HomeyAPIV2/ManagerInsights');
12
9
 
13
10
  /**
14
- * This class is returned by {@link AthomCloudAPI.Homey#authenticate} for a Homey with firmware `>=2.0.0`.
11
+ * This class is returned by {@link AthomCloudAPI.Homey#authenticate} for a Homey with `platform: 'local'` and `platformVersion: 1`.
15
12
  *
16
13
  * @class
17
14
  * @hideconstructor
18
15
  * @extends HomeyAPI
19
16
  */
20
- class HomeyAPIV2 extends HomeyAPI {
17
+ class HomeyAPIV2 extends HomeyAPIV3 {
21
18
 
22
19
  static MANAGERS = {
23
- ManagerApps,
20
+ ...super.MANAGERS,
21
+ ManagerFlow,
22
+ ManagerFlowToken,
24
23
  ManagerDevices,
24
+ ManagerDrivers,
25
+ ManagerInsights,
25
26
  };
26
27
 
27
- constructor({
28
- properties,
29
- strategy = [
30
- HomeyAPI.DISCOVERY_STRATEGIES.MDNS,
31
- HomeyAPI.DISCOVERY_STRATEGIES.CLOUD,
32
- HomeyAPI.DISCOVERY_STRATEGIES.LOCAL,
33
- HomeyAPI.DISCOVERY_STRATEGIES.LOCAL_SECURE,
34
- HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED,
35
- ],
36
- ...props
37
- }) {
38
- super({ properties, ...props });
39
-
40
- Object.defineProperty(this, '__baseUrl', {
41
- value: null,
42
- enumerable: false,
43
- writable: true,
44
- });
45
-
46
- Object.defineProperty(this, '__strategyId', {
47
- value: null,
48
- enumerable: false,
49
- writable: true,
50
- });
51
-
52
- Object.defineProperty(this, '__token', {
53
- value: null,
54
- enumerable: false,
55
- writable: true,
56
- });
57
-
58
- Object.defineProperty(this, '__strategies', {
59
- value: Array.isArray(strategy)
60
- ? strategy
61
- : [strategy],
62
- enumerable: false,
63
- writable: false,
64
- });
65
-
66
- Object.defineProperty(this, '__managers', {
67
- value: {},
68
- enumerable: false,
69
- writable: false,
70
- });
71
-
72
- Object.defineProperty(this, '__baseUrlPromise', {
73
- value: null,
74
- enumerable: false,
75
- writable: true,
76
- });
77
-
78
- Object.defineProperty(this, '__loginPromise', {
79
- value: null,
80
- enumerable: false,
81
- writable: true,
82
- });
83
-
84
- Object.defineProperty(this, '__connected', {
85
- value: false,
86
- enumerable: false,
87
- writable: true,
88
- });
89
-
90
- this.generateManagersFromSpecification();
91
- }
92
-
93
- /*
94
- * Get the Homey's base URL promise
95
- */
96
- get baseUrl() {
97
- return (async () => {
98
- if (!this.__baseUrlPromise) {
99
- this.__baseUrlPromise = this.discoverBaseUrl().then(({ baseUrl }) => baseUrl);
100
- this.__baseUrlPromise.catch(() => { });
101
- }
102
-
103
- return this.__baseUrlPromise;
104
- })();
105
- }
106
-
107
- get strategyId() {
108
- return this.__strategyId;
109
- }
110
-
111
- /*
112
- * Generate Managers from JSON specification
113
- * A manager instance is created when it's first accessed
114
- */
115
-
116
- getSpecification() {
117
- // eslint-disable-next-line global-require
118
- return require('../../assets/specifications/HomeyAPIV2.json');
119
- }
120
-
121
- generateManagersFromSpecification() {
122
- const { managers } = this.getSpecification();
123
- Object.entries(managers).forEach(([managerName, manager]) => {
124
- this.generateManagerFromSpecification(managerName, manager);
125
- });
126
- }
127
-
128
- generateManagerFromSpecification(managerName, manager) {
129
- Object.defineProperty(this, manager.idCamelCase, {
130
- get: () => {
131
- if (!this.__managers[managerName]) {
132
- // eslint-disable-next-line global-require, import/no-dynamic-require
133
- const ManagerClass = this.constructor.MANAGERS[managerName]
134
- ? this.constructor.MANAGERS[managerName]
135
- : Manager;
136
-
137
- this.__managers[managerName] = new ManagerClass({
138
- homey: this,
139
- id: manager.id,
140
- name: managerName,
141
- items: manager.items || {},
142
- operations: manager.operations || {},
143
- });
144
- }
145
-
146
- return this.__managers[managerName];
147
- },
148
- enumerable: false,
149
- });
150
- }
151
-
152
- /*
153
- * Discover the URL to talk to Homey
154
- * We prefer localSecure, because it's fastest and most secure
155
- * If that doesn't work, we prefer local OR mdns, whichever is fastest
156
- * Finally, we fallback to cloud
157
- */
158
-
159
- async discoverBaseUrl() {
160
- const urls = {};
161
-
162
- if (this.__strategies.includes(HomeyAPI.DISCOVERY_STRATEGIES.MDNS)) {
163
- if (Util.isHTTPUnsecureSupported()) {
164
- urls[HomeyAPI.DISCOVERY_STRATEGIES.MDNS] = `http://homey-${this.id}.local`;
165
- }
166
- }
167
-
168
- if (this.__strategies.includes(HomeyAPI.DISCOVERY_STRATEGIES.LOCAL)) {
169
- if (Util.isHTTPUnsecureSupported() && this.__properties.localUrl) {
170
- urls[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL] = `${this.__properties.localUrl}`;
171
- }
172
- }
173
-
174
- if (this.__strategies.includes(HomeyAPI.DISCOVERY_STRATEGIES.LOCAL_SECURE)) {
175
- if (this.__properties.localUrlSecure) {
176
- urls[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL_SECURE] = `${this.__properties.localUrlSecure}`;
177
- }
178
- }
179
-
180
- if (this.__strategies.includes(HomeyAPI.DISCOVERY_STRATEGIES.CLOUD)) {
181
- if (this.__properties.remoteUrl) {
182
- urls[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD] = `${this.__properties.remoteUrl}`;
183
- }
184
- }
185
-
186
- if (this.__strategies.includes(HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED)) {
187
- if (this.__properties.remoteUrlForwarded) {
188
- urls[HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED] = `${this.__properties.remoteUrlForwarded}`;
189
- }
190
- }
191
-
192
- if (!Object.keys(urls).length) {
193
- throw new Error('No Discovery Strategies Available');
194
- }
195
-
196
- // Don't discover, just set the only strategy
197
- if (Object.keys(urls).length === 1) {
198
- this.__baseUrl = Object.values(urls)[0];
199
- this.__strategyId = Object.keys(urls)[0];
200
-
201
- return {
202
- baseUrl: this.__baseUrl,
203
- strategyId: this.__strategyId,
204
- };
205
- }
206
-
207
- this.__debug(`Discovery Strategies: ${Object.keys(urls).join(',')}`);
208
-
209
- // Create the returned Promise
210
- let resolve;
211
- let reject;
212
- const promise = new Promise((resolve_, reject_) => {
213
- resolve = resolve_;
214
- reject = reject_;
215
- });
216
- promise
217
- .then(({ baseUrl, strategyId }) => {
218
- this.__baseUrl = baseUrl;
219
- this.__strategyId = strategyId;
220
- })
221
- .catch(() => { });
222
-
223
- // Ping method
224
- const ping = async (strategyId, timeout) => {
225
- let pingTimeout;
226
- const baseUrl = urls[strategyId];
227
- return Promise.race([
228
- Util.fetch(`${baseUrl}/api/manager/system/ping?id=${this.id}`, {
229
- headers: {
230
- 'X-Homey-ID': this.id,
231
- },
232
- }).then(async res => {
233
- const text = await res.text();
234
- if (!res.ok) throw new Error(text || res.statusText);
235
- if (text === 'false') throw new Error('Invalid Homey ID');
236
-
237
- const homeyId = res.headers.get('X-Homey-ID');
238
- if (homeyId) {
239
- if (homeyId !== this.id) throw new Error('Invalid Homey ID'); // TODO: Add to Homey Connect
240
- }
241
-
242
- // Set the version that Homey told us.
243
- // It's the absolute truth, because the Cloud API may be behind.
244
- const homeyVersion = res.headers.get('X-Homey-Version');
245
- if (homeyVersion !== this.version) {
246
- this.version = homeyVersion;
247
- }
248
-
249
- return {
250
- baseUrl,
251
- strategyId,
252
- };
253
- }),
254
- new Promise((_, reject) => {
255
- pingTimeout = setTimeout(() => reject(new Error('PingTimeout')), timeout);
256
- }),
257
- ]).finally(() => clearTimeout(pingTimeout));
258
- };
259
-
260
- const pings = {};
261
-
262
- // Ping localSecure (https://xxx-xxx-xxx-xx.homey.homeylocal.com)
263
- if (urls[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL_SECURE]) {
264
- pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL_SECURE] = ping(HomeyAPI.DISCOVERY_STRATEGIES.LOCAL_SECURE, 1200);
265
- pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL_SECURE].catch(err => this.__debug(`Ping ${HomeyAPI.DISCOVERY_STRATEGIES.LOCAL_SECURE} Error:`, err && err.message));
266
- }
267
-
268
- // Ping local (http://xxx-xxx-xxx-xxx)
269
- if (urls[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL]) {
270
- pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL] = ping(HomeyAPI.DISCOVERY_STRATEGIES.LOCAL, 1000);
271
- pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL].catch(err => this.__debug(`Ping ${HomeyAPI.DISCOVERY_STRATEGIES.LOCAL} Error:`, err && err.message));
272
- }
273
-
274
- // Ping mdns (http://homey-<homeyId>.local)
275
- if (urls[HomeyAPI.DISCOVERY_STRATEGIES.MDNS]) {
276
- pings[HomeyAPI.DISCOVERY_STRATEGIES.MDNS] = ping(HomeyAPI.DISCOVERY_STRATEGIES.MDNS, 3000);
277
- pings[HomeyAPI.DISCOVERY_STRATEGIES.MDNS].catch(err => this.__debug(`Ping ${HomeyAPI.DISCOVERY_STRATEGIES.MDNS} Error:`, err && err.message));
278
- }
279
-
280
- // Ping cloud (https://<homeyId>.connect.athom.com)
281
- if (urls[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]) {
282
- pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD] = ping(HomeyAPI.DISCOVERY_STRATEGIES.CLOUD, 5000);
283
- pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD].catch(err => this.__debug(`Ping ${HomeyAPI.DISCOVERY_STRATEGIES.CLOUD} Error:`, err && err.message));
284
- }
285
-
286
- // Ping Direct (https://xxx-xxx-xxx-xx.homey.homeylocal.com:12345)
287
- if (urls[HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED]) {
288
- pings[HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED] = ping(HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED, 2000);
289
- pings[HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED].catch(err => this.__debug(`Ping ${HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED} Error:`, err && err.message));
290
- }
291
-
292
- // Select the best route
293
- if (pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL_SECURE]) {
294
- pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL_SECURE]
295
- .then(result => resolve(result))
296
- .catch(() => {
297
- const promises = [];
298
-
299
- if (pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL]) {
300
- promises.push(pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL]);
301
- }
302
-
303
- if (pings[HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED]) {
304
- promises.push(pings[HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED]);
305
- }
306
-
307
- if (pings[HomeyAPI.DISCOVERY_STRATEGIES.MDNS]) {
308
- promises.push(pings[HomeyAPI.DISCOVERY_STRATEGIES.MDNS]);
309
- }
310
-
311
- if (pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]) {
312
- promises.push(pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]);
313
- }
314
-
315
- if (!promises.length) {
316
- throw new APIErrorHomeyOffline();
317
- }
318
-
319
- return Util.promiseAny(promises);
320
- })
321
- .then(result => resolve(result))
322
- .catch(() => reject(new APIErrorHomeyOffline()));
323
- } else if (pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL]) {
324
- pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL]
325
- .then(result => resolve(result))
326
- .catch(() => {
327
- if (pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]) {
328
- pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]
329
- .then(result => resolve(result))
330
- .catch(err => reject(new APIErrorHomeyOffline(err)));
331
- }
332
- });
333
- } else if (pings[HomeyAPI.DISCOVERY_STRATEGIES.MDNS]) {
334
- pings[HomeyAPI.DISCOVERY_STRATEGIES.MDNS]
335
- .then(result => resolve(result))
336
- .catch(() => {
337
- if (pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]) {
338
- pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]
339
- .then(result => resolve(result))
340
- .catch(err => reject(new APIErrorHomeyOffline(err)));
341
- }
342
- });
343
- } else if (pings[HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED]) {
344
- pings[HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED]
345
- .then(result => resolve(result))
346
- .catch(() => {
347
- if (pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]) {
348
- pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]
349
- .then(result => resolve(result))
350
- .catch(err => reject(new APIErrorHomeyOffline(err)));
351
- }
352
- });
353
- } else if (pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]) {
354
- pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]
355
- .then(result => resolve(result))
356
- .catch(err => reject(new APIErrorHomeyOffline(err)));
357
- } else {
358
- reject(new APIErrorHomeyOffline());
359
- }
360
-
361
- return promise;
362
- }
363
-
364
- async call({
365
- $timeout = 10000,
366
- method,
367
- headers,
368
- path,
369
- body,
370
- retryAfterRefresh = false,
371
- }) {
372
- const baseUrl = await this.baseUrl;
373
-
374
- method = method.toUpperCase();
375
-
376
- headers = {
377
- ...headers,
378
- 'X-Homey-ID': this.id,
379
- };
380
-
381
- if (body) {
382
- headers['Content-Type'] = 'application/json';
383
- }
384
-
385
- if (this.__token) {
386
- headers['Authorization'] = `Bearer ${this.__token}`;
387
- }
388
-
389
- this.__debug(method, `${baseUrl}${path}`);
390
- const res = await Util.timeout(Util.fetch(`${baseUrl}${path}`, {
391
- method,
392
- headers,
393
- body: ['PUT', 'POST'].includes(method) && typeof body !== 'undefined'
394
- ? JSON.stringify(body)
395
- : undefined,
396
- }), $timeout);
397
-
398
- const resStatusCode = res.status;
399
- if (resStatusCode === 204) return undefined;
400
-
401
- const resStatusText = res.status;
402
- const resHeadersContentType = res.headers.get('Content-Type');
403
- const resBodyText = await res.text();
404
- let resBodyJson;
405
- if (resHeadersContentType && resHeadersContentType.startsWith('application/json')) {
406
- try {
407
- resBodyJson = JSON.parse(resBodyText);
408
- } catch (err) { }
409
- }
410
-
411
- if (!res.ok) {
412
- if (resBodyJson) {
413
- // If Session Expired, clear the stored token
414
- if (resStatusCode === 401) {
415
- this.__debug('Session expired, invalidating token...');
416
- await this.logout();
417
- }
418
-
419
- // If Session Expired, try to refresh the Token
420
- if (resStatusCode === 401 && !retryAfterRefresh) {
421
- this.__debug('Session expired, refreshing...');
422
- await this.login();
423
- return this.call({
424
- method,
425
- headers,
426
- path,
427
- body,
428
- retryAfterRefresh: true,
429
- });
430
- }
431
-
432
- throw new HomeyAPIError({
433
- error: resBodyJson.error,
434
- error_description: resBodyJson.error_description,
435
- stack: resBodyJson.stack,
436
- }, resStatusCode);
437
- }
438
-
439
- if (resBodyText) {
440
- throw new HomeyAPIError({
441
- error: resBodyText,
442
- }, resStatusCode);
443
- }
444
-
445
- throw new HomeyAPIError({
446
- error: resStatusText,
447
- }, resStatusCode);
448
- }
449
-
450
- if (typeof resBodyJson !== 'undefined') {
451
- return resBodyJson;
452
- }
453
-
454
- return resBodyText;
455
- }
456
-
457
- async login() {
458
- if (!this.__loginPromise) {
459
- this.__loginPromise = Promise.resolve().then(async () => {
460
- // Check store for a valid Homey.Session
461
- const store = await this.__getStore();
462
- if (store && store.token) {
463
- this.__debug('Got token from store');
464
- return store.token;
465
- }
466
-
467
- // Create a Session by generating a JWT token on AthomCloudAPI,
468
- // and then sending the JWT token to Homey.
469
- this.__debug('Retrieving token...');
470
- const jwtToken = await this.__api.createDelegationToken({ audience: 'homey' });
471
- const token = await this.users.login({
472
- $socket: false,
473
- token: jwtToken,
474
- });
475
- await this.__setStore({ token });
476
- this.__debug('Got token');
477
-
478
- return token;
479
- });
480
-
481
- this.__loginPromise
482
- .then(token => {
483
- this.__token = token;
484
- })
485
- .catch(err => {
486
- this.__debug('Error Logging In:', err);
487
- })
488
- .finally(() => {
489
- this.__loginPromise = null;
490
- });
491
- }
492
-
493
- return this.__loginPromise;
494
- }
495
-
496
- async logout() {
497
- await this.__setStore({
498
- token: null,
499
- });
500
-
501
- this.__token = null;
502
- }
503
-
504
- /**
505
- * If Homey is connected to Socket.io.
506
- * @returns {Boolean}
507
- */
508
- isConnected() {
509
- return this.__connected === true;
510
- }
511
-
512
- async subscribe(uri, {
513
- onConnect = () => { },
514
- onReconnect = () => { },
515
- onReconnectError = () => { },
516
- onDisconnect = () => { },
517
- onEvent = () => { },
518
- }) {
519
- this.__debug('subscribe', uri);
520
-
521
- await this.connect();
522
- await new Promise((resolve, reject) => {
523
- this.__ioNamespace.once('disconnect', reject);
524
- this.__ioNamespace.emit('subscribe', uri, err => {
525
- if (err) return reject(err);
526
- return resolve();
527
- });
528
- });
529
-
530
- // On Connect
531
- const __onEvent = (event, data) => {
532
- onEvent(event, data);
533
- };
534
- this.__ioNamespace.on(uri, __onEvent);
535
-
536
- onConnect();
537
-
538
- // On Disconnect
539
- const __onDisconnect = reason => {
540
- onDisconnect(reason);
541
- };
542
- this.__io.on('disconnect', __onDisconnect);
543
-
544
- // On Reconnect
545
- const __onReconnect = () => {
546
- Promise.resolve().then(async () => {
547
- await this.connect();
548
- await new Promise((resolve, reject) => {
549
- this.__ioNamespace.emit('subscribe', uri, err => {
550
- if (err) return reject(err);
551
- return resolve();
552
- });
553
- });
554
-
555
- this.__ioNamespace.on(uri, __onEvent);
556
-
557
- onReconnect();
558
- }).catch(err => onReconnectError(err));
559
- };
560
- this.__io.on('reconnect', __onReconnect);
561
-
562
- return {
563
- unsubscribe: () => {
564
- this.__ioNamespace.emit('unsubscribe', uri);
565
- this.__ioNamespace.removeListener(uri, __onEvent);
566
- this.__io.removeListener('disconnect', __onDisconnect);
567
- this.__io.removeListener('reconnect', __onReconnect);
568
- },
569
- };
570
- }
571
-
572
- async connect() {
573
- if (!this.io) {
574
- this.io = Promise.resolve().then(async () => {
575
- // Ensure Base URL
576
- const baseUrl = await this.baseUrl;
577
-
578
- // Ensure Token
579
- if (!this.__token) await this.login();
580
-
581
- return new Promise((resolve, reject) => {
582
- this.__debug(`SocketIOClient ${baseUrl}`);
583
- this.__io = SocketIOClient(baseUrl, {
584
- autoConnect: false,
585
- transports: ['websocket'],
586
- transportOptions: {
587
- pingTimeout: 8000,
588
- pingInterval: 5000,
589
- },
590
- });
591
- this.__io.on('disconnect', reason => {
592
- this.__debug('SocketIOClient.onDisconnect', reason);
593
- this.__connected = false;
594
-
595
- if (this.__ioNamespace) {
596
- this.__ioNamespace.disconnect();
597
- this.__ioNamespace.destroy();
598
- this.__ioNamespace.removeAllListeners();
599
- }
600
-
601
- reject(new Error('Disconnected'));
602
- });
603
- this.__io.on('error', err => {
604
- this.__debug('SocketIOClient.onError', err.message);
605
- });
606
- this.__io.on('reconnect', () => {
607
- this.__debug('SocketIOClient.onReconnect');
608
- this.__handshakeClient()
609
- .then(() => {
610
- this.__debug('SocketIOClient.onReconnect.onHandshakeClientSuccess');
611
- this.__connected = true;
612
- resolve();
613
- })
614
- .catch(err => {
615
- this.__debug('SocketIOClient.onReconnect.onHandshakeClientError', err.message);
616
- reject(err);
617
- });
618
- });
619
- this.__io.on('reconnecting', attempt => {
620
- this.__debug(`SocketIOClient.onReconnecting (Attempt #${attempt})`);
621
- });
622
- this.__io.on('reconnect_error', err => {
623
- this.__debug('SocketIOClient.onReconnectError', err.message);
624
- });
625
- this.__io.once('connect_error', err => {
626
- this.__debug('SocketIOClient.onConnectError', err.message);
627
- reject(err);
628
- });
629
- this.__io.once('connect', () => {
630
- this.__debug('SocketIOClient.onConnect');
631
- this.__handshakeClient()
632
- .then(() => {
633
- this.__debug('SocketIOClient.onConnect.onHandshakeClientSuccess');
634
- this.__connected = true;
635
- resolve();
636
- })
637
- .catch(err => {
638
- this.__debug('SocketIOClient.onConnect.onHandshakeClientError', err.message);
639
- reject(err);
640
- });
641
- });
642
- this.__io.connect();
643
- });
644
- });
645
- this.io.catch(err => {
646
- this.__debug('SocketIOClient Error', err.message);
647
- });
648
- }
649
-
650
- return this.io;
651
- }
652
-
653
- async disconnect() {
654
- if (this.__io) {
655
- await new Promise(resolve => {
656
- this.__io.once('disconnect', resolve());
657
- this.__io.disconnect();
658
- this.__io.removeAllListeners();
659
- this.__io = null;
660
- });
661
- }
662
- // TODO todo what?
663
- }
664
-
665
- destroy() {
666
- if (this.__io) {
667
- this.__io.removeAllListeners();
668
- this.__io.close();
669
- }
670
- }
671
-
672
- async __handshakeClient() {
673
- return new Promise((resolve, reject) => {
674
- this.__io.emit('handshakeClient', {
675
- token: this.__token,
676
- homeyId: this.id,
677
- }, (err, result) => {
678
- if (err) return reject(err);
679
- return resolve(result);
680
- });
681
- })
682
- .catch(async err => {
683
- // If token is expired, try to refresh
684
- if (err.statusCode === 401) {
685
- this.__debug('Token expired, refreshing...');
686
- await this.logout();
687
- await this.login();
688
-
689
- return new Promise((resolve, reject) => {
690
- this.__io.emit('handshakeClient', {
691
- token: this.__token,
692
- homeyId: this.id,
693
- }, (err, result) => {
694
- if (err) return reject(err);
695
- return resolve(result);
696
- });
697
- });
698
- }
699
-
700
- throw err;
701
- })
702
- .then(({ namespace }) => {
703
- this.__debug('SocketIOClient.onHandshakeClientSuccess', `Namespace: ${namespace}`);
704
-
705
- return new Promise((resolve, reject) => {
706
- this.__ioNamespace = this.__io.io.socket(namespace);
707
- this.__ioNamespace.once('connect', () => {
708
- this.__debug(`SocketIOClient.Namespace[${namespace}].onConnect`);
709
- resolve();
710
- });
711
- this.__ioNamespace.once('connect_error', err => {
712
- this.__debug(`SocketIOClient.Namespace[${namespace}].onConnectError`, err.message);
713
- reject(err);
714
- });
715
- this.__ioNamespace.on('reconnecting', attempt => {
716
- this.__debug(`SocketIOClient.Namespace[${namespace}].onReconnecting (Attempt #${attempt})`);
717
- });
718
- this.__ioNamespace.on('reconnect', () => {
719
- this.__debug(`SocketIOClient.Namespace[${namespace}].onReconnect`);
720
- });
721
- this.__ioNamespace.on('reconnect_error', err => {
722
- this.__debug(`SocketIOClient.Namespace[${namespace}].onReconnectError`, err.message);
723
- });
724
- this.__ioNamespace.on('disconnect', reason => {
725
- this.__debug(`SocketIOClient.Namespace[${namespace}].onDisconnect`, reason);
726
- });
727
- this.__ioNamespace.connect();
728
- });
729
- });
730
- }
731
-
732
28
  }
733
29
 
734
30
  module.exports = HomeyAPIV2;