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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. 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.540",
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,101 +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
- const context = await browser.createIncognitoBrowserContext();
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
- // Navigate to MELCloud
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
- await page.waitForTimeout(3000);
361
+ // Wait extra to ensure UI is rendered
362
+ await new Promise(r => setTimeout(r, 3000));
373
363
 
374
- // Find login button
375
- this.emit('warn', 'Looking for login button...');
376
- const loginBtn = await page.waitForSelector('button.btn--blue', { timeout: GLOBAL_TIMEOUT / 6 });
377
- const loginText = await page.evaluate(el => el.textContent.trim(), loginBtn);
378
-
379
- if (!['Zaloguj', 'Sign In', 'Login'].includes(loginText)) {
380
- 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';
381
373
  return accountInfo;
382
374
  }
383
375
 
384
- await loginBtn.click();
385
- await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: GLOBAL_TIMEOUT / 3 });
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', 'Typing credentials...');
398
- await usernameInput.type(this.user, { delay: 50 });
399
- 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 });
400
390
 
401
- // Submit form
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
- await Promise.all([
410
- submitButton.click(),
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
- await page.waitForTimeout(2000);
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() < deadline && (!c1 || !c2)) {
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 page.waitForTimeout(500);
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 {