homey-api 3.4.19 → 3.4.21
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/specifications/HomeyAPIV3Cloud.json +27 -0
- package/assets/types/homey-api.d.ts +6 -0
- package/assets/types/homey-api.private.d.ts +6 -0
- package/lib/AthomCloudAPI.js +2 -2
- package/lib/HomeyAPI/HomeyAPIV3/Manager.js +4 -1
- package/lib/HomeyAPI/HomeyAPIV3.js +81 -27
- package/package.json +7 -4
|
@@ -3775,6 +3775,33 @@
|
|
|
3775
3775
|
"idCamelCase": "zwave",
|
|
3776
3776
|
"items": {},
|
|
3777
3777
|
"operations": {
|
|
3778
|
+
"getOptionRegionOverride": {
|
|
3779
|
+
"method": "GET",
|
|
3780
|
+
"path": "/option/regionOverride",
|
|
3781
|
+
"scopes": [
|
|
3782
|
+
"homey.system.readonly"
|
|
3783
|
+
]
|
|
3784
|
+
},
|
|
3785
|
+
"setOptionRegionOverride": {
|
|
3786
|
+
"method": "PUT",
|
|
3787
|
+
"path": "/option/regionOverride",
|
|
3788
|
+
"scopes": [
|
|
3789
|
+
"homey.system"
|
|
3790
|
+
],
|
|
3791
|
+
"parameters": {
|
|
3792
|
+
"value": {
|
|
3793
|
+
"in": "body",
|
|
3794
|
+
"required": true
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
},
|
|
3798
|
+
"unsetOptionRegionOverride": {
|
|
3799
|
+
"method": "DELETE",
|
|
3800
|
+
"path": "/option/regionOverride",
|
|
3801
|
+
"scopes": [
|
|
3802
|
+
"homey.system"
|
|
3803
|
+
]
|
|
3804
|
+
},
|
|
3778
3805
|
"getState": {
|
|
3779
3806
|
"method": "get",
|
|
3780
3807
|
"path": "/state",
|
|
@@ -2634,6 +2634,12 @@ export namespace HomeyAPIV3Cloud {
|
|
|
2634
2634
|
}
|
|
2635
2635
|
|
|
2636
2636
|
export class ManagerZwave extends HomeyAPIV3.ManagerZwave {
|
|
2637
|
+
getOptionRegionOverride(): Promise<any>;
|
|
2638
|
+
|
|
2639
|
+
setOptionRegionOverride(opts: { value: any }): Promise<any>;
|
|
2640
|
+
|
|
2641
|
+
unsetOptionRegionOverride(): Promise<any>;
|
|
2642
|
+
|
|
2637
2643
|
getState(): Promise<any>;
|
|
2638
2644
|
|
|
2639
2645
|
runCommand(opts: {
|
|
@@ -8640,6 +8640,12 @@ export namespace HomeyAPIV3Cloud {
|
|
|
8640
8640
|
}
|
|
8641
8641
|
|
|
8642
8642
|
export class ManagerZwave extends HomeyAPIV3.ManagerZwave {
|
|
8643
|
+
getOptionRegionOverride(): Promise<any>;
|
|
8644
|
+
|
|
8645
|
+
setOptionRegionOverride(opts: { value: any }): Promise<any>;
|
|
8646
|
+
|
|
8647
|
+
unsetOptionRegionOverride(): Promise<any>;
|
|
8648
|
+
|
|
8643
8649
|
getState(): Promise<any>;
|
|
8644
8650
|
|
|
8645
8651
|
runCommand(opts: {
|
package/lib/AthomCloudAPI.js
CHANGED
|
@@ -324,7 +324,7 @@ for(const {@link HomeyAPIV2.ManagerDevices.Device device} of Object.values(devic
|
|
|
324
324
|
...props
|
|
325
325
|
}) {
|
|
326
326
|
if (statusCode === 401 && body.error === 'invalid_token') {
|
|
327
|
-
if (this.__autoRefreshTokens && !request.context.
|
|
327
|
+
if (this.__autoRefreshTokens && !request.context.isRetryAfterRefresh) {
|
|
328
328
|
this.__debug('Refreshing token...');
|
|
329
329
|
await this.authenticateWithRefreshToken();
|
|
330
330
|
|
|
@@ -333,7 +333,7 @@ for(const {@link HomeyAPIV2.ManagerDevices.Device device} of Object.values(devic
|
|
|
333
333
|
...request,
|
|
334
334
|
context: {
|
|
335
335
|
...request.context,
|
|
336
|
-
|
|
336
|
+
isRetryAfterRefresh: true,
|
|
337
337
|
},
|
|
338
338
|
});
|
|
339
339
|
}
|
|
@@ -107,6 +107,7 @@ class Manager extends EventEmitter {
|
|
|
107
107
|
$body = {},
|
|
108
108
|
$query = {},
|
|
109
109
|
$headers = {},
|
|
110
|
+
shouldRetry,
|
|
110
111
|
...args
|
|
111
112
|
} = {}) => {
|
|
112
113
|
let { path } = operation;
|
|
@@ -215,6 +216,7 @@ class Manager extends EventEmitter {
|
|
|
215
216
|
body,
|
|
216
217
|
query,
|
|
217
218
|
headers,
|
|
219
|
+
shouldRetry,
|
|
218
220
|
...args,
|
|
219
221
|
});
|
|
220
222
|
|
|
@@ -244,7 +246,7 @@ class Manager extends EventEmitter {
|
|
|
244
246
|
}
|
|
245
247
|
}
|
|
246
248
|
|
|
247
|
-
async __request({ $cache, $updateCache, $timeout, $socket, operationId, operation, path, body, headers, ...args }) {
|
|
249
|
+
async __request({ $cache, $updateCache, $timeout, $socket, operationId, operation, path, body, headers, shouldRetry, ...args }) {
|
|
248
250
|
let result;
|
|
249
251
|
const benchmark = Util.benchmark();
|
|
250
252
|
|
|
@@ -323,6 +325,7 @@ class Manager extends EventEmitter {
|
|
|
323
325
|
body,
|
|
324
326
|
path: `/api/manager/${this.constructor.ID}${path}`,
|
|
325
327
|
method: operation.method,
|
|
328
|
+
shouldRetry,
|
|
326
329
|
});
|
|
327
330
|
}
|
|
328
331
|
|
|
@@ -53,6 +53,8 @@ class HomeyAPIV3 extends HomeyAPI {
|
|
|
53
53
|
}) {
|
|
54
54
|
super({ properties, ...props });
|
|
55
55
|
|
|
56
|
+
this.__refreshMap = {};
|
|
57
|
+
|
|
56
58
|
Object.defineProperty(this, '__baseUrl', {
|
|
57
59
|
value: null,
|
|
58
60
|
enumerable: false,
|
|
@@ -412,7 +414,17 @@ class HomeyAPIV3 extends HomeyAPI {
|
|
|
412
414
|
return promise;
|
|
413
415
|
}
|
|
414
416
|
|
|
415
|
-
async call({
|
|
417
|
+
async call({
|
|
418
|
+
$timeout = 10000,
|
|
419
|
+
method,
|
|
420
|
+
headers,
|
|
421
|
+
path,
|
|
422
|
+
body,
|
|
423
|
+
json = true,
|
|
424
|
+
isRetryAfterRefresh = false,
|
|
425
|
+
shouldRetry = true,
|
|
426
|
+
}) {
|
|
427
|
+
const token = this.__token;
|
|
416
428
|
const baseUrl = await this.baseUrl;
|
|
417
429
|
|
|
418
430
|
method = String(method).toUpperCase();
|
|
@@ -422,11 +434,12 @@ class HomeyAPIV3 extends HomeyAPI {
|
|
|
422
434
|
'X-Homey-ID': this.id,
|
|
423
435
|
};
|
|
424
436
|
|
|
425
|
-
if (
|
|
426
|
-
headers['Authorization'] = `Bearer ${
|
|
437
|
+
if (token) {
|
|
438
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
427
439
|
}
|
|
428
440
|
|
|
429
441
|
const originalBody = body;
|
|
442
|
+
|
|
430
443
|
if (['PUT', 'POST'].includes(method)) {
|
|
431
444
|
if (body && json === true) {
|
|
432
445
|
headers['Content-Type'] = 'application/json';
|
|
@@ -470,26 +483,23 @@ class HomeyAPIV3 extends HomeyAPI {
|
|
|
470
483
|
}
|
|
471
484
|
|
|
472
485
|
if (!res.ok) {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
await this.logout();
|
|
478
|
-
}
|
|
486
|
+
// If Session Expired, clear the stored token
|
|
487
|
+
if (resStatusCode === 401 && shouldRetry === true && token != null) {
|
|
488
|
+
this.__debug('Session expired');
|
|
489
|
+
await this.refreshForToken(token, isRetryAfterRefresh);
|
|
479
490
|
|
|
480
|
-
|
|
481
|
-
if (resStatusCode === 401 && !retryAfterRefresh) {
|
|
482
|
-
this.__debug('Session expired, refreshing...');
|
|
483
|
-
await this.login();
|
|
491
|
+
if (!isRetryAfterRefresh) {
|
|
484
492
|
return this.call({
|
|
485
493
|
method,
|
|
486
494
|
headers,
|
|
487
495
|
path,
|
|
488
496
|
body: originalBody,
|
|
489
|
-
|
|
497
|
+
isRetryAfterRefresh: true,
|
|
490
498
|
});
|
|
491
499
|
}
|
|
500
|
+
}
|
|
492
501
|
|
|
502
|
+
if (resBodyJson) {
|
|
493
503
|
throw new HomeyAPIError(
|
|
494
504
|
{
|
|
495
505
|
error: resBodyJson.error,
|
|
@@ -546,11 +556,13 @@ class HomeyAPIV3 extends HomeyAPI {
|
|
|
546
556
|
const token = await this.users.login({
|
|
547
557
|
$socket: false,
|
|
548
558
|
token: jwtToken,
|
|
559
|
+
shouldRetry: false,
|
|
549
560
|
});
|
|
550
561
|
this.__token = token;
|
|
551
562
|
|
|
552
563
|
const session = await this.sessions.getSessionMe({
|
|
553
564
|
$socket: false,
|
|
565
|
+
shouldRetry: false,
|
|
554
566
|
});
|
|
555
567
|
this.__session = session;
|
|
556
568
|
|
|
@@ -564,14 +576,14 @@ class HomeyAPIV3 extends HomeyAPI {
|
|
|
564
576
|
});
|
|
565
577
|
|
|
566
578
|
this.__loginPromise
|
|
567
|
-
.then(() => {
|
|
579
|
+
.then(() => {
|
|
580
|
+
this.__loginPromise = null;
|
|
581
|
+
})
|
|
568
582
|
.catch(err => {
|
|
569
583
|
this.__debug('Error Logging In:', err);
|
|
584
|
+
this.__loginPromise = null;
|
|
570
585
|
this.__token = null;
|
|
571
586
|
this.__session = null;
|
|
572
|
-
})
|
|
573
|
-
.finally(() => {
|
|
574
|
-
this.__loginPromise = null;
|
|
575
587
|
});
|
|
576
588
|
}
|
|
577
589
|
|
|
@@ -579,11 +591,49 @@ class HomeyAPIV3 extends HomeyAPI {
|
|
|
579
591
|
}
|
|
580
592
|
|
|
581
593
|
async logout() {
|
|
594
|
+
this.__token = null;
|
|
595
|
+
this.__session = null;
|
|
596
|
+
|
|
582
597
|
await this.__setStore({
|
|
583
598
|
token: null,
|
|
599
|
+
session: null,
|
|
584
600
|
});
|
|
601
|
+
}
|
|
585
602
|
|
|
586
|
-
|
|
603
|
+
async refreshForToken(token, isRetryAfterRefresh = false) {
|
|
604
|
+
if (this.__token === token && this.__token !== null && this.__refreshMap[token] == null) {
|
|
605
|
+
this.__refreshMap[token] = Promise.resolve().then(async () => {
|
|
606
|
+
await this.__setStore({
|
|
607
|
+
token: null,
|
|
608
|
+
session: null,
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
if (!isRetryAfterRefresh) {
|
|
612
|
+
this.__debug('Refreshing token...');
|
|
613
|
+
// If the login fails, the tokens are cleared on the instance. We don't call logout and clear
|
|
614
|
+
// the token on the instance because that could cause a handshake client to fire with a null
|
|
615
|
+
// token. Handshake client also needs to attempt to refresh the token, and if the refresh was
|
|
616
|
+
// already started, it should reuse this promise. That also goes the other way around.
|
|
617
|
+
await this.login();
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
this.__refreshMap[token]
|
|
622
|
+
.then(() => {})
|
|
623
|
+
.catch(err => {
|
|
624
|
+
this.__debug('Error Refreshing Token:', err);
|
|
625
|
+
})
|
|
626
|
+
.finally(() => {
|
|
627
|
+
// Delete after 30 seconds some requests might still be pending an they should be able
|
|
628
|
+
// to receive a rejected promise for this token.
|
|
629
|
+
this.__refreshMap[token+'timeout'] = setTimeout(() => {
|
|
630
|
+
delete this.__refreshMap[token];
|
|
631
|
+
delete this.__refreshMap[token+'timeout'];
|
|
632
|
+
}, 30 * 1000);
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
await this.__refreshMap[token];
|
|
587
637
|
}
|
|
588
638
|
|
|
589
639
|
/**
|
|
@@ -879,16 +929,16 @@ class HomeyAPIV3 extends HomeyAPI {
|
|
|
879
929
|
});
|
|
880
930
|
};
|
|
881
931
|
|
|
882
|
-
const handshakeClient = async
|
|
932
|
+
const handshakeClient = async token => {
|
|
883
933
|
return new Promise((resolve, reject) => {
|
|
884
934
|
this.__socket.emit(
|
|
885
935
|
'handshakeClient',
|
|
886
936
|
{
|
|
887
|
-
token
|
|
937
|
+
token,
|
|
888
938
|
homeyId: this.id,
|
|
889
939
|
},
|
|
890
940
|
(err, result) => {
|
|
891
|
-
if (err != null) {
|
|
941
|
+
if (err != null) {
|
|
892
942
|
if (typeof err === 'object') {
|
|
893
943
|
err = new HomeyAPIError(
|
|
894
944
|
{
|
|
@@ -916,18 +966,22 @@ class HomeyAPIV3 extends HomeyAPI {
|
|
|
916
966
|
});
|
|
917
967
|
};
|
|
918
968
|
|
|
969
|
+
const token = this.__token;
|
|
970
|
+
|
|
919
971
|
try {
|
|
920
|
-
const result = await Util.timeout(
|
|
972
|
+
const result = await Util.timeout(
|
|
973
|
+
handshakeClient(token),
|
|
974
|
+
5000,
|
|
975
|
+
`Failed to handshake client (Timeout after 5000ms).`
|
|
976
|
+
);
|
|
921
977
|
|
|
922
978
|
return onResult(result);
|
|
923
979
|
} catch (err) {
|
|
924
980
|
if (err.statusCode === 401 || err.code === 401) {
|
|
925
981
|
this.__debug('Token expired, refreshing...');
|
|
926
|
-
await this.
|
|
927
|
-
await this.login();
|
|
928
|
-
|
|
982
|
+
await this.refreshForToken(token, false);
|
|
929
983
|
const result = await Util.timeout(
|
|
930
|
-
handshakeClient(),
|
|
984
|
+
handshakeClient(this.__token),
|
|
931
985
|
5000,
|
|
932
986
|
`Failed to handshake client (Timeout after 5000ms).`
|
|
933
987
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homey-api",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.21",
|
|
4
4
|
"description": "Homey API",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
],
|
|
13
13
|
"types": "assets/types/homey-api.d.ts",
|
|
14
14
|
"scripts": {
|
|
15
|
-
"test": "jest",
|
|
15
|
+
"test": "jest --verbose",
|
|
16
16
|
"lint": "eslint .",
|
|
17
17
|
"serve": "concurrently \"serve jsdoc/\" \"npm run jsdoc:watch\"",
|
|
18
18
|
"build": "npm run build:specs; npm run build:jsdoc; npm run build:types; npm run build:webpack;",
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"@babel/core": "^7.16.0",
|
|
57
57
|
"@babel/plugin-proposal-class-properties": "^7.16.0",
|
|
58
58
|
"@babel/preset-env": "^7.16.0",
|
|
59
|
+
"@types/jest": "^29.5.12",
|
|
59
60
|
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
|
60
61
|
"@typescript-eslint/parser": "^6.1.0",
|
|
61
62
|
"babel-loader": "^8.2.3",
|
|
@@ -64,17 +65,19 @@
|
|
|
64
65
|
"eslint": "^7.32.0",
|
|
65
66
|
"eslint-config-athom": "^2.1.1",
|
|
66
67
|
"eslint-plugin-jest": "^27.4.0",
|
|
68
|
+
"express": "^4.18.2",
|
|
67
69
|
"fs-extra": "^10.0.0",
|
|
68
70
|
"homey-api": "^3.0.8",
|
|
69
71
|
"http-server": "^0.12.3",
|
|
70
|
-
"jest": "^
|
|
72
|
+
"jest": "^30.0.0-alpha.2",
|
|
71
73
|
"jsdoc": "^4.0.2",
|
|
72
74
|
"jsdoc-to-markdown": "^8.0.0",
|
|
73
75
|
"jsdoc-ts-utils": "^4.0.0",
|
|
74
76
|
"jsdoc-tsimport-plugin": "^1.0.5",
|
|
75
77
|
"keypather": "^3.1.0",
|
|
76
|
-
"prettier": "^3.
|
|
78
|
+
"prettier": "^3.2.5",
|
|
77
79
|
"serve": "^14.0.1",
|
|
80
|
+
"socket.io": "^4.7.4",
|
|
78
81
|
"typescript": "^5.1.6",
|
|
79
82
|
"watch": "^1.0.2",
|
|
80
83
|
"webpack": "^5.64.0",
|