homebridge-melcloud-control 4.0.0-beta.541 → 4.0.0-beta.543
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 +51 -56
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.543",
|
|
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,97 +327,94 @@ 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: true,
|
|
332
|
+
headless: true,
|
|
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
345
|
const page = await browser.newPage();
|
|
348
346
|
page.setDefaultTimeout(GLOBAL_TIMEOUT);
|
|
349
347
|
page.setDefaultNavigationTimeout(GLOBAL_TIMEOUT);
|
|
350
348
|
|
|
351
|
-
//
|
|
349
|
+
// Clear cookies before navigation
|
|
350
|
+
try {
|
|
351
|
+
const client = await page.createCDPSession();
|
|
352
|
+
await client.send('Network.clearBrowserCookies');
|
|
353
|
+
} catch (err) {
|
|
354
|
+
this.emit('warn', `Warning: could not clear cookies: ${err.message}`);
|
|
355
|
+
}
|
|
356
|
+
|
|
352
357
|
page.on('error', err => this.emit('error', `Page crashed: ${err.message}`));
|
|
353
358
|
page.on('pageerror', err => this.emit('error', `Browser error: ${err.message}`));
|
|
354
359
|
browser.on('disconnected', () => this.emit('debug', 'Browser disconnected'));
|
|
355
360
|
|
|
356
|
-
|
|
357
|
-
this.emit('warn', 'Navigating to MELCloud...');
|
|
361
|
+
this.emit('warn', `Navigating to MELCloud...`);
|
|
358
362
|
try {
|
|
359
|
-
await page.goto(ApiUrlsHome.BaseURL, {
|
|
360
|
-
waitUntil: 'networkidle2',
|
|
361
|
-
timeout: GLOBAL_TIMEOUT,
|
|
362
|
-
});
|
|
363
|
+
await page.goto(ApiUrlsHome.BaseURL, { waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT });
|
|
363
364
|
} catch (error) {
|
|
364
365
|
accountInfo.Info = `Navigation to ${ApiUrlsHome.BaseURL} failed: ${error.message}`;
|
|
365
366
|
return accountInfo;
|
|
366
367
|
}
|
|
367
368
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
// Find login button
|
|
371
|
-
this.emit('warn', 'Looking for login button...');
|
|
372
|
-
const loginBtn = await page.waitForSelector('button.btn--blue', { timeout: GLOBAL_TIMEOUT / 6 });
|
|
373
|
-
const loginText = await page.evaluate(el => el.textContent.trim(), loginBtn);
|
|
369
|
+
// Wait extra to ensure UI is rendered
|
|
370
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
374
371
|
|
|
375
|
-
|
|
376
|
-
|
|
372
|
+
this.emit('warn', `Looking for login button...`);
|
|
373
|
+
let loginBtn;
|
|
374
|
+
try {
|
|
375
|
+
loginBtn = await page.waitForFunction(() => {
|
|
376
|
+
const btns = Array.from(document.querySelectorAll('button.btn--blue'));
|
|
377
|
+
return btns.find(b => ['Zaloguj', 'Sign In', 'Login'].includes(b.textContent.trim()));
|
|
378
|
+
}, { timeout: GLOBAL_TIMEOUT / 6 });
|
|
379
|
+
} catch {
|
|
380
|
+
accountInfo.Info = 'Login button not found';
|
|
377
381
|
return accountInfo;
|
|
378
382
|
}
|
|
379
383
|
|
|
380
|
-
|
|
381
|
-
await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: GLOBAL_TIMEOUT /
|
|
382
|
-
|
|
383
|
-
// Find and fill form
|
|
384
|
-
this.emit('warn', 'Looking for credentials form...');
|
|
385
|
-
const usernameInput = await page.waitForSelector('input[name="username"]', { timeout: GLOBAL_TIMEOUT / 6 });
|
|
386
|
-
const passwordInput = await page.waitForSelector('input[name="password"]', { timeout: GLOBAL_TIMEOUT / 6 });
|
|
384
|
+
this.emit('warn', `Found login button ${loginBtn}`);
|
|
385
|
+
await Promise.race([Promise.all([loginBtn.click(), page.waitForNavigation({ waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT / 4 })]), new Promise(r => setTimeout(r, GLOBAL_TIMEOUT / 3))]);
|
|
387
386
|
|
|
387
|
+
this.emit('warn', `Looking for credentials form...`);
|
|
388
|
+
const usernameInput = await page.$('input[name="username"]');
|
|
389
|
+
const passwordInput = await page.$('input[name="password"]');
|
|
388
390
|
if (!usernameInput || !passwordInput) {
|
|
389
391
|
accountInfo.Info = 'Username or password input not found';
|
|
390
392
|
return accountInfo;
|
|
391
393
|
}
|
|
392
394
|
|
|
393
|
-
this.emit('warn',
|
|
394
|
-
await
|
|
395
|
-
await
|
|
395
|
+
this.emit('warn', `Type credentials data..`);
|
|
396
|
+
await page.type('input[name="username"]', this.user, { delay: 50 });
|
|
397
|
+
await page.type('input[name="password"]', this.passwd, { delay: 50 });
|
|
396
398
|
|
|
397
|
-
|
|
398
|
-
this.emit('warn', 'Submitting login form...');
|
|
399
|
+
this.emit('warn', `Looking for submit button...`);
|
|
399
400
|
const submitButton = await page.$('input[type="submit"], button[type="submit"]');
|
|
400
401
|
if (!submitButton) {
|
|
401
402
|
accountInfo.Info = 'Submit button not found';
|
|
402
403
|
return accountInfo;
|
|
403
404
|
}
|
|
404
405
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
page.waitForNavigation({ waitUntil: 'networkidle2', timeout: GLOBAL_TIMEOUT / 2 }),
|
|
408
|
-
]);
|
|
409
|
-
|
|
410
|
-
await page.waitForTimeout(2000);
|
|
406
|
+
this.emit('warn', `Found submit button ${submitButton}`);
|
|
407
|
+
await Promise.race([Promise.all([submitButton.click(), page.waitForNavigation({ waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT / 4 })]), new Promise(r => setTimeout(r, GLOBAL_TIMEOUT / 3))]);
|
|
411
408
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const deadline = Date.now() + GLOBAL_TIMEOUT / 2;
|
|
409
|
+
this.emit('warn', `Looking for cookies...`);
|
|
410
|
+
// Extract cookies
|
|
415
411
|
let c1 = null, c2 = null;
|
|
416
|
-
|
|
417
|
-
while (Date.now()
|
|
418
|
-
const cookies = await page.cookies();
|
|
412
|
+
const start = Date.now();
|
|
413
|
+
while ((!c1 || !c2) && Date.now() - start < GLOBAL_TIMEOUT / 2) {
|
|
414
|
+
const cookies = await page.browserContext().cookies();
|
|
419
415
|
c1 = cookies.find(c => c.name === '__Secure-monitorandcontrolC1')?.value || c1;
|
|
420
416
|
c2 = cookies.find(c => c.name === '__Secure-monitorandcontrolC2')?.value || c2;
|
|
421
|
-
if (!c1 || !c2) await
|
|
417
|
+
if (!c1 || !c2) await new Promise(r => setTimeout(r, 500));
|
|
422
418
|
}
|
|
423
419
|
|
|
424
420
|
if (!c1 || !c2) {
|
|
@@ -426,20 +422,20 @@ class MelCloud extends EventEmitter {
|
|
|
426
422
|
return accountInfo;
|
|
427
423
|
}
|
|
428
424
|
|
|
425
|
+
this.emit('warn', `Found cookies`);
|
|
429
426
|
const contextKey = [
|
|
430
427
|
'__Secure-monitorandcontrol=chunks-2',
|
|
431
428
|
`__Secure-monitorandcontrolC1=${c1}`,
|
|
432
|
-
`__Secure-monitorandcontrolC2=${c2}
|
|
429
|
+
`__Secure-monitorandcontrolC2=${c2}`
|
|
433
430
|
].join('; ');
|
|
431
|
+
this.contextKey = contextKey;
|
|
434
432
|
|
|
435
433
|
accountInfo.State = true;
|
|
436
434
|
accountInfo.Info = 'Connect to MELCloud Home Success';
|
|
437
435
|
accountInfo.ContextKey = contextKey;
|
|
438
|
-
this.contextKey = contextKey;
|
|
439
|
-
|
|
440
436
|
await this.functions.saveData(this.accountFile, accountInfo);
|
|
441
|
-
return accountInfo;
|
|
442
437
|
|
|
438
|
+
return accountInfo;
|
|
443
439
|
} catch (error) {
|
|
444
440
|
throw new Error(`Connect error: ${error.message}`);
|
|
445
441
|
} finally {
|
|
@@ -450,7 +446,6 @@ class MelCloud extends EventEmitter {
|
|
|
450
446
|
}
|
|
451
447
|
}
|
|
452
448
|
|
|
453
|
-
|
|
454
449
|
async checkDevicesList() {
|
|
455
450
|
const TIMEOUT_MS = 30000; // 30 seconds timeout
|
|
456
451
|
try {
|