@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.
- package/dist/devices/deviceBase.d.ts +1 -1
- package/dist/devices/deviceBase.d.ts.map +1 -1
- package/dist/devices/deviceBase.js +1 -1
- package/dist/devices/deviceBase.js.map +1 -1
- package/dist/homebridge-ui/public/index.html +129 -10
- package/dist/homebridge-ui/server.d.ts.map +1 -1
- package/dist/homebridge-ui/server.js +62 -0
- package/dist/homebridge-ui/server.js.map +1 -1
- package/docs/variables/default.html +1 -1
- package/package.json +1 -1
- package/src/devices/deviceBase.ts +2 -2
- package/src/homebridge-ui/public/index.html +129 -10
- package/src/homebridge-ui/server.ts +71 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deviceBase.d.ts","sourceRoot":"","sources":["../../src/devices/deviceBase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,
|
|
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deviceBase.js","sourceRoot":"","sources":["../../src/devices/deviceBase.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,
|
|
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:
|
|
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:
|
|
13
|
-
|
|
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
|
|
18
|
-
|
|
19
|
-
<div
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
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">=></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/
|
|
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">=></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.
|
|
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:
|
|
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:
|
|
13
|
-
|
|
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
|
|
18
|
-
|
|
19
|
-
<div
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|