homebridge-melcloud-control 4.0.0-beta.241 → 4.0.0-beta.243
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/melcloud.js +1 -1
- package/src/melcloudhometoken.js +33 -42
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.243",
|
|
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
|
"tough-cookie": "^6.0.0",
|
|
44
|
-
"axios-cookiejar-support": "^6.0.4"
|
|
44
|
+
"axios-cookiejar-support": "^6.0.4",
|
|
45
|
+
"jsdom": "^27.0.1"
|
|
45
46
|
},
|
|
46
47
|
"keywords": [
|
|
47
48
|
"homebridge",
|
package/src/melcloud.js
CHANGED
|
@@ -178,7 +178,7 @@ class MelCloud extends EventEmitter {
|
|
|
178
178
|
async connectToMelCloudHome() {
|
|
179
179
|
const oauth = new MelCloudHomeToken(this.log);
|
|
180
180
|
this.tokens = await oauth.getTokensWithCredentials(this.user, this.passwd);
|
|
181
|
-
this.emit('warn', `Token${JSON.stringify(this.tokens, null, 2)}`);
|
|
181
|
+
this.emit('warn', `Token ${JSON.stringify(this.tokens, null, 2)}`);
|
|
182
182
|
//this.axiosInstance.defaults.headers['Authorization'] = `Bearer ${this.tokens.accessToken}`;
|
|
183
183
|
return false;
|
|
184
184
|
}
|
package/src/melcloudhometoken.js
CHANGED
|
@@ -3,6 +3,7 @@ import axios from 'axios';
|
|
|
3
3
|
import { CookieJar } from 'tough-cookie';
|
|
4
4
|
import { wrapper } from 'axios-cookiejar-support';
|
|
5
5
|
import crypto from 'crypto';
|
|
6
|
+
import { JSDOM } from 'jsdom';
|
|
6
7
|
|
|
7
8
|
const MOBILE_USER_AGENT = 'MonitorAndControl.App.Mobile/35 CFNetwork/3860.100.1 Darwin/25.0.0';
|
|
8
9
|
const CLIENT_ID = 'homemobile';
|
|
@@ -11,7 +12,7 @@ const SCOPE = 'openid profile email offline_access IdentityServerApi';
|
|
|
11
12
|
const TOKEN_ENDPOINT = 'https://auth.melcloudhome.com/connect/token';
|
|
12
13
|
const AUTHORIZE_ENDPOINT = 'https://auth.melcloudhome.com/connect/authorize';
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
class MelCloudHomeToken {
|
|
15
16
|
constructor(log) {
|
|
16
17
|
this.jar = new CookieJar();
|
|
17
18
|
this.axiosInstance = wrapper(axios.create({
|
|
@@ -53,71 +54,61 @@ export class MelCloudHomeToken {
|
|
|
53
54
|
return { html, loginUrl: resp.request.res.responseUrl, csrf: csrfMatch[1], cookies };
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
async submitLogin(loginUrl,
|
|
57
|
+
async submitLogin(loginUrl, email, password, cookies) {
|
|
57
58
|
console.log('Submitting login credentials...');
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
headers: {
|
|
63
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
64
|
-
'Cookie': cookies,
|
|
65
|
-
'Origin': 'https://live-melcloudhome.auth.eu-west-1.amazoncognito.com',
|
|
66
|
-
'Referer': loginUrl
|
|
67
|
-
},
|
|
60
|
+
// 1. Pobieramy stronę logowania
|
|
61
|
+
const resp = await this.axiosInstance.get(loginUrl, {
|
|
62
|
+
headers: { Cookie: cookies },
|
|
68
63
|
maxRedirects: 0,
|
|
69
|
-
validateStatus:
|
|
70
|
-
}).catch(err =>
|
|
71
|
-
if (err.response && err.response.status >= 300 && err.response.status < 400) return err.response;
|
|
72
|
-
throw err;
|
|
73
|
-
});
|
|
64
|
+
validateStatus: s => s >= 200 && s < 400
|
|
65
|
+
}).catch(err => err.response || err);
|
|
74
66
|
|
|
75
|
-
const
|
|
76
|
-
if (locationHeader && locationHeader.startsWith('melcloudhome://')) {
|
|
77
|
-
const codeMatch = locationHeader.match(/[?&]code=([^&]+)/);
|
|
78
|
-
if (codeMatch) return codeMatch[1];
|
|
79
|
-
}
|
|
67
|
+
const html = resp.data;
|
|
80
68
|
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
const
|
|
69
|
+
// 2. Parsujemy formę logowania i zbieramy hidden inputs
|
|
70
|
+
const dom = new JSDOM(html);
|
|
71
|
+
const form = dom.window.document.querySelector('form');
|
|
72
|
+
if (!form) throw new Error('Nie znaleziono formy logowania');
|
|
84
73
|
|
|
85
|
-
|
|
86
|
-
|
|
74
|
+
const formData = new URLSearchParams();
|
|
75
|
+
for (const input of form.querySelectorAll('input')) {
|
|
76
|
+
const name = input.getAttribute('name');
|
|
77
|
+
const value = input.getAttribute('value') || '';
|
|
78
|
+
formData.append(name, value);
|
|
87
79
|
}
|
|
88
80
|
|
|
89
|
-
|
|
90
|
-
|
|
81
|
+
// 3. Nadpisujemy username i password
|
|
82
|
+
formData.set('username', email);
|
|
83
|
+
formData.set('password', password);
|
|
91
84
|
|
|
92
|
-
|
|
93
|
-
}
|
|
85
|
+
const actionUrl = new URL(form.getAttribute('action'), loginUrl).toString();
|
|
94
86
|
|
|
95
|
-
|
|
96
|
-
|
|
87
|
+
// 4. Wysyłamy POST do form action
|
|
88
|
+
return this.submitFormPost(actionUrl, formData);
|
|
89
|
+
}
|
|
97
90
|
|
|
91
|
+
async submitFormPost(actionUrl, formData) {
|
|
98
92
|
const resp = await this.axiosInstance.post(actionUrl, formData.toString(), {
|
|
99
93
|
headers: {
|
|
100
94
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
101
|
-
'Origin': 'https://live-melcloudhome.auth.eu-west-1.amazoncognito.com',
|
|
102
|
-
'Referer': actionUrl
|
|
103
95
|
},
|
|
104
96
|
maxRedirects: 0,
|
|
105
|
-
validateStatus:
|
|
106
|
-
}).catch(err =>
|
|
107
|
-
if (err.response && err.response.status >= 300 && err.response.status < 400) return err.response;
|
|
108
|
-
throw err;
|
|
109
|
-
});
|
|
97
|
+
validateStatus: s => s >= 200 && s < 400
|
|
98
|
+
}).catch(err => err.response || err);
|
|
110
99
|
|
|
111
|
-
|
|
100
|
+
// 5. Szukamy authorization code w redirect
|
|
101
|
+
const locationHeader = resp.headers?.location;
|
|
112
102
|
if (locationHeader && locationHeader.startsWith('melcloudhome://')) {
|
|
113
103
|
const codeMatch = locationHeader.match(/[?&]code=([^&]+)/);
|
|
114
104
|
if (codeMatch) return codeMatch[1];
|
|
115
105
|
}
|
|
116
106
|
|
|
117
|
-
|
|
107
|
+
// 6. Albo w body (czasami JS redirect)
|
|
108
|
+
const bodyCodeMatch = resp.data?.match(/melcloudhome:\/\/[^"'\s]*[?&]code=([^&"'\s]+)/);
|
|
118
109
|
if (bodyCodeMatch) return bodyCodeMatch[1];
|
|
119
110
|
|
|
120
|
-
throw new Error('No authorization code found
|
|
111
|
+
throw new Error('No authorization code found after login');
|
|
121
112
|
}
|
|
122
113
|
|
|
123
114
|
async exchangeCodeForTokens(code, codeVerifier) {
|