/* nodejs-poolController. An application to control pool equipment.
Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
Russell Goldin, tagyoureit. russ.goldin@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
import { webApp } from "../../web/Server";
import extend=require("extend");
import { logger } from "../../logger/Logger";
import { PoolSystem, sys } from "../../controller/Equipment";
import { State, state } from "../../controller/State";
import { InterfaceContext, InterfaceEvent, BaseInterfaceBindings } from "./baseInterface";
export class RuleInterfaceBindings extends BaseInterfaceBindings {
constructor(cfg) { super(cfg);}
declare events: RuleInterfaceEvent[];
public bindProcessor(evt: RuleInterfaceEvent) {
if (evt.processorBound) return;
if (typeof evt.fnProcessor === 'undefined') {
let fnBody = Array.isArray(evt.processor) ? evt.processor.join('\n') : evt.processor;
if (typeof fnBody !== 'undefined' && fnBody !== '') {
//let AsyncFunction = Object.getPrototypeOf(async => () => { }).constructor;
let AsyncFunction = Object.getPrototypeOf(async function () { }).constructor;
try {
evt.fnProcessor = new AsyncFunction('rule', 'options', 'vars', 'logger', 'webApp', 'sys', 'state', 'data', fnBody) as (rule: RuleInterfaceEvent, vars: any, sys: PoolSystem, state: State, data: any) => void;
} catch (err) { logger.error(`Error compiling rule event processor: ${err} -- ${fnBody}`); }
}
}
evt.processorBound = true;
}
public executeProcessor(eventName: string, evt: RuleInterfaceEvent, ...data: any) {
this.bindProcessor(evt);
let vars = this.bindVarTokens(evt, eventName, data);
let opts = extend(true, this.cfg.options, this.context.options, evt.options);
// `fnProcessor` is an AsyncFunction; we should await it to avoid unhandled promise rejections
// and to preserve consistent ordering when multiple events fire in a tight loop.
if (typeof evt.fnProcessor !== 'undefined') {
Promise.resolve(evt.fnProcessor(evt, opts, vars, logger, webApp, sys, state, data))
.catch(err => logger.error(`Error executing rule event processor (${eventName}): ${err?.message || err}`));
}
}
public bindEvent(evt: string, ...data: any) {
// Find the binding by first looking for the specific event name.
// If that doesn't exist then look for the "*" (all events).
if (typeof this.events !== 'undefined') {
let evts = this.events.filter(elem => elem.name === evt);
// If we don't have an explicitly defined event then see if there is a default.
if (evts.length === 0) {
let e = this.events.find(elem => elem.name === '*');
evts = e ? [e] : [];
}
if (evts.length > 0) {
let toks = {};
for (let i = 0; i < evts.length; i++) {
let e = evts[i];
if (typeof e.enabled !== 'undefined' && !e.enabled) continue;
// Figure out whether we need to check the filter.
if (typeof e.filter !== 'undefined') {
try {
this.buildTokens(e.filter, evt, toks, e, data[0]);
if (eval(this.replaceTokens(e.filter, toks)) === false) continue;
} catch (err) {
logger.error(`Error evaluating rule filter (${evt}): ${err?.message || err} -- ${e.filter}`);
continue;
}
}
// Look for the processor.
try {
this.executeProcessor(evt, e, ...data);
} catch (err) {
logger.error(`Error running rule processor (${evt}): ${err?.message || err}`);
}
}
}
}
}
}
class RuleInterfaceEvent extends InterfaceEvent {
event: string;
description: string;
fnProcessor: (rule: RuleInterfaceEvent, options:any, vars: any, logger: any, webApp: any, sys: PoolSystem, state: State, data: any) => Promise | void;
processorBound: boolean = false;
}
export interface IRuleInterfaceEvent {
event: string,
description: string,
processor?: string
}