@switchbot/homebridge-switchbot 5.0.0-beta.108 → 5.0.0-beta.109

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 (148) hide show
  1. package/TODO.md +564 -0
  2. package/config.schema.json +100 -25
  3. package/dist/device-types.d.ts +15 -0
  4. package/dist/device-types.d.ts.map +1 -0
  5. package/dist/device-types.js +145 -0
  6. package/dist/device-types.js.map +1 -0
  7. package/dist/deviceFactory.d.ts +1 -1
  8. package/dist/deviceFactory.d.ts.map +1 -1
  9. package/dist/deviceFactory.js +55 -17
  10. package/dist/deviceFactory.js.map +1 -1
  11. package/dist/devices/genericDevice.d.ts +1 -1
  12. package/dist/devices/genericDevice.d.ts.map +1 -1
  13. package/dist/devices/genericDevice.js +4 -2
  14. package/dist/devices/genericDevice.js.map +1 -1
  15. package/dist/homebridge-ui/endpoints/config.d.ts +6 -0
  16. package/dist/homebridge-ui/endpoints/config.d.ts.map +1 -0
  17. package/dist/homebridge-ui/endpoints/config.js +73 -0
  18. package/dist/homebridge-ui/endpoints/config.js.map +1 -0
  19. package/dist/homebridge-ui/endpoints/credentials.d.ts +6 -0
  20. package/dist/homebridge-ui/endpoints/credentials.d.ts.map +1 -0
  21. package/dist/homebridge-ui/endpoints/credentials.js +65 -0
  22. package/dist/homebridge-ui/endpoints/credentials.js.map +1 -0
  23. package/dist/homebridge-ui/endpoints/devices.d.ts +6 -0
  24. package/dist/homebridge-ui/endpoints/devices.d.ts.map +1 -0
  25. package/dist/homebridge-ui/endpoints/devices.js +285 -0
  26. package/dist/homebridge-ui/endpoints/devices.js.map +1 -0
  27. package/dist/homebridge-ui/endpoints/discovery.d.ts +7 -0
  28. package/dist/homebridge-ui/endpoints/discovery.d.ts.map +1 -0
  29. package/dist/homebridge-ui/endpoints/discovery.js +171 -0
  30. package/dist/homebridge-ui/endpoints/discovery.js.map +1 -0
  31. package/dist/homebridge-ui/public/index.html +144 -624
  32. package/dist/homebridge-ui/public/js/api.d.ts +15 -0
  33. package/dist/homebridge-ui/public/js/api.d.ts.map +1 -0
  34. package/dist/homebridge-ui/public/js/api.js +164 -0
  35. package/dist/homebridge-ui/public/js/api.js.map +1 -0
  36. package/dist/homebridge-ui/public/js/api.ts +189 -0
  37. package/dist/homebridge-ui/public/js/app.d.ts +2 -0
  38. package/dist/homebridge-ui/public/js/app.d.ts.map +1 -0
  39. package/dist/homebridge-ui/public/js/app.js +1069 -0
  40. package/dist/homebridge-ui/public/js/app.js.map +7 -0
  41. package/dist/homebridge-ui/public/js/app.ts +21 -0
  42. package/dist/homebridge-ui/public/js/constants.d.ts +2 -0
  43. package/dist/homebridge-ui/public/js/constants.d.ts.map +1 -0
  44. package/dist/homebridge-ui/public/js/constants.js +2 -0
  45. package/dist/homebridge-ui/public/js/constants.js.map +1 -0
  46. package/dist/homebridge-ui/public/js/constants.ts +1 -0
  47. package/dist/homebridge-ui/public/js/credentials.d.ts +3 -0
  48. package/dist/homebridge-ui/public/js/credentials.d.ts.map +1 -0
  49. package/dist/homebridge-ui/public/js/credentials.js +96 -0
  50. package/dist/homebridge-ui/public/js/credentials.js.map +1 -0
  51. package/dist/homebridge-ui/public/js/credentials.ts +100 -0
  52. package/dist/homebridge-ui/public/js/devices-delete.d.ts +2 -0
  53. package/dist/homebridge-ui/public/js/devices-delete.d.ts.map +1 -0
  54. package/dist/homebridge-ui/public/js/devices-delete.js +99 -0
  55. package/dist/homebridge-ui/public/js/devices-delete.js.map +1 -0
  56. package/dist/homebridge-ui/public/js/devices-delete.ts +113 -0
  57. package/dist/homebridge-ui/public/js/devices.d.ts +8 -0
  58. package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -0
  59. package/dist/homebridge-ui/public/js/devices.js +71 -0
  60. package/dist/homebridge-ui/public/js/devices.js.map +1 -0
  61. package/dist/homebridge-ui/public/js/devices.ts +78 -0
  62. package/dist/homebridge-ui/public/js/discovery.d.ts +3 -0
  63. package/dist/homebridge-ui/public/js/discovery.d.ts.map +1 -0
  64. package/dist/homebridge-ui/public/js/discovery.js +107 -0
  65. package/dist/homebridge-ui/public/js/discovery.js.map +1 -0
  66. package/dist/homebridge-ui/public/js/discovery.ts +123 -0
  67. package/dist/homebridge-ui/public/js/logger.d.ts +6 -0
  68. package/dist/homebridge-ui/public/js/logger.d.ts.map +1 -0
  69. package/dist/homebridge-ui/public/js/logger.js +12 -0
  70. package/dist/homebridge-ui/public/js/logger.js.map +1 -0
  71. package/dist/homebridge-ui/public/js/logger.ts +11 -0
  72. package/dist/homebridge-ui/public/js/modal.d.ts +5 -0
  73. package/dist/homebridge-ui/public/js/modal.d.ts.map +1 -0
  74. package/dist/homebridge-ui/public/js/modal.js +26 -0
  75. package/dist/homebridge-ui/public/js/modal.js.map +1 -0
  76. package/dist/homebridge-ui/public/js/modal.ts +28 -0
  77. package/dist/homebridge-ui/public/js/modals.d.ts +2 -0
  78. package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -0
  79. package/dist/homebridge-ui/public/js/modals.js +132 -0
  80. package/dist/homebridge-ui/public/js/modals.js.map +1 -0
  81. package/dist/homebridge-ui/public/js/modals.ts +153 -0
  82. package/dist/homebridge-ui/public/js/render.d.ts +6 -0
  83. package/dist/homebridge-ui/public/js/render.d.ts.map +1 -0
  84. package/dist/homebridge-ui/public/js/render.js +166 -0
  85. package/dist/homebridge-ui/public/js/render.js.map +1 -0
  86. package/dist/homebridge-ui/public/js/render.ts +190 -0
  87. package/dist/homebridge-ui/public/js/toast.d.ts +6 -0
  88. package/dist/homebridge-ui/public/js/toast.d.ts.map +1 -0
  89. package/dist/homebridge-ui/public/js/toast.js +29 -0
  90. package/dist/homebridge-ui/public/js/toast.js.map +1 -0
  91. package/dist/homebridge-ui/public/js/toast.ts +37 -0
  92. package/dist/homebridge-ui/public/js/types.d.ts +23 -0
  93. package/dist/homebridge-ui/public/js/types.d.ts.map +1 -0
  94. package/dist/homebridge-ui/public/js/types.js +2 -0
  95. package/dist/homebridge-ui/public/js/types.js.map +1 -0
  96. package/dist/homebridge-ui/public/js/types.ts +26 -0
  97. package/dist/homebridge-ui/server.d.ts +1 -3
  98. package/dist/homebridge-ui/server.d.ts.map +1 -1
  99. package/dist/homebridge-ui/server.js +10 -561
  100. package/dist/homebridge-ui/server.js.map +1 -1
  101. package/dist/homebridge-ui/utils/config-parser.d.ts +35 -0
  102. package/dist/homebridge-ui/utils/config-parser.d.ts.map +1 -0
  103. package/dist/homebridge-ui/utils/config-parser.js +87 -0
  104. package/dist/homebridge-ui/utils/config-parser.js.map +1 -0
  105. package/dist/homebridge-ui/utils/logger.d.ts +7 -0
  106. package/dist/homebridge-ui/utils/logger.d.ts.map +1 -0
  107. package/dist/homebridge-ui/utils/logger.js +17 -0
  108. package/dist/homebridge-ui/utils/logger.js.map +1 -0
  109. package/dist/platform.d.ts.map +1 -1
  110. package/dist/platform.js +6 -2
  111. package/dist/platform.js.map +1 -1
  112. package/dist/switchbotClient.d.ts.map +1 -1
  113. package/dist/switchbotClient.js +2 -0
  114. package/dist/switchbotClient.js.map +1 -1
  115. package/docs/variables/default.html +1 -1
  116. package/eslint.config.js +5 -0
  117. package/nodemon.json +2 -2
  118. package/package.json +20 -10
  119. package/scripts/build-ui.js +36 -0
  120. package/scripts/free-dev-ports.mjs +105 -0
  121. package/scripts/sync-device-types.mjs +28 -0
  122. package/src/device-types.ts +145 -0
  123. package/src/deviceFactory.ts +71 -31
  124. package/src/devices/genericDevice.ts +5 -2
  125. package/src/homebridge-ui/endpoints/config.ts +79 -0
  126. package/src/homebridge-ui/endpoints/credentials.ts +74 -0
  127. package/src/homebridge-ui/endpoints/devices.ts +321 -0
  128. package/src/homebridge-ui/endpoints/discovery.ts +191 -0
  129. package/src/homebridge-ui/public/index.html +144 -624
  130. package/src/homebridge-ui/public/js/api.ts +189 -0
  131. package/src/homebridge-ui/public/js/app.ts +21 -0
  132. package/src/homebridge-ui/public/js/constants.ts +1 -0
  133. package/src/homebridge-ui/public/js/credentials.ts +100 -0
  134. package/src/homebridge-ui/public/js/devices-delete.ts +113 -0
  135. package/src/homebridge-ui/public/js/devices.ts +78 -0
  136. package/src/homebridge-ui/public/js/discovery.ts +123 -0
  137. package/src/homebridge-ui/public/js/logger.ts +11 -0
  138. package/src/homebridge-ui/public/js/modal.ts +28 -0
  139. package/src/homebridge-ui/public/js/modals.ts +153 -0
  140. package/src/homebridge-ui/public/js/render.ts +190 -0
  141. package/src/homebridge-ui/public/js/toast.ts +37 -0
  142. package/src/homebridge-ui/public/js/types.ts +26 -0
  143. package/src/homebridge-ui/server.ts +11 -647
  144. package/src/homebridge-ui/utils/config-parser.ts +108 -0
  145. package/src/homebridge-ui/utils/logger.ts +17 -0
  146. package/src/platform.ts +6 -2
  147. package/src/switchbotClient.ts +2 -0
  148. package/switchbot-homebridge-switchbot-5.0.0.tgz +0 -0
@@ -0,0 +1,113 @@
1
+ import { deleteDevice as apiDeleteDevice, syncParentPluginConfigFromDisk, fetchDevices } from './api.js'
2
+ import { uiLog } from './logger.js'
3
+ import { hideBusyUi, showBusyUi } from './modal.js'
4
+ import { renderDeviceList } from './render.js'
5
+ import { toastError, toastSuccess, toastWarning } from './toast.js'
6
+
7
+ async function confirmDeleteDialog(deviceNameOrId: string): Promise<boolean> {
8
+ return new Promise((resolve) => {
9
+ const overlay = document.createElement('div')
10
+ overlay.style.position = 'fixed'
11
+ overlay.style.top = '0'
12
+ overlay.style.left = '0'
13
+ overlay.style.width = '100%'
14
+ overlay.style.height = '100%'
15
+ overlay.style.background = 'rgba(0,0,0,0.6)'
16
+ overlay.style.display = 'flex'
17
+ overlay.style.alignItems = 'center'
18
+ overlay.style.justifyContent = 'center'
19
+ overlay.style.zIndex = '10000'
20
+
21
+ const modal = document.createElement('div')
22
+ modal.style.background = getComputedStyle(document.body).backgroundColor
23
+ modal.style.color = getComputedStyle(document.body).color
24
+ modal.style.padding = '20px'
25
+ modal.style.borderRadius = '10px'
26
+ modal.style.minWidth = '340px'
27
+ modal.style.maxWidth = '90vw'
28
+ modal.style.boxShadow = '0 12px 40px rgba(0,0,0,0.35)'
29
+
30
+ const title = document.createElement('h3')
31
+ title.textContent = 'Delete Device'
32
+ title.style.margin = '0 0 10px 0'
33
+
34
+ const message = document.createElement('p')
35
+ message.textContent = `Remove "${deviceNameOrId}" from configuration?`
36
+ message.style.margin = '0 0 16px 0'
37
+
38
+ const actions = document.createElement('div')
39
+ actions.style.display = 'flex'
40
+ actions.style.justifyContent = 'flex-end'
41
+ actions.style.gap = '8px'
42
+
43
+ const cancelBtn = document.createElement('button')
44
+ cancelBtn.textContent = 'Cancel'
45
+ cancelBtn.style.background = '#6b7280'
46
+
47
+ const deleteBtn = document.createElement('button')
48
+ deleteBtn.textContent = 'Delete'
49
+ deleteBtn.style.background = '#ef4444'
50
+
51
+ const cleanup = (result: boolean) => {
52
+ overlay.remove()
53
+ resolve(result)
54
+ }
55
+
56
+ cancelBtn.onclick = () => cleanup(false)
57
+ deleteBtn.onclick = () => cleanup(true)
58
+ overlay.addEventListener('click', (event) => {
59
+ if (event.target === overlay) {
60
+ cleanup(false)
61
+ }
62
+ })
63
+
64
+ actions.appendChild(cancelBtn)
65
+ actions.appendChild(deleteBtn)
66
+ modal.appendChild(title)
67
+ modal.appendChild(message)
68
+ modal.appendChild(actions)
69
+ overlay.appendChild(modal)
70
+ document.body.appendChild(overlay)
71
+ })
72
+ }
73
+
74
+ export async function deleteDeviceFromConfig(deviceId: string, deviceName: string): Promise<void> {
75
+ uiLog.info('Delete button clicked for device:', deviceId, deviceName)
76
+
77
+ const confirmed = await confirmDeleteDialog(deviceName || deviceId)
78
+ if (!confirmed) {
79
+ uiLog.info('Delete cancelled by user')
80
+ return
81
+ }
82
+
83
+ try {
84
+ showBusyUi()
85
+ uiLog.info('Sending delete request for deviceId:', deviceId)
86
+ const resp = await apiDeleteDevice(deviceId)
87
+ uiLog.info('Delete response:', resp)
88
+
89
+ if (!resp || resp.success === false) {
90
+ throw new Error(resp?.data?.message || 'Failed to delete device')
91
+ }
92
+
93
+ uiLog.info('Syncing parent config from disk...')
94
+ const synced = await syncParentPluginConfigFromDisk(true)
95
+ if (!synced) {
96
+ toastWarning('Device deleted, but configuration sync failed')
97
+ }
98
+
99
+ uiLog.info('Refreshing device list...')
100
+ // Refresh device list
101
+ const list = await fetchDevices()
102
+ uiLog.info('Rendering devices:', list.length)
103
+ renderDeviceList(list)
104
+
105
+ uiLog.info('✓ Device deleted successfully')
106
+ toastSuccess(`Device "${deviceName || deviceId}" deleted successfully`)
107
+ } catch (e) {
108
+ uiLog.error('Delete error:', e)
109
+ toastError(e instanceof Error ? e.message : 'Failed to delete device')
110
+ } finally {
111
+ hideBusyUi()
112
+ }
113
+ }
@@ -0,0 +1,8 @@
1
+ export declare function addDeviceToConfig(device: any, options?: {
2
+ refresh?: boolean;
3
+ showStatus?: boolean;
4
+ }): Promise<{
5
+ added: boolean;
6
+ }>;
7
+ export declare function loadConfiguredDevices(): Promise<void>;
8
+ //# sourceMappingURL=devices.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devices.d.ts","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/devices.ts"],"names":[],"mappings":"AAMA,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CAkE3I;AAED,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAG3D"}
@@ -0,0 +1,71 @@
1
+ import { addDevice, fetchDevices, syncParentPluginConfigFromDisk } from './api.js';
2
+ import { uiLog } from './logger.js';
3
+ import { hideBusyUi, showBusyUi } from './modal.js';
4
+ import { renderDeviceList } from './render.js';
5
+ import { toastError, toastInfo, toastSuccess, toastWarning } from './toast.js';
6
+ export async function addDeviceToConfig(device, options = {}) {
7
+ const { refresh = true, showStatus = true } = options;
8
+ try {
9
+ showBusyUi();
10
+ uiLog.info('Adding device to config:', device);
11
+ const resp = await addDevice(device.id, device.name, device.type);
12
+ uiLog.info('Add device response:', resp);
13
+ if (!resp || resp.success === false) {
14
+ throw new Error(resp?.data?.message || 'Failed to add device');
15
+ }
16
+ const alreadyExists = !!resp?.alreadyExists;
17
+ const message = resp?.message
18
+ || (alreadyExists
19
+ ? `Device "${device.name}" already in config`
20
+ : `Device "${device.name}" added successfully!`);
21
+ if (alreadyExists) {
22
+ toastInfo(message);
23
+ }
24
+ else {
25
+ toastSuccess(message);
26
+ }
27
+ if (showStatus) {
28
+ const status = document.getElementById('discoverStatus');
29
+ if (status) {
30
+ status.textContent = (alreadyExists ? '• ' : '✓ ') + message;
31
+ status.classList.remove('error');
32
+ status.classList.add('success-msg');
33
+ // Sync parent Homebridge form cache and auto-save to prevent cache overwrite
34
+ if (!alreadyExists) {
35
+ const synced = await syncParentPluginConfigFromDisk(true);
36
+ status.textContent += synced
37
+ ? ' - Config saved automatically.'
38
+ : ' - Warning: config may not persist until you close/reopen settings.';
39
+ if (synced) {
40
+ toastSuccess('Configuration synced and saved automatically');
41
+ }
42
+ else {
43
+ toastWarning('Configuration sync failed; close and reopen settings before Save');
44
+ }
45
+ }
46
+ }
47
+ }
48
+ if (refresh) {
49
+ await loadConfiguredDevices();
50
+ }
51
+ return { added: !alreadyExists };
52
+ }
53
+ catch (e) {
54
+ uiLog.error('Add device error:', e);
55
+ const status = document.getElementById('discoverStatus');
56
+ if (status) {
57
+ status.textContent = `✗ Error: ${e instanceof Error ? e.message : 'Failed to add device'}`;
58
+ status.classList.add('error');
59
+ }
60
+ toastError(e instanceof Error ? e.message : 'Failed to add device');
61
+ return { added: false };
62
+ }
63
+ finally {
64
+ hideBusyUi();
65
+ }
66
+ }
67
+ export async function loadConfiguredDevices() {
68
+ const list = await fetchDevices();
69
+ renderDeviceList(list);
70
+ }
71
+ //# sourceMappingURL=devices.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devices.js","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/devices.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ import { addDevice, fetchDevices, syncParentPluginConfigFromDisk } from './api.js'
2
+ import { uiLog } from './logger.js'
3
+ import { hideBusyUi, showBusyUi } from './modal.js'
4
+ import { renderDeviceList } from './render.js'
5
+ import { toastError, toastInfo, toastSuccess, toastWarning } from './toast.js'
6
+
7
+ export async function addDeviceToConfig(device: any, options: { refresh?: boolean, showStatus?: boolean } = {}): Promise<{ added: boolean }> {
8
+ const { refresh = true, showStatus = true } = options
9
+
10
+ try {
11
+ showBusyUi()
12
+ uiLog.info('Adding device to config:', device)
13
+ const resp = await addDevice(device.id, device.name, device.type)
14
+ uiLog.info('Add device response:', resp)
15
+
16
+ if (!resp || resp.success === false) {
17
+ throw new Error(resp?.data?.message || 'Failed to add device')
18
+ }
19
+
20
+ const alreadyExists = !!resp?.alreadyExists
21
+ const message
22
+ = resp?.message
23
+ || (alreadyExists
24
+ ? `Device "${device.name}" already in config`
25
+ : `Device "${device.name}" added successfully!`)
26
+
27
+ if (alreadyExists) {
28
+ toastInfo(message)
29
+ } else {
30
+ toastSuccess(message)
31
+ }
32
+
33
+ if (showStatus) {
34
+ const status = document.getElementById('discoverStatus')
35
+ if (status) {
36
+ status.textContent = (alreadyExists ? '• ' : '✓ ') + message
37
+ status.classList.remove('error')
38
+ status.classList.add('success-msg')
39
+
40
+ // Sync parent Homebridge form cache and auto-save to prevent cache overwrite
41
+ if (!alreadyExists) {
42
+ const synced = await syncParentPluginConfigFromDisk(true)
43
+ status.textContent += synced
44
+ ? ' - Config saved automatically.'
45
+ : ' - Warning: config may not persist until you close/reopen settings.'
46
+
47
+ if (synced) {
48
+ toastSuccess('Configuration synced and saved automatically')
49
+ } else {
50
+ toastWarning('Configuration sync failed; close and reopen settings before Save')
51
+ }
52
+ }
53
+ }
54
+ }
55
+
56
+ if (refresh) {
57
+ await loadConfiguredDevices()
58
+ }
59
+
60
+ return { added: !alreadyExists }
61
+ } catch (e) {
62
+ uiLog.error('Add device error:', e)
63
+ const status = document.getElementById('discoverStatus')
64
+ if (status) {
65
+ status.textContent = `✗ Error: ${e instanceof Error ? e.message : 'Failed to add device'}`
66
+ status.classList.add('error')
67
+ }
68
+ toastError(e instanceof Error ? e.message : 'Failed to add device')
69
+ return { added: false }
70
+ } finally {
71
+ hideBusyUi()
72
+ }
73
+ }
74
+
75
+ export async function loadConfiguredDevices(): Promise<void> {
76
+ const list = await fetchDevices()
77
+ renderDeviceList(list)
78
+ }
@@ -0,0 +1,3 @@
1
+ export declare function discoverDevices(): Promise<void>;
2
+ export declare function addDeviceToConfig(device: any): Promise<void>;
3
+ //# sourceMappingURL=discovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/discovery.ts"],"names":[],"mappings":"AAYA,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAyGrD;AAED,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAGlE"}
@@ -0,0 +1,107 @@
1
+ import { addDevicesInBulk, discoverDevices as apiDiscoverDevices, syncParentPluginConfigFromDisk, } from './api.js';
2
+ import { loadConfiguredDevices } from './devices.js';
3
+ import { uiLog } from './logger.js';
4
+ import { hideBusyUi, showBusyUi } from './modal.js';
5
+ import { renderDiscoveredDevices } from './render.js';
6
+ import { toastError, toastInfo, toastSuccess, toastWarning } from './toast.js';
7
+ export async function discoverDevices() {
8
+ const btn = document.getElementById('discoverBtn');
9
+ const status = document.getElementById('discoverStatus');
10
+ const list = document.getElementById('discoveredList');
11
+ const autoAddAll = document.getElementById('autoAddAllCheckbox')?.checked;
12
+ if (!btn || !status || !list) {
13
+ return;
14
+ }
15
+ try {
16
+ showBusyUi();
17
+ btn.disabled = true;
18
+ btn.textContent = '🔍 Discovering...';
19
+ status.textContent = 'Searching SwitchBot account...';
20
+ status.classList.remove('error');
21
+ const devicesRaw = await apiDiscoverDevices();
22
+ uiLog.info('Discover response:', devicesRaw);
23
+ // Deduplicate by device ID
24
+ const devices = devicesRaw.filter((d, index, arr) => !!d?.id && arr.findIndex(x => x?.id === d.id) === index);
25
+ if (!devices.length) {
26
+ status.textContent = 'No devices found in your SwitchBot account';
27
+ toastInfo('No devices found in your SwitchBot account');
28
+ list.style.display = 'none';
29
+ return;
30
+ }
31
+ // If auto-add is enabled, add all devices immediately using bulk endpoint
32
+ if (autoAddAll) {
33
+ status.textContent = `Auto-adding ${devices.length} device(s)...`;
34
+ try {
35
+ const bulkResult = await addDevicesInBulk(devices.map(d => ({
36
+ deviceId: d.id,
37
+ name: d.name,
38
+ type: d.type,
39
+ })));
40
+ uiLog.info('Bulk add response:', bulkResult);
41
+ if (!bulkResult || bulkResult.success === false) {
42
+ throw new Error(bulkResult?.data?.message || 'Bulk add failed');
43
+ }
44
+ const addedCount = bulkResult?.addedCount
45
+ ?? bulkResult?.data?.addedCount
46
+ ?? 0;
47
+ const skippedCount = bulkResult?.skippedCount
48
+ ?? bulkResult?.data?.skippedCount
49
+ ?? 0;
50
+ status.textContent = `✓ Added ${addedCount} device(s)${skippedCount > 0 ? ` (${skippedCount} skipped)` : ''}`;
51
+ if (addedCount > 0) {
52
+ toastSuccess(`Added ${addedCount} device(s)${skippedCount > 0 ? ` (${skippedCount} skipped)` : ''}`);
53
+ }
54
+ else if (skippedCount > 0) {
55
+ toastWarning(`No new devices were added (${skippedCount} skipped)`);
56
+ }
57
+ status.classList.remove('error');
58
+ list.style.display = 'none';
59
+ // Sync parent Homebridge form cache and auto-save to prevent cache overwrite
60
+ // Run sync when discovery returned devices (even if backend response shape changes)
61
+ if (devices.length > 0) {
62
+ const synced = await syncParentPluginConfigFromDisk(true);
63
+ status.textContent += synced
64
+ ? ' - Config saved automatically.'
65
+ : ' - Warning: config may not persist until you close/reopen settings.';
66
+ if (synced) {
67
+ toastSuccess('Configuration synced and saved automatically');
68
+ }
69
+ else {
70
+ toastWarning('Configuration sync failed; close and reopen settings before Save');
71
+ }
72
+ }
73
+ }
74
+ catch (e) {
75
+ uiLog.error('Bulk add error:', e);
76
+ status.textContent = `✗ Error: ${e instanceof Error ? e.message : 'Failed to add devices'}`;
77
+ status.classList.add('error');
78
+ toastError(e instanceof Error ? e.message : 'Failed to add devices');
79
+ }
80
+ // Refresh the configured devices list
81
+ await loadConfiguredDevices();
82
+ return;
83
+ }
84
+ status.textContent = `Found ${devices.length} device(s)`;
85
+ const ul = await renderDiscoveredDevices(devices);
86
+ document.getElementById('discoveredDevices')?.replaceWith(ul);
87
+ ul.id = 'discoveredDevices';
88
+ list.style.display = 'block';
89
+ }
90
+ catch (e) {
91
+ uiLog.error('Discovery error:', e);
92
+ status.textContent = `Error: ${e instanceof Error ? e.message : 'Discovery failed'}`;
93
+ status.classList.add('error');
94
+ toastError(e instanceof Error ? e.message : 'Discovery failed');
95
+ list.style.display = 'none';
96
+ }
97
+ finally {
98
+ hideBusyUi();
99
+ btn.disabled = false;
100
+ btn.textContent = '🔍 Discover Devices';
101
+ }
102
+ }
103
+ export async function addDeviceToConfig(device) {
104
+ const { addDeviceToConfig: addDevice } = await import('./devices.js');
105
+ await addDevice(device);
106
+ }
107
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/discovery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,123 @@
1
+ import {
2
+ addDevicesInBulk,
3
+ discoverDevices as apiDiscoverDevices,
4
+ fetchDevices,
5
+ syncParentPluginConfigFromDisk,
6
+ } from './api.js'
7
+ import { loadConfiguredDevices } from './devices.js'
8
+ import { uiLog } from './logger.js'
9
+ import { hideBusyUi, showBusyUi } from './modal.js'
10
+ import { renderDiscoveredDevices } from './render.js'
11
+ import { toastError, toastInfo, toastSuccess, toastWarning } from './toast.js'
12
+
13
+ export async function discoverDevices(): Promise<void> {
14
+ const btn = document.getElementById('discoverBtn') as HTMLButtonElement
15
+ const status = document.getElementById('discoverStatus')
16
+ const list = document.getElementById('discoveredList')
17
+ const autoAddAll = (document.getElementById('autoAddAllCheckbox') as HTMLInputElement)?.checked
18
+
19
+ if (!btn || !status || !list) { return }
20
+
21
+ try {
22
+ showBusyUi()
23
+ btn.disabled = true
24
+ btn.textContent = '🔍 Discovering...'
25
+ status.textContent = 'Searching SwitchBot account...'
26
+ status.classList.remove('error')
27
+
28
+ const devicesRaw = await apiDiscoverDevices()
29
+ uiLog.info('Discover response:', devicesRaw)
30
+
31
+ // Deduplicate by device ID
32
+ const devices = devicesRaw.filter((d, index, arr) => !!d?.id && arr.findIndex(x => x?.id === d.id) === index)
33
+
34
+ if (!devices.length) {
35
+ status.textContent = 'No devices found in your SwitchBot account'
36
+ toastInfo('No devices found in your SwitchBot account')
37
+ list.style.display = 'none'
38
+ return
39
+ }
40
+
41
+ // If auto-add is enabled, add all devices immediately using bulk endpoint
42
+ if (autoAddAll) {
43
+ status.textContent = `Auto-adding ${devices.length} device(s)...`
44
+
45
+ try {
46
+ const bulkResult = await addDevicesInBulk(
47
+ devices.map(d => ({
48
+ deviceId: d.id,
49
+ name: d.name,
50
+ type: d.type,
51
+ })),
52
+ )
53
+ uiLog.info('Bulk add response:', bulkResult)
54
+
55
+ if (!bulkResult || bulkResult.success === false) {
56
+ throw new Error(bulkResult?.data?.message || 'Bulk add failed')
57
+ }
58
+
59
+ const addedCount
60
+ = bulkResult?.addedCount
61
+ ?? bulkResult?.data?.addedCount
62
+ ?? 0
63
+ const skippedCount
64
+ = bulkResult?.skippedCount
65
+ ?? bulkResult?.data?.skippedCount
66
+ ?? 0
67
+ status.textContent = `✓ Added ${addedCount} device(s)${skippedCount > 0 ? ` (${skippedCount} skipped)` : ''}`
68
+ if (addedCount > 0) {
69
+ toastSuccess(`Added ${addedCount} device(s)${skippedCount > 0 ? ` (${skippedCount} skipped)` : ''}`)
70
+ } else if (skippedCount > 0) {
71
+ toastWarning(`No new devices were added (${skippedCount} skipped)`)
72
+ }
73
+ status.classList.remove('error')
74
+ list.style.display = 'none'
75
+
76
+ // Sync parent Homebridge form cache and auto-save to prevent cache overwrite
77
+ // Run sync when discovery returned devices (even if backend response shape changes)
78
+ if (devices.length > 0) {
79
+ const synced = await syncParentPluginConfigFromDisk(true)
80
+ status.textContent += synced
81
+ ? ' - Config saved automatically.'
82
+ : ' - Warning: config may not persist until you close/reopen settings.'
83
+
84
+ if (synced) {
85
+ toastSuccess('Configuration synced and saved automatically')
86
+ } else {
87
+ toastWarning('Configuration sync failed; close and reopen settings before Save')
88
+ }
89
+ }
90
+ } catch (e) {
91
+ uiLog.error('Bulk add error:', e)
92
+ status.textContent = `✗ Error: ${e instanceof Error ? e.message : 'Failed to add devices'}`
93
+ status.classList.add('error')
94
+ toastError(e instanceof Error ? e.message : 'Failed to add devices')
95
+ }
96
+
97
+ // Refresh the configured devices list
98
+ await loadConfiguredDevices()
99
+ return
100
+ }
101
+
102
+ status.textContent = `Found ${devices.length} device(s)`
103
+ const ul = await renderDiscoveredDevices(devices)
104
+ document.getElementById('discoveredDevices')?.replaceWith(ul)
105
+ ul.id = 'discoveredDevices'
106
+ list.style.display = 'block'
107
+ } catch (e) {
108
+ uiLog.error('Discovery error:', e)
109
+ status.textContent = `Error: ${e instanceof Error ? e.message : 'Discovery failed'}`
110
+ status.classList.add('error')
111
+ toastError(e instanceof Error ? e.message : 'Discovery failed')
112
+ list.style.display = 'none'
113
+ } finally {
114
+ hideBusyUi()
115
+ btn.disabled = false
116
+ btn.textContent = '🔍 Discover Devices'
117
+ }
118
+ }
119
+
120
+ export async function addDeviceToConfig(device: any): Promise<void> {
121
+ const { addDeviceToConfig: addDevice } = await import('./devices.js')
122
+ await addDevice(device)
123
+ }
@@ -0,0 +1,6 @@
1
+ export declare const uiLog: {
2
+ info: (message: string, ...parameters: any[]) => void;
3
+ warn: (message: string, ...parameters: any[]) => void;
4
+ error: (message: string, ...parameters: any[]) => void;
5
+ };
6
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/logger.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,KAAK;oBACA,MAAM,iBAAiB,GAAG,EAAE,KAAG,IAAI;oBAGnC,MAAM,iBAAiB,GAAG,EAAE,KAAG,IAAI;qBAGlC,MAAM,iBAAiB,GAAG,EAAE,KAAG,IAAI;CAGrD,CAAA"}
@@ -0,0 +1,12 @@
1
+ export const uiLog = {
2
+ info: (message, ...parameters) => {
3
+ console.warn('[SwitchBot UI]', message, ...parameters);
4
+ },
5
+ warn: (message, ...parameters) => {
6
+ console.warn('[SwitchBot UI]', message, ...parameters);
7
+ },
8
+ error: (message, ...parameters) => {
9
+ console.error('[SwitchBot UI]', message, ...parameters);
10
+ },
11
+ };
12
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/logger.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,IAAI,EAAE,CAAC,OAAe,EAAE,GAAG,UAAiB,EAAQ,EAAE;QACpD,OAAO,CAAC,IAAI,CAAC,gBAAgB,EAAE,OAAO,EAAE,GAAG,UAAU,CAAC,CAAA;IACxD,CAAC;IACD,IAAI,EAAE,CAAC,OAAe,EAAE,GAAG,UAAiB,EAAQ,EAAE;QACpD,OAAO,CAAC,IAAI,CAAC,gBAAgB,EAAE,OAAO,EAAE,GAAG,UAAU,CAAC,CAAA;IACxD,CAAC;IACD,KAAK,EAAE,CAAC,OAAe,EAAE,GAAG,UAAiB,EAAQ,EAAE;QACrD,OAAO,CAAC,KAAK,CAAC,gBAAgB,EAAE,OAAO,EAAE,GAAG,UAAU,CAAC,CAAA;IACzD,CAAC;CACF,CAAA"}
@@ -0,0 +1,11 @@
1
+ export const uiLog = {
2
+ info: (message: string, ...parameters: any[]): void => {
3
+ console.warn('[SwitchBot UI]', message, ...parameters)
4
+ },
5
+ warn: (message: string, ...parameters: any[]): void => {
6
+ console.warn('[SwitchBot UI]', message, ...parameters)
7
+ },
8
+ error: (message: string, ...parameters: any[]): void => {
9
+ console.error('[SwitchBot UI]', message, ...parameters)
10
+ },
11
+ }
@@ -0,0 +1,5 @@
1
+ import './types.js';
2
+ export declare function showBusyUi(): void;
3
+ export declare function hideBusyUi(): void;
4
+ export declare function closeSettingsModal(): void;
5
+ //# sourceMappingURL=modal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/modal.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,CAAA;AAenB,wBAAgB,UAAU,IAAI,IAAI,CAGjC;AAED,wBAAgB,UAAU,IAAI,IAAI,CAGjC;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
@@ -0,0 +1,26 @@
1
+ import './types.js';
2
+ import { uiLog } from './logger.js';
3
+ function callUiMethod(name) {
4
+ try {
5
+ const value = homebridge?.[name];
6
+ if (typeof value === 'function') {
7
+ const fn = value;
8
+ fn();
9
+ }
10
+ }
11
+ catch (e) {
12
+ uiLog.warn(`Homebridge UI method ${String(name)} failed:`, e);
13
+ }
14
+ }
15
+ export function showBusyUi() {
16
+ callUiMethod('disableSaveButton');
17
+ callUiMethod('showSpinner');
18
+ }
19
+ export function hideBusyUi() {
20
+ callUiMethod('hideSpinner');
21
+ callUiMethod('enableSaveButton');
22
+ }
23
+ export function closeSettingsModal() {
24
+ callUiMethod('closeSettings');
25
+ }
26
+ //# sourceMappingURL=modal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modal.js","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/modal.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,CAAA;AACnB,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAEnC,SAAS,YAAY,CAAC,IAAiC;IACrD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC,IAAI,CAAC,CAAA;QAChC,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE,CAAC;YAChC,MAAM,EAAE,GAAG,KAAmB,CAAA;YAC9B,EAAE,EAAE,CAAA;QACN,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,KAAK,CAAC,IAAI,CAAC,wBAAwB,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAA;IAC/D,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,YAAY,CAAC,mBAAmB,CAAC,CAAA;IACjC,YAAY,CAAC,aAAa,CAAC,CAAA;AAC7B,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,YAAY,CAAC,aAAa,CAAC,CAAA;IAC3B,YAAY,CAAC,kBAAkB,CAAC,CAAA;AAClC,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,YAAY,CAAC,eAAe,CAAC,CAAA;AAC/B,CAAC"}
@@ -0,0 +1,28 @@
1
+ import './types.js'
2
+ import { uiLog } from './logger.js'
3
+
4
+ function callUiMethod(name: keyof HomebridgePluginUiAPI): void {
5
+ try {
6
+ const value = homebridge?.[name]
7
+ if (typeof value === 'function') {
8
+ const fn = value as () => void
9
+ fn()
10
+ }
11
+ } catch (e) {
12
+ uiLog.warn(`Homebridge UI method ${String(name)} failed:`, e)
13
+ }
14
+ }
15
+
16
+ export function showBusyUi(): void {
17
+ callUiMethod('disableSaveButton')
18
+ callUiMethod('showSpinner')
19
+ }
20
+
21
+ export function hideBusyUi(): void {
22
+ callUiMethod('hideSpinner')
23
+ callUiMethod('enableSaveButton')
24
+ }
25
+
26
+ export function closeSettingsModal(): void {
27
+ callUiMethod('closeSettings')
28
+ }
@@ -0,0 +1,2 @@
1
+ export declare function editDevice(device: any): Promise<void>;
2
+ //# sourceMappingURL=modals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modals.d.ts","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/modals.ts"],"names":[],"mappings":"AAGA,wBAAsB,UAAU,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAqJ3D"}