homey-api 1.5.13 → 1.5.16

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.
@@ -113,32 +113,105 @@ class Device extends Item {
113
113
  async connect() {
114
114
  this.__debug('connect');
115
115
 
116
- if (!this.io) {
117
- this.io = await this.homey.subscribe(this.uri, {
118
- onConnect: () => {
119
- this.__debug('onConnect');
120
- this.__connected = true;
121
- },
122
- onDisconnect: () => {
123
- this.__debug('onDisconnect');
124
- this.__connected = false;
125
- },
126
- onEvent: (event, data) => {
127
- // // Fire event listeners
128
- if (Array.isArray(this.__listeners[event])) {
129
- this.__listeners[event].forEach(listener => listener(data));
130
- }
131
- },
116
+ // If disconnecting, await that first
117
+ try {
118
+ await this.__disconnectPromise;
119
+ } catch (err) { }
120
+
121
+ this.__connectPromise = Promise.resolve().then(async () => {
122
+ if (!this.io) {
123
+ this.io = this.homey.subscribe(this.uri, {
124
+ onConnect: () => {
125
+ this.__debug('onConnect');
126
+ this.__connected = true;
127
+ },
128
+ onDisconnect: () => {
129
+ this.__debug('onDisconnect');
130
+ this.__connected = false;
131
+ },
132
+ onReconnect: () => {
133
+ this.__debug('onDisconnect');
134
+
135
+ const capabilityInstances = this.__capabilityInstances;
136
+ if (Object.keys(capabilityInstances).length > 0) {
137
+ // Get the device's latest values
138
+ // TODO: Optimize this with `getDevices()` when >1 device has >0 capability instances.
139
+ this.manager.getDevice({
140
+ id: this.id,
141
+ }).then(async device => {
142
+ Object.entries(capabilityInstances).forEach(([capabilityId, capabilityInstance]) => {
143
+ const value = device.capabilitiesObj
144
+ ? typeof device.capabilitiesObj[capabilityId] !== 'undefined'
145
+ ? device.capabilitiesObj[capabilityId].value
146
+ : null
147
+ : null;
148
+
149
+ capabilityInstance.__onCapabilityValue({
150
+ capabilityId,
151
+ value,
152
+ transactionId: Util.uuid(),
153
+ });
154
+ });
155
+ })
156
+ // eslint-disable-next-line no-console
157
+ .catch(err => console.error(`Device[${this.id}].onReconnectError:`, err));
158
+ }
159
+ },
160
+ onEvent: (event, data) => {
161
+ // // Fire event listeners
162
+ if (Array.isArray(this.__listeners[event])) {
163
+ this.__listeners[event].forEach(listener => listener(data));
164
+ }
165
+ },
166
+ });
167
+ }
168
+
169
+ await this.io;
170
+ });
171
+
172
+ // Delete the connecting Promise
173
+ this.__connectPromise
174
+ .catch(() => { })
175
+ .finally(() => {
176
+ delete this.__connectPromise;
132
177
  });
133
- }
178
+
179
+ await this.__connectPromise;
134
180
  }
135
181
 
136
182
  async disconnect() {
137
183
  this.__debug('disconnect');
138
184
 
139
- if (this.io) {
140
- this.io.unsubscribe();
141
- }
185
+ // If connecting, await that first
186
+ try {
187
+ await this.__connectPromise;
188
+ } catch (err) { }
189
+
190
+ this.__disconnectPromise = Promise.resolve().then(async () => {
191
+ this.__connected = false;
192
+
193
+ if (this.io) {
194
+ this.io
195
+ .then(io => io.unsubscribe())
196
+ .catch(err => this.__debug('Error Disconnecting:', err));
197
+ }
198
+ });
199
+
200
+ // Delete the disconnecting Promise
201
+ this.__disconnectPromise
202
+ .catch(() => { })
203
+ .finally(() => {
204
+ delete this.__disconnectPromise;
205
+ });
206
+
207
+ await this.__disconnectPromise;
208
+ }
209
+
210
+ destroy() {
211
+ super.destroy();
212
+
213
+ // Disconnect from Socket.io
214
+ this.disconnect().catch(() => { });
142
215
  }
143
216
 
144
217
  }
@@ -74,6 +74,7 @@ class DeviceCapability extends EventEmitter {
74
74
  this.emit('destroy');
75
75
  this.device.off('capability', this.__onCapabilityValue);
76
76
  this.device.off('delete', this.__onDeviceDelete);
77
+ this.removeAllListeners();
77
78
  }
78
79
 
79
80
  __onCapabilityValue({
@@ -55,7 +55,8 @@ class Item extends EventEmitter {
55
55
  }
56
56
 
57
57
  destroy() {
58
- // TODO
58
+ // Remove all event listeners
59
+ this.removeAllListeners();
59
60
  }
60
61
 
61
62
  }
@@ -253,6 +253,7 @@ class Manager extends EventEmitter {
253
253
  } else {
254
254
  // Get from HTTP
255
255
  result = await this.homey.call({
256
+ $timeout,
256
257
  headers,
257
258
  body,
258
259
  path: `/api/manager/${this.id}${path}`,
@@ -383,80 +384,102 @@ class Manager extends EventEmitter {
383
384
  async connect() {
384
385
  this.__debug('connect');
385
386
 
386
- if (!this.io) {
387
- this.io = await this.homey.subscribe(this.uri, {
388
- onConnect: () => {
389
- this.__debug('onConnect');
390
- this.__connected = true;
391
- },
392
- onDisconnect: () => {
393
- this.__debug('onDisconnect');
394
- this.__connected = false;
395
-
396
- // Clear CRUD Item cache
397
- for (const itemId of Object.keys(this.__cache)) {
398
- this.__cache[itemId] = {};
399
- this.__cacheAllComplete[itemId] = false;
400
- }
401
- },
402
- onEvent: (event, data) => {
403
- this.__debug('onEvent', event);
404
-
405
- // Transform & add to cache if this is a CRUD event
406
- if (event.endsWith('.create')
407
- || event.endsWith('.update')
408
- || event.endsWith('.delete')) {
409
- const [itemId, operation] = event.split('.');
410
- const ItemClass = this.constructor.ITEMS[itemId] || Item;
411
- const itemType = this.__itemsById[itemId].type;
412
- const key = itemType === 'filter'
413
- ? `${data.uri}:${data.id}`
414
- : `${data.id}`;
415
-
416
- switch (operation) {
417
- case 'create': {
418
- this.__cache[itemId][key] = new ItemClass({
419
- key,
420
- manager: this,
421
- properties: { ...data },
422
- });
423
- this.__cache[itemId][key].emit('create', data);
387
+ // If disconnecting, await that first
388
+ try {
389
+ await this.__disconnectPromise;
390
+ } catch (err) { }
391
+
392
+ this.__connectPromise = Promise.resolve().then(async () => {
393
+ if (!this.io) {
394
+ this.io = this.homey.subscribe(this.uri, {
395
+ onConnect: () => {
396
+ this.__debug('onConnect');
397
+ this.__connected = true;
398
+ },
399
+ onDisconnect: () => {
400
+ this.__debug('onDisconnect');
401
+ this.__connected = false;
402
+
403
+ // Clear CRUD Item cache
404
+ for (const itemId of Object.keys(this.__cache)) {
405
+ this.__cache[itemId] = {};
406
+ this.__cacheAllComplete[itemId] = false;
407
+ }
408
+ },
409
+ onEvent: (event, data) => {
410
+ this.__debug('onEvent', event);
411
+
412
+ // Transform & add to cache if this is a CRUD event
413
+ if (event.endsWith('.create')
414
+ || event.endsWith('.update')
415
+ || event.endsWith('.delete')) {
416
+ const [itemId, operation] = event.split('.');
417
+ const ItemClass = this.constructor.ITEMS[itemId] || Item;
418
+ const itemType = this.__itemsById[itemId].type;
419
+ const key = itemType === 'filter'
420
+ ? `${data.uri}:${data.id}`
421
+ : `${data.id}`;
422
+
423
+ switch (operation) {
424
+ case 'create': {
425
+ this.__cache[itemId][key] = new ItemClass({
426
+ key,
427
+ manager: this,
428
+ properties: { ...data },
429
+ });
430
+ this.__cache[itemId][key].emit('create', data);
424
431
 
425
- data = this.__cache[itemId][key];
432
+ data = this.__cache[itemId][key];
426
433
 
427
- break;
428
- }
429
- case 'update': {
430
- if (this.__cache[itemId][key]) {
431
- this.__cache[itemId][key].__update(data);
432
- this.__cache[itemId][key].emit('update', data);
434
+ break;
433
435
  }
436
+ case 'update': {
437
+ if (this.__cache[itemId][key]) {
438
+ this.__cache[itemId][key].__update(data);
439
+ this.__cache[itemId][key].emit('update', data);
440
+ } else {
441
+ this.__cache[itemId][key] = new ItemClass({
442
+ key,
443
+ manager: this,
444
+ properties: { ...data },
445
+ });
446
+ }
434
447
 
435
- data = this.__cache[itemId][key];
448
+ data = this.__cache[itemId][key];
436
449
 
437
- break;
438
- }
439
- case 'delete': {
440
- if (this.__cache[itemId][key]) {
441
- this.__cache[itemId][key].emit('delete');
442
- this.__cache[itemId][key].destroy();
443
- delete this.__cache[itemId][key];
444
- data = null;
450
+ break;
445
451
  }
446
- break;
452
+ case 'delete': {
453
+ if (this.__cache[itemId][key]) {
454
+ this.__cache[itemId][key].emit('delete');
455
+ this.__cache[itemId][key].destroy();
456
+ delete this.__cache[itemId][key];
457
+ data = null;
458
+ }
459
+ break;
460
+ }
461
+ default:
462
+ break;
447
463
  }
448
- default:
449
- break;
450
464
  }
451
- }
452
465
 
453
- // Fire event listeners
454
- if (Array.isArray(this.__listeners[event])) {
455
- this.__listeners[event].forEach(listener => listener(data));
456
- }
457
- },
466
+ // Fire event listeners
467
+ this.emit(event, data);
468
+ },
469
+ });
470
+ }
471
+
472
+ await this.io;
473
+ });
474
+
475
+ // Delete the connecting Promise
476
+ this.__connectPromise
477
+ .catch(() => { })
478
+ .finally(() => {
479
+ delete this.__connectPromise;
458
480
  });
459
- }
481
+
482
+ await this.__connectPromise;
460
483
  }
461
484
 
462
485
  /**
@@ -466,12 +489,35 @@ class Manager extends EventEmitter {
466
489
  async disconnect() {
467
490
  this.__debug('disconnect');
468
491
 
469
- if (this.io) {
470
- this.io.unsubscribe();
471
- }
492
+ // If connecting, await that first
493
+ try {
494
+ await this.__connectPromise;
495
+ } catch (err) { }
496
+
497
+ this.__disconnectPromise = Promise.resolve().then(async () => {
498
+ this.__connected = false;
499
+
500
+ if (this.io) {
501
+ await this.io
502
+ .then(io => io.unsubscribe())
503
+ .catch(err => this.__debug('Error Disconnecting:', err));
504
+
505
+ delete this.io;
506
+ }
507
+ });
508
+
509
+ // Delete the disconnecting Promise
510
+ this.__disconnectPromise
511
+ .catch(() => { })
512
+ .finally(() => {
513
+ delete this.__disconnectPromise;
514
+ });
515
+
516
+ await this.__disconnectPromise;
472
517
  }
473
518
 
474
519
  destroy() {
520
+ // Clear cache
475
521
  for (const id of Object.keys(this.__cache)) {
476
522
  this.__cache[id] = {};
477
523
  }
@@ -480,6 +526,10 @@ class Manager extends EventEmitter {
480
526
  this.__cacheAllComplete[id] = false;
481
527
  }
482
528
 
529
+ // Remove all event listeners
530
+ this.removeAllListeners();
531
+
532
+ // Disconnect from Socket.io
483
533
  this.disconnect().catch(() => { });
484
534
  }
485
535
 
@@ -184,9 +184,12 @@ class HomeyAPIV2 extends HomeyAPI {
184
184
 
185
185
  // Don't discover, just set the only strategy
186
186
  if (Object.keys(urls).length === 1) {
187
+ this.__baseUrl = Object.values(urls)[0];
188
+ this.__strategyId = Object.keys(urls)[0];
189
+
187
190
  return {
188
- strategyId: Object.keys(urls)[0],
189
- baseUrl: Object.values(urls)[0],
191
+ baseUrl: this.__baseUrl,
192
+ strategyId: this.__strategyId,
190
193
  };
191
194
  }
192
195
 
@@ -285,8 +288,6 @@ class HomeyAPIV2 extends HomeyAPI {
285
288
  promises.push(pings[HomeyAPI.DISCOVERY_STRATEGIES.MDNS]);
286
289
  }
287
290
 
288
- // TODO: Move this to the catch handler to always fallback on cloud
289
- // Now mdns or local will error first and cloud won't have a chance!!
290
291
  if (pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]) {
291
292
  promises.push(pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]);
292
293
  }
@@ -298,7 +299,16 @@ class HomeyAPIV2 extends HomeyAPI {
298
299
  return Promise.race(promises);
299
300
  })
300
301
  .then(result => resolve(result))
301
- .catch(() => reject(new HomeyOfflineError()));
302
+ .catch(err => {
303
+ // Last resort: try cloud
304
+ if (pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]) {
305
+ return pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD].catch(err => {
306
+ throw new HomeyOfflineError(err);
307
+ });
308
+ }
309
+
310
+ reject(new HomeyOfflineError(err));
311
+ });
302
312
  } else if (pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL]) {
303
313
  pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL]
304
314
  .then(result => resolve(result))
@@ -526,6 +536,7 @@ class HomeyAPIV2 extends HomeyAPI {
526
536
 
527
537
  return {
528
538
  unsubscribe: () => {
539
+ this.__ioNamespace.emit('unsubscribe', uri);
529
540
  this.__ioNamespace.removeListener(uri, __onEvent);
530
541
  this.__io.removeListener('disconnect', __onDisconnect);
531
542
  this.__io.removeListener('reconnect', __onReconnect);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homey-api",
3
- "version": "1.5.13",
3
+ "version": "1.5.16",
4
4
  "description": "Homey API",
5
5
  "main": "src/index.js",
6
6
  "types": "assets/types/homey-api.d.ts",