homebridge-melcloud-control 4.0.0-beta.246 → 4.0.0-beta.248
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 +1 -3
- package/src/melcloudhometoken.js +42 -18
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.
|
|
4
|
+
"version": "4.0.0-beta.248",
|
|
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": [
|
package/src/melcloudhometoken.js
CHANGED
|
@@ -12,8 +12,8 @@ const TOKEN_ENDPOINT = 'https://auth.melcloudhome.com/connect/token';
|
|
|
12
12
|
const AUTHORIZE_ENDPOINT = 'https://auth.melcloudhome.com/connect/authorize';
|
|
13
13
|
|
|
14
14
|
class MelCloudHomeToken {
|
|
15
|
-
constructor() {
|
|
16
|
-
|
|
15
|
+
constructor(log) {
|
|
16
|
+
this.log = log;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
// --- PKCE ---
|
|
@@ -43,7 +43,7 @@ class MelCloudHomeToken {
|
|
|
43
43
|
if (cookies.length > 0) reqOptions.headers['Cookie'] = cookies.join('; ');
|
|
44
44
|
|
|
45
45
|
const req = https.request(reqOptions, (res) => {
|
|
46
|
-
//
|
|
46
|
+
// Collect Set-Cookie
|
|
47
47
|
const setCookie = res.headers['set-cookie'];
|
|
48
48
|
if (setCookie) {
|
|
49
49
|
setCookie.forEach(c => {
|
|
@@ -54,7 +54,7 @@ class MelCloudHomeToken {
|
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
//
|
|
57
|
+
// Follow redirects
|
|
58
58
|
if ([301, 302, 303].includes(res.statusCode) && res.headers.location) {
|
|
59
59
|
const redirectUrl = res.headers.location.startsWith('http')
|
|
60
60
|
? res.headers.location
|
|
@@ -88,14 +88,14 @@ class MelCloudHomeToken {
|
|
|
88
88
|
const form = dom.window.document.querySelector('form');
|
|
89
89
|
if (!form) throw new Error('Login form not found');
|
|
90
90
|
|
|
91
|
-
return {
|
|
91
|
+
return { form, cookies: resp.cookies, loginPageUrl: resp.finalUrl };
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
// --- Submit login ---
|
|
94
|
+
// --- Submit login and handle form_post ---
|
|
95
95
|
async submitLogin(form, email, password, cookies, loginPageUrl) {
|
|
96
|
-
|
|
96
|
+
console.log('Submitting login credentials...');
|
|
97
97
|
|
|
98
|
-
// Collect all hidden inputs
|
|
98
|
+
// Step 1: Collect all hidden inputs
|
|
99
99
|
const formData = new URLSearchParams();
|
|
100
100
|
form.querySelectorAll('input').forEach(input => {
|
|
101
101
|
const name = input.getAttribute('name');
|
|
@@ -112,11 +112,35 @@ class MelCloudHomeToken {
|
|
|
112
112
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
|
|
113
113
|
}, formData.toString(), cookies);
|
|
114
114
|
|
|
115
|
-
//
|
|
116
|
-
const
|
|
117
|
-
|
|
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
|
+
}
|
|
118
140
|
|
|
119
|
-
//
|
|
141
|
+
// Fallback: check resp finalUrl or body
|
|
142
|
+
const codeMatch = resp.finalUrl.match(/[?&]code=([^&]+)/);
|
|
143
|
+
if (codeMatch) return codeMatch[1];
|
|
120
144
|
const codeFromBody = resp.html.match(/melcloudhome:\/\/[^"'\s]*[?&]code=([^&"'\s]+)/);
|
|
121
145
|
if (codeFromBody) return codeFromBody[1];
|
|
122
146
|
|
|
@@ -165,15 +189,15 @@ class MelCloudHomeToken {
|
|
|
165
189
|
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
166
190
|
authUrl.searchParams.set('state', state);
|
|
167
191
|
|
|
168
|
-
// 1️⃣
|
|
169
|
-
const { form, cookies } = await this.getLoginPage(authUrl.toString());
|
|
192
|
+
// 1️⃣ Get login page
|
|
193
|
+
const { form, cookies, loginPageUrl } = await this.getLoginPage(authUrl.toString());
|
|
170
194
|
|
|
171
|
-
// 2️⃣
|
|
172
|
-
const authCode = await this.submitLogin(form, email, password, cookies,
|
|
195
|
+
// 2️⃣ Submit login + handle form_post
|
|
196
|
+
const authCode = await this.submitLogin(form, email, password, cookies, loginPageUrl);
|
|
173
197
|
|
|
174
|
-
// 3️⃣
|
|
198
|
+
// 3️⃣ Exchange code for tokens
|
|
175
199
|
const tokens = await this.exchangeCodeForTokens(authCode, pkce.verifier);
|
|
176
|
-
|
|
200
|
+
console.log('✓ Successfully obtained OAuth tokens');
|
|
177
201
|
return tokens;
|
|
178
202
|
}
|
|
179
203
|
}
|