@riddix/hamh 2.0.53 → 2.0.54

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.
@@ -133321,12 +133321,93 @@ function accessLogger(logger253) {
133321
133321
  }
133322
133322
 
133323
133323
  // src/api/backup-api.ts
133324
- import fs from "node:fs";
133325
- import path from "node:path";
133324
+ import fs2 from "node:fs";
133325
+ import path2 from "node:path";
133326
133326
  import archiver from "archiver";
133327
133327
  import express from "express";
133328
133328
  import multer from "multer";
133329
133329
  import unzipper from "unzipper";
133330
+
133331
+ // src/plugins/plugin-storage.ts
133332
+ init_esm();
133333
+ import * as fs from "node:fs";
133334
+ import * as path from "node:path";
133335
+ var logger177 = Logger.get("PluginStorage");
133336
+ var SAVE_DEBOUNCE_MS = 500;
133337
+ var safe = (s) => s.replace(/[^a-zA-Z0-9_-]/g, "_");
133338
+ function pluginStorageFilePath(storageDir, bridgeId, pluginName) {
133339
+ return path.join(
133340
+ storageDir,
133341
+ `plugin-${safe(bridgeId)}-${safe(pluginName)}.json`
133342
+ );
133343
+ }
133344
+ function pluginStateFilePath(storageDir, bridgeId) {
133345
+ return path.join(storageDir, `plugin-state-${safe(bridgeId)}.json`);
133346
+ }
133347
+ var FilePluginStorage = class {
133348
+ data = {};
133349
+ dirty = false;
133350
+ filePath;
133351
+ saveTimer;
133352
+ constructor(storageDir, bridgeId, pluginName) {
133353
+ this.filePath = pluginStorageFilePath(storageDir, bridgeId, pluginName);
133354
+ this.load();
133355
+ }
133356
+ async get(key, defaultValue) {
133357
+ const value = this.data[key];
133358
+ return value ?? defaultValue;
133359
+ }
133360
+ async set(key, value) {
133361
+ this.data[key] = value;
133362
+ this.dirty = true;
133363
+ this.scheduleSave();
133364
+ }
133365
+ async delete(key) {
133366
+ delete this.data[key];
133367
+ this.dirty = true;
133368
+ this.scheduleSave();
133369
+ }
133370
+ async keys() {
133371
+ return Object.keys(this.data);
133372
+ }
133373
+ load() {
133374
+ try {
133375
+ if (fs.existsSync(this.filePath)) {
133376
+ const raw = fs.readFileSync(this.filePath, "utf-8");
133377
+ this.data = JSON.parse(raw);
133378
+ }
133379
+ } catch (e) {
133380
+ logger177.warn(`Failed to load plugin storage from ${this.filePath}:`, e);
133381
+ this.data = {};
133382
+ }
133383
+ }
133384
+ scheduleSave() {
133385
+ if (this.saveTimer) clearTimeout(this.saveTimer);
133386
+ this.saveTimer = setTimeout(() => this.save(), SAVE_DEBOUNCE_MS);
133387
+ }
133388
+ save() {
133389
+ if (!this.dirty) return;
133390
+ if (this.saveTimer) {
133391
+ clearTimeout(this.saveTimer);
133392
+ this.saveTimer = void 0;
133393
+ }
133394
+ try {
133395
+ const dir = path.dirname(this.filePath);
133396
+ if (!fs.existsSync(dir)) {
133397
+ fs.mkdirSync(dir, { recursive: true });
133398
+ }
133399
+ fs.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2));
133400
+ this.dirty = false;
133401
+ } catch (e) {
133402
+ logger177.warn(`Failed to save plugin storage to ${this.filePath}:`, e);
133403
+ }
133404
+ }
133405
+ flush() {
133406
+ this.save();
133407
+ }
133408
+ };
133409
+
133410
+ // src/api/backup-api.ts
133330
133411
  var upload = multer({ storage: multer.memoryStorage() });
133331
133412
  function backupApi(bridgeStorage, mappingStorage, storageLocation, backupService, settingsStorage, _bridgeService) {
133332
133413
  const router = express.Router();
@@ -133342,9 +133423,9 @@ function backupApi(bridgeStorage, mappingStorage, storageLocation, backupService
133342
133423
  }
133343
133424
  }
133344
133425
  let includesIcons = false;
133345
- const iconsDir = path.join(storageLocation, "bridge-icons");
133346
- if (includeIdentity && fs.existsSync(iconsDir)) {
133347
- const iconFiles = fs.readdirSync(iconsDir);
133426
+ const iconsDir = path2.join(storageLocation, "bridge-icons");
133427
+ if (includeIdentity && fs2.existsSync(iconsDir)) {
133428
+ const iconFiles = fs2.readdirSync(iconsDir);
133348
133429
  includesIcons = iconFiles.some((iconFile) => {
133349
133430
  const bridgeId = iconFile.split(".")[0];
133350
133431
  return bridges.some((b) => b.id === bridgeId);
@@ -133381,19 +133462,25 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
133381
133462
  `,
133382
133463
  { name: "README.txt" }
133383
133464
  );
133465
+ for (const bridge of bridges) {
133466
+ const pluginState = pluginStateFilePath(storageLocation, bridge.id);
133467
+ if (fs2.existsSync(pluginState)) {
133468
+ archive.file(pluginState, { name: `plugin-state/${bridge.id}.json` });
133469
+ }
133470
+ }
133384
133471
  if (includeIdentity) {
133385
133472
  for (const bridge of bridges) {
133386
- const bridgeStoragePath = path.join(storageLocation, bridge.id);
133387
- if (fs.existsSync(bridgeStoragePath)) {
133473
+ const bridgeStoragePath = path2.join(storageLocation, bridge.id);
133474
+ if (fs2.existsSync(bridgeStoragePath)) {
133388
133475
  archive.directory(bridgeStoragePath, `identity/${bridge.id}`);
133389
133476
  }
133390
133477
  }
133391
133478
  if (includesIcons) {
133392
- const iconFiles = fs.readdirSync(iconsDir);
133479
+ const iconFiles = fs2.readdirSync(iconsDir);
133393
133480
  for (const iconFile of iconFiles) {
133394
133481
  const bridgeId = iconFile.split(".")[0];
133395
133482
  if (bridges.some((b) => b.id === bridgeId)) {
133396
- const iconPath = path.join(iconsDir, iconFile);
133483
+ const iconPath = path2.join(iconsDir, iconFile);
133397
133484
  archive.file(iconPath, { name: `bridge-icons/${iconFile}` });
133398
133485
  }
133399
133486
  }
@@ -133542,6 +133629,7 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
133542
133629
  iconsRestored++;
133543
133630
  }
133544
133631
  }
133632
+ await restorePluginState(zipDirectory, bridge.id, storageLocation);
133545
133633
  } catch (e) {
133546
133634
  errors.push({
133547
133635
  bridgeId: bridge.id,
@@ -133600,7 +133688,7 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
133600
133688
  "Content-Disposition",
133601
133689
  `attachment; filename="${req.params.filename}"`
133602
133690
  );
133603
- const stream = fs.createReadStream(filepath);
133691
+ const stream = fs2.createReadStream(filepath);
133604
133692
  stream.pipe(res);
133605
133693
  } catch (error) {
133606
133694
  const message = error instanceof Error ? error.message : "Failed to download backup";
@@ -133614,7 +133702,7 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
133614
133702
  res.status(404).json({ error: "Backup not found" });
133615
133703
  return;
133616
133704
  }
133617
- const buffer = fs.readFileSync(filepath);
133705
+ const buffer = fs2.readFileSync(filepath);
133618
133706
  const options = req.body || {};
133619
133707
  const { backupData, zipDirectory } = await extractBackupData(buffer);
133620
133708
  const existingIds = new Set(bridgeStorage.bridges.map((b) => b.id));
@@ -133709,6 +133797,7 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
133709
133797
  iconsRestored++;
133710
133798
  }
133711
133799
  }
133800
+ await restorePluginState(zipDirectory, bridge.id, storageLocation);
133712
133801
  } catch (e) {
133713
133802
  errors.push({
133714
133803
  bridgeId: bridge.id,
@@ -133776,12 +133865,12 @@ async function extractBackupData(buffer) {
133776
133865
  return { backupData: data, zipDirectory: directory };
133777
133866
  }
133778
133867
  function resolveWithin(baseDir, relative) {
133779
- if (relative.length === 0 || path.isAbsolute(relative)) {
133868
+ if (relative.length === 0 || path2.isAbsolute(relative)) {
133780
133869
  return null;
133781
133870
  }
133782
- const resolvedBase = path.resolve(baseDir);
133783
- const resolvedTarget = path.resolve(resolvedBase, relative);
133784
- if (resolvedTarget !== resolvedBase && !resolvedTarget.startsWith(resolvedBase + path.sep)) {
133871
+ const resolvedBase = path2.resolve(baseDir);
133872
+ const resolvedTarget = path2.resolve(resolvedBase, relative);
133873
+ if (resolvedTarget !== resolvedBase && !resolvedTarget.startsWith(resolvedBase + path2.sep)) {
133785
133874
  return null;
133786
133875
  }
133787
133876
  return resolvedTarget;
@@ -133794,8 +133883,8 @@ async function restoreIdentityFiles(zipDirectory, bridgeId, storageLocation) {
133794
133883
  if (identityFiles.length === 0) {
133795
133884
  return false;
133796
133885
  }
133797
- const targetDir = path.join(storageLocation, bridgeId);
133798
- fs.mkdirSync(targetDir, { recursive: true });
133886
+ const targetDir = path2.join(storageLocation, bridgeId);
133887
+ fs2.mkdirSync(targetDir, { recursive: true });
133799
133888
  for (const file of identityFiles) {
133800
133889
  const relativePath = file.path.substring(identityPrefix.length);
133801
133890
  const targetPath = resolveWithin(targetDir, relativePath);
@@ -133804,11 +133893,22 @@ async function restoreIdentityFiles(zipDirectory, bridgeId, storageLocation) {
133804
133893
  `Refusing to restore identity file with unsafe path: ${file.path}`
133805
133894
  );
133806
133895
  }
133807
- const targetDirPath = path.dirname(targetPath);
133808
- fs.mkdirSync(targetDirPath, { recursive: true });
133896
+ const targetDirPath = path2.dirname(targetPath);
133897
+ fs2.mkdirSync(targetDirPath, { recursive: true });
133809
133898
  const content = await file.buffer();
133810
- fs.writeFileSync(targetPath, content);
133899
+ fs2.writeFileSync(targetPath, content);
133900
+ }
133901
+ return true;
133902
+ }
133903
+ async function restorePluginState(zipDirectory, bridgeId, storageLocation) {
133904
+ const entry = zipDirectory.files.find(
133905
+ (f) => f.path === `plugin-state/${bridgeId}.json` && f.type === "File"
133906
+ );
133907
+ if (!entry) {
133908
+ return false;
133811
133909
  }
133910
+ const content = await entry.buffer();
133911
+ fs2.writeFileSync(pluginStateFilePath(storageLocation, bridgeId), content);
133812
133912
  return true;
133813
133913
  }
133814
133914
  async function restoreBridgeIcon(zipDirectory, bridgeId, storageLocation) {
@@ -133819,8 +133919,8 @@ async function restoreBridgeIcon(zipDirectory, bridgeId, storageLocation) {
133819
133919
  if (iconFiles.length === 0) {
133820
133920
  return false;
133821
133921
  }
133822
- const iconsDir = path.join(storageLocation, "bridge-icons");
133823
- fs.mkdirSync(iconsDir, { recursive: true });
133922
+ const iconsDir = path2.join(storageLocation, "bridge-icons");
133923
+ fs2.mkdirSync(iconsDir, { recursive: true });
133824
133924
  for (const file of iconFiles) {
133825
133925
  const fileName = file.path.substring(iconPrefix.length);
133826
133926
  const targetPath = resolveWithin(iconsDir, fileName);
@@ -133830,7 +133930,7 @@ async function restoreBridgeIcon(zipDirectory, bridgeId, storageLocation) {
133830
133930
  );
133831
133931
  }
133832
133932
  const content = await file.buffer();
133833
- fs.writeFileSync(targetPath, content);
133933
+ fs2.writeFileSync(targetPath, content);
133834
133934
  }
133835
133935
  return true;
133836
133936
  }
@@ -133839,7 +133939,7 @@ async function restoreBridgeIcon(zipDirectory, bridgeId, storageLocation) {
133839
133939
  init_dist();
133840
133940
  init_esm();
133841
133941
  import express2 from "express";
133842
- var logger177 = Logger.get("BridgeExportApi");
133942
+ var logger178 = Logger.get("BridgeExportApi");
133843
133943
  function migrateFilter(legacyFilter) {
133844
133944
  if (!legacyFilter) {
133845
133945
  return { include: [], exclude: [] };
@@ -133990,7 +134090,7 @@ function bridgeExportApi(bridgeStorage) {
133990
134090
  res.json(result);
133991
134091
  } catch (e) {
133992
134092
  const message = e instanceof Error ? e.message : String(e);
133993
- logger177.warn(`Failed to import bridges: ${message}`, e);
134093
+ logger178.warn(`Failed to import bridges: ${message}`, e);
133994
134094
  res.status(400).json({ error: `Failed to import bridges: ${message}` });
133995
134095
  }
133996
134096
  });
@@ -133998,16 +134098,16 @@ function bridgeExportApi(bridgeStorage) {
133998
134098
  }
133999
134099
 
134000
134100
  // src/api/bridge-icon-api.ts
134001
- import fs2 from "node:fs";
134002
- import path2 from "node:path";
134101
+ import fs3 from "node:fs";
134102
+ import path3 from "node:path";
134003
134103
  import express3 from "express";
134004
134104
  import multer2 from "multer";
134005
134105
  var ALLOWED_EXTENSIONS = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"];
134006
134106
  var MAX_FILE_SIZE = 5 * 1024 * 1024;
134007
134107
  function bridgeIconApi(storageLocation) {
134008
- const iconsDir = path2.join(storageLocation, "bridge-icons");
134009
- if (!fs2.existsSync(iconsDir)) {
134010
- fs2.mkdirSync(iconsDir, { recursive: true });
134108
+ const iconsDir = path3.join(storageLocation, "bridge-icons");
134109
+ if (!fs3.existsSync(iconsDir)) {
134110
+ fs3.mkdirSync(iconsDir, { recursive: true });
134011
134111
  }
134012
134112
  const storage2 = multer2.diskStorage({
134013
134113
  destination: (_req, _file, cb) => {
@@ -134015,12 +134115,12 @@ function bridgeIconApi(storageLocation) {
134015
134115
  },
134016
134116
  filename: (req, file, cb) => {
134017
134117
  const bridgeId = req.params.bridgeId;
134018
- const ext = path2.extname(file.originalname).toLowerCase();
134118
+ const ext = path3.extname(file.originalname).toLowerCase();
134019
134119
  cb(null, `${bridgeId}${ext}`);
134020
134120
  }
134021
134121
  });
134022
134122
  const fileFilter = (_req, file, cb) => {
134023
- const ext = path2.extname(file.originalname).toLowerCase();
134123
+ const ext = path3.extname(file.originalname).toLowerCase();
134024
134124
  if (ALLOWED_EXTENSIONS.includes(ext)) {
134025
134125
  cb(null, true);
134026
134126
  } else {
@@ -134053,40 +134153,40 @@ function bridgeIconApi(storageLocation) {
134053
134153
  "/:bridgeId/exists",
134054
134154
  (req, res) => {
134055
134155
  const bridgeId = req.params.bridgeId;
134056
- const files = fs2.readdirSync(iconsDir);
134156
+ const files = fs3.readdirSync(iconsDir);
134057
134157
  const exists = files.some((f) => f.startsWith(`${bridgeId}.`));
134058
134158
  res.json({ exists });
134059
134159
  }
134060
134160
  );
134061
134161
  router.get("/:bridgeId", (req, res) => {
134062
134162
  const bridgeId = req.params.bridgeId;
134063
- const files = fs2.readdirSync(iconsDir);
134163
+ const files = fs3.readdirSync(iconsDir);
134064
134164
  const iconFile = files.find((f) => f.startsWith(`${bridgeId}.`));
134065
134165
  if (!iconFile) {
134066
134166
  res.status(404).json({ error: "Icon not found" });
134067
134167
  return;
134068
134168
  }
134069
- const filePath = path2.join(iconsDir, iconFile);
134169
+ const filePath = path3.join(iconsDir, iconFile);
134070
134170
  res.sendFile(filePath);
134071
134171
  });
134072
134172
  router.delete("/:bridgeId", (req, res) => {
134073
134173
  const bridgeId = req.params.bridgeId;
134074
- const files = fs2.readdirSync(iconsDir);
134174
+ const files = fs3.readdirSync(iconsDir);
134075
134175
  const iconFile = files.find((f) => f.startsWith(`${bridgeId}.`));
134076
134176
  if (!iconFile) {
134077
134177
  res.status(404).json({ error: "Icon not found" });
134078
134178
  return;
134079
134179
  }
134080
- const filePath = path2.join(iconsDir, iconFile);
134081
- fs2.unlinkSync(filePath);
134180
+ const filePath = path3.join(iconsDir, iconFile);
134181
+ fs3.unlinkSync(filePath);
134082
134182
  res.json({ success: true });
134083
134183
  });
134084
134184
  return router;
134085
134185
  }
134086
134186
 
134087
134187
  // src/api/device-image-api.ts
134088
- import fs3 from "node:fs";
134089
- import path3 from "node:path";
134188
+ import fs4 from "node:fs";
134189
+ import path4 from "node:path";
134090
134190
  import express4 from "express";
134091
134191
  import multer3 from "multer";
134092
134192
  var ALLOWED_EXTENSIONS2 = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"];
@@ -134097,10 +134197,10 @@ function sanitizeEntityId(entityId) {
134097
134197
  }
134098
134198
  function findCustomImage(imagesDir, entityId) {
134099
134199
  const sanitized = sanitizeEntityId(entityId);
134100
- if (!fs3.existsSync(imagesDir)) return void 0;
134101
- const files = fs3.readdirSync(imagesDir);
134102
- const imageFile = files.find((f) => path3.parse(f).name === sanitized);
134103
- return imageFile ? path3.join(imagesDir, imageFile) : void 0;
134200
+ if (!fs4.existsSync(imagesDir)) return void 0;
134201
+ const files = fs4.readdirSync(imagesDir);
134202
+ const imageFile = files.find((f) => path4.parse(f).name === sanitized);
134203
+ return imageFile ? path4.join(imagesDir, imageFile) : void 0;
134104
134204
  }
134105
134205
  function resolveZ2mImageUrl(haRegistry, entityId) {
134106
134206
  const entity = haRegistry.entities[entityId];
@@ -134110,9 +134210,9 @@ function resolveZ2mImageUrl(haRegistry, entityId) {
134110
134210
  return `${Z2M_IMAGE_BASE}/${encodeURIComponent(device.model)}.png`;
134111
134211
  }
134112
134212
  function deviceImageApi(storageLocation, haRegistry) {
134113
- const imagesDir = path3.join(storageLocation, "device-images");
134114
- if (!fs3.existsSync(imagesDir)) {
134115
- fs3.mkdirSync(imagesDir, { recursive: true });
134213
+ const imagesDir = path4.join(storageLocation, "device-images");
134214
+ if (!fs4.existsSync(imagesDir)) {
134215
+ fs4.mkdirSync(imagesDir, { recursive: true });
134116
134216
  }
134117
134217
  const storage2 = multer3.diskStorage({
134118
134218
  destination: (_req, _file, cb) => {
@@ -134120,12 +134220,12 @@ function deviceImageApi(storageLocation, haRegistry) {
134120
134220
  },
134121
134221
  filename: (req, file, cb) => {
134122
134222
  const entityId = sanitizeEntityId(req.params.entityId);
134123
- const ext = path3.extname(file.originalname).toLowerCase();
134223
+ const ext = path4.extname(file.originalname).toLowerCase();
134124
134224
  cb(null, `${entityId}${ext}`);
134125
134225
  }
134126
134226
  });
134127
134227
  const fileFilter = (_req, file, cb) => {
134128
- const ext = path3.extname(file.originalname).toLowerCase();
134228
+ const ext = path4.extname(file.originalname).toLowerCase();
134129
134229
  if (ALLOWED_EXTENSIONS2.includes(ext)) {
134130
134230
  cb(null, true);
134131
134231
  } else {
@@ -134173,10 +134273,10 @@ function deviceImageApi(storageLocation, haRegistry) {
134173
134273
  return;
134174
134274
  }
134175
134275
  const sanitized = sanitizeEntityId(req.params.entityId);
134176
- const files = fs3.readdirSync(imagesDir);
134276
+ const files = fs4.readdirSync(imagesDir);
134177
134277
  for (const f of files) {
134178
- if (path3.parse(f).name === sanitized && f !== req.file.filename) {
134179
- fs3.unlinkSync(path3.join(imagesDir, f));
134278
+ if (path4.parse(f).name === sanitized && f !== req.file.filename) {
134279
+ fs4.unlinkSync(path4.join(imagesDir, f));
134180
134280
  }
134181
134281
  }
134182
134282
  res.json({ success: true });
@@ -134203,7 +134303,7 @@ function deviceImageApi(storageLocation, haRegistry) {
134203
134303
  res.status(404).json({ error: "No custom image found" });
134204
134304
  return;
134205
134305
  }
134206
- fs3.unlinkSync(customImage);
134306
+ fs4.unlinkSync(customImage);
134207
134307
  res.json({ success: true });
134208
134308
  });
134209
134309
  router.head("/:entityId", (req, res) => {
@@ -136598,26 +136698,26 @@ var BUILTIN_PLUGIN_NAMES = ["camera", "security"];
136598
136698
  // src/plugins/plugin-installer.ts
136599
136699
  init_esm();
136600
136700
  import { execFile } from "node:child_process";
136601
- import * as fs4 from "node:fs";
136602
- import * as path4 from "node:path";
136603
- var logger178 = Logger.get("PluginInstaller");
136701
+ import * as fs5 from "node:fs";
136702
+ import * as path5 from "node:path";
136703
+ var logger179 = Logger.get("PluginInstaller");
136604
136704
  var VALID_PACKAGE_RE = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*(@[^@\s]+)?$/;
136605
136705
  var PluginInstaller = class {
136606
136706
  pluginDir;
136607
136707
  constructor(storageLocation) {
136608
- this.pluginDir = path4.join(storageLocation, "plugin-packages");
136708
+ this.pluginDir = path5.join(storageLocation, "plugin-packages");
136609
136709
  this.ensurePluginDir();
136610
136710
  }
136611
136711
  get installDir() {
136612
136712
  return this.pluginDir;
136613
136713
  }
136614
136714
  ensurePluginDir() {
136615
- if (!fs4.existsSync(this.pluginDir)) {
136616
- fs4.mkdirSync(this.pluginDir, { recursive: true });
136715
+ if (!fs5.existsSync(this.pluginDir)) {
136716
+ fs5.mkdirSync(this.pluginDir, { recursive: true });
136617
136717
  }
136618
- const pkgJson = path4.join(this.pluginDir, "package.json");
136619
- if (!fs4.existsSync(pkgJson)) {
136620
- fs4.writeFileSync(
136718
+ const pkgJson = path5.join(this.pluginDir, "package.json");
136719
+ if (!fs5.existsSync(pkgJson)) {
136720
+ fs5.writeFileSync(
136621
136721
  pkgJson,
136622
136722
  JSON.stringify(
136623
136723
  {
@@ -136640,7 +136740,7 @@ var PluginInstaller = class {
136640
136740
  error: `Invalid package name: "${packageName}"`
136641
136741
  };
136642
136742
  }
136643
- logger178.info(`Installing plugin: ${packageName}`);
136743
+ logger179.info(`Installing plugin: ${packageName}`);
136644
136744
  return new Promise((resolve11) => {
136645
136745
  execFile(
136646
136746
  "npm",
@@ -136652,7 +136752,7 @@ var PluginInstaller = class {
136652
136752
  },
136653
136753
  (error, _stdout, stderr) => {
136654
136754
  if (error) {
136655
- logger178.error(
136755
+ logger179.error(
136656
136756
  `Failed to install ${packageName}:`,
136657
136757
  stderr || error.message
136658
136758
  );
@@ -136664,7 +136764,7 @@ var PluginInstaller = class {
136664
136764
  return;
136665
136765
  }
136666
136766
  const version2 = this.getInstalledVersion(packageName);
136667
- logger178.info(`Installed ${packageName}@${version2 || "unknown"}`);
136767
+ logger179.info(`Installed ${packageName}@${version2 || "unknown"}`);
136668
136768
  resolve11({
136669
136769
  success: true,
136670
136770
  packageName,
@@ -136682,7 +136782,7 @@ var PluginInstaller = class {
136682
136782
  error: `Invalid package name: "${packageName}"`
136683
136783
  };
136684
136784
  }
136685
- logger178.info(`Uninstalling plugin: ${packageName}`);
136785
+ logger179.info(`Uninstalling plugin: ${packageName}`);
136686
136786
  return new Promise((resolve11) => {
136687
136787
  execFile(
136688
136788
  "npm",
@@ -136693,7 +136793,7 @@ var PluginInstaller = class {
136693
136793
  },
136694
136794
  (error, _stdout, stderr) => {
136695
136795
  if (error) {
136696
- logger178.error(
136796
+ logger179.error(
136697
136797
  `Failed to uninstall ${packageName}:`,
136698
136798
  stderr || error.message
136699
136799
  );
@@ -136704,7 +136804,7 @@ var PluginInstaller = class {
136704
136804
  });
136705
136805
  return;
136706
136806
  }
136707
- logger178.info(`Uninstalled ${packageName}`);
136807
+ logger179.info(`Uninstalled ${packageName}`);
136708
136808
  resolve11({ success: true, packageName });
136709
136809
  }
136710
136810
  );
@@ -136715,16 +136815,16 @@ var PluginInstaller = class {
136715
136815
  * This is used by PluginManager.loadExternal() to import the plugin.
136716
136816
  */
136717
136817
  getPluginPath(packageName) {
136718
- return path4.join(this.pluginDir, "node_modules", packageName);
136818
+ return path5.join(this.pluginDir, "node_modules", packageName);
136719
136819
  }
136720
136820
  /**
136721
136821
  * List all installed plugin packages from the plugin directory's package.json.
136722
136822
  */
136723
136823
  listInstalled() {
136724
136824
  try {
136725
- const pkgJson = path4.join(this.pluginDir, "package.json");
136726
- if (!fs4.existsSync(pkgJson)) return [];
136727
- const pkg = JSON.parse(fs4.readFileSync(pkgJson, "utf-8"));
136825
+ const pkgJson = path5.join(this.pluginDir, "package.json");
136826
+ if (!fs5.existsSync(pkgJson)) return [];
136827
+ const pkg = JSON.parse(fs5.readFileSync(pkgJson, "utf-8"));
136728
136828
  const deps = pkg.dependencies ?? {};
136729
136829
  return Object.entries(deps).map(([name, ver]) => ({
136730
136830
  name,
@@ -136735,9 +136835,9 @@ var PluginInstaller = class {
136735
136835
  }
136736
136836
  }
136737
136837
  async installFromTgz(tgzBuffer) {
136738
- const tgzPath = path4.join(this.pluginDir, `.upload-${Date.now()}.tgz`);
136838
+ const tgzPath = path5.join(this.pluginDir, `.upload-${Date.now()}.tgz`);
136739
136839
  try {
136740
- fs4.writeFileSync(tgzPath, tgzBuffer);
136840
+ fs5.writeFileSync(tgzPath, tgzBuffer);
136741
136841
  const depsBefore = new Set(Object.keys(this.readDeps()));
136742
136842
  const result = await this.installFromNpm(tgzPath);
136743
136843
  if (!result.success) return result;
@@ -136754,11 +136854,11 @@ var PluginInstaller = class {
136754
136854
  return result;
136755
136855
  } catch (e) {
136756
136856
  const msg = e instanceof Error ? e.message : String(e);
136757
- logger178.error("Failed to install from tgz:", msg);
136857
+ logger179.error("Failed to install from tgz:", msg);
136758
136858
  return { success: false, packageName: "unknown", error: msg };
136759
136859
  } finally {
136760
136860
  try {
136761
- if (fs4.existsSync(tgzPath)) fs4.unlinkSync(tgzPath);
136861
+ if (fs5.existsSync(tgzPath)) fs5.unlinkSync(tgzPath);
136762
136862
  } catch {
136763
136863
  }
136764
136864
  }
@@ -136777,14 +136877,14 @@ var PluginInstaller = class {
136777
136877
  if (error) {
136778
136878
  resolve11({
136779
136879
  success: false,
136780
- packageName: path4.basename(target),
136880
+ packageName: path5.basename(target),
136781
136881
  error: stderr || error.message
136782
136882
  });
136783
136883
  return;
136784
136884
  }
136785
136885
  resolve11({
136786
136886
  success: true,
136787
- packageName: path4.basename(target)
136887
+ packageName: path5.basename(target)
136788
136888
  });
136789
136889
  }
136790
136890
  );
@@ -136792,25 +136892,25 @@ var PluginInstaller = class {
136792
136892
  }
136793
136893
  readDeps() {
136794
136894
  try {
136795
- const pkgJson = path4.join(this.pluginDir, "package.json");
136796
- if (!fs4.existsSync(pkgJson)) return {};
136797
- const pkg = JSON.parse(fs4.readFileSync(pkgJson, "utf-8"));
136895
+ const pkgJson = path5.join(this.pluginDir, "package.json");
136896
+ if (!fs5.existsSync(pkgJson)) return {};
136897
+ const pkg = JSON.parse(fs5.readFileSync(pkgJson, "utf-8"));
136798
136898
  return pkg.dependencies ?? {};
136799
136899
  } catch {
136800
136900
  return {};
136801
136901
  }
136802
136902
  }
136803
136903
  installFromLocal(localPath) {
136804
- const resolvedPath = path4.resolve(localPath);
136805
- if (!fs4.existsSync(resolvedPath)) {
136904
+ const resolvedPath = path5.resolve(localPath);
136905
+ if (!fs5.existsSync(resolvedPath)) {
136806
136906
  return {
136807
136907
  success: false,
136808
136908
  packageName: "unknown",
136809
136909
  error: `Path does not exist: ${resolvedPath}`
136810
136910
  };
136811
136911
  }
136812
- const pkgJsonPath = path4.join(resolvedPath, "package.json");
136813
- if (!fs4.existsSync(pkgJsonPath)) {
136912
+ const pkgJsonPath = path5.join(resolvedPath, "package.json");
136913
+ if (!fs5.existsSync(pkgJsonPath)) {
136814
136914
  return {
136815
136915
  success: false,
136816
136916
  packageName: "unknown",
@@ -136819,7 +136919,7 @@ var PluginInstaller = class {
136819
136919
  }
136820
136920
  let pkg;
136821
136921
  try {
136822
- pkg = JSON.parse(fs4.readFileSync(pkgJsonPath, "utf-8"));
136922
+ pkg = JSON.parse(fs5.readFileSync(pkgJsonPath, "utf-8"));
136823
136923
  } catch {
136824
136924
  return {
136825
136925
  success: false,
@@ -136835,13 +136935,13 @@ var PluginInstaller = class {
136835
136935
  error: "Invalid package.json: missing 'name' field"
136836
136936
  };
136837
136937
  }
136838
- const targetLink = path4.join(this.pluginDir, "node_modules", packageName);
136839
- if (fs4.existsSync(targetLink)) {
136840
- fs4.rmSync(targetLink, { recursive: true, force: true });
136938
+ const targetLink = path5.join(this.pluginDir, "node_modules", packageName);
136939
+ if (fs5.existsSync(targetLink)) {
136940
+ fs5.rmSync(targetLink, { recursive: true, force: true });
136841
136941
  }
136842
- fs4.mkdirSync(path4.dirname(targetLink), { recursive: true });
136843
- fs4.symlinkSync(resolvedPath, targetLink, "dir");
136844
- logger178.info(
136942
+ fs5.mkdirSync(path5.dirname(targetLink), { recursive: true });
136943
+ fs5.symlinkSync(resolvedPath, targetLink, "dir");
136944
+ logger179.info(
136845
136945
  `Linked local plugin: ${packageName}@${pkg.version || "unknown"} \u2192 ${resolvedPath}`
136846
136946
  );
136847
136947
  return {
@@ -136852,14 +136952,14 @@ var PluginInstaller = class {
136852
136952
  }
136853
136953
  getInstalledVersion(packageName) {
136854
136954
  try {
136855
- const pkgPath = path4.join(
136955
+ const pkgPath = path5.join(
136856
136956
  this.pluginDir,
136857
136957
  "node_modules",
136858
136958
  packageName,
136859
136959
  "package.json"
136860
136960
  );
136861
- if (fs4.existsSync(pkgPath)) {
136862
- const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
136961
+ if (fs5.existsSync(pkgPath)) {
136962
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
136863
136963
  return pkg.version ?? null;
136864
136964
  }
136865
136965
  } catch {
@@ -136870,40 +136970,40 @@ var PluginInstaller = class {
136870
136970
 
136871
136971
  // src/plugins/plugin-registry.ts
136872
136972
  init_esm();
136873
- import * as fs5 from "node:fs";
136874
- import * as path5 from "node:path";
136875
- var logger179 = Logger.get("PluginRegistry");
136973
+ import * as fs6 from "node:fs";
136974
+ import * as path6 from "node:path";
136975
+ var logger180 = Logger.get("PluginRegistry");
136876
136976
  var PluginRegistry = class {
136877
136977
  plugins = [];
136878
136978
  filePath;
136879
136979
  constructor(storageLocation) {
136880
- this.filePath = path5.join(storageLocation, "installed-plugins.json");
136980
+ this.filePath = path6.join(storageLocation, "installed-plugins.json");
136881
136981
  this.load();
136882
136982
  }
136883
136983
  load() {
136884
136984
  try {
136885
- if (fs5.existsSync(this.filePath)) {
136886
- const raw = fs5.readFileSync(this.filePath, "utf-8");
136985
+ if (fs6.existsSync(this.filePath)) {
136986
+ const raw = fs6.readFileSync(this.filePath, "utf-8");
136887
136987
  this.plugins = JSON.parse(raw);
136888
136988
  }
136889
136989
  } catch (e) {
136890
- logger179.warn("Failed to load plugin registry:", e);
136990
+ logger180.warn("Failed to load plugin registry:", e);
136891
136991
  this.plugins = [];
136892
136992
  }
136893
136993
  }
136894
136994
  save() {
136895
136995
  try {
136896
- const dir = path5.dirname(this.filePath);
136897
- if (!fs5.existsSync(dir)) {
136898
- fs5.mkdirSync(dir, { recursive: true });
136996
+ const dir = path6.dirname(this.filePath);
136997
+ if (!fs6.existsSync(dir)) {
136998
+ fs6.mkdirSync(dir, { recursive: true });
136899
136999
  }
136900
- fs5.writeFileSync(
137000
+ fs6.writeFileSync(
136901
137001
  this.filePath,
136902
137002
  JSON.stringify(this.plugins, null, 2),
136903
137003
  "utf-8"
136904
137004
  );
136905
137005
  } catch (e) {
136906
- logger179.error("Failed to save plugin registry:", e);
137006
+ logger180.error("Failed to save plugin registry:", e);
136907
137007
  }
136908
137008
  }
136909
137009
  getAll() {
@@ -137036,19 +137136,35 @@ function pluginApi(bridgeService, storageLocation) {
137036
137136
  }
137037
137137
  return bridge;
137038
137138
  }
137039
- router.post("/:bridgeId/:pluginName/enable", (req, res) => {
137139
+ router.post("/:bridgeId/:pluginName/enable", async (req, res) => {
137040
137140
  const bridge = pluginBridge(req.params.bridgeId, res);
137041
137141
  if (!bridge) return;
137042
137142
  const { pluginName } = req.params;
137043
- bridge.enablePlugin(pluginName);
137044
- res.json({ success: true, pluginName, enabled: true });
137143
+ const metadata = await bridge.enablePlugin(pluginName);
137144
+ if (!metadata) {
137145
+ res.status(404).json({ error: "Plugin not found" });
137146
+ return;
137147
+ }
137148
+ res.json({
137149
+ success: metadata.enabled === true,
137150
+ pluginName,
137151
+ enabled: metadata.enabled
137152
+ });
137045
137153
  });
137046
- router.post("/:bridgeId/:pluginName/disable", (req, res) => {
137154
+ router.post("/:bridgeId/:pluginName/disable", async (req, res) => {
137047
137155
  const bridge = pluginBridge(req.params.bridgeId, res);
137048
137156
  if (!bridge) return;
137049
137157
  const { pluginName } = req.params;
137050
- bridge.disablePlugin(pluginName);
137051
- res.json({ success: true, pluginName, enabled: false });
137158
+ const metadata = await bridge.disablePlugin(pluginName);
137159
+ if (!metadata) {
137160
+ res.status(404).json({ error: "Plugin not found" });
137161
+ return;
137162
+ }
137163
+ res.json({
137164
+ success: metadata.enabled === false,
137165
+ pluginName,
137166
+ enabled: metadata.enabled
137167
+ });
137052
137168
  });
137053
137169
  router.get("/:bridgeId/:pluginName/config-schema", (req, res) => {
137054
137170
  const bridge = pluginBridge(req.params.bridgeId, res);
@@ -137249,7 +137365,7 @@ function pluginApi(bridgeService, storageLocation) {
137249
137365
  }
137250
137366
 
137251
137367
  // src/api/proxy-support.ts
137252
- import path6 from "node:path";
137368
+ import path7 from "node:path";
137253
137369
  var ingressPath = "x-ingress-path";
137254
137370
  var forwardedPrefix = "x-forwarded-prefix";
137255
137371
  function supportIngress(req, _, next) {
@@ -137288,7 +137404,7 @@ function supportProxyLocation(req, res, next) {
137288
137404
  next();
137289
137405
  }
137290
137406
  function buildPath(...paths) {
137291
- let result = path6.posix.join(...paths);
137407
+ let result = path7.posix.join(...paths);
137292
137408
  if (!result.startsWith("/")) {
137293
137409
  result = `/${result}`;
137294
137410
  }
@@ -137560,7 +137676,7 @@ import { promisify } from "node:util";
137560
137676
  import v8 from "node:v8";
137561
137677
  import express15 from "express";
137562
137678
  var execAsync = promisify(exec);
137563
- var logger180 = Logger.get("SystemApi");
137679
+ var logger181 = Logger.get("SystemApi");
137564
137680
  function detectEnvironment2() {
137565
137681
  if (process.env.SUPERVISOR_TOKEN || process.env.HASSIO_TOKEN) {
137566
137682
  return "Home Assistant Add-on";
@@ -137607,7 +137723,7 @@ function systemApi(version2) {
137607
137723
  const data = await response.json();
137608
137724
  res.json(toUpdateCheckResponse(version2, data, detectEnvironment2()));
137609
137725
  } catch (error) {
137610
- logger180.error("Failed to check for updates:", error);
137726
+ logger181.error("Failed to check for updates:", error);
137611
137727
  res.status(500).json({ error: "Failed to check for updates" });
137612
137728
  }
137613
137729
  });
@@ -137656,7 +137772,7 @@ function systemApi(version2) {
137656
137772
  };
137657
137773
  res.json(systemInfo);
137658
137774
  } catch (error) {
137659
- logger180.error("Failed to get system info:", error);
137775
+ logger181.error("Failed to get system info:", error);
137660
137776
  res.status(500).json({ error: "Failed to get system info" });
137661
137777
  }
137662
137778
  });
@@ -137699,7 +137815,7 @@ async function getStorageInfo() {
137699
137815
  return await getUnixStorageInfo(pathToCheck);
137700
137816
  }
137701
137817
  } catch (error) {
137702
- logger180.error("Failed to get storage info:", error);
137818
+ logger181.error("Failed to get storage info:", error);
137703
137819
  return { total: 0, used: 0, free: 0 };
137704
137820
  }
137705
137821
  }
@@ -137768,8 +137884,8 @@ async function getUnixStorageInfo(path14) {
137768
137884
  }
137769
137885
 
137770
137886
  // src/api/web-ui.ts
137771
- import fs6 from "node:fs";
137772
- import path7 from "node:path";
137887
+ import fs7 from "node:fs";
137888
+ import path8 from "node:path";
137773
137889
  import express16 from "express";
137774
137890
  function webUi(dist) {
137775
137891
  const router = express16.Router();
@@ -137788,7 +137904,7 @@ function replaceBase(dist) {
137788
137904
  if (!baseUrl.endsWith("/")) {
137789
137905
  baseUrl += "/";
137790
137906
  }
137791
- const content = fs6.readFileSync(path7.join(dist, "index.html"), "utf8").replace(
137907
+ const content = fs7.readFileSync(path8.join(dist, "index.html"), "utf8").replace(
137792
137908
  /<!-- BASE -->[\s\S]*<!-- \/BASE -->/,
137793
137909
  `<base href='${baseUrl}' />`
137794
137910
  );
@@ -138384,7 +138500,7 @@ var FilteredNetwork = class extends NodeJsNetwork {
138384
138500
  };
138385
138501
 
138386
138502
  // src/core/app/mdns.ts
138387
- var logger181 = Logger.get("Mdns");
138503
+ var logger182 = Logger.get("Mdns");
138388
138504
  function mdns(env, options) {
138389
138505
  if (options.stripGlobalIpv6) {
138390
138506
  env.set(Network, new FilteredNetwork());
@@ -138399,7 +138515,7 @@ function mdns(env, options) {
138399
138515
  function warnAboutAdvertising(options) {
138400
138516
  const choice = selectMdnsInterface(os5.networkInterfaces());
138401
138517
  if (choice.hasGlobalIpv6) {
138402
- logger181.warn(
138518
+ logger182.warn(
138403
138519
  "Matter mDNS is advertising a global IPv6 address that controllers may not reach on the LAN, so devices can show No Response (#361). Set mdns-strip-global-ipv6 if devices stay unreachable."
138404
138520
  );
138405
138521
  }
@@ -138408,7 +138524,7 @@ function warnAboutAdvertising(options) {
138408
138524
  }
138409
138525
  const suggestion = choice.selected ? ` Likely LAN interface: ${choice.selected}.` : "";
138410
138526
  if (choice.hasThreadInterface) {
138411
- logger181.warn(
138527
+ logger182.warn(
138412
138528
  `Matter mDNS is advertising on an OpenThread/OTBR interface (wpan/otbr) whose mesh-local address controllers cannot reach, so devices may show offline (#388). Set mdns-network-interface to your LAN interface.${suggestion}`
138413
138529
  );
138414
138530
  }
@@ -138416,16 +138532,16 @@ function warnAboutAdvertising(options) {
138416
138532
  return;
138417
138533
  }
138418
138534
  const list3 = choice.external.map((i) => `${i.name} (${i.ipv4[0] ?? i.ipv6[0] ?? "?"})`).join(", ");
138419
- logger181.warn(
138535
+ logger182.warn(
138420
138536
  `Matter mDNS is advertising on several interfaces including likely Docker-internal ones, so controllers may show devices as offline (#361). Set mdns-network-interface to your LAN interface.${suggestion} Interfaces: ${list3}.`
138421
138537
  );
138422
138538
  }
138423
138539
 
138424
138540
  // src/core/app/storage.ts
138425
138541
  init_esm7();
138426
- import fs7 from "node:fs";
138542
+ import fs8 from "node:fs";
138427
138543
  import os6 from "node:os";
138428
- import path8 from "node:path";
138544
+ import path9 from "node:path";
138429
138545
 
138430
138546
  // src/core/app/storage/custom-storage.ts
138431
138547
  init_dist();
@@ -138445,7 +138561,7 @@ var CustomStorage = class extends FileStorageDriver {
138445
138561
  // src/core/app/storage.ts
138446
138562
  function storage(environment, options) {
138447
138563
  const location = resolveStorageLocation(options.location);
138448
- fs7.mkdirSync(location, { recursive: true });
138564
+ fs8.mkdirSync(location, { recursive: true });
138449
138565
  environment.get(VariableService).set("storage.path", location);
138450
138566
  const storageService = environment.get(StorageService);
138451
138567
  storageService.registerDriver({
@@ -138460,7 +138576,7 @@ function storage(environment, options) {
138460
138576
  }
138461
138577
  function resolveStorageLocation(storageLocation) {
138462
138578
  const homedir = os6.homedir();
138463
- return storageLocation ? path8.resolve(storageLocation.replace(/^~\//, `${homedir}/`)) : path8.join(homedir, ".home-assistant-matter-hub");
138579
+ return storageLocation ? path9.resolve(storageLocation.replace(/^~\//, `${homedir}/`)) : path9.join(homedir, ".home-assistant-matter-hub");
138464
138580
  }
138465
138581
 
138466
138582
  // src/core/app/configure-default-environment.ts
@@ -138480,7 +138596,7 @@ function configureDefaultEnvironment(options) {
138480
138596
  init_esm7();
138481
138597
  import { createRequire } from "node:module";
138482
138598
  import os7 from "node:os";
138483
- import path9 from "node:path";
138599
+ import path10 from "node:path";
138484
138600
  function resolveAppVersion() {
138485
138601
  try {
138486
138602
  const require2 = createRequire(import.meta.url);
@@ -138550,7 +138666,7 @@ var Options = class {
138550
138666
  resolveStorageLocation() {
138551
138667
  const storageLocation = notEmpty(this.startOptions.storageLocation);
138552
138668
  const homedir = os7.homedir();
138553
- return storageLocation ? path9.resolve(storageLocation.replace(/^~\//, `${homedir}/`)) : path9.join(homedir, ".home-assistant-matter-hub");
138669
+ return storageLocation ? path10.resolve(storageLocation.replace(/^~\//, `${homedir}/`)) : path10.join(homedir, ".home-assistant-matter-hub");
138554
138670
  }
138555
138671
  get bridgeService() {
138556
138672
  return {
@@ -138590,8 +138706,8 @@ init_esm7();
138590
138706
 
138591
138707
  // src/services/backup/backup-service.ts
138592
138708
  init_esm();
138593
- import fs8 from "node:fs";
138594
- import path10 from "node:path";
138709
+ import fs9 from "node:fs";
138710
+ import path11 from "node:path";
138595
138711
  import archiver2 from "archiver";
138596
138712
  var BackupService = class {
138597
138713
  constructor(bridgeStorage, mappingStorage, settingsStorage, props) {
@@ -138599,8 +138715,8 @@ var BackupService = class {
138599
138715
  this.mappingStorage = mappingStorage;
138600
138716
  this.settingsStorage = settingsStorage;
138601
138717
  this.props = props;
138602
- this.backupDir = path10.join(props.storageLocation, "backups");
138603
- fs8.mkdirSync(this.backupDir, { recursive: true });
138718
+ this.backupDir = path11.join(props.storageLocation, "backups");
138719
+ fs9.mkdirSync(this.backupDir, { recursive: true });
138604
138720
  }
138605
138721
  bridgeStorage;
138606
138722
  mappingStorage;
@@ -138614,7 +138730,7 @@ var BackupService = class {
138614
138730
  const dateStr = now.toISOString().replace(/T/, "_").replace(/:/g, "-").replace(/\.\d+Z$/, "");
138615
138731
  const prefix = auto ? "auto" : "manual";
138616
138732
  const filename = `hamh-${prefix}-${version2}-${dateStr}.zip`;
138617
- const filepath = path10.join(this.backupDir, filename);
138733
+ const filepath = path11.join(this.backupDir, filename);
138618
138734
  const bridges = this.bridgeStorage.bridges;
138619
138735
  const entityMappings = {};
138620
138736
  for (const bridge of bridges) {
@@ -138624,9 +138740,9 @@ var BackupService = class {
138624
138740
  }
138625
138741
  }
138626
138742
  let includesIcons = false;
138627
- const iconsDir = path10.join(this.props.storageLocation, "bridge-icons");
138628
- if (fs8.existsSync(iconsDir)) {
138629
- const iconFiles = fs8.readdirSync(iconsDir);
138743
+ const iconsDir = path11.join(this.props.storageLocation, "bridge-icons");
138744
+ if (fs9.existsSync(iconsDir)) {
138745
+ const iconFiles = fs9.readdirSync(iconsDir);
138630
138746
  includesIcons = iconFiles.some((f) => {
138631
138747
  const bridgeId = f.split(".")[0];
138632
138748
  return bridges.some((b) => b.id === bridgeId);
@@ -138643,7 +138759,7 @@ var BackupService = class {
138643
138759
  auto
138644
138760
  };
138645
138761
  await new Promise((resolve11, reject) => {
138646
- const output = fs8.createWriteStream(filepath);
138762
+ const output = fs9.createWriteStream(filepath);
138647
138763
  const archive = archiver2("zip", { zlib: { level: 9 } });
138648
138764
  output.on("close", () => resolve11());
138649
138765
  archive.on("error", (err) => reject(err));
@@ -138667,20 +138783,27 @@ var BackupService = class {
138667
138783
  { name: "README.txt" }
138668
138784
  );
138669
138785
  for (const bridge of bridges) {
138670
- const bridgeStoragePath = path10.join(
138786
+ const bridgeStoragePath = path11.join(
138671
138787
  this.props.storageLocation,
138672
138788
  bridge.id
138673
138789
  );
138674
- if (fs8.existsSync(bridgeStoragePath)) {
138790
+ if (fs9.existsSync(bridgeStoragePath)) {
138675
138791
  archive.directory(bridgeStoragePath, `identity/${bridge.id}`);
138676
138792
  }
138793
+ const pluginState = pluginStateFilePath(
138794
+ this.props.storageLocation,
138795
+ bridge.id
138796
+ );
138797
+ if (fs9.existsSync(pluginState)) {
138798
+ archive.file(pluginState, { name: `plugin-state/${bridge.id}.json` });
138799
+ }
138677
138800
  }
138678
138801
  if (includesIcons) {
138679
- const iconFiles = fs8.readdirSync(iconsDir);
138802
+ const iconFiles = fs9.readdirSync(iconsDir);
138680
138803
  for (const iconFile of iconFiles) {
138681
138804
  const bridgeId = iconFile.split(".")[0];
138682
138805
  if (bridges.some((b) => b.id === bridgeId)) {
138683
- archive.file(path10.join(iconsDir, iconFile), {
138806
+ archive.file(path11.join(iconsDir, iconFile), {
138684
138807
  name: `bridge-icons/${iconFile}`
138685
138808
  });
138686
138809
  }
@@ -138688,7 +138811,7 @@ var BackupService = class {
138688
138811
  }
138689
138812
  archive.finalize();
138690
138813
  });
138691
- const stat4 = fs8.statSync(filepath);
138814
+ const stat4 = fs9.statSync(filepath);
138692
138815
  const metadata = {
138693
138816
  filename,
138694
138817
  version: version2,
@@ -138716,13 +138839,13 @@ var BackupService = class {
138716
138839
  }
138717
138840
  }
138718
138841
  listBackups() {
138719
- if (!fs8.existsSync(this.backupDir)) {
138842
+ if (!fs9.existsSync(this.backupDir)) {
138720
138843
  return [];
138721
138844
  }
138722
- const files = fs8.readdirSync(this.backupDir).filter((f) => f.startsWith("hamh-") && f.endsWith(".zip"));
138845
+ const files = fs9.readdirSync(this.backupDir).filter((f) => f.startsWith("hamh-") && f.endsWith(".zip"));
138723
138846
  return files.map((filename) => {
138724
138847
  try {
138725
- const stat4 = fs8.statSync(path10.join(this.backupDir, filename));
138848
+ const stat4 = fs9.statSync(path11.join(this.backupDir, filename));
138726
138849
  const parsed = this.parseFilename(filename);
138727
138850
  return {
138728
138851
  filename,
@@ -138742,8 +138865,8 @@ var BackupService = class {
138742
138865
  if (filename.includes("..") || filename.includes("/")) {
138743
138866
  return null;
138744
138867
  }
138745
- const filepath = path10.join(this.backupDir, filename);
138746
- if (!fs8.existsSync(filepath)) {
138868
+ const filepath = path11.join(this.backupDir, filename);
138869
+ if (!fs9.existsSync(filepath)) {
138747
138870
  return null;
138748
138871
  }
138749
138872
  return filepath;
@@ -138752,7 +138875,7 @@ var BackupService = class {
138752
138875
  const filepath = this.getBackupPath(filename);
138753
138876
  if (!filepath) return false;
138754
138877
  try {
138755
- fs8.unlinkSync(filepath);
138878
+ fs9.unlinkSync(filepath);
138756
138879
  this.log.info(`Backup deleted: ${filename}`);
138757
138880
  return true;
138758
138881
  } catch (e) {
@@ -139332,7 +139455,7 @@ async function getAreaRegistry(connection, timeoutMs) {
139332
139455
  }
139333
139456
 
139334
139457
  // src/services/home-assistant/home-assistant-registry.ts
139335
- var logger182 = Logger.get("HomeAssistantRegistry");
139458
+ var logger183 = Logger.get("HomeAssistantRegistry");
139336
139459
  var HomeAssistantRegistry = class extends Service {
139337
139460
  constructor(client, options) {
139338
139461
  super("HomeAssistantRegistry");
@@ -139367,7 +139490,7 @@ var HomeAssistantRegistry = class extends Service {
139367
139490
  try {
139368
139491
  await this.reload();
139369
139492
  } catch (e) {
139370
- logger182.warn(
139493
+ logger183.warn(
139371
139494
  "Initial registry fetch failed, starting empty and relying on auto-refresh:",
139372
139495
  e
139373
139496
  );
@@ -139381,7 +139504,7 @@ var HomeAssistantRegistry = class extends Service {
139381
139504
  let refreshing = false;
139382
139505
  this.autoRefresh = setInterval(async () => {
139383
139506
  if (refreshing) {
139384
- logger182.debug("Skipping registry refresh, previous tick still running");
139507
+ logger183.debug("Skipping registry refresh, previous tick still running");
139385
139508
  return;
139386
139509
  }
139387
139510
  refreshing = true;
@@ -139391,7 +139514,7 @@ var HomeAssistantRegistry = class extends Service {
139391
139514
  await onRefresh();
139392
139515
  }
139393
139516
  } catch (e) {
139394
- logger182.warn("Failed to refresh registry, will retry next interval:", e);
139517
+ logger183.warn("Failed to refresh registry, will retry next interval:", e);
139395
139518
  } finally {
139396
139519
  refreshing = false;
139397
139520
  }
@@ -139409,7 +139532,7 @@ var HomeAssistantRegistry = class extends Service {
139409
139532
  baseDelayMs: 2e3,
139410
139533
  maxDelayMs: 3e4,
139411
139534
  onRetry: (attempt, error, delayMs) => {
139412
- logger182.warn(
139535
+ logger183.warn(
139413
139536
  `Registry fetch failed (attempt ${attempt}), retrying in ${delayMs}ms:`,
139414
139537
  error
139415
139538
  );
@@ -139422,7 +139545,7 @@ var HomeAssistantRegistry = class extends Service {
139422
139545
  return await this.runRegistryQueries();
139423
139546
  } catch (e) {
139424
139547
  if (!isConnectionLost(e)) throw e;
139425
- logger182.debug("Registry fetch hit connection drop, waiting for reconnect");
139548
+ logger183.debug("Registry fetch hit connection drop, waiting for reconnect");
139426
139549
  await this.waitForConnection(6e4);
139427
139550
  return await this.runRegistryQueries();
139428
139551
  }
@@ -139430,7 +139553,7 @@ var HomeAssistantRegistry = class extends Service {
139430
139553
  async waitForConnection(timeoutMs) {
139431
139554
  const connection = this.client.connection;
139432
139555
  if (connection.connected) return;
139433
- logger182.debug("Connection not ready, waiting for reconnect...");
139556
+ logger183.debug("Connection not ready, waiting for reconnect...");
139434
139557
  await new Promise((resolve11) => {
139435
139558
  const timeout = setTimeout(() => {
139436
139559
  connection.removeEventListener("ready", onReady);
@@ -139491,7 +139614,7 @@ var HomeAssistantRegistry = class extends Service {
139491
139614
  const fingerprint = hash2.digest("hex");
139492
139615
  this._states = keyBy(statesList, "entity_id");
139493
139616
  if (fingerprint === this.lastRegistryFingerprint) {
139494
- logger182.debug("Registry unchanged, skipping full refresh");
139617
+ logger183.debug("Registry unchanged, skipping full refresh");
139495
139618
  return false;
139496
139619
  }
139497
139620
  this.lastRegistryFingerprint = fingerprint;
@@ -139512,10 +139635,10 @@ var HomeAssistantRegistry = class extends Service {
139512
139635
  const missingDevices = fromPairs(missingDeviceIds.map((d) => [d, { id: d }]));
139513
139636
  this._devices = { ...missingDevices, ...realDevices };
139514
139637
  this._entities = allEntities;
139515
- logger182.debug(
139638
+ logger183.debug(
139516
139639
  `Loaded HA registry: ${keys(allEntities).length} entities, ${keys(realDevices).length} devices, ${keys(this._states).length} states`
139517
139640
  );
139518
- logMemoryUsage(logger182, "after HA registry load");
139641
+ logMemoryUsage(logger183, "after HA registry load");
139519
139642
  this._labels = labels;
139520
139643
  this._areas = new Map(areas.map((a) => [a.area_id, a.name]));
139521
139644
  return true;
@@ -140388,7 +140511,7 @@ var __privateIn5 = (member, obj) => Object(obj) !== obj ? __typeError50('Cannot
140388
140511
  var __privateGet5 = (obj, member, getter) => (__accessCheck5(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
140389
140512
  var __privateSet5 = (obj, member, value, setter) => (__accessCheck5(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
140390
140513
  var __privateMethod5 = (obj, member, method) => (__accessCheck5(obj, member, "access private method"), method);
140391
- var logger183 = Logger.get("ScenesManagementServer");
140514
+ var logger184 = Logger.get("ScenesManagementServer");
140392
140515
  var UNDEFINED_SCENE_ID = 255;
140393
140516
  function constraintErrorWithSceneId(groupId22, sceneId) {
140394
140517
  const response = { status: Status2.ConstraintError, groupId: groupId22, sceneId };
@@ -140515,11 +140638,11 @@ var ScenesManagementServer = class extends ScenesManagementBase {
140515
140638
  return { status: Status2.ResourceExhausted, groupId: groupId22, sceneId };
140516
140639
  }
140517
140640
  this.state.sceneTable.push(sceneData);
140518
- logger183.debug(`Added scene ${sceneId} in group ${groupId22} for fabric ${fabricIndex}`);
140641
+ logger184.debug(`Added scene ${sceneId} in group ${groupId22} for fabric ${fabricIndex}`);
140519
140642
  this.#updateFabricSceneInfoCountsForFabric(fabricIndex);
140520
140643
  } else {
140521
140644
  this.state.sceneTable[existingSceneIndex] = sceneData;
140522
- logger183.debug(`Updated scene ${sceneId} in group ${groupId22} for fabric ${fabricIndex}`);
140645
+ logger184.debug(`Updated scene ${sceneId} in group ${groupId22} for fabric ${fabricIndex}`);
140523
140646
  }
140524
140647
  return { status: Status2.Success, groupId: groupId22, sceneId };
140525
140648
  }
@@ -140822,20 +140945,20 @@ var ScenesManagementServer = class extends ScenesManagementBase {
140822
140945
  }
140823
140946
  }
140824
140947
  if (fieldCount !== 2) {
140825
- logger183.warn(
140948
+ logger184.warn(
140826
140949
  `AttributeValuePair has invalid number (${fieldCount}) of fields (${serialize(attributeValuePair)})`
140827
140950
  );
140828
140951
  return void 0;
140829
140952
  }
140830
140953
  const value = attributeValuePair[mappedType];
140831
140954
  if (value === void 0) {
140832
- logger183.warn(
140955
+ logger184.warn(
140833
140956
  `AttributeValuePair missing value for mappedType ${mappedType} (${serialize(attributeValuePair)})`
140834
140957
  );
140835
140958
  return void 0;
140836
140959
  }
140837
140960
  if (typeof value !== "number" && typeof value !== "bigint") {
140838
- logger183.warn(
140961
+ logger184.warn(
140839
140962
  `AttributeValuePair has invalid non-numeric value for mappedType ${mappedType} (${serialize(attributeValuePair)})`
140840
140963
  // Should never happen
140841
140964
  );
@@ -140933,7 +141056,7 @@ var ScenesManagementServer = class extends ScenesManagementBase {
140933
141056
  } else if (schema6.schema.baseTypeMin < 0 && schema6.schema.min > schema6.schema.baseTypeMin) {
140934
141057
  return { attributeId, [mappedType]: schema6.schema.baseTypeMin };
140935
141058
  } else {
140936
- logger183.warn(
141059
+ logger184.warn(
140937
141060
  `Cannot determine out-of-bounds value for attribute schema, returning min value of datatype schema`
140938
141061
  );
140939
141062
  }
@@ -140954,7 +141077,7 @@ var ScenesManagementServer = class extends ScenesManagementBase {
140954
141077
  }
140955
141078
  }
140956
141079
  });
140957
- logger183.debug(`Collected scene attribute values on Endpoint ${this.endpoint.id}: ${serialize(sceneValues)}`);
141080
+ logger184.debug(`Collected scene attribute values on Endpoint ${this.endpoint.id}: ${serialize(sceneValues)}`);
140958
141081
  return sceneValues;
140959
141082
  }
140960
141083
  /**
@@ -140993,7 +141116,7 @@ var ScenesManagementServer = class extends ScenesManagementBase {
140993
141116
  }
140994
141117
  const attrType = attribute.primitiveBase?.name;
140995
141118
  if (attrType === void 0 || DataTypeToSceneAttributeDataMap[attrType] === void 0) {
140996
- logger183.warn(
141119
+ logger184.warn(
140997
141120
  `Scene Attribute ${attribute.name} on Cluster ${clusterName} has unsupported datatype ${attrType} for scene management on Endpoint ${this.endpoint.id}`
140998
141121
  );
140999
141122
  continue;
@@ -141008,7 +141131,7 @@ var ScenesManagementServer = class extends ScenesManagementBase {
141008
141131
  });
141009
141132
  }
141010
141133
  if (sceneClusterDetails) {
141011
- logger183.info(
141134
+ logger184.info(
141012
141135
  `Registered ${sceneClusterDetails.attributes.size} scene attributes for Cluster ${clusterName} on Endpoint ${this.endpoint.id}`
141013
141136
  );
141014
141137
  this.internal.endpointSceneableBehaviors.add(sceneClusterDetails);
@@ -141016,7 +141139,7 @@ var ScenesManagementServer = class extends ScenesManagementBase {
141016
141139
  }
141017
141140
  /** Apply scene attribute values in the various clusters on the endpoint. */
141018
141141
  #applySceneAttributeValues(sceneValues, transitionTime = null) {
141019
- logger183.debug(`Recalling scene on Endpoint ${this.endpoint.id} with values: ${serialize(sceneValues)}`);
141142
+ logger184.debug(`Recalling scene on Endpoint ${this.endpoint.id} with values: ${serialize(sceneValues)}`);
141020
141143
  const agent = this.endpoint.agentFor(this.context);
141021
141144
  const promises = [];
141022
141145
  for (const [clusterName, clusterAttributes] of Object.entries(sceneValues)) {
@@ -141027,7 +141150,7 @@ var ScenesManagementServer = class extends ScenesManagementBase {
141027
141150
  promises.push(result);
141028
141151
  }
141029
141152
  } else {
141030
- logger183.warn(
141153
+ logger184.warn(
141031
141154
  `No scenes implementation found for cluster ${clusterName} on Endpoint ${this.endpoint.id} during scene recall. Values are ignored`
141032
141155
  );
141033
141156
  }
@@ -141035,7 +141158,7 @@ var ScenesManagementServer = class extends ScenesManagementBase {
141035
141158
  if (promises.length) {
141036
141159
  return Promise.all(promises).then(
141037
141160
  () => void 0,
141038
- (error) => logger183.warn(`Error applying scene attribute values on Endpoint ${this.endpoint.id}:`, error)
141161
+ (error) => logger184.warn(`Error applying scene attribute values on Endpoint ${this.endpoint.id}:`, error)
141039
141162
  );
141040
141163
  }
141041
141164
  }
@@ -141228,7 +141351,7 @@ var GroupsBehaviorConstructor = ClusterBehavior.for(Groups4);
141228
141351
  var GroupsBehavior = GroupsBehaviorConstructor;
141229
141352
 
141230
141353
  // ../../node_modules/.pnpm/@matter+node@0.17.9_patch_hash=e3376f1c2fdd0a0df09b8d04625a4d7af65b47d7277cc88cdb80baf7fb32bd0d/node_modules/@matter/node/dist/esm/behaviors/groups/GroupsServer.js
141231
- var logger184 = Logger.get("GroupsServer");
141354
+ var logger185 = Logger.get("GroupsServer");
141232
141355
  var { commands: commands3 } = Groups4.schema;
141233
141356
  var addGroup = commands3.require("AddGroup");
141234
141357
  var addGroupIfIdentifying = commands3.require("AddGroupIfIdentifying");
@@ -141307,7 +141430,7 @@ var GroupsServer = class extends GroupsBase {
141307
141430
  (fabric2, gkm) => gkm.addEndpointForGroup(fabric2, groupId3, endpointNumber, groupName)
141308
141431
  );
141309
141432
  } catch (error) {
141310
- logger184.debug("Could not add group", error);
141433
+ logger185.debug("Could not add group", error);
141311
141434
  StatusResponseError.accept(error);
141312
141435
  return { status: error.code, groupId: groupId3 };
141313
141436
  }
@@ -141984,7 +142107,7 @@ var SwitchBehavior = SwitchBehaviorConstructor;
141984
142107
  // ../../node_modules/.pnpm/@matter+node@0.17.9_patch_hash=e3376f1c2fdd0a0df09b8d04625a4d7af65b47d7277cc88cdb80baf7fb32bd0d/node_modules/@matter/node/dist/esm/behaviors/switch/SwitchServer.js
141985
142108
  var DEFAULT_MULTIPRESS_DELAY = Millis(300);
141986
142109
  var DEFAULT_LONG_PRESS_DELAY = Seconds(2);
141987
- var logger185 = Logger.get("SwitchServer");
142110
+ var logger186 = Logger.get("SwitchServer");
141988
142111
  var SwitchServerBase = SwitchBehavior.with(
141989
142112
  Switch3.Feature.LatchingSwitch,
141990
142113
  Switch3.Feature.MomentarySwitch,
@@ -142037,7 +142160,7 @@ var SwitchBaseServer = class extends SwitchServerBase {
142037
142160
  this.internal.currentIsLongPress = false;
142038
142161
  this.internal.multiPressTimer?.stop();
142039
142162
  this.internal.longPressTimer?.stop();
142040
- logger185.info("State of Switch got reset");
142163
+ logger186.info("State of Switch got reset");
142041
142164
  }
142042
142165
  // TODO remove when Validator logic can assess that with 1.3 introduction
142043
142166
  #assertPositionInRange(position) {
@@ -142275,11 +142398,11 @@ var WebRtcTransportRequestorBehaviorConstructor = ClusterBehavior.for(WebRtcTran
142275
142398
  var WebRtcTransportRequestorBehavior = WebRtcTransportRequestorBehaviorConstructor;
142276
142399
 
142277
142400
  // ../../node_modules/.pnpm/@matter+node@0.17.9_patch_hash=e3376f1c2fdd0a0df09b8d04625a4d7af65b47d7277cc88cdb80baf7fb32bd0d/node_modules/@matter/node/dist/esm/behaviors/web-rtc-transport-requestor/WebRtcTransportRequestorServer.js
142278
- var logger186 = Logger.get("WebRtcTransportRequestorServer");
142401
+ var logger187 = Logger.get("WebRtcTransportRequestorServer");
142279
142402
  var WebRtcTransportRequestorServer = class extends WebRtcTransportRequestorBehavior {
142280
142403
  async initialize() {
142281
142404
  const node = Node.forEndpoint(this.endpoint);
142282
- logger186.info(
142405
+ logger187.info(
142283
142406
  `WebRtcTransportRequestor initialized on endpoint=${this.endpoint.number} (id="${this.endpoint.id}")`
142284
142407
  );
142285
142408
  this.reactTo(node.lifecycle.online, this.#nodeOnline);
@@ -142345,7 +142468,7 @@ var WebRtcTransportRequestorServer = class extends WebRtcTransportRequestorBehav
142345
142468
  * `offer` event.
142346
142469
  */
142347
142470
  async offer(request) {
142348
- logger186.debug(`incoming Offer webRtcSessionId=${request.webRtcSessionId} sdpLen=${request.sdp.length}`);
142471
+ logger187.debug(`incoming Offer webRtcSessionId=${request.webRtcSessionId} sdpLen=${request.sdp.length}`);
142349
142472
  const session = this.#findSessionStrict(request.webRtcSessionId);
142350
142473
  this.events.offer.emit(session, request);
142351
142474
  }
@@ -142354,7 +142477,7 @@ var WebRtcTransportRequestorServer = class extends WebRtcTransportRequestorBehav
142354
142477
  * `answer` event.
142355
142478
  */
142356
142479
  async answer(request) {
142357
- logger186.debug(`incoming Answer webRtcSessionId=${request.webRtcSessionId} sdpLen=${request.sdp.length}`);
142480
+ logger187.debug(`incoming Answer webRtcSessionId=${request.webRtcSessionId} sdpLen=${request.sdp.length}`);
142358
142481
  const session = this.#findSessionStrict(request.webRtcSessionId);
142359
142482
  this.events.answer.emit(session, request.sdp);
142360
142483
  }
@@ -142362,7 +142485,7 @@ var WebRtcTransportRequestorServer = class extends WebRtcTransportRequestorBehav
142362
142485
  * `iceCandidates` event.
142363
142486
  */
142364
142487
  async iceCandidates(request) {
142365
- logger186.debug(
142488
+ logger187.debug(
142366
142489
  `incoming ICECandidates webRtcSessionId=${request.webRtcSessionId} count=${request.iceCandidates.length}`
142367
142490
  );
142368
142491
  if (request.iceCandidates.length === 0) {
@@ -142375,7 +142498,7 @@ var WebRtcTransportRequestorServer = class extends WebRtcTransportRequestorBehav
142375
142498
  * {@link WebRtcTransportRequestorServer.Events} `end` event.
142376
142499
  */
142377
142500
  async end(request) {
142378
- logger186.debug(`incoming End webRtcSessionId=${request.webRtcSessionId} reason=${request.reason}`);
142501
+ logger187.debug(`incoming End webRtcSessionId=${request.webRtcSessionId} reason=${request.reason}`);
142379
142502
  const session = this.#findSessionStrict(request.webRtcSessionId);
142380
142503
  this.removeSession(request.webRtcSessionId);
142381
142504
  this.events.end.emit(session, request.reason);
@@ -142820,11 +142943,11 @@ var OccupancySensingBehaviorConstructor = ClusterBehavior.for(OccupancySensing3)
142820
142943
  var OccupancySensingBehavior = OccupancySensingBehaviorConstructor;
142821
142944
 
142822
142945
  // ../../node_modules/.pnpm/@matter+node@0.17.9_patch_hash=e3376f1c2fdd0a0df09b8d04625a4d7af65b47d7277cc88cdb80baf7fb32bd0d/node_modules/@matter/node/dist/esm/behaviors/occupancy-sensing/OccupancySensingServer.js
142823
- var logger187 = Logger.get("OccupancySensingServer");
142946
+ var logger188 = Logger.get("OccupancySensingServer");
142824
142947
  var OccupancySensingServer = class extends OccupancySensingBehavior {
142825
142948
  initialize() {
142826
142949
  if (!Object.values(this.features).some((feature) => feature)) {
142827
- logger187.error(
142950
+ logger188.error(
142828
142951
  `OccupancySensingServer: Since revision 5 of the cluster features need to be set based on the detector type. Currently no features are enabled.`
142829
142952
  );
142830
142953
  } else if (!Object.values(this.state.occupancySensorTypeBitmap).some((feature) => feature) || this.state.occupancySensorType === void 0) {
@@ -142851,7 +142974,7 @@ var OccupancySensingServer = class extends OccupancySensingBehavior {
142851
142974
  } else if (this.state.occupancySensorTypeBitmap.physicalContact) {
142852
142975
  this.state.occupancySensorType = OccupancySensing3.OccupancySensorType.PhysicalContact;
142853
142976
  }
142854
- logger187.debug(
142977
+ logger188.debug(
142855
142978
  "Sync occupancySensorType to",
142856
142979
  OccupancySensing3.OccupancySensorType[this.state.occupancySensorType],
142857
142980
  "and occupancySensorTypeBitmap to",
@@ -142864,7 +142987,7 @@ var OccupancySensingServer = class extends OccupancySensingBehavior {
142864
142987
  if (this.features.occupancyEvent) {
142865
142988
  this.reactTo(this.events.occupancy$Changed, this.#emitOccupancyChanged);
142866
142989
  } else {
142867
- logger187.info(
142990
+ logger188.info(
142868
142991
  'OccupancySensingServer: enable the OccupancyEvent feature (e.g. OccupancySensingServer.with("<DetectorType>", "OccupancyEvent")) to emit the OccupancyChanged event.'
142869
142992
  );
142870
142993
  }
@@ -145460,7 +145583,7 @@ function miredsToXy(mireds) {
145460
145583
  }
145461
145584
 
145462
145585
  // ../../node_modules/.pnpm/@matter+node@0.17.9_patch_hash=e3376f1c2fdd0a0df09b8d04625a4d7af65b47d7277cc88cdb80baf7fb32bd0d/node_modules/@matter/node/dist/esm/behaviors/color-control/ColorControlServer.js
145463
- var logger188 = Logger.get("ColorControlServer");
145586
+ var logger189 = Logger.get("ColorControlServer");
145464
145587
  var ColorControlBase = ColorControlBehavior.with(
145465
145588
  ColorControl3.Feature.HueSaturation,
145466
145589
  ColorControl3.Feature.EnhancedHue,
@@ -146641,7 +146764,7 @@ var ColorControlBaseServer = class _ColorControlBaseServer extends ColorControlB
146641
146764
  switch (oldMode) {
146642
146765
  case ColorControl3.ColorMode.CurrentHueAndCurrentSaturation:
146643
146766
  if (this.state.currentHue === void 0 || this.state.currentSaturation === void 0) {
146644
- logger188.warn("Could not convert from hue/saturation because one of them is undefined");
146767
+ logger189.warn("Could not convert from hue/saturation because one of them is undefined");
146645
146768
  break;
146646
146769
  }
146647
146770
  switch (newMode) {
@@ -146653,7 +146776,7 @@ var ColorControlBaseServer = class _ColorControlBaseServer extends ColorControlB
146653
146776
  case ColorControl3.ColorMode.ColorTemperatureMireds:
146654
146777
  const mireds = hsvToMireds(this.hue, this.saturation);
146655
146778
  if (mireds === void 0) {
146656
- logger188.warn(
146779
+ logger189.warn(
146657
146780
  `Could not convert hue/saturation (${this.hue}/${this.saturation}) to color temperature`
146658
146781
  );
146659
146782
  } else {
@@ -146664,7 +146787,7 @@ var ColorControlBaseServer = class _ColorControlBaseServer extends ColorControlB
146664
146787
  break;
146665
146788
  case ColorControl3.ColorMode.CurrentXAndCurrentY:
146666
146789
  if (this.state.currentX === void 0 || this.state.currentY === void 0) {
146667
- logger188.warn("Could not convert from xy because one of them is undefined");
146790
+ logger189.warn("Could not convert from xy because one of them is undefined");
146668
146791
  break;
146669
146792
  }
146670
146793
  switch (newMode) {
@@ -146676,7 +146799,7 @@ var ColorControlBaseServer = class _ColorControlBaseServer extends ColorControlB
146676
146799
  case ColorControl3.ColorMode.ColorTemperatureMireds:
146677
146800
  const mireds = xyToMireds(this.x, this.y);
146678
146801
  if (mireds === void 0) {
146679
- logger188.warn(`Could not convert xy ${this.x / this.y} to color temperature`);
146802
+ logger189.warn(`Could not convert xy ${this.x / this.y} to color temperature`);
146680
146803
  } else {
146681
146804
  this.mireds = mireds;
146682
146805
  }
@@ -146685,14 +146808,14 @@ var ColorControlBaseServer = class _ColorControlBaseServer extends ColorControlB
146685
146808
  break;
146686
146809
  case ColorControl3.ColorMode.ColorTemperatureMireds:
146687
146810
  if (this.state.colorTemperatureMireds === void 0) {
146688
- logger188.warn("Could not convert from color temperature because it is undefined");
146811
+ logger189.warn("Could not convert from color temperature because it is undefined");
146689
146812
  break;
146690
146813
  }
146691
146814
  switch (newMode) {
146692
146815
  case ColorControl3.ColorMode.CurrentHueAndCurrentSaturation:
146693
146816
  const hsvResult = miredsToHsv(this.mireds);
146694
146817
  if (hsvResult === void 0) {
146695
- logger188.warn(`Could not convert color temperature ${this.mireds} to hue/saturation`);
146818
+ logger189.warn(`Could not convert color temperature ${this.mireds} to hue/saturation`);
146696
146819
  } else {
146697
146820
  const [hue, saturation] = hsvResult;
146698
146821
  this.hue = hue;
@@ -146702,7 +146825,7 @@ var ColorControlBaseServer = class _ColorControlBaseServer extends ColorControlB
146702
146825
  case ColorControl3.ColorMode.CurrentXAndCurrentY:
146703
146826
  const xyResult = miredsToXy(this.mireds);
146704
146827
  if (xyResult === void 0) {
146705
- logger188.warn("Could not convert color temperature to xy");
146828
+ logger189.warn("Could not convert color temperature to xy");
146706
146829
  } else {
146707
146830
  const [x, y] = xyResult;
146708
146831
  this.x = x;
@@ -146751,7 +146874,7 @@ var ColorControlBaseServer = class _ColorControlBaseServer extends ColorControlB
146751
146874
  );
146752
146875
  newColorTemp = tempPhysMax - tempDelta;
146753
146876
  }
146754
- logger188.debug(`Synced color temperature with level: ${level}, new color temperature: ${newColorTemp}`);
146877
+ logger189.debug(`Synced color temperature with level: ${level}, new color temperature: ${newColorTemp}`);
146755
146878
  return this.moveToColorTemperatureLogic(newColorTemp, 0);
146756
146879
  }
146757
146880
  #assertRate(mode, rate) {
@@ -146955,7 +147078,7 @@ var ColorControlBaseServer = class _ColorControlBaseServer extends ColorControlB
146955
147078
  targetEnhancedColorMode = values4.enhancedColorMode;
146956
147079
  }
146957
147080
  if (!this.#supportsColorMode(targetEnhancedColorMode)) {
146958
- logger188.info(
147081
+ logger189.info(
146959
147082
  `Can not apply scene with unsupported color mode: ${ColorControl3.EnhancedColorMode[targetEnhancedColorMode]} (${targetEnhancedColorMode})`
146960
147083
  );
146961
147084
  }
@@ -146997,7 +147120,7 @@ var ColorControlBaseServer = class _ColorControlBaseServer extends ColorControlB
146997
147120
  }
146998
147121
  break;
146999
147122
  default:
147000
- logger188.info(
147123
+ logger189.info(
147001
147124
  `No supported color mode found to apply scene: ${ColorControl3.EnhancedColorMode[targetEnhancedColorMode]} (${targetEnhancedColorMode})`
147002
147125
  );
147003
147126
  break;
@@ -147092,7 +147215,7 @@ var LevelControlBehaviorConstructor = ClusterBehavior.for(LevelControl3);
147092
147215
  var LevelControlBehavior = LevelControlBehaviorConstructor;
147093
147216
 
147094
147217
  // ../../node_modules/.pnpm/@matter+node@0.17.9_patch_hash=e3376f1c2fdd0a0df09b8d04625a4d7af65b47d7277cc88cdb80baf7fb32bd0d/node_modules/@matter/node/dist/esm/behaviors/level-control/LevelControlServer.js
147095
- var logger189 = Logger.get("LevelControlServer");
147218
+ var logger190 = Logger.get("LevelControlServer");
147096
147219
  var LevelControlBase = LevelControlBehavior.with(LevelControl3.Feature.OnOff, LevelControl3.Feature.Lighting);
147097
147220
  var LevelControlBaseServer = class _LevelControlBaseServer extends LevelControlBase {
147098
147221
  /** Returns the minimum level, including feature specific fallback value handling. */
@@ -147185,17 +147308,17 @@ var LevelControlBaseServer = class _LevelControlBaseServer extends LevelControlB
147185
147308
  */
147186
147309
  initializeLighting() {
147187
147310
  if (this.state.currentLevel === 0) {
147188
- logger189.warn(
147311
+ logger190.warn(
147189
147312
  `The currentLevel value of ${this.state.currentLevel} is invalid according to Matter specification. The value must not be 0.`
147190
147313
  );
147191
147314
  }
147192
147315
  if (this.minLevel !== 1) {
147193
- logger189.warn(
147316
+ logger190.warn(
147194
147317
  `The minLevel value of ${this.minLevel} is invalid according to Matter specification. The value should be 1.`
147195
147318
  );
147196
147319
  }
147197
147320
  if (this.maxLevel !== 254) {
147198
- logger189.warn(
147321
+ logger190.warn(
147199
147322
  `The maxLevel value of ${this.maxLevel} is invalid according to Matter specification. The value should be 254.`
147200
147323
  );
147201
147324
  }
@@ -147506,7 +147629,7 @@ var LevelControlBaseServer = class _LevelControlBaseServer extends LevelControlB
147506
147629
  if (!onOff || this.state.onLevel === null) {
147507
147630
  return;
147508
147631
  }
147509
- logger189.debug(`OnOff changed to ON, setting level to onLevel value of ${this.state.onLevel}`);
147632
+ logger190.debug(`OnOff changed to ON, setting level to onLevel value of ${this.state.onLevel}`);
147510
147633
  this.state.currentLevel = this.state.onLevel;
147511
147634
  }
147512
147635
  #calculateEffectiveOptions(optionsMask, optionsOverride) {
@@ -150569,7 +150692,7 @@ var ModeSelectBehaviorConstructor = ClusterBehavior.for(ModeSelect3);
150569
150692
  var ModeSelectBehavior = ModeSelectBehaviorConstructor;
150570
150693
 
150571
150694
  // ../../node_modules/.pnpm/@matter+node@0.17.9_patch_hash=e3376f1c2fdd0a0df09b8d04625a4d7af65b47d7277cc88cdb80baf7fb32bd0d/node_modules/@matter/node/dist/esm/behaviors/mode-select/ModeSelectServer.js
150572
- var logger190 = Logger.get("ModeSelectServer");
150695
+ var logger191 = Logger.get("ModeSelectServer");
150573
150696
  var ModeSelectBase = ModeSelectBehavior.with(ModeSelect3.Feature.OnOff);
150574
150697
  var ModeSelectBaseServer = class extends ModeSelectBase {
150575
150698
  initialize() {
@@ -150586,7 +150709,7 @@ var ModeSelectBaseServer = class extends ModeSelectBase {
150586
150709
  }
150587
150710
  this.reactTo(onOffServer.events.onOff$Changed, this.#handleOnOffDependency);
150588
150711
  } else {
150589
- logger190.warn("OnOffServer not found on endpoint, but OnMode is set.");
150712
+ logger191.warn("OnOffServer not found on endpoint, but OnMode is set.");
150590
150713
  }
150591
150714
  }
150592
150715
  if (!currentModeOverridden && this.state.startUpMode !== void 0 && this.state.startUpMode !== null && this.#getBootReason() !== GeneralDiagnostics3.BootReason.SoftwareUpdateCompleted) {
@@ -151787,7 +151910,7 @@ init_esm3();
151787
151910
 
151788
151911
  // ../../node_modules/.pnpm/@matter+node@0.17.9_patch_hash=e3376f1c2fdd0a0df09b8d04625a4d7af65b47d7277cc88cdb80baf7fb32bd0d/node_modules/@matter/node/dist/esm/behaviors/thermostat/AtomicWriteState.js
151789
151912
  init_esm();
151790
- var logger191 = Logger.get("AtomicWriteState");
151913
+ var logger192 = Logger.get("AtomicWriteState");
151791
151914
  var MAXIMUM_ALLOWED_TIMEOUT = Seconds(9);
151792
151915
  var AtomicWriteState = class {
151793
151916
  peerAddress;
@@ -151822,19 +151945,19 @@ var AtomicWriteState = class {
151822
151945
  });
151823
151946
  }
151824
151947
  start() {
151825
- logger191.debug(
151948
+ logger192.debug(
151826
151949
  `Starting atomic write state for peer ${this.peerAddress.toString()} on endpoint ${this.endpoint.id}`
151827
151950
  );
151828
151951
  this.#timer.start();
151829
151952
  }
151830
151953
  #timeoutTriggered() {
151831
- logger191.debug(
151954
+ logger192.debug(
151832
151955
  `Atomic write state for peer ${this.peerAddress.toString()} on endpoint ${this.endpoint.id} timed out`
151833
151956
  );
151834
151957
  this.close();
151835
151958
  }
151836
151959
  close() {
151837
- logger191.debug(
151960
+ logger192.debug(
151838
151961
  `Closing atomic write state for peer ${this.peerAddress.toString()} on endpoint ${this.endpoint.id}`
151839
151962
  );
151840
151963
  if (this.#timer.isRunning) {
@@ -151845,7 +151968,7 @@ var AtomicWriteState = class {
151845
151968
  };
151846
151969
 
151847
151970
  // ../../node_modules/.pnpm/@matter+node@0.17.9_patch_hash=e3376f1c2fdd0a0df09b8d04625a4d7af65b47d7277cc88cdb80baf7fb32bd0d/node_modules/@matter/node/dist/esm/behaviors/thermostat/AtomicWriteHandler.js
151848
- var logger192 = Logger.get("AtomicWriteHandler");
151971
+ var logger193 = Logger.get("AtomicWriteHandler");
151849
151972
  var AtomicWriteHandler = class _AtomicWriteHandler {
151850
151973
  #observers = new ObserverGroup();
151851
151974
  #pendingWrites = new BasicSet();
@@ -151907,7 +152030,7 @@ var AtomicWriteHandler = class _AtomicWriteHandler {
151907
152030
  this.#pendingWrites.add(state);
151908
152031
  state.closed.on(() => void this.#pendingWrites.delete(state));
151909
152032
  state.start();
151910
- logger192.debug("Added atomic write state:", state);
152033
+ logger193.debug("Added atomic write state:", state);
151911
152034
  return state;
151912
152035
  }
151913
152036
  if (existingState === void 0) {
@@ -151982,14 +152105,14 @@ var AtomicWriteHandler = class _AtomicWriteHandler {
151982
152105
  writeAttribute(context, endpoint, cluster2, attribute, value) {
151983
152106
  const state = this.#assertPendingWriteForAttributeAndPeer(context, endpoint, cluster2, attribute);
151984
152107
  const attributeName = state.attributeNames.get(attribute);
151985
- logger192.debug(`Writing pending value for attribute ${attributeName}, ${attribute} in atomic write`, value);
152108
+ logger193.debug(`Writing pending value for attribute ${attributeName}, ${attribute} in atomic write`, value);
151986
152109
  endpoint.eventsOf(cluster2.id)[`${attributeName}$AtomicChanging`]?.emit(
151987
152110
  value,
151988
152111
  state.pendingAttributeValues[attribute] !== void 0 ? state.pendingAttributeValues[attribute] : state.initialValues[attribute],
151989
152112
  context
151990
152113
  );
151991
152114
  state.pendingAttributeValues[attribute] = value;
151992
- logger192.debug("Atomic write state after current write:", state);
152115
+ logger193.debug("Atomic write state after current write:", state);
151993
152116
  }
151994
152117
  /**
151995
152118
  * Implements the commit logic for an atomic write.
@@ -152008,7 +152131,7 @@ var AtomicWriteHandler = class _AtomicWriteHandler {
152008
152131
  await context.transaction?.commit();
152009
152132
  } catch (error) {
152010
152133
  await context.transaction?.rollback();
152011
- logger192.info(
152134
+ logger193.info(
152012
152135
  `Failed to write attribute ${attr} during atomic write commit:`,
152013
152136
  Diagnostic.errorMessage(asError(error))
152014
152137
  );
@@ -152047,7 +152170,7 @@ var AtomicWriteHandler = class _AtomicWriteHandler {
152047
152170
  const fabricIndex = fabric.fabricIndex;
152048
152171
  for (const writeState of Array.from(this.#pendingWrites)) {
152049
152172
  if (writeState.peerAddress.fabricIndex === fabricIndex) {
152050
- logger192.debug(
152173
+ logger193.debug(
152051
152174
  `Closing atomic write state for peer ${writeState.peerAddress.toString()} on endpoint ${writeState.endpoint.id} due to fabric removal`
152052
152175
  );
152053
152176
  writeState.close();
@@ -152096,7 +152219,7 @@ var AtomicWriteHandler = class _AtomicWriteHandler {
152096
152219
  if (!PeerAddress.is(attrWriteState.peerAddress, peerAddress)) {
152097
152220
  return void 0;
152098
152221
  }
152099
- logger192.debug(
152222
+ logger193.debug(
152100
152223
  `Found pending value for attribute ${attribute} for peer ${peerAddress.nodeId}`,
152101
152224
  serialize(attrWriteState.pendingAttributeValues[attribute])
152102
152225
  );
@@ -152137,7 +152260,7 @@ var ThermostatBehaviorConstructor = ClusterBehavior.for(Thermostat3);
152137
152260
  var ThermostatBehavior = ThermostatBehaviorConstructor;
152138
152261
 
152139
152262
  // ../../node_modules/.pnpm/@matter+node@0.17.9_patch_hash=e3376f1c2fdd0a0df09b8d04625a4d7af65b47d7277cc88cdb80baf7fb32bd0d/node_modules/@matter/node/dist/esm/behaviors/thermostat/ThermostatServer.js
152140
- var logger193 = Logger.get("ThermostatServer");
152263
+ var logger194 = Logger.get("ThermostatServer");
152141
152264
  var ThermostatBehaviorLogicBase = ThermostatBehavior.with(
152142
152265
  Thermostat3.Feature.Heating,
152143
152266
  Thermostat3.Feature.Cooling,
@@ -152166,7 +152289,7 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
152166
152289
  throw new ImplementationError("Setback feature is deprecated and not allowed to be enabled");
152167
152290
  }
152168
152291
  if (this.features.matterScheduleConfiguration) {
152169
- logger193.warn("MatterScheduleConfiguration feature is not yet implemented. Please do not activate it");
152292
+ logger194.warn("MatterScheduleConfiguration feature is not yet implemented. Please do not activate it");
152170
152293
  }
152171
152294
  if (!this.features.presets && !this.features.matterScheduleConfiguration) {
152172
152295
  this.atomicRequest = Behavior.unimplemented;
@@ -152287,7 +152410,7 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
152287
152410
  throw new StatusResponse.InvalidCommandError("Requested PresetHandle not found");
152288
152411
  }
152289
152412
  }
152290
- logger193.info(`Setting active preset handle to`, presetHandle);
152413
+ logger194.info(`Setting active preset handle to`, presetHandle);
152291
152414
  this.state.activePresetHandle = presetHandle;
152292
152415
  return preset;
152293
152416
  }
@@ -152357,7 +152480,7 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
152357
152480
  }
152358
152481
  if (this.state.setpointHoldExpiryTimestamp === void 0) {
152359
152482
  } else {
152360
- logger193.warn(
152483
+ logger194.warn(
152361
152484
  "Handling for setpointHoldExpiryTimestamp is not yet implemented. To use this attribute you need to install the needed logic yourself"
152362
152485
  );
152363
152486
  }
@@ -152430,7 +152553,7 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
152430
152553
  "RemoteSensing cannot be set to LocalTemperature when LocalTemperatureNotExposed feature is enabled"
152431
152554
  );
152432
152555
  }
152433
- logger193.debug("LocalTemperatureNotExposed feature is enabled, ignoring local temperature measurement");
152556
+ logger194.debug("LocalTemperatureNotExposed feature is enabled, ignoring local temperature measurement");
152434
152557
  this.state.localTemperature = null;
152435
152558
  }
152436
152559
  let localTemperature = null;
@@ -152439,11 +152562,11 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
152439
152562
  const endpoints = this.env.get(ServerNode).endpoints;
152440
152563
  const endpoint = endpoints.has(localTempEndpoint) ? endpoints.for(localTempEndpoint) : void 0;
152441
152564
  if (endpoint !== void 0 && endpoint.behaviors.has(TemperatureMeasurementServer)) {
152442
- logger193.debug(
152565
+ logger194.debug(
152443
152566
  `Using existing TemperatureMeasurement cluster on endpoint #${localTempEndpoint} for local temperature measurement`
152444
152567
  );
152445
152568
  if (this.state.externalMeasuredIndoorTemperature !== void 0) {
152446
- logger193.warn(
152569
+ logger194.warn(
152447
152570
  "Both local TemperatureMeasurement cluster and externalMeasuredIndoorTemperature state are set, using local cluster"
152448
152571
  );
152449
152572
  }
@@ -152453,19 +152576,19 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
152453
152576
  );
152454
152577
  localTemperature = endpoint.stateOf(TemperatureMeasurementServer).measuredValue;
152455
152578
  } else {
152456
- logger193.warn(
152579
+ logger194.warn(
152457
152580
  `No TemperatureMeasurement cluster found on endpoint #${localTempEndpoint}, falling back to externalMeasuredIndoorTemperature state if set`
152458
152581
  );
152459
152582
  }
152460
152583
  } else {
152461
152584
  if (this.state.externalMeasuredIndoorTemperature === void 0) {
152462
152585
  if (this.state.localTemperatureCalibration !== void 0) {
152463
- logger193.warn(
152586
+ logger194.warn(
152464
152587
  "No local TemperatureMeasurement cluster available, externalMeasuredIndoorTemperature state not set but localTemperatureCalibration is used: Ensure to correctly consider the calibration when updating the localTemperature value"
152465
152588
  );
152466
152589
  }
152467
152590
  } else {
152468
- logger193.info("Using measured temperature via externalMeasuredIndoorTemperature state");
152591
+ logger194.info("Using measured temperature via externalMeasuredIndoorTemperature state");
152469
152592
  localTemperature = this.state.externalMeasuredIndoorTemperature ?? null;
152470
152593
  }
152471
152594
  this.reactTo(this.events.externalMeasuredIndoorTemperature$Changed, this.#handleMeasuredTemperatureChange);
@@ -152505,28 +152628,28 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
152505
152628
  const endpoints = this.env.get(ServerNode).endpoints;
152506
152629
  const endpoint = endpoints.has(localOccupancyEndpoint) ? endpoints.for(localOccupancyEndpoint) : void 0;
152507
152630
  if (endpoint !== void 0 && endpoint.behaviors.has(OccupancySensingServer)) {
152508
- logger193.debug(
152631
+ logger194.debug(
152509
152632
  `Using existing OccupancySensing cluster on endpoint ${localOccupancyEndpoint} for local occupancy sensing`
152510
152633
  );
152511
152634
  if (this.state.externallyMeasuredOccupancy !== void 0) {
152512
- logger193.warn(
152635
+ logger194.warn(
152513
152636
  "Both local OccupancySensing cluster and externallyMeasuredOccupancy state are set, using local cluster"
152514
152637
  );
152515
152638
  }
152516
152639
  this.reactTo(endpoint.eventsOf(OccupancySensingServer).occupancy$Changed, this.#handleOccupancyChange);
152517
152640
  currentOccupancy = !!endpoint.stateOf(OccupancySensingServer).occupancy.occupied;
152518
152641
  } else {
152519
- logger193.warn(
152642
+ logger194.warn(
152520
152643
  `No OccupancySensing cluster found on endpoint ${localOccupancyEndpoint}, falling back to externallyMeasuredOccupancy state if set`
152521
152644
  );
152522
152645
  }
152523
152646
  } else {
152524
152647
  if (this.state.externallyMeasuredOccupancy === void 0) {
152525
- logger193.warn(
152648
+ logger194.warn(
152526
152649
  "No local OccupancySensing cluster available and externallyMeasuredOccupancy state not set"
152527
152650
  );
152528
152651
  } else {
152529
- logger193.info("Using occupancy via externallyMeasuredOccupancy state");
152652
+ logger194.info("Using occupancy via externallyMeasuredOccupancy state");
152530
152653
  currentOccupancy = this.state.externallyMeasuredOccupancy;
152531
152654
  }
152532
152655
  this.reactTo(this.events.externallyMeasuredOccupancy$Changed, this.#handleExternalOccupancyChange);
@@ -152769,7 +152892,7 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
152769
152892
  max = this.state[`max${scope}`] ?? defaults.absMax,
152770
152893
  absMax = this.state[`absMax${scope}`] ?? defaults.absMax
152771
152894
  } = details;
152772
- logger193.debug(
152895
+ logger194.debug(
152773
152896
  `Validating user setpoint limits for ${scope}: absMin=${absMin}, min=${min}, max=${max}, absMax=${absMax}`
152774
152897
  );
152775
152898
  if (absMin > min) {
@@ -152816,7 +152939,7 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
152816
152939
  const limitMax = scope === "Heat" ? this.heatSetpointMaximum : this.coolSetpointMaximum;
152817
152940
  const result = cropValueRange(setpoint, limitMin, limitMax);
152818
152941
  if (result !== setpoint) {
152819
- logger193.debug(
152942
+ logger194.debug(
152820
152943
  `${scope} setpoint (${setpoint}) is out of limits [${limitMin}, ${limitMax}], clamping to ${result}`
152821
152944
  );
152822
152945
  }
@@ -153296,7 +153419,7 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
153296
153419
  */
153297
153420
  #handlePersistedPresetsChanged(newPresets, oldPresets) {
153298
153421
  if (oldPresets === void 0) {
153299
- logger193.debug(
153422
+ logger194.debug(
153300
153423
  "Old presets is undefined, skipping some checks. This should only happen on setup of the behavior."
153301
153424
  );
153302
153425
  }
@@ -153305,7 +153428,7 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
153305
153428
  const newPresetHandles = /* @__PURE__ */ new Set();
153306
153429
  for (const preset of newPresets) {
153307
153430
  if (preset.presetHandle === null) {
153308
- logger193.debug("Preset is missing presetHandle, generating a new one");
153431
+ logger194.debug("Preset is missing presetHandle, generating a new one");
153309
153432
  preset.presetHandle = entropy.randomBytes(16);
153310
153433
  changed = true;
153311
153434
  }
@@ -153354,7 +153477,7 @@ var ThermostatBaseServer = class _ThermostatBaseServer extends ThermostatBehavio
153354
153477
  throw new StatusResponse.InvalidInStateError(`ActivePresetHandle references non-existing presetHandle`);
153355
153478
  }
153356
153479
  if (changed) {
153357
- logger193.debug("PresetHandles or BuiltIn flags were updated, updating persistedPresets");
153480
+ logger194.debug("PresetHandles or BuiltIn flags were updated, updating persistedPresets");
153358
153481
  this.state.persistedPresets = newPresets;
153359
153482
  }
153360
153483
  }
@@ -154443,7 +154566,7 @@ var WindowCoveringBehaviorConstructor = ClusterBehavior.for(WindowCovering3);
154443
154566
  var WindowCoveringBehavior = WindowCoveringBehaviorConstructor;
154444
154567
 
154445
154568
  // ../../node_modules/.pnpm/@matter+node@0.17.9_patch_hash=e3376f1c2fdd0a0df09b8d04625a4d7af65b47d7277cc88cdb80baf7fb32bd0d/node_modules/@matter/node/dist/esm/behaviors/window-covering/WindowCoveringServer.js
154446
- var logger194 = Logger.get("WindowCoveringServer");
154569
+ var logger195 = Logger.get("WindowCoveringServer");
154447
154570
  var WindowCoveringBase = WindowCoveringBehavior.with(
154448
154571
  WindowCovering3.Feature.Lift,
154449
154572
  WindowCovering3.Feature.Tilt,
@@ -154528,7 +154651,7 @@ var WindowCoveringBaseServer = class extends WindowCoveringBase {
154528
154651
  this.state.configStatus = configStatus;
154529
154652
  });
154530
154653
  }
154531
- logger194.debug(
154654
+ logger195.debug(
154532
154655
  `Mode changed to ${Diagnostic.json(mode)} and config status to ${Diagnostic.json(configStatus)} and internal calibration mode to ${this.internal.calibrationMode}`
154533
154656
  );
154534
154657
  }
@@ -154536,7 +154659,7 @@ var WindowCoveringBaseServer = class extends WindowCoveringBase {
154536
154659
  #handleOperationalStatusChanging(operationalStatus) {
154537
154660
  const globalStatus = operationalStatus.lift !== WindowCovering3.MovementStatus.Stopped ? operationalStatus.lift : operationalStatus.tilt;
154538
154661
  operationalStatus.global = globalStatus;
154539
- logger194.debug(
154662
+ logger195.debug(
154540
154663
  `Operational status changed to ${Diagnostic.json(operationalStatus)} with new global status ${globalStatus}`
154541
154664
  );
154542
154665
  this.state.operationalStatus = operationalStatus;
@@ -154565,10 +154688,10 @@ var WindowCoveringBaseServer = class extends WindowCoveringBase {
154565
154688
  this.state.currentPositionLiftPercentage = percent100ths3 === null ? percent100ths3 : Math.floor(percent100ths3 / WC_PERCENT100THS_COEFFICIENT);
154566
154689
  if (this.state.operationalStatus.lift !== WindowCovering3.MovementStatus.Stopped && percent100ths3 === this.state.targetPositionLiftPercent100ths) {
154567
154690
  this.state.operationalStatus.lift = WindowCovering3.MovementStatus.Stopped;
154568
- logger194.debug("Lift movement stopped, target value reached");
154691
+ logger195.debug("Lift movement stopped, target value reached");
154569
154692
  }
154570
154693
  }
154571
- logger194.debug(
154694
+ logger195.debug(
154572
154695
  `Syncing lift position ${this.state.currentPositionLiftPercent100ths === null ? null : (this.state.currentPositionLiftPercent100ths / 100).toFixed(2)} to ${this.state.currentPositionLiftPercentage}%`
154573
154696
  );
154574
154697
  }
@@ -154578,10 +154701,10 @@ var WindowCoveringBaseServer = class extends WindowCoveringBase {
154578
154701
  this.state.currentPositionTiltPercentage = percent100ths3 === null ? percent100ths3 : Math.floor(percent100ths3 / WC_PERCENT100THS_COEFFICIENT);
154579
154702
  if (this.state.operationalStatus.tilt !== WindowCovering3.MovementStatus.Stopped && percent100ths3 === this.state.targetPositionTiltPercent100ths) {
154580
154703
  this.state.operationalStatus.tilt = WindowCovering3.MovementStatus.Stopped;
154581
- logger194.debug("Tilt movement stopped, target value reached");
154704
+ logger195.debug("Tilt movement stopped, target value reached");
154582
154705
  }
154583
154706
  }
154584
- logger194.debug(
154707
+ logger195.debug(
154585
154708
  `Syncing tilt position ${this.state.currentPositionTiltPercent100ths === null ? null : (this.state.currentPositionTiltPercent100ths / 100).toFixed(2)} to ${this.state.currentPositionTiltPercentage}%`
154586
154709
  );
154587
154710
  }
@@ -154669,7 +154792,7 @@ var WindowCoveringBaseServer = class extends WindowCoveringBase {
154669
154792
  }
154670
154793
  const directionInfo = direction === 2 ? ` in direction by position` : ` in direction ${direction === 1 ? "Close" : "Open"}`;
154671
154794
  const targetInfo = targetPercent100ths === void 0 ? "" : ` to target position ${(targetPercent100ths / 100).toFixed(2)}`;
154672
- logger194.debug(
154795
+ logger195.debug(
154673
154796
  `Moving the device ${type === 0 ? "Lift" : "Tilt"}${directionInfo} (reversed=${reversed})${targetInfo}`
154674
154797
  );
154675
154798
  }
@@ -154691,7 +154814,7 @@ var WindowCoveringBaseServer = class extends WindowCoveringBase {
154691
154814
  );
154692
154815
  }
154693
154816
  if (type === 0 && this.state.configStatus.liftMovementReversed) {
154694
- logger194.debug("Lift movement is reversed");
154817
+ logger195.debug("Lift movement is reversed");
154695
154818
  }
154696
154819
  switch (type) {
154697
154820
  case 0:
@@ -154947,7 +155070,7 @@ function matterSubscriptionOptions() {
154947
155070
  }
154948
155071
 
154949
155072
  // src/matter/endpoints/server-mode-server-node.ts
154950
- var logger195 = Logger.get("ServerModeServerNode");
155073
+ var logger196 = Logger.get("ServerModeServerNode");
154951
155074
  var ServerModeServerNode = class extends ServerNode {
154952
155075
  deviceEndpoints = /* @__PURE__ */ new Map();
154953
155076
  featureFlags;
@@ -155044,7 +155167,7 @@ var ServerModeServerNode = class extends ServerNode {
155044
155167
  await this.set({ basicInformation });
155045
155168
  } catch (e) {
155046
155169
  const msg = e instanceof Error ? e.message : String(e);
155047
- logger195.warn(
155170
+ logger196.warn(
155048
155171
  `Failed to apply server-mode identity for ${entityId}: ${msg}`
155049
155172
  );
155050
155173
  }
@@ -155055,7 +155178,7 @@ var ServerModeServerNode = class extends ServerNode {
155055
155178
  await this.set({ productDescription: { deviceType } });
155056
155179
  } catch (e) {
155057
155180
  const msg = e instanceof Error ? e.message : String(e);
155058
- logger195.warn(`Failed to set server-mode device type: ${msg}`);
155181
+ logger196.warn(`Failed to set server-mode device type: ${msg}`);
155059
155182
  }
155060
155183
  }
155061
155184
  async factoryReset() {
@@ -155075,84 +155198,6 @@ function dropUndefined(obj) {
155075
155198
 
155076
155199
  // src/plugins/builtin/camera/camera-tcp-requirement.ts
155077
155200
  import { readFileSync as readFileSync6 } from "node:fs";
155078
-
155079
- // src/plugins/plugin-storage.ts
155080
- init_esm();
155081
- import * as fs9 from "node:fs";
155082
- import * as path11 from "node:path";
155083
- var logger196 = Logger.get("PluginStorage");
155084
- var SAVE_DEBOUNCE_MS = 500;
155085
- function pluginStorageFilePath(storageDir, bridgeId, pluginName) {
155086
- const safe = (s) => s.replace(/[^a-zA-Z0-9_-]/g, "_");
155087
- return path11.join(
155088
- storageDir,
155089
- `plugin-${safe(bridgeId)}-${safe(pluginName)}.json`
155090
- );
155091
- }
155092
- var FilePluginStorage = class {
155093
- data = {};
155094
- dirty = false;
155095
- filePath;
155096
- saveTimer;
155097
- constructor(storageDir, bridgeId, pluginName) {
155098
- this.filePath = pluginStorageFilePath(storageDir, bridgeId, pluginName);
155099
- this.load();
155100
- }
155101
- async get(key, defaultValue) {
155102
- const value = this.data[key];
155103
- return value ?? defaultValue;
155104
- }
155105
- async set(key, value) {
155106
- this.data[key] = value;
155107
- this.dirty = true;
155108
- this.scheduleSave();
155109
- }
155110
- async delete(key) {
155111
- delete this.data[key];
155112
- this.dirty = true;
155113
- this.scheduleSave();
155114
- }
155115
- async keys() {
155116
- return Object.keys(this.data);
155117
- }
155118
- load() {
155119
- try {
155120
- if (fs9.existsSync(this.filePath)) {
155121
- const raw = fs9.readFileSync(this.filePath, "utf-8");
155122
- this.data = JSON.parse(raw);
155123
- }
155124
- } catch (e) {
155125
- logger196.warn(`Failed to load plugin storage from ${this.filePath}:`, e);
155126
- this.data = {};
155127
- }
155128
- }
155129
- scheduleSave() {
155130
- if (this.saveTimer) clearTimeout(this.saveTimer);
155131
- this.saveTimer = setTimeout(() => this.save(), SAVE_DEBOUNCE_MS);
155132
- }
155133
- save() {
155134
- if (!this.dirty) return;
155135
- if (this.saveTimer) {
155136
- clearTimeout(this.saveTimer);
155137
- this.saveTimer = void 0;
155138
- }
155139
- try {
155140
- const dir = path11.dirname(this.filePath);
155141
- if (!fs9.existsSync(dir)) {
155142
- fs9.mkdirSync(dir, { recursive: true });
155143
- }
155144
- fs9.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2));
155145
- this.dirty = false;
155146
- } catch (e) {
155147
- logger196.warn(`Failed to save plugin storage to ${this.filePath}:`, e);
155148
- }
155149
- }
155150
- flush() {
155151
- this.save();
155152
- }
155153
- };
155154
-
155155
- // src/plugins/builtin/camera/camera-tcp-requirement.ts
155156
155201
  var CAMERA_TCP_CONFIG = { incoming: true, outgoing: false };
155157
155202
  function parseCameraList(cameras) {
155158
155203
  if (typeof cameras !== "string") return [];
@@ -156836,6 +156881,28 @@ var SafePluginRunner = class {
156836
156881
  return void 0;
156837
156882
  }
156838
156883
  }
156884
+ /**
156885
+ * Run a shutdown-class hook with timeout only. An open breaker never blocks
156886
+ * it and its failures never count toward the breaker: cleanup has to run
156887
+ * exactly when a plugin is at its most broken, or timers and sockets leak.
156888
+ */
156889
+ async runCleanup(pluginName, operation, fn, timeoutMs = DEFAULT_TIMEOUT_MS) {
156890
+ const timeout = this.createTimeout(pluginName, operation, timeoutMs);
156891
+ try {
156892
+ const result = await Promise.race([
156893
+ Promise.resolve().then(fn),
156894
+ timeout.promise
156895
+ ]);
156896
+ timeout.clear();
156897
+ return result;
156898
+ } catch (error) {
156899
+ timeout.clear();
156900
+ logger201.error(
156901
+ `Plugin "${pluginName}" failed during ${operation}: ${error instanceof Error ? error.message : String(error)}`
156902
+ );
156903
+ return void 0;
156904
+ }
156905
+ }
156839
156906
  /**
156840
156907
  * Run a synchronous plugin function with try/catch + circuit breaker.
156841
156908
  */
@@ -156886,6 +156953,8 @@ var SafePluginRunner = class {
156886
156953
  var logger202 = Logger.get("PluginManager");
156887
156954
  var PLUGIN_API_VERSION = 1;
156888
156955
  var MAX_PLUGIN_DEVICE_ID_LENGTH = 100;
156956
+ var LEGACY_ENABLED_KEY = "__enabled";
156957
+ var PLUGIN_CONFIG_KEY = "config";
156889
156958
  function validatePluginDevice(device) {
156890
156959
  if (!device || typeof device !== "object") return "device must be an object";
156891
156960
  const d = device;
@@ -156919,18 +156988,23 @@ var PluginManager = class {
156919
156988
  domainMappingOwners = /* @__PURE__ */ new Map();
156920
156989
  storageDir;
156921
156990
  bridgeId;
156991
+ stateFile;
156922
156992
  homeAssistant;
156923
156993
  runner = new SafePluginRunner();
156924
156994
  registry;
156925
156995
  /** Callback invoked when a plugin registers a new device */
156926
156996
  onDeviceRegistered;
156927
- /** Callback invoked when a plugin removes a device */
156997
+ /**
156998
+ * Callback invoked when a plugin removes a device. keepIdentity marks a
156999
+ * reversible stop: the endpoint closes but keeps its persisted number.
157000
+ */
156928
157001
  onDeviceUnregistered;
156929
157002
  /** Callback invoked when a plugin updates device state */
156930
157003
  onDeviceStateUpdated;
156931
157004
  constructor(bridgeId, storageDir, homeAssistant) {
156932
157005
  this.bridgeId = bridgeId;
156933
157006
  this.storageDir = storageDir;
157007
+ this.stateFile = pluginStateFilePath(storageDir, bridgeId);
156934
157008
  this.homeAssistant = homeAssistant;
156935
157009
  }
156936
157010
  setRegistry(registry3) {
@@ -157015,35 +157089,59 @@ var PluginManager = class {
157015
157089
  this.bridgeId,
157016
157090
  plugin.name
157017
157091
  );
157092
+ let enabled = this.readEnabledState()[plugin.name];
157093
+ const legacy = await storage2.get(LEGACY_ENABLED_KEY);
157094
+ if (typeof legacy === "boolean") {
157095
+ if (enabled === void 0) {
157096
+ enabled = legacy;
157097
+ this.persistEnabled(plugin.name, legacy);
157098
+ }
157099
+ await storage2.delete(LEGACY_ENABLED_KEY);
157100
+ storage2.flush();
157101
+ }
157102
+ if (enabled === false) {
157103
+ metadata.enabled = false;
157104
+ logger202.info(`Plugin "${plugin.name}" stays disabled (persisted)`);
157105
+ }
157018
157106
  const devices = /* @__PURE__ */ new Map();
157019
157107
  const pluginLogger = Logger.get(`Plugin:${plugin.name}`);
157108
+ const registerDeviceAt = (epoch) => async (device) => {
157109
+ const live = this.instances.get(plugin.name);
157110
+ if (!live || !live.metadata.enabled || live.epoch !== epoch) {
157111
+ pluginLogger.warn(
157112
+ `Dropped a device registration from a disabled or superseded start of "${plugin.name}"`
157113
+ );
157114
+ return;
157115
+ }
157116
+ const validationError = validatePluginDevice(device);
157117
+ if (validationError) {
157118
+ pluginLogger.warn(`Rejected device registration: ${validationError}`);
157119
+ return;
157120
+ }
157121
+ if (devices.has(device.id)) {
157122
+ pluginLogger.warn(
157123
+ `Device "${device.id}" already registered, updating`
157124
+ );
157125
+ }
157126
+ devices.set(device.id, device);
157127
+ await this.onDeviceRegistered?.(plugin.name, device);
157128
+ pluginLogger.debug(`Registered device: ${device.name} (${device.id})`);
157129
+ };
157020
157130
  const context = {
157021
157131
  bridgeId: this.bridgeId,
157022
157132
  storage: storage2,
157023
157133
  log: pluginLogger,
157024
157134
  homeAssistant: this.homeAssistant,
157025
- registerDevice: async (device) => {
157026
- const validationError = validatePluginDevice(device);
157027
- if (validationError) {
157028
- pluginLogger.warn(`Rejected device registration: ${validationError}`);
157029
- return;
157030
- }
157031
- if (devices.has(device.id)) {
157032
- pluginLogger.warn(
157033
- `Device "${device.id}" already registered, updating`
157034
- );
157035
- }
157036
- devices.set(device.id, device);
157037
- await this.onDeviceRegistered?.(plugin.name, device);
157038
- pluginLogger.debug(`Registered device: ${device.name} (${device.id})`);
157039
- },
157135
+ registerDevice: registerDeviceAt(0),
157040
157136
  unregisterDevice: async (deviceId) => {
157041
157137
  if (!devices.has(deviceId)) {
157042
157138
  pluginLogger.warn(`Device "${deviceId}" not found`);
157043
157139
  return;
157044
157140
  }
157045
157141
  devices.delete(deviceId);
157046
- await this.onDeviceUnregistered?.(plugin.name, deviceId);
157142
+ await this.onDeviceUnregistered?.(plugin.name, deviceId, {
157143
+ keepIdentity: this.instances.get(plugin.name)?.suspending === true
157144
+ });
157047
157145
  pluginLogger.debug(`Unregistered device: ${deviceId}`);
157048
157146
  },
157049
157147
  updateDeviceState: (deviceId, clusterId3, attributes9) => {
@@ -157082,7 +157180,14 @@ var PluginManager = class {
157082
157180
  context,
157083
157181
  metadata,
157084
157182
  devices,
157085
- started: false
157183
+ started: false,
157184
+ queue: Promise.resolve(),
157185
+ epoch: 0,
157186
+ contextAt: (epoch) => ({
157187
+ ...context,
157188
+ registerDevice: registerDeviceAt(epoch)
157189
+ }),
157190
+ suspending: false
157086
157191
  });
157087
157192
  logger202.info(
157088
157193
  `Registered plugin: ${plugin.name} v${plugin.version} (${metadata.source})`
@@ -157093,28 +157198,42 @@ var PluginManager = class {
157093
157198
  */
157094
157199
  async startAll() {
157095
157200
  for (const [name, instance] of this.instances) {
157096
- if (!instance.metadata.enabled) continue;
157097
- if (this.runner.isDisabled(name)) {
157098
- logger202.warn(
157099
- `Plugin "${name}" is disabled (circuit breaker), skipping start`
157100
- );
157101
- instance.metadata.enabled = false;
157102
- continue;
157103
- }
157104
- logger202.info(`Starting plugin: ${name}`);
157105
- await this.runner.run(
157106
- name,
157107
- "onStart",
157108
- () => instance.plugin.onStart(instance.context)
157201
+ await this.inTransition(instance, () => this.startPlugin(name, instance));
157202
+ }
157203
+ }
157204
+ // One transition at a time per plugin: a disable during a slow start waits
157205
+ // for the start to settle instead of interleaving with it.
157206
+ inTransition(instance, fn) {
157207
+ const run = instance.queue.then(fn, fn);
157208
+ instance.queue = run.then(
157209
+ () => void 0,
157210
+ () => void 0
157211
+ );
157212
+ return run;
157213
+ }
157214
+ async startPlugin(name, instance) {
157215
+ if (!instance.metadata.enabled) return;
157216
+ if (this.runner.isDisabled(name)) {
157217
+ logger202.warn(
157218
+ `Plugin "${name}" is disabled (circuit breaker), skipping start`
157109
157219
  );
157110
- if (this.runner.isDisabled(name)) {
157111
- instance.metadata.enabled = false;
157112
- } else if (this.runner.getState(name).failures === 0) {
157113
- instance.started = true;
157114
- }
157115
- if (instance.plugin.getCurrentConfig) {
157116
- instance.metadata.config = instance.plugin.getCurrentConfig();
157117
- }
157220
+ instance.metadata.enabled = false;
157221
+ return;
157222
+ }
157223
+ logger202.info(`Starting plugin: ${name}`);
157224
+ const epoch = ++instance.epoch;
157225
+ await this.runner.run(
157226
+ name,
157227
+ "onStart",
157228
+ () => instance.plugin.onStart(instance.contextAt(epoch))
157229
+ );
157230
+ if (this.runner.isDisabled(name)) {
157231
+ instance.metadata.enabled = false;
157232
+ } else if (this.runner.getState(name).failures === 0) {
157233
+ instance.started = true;
157234
+ }
157235
+ if (instance.plugin.getCurrentConfig) {
157236
+ instance.metadata.config = instance.plugin.getCurrentConfig();
157118
157237
  }
157119
157238
  }
157120
157239
  /**
@@ -157136,23 +157255,32 @@ var PluginManager = class {
157136
157255
  }
157137
157256
  }
157138
157257
  /**
157139
- * Shut down all plugins via SafePluginRunner.
157258
+ * Shut down all plugins. Runs outside the circuit breaker: cleanup must
157259
+ * happen even for a plugin the breaker took down.
157140
157260
  */
157141
157261
  async shutdownAll(reason) {
157142
157262
  for (const [name, instance] of this.instances) {
157143
- if (instance.started && instance.plugin.onShutdown) {
157144
- await this.runner.run(
157145
- name,
157146
- "onShutdown",
157147
- () => instance.plugin.onShutdown(reason)
157148
- );
157149
- }
157150
- const storage2 = instance.context.storage;
157151
- if (storage2 instanceof FilePluginStorage) {
157152
- storage2.flush();
157153
- }
157154
- instance.started = false;
157155
- logger202.info(`Plugin "${name}" shut down`);
157263
+ await this.inTransition(instance, async () => {
157264
+ instance.epoch++;
157265
+ instance.suspending = true;
157266
+ try {
157267
+ if (instance.started && instance.plugin.onShutdown) {
157268
+ await this.runner.runCleanup(
157269
+ name,
157270
+ "onShutdown",
157271
+ () => instance.plugin.onShutdown(reason)
157272
+ );
157273
+ }
157274
+ } finally {
157275
+ instance.suspending = false;
157276
+ }
157277
+ const storage2 = instance.context.storage;
157278
+ if (storage2 instanceof FilePluginStorage) {
157279
+ storage2.flush();
157280
+ }
157281
+ instance.started = false;
157282
+ logger202.info(`Plugin "${name}" shut down`);
157283
+ });
157156
157284
  }
157157
157285
  this.instances.clear();
157158
157286
  }
@@ -157185,23 +157313,122 @@ var PluginManager = class {
157185
157313
  instance.metadata.enabled = true;
157186
157314
  }
157187
157315
  }
157188
- disablePlugin(pluginName) {
157316
+ /**
157317
+ * Disable a plugin: stop it, unmount its devices, and persist the choice so
157318
+ * a bridge restart does not bring it back (#439). The shutdown runs outside
157319
+ * the circuit breaker, a broken plugin still has to release its resources.
157320
+ * Returns the resulting metadata, or undefined for an unknown name.
157321
+ */
157322
+ async disablePlugin(pluginName) {
157189
157323
  const instance = this.instances.get(pluginName);
157190
- if (instance) {
157324
+ if (!instance) return void 0;
157325
+ return this.inTransition(instance, async () => {
157191
157326
  instance.metadata.enabled = false;
157192
- }
157193
- for (const [domain, owner] of this.domainMappingOwners) {
157194
- if (owner === pluginName) {
157195
- this.domainMappings.delete(domain);
157196
- this.domainMappingOwners.delete(domain);
157327
+ instance.epoch++;
157328
+ this.persistEnabled(pluginName, false);
157329
+ instance.suspending = true;
157330
+ try {
157331
+ if (instance.started && instance.plugin.onShutdown) {
157332
+ await this.runner.runCleanup(
157333
+ pluginName,
157334
+ "onShutdown",
157335
+ () => instance.plugin.onShutdown("Plugin disabled")
157336
+ );
157337
+ }
157338
+ instance.started = false;
157339
+ for (const deviceId of [...instance.devices.keys()]) {
157340
+ instance.devices.delete(deviceId);
157341
+ await this.onDeviceUnregistered?.(pluginName, deviceId, {
157342
+ keepIdentity: true
157343
+ });
157344
+ }
157345
+ } finally {
157346
+ instance.suspending = false;
157197
157347
  }
157198
- }
157348
+ for (const [domain, owner] of this.domainMappingOwners) {
157349
+ if (owner === pluginName) {
157350
+ this.domainMappings.delete(domain);
157351
+ this.domainMappingOwners.delete(domain);
157352
+ }
157353
+ }
157354
+ return instance.metadata;
157355
+ });
157199
157356
  }
157200
- enablePlugin(pluginName) {
157201
- this.runner.resetCircuitBreaker(pluginName);
157357
+ /**
157358
+ * Enable a plugin, persist the choice, and start it right away so its
157359
+ * devices come back without a bridge restart. Returns the resulting
157360
+ * metadata, or undefined for an unknown name.
157361
+ */
157362
+ async enablePlugin(pluginName) {
157202
157363
  const instance = this.instances.get(pluginName);
157203
- if (instance) {
157364
+ if (!instance) return void 0;
157365
+ return this.inTransition(instance, async () => {
157366
+ this.runner.resetCircuitBreaker(pluginName);
157204
157367
  instance.metadata.enabled = true;
157368
+ this.persistEnabled(pluginName, true);
157369
+ if (instance.started) return instance.metadata;
157370
+ const pending = instance.pendingConfig;
157371
+ if (pending) {
157372
+ try {
157373
+ await instance.context.storage.set(PLUGIN_CONFIG_KEY, pending);
157374
+ await instance.context.storage.flush?.();
157375
+ instance.metadata.config = pending;
157376
+ instance.pendingConfig = void 0;
157377
+ } catch (e) {
157378
+ logger202.warn(
157379
+ `Failed to persist the parked config for "${pluginName}", it stays parked:`,
157380
+ e
157381
+ );
157382
+ }
157383
+ }
157384
+ logger202.info(`Starting plugin: ${pluginName}`);
157385
+ const epoch = ++instance.epoch;
157386
+ await this.runner.run(
157387
+ pluginName,
157388
+ "onStart",
157389
+ () => instance.plugin.onStart(instance.contextAt(epoch))
157390
+ );
157391
+ if (this.runner.isDisabled(pluginName)) {
157392
+ instance.metadata.enabled = false;
157393
+ return instance.metadata;
157394
+ }
157395
+ if (this.runner.getState(pluginName).failures === 0) {
157396
+ instance.started = true;
157397
+ }
157398
+ if (instance.plugin.getCurrentConfig) {
157399
+ instance.metadata.config = instance.plugin.getCurrentConfig();
157400
+ }
157401
+ if (instance.started && instance.plugin.onConfigure) {
157402
+ await this.runner.run(
157403
+ pluginName,
157404
+ "onConfigure",
157405
+ () => instance.plugin.onConfigure()
157406
+ );
157407
+ }
157408
+ return instance.metadata;
157409
+ });
157410
+ }
157411
+ readEnabledState() {
157412
+ try {
157413
+ if (fs10.existsSync(this.stateFile)) {
157414
+ return JSON.parse(fs10.readFileSync(this.stateFile, "utf-8"));
157415
+ }
157416
+ } catch (e) {
157417
+ logger202.warn(`Failed to read the plugin state file:`, e);
157418
+ }
157419
+ return {};
157420
+ }
157421
+ persistEnabled(pluginName, enabled) {
157422
+ try {
157423
+ const state = this.readEnabledState();
157424
+ state[pluginName] = enabled;
157425
+ fs10.mkdirSync(path12.dirname(this.stateFile), { recursive: true });
157426
+ fs10.writeFileSync(this.stateFile, JSON.stringify(state, null, 2));
157427
+ } catch (e) {
157428
+ logger202.warn(
157429
+ `Failed to persist enabled=${enabled} for "${pluginName}":`,
157430
+ e
157431
+ );
157205
157432
  }
157206
157433
  }
157207
157434
  getConfigSchema(pluginName) {
@@ -157215,27 +157442,36 @@ var PluginManager = class {
157215
157442
  async updateConfig(pluginName, config8) {
157216
157443
  const instance = this.instances.get(pluginName);
157217
157444
  if (!instance) return false;
157218
- config8 = { ...config8 };
157219
- const schema6 = instance.plugin.getConfigSchema?.();
157220
- if (schema6) {
157221
- for (const [key, prop] of Object.entries(schema6.properties)) {
157222
- if (prop.secret && config8[key] === SECRET_UNCHANGED) {
157223
- const stored = instance.metadata.config[key];
157224
- if (stored == null) delete config8[key];
157225
- else config8[key] = stored;
157445
+ return this.inTransition(instance, async () => {
157446
+ config8 = { ...config8 };
157447
+ const schema6 = instance.plugin.getConfigSchema?.();
157448
+ if (schema6) {
157449
+ for (const [key, prop] of Object.entries(schema6.properties)) {
157450
+ if (prop.secret && config8[key] === SECRET_UNCHANGED) {
157451
+ const stored = instance.metadata.config[key];
157452
+ if (stored == null) delete config8[key];
157453
+ else config8[key] = stored;
157454
+ }
157455
+ }
157456
+ }
157457
+ instance.metadata.config = config8;
157458
+ this.registry?.updateConfig(pluginName, config8);
157459
+ if (instance.plugin.onConfigChanged) {
157460
+ if (!instance.metadata.enabled) {
157461
+ instance.pendingConfig = config8;
157462
+ logger202.info(
157463
+ `Plugin "${pluginName}" is disabled, the config applies on enable`
157464
+ );
157465
+ } else {
157466
+ await this.runner.run(
157467
+ pluginName,
157468
+ "onConfigChanged",
157469
+ () => instance.plugin.onConfigChanged(config8)
157470
+ );
157226
157471
  }
157227
157472
  }
157228
- }
157229
- instance.metadata.config = config8;
157230
- this.registry?.updateConfig(pluginName, config8);
157231
- if (instance.plugin.onConfigChanged) {
157232
- await this.runner.run(
157233
- pluginName,
157234
- "onConfigChanged",
157235
- () => instance.plugin.onConfigChanged(config8)
157236
- );
157237
- }
157238
- return true;
157473
+ return true;
157474
+ });
157239
157475
  }
157240
157476
  };
157241
157477
 
@@ -157986,11 +158222,11 @@ var Bridge = class {
157986
158222
  get pluginInfo() {
157987
158223
  return this.endpointManager.getPluginInfo();
157988
158224
  }
157989
- enablePlugin(pluginName) {
157990
- this.endpointManager.enablePlugin(pluginName);
158225
+ async enablePlugin(pluginName) {
158226
+ return await this.endpointManager.enablePlugin(pluginName);
157991
158227
  }
157992
- disablePlugin(pluginName) {
157993
- this.endpointManager.disablePlugin(pluginName);
158228
+ async disablePlugin(pluginName) {
158229
+ return await this.endpointManager.disablePlugin(pluginName);
157994
158230
  }
157995
158231
  resetPlugin(pluginName) {
157996
158232
  this.endpointManager.resetPlugin(pluginName);
@@ -174632,10 +174868,11 @@ function unregisterRequestor(sessionId) {
174632
174868
  pendingDeliveries.delete(sessionId);
174633
174869
  }
174634
174870
  }
174635
- function unregisterAllRequestors() {
174636
- for (const id of [...registry2.keys()]) unregisterRequestor(id);
174637
- for (const timer of pendingDeliveries.values()) clearTimeout(timer);
174638
- pendingDeliveries.clear();
174871
+ function unregisterRequestorsByOwner(owner) {
174872
+ if (owner == null) return;
174873
+ for (const [id, registration] of [...registry2]) {
174874
+ if (registration.owner === owner) unregisterRequestor(id);
174875
+ }
174639
174876
  }
174640
174877
  function deliverAnswerDeferred(sessionId, sdp, onGiveUp) {
174641
174878
  const prior = pendingDeliveries.get(sessionId);
@@ -174803,7 +175040,9 @@ var CameraWebRtcProviderServer = class extends WebRtcTransportProviderServer {
174803
175040
  registerRequestor(id, {
174804
175041
  session,
174805
175042
  requestorEndpoint,
174806
- env: this.env
175043
+ env: this.env,
175044
+ // The bridge instance scopes this session to its camera plugin.
175045
+ owner: this.state.bridge
174807
175046
  });
174808
175047
  }
174809
175048
  let answerSdp;
@@ -175508,10 +175747,11 @@ var CameraPlugin = class {
175508
175747
  });
175509
175748
  }
175510
175749
  this.deviceIds = [];
175511
- await this.bridge?.close().catch(() => {
175512
- });
175750
+ const bridge = this.bridge;
175513
175751
  this.bridge = void 0;
175514
- unregisterAllRequestors();
175752
+ await bridge?.close().catch(() => {
175753
+ });
175754
+ if (bridge) unregisterRequestorsByOwner(bridge);
175515
175755
  }
175516
175756
  };
175517
175757
 
@@ -175869,6 +176109,9 @@ var SecurityPlugin = class {
175869
176109
  // coalesced per entity so a hung call cannot pile up work behind it.
175870
176110
  tasks = [];
175871
176111
  draining = false;
176112
+ // Bumped on every teardown and bring-up; queued tasks from an older
176113
+ // generation never dispatch.
176114
+ effectGeneration = 0;
175872
176115
  connection;
175873
176116
  unsubscribeEvents;
175874
176117
  retryTimer;
@@ -175881,6 +176124,21 @@ var SecurityPlugin = class {
175881
176124
  const stored = await context.storage.get(CONFIG_KEY2);
175882
176125
  this.config = { ...this.config, ...stored ?? {} };
175883
176126
  this.applyLists();
176127
+ if (!this.isConfigured()) {
176128
+ this.log.info(
176129
+ "no trigger entities configured, the security devices stay unregistered"
176130
+ );
176131
+ return;
176132
+ }
176133
+ await this.bringUp();
176134
+ }
176135
+ isConfigured() {
176136
+ return this.watched.size > 0;
176137
+ }
176138
+ async bringUp() {
176139
+ const context = this.context;
176140
+ if (!context) return;
176141
+ this.effectGeneration++;
175884
176142
  this.machine = new SecurityStateMachine(
175885
176143
  this.machineConfig(),
175886
176144
  this.effects()
@@ -175903,21 +176161,38 @@ var SecurityPlugin = class {
175903
176161
  this.pushDeviceStates();
175904
176162
  this.startConnection();
175905
176163
  }
176164
+ async tearDown() {
176165
+ this.machine?.shutdown();
176166
+ this.machine = void 0;
176167
+ this.tasks = [];
176168
+ this.effectGeneration++;
176169
+ await this.stopConnection();
176170
+ for (const id of [...Object.values(MODE_DEVICE_IDS), ALARM_DEVICE_ID]) {
176171
+ await this.context?.unregisterDevice(id).catch(() => {
176172
+ });
176173
+ }
176174
+ }
175906
176175
  async onConfigChanged(config8) {
175907
176176
  this.config = config8;
175908
176177
  await this.context?.storage.set(CONFIG_KEY2, this.config);
175909
176178
  this.applyLists();
175910
- this.machine?.setConfig(this.machineConfig());
176179
+ if (!this.isConfigured()) {
176180
+ if (this.machine) {
176181
+ this.log.info("trigger lists emptied, removing the security devices");
176182
+ await this.tearDown();
176183
+ }
176184
+ return;
176185
+ }
176186
+ if (!this.machine) {
176187
+ await this.bringUp();
176188
+ return;
176189
+ }
176190
+ this.machine.setConfig(this.machineConfig());
175911
176191
  await this.stopConnection();
175912
176192
  this.startConnection();
175913
176193
  }
175914
176194
  async onShutdown() {
175915
- this.machine?.shutdown();
175916
- await this.stopConnection();
175917
- for (const id of [...Object.values(MODE_DEVICE_IDS), ALARM_DEVICE_ID]) {
175918
- await this.context?.unregisterDevice(id).catch(() => {
175919
- });
175920
- }
176195
+ await this.tearDown();
175921
176196
  }
175922
176197
  getCurrentConfig() {
175923
176198
  return { ...this.config };
@@ -176155,6 +176430,7 @@ var SecurityPlugin = class {
176155
176430
  });
176156
176431
  }
176157
176432
  pushTask(task) {
176433
+ task.gen = this.effectGeneration;
176158
176434
  this.tasks.push(task);
176159
176435
  queueMicrotask(() => void this.drain());
176160
176436
  }
@@ -176202,6 +176478,7 @@ var SecurityPlugin = class {
176202
176478
  for (; ; ) {
176203
176479
  const task = this.tasks.shift();
176204
176480
  if (!task) return;
176481
+ if (task.gen !== this.effectGeneration) continue;
176205
176482
  try {
176206
176483
  await this.runTask(task);
176207
176484
  } catch (e) {
@@ -176827,7 +177104,7 @@ var BridgeEndpointManager = class extends Service {
176827
177104
  );
176828
177105
  }
176829
177106
  };
176830
- this.pluginManager.onDeviceUnregistered = async (pluginName, deviceId) => {
177107
+ this.pluginManager.onDeviceUnregistered = async (pluginName, deviceId, options) => {
176831
177108
  const listeners = this.pluginListeners.get(deviceId);
176832
177109
  if (listeners) {
176833
177110
  for (const { observable, listener } of listeners) {
@@ -176841,7 +177118,11 @@ var BridgeEndpointManager = class extends Service {
176841
177118
  const endpoint = this.pluginEndpoints.get(deviceId);
176842
177119
  if (endpoint) {
176843
177120
  try {
176844
- await endpoint.delete();
177121
+ if (options?.keepIdentity) {
177122
+ await endpoint.close();
177123
+ } else {
177124
+ await endpoint.delete();
177125
+ }
176845
177126
  } catch (e) {
176846
177127
  this.log.warn(
176847
177128
  `Plugin "${pluginName}": failed to remove device "${deviceId}":`,
@@ -176977,11 +177258,11 @@ var BridgeEndpointManager = class extends Service {
176977
177258
  circuitBreakers
176978
177259
  };
176979
177260
  }
176980
- enablePlugin(pluginName) {
176981
- this.pluginManager?.enablePlugin(pluginName);
177261
+ async enablePlugin(pluginName) {
177262
+ return await this.pluginManager?.enablePlugin(pluginName);
176982
177263
  }
176983
- disablePlugin(pluginName) {
176984
- this.pluginManager?.disablePlugin(pluginName);
177264
+ async disablePlugin(pluginName) {
177265
+ return await this.pluginManager?.disablePlugin(pluginName);
176985
177266
  }
176986
177267
  resetPlugin(pluginName) {
176987
177268
  this.pluginManager?.resetPlugin(pluginName);
@@ -180734,9 +181015,9 @@ function startCommand(webDist) {
180734
181015
  }
180735
181016
 
180736
181017
  // src/cli.ts
180737
- var dirname7 = import.meta.dirname ?? url.fileURLToPath(new URL(".", import.meta.url));
181018
+ var dirname8 = import.meta.dirname ?? url.fileURLToPath(new URL(".", import.meta.url));
180738
181019
  async function cli(argv) {
180739
- const webDist = process.env.NODE_ENV === "development" ? void 0 : path13.join(dirname7, "../frontend");
181020
+ const webDist = process.env.NODE_ENV === "development" ? void 0 : path13.join(dirname8, "../frontend");
180740
181021
  const cli2 = yargs(hideBin(argv));
180741
181022
  cli2.scriptName("home-assistant-matter-hub").version().strict().recommendCommands().detectLocale(false).help().command(startCommand(webDist)).demandCommand().wrap(Math.min(140, cli2.terminalWidth())).parse();
180742
181023
  }