@switchbot/homebridge-switchbot 5.1.0-beta.0 → 5.1.0-beta.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.
Files changed (101) hide show
  1. package/.github/copilot-instructions.md +63 -0
  2. package/.github/scripts/normalize-changelog-header.cjs +86 -0
  3. package/.github/scripts/npm-version-script-esm-auto.js +201 -0
  4. package/.github/scripts/resolve-version-bump-esm.js +118 -0
  5. package/.github/workflows/release.yml +62 -13
  6. package/CHANGELOG.md +18 -0
  7. package/dist/{SwitchBotHAPPlatform.d.ts → Platform.HAP.d.ts} +2 -2
  8. package/dist/Platform.HAP.d.ts.map +1 -0
  9. package/dist/{SwitchBotHAPPlatform.js → Platform.HAP.js} +4 -2
  10. package/dist/Platform.HAP.js.map +1 -0
  11. package/dist/{SwitchBotMatterPlatform.d.ts → Platform.Matter.d.ts} +3 -2
  12. package/dist/Platform.Matter.d.ts.map +1 -0
  13. package/dist/{SwitchBotMatterPlatform.js → Platform.Matter.js} +24 -1
  14. package/dist/Platform.Matter.js.map +1 -0
  15. package/dist/homebridge-ui/endpoints/config.d.ts.map +1 -1
  16. package/dist/homebridge-ui/endpoints/config.js +9 -2
  17. package/dist/homebridge-ui/endpoints/config.js.map +1 -1
  18. package/dist/homebridge-ui/homebridge-ui/endpoints/config.js +9 -2
  19. package/dist/homebridge-ui/homebridge-ui/endpoints/config.js.map +1 -1
  20. package/dist/homebridge-ui/homebridge-ui/utils/config-parser.js +8 -0
  21. package/dist/homebridge-ui/homebridge-ui/utils/config-parser.js.map +1 -1
  22. package/dist/homebridge-ui/homebridge-ui/utils/v4-detection.js +23 -0
  23. package/dist/homebridge-ui/homebridge-ui/utils/v4-detection.js.map +1 -0
  24. package/dist/homebridge-ui/public/css/styles.css +62 -2
  25. package/dist/homebridge-ui/public/index.html +61 -6
  26. package/dist/homebridge-ui/public/js/advanced-settings.d.ts.map +1 -1
  27. package/dist/homebridge-ui/public/js/advanced-settings.js +4 -3
  28. package/dist/homebridge-ui/public/js/advanced-settings.js.map +1 -1
  29. package/dist/homebridge-ui/public/js/advanced-settings.ts +4 -3
  30. package/dist/homebridge-ui/public/js/api.d.ts +11 -0
  31. package/dist/homebridge-ui/public/js/api.d.ts.map +1 -1
  32. package/dist/homebridge-ui/public/js/api.js +44 -1
  33. package/dist/homebridge-ui/public/js/api.js.map +1 -1
  34. package/dist/homebridge-ui/public/js/api.ts +46 -1
  35. package/dist/homebridge-ui/public/js/app.js +241 -182
  36. package/dist/homebridge-ui/public/js/app.js.map +4 -4
  37. package/dist/homebridge-ui/public/js/app.ts +47 -4
  38. package/dist/homebridge-ui/public/js/credentials.d.ts.map +1 -1
  39. package/dist/homebridge-ui/public/js/credentials.js +11 -5
  40. package/dist/homebridge-ui/public/js/credentials.js.map +1 -1
  41. package/dist/homebridge-ui/public/js/credentials.ts +11 -5
  42. package/dist/homebridge-ui/public/js/discovery.d.ts.map +1 -1
  43. package/dist/homebridge-ui/public/js/discovery.js +0 -36
  44. package/dist/homebridge-ui/public/js/discovery.js.map +1 -1
  45. package/dist/homebridge-ui/public/js/discovery.ts +0 -37
  46. package/dist/homebridge-ui/public/js/legacy-view.d.ts +3 -0
  47. package/dist/homebridge-ui/public/js/legacy-view.d.ts.map +1 -0
  48. package/dist/homebridge-ui/public/js/legacy-view.js +152 -0
  49. package/dist/homebridge-ui/public/js/legacy-view.js.map +1 -0
  50. package/dist/homebridge-ui/public/js/legacy-view.ts +107 -0
  51. package/dist/homebridge-ui/public/js/render.d.ts.map +1 -1
  52. package/dist/homebridge-ui/public/js/render.js +38 -31
  53. package/dist/homebridge-ui/public/js/render.js.map +1 -1
  54. package/dist/homebridge-ui/public/js/render.ts +39 -34
  55. package/dist/homebridge-ui/public/js/toast.d.ts.map +1 -1
  56. package/dist/homebridge-ui/public/js/toast.js +2 -3
  57. package/dist/homebridge-ui/public/js/toast.js.map +1 -1
  58. package/dist/homebridge-ui/public/js/toast.ts +2 -3
  59. package/dist/homebridge-ui/utils/config-parser.d.ts +8 -0
  60. package/dist/homebridge-ui/utils/config-parser.d.ts.map +1 -1
  61. package/dist/homebridge-ui/utils/config-parser.js +8 -0
  62. package/dist/homebridge-ui/utils/config-parser.js.map +1 -1
  63. package/dist/homebridge-ui/utils/v4-detection.d.ts +12 -0
  64. package/dist/homebridge-ui/utils/v4-detection.d.ts.map +1 -0
  65. package/dist/homebridge-ui/utils/v4-detection.js +23 -0
  66. package/dist/homebridge-ui/utils/v4-detection.js.map +1 -0
  67. package/dist/index.js +2 -2
  68. package/dist/index.js.map +1 -1
  69. package/docs/assets/icons.js +1 -1
  70. package/docs/assets/icons.svg +1 -1
  71. package/docs/assets/main.js +3 -3
  72. package/docs/variables/default.html +1 -1
  73. package/package.json +12 -18
  74. package/scripts/build-ui.js +30 -0
  75. package/src/{SwitchBotHAPPlatform.ts → Platform.HAP.ts} +5 -2
  76. package/src/{SwitchBotMatterPlatform.ts → Platform.Matter.ts} +28 -1
  77. package/src/homebridge-ui/endpoints/config.ts +10 -2
  78. package/src/homebridge-ui/public/css/styles.css +62 -2
  79. package/src/homebridge-ui/public/index.html +59 -4
  80. package/src/homebridge-ui/public/js/advanced-settings.ts +4 -3
  81. package/src/homebridge-ui/public/js/api.ts +46 -1
  82. package/src/homebridge-ui/public/js/app.ts +47 -4
  83. package/src/homebridge-ui/public/js/credentials.ts +11 -5
  84. package/src/homebridge-ui/public/js/discovery.ts +0 -37
  85. package/src/homebridge-ui/public/js/legacy-view.js +152 -0
  86. package/src/homebridge-ui/public/js/legacy-view.ts +107 -0
  87. package/src/homebridge-ui/public/js/render.ts +39 -34
  88. package/src/homebridge-ui/public/js/toast.ts +2 -3
  89. package/src/homebridge-ui/utils/config-parser.ts +9 -0
  90. package/src/homebridge-ui/utils/v4-detection.ts +23 -0
  91. package/src/index.ts +2 -2
  92. package/test/homebridge-ui/config-parser.spec.ts +61 -0
  93. package/test/platform/accessory-restore.spec.ts +2 -2
  94. package/test/platform/matter-childbridge.spec.ts +1 -1
  95. package/test/platform/matter-integration.spec.ts +51 -1
  96. package/test/platform/platform-edge.spec.ts +2 -2
  97. package/test/platform/platform.integration.spec.ts +1 -1
  98. package/dist/SwitchBotHAPPlatform.d.ts.map +0 -1
  99. package/dist/SwitchBotHAPPlatform.js.map +0 -1
  100. package/dist/SwitchBotMatterPlatform.d.ts.map +0 -1
  101. package/dist/SwitchBotMatterPlatform.js.map +0 -1
@@ -8,111 +8,6 @@ var __export = (target, all) => {
8
8
  __defProp(target, name, { get: all[name], enumerable: true });
9
9
  };
10
10
 
11
- // src/homebridge-ui/public/js/logger.ts
12
- var PREFIX, uiLog;
13
- var init_logger = __esm({
14
- "src/homebridge-ui/public/js/logger.ts"() {
15
- "use strict";
16
- PREFIX = "[SwitchBot UI/html]";
17
- uiLog = {
18
- info: (message, ...parameters) => {
19
- console.log(PREFIX, message, ...parameters);
20
- },
21
- warn: (message, ...parameters) => {
22
- console.warn(PREFIX, message, ...parameters);
23
- },
24
- error: (message, ...parameters) => {
25
- console.error(PREFIX, message, ...parameters);
26
- },
27
- debug: (message, ...parameters) => {
28
- console.debug(PREFIX, message, ...parameters);
29
- }
30
- };
31
- }
32
- });
33
-
34
- // src/homebridge-ui/public/js/types.ts
35
- var init_types = __esm({
36
- "src/homebridge-ui/public/js/types.ts"() {
37
- "use strict";
38
- }
39
- });
40
-
41
- // src/homebridge-ui/public/js/modal.ts
42
- function callUiMethod(name, ...args) {
43
- try {
44
- if (typeof homebridge?.[name] === "function") {
45
- uiLog.info(`[callUiMethod] Invoking homebridge.${String(name)}()`);
46
- const fn = homebridge?.[name];
47
- if (typeof fn === "function") {
48
- uiLog.info(`[callUiMethod] Invoking homebridge.${String(name)}()`);
49
- fn.apply(homebridge, args);
50
- } else {
51
- uiLog.warn(`[callUiMethod] homebridge[${String(name)}] is not a function.`);
52
- }
53
- } else {
54
- uiLog.warn(`[callUiMethod] homebridge[${String(name)}] is not a function.`);
55
- }
56
- } catch (e) {
57
- uiLog.warn(`Homebridge UI method ${String(name)} failed:`, e);
58
- }
59
- }
60
- function showBusyUi() {
61
- callUiMethod("disableSaveButton");
62
- callUiMethod("showSpinner");
63
- }
64
- function hideBusyUi() {
65
- callUiMethod("hideSpinner");
66
- callUiMethod("enableSaveButton");
67
- }
68
- var init_modal = __esm({
69
- "src/homebridge-ui/public/js/modal.ts"() {
70
- "use strict";
71
- init_types();
72
- init_logger();
73
- }
74
- });
75
-
76
- // src/homebridge-ui/public/js/toast.ts
77
- function showToast(method, message, title = "SwitchBot") {
78
- try {
79
- const hb = typeof window !== "undefined" ? window.homebridge : void 0;
80
- const toast = hb && typeof hb.toast === "object" ? hb.toast : void 0;
81
- const fn = toast && typeof toast[method] === "function" ? toast[method] : void 0;
82
- if (fn) {
83
- try {
84
- fn(message, title);
85
- return;
86
- } catch (err) {
87
- uiLog.warn(`Toast ${method} threw:`, err);
88
- }
89
- }
90
- uiLog.info(`[Toast:${method}] ${title} - ${message}`);
91
- } catch (e) {
92
- uiLog.warn(`Toast ${method} outer error:`, e);
93
- uiLog.info(`[Toast:${method}] ${title} - ${message}`);
94
- }
95
- }
96
- function toastSuccess(message, title) {
97
- showToast("success", message, title);
98
- }
99
- function toastError(message, title) {
100
- showToast("error", message, title);
101
- }
102
- function toastWarning(message, title) {
103
- showToast("warning", message, title);
104
- }
105
- function toastInfo(message, title) {
106
- showToast("info", message, title);
107
- }
108
- var init_toast = __esm({
109
- "src/homebridge-ui/public/js/toast.ts"() {
110
- "use strict";
111
- init_types();
112
- init_logger();
113
- }
114
- });
115
-
116
11
  // src/device-types.js
117
12
  function getValidDeviceTypes() {
118
13
  const validTypes = /* @__PURE__ */ new Set();
@@ -354,6 +249,90 @@ var init_device_types = __esm({
354
249
  }
355
250
  });
356
251
 
252
+ // src/homebridge-ui/utils/v4-detection.ts
253
+ function isV4Config(platform) {
254
+ if (!platform || typeof platform !== "object") {
255
+ return false;
256
+ }
257
+ const hasCredentials = platform.credentials !== void 0 && typeof platform.credentials === "object";
258
+ const hasOptionsDevices = platform.options !== void 0 && typeof platform.options === "object" && Array.isArray(platform.options.devices);
259
+ return hasCredentials || hasOptionsDevices;
260
+ }
261
+ var init_v4_detection = __esm({
262
+ "src/homebridge-ui/utils/v4-detection.ts"() {
263
+ "use strict";
264
+ }
265
+ });
266
+
267
+ // src/homebridge-ui/public/js/types.ts
268
+ var init_types = __esm({
269
+ "src/homebridge-ui/public/js/types.ts"() {
270
+ "use strict";
271
+ }
272
+ });
273
+
274
+ // src/homebridge-ui/public/js/logger.ts
275
+ var PREFIX, uiLog;
276
+ var init_logger = __esm({
277
+ "src/homebridge-ui/public/js/logger.ts"() {
278
+ "use strict";
279
+ PREFIX = "[SwitchBot UI/html]";
280
+ uiLog = {
281
+ info: (message, ...parameters) => {
282
+ console.log(PREFIX, message, ...parameters);
283
+ },
284
+ warn: (message, ...parameters) => {
285
+ console.warn(PREFIX, message, ...parameters);
286
+ },
287
+ error: (message, ...parameters) => {
288
+ console.error(PREFIX, message, ...parameters);
289
+ },
290
+ debug: (message, ...parameters) => {
291
+ console.debug(PREFIX, message, ...parameters);
292
+ }
293
+ };
294
+ }
295
+ });
296
+
297
+ // src/homebridge-ui/public/js/toast.ts
298
+ function showToast(method, message, title = "SwitchBot") {
299
+ try {
300
+ const hb = typeof window !== "undefined" ? window.homebridge : void 0;
301
+ const toast = hb && typeof hb.toast === "object" ? hb.toast : void 0;
302
+ if (toast && typeof toast[method] === "function") {
303
+ try {
304
+ toast[method](message, title);
305
+ return;
306
+ } catch (err) {
307
+ uiLog.warn(`Toast ${method} threw:`, err);
308
+ }
309
+ }
310
+ uiLog.info(`[Toast:${method}] ${title} - ${message}`);
311
+ } catch (e) {
312
+ uiLog.warn(`Toast ${method} outer error:`, e);
313
+ uiLog.info(`[Toast:${method}] ${title} - ${message}`);
314
+ }
315
+ }
316
+ function toastSuccess(message, title) {
317
+ showToast("success", message, title);
318
+ }
319
+ function toastError(message, title) {
320
+ showToast("error", message, title);
321
+ }
322
+ function toastWarning(message, title) {
323
+ showToast("warning", message, title);
324
+ }
325
+ function toastInfo(message, title) {
326
+ showToast("info", message, title);
327
+ }
328
+ var init_toast = __esm({
329
+ "src/homebridge-ui/public/js/toast.ts"() {
330
+ "use strict";
331
+ init_types();
332
+ init_logger();
333
+ }
334
+ });
335
+
357
336
  // src/homebridge-ui/public/js/api.ts
358
337
  var api_exports = {};
359
338
  __export(api_exports, {
@@ -361,11 +340,13 @@ __export(api_exports, {
361
340
  addDevicesInBulk: () => addDevicesInBulk,
362
341
  deleteAllDevices: () => deleteAllDevices,
363
342
  deleteDevice: () => deleteDevice,
343
+ detectV4Config: () => detectV4Config,
364
344
  discoverDevices: () => discoverDevices,
365
345
  fetchBluetoothStatus: () => fetchBluetoothStatus,
366
346
  fetchCredentialStatus: () => fetchCredentialStatus,
367
347
  fetchDevices: () => fetchDevices,
368
- saveCredentials: () => saveCredentials2,
348
+ normalizeBulkAddDevicesResponse: () => normalizeBulkAddDevicesResponse,
349
+ saveCredentials: () => saveCredentials,
369
350
  syncParentPluginConfigFromDisk: () => syncParentPluginConfigFromDisk,
370
351
  testDeviceConnection: () => testDeviceConnection,
371
352
  updateDevice: () => updateDevice,
@@ -388,6 +369,25 @@ async function fetchDevices() {
388
369
  return [];
389
370
  }
390
371
  }
372
+ async function detectV4Config() {
373
+ try {
374
+ if (typeof homebridge.getPluginConfig !== "function") {
375
+ return false;
376
+ }
377
+ const configArr = await homebridge.getPluginConfig();
378
+ if (!Array.isArray(configArr)) {
379
+ return false;
380
+ }
381
+ const config = configArr.find(isSwitchBotPlatformConfig);
382
+ if (!config || typeof config !== "object") {
383
+ return false;
384
+ }
385
+ return isV4Config(config);
386
+ } catch (e) {
387
+ uiLog.warn("Error detecting v4 config:", e);
388
+ return false;
389
+ }
390
+ }
391
391
  function validateAndFixDeviceTypes(devices) {
392
392
  const errors = [];
393
393
  for (const d of devices) {
@@ -457,7 +457,7 @@ async function fetchCredentialStatus() {
457
457
  return null;
458
458
  }
459
459
  }
460
- async function saveCredentials2(token, secret) {
460
+ async function saveCredentials(token, secret) {
461
461
  uiLog.info("Saving credentials...");
462
462
  const resp = await homebridge.request("/credentials", { token, secret });
463
463
  uiLog.info("Save response:", resp);
@@ -563,7 +563,20 @@ async function addDevicesInBulk(devices) {
563
563
  if (!resp || resp.success === false) {
564
564
  throw new Error(resp?.data?.message || "Bulk add failed");
565
565
  }
566
- return resp.data || resp;
566
+ return normalizeBulkAddDevicesResponse(resp);
567
+ }
568
+ function normalizeBulkAddDevicesResponse(resp) {
569
+ const payload = resp?.data && typeof resp.data === "object" ? resp.data : resp;
570
+ const addedCount = Number(payload?.addedCount ?? payload?.added ?? 0);
571
+ const skippedCount = Number(payload?.skippedCount ?? payload?.skipped ?? 0);
572
+ const updatedCount = Number(payload?.updatedCount ?? payload?.updated ?? 0);
573
+ return {
574
+ ...payload,
575
+ success: resp?.success ?? true,
576
+ addedCount,
577
+ skippedCount,
578
+ updatedCount
579
+ };
567
580
  }
568
581
  async function updateDevice(deviceId, configDeviceName, configDeviceType, options) {
569
582
  if (typeof homebridge.getPluginConfig !== "function" || typeof homebridge.updatePluginConfig !== "function") {
@@ -657,12 +670,48 @@ var init_api = __esm({
657
670
  "src/homebridge-ui/public/js/api.ts"() {
658
671
  "use strict";
659
672
  init_device_types();
673
+ init_v4_detection();
660
674
  init_types();
661
675
  init_logger();
662
676
  init_toast();
663
677
  }
664
678
  });
665
679
 
680
+ // src/homebridge-ui/public/js/modal.ts
681
+ function callUiMethod(name, ...args) {
682
+ try {
683
+ if (typeof homebridge?.[name] === "function") {
684
+ uiLog.info(`[callUiMethod] Invoking homebridge.${String(name)}()`);
685
+ const fn = homebridge?.[name];
686
+ if (typeof fn === "function") {
687
+ uiLog.info(`[callUiMethod] Invoking homebridge.${String(name)}()`);
688
+ fn.apply(homebridge, args);
689
+ } else {
690
+ uiLog.warn(`[callUiMethod] homebridge[${String(name)}] is not a function.`);
691
+ }
692
+ } else {
693
+ uiLog.warn(`[callUiMethod] homebridge[${String(name)}] is not a function.`);
694
+ }
695
+ } catch (e) {
696
+ uiLog.warn(`Homebridge UI method ${String(name)} failed:`, e);
697
+ }
698
+ }
699
+ function showBusyUi() {
700
+ callUiMethod("disableSaveButton");
701
+ callUiMethod("showSpinner");
702
+ }
703
+ function hideBusyUi() {
704
+ callUiMethod("hideSpinner");
705
+ callUiMethod("enableSaveButton");
706
+ }
707
+ var init_modal = __esm({
708
+ "src/homebridge-ui/public/js/modal.ts"() {
709
+ "use strict";
710
+ init_types();
711
+ init_logger();
712
+ }
713
+ });
714
+
666
715
  // src/homebridge-ui/public/js/discovery.ts
667
716
  var discovery_exports = {};
668
717
  __export(discovery_exports, {
@@ -963,7 +1012,6 @@ function setDiscoveryGroupExpanded(groupKey, expanded) {
963
1012
  }
964
1013
  async function discoverDevices2() {
965
1014
  const btn = document.getElementById("discoverBtn");
966
- const cancelBtn = document.getElementById("cancelDiscoverBtn");
967
1015
  const status = document.getElementById("discoverStatus");
968
1016
  const phaseProgress = document.getElementById("discoverPhaseProgress");
969
1017
  const phaseFill = document.getElementById("discoverPhaseFill");
@@ -991,7 +1039,6 @@ async function discoverDevices2() {
991
1039
  const startedAt = Date.now();
992
1040
  let phaseStartedAt = startedAt;
993
1041
  let phase = "Preparing discovery...";
994
- let cancelled = false;
995
1042
  const setPhase = (nextPhase) => {
996
1043
  phase = nextPhase;
997
1044
  phaseStartedAt = Date.now();
@@ -1428,31 +1475,8 @@ async function discoverDevices2() {
1428
1475
  devicesFoundDisplay.style.display = "none";
1429
1476
  devicesFoundDisplay.classList.remove("discovery-scanning-pulse");
1430
1477
  }
1431
- if (cancelBtn) {
1432
- cancelBtn.style.display = "inline-block";
1433
- cancelBtn.disabled = false;
1434
- cancelBtn.onclick = () => {
1435
- cancelled = true;
1436
- const totalSeconds = Math.max(0, Math.floor((Date.now() - startedAt) / 1e3));
1437
- status.textContent = `Discovery cancelled (${totalSeconds}s total)`;
1438
- if (phaseProgress) {
1439
- phaseProgress.style.display = "none";
1440
- }
1441
- if (devicesFoundDisplay) {
1442
- devicesFoundDisplay.style.display = "none";
1443
- devicesFoundDisplay.classList.remove("discovery-scanning-pulse");
1444
- }
1445
- cancelBtn.style.display = "none";
1446
- btn.disabled = false;
1447
- btn.textContent = "\u{1F50D} Discover Devices";
1448
- hideBusyUi();
1449
- };
1450
- }
1451
1478
  if (bleSettings.bleEnabled) {
1452
1479
  const bleDevicesRaw = await discoverDevices("ble", bleSettings);
1453
- if (cancelled) {
1454
- return;
1455
- }
1456
1480
  discoveredDevices = dedupeById(bleDevicesRaw);
1457
1481
  uiLog.info("BLE discover response:", bleDevicesRaw);
1458
1482
  if (devicesFoundDisplay && bleDevicesRaw.length > 0) {
@@ -1473,9 +1497,6 @@ async function discoverDevices2() {
1473
1497
  renderProgress();
1474
1498
  try {
1475
1499
  const openApiDevicesRaw = await discoverDevices("openapi");
1476
- if (cancelled) {
1477
- return;
1478
- }
1479
1500
  uiLog.info("OpenAPI discover response:", openApiDevicesRaw);
1480
1501
  discoveredDevices = mergeDiscoveredDevices(discoveredDevices, openApiDevicesRaw);
1481
1502
  if (devicesFoundDisplay && discoveredDevices.length > 0) {
@@ -1555,9 +1576,6 @@ async function discoverDevices2() {
1555
1576
  setDiscoveryCache(discoveredDevices);
1556
1577
  updateLastScannedStatus();
1557
1578
  } catch (e) {
1558
- if (cancelled) {
1559
- return;
1560
- }
1561
1579
  uiLog.error("Discovery error:", e);
1562
1580
  status.textContent = `Error: ${e instanceof Error ? e.message : "Discovery failed"}`;
1563
1581
  status.classList.add("error");
@@ -1582,11 +1600,6 @@ async function discoverDevices2() {
1582
1600
  }
1583
1601
  btn.disabled = false;
1584
1602
  btn.textContent = "\u{1F50D} Discover Devices";
1585
- if (cancelBtn) {
1586
- cancelBtn.disabled = false;
1587
- cancelBtn.style.display = "none";
1588
- cancelBtn.onclick = null;
1589
- }
1590
1603
  }
1591
1604
  }
1592
1605
  async function updateDiscoveryView(allDevices, preferences, groupBy, hideAdded, selectedIds) {
@@ -2714,6 +2727,21 @@ __export(render_exports, {
2714
2727
  setDiscoveryPreferences: () => setDiscoveryPreferences,
2715
2728
  sortDevices: () => sortDevices
2716
2729
  });
2730
+ async function copyTextWithFallback(text) {
2731
+ try {
2732
+ await navigator.clipboard.writeText(text);
2733
+ } catch {
2734
+ const textarea = document.createElement("textarea");
2735
+ textarea.value = text;
2736
+ textarea.setAttribute("readonly", "");
2737
+ textarea.style.cssText = "position:fixed;left:-9999px;opacity:0;pointer-events:none";
2738
+ document.body.appendChild(textarea);
2739
+ textarea.select();
2740
+ textarea.setSelectionRange(0, textarea.value.length);
2741
+ document.execCommand("copy");
2742
+ textarea.remove();
2743
+ }
2744
+ }
2717
2745
  function getRssiSignalQuality(rssi) {
2718
2746
  if (!rssi || rssi === 0) {
2719
2747
  return {
@@ -2794,7 +2822,6 @@ function renderSignalQualityBadge(rssi) {
2794
2822
  border-radius: 3px;
2795
2823
  font-size: 10px;
2796
2824
  font-weight: 600;
2797
- margin-left: 8px;
2798
2825
  `;
2799
2826
  badge.title = quality.description;
2800
2827
  return badge;
@@ -3439,6 +3466,7 @@ function renderDeviceList(list) {
3439
3466
  }
3440
3467
  status.textContent = `Found ${list.length} device(s)`;
3441
3468
  ul.classList.add("device-grid");
3469
+ ul.style.padding = "0";
3442
3470
  ul.innerHTML = "";
3443
3471
  if (removeAllContainer) {
3444
3472
  removeAllContainer.style.display = "block";
@@ -3450,6 +3478,7 @@ function renderDeviceList(list) {
3450
3478
  li.style.display = "flex";
3451
3479
  li.style.flexDirection = "column";
3452
3480
  li.style.alignItems = "stretch";
3481
+ li.style.padding = "5px 8px";
3453
3482
  li.style.marginBottom = "0";
3454
3483
  const info = document.createElement("div");
3455
3484
  info.style.flex = "1 1 auto";
@@ -3457,13 +3486,14 @@ function renderDeviceList(list) {
3457
3486
  info.style.minWidth = "0";
3458
3487
  const nameContainer = document.createElement("div");
3459
3488
  nameContainer.style.display = "flex";
3460
- nameContainer.style.flexDirection = "column";
3461
- nameContainer.style.alignItems = "flex-start";
3462
- nameContainer.style.gap = "0";
3489
+ nameContainer.style.alignItems = "center";
3490
+ nameContainer.style.marginBottom = "0";
3491
+ nameContainer.style.flexWrap = "wrap";
3492
+ nameContainer.style.gap = "4px";
3463
3493
  const name = document.createElement("div");
3464
3494
  name.style.fontWeight = "500";
3465
3495
  name.style.fontSize = "13px";
3466
- name.textContent = d.name || d.id;
3496
+ name.textContent = d.configDeviceName || d.name || d.id;
3467
3497
  const expandedDetails = document.createElement("div");
3468
3498
  expandedDetails.style.display = "none";
3469
3499
  expandedDetails.appendChild(renderDeviceDetailsPanel(d));
@@ -3481,36 +3511,23 @@ function renderDeviceList(list) {
3481
3511
  expandedDetails.style.display = isHidden ? "block" : "none";
3482
3512
  expandBtn.style.transform = isHidden ? "rotate(180deg)" : "rotate(0deg)";
3483
3513
  };
3484
- const code = document.createElement("code");
3485
- code.textContent = d.id;
3486
- code.style.fontSize = "10px";
3487
- code.style.opacity = "0.75";
3488
- code.style.marginLeft = "0";
3489
- code.style.whiteSpace = "normal";
3490
- code.style.overflowWrap = "anywhere";
3491
- code.style.wordBreak = "break-word";
3492
- code.style.maxWidth = "100%";
3493
- const headerRow = document.createElement("div");
3494
- headerRow.style.display = "inline-flex";
3495
- headerRow.style.alignItems = "center";
3496
- headerRow.style.gap = "4px";
3497
- headerRow.appendChild(name);
3498
- headerRow.appendChild(expandBtn);
3499
- nameContainer.appendChild(headerRow);
3500
- nameContainer.appendChild(code);
3514
+ nameContainer.appendChild(name);
3515
+ nameContainer.appendChild(expandBtn);
3501
3516
  if (d.rssi !== void 0 && d.rssi !== null && d.rssi !== 0) {
3502
3517
  nameContainer.appendChild(renderSignalBars(d.rssi));
3503
3518
  nameContainer.appendChild(renderSignalQualityBadge(d.rssi));
3504
3519
  }
3505
3520
  const meta = document.createElement("div");
3506
- meta.style.opacity = "0.75";
3507
- meta.style.marginTop = "0";
3508
- meta.style.fontSize = "11px";
3509
- const typeText = d.type ? `type: ${d.type}` : "";
3510
- const connText = d.connectionPreference ? `conn: ${d.connectionPreference}` : "";
3521
+ meta.style.opacity = "0.7";
3522
+ meta.style.fontSize = "10px";
3523
+ meta.style.fontFamily = "monospace";
3524
+ const deviceIdentifier = d.deviceId || d.id;
3525
+ const id = `ID: ${deviceIdentifier}`;
3526
+ const typeText = d.configDeviceType || d.type ? `Type: ${d.configDeviceType || d.type}` : "";
3527
+ const connText = d.connectionPreference ? `Conn: ${d.connectionPreference}` : "";
3511
3528
  const extText = d.externalPublishProtocol && d.externalPublishProtocol !== "none" ? `external: ${d.externalPublishProtocol}` : "";
3512
- const roomText = d.room ? `room: ${d.room}` : "";
3513
- meta.textContent = [typeText, connText, extText, roomText].filter(Boolean).join(" | ");
3529
+ const roomText = d.room ? `Room: ${d.room}` : "";
3530
+ meta.textContent = [id, typeText, connText, extText, roomText].filter(Boolean).join(" | ");
3514
3531
  info.appendChild(nameContainer);
3515
3532
  info.appendChild(meta);
3516
3533
  info.appendChild(expandedDetails);
@@ -3537,7 +3554,9 @@ function renderDeviceList(list) {
3537
3554
  copyBtn.style.fontSize = "11px";
3538
3555
  copyBtn.addEventListener("click", async () => {
3539
3556
  try {
3540
- await navigator.clipboard.writeText(d.id);
3557
+ if (deviceIdentifier) {
3558
+ await copyTextWithFallback(deviceIdentifier);
3559
+ }
3541
3560
  copyBtn.textContent = "Copied";
3542
3561
  copyBtn.classList.add("success");
3543
3562
  setTimeout(() => {
@@ -3564,8 +3583,8 @@ function renderDeviceList(list) {
3564
3583
  };
3565
3584
  buttons.appendChild(editBtn);
3566
3585
  buttons.appendChild(copyBtn);
3567
- buttons.appendChild(createConnectionTestControls(d));
3568
3586
  buttons.appendChild(deleteBtn);
3587
+ buttons.appendChild(createConnectionTestControls(d));
3569
3588
  li.appendChild(info);
3570
3589
  li.appendChild(buttons);
3571
3590
  ul.appendChild(li);
@@ -3685,6 +3704,9 @@ var init_devices = __esm({
3685
3704
  }
3686
3705
  });
3687
3706
 
3707
+ // src/homebridge-ui/public/js/app.ts
3708
+ init_api();
3709
+
3688
3710
  // src/homebridge-ui/public/js/credentials.ts
3689
3711
  init_logger();
3690
3712
  init_modal();
@@ -3722,7 +3744,7 @@ async function loadCredentialStatus() {
3722
3744
  uiLog.error("Error loading credentials:", e);
3723
3745
  }
3724
3746
  }
3725
- async function saveCredentials() {
3747
+ async function saveCredentials2() {
3726
3748
  const token = document.getElementById("token")?.value;
3727
3749
  const secret = document.getElementById("secret")?.value;
3728
3750
  const saveStatus = document.getElementById("saveStatus");
@@ -3744,14 +3766,19 @@ async function saveCredentials() {
3744
3766
  if (typeof homebridge.getPluginConfig !== "function" || typeof homebridge.updatePluginConfig !== "function") {
3745
3767
  throw new TypeError("Homebridge UI API not available");
3746
3768
  }
3747
- const configArr = await homebridge.getPluginConfig();
3748
- if (!Array.isArray(configArr) || configArr.length === 0) {
3749
- throw new Error("No plugin config found");
3769
+ let configArr = await homebridge.getPluginConfig();
3770
+ if (!Array.isArray(configArr)) {
3771
+ configArr = [];
3750
3772
  }
3751
- const config = configArr[0];
3773
+ let idx = configArr.findIndex((c) => (c.platform || c.name || "").toLowerCase() === "switchbot");
3774
+ if (idx === -1) {
3775
+ configArr.push({ platform: "SwitchBot" });
3776
+ idx = configArr.length - 1;
3777
+ }
3778
+ const config = configArr[idx];
3752
3779
  config.openApiToken = token;
3753
3780
  config.openApiSecret = secret;
3754
- await homebridge.updatePluginConfig([config]);
3781
+ await homebridge.updatePluginConfig(configArr);
3755
3782
  if (typeof homebridge.savePluginConfig === "function") {
3756
3783
  await homebridge.savePluginConfig();
3757
3784
  }
@@ -3781,10 +3808,42 @@ async function saveCredentials() {
3781
3808
  // src/homebridge-ui/public/js/app.ts
3782
3809
  init_devices();
3783
3810
  init_discovery();
3811
+ async function ensureSwitchBotConfigBlock() {
3812
+ try {
3813
+ if (typeof homebridge.getPluginConfig !== "function" || typeof homebridge.updatePluginConfig !== "function") {
3814
+ return;
3815
+ }
3816
+ let configArr = await homebridge.getPluginConfig();
3817
+ if (!Array.isArray(configArr)) {
3818
+ configArr = [];
3819
+ }
3820
+ const idx = configArr.findIndex((c) => (c.platform || c.name || "").toLowerCase().includes("switchbot"));
3821
+ if (idx === -1) {
3822
+ configArr.push({ platform: "SwitchBot" });
3823
+ await homebridge.updatePluginConfig(configArr);
3824
+ if (typeof homebridge.savePluginConfig === "function") {
3825
+ await homebridge.savePluginConfig();
3826
+ }
3827
+ }
3828
+ } catch (_e) {
3829
+ }
3830
+ }
3784
3831
  window.loadCredentialStatus = loadCredentialStatus;
3785
- window.saveCredentials = saveCredentials;
3832
+ window.saveCredentials = saveCredentials2;
3786
3833
  window.discoverDevices = discoverDevices2;
3834
+ async function checkV4Config() {
3835
+ const banner = document.getElementById("v4ConfigWarning");
3836
+ if (!banner) {
3837
+ return;
3838
+ }
3839
+ const isV4 = await detectV4Config();
3840
+ if (isV4) {
3841
+ banner.style.display = "block";
3842
+ }
3843
+ }
3787
3844
  async function init() {
3845
+ await ensureSwitchBotConfigBlock();
3846
+ await checkV4Config();
3788
3847
  await loadCredentialStatus();
3789
3848
  await initializeDiscoverySettings();
3790
3849
  await loadConfiguredDevices();