homebridge-melcloud-control 4.3.11-beta.2 → 4.3.11-beta.20

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/index.js CHANGED
@@ -105,8 +105,8 @@ class MelCloudPlatform {
105
105
 
106
106
  //connect
107
107
  const accountInfo = await melcloud.connect();
108
- if (!accountInfo.State) {
109
- if (logLevel.warn) log.warn(`${accountName}, ${accountInfo.Info}`);
108
+ if (!accountInfo?.State) {
109
+ if (logLevel.warn) log.warn(`${accountName}, ${accountInfo?.Info}`);
110
110
  return;
111
111
  }
112
112
  if (logLevel.success) log.success(`${accountName}, ${accountInfo.Info}`);
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.2",
4
+ "version": "4.3.11-beta.20",
5
5
  "description": "Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.",
6
6
  "license": "MIT",
7
7
  "author": "grzegorz914",
@@ -40,7 +40,10 @@
40
40
  "axios": "^1.13.2",
41
41
  "express": "^5.2.1",
42
42
  "puppeteer": "^24.32.0",
43
- "ws": "^8.18.3"
43
+ "ws": "^8.18.3",
44
+ "axios-cookiejar-support": "^6.0.5",
45
+ "tough-cookie": "^6.0.0",
46
+ "jsdom": "^27.2.0"
44
47
  },
45
48
  "keywords": [
46
49
  "homebridge",
package/src/constants.js CHANGED
@@ -23,7 +23,7 @@ export const ApiUrlsHome = {
23
23
  GetConfiguration: "https://melcloudhome.com/api/configuration",
24
24
  GetUserContext: "/api/user/context",
25
25
  GetUserScenes: "/api/user/scenes",
26
- PostSchedule: " /api/cloudschedule/deviceid", // POST {"days":[2],"time":"17:59:00","enabled":true,"id":"53c5e804-0663-47d0-85c2-2d8ccd2573de","power":false,"operationMode":null,"setPoint":null,"vaneVerticalDirection":null,"vaneHorizontalDirection":null,"setFanSpeed":null}
26
+ PostSchedule: "/api/cloudschedule/deviceid", // POST {"days":[2],"time":"17:59:00","enabled":true,"id":"53c5e804-0663-47d0-85c2-2d8ccd2573de","power":false,"operationMode":null,"setPoint":null,"vaneVerticalDirection":null,"vaneHorizontalDirection":null,"setFanSpeed":null}
27
27
  PostProtectionFrost: "/api/protection/frost", // POST {"enabled":true,"min":13,"max":16,"units":{"ATA":["ef333525-2699-4290-af5a-2922566676da"]}}
28
28
  PostProtectionOverheat: "/api/protection/overheat", // POST {"enabled":true,"min":32,"max":35,"units":{"ATA":["ef333525-2699-4290-af5a-2922566676da"]}}
29
29
  PostHolidayMode: " /api/holidaymode", // POST {"enabled":true,"startDate":"2025-11-11T17:42:24.913","endDate":"2026-06-01T09:18:00","units":{"ATA":["ef333525-2699-4290-af5a-2922566676da"]}}
@@ -35,6 +35,7 @@ export const ApiUrlsHome = {
35
35
  Enable: "/api/scene/sceneid/enable",
36
36
  Disable: "/api/scene/sceneid/disable",
37
37
  },
38
+ DeleteSchedule: "/api/cloudschedule/deviceid/scheduleid",
38
39
  Referers: {
39
40
  GetPutScenes: "https://melcloudhome.com/scenes",
40
41
  PostHolidayMode: "https://melcloudhome.com/ata/deviceid/holidaymode",
@@ -60,7 +61,7 @@ export const AirConditioner = {
60
61
  SystemMapEnumToString: { 0: "Air Conditioner Off", 1: "Air Conditioner On", 2: "Air Conditioner Offline" },
61
62
  OperationModeMapStringToEnum: { "0": 0, "Heat": 1, "Dry": 2, "Cool": 3, "4": 4, "5": 5, "6": 6, "Fan": 7, "Automatic": 8, "Heat Isee": 9, "Dry Isee": 10, "Cool Isee": 11 },
62
63
  OperationModeMapEnumToString: { 0: "0", 1: "Heat", 2: "Dry", 3: "Cool", 4: "4", 5: "5", 6: "6", 7: "Fan", 8: "Automatic", 9: "Heat Isee", 10: "Dry Isee", 11: "Cool Isee" },
63
- OperationModeMapEnumToEnumWs: { 0: 0, 1: 1, 2: 2, 3: 3, 4: 7, 5: 8, 6: 9, 7: 10, 8: 11 },
64
+ OperationModeMapEnumToEnumWs: { 0: 0, 1: 1, 2: 2, 3: 3, 4: 7, 5: 8 },
64
65
  FanSpeedMapStringToEnum: { "Auto": 0, "One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5, "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5 },
65
66
  FanSpeedMapEnumToString: { 0: "Auto", 1: "One", 2: "Two", 3: "Three", 4: "Four", 5: "Five" },
66
67
  SetFanSpeedMapStringToEnum: { "Auto": 0, "One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5, "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5 },
package/src/deviceata.js CHANGED
@@ -142,7 +142,7 @@ class DeviceAta extends EventEmitter {
142
142
  });
143
143
  }
144
144
  } catch (error) {
145
- this.emit('warn', `RESTFul integration start error: ${error}`);
145
+ if (this.logWarn) this.emit('warn', `RESTFul integration start error: ${error}`);
146
146
  };
147
147
  }
148
148
 
@@ -186,7 +186,7 @@ class DeviceAta extends EventEmitter {
186
186
  });
187
187
  }
188
188
  } catch (error) {
189
- this.emit('warn', `MQTT integration start error: ${error}`);
189
+ if (this.logWarn) this.emit('warn', `MQTT integration start error: ${error}`);
190
190
  };
191
191
  }
192
192
 
@@ -317,6 +317,7 @@ class DeviceAta extends EventEmitter {
317
317
  const supportsOutdoorTemperature = this.accessory.supportsOutdoorTemperature;
318
318
  const numberOfFanSpeeds = this.accessory.numberOfFanSpeeds;
319
319
  const supportsSwingFunction = this.accessory.supportsSwingFunction;
320
+ const supportsVideWane = this.accessory.supportsVideWane;
320
321
  const autoDryFanMode = [this.accessory.operationMode, 8, supportsDry ? 2 : 8, 7][this.autoDryFanMode]; //NONE, AUTO - 8, DRY - 2, FAN - 7
321
322
  const heatDryFanMode = [this.accessory.operationMode, 1, supportsDry ? 2 : 1, 7][this.heatDryFanMode]; //NONE, HEAT - 1, DRY - 2, FAN - 7
322
323
  const coolDryFanMode = [this.accessory.operationMode, 3, supportsDry ? 2 : 3, 7][this.coolDryFanMode]; //NONE, COOL - 3, DRY - 2, FAN - 7
@@ -446,7 +447,7 @@ class DeviceAta extends EventEmitter {
446
447
  })
447
448
  .onSet(async (value) => {
448
449
  try {
449
- deviceData.Device.VaneHorizontalDirection = value ? 12 : 0;
450
+ if (supportsVideWane) deviceData.Device.VaneHorizontalDirection = value ? 12 : 0;
450
451
  deviceData.Device.VaneVerticalDirection = value ? 7 : 0;
451
452
  if (this.logInfo) this.emit('info', `Set air direction mode: ${AirConditioner.AirDirectionMapEnumToString[value]}`);
452
453
  await this.melCloudAta.send(this.accountType, this.displayType, deviceData, AirConditioner.EffectiveFlags.VaneVerticalVaneHorizontal);
@@ -1129,91 +1130,91 @@ class DeviceAta extends EventEmitter {
1129
1130
  case 7: //OPERATING MODE DRY CONTROL HIDE
1130
1131
  deviceData.HideDryModeControl = state;
1131
1132
  break;
1132
- case 10: //VANE H SWING MODE AUTO
1133
+ case 10: //VANE H MODE AUTO
1133
1134
  button.previousValue = state ? deviceData.Device.VaneHorizontalDirection : button.previousValue ?? deviceData.Device.VaneHorizontalDirection;
1134
1135
  deviceData.Device.Power = true;
1135
1136
  deviceData.Device.VaneHorizontalDirection = state ? 0 : button.previousValue;
1136
1137
  flag = AirConditioner.EffectiveFlags.PowerVaneHorizontal;
1137
1138
  break;
1138
- case 11: //VANE H SWING MODE 1
1139
+ case 11: //VANE H MODE 1
1139
1140
  button.previousValue = state ? deviceData.Device.VaneHorizontalDirection : button.previousValue ?? deviceData.Device.VaneHorizontalDirection;
1140
1141
  deviceData.Device.Power = true;
1141
1142
  deviceData.Device.VaneHorizontalDirection = state ? 1 : button.previousValue;
1142
1143
  flag = AirConditioner.EffectiveFlags.PowerVaneHorizontal;
1143
1144
  break;
1144
- case 12: //VANE H SWING MODE 2
1145
+ case 12: //VANE H MODE 2
1145
1146
  button.previousValue = state ? deviceData.Device.VaneHorizontalDirection : button.previousValue ?? deviceData.Device.VaneHorizontalDirection;
1146
1147
  deviceData.Device.Power = true;
1147
1148
  deviceData.Device.VaneHorizontalDirection = state ? 2 : button.previousValue;
1148
1149
  flag = AirConditioner.EffectiveFlags.PowerVaneHorizontal;
1149
1150
  break;
1150
- case 13: //VANE H SWING MODE 3
1151
+ case 13: //VANE H MODE 3
1151
1152
  button.previousValue = state ? deviceData.Device.VaneHorizontalDirection : button.previousValue ?? deviceData.Device.VaneHorizontalDirection;
1152
1153
  deviceData.Device.Power = true;
1153
1154
  deviceData.Device.VaneHorizontalDirection = state ? 3 : button.previousValue;
1154
1155
  flag = AirConditioner.EffectiveFlags.PowerVaneHorizontal;
1155
1156
  break;
1156
- case 14: //VANE H SWING MODE 4
1157
+ case 14: //VANE H MODE 4
1157
1158
  button.previousValue = state ? deviceData.Device.VaneHorizontalDirection : button.previousValue ?? deviceData.Device.VaneHorizontalDirection;
1158
1159
  deviceData.Device.Power = true;
1159
1160
  deviceData.Device.VaneHorizontalDirection = state ? 4 : button.previousValue;
1160
1161
  flag = AirConditioner.EffectiveFlags.PowerVaneHorizontal;
1161
1162
  break;
1162
- case 15: //VANE H SWING MODE 5
1163
+ case 15: //VANE H MODE 5
1163
1164
  button.previousValue = state ? deviceData.Device.VaneHorizontalDirection : button.previousValue ?? deviceData.Device.VaneHorizontalDirection;
1164
1165
  deviceData.Device.Power = true;
1165
1166
  deviceData.Device.VaneHorizontalDirection = state ? 5 : button.previousValue;
1166
1167
  flag = AirConditioner.EffectiveFlags.PowerVaneHorizontal;
1167
1168
  break;
1168
- case 16: //VANE H SWING MODE SPLIT
1169
+ case 16: //VANE H MODE SPLIT
1169
1170
  button.previousValue = state ? deviceData.Device.VaneHorizontalDirection : button.previousValue ?? deviceData.Device.VaneHorizontalDirection;
1170
1171
  deviceData.Device.Power = true;
1171
1172
  deviceData.Device.VaneHorizontalDirection = state ? 8 : button.previousValue;
1172
1173
  flag = AirConditioner.EffectiveFlags.PowerVaneHorizontal;
1173
1174
  break;
1174
- case 17: //VANE H SWING MODE SWING
1175
+ case 17: //VANE H MODE SWING
1175
1176
  button.previousValue = state ? deviceData.Device.VaneHorizontalDirection : button.previousValue ?? deviceData.Device.VaneHorizontalDirection;
1176
1177
  deviceData.Device.Power = true;
1177
1178
  deviceData.Device.VaneHorizontalDirection = state ? 12 : button.previousValue;
1178
1179
  flag = AirConditioner.EffectiveFlags.PowerVaneHorizontal;
1179
1180
  break;
1180
- case 20: //VANE V SWING MODE AUTO
1181
+ case 20: //VANE V MODE AUTO
1181
1182
  button.previousValue = state ? deviceData.Device.VaneVerticalDirection : button.previousValue ?? deviceData.Device.VaneVerticalDirection;
1182
1183
  deviceData.Device.Power = true;
1183
1184
  deviceData.Device.VaneVerticalDirection = state ? 0 : button.previousValue;
1184
1185
  flag = AirConditioner.EffectiveFlags.PowerVaneVertical;
1185
1186
  break;
1186
- case 21: //VANE V SWING MODE 1
1187
+ case 21: //VANE V MODE 1
1187
1188
  button.previousValue = state ? deviceData.Device.VaneVerticalDirection : button.previousValue ?? deviceData.Device.VaneVerticalDirection;
1188
1189
  deviceData.Device.Power = true;
1189
1190
  deviceData.Device.VaneVerticalDirection = state ? 1 : button.previousValue;
1190
1191
  flag = AirConditioner.EffectiveFlags.PowerVaneVertical;
1191
1192
  break;
1192
- case 22: //VANE V SWING MODE 2
1193
+ case 22: //VANE V MODE 2
1193
1194
  button.previousValue = state ? deviceData.Device.VaneVerticalDirection : button.previousValue ?? deviceData.Device.VaneVerticalDirection;
1194
1195
  deviceData.Device.Power = true;
1195
1196
  deviceData.Device.VaneVerticalDirection = state ? 2 : button.previousValue;
1196
1197
  flag = AirConditioner.EffectiveFlags.PowerVaneVertical;
1197
1198
  break;
1198
- case 23: //VANE V SWING MODE 3
1199
+ case 23: //VANE V MODE 3
1199
1200
  button.previousValue = state ? deviceData.Device.VaneVerticalDirection : button.previousValue ?? deviceData.Device.VaneVerticalDirection;
1200
1201
  deviceData.Device.Power = true;
1201
1202
  deviceData.Device.VaneVerticalDirection = state ? 3 : button.previousValue;
1202
1203
  flag = AirConditioner.EffectiveFlags.PowerVaneVertical;
1203
1204
  break;
1204
- case 24: //VANE V SWING MODE 4
1205
+ case 24: //VANE V MODE 4
1205
1206
  button.previousValue = state ? deviceData.Device.VaneVerticalDirection : button.previousValue ?? deviceData.Device.VaneVerticalDirection;
1206
1207
  deviceData.Device.Power = true;
1207
1208
  deviceData.Device.VaneVerticalDirection = state ? 4 : button.previousValue;
1208
1209
  flag = AirConditioner.EffectiveFlags.PowerVaneVertical;
1209
1210
  break;
1210
- case 25: //VANE V SWING MODE 5
1211
+ case 25: //VANE V MODE 5
1211
1212
  button.previousValue = state ? deviceData.Device.VaneVerticalDirection : button.previousValue ?? deviceData.Device.VaneVerticalDirection;
1212
1213
  deviceData.Device.Power = true;
1213
1214
  deviceData.Device.VaneVerticalDirection = state ? 5 : button.previousValue;
1214
1215
  flag = AirConditioner.EffectiveFlags.PowerVaneVertical;
1215
1216
  break;
1216
- case 26: //VANE V SWING MODE SWING
1217
+ case 26: //VANE V MODE SWING
1217
1218
  button.previousValue = state ? deviceData.Device.VaneVerticalDirection : button.previousValue ?? deviceData.Device.VaneVerticalDirection;
1218
1219
  deviceData.Device.Power = true;
1219
1220
  deviceData.Device.VaneVerticalDirection = state ? 7 : button.previousValue;
@@ -1425,6 +1426,7 @@ class DeviceAta extends EventEmitter {
1425
1426
  const outdoorTemperature = deviceData.Device.OutdoorTemperature;
1426
1427
  const isConnected = accountTypeMelcloud ? !deviceData.Device[connectKey] : deviceData.Device[connectKey];
1427
1428
  const isInError = deviceData.Device[errorKey];
1429
+ const currentSwingMode = supportsSwingFunction ? (supportsWideVane ? vaneHorizontalDirection === 12 && vaneVerticalDirection === 7 ? 1 : 0 : vaneVerticalDirection === 7 ? 1 : 0) : 0;
1428
1430
 
1429
1431
  //accessory
1430
1432
  const obj = {
@@ -1461,7 +1463,7 @@ class DeviceAta extends EventEmitter {
1461
1463
  automaticFanSpeed: automaticFanSpeed,
1462
1464
  vaneVerticalSwing: vaneVerticalSwing,
1463
1465
  vaneHorizontalSwing: vaneHorizontalSwing,
1464
- currentSwingMode: supportsSwingFunction && vaneHorizontalDirection === 12 && vaneVerticalDirection === 7 ? 1 : 0,
1466
+ currentSwingMode: currentSwingMode,
1465
1467
  lockPhysicalControl: prohibitSetTemperature && prohibitOperationMode && prohibitPower ? 1 : 0,
1466
1468
  temperatureStep: temperatureStep,
1467
1469
  useFahrenheit: this.accountInfo.useFahrenheit ? 1 : 0,
@@ -1753,49 +1755,49 @@ class DeviceAta extends EventEmitter {
1753
1755
  case 7: //OPERATING MODE DRY CONTROL HIDE
1754
1756
  button.state = power ? (hideDryModeControl === true) : false;
1755
1757
  break;
1756
- case 10: //VANE H SWING MODE AUTO
1758
+ case 10: //VANE H MODE AUTO
1757
1759
  button.state = power ? (vaneHorizontalDirection === 0) : false;
1758
1760
  break;
1759
- case 11: //VANE H SWING MODE 1
1761
+ case 11: //VANE H MODE 1
1760
1762
  button.state = power ? (vaneHorizontalDirection === 1) : false;
1761
1763
  break;
1762
- case 12: //VANE H SWING MODE 2
1764
+ case 12: //VANE H MODE 2
1763
1765
  button.state = power ? (vaneHorizontalDirection === 2) : false;
1764
1766
  break;
1765
- case 13: //VANE H SWING MODE 3
1767
+ case 13: //VANE H MODE 3
1766
1768
  button.state = power ? (vaneHorizontalDirection === 3) : false;
1767
1769
  break;
1768
- case 14: //VANE H SWING MODE 4
1770
+ case 14: //VANE H MODE 4
1769
1771
  button.state = power ? (vaneHorizontalDirection === 4) : false;
1770
1772
  break;
1771
- case 15: //VANE H SWING MODE 5
1773
+ case 15: //VANE H MODE 5
1772
1774
  button.state = power ? (vaneHorizontalDirection === 5) : false;
1773
1775
  break;
1774
- case 16: //VANE H SWING MODE SPLIT
1776
+ case 16: //VANE H MODE SPLIT
1775
1777
  button.state = power ? (vaneHorizontalDirection === 8) : false;
1776
1778
  break;
1777
- case 17: //VANE H SWING MODE SWING
1779
+ case 17: //VANE H MODE SWING
1778
1780
  button.state = power ? (vaneHorizontalDirection === 12) : false;
1779
1781
  break;
1780
- case 20: //VANE V SWING MODE AUTO
1782
+ case 20: //VANE V MODE AUTO
1781
1783
  button.state = power ? (vaneVerticalDirection === 0) : false;
1782
1784
  break;
1783
- case 21: //VANE V SWING MODE 1
1785
+ case 21: //VANE V MODE 1
1784
1786
  button.state = power ? (vaneVerticalDirection === 1) : false;
1785
1787
  break;
1786
- case 22: //VANE V SWING MODE 2
1788
+ case 22: //VANE V MODE 2
1787
1789
  button.state = power ? (vaneVerticalDirection === 2) : false;
1788
1790
  break;
1789
- case 23: //VANE V SWING MODE 3
1791
+ case 23: //VANE V MODE 3
1790
1792
  button.state = power ? (vaneVerticalDirection === 3) : false;
1791
1793
  break;
1792
- case 24: //VANE V SWING MODE 4
1794
+ case 24: //VANE V MODE 4
1793
1795
  button.state = power ? (vaneVerticalDirection === 4) : false;
1794
1796
  break;
1795
- case 25: //VANE V SWING MODE 5
1797
+ case 25: //VANE V MODE 5
1796
1798
  button.state = power ? (vaneVerticalDirection === 5) : false;
1797
1799
  break;
1798
- case 26: //VANE V SWING MODE SWING
1800
+ case 26: //VANE V MODE SWING
1799
1801
  button.state = power ? (vaneVerticalDirection === 7) : false;
1800
1802
  break;
1801
1803
  case 27: //VANE H/V CONTROLS HIDE
@@ -1859,7 +1861,7 @@ class DeviceAta extends EventEmitter {
1859
1861
  if (supportsFanSpeed) this.emit('info', `Current fan speed: ${AirConditioner.AktualFanSpeedMapEnumToString[actualFanSpeed]}`);
1860
1862
  if (vaneHorizontalDirection !== null) this.emit('info', `Vane horizontal: ${AirConditioner.VaneHorizontalDirectionMapEnumToString[vaneHorizontalDirection]}`);
1861
1863
  if (vaneVerticalDirection !== null) this.emit('info', `Vane vertical: ${AirConditioner.VaneVerticalDirectionMapEnumToString[vaneVerticalDirection]}`);
1862
- if (supportsSwingFunction) this.emit('info', `Air direction: ${AirConditioner.AirDirectionMapEnumToString[obj.currentSwingMode]}`);
1864
+ if (supportsSwingFunction) this.emit('info', `Air direction: ${AirConditioner.AirDirectionMapEnumToString[currentSwingMode]}`);
1863
1865
  this.emit('info', `Temperature display unit: ${obj.temperatureUnit}`);
1864
1866
  this.emit('info', `Lock physical controls: ${obj.lockPhysicalControl ? 'Locked' : 'Unlocked'}`);
1865
1867
  if (this.accountType === 'melcloudhome') this.emit('info', `Signal strength: ${deviceData.Rssi}dBm`);
package/src/deviceatw.js CHANGED
@@ -146,7 +146,7 @@ class DeviceAtw extends EventEmitter {
146
146
  });
147
147
  }
148
148
  } catch (error) {
149
- this.emit('warn', `RESTFul integration start error: ${error}`);
149
+ if (this.logWarn) this.emit('warn', `RESTFul integration start error: ${error}`);
150
150
  };
151
151
  }
152
152
 
@@ -190,7 +190,7 @@ class DeviceAtw extends EventEmitter {
190
190
  });
191
191
  }
192
192
  } catch (error) {
193
- this.emit('warn', `MQTT integration start error: ${error}`);
193
+ if (this.logWarn) this.emit('warn', `MQTT integration start error: ${error}`);
194
194
  };
195
195
  }
196
196
  }
package/src/deviceerv.js CHANGED
@@ -138,7 +138,7 @@ class DeviceErv extends EventEmitter {
138
138
  });
139
139
  }
140
140
  } catch (error) {
141
- this.emit('warn', `RESTFul integration start error: ${error}`);
141
+ if (this.logWarn) this.emit('warn', `RESTFul integration start error: ${error}`);
142
142
  };
143
143
  }
144
144
 
@@ -182,7 +182,7 @@ class DeviceErv extends EventEmitter {
182
182
  });
183
183
  }
184
184
  } catch (error) {
185
- this.emit('warn', `MQTT integration start error: ${error}`);
185
+ if (this.logWarn) this.emit('warn', `MQTT integration start error: ${error}`);
186
186
  };
187
187
  }
188
188
  }
package/src/functions.js CHANGED
@@ -58,30 +58,30 @@ class Functions extends EventEmitter {
58
58
  let chromiumPath = '/usr/bin/chromium-browser';
59
59
 
60
60
  try {
61
- // --- Detect OS ---
61
+ // Detect OS
62
62
  const { stdout: osOut } = await execPromise('uname -s');
63
63
  const osName = osOut.trim();
64
64
  if (this.logDebug) this.emit('debug', `Detected OS: ${osName}`);
65
65
 
66
- // --- Detect Architecture ---
66
+ // Detect Architecture
67
67
  const { stdout: archOut } = await execPromise('uname -m');
68
68
  const arch = archOut.trim();
69
69
  if (this.logDebug) this.emit('debug', `Detected architecture: ${arch}`);
70
70
 
71
- // --- Detect Docker ---
71
+ // Docker detection
72
72
  let isDocker = false;
73
73
  try {
74
- await access('/.dockerenv', fs.constants.F_OK); isDocker = true;
74
+ await access('/.dockerenv', fs.constants.F_OK);
75
+ isDocker = true;
75
76
  } catch { }
76
77
 
77
78
  try {
78
79
  const { stdout } = await execPromise('cat /proc/1/cgroup || true');
79
80
  if (stdout.includes('docker') || stdout.includes('containerd')) isDocker = true;
80
81
  } catch { }
82
+ if (isDocker && this.logDebug) this.emit('debug', 'Running inside Docker container');
81
83
 
82
- if (isDocker && this.logDebug) this.emit('debug', 'Running inside Docker container.');
83
-
84
- // === macOS ===
84
+ // macOS
85
85
  if (osName === 'Darwin') {
86
86
  chromiumPath = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
87
87
  try {
@@ -92,10 +92,9 @@ class Functions extends EventEmitter {
92
92
  }
93
93
  }
94
94
 
95
- // === ARM ===
96
- if (arch.startsWith('arm')) {
95
+ // ARM
96
+ if (arch.startsWith('arm') || arch.startsWith('aarch')) {
97
97
  try {
98
- chromiumPath = '/usr/bin/chromium-browser';
99
98
  await access(chromiumPath, fs.constants.X_OK);
100
99
  return chromiumPath;
101
100
  } catch {
@@ -108,7 +107,7 @@ class Functions extends EventEmitter {
108
107
  }
109
108
  }
110
109
 
111
- // === Linux x64 ===
110
+ // Linux x64
112
111
  if (osName === 'Linux') {
113
112
  let systemChromium = null;
114
113
  try {
@@ -116,7 +115,7 @@ class Functions extends EventEmitter {
116
115
  systemChromium = checkOut.trim() || null;
117
116
  } catch { }
118
117
 
119
- // --- Detect Entware (QNAP) ---
118
+ // Entware (QNAP)
120
119
  let entwareExists = false;
121
120
  try {
122
121
  await access('/opt/bin/opkg', fs.constants.X_OK);
@@ -131,24 +130,24 @@ class Functions extends EventEmitter {
131
130
  } catch { }
132
131
  }
133
132
 
134
- // --- Generic Linux installs missing libs for Puppeteer ---
135
- const depInstall = [
133
+ // Install missing libs
134
+ const depCommands = [
136
135
  'apt-get update -y && apt-get install -y libnspr4 libnss3 libx11-6 libxcomposite1 libxdamage1 libxrandr2 libatk1.0-0 libcups2 libdrm2 libgbm1 libasound2',
137
136
  'apk add --no-cache nspr nss libx11 libxcomposite libxdamage libxrandr atk cups libdrm libgbm alsa-lib',
138
137
  'yum install -y nspr nss libX11 libXcomposite libXdamage libXrandr atk cups libdrm libgbm alsa-lib'
139
138
  ];
140
- for (const cmd of depInstall) {
139
+
140
+ for (const cmd of depCommands) {
141
141
  try {
142
142
  await execPromise(`sudo ${cmd}`);
143
143
  } catch { }
144
144
  }
145
145
 
146
- // Set LD_LIBRARY_PATH so Puppeteer's Chromium can find libs
147
146
  process.env.LD_LIBRARY_PATH = `/usr/lib:/usr/lib64:${process.env.LD_LIBRARY_PATH || ''}`;
148
147
  return systemChromium;
149
148
  }
150
149
 
151
- if (this.logDebug) this.emit('debug', `Unsupported OS: ${osName}.`);
150
+ if (this.logDebug) this.emit('debug', `Unsupported OS: ${osName}`);
152
151
  return null;
153
152
  } catch (error) {
154
153
  if (this.logError) this.emit('error', `Chromium detection/install error: ${error.message}`);
package/src/melcloud.js CHANGED
@@ -109,7 +109,6 @@ class MelCloud extends EventEmitter {
109
109
  devicesList.State = true;
110
110
  devicesList.Info = `Found ${devicesCount} devices`;
111
111
  devicesList.Devices = devices;
112
- devicesList.Headers = this.headers;
113
112
  this.emit('devicesList', devicesList);
114
113
 
115
114
  return devicesList;
@@ -164,6 +163,7 @@ class MelCloud extends EventEmitter {
164
163
  'Content-Type': 'application/json'
165
164
  };
166
165
  this.emit('headers', headers);
166
+
167
167
  this.headers = headers;
168
168
  this.axiosInstance = axios.create({
169
169
  baseURL: ApiUrls.BaseURL,
@@ -24,7 +24,7 @@ class MelCloudAta extends EventEmitter {
24
24
 
25
25
  //set default values
26
26
  this.deviceData = {};
27
- this.headers = {};
27
+ this.headers = melcloud.headers;
28
28
 
29
29
  //handle melcloud events
30
30
  let deviceData = null;
@@ -148,9 +148,6 @@ class MelCloudAta extends EventEmitter {
148
148
  return acc;
149
149
  }, { indoor: {}, outdoor: {} });
150
150
 
151
- //display info if units are not configured in MELCloud service
152
- if (this.accountType === 'melcloud' && unitsCount === 0 && this.logDebug) if (this.logDebug) this.emit('debug', `Units are not configured in MELCloud service`);
153
-
154
151
  //filter info
155
152
  const { Device: _ignored, ...info } = deviceData;
156
153
 
@@ -185,7 +182,6 @@ class MelCloudAta extends EventEmitter {
185
182
 
186
183
  async checkState(devicesData) {
187
184
  try {
188
- this.headers = devicesData.Headers;
189
185
  const deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
190
186
  deviceData.Scenes = devicesData.Scenes ?? [];
191
187
  await this.updateState('request', deviceData);
@@ -238,7 +234,7 @@ class MelCloudAta extends EventEmitter {
238
234
  break;
239
235
  }
240
236
 
241
- if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
237
+ if (this.logDebug) this.emit('debug', `Send data: ${JSON.stringify(payload, null, 2)}`);
242
238
 
243
239
  await axios(path, {
244
240
  method: 'POST',
@@ -328,7 +324,7 @@ class MelCloudAta extends EventEmitter {
328
324
  //sens payload
329
325
  headers['Content-Type'] = 'application/json; charset=utf-8';
330
326
  headers.Origin = ApiUrlsHome.Origin;
331
- if (!this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
327
+ if (!this.logDebug) this.emit('debug', `Send data: ${JSON.stringify(payload, null, 2)}`);
332
328
 
333
329
  await axios(path, {
334
330
  method: method,
@@ -343,7 +339,6 @@ class MelCloudAta extends EventEmitter {
343
339
  return;
344
340
  }
345
341
  } catch (error) {
346
- if (error.response?.status === 500) return true; // Return 500 for schedule hovewer working correct
347
342
  throw new Error(`Send data error: ${error.message}`);
348
343
  }
349
344
  }
@@ -24,7 +24,7 @@ class MelCloudAtw extends EventEmitter {
24
24
 
25
25
  //set default values
26
26
  this.deviceData = {};
27
- this.headers = {};
27
+ this.headers = melcloud.headers;
28
28
 
29
29
  //handle melcloud events
30
30
  let deviceData = null;
@@ -135,9 +135,6 @@ class MelCloudAtw extends EventEmitter {
135
135
  return acc;
136
136
  }, { indoor: {}, outdoor: {} });
137
137
 
138
- //display info if units are not configured in MELCloud service
139
- if (this.accountType === 'melcloud' && unitsCount === 0 && this.logDebug) if (this.logDebug) this.emit('debug', `Units are not configured in MELCloud service`);
140
-
141
138
  //filter info
142
139
  const { Device: _ignored, ...info } = deviceData;
143
140
 
@@ -172,7 +169,6 @@ class MelCloudAtw extends EventEmitter {
172
169
 
173
170
  async checkState(devicesData) {
174
171
  try {
175
- this.headers = devicesData.Headers;
176
172
  const deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
177
173
  deviceData.Scenes = devicesData.Scenes ?? [];
178
174
  await this.updateState('request', deviceData);
@@ -240,7 +236,7 @@ class MelCloudAtw extends EventEmitter {
240
236
  break;
241
237
  }
242
238
 
243
- if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
239
+ if (this.logDebug) this.emit('debug', `Send data: ${JSON.stringify(payload, null, 2)}`);
244
240
 
245
241
  await axios(path, {
246
242
  method: 'POST',
@@ -300,7 +296,7 @@ class MelCloudAtw extends EventEmitter {
300
296
 
301
297
  headers['Content-Type'] = 'application/json; charset=utf-8';
302
298
  headers.Origin = ApiUrlsHome.Origin;
303
- if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
299
+ if (this.logDebug) this.emit('debug', `Send data: ${JSON.stringify(payload, null, 2)}`);
304
300
 
305
301
  await axios(path, {
306
302
  method: method,
@@ -315,7 +311,6 @@ class MelCloudAtw extends EventEmitter {
315
311
  return;
316
312
  }
317
313
  } catch (error) {
318
- if (error.response?.status === 500) return true; // Return 500 for schedule hovewer working correct
319
314
  throw new Error(`Send data error: ${error.message}`);
320
315
  }
321
316
  }
@@ -24,7 +24,7 @@ class MelCloudErv extends EventEmitter {
24
24
 
25
25
  //set default values
26
26
  this.deviceData = {};
27
- this.headers = {};
27
+ this.headers = melcloud.headers;
28
28
 
29
29
  //handle melcloud events
30
30
  let deviceData = null;
@@ -134,9 +134,6 @@ class MelCloudErv extends EventEmitter {
134
134
  return acc;
135
135
  }, { indoor: {}, outdoor: {} });
136
136
 
137
- //display info if units are not configured in MELCloud service
138
- if (this.accountType === 'melcloud' && unitsCount === 0 && this.logDebug) if (this.logDebug) this.emit('debug', `Units are not configured in MELCloud service`);
139
-
140
137
  //filter info
141
138
  const { Device: _ignored, ...info } = deviceData;
142
139
 
@@ -171,7 +168,6 @@ class MelCloudErv extends EventEmitter {
171
168
 
172
169
  async checkState(devicesData) {
173
170
  try {
174
- this.headers = devicesData.Headers;
175
171
  const deviceData = devicesData.Devices.find(device => device.DeviceID === this.deviceId);
176
172
  deviceData.Scenes = devicesData.Scenes ?? [];
177
173
  await this.updateState('request', deviceData);
@@ -240,7 +236,7 @@ class MelCloudErv extends EventEmitter {
240
236
  break;
241
237
  }
242
238
 
243
- if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
239
+ if (this.logDebug) this.emit('debug', `Send data: ${JSON.stringify(payload, null, 2)}`);
244
240
 
245
241
  await axios(path, {
246
242
  method: 'POST',
@@ -304,7 +300,7 @@ class MelCloudErv extends EventEmitter {
304
300
 
305
301
  headers['Content-Type'] = 'application/json; charset=utf-8';
306
302
  headers.Origin = ApiUrlsHome.Origin;
307
- if (this.logDebug) this.emit('debug', `Send Data: ${JSON.stringify(payload, null, 2)}`);
303
+ if (this.logDebug) this.emit('debug', `Send data: ${JSON.stringify(payload, null, 2)}`);
308
304
 
309
305
  await axios(path, {
310
306
  method: method,
@@ -319,7 +315,6 @@ class MelCloudErv extends EventEmitter {
319
315
  return;
320
316
  }
321
317
  } catch (error) {
322
- if (error.response?.status === 500) return true; // Return 500 for schedule hovewer working correct
323
318
  throw new Error(`Send data error: ${error.message}`);
324
319
  }
325
320
  }
@@ -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';
@@ -202,8 +203,11 @@ class MelCloudHome extends EventEmitter {
202
203
  // Get scenes
203
204
  let scenes = [];
204
205
  try {
205
- scenes = await this.checkScenesList();
206
- if (this.logDebug) this.emit('debug', `Found ${scenes.length} scenes`);
206
+ const scenesList = await this.checkScenesList();
207
+ if (this.logDebug) this.emit('debug', `Found ${scenesList.length} scenes`);
208
+ if (scenesList.length > 0) {
209
+ scenes = scenesList;
210
+ }
207
211
  } catch (error) {
208
212
  if (this.logError) this.emit('error', `Get scenes error: ${error}`);
209
213
  }
@@ -256,7 +260,6 @@ class MelCloudHome extends EventEmitter {
256
260
  devicesList.Info = `Found ${devicesCount} devices ${scenes.length > 0 ? `and ${scenes.length} scenes` : ''}`;
257
261
  devicesList.Devices = devices;
258
262
  devicesList.Scenes = scenes;
259
- devicesList.Headers = this.headers;
260
263
  this.emit('devicesList', devicesList);
261
264
 
262
265
  return devicesList;
@@ -265,26 +268,30 @@ class MelCloudHome extends EventEmitter {
265
268
  }
266
269
  }
267
270
 
268
- async connect() {
271
+ async connect1() {
269
272
  if (this.logDebug) this.emit('debug', 'Connecting to MELCloud Home');
270
273
  const GLOBAL_TIMEOUT = 90000;
271
274
 
272
275
  let browser;
273
276
  try {
274
277
  const accountInfo = { State: false, Info: '', Account: {}, UseFahrenheit: false };
278
+
279
+ // Get Chromium path
275
280
  let chromiumPath = await this.functions.ensureChromiumInstalled();
276
281
 
277
282
  // === Fallback to Puppeteer's built-in Chromium ===
278
283
  if (!chromiumPath) {
279
- try {
280
- const puppeteerPath = puppeteer.executablePath();
281
- if (puppeteerPath && fs.existsSync(puppeteerPath)) {
282
- chromiumPath = puppeteerPath;
283
- if (this.logDebug) this.emit('debug', `Using puppeteer Chromium at ${chromiumPath}`);
284
- }
285
- } catch { }
286
- } else {
287
- if (this.logDebug) this.emit('debug', `Using system Chromium at ${chromiumPath}`);
284
+ if (!arch.startsWith('arm')) {
285
+ try {
286
+ const puppeteerPath = puppeteer.executablePath();
287
+ if (puppeteerPath && fs.existsSync(puppeteerPath)) {
288
+ chromiumPath = puppeteerPath;
289
+ if (this.logDebug) this.emit('debug', `Using Puppeteer Chromium at ${chromiumPath}`);
290
+ }
291
+ } catch { }
292
+ } else {
293
+ if (this.logDebug) this.emit('debug', 'Skipping Puppeteer Chromium on ARM (incompatible)');
294
+ }
288
295
  }
289
296
 
290
297
  if (!chromiumPath) {
@@ -301,6 +308,7 @@ class MelCloudHome extends EventEmitter {
301
308
  return accountInfo;
302
309
  }
303
310
 
311
+ // Launch Chromium
304
312
  if (this.logDebug) this.emit('debug', `Launching Chromium...`);
305
313
  browser = await puppeteer.launch({
306
314
  headless: true,
@@ -334,8 +342,8 @@ class MelCloudHome extends EventEmitter {
334
342
  hash = params.get('hash');
335
343
  if (this.logDebug) this.emit('debug', `MelCloudHome WS hash detected: ${hash}`);
336
344
  }
337
- } catch (err) {
338
- this.emit('error', `CDP WebSocketCreated handler error: ${err.message}`);
345
+ } catch (error) {
346
+ if (this.logError) this.emit('error', `CDP WebSocketCreated handler error: ${error.message}`);
339
347
  }
340
348
  });
341
349
 
@@ -446,6 +454,29 @@ class MelCloudHome extends EventEmitter {
446
454
  }
447
455
  }
448
456
  }
457
+
458
+ async connect() {
459
+ if (this.logDebug) this.emit('debug', 'Connecting to MELCloud Home');
460
+ const client = new MELCloudHomeAuth({ debug: true });
461
+ try {
462
+ await client.login(this.user, this.passwd);
463
+ console.log("Logged in!");
464
+
465
+ const ctx = await client.getUserContext();
466
+ console.log("User context:", JSON.stringify(ctx, null, 2));
467
+
468
+ const devices = await client.getDevices();
469
+ console.log("Devices:", JSON.stringify(devices, null, 2));
470
+
471
+ // If you need a token-like object:
472
+ const tokens = await client.getAuthTokenCandidates();
473
+ console.log("Token candidates:", tokens);
474
+ } catch (err) {
475
+ console.error("Error:", err.message || err);
476
+ } finally {
477
+ await client.logout();
478
+ }
479
+ }
449
480
  }
450
481
 
451
482
  export default MelCloudHome;
@@ -0,0 +1,228 @@
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
+ // Constants
7
+ const USER_AGENT =
8
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
9
+
10
+ const BASE_URL = "https://melcloudhome.com";
11
+ const COGNITO_BASE =
12
+ "https://live-melcloudhome.auth.eu-west-1.amazoncognito.com";
13
+
14
+ class MELCloudHomeAuth {
15
+ constructor({ debug = false } = {}) {
16
+ this.debug = debug;
17
+ this.jar = new CookieJar();
18
+ this.axios = wrapper(
19
+ axios.create({
20
+ jar: this.jar,
21
+ withCredentials: true,
22
+ maxRedirects: 10,
23
+ headers: {
24
+ "User-Agent": USER_AGENT,
25
+ Accept:
26
+ "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
27
+ "Accept-Language": "en-US,en;q=0.9",
28
+ },
29
+ })
30
+ );
31
+ this.authenticated = false;
32
+ }
33
+
34
+ log(...args) {
35
+ if (this.debug) console.log("[MelcloudHomeClient]", ...args);
36
+ }
37
+
38
+ _delay(ms) {
39
+ return new Promise((r) => setTimeout(r, ms));
40
+ }
41
+
42
+ // --- Helper: generate cognitoAsfData (simplified) ---
43
+ generateAsfData() {
44
+ const data = {
45
+ deviceName: "Chrome",
46
+ osName: "Macintosh",
47
+ osVersion: "14_5",
48
+ timezoneOffset: new Date().getTimezoneOffset(),
49
+ userAgent: USER_AGENT,
50
+ screen: { width: 1440, height: 900 },
51
+ language: "en-US",
52
+ plugins: [],
53
+ fonts: [],
54
+ canvasFingerprint: "simplified", // placeholder
55
+ };
56
+ const json = JSON.stringify(data);
57
+ return Buffer.from(json).toString("base64");
58
+ }
59
+
60
+ // --- Extract CSRF token from HTML ---
61
+ extractCsrf(html) {
62
+ if (!html) return null;
63
+ try {
64
+ const dom = new JSDOM(html);
65
+ const el = dom.window.document.querySelector('input[name="_csrf"]');
66
+ if (el && el.value) return el.value;
67
+ } catch {}
68
+ const m = html.match(/<input[^>]+name="_csrf"[^>]+value="([^"]+)"/i);
69
+ if (m) return m[1];
70
+ return null;
71
+ }
72
+
73
+ findCognitoLoginUrl(html) {
74
+ if (!html) return null;
75
+ const m = html.match(/action="([^"]*amazoncognito\.com[^"]*)"/i);
76
+ if (m) return m[1];
77
+ return null;
78
+ }
79
+
80
+ extractErrorMessage(html) {
81
+ if (!html) return null;
82
+ const patterns = [
83
+ /<div[^>]*class="[^"]*error[^"]*"[^>]*>([^<]+)<\/div>/i,
84
+ /<span[^>]*class="[^"]*error[^"]*"[^>]*>([^<]+)<\/span>/i,
85
+ /<p[^>]*class="[^"]*error[^"]*"[^>]*>([^<]+)<\/p>/i,
86
+ ];
87
+ for (const p of patterns) {
88
+ const m = html.match(p);
89
+ if (m) return m[1].trim();
90
+ }
91
+ return null;
92
+ }
93
+
94
+ async startLoginFlow() {
95
+ this.log("Starting login flow: GET /bff/login");
96
+ const resp = await this.axios.get(`${BASE_URL}/bff/login`, {
97
+ params: { returnUrl: "/dashboard" },
98
+ validateStatus: () => true,
99
+ });
100
+
101
+ const finalUrl =
102
+ resp.request?.res?.responseUrl || resp.config?.url || "";
103
+
104
+ this.log("Received final redirect URL:", finalUrl);
105
+
106
+ const csrf = this.extractCsrf(resp.data);
107
+ if (!csrf) throw new Error("Failed to extract CSRF token");
108
+
109
+ const loginUrl = this.findCognitoLoginUrl(resp.data) || finalUrl;
110
+ return { loginUrl, csrf };
111
+ }
112
+
113
+ async submitCredentials(loginUrl, csrf, username, password) {
114
+ this.log("Submitting credentials to Cognito...");
115
+
116
+ const payload = new URLSearchParams({
117
+ _csrf: csrf,
118
+ username,
119
+ password,
120
+ cognitoAsfData: this.generateAsfData(),
121
+ });
122
+
123
+ // Step 1: POST credentials
124
+ const resp = await this.axios.post(loginUrl, payload.toString(), {
125
+ headers: {
126
+ "Content-Type": "application/x-www-form-urlencoded",
127
+ Origin: COGNITO_BASE,
128
+ Referer: loginUrl,
129
+ },
130
+ validateStatus: () => true,
131
+ maxRedirects: 0,
132
+ });
133
+
134
+ const redirectUrl = resp.headers.location || resp.request?.res?.responseUrl;
135
+ if (!redirectUrl.includes("signin-oidc")) {
136
+ throw new Error(
137
+ "Authentication failed: signin-oidc callback not received"
138
+ );
139
+ }
140
+
141
+ this.log("Calling signin-oidc callback:", redirectUrl);
142
+
143
+ // Step 2: GET callback to set cookies
144
+ const callback = await this.axios.get(redirectUrl, {
145
+ headers: {
146
+ Referer: loginUrl,
147
+ "User-Agent": USER_AGENT,
148
+ Accept: "text/html",
149
+ },
150
+ validateStatus: () => true,
151
+ });
152
+
153
+ this.log("signin-oidc status:", callback.status);
154
+
155
+ // Check if cookie meu.identity is set
156
+ const cookies = await this.jar.getCookies(BASE_URL);
157
+ const hasIdentity = cookies.some((c) => c.key.includes("meu.identity"));
158
+ if (!hasIdentity) {
159
+ throw new Error(
160
+ "OpenID callback did not produce session cookie (cognitoAsfData missing or rejected)"
161
+ );
162
+ }
163
+
164
+ this.authenticated = true;
165
+ this.log("Login successful, session cookie meu.identity is present");
166
+ await this._delay(1000); // short wait for Blazor initialization
167
+ return true;
168
+ }
169
+
170
+ async login(username, password) {
171
+ const { loginUrl, csrf } = await this.startLoginFlow();
172
+ await this.submitCredentials(loginUrl, csrf, username, password);
173
+
174
+ const valid = await this.checkSession();
175
+ if (!valid) throw new Error("Session not valid after login");
176
+
177
+ this.log("Login flow complete");
178
+ return true;
179
+ }
180
+
181
+ async checkSession() {
182
+ if (!this.authenticated) return false;
183
+ try {
184
+ const resp = await this.axios.get(`${BASE_URL}/api/user/context`, {
185
+ headers: {
186
+ Accept: "application/json",
187
+ "x-csrf": "1",
188
+ Referer: `${BASE_URL}/dashboard`,
189
+ },
190
+ validateStatus: () => true,
191
+ });
192
+ return resp.status === 200;
193
+ } catch {
194
+ return false;
195
+ }
196
+ }
197
+
198
+ async getUserContext() {
199
+ if (!this.authenticated) throw new Error("Not authenticated");
200
+ const resp = await this.axios.get(`${BASE_URL}/api/user/context`, {
201
+ headers: { Accept: "application/json", "x-csrf": "1", Referer: `${BASE_URL}/dashboard` },
202
+ });
203
+ return resp.data;
204
+ }
205
+
206
+ async getDevices() {
207
+ if (!this.authenticated) throw new Error("Not authenticated");
208
+ const resp = await this.axios.get(`${BASE_URL}/api/devices/devices`, {
209
+ headers: { Accept: "application/json", "x-csrf": "1", Referer: `${BASE_URL}/dashboard` },
210
+ validateStatus: () => true,
211
+ });
212
+ if (resp.status !== 200) throw new Error(`Failed to get devices: ${resp.status}`);
213
+ return resp.data;
214
+ }
215
+
216
+ async logout() {
217
+ try {
218
+ await this.axios.get(`${BASE_URL}/bff/logout`, { validateStatus: () => true });
219
+ } finally {
220
+ this.authenticated = false;
221
+ }
222
+ }
223
+ }
224
+
225
+
226
+ export default MELCloudHomeAuth;
227
+
228
+