homebridge-melcloud-control 4.0.0-beta.539 → 4.0.0-beta.540

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 +53 -57
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.539",
4
+ "version": "4.0.0-beta.540",
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,148 +307,143 @@ 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
-
311
310
  let browser;
311
+
312
+ const accountInfo = { State: false, Info: '', ContextKey: null, UseFahrenheit: false };
313
+
312
314
  try {
313
- const accountInfo = { State: false, Info: '', ContextKey: null, UseFahrenheit: false };
314
315
  const chromiumPath = await this.functions.ensureChromiumInstalled();
315
316
  if (!chromiumPath) {
316
- accountInfo.State = false;
317
- accountInfo.Info = 'Chromium not found on Your device, please install it manually and try again';
317
+ accountInfo.Info = 'Chromium not found on your device, please install it manually and try again';
318
318
  return accountInfo;
319
319
  }
320
320
 
321
- // Verify executable works
321
+ // Check if executable is valid
322
322
  try {
323
323
  const { stdout } = await execPromise(`"${chromiumPath}" --version`);
324
324
  this.emit('warn', `Chromium detected: ${stdout.trim()}`);
325
325
  if (this.logDebug) this.emit('debug', `Chromium detected: ${stdout.trim()}`);
326
326
  } catch (error) {
327
- accountInfo.State = false;
328
327
  accountInfo.Info = `Chromium found at ${chromiumPath}, but cannot be executed: ${error.message}`;
329
328
  return accountInfo;
330
329
  }
331
330
 
332
- this.emit('warn', `Launching Chromium...`);
331
+ this.emit('warn', 'Launching Chromium...');
333
332
  browser = await puppeteer.launch({
334
- headless: 'new',
333
+ headless: true, // "new" deprecated, use true
335
334
  executablePath: chromiumPath,
336
335
  timeout: GLOBAL_TIMEOUT,
337
336
  args: [
338
337
  '--no-sandbox',
339
338
  '--disable-setuid-sandbox',
340
339
  '--disable-dev-shm-usage',
341
- '--single-process',
342
340
  '--disable-gpu',
343
- '--no-zygote'
344
- ]
341
+ '--no-zygote',
342
+ '--disable-background-networking',
343
+ '--disable-software-rasterizer',
344
+ ],
345
345
  });
346
346
 
347
- let context;
348
- if (typeof browser.createBrowserContext === 'function') {
349
- // nowoczesne API
350
- context = await browser.createBrowserContext();
351
- } else {
352
- // fallback do starego API (dla bezpieczeństwa)
353
- context = browser.defaultBrowserContext?.() || browser;
354
- }
355
-
347
+ const context = await browser.createIncognitoBrowserContext();
356
348
  const page = await context.newPage();
357
349
 
358
- // --- wyczyszczenie cookies ---
359
- const client = await page.createCDPSession();
360
- await client.send('Network.clearBrowserCookies');
361
-
350
+ await page.setViewport({ width: 1280, height: 800 });
362
351
  await page.goto('about:blank');
363
352
  page.setDefaultTimeout(GLOBAL_TIMEOUT);
364
353
  page.setDefaultNavigationTimeout(GLOBAL_TIMEOUT);
365
354
 
355
+ // Handle page errors
366
356
  page.on('error', err => this.emit('error', `Page crashed: ${err.message}`));
367
357
  page.on('pageerror', err => this.emit('error', `Browser error: ${err.message}`));
368
358
  browser.on('disconnected', () => this.emit('debug', 'Browser disconnected'));
369
359
 
370
- this.emit('warn', `Navigating to MELCloud...`);
360
+ // Navigate to MELCloud
361
+ this.emit('warn', 'Navigating to MELCloud...');
371
362
  try {
372
- await page.goto(ApiUrlsHome.BaseURL, { waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT });
363
+ await page.goto(ApiUrlsHome.BaseURL, {
364
+ waitUntil: 'networkidle2',
365
+ timeout: GLOBAL_TIMEOUT,
366
+ });
373
367
  } catch (error) {
374
- accountInfo.State = false;
375
368
  accountInfo.Info = `Navigation to ${ApiUrlsHome.BaseURL} failed: ${error.message}`;
376
369
  return accountInfo;
377
370
  }
378
371
 
379
- // Wait extra to ensure UI is rendered
380
- await new Promise(r => setTimeout(r, 3000));
372
+ await page.waitForTimeout(3000);
381
373
 
382
- this.emit('warn', `Looking for login button...`);
374
+ // Find login button
375
+ this.emit('warn', 'Looking for login button...');
383
376
  const loginBtn = await page.waitForSelector('button.btn--blue', { timeout: GLOBAL_TIMEOUT / 6 });
384
- const loginText = await loginBtn.evaluate(el => el.textContent.trim());
377
+ const loginText = await page.evaluate(el => el.textContent.trim(), loginBtn);
378
+
385
379
  if (!['Zaloguj', 'Sign In', 'Login'].includes(loginText)) {
386
- accountInfo.State = false;
387
380
  accountInfo.Info = 'Login button not found or has unexpected label';
388
381
  return accountInfo;
389
382
  }
390
383
 
391
- this.emit('warn', `Found login button ${loginBtn}`);
392
384
  await loginBtn.click();
393
- await page.waitForNavigation({ waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT / 4 });
385
+ await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: GLOBAL_TIMEOUT / 3 });
394
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 });
395
391
 
396
- this.emit('warn', `Looking for credentials form...`);
397
- const usernameInput = await page.$('input[name="username"]');
398
- const passwordInput = await page.$('input[name="password"]');
399
392
  if (!usernameInput || !passwordInput) {
400
- accountInfo.State = false;
401
393
  accountInfo.Info = 'Username or password input not found';
402
394
  return accountInfo;
403
395
  }
404
396
 
405
- this.emit('warn', `Type credentials data..`);
406
- await page.type('input[name="username"]', this.user, { delay: 50 });
407
- await page.type('input[name="password"]', this.passwd, { delay: 50 });
397
+ this.emit('warn', 'Typing credentials...');
398
+ await usernameInput.type(this.user, { delay: 50 });
399
+ await passwordInput.type(this.passwd, { delay: 50 });
408
400
 
409
- this.emit('warn', `Looking for submit button...`);
401
+ // Submit form
402
+ this.emit('warn', 'Submitting login form...');
410
403
  const submitButton = await page.$('input[type="submit"], button[type="submit"]');
411
404
  if (!submitButton) {
412
- accountInfo.State = false;
413
405
  accountInfo.Info = 'Submit button not found';
414
406
  return accountInfo;
415
407
  }
416
408
 
417
- this.emit('warn', `Found submit button ${submitButton}`);
418
- await Promise.race([Promise.all([submitButton.click(), page.waitForNavigation({ waitUntil: ['domcontentloaded', 'networkidle2'], timeout: GLOBAL_TIMEOUT / 4 })]), new Promise(r => setTimeout(r, GLOBAL_TIMEOUT / 3))]);
419
- await new Promise(r => setTimeout(r, 2000));
409
+ await Promise.all([
410
+ submitButton.click(),
411
+ page.waitForNavigation({ waitUntil: 'networkidle2', timeout: GLOBAL_TIMEOUT / 2 }),
412
+ ]);
413
+
414
+ await page.waitForTimeout(2000);
420
415
 
421
- this.emit('warn', `Looking for cookies...`);
422
- // Extract cookies
416
+ // Get cookies
417
+ this.emit('warn', 'Looking for cookies...');
418
+ const deadline = Date.now() + GLOBAL_TIMEOUT / 2;
423
419
  let c1 = null, c2 = null;
424
- const start = Date.now();
425
- while ((!c1 || !c2) && Date.now() - start < GLOBAL_TIMEOUT / 2) {
426
- const cookies = await page.browserContext().cookies();
420
+
421
+ while (Date.now() < deadline && (!c1 || !c2)) {
422
+ const cookies = await page.cookies();
427
423
  c1 = cookies.find(c => c.name === '__Secure-monitorandcontrolC1')?.value || c1;
428
424
  c2 = cookies.find(c => c.name === '__Secure-monitorandcontrolC2')?.value || c2;
429
- if (!c1 || !c2) await new Promise(r => setTimeout(r, 500));
425
+ if (!c1 || !c2) await page.waitForTimeout(500);
430
426
  }
431
427
 
432
428
  if (!c1 || !c2) {
433
- accountInfo.State = false;
434
429
  accountInfo.Info = 'Cookies C1/C2 missing';
435
430
  return accountInfo;
436
431
  }
437
432
 
438
- this.emit('warn', `Found cookies`);
439
433
  const contextKey = [
440
434
  '__Secure-monitorandcontrol=chunks-2',
441
435
  `__Secure-monitorandcontrolC1=${c1}`,
442
- `__Secure-monitorandcontrolC2=${c2}`
436
+ `__Secure-monitorandcontrolC2=${c2}`,
443
437
  ].join('; ');
444
- this.contextKey = contextKey;
445
438
 
446
439
  accountInfo.State = true;
447
440
  accountInfo.Info = 'Connect to MELCloud Home Success';
448
441
  accountInfo.ContextKey = contextKey;
449
- await this.functions.saveData(this.accountFile, accountInfo);
442
+ this.contextKey = contextKey;
450
443
 
444
+ await this.functions.saveData(this.accountFile, accountInfo);
451
445
  return accountInfo;
446
+
452
447
  } catch (error) {
453
448
  throw new Error(`Connect error: ${error.message}`);
454
449
  } finally {
@@ -459,6 +454,7 @@ class MelCloud extends EventEmitter {
459
454
  }
460
455
  }
461
456
 
457
+
462
458
  async checkDevicesList() {
463
459
  const TIMEOUT_MS = 30000; // 30 seconds timeout
464
460
  try {