homebridge-nuheat2 1.2.16 → 1.2.18
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 +15 -0
- package/README.md +3 -9
- package/config.schema.json +0 -29
- package/homebridge-ui/public/index.html +0 -56
- package/homebridge-ui/public/index.js +36 -121
- package/homebridge-ui/public/styles.css +9 -1
- package/lib/NuHeatAPI.js +5 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@ All notable changes to this project should be documented in this file
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.2.18] - 2026-04-29
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Remove user-facing Advanced OAuth settings from the Homebridge schema, custom UI, and README now that Nuheat provides a built-in PKCE public client for this plugin
|
|
12
|
+
- Remove the editable Platform Name field from the custom admin UI while keeping the config key supported for backward compatibility
|
|
13
|
+
- Improve Accessories allow-list rendering so saved thermostat and group rows are normalized and displayed as clearly editable rows
|
|
14
|
+
|
|
15
|
+
## [1.2.17] - 2026-04-29
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Treat the built-in Nuheat public client ID as PKCE-only even when it is explicitly saved in Advanced OAuth settings
|
|
20
|
+
- Ignore and clear stale saved client secrets when users return to the built-in PKCE public client
|
|
21
|
+
|
|
7
22
|
## [1.2.16] - 2026-04-29
|
|
8
23
|
|
|
9
24
|
### Changed
|
package/README.md
CHANGED
|
@@ -18,7 +18,6 @@ This project builds on the original [`senorshaun/homebridge-nuheat`](https://git
|
|
|
18
18
|
- Uses the official Nuheat PKCE public client by default, with no distributable client secret
|
|
19
19
|
- Includes compatibility improvements for Homebridge 1.8+ and 2.0 betas
|
|
20
20
|
- Can optionally expose a schedule switch for each thermostat
|
|
21
|
-
- Allows advanced OAuth overrides for long-term API stability
|
|
22
21
|
|
|
23
22
|
## Compatibility
|
|
24
23
|
|
|
@@ -45,9 +44,9 @@ The published package name for this maintained fork is `homebridge-nuheat2`. The
|
|
|
45
44
|
|
|
46
45
|
## Configuration
|
|
47
46
|
|
|
48
|
-
Most users should configure the plugin through the custom Homebridge admin UI. It is organized into Account, Accessories, Behavior,
|
|
47
|
+
Most users should configure the plugin through the custom Homebridge admin UI. It is organized into Account, Accessories, Behavior, and Diagnostics panels and writes the same config keys shown below.
|
|
49
48
|
|
|
50
|
-
Sensitive values are handled deliberately: saved passwords
|
|
49
|
+
Sensitive values are handled deliberately: saved passwords are not redisplayed in the UI. Leave the password field blank to keep the saved value, or enter a new value to replace it.
|
|
51
50
|
|
|
52
51
|
The Diagnostics panel summarizes the saved configuration and exposure strategy before restart. It does not make live Nuheat API calls.
|
|
53
52
|
|
|
@@ -90,9 +89,6 @@ The equivalent JSON looks like this:
|
|
|
90
89
|
- `refresh`: Poll interval in seconds, default `60`. Values lower than `30` are raised to `30` to reduce API traffic
|
|
91
90
|
- `enableNotifications`: Enables Nuheat SignalR notifications for faster updates. Defaults to `true`; set to `false` only while troubleshooting
|
|
92
91
|
- `debug`: Enables verbose Nuheat API, notification, and accessory logging. Defaults to `false`
|
|
93
|
-
- `clientId`: Optional advanced override for the Nuheat OAuth client ID. Leave blank to use the built-in PKCE public client ID, `homebridge-nuheat2_260421`
|
|
94
|
-
- `clientSecret`: Optional legacy OAuth client secret override for confidential-client credentials. Leave blank for the PKCE public-client flow. Do not publish or share this value
|
|
95
|
-
- `redirectUri`: Optional advanced override for the Nuheat OAuth redirect URI, default `http://localhost`
|
|
96
92
|
|
|
97
93
|
### Hold Length Behavior
|
|
98
94
|
|
|
@@ -115,9 +111,7 @@ Nuheat's public OpenAPI documentation indicates that third-party developers shou
|
|
|
115
111
|
- [Nuheat OpenAPI docs](https://api.mynuheat.com/)
|
|
116
112
|
- [Nuheat API access request page](https://www.nuheat.com/openapi)
|
|
117
113
|
|
|
118
|
-
This fork uses Nuheat's PKCE-based public client for normal authentication. The built-in public `clientId` is `homebridge-nuheat2_260421`; there is no distributable client secret.
|
|
119
|
-
|
|
120
|
-
Legacy confidential-client credentials can still be supplied with `clientId` and `clientSecret` for testing alternate Nuheat-issued clients. Keep any `clientSecret` out of GitHub, npm, screenshots, and shared logs.
|
|
114
|
+
This fork uses Nuheat's PKCE-based public client for normal authentication. The built-in public `clientId` is `homebridge-nuheat2_260421`; there is no distributable client secret and no API key setup is required.
|
|
121
115
|
|
|
122
116
|
## What's New In This Fork
|
|
123
117
|
|
package/config.schema.json
CHANGED
|
@@ -121,23 +121,6 @@
|
|
|
121
121
|
"type": "boolean",
|
|
122
122
|
"default": false,
|
|
123
123
|
"description": "Write detailed Nuheat API and accessory activity to the Homebridge log."
|
|
124
|
-
},
|
|
125
|
-
"clientId": {
|
|
126
|
-
"title": "Nuheat Client ID",
|
|
127
|
-
"type": "string",
|
|
128
|
-
"description": "Advanced OAuth override. Leave blank to use the built-in PKCE public client ID."
|
|
129
|
-
},
|
|
130
|
-
"clientSecret": {
|
|
131
|
-
"title": "Nuheat Client Secret (Legacy)",
|
|
132
|
-
"type": "string",
|
|
133
|
-
"format": "password",
|
|
134
|
-
"description": "Legacy OAuth override for confidential-client credentials. Leave blank for the PKCE public-client flow. Do not publish or share this value."
|
|
135
|
-
},
|
|
136
|
-
"redirectUri": {
|
|
137
|
-
"title": "Nuheat Redirect URI",
|
|
138
|
-
"type": "string",
|
|
139
|
-
"default": "http://localhost",
|
|
140
|
-
"description": "Advanced OAuth redirect URI. The default is http://localhost."
|
|
141
124
|
}
|
|
142
125
|
}
|
|
143
126
|
},
|
|
@@ -171,18 +154,6 @@
|
|
|
171
154
|
"enableNotifications",
|
|
172
155
|
"debug"
|
|
173
156
|
]
|
|
174
|
-
},
|
|
175
|
-
{
|
|
176
|
-
"type": "fieldset",
|
|
177
|
-
"title": "Advanced OAuth",
|
|
178
|
-
"expandable": true,
|
|
179
|
-
"expanded": false,
|
|
180
|
-
"description": "Only change these fields when testing alternate Nuheat-issued API credentials.",
|
|
181
|
-
"items": [
|
|
182
|
-
"clientId",
|
|
183
|
-
"clientSecret",
|
|
184
|
-
"redirectUri"
|
|
185
|
-
]
|
|
186
157
|
}
|
|
187
158
|
],
|
|
188
159
|
"form": null,
|
|
@@ -22,12 +22,6 @@
|
|
|
22
22
|
</div>
|
|
23
23
|
|
|
24
24
|
<div class="settings-grid">
|
|
25
|
-
<label class="field">
|
|
26
|
-
<span>Platform Name</span>
|
|
27
|
-
<input id="name" type="text" placeholder="NuHeat" />
|
|
28
|
-
<small>Name shown in Homebridge logs for this platform instance.</small>
|
|
29
|
-
</label>
|
|
30
|
-
|
|
31
25
|
<label class="field">
|
|
32
26
|
<span>MyNuheat Email</span>
|
|
33
27
|
<input id="email" type="email" placeholder="name@example.com" />
|
|
@@ -168,56 +162,6 @@
|
|
|
168
162
|
</div>
|
|
169
163
|
</section>
|
|
170
164
|
|
|
171
|
-
<section class="panel">
|
|
172
|
-
<div class="section-header">
|
|
173
|
-
<div>
|
|
174
|
-
<h2>Advanced OAuth</h2>
|
|
175
|
-
<p class="help">
|
|
176
|
-
Leave these fields blank to use the built-in Nuheat PKCE public
|
|
177
|
-
client. Only change them when testing alternate Nuheat-issued API
|
|
178
|
-
credentials.
|
|
179
|
-
</p>
|
|
180
|
-
</div>
|
|
181
|
-
<span id="oauth-status" class="status-pill good">Built-in PKCE</span>
|
|
182
|
-
</div>
|
|
183
|
-
|
|
184
|
-
<div class="settings-grid">
|
|
185
|
-
<label class="field">
|
|
186
|
-
<span>Nuheat Client ID</span>
|
|
187
|
-
<input id="client-id" type="text" placeholder="Optional client ID" />
|
|
188
|
-
<small>
|
|
189
|
-
Advanced override. Blank uses homebridge-nuheat2_260421.
|
|
190
|
-
</small>
|
|
191
|
-
</label>
|
|
192
|
-
|
|
193
|
-
<label id="client-secret-row" class="field">
|
|
194
|
-
<span>Nuheat Client Secret (Legacy)</span>
|
|
195
|
-
<input
|
|
196
|
-
id="client-secret"
|
|
197
|
-
type="password"
|
|
198
|
-
placeholder="Leave blank to keep existing"
|
|
199
|
-
/>
|
|
200
|
-
<small>
|
|
201
|
-
Leave blank for PKCE public-client auth. Only use this for legacy
|
|
202
|
-
confidential-client credentials.
|
|
203
|
-
</small>
|
|
204
|
-
</label>
|
|
205
|
-
|
|
206
|
-
<label class="field">
|
|
207
|
-
<span>Nuheat Redirect URI</span>
|
|
208
|
-
<input id="redirect-uri" type="text" placeholder="http://localhost" />
|
|
209
|
-
<small>Defaults to http://localhost.</small>
|
|
210
|
-
</label>
|
|
211
|
-
</div>
|
|
212
|
-
|
|
213
|
-
<div class="actions">
|
|
214
|
-
<button id="save-oauth" class="primary">Save OAuth Settings</button>
|
|
215
|
-
<button id="clear-oauth" class="secondary" type="button">
|
|
216
|
-
Clear Overrides
|
|
217
|
-
</button>
|
|
218
|
-
</div>
|
|
219
|
-
</section>
|
|
220
|
-
|
|
221
165
|
<section class="panel">
|
|
222
166
|
<div class="section-header">
|
|
223
167
|
<div>
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const PLATFORM_NAME = "NuHeat";
|
|
2
2
|
|
|
3
3
|
const elements = {
|
|
4
|
-
name: document.getElementById("name"),
|
|
5
4
|
email: document.getElementById("email"),
|
|
6
5
|
password: document.getElementById("password"),
|
|
7
6
|
devicesList: document.getElementById("devices-list"),
|
|
@@ -14,20 +13,14 @@ const elements = {
|
|
|
14
13
|
refresh: document.getElementById("refresh"),
|
|
15
14
|
enableNotifications: document.getElementById("enable-notifications"),
|
|
16
15
|
debug: document.getElementById("debug"),
|
|
17
|
-
clientId: document.getElementById("client-id"),
|
|
18
|
-
clientSecret: document.getElementById("client-secret"),
|
|
19
|
-
redirectUri: document.getElementById("redirect-uri"),
|
|
20
16
|
saveAccount: document.getElementById("save-account"),
|
|
21
17
|
addDevice: document.getElementById("add-device"),
|
|
22
18
|
addGroup: document.getElementById("add-group"),
|
|
23
19
|
saveAccessories: document.getElementById("save-accessories"),
|
|
24
20
|
saveBehavior: document.getElementById("save-behavior"),
|
|
25
|
-
saveOauth: document.getElementById("save-oauth"),
|
|
26
|
-
clearOauth: document.getElementById("clear-oauth"),
|
|
27
21
|
authStatus: document.getElementById("auth-status"),
|
|
28
22
|
accessoryStatus: document.getElementById("accessory-status"),
|
|
29
23
|
behaviorStatus: document.getElementById("behavior-status"),
|
|
30
|
-
oauthStatus: document.getElementById("oauth-status"),
|
|
31
24
|
toastContainer: document.getElementById("toast-container"),
|
|
32
25
|
refreshDiagnostics: document.getElementById("refresh-diagnostics"),
|
|
33
26
|
diagnosticsSummary: document.getElementById("diagnostics-summary"),
|
|
@@ -39,7 +32,6 @@ const state = {
|
|
|
39
32
|
configs: [],
|
|
40
33
|
config: null,
|
|
41
34
|
hasPassword: false,
|
|
42
|
-
hasClientSecret: false,
|
|
43
35
|
};
|
|
44
36
|
|
|
45
37
|
function showToast(type, message) {
|
|
@@ -117,7 +109,6 @@ function createDefaultConfig() {
|
|
|
117
109
|
}
|
|
118
110
|
|
|
119
111
|
function renderConfig(config) {
|
|
120
|
-
elements.name.value = config.name || "NuHeat";
|
|
121
112
|
elements.email.value = config.email || config.Email || "";
|
|
122
113
|
elements.password.value = "";
|
|
123
114
|
state.hasPassword = Boolean(config.password);
|
|
@@ -137,25 +128,13 @@ function renderConfig(config) {
|
|
|
137
128
|
elements.enableNotifications.checked = config.enableNotifications !== false;
|
|
138
129
|
elements.debug.checked = Boolean(config.debug);
|
|
139
130
|
|
|
140
|
-
elements.clientId.value = config.clientId || "";
|
|
141
|
-
elements.clientSecret.value = "";
|
|
142
|
-
state.hasClientSecret = Boolean(config.clientSecret);
|
|
143
|
-
elements.clientSecret.placeholder = state.hasClientSecret
|
|
144
|
-
? "Saved secret (leave blank to keep)"
|
|
145
|
-
: "Optional client secret";
|
|
146
|
-
elements.redirectUri.value = config.redirectUri || "http://localhost";
|
|
147
|
-
|
|
148
131
|
updateStatuses();
|
|
149
132
|
renderDiagnostics();
|
|
150
133
|
}
|
|
151
134
|
|
|
152
135
|
function renderRows(container, type, items) {
|
|
153
136
|
container.innerHTML = "";
|
|
154
|
-
const
|
|
155
|
-
const visibleItems = normalizedItems.filter((item) => {
|
|
156
|
-
const value = type === "device" ? item.serialNumber : item.groupName;
|
|
157
|
-
return typeof value === "string" && value.trim().length > 0;
|
|
158
|
-
});
|
|
137
|
+
const visibleItems = normalizeConfigRows(type, items);
|
|
159
138
|
|
|
160
139
|
if (visibleItems.length === 0) {
|
|
161
140
|
const empty = document.createElement("div");
|
|
@@ -172,12 +151,44 @@ function renderRows(container, type, items) {
|
|
|
172
151
|
addRow(
|
|
173
152
|
container,
|
|
174
153
|
type,
|
|
175
|
-
|
|
176
|
-
|
|
154
|
+
item.value,
|
|
155
|
+
item.disabled,
|
|
177
156
|
);
|
|
178
157
|
});
|
|
179
158
|
}
|
|
180
159
|
|
|
160
|
+
function normalizeConfigRows(type, items) {
|
|
161
|
+
const normalizedItems = Array.isArray(items) ? items : [];
|
|
162
|
+
return normalizedItems
|
|
163
|
+
.map((item) => ({
|
|
164
|
+
value: getConfigRowValue(type, item),
|
|
165
|
+
disabled: typeof item === "object" && item !== null
|
|
166
|
+
? Boolean(item.disabled)
|
|
167
|
+
: false,
|
|
168
|
+
}))
|
|
169
|
+
.filter((item) => item.value.length > 0);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function getConfigRowValue(type, item) {
|
|
173
|
+
if (typeof item === "string" || typeof item === "number") {
|
|
174
|
+
return String(item).trim();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!item || typeof item !== "object") {
|
|
178
|
+
return "";
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const keys = type === "device"
|
|
182
|
+
? ["serialNumber", "SerialNumber", "serial", "Serial", "deviceId", "DeviceId"]
|
|
183
|
+
: ["groupName", "GroupName", "name", "Name"];
|
|
184
|
+
|
|
185
|
+
const value = keys
|
|
186
|
+
.map((key) => item[key])
|
|
187
|
+
.find((candidate) => typeof candidate === "string" || typeof candidate === "number");
|
|
188
|
+
|
|
189
|
+
return value === undefined ? "" : String(value).trim();
|
|
190
|
+
}
|
|
191
|
+
|
|
181
192
|
function addRow(container, type, value = "", disabled = false) {
|
|
182
193
|
const empty = container.querySelector(".empty-list");
|
|
183
194
|
if (empty) {
|
|
@@ -243,7 +254,6 @@ function collectRows(container, key) {
|
|
|
243
254
|
}
|
|
244
255
|
|
|
245
256
|
async function saveAccount() {
|
|
246
|
-
const name = elements.name.value.trim() || "NuHeat";
|
|
247
257
|
const email = elements.email.value.trim();
|
|
248
258
|
const password = elements.password.value;
|
|
249
259
|
|
|
@@ -261,7 +271,6 @@ async function saveAccount() {
|
|
|
261
271
|
|
|
262
272
|
const patch = {
|
|
263
273
|
platform: PLATFORM_NAME,
|
|
264
|
-
name,
|
|
265
274
|
email,
|
|
266
275
|
Email: undefined,
|
|
267
276
|
};
|
|
@@ -309,55 +318,6 @@ async function saveBehavior() {
|
|
|
309
318
|
showToast("success", "Behavior settings saved.");
|
|
310
319
|
}
|
|
311
320
|
|
|
312
|
-
async function saveOauth() {
|
|
313
|
-
const clientId = elements.clientId.value.trim();
|
|
314
|
-
const clientSecret = elements.clientSecret.value;
|
|
315
|
-
const redirectUri = elements.redirectUri.value.trim();
|
|
316
|
-
|
|
317
|
-
if (!clientId && clientSecret) {
|
|
318
|
-
showToast("error", "A client secret requires a Nuheat client ID.");
|
|
319
|
-
elements.clientId.focus();
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const patch = {
|
|
324
|
-
clientId: clientId || undefined,
|
|
325
|
-
redirectUri: redirectUri && redirectUri !== "http://localhost" ? redirectUri : undefined,
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
if (clientSecret) {
|
|
329
|
-
patch.clientSecret = clientSecret;
|
|
330
|
-
} else if (!clientId || clientId !== (state.config?.clientId || "")) {
|
|
331
|
-
patch.clientSecret = undefined;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
await persistPatch(patch);
|
|
335
|
-
if (clientSecret) {
|
|
336
|
-
state.hasClientSecret = true;
|
|
337
|
-
elements.clientSecret.value = "";
|
|
338
|
-
elements.clientSecret.placeholder = "Saved secret (leave blank to keep)";
|
|
339
|
-
updateStatuses();
|
|
340
|
-
renderDiagnostics();
|
|
341
|
-
}
|
|
342
|
-
showToast("success", "OAuth settings saved.");
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
async function clearOauth() {
|
|
346
|
-
await persistPatch({
|
|
347
|
-
clientId: undefined,
|
|
348
|
-
clientSecret: undefined,
|
|
349
|
-
redirectUri: undefined,
|
|
350
|
-
});
|
|
351
|
-
state.hasClientSecret = false;
|
|
352
|
-
elements.clientId.value = "";
|
|
353
|
-
elements.clientSecret.value = "";
|
|
354
|
-
elements.clientSecret.placeholder = "Optional client secret";
|
|
355
|
-
elements.redirectUri.value = "http://localhost";
|
|
356
|
-
updateStatuses();
|
|
357
|
-
renderDiagnostics();
|
|
358
|
-
showToast("success", "OAuth overrides cleared.");
|
|
359
|
-
}
|
|
360
|
-
|
|
361
321
|
async function persistPatch(patch) {
|
|
362
322
|
await withSpinner(async () => {
|
|
363
323
|
if (!state.config) {
|
|
@@ -390,7 +350,6 @@ function updateStatuses() {
|
|
|
390
350
|
updateAuthStatus();
|
|
391
351
|
updateAccessoryStatus();
|
|
392
352
|
updateBehaviorStatus();
|
|
393
|
-
updateOauthStatus();
|
|
394
353
|
}
|
|
395
354
|
|
|
396
355
|
function updateAuthStatus() {
|
|
@@ -436,26 +395,6 @@ function updateBehaviorStatus() {
|
|
|
436
395
|
);
|
|
437
396
|
}
|
|
438
397
|
|
|
439
|
-
function updateOauthStatus() {
|
|
440
|
-
const hasClientId = elements.clientId.value.trim().length > 0;
|
|
441
|
-
const hasClientSecret = hasUsableClientSecret(elements.clientId.value.trim());
|
|
442
|
-
const text = hasClientId
|
|
443
|
-
? hasClientSecret
|
|
444
|
-
? "Custom legacy OAuth"
|
|
445
|
-
: "Custom PKCE"
|
|
446
|
-
: "Built-in PKCE";
|
|
447
|
-
setStatus(elements.oauthStatus, true, text);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function hasUsableClientSecret(clientId) {
|
|
451
|
-
if (elements.clientSecret.value) {
|
|
452
|
-
return true;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
const savedClientId = state.config?.clientId || "";
|
|
456
|
-
return Boolean(clientId && state.hasClientSecret && clientId === savedClientId);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
398
|
function setStatus(element, isGood, text) {
|
|
460
399
|
element.textContent = text;
|
|
461
400
|
element.classList.toggle("good", Boolean(isGood));
|
|
@@ -478,10 +417,9 @@ function renderDiagnostics() {
|
|
|
478
417
|
|
|
479
418
|
addDiagnosticCard("Account", [
|
|
480
419
|
["Platform", PLATFORM_NAME],
|
|
481
|
-
["Name", config.name || "NuHeat"],
|
|
482
420
|
["Email", config.email || "not configured"],
|
|
483
421
|
["Password", state.hasPassword || elements.password.value ? "saved" : "missing"],
|
|
484
|
-
["
|
|
422
|
+
["API Access", "built-in Nuheat PKCE client"],
|
|
485
423
|
]);
|
|
486
424
|
|
|
487
425
|
addDiagnosticCard("Accessories", [
|
|
@@ -527,7 +465,6 @@ function addDiagnosticCard(title, rows) {
|
|
|
527
465
|
function getDraftConfig() {
|
|
528
466
|
return {
|
|
529
467
|
...(state.config || createDefaultConfig()),
|
|
530
|
-
name: elements.name.value.trim() || "NuHeat",
|
|
531
468
|
email: elements.email.value.trim(),
|
|
532
469
|
devices: collectRows(elements.devicesList, "serialNumber"),
|
|
533
470
|
groups: collectRows(elements.groupsList, "groupName"),
|
|
@@ -539,8 +476,6 @@ function getDraftConfig() {
|
|
|
539
476
|
refresh: normalizeRefresh(elements.refresh.value),
|
|
540
477
|
enableNotifications: Boolean(elements.enableNotifications.checked),
|
|
541
478
|
debug: Boolean(elements.debug.checked),
|
|
542
|
-
clientId: elements.clientId.value.trim(),
|
|
543
|
-
redirectUri: elements.redirectUri.value.trim() || "http://localhost",
|
|
544
479
|
};
|
|
545
480
|
}
|
|
546
481
|
|
|
@@ -576,16 +511,6 @@ function getHoldSummary(value) {
|
|
|
576
511
|
return `${holdLength} minute timed hold`;
|
|
577
512
|
}
|
|
578
513
|
|
|
579
|
-
function getOauthMode(config) {
|
|
580
|
-
if (config.clientId && hasUsableClientSecret(config.clientId)) {
|
|
581
|
-
return "configured confidential client";
|
|
582
|
-
}
|
|
583
|
-
if (config.clientId) {
|
|
584
|
-
return "configured PKCE public client";
|
|
585
|
-
}
|
|
586
|
-
return "built-in PKCE public client";
|
|
587
|
-
}
|
|
588
|
-
|
|
589
514
|
function normalizeHoldLength(value) {
|
|
590
515
|
const parsed = Number.parseInt(value, 10);
|
|
591
516
|
if (!Number.isFinite(parsed)) {
|
|
@@ -627,12 +552,6 @@ function bindEvents() {
|
|
|
627
552
|
elements.saveBehavior.addEventListener("click", () => {
|
|
628
553
|
saveBehavior().catch(() => showToast("error", "Failed to save behavior."));
|
|
629
554
|
});
|
|
630
|
-
elements.saveOauth.addEventListener("click", () => {
|
|
631
|
-
saveOauth().catch(() => showToast("error", "Failed to save OAuth settings."));
|
|
632
|
-
});
|
|
633
|
-
elements.clearOauth.addEventListener("click", () => {
|
|
634
|
-
clearOauth().catch(() => showToast("error", "Failed to clear OAuth settings."));
|
|
635
|
-
});
|
|
636
555
|
elements.addDevice.addEventListener("click", () => {
|
|
637
556
|
addRow(elements.devicesList, "device");
|
|
638
557
|
});
|
|
@@ -642,7 +561,6 @@ function bindEvents() {
|
|
|
642
561
|
elements.refreshDiagnostics.addEventListener("click", renderDiagnostics);
|
|
643
562
|
|
|
644
563
|
[
|
|
645
|
-
elements.name,
|
|
646
564
|
elements.email,
|
|
647
565
|
elements.password,
|
|
648
566
|
elements.autoPopulateAwayModeSwitches,
|
|
@@ -651,9 +569,6 @@ function bindEvents() {
|
|
|
651
569
|
elements.refresh,
|
|
652
570
|
elements.enableNotifications,
|
|
653
571
|
elements.debug,
|
|
654
|
-
elements.clientId,
|
|
655
|
-
elements.clientSecret,
|
|
656
|
-
elements.redirectUri,
|
|
657
572
|
].forEach((element) => {
|
|
658
573
|
element.addEventListener("input", () => {
|
|
659
574
|
updateStatuses();
|
|
@@ -295,7 +295,15 @@ button.secondary:hover {
|
|
|
295
295
|
display: grid;
|
|
296
296
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
297
297
|
gap: 10px;
|
|
298
|
-
align-items:
|
|
298
|
+
align-items: end;
|
|
299
|
+
background: var(--plugin-control-surface);
|
|
300
|
+
border: 1px solid var(--plugin-border);
|
|
301
|
+
border-radius: 8px;
|
|
302
|
+
padding: 12px;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.config-row .field {
|
|
306
|
+
min-width: 0;
|
|
299
307
|
}
|
|
300
308
|
|
|
301
309
|
.row-checkbox {
|
package/lib/NuHeatAPI.js
CHANGED
|
@@ -33,15 +33,16 @@ class NuHeatAPI {
|
|
|
33
33
|
this.log = log;
|
|
34
34
|
const configuredClientId = options.clientId || process.env.NUHEAT_API_CLIENT_ID || "";
|
|
35
35
|
const configuredClientSecret = options.clientSecret || process.env.NUHEAT_API_CLIENT_SECRET || "";
|
|
36
|
+
const usingBuiltInPublicClient = !configuredClientId || configuredClientId === settings_1.NUHEAT_API_CLIENT_ID;
|
|
36
37
|
this.oauthClientId = configuredClientId || settings_1.NUHEAT_API_CLIENT_ID;
|
|
37
|
-
this.oauthClientSecret =
|
|
38
|
-
?
|
|
39
|
-
:
|
|
38
|
+
this.oauthClientSecret = usingBuiltInPublicClient
|
|
39
|
+
? ""
|
|
40
|
+
: configuredClientSecret || settings_1.NUHEAT_API_CLIENT_SECRET;
|
|
40
41
|
this.oauthRedirectUri =
|
|
41
42
|
options.redirectUri ||
|
|
42
43
|
process.env.NUHEAT_API_REDIRECT_URI ||
|
|
43
44
|
settings_1.NUHEAT_API_REDIRECT_URI;
|
|
44
|
-
this.usingBuiltInClient =
|
|
45
|
+
this.usingBuiltInClient = usingBuiltInPublicClient;
|
|
45
46
|
this.usePkce = !this.oauthClientSecret;
|
|
46
47
|
this.pkceCodeVerifier = "";
|
|
47
48
|
this.headers = new HeadersCtor();
|