174 lines
7.8 KiB
JavaScript
174 lines
7.8 KiB
JavaScript
/*
|
||
Сервис интеграции ПП Парус 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;
|