homebridge-melcloud-control 4.0.0-beta.539 → 4.0.0-beta.540
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 +1 -1
- package/src/melcloud.js +53 -57
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.540",
|
|
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
|
@@ -307,148 +307,143 @@ class MelCloud extends EventEmitter {
|
|
|
307
307
|
async connectToMelCloudHome() {
|
|
308
308
|
if (this.logDebug) this.emit('debug', 'Connecting to MELCloud Home');
|
|
309
309
|
const GLOBAL_TIMEOUT = 90000;
|
|
310
|
-
|
|
311
310
|
let browser;
|
|
311
|
+
|
|
312
|
+
const accountInfo = { State: false, Info: '', ContextKey: null, UseFahrenheit: false };
|
|
313
|
+
|
|
312
314
|
try {
|
|
313
|
-
const accountInfo = { State: false, Info: '', ContextKey: null, UseFahrenheit: false };
|
|
314
315
|
const chromiumPath = await this.functions.ensureChromiumInstalled();
|
|
315
316
|
if (!chromiumPath) {
|
|
316
|
-
accountInfo.
|
|
317
|
-
accountInfo.Info = 'Chromium not found on Your device, please install it manually and try again';
|
|
317
|
+
accountInfo.Info = 'Chromium not found on your device, please install it manually and try again';
|
|
318
318
|
return accountInfo;
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
-
//
|
|
321
|
+
// Check if executable is valid
|
|
322
322
|
try {
|
|
323
323
|
const { stdout } = await execPromise(`"${chromiumPath}" --version`);
|
|
324
324
|
this.emit('warn', `Chromium detected: ${stdout.trim()}`);
|
|
325
325
|
if (this.logDebug) this.emit('debug', `Chromium detected: ${stdout.trim()}`);
|
|
326
326
|
} catch (error) {
|
|
327
|
-
accountInfo.State = false;
|
|
328
327
|
accountInfo.Info = `Chromium found at ${chromiumPath}, but cannot be executed: ${error.message}`;
|
|
329
328
|
return accountInfo;
|
|
330
329
|
}
|
|
331
330
|
|
|
332
|
-
this.emit('warn',
|
|
331
|
+
this.emit('warn', 'Launching Chromium...');
|
|
333
332
|
browser = await puppeteer.launch({
|
|
334
|
-
headless:
|
|
333
|
+
headless: true, // "new" deprecated, use true
|
|
335
334
|
executablePath: chromiumPath,
|
|
336
335
|
timeout: GLOBAL_TIMEOUT,
|
|
337
336
|
args: [
|
|
338
337
|
'--no-sandbox',
|
|
339
338
|
'--disable-setuid-sandbox',
|
|
340
339
|
'--disable-dev-shm-usage',
|
|
341
|
-
'--single-process',
|
|
342
340
|
'--disable-gpu',
|
|
343
|
-
'--no-zygote'
|
|
344
|
-
|
|
341
|
+
'--no-zygote',
|
|
342
|
+
'--disable-background-networking',
|
|
343
|
+
'--disable-software-rasterizer',
|
|
344
|
+
],
|
|
345
345
|
});
|
|
346
346
|
|
|
347
|
-
|
|
348
|
-
if (typeof browser.createBrowserContext === 'function') {
|
|
349
|
-
// nowoczesne API
|
|
350
|
-
context = await browser.createBrowserContext();
|
|
351
|
-
} else {
|
|
352
|
-
// fallback do starego API (dla bezpieczeństwa)
|
|
353
|
-
context = browser.defaultBrowserContext?.() || browser;
|
|
354
|
-
}
|
|
355
|
-
|
|
347
|
+
const context = await browser.createIncognitoBrowserContext();
|
|
356
348
|
const page = await context.newPage();
|
|
357
349
|
|
|
358
|
-
|
|
359
|
-
const client = await page.createCDPSession();
|
|
360
|
-
await client.send('Network.clearBrowserCookies');
|
|
361
|
-
|
|
350
|
+
await page.setViewport({ width: 1280, height: 800 });
|
|
362
351
|
await page.goto('about:blank');
|
|
363
352
|
page.setDefaultTimeout(GLOBAL_TIMEOUT);
|
|
364
353
|
page.setDefaultNavigationTimeout(GLOBAL_TIMEOUT);
|
|
365
354
|
|
|
355
|
+
// Handle page errors
|
|
366
356
|
page.on('error', err => this.emit('error', `Page crashed: ${err.message}`));
|
|
367
357
|
page.on('pageerror', err => this.emit('error', `Browser error: ${err.message}`));
|
|
368
358
|
browser.on('disconnected', () => this.emit('debug', 'Browser disconnected'));
|
|
369
359
|
|
|
370
|
-
|
|
360
|
+
// Navigate to MELCloud
|
|
361
|
+
this.emit('warn', 'Navigating to MELCloud...');
|
|
371
362
|
try {
|
|
372
|
-
await page.goto(ApiUrlsHome.BaseURL, {
|
|
363
|
+
await page.goto(ApiUrlsHome.BaseURL, {
|
|
364
|
+
waitUntil: 'networkidle2',
|
|
365
|
+
timeout: GLOBAL_TIMEOUT,
|
|
366
|
+
});
|
|
373
367
|
} catch (error) {
|
|
374
|
-
accountInfo.State = false;
|
|
375
368
|
accountInfo.Info = `Navigation to ${ApiUrlsHome.BaseURL} failed: ${error.message}`;
|
|
376
369
|
return accountInfo;
|
|
377
370
|
}
|
|
378
371
|
|
|
379
|
-
|
|
380
|
-
await new Promise(r => setTimeout(r, 3000));
|
|
372
|
+
await page.waitForTimeout(3000);
|
|
381
373
|
|
|
382
|
-
|
|
374
|
+
// Find login button
|
|
375
|
+
this.emit('warn', 'Looking for login button...');
|
|
383
376
|
const loginBtn = await page.waitForSelector('button.btn--blue', { timeout: GLOBAL_TIMEOUT / 6 });
|
|
384
|
-
const loginText = await
|
|
377
|
+
const loginText = await page.evaluate(el => el.textContent.trim(), loginBtn);
|
|
378
|
+
|
|
385
379
|
if (!['Zaloguj', 'Sign In', 'Login'].includes(loginText)) {
|
|
386
|
-
accountInfo.State = false;
|
|
387
380
|
accountInfo.Info = 'Login button not found or has unexpected label';
|
|
388
381
|
return accountInfo;
|
|
389
382
|
}
|
|
390
383
|
|
|
391
|
-
this.emit('warn', `Found login button ${loginBtn}`);
|
|
392
384
|
await loginBtn.click();
|
|
393
|
-
await page.waitForNavigation({ waitUntil:
|
|
385
|
+
await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: GLOBAL_TIMEOUT / 3 });
|
|
394
386
|
|
|
387
|
+
// Find and fill form
|
|
388
|
+
this.emit('warn', 'Looking for credentials form...');
|
|
389
|
+
const usernameInput = await page.waitForSelector('input[name="username"]', { timeout: GLOBAL_TIMEOUT / 6 });
|
|
390
|
+
const passwordInput = await page.waitForSelector('input[name="password"]', { timeout: GLOBAL_TIMEOUT / 6 });
|
|
395
391
|
|
|
396
|
-
this.emit('warn', `Looking for credentials form...`);
|
|
397
|
-
const usernameInput = await page.$('input[name="username"]');
|
|
398
|
-
const passwordInput = await page.$('input[name="password"]');
|
|
399
392
|
if (!usernameInput || !passwordInput) {
|
|
400
|
-
accountInfo.State = false;
|
|
401
393
|
accountInfo.Info = 'Username or password input not found';
|
|
402
394
|
return accountInfo;
|
|
403
395
|
}
|
|
404
396
|
|
|
405
|
-
this.emit('warn',
|
|
406
|
-
await
|
|
407
|
-
await
|
|
397
|
+
this.emit('warn', 'Typing credentials...');
|
|
398
|
+
await usernameInput.type(this.user, { delay: 50 });
|
|
399
|
+
await passwordInput.type(this.passwd, { delay: 50 });
|
|
408
400
|
|
|
409
|
-
|
|
401
|
+
// Submit form
|
|
402
|
+
this.emit('warn', 'Submitting login form...');
|
|
410
403
|
const submitButton = await page.$('input[type="submit"], button[type="submit"]');
|
|
411
404
|
if (!submitButton) {
|
|
412
|
-
accountInfo.State = false;
|
|
413
405
|
accountInfo.Info = 'Submit button not found';
|
|
414
406
|
return accountInfo;
|
|
415
407
|
}
|
|
416
408
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
409
|
+
await Promise.all([
|
|
410
|
+
submitButton.click(),
|
|
411
|
+
page.waitForNavigation({ waitUntil: 'networkidle2', timeout: GLOBAL_TIMEOUT / 2 }),
|
|
412
|
+
]);
|
|
413
|
+
|
|
414
|
+
await page.waitForTimeout(2000);
|
|
420
415
|
|
|
421
|
-
|
|
422
|
-
|
|
416
|
+
// Get cookies
|
|
417
|
+
this.emit('warn', 'Looking for cookies...');
|
|
418
|
+
const deadline = Date.now() + GLOBAL_TIMEOUT / 2;
|
|
423
419
|
let c1 = null, c2 = null;
|
|
424
|
-
|
|
425
|
-
while (
|
|
426
|
-
const cookies = await page.
|
|
420
|
+
|
|
421
|
+
while (Date.now() < deadline && (!c1 || !c2)) {
|
|
422
|
+
const cookies = await page.cookies();
|
|
427
423
|
c1 = cookies.find(c => c.name === '__Secure-monitorandcontrolC1')?.value || c1;
|
|
428
424
|
c2 = cookies.find(c => c.name === '__Secure-monitorandcontrolC2')?.value || c2;
|
|
429
|
-
if (!c1 || !c2) await
|
|
425
|
+
if (!c1 || !c2) await page.waitForTimeout(500);
|
|
430
426
|
}
|
|
431
427
|
|
|
432
428
|
if (!c1 || !c2) {
|
|
433
|
-
accountInfo.State = false;
|
|
434
429
|
accountInfo.Info = 'Cookies C1/C2 missing';
|
|
435
430
|
return accountInfo;
|
|
436
431
|
}
|
|
437
432
|
|
|
438
|
-
this.emit('warn', `Found cookies`);
|
|
439
433
|
const contextKey = [
|
|
440
434
|
'__Secure-monitorandcontrol=chunks-2',
|
|
441
435
|
`__Secure-monitorandcontrolC1=${c1}`,
|
|
442
|
-
`__Secure-monitorandcontrolC2=${c2}
|
|
436
|
+
`__Secure-monitorandcontrolC2=${c2}`,
|
|
443
437
|
].join('; ');
|
|
444
|
-
this.contextKey = contextKey;
|
|
445
438
|
|
|
446
439
|
accountInfo.State = true;
|
|
447
440
|
accountInfo.Info = 'Connect to MELCloud Home Success';
|
|
448
441
|
accountInfo.ContextKey = contextKey;
|
|
449
|
-
|
|
442
|
+
this.contextKey = contextKey;
|
|
450
443
|
|
|
444
|
+
await this.functions.saveData(this.accountFile, accountInfo);
|
|
451
445
|
return accountInfo;
|
|
446
|
+
|
|
452
447
|
} catch (error) {
|
|
453
448
|
throw new Error(`Connect error: ${error.message}`);
|
|
454
449
|
} finally {
|
|
@@ -459,6 +454,7 @@ class MelCloud extends EventEmitter {
|
|
|
459
454
|
}
|
|
460
455
|
}
|
|
461
456
|
|
|
457
|
+
|
|
462
458
|
async checkDevicesList() {
|
|
463
459
|
const TIMEOUT_MS = 30000; // 30 seconds timeout
|
|
464
460
|
try {
|