homebridge-melcloud-control 4.0.0-beta.540 → 4.0.0-beta.542
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 +44 -61
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.542",
|
|
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,18 +307,17 @@ 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
|
-
let browser;
|
|
311
|
-
|
|
312
|
-
const accountInfo = { State: false, Info: '', ContextKey: null, UseFahrenheit: false };
|
|
313
310
|
|
|
311
|
+
let browser;
|
|
314
312
|
try {
|
|
313
|
+
const accountInfo = { State: false, Info: '', ContextKey: null, UseFahrenheit: false };
|
|
315
314
|
const chromiumPath = await this.functions.ensureChromiumInstalled();
|
|
316
315
|
if (!chromiumPath) {
|
|
317
|
-
accountInfo.Info = 'Chromium not found on
|
|
316
|
+
accountInfo.Info = 'Chromium not found on Your device, please install it manually and try again';
|
|
318
317
|
return accountInfo;
|
|
319
318
|
}
|
|
320
319
|
|
|
321
|
-
//
|
|
320
|
+
// Verify executable works
|
|
322
321
|
try {
|
|
323
322
|
const { stdout } = await execPromise(`"${chromiumPath}" --version`);
|
|
324
323
|
this.emit('warn', `Chromium detected: ${stdout.trim()}`);
|
|
@@ -328,101 +327,86 @@ class MelCloud extends EventEmitter {
|
|
|
328
327
|
return accountInfo;
|
|
329
328
|
}
|
|
330
329
|
|
|
331
|
-
this.emit('warn',
|
|
330
|
+
this.emit('warn', `Launching Chromium...`);
|
|
332
331
|
browser = await puppeteer.launch({
|
|
333
|
-
headless:
|
|
332
|
+
headless: 'shell',
|
|
334
333
|
executablePath: chromiumPath,
|
|
335
334
|
timeout: GLOBAL_TIMEOUT,
|
|
336
335
|
args: [
|
|
337
336
|
'--no-sandbox',
|
|
338
337
|
'--disable-setuid-sandbox',
|
|
339
338
|
'--disable-dev-shm-usage',
|
|
339
|
+
'--single-process',
|
|
340
340
|
'--disable-gpu',
|
|
341
|
-
'--no-zygote'
|
|
342
|
-
|
|
343
|
-
'--disable-software-rasterizer',
|
|
344
|
-
],
|
|
341
|
+
'--no-zygote'
|
|
342
|
+
]
|
|
345
343
|
});
|
|
346
344
|
|
|
347
|
-
const
|
|
348
|
-
const page = await context.newPage();
|
|
349
|
-
|
|
350
|
-
await page.setViewport({ width: 1280, height: 800 });
|
|
351
|
-
await page.goto('about:blank');
|
|
345
|
+
const page = await browser.newPage();
|
|
352
346
|
page.setDefaultTimeout(GLOBAL_TIMEOUT);
|
|
353
347
|
page.setDefaultNavigationTimeout(GLOBAL_TIMEOUT);
|
|
354
348
|
|
|
355
|
-
// Handle page errors
|
|
356
349
|
page.on('error', err => this.emit('error', `Page crashed: ${err.message}`));
|
|
357
350
|
page.on('pageerror', err => this.emit('error', `Browser error: ${err.message}`));
|
|
358
351
|
browser.on('disconnected', () => this.emit('debug', 'Browser disconnected'));
|
|
359
352
|
|
|
360
|
-
|
|
361
|
-
this.emit('warn', 'Navigating to MELCloud...');
|
|
353
|
+
this.emit('warn', `Navigating to MELCloud...`);
|
|
362
354
|
try {
|
|
363
|
-
await page.goto(ApiUrlsHome.BaseURL, {
|
|
364
|
-
waitUntil: 'networkidle2',
|
|
365
|
-
timeout: GLOBAL_TIMEOUT,
|
|
366
|
-
});
|
|
355
|
+
await page.goto(ApiUrlsHome.BaseURL, { waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT });
|
|
367
356
|
} catch (error) {
|
|
368
357
|
accountInfo.Info = `Navigation to ${ApiUrlsHome.BaseURL} failed: ${error.message}`;
|
|
369
358
|
return accountInfo;
|
|
370
359
|
}
|
|
371
360
|
|
|
372
|
-
|
|
361
|
+
// Wait extra to ensure UI is rendered
|
|
362
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
373
363
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
364
|
+
this.emit('warn', `Looking for login button...`);
|
|
365
|
+
let loginBtn;
|
|
366
|
+
try {
|
|
367
|
+
loginBtn = await page.waitForFunction(() => {
|
|
368
|
+
const btns = Array.from(document.querySelectorAll('button.btn--blue'));
|
|
369
|
+
return btns.find(b => ['Zaloguj', 'Sign In', 'Login'].includes(b.textContent.trim()));
|
|
370
|
+
}, { timeout: GLOBAL_TIMEOUT / 6 });
|
|
371
|
+
} catch {
|
|
372
|
+
accountInfo.Info = 'Login button not found';
|
|
381
373
|
return accountInfo;
|
|
382
374
|
}
|
|
383
375
|
|
|
384
|
-
|
|
385
|
-
await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: GLOBAL_TIMEOUT /
|
|
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 });
|
|
376
|
+
this.emit('warn', `Found login button ${loginBtn}`);
|
|
377
|
+
await Promise.race([Promise.all([loginBtn.click(), page.waitForNavigation({ waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT / 4 })]), new Promise(r => setTimeout(r, GLOBAL_TIMEOUT / 3))]);
|
|
391
378
|
|
|
379
|
+
this.emit('warn', `Looking for credentials form...`);
|
|
380
|
+
const usernameInput = await page.$('input[name="username"]');
|
|
381
|
+
const passwordInput = await page.$('input[name="password"]');
|
|
392
382
|
if (!usernameInput || !passwordInput) {
|
|
393
383
|
accountInfo.Info = 'Username or password input not found';
|
|
394
384
|
return accountInfo;
|
|
395
385
|
}
|
|
396
386
|
|
|
397
|
-
this.emit('warn',
|
|
398
|
-
await
|
|
399
|
-
await
|
|
387
|
+
this.emit('warn', `Type credentials data..`);
|
|
388
|
+
await page.type('input[name="username"]', this.user, { delay: 50 });
|
|
389
|
+
await page.type('input[name="password"]', this.passwd, { delay: 50 });
|
|
400
390
|
|
|
401
|
-
|
|
402
|
-
this.emit('warn', 'Submitting login form...');
|
|
391
|
+
this.emit('warn', `Looking for submit button...`);
|
|
403
392
|
const submitButton = await page.$('input[type="submit"], button[type="submit"]');
|
|
404
393
|
if (!submitButton) {
|
|
405
394
|
accountInfo.Info = 'Submit button not found';
|
|
406
395
|
return accountInfo;
|
|
407
396
|
}
|
|
408
397
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
page.waitForNavigation({ waitUntil: 'networkidle2', timeout: GLOBAL_TIMEOUT / 2 }),
|
|
412
|
-
]);
|
|
398
|
+
this.emit('warn', `Found submit button ${submitButton}`);
|
|
399
|
+
await Promise.race([Promise.all([submitButton.click(), page.waitForNavigation({ waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT / 4 })]), new Promise(r => setTimeout(r, GLOBAL_TIMEOUT / 3))]);
|
|
413
400
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
// Get cookies
|
|
417
|
-
this.emit('warn', 'Looking for cookies...');
|
|
418
|
-
const deadline = Date.now() + GLOBAL_TIMEOUT / 2;
|
|
401
|
+
this.emit('warn', `Looking for cookies...`);
|
|
402
|
+
// Extract cookies
|
|
419
403
|
let c1 = null, c2 = null;
|
|
420
|
-
|
|
421
|
-
while (Date.now()
|
|
422
|
-
const cookies = await page.cookies();
|
|
404
|
+
const start = Date.now();
|
|
405
|
+
while ((!c1 || !c2) && Date.now() - start < GLOBAL_TIMEOUT / 2) {
|
|
406
|
+
const cookies = await page.browserContext().cookies();
|
|
423
407
|
c1 = cookies.find(c => c.name === '__Secure-monitorandcontrolC1')?.value || c1;
|
|
424
408
|
c2 = cookies.find(c => c.name === '__Secure-monitorandcontrolC2')?.value || c2;
|
|
425
|
-
if (!c1 || !c2) await
|
|
409
|
+
if (!c1 || !c2) await new Promise(r => setTimeout(r, 500));
|
|
426
410
|
}
|
|
427
411
|
|
|
428
412
|
if (!c1 || !c2) {
|
|
@@ -430,20 +414,20 @@ class MelCloud extends EventEmitter {
|
|
|
430
414
|
return accountInfo;
|
|
431
415
|
}
|
|
432
416
|
|
|
417
|
+
this.emit('warn', `Found cookies`);
|
|
433
418
|
const contextKey = [
|
|
434
419
|
'__Secure-monitorandcontrol=chunks-2',
|
|
435
420
|
`__Secure-monitorandcontrolC1=${c1}`,
|
|
436
|
-
`__Secure-monitorandcontrolC2=${c2}
|
|
421
|
+
`__Secure-monitorandcontrolC2=${c2}`
|
|
437
422
|
].join('; ');
|
|
423
|
+
this.contextKey = contextKey;
|
|
438
424
|
|
|
439
425
|
accountInfo.State = true;
|
|
440
426
|
accountInfo.Info = 'Connect to MELCloud Home Success';
|
|
441
427
|
accountInfo.ContextKey = contextKey;
|
|
442
|
-
this.contextKey = contextKey;
|
|
443
|
-
|
|
444
428
|
await this.functions.saveData(this.accountFile, accountInfo);
|
|
445
|
-
return accountInfo;
|
|
446
429
|
|
|
430
|
+
return accountInfo;
|
|
447
431
|
} catch (error) {
|
|
448
432
|
throw new Error(`Connect error: ${error.message}`);
|
|
449
433
|
} finally {
|
|
@@ -454,7 +438,6 @@ class MelCloud extends EventEmitter {
|
|
|
454
438
|
}
|
|
455
439
|
}
|
|
456
440
|
|
|
457
|
-
|
|
458
441
|
async checkDevicesList() {
|
|
459
442
|
const TIMEOUT_MS = 30000; // 30 seconds timeout
|
|
460
443
|
try {
|