umbot
Version:
Мультиплатформенный фреймворк для создания голосовых навыков и чат-ботов с единой бизнес-логикой. Встроенная поддержка ВКонтакте, Telegram, Viber, MAX, Яндекс Алисы, Маруси и Сбера SmartApp. Архитектура на адаптерах позволяет подключать любые другие платф
287 lines (286 loc) • 10.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Request = void 0;
/**
* Модуль для отправки HTTP-запросов.
* Предоставляет функционал для работы с различными типами запросов и ответов
*/
const utils_1 = require("../../utils");
const core_1 = require("../../core");
const path_1 = require("path");
/**
* Класс для отправки HTTP-запросов к API различных платформ. Используется внутри адаптеров для взаимодействия с внешними сервисами.
* Поддерживает различные типы запросов, заголовки и отправку файлов
*
* @class Request
*/
class Request {
/** Заголовок для отправки form-data */
static HEADER_FORM_DATA = {
'Content-Type': 'multipart/form-data',
};
/** Заголовок для JSON контента */
static HEADER_JSON = {
'Content-Type': 'application/json',
};
/** URL для отправки запроса */
url;
/** GET-параметры запроса */
get;
/** POST-параметры запроса */
post;
/** POST-параметры запроса в виде строки */
postInString;
/** HTTP-заголовки запроса */
header;
/** Прикрепленный файл (URL, путь или содержимое) */
attach;
/**
* Тип передаваемого файла
* true - передается содержимое файла
* false - передается путь к файлу
* @defaultValue false
*/
isAttachContent;
/**
* Имя параметра при отправке файла
* @defaultValue file
*/
attachName;
/** Кастомный HTTP-метод (DELETE и т.д.) */
customRequest;
/** Максимальное время ожидания ответа (мс) */
maxTimeQuery;
/**
* Преобразование ответа в JSON
* true - ответ будет преобразован в JSON
* false - ответ будет возвращен как текст
* @defaultValue true
*/
isConvertJson;
/**
* Флаг, указывающий, что ожидается бинарный ответ
*/
isBinaryResponse = false;
/** Текст ошибки при выполнении запроса */
#error;
/**
* Контекст приложения
*/
#appContext;
/**
* Создает новый экземпляр Request.
* Инициализирует все поля значениями по умолчанию
*/
constructor(appContext) {
this.url = null;
this.get = null;
this.post = null;
this.postInString = null;
this.header = null;
this.attach = null;
this.isAttachContent = false;
this.attachName = 'file';
this.customRequest = null;
this.maxTimeQuery = null;
this.isConvertJson = true;
this.#error = null;
this.isBinaryResponse = false;
this.#appContext = appContext;
}
/**
* Устанавливает контекст приложения
* @param appContext
*/
setAppContext(appContext) {
if (appContext) {
this.#appContext = appContext;
}
}
/**
* Отправляет HTTP-запрос
*
* @param url - URL для отправки запроса (если не указан, используется this.url)
* @returns Результат выполнения запроса
*/
async send(url = null) {
if (url) {
this.url = url;
}
this.#error = null;
const data = (await this.#run());
this.attachName = 'file';
this.attach = null;
this.post = null;
this.postInString = null;
if (this.#error) {
return { status: false, data: null, err: this.#error };
}
return { status: true, data };
}
/**
* Формирует URL с GET-параметрами
*
* @returns {string} Полный URL с параметрами
*/
_getUrl() {
let url = this.url || '';
if (this.get) {
url += '?' + (0, utils_1.httpBuildQuery)(this.get);
}
return url;
}
/**
* Возвращает функцию для отправки запроса
*/
#getHttpClient() {
// Легитимный доступ к HTTP-клиенту: используется либо переданный контекст, либо глобальный fetch.
if (this.#appContext?.httpClient) {
return this.#appContext?.httpClient;
}
return fetch;
}
/**
* Выполняет HTTP-запрос
*
* @returns Ответ сервера или null в случае ошибки
*/
async #run() {
if (this.url) {
try {
const start = this.#appContext?.usedMetric ? performance.now() : 0;
const response = await this.#getHttpClient()(this._getUrl(), await this._getOptions());
if (this.#appContext?.usedMetric) {
this.#appContext?.logMetric(core_1.EMetric.REQUEST, performance.now() - start, {
url: this.url,
method: this.customRequest || 'POST',
status: response.status || 0,
});
}
if (response.ok) {
if (this.isConvertJson) {
return await response.json();
}
if (this.isBinaryResponse) {
return (await response.arrayBuffer());
}
return await response.text();
}
this.#error = `Не удалось получить данные с "${this.url}". Статус: ${response.status}`;
}
catch (e) {
this.#error = e;
}
}
else {
this.#error = 'Не указан url!';
}
return null;
}
/**
* Формирует параметры для http запроса
*
* @returns {RequestInit|undefined} Параметры запроса
*/
async _getOptions() {
const options = {};
if (this.maxTimeQuery) {
options.signal = AbortSignal.timeout(this.maxTimeQuery);
}
let post = null;
if (this.attach) {
if (await (0, utils_1.isFile)(this.attach)) {
const formData = await this.getAttachFile(this.attach, this.attachName);
if (!formData) {
this.#error = `Не удалось прочитать файл: ${this.attach}`;
return;
}
// Добавляем дополнительные поля из this.post в FormData
if (this.post && typeof this.post === 'object') {
for (const [key, value] of Object.entries(this.post)) {
formData.append(key, typeof value === 'object' ? JSON.stringify(value) : String(value));
}
}
post = formData;
}
else {
this.#error = `Не удалось найти файл: ${this.attach}`;
return;
}
}
else if (this.post || this.postInString) {
if (this.postInString) {
post = this.postInString;
}
else if (!(this.post instanceof FormData)) {
post = JSON.stringify(this.post);
}
else {
post = this.post;
}
}
if (post) {
options.body = post;
options.method = this.customRequest || 'POST';
options.headers = this.header || Request.HEADER_JSON;
}
if (this.header) {
options.headers = this.header;
}
if (post instanceof FormData && options.headers) {
const headers = new Headers(options.headers);
headers.delete('Content-Type');
options.headers = headers;
}
if (this.customRequest) {
options.method = this.customRequest;
}
return options;
}
/**
* Добавляет файл в FormData
* @param formData
* @param filePath
* @param fileName
*/
async addAttachFile(formData, filePath, fileName) {
const fileData = await (0, utils_1.fread)(filePath);
if (fileData.success) {
const fileResult = fileData.data;
const fileBlob = new Blob([fileResult]);
formData.append(fileName || 'file', fileBlob, (0, path_1.basename)(filePath));
}
else {
this.#appContext?.logError(`Ошибка чтения файла: "${filePath}"`, {
error: fileData.error,
});
}
}
/**
* Создает FormData для отправки файла
*
* @param {string} filePath - Путь к файлу
* @param {string} [fileName] - Имя файла
* @returns {FormData|null} FormData с файлом или null в случае ошибки
*/
async getAttachFile(filePath, fileName) {
try {
const formData = new FormData();
await this.addAttachFile(formData, filePath, fileName);
return formData;
}
catch (e) {
this.#appContext?.logError('Ошибка при чтении файла:', e);
}
return null;
}
/**
* Возвращает текст последней ошибки
*
* @returns {string|null} Текст ошибки или null
*/
getError() {
return this.#error;
}
}
exports.Request = Request;