homebridge-melcloud-control 4.0.0-beta.241 → 4.0.0-beta.243

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.241",
4
+ "version": "4.0.0-beta.243",
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
@@ -178,7 +178,7 @@ class MelCloud extends EventEmitter {
178
178
  async connectToMelCloudHome() {
179
179
  const oauth = new MelCloudHomeToken(this.log);
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';
@@ -11,7 +12,7 @@ const SCOPE = 'openid profile email offline_access IdentityServerApi';
11
12
  const TOKEN_ENDPOINT = 'https://auth.melcloudhome.com/connect/token';
12
13
  const AUTHORIZE_ENDPOINT = 'https://auth.melcloudhome.com/connect/authorize';
13
14
 
14
- export class MelCloudHomeToken {
15
+ class MelCloudHomeToken {
15
16
  constructor(log) {
16
17
  this.jar = new CookieJar();
17
18
  this.axiosInstance = wrapper(axios.create({
@@ -53,71 +54,61 @@ export class MelCloudHomeToken {
53
54
  return { html, loginUrl: resp.request.res.responseUrl, csrf: csrfMatch[1], cookies };
54
55
  }
55
56
 
56
- async submitLogin(loginUrl, csrf, email, password, cookies) {
57
+ async submitLogin(loginUrl, email, password, cookies) {
57
58
  console.log('Submitting login credentials...');
58
59
 
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
- },
60
+ // 1. Pobieramy stronę logowania
61
+ const resp = await this.axiosInstance.get(loginUrl, {
62
+ headers: { Cookie: cookies },
68
63
  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
- });
64
+ validateStatus: s => s >= 200 && s < 400
65
+ }).catch(err => err.response || err);
74
66
 
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
- }
67
+ const html = resp.data;
80
68
 
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="([^"]+)"/);
69
+ // 2. Parsujemy formę logowania i zbieramy hidden inputs
70
+ const dom = new JSDOM(html);
71
+ const form = dom.window.document.querySelector('form');
72
+ if (!form) throw new Error('Nie znaleziono formy logowania');
84
73
 
85
- if (formCodeMatch && formStateMatch && formActionMatch) {
86
- return this.submitFormPost(formActionMatch[1], formCodeMatch[1], formStateMatch[1]);
74
+ const formData = new URLSearchParams();
75
+ for (const input of form.querySelectorAll('input')) {
76
+ const name = input.getAttribute('name');
77
+ const value = input.getAttribute('value') || '';
78
+ formData.append(name, value);
87
79
  }
88
80
 
89
- const bodyCodeMatch = resp.data.match(/melcloudhome:\/\/[^"'\s]*[?&]code=([^&"'\s]+)/);
90
- if (bodyCodeMatch) return bodyCodeMatch[1];
81
+ // 3. Nadpisujemy username i password
82
+ formData.set('username', email);
83
+ formData.set('password', password);
91
84
 
92
- throw new Error('No authorization code found after login');
93
- }
85
+ const actionUrl = new URL(form.getAttribute('action'), loginUrl).toString();
94
86
 
95
- async submitFormPost(actionUrl, code, state) {
96
- const formData = new URLSearchParams({ code, state });
87
+ // 4. Wysyłamy POST do form action
88
+ return this.submitFormPost(actionUrl, formData);
89
+ }
97
90
 
91
+ async submitFormPost(actionUrl, formData) {
98
92
  const resp = await this.axiosInstance.post(actionUrl, formData.toString(), {
99
93
  headers: {
100
94
  'Content-Type': 'application/x-www-form-urlencoded',
101
- 'Origin': 'https://live-melcloudhome.auth.eu-west-1.amazoncognito.com',
102
- 'Referer': actionUrl
103
95
  },
104
96
  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
- });
97
+ validateStatus: s => s >= 200 && s < 400
98
+ }).catch(err => err.response || err);
110
99
 
111
- const locationHeader = resp.headers.location;
100
+ // 5. Szukamy authorization code w redirect
101
+ const locationHeader = resp.headers?.location;
112
102
  if (locationHeader && locationHeader.startsWith('melcloudhome://')) {
113
103
  const codeMatch = locationHeader.match(/[?&]code=([^&]+)/);
114
104
  if (codeMatch) return codeMatch[1];
115
105
  }
116
106
 
117
- const bodyCodeMatch = resp.data.match(/melcloudhome:\/\/[^"'\s]*[?&]code=([^&"'\s]+)/);
107
+ // 6. Albo w body (czasami JS redirect)
108
+ const bodyCodeMatch = resp.data?.match(/melcloudhome:\/\/[^"'\s]*[?&]code=([^&"'\s]+)/);
118
109
  if (bodyCodeMatch) return bodyCodeMatch[1];
119
110
 
120
- throw new Error('No authorization code found in form_post callback');
111
+ throw new Error('No authorization code found after login');
121
112
  }
122
113
 
123
114
  async exchangeCodeForTokens(code, codeVerifier) {