homebridge-melcloud-control 4.0.0-beta.407 → 4.0.0-beta.408
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 +1 -1
- package/src/melcloudhometoken.js +35 -28
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.408",
|
|
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
|
@@ -291,7 +291,7 @@ class MelCloud extends EventEmitter {
|
|
|
291
291
|
|
|
292
292
|
//const { codeVerifier, url } = await melCloudHomeToken.buildAuthorizeUrl();
|
|
293
293
|
//const code = await melCloudHomeToken.loginToMelCloudHome(url);
|
|
294
|
-
const token = await melCloudHomeToken.
|
|
294
|
+
const token = await melCloudHomeToken.login(this.user, this.passwd);
|
|
295
295
|
|
|
296
296
|
const accountInfo = { ContextKey: token, UseFahrenheit: false };
|
|
297
297
|
this.contextKey = code;
|
package/src/melcloudhometoken.js
CHANGED
|
@@ -19,7 +19,7 @@ class MelCloudHomeToken extends EventEmitter {
|
|
|
19
19
|
this.http = wrapper(axios.create({ jar: this.cookieJar }));
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
// ---
|
|
22
|
+
// --- PKCE ---
|
|
23
23
|
_generatePkcePair() {
|
|
24
24
|
const verifier = crypto.randomBytes(32).toString('base64url');
|
|
25
25
|
const challenge = crypto
|
|
@@ -29,14 +29,19 @@ class MelCloudHomeToken extends EventEmitter {
|
|
|
29
29
|
return { verifier, challenge };
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
// ---
|
|
33
|
-
|
|
32
|
+
// --- Extract Cognito hidden form fields ---
|
|
33
|
+
_extractLoginForm(html) {
|
|
34
34
|
if (typeof html !== 'string') return null;
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
|
|
36
|
+
const state = html.match(/name=['"]state['"][^>]*value=['"]([^'"]+)['"]/i)?.[1];
|
|
37
|
+
const clientId = html.match(/name=['"]client_id['"][^>]*value=['"]([^'"]+)['"]/i)?.[1];
|
|
38
|
+
const redirectUri = html.match(/name=['"]redirect_uri['"][^>]*value=['"]([^'"]+)['"]/i)?.[1];
|
|
39
|
+
|
|
40
|
+
if (!state || !clientId || !redirectUri) return null;
|
|
41
|
+
return { state, clientId, redirectUri };
|
|
37
42
|
}
|
|
38
43
|
|
|
39
|
-
// --- GET
|
|
44
|
+
// --- GET helper with manual redirect handling ---
|
|
40
45
|
async _get(url, depth = 0) {
|
|
41
46
|
const cookies = await this.cookieJar.getCookieString(url);
|
|
42
47
|
const resp = await this.http.get(url, {
|
|
@@ -61,7 +66,7 @@ class MelCloudHomeToken extends EventEmitter {
|
|
|
61
66
|
);
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
//
|
|
69
|
+
// Handle redirect
|
|
65
70
|
if (
|
|
66
71
|
resp.status >= 300 &&
|
|
67
72
|
resp.status < 400 &&
|
|
@@ -76,7 +81,7 @@ class MelCloudHomeToken extends EventEmitter {
|
|
|
76
81
|
return resp;
|
|
77
82
|
}
|
|
78
83
|
|
|
79
|
-
// --- POST
|
|
84
|
+
// --- POST helper ---
|
|
80
85
|
async _post(url, data, headers = {}) {
|
|
81
86
|
const cookies = await this.cookieJar.getCookieString(url);
|
|
82
87
|
const resp = await this.http.post(url, data, {
|
|
@@ -103,11 +108,10 @@ class MelCloudHomeToken extends EventEmitter {
|
|
|
103
108
|
return resp;
|
|
104
109
|
}
|
|
105
110
|
|
|
106
|
-
// ---
|
|
107
|
-
async
|
|
111
|
+
// --- MAIN LOGIN FLOW ---
|
|
112
|
+
async login(email, password) {
|
|
108
113
|
const { verifier, challenge } = this._generatePkcePair();
|
|
109
114
|
|
|
110
|
-
// Build authorize URL
|
|
111
115
|
const authUrl =
|
|
112
116
|
`${AUTH_BASE}/oauth2/authorize?` +
|
|
113
117
|
new URLSearchParams({
|
|
@@ -119,7 +123,7 @@ class MelCloudHomeToken extends EventEmitter {
|
|
|
119
123
|
code_challenge_method: 'S256',
|
|
120
124
|
});
|
|
121
125
|
|
|
122
|
-
console.log(
|
|
126
|
+
console.log('[OAuth] Opening login page...');
|
|
123
127
|
let resp = await this._get(authUrl);
|
|
124
128
|
if (resp.status >= 300 && resp.status < 400) {
|
|
125
129
|
const redirectUrl = new URL(resp.headers.location, AUTH_BASE).href;
|
|
@@ -127,47 +131,48 @@ class MelCloudHomeToken extends EventEmitter {
|
|
|
127
131
|
resp = await this._get(redirectUrl);
|
|
128
132
|
}
|
|
129
133
|
|
|
130
|
-
//
|
|
131
|
-
const
|
|
132
|
-
if (!
|
|
133
|
-
console.error('[OAuth] ❌ Cannot find
|
|
134
|
-
console.log(resp.data.substring(0,
|
|
135
|
-
throw new Error('Cannot find
|
|
134
|
+
// Extract login form fields
|
|
135
|
+
const form = this._extractLoginForm(resp.data);
|
|
136
|
+
if (!form) {
|
|
137
|
+
console.error('[OAuth] ❌ Cannot find Cognito login form fields!');
|
|
138
|
+
console.log(resp.data.substring(0, 500));
|
|
139
|
+
throw new Error('Cannot find login form');
|
|
136
140
|
}
|
|
137
141
|
|
|
138
|
-
console.log(`[OAuth] Found
|
|
142
|
+
console.log(`[OAuth] Found form state: ${form.state.substring(0, 12)}...`);
|
|
139
143
|
|
|
140
|
-
// --- Submit login form ---
|
|
141
144
|
const loginUrl = resp.request.res.responseUrl || `${AUTH_BASE}/login`;
|
|
142
145
|
const formData = new URLSearchParams({
|
|
143
|
-
|
|
146
|
+
state: form.state,
|
|
147
|
+
client_id: form.clientId,
|
|
148
|
+
redirect_uri: form.redirectUri,
|
|
144
149
|
username: email,
|
|
145
150
|
password: password,
|
|
146
151
|
});
|
|
147
152
|
|
|
148
|
-
console.log(
|
|
153
|
+
console.log('[OAuth] Submitting Cognito login...');
|
|
149
154
|
const loginResp = await this._post(loginUrl, formData.toString());
|
|
150
155
|
|
|
156
|
+
// Cognito redirects with ?code=
|
|
151
157
|
if (loginResp.status >= 300 && loginResp.status < 400) {
|
|
152
158
|
const redirectUrl = new URL(loginResp.headers.location, AUTH_BASE).href;
|
|
153
159
|
console.log(`[OAuth] Login redirect → ${redirectUrl}`);
|
|
154
160
|
|
|
155
|
-
// Extract code from redirect URL
|
|
156
161
|
const codeMatch = redirectUrl.match(/[?&]code=([^&]+)/);
|
|
157
162
|
if (!codeMatch) throw new Error('No code found after login redirect');
|
|
163
|
+
|
|
158
164
|
const code = codeMatch[1];
|
|
159
165
|
console.log(`[OAuth] ✅ Got authorization code: ${code}`);
|
|
160
166
|
|
|
161
|
-
// Exchange code for token
|
|
162
167
|
return this.exchangeToken(code, verifier);
|
|
163
168
|
}
|
|
164
169
|
|
|
165
|
-
console.error('[OAuth] ❌ Unexpected status
|
|
170
|
+
console.error('[OAuth] ❌ Unexpected login status:', loginResp.status);
|
|
166
171
|
console.log(loginResp.data.substring(0, 400));
|
|
167
|
-
throw new Error('Unexpected
|
|
172
|
+
throw new Error('Unexpected login response');
|
|
168
173
|
}
|
|
169
174
|
|
|
170
|
-
// --- Exchange code for
|
|
175
|
+
// --- Exchange code for token ---
|
|
171
176
|
async exchangeToken(code, verifier) {
|
|
172
177
|
console.log('[OAuth] Exchanging code for token...');
|
|
173
178
|
|
|
@@ -192,12 +197,14 @@ class MelCloudHomeToken extends EventEmitter {
|
|
|
192
197
|
throw new Error('Token exchange failed');
|
|
193
198
|
}
|
|
194
199
|
|
|
195
|
-
console.log('[OAuth] ✅ Token
|
|
200
|
+
console.log('[OAuth] ✅ Token received successfully');
|
|
196
201
|
return resp.data;
|
|
197
202
|
}
|
|
198
203
|
}
|
|
199
204
|
|
|
200
205
|
|
|
206
|
+
|
|
207
|
+
|
|
201
208
|
export default MelCloudHomeToken;
|
|
202
209
|
|
|
203
210
|
|