Изменена работа с прокси/tls в модуле http_client, теперь используются модули http, https и net вместо undici. Добавлена инициализация thick-режима при работе с базой данных Oracle для расширения "Интеграция с ГАР". Исправлены ошибки модуля обработки входящих сообщений (in_queue).
This commit is contained in:
parent
97cb8516c3
commit
19a7023291
@ -7,7 +7,10 @@
|
|||||||
// Подключение внешних библиотек
|
// Подключение внешних библиотек
|
||||||
//------------------------------
|
//------------------------------
|
||||||
|
|
||||||
|
const http = require("http");
|
||||||
|
const https = require("https");
|
||||||
const { URL } = require("url");
|
const { URL } = require("url");
|
||||||
|
const { Socket } = require("net");
|
||||||
|
|
||||||
//--------------------------
|
//--------------------------
|
||||||
// Локальные идентификаторы
|
// Локальные идентификаторы
|
||||||
@ -15,6 +18,362 @@ const { URL } = require("url");
|
|||||||
|
|
||||||
const DEFAULT_TIMEOUT = 30000; //Таймаут по умолчанию
|
const DEFAULT_TIMEOUT = 30000; //Таймаут по умолчанию
|
||||||
|
|
||||||
|
//Ошибка HTTP-запроса
|
||||||
|
class HttpError extends Error {
|
||||||
|
constructor(message, response) {
|
||||||
|
super(message);
|
||||||
|
this.name = "HttpError";
|
||||||
|
this.response = response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Обработка ответа (парсинг JSON, форматирование для совместимости с request-promise)
|
||||||
|
const processResponse = (result, options) => {
|
||||||
|
let processedBody = result.body;
|
||||||
|
|
||||||
|
//Если запрошен автоматический парсинг JSON
|
||||||
|
if (options.json === true && processedBody) {
|
||||||
|
try {
|
||||||
|
const text = processedBody.toString(options.encoding || "utf8");
|
||||||
|
processedBody = text ? JSON.parse(text) : null;
|
||||||
|
} catch (e) {
|
||||||
|
//Если не удалось распарсить JSON, возвращаем как есть
|
||||||
|
processedBody = processedBody.toString(options.encoding || "utf8");
|
||||||
|
}
|
||||||
|
} else if (options.encoding && processedBody) {
|
||||||
|
//Если указана кодировка, конвертируем в строку
|
||||||
|
processedBody = processedBody.toString(options.encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Формируем результат в зависимости от resolveWithFullResponse
|
||||||
|
if (options.resolveWithFullResponse) {
|
||||||
|
return {
|
||||||
|
statusCode: result.statusCode,
|
||||||
|
statusMessage: result.statusMessage || "",
|
||||||
|
headers: result.headers,
|
||||||
|
body: processedBody,
|
||||||
|
url: result.url
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
//Если json: true, возвращаем распарсенный объект, иначе Buffer/string
|
||||||
|
return processedBody;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//Выполнение HTTP/HTTPS запроса через встроенные модули (для прокси и TLS)
|
||||||
|
const httpRequestNative = (options, url, headers, body) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const urlObj = new URL(url);
|
||||||
|
const isHttps = urlObj.protocol === "https:";
|
||||||
|
const httpModule = isHttps ? https : http;
|
||||||
|
|
||||||
|
//Настройки TLS
|
||||||
|
const tlsOptions = {};
|
||||||
|
if (options.ca) {
|
||||||
|
tlsOptions.ca = Array.isArray(options.ca) ? options.ca : [options.ca];
|
||||||
|
}
|
||||||
|
if (options.cert) {
|
||||||
|
tlsOptions.cert = options.cert;
|
||||||
|
}
|
||||||
|
if (options.key) {
|
||||||
|
tlsOptions.key = options.key;
|
||||||
|
}
|
||||||
|
if (options.passphrase) {
|
||||||
|
tlsOptions.passphrase = options.passphrase;
|
||||||
|
}
|
||||||
|
if (options.rejectUnauthorized !== undefined) {
|
||||||
|
tlsOptions.rejectUnauthorized = options.rejectUnauthorized;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Настройки прокси
|
||||||
|
let proxyUrl = null;
|
||||||
|
let proxyAuth = null;
|
||||||
|
if (options.proxy) {
|
||||||
|
try {
|
||||||
|
proxyUrl = new URL(options.proxy);
|
||||||
|
if (proxyUrl.username || proxyUrl.password) {
|
||||||
|
proxyAuth = `Basic ${Buffer.from(`${decodeURIComponent(proxyUrl.username || "")}:${decodeURIComponent(proxyUrl.password || "")}`).toString("base64")}`;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return reject(new Error(`Некорректный URL прокси: ${e.message}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Параметры запроса
|
||||||
|
const requestOptions = {
|
||||||
|
hostname: proxyUrl ? proxyUrl.hostname : urlObj.hostname,
|
||||||
|
port: proxyUrl ? proxyUrl.port || (proxyUrl.protocol === "https:" ? 443 : 80) : urlObj.port || (isHttps ? 443 : 80),
|
||||||
|
path: proxyUrl ? url : urlObj.pathname + urlObj.search,
|
||||||
|
method: options.method,
|
||||||
|
headers: Object.fromEntries(headers),
|
||||||
|
timeout: options.timeout || DEFAULT_TIMEOUT
|
||||||
|
};
|
||||||
|
|
||||||
|
//Добавляем авторизацию прокси в заголовки
|
||||||
|
if (proxyAuth) {
|
||||||
|
requestOptions.headers["Proxy-Authorization"] = proxyAuth;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Для HTTPS добавляем TLS опции
|
||||||
|
if (isHttps && Object.keys(tlsOptions).length > 0) {
|
||||||
|
Object.assign(requestOptions, tlsOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Если используется прокси, нужно использовать CONNECT метод для HTTPS
|
||||||
|
if (proxyUrl && isHttps) {
|
||||||
|
//Для HTTPS через прокси используем туннелирование
|
||||||
|
return httpRequestThroughProxy(requestOptions, urlObj, headers, body, proxyUrl, proxyAuth, tlsOptions, options)
|
||||||
|
.then(result => resolve(processResponse(result, options)))
|
||||||
|
.catch(reject);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Обычный запрос (с прокси для HTTP или без прокси)
|
||||||
|
const req = httpModule.request(requestOptions, res => {
|
||||||
|
const chunks = [];
|
||||||
|
res.on("data", chunk => {
|
||||||
|
chunks.push(chunk);
|
||||||
|
});
|
||||||
|
res.on("end", () => {
|
||||||
|
const responseBody = Buffer.concat(chunks);
|
||||||
|
const result = {
|
||||||
|
statusCode: res.statusCode,
|
||||||
|
statusMessage: res.statusMessage || "",
|
||||||
|
headers: res.headers,
|
||||||
|
body: responseBody,
|
||||||
|
ok: res.statusCode >= 200 && res.statusCode < 300,
|
||||||
|
url: url
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options.throwOnErrorStatus && !result.ok) {
|
||||||
|
const error = new HttpError(`Запрос не выполнен со статусом ${result.statusCode}`, result);
|
||||||
|
const httpError = new Error(error.message);
|
||||||
|
httpError.response = result;
|
||||||
|
httpError.statusCode = result.statusCode;
|
||||||
|
return reject(httpError);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(processResponse(result, options));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on("error", err => {
|
||||||
|
const error = new Error(err.message);
|
||||||
|
error.code = err.code;
|
||||||
|
error.error = { code: err.code };
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on("timeout", () => {
|
||||||
|
req.destroy();
|
||||||
|
const timeoutError = new Error(`Время ожидания выполнения запроса истекло после ${options.timeout || DEFAULT_TIMEOUT} мс`);
|
||||||
|
timeoutError.code = "ETIMEDOUT";
|
||||||
|
timeoutError.error = { code: "ETIMEDOUT" };
|
||||||
|
reject(timeoutError);
|
||||||
|
});
|
||||||
|
|
||||||
|
//Отправляем тело запроса
|
||||||
|
if (body) {
|
||||||
|
if (Buffer.isBuffer(body)) {
|
||||||
|
req.write(body);
|
||||||
|
} else if (typeof body === "string") {
|
||||||
|
req.write(Buffer.from(body));
|
||||||
|
} else {
|
||||||
|
req.write(Buffer.from(JSON.stringify(body)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//Выполнение HTTPS запроса через HTTP прокси (туннелирование)
|
||||||
|
const httpRequestThroughProxy = (requestOptions, targetUrl, headers, body, proxyUrl, proxyAuth, tlsOptions, options) => {
|
||||||
|
const timeout = options.timeout || DEFAULT_TIMEOUT;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const proxyHost = proxyUrl.hostname;
|
||||||
|
const proxyPort = parseInt(proxyUrl.port || (proxyUrl.protocol === "https:" ? 443 : 80), 10);
|
||||||
|
const targetHost = targetUrl.hostname;
|
||||||
|
const targetPort = parseInt(targetUrl.port || 443, 10);
|
||||||
|
|
||||||
|
//Создаем соединение с прокси
|
||||||
|
const proxySocket = new Socket();
|
||||||
|
let connected = false;
|
||||||
|
let requestSent = false;
|
||||||
|
|
||||||
|
const connectTimeout = setTimeout(() => {
|
||||||
|
if (!connected || !requestSent) {
|
||||||
|
proxySocket.destroy();
|
||||||
|
reject(new Error(`Время ожидания подключения к прокси истекло после ${timeout} мс`));
|
||||||
|
}
|
||||||
|
}, timeout);
|
||||||
|
|
||||||
|
proxySocket.setTimeout(timeout);
|
||||||
|
proxySocket.on("timeout", () => {
|
||||||
|
proxySocket.destroy();
|
||||||
|
reject(new Error(`Время ожидания выполнения запроса истекло после ${timeout} мс`));
|
||||||
|
});
|
||||||
|
|
||||||
|
proxySocket.connect(proxyPort, proxyHost, () => {
|
||||||
|
connected = true;
|
||||||
|
|
||||||
|
//Отправляем CONNECT запрос
|
||||||
|
const connectRequest = `CONNECT ${targetHost}:${targetPort} HTTP/1.1\r\nHost: ${targetHost}:${targetPort}\r\n`;
|
||||||
|
const authHeader = proxyAuth ? `Proxy-Authorization: ${proxyAuth}\r\n` : "";
|
||||||
|
const connectHeaders = connectRequest + authHeader + "\r\n";
|
||||||
|
|
||||||
|
proxySocket.write(connectHeaders);
|
||||||
|
|
||||||
|
let connectResponse = "";
|
||||||
|
const onConnectData = chunk => {
|
||||||
|
connectResponse += chunk.toString();
|
||||||
|
const headerEnd = connectResponse.indexOf("\r\n\r\n");
|
||||||
|
if (headerEnd !== -1) {
|
||||||
|
proxySocket.removeListener("data", onConnectData);
|
||||||
|
clearTimeout(connectTimeout);
|
||||||
|
|
||||||
|
//Проверяем ответ на CONNECT
|
||||||
|
const statusLine = connectResponse.substring(0, connectResponse.indexOf("\r\n"));
|
||||||
|
if (statusLine.includes("200")) {
|
||||||
|
//Успешное подключение, создаем TLS соединение
|
||||||
|
const tls = require("tls");
|
||||||
|
const secureSocket = tls.connect(
|
||||||
|
{
|
||||||
|
socket: proxySocket,
|
||||||
|
host: targetHost,
|
||||||
|
servername: targetHost,
|
||||||
|
...tlsOptions
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
requestSent = true;
|
||||||
|
clearTimeout(connectTimeout);
|
||||||
|
|
||||||
|
//Отправляем HTTP запрос через TLS
|
||||||
|
const path = targetUrl.pathname + targetUrl.search;
|
||||||
|
const requestLine = `${requestOptions.method} ${path} HTTP/1.1\r\n`;
|
||||||
|
const hostHeader = `Host: ${targetHost}${targetPort !== 443 ? `:${targetPort}` : ""}\r\n`;
|
||||||
|
const requestHeaders = Array.from(headers.entries())
|
||||||
|
.map(([key, value]) => `${key}: ${value}\r\n`)
|
||||||
|
.join("");
|
||||||
|
const httpRequest = requestLine + hostHeader + requestHeaders + "\r\n";
|
||||||
|
|
||||||
|
secureSocket.write(httpRequest);
|
||||||
|
|
||||||
|
if (body) {
|
||||||
|
const bodyBuffer = Buffer.isBuffer(body)
|
||||||
|
? body
|
||||||
|
: typeof body === "string"
|
||||||
|
? Buffer.from(body)
|
||||||
|
: Buffer.from(JSON.stringify(body));
|
||||||
|
secureSocket.write(bodyBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Читаем ответ
|
||||||
|
let responseBuffer = Buffer.alloc(0);
|
||||||
|
let responseHeaders = {};
|
||||||
|
let statusCode = 200;
|
||||||
|
let headerParsed = false;
|
||||||
|
let contentLength = -1;
|
||||||
|
|
||||||
|
const onSecureData = chunk => {
|
||||||
|
responseBuffer = Buffer.concat([responseBuffer, chunk]);
|
||||||
|
|
||||||
|
if (!headerParsed) {
|
||||||
|
const headerEnd = responseBuffer.indexOf("\r\n\r\n");
|
||||||
|
if (headerEnd !== -1) {
|
||||||
|
const headerText = responseBuffer.subarray(0, headerEnd).toString();
|
||||||
|
const bodyStart = headerEnd + 4;
|
||||||
|
responseBuffer = responseBuffer.subarray(bodyStart);
|
||||||
|
|
||||||
|
//Парсим заголовки
|
||||||
|
const lines = headerText.split("\r\n");
|
||||||
|
const statusLine = lines[0];
|
||||||
|
const statusMatch = statusLine.match(/HTTP\/[\d.]+\s+(\d+)/);
|
||||||
|
if (statusMatch) {
|
||||||
|
statusCode = parseInt(statusMatch[1], 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i < lines.length; i++) {
|
||||||
|
const line = lines[i];
|
||||||
|
const colonIndex = line.indexOf(":");
|
||||||
|
if (colonIndex !== -1) {
|
||||||
|
const key = line.substring(0, colonIndex).trim().toLowerCase();
|
||||||
|
const value = line.substring(colonIndex + 1).trim();
|
||||||
|
responseHeaders[key] = value;
|
||||||
|
if (key === "content-length") {
|
||||||
|
contentLength = parseInt(value, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
headerParsed = true;
|
||||||
|
|
||||||
|
//Если есть Content-Length и мы уже получили все данные
|
||||||
|
if (contentLength >= 0 && responseBuffer.length >= contentLength) {
|
||||||
|
secureSocket.removeListener("data", onSecureData);
|
||||||
|
secureSocket.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Проверяем, получили ли мы все данные
|
||||||
|
if (contentLength >= 0 && responseBuffer.length >= contentLength) {
|
||||||
|
secureSocket.removeListener("data", onSecureData);
|
||||||
|
secureSocket.end();
|
||||||
|
} else if (contentLength < 0) {
|
||||||
|
//Нет Content-Length, читаем до конца
|
||||||
|
//Будем ждать закрытия соединения
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
secureSocket.on("data", onSecureData);
|
||||||
|
|
||||||
|
secureSocket.on("end", () => {
|
||||||
|
const result = {
|
||||||
|
statusCode: statusCode,
|
||||||
|
statusMessage: "",
|
||||||
|
headers: responseHeaders,
|
||||||
|
body: responseBuffer,
|
||||||
|
ok: statusCode >= 200 && statusCode < 300,
|
||||||
|
url: targetUrl.toString()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options.throwOnErrorStatus && !result.ok) {
|
||||||
|
const error = new HttpError(`Запрос не выполнен со статусом ${result.statusCode}`, result);
|
||||||
|
const httpError = new Error(error.message);
|
||||||
|
httpError.response = result;
|
||||||
|
httpError.statusCode = result.statusCode;
|
||||||
|
return reject(httpError);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(processResponse(result, options));
|
||||||
|
});
|
||||||
|
|
||||||
|
secureSocket.on("error", err => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
secureSocket.on("error", err => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
proxySocket.destroy();
|
||||||
|
reject(new Error(`Прокси вернул ошибку: ${statusLine}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
proxySocket.on("data", onConnectData);
|
||||||
|
});
|
||||||
|
|
||||||
|
proxySocket.on("error", err => {
|
||||||
|
clearTimeout(connectTimeout);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
//Выполнение HTTP/HTTPS запроса
|
//Выполнение HTTP/HTTPS запроса
|
||||||
const httpRequest = async (rawOptions = {}) => {
|
const httpRequest = async (rawOptions = {}) => {
|
||||||
try {
|
try {
|
||||||
@ -26,7 +385,7 @@ const httpRequest = async (rawOptions = {}) => {
|
|||||||
const headers = prepareHeaders(options.headers);
|
const headers = prepareHeaders(options.headers);
|
||||||
const { body, contentLength, isStream } = prepareBody({
|
const { body, contentLength, isStream } = prepareBody({
|
||||||
body: options.body,
|
body: options.body,
|
||||||
json: options.json,
|
jsonRequest: options.jsonRequest,
|
||||||
headers
|
headers
|
||||||
});
|
});
|
||||||
//Если не указан размер тела
|
//Если не указан размер тела
|
||||||
@ -34,30 +393,8 @@ const httpRequest = async (rawOptions = {}) => {
|
|||||||
//Установим размер тела в заголовок
|
//Установим размер тела в заголовок
|
||||||
headers.set("content-length", String(contentLength));
|
headers.set("content-length", String(contentLength));
|
||||||
}
|
}
|
||||||
//Выполним запрос с использованием fetch
|
|
||||||
return httpRequestFetch({ options, url, headers, body, isStream });
|
|
||||||
} catch (e) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//Ошибка HTTP-запроса
|
|
||||||
class HttpError extends Error {
|
|
||||||
constructor(message, response) {
|
|
||||||
super(message);
|
|
||||||
this.name = "HttpError";
|
|
||||||
this.response = response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Выполнение запроса с использованием fetch
|
|
||||||
const httpRequestFetch = async ({ options, url, headers, body, isStream }) => {
|
|
||||||
try {
|
|
||||||
const fetchImpl = globalThis.fetch;
|
|
||||||
if (typeof fetchImpl !== "function") {
|
|
||||||
throw new Error("globalThis.fetch недоступен. Используйте Node.js >= 18.");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
//Если есть поток, читаем его
|
||||||
let requestBody = body;
|
let requestBody = body;
|
||||||
if (isStream && body && typeof body.pipe === "function") {
|
if (isStream && body && typeof body.pipe === "function") {
|
||||||
const chunks = [];
|
const chunks = [];
|
||||||
@ -71,6 +408,17 @@ const httpRequestFetch = async ({ options, url, headers, body, isStream }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Если нужен прокси или специальные TLS настройки, используем встроенные модули
|
||||||
|
if (options.proxy || options.ca || options.cert || options.key || options.passphrase || options.rejectUnauthorized === false) {
|
||||||
|
return await httpRequestNative(options, url.toString(), headers, requestBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Для простых запросов используем встроенный fetch
|
||||||
|
const fetchImpl = globalThis.fetch;
|
||||||
|
if (typeof fetchImpl !== "function") {
|
||||||
|
throw new Error("globalThis.fetch недоступен. Используйте Node.js >= 18.");
|
||||||
|
}
|
||||||
|
|
||||||
const timeoutController = new AbortController();
|
const timeoutController = new AbortController();
|
||||||
const signals = [];
|
const signals = [];
|
||||||
|
|
||||||
@ -97,53 +445,11 @@ const httpRequestFetch = async ({ options, url, headers, body, isStream }) => {
|
|||||||
signal: combinedSignal,
|
signal: combinedSignal,
|
||||||
redirect: options.followRedirects ? "follow" : "manual"
|
redirect: options.followRedirects ? "follow" : "manual"
|
||||||
};
|
};
|
||||||
|
|
||||||
if (options.proxy || options.ca || options.cert || options.key || options.passphrase || options.rejectUnauthorized === false) {
|
|
||||||
const { Agent, ProxyAgent } = require("undici");
|
|
||||||
const connectOptions = {
|
|
||||||
rejectUnauthorized: options.rejectUnauthorized !== undefined ? options.rejectUnauthorized : true
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.ca) {
|
|
||||||
connectOptions.ca = Array.isArray(options.ca) ? options.ca : [options.ca];
|
|
||||||
}
|
|
||||||
if (options.cert) {
|
|
||||||
connectOptions.cert = options.cert;
|
|
||||||
}
|
|
||||||
if (options.key) {
|
|
||||||
connectOptions.key = options.key;
|
|
||||||
}
|
|
||||||
if (options.passphrase) {
|
|
||||||
connectOptions.passphrase = options.passphrase;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.proxy) {
|
|
||||||
const proxyUrl = new URL(options.proxy);
|
|
||||||
if (proxyUrl.protocol !== "http:" && proxyUrl.protocol !== "https:") {
|
|
||||||
throw new Error("Поддерживаются только HTTP/HTTPS-прокси (protocol=http|https).");
|
|
||||||
}
|
|
||||||
const proxyAuth =
|
|
||||||
proxyUrl.username || proxyUrl.password
|
|
||||||
? `Basic ${Buffer.from(`${decodeURIComponent(proxyUrl.username)}:${decodeURIComponent(proxyUrl.password)}`).toString(
|
|
||||||
"base64"
|
|
||||||
)}`
|
|
||||||
: null;
|
|
||||||
fetchOptions.dispatcher = new ProxyAgent({
|
|
||||||
uri: options.proxy,
|
|
||||||
token: proxyAuth,
|
|
||||||
connect: connectOptions
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
fetchOptions.dispatcher = new Agent({
|
|
||||||
connect: connectOptions
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetchImpl(url.toString(), fetchOptions);
|
const response = await fetchImpl(url.toString(), fetchOptions);
|
||||||
const responseBody = Buffer.from(await response.arrayBuffer());
|
const responseBody = Buffer.from(await response.arrayBuffer());
|
||||||
const result = {
|
const result = {
|
||||||
statusCode: response.status,
|
statusCode: response.status,
|
||||||
|
statusMessage: response.statusText || "",
|
||||||
headers: Object.fromEntries(response.headers.entries()),
|
headers: Object.fromEntries(response.headers.entries()),
|
||||||
body: responseBody,
|
body: responseBody,
|
||||||
ok: response.ok,
|
ok: response.ok,
|
||||||
@ -151,14 +457,39 @@ const httpRequestFetch = async ({ options, url, headers, body, isStream }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (options.throwOnErrorStatus && !result.ok) {
|
if (options.throwOnErrorStatus && !result.ok) {
|
||||||
throw new HttpError(`Запрос не выполнен со статусом ${result.statusCode}`, result);
|
const error = new HttpError(`Запрос не выполнен со статусом ${result.statusCode}`, result);
|
||||||
|
const httpError = new Error(error.message);
|
||||||
|
httpError.response = result;
|
||||||
|
httpError.statusCode = result.statusCode;
|
||||||
|
throw httpError;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return processResponse(result, options);
|
||||||
} finally {
|
} finally {
|
||||||
if (timeoutId) clearTimeout(timeoutId);
|
if (timeoutId) clearTimeout(timeoutId);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
//Преобразуем ошибки для совместимости с request-promise
|
||||||
|
if (e.name === "AbortError" || e.message.includes("время ожидания")) {
|
||||||
|
const timeoutError = new Error(e.message);
|
||||||
|
timeoutError.code = "ETIMEDOUT";
|
||||||
|
timeoutError.error = { code: "ETIMEDOUT" };
|
||||||
|
throw timeoutError;
|
||||||
|
}
|
||||||
|
//Если ошибка уже имеет response (из httpRequestNative или fetch), пробрасываем как есть
|
||||||
|
if (e.response) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
if (e instanceof HttpError) {
|
||||||
|
const httpError = new Error(e.message);
|
||||||
|
httpError.response = e.response;
|
||||||
|
httpError.statusCode = e.response.statusCode;
|
||||||
|
throw httpError;
|
||||||
|
}
|
||||||
|
//Для других ошибок добавляем поле error для совместимости
|
||||||
|
if (e.code) {
|
||||||
|
e.error = { code: e.code };
|
||||||
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -168,24 +499,58 @@ const normalizeOptions = options => {
|
|||||||
if (!options || typeof options !== "object") {
|
if (!options || typeof options !== "object") {
|
||||||
throw new TypeError("options должен быть объектом");
|
throw new TypeError("options должен быть объектом");
|
||||||
}
|
}
|
||||||
if (!options.url) throw new Error("options.url обязателен");
|
|
||||||
|
//Поддержка параметра uri (как в request-promise)
|
||||||
|
const url = options.uri || options.url;
|
||||||
|
if (!url) throw new Error("options.url или options.uri обязателен");
|
||||||
|
|
||||||
|
//Обработаем JSON
|
||||||
|
const hasJsonOption = Object.prototype.hasOwnProperty.call(options, "json");
|
||||||
|
const jsonOption = options.json;
|
||||||
|
|
||||||
|
let body = options.body;
|
||||||
|
let jsonRequest = false;
|
||||||
|
let json = false;
|
||||||
|
|
||||||
|
if (hasJsonOption) {
|
||||||
|
if (jsonOption === true) {
|
||||||
|
json = true;
|
||||||
|
} else if (jsonOption && typeof jsonOption === "object") {
|
||||||
|
if (body === undefined) {
|
||||||
|
body = jsonOption;
|
||||||
|
jsonRequest = true;
|
||||||
|
}
|
||||||
|
json = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Определяем HTTP-метод
|
||||||
|
let method = options.method;
|
||||||
|
if (!method) {
|
||||||
|
const hasRequestBody = body != null;
|
||||||
|
method = hasRequestBody ? "POST" : "GET";
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
method: (options.method || "GET").toUpperCase(),
|
method: String(method).toUpperCase(),
|
||||||
url: options.url,
|
url,
|
||||||
headers: options.headers || {},
|
headers: options.headers || {},
|
||||||
query: options.query || options.qs || {},
|
query: options.query || options.qs || {},
|
||||||
body: options.body,
|
body,
|
||||||
json: options.json,
|
json,
|
||||||
|
jsonRequest,
|
||||||
timeout: options.timeout ?? DEFAULT_TIMEOUT,
|
timeout: options.timeout ?? DEFAULT_TIMEOUT,
|
||||||
followRedirects: options.followRedirects ?? false,
|
followRedirects: options.followRedirects ?? true,
|
||||||
proxy: options.proxy || null,
|
proxy: options.proxy || null,
|
||||||
ca: options.ca,
|
ca: options.ca,
|
||||||
cert: options.cert,
|
cert: options.cert,
|
||||||
key: options.key,
|
key: options.key,
|
||||||
passphrase: options.passphrase,
|
passphrase: options.passphrase,
|
||||||
rejectUnauthorized: options.rejectUnauthorized !== undefined ? options.rejectUnauthorized : true,
|
rejectUnauthorized: options.rejectUnauthorized !== undefined ? options.rejectUnauthorized : true,
|
||||||
throwOnErrorStatus: options.throwOnErrorStatus ?? false,
|
throwOnErrorStatus: options.throwOnErrorStatus ?? options.simple !== false,
|
||||||
signal: options.signal || null
|
signal: options.signal || null,
|
||||||
|
resolveWithFullResponse: options.resolveWithFullResponse || false,
|
||||||
|
encoding: options.encoding || null
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -210,13 +575,18 @@ const prepareHeaders = (inputHeaders = {}) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
//Подготовка тела запроса
|
//Подготовка тела запроса
|
||||||
const prepareBody = ({ body, json, headers }) => {
|
const prepareBody = ({ body, jsonRequest, headers }) => {
|
||||||
if (json !== undefined) {
|
if (body === undefined || body === null) {
|
||||||
const payload = Buffer.from(JSON.stringify(json));
|
return { body: undefined, contentLength: undefined, isStream: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
//Явно указано, что тело нужно сериализовать как JSON
|
||||||
|
if (jsonRequest) {
|
||||||
|
const payload = Buffer.from(JSON.stringify(body));
|
||||||
if (!headers.has("content-type")) headers.set("content-type", "application/json; charset=utf-8");
|
if (!headers.has("content-type")) headers.set("content-type", "application/json; charset=utf-8");
|
||||||
return { body: payload, contentLength: payload.length, isStream: false };
|
return { body: payload, contentLength: payload.length, isStream: false };
|
||||||
}
|
}
|
||||||
if (body === undefined || body === null) return { body: undefined, contentLength: undefined, isStream: false };
|
|
||||||
if (Buffer.isBuffer(body) || body instanceof Uint8Array) {
|
if (Buffer.isBuffer(body) || body instanceof Uint8Array) {
|
||||||
return { body, contentLength: body.length, isStream: false };
|
return { body, contentLength: body.length, isStream: false };
|
||||||
}
|
}
|
||||||
@ -227,8 +597,16 @@ const prepareBody = ({ body, json, headers }) => {
|
|||||||
if (typeof body.pipe === "function") {
|
if (typeof body.pipe === "function") {
|
||||||
return { body, contentLength: undefined, isStream: true };
|
return { body, contentLength: undefined, isStream: true };
|
||||||
}
|
}
|
||||||
const payload = Buffer.from(JSON.stringify(body));
|
|
||||||
if (!headers.has("content-type")) headers.set("content-type", "application/json; charset=utf-8");
|
//По умолчанию объектное тело сериализуем в JSON
|
||||||
|
if (typeof body === "object") {
|
||||||
|
const payload = Buffer.from(JSON.stringify(body));
|
||||||
|
if (!headers.has("content-type")) headers.set("content-type", "application/json; charset=utf-8");
|
||||||
|
return { body: payload, contentLength: payload.length, isStream: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
//Фолбэк для прочих типов
|
||||||
|
const payload = Buffer.from(String(body));
|
||||||
return { body: payload, contentLength: payload.length, isStream: false };
|
return { body: payload, contentLength: payload.length, isStream: false };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -123,13 +123,25 @@ class InQueue extends EventEmitter {
|
|||||||
let optionsResp = {};
|
let optionsResp = {};
|
||||||
//Флаг прекращения обработки сообщения
|
//Флаг прекращения обработки сообщения
|
||||||
let bStopPropagation = false;
|
let bStopPropagation = false;
|
||||||
|
//Нормализация сообщения
|
||||||
|
const toMessageBuffer = body => {
|
||||||
|
if (body === undefined || body === null) return null;
|
||||||
|
if (Buffer.isBuffer(body)) return body.length ? body : null;
|
||||||
|
if (body instanceof Uint8Array) return body.length ? Buffer.from(body) : null;
|
||||||
|
if (typeof body === "string") return body.length ? Buffer.from(body, "utf8") : null;
|
||||||
|
if (typeof body === "object") {
|
||||||
|
if (Object.keys(body).length === 0) return null;
|
||||||
|
return Buffer.from(JSON.stringify(body));
|
||||||
|
}
|
||||||
|
return Buffer.from(String(body));
|
||||||
|
};
|
||||||
//Определимся с телом сообщения - для POST, PATCH и PUT сообщений - это тело запроса
|
//Определимся с телом сообщения - для POST, PATCH и PUT сообщений - это тело запроса
|
||||||
if (
|
if (
|
||||||
[objServiceFnSchema.NFN_PRMS_TYPE_POST, objServiceFnSchema.NFN_PRMS_TYPE_PATCH, objServiceFnSchema.NFN_PRMS_TYPE_PUT].includes(
|
[objServiceFnSchema.NFN_PRMS_TYPE_POST, objServiceFnSchema.NFN_PRMS_TYPE_PATCH, objServiceFnSchema.NFN_PRMS_TYPE_PUT].includes(
|
||||||
prms.function.nFnPrmsType
|
prms.function.nFnPrmsType
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
blMsg = prms.req.body && !(Object.keys(prms.req.body ?? {}).length === 0) ? Buffer.from(JSON.stringify(prms.req.body)) : null;
|
blMsg = toMessageBuffer(prms.req.body);
|
||||||
} else {
|
} else {
|
||||||
//Для GET, HEAD, DELETE, CONNECT, OPTIONS и TRACE - параметры запроса
|
//Для GET, HEAD, DELETE, CONNECT, OPTIONS и TRACE - параметры запроса
|
||||||
if (!(Object.keys(prms.req.query ?? {}).length === 0)) blMsg = Buffer.from(JSON.stringify(prms.req.query));
|
if (!(Object.keys(prms.req.query ?? {}).length === 0)) blMsg = Buffer.from(JSON.stringify(prms.req.query));
|
||||||
@ -151,6 +163,8 @@ class InQueue extends EventEmitter {
|
|||||||
sOptions: buildOptionsXML({ options }),
|
sOptions: buildOptionsXML({ options }),
|
||||||
blMsg
|
blMsg
|
||||||
});
|
});
|
||||||
|
//Запомним идентификатор записи очереди в запросе
|
||||||
|
prms.req.nQId = q.nId;
|
||||||
//Скажем что пришло новое входящее сообщение
|
//Скажем что пришло новое входящее сообщение
|
||||||
await this.logger.info(
|
await this.logger.info(
|
||||||
`Новое входящее сообщение от ${prms.req.connection.address().address} для функции ${prms.function.sCode} (${buildURL({
|
`Новое входящее сообщение от ${prms.req.connection.address().address} для функции ${prms.function.sCode} (${buildURL({
|
||||||
@ -328,13 +342,19 @@ class InQueue extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Если мы еще не отдали ответ от сервера
|
//Всё успешно - отдаём результат клиенту, если ещё не отдали
|
||||||
if (!prms.res.writableFinished) {
|
if (bStopPropagation === false && !prms.res.writableFinished) {
|
||||||
//Всё успешно - отдаём результат клиенту
|
if (optionsResp.headers) prms.res.set(optionsResp.headers);
|
||||||
if (bStopPropagation === false) {
|
prms.res.status(optionsResp.statusCode || 200).send(blResp);
|
||||||
if (optionsResp.headers) prms.res.set(optionsResp.headers);
|
}
|
||||||
prms.res.status(optionsResp.statusCode || 200).send(blResp);
|
//Если отправка ответа была прервана по таймауту
|
||||||
}
|
if (prms.req.bIsTimedOut === true) {
|
||||||
|
//Вернем ошибку обработчика с информацией об этом
|
||||||
|
throw new ServerError(
|
||||||
|
SERR_WEB_SERVER,
|
||||||
|
"Истекло время ожидания обработки входящего запроса. Канал закрыт. Клиенту был отправлен ответ с ошибкой истечения таймаута (504)."
|
||||||
|
);
|
||||||
|
} else {
|
||||||
//Фиксируем успех обработки - в протоколе работы сервиса
|
//Фиксируем успех обработки - в протоколе работы сервиса
|
||||||
await this.logger.info(`Входящее сообщение ${q.nId} успешно отработано`, { nQueueId: q.nId });
|
await this.logger.info(`Входящее сообщение ${q.nId} успешно отработано`, { nQueueId: q.nId });
|
||||||
//Фиксируем успех обработки - в статусе сообщения
|
//Фиксируем успех обработки - в статусе сообщения
|
||||||
@ -343,12 +363,6 @@ class InQueue extends EventEmitter {
|
|||||||
nIncExecCnt: NINC_EXEC_CNT_YES,
|
nIncExecCnt: NINC_EXEC_CNT_YES,
|
||||||
nExecState: objQueueSchema.NQUEUE_EXEC_STATE_OK
|
nExecState: objQueueSchema.NQUEUE_EXEC_STATE_OK
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
//Или расскажем об ошибке
|
|
||||||
throw new ServerError(
|
|
||||||
SERR_WEB_SERVER,
|
|
||||||
"Истекло время ожидания обработки входящего запроса. Канал закрыт. Клиенту был отправлен ответ."
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
//Тема и текст уведомления об ошибке
|
//Тема и текст уведомления об ошибке
|
||||||
@ -554,21 +568,24 @@ class InQueue extends EventEmitter {
|
|||||||
if (req.headers["content-type"] === "false") req.headers["content-type"] = "application/octet-stream";
|
if (req.headers["content-type"] === "false") req.headers["content-type"] = "application/octet-stream";
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
//Если требуется установить таймаут на обработку сообщений
|
//Конфигурируем сервер - устанавливаем таймаут обработки сообщений
|
||||||
if (this.inComing.nTimeout !== 0) {
|
this.webApp.use((req, res, next) => {
|
||||||
//Конфигурируем сервер - устанавливаем таймаут обработки сообщений
|
//Поднимем флаг истечения таймаута обработки
|
||||||
this.webApp.use((req, res, next) => {
|
req.bIsTimedOut = false;
|
||||||
|
//Если требуется установить таймаут на обработку сообщений
|
||||||
|
if (this.inComing.nTimeout !== 0)
|
||||||
//Устанавливаем таймаут на ответ от сервера
|
//Устанавливаем таймаут на ответ от сервера
|
||||||
res.setTimeout(this.inComing.nTimeout, () => {
|
res.setTimeout(this.inComing.nTimeout, () => {
|
||||||
|
//Поднимем флаг исчетечение таймаута обработки
|
||||||
|
req.bIsTimedOut = true;
|
||||||
//Формируем ошибку
|
//Формируем ошибку
|
||||||
let err = new Error("Истекло время ожидания формирования ответа для завершения текущего запроса.");
|
let err = new Error("Истекло время ожидания формирования ответа для завершения текущего запроса.");
|
||||||
err.status = 504;
|
err.status = 504;
|
||||||
//Отправляем ошибку
|
//Отправляем ошибку
|
||||||
next(err);
|
next(err);
|
||||||
});
|
});
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
}
|
|
||||||
//Конфигурируем сервер - обработка тела сообщения
|
//Конфигурируем сервер - обработка тела сообщения
|
||||||
this.webApp.use(express.json());
|
this.webApp.use(express.json());
|
||||||
this.webApp.use(express.urlencoded({ extended: true }));
|
this.webApp.use(express.urlencoded({ extended: true }));
|
||||||
@ -624,7 +641,8 @@ class InQueue extends EventEmitter {
|
|||||||
//Протоколируем в журнал работы сервера
|
//Протоколируем в журнал работы сервера
|
||||||
await this.logger.error(makeErrorText(new ServerError(SERR_WEB_SERVER, err.message)), {
|
await this.logger.error(makeErrorText(new ServerError(SERR_WEB_SERVER, err.message)), {
|
||||||
nServiceId: srvs.nId,
|
nServiceId: srvs.nId,
|
||||||
nServiceFnId: fn.nId
|
nServiceFnId: fn.nId,
|
||||||
|
nQueueId: req.nQId || null
|
||||||
});
|
});
|
||||||
//Отправим ошибку клиенту
|
//Отправим ошибку клиенту
|
||||||
res.status(err.status || 500).send(makeErrorText(new ServerError(SERR_WEB_SERVER, err.message)));
|
res.status(err.status || 500).send(makeErrorText(new ServerError(SERR_WEB_SERVER, err.message)));
|
||||||
|
|||||||
@ -342,7 +342,8 @@ const appProcess = async prms => {
|
|||||||
url: options.url,
|
url: options.url,
|
||||||
auth: options.auth,
|
auth: options.auth,
|
||||||
topic: options.topic,
|
topic: options.topic,
|
||||||
message: options.body
|
message: options.body,
|
||||||
|
logger
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
//MQTT/MQTTS
|
//MQTT/MQTTS
|
||||||
@ -352,7 +353,8 @@ const appProcess = async prms => {
|
|||||||
url: options.url,
|
url: options.url,
|
||||||
auth: options.auth,
|
auth: options.auth,
|
||||||
topic: options.topic,
|
topic: options.topic,
|
||||||
message: options.body
|
message: options.body,
|
||||||
|
logger
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
//HTTP/HTTPS
|
//HTTP/HTTPS
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
// Подключение внешних библиотек
|
// Подключение внешних библиотек
|
||||||
//------------------------------
|
//------------------------------
|
||||||
|
|
||||||
const httpRequest = require("./http_client"); //Работа с HTTP/HTTPS запросами
|
const { httpRequest } = require("./http_client"); //Работа с HTTP/HTTPS запросами
|
||||||
const EventEmitter = require("events"); //Обработчик пользовательских событий
|
const EventEmitter = require("events"); //Обработчик пользовательских событий
|
||||||
const { ServerError } = require("./server_errors"); //Типовая ошибка
|
const { ServerError } = require("./server_errors"); //Типовая ошибка
|
||||||
const { SERR_SERVICE_UNAVAILABLE, SERR_OBJECT_BAD_INTERFACE } = require("./constants"); //Общесистемные константы
|
const { SERR_SERVICE_UNAVAILABLE, SERR_OBJECT_BAD_INTERFACE } = require("./constants"); //Общесистемные константы
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
//------------------------------
|
//------------------------------
|
||||||
|
|
||||||
const xml2js = require("xml2js"); //Конвертация XML в JSON и JSON в XML
|
const xml2js = require("xml2js"); //Конвертация XML в JSON и JSON в XML
|
||||||
const httpRequest = require("../core/http_client"); //Работа с HTTP/HTTPS запросами
|
const { httpRequest } = require("../core/http_client"); //Работа с HTTP/HTTPS запросами
|
||||||
const config = require("../config"); //Параметры сервера
|
const config = require("../config"); //Параметры сервера
|
||||||
const { SDDAUTH_API_CLIENT_ID, SDEPARTMENT_NAME, SDEPARTMENT_ID } = require("./diadoc_config"); //Ключ разработчика
|
const { SDDAUTH_API_CLIENT_ID, SDEPARTMENT_NAME, SDEPARTMENT_ID } = require("./diadoc_config"); //Ключ разработчика
|
||||||
|
|
||||||
@ -135,15 +135,23 @@ const buildHeaders = (sAPIClientId, sToken = null) => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Отбор организций
|
//Отбор организций
|
||||||
const getOrganizations = organizations => {
|
const getOrganizations = (organizations, sNumEdo = null) => {
|
||||||
//Параметры отбора
|
//Параметры отбора
|
||||||
let isRoaming = false;
|
let isRoaming = false;
|
||||||
let isActive = true;
|
let isActive = true;
|
||||||
//Итоговая выборка
|
//Итоговая выборка
|
||||||
let organization = { Organizations: [] };
|
let organization = { Organizations: [] };
|
||||||
//Найдем активную организацию не в роуминге
|
//Задан код участника ЭДО
|
||||||
organization.Organizations[0] = organizations.Organizations.find(org => org.IsRoaming === isRoaming && org.IsActive === isActive);
|
if (sNumEdo) {
|
||||||
//Если не удалось получить организацию не в роуминге
|
//Найдем активную организацию не в роуминге
|
||||||
|
organization.Organizations[0] = organizations.Organizations.find(org => org.FnsParticipantId === sNumEdo);
|
||||||
|
}
|
||||||
|
//Не удалось получить организацию по коду участника ЭДО
|
||||||
|
if (!organization.Organizations[0]) {
|
||||||
|
//Найдем активную организацию не в роуминге
|
||||||
|
organization.Organizations[0] = organizations.Organizations.find(org => org.IsRoaming === isRoaming && org.IsActive === isActive);
|
||||||
|
}
|
||||||
|
//Не удалось получить организацию не в роуминге
|
||||||
if (!organization.Organizations[0]) {
|
if (!organization.Organizations[0]) {
|
||||||
//Найдем активную организацию
|
//Найдем активную организацию
|
||||||
organization.Organizations[0] = organizations.Organizations.find(org => org.IsActive === isActive);
|
organization.Organizations[0] = organizations.Organizations.find(org => org.IsActive === isActive);
|
||||||
@ -184,7 +192,7 @@ const getOrganization = async (sSrvRoot, headers, nInn, nKpp) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
//Получение ящика организации по ИНН/КПП контрагента
|
//Получение ящика организации по ИНН/КПП контрагента
|
||||||
const getOrganizationBoxId = async (sSrvRoot, headers, nInn, nKpp) => {
|
const getOrganizationBoxId = async (sSrvRoot, headers, nInn, nKpp, sNumEdo) => {
|
||||||
//Параметры запроса
|
//Параметры запроса
|
||||||
let httpRequestOptions;
|
let httpRequestOptions;
|
||||||
let serverResp;
|
let serverResp;
|
||||||
@ -204,9 +212,11 @@ const getOrganizationBoxId = async (sSrvRoot, headers, nInn, nKpp) => {
|
|||||||
serverResp = await httpRequest(httpRequestOptions);
|
serverResp = await httpRequest(httpRequestOptions);
|
||||||
try {
|
try {
|
||||||
//Получим организацию не в роуминге (или единственную организацию в роуминге)
|
//Получим организацию не в роуминге (или единственную организацию в роуминге)
|
||||||
serverResp = getOrganizations(serverResp);
|
serverResp = getOrganizations(serverResp, sNumEdo);
|
||||||
if (!serverResp?.Organizations[0]) {
|
if (!serverResp?.Organizations[0]) {
|
||||||
throw Error(`Не удалось получить ящик получателя для контрагента с ИНН: ${nInn} и КПП: ${nKpp}`);
|
throw Error(
|
||||||
|
`Не удалось получить ящик получателя для контрагента с ${sNumEdo ? `кодом участника ЭДО: ${sNumEdo}, ` : ""}ИНН: ${nInn} и КПП: ${nKpp}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
//Получим головную организацию по ИНН/КПП
|
//Получим головную организацию по ИНН/КПП
|
||||||
@ -297,6 +307,8 @@ const beforeMessagePost = async prms => {
|
|||||||
}
|
}
|
||||||
//Если не достали из контекста токен доступа - значит нет аутентификации на сервере
|
//Если не достали из контекста токен доступа - значит нет аутентификации на сервере
|
||||||
if (!sToken) return { bUnAuth: true };
|
if (!sToken) return { bUnAuth: true };
|
||||||
|
//Получим параметры запроса
|
||||||
|
const optionsData = await toJSON(prms.queue.sOptions);
|
||||||
//Конвертируем XML из "Парус 8" в JSON
|
//Конвертируем XML из "Парус 8" в JSON
|
||||||
let obj = await toJSON(prms.queue.blMsg.toString());
|
let obj = await toJSON(prms.queue.blMsg.toString());
|
||||||
//Формируем запрос для получения FromBoxId
|
//Формируем запрос для получения FromBoxId
|
||||||
@ -314,7 +326,7 @@ const beforeMessagePost = async prms => {
|
|||||||
//Получим идентификатор организации по ИНН/КПП поставщика документа
|
//Получим идентификатор организации по ИНН/КПП поставщика документа
|
||||||
for (let i in serverResp.Organizations) {
|
for (let i in serverResp.Organizations) {
|
||||||
//Если найдена подходящая организация - запомним идентификатор и выходим из цикла
|
//Если найдена подходящая организация - запомним идентификатор и выходим из цикла
|
||||||
if (serverResp.Organizations[i].Inn == prms.options.inn_pr && serverResp.Organizations[i].Kpp == prms.options.kpp_pr) {
|
if (serverResp.Organizations[i].Inn == optionsData.inn_pr && serverResp.Organizations[i].Kpp == optionsData.kpp_pr) {
|
||||||
//Сохраняем полученный ответ
|
//Сохраняем полученный ответ
|
||||||
obj.FromBoxId = serverResp.Organizations[i].Boxes[0].BoxId;
|
obj.FromBoxId = serverResp.Organizations[i].Boxes[0].BoxId;
|
||||||
break;
|
break;
|
||||||
@ -322,7 +334,7 @@ const beforeMessagePost = async prms => {
|
|||||||
}
|
}
|
||||||
//Не удалось получить ящик отправителя
|
//Не удалось получить ящик отправителя
|
||||||
if (!obj.FromBoxId) {
|
if (!obj.FromBoxId) {
|
||||||
throw new Error(`Не удалось получить ящик текущей организации с ИНН: ${prms.options.inn_pr} и КПП: ${prms.options.kpp_pr}`);
|
throw new Error(`Не удалось получить ящик текущей организации с ИНН: ${optionsData.inn_pr} и КПП: ${optionsData.kpp_pr}`);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw Error(`Ошибка при получении ящика текущей организации: ${e.message}`);
|
throw Error(`Ошибка при получении ящика текущей организации: ${e.message}`);
|
||||||
@ -331,8 +343,9 @@ const beforeMessagePost = async prms => {
|
|||||||
organization = await getOrganizationBoxId(
|
organization = await getOrganizationBoxId(
|
||||||
prms.service.sSrvRoot,
|
prms.service.sSrvRoot,
|
||||||
buildHeaders(sAPIClientId, sToken),
|
buildHeaders(sAPIClientId, sToken),
|
||||||
prms.options.inn_cs,
|
optionsData.inn_cs,
|
||||||
prms.options.kpp_cs
|
optionsData.kpp_cs,
|
||||||
|
optionsData.num_cs
|
||||||
);
|
);
|
||||||
obj.ToBoxId = organization.BoxId;
|
obj.ToBoxId = organization.BoxId;
|
||||||
//Если не заполнен идентификатор подразделения и при получении ящика удалось его подобрать
|
//Если не заполнен идентификатор подразделения и при получении ящика удалось его подобрать
|
||||||
@ -460,6 +473,8 @@ const beforeEvent = async prms => {
|
|||||||
}
|
}
|
||||||
//Если не достали из контекста токен доступа - значит нет аутентификации на сервере
|
//Если не достали из контекста токен доступа - значит нет аутентификации на сервере
|
||||||
if (!sToken) return { bUnAuth: true };
|
if (!sToken) return { bUnAuth: true };
|
||||||
|
//Получим параметры запроса
|
||||||
|
const optionsData = await toJSON(prms.queue.sOptions);
|
||||||
//Формируем запрос для получения BoxId
|
//Формируем запрос для получения BoxId
|
||||||
let httpRequestOptions = {
|
let httpRequestOptions = {
|
||||||
uri: buildMyOrganizationURL(prms.service.sSrvRoot),
|
uri: buildMyOrganizationURL(prms.service.sSrvRoot),
|
||||||
@ -472,7 +487,7 @@ const beforeEvent = async prms => {
|
|||||||
//Получим идентификатор организации по ИНН/КПП контрагента организации
|
//Получим идентификатор организации по ИНН/КПП контрагента организации
|
||||||
for (let i in serverResp.Organizations) {
|
for (let i in serverResp.Organizations) {
|
||||||
//Если найдена подходящая организация - запомним идентификатор и выходим из цикла
|
//Если найдена подходящая организация - запомним идентификатор и выходим из цикла
|
||||||
if (serverResp.Organizations[i].Inn == prms.options.inn && serverResp.Organizations[i].Kpp == prms.options.kpp) {
|
if (serverResp.Organizations[i].Inn == optionsData.inn && serverResp.Organizations[i].Kpp == optionsData.kpp) {
|
||||||
//Сохраняем полученный ответ
|
//Сохраняем полученный ответ
|
||||||
sBoxId = serverResp.Organizations[i].Boxes[0].BoxId;
|
sBoxId = serverResp.Organizations[i].Boxes[0].BoxId;
|
||||||
//Если задано подразделение
|
//Если задано подразделение
|
||||||
@ -499,7 +514,7 @@ const beforeEvent = async prms => {
|
|||||||
}
|
}
|
||||||
//Не удалось получить ящик текущей организации
|
//Не удалось получить ящик текущей организации
|
||||||
if (!sBoxId) {
|
if (!sBoxId) {
|
||||||
throw new Error(`Не удалось получить ящик текущей организации с ИНН: ${prms.options.inn} и КПП: ${prms.options.kpp}`);
|
throw new Error(`Не удалось получить ящик текущей организации с ИНН: ${optionsData.inn} и КПП: ${optionsData.kpp}`);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw Error(`Ошибка при получении ящика текущей организации: ${e.message}`);
|
throw Error(`Ошибка при получении ящика текущей организации: ${e.message}`);
|
||||||
@ -839,14 +854,18 @@ const beforeDepartmentIdGet = async prms => {
|
|||||||
const afterDepartmentIdGet = async prms => {
|
const afterDepartmentIdGet = async prms => {
|
||||||
let resu = null;
|
let resu = null;
|
||||||
let organization = {};
|
let organization = {};
|
||||||
|
//Получим параметры запроса
|
||||||
|
const optionsData = await toJSON(prms.queue.sOptions);
|
||||||
//Действие выполнено успешно
|
//Действие выполнено успешно
|
||||||
if (prms.optionsResp.statusCode == 200) {
|
if (prms.optionsResp.statusCode == 200) {
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
//Получим организацию не в роуминге (или единственную организацию в роуминге)
|
//Получим организацию не в роуминге (или единственную организацию в роуминге)
|
||||||
organization = getOrganizations(JSON.parse(prms.queue.blResp.toString()));
|
organization = getOrganizations(JSON.parse(prms.queue.blResp.toString()), optionsData.sNumEdo);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw Error(`Не удалось получить ящик для контрагента с ИНН: ${prms.options.nINN} и КПП: ${prms.options.nKPP}`);
|
throw Error(
|
||||||
|
`Не удалось получить ящик для контрагента с ${optionsData.sNumEdo ? `кодом участника ЭДО: ${optionsData.sNumEdo}, ` : ""}ИНН: ${optionsData.nINN} и КПП: ${optionsData.nKPP}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
//Получим ключ разработчика
|
//Получим ключ разработчика
|
||||||
@ -854,7 +873,7 @@ const afterDepartmentIdGet = async prms => {
|
|||||||
//Считаем токен доступа из контекста сервиса
|
//Считаем токен доступа из контекста сервиса
|
||||||
let sToken = prms.service.sCtx;
|
let sToken = prms.service.sCtx;
|
||||||
//Получим головную организацию по ИНН/КПП
|
//Получим головную организацию по ИНН/КПП
|
||||||
organization = await getOrganization(prms.service.sSrvRoot, buildHeaders(sAPIClientId, sToken), prms.options.nINN, prms.options.nKPP);
|
organization = await getOrganization(prms.service.sSrvRoot, buildHeaders(sAPIClientId, sToken), optionsData.nINN, optionsData.nKPP);
|
||||||
}
|
}
|
||||||
//Преобразуем JSON ответ сервиса "ДИАДОК" в XML, понятный "Парус 8"
|
//Преобразуем JSON ответ сервиса "ДИАДОК" в XML, понятный "Парус 8"
|
||||||
resu = toXML({ root: organization });
|
resu = toXML({ root: organization });
|
||||||
|
|||||||
@ -14,6 +14,24 @@ const { WRK_MSG_TYPE, logInf, logErr, makeTaskOKResult, makeTaskErrResult, makeS
|
|||||||
const { PARSERS, findModelByFileName } = require("./parsers"); //Модели и парсеры
|
const { PARSERS, findModelByFileName } = require("./parsers"); //Модели и парсеры
|
||||||
const sax = require("./node_modules/sax"); //Событийный XML-парсер
|
const sax = require("./node_modules/sax"); //Событийный XML-парсер
|
||||||
|
|
||||||
|
//---------------------------
|
||||||
|
// Инициализация Thick-режима
|
||||||
|
//---------------------------
|
||||||
|
|
||||||
|
//Инициализируем Thick-режим до любых подключений к БД
|
||||||
|
try {
|
||||||
|
if (typeof oracledb.initOracleClient === "function" && !(process.env.NODE_ORACLE_DB_THIN_MODE === 1)) {
|
||||||
|
const libDir = process.env.ORACLE_CLIENT_LIB_DIR;
|
||||||
|
if (libDir) {
|
||||||
|
oracledb.initOracleClient({ libDir });
|
||||||
|
} else {
|
||||||
|
oracledb.initOracleClient();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`Не удалось инициализировать Oracle Client (Thick-режим): ${makeErrorText(e)}`);
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------
|
//--------------------------
|
||||||
// Глобальные идентификаторы
|
// Глобальные идентификаторы
|
||||||
//--------------------------
|
//--------------------------
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
//------------------------------
|
//------------------------------
|
||||||
|
|
||||||
const xml2js = require("xml2js"); //Конвертация XML в JSON и JSON в XML
|
const xml2js = require("xml2js"); //Конвертация XML в JSON и JSON в XML
|
||||||
const httpRequest = require("../core/http_client"); //Работа с HTTP/HTTPS запросами
|
const { httpRequest } = require("../core/http_client"); //Работа с HTTP/HTTPS запросами
|
||||||
const { SMCHD_STORAGE_SYSTEM } = require("./sbis_config"); //Система хранения МЧД
|
const { SMCHD_STORAGE_SYSTEM } = require("./sbis_config"); //Система хранения МЧД
|
||||||
|
|
||||||
//---------------------
|
//---------------------
|
||||||
|
|||||||
96
package-lock.json
generated
96
package-lock.json
generated
@ -16,7 +16,6 @@
|
|||||||
"nodemailer": "^6.9.16",
|
"nodemailer": "^6.9.16",
|
||||||
"oracledb": "^6.6.0",
|
"oracledb": "^6.6.0",
|
||||||
"pg": "^8.13.1",
|
"pg": "^8.13.1",
|
||||||
"undici": "^6.0.0",
|
|
||||||
"validate": "^5.1.0",
|
"validate": "^5.1.0",
|
||||||
"xml2js": "^0.6.2"
|
"xml2js": "^0.6.2"
|
||||||
},
|
},
|
||||||
@ -40,9 +39,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "25.0.8",
|
"version": "25.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz",
|
||||||
"integrity": "sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg==",
|
"integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.16.0"
|
"undici-types": "~7.16.0"
|
||||||
@ -148,15 +147,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/broker-factory": {
|
"node_modules/broker-factory": {
|
||||||
"version": "3.1.12",
|
"version": "3.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.1.13.tgz",
|
||||||
"integrity": "sha512-5Bmeki5j2IVO+lE07dSOUMZp1ZGKkE47b3ILv4ZD0nmTdc0iTKVS1CgYPDCy5m0Qb9jIKHBaF9SUrtqg5oW+1A==",
|
"integrity": "sha512-H2VALe31mEtO/SRcNp4cUU5BAm1biwhc/JaF77AigUuni/1YT0FLCJfbUxwIEs9y6Kssjk2fmXgf+Y9ALvmKlw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.28.4",
|
"@babel/runtime": "^7.28.6",
|
||||||
"fast-unique-numbers": "^9.0.25",
|
"fast-unique-numbers": "^9.0.26",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"worker-factory": "^7.0.47"
|
"worker-factory": "^7.0.48"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/buffer": {
|
"node_modules/buffer": {
|
||||||
@ -483,12 +482,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fast-unique-numbers": {
|
"node_modules/fast-unique-numbers": {
|
||||||
"version": "9.0.25",
|
"version": "9.0.26",
|
||||||
"resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-9.0.25.tgz",
|
"resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-9.0.26.tgz",
|
||||||
"integrity": "sha512-vHLSJfu0jSazb5X1jgYZIbsUd4mztxHxyFxUAPYvaYLkTsvQDn5+NbJRtfp+/tLIsUlMkD/geL2710QBxylH6w==",
|
"integrity": "sha512-3Mtq8p1zQinjGyWfKeuBunbuFoixG72AUkk4VvzbX4ykCW9Q4FzRaNyIlfQhUjnKw2ARVP+/CKnoyr6wfHftig==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.28.4",
|
"@babel/runtime": "^7.28.6",
|
||||||
"tslib": "^2.8.1"
|
"tslib": "^2.8.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -946,12 +945,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pg": {
|
"node_modules/pg": {
|
||||||
"version": "8.17.1",
|
"version": "8.17.2",
|
||||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/pg/-/pg-8.17.2.tgz",
|
||||||
"integrity": "sha512-EIR+jXdYNSMOrpRp7g6WgQr7SaZNZfS7IzZIO0oTNEeibq956JxeD15t3Jk3zZH0KH8DmOIx38qJfQenoE8bXQ==",
|
"integrity": "sha512-vjbKdiBJRqzcYw1fNU5KuHyYvdJ1qpcQg1CeBrHFqV1pWgHeVR6j/+kX0E1AAXfyuLUGY1ICrN2ELKA/z2HWzw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pg-connection-string": "^2.10.0",
|
"pg-connection-string": "^2.10.1",
|
||||||
"pg-pool": "^3.11.0",
|
"pg-pool": "^3.11.0",
|
||||||
"pg-protocol": "^1.11.0",
|
"pg-protocol": "^1.11.0",
|
||||||
"pg-types": "2.2.0",
|
"pg-types": "2.2.0",
|
||||||
@ -980,9 +979,9 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/pg-connection-string": {
|
"node_modules/pg-connection-string": {
|
||||||
"version": "2.10.0",
|
"version": "2.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.10.1.tgz",
|
||||||
"integrity": "sha512-ur/eoPKzDx2IjPaYyXS6Y8NSblxM7X64deV2ObV57vhjsWiwLvUD6meukAzogiOsu60GO8m/3Cb6FdJsWNjwXg==",
|
"integrity": "sha512-iNzslsoeSH2/gmDDKiyMqF64DATUCWj3YJ0wP14kqcsf2TUklwimd+66yYojKwZCA7h2yRNLGug71hCBA2a4sw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/pg-int8": {
|
"node_modules/pg-int8": {
|
||||||
@ -1428,15 +1427,6 @@
|
|||||||
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
|
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/undici": {
|
|
||||||
"version": "6.23.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz",
|
|
||||||
"integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18.17"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "7.16.0",
|
"version": "7.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||||
@ -1482,50 +1472,50 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/worker-factory": {
|
"node_modules/worker-factory": {
|
||||||
"version": "7.0.47",
|
"version": "7.0.48",
|
||||||
"resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.47.tgz",
|
"resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.48.tgz",
|
||||||
"integrity": "sha512-Ga5U8n7hJqovn98nlFnbyuJj66s8dCU4QOQd0dU0bje7uvrGGhOFeKtsTdB3b6fO5BD93F88rHpkBCGzgGloKw==",
|
"integrity": "sha512-CGmBy3tJvpBPjUvb0t4PrpKubUsfkI1Ohg0/GGFU2RvA9j/tiVYwKU8O7yu7gH06YtzbeJLzdUR29lmZKn5pag==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.28.4",
|
"@babel/runtime": "^7.28.6",
|
||||||
"fast-unique-numbers": "^9.0.25",
|
"fast-unique-numbers": "^9.0.26",
|
||||||
"tslib": "^2.8.1"
|
"tslib": "^2.8.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/worker-timers": {
|
"node_modules/worker-timers": {
|
||||||
"version": "8.0.28",
|
"version": "8.0.29",
|
||||||
"resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-8.0.28.tgz",
|
"resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-8.0.29.tgz",
|
||||||
"integrity": "sha512-+AuNePH2P/PuhQURf5I+SIGBty4dq2CzoQEB+bMXIQiPrYj3WhkUtIW2bSzeETFWyXJFUdQGsyFeZtit15LkOw==",
|
"integrity": "sha512-9jk0MWHhWAZ2xlJPXr45oe5UF/opdpfZrY0HtyPizWuJ+ce1M3IYk/4IIdGct3kn9Ncfs+tkZt3w1tU6KW2Fsg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.28.4",
|
"@babel/runtime": "^7.28.6",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"worker-timers-broker": "^8.0.14",
|
"worker-timers-broker": "^8.0.15",
|
||||||
"worker-timers-worker": "^9.0.12"
|
"worker-timers-worker": "^9.0.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/worker-timers-broker": {
|
"node_modules/worker-timers-broker": {
|
||||||
"version": "8.0.14",
|
"version": "8.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-8.0.14.tgz",
|
"resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-8.0.15.tgz",
|
||||||
"integrity": "sha512-ooCGGWGcAYbWEJY2nkA60K9mZ33atvg/QIOBJ3OzdQJU5Z7/NdPFlEiMLiCYW8dpeP/qLcsaUsZzETrKNgGicg==",
|
"integrity": "sha512-Te+EiVUMzG5TtHdmaBZvBrZSFNauym6ImDaCAnzQUxvjnw+oGjMT2idmAOgDy30vOZMLejd0bcsc90Axu6XPWA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.28.4",
|
"@babel/runtime": "^7.28.6",
|
||||||
"broker-factory": "^3.1.12",
|
"broker-factory": "^3.1.13",
|
||||||
"fast-unique-numbers": "^9.0.25",
|
"fast-unique-numbers": "^9.0.26",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"worker-timers-worker": "^9.0.12"
|
"worker-timers-worker": "^9.0.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/worker-timers-worker": {
|
"node_modules/worker-timers-worker": {
|
||||||
"version": "9.0.12",
|
"version": "9.0.13",
|
||||||
"resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-9.0.12.tgz",
|
"resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-9.0.13.tgz",
|
||||||
"integrity": "sha512-NBXCnKB/9CkhjWZz2dITgK94QM5GIJx+7LAlCA8mKeO6whdwmfH9S3iPEwakhn3+NOB9nHE3jQqdpKpZZJI23g==",
|
"integrity": "sha512-qjn18szGb1kjcmh2traAdki1eiIS5ikFo+L90nfMOvSRpuDw1hAcR1nzkP2+Hkdqz5thIRnfuWx7QSpsEUsA6Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.28.4",
|
"@babel/runtime": "^7.28.6",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"worker-factory": "^7.0.47"
|
"worker-factory": "^7.0.48"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/wrappy": {
|
"node_modules/wrappy": {
|
||||||
|
|||||||
@ -25,7 +25,6 @@
|
|||||||
"nodemailer": "^6.9.16",
|
"nodemailer": "^6.9.16",
|
||||||
"oracledb": "^6.6.0",
|
"oracledb": "^6.6.0",
|
||||||
"pg": "^8.13.1",
|
"pg": "^8.13.1",
|
||||||
"undici": "^6.0.0",
|
|
||||||
"validate": "^5.1.0",
|
"validate": "^5.1.0",
|
||||||
"xml2js": "^0.6.2"
|
"xml2js": "^0.6.2"
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user