homey-api 1.10.20 → 3.0.0-rc.10
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 +54 -647
- package/assets/types/homey-api.private.d.ts +54 -707
- 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 +53 -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 +6 -3
- package/lib/HomeyAPI/HomeyAPIV2/ManagerDrivers/Driver.js +25 -0
- package/lib/HomeyAPI/HomeyAPIV2/ManagerDrivers/PairSession.js +20 -0
- package/lib/HomeyAPI/HomeyAPIV2/ManagerDrivers.js +46 -0
- package/lib/HomeyAPI/HomeyAPIV2/ManagerFlow/AdvancedFlow.js +30 -0
- package/lib/HomeyAPI/HomeyAPIV2/ManagerFlow/Flow.js +57 -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 +127 -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 +22 -0
- package/lib/HomeyAPI/HomeyAPIV2/ManagerInsights.js +46 -0
- package/lib/HomeyAPI/HomeyAPIV2.js +12 -716
- package/lib/HomeyAPI/HomeyAPIV3/Item.js +186 -2
- package/lib/HomeyAPI/HomeyAPIV3/Manager.js +513 -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 +82 -7
- package/lib/HomeyAPI/{HomeyAPIV2 → HomeyAPIV3/ManagerDevices}/DeviceCapability.js +4 -4
- package/lib/HomeyAPI/HomeyAPIV3/ManagerDevices.js +17 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerDrivers/Driver.js +9 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerDrivers/PairSession.js +9 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerDrivers.js +17 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerFlow/AdvancedFlow.js +9 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerFlow/Flow.js +17 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerFlow/FlowCard.js +17 -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 +19 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerFlowToken.js +15 -0
- package/lib/HomeyAPI/HomeyAPIV3/ManagerInsights/Log.js +23 -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,8 +1,192 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const EventEmitter = require('../../EventEmitter');
|
|
4
4
|
|
|
5
|
-
class Item extends
|
|
5
|
+
class Item extends EventEmitter {
|
|
6
|
+
|
|
7
|
+
static ID = null; // Set by Manager.js
|
|
8
|
+
|
|
9
|
+
constructor({
|
|
10
|
+
id,
|
|
11
|
+
homey,
|
|
12
|
+
manager,
|
|
13
|
+
properties,
|
|
14
|
+
}) {
|
|
15
|
+
super();
|
|
16
|
+
|
|
17
|
+
// Set ID
|
|
18
|
+
Object.defineProperty(this, 'id', {
|
|
19
|
+
value: id,
|
|
20
|
+
enumerable: true,
|
|
21
|
+
writable: false,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Set Homey
|
|
25
|
+
Object.defineProperty(this, 'homey', {
|
|
26
|
+
value: homey,
|
|
27
|
+
enumerable: false,
|
|
28
|
+
writable: false,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Set Manager
|
|
32
|
+
Object.defineProperty(this, 'manager', {
|
|
33
|
+
value: manager,
|
|
34
|
+
enumerable: false,
|
|
35
|
+
writable: false,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Set Connected
|
|
39
|
+
Object.defineProperty(this, '__connected', {
|
|
40
|
+
value: false,
|
|
41
|
+
enumerable: false,
|
|
42
|
+
writable: true,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Set Properties
|
|
46
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
47
|
+
if (key === 'id') continue;
|
|
48
|
+
|
|
49
|
+
Object.defineProperty(this, key, {
|
|
50
|
+
value,
|
|
51
|
+
enumerable: true,
|
|
52
|
+
writable: true,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
get uri() {
|
|
58
|
+
return `homey:${this.constructor.ID}:${this.id}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
__debug(...props) {
|
|
62
|
+
this.manager.__debug(`[${this.constructor.name}:${this.id}]`, ...props);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
__update(properties) {
|
|
66
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
67
|
+
if (key === 'id') continue;
|
|
68
|
+
|
|
69
|
+
this[key] = value;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.emit('update', properties);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
__delete() {
|
|
76
|
+
this.destroy();
|
|
77
|
+
this.emit('delete');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async connect() {
|
|
81
|
+
this.__debug('connect');
|
|
82
|
+
|
|
83
|
+
// If disconnecting, await that first
|
|
84
|
+
try {
|
|
85
|
+
// Ensure all microtasks are done first. E.g. if disconnect is called in the same tick as
|
|
86
|
+
// connect. This way the disconnect is always started first so we can await the disconnect
|
|
87
|
+
// promise before we try to connect again.
|
|
88
|
+
await Promise.resolve();
|
|
89
|
+
await this.__disconnectPromise;
|
|
90
|
+
} catch (err) { }
|
|
91
|
+
|
|
92
|
+
this.__connectPromise = Promise.resolve().then(async () => {
|
|
93
|
+
if (!this.io) {
|
|
94
|
+
this.io = this.homey.subscribe(this.uri, {
|
|
95
|
+
onConnect: () => {
|
|
96
|
+
this.__debug('onConnect');
|
|
97
|
+
this.__connected = true;
|
|
98
|
+
|
|
99
|
+
this.onConnect();
|
|
100
|
+
},
|
|
101
|
+
onDisconnect: () => {
|
|
102
|
+
this.__debug('onDisconnect');
|
|
103
|
+
this.__connected = false;
|
|
104
|
+
|
|
105
|
+
this.onDisconnect();
|
|
106
|
+
},
|
|
107
|
+
onReconnect: () => {
|
|
108
|
+
this.__debug('onDisconnect');
|
|
109
|
+
|
|
110
|
+
this.onReconnect();
|
|
111
|
+
},
|
|
112
|
+
onEvent: (event, data) => {
|
|
113
|
+
this.__debug('onEvent', event, data);
|
|
114
|
+
|
|
115
|
+
this.emit(event, data);
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
await this.io;
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Delete the connecting Promise
|
|
124
|
+
this.__connectPromise
|
|
125
|
+
.catch(() => { })
|
|
126
|
+
.finally(() => {
|
|
127
|
+
delete this.__connectPromise;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
await this.__connectPromise;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async disconnect() {
|
|
134
|
+
this.__debug('disconnect');
|
|
135
|
+
|
|
136
|
+
// If connecting, await that first
|
|
137
|
+
try {
|
|
138
|
+
await this.__connectPromise;
|
|
139
|
+
} catch (err) { }
|
|
140
|
+
|
|
141
|
+
this.__disconnectPromise = Promise.resolve().then(async () => {
|
|
142
|
+
this.__connected = false;
|
|
143
|
+
|
|
144
|
+
if (this.io) {
|
|
145
|
+
this.io
|
|
146
|
+
.then(io => io.unsubscribe())
|
|
147
|
+
.catch(err => this.__debug('Error Disconnecting:', err));
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Delete the disconnecting Promise
|
|
152
|
+
this.__disconnectPromise
|
|
153
|
+
.catch(() => { })
|
|
154
|
+
.finally(() => {
|
|
155
|
+
delete this.__disconnectPromise;
|
|
156
|
+
// Delete this.io so connect can start new connections.
|
|
157
|
+
delete this.io;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
await this.__disconnectPromise;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
onConnect() {
|
|
164
|
+
// Overload Me
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
onReconnect() {
|
|
168
|
+
// Overload Me
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
onDisconnect() {
|
|
172
|
+
// Overload Me
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
destroy() {
|
|
176
|
+
// Remove all event listeners
|
|
177
|
+
this.removeAllListeners();
|
|
178
|
+
|
|
179
|
+
// Disconnect from Socket.io
|
|
180
|
+
this.disconnect().catch(() => { });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
static transformGet(item) {
|
|
184
|
+
return item;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
static transformSet(item) {
|
|
188
|
+
return item;
|
|
189
|
+
}
|
|
6
190
|
|
|
7
191
|
}
|
|
8
192
|
|
|
@@ -1,14 +1,524 @@
|
|
|
1
|
+
/* eslint-disable no-multi-assign */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
|
-
const
|
|
5
|
+
const EventEmitter = require('../../EventEmitter');
|
|
6
|
+
const Util = require('../../Util');
|
|
7
|
+
const HomeyAPIError = require('../HomeyAPIError');
|
|
8
|
+
|
|
9
|
+
// eslint-disable-next-line no-unused-vars
|
|
10
|
+
const Item = require('./Item');
|
|
4
11
|
|
|
5
12
|
/**
|
|
6
13
|
* @class
|
|
7
|
-
* @extends HomeyAPIV2.Manager
|
|
8
14
|
* @hideconstructor
|
|
9
15
|
* @memberof HomeyAPIV3
|
|
10
16
|
*/
|
|
11
|
-
class Manager extends
|
|
17
|
+
class Manager extends EventEmitter {
|
|
18
|
+
|
|
19
|
+
static ID = null; // Set by HomeyAPIV3.js
|
|
20
|
+
static CRUD = {};
|
|
21
|
+
|
|
22
|
+
constructor({
|
|
23
|
+
homey,
|
|
24
|
+
items,
|
|
25
|
+
operations,
|
|
26
|
+
}) {
|
|
27
|
+
super();
|
|
28
|
+
|
|
29
|
+
// Set Homey
|
|
30
|
+
Object.defineProperty(this, 'homey', {
|
|
31
|
+
value: homey,
|
|
32
|
+
enumerable: false,
|
|
33
|
+
writable: false,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Set Items
|
|
37
|
+
Object.defineProperty(this, 'items', {
|
|
38
|
+
value: Object.entries(items).reduce((obj, [itemName, item]) => {
|
|
39
|
+
const ItemClass = this.constructor.CRUD[itemName]
|
|
40
|
+
? this.constructor.CRUD[itemName]
|
|
41
|
+
// eslint-disable-next-line no-eval
|
|
42
|
+
: eval(`(class ${itemName} extends Item {})`);
|
|
43
|
+
ItemClass.ID = item.id;
|
|
44
|
+
obj[item.id] = ItemClass;
|
|
45
|
+
|
|
46
|
+
return obj;
|
|
47
|
+
}, {}),
|
|
48
|
+
enumerable: false,
|
|
49
|
+
writable: false,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Set Connected
|
|
53
|
+
Object.defineProperty(this, '__connected', {
|
|
54
|
+
value: false,
|
|
55
|
+
enumerable: false,
|
|
56
|
+
writable: true,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Set Cache
|
|
60
|
+
Object.defineProperty(this, '__cache', {
|
|
61
|
+
value: Object.values(items).reduce((obj, item) => ({
|
|
62
|
+
...obj,
|
|
63
|
+
[item.id]: {},
|
|
64
|
+
}), {}),
|
|
65
|
+
enumerable: false,
|
|
66
|
+
writable: false,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
Object.defineProperty(this, '__cacheAllComplete', {
|
|
70
|
+
value: Object.values(items).reduce((obj, item) => ({
|
|
71
|
+
...obj,
|
|
72
|
+
[item.id]: false,
|
|
73
|
+
}), {}),
|
|
74
|
+
enumerable: false,
|
|
75
|
+
writable: false,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Create methods
|
|
79
|
+
for (const [operationId, operation] of Object.entries(operations)) {
|
|
80
|
+
Object.defineProperty(this,
|
|
81
|
+
// Name method __super__foo if there's an override method
|
|
82
|
+
this[operationId]
|
|
83
|
+
? `__super__${operationId}`
|
|
84
|
+
: operationId,
|
|
85
|
+
{
|
|
86
|
+
value: async ({
|
|
87
|
+
$validate = true,
|
|
88
|
+
$cache = true,
|
|
89
|
+
$timeout = operation.timeout ?? 5000,
|
|
90
|
+
$socket = operation.socket ?? true,
|
|
91
|
+
$body = {},
|
|
92
|
+
$query = {},
|
|
93
|
+
$headers = {},
|
|
94
|
+
...args
|
|
95
|
+
} = {}) => {
|
|
96
|
+
let { path } = operation;
|
|
97
|
+
let body = { ...$body };
|
|
98
|
+
const query = { ...$query };
|
|
99
|
+
const headers = { ...$headers };
|
|
100
|
+
|
|
101
|
+
// Verify & Transform parameters
|
|
102
|
+
if (operation.parameters) {
|
|
103
|
+
// Parse Parameters
|
|
104
|
+
for (const [parameterId, parameter] of Object.entries(operation.parameters)) {
|
|
105
|
+
const value = args[parameterId];
|
|
106
|
+
|
|
107
|
+
// Validate the parameter
|
|
108
|
+
if ($validate) {
|
|
109
|
+
if (parameter.required === true && typeof value === 'undefined') {
|
|
110
|
+
throw new Error(`Missing Parameter: ${parameterId}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (typeof value !== 'undefined') {
|
|
114
|
+
if (parameter.type === 'string' && typeof value !== 'string') {
|
|
115
|
+
throw new Error(`Invalid Parameter Type: ${parameterId}. Got: ${typeof value}. Expected: string`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (parameter.type === 'number' && typeof value !== 'number') {
|
|
119
|
+
throw new Error(`Invalid Parameter Type: ${parameterId}. Got: ${typeof value}. Expected: number`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (parameter.type === 'boolean' && typeof value !== 'boolean') {
|
|
123
|
+
throw new Error(`Invalid Parameter Type: ${parameterId}. Got: ${typeof value}. Expected: boolean`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (parameter.type === 'object' && typeof value !== 'object') {
|
|
127
|
+
throw new Error(`Invalid Parameter Type: ${parameterId}. Got: ${typeof value}. Expected: object`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (parameter.type === 'array' && !Array.isArray(value)) {
|
|
131
|
+
throw new Error(`Invalid Parameter Type: ${parameterId}. Got: ${typeof value}. Expected: array`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (Array.isArray(parameter.type)) {
|
|
135
|
+
// TODO
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Set the parameter
|
|
141
|
+
if (typeof value !== 'undefined') {
|
|
142
|
+
switch (parameter.in) {
|
|
143
|
+
case 'path': {
|
|
144
|
+
if (typeof value !== 'string') {
|
|
145
|
+
throw new Error(`Invalid Parameter Type: ${parameterId}. Got: ${typeof value}. Expected: string`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
path = path.replace(`:${parameterId}`, value);
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case 'body': {
|
|
152
|
+
if (parameter.root) {
|
|
153
|
+
body = value;
|
|
154
|
+
} else {
|
|
155
|
+
body[parameterId] = value;
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
case 'query': {
|
|
160
|
+
if (typeof value !== 'string') {
|
|
161
|
+
throw new Error(`Invalid Parameter Type: ${parameterId}. Got: ${typeof value}. Expected: string`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
query[parameterId] = value;
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
default: {
|
|
168
|
+
throw new Error(`Invalid 'in': ${parameter.in}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Append query to path
|
|
176
|
+
if (Object.keys(query).length > 0) {
|
|
177
|
+
const queryString = Object.entries(query).map(([key, value]) => {
|
|
178
|
+
return `${key}=${encodeURIComponent(value)}`;
|
|
179
|
+
}).join('&');
|
|
180
|
+
path = `${path}?${queryString}`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
let result;
|
|
184
|
+
const benchmark = Util.benchmark();
|
|
185
|
+
|
|
186
|
+
// If connected to Socket.io,
|
|
187
|
+
// try to get the CRUD Item from Cache.
|
|
188
|
+
if (this.isConnected() && operation.crud && $cache === true) {
|
|
189
|
+
const itemId = items[operation.crud.item].id;
|
|
190
|
+
|
|
191
|
+
switch (operation.crud.type) {
|
|
192
|
+
case 'getOne': {
|
|
193
|
+
if (this.__cache[itemId][args.id]) {
|
|
194
|
+
return this.__cache[itemId][args.id];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
case 'getAll': {
|
|
200
|
+
if (this.__cache[itemId]
|
|
201
|
+
&& this.__cacheAllComplete[itemId]) {
|
|
202
|
+
return this.__cache[itemId];
|
|
203
|
+
}
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
default:
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// If Homey is connected to Socket.io,
|
|
212
|
+
// send the API request to socket.io.
|
|
213
|
+
// This is about ~2x faster than HTTP
|
|
214
|
+
if (this.homey.isConnected() && $socket === true) {
|
|
215
|
+
result = await Util.timeout(new Promise((resolve, reject) => {
|
|
216
|
+
this.homey.__ioNamespace.emit('api', {
|
|
217
|
+
args,
|
|
218
|
+
operation: operationId,
|
|
219
|
+
uri: this.uri,
|
|
220
|
+
}, (err, result) => {
|
|
221
|
+
// String Error
|
|
222
|
+
if (typeof err === 'string') {
|
|
223
|
+
err = new HomeyAPIError({
|
|
224
|
+
error: err,
|
|
225
|
+
}, 500);
|
|
226
|
+
return reject(err);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Object Error
|
|
230
|
+
if (typeof err === 'object' && err !== null) {
|
|
231
|
+
err = new HomeyAPIError({
|
|
232
|
+
stack: err.stack,
|
|
233
|
+
error: err.error,
|
|
234
|
+
error_description: err.error_description,
|
|
235
|
+
}, err.statusCode || 500);
|
|
236
|
+
return reject(err);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return resolve(result);
|
|
240
|
+
});
|
|
241
|
+
}), $timeout);
|
|
242
|
+
} else {
|
|
243
|
+
// Get from HTTP
|
|
244
|
+
result = await this.homey.call({
|
|
245
|
+
$timeout,
|
|
246
|
+
headers,
|
|
247
|
+
body,
|
|
248
|
+
path: `/api/manager/${this.constructor.ID}${path}`,
|
|
249
|
+
method: operation.method,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Transform and cache output if this is a CRUD call
|
|
254
|
+
if (operation.crud) {
|
|
255
|
+
const itemId = items[operation.crud.item].id;
|
|
256
|
+
const Item = this.items[itemId];
|
|
257
|
+
|
|
258
|
+
switch (operation.crud.type) {
|
|
259
|
+
case 'getOne': {
|
|
260
|
+
let item = { ...result };
|
|
261
|
+
item = Item.transformGet(item);
|
|
262
|
+
item = new Item({
|
|
263
|
+
id: item.id,
|
|
264
|
+
homey: this.homey,
|
|
265
|
+
manager: this,
|
|
266
|
+
properties: { ...item },
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
if (this.isConnected()) {
|
|
270
|
+
this.__cache[itemId][item.id] = item;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return item;
|
|
274
|
+
}
|
|
275
|
+
case 'getAll': {
|
|
276
|
+
const items = {};
|
|
277
|
+
|
|
278
|
+
// Add all to cache
|
|
279
|
+
for (let item of Object.values(result)) {
|
|
280
|
+
item = Item.transformGet(item);
|
|
281
|
+
if (this.__cache[itemId][item.id]) {
|
|
282
|
+
items[item.id] = this.__cache[itemId][item.id];
|
|
283
|
+
items[item.id].__update(item);
|
|
284
|
+
} else {
|
|
285
|
+
items[item.id] = new Item({
|
|
286
|
+
id: item.id,
|
|
287
|
+
homey: this.homey,
|
|
288
|
+
manager: this,
|
|
289
|
+
properties: { ...item },
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
if (this.isConnected()) {
|
|
293
|
+
this.__cache[itemId][item.id] = items[item.id];
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Find and delete deleted items from cache
|
|
299
|
+
if (this.__cache[itemId]) {
|
|
300
|
+
for (const cachedItem of Object.values(this.__cache[itemId])) {
|
|
301
|
+
if (!items[cachedItem.id]) {
|
|
302
|
+
delete this.__cache[itemId][cachedItem.id];
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Mark cache as complete
|
|
308
|
+
if (this.isConnected()) {
|
|
309
|
+
this.__cacheAllComplete[itemId] = true;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return items;
|
|
313
|
+
}
|
|
314
|
+
case 'createOne':
|
|
315
|
+
case 'updateOne': {
|
|
316
|
+
let item = { ...result };
|
|
317
|
+
item = Item.transformGet(item);
|
|
318
|
+
|
|
319
|
+
if (this.__cache[itemId][item.id]) {
|
|
320
|
+
item = this.__cache[itemId][item.id];
|
|
321
|
+
item.__update(item);
|
|
322
|
+
} else {
|
|
323
|
+
item = Item.transformGet(item);
|
|
324
|
+
item = new Item({
|
|
325
|
+
id: item.id,
|
|
326
|
+
homey: this.homey,
|
|
327
|
+
manager: this,
|
|
328
|
+
properties: { ...item },
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
if (this.isConnected()) {
|
|
332
|
+
this.__cache[itemId][item.id] = item;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return item;
|
|
337
|
+
}
|
|
338
|
+
case 'deleteOne': {
|
|
339
|
+
if (this.__cache[itemId][args.id]) {
|
|
340
|
+
this.__cache[itemId][args.id].destroy();
|
|
341
|
+
delete this.__cache[itemId][args.id];
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return undefined;
|
|
345
|
+
}
|
|
346
|
+
default:
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
this.__debug(`${operationId} took ${benchmark()}ms`);
|
|
352
|
+
return result;
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
get uri() {
|
|
359
|
+
return `homey:manager:${this.constructor.ID}`;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
__debug(...props) {
|
|
363
|
+
this.homey.__debug(`[${this.constructor.name}]`, ...props);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* If this manager's namespace is connected to Socket.io.
|
|
368
|
+
* @returns {Boolean}
|
|
369
|
+
*/
|
|
370
|
+
isConnected() {
|
|
371
|
+
return this.__connected === true;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Connect to the realtime namespace.
|
|
376
|
+
* @returns {Promise<void>}
|
|
377
|
+
*/
|
|
378
|
+
async connect() {
|
|
379
|
+
this.__debug('connect');
|
|
380
|
+
|
|
381
|
+
// If disconnecting, await that first
|
|
382
|
+
try {
|
|
383
|
+
await this.__disconnectPromise;
|
|
384
|
+
} catch (err) { }
|
|
385
|
+
|
|
386
|
+
this.__connectPromise = Promise.resolve().then(async () => {
|
|
387
|
+
if (!this.io) {
|
|
388
|
+
this.io = this.homey.subscribe(this.uri, {
|
|
389
|
+
onConnect: () => {
|
|
390
|
+
this.__debug('onConnect');
|
|
391
|
+
this.__connected = true;
|
|
392
|
+
},
|
|
393
|
+
onDisconnect: reason => {
|
|
394
|
+
this.__debug(`onDisconnect Reason:${reason}`);
|
|
395
|
+
this.__connected = false;
|
|
396
|
+
|
|
397
|
+
// Clear CRUD Item cache
|
|
398
|
+
for (const itemId of Object.keys(this.__cache)) {
|
|
399
|
+
this.__cache[itemId] = {};
|
|
400
|
+
this.__cacheAllComplete[itemId] = false;
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
onEvent: (event, data) => {
|
|
404
|
+
this.__debug('onEvent', event);
|
|
405
|
+
|
|
406
|
+
// Transform & add to cache if this is a CRUD event
|
|
407
|
+
if (event.endsWith('.create')
|
|
408
|
+
|| event.endsWith('.update')
|
|
409
|
+
|| event.endsWith('.delete')) {
|
|
410
|
+
const [itemId, operation] = event.split('.');
|
|
411
|
+
const Item = this.items[itemId];
|
|
412
|
+
|
|
413
|
+
switch (operation) {
|
|
414
|
+
case 'create': {
|
|
415
|
+
data = Item.transformGet(data);
|
|
416
|
+
|
|
417
|
+
const item = new Item({
|
|
418
|
+
id: data.id,
|
|
419
|
+
homey: this.homey,
|
|
420
|
+
manager: this,
|
|
421
|
+
properties: { ...data },
|
|
422
|
+
});
|
|
423
|
+
this.__cache[itemId][data.id] = item;
|
|
424
|
+
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
case 'update': {
|
|
428
|
+
data = Item.transformGet(data);
|
|
429
|
+
|
|
430
|
+
if (this.__cache[itemId][data.id]) {
|
|
431
|
+
const item = this.__cache[itemId][data.id];
|
|
432
|
+
item.__update(data);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
case 'delete': {
|
|
438
|
+
data = Item.transformGet(data);
|
|
439
|
+
|
|
440
|
+
if (this.__cache[itemId][data.id]) {
|
|
441
|
+
const item = this.__cache[itemId][data.id];
|
|
442
|
+
item.__delete();
|
|
443
|
+
delete this.__cache[itemId][item.id];
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
default:
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Fire event listeners
|
|
454
|
+
this.emit(event, data);
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
await this.io;
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// Delete the connecting Promise
|
|
463
|
+
this.__connectPromise
|
|
464
|
+
.catch(() => { })
|
|
465
|
+
.finally(() => {
|
|
466
|
+
delete this.__connectPromise;
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
await this.__connectPromise;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Disconnect from the realtime namespace.
|
|
474
|
+
* @returns {Promise<void>}
|
|
475
|
+
*/
|
|
476
|
+
async disconnect() {
|
|
477
|
+
this.__debug('disconnect');
|
|
478
|
+
|
|
479
|
+
// If connecting, await that first
|
|
480
|
+
try {
|
|
481
|
+
await this.__connectPromise;
|
|
482
|
+
} catch (err) { }
|
|
483
|
+
|
|
484
|
+
this.__disconnectPromise = Promise.resolve().then(async () => {
|
|
485
|
+
this.__connected = false;
|
|
486
|
+
|
|
487
|
+
if (this.io) {
|
|
488
|
+
await this.io
|
|
489
|
+
.then(io => io.unsubscribe())
|
|
490
|
+
.catch(err => this.__debug('Error Disconnecting:', err));
|
|
491
|
+
|
|
492
|
+
delete this.io;
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// Delete the disconnecting Promise
|
|
497
|
+
this.__disconnectPromise
|
|
498
|
+
.catch(() => { })
|
|
499
|
+
.finally(() => {
|
|
500
|
+
delete this.__disconnectPromise;
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
await this.__disconnectPromise;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
destroy() {
|
|
507
|
+
// Clear cache
|
|
508
|
+
for (const id of Object.keys(this.__cache)) {
|
|
509
|
+
this.__cache[id] = {};
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
for (const id of Object.keys(this.__cacheAllComplete)) {
|
|
513
|
+
this.__cacheAllComplete[id] = false;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Remove all event listeners
|
|
517
|
+
this.removeAllListeners();
|
|
518
|
+
|
|
519
|
+
// Disconnect from Socket.io
|
|
520
|
+
this.disconnect().catch(() => { });
|
|
521
|
+
}
|
|
12
522
|
|
|
13
523
|
}
|
|
14
524
|
|