homebridge-melcloud-control 4.0.0-beta.541 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/melcloud.js +43 -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.541",
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 your device, please install it manually and try again';
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
- // Check if executable is valid
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,86 @@ class MelCloud extends EventEmitter {
328
327
  return accountInfo;
329
328
  }
330
329
 
331
- this.emit('warn', 'Launching Chromium...');
330
+ this.emit('warn', `Launching Chromium...`);
332
331
  browser = await puppeteer.launch({
333
- headless: true, // "new" deprecated, use true
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
- '--disable-background-networking',
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
- // Handle page errors
352
349
  page.on('error', err => this.emit('error', `Page crashed: ${err.message}`));
353
350
  page.on('pageerror', err => this.emit('error', `Browser error: ${err.message}`));
354
351
  browser.on('disconnected', () => this.emit('debug', 'Browser disconnected'));
355
352
 
356
- // Navigate to MELCloud
357
- this.emit('warn', 'Navigating to MELCloud...');
353
+ this.emit('warn', `Navigating to MELCloud...`);
358
354
  try {
359
- await page.goto(ApiUrlsHome.BaseURL, {
360
- waitUntil: 'networkidle2',
361
- timeout: GLOBAL_TIMEOUT,
362
- });
355
+ await page.goto(ApiUrlsHome.BaseURL, { waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT });
363
356
  } catch (error) {
364
357
  accountInfo.Info = `Navigation to ${ApiUrlsHome.BaseURL} failed: ${error.message}`;
365
358
  return accountInfo;
366
359
  }
367
360
 
368
- await page.waitForTimeout(3000);
361
+ // Wait extra to ensure UI is rendered
362
+ await new Promise(r => setTimeout(r, 3000));
369
363
 
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);
374
-
375
- if (!['Zaloguj', 'Sign In', 'Login'].includes(loginText)) {
376
- accountInfo.Info = 'Login button not found or has unexpected label';
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';
377
373
  return accountInfo;
378
374
  }
379
375
 
380
- await loginBtn.click();
381
- await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: GLOBAL_TIMEOUT / 3 });
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 });
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))]);
387
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"]');
388
382
  if (!usernameInput || !passwordInput) {
389
383
  accountInfo.Info = 'Username or password input not found';
390
384
  return accountInfo;
391
385
  }
392
386
 
393
- this.emit('warn', 'Typing credentials...');
394
- await usernameInput.type(this.user, { delay: 50 });
395
- await passwordInput.type(this.passwd, { delay: 50 });
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 });
396
390
 
397
- // Submit form
398
- this.emit('warn', 'Submitting login form...');
391
+ this.emit('warn', `Looking for submit button...`);
399
392
  const submitButton = await page.$('input[type="submit"], button[type="submit"]');
400
393
  if (!submitButton) {
401
394
  accountInfo.Info = 'Submit button not found';
402
395
  return accountInfo;
403
396
  }
404
397
 
405
- await Promise.all([
406
- submitButton.click(),
407
- page.waitForNavigation({ waitUntil: 'networkidle2', timeout: GLOBAL_TIMEOUT / 2 }),
408
- ]);
409
-
410
- await page.waitForTimeout(2000);
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))]);
411
400
 
412
- // Get cookies
413
- this.emit('warn', 'Looking for cookies...');
414
- const deadline = Date.now() + GLOBAL_TIMEOUT / 2;
401
+ this.emit('warn', `Looking for cookies...`);
402
+ // Extract cookies
415
403
  let c1 = null, c2 = null;
416
-
417
- while (Date.now() < deadline && (!c1 || !c2)) {
418
- 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();
419
407
  c1 = cookies.find(c => c.name === '__Secure-monitorandcontrolC1')?.value || c1;
420
408
  c2 = cookies.find(c => c.name === '__Secure-monitorandcontrolC2')?.value || c2;
421
- if (!c1 || !c2) await page.waitForTimeout(500);
409
+ if (!c1 || !c2) await new Promise(r => setTimeout(r, 500));
422
410
  }
423
411
 
424
412
  if (!c1 || !c2) {
@@ -426,20 +414,20 @@ class MelCloud extends EventEmitter {
426
414
  return accountInfo;
427
415
  }
428
416
 
417
+ this.emit('warn', `Found cookies`);
429
418
  const contextKey = [
430
419
  '__Secure-monitorandcontrol=chunks-2',
431
420
  `__Secure-monitorandcontrolC1=${c1}`,
432
- `__Secure-monitorandcontrolC2=${c2}`,
421
+ `__Secure-monitorandcontrolC2=${c2}`
433
422
  ].join('; ');
423
+ this.contextKey = contextKey;
434
424
 
435
425
  accountInfo.State = true;
436
426
  accountInfo.Info = 'Connect to MELCloud Home Success';
437
427
  accountInfo.ContextKey = contextKey;
438
- this.contextKey = contextKey;
439
-
440
428
  await this.functions.saveData(this.accountFile, accountInfo);
441
- return accountInfo;
442
429
 
430
+ return accountInfo;
443
431
  } catch (error) {
444
432
  throw new Error(`Connect error: ${error.message}`);
445
433
  } finally {
@@ -450,7 +438,6 @@ class MelCloud extends EventEmitter {
450
438
  }
451
439
  }
452
440
 
453
-
454
441
  async checkDevicesList() {
455
442
  const TIMEOUT_MS = 30000; // 30 seconds timeout
456
443
  try {