homebridge-melcloud-control 4.0.0-beta.412 → 4.0.0-beta.413

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 +63 -29
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.412",
4
+ "version": "4.0.0-beta.413",
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
@@ -331,25 +331,29 @@ class MelCloud extends EventEmitter {
331
331
  try {
332
332
  browser = await puppeteer.launch({
333
333
  headless: true,
334
- args: ['--no-sandbox', '--disable-setuid-sandbox']
334
+ args: [
335
+ '--no-sandbox',
336
+ '--disable-setuid-sandbox',
337
+ '--disable-dev-shm-usage',
338
+ '--single-process',
339
+ '--no-zygote'
340
+ ]
335
341
  });
336
342
 
337
343
  const page = await browser.newPage();
338
344
 
339
- // Emit page errors to logs
340
345
  page.on('error', err => this.emit('warn', `Page crashed: ${err.message}`));
341
346
  page.on('pageerror', err => this.emit('warn', `Browser error: ${err.message}`));
347
+ page.on('close', () => this.emit('warn', 'Page was closed unexpectedly'));
348
+ browser.on('disconnected', () => this.emit('warn', 'Browser disconnected unexpectedly'));
342
349
 
343
- await page.goto(ApiUrlsHome.BaseURL, { waitUntil: ['domcontentloaded', 'networkidle2'] });
350
+ page.setDefaultTimeout(30000);
351
+ page.setDefaultNavigationTimeout(30000);
344
352
 
345
- // Ensure login button is visible
346
- await page.waitForSelector('button.btn--blue', { timeout: 10000 }).catch(() => {
347
- throw new Error('Login button not found on MELCloud Home');
348
- });
353
+ await page.goto(ApiUrlsHome.BaseURL, { waitUntil: ['domcontentloaded', 'networkidle2'] });
349
354
 
350
355
  const buttons = await page.$$('button.btn--blue');
351
356
  let loginBtn = null;
352
-
353
357
  for (const btn of buttons) {
354
358
  const text = await page.evaluate(el => el.textContent.trim(), btn);
355
359
  if (['Zaloguj', 'Sign In', 'Login'].includes(text)) {
@@ -357,44 +361,70 @@ class MelCloud extends EventEmitter {
357
361
  break;
358
362
  }
359
363
  }
364
+ if (!loginBtn) {
365
+ this.emit('warn', 'Login button not found');
366
+ return null;
367
+ }
360
368
 
361
- if (!loginBtn) throw new Error('Login button not found (no matching text)');
362
-
363
- if (this.logDebug) this.emit('debug', `Clicking login button...`);
369
+ if (page.isClosed()) {
370
+ this.emit('warn', 'Page closed before login click');
371
+ return null;
372
+ }
364
373
 
365
- await Promise.all([
366
- loginBtn.click(),
367
- page.waitForNavigation({ waitUntil: ['domcontentloaded', 'networkidle2'], timeout: 15000 })
374
+ await Promise.race([
375
+ Promise.all([
376
+ loginBtn.click(),
377
+ page.waitForNavigation({ waitUntil: ['domcontentloaded', 'networkidle2'], timeout: 15000 })
378
+ ]),
379
+ new Promise(r => setTimeout(r, 12000))
368
380
  ]);
369
381
 
370
- await page.waitForSelector('input[name="username"]', { timeout: 10000 });
382
+ const usernameInput = await page.$('input[name="username"]');
383
+ const passwordInput = await page.$('input[name="password"]');
384
+ if (!usernameInput || !passwordInput) {
385
+ this.emit('warn', 'Username or password input not found');
386
+ return null;
387
+ }
388
+
389
+ if (page.isClosed()) {
390
+ this.emit('warn', 'Page closed before typing credentials');
391
+ return null;
392
+ }
393
+
371
394
  await page.type('input[name="username"]', this.user, { delay: 50 });
372
395
  await page.type('input[name="password"]', this.passwd, { delay: 50 });
373
396
 
374
- // Handle possible "Show password" overlays
375
397
  const submitButton = await page.$('input[type="submit"], button[type="submit"]');
376
- if (!submitButton) throw new Error('Submit button not found on login form');
398
+ if (!submitButton) {
399
+ this.emit('warn', 'Submit button not found on login form');
400
+ return null;
401
+ }
377
402
 
378
- if (this.logDebug) this.emit('debug', `Submitting credentials...`);
403
+ if (page.isClosed()) {
404
+ this.emit('warn', 'Page closed before submitting form');
405
+ return null;
406
+ }
379
407
 
380
- await Promise.all([
381
- submitButton.click(),
382
- page.waitForNavigation({ waitUntil: ['domcontentloaded', 'networkidle2'], timeout: 20000 })
408
+ await Promise.race([
409
+ Promise.all([
410
+ submitButton.click(),
411
+ page.waitForNavigation({ waitUntil: ['domcontentloaded', 'networkidle2'], timeout: 20000 })
412
+ ]),
413
+ new Promise(r => setTimeout(r, 15000))
383
414
  ]);
384
415
 
385
- // Check for login errors
386
416
  const pageText = await page.content();
387
417
  if (pageText.includes('incorrect') || pageText.includes('Invalid') || pageText.includes('nieprawidłowe')) {
388
- throw new Error('Login failed: incorrect email or password');
418
+ this.emit('warn', 'Login failed: incorrect email or password');
419
+ return null;
389
420
  }
390
421
 
391
- // Check for CAPTCHA
392
422
  const captchaPresent = await page.$('iframe[src*="captcha"], div[class*="captcha"]');
393
423
  if (captchaPresent) {
394
- throw new Error('Login blocked by CAPTCHA. Manual verification required.');
424
+ this.emit('warn', 'Login blocked by CAPTCHA. Manual verification required.');
425
+ return null;
395
426
  }
396
427
 
397
- // Extract cookies with retry mechanism
398
428
  let c1 = null, c2 = null;
399
429
  const start = Date.now();
400
430
  while ((!c1 || !c2) && Date.now() - start < 20000) {
@@ -404,7 +434,10 @@ class MelCloud extends EventEmitter {
404
434
  if (!c1 || !c2) await new Promise(r => setTimeout(r, 500));
405
435
  }
406
436
 
407
- if (!c1 || !c2) throw new Error('Cookies C1/C2 missing after login');
437
+ if (!c1 || !c2) {
438
+ this.emit('warn', 'Cookies C1/C2 missing after login');
439
+ return null;
440
+ }
408
441
 
409
442
  const contextKey = [
410
443
  '__Secure-monitorandcontrol=chunks-2',
@@ -420,7 +453,7 @@ class MelCloud extends EventEmitter {
420
453
 
421
454
  return accountInfo;
422
455
  } catch (error) {
423
- // Handle Puppeteer system dependency errors
456
+ // Puppeteer / system error handling
424
457
  if (error.message.includes('libnspr4.so') || error.message.includes('Failed to launch the browser process')) {
425
458
  const inDocker = await this.functions.isRunningInDocker();
426
459
  this.emit('warn', `Missing system libraries detected.`);
@@ -434,6 +467,7 @@ class MelCloud extends EventEmitter {
434
467
  if (inDocker) {
435
468
  this.emit('warn', `Running in Docker — attempting automatic fix...`);
436
469
  try {
470
+ const { execSync } = require('child_process');
437
471
  execSync(installCmd, { stdio: 'inherit' });
438
472
  this.emit('success', `System libraries installed. Retry the connection.`);
439
473
  return true;
@@ -445,7 +479,7 @@ class MelCloud extends EventEmitter {
445
479
  }
446
480
  }
447
481
 
448
- // Generic Puppeteer error
482
+ // Only throw for real technical errors
449
483
  throw new Error(`Connect error: ${error.message}`);
450
484
  } finally {
451
485
  if (browser) {