UNPKG

node-red-contrib-home-assistant-websocket

Version:
342 lines (341 loc) 19.5 kB
"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();