@switchbot/homebridge-switchbot 5.0.0-beta.72 → 5.0.0-beta.74
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/config.schema.json +100 -100
- 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 +53 -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 +62 -0
package/config.schema.json
CHANGED
|
@@ -1,106 +1,106 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"pluginAlias": "SwitchBot",
|
|
3
|
+
"pluginType": "platform",
|
|
4
|
+
"singular": true,
|
|
5
|
+
"customUi": true,
|
|
6
|
+
"customUiPath": "./dist/homebridge-ui",
|
|
7
|
+
"headerDisplay": "<p align='center'><img width='100px' src='https://user-images.githubusercontent.com/9875439/102681754-ac2eb800-4189-11eb-8e17-7c47c804484b.png'></p>\n\nThe **SwitchBot** plugin allows you to control SwitchBot Devices. Use the hybrid client (BLE + OpenAPI) or only OpenAPI by supplying an Open Token.",
|
|
8
|
+
"footerDisplay": "Please raise any issues on our [project page](https://github.com/OpenWonderLabs/homebridge-switchbot/issues).",
|
|
9
|
+
"schema": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"required": [],
|
|
12
|
+
"properties": {
|
|
13
|
+
"name": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"title": "Name",
|
|
16
|
+
"default": "SwitchBot"
|
|
17
|
+
},
|
|
18
|
+
"openApiToken": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"title": "OpenAPI Token",
|
|
21
|
+
"description": "SwitchBot OpenAPI token used as a fallback when BLE isn't available. Keep this secret.",
|
|
22
|
+
"format": "password"
|
|
23
|
+
},
|
|
24
|
+
"openApiSecret": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"title": "OpenAPI Secret (optional)",
|
|
27
|
+
"description": "Optional OpenAPI secret for certain account setups.",
|
|
28
|
+
"format": "password"
|
|
29
|
+
},
|
|
30
|
+
"enableMatter": {
|
|
31
|
+
"type": "boolean",
|
|
32
|
+
"title": "Enable Matter Support",
|
|
33
|
+
"default": true,
|
|
34
|
+
"description": "Allow the plugin to register Matter accessories when Homebridge Matter is available."
|
|
35
|
+
},
|
|
36
|
+
"preferMatter": {
|
|
37
|
+
"type": "boolean",
|
|
38
|
+
"title": "Prefer Matter when available",
|
|
39
|
+
"default": true,
|
|
40
|
+
"description": "If enabled and Matter is available, device will be presented as Matter accessories where supported."
|
|
41
|
+
},
|
|
42
|
+
"devices": {
|
|
43
|
+
"type": "array",
|
|
44
|
+
"title": "Devices",
|
|
45
|
+
"description": "List of SwitchBot devices to manage. Use the UI to add devices and copy device IDs.",
|
|
46
|
+
"items": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"required": ["deviceId", "configDeviceType"],
|
|
49
|
+
"properties": {
|
|
50
|
+
"configDeviceName": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"title": "Name",
|
|
53
|
+
"description": "Friendly name for this device as shown in HomeKit/Matter."
|
|
54
|
+
},
|
|
55
|
+
"deviceId": {
|
|
56
|
+
"type": "string",
|
|
57
|
+
"title": "Device ID",
|
|
58
|
+
"description": "The SwitchBot device identifier (MAC or SwitchBot Cloud deviceId). Use the plugin UI to copy this."
|
|
59
|
+
},
|
|
60
|
+
"configDeviceType": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"title": "Device Type",
|
|
63
|
+
"description": "Select the device type so the plugin can expose the correct HomeKit/Matter services and characteristics.",
|
|
64
|
+
"enum": [
|
|
65
|
+
"bot",
|
|
66
|
+
"curtain",
|
|
67
|
+
"fan",
|
|
68
|
+
"light",
|
|
69
|
+
"lightstrip",
|
|
70
|
+
"motion",
|
|
71
|
+
"contact",
|
|
72
|
+
"vacuum",
|
|
73
|
+
"lock",
|
|
74
|
+
"humidifier",
|
|
75
|
+
"temperature",
|
|
76
|
+
"relay",
|
|
77
|
+
"plug",
|
|
78
|
+
"wosweeper",
|
|
79
|
+
"blindtilt",
|
|
80
|
+
"curtain3",
|
|
81
|
+
"rollershade",
|
|
82
|
+
"meter",
|
|
83
|
+
"waterdetector",
|
|
84
|
+
"walletfinder",
|
|
85
|
+
"unknown"
|
|
86
|
+
],
|
|
87
|
+
"default": "unknown"
|
|
88
|
+
},
|
|
89
|
+
"connectionPreference": {
|
|
90
|
+
"type": "string",
|
|
91
|
+
"title": "Connection Preference",
|
|
92
|
+
"description": "Select whether this device should prefer BLE, OpenAPI, or auto-detect (hybrid).",
|
|
93
|
+
"enum": ["auto", "ble", "openapi"],
|
|
94
|
+
"default": "auto"
|
|
95
|
+
},
|
|
96
|
+
"room": {
|
|
97
|
+
"type": "string",
|
|
98
|
+
"title": "Room",
|
|
99
|
+
"description": "Optional room/location metadata for the device."
|
|
101
100
|
}
|
|
102
101
|
}
|
|
103
102
|
}
|
|
104
103
|
}
|
|
105
104
|
}
|
|
106
105
|
}
|
|
106
|
+
}
|
|
@@ -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;AAiH7C,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,41 @@ 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
|
+
return {
|
|
72
|
+
hasToken: !!platform.openApiToken,
|
|
73
|
+
hasSecret: !!platform.openApiSecret,
|
|
74
|
+
tokenLength: platform.openApiToken ? String(platform.openApiToken).length : 0,
|
|
75
|
+
secretLength: platform.openApiSecret ? String(platform.openApiSecret).length : 0,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// POST request - save credentials
|
|
80
|
+
const { token, secret } = body;
|
|
81
|
+
if (!token || !secret) {
|
|
82
|
+
throw new Error('Token and secret are required');
|
|
83
|
+
}
|
|
84
|
+
const { config, platform, cfgPath } = await getSwitchBotPlatformConfig();
|
|
85
|
+
// Save token and secret directly on the platform config
|
|
86
|
+
platform.openApiToken = token;
|
|
87
|
+
platform.openApiSecret = secret;
|
|
88
|
+
// Write back to config file
|
|
89
|
+
await fs.writeFile(cfgPath, JSON.stringify(config, null, 2), 'utf8');
|
|
90
|
+
return {
|
|
91
|
+
success: true,
|
|
92
|
+
message: 'Credentials saved successfully',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
throw new RequestError('Failed to handle credentials request', e);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
47
100
|
server.ready();
|
|
48
101
|
export default server;
|
|
49
102
|
//# 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/af2eb0c3a4dfe2830519b32acc45b53dbb204968/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.74",
|
|
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,46 @@ 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
|
+
|
|
83
|
+
return {
|
|
84
|
+
hasToken: !!platform.openApiToken,
|
|
85
|
+
hasSecret: !!platform.openApiSecret,
|
|
86
|
+
tokenLength: platform.openApiToken ? String(platform.openApiToken).length : 0,
|
|
87
|
+
secretLength: platform.openApiSecret ? String(platform.openApiSecret).length : 0,
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
// POST request - save credentials
|
|
91
|
+
const { token, secret } = body
|
|
92
|
+
|
|
93
|
+
if (!token || !secret) {
|
|
94
|
+
throw new Error('Token and secret are required')
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const { config, platform, cfgPath } = await getSwitchBotPlatformConfig()
|
|
98
|
+
|
|
99
|
+
// Save token and secret directly on the platform config
|
|
100
|
+
platform.openApiToken = token
|
|
101
|
+
platform.openApiSecret = secret
|
|
102
|
+
|
|
103
|
+
// Write back to config file
|
|
104
|
+
await fs.writeFile(cfgPath, JSON.stringify(config, null, 2), 'utf8')
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
success: true,
|
|
108
|
+
message: 'Credentials saved successfully',
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} catch (e) {
|
|
112
|
+
throw new RequestError('Failed to handle credentials request', e)
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
|
|
54
116
|
server.ready()
|
|
55
117
|
|
|
56
118
|
export default server
|