@switchbot/homebridge-switchbot 5.1.0-beta.1 → 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 (76) 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} +2 -2
  12. package/dist/Platform.Matter.d.ts.map +1 -0
  13. package/dist/{SwitchBotMatterPlatform.js → Platform.Matter.js} +1 -1
  14. package/dist/Platform.Matter.js.map +1 -0
  15. package/dist/homebridge-ui/public/css/styles.css +1 -1
  16. package/dist/homebridge-ui/public/index.html +38 -6
  17. package/dist/homebridge-ui/public/js/advanced-settings.d.ts.map +1 -1
  18. package/dist/homebridge-ui/public/js/advanced-settings.js +4 -3
  19. package/dist/homebridge-ui/public/js/advanced-settings.js.map +1 -1
  20. package/dist/homebridge-ui/public/js/advanced-settings.ts +4 -3
  21. package/dist/homebridge-ui/public/js/api.d.ts +1 -0
  22. package/dist/homebridge-ui/public/js/api.d.ts.map +1 -1
  23. package/dist/homebridge-ui/public/js/api.js +14 -1
  24. package/dist/homebridge-ui/public/js/api.js.map +1 -1
  25. package/dist/homebridge-ui/public/js/api.ts +16 -1
  26. package/dist/homebridge-ui/public/js/app.js +85 -76
  27. package/dist/homebridge-ui/public/js/app.js.map +2 -2
  28. package/dist/homebridge-ui/public/js/app.ts +25 -0
  29. package/dist/homebridge-ui/public/js/credentials.d.ts.map +1 -1
  30. package/dist/homebridge-ui/public/js/credentials.js +11 -5
  31. package/dist/homebridge-ui/public/js/credentials.js.map +1 -1
  32. package/dist/homebridge-ui/public/js/credentials.ts +11 -5
  33. package/dist/homebridge-ui/public/js/discovery.d.ts.map +1 -1
  34. package/dist/homebridge-ui/public/js/discovery.js +0 -36
  35. package/dist/homebridge-ui/public/js/discovery.js.map +1 -1
  36. package/dist/homebridge-ui/public/js/discovery.ts +0 -37
  37. package/dist/homebridge-ui/public/js/legacy-view.d.ts +3 -0
  38. package/dist/homebridge-ui/public/js/legacy-view.d.ts.map +1 -0
  39. package/dist/homebridge-ui/public/js/legacy-view.js +152 -0
  40. package/dist/homebridge-ui/public/js/legacy-view.js.map +1 -0
  41. package/dist/homebridge-ui/public/js/legacy-view.ts +107 -0
  42. package/dist/homebridge-ui/public/js/render.d.ts.map +1 -1
  43. package/dist/homebridge-ui/public/js/render.js +38 -31
  44. package/dist/homebridge-ui/public/js/render.js.map +1 -1
  45. package/dist/homebridge-ui/public/js/render.ts +39 -34
  46. package/dist/homebridge-ui/public/js/toast.d.ts.map +1 -1
  47. package/dist/homebridge-ui/public/js/toast.js +2 -3
  48. package/dist/homebridge-ui/public/js/toast.js.map +1 -1
  49. package/dist/homebridge-ui/public/js/toast.ts +2 -3
  50. package/dist/index.js +2 -2
  51. package/dist/index.js.map +1 -1
  52. package/docs/variables/default.html +1 -1
  53. package/package.json +9 -15
  54. package/src/{SwitchBotHAPPlatform.ts → Platform.HAP.ts} +5 -2
  55. package/src/{SwitchBotMatterPlatform.ts → Platform.Matter.ts} +2 -1
  56. package/src/homebridge-ui/public/css/styles.css +1 -1
  57. package/src/homebridge-ui/public/index.html +36 -4
  58. package/src/homebridge-ui/public/js/advanced-settings.ts +4 -3
  59. package/src/homebridge-ui/public/js/api.ts +16 -1
  60. package/src/homebridge-ui/public/js/app.ts +25 -0
  61. package/src/homebridge-ui/public/js/credentials.ts +11 -5
  62. package/src/homebridge-ui/public/js/discovery.ts +0 -37
  63. package/src/homebridge-ui/public/js/legacy-view.js +152 -0
  64. package/src/homebridge-ui/public/js/legacy-view.ts +107 -0
  65. package/src/homebridge-ui/public/js/render.ts +39 -34
  66. package/src/homebridge-ui/public/js/toast.ts +2 -3
  67. package/src/index.ts +2 -2
  68. package/test/platform/accessory-restore.spec.ts +2 -2
  69. package/test/platform/matter-childbridge.spec.ts +1 -1
  70. package/test/platform/matter-integration.spec.ts +1 -1
  71. package/test/platform/platform-edge.spec.ts +2 -2
  72. package/test/platform/platform.integration.spec.ts +1 -1
  73. package/dist/SwitchBotHAPPlatform.d.ts.map +0 -1
  74. package/dist/SwitchBotHAPPlatform.js.map +0 -1
  75. package/dist/SwitchBotMatterPlatform.d.ts.map +0 -1
  76. package/dist/SwitchBotMatterPlatform.js.map +0 -1
@@ -5,6 +5,22 @@ const SPACES_REGEX = /\s/g
5
5
  const CAMELCASE_REGEX = /([A-Z])/g
6
6
  const FIRST_CHAR_REGEX = /^./
7
7
 
8
+ async function copyTextWithFallback(text: string): Promise<void> {
9
+ try {
10
+ await navigator.clipboard.writeText(text)
11
+ } catch {
12
+ const textarea = document.createElement('textarea')
13
+ textarea.value = text
14
+ textarea.setAttribute('readonly', '')
15
+ textarea.style.cssText = 'position:fixed;left:-9999px;opacity:0;pointer-events:none'
16
+ document.body.appendChild(textarea)
17
+ textarea.select()
18
+ textarea.setSelectionRange(0, textarea.value.length)
19
+ document.execCommand('copy')
20
+ textarea.remove()
21
+ }
22
+ }
23
+
8
24
  /**
9
25
  * Get RSSI signal quality level and color based on dBm value
10
26
  * @param rssi Signal strength in dBm (typically -30 to -90)
@@ -119,7 +135,6 @@ export function renderSignalQualityBadge(rssi: number | undefined): HTMLElement
119
135
  border-radius: 3px;
120
136
  font-size: 10px;
121
137
  font-weight: 600;
122
- margin-left: 8px;
123
138
  `
124
139
  badge.title = quality.description
125
140
 
@@ -927,6 +942,7 @@ export function renderDeviceList(list: any[]): void {
927
942
 
928
943
  status.textContent = `Found ${list.length} device(s)`
929
944
  ul.classList.add('device-grid')
945
+ ul.style.padding = '0'
930
946
  ul.innerHTML = ''
931
947
 
932
948
  // Show remove all button when devices exist
@@ -941,6 +957,7 @@ export function renderDeviceList(list: any[]): void {
941
957
  li.style.display = 'flex'
942
958
  li.style.flexDirection = 'column'
943
959
  li.style.alignItems = 'stretch'
960
+ li.style.padding = '5px 8px'
944
961
  li.style.marginBottom = '0'
945
962
 
946
963
  const info = document.createElement('div')
@@ -950,14 +967,15 @@ export function renderDeviceList(list: any[]): void {
950
967
 
951
968
  const nameContainer = document.createElement('div')
952
969
  nameContainer.style.display = 'flex'
953
- nameContainer.style.flexDirection = 'column'
954
- nameContainer.style.alignItems = 'flex-start'
955
- nameContainer.style.gap = '0'
970
+ nameContainer.style.alignItems = 'center'
971
+ nameContainer.style.marginBottom = '0'
972
+ nameContainer.style.flexWrap = 'wrap'
973
+ nameContainer.style.gap = '4px'
956
974
 
957
975
  const name = document.createElement('div')
958
976
  name.style.fontWeight = '500'
959
977
  name.style.fontSize = '13px'
960
- name.textContent = d.name || d.id
978
+ name.textContent = d.configDeviceName || d.name || d.id
961
979
 
962
980
  const expandedDetails = document.createElement('div')
963
981
  expandedDetails.style.display = 'none'
@@ -978,25 +996,8 @@ export function renderDeviceList(list: any[]): void {
978
996
  expandBtn.style.transform = isHidden ? 'rotate(180deg)' : 'rotate(0deg)'
979
997
  }
980
998
 
981
- const code = document.createElement('code')
982
- code.textContent = d.id
983
- code.style.fontSize = '10px'
984
- code.style.opacity = '0.75'
985
- code.style.marginLeft = '0'
986
- code.style.whiteSpace = 'normal'
987
- code.style.overflowWrap = 'anywhere'
988
- code.style.wordBreak = 'break-word'
989
- code.style.maxWidth = '100%'
990
-
991
- const headerRow = document.createElement('div')
992
- headerRow.style.display = 'inline-flex'
993
- headerRow.style.alignItems = 'center'
994
- headerRow.style.gap = '4px'
995
- headerRow.appendChild(name)
996
- headerRow.appendChild(expandBtn)
997
-
998
- nameContainer.appendChild(headerRow)
999
- nameContainer.appendChild(code)
999
+ nameContainer.appendChild(name)
1000
+ nameContainer.appendChild(expandBtn)
1000
1001
 
1001
1002
  // Add signal strength visualization if RSSI is available
1002
1003
  if (d.rssi !== undefined && d.rssi !== null && d.rssi !== 0) {
@@ -1005,15 +1006,17 @@ export function renderDeviceList(list: any[]): void {
1005
1006
  }
1006
1007
 
1007
1008
  const meta = document.createElement('div')
1008
- meta.style.opacity = '0.75'
1009
- meta.style.marginTop = '0'
1010
- meta.style.fontSize = '11px'
1011
-
1012
- const typeText = d.type ? `type: ${d.type}` : ''
1013
- const connText = d.connectionPreference ? `conn: ${d.connectionPreference}` : ''
1009
+ meta.style.opacity = '0.7'
1010
+ meta.style.fontSize = '10px'
1011
+ meta.style.fontFamily = 'monospace'
1012
+
1013
+ const deviceIdentifier = d.deviceId || d.id
1014
+ const id = `ID: ${deviceIdentifier}`
1015
+ const typeText = d.configDeviceType || d.type ? `Type: ${d.configDeviceType || d.type}` : ''
1016
+ const connText = d.connectionPreference ? `Conn: ${d.connectionPreference}` : ''
1014
1017
  const extText = d.externalPublishProtocol && d.externalPublishProtocol !== 'none' ? `external: ${d.externalPublishProtocol}` : ''
1015
- const roomText = d.room ? `room: ${d.room}` : ''
1016
- meta.textContent = [typeText, connText, extText, roomText].filter(Boolean).join(' | ')
1018
+ const roomText = d.room ? `Room: ${d.room}` : ''
1019
+ meta.textContent = [id, typeText, connText, extText, roomText].filter(Boolean).join(' | ')
1017
1020
 
1018
1021
  info.appendChild(nameContainer)
1019
1022
  info.appendChild(meta)
@@ -1044,7 +1047,9 @@ export function renderDeviceList(list: any[]): void {
1044
1047
  copyBtn.style.fontSize = '11px'
1045
1048
  copyBtn.addEventListener('click', async () => {
1046
1049
  try {
1047
- await navigator.clipboard.writeText(d.id)
1050
+ if (deviceIdentifier) {
1051
+ await copyTextWithFallback(deviceIdentifier)
1052
+ }
1048
1053
  copyBtn.textContent = 'Copied'
1049
1054
  copyBtn.classList.add('success')
1050
1055
  setTimeout(() => {
@@ -1073,8 +1078,8 @@ export function renderDeviceList(list: any[]): void {
1073
1078
 
1074
1079
  buttons.appendChild(editBtn)
1075
1080
  buttons.appendChild(copyBtn)
1076
- buttons.appendChild(createConnectionTestControls(d))
1077
1081
  buttons.appendChild(deleteBtn)
1082
+ buttons.appendChild(createConnectionTestControls(d))
1078
1083
 
1079
1084
  li.appendChild(info)
1080
1085
  li.appendChild(buttons)
@@ -1 +1 @@
1
- {"version":3,"file":"toast.d.ts","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/toast.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,CAAA;AA6BnB,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAElE;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAEhE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAElE;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAE/D"}
1
+ {"version":3,"file":"toast.d.ts","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/toast.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,CAAA;AA4BnB,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAElE;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAEhE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAElE;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAE/D"}
@@ -5,10 +5,9 @@ function showToast(method, message, title = 'SwitchBot') {
5
5
  // Defensive: check for window and homebridge existence
6
6
  const hb = typeof window !== 'undefined' ? window.homebridge : undefined;
7
7
  const toast = hb && typeof hb.toast === 'object' ? hb.toast : undefined;
8
- const fn = toast && typeof toast[method] === 'function' ? toast[method] : undefined;
9
- if (fn) {
8
+ if (toast && typeof toast[method] === 'function') {
10
9
  try {
11
- fn(message, title);
10
+ toast[method](message, title);
12
11
  return;
13
12
  }
14
13
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"toast.js","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/toast.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,CAAA;AACnB,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAEnC,SAAS,SAAS,CAChB,MAAgD,EAChD,OAAe,EACf,KAAK,GAAG,WAAW;IAEnB,IAAI,CAAC;QACH,uDAAuD;QACvD,MAAM,EAAE,GAAG,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAE,MAAc,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAA;QACjF,MAAM,KAAK,GAAG,EAAE,IAAI,OAAO,EAAE,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;QACvE,MAAM,EAAE,GAAG,KAAK,IAAI,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QACnF,IAAI,EAAE,EAAE,CAAC;YACP,IAAI,CAAC;gBACH,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;gBAClB,OAAM;YACR,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,KAAK,CAAC,IAAI,CAAC,SAAS,MAAM,SAAS,EAAE,GAAG,CAAC,CAAA;YAC3C,CAAC;QACH,CAAC;QACD,2BAA2B;QAC3B,KAAK,CAAC,IAAI,CAAC,UAAU,MAAM,KAAK,KAAK,MAAM,OAAO,EAAE,CAAC,CAAA;IACvD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,KAAK,CAAC,IAAI,CAAC,SAAS,MAAM,eAAe,EAAE,CAAC,CAAC,CAAA;QAC7C,KAAK,CAAC,IAAI,CAAC,UAAU,MAAM,KAAK,KAAK,MAAM,OAAO,EAAE,CAAC,CAAA;IACvD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,KAAc;IAC1D,SAAS,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;AACtC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,OAAe,EAAE,KAAc;IACxD,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;AACpC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,KAAc;IAC1D,SAAS,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;AACtC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAe,EAAE,KAAc;IACvD,SAAS,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;AACnC,CAAC"}
1
+ {"version":3,"file":"toast.js","sourceRoot":"","sources":["../../../../src/homebridge-ui/public/js/toast.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,CAAA;AACnB,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAEnC,SAAS,SAAS,CAChB,MAAgD,EAChD,OAAe,EACf,KAAK,GAAG,WAAW;IAEnB,IAAI,CAAC;QACH,uDAAuD;QACvD,MAAM,EAAE,GAAG,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAE,MAAc,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAA;QACjF,MAAM,KAAK,GAAG,EAAE,IAAI,OAAO,EAAE,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;QACvE,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,UAAU,EAAE,CAAC;YACjD,IAAI,CAAC;gBACH,KAAK,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;gBAC7B,OAAM;YACR,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,KAAK,CAAC,IAAI,CAAC,SAAS,MAAM,SAAS,EAAE,GAAG,CAAC,CAAA;YAC3C,CAAC;QACH,CAAC;QACD,2BAA2B;QAC3B,KAAK,CAAC,IAAI,CAAC,UAAU,MAAM,KAAK,KAAK,MAAM,OAAO,EAAE,CAAC,CAAA;IACvD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,KAAK,CAAC,IAAI,CAAC,SAAS,MAAM,eAAe,EAAE,CAAC,CAAC,CAAA;QAC7C,KAAK,CAAC,IAAI,CAAC,UAAU,MAAM,KAAK,KAAK,MAAM,OAAO,EAAE,CAAC,CAAA;IACvD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,KAAc;IAC1D,SAAS,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;AACtC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,OAAe,EAAE,KAAc;IACxD,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;AACpC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,KAAc;IAC1D,SAAS,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;AACtC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAe,EAAE,KAAc;IACvD,SAAS,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;AACnC,CAAC"}
@@ -10,10 +10,9 @@ function showToast(
10
10
  // Defensive: check for window and homebridge existence
11
11
  const hb = typeof window !== 'undefined' ? (window as any).homebridge : undefined
12
12
  const toast = hb && typeof hb.toast === 'object' ? hb.toast : undefined
13
- const fn = toast && typeof toast[method] === 'function' ? toast[method] : undefined
14
- if (fn) {
13
+ if (toast && typeof toast[method] === 'function') {
15
14
  try {
16
- fn(message, title)
15
+ toast[method](message, title)
17
16
  return
18
17
  } catch (err) {
19
18
  uiLog.warn(`Toast ${method} threw:`, err)
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
+ import { SwitchBotHAPPlatform } from './Platform.HAP.js';
2
+ import { SwitchBotMatterPlatform } from './Platform.Matter.js';
1
3
  import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
2
- import { SwitchBotHAPPlatform } from './SwitchBotHAPPlatform.js';
3
- import { SwitchBotMatterPlatform } from './SwitchBotMatterPlatform.js';
4
4
  import { createPlatformProxy } from './utils.js';
5
5
  /**
6
6
  * Registers the SwitchBot platform with Homebridge.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAEhD;;;;;;;;;GASG;AACH,eAAe,CAAC,GAAQ,EAAQ,EAAE;IAChC,MAAM,SAAS,GAAG,mBAAmB,CAAC,oBAAoB,EAAE,uBAAuB,CAAC,CAAA;IACpF,GAAG,CAAC,gBAAgB,CAAC,WAAW,EAAE,aAAa,EAAE,SAAgB,CAAC,CAAA;AACpE,CAAC,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAA;AAC9D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAEhD;;;;;;;;;GASG;AACH,eAAe,CAAC,GAAQ,EAAQ,EAAE;IAChC,MAAM,SAAS,GAAG,mBAAmB,CAAC,oBAAoB,EAAE,uBAAuB,CAAC,CAAA;IACpF,GAAG,CAAC,gBAAgB,CAAC,WAAW,EAAE,aAAa,EAAE,SAAgB,CAAC,CAAA;AACpE,CAAC,CAAA"}
@@ -1,3 +1,3 @@
1
1
  <!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>default | @switchbot/homebridge-switchbot</title><meta name="description" content="Documentation for @switchbot/homebridge-switchbot"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">@switchbot/homebridge-switchbot</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">default</a></li></ul><h1>Variable default</h1></div><div class="tsd-signature"><span class="tsd-kind-variable">default</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol">(</span><span class="tsd-kind-parameter">api</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">API</span><span class="tsd-signature-symbol">)</span> <span class="tsd-signature-symbol">=&gt;</span> <span class="tsd-signature-type">void</span></div><div class="tsd-comment tsd-typography"><p>Registers the SwitchBot platform with Homebridge.</p>
2
2
  </div><div class="tsd-type-declaration"><h4>Type Declaration</h4><ul class="tsd-parameters"><li class="tsd-parameter-signature"><ul class="tsd-signatures"><li class="tsd-signature" id="__type"><span class="tsd-signature-symbol">(</span><span class="tsd-kind-parameter">api</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">API</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">void</span></li><li class="tsd-description"><div class="tsd-parameters"><h4 class="tsd-parameters-title">Parameters</h4><ul class="tsd-parameter-list"><li><span><span class="tsd-kind-parameter">api</span>: <span class="tsd-signature-type">API</span></span><div class="tsd-comment tsd-typography"><p>The Homebridge API instance.</p>
3
- </div></li></ul></div><h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">void</span></h4></li></ul></li></ul></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/OpenWonderLabs/homebridge-switchbot/blob/c12da554d58005474bfa08e2e8abd1ed5882b64b/src/index.ts#L22">index.ts:22</a></li></ul></aside></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">@switchbot/homebridge-switchbot</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer></footer><div class="overlay"></div></body></html>
3
+ </div></li></ul></div><h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">void</span></h4></li></ul></li></ul></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/OpenWonderLabs/homebridge-switchbot/blob/232580f7c1a983dfd8db64d7c7cac73920a648db/src/index.ts#L22">index.ts:22</a></li></ul></aside></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">@switchbot/homebridge-switchbot</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer></footer><div class="overlay"></div></body></html>
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@switchbot/homebridge-switchbot",
3
3
  "displayName": "SwitchBot",
4
4
  "type": "module",
5
- "version": "5.1.0-beta.1",
5
+ "version": "5.1.0-beta.10",
6
6
  "description": "The SwitchBot plugin allows you to access your SwitchBot device(s) from HomeKit.",
7
7
  "author": "SwitchBot <support@wondertechlabs.com> (https://github.com/SwitchBot)",
8
8
  "contributors": [
@@ -32,6 +32,7 @@
32
32
  },
33
33
  "keywords": [
34
34
  "homebridge-plugin",
35
+ "homebridge-matter",
35
36
  "switchbot",
36
37
  "lights",
37
38
  "fan",
@@ -92,28 +93,21 @@
92
93
  },
93
94
  "dependencies": {
94
95
  "@homebridge/plugin-ui-utils": "^2.2.3",
95
- "class-validator": "^0.15.1",
96
- "node-switchbot": "^4.0.1"
96
+ "node-switchbot": "^4.0.2"
97
97
  },
98
98
  "devDependencies": {
99
99
  "@antfu/eslint-config": "^8.2.0",
100
- "@types/debug": "^4.1.13",
101
- "@types/fs-extra": "^11.0.4",
102
100
  "@types/node": "^25.6.0",
103
- "@types/semver": "^7.7.1",
104
- "@types/source-map-support": "^0.5.10",
105
- "@vitest/coverage-v8": "^4.1.4",
101
+ "@vitest/coverage-v8": "^4.1.5",
106
102
  "esbuild": "^0.28.0",
107
- "eslint": "^10.2.0",
103
+ "eslint": "^10.3.0",
108
104
  "eslint-plugin-format": "^2.0.1",
109
- "eslint-plugin-html": "^8.1.4",
110
- "eslint-plugin-perfectionist": "^5.8.0",
111
- "homebridge": "^2.0.0-beta.89",
112
- "homebridge-config-ui-x": "^5.21.0",
105
+ "eslint-plugin-perfectionist": "^5.9.0",
106
+ "homebridge": "^2.0.1",
113
107
  "nodemon": "^3.1.14",
114
108
  "shx": "^0.4.0",
115
109
  "typedoc": "^0.28.19",
116
- "typescript": "^6.0.2",
117
- "vitest": "^4.1.4"
110
+ "typescript": "^6.0.3",
111
+ "vitest": "^4.1.5"
118
112
  }
119
113
  }
@@ -1,6 +1,7 @@
1
- import type { SwitchBotPluginConfig } from './settings.js'
2
1
  import type { API, Logger, PlatformConfig } from 'homebridge'
3
2
 
3
+ import type { SwitchBotPluginConfig } from './settings.js'
4
+
4
5
  import { createDevice } from './deviceFactory.js'
5
6
  import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js'
6
7
  import { SwitchBotClient } from './switchbotClient.js'
@@ -214,7 +215,9 @@ export class SwitchBotHAPPlatform {
214
215
  }
215
216
  }
216
217
  if (!accessory) {
217
- accessory = new (this.api as any).platformAccessory(d.name || type, uuid)
218
+ // Use the PlatformAccessory constructor from the HAP API
219
+ const PlatformAccessory = (this.api as any).hap?.PlatformAccessory || (this.api as any).platformAccessory
220
+ accessory = new PlatformAccessory(d.name || type, uuid)
218
221
  try {
219
222
  accessory.context = accessory.context || {}
220
223
  accessory.context.deviceId = d.id
@@ -1,6 +1,7 @@
1
- import type { SwitchBotPluginConfig } from './settings.js'
2
1
  import type { API, Logger, PlatformConfig } from 'homebridge'
3
2
 
3
+ import type { SwitchBotPluginConfig } from './settings.js'
4
+
4
5
  import { createDevice } from './deviceFactory.js'
5
6
  import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js'
6
7
  import { SwitchBotClient } from './switchbotClient.js'
@@ -201,7 +201,7 @@ label {
201
201
  }
202
202
 
203
203
  input {
204
- width: 100%;
204
+ margin-right: 4px;
205
205
  max-width: 400px;
206
206
  padding: 6px 8px;
207
207
  border: 1px solid #d1d5db;
@@ -9,8 +9,34 @@
9
9
  </head>
10
10
 
11
11
  <body>
12
+
12
13
  <h1>🤖 SwitchBot Configuration</h1>
13
14
 
15
+ <!-- ...existing code... -->
16
+
17
+ <!-- Centered legacy view button at the bottom -->
18
+ <div class="centered-toggle-btn">
19
+ <button id="legacyViewBtn">Legacy View</button>
20
+ <button id="dynamicViewBtn" style="display: none;">Dynamic View</button>
21
+ </div>
22
+ <style>
23
+ .centered-toggle-btn {
24
+ display: flex;
25
+ flex-direction: row;
26
+ justify-content: center;
27
+ align-items: center;
28
+ margin-top: 32px;
29
+ margin-bottom: 12px;
30
+ gap: 12px;
31
+ }
32
+ #legacyViewBtn, #dynamicViewBtn {
33
+ font-size: 12px;
34
+ padding: 4px 10px;
35
+ }
36
+ </style>
37
+
38
+ <div id="dynamicUiContainer">
39
+
14
40
  <!-- v4 config incompatibility warning – shown dynamically by app.js -->
15
41
  <div id="v4ConfigWarning" class="v4-config-warning" style="display: none">
16
42
  <strong>⚠️ Incompatible v4 Configuration Detected</strong>
@@ -104,10 +130,6 @@
104
130
  <button id="discoverBtn" onclick="discoverDevices()" style="margin-bottom: 8px">
105
131
  🔍 Discover
106
132
  </button>
107
- <button id="cancelDiscoverBtn"
108
- style="margin-left: 6px; margin-bottom: 8px; display: none; padding: 6px 10px; font-size: 11px" class="secondary">
109
- Cancel
110
- </button>
111
133
  <div id="discoverStatus" style="margin-bottom: 6px; font-size: 11px"></div>
112
134
  <div id="discoverDevicesFound" style="margin-bottom: 6px; font-size: 11px; display: none"></div>
113
135
  <div id="discoverPhaseProgress" class="discover-phase-progress" style="display: none">
@@ -231,8 +253,18 @@
231
253
  </form>
232
254
  </div>
233
255
 
256
+ </div> <!-- end dynamicUiContainer -->
257
+
258
+ <div id="legacyViewContainer" style="display: none;">
259
+ <div class="card compact">
260
+ <h2>Legacy Config View</h2>
261
+ <div id="legacyConfigForm"></div>
262
+ </div>
263
+ </div>
264
+
234
265
  <script type="module" src="js/app.js"></script>
235
266
  <script type="module" src="js/advanced-settings.js"></script>
267
+ <script type="module" src="js/legacy-view.js"></script>
236
268
  </body>
237
269
 
238
270
  </html>
@@ -46,10 +46,11 @@ async function saveAdvancedSettings(): Promise<void> {
46
46
  if (!Array.isArray(pluginConfigBlocks) || !pluginConfigBlocks.length) {
47
47
  throw new Error('No plugin config blocks returned from Homebridge')
48
48
  }
49
- // Find the SwitchBot config block
50
- const idx = pluginConfigBlocks.findIndex(c => (c.platform || c.name || '').toLowerCase().includes('switchbot'))
49
+ // Find or add the SwitchBot config block
50
+ let idx = pluginConfigBlocks.findIndex(c => (c.platform || c.name || '').toLowerCase().includes('switchbot'))
51
51
  if (idx === -1) {
52
- throw new Error('SwitchBot config not found')
52
+ pluginConfigBlocks.push({ platform: 'SwitchBot' })
53
+ idx = pluginConfigBlocks.length - 1
53
54
  }
54
55
  const config = pluginConfigBlocks[idx]
55
56
  // Update config values from UI
@@ -300,7 +300,22 @@ export async function addDevicesInBulk(
300
300
  if (!resp || resp.success === false) {
301
301
  throw new Error(resp?.data?.message || 'Bulk add failed')
302
302
  }
303
- return resp.data || resp
303
+ return normalizeBulkAddDevicesResponse(resp)
304
+ }
305
+
306
+ export function normalizeBulkAddDevicesResponse(resp: any): any {
307
+ const payload = resp?.data && typeof resp.data === 'object' ? resp.data : resp
308
+ const addedCount = Number(payload?.addedCount ?? payload?.added ?? 0)
309
+ const skippedCount = Number(payload?.skippedCount ?? payload?.skipped ?? 0)
310
+ const updatedCount = Number(payload?.updatedCount ?? payload?.updated ?? 0)
311
+
312
+ return {
313
+ ...payload,
314
+ success: resp?.success ?? true,
315
+ addedCount,
316
+ skippedCount,
317
+ updatedCount,
318
+ }
304
319
  }
305
320
 
306
321
  export async function updateDevice(
@@ -3,6 +3,29 @@ import { loadCredentialStatus, saveCredentials } from './credentials.js'
3
3
  import { initRemoveAllButton, loadConfiguredDevices } from './devices.js'
4
4
  import { discoverDevices, initializeDiscoverySettings } from './discovery.js'
5
5
 
6
+ // Ensure SwitchBot config block exists before any UI actions
7
+ async function ensureSwitchBotConfigBlock(): Promise<void> {
8
+ try {
9
+ if (typeof homebridge.getPluginConfig !== 'function' || typeof homebridge.updatePluginConfig !== 'function') {
10
+ return
11
+ }
12
+ let configArr = await homebridge.getPluginConfig()
13
+ if (!Array.isArray(configArr)) {
14
+ configArr = []
15
+ }
16
+ const idx = configArr.findIndex(c => (c.platform || c.name || '').toLowerCase().includes('switchbot'))
17
+ if (idx === -1) {
18
+ configArr.push({ platform: 'SwitchBot' })
19
+ await homebridge.updatePluginConfig(configArr)
20
+ if (typeof homebridge.savePluginConfig === 'function') {
21
+ await homebridge.savePluginConfig()
22
+ }
23
+ }
24
+ } catch (_e) {
25
+ // Ignore errors
26
+ }
27
+ }
28
+
6
29
  ;
7
30
 
8
31
  (window as any).loadCredentialStatus = loadCredentialStatus
@@ -24,7 +47,9 @@ async function checkV4Config(): Promise<void> {
24
47
  }
25
48
 
26
49
  // Initialize on page load
50
+
27
51
  async function init(): Promise<void> {
52
+ await ensureSwitchBotConfigBlock()
28
53
  await checkV4Config()
29
54
  await loadCredentialStatus()
30
55
  await initializeDiscoverySettings()
@@ -63,14 +63,20 @@ export async function saveCredentials(): Promise<void> {
63
63
  if (typeof homebridge.getPluginConfig !== 'function' || typeof homebridge.updatePluginConfig !== 'function') {
64
64
  throw new TypeError('Homebridge UI API not available')
65
65
  }
66
- const configArr = await homebridge.getPluginConfig()
67
- if (!Array.isArray(configArr) || configArr.length === 0) {
68
- throw new Error('No plugin config found')
66
+ let configArr = await homebridge.getPluginConfig()
67
+ if (!Array.isArray(configArr)) {
68
+ configArr = []
69
+ }
70
+ let idx = configArr.findIndex(c => (c.platform || c.name || '').toLowerCase() === 'switchbot')
71
+ if (idx === -1) {
72
+ // Add new SwitchBot platform config
73
+ configArr.push({ platform: 'SwitchBot' })
74
+ idx = configArr.length - 1
69
75
  }
70
- const config = configArr[0]
76
+ const config = configArr[idx]
71
77
  config.openApiToken = token
72
78
  config.openApiSecret = secret
73
- await homebridge.updatePluginConfig([config])
79
+ await homebridge.updatePluginConfig(configArr)
74
80
  if (typeof homebridge.savePluginConfig === 'function') {
75
81
  await homebridge.savePluginConfig()
76
82
  }
@@ -394,7 +394,6 @@ function setDiscoveryGroupExpanded(groupKey: string, expanded: boolean): void {
394
394
 
395
395
  export async function discoverDevices(): Promise<void> {
396
396
  const btn = document.getElementById('discoverBtn') as HTMLButtonElement
397
- const cancelBtn = document.getElementById('cancelDiscoverBtn') as HTMLButtonElement | null
398
397
  const status = document.getElementById('discoverStatus')
399
398
  const phaseProgress = document.getElementById('discoverPhaseProgress') as HTMLElement | null
400
399
  const phaseFill = document.getElementById('discoverPhaseFill') as HTMLElement | null
@@ -424,7 +423,6 @@ export async function discoverDevices(): Promise<void> {
424
423
  const startedAt = Date.now()
425
424
  let phaseStartedAt = startedAt
426
425
  let phase = 'Preparing discovery...'
427
- let cancelled = false
428
426
 
429
427
  // --- Real-time RSSI polling additions ---
430
428
  // (bleScanDurationSeconds is now only used in bleSettings below)
@@ -936,32 +934,8 @@ export async function discoverDevices(): Promise<void> {
936
934
  devicesFoundDisplay.classList.remove('discovery-scanning-pulse')
937
935
  }
938
936
 
939
- if (cancelBtn) {
940
- cancelBtn.style.display = 'inline-block'
941
- cancelBtn.disabled = false
942
- cancelBtn.onclick = () => {
943
- cancelled = true
944
- const totalSeconds = Math.max(0, Math.floor((Date.now() - startedAt) / 1000))
945
- status.textContent = `Discovery cancelled (${totalSeconds}s total)`
946
- if (phaseProgress) {
947
- phaseProgress.style.display = 'none'
948
- }
949
- if (devicesFoundDisplay) {
950
- devicesFoundDisplay.style.display = 'none'
951
- devicesFoundDisplay.classList.remove('discovery-scanning-pulse')
952
- }
953
- cancelBtn.style.display = 'none'
954
- btn.disabled = false
955
- btn.textContent = '🔍 Discover Devices'
956
- hideBusyUi()
957
- }
958
- }
959
-
960
937
  if (bleSettings.bleEnabled) {
961
938
  const bleDevicesRaw = await apiDiscoverDevices('ble', bleSettings)
962
- if (cancelled) {
963
- return
964
- }
965
939
 
966
940
  discoveredDevices = dedupeById(bleDevicesRaw)
967
941
  uiLog.info('BLE discover response:', bleDevicesRaw)
@@ -988,9 +962,6 @@ export async function discoverDevices(): Promise<void> {
988
962
 
989
963
  try {
990
964
  const openApiDevicesRaw = await apiDiscoverDevices('openapi')
991
- if (cancelled) {
992
- return
993
- }
994
965
 
995
966
  uiLog.info('OpenAPI discover response:', openApiDevicesRaw)
996
967
  discoveredDevices = mergeDiscoveredDevices(discoveredDevices, openApiDevicesRaw)
@@ -1096,9 +1067,6 @@ export async function discoverDevices(): Promise<void> {
1096
1067
  setDiscoveryCache(discoveredDevices)
1097
1068
  updateLastScannedStatus()
1098
1069
  } catch (e) {
1099
- if (cancelled) {
1100
- return
1101
- }
1102
1070
  uiLog.error('Discovery error:', e)
1103
1071
  status.textContent = `Error: ${e instanceof Error ? e.message : 'Discovery failed'}`
1104
1072
  status.classList.add('error')
@@ -1123,11 +1091,6 @@ export async function discoverDevices(): Promise<void> {
1123
1091
  }
1124
1092
  btn.disabled = false
1125
1093
  btn.textContent = '🔍 Discover Devices'
1126
- if (cancelBtn) {
1127
- cancelBtn.disabled = false
1128
- cancelBtn.style.display = 'none'
1129
- cancelBtn.onclick = null
1130
- }
1131
1094
  }
1132
1095
  }
1133
1096