homebridge-melcloud-control 4.0.0-beta.257 → 4.0.0-beta.259
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 +3 -2
- package/src/melcloudhometoken.js +62 -14
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.259",
|
|
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
|
"axios-cookiejar-support": "^6.0.4",
|
|
44
|
-
"tough-cookie": "^6.0.0"
|
|
44
|
+
"tough-cookie": "^6.0.0",
|
|
45
|
+
"cheerio": "^1.1.2"
|
|
45
46
|
},
|
|
46
47
|
"keywords": [
|
|
47
48
|
"homebridge",
|
package/src/melcloudhometoken.js
CHANGED
|
@@ -2,6 +2,7 @@ import axios from 'axios';
|
|
|
2
2
|
import { wrapper } from 'axios-cookiejar-support';
|
|
3
3
|
import { CookieJar } from 'tough-cookie';
|
|
4
4
|
import crypto from 'crypto';
|
|
5
|
+
import * as cheerio from 'cheerio';
|
|
5
6
|
|
|
6
7
|
const MOBILE_USER_AGENT = 'MonitorAndControl.App.Mobile/35 CFNetwork/3860.100.1 Darwin/25.0.0';
|
|
7
8
|
const CLIENT_ID = 'homemobile';
|
|
@@ -29,40 +30,84 @@ class MelCloudHomeToken {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
async getLoginPage(authUrl) {
|
|
32
|
-
|
|
33
|
+
console.log('Getting login page...');
|
|
33
34
|
const res = await this.client.get(authUrl, { maxRedirects: 10 });
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
const $ = cheerio.load(res.data);
|
|
36
|
+
const csrf = $('input[name="_csrf"]').attr('value');
|
|
37
|
+
if (!csrf) throw new Error('Could not extract CSRF token from login page');
|
|
38
|
+
console.log('CSRF token found:', csrf);
|
|
39
|
+
return { csrf, url: res.request.res.responseUrl };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async submitFormPost(actionUrl, code, state) {
|
|
43
|
+
console.log('Submitting form_post to callback URL:', actionUrl);
|
|
44
|
+
const form = new URLSearchParams({ code, state });
|
|
45
|
+
const res = await this.client.post(actionUrl, form.toString(), {
|
|
46
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
47
|
+
maxRedirects: 0,
|
|
48
|
+
validateStatus: (status) => status >= 200 && status < 400,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const location = res.headers.location;
|
|
52
|
+
if (location?.startsWith('melcloudhome://')) {
|
|
53
|
+
const codeMatch = location.match(/[?&]code=([^&]+)/);
|
|
54
|
+
if (codeMatch) {
|
|
55
|
+
console.log('Authorization code obtained from form_post redirect:', codeMatch[1]);
|
|
56
|
+
return codeMatch[1];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
throw new Error('No authorization code found in form_post response');
|
|
37
60
|
}
|
|
38
61
|
|
|
39
62
|
async submitLogin(loginUrl, csrf, email, password) {
|
|
63
|
+
console.log('Submitting login credentials to:', loginUrl);
|
|
40
64
|
const form = new URLSearchParams({ _csrf: csrf, username: email, password });
|
|
41
65
|
const res = await this.client.post(loginUrl, form.toString(), {
|
|
42
66
|
headers: {
|
|
43
67
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
44
68
|
Referer: loginUrl,
|
|
45
69
|
},
|
|
46
|
-
maxRedirects: 0,
|
|
70
|
+
maxRedirects: 0,
|
|
47
71
|
validateStatus: (status) => status >= 200 && status < 400,
|
|
48
72
|
});
|
|
49
73
|
|
|
50
|
-
|
|
74
|
+
console.log('Login POST status:', res.status);
|
|
75
|
+
|
|
76
|
+
// Spróbuj pobrać kod z nagłówka Location
|
|
51
77
|
const location = res.headers.location;
|
|
52
78
|
if (location?.startsWith('melcloudhome://')) {
|
|
53
79
|
const codeMatch = location.match(/[?&]code=([^&]+)/);
|
|
54
|
-
if (codeMatch)
|
|
80
|
+
if (codeMatch) {
|
|
81
|
+
console.log('Authorization code obtained from Location header:', codeMatch[1]);
|
|
82
|
+
return codeMatch[1];
|
|
83
|
+
}
|
|
55
84
|
}
|
|
56
85
|
|
|
57
|
-
//
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
if (
|
|
86
|
+
// Spróbuj parsować HTML w poszukiwaniu form_post
|
|
87
|
+
const $ = cheerio.load(res.data);
|
|
88
|
+
const formElement = $('form');
|
|
89
|
+
if (formElement.length) {
|
|
90
|
+
const action = formElement.attr('action');
|
|
91
|
+
const code = $('input[name="code"]').attr('value');
|
|
92
|
+
const state = $('input[name="state"]').attr('value');
|
|
93
|
+
if (action && code && state) {
|
|
94
|
+
console.log('Found form_post. Action:', action, 'Code:', code, 'State:', state);
|
|
95
|
+
return this.submitFormPost(action, code, state);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Spróbuj wyciągnąć kod z JS w ciele strony
|
|
100
|
+
const bodyCodeMatch = res.data.match(/melcloudhome:\/\/[^"'\s]*[?&]code=([^&"'\s]+)/);
|
|
101
|
+
if (bodyCodeMatch) {
|
|
102
|
+
console.log('Authorization code obtained from HTML/JS body:', bodyCodeMatch[1]);
|
|
103
|
+
return bodyCodeMatch[1];
|
|
104
|
+
}
|
|
61
105
|
|
|
62
106
|
throw new Error('Authorization code not found in login response');
|
|
63
107
|
}
|
|
64
108
|
|
|
65
109
|
async exchangeCodeForTokens(code, codeVerifier) {
|
|
110
|
+
console.log('Exchanging authorization code for tokens...');
|
|
66
111
|
const form = new URLSearchParams({
|
|
67
112
|
grant_type: 'authorization_code',
|
|
68
113
|
code,
|
|
@@ -79,6 +124,8 @@ class MelCloudHomeToken {
|
|
|
79
124
|
});
|
|
80
125
|
|
|
81
126
|
if (res.data.error) throw new Error(`Token exchange failed: ${res.data.error_description || res.data.error}`);
|
|
127
|
+
|
|
128
|
+
console.log('Tokens obtained:', res.data);
|
|
82
129
|
return {
|
|
83
130
|
accessToken: res.data.access_token,
|
|
84
131
|
refreshToken: res.data.refresh_token,
|
|
@@ -89,7 +136,7 @@ class MelCloudHomeToken {
|
|
|
89
136
|
|
|
90
137
|
async getTokensWithCredentials(email, password) {
|
|
91
138
|
try {
|
|
92
|
-
|
|
139
|
+
console.log('Starting OAuth flow...');
|
|
93
140
|
const pkce = this.generatePKCE();
|
|
94
141
|
const state = this.generateState();
|
|
95
142
|
|
|
@@ -106,16 +153,17 @@ class MelCloudHomeToken {
|
|
|
106
153
|
const authCode = await this.submitLogin(loginPage.url, loginPage.csrf, email, password);
|
|
107
154
|
const tokens = await this.exchangeCodeForTokens(authCode, pkce.verifier);
|
|
108
155
|
|
|
109
|
-
|
|
156
|
+
console.log('✓ Successfully obtained OAuth tokens');
|
|
110
157
|
return tokens;
|
|
111
158
|
} catch (error) {
|
|
112
|
-
|
|
159
|
+
console.error('Failed to obtain OAuth tokens:', error);
|
|
113
160
|
throw error;
|
|
114
161
|
}
|
|
115
162
|
}
|
|
116
163
|
}
|
|
117
164
|
|
|
118
165
|
|
|
166
|
+
|
|
119
167
|
export default MelCloudHomeToken;
|
|
120
168
|
|
|
121
169
|
|