UNPKG

solver-sdk

Version:

SDK для интеграции с Code Solver Backend API

279 lines 11.5 kB
import axios from 'axios'; /** * Определение типа среды выполнения * @returns 'browser' | 'node' */ function getEnvironment() { return (typeof window !== 'undefined' && typeof window.document !== 'undefined') ? 'browser' : 'node'; } /** * HTTP клиент для выполнения запросов к API * * Предоставляет методы для работы с REST API, включая обработку ошибок, * повторные попытки и таймауты. */ export class HttpClient { /** * Создает новый HTTP клиент * @param {string} baseURL Базовый URL для запросов * @param {HttpClientOptions} [options] Опции для HTTP клиента */ constructor(baseURL, options = {}) { this.baseURL = baseURL; this.options = { headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', ...(options.headers || {}) }, timeout: options.timeout || 30000, retry: options.retry || { maxRetries: 3, retryDelay: 1000, maxRetryDelay: 10000, retryStatusCodes: [408, 429, 500, 502, 503, 504] }, httpsAgent: options.httpsAgent }; this.environment = getEnvironment(); // Создаем Axios инстанс с базовыми настройками this.axiosInstance = axios.create({ baseURL: this.baseURL, timeout: this.options.timeout, headers: this.options.headers, ...(this.environment === 'node' && this.options.httpsAgent ? { httpsAgent: this.options.httpsAgent } : {}) }); // Добавляем интерцептор для обработки ошибок и повторных попыток this.setupInterceptors(); } /** * Настраивает интерцепторы Axios */ setupInterceptors() { // Интерцептор для ответов this.axiosInstance.interceptors.response.use((response) => response.data, async (error) => { const { config, response } = error; // Если нет конфига запроса или это уже повторный запрос, или skipRetry установлен, возвращаем ошибку if (!config || config._retryCount || config.skipRetry) { return Promise.reject(error); } config._retryCount = config._retryCount || 0; const retryConfig = this.options.retry; // Проверяем, нужно ли делать повторную попытку для данного статуса const shouldRetry = response && retryConfig.retryStatusCodes?.includes(response.status) && config._retryCount < (retryConfig.maxRetries || 3); if (shouldRetry) { config._retryCount += 1; // Вычисляем время задержки перед повторной попыткой const delay = Math.min((retryConfig.retryDelay || 1000) * Math.pow(2, config._retryCount - 1), retryConfig.maxRetryDelay || 10000); // Ждем перед повторной попыткой await new Promise(resolve => setTimeout(resolve, delay)); // Делаем повторную попытку return this.axiosInstance(config); } // Если не нужно делать повторную попытку, возвращаем ошибку return Promise.reject(error); }); } /** * Выполняет HTTP запрос * @param {RequestOptions} options Опции запроса * @returns {Promise<T>} Ответ от API */ async request(options) { const axiosConfig = { url: options.url, method: options.method, data: options.data, params: options.params, headers: { ...this.options.headers, ...(options.headers || {}) }, timeout: options.timeout || this.options.timeout }; // Если указано не делать повторные попытки, добавляем специальный флаг if (options.noRetry) { axiosConfig.skipRetry = true; } try { return await this.axiosInstance.request(axiosConfig); } catch (error) { throw this.handleError(error); } } /** * Выполняет GET запрос * @param {string} url URL для запроса * @param {Record<string, any>} [params] Query параметры * @param {Record<string, string>} [headers] HTTP заголовки * @returns {Promise<T>} Ответ от API */ async get(url, params, headers) { return this.request({ url, method: 'GET', params, headers }); } /** * Выполняет POST запрос * @param {string} url URL для запроса * @param {any} [data] Данные для отправки * @param {Record<string, string>} [headers] HTTP заголовки * @returns {Promise<T>} Ответ от API */ async post(url, data, headers) { return this.request({ url, method: 'POST', data, headers }); } /** * Выполняет PUT запрос * @param {string} url URL для запроса * @param {any} [data] Данные для отправки * @param {Record<string, string>} [headers] HTTP заголовки * @returns {Promise<T>} Ответ от API */ async put(url, data, headers) { return this.request({ url, method: 'PUT', data, headers }); } /** * Выполняет DELETE запрос * @param {string} url URL для запроса * @param {Record<string, any>} [params] Query параметры * @param {Record<string, string>} [headers] HTTP заголовки * @returns {Promise<T>} Ответ от API */ async delete(url, params, headers) { return this.request({ url, method: 'DELETE', params, headers }); } /** * Выполняет PATCH запрос * @param {string} url URL для запроса * @param {any} [data] Данные для отправки * @param {Record<string, string>} [headers] HTTP заголовки * @returns {Promise<T>} Ответ от API */ async patch(url, data, headers) { return this.request({ url, method: 'PATCH', data, headers }); } /** * Выполняет POST запрос с потоковым ответом * @param {string} url URL для запроса * @param {any} [data] Данные для отправки * @param {Record<string, string>} [headers] HTTP заголовки * @returns {Promise<Response>} Объект Response с потоком данных */ async postStream(url, data, headers) { // Для Node.js среды используем node-fetch или другую библиотеку if (this.environment === 'node') { try { // Используем динамический импорт для совместимости с браузером const { default: nodeFetch } = await import('node-fetch'); return nodeFetch(`${this.baseURL}${url}`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...this.options.headers, ...(headers || {}) }, body: JSON.stringify(data) }); } catch (error) { console.error('Ошибка при загрузке node-fetch:', error); throw new Error('Для использования потоковой передачи в Node.js необходимо установить node-fetch'); } } // Для браузера используем нативный fetch return fetch(`${this.baseURL}${url}`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...this.options.headers, ...(headers || {}) }, body: JSON.stringify(data) }); } /** * Обрабатывает ошибки от Axios * @param {any} error Ошибка от Axios * @returns {Error} Обработанная ошибка */ handleError(error) { let enhancedError; if (error.response) { // Ошибка от сервера с кодом ответа const { status, data } = error.response; const message = data?.message || data?.error || `HTTP ошибка ${status}`; enhancedError = new Error(message); enhancedError.status = status; enhancedError.data = data; enhancedError.isApiError = true; } else if (error.request) { // Запрос был сделан, но ответ не получен enhancedError = new Error('Нет ответа от сервера. Проверьте подключение к интернету.'); enhancedError.request = error.request; enhancedError.isNetworkError = true; } else { // Произошла ошибка при настройке запроса enhancedError = new Error(error.message || 'Произошла неизвестная ошибка'); enhancedError.isUnknownError = true; } // Сохраняем оригинальную ошибку для отладки enhancedError.originalError = error; // Пытаемся обработать ошибку через глобальный обработчик try { // eslint-disable-next-line @typescript-eslint/no-var-requires const { CodeSolverSDK } = require('../code-solver-sdk.js'); if (typeof CodeSolverSDK.handleError === 'function') { CodeSolverSDK.handleError(enhancedError); } } catch (e) { // Игнорируем ошибки при импорте или вызове обработчика } return enhancedError; } /** * Получает экземпляр Axios для тестирования */ get axiosConfig() { return this.axiosInstance; } /** * Получает базовый URL API * @returns {string} Базовый URL */ getBaseURL() { return this.baseURL; } } //# sourceMappingURL=http-client.js.map