homey-api 3.0.10 → 3.0.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.
@@ -40,6 +40,7 @@ class User extends APIDefinition {
40
40
  */
41
41
  getHomeyById(id) {
42
42
  const homey = this.homeys.find(homey => homey.id === id);
43
+
43
44
  if (!homey) {
44
45
  throw new Error(`Homey Not Found: ${id}`);
45
46
  }
@@ -52,6 +53,7 @@ class User extends APIDefinition {
52
53
  */
53
54
  getFirstHomey() {
54
55
  const homey = this.homeys[0];
56
+
55
57
  if (!homey) {
56
58
  throw new Error('No Homey Available');
57
59
  }
@@ -126,17 +126,10 @@ for(const {@link HomeyAPIV2.ManagerDevices.Device device} of Object.values(devic
126
126
  */
127
127
  async isLoggedIn() {
128
128
  const store = await this.__getStore();
129
- if (!store.token
130
- || !store.token.access_token) return false;
131
129
 
132
- try {
133
- this.__user = await this.getAuthenticatedUser({
134
- $cache: false,
135
- });
136
- return true;
137
- } catch (err) {
138
- return false;
139
- }
130
+ if (!store.token || !store.token.access_token) return false;
131
+
132
+ return true;
140
133
  }
141
134
 
142
135
  /**
@@ -145,20 +138,56 @@ for(const {@link HomeyAPIV2.ManagerDevices.Device device} of Object.values(devic
145
138
  * @param {object} [opts.$cache=true] - Use the cache
146
139
  * @returns {Promise<AthomCloudAPI.User>}
147
140
  */
148
- async getAuthenticatedUser({
149
- $cache = true,
150
- } = {}) {
141
+ async getAuthenticatedUser({ $cache = true } = {}) {
151
142
  if ($cache === true && this.__user instanceof User) {
152
143
  return this.__user;
153
144
  }
154
145
 
146
+ const properties = await this.call({
147
+ method: 'get',
148
+ path: '/user/me',
149
+ });
150
+
155
151
  this.__user = new User({
156
152
  api: this,
157
- properties: await this.call({
158
- method: 'get',
159
- path: '/user/me',
160
- }),
153
+ properties: properties,
161
154
  });
155
+
156
+ return this.__user;
157
+ }
158
+
159
+ async getAuthenticatedUserFromStore({ $cache = true } = {}) {
160
+ if ($cache === true && this.__user instanceof User) {
161
+ return this.__user;
162
+ }
163
+
164
+ if ($cache === true && this.__user == null) {
165
+ const store = await this.__getStore();
166
+
167
+ if (store.user) {
168
+ this.__user = new User({
169
+ api: this,
170
+ properties: store.user,
171
+ });
172
+
173
+ return this.__user;
174
+ }
175
+ }
176
+
177
+ const properties = await this.call({
178
+ method: 'get',
179
+ path: '/user/me',
180
+ });
181
+
182
+ this.__user = new User({
183
+ api: this,
184
+ properties: properties,
185
+ });
186
+
187
+ await this.__setStore({
188
+ user: properties,
189
+ })
190
+
162
191
  return this.__user;
163
192
  }
164
193
 
@@ -201,6 +230,31 @@ for(const {@link HomeyAPIV2.ManagerDevices.Device device} of Object.values(devic
201
230
  return `${this.baseUrl}/oauth2/authorise?${search.toString()}`;
202
231
  }
203
232
 
233
+ async getDelegatedLoginUrl(args = {}) {
234
+ if (args.baseUrl == null) {
235
+ throw new TypeError('baseUrl is required');
236
+ }
237
+
238
+ const token = await this.createDelegationToken({
239
+ audience: args.audience,
240
+ meta: args.meta,
241
+ });
242
+
243
+ const search = new URLSearchParams();
244
+ search.append('user_token', token);
245
+
246
+ if (typeof args.state === 'string') {
247
+ search.append('state', args.state);
248
+ }
249
+
250
+ if (typeof args.resource === 'string') {
251
+ search.append('resource', args.resource);
252
+ }
253
+
254
+ const seperator = args.baseUrl.indexOf('?') >= 0 ? '&' : '?';
255
+ return args.baseUrl + seperator + search.toString();
256
+ }
257
+
204
258
  /**
205
259
  * Logout and delete the local token.
206
260
  * @returns {Promise<void>}
@@ -70,6 +70,7 @@ class EventEmitter {
70
70
  * @param {function} callback
71
71
  */
72
72
  off(event, callback) {
73
+ this.__listeners[event] = this.__listeners[event] || [];
73
74
  this.__listeners[event] = this.__listeners[event].filter(cb => cb !== callback);
74
75
 
75
76
  return this;
@@ -13,7 +13,6 @@ class Device extends DeviceV3 {
13
13
  delete item.zoneName;
14
14
  return item;
15
15
  }
16
-
17
16
  }
18
17
 
19
18
  module.exports = Device;
@@ -19,7 +19,7 @@ class Driver extends DriverV3 {
19
19
 
20
20
  return item;
21
21
  }
22
-
22
+
23
23
  }
24
24
 
25
25
  module.exports = Driver;
@@ -35,14 +35,26 @@ class Manager extends EventEmitter {
35
35
  });
36
36
 
37
37
  // Set Items
38
- Object.defineProperty(this, 'items', {
38
+ Object.defineProperty(this, 'itemClasses', {
39
39
  value: Object.entries(items).reduce((obj, [itemName, item]) => {
40
40
  const ItemClass = this.constructor.CRUD[itemName]
41
41
  ? this.constructor.CRUD[itemName]
42
- // eslint-disable-next-line no-eval
43
- : eval(`(class ${itemName} extends Item {})`);
42
+ : (() => {
43
+ return class extends Item {};
44
+ })();
45
+
44
46
  ItemClass.ID = item.id;
45
- obj[item.id] = ItemClass;
47
+ obj[itemName] = ItemClass;
48
+
49
+ return obj;
50
+ }, {}),
51
+ enumerable: false,
52
+ writable: false,
53
+ });
54
+
55
+ Object.defineProperty(this, 'itemNames', {
56
+ value: Object.entries(items).reduce((obj, [itemName, item]) => {
57
+ obj[item.id] = itemName;
46
58
 
47
59
  return obj;
48
60
  }, {}),
@@ -76,6 +88,12 @@ class Manager extends EventEmitter {
76
88
  writable: false,
77
89
  });
78
90
 
91
+ Object.defineProperty(this, '__pendingCalls', {
92
+ value: {},
93
+ enumerable: false,
94
+ writable: false,
95
+ });
96
+
79
97
  // Create methods
80
98
  for (const [operationId, operation] of Object.entries(operations)) {
81
99
  Object.defineProperty(this,
@@ -181,181 +199,219 @@ class Manager extends EventEmitter {
181
199
  path = `${path}?${queryString}`;
182
200
  }
183
201
 
184
- let result;
185
- const benchmark = Util.benchmark();
186
-
187
- // If connected to Socket.io,
188
- // try to get the CRUD Item from Cache.
189
- if (this.isConnected() && operation.crud && $cache === true) {
190
- const itemId = items[operation.crud.item].id;
191
-
192
- switch (operation.crud.type) {
193
- case 'getOne': {
194
- if (this.__cache[itemId][args.id]) {
195
- return this.__cache[itemId][args.id];
196
- }
202
+ if (
203
+ operation.method.toLowerCase() === 'get' &&
204
+ $cache === true &&
205
+ this.__pendingCalls[path] != null &&
206
+ Object.keys(body).length === 0
207
+ ) {
208
+ this.__debug(`Reusing pending call ${operationId}`);
209
+ const result = await this.__pendingCalls[path];
197
210
 
198
- break;
199
- }
200
- case 'getAll': {
201
- if (this.__cache[itemId]
202
- && this.__cacheAllComplete[itemId]) {
203
- return this.__cache[itemId];
204
- }
205
- break;
206
- }
207
- default:
208
- break;
209
- }
211
+ return result;
210
212
  }
211
213
 
212
- // If Homey is connected to Socket.io,
213
- // send the API request to socket.io.
214
- // This is about ~2x faster than HTTP
215
- if (this.homey.isConnected() && $socket === true) {
216
- result = await Util.timeout(new Promise((resolve, reject) => {
217
- this.__debug(`IO ${operationId}`);
218
- this.homey.__ioNamespace.emit('api', {
219
- args,
220
- operation: operationId,
221
- uri: this.uri,
222
- }, (err, result) => {
223
- // String Error
224
- if (typeof err === 'string') {
225
- err = new HomeyAPIError({
226
- error: err,
227
- }, 500);
228
- return reject(err);
229
- }
230
-
231
- // Object Error
232
- if (typeof err === 'object' && err !== null) {
233
- err = new HomeyAPIError({
234
- stack: err.stack,
235
- error: err.error,
236
- error_description: err.error_description,
237
- }, err.statusCode || err.code || 500);
238
- return reject(err);
239
- }
240
-
241
- return resolve(result);
242
- });
243
- }), $timeout);
244
- } else {
245
- // Get from HTTP
246
- result = await this.homey.call({
214
+ this.__pendingCalls[path] = (async () => {
215
+ const result = await this.__request({
216
+ $validate,
217
+ $cache,
247
218
  $timeout,
248
- headers,
219
+ $socket,
220
+ operationId,
221
+ operation,
222
+ path,
249
223
  body,
250
- path: `/api/manager/${this.constructor.ID}${path}`,
251
- method: operation.method,
224
+ query,
225
+ headers,
226
+ ...args
252
227
  });
253
- }
254
228
 
255
- // Transform and cache output if this is a CRUD call
256
- if (operation.crud) {
257
- const itemId = items[operation.crud.item].id;
258
- const Item = this.items[itemId];
259
-
260
- switch (operation.crud.type) {
261
- case 'getOne': {
262
- let item = { ...result };
263
- item = Item.transformGet(item);
264
- item = new Item({
265
- id: item.id,
266
- homey: this.homey,
267
- manager: this,
268
- properties: { ...item },
269
- });
229
+ return result;
230
+ })().finally(() => {
231
+ delete this.__pendingCalls[path];
232
+ });
270
233
 
271
- if (this.isConnected()) {
272
- this.__cache[itemId][item.id] = item;
273
- }
234
+ const result = await this.__pendingCalls[path];
274
235
 
275
- return item;
276
- }
277
- case 'getAll': {
278
- const items = {};
279
-
280
- // Add all to cache
281
- for (let item of Object.values(result)) {
282
- item = Item.transformGet(item);
283
-
284
- if (this.isConnected() && this.__cache[itemId][item.id]) {
285
- items[item.id] = this.__cache[itemId][item.id];
286
- items[item.id].__update(item);
287
- } else {
288
- items[item.id] = new Item({
289
- id: item.id,
290
- homey: this.homey,
291
- manager: this,
292
- properties: { ...item },
293
- });
294
-
295
- if (this.isConnected()) {
296
- this.__cache[itemId][item.id] = items[item.id];
297
- }
298
- }
299
- }
300
-
301
- // Find and delete deleted items from cache
302
- if (this.__cache[itemId]) {
303
- for (const cachedItem of Object.values(this.__cache[itemId])) {
304
- if (!items[cachedItem.id]) {
305
- delete this.__cache[itemId][cachedItem.id];
306
- }
307
- }
308
- }
309
-
310
- // Mark cache as complete
311
- if (this.isConnected()) {
312
- this.__cacheAllComplete[itemId] = true;
313
- }
236
+ return result;
237
+ },
238
+ });
239
+ }
240
+ }
314
241
 
315
- return items;
316
- }
317
- case 'createOne':
318
- case 'updateOne': {
319
- let item = { ...result };
320
- item = Item.transformGet(item);
321
-
322
- if (this.isConnected() && this.__cache[itemId][item.id]) {
323
- item = this.__cache[itemId][item.id];
324
- item.__update(item);
325
- } else {
326
- item = Item.transformGet(item);
327
- item = new Item({
328
- id: item.id,
329
- homey: this.homey,
330
- manager: this,
331
- properties: { ...item },
332
- });
242
+ async __request({ $cache, $timeout, $socket, operationId, operation, path, body, headers, ...args }) {
243
+ let result;
244
+ const benchmark = Util.benchmark();
245
+
246
+ // If connected to Socket.io,
247
+ // try to get the CRUD Item from Cache.
248
+ if (this.isConnected() && operation.crud && $cache === true) {
249
+ const itemId = this.itemClasses[operation.crud.item].ID;
250
+
251
+ switch (operation.crud.type) {
252
+ case 'getOne': {
253
+ if (this.__cache[itemId][args.id]) {
254
+ return this.__cache[itemId][args.id];
255
+ }
256
+
257
+ break;
258
+ }
259
+ case 'getAll': {
260
+ if (this.__cache[itemId] && this.__cacheAllComplete[itemId]) {
261
+ return this.__cache[itemId];
262
+ }
263
+ break;
264
+ }
265
+ default:
266
+ break;
267
+ }
268
+ }
333
269
 
334
- if (this.isConnected()) {
335
- this.__cache[itemId][item.id] = item;
336
- }
337
- }
270
+ // If Homey is connected to Socket.io,
271
+ // send the API request to socket.io.
272
+ // This is about ~2x faster than HTTP
273
+ if (this.homey.isConnected() && $socket === true) {
274
+ result = await Util.timeout(new Promise((resolve, reject) => {
275
+ this.__debug(`IO ${operationId}`);
276
+ this.homey.__ioNamespace.emit('api', {
277
+ args,
278
+ operation: operationId,
279
+ uri: this.uri,
280
+ }, (err, result) => {
281
+ // String Error
282
+ if (typeof err === 'string') {
283
+ err = new HomeyAPIError({
284
+ error: err,
285
+ }, 500);
286
+ return reject(err);
287
+ }
288
+
289
+ // Object Error
290
+ if (typeof err === 'object' && err !== null) {
291
+ err = new HomeyAPIError({
292
+ stack: err.stack,
293
+ error: err.error,
294
+ error_description: err.error_description,
295
+ }, err.statusCode || err.code || 500);
296
+ return reject(err);
297
+ }
298
+
299
+ return resolve(result);
300
+ });
301
+ }), $timeout);
302
+ } else {
303
+ // Get from HTTP
304
+ result = await this.homey.call({
305
+ $timeout,
306
+ headers,
307
+ body,
308
+ path: `/api/manager/${this.constructor.ID}${path}`,
309
+ method: operation.method,
310
+ });
311
+ }
338
312
 
339
- return item;
340
- }
341
- case 'deleteOne': {
342
- if (this.isConnected() && this.__cache[itemId][args.id]) {
343
- this.__cache[itemId][args.id].destroy();
344
- delete this.__cache[itemId][args.id];
345
- }
313
+ // Transform and cache output if this is a CRUD call
314
+ if (operation.crud) {
315
+ const ItemClass = this.itemClasses[operation.crud.item];
316
+
317
+ switch (operation.crud.type) {
318
+ case 'getOne': {
319
+ let props = { ...result };
320
+ props = ItemClass.transformGet(props);
321
+
322
+ const item = new ItemClass({
323
+ id: props.id,
324
+ homey: this.homey,
325
+ manager: this,
326
+ properties: props,
327
+ });
328
+
329
+ if (this.isConnected()) {
330
+ this.__cache[ItemClass.ID][item.id] = item;
331
+ }
332
+
333
+ return item;
334
+ }
335
+ case 'getAll': {
336
+ const items = {};
337
+
338
+ // Add all to cache
339
+ for (let props of Object.values(result)) {
340
+ props = ItemClass.transformGet(props);
341
+
342
+ if (this.isConnected() && this.__cache[ItemClass.ID][props.id]) {
343
+ items[props.id] = this.__cache[ItemClass.ID][props.id];
344
+ items[props.id].__update(props);
345
+ } else {
346
+ items[props.id] = new ItemClass({
347
+ id: props.id,
348
+ homey: this.homey,
349
+ manager: this,
350
+ properties: props,
351
+ });
346
352
 
347
- return undefined;
348
- }
349
- default:
350
- break;
353
+ if (this.isConnected()) {
354
+ this.__cache[ItemClass.ID][props.id] = items[props.id];
351
355
  }
352
356
  }
357
+ }
353
358
 
354
- this.__debug(`${operationId} took ${benchmark()}ms`);
355
- return result;
356
- },
357
- });
359
+ // Find and delete deleted items from cache
360
+ if (this.__cache[ItemClass.ID]) {
361
+ for (const cachedItem of Object.values(this.__cache[ItemClass.ID])) {
362
+ if (!items[cachedItem.id]) {
363
+ delete this.__cache[ItemClass.ID][cachedItem.id];
364
+ }
365
+ }
366
+ }
367
+
368
+ // Mark cache as complete
369
+ if (this.isConnected()) {
370
+ this.__cacheAllComplete[ItemClass.ID] = true;
371
+ }
372
+
373
+ return items;
374
+ }
375
+ case 'createOne':
376
+ case 'updateOne': {
377
+ let item = null;
378
+ let props = { ...result };
379
+
380
+ props = ItemClass.transformGet(props);
381
+
382
+ if (this.isConnected() && this.__cache[ItemClass.ID][props.id]) {
383
+ item = this.__cache[ItemClass.ID][props.id];
384
+ item.__update(props);
385
+ } else {
386
+ item = new ItemClass({
387
+ id: props.id,
388
+ homey: this.homey,
389
+ manager: this,
390
+ properties: { ...props },
391
+ });
392
+
393
+ if (this.isConnected()) {
394
+ this.__cache[ItemClass.ID][props.id] = item;
395
+ }
396
+ }
397
+
398
+ return item;
399
+ }
400
+ case 'deleteOne': {
401
+ if (this.isConnected() && this.__cache[ItemClass.ID][args.id]) {
402
+ this.__cache[ItemClass.ID][args.id].destroy();
403
+ delete this.__cache[ItemClass.ID][args.id];
404
+ }
405
+
406
+ return undefined;
407
+ }
408
+ default:
409
+ break;
410
+ }
358
411
  }
412
+
413
+ this.__debug(`${operationId} took ${benchmark()}ms`);
414
+ return result;
359
415
  }
360
416
 
361
417
  /**
@@ -426,40 +482,41 @@ class Manager extends EventEmitter {
426
482
  || event.endsWith('.update')
427
483
  || event.endsWith('.delete')) {
428
484
  const [itemId, operation] = event.split('.');
429
- const Item = this.items[itemId];
485
+ const itemName = this.itemNames[itemId];
486
+ const ItemClass = this.itemClasses[itemName];
430
487
 
431
488
  switch (operation) {
432
489
  case 'create': {
433
- data = Item.transformGet(data);
490
+ const props = ItemClass.transformGet(data);
434
491
 
435
- const item = new Item({
436
- id: data.id,
492
+ const item = new ItemClass({
493
+ id: props.id,
437
494
  homey: this.homey,
438
495
  manager: this,
439
- properties: { ...data },
496
+ properties: props,
440
497
  });
441
- this.__cache[itemId][data.id] = item;
498
+ this.__cache[ItemClass.ID][props.id] = item;
442
499
 
443
500
  return this.emit(event, item);
444
501
  }
445
502
  case 'update': {
446
- data = Item.transformGet(data);
503
+ const props = ItemClass.transformGet(data);
447
504
 
448
- if (this.__cache[itemId][data.id]) {
449
- const item = this.__cache[itemId][data.id];
450
- item.__update(data);
505
+ if (this.__cache[ItemClass.ID][props.id]) {
506
+ const item = this.__cache[ItemClass.ID][props.id];
507
+ item.__update(props);
451
508
  return this.emit(event, item);
452
509
  }
453
510
 
454
511
  break;
455
512
  }
456
513
  case 'delete': {
457
- data = Item.transformGet(data);
514
+ const props = ItemClass.transformGet(data);
458
515
 
459
- if (this.__cache[itemId][data.id]) {
460
- const item = this.__cache[itemId][data.id];
516
+ if (this.__cache[ItemClass.ID][props.id]) {
517
+ const item = this.__cache[ItemClass.ID][props.id];
461
518
  item.__delete();
462
- delete this.__cache[itemId][item.id];
519
+ delete this.__cache[ItemClass.ID][item.id];
463
520
  return this.emit(event, {
464
521
  id: item.id,
465
522
  });
@@ -10,6 +10,11 @@ const Item = require('../Item');
10
10
  */
11
11
  class Capability extends Item {
12
12
 
13
+ get uri() {
14
+ console.warn('Capability.uri is deprecated. Please use Capability.ownerUri instead.');
15
+ return undefined;
16
+ }
17
+
13
18
  }
14
19
 
15
20
  module.exports = Capability;
@@ -40,6 +40,8 @@ class Device extends Item {
40
40
  * onOffInstance.setValue(true).catch(console.error);
41
41
  */
42
42
  makeCapabilityInstance(capabilityId, listener) {
43
+ this.__debug('Creating capability instance for: ', capabilityId);
44
+
43
45
  this.connect().catch(err => {
44
46
  this.__debug(err);
45
47
  });
@@ -121,18 +123,20 @@ class Device extends Item {
121
123
  this.manager.getDevice({
122
124
  id: this.id,
123
125
  }).then(async device => {
124
- Object.entries(this.__capabilityInstances).forEach(([capabilityId, capabilityInstance]) => {
126
+ Object.entries(this.__capabilityInstances).forEach(([capabilityId, capabilityInstances]) => {
125
127
  const value = device.capabilitiesObj
126
128
  ? typeof device.capabilitiesObj[capabilityId] !== 'undefined'
127
129
  ? device.capabilitiesObj[capabilityId].value
128
130
  : null
129
131
  : null;
130
132
 
131
- capabilityInstance.__onCapabilityValue({
132
- capabilityId,
133
- value,
134
- transactionId: Util.uuid(),
135
- });
133
+ for (const capabilityInstance of capabilityInstances) {
134
+ capabilityInstance.__onCapabilityValue({
135
+ capabilityId,
136
+ value,
137
+ transactionId: Util.uuid(),
138
+ });
139
+ }
136
140
  });
137
141
  })
138
142
  // eslint-disable-next-line no-console
@@ -169,10 +173,10 @@ class Device extends Item {
169
173
 
170
174
  return Object.values(logs)
171
175
  .filter(log => log.ownerUri === this.uri)
172
- .reduce((result, log) => ({
173
- ...result,
174
- [log.id]: log,
175
- }), {});
176
+ .reduce((accumulator, log) => {
177
+ accumulator[log.id] = log;
178
+ return accumulator;
179
+ }, {});
176
180
  }
177
181
 
178
182
  /**
@@ -237,6 +241,16 @@ class Device extends Item {
237
241
  return item;
238
242
  }
239
243
 
244
+ get driverUri() {
245
+ console.warn('Device.driverUri is deprecated. Please use Device.driverId instead.');
246
+ return undefined;
247
+ }
248
+
249
+ get zoneName() {
250
+ console.warn('Device.zoneName is deprecated.');
251
+ return undefined;
252
+ }
253
+
240
254
  }
241
255
 
242
256
  module.exports = Device;
@@ -112,7 +112,7 @@ class DeviceCapability extends EventEmitter {
112
112
  capabilityReference.lastUpdated = this.__lastChanged;
113
113
  }
114
114
 
115
- this.__listener(value);
115
+ this.__listener(value, this);
116
116
  }
117
117
 
118
118
  __onDeviceDelete() {
@@ -160,7 +160,7 @@ class DeviceCapability extends EventEmitter {
160
160
  });
161
161
 
162
162
  this.__value = value;
163
- this.__lastChanged = new Date();
163
+ this.__lastChanged = transactionTime;
164
164
 
165
165
  // Mutate the current device capabilitiesObj so it always reflects the last value.
166
166
  const capabilityReference = this.device.capabilitiesObj && this.device.capabilitiesObj[this.id];
@@ -9,6 +9,16 @@ const Item = require('../Item');
9
9
  */
10
10
  class Driver extends Item {
11
11
 
12
+ get uri() {
13
+ console.warn('Driver.uri is deprecated. Please use Driver.ownerUri instead.');
14
+ return undefined;
15
+ }
16
+
17
+ get uriObj() {
18
+ console.warn('Driver.uriObj is deprecated.');
19
+ return undefined;
20
+ }
21
+
12
22
  }
13
23
 
14
24
  module.exports = Driver;
@@ -9,6 +9,14 @@ const Item = require('../Item');
9
9
  */
10
10
  class PairSession extends Item {
11
11
 
12
+ static transformGet(item) {
13
+ item = super.transformGet(item);
14
+
15
+ delete item.uri;
16
+
17
+ return item;
18
+ }
19
+
12
20
  }
13
21
 
14
22
  module.exports = PairSession;
@@ -10,6 +10,16 @@ const FlowCard = require('./FlowCard');
10
10
  */
11
11
  class FlowCardAction extends FlowCard {
12
12
 
13
+ get uri() {
14
+ console.warn('FlowCardAction.uri is deprecated. Use FlowCardAction.ownerUri instead.');
15
+ return undefined;
16
+ }
17
+
18
+ get uriObj() {
19
+ console.warn('FlowCardAction.uriObj is deprecated.');
20
+ return undefined;
21
+ }
22
+
13
23
  }
14
24
 
15
25
  module.exports = FlowCardAction;
@@ -10,6 +10,16 @@ const FlowCard = require('./FlowCard');
10
10
  */
11
11
  class FlowCardCondition extends FlowCard {
12
12
 
13
+ get uri() {
14
+ console.warn('FlowCardCondition.uri is deprecated. Use FlowCardCondition.ownerUri instead.');
15
+ return undefined;
16
+ }
17
+
18
+ get uriObj() {
19
+ console.warn('FlowCardCondition.uriObj is deprecated.');
20
+ return undefined;
21
+ }
22
+
13
23
  }
14
24
 
15
25
  module.exports = FlowCardCondition;
@@ -10,6 +10,16 @@ const FlowCard = require('./FlowCard');
10
10
  */
11
11
  class FlowCardTrigger extends FlowCard {
12
12
 
13
+ get uri() {
14
+ console.warn('FlowCardTrigger.uri is deprecated. Use FlowCardTrigger.ownerUri instead.');
15
+ return undefined;
16
+ }
17
+
18
+ get uriObj() {
19
+ console.warn('FlowCardTrigger.uriObj is deprecated.');
20
+ return undefined;
21
+ }
22
+
13
23
  }
14
24
 
15
25
  module.exports = FlowCardTrigger;
@@ -25,6 +25,21 @@ class FlowToken extends Item {
25
25
  return item;
26
26
  }
27
27
 
28
+ get uri() {
29
+ console.warn('FlowToken.uri is deprecated. Use FlowToken.ownerUri instead.');
30
+ return undefined;
31
+ }
32
+
33
+ get uriObj() {
34
+ console.warn('FlowToken.uriObj is deprecated.');
35
+ return undefined;
36
+ }
37
+
38
+ get ownerName() {
39
+ console.warn('FlowToken.ownerName is deprecated.');
40
+ return undefined;
41
+ }
42
+
28
43
  }
29
44
 
30
45
  module.exports = FlowToken;
@@ -24,6 +24,21 @@ class Log extends Item {
24
24
  return item;
25
25
  }
26
26
 
27
+ get uri() {
28
+ console.warn('Log.uri is deprecated. Use Log.ownerUri instead.');
29
+ return undefined;
30
+ }
31
+
32
+ get uriObj() {
33
+ console.warn('Log.uriObj is deprecated.');
34
+ return undefined;
35
+ }
36
+
37
+ get ownerName() {
38
+ console.warn('Log.ownerName is deprecated.');
39
+ return undefined;
40
+ }
41
+
27
42
  }
28
43
 
29
44
  module.exports = Log;
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ const Manager = require('./Manager');
4
+
5
+ class ManagerUsers extends Manager {
6
+
7
+ __userMe = null;
8
+
9
+ async getUserMe(...args) {
10
+ if (this.__userMe != null) {
11
+ return await this.__userMe;
12
+ }
13
+
14
+ // TODO
15
+ //
16
+ // - dont cache if args contains cache false
17
+ // - dont cache if not connected to manager users
18
+ // - remove on disonnect manager users
19
+ // - set the user in the users cache
20
+
21
+ this.__userMe = this.__super__getUserMe(...args).catch(err => {
22
+ this.__userMe = null;
23
+ throw err;
24
+ });
25
+
26
+ return await this.__userMe;
27
+ }
28
+
29
+ }
30
+
31
+ module.exports = ManagerUsers;
@@ -11,6 +11,7 @@ const ManagerDevices = require('./HomeyAPIV3/ManagerDevices');
11
11
  const ManagerFlow = require('./HomeyAPIV3/ManagerFlow');
12
12
  const ManagerFlowToken = require('./HomeyAPIV3/ManagerFlowToken');
13
13
  const ManagerInsights = require('./HomeyAPIV3/ManagerInsights');
14
+ const ManagerUsers = require('./HomeyAPIV3/ManagerUsers');
14
15
 
15
16
  // eslint-disable-next-line no-unused-vars
16
17
  const Manager = require('./HomeyAPIV3/Manager');
@@ -30,6 +31,7 @@ class HomeyAPIV3 extends HomeyAPI {
30
31
  ManagerFlow,
31
32
  ManagerFlowToken,
32
33
  ManagerInsights,
34
+ ManagerUsers,
33
35
  };
34
36
 
35
37
  constructor({
@@ -143,8 +145,9 @@ class HomeyAPIV3 extends HomeyAPI {
143
145
  if (!this.__managers[managerName]) {
144
146
  const ManagerClass = this.constructor.MANAGERS[managerName]
145
147
  ? this.constructor.MANAGERS[managerName]
146
- // eslint-disable-next-line no-eval
147
- : eval(`(class ${managerName} extends Manager {})`);
148
+ : (() => {
149
+ return class extends Manager {};
150
+ })();
148
151
 
149
152
  ManagerClass.ID = manager.id;
150
153
 
@@ -416,6 +419,11 @@ class HomeyAPIV3 extends HomeyAPI {
416
419
 
417
420
  const resStatusText = res.status;
418
421
  const resHeadersContentType = res.headers.get('Content-Type');
422
+ const version = res.headers.get('x-homey-version');
423
+ if (version) this.version = version;
424
+ const tier = res.headers.get('x-homey-tier');
425
+ if (tier) this.tier = tier;
426
+
419
427
  const resBodyText = await res.text();
420
428
  let resBodyJson;
421
429
  if (resHeadersContentType && resHeadersContentType.startsWith('application/json')) {
@@ -545,7 +553,11 @@ class HomeyAPIV3 extends HomeyAPI {
545
553
  await new Promise((resolve, reject) => {
546
554
  this.__ioNamespace.once('disconnect', reject);
547
555
  this.__ioNamespace.emit('subscribe', uri, err => {
548
- if (err) return reject(err);
556
+ if (err) {
557
+ this.__debug('Failed to subscribe', uri, err);
558
+ return reject(err)
559
+ }
560
+
549
561
  return resolve();
550
562
  });
551
563
  });
@@ -570,7 +582,11 @@ class HomeyAPIV3 extends HomeyAPI {
570
582
  await this.connect();
571
583
  await new Promise((resolve, reject) => {
572
584
  this.__ioNamespace.emit('subscribe', uri, err => {
573
- if (err) return reject(err);
585
+ if (err) {
586
+ this.__debug('Failed to subscribe', uri, err);
587
+ return reject(err)
588
+ }
589
+
574
590
  return resolve();
575
591
  });
576
592
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homey-api",
3
- "version": "3.0.10",
3
+ "version": "3.0.11",
4
4
  "description": "Homey API",
5
5
  "main": "index.js",
6
6
  "files": [