homey-api 3.0.0-rc.8 → 3.0.0

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.
package/index.browser.js CHANGED
@@ -11,3 +11,4 @@ global.HomeyCloudAPI = require('./lib/HomeyCloudAPI');
11
11
  global.HomeyAPIV2 = require('./lib/HomeyAPI/HomeyAPIV2');
12
12
  global.HomeyAPIV3Local = require('./lib/HomeyAPI/HomeyAPIV3Local');
13
13
  global.HomeyAPIV3Cloud = require('./lib/HomeyAPI/HomeyAPIV3Cloud');
14
+ global.HomeyAPI = require('./lib/HomeyAPI/HomeyAPI');
package/index.js CHANGED
@@ -2,23 +2,42 @@
2
2
 
3
3
  'use strict';
4
4
 
5
- module.exports = {
6
- AthomAppsAPI: require('./lib/AthomAppsAPI'),
7
- AthomBackupAPI: require('./lib/AthomBackupAPI'),
8
- AthomCallbackAPI: require('./lib/AthomCallbackAPI'),
9
- AthomCloudAPI: require('./lib/AthomCloudAPI'),
10
- AthomConnectAPI: require('./lib/AthomConnectAPI'),
11
- AthomDNSAPI: require('./lib/AthomDNSAPI'),
12
- AthomFirmwareAPI: require('./lib/AthomFirmwareAPI'),
13
- AthomFlowAPI: require('./lib/AthomFlowAPI'),
14
- AthomGeolocateAPI: require('./lib/AthomGeolocateAPI'),
15
- AthomIconsAPI: require('./lib/AthomIconsAPI'),
16
- AthomInfraredAPI: require('./lib/AthomInfraredAPI'),
17
- AthomNotificationsAPI: require('./lib/AthomNotificationsAPI'),
18
- AthomSetupAPI: require('./lib/AthomSetupAPI'),
19
- AthomStoreAPI: require('./lib/AthomStoreAPI'),
20
- AthomWeatherAPI: require('./lib/AthomWeatherAPI'),
21
- AthomWebhooksAPI: require('./lib/AthomWebhooksAPI'),
22
- HomeyCloudAPI: require('./lib/HomeyCloudAPI'),
23
- HomeyAPI: require('./lib/HomeyAPI/HomeyAPI'),
24
- };
5
+ module.exports = {};
6
+
7
+ // Athom Cloud
8
+ module.exports.AthomAppsAPI = require('./lib/AthomAppsAPI');
9
+ module.exports.AthomBackupAPI = require('./lib/AthomBackupAPI');
10
+ module.exports.AthomCallbackAPI = require('./lib/AthomCallbackAPI');
11
+ module.exports.AthomCloudAPI = require('./lib/AthomCloudAPI');
12
+ module.exports.AthomConnectAPI = require('./lib/AthomConnectAPI');
13
+ module.exports.AthomDNSAPI = require('./lib/AthomDNSAPI');
14
+ module.exports.AthomFirmwareAPI = require('./lib/AthomFirmwareAPI');
15
+ module.exports.AthomFlowAPI = require('./lib/AthomFlowAPI');
16
+ module.exports.AthomGeolocateAPI = require('./lib/AthomGeolocateAPI');
17
+ module.exports.AthomIconsAPI = require('./lib/AthomIconsAPI');
18
+ module.exports.AthomInfraredAPI = require('./lib/AthomInfraredAPI');
19
+ module.exports.AthomNotificationsAPI = require('./lib/AthomNotificationsAPI');
20
+ module.exports.AthomSetupAPI = require('./lib/AthomSetupAPI');
21
+ module.exports.AthomStoreAPI = require('./lib/AthomStoreAPI');
22
+ module.exports.AthomWeatherAPI = require('./lib/AthomWeatherAPI');
23
+ module.exports.AthomWebhooksAPI = require('./lib/AthomWebhooksAPI');
24
+
25
+ // Homey Cloud
26
+ module.exports.HomeyCloudAPI = require('./lib/HomeyCloudAPI');
27
+
28
+ // Homey API
29
+ module.exports.HomeyAPI = require('./lib/HomeyAPI/HomeyAPI');
30
+ module.exports.HomeyAPIV1 = require('./lib/HomeyAPI/HomeyAPIV1');
31
+ module.exports.HomeyAPIV2 = require('./lib/HomeyAPI/HomeyAPIV2');
32
+ module.exports.HomeyAPIV3 = require('./lib/HomeyAPI/HomeyAPIV3');
33
+ module.exports.HomeyAPIV3Local = require('./lib/HomeyAPI/HomeyAPIV3Local');
34
+ module.exports.HomeyAPIV3Cloud = require('./lib/HomeyAPI/HomeyAPIV3Cloud');
35
+
36
+ // APIError
37
+ module.exports.APIError = require('./lib/APIError');
38
+ module.exports.APIErrorHomeyOffline = require('./lib/APIErrorHomeyOffline');
39
+ module.exports.APIErrorNotFound = require('./lib/APIErrorNotFound');
40
+ module.exports.APIErrorTimeout = require('./lib/APIErrorTimeout');
41
+
42
+ // Util
43
+ module.exports.Util = require('./lib/Util');
package/lib/API.js CHANGED
@@ -73,6 +73,7 @@ class API {
73
73
  $body = {},
74
74
  $query = {},
75
75
  $headers = {},
76
+ $token = null,
76
77
  ...args
77
78
  } = {}) => {
78
79
  let pathWithParameters = operation.path;
@@ -89,6 +90,11 @@ class API {
89
90
  value = this.__secret;
90
91
  }
91
92
 
93
+ // Set Authorization header if $token is provided
94
+ if (typeof $token === 'string') {
95
+ headers.Authorization = `Bearer ${$token}`;
96
+ }
97
+
92
98
  // Validate the parameter
93
99
  if ($validate) {
94
100
  if (parameter.required === true && typeof value === 'undefined') {
@@ -2,6 +2,7 @@
2
2
 
3
3
  /**
4
4
  * Abstract storage adapter. To be extended by your own, or {@link AthomCloudAPI.StorageAdapterBrowser} or {@link AthomCloudAPI.StorageAdapterMemory}.
5
+ * When creating your own, make sure to overload the `get` and `set` methods.
5
6
  * @class
6
7
  * @memberof AthomCloudAPI
7
8
  */
@@ -1,13 +1,28 @@
1
1
  'use strict';
2
2
 
3
+ const Util = require('../Util');
4
+
3
5
  /**
4
- * A Homey API, to be extended.
6
+ * An authenticated Homey API. Do not construct this class manually.
7
+ * @class
5
8
  * @hideconstructor
6
9
  */
7
10
  class HomeyAPI {
8
11
 
9
12
  /**
10
- * Possible Discovery Strategies
13
+ * Platforms
14
+ * @static
15
+ * @property {object} PLATFORMS
16
+ * @property {string} PLATFORMS.LOCAL - Homey (2016 — 2019) & Homey Pro (2019 — 2023)
17
+ * @property {string} PLATFORMS.CLOUD - Homey Cloud
18
+ */
19
+ static PLATFORMS = {
20
+ LOCAL: 'local',
21
+ CLOUD: 'cloud',
22
+ };
23
+
24
+ /**
25
+ * Discovery Strategies
11
26
  * @static
12
27
  * @property {object} DISCOVERY_STRATEGIES
13
28
  * @property {string} DISCOVERY_STRATEGIES.CLOUD - Cloud HTTPS, e.g. `https://abcdef.connect.athom.com`.
@@ -25,8 +40,8 @@ class HomeyAPI {
25
40
  };
26
41
 
27
42
  constructor({
28
- api,
29
- properties,
43
+ api = null,
44
+ properties = {},
30
45
  debug = () => { },
31
46
  }) {
32
47
  // Set Debug Enabled
@@ -45,14 +60,28 @@ class HomeyAPI {
45
60
 
46
61
  // Set ID
47
62
  Object.defineProperty(this, 'id', {
48
- value: properties.id || properties._id || null,
63
+ value: properties.id ?? properties._id ?? null,
49
64
  enumerable: true,
50
65
  writable: true,
51
66
  });
52
67
 
53
68
  // Set Version
54
69
  Object.defineProperty(this, 'version', {
55
- value: properties.softwareVersion || null,
70
+ value: properties.softwareVersion ?? null,
71
+ enumerable: true,
72
+ writable: true,
73
+ });
74
+
75
+ // Set Name
76
+ Object.defineProperty(this, 'name', {
77
+ value: properties.name ?? null,
78
+ enumerable: true,
79
+ writable: true,
80
+ });
81
+
82
+ // Set Language
83
+ Object.defineProperty(this, 'Language', {
84
+ value: properties.Language ?? null,
56
85
  enumerable: true,
57
86
  writable: true,
58
87
  });
@@ -65,6 +94,10 @@ class HomeyAPI {
65
94
  });
66
95
  }
67
96
 
97
+ get _id() {
98
+ throw new Error('HomeyAPI._id is not supported, please use HomeyAPI.id');
99
+ }
100
+
68
101
  __debug(...props) {
69
102
  if (!this.__debugFunction) return;
70
103
  this.__debugFunction(...props);
@@ -126,21 +159,44 @@ class HomeyAPI {
126
159
  }
127
160
 
128
161
  /**
129
- * Create a {@link HomeyAPIV3Local} instance for use in the Apps SDK.
162
+ * Creates a {@link HomeyAPIV3Local} or {@link HomeyAPIV2} instance for use in the Apps SDK.
130
163
  * @param {Object} opts
131
- * @param {Homey} opts.homey — Homey instance
132
- * @param {Function} opts.debug — Debug method, defaults to `homey.app.log`
164
+ * @param {Homey} opts.homey — Homey (Apps SDK) instance.
165
+ * @param {Function|null} opts.debug — Debug function, defaults to `null`.
166
+ * @example
167
+ * const { HomeyAPI } = require('homey-api');
168
+ *
169
+ * module.exports = class MyApp extends Homey.App {
170
+ *
171
+ * async onInit() {
172
+ * // Create a HomeyAPI instance. Ensure your app has the `homey:manager:api` permission.
173
+ * this.homeyApi = await HomeyAPI.createAppAPI({
174
+ * homey: this.homey,
175
+ * });
176
+ *
177
+ * // Get all the devices, and log their names.
178
+ * const devices = await this.{@link HomeyAPIV3Local homeyApi}.{@link HomeyAPIV3Local.ManagerDevices devices}.{@link HomeyAPIV3Local.ManagerDevices#getDevices getDevices}();
179
+ * for(const device of Object.values(devices)) {
180
+ * this.log(device.name);
181
+ * }
182
+ * }
183
+ *
184
+ * }
133
185
  */
134
186
  static async createAppAPI({
135
187
  homey,
136
- debug = (...props) => homey.app.log('[homey-api]', ...props),
188
+ debug = null,
137
189
  } = {}) {
138
190
  if (!homey) {
139
191
  throw new Error('Invalid Homey');
140
192
  }
141
193
 
194
+ if (debug === true) {
195
+ debug = (...props) => homey.app.log('[homey-api]', ...props);
196
+ }
197
+
142
198
  const props = {
143
- debug,
199
+ debug: debug ?? function debug() { },
144
200
  token: await homey.api.getOwnerApiToken(),
145
201
  baseUrl: await homey.api.getLocalUrl(),
146
202
  strategy: [],
@@ -168,6 +224,53 @@ class HomeyAPI {
168
224
  throw new Error(`Invalid Homey Platform Version: ${homey.platformVersion}`);
169
225
  }
170
226
 
227
+ /**
228
+ * Creates a {@link HomeyAPIV3Local} instance for use in a project.
229
+ * @param {Object} opts
230
+ * @param {String} opts.address — The address of the Homey, e.g. `http://192.168.1.123:80`.
231
+ * @param {String} opts.token — A Personal Access Token created in the Homey Web App.
232
+ * @param {Function|null} opts.debug — Debug function, defaults to `null`.
233
+ * @example
234
+ * import { HomeyAPI } from 'homey-api';
235
+ * const homeyApi = await HomeyAPI.createLocalAPI({
236
+ * address: 'http://192.169.1.123',
237
+ * token: '<my_personal_access_token>',
238
+ * });
239
+ * const devices = await homeyApi.devices.getDevices();
240
+ */
241
+ static async createLocalAPI({
242
+ address,
243
+ token,
244
+ debug = null,
245
+ }) {
246
+ if (!address) {
247
+ throw new Error('Invalid Address');
248
+ }
249
+
250
+ if (!token) {
251
+ throw new Error('Invalid Token');
252
+ }
253
+
254
+ const res = await Util.fetch(`${address}/api/manager/system/ping`);
255
+ if (!res.headers.has('X-Homey-ID')) {
256
+ throw new Error(`No Homey Found At Address: ${address}`);
257
+ }
258
+
259
+ const props = {
260
+ token,
261
+ debug: debug ?? function debug() { },
262
+ baseUrl: address,
263
+ strategy: [],
264
+ properties: {
265
+ id: res.headers.get('X-Homey-ID'),
266
+ softwareVersion: res.headers.get('X-Homey-Version'),
267
+ },
268
+ };
269
+
270
+ const HomeyAPIV3Local = require('./HomeyAPIV3Local');
271
+ return new HomeyAPIV3Local(props);
272
+ }
273
+
171
274
  }
172
275
 
173
276
  module.exports = HomeyAPI;
@@ -19,6 +19,14 @@ class HomeyAPIV1 extends HomeyAPI {
19
19
  this.token = token;
20
20
  }
21
21
 
22
+ get platform() {
23
+ return 'local';
24
+ }
25
+
26
+ get platformVersion() {
27
+ return 1;
28
+ }
29
+
22
30
  updates = {
23
31
  async getUpdates() {
24
32
  throw new Error('Not Implemented');
@@ -5,9 +5,10 @@ const DeviceV3 = require('../../HomeyAPIV3/ManagerDevices/Device');
5
5
  class Device extends DeviceV3 {
6
6
 
7
7
  static transformGet(item) {
8
+ item.driverId = `${item.driverUri}:${item.driverId}`;
9
+
8
10
  item = super.transformGet(item);
9
11
 
10
- item.driverId = `${item.driverUri}:${item.driverId}`;
11
12
  delete item.driverUri;
12
13
  delete item.zoneName;
13
14
  return item;
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const AdvancedFlowV3 = require('../../HomeyAPIV3/ManagerFlow/Flow');
3
+ const AdvancedFlowV3 = require('../../HomeyAPIV3/ManagerFlow/AdvancedFlow');
4
4
 
5
5
  class AdvancedFlow extends AdvancedFlowV3 {
6
6
 
@@ -11,6 +11,8 @@ class AdvancedFlow extends AdvancedFlowV3 {
11
11
  }
12
12
  }
13
13
 
14
+ delete item.broken;
15
+
14
16
  return item;
15
17
  }
16
18
 
@@ -26,6 +26,8 @@ class Flow extends FlowV3 {
26
26
  });
27
27
  }
28
28
 
29
+ delete item.broken;
30
+
29
31
  return item;
30
32
  }
31
33
 
@@ -66,7 +66,7 @@ class ManagerFlow extends Manager {
66
66
  return this.__cache['flowcardtrigger'][id];
67
67
  }
68
68
 
69
- return this.__super__getFlowCardCondition({
69
+ return this.__super__getFlowCardTrigger({
70
70
  id: id.split(':').reverse()[0],
71
71
  uri: id.split(':', 3).join(':'),
72
72
  });
@@ -25,6 +25,14 @@ class HomeyAPIV2 extends HomeyAPIV3 {
25
25
  ManagerInsights,
26
26
  };
27
27
 
28
+ get platform() {
29
+ return 'local';
30
+ }
31
+
32
+ get platformVersion() {
33
+ return 1;
34
+ }
35
+
28
36
  }
29
37
 
30
38
  module.exports = HomeyAPIV2;
@@ -213,6 +213,7 @@ class Manager extends EventEmitter {
213
213
  // This is about ~2x faster than HTTP
214
214
  if (this.homey.isConnected() && $socket === true) {
215
215
  result = await Util.timeout(new Promise((resolve, reject) => {
216
+ this.__debug(`IO ${operationId}`);
216
217
  this.homey.__ioNamespace.emit('api', {
217
218
  args,
218
219
  operation: operationId,
@@ -278,7 +279,8 @@ class Manager extends EventEmitter {
278
279
  // Add all to cache
279
280
  for (let item of Object.values(result)) {
280
281
  item = Item.transformGet(item);
281
- if (this.__cache[itemId][item.id]) {
282
+
283
+ if (this.isConnected() && this.__cache[itemId][item.id]) {
282
284
  items[item.id] = this.__cache[itemId][item.id];
283
285
  items[item.id].__update(item);
284
286
  } else {
@@ -316,7 +318,7 @@ class Manager extends EventEmitter {
316
318
  let item = { ...result };
317
319
  item = Item.transformGet(item);
318
320
 
319
- if (this.__cache[itemId][item.id]) {
321
+ if (this.isConnected() && this.__cache[itemId][item.id]) {
320
322
  item = this.__cache[itemId][item.id];
321
323
  item.__update(item);
322
324
  } else {
@@ -336,7 +338,7 @@ class Manager extends EventEmitter {
336
338
  return item;
337
339
  }
338
340
  case 'deleteOne': {
339
- if (this.__cache[itemId][args.id]) {
341
+ if (this.isConnected() && this.__cache[itemId][args.id]) {
340
342
  this.__cache[itemId][args.id].destroy();
341
343
  delete this.__cache[itemId][args.id];
342
344
  }
@@ -422,7 +424,7 @@ class Manager extends EventEmitter {
422
424
  });
423
425
  this.__cache[itemId][data.id] = item;
424
426
 
425
- break;
427
+ return this.emit(event, item);
426
428
  }
427
429
  case 'update': {
428
430
  data = Item.transformGet(data);
@@ -430,6 +432,7 @@ class Manager extends EventEmitter {
430
432
  if (this.__cache[itemId][data.id]) {
431
433
  const item = this.__cache[itemId][data.id];
432
434
  item.__update(data);
435
+ return this.emit(event, item);
433
436
  }
434
437
 
435
438
  break;
@@ -441,6 +444,9 @@ class Manager extends EventEmitter {
441
444
  const item = this.__cache[itemId][data.id];
442
445
  item.__delete();
443
446
  delete this.__cache[itemId][item.id];
447
+ return this.emit(event, {
448
+ id: item.id,
449
+ });
444
450
  }
445
451
 
446
452
  break;
@@ -198,6 +198,8 @@ class Device extends Item {
198
198
  static transformGet(item) {
199
199
  item = super.transformGet(item);
200
200
 
201
+ delete item.driverUri;
202
+
201
203
  if (item.capabilitiesObj) {
202
204
  for (const capabilityObj of Object.values(item.capabilitiesObj)) {
203
205
  if (capabilityObj.lastUpdated) {
@@ -6,7 +6,7 @@ const EventEmitter = require('../../../EventEmitter');
6
6
  /**
7
7
  * @class
8
8
  * @hideconstructor
9
- * @memberof HomeyAPIV2.ManagerDevices.Device
9
+ * @memberof HomeyAPIV3.ManagerDevices.Device
10
10
  */
11
11
  class DeviceCapability extends EventEmitter {
12
12
 
@@ -79,7 +79,7 @@ class DeviceCapability extends EventEmitter {
79
79
  }
80
80
 
81
81
  /**
82
- * Destroy the listener, and if it's the last one, unsubscribe from the device's realtime events.
82
+ * Destroy this capability listener, and if it's the last one, unsubscribe from the device's realtime events.
83
83
  */
84
84
  destroy() {
85
85
  this.emit('destroy');
@@ -139,7 +139,7 @@ class DeviceCapability extends EventEmitter {
139
139
  }
140
140
 
141
141
  /**
142
- *
142
+ * Sets a new capability value.
143
143
  * @param {boolean|number|string} value - The new capability value
144
144
  * @param {object} opts
145
145
  */
@@ -1,9 +1,128 @@
1
+ /* eslint-disable no-unused-vars */
2
+
1
3
  'use strict';
2
4
 
3
5
  const Item = require('../Item');
4
6
 
5
7
  class AdvancedFlow extends Item {
6
8
 
9
+ async isBroken() {
10
+ const managerFlow = this.homey.flow;
11
+ if (!managerFlow.isConnected()) {
12
+ throw new Error('Flow.isBroken requires ManagerFlow to be connected.');
13
+ }
14
+
15
+ const managerFlowToken = this.homey.flowtoken;
16
+ if (!managerFlowToken.isConnected()) {
17
+ throw new Error('Flow.isBroken requires ManagerFlowToken to be connected.');
18
+ }
19
+
20
+ // Array of local & global Token IDs.
21
+ // For example [ 'foo', 'homey:x:y|abc' ]
22
+ const tokenIds = [];
23
+
24
+ const checkToken = async tokenId => {
25
+ // If this is a global Token, fetch all FlowTokens
26
+ if (tokenId.includes('|')) {
27
+ const flowTokens = await managerFlowToken.getFlowTokens(); // Fill the cache
28
+ for (const flowTokenId of Object.keys(flowTokens)) {
29
+ tokenIds.push(flowTokenId);
30
+ }
31
+
32
+ tokenId = tokenId.replace('|', ':');
33
+ }
34
+
35
+ if (!tokenIds.includes(tokenId)) {
36
+ throw new Error(`Missing Token: ${tokenId}`);
37
+ }
38
+ };
39
+
40
+ const checkTokens = async card => {
41
+ // Check droptoken
42
+ if (card.droptoken) {
43
+ await checkToken(card.droptoken);
44
+ }
45
+
46
+ if (typeof card.args === 'object') {
47
+ for (const arg of Object.values(card.args)) {
48
+ if (typeof arg !== 'string') continue;
49
+ for (const [tokenMatch, tokenId] of arg.matchAll(/\[\[(.*?)\]\]/g)) {
50
+ await checkToken(tokenId);
51
+ }
52
+ }
53
+ }
54
+ };
55
+
56
+ // Check if FlowCards exist, and add Tokens
57
+ for (const [cardId, card] of Object.entries(this.cards)) {
58
+ switch (card.type) {
59
+ case 'trigger': {
60
+ try {
61
+ await managerFlow.getFlowCardTriggers(); // Fill the cache
62
+ const triggerCard = await this.manager.getFlowCardTrigger({ id: card.id });
63
+
64
+ // Add FlowCardTrigger.tokens to internal tokens cache
65
+ if (Array.isArray(triggerCard.tokens)) {
66
+ for (const token of Object.values(triggerCard.tokens)) {
67
+ tokenIds.push(`trigger::${cardId}::${token.id}`);
68
+ }
69
+ }
70
+
71
+ break;
72
+ } catch (err) {
73
+ this.__debug(err);
74
+ return true;
75
+ }
76
+ }
77
+ case 'condition': {
78
+ try {
79
+ await managerFlow.getFlowCardConditions(); // Fill the cache
80
+ const conditionCard = await this.manager.getFlowCardCondition({ id: card.id });
81
+
82
+ // Add Error Token
83
+ tokenIds.push(`card::${cardId}::error`);
84
+
85
+ break;
86
+ } catch (err) {
87
+ this.__debug(err);
88
+ return true;
89
+ }
90
+ }
91
+ case 'action': {
92
+ try {
93
+ await managerFlow.getFlowCardActions(); // Fill the cache
94
+ const actionCard = await this.manager.getFlowCardAction({ id: card.id });
95
+
96
+ // Add Error Token
97
+ tokenIds.push(`card::${cardId}::error`);
98
+
99
+ // Add FlowCardAction.tokens to internal tokens cache
100
+ if (Array.isArray(actionCard.tokens)) {
101
+ for (const token of Object.values(actionCard.tokens)) {
102
+ tokenIds.push(`action::${cardId}::${token.id}`);
103
+ }
104
+ }
105
+
106
+ break;
107
+ } catch (err) {
108
+ this.__debug(err);
109
+ return true;
110
+ }
111
+ }
112
+ default: {
113
+ // Do nothing
114
+ }
115
+ }
116
+ }
117
+
118
+ // Check Tokens
119
+ for (const card of Object.values(this.cards)) {
120
+ await checkTokens(card);
121
+ }
122
+
123
+ return false;
124
+ }
125
+
7
126
  }
8
127
 
9
128
  module.exports = AdvancedFlow;