homebridge-melcloud-control 4.0.0-beta.31 → 4.0.0-beta.310
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/CHANGELOG.md +9 -0
- package/README.md +12 -11
- package/config.schema.json +296 -310
- package/homebridge-ui/public/index.html +103 -51
- package/homebridge-ui/server.js +4 -3
- package/index.js +39 -20
- package/package.json +5 -2
- package/src/constants.js +15 -22
- package/src/deviceata.js +199 -188
- package/src/deviceatw.js +7 -5
- package/src/deviceerv.js +28 -26
- package/src/functions.js +18 -3
- package/src/melcloud.js +264 -84
- package/src/melcloudata.js +139 -308
- package/src/melcloudatw.js +3 -228
- package/src/melclouderv.js +5 -162
- package/src/melcloudhometoken.js +179 -0
package/src/melcloud.js
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
1
|
+
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
3
|
import puppeteer from 'puppeteer';
|
|
4
|
+
import axios from 'axios';
|
|
4
5
|
import EventEmitter from 'events';
|
|
6
|
+
import MelCloudHomeToken from './melcloudhometoken.js';
|
|
5
7
|
import ImpulseGenerator from './impulsegenerator.js';
|
|
6
8
|
import Functions from './functions.js';
|
|
7
9
|
import { ApiUrls, ApiUrlsHome } from './constants.js';
|
|
10
|
+
import { URLSearchParams } from 'url';
|
|
11
|
+
const TOKEN_ENDPOINT = 'https://auth.melcloudhome.com/connect/token';
|
|
12
|
+
const REDIRECT_URI = 'melcloudhome://';
|
|
8
13
|
|
|
9
14
|
class MelCloud extends EventEmitter {
|
|
10
|
-
constructor(user, passwd, language, accountFile, buildingsFile, devicesFile,
|
|
15
|
+
constructor(accountType, user, passwd, language, accountFile, buildingsFile, devicesFile, logWarn, logDebug, requestConfig) {
|
|
11
16
|
super();
|
|
17
|
+
this.accountType = accountType;
|
|
12
18
|
this.user = user;
|
|
13
19
|
this.passwd = passwd;
|
|
14
20
|
this.language = language;
|
|
15
21
|
this.accountFile = accountFile;
|
|
16
22
|
this.buildingsFile = buildingsFile;
|
|
17
23
|
this.devicesFile = devicesFile;
|
|
18
|
-
this.cookiesFile = cookiesFile;
|
|
19
24
|
this.logWarn = logWarn;
|
|
20
25
|
this.logDebug = logDebug;
|
|
21
26
|
this.requestConfig = requestConfig;
|
|
@@ -23,6 +28,12 @@ class MelCloud extends EventEmitter {
|
|
|
23
28
|
this.contextKey = '';
|
|
24
29
|
this.functions = new Functions();
|
|
25
30
|
|
|
31
|
+
this.axiosInstance = axios.create({
|
|
32
|
+
baseURL: 'https://app.melcloud.com/Mitsubishi.Wifi.Client',
|
|
33
|
+
headers: { 'User-Agent': 'MonitorAndControl.App.Mobile/35 CFNetwork/3860.100.1 Darwin/25.0.0' }
|
|
34
|
+
});
|
|
35
|
+
this.tokens = null;
|
|
36
|
+
|
|
26
37
|
this.loginData = {
|
|
27
38
|
Email: user,
|
|
28
39
|
Password: passwd,
|
|
@@ -33,18 +44,15 @@ class MelCloud extends EventEmitter {
|
|
|
33
44
|
Persist: true
|
|
34
45
|
};
|
|
35
46
|
|
|
36
|
-
this.axiosDefaults = {
|
|
37
|
-
timeout: 15000,
|
|
38
|
-
maxContentLength: 100000000,
|
|
39
|
-
maxBodyLength: 1000000000,
|
|
40
|
-
httpsAgent: new Agent({
|
|
41
|
-
keepAlive: false,
|
|
42
|
-
rejectUnauthorized: false
|
|
43
|
-
})
|
|
44
|
-
};
|
|
45
|
-
|
|
46
47
|
if (!requestConfig) {
|
|
47
48
|
this.impulseGenerator = new ImpulseGenerator()
|
|
49
|
+
.on('connect', async () => {
|
|
50
|
+
try {
|
|
51
|
+
await this.connect(true);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
this.emit('error', `Impulse generator error: ${error}`);
|
|
54
|
+
}
|
|
55
|
+
})
|
|
48
56
|
.on('checkDevicesList', async () => {
|
|
49
57
|
try {
|
|
50
58
|
await this.checkDevicesList(this.contextKey);
|
|
@@ -58,13 +66,14 @@ class MelCloud extends EventEmitter {
|
|
|
58
66
|
}
|
|
59
67
|
}
|
|
60
68
|
|
|
61
|
-
|
|
69
|
+
// MELCloud
|
|
70
|
+
async checkMelcloudDevicesList(contextKey) {
|
|
62
71
|
try {
|
|
63
72
|
const axiosInstanceGet = axios.create({
|
|
64
73
|
method: 'GET',
|
|
65
74
|
baseURL: ApiUrls.BaseURL,
|
|
66
|
-
|
|
67
|
-
|
|
75
|
+
timeout: 15000,
|
|
76
|
+
headers: { 'X-MitsContextKey': contextKey }
|
|
68
77
|
});
|
|
69
78
|
|
|
70
79
|
if (this.logDebug) this.emit('debug', `Scanning for devices`);
|
|
@@ -91,6 +100,14 @@ class MelCloud extends EventEmitter {
|
|
|
91
100
|
...buildingStructure.Areas.flatMap(area => area.Devices),
|
|
92
101
|
...buildingStructure.Devices
|
|
93
102
|
];
|
|
103
|
+
|
|
104
|
+
// Zamiana DeviceID na string
|
|
105
|
+
allDevices.forEach(device => {
|
|
106
|
+
if (device.DeviceID !== undefined && device.DeviceID !== null) {
|
|
107
|
+
device.DeviceID = device.DeviceID.toString();
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
94
111
|
devices.push(...allDevices);
|
|
95
112
|
}
|
|
96
113
|
|
|
@@ -109,21 +126,20 @@ class MelCloud extends EventEmitter {
|
|
|
109
126
|
}
|
|
110
127
|
}
|
|
111
128
|
|
|
112
|
-
async
|
|
129
|
+
async connectToMelCloud() {
|
|
113
130
|
if (this.logDebug) this.emit('debug', `Connecting to MELCloud`);
|
|
114
131
|
|
|
115
132
|
try {
|
|
116
133
|
const axiosInstanceLogin = axios.create({
|
|
117
134
|
method: 'POST',
|
|
118
135
|
baseURL: ApiUrls.BaseURL,
|
|
119
|
-
|
|
136
|
+
timeout: 15000,
|
|
120
137
|
});
|
|
121
138
|
|
|
122
139
|
const accountData = await axiosInstanceLogin(ApiUrls.ClientLogin, { data: this.loginData });
|
|
123
140
|
const account = accountData.data;
|
|
124
141
|
const accountInfo = account.LoginData;
|
|
125
142
|
const contextKey = accountInfo?.ContextKey;
|
|
126
|
-
const useFahrenheit = accountInfo?.UseFahrenheit ?? false;
|
|
127
143
|
this.contextKey = contextKey;
|
|
128
144
|
|
|
129
145
|
const debugData = {
|
|
@@ -145,52 +161,33 @@ class MelCloud extends EventEmitter {
|
|
|
145
161
|
this.axiosInstancePost = axios.create({
|
|
146
162
|
method: 'POST',
|
|
147
163
|
baseURL: ApiUrls.BaseURL,
|
|
164
|
+
timeout: 15000,
|
|
148
165
|
headers: {
|
|
149
166
|
'X-MitsContextKey': contextKey,
|
|
150
167
|
'content-type': 'application/json'
|
|
151
|
-
}
|
|
152
|
-
...this.axiosDefaults
|
|
168
|
+
}
|
|
153
169
|
});
|
|
154
170
|
|
|
155
171
|
await this.functions.saveData(this.accountFile, accountInfo);
|
|
156
|
-
|
|
157
172
|
this.emit('success', `Connect to MELCloud Success`);
|
|
158
173
|
|
|
159
|
-
return
|
|
160
|
-
accountInfo,
|
|
161
|
-
contextKey,
|
|
162
|
-
useFahrenheit
|
|
163
|
-
};
|
|
174
|
+
return accountInfo
|
|
164
175
|
} catch (error) {
|
|
165
176
|
throw new Error(`Connect to MELCloud error: ${error.message}`);
|
|
166
177
|
}
|
|
167
178
|
}
|
|
168
179
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
180
|
+
// MELCloud Home
|
|
181
|
+
async checkMelcloudHomeDevicesList(contextKey) {
|
|
172
182
|
try {
|
|
173
|
-
const coocies = await this.getCookies();
|
|
174
|
-
const c1 = coocies.C1.trim();
|
|
175
|
-
const c2 = coocies.C2.trim();
|
|
176
|
-
|
|
177
|
-
const cookies = [
|
|
178
|
-
'__Secure-monitorandcontrol=chunks-2',
|
|
179
|
-
`__Secure-monitorandcontrolC1=${c1}`,
|
|
180
|
-
`__Secure-monitorandcontrolC2=${c2}`,
|
|
181
|
-
].join('; ');
|
|
182
|
-
|
|
183
183
|
const axiosInstance = axios.create({
|
|
184
|
+
method: 'GET',
|
|
184
185
|
baseURL: ApiUrlsHome.BaseURL,
|
|
185
|
-
timeout:
|
|
186
|
-
httpsAgent: new Agent({
|
|
187
|
-
keepAlive: false,
|
|
188
|
-
rejectUnauthorized: false
|
|
189
|
-
}),
|
|
186
|
+
timeout: 25000,
|
|
190
187
|
headers: {
|
|
191
188
|
'Accept': '*/*',
|
|
192
189
|
'Accept-Language': 'en-US,en;q=0.9',
|
|
193
|
-
'Cookie':
|
|
190
|
+
'Cookie': contextKey,
|
|
194
191
|
'User-Agent': 'homebridge-melcloud-control/4.0.0',
|
|
195
192
|
'DNT': '1',
|
|
196
193
|
'Origin': 'https://melcloudhome.com',
|
|
@@ -202,94 +199,277 @@ class MelCloud extends EventEmitter {
|
|
|
202
199
|
}
|
|
203
200
|
});
|
|
204
201
|
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
|
|
202
|
+
if (this.logDebug) this.emit('debug', `Scanning for devices`);
|
|
203
|
+
const listDevicesData = await axiosInstance(ApiUrlsHome.GetUserContext);
|
|
204
|
+
const buildingsList = listDevicesData.data.buildings;
|
|
205
|
+
if (this.logDebug) this.emit('debug', `Buildings: ${JSON.stringify(buildingsList, null, 2)}`);
|
|
206
|
+
|
|
207
|
+
if (!buildingsList) {
|
|
208
|
+
if (this.logWarn) this.emit('warn', `No building found`);
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
await this.functions.saveData(this.buildingsFile, buildingsList);
|
|
213
|
+
if (this.logDebug) this.emit('debug', `Buildings list saved`);
|
|
214
|
+
|
|
215
|
+
const devices = buildingsList.flatMap(building => {
|
|
216
|
+
// Funkcja kapitalizująca klucze obiektu
|
|
217
|
+
const capitalizeKeys = obj =>
|
|
218
|
+
Object.fromEntries(
|
|
219
|
+
Object.entries(obj).map(([key, value]) => [
|
|
220
|
+
key.charAt(0).toUpperCase() + key.slice(1),
|
|
221
|
+
value
|
|
222
|
+
])
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
// Funkcja tworząca finalny obiekt Device
|
|
226
|
+
const createDevice = (device, type, contextKey) => {
|
|
227
|
+
// Settings już kapitalizowane w nazwach
|
|
228
|
+
const settingsArray = device.Settings || [];
|
|
229
|
+
|
|
230
|
+
const settingsObject = Object.fromEntries(
|
|
231
|
+
settingsArray.map(({ name, value }) => {
|
|
232
|
+
let parsedValue = value;
|
|
233
|
+
if (value === "True") parsedValue = true;
|
|
234
|
+
else if (value === "False") parsedValue = false;
|
|
235
|
+
else if (!isNaN(value) && value !== "") parsedValue = Number(value);
|
|
236
|
+
|
|
237
|
+
const key = name.charAt(0).toUpperCase() + name.slice(1);
|
|
238
|
+
return [key, parsedValue];
|
|
239
|
+
})
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
// Scal Capabilities + Settings + DeviceType w Device
|
|
243
|
+
const deviceObject = {
|
|
244
|
+
...capitalizeKeys(device.Capabilities || {}),
|
|
245
|
+
...settingsObject,
|
|
246
|
+
DeviceType: type
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// Usuń stare pola Settings i Capabilities
|
|
250
|
+
const { Settings, Capabilities, Id, GivenDisplayName, ...rest } = device;
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
...rest,
|
|
254
|
+
ContextKey: contextKey,
|
|
255
|
+
Type: type,
|
|
256
|
+
DeviceID: Id,
|
|
257
|
+
DeviceName: GivenDisplayName,
|
|
258
|
+
Device: deviceObject
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
return [
|
|
263
|
+
...(building.airToAirUnits || []).map(d => createDevice(capitalizeKeys(d), 0, this.contextKey)),
|
|
264
|
+
...(building.airToWaterUnits || []).map(d => createDevice(capitalizeKeys(d), 1, this.contextKey)),
|
|
265
|
+
...(building.airToVentilationUnits || []).map(d => createDevice(capitalizeKeys(d), 3, this.contextKey))
|
|
266
|
+
];
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const devicesCount = devices.length;
|
|
270
|
+
if (devicesCount === 0) {
|
|
271
|
+
if (this.logWarn) this.emit('warn', `No devices found`);
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
208
274
|
|
|
209
|
-
this.
|
|
275
|
+
await this.functions.saveData(this.devicesFile, devices);
|
|
276
|
+
if (this.logDebug) this.emit('debug', `${devicesCount} devices saved`);
|
|
210
277
|
|
|
211
|
-
return
|
|
278
|
+
return devices;
|
|
212
279
|
} catch (error) {
|
|
213
280
|
throw new Error(`Connect to MELCloud Home error: ${error.message}`);
|
|
214
281
|
}
|
|
215
282
|
}
|
|
216
283
|
|
|
217
|
-
async
|
|
218
|
-
|
|
219
|
-
|
|
284
|
+
async connectToMelCloudHome() {
|
|
285
|
+
let browser;
|
|
286
|
+
try {
|
|
287
|
+
|
|
288
|
+
const melCloudHomeToken = new MelCloudHomeToken({
|
|
289
|
+
user: this.user,
|
|
290
|
+
passwd: this.passwd,
|
|
291
|
+
logWarn: this.logWarn,
|
|
292
|
+
logError: this.logError,
|
|
293
|
+
})
|
|
294
|
+
.on('success', message => this.emit('success', message))
|
|
295
|
+
.on('warn', warn => this.emit('warn', warn))
|
|
296
|
+
.on('error', error => this.emit('error', error));
|
|
297
|
+
|
|
298
|
+
const data = await melCloudHomeToken.buildAuthorizeUrl();
|
|
299
|
+
const code = await melCloudHomeToken.loginToMelCloudHome(data.url);
|
|
300
|
+
|
|
301
|
+
return false
|
|
302
|
+
|
|
303
|
+
browser = await puppeteer.launch({
|
|
304
|
+
headless: true,
|
|
305
|
+
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const page = await browser.newPage();
|
|
309
|
+
await page.goto(data.url, { waitUntil: 'networkidle2' });
|
|
310
|
+
|
|
311
|
+
await page.waitForSelector('input[name="username"]', { timeout: 5000 });
|
|
312
|
+
await page.type('input[name="username"]', this.user, { delay: 50 });
|
|
313
|
+
await page.type('input[name="password"]', this.passwd, { delay: 50 });
|
|
314
|
+
const button1 = await page.$('input[type="submit"]');
|
|
315
|
+
await Promise.all([button1.click(), page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 10000 })]);
|
|
316
|
+
const html = await page.content();
|
|
317
|
+
const code1 = await melCloudHomeToken.extractCodeFromHtml(html);
|
|
318
|
+
this.emit('warn', code);
|
|
319
|
+
|
|
320
|
+
// 5. Wymień code na access_token
|
|
321
|
+
const tokenResponse = await melCloudHomeToken.getTokens(code, data.codeVerifier);
|
|
322
|
+
const tokenData = await tokenResponse.json();
|
|
323
|
+
this.emit('debug', `Token: ${JSON.stringify(tokenData, null, 2)}`);
|
|
324
|
+
const accountInfo = {
|
|
325
|
+
accessToken: tokenData.access_token,
|
|
326
|
+
refreshToken: tokenData.refresh_token,
|
|
327
|
+
expiresIn: tokenData.expires_in
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
return accountInfo;
|
|
331
|
+
} catch (error) {
|
|
332
|
+
if (browser) await browser.close().catch(() => { });
|
|
333
|
+
throw error;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async connectToMelCloudHome1(refresh = false) {
|
|
338
|
+
if (this.logDebug) this.emit('debug', `Connecting to MELCloud Home`);
|
|
220
339
|
|
|
340
|
+
let browser;
|
|
221
341
|
try {
|
|
222
|
-
|
|
342
|
+
browser = await puppeteer.launch({
|
|
343
|
+
headless: true,
|
|
344
|
+
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
const page = await browser.newPage();
|
|
223
348
|
await page.goto(ApiUrlsHome.BaseURL, { waitUntil: 'networkidle2' });
|
|
224
349
|
|
|
225
|
-
// Kliknij przycisk logowania
|
|
226
350
|
const buttons = await page.$$('button.btn--blue');
|
|
227
351
|
let loginBtn = null;
|
|
352
|
+
|
|
228
353
|
for (const btn of buttons) {
|
|
229
354
|
const text = await page.evaluate(el => el.textContent, btn);
|
|
230
|
-
if (text.trim() === 'Zaloguj') {
|
|
355
|
+
if (text.trim() === 'Zaloguj' || text.trim() === 'Sign In') {
|
|
231
356
|
loginBtn = btn;
|
|
232
357
|
break;
|
|
233
358
|
}
|
|
234
359
|
}
|
|
235
360
|
|
|
236
|
-
if (!loginBtn
|
|
361
|
+
if (!loginBtn && this.logWarn) this.emit('warn', `Login button not found`);
|
|
237
362
|
|
|
238
363
|
await Promise.all([
|
|
239
364
|
loginBtn.click(),
|
|
240
|
-
page.waitForNavigation({ waitUntil: 'networkidle2', timeout:
|
|
365
|
+
page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 10000 })
|
|
241
366
|
]);
|
|
242
367
|
|
|
243
|
-
|
|
244
|
-
await page.waitForSelector('input[name="username"]', { timeout: 15000 });
|
|
245
|
-
|
|
246
|
-
this.emit('warn', 'Typing credentials...');
|
|
368
|
+
await page.waitForSelector('input[name="username"]', { timeout: 5000 });
|
|
247
369
|
await page.type('input[name="username"]', this.user, { delay: 50 });
|
|
248
370
|
await page.type('input[name="password"]', this.passwd, { delay: 50 });
|
|
249
371
|
|
|
250
|
-
// Kliknij przycisk logowania
|
|
251
372
|
const button1 = await page.$('input[type="submit"]');
|
|
252
373
|
await Promise.all([
|
|
253
374
|
button1.click(),
|
|
254
|
-
page.waitForNavigation({ waitUntil: 'networkidle2', timeout:
|
|
375
|
+
page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 10000 })
|
|
255
376
|
]);
|
|
256
377
|
|
|
257
378
|
let c1 = null, c2 = null;
|
|
258
379
|
const start = Date.now();
|
|
259
380
|
|
|
260
|
-
// Loop max 20s, czekamy aż pojawią się cookies
|
|
261
381
|
while ((!c1 || !c2) && Date.now() - start < 20000) {
|
|
262
|
-
const cookies = await page.cookies();
|
|
382
|
+
const cookies = await page.browserContext().cookies();
|
|
263
383
|
c1 = cookies.find(c => c.name === '__Secure-monitorandcontrolC1')?.value || c1;
|
|
264
384
|
c2 = cookies.find(c => c.name === '__Secure-monitorandcontrolC2')?.value || c2;
|
|
265
385
|
if (!c1 || !c2) await new Promise(r => setTimeout(r, 500));
|
|
266
386
|
}
|
|
267
387
|
|
|
388
|
+
|
|
268
389
|
if (!c1 || !c2) {
|
|
269
|
-
|
|
270
|
-
|
|
390
|
+
if (this.logWarn) this.emit('warn', `Cookies C1/C2 missing`);
|
|
391
|
+
return null;
|
|
271
392
|
}
|
|
272
393
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
394
|
+
const contextKey = [
|
|
395
|
+
'__Secure-monitorandcontrol=chunks-2',
|
|
396
|
+
`__Secure-monitorandcontrolC1=${c1}`,
|
|
397
|
+
`__Secure-monitorandcontrolC2=${c2}`
|
|
398
|
+
].join('; ');
|
|
399
|
+
|
|
400
|
+
const accountInfo = { ContextKey: contextKey, UseFahrenheit: false };
|
|
401
|
+
this.contextKey = contextKey;
|
|
402
|
+
|
|
403
|
+
await this.functions.saveData(this.accountFile, accountInfo);
|
|
404
|
+
if (!refresh) this.emit('success', `Connect to MELCloud Home Success`);
|
|
405
|
+
|
|
406
|
+
return accountInfo;
|
|
407
|
+
} catch (error) {
|
|
408
|
+
if (error.message.includes('libnspr4.so') || error.message.includes('Failed to launch the browser process')) {
|
|
409
|
+
const inDocker = await this.functions.isRunningInDocker();
|
|
410
|
+
if (this.logWarn) this.emit('warn', `Missing system libraries detected.`);
|
|
411
|
+
|
|
412
|
+
if (inDocker) {
|
|
413
|
+
this.emit('warn', `Running in Docker — attempting automatic fix...`);
|
|
414
|
+
|
|
415
|
+
const installCmd =
|
|
416
|
+
'apt-get update && apt-get install -y ' +
|
|
417
|
+
'libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 ' +
|
|
418
|
+
'libxcomposite1 libxrandr2 libxdamage1 libxkbcommon0 libpango-1.0-0 ' +
|
|
419
|
+
'libgbm1 libasound2 libxshmfence1 fonts-liberation libappindicator3-1 libu2f-udev';
|
|
420
|
+
|
|
421
|
+
try {
|
|
422
|
+
execSync(installCmd, { stdio: 'inherit' });
|
|
423
|
+
if (this.logDebug) this.emit('debug', `Libraries installed. Retrying Puppeteer...`);
|
|
424
|
+
|
|
425
|
+
const testBrowser = await puppeteer.launch({
|
|
426
|
+
headless: true,
|
|
427
|
+
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
|
428
|
+
});
|
|
429
|
+
await testBrowser.close();
|
|
430
|
+
|
|
431
|
+
this.emit('success', `Puppeteer repaired and running, try again.`);
|
|
432
|
+
return true;
|
|
433
|
+
} catch (fixError) {
|
|
434
|
+
throw new Error(`Automatic fix failed. Run manually inside container:\n${installCmd}`);
|
|
435
|
+
}
|
|
276
436
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
437
|
+
} else {
|
|
438
|
+
throw new Error(`System libraries missing. Non-Docker environment detected — install dependencies manually.`);
|
|
439
|
+
}
|
|
440
|
+
} else {
|
|
441
|
+
throw new Error(`Puppeteer failed: ${error.message}`);
|
|
442
|
+
}
|
|
282
443
|
} finally {
|
|
283
|
-
await browser.close();
|
|
444
|
+
if (browser) await browser.close().catch(() => { });
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async checkDevicesList(contextKey) {
|
|
449
|
+
let devices = [];
|
|
450
|
+
switch (this.accountType) {
|
|
451
|
+
case "melcloud":
|
|
452
|
+
devices = await this.checkMelcloudDevicesList(contextKey);
|
|
453
|
+
return devices
|
|
454
|
+
case "melcloudhome":
|
|
455
|
+
devices = await this.checkMelcloudHomeDevicesList(contextKey);
|
|
456
|
+
return devices;
|
|
457
|
+
default:
|
|
458
|
+
return devices;
|
|
284
459
|
}
|
|
285
460
|
}
|
|
286
461
|
|
|
287
|
-
async connect(
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
462
|
+
async connect(refresh) {
|
|
463
|
+
let response = {};
|
|
464
|
+
switch (this.accountType) {
|
|
465
|
+
case "melcloud":
|
|
466
|
+
response = await this.connectToMelCloud();
|
|
467
|
+
return response
|
|
468
|
+
case "melcloudhome":
|
|
469
|
+
response = await this.connectToMelCloudHome(refresh);
|
|
470
|
+
return response
|
|
291
471
|
default:
|
|
292
|
-
return
|
|
472
|
+
return response
|
|
293
473
|
}
|
|
294
474
|
}
|
|
295
475
|
|