homebridge-melcloud-control 4.0.0-beta.297 → 4.0.0-beta.299
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 +5 -46
- package/src/melcloudhometoken.js +62 -39
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.299",
|
|
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 { URLSearchParams } from 'url';
|
|
11
11
|
const TOKEN_ENDPOINT = 'https://auth.melcloudhome.com/connect/token';
|
|
12
|
+
const REDIRECT_URI = 'melcloudhome://';
|
|
12
13
|
|
|
13
14
|
class MelCloud extends EventEmitter {
|
|
14
15
|
constructor(accountType, user, passwd, language, accountFile, buildingsFile, devicesFile, logWarn, logDebug, requestConfig) {
|
|
@@ -291,8 +292,6 @@ class MelCloud extends EventEmitter {
|
|
|
291
292
|
const melCloudHomeToken = new MelCloudHomeToken();
|
|
292
293
|
const url = await melCloudHomeToken.buildAuthorizeUrl();
|
|
293
294
|
|
|
294
|
-
this.emit('warn', url);
|
|
295
|
-
|
|
296
295
|
const page = await browser.newPage();
|
|
297
296
|
await page.goto(url, { waitUntil: 'networkidle2' });
|
|
298
297
|
|
|
@@ -302,52 +301,12 @@ class MelCloud extends EventEmitter {
|
|
|
302
301
|
const button1 = await page.$('input[type="submit"]');
|
|
303
302
|
await Promise.all([button1.click(), page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 10000 })]);
|
|
304
303
|
const html = await page.content();
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
console.log(code);
|
|
308
|
-
return
|
|
309
|
-
// Check for form_post response (HTML with hidden form)
|
|
310
|
-
const formCodeMatch = html.match(/name="code"\s+value="([^"]+)"/);
|
|
311
|
-
const formStateMatch = html.match(/name="state"\s+value="([^"]+)"/);
|
|
312
|
-
const formActionMatch = html.match(/action="([^"]+)"/);
|
|
313
|
-
|
|
314
|
-
if (formCodeMatch && formStateMatch && formActionMatch) {
|
|
315
|
-
console.log('Found form_post response, submitting to callback endpoint...');
|
|
316
|
-
this.submitFormPost(formActionMatch[1], formCodeMatch[1], formStateMatch[1], cookies)
|
|
317
|
-
.then(code => resolve(code))
|
|
318
|
-
.catch(err => reject(err));
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Check for melcloudhome:// redirect in JS
|
|
323
|
-
const bodyCodeMatch = html.match(/melcloudhome:\/\/[^"'\s]*[?&]code=([^&"'\s]+)/);
|
|
324
|
-
if (bodyCodeMatch) {
|
|
325
|
-
console.log('Found authorization code in response body (JS)');
|
|
326
|
-
resolve(bodyCodeMatch[1]);
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// 🆕 NEW: Check for data-url with code parameter (like in 500 error pages)
|
|
331
|
-
const dataUrlMatch = html.match(/data-url="[^"]*code=([^&"']+)/);
|
|
332
|
-
if (dataUrlMatch) {
|
|
333
|
-
console.log('Found authorization code in data-url attribute');
|
|
334
|
-
resolve(dataUrlMatch[1]);
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
reject(new Error('Authorization code not found in URL or HTML'));
|
|
304
|
+
const authCode = await melCloudHomeToken.extractCodeFromHtml(html);
|
|
305
|
+
this.emit('warn', authCode);
|
|
339
306
|
|
|
340
307
|
// 5. Wymień code na access_token
|
|
341
|
-
const tokenResponse = await
|
|
342
|
-
|
|
343
|
-
grant_type: 'authorization_code',
|
|
344
|
-
code: authCode,
|
|
345
|
-
redirect_uri: 'melcloudhome://'
|
|
346
|
-
}).toString(), {
|
|
347
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
const tokenData = await tokenResp.json();
|
|
308
|
+
const tokenResponse = await melCloudHomeToken.getTokens(authCode);
|
|
309
|
+
const tokenData = await tokenResponse.json();
|
|
351
310
|
this.emit('debug', `Token: ${JSON.stringify(tokenData, null, 2)}`);
|
|
352
311
|
return {
|
|
353
312
|
accessToken: tokenData.access_token,
|
package/src/melcloudhometoken.js
CHANGED
|
@@ -11,8 +11,7 @@ const TOKEN_ENDPOINT = 'https://auth.melcloudhome.com/connect/token';
|
|
|
11
11
|
const AUTHORIZE_ENDPOINT = 'https://auth.melcloudhome.com/connect/authorize';
|
|
12
12
|
|
|
13
13
|
class MelCloudHomeToken {
|
|
14
|
-
constructor(
|
|
15
|
-
this.log = log;
|
|
14
|
+
constructor() {
|
|
16
15
|
const jar = new CookieJar();
|
|
17
16
|
this.client = wrapper(axios.create({ jar, withCredentials: true }));
|
|
18
17
|
this.client.defaults.headers['User-Agent'] = MOBILE_USER_AGENT;
|
|
@@ -33,48 +32,73 @@ class MelCloudHomeToken {
|
|
|
33
32
|
return { url: res.request.res.responseUrl };
|
|
34
33
|
}
|
|
35
34
|
|
|
36
|
-
async extractCodeFromHtml(html) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
35
|
+
async extractCodeFromHtml(html, cookies = '') {
|
|
36
|
+
try {
|
|
37
|
+
// Code w URL
|
|
38
|
+
const urlMatch = html.match(/[?&]code=([^&]+)/);
|
|
39
|
+
if (urlMatch) {
|
|
40
|
+
return urlMatch[1];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Hidden form (form_post)
|
|
44
|
+
const formCodeMatch = html.match(/name="code"\s+value="([^"]+)"/);
|
|
45
|
+
const formStateMatch = html.match(/name="state"\s+value="([^"]+)"/);
|
|
46
|
+
const formActionMatch = html.match(/action="([^"]+)"/);
|
|
47
|
+
|
|
48
|
+
if (formCodeMatch && formStateMatch && formActionMatch) {
|
|
49
|
+
const code = await this.submitFormPost(
|
|
50
|
+
formActionMatch[1],
|
|
51
|
+
formCodeMatch[1],
|
|
52
|
+
formStateMatch[1],
|
|
53
|
+
cookies
|
|
54
|
+
);
|
|
55
|
+
return code;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// melcloudhome:// w body
|
|
59
|
+
const bodyCodeMatch = html.match(/melcloudhome:\/\/[^"'\s]*[?&]code=([^&"'\s]+)/);
|
|
60
|
+
if (bodyCodeMatch) {
|
|
61
|
+
return bodyCodeMatch[1];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// data-url (np. z błędnej strony 500)
|
|
65
|
+
const dataUrlMatch = html.match(/data-url="[^"]*code=([^&"']+)/);
|
|
66
|
+
if (dataUrlMatch) {
|
|
67
|
+
return dataUrlMatch[1];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.warn('Authorization code not found in HTML response');
|
|
71
|
+
return null;
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.error('extractCodeFromHtml error:', err);
|
|
74
|
+
return null;
|
|
65
75
|
}
|
|
76
|
+
}
|
|
66
77
|
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
78
|
+
async getTokens(code) {
|
|
79
|
+
const pkce = this.generatePKCE();
|
|
80
|
+
const data = new URLSearchParams();
|
|
81
|
+
data.append('grant_type', 'authorization_code');
|
|
82
|
+
data.append('client_id', CLIENT_ID);
|
|
83
|
+
data.append('code', code);
|
|
84
|
+
data.append('redirect_uri', REDIRECT_URI);
|
|
85
|
+
data.append('code_verifier', pkce.verifier);
|
|
86
|
+
|
|
87
|
+
const res = await this.client.post(TOKEN_ENDPOINT, data.toString(), {
|
|
88
|
+
headers: {
|
|
89
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
90
|
+
'Authorization': 'Basic aG9tZW1vYmlsZTo='
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (res.status !== 200) {
|
|
95
|
+
throw new Error(`Token exchange failed: ${res.status} ${res.statusText}`);
|
|
73
96
|
}
|
|
74
97
|
|
|
75
|
-
return
|
|
98
|
+
return res.data;
|
|
76
99
|
}
|
|
77
100
|
|
|
101
|
+
|
|
78
102
|
async buildAuthorizeUrl() {
|
|
79
103
|
try {
|
|
80
104
|
const pkce = this.generatePKCE();
|
|
@@ -87,7 +111,6 @@ class MelCloudHomeToken {
|
|
|
87
111
|
authUrl.searchParams.set('code_challenge', pkce.challenge);
|
|
88
112
|
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
89
113
|
authUrl.searchParams.set('state', state);
|
|
90
|
-
|
|
91
114
|
const loginPage = await this.getLoginPage(authUrl.toString());
|
|
92
115
|
|
|
93
116
|
return loginPage.url;
|