homebridge-melcloud-control 4.0.0-beta.245 → 4.0.0-beta.247

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.245",
4
+ "version": "4.0.0-beta.247",
5
5
  "description": "Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.",
6
6
  "license": "MIT",
7
7
  "author": "grzegorz914",
@@ -40,8 +40,6 @@
40
40
  "axios": "^1.12.2",
41
41
  "express": "^5.1.0",
42
42
  "puppeteer": "^24.26.1",
43
- "tough-cookie": "^6.0.0",
44
- "axios-cookiejar-support": "^6.0.4",
45
43
  "jsdom": "^27.0.1"
46
44
  },
47
45
  "keywords": [
@@ -13,13 +13,7 @@ const AUTHORIZE_ENDPOINT = 'https://auth.melcloudhome.com/connect/authorize';
13
13
 
14
14
  class MelCloudHomeToken {
15
15
  constructor(log) {
16
- this.jar = new CookieJar();
17
- this.axiosInstance = wrapper(axios.create({
18
- jar: this.jar,
19
- withCredentials: true,
20
- headers: { 'User-Agent': MOBILE_USER_AGENT },
21
- maxRedirects: 10
22
- }));
16
+ this.log = log;
23
17
  }
24
18
 
25
19
  // --- PKCE ---
@@ -49,7 +43,7 @@ class MelCloudHomeToken {
49
43
  if (cookies.length > 0) reqOptions.headers['Cookie'] = cookies.join('; ');
50
44
 
51
45
  const req = https.request(reqOptions, (res) => {
52
- // collect Set-Cookie
46
+ // Collect Set-Cookie
53
47
  const setCookie = res.headers['set-cookie'];
54
48
  if (setCookie) {
55
49
  setCookie.forEach(c => {
@@ -60,7 +54,7 @@ class MelCloudHomeToken {
60
54
  });
61
55
  }
62
56
 
63
- // follow redirect
57
+ // Follow redirects
64
58
  if ([301, 302, 303].includes(res.statusCode) && res.headers.location) {
65
59
  const redirectUrl = res.headers.location.startsWith('http')
66
60
  ? res.headers.location
@@ -81,7 +75,7 @@ class MelCloudHomeToken {
81
75
 
82
76
  // --- Get login page ---
83
77
  async getLoginPage(authUrl) {
84
- console.log('Getting login page...');
78
+ this.log.info('Getting login page...');
85
79
  const resp = await this.httpsRequest(authUrl, {
86
80
  method: 'GET',
87
81
  headers: {
@@ -94,14 +88,14 @@ class MelCloudHomeToken {
94
88
  const form = dom.window.document.querySelector('form');
95
89
  if (!form) throw new Error('Login form not found');
96
90
 
97
- return { formHtml: resp.html, cookies: resp.cookies, form };
91
+ return { form, cookies: resp.cookies, loginPageUrl: resp.finalUrl };
98
92
  }
99
93
 
100
- // --- Submit login ---
94
+ // --- Submit login and handle form_post ---
101
95
  async submitLogin(form, email, password, cookies, loginPageUrl) {
102
- console.log('Submitting login credentials...');
96
+ this.log.info('Submitting login credentials...');
103
97
 
104
- // Collect all hidden inputs
98
+ // Step 1: Collect all hidden inputs
105
99
  const formData = new URLSearchParams();
106
100
  form.querySelectorAll('input').forEach(input => {
107
101
  const name = input.getAttribute('name');
@@ -118,11 +112,35 @@ class MelCloudHomeToken {
118
112
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
119
113
  }, formData.toString(), cookies);
120
114
 
121
- // Try to extract code from redirect
122
- const codeFromLocation = resp.finalUrl.match(/[?&]code=([^&]+)/);
123
- if (codeFromLocation) return codeFromLocation[1];
115
+ // Step 2: Check if body contains hidden form_post
116
+ const dom = new JSDOM(resp.html);
117
+ const postForm = dom.window.document.querySelector('form');
118
+ if (postForm) {
119
+ // Collect all hidden inputs from form_post
120
+ const postData = new URLSearchParams();
121
+ postForm.querySelectorAll('input').forEach(input => {
122
+ const name = input.getAttribute('name');
123
+ if (name) postData.append(name, input.getAttribute('value') || '');
124
+ });
125
+
126
+ const postAction = new URL(postForm.getAttribute('action'), resp.finalUrl).toString();
127
+ const postResp = await this.httpsRequest(postAction, {
128
+ method: 'POST',
129
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
130
+ }, postData.toString(), cookies);
131
+
132
+ // Try to extract code from redirect URL
133
+ const codeMatch = postResp.finalUrl.match(/[?&]code=([^&]+)/);
134
+ if (codeMatch) return codeMatch[1];
135
+
136
+ // Or from body (JS redirect)
137
+ const codeFromBody = postResp.html.match(/melcloudhome:\/\/[^"'\s]*[?&]code=([^&"'\s]+)/);
138
+ if (codeFromBody) return codeFromBody[1];
139
+ }
124
140
 
125
- // Or from body (JS redirect or hidden form_post)
141
+ // Fallback: check resp finalUrl or body
142
+ const codeMatch = resp.finalUrl.match(/[?&]code=([^&]+)/);
143
+ if (codeMatch) return codeMatch[1];
126
144
  const codeFromBody = resp.html.match(/melcloudhome:\/\/[^"'\s]*[?&]code=([^&"'\s]+)/);
127
145
  if (codeFromBody) return codeFromBody[1];
128
146
 
@@ -131,7 +149,7 @@ class MelCloudHomeToken {
131
149
 
132
150
  // --- Exchange code for tokens ---
133
151
  async exchangeCodeForTokens(code, codeVerifier) {
134
- console.log('Exchanging authorization code for tokens...');
152
+ this.log.info('Exchanging authorization code for tokens...');
135
153
 
136
154
  const tokenData = new URLSearchParams({
137
155
  grant_type: 'authorization_code',
@@ -157,7 +175,7 @@ class MelCloudHomeToken {
157
175
 
158
176
  // --- Main flow ---
159
177
  async getTokensWithCredentials(email, password) {
160
- console.log('Obtaining OAuth tokens automatically...');
178
+ this.log.info('Obtaining OAuth tokens automatically...');
161
179
 
162
180
  const pkce = this.generatePKCE();
163
181
  const state = this.generateState();
@@ -171,15 +189,15 @@ class MelCloudHomeToken {
171
189
  authUrl.searchParams.set('code_challenge_method', 'S256');
172
190
  authUrl.searchParams.set('state', state);
173
191
 
174
- // 1️⃣ get login page
175
- const { form, cookies } = await this.getLoginPage(authUrl.toString());
192
+ // 1️⃣ Get login page
193
+ const { form, cookies, loginPageUrl } = await this.getLoginPage(authUrl.toString());
176
194
 
177
- // 2️⃣ submit login
178
- const authCode = await this.submitLogin(form, email, password, cookies, authUrl.toString());
195
+ // 2️⃣ Submit login + handle form_post
196
+ const authCode = await this.submitLogin(form, email, password, cookies, loginPageUrl);
179
197
 
180
- // 3️⃣ exchange code for tokens
198
+ // 3️⃣ Exchange code for tokens
181
199
  const tokens = await this.exchangeCodeForTokens(authCode, pkce.verifier);
182
- console.log('✓ Successfully obtained OAuth tokens');
200
+ this.log.info('✓ Successfully obtained OAuth tokens');
183
201
  return tokens;
184
202
  }
185
203
  }