homey-api 1.6.0 → 1.7.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/assets/templates/jsdoc/api.ejs +59 -0
- package/assets/templates/jsdoc/item.ejs +35 -0
- package/assets/templates/jsdoc/manager.ejs +76 -0
- package/assets/templates/types/declaration.ejs +62 -0
- package/assets/types/homey-api.d.ts +23 -0
- package/assets/types/homey-api.private.d.ts +67 -0
- package/lib/AthomCloudAPI.js +108 -16
- package/lib/HomeyAPI/HomeyAPIV2/Device.js +8 -0
- package/lib/Util.js +45 -7
- package/package.json +9 -3
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Class
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @class <%= Api.name %>
|
|
5
|
+
*
|
|
6
|
+
* @description
|
|
7
|
+
* <% if(Api.JSDOC_DESCRIPTION) { %>
|
|
8
|
+
<%- Api.JSDOC_DESCRIPTION.split('\n').join('\n *'); %>
|
|
9
|
+
* <% } %>
|
|
10
|
+
* This API is available at `https://<%= Api.SPECIFICATION.host %><%= Api.SPECIFICATION.basePath %>`
|
|
11
|
+
*
|
|
12
|
+
* <% if(Api.JSDOC_EXAMPLE) { %>
|
|
13
|
+
* @example <%- Api.JSDOC_EXAMPLE %>
|
|
14
|
+
* <% } %>
|
|
15
|
+
*
|
|
16
|
+
* <% if(Api.JSDOC_PRIVATE === true) { %>
|
|
17
|
+
* @private
|
|
18
|
+
* <% } %>
|
|
19
|
+
*
|
|
20
|
+
* @param {object} [opts]
|
|
21
|
+
* @param {string} [opts.baseUrl=`https://<%= Api.SPECIFICATION.host %><%= Api.SPECIFICATION.basePath %>`] - The Base URL of the API. Set `<%= envKey %>_BASEURL=http://...` as environment variable on Node.js, or in `window.localStorage` on browsers to override.
|
|
22
|
+
* @param {boolean} [opts.debug=false] - Send debug messages to `console.log` if set to `true`. Set `<%= envKey %>_DEBUG=1` as environment variable on Node.js, or in `window.localStorage` on browsers to override.
|
|
23
|
+
* @param {secret} [opts.secret] - A shared secret for APIs with a `secret` parameter in an endpoint. Set `<%= envKey %>_SECRET=...` as environment variable on Node.js, or in `window.localStorage` on browsers to override.
|
|
24
|
+
* <% if( Api.JSDOC_PARAMS ) { %>
|
|
25
|
+
<%- Api.JSDOC_PARAMS.split('\n').join('\n *'); %>
|
|
26
|
+
* <% } %>
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
// Methods
|
|
30
|
+
<% Object.entries(Api.SPECIFICATION.operations).forEach(([ operationId, operation ]) => { %>
|
|
31
|
+
/**
|
|
32
|
+
* <h4>HTTP</h4>
|
|
33
|
+
*
|
|
34
|
+
* `<%= String(operation.method).toUpperCase() %> <%= operation.path %>`
|
|
35
|
+
*
|
|
36
|
+
* @async
|
|
37
|
+
* @function <%= Api.name %>#<%= operationId %>
|
|
38
|
+
* <% if(operation.private) { %>
|
|
39
|
+
* @private
|
|
40
|
+
* <% } %>
|
|
41
|
+
*
|
|
42
|
+
* <% if (Object.keys(operation.parameters || {}).length) { %>
|
|
43
|
+
* @param {object} opts
|
|
44
|
+
* <% Object.entries(operation.parameters).forEach(([parameterId, parameter]) => { %>
|
|
45
|
+
* <% if (parameter.required) { %>
|
|
46
|
+
* @param {<%= String(parameter.type).replace(/\,/g, '|') %>} opts.<%= parameterId %> - In `<%= parameter.in %>` <% if( parameter.unpack ) { %><span>(unpacked)</span><% } %>
|
|
47
|
+
* <% } else { %>
|
|
48
|
+
* @param {<%= String(parameter.type).replace(/\,/g, '|') %>} [opts.<%= parameterId %>] - In `<%= parameter.in %>` <% if( parameter.unpack ) { %><span>(unpacked)</span><% } %>
|
|
49
|
+
* <% } %>
|
|
50
|
+
* <% if (Object.keys(parameter.properties || {}).length) { %>
|
|
51
|
+
* <% Object.entries(parameter.properties).forEach(([propertyId, property]) => { %>
|
|
52
|
+
* @param {<%= String(property.type).replace(/\,/g, '|') %>} opts.<%= parameterId %>.<%= propertyId %>
|
|
53
|
+
* <% }) %>
|
|
54
|
+
* <% } %>
|
|
55
|
+
* <% }) %>
|
|
56
|
+
* <% } %>
|
|
57
|
+
*
|
|
58
|
+
*/
|
|
59
|
+
<% }) %>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Class
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* <%= itemName %> as returned by {@link <%= HomeyAPI %>.<%= managerName %>}.
|
|
5
|
+
* @class <%= itemName %>
|
|
6
|
+
* @hideconstructor
|
|
7
|
+
* @memberof <%= HomeyAPI %>.<%= managerName %>
|
|
8
|
+
*
|
|
9
|
+
* <% if(manager.private) { %>
|
|
10
|
+
* @private
|
|
11
|
+
* <% } %>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// Properties
|
|
15
|
+
|
|
16
|
+
<% if (item.schema) { %>
|
|
17
|
+
<% for (const [propertyId, property] of Object.entries(item.schema.properties || {}) ) { %>
|
|
18
|
+
/**
|
|
19
|
+
* <% if (property.type) { %>
|
|
20
|
+
* @var {<%= String(property.type || '*').replace(/\,/g, '|') %>} <%= HomeyAPI %>.<%= managerName %>.<%= itemName %>#<%= propertyId %>
|
|
21
|
+
* <% } %>
|
|
22
|
+
*/
|
|
23
|
+
<% } %>
|
|
24
|
+
<% } %>
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @memberof <%= HomeyAPI %>.<%= managerName%>.<%= itemName %>
|
|
28
|
+
* @event update
|
|
29
|
+
* @param {object} <%= item.id %>
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @memberof <%= HomeyAPI %>.<%= managerName%>.<%= itemName %>
|
|
34
|
+
* @event delete
|
|
35
|
+
*/
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Class
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @description Access this instance at `{@link <%= HomeyAPI %>}.<%= manager.idCamelCase; %>`.
|
|
5
|
+
* @class <%= managerName %>
|
|
6
|
+
* @hideconstructor
|
|
7
|
+
* @extends <%= HomeyAPI %>.Manager
|
|
8
|
+
* @memberof <%= HomeyAPI %>
|
|
9
|
+
*
|
|
10
|
+
* <% if(manager.private) { %>
|
|
11
|
+
* @private
|
|
12
|
+
* <% } %>
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// Methods
|
|
16
|
+
<% Object.entries(manager.operations || {}).forEach(([ operationId, operation ]) => { %>
|
|
17
|
+
/**
|
|
18
|
+
* <h4>Scopes</h4>
|
|
19
|
+
*
|
|
20
|
+
* `<%= operation.scopes.length ? operation.scopes.join(', ') : '-' %>`
|
|
21
|
+
*
|
|
22
|
+
* <h4>HTTP</h4>
|
|
23
|
+
*
|
|
24
|
+
* `<%= operation.method %> /api/manager/<%= manager.id %><%= operation.path %>`
|
|
25
|
+
*
|
|
26
|
+
* @async
|
|
27
|
+
* @function <%= HomeyAPI %>.<%= managerName %>#<%= operationId %>
|
|
28
|
+
* <% if(operation.private) { %>
|
|
29
|
+
* @private
|
|
30
|
+
* <% } %>
|
|
31
|
+
*
|
|
32
|
+
* <% if (Object.keys(operation.parameters || {}).length) { %>
|
|
33
|
+
* @param {object} opts
|
|
34
|
+
* <% Object.entries(operation.parameters).forEach(([ parameterId, parameter ]) => { %>
|
|
35
|
+
* <% if (parameter.required) { %>
|
|
36
|
+
* @param {<%= String(parameter.type || '*').replace(/\,/g, '|') %>} opts.<%= parameterId %>
|
|
37
|
+
* <% } else { %>
|
|
38
|
+
* @param {<%= String(parameter.type || '*').replace(/\,/g, '|') %>} [opts.<%= parameterId %>]
|
|
39
|
+
* <% } %>
|
|
40
|
+
* <% }) %>
|
|
41
|
+
* <% } %>
|
|
42
|
+
*
|
|
43
|
+
* <% if(operation.crud) { %>
|
|
44
|
+
* <% if(operation.crud.type === 'getOne' || operation.crud.type === 'createOne' || operation.crud.type === 'updateOne') { %>
|
|
45
|
+
* @returns {Promise<<%= HomeyAPI %>.<%= managerName %>.<%= operation.crud.item %>>}
|
|
46
|
+
* <% } else if(operation.crud.type === 'getAll') { %>
|
|
47
|
+
* @returns {Promise<<%= HomeyAPI %>.<%= managerName %>.<%= operation.crud.item %>>}
|
|
48
|
+
* <% } %>
|
|
49
|
+
* <% } else { %>
|
|
50
|
+
* @returns {Promise<any>}
|
|
51
|
+
* <% } %>
|
|
52
|
+
*/
|
|
53
|
+
<% }) %>
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
// CRUD Events
|
|
58
|
+
<% Object.entries(manager.items || {}).forEach(([ itemName, item ]) => { %>
|
|
59
|
+
/**
|
|
60
|
+
* @memberof <%= HomeyAPI %>.<%= managerName%>
|
|
61
|
+
* @event "<%= item.id %>.create"
|
|
62
|
+
* @param {<%= HomeyAPI %>.<%= managerName %>.<%= itemName %>} <%= item.id %>
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @memberof <%= HomeyAPI %>.<%= managerName%>
|
|
67
|
+
* @event "<%= item.id %>.update"
|
|
68
|
+
* @param {<%= HomeyAPI %>.<%= managerName %>.<%= itemName %>} <%= item.id %>
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @memberof <%= HomeyAPI %>.<%= managerName%>
|
|
73
|
+
* @event "<%= item.id %>.delete"
|
|
74
|
+
* @param {<%= HomeyAPI %>.<%= managerName %>.<%= itemName %>} <%= item.id %>
|
|
75
|
+
*/
|
|
76
|
+
<% }) %>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<%
|
|
2
|
+
function jsdocToTypescript(type) {
|
|
3
|
+
return type.names
|
|
4
|
+
.flatMap(type => type.split('|'))
|
|
5
|
+
.map(type => {
|
|
6
|
+
if (type === 'secret') return 'string';
|
|
7
|
+
if (type === 'array') return 'Array<any>';
|
|
8
|
+
if (type === 'date') return 'string';
|
|
9
|
+
// replace Promise.<> with Promise<>
|
|
10
|
+
return type.replace(/\.\</g, '<');
|
|
11
|
+
})
|
|
12
|
+
.join(' | ');
|
|
13
|
+
}
|
|
14
|
+
%>
|
|
15
|
+
|
|
16
|
+
<% function renderParam(params) { %>
|
|
17
|
+
<% Object.entries(params).forEach(([key, value]) => { %>
|
|
18
|
+
<% if (typeof value === 'object') { %>
|
|
19
|
+
<%= key %>: {<%= renderParam(value) %>},
|
|
20
|
+
<% } else { %>
|
|
21
|
+
<%= key %>: <%- jsdocToTypescript({ names: [value] }) %>,
|
|
22
|
+
<% } %>
|
|
23
|
+
<% }) %>
|
|
24
|
+
<% } %>
|
|
25
|
+
|
|
26
|
+
<% function renderClass(namespaceClass) { %>
|
|
27
|
+
export class <%= namespaceClass.name %> <%= Array.isArray(namespaceClass.augments) ? 'extends ' + namespaceClass.augments[0] : '' %> {
|
|
28
|
+
<% if (namespaceClass.constructor && namespaceClass.hideconstructor !== true) { %>
|
|
29
|
+
constructor(
|
|
30
|
+
<% if (namespaceClass.constructor.params) { %>
|
|
31
|
+
<%= renderParam(namespaceClass.constructor.params) %>
|
|
32
|
+
<% } %>
|
|
33
|
+
)
|
|
34
|
+
<% } %>
|
|
35
|
+
|
|
36
|
+
<% namespaceClass.members.forEach(classMember => { %>
|
|
37
|
+
<%= classMember.name %>: <%- jsdocToTypescript(classMember.type) %>;
|
|
38
|
+
<% }) %>
|
|
39
|
+
|
|
40
|
+
<% namespaceClass.functions.forEach(classFunction => { %>
|
|
41
|
+
<%= classFunction.name %>(
|
|
42
|
+
<% if (classFunction.params) { %>
|
|
43
|
+
<%= renderParam(classFunction.params) %>
|
|
44
|
+
<% } %>
|
|
45
|
+
):
|
|
46
|
+
<%- classFunction.returns
|
|
47
|
+
? jsdocToTypescript(classFunction.returns[0].type)
|
|
48
|
+
: classFunction.async ? 'Promise<any>' : 'any'
|
|
49
|
+
%>;
|
|
50
|
+
<% }) %>
|
|
51
|
+
}
|
|
52
|
+
<% } %>
|
|
53
|
+
|
|
54
|
+
<% global.forEach(globalClass => renderClass(globalClass)) %>
|
|
55
|
+
|
|
56
|
+
<% Object.entries(namespaces).forEach(([namespaceId, namespaceClasses]) => { %>
|
|
57
|
+
<% if (namespaceClasses.length) { %>
|
|
58
|
+
export namespace <%= namespaceId %> {
|
|
59
|
+
<% namespaceClasses.forEach(namespaceClass => renderClass(namespaceClass)) %>
|
|
60
|
+
}
|
|
61
|
+
<% } %>
|
|
62
|
+
<% }); %>
|
|
@@ -201,6 +201,29 @@
|
|
|
201
201
|
):
|
|
202
202
|
Promise<void>;
|
|
203
203
|
|
|
204
|
+
authenticateWithAuthorizationCode(
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
opts: {
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
code: String,
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
removeCodeFromHistory: Boolean,
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
):
|
|
225
|
+
Promise<AthomCloudAPI.Token>;
|
|
226
|
+
|
|
204
227
|
}
|
|
205
228
|
|
|
206
229
|
export class HomeyAPI {
|
|
@@ -2985,6 +2985,73 @@
|
|
|
2985
2985
|
):
|
|
2986
2986
|
Promise<void>;
|
|
2987
2987
|
|
|
2988
|
+
authenticateWithAuthorizationCode(
|
|
2989
|
+
|
|
2990
|
+
|
|
2991
|
+
|
|
2992
|
+
|
|
2993
|
+
opts: {
|
|
2994
|
+
|
|
2995
|
+
|
|
2996
|
+
code: String,
|
|
2997
|
+
|
|
2998
|
+
|
|
2999
|
+
|
|
3000
|
+
removeCodeFromHistory: Boolean,
|
|
3001
|
+
|
|
3002
|
+
|
|
3003
|
+
},
|
|
3004
|
+
|
|
3005
|
+
|
|
3006
|
+
|
|
3007
|
+
|
|
3008
|
+
):
|
|
3009
|
+
Promise<AthomCloudAPI.Token>;
|
|
3010
|
+
|
|
3011
|
+
updateUserMe(
|
|
3012
|
+
|
|
3013
|
+
|
|
3014
|
+
|
|
3015
|
+
|
|
3016
|
+
opts: {
|
|
3017
|
+
|
|
3018
|
+
|
|
3019
|
+
firstname: String,
|
|
3020
|
+
|
|
3021
|
+
|
|
3022
|
+
|
|
3023
|
+
lastname: String,
|
|
3024
|
+
|
|
3025
|
+
|
|
3026
|
+
|
|
3027
|
+
email: String,
|
|
3028
|
+
|
|
3029
|
+
|
|
3030
|
+
},
|
|
3031
|
+
|
|
3032
|
+
|
|
3033
|
+
|
|
3034
|
+
|
|
3035
|
+
):
|
|
3036
|
+
Promise<AthomCloudAPI.User>;
|
|
3037
|
+
|
|
3038
|
+
updateUserMeAvatar(
|
|
3039
|
+
|
|
3040
|
+
|
|
3041
|
+
|
|
3042
|
+
|
|
3043
|
+
imageBuffer: Buffer,
|
|
3044
|
+
|
|
3045
|
+
|
|
3046
|
+
|
|
3047
|
+
imageType: "jpg" | "jpeg" | "png" | "gif",
|
|
3048
|
+
|
|
3049
|
+
|
|
3050
|
+
|
|
3051
|
+
|
|
3052
|
+
):
|
|
3053
|
+
Promise<Object>;
|
|
3054
|
+
|
|
2988
3055
|
}
|
|
2989
3056
|
|
|
2990
3057
|
export class AthomConnectAPI {
|
package/lib/AthomCloudAPI.js
CHANGED
|
@@ -35,7 +35,7 @@ and login on that user's Homey.`;
|
|
|
35
35
|
const AthomCloudAPI = require('homey-api/lib/AthomCloudAPI');
|
|
36
36
|
|
|
37
37
|
// Create an AthomCloudAPI instance
|
|
38
|
-
const
|
|
38
|
+
const cloudApi = new {@link AthomCloudAPI AthomCloudAPI}({
|
|
39
39
|
clientId: '5a8d4ca6eb9f7a2c9d6ccf6d',
|
|
40
40
|
clientSecret: 'e3ace394af9f615857ceaa61b053f966ddcfb12a',
|
|
41
41
|
redirectUrl: 'http://localhost',
|
|
@@ -43,30 +43,38 @@ const api = new AthomCloudAPI({
|
|
|
43
43
|
|
|
44
44
|
// Check if we're logged in
|
|
45
45
|
// If not, redirect the user to the OAuth2 dialog
|
|
46
|
-
const loggedIn = await
|
|
46
|
+
const loggedIn = await {@link AthomCloudAPI cloudApi}.{@link AthomCloudAPI#isLoggedIn isLoggedIn}();
|
|
47
47
|
if (!loggedIn) {
|
|
48
|
-
if (
|
|
49
|
-
const token = await
|
|
48
|
+
if ({@link AthomCloudAPI cloudApi}.{@link AthomCloudAPI#hasAuthorizationCode hasAuthorizationCode}()) {
|
|
49
|
+
const token = await {@link AthomCloudAPI cloudApi}.{@link AthomCloudAPI#authenticateWithAuthorizationCode authenticateWithAuthorizationCode}();
|
|
50
50
|
} else {
|
|
51
|
-
window.location.href =
|
|
51
|
+
window.location.href = {@link AthomCloudAPI cloudApi}.{@link AthomCloudAPI#getLoginUrl getLoginUrl}();
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
// Get the logged in user
|
|
57
|
-
const user = await cloudApi.getAuthenticatedUser();
|
|
57
|
+
const user = await {@link AthomCloudAPI cloudApi}.{@link AthomCloudAPI#getAuthenticatedUser getAuthenticatedUser}();
|
|
58
58
|
|
|
59
59
|
// Get the first Homey of the logged in user
|
|
60
|
-
const homey = await user.getFirstHomey();
|
|
60
|
+
const homey = await {@link AthomCloudAPI.User user}.{@link AthomCloudAPI.User#getFirstHomey getFirstHomey}();
|
|
61
61
|
|
|
62
62
|
// Create a session on this Homey
|
|
63
|
-
const homeyApi = await homey.authenticate();
|
|
63
|
+
const homeyApi = await {@link AthomCloudAPI.Homey homey}.{@link AthomCloudAPI.Homey#authenticate authenticate}();
|
|
64
64
|
|
|
65
|
-
//
|
|
66
|
-
const
|
|
67
|
-
|
|
65
|
+
// Get all Zones from ManagerZones
|
|
66
|
+
const zones = await {@link HomeyAPIV2 homeyApi}.{@link HomeyAPIV2.ManagerZones zones}.{@link HomeyAPIV2.ManagerZones#getZones getZones}();
|
|
67
|
+
|
|
68
|
+
// Get all Devices from ManagerDevices
|
|
69
|
+
const devices = await {@link HomeyAPIV2 homeyApi}.{@link HomeyAPIV2.ManagerDevices devices}.{@link HomeyAPIV2.ManagerDevices#getDevices getDevices}();
|
|
70
|
+
|
|
71
|
+
// Turn all devices on
|
|
72
|
+
for(const {@link HomeyAPIV2.ManagerDevices.Device device} of Object.values(devices)) {
|
|
68
73
|
// Turn device on
|
|
69
|
-
await device.
|
|
74
|
+
await {@link HomeyAPIV2.ManagerDevices.Device device}.{@link HomeyAPIV2.ManagerDevices.Device#setCapabilityValue setCapabilityValue}({
|
|
75
|
+
capabilityId: 'onoff',
|
|
76
|
+
value: true,
|
|
77
|
+
});
|
|
70
78
|
}`;
|
|
71
79
|
|
|
72
80
|
static JSDOC_PARAMS = `
|
|
@@ -198,6 +206,9 @@ for(const device of Object.values(devices)) {
|
|
|
198
206
|
* @returns {Promise<void>}
|
|
199
207
|
*/
|
|
200
208
|
async logout() {
|
|
209
|
+
// Delete Cached User
|
|
210
|
+
this.__user = null;
|
|
211
|
+
|
|
201
212
|
// Delete Token from Store
|
|
202
213
|
await this.__resetStore();
|
|
203
214
|
this.__token = null;
|
|
@@ -304,10 +315,11 @@ for(const device of Object.values(devices)) {
|
|
|
304
315
|
body.append('grant_type', 'client_credentials');
|
|
305
316
|
|
|
306
317
|
const response = await Util.fetch(`${this.baseUrl}/oauth2/token`, {
|
|
307
|
-
body,
|
|
318
|
+
body: body.toString(),
|
|
308
319
|
method: 'post',
|
|
309
320
|
headers: {
|
|
310
321
|
Authorization: `Basic ${Util.base64(`${this.__clientId}:${this.__clientSecret}`)}`,
|
|
322
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
311
323
|
},
|
|
312
324
|
});
|
|
313
325
|
|
|
@@ -332,6 +344,13 @@ for(const device of Object.values(devices)) {
|
|
|
332
344
|
return this.__token;
|
|
333
345
|
}
|
|
334
346
|
|
|
347
|
+
/**
|
|
348
|
+
* Authenticate with an authorization code.
|
|
349
|
+
* @param {Object} [opts]
|
|
350
|
+
* @param {String} opts.code - Default to `?code=...` when in a browser.
|
|
351
|
+
* @param {Boolean} [opts.removeCodeFromHistory=true] - Remove `?code=...` from the URL in the address bar.
|
|
352
|
+
* @returns {Promise<AthomCloudAPI.Token>}
|
|
353
|
+
*/
|
|
335
354
|
async authenticateWithAuthorizationCode({
|
|
336
355
|
code,
|
|
337
356
|
removeCodeFromHistory = true,
|
|
@@ -358,10 +377,11 @@ for(const device of Object.values(devices)) {
|
|
|
358
377
|
body.append('code', code);
|
|
359
378
|
|
|
360
379
|
const response = await Util.fetch(`${this.baseUrl}/oauth2/token`, {
|
|
361
|
-
body,
|
|
380
|
+
body: body.toString(),
|
|
362
381
|
method: 'post',
|
|
363
382
|
headers: {
|
|
364
383
|
Authorization: `Basic ${Util.base64(`${this.__clientId}:${this.__clientSecret}`)}`,
|
|
384
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
365
385
|
},
|
|
366
386
|
});
|
|
367
387
|
|
|
@@ -418,10 +438,11 @@ for(const device of Object.values(devices)) {
|
|
|
418
438
|
body.append('password', password);
|
|
419
439
|
|
|
420
440
|
const response = await Util.fetch(`${this.baseUrl}/oauth2/token`, {
|
|
421
|
-
body,
|
|
441
|
+
body: body.toString(),
|
|
422
442
|
method: 'post',
|
|
423
443
|
headers: {
|
|
424
444
|
Authorization: `Basic ${Util.base64(`${this.__clientId}:${this.__clientSecret}`)}`,
|
|
445
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
425
446
|
},
|
|
426
447
|
});
|
|
427
448
|
|
|
@@ -462,10 +483,11 @@ for(const device of Object.values(devices)) {
|
|
|
462
483
|
body.append('refresh_token', this.__token.refresh_token);
|
|
463
484
|
|
|
464
485
|
const response = await Util.fetch(`${this.baseUrl}/oauth2/token`, {
|
|
465
|
-
body,
|
|
486
|
+
body: body.toString(),
|
|
466
487
|
method: 'post',
|
|
467
488
|
headers: {
|
|
468
489
|
Authorization: `Basic ${Util.base64(`${this.__clientId}:${this.__clientSecret}`)}`,
|
|
490
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
469
491
|
},
|
|
470
492
|
});
|
|
471
493
|
|
|
@@ -500,6 +522,76 @@ for(const device of Object.values(devices)) {
|
|
|
500
522
|
return this.__refreshTokenPromise;
|
|
501
523
|
}
|
|
502
524
|
|
|
525
|
+
/**
|
|
526
|
+
* Update the currently authenticated user.
|
|
527
|
+
*
|
|
528
|
+
* @private
|
|
529
|
+
* @param {Object} [opts]
|
|
530
|
+
* @param {String} [opts.firstname]
|
|
531
|
+
* @param {String} [opts.lastname]
|
|
532
|
+
* @param {String} [opts.email]
|
|
533
|
+
* @returns {Promise<AthomCloudAPI.User>}
|
|
534
|
+
*/
|
|
535
|
+
async updateUserMe({
|
|
536
|
+
firstname,
|
|
537
|
+
lastname,
|
|
538
|
+
email,
|
|
539
|
+
}) {
|
|
540
|
+
const me = await this.getAuthenticatedUser();
|
|
541
|
+
return this.updateUser({
|
|
542
|
+
id: me._id,
|
|
543
|
+
user: {
|
|
544
|
+
firstname,
|
|
545
|
+
lastname,
|
|
546
|
+
email,
|
|
547
|
+
},
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Update the currently authenticated user's avatar.
|
|
553
|
+
*
|
|
554
|
+
* @private
|
|
555
|
+
* @param {Buffer} imageBuffer Buffer of the new avatar
|
|
556
|
+
* @param {"jpg"|"jpeg"|"png"|"gif"} imageType Type of the new avatar
|
|
557
|
+
* @returns {Promise<Object>}
|
|
558
|
+
*/
|
|
559
|
+
async updateUserMeAvatar(imageBuffer, imageType) {
|
|
560
|
+
if (!Buffer.isBuffer(imageBuffer)) {
|
|
561
|
+
throw new Error('Invalid Image. Expected Buffer.');
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (!imageType) {
|
|
565
|
+
throw new Error('Missing Image Type');
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (!['jpg', 'png', 'gif'].includes(imageType)) {
|
|
569
|
+
throw new Error(`Invalid Image Type: ${imageType}`);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (imageType === 'jpg') {
|
|
573
|
+
imageType = 'jpeg';
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const me = await this.getAuthenticatedUser();
|
|
577
|
+
const body = Buffer.concat([
|
|
578
|
+
Buffer.from(`--__X_HOMEY_BOUNDARY__\r\nContent-Disposition: form-data; name="avatar"; filename="avatar"\r\nContent-Type: image/${imageType}\r\n\r\n`),
|
|
579
|
+
Buffer.from(imageBuffer),
|
|
580
|
+
Buffer.from('\r\n--__X_HOMEY_BOUNDARY__--\r\n'),
|
|
581
|
+
]);
|
|
582
|
+
|
|
583
|
+
return this.call({
|
|
584
|
+
method: 'POST',
|
|
585
|
+
path: `/user/${me._id}/avatar`,
|
|
586
|
+
headers: {
|
|
587
|
+
'Content-Type': 'multipart/form-data; boundary="__X_HOMEY_BOUNDARY__"',
|
|
588
|
+
'Content-Length': body.length,
|
|
589
|
+
},
|
|
590
|
+
body,
|
|
591
|
+
bodyJSON: false,
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
|
|
503
595
|
}
|
|
504
596
|
|
|
505
597
|
module.exports = AthomCloudAPI;
|
|
@@ -24,6 +24,14 @@ class Device extends Item {
|
|
|
24
24
|
* @param {number|boolean|string} listener.value
|
|
25
25
|
* @returns {HomeyAPIV2.ManagerDevices.Device.DeviceCapability}
|
|
26
26
|
* @function HomeyAPIV2.ManagerDevices.Device#makeCapabilityInstance
|
|
27
|
+
* @example
|
|
28
|
+
*
|
|
29
|
+
* const onOffInstance = device.makeCapabilityInstance('onoff', value => {
|
|
30
|
+
* console.log('Device onoff changed to:', value);
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* // Turn on
|
|
34
|
+
* onOffInstance.setValue(true).catch(console.error);
|
|
27
35
|
*/
|
|
28
36
|
makeCapabilityInstance(capabilityId, listener) {
|
|
29
37
|
this.connect().catch(err => {
|
package/lib/Util.js
CHANGED
|
@@ -22,7 +22,6 @@ class Util {
|
|
|
22
22
|
return fetch(...args);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
// If in a browser
|
|
26
25
|
if (this.isBrowser()) {
|
|
27
26
|
return window.fetch(...args);
|
|
28
27
|
}
|
|
@@ -138,19 +137,58 @@ class Util {
|
|
|
138
137
|
}
|
|
139
138
|
|
|
140
139
|
/**
|
|
140
|
+
* This method encodes a string into a base64 string.
|
|
141
|
+
* It's provided as Util because Node.js uses `Buffer`,
|
|
142
|
+
* browsers use `btoa` and React Native doesn't provide anything.
|
|
141
143
|
* @param {string} input - Input
|
|
142
144
|
* @returns {string} - Base64 encoded output
|
|
143
145
|
*/
|
|
144
|
-
static base64(
|
|
145
|
-
|
|
146
|
-
|
|
146
|
+
static base64(s) {
|
|
147
|
+
function btoaLookup(index) {
|
|
148
|
+
if (index >= 0 && index < 64) {
|
|
149
|
+
const keystr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
150
|
+
return keystr[index];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Throw INVALID_CHARACTER_ERR exception here -- won't be hit in the tests.
|
|
154
|
+
return undefined;
|
|
147
155
|
}
|
|
148
156
|
|
|
149
|
-
if (typeof
|
|
150
|
-
|
|
157
|
+
if (typeof s !== 'string') {
|
|
158
|
+
throw new Error('Invalid Input');
|
|
151
159
|
}
|
|
152
160
|
|
|
153
|
-
|
|
161
|
+
let i;
|
|
162
|
+
|
|
163
|
+
// "The btoa() method must throw an "InvalidCharacterError" DOMException if
|
|
164
|
+
// data contains any character whose code point is greater than U+00FF."
|
|
165
|
+
for (i = 0; i < s.length; i++) {
|
|
166
|
+
if (s.charCodeAt(i) > 255) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
let out = '';
|
|
171
|
+
for (i = 0; i < s.length; i += 3) {
|
|
172
|
+
const groupsOfSix = [undefined, undefined, undefined, undefined];
|
|
173
|
+
groupsOfSix[0] = s.charCodeAt(i) >> 2;
|
|
174
|
+
groupsOfSix[1] = (s.charCodeAt(i) & 0x03) << 4;
|
|
175
|
+
if (s.length > i + 1) {
|
|
176
|
+
groupsOfSix[1] |= s.charCodeAt(i + 1) >> 4;
|
|
177
|
+
groupsOfSix[2] = (s.charCodeAt(i + 1) & 0x0f) << 2;
|
|
178
|
+
}
|
|
179
|
+
if (s.length > i + 2) {
|
|
180
|
+
groupsOfSix[2] |= s.charCodeAt(i + 2) >> 6;
|
|
181
|
+
groupsOfSix[3] = s.charCodeAt(i + 2) & 0x3f;
|
|
182
|
+
}
|
|
183
|
+
for (let j = 0; j < groupsOfSix.length; j++) {
|
|
184
|
+
if (typeof groupsOfSix[j] === 'undefined') {
|
|
185
|
+
out += '=';
|
|
186
|
+
} else {
|
|
187
|
+
out += btoaLookup(groupsOfSix[j]);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return out;
|
|
154
192
|
}
|
|
155
193
|
|
|
156
194
|
/**
|
package/package.json
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homey-api",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.2",
|
|
4
4
|
"description": "Homey API",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"/lib",
|
|
8
|
+
"/assets",
|
|
9
|
+
"/index.js",
|
|
10
|
+
"/index.browser.js"
|
|
11
|
+
],
|
|
6
12
|
"types": "assets/types/homey-api.d.ts",
|
|
7
13
|
"scripts": {
|
|
8
14
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
@@ -44,6 +50,7 @@
|
|
|
44
50
|
"socket.io-client": "^1.7.4"
|
|
45
51
|
},
|
|
46
52
|
"devDependencies": {
|
|
53
|
+
"@athombv/jsdoc-template": "^1.6.1",
|
|
47
54
|
"@babel/core": "^7.16.0",
|
|
48
55
|
"@babel/plugin-proposal-class-properties": "^7.16.0",
|
|
49
56
|
"@babel/preset-env": "^7.16.0",
|
|
@@ -53,7 +60,6 @@
|
|
|
53
60
|
"eslint": "^7.32.0",
|
|
54
61
|
"eslint-config-athom": "^2.1.1",
|
|
55
62
|
"fs-extra": "^10.0.0",
|
|
56
|
-
"homey-jsdoc-template": "github:athombv/homey-jsdoc-template#1.4",
|
|
57
63
|
"http-server": "^0.12.3",
|
|
58
64
|
"jsdoc": "^3.6.7",
|
|
59
65
|
"jsdoc-to-markdown": "^7.1.0",
|
|
@@ -72,4 +78,4 @@
|
|
|
72
78
|
"not IE 11",
|
|
73
79
|
"not IE_Mob 11"
|
|
74
80
|
]
|
|
75
|
-
}
|
|
81
|
+
}
|