217 lines
8.7 KiB
JavaScript
217 lines
8.7 KiB
JavaScript
/*
|
||
Парус 8 - Панели мониторинга
|
||
Ядро: Клиент для взаимодействия с сервером приложений "Парус 8 Онлайн"
|
||
*/
|
||
|
||
//---------------------
|
||
//Подключение библиотек
|
||
//---------------------
|
||
|
||
import { XMLParser, XMLBuilder } from "fast-xml-parser"; //Конвертация XML в JSON и JSON в XML
|
||
import dayjs from "dayjs"; //Работа с датами
|
||
import { SYSTEM } from "../../app.config"; //Настройки приложения
|
||
|
||
//---------
|
||
//Константы
|
||
//---------
|
||
|
||
//Коды функций сервера
|
||
const SRV_FN_CODE_EXEC_STORED = "EXEC_STORED"; //Код функции сервера "Запуск хранимой процедуры"
|
||
|
||
//Типы данных сервера
|
||
const SERV_DATA_TYPE_STR = "STR"; //Тип данных "строка"
|
||
const SERV_DATA_TYPE_NUMB = "NUMB"; //Тип данных "число"
|
||
const SERV_DATA_TYPE_DATE = "DATE"; //Тип данных "дата"
|
||
const SERV_DATA_TYPE_CLOB = "CLOB"; //Тип данных "текст"
|
||
|
||
//Состояния ответов сервера
|
||
const RESP_STATUS_OK = "OK"; //Успех
|
||
const RESP_STATUS_ERR = "ERR"; //Ошибка
|
||
|
||
//Типовые ошибки клиента
|
||
const ERR_APPSERVER = "Ошибка сервера приложений"; //Общая ошибка клиента
|
||
const ERR_UNEXPECTED = "Неожиданный ответ сервера"; //Неожиданный ответ сервера
|
||
const ERR_NETWORK = "Ошибка соединения с сервером"; //Ошибка сети
|
||
|
||
//Типовые пути конвертации в массив (при переводе XML -> JSON)
|
||
const XML_ALWAYS_ARRAY_PATHS = [
|
||
"XRESPOND.XPAYLOAD.XOUT_ARGUMENTS",
|
||
"XRESPOND.XPAYLOAD.XROWS",
|
||
"XRESPOND.XPAYLOAD.XCOLUMNS_DEF",
|
||
"XRESPOND.XPAYLOAD.XCOLUMNS_DEF.values"
|
||
];
|
||
|
||
//Типовой постфикс тега для массива (при переводе XML -> JSON)
|
||
const XML_ALWAYS_ARRAY_POSTFIX = "__SYSTEM__ARRAY__";
|
||
|
||
//-----------
|
||
//Тело модуля
|
||
//-----------
|
||
|
||
//Определение типа данных значения аргумента
|
||
const getServerDataType = value => {
|
||
let res = SERV_DATA_TYPE_STR;
|
||
if (typeof value == "number") res = SERV_DATA_TYPE_NUMB;
|
||
if (value instanceof Date) res = SERV_DATA_TYPE_DATE;
|
||
return res;
|
||
};
|
||
|
||
//Формирование стандартного ответа - ошибка
|
||
const makeRespErr = ({ message }) => ({ SSTATUS: RESP_STATUS_ERR, SMESSAGE: message });
|
||
|
||
//Разбор XML
|
||
const parseXML = (xmlDoc, isArray, transformTagName) => {
|
||
return new Promise((resolve, reject) => {
|
||
try {
|
||
let opts = {
|
||
ignoreDeclaration: true,
|
||
ignoreAttributes: false,
|
||
parseAttributeValue: true,
|
||
attributeNamePrefix: ""
|
||
};
|
||
if (isArray) opts.isArray = isArray;
|
||
if (transformTagName) opts.transformTagName = transformTagName;
|
||
const parser = new XMLParser(opts);
|
||
resolve(parser.parse(xmlDoc));
|
||
} catch (e) {
|
||
reject(e);
|
||
}
|
||
});
|
||
};
|
||
|
||
//Формирование XML
|
||
const buildXML = jsonObj => {
|
||
return new Promise((resolve, reject) => {
|
||
try {
|
||
const builder = new XMLBuilder({ ignoreAttributes: false, oneListGroup: true });
|
||
resolve(builder.build(jsonObj));
|
||
} catch (e) {
|
||
reject(e);
|
||
}
|
||
});
|
||
};
|
||
|
||
//Проверка ответа на наличие ошибки
|
||
const isRespErr = resp => resp && resp?.SSTATUS && resp?.SSTATUS === RESP_STATUS_ERR;
|
||
|
||
//Извлечение ошибки из ответа
|
||
const getRespErrMessage = resp => (isRespErr(resp) && resp.SMESSAGE ? resp.SMESSAGE : "");
|
||
|
||
//Извлечение полезного содержимого из ответа
|
||
const getRespPayload = resp => (resp && resp.XPAYLOAD ? resp.XPAYLOAD : null);
|
||
|
||
//Исполнение действия на сервере
|
||
const executeAction = async ({ serverURL, action, payload = {}, isArray, transformTagName } = {}) => {
|
||
console.log(`EXECUTING ${action ? action : ""} ON ${serverURL} WITH PAYLOAD:`);
|
||
console.log(payload ? payload : "NO PAYLOAD");
|
||
let response = null;
|
||
let responseJSON = null;
|
||
try {
|
||
//Сформируем типовой запрос
|
||
const rqBody = {
|
||
XREQUEST: { SACTION: action, XPAYLOAD: payload }
|
||
};
|
||
//Выполняем запрос
|
||
response = await fetch(serverURL, {
|
||
method: "POST",
|
||
body: await buildXML(rqBody),
|
||
headers: {
|
||
"content-type": "application/xml"
|
||
}
|
||
});
|
||
} catch (e) {
|
||
//Сетевая ошибка
|
||
throw new Error(`${ERR_NETWORK}: ${e.message}`);
|
||
}
|
||
//Проверим на наличие ошибок HTTP - если есть вернём их
|
||
if (!response.ok) throw new Error(`${ERR_APPSERVER}: ${response.statusText}`);
|
||
//Ошибок нет - пробуем разобрать
|
||
try {
|
||
let responseText = await response.text();
|
||
//console.log("SERVER RESPONSE TEXT:");
|
||
//console.log(responseText);
|
||
responseJSON = await parseXML(responseText, isArray, transformTagName);
|
||
} catch (e) {
|
||
//Что-то пошло не так при парсинге
|
||
throw new Error(ERR_UNEXPECTED);
|
||
}
|
||
//Разобрали, проверяем структуру ответа на обязательные атрибуты
|
||
if (
|
||
!responseJSON?.XRESPOND ||
|
||
!responseJSON?.XRESPOND?.SSTATUS ||
|
||
![RESP_STATUS_ERR, RESP_STATUS_OK].includes(responseJSON?.XRESPOND?.SSTATUS) ||
|
||
(responseJSON?.XRESPOND?.SSTATUS === RESP_STATUS_OK && responseJSON?.XRESPOND?.XPAYLOAD == undefined) ||
|
||
(responseJSON?.XRESPOND?.SSTATUS === RESP_STATUS_ERR && responseJSON?.XRESPOND?.SMESSAGE == undefined)
|
||
)
|
||
throw new Error(ERR_UNEXPECTED);
|
||
//Всё хорошо - возвращаем (без корня, он не нужен)
|
||
console.log("SERVER RESPONSE JSON:");
|
||
console.log(responseJSON.XRESPOND);
|
||
return responseJSON.XRESPOND;
|
||
};
|
||
|
||
//Запуск хранимой процедуры
|
||
const executeStored = async ({ stored, args, respArg, throwError = true, spreadOutArguments = false } = {}) => {
|
||
let res = null;
|
||
try {
|
||
let serverArgs = [];
|
||
if (args)
|
||
for (const arg in args) {
|
||
let typedArg = false;
|
||
if (Object.hasOwn(args[arg], "VALUE") && Object.hasOwn(args[arg], "SDATA_TYPE") && args[arg]?.SDATA_TYPE) typedArg = true;
|
||
const dataType = typedArg ? args[arg].SDATA_TYPE : getServerDataType(args[arg]);
|
||
let value = typedArg ? args[arg].VALUE : args[arg];
|
||
if (dataType === SERV_DATA_TYPE_DATE) value = dayjs(value).format("YYYY-MM-DDTHH:mm:ss");
|
||
serverArgs.push({ XARGUMENT: { SNAME: arg, VALUE: value, SDATA_TYPE: dataType } });
|
||
}
|
||
res = await executeAction({
|
||
serverURL: `${SYSTEM.SERVER}${!SYSTEM.SERVER.endsWith("/") ? "/" : ""}Process`,
|
||
action: SRV_FN_CODE_EXEC_STORED,
|
||
payload: { SSTORED: stored, XARGUMENTS: serverArgs, SRESP_ARG: respArg },
|
||
isArray: (name, jPath) => XML_ALWAYS_ARRAY_PATHS.indexOf(jPath) !== -1 || jPath.endsWith(XML_ALWAYS_ARRAY_POSTFIX)
|
||
});
|
||
if (spreadOutArguments === true && Array.isArray(res?.XPAYLOAD?.XOUT_ARGUMENTS)) {
|
||
let spreadArgs = {};
|
||
for (let arg of res.XPAYLOAD.XOUT_ARGUMENTS) spreadArgs[arg.SNAME] = arg.VALUE;
|
||
delete res.XPAYLOAD.XOUT_ARGUMENTS;
|
||
res.XPAYLOAD = { ...res.XPAYLOAD, ...spreadArgs };
|
||
}
|
||
} catch (e) {
|
||
if (throwError) throw e;
|
||
else return makeRespErr({ message: e.message });
|
||
}
|
||
if (res.SSTATUS === RESP_STATUS_ERR && throwError === true) throw new Error(res.SMESSAGE);
|
||
return res;
|
||
};
|
||
|
||
//Чтение конфигурации плагина
|
||
const getConfig = async ({ throwError = true } = {}) => {
|
||
let res = null;
|
||
try {
|
||
res = await executeAction({
|
||
serverURL: `${SYSTEM.SERVER}${!SYSTEM.SERVER.endsWith("/") ? "/" : ""}GetConfig`
|
||
});
|
||
} catch (e) {
|
||
if (throwError) throw e;
|
||
else return makeRespErr({ message: e.message });
|
||
}
|
||
if (res.SSTATUS === RESP_STATUS_ERR && throwError === true) throw new Error(res.SMESSAGE);
|
||
return res;
|
||
};
|
||
|
||
//----------------
|
||
//Интерфейс модуля
|
||
//----------------
|
||
|
||
export default {
|
||
SERV_DATA_TYPE_STR,
|
||
SERV_DATA_TYPE_NUMB,
|
||
SERV_DATA_TYPE_DATE,
|
||
SERV_DATA_TYPE_CLOB,
|
||
isRespErr,
|
||
getRespErrMessage,
|
||
getRespPayload,
|
||
executeStored,
|
||
getConfig
|
||
};
|