Изменена работа с прокси/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 { Socket } = require("net");
|
||||
|
||||
//--------------------------
|
||||
// Локальные идентификаторы
|
||||
@ -15,6 +18,362 @@ const { URL } = require("url");
|
||||
|
||||
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 запроса
|
||||
const httpRequest = async (rawOptions = {}) => {
|
||||
try {
|
||||
@ -26,7 +385,7 @@ const httpRequest = async (rawOptions = {}) => {
|
||||
const headers = prepareHeaders(options.headers);
|
||||
const { body, contentLength, isStream } = prepareBody({
|
||||
body: options.body,
|
||||
json: options.json,
|
||||
jsonRequest: options.jsonRequest,
|
||||
headers
|
||||
});
|
||||
//Если не указан размер тела
|
||||
@ -34,30 +393,8 @@ const httpRequest = async (rawOptions = {}) => {
|
||||
//Установим размер тела в заголовок
|
||||
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;
|
||||
if (isStream && body && typeof body.pipe === "function") {
|
||||
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 signals = [];
|
||||
|
||||
@ -97,53 +445,11 @@ const httpRequestFetch = async ({ options, url, headers, body, isStream }) => {
|
||||
signal: combinedSignal,
|
||||
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 responseBody = Buffer.from(await response.arrayBuffer());
|
||||
const result = {
|
||||
statusCode: response.status,
|
||||
statusMessage: response.statusText || "",
|
||||
headers: Object.fromEntries(response.headers.entries()),
|
||||
body: responseBody,
|
||||
ok: response.ok,
|
||||
@ -151,14 +457,39 @@ const httpRequestFetch = async ({ options, url, headers, body, isStream }) => {
|
||||
};
|
||||
|
||||
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 {
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
};
|
||||
@ -168,24 +499,58 @@ const normalizeOptions = options => {
|
||||
if (!options || typeof options !== "object") {
|
||||
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 {
|
||||
method: (options.method || "GET").toUpperCase(),
|
||||
url: options.url,
|
||||
method: String(method).toUpperCase(),
|
||||
url,
|
||||
headers: options.headers || {},
|
||||
query: options.query || options.qs || {},
|
||||
body: options.body,
|
||||
json: options.json,
|
||||
body,
|
||||
json,
|
||||
jsonRequest,
|
||||
timeout: options.timeout ?? DEFAULT_TIMEOUT,
|
||||
followRedirects: options.followRedirects ?? false,
|
||||
followRedirects: options.followRedirects ?? true,
|
||||
proxy: options.proxy || null,
|
||||
ca: options.ca,
|
||||
cert: options.cert,
|
||||
key: options.key,
|
||||
passphrase: options.passphrase,
|
||||
rejectUnauthorized: options.rejectUnauthorized !== undefined ? options.rejectUnauthorized : true,
|
||||
throwOnErrorStatus: options.throwOnErrorStatus ?? false,
|
||||
signal: options.signal || null
|
||||
throwOnErrorStatus: options.throwOnErrorStatus ?? options.simple !== false,
|
||||
signal: options.signal || null,
|
||||
resolveWithFullResponse: options.resolveWithFullResponse || false,
|
||||
encoding: options.encoding || null
|
||||
};
|
||||
};
|
||||
|
||||
@ -210,13 +575,18 @@ const prepareHeaders = (inputHeaders = {}) => {
|
||||
};
|
||||
|
||||
//Подготовка тела запроса
|
||||
const prepareBody = ({ body, json, headers }) => {
|
||||
if (json !== undefined) {
|
||||
const payload = Buffer.from(JSON.stringify(json));
|
||||
const prepareBody = ({ body, jsonRequest, headers }) => {
|
||||
if (body === undefined || body === null) {
|
||||
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");
|
||||
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) {
|
||||
return { body, contentLength: body.length, isStream: false };
|
||||
}
|
||||
@ -227,8 +597,16 @@ const prepareBody = ({ body, json, headers }) => {
|
||||
if (typeof body.pipe === "function") {
|
||||
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 };
|
||||
};
|
||||
|
||||
|
||||
@ -123,13 +123,25 @@ class InQueue extends EventEmitter {
|
||||
let optionsResp = {};
|
||||
//Флаг прекращения обработки сообщения
|
||||
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 сообщений - это тело запроса
|
||||
if (
|
||||
[objServiceFnSchema.NFN_PRMS_TYPE_POST, objServiceFnSchema.NFN_PRMS_TYPE_PATCH, objServiceFnSchema.NFN_PRMS_TYPE_PUT].includes(
|
||||
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 {
|
||||
//Для GET, HEAD, DELETE, CONNECT, OPTIONS и TRACE - параметры запроса
|
||||
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 }),
|
||||
blMsg
|
||||
});
|
||||
//Запомним идентификатор записи очереди в запросе
|
||||
prms.req.nQId = q.nId;
|
||||
//Скажем что пришло новое входящее сообщение
|
||||
await this.logger.info(
|
||||
`Новое входящее сообщение от ${prms.req.connection.address().address} для функции ${prms.function.sCode} (${buildURL({
|
||||
@ -328,13 +342,19 @@ class InQueue extends EventEmitter {
|
||||
}
|
||||
}
|
||||
}
|
||||
//Если мы еще не отдали ответ от сервера
|
||||
if (!prms.res.writableFinished) {
|
||||
//Всё успешно - отдаём результат клиенту
|
||||
if (bStopPropagation === false) {
|
||||
if (optionsResp.headers) prms.res.set(optionsResp.headers);
|
||||
prms.res.status(optionsResp.statusCode || 200).send(blResp);
|
||||
}
|
||||
//Всё успешно - отдаём результат клиенту, если ещё не отдали
|
||||
if (bStopPropagation === false && !prms.res.writableFinished) {
|
||||
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 });
|
||||
//Фиксируем успех обработки - в статусе сообщения
|
||||
@ -343,12 +363,6 @@ class InQueue extends EventEmitter {
|
||||
nIncExecCnt: NINC_EXEC_CNT_YES,
|
||||
nExecState: objQueueSchema.NQUEUE_EXEC_STATE_OK
|
||||
});
|
||||
} else {
|
||||
//Или расскажем об ошибке
|
||||
throw new ServerError(
|
||||
SERR_WEB_SERVER,
|
||||
"Истекло время ожидания обработки входящего запроса. Канал закрыт. Клиенту был отправлен ответ."
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
//Тема и текст уведомления об ошибке
|
||||
@ -554,21 +568,24 @@ class InQueue extends EventEmitter {
|
||||
if (req.headers["content-type"] === "false") req.headers["content-type"] = "application/octet-stream";
|
||||
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, () => {
|
||||
//Поднимем флаг исчетечение таймаута обработки
|
||||
req.bIsTimedOut = true;
|
||||
//Формируем ошибку
|
||||
let err = new Error("Истекло время ожидания формирования ответа для завершения текущего запроса.");
|
||||
err.status = 504;
|
||||
//Отправляем ошибку
|
||||
next(err);
|
||||
});
|
||||
next();
|
||||
});
|
||||
}
|
||||
next();
|
||||
});
|
||||
//Конфигурируем сервер - обработка тела сообщения
|
||||
this.webApp.use(express.json());
|
||||
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)), {
|
||||
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)));
|
||||
|
||||
@ -342,7 +342,8 @@ const appProcess = async prms => {
|
||||
url: options.url,
|
||||
auth: options.auth,
|
||||
topic: options.topic,
|
||||
message: options.body
|
||||
message: options.body,
|
||||
logger
|
||||
});
|
||||
break;
|
||||
//MQTT/MQTTS
|
||||
@ -352,7 +353,8 @@ const appProcess = async prms => {
|
||||
url: options.url,
|
||||
auth: options.auth,
|
||||
topic: options.topic,
|
||||
message: options.body
|
||||
message: options.body,
|
||||
logger
|
||||
});
|
||||
break;
|
||||
//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 { ServerError } = require("./server_errors"); //Типовая ошибка
|
||||
const { SERR_SERVICE_UNAVAILABLE, SERR_OBJECT_BAD_INTERFACE } = require("./constants"); //Общесистемные константы
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
//------------------------------
|
||||
|
||||
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 { 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 isActive = true;
|
||||
//Итоговая выборка
|
||||
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]) {
|
||||
//Найдем активную организацию
|
||||
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 serverResp;
|
||||
@ -204,9 +212,11 @@ const getOrganizationBoxId = async (sSrvRoot, headers, nInn, nKpp) => {
|
||||
serverResp = await httpRequest(httpRequestOptions);
|
||||
try {
|
||||
//Получим организацию не в роуминге (или единственную организацию в роуминге)
|
||||
serverResp = getOrganizations(serverResp);
|
||||
serverResp = getOrganizations(serverResp, sNumEdo);
|
||||
if (!serverResp?.Organizations[0]) {
|
||||
throw Error(`Не удалось получить ящик получателя для контрагента с ИНН: ${nInn} и КПП: ${nKpp}`);
|
||||
throw Error(
|
||||
`Не удалось получить ящик получателя для контрагента с ${sNumEdo ? `кодом участника ЭДО: ${sNumEdo}, ` : ""}ИНН: ${nInn} и КПП: ${nKpp}`
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
//Получим головную организацию по ИНН/КПП
|
||||
@ -297,6 +307,8 @@ const beforeMessagePost = async prms => {
|
||||
}
|
||||
//Если не достали из контекста токен доступа - значит нет аутентификации на сервере
|
||||
if (!sToken) return { bUnAuth: true };
|
||||
//Получим параметры запроса
|
||||
const optionsData = await toJSON(prms.queue.sOptions);
|
||||
//Конвертируем XML из "Парус 8" в JSON
|
||||
let obj = await toJSON(prms.queue.blMsg.toString());
|
||||
//Формируем запрос для получения FromBoxId
|
||||
@ -314,7 +326,7 @@ const beforeMessagePost = async prms => {
|
||||
//Получим идентификатор организации по ИНН/КПП поставщика документа
|
||||
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;
|
||||
break;
|
||||
@ -322,7 +334,7 @@ const beforeMessagePost = async prms => {
|
||||
}
|
||||
//Не удалось получить ящик отправителя
|
||||
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) {
|
||||
throw Error(`Ошибка при получении ящика текущей организации: ${e.message}`);
|
||||
@ -331,8 +343,9 @@ const beforeMessagePost = async prms => {
|
||||
organization = await getOrganizationBoxId(
|
||||
prms.service.sSrvRoot,
|
||||
buildHeaders(sAPIClientId, sToken),
|
||||
prms.options.inn_cs,
|
||||
prms.options.kpp_cs
|
||||
optionsData.inn_cs,
|
||||
optionsData.kpp_cs,
|
||||
optionsData.num_cs
|
||||
);
|
||||
obj.ToBoxId = organization.BoxId;
|
||||
//Если не заполнен идентификатор подразделения и при получении ящика удалось его подобрать
|
||||
@ -460,6 +473,8 @@ const beforeEvent = async prms => {
|
||||
}
|
||||
//Если не достали из контекста токен доступа - значит нет аутентификации на сервере
|
||||
if (!sToken) return { bUnAuth: true };
|
||||
//Получим параметры запроса
|
||||
const optionsData = await toJSON(prms.queue.sOptions);
|
||||
//Формируем запрос для получения BoxId
|
||||
let httpRequestOptions = {
|
||||
uri: buildMyOrganizationURL(prms.service.sSrvRoot),
|
||||
@ -472,7 +487,7 @@ const beforeEvent = async prms => {
|
||||
//Получим идентификатор организации по ИНН/КПП контрагента организации
|
||||
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;
|
||||
//Если задано подразделение
|
||||
@ -499,7 +514,7 @@ const beforeEvent = async prms => {
|
||||
}
|
||||
//Не удалось получить ящик текущей организации
|
||||
if (!sBoxId) {
|
||||
throw new Error(`Не удалось получить ящик текущей организации с ИНН: ${prms.options.inn} и КПП: ${prms.options.kpp}`);
|
||||
throw new Error(`Не удалось получить ящик текущей организации с ИНН: ${optionsData.inn} и КПП: ${optionsData.kpp}`);
|
||||
}
|
||||
} catch (e) {
|
||||
throw Error(`Ошибка при получении ящика текущей организации: ${e.message}`);
|
||||
@ -839,14 +854,18 @@ const beforeDepartmentIdGet = async prms => {
|
||||
const afterDepartmentIdGet = async prms => {
|
||||
let resu = null;
|
||||
let organization = {};
|
||||
//Получим параметры запроса
|
||||
const optionsData = await toJSON(prms.queue.sOptions);
|
||||
//Действие выполнено успешно
|
||||
if (prms.optionsResp.statusCode == 200) {
|
||||
try {
|
||||
try {
|
||||
//Получим организацию не в роуминге (или единственную организацию в роуминге)
|
||||
organization = getOrganizations(JSON.parse(prms.queue.blResp.toString()));
|
||||
organization = getOrganizations(JSON.parse(prms.queue.blResp.toString()), optionsData.sNumEdo);
|
||||
if (!organization) {
|
||||
throw Error(`Не удалось получить ящик для контрагента с ИНН: ${prms.options.nINN} и КПП: ${prms.options.nKPP}`);
|
||||
throw Error(
|
||||
`Не удалось получить ящик для контрагента с ${optionsData.sNumEdo ? `кодом участника ЭДО: ${optionsData.sNumEdo}, ` : ""}ИНН: ${optionsData.nINN} и КПП: ${optionsData.nKPP}`
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
//Получим ключ разработчика
|
||||
@ -854,7 +873,7 @@ const afterDepartmentIdGet = async prms => {
|
||||
//Считаем токен доступа из контекста сервиса
|
||||
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"
|
||||
resu = toXML({ root: organization });
|
||||
|
||||
@ -14,6 +14,24 @@ const { WRK_MSG_TYPE, logInf, logErr, makeTaskOKResult, makeTaskErrResult, makeS
|
||||
const { PARSERS, findModelByFileName } = require("./parsers"); //Модели и парсеры
|
||||
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 httpRequest = require("../core/http_client"); //Работа с HTTP/HTTPS запросами
|
||||
const { httpRequest } = require("../core/http_client"); //Работа с HTTP/HTTPS запросами
|
||||
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",
|
||||
"oracledb": "^6.6.0",
|
||||
"pg": "^8.13.1",
|
||||
"undici": "^6.0.0",
|
||||
"validate": "^5.1.0",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
@ -40,9 +39,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "25.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.8.tgz",
|
||||
"integrity": "sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg==",
|
||||
"version": "25.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz",
|
||||
"integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
@ -148,15 +147,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/broker-factory": {
|
||||
"version": "3.1.12",
|
||||
"resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.1.12.tgz",
|
||||
"integrity": "sha512-5Bmeki5j2IVO+lE07dSOUMZp1ZGKkE47b3ILv4ZD0nmTdc0iTKVS1CgYPDCy5m0Qb9jIKHBaF9SUrtqg5oW+1A==",
|
||||
"version": "3.1.13",
|
||||
"resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.1.13.tgz",
|
||||
"integrity": "sha512-H2VALe31mEtO/SRcNp4cUU5BAm1biwhc/JaF77AigUuni/1YT0FLCJfbUxwIEs9y6Kssjk2fmXgf+Y9ALvmKlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"fast-unique-numbers": "^9.0.25",
|
||||
"@babel/runtime": "^7.28.6",
|
||||
"fast-unique-numbers": "^9.0.26",
|
||||
"tslib": "^2.8.1",
|
||||
"worker-factory": "^7.0.47"
|
||||
"worker-factory": "^7.0.48"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
@ -483,12 +482,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fast-unique-numbers": {
|
||||
"version": "9.0.25",
|
||||
"resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-9.0.25.tgz",
|
||||
"integrity": "sha512-vHLSJfu0jSazb5X1jgYZIbsUd4mztxHxyFxUAPYvaYLkTsvQDn5+NbJRtfp+/tLIsUlMkD/geL2710QBxylH6w==",
|
||||
"version": "9.0.26",
|
||||
"resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-9.0.26.tgz",
|
||||
"integrity": "sha512-3Mtq8p1zQinjGyWfKeuBunbuFoixG72AUkk4VvzbX4ykCW9Q4FzRaNyIlfQhUjnKw2ARVP+/CKnoyr6wfHftig==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@babel/runtime": "^7.28.6",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
@ -946,12 +945,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/pg": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.17.1.tgz",
|
||||
"integrity": "sha512-EIR+jXdYNSMOrpRp7g6WgQr7SaZNZfS7IzZIO0oTNEeibq956JxeD15t3Jk3zZH0KH8DmOIx38qJfQenoE8bXQ==",
|
||||
"version": "8.17.2",
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.17.2.tgz",
|
||||
"integrity": "sha512-vjbKdiBJRqzcYw1fNU5KuHyYvdJ1qpcQg1CeBrHFqV1pWgHeVR6j/+kX0E1AAXfyuLUGY1ICrN2ELKA/z2HWzw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pg-connection-string": "^2.10.0",
|
||||
"pg-connection-string": "^2.10.1",
|
||||
"pg-pool": "^3.11.0",
|
||||
"pg-protocol": "^1.11.0",
|
||||
"pg-types": "2.2.0",
|
||||
@ -980,9 +979,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/pg-connection-string": {
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.10.0.tgz",
|
||||
"integrity": "sha512-ur/eoPKzDx2IjPaYyXS6Y8NSblxM7X64deV2ObV57vhjsWiwLvUD6meukAzogiOsu60GO8m/3Cb6FdJsWNjwXg==",
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.10.1.tgz",
|
||||
"integrity": "sha512-iNzslsoeSH2/gmDDKiyMqF64DATUCWj3YJ0wP14kqcsf2TUklwimd+66yYojKwZCA7h2yRNLGug71hCBA2a4sw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pg-int8": {
|
||||
@ -1428,15 +1427,6 @@
|
||||
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
|
||||
"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": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||
@ -1482,50 +1472,50 @@
|
||||
}
|
||||
},
|
||||
"node_modules/worker-factory": {
|
||||
"version": "7.0.47",
|
||||
"resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.47.tgz",
|
||||
"integrity": "sha512-Ga5U8n7hJqovn98nlFnbyuJj66s8dCU4QOQd0dU0bje7uvrGGhOFeKtsTdB3b6fO5BD93F88rHpkBCGzgGloKw==",
|
||||
"version": "7.0.48",
|
||||
"resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.48.tgz",
|
||||
"integrity": "sha512-CGmBy3tJvpBPjUvb0t4PrpKubUsfkI1Ohg0/GGFU2RvA9j/tiVYwKU8O7yu7gH06YtzbeJLzdUR29lmZKn5pag==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"fast-unique-numbers": "^9.0.25",
|
||||
"@babel/runtime": "^7.28.6",
|
||||
"fast-unique-numbers": "^9.0.26",
|
||||
"tslib": "^2.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/worker-timers": {
|
||||
"version": "8.0.28",
|
||||
"resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-8.0.28.tgz",
|
||||
"integrity": "sha512-+AuNePH2P/PuhQURf5I+SIGBty4dq2CzoQEB+bMXIQiPrYj3WhkUtIW2bSzeETFWyXJFUdQGsyFeZtit15LkOw==",
|
||||
"version": "8.0.29",
|
||||
"resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-8.0.29.tgz",
|
||||
"integrity": "sha512-9jk0MWHhWAZ2xlJPXr45oe5UF/opdpfZrY0HtyPizWuJ+ce1M3IYk/4IIdGct3kn9Ncfs+tkZt3w1tU6KW2Fsg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@babel/runtime": "^7.28.6",
|
||||
"tslib": "^2.8.1",
|
||||
"worker-timers-broker": "^8.0.14",
|
||||
"worker-timers-worker": "^9.0.12"
|
||||
"worker-timers-broker": "^8.0.15",
|
||||
"worker-timers-worker": "^9.0.13"
|
||||
}
|
||||
},
|
||||
"node_modules/worker-timers-broker": {
|
||||
"version": "8.0.14",
|
||||
"resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-8.0.14.tgz",
|
||||
"integrity": "sha512-ooCGGWGcAYbWEJY2nkA60K9mZ33atvg/QIOBJ3OzdQJU5Z7/NdPFlEiMLiCYW8dpeP/qLcsaUsZzETrKNgGicg==",
|
||||
"version": "8.0.15",
|
||||
"resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-8.0.15.tgz",
|
||||
"integrity": "sha512-Te+EiVUMzG5TtHdmaBZvBrZSFNauym6ImDaCAnzQUxvjnw+oGjMT2idmAOgDy30vOZMLejd0bcsc90Axu6XPWA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"broker-factory": "^3.1.12",
|
||||
"fast-unique-numbers": "^9.0.25",
|
||||
"@babel/runtime": "^7.28.6",
|
||||
"broker-factory": "^3.1.13",
|
||||
"fast-unique-numbers": "^9.0.26",
|
||||
"tslib": "^2.8.1",
|
||||
"worker-timers-worker": "^9.0.12"
|
||||
"worker-timers-worker": "^9.0.13"
|
||||
}
|
||||
},
|
||||
"node_modules/worker-timers-worker": {
|
||||
"version": "9.0.12",
|
||||
"resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-9.0.12.tgz",
|
||||
"integrity": "sha512-NBXCnKB/9CkhjWZz2dITgK94QM5GIJx+7LAlCA8mKeO6whdwmfH9S3iPEwakhn3+NOB9nHE3jQqdpKpZZJI23g==",
|
||||
"version": "9.0.13",
|
||||
"resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-9.0.13.tgz",
|
||||
"integrity": "sha512-qjn18szGb1kjcmh2traAdki1eiIS5ikFo+L90nfMOvSRpuDw1hAcR1nzkP2+Hkdqz5thIRnfuWx7QSpsEUsA6Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@babel/runtime": "^7.28.6",
|
||||
"tslib": "^2.8.1",
|
||||
"worker-factory": "^7.0.47"
|
||||
"worker-factory": "^7.0.48"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
|
||||
@ -25,7 +25,6 @@
|
||||
"nodemailer": "^6.9.16",
|
||||
"oracledb": "^6.6.0",
|
||||
"pg": "^8.13.1",
|
||||
"undici": "^6.0.0",
|
||||
"validate": "^5.1.0",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user