@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.
@@ -59,11 +59,11 @@
59
59
  async function loadCredentialStatus() {
60
60
  try {
61
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
- }
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?.data?.message || 'Save failed')
114
+ throw new Error(resp?.message || 'Save failed')
110
115
  }
111
116
 
112
- saveStatus.textContent = '✓ ' + (resp.data?.message || 'Credentials saved')
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(), 1000)
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":"AAEA,OAAO,EAAE,wBAAwB,EAAgB,MAAM,6BAA6B,CAAA;AAEpF,QAAA,MAAM,MAAM,0BAAiC,CAAA;AAiH7C,eAAe,MAAM,CAAA"}
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
- import fs from 'node:fs/promises';
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
- return found;
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
- return {
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":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/homebridge-ui/server.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,OAAO,EAAE,wBAAwB,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AACpF,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAEjC,MAAM,MAAM,GAAG,IAAI,wBAAwB,EAAE,CAAA;AAE7C,wDAAwD;AACxD,KAAK,UAAU,0BAA0B;IACvC,MAAM,OAAO,GAAG,MAAM,CAAC,oBAAoB,CAAA;IAC3C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAA;IACnD,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC3B,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAA;IAEnE,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;QAC1B,MAAM,YAAY,GAAG,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,IAAI,IAAI,EAAE,CAAA;QAC/C,IAAI,CAAC,YAAY,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;YAC9D,SAAQ;QACV,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAA;IAC9C,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAA;AAC3D,CAAC;AAED,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,CAAC,GAAG,CAAC,qCAAqC,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;QAC3E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;IACvC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,mCAAmC,EAAE,CAAC,CAAC,CAAA;QACrD,MAAM,IAAI,YAAY,CAAC,kCAAkC,EAAE,CAAC,CAAC,CAAA;IAC/D,CAAC;AACH,CAAC,CAAC,CAAA;AAEF,MAAM,CAAC,SAAS,CAAC,cAAc,EAAE,KAAK,EAAE,IAAS,EAAE,EAAE;IACnD,IAAI,CAAC;QACH,oCAAoC;QACpC,IAAI,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5C,sCAAsC;YACtC,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,0BAA0B,EAAE,CAAA;YAEvD,MAAM,MAAM,GAAG;gBACb,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,YAAY;gBACjC,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,aAAa;gBACnC,WAAW,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBAC7E,YAAY,EAAE,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;aACjF,CAAA;YAED,OAAO,CAAC,GAAG,CAAC,2CAA2C,EAAE,MAAM,CAAC,CAAA;YAChE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;QACxC,CAAC;aAAM,CAAC;YACN,kCAAkC;YAClC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;YAE9B,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;gBACtB,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAA;YAClD,CAAC;YAED,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,0BAA0B,EAAE,CAAA;YAExE,OAAO,CAAC,GAAG,CAAC,wDAAwD,EAAE,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAA;YACzG,OAAO,CAAC,GAAG,CAAC,6BAA6B,EAAE,OAAO,CAAC,CAAA;YACnD,OAAO,CAAC,GAAG,CAAC,8BAA8B,EAAE,KAAK,CAAC,MAAM,EAAE,gBAAgB,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;YAE1F,wDAAwD;YACxD,QAAQ,CAAC,YAAY,GAAG,KAAK,CAAA;YAC7B,QAAQ,CAAC,aAAa,GAAG,MAAM,CAAA;YAE/B,4BAA4B;YAC5B,MAAM,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;YAEpE,OAAO,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAA;YAE5D,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,IAAI,EAAE,EAAE,OAAO,EAAE,gCAAgC,EAAE;aACpD,CAAA;QACH,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,uCAAuC,EAAE,CAAC,CAAC,CAAA;QACzD,MAAM,IAAI,YAAY,CAAC,sCAAsC,EAAE,CAAC,CAAC,CAAA;IACnE,CAAC;AACH,CAAC,CAAC,CAAA;AAEF,MAAM,CAAC,KAAK,EAAE,CAAA;AAEd,eAAe,MAAM,CAAA"}
@@ -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;AAGD,qBAAa,eAAgB,YAAW,gBAAgB;IACtD,OAAO,CAAC,GAAG,CAAuB;IAClC,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,OAAO,CAAoC;IAEnD,OAAO,CAAC,cAAc,CAAO;IAC7B,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,aAAa,CAAM;IAC3B,OAAO,CAAC,YAAY,CAAO;IAC3B,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,MAAM,CAAO;IAGrB,OAAO,CAAC,eAAe,CAAM;IAE7B,OAAO,CAAC,MAAM,CAAK;IAEnB,OAAO,CAAC,gBAAgB,CAAiC;IACzD,OAAO,CAAC,mBAAmB,CAAI;IAE/B,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,sBAAsB,CAAiC;gBAEnD,GAAG,EAAE,qBAAqB;IAetC,OAAO,CAAC,aAAa,CAA0H;IAEzI,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAoBb,wBAAwB;IAkHhC,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAoBnC,UAAU,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAsB5B,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;YAiC3C,iBAAiB;IAuCzB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAM/B"}
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"}
@@ -1,26 +1,17 @@
1
- // Adapter that tries to use the node-switchbot v4 hybrid API when available.
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
- // per-device retry tracking
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
- const mod = await import('node-switchbot');
54
- // The new library may export a default or named client. Try common names.
55
- const m = mod;
56
- this.client = m?.default ?? m?.SwitchBot ?? m;
57
- if (typeof this.client === 'function') {
58
- // some builds export a constructor
59
- this.client = new this.client({ token: this.cfg.openApiToken });
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
- // If import fails, leave client null and rely on OpenAPI code paths later.
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, deviceId) {
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 environments where defineProperty may fail
60
+ // ignore
94
61
  }
95
62
  const resp = await fetch(url, fetchOpts);
96
63
  clearTimeout(timer);
97
- // helper to safely read response body when available
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
- // Do not retry on client errors (4xx), except 429 (rate limit)
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 with optional jitter
171
- let backoff = Math.min(this.baseBackoffMs * Math.pow(this.backoffFactor, attempt - 1), this.maxBackoffMs);
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 (handles both BLE and OpenAPI)
183
- if (this.client && typeof this.client.getDevice === 'function') {
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
- // Fall through to OpenAPI fallback on error
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 using token if available.
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, undefined, undefined, id);
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 for device discovery
203
- if (this.client && typeof this.client.getDevices === 'function') {
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
- // Fall through to OpenAPI fallback on error
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 to list all devices
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
- // If debounce is disabled do a direct write path
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 && typeof this.client.setDeviceState === 'function') {
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 command methods
268
- if (this.client && typeof this.client.sendCommand === 'function') {
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
- // Fall through to OpenAPI on error
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, undefined, undefined, id);
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 && typeof this.client.destroy === 'function') {
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">=&gt;</span> <span class="tsd-signature-type">void</span></div><div class="tsd-type-declaration"><h4>Type Declaration</h4><ul class="tsd-parameters"><li class="tsd-parameter-signature"><ul class="tsd-signatures"><li class="tsd-signature" id="__type"><span class="tsd-signature-symbol">(</span><span class="tsd-kind-parameter">api</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">API</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">void</span></li><li class="tsd-description"><div class="tsd-parameters"><h4 class="tsd-parameters-title">Parameters</h4><ul class="tsd-parameter-list"><li><span><span class="tsd-kind-parameter">api</span>: <span class="tsd-signature-type">API</span></span></li></ul></div><h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">void</span></h4></li></ul></li></ul></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/OpenWonderLabs/homebridge-switchbot/blob/a37d0523a61900e834f82f89f207842b2140a445/src/index.ts#L12">index.ts:12</a></li></ul></aside></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">@switchbot/homebridge-switchbot</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer></footer><div class="overlay"></div></body></html>
1
+ <!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>default | @switchbot/homebridge-switchbot</title><meta name="description" content="Documentation for @switchbot/homebridge-switchbot"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">@switchbot/homebridge-switchbot</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">default</a></li></ul><h1>Variable default</h1></div><div class="tsd-signature"><span class="tsd-kind-variable">default</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol">(</span><span class="tsd-kind-parameter">api</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">API</span><span class="tsd-signature-symbol">)</span> <span class="tsd-signature-symbol">=&gt;</span> <span class="tsd-signature-type">void</span></div><div class="tsd-type-declaration"><h4>Type Declaration</h4><ul class="tsd-parameters"><li class="tsd-parameter-signature"><ul class="tsd-signatures"><li class="tsd-signature" id="__type"><span class="tsd-signature-symbol">(</span><span class="tsd-kind-parameter">api</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">API</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">void</span></li><li class="tsd-description"><div class="tsd-parameters"><h4 class="tsd-parameters-title">Parameters</h4><ul class="tsd-parameter-list"><li><span><span class="tsd-kind-parameter">api</span>: <span class="tsd-signature-type">API</span></span></li></ul></div><h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">void</span></h4></li></ul></li></ul></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/OpenWonderLabs/homebridge-switchbot/blob/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
- 'builtin-type',
22
- 'external-type',
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.75",
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.0",
86
- "async-mqtt": "^2.6.3",
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.7.3",
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/mdast": "^4.0.4",
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": "^9.39.3",
103
- "eslint-plugin-format": "^1.5.0",
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.18.0",
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
- if (!resp || !resp.success === false) {
63
- if (!resp?.data) {
64
- console.error('Failed to load credentials:', resp?.data?.message)
65
- return
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?.data?.message || 'Save failed')
114
+ throw new Error(resp?.message || 'Save failed')
110
115
  }
111
116
 
112
- saveStatus.textContent = '✓ ' + (resp.data?.message || 'Credentials saved')
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(), 1000)
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
- import fs from 'node:fs/promises'
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
- return found
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
- return {
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
  })
@@ -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
- // Adapter that tries to use the node-switchbot v4 hybrid API when available.
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
- // per-device retry tracking
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
- const mod = await import('node-switchbot')
57
- // The new library may export a default or named client. Try common names.
58
- const m: any = mod as any
59
- this.client = m?.default ?? m?.SwitchBot ?? m
60
- if (typeof this.client === 'function') {
61
- // some builds export a constructor
62
- this.client = new this.client({ token: this.cfg.openApiToken })
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
- // If import fails, leave client null and rely on OpenAPI code paths later.
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, deviceId?: string) {
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 environments where defineProperty may fail
69
+ // ignore
98
70
  }
99
71
  const resp = await fetch(url, fetchOpts)
100
72
  clearTimeout(timer)
101
-
102
- // helper to safely read response body when available
103
- const safeReadBody = async (r: any) => {
104
- try {
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
- // exponential backoff with optional jitter
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 (handles both BLE and OpenAPI)
187
- if (this.client && typeof this.client.getDevice === 'function') {
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
- // Fall through to OpenAPI fallback on error
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 using token if available.
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, undefined, undefined, id)
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 for device discovery
207
- if (this.client && typeof this.client.getDevices === 'function') {
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
- // Fall through to OpenAPI fallback on error
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 to list all devices
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
- // If debounce is disabled do a direct write path
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 && typeof this.client.setDeviceState === 'function') {
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 command methods
272
- if (this.client && typeof this.client.sendCommand === 'function') {
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
- // Fall through to OpenAPI on error
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, undefined, undefined, id)
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 && typeof this.client.destroy === 'function') {
197
+ if (this.client?.destroy) {
301
198
  await this.client.destroy()
302
199
  }
303
200
  this.client = null
304
201
  }
305
202
  }
203
+