solver-sdk
Version:
SDK для интеграции с Code Solver Backend API
279 lines • 11.5 kB
JavaScript
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