174 lines
7.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
Сервис интеграции ПП Парус 8 с WEB API
Дополнительный модуль: Parus WEB Service (PWS)
*/
//------------------------------
// Подключение внешних библиотек
//------------------------------
const xml2js = require("xml2js"); //Конвертация XML в JSON и JSON в XML
const { deepClone } = require("../core/utils"); //Вспомогательные функции
//---------------------
// Глобальные константы
//---------------------
//Наименования специальных управляющих атрибутов XML
const SJSON_CONTROL_ATTR_ARRAY = "___array___"; //Управляющий атрибут для указания параметров конвертации массива
//Поля заголовка сообщения
const SHEADER_REQ_CONTENT_TYPE_JSON = "application/json"; //Значение "content-type" для JSON-запроса
const SHEADER_RESP_CONTENT_TYPE_JSON = "application/json;charset=utf-8"; //Значение "content-type" для JSON-ответа
//Поля запроса
const SQUERY_RESP_CT = "SRESP_CT"; //Параметр URL-запроса для принудительного указания типа возвращаемых данных
//Коды функций-обработчиков (согласно настройкам "Сервисов обмена")
const SFNC_UPLOAD = "Upload"; //Функция загрузки
//Имена узлов/аргументов PWS-запроса
const SNODE_XREQUEST = "XREQUEST"; //Корневой узел запроса
const SNODE_XPAYLOAD = "XPAYLOAD"; //Полезная нагрузка запроса
const SNODE_XARGUMENTS = "XARGUMENTS"; //Коллекция аргументов
const SNODE_XARGUMENT = "XARGUMENT"; //Элемент аргумента
const SNODE_SNAME = "SNAME"; //Имя аргумента
const SNODE_VALUE = "VALUE"; //Значение аргумента
const SARG_SSEARCH = "SSEARCH"; //Аргумент строки поиска
//------------
// Тело модуля
//------------
//Разбор XML
const parseXML = xmlDoc => {
return new Promise((resolve, reject) => {
xml2js.parseString(xmlDoc, { explicitArray: false, mergeAttrs: true }, (err, result) => {
if (err) reject(err);
else resolve(result);
});
});
};
//Дополнительная конвертация выходного JSON - корректное преобразование массивов
const converXMLArraysToJSON = (obj, arrayKey) => {
for (key in obj) {
if (obj[key][arrayKey]) {
let tmp = [];
let itemKey = obj[key][arrayKey];
if (obj[key][itemKey]) {
if (Array.isArray(obj[key][itemKey])) {
for (let i = 0; i < obj[key][itemKey].length; i++) {
let buf = {};
buf[itemKey] = deepClone(obj[key][itemKey][i]);
tmp.push(buf);
}
} else {
let buf = {};
buf[itemKey] = deepClone(obj[key][itemKey]);
tmp.push(buf);
}
}
obj[key] = tmp;
converXMLArraysToJSON(obj[key], arrayKey);
} else {
if (typeof obj[key] === "object" && obj[key] !== null && !Array.isArray(obj[key])) converXMLArraysToJSON(obj[key], arrayKey);
if (Array.isArray(obj[key])) for (let i = 0; i < obj[key].length; i++) converXMLArraysToJSON(obj[key][i], arrayKey);
}
}
};
//Кодирование строки в Base64 (UTF-8)
const encodeBase64Utf8 = sValue => Buffer.from(sValue, "utf8").toString("base64");
//Получение полезной нагрузки запроса
const getRequestPayload = request => {
if (!request || typeof request !== "object") return null;
const xRequest = request[SNODE_XREQUEST];
if (!xRequest || typeof xRequest !== "object") return null;
const xPayload = xRequest[SNODE_XPAYLOAD];
if (!xPayload || typeof xPayload !== "object") return null;
return xPayload;
};
//Подготовка JSON-запроса перед конвертацией в XML
const prepareRequestForXml = request => {
//Получим полезную нагрузку запроса
const xPayload = getRequestPayload(request);
if (!xPayload) return request;
//Получим коллекцию аргументов
const xArguments = xPayload[SNODE_XARGUMENTS];
if (!Array.isArray(xArguments)) return request;
//Обработаем аргументы
for (let i = 0; i < xArguments.length; i++) {
const item = xArguments[i];
const arg = item && (item[SNODE_XARGUMENT] || item);
if (!arg || typeof arg !== "object" || arg[SNODE_SNAME] !== SARG_SSEARCH) continue;
if (arg[SNODE_VALUE] === undefined || arg[SNODE_VALUE] === null) continue;
arg[SNODE_VALUE] = encodeBase64Utf8(arg[SNODE_VALUE]);
}
return request;
};
//Обработчик "До" для полученного сообщения
const before = async prms => {
//Если пришел запрос в JSON
if (prms.options.headers["content-type"] && prms.options.headers["content-type"].startsWith(SHEADER_REQ_CONTENT_TYPE_JSON)) {
//Конвертируем полученный в JSON-запрос в XML, понятный серверной части
let requestXML = "";
try {
let request = JSON.parse(prms.queue.blMsg.toString());
request = prepareRequestForXml(request);
let builder = new xml2js.Builder();
requestXML = builder.buildObject(request);
} catch (e) {
requestXML = "";
}
//Возвращаем отконвертированное в качестве тела запроса
return {
blMsg: Buffer.from(requestXML)
};
}
};
//Обработчик "После" для полученного сообщения
const after = async prms => {
//Если пришел запрос в JSON или просили ответ в JSON
if (
(prms.function.sCode === SFNC_UPLOAD &&
prms.options.qs &&
prms.options.qs[SQUERY_RESP_CT] &&
prms.options.qs[SQUERY_RESP_CT].startsWith(SHEADER_REQ_CONTENT_TYPE_JSON)) ||
(prms.function.sCode != SFNC_UPLOAD &&
((prms.options.headers["content-type"] && prms.options.headers["content-type"].startsWith(SHEADER_REQ_CONTENT_TYPE_JSON)) ||
(prms.options.qs && prms.options.qs[SQUERY_RESP_CT] && prms.options.qs[SQUERY_RESP_CT].startsWith(SHEADER_REQ_CONTENT_TYPE_JSON))))
) {
//Буфер для конвертации
let parseRes = "";
try {
//Конвертируем ответ, подготовленный сервером, в JSON
parseRes = await parseXML(prms.queue.blResp.toString());
//Доработаем полученный JSON - корректно конвертируем массивы
converXMLArraysToJSON(parseRes, SJSON_CONTROL_ATTR_ARRAY);
} catch (e) {
//Любые ошибки - игнорируем (если не смогли конвертировать будем отдавать данные "как есть")
}
//Вернём его клиенту в таком виде
return {
optionsResp: {
headers: {
"content-type": SHEADER_RESP_CONTENT_TYPE_JSON
}
},
blResp: Buffer.from(parseRes ? JSON.stringify(parseRes) : prms.queue.blResp)
};
}
};
//-----------------
// Интерфейс модуля
//-----------------
exports.before = before;
exports.after = after;