homebridge-melcloud-control 4.0.0-beta.269 → 4.0.0-beta.270
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 -1
- package/src/melcloud.js +6 -4
- package/src/melcloudhometoken.js +2 -105
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.270",
|
|
5
5
|
"description": "Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "grzegorz914",
|
package/src/melcloud.js
CHANGED
|
@@ -9,6 +9,7 @@ import Functions from './functions.js';
|
|
|
9
9
|
import { ApiUrls, ApiUrlsHome } from './constants.js';
|
|
10
10
|
import fetch from 'node-fetch';
|
|
11
11
|
import { URLSearchParams } from 'url';
|
|
12
|
+
const TOKEN_ENDPOINT = 'https://auth.melcloudhome.com/connect/token';
|
|
12
13
|
|
|
13
14
|
class MelCloud extends EventEmitter {
|
|
14
15
|
constructor(accountType, user, passwd, language, accountFile, buildingsFile, devicesFile, logWarn, logDebug, requestConfig) {
|
|
@@ -288,10 +289,12 @@ class MelCloud extends EventEmitter {
|
|
|
288
289
|
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
|
289
290
|
});
|
|
290
291
|
|
|
292
|
+
const melCloudHomeToken = new MelCloudHomeToken();
|
|
293
|
+
const url = await melCloudHomeToken.getUrl();
|
|
294
|
+
|
|
291
295
|
const page = await browser.newPage();
|
|
292
|
-
await page.goto(
|
|
296
|
+
await page.goto(url, { waitUntil: 'networkidle2' });
|
|
293
297
|
|
|
294
|
-
// 2. Kliknij login (jeżeli strona główna ma przycisk)
|
|
295
298
|
const buttons = await page.$$('button.btn--blue');
|
|
296
299
|
let loginBtn = null;
|
|
297
300
|
|
|
@@ -310,7 +313,6 @@ class MelCloud extends EventEmitter {
|
|
|
310
313
|
page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 10000 })
|
|
311
314
|
]);
|
|
312
315
|
|
|
313
|
-
// 3. Wypełnij login i hasło
|
|
314
316
|
await page.waitForSelector('input[name="username"]', { timeout: 5000 });
|
|
315
317
|
await page.type('input[name="username"]', this.user, { delay: 50 });
|
|
316
318
|
await page.type('input[name="password"]', this.passwd, { delay: 50 });
|
|
@@ -329,7 +331,7 @@ class MelCloud extends EventEmitter {
|
|
|
329
331
|
if (!authCode) throw new Error('Authorization code not found in URL');
|
|
330
332
|
|
|
331
333
|
// 5. Wymień code na access_token
|
|
332
|
-
const tokenResponse = await axios.post(
|
|
334
|
+
const tokenResponse = await axios.post(TOKEN_ENDPOINT, new URLSearchParams({
|
|
333
335
|
client_id: clientId,
|
|
334
336
|
grant_type: 'authorization_code',
|
|
335
337
|
code: authCode,
|
package/src/melcloudhometoken.js
CHANGED
|
@@ -39,107 +39,7 @@ class MelCloudHomeToken {
|
|
|
39
39
|
return { csrf, url: res.request.res.responseUrl };
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
async
|
|
43
|
-
console.log('Submitting login credentials to:', loginUrl);
|
|
44
|
-
|
|
45
|
-
const form = new URLSearchParams({ _csrf: csrf, username: email, password });
|
|
46
|
-
let res = await this.client.post(loginUrl, form.toString(), {
|
|
47
|
-
headers: {
|
|
48
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
49
|
-
Referer: loginUrl,
|
|
50
|
-
},
|
|
51
|
-
maxRedirects: 0,
|
|
52
|
-
validateStatus: (status) => status >= 200 && status < 400,
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
console.log('Login POST status:', res.status);
|
|
56
|
-
|
|
57
|
-
// Obsługa 302 redirect
|
|
58
|
-
while (res.status === 302 && res.headers.location) {
|
|
59
|
-
const location = res.headers.location;
|
|
60
|
-
console.log('Following redirect:', location);
|
|
61
|
-
res = await this.client.get(location, {
|
|
62
|
-
maxRedirects: 0,
|
|
63
|
-
validateStatus: (status) => status >= 200 && status < 400,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Sprawdź HTML na form_post
|
|
68
|
-
let $ = cheerio.load(res.data);
|
|
69
|
-
let formElement = $('form');
|
|
70
|
-
if (formElement.length) {
|
|
71
|
-
const action = formElement.attr('action');
|
|
72
|
-
const code = $('input[name="code"]').attr('value');
|
|
73
|
-
const state = $('input[name="state"]').attr('value');
|
|
74
|
-
if (action && code && state) {
|
|
75
|
-
console.log('Found form_post. Action:', action, 'Code:', code, 'State:', state);
|
|
76
|
-
return this.submitFormPost(action, code, state);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Spróbuj pobrać kod bezpośrednio z JS w body
|
|
81
|
-
const bodyCodeMatch = res.data.match(/melcloudhome:\/\/[^"'\s]*[?&]code=([^&"'\s]+)/);
|
|
82
|
-
if (bodyCodeMatch) {
|
|
83
|
-
console.log('Authorization code obtained from HTML/JS body:', bodyCodeMatch[1]);
|
|
84
|
-
return bodyCodeMatch[1];
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
throw new Error('Authorization code not found in login response');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async submitFormPost(actionUrl, code, state) {
|
|
91
|
-
console.log('Submitting form_post to callback URL:', actionUrl);
|
|
92
|
-
|
|
93
|
-
const form = new URLSearchParams({ code, state });
|
|
94
|
-
const res = await this.client.post(actionUrl, form.toString(), {
|
|
95
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
96
|
-
maxRedirects: 0,
|
|
97
|
-
validateStatus: (status) => status >= 200 && status < 400,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
// Sprawdź redirect z finalnym code
|
|
101
|
-
const location = res.headers.location;
|
|
102
|
-
if (location?.startsWith('melcloudhome://')) {
|
|
103
|
-
const codeMatch = location.match(/[?&]code=([^&]+)/);
|
|
104
|
-
if (codeMatch) {
|
|
105
|
-
console.log('Authorization code obtained from form_post redirect:', codeMatch[1]);
|
|
106
|
-
return codeMatch[1];
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
throw new Error('No authorization code found in form_post response');
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
async exchangeCodeForTokens(code, codeVerifier) {
|
|
115
|
-
console.log('Exchanging authorization code for tokens...');
|
|
116
|
-
const form = new URLSearchParams({
|
|
117
|
-
grant_type: 'authorization_code',
|
|
118
|
-
code,
|
|
119
|
-
redirect_uri: REDIRECT_URI,
|
|
120
|
-
client_id: CLIENT_ID,
|
|
121
|
-
code_verifier: codeVerifier,
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
const res = await this.client.post(TOKEN_ENDPOINT, form.toString(), {
|
|
125
|
-
headers: {
|
|
126
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
127
|
-
Authorization: 'Basic aG9tZW1vYmlsZTo=',
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
if (res.data.error) throw new Error(`Token exchange failed: ${res.data.error_description || res.data.error}`);
|
|
132
|
-
|
|
133
|
-
console.log('Tokens obtained:', res.data);
|
|
134
|
-
return {
|
|
135
|
-
accessToken: res.data.access_token,
|
|
136
|
-
refreshToken: res.data.refresh_token,
|
|
137
|
-
expiresIn: res.data.expires_in,
|
|
138
|
-
idToken: res.data.id_token,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
async getTokensWithCredentials(email, password) {
|
|
42
|
+
async getUrl() {
|
|
143
43
|
try {
|
|
144
44
|
console.log('Starting OAuth flow...');
|
|
145
45
|
const pkce = this.generatePKCE();
|
|
@@ -155,11 +55,8 @@ class MelCloudHomeToken {
|
|
|
155
55
|
authUrl.searchParams.set('state', state);
|
|
156
56
|
|
|
157
57
|
const loginPage = await this.getLoginPage(authUrl.toString());
|
|
158
|
-
const authCode = await this.submitLogin(loginPage.url, loginPage.csrf, email, password);
|
|
159
|
-
const tokens = await this.exchangeCodeForTokens(authCode, pkce.verifier);
|
|
160
58
|
|
|
161
|
-
|
|
162
|
-
return tokens;
|
|
59
|
+
return loginPage;
|
|
163
60
|
} catch (error) {
|
|
164
61
|
console.error('Failed to obtain OAuth tokens:', error);
|
|
165
62
|
throw error;
|