homebridge-melcloud-control 4.0.0-beta.242 → 4.0.0-beta.244

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.242",
4
+ "version": "4.0.0-beta.244",
5
5
  "description": "Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.",
6
6
  "license": "MIT",
7
7
  "author": "grzegorz914",
@@ -41,7 +41,8 @@
41
41
  "express": "^5.1.0",
42
42
  "puppeteer": "^24.26.1",
43
43
  "tough-cookie": "^6.0.0",
44
- "axios-cookiejar-support": "^6.0.4"
44
+ "axios-cookiejar-support": "^6.0.4",
45
+ "jsdom": "^27.0.1"
45
46
  },
46
47
  "keywords": [
47
48
  "homebridge",
package/src/melcloud.js CHANGED
@@ -176,9 +176,9 @@ class MelCloud extends EventEmitter {
176
176
 
177
177
  // MELCloud Home
178
178
  async connectToMelCloudHome() {
179
- const oauth = new MelCloudHomeToken(this.log);
179
+ const oauth = new MelCloudHomeToken();
180
180
  this.tokens = await oauth.getTokensWithCredentials(this.user, this.passwd);
181
- this.emit('warn', `Token${JSON.stringify(this.tokens, null, 2)}`);
181
+ this.emit('warn', `Token ${JSON.stringify(this.tokens, null, 2)}`);
182
182
  //this.axiosInstance.defaults.headers['Authorization'] = `Bearer ${this.tokens.accessToken}`;
183
183
  return false;
184
184
  }
@@ -3,6 +3,7 @@ import axios from 'axios';
3
3
  import { CookieJar } from 'tough-cookie';
4
4
  import { wrapper } from 'axios-cookiejar-support';
5
5
  import crypto from 'crypto';
6
+ import { JSDOM } from 'jsdom';
6
7
 
7
8
  const MOBILE_USER_AGENT = 'MonitorAndControl.App.Mobile/35 CFNetwork/3860.100.1 Darwin/25.0.0';
8
9
  const CLIENT_ID = 'homemobile';
@@ -36,6 +37,7 @@ class MelCloudHomeToken {
36
37
  console.log('Getting login page...');
37
38
  const resp = await this.axiosInstance.get(authUrl, {
38
39
  headers: {
40
+ 'User-Agent': MOBILE_USER_AGENT,
39
41
  'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
40
42
  'Accept-Language': 'en-US,en;q=0.9',
41
43
  },
@@ -53,71 +55,61 @@ class MelCloudHomeToken {
53
55
  return { html, loginUrl: resp.request.res.responseUrl, csrf: csrfMatch[1], cookies };
54
56
  }
55
57
 
56
- async submitLogin(loginUrl, csrf, email, password, cookies) {
58
+ async submitLogin(loginUrl, email, password, cookies) {
57
59
  console.log('Submitting login credentials...');
58
60
 
59
- const formData = new URLSearchParams({ _csrf: csrf, username: email, password: password });
60
-
61
- const resp = await this.axiosInstance.post(loginUrl, formData.toString(), {
62
- headers: {
63
- 'Content-Type': 'application/x-www-form-urlencoded',
64
- 'Cookie': cookies,
65
- 'Origin': 'https://live-melcloudhome.auth.eu-west-1.amazoncognito.com',
66
- 'Referer': loginUrl
67
- },
61
+ // 1. Pobieramy stronę logowania
62
+ const resp = await this.axiosInstance.get(loginUrl, {
63
+ headers: { Cookie: cookies },
68
64
  maxRedirects: 0,
69
- validateStatus: status => status >= 200 && status < 400
70
- }).catch(err => {
71
- if (err.response && err.response.status >= 300 && err.response.status < 400) return err.response;
72
- throw err;
73
- });
65
+ validateStatus: s => s >= 200 && s < 400
66
+ }).catch(err => err.response || err);
74
67
 
75
- const locationHeader = resp.headers.location;
76
- if (locationHeader && locationHeader.startsWith('melcloudhome://')) {
77
- const codeMatch = locationHeader.match(/[?&]code=([^&]+)/);
78
- if (codeMatch) return codeMatch[1];
79
- }
68
+ const html = resp.data;
80
69
 
81
- const formCodeMatch = resp.data.match(/name="code"\s+value="([^"]+)"/);
82
- const formStateMatch = resp.data.match(/name="state"\s+value="([^"]+)"/);
83
- const formActionMatch = resp.data.match(/action="([^"]+)"/);
70
+ // 2. Parsujemy formę logowania i zbieramy hidden inputs
71
+ const dom = new JSDOM(html);
72
+ const form = dom.window.document.querySelector('form');
73
+ if (!form) throw new Error('Nie znaleziono formy logowania');
84
74
 
85
- if (formCodeMatch && formStateMatch && formActionMatch) {
86
- return this.submitFormPost(formActionMatch[1], formCodeMatch[1], formStateMatch[1]);
75
+ const formData = new URLSearchParams();
76
+ for (const input of form.querySelectorAll('input')) {
77
+ const name = input.getAttribute('name');
78
+ const value = input.getAttribute('value') || '';
79
+ formData.append(name, value);
87
80
  }
88
81
 
89
- const bodyCodeMatch = resp.data.match(/melcloudhome:\/\/[^"'\s]*[?&]code=([^&"'\s]+)/);
90
- if (bodyCodeMatch) return bodyCodeMatch[1];
82
+ // 3. Nadpisujemy username i password
83
+ formData.set('username', email);
84
+ formData.set('password', password);
91
85
 
92
- throw new Error('No authorization code found after login');
93
- }
86
+ const actionUrl = new URL(form.getAttribute('action'), loginUrl).toString();
94
87
 
95
- async submitFormPost(actionUrl, code, state) {
96
- const formData = new URLSearchParams({ code, state });
88
+ // 4. Wysyłamy POST do form action
89
+ return this.submitFormPost(actionUrl, formData);
90
+ }
97
91
 
92
+ async submitFormPost(actionUrl, formData) {
98
93
  const resp = await this.axiosInstance.post(actionUrl, formData.toString(), {
99
94
  headers: {
100
95
  'Content-Type': 'application/x-www-form-urlencoded',
101
- 'Origin': 'https://live-melcloudhome.auth.eu-west-1.amazoncognito.com',
102
- 'Referer': actionUrl
103
96
  },
104
97
  maxRedirects: 0,
105
- validateStatus: status => status >= 200 && status < 400
106
- }).catch(err => {
107
- if (err.response && err.response.status >= 300 && err.response.status < 400) return err.response;
108
- throw err;
109
- });
98
+ validateStatus: s => s >= 200 && s < 400
99
+ }).catch(err => err.response || err);
110
100
 
111
- const locationHeader = resp.headers.location;
101
+ // 5. Szukamy authorization code w redirect
102
+ const locationHeader = resp.headers?.location;
112
103
  if (locationHeader && locationHeader.startsWith('melcloudhome://')) {
113
104
  const codeMatch = locationHeader.match(/[?&]code=([^&]+)/);
114
105
  if (codeMatch) return codeMatch[1];
115
106
  }
116
107
 
117
- const bodyCodeMatch = resp.data.match(/melcloudhome:\/\/[^"'\s]*[?&]code=([^&"'\s]+)/);
108
+ // 6. Albo w body (czasami JS redirect)
109
+ const bodyCodeMatch = resp.data?.match(/melcloudhome:\/\/[^"'\s]*[?&]code=([^&"'\s]+)/);
118
110
  if (bodyCodeMatch) return bodyCodeMatch[1];
119
111
 
120
- throw new Error('No authorization code found in form_post callback');
112
+ throw new Error('No authorization code found after login');
121
113
  }
122
114
 
123
115
  async exchangeCodeForTokens(code, codeVerifier) {