homebridge-melcloud-control 4.0.0-beta.317 → 4.0.0-beta.319

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.317",
4
+ "version": "4.0.0-beta.319",
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
@@ -297,6 +297,7 @@ class MelCloud extends EventEmitter {
297
297
 
298
298
  const data = await melCloudHomeToken.buildAuthorizeUrl();
299
299
  const code = await melCloudHomeToken.loginToMelCloudHome(data.url);
300
+ const token = await melCloudHomeToken.getTokens(code);
300
301
 
301
302
  return false
302
303
 
@@ -83,58 +83,27 @@ class MelCloudHomeToken extends EventEmitter {
83
83
  }
84
84
  }
85
85
 
86
- async getTokens(code, codeVerifier) {
86
+ async getTokens(code) {
87
87
  try {
88
- const data = new URLSearchParams({
88
+ const tokenResponse = await axios.post('https://auth.melcloudhome.com/oauth2/token', new URLSearchParams({
89
89
  grant_type: 'authorization_code',
90
+ client_id: this.clientId,
90
91
  code: code,
91
- redirect_uri: REDIRECT_URI,
92
- client_id: CLIENT_ID,
93
- code_verifier: codeVerifier,
94
- });
95
-
96
- const res = await axios({
97
- method: 'POST',
98
- url: TOKEN_ENDPOINT,
92
+ redirect_uri: this.redirectUri
93
+ }).toString(), {
99
94
  headers: {
100
- 'Content-Type': 'application/x-www-form-urlencoded',
101
- 'Content-Length': data.toString().length
102
- },
103
- data: data
95
+ 'Content-Type': 'application/x-www-form-urlencoded'
96
+ }
104
97
  });
105
- console.log(res);
106
- if (res.status !== 200) {
107
- throw new Error(`Token exchange failed: ${res.status} ${res.statusText}`);
108
- }
109
98
 
110
- return res.data;
99
+ this.emit('warn', `Token ${tokenResponse.data}`);
100
+ return tokenResponse.data;
111
101
  } catch (error) {
112
102
  console.error('Failed to obtain OAuth tokens:', error);
113
103
  throw error;
114
104
  }
115
105
  }
116
106
 
117
- async buildAuthorizeUrl() {
118
- try {
119
- const pkce = this.generatePKCE();
120
- const state = this.generateState();
121
- const authUrl = new URL(AUTHORIZE_ENDPOINT);
122
- authUrl.searchParams.set('client_id', CLIENT_ID);
123
- authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
124
- authUrl.searchParams.set('response_type', 'code');
125
- authUrl.searchParams.set('scope', SCOPE);
126
- authUrl.searchParams.set('code_challenge', pkce.challenge);
127
- authUrl.searchParams.set('code_challenge_method', 'S256');
128
- authUrl.searchParams.set('state', state);
129
-
130
- const loginPage = await this.getLoginPage(authUrl.toString());
131
- const data = { codeVerifier: pkce.verifier, url: loginPage.url }
132
- return data;
133
- } catch (error) {
134
- console.error('Failed to obtain OAuth link:', error);
135
- throw error;
136
- }
137
- }
138
107
 
139
108
  async loginToMelCloudHome(url) {
140
109
  try {
@@ -149,10 +118,29 @@ class MelCloudHomeToken extends EventEmitter {
149
118
 
150
119
  const cookies = getResp.headers['set-cookie'] || [];
151
120
  const dom = new JSDOM(getResp.data);
152
- const csrf = dom.window.document.querySelector('input[name="_csrf"]')?.value;
121
+ const formAction = dom.window.document.querySelector('form[action]')?.getAttribute('action');
122
+ if (!formAction) {
123
+ if (this.logWarn) this.emit('warn', `Login form not found in HTML`);
124
+ return null;
125
+ }
126
+
127
+ const formUrl = new URL(formAction, 'https://auth.melcloudhome.com'); // absolutny URL
128
+ const clientId = formUrl.searchParams.get('client_id');
129
+ const redirectUri = formUrl.searchParams.get('redirect_uri');
130
+
131
+ if (!clientId || !redirectUri) {
132
+ if (this.logWarn) this.emit('warn', `client_id or redirect_uri missing in login form`);
133
+ return null;
134
+ }
135
+
136
+ // Zapisz do this.* żeby użyć później w exchangeCodeForToken()
137
+ this.clientId = clientId;
138
+ this.redirectUri = redirectUri;
153
139
 
140
+ const csrf = dom.window.document.querySelector('input[name="_csrf"]')?.value;
154
141
  if (!csrf) {
155
- throw new Error('CSRF token not found');
142
+ if (this.logWarn) this.emit('warn', `CSRF token not found`);
143
+ return null;
156
144
  }
157
145
 
158
146
  // Step 2: Przygotuj dane logowania
@@ -171,43 +159,53 @@ class MelCloudHomeToken extends EventEmitter {
171
159
  'Referer': url,
172
160
  'User-Agent': 'Mozilla/5.0',
173
161
  },
174
- maxRedirects: 0, // ważne, bo Cognito robi redirect po sukcesie
162
+ maxRedirects: 0,
175
163
  validateStatus: status => [200, 302, 400].includes(status),
176
164
  });
177
165
 
178
- this.emit('warn', `No cookie returned from login. Response headers: ${response}`);
179
- if (response.status === 302 && postResp.headers.location) {
166
+ if (response.status === 302 && response.headers.location) {
180
167
  // Login success — mamy redirect z "code="
181
168
  const redirectUrl = response.headers.location;
182
- console.log('Login success, redirect URL:', redirectUrl);
183
-
184
169
  const match = redirectUrl.match(/[?&]code=([^&]+)/);
185
170
  if (match) {
186
171
  const code = match[1];
187
- console.log('Authorization code:', code);
188
172
  return code;
189
173
  } else {
190
174
  console.warn('Redirect URL found, but no "code=" param.');
175
+ if (this.logWarn) this.emit('warn', `Redirect URL found, but no "code=" param: ${redirectUrl}`);
191
176
  return null;
192
177
  }
193
178
  }
194
179
 
195
- if (response.status !== 200) {
196
- if (this.logError) this.emit('error', `Login failed with status code: ${response.status}`);
197
- return null;
198
- }
180
+ if (response.status === 400) if (this.logWarn) this.emit('warn', `Login failed (bad request or invalid credentials: ${response.data}`);
199
181
 
200
- const cookie = response.headers['set-cookie'];
201
- if (!cookie) {
202
- if (this.logWarn) this.emit('warn', `No cookie returned from login. Response headers: ${JSON.stringify(response.headers)}`);
203
- return null;
204
- }
205
-
206
- return cookie;
182
+ return null;
207
183
  } catch (error) {
208
184
  throw new Error(`Login to MELCloud Home error: ${error}`);
209
185
  }
210
186
  }
187
+
188
+ async buildAuthorizeUrl() {
189
+ try {
190
+ const pkce = this.generatePKCE();
191
+ const state = this.generateState();
192
+ const authUrl = new URL(AUTHORIZE_ENDPOINT);
193
+ authUrl.searchParams.set('client_id', CLIENT_ID);
194
+ authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
195
+ authUrl.searchParams.set('response_type', 'code');
196
+ authUrl.searchParams.set('scope', SCOPE);
197
+ authUrl.searchParams.set('code_challenge', pkce.challenge);
198
+ authUrl.searchParams.set('code_challenge_method', 'S256');
199
+ authUrl.searchParams.set('state', state);
200
+
201
+ const loginPage = await this.getLoginPage(authUrl.toString());
202
+ const data = { codeVerifier: pkce.verifier, url: loginPage.url }
203
+ return data;
204
+ } catch (error) {
205
+ console.error('Failed to obtain OAuth link:', error);
206
+ throw error;
207
+ }
208
+ }
211
209
  }
212
210
 
213
211
  export default MelCloudHomeToken;