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

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.372",
4
+ "version": "4.0.0-beta.374",
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
@@ -28,10 +28,6 @@ class MelCloud extends EventEmitter {
28
28
  this.contextKey = '';
29
29
  this.functions = new Functions();
30
30
 
31
- this.axiosInstance = axios.create({
32
- baseURL: 'https://app.melcloud.com/Mitsubishi.Wifi.Client',
33
- headers: { 'User-Agent': 'MonitorAndControl.App.Mobile/35 CFNetwork/3860.100.1 Darwin/25.0.0' }
34
- });
35
31
  this.tokens = null;
36
32
 
37
33
  this.loginData = {
@@ -31,11 +31,7 @@ class MelCloudHomeToken extends EventEmitter {
31
31
  }
32
32
 
33
33
  async getLoginPage(authUrl) {
34
- const res = await this.axiosInstance({
35
- method: 'GET',
36
- baseURL: authUrl,
37
- maxRedirects: 10
38
- });
34
+ const res = await this.axiosInstance.get(authUrl, { maxRedirects: 10 });
39
35
  return { url: res.request.res.responseUrl };
40
36
  }
41
37
 
@@ -86,47 +82,46 @@ class MelCloudHomeToken extends EventEmitter {
86
82
  try {
87
83
  const tokenData = new URLSearchParams({
88
84
  grant_type: 'authorization_code',
89
- code: code,
85
+ code,
90
86
  redirect_uri: REDIRECT_URI,
91
87
  client_id: CLIENT_ID,
92
88
  code_verifier: codeVerifier,
93
89
  });
94
90
 
95
- const tokenResponse = await this.axiosInstance(tokenData.toString(), {
96
- method: 'POST',
97
- baseURL: TOKEN_ENDPOINT,
98
- headers: {
99
- 'Content-Type': 'application/x-www-form-urlencoded',
100
- 'Content-Length': tokenData.toString().length,
101
- 'Authorization': 'Basic aG9tZW1vYmlsZTo=',
102
- },
103
- });
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;
104
104
 
105
- const tokens = tokenResponse.data;
106
- this.emit('warn', `Token ${JSON.stringify(tokens)}`);
107
- return tokens;
108
105
  } catch (error) {
109
- throw new Error(`Failed to obtain OAuth token: ${error}`);
106
+ throw new Error(`Failed to obtain OAuth token: ${error.response?.data || error.message}`);
110
107
  }
111
108
  }
112
109
 
110
+
113
111
  async loginToMelCloudHome(url) {
114
112
  try {
115
- // Step 1: Pobierz stronę logowania, żeby uzyskać sesję + token _csrf
116
- const getResp = await this.axiosInstance({
117
- method: 'GET',
118
- baseURL: url,
119
- headers: {
120
- 'Accept': 'text/html',
121
- },
113
+ // Step 1: Pobierz stronę logowania
114
+ const getResp = await this.axiosInstance.get(url, {
115
+ headers: { 'Accept': 'text/html' },
122
116
  withCredentials: true,
123
117
  });
124
118
 
125
- const cookies = getResp.headers['set-cookie'] || [];
119
+ const cookies = (getResp.headers['set-cookie'] || []).map(c => c.split(';')[0]).join('; ');
126
120
  const dom = new JSDOM(getResp.data);
127
121
  const csrf = dom.window.document.querySelector('input[name="_csrf"]')?.value;
122
+
128
123
  if (!csrf) {
129
- if (this.logWarn) this.emit('warn', `CSRF token not found`);
124
+ this.emit('warn', `CSRF token not found`);
130
125
  return null;
131
126
  }
132
127
 
@@ -137,49 +132,66 @@ class MelCloudHomeToken extends EventEmitter {
137
132
  password: this.passwd,
138
133
  });
139
134
 
140
- if (this.logWarn) this.emit('warn', `Redirect URL: ${url}`);
141
-
142
- // Step 3: Wyślij POST z danymi logowania
143
- const response = await this.axiosInstance(formData.toString(), {
144
- method: 'POST',
145
- baseURL: url,
146
- headers: {
147
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
148
- 'Accept-Language': 'en-US,en;q=0.9',
149
- 'Content-Type': 'application/x-www-form-urlencoded',
150
- 'Content-Length': formData.toString().length,
151
- 'Cookie': cookies,
152
- 'Origin': 'https://live-melcloudhome.auth.eu-west-1.amazoncognito.com',
153
- 'Referer': url,
154
- },
155
- maxRedirects: 0,
156
- validateStatus: status => [200, 302, 400].includes(status),
157
- });
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
+ );
158
151
 
159
- if (response.status === 400) if (this.logWarn) this.emit('warn', `Login failed (bad request or invalid credentials: ${response.data}`);
160
- if (response.status === 302 && response.headers.location) {
161
- // Login success — mamy redirect z "code="
162
- const redirectUrl = response.headers.location;
163
- if (this.logWarn) this.emit('warn', `Redirect URL: ${redirectUrl}`);
164
- if (this.logWarn) this.emit('warn', `Response headers: ${response.headers}`);
165
-
166
- const match = redirectUrl.match(/[?&]code=([^&]+)/);
167
- if (match) {
168
- const code = match[1];
169
- if (this.logWarn) this.emit('warn', `Redirect URL found code: ${code}`);
170
- return code;
171
- } else {
172
- if (this.logWarn) this.emit('warn', `Redirect URL found, but no "code=" param: ${redirectUrl}`);
173
- return null;
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;
175
+
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
+ }
174
183
  }
175
184
  }
176
185
 
186
+ this.emit('warn', 'Login flow finished, but no code found');
177
187
  return null;
188
+
178
189
  } catch (error) {
179
- throw new Error(`Login to MELCloud Home error: ${error}`);
190
+ throw new Error(`Login to MELCloud Home error: ${error.message}`);
180
191
  }
181
192
  }
182
193
 
194
+
183
195
  async buildAuthorizeUrl() {
184
196
  try {
185
197
  const pkce = this.generatePKCE();