UNPKG

umbot

Version:

Мультиплатформенный фреймворк для создания голосовых навыков и чат-ботов с единой бизнес-логикой. Встроенная поддержка ВКонтакте, Telegram, Viber, MAX, Яндекс Алисы, Маруси и Сбера SmartApp. Архитектура на адаптерах позволяет подключать любые другие платф

287 lines (286 loc) 10.3 kB
"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;