From 59893f04583e2257aba30d61215d2e1fda4cc406 Mon Sep 17 00:00:00 2001 From: Dollerino Date: Thu, 19 Sep 2024 15:12:44 +0300 Subject: [PATCH 1/4] =?UTF-8?q?=D0=A6=D0=98=D0=A2=D0=9A-901=20(=D0=94?= =?UTF-8?q?=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA=D0=B8=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D1=82=D0=BE=D0=BA=D0=BE=D0=BB=D0=BE=D0=B2=20MQTT=20?= =?UTF-8?q?=D0=B8=20KAFKA)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.js | 30 ++ config_default.js | 30 ++ core/app.js | 12 +- core/in_queue.js | 421 +++++++++++++++- core/kafka_connector.js | 118 +++++ core/mqtt_connector.js | 89 ++++ core/out_queue.js | 72 +-- core/out_queue_processor.js | 121 ++++- core/utils.js | 56 +-- models/obj_service_function.js | 66 ++- package-lock.json | 844 +++++++++++++++++++++++++++++++++ package.json | 2 + 12 files changed, 1724 insertions(+), 137 deletions(-) create mode 100644 core/kafka_connector.js create mode 100644 core/mqtt_connector.js diff --git a/config.js b/config.js index 2ab47f0..fa73f2f 100644 --- a/config.js +++ b/config.js @@ -71,6 +71,34 @@ let inComing = { nPoolIncrement: 0 }; +//Параметры подключения к Kafka +let kafkaConnection = { + //ID клиента-отправителя + sClientIdSender: "Parus", + //ID клиента-получателя + sClientIdRecipient: "Parus", + //Время ожидания успешного подключения (мс) + nConnectionTimeout: 5000, + //Необходимость попытки переподключения при потере соединения + bRestartOnFailure: false, + //Время максимального ожидания между попытками переподключения (мс) + nMaxRetryTime: 20000, + //Время ожидания между попытками переподключения (мс) + nInitialRetryTime: 10000 +}; + +//Параметры подключения по MQTT протоколу +let mqttConnection = { + //ID клиента-отправителя + sClientIdSender: "Parus", + //ID клиента-получателя (если равен sClientIdSender, то отправленные сообщения будут игнорироваться) + sClientIdRecipient: "ParusRecipient", + //Время ожидания успешного подключения (мс) + nConnectTimeout: 5000, + //Время ожидания между попытками переподключения (мс) + nReconnectPeriod: 10000 +}; + //Параметры отправки E-Mail уведомлений let mail = { //Адреc сервера SMTP @@ -98,5 +126,7 @@ module.exports = { dbConnect, outGoing, inComing, + kafkaConnection, + mqttConnection, mail }; diff --git a/config_default.js b/config_default.js index 4d2e174..1d5007f 100644 --- a/config_default.js +++ b/config_default.js @@ -71,6 +71,34 @@ let inComing = { nPoolIncrement: 0 }; +//Параметры подключения к Kafka +let kafkaConnection = { + //ID клиента-отправителя + sClientIdSender: "Parus", + //ID клиента-получателя + sClientIdRecipient: "Parus", + //Время ожидания успешного подключения (мс) + nConnectionTimeout: 5000, + //Необходимость попытки переподключения при потере соединения + bRestartOnFailure: false, + //Время максимального ожидания между попытками переподключения (мс) + nMaxRetryTime: 20000, + //Время ожидания между попытками переподключения (мс) + nInitialRetryTime: 10000 +}; + +//Параметры подключения по MQTT протоколу +let mqttConnection = { + //ID клиента-отправителя + sClientIdSender: "Parus", + //ID клиента-получателя (если равен sClientIdSender, то отправленные сообщения будут игнорироваться) + sClientIdRecipient: "ParusRecipient", + //Время ожидания успешного подключения (мс) + nConnectTimeout: 5000, + //Время ожидания между попытками переподключения (мс) + nReconnectPeriod: 10000 +}; + //Параметры отправки E-Mail уведомлений let mail = { //Адреc сервера SMTP @@ -98,5 +126,7 @@ module.exports = { dbConnect, outGoing, inComing, + kafkaConnection, + mqttConnection, mail }; diff --git a/core/app.js b/core/app.js index 77f3e4e..cf406d5 100644 --- a/core/app.js +++ b/core/app.js @@ -196,9 +196,7 @@ class ParusAppServer { //Если настройки верны - будем стартовать if (!sCheckResult) { //Протоколируем версию и релиз - await this.logger.info( - `Версия сервера приложений: ${prms.config.common.sVersion}, релиз: ${prms.config.common.sRelease}` - ); + await this.logger.info(`Версия сервера приложений: ${prms.config.common.sVersion}, релиз: ${prms.config.common.sRelease}`); //Создаём подключение к БД this.dbConn = new db.DBConnector({ connectSettings: { @@ -219,7 +217,9 @@ class ParusAppServer { dbConn: this.dbConn, logger: this.logger, notifier: this.notifier, - sProxy: prms.config.outGoing.sProxy + sProxy: prms.config.outGoing.sProxy, + kafkaConnectionPrms: prms.config.kafkaConnection, + mqttConnectionPrms: prms.config.mqttConnection }); //Создаём обработчик очереди входящих this.inQ = new iq.InQueue({ @@ -227,7 +227,9 @@ class ParusAppServer { inComing: prms.config.inComing, dbConn: this.dbConn, logger: this.logger, - notifier: this.notifier + notifier: this.notifier, + kafkaConnectionPrms: prms.config.kafkaConnection, + mqttConnectionPrms: prms.config.mqttConnection }); //Создаём контроллер доступности удалённых сервисов this.srvAvlCtrl = new sac.ServiceAvailableController({ diff --git a/core/in_queue.js b/core/in_queue.js index cb2f519..1237e04 100644 --- a/core/in_queue.js +++ b/core/in_queue.js @@ -13,7 +13,16 @@ const express = require("express"); //WEB-сервер Express const cors = require("cors"); //Управление заголовками безопасности для WEB-сервера Express const bodyParser = require("body-parser"); //Модуль для Express (разбор тела входящего запроса) const { ServerError } = require("./server_errors"); //Типовая ошибка -const { makeErrorText, validateObject, buildURL, getAppSrvFunction, buildOptionsXML, parseOptionsXML, deepMerge } = require("./utils"); //Вспомогательные функции +const { + makeErrorText, + validateObject, + buildURL, + getAppSrvFunction, + buildOptionsXML, + parseOptionsXML, + deepMerge, + getKafkaBroker +} = require("./utils"); //Вспомогательные функции const { NINC_EXEC_CNT_YES, NIS_ORIGINAL_NO, NIS_ORIGINAL_YES } = require("../models/prms_db_connector"); //Схемы валидации параметров функций модуля взаимодействия с БД const objInQueueSchema = require("../models/obj_in_queue"); //Схема валидации сообщений обмена с бработчиком очереди входящих сообщений const objServiceSchema = require("../models/obj_service"); //Схемы валидации сервиса @@ -28,6 +37,8 @@ const { SERR_DB_SERVER, SERR_UNAUTH } = require("./constants"); //Общесистемные константы +const { subscribeMQTT } = require("./mqtt_connector"); //Модуль для работы с MQTT +const { subscribeKafka } = require("./kafka_connector"); //Модуль для работы с Kafka //-------------------------- // Глобальные идентификаторы @@ -71,6 +82,13 @@ class InQueue extends EventEmitter { this.webApp.options("*", cors()); //WEB-сервер this.srv = null; + //Параметры подключения к Kafka + this.kafkaConnectionPrms = _.cloneDeep(prms.kafkaConnectionPrms); + //Параметры подключения к MQTT + this.mqttConnectionPrms = _.cloneDeep(prms.mqttConnectionPrms); + //Внешние подключения + this.kafkaConnections = []; + this.mqttConnections = []; } else { throw new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult); } @@ -360,6 +378,326 @@ class InQueue extends EventEmitter { throw new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult); } } + + //Обработка сообщения kafka + async processKafkaMessage({ message, service, fn }) { + //Буфер для сообщения очереди + let q = null; + try { + //Тело сообщения и ответ на него + let blMsg = null; + let blResp = null; + //Параметры сообщения и ответа на него + let options = {}; + let optionsResp = {}; + //Флаг прекращения обработки сообщения + let bStopPropagation = false; + //Получим тело сообщения + blMsg = message.value ? message.value : null; + //Определимся с параметрами сообщения полученными от внешней системы + options = { + method: fn.sFnPrmsType, + headers: _.cloneDeep(message.headers) + }; + //Кладём сообщение в очередь + q = await this.dbConn.putQueue({ + nServiceFnId: fn.nId, + sOptions: buildOptionsXML({ options }), + blMsg + }); + //Скажем что пришло новое входящее сообщение + await this.logger.info( + `Новое входящее Kafka-сообщение для функции ${fn.sCode} (${buildURL({ + sSrvRoot: service.sSrvRoot, + sFnURL: fn.sFnURL + })})`, + { nQueueId: q.nId } + ); + //Вызываем обработчик со стороны БД (если он есть) + if (fn.sPrcResp) { + //Фиксируем начало исполнения сервером БД - в статусе сообщения + q = await this.dbConn.setQueueState({ + nQueueId: q.nId, + nExecState: objQueueSchema.NQUEUE_EXEC_STATE_DB + }); + //Вызов обработчика БД + let prcRes = await this.dbConn.execQueueDBPrc({ nQueueId: q.nId }); + //Если результат - ошибка пробрасываем её + if (prcRes.sResult == objQueueSchema.SPRC_RESP_RESULT_ERR) throw new ServerError(SERR_DB_SERVER, prcRes.sMsg); + //Если результат - ошибка аутентификации, то и её пробрасываем, но с правильным кодом + if (prcRes.sResult == objQueueSchema.SPRC_RESP_RESULT_UNAUTH) throw new ServerError(SERR_UNAUTH, prcRes.sMsg || "Нет аутентификации"); + //Выставим статус сообщению очереди - исполнено обработчиком БД + q = await this.dbConn.setQueueState({ + nQueueId: q.nId, + nExecState: objQueueSchema.NQUEUE_EXEC_STATE_DB_OK + }); + //Считаем ответ полученный от системы + let qData = await this.dbConn.getQueueResp({ nQueueId: q.nId }); + blResp = qData.blResp; + } + //Выполняем обработчик "После" (если он есть) + if (bStopPropagation === false && fn.sAppSrvAfter) { + //Выставим статус сообщению очереди - исполняется сервером приложений + q = await this.dbConn.setQueueState({ + nQueueId: q.nId, + nExecState: objQueueSchema.NQUEUE_EXEC_STATE_APP + }); + //Выполняем + const fnAfter = getAppSrvFunction(fn.sAppSrvAfter); + let resAfter = null; + try { + let resAfterPrms = { res: { body: blMsg }, service: service, function: fn }; + resAfterPrms.queue = _.cloneDeep(q); + resAfterPrms.queue.blMsg = blMsg; + resAfterPrms.queue.blResp = blResp; + resAfterPrms.options = _.cloneDeep(options); + resAfterPrms.optionsResp = _.cloneDeep(optionsResp); + resAfterPrms.dbConn = this.dbConn; + resAfterPrms.notifier = this.notifier; + resAfter = await fnAfter(resAfterPrms); + } catch (e) { + throw new ServerError(SERR_APP_SERVER_AFTER, e.message); + } + //Проверяем структуру ответа функции предобработки + if (resAfter) { + let sCheckResult = validateObject( + resAfter, + objInQueueSchema.InQueueProcessorFnAfter, + "Результат функции постобработки входящего сообщения" + ); + //Если структура ответа в норме + if (!sCheckResult) { + //Выставим статус сообщению очереди - исполнено сервером приложений + q = await this.dbConn.setQueueState({ + nQueueId: q.nId, + nExecState: objQueueSchema.NQUEUE_EXEC_STATE_APP_OK + }); + //Фиксируем результат исполнения "После" - ответ системы + if (!_.isUndefined(resAfter.blResp)) { + blResp = resAfter.blResp; + q = await this.dbConn.setQueueResp({ + nQueueId: q.nId, + blResp, + nIsOriginal: NIS_ORIGINAL_NO + }); + } + //Фиксируем результат исполнения "После" - параметры ответа на запрос + if (!_.isUndefined(resAfter.optionsResp)) { + optionsResp = deepMerge(optionsResp, resAfter.optionsResp); + let sOptionsResp = buildOptionsXML({ options: optionsResp }); + q = await this.dbConn.setQueueOptionsResp({ nQueueId: q.nId, sOptionsResp }); + } + //Если пришел флаг ошибочной аутентификации и он положительный - то это ошибка, дальше ничего не делаем + if (!_.isUndefined(resAfter.bUnAuth)) if (resAfter.bUnAuth === true) throw new ServerError(SERR_UNAUTH, "Нет аутентификации"); + } else { + //Или расскажем об ошибке + throw new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult); + } + } + } + //Фиксируем успех обработки - в протоколе работы сервиса + await this.logger.info(`Входящее сообщение ${q.nId} успешно отработано`, { nQueueId: q.nId }); + //Фиксируем успех обработки - в статусе сообщения + q = await this.dbConn.setQueueState({ + nQueueId: q.nId, + nIncExecCnt: NINC_EXEC_CNT_YES, + nExecState: objQueueSchema.NQUEUE_EXEC_STATE_OK + }); + } catch (e) { + //Тема и текст уведомления об ошибке + let sSubject = `Ошибка обработки входящего сообщения сервером приложений для функции "${fn.sCode}" сервиса "${service.sCode}"`; + let sMessage = makeErrorText(e); + //Если сообщение очереди успели создать + if (q) { + //Фиксируем ошибку обработки сервером приложений - в статусе сообщения + q = await this.dbConn.setQueueState({ + nQueueId: q.nId, + sExecMsg: sMessage, + nIncExecCnt: NINC_EXEC_CNT_YES, + nExecState: objQueueSchema.NQUEUE_EXEC_STATE_ERR + }); + //Фиксируем ошибку обработки сервером приложений - в протоколе работы сервиса + await this.logger.error(`Ошибка обработки входящего сообщения ${q.nId} сервером приложений: ${sMessage}`, { nQueueId: q.nId }); + //Добавим чуть больше информации в тему сообщения + sSubject = `Ошибка обработки входящего сообщения ${q.nId} сервером приложений для функции "${fn.sCode}" сервиса "${service.sCode}"`; + } else { + //Ограничимся общей ошибкой + await this.logger.error(sMessage, { + nServiceId: service.nId, + nServiceFnId: fn.nId + }); + } + //Если для функции-обработчика указан признак необходимости оповещения об ошибках + if (fn.nErrNtfSign == objServiceFnSchema.NERR_NTF_SIGN_YES) { + //Отправим уведомление об ошибке отработки в почту + await this.notifier.addMessage({ + sTo: fn.sErrNtfMail, + sSubject, + sMessage + }); + } + } + } + + //Обработка сообщения + async processMQTTMessage({ message, service, fn }) { + //Буфер для сообщения очереди + let q = null; + try { + //Тело сообщения и ответ на него + let blMsg = null; + let blResp = null; + //Параметры сообщения и ответа на него + let options = {}; + let optionsResp = {}; + //Получим тело сообщения + blMsg = message ? message : null; + //Определимся с параметрами сообщения полученными от внешней системы + options = { + method: fn.sFnPrmsType + }; + console.log("Кладём в очередь"); + //Кладём сообщение в очередь + q = await this.dbConn.putQueue({ + nServiceFnId: fn.nId, + sOptions: buildOptionsXML({ options }), + blMsg + }); + console.log("Продолжаем"); + //Скажем что пришло новое входящее сообщение + await this.logger.info( + `Новое входящее MQTT-сообщение для функции ${fn.sCode} (${buildURL({ + sSrvRoot: service.sSrvRoot, + sFnURL: fn.sFnURL + })})`, + { nQueueId: q.nId } + ); + //Вызываем обработчик со стороны БД (если он есть) + if (fn.sPrcResp) { + //Фиксируем начало исполнения сервером БД - в статусе сообщения + q = await this.dbConn.setQueueState({ + nQueueId: q.nId, + nExecState: objQueueSchema.NQUEUE_EXEC_STATE_DB + }); + //Вызов обработчика БД + let prcRes = await this.dbConn.execQueueDBPrc({ nQueueId: q.nId }); + //Если результат - ошибка пробрасываем её + if (prcRes.sResult == objQueueSchema.SPRC_RESP_RESULT_ERR) throw new ServerError(SERR_DB_SERVER, prcRes.sMsg); + //Если результат - ошибка аутентификации, то и её пробрасываем, но с правильным кодом + if (prcRes.sResult == objQueueSchema.SPRC_RESP_RESULT_UNAUTH) throw new ServerError(SERR_UNAUTH, prcRes.sMsg || "Нет аутентификации"); + //Выставим статус сообщению очереди - исполнено обработчиком БД + q = await this.dbConn.setQueueState({ + nQueueId: q.nId, + nExecState: objQueueSchema.NQUEUE_EXEC_STATE_DB_OK + }); + //Считаем ответ полученный от системы + let qData = await this.dbConn.getQueueResp({ nQueueId: q.nId }); + blResp = qData.blResp; + } + //Выполняем обработчик "После" (если он есть) + if (fn.sAppSrvAfter) { + //Выставим статус сообщению очереди - исполняется сервером приложений + q = await this.dbConn.setQueueState({ + nQueueId: q.nId, + nExecState: objQueueSchema.NQUEUE_EXEC_STATE_APP + }); + //Выполняем + const fnAfter = getAppSrvFunction(fn.sAppSrvAfter); + let resAfter = null; + try { + let resAfterPrms = { res: { body: message }, service: service, function: fn }; + resAfterPrms.queue = _.cloneDeep(q); + resAfterPrms.queue.blMsg = blMsg; + resAfterPrms.queue.blResp = blResp; + resAfterPrms.options = _.cloneDeep(options); + resAfterPrms.optionsResp = _.cloneDeep(optionsResp); + resAfterPrms.dbConn = this.dbConn; + resAfterPrms.notifier = this.notifier; + resAfter = await fnAfter(resAfterPrms); + } catch (e) { + throw new ServerError(SERR_APP_SERVER_AFTER, e.message); + } + //Проверяем структуру ответа функции предобработки + if (resAfter) { + let sCheckResult = validateObject( + resAfter, + objInQueueSchema.InQueueProcessorFnAfter, + "Результат функции постобработки входящего сообщения" + ); + //Если структура ответа в норме + if (!sCheckResult) { + //Выставим статус сообщению очереди - исполнено сервером приложений + q = await this.dbConn.setQueueState({ + nQueueId: q.nId, + nExecState: objQueueSchema.NQUEUE_EXEC_STATE_APP_OK + }); + //Фиксируем результат исполнения "После" - ответ системы + if (!_.isUndefined(resAfter.blResp)) { + blResp = resAfter.blResp; + q = await this.dbConn.setQueueResp({ + nQueueId: q.nId, + blResp, + nIsOriginal: NIS_ORIGINAL_NO + }); + } + //Фиксируем результат исполнения "После" - параметры ответа на запрос + if (!_.isUndefined(resAfter.optionsResp)) { + optionsResp = deepMerge(optionsResp, resAfter.optionsResp); + let sOptionsResp = buildOptionsXML({ options: optionsResp }); + q = await this.dbConn.setQueueOptionsResp({ nQueueId: q.nId, sOptionsResp }); + } + //Если пришел флаг ошибочной аутентификации и он положительный - то это ошибка, дальше ничего не делаем + if (!_.isUndefined(resAfter.bUnAuth)) if (resAfter.bUnAuth === true) throw new ServerError(SERR_UNAUTH, "Нет аутентификации"); + } else { + //Или расскажем об ошибке + throw new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult); + } + } + } + //Фиксируем успех обработки - в протоколе работы сервиса + await this.logger.info(`Входящее сообщение ${q.nId} успешно отработано`, { nQueueId: q.nId }); + //Фиксируем успех обработки - в статусе сообщения + q = await this.dbConn.setQueueState({ + nQueueId: q.nId, + nIncExecCnt: NINC_EXEC_CNT_YES, + nExecState: objQueueSchema.NQUEUE_EXEC_STATE_OK + }); + } catch (e) { + //Тема и текст уведомления об ошибке + let sSubject = `Ошибка обработки входящего сообщения сервером приложений для функции "${fn.sCode}" сервиса "${service.sCode}"`; + let sMessage = makeErrorText(e); + //Если сообщение очереди успели создать + if (q) { + //Фиксируем ошибку обработки сервером приложений - в статусе сообщения + q = await this.dbConn.setQueueState({ + nQueueId: q.nId, + sExecMsg: sMessage, + nIncExecCnt: NINC_EXEC_CNT_YES, + nExecState: objQueueSchema.NQUEUE_EXEC_STATE_ERR + }); + //Фиксируем ошибку обработки сервером приложений - в протоколе работы сервиса + await this.logger.error(`Ошибка обработки входящего сообщения ${q.nId} сервером приложений: ${sMessage}`, { nQueueId: q.nId }); + //Добавим чуть больше информации в тему сообщения + sSubject = `Ошибка обработки входящего сообщения ${q.nId} сервером приложений для функции "${fn.sCode}" сервиса "${service.sCode}"`; + } else { + //Ограничимся общей ошибкой + await this.logger.error(sMessage, { + nServiceId: service.nId, + nServiceFnId: fn.nId + }); + } + //Если для функции-обработчика указан признак необходимости оповещения об ошибках + if (fn.nErrNtfSign == objServiceFnSchema.NERR_NTF_SIGN_YES) { + //Отправим уведомление об ошибке отработки в почту + await this.notifier.addMessage({ + sTo: fn.sErrNtfMail, + sSubject, + sMessage + }); + } + } + } + //Запуск обработки очереди входящих сообщений startProcessing(prms) { //Проверяем структуру переданного объекта для старта @@ -428,6 +766,58 @@ class InQueue extends EventEmitter { } ); }); + //Считываем прием сообщений по Kafka + let kafkaSrvs = _.filter(this.services, srv => { + return srv.nSrvType === objServiceSchema.NSRV_TYPE_RECIVE && srv.sSrvRoot.startsWith("kafka://"); + }); + //Если есть сервисы с приемом сообщений по Kafka + if (kafkaSrvs.length !== 0) { + //Обходим данные сервисы + _.forEach(kafkaSrvs, async srvs => { + //Если у сервиса обмена есть функции + if (srvs.functions.length !== 0) { + //Подключаемся и подписываемся на соответствующий брокер + let connectionKafka = await subscribeKafka({ + connectionPrms: this.kafkaConnectionPrms, + service: srvs, + processKafkaMessage: prms => this.processKafkaMessage(prms), + logger: this.logger + }); + //Если подключение было создано + if (connectionKafka) { + //Добавляем в общий список подключений kafka + this.kafkaConnections.push(connectionKafka); + } + } + }); + } + //Считываем прием сообщений по MQTT + let mqttSrvs = _.filter(this.services, srv => { + return ( + srv.nSrvType === objServiceSchema.NSRV_TYPE_RECIVE && (srv.sSrvRoot.startsWith("mqtt://") || srv.sSrvRoot.startsWith("mqtts://")) + ); + }); + //Если есть сервисы с приемом сообщений по MQTT + if (mqttSrvs.length !== 0) { + //Обходим данные сервисы + _.forEach(mqttSrvs, async srvs => { + //Если у сервиса обмена есть функции + if (srvs.functions.length !== 0) { + //Подключаемся и подписываемся на соответствующий брокер + let connectionMQTT = await subscribeMQTT({ + connectionPrms: this.mqttConnectionPrms, + service: srvs, + processMQTTMessage: prms => this.processMQTTMessage(prms), + logger: this.logger + }); + //Если подключение было создано + if (connectionMQTT) { + //Добавляем в общий список подключений kafka + this.mqttConnections.push(connectionMQTT); + } + } + }); + } //Запросы на адреса, не входящие в состав объявленных сервисов - 404 NOT FOUND this.webApp.use("*", (req, res) => { res.status(404).send( @@ -453,10 +843,39 @@ class InQueue extends EventEmitter { throw new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult); } } + + //Закрытие подключений, если они есть + stopConnections() { + //Если у нас есть соединения с MQTT + if (this.mqttConnections.length !== 0) { + //Закрываем их + _.forEach(this.mqttConnections, async connection => { + try { + await connection.end(); + } catch (e) { + await this.logger.error(`Ошибка завершения MQTT подключения: ${makeErrorText(e)}`); + } + }); + } + //Если у нас есть соединения с Kafka + if (this.kafkaConnections.length !== 0) { + //Закрываем их + _.forEach(this.kafkaConnections, async connection => { + try { + await connection.disconnect(); + } catch (e) { + await this.logger.error(`Ошибка завершения Kafka подключения: ${makeErrorText(e)}`); + } + }); + } + } + //Остановка обработки очереди исходящих сообщений stopProcessing() { //Выставляем флаг неработы this.bWorking = false; + //Закрываем подключения, если они есть + this.stopConnections(); //Останавливаем WEB-сервер (если создавался) if (this.srv) { this.srv.close(() => { diff --git a/core/kafka_connector.js b/core/kafka_connector.js new file mode 100644 index 0000000..5ee2841 --- /dev/null +++ b/core/kafka_connector.js @@ -0,0 +1,118 @@ +/* + Сервис интеграции ПП Парус 8 с WEB API + Модуль ядра: обработчик kafka сообщений +*/ + +//---------------------- +// Подключение библиотек +//---------------------- + +const _ = require("lodash"); //Работа с массивами и коллекциями +const { makeErrorText, getKafkaBroker, getKafkaAuth } = require("./utils"); //Вспомогательные функции +const { Kafka, logLevel } = require("kafkajs"); //Работа с Kafka + +//------------ +// Тело модуля +//------------ + +//Отправка сообщения Kafka +const publishKafka = async ({ connectionPrms, url, auth, topic, message }) => { + //Иницализируем подключение к Kafka + let kafka = new Kafka({ + clientId: connectionPrms.sClientIdSender, + brokers: [url], + connectionTimeout: connectionPrms.nConnectionTimeout, + logLevel: logLevel.NOTHING, + ...auth + }); + //Инициализируем продюсера + let producer = kafka.producer(); + //Подключаемся к Kafka + await producer.connect(); + //Отправляем сообщение + let res = await producer.send({ topic: topic, messages: [{ value: message }] }); + //Отключаемся + await producer.disconnect(); + //Возвращаем ответ + return res; +}; + +//Получение MQTT сообщений +const subscribeKafka = async ({ connectionPrms, service, processKafkaMessage, logger }) => { + try { + //Признак необходимости вывода сообщения о потере соединения + let bLogLostConnection = true; + //Получаем брокера по URL сервиса + let sBroker = getKafkaBroker(service.sSrvRoot); + //Иницализируем подключение к Kafka + let client = new Kafka({ + clientId: connectionPrms.sClientIdRecipient, + brokers: [sBroker], + connectionTimeout: connectionPrms.nConnectionTimeout, + ...getKafkaAuth(service.sSrvUser, service.sSrvPass), + logLevel: logLevel.NOTHING, + retry: { + retries: 0, + maxRetryTime: connectionPrms.nMaxRetryTime, + initialRetryTime: connectionPrms.nInitialRetryTime, + restartOnFailure: error => { + return new Promise(resolve => { + //Если требуется вывести ошибку + if (bLogLostConnection) { + //Выводим ошибку + logger.error(`Соединение с Kafka потеряно (${sBroker}): ${makeErrorText(error)}`); + //Сбрасываем признак необходимости вывода ошибки + bLogLostConnection = false; + } + resolve(connectionPrms.bRestartOnFailure); + }); + } + } + }); + + //Инициализируем получателя + let consumer = client.consumer({ groupId: "ParusWebApi" }); + + //Устанавливаем прослушивание + await consumer.connect(); + consumer.subscribe({ topics: _.map(service.functions, "sFnURL") }); + + //Запускаем прослушивание необходимых топиков + consumer.run({ + eachMessage: async ({ topic, message }) => { + try { + //Вызываем обработчик + processKafkaMessage({ + message, + service, + fn: _.find(service.functions, { sFnURL: topic }) + }); + } catch (e) { + await logger.error(`Ошибка обработки исходящего сообщения Kafka: ${makeErrorText(e)}`); + } + } + }); + + //Отслеживаем соединение + consumer.on(consumer.events.CONNECT, () => { + //Если сообщение о потере соединения уже выводилось + if (!bLogLostConnection) { + //Сообщим о восстановлении соединения + logger.info(`Соединение с Kafka восстановлено (${sBroker})`); + //Устанавливаем признак сообщения о потере соединения + bLogLostConnection = true; + } + }); + //Возвращаем соединение + return consumer; + } catch (e) { + await logger.error(`Ошибка запуска обработчика очереди исходящих сообщений Kafka: ${makeErrorText(e)}`); + } +}; + +//----------------- +// Интерфейс модуля +//----------------- + +exports.publishKafka = publishKafka; +exports.subscribeKafka = subscribeKafka; diff --git a/core/mqtt_connector.js b/core/mqtt_connector.js new file mode 100644 index 0000000..48fcbc6 --- /dev/null +++ b/core/mqtt_connector.js @@ -0,0 +1,89 @@ +/* + Сервис интеграции ПП Парус 8 с WEB API + Модуль ядра: обработчик mqtt сообщений +*/ + +//---------------------- +// Подключение библиотек +//---------------------- + +const _ = require("lodash"); //Работа с массивами и коллекциями +const { makeErrorText } = require("./utils"); //Вспомогательные функции +const mqtt = require("mqtt"); //Работа с MQTT + +//------------ +// Тело модуля +//------------ + +//Отправка MQTT сообщения +const publishMQTT = async ({ connectionPrms, url, auth, topic, message }) => { + //Инициализируем подключение + const client = await mqtt.connectAsync(url, { + clientId: connectionPrms.sClientIdSender, + clean: true, + connectTimeout: connectionPrms.nConnectTimeout, + username: auth.user, + password: auth.pass, + reconnectPeriod: connectionPrms.nReconnectPeriod + }); + //Отправляем сообщение + await client.publishAsync(topic, message); + //Закрываем подключение + await client.endAsync(); + //Возвращаем сообщение, которое было отправлено + return { statusCode: 200 }; +}; + +//Получение MQTT сообщений +const subscribeMQTT = async ({ connectionPrms, service, processMQTTMessage, logger }) => { + try { + //Инициализируем строку подключения + let sBroker = service.sSrvRoot; + //Инициализируем подключение + const client = await mqtt.connectAsync(sBroker, { + clientId: connectionPrms.sClientIdRecipient, + clean: true, + connectTimeout: connectionPrms.nConnectTimeout, + username: service.sSrvUser, + password: service.sSrvPass, + reconnectPeriod: connectionPrms.nReconnectPeriod + }); + + //Обходим функции сервиса + _.forEach( + _.filter(service.functions, fn => !fn.sFnURL.startsWith("@")), + fn => { + client.subscribe(fn.sFnURL); + } + ); + + //Прослушиваем сообщения + client.on("message", (topic, message) => { + console.log("message"); + //Обрабатываем сообщение + processMQTTMessage({ message, service, fn: _.find(service.functions, { sFnURL: topic }) }); + }); + //Прослушиваем отключение от сервера + client.on("offline", () => { + //Выводим ошибку + logger.error(`Соединение с MQTT потеряно (${sBroker})`); + }); + //Прослушиваем восстановление соединения + client.on("reconnect", () => { + //Сообщим о восстановлении соединения + logger.info(`Соединение с MQTT восстановлено (${sBroker})`); + }); + + //Возвращаем подключение + return client; + } catch (e) { + logger.error(`Ошибка запуска обработчика очереди исходящих сообщений MQTT: ${makeErrorText(e)}`); + } +}; + +//----------------- +// Интерфейс модуля +//----------------- + +exports.publishMQTT = publishMQTT; +exports.subscribeMQTT = subscribeMQTT; diff --git a/core/out_queue.js b/core/out_queue.js index 1c60d8b..c15acf7 100644 --- a/core/out_queue.js +++ b/core/out_queue.js @@ -70,6 +70,10 @@ class OutQueue extends EventEmitter { this.inProgress = []; //Привяжем методы к указателю на себя для использования в обработчиках событий this.outDetectingLoop = this.outDetectingLoop.bind(this); + //Параметры подключения к Kafka + this.kafkaConnectionPrms = _.cloneDeep(prms.kafkaConnectionPrms); + //Параметры подключения к MQTT + this.mqttConnectionPrms = _.cloneDeep(prms.mqttConnectionPrms); } else { throw new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult); } @@ -141,11 +145,7 @@ class OutQueue extends EventEmitter { //Старт обработчика startQueueProcessor(prms) { //Проверяем структуру переданного объекта для старта обработчика - let sCheckResult = validateObject( - prms, - prmsOutQueueSchema.startQueueProcessor, - "Параметры функции запуска обработчика сообщения очереди" - ); + let sCheckResult = validateObject(prms, prmsOutQueueSchema.startQueueProcessor, "Параметры функции запуска обработчика сообщения очереди"); //Если структура объекта в норме if (!sCheckResult) { //Добавляем идентификатор позиции очереди в список обрабатываемых @@ -163,7 +163,9 @@ class OutQueue extends EventEmitter { function: _.find(_.find(this.services, { nId: prms.queue.nServiceId }).functions, { nId: prms.queue.nServiceFnId }), - sProxy: this.sProxy + sProxy: this.sProxy, + kafkaConnectionPrms: this.kafkaConnectionPrms, + mqttConnectionPrms: this.mqttConnectionPrms }); //Уменьшаем количество доступных обработчиков this.nWorkersLeft--; @@ -174,11 +176,7 @@ class OutQueue extends EventEmitter { //Останов обработчика stopQueueProcessor(prms) { //Проверяем структуру переданного объекта для останова обработчика - let sCheckResult = validateObject( - prms, - prmsOutQueueSchema.stopQueueProcessor, - "Параметры функции останова обработчика сообщения очереди" - ); + let sCheckResult = validateObject(prms, prmsOutQueueSchema.stopQueueProcessor, "Параметры функции останова обработчика сообщения очереди"); //Если структура объекта в норме if (!sCheckResult) { //Удаляем идентификатор позиции очереди из списка обрабатываемых @@ -219,19 +217,13 @@ class OutQueue extends EventEmitter { throw new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult); } } catch (e) { - await this.logger.error( - `При отправке уведомления об ошибке обработки исходящего сообщения: ${makeErrorText(e)}` - ); + await this.logger.error(`При отправке уведомления об ошибке обработки исходящего сообщения: ${makeErrorText(e)}`); } } //Запуск обработки очередного сообщения processMessage(prms) { //Проверяем структуру переданного объекта - let sCheckResult = validateObject( - prms, - prmsOutQueueSchema.processMessage, - "Параметры функции запуска обработки очередного сообщения" - ); + let sCheckResult = validateObject(prms, prmsOutQueueSchema.processMessage, "Параметры функции запуска обработки очередного сообщения"); //Если структура объекта в норме if (!sCheckResult) { //Проверим, что есть доступные обработчики @@ -276,10 +268,9 @@ class OutQueue extends EventEmitter { }); } catch (e) { //Отразим в протоколе ошибку постановки задачи на аутентификацию сервиса - await self.logger.error( - `Ошибка постановки задачи на аутентификацию сервиса: ${makeErrorText(e)}`, - { nQueueId: prms.queue.nId } - ); + await self.logger.error(`Ошибка постановки задачи на аутентификацию сервиса: ${makeErrorText(e)}`, { + nQueueId: prms.queue.nId + }); } } } @@ -298,25 +289,21 @@ class OutQueue extends EventEmitter { sExecMsg: sError, nIncExecCnt: nQueueOldExecCnt == prms.queue.nExecCnt ? NINC_EXEC_CNT_YES : NINC_EXEC_CNT_NO, nExecState: - (nQueueOldExecCnt == prms.queue.nExecCnt - ? prms.queue.nExecCnt + 1 - : prms.queue.nExecCnt) < prms.queue.nRetryAttempts + (nQueueOldExecCnt == prms.queue.nExecCnt ? prms.queue.nExecCnt + 1 : prms.queue.nExecCnt) < prms.queue.nRetryAttempts ? prms.queue.nExecState : objQueueSchema.NQUEUE_EXEC_STATE_ERR }); } //Если исполнение завершилось полностью и с ошибкой - расскажем об этом - if (prms.queue.nExecState == objQueueSchema.NQUEUE_EXEC_STATE_ERR) - await this.notifyMessageProcessError(prms); + if (prms.queue.nExecState == objQueueSchema.NQUEUE_EXEC_STATE_ERR) await this.notifyMessageProcessError(prms); //Останавливаем обработчик и инкрементируем флаг их доступного количества try { this.stopQueueProcessor({ nQueueId: prms.queue.nId, proc }); } catch (e) { //Отразим в протоколе ошибку останова - await self.logger.error( - `Ошибка останова обработчика исходящего сообщения: ${makeErrorText(e)}`, - { nQueueId: prms.queue.nId } - ); + await self.logger.error(`Ошибка останова обработчика исходящего сообщения: ${makeErrorText(e)}`, { + nQueueId: prms.queue.nId + }); } }); //Перехват ошибок обработчика @@ -333,23 +320,20 @@ class OutQueue extends EventEmitter { sExecMsg: makeErrorText(e), nIncExecCnt: nQueueOldExecCnt == prms.queue.nExecCnt ? NINC_EXEC_CNT_YES : NINC_EXEC_CNT_NO, nExecState: - (nQueueOldExecCnt == prms.queue.nExecCnt ? prms.queue.nExecCnt + 1 : prms.queue.nExecCnt) < - prms.queue.nRetryAttempts + (nQueueOldExecCnt == prms.queue.nExecCnt ? prms.queue.nExecCnt + 1 : prms.queue.nExecCnt) < prms.queue.nRetryAttempts ? prms.queue.nExecState : objQueueSchema.NQUEUE_EXEC_STATE_ERR }); //Если исполнение завершилось полностью и с ошибкой - расскажем об этом - if (prms.queue.nExecState == objQueueSchema.NQUEUE_EXEC_STATE_ERR) - await this.notifyMessageProcessError(prms); + if (prms.queue.nExecState == objQueueSchema.NQUEUE_EXEC_STATE_ERR) await this.notifyMessageProcessError(prms); //Останавливаем обработчик и инкрементируем флаг их доступного количества try { this.stopQueueProcessor({ nQueueId: prms.queue.nId, proc }); } catch (e) { //Отразим в протоколе ошибку останова - await self.logger.error( - `Ошибка останова обработчика исходящего сообщения: ${makeErrorText(e)}`, - { nQueueId: prms.queue.nId } - ); + await self.logger.error(`Ошибка останова обработчика исходящего сообщения: ${makeErrorText(e)}`, { + nQueueId: prms.queue.nId + }); } }); //Перехват останова обработчика @@ -392,16 +376,12 @@ class OutQueue extends EventEmitter { nQueueId: outMsg.nId, sExecMsg: makeErrorText(e), nIncExecCnt: NINC_EXEC_CNT_YES, - nExecState: - outMsg.nExecCnt + 1 < outMsg.nRetryAttempts - ? outMsg.nExecState - : objQueueSchema.NQUEUE_EXEC_STATE_ERR + nExecState: outMsg.nExecCnt + 1 < outMsg.nRetryAttempts ? outMsg.nExecState : objQueueSchema.NQUEUE_EXEC_STATE_ERR }); //Фиксируем ошибку обработки сервером приложений - запись в протокол работы сервера приложений await this.logger.error(makeErrorText(e), { nQueueId: outMsg.nId }); //Если исполнение завершилось полностью и с ошибкой - расскажем об этом - if (queue.nExecState == objQueueSchema.NQUEUE_EXEC_STATE_ERR) - await this.notifyMessageProcessError({ queue }); + if (queue.nExecState == objQueueSchema.NQUEUE_EXEC_STATE_ERR) await this.notifyMessageProcessError({ queue }); } } } diff --git a/core/out_queue_processor.js b/core/out_queue_processor.js index 05c09dc..867ac5f 100644 --- a/core/out_queue_processor.js +++ b/core/out_queue_processor.js @@ -12,7 +12,18 @@ const _ = require("lodash"); //Работа с массивами и объек const rqp = require("request-promise"); //Работа с HTTP/HTTPS запросами const lg = require("./logger"); //Протоколирование работы const db = require("./db_connector"); //Взаимодействие с БД -const { makeErrorText, validateObject, getAppSrvFunction, buildURL, parseOptionsXML, buildOptionsXML, deepMerge } = require("./utils"); //Вспомогательные функции +const { + makeErrorText, + validateObject, + getAppSrvFunction, + buildURL, + parseOptionsXML, + buildOptionsXML, + deepMerge, + getKafkaBroker, + getKafkaAuth, + getURLProtocol +} = require("./utils"); //Вспомогательные функции const { ServerError } = require("./server_errors"); //Типовая ошибка const objOutQueueProcessorSchema = require("../models/obj_out_queue_processor"); //Схема валидации сообщений обмена с бработчиком очереди исходящих сообщений const prmsOutQueueProcessorSchema = require("../models/prms_out_queue_processor"); //Схема валидации параметров функций модуля @@ -28,6 +39,8 @@ const { SERR_UNAUTH } = require("./constants"); //Глобальные константы const { NINC_EXEC_CNT_YES, NINC_EXEC_CNT_NO, NIS_ORIGINAL_NO, NIS_ORIGINAL_YES } = require("../models/prms_db_connector"); //Схемы валидации параметров функций модуля взаимодействия с БД +const { publishMQTT } = require("./mqtt_connector"); //Работа с MQTT/MQTTS запросами +const { publishKafka } = require("./kafka_connector"); //Работа с Kafka запросами //-------------------------- // Глобальные идентификаторы @@ -129,21 +142,53 @@ const appProcess = async prms => { let optionsResp = {}; //Флаг прекращения обработки сообщения let bStopPropagation = false; - //Определимся с URL и телом сообщения в зависимости от способа передачи параметров (для POST, PATCH и PUT - данные в теле, для остальных - в URI) - if ( - [objServiceFnSchema.NFN_PRMS_TYPE_POST, objServiceFnSchema.NFN_PRMS_TYPE_PATCH, objServiceFnSchema.NFN_PRMS_TYPE_PUT].includes( - prms.function.nFnPrmsType - ) - ) { - options.url = buildURL({ sSrvRoot: prms.service.sSrvRoot, sFnURL: prms.function.sFnURL }); - options.body = prms.queue.blMsg; - options.headers = { "content-type": "application/octet-stream" }; - } else { - options.url = buildURL({ - sSrvRoot: prms.service.sSrvRoot, - sFnURL: prms.function.sFnURL, - sQuery: prms.queue.blMsg === null ? "" : prms.queue.blMsg.toString() - }); + //Флаг выполнения обработчика "До" + let bExecuteBefore = true; + //Флаг выполнения обработчика "После" + let bExecuteAfter = true; + //Считываем протокол работы + let sProtocol = getURLProtocol(prms.service.sSrvRoot); + //Исходя из протокола собираем параметры + switch (true) { + //Kafka + case sProtocol === objServiceFnSchema.SPROTOCOL_KAFKA: + options.url = getKafkaBroker(prms.service.sSrvRoot); + options.body = prms.queue.blMsg; + options.topic = prms.function.sFnURL; + options.auth = getKafkaAuth(prms.service.sSrvUser, prms.service.sSrvPass); + //Указываем, что выполнение обработчика "После" невозможно + bExecuteAfter = false; + break; + //mqtt и mqtts + case [objServiceFnSchema.SPROTOCOL_MQTT, objServiceFnSchema.SPROTOCOL_MQTTS].includes(sProtocol): + options.url = prms.service.sSrvRoot; + options.body = prms.queue.blMsg; + options.topic = prms.function.sFnURL; + options.auth = { user: prms.service.sSrvUser, pass: prms.service.sSrvPass }; + //Указываем, что выполнение обработчика "После" невозможно + bExecuteAfter = false; + break; + //Другие + default: + //Определимся с URL и телом сообщения в зависимости от способа передачи параметров (для POST, PATCH и PUT - данные в теле, для остальных - в URI) + if ( + [ + objServiceFnSchema.NFN_PRMS_TYPE_POST, + objServiceFnSchema.NFN_PRMS_TYPE_PATCH, + objServiceFnSchema.NFN_PRMS_TYPE_PUT + ].includes(prms.function.nFnPrmsType) + ) { + options.url = buildURL({ sSrvRoot: prms.service.sSrvRoot, sFnURL: prms.function.sFnURL }); + options.body = prms.queue.blMsg; + options.headers = { "content-type": "application/octet-stream" }; + } else { + options.url = buildURL({ + sSrvRoot: prms.service.sSrvRoot, + sFnURL: prms.function.sFnURL, + sQuery: prms.queue.blMsg === null ? "" : prms.queue.blMsg.toString() + }); + } + break; } // Если у сервиса указан прокси, либо у приложения установлен глобальный прокси if (prms.service.sProxyURL || prms.sProxy) { @@ -167,7 +212,7 @@ const appProcess = async prms => { } } //Выполняем обработчик "До" (если он есть) - if (prms.function.sAppSrvBefore) { + if (prms.function.sAppSrvBefore && bExecuteBefore) { const fnBefore = getAppSrvFunction(prms.function.sAppSrvBefore); let resBefore = null; try { @@ -199,7 +244,12 @@ const appProcess = async prms => { objServiceFnSchema.NFN_PRMS_TYPE_POST, objServiceFnSchema.NFN_PRMS_TYPE_PATCH, objServiceFnSchema.NFN_PRMS_TYPE_PUT - ].includes(prms.function.nFnPrmsType) + ].includes(prms.function.nFnPrmsType) || + [ + objServiceFnSchema.SPROTOCOL_KAFKA, + objServiceFnSchema.SPROTOCOL_MQTT, + objServiceFnSchema.SPROTOCOL_MQTTS + ].includes(sProtocol) ) { options.body = prms.queue.blMsg; } else { @@ -263,7 +313,34 @@ const appProcess = async prms => { } //Ждем ответ от удалённого сервера options.resolveWithFullResponse = true; - let serverResp = await rqp(options); + let serverResp = null; + //Выполняем отправку исходя из протокола + switch (true) { + //Kafka + case sProtocol === objServiceFnSchema.SPROTOCOL_KAFKA: + serverResp = await publishKafka({ + connectionPrms: prms.kafkaConnectionPrms, + url: options.url, + auth: options.auth, + topic: options.topic, + message: options.body + }); + break; + //mqtt и mqtts + case [objServiceFnSchema.SPROTOCOL_MQTT, objServiceFnSchema.SPROTOCOL_MQTTS].includes(sProtocol): + serverResp = await publishMQTT({ + connectionPrms: prms.mqttConnectionPrms, + url: options.url, + auth: options.auth, + topic: options.topic, + message: options.body + }); + break; + //Другие + default: + serverResp = await rqp(options); + break; + } //Сохраняем полученный ответ prms.queue.blResp = Buffer.from(serverResp.body || ""); await dbConn.setQueueResp({ @@ -293,7 +370,7 @@ const appProcess = async prms => { throw new ServerError(SERR_WEB_SERVER, sError); } //Выполняем обработчик "После" (если он есть) - if (prms.function.sAppSrvAfter) { + if (prms.function.sAppSrvAfter && bExecuteAfter) { const fnAfter = getAppSrvFunction(prms.function.sAppSrvAfter); let resAfter = null; try { @@ -515,7 +592,9 @@ const processTask = async prms => { queue: q, service: prms.task.service, function: prms.task.function, - sProxy: prms.task.sProxy + sProxy: prms.task.sProxy, + kafkaConnectionPrms: prms.task.kafkaConnectionPrms, + mqttConnectionPrms: prms.task.mqttConnectionPrms }); //Если результат обработки ошибка - пробрасываем её дальше if (res instanceof ServerError) { diff --git a/core/utils.js b/core/utils.js index 13d309a..011023f 100644 --- a/core/utils.js +++ b/core/utils.js @@ -45,9 +45,7 @@ const validateObject = (obj, schema, sObjName) => { let a = errors.map(e => { return e.message; }); - sRes = `Объект${sObjName ? ` "${sObjName}" ` : " "}имеет некорректный формат: ${_.uniq(a).join( - "; " - )}`; + sRes = `Объект${sObjName ? ` "${sObjName}" ` : " "}имеет некорректный формат: ${_.uniq(a).join("; ")}`; } } else { //Валидатор вернул не то, что мы ожидали @@ -142,8 +140,7 @@ const getAppSrvFunction = sAppSrv => { //Объявим формат (для сообщений об ошибках) const sFormat = "(ожидаемый формат: <МОДУЛЬ>/<ФУНКЦИЯ>)"; //Проверим, что есть что разбирать - if (!sAppSrv) - throw new ServerError(SERR_MODULES_NO_MODULE_SPECIFIED, `Не указаны модуль и функция обработчика ${sFormat}`); + if (!sAppSrv) throw new ServerError(SERR_MODULES_NO_MODULE_SPECIFIED, `Не указаны модуль и функция обработчика ${sFormat}`); //Разбираем try { //Разбираем на модуль и функцию @@ -175,11 +172,7 @@ const getAppSrvFunction = sAppSrv => { const sendMail = prms => { return new Promise((resolve, reject) => { //Проверяем структуру переданного объекта для отправки E-Mail уведомления - let sCheckResult = validateObject( - prms, - prmsUtilsSchema.sendMail, - "Параметры функции отправки E-Mail уведомления" - ); + let sCheckResult = validateObject(prms, prmsUtilsSchema.sendMail, "Параметры функции отправки E-Mail уведомления"); //Если структура объекта в норме if (!sCheckResult) { //Формируем параметры для подключения к SMTP @@ -196,7 +189,7 @@ const sendMail = prms => { transpOptions.auth = { user: prms.mail.sUser, pass: prms.mail.sPass - } + }; } //Настраиваем подключение к SMTP-серверу let transporter = nodemailer.createTransport(transpOptions); @@ -215,12 +208,7 @@ const sendMail = prms => { reject(new ServerError(SERR_MAIL_FAILED, `${error.code}: ${error}`)); } else { if (info.rejected && Array.isArray(info.rejected) && info.rejected.length > 0) { - reject( - new ServerError( - SERR_MAIL_FAILED, - `Сообщение не доствлено адресатам: ${info.rejected.join(", ")}` - ) - ); + reject(new ServerError(SERR_MAIL_FAILED, `Сообщение не доствлено адресатам: ${info.rejected.join(", ")}`)); } else { resolve(info); } @@ -239,7 +227,7 @@ const buildURL = prms => { //Если структура объекта в норме if (!sCheckResult) { //Формируем URL с учетом лишних "/" - return `${prms.sSrvRoot.replace(/\/+$/, '')}/${prms.sFnURL.replace(/^\/+/, '')}${prms.sQuery ? `?${prms.sQuery}` : ""}`; + return `${prms.sSrvRoot.replace(/\/+$/, "")}/${prms.sFnURL.replace(/^\/+/, "")}${prms.sQuery ? `?${prms.sQuery}` : ""}`; } else { throw new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult); } @@ -283,11 +271,7 @@ const parseXML = prms => { //Разбор параметров сообщения/ответа (XML > JSON) const parseOptionsXML = async prms => { //Проверяем структуру переданных параметров - let sCheckResult = validateObject( - prms, - prmsUtilsSchema.parseOptionsXML, - "Параметры функции разбора XML параметров сообщения/ответа" - ); + let sCheckResult = validateObject(prms, prmsUtilsSchema.parseOptionsXML, "Параметры функции разбора XML параметров сообщения/ответа"); //Если структура объекта в норме if (!sCheckResult) { try { @@ -311,11 +295,7 @@ const parseOptionsXML = async prms => { //Сборка параметров сообщения/ответа (JSON > XML) const buildOptionsXML = prms => { //Проверяем структуру переданных параметров - let sCheckResult = validateObject( - prms, - prmsUtilsSchema.buildOptionsXML, - "Параметры функции сборки XML параметров сообщения/ответа" - ); + let sCheckResult = validateObject(prms, prmsUtilsSchema.buildOptionsXML, "Параметры функции сборки XML параметров сообщения/ответа"); //Если структура объекта в норме if (!sCheckResult) { try { @@ -346,6 +326,23 @@ const deepMerge = (...args) => { return res; }; +//Получение брокера Kafka по адресу сервиса обмена +const getKafkaBroker = sURL => { + //Убираем лишние символы + return sURL.slice(8); +}; + +//Получение авторизации для Kafka +const getKafkaAuth = (sUser, sPass) => { + return sUser ? { ssl: true, sasl: { mechanism: "plain", username: sUser, password: sPass } } : null; +}; + +//Получение протокола адреса +const getURLProtocol = sURL => { + //Считываем протокол адреса + return new URL(sURL).protocol.slice(0, -1); +}; + //----------------- // Интерфейс модуля //----------------- @@ -364,3 +361,6 @@ exports.parseOptionsXML = parseOptionsXML; exports.buildOptionsXML = buildOptionsXML; exports.getNowString = getNowString; exports.deepMerge = deepMerge; +exports.getKafkaBroker = getKafkaBroker; +exports.getKafkaAuth = getKafkaAuth; +exports.getURLProtocol = getURLProtocol; diff --git a/models/obj_service_function.js b/models/obj_service_function.js index cf266cb..0be959d 100644 --- a/models/obj_service_function.js +++ b/models/obj_service_function.js @@ -70,6 +70,13 @@ const NERR_NTF_SIGN_YES = 1; //Оповещать об ошибке исполн const SERR_NTF_SIGN_NO = "ERR_NTF_SIGN_NO"; //Не оповещать об ошибке исполнения (строковый код) const SERR_NTF_SIGN_YES = "ERR_NTF_SIGN_YES"; //Оповещать об ошибке исполнения (строковый код) +//Протоколы работы сервиса +const SPROTOCOL_HTTP = "http"; //Протокол HTTP +const SPROTOCOL_HTTPS = "https"; //Протокол HTTPS +const SPROTOCOL_MQTT = "mqtt"; //Протокол MQTT +const SPROTOCOL_MQTTS = "mqtts"; //Протокол MQTTS +const SPROTOCOL_KAFKA = "kafka"; //Протокол для работы с KAFKA + //------------- // Тело модуля //------------- @@ -139,6 +146,11 @@ exports.NERR_NTF_SIGN_NO = NERR_NTF_SIGN_NO; exports.NERR_NTF_SIGN_YES = NERR_NTF_SIGN_YES; exports.SERR_NTF_SIGN_NO = SERR_NTF_SIGN_NO; exports.SERR_NTF_SIGN_YES = SERR_NTF_SIGN_YES; +exports.SPROTOCOL_HTTP = SPROTOCOL_HTTP; +exports.SPROTOCOL_HTTPS = SPROTOCOL_HTTPS; +exports.SPROTOCOL_MQTT = SPROTOCOL_MQTT; +exports.SPROTOCOL_MQTTS = SPROTOCOL_MQTTS; +exports.SPROTOCOL_KAFKA = SPROTOCOL_KAFKA; //Схема валидации функции сервиса exports.ServiceFunction = new Schema({ @@ -156,8 +168,7 @@ exports.ServiceFunction = new Schema({ type: Number, required: true, message: { - type: path => - `Идентификатор родительского сервиса функции (${path}) имеет некорректный тип данных (ожидалось - Number)`, + type: path => `Идентификатор родительского сервиса функции (${path}) имеет некорректный тип данных (ожидалось - Number)`, required: path => `Не указан идентификатор родительского сервиса функции (${path})` } }, @@ -187,8 +198,7 @@ exports.ServiceFunction = new Schema({ enum: [SFN_TYPE_DATA, SFN_TYPE_LOGIN, SFN_TYPE_LOGOUT], required: true, message: { - type: path => - `Строковый код типа функции сервиса (${path}) имеет некорректный тип данных (ожидалось - String)`, + type: path => `Строковый код типа функции сервиса (${path}) имеет некорректный тип данных (ожидалось - String)`, enum: path => `Значение строкового кода типа функции сервиса (${path}) не поддерживается`, required: path => `Не указан строковый код типа функции сервиса (${path})` } @@ -218,8 +228,7 @@ exports.ServiceFunction = new Schema({ ], required: true, message: { - type: path => - `Способ передачи параметров функции сервиса (${path}) имеет некорректный тип данных (ожидалось - Number)`, + type: path => `Способ передачи параметров функции сервиса (${path}) имеет некорректный тип данных (ожидалось - Number)`, enum: path => `Значение способа передачи параметров функции сервиса (${path}) не поддерживается`, required: path => `Не указан способ передачи параметров функции сервиса (${path})` } @@ -240,10 +249,8 @@ exports.ServiceFunction = new Schema({ ], required: true, message: { - type: path => - `Строковый код способа передачи параметров функции сервиса (${path}) имеет некорректный тип данных (ожидалось - String)`, - enum: path => - `Значение строкового кода способа передачи параметров функции сервиса (${path}) не поддерживается`, + type: path => `Строковый код способа передачи параметров функции сервиса (${path}) имеет некорректный тип данных (ожидалось - String)`, + enum: path => `Значение строкового кода способа передачи параметров функции сервиса (${path}) не поддерживается`, required: path => `Не указан строковый код способа передачи параметров функции сервиса (${path})` } }, @@ -261,8 +268,7 @@ exports.ServiceFunction = new Schema({ ], required: true, message: { - type: path => - `График повторной отправки запроса функции сервиса (${path}) имеет некорректный тип данных (ожидалось - Number)`, + type: path => `График повторной отправки запроса функции сервиса (${path}) имеет некорректный тип данных (ожидалось - Number)`, enum: path => `Значение графика повторной отправки запроса функции сервиса (${path}) не поддерживается`, required: path => `Не указан график повторной отправки запроса функции сервиса (${path})` } @@ -283,8 +289,7 @@ exports.ServiceFunction = new Schema({ message: { type: path => `Строковый код графика повторной отправки запроса функции сервиса (${path}) имеет некорректный тип данных (ожидалось - String)`, - enum: path => - `Значение строкового кода графика повторной отправки запроса функции сервиса (${path}) не поддерживается`, + enum: path => `Значение строкового кода графика повторной отправки запроса функции сервиса (${path}) не поддерживается`, required: path => `Не указан строковый код графика повторной отправки запроса функции сервиса (${path})` } }, @@ -295,8 +300,7 @@ exports.ServiceFunction = new Schema({ message: { type: path => `Идентификатор типового сообщения обмена, обрабатываемого функцией сервиса (${path}) имеет некорректный тип данных (ожидалось - Number)`, - required: path => - `Не указан идентификатор типового сообщения обмена, обрабатываемого функцией сервиса (${path})` + required: path => `Не указан идентификатор типового сообщения обмена, обрабатываемого функцией сервиса (${path})` } }, //Код типового сообщения обмена, обрабатываемого функцией сервиса @@ -314,8 +318,7 @@ exports.ServiceFunction = new Schema({ type: String, required: false, message: { - type: path => - `Обработчик сообщения со стороны БД для функции сервиса (${path}) имеет некорректный тип данных (ожидалось - String)`, + type: path => `Обработчик сообщения со стороны БД для функции сервиса (${path}) имеет некорректный тип данных (ожидалось - String)`, required: path => `Не указан обработчик сообщения со стороны БД для функции сервиса (${path})` } }, @@ -327,8 +330,7 @@ exports.ServiceFunction = new Schema({ message: { type: path => `Обработчик сообщения 'до' на строне сервера приложений для функции сервиса (${path}) имеет некорректный тип данных (ожидалось - String)`, - required: path => - `Не указан обработчик сообщения 'до' на строне сервера приложений для функции сервиса (${path})`, + required: path => `Не указан обработчик сообщения 'до' на строне сервера приложений для функции сервиса (${path})`, validateAppSrvFn: path => `Обработчик сообщения 'до' на строне сервера приложений для функции сервиса (${path}) имеет некорректный формат, ожидалось: <МОДУЛЬ>.js/<ФУНКЦИЯ>` } @@ -341,8 +343,7 @@ exports.ServiceFunction = new Schema({ message: { type: path => `Обработчик сообщения 'после' на строне сервера приложений для функции сервиса (${path}) имеет некорректный тип данных (ожидалось - String)`, - required: path => - `Не указан обработчик сообщения 'после' на строне сервера приложений для функции сервиса (${path})`, + required: path => `Не указан обработчик сообщения 'после' на строне сервера приложений для функции сервиса (${path})`, validateAppSrvFn: path => `Обработчик сообщения 'после' на строне сервера приложений для функции сервиса (${path}) имеет некорректный формат, ожидалось: <МОДУЛЬ>.js/<ФУНКЦИЯ>` } @@ -355,10 +356,8 @@ exports.ServiceFunction = new Schema({ message: { type: path => `Признак необходимости аутентификации для исполнения функции сервсиа обмена (${path}) имеет некорректный тип данных (ожидалось - Number)`, - enum: path => - `Значение признака необходимости аутентификации для исполнения функции сервсиа обмена (${path}) не поддерживается`, - required: path => - `Не указан признак необходимости аутентификации для исполнения функции сервсиа обмена (${path})` + enum: path => `Значение признака необходимости аутентификации для исполнения функции сервсиа обмена (${path}) не поддерживается`, + required: path => `Не указан признак необходимости аутентификации для исполнения функции сервсиа обмена (${path})` } }, //Признак необходимости аутентификации для исполнения функции сервсиа обмена (строковый код) @@ -371,8 +370,7 @@ exports.ServiceFunction = new Schema({ `Строковый код признака необходимости аутентификации для исполнения функции сервсиа обмена (${path}) имеет некорректный тип данных (ожидалось - String)`, enum: path => `Значение строкового кода признака необходимости аутентификации для исполнения функции сервсиа обмена (${path}) не поддерживается`, - required: path => - `Не указан строковый код признака необходимости аутентификации для исполнения функции сервсиа обмена (${path})` + required: path => `Не указан строковый код признака необходимости аутентификации для исполнения функции сервсиа обмена (${path})` } }, //Признак оповещения об ошибке исполнения сообщения очереди для функции обработки @@ -383,10 +381,8 @@ exports.ServiceFunction = new Schema({ message: { type: path => `Признак оповещения об ошибке исполнения сообщения очереди для функции обработки (${path}) имеет некорректный тип данных (ожидалось - Number)`, - enum: path => - `Значение признака оповещения об ошибке исполнения сообщения очереди для функции обработки (${path}) не поддерживается`, - required: path => - `Не указан признак оповещения об ошибке исполнения сообщения очереди для функции обработки (${path})` + enum: path => `Значение признака оповещения об ошибке исполнения сообщения очереди для функции обработки (${path}) не поддерживается`, + required: path => `Не указан признак оповещения об ошибке исполнения сообщения очереди для функции обработки (${path})` } }, //Признак оповещения об ошибке исполнения сообщения очереди для функции обработки (строковый код) @@ -399,8 +395,7 @@ exports.ServiceFunction = new Schema({ `Строковый код признака оповещения об ошибке исполнения сообщения очереди для функции обработки (${path}) имеет некорректный тип данных (ожидалось - String)`, enum: path => `Значение строкового кода признака оповещения об ошибке исполнения сообщения очереди для функции обработки (${path}) не поддерживается`, - required: path => - `Не указан строковый код признака оповещения об ошибке исполнения сообщения очереди для функции обработки (${path})` + required: path => `Не указан строковый код признака оповещения об ошибке исполнения сообщения очереди для функции обработки (${path})` } }, //Список адресов E-Mail для оповещения об ошибке исполнения сообщения очереди для функции обработки @@ -411,8 +406,7 @@ exports.ServiceFunction = new Schema({ message: { type: path => `Список адресов E-Mail для оповещения об ошибке исполнения сообщения очереди для функции обработки (${path}) имеет некорректный тип данных (ожидалось - String)`, - required: path => - `Не указан список адресов E-Mail для оповещения об ошибке исполнения сообщения очереди для функции обработки (${path})`, + required: path => `Не указан список адресов E-Mail для оповещения об ошибке исполнения сообщения очереди для функции обработки (${path})`, validateErrNtfMail: path => `Неверный формат списка адресов E-Mail для оповещения об ошибке исполнения сообщения очереди для функции обработки (${path}), для указания нескольких адресов следует использовать запятую в качестве разделителя (без пробелов)` } diff --git a/package-lock.json b/package-lock.json index 21f8ef3..46e2f86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,10 @@ "body-parser": "^1.19.0", "cors": "^2.8.5", "express": "^4.17.1", + "kafkajs": "^2.2.4", "lodash": "^4.17.19", "module-alias": "^2.2.2", + "mqtt": "^5.10.1", "nodemailer": "^6.4.11", "oracledb": "^4.2.0", "request": "^2.88.2", @@ -22,6 +24,53 @@ "xml2js": "^0.4.23" } }, + "node_modules/@babel/runtime": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz", + "integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@types/node": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", + "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/readable-stream": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.15.tgz", + "integrity": "sha512-oAZ3kw+kJFkEqyh7xORZOku1YAKvsFTogRY8kVl4vHpEKiDkfnSA/My8haRE7fvmix5Zyy+1pwzOi7yycGLBJw==", + "dependencies": { + "@types/node": "*", + "safe-buffer": "~5.1.1" + } + }, + "node_modules/@types/ws": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -88,6 +137,25 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -96,6 +164,22 @@ "tweetnacl": "^0.14.3" } }, + "node_modules/bl": { + "version": "6.0.15", + "resolved": "https://registry.npmjs.org/bl/-/bl-6.0.15.tgz", + "integrity": "sha512-RGhjD1XCPS7ZdAH6cEJVaR3gLV4KJP2hvkQ49AH5kwScjiyd0jBM8RsP4oHKzcx+kNCON9752zPeRnuv0HHwzw==", + "dependencies": { + "@types/readable-stream": "^4.0.0", + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^4.2.0" + } + }, + "node_modules/bl/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -121,6 +205,34 @@ "node": ">= 0.8" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, "node_modules/bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -145,11 +257,43 @@ "node": ">= 0.8" } }, + "node_modules/commist": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/commist/-/commist-3.2.0.tgz", + "integrity": "sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==" + }, "node_modules/component-type": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-type/-/component-type-1.2.1.tgz", "integrity": "sha1-ikeQFwAjjk/DIml3EjAibyS0Fak=" }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", @@ -280,6 +424,22 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/express": { "version": "4.17.1", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", @@ -343,6 +503,18 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, + "node_modules/fast-unique-numbers": { + "version": "8.0.13", + "resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-8.0.13.tgz", + "integrity": "sha512-7OnTFAVPefgw2eBJ1xj2PGGR9FwYzSUso9decayHgCDX4sJkHLdcsYTytTg+tYv+wKF3U8gJuSBz2jJpQV4u/g==", + "dependencies": { + "@babel/runtime": "^7.23.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.1.0" + } + }, "node_modules/finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -426,6 +598,11 @@ "node": ">=6" } }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==" + }, "node_modules/http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", @@ -466,6 +643,25 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", @@ -489,6 +685,15 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, + "node_modules/js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -523,11 +728,24 @@ "verror": "1.10.0" } }, + "node_modules/kafkajs": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/kafkajs/-/kafkajs-2.2.4.tgz", + "integrity": "sha512-j/YeapB1vfPT2iOIUn/vxdyKEuhuY2PxMBvf5JWux6iSaukAccrMtXEY/Lb7OvavDhOWME589bpLrEdnVHjfjA==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -579,11 +797,102 @@ "node": ">= 0.6" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/module-alias": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==" }, + "node_modules/mqtt": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.10.1.tgz", + "integrity": "sha512-hXCOki8sANoQ7w+2OzJzg6qMBxTtrH9RlnVNV8panLZgnl+Gh0J/t4k6r8Az8+C7y3KAcyXtn0mmLixyUom8Sw==", + "dependencies": { + "@types/readable-stream": "^4.0.5", + "@types/ws": "^8.5.9", + "commist": "^3.2.0", + "concat-stream": "^2.0.0", + "debug": "^4.3.4", + "help-me": "^5.0.0", + "lru-cache": "^10.0.1", + "minimist": "^1.2.8", + "mqtt-packet": "^9.0.0", + "number-allocator": "^1.0.14", + "readable-stream": "^4.4.2", + "reinterval": "^1.1.0", + "rfdc": "^1.3.0", + "split2": "^4.2.0", + "worker-timers": "^7.1.4", + "ws": "^8.17.1" + }, + "bin": { + "mqtt": "build/bin/mqtt.js", + "mqtt_pub": "build/bin/pub.js", + "mqtt_sub": "build/bin/sub.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/mqtt-packet": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-9.0.0.tgz", + "integrity": "sha512-8v+HkX+fwbodsWAZIZTI074XIoxVBOmPeggQuDFCGg1SqNcC+uoRMWu7J6QlJPqIUIJXmjNYYHxBBLr1Y/Df4w==", + "dependencies": { + "bl": "^6.0.8", + "debug": "^4.3.4", + "process-nextick-args": "^2.0.1" + } + }, + "node_modules/mqtt-packet/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/mqtt-packet/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/mqtt/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/mqtt/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/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -605,6 +914,36 @@ "node": ">=6.0.0" } }, + "node_modules/number-allocator": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz", + "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==", + "dependencies": { + "debug": "^4.3.1", + "js-sdsl": "4.3.0" + } + }, + "node_modules/number-allocator/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/number-allocator/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/oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -659,6 +998,19 @@ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "node_modules/proxy-addr": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", @@ -714,6 +1066,31 @@ "node": ">= 0.8" } }, + "node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/reinterval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz", + "integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==" + }, "node_modules/request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -785,6 +1162,11 @@ "node": ">=0.6" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -847,6 +1229,14 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", @@ -887,6 +1277,33 @@ "node": ">=0.10.0" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -907,6 +1324,11 @@ "node": ">=0.8" } }, + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -940,6 +1362,16 @@ "resolved": "https://registry.npmjs.org/typecast/-/typecast-0.0.1.tgz", "integrity": "sha1-//t13La98d744pO2tuiT1sHtGd4=" }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -956,6 +1388,11 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -1006,6 +1443,57 @@ "extsprintf": "^1.2.0" } }, + "node_modules/worker-timers": { + "version": "7.1.8", + "resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-7.1.8.tgz", + "integrity": "sha512-R54psRKYVLuzff7c1OTFcq/4Hue5Vlz4bFtNEIarpSiCYhpifHU3aIQI29S84o1j87ePCYqbmEJPqwBTf+3sfw==", + "dependencies": { + "@babel/runtime": "^7.24.5", + "tslib": "^2.6.2", + "worker-timers-broker": "^6.1.8", + "worker-timers-worker": "^7.0.71" + } + }, + "node_modules/worker-timers-broker": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-6.1.8.tgz", + "integrity": "sha512-FUCJu9jlK3A8WqLTKXM9E6kAmI/dR1vAJ8dHYLMisLNB/n3GuaFIjJ7pn16ZcD1zCOf7P6H62lWIEBi+yz/zQQ==", + "dependencies": { + "@babel/runtime": "^7.24.5", + "fast-unique-numbers": "^8.0.13", + "tslib": "^2.6.2", + "worker-timers-worker": "^7.0.71" + } + }, + "node_modules/worker-timers-worker": { + "version": "7.0.71", + "resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-7.0.71.tgz", + "integrity": "sha512-ks/5YKwZsto1c2vmljroppOKCivB/ma97g9y77MAAz2TBBjPPgpoOiS1qYQKIgvGTr2QYPT3XhJWIB6Rj2MVPQ==", + "dependencies": { + "@babel/runtime": "^7.24.5", + "tslib": "^2.6.2" + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xml2js": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", @@ -1028,6 +1516,47 @@ } }, "dependencies": { + "@babel/runtime": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz", + "integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==", + "requires": { + "regenerator-runtime": "^0.14.0" + } + }, + "@types/node": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", + "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", + "requires": { + "undici-types": "~6.19.2" + } + }, + "@types/readable-stream": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.15.tgz", + "integrity": "sha512-oAZ3kw+kJFkEqyh7xORZOku1YAKvsFTogRY8kVl4vHpEKiDkfnSA/My8haRE7fvmix5Zyy+1pwzOi7yycGLBJw==", + "requires": { + "@types/node": "*", + "safe-buffer": "~5.1.1" + } + }, + "@types/ws": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "requires": { + "@types/node": "*" + } + }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -1081,6 +1610,11 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -1089,6 +1623,24 @@ "tweetnacl": "^0.14.3" } }, + "bl": { + "version": "6.0.15", + "resolved": "https://registry.npmjs.org/bl/-/bl-6.0.15.tgz", + "integrity": "sha512-RGhjD1XCPS7ZdAH6cEJVaR3gLV4KJP2hvkQ49AH5kwScjiyd0jBM8RsP4oHKzcx+kNCON9752zPeRnuv0HHwzw==", + "requires": { + "@types/readable-stream": "^4.0.0", + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^4.2.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + } + } + }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -1111,6 +1663,20 @@ "type-is": "~1.6.17" } }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -1129,11 +1695,39 @@ "delayed-stream": "~1.0.0" } }, + "commist": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/commist/-/commist-3.2.0.tgz", + "integrity": "sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==" + }, "component-type": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-type/-/component-type-1.2.1.tgz", "integrity": "sha1-ikeQFwAjjk/DIml3EjAibyS0Fak=" }, + "concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", @@ -1236,6 +1830,16 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" + }, "express": { "version": "4.17.1", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", @@ -1293,6 +1897,15 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, + "fast-unique-numbers": { + "version": "8.0.13", + "resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-8.0.13.tgz", + "integrity": "sha512-7OnTFAVPefgw2eBJ1xj2PGGR9FwYzSUso9decayHgCDX4sJkHLdcsYTytTg+tYv+wKF3U8gJuSBz2jJpQV4u/g==", + "requires": { + "@babel/runtime": "^7.23.8", + "tslib": "^2.6.2" + } + }, "finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -1354,6 +1967,11 @@ "har-schema": "^2.0.0" } }, + "help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==" + }, "http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", @@ -1384,6 +2002,11 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", @@ -1404,6 +2027,11 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, + "js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==" + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -1435,11 +2063,21 @@ "verror": "1.10.0" } }, + "kafkajs": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/kafkajs/-/kafkajs-2.2.4.tgz", + "integrity": "sha512-j/YeapB1vfPT2iOIUn/vxdyKEuhuY2PxMBvf5JWux6iSaukAccrMtXEY/Lb7OvavDhOWME589bpLrEdnVHjfjA==" + }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -1473,11 +2111,79 @@ "mime-db": "1.47.0" } }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, "module-alias": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==" }, + "mqtt": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.10.1.tgz", + "integrity": "sha512-hXCOki8sANoQ7w+2OzJzg6qMBxTtrH9RlnVNV8panLZgnl+Gh0J/t4k6r8Az8+C7y3KAcyXtn0mmLixyUom8Sw==", + "requires": { + "@types/readable-stream": "^4.0.5", + "@types/ws": "^8.5.9", + "commist": "^3.2.0", + "concat-stream": "^2.0.0", + "debug": "^4.3.4", + "help-me": "^5.0.0", + "lru-cache": "^10.0.1", + "minimist": "^1.2.8", + "mqtt-packet": "^9.0.0", + "number-allocator": "^1.0.14", + "readable-stream": "^4.4.2", + "reinterval": "^1.1.0", + "rfdc": "^1.3.0", + "split2": "^4.2.0", + "worker-timers": "^7.1.4", + "ws": "^8.17.1" + }, + "dependencies": { + "debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "mqtt-packet": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-9.0.0.tgz", + "integrity": "sha512-8v+HkX+fwbodsWAZIZTI074XIoxVBOmPeggQuDFCGg1SqNcC+uoRMWu7J6QlJPqIUIJXmjNYYHxBBLr1Y/Df4w==", + "requires": { + "bl": "^6.0.8", + "debug": "^4.3.4", + "process-nextick-args": "^2.0.1" + }, + "dependencies": { + "debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -1493,6 +2199,30 @@ "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.0.tgz", "integrity": "sha512-ikSMDU1nZqpo2WUPE0wTTw/NGGImTkwpJKDIFPZT+YvvR9Sj+ze5wzu95JHkBMglQLoG2ITxU21WukCC/XsFkg==" }, + "number-allocator": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz", + "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==", + "requires": { + "debug": "^4.3.1", + "js-sdsl": "4.3.0" + }, + "dependencies": { + "debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -1531,6 +2261,16 @@ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "proxy-addr": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", @@ -1571,6 +2311,28 @@ "unpipe": "1.0.0" } }, + "readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "requires": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + } + }, + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "reinterval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz", + "integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==" + }, "request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -1624,6 +2386,11 @@ "lodash": "^4.17.19" } }, + "rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -1682,6 +2449,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, + "split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==" + }, "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", @@ -1708,6 +2480,21 @@ "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -1722,6 +2509,11 @@ "punycode": "^2.1.1" } }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -1749,6 +2541,16 @@ "resolved": "https://registry.npmjs.org/typecast/-/typecast-0.0.1.tgz", "integrity": "sha1-//t13La98d744pO2tuiT1sHtGd4=" }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, + "undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -1762,6 +2564,11 @@ "punycode": "^2.1.0" } }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -1797,6 +2604,43 @@ "extsprintf": "^1.2.0" } }, + "worker-timers": { + "version": "7.1.8", + "resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-7.1.8.tgz", + "integrity": "sha512-R54psRKYVLuzff7c1OTFcq/4Hue5Vlz4bFtNEIarpSiCYhpifHU3aIQI29S84o1j87ePCYqbmEJPqwBTf+3sfw==", + "requires": { + "@babel/runtime": "^7.24.5", + "tslib": "^2.6.2", + "worker-timers-broker": "^6.1.8", + "worker-timers-worker": "^7.0.71" + } + }, + "worker-timers-broker": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-6.1.8.tgz", + "integrity": "sha512-FUCJu9jlK3A8WqLTKXM9E6kAmI/dR1vAJ8dHYLMisLNB/n3GuaFIjJ7pn16ZcD1zCOf7P6H62lWIEBi+yz/zQQ==", + "requires": { + "@babel/runtime": "^7.24.5", + "fast-unique-numbers": "^8.0.13", + "tslib": "^2.6.2", + "worker-timers-worker": "^7.0.71" + } + }, + "worker-timers-worker": { + "version": "7.0.71", + "resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-7.0.71.tgz", + "integrity": "sha512-ks/5YKwZsto1c2vmljroppOKCivB/ma97g9y77MAAz2TBBjPPgpoOiS1qYQKIgvGTr2QYPT3XhJWIB6Rj2MVPQ==", + "requires": { + "@babel/runtime": "^7.24.5", + "tslib": "^2.6.2" + } + }, + "ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "requires": {} + }, "xml2js": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", diff --git a/package.json b/package.json index 25eed3c..deec4a9 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,10 @@ "body-parser": "^1.19.0", "cors": "^2.8.5", "express": "^4.17.1", + "kafkajs": "^2.2.4", "lodash": "^4.17.19", "module-alias": "^2.2.2", + "mqtt": "^5.10.1", "nodemailer": "^6.4.11", "oracledb": "^4.2.0", "request": "^2.88.2", From 3128ed04923b90243cb6d61495c61d5c1fc37bfd Mon Sep 17 00:00:00 2001 From: Dollerino Date: Thu, 19 Sep 2024 15:19:28 +0300 Subject: [PATCH 2/4] =?UTF-8?q?=D0=A6=D0=98=D0=A2=D0=9A-901=20(=D0=94?= =?UTF-8?q?=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA=D0=B8=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D1=82=D0=BE=D0=BA=D0=BE=D0=BB=D0=BE=D0=B2=20MQTT=20?= =?UTF-8?q?=D0=B8=20KAFKA)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/in_queue.js | 2 -- core/mqtt_connector.js | 1 - 2 files changed, 3 deletions(-) diff --git a/core/in_queue.js b/core/in_queue.js index 1237e04..316ecc3 100644 --- a/core/in_queue.js +++ b/core/in_queue.js @@ -556,14 +556,12 @@ class InQueue extends EventEmitter { options = { method: fn.sFnPrmsType }; - console.log("Кладём в очередь"); //Кладём сообщение в очередь q = await this.dbConn.putQueue({ nServiceFnId: fn.nId, sOptions: buildOptionsXML({ options }), blMsg }); - console.log("Продолжаем"); //Скажем что пришло новое входящее сообщение await this.logger.info( `Новое входящее MQTT-сообщение для функции ${fn.sCode} (${buildURL({ diff --git a/core/mqtt_connector.js b/core/mqtt_connector.js index 48fcbc6..90c929a 100644 --- a/core/mqtt_connector.js +++ b/core/mqtt_connector.js @@ -59,7 +59,6 @@ const subscribeMQTT = async ({ connectionPrms, service, processMQTTMessage, logg //Прослушиваем сообщения client.on("message", (topic, message) => { - console.log("message"); //Обрабатываем сообщение processMQTTMessage({ message, service, fn: _.find(service.functions, { sFnURL: topic }) }); }); From 226fe582b66677e286cf6d66efcdddc69b898351 Mon Sep 17 00:00:00 2001 From: Dollerino Date: Thu, 19 Sep 2024 18:20:58 +0300 Subject: [PATCH 3/4] =?UTF-8?q?=D0=A6=D0=98=D0=A2=D0=9A-901=20(=D0=94?= =?UTF-8?q?=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA=D0=B8=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D1=82=D0=BE=D0=BA=D0=BE=D0=BB=D0=BE=D0=B2=20MQTT=20?= =?UTF-8?q?=D0=B8=20KAFKA)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.js | 2 ++ core/kafka_connector.js | 2 +- core/mqtt_connector.js | 9 +++------ 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/config.js b/config.js index fa73f2f..edea45f 100644 --- a/config.js +++ b/config.js @@ -77,6 +77,8 @@ let kafkaConnection = { sClientIdSender: "Parus", //ID клиента-получателя sClientIdRecipient: "Parus", + //Группа получателя + sGroupId: "Parus", //Время ожидания успешного подключения (мс) nConnectionTimeout: 5000, //Необходимость попытки переподключения при потере соединения diff --git a/core/kafka_connector.js b/core/kafka_connector.js index 5ee2841..be09621 100644 --- a/core/kafka_connector.js +++ b/core/kafka_connector.js @@ -71,7 +71,7 @@ const subscribeKafka = async ({ connectionPrms, service, processKafkaMessage, lo }); //Инициализируем получателя - let consumer = client.consumer({ groupId: "ParusWebApi" }); + let consumer = client.consumer({ groupId: connectionPrms.sGroupId }); //Устанавливаем прослушивание await consumer.connect(); diff --git a/core/mqtt_connector.js b/core/mqtt_connector.js index 90c929a..f63f226 100644 --- a/core/mqtt_connector.js +++ b/core/mqtt_connector.js @@ -50,12 +50,9 @@ const subscribeMQTT = async ({ connectionPrms, service, processMQTTMessage, logg }); //Обходим функции сервиса - _.forEach( - _.filter(service.functions, fn => !fn.sFnURL.startsWith("@")), - fn => { - client.subscribe(fn.sFnURL); - } - ); + _.forEach(service.functions, fn => { + client.subscribe(fn.sFnURL); + }); //Прослушиваем сообщения client.on("message", (topic, message) => { From 6ad825c2b6390672c9b15831f0a01d1d5f0db3b0 Mon Sep 17 00:00:00 2001 From: Dollerino Date: Tue, 24 Sep 2024 17:58:57 +0300 Subject: [PATCH 4/4] =?UTF-8?q?=D0=A6=D0=98=D0=A2=D0=9A-901=20(=D0=94?= =?UTF-8?q?=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA=D0=B8=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D1=82=D0=BE=D0=BA=D0=BE=D0=BB=D0=BE=D0=B2=20MQTT=20?= =?UTF-8?q?=D0=B8=20KAFKA)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.js | 66 ++++++------ config_default.js | 64 +++++++----- core/app.js | 10 +- core/in_queue.js | 185 +++++++++++++++++++-------------- core/kafka_connector.js | 41 ++++---- core/mqtt_connector.js | 32 +++--- core/out_queue.js | 8 +- core/out_queue_processor.js | 52 ++++++--- core/utils.js | 54 ++++++++-- models/obj_service.js | 45 ++++---- models/obj_service_function.js | 12 --- 11 files changed, 337 insertions(+), 232 deletions(-) diff --git a/config.js b/config.js index edea45f..c86ddc7 100644 --- a/config.js +++ b/config.js @@ -72,34 +72,42 @@ let inComing = { }; //Параметры подключения к Kafka -let kafkaConnection = { - //ID клиента-отправителя - sClientIdSender: "Parus", - //ID клиента-получателя - sClientIdRecipient: "Parus", - //Группа получателя - sGroupId: "Parus", - //Время ожидания успешного подключения (мс) - nConnectionTimeout: 5000, - //Необходимость попытки переподключения при потере соединения - bRestartOnFailure: false, - //Время максимального ожидания между попытками переподключения (мс) - nMaxRetryTime: 20000, - //Время ожидания между попытками переподключения (мс) - nInitialRetryTime: 10000 -}; +const kafka = [ + { + //Мнемокод сервиса обмена (пусто - использовать по умолчанию) + sService: "", + //ID клиента-отправителя + sClientIdSender: "Parus", + //ID клиента-получателя + sClientIdRecipient: "Parus", + //Группа получателя + sGroupId: "Parus", + //Время ожидания успешного подключения (мс) + nConnectionTimeout: 5000, + //Необходимость попытки переподключения при потере соединения + bRestartOnFailure: true, + //Время максимального ожидания между попытками переподключения (мс) + nMaxRetryTime: 20000, + //Время ожидания между попытками переподключения (мс) + nInitialRetryTime: 10000 + } +]; -//Параметры подключения по MQTT протоколу -let mqttConnection = { - //ID клиента-отправителя - sClientIdSender: "Parus", - //ID клиента-получателя (если равен sClientIdSender, то отправленные сообщения будут игнорироваться) - sClientIdRecipient: "ParusRecipient", - //Время ожидания успешного подключения (мс) - nConnectTimeout: 5000, - //Время ожидания между попытками переподключения (мс) - nReconnectPeriod: 10000 -}; +//Параметры подключения к MQTT +const mqtt = [ + { + //Мнемокод сервиса обмена (пусто - использовать по умолчанию) + sService: "", + //ID клиента-отправителя + sClientIdSender: "Parus", + //ID клиента-получателя (если равен sClientIdSender, то отправленные сообщения будут игнорироваться) + sClientIdRecipient: "ParusRecipient", + //Время ожидания успешного подключения (мс) + nConnectTimeout: 5000, + //Время ожидания между попытками переподключения (мс) + nReconnectPeriod: 10000 + } +]; //Параметры отправки E-Mail уведомлений let mail = { @@ -128,7 +136,7 @@ module.exports = { dbConnect, outGoing, inComing, - kafkaConnection, - mqttConnection, + kafka, + mqtt, mail }; diff --git a/config_default.js b/config_default.js index 1d5007f..c289e71 100644 --- a/config_default.js +++ b/config_default.js @@ -72,32 +72,42 @@ let inComing = { }; //Параметры подключения к Kafka -let kafkaConnection = { - //ID клиента-отправителя - sClientIdSender: "Parus", - //ID клиента-получателя - sClientIdRecipient: "Parus", - //Время ожидания успешного подключения (мс) - nConnectionTimeout: 5000, - //Необходимость попытки переподключения при потере соединения - bRestartOnFailure: false, - //Время максимального ожидания между попытками переподключения (мс) - nMaxRetryTime: 20000, - //Время ожидания между попытками переподключения (мс) - nInitialRetryTime: 10000 -}; +const kafka = [ + { + //Мнемокод сервиса обмена (пусто - использовать по умолчанию) + sService: "", + //ID клиента-отправителя + sClientIdSender: "Parus", + //ID клиента-получателя + sClientIdRecipient: "Parus", + //Группа получателя + sGroupId: "Parus", + //Время ожидания успешного подключения (мс) + nConnectionTimeout: 5000, + //Необходимость попытки переподключения при потере соединения + bRestartOnFailure: true, + //Время максимального ожидания между попытками переподключения (мс) + nMaxRetryTime: 20000, + //Время ожидания между попытками переподключения (мс) + nInitialRetryTime: 10000 + } +]; -//Параметры подключения по MQTT протоколу -let mqttConnection = { - //ID клиента-отправителя - sClientIdSender: "Parus", - //ID клиента-получателя (если равен sClientIdSender, то отправленные сообщения будут игнорироваться) - sClientIdRecipient: "ParusRecipient", - //Время ожидания успешного подключения (мс) - nConnectTimeout: 5000, - //Время ожидания между попытками переподключения (мс) - nReconnectPeriod: 10000 -}; +//Параметры подключения к MQTT +const mqtt = [ + { + //Мнемокод сервиса обмена (пусто - использовать по умолчанию) + sService: "", + //ID клиента-отправителя + sClientIdSender: "Parus", + //ID клиента-получателя (если равен sClientIdSender, то отправленные сообщения будут игнорироваться) + sClientIdRecipient: "ParusRecipient", + //Время ожидания успешного подключения (мс) + nConnectTimeout: 5000, + //Время ожидания между попытками переподключения (мс) + nReconnectPeriod: 10000 + } +]; //Параметры отправки E-Mail уведомлений let mail = { @@ -126,7 +136,7 @@ module.exports = { dbConnect, outGoing, inComing, - kafkaConnection, - mqttConnection, + kafka, + mqtt, mail }; diff --git a/core/app.js b/core/app.js index cf406d5..c6d096e 100644 --- a/core/app.js +++ b/core/app.js @@ -96,7 +96,7 @@ class ParusAppServer { //Запускаем обслуживание очереди входящих await this.logger.info("Запуск обработчика очереди входящих сообщений..."); try { - this.inQ.startProcessing({ services: this.services }); + await this.inQ.startProcessing({ services: this.services }); } catch (e) { await this.logger.error(`Ошибка запуска обработчика очереди входящих сообщений: ${makeErrorText(e)}`); await this.stop(); @@ -218,8 +218,8 @@ class ParusAppServer { logger: this.logger, notifier: this.notifier, sProxy: prms.config.outGoing.sProxy, - kafkaConnectionPrms: prms.config.kafkaConnection, - mqttConnectionPrms: prms.config.mqttConnection + kafka: prms.config.kafka, + mqtt: prms.config.mqtt }); //Создаём обработчик очереди входящих this.inQ = new iq.InQueue({ @@ -228,8 +228,8 @@ class ParusAppServer { dbConn: this.dbConn, logger: this.logger, notifier: this.notifier, - kafkaConnectionPrms: prms.config.kafkaConnection, - mqttConnectionPrms: prms.config.mqttConnection + kafka: prms.config.kafka, + mqtt: prms.config.mqtt }); //Создаём контроллер доступности удалённых сервисов this.srvAvlCtrl = new sac.ServiceAvailableController({ diff --git a/core/in_queue.js b/core/in_queue.js index 316ecc3..ce20127 100644 --- a/core/in_queue.js +++ b/core/in_queue.js @@ -21,7 +21,9 @@ const { buildOptionsXML, parseOptionsXML, deepMerge, - getKafkaBroker + getKafkaConnectionSettings, + getMQTTConnectionSettings, + getURLProtocol } = require("./utils"); //Вспомогательные функции const { NINC_EXEC_CNT_YES, NIS_ORIGINAL_NO, NIS_ORIGINAL_YES } = require("../models/prms_db_connector"); //Схемы валидации параметров функций модуля взаимодействия с БД const objInQueueSchema = require("../models/obj_in_queue"); //Схема валидации сообщений обмена с бработчиком очереди входящих сообщений @@ -83,9 +85,9 @@ class InQueue extends EventEmitter { //WEB-сервер this.srv = null; //Параметры подключения к Kafka - this.kafkaConnectionPrms = _.cloneDeep(prms.kafkaConnectionPrms); + this.kafka = prms.kafka; //Параметры подключения к MQTT - this.mqttConnectionPrms = _.cloneDeep(prms.mqttConnectionPrms); + this.mqtt = prms.mqtt; //Внешние подключения this.kafkaConnections = []; this.mqttConnections = []; @@ -697,7 +699,7 @@ class InQueue extends EventEmitter { } //Запуск обработки очереди входящих сообщений - startProcessing(prms) { + async startProcessing(prms) { //Проверяем структуру переданного объекта для старта let sCheckResult = validateObject(prms, prmsInQueueSchema.startProcessing, "Параметры функции запуска обработки очереди входящих сообщений"); //Если структура объекта в норме @@ -715,106 +717,135 @@ class InQueue extends EventEmitter { //Конфигурируем сервер - обработка тела сообщения this.webApp.use(bodyParser.raw({ limit: `${this.inComing.nMsgMaxSize}mb`, type: "*/*" })); //Конфигурируем сервер - обходим все сервисы, работающие на приём сообщений - _.forEach(_.filter(this.services, { nSrvType: objServiceSchema.NSRV_TYPE_RECIVE }), srvs => { - //Для любых запросов к корневому адресу сервиса - ответ о том, что это за сервис, и что он работает - this.webApp.all(srvs.sSrvRoot, (req, res) => { - res.status(200).send( - `

Сервер приложений ПП Парус 8
(${this.common.sVersion} релиз ${this.common.sRelease})

Сервис: ${srvs.sName}

` + _.forEach( + _.filter(this.services, srv => { + return ( + srv.nSrvType === objServiceSchema.NSRV_TYPE_RECIVE && + [objServiceSchema.SPROTOCOL_HTTP, objServiceSchema.SPROTOCOL_HTTPS].includes(getURLProtocol(srv.sSrvRoot)) ); - }); - //Для всех статических функций сервиса... - _.forEach( - _.filter(srvs.functions, fn => fn.sFnURL.startsWith("@")), - fn => { - this.webApp.use( - buildURL({ sSrvRoot: srvs.sSrvRoot, sFnURL: fn.sFnURL.substr(1) }), - express.static(`${this.inComing.sStaticDir}/${fn.sFnURL.substr(1)}`) + }), + srvs => { + //Для любых запросов к корневому адресу сервиса - ответ о том, что это за сервис, и что он работает + this.webApp.all(srvs.sSrvRoot, (req, res) => { + res.status(200).send( + `

Сервер приложений ПП Парус 8
(${this.common.sVersion} релиз ${this.common.sRelease})

Сервис: ${srvs.sName}

` ); - } - ); - //Для всех функций сервиса (кроме статических)... - _.forEach( - _.filter(srvs.functions, fn => !fn.sFnURL.startsWith("@")), - fn => { - //...собственный обработчик, в зависимости от указанного способа передачи параметров - this.webApp[fn.sFnPrmsType.toLowerCase()](buildURL({ sSrvRoot: srvs.sSrvRoot, sFnURL: fn.sFnURL }), async (req, res) => { - try { - //Вызываем обработчик - await this.processMessage({ req, res, service: srvs, function: fn }); - } catch (e) { + }); + //Для всех статических функций сервиса... + _.forEach( + _.filter(srvs.functions, fn => fn.sFnURL.startsWith("@")), + fn => { + this.webApp.use( + buildURL({ sSrvRoot: srvs.sSrvRoot, sFnURL: fn.sFnURL.substr(1) }), + express.static(`${this.inComing.sStaticDir}/${fn.sFnURL.substr(1)}`) + ); + } + ); + //Для всех функций сервиса (кроме статических)... + _.forEach( + _.filter(srvs.functions, fn => !fn.sFnURL.startsWith("@")), + fn => { + //...собственный обработчик, в зависимости от указанного способа передачи параметров + this.webApp[fn.sFnPrmsType.toLowerCase()](buildURL({ sSrvRoot: srvs.sSrvRoot, sFnURL: fn.sFnURL }), async (req, res) => { + try { + //Вызываем обработчик + await this.processMessage({ req, res, service: srvs, function: fn }); + } catch (e) { + //Протоколируем в журнал работы сервера + await this.logger.error(makeErrorText(e), { + nServiceId: srvs.nId, + nServiceFnId: fn.nId + }); + //Отправим ошибку клиенту + res.status(500).send(makeErrorText(e)); + } + }); + //...и собственный обработчик ошибок + this.webApp.use(buildURL({ sSrvRoot: srvs.sSrvRoot, sFnURL: fn.sFnURL }), async (err, req, res, next) => { //Протоколируем в журнал работы сервера - await this.logger.error(makeErrorText(e), { + await this.logger.error(makeErrorText(new ServerError(SERR_WEB_SERVER, err.message)), { nServiceId: srvs.nId, nServiceFnId: fn.nId }); //Отправим ошибку клиенту - res.status(500).send(makeErrorText(e)); - } - }); - //...и собственный обработчик ошибок - this.webApp.use(buildURL({ sSrvRoot: srvs.sSrvRoot, sFnURL: fn.sFnURL }), async (err, req, res, next) => { - //Протоколируем в журнал работы сервера - await this.logger.error(makeErrorText(new ServerError(SERR_WEB_SERVER, err.message)), { - nServiceId: srvs.nId, - nServiceFnId: fn.nId + res.status(500).send(makeErrorText(new ServerError(SERR_WEB_SERVER, err.message))); }); - //Отправим ошибку клиенту - res.status(500).send(makeErrorText(new ServerError(SERR_WEB_SERVER, err.message))); - }); - } - ); - }); + } + ); + } + ); + //Инициализируем настройки подключения + let connectionSettings = null; //Считываем прием сообщений по Kafka - let kafkaSrvs = _.filter(this.services, srv => { - return srv.nSrvType === objServiceSchema.NSRV_TYPE_RECIVE && srv.sSrvRoot.startsWith("kafka://"); + let kafkaSrvs = this.services.filter(srv => { + return srv.nSrvType === objServiceSchema.NSRV_TYPE_RECIVE && getURLProtocol(srv.sSrvRoot) === objServiceSchema.SPROTOCOL_KAFKA; }); //Если есть сервисы с приемом сообщений по Kafka if (kafkaSrvs.length !== 0) { //Обходим данные сервисы - _.forEach(kafkaSrvs, async srvs => { + for (let srv of kafkaSrvs) { //Если у сервиса обмена есть функции - if (srvs.functions.length !== 0) { - //Подключаемся и подписываемся на соответствующий брокер - let connectionKafka = await subscribeKafka({ - connectionPrms: this.kafkaConnectionPrms, - service: srvs, - processKafkaMessage: prms => this.processKafkaMessage(prms), - logger: this.logger - }); - //Если подключение было создано - if (connectionKafka) { - //Добавляем в общий список подключений kafka - this.kafkaConnections.push(connectionKafka); + if (srv.functions.length !== 0) { + //Считываем настройки подключения к Kafka + connectionSettings = getKafkaConnectionSettings(srv.sCode, this.kafka); + //Если настройки подключения считаны + if (connectionSettings) { + //Подключаемся и подписываемся на соответствующий брокер + let connectionKafka = await subscribeKafka({ + settings: connectionSettings, + service: srv, + processKafkaMessage: prms => this.processKafkaMessage(prms), + logger: this.logger + }); + //Если подключение было создано + if (connectionKafka) { + //Добавляем в общий список подключений kafka + this.kafkaConnections.push(connectionKafka); + } + } else { + await this.logger.error( + `Ошибка получения настроек подключения к Kafka для сервиса "${srv.sCode}". Необходимо проверить соответствующий параметр ("kafka") файла конфигурации сервиса приложений ("config.js").` + ); } } - }); + } } //Считываем прием сообщений по MQTT - let mqttSrvs = _.filter(this.services, srv => { + let mqttSrvs = this.services.filter(srv => { return ( - srv.nSrvType === objServiceSchema.NSRV_TYPE_RECIVE && (srv.sSrvRoot.startsWith("mqtt://") || srv.sSrvRoot.startsWith("mqtts://")) + srv.nSrvType === objServiceSchema.NSRV_TYPE_RECIVE && + [objServiceSchema.SPROTOCOL_MQTT, objServiceSchema.SPROTOCOL_MQTTS].includes(getURLProtocol(srv.sSrvRoot)) ); }); //Если есть сервисы с приемом сообщений по MQTT if (mqttSrvs.length !== 0) { //Обходим данные сервисы - _.forEach(mqttSrvs, async srvs => { + for (let srv of mqttSrvs) { //Если у сервиса обмена есть функции - if (srvs.functions.length !== 0) { - //Подключаемся и подписываемся на соответствующий брокер - let connectionMQTT = await subscribeMQTT({ - connectionPrms: this.mqttConnectionPrms, - service: srvs, - processMQTTMessage: prms => this.processMQTTMessage(prms), - logger: this.logger - }); - //Если подключение было создано - if (connectionMQTT) { - //Добавляем в общий список подключений kafka - this.mqttConnections.push(connectionMQTT); + if (srv.functions.length !== 0) { + //Считываем настройки подключения к MQTT + connectionSettings = getMQTTConnectionSettings(srv.sCode, this.mqtt); + //Если настройки подключения считаны + if (connectionSettings) { + //Подключаемся и подписываемся на соответствующий брокер + let connectionMQTT = await subscribeMQTT({ + settings: connectionSettings, + service: srv, + processMQTTMessage: prms => this.processMQTTMessage(prms), + logger: this.logger + }); + //Если подключение было создано + if (connectionMQTT) { + //Добавляем в общий список подключений kafka + this.mqttConnections.push(connectionMQTT); + } + } else { + await this.logger.error( + `Ошибка получения настроек подключения к MQTT для сервиса "${srv.sCode}". Необходимо проверить соответствующий параметр ("mqtt") файла конфигурации сервиса приложений ("config.js").` + ); } } - }); + } } //Запросы на адреса, не входящие в состав объявленных сервисов - 404 NOT FOUND this.webApp.use("*", (req, res) => { diff --git a/core/kafka_connector.js b/core/kafka_connector.js index be09621..5504fa6 100644 --- a/core/kafka_connector.js +++ b/core/kafka_connector.js @@ -7,7 +7,6 @@ // Подключение библиотек //---------------------- -const _ = require("lodash"); //Работа с массивами и коллекциями const { makeErrorText, getKafkaBroker, getKafkaAuth } = require("./utils"); //Вспомогательные функции const { Kafka, logLevel } = require("kafkajs"); //Работа с Kafka @@ -16,12 +15,12 @@ const { Kafka, logLevel } = require("kafkajs"); //Работа с Kafka //------------ //Отправка сообщения Kafka -const publishKafka = async ({ connectionPrms, url, auth, topic, message }) => { +const publishKafka = async ({ settings, url, auth, topic, message }) => { //Иницализируем подключение к Kafka let kafka = new Kafka({ - clientId: connectionPrms.sClientIdSender, + clientId: settings.sClientIdSender, brokers: [url], - connectionTimeout: connectionPrms.nConnectionTimeout, + connectionTimeout: settings.nConnectionTimeout, logLevel: logLevel.NOTHING, ...auth }); @@ -30,15 +29,15 @@ const publishKafka = async ({ connectionPrms, url, auth, topic, message }) => { //Подключаемся к Kafka await producer.connect(); //Отправляем сообщение - let res = await producer.send({ topic: topic, messages: [{ value: message }] }); + await producer.send({ topic: topic, messages: [{ value: message }] }); //Отключаемся await producer.disconnect(); - //Возвращаем ответ - return res; + //Возвращаем статус успешной отправки + return { statusCode: 200 }; }; //Получение MQTT сообщений -const subscribeKafka = async ({ connectionPrms, service, processKafkaMessage, logger }) => { +const subscribeKafka = async ({ settings, service, processKafkaMessage, logger }) => { try { //Признак необходимости вывода сообщения о потере соединения let bLogLostConnection = true; @@ -46,15 +45,15 @@ const subscribeKafka = async ({ connectionPrms, service, processKafkaMessage, lo let sBroker = getKafkaBroker(service.sSrvRoot); //Иницализируем подключение к Kafka let client = new Kafka({ - clientId: connectionPrms.sClientIdRecipient, + clientId: settings.sClientIdRecipient, brokers: [sBroker], - connectionTimeout: connectionPrms.nConnectionTimeout, + connectionTimeout: settings.nConnectionTimeout, ...getKafkaAuth(service.sSrvUser, service.sSrvPass), logLevel: logLevel.NOTHING, retry: { retries: 0, - maxRetryTime: connectionPrms.nMaxRetryTime, - initialRetryTime: connectionPrms.nInitialRetryTime, + maxRetryTime: settings.nMaxRetryTime, + initialRetryTime: settings.nInitialRetryTime, restartOnFailure: error => { return new Promise(resolve => { //Если требуется вывести ошибку @@ -64,19 +63,20 @@ const subscribeKafka = async ({ connectionPrms, service, processKafkaMessage, lo //Сбрасываем признак необходимости вывода ошибки bLogLostConnection = false; } - resolve(connectionPrms.bRestartOnFailure); + resolve(settings.bRestartOnFailure); }); } } }); - //Инициализируем получателя - let consumer = client.consumer({ groupId: connectionPrms.sGroupId }); - + let consumer = client.consumer({ groupId: settings.sGroupId }); //Устанавливаем прослушивание await consumer.connect(); - consumer.subscribe({ topics: _.map(service.functions, "sFnURL") }); - + consumer.subscribe({ + topics: service.functions.map(fn => { + return fn.sFnURL; + }) + }); //Запускаем прослушивание необходимых топиков consumer.run({ eachMessage: async ({ topic, message }) => { @@ -85,14 +85,15 @@ const subscribeKafka = async ({ connectionPrms, service, processKafkaMessage, lo processKafkaMessage({ message, service, - fn: _.find(service.functions, { sFnURL: topic }) + fn: service.functions.find(fn => { + return fn.sFnURL === topic; + }) }); } catch (e) { await logger.error(`Ошибка обработки исходящего сообщения Kafka: ${makeErrorText(e)}`); } } }); - //Отслеживаем соединение consumer.on(consumer.events.CONNECT, () => { //Если сообщение о потере соединения уже выводилось diff --git a/core/mqtt_connector.js b/core/mqtt_connector.js index f63f226..17915f5 100644 --- a/core/mqtt_connector.js +++ b/core/mqtt_connector.js @@ -7,7 +7,6 @@ // Подключение библиотек //---------------------- -const _ = require("lodash"); //Работа с массивами и коллекциями const { makeErrorText } = require("./utils"); //Вспомогательные функции const mqtt = require("mqtt"); //Работа с MQTT @@ -16,48 +15,52 @@ const mqtt = require("mqtt"); //Работа с MQTT //------------ //Отправка MQTT сообщения -const publishMQTT = async ({ connectionPrms, url, auth, topic, message }) => { +const publishMQTT = async ({ settings, url, auth, topic, message }) => { //Инициализируем подключение const client = await mqtt.connectAsync(url, { - clientId: connectionPrms.sClientIdSender, + clientId: settings.sClientIdSender, clean: true, - connectTimeout: connectionPrms.nConnectTimeout, + connectTimeout: settings.nConnectTimeout, username: auth.user, password: auth.pass, - reconnectPeriod: connectionPrms.nReconnectPeriod + reconnectPeriod: settings.nReconnectPeriod }); //Отправляем сообщение await client.publishAsync(topic, message); //Закрываем подключение await client.endAsync(); - //Возвращаем сообщение, которое было отправлено + //Возвращаем статус успешной отправки return { statusCode: 200 }; }; //Получение MQTT сообщений -const subscribeMQTT = async ({ connectionPrms, service, processMQTTMessage, logger }) => { +const subscribeMQTT = async ({ settings, service, processMQTTMessage, logger }) => { try { //Инициализируем строку подключения let sBroker = service.sSrvRoot; //Инициализируем подключение const client = await mqtt.connectAsync(sBroker, { - clientId: connectionPrms.sClientIdRecipient, + clientId: settings.sClientIdRecipient, clean: true, - connectTimeout: connectionPrms.nConnectTimeout, + connectTimeout: settings.nConnectTimeout, username: service.sSrvUser, password: service.sSrvPass, - reconnectPeriod: connectionPrms.nReconnectPeriod + reconnectPeriod: settings.nReconnectPeriod }); - //Обходим функции сервиса - _.forEach(service.functions, fn => { + service.functions.forEach(fn => { client.subscribe(fn.sFnURL); }); - //Прослушиваем сообщения client.on("message", (topic, message) => { //Обрабатываем сообщение - processMQTTMessage({ message, service, fn: _.find(service.functions, { sFnURL: topic }) }); + processMQTTMessage({ + message, + service, + fn: service.functions.find(fn => { + return fn.sFnURL === topic; + }) + }); }); //Прослушиваем отключение от сервера client.on("offline", () => { @@ -69,7 +72,6 @@ const subscribeMQTT = async ({ connectionPrms, service, processMQTTMessage, logg //Сообщим о восстановлении соединения logger.info(`Соединение с MQTT восстановлено (${sBroker})`); }); - //Возвращаем подключение return client; } catch (e) { diff --git a/core/out_queue.js b/core/out_queue.js index c15acf7..fcab507 100644 --- a/core/out_queue.js +++ b/core/out_queue.js @@ -71,9 +71,9 @@ class OutQueue extends EventEmitter { //Привяжем методы к указателю на себя для использования в обработчиках событий this.outDetectingLoop = this.outDetectingLoop.bind(this); //Параметры подключения к Kafka - this.kafkaConnectionPrms = _.cloneDeep(prms.kafkaConnectionPrms); + this.kafka = prms.kafka; //Параметры подключения к MQTT - this.mqttConnectionPrms = _.cloneDeep(prms.mqttConnectionPrms); + this.mqtt = prms.mqtt; } else { throw new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult); } @@ -164,8 +164,8 @@ class OutQueue extends EventEmitter { nId: prms.queue.nServiceFnId }), sProxy: this.sProxy, - kafkaConnectionPrms: this.kafkaConnectionPrms, - mqttConnectionPrms: this.mqttConnectionPrms + kafka: this.kafka, + mqtt: this.mqtt }); //Уменьшаем количество доступных обработчиков this.nWorkersLeft--; diff --git a/core/out_queue_processor.js b/core/out_queue_processor.js index 867ac5f..1383fb1 100644 --- a/core/out_queue_processor.js +++ b/core/out_queue_processor.js @@ -20,6 +20,8 @@ const { parseOptionsXML, buildOptionsXML, deepMerge, + getKafkaConnectionSettings, + getMQTTConnectionSettings, getKafkaBroker, getKafkaAuth, getURLProtocol @@ -31,6 +33,7 @@ const objQueueSchema = require("../models/obj_queue"); //Схемы валида const objServiceSchema = require("../models/obj_service"); //Схемы валидации сервиса const objServiceFnSchema = require("../models/obj_service_function"); //Схемы валидации функции сервиса const { + SERR_UNEXPECTED, SERR_OBJECT_BAD_INTERFACE, SERR_APP_SERVER_BEFORE, SERR_APP_SERVER_AFTER, @@ -151,20 +154,38 @@ const appProcess = async prms => { //Исходя из протокола собираем параметры switch (true) { //Kafka - case sProtocol === objServiceFnSchema.SPROTOCOL_KAFKA: + case sProtocol === objServiceSchema.SPROTOCOL_KAFKA: options.url = getKafkaBroker(prms.service.sSrvRoot); options.body = prms.queue.blMsg; options.topic = prms.function.sFnURL; options.auth = getKafkaAuth(prms.service.sSrvUser, prms.service.sSrvPass); + options.settings = getKafkaConnectionSettings(prms.service.sCode, prms.kafka); + //Если параметры подключения не считаны + if (!options.settings) { + //Расскажем об ошибке считывания + throw new ServerError( + SERR_UNEXPECTED, + `Ошибка получения настроек подключения к Kafka для сервиса "${prms.service.sCode}". Необходимо проверить соответствующий параметр ("kafka") файла конфигурации сервиса приложений ("config.js").` + ); + } //Указываем, что выполнение обработчика "После" невозможно bExecuteAfter = false; break; //mqtt и mqtts - case [objServiceFnSchema.SPROTOCOL_MQTT, objServiceFnSchema.SPROTOCOL_MQTTS].includes(sProtocol): + case [objServiceSchema.SPROTOCOL_MQTT, objServiceSchema.SPROTOCOL_MQTTS].includes(sProtocol): options.url = prms.service.sSrvRoot; options.body = prms.queue.blMsg; options.topic = prms.function.sFnURL; options.auth = { user: prms.service.sSrvUser, pass: prms.service.sSrvPass }; + options.settings = getMQTTConnectionSettings(prms.service.sCode, prms.mqtt); + //Если параметры подключения не считаны + if (!options.settings) { + //Расскажем об ошибке считывания + throw new ServerError( + SERR_UNEXPECTED, + `Ошибка получения настроек подключения к MQTT для сервиса "${prms.service.sCode}". Необходимо проверить соответствующий параметр ("mqtt") файла конфигурации сервиса приложений ("config.js").` + ); + } //Указываем, что выполнение обработчика "После" невозможно bExecuteAfter = false; break; @@ -245,11 +266,9 @@ const appProcess = async prms => { objServiceFnSchema.NFN_PRMS_TYPE_PATCH, objServiceFnSchema.NFN_PRMS_TYPE_PUT ].includes(prms.function.nFnPrmsType) || - [ - objServiceFnSchema.SPROTOCOL_KAFKA, - objServiceFnSchema.SPROTOCOL_MQTT, - objServiceFnSchema.SPROTOCOL_MQTTS - ].includes(sProtocol) + [objServiceSchema.SPROTOCOL_KAFKA, objServiceSchema.SPROTOCOL_MQTT, objServiceSchema.SPROTOCOL_MQTTS].includes( + sProtocol + ) ) { options.body = prms.queue.blMsg; } else { @@ -311,25 +330,25 @@ const appProcess = async prms => { nQueueId: prms.queue.nId }); } - //Ждем ответ от удалённого сервера - options.resolveWithFullResponse = true; + //Инициализируем ответ от сервера let serverResp = null; //Выполняем отправку исходя из протокола switch (true) { //Kafka - case sProtocol === objServiceFnSchema.SPROTOCOL_KAFKA: + case sProtocol === objServiceSchema.SPROTOCOL_KAFKA: serverResp = await publishKafka({ - connectionPrms: prms.kafkaConnectionPrms, + settings: options.settings, url: options.url, auth: options.auth, topic: options.topic, message: options.body }); + console.log(serverResp); break; //mqtt и mqtts - case [objServiceFnSchema.SPROTOCOL_MQTT, objServiceFnSchema.SPROTOCOL_MQTTS].includes(sProtocol): + case [objServiceSchema.SPROTOCOL_MQTT, objServiceSchema.SPROTOCOL_MQTTS].includes(sProtocol): serverResp = await publishMQTT({ - connectionPrms: prms.mqttConnectionPrms, + settings: options.settings, url: options.url, auth: options.auth, topic: options.topic, @@ -338,6 +357,9 @@ const appProcess = async prms => { break; //Другие default: + //Ждем ответ от удалённого сервера + options.resolveWithFullResponse = true; + //Отправляем запрос serverResp = await rqp(options); break; } @@ -593,8 +615,8 @@ const processTask = async prms => { service: prms.task.service, function: prms.task.function, sProxy: prms.task.sProxy, - kafkaConnectionPrms: prms.task.kafkaConnectionPrms, - mqttConnectionPrms: prms.task.mqttConnectionPrms + kafka: prms.task.kafka, + mqtt: prms.task.mqtt }); //Если результат обработки ошибка - пробрасываем её дальше if (res instanceof ServerError) { diff --git a/core/utils.js b/core/utils.js index 011023f..e4973d4 100644 --- a/core/utils.js +++ b/core/utils.js @@ -22,6 +22,7 @@ const { } = require("./constants"); //Глобавльные константы системы const { ServerError } = require("./server_errors"); //Ошибка сервера const prmsUtilsSchema = require("../models/prms_utils"); //Схемы валидации параметров функций +const { SPROTOCOL_HTTP, SPROTOCOL_KAFKA } = require("../models/obj_service"); //Схемы валидации сервиса //------------ // Тело модуля @@ -326,10 +327,49 @@ const deepMerge = (...args) => { return res; }; -//Получение брокера Kafka по адресу сервиса обмена +//Считывание параметров подключения для сервиса обмена (при service === "" считывание подключения "По умолчанию", settingsArray - массив объектов [{sService: "", ...},...]) +const getConnectionSettings = (service, settingsArray) => { + //Считываем параметры и возвращаем + return settingsArray.find(connection => { + return connection.sService === service; + }); +}; + +//Считывание параметров подключения к Kafka для сервиса обмена (kafka - массив объектов [{sService: "", ...},...]) +const getKafkaConnectionSettings = (service, kafka) => { + //Считываем подключение с указанным сервисом обмена + let kafkaConnection = getConnectionSettings(service, kafka); + //Если нет подключения с указанным сервисом обмена + if (!kafkaConnection) { + //Считываем "По умолчанию" + kafkaConnection = getConnectionSettings("", kafka); + } + //Вернем результат + return kafkaConnection; +}; + +//Считывание параметров подключения к MQTT для сервиса обмена (mqtt - массив объектов [{sService: "", ...},...]) +const getMQTTConnectionSettings = (service, mqtt) => { + //Считываем подключение с указанным сервисом обмена + let mqttConnection = getConnectionSettings(service, mqtt); + //Если нет подключения с указанным сервисом обмена + if (!mqttConnection) { + //Считываем "По умолчанию" + mqttConnection = getConnectionSettings("", mqtt); + } + //Вернем результат + return mqttConnection; +}; + +//Получение брокера Kafka по адресу сервиса обмена (прим. kafka://server.ru -> server.ru, https://server.ru => undefined) const getKafkaBroker = sURL => { - //Убираем лишние символы - return sURL.slice(8); + //Если протокол URL - Kafka + if (getURLProtocol(sURL) === SPROTOCOL_KAFKA) { + //Возвращаем брокера + return sURL.slice(8); + } + //Возвращаем undefined + return; }; //Получение авторизации для Kafka @@ -337,10 +377,10 @@ const getKafkaAuth = (sUser, sPass) => { return sUser ? { ssl: true, sasl: { mechanism: "plain", username: sUser, password: sPass } } : null; }; -//Получение протокола адреса +//Получение протокола адреса (прим. mqtt://server.ru -> mqtt, https://server.ru => https, ...) const getURLProtocol = sURL => { - //Считываем протокол адреса - return new URL(sURL).protocol.slice(0, -1); + //Если начинается с "/" - HTTP, иначе получаем из URL + return sURL.substring(0, 1) === "/" ? SPROTOCOL_HTTP : new URL(sURL).protocol.slice(0, -1); }; //----------------- @@ -361,6 +401,8 @@ exports.parseOptionsXML = parseOptionsXML; exports.buildOptionsXML = buildOptionsXML; exports.getNowString = getNowString; exports.deepMerge = deepMerge; +exports.getKafkaConnectionSettings = getKafkaConnectionSettings; +exports.getMQTTConnectionSettings = getMQTTConnectionSettings; exports.getKafkaBroker = getKafkaBroker; exports.getKafkaAuth = getKafkaAuth; exports.getURLProtocol = getURLProtocol; diff --git a/models/obj_service.js b/models/obj_service.js index 5d86db2..598c0fa 100644 --- a/models/obj_service.js +++ b/models/obj_service.js @@ -33,6 +33,13 @@ const NIS_AUTH_NO = 0; //Неаутентифицирован const SIS_AUTH_YES = "IS_AUTH_YES"; //Аутентифицирован (строковый код) const SIS_AUTH_NO = "IS_AUTH_NO"; //Неаутентифицирован (строковый код) +//Протоколы работы сервиса +const SPROTOCOL_HTTP = "http"; //Протокол HTTP +const SPROTOCOL_HTTPS = "https"; //Протокол HTTPS +const SPROTOCOL_MQTT = "mqtt"; //Протокол MQTT +const SPROTOCOL_MQTTS = "mqtts"; //Протокол MQTTS +const SPROTOCOL_KAFKA = "kafka"; //Протокол для работы с KAFKA + //------------- // Тело модуля //------------- @@ -59,6 +66,11 @@ exports.NIS_AUTH_YES = NIS_AUTH_YES; exports.NIS_AUTH_NO = NIS_AUTH_NO; exports.SIS_AUTH_YES = SIS_AUTH_YES; exports.SIS_AUTH_NO = SIS_AUTH_NO; +exports.SPROTOCOL_HTTP = SPROTOCOL_HTTP; +exports.SPROTOCOL_HTTPS = SPROTOCOL_HTTPS; +exports.SPROTOCOL_MQTT = SPROTOCOL_MQTT; +exports.SPROTOCOL_MQTTS = SPROTOCOL_MQTTS; +exports.SPROTOCOL_KAFKA = SPROTOCOL_KAFKA; //Схема валидации сервиса exports.Service = new Schema({ @@ -144,10 +156,8 @@ exports.Service = new Schema({ enum: [NUNAVLBL_NTF_SIGN_NO, NUNAVLBL_NTF_SIGN_YES], required: true, message: { - type: path => - `Признак необходимости оповещения о простое внешнего сервиса (${path}) имеет некорректный тип данных (ожидалось - Number)`, - enum: path => - `Значение признака необходимости оповещения о простое внешнего сервиса (${path}) не поддерживается`, + type: path => `Признак необходимости оповещения о простое внешнего сервиса (${path}) имеет некорректный тип данных (ожидалось - Number)`, + enum: path => `Значение признака необходимости оповещения о простое внешнего сервиса (${path}) не поддерживается`, required: path => `Не указан признак необходимости оповещения о простое внешнего сервиса (${path})` } }, @@ -159,10 +169,8 @@ exports.Service = new Schema({ message: { type: path => `Строковый код признака необходимости оповещения о простое внешнего сервиса (${path}) имеет некорректный тип данных (ожидалось - String)`, - enum: path => - `Значение строкового кода признака необходимости оповещения о простое внешнего сервиса (${path}) не поддерживается`, - required: path => - `Не указан строковый код признака необходимости оповещения о простое внешнего сервиса (${path})` + enum: path => `Значение строкового кода признака необходимости оповещения о простое внешнего сервиса (${path}) не поддерживается`, + required: path => `Не указан строковый код признака необходимости оповещения о простое внешнего сервиса (${path})` } }, //Максимальное время простоя (мин) удалённого сервиса для генерации оповещения @@ -172,8 +180,7 @@ exports.Service = new Schema({ message: { type: path => `Максимальное время простоя (мин) удалённого сервиса для генерации оповещения (${path}) имеет некорректный тип данных (ожидалось - Number)`, - required: path => - `Не указано максимальное время простоя (мин) удалённого сервиса для генерации оповещения (${path})` + required: path => `Не указано максимальное время простоя (мин) удалённого сервиса для генерации оповещения (${path})` } }, //Список адресов E-Mail для оповещения о простое внешнего сервиса @@ -194,8 +201,7 @@ exports.Service = new Schema({ type: String, required: false, message: { - type: path => - `Адрес прокси-сервера в очереди обмена (${path}) имеет некорректный тип данных (ожидалось - String)`, + type: path => `Адрес прокси-сервера в очереди обмена (${path}) имеет некорректный тип данных (ожидалось - String)`, required: path => `Не указан адрес прокси-сервера в очереди обмена (${path})` } }, @@ -237,8 +243,7 @@ exports.ServiceCtx = new Schema({ type: String, required: false, message: { - type: path => - `Строковое представление даты истечения контекста (${path}) имеет некорректный тип данных (ожидалось - String)`, + type: path => `Строковое представление даты истечения контекста (${path}) имеет некорректный тип данных (ожидалось - String)`, required: path => `Не указано строковое представление даты истечения контекста (${path})` } }, @@ -248,8 +253,7 @@ exports.ServiceCtx = new Schema({ enum: [NIS_AUTH_YES, NIS_AUTH_NO], required: true, message: { - type: path => - `Признака аутентицированности сервиса (${path}) имеет некорректный тип данных (ожидалось - Number)`, + type: path => `Признака аутентицированности сервиса (${path}) имеет некорректный тип данных (ожидалось - Number)`, enum: path => `Значение признака аутентицированности сервиса (${path}) не поддерживается`, required: path => `Не указан признак аутентицированности сервиса (${path})` } @@ -260,8 +264,7 @@ exports.ServiceCtx = new Schema({ enum: [SIS_AUTH_YES, SIS_AUTH_NO], required: true, message: { - type: path => - `Строковый код признака аутентицированности сервиса (${path}) имеет некорректный тип данных (ожидалось - String)`, + type: path => `Строковый код признака аутентицированности сервиса (${path}) имеет некорректный тип данных (ожидалось - String)`, enum: path => `Значение строкового кода признака аутентицированности сервиса (${path}) не поддерживается`, required: path => `Не указан строковый код признака аутентицированности сервиса (${path})` } @@ -284,8 +287,7 @@ exports.ServiceExpiredQueueInfo = new Schema({ type: Number, required: true, message: { - type: path => - `Количество просроченных сообщений обмена (${path}) имеет некорректный тип данных (ожидалось - Number)`, + type: path => `Количество просроченных сообщений обмена (${path}) имеет некорректный тип данных (ожидалось - Number)`, required: path => `Не указано количество просроченных сообщений обмена (${path})` } }, @@ -294,8 +296,7 @@ exports.ServiceExpiredQueueInfo = new Schema({ type: String, required: true, message: { - type: path => - `Информация о просроченных сообщениях обмена (${path}) имеет некорректный тип данных (ожидалось - String)`, + type: path => `Информация о просроченных сообщениях обмена (${path}) имеет некорректный тип данных (ожидалось - String)`, required: path => `Не указана информация о просроченных сообщениях обмена (${path})` } } diff --git a/models/obj_service_function.js b/models/obj_service_function.js index 0be959d..a8e24c3 100644 --- a/models/obj_service_function.js +++ b/models/obj_service_function.js @@ -70,13 +70,6 @@ const NERR_NTF_SIGN_YES = 1; //Оповещать об ошибке исполн const SERR_NTF_SIGN_NO = "ERR_NTF_SIGN_NO"; //Не оповещать об ошибке исполнения (строковый код) const SERR_NTF_SIGN_YES = "ERR_NTF_SIGN_YES"; //Оповещать об ошибке исполнения (строковый код) -//Протоколы работы сервиса -const SPROTOCOL_HTTP = "http"; //Протокол HTTP -const SPROTOCOL_HTTPS = "https"; //Протокол HTTPS -const SPROTOCOL_MQTT = "mqtt"; //Протокол MQTT -const SPROTOCOL_MQTTS = "mqtts"; //Протокол MQTTS -const SPROTOCOL_KAFKA = "kafka"; //Протокол для работы с KAFKA - //------------- // Тело модуля //------------- @@ -146,11 +139,6 @@ exports.NERR_NTF_SIGN_NO = NERR_NTF_SIGN_NO; exports.NERR_NTF_SIGN_YES = NERR_NTF_SIGN_YES; exports.SERR_NTF_SIGN_NO = SERR_NTF_SIGN_NO; exports.SERR_NTF_SIGN_YES = SERR_NTF_SIGN_YES; -exports.SPROTOCOL_HTTP = SPROTOCOL_HTTP; -exports.SPROTOCOL_HTTPS = SPROTOCOL_HTTPS; -exports.SPROTOCOL_MQTT = SPROTOCOL_MQTT; -exports.SPROTOCOL_MQTTS = SPROTOCOL_MQTTS; -exports.SPROTOCOL_KAFKA = SPROTOCOL_KAFKA; //Схема валидации функции сервиса exports.ServiceFunction = new Schema({