@switchbot/homebridge-switchbot 5.0.0-beta.75 → 5.0.0-beta.77
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/homebridge-ui/public/index.html +16 -9
- package/dist/homebridge-ui/server.d.ts.map +1 -1
- package/dist/homebridge-ui/server.js +14 -4
- package/dist/homebridge-ui/server.js.map +1 -1
- package/dist/switchbotClient.d.ts +7 -9
- package/dist/switchbotClient.d.ts.map +1 -1
- package/dist/switchbotClient.js +46 -152
- package/dist/switchbotClient.js.map +1 -1
- package/docs/variables/default.html +1 -1
- package/eslint.config.js +2 -8
- package/package.json +9 -17
- package/src/homebridge-ui/public/index.html +16 -9
- package/src/homebridge-ui/server.ts +17 -5
- package/src/switchbotClient.ts +48 -150
|
@@ -59,11 +59,11 @@
|
|
|
59
59
|
async function loadCredentialStatus() {
|
|
60
60
|
try {
|
|
61
61
|
const resp = await homebridge.request('/credentials', {})
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
console.log('Load credentials response:', resp)
|
|
63
|
+
|
|
64
|
+
if (!resp || resp.success === false) {
|
|
65
|
+
console.error('Failed to load credentials:', resp)
|
|
66
|
+
return
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
const creds = resp.data || {}
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
tokenStatus.classList.add('ok')
|
|
76
76
|
} else {
|
|
77
77
|
tokenStatus.textContent = 'Not configured'
|
|
78
|
+
tokenStatus.classList.remove('ok')
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
if (creds.hasSecret) {
|
|
@@ -82,6 +83,7 @@
|
|
|
82
83
|
secretStatus.classList.add('ok')
|
|
83
84
|
} else {
|
|
84
85
|
secretStatus.textContent = 'Not configured'
|
|
86
|
+
secretStatus.classList.remove('ok')
|
|
85
87
|
}
|
|
86
88
|
} catch (e) {
|
|
87
89
|
console.error('Error loading credentials:', e)
|
|
@@ -104,12 +106,16 @@
|
|
|
104
106
|
saveBtn.disabled = true
|
|
105
107
|
saveBtn.textContent = 'Saving...'
|
|
106
108
|
|
|
109
|
+
console.log('Saving credentials...')
|
|
107
110
|
const resp = await homebridge.request('/credentials', { token, secret })
|
|
111
|
+
console.log('Save response:', resp)
|
|
112
|
+
|
|
108
113
|
if (!resp || resp.success === false) {
|
|
109
|
-
throw new Error(resp?.
|
|
114
|
+
throw new Error(resp?.message || 'Save failed')
|
|
110
115
|
}
|
|
111
116
|
|
|
112
|
-
|
|
117
|
+
const result = resp.data || resp
|
|
118
|
+
saveStatus.textContent = '✓ ' + (result.message || 'Credentials saved successfully')
|
|
113
119
|
saveStatus.classList.remove('error')
|
|
114
120
|
saveStatus.classList.add('success-msg')
|
|
115
121
|
|
|
@@ -117,8 +123,8 @@
|
|
|
117
123
|
document.getElementById('token').value = ''
|
|
118
124
|
document.getElementById('secret').value = ''
|
|
119
125
|
|
|
120
|
-
// Reload status
|
|
121
|
-
setTimeout(() => loadCredentialStatus(),
|
|
126
|
+
// Reload status to verify save
|
|
127
|
+
setTimeout(() => loadCredentialStatus(), 500)
|
|
122
128
|
|
|
123
129
|
// Clear status message
|
|
124
130
|
setTimeout(() => {
|
|
@@ -126,6 +132,7 @@
|
|
|
126
132
|
saveStatus.classList.remove('success-msg')
|
|
127
133
|
}, 3000)
|
|
128
134
|
} catch (e) {
|
|
135
|
+
console.error('Save error:', e)
|
|
129
136
|
saveStatus.textContent = 'Error: ' + (e?.message || 'Failed to save')
|
|
130
137
|
saveStatus.classList.add('error')
|
|
131
138
|
} finally {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/homebridge-ui/server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/homebridge-ui/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAgB,MAAM,6BAA6B,CAAA;AAGpF,QAAA,MAAM,MAAM,0BAAiC,CAAA;AA6H7C,eAAe,MAAM,CAAA"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
2
|
import { HomebridgePluginUiServer, RequestError } from '@homebridge/plugin-ui-utils';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
3
4
|
const server = new HomebridgePluginUiServer();
|
|
4
5
|
// Helper function to find the SwitchBot platform config
|
|
5
6
|
async function getSwitchBotPlatformConfig() {
|
|
@@ -56,9 +57,11 @@ server.onRequest('/devices', async () => {
|
|
|
56
57
|
// ignore malformed platform entries
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
|
-
|
|
60
|
+
console.log('[SwitchBot UI] GET /devices - Found', found.length, 'devices');
|
|
61
|
+
return { success: true, data: found };
|
|
60
62
|
}
|
|
61
63
|
catch (e) {
|
|
64
|
+
console.error('[SwitchBot UI] Error in /devices:', e);
|
|
62
65
|
throw new RequestError('Failed to read Homebridge config', e);
|
|
63
66
|
}
|
|
64
67
|
});
|
|
@@ -68,12 +71,14 @@ server.onRequest('/credentials', async (body) => {
|
|
|
68
71
|
if (!body || Object.keys(body).length === 0) {
|
|
69
72
|
// GET request - return current status
|
|
70
73
|
const { platform } = await getSwitchBotPlatformConfig();
|
|
71
|
-
|
|
74
|
+
const status = {
|
|
72
75
|
hasToken: !!platform.openApiToken,
|
|
73
76
|
hasSecret: !!platform.openApiSecret,
|
|
74
77
|
tokenLength: platform.openApiToken ? String(platform.openApiToken).length : 0,
|
|
75
78
|
secretLength: platform.openApiSecret ? String(platform.openApiSecret).length : 0,
|
|
76
79
|
};
|
|
80
|
+
console.log('[SwitchBot UI] GET /credentials - Status:', status);
|
|
81
|
+
return { success: true, data: status };
|
|
77
82
|
}
|
|
78
83
|
else {
|
|
79
84
|
// POST request - save credentials
|
|
@@ -82,18 +87,23 @@ server.onRequest('/credentials', async (body) => {
|
|
|
82
87
|
throw new Error('Token and secret are required');
|
|
83
88
|
}
|
|
84
89
|
const { config, platform, cfgPath } = await getSwitchBotPlatformConfig();
|
|
90
|
+
console.log('[SwitchBot UI] POST /credentials - Saving to platform:', platform.platform || platform.name);
|
|
91
|
+
console.log('[SwitchBot UI] Config path:', cfgPath);
|
|
92
|
+
console.log('[SwitchBot UI] Token length:', token.length, 'Secret length:', secret.length);
|
|
85
93
|
// Save token and secret directly on the platform config
|
|
86
94
|
platform.openApiToken = token;
|
|
87
95
|
platform.openApiSecret = secret;
|
|
88
96
|
// Write back to config file
|
|
89
97
|
await fs.writeFile(cfgPath, JSON.stringify(config, null, 2), 'utf8');
|
|
98
|
+
console.log('[SwitchBot UI] Credentials saved successfully');
|
|
90
99
|
return {
|
|
91
100
|
success: true,
|
|
92
|
-
message: 'Credentials saved successfully',
|
|
101
|
+
data: { message: 'Credentials saved successfully' },
|
|
93
102
|
};
|
|
94
103
|
}
|
|
95
104
|
}
|
|
96
105
|
catch (e) {
|
|
106
|
+
console.error('[SwitchBot UI] Error in /credentials:', e);
|
|
97
107
|
throw new RequestError('Failed to handle credentials request', e);
|
|
98
108
|
}
|
|
99
109
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/homebridge-ui/server.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/homebridge-ui/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,26 +3,24 @@ export interface ISwitchBotClient {
|
|
|
3
3
|
init(): Promise<void>;
|
|
4
4
|
getDevice(id: string): Promise<any>;
|
|
5
5
|
getDevices(): Promise<any[]>;
|
|
6
|
+
setDeviceState(id: string, body: any): Promise<any>;
|
|
6
7
|
destroy(): Promise<void>;
|
|
7
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Thin wrapper around node-switchbot v4.0.0-beta.2+
|
|
11
|
+
* Leverages upstream resilience features (retry, circuit breaker, connection intelligence)
|
|
12
|
+
* while maintaining plugin-specific features like write debouncing and OpenAPI fallback.
|
|
13
|
+
*/
|
|
8
14
|
export declare class SwitchBotClient implements ISwitchBotClient {
|
|
9
15
|
private cfg;
|
|
10
16
|
private client;
|
|
11
17
|
private baseUrl;
|
|
12
18
|
private requestTimeout;
|
|
13
19
|
private maxRetries;
|
|
14
|
-
private baseBackoffMs;
|
|
15
|
-
private maxBackoffMs;
|
|
16
|
-
private backoffFactor;
|
|
17
|
-
private jitter;
|
|
18
20
|
private writeDebounceMs;
|
|
19
21
|
private logger;
|
|
20
|
-
private perDeviceRetries;
|
|
21
|
-
private perDeviceMaxRetries;
|
|
22
|
-
private perDeviceCooldownMs;
|
|
23
|
-
private perDeviceCooldownUntil;
|
|
24
|
-
constructor(cfg: SwitchBotPluginConfig);
|
|
25
22
|
private pendingWrites;
|
|
23
|
+
constructor(cfg: SwitchBotPluginConfig);
|
|
26
24
|
init(): Promise<void>;
|
|
27
25
|
private fetchWithTimeoutAndRetry;
|
|
28
26
|
getDevice(id: string): Promise<any>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"switchbotClient.d.ts","sourceRoot":"","sources":["../src/switchbotClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAE1D,MAAM,WAAW,gBAAgB;IAC/B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACrB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;IACnC,UAAU,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;IAC5B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACzB;
|
|
1
|
+
{"version":3,"file":"switchbotClient.d.ts","sourceRoot":"","sources":["../src/switchbotClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAE1D,MAAM,WAAW,gBAAgB;IAC/B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACrB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;IACnC,UAAU,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;IAC5B,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;IACnD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACzB;AAED;;;;GAIG;AACH,qBAAa,eAAgB,YAAW,gBAAgB;IACtD,OAAO,CAAC,GAAG,CAAuB;IAClC,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,OAAO,CAAoC;IACnD,OAAO,CAAC,cAAc,CAAO;IAC7B,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,eAAe,CAAM;IAC7B,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,aAAa,CAA0H;gBAEnI,GAAG,EAAE,qBAAqB;IAQhC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAqBb,wBAAwB;IAkChC,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAmBnC,UAAU,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAoB5B,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;YA+B3C,iBAAiB;IAqCzB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAM/B"}
|
package/dist/switchbotClient.js
CHANGED
|
@@ -1,26 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Thin wrapper around node-switchbot v4.0.0-beta.2+
|
|
3
|
+
* Leverages upstream resilience features (retry, circuit breaker, connection intelligence)
|
|
4
|
+
* while maintaining plugin-specific features like write debouncing and OpenAPI fallback.
|
|
5
|
+
*/
|
|
2
6
|
export class SwitchBotClient {
|
|
3
7
|
cfg;
|
|
4
8
|
client = null;
|
|
5
9
|
baseUrl = 'https://api.switch-bot.com/v1.0';
|
|
6
|
-
// configurable network options
|
|
7
10
|
requestTimeout = 5000;
|
|
8
11
|
maxRetries = 2;
|
|
9
|
-
baseBackoffMs = 100;
|
|
10
|
-
maxBackoffMs = 2000;
|
|
11
|
-
backoffFactor = 2;
|
|
12
|
-
jitter = true;
|
|
13
|
-
// per-device write debounce (ms). 0 = disabled. Configurable via plugin options.
|
|
14
|
-
// Default enabled to coalesce rapid writes and prevent command floods.
|
|
15
12
|
writeDebounceMs = 100;
|
|
16
|
-
// structured logger (expects info/warn/error/debug); defaults to console
|
|
17
13
|
logger;
|
|
18
|
-
|
|
19
|
-
perDeviceRetries = new Map();
|
|
20
|
-
perDeviceMaxRetries = 3;
|
|
21
|
-
// per-device cooldown after exceeding retries (ms)
|
|
22
|
-
perDeviceCooldownMs = 60_000;
|
|
23
|
-
perDeviceCooldownUntil = new Map();
|
|
14
|
+
pendingWrites = new Map();
|
|
24
15
|
constructor(cfg) {
|
|
25
16
|
this.cfg = cfg;
|
|
26
17
|
this.logger = cfg?.logger ?? console;
|
|
@@ -28,188 +19,96 @@ export class SwitchBotClient {
|
|
|
28
19
|
this.requestTimeout = cfg.requestTimeout;
|
|
29
20
|
if (typeof cfg?.maxRetries === 'number')
|
|
30
21
|
this.maxRetries = cfg.maxRetries;
|
|
31
|
-
if (typeof cfg?.perDeviceMaxRetries === 'number')
|
|
32
|
-
this.perDeviceMaxRetries = cfg.perDeviceMaxRetries;
|
|
33
|
-
if (typeof cfg?.baseBackoffMs === 'number')
|
|
34
|
-
this.baseBackoffMs = cfg.baseBackoffMs;
|
|
35
|
-
if (typeof cfg?.maxBackoffMs === 'number')
|
|
36
|
-
this.maxBackoffMs = cfg.maxBackoffMs;
|
|
37
|
-
if (typeof cfg?.backoffFactor === 'number')
|
|
38
|
-
this.backoffFactor = cfg.backoffFactor;
|
|
39
|
-
if (typeof cfg?.jitter === 'boolean')
|
|
40
|
-
this.jitter = cfg.jitter;
|
|
41
|
-
if (typeof cfg?.perDeviceCooldownMs === 'number')
|
|
42
|
-
this.perDeviceCooldownMs = cfg.perDeviceCooldownMs;
|
|
43
22
|
if (typeof cfg?.writeDebounceMs === 'number')
|
|
44
23
|
this.writeDebounceMs = cfg.writeDebounceMs;
|
|
45
24
|
}
|
|
46
|
-
// pending writes coalescing: deviceId -> { timer, body, resolvers }
|
|
47
|
-
pendingWrites = new Map();
|
|
48
25
|
async init() {
|
|
49
|
-
// Try to dynamically import the new node-switchbot package. If it exposes
|
|
50
|
-
// a hybrid client we will use it. Otherwise the consumer can implement
|
|
51
|
-
// OpenAPI fallback logic here.
|
|
52
26
|
try {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
27
|
+
// Dynamic import of node-switchbot v4 with native resilience features
|
|
28
|
+
const { SwitchBot } = await import('node-switchbot');
|
|
29
|
+
this.client = new SwitchBot({
|
|
30
|
+
token: this.cfg.openApiToken,
|
|
31
|
+
secret: this.cfg.openApiSecret,
|
|
32
|
+
// Enable all built-in resilience features from node-switchbot v4
|
|
33
|
+
enableFallback: true, // Auto-fallback from BLE to API
|
|
34
|
+
enableRetry: true, // Retry with exponential backoff
|
|
35
|
+
enableCircuitBreaker: true, // Circuit breaker per connection type
|
|
36
|
+
enableMetrics: true, // Connection tracking and statistics
|
|
37
|
+
...(typeof this.cfg?.nodeClientConfig === 'object' && this.cfg.nodeClientConfig),
|
|
38
|
+
});
|
|
39
|
+
this.logger?.info?.('SwitchBot client initialized with native resilience features');
|
|
61
40
|
}
|
|
62
41
|
catch (e) {
|
|
63
|
-
|
|
64
|
-
this.logger?.warn?.('node-switchbot import failed; falling back to OpenAPI when token available');
|
|
42
|
+
this.logger?.warn?.('Failed to load node-switchbot; will use OpenAPI fallback:', e);
|
|
65
43
|
this.client = null;
|
|
66
44
|
}
|
|
67
45
|
}
|
|
68
|
-
async fetchWithTimeoutAndRetry(url, opts = {}, timeoutMs, retries
|
|
46
|
+
async fetchWithTimeoutAndRetry(url, opts = {}, timeoutMs, retries) {
|
|
69
47
|
const to = typeof timeoutMs === 'number' ? timeoutMs : this.requestTimeout;
|
|
70
48
|
const max = typeof retries === 'number' ? retries : this.maxRetries;
|
|
71
|
-
// Check per-device cooldown
|
|
72
|
-
if (deviceId) {
|
|
73
|
-
const until = this.perDeviceCooldownUntil.get(deviceId) ?? 0;
|
|
74
|
-
if (Date.now() < until) {
|
|
75
|
-
const ms = until - Date.now();
|
|
76
|
-
this.logger?.warn?.('device in cooldown, aborting fetch', { deviceId, cooldownMs: ms });
|
|
77
|
-
throw new Error(`device ${deviceId} in cooldown for ${ms}ms`);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
49
|
let attempt = 0;
|
|
81
50
|
while (true) {
|
|
82
51
|
attempt += 1;
|
|
83
|
-
// AbortController-based timeout
|
|
84
52
|
const controller = new AbortController();
|
|
85
53
|
const timer = setTimeout(() => controller.abort(), to);
|
|
86
54
|
try {
|
|
87
|
-
this.logger?.debug?.('fetch', { url, attempt, deviceId });
|
|
88
55
|
const fetchOpts = Object.assign({}, opts);
|
|
89
56
|
try {
|
|
90
57
|
Object.defineProperty(fetchOpts, 'signal', { value: controller.signal, enumerable: false, configurable: true });
|
|
91
58
|
}
|
|
92
59
|
catch (_e) {
|
|
93
|
-
// ignore
|
|
60
|
+
// ignore
|
|
94
61
|
}
|
|
95
62
|
const resp = await fetch(url, fetchOpts);
|
|
96
63
|
clearTimeout(timer);
|
|
97
|
-
|
|
98
|
-
const safeReadBody = async (r) => {
|
|
99
|
-
try {
|
|
100
|
-
if (!r)
|
|
101
|
-
return '';
|
|
102
|
-
if (typeof r.text === 'function')
|
|
103
|
-
return await r.text();
|
|
104
|
-
if (typeof r.json === 'function') {
|
|
105
|
-
const j = await r.json().catch(() => null);
|
|
106
|
-
return j ? JSON.stringify(j) : '';
|
|
107
|
-
}
|
|
108
|
-
return String(r);
|
|
109
|
-
}
|
|
110
|
-
catch (_e) {
|
|
111
|
-
return '';
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
// Some tests/mocks provide response-like objects without numeric `status`.
|
|
115
|
-
// In those cases attempt to parse the body and return a Response-like
|
|
116
|
-
// object so callers (resp.json()) continue to work instead of forcing
|
|
117
|
-
// a retry path.
|
|
118
|
-
if (typeof resp.status !== 'number') {
|
|
119
|
-
const bodyStr = await safeReadBody(resp);
|
|
120
|
-
try {
|
|
121
|
-
const parsed = bodyStr ? JSON.parse(bodyStr) : {};
|
|
122
|
-
if (deviceId)
|
|
123
|
-
this.perDeviceRetries.set(deviceId, 0);
|
|
124
|
-
return { ok: true, status: 200, json: async () => parsed };
|
|
125
|
-
}
|
|
126
|
-
catch (_e) {
|
|
127
|
-
if (deviceId)
|
|
128
|
-
this.perDeviceRetries.set(deviceId, 0);
|
|
129
|
-
return { ok: true, status: 200, json: async () => ({ body: bodyStr }) };
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
// classify response
|
|
133
|
-
if (resp.ok) {
|
|
134
|
-
if (deviceId)
|
|
135
|
-
this.perDeviceRetries.set(deviceId, 0);
|
|
64
|
+
if (resp.ok)
|
|
136
65
|
return resp;
|
|
66
|
+
// server error / rate limit: retry
|
|
67
|
+
if (resp.status >= 500 || resp.status === 429) {
|
|
68
|
+
throw new Error(`retriable response: ${resp.status}`);
|
|
137
69
|
}
|
|
138
|
-
|
|
139
|
-
if (resp.status >= 400 && resp.status < 500 && resp.status !== 429) {
|
|
140
|
-
const body = await safeReadBody(resp);
|
|
141
|
-
this.logger?.error?.('fetch non-retriable response', { url, status: resp.status, body });
|
|
142
|
-
const err = new Error(`HTTP ${resp.status}`);
|
|
143
|
-
err.status = resp.status;
|
|
144
|
-
throw err;
|
|
145
|
-
}
|
|
146
|
-
// server error / rate limit: treat as retriable
|
|
147
|
-
const body = await safeReadBody(resp);
|
|
148
|
-
this.logger?.warn?.('fetch retriable response', { url, status: resp.status, body });
|
|
149
|
-
// throw to enter retry/catch logic below and apply backoff
|
|
150
|
-
throw new Error(`retriable response: ${resp.status || 'unknown'}`);
|
|
70
|
+
return resp;
|
|
151
71
|
}
|
|
152
72
|
catch (err) {
|
|
153
73
|
clearTimeout(timer);
|
|
154
|
-
const isAbort = err?.name === 'AbortError' || err?.message === 'The user aborted a request.' || err?.message === 'timeout';
|
|
155
|
-
const reason = isAbort ? 'timeout' : err?.message ?? String(err);
|
|
156
|
-
this.logger?.warn?.('fetch failed', { url, attempt, deviceId, reason });
|
|
157
|
-
// per-device retry guard
|
|
158
|
-
if (deviceId) {
|
|
159
|
-
const prev = this.perDeviceRetries.get(deviceId) ?? 0;
|
|
160
|
-
const next = prev + 1;
|
|
161
|
-
this.perDeviceRetries.set(deviceId, next);
|
|
162
|
-
if (next > this.perDeviceMaxRetries) {
|
|
163
|
-
this.logger?.error?.('per-device retry limit exceeded, entering cooldown', { deviceId, attempts: next });
|
|
164
|
-
this.perDeviceCooldownUntil.set(deviceId, Date.now() + this.perDeviceCooldownMs);
|
|
165
|
-
throw new Error(`per-device retry limit exceeded for ${deviceId}`);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
74
|
if (attempt > max)
|
|
169
75
|
throw err;
|
|
170
|
-
// exponential backoff
|
|
171
|
-
|
|
172
|
-
if (this.jitter) {
|
|
173
|
-
const jitterVal = Math.floor(Math.random() * backoff);
|
|
174
|
-
backoff = Math.floor(backoff / 2) + jitterVal;
|
|
175
|
-
}
|
|
176
|
-
this.logger?.debug?.('backoff before retry', { url, attempt, backoff });
|
|
76
|
+
// exponential backoff
|
|
77
|
+
const backoff = Math.min(100 * Math.pow(2, attempt - 1), 2000);
|
|
177
78
|
await new Promise((r) => setTimeout(r, backoff));
|
|
178
79
|
}
|
|
179
80
|
}
|
|
180
81
|
}
|
|
181
82
|
async getDevice(id) {
|
|
182
|
-
// Try client API first (
|
|
183
|
-
if (this.client
|
|
83
|
+
// Try client API first (with node-switchbot's smart fallback and retry)
|
|
84
|
+
if (this.client?.getDevice) {
|
|
184
85
|
try {
|
|
185
86
|
return await this.client.getDevice(id);
|
|
186
87
|
}
|
|
187
88
|
catch (e) {
|
|
188
|
-
|
|
189
|
-
this.logger?.warn?.(`Client getDevice failed for ${id}, falling back to OpenAPI:`, e);
|
|
89
|
+
this.logger?.warn?.(`Client getDevice failed for ${id}, trying OpenAPI fallback:`, e);
|
|
190
90
|
}
|
|
191
91
|
}
|
|
192
|
-
// Fallback: call OpenAPI via HTTP
|
|
92
|
+
// Fallback: call OpenAPI via HTTP
|
|
193
93
|
if (this.cfg.openApiToken) {
|
|
194
94
|
const url = `${this.baseUrl}/devices/${id}`;
|
|
195
95
|
const opts = { headers: { Authorization: this.cfg.openApiToken } };
|
|
196
|
-
const resp = await this.fetchWithTimeoutAndRetry(url, opts
|
|
96
|
+
const resp = await this.fetchWithTimeoutAndRetry(url, opts);
|
|
197
97
|
return resp.json();
|
|
198
98
|
}
|
|
199
99
|
throw new Error('No SwitchBot client available');
|
|
200
100
|
}
|
|
201
101
|
async getDevices() {
|
|
202
|
-
// Try client API first
|
|
203
|
-
if (this.client
|
|
102
|
+
// Try client API first
|
|
103
|
+
if (this.client?.getDevices) {
|
|
204
104
|
try {
|
|
205
105
|
return await this.client.getDevices();
|
|
206
106
|
}
|
|
207
107
|
catch (e) {
|
|
208
|
-
|
|
209
|
-
this.logger?.warn?.('Client getDevices failed, falling back to OpenAPI:', e);
|
|
108
|
+
this.logger?.warn?.('Client getDevices failed, trying OpenAPI fallback:', e);
|
|
210
109
|
}
|
|
211
110
|
}
|
|
212
|
-
// Fallback: call OpenAPI
|
|
111
|
+
// Fallback: call OpenAPI
|
|
213
112
|
if (this.cfg.openApiToken) {
|
|
214
113
|
const url = `${this.baseUrl}/devices`;
|
|
215
114
|
const opts = { headers: { Authorization: this.cfg.openApiToken } };
|
|
@@ -217,21 +116,18 @@ export class SwitchBotClient {
|
|
|
217
116
|
const data = await resp.json();
|
|
218
117
|
return (data?.body || data);
|
|
219
118
|
}
|
|
220
|
-
this.logger?.warn('No SwitchBot client available for device discovery');
|
|
221
119
|
return [];
|
|
222
120
|
}
|
|
223
121
|
async setDeviceState(id, body) {
|
|
224
|
-
//
|
|
122
|
+
// Plugin-level debounce: coalesce rapid writes per device
|
|
225
123
|
if (!this.writeDebounceMs || this.writeDebounceMs <= 0) {
|
|
226
124
|
return this._doSetDeviceState(id, body);
|
|
227
125
|
}
|
|
228
|
-
// Coalesce writes per-device within debounce window. Last write wins.
|
|
229
126
|
return new Promise((resolve, reject) => {
|
|
230
127
|
const existing = this.pendingWrites.get(id);
|
|
231
128
|
if (existing) {
|
|
232
129
|
existing.body = body;
|
|
233
130
|
existing.resolvers.push({ resolve, reject });
|
|
234
|
-
// timer already scheduled
|
|
235
131
|
return;
|
|
236
132
|
}
|
|
237
133
|
const resolvers = [{ resolve, reject }];
|
|
@@ -254,24 +150,22 @@ export class SwitchBotClient {
|
|
|
254
150
|
});
|
|
255
151
|
}
|
|
256
152
|
async _doSetDeviceState(id, body) {
|
|
257
|
-
// Prefer client API
|
|
258
|
-
if (this.client
|
|
153
|
+
// Prefer client API (which has node-switchbot's native resilience)
|
|
154
|
+
if (this.client?.setDeviceState) {
|
|
259
155
|
try {
|
|
260
156
|
return await this.client.setDeviceState(id, body);
|
|
261
157
|
}
|
|
262
158
|
catch (e) {
|
|
263
|
-
// Fall through to other methods on error
|
|
264
159
|
this.logger?.warn?.(`Client setDeviceState failed for ${id}, trying fallback:`, e);
|
|
265
160
|
}
|
|
266
161
|
}
|
|
267
|
-
// Try generic
|
|
268
|
-
if (this.client
|
|
162
|
+
// Try generic sendCommand if available
|
|
163
|
+
if (this.client?.sendCommand) {
|
|
269
164
|
try {
|
|
270
165
|
return await this.client.sendCommand(id, body);
|
|
271
166
|
}
|
|
272
167
|
catch (e) {
|
|
273
|
-
|
|
274
|
-
this.logger?.warn?.(`Client sendCommand failed for ${id}, falling back to OpenAPI:`, e);
|
|
168
|
+
this.logger?.warn?.(`Client sendCommand failed for ${id}, trying OpenAPI:`, e);
|
|
275
169
|
}
|
|
276
170
|
}
|
|
277
171
|
// OpenAPI fallback
|
|
@@ -285,13 +179,13 @@ export class SwitchBotClient {
|
|
|
285
179
|
},
|
|
286
180
|
body: JSON.stringify(body),
|
|
287
181
|
};
|
|
288
|
-
const resp = await this.fetchWithTimeoutAndRetry(url, opts
|
|
182
|
+
const resp = await this.fetchWithTimeoutAndRetry(url, opts);
|
|
289
183
|
return resp.json();
|
|
290
184
|
}
|
|
291
185
|
throw new Error('No SwitchBot client available for setDeviceState');
|
|
292
186
|
}
|
|
293
187
|
async destroy() {
|
|
294
|
-
if (this.client
|
|
188
|
+
if (this.client?.destroy) {
|
|
295
189
|
await this.client.destroy();
|
|
296
190
|
}
|
|
297
191
|
this.client = null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"switchbotClient.js","sourceRoot":"","sources":["../src/switchbotClient.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"switchbotClient.js","sourceRoot":"","sources":["../src/switchbotClient.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/fdf94c065018b63f189137635c0d74689aa57769/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/eslint.config.js
CHANGED
|
@@ -10,7 +10,6 @@ export default antfu(
|
|
|
10
10
|
},
|
|
11
11
|
rules: {
|
|
12
12
|
'curly': ['error', 'multi-line'],
|
|
13
|
-
'import/order': 0,
|
|
14
13
|
'jsdoc/check-alignment': 'error',
|
|
15
14
|
'jsdoc/check-line-alignment': 'error',
|
|
16
15
|
'perfectionist/sort-exports': 'error',
|
|
@@ -18,15 +17,10 @@ export default antfu(
|
|
|
18
17
|
'error',
|
|
19
18
|
{
|
|
20
19
|
groups: [
|
|
21
|
-
'
|
|
22
|
-
'external
|
|
23
|
-
'internal-type',
|
|
24
|
-
['parent-type', 'sibling-type', 'index-type'],
|
|
25
|
-
'builtin',
|
|
26
|
-
'external',
|
|
20
|
+
'type',
|
|
21
|
+
['builtin', 'external'],
|
|
27
22
|
'internal',
|
|
28
23
|
['parent', 'sibling', 'index'],
|
|
29
|
-
'object',
|
|
30
24
|
'unknown',
|
|
31
25
|
],
|
|
32
26
|
order: 'asc',
|
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.77",
|
|
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": [
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"ir"
|
|
53
53
|
],
|
|
54
54
|
"main": "dist/index.js",
|
|
55
|
+
"bin": {},
|
|
55
56
|
"icon": "https://raw.githubusercontent.com/OpenWonderLabs/homebridge-switchbot/latest/branding/icon.png",
|
|
56
57
|
"engineStrict": true,
|
|
57
58
|
"engines": {
|
|
@@ -77,36 +78,27 @@
|
|
|
77
78
|
"docs": "typedoc",
|
|
78
79
|
"docs:lint": "typedoc --emit none --treatWarningsAsErrors"
|
|
79
80
|
},
|
|
80
|
-
"bin": {},
|
|
81
81
|
"publishConfig": {
|
|
82
82
|
"access": "public"
|
|
83
83
|
},
|
|
84
84
|
"dependencies": {
|
|
85
|
-
"@homebridge/plugin-ui-utils": "^2.2.
|
|
86
|
-
"
|
|
87
|
-
"fakegato-history": "^0.6.7",
|
|
88
|
-
"homebridge-lib": "^7.3.1",
|
|
89
|
-
"node-switchbot": "^4.0.0-beta.1",
|
|
90
|
-
"rxjs": "^7.8.2"
|
|
85
|
+
"@homebridge/plugin-ui-utils": "^2.2.1",
|
|
86
|
+
"node-switchbot": "^4.0.0-beta.2"
|
|
91
87
|
},
|
|
92
88
|
"devDependencies": {
|
|
93
|
-
"@antfu/eslint-config": "^6.
|
|
94
|
-
"@types/aes-js": "^3.1.4",
|
|
89
|
+
"@antfu/eslint-config": "^7.6.1",
|
|
95
90
|
"@types/debug": "^4.1.12",
|
|
96
91
|
"@types/fs-extra": "^11.0.4",
|
|
97
|
-
"@types/
|
|
98
|
-
"@types/node": "^24.11.0",
|
|
92
|
+
"@types/node": "^25.3.3",
|
|
99
93
|
"@types/semver": "^7.7.1",
|
|
100
94
|
"@types/source-map-support": "^0.5.10",
|
|
101
95
|
"@vitest/coverage-v8": "^4.0.18",
|
|
102
|
-
"eslint": "^
|
|
103
|
-
"eslint-plugin-format": "^
|
|
104
|
-
"eslint-plugin-import": "^2.32.0",
|
|
96
|
+
"eslint": "^10.0.2",
|
|
97
|
+
"eslint-plugin-format": "^2.0.1",
|
|
105
98
|
"homebridge": "^2.0.0-beta.76",
|
|
106
|
-
"homebridge-config-ui-x": "^5.
|
|
99
|
+
"homebridge-config-ui-x": "^5.19.0",
|
|
107
100
|
"nodemon": "^3.1.14",
|
|
108
101
|
"shx": "^0.4.0",
|
|
109
|
-
"ts-node": "^10.9.2",
|
|
110
102
|
"typedoc": "^0.28.17",
|
|
111
103
|
"typescript": "^5.9.3",
|
|
112
104
|
"vitest": "^4.0.18"
|
|
@@ -59,11 +59,11 @@
|
|
|
59
59
|
async function loadCredentialStatus() {
|
|
60
60
|
try {
|
|
61
61
|
const resp = await homebridge.request('/credentials', {})
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
console.log('Load credentials response:', resp)
|
|
63
|
+
|
|
64
|
+
if (!resp || resp.success === false) {
|
|
65
|
+
console.error('Failed to load credentials:', resp)
|
|
66
|
+
return
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
const creds = resp.data || {}
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
tokenStatus.classList.add('ok')
|
|
76
76
|
} else {
|
|
77
77
|
tokenStatus.textContent = 'Not configured'
|
|
78
|
+
tokenStatus.classList.remove('ok')
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
if (creds.hasSecret) {
|
|
@@ -82,6 +83,7 @@
|
|
|
82
83
|
secretStatus.classList.add('ok')
|
|
83
84
|
} else {
|
|
84
85
|
secretStatus.textContent = 'Not configured'
|
|
86
|
+
secretStatus.classList.remove('ok')
|
|
85
87
|
}
|
|
86
88
|
} catch (e) {
|
|
87
89
|
console.error('Error loading credentials:', e)
|
|
@@ -104,12 +106,16 @@
|
|
|
104
106
|
saveBtn.disabled = true
|
|
105
107
|
saveBtn.textContent = 'Saving...'
|
|
106
108
|
|
|
109
|
+
console.log('Saving credentials...')
|
|
107
110
|
const resp = await homebridge.request('/credentials', { token, secret })
|
|
111
|
+
console.log('Save response:', resp)
|
|
112
|
+
|
|
108
113
|
if (!resp || resp.success === false) {
|
|
109
|
-
throw new Error(resp?.
|
|
114
|
+
throw new Error(resp?.message || 'Save failed')
|
|
110
115
|
}
|
|
111
116
|
|
|
112
|
-
|
|
117
|
+
const result = resp.data || resp
|
|
118
|
+
saveStatus.textContent = '✓ ' + (result.message || 'Credentials saved successfully')
|
|
113
119
|
saveStatus.classList.remove('error')
|
|
114
120
|
saveStatus.classList.add('success-msg')
|
|
115
121
|
|
|
@@ -117,8 +123,8 @@
|
|
|
117
123
|
document.getElementById('token').value = ''
|
|
118
124
|
document.getElementById('secret').value = ''
|
|
119
125
|
|
|
120
|
-
// Reload status
|
|
121
|
-
setTimeout(() => loadCredentialStatus(),
|
|
126
|
+
// Reload status to verify save
|
|
127
|
+
setTimeout(() => loadCredentialStatus(), 500)
|
|
122
128
|
|
|
123
129
|
// Clear status message
|
|
124
130
|
setTimeout(() => {
|
|
@@ -126,6 +132,7 @@
|
|
|
126
132
|
saveStatus.classList.remove('success-msg')
|
|
127
133
|
}, 3000)
|
|
128
134
|
} catch (e) {
|
|
135
|
+
console.error('Save error:', e)
|
|
129
136
|
saveStatus.textContent = 'Error: ' + (e?.message || 'Failed to save')
|
|
130
137
|
saveStatus.classList.add('error')
|
|
131
138
|
} finally {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/* eslint-disable no-console */
|
|
3
2
|
import { HomebridgePluginUiServer, RequestError } from '@homebridge/plugin-ui-utils'
|
|
3
|
+
import fs from 'node:fs/promises'
|
|
4
4
|
|
|
5
5
|
const server = new HomebridgePluginUiServer()
|
|
6
6
|
|
|
@@ -67,8 +67,10 @@ server.onRequest('/devices', async () => {
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
console.log('[SwitchBot UI] GET /devices - Found', found.length, 'devices')
|
|
71
|
+
return { success: true, data: found }
|
|
71
72
|
} catch (e) {
|
|
73
|
+
console.error('[SwitchBot UI] Error in /devices:', e)
|
|
72
74
|
throw new RequestError('Failed to read Homebridge config', e)
|
|
73
75
|
}
|
|
74
76
|
})
|
|
@@ -80,12 +82,15 @@ server.onRequest('/credentials', async (body: any) => {
|
|
|
80
82
|
// GET request - return current status
|
|
81
83
|
const { platform } = await getSwitchBotPlatformConfig()
|
|
82
84
|
|
|
83
|
-
|
|
85
|
+
const status = {
|
|
84
86
|
hasToken: !!platform.openApiToken,
|
|
85
87
|
hasSecret: !!platform.openApiSecret,
|
|
86
88
|
tokenLength: platform.openApiToken ? String(platform.openApiToken).length : 0,
|
|
87
89
|
secretLength: platform.openApiSecret ? String(platform.openApiSecret).length : 0,
|
|
88
90
|
}
|
|
91
|
+
|
|
92
|
+
console.log('[SwitchBot UI] GET /credentials - Status:', status)
|
|
93
|
+
return { success: true, data: status }
|
|
89
94
|
} else {
|
|
90
95
|
// POST request - save credentials
|
|
91
96
|
const { token, secret } = body
|
|
@@ -96,6 +101,10 @@ server.onRequest('/credentials', async (body: any) => {
|
|
|
96
101
|
|
|
97
102
|
const { config, platform, cfgPath } = await getSwitchBotPlatformConfig()
|
|
98
103
|
|
|
104
|
+
console.log('[SwitchBot UI] POST /credentials - Saving to platform:', platform.platform || platform.name)
|
|
105
|
+
console.log('[SwitchBot UI] Config path:', cfgPath)
|
|
106
|
+
console.log('[SwitchBot UI] Token length:', token.length, 'Secret length:', secret.length)
|
|
107
|
+
|
|
99
108
|
// Save token and secret directly on the platform config
|
|
100
109
|
platform.openApiToken = token
|
|
101
110
|
platform.openApiSecret = secret
|
|
@@ -103,12 +112,15 @@ server.onRequest('/credentials', async (body: any) => {
|
|
|
103
112
|
// Write back to config file
|
|
104
113
|
await fs.writeFile(cfgPath, JSON.stringify(config, null, 2), 'utf8')
|
|
105
114
|
|
|
115
|
+
console.log('[SwitchBot UI] Credentials saved successfully')
|
|
116
|
+
|
|
106
117
|
return {
|
|
107
118
|
success: true,
|
|
108
|
-
message: 'Credentials saved successfully',
|
|
119
|
+
data: { message: 'Credentials saved successfully' },
|
|
109
120
|
}
|
|
110
121
|
}
|
|
111
122
|
} catch (e) {
|
|
123
|
+
console.error('[SwitchBot UI] Error in /credentials:', e)
|
|
112
124
|
throw new RequestError('Failed to handle credentials request', e)
|
|
113
125
|
}
|
|
114
126
|
})
|
package/src/switchbotClient.ts
CHANGED
|
@@ -4,215 +4,117 @@ export interface ISwitchBotClient {
|
|
|
4
4
|
init(): Promise<void>
|
|
5
5
|
getDevice(id: string): Promise<any>
|
|
6
6
|
getDevices(): Promise<any[]>
|
|
7
|
+
setDeviceState(id: string, body: any): Promise<any>
|
|
7
8
|
destroy(): Promise<void>
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Thin wrapper around node-switchbot v4.0.0-beta.2+
|
|
13
|
+
* Leverages upstream resilience features (retry, circuit breaker, connection intelligence)
|
|
14
|
+
* while maintaining plugin-specific features like write debouncing and OpenAPI fallback.
|
|
15
|
+
*/
|
|
11
16
|
export class SwitchBotClient implements ISwitchBotClient {
|
|
12
17
|
private cfg: SwitchBotPluginConfig
|
|
13
18
|
private client: any | null = null
|
|
14
19
|
private baseUrl = 'https://api.switch-bot.com/v1.0'
|
|
15
|
-
// configurable network options
|
|
16
20
|
private requestTimeout = 5000
|
|
17
21
|
private maxRetries = 2
|
|
18
|
-
private baseBackoffMs = 100
|
|
19
|
-
private maxBackoffMs = 2000
|
|
20
|
-
private backoffFactor = 2
|
|
21
|
-
private jitter = true
|
|
22
|
-
// per-device write debounce (ms). 0 = disabled. Configurable via plugin options.
|
|
23
|
-
// Default enabled to coalesce rapid writes and prevent command floods.
|
|
24
22
|
private writeDebounceMs = 100
|
|
25
|
-
// structured logger (expects info/warn/error/debug); defaults to console
|
|
26
23
|
private logger: any
|
|
27
|
-
|
|
28
|
-
private perDeviceRetries: Map<string, number> = new Map()
|
|
29
|
-
private perDeviceMaxRetries = 3
|
|
30
|
-
// per-device cooldown after exceeding retries (ms)
|
|
31
|
-
private perDeviceCooldownMs = 60_000
|
|
32
|
-
private perDeviceCooldownUntil: Map<string, number> = new Map()
|
|
24
|
+
private pendingWrites: Map<string, { timer: any; body: any; resolvers: Array<{ resolve: (v:any)=>void; reject: (e:any)=>void }>; }> = new Map()
|
|
33
25
|
|
|
34
26
|
constructor(cfg: SwitchBotPluginConfig) {
|
|
35
27
|
this.cfg = cfg
|
|
36
28
|
this.logger = (cfg as any)?.logger ?? console
|
|
37
29
|
if (typeof (cfg as any)?.requestTimeout === 'number') this.requestTimeout = (cfg as any).requestTimeout
|
|
38
30
|
if (typeof (cfg as any)?.maxRetries === 'number') this.maxRetries = (cfg as any).maxRetries
|
|
39
|
-
if (typeof (cfg as any)?.perDeviceMaxRetries === 'number') this.perDeviceMaxRetries = (cfg as any).perDeviceMaxRetries
|
|
40
|
-
if (typeof (cfg as any)?.baseBackoffMs === 'number') this.baseBackoffMs = (cfg as any).baseBackoffMs
|
|
41
|
-
if (typeof (cfg as any)?.maxBackoffMs === 'number') this.maxBackoffMs = (cfg as any).maxBackoffMs
|
|
42
|
-
if (typeof (cfg as any)?.backoffFactor === 'number') this.backoffFactor = (cfg as any).backoffFactor
|
|
43
|
-
if (typeof (cfg as any)?.jitter === 'boolean') this.jitter = (cfg as any).jitter
|
|
44
|
-
if (typeof (cfg as any)?.perDeviceCooldownMs === 'number') this.perDeviceCooldownMs = (cfg as any).perDeviceCooldownMs
|
|
45
31
|
if (typeof (cfg as any)?.writeDebounceMs === 'number') this.writeDebounceMs = (cfg as any).writeDebounceMs
|
|
46
32
|
}
|
|
47
33
|
|
|
48
|
-
// pending writes coalescing: deviceId -> { timer, body, resolvers }
|
|
49
|
-
private pendingWrites: Map<string, { timer: any; body: any; resolvers: Array<{ resolve: (v:any)=>void; reject: (e:any)=>void }>; }> = new Map()
|
|
50
|
-
|
|
51
34
|
async init(): Promise<void> {
|
|
52
|
-
// Try to dynamically import the new node-switchbot package. If it exposes
|
|
53
|
-
// a hybrid client we will use it. Otherwise the consumer can implement
|
|
54
|
-
// OpenAPI fallback logic here.
|
|
55
35
|
try {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
36
|
+
// Dynamic import of node-switchbot v4 with native resilience features
|
|
37
|
+
const { SwitchBot } = await import('node-switchbot')
|
|
38
|
+
this.client = new SwitchBot({
|
|
39
|
+
token: this.cfg.openApiToken,
|
|
40
|
+
secret: this.cfg.openApiSecret,
|
|
41
|
+
// Enable all built-in resilience features from node-switchbot v4
|
|
42
|
+
enableFallback: true, // Auto-fallback from BLE to API
|
|
43
|
+
enableRetry: true, // Retry with exponential backoff
|
|
44
|
+
enableCircuitBreaker: true, // Circuit breaker per connection type
|
|
45
|
+
enableMetrics: true, // Connection tracking and statistics
|
|
46
|
+
...(typeof (this.cfg as any)?.nodeClientConfig === 'object' && (this.cfg as any).nodeClientConfig),
|
|
47
|
+
})
|
|
48
|
+
this.logger?.info?.('SwitchBot client initialized with native resilience features')
|
|
64
49
|
} catch (e) {
|
|
65
|
-
|
|
66
|
-
this.logger?.warn?.('node-switchbot import failed; falling back to OpenAPI when token available')
|
|
50
|
+
this.logger?.warn?.('Failed to load node-switchbot; will use OpenAPI fallback:', e)
|
|
67
51
|
this.client = null
|
|
68
52
|
}
|
|
69
53
|
}
|
|
70
54
|
|
|
71
|
-
private async fetchWithTimeoutAndRetry(url: string, opts: any = {}, timeoutMs?: number, retries?: number
|
|
55
|
+
private async fetchWithTimeoutAndRetry(url: string, opts: any = {}, timeoutMs?: number, retries?: number) {
|
|
72
56
|
const to = typeof timeoutMs === 'number' ? timeoutMs : this.requestTimeout
|
|
73
57
|
const max = typeof retries === 'number' ? retries : this.maxRetries
|
|
74
58
|
|
|
75
|
-
// Check per-device cooldown
|
|
76
|
-
if (deviceId) {
|
|
77
|
-
const until = this.perDeviceCooldownUntil.get(deviceId) ?? 0
|
|
78
|
-
if (Date.now() < until) {
|
|
79
|
-
const ms = until - Date.now()
|
|
80
|
-
this.logger?.warn?.('device in cooldown, aborting fetch', { deviceId, cooldownMs: ms })
|
|
81
|
-
throw new Error(`device ${deviceId} in cooldown for ${ms}ms`)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
59
|
let attempt = 0
|
|
86
60
|
while (true) {
|
|
87
61
|
attempt += 1
|
|
88
|
-
// AbortController-based timeout
|
|
89
62
|
const controller = new AbortController()
|
|
90
63
|
const timer = setTimeout(() => controller.abort(), to)
|
|
91
64
|
try {
|
|
92
|
-
this.logger?.debug?.('fetch', { url, attempt, deviceId })
|
|
93
65
|
const fetchOpts = Object.assign({}, opts)
|
|
94
66
|
try {
|
|
95
67
|
Object.defineProperty(fetchOpts, 'signal', { value: controller.signal, enumerable: false, configurable: true })
|
|
96
68
|
} catch (_e) {
|
|
97
|
-
// ignore
|
|
69
|
+
// ignore
|
|
98
70
|
}
|
|
99
71
|
const resp = await fetch(url, fetchOpts)
|
|
100
72
|
clearTimeout(timer)
|
|
101
|
-
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (!r) return ''
|
|
106
|
-
if (typeof r.text === 'function') return await r.text()
|
|
107
|
-
if (typeof r.json === 'function') {
|
|
108
|
-
const j = await r.json().catch(() => null)
|
|
109
|
-
return j ? JSON.stringify(j) : ''
|
|
110
|
-
}
|
|
111
|
-
return String(r)
|
|
112
|
-
} catch (_e) {
|
|
113
|
-
return ''
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Some tests/mocks provide response-like objects without numeric `status`.
|
|
118
|
-
// In those cases attempt to parse the body and return a Response-like
|
|
119
|
-
// object so callers (resp.json()) continue to work instead of forcing
|
|
120
|
-
// a retry path.
|
|
121
|
-
if (typeof (resp as any).status !== 'number') {
|
|
122
|
-
const bodyStr = await safeReadBody(resp)
|
|
123
|
-
try {
|
|
124
|
-
const parsed = bodyStr ? JSON.parse(bodyStr) : {}
|
|
125
|
-
if (deviceId) this.perDeviceRetries.set(deviceId, 0)
|
|
126
|
-
return { ok: true, status: 200, json: async () => parsed }
|
|
127
|
-
} catch (_e) {
|
|
128
|
-
if (deviceId) this.perDeviceRetries.set(deviceId, 0)
|
|
129
|
-
return { ok: true, status: 200, json: async () => ({ body: bodyStr }) }
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// classify response
|
|
134
|
-
if (resp.ok) {
|
|
135
|
-
if (deviceId) this.perDeviceRetries.set(deviceId, 0)
|
|
136
|
-
return resp
|
|
73
|
+
if (resp.ok) return resp
|
|
74
|
+
// server error / rate limit: retry
|
|
75
|
+
if (resp.status >= 500 || resp.status === 429) {
|
|
76
|
+
throw new Error(`retriable response: ${resp.status}`)
|
|
137
77
|
}
|
|
138
|
-
|
|
139
|
-
// Do not retry on client errors (4xx), except 429 (rate limit)
|
|
140
|
-
if (resp.status >= 400 && resp.status < 500 && resp.status !== 429) {
|
|
141
|
-
const body = await safeReadBody(resp)
|
|
142
|
-
this.logger?.error?.('fetch non-retriable response', { url, status: resp.status, body })
|
|
143
|
-
const err = new Error(`HTTP ${resp.status}`)
|
|
144
|
-
;(err as any).status = resp.status
|
|
145
|
-
throw err
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// server error / rate limit: treat as retriable
|
|
149
|
-
const body = await safeReadBody(resp)
|
|
150
|
-
this.logger?.warn?.('fetch retriable response', { url, status: resp.status, body })
|
|
151
|
-
// throw to enter retry/catch logic below and apply backoff
|
|
152
|
-
throw new Error(`retriable response: ${resp.status || 'unknown'}`)
|
|
78
|
+
return resp
|
|
153
79
|
} catch (err: any) {
|
|
154
80
|
clearTimeout(timer)
|
|
155
|
-
const isAbort = err?.name === 'AbortError' || err?.message === 'The user aborted a request.' || err?.message === 'timeout'
|
|
156
|
-
const reason = isAbort ? 'timeout' : err?.message ?? String(err)
|
|
157
|
-
this.logger?.warn?.('fetch failed', { url, attempt, deviceId, reason })
|
|
158
|
-
|
|
159
|
-
// per-device retry guard
|
|
160
|
-
if (deviceId) {
|
|
161
|
-
const prev = this.perDeviceRetries.get(deviceId) ?? 0
|
|
162
|
-
const next = prev + 1
|
|
163
|
-
this.perDeviceRetries.set(deviceId, next)
|
|
164
|
-
if (next > this.perDeviceMaxRetries) {
|
|
165
|
-
this.logger?.error?.('per-device retry limit exceeded, entering cooldown', { deviceId, attempts: next })
|
|
166
|
-
this.perDeviceCooldownUntil.set(deviceId, Date.now() + this.perDeviceCooldownMs)
|
|
167
|
-
throw new Error(`per-device retry limit exceeded for ${deviceId}`)
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
81
|
if (attempt > max) throw err
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
let backoff = Math.min(this.baseBackoffMs * Math.pow(this.backoffFactor, attempt - 1), this.maxBackoffMs)
|
|
175
|
-
if (this.jitter) {
|
|
176
|
-
const jitterVal = Math.floor(Math.random() * backoff)
|
|
177
|
-
backoff = Math.floor(backoff / 2) + jitterVal
|
|
178
|
-
}
|
|
179
|
-
this.logger?.debug?.('backoff before retry', { url, attempt, backoff })
|
|
82
|
+
// exponential backoff
|
|
83
|
+
const backoff = Math.min(100 * Math.pow(2, attempt - 1), 2000)
|
|
180
84
|
await new Promise((r) => setTimeout(r, backoff))
|
|
181
85
|
}
|
|
182
86
|
}
|
|
183
87
|
}
|
|
184
88
|
|
|
185
89
|
async getDevice(id: string): Promise<any> {
|
|
186
|
-
// Try client API first (
|
|
187
|
-
if (this.client
|
|
90
|
+
// Try client API first (with node-switchbot's smart fallback and retry)
|
|
91
|
+
if (this.client?.getDevice) {
|
|
188
92
|
try {
|
|
189
93
|
return await this.client.getDevice(id)
|
|
190
94
|
} catch (e) {
|
|
191
|
-
|
|
192
|
-
this.logger?.warn?.(`Client getDevice failed for ${id}, falling back to OpenAPI:`, e)
|
|
95
|
+
this.logger?.warn?.(`Client getDevice failed for ${id}, trying OpenAPI fallback:`, e)
|
|
193
96
|
}
|
|
194
97
|
}
|
|
195
|
-
// Fallback: call OpenAPI via HTTP
|
|
98
|
+
// Fallback: call OpenAPI via HTTP
|
|
196
99
|
if (this.cfg.openApiToken) {
|
|
197
100
|
const url = `${this.baseUrl}/devices/${id}`
|
|
198
101
|
const opts = { headers: { Authorization: this.cfg.openApiToken } }
|
|
199
|
-
const resp = await this.fetchWithTimeoutAndRetry(url, opts
|
|
102
|
+
const resp = await this.fetchWithTimeoutAndRetry(url, opts)
|
|
200
103
|
return resp.json()
|
|
201
104
|
}
|
|
202
105
|
throw new Error('No SwitchBot client available')
|
|
203
106
|
}
|
|
204
107
|
|
|
205
108
|
async getDevices(): Promise<any[]> {
|
|
206
|
-
// Try client API first
|
|
207
|
-
if (this.client
|
|
109
|
+
// Try client API first
|
|
110
|
+
if (this.client?.getDevices) {
|
|
208
111
|
try {
|
|
209
112
|
return await this.client.getDevices()
|
|
210
113
|
} catch (e) {
|
|
211
|
-
|
|
212
|
-
this.logger?.warn?.('Client getDevices failed, falling back to OpenAPI:', e)
|
|
114
|
+
this.logger?.warn?.('Client getDevices failed, trying OpenAPI fallback:', e)
|
|
213
115
|
}
|
|
214
116
|
}
|
|
215
|
-
// Fallback: call OpenAPI
|
|
117
|
+
// Fallback: call OpenAPI
|
|
216
118
|
if (this.cfg.openApiToken) {
|
|
217
119
|
const url = `${this.baseUrl}/devices`
|
|
218
120
|
const opts = { headers: { Authorization: this.cfg.openApiToken } }
|
|
@@ -220,23 +122,20 @@ export class SwitchBotClient implements ISwitchBotClient {
|
|
|
220
122
|
const data = await resp.json()
|
|
221
123
|
return (data?.body || data) as any[]
|
|
222
124
|
}
|
|
223
|
-
this.logger?.warn('No SwitchBot client available for device discovery')
|
|
224
125
|
return []
|
|
225
126
|
}
|
|
226
127
|
|
|
227
128
|
async setDeviceState(id: string, body: any): Promise<any> {
|
|
228
|
-
//
|
|
129
|
+
// Plugin-level debounce: coalesce rapid writes per device
|
|
229
130
|
if (!this.writeDebounceMs || this.writeDebounceMs <= 0) {
|
|
230
131
|
return this._doSetDeviceState(id, body)
|
|
231
132
|
}
|
|
232
133
|
|
|
233
|
-
// Coalesce writes per-device within debounce window. Last write wins.
|
|
234
134
|
return new Promise((resolve, reject) => {
|
|
235
135
|
const existing = this.pendingWrites.get(id)
|
|
236
136
|
if (existing) {
|
|
237
137
|
existing.body = body
|
|
238
138
|
existing.resolvers.push({ resolve, reject })
|
|
239
|
-
// timer already scheduled
|
|
240
139
|
return
|
|
241
140
|
}
|
|
242
141
|
|
|
@@ -258,23 +157,21 @@ export class SwitchBotClient implements ISwitchBotClient {
|
|
|
258
157
|
}
|
|
259
158
|
|
|
260
159
|
private async _doSetDeviceState(id: string, body: any): Promise<any> {
|
|
261
|
-
// Prefer client API
|
|
262
|
-
if (this.client
|
|
160
|
+
// Prefer client API (which has node-switchbot's native resilience)
|
|
161
|
+
if (this.client?.setDeviceState) {
|
|
263
162
|
try {
|
|
264
163
|
return await this.client.setDeviceState(id, body)
|
|
265
164
|
} catch (e) {
|
|
266
|
-
// Fall through to other methods on error
|
|
267
165
|
this.logger?.warn?.(`Client setDeviceState failed for ${id}, trying fallback:`, e)
|
|
268
166
|
}
|
|
269
167
|
}
|
|
270
168
|
|
|
271
|
-
// Try generic
|
|
272
|
-
if (this.client
|
|
169
|
+
// Try generic sendCommand if available
|
|
170
|
+
if (this.client?.sendCommand) {
|
|
273
171
|
try {
|
|
274
172
|
return await this.client.sendCommand(id, body)
|
|
275
173
|
} catch (e) {
|
|
276
|
-
|
|
277
|
-
this.logger?.warn?.(`Client sendCommand failed for ${id}, falling back to OpenAPI:`, e)
|
|
174
|
+
this.logger?.warn?.(`Client sendCommand failed for ${id}, trying OpenAPI:`, e)
|
|
278
175
|
}
|
|
279
176
|
}
|
|
280
177
|
|
|
@@ -289,7 +186,7 @@ export class SwitchBotClient implements ISwitchBotClient {
|
|
|
289
186
|
},
|
|
290
187
|
body: JSON.stringify(body),
|
|
291
188
|
}
|
|
292
|
-
const resp = await this.fetchWithTimeoutAndRetry(url, opts
|
|
189
|
+
const resp = await this.fetchWithTimeoutAndRetry(url, opts)
|
|
293
190
|
return resp.json()
|
|
294
191
|
}
|
|
295
192
|
|
|
@@ -297,9 +194,10 @@ export class SwitchBotClient implements ISwitchBotClient {
|
|
|
297
194
|
}
|
|
298
195
|
|
|
299
196
|
async destroy(): Promise<void> {
|
|
300
|
-
if (this.client
|
|
197
|
+
if (this.client?.destroy) {
|
|
301
198
|
await this.client.destroy()
|
|
302
199
|
}
|
|
303
200
|
this.client = null
|
|
304
201
|
}
|
|
305
202
|
}
|
|
203
|
+
|