node-red-contrib-home-assistant-websocket
Version:
Node-RED integration with Home Assistant through websocket and REST API
110 lines (109 loc) • 3.8 kB
JavaScript
"use strict";
const { merge } = require('lodash');
const BaseError = require('../common/errors/BaseError').default;
const BaseNode = require('./BaseNode');
const { INTEGRATION_EVENT, INTEGRATION_LOADED, INTEGRATION_UNLOADED, INTEGRATION_NOT_LOADED, } = require('../const');
const DEFAULT_NODE_OPTIONS = {
debug: false,
config: {
exposeToHomeAssistant: (nodeDef) => nodeDef.exposeToHomeAssistant === undefined
? false
: nodeDef.exposeToHomeAssistant,
},
};
class EventsNode extends BaseNode {
constructor({ node, config, RED, status, nodeOptions = {} }) {
nodeOptions = merge({}, DEFAULT_NODE_OPTIONS, nodeOptions);
super({ node, config, RED, status, nodeOptions });
this.listeners = {};
this.registered = false;
this.integrationErrorMessage =
'Node-RED custom integration needs to be installed in Home Assistant for this node to function correctly.';
this.status = status;
this.status.init({
nodeState: this.isEnabled,
homeAssistant: this.homeAssistant,
});
// Setup event listeners
const events = {
'ha_client:close': this.onHaEventsClose,
'ha_client:error': this.onHaEventsError,
[INTEGRATION_EVENT]: this.onHaIntegration,
};
Object.entries(events).forEach(([event, callback]) => this.addEventClientListener(event, callback.bind(this)));
}
errorHanlderForEvents(callback) {
return (...args) => {
try {
// eslint-disable-next-line n/no-callback-literal
callback(...args);
}
catch (err) {
if (err instanceof BaseError) {
this.status.setFailed(err.statusMessage);
this.node.error(err);
}
else {
this.status.setFailed(this.RED._('home-assistant.status.error'));
this.node.error(err.message);
}
}
};
}
addEventClientListener(event, handler) {
if (this.homeAssistant) {
const callback = this.errorHanlderForEvents(handler);
this.listeners[event] = callback;
this.homeAssistant.addListener(event, callback);
}
}
removeEventClientListeners() {
if (this.homeAssistant) {
Object.entries(this.listeners).forEach(([event, handler]) => {
this.homeAssistant.removeListener(event, handler);
});
}
}
async onClose(removed) {
super.onClose(removed);
this.removeEventClientListeners();
if (typeof this.status.destroy === 'function') {
this.status.destroy();
}
}
onHaEventsClose() {
this.registered = false;
}
onHaEventsError(err) {
if (err && err.message)
this.node.error(err.message);
}
onHaIntegration(type) {
switch (type) {
case INTEGRATION_LOADED:
this.registerEntity();
break;
case INTEGRATION_UNLOADED:
case INTEGRATION_NOT_LOADED:
this.registered = false;
break;
}
}
registerEntity() {
if (this.nodeConfig.exposeToHomeAssistant === false) {
return false;
}
if (!this.isIntegrationLoaded) {
if (this.node.type === 'ha-webhook' ||
this.node.type === 'ha-entity') {
this.node.error(this.integrationErrorMessage);
this.status.setFailed('Error');
}
return false;
}
if (this.registered) {
return false;
}
}
}
module.exports = EventsNode;