homebridge-nuheat2 1.2.17 → 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 +8 -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 -134
- package/homebridge-ui/public/styles.css +9 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ 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
|
+
|
|
7
15
|
## [1.2.17] - 2026-04-29
|
|
8
16
|
|
|
9
17
|
### Fixed
|
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,8 +1,6 @@
|
|
|
1
1
|
const PLATFORM_NAME = "NuHeat";
|
|
2
|
-
const BUILT_IN_CLIENT_ID = "homebridge-nuheat2_260421";
|
|
3
2
|
|
|
4
3
|
const elements = {
|
|
5
|
-
name: document.getElementById("name"),
|
|
6
4
|
email: document.getElementById("email"),
|
|
7
5
|
password: document.getElementById("password"),
|
|
8
6
|
devicesList: document.getElementById("devices-list"),
|
|
@@ -15,20 +13,14 @@ const elements = {
|
|
|
15
13
|
refresh: document.getElementById("refresh"),
|
|
16
14
|
enableNotifications: document.getElementById("enable-notifications"),
|
|
17
15
|
debug: document.getElementById("debug"),
|
|
18
|
-
clientId: document.getElementById("client-id"),
|
|
19
|
-
clientSecret: document.getElementById("client-secret"),
|
|
20
|
-
redirectUri: document.getElementById("redirect-uri"),
|
|
21
16
|
saveAccount: document.getElementById("save-account"),
|
|
22
17
|
addDevice: document.getElementById("add-device"),
|
|
23
18
|
addGroup: document.getElementById("add-group"),
|
|
24
19
|
saveAccessories: document.getElementById("save-accessories"),
|
|
25
20
|
saveBehavior: document.getElementById("save-behavior"),
|
|
26
|
-
saveOauth: document.getElementById("save-oauth"),
|
|
27
|
-
clearOauth: document.getElementById("clear-oauth"),
|
|
28
21
|
authStatus: document.getElementById("auth-status"),
|
|
29
22
|
accessoryStatus: document.getElementById("accessory-status"),
|
|
30
23
|
behaviorStatus: document.getElementById("behavior-status"),
|
|
31
|
-
oauthStatus: document.getElementById("oauth-status"),
|
|
32
24
|
toastContainer: document.getElementById("toast-container"),
|
|
33
25
|
refreshDiagnostics: document.getElementById("refresh-diagnostics"),
|
|
34
26
|
diagnosticsSummary: document.getElementById("diagnostics-summary"),
|
|
@@ -40,7 +32,6 @@ const state = {
|
|
|
40
32
|
configs: [],
|
|
41
33
|
config: null,
|
|
42
34
|
hasPassword: false,
|
|
43
|
-
hasClientSecret: false,
|
|
44
35
|
};
|
|
45
36
|
|
|
46
37
|
function showToast(type, message) {
|
|
@@ -118,7 +109,6 @@ function createDefaultConfig() {
|
|
|
118
109
|
}
|
|
119
110
|
|
|
120
111
|
function renderConfig(config) {
|
|
121
|
-
elements.name.value = config.name || "NuHeat";
|
|
122
112
|
elements.email.value = config.email || config.Email || "";
|
|
123
113
|
elements.password.value = "";
|
|
124
114
|
state.hasPassword = Boolean(config.password);
|
|
@@ -138,27 +128,13 @@ function renderConfig(config) {
|
|
|
138
128
|
elements.enableNotifications.checked = config.enableNotifications !== false;
|
|
139
129
|
elements.debug.checked = Boolean(config.debug);
|
|
140
130
|
|
|
141
|
-
elements.clientId.value = config.clientId || "";
|
|
142
|
-
elements.clientSecret.value = "";
|
|
143
|
-
state.hasClientSecret = Boolean(
|
|
144
|
-
config.clientSecret && config.clientId !== BUILT_IN_CLIENT_ID,
|
|
145
|
-
);
|
|
146
|
-
elements.clientSecret.placeholder = state.hasClientSecret
|
|
147
|
-
? "Saved secret (leave blank to keep)"
|
|
148
|
-
: "Optional client secret";
|
|
149
|
-
elements.redirectUri.value = config.redirectUri || "http://localhost";
|
|
150
|
-
|
|
151
131
|
updateStatuses();
|
|
152
132
|
renderDiagnostics();
|
|
153
133
|
}
|
|
154
134
|
|
|
155
135
|
function renderRows(container, type, items) {
|
|
156
136
|
container.innerHTML = "";
|
|
157
|
-
const
|
|
158
|
-
const visibleItems = normalizedItems.filter((item) => {
|
|
159
|
-
const value = type === "device" ? item.serialNumber : item.groupName;
|
|
160
|
-
return typeof value === "string" && value.trim().length > 0;
|
|
161
|
-
});
|
|
137
|
+
const visibleItems = normalizeConfigRows(type, items);
|
|
162
138
|
|
|
163
139
|
if (visibleItems.length === 0) {
|
|
164
140
|
const empty = document.createElement("div");
|
|
@@ -175,12 +151,44 @@ function renderRows(container, type, items) {
|
|
|
175
151
|
addRow(
|
|
176
152
|
container,
|
|
177
153
|
type,
|
|
178
|
-
|
|
179
|
-
|
|
154
|
+
item.value,
|
|
155
|
+
item.disabled,
|
|
180
156
|
);
|
|
181
157
|
});
|
|
182
158
|
}
|
|
183
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
|
+
|
|
184
192
|
function addRow(container, type, value = "", disabled = false) {
|
|
185
193
|
const empty = container.querySelector(".empty-list");
|
|
186
194
|
if (empty) {
|
|
@@ -246,7 +254,6 @@ function collectRows(container, key) {
|
|
|
246
254
|
}
|
|
247
255
|
|
|
248
256
|
async function saveAccount() {
|
|
249
|
-
const name = elements.name.value.trim() || "NuHeat";
|
|
250
257
|
const email = elements.email.value.trim();
|
|
251
258
|
const password = elements.password.value;
|
|
252
259
|
|
|
@@ -264,7 +271,6 @@ async function saveAccount() {
|
|
|
264
271
|
|
|
265
272
|
const patch = {
|
|
266
273
|
platform: PLATFORM_NAME,
|
|
267
|
-
name,
|
|
268
274
|
email,
|
|
269
275
|
Email: undefined,
|
|
270
276
|
};
|
|
@@ -312,57 +318,6 @@ async function saveBehavior() {
|
|
|
312
318
|
showToast("success", "Behavior settings saved.");
|
|
313
319
|
}
|
|
314
320
|
|
|
315
|
-
async function saveOauth() {
|
|
316
|
-
const clientId = elements.clientId.value.trim();
|
|
317
|
-
const clientSecret = elements.clientSecret.value;
|
|
318
|
-
const redirectUri = elements.redirectUri.value.trim();
|
|
319
|
-
const usesBuiltInClient = !clientId || clientId === BUILT_IN_CLIENT_ID;
|
|
320
|
-
|
|
321
|
-
if (!clientId && clientSecret) {
|
|
322
|
-
showToast("error", "A client secret requires a Nuheat client ID.");
|
|
323
|
-
elements.clientId.focus();
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
const patch = {
|
|
328
|
-
clientId: usesBuiltInClient ? undefined : clientId,
|
|
329
|
-
redirectUri: redirectUri && redirectUri !== "http://localhost" ? redirectUri : undefined,
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
if (clientSecret && !usesBuiltInClient) {
|
|
333
|
-
patch.clientSecret = clientSecret;
|
|
334
|
-
} else if (usesBuiltInClient || clientId !== (state.config?.clientId || "")) {
|
|
335
|
-
patch.clientSecret = undefined;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
await persistPatch(patch);
|
|
339
|
-
state.hasClientSecret = Boolean(state.config?.clientSecret);
|
|
340
|
-
elements.clientId.value = state.config?.clientId || "";
|
|
341
|
-
elements.clientSecret.value = "";
|
|
342
|
-
elements.clientSecret.placeholder = state.hasClientSecret
|
|
343
|
-
? "Saved secret (leave blank to keep)"
|
|
344
|
-
: "Optional client secret";
|
|
345
|
-
updateStatuses();
|
|
346
|
-
renderDiagnostics();
|
|
347
|
-
showToast("success", "OAuth settings saved.");
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
async function clearOauth() {
|
|
351
|
-
await persistPatch({
|
|
352
|
-
clientId: undefined,
|
|
353
|
-
clientSecret: undefined,
|
|
354
|
-
redirectUri: undefined,
|
|
355
|
-
});
|
|
356
|
-
state.hasClientSecret = false;
|
|
357
|
-
elements.clientId.value = "";
|
|
358
|
-
elements.clientSecret.value = "";
|
|
359
|
-
elements.clientSecret.placeholder = "Optional client secret";
|
|
360
|
-
elements.redirectUri.value = "http://localhost";
|
|
361
|
-
updateStatuses();
|
|
362
|
-
renderDiagnostics();
|
|
363
|
-
showToast("success", "OAuth overrides cleared.");
|
|
364
|
-
}
|
|
365
|
-
|
|
366
321
|
async function persistPatch(patch) {
|
|
367
322
|
await withSpinner(async () => {
|
|
368
323
|
if (!state.config) {
|
|
@@ -395,7 +350,6 @@ function updateStatuses() {
|
|
|
395
350
|
updateAuthStatus();
|
|
396
351
|
updateAccessoryStatus();
|
|
397
352
|
updateBehaviorStatus();
|
|
398
|
-
updateOauthStatus();
|
|
399
353
|
}
|
|
400
354
|
|
|
401
355
|
function updateAuthStatus() {
|
|
@@ -441,31 +395,6 @@ function updateBehaviorStatus() {
|
|
|
441
395
|
);
|
|
442
396
|
}
|
|
443
397
|
|
|
444
|
-
function updateOauthStatus() {
|
|
445
|
-
const clientId = elements.clientId.value.trim();
|
|
446
|
-
const hasClientId = clientId.length > 0 && clientId !== BUILT_IN_CLIENT_ID;
|
|
447
|
-
const hasClientSecret = hasUsableClientSecret(clientId);
|
|
448
|
-
const text = hasClientId
|
|
449
|
-
? hasClientSecret
|
|
450
|
-
? "Custom legacy OAuth"
|
|
451
|
-
: "Custom PKCE"
|
|
452
|
-
: "Built-in PKCE";
|
|
453
|
-
setStatus(elements.oauthStatus, true, text);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
function hasUsableClientSecret(clientId) {
|
|
457
|
-
if (!clientId || clientId === BUILT_IN_CLIENT_ID) {
|
|
458
|
-
return false;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
if (elements.clientSecret.value) {
|
|
462
|
-
return true;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
const savedClientId = state.config?.clientId || "";
|
|
466
|
-
return Boolean(clientId && state.hasClientSecret && clientId === savedClientId);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
398
|
function setStatus(element, isGood, text) {
|
|
470
399
|
element.textContent = text;
|
|
471
400
|
element.classList.toggle("good", Boolean(isGood));
|
|
@@ -488,10 +417,9 @@ function renderDiagnostics() {
|
|
|
488
417
|
|
|
489
418
|
addDiagnosticCard("Account", [
|
|
490
419
|
["Platform", PLATFORM_NAME],
|
|
491
|
-
["Name", config.name || "NuHeat"],
|
|
492
420
|
["Email", config.email || "not configured"],
|
|
493
421
|
["Password", state.hasPassword || elements.password.value ? "saved" : "missing"],
|
|
494
|
-
["
|
|
422
|
+
["API Access", "built-in Nuheat PKCE client"],
|
|
495
423
|
]);
|
|
496
424
|
|
|
497
425
|
addDiagnosticCard("Accessories", [
|
|
@@ -537,7 +465,6 @@ function addDiagnosticCard(title, rows) {
|
|
|
537
465
|
function getDraftConfig() {
|
|
538
466
|
return {
|
|
539
467
|
...(state.config || createDefaultConfig()),
|
|
540
|
-
name: elements.name.value.trim() || "NuHeat",
|
|
541
468
|
email: elements.email.value.trim(),
|
|
542
469
|
devices: collectRows(elements.devicesList, "serialNumber"),
|
|
543
470
|
groups: collectRows(elements.groupsList, "groupName"),
|
|
@@ -549,8 +476,6 @@ function getDraftConfig() {
|
|
|
549
476
|
refresh: normalizeRefresh(elements.refresh.value),
|
|
550
477
|
enableNotifications: Boolean(elements.enableNotifications.checked),
|
|
551
478
|
debug: Boolean(elements.debug.checked),
|
|
552
|
-
clientId: elements.clientId.value.trim(),
|
|
553
|
-
redirectUri: elements.redirectUri.value.trim() || "http://localhost",
|
|
554
479
|
};
|
|
555
480
|
}
|
|
556
481
|
|
|
@@ -586,19 +511,6 @@ function getHoldSummary(value) {
|
|
|
586
511
|
return `${holdLength} minute timed hold`;
|
|
587
512
|
}
|
|
588
513
|
|
|
589
|
-
function getOauthMode(config) {
|
|
590
|
-
if (!config.clientId || config.clientId === BUILT_IN_CLIENT_ID) {
|
|
591
|
-
return "built-in PKCE public client";
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
if (config.clientId && hasUsableClientSecret(config.clientId)) {
|
|
595
|
-
return "configured confidential client";
|
|
596
|
-
}
|
|
597
|
-
if (config.clientId) {
|
|
598
|
-
return "configured PKCE public client";
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
|
|
602
514
|
function normalizeHoldLength(value) {
|
|
603
515
|
const parsed = Number.parseInt(value, 10);
|
|
604
516
|
if (!Number.isFinite(parsed)) {
|
|
@@ -640,12 +552,6 @@ function bindEvents() {
|
|
|
640
552
|
elements.saveBehavior.addEventListener("click", () => {
|
|
641
553
|
saveBehavior().catch(() => showToast("error", "Failed to save behavior."));
|
|
642
554
|
});
|
|
643
|
-
elements.saveOauth.addEventListener("click", () => {
|
|
644
|
-
saveOauth().catch(() => showToast("error", "Failed to save OAuth settings."));
|
|
645
|
-
});
|
|
646
|
-
elements.clearOauth.addEventListener("click", () => {
|
|
647
|
-
clearOauth().catch(() => showToast("error", "Failed to clear OAuth settings."));
|
|
648
|
-
});
|
|
649
555
|
elements.addDevice.addEventListener("click", () => {
|
|
650
556
|
addRow(elements.devicesList, "device");
|
|
651
557
|
});
|
|
@@ -655,7 +561,6 @@ function bindEvents() {
|
|
|
655
561
|
elements.refreshDiagnostics.addEventListener("click", renderDiagnostics);
|
|
656
562
|
|
|
657
563
|
[
|
|
658
|
-
elements.name,
|
|
659
564
|
elements.email,
|
|
660
565
|
elements.password,
|
|
661
566
|
elements.autoPopulateAwayModeSwitches,
|
|
@@ -664,9 +569,6 @@ function bindEvents() {
|
|
|
664
569
|
elements.refresh,
|
|
665
570
|
elements.enableNotifications,
|
|
666
571
|
elements.debug,
|
|
667
|
-
elements.clientId,
|
|
668
|
-
elements.clientSecret,
|
|
669
|
-
elements.redirectUri,
|
|
670
572
|
].forEach((element) => {
|
|
671
573
|
element.addEventListener("input", () => {
|
|
672
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 {
|