homebridge-melcloud-control 4.0.0-beta.507 → 4.0.0-beta.509
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 +41 -40
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.509",
|
|
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
|
@@ -303,20 +303,22 @@ class MelCloud extends EventEmitter {
|
|
|
303
303
|
|
|
304
304
|
async connectToMelCloudHome(refresh = false) {
|
|
305
305
|
if (this.logDebug) this.emit('debug', 'Connecting to MELCloud Home');
|
|
306
|
+
const GLOBAL_TIMEOUT = 45000;
|
|
306
307
|
|
|
307
308
|
let browser;
|
|
308
309
|
try {
|
|
309
|
-
const accountInfo = { State: false, Info: '', ContextKey: null, UseFahrenheit: false }
|
|
310
|
+
const accountInfo = { State: false, Info: '', ContextKey: null, UseFahrenheit: false };
|
|
310
311
|
const chromiumPath = await this.functions.ensureChromiumInstalled();
|
|
311
312
|
if (!chromiumPath) {
|
|
312
313
|
accountInfo.State = false;
|
|
313
|
-
accountInfo.Info = 'Chromium not found on Your device, please install it manually and try again'
|
|
314
|
+
accountInfo.Info = 'Chromium not found on Your device, please install it manually and try again';
|
|
314
315
|
return accountInfo;
|
|
315
316
|
}
|
|
316
317
|
|
|
318
|
+
// Verify executable works
|
|
317
319
|
try {
|
|
318
320
|
const { stdout } = await execPromise(`"${chromiumPath}" --version`);
|
|
319
|
-
this.emit('warn', `
|
|
321
|
+
this.emit('warn', `Chromium detected: ${stdout.trim()}`);
|
|
320
322
|
if (this.logDebug) this.emit('debug', `Chromium detected: ${stdout.trim()}`);
|
|
321
323
|
} catch (error) {
|
|
322
324
|
accountInfo.State = false;
|
|
@@ -324,11 +326,11 @@ class MelCloud extends EventEmitter {
|
|
|
324
326
|
return accountInfo;
|
|
325
327
|
}
|
|
326
328
|
|
|
327
|
-
this.emit('warn', `
|
|
329
|
+
this.emit('warn', `Launching Chromium...`);
|
|
328
330
|
browser = await puppeteer.launch({
|
|
329
331
|
headless: 'shell',
|
|
330
332
|
executablePath: chromiumPath,
|
|
331
|
-
timeout:
|
|
333
|
+
timeout: GLOBAL_TIMEOUT,
|
|
332
334
|
args: [
|
|
333
335
|
'--no-sandbox',
|
|
334
336
|
'--disable-setuid-sandbox',
|
|
@@ -339,52 +341,47 @@ class MelCloud extends EventEmitter {
|
|
|
339
341
|
]
|
|
340
342
|
});
|
|
341
343
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
// Defensive check for main frame availability
|
|
346
|
-
this.emit('warn', `Test 2`);
|
|
347
|
-
const pages = await browser.pages();
|
|
348
|
-
let page = pages[0];
|
|
349
|
-
if (!page) {
|
|
350
|
-
if (this.logWarn) this.emit('warn', 'No initial page found, creating a new one');
|
|
351
|
-
page = await browser.newPage();
|
|
352
|
-
await new Promise(r => setTimeout(r, 1500));
|
|
353
|
-
}
|
|
344
|
+
const [page] = await browser.pages();
|
|
345
|
+
page.setDefaultTimeout(GLOBAL_TIMEOUT);
|
|
346
|
+
page.setDefaultNavigationTimeout(GLOBAL_TIMEOUT);
|
|
354
347
|
|
|
355
|
-
this.emit('
|
|
356
|
-
page.on('
|
|
357
|
-
|
|
358
|
-
page.on('close', () => { if (this.logDebug) this.emit('debug', 'Page was closed unexpectedly'); });
|
|
359
|
-
browser.on('disconnected', () => { if (this.logWarn) this.emit('debug', 'Browser disconnected unexpectedly'); });
|
|
348
|
+
page.on('error', err => this.emit('error', `Page crashed: ${err.message}`));
|
|
349
|
+
page.on('pageerror', err => this.emit('error', `Browser error: ${err.message}`));
|
|
350
|
+
browser.on('disconnected', () => this.emit('warn', 'Browser disconnected unexpectedly'));
|
|
360
351
|
|
|
361
|
-
|
|
362
|
-
this.emit('warn', `Test 4`);
|
|
352
|
+
this.emit('warn', `Navigating to MELCloud...`);
|
|
363
353
|
try {
|
|
364
|
-
await page.goto(ApiUrlsHome.BaseURL, { waitUntil: ['domcontentloaded', 'networkidle2'], timeout:
|
|
354
|
+
await page.goto(ApiUrlsHome.BaseURL, { waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT });
|
|
365
355
|
} catch (error) {
|
|
366
356
|
accountInfo.State = false;
|
|
367
357
|
accountInfo.Info = `Navigation to ${ApiUrlsHome.BaseURL} failed: ${error.message}`;
|
|
368
358
|
return accountInfo;
|
|
369
359
|
}
|
|
370
|
-
await new Promise(r => setTimeout(r, 4000));
|
|
371
360
|
|
|
361
|
+
// Wait extra to ensure UI is rendered
|
|
362
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
363
|
+
|
|
364
|
+
this.emit('warn', `Looking for login button...`);
|
|
372
365
|
let loginBtn;
|
|
373
366
|
try {
|
|
374
|
-
this.emit('warn', `Test 5`);
|
|
375
367
|
loginBtn = await page.waitForFunction(() => {
|
|
376
368
|
const btns = Array.from(document.querySelectorAll('button.btn--blue'));
|
|
377
369
|
return btns.find(b => ['Zaloguj', 'Sign In', 'Login'].includes(b.textContent.trim()));
|
|
378
|
-
}, { timeout:
|
|
370
|
+
}, { timeout: GLOBAL_TIMEOUT / 6 });
|
|
379
371
|
} catch {
|
|
380
372
|
accountInfo.State = false;
|
|
381
|
-
accountInfo.Info = 'Login button not found
|
|
373
|
+
accountInfo.Info = 'Login button not found';
|
|
382
374
|
return accountInfo;
|
|
383
375
|
}
|
|
384
376
|
|
|
385
|
-
await Promise.race([
|
|
377
|
+
await Promise.race([
|
|
378
|
+
Promise.all([
|
|
379
|
+
loginBtn.click(),
|
|
380
|
+
page.waitForNavigation({ waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT / 4 })
|
|
381
|
+
]),
|
|
382
|
+
new Promise(r => setTimeout(r, GLOBAL_TIMEOUT / 3))
|
|
383
|
+
]);
|
|
386
384
|
|
|
387
|
-
this.emit('warn', `Test 6`);
|
|
388
385
|
const usernameInput = await page.$('input[name="username"]');
|
|
389
386
|
const passwordInput = await page.$('input[name="password"]');
|
|
390
387
|
if (!usernameInput || !passwordInput) {
|
|
@@ -393,31 +390,34 @@ class MelCloud extends EventEmitter {
|
|
|
393
390
|
return accountInfo;
|
|
394
391
|
}
|
|
395
392
|
|
|
396
|
-
this.emit('warn', `Test 7`);
|
|
397
393
|
await page.type('input[name="username"]', this.user, { delay: 50 });
|
|
398
394
|
await page.type('input[name="password"]', this.passwd, { delay: 50 });
|
|
399
395
|
|
|
400
|
-
this.emit('warn', `Test 8`);
|
|
401
396
|
const submitButton = await page.$('input[type="submit"], button[type="submit"]');
|
|
402
397
|
if (!submitButton) {
|
|
403
398
|
accountInfo.State = false;
|
|
404
|
-
accountInfo.Info = 'Submit button not found
|
|
399
|
+
accountInfo.Info = 'Submit button not found';
|
|
405
400
|
return accountInfo;
|
|
406
401
|
}
|
|
407
402
|
|
|
408
|
-
await Promise.race([
|
|
403
|
+
await Promise.race([
|
|
404
|
+
Promise.all([
|
|
405
|
+
submitButton.click(),
|
|
406
|
+
page.waitForNavigation({ waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT / 4 })
|
|
407
|
+
]),
|
|
408
|
+
new Promise(r => setTimeout(r, GLOBAL_TIMEOUT / 3))
|
|
409
|
+
]);
|
|
409
410
|
|
|
410
|
-
|
|
411
|
+
// Extract cookies
|
|
411
412
|
let c1 = null, c2 = null;
|
|
412
413
|
const start = Date.now();
|
|
413
|
-
while ((!c1 || !c2) && Date.now() - start <
|
|
414
|
+
while ((!c1 || !c2) && Date.now() - start < GLOBAL_TIMEOUT / 2) {
|
|
414
415
|
const cookies = await page.browserContext().cookies();
|
|
415
416
|
c1 = cookies.find(c => c.name === '__Secure-monitorandcontrolC1')?.value || c1;
|
|
416
417
|
c2 = cookies.find(c => c.name === '__Secure-monitorandcontrolC2')?.value || c2;
|
|
417
418
|
if (!c1 || !c2) await new Promise(r => setTimeout(r, 500));
|
|
418
419
|
}
|
|
419
420
|
|
|
420
|
-
this.emit('warn', `Test 10`);
|
|
421
421
|
if (!c1 || !c2) {
|
|
422
422
|
accountInfo.State = false;
|
|
423
423
|
accountInfo.Info = 'Cookies C1/C2 missing';
|
|
@@ -435,21 +435,22 @@ class MelCloud extends EventEmitter {
|
|
|
435
435
|
accountInfo.Info = 'Connect success';
|
|
436
436
|
accountInfo.ContextKey = contextKey;
|
|
437
437
|
|
|
438
|
-
this.emit('warn', `Test 11`);
|
|
439
438
|
await this.functions.saveData(this.accountFile, accountInfo);
|
|
440
439
|
|
|
441
440
|
if (!refresh) this.emit('success', 'Connect to MELCloud Home Success');
|
|
442
441
|
return accountInfo;
|
|
442
|
+
|
|
443
443
|
} catch (error) {
|
|
444
444
|
throw new Error(`Connect error: ${error.message}`);
|
|
445
445
|
} finally {
|
|
446
446
|
if (browser) {
|
|
447
447
|
try { await browser.close(); }
|
|
448
|
-
catch (closeErr) {
|
|
448
|
+
catch (closeErr) { this.emit('error', `Failed to close Puppeteer: ${closeErr.message}`); }
|
|
449
449
|
}
|
|
450
450
|
}
|
|
451
451
|
}
|
|
452
452
|
|
|
453
|
+
|
|
453
454
|
async checkDevicesList() {
|
|
454
455
|
const TIMEOUT_MS = 30000; // 30 seconds timeout
|
|
455
456
|
try {
|