homebridge-melcloud-control 4.0.0-beta.410 → 4.0.0-beta.411

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 +2 -2
  2. package/src/melcloud.js +64 -38
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.410",
4
+ "version": "4.0.0-beta.411",
5
5
  "description": "Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.",
6
6
  "license": "MIT",
7
7
  "author": "grzegorz914",
@@ -39,7 +39,7 @@
39
39
  "async-mqtt": "^2.6.3",
40
40
  "axios": "^1.12.2",
41
41
  "express": "^5.1.0",
42
- "puppeteer-core": "^24.26.1",
42
+ "puppeteer": "^24.26.1",
43
43
  "axios-cookiejar-support": "^6.0.4",
44
44
  "tough-cookie": "^6.0.0",
45
45
  "jsdom": "^27.0.1"
package/src/melcloud.js CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  import { execSync } from 'child_process';
3
- import puppeteer from 'puppeteer-core';
3
+ import puppeteer from 'puppeteer';
4
4
  import axios from 'axios';
5
5
  import EventEmitter from 'events';
6
6
  import MelCloudHomeToken from './melcloudhometoken.js';
@@ -310,39 +310,68 @@ class MelCloud extends EventEmitter {
310
310
  });
311
311
 
312
312
  const page = await browser.newPage();
313
- await page.goto(ApiUrlsHome.BaseURL, { waitUntil: 'networkidle2' });
313
+
314
+ // Emit page errors to logs
315
+ page.on('error', err => this.emit('warn', `Page crashed: ${err.message}`));
316
+ page.on('pageerror', err => this.emit('warn', `Browser error: ${err.message}`));
317
+
318
+ await page.goto(ApiUrlsHome.BaseURL, { waitUntil: ['domcontentloaded', 'networkidle2'] });
319
+
320
+ // Ensure login button is visible
321
+ await page.waitForSelector('button.btn--blue', { timeout: 10000 }).catch(() => {
322
+ throw new Error('Login button not found on MELCloud Home');
323
+ });
314
324
 
315
325
  const buttons = await page.$$('button.btn--blue');
316
326
  let loginBtn = null;
317
327
 
318
328
  for (const btn of buttons) {
319
- const text = await page.evaluate(el => el.textContent, btn);
320
- if (text.trim() === 'Zaloguj' || text.trim() === 'Sign In') {
329
+ const text = await page.evaluate(el => el.textContent.trim(), btn);
330
+ if (['Zaloguj', 'Sign In', 'Login'].includes(text)) {
321
331
  loginBtn = btn;
322
332
  break;
323
333
  }
324
334
  }
325
335
 
326
- if (!loginBtn && this.logWarn) this.emit('warn', `Login button not found`);
336
+ if (!loginBtn) throw new Error('Login button not found (no matching text)');
337
+
338
+ if (this.logDebug) this.emit('debug', `Clicking login button...`);
327
339
 
328
340
  await Promise.all([
329
341
  loginBtn.click(),
330
- page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 10000 })
342
+ page.waitForNavigation({ waitUntil: ['domcontentloaded', 'networkidle2'], timeout: 15000 })
331
343
  ]);
332
344
 
333
- await page.waitForSelector('input[name="username"]', { timeout: 5000 });
345
+ await page.waitForSelector('input[name="username"]', { timeout: 10000 });
334
346
  await page.type('input[name="username"]', this.user, { delay: 50 });
335
347
  await page.type('input[name="password"]', this.passwd, { delay: 50 });
336
348
 
337
- const button1 = await page.$('input[type="submit"]');
349
+ // Handle possible "Show password" overlays
350
+ const submitButton = await page.$('input[type="submit"], button[type="submit"]');
351
+ if (!submitButton) throw new Error('Submit button not found on login form');
352
+
353
+ if (this.logDebug) this.emit('debug', `Submitting credentials...`);
354
+
338
355
  await Promise.all([
339
- button1.click(),
340
- page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 10000 })
356
+ submitButton.click(),
357
+ page.waitForNavigation({ waitUntil: ['domcontentloaded', 'networkidle2'], timeout: 20000 })
341
358
  ]);
342
359
 
360
+ // Check for login errors
361
+ const pageText = await page.content();
362
+ if (pageText.includes('incorrect') || pageText.includes('Invalid') || pageText.includes('nieprawidłowe')) {
363
+ throw new Error('Login failed: incorrect email or password');
364
+ }
365
+
366
+ // Check for CAPTCHA
367
+ const captchaPresent = await page.$('iframe[src*="captcha"], div[class*="captcha"]');
368
+ if (captchaPresent) {
369
+ throw new Error('Login blocked by CAPTCHA. Manual verification required.');
370
+ }
371
+
372
+ // Extract cookies with retry mechanism
343
373
  let c1 = null, c2 = null;
344
374
  const start = Date.now();
345
-
346
375
  while ((!c1 || !c2) && Date.now() - start < 20000) {
347
376
  const cookies = await page.browserContext().cookies();
348
377
  c1 = cookies.find(c => c.name === '__Secure-monitorandcontrolC1')?.value || c1;
@@ -350,11 +379,7 @@ class MelCloud extends EventEmitter {
350
379
  if (!c1 || !c2) await new Promise(r => setTimeout(r, 500));
351
380
  }
352
381
 
353
-
354
- if (!c1 || !c2) {
355
- if (this.logWarn) this.emit('warn', `Cookies C1/C2 missing`);
356
- return null;
357
- }
382
+ if (!c1 || !c2) throw new Error('Cookies C1/C2 missing after login');
358
383
 
359
384
  const contextKey = [
360
385
  '__Secure-monitorandcontrol=chunks-2',
@@ -369,47 +394,48 @@ class MelCloud extends EventEmitter {
369
394
  if (!refresh) this.emit('success', `Connect to MELCloud Home Success`);
370
395
 
371
396
  return accountInfo;
397
+
372
398
  } catch (error) {
399
+ // Handle Puppeteer system dependency errors
373
400
  if (error.message.includes('libnspr4.so') || error.message.includes('Failed to launch the browser process')) {
374
401
  const inDocker = await this.functions.isRunningInDocker();
375
- if (this.logWarn) this.emit('warn', `Missing system libraries detected.`);
402
+ this.emit('warn', `Missing system libraries detected.`);
403
+
404
+ const installCmd =
405
+ 'apt-get update && apt-get install -y ' +
406
+ 'libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 ' +
407
+ 'libxcomposite1 libxrandr2 libxdamage1 libxkbcommon0 libpango-1.0-0 ' +
408
+ 'libgbm1 libasound2 libxshmfence1 fonts-liberation libappindicator3-1 libu2f-udev';
376
409
 
377
410
  if (inDocker) {
378
411
  this.emit('warn', `Running in Docker — attempting automatic fix...`);
379
-
380
- const installCmd =
381
- 'apt-get update && apt-get install -y ' +
382
- 'libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 ' +
383
- 'libxcomposite1 libxrandr2 libxdamage1 libxkbcommon0 libpango-1.0-0 ' +
384
- 'libgbm1 libasound2 libxshmfence1 fonts-liberation libappindicator3-1 libu2f-udev';
385
-
386
412
  try {
387
413
  execSync(installCmd, { stdio: 'inherit' });
388
- if (this.logDebug) this.emit('debug', `Libraries installed. Retrying Puppeteer...`);
389
-
390
- const testBrowser = await puppeteer.launch({
391
- headless: true,
392
- args: ['--no-sandbox', '--disable-setuid-sandbox']
393
- });
394
- await testBrowser.close();
395
-
396
- this.emit('success', `Puppeteer repaired and running, try again.`);
414
+ this.emit('success', `System libraries installed. Retry the connection.`);
397
415
  return true;
398
416
  } catch (fixError) {
399
417
  throw new Error(`Automatic fix failed. Run manually inside container:\n${installCmd}`);
400
418
  }
401
-
402
419
  } else {
403
- throw new Error(`System libraries missing. Non-Docker environment detected — install dependencies manually.`);
420
+ throw new Error(`Missing system libraries. Please install manually:\n${installCmd}`);
404
421
  }
405
- } else {
406
- throw new Error(`Puppeteer failed: ${error.message}`);
407
422
  }
423
+
424
+ // Generic Puppeteer error
425
+ throw new Error(`MELCloud connection failed: ${error.message}`);
426
+
408
427
  } finally {
409
- if (browser) await browser.close().catch(() => { });
428
+ if (browser) {
429
+ try {
430
+ await browser.close();
431
+ } catch (closeErr) {
432
+ this.emit('warn', `Failed to close Puppeteer browser: ${closeErr.message}`);
433
+ }
434
+ }
410
435
  }
411
436
  }
412
437
 
438
+
413
439
  async checkDevicesList(contextKey) {
414
440
  let devices = [];
415
441
  switch (this.accountType) {