zwave-js
Version:
Z-Wave driver written entirely in JavaScript/TypeScript
580 lines (579 loc) • 23.2 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var NetworkCache_exports = {};
__export(NetworkCache_exports, {
cacheKeyUtils: () => cacheKeyUtils,
cacheKeys: () => cacheKeys,
deserializeNetworkCacheValue: () => deserializeNetworkCacheValue,
migrateLegacyNetworkCache: () => migrateLegacyNetworkCache,
serializeNetworkCacheValue: () => serializeNetworkCacheValue
});
module.exports = __toCommonJS(NetworkCache_exports);
var import_core = require("@zwave-js/core");
var import_shared = require("@zwave-js/shared");
var import_typeguards = require("alcalzone-shared/typeguards");
var import_pathe = __toESM(require("pathe"), 1);
var import_Inclusion = require("../controller/Inclusion.js");
var import_DeviceClass = require("../node/DeviceClass.js");
var import_Types = require("../node/_Types.js");
const cacheKeys = {
controller: {
provisioningList: "controller.provisioningList",
associations: /* @__PURE__ */ __name((groupId) => `controller.associations.${groupId}`, "associations"),
securityKeys: /* @__PURE__ */ __name((secClass) => `controller.securityKeys.${(0, import_shared.getEnumMemberName)(import_core.SecurityClass, secClass)}`, "securityKeys"),
securityKeysLongRange: /* @__PURE__ */ __name((secClass) => `controller.securityKeyLongRange.${(0, import_shared.getEnumMemberName)(import_core.SecurityClass, secClass)}`, "securityKeysLongRange"),
privateKey: "controller.privateKey"
},
// TODO: somehow these functions should be combined with the pattern matching below
node: /* @__PURE__ */ __name((nodeId) => {
const nodeBaseKey = `node.${nodeId}.`;
return {
_baseKey: nodeBaseKey,
_securityClassBaseKey: `${nodeBaseKey}securityClasses`,
_priorityReturnRouteBaseKey: `${nodeBaseKey}priorityReturnRoute`,
interviewStage: `${nodeBaseKey}interviewStage`,
bootstrapped: `${nodeBaseKey}bootstrapped`,
deviceClass: `${nodeBaseKey}deviceClass`,
isListening: `${nodeBaseKey}isListening`,
isFrequentListening: `${nodeBaseKey}isFrequentListening`,
isRouting: `${nodeBaseKey}isRouting`,
supportedDataRates: `${nodeBaseKey}supportedDataRates`,
protocolVersion: `${nodeBaseKey}protocolVersion`,
nodeType: `${nodeBaseKey}nodeType`,
supportsSecurity: `${nodeBaseKey}supportsSecurity`,
supportsBeaming: `${nodeBaseKey}supportsBeaming`,
securityClass: /* @__PURE__ */ __name((secClass) => `${nodeBaseKey}securityClasses.${(0, import_shared.getEnumMemberName)(import_core.SecurityClass, secClass)}`, "securityClass"),
dsk: `${nodeBaseKey}dsk`,
failedS2Bootstrapping: `${nodeBaseKey}failedS2Bootstrapping`,
endpoint: /* @__PURE__ */ __name((index) => {
const endpointBaseKey = `${nodeBaseKey}endpoint.${index}.`;
const ccBaseKey = `${endpointBaseKey}commandClass.`;
return {
_baseKey: endpointBaseKey,
_ccBaseKey: ccBaseKey,
commandClass: /* @__PURE__ */ __name((ccId) => {
const ccAsHex = (0, import_shared.num2hex)(ccId);
return `${ccBaseKey}${ccAsHex}`;
}, "commandClass")
};
}, "endpoint"),
hasSUCReturnRoute: `${nodeBaseKey}hasSUCReturnRoute`,
priorityReturnRoute: /* @__PURE__ */ __name((destinationNodeId) => `${nodeBaseKey}priorityReturnRoute.${destinationNodeId}`, "priorityReturnRoute"),
prioritySUCReturnRoute: `${nodeBaseKey}priorityReturnRoute.SUC`,
customReturnRoutes: /* @__PURE__ */ __name((destinationNodeId) => `${nodeBaseKey}customReturnRoutes.${destinationNodeId}`, "customReturnRoutes"),
customSUCReturnRoutes: `${nodeBaseKey}customReturnRoutes.SUC`,
defaultTransitionDuration: `${nodeBaseKey}defaultTransitionDuration`,
defaultVolume: `${nodeBaseKey}defaultVolume`,
lastSeen: `${nodeBaseKey}lastSeen`,
deviceConfigHash: `${nodeBaseKey}deviceConfigHash`
};
}, "node")
};
const cacheKeyUtils = {
nodeIdFromKey: /* @__PURE__ */ __name((key) => {
const match = /^node\.(?<nodeId>\d+)\./.exec(key);
if (match) {
return parseInt(match.groups.nodeId, 10);
}
}, "nodeIdFromKey"),
nodePropertyFromKey: /* @__PURE__ */ __name((key) => {
const match = /^node\.\d+\.(?<property>[^.]+)$/.exec(key);
return match?.groups?.property;
}, "nodePropertyFromKey"),
isEndpointKey: /* @__PURE__ */ __name((key) => {
return /endpoints\.(?<index>\d+)$/.test(key);
}, "isEndpointKey"),
endpointIndexFromKey: /* @__PURE__ */ __name((key) => {
const match = /endpoints\.(?<index>\d+)$/.exec(key);
if (match) {
return parseInt(match.groups.index, 10);
}
}, "endpointIndexFromKey"),
destinationFromPriorityReturnRouteKey: /* @__PURE__ */ __name((key) => {
const match = /\.priorityReturnRoute\.(?<nodeId>\d+)$/.exec(key);
if (match) {
return parseInt(match.groups.nodeId, 10);
}
}, "destinationFromPriorityReturnRouteKey")
};
function tryParseInterviewStage(value) {
if ((typeof value === "string" || typeof value === "number") && value in import_Types.InterviewStage) {
return typeof value === "number" ? value : import_Types.InterviewStage[value];
}
}
__name(tryParseInterviewStage, "tryParseInterviewStage");
function tryParseDeviceClass(value) {
if ((0, import_typeguards.isObject)(value)) {
const { basic, generic, specific } = value;
if (typeof basic === "number" && typeof generic === "number" && typeof specific === "number") {
return new import_DeviceClass.DeviceClass(basic, generic, specific);
}
}
}
__name(tryParseDeviceClass, "tryParseDeviceClass");
function tryParseSecurityClasses(value) {
if ((0, import_typeguards.isObject)(value)) {
const ret = /* @__PURE__ */ new Map();
for (const [key, val] of Object.entries(value)) {
if (key in import_core.SecurityClass && typeof import_core.SecurityClass[key] === "number" && typeof val === "boolean") {
ret.set(import_core.SecurityClass[key], val);
}
}
return ret;
}
}
__name(tryParseSecurityClasses, "tryParseSecurityClasses");
function tryParseNodeType(value) {
if (typeof value === "string" && value in import_core.NodeType) {
return import_core.NodeType[value];
}
}
__name(tryParseNodeType, "tryParseNodeType");
function tryParseProvisioningList(value) {
const ret = [];
if (!(0, import_typeguards.isArray)(value))
return;
for (const entry of value) {
if ((0, import_typeguards.isObject)(entry) && typeof entry.dsk === "string" && (0, import_typeguards.isArray)(entry.securityClasses) && entry.securityClasses.every((s) => isSerializedSecurityClass(s)) && (entry.requestedSecurityClasses == void 0 || (0, import_typeguards.isArray)(entry.requestedSecurityClasses) && entry.requestedSecurityClasses.every((s) => isSerializedSecurityClass(s))) && (entry.protocol == void 0 || isSerializedProtocol(entry.protocol)) && (entry.supportedProtocols == void 0 || (0, import_typeguards.isArray)(entry.supportedProtocols) && entry.supportedProtocols.every((s) => isSerializedProtocol(s))) && (entry.status == void 0 || isSerializedProvisioningEntryStatus(entry.status))) {
if ("nodeId" in entry && typeof entry.nodeId !== "number") {
return;
}
const parsed = {
...entry
};
parsed.securityClasses = entry.securityClasses.map((s) => tryParseSerializedSecurityClass(s)).filter((s) => s !== void 0);
if (entry.requestedSecurityClasses) {
parsed.requestedSecurityClasses = entry.requestedSecurityClasses.map((s) => tryParseSerializedSecurityClass(s)).filter((s) => s !== void 0);
}
if (entry.status != void 0) {
parsed.status = import_Inclusion.ProvisioningEntryStatus[entry.status];
}
if (entry.protocol != void 0) {
parsed.protocol = tryParseSerializedProtocol(entry.protocol);
}
if (entry.supportedProtocols) {
parsed.supportedProtocols = entry.supportedProtocols.map((s) => tryParseSerializedProtocol(s)).filter((s) => s !== void 0);
}
ret.push(parsed);
} else {
return;
}
}
return ret;
}
__name(tryParseProvisioningList, "tryParseProvisioningList");
function isSerializedSecurityClass(value) {
if (typeof value === "number" && value in import_core.SecurityClass)
return true;
if (typeof value === "string") {
if (value.startsWith("unknown (0x") && value.endsWith(")")) {
value = value.slice(11, -1);
}
if (value in import_core.SecurityClass && typeof import_core.SecurityClass[value] === "number") {
return true;
}
}
return false;
}
__name(isSerializedSecurityClass, "isSerializedSecurityClass");
function tryParseSerializedSecurityClass(value) {
if (typeof value === "number" && value in import_core.SecurityClass)
return value;
if (typeof value === "string") {
if (value.startsWith("unknown (0x") && value.endsWith(")")) {
value = value.slice(11, -1);
}
if (value in import_core.SecurityClass && typeof import_core.SecurityClass[value] === "number") {
return import_core.SecurityClass[value];
}
}
}
__name(tryParseSerializedSecurityClass, "tryParseSerializedSecurityClass");
function isSerializedProvisioningEntryStatus(s) {
return typeof s === "string" && s in import_Inclusion.ProvisioningEntryStatus && typeof import_Inclusion.ProvisioningEntryStatus[s] === "number";
}
__name(isSerializedProvisioningEntryStatus, "isSerializedProvisioningEntryStatus");
function isSerializedProtocol(s) {
if (typeof s === "number" && s in import_core.Protocols)
return true;
return typeof s === "string" && s in import_core.Protocols && typeof import_core.Protocols[s] === "number";
}
__name(isSerializedProtocol, "isSerializedProtocol");
function tryParseSerializedProtocol(value) {
if (typeof value === "number" && value in import_core.Protocols)
return value;
if (typeof value === "string") {
if (value in import_core.Protocols && typeof import_core.Protocols[value] === "number") {
return import_core.Protocols[value];
}
}
}
__name(tryParseSerializedProtocol, "tryParseSerializedProtocol");
function tryParseDate(value) {
if (typeof value === "number") {
const ret = new Date(value);
if (!isNaN(ret.getTime()))
return ret;
}
}
__name(tryParseDate, "tryParseDate");
function tryParseAssociationAddress(value) {
if ((0, import_typeguards.isObject)(value)) {
const { nodeId, endpoint } = value;
if (typeof nodeId !== "number")
return;
if (endpoint !== void 0 && typeof endpoint !== "number")
return;
return { nodeId, endpoint };
}
}
__name(tryParseAssociationAddress, "tryParseAssociationAddress");
function tryParseBuffer(value) {
if (typeof value === "string") {
try {
return import_shared.Bytes.from(value, "hex");
} catch {
}
}
}
__name(tryParseBuffer, "tryParseBuffer");
function tryParseBufferBase64(value) {
if (typeof value === "string") {
try {
return import_shared.Bytes.from(value, "base64");
} catch {
}
}
}
__name(tryParseBufferBase64, "tryParseBufferBase64");
function deserializeNetworkCacheValue(key, value) {
function ensureType(value2, type) {
if (typeof value2 === type)
return value2;
throw new import_core.ZWaveError(`Incorrect type ${typeof value2} for property "${key}"`, import_core.ZWaveErrorCodes.Driver_InvalidCache);
}
__name(ensureType, "ensureType");
function fail() {
throw new import_core.ZWaveError(`Failed to deserialize property "${key}"`, import_core.ZWaveErrorCodes.Driver_InvalidCache);
}
__name(fail, "fail");
switch (cacheKeyUtils.nodePropertyFromKey(key)) {
case "interviewStage": {
value = tryParseInterviewStage(value);
if (value)
return value;
fail();
}
case "deviceClass": {
value = tryParseDeviceClass(value);
if (value)
return value;
fail();
}
case "isListening":
case "isRouting":
case "hasSUCReturnRoute":
return ensureType(value, "boolean");
case "isFrequentListening": {
switch (value) {
case "1000ms":
case true:
return "1000ms";
case "250ms":
return "250ms";
case false:
return false;
}
fail();
}
case "dsk": {
if (typeof value === "string") {
return (0, import_core.dskFromString)(value);
}
fail();
}
case "failedS2Bootstrapping": {
return ensureType(value, "boolean");
}
case "supportsSecurity":
return ensureType(value, "boolean");
case "supportsBeaming":
try {
return ensureType(value, "boolean");
} catch {
return ensureType(value, "string");
}
case "protocolVersion":
return ensureType(value, "number");
case "nodeType": {
value = tryParseNodeType(value);
if (value)
return value;
fail();
}
case "supportedDataRates": {
if ((0, import_typeguards.isArray)(value) && value.every((r) => typeof r === "number")) {
return value;
}
fail();
}
case "lastSeen": {
value = tryParseDate(value);
if (value)
return value;
fail();
}
case "deviceConfigHash": {
if (typeof value !== "string")
fail();
const versionMatch = value.match(/^\$v\d+\$/)?.[0];
if (versionMatch) {
value = tryParseBufferBase64(value.slice(versionMatch.length));
if (value) {
value = import_shared.Bytes.concat([
import_shared.Bytes.from(versionMatch, "utf8"),
value
]);
}
} else {
value = tryParseBuffer(value);
}
if (value)
return value;
fail();
}
}
if (key.startsWith("controller.associations.")) {
value = tryParseAssociationAddress(value);
if (value)
return value;
fail();
} else if (key.startsWith("controller.securityKeys.")) {
value = tryParseBuffer(value);
if (value)
return value;
fail();
}
switch (key) {
case cacheKeys.controller.provisioningList: {
value = tryParseProvisioningList(value);
if (value)
return value;
fail();
}
case cacheKeys.controller.privateKey: {
value = tryParseBuffer(value);
if (value)
return value;
fail();
}
}
return value;
}
__name(deserializeNetworkCacheValue, "deserializeNetworkCacheValue");
function serializeNetworkCacheValue(key, value) {
switch (cacheKeyUtils.nodePropertyFromKey(key)) {
case "interviewStage": {
return import_Types.InterviewStage[value];
}
case "deviceClass": {
const deviceClass = value;
return {
basic: deviceClass.basic,
generic: deviceClass.generic.key,
specific: deviceClass.specific.key
};
}
case "nodeType": {
return import_core.NodeType[value];
}
case "securityClasses": {
const ret = {};
for (const secClass of import_core.securityClassOrder) {
if (secClass in value) {
ret[import_core.SecurityClass[secClass]] = value[secClass];
}
}
return ret;
}
case "dsk": {
return (0, import_core.dskToString)(value);
}
case "lastSeen": {
return value.getTime();
}
case "deviceConfigHash": {
const valueAsString = import_shared.Bytes.view(value).toString("utf8");
const versionMatch = valueAsString.match(/^\$v\d+\$/)?.[0];
if (versionMatch) {
return versionMatch + import_shared.Bytes.view(value).subarray(versionMatch.length).toString("base64");
} else {
return import_shared.Bytes.view(value).toString("hex");
}
}
}
if (key.startsWith("controller.securityKeys.")) {
return import_shared.Bytes.view(value).toString("hex");
}
switch (key) {
case cacheKeys.controller.provisioningList: {
const ret = [];
for (const entry of value) {
const serialized = { ...entry };
serialized.securityClasses = entry.securityClasses.map((c) => (0, import_shared.getEnumMemberName)(import_core.SecurityClass, c));
if (entry.requestedSecurityClasses) {
serialized.requestedSecurityClasses = entry.requestedSecurityClasses.map((c) => (0, import_shared.getEnumMemberName)(import_core.SecurityClass, c));
}
if (entry.status != void 0) {
serialized.status = (0, import_shared.getEnumMemberName)(import_Inclusion.ProvisioningEntryStatus, entry.status);
}
if (entry.protocol != void 0) {
serialized.protocol = (0, import_shared.getEnumMemberName)(import_core.Protocols, entry.protocol);
}
if (entry.supportedProtocols != void 0) {
serialized.supportedProtocols = entry.supportedProtocols.map((p) => (0, import_shared.getEnumMemberName)(import_core.Protocols, p));
}
ret.push(serialized);
}
return ret;
}
case cacheKeys.controller.privateKey: {
return import_shared.Bytes.view(value).toString("hex");
}
}
return value;
}
__name(serializeNetworkCacheValue, "serializeNetworkCacheValue");
const legacyPaths = {
// These seem to duplicate the ones in cacheKeys, but this allows us to change
// something in the future without breaking migration
controller: {
provisioningList: "controller.provisioningList"
},
node: {
// These are relative to the node object
interviewStage: `interviewStage`,
deviceClass: `deviceClass`,
isListening: `isListening`,
isFrequentListening: `isFrequentListening`,
isRouting: `isRouting`,
supportedDataRates: `supportedDataRates`,
protocolVersion: `protocolVersion`,
nodeType: `nodeType`,
supportsSecurity: `supportsSecurity`,
supportsBeaming: `supportsBeaming`,
securityClasses: `securityClasses`,
dsk: `dsk`
},
commandClass: {
// These are relative to the commandClasses object
name: `name`,
endpoint: /* @__PURE__ */ __name((index) => `endpoints.${index}`, "endpoint")
}
};
async function migrateLegacyNetworkCache(homeId, networkCache, valueDB, fs, cacheDir) {
const cacheFile = import_pathe.default.join(cacheDir, `${homeId.toString(16)}.json`);
try {
const stat = await fs.stat(cacheFile);
if (!stat.isFile())
return;
} catch {
return;
}
const legacyContents = await fs.readFile(cacheFile);
const legacy = JSON.parse(import_shared.Bytes.view(legacyContents).toString("utf8"));
const jsonl = networkCache;
function tryMigrate(targetKey, source, sourcePath, converter) {
let val = (0, import_shared.pickDeep)(source, sourcePath);
if (val != void 0 && converter)
val = converter(val);
if (val != void 0)
jsonl.set(targetKey, val);
}
__name(tryMigrate, "tryMigrate");
tryMigrate(cacheKeys.controller.provisioningList, legacy, legacyPaths.controller.provisioningList, tryParseProvisioningList);
if ((0, import_typeguards.isObject)(legacy.nodes)) {
for (const node of Object.values(legacy.nodes)) {
if (!(0, import_typeguards.isObject)(node) || typeof node.id !== "number")
continue;
const nodeCacheKeys = cacheKeys.node(node.id);
tryMigrate(nodeCacheKeys.interviewStage, node, legacyPaths.node.interviewStage, tryParseInterviewStage);
tryMigrate(nodeCacheKeys.deviceClass, node, legacyPaths.node.deviceClass, (v) => tryParseDeviceClass(v));
tryMigrate(nodeCacheKeys.isListening, node, legacyPaths.node.isListening);
tryMigrate(nodeCacheKeys.isFrequentListening, node, legacyPaths.node.isFrequentListening);
tryMigrate(nodeCacheKeys.isRouting, node, legacyPaths.node.isRouting);
tryMigrate(nodeCacheKeys.supportedDataRates, node, legacyPaths.node.supportedDataRates);
tryMigrate(nodeCacheKeys.protocolVersion, node, legacyPaths.node.protocolVersion);
tryMigrate(nodeCacheKeys.nodeType, node, legacyPaths.node.nodeType, tryParseNodeType);
tryMigrate(nodeCacheKeys.supportsSecurity, node, legacyPaths.node.supportsSecurity);
tryMigrate(nodeCacheKeys.supportsBeaming, node, legacyPaths.node.supportsBeaming);
const securityClasses = tryParseSecurityClasses((0, import_shared.pickDeep)(node, legacyPaths.node.securityClasses));
if (securityClasses) {
for (const [secClass, val] of securityClasses) {
jsonl.set(nodeCacheKeys.securityClass(secClass), val);
}
}
tryMigrate(nodeCacheKeys.dsk, node, legacyPaths.node.dsk, import_core.dskFromString);
if ((0, import_typeguards.isObject)(node.commandClasses)) {
for (const [ccIdHex, cc] of Object.entries(node.commandClasses)) {
const ccId = parseInt(ccIdHex, 16);
if ((0, import_typeguards.isObject)(cc.endpoints)) {
for (const endpointId of Object.keys(cc.endpoints)) {
const endpointIndex = parseInt(endpointId, 10);
const cacheKey = nodeCacheKeys.endpoint(endpointIndex).commandClass(ccId);
tryMigrate(cacheKey, cc, legacyPaths.commandClass.endpoint(endpointIndex));
}
}
}
}
const dbKey = JSON.stringify({
nodeId: node.id,
commandClass: -1,
endpoint: 0,
property: "hasSUCReturnRoute"
});
if (valueDB.has(dbKey)) {
const hasSUCReturnRoute = valueDB.get(dbKey);
valueDB.delete(dbKey);
jsonl.set(nodeCacheKeys.hasSUCReturnRoute, hasSUCReturnRoute);
}
}
}
}
__name(migrateLegacyNetworkCache, "migrateLegacyNetworkCache");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
cacheKeyUtils,
cacheKeys,
deserializeNetworkCacheValue,
migrateLegacyNetworkCache,
serializeNetworkCacheValue
});
//# sourceMappingURL=NetworkCache.js.map