homebridge-melcloud-control 4.0.0-beta.269 → 4.0.0-beta.270

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.269",
4
+ "version": "4.0.0-beta.270",
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 fetch from 'node-fetch';
11
11
  import { URLSearchParams } from 'url';
12
+ const TOKEN_ENDPOINT = 'https://auth.melcloudhome.com/connect/token';
12
13
 
13
14
  class MelCloud extends EventEmitter {
14
15
  constructor(accountType, user, passwd, language, accountFile, buildingsFile, devicesFile, logWarn, logDebug, requestConfig) {
@@ -288,10 +289,12 @@ class MelCloud extends EventEmitter {
288
289
  args: ['--no-sandbox', '--disable-setuid-sandbox']
289
290
  });
290
291
 
292
+ const melCloudHomeToken = new MelCloudHomeToken();
293
+ const url = await melCloudHomeToken.getUrl();
294
+
291
295
  const page = await browser.newPage();
292
- await page.goto(ApiUrlsHome.BaseURL, { waitUntil: 'networkidle2' });
296
+ await page.goto(url, { waitUntil: 'networkidle2' });
293
297
 
294
- // 2. Kliknij login (jeżeli strona główna ma przycisk)
295
298
  const buttons = await page.$$('button.btn--blue');
296
299
  let loginBtn = null;
297
300
 
@@ -310,7 +313,6 @@ class MelCloud extends EventEmitter {
310
313
  page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 10000 })
311
314
  ]);
312
315
 
313
- // 3. Wypełnij login i hasło
314
316
  await page.waitForSelector('input[name="username"]', { timeout: 5000 });
315
317
  await page.type('input[name="username"]', this.user, { delay: 50 });
316
318
  await page.type('input[name="password"]', this.passwd, { delay: 50 });
@@ -329,7 +331,7 @@ class MelCloud extends EventEmitter {
329
331
  if (!authCode) throw new Error('Authorization code not found in URL');
330
332
 
331
333
  // 5. Wymień code na access_token
332
- const tokenResponse = await axios.post('https://app.melcloud.com/Mitsubishi.Wifi.Client/OAuth/Token', new URLSearchParams({
334
+ const tokenResponse = await axios.post(TOKEN_ENDPOINT, new URLSearchParams({
333
335
  client_id: clientId,
334
336
  grant_type: 'authorization_code',
335
337
  code: authCode,
@@ -39,107 +39,7 @@ class MelCloudHomeToken {
39
39
  return { csrf, url: res.request.res.responseUrl };
40
40
  }
41
41
 
42
- async submitLogin(loginUrl, csrf, email, password) {
43
- console.log('Submitting login credentials to:', loginUrl);
44
-
45
- const form = new URLSearchParams({ _csrf: csrf, username: email, password });
46
- let res = await this.client.post(loginUrl, form.toString(), {
47
- headers: {
48
- 'Content-Type': 'application/x-www-form-urlencoded',
49
- Referer: loginUrl,
50
- },
51
- maxRedirects: 0,
52
- validateStatus: (status) => status >= 200 && status < 400,
53
- });
54
-
55
- console.log('Login POST status:', res.status);
56
-
57
- // Obsługa 302 redirect
58
- while (res.status === 302 && res.headers.location) {
59
- const location = res.headers.location;
60
- console.log('Following redirect:', location);
61
- res = await this.client.get(location, {
62
- maxRedirects: 0,
63
- validateStatus: (status) => status >= 200 && status < 400,
64
- });
65
- }
66
-
67
- // Sprawdź HTML na form_post
68
- let $ = cheerio.load(res.data);
69
- let formElement = $('form');
70
- if (formElement.length) {
71
- const action = formElement.attr('action');
72
- const code = $('input[name="code"]').attr('value');
73
- const state = $('input[name="state"]').attr('value');
74
- if (action && code && state) {
75
- console.log('Found form_post. Action:', action, 'Code:', code, 'State:', state);
76
- return this.submitFormPost(action, code, state);
77
- }
78
- }
79
-
80
- // Spróbuj pobrać kod bezpośrednio z JS w body
81
- const bodyCodeMatch = res.data.match(/melcloudhome:\/\/[^"'\s]*[?&]code=([^&"'\s]+)/);
82
- if (bodyCodeMatch) {
83
- console.log('Authorization code obtained from HTML/JS body:', bodyCodeMatch[1]);
84
- return bodyCodeMatch[1];
85
- }
86
-
87
- throw new Error('Authorization code not found in login response');
88
- }
89
-
90
- async submitFormPost(actionUrl, code, state) {
91
- console.log('Submitting form_post to callback URL:', actionUrl);
92
-
93
- const form = new URLSearchParams({ code, state });
94
- const res = await this.client.post(actionUrl, form.toString(), {
95
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
96
- maxRedirects: 0,
97
- validateStatus: (status) => status >= 200 && status < 400,
98
- });
99
-
100
- // Sprawdź redirect z finalnym code
101
- const location = res.headers.location;
102
- if (location?.startsWith('melcloudhome://')) {
103
- const codeMatch = location.match(/[?&]code=([^&]+)/);
104
- if (codeMatch) {
105
- console.log('Authorization code obtained from form_post redirect:', codeMatch[1]);
106
- return codeMatch[1];
107
- }
108
- }
109
-
110
- throw new Error('No authorization code found in form_post response');
111
- }
112
-
113
-
114
- async exchangeCodeForTokens(code, codeVerifier) {
115
- console.log('Exchanging authorization code for tokens...');
116
- const form = new URLSearchParams({
117
- grant_type: 'authorization_code',
118
- code,
119
- redirect_uri: REDIRECT_URI,
120
- client_id: CLIENT_ID,
121
- code_verifier: codeVerifier,
122
- });
123
-
124
- const res = await this.client.post(TOKEN_ENDPOINT, form.toString(), {
125
- headers: {
126
- 'Content-Type': 'application/x-www-form-urlencoded',
127
- Authorization: 'Basic aG9tZW1vYmlsZTo=',
128
- },
129
- });
130
-
131
- if (res.data.error) throw new Error(`Token exchange failed: ${res.data.error_description || res.data.error}`);
132
-
133
- console.log('Tokens obtained:', res.data);
134
- return {
135
- accessToken: res.data.access_token,
136
- refreshToken: res.data.refresh_token,
137
- expiresIn: res.data.expires_in,
138
- idToken: res.data.id_token,
139
- };
140
- }
141
-
142
- async getTokensWithCredentials(email, password) {
42
+ async getUrl() {
143
43
  try {
144
44
  console.log('Starting OAuth flow...');
145
45
  const pkce = this.generatePKCE();
@@ -155,11 +55,8 @@ class MelCloudHomeToken {
155
55
  authUrl.searchParams.set('state', state);
156
56
 
157
57
  const loginPage = await this.getLoginPage(authUrl.toString());
158
- const authCode = await this.submitLogin(loginPage.url, loginPage.csrf, email, password);
159
- const tokens = await this.exchangeCodeForTokens(authCode, pkce.verifier);
160
58
 
161
- console.log('✓ Successfully obtained OAuth tokens');
162
- return tokens;
59
+ return loginPage;
163
60
  } catch (error) {
164
61
  console.error('Failed to obtain OAuth tokens:', error);
165
62
  throw error;