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.
- package/lib/AthomCloudAPI/User.js +2 -0
- package/lib/AthomCloudAPI.js +71 -17
- package/lib/EventEmitter.js +1 -0
- package/lib/HomeyAPI/HomeyAPIV2/ManagerDevices/Device.js +0 -1
- package/lib/HomeyAPI/HomeyAPIV2/ManagerDrivers/Driver.js +1 -1
- package/lib/HomeyAPI/HomeyAPIV3/Manager.js +233 -176
- package/lib/HomeyAPI/HomeyAPIV3/ManagerDevices/Capability.js +5 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerDevices/Device.js +24 -10
- package/lib/HomeyAPI/HomeyAPIV3/ManagerDevices/DeviceCapability.js +2 -2
- package/lib/HomeyAPI/HomeyAPIV3/ManagerDrivers/Driver.js +10 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerDrivers/PairSession.js +8 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerFlow/FlowCardAction.js +10 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerFlow/FlowCardCondition.js +10 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerFlow/FlowCardTrigger.js +10 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerFlowToken/FlowToken.js +15 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerInsights/Log.js +15 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerUsers.js +31 -0
- package/lib/HomeyAPI/HomeyAPIV3.js +20 -4
- package/package.json +1 -1
|
@@ -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
|
}
|
package/lib/AthomCloudAPI.js
CHANGED
|
@@ -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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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:
|
|
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>}
|
package/lib/EventEmitter.js
CHANGED
|
@@ -35,14 +35,26 @@ class Manager extends EventEmitter {
|
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
// Set Items
|
|
38
|
-
Object.defineProperty(this, '
|
|
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
|
-
|
|
43
|
-
|
|
42
|
+
: (() => {
|
|
43
|
+
return class extends Item {};
|
|
44
|
+
})();
|
|
45
|
+
|
|
44
46
|
ItemClass.ID = item.id;
|
|
45
|
-
obj[
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
219
|
+
$socket,
|
|
220
|
+
operationId,
|
|
221
|
+
operation,
|
|
222
|
+
path,
|
|
249
223
|
body,
|
|
250
|
-
|
|
251
|
-
|
|
224
|
+
query,
|
|
225
|
+
headers,
|
|
226
|
+
...args
|
|
252
227
|
});
|
|
253
|
-
}
|
|
254
228
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
272
|
-
this.__cache[itemId][item.id] = item;
|
|
273
|
-
}
|
|
234
|
+
const result = await this.__pendingCalls[path];
|
|
274
235
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
335
|
-
|
|
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
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
|
|
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
|
-
|
|
355
|
-
|
|
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
|
|
485
|
+
const itemName = this.itemNames[itemId];
|
|
486
|
+
const ItemClass = this.itemClasses[itemName];
|
|
430
487
|
|
|
431
488
|
switch (operation) {
|
|
432
489
|
case 'create': {
|
|
433
|
-
|
|
490
|
+
const props = ItemClass.transformGet(data);
|
|
434
491
|
|
|
435
|
-
const item = new
|
|
436
|
-
id:
|
|
492
|
+
const item = new ItemClass({
|
|
493
|
+
id: props.id,
|
|
437
494
|
homey: this.homey,
|
|
438
495
|
manager: this,
|
|
439
|
-
properties:
|
|
496
|
+
properties: props,
|
|
440
497
|
});
|
|
441
|
-
this.__cache[
|
|
498
|
+
this.__cache[ItemClass.ID][props.id] = item;
|
|
442
499
|
|
|
443
500
|
return this.emit(event, item);
|
|
444
501
|
}
|
|
445
502
|
case 'update': {
|
|
446
|
-
|
|
503
|
+
const props = ItemClass.transformGet(data);
|
|
447
504
|
|
|
448
|
-
if (this.__cache[
|
|
449
|
-
const item = this.__cache[
|
|
450
|
-
item.__update(
|
|
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
|
-
|
|
514
|
+
const props = ItemClass.transformGet(data);
|
|
458
515
|
|
|
459
|
-
if (this.__cache[
|
|
460
|
-
const item = this.__cache[
|
|
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[
|
|
519
|
+
delete this.__cache[ItemClass.ID][item.id];
|
|
463
520
|
return this.emit(event, {
|
|
464
521
|
id: item.id,
|
|
465
522
|
});
|
|
@@ -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,
|
|
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
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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((
|
|
173
|
-
|
|
174
|
-
|
|
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 =
|
|
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;
|
|
@@ -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
|
-
|
|
147
|
-
|
|
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)
|
|
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)
|
|
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
|
});
|