homebridge-melcloud-control 4.0.0-beta.507 → 4.0.0-beta.509

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/melcloud.js +41 -40
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "displayName": "MELCloud Control",
3
3
  "name": "homebridge-melcloud-control",
4
- "version": "4.0.0-beta.507",
4
+ "version": "4.0.0-beta.509",
5
5
  "description": "Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.",
6
6
  "license": "MIT",
7
7
  "author": "grzegorz914",
package/src/melcloud.js CHANGED
@@ -303,20 +303,22 @@ class MelCloud extends EventEmitter {
303
303
 
304
304
  async connectToMelCloudHome(refresh = false) {
305
305
  if (this.logDebug) this.emit('debug', 'Connecting to MELCloud Home');
306
+ const GLOBAL_TIMEOUT = 45000;
306
307
 
307
308
  let browser;
308
309
  try {
309
- const accountInfo = { State: false, Info: '', ContextKey: null, UseFahrenheit: false }
310
+ const accountInfo = { State: false, Info: '', ContextKey: null, UseFahrenheit: false };
310
311
  const chromiumPath = await this.functions.ensureChromiumInstalled();
311
312
  if (!chromiumPath) {
312
313
  accountInfo.State = false;
313
- accountInfo.Info = 'Chromium not found on Your device, please install it manually and try again'
314
+ accountInfo.Info = 'Chromium not found on Your device, please install it manually and try again';
314
315
  return accountInfo;
315
316
  }
316
317
 
318
+ // Verify executable works
317
319
  try {
318
320
  const { stdout } = await execPromise(`"${chromiumPath}" --version`);
319
- this.emit('warn', `Test 0`);
321
+ this.emit('warn', `Chromium detected: ${stdout.trim()}`);
320
322
  if (this.logDebug) this.emit('debug', `Chromium detected: ${stdout.trim()}`);
321
323
  } catch (error) {
322
324
  accountInfo.State = false;
@@ -324,11 +326,11 @@ class MelCloud extends EventEmitter {
324
326
  return accountInfo;
325
327
  }
326
328
 
327
- this.emit('warn', `Test 1`);
329
+ this.emit('warn', `Launching Chromium...`);
328
330
  browser = await puppeteer.launch({
329
331
  headless: 'shell',
330
332
  executablePath: chromiumPath,
331
- timeout: 30000,
333
+ timeout: GLOBAL_TIMEOUT,
332
334
  args: [
333
335
  '--no-sandbox',
334
336
  '--disable-setuid-sandbox',
@@ -339,52 +341,47 @@ class MelCloud extends EventEmitter {
339
341
  ]
340
342
  });
341
343
 
342
- // Wait for Puppeteer target to be ready (browser internal page)
343
- await new Promise(r => setTimeout(r, 1000));
344
-
345
- // Defensive check for main frame availability
346
- this.emit('warn', `Test 2`);
347
- const pages = await browser.pages();
348
- let page = pages[0];
349
- if (!page) {
350
- if (this.logWarn) this.emit('warn', 'No initial page found, creating a new one');
351
- page = await browser.newPage();
352
- await new Promise(r => setTimeout(r, 1500));
353
- }
344
+ const [page] = await browser.pages();
345
+ page.setDefaultTimeout(GLOBAL_TIMEOUT);
346
+ page.setDefaultNavigationTimeout(GLOBAL_TIMEOUT);
354
347
 
355
- this.emit('warn', `Test 3`);
356
- page.on('error', err => { if (this.logError) this.emit('error', `Page crashed: ${err.message}`); });
357
- page.on('pageerror', err => { if (this.logDebug) this.emit('error', `Browser error: ${err.message}`); });
358
- page.on('close', () => { if (this.logDebug) this.emit('debug', 'Page was closed unexpectedly'); });
359
- browser.on('disconnected', () => { if (this.logWarn) this.emit('debug', 'Browser disconnected unexpectedly'); });
348
+ page.on('error', err => this.emit('error', `Page crashed: ${err.message}`));
349
+ page.on('pageerror', err => this.emit('error', `Browser error: ${err.message}`));
350
+ browser.on('disconnected', () => this.emit('warn', 'Browser disconnected unexpectedly'));
360
351
 
361
- // Now safe to navigate
362
- this.emit('warn', `Test 4`);
352
+ this.emit('warn', `Navigating to MELCloud...`);
363
353
  try {
364
- await page.goto(ApiUrlsHome.BaseURL, { waitUntil: ['domcontentloaded', 'networkidle2'], timeout: 45000 });
354
+ await page.goto(ApiUrlsHome.BaseURL, { waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT });
365
355
  } catch (error) {
366
356
  accountInfo.State = false;
367
357
  accountInfo.Info = `Navigation to ${ApiUrlsHome.BaseURL} failed: ${error.message}`;
368
358
  return accountInfo;
369
359
  }
370
- await new Promise(r => setTimeout(r, 4000));
371
360
 
361
+ // Wait extra to ensure UI is rendered
362
+ await new Promise(r => setTimeout(r, 3000));
363
+
364
+ this.emit('warn', `Looking for login button...`);
372
365
  let loginBtn;
373
366
  try {
374
- this.emit('warn', `Test 5`);
375
367
  loginBtn = await page.waitForFunction(() => {
376
368
  const btns = Array.from(document.querySelectorAll('button.btn--blue'));
377
369
  return btns.find(b => ['Zaloguj', 'Sign In', 'Login'].includes(b.textContent.trim()));
378
- }, { timeout: 5000 });
370
+ }, { timeout: GLOBAL_TIMEOUT / 6 });
379
371
  } catch {
380
372
  accountInfo.State = false;
381
- accountInfo.Info = 'Login button not found after 5s';
373
+ accountInfo.Info = 'Login button not found';
382
374
  return accountInfo;
383
375
  }
384
376
 
385
- await Promise.race([Promise.all([loginBtn.click(), page.waitForNavigation({ waitUntil: ['domcontentloaded', 'networkidle2'], timeout: 10000 })]), new Promise(r => setTimeout(r, 8000))]);
377
+ await Promise.race([
378
+ Promise.all([
379
+ loginBtn.click(),
380
+ page.waitForNavigation({ waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT / 4 })
381
+ ]),
382
+ new Promise(r => setTimeout(r, GLOBAL_TIMEOUT / 3))
383
+ ]);
386
384
 
387
- this.emit('warn', `Test 6`);
388
385
  const usernameInput = await page.$('input[name="username"]');
389
386
  const passwordInput = await page.$('input[name="password"]');
390
387
  if (!usernameInput || !passwordInput) {
@@ -393,31 +390,34 @@ class MelCloud extends EventEmitter {
393
390
  return accountInfo;
394
391
  }
395
392
 
396
- this.emit('warn', `Test 7`);
397
393
  await page.type('input[name="username"]', this.user, { delay: 50 });
398
394
  await page.type('input[name="password"]', this.passwd, { delay: 50 });
399
395
 
400
- this.emit('warn', `Test 8`);
401
396
  const submitButton = await page.$('input[type="submit"], button[type="submit"]');
402
397
  if (!submitButton) {
403
398
  accountInfo.State = false;
404
- accountInfo.Info = 'Submit button not found on login form';
399
+ accountInfo.Info = 'Submit button not found';
405
400
  return accountInfo;
406
401
  }
407
402
 
408
- await Promise.race([Promise.all([submitButton.click(), page.waitForNavigation({ waitUntil: ['domcontentloaded', 'networkidle2'], timeout: 10000 })]), new Promise(r => setTimeout(r, 8000))]);
403
+ await Promise.race([
404
+ Promise.all([
405
+ submitButton.click(),
406
+ page.waitForNavigation({ waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT / 4 })
407
+ ]),
408
+ new Promise(r => setTimeout(r, GLOBAL_TIMEOUT / 3))
409
+ ]);
409
410
 
410
- this.emit('warn', `Test 9`);
411
+ // Extract cookies
411
412
  let c1 = null, c2 = null;
412
413
  const start = Date.now();
413
- while ((!c1 || !c2) && Date.now() - start < 20000) {
414
+ while ((!c1 || !c2) && Date.now() - start < GLOBAL_TIMEOUT / 2) {
414
415
  const cookies = await page.browserContext().cookies();
415
416
  c1 = cookies.find(c => c.name === '__Secure-monitorandcontrolC1')?.value || c1;
416
417
  c2 = cookies.find(c => c.name === '__Secure-monitorandcontrolC2')?.value || c2;
417
418
  if (!c1 || !c2) await new Promise(r => setTimeout(r, 500));
418
419
  }
419
420
 
420
- this.emit('warn', `Test 10`);
421
421
  if (!c1 || !c2) {
422
422
  accountInfo.State = false;
423
423
  accountInfo.Info = 'Cookies C1/C2 missing';
@@ -435,21 +435,22 @@ class MelCloud extends EventEmitter {
435
435
  accountInfo.Info = 'Connect success';
436
436
  accountInfo.ContextKey = contextKey;
437
437
 
438
- this.emit('warn', `Test 11`);
439
438
  await this.functions.saveData(this.accountFile, accountInfo);
440
439
 
441
440
  if (!refresh) this.emit('success', 'Connect to MELCloud Home Success');
442
441
  return accountInfo;
442
+
443
443
  } catch (error) {
444
444
  throw new Error(`Connect error: ${error.message}`);
445
445
  } finally {
446
446
  if (browser) {
447
447
  try { await browser.close(); }
448
- catch (closeErr) { if (this.logError) this.emit('error', `Failed to close Puppeteer browser: ${closeErr.message}`); }
448
+ catch (closeErr) { this.emit('error', `Failed to close Puppeteer: ${closeErr.message}`); }
449
449
  }
450
450
  }
451
451
  }
452
452
 
453
+
453
454
  async checkDevicesList() {
454
455
  const TIMEOUT_MS = 30000; // 30 seconds timeout
455
456
  try {