homey-api 3.4.10 → 3.4.11

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.
@@ -16,15 +16,10 @@ const Item = require('./Item');
16
16
  * @memberof HomeyAPIV3
17
17
  */
18
18
  class Manager extends EventEmitter {
19
-
20
19
  static ID = null; // Set by HomeyAPIV3.js
21
20
  static CRUD = {};
22
21
 
23
- constructor({
24
- homey,
25
- items,
26
- operations,
27
- }) {
22
+ constructor({ homey, items, operations }) {
28
23
  super();
29
24
 
30
25
  Object.defineProperty(this, '__homey', {
@@ -38,9 +33,9 @@ class Manager extends EventEmitter {
38
33
  const ItemClass = this.constructor.CRUD[itemName]
39
34
  ? this.constructor.CRUD[itemName]
40
35
  : (() => {
41
- return class extends Item {};
42
- })();
43
-
36
+ return class extends Item {};
37
+ })();
38
+
44
39
  ItemClass.ID = item.id;
45
40
  obj[itemName] = ItemClass;
46
41
 
@@ -67,19 +62,25 @@ class Manager extends EventEmitter {
67
62
  });
68
63
 
69
64
  Object.defineProperty(this, '__cache', {
70
- value: Object.values(items).reduce((obj, item) => ({
71
- ...obj,
72
- [item.id]: {},
73
- }), {}),
65
+ value: Object.values(items).reduce(
66
+ (obj, item) => ({
67
+ ...obj,
68
+ [item.id]: {},
69
+ }),
70
+ {}
71
+ ),
74
72
  enumerable: false,
75
73
  writable: false,
76
74
  });
77
75
 
78
76
  Object.defineProperty(this, '__cacheAllComplete', {
79
- value: Object.values(items).reduce((obj, item) => ({
80
- ...obj,
81
- [item.id]: false,
82
- }), {}),
77
+ value: Object.values(items).reduce(
78
+ (obj, item) => ({
79
+ ...obj,
80
+ [item.id]: false,
81
+ }),
82
+ {}
83
+ ),
83
84
  enumerable: false,
84
85
  writable: false,
85
86
  });
@@ -92,15 +93,15 @@ class Manager extends EventEmitter {
92
93
 
93
94
  // Create methods
94
95
  for (const [operationId, operation] of Object.entries(operations)) {
95
- Object.defineProperty(this,
96
+ Object.defineProperty(
97
+ this,
96
98
  // Name method __super__foo if there's an override method
97
- this[operationId]
98
- ? `__super__${operationId}`
99
- : operationId,
99
+ this[operationId] ? `__super__${operationId}` : operationId,
100
100
  {
101
101
  value: async ({
102
102
  $validate = true,
103
103
  $cache = true,
104
+ $updateCache = true,
104
105
  $timeout = operation.timeout ?? 5000,
105
106
  $socket = operation.socket ?? true,
106
107
  $body = {},
@@ -135,7 +136,9 @@ class Manager extends EventEmitter {
135
136
  }
136
137
 
137
138
  if (parameter.type === 'boolean' && typeof value !== 'boolean') {
138
- throw new Error(`Invalid Parameter Type: ${parameterId}. Got: ${typeof value}. Expected: boolean`);
139
+ throw new Error(
140
+ `Invalid Parameter Type: ${parameterId}. Got: ${typeof value}. Expected: boolean`
141
+ );
139
142
  }
140
143
 
141
144
  if (parameter.type === 'object' && typeof value !== 'object') {
@@ -185,7 +188,7 @@ class Manager extends EventEmitter {
185
188
  path = `${path}?${queryString}`;
186
189
  }
187
190
 
188
- const pendingCallId = `${operationId}::${path}`
191
+ const pendingCallId = `${operationId}::${path}`;
189
192
 
190
193
  if (
191
194
  operation.method.toLowerCase() === 'get' &&
@@ -203,6 +206,7 @@ class Manager extends EventEmitter {
203
206
  const pendingCall = this.__request({
204
207
  $validate,
205
208
  $cache,
209
+ $updateCache,
206
210
  $timeout,
207
211
  $socket,
208
212
  operationId,
@@ -211,7 +215,7 @@ class Manager extends EventEmitter {
211
215
  body,
212
216
  query,
213
217
  headers,
214
- ...args
218
+ ...args,
215
219
  });
216
220
 
217
221
  if (
@@ -221,23 +225,26 @@ class Manager extends EventEmitter {
221
225
  Object.keys(body).length === 0
222
226
  ) {
223
227
  this.__pendingCalls[pendingCallId] = pendingCall;
224
- this.__pendingCalls[pendingCallId].catch(() => {
225
- // We do nothing with the error here the caller is responsible. We just want to
226
- // cleanup the pending call.
227
- }).finally(() => {
228
- delete this.__pendingCalls[pendingCallId];
229
- });
228
+ this.__pendingCalls[pendingCallId]
229
+ .catch(() => {
230
+ // We do nothing with the error here the caller is responsible. We just want to
231
+ // cleanup the pending call.
232
+ })
233
+ .finally(() => {
234
+ delete this.__pendingCalls[pendingCallId];
235
+ });
230
236
  }
231
237
 
232
238
  const result = await pendingCall;
233
239
 
234
240
  return result;
235
241
  },
236
- });
242
+ }
243
+ );
237
244
  }
238
245
  }
239
246
 
240
- async __request({ $cache, $timeout, $socket, operationId, operation, path, body, headers, ...args }) {
247
+ async __request({ $cache, $updateCache, $timeout, $socket, operationId, operation, path, body, headers, ...args }) {
241
248
  let result;
242
249
  const benchmark = Util.benchmark();
243
250
 
@@ -269,34 +276,47 @@ class Manager extends EventEmitter {
269
276
  // send the API request to socket.io.
270
277
  // This is about ~2x faster than HTTP
271
278
  if (this.homey.isConnected() && $socket === true) {
272
- result = await Util.timeout(new Promise((resolve, reject) => {
273
- this.__debug(`IO ${operationId}`);
274
- this.homey.__homeySocket.emit('api', {
275
- args,
276
- operation: operationId,
277
- uri: this.uri,
278
- }, (err, result) => {
279
- // String Error
280
- if (typeof err === 'string') {
281
- err = new HomeyAPIError({
282
- error: err,
283
- }, 500);
284
- return reject(err);
285
- }
279
+ result = await Util.timeout(
280
+ new Promise((resolve, reject) => {
281
+ this.__debug(`IO ${operationId}`);
282
+ this.homey.__homeySocket.emit(
283
+ 'api',
284
+ {
285
+ args,
286
+ operation: operationId,
287
+ uri: this.uri,
288
+ },
289
+ (err, result) => {
290
+ // String Error
291
+ if (typeof err === 'string') {
292
+ err = new HomeyAPIError(
293
+ {
294
+ error: err,
295
+ },
296
+ 500
297
+ );
298
+ return reject(err);
299
+ }
286
300
 
287
- // Object Error
288
- if (typeof err === 'object' && err !== null) {
289
- err = new HomeyAPIError({
290
- stack: err.stack,
291
- error: err.error,
292
- error_description: err.error_description,
293
- }, err.statusCode || err.code || 500);
294
- return reject(err);
295
- }
301
+ // Object Error
302
+ if (typeof err === 'object' && err !== null) {
303
+ err = new HomeyAPIError(
304
+ {
305
+ stack: err.stack,
306
+ error: err.error,
307
+ error_description: err.error_description,
308
+ },
309
+ err.statusCode || err.code || 500
310
+ );
311
+ return reject(err);
312
+ }
296
313
 
297
- return resolve(result);
298
- });
299
- }), $timeout);
314
+ return resolve(result);
315
+ }
316
+ );
317
+ }),
318
+ $timeout
319
+ );
300
320
  } else {
301
321
  // Get from HTTP
302
322
  result = await this.homey.call({
@@ -324,7 +344,7 @@ class Manager extends EventEmitter {
324
344
  properties: props,
325
345
  });
326
346
 
327
- if (this.isConnected()) {
347
+ if (this.isConnected() && $updateCache === true) {
328
348
  this.__cache[ItemClass.ID][item.id] = item;
329
349
  }
330
350
 
@@ -337,7 +357,7 @@ class Manager extends EventEmitter {
337
357
  for (let props of Object.values(result)) {
338
358
  props = ItemClass.transformGet(props);
339
359
 
340
- if (this.isConnected() && this.__cache[ItemClass.ID][props.id]) {
360
+ if (this.isConnected() && $updateCache === true && this.__cache[ItemClass.ID][props.id]) {
341
361
  items[props.id] = this.__cache[ItemClass.ID][props.id];
342
362
  items[props.id].__update(props);
343
363
  } else {
@@ -348,14 +368,14 @@ class Manager extends EventEmitter {
348
368
  properties: props,
349
369
  });
350
370
 
351
- if (this.isConnected()) {
371
+ if (this.isConnected() && $updateCache === true) {
352
372
  this.__cache[ItemClass.ID][props.id] = items[props.id];
353
373
  }
354
374
  }
355
375
  }
356
376
 
357
377
  // Find and delete deleted items from cache
358
- if (this.__cache[ItemClass.ID]) {
378
+ if (this.__cache[ItemClass.ID] && $updateCache === true) {
359
379
  for (const cachedItem of Object.values(this.__cache[ItemClass.ID])) {
360
380
  if (!items[cachedItem.id]) {
361
381
  delete this.__cache[ItemClass.ID][cachedItem.id];
@@ -364,7 +384,7 @@ class Manager extends EventEmitter {
364
384
  }
365
385
 
366
386
  // Mark cache as complete
367
- if (this.isConnected()) {
387
+ if (this.isConnected() && $updateCache === true) {
368
388
  this.__cacheAllComplete[ItemClass.ID] = true;
369
389
  }
370
390
 
@@ -377,7 +397,7 @@ class Manager extends EventEmitter {
377
397
 
378
398
  props = ItemClass.transformGet(props);
379
399
 
380
- if (this.isConnected() && this.__cache[ItemClass.ID][props.id]) {
400
+ if (this.isConnected() && $updateCache === true && this.__cache[ItemClass.ID][props.id]) {
381
401
  item = this.__cache[ItemClass.ID][props.id];
382
402
  item.__update(props);
383
403
  } else {
@@ -388,7 +408,7 @@ class Manager extends EventEmitter {
388
408
  properties: { ...props },
389
409
  });
390
410
 
391
- if (this.isConnected()) {
411
+ if (this.isConnected() && $updateCache === true) {
392
412
  this.__cache[ItemClass.ID][props.id] = item;
393
413
  }
394
414
  }
@@ -396,7 +416,7 @@ class Manager extends EventEmitter {
396
416
  return item;
397
417
  }
398
418
  case 'deleteOne': {
399
- if (this.isConnected() && this.__cache[ItemClass.ID][args.id]) {
419
+ if (this.isConnected() && $updateCache === true && this.__cache[ItemClass.ID][args.id]) {
400
420
  this.__cache[ItemClass.ID][args.id].destroy();
401
421
  delete this.__cache[ItemClass.ID][args.id];
402
422
  }
@@ -450,19 +470,17 @@ class Manager extends EventEmitter {
450
470
  // If disconnecting, await that first
451
471
  try {
452
472
  await this.__disconnectPromise;
453
- // eslint-disable-next-line no-empty
454
- } catch (err) {
455
-
456
- }
473
+ // eslint-disable-next-line no-empty
474
+ } catch (err) {}
457
475
 
458
476
  if (this.__connectPromise) {
459
- await this.__connectPromise
477
+ await this.__connectPromise;
460
478
  return;
461
479
  }
462
480
 
463
481
  if (this.io) {
464
482
  await this.io;
465
- return
483
+ return;
466
484
  }
467
485
 
468
486
  this.__connectPromise = Promise.resolve().then(async () => {
@@ -492,9 +510,7 @@ class Manager extends EventEmitter {
492
510
  this.__debug('onEvent', event);
493
511
 
494
512
  // Transform & add to cache if this is a CRUD event
495
- if (event.endsWith('.create')
496
- || event.endsWith('.update')
497
- || event.endsWith('.delete')) {
513
+ if (event.endsWith('.create') || event.endsWith('.update') || event.endsWith('.delete')) {
498
514
  const [itemId, operation] = event.split('.');
499
515
  const itemName = this.itemNames[itemId];
500
516
  const ItemClass = this.itemClasses[itemName];
@@ -554,7 +570,7 @@ class Manager extends EventEmitter {
554
570
 
555
571
  // Delete the connecting Promise
556
572
  this.__connectPromise
557
- .catch(() => {
573
+ .catch(() => {
558
574
  delete this.io;
559
575
  })
560
576
  .finally(() => {
@@ -574,18 +590,14 @@ class Manager extends EventEmitter {
574
590
  // If connecting, await that first
575
591
  try {
576
592
  await this.__connectPromise;
577
- // eslint-disable-next-line no-empty
578
- } catch (err) {
579
-
580
- }
593
+ // eslint-disable-next-line no-empty
594
+ } catch (err) {}
581
595
 
582
596
  this.__disconnectPromise = Promise.resolve().then(async () => {
583
597
  this.__connected = false;
584
598
 
585
599
  if (this.io) {
586
- await this.io
587
- .then(io => io.unsubscribe())
588
- .catch(err => this.__debug('Error Disconnecting:', err));
600
+ await this.io.then(io => io.unsubscribe()).catch(err => this.__debug('Error Disconnecting:', err));
589
601
 
590
602
  delete this.io;
591
603
  }
@@ -593,7 +605,7 @@ class Manager extends EventEmitter {
593
605
 
594
606
  // Delete the disconnecting Promise
595
607
  this.__disconnectPromise
596
- .catch(() => { })
608
+ .catch(() => {})
597
609
  .finally(() => {
598
610
  delete this.__disconnectPromise;
599
611
  });
@@ -618,9 +630,8 @@ class Manager extends EventEmitter {
618
630
  this.removeAllListeners();
619
631
 
620
632
  // Disconnect from Socket.io
621
- this.disconnect().catch(() => { });
633
+ this.disconnect().catch(() => {});
622
634
  }
623
-
624
635
  }
625
636
 
626
637
  module.exports = Manager;
@@ -127,31 +127,6 @@ class Device extends Item {
127
127
  this.manager.scheduleRefresh();
128
128
  }
129
129
 
130
- refreshCapabilityInstances() {
131
- Object.entries(this.__capabilityInstances).forEach(([capabilityId, capabilityInstances]) => {
132
- const value = this.capabilitiesObj
133
- ? this.capabilitiesObj[capabilityId] != null
134
- ? this.capabilitiesObj[capabilityId].value
135
- : null
136
- : null;
137
-
138
- const lastUpdated = this.capabilitiesObj
139
- ? this.capabilitiesObj[capabilityId] != null
140
- ? this.capabilitiesObj[capabilityId].lastUpdated
141
- : null
142
- : null;
143
-
144
- for (const capabilityInstance of capabilityInstances) {
145
- capabilityInstance.__onCapabilityValue({
146
- capabilityId,
147
- value,
148
- transactionId: Util.uuid(),
149
- transactionTime: lastUpdated
150
- });
151
- }
152
- });
153
- }
154
-
155
130
  /**
156
131
  * Get the device's zone.
157
132
  * @returns {Promise<HomeyAPIV3.ManagerZones.Zone>}
@@ -96,16 +96,24 @@ class DeviceCapability extends EventEmitter {
96
96
  transactionTime,
97
97
  }) {
98
98
  if (capabilityId !== this.id) return;
99
+
99
100
  if (this.__transactionIds[transactionId]) {
100
101
  delete this.__transactionIds[transactionId];
101
102
  return;
102
103
  }
103
104
 
105
+ const nextLastChanged = new Date(transactionTime);
106
+
107
+ if (this.__lastChanged != null && nextLastChanged.getTime() === this.__lastChanged.getTime()) {
108
+ return;
109
+ }
110
+
104
111
  this.__value = value;
105
- this.__lastChanged = new Date(transactionTime);
112
+ this.__lastChanged = nextLastChanged;
106
113
 
107
114
  // Mutate the current device capabilitiesObj so it always reflects the last value.
108
115
  const capabilityReference = this.device.capabilitiesObj && this.device.capabilitiesObj[this.id];
116
+
109
117
  if (capabilityReference) {
110
118
  capabilityReference.value = value;
111
119
  capabilityReference.lastUpdated = this.__lastChanged;
@@ -3,14 +3,14 @@
3
3
  const Manager = require('./Manager');
4
4
  const Capability = require('./ManagerDevices/Capability');
5
5
  const Device = require('./ManagerDevices/Device');
6
+ const Util = require('../../Util');
6
7
 
7
8
  class ManagerDevices extends Manager {
8
-
9
9
  static CRUD = {
10
10
  ...super.CRUD,
11
11
  Capability,
12
12
  Device,
13
- }
13
+ };
14
14
 
15
15
  scheduleRefresh() {
16
16
  if (this.__refreshTimeout) {
@@ -24,20 +24,48 @@ class ManagerDevices extends Manager {
24
24
  this.__refreshTimeout = setTimeout(() => {
25
25
  if (this.isConnected()) {
26
26
  this.__debug('Refreshing devices...');
27
- this.__pendingRefreshDevicesCall = this.getDevices({ $cache: false }).then(devices => {
28
- for (const device of Object.values(devices)) {
29
- device.refreshCapabilityInstances();
30
- this.emit('device.update', device);
31
- }
32
- }).catch(err => {
33
- this.__debug('Failed to refresh devices.', err);
34
- }).finally(() => {
35
- this.__pendingRefreshDevicesCall = null;
36
- });
27
+ this.__pendingRefreshDevicesCall = this.getDevices({ $cache: false, $updateCache: false })
28
+ .then(devices => {
29
+ const cache = this.__cache['device'];
30
+
31
+ for (const device of Object.values(devices)) {
32
+ const cachedDevice = cache[device.id];
33
+
34
+ if (cachedDevice != null) {
35
+ Object.entries(cachedDevice.__capabilityInstances).forEach(([capabilityId, capabilityInstances]) => {
36
+ const value = device.capabilitiesObj
37
+ ? device.capabilitiesObj[capabilityId] != null
38
+ ? device.capabilitiesObj[capabilityId].value
39
+ : null
40
+ : null;
41
+
42
+ const lastUpdated = device.capabilitiesObj
43
+ ? device.capabilitiesObj[capabilityId] != null
44
+ ? device.capabilitiesObj[capabilityId].lastUpdated
45
+ : null
46
+ : null;
47
+
48
+ for (const capabilityInstance of capabilityInstances) {
49
+ capabilityInstance.__onCapabilityValue({
50
+ capabilityId,
51
+ value,
52
+ transactionId: Util.uuid(),
53
+ transactionTime: lastUpdated,
54
+ });
55
+ }
56
+ });
57
+ }
58
+ }
59
+ })
60
+ .catch(err => {
61
+ this.__debug('Failed to refresh devices.', err);
62
+ })
63
+ .finally(() => {
64
+ this.__pendingRefreshDevicesCall = null;
65
+ });
37
66
  }
38
67
  }, 1000);
39
68
  }
40
-
41
69
  }
42
70
 
43
71
  module.exports = ManagerDevices;
@@ -16,6 +16,9 @@ const ManagerUsers = require('./HomeyAPIV3/ManagerUsers');
16
16
  // eslint-disable-next-line no-unused-vars
17
17
  const Manager = require('./HomeyAPIV3/Manager');
18
18
 
19
+ const tierCache = {};
20
+ const versionCache = {};
21
+
19
22
  /**
20
23
  * An authenticated Homey API. Do not construct this class manually.
21
24
  * @class
@@ -111,7 +114,9 @@ class HomeyAPIV3 extends HomeyAPI {
111
114
  get baseUrl() {
112
115
  return (async () => {
113
116
  if (!this.__baseUrlPromise) {
114
- this.__baseUrlPromise = this.discoverBaseUrl().then(({ baseUrl }) => baseUrl);
117
+ this.__baseUrlPromise = this.discoverBaseUrl().then(({ baseUrl }) => {
118
+ return baseUrl;
119
+ });
115
120
  this.__baseUrlPromise.catch(() => { });
116
121
  }
117
122
 
@@ -119,6 +124,14 @@ class HomeyAPIV3 extends HomeyAPI {
119
124
  })();
120
125
  }
121
126
 
127
+ get tier() {
128
+ return tierCache[this.id];
129
+ }
130
+
131
+ get version() {
132
+ return versionCache[this.id];
133
+ }
134
+
122
135
  get strategyId() {
123
136
  return this.__strategyId;
124
137
  }
@@ -425,9 +438,13 @@ class HomeyAPIV3 extends HomeyAPI {
425
438
  const resStatusText = res.status;
426
439
  const resHeadersContentType = res.headers.get('Content-Type');
427
440
  const version = res.headers.get('x-homey-version');
428
- if (version) this.version = version;
441
+ if (version) {
442
+ versionCache[this.id] = version;
443
+ }
429
444
  const tier = res.headers.get('x-homey-tier');
430
- if (tier) this.tier = tier;
445
+ if (tier) {
446
+ tierCache[this.id] = tier;
447
+ }
431
448
 
432
449
  const resBodyText = await res.text();
433
450
  let resBodyJson;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homey-api",
3
- "version": "3.4.10",
3
+ "version": "3.4.11",
4
4
  "description": "Homey API",
5
5
  "main": "index.js",
6
6
  "files": [