Skip to content

Commit

Permalink
fix: Use error handler for event class (#1018)
Browse files Browse the repository at this point in the history
  • Loading branch information
zachowj authored Aug 3, 2023
1 parent 3f31af6 commit b009362
Show file tree
Hide file tree
Showing 16 changed files with 77 additions and 93 deletions.
48 changes: 1 addition & 47 deletions src/common/events/ClientEvents.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,17 @@
import EventEmitter from 'events';
import Joi from 'joi';
import { Node } from 'node-red';

import { RED } from '../../globals';
import { ClientEvent } from '../../homeAssistant/Websocket';
import BaseError from '../errors/BaseError';
import Status from '../status/Status';
import Events, { EventHandler } from './Events';
import Events from './Events';

export default class ClientEvents extends Events {
#status?: Status;

constructor({ node, emitter }: { node: Node; emitter: EventEmitter }) {
super({ node, emitter });

this.emitter.on(ClientEvent.Error, this.onHaEventsError.bind(this));
}

addListener(
event: string | symbol,
handler: EventHandler,
options = { once: false }
): void {
super.addListener(event, this.#errorHandler(handler), options);
}

onHaEventsError(err: Error) {
if (err?.message) this.node.error(err.message);
}

// set status for error reporting
setStatus(status: Status) {
this.#status = status;
}

#errorHandler(callback: any) {
return (...args: any) => {
try {
// eslint-disable-next-line n/no-callback-literal
callback(...args);
} catch (e) {
let statusMessage = RED._('home-assistant.status.error');
if (e instanceof Joi.ValidationError) {
statusMessage = RED._(
'home-assistant.status.validation_error'
);
this.node.error(e);
} else if (e instanceof BaseError) {
statusMessage = e.statusMessage;
this.node.error(e);
} else if (e instanceof Error) {
this.node.error(e);
} else if (typeof e === 'string') {
this.node.error(new Error(e));
} else {
this.node.error(new Error(`Unrecognised error: ${e}`));
}
this.#status?.setFailed(statusMessage);
}
};
}
}
60 changes: 50 additions & 10 deletions src/common/events/Events.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import EventEmitter from 'events';
import Joi from 'joi';
import { Node } from 'node-red';

import { RED } from '../../globals';
import { NodeDone } from '../../types/nodes';
import BaseError from '../errors/BaseError';
import JSONataError from '../errors/JSONataError';
import Status from '../status/Status';

export type EventHandler = (...args: any[]) => void;
type EventHandler = (...args: any[]) => void | Promise<void>;
export type EventsList = [string | symbol, EventHandler][];

export enum NodeEvent {
Expand All @@ -14,6 +19,8 @@ export enum NodeEvent {

export default class Events {
#listeners: EventsList = [];
#status?: Status;

protected readonly node;
protected readonly emitter;

Expand All @@ -22,42 +29,75 @@ export default class Events {
this.emitter = emitter;
emitter.setMaxListeners(0);

node.on(NodeEvent.Close, this.onClose.bind(this));
node.on(NodeEvent.Close, this.#onClose.bind(this));
}

#errorHandler(callback: EventHandler) {
return async (...args: any) => {
try {
// eslint-disable-next-line n/no-callback-literal
await callback(...args);
} catch (e) {
let statusMessage = RED._('home-assistant.status.error');
let error = e;
if (e instanceof Joi.ValidationError) {
error = new JSONataError(e);
statusMessage = RED._(
'home-assistant.status.validation_error'
);
} else if (e instanceof BaseError) {
statusMessage = e.statusMessage;
} else if (typeof e === 'string') {
error = new Error(e);
} else {
error = new Error(
`Unrecognised error ${JSON.stringify(e)}`
);
}
this.node.error(error);
this.#status?.setFailed(statusMessage);
}
};
}

onClose(_removed: boolean, done: NodeDone) {
#onClose(_removed: boolean, done: NodeDone) {
this.removeListeners();
done();
}

addListener(
public addListener(
event: string | symbol,
handler: EventHandler,
options = { once: false }
): void {
this.#listeners.push([event, handler]);
this.#listeners.push([event, this.#errorHandler(handler)]);

if (options.once === true) {
this.emitter.once(event, handler);
this.emitter.once(event, this.#errorHandler(handler));
} else {
this.emitter.on(event, handler);
this.emitter.on(event, this.#errorHandler(handler));
}
}

addListeners(bind: any, eventsList: EventsList) {
public addListeners(bind: unknown, eventsList: EventsList) {
eventsList.forEach(([event, handler]) => {
this.addListener(event, handler.bind(bind));
});
}

removeListeners() {
public removeListeners() {
this.#listeners.forEach(([event, handler]) => {
this.emitter.removeListener(event, handler);
});
this.#listeners = [];
}

emit(event: string | symbol, ...args: any[]): boolean {
// set status for error reporting
public setStatus(status: Status) {
this.#status = status;
}

public emit(event: string | symbol, ...args: unknown[]): boolean {
return this.emitter.emit(event, ...args);
}
}
5 changes: 3 additions & 2 deletions src/nodes/button/ButtonController.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import OutputController from '../../common/controllers/OutputController';
import { HassEntity } from '../../types/home-assistant';
import { NodeMessage } from '../../types/nodes';
import { ButtonNode } from '.';

export default class ButtonController extends OutputController<ButtonNode> {
public onTrigger(data: { entity: HassEntity }) {
this.status.setSuccess('home-assistant.status.pressed');
const message = {};
const message: NodeMessage = {};
this.setCustomOutputs(this.node.config.outputProperties, message, {
config: this.node.config,
entity: data.entity,
entityState: data.entity.state,
triggerId: data.entity.entity_id,
});
this.status.setSuccess('home-assistant.status.pressed');
this.node.send(message);
}
}
1 change: 1 addition & 0 deletions src/nodes/button/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export default function buttonNode(
node: this,
emitter: entityConfigNode,
});
entityConfigEvents.setStatus(status);
const controllerDeps = createControllerDependencies(this, homeAssistant);

entityConfigNode.integration.setStatus(status);
Expand Down
2 changes: 1 addition & 1 deletion src/nodes/number/NumberController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ export default class NumberController extends InputOutputController<
previousValue?: number
): Promise<void> {
await this.integration?.updateHomeAssistant(value);
this.status.setSuccess(value.toString());
if (!previousValue) {
previousValue = this.#entityConfigNode?.state?.getLastPayload()
?.state as number | undefined;
Expand All @@ -158,5 +157,6 @@ export default class NumberController extends InputOutputController<
state: value,
attributes: {},
});
this.status.setSuccess(value.toString());
}
}
2 changes: 1 addition & 1 deletion src/nodes/number/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export default function numberNode(
node: this,
emitter: entityConfigNode,
});

entityConfigEvents.setStatus(status);
entityConfigEvents.addListener(
IntegrationEvent.ValueChange,
controller.onValueChange.bind(controller)
Expand Down
2 changes: 1 addition & 1 deletion src/nodes/select/SelectController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ export default class SelectController extends InputOutputController<
}

await this.integration?.updateHomeAssistant(value);
this.status.setSuccess(value);
if (!previousValue) {
previousValue = this.#entityConfigNode?.state?.getLastPayload()
?.state as string | undefined;
Expand All @@ -135,6 +134,7 @@ export default class SelectController extends InputOutputController<
state: value,
attributes: {},
});
this.status.setSuccess(value);
}

public async onValueChange(value: string, previousValue?: string) {
Expand Down
2 changes: 1 addition & 1 deletion src/nodes/select/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export default function selectNode(
node: this,
emitter: entityConfigNode,
});

entityConfigEvents.setStatus(status);
entityConfigEvents.addListener(
IntegrationEvent.ValueChange,
controller.onValueChange.bind(controller)
Expand Down
18 changes: 6 additions & 12 deletions src/nodes/sentence/SentenceController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,13 @@ interface SentenceResponse {

export default class SentenseController extends OutputController<SentenceNode> {
public onReceivedMessage(data: SentenceResponse) {
this.status.setSuccess('home-assistant.status.triggered');
const message: NodeMessage = {};
try {
this.setCustomOutputs(this.node.config.outputProperties, message, {
config: this.node.config,
triggerId: data.sentence,
results: data.result,
});
} catch (e) {
this.node.error(e);
this.status.setFailed('home-assistant.status.error');
return;
}
this.setCustomOutputs(this.node.config.outputProperties, message, {
config: this.node.config,
triggerId: data.sentence,
results: data.result,
});
this.status.setSuccess('home-assistant.status.triggered');
this.node.send(message);
}
}
2 changes: 1 addition & 1 deletion src/nodes/sentence/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default function sentenceNode(
config: serverConfigNode.config,
node: this,
});

nodeEvents.setStatus(status);
const controllerDeps = createControllerDependencies(this, homeAssistant);
const integration = new SentenceIntegration({
node: this,
Expand Down
2 changes: 1 addition & 1 deletion src/nodes/text/TextController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,6 @@ export default class TextController extends InputOutputController<
}

await this.integration?.updateHomeAssistant(value);
this.status.setSuccess(value);
if (!previousValue) {
previousValue = this.#entityConfigNode?.state?.getLastPayload()
?.state as string | undefined;
Expand All @@ -140,6 +139,7 @@ export default class TextController extends InputOutputController<
state: value,
attributes: {},
});
this.status.setSuccess(value);
}

public async onValueChange(value: string, previousValue?: string) {
Expand Down
2 changes: 1 addition & 1 deletion src/nodes/text/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export default function textNode(this: TextNode, config: TextNodeProperties) {
node: this,
emitter: entityConfigNode,
});

entityConfigEvents.setStatus(status);
entityConfigEvents.addListener(
IntegrationEvent.ValueChange,
controller.onValueChange.bind(controller)
Expand Down
2 changes: 1 addition & 1 deletion src/nodes/time-entity/TimeEntityController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,6 @@ export default class TimeEntityController extends InputOutputController<
value = this.#getFormattedValue(value);

await this.integration?.updateHomeAssistant(value);
this.status.setSuccess(value);
if (!previousValue) {
previousValue = this.#entityConfigNode?.state?.getLastPayload()
?.state as string | undefined;
Expand All @@ -168,5 +167,6 @@ export default class TimeEntityController extends InputOutputController<
state: value,
attributes: {},
});
this.status.setSuccess(value);
}
}
2 changes: 1 addition & 1 deletion src/nodes/time-entity/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export default function timeEntityNode(
node: this,
emitter: entityConfigNode,
});

entityConfigEvents.setStatus(status);
entityConfigEvents.addListener(
IntegrationEvent.ValueChange,
controller.onValueChange.bind(controller)
Expand Down
19 changes: 6 additions & 13 deletions src/nodes/webhook/WebhookController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,12 @@ interface WebhookResponse {
export default class WebhookController extends OutputController<WebhookNode> {
public onReceivedMessage(data: WebhookResponse) {
const message: NodeMessage = {};
try {
this.setCustomOutputs(this.node.config.outputProperties, message, {
config: this.node.config,
data: data.payload,
headers: data.headers,
params: data.params,
});
} catch (e) {
this.node.error(e);
this.status.setFailed('error');
return;
}

this.setCustomOutputs(this.node.config.outputProperties, message, {
config: this.node.config,
data: data.payload,
headers: data.headers,
params: data.params,
});
this.status.setSuccess('home-assistant.status.received');
this.node.send(message);
}
Expand Down
1 change: 1 addition & 0 deletions src/nodes/webhook/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export default function webhookNode(
config: serverConfigNode.config,
node: this,
});
nodeEvents.setStatus(status);

const controllerDeps = createControllerDependencies(this, homeAssistant);
const integration = new WebhookIntegration({
Expand Down

0 comments on commit b009362

Please sign in to comment.