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.
- package/package.json +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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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:
|
|
342
|
+
page.waitForNavigation({ waitUntil: ['domcontentloaded', 'networkidle2'], timeout: 15000 })
|
|
331
343
|
]);
|
|
332
344
|
|
|
333
|
-
await page.waitForSelector('input[name="username"]', { timeout:
|
|
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
|
-
|
|
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
|
-
|
|
340
|
-
page.waitForNavigation({ waitUntil: 'networkidle2', timeout:
|
|
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
|
-
|
|
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
|
-
|
|
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(`
|
|
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)
|
|
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) {
|