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.
@@ -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 {
@@ -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 api = new AthomCloudAPI({
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 api.isLoggedIn();
46
+ const loggedIn = await {@link AthomCloudAPI cloudApi}.{@link AthomCloudAPI#isLoggedIn isLoggedIn}();
47
47
  if (!loggedIn) {
48
- if (api.hasAuthorizationCode()) {
49
- const token = await api.authenticateWithAuthorizationCode();
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 = api.getLoginUrl();
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
- // Loop all devices
66
- const devices = await homeyApi.devices.getDevices();
67
- for(const device of Object.values(devices)) {
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.setCapabilityValue({ capabilityId: 'onoff', value: true });
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(str) {
145
- if (typeof btoa === 'function') {
146
- return btoa(str);
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 Buffer !== 'undefined') {
150
- return Buffer.from(str).toString('base64');
157
+ if (typeof s !== 'string') {
158
+ throw new Error('Invalid Input');
151
159
  }
152
160
 
153
- throw new Error('No Base64 Methods Available');
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.6.0",
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
+ }