node-red-contrib-home-assistant-websocket
Version:
Node-RED integration with Home Assistant through websocket and REST API
342 lines (341 loc) • 19.5 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _IssueService_instances, _IssueService_initialized, _IssueService_nodesToCheck, _IssueService_issues, _IssueService_hiddenIssues, _IssueService_updateHandlers, _IssueService_tabs, _IssueService_getChangedNodes, _IssueService_getTabInfo, _IssueService_handleFlowsStarted, _IssueService_handlePeriodicCheck, _IssueService_performChecks, _IssueService_issueCheckMap, _IssueService_checkForIssues, _IssueService_listenForIssueEvents, _IssueService_listenForRegistryEvent, _IssueService_setIssue, _IssueService_removeIssue, _IssueService_issuesUpdated, _IssueService_loadHiddenIssues, _IssueService_saveHiddenIssues, _IssueService_addListener;
Object.defineProperty(exports, "__esModule", { value: true });
exports.IssueType = void 0;
const globals_1 = require("../../../globals");
const homeAssistant_1 = require("../../../homeAssistant");
const Websocket_1 = require("../../../homeAssistant/Websocket");
const issue_check_1 = __importStar(require("../../../nodes/action/issue-check"));
const issue_check_2 = __importStar(require("../../../nodes/current-state/issue-check"));
const issue_check_3 = __importStar(require("../../../nodes/device/issue-check"));
const issue_check_4 = __importStar(require("../../../nodes/events-calendar/issue-check"));
const issue_check_5 = __importStar(require("../../../nodes/events-state/issue-check"));
const issue_check_6 = __importStar(require("../../../nodes/get-history/issue-check"));
const issue_check_7 = __importStar(require("../../../nodes/poll-state/issue-check"));
const issue_check_8 = __importStar(require("../../../nodes/tag/issue-check"));
const issue_check_9 = __importStar(require("../../../nodes/time/issue-check"));
const issue_check_10 = __importStar(require("../../../nodes/trigger-state/issue-check"));
const issue_check_11 = __importStar(require("../../../nodes/wait-until/issue-check"));
const issue_check_12 = __importStar(require("../../../nodes/zone/issue-check"));
const StorageService_1 = __importDefault(require("../../services/StorageService"));
const utils_1 = require("./utils");
var NodeRedEvent;
(function (NodeRedEvent) {
NodeRedEvent["FlowsStarted"] = "flows:started";
})(NodeRedEvent || (NodeRedEvent = {}));
var DeploymentType;
(function (DeploymentType) {
DeploymentType["Full"] = "full";
DeploymentType["Nodes"] = "nodes";
})(DeploymentType || (DeploymentType = {}));
var IssueType;
(function (IssueType) {
IssueType["AreaId"] = "areaId";
IssueType["DeviceId"] = "deviceId";
IssueType["EntityId"] = "entityId";
IssueType["FloorId"] = "floorId";
IssueType["InvalidAction"] = "invalidAction";
IssueType["LabelId"] = "labelId";
IssueType["StateId"] = "stateId";
IssueType["TagId"] = "tagId";
})(IssueType || (exports.IssueType = IssueType = {}));
const IssueTypeToRegistryEventMap = {
[IssueType.AreaId]: homeAssistant_1.HaEvent.AreaRegistryUpdated,
[IssueType.DeviceId]: homeAssistant_1.HaEvent.DeviceRegistryUpdated,
[IssueType.FloorId]: homeAssistant_1.HaEvent.FloorRegistryUpdated,
[IssueType.LabelId]: homeAssistant_1.HaEvent.LabelRegistryUpdated,
};
const SIX_HOURS = 6 * 60 * 60 * 1000;
class IssueService {
constructor() {
_IssueService_instances.add(this);
_IssueService_initialized.set(this, false);
_IssueService_nodesToCheck.set(this, new Map());
_IssueService_issues.set(this, new Map());
_IssueService_hiddenIssues.set(this, new Set());
_IssueService_updateHandlers.set(this, []);
_IssueService_tabs.set(this, {});
// Define a mapping of node type check functions to issue check functions
_IssueService_issueCheckMap.set(this, [
[issue_check_1.isActionNodeProperties, issue_check_1.default],
[issue_check_2.isCurrentStateNodeProperties, issue_check_2.default],
[issue_check_3.isDeviceNodeProperties, issue_check_3.default],
[issue_check_4.isEventsCalendarNodeProperties, issue_check_4.default],
[issue_check_5.isEventsStateNodeProperties, issue_check_5.default],
[issue_check_6.isGetHistoryNodeProperties, issue_check_6.default],
[issue_check_7.isPollStateNodeProperties, issue_check_7.default],
[issue_check_8.isTagNodeProperties, issue_check_8.default],
[issue_check_9.isTimeNodeProperties, issue_check_9.default],
[issue_check_10.isTriggerStateProperties, issue_check_10.default],
[issue_check_11.isWaitUntilNodeProperties, issue_check_11.default],
[issue_check_12.isZoneNodeProperties, issue_check_12.default],
]);
}
init(updateHandler) {
if (__classPrivateFieldGet(this, _IssueService_initialized, "f")) {
return;
}
__classPrivateFieldSet(this, _IssueService_initialized, true, "f");
if (updateHandler) {
if (Array.isArray(updateHandler)) {
__classPrivateFieldGet(this, _IssueService_updateHandlers, "f").push(...updateHandler);
}
else {
__classPrivateFieldGet(this, _IssueService_updateHandlers, "f").push(updateHandler);
}
}
__classPrivateFieldGet(this, _IssueService_instances, "m", _IssueService_loadHiddenIssues).call(this);
globals_1.RED.events.on(NodeRedEvent.FlowsStarted, (event) => {
__classPrivateFieldGet(this, _IssueService_instances, "m", _IssueService_getTabInfo).call(this);
__classPrivateFieldGet(this, _IssueService_instances, "m", _IssueService_handleFlowsStarted).call(this, event);
});
// every hour, check all nodes for issues
setInterval(__classPrivateFieldGet(this, _IssueService_instances, "m", _IssueService_handlePeriodicCheck).bind(this), SIX_HOURS);
}
toggleIssueHiddenStatus(nodeId) {
if (__classPrivateFieldGet(this, _IssueService_hiddenIssues, "f").has(nodeId)) {
__classPrivateFieldGet(this, _IssueService_hiddenIssues, "f").delete(nodeId);
}
else {
__classPrivateFieldGet(this, _IssueService_hiddenIssues, "f").add(nodeId);
}
__classPrivateFieldGet(this, _IssueService_instances, "m", _IssueService_saveHiddenIssues).call(this);
__classPrivateFieldGet(this, _IssueService_instances, "m", _IssueService_issuesUpdated).call(this);
}
addUpdateHandler(handler) {
__classPrivateFieldGet(this, _IssueService_updateHandlers, "f").push(handler);
}
removeUpdateHandler(handler) {
const index = __classPrivateFieldGet(this, _IssueService_updateHandlers, "f").indexOf(handler);
if (index !== -1) {
__classPrivateFieldGet(this, _IssueService_updateHandlers, "f").splice(index, 1);
}
}
}
_IssueService_initialized = new WeakMap(), _IssueService_nodesToCheck = new WeakMap(), _IssueService_issues = new WeakMap(), _IssueService_hiddenIssues = new WeakMap(), _IssueService_updateHandlers = new WeakMap(), _IssueService_tabs = new WeakMap(), _IssueService_issueCheckMap = new WeakMap(), _IssueService_instances = new WeakSet(), _IssueService_getChangedNodes = function _IssueService_getChangedNodes(eventData) {
if (eventData.type === DeploymentType.Full) {
__classPrivateFieldGet(this, _IssueService_issues, "f").clear();
return eventData.config.flows;
}
const changedNodes = new Set();
if (eventData.diff) {
for (const id of eventData.diff.changed) {
// a node can be changed and removed in the same deploy
if (eventData.diff.removed.includes(id)) {
continue;
}
changedNodes.add(id);
}
for (const id of eventData.diff.added) {
changedNodes.add(id);
}
for (const id of eventData.diff.removed) {
__classPrivateFieldGet(this, _IssueService_instances, "m", _IssueService_removeIssue).call(this, id);
}
}
const changedNodesArray = Array.from(changedNodes);
const changedNodesArrayWithDefs = changedNodesArray.reduce((acc, id) => {
const node = eventData.config.flows.find((flow) => flow.id === id);
if (node) {
acc.push(node);
}
return acc;
}, []);
return changedNodesArrayWithDefs;
}, _IssueService_getTabInfo = function _IssueService_getTabInfo() {
globals_1.RED.nodes.eachNode((node) => {
if (node.type === 'tab') {
// @ts-expect-error - disabled is not defined in NodeDef
__classPrivateFieldGet(this, _IssueService_tabs, "f")[node.id] = node.disabled;
}
});
}, _IssueService_handleFlowsStarted = function _IssueService_handleFlowsStarted(event) {
const changedNodes = __classPrivateFieldGet(this, _IssueService_instances, "m", _IssueService_getChangedNodes).call(this, event).filter((node) => !(0, utils_1.isNodeDisabled)(node, __classPrivateFieldGet(this, _IssueService_tabs, "f")[node.z]));
__classPrivateFieldGet(this, _IssueService_instances, "m", _IssueService_performChecks).call(this, changedNodes);
}, _IssueService_handlePeriodicCheck = function _IssueService_handlePeriodicCheck() {
var _a;
const nodes = [];
const foundHiddenIssueNodes = new Set();
(_a = globals_1.RED.nodes) === null || _a === void 0 ? void 0 : _a.eachNode((node) => {
// build a list that still exists
if (__classPrivateFieldGet(this, _IssueService_hiddenIssues, "f").has(node.id)) {
foundHiddenIssueNodes.add(node.id);
}
// only check nodes that are not disabled
if (!(0, utils_1.isNodeDisabled)(node, __classPrivateFieldGet(this, _IssueService_tabs, "f")[node.z])) {
nodes.push(node);
}
});
// remove hidden issues that no longer exist
__classPrivateFieldSet(this, _IssueService_hiddenIssues, foundHiddenIssueNodes, "f");
__classPrivateFieldGet(this, _IssueService_instances, "m", _IssueService_saveHiddenIssues).call(this);
__classPrivateFieldGet(this, _IssueService_instances, "m", _IssueService_performChecks).call(this, nodes);
}, _IssueService_performChecks = function _IssueService_performChecks(nodes) {
var _a;
for (const node of nodes) {
if (!(0, utils_1.isHomeAssistantNode)(node)) {
continue;
}
if ((0, utils_1.isHomeAssistantDataLoaded)(node)) {
__classPrivateFieldGet(this, _IssueService_instances, "m", _IssueService_checkForIssues).call(this, node);
continue;
}
const serverId = (0, utils_1.getServerId)(node);
if (!serverId) {
continue;
}
if (__classPrivateFieldGet(this, _IssueService_nodesToCheck, "f").has(serverId)) {
(_a = __classPrivateFieldGet(this, _IssueService_nodesToCheck, "f").get(serverId)) === null || _a === void 0 ? void 0 : _a.push(node);
}
else {
__classPrivateFieldGet(this, _IssueService_nodesToCheck, "f").set(serverId, [node]);
}
const ha = (0, utils_1.getHomeAssistant)(node);
// if HA is not connected, wait for it to connect and load data
ha === null || ha === void 0 ? void 0 : ha.eventBus.once(Websocket_1.ClientEvent.RegistriesLoaded, () => {
const nodes = __classPrivateFieldGet(this, _IssueService_nodesToCheck, "f").get(serverId);
if (nodes) {
for (const node of nodes) {
__classPrivateFieldGet(this, _IssueService_instances, "m", _IssueService_checkForIssues).call(this, node);
}
}
__classPrivateFieldGet(this, _IssueService_nodesToCheck, "f").delete(serverId);
});
}
}, _IssueService_checkForIssues = function _IssueService_checkForIssues(node) {
let issues = [];
for (const [checkFn, issueFn] of __classPrivateFieldGet(this, _IssueService_issueCheckMap, "f")) {
if (checkFn(node)) {
issues = issueFn(node);
break;
}
}
if (issues.length) {
__classPrivateFieldGet(this, _IssueService_instances, "m", _IssueService_listenForIssueEvents).call(this, node, issues);
__classPrivateFieldGet(this, _IssueService_instances, "m", _IssueService_setIssue).call(this, node.id, issues);
}
else {
__classPrivateFieldGet(this, _IssueService_instances, "m", _IssueService_removeIssue).call(this, node.id);
}
return issues;
}, _IssueService_listenForIssueEvents = function _IssueService_listenForIssueEvents(node, issues) {
const ha = (0, utils_1.getHomeAssistant)(node);
if (!ha) {
return;
}
for (const issue of issues) {
switch (issue.type) {
case IssueType.StateId: {
// listen for state changes to check if the issue is fixed
const eventName = `ha_events:state_changed:${issue.identity}`;
const onEvent = (event) => {
var _a;
if ((event === null || event === void 0 ? void 0 : event.entity_id) === issue.identity) {
const foundIssues = __classPrivateFieldGet(this, _IssueService_instances, "m", _IssueService_checkForIssues).call(this, node);
// if the issue is still present, keep listening
if (!(0, utils_1.includesIssue)(foundIssues, issue)) {
(_a = issue.unsubscribe) === null || _a === void 0 ? void 0 : _a.call(issue);
}
}
};
__classPrivateFieldGet(this, _IssueService_instances, "m", _IssueService_addListener).call(this, ha, issue, eventName, onEvent);
break;
}
case IssueType.AreaId:
case IssueType.DeviceId:
case IssueType.EntityId:
case IssueType.FloorId:
case IssueType.LabelId:
__classPrivateFieldGet(this, _IssueService_instances, "m", _IssueService_listenForRegistryEvent).call(this, ha, issue, node);
break;
}
}
}, _IssueService_listenForRegistryEvent = function _IssueService_listenForRegistryEvent(ha, issue, node) {
const event = IssueTypeToRegistryEventMap[issue.type];
// listen for registry changes to check if the issue is fixed
const onEvent = () => {
var _a;
const foundIssues = __classPrivateFieldGet(this, _IssueService_instances, "m", _IssueService_checkForIssues).call(this, node);
// if the issue is still present, keep listening
if (!(0, utils_1.includesIssue)(foundIssues, issue)) {
(_a = issue.unsubscribe) === null || _a === void 0 ? void 0 : _a.call(issue);
}
};
__classPrivateFieldGet(this, _IssueService_instances, "m", _IssueService_addListener).call(this, ha, issue, event, onEvent);
}, _IssueService_setIssue = function _IssueService_setIssue(nodeId, issues) {
if ((0, utils_1.isIssuesEqual)(issues, __classPrivateFieldGet(this, _IssueService_issues, "f").get(nodeId) || [])) {
return;
}
__classPrivateFieldGet(this, _IssueService_issues, "f").set(nodeId, issues);
globals_1.RED.log.debug(`[Home Assistant] Issue added: ${nodeId}`);
__classPrivateFieldGet(this, _IssueService_instances, "m", _IssueService_issuesUpdated).call(this);
}, _IssueService_removeIssue = function _IssueService_removeIssue(nodeId) {
if (!__classPrivateFieldGet(this, _IssueService_issues, "f").has(nodeId)) {
return;
}
// remove listeners
const issues = __classPrivateFieldGet(this, _IssueService_issues, "f").get(nodeId);
issues === null || issues === void 0 ? void 0 : issues.forEach((issue) => {
var _a;
(_a = issue.unsubscribe) === null || _a === void 0 ? void 0 : _a.call(issue);
});
__classPrivateFieldGet(this, _IssueService_issues, "f").delete(nodeId);
__classPrivateFieldGet(this, _IssueService_hiddenIssues, "f").delete(nodeId);
globals_1.RED.log.debug(`[Home Assistant] Issue removed: ${nodeId}`);
__classPrivateFieldGet(this, _IssueService_instances, "m", _IssueService_issuesUpdated).call(this);
}, _IssueService_issuesUpdated = function _IssueService_issuesUpdated() {
const issues = Array.from(__classPrivateFieldGet(this, _IssueService_issues, "f")).map(([nodeId, issues]) => ({
nodeId,
messages: issues.map((issue) => issue.message),
hide: __classPrivateFieldGet(this, _IssueService_hiddenIssues, "f").has(nodeId),
}));
__classPrivateFieldGet(this, _IssueService_updateHandlers, "f").forEach((handler) => handler(issues));
}, _IssueService_loadHiddenIssues = function _IssueService_loadHiddenIssues() {
__classPrivateFieldSet(this, _IssueService_hiddenIssues, new Set(StorageService_1.default.getIssues()), "f");
}, _IssueService_saveHiddenIssues = function _IssueService_saveHiddenIssues() {
StorageService_1.default.saveIssues(Array.from(__classPrivateFieldGet(this, _IssueService_hiddenIssues, "f")));
}, _IssueService_addListener = function _IssueService_addListener(ha, issue, event, handler) {
ha.eventBus.on(event, handler);
issue.unsubscribe = () => {
ha.eventBus.removeListener(event, handler);
issue.unsubscribe = undefined;
};
};
exports.default = new IssueService();