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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. 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.541",
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 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,94 @@ 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: 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
- '--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
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
- // Navigate to MELCloud
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
- await page.waitForTimeout(3000);
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
- if (!['Zaloguj', 'Sign In', 'Login'].includes(loginText)) {
376
- accountInfo.Info = 'Login button not found or has unexpected label';
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
- 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 });
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', 'Typing credentials...');
394
- await usernameInput.type(this.user, { delay: 50 });
395
- await passwordInput.type(this.passwd, { delay: 50 });
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
- // Submit form
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
- await Promise.all([
406
- submitButton.click(),
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
- // Get cookies
413
- this.emit('warn', 'Looking for cookies...');
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() < deadline && (!c1 || !c2)) {
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 page.waitForTimeout(500);
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 {