@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.
package/dist/backend/cli.js
CHANGED
|
@@ -133321,12 +133321,93 @@ function accessLogger(logger253) {
|
|
|
133321
133321
|
}
|
|
133322
133322
|
|
|
133323
133323
|
// src/api/backup-api.ts
|
|
133324
|
-
import
|
|
133325
|
-
import
|
|
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 =
|
|
133346
|
-
if (includeIdentity &&
|
|
133347
|
-
const iconFiles =
|
|
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 =
|
|
133387
|
-
if (
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 ||
|
|
133868
|
+
if (relative.length === 0 || path2.isAbsolute(relative)) {
|
|
133780
133869
|
return null;
|
|
133781
133870
|
}
|
|
133782
|
-
const resolvedBase =
|
|
133783
|
-
const resolvedTarget =
|
|
133784
|
-
if (resolvedTarget !== resolvedBase && !resolvedTarget.startsWith(resolvedBase +
|
|
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 =
|
|
133798
|
-
|
|
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 =
|
|
133808
|
-
|
|
133896
|
+
const targetDirPath = path2.dirname(targetPath);
|
|
133897
|
+
fs2.mkdirSync(targetDirPath, { recursive: true });
|
|
133809
133898
|
const content = await file.buffer();
|
|
133810
|
-
|
|
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 =
|
|
133823
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
134002
|
-
import
|
|
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 =
|
|
134009
|
-
if (!
|
|
134010
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
134081
|
-
|
|
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
|
|
134089
|
-
import
|
|
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 (!
|
|
134101
|
-
const files =
|
|
134102
|
-
const imageFile = files.find((f) =>
|
|
134103
|
-
return imageFile ?
|
|
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 =
|
|
134114
|
-
if (!
|
|
134115
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
134276
|
+
const files = fs4.readdirSync(imagesDir);
|
|
134177
134277
|
for (const f of files) {
|
|
134178
|
-
if (
|
|
134179
|
-
|
|
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
|
-
|
|
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
|
|
136602
|
-
import * as
|
|
136603
|
-
var
|
|
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 =
|
|
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 (!
|
|
136616
|
-
|
|
136715
|
+
if (!fs5.existsSync(this.pluginDir)) {
|
|
136716
|
+
fs5.mkdirSync(this.pluginDir, { recursive: true });
|
|
136617
136717
|
}
|
|
136618
|
-
const pkgJson =
|
|
136619
|
-
if (!
|
|
136620
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
136726
|
-
if (!
|
|
136727
|
-
const pkg = JSON.parse(
|
|
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 =
|
|
136838
|
+
const tgzPath = path5.join(this.pluginDir, `.upload-${Date.now()}.tgz`);
|
|
136739
136839
|
try {
|
|
136740
|
-
|
|
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
|
-
|
|
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 (
|
|
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:
|
|
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:
|
|
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 =
|
|
136796
|
-
if (!
|
|
136797
|
-
const pkg = JSON.parse(
|
|
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 =
|
|
136805
|
-
if (!
|
|
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 =
|
|
136813
|
-
if (!
|
|
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(
|
|
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 =
|
|
136839
|
-
if (
|
|
136840
|
-
|
|
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
|
-
|
|
136843
|
-
|
|
136844
|
-
|
|
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 =
|
|
136955
|
+
const pkgPath = path5.join(
|
|
136856
136956
|
this.pluginDir,
|
|
136857
136957
|
"node_modules",
|
|
136858
136958
|
packageName,
|
|
136859
136959
|
"package.json"
|
|
136860
136960
|
);
|
|
136861
|
-
if (
|
|
136862
|
-
const pkg = JSON.parse(
|
|
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
|
|
136874
|
-
import * as
|
|
136875
|
-
var
|
|
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 =
|
|
136980
|
+
this.filePath = path6.join(storageLocation, "installed-plugins.json");
|
|
136881
136981
|
this.load();
|
|
136882
136982
|
}
|
|
136883
136983
|
load() {
|
|
136884
136984
|
try {
|
|
136885
|
-
if (
|
|
136886
|
-
const raw =
|
|
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
|
-
|
|
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 =
|
|
136897
|
-
if (!
|
|
136898
|
-
|
|
136996
|
+
const dir = path6.dirname(this.filePath);
|
|
136997
|
+
if (!fs6.existsSync(dir)) {
|
|
136998
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
136899
136999
|
}
|
|
136900
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
137772
|
-
import
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
138542
|
+
import fs8 from "node:fs";
|
|
138427
138543
|
import os6 from "node:os";
|
|
138428
|
-
import
|
|
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
|
-
|
|
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 ?
|
|
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
|
|
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 ?
|
|
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
|
|
138594
|
-
import
|
|
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 =
|
|
138603
|
-
|
|
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 =
|
|
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 =
|
|
138628
|
-
if (
|
|
138629
|
-
const iconFiles =
|
|
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 =
|
|
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 =
|
|
138786
|
+
const bridgeStoragePath = path11.join(
|
|
138671
138787
|
this.props.storageLocation,
|
|
138672
138788
|
bridge.id
|
|
138673
138789
|
);
|
|
138674
|
-
if (
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 (!
|
|
138842
|
+
if (!fs9.existsSync(this.backupDir)) {
|
|
138720
138843
|
return [];
|
|
138721
138844
|
}
|
|
138722
|
-
const files =
|
|
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 =
|
|
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 =
|
|
138746
|
-
if (!
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) =>
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
152648
|
+
logger194.warn(
|
|
152526
152649
|
"No local OccupancySensing cluster available and externallyMeasuredOccupancy state not set"
|
|
152527
152650
|
);
|
|
152528
152651
|
} else {
|
|
152529
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
154691
|
+
logger195.debug("Lift movement stopped, target value reached");
|
|
154569
154692
|
}
|
|
154570
154693
|
}
|
|
154571
|
-
|
|
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
|
-
|
|
154704
|
+
logger195.debug("Tilt movement stopped, target value reached");
|
|
154582
154705
|
}
|
|
154583
154706
|
}
|
|
154584
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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:
|
|
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
|
-
|
|
157097
|
-
|
|
157098
|
-
|
|
157099
|
-
|
|
157100
|
-
|
|
157101
|
-
|
|
157102
|
-
|
|
157103
|
-
|
|
157104
|
-
|
|
157105
|
-
|
|
157106
|
-
|
|
157107
|
-
|
|
157108
|
-
|
|
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
|
-
|
|
157111
|
-
|
|
157112
|
-
|
|
157113
|
-
|
|
157114
|
-
|
|
157115
|
-
|
|
157116
|
-
|
|
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
|
|
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
|
-
|
|
157144
|
-
|
|
157145
|
-
|
|
157146
|
-
|
|
157147
|
-
(
|
|
157148
|
-
|
|
157149
|
-
|
|
157150
|
-
|
|
157151
|
-
|
|
157152
|
-
|
|
157153
|
-
|
|
157154
|
-
|
|
157155
|
-
|
|
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
|
-
|
|
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
|
-
|
|
157194
|
-
|
|
157195
|
-
|
|
157196
|
-
|
|
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
|
-
|
|
157201
|
-
|
|
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
|
-
|
|
157219
|
-
|
|
157220
|
-
|
|
157221
|
-
|
|
157222
|
-
|
|
157223
|
-
|
|
157224
|
-
|
|
157225
|
-
|
|
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
|
-
|
|
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
|
|
174636
|
-
|
|
174637
|
-
for (const
|
|
174638
|
-
|
|
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
|
-
|
|
175512
|
-
});
|
|
175750
|
+
const bridge = this.bridge;
|
|
175513
175751
|
this.bridge = void 0;
|
|
175514
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
}
|