homey-api 1.10.17 → 3.0.0-rc.2
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/README.md +1 -1
- package/assets/types/homey-api.d.ts +47 -588
- package/assets/types/homey-api.private.d.ts +47 -648
- package/index.js +1 -1
- package/lib/APIErrorNotFound.js +20 -0
- package/lib/AthomCloudAPI/Homey.js +3 -1
- package/lib/EventEmitter.js +0 -6
- package/lib/HomeyAPI/HomeyAPI.js +49 -5
- package/lib/HomeyAPI/HomeyAPIErrorNotFound.js +21 -0
- package/lib/HomeyAPI/HomeyAPIV2/Manager.js +2 -575
- package/lib/HomeyAPI/HomeyAPIV2/ManagerDevices/Capability.js +20 -0
- package/lib/HomeyAPI/HomeyAPIV2/ManagerDevices/Device.js +18 -0
- package/lib/HomeyAPI/HomeyAPIV2/ManagerDevices.js +20 -3
- package/lib/HomeyAPI/HomeyAPIV2/ManagerDrivers/Driver.js +25 -0
- package/lib/HomeyAPI/HomeyAPIV2/ManagerDrivers.js +29 -0
- package/lib/HomeyAPI/HomeyAPIV2/ManagerFlow/AdvancedFlow.js +17 -0
- package/lib/HomeyAPI/HomeyAPIV2/ManagerFlow/Flow.js +34 -0
- package/lib/HomeyAPI/HomeyAPIV2/ManagerFlow/FlowCardAction.js +25 -0
- package/lib/HomeyAPI/HomeyAPIV2/ManagerFlow/FlowCardCondition.js +25 -0
- package/lib/HomeyAPI/HomeyAPIV2/ManagerFlow/FlowCardTrigger.js +25 -0
- package/lib/HomeyAPI/HomeyAPIV2/ManagerFlow.js +104 -0
- package/lib/HomeyAPI/HomeyAPIV2/ManagerFlowToken/FlowToken.js +24 -0
- package/lib/HomeyAPI/HomeyAPIV2/ManagerFlowToken.js +29 -0
- package/lib/HomeyAPI/HomeyAPIV2/ManagerInsights/Log.js +23 -0
- package/lib/HomeyAPI/HomeyAPIV2/ManagerInsights.js +29 -0
- package/lib/HomeyAPI/HomeyAPIV2.js +12 -716
- package/lib/HomeyAPI/HomeyAPIV3/Item.js +173 -2
- package/lib/HomeyAPI/HomeyAPIV3/Manager.js +531 -3
- package/lib/HomeyAPI/{HomeyAPIV2 → HomeyAPIV3/ManagerApps}/App.js +1 -1
- package/lib/HomeyAPI/{HomeyAPIV2 → HomeyAPIV3}/ManagerApps.js +4 -3
- package/lib/HomeyAPI/HomeyAPIV3/ManagerDevices/Capability.js +9 -0
- package/lib/HomeyAPI/{HomeyAPIV2 → HomeyAPIV3/ManagerDevices}/Device.js +78 -3
- package/lib/HomeyAPI/{HomeyAPIV2 → HomeyAPIV3/ManagerDevices}/DeviceCapability.js +3 -3
- package/lib/HomeyAPI/HomeyAPIV3/ManagerDevices.js +17 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerDrivers/Driver.js +9 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerDrivers.js +15 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerFlow/AdvancedFlow.js +9 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerFlow/Flow.js +9 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerFlow/FlowCard.js +9 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerFlow/FlowCardAction.js +9 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerFlow/FlowCardCondition.js +9 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerFlow/FlowCardTrigger.js +9 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerFlow.js +12 -23
- package/lib/HomeyAPI/HomeyAPIV3/ManagerFlowToken/FlowToken.js +16 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerFlowToken.js +15 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerInsights/Log.js +9 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerInsights.js +15 -0
- package/lib/HomeyAPI/HomeyAPIV3.js +728 -4
- package/lib/HomeyAPI/HomeyAPIV3Cloud.js +1 -1
- package/lib/HomeyAPI/HomeyAPIV3Local.js +1 -1
- package/package.json +1 -1
- package/lib/HomeyAPI/HomeyAPIApp.js +0 -127
- package/lib/HomeyAPI/HomeyAPIV2/Item.js +0 -177
|
@@ -1,734 +1,30 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const ManagerApps = require('./HomeyAPIV2/ManagerApps');
|
|
3
|
+
const HomeyAPIV3 = require('./HomeyAPIV3');
|
|
4
|
+
const ManagerFlow = require('./HomeyAPIV2/ManagerFlow');
|
|
5
|
+
const ManagerFlowToken = require('./HomeyAPIV2/ManagerFlowToken');
|
|
7
6
|
const ManagerDevices = require('./HomeyAPIV2/ManagerDevices');
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const APIErrorHomeyOffline = require('../APIErrorHomeyOffline');
|
|
11
|
-
const Util = require('../Util');
|
|
7
|
+
const ManagerDrivers = require('./HomeyAPIV2/ManagerDrivers');
|
|
8
|
+
const ManagerInsights = require('./HomeyAPIV2/ManagerInsights');
|
|
12
9
|
|
|
13
10
|
/**
|
|
14
|
-
* This class is returned by {@link AthomCloudAPI.Homey#authenticate} for a Homey with
|
|
11
|
+
* This class is returned by {@link AthomCloudAPI.Homey#authenticate} for a Homey with `platform: 'local'` and `platformVersion: 1`.
|
|
15
12
|
*
|
|
16
13
|
* @class
|
|
17
14
|
* @hideconstructor
|
|
18
15
|
* @extends HomeyAPI
|
|
19
16
|
*/
|
|
20
|
-
class HomeyAPIV2 extends
|
|
17
|
+
class HomeyAPIV2 extends HomeyAPIV3 {
|
|
21
18
|
|
|
22
19
|
static MANAGERS = {
|
|
23
|
-
|
|
20
|
+
...super.MANAGERS,
|
|
21
|
+
ManagerFlow,
|
|
22
|
+
ManagerFlowToken,
|
|
24
23
|
ManagerDevices,
|
|
24
|
+
ManagerDrivers,
|
|
25
|
+
ManagerInsights,
|
|
25
26
|
};
|
|
26
27
|
|
|
27
|
-
constructor({
|
|
28
|
-
properties,
|
|
29
|
-
strategy = [
|
|
30
|
-
HomeyAPI.DISCOVERY_STRATEGIES.MDNS,
|
|
31
|
-
HomeyAPI.DISCOVERY_STRATEGIES.CLOUD,
|
|
32
|
-
HomeyAPI.DISCOVERY_STRATEGIES.LOCAL,
|
|
33
|
-
HomeyAPI.DISCOVERY_STRATEGIES.LOCAL_SECURE,
|
|
34
|
-
HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED,
|
|
35
|
-
],
|
|
36
|
-
...props
|
|
37
|
-
}) {
|
|
38
|
-
super({ properties, ...props });
|
|
39
|
-
|
|
40
|
-
Object.defineProperty(this, '__baseUrl', {
|
|
41
|
-
value: null,
|
|
42
|
-
enumerable: false,
|
|
43
|
-
writable: true,
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
Object.defineProperty(this, '__strategyId', {
|
|
47
|
-
value: null,
|
|
48
|
-
enumerable: false,
|
|
49
|
-
writable: true,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
Object.defineProperty(this, '__token', {
|
|
53
|
-
value: null,
|
|
54
|
-
enumerable: false,
|
|
55
|
-
writable: true,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
Object.defineProperty(this, '__strategies', {
|
|
59
|
-
value: Array.isArray(strategy)
|
|
60
|
-
? strategy
|
|
61
|
-
: [strategy],
|
|
62
|
-
enumerable: false,
|
|
63
|
-
writable: false,
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
Object.defineProperty(this, '__managers', {
|
|
67
|
-
value: {},
|
|
68
|
-
enumerable: false,
|
|
69
|
-
writable: false,
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
Object.defineProperty(this, '__baseUrlPromise', {
|
|
73
|
-
value: null,
|
|
74
|
-
enumerable: false,
|
|
75
|
-
writable: true,
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
Object.defineProperty(this, '__loginPromise', {
|
|
79
|
-
value: null,
|
|
80
|
-
enumerable: false,
|
|
81
|
-
writable: true,
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
Object.defineProperty(this, '__connected', {
|
|
85
|
-
value: false,
|
|
86
|
-
enumerable: false,
|
|
87
|
-
writable: true,
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
this.generateManagersFromSpecification();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/*
|
|
94
|
-
* Get the Homey's base URL promise
|
|
95
|
-
*/
|
|
96
|
-
get baseUrl() {
|
|
97
|
-
return (async () => {
|
|
98
|
-
if (!this.__baseUrlPromise) {
|
|
99
|
-
this.__baseUrlPromise = this.discoverBaseUrl().then(({ baseUrl }) => baseUrl);
|
|
100
|
-
this.__baseUrlPromise.catch(() => { });
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return this.__baseUrlPromise;
|
|
104
|
-
})();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
get strategyId() {
|
|
108
|
-
return this.__strategyId;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/*
|
|
112
|
-
* Generate Managers from JSON specification
|
|
113
|
-
* A manager instance is created when it's first accessed
|
|
114
|
-
*/
|
|
115
|
-
|
|
116
|
-
getSpecification() {
|
|
117
|
-
// eslint-disable-next-line global-require
|
|
118
|
-
return require('../../assets/specifications/HomeyAPIV2.json');
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
generateManagersFromSpecification() {
|
|
122
|
-
const { managers } = this.getSpecification();
|
|
123
|
-
Object.entries(managers).forEach(([managerName, manager]) => {
|
|
124
|
-
this.generateManagerFromSpecification(managerName, manager);
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
generateManagerFromSpecification(managerName, manager) {
|
|
129
|
-
Object.defineProperty(this, manager.idCamelCase, {
|
|
130
|
-
get: () => {
|
|
131
|
-
if (!this.__managers[managerName]) {
|
|
132
|
-
// eslint-disable-next-line global-require, import/no-dynamic-require
|
|
133
|
-
const ManagerClass = this.constructor.MANAGERS[managerName]
|
|
134
|
-
? this.constructor.MANAGERS[managerName]
|
|
135
|
-
: Manager;
|
|
136
|
-
|
|
137
|
-
this.__managers[managerName] = new ManagerClass({
|
|
138
|
-
homey: this,
|
|
139
|
-
id: manager.id,
|
|
140
|
-
name: managerName,
|
|
141
|
-
items: manager.items || {},
|
|
142
|
-
operations: manager.operations || {},
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return this.__managers[managerName];
|
|
147
|
-
},
|
|
148
|
-
enumerable: false,
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/*
|
|
153
|
-
* Discover the URL to talk to Homey
|
|
154
|
-
* We prefer localSecure, because it's fastest and most secure
|
|
155
|
-
* If that doesn't work, we prefer local OR mdns, whichever is fastest
|
|
156
|
-
* Finally, we fallback to cloud
|
|
157
|
-
*/
|
|
158
|
-
|
|
159
|
-
async discoverBaseUrl() {
|
|
160
|
-
const urls = {};
|
|
161
|
-
|
|
162
|
-
if (this.__strategies.includes(HomeyAPI.DISCOVERY_STRATEGIES.MDNS)) {
|
|
163
|
-
if (Util.isHTTPUnsecureSupported()) {
|
|
164
|
-
urls[HomeyAPI.DISCOVERY_STRATEGIES.MDNS] = `http://homey-${this.id}.local`;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (this.__strategies.includes(HomeyAPI.DISCOVERY_STRATEGIES.LOCAL)) {
|
|
169
|
-
if (Util.isHTTPUnsecureSupported() && this.__properties.localUrl) {
|
|
170
|
-
urls[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL] = `${this.__properties.localUrl}`;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (this.__strategies.includes(HomeyAPI.DISCOVERY_STRATEGIES.LOCAL_SECURE)) {
|
|
175
|
-
if (this.__properties.localUrlSecure) {
|
|
176
|
-
urls[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL_SECURE] = `${this.__properties.localUrlSecure}`;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (this.__strategies.includes(HomeyAPI.DISCOVERY_STRATEGIES.CLOUD)) {
|
|
181
|
-
if (this.__properties.remoteUrl) {
|
|
182
|
-
urls[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD] = `${this.__properties.remoteUrl}`;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (this.__strategies.includes(HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED)) {
|
|
187
|
-
if (this.__properties.remoteUrlForwarded) {
|
|
188
|
-
urls[HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED] = `${this.__properties.remoteUrlForwarded}`;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (!Object.keys(urls).length) {
|
|
193
|
-
throw new Error('No Discovery Strategies Available');
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Don't discover, just set the only strategy
|
|
197
|
-
if (Object.keys(urls).length === 1) {
|
|
198
|
-
this.__baseUrl = Object.values(urls)[0];
|
|
199
|
-
this.__strategyId = Object.keys(urls)[0];
|
|
200
|
-
|
|
201
|
-
return {
|
|
202
|
-
baseUrl: this.__baseUrl,
|
|
203
|
-
strategyId: this.__strategyId,
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
this.__debug(`Discovery Strategies: ${Object.keys(urls).join(',')}`);
|
|
208
|
-
|
|
209
|
-
// Create the returned Promise
|
|
210
|
-
let resolve;
|
|
211
|
-
let reject;
|
|
212
|
-
const promise = new Promise((resolve_, reject_) => {
|
|
213
|
-
resolve = resolve_;
|
|
214
|
-
reject = reject_;
|
|
215
|
-
});
|
|
216
|
-
promise
|
|
217
|
-
.then(({ baseUrl, strategyId }) => {
|
|
218
|
-
this.__baseUrl = baseUrl;
|
|
219
|
-
this.__strategyId = strategyId;
|
|
220
|
-
})
|
|
221
|
-
.catch(() => { });
|
|
222
|
-
|
|
223
|
-
// Ping method
|
|
224
|
-
const ping = async (strategyId, timeout) => {
|
|
225
|
-
let pingTimeout;
|
|
226
|
-
const baseUrl = urls[strategyId];
|
|
227
|
-
return Promise.race([
|
|
228
|
-
Util.fetch(`${baseUrl}/api/manager/system/ping?id=${this.id}`, {
|
|
229
|
-
headers: {
|
|
230
|
-
'X-Homey-ID': this.id,
|
|
231
|
-
},
|
|
232
|
-
}).then(async res => {
|
|
233
|
-
const text = await res.text();
|
|
234
|
-
if (!res.ok) throw new Error(text || res.statusText);
|
|
235
|
-
if (text === 'false') throw new Error('Invalid Homey ID');
|
|
236
|
-
|
|
237
|
-
const homeyId = res.headers.get('X-Homey-ID');
|
|
238
|
-
if (homeyId) {
|
|
239
|
-
if (homeyId !== this.id) throw new Error('Invalid Homey ID'); // TODO: Add to Homey Connect
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Set the version that Homey told us.
|
|
243
|
-
// It's the absolute truth, because the Cloud API may be behind.
|
|
244
|
-
const homeyVersion = res.headers.get('X-Homey-Version');
|
|
245
|
-
if (homeyVersion !== this.version) {
|
|
246
|
-
this.version = homeyVersion;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
return {
|
|
250
|
-
baseUrl,
|
|
251
|
-
strategyId,
|
|
252
|
-
};
|
|
253
|
-
}),
|
|
254
|
-
new Promise((_, reject) => {
|
|
255
|
-
pingTimeout = setTimeout(() => reject(new Error('PingTimeout')), timeout);
|
|
256
|
-
}),
|
|
257
|
-
]).finally(() => clearTimeout(pingTimeout));
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
const pings = {};
|
|
261
|
-
|
|
262
|
-
// Ping localSecure (https://xxx-xxx-xxx-xx.homey.homeylocal.com)
|
|
263
|
-
if (urls[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL_SECURE]) {
|
|
264
|
-
pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL_SECURE] = ping(HomeyAPI.DISCOVERY_STRATEGIES.LOCAL_SECURE, 1200);
|
|
265
|
-
pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL_SECURE].catch(err => this.__debug(`Ping ${HomeyAPI.DISCOVERY_STRATEGIES.LOCAL_SECURE} Error:`, err && err.message));
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Ping local (http://xxx-xxx-xxx-xxx)
|
|
269
|
-
if (urls[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL]) {
|
|
270
|
-
pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL] = ping(HomeyAPI.DISCOVERY_STRATEGIES.LOCAL, 1000);
|
|
271
|
-
pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL].catch(err => this.__debug(`Ping ${HomeyAPI.DISCOVERY_STRATEGIES.LOCAL} Error:`, err && err.message));
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Ping mdns (http://homey-<homeyId>.local)
|
|
275
|
-
if (urls[HomeyAPI.DISCOVERY_STRATEGIES.MDNS]) {
|
|
276
|
-
pings[HomeyAPI.DISCOVERY_STRATEGIES.MDNS] = ping(HomeyAPI.DISCOVERY_STRATEGIES.MDNS, 3000);
|
|
277
|
-
pings[HomeyAPI.DISCOVERY_STRATEGIES.MDNS].catch(err => this.__debug(`Ping ${HomeyAPI.DISCOVERY_STRATEGIES.MDNS} Error:`, err && err.message));
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Ping cloud (https://<homeyId>.connect.athom.com)
|
|
281
|
-
if (urls[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]) {
|
|
282
|
-
pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD] = ping(HomeyAPI.DISCOVERY_STRATEGIES.CLOUD, 5000);
|
|
283
|
-
pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD].catch(err => this.__debug(`Ping ${HomeyAPI.DISCOVERY_STRATEGIES.CLOUD} Error:`, err && err.message));
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Ping Direct (https://xxx-xxx-xxx-xx.homey.homeylocal.com:12345)
|
|
287
|
-
if (urls[HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED]) {
|
|
288
|
-
pings[HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED] = ping(HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED, 2000);
|
|
289
|
-
pings[HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED].catch(err => this.__debug(`Ping ${HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED} Error:`, err && err.message));
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Select the best route
|
|
293
|
-
if (pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL_SECURE]) {
|
|
294
|
-
pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL_SECURE]
|
|
295
|
-
.then(result => resolve(result))
|
|
296
|
-
.catch(() => {
|
|
297
|
-
const promises = [];
|
|
298
|
-
|
|
299
|
-
if (pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL]) {
|
|
300
|
-
promises.push(pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL]);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (pings[HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED]) {
|
|
304
|
-
promises.push(pings[HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED]);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
if (pings[HomeyAPI.DISCOVERY_STRATEGIES.MDNS]) {
|
|
308
|
-
promises.push(pings[HomeyAPI.DISCOVERY_STRATEGIES.MDNS]);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
if (pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]) {
|
|
312
|
-
promises.push(pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
if (!promises.length) {
|
|
316
|
-
throw new APIErrorHomeyOffline();
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return Util.promiseAny(promises);
|
|
320
|
-
})
|
|
321
|
-
.then(result => resolve(result))
|
|
322
|
-
.catch(() => reject(new APIErrorHomeyOffline()));
|
|
323
|
-
} else if (pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL]) {
|
|
324
|
-
pings[HomeyAPI.DISCOVERY_STRATEGIES.LOCAL]
|
|
325
|
-
.then(result => resolve(result))
|
|
326
|
-
.catch(() => {
|
|
327
|
-
if (pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]) {
|
|
328
|
-
pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]
|
|
329
|
-
.then(result => resolve(result))
|
|
330
|
-
.catch(err => reject(new APIErrorHomeyOffline(err)));
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
} else if (pings[HomeyAPI.DISCOVERY_STRATEGIES.MDNS]) {
|
|
334
|
-
pings[HomeyAPI.DISCOVERY_STRATEGIES.MDNS]
|
|
335
|
-
.then(result => resolve(result))
|
|
336
|
-
.catch(() => {
|
|
337
|
-
if (pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]) {
|
|
338
|
-
pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]
|
|
339
|
-
.then(result => resolve(result))
|
|
340
|
-
.catch(err => reject(new APIErrorHomeyOffline(err)));
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
} else if (pings[HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED]) {
|
|
344
|
-
pings[HomeyAPI.DISCOVERY_STRATEGIES.REMOTE_FORWARDED]
|
|
345
|
-
.then(result => resolve(result))
|
|
346
|
-
.catch(() => {
|
|
347
|
-
if (pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]) {
|
|
348
|
-
pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]
|
|
349
|
-
.then(result => resolve(result))
|
|
350
|
-
.catch(err => reject(new APIErrorHomeyOffline(err)));
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
} else if (pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]) {
|
|
354
|
-
pings[HomeyAPI.DISCOVERY_STRATEGIES.CLOUD]
|
|
355
|
-
.then(result => resolve(result))
|
|
356
|
-
.catch(err => reject(new APIErrorHomeyOffline(err)));
|
|
357
|
-
} else {
|
|
358
|
-
reject(new APIErrorHomeyOffline());
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
return promise;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
async call({
|
|
365
|
-
$timeout = 10000,
|
|
366
|
-
method,
|
|
367
|
-
headers,
|
|
368
|
-
path,
|
|
369
|
-
body,
|
|
370
|
-
retryAfterRefresh = false,
|
|
371
|
-
}) {
|
|
372
|
-
const baseUrl = await this.baseUrl;
|
|
373
|
-
|
|
374
|
-
method = method.toUpperCase();
|
|
375
|
-
|
|
376
|
-
headers = {
|
|
377
|
-
...headers,
|
|
378
|
-
'X-Homey-ID': this.id,
|
|
379
|
-
};
|
|
380
|
-
|
|
381
|
-
if (body) {
|
|
382
|
-
headers['Content-Type'] = 'application/json';
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
if (this.__token) {
|
|
386
|
-
headers['Authorization'] = `Bearer ${this.__token}`;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
this.__debug(method, `${baseUrl}${path}`);
|
|
390
|
-
const res = await Util.timeout(Util.fetch(`${baseUrl}${path}`, {
|
|
391
|
-
method,
|
|
392
|
-
headers,
|
|
393
|
-
body: ['PUT', 'POST'].includes(method) && typeof body !== 'undefined'
|
|
394
|
-
? JSON.stringify(body)
|
|
395
|
-
: undefined,
|
|
396
|
-
}), $timeout);
|
|
397
|
-
|
|
398
|
-
const resStatusCode = res.status;
|
|
399
|
-
if (resStatusCode === 204) return undefined;
|
|
400
|
-
|
|
401
|
-
const resStatusText = res.status;
|
|
402
|
-
const resHeadersContentType = res.headers.get('Content-Type');
|
|
403
|
-
const resBodyText = await res.text();
|
|
404
|
-
let resBodyJson;
|
|
405
|
-
if (resHeadersContentType && resHeadersContentType.startsWith('application/json')) {
|
|
406
|
-
try {
|
|
407
|
-
resBodyJson = JSON.parse(resBodyText);
|
|
408
|
-
} catch (err) { }
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
if (!res.ok) {
|
|
412
|
-
if (resBodyJson) {
|
|
413
|
-
// If Session Expired, clear the stored token
|
|
414
|
-
if (resStatusCode === 401) {
|
|
415
|
-
this.__debug('Session expired, invalidating token...');
|
|
416
|
-
await this.logout();
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// If Session Expired, try to refresh the Token
|
|
420
|
-
if (resStatusCode === 401 && !retryAfterRefresh) {
|
|
421
|
-
this.__debug('Session expired, refreshing...');
|
|
422
|
-
await this.login();
|
|
423
|
-
return this.call({
|
|
424
|
-
method,
|
|
425
|
-
headers,
|
|
426
|
-
path,
|
|
427
|
-
body,
|
|
428
|
-
retryAfterRefresh: true,
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
throw new HomeyAPIError({
|
|
433
|
-
error: resBodyJson.error,
|
|
434
|
-
error_description: resBodyJson.error_description,
|
|
435
|
-
stack: resBodyJson.stack,
|
|
436
|
-
}, resStatusCode);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
if (resBodyText) {
|
|
440
|
-
throw new HomeyAPIError({
|
|
441
|
-
error: resBodyText,
|
|
442
|
-
}, resStatusCode);
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
throw new HomeyAPIError({
|
|
446
|
-
error: resStatusText,
|
|
447
|
-
}, resStatusCode);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
if (typeof resBodyJson !== 'undefined') {
|
|
451
|
-
return resBodyJson;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
return resBodyText;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
async login() {
|
|
458
|
-
if (!this.__loginPromise) {
|
|
459
|
-
this.__loginPromise = Promise.resolve().then(async () => {
|
|
460
|
-
// Check store for a valid Homey.Session
|
|
461
|
-
const store = await this.__getStore();
|
|
462
|
-
if (store && store.token) {
|
|
463
|
-
this.__debug('Got token from store');
|
|
464
|
-
return store.token;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// Create a Session by generating a JWT token on AthomCloudAPI,
|
|
468
|
-
// and then sending the JWT token to Homey.
|
|
469
|
-
this.__debug('Retrieving token...');
|
|
470
|
-
const jwtToken = await this.__api.createDelegationToken({ audience: 'homey' });
|
|
471
|
-
const token = await this.users.login({
|
|
472
|
-
$socket: false,
|
|
473
|
-
token: jwtToken,
|
|
474
|
-
});
|
|
475
|
-
await this.__setStore({ token });
|
|
476
|
-
this.__debug('Got token');
|
|
477
|
-
|
|
478
|
-
return token;
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
this.__loginPromise
|
|
482
|
-
.then(token => {
|
|
483
|
-
this.__token = token;
|
|
484
|
-
})
|
|
485
|
-
.catch(err => {
|
|
486
|
-
this.__debug('Error Logging In:', err);
|
|
487
|
-
})
|
|
488
|
-
.finally(() => {
|
|
489
|
-
this.__loginPromise = null;
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
return this.__loginPromise;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
async logout() {
|
|
497
|
-
await this.__setStore({
|
|
498
|
-
token: null,
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
this.__token = null;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
/**
|
|
505
|
-
* If Homey is connected to Socket.io.
|
|
506
|
-
* @returns {Boolean}
|
|
507
|
-
*/
|
|
508
|
-
isConnected() {
|
|
509
|
-
return this.__connected === true;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
async subscribe(uri, {
|
|
513
|
-
onConnect = () => { },
|
|
514
|
-
onReconnect = () => { },
|
|
515
|
-
onReconnectError = () => { },
|
|
516
|
-
onDisconnect = () => { },
|
|
517
|
-
onEvent = () => { },
|
|
518
|
-
}) {
|
|
519
|
-
this.__debug('subscribe', uri);
|
|
520
|
-
|
|
521
|
-
await this.connect();
|
|
522
|
-
await new Promise((resolve, reject) => {
|
|
523
|
-
this.__ioNamespace.once('disconnect', reject);
|
|
524
|
-
this.__ioNamespace.emit('subscribe', uri, err => {
|
|
525
|
-
if (err) return reject(err);
|
|
526
|
-
return resolve();
|
|
527
|
-
});
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
// On Connect
|
|
531
|
-
const __onEvent = (event, data) => {
|
|
532
|
-
onEvent(event, data);
|
|
533
|
-
};
|
|
534
|
-
this.__ioNamespace.on(uri, __onEvent);
|
|
535
|
-
|
|
536
|
-
onConnect();
|
|
537
|
-
|
|
538
|
-
// On Disconnect
|
|
539
|
-
const __onDisconnect = reason => {
|
|
540
|
-
onDisconnect(reason);
|
|
541
|
-
};
|
|
542
|
-
this.__io.on('disconnect', __onDisconnect);
|
|
543
|
-
|
|
544
|
-
// On Reconnect
|
|
545
|
-
const __onReconnect = () => {
|
|
546
|
-
Promise.resolve().then(async () => {
|
|
547
|
-
await this.connect();
|
|
548
|
-
await new Promise((resolve, reject) => {
|
|
549
|
-
this.__ioNamespace.emit('subscribe', uri, err => {
|
|
550
|
-
if (err) return reject(err);
|
|
551
|
-
return resolve();
|
|
552
|
-
});
|
|
553
|
-
});
|
|
554
|
-
|
|
555
|
-
this.__ioNamespace.on(uri, __onEvent);
|
|
556
|
-
|
|
557
|
-
onReconnect();
|
|
558
|
-
}).catch(err => onReconnectError(err));
|
|
559
|
-
};
|
|
560
|
-
this.__io.on('reconnect', __onReconnect);
|
|
561
|
-
|
|
562
|
-
return {
|
|
563
|
-
unsubscribe: () => {
|
|
564
|
-
this.__ioNamespace.emit('unsubscribe', uri);
|
|
565
|
-
this.__ioNamespace.removeListener(uri, __onEvent);
|
|
566
|
-
this.__io.removeListener('disconnect', __onDisconnect);
|
|
567
|
-
this.__io.removeListener('reconnect', __onReconnect);
|
|
568
|
-
},
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
async connect() {
|
|
573
|
-
if (!this.io) {
|
|
574
|
-
this.io = Promise.resolve().then(async () => {
|
|
575
|
-
// Ensure Base URL
|
|
576
|
-
const baseUrl = await this.baseUrl;
|
|
577
|
-
|
|
578
|
-
// Ensure Token
|
|
579
|
-
if (!this.__token) await this.login();
|
|
580
|
-
|
|
581
|
-
return new Promise((resolve, reject) => {
|
|
582
|
-
this.__debug(`SocketIOClient ${baseUrl}`);
|
|
583
|
-
this.__io = SocketIOClient(baseUrl, {
|
|
584
|
-
autoConnect: false,
|
|
585
|
-
transports: ['websocket'],
|
|
586
|
-
transportOptions: {
|
|
587
|
-
pingTimeout: 8000,
|
|
588
|
-
pingInterval: 5000,
|
|
589
|
-
},
|
|
590
|
-
});
|
|
591
|
-
this.__io.on('disconnect', reason => {
|
|
592
|
-
this.__debug('SocketIOClient.onDisconnect', reason);
|
|
593
|
-
this.__connected = false;
|
|
594
|
-
|
|
595
|
-
if (this.__ioNamespace) {
|
|
596
|
-
this.__ioNamespace.disconnect();
|
|
597
|
-
this.__ioNamespace.destroy();
|
|
598
|
-
this.__ioNamespace.removeAllListeners();
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
reject(new Error('Disconnected'));
|
|
602
|
-
});
|
|
603
|
-
this.__io.on('error', err => {
|
|
604
|
-
this.__debug('SocketIOClient.onError', err.message);
|
|
605
|
-
});
|
|
606
|
-
this.__io.on('reconnect', () => {
|
|
607
|
-
this.__debug('SocketIOClient.onReconnect');
|
|
608
|
-
this.__handshakeClient()
|
|
609
|
-
.then(() => {
|
|
610
|
-
this.__debug('SocketIOClient.onReconnect.onHandshakeClientSuccess');
|
|
611
|
-
this.__connected = true;
|
|
612
|
-
resolve();
|
|
613
|
-
})
|
|
614
|
-
.catch(err => {
|
|
615
|
-
this.__debug('SocketIOClient.onReconnect.onHandshakeClientError', err.message);
|
|
616
|
-
reject(err);
|
|
617
|
-
});
|
|
618
|
-
});
|
|
619
|
-
this.__io.on('reconnecting', attempt => {
|
|
620
|
-
this.__debug(`SocketIOClient.onReconnecting (Attempt #${attempt})`);
|
|
621
|
-
});
|
|
622
|
-
this.__io.on('reconnect_error', err => {
|
|
623
|
-
this.__debug('SocketIOClient.onReconnectError', err.message);
|
|
624
|
-
});
|
|
625
|
-
this.__io.once('connect_error', err => {
|
|
626
|
-
this.__debug('SocketIOClient.onConnectError', err.message);
|
|
627
|
-
reject(err);
|
|
628
|
-
});
|
|
629
|
-
this.__io.once('connect', () => {
|
|
630
|
-
this.__debug('SocketIOClient.onConnect');
|
|
631
|
-
this.__handshakeClient()
|
|
632
|
-
.then(() => {
|
|
633
|
-
this.__debug('SocketIOClient.onConnect.onHandshakeClientSuccess');
|
|
634
|
-
this.__connected = true;
|
|
635
|
-
resolve();
|
|
636
|
-
})
|
|
637
|
-
.catch(err => {
|
|
638
|
-
this.__debug('SocketIOClient.onConnect.onHandshakeClientError', err.message);
|
|
639
|
-
reject(err);
|
|
640
|
-
});
|
|
641
|
-
});
|
|
642
|
-
this.__io.connect();
|
|
643
|
-
});
|
|
644
|
-
});
|
|
645
|
-
this.io.catch(err => {
|
|
646
|
-
this.__debug('SocketIOClient Error', err.message);
|
|
647
|
-
});
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
return this.io;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
async disconnect() {
|
|
654
|
-
if (this.__io) {
|
|
655
|
-
await new Promise(resolve => {
|
|
656
|
-
this.__io.once('disconnect', resolve());
|
|
657
|
-
this.__io.disconnect();
|
|
658
|
-
this.__io.removeAllListeners();
|
|
659
|
-
this.__io = null;
|
|
660
|
-
});
|
|
661
|
-
}
|
|
662
|
-
// TODO todo what?
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
destroy() {
|
|
666
|
-
if (this.__io) {
|
|
667
|
-
this.__io.removeAllListeners();
|
|
668
|
-
this.__io.close();
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
async __handshakeClient() {
|
|
673
|
-
return new Promise((resolve, reject) => {
|
|
674
|
-
this.__io.emit('handshakeClient', {
|
|
675
|
-
token: this.__token,
|
|
676
|
-
homeyId: this.id,
|
|
677
|
-
}, (err, result) => {
|
|
678
|
-
if (err) return reject(err);
|
|
679
|
-
return resolve(result);
|
|
680
|
-
});
|
|
681
|
-
})
|
|
682
|
-
.catch(async err => {
|
|
683
|
-
// If token is expired, try to refresh
|
|
684
|
-
if (err.statusCode === 401) {
|
|
685
|
-
this.__debug('Token expired, refreshing...');
|
|
686
|
-
await this.logout();
|
|
687
|
-
await this.login();
|
|
688
|
-
|
|
689
|
-
return new Promise((resolve, reject) => {
|
|
690
|
-
this.__io.emit('handshakeClient', {
|
|
691
|
-
token: this.__token,
|
|
692
|
-
homeyId: this.id,
|
|
693
|
-
}, (err, result) => {
|
|
694
|
-
if (err) return reject(err);
|
|
695
|
-
return resolve(result);
|
|
696
|
-
});
|
|
697
|
-
});
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
throw err;
|
|
701
|
-
})
|
|
702
|
-
.then(({ namespace }) => {
|
|
703
|
-
this.__debug('SocketIOClient.onHandshakeClientSuccess', `Namespace: ${namespace}`);
|
|
704
|
-
|
|
705
|
-
return new Promise((resolve, reject) => {
|
|
706
|
-
this.__ioNamespace = this.__io.io.socket(namespace);
|
|
707
|
-
this.__ioNamespace.once('connect', () => {
|
|
708
|
-
this.__debug(`SocketIOClient.Namespace[${namespace}].onConnect`);
|
|
709
|
-
resolve();
|
|
710
|
-
});
|
|
711
|
-
this.__ioNamespace.once('connect_error', err => {
|
|
712
|
-
this.__debug(`SocketIOClient.Namespace[${namespace}].onConnectError`, err.message);
|
|
713
|
-
reject(err);
|
|
714
|
-
});
|
|
715
|
-
this.__ioNamespace.on('reconnecting', attempt => {
|
|
716
|
-
this.__debug(`SocketIOClient.Namespace[${namespace}].onReconnecting (Attempt #${attempt})`);
|
|
717
|
-
});
|
|
718
|
-
this.__ioNamespace.on('reconnect', () => {
|
|
719
|
-
this.__debug(`SocketIOClient.Namespace[${namespace}].onReconnect`);
|
|
720
|
-
});
|
|
721
|
-
this.__ioNamespace.on('reconnect_error', err => {
|
|
722
|
-
this.__debug(`SocketIOClient.Namespace[${namespace}].onReconnectError`, err.message);
|
|
723
|
-
});
|
|
724
|
-
this.__ioNamespace.on('disconnect', reason => {
|
|
725
|
-
this.__debug(`SocketIOClient.Namespace[${namespace}].onDisconnect`, reason);
|
|
726
|
-
});
|
|
727
|
-
this.__ioNamespace.connect();
|
|
728
|
-
});
|
|
729
|
-
});
|
|
730
|
-
}
|
|
731
|
-
|
|
732
28
|
}
|
|
733
29
|
|
|
734
30
|
module.exports = HomeyAPIV2;
|