homebridge-melcloud-control 4.0.0-beta.297 → 4.0.0-beta.299

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "displayName": "MELCloud Control",
3
3
  "name": "homebridge-melcloud-control",
4
- "version": "4.0.0-beta.297",
4
+ "version": "4.0.0-beta.299",
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
@@ -9,6 +9,7 @@ import Functions from './functions.js';
9
9
  import { ApiUrls, ApiUrlsHome } from './constants.js';
10
10
  import { URLSearchParams } from 'url';
11
11
  const TOKEN_ENDPOINT = 'https://auth.melcloudhome.com/connect/token';
12
+ const REDIRECT_URI = 'melcloudhome://';
12
13
 
13
14
  class MelCloud extends EventEmitter {
14
15
  constructor(accountType, user, passwd, language, accountFile, buildingsFile, devicesFile, logWarn, logDebug, requestConfig) {
@@ -291,8 +292,6 @@ class MelCloud extends EventEmitter {
291
292
  const melCloudHomeToken = new MelCloudHomeToken();
292
293
  const url = await melCloudHomeToken.buildAuthorizeUrl();
293
294
 
294
- this.emit('warn', url);
295
-
296
295
  const page = await browser.newPage();
297
296
  await page.goto(url, { waitUntil: 'networkidle2' });
298
297
 
@@ -302,52 +301,12 @@ class MelCloud extends EventEmitter {
302
301
  const button1 = await page.$('input[type="submit"]');
303
302
  await Promise.all([button1.click(), page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 10000 })]);
304
303
  const html = await page.content();
305
- const code = await melCloudHomeToken.extractCodeFromHtml(html);
306
-
307
- console.log(code);
308
- return
309
- // Check for form_post response (HTML with hidden form)
310
- const formCodeMatch = html.match(/name="code"\s+value="([^"]+)"/);
311
- const formStateMatch = html.match(/name="state"\s+value="([^"]+)"/);
312
- const formActionMatch = html.match(/action="([^"]+)"/);
313
-
314
- if (formCodeMatch && formStateMatch && formActionMatch) {
315
- console.log('Found form_post response, submitting to callback endpoint...');
316
- this.submitFormPost(formActionMatch[1], formCodeMatch[1], formStateMatch[1], cookies)
317
- .then(code => resolve(code))
318
- .catch(err => reject(err));
319
- return;
320
- }
321
-
322
- // Check for melcloudhome:// redirect in JS
323
- const bodyCodeMatch = html.match(/melcloudhome:\/\/[^"'\s]*[?&]code=([^&"'\s]+)/);
324
- if (bodyCodeMatch) {
325
- console.log('Found authorization code in response body (JS)');
326
- resolve(bodyCodeMatch[1]);
327
- return;
328
- }
329
-
330
- // 🆕 NEW: Check for data-url with code parameter (like in 500 error pages)
331
- const dataUrlMatch = html.match(/data-url="[^"]*code=([^&"']+)/);
332
- if (dataUrlMatch) {
333
- console.log('Found authorization code in data-url attribute');
334
- resolve(dataUrlMatch[1]);
335
- return;
336
- }
337
-
338
- reject(new Error('Authorization code not found in URL or HTML'));
304
+ const authCode = await melCloudHomeToken.extractCodeFromHtml(html);
305
+ this.emit('warn', authCode);
339
306
 
340
307
  // 5. Wymień code na access_token
341
- const tokenResponse = await axios.post(TOKEN_ENDPOINT, new URLSearchParams({
342
- client_id: 'homemobile',
343
- grant_type: 'authorization_code',
344
- code: authCode,
345
- redirect_uri: 'melcloudhome://'
346
- }).toString(), {
347
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
348
- });
349
-
350
- const tokenData = await tokenResp.json();
308
+ const tokenResponse = await melCloudHomeToken.getTokens(authCode);
309
+ const tokenData = await tokenResponse.json();
351
310
  this.emit('debug', `Token: ${JSON.stringify(tokenData, null, 2)}`);
352
311
  return {
353
312
  accessToken: tokenData.access_token,
@@ -11,8 +11,7 @@ const TOKEN_ENDPOINT = 'https://auth.melcloudhome.com/connect/token';
11
11
  const AUTHORIZE_ENDPOINT = 'https://auth.melcloudhome.com/connect/authorize';
12
12
 
13
13
  class MelCloudHomeToken {
14
- constructor(log = console) {
15
- this.log = log;
14
+ constructor() {
16
15
  const jar = new CookieJar();
17
16
  this.client = wrapper(axios.create({ jar, withCredentials: true }));
18
17
  this.client.defaults.headers['User-Agent'] = MOBILE_USER_AGENT;
@@ -33,48 +32,73 @@ class MelCloudHomeToken {
33
32
  return { url: res.request.res.responseUrl };
34
33
  }
35
34
 
36
- async extractCodeFromHtml(html) {
37
- let code = null;
38
-
39
- // Sprawdzenie, czy code jest w URL
40
- const urlMatch = html.match(/[?&]code=([^&]+)/);
41
- if (urlMatch) {
42
- console.log('Authorization code found in URL');
43
- return urlMatch[1];
44
- }
45
-
46
- // Check for form_post response (HTML with hidden form)
47
- const formCodeMatch = html.match(/name="code"\s+value="([^"]+)"/);
48
- const formStateMatch = html.match(/name="state"\s+value="([^"]+)"/);
49
- const formActionMatch = html.match(/action="([^"]+)"/);
50
-
51
- if (formCodeMatch && formStateMatch && formActionMatch) {
52
- console.log('Found form_post response, submitting to callback endpoint...');
53
- this.submitFormPost(formActionMatch[1], formCodeMatch[1], formStateMatch[1], cookies)
54
- .then(code => resolve(code))
55
- .catch(err => reject(err));
56
- return;
57
- }
58
-
59
- // Check for melcloudhome:// redirect in JS
60
- const bodyCodeMatch = html.match(/melcloudhome:\/\/[^"'\s]*[?&]code=([^&"'\s]+)/);
61
- if (bodyCodeMatch) {
62
- console.log('Found authorization code in response body (JS)');
63
- resolve(bodyCodeMatch[1]);
64
- return;
35
+ async extractCodeFromHtml(html, cookies = '') {
36
+ try {
37
+ // Code w URL
38
+ const urlMatch = html.match(/[?&]code=([^&]+)/);
39
+ if (urlMatch) {
40
+ return urlMatch[1];
41
+ }
42
+
43
+ // Hidden form (form_post)
44
+ const formCodeMatch = html.match(/name="code"\s+value="([^"]+)"/);
45
+ const formStateMatch = html.match(/name="state"\s+value="([^"]+)"/);
46
+ const formActionMatch = html.match(/action="([^"]+)"/);
47
+
48
+ if (formCodeMatch && formStateMatch && formActionMatch) {
49
+ const code = await this.submitFormPost(
50
+ formActionMatch[1],
51
+ formCodeMatch[1],
52
+ formStateMatch[1],
53
+ cookies
54
+ );
55
+ return code;
56
+ }
57
+
58
+ // melcloudhome:// w body
59
+ const bodyCodeMatch = html.match(/melcloudhome:\/\/[^"'\s]*[?&]code=([^&"'\s]+)/);
60
+ if (bodyCodeMatch) {
61
+ return bodyCodeMatch[1];
62
+ }
63
+
64
+ // data-url (np. z błędnej strony 500)
65
+ const dataUrlMatch = html.match(/data-url="[^"]*code=([^&"']+)/);
66
+ if (dataUrlMatch) {
67
+ return dataUrlMatch[1];
68
+ }
69
+
70
+ console.warn('Authorization code not found in HTML response');
71
+ return null;
72
+ } catch (err) {
73
+ console.error('extractCodeFromHtml error:', err);
74
+ return null;
65
75
  }
76
+ }
66
77
 
67
- // 🆕 NEW: Check for data-url with code parameter (like in 500 error pages)
68
- const dataUrlMatch = html.match(/data-url="[^"]*code=([^&"']+)/);
69
- if (dataUrlMatch) {
70
- console.log('Found authorization code in data-url attribute');
71
- resolve(dataUrlMatch[1]);
72
- return;
78
+ async getTokens(code) {
79
+ const pkce = this.generatePKCE();
80
+ const data = new URLSearchParams();
81
+ data.append('grant_type', 'authorization_code');
82
+ data.append('client_id', CLIENT_ID);
83
+ data.append('code', code);
84
+ data.append('redirect_uri', REDIRECT_URI);
85
+ data.append('code_verifier', pkce.verifier);
86
+
87
+ const res = await this.client.post(TOKEN_ENDPOINT, data.toString(), {
88
+ headers: {
89
+ 'Content-Type': 'application/x-www-form-urlencoded',
90
+ 'Authorization': 'Basic aG9tZW1vYmlsZTo='
91
+ },
92
+ });
93
+
94
+ if (res.status !== 200) {
95
+ throw new Error(`Token exchange failed: ${res.status} ${res.statusText}`);
73
96
  }
74
97
 
75
- return code;
98
+ return res.data;
76
99
  }
77
100
 
101
+
78
102
  async buildAuthorizeUrl() {
79
103
  try {
80
104
  const pkce = this.generatePKCE();
@@ -87,7 +111,6 @@ class MelCloudHomeToken {
87
111
  authUrl.searchParams.set('code_challenge', pkce.challenge);
88
112
  authUrl.searchParams.set('code_challenge_method', 'S256');
89
113
  authUrl.searchParams.set('state', state);
90
-
91
114
  const loginPage = await this.getLoginPage(authUrl.toString());
92
115
 
93
116
  return loginPage.url;