@ha4us/script.adapter
Version:
Scripting Adapter for the ha4us
233 lines • 8.14 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const vm = require("vm");
const domain = require("domain");
const rxjs_1 = require("rxjs");
const core_1 = require("@babel/core");
const core_2 = require("@ha4us/core");
const sandbox_class_1 = require("./sandbox.class");
var ScriptEventType;
(function (ScriptEventType) {
ScriptEventType["Error"] = "error";
ScriptEventType["Log"] = "log";
ScriptEventType["State"] = "state";
})(ScriptEventType = exports.ScriptEventType || (exports.ScriptEventType = {}));
class ScriptEvent {
constructor(name, type, data) {
this.name = name;
this.type = type;
this.data = data;
}
}
exports.ScriptEvent = ScriptEvent;
class LogEvent {
constructor(severity, message, attachments) {
this.severity = severity;
this.message = message;
this.attachments = attachments;
}
}
exports.LogEvent = LogEvent;
class Ha4usScript {
constructor(scriptObject, opts) {
this.opts = opts;
this.autostart = false;
this._status = 'stopped';
this.status$ = new rxjs_1.Subject();
this.log$ = new rxjs_1.Subject();
this.name = scriptObject.topic;
this.topic = core_2.MqttUtil.slice(this.name, 1);
this.path = opts.$args.scriptsDir || process.cwd();
this.$log = opts.$log;
this._source = scriptObject.native.source;
this.autostart =
scriptObject.native.autostart ||
typeof scriptObject.native.autostart === 'undefined';
}
set source(val) {
this._source = val;
}
set status(val) {
this._status = val;
this.status$.next(val);
}
get status() {
return this._status;
}
log(severity, msg, attachments) {
this.log$.next(new LogEvent(severity, msg, attachments));
}
prepareStack(e, match) {
const lines = e.stack.split('\n');
const stack = [];
stack.push(e.message);
for (const line of lines) {
stack.push(line);
if (line.match(match)) {
// stack.splice(-1, 1)
break;
}
}
return stack.join('\n');
}
init() {
return __awaiter(this, void 0, void 0, function* () {
this.status = 'stopped';
this.sandbox = new sandbox_class_1.Sandbox(this);
this.domain = domain.create();
this.enterDomain();
this.$log.debug(`creating sandbox for ${this.name}`);
this.domain.on('error', e => {
this.$log.warn(`catched domain error ${e.message} in ${this.name}`);
this.log$.next(new LogEvent('error', this.prepareStack(e, /domain:$/)));
});
return this;
});
}
transpile(source) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
core_1.transform(source, {
presets: [
[
'@babel/env',
{
targets: {
node: 'current',
},
},
],
],
}, (err, res) => {
if (err) {
reject(err);
}
else {
this.$log.debug(`${this.name} transpiled`);
resolve(res.code);
}
});
});
});
}
compile() {
return __awaiter(this, void 0, void 0, function* () {
if (!this._source) {
throw new core_2.Ha4usError(500, `source not available`);
}
try {
const transpiled = yield this.transpile(this._source);
const extSource = `((async ()=>{${transpiled}})())`;
this.script = new vm.Script(extSource, {
filename: this.name,
timeout: 1000,
});
this.log('info', `script successfully compiled`);
this.$log.debug(`${this.name} compiled`);
return this;
}
catch (e) {
this.$log.warn(`compilation of ${this.name} failed ${e.message}`);
this.status = 'error';
this.log('error', this.prepareStack(e, /at Ha4usScript.compile|at Parser.raise/));
throw e;
}
});
}
enterDomain() {
if (process.domain !== this.domain) {
this.$log.debug(`${this.name} entering script domain`);
this.domain.enter();
}
}
start() {
return __awaiter(this, void 0, void 0, function* () {
if (this.status === 'running') {
return this;
}
if (!this.script) {
throw new core_2.Ha4usError(500, `script is not compiled`);
}
this.$log.debug('Starting script');
const context = vm.createContext(this.sandbox.sandbox);
return this.script
.runInContext(context)
.then((val) => {
this.result = val;
if (!this.sandbox._onDestroy) {
this.log('info', `script has no $onDestroy function`);
}
this.log('info', `script finished with result ${val}`);
this.status = 'running';
return this;
})
.catch(e => {
this.log('error', this.prepareStack(e, /Script.runInContext/));
this.status = 'error';
throw new core_2.Ha4usError(500, 'error script execution', e);
});
});
}
stop() {
return __awaiter(this, void 0, void 0, function* () {
if (this.status !== 'running') {
return this;
}
this.$log.debug('Stopping script');
this.sandbox.stop$.next();
try {
if (this.sandbox._onDestroy) {
this.sandbox._onDestroy();
}
}
catch (e) {
this.log('error', this.prepareStack(e, /at ContextifyScript.Script.runInContext/));
}
finally {
this.status = 'stopped';
}
return this;
});
}
restart() {
return __awaiter(this, void 0, void 0, function* () {
this.$log.debug('Restarting script');
yield this.stop();
if (this.autostart === true) {
yield this.start();
}
return this;
});
}
destroy() {
return __awaiter(this, void 0, void 0, function* () {
yield this.stop();
this.log('info', `script destroyed`);
this.log$.complete();
this.status$.complete();
if (this.domain) {
this.domain.removeAllListeners();
this.domain.exit();
}
return this;
});
}
toHa4usObject() {
return {
role: core_2.Ha4usRole.Script,
native: {
source: this._source,
},
};
}
}
exports.Ha4usScript = Ha4usScript;
//# sourceMappingURL=ha4us-script.js.map