homey-api 1.5.14 → 1.5.17

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,64 +113,105 @@ class Device extends Item {
113
113
  async connect() {
114
114
  this.__debug('connect');
115
115
 
116
- if (!this.io) {
117
- this.io = 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
- onReconnect: () => {
127
- this.__debug('onDisconnect');
128
-
129
- const capabilityInstances = this.__capabilityInstances;
130
- if (Object.keys(capabilityInstances).length > 0) {
131
- // Get the device's latest values
132
- // TODO: Optimize this with `getDevices()` when >1 device has >0 capability instances.
133
- this.manager.getDevice({
134
- id: this.id,
135
- }).then(async device => {
136
- Object.entries(capabilityInstances).forEach(([capabilityId, capabilityInstance]) => {
137
- const value = device.capabilitiesObj
138
- ? typeof device.capabilitiesObj[capabilityId] !== 'undefined'
139
- ? device.capabilitiesObj[capabilityId].value
140
- : null
141
- : null;
142
-
143
- capabilityInstance.__onCapabilityValue({
144
- capabilityId,
145
- value,
146
- transactionId: Util.uuid(),
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
+ });
147
154
  });
148
- });
149
- })
150
- // eslint-disable-next-line no-console
151
- .catch(err => console.error(`Device[${this.id}].onReconnectError:`, err));
152
- }
153
- },
154
- onEvent: (event, data) => {
155
- // // Fire event listeners
156
- if (Array.isArray(this.__listeners[event])) {
157
- this.__listeners[event].forEach(listener => listener(data));
158
- }
159
- },
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;
160
177
  });
161
- }
162
178
 
163
- await this.io;
179
+ await this.__connectPromise;
164
180
  }
165
181
 
166
182
  async disconnect() {
167
183
  this.__debug('disconnect');
168
184
 
169
- if (this.io) {
170
- this.io
171
- .then(io => io.unsubscribe())
172
- .catch(err => this.__debug('Error Disconnecting:', err));
173
- }
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(() => { });
174
215
  }
175
216
 
176
217
  }
@@ -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
  }
@@ -167,10 +167,7 @@ class Manager extends EventEmitter {
167
167
  }
168
168
  case 'body': {
169
169
  if (parameter.root) {
170
- body = {
171
- ...body,
172
- ...value,
173
- };
170
+ body = value;
174
171
  } else {
175
172
  body[parameterId] = value;
176
173
  }
@@ -253,6 +250,7 @@ class Manager extends EventEmitter {
253
250
  } else {
254
251
  // Get from HTTP
255
252
  result = await this.homey.call({
253
+ $timeout,
256
254
  headers,
257
255
  body,
258
256
  path: `/api/manager/${this.id}${path}`,
@@ -383,82 +381,102 @@ class Manager extends EventEmitter {
383
381
  async connect() {
384
382
  this.__debug('connect');
385
383
 
386
- if (!this.io) {
387
- this.io = 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);
384
+ // If disconnecting, await that first
385
+ try {
386
+ await this.__disconnectPromise;
387
+ } catch (err) { }
388
+
389
+ this.__connectPromise = Promise.resolve().then(async () => {
390
+ if (!this.io) {
391
+ this.io = this.homey.subscribe(this.uri, {
392
+ onConnect: () => {
393
+ this.__debug('onConnect');
394
+ this.__connected = true;
395
+ },
396
+ onDisconnect: () => {
397
+ this.__debug('onDisconnect');
398
+ this.__connected = false;
399
+
400
+ // Clear CRUD Item cache
401
+ for (const itemId of Object.keys(this.__cache)) {
402
+ this.__cache[itemId] = {};
403
+ this.__cacheAllComplete[itemId] = false;
404
+ }
405
+ },
406
+ onEvent: (event, data) => {
407
+ this.__debug('onEvent', event);
408
+
409
+ // Transform & add to cache if this is a CRUD event
410
+ if (event.endsWith('.create')
411
+ || event.endsWith('.update')
412
+ || event.endsWith('.delete')) {
413
+ const [itemId, operation] = event.split('.');
414
+ const ItemClass = this.constructor.ITEMS[itemId] || Item;
415
+ const itemType = this.__itemsById[itemId].type;
416
+ const key = itemType === 'filter'
417
+ ? `${data.uri}:${data.id}`
418
+ : `${data.id}`;
419
+
420
+ switch (operation) {
421
+ case 'create': {
422
+ this.__cache[itemId][key] = new ItemClass({
423
+ key,
424
+ manager: this,
425
+ properties: { ...data },
426
+ });
427
+ this.__cache[itemId][key].emit('create', data);
424
428
 
425
- data = this.__cache[itemId][key];
429
+ data = this.__cache[itemId][key];
426
430
 
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);
431
+ break;
433
432
  }
433
+ case 'update': {
434
+ if (this.__cache[itemId][key]) {
435
+ this.__cache[itemId][key].__update(data);
436
+ this.__cache[itemId][key].emit('update', data);
437
+ } else {
438
+ this.__cache[itemId][key] = new ItemClass({
439
+ key,
440
+ manager: this,
441
+ properties: { ...data },
442
+ });
443
+ }
434
444
 
435
- data = this.__cache[itemId][key];
445
+ data = this.__cache[itemId][key];
436
446
 
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;
447
+ break;
445
448
  }
446
- break;
449
+ case 'delete': {
450
+ if (this.__cache[itemId][key]) {
451
+ this.__cache[itemId][key].emit('delete');
452
+ this.__cache[itemId][key].destroy();
453
+ delete this.__cache[itemId][key];
454
+ data = null;
455
+ }
456
+ break;
457
+ }
458
+ default:
459
+ break;
447
460
  }
448
- default:
449
- break;
450
461
  }
451
- }
452
462
 
453
- // Fire event listeners
454
- if (Array.isArray(this.__listeners[event])) {
455
- this.__listeners[event].forEach(listener => listener(data));
456
- }
457
- },
463
+ // Fire event listeners
464
+ this.emit(event, data);
465
+ },
466
+ });
467
+ }
468
+
469
+ await this.io;
470
+ });
471
+
472
+ // Delete the connecting Promise
473
+ this.__connectPromise
474
+ .catch(() => { })
475
+ .finally(() => {
476
+ delete this.__connectPromise;
458
477
  });
459
- }
460
478
 
461
- await this.io;
479
+ await this.__connectPromise;
462
480
  }
463
481
 
464
482
  /**
@@ -468,11 +486,31 @@ class Manager extends EventEmitter {
468
486
  async disconnect() {
469
487
  this.__debug('disconnect');
470
488
 
471
- if (this.io) {
472
- this.io
473
- .then(io => io.unsubscribe())
474
- .catch(err => this.__debug('Error Disconnecting:', err));
475
- }
489
+ // If connecting, await that first
490
+ try {
491
+ await this.__connectPromise;
492
+ } catch (err) { }
493
+
494
+ this.__disconnectPromise = Promise.resolve().then(async () => {
495
+ this.__connected = false;
496
+
497
+ if (this.io) {
498
+ await this.io
499
+ .then(io => io.unsubscribe())
500
+ .catch(err => this.__debug('Error Disconnecting:', err));
501
+
502
+ delete this.io;
503
+ }
504
+ });
505
+
506
+ // Delete the disconnecting Promise
507
+ this.__disconnectPromise
508
+ .catch(() => { })
509
+ .finally(() => {
510
+ delete this.__disconnectPromise;
511
+ });
512
+
513
+ await this.__disconnectPromise;
476
514
  }
477
515
 
478
516
  destroy() {
@@ -288,8 +288,6 @@ class HomeyAPIV2 extends HomeyAPI {
288
288
  promises.push(pings[HomeyAPI.DISCOVERY_STRATEGIES.MDNS]);
289
289
  }
290
290
 
291
- // TODO: Move this to the catch handler to always fallback on cloud
292
- // Now mdns or local will error first and cloud won't have a chance!!
293
291
  if (pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]) {
294
292
  promises.push(pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]);
295
293
  }
@@ -301,7 +299,16 @@ class HomeyAPIV2 extends HomeyAPI {
301
299
  return Promise.race(promises);
302
300
  })
303
301
  .then(result => resolve(result))
304
- .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
+ });
305
312
  } else if (pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL]) {
306
313
  pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL]
307
314
  .then(result => resolve(result))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homey-api",
3
- "version": "1.5.14",
3
+ "version": "1.5.17",
4
4
  "description": "Homey API",
5
5
  "main": "src/index.js",
6
6
  "types": "assets/types/homey-api.d.ts",