commit 66760c4325403560ba07f57f5ac97a5d45624d1a Author: Simon Zeyer Date: Mon Sep 23 22:45:58 2024 +0200 version 0.0.1 diff --git a/base.js b/base.js new file mode 100644 index 0000000..43622e3 --- /dev/null +++ b/base.js @@ -0,0 +1,129 @@ +const PluginBaseTemplate = require('./../../templates/pluginBaseTemplate'); +const manifest = require('./manifest.json'); + +const Basedata = require('../../../models/basedata'); +const Operation = require('../../../models/core/core_operation'); +const Organizations = require('../../../models/organizations'); +const Alertdevices = require('../../../models/alertdevices'); +const Configuration = require('../../../models/configuration'); +const FCMTokens = require('../../../models/fctmtokens'); +/** + * Class for DIVERA_STATUS communication + * @class plugins/inbound/divera_status + * @namespace plugins/inbound/divera_status + * @memberof plugins/inbound/divera_status + */ +class divera_status extends PluginBaseTemplate { + + /** + * Constructor + * + * Sets up everything, starts service DIVERA_STATUS + */ + constructor() { + super(manifest, __dirname); + + this.basedata = new Basedata(global.plgManager.logger); + this.organizations = new Organizations(global.plgManager.logger); + this.alertdevices = new Alertdevices(global.plgManager.logger); + this.configuration = new Configuration(global.plgManager.logger); + this.fcmTokens = new FCMTokens(global.plgManager.logger); + } + + /** + * Plugin-specific implementation + */ + newFeedback(feedbackInfo) { + global.plgManager.logger.info('Received new Feedback from REST API', {label: this.logIdentifier}); + global.plgManager.event_new_feedback_received(feedbackInfo.operationUUID, feedbackInfo.basedataUUID, feedbackInfo.stateId); + } + + /** + * reconfigures DIVERA_STATUS plugin with new config settings + * @param {function} callback success, true or false + * @memberof plugins/inbound/divera_status + */ + initializePlugin(callback) { + this.config = { + AUTOLOGIN_KEY: this.getConfigValue('AUTOLOGIN_KEY'), + STATUS_EB: this.getConfigValue('STATUS_EB'), + STATUS_NEB: this.getConfigValue('STATUS_NEB'), + MONITOR_ID: this.getConfigValue('MONITOR_ID'), + loglevel: 'debug' + } + if ( + (typeof this.config.AUTOLOGIN_KEY !== 'undefined') && + (typeof this.config.status_mapping_use_text !== 'undefined') + //|| (typeof this.config.httpsPort !== 'undefined') && (typeof this.config.runOnHTTPS !== 'undefined') + ) { + global.plgManager.logger.info('Properly inititlized settings', {label: this.logIdentifier}); + if (this.config.AUTOLOGIN_KEY != '') { + callback(true); + } else { + global.plgManager.logger.error('Could not initialize settings', {label: this.logIdentifier}); + callback(false); + } + + } else { + global.plgManager.logger.error('Could not initialize settings', {label: this.logIdentifier}); + callback(false); + } + } + + /** + * Wires messages from service to event model of plugin + * All Messages in service need to be wired here to be recognised in plugin + * @memberof plugins/inbound/divera_status + */ + wireServiceCommunication() { + this.subProcess.on('message', function (msg) { + var payload = []; + + try { + payload = JSON.parse(msg); + } catch (err) { + payload = []; + } + if (typeof payload.logmessage != 'undefined') { + msg = payload.logmessage; + } + global.plgManager.logger.info(JSON.stringify(msg), {label: this.logIdentifier}); + + var payload = null; + try { + payload = JSON.parse(msg); + } catch (err) { + payload = err.message; + } + if (typeof payload.feedbackState != 'undefined') { + var basedataUUID = payload.feedbackState.basedataUUID; + var operationUUID = payload.feedbackState.operationUUID; + var feedbackState = payload.feedbackState.feedbackState; + global.plgManager.event_new_feedback_received(operationUUID, basedataUUID, feedbackState); + global.plgManager.logger.info('Sending new Feedback to PluginManager', {label: this.logIdentifier}); + } + + if (typeof payload.logmessage != 'undefined') { + global.plgManager.logger.info(payload.logmessage, { label: this.logIdentifier }); + + } else if (typeof payload.event != 'undefined') { + // service send structured data + + if (payload.event.event === 'event_new_feedback') { + var message = payload.event.feedback; + global.plgManager.logger.info('NEW FEEDBACK', {label: this.logIdentifier}); + //this.event_new_feedback_received() + } else if (payload.event.event === 'error') { + global.plgManager.logger.error('PLUGIN | RESTAPI | SERVICE | ERROR',{label: this.logIdentifier}); + global.plgManager.logger.error('ERROR ' + JSON.stringify(payload.event.error), {label: this.logIdentifier}); + global.plgManager.event_new_admin_notification('REST API Fehler: ' + JSON.stringify(payload.event.error)); + this.executeServiceRestartRoutine(); + } + } else { + global.plgManager.logger.info('UNSOLICITED MESSAGE: ' + JSON.stringify(msg), {label: this.logIdentifier}); + } + }.bind(this)); + } +} + +module.exports = divera_status; \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..d727405 --- /dev/null +++ b/manifest.json @@ -0,0 +1,50 @@ +{ + "name": "Divera Status", + "version": "0.0.1", + "description": "Übernehmen des Status aus der Divera App in Alarmiator", + "main": "base.js", + "service": "service.js", + "namespace" : "divera_status", + "logidentifier" : "DIVERA_STATUS", + "author": "Simon Zeyer", + "plgtype": "inbound", + "isService" : true, + "configstore" : { + "fields" : [ + { + "fieldname" : "AUTOLOGIN_KEY", + "label" : "Monitor Autologin key", + "description" : "Auto-Login-Key eines Monitor, der den Status der Nutzer sehen darf.", + "presetvalue" : "", + "type" : "string", + "mandantory" : true + }, + { + "fieldname" : "STATUS_EB", + "label" : "Status für Einsatzbereit", + "description" : "Aktiviert alle seine Gruppen in ALARMIATOR wenn User in Divera diesen Status erhällt (Geofence z.B.)", + "presetvalue" : "", + "type" : "string", + "mandantory" : false + }, + { + "fieldname" : "STATUS_NEB", + "label" : "Status für nicht Einsatzbereit", + "description" : "Deaktiviert alle seine Gruppen in ALARMIATOR wenn User in Divera diesen Status erhällt (Geofence z.B.)", + "presetvalue" : "", + "type" : "string", + "mandantory" : false + }, + { + "fieldname" : "MONITOR_ID", + "label" : "ID des Monitor", + "description" : "Normalerweise 1", + "presetvalue" : "1", + "type" : "string", + "mandantory" : true + } + ] + }, + "alarmingByUser" : false + } + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..d71bcf3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,209 @@ +{ + "name": "alarmiator_divera_status", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "alarmiator_divera_status", + "version": "0.0.1", + "dependencies": { + "axios": "^1.7.7", + "axios-cookiejar-support": "^5.0.2" + } + }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios-cookiejar-support": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/axios-cookiejar-support/-/axios-cookiejar-support-5.0.2.tgz", + "integrity": "sha512-4KwFqiH62Ry/J+ue1NwqLvBSaK3Bp/Owc9jTe7BS9c22uN8/4MB5pY7HV9QwQjV3ZRonCoSj5V8Pjx911qQqAw==", + "dependencies": { + "http-cookie-agent": "^6.0.5" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/3846masa" + }, + "peerDependencies": { + "axios": ">=0.20.0", + "tough-cookie": ">=4.0.0" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-cookie-agent": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/http-cookie-agent/-/http-cookie-agent-6.0.6.tgz", + "integrity": "sha512-XkwhYUWo0yhiHBWqLmAe2kIBymVY70ewi9sKmy6YBHpNU3BCH4nipKrtY5/effAxj0qneQ9ziZG5TXgaKLfYgg==", + "dependencies": { + "agent-base": "^7.1.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/3846masa" + }, + "peerDependencies": { + "tough-cookie": "^4.0.0 || ^5.0.0", + "undici": "^5.11.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "undici": { + "optional": true + } + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/tldts": { + "version": "6.1.47", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.47.tgz", + "integrity": "sha512-R/K2tZ5MiY+mVrnSkNJkwqYT2vUv1lcT6wJvd2emGaMJ7PHUGRY4e3tUsdFCXgqxi2QgbHjL3yJgXCo40v9Hxw==", + "peer": true, + "dependencies": { + "tldts-core": "^6.1.47" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.47", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.47.tgz", + "integrity": "sha512-6SWyFMnlst1fEt7GQVAAu16EGgFK0cLouH/2Mk6Ftlwhv3Ol40L0dlpGMcnnNiiOMyD2EV/aF3S+U2nKvvLvrA==", + "peer": true + }, + "node_modules/tough-cookie": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", + "peer": true, + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..024e3f3 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "alarmiator_divera_status", + "version": "0.0.1", + "private": true, + "description": "Divera status to Alarmiator", + "keywords": [], + "author": "", + "license": "", + "main": "service.js", + "scripts": { + "start": "node service.js", + "test": "swagger project test" + }, + "dependencies": { + "axios": "^1.7.7", + "axios-cookiejar-support": "^5.0.2" + } +} diff --git a/service.js b/service.js new file mode 100644 index 0000000..750b341 --- /dev/null +++ b/service.js @@ -0,0 +1,250 @@ + +var Database = require('../../../models/database'); +var manifest = require('./manifest.json'); +const { createWinstonLogger } = require('../../../utils/logging'); +const Basedata = require('../../../models/basedata'); +const Feedbackstates = require('../../../models/feedbackstates'); +const Feedbackstore = require('../../../models/core/feedbackstore'); +const Operations = require('../../../models/operationshelper'); +const Groups = require('../../../models/groups'); +// axios session +const axios = require('axios').default; +const { CookieJar } = require('tough-cookie'); +const { wrapper } = require('axios-cookiejar-support'); +const jar = new CookieJar(); +const client = wrapper(axios.create({ jar })); + + +global.manifest = manifest; +global.apiAdress = null; +var serviceState = 0; +var config = null; +var pulled_data = {}; +const AUTH_ENDPOINT = "https://app.divera247.com/monitor/" +const PULL_ENDPOINT = "https://app.divera247.com/api/pull" + +async function getFeedbackStateIDbyName(name){ + return new Promise((resolve, reject) => { + var feedbackstates = new Feedbackstates(global.logger); + feedbackstates.getAllActiveStates((rows)=>{ + if(rows !== null){ + rows.forEach((e)=>{ + if(name == e.stateLabel){ + resolve(e.id) + } + }) + } + resolve(null) + }) + }); +} + +function setFeedback(operationUUID, basedataUUID, feedbackState) { + var feedbackstore = new Feedbackstore(global.logger); + global.logger.debug('DIVERA_STATUS | FEEDBACK | ' + Math.floor(new Date().getTime()) + ' setFeedback | persisting feedback now ..'); + feedbackstore.setFeedback(basedataUUID, operationUUID, feedbackState, (success) => { + if (success) { + global.logger.debug('DIVERA_STATUS | FEEDBACK | ' + Math.floor(new Date().getTime()) + ' setFeedback | successfully persisted feedback'); + var feedbackInfo = { + basedataUUID: basedataUUID, + operationUUID: operationUUID, + feedbackState: feedbackState + } + var sendMessage = { + feedbackState : feedbackInfo + } + process.send(JSON.stringify(sendMessage)); + + } else { + global.logger.debug('DIVERA_STATUS | FEEDBACK | ' + Math.floor(new Date().getTime()) + ' setFeedback | ERROR - Could not persist feedback'); + } + }) +} + +async function getMemberByName(firstname,lastname){ + return new Promise((resolve, reject) => { + new Basedata(global.logger).getListOfBasedata((rows)=>{ + if(rows !== null){ + rows.forEach((e)=> { + if(e.firstname == firstname && e.lastname == lastname){ + resolve(e.uuid) + } + }) + } + resolve(null) + }) + }); +} + +async function setGroupsMutedStateForMember(member_guid, muted = false){ + var b= new Basedata(global.logger) + b.getBaseDataForUUID(member_guid,(_rows)=> { + if(_rows !== null){ + _rows.forEach((_e)=>{ + var g = new Groups(global.logger) + g.setAllGroupsMutedStateForBasedata(_e.id,muted?1:0,(success)=>{}) + }) + } + + }) + +} + +async function getActiveOperation(){ + return new Promise((resolve, reject) => { + + new Operations(global.logger).getListOfActiveOperations((rows)=>{ + if(rows !== null && rows.length > 0){ + resolve(rows[0].uuid) + } + resolve(null) + }) + }); +} + +/** + * Log Function for inter process communication + */ +function LogAtMain(msg) { + process.send('{"logmessage" : "' + msg + '"}'); +} + +/** + * + * Wire messages coming from Plugin base.js + * + */ +process.on('message', (msg) => { + if (typeof msg.config != 'undefined') { + config = msg.config; + global.apiAdress = config.apiAdress || null; + serviceState = 1; + LogAtMain('Recieved Configuration from CORE'); + } else if (typeof msg.start != 'undefined') { + if (msg.start === 1) { + startAPI(); + } + } +}); + +// GENERIC Error Handling +process.on('unhandledRejection', (reason, p) => { + console.log('PLUGIN | DIVERA_STATUS | Unhandled Rejection at: Promise', p, 'reason:', reason.stack); + LogAtMain('Unhandled Rejection at: Promise', p, 'reason:', reason.stack); +}); +process.on('uncaughtException', function (err) { + console.log('PLUGIN | DIVERA_STATUS | Caught exception: ' + err); + process.send('{"error" : "' + err.message + '"}'); +}); + +/** + * Start API Logger + */ + +// create a rolling file logger based on date/time that fires process events +global.appRoot = __dirname + '/../../../'; + +global.logger = createWinstonLogger('DIVERA_STATUS ', global.appRoot + '/logs/api/DIVERA_STATUS-%DATE%.log'); +global.logger.info('Started DIVERA_STATUS Sync', {label: 'APPJS '}); + +/** + * connect to database + */ +var coreDb = new Database(global.logger); + +coreDb.openCoreDatabase((successDatabase) => { + if (successDatabase) { + global.coreDb = coreDb.db; + global.logger.info('database started', {label: 'APPJS '}); + } else { + global.logger.error('Could not connect to database', {label: 'APPJS '}); + } +}); + + +async function auth(){ + let resp = await client.get(`${AUTH_ENDPOINT}${config.MONITOR_ID}.html?autologin=${config.AUTOLOGIN_KEY}`) + if(resp.status === 200){ + return true + } + return false +} + +async function pull(){ + let resp = await client.get(`${PULL_ENDPOINT}?ts=0`) + if(resp.status === 200){ + return resp.data + } + return null +} + +async function onStateChange(consumer_id, status_id){ + + var d_consumer = pulled_data['consumer'][consumer_id] + var d_status = pulled_data['status'][status_id] + global.logger.info(`${d_consumer['stdformat_name']}: ${d_status['name']}`) + + let a_consumer = await getMemberByName(d_consumer.firstname, d_consumer.lastname) + if(a_consumer === null){ + global.logger.debug(`basedata nicht gefunden`) + return + } + + let a_status = await getFeedbackStateIDbyName(d_status['name']) + if(a_status !== null){ + // Status ist eine Rückmeldung + let a_operation = await getActiveOperation() + if(a_consumer === null){ + global.logger.debug(`kein aktiver alarm`) + return + } + setFeedback(a_operation, a_consumer, a_status) + }else{ + // Status auf einsatzbereit/nicht einsatzbereit prüfen + if(config.STATUS_EB == d_status['name']){ + //Alle Gruppen alamieren + setGroupsMutedStateForMember(a_consumer,false) + } + if(config.STATUS_NEB == d_status['name']){ + //Alle Gruppen alamieren deaktivieren + setGroupsMutedStateForMember(a_consumer,true) + } + } +} + +async function startAPI() { + let _auth = await auth() + if(_auth){ + global.logger.info('auth OK') + var _pulled_data = await pull() + while(_pulled_data !== null){ + if(_pulled_data !== null){ + if(_pulled_data.success){ + var d = _pulled_data.data + if('cluster' in d){ + pulled_data['consumer'] = d['cluster']['consumer'] + pulled_data['status'] = d['cluster']['status'] + } + if('monitor' in d){ + var m = d['monitor'][config.MONITOR_ID] + Object.entries(m).forEach(([key, value]) => { + if(!('monitor' in pulled_data) || ('monitor' in pulled_data && value.status !== pulled_data['monitor'][key]['status'])){ + onStateChange(value.id,value.status ) + } + }); + pulled_data['monitor'] = d['monitor'][config.MONITOR_ID] + } + } + } + _pulled_data = await pull() + if(_pulled_data === null){ + global.logger.info('_pulled_data === null, reauth') + _auth = await auth() + _pulled_data = await pull() + if(_pulled_data === null){ + global.logger.info('_pulled_data === null after reauth, EXIT!!') + } + } + } + } +} \ No newline at end of file