Изменена работа с прокси/tls в модуле http_client, теперь используются модули http, https и net вместо undici. Добавлена инициализация thick-режима при работе с базой данных Oracle для расширения "Интеграция с ГАР". Исправлены ошибки модуля обработки входящих сообщений (in_queue).

This commit is contained in:
boa604 2026-02-02 17:53:04 +03:00
parent 97cb8516c3
commit 19a7023291
9 changed files with 604 additions and 180 deletions

View File

@ -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 };
}; };

View File

@ -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)));

View File

@ -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

View File

@ -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"); //Общесистемные константы

View File

@ -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 });

View File

@ -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)}`);
}
//-------------------------- //--------------------------
// Глобальные идентификаторы // Глобальные идентификаторы
//-------------------------- //--------------------------

View File

@ -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
View File

@ -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": {

View File

@ -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"
}, },