homebridge-melcloud-control 4.0.0-beta.374 → 4.0.0-beta.376

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.374",
4
+ "version": "4.0.0-beta.376",
5
5
  "description": "Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.",
6
6
  "license": "MIT",
7
7
  "author": "grzegorz914",
@@ -1,5 +1,7 @@
1
1
  import axios from 'axios';
2
2
  import crypto from 'crypto';
3
+ import { wrapper } from 'axios-cookiejar-support';
4
+ import { CookieJar } from 'tough-cookie';
3
5
  import { JSDOM } from 'jsdom';
4
6
  import EventEmitter from 'events';
5
7
 
@@ -17,7 +19,10 @@ class MelCloudHomeToken extends EventEmitter {
17
19
  this.passwd = config.passwd;
18
20
  this.logWarn = config.logWarn;
19
21
  this.logError = config.logError;
20
- this.axiosInstance = axios.create({ headers: { 'User-Agent': MOBILE_USER_AGENT } });
22
+
23
+ const jar = new CookieJar();
24
+ this.axiosInstance = wrapper(axios.create({ jar, withCredentials: true }));
25
+ this.client.defaults.headers['User-Agent'] = MOBILE_USER_AGENT;
21
26
  }
22
27
 
23
28
  generatePKCE() {
@@ -31,97 +36,50 @@ class MelCloudHomeToken extends EventEmitter {
31
36
  }
32
37
 
33
38
  async getLoginPage(authUrl) {
34
- const res = await this.axiosInstance.get(authUrl, { maxRedirects: 10 });
39
+ const res = await this.client.get(authUrl, { maxRedirects: 10 });
35
40
  return { url: res.request.res.responseUrl };
36
41
  }
37
42
 
38
- async extractCodeFromHtml(html, cookies = '') {
39
- try {
40
- // Code w URL
41
- const urlMatch = html.match(/[?&]code=([^&]+)/);
42
- if (urlMatch) {
43
- return urlMatch[1];
44
- }
45
-
46
- // Hidden form (form_post)
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
- const code = await this.submitFormPost(
53
- formActionMatch[1],
54
- formCodeMatch[1],
55
- formStateMatch[1],
56
- cookies
57
- );
58
- return code;
59
- }
60
-
61
- // melcloudhome:// w body
62
- const bodyCodeMatch = html.match(/melcloudhome:\/\/[^"'\s]*[?&]code=([^&"'\s]+)/);
63
- if (bodyCodeMatch) {
64
- return bodyCodeMatch[1];
65
- }
66
-
67
- // data-url (np. z błędnej strony 500)
68
- const dataUrlMatch = html.match(/data-url="[^"]*code=([^&"']+)/);
69
- if (dataUrlMatch) {
70
- return dataUrlMatch[1];
71
- }
72
-
73
- console.warn('Authorization code not found in HTML response');
74
- return null;
75
- } catch (err) {
76
- console.error('extractCodeFromHtml error:', err);
77
- return null;
78
- }
79
- }
80
-
81
43
  async getTokens(code, codeVerifier) {
82
44
  try {
83
45
  const tokenData = new URLSearchParams({
84
46
  grant_type: 'authorization_code',
85
- code,
47
+ code: code,
86
48
  redirect_uri: REDIRECT_URI,
87
49
  client_id: CLIENT_ID,
88
50
  code_verifier: codeVerifier,
89
51
  });
90
52
 
91
- const response = await this.axiosInstance.post(
92
- TOKEN_ENDPOINT,
93
- tokenData.toString(),
94
- {
95
- headers: {
96
- 'Content-Type': 'application/x-www-form-urlencoded',
97
- 'Authorization': 'Basic aG9tZW1vYmlsZTo=', // optional, Cognito may ignore
98
- },
99
- }
100
- );
101
-
102
- this.emit('warn', `Token response: ${JSON.stringify(response.data)}`);
103
- return response.data;
53
+ const tokenResponse = await this.client.post(TOKEN_ENDPOINT, tokenData.toString(), {
54
+ headers: {
55
+ 'Content-Type': 'application/x-www-form-urlencoded',
56
+ 'Authorization': 'Basic aG9tZW1vYmlsZTo=',
57
+ },
58
+ });
104
59
 
60
+ const tokens = tokenResponse.data;
61
+ this.emit('warn', `Token ${JSON.stringify(tokens)}`);
62
+ return tokens;
105
63
  } catch (error) {
106
- throw new Error(`Failed to obtain OAuth token: ${error.response?.data || error.message}`);
64
+ throw new Error(`Failed to obtain OAuth token: ${error}`);
107
65
  }
108
66
  }
109
67
 
110
-
111
68
  async loginToMelCloudHome(url) {
112
69
  try {
113
- // Step 1: Pobierz stronę logowania
114
- const getResp = await this.axiosInstance.get(url, {
115
- headers: { 'Accept': 'text/html' },
116
- withCredentials: true,
70
+ // Step 1: Pobierz stronę logowania, żeby uzyskać sesję + token _csrf
71
+ const getResp = await this.client.get(url, {
72
+ headers: {
73
+ 'User-Agent': MOBILE_USER_AGENT,
74
+ 'Accept': 'text/html',
75
+ }
117
76
  });
118
77
 
119
- const cookies = (getResp.headers['set-cookie'] || []).map(c => c.split(';')[0]).join('; ');
78
+ const cookies = getResp.headers['set-cookie'] || [];
120
79
  const dom = new JSDOM(getResp.data);
121
80
  const csrf = dom.window.document.querySelector('input[name="_csrf"]')?.value;
122
-
123
81
  if (!csrf) {
124
- this.emit('warn', `CSRF token not found`);
82
+ if (this.logWarn) this.emit('warn', `CSRF token not found`);
125
83
  return null;
126
84
  }
127
85
 
@@ -132,66 +90,47 @@ class MelCloudHomeToken extends EventEmitter {
132
90
  password: this.passwd,
133
91
  });
134
92
 
135
- // Step 3: Wyślij login POST
136
- const postResp = await this.axiosInstance.post(
137
- url,
138
- formData.toString(),
139
- {
140
- headers: {
141
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
142
- 'Content-Type': 'application/x-www-form-urlencoded',
143
- 'Cookie': cookies,
144
- 'Origin': 'https://live-melcloudhome.auth.eu-west-1.amazoncognito.com',
145
- 'Referer': url,
146
- },
147
- maxRedirects: 0, // kontrolujmy redirecty ręcznie
148
- validateStatus: s => [200, 302, 400].includes(s),
149
- }
150
- );
151
-
152
- if (postResp.status === 400) {
153
- this.emit('warn', `Login failed: ${postResp.data}`);
154
- return null;
155
- }
156
-
157
- let redirectUrl = postResp.headers.location;
158
- let cookieChain = cookies;
159
-
160
- // Step 4: Podążaj za przekierowaniami
161
- for (let i = 0; i < 5 && redirectUrl; i++) {
162
- const redirectResp = await this.axiosInstance.get(redirectUrl, {
163
- headers: { 'Cookie': cookieChain },
164
- maxRedirects: 0,
165
- validateStatus: s => [200, 302].includes(s),
166
- });
167
-
168
- // aktualizuj cookies
169
- const newCookies = redirectResp.headers['set-cookie'] || [];
170
- if (newCookies.length) {
171
- cookieChain += '; ' + newCookies.map(c => c.split(';')[0]).join('; ');
172
- }
173
-
174
- redirectUrl = redirectResp.headers.location;
93
+ if (this.logWarn) this.emit('warn', `Redirect URL: ${url}`);
94
+
95
+ // Step 3: Wyślij POST z danymi logowania
96
+ const response = await this.client.post(url, formData.toString(), {
97
+ headers: {
98
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
99
+ 'Accept-Language': 'en-US,en;q=0.9',
100
+ 'Content-Type': 'application/x-www-form-urlencoded',
101
+ 'Content-Length': formData.toString().length,
102
+ 'Cookie': cookies,
103
+ 'Origin': 'https://live-melcloudhome.auth.eu-west-1.amazoncognito.com',
104
+ 'Referer': url,
105
+ },
106
+ maxRedirects: 0,
107
+ validateStatus: status => [200, 302, 400].includes(status),
108
+ });
175
109
 
176
- if (redirectUrl && redirectUrl.includes('code=')) {
177
- const match = redirectUrl.match(/[?&]code=([^&]+)/);
178
- if (match) {
179
- const code = match[1];
180
- this.emit('warn', `Authorization code obtained: ${code}`);
181
- return code;
182
- }
110
+ if (response.status === 400) if (this.logWarn) this.emit('warn', `Login failed (bad request or invalid credentials: ${response.data}`);
111
+ if (response.status === 302 && response.headers.location) {
112
+ // Login success — mamy redirect z "code="
113
+ const redirectUrl = response.headers.location;
114
+ if (this.logWarn) this.emit('warn', `Redirect URL: ${redirectUrl}`);
115
+ if (this.logWarn) this.emit('warn', `Response headers: ${response.headers}`);
116
+
117
+ const match = redirectUrl.match(/[?&]code=([^&]+)/);
118
+ if (match) {
119
+ const code = match[1];
120
+ if (this.logWarn) this.emit('warn', `Redirect URL found code: ${code}`);
121
+ return code;
122
+ } else {
123
+ if (this.logWarn) this.emit('warn', `Redirect URL found, but no "code=" param: ${redirectUrl}`);
124
+ return null;
183
125
  }
184
126
  }
185
127
 
186
- this.emit('warn', 'Login flow finished, but no code found');
187
128
  return null;
188
-
189
129
  } catch (error) {
190
- throw new Error(`Login to MELCloud Home error: ${error.message}`);
130
+ throw new Error(`Login to MELCloud Home error: ${error}`);
191
131
  }
192
132
  }
193
133
 
194
-
195
134
  async buildAuthorizeUrl() {
196
135
  try {
197
136
  const pkce = this.generatePKCE();