homebridge-nuheat2 1.2.8 → 1.2.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/CHANGELOG.md +16 -0
- package/lib/NuHeatAPI.js +31 -4
- package/package.json +1 -4
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ All notable changes to this project should be documented in this file
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.2.10] - 2026-04-21
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Add non-sensitive OAuth debug logging for configured client ID, requested scopes, consent handling, and refresh-token rotation during local API credential validation
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Request the full issued Nuheat OAuth scope set: `openapi openid profile offline_access`
|
|
16
|
+
|
|
17
|
+
## [1.2.9] - 2026-04-15
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Remove the published `homebridge` peer dependency so verification installs no longer pull in `homebridge` and `hap-nodejs`
|
|
22
|
+
|
|
7
23
|
## [1.2.8] - 2026-04-15
|
|
8
24
|
|
|
9
25
|
### Fixed
|
package/lib/NuHeatAPI.js
CHANGED
|
@@ -8,6 +8,7 @@ const isRedirect = fetchModule.isRedirect;
|
|
|
8
8
|
const { parse } = htmlParser;
|
|
9
9
|
const settings_1 = require("./settings");
|
|
10
10
|
const NuHeatModels_1 = require("./NuHeatModels");
|
|
11
|
+
const OAUTH_SCOPES = ["openapi", "openid", "profile", "offline_access"];
|
|
11
12
|
class NuHeatAPI {
|
|
12
13
|
email;
|
|
13
14
|
password;
|
|
@@ -56,6 +57,16 @@ class NuHeatAPI {
|
|
|
56
57
|
if (this.usingFallbackCredentials) {
|
|
57
58
|
this.log.warn("NuHeatAPI: Using built-in OAuth client credentials. Request your own Nuheat API client for long-term reliability.");
|
|
58
59
|
}
|
|
60
|
+
else {
|
|
61
|
+
this.log.info("NuHeatAPI: Using configured OAuth client ID " + this.oauthClientId + ".");
|
|
62
|
+
}
|
|
63
|
+
this.log.debug("NuHeatAPI: OAuth redirect URI " +
|
|
64
|
+
this.oauthRedirectUri +
|
|
65
|
+
". Requested scopes: " +
|
|
66
|
+
this.getRequestedScope());
|
|
67
|
+
}
|
|
68
|
+
getRequestedScope() {
|
|
69
|
+
return OAUTH_SCOPES.join(" ");
|
|
59
70
|
}
|
|
60
71
|
async setAwayMode(groupId, awayMode) {
|
|
61
72
|
const callURL = "https://api.mynuheat.com/api/v1/Group";
|
|
@@ -236,7 +247,9 @@ class NuHeatAPI {
|
|
|
236
247
|
authEndpoint.searchParams.set("response_type", "code");
|
|
237
248
|
authEndpoint.searchParams.set("client_id", this.oauthClientId);
|
|
238
249
|
authEndpoint.searchParams.set("redirect_uri", this.oauthRedirectUri);
|
|
239
|
-
authEndpoint.searchParams.set("scope",
|
|
250
|
+
authEndpoint.searchParams.set("scope", this.getRequestedScope());
|
|
251
|
+
this.log.debug("NuHeatAPI: Requesting OAuth authorization page with scopes: " +
|
|
252
|
+
this.getRequestedScope());
|
|
240
253
|
const response = await this.fetch(authEndpoint.toString(), {
|
|
241
254
|
redirect: "follow",
|
|
242
255
|
});
|
|
@@ -322,9 +335,11 @@ class NuHeatAPI {
|
|
|
322
335
|
RememberConsent: "true",
|
|
323
336
|
__RequestVerificationToken: requestVerificationToken,
|
|
324
337
|
});
|
|
338
|
+
this.log.debug("NuHeatAPI: OAuth consent required. Confirming scopes: " +
|
|
339
|
+
this.getRequestedScope());
|
|
325
340
|
const response = await this.fetch(settings_1.NUHEAT_API_CONSENT_URI, {
|
|
326
341
|
body: loginBody.toString() +
|
|
327
|
-
"&ScopesConsented=
|
|
342
|
+
OAUTH_SCOPES.map((scope) => "&ScopesConsented=" + scope).join(""),
|
|
328
343
|
headers: {
|
|
329
344
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
330
345
|
Cookie: cookie + "; " + sessionCookie,
|
|
@@ -401,7 +416,12 @@ class NuHeatAPI {
|
|
|
401
416
|
return null;
|
|
402
417
|
}
|
|
403
418
|
this.tokenScope = redirectUrl.searchParams.get("scope") ?? "";
|
|
404
|
-
|
|
419
|
+
const token = (await response.json());
|
|
420
|
+
this.log.debug("NuHeatAPI: OAuth access token received. Granted scopes: " +
|
|
421
|
+
(token.scope || this.tokenScope || "unknown") +
|
|
422
|
+
". Refresh token present: " +
|
|
423
|
+
String(!!token.refresh_token));
|
|
424
|
+
return token;
|
|
405
425
|
}
|
|
406
426
|
return null;
|
|
407
427
|
}
|
|
@@ -431,12 +451,19 @@ class NuHeatAPI {
|
|
|
431
451
|
this.refreshToken = token.refresh_token;
|
|
432
452
|
this.tokenScope = token.scope ?? this.tokenScope;
|
|
433
453
|
this.tokenType = token.token_type;
|
|
454
|
+
const refreshTokenRotated = token.refresh_token !== this.refreshToken;
|
|
434
455
|
this.refreshInterval -= 420;
|
|
435
456
|
if (this.refreshInterval < 300) {
|
|
436
457
|
this.refreshInterval = 300;
|
|
437
458
|
}
|
|
438
459
|
this.headers.set("Authorization", token.token_type + " " + token.access_token);
|
|
439
|
-
this.log.debug("NuHeatAPI: Successfully refreshed the NuHeat API access token."
|
|
460
|
+
this.log.debug("NuHeatAPI: Successfully refreshed the NuHeat API access token. Scope: " +
|
|
461
|
+
this.tokenScope +
|
|
462
|
+
". Refresh token rotated: " +
|
|
463
|
+
String(refreshTokenRotated) +
|
|
464
|
+
". Expires in: " +
|
|
465
|
+
token.expires_in +
|
|
466
|
+
" seconds.");
|
|
440
467
|
return true;
|
|
441
468
|
}
|
|
442
469
|
async returnAccessToken() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-nuheat2",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.10",
|
|
4
4
|
"description": "Homebridge Platform for NuHeat Signature Thermostats",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -42,9 +42,6 @@
|
|
|
42
42
|
"ws": "^7.5.10",
|
|
43
43
|
"tough-cookie": "^4.1.3"
|
|
44
44
|
},
|
|
45
|
-
"peerDependencies": {
|
|
46
|
-
"homebridge": "^1.8.0 || ^2.0.0-beta.0"
|
|
47
|
-
},
|
|
48
45
|
"engines": {
|
|
49
46
|
"node": "^18.20.4 || ^20.18.0 || ^22.0.0 || ^24.0.0",
|
|
50
47
|
"homebridge": "^1.8.0 || ^2.0.0-beta.0"
|