@switchbot/homebridge-switchbot 5.0.0-beta.72 → 5.0.0-beta.73

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.
@@ -1,4 +1,4 @@
1
- import type { SwitchBotPluginConfig } from '../settings';
1
+ import type { SwitchBotPluginConfig } from '../settings.js';
2
2
  export interface DeviceOptions {
3
3
  id: string;
4
4
  type: string;
@@ -1 +1 @@
1
- {"version":3,"file":"deviceBase.d.ts","sourceRoot":"","sources":["../../src/devices/deviceBase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAIxD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB;AAED,8BAAsB,UAAU;IAC9B,SAAS,CAAC,IAAI,EAAE,aAAa,CAAA;IAC7B,SAAS,CAAC,GAAG,EAAE,qBAAqB,CAAA;IACpC,SAAS,CAAC,MAAM,EAAE,GAAG,GAAG,IAAI,CAAA;gBAEhB,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,qBAAqB;IAOrD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAG3B,QAAQ,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC;IAGjC,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAE5C;;;;;;;;;;;;;;;;;;;OAmBG;IACH,kBAAkB,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG;IAmCjC;;;;;;;;;OASG;IACH,qBAAqB,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG;IA2C9B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAC/B"}
1
+ {"version":3,"file":"deviceBase.d.ts","sourceRoot":"","sources":["../../src/devices/deviceBase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AAI3D,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB;AAED,8BAAsB,UAAU;IAC9B,SAAS,CAAC,IAAI,EAAE,aAAa,CAAA;IAC7B,SAAS,CAAC,GAAG,EAAE,qBAAqB,CAAA;IACpC,SAAS,CAAC,MAAM,EAAE,GAAG,GAAG,IAAI,CAAA;gBAEhB,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,qBAAqB;IAOrD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAG3B,QAAQ,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC;IAGjC,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAE5C;;;;;;;;;;;;;;;;;;;OAmBG;IACH,kBAAkB,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG;IAmCjC;;;;;;;;;OASG;IACH,qBAAqB,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG;IA2C9B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAC/B"}
@@ -1,4 +1,4 @@
1
- import { MATTER_ATTRIBUTE_IDS, MATTER_CLUSTER_IDS } from '../utils';
1
+ import { MATTER_ATTRIBUTE_IDS, MATTER_CLUSTER_IDS } from '../utils.js';
2
2
  export class DeviceBase {
3
3
  opts;
4
4
  cfg;
@@ -1 +1 @@
1
- {"version":3,"file":"deviceBase.js","sourceRoot":"","sources":["../../src/devices/deviceBase.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AASnE,MAAM,OAAgB,UAAU;IACpB,IAAI,CAAe;IACnB,GAAG,CAAuB;IAC1B,MAAM,CAAY;IAE5B,YAAY,IAAmB,EAAE,GAA0B;QACzD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAA;QACd,IAAI,CAAC,MAAM,GAAI,GAAW,EAAE,OAAO,IAAI,IAAI,CAAA;IAC7C,CAAC;IAED,uCAAuC;IACvC,KAAK,CAAC,IAAI,KAAmB,CAAC;IAQ9B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,kBAAkB,CAAC,GAAQ;QACzB,MAAM,IAAI,GAAQ;YAChB,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE;YAChB,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI;YACtC,QAAQ,EAAE,KAAK;YACf,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,QAAQ;oBACd,eAAe,EAAE;wBACf,EAAE,EAAE;4BACF,GAAG,EAAE,KAAK,IAAI,EAAE;gCACd,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAA;gCAC/B,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAA;4BACzE,CAAC;4BACD,GAAG,EAAE,KAAK,EAAE,CAAM,EAAE,EAAE;gCACpB,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;4BAClC,CAAC;yBACF;qBACF;iBACF;aACF;SACF,CAAA;QAED,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,GAAG,EAAE,GAAG,CAAA;YACpB,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,cAAc,EAAE,CAAC;gBAC7C,IAAI,CAAC,SAAS,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,CAAC,cAAc,EAAE,CAAA;YAC/E,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,SAAS;QACX,CAAC;QAED,OAAO,IAAI,CAAA;IACb,CAAC;IAED;;;;;;;;;OASG;IACH,qBAAqB,CAAC,GAAQ;QAC5B,MAAM,IAAI,GAAQ;YAChB,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE;YAChB,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI;YACtC,QAAQ,EAAE,QAAQ;YAClB,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,OAAO;oBACb,SAAS,EAAE,kBAAkB,CAAC,KAAK;oBACnC,UAAU,EAAE;wBACV,KAAK,EAAE;4BACL,IAAI,EAAE,KAAK,IAAI,EAAE;gCACf,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAA;gCAC/B,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAA;4BACzE,CAAC;4BACD,KAAK,EAAE,KAAK,EAAE,CAAM,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;yBACpD;wBACD,CAAC,oBAAoB,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;4BAClC,IAAI,EAAE,KAAK,IAAI,EAAE;gCACf,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAA;gCAC/B,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAA;4BACzE,CAAC;4BACD,KAAK,EAAE,KAAK,EAAE,CAAM,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;yBACpD;qBACF;iBACF;aACF;SACF,CAAA;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,GAAG,EAAE,MAAM,CAAA;YAC1B,IAAI,MAAM,EAAE,CAAC;gBACX,IAAI,CAAC,SAAS,GAAG;oBACf,MAAM;iBACP,CAAA;YACH,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,SAAS;QACX,CAAC;QAED,OAAO,IAAI,CAAA;IACb,CAAC;IAED,KAAK,CAAC,OAAO,KAAmB,CAAC;CAClC"}
1
+ {"version":3,"file":"deviceBase.js","sourceRoot":"","sources":["../../src/devices/deviceBase.ts"],"names":[],"mappings":"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"}
@@ -5,21 +5,135 @@
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1" />
6
6
  <title>SwitchBot Plugin UI</title>
7
7
  <style>
8
- body { font-family: system-ui, -apple-system, Arial; padding: 16px; }
9
- h1 { font-size: 18px }
8
+ body { font-family: system-ui, -apple-system, Arial; padding: 16px; background:#1a1a1a; color:#fff }
9
+ h1 { font-size: 24px; margin-top:0 }
10
+ h2 { font-size: 16px; margin-top: 24px; margin-bottom: 12px; border-bottom:1px solid #444; padding-bottom:8px }
10
11
  ul { padding-left: 0; list-style: none }
11
12
  li { margin: 8px 0; display:flex; gap:8px; align-items:center }
12
- button { padding: 6px 10px }
13
- code { background:#f3f3f3; padding:4px 6px; border-radius:4px }
13
+ button { padding: 8px 16px; background:#6366f1; color:#fff; border:none; border-radius:4px; cursor:pointer }
14
+ button:hover { background:#4f46e5 }
15
+ button.success { background:#10b981 }
16
+ code { background:#333; padding:4px 6px; border-radius:4px; color:#fff }
17
+ .form-group { margin-bottom:16px }
18
+ label { display:block; margin-bottom:6px; font-weight:500 }
19
+ input { width:100%; max-width:400px; padding:8px; background:#2a2a2a; border:1px solid #444; border-radius:4px; color:#fff; font-family:monospace }
20
+ input:focus { outline:none; border-color:#6366f1 }
21
+ .status { font-size:14px; color:#888; margin-top:4px }
22
+ .status.ok { color:#10b981 }
23
+ .error { color:#ef4444 }
24
+ .success-msg { color:#10b981; margin-top:8px }
25
+ .card { background:#2a2a2a; padding:16px; border-radius:8px; margin-bottom:16px }
14
26
  </style>
15
27
  </head>
16
28
  <body>
17
- <h1>SwitchBot: Configured Devices</h1>
18
- <p>This page lists devices found in your Homebridge config for the SwitchBot platform. Use the copy button to insert device IDs into the plugin configuration. Connection preference (BLE/OpenAPI) is shown when available.</p>
19
- <div id="status">Loading…</div>
20
- <ul id="devices"></ul>
29
+ <h1>🤖 SwitchBot Configuration</h1>
30
+
31
+ <div class="card">
32
+ <h2>API Credentials</h2>
33
+ <p>Configure your SwitchBot API token and secret to enable device discovery and control.</p>
34
+
35
+ <div class="form-group">
36
+ <label for="token">API Token:</label>
37
+ <input type="password" id="token" placeholder="Enter your SwitchBot API token" />
38
+ <div class="status" id="tokenStatus"></div>
39
+ </div>
40
+
41
+ <div class="form-group">
42
+ <label for="secret">API Secret:</label>
43
+ <input type="password" id="secret" placeholder="Enter your SwitchBot API secret" />
44
+ <div class="status" id="secretStatus"></div>
45
+ </div>
46
+
47
+ <button id="saveBtn" onclick="saveCredentials()">Save Credentials</button>
48
+ <div id="saveStatus"></div>
49
+ </div>
50
+
51
+ <div class="card">
52
+ <h2>Configured Devices</h2>
53
+ <p>This page lists devices found in your Homebridge config for the SwitchBot platform. Use the copy button to insert device IDs into the plugin configuration. Connection preference (BLE/OpenAPI) is shown when available.</p>
54
+ <div id="status">Loading…</div>
55
+ <ul id="devices"></ul>
56
+ </div>
21
57
 
22
58
  <script>
59
+ async function loadCredentialStatus() {
60
+ try {
61
+ const resp = await homebridge.request('/credentials', {})
62
+ if (!resp || !resp.success === false) {
63
+ if (!resp?.data) {
64
+ console.error('Failed to load credentials:', resp?.data?.message)
65
+ return
66
+ }
67
+ }
68
+
69
+ const creds = resp.data || {}
70
+ const tokenStatus = document.getElementById('tokenStatus')
71
+ const secretStatus = document.getElementById('secretStatus')
72
+
73
+ if (creds.hasToken) {
74
+ tokenStatus.textContent = `✓ Configured (${creds.tokenLength} characters)`
75
+ tokenStatus.classList.add('ok')
76
+ } else {
77
+ tokenStatus.textContent = 'Not configured'
78
+ }
79
+
80
+ if (creds.hasSecret) {
81
+ secretStatus.textContent = `✓ Configured (${creds.secretLength} characters)`
82
+ secretStatus.classList.add('ok')
83
+ } else {
84
+ secretStatus.textContent = 'Not configured'
85
+ }
86
+ } catch (e) {
87
+ console.error('Error loading credentials:', e)
88
+ }
89
+ }
90
+
91
+ async function saveCredentials() {
92
+ const token = document.getElementById('token').value
93
+ const secret = document.getElementById('secret').value
94
+ const saveStatus = document.getElementById('saveStatus')
95
+ const saveBtn = document.getElementById('saveBtn')
96
+
97
+ if (!token || !secret) {
98
+ saveStatus.textContent = 'Please enter both token and secret'
99
+ saveStatus.classList.add('error')
100
+ return
101
+ }
102
+
103
+ try {
104
+ saveBtn.disabled = true
105
+ saveBtn.textContent = 'Saving...'
106
+
107
+ const resp = await homebridge.request('/credentials', { token, secret })
108
+ if (!resp || resp.success === false) {
109
+ throw new Error(resp?.data?.message || 'Save failed')
110
+ }
111
+
112
+ saveStatus.textContent = '✓ ' + (resp.data?.message || 'Credentials saved')
113
+ saveStatus.classList.remove('error')
114
+ saveStatus.classList.add('success-msg')
115
+
116
+ // Clear inputs after successful save
117
+ document.getElementById('token').value = ''
118
+ document.getElementById('secret').value = ''
119
+
120
+ // Reload status
121
+ setTimeout(() => loadCredentialStatus(), 1000)
122
+
123
+ // Clear status message
124
+ setTimeout(() => {
125
+ saveStatus.textContent = ''
126
+ saveStatus.classList.remove('success-msg')
127
+ }, 3000)
128
+ } catch (e) {
129
+ saveStatus.textContent = 'Error: ' + (e?.message || 'Failed to save')
130
+ saveStatus.classList.add('error')
131
+ } finally {
132
+ saveBtn.disabled = false
133
+ saveBtn.textContent = 'Save Credentials'
134
+ }
135
+ }
136
+
23
137
  async function fetchDevices() {
24
138
  try {
25
139
  const resp = await homebridge.request('/devices', {})
@@ -59,7 +173,11 @@
59
173
  try {
60
174
  await navigator.clipboard.writeText(d.id)
61
175
  btn.textContent = 'Copied'
62
- setTimeout(() => (btn.textContent = 'Copy ID'), 1200)
176
+ btn.classList.add('success')
177
+ setTimeout(() => {
178
+ btn.textContent = 'Copy ID'
179
+ btn.classList.remove('success')
180
+ }, 1200)
63
181
  } catch (e) {
64
182
  alert('Failed to copy')
65
183
  }
@@ -73,9 +191,10 @@
73
191
  }
74
192
 
75
193
  (async () => {
194
+ await loadCredentialStatus()
76
195
  const list = await fetchDevices()
77
196
  render(list)
78
197
  })()
79
198
  </script>
80
199
  </body>
81
- </html>
200
+ </html>
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/homebridge-ui/server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,wBAAwB,EAAgB,MAAM,6BAA6B,CAAA;AAEpF,QAAA,MAAM,MAAM,0BAAiC,CAAA;AAmD7C,eAAe,MAAM,CAAA"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/homebridge-ui/server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,wBAAwB,EAAgB,MAAM,6BAA6B,CAAA;AAEpF,QAAA,MAAM,MAAM,0BAAiC,CAAA;AA0H7C,eAAe,MAAM,CAAA"}
@@ -1,6 +1,24 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import { HomebridgePluginUiServer, RequestError } from '@homebridge/plugin-ui-utils';
3
3
  const server = new HomebridgePluginUiServer();
4
+ // Helper function to find the SwitchBot platform config
5
+ async function getSwitchBotPlatformConfig() {
6
+ const cfgPath = server.homebridgeConfigPath;
7
+ if (!cfgPath) {
8
+ throw new Error('HOMEBRIDGE_CONFIG_PATH not set');
9
+ }
10
+ const raw = await fs.readFile(cfgPath, 'utf8');
11
+ const cfg = JSON.parse(raw);
12
+ const platforms = Array.isArray(cfg.platforms) ? cfg.platforms : [];
13
+ for (const p of platforms) {
14
+ const platformName = p.platform || p.name || '';
15
+ if (!platformName || !/switchbot/i.test(String(platformName))) {
16
+ continue;
17
+ }
18
+ return { config: cfg, platform: p, cfgPath };
19
+ }
20
+ throw new Error('SwitchBot platform not found in config');
21
+ }
4
22
  server.onRequest('/devices', async () => {
5
23
  try {
6
24
  const cfgPath = server.homebridgeConfigPath;
@@ -44,6 +62,50 @@ server.onRequest('/devices', async () => {
44
62
  throw new RequestError('Failed to read Homebridge config', e);
45
63
  }
46
64
  });
65
+ server.onRequest('/credentials', async (body) => {
66
+ try {
67
+ // Handle both GET and POST requests
68
+ if (!body || Object.keys(body).length === 0) {
69
+ // GET request - return current status
70
+ const { platform } = await getSwitchBotPlatformConfig();
71
+ const options = platform.options || platform;
72
+ return {
73
+ hasToken: !!options.token,
74
+ hasSecret: !!options.secret,
75
+ tokenLength: options.token ? String(options.token).length : 0,
76
+ secretLength: options.secret ? String(options.secret).length : 0,
77
+ };
78
+ }
79
+ else {
80
+ // POST request - save credentials
81
+ const { token, secret } = body;
82
+ if (!token || !secret) {
83
+ throw new Error('Token and secret are required');
84
+ }
85
+ const { config, platform, cfgPath } = await getSwitchBotPlatformConfig();
86
+ // Update the platform config with new credentials
87
+ const options = platform.options || {};
88
+ options.token = token;
89
+ options.secret = secret;
90
+ if (platform.options) {
91
+ platform.options = options;
92
+ }
93
+ else {
94
+ platform.token = token;
95
+ platform.secret = secret;
96
+ }
97
+ // Write back to config file
98
+ await fs.writeFile(cfgPath, JSON.stringify(config, null, 2), 'utf8');
99
+ return {
100
+ success: true,
101
+ message: 'Credentials saved successfully',
102
+ };
103
+ }
104
+ }
105
+ catch (e) {
106
+ throw new RequestError('Failed to handle credentials request', e);
107
+ }
108
+ });
47
109
  server.ready();
48
110
  export default server;
49
111
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/homebridge-ui/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAEjC,OAAO,EAAE,wBAAwB,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAEpF,MAAM,MAAM,GAAG,IAAI,wBAAwB,EAAE,CAAA;AAE7C,MAAM,CAAC,SAAS,CAAC,UAAU,EAAE,KAAK,IAAI,EAAE;IACtC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,CAAC,oBAAoB,CAAA;QAC3C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAA;QACnD,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAC3B,MAAM,KAAK,GAAsG,EAAE,CAAA;QAEnH,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAA;QACnE,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;YAC1B,IAAI,CAAC;gBACH,MAAM,YAAY,GAAG,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,IAAI,IAAI,EAAE,CAAA;gBAC/C,6CAA6C;gBAC7C,IAAI,CAAC,YAAY,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;oBAC9D,SAAQ;gBACV,CAAC;gBAED,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,IAAI,CAAC,CAAA;gBAC9B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAA;gBACrE,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;oBACxB,MAAM,EAAE,GAAG,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,EAAE,CAAA;oBAC7B,IAAI,CAAC,EAAE,EAAE,CAAC;wBACR,SAAQ;oBACV,CAAC;oBAED,KAAK,CAAC,IAAI,CAAC;wBACT,EAAE;wBACF,IAAI,EAAE,CAAC,CAAC,gBAAgB,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,UAAU;wBAClD,IAAI,EAAE,CAAC,CAAC,gBAAgB,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,UAAU;wBAClD,oBAAoB,EAAE,CAAC,CAAC,oBAAoB,IAAI,CAAC,CAAC,UAAU,IAAI,MAAM;wBACtE,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,QAAQ,IAAI,SAAS;qBACxC,CAAC,CAAA;gBACJ,CAAC;YACH,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,oCAAoC;YACtC,CAAC;QACH,CAAC;QAED,OAAO,KAAK,CAAA;IACd,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,YAAY,CAAC,kCAAkC,EAAE,CAAC,CAAC,CAAA;IAC/D,CAAC;AACH,CAAC,CAAC,CAAA;AAEF,MAAM,CAAC,KAAK,EAAE,CAAA;AAEd,eAAe,MAAM,CAAA"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/homebridge-ui/server.ts"],"names":[],"mappings":"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"}
@@ -1 +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-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></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/c94a256343c2ebc4d7ccd446bf98eb0e2958061a/src/index.ts#L12">index.ts:12</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>
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-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></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/10cf33ad66407769681cff747780d250996366c8/src/index.ts#L12">index.ts:12</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.0.0-beta.72",
5
+ "version": "5.0.0-beta.73",
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": [
@@ -1,6 +1,6 @@
1
- import type { SwitchBotPluginConfig } from '../settings'
1
+ import type { SwitchBotPluginConfig } from '../settings.js'
2
2
 
3
- import { MATTER_ATTRIBUTE_IDS, MATTER_CLUSTER_IDS } from '../utils'
3
+ import { MATTER_ATTRIBUTE_IDS, MATTER_CLUSTER_IDS } from '../utils.js'
4
4
 
5
5
  export interface DeviceOptions {
6
6
  id: string
@@ -5,21 +5,135 @@
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1" />
6
6
  <title>SwitchBot Plugin UI</title>
7
7
  <style>
8
- body { font-family: system-ui, -apple-system, Arial; padding: 16px; }
9
- h1 { font-size: 18px }
8
+ body { font-family: system-ui, -apple-system, Arial; padding: 16px; background:#1a1a1a; color:#fff }
9
+ h1 { font-size: 24px; margin-top:0 }
10
+ h2 { font-size: 16px; margin-top: 24px; margin-bottom: 12px; border-bottom:1px solid #444; padding-bottom:8px }
10
11
  ul { padding-left: 0; list-style: none }
11
12
  li { margin: 8px 0; display:flex; gap:8px; align-items:center }
12
- button { padding: 6px 10px }
13
- code { background:#f3f3f3; padding:4px 6px; border-radius:4px }
13
+ button { padding: 8px 16px; background:#6366f1; color:#fff; border:none; border-radius:4px; cursor:pointer }
14
+ button:hover { background:#4f46e5 }
15
+ button.success { background:#10b981 }
16
+ code { background:#333; padding:4px 6px; border-radius:4px; color:#fff }
17
+ .form-group { margin-bottom:16px }
18
+ label { display:block; margin-bottom:6px; font-weight:500 }
19
+ input { width:100%; max-width:400px; padding:8px; background:#2a2a2a; border:1px solid #444; border-radius:4px; color:#fff; font-family:monospace }
20
+ input:focus { outline:none; border-color:#6366f1 }
21
+ .status { font-size:14px; color:#888; margin-top:4px }
22
+ .status.ok { color:#10b981 }
23
+ .error { color:#ef4444 }
24
+ .success-msg { color:#10b981; margin-top:8px }
25
+ .card { background:#2a2a2a; padding:16px; border-radius:8px; margin-bottom:16px }
14
26
  </style>
15
27
  </head>
16
28
  <body>
17
- <h1>SwitchBot: Configured Devices</h1>
18
- <p>This page lists devices found in your Homebridge config for the SwitchBot platform. Use the copy button to insert device IDs into the plugin configuration. Connection preference (BLE/OpenAPI) is shown when available.</p>
19
- <div id="status">Loading…</div>
20
- <ul id="devices"></ul>
29
+ <h1>🤖 SwitchBot Configuration</h1>
30
+
31
+ <div class="card">
32
+ <h2>API Credentials</h2>
33
+ <p>Configure your SwitchBot API token and secret to enable device discovery and control.</p>
34
+
35
+ <div class="form-group">
36
+ <label for="token">API Token:</label>
37
+ <input type="password" id="token" placeholder="Enter your SwitchBot API token" />
38
+ <div class="status" id="tokenStatus"></div>
39
+ </div>
40
+
41
+ <div class="form-group">
42
+ <label for="secret">API Secret:</label>
43
+ <input type="password" id="secret" placeholder="Enter your SwitchBot API secret" />
44
+ <div class="status" id="secretStatus"></div>
45
+ </div>
46
+
47
+ <button id="saveBtn" onclick="saveCredentials()">Save Credentials</button>
48
+ <div id="saveStatus"></div>
49
+ </div>
50
+
51
+ <div class="card">
52
+ <h2>Configured Devices</h2>
53
+ <p>This page lists devices found in your Homebridge config for the SwitchBot platform. Use the copy button to insert device IDs into the plugin configuration. Connection preference (BLE/OpenAPI) is shown when available.</p>
54
+ <div id="status">Loading…</div>
55
+ <ul id="devices"></ul>
56
+ </div>
21
57
 
22
58
  <script>
59
+ async function loadCredentialStatus() {
60
+ try {
61
+ const resp = await homebridge.request('/credentials', {})
62
+ if (!resp || !resp.success === false) {
63
+ if (!resp?.data) {
64
+ console.error('Failed to load credentials:', resp?.data?.message)
65
+ return
66
+ }
67
+ }
68
+
69
+ const creds = resp.data || {}
70
+ const tokenStatus = document.getElementById('tokenStatus')
71
+ const secretStatus = document.getElementById('secretStatus')
72
+
73
+ if (creds.hasToken) {
74
+ tokenStatus.textContent = `✓ Configured (${creds.tokenLength} characters)`
75
+ tokenStatus.classList.add('ok')
76
+ } else {
77
+ tokenStatus.textContent = 'Not configured'
78
+ }
79
+
80
+ if (creds.hasSecret) {
81
+ secretStatus.textContent = `✓ Configured (${creds.secretLength} characters)`
82
+ secretStatus.classList.add('ok')
83
+ } else {
84
+ secretStatus.textContent = 'Not configured'
85
+ }
86
+ } catch (e) {
87
+ console.error('Error loading credentials:', e)
88
+ }
89
+ }
90
+
91
+ async function saveCredentials() {
92
+ const token = document.getElementById('token').value
93
+ const secret = document.getElementById('secret').value
94
+ const saveStatus = document.getElementById('saveStatus')
95
+ const saveBtn = document.getElementById('saveBtn')
96
+
97
+ if (!token || !secret) {
98
+ saveStatus.textContent = 'Please enter both token and secret'
99
+ saveStatus.classList.add('error')
100
+ return
101
+ }
102
+
103
+ try {
104
+ saveBtn.disabled = true
105
+ saveBtn.textContent = 'Saving...'
106
+
107
+ const resp = await homebridge.request('/credentials', { token, secret })
108
+ if (!resp || resp.success === false) {
109
+ throw new Error(resp?.data?.message || 'Save failed')
110
+ }
111
+
112
+ saveStatus.textContent = '✓ ' + (resp.data?.message || 'Credentials saved')
113
+ saveStatus.classList.remove('error')
114
+ saveStatus.classList.add('success-msg')
115
+
116
+ // Clear inputs after successful save
117
+ document.getElementById('token').value = ''
118
+ document.getElementById('secret').value = ''
119
+
120
+ // Reload status
121
+ setTimeout(() => loadCredentialStatus(), 1000)
122
+
123
+ // Clear status message
124
+ setTimeout(() => {
125
+ saveStatus.textContent = ''
126
+ saveStatus.classList.remove('success-msg')
127
+ }, 3000)
128
+ } catch (e) {
129
+ saveStatus.textContent = 'Error: ' + (e?.message || 'Failed to save')
130
+ saveStatus.classList.add('error')
131
+ } finally {
132
+ saveBtn.disabled = false
133
+ saveBtn.textContent = 'Save Credentials'
134
+ }
135
+ }
136
+
23
137
  async function fetchDevices() {
24
138
  try {
25
139
  const resp = await homebridge.request('/devices', {})
@@ -59,7 +173,11 @@
59
173
  try {
60
174
  await navigator.clipboard.writeText(d.id)
61
175
  btn.textContent = 'Copied'
62
- setTimeout(() => (btn.textContent = 'Copy ID'), 1200)
176
+ btn.classList.add('success')
177
+ setTimeout(() => {
178
+ btn.textContent = 'Copy ID'
179
+ btn.classList.remove('success')
180
+ }, 1200)
63
181
  } catch (e) {
64
182
  alert('Failed to copy')
65
183
  }
@@ -73,9 +191,10 @@
73
191
  }
74
192
 
75
193
  (async () => {
194
+ await loadCredentialStatus()
76
195
  const list = await fetchDevices()
77
196
  render(list)
78
197
  })()
79
198
  </script>
80
199
  </body>
81
- </html>
200
+ </html>
@@ -4,6 +4,28 @@ import { HomebridgePluginUiServer, RequestError } from '@homebridge/plugin-ui-ut
4
4
 
5
5
  const server = new HomebridgePluginUiServer()
6
6
 
7
+ // Helper function to find the SwitchBot platform config
8
+ async function getSwitchBotPlatformConfig() {
9
+ const cfgPath = server.homebridgeConfigPath
10
+ if (!cfgPath) {
11
+ throw new Error('HOMEBRIDGE_CONFIG_PATH not set')
12
+ }
13
+
14
+ const raw = await fs.readFile(cfgPath, 'utf8')
15
+ const cfg = JSON.parse(raw)
16
+ const platforms = Array.isArray(cfg.platforms) ? cfg.platforms : []
17
+
18
+ for (const p of platforms) {
19
+ const platformName = p.platform || p.name || ''
20
+ if (!platformName || !/switchbot/i.test(String(platformName))) {
21
+ continue
22
+ }
23
+ return { config: cfg, platform: p, cfgPath }
24
+ }
25
+
26
+ throw new Error('SwitchBot platform not found in config')
27
+ }
28
+
7
29
  server.onRequest('/devices', async () => {
8
30
  try {
9
31
  const cfgPath = server.homebridgeConfigPath
@@ -51,6 +73,55 @@ server.onRequest('/devices', async () => {
51
73
  }
52
74
  })
53
75
 
76
+ server.onRequest('/credentials', async (body: any) => {
77
+ try {
78
+ // Handle both GET and POST requests
79
+ if (!body || Object.keys(body).length === 0) {
80
+ // GET request - return current status
81
+ const { platform } = await getSwitchBotPlatformConfig()
82
+ const options = platform.options || platform
83
+
84
+ return {
85
+ hasToken: !!options.token,
86
+ hasSecret: !!options.secret,
87
+ tokenLength: options.token ? String(options.token).length : 0,
88
+ secretLength: options.secret ? String(options.secret).length : 0,
89
+ }
90
+ } else {
91
+ // POST request - save credentials
92
+ const { token, secret } = body
93
+
94
+ if (!token || !secret) {
95
+ throw new Error('Token and secret are required')
96
+ }
97
+
98
+ const { config, platform, cfgPath } = await getSwitchBotPlatformConfig()
99
+
100
+ // Update the platform config with new credentials
101
+ const options = platform.options || {}
102
+ options.token = token
103
+ options.secret = secret
104
+
105
+ if (platform.options) {
106
+ platform.options = options
107
+ } else {
108
+ platform.token = token
109
+ platform.secret = secret
110
+ }
111
+
112
+ // Write back to config file
113
+ await fs.writeFile(cfgPath, JSON.stringify(config, null, 2), 'utf8')
114
+
115
+ return {
116
+ success: true,
117
+ message: 'Credentials saved successfully',
118
+ }
119
+ }
120
+ } catch (e) {
121
+ throw new RequestError('Failed to handle credentials request', e)
122
+ }
123
+ })
124
+
54
125
  server.ready()
55
126
 
56
127
  export default server