homebridge-melcloud-control 4.10.11 → 4.10.12-beta.0

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/CHANGELOG.md CHANGED
@@ -24,6 +24,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
24
24
  - For plugin < v4.6.0 use Homebridge UI <= v5.5.0
25
25
  - For plugin >= v4.6.0 use Homebridge UI >= v5.13.0
26
26
 
27
+ ## [4.10.12] - (28.05.2026)
28
+
29
+ ### Changes
30
+
31
+ - fix: plugin config UI — Connect to MELCloud spinner no longer hangs indefinitely
32
+ - fix: server responses now delivered via HTTP file polling, bypassing unreliable Socket.IO server→client channel in Homebridge Config UI
33
+
27
34
  ## [4.10.9] - (28.05.2026)
28
35
 
29
36
  ### Changes
@@ -257,6 +257,31 @@
257
257
  return parts.length ? `${type}: ${parts.join(', ')}` : '';
258
258
  }
259
259
 
260
+ // Trigger the operation via homebridge.request() (Socket.IO — unreliable for response),
261
+ // then poll _result.json via HTTP until the server writes the result.
262
+ async function requestViaFile(path, body, ms = 15000) {
263
+ const ts = Date.now();
264
+
265
+ homebridge.request(path, body).catch(() => {}); // fire-and-forget
266
+
267
+ const deadline = ts + ms;
268
+ while (Date.now() < deadline) {
269
+ await new Promise(r => setTimeout(r, 300));
270
+ try {
271
+ const res = await fetch(`_result.json?t=${Date.now()}`);
272
+ if (!res.ok) continue;
273
+ const data = await res.json();
274
+ if ((data.ts ?? 0) >= ts) {
275
+ if (data.ok) return data.data ?? true;
276
+ throw new Error(data.error || 'Unknown error');
277
+ }
278
+ } catch (e) {
279
+ if (e.message && e.message !== 'Failed to fetch') throw e;
280
+ }
281
+ }
282
+ throw new Error('Request timed out');
283
+ }
284
+
260
285
  // Login & Sync Logic
261
286
  $('logIn').addEventListener('click', async () => {
262
287
  $('logIn').disabled = true;
@@ -268,16 +293,10 @@
268
293
  updateInfo('info2', '', fontColor);
269
294
  homebridge.showSpinner();
270
295
 
271
- const statusListener = event => updateInfo('info', event.data, fontColor);
272
- homebridge.addEventListener('status', statusListener);
273
-
274
296
  try {
275
297
  const account = this.account;
276
298
  const accountTypeMelcloud = account.type === 'melcloud';
277
- const connectTimeout = new Promise((_, reject) =>
278
- setTimeout(() => reject(new Error('Connection timed out — please try again')), 160_000)
279
- );
280
- const melCloudDevicesData = await Promise.race([homebridge.request('/connect', account), connectTimeout]);
299
+ const melCloudDevicesData = await requestViaFile('/connect', account, 160_000);
281
300
 
282
301
  if (!melCloudDevicesData.State) {
283
302
  updateInfo('info', melCloudDevicesData.Status, 'red');
@@ -457,13 +476,11 @@
457
476
  await homebridge.updatePluginConfig(pluginConfig);
458
477
  await homebridge.savePluginConfig();
459
478
  } catch (error) {
460
- const msg = error?.message || error?.error || String(error);
461
- updateInfo('info', 'Connection failed', 'red');
462
- updateInfo('info1', msg, 'red');
479
+ updateInfo('info', `Prepare config error`, "red");
480
+ updateInfo('info1', `Error: ${JSON.stringify(error)}`, "red");
463
481
  } finally {
464
482
  homebridge.hideSpinner();
465
483
  $('logIn').disabled = false;
466
- try { homebridge.removeEventListener?.('status', statusListener); } catch (_) {}
467
484
  }
468
485
  });
469
486
  })();
@@ -1,7 +1,12 @@
1
1
  import { HomebridgePluginUiServer } from '@homebridge/plugin-ui-utils';
2
+ import { promises as fsPromises } from 'fs';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join } from 'path';
2
5
  import MelCloud from '../src/melcloud.js';
3
6
  import MelCloudHome from '../src/melcloudhome.js';
4
7
 
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+
5
10
  class PluginUiServer extends HomebridgePluginUiServer {
6
11
  constructor() {
7
12
  super();
@@ -13,6 +18,15 @@ class PluginUiServer extends HomebridgePluginUiServer {
13
18
  this.ready();
14
19
  };
15
20
 
21
+ async writeResult(result) {
22
+ try {
23
+ await fsPromises.writeFile(
24
+ join(__dirname, 'public', '_result.json'),
25
+ JSON.stringify({ ...result, ts: Date.now() })
26
+ );
27
+ } catch (_) {}
28
+ }
29
+
16
30
  withTimeout(promise, ms, label) {
17
31
  const timer = new Promise((_, reject) =>
18
32
  setTimeout(() => reject(new Error(`${label} timed out after ${ms / 1000}s`)), ms)
@@ -26,13 +40,19 @@ class PluginUiServer extends HomebridgePluginUiServer {
26
40
  try {
27
41
  this.pushEvent('status', 'Connecting to account...');
28
42
  const melCloudAccountData = await this.withTimeout(melCloudClass.connect(), 90_000, 'connect');
29
- if (!melCloudAccountData.State) return melCloudAccountData;
43
+ if (!melCloudAccountData.State) {
44
+ await this.writeResult({ ok: true, data: melCloudAccountData });
45
+ return melCloudAccountData;
46
+ }
30
47
 
31
48
  this.pushEvent('status', 'Loading devices...');
32
49
  const melCloudDevicesData = await this.withTimeout(melCloudClass.checkDevicesList(), 60_000, 'checkDevicesList');
50
+ await this.writeResult({ ok: true, data: melCloudDevicesData });
33
51
  return melCloudDevicesData;
34
52
  } catch (error) {
35
- throw new Error(error.message ?? String(error));
53
+ const msg = error.message ?? String(error);
54
+ await this.writeResult({ ok: false, error: msg });
55
+ throw new Error(msg);
36
56
  }
37
57
  }
38
58
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "displayName": "MELCloud Control",
3
3
  "name": "homebridge-melcloud-control",
4
- "version": "4.10.11",
4
+ "version": "4.10.12-beta.0",
5
5
  "description": "Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.",
6
6
  "license": "MIT",
7
7
  "author": "grzegorz914",