homebridge-melcloud-control 4.3.11-beta.11 → 4.3.11-beta.12
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/melcloudata.js +0 -1
- package/src/melcloudatw.js +0 -1
- package/src/melclouderv.js +0 -1
- package/src/melcloudhome.js +27 -1
- package/src/melcloudhomeauth.js +180 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"displayName": "MELCloud Control",
|
|
3
3
|
"name": "homebridge-melcloud-control",
|
|
4
|
-
"version": "4.3.11-beta.
|
|
4
|
+
"version": "4.3.11-beta.12",
|
|
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/melcloudata.js
CHANGED
|
@@ -339,7 +339,6 @@ class MelCloudAta extends EventEmitter {
|
|
|
339
339
|
return;
|
|
340
340
|
}
|
|
341
341
|
} catch (error) {
|
|
342
|
-
//if (error.response?.status === 500) return true; // Return 500 for schedule hovewer working correct
|
|
343
342
|
throw new Error(`Send data error: ${error.message}`);
|
|
344
343
|
}
|
|
345
344
|
}
|
package/src/melcloudatw.js
CHANGED
|
@@ -311,7 +311,6 @@ class MelCloudAtw extends EventEmitter {
|
|
|
311
311
|
return;
|
|
312
312
|
}
|
|
313
313
|
} catch (error) {
|
|
314
|
-
if (error.response?.status === 500) return true; // Return 500 for schedule hovewer working correct
|
|
315
314
|
throw new Error(`Send data error: ${error.message}`);
|
|
316
315
|
}
|
|
317
316
|
}
|
package/src/melclouderv.js
CHANGED
|
@@ -315,7 +315,6 @@ class MelCloudErv extends EventEmitter {
|
|
|
315
315
|
return;
|
|
316
316
|
}
|
|
317
317
|
} catch (error) {
|
|
318
|
-
if (error.response?.status === 500) return true; // Return 500 for schedule hovewer working correct
|
|
319
318
|
throw new Error(`Send data error: ${error.message}`);
|
|
320
319
|
}
|
|
321
320
|
}
|
package/src/melcloudhome.js
CHANGED
|
@@ -5,6 +5,7 @@ import { exec } from 'child_process';
|
|
|
5
5
|
import { promisify } from 'util';
|
|
6
6
|
import EventEmitter from 'events';
|
|
7
7
|
import puppeteer from 'puppeteer';
|
|
8
|
+
import { MELCloudHomeAuth } from "./melcloudhomeauth.js";
|
|
8
9
|
import ImpulseGenerator from './impulsegenerator.js';
|
|
9
10
|
import Functions from './functions.js';
|
|
10
11
|
import { ApiUrlsHome, LanguageLocaleMap } from './constants.js';
|
|
@@ -267,7 +268,7 @@ class MelCloudHome extends EventEmitter {
|
|
|
267
268
|
}
|
|
268
269
|
}
|
|
269
270
|
|
|
270
|
-
async
|
|
271
|
+
async connect1() {
|
|
271
272
|
if (this.logDebug) this.emit('debug', 'Connecting to MELCloud Home');
|
|
272
273
|
const GLOBAL_TIMEOUT = 90000;
|
|
273
274
|
|
|
@@ -453,6 +454,31 @@ class MelCloudHome extends EventEmitter {
|
|
|
453
454
|
}
|
|
454
455
|
}
|
|
455
456
|
}
|
|
457
|
+
|
|
458
|
+
async connect() {
|
|
459
|
+
if (this.logDebug) this.emit('debug', 'Connecting to MELCloud Home');
|
|
460
|
+
try {
|
|
461
|
+
const auth = new MELCloudHomeAuth();
|
|
462
|
+
|
|
463
|
+
await auth.login(this.user, this.passwd);
|
|
464
|
+
|
|
465
|
+
console.log("Logged in!");
|
|
466
|
+
|
|
467
|
+
const client = auth.getClient();
|
|
468
|
+
|
|
469
|
+
const ctx = await client.get("https://melcloudhome.com/api/user/context", {
|
|
470
|
+
headers: {
|
|
471
|
+
Accept: "application/json",
|
|
472
|
+
"x-csrf": "1",
|
|
473
|
+
referer: "https://melcloudhome.com/dashboard",
|
|
474
|
+
},
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
console.log(ctx.data);
|
|
478
|
+
} catch (error) {
|
|
479
|
+
throw new Error(`Connect error: ${error.message}`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
456
482
|
}
|
|
457
483
|
|
|
458
484
|
export default MelCloudHome;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { wrapper } from "axios-cookiejar-support";
|
|
3
|
+
import { CookieJar } from "tough-cookie";
|
|
4
|
+
import { JSDOM } from "jsdom";
|
|
5
|
+
|
|
6
|
+
const USER_AGENT =
|
|
7
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
|
|
8
|
+
|
|
9
|
+
const BASE_URL = "https://melcloudhome.com";
|
|
10
|
+
const COGNITO_BASE =
|
|
11
|
+
"https://live-melcloudhome.auth.eu-west-1.amazoncognito.com";
|
|
12
|
+
|
|
13
|
+
export class MELCloudHomeAuth {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.jar = new CookieJar();
|
|
16
|
+
this.client = wrapper(
|
|
17
|
+
axios.create({
|
|
18
|
+
jar: this.jar,
|
|
19
|
+
timeout: 30000,
|
|
20
|
+
maxRedirects: 10,
|
|
21
|
+
headers: {
|
|
22
|
+
"User-Agent": USER_AGENT,
|
|
23
|
+
Accept:
|
|
24
|
+
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
|
25
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
26
|
+
"sec-ch-ua":
|
|
27
|
+
'"Chromium";v="124", "Google Chrome";v="124", "Not A(Brand";v="99"',
|
|
28
|
+
"sec-ch-ua-mobile": "?0",
|
|
29
|
+
"sec-ch-ua-platform": '"macOS"',
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
this.authenticated = false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//
|
|
38
|
+
// 1. Start OAuth login → redirected to AWS Cognito login page
|
|
39
|
+
//
|
|
40
|
+
async startLoginFlow() {
|
|
41
|
+
const resp = await this.client.get(`${BASE_URL}/bff/login`, {
|
|
42
|
+
params: { returnUrl: "/dashboard" },
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const finalUrl = resp.request.res.responseUrl;
|
|
46
|
+
|
|
47
|
+
if (!finalUrl.includes("amazoncognito.com/login")) {
|
|
48
|
+
throw new Error(`Unexpected redirect: ${finalUrl}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const html = resp.data;
|
|
52
|
+
const csrf = this.extractCsrf(html);
|
|
53
|
+
|
|
54
|
+
if (!csrf) {
|
|
55
|
+
throw new Error("Failed to extract CSRF token.");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { loginUrl: finalUrl, csrf };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
//
|
|
62
|
+
// 2. Submit credentials to Cognito
|
|
63
|
+
//
|
|
64
|
+
async submitCredentials(loginUrl, csrf, username, password) {
|
|
65
|
+
const payload = new URLSearchParams({
|
|
66
|
+
_csrf: csrf,
|
|
67
|
+
username,
|
|
68
|
+
password,
|
|
69
|
+
cognitoAsfData: "",
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const resp = await this.client.post(loginUrl, payload, {
|
|
73
|
+
headers: {
|
|
74
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
75
|
+
Origin: COGNITO_BASE,
|
|
76
|
+
Referer: loginUrl,
|
|
77
|
+
},
|
|
78
|
+
maxRedirects: 10,
|
|
79
|
+
validateStatus: () => true,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const finalUrl = resp.request.res.responseUrl;
|
|
83
|
+
|
|
84
|
+
if (finalUrl.includes("/error")) {
|
|
85
|
+
throw new Error("Authentication failed: error returned from Cognito");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (finalUrl.includes("amazoncognito.com")) {
|
|
89
|
+
throw new Error("Authentication failed: invalid username or password");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Success: redirected back to melcloudhome.com
|
|
93
|
+
if (finalUrl.includes("melcloudhome.com")) {
|
|
94
|
+
this.authenticated = true;
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
throw new Error(`Unexpected final redirect: ${finalUrl}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
//
|
|
102
|
+
// 3. Validate session – equivalent to Python check_session()
|
|
103
|
+
//
|
|
104
|
+
async checkSession() {
|
|
105
|
+
if (!this.authenticated) return false;
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const resp = await this.client.get(`${BASE_URL}/api/user/context`, {
|
|
109
|
+
headers: {
|
|
110
|
+
Accept: "application/json",
|
|
111
|
+
"x-csrf": "1",
|
|
112
|
+
referer: `${BASE_URL}/dashboard`,
|
|
113
|
+
},
|
|
114
|
+
validateStatus: () => true,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (resp.status === 200) return true;
|
|
118
|
+
if (resp.status === 401) return false;
|
|
119
|
+
|
|
120
|
+
return false;
|
|
121
|
+
} catch {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
//
|
|
127
|
+
// 4. Main login() method
|
|
128
|
+
//
|
|
129
|
+
async login(username, password) {
|
|
130
|
+
const step1 = await this.startLoginFlow();
|
|
131
|
+
|
|
132
|
+
await this.submitCredentials(
|
|
133
|
+
step1.loginUrl,
|
|
134
|
+
step1.csrf,
|
|
135
|
+
username,
|
|
136
|
+
password
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// Wait for Blazor session init – same as Python sleep(3)
|
|
140
|
+
await new Promise((r) => setTimeout(r, 3000));
|
|
141
|
+
|
|
142
|
+
const ok = await this.checkSession();
|
|
143
|
+
if (!ok) throw new Error("Session not valid after login");
|
|
144
|
+
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
//
|
|
149
|
+
// Extract CSRF from Cognito HTML
|
|
150
|
+
//
|
|
151
|
+
extractCsrf(html) {
|
|
152
|
+
const dom = new JSDOM(html);
|
|
153
|
+
const input = dom.window.document.querySelector('input[name="_csrf"]');
|
|
154
|
+
return input?.value || null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
//
|
|
158
|
+
// Return axios instance for authenticated calls
|
|
159
|
+
//
|
|
160
|
+
getClient() {
|
|
161
|
+
if (!this.authenticated) {
|
|
162
|
+
throw new Error("Not authenticated. Call login() first.");
|
|
163
|
+
}
|
|
164
|
+
return this.client;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
//
|
|
168
|
+
// Logout
|
|
169
|
+
//
|
|
170
|
+
async logout() {
|
|
171
|
+
try {
|
|
172
|
+
await this.client.get(`${BASE_URL}/bff/logout`);
|
|
173
|
+
} catch (_) { }
|
|
174
|
+
|
|
175
|
+
this.authenticated = false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export default MELCloudHomeAuth;
|
|
180
|
+
|