solver-sdk
Version:
SDK for API integration
386 lines • 16.4 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpClient = void 0;
const axios_1 = __importDefault(require("axios"));
/**
* Определение типа среды выполнения
* @returns 'browser' | 'node'
*/
function getEnvironment() {
return (typeof window !== 'undefined' && typeof window.document !== 'undefined')
? 'browser'
: 'node';
}
/**
* HTTP клиент для выполнения запросов к API
*
* Предоставляет методы для работы с REST API, включая обработку ошибок,
* повторные попытки и таймауты.
*/
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_1.default.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) {
// 🔑 Динамическое получение токена перед запросом
let authHeaders = {};
if (this.options.getAuthToken) {
try {
const token = await Promise.resolve(this.options.getAuthToken());
if (token) {
authHeaders['Authorization'] = `Bearer ${token}`;
}
}
catch (error) {
// Если не удалось получить токен, продолжаем без него
console.warn('⚠️ Failed to get auth token:', error);
}
}
const axiosConfig = {
url: options.url,
method: options.method,
data: options.data,
params: options.params,
headers: {
...this.options.headers,
...authHeaders, // ← Динамический токен имеет приоритет
...(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) {
// Формируем полный URL правильно
const fullUrl = url.startsWith('http') ? url : `${this.baseURL}${url}`;
const requestHeaders = {
'Content-Type': 'application/json',
...this.options.headers,
...(headers || {})
};
// Для Node.js среды используем нативный fetch если доступен, иначе node-fetch
if (this.environment === 'node') {
// Проверяем доступность нативного fetch (Node.js 18+)
if (typeof globalThis.fetch !== 'undefined') {
return globalThis.fetch(fullUrl, {
method: 'POST',
headers: requestHeaders,
body: JSON.stringify(data)
});
}
try {
// Fallback на node-fetch для старых версий Node.js
const { default: nodeFetch } = await Promise.resolve().then(() => __importStar(require('node-fetch')));
return nodeFetch(fullUrl, {
method: 'POST',
headers: requestHeaders,
body: JSON.stringify(data)
});
}
catch (error) {
console.error('Ошибка при загрузке node-fetch:', error);
throw new Error('Для использования потоковой передачи в Node.js необходимо установить node-fetch или использовать Node.js 18+');
}
}
// Для браузера используем нативный fetch
return fetch(fullUrl, {
method: 'POST',
headers: requestHeaders,
body: JSON.stringify(data)
});
}
/**
* Обрабатывает ошибки от Axios
* @param {any} error Ошибка от Axios
* @returns {Error} Обработанная ошибка
*/
handleError(error) {
let enhancedError;
let message;
if (error.response) {
// Ошибка от сервера с кодом ответа
const { status, data } = error.response;
// Улучшенное извлечение сообщения ошибки
if (typeof data === 'string') {
message = data;
}
else if (data && typeof data === 'object') {
// Пробуем извлечь сообщение из разных возможных полей
const rawMessage = data.message || data.error || data.errorMessage || data.msg;
if (rawMessage) {
// Если нашли сообщение, проверяем его тип
if (typeof rawMessage === 'string') {
message = rawMessage;
}
else if (typeof rawMessage === 'object') {
// Если это объект, сериализуем его
try {
message = JSON.stringify(rawMessage);
}
catch (e) {
message = String(rawMessage);
}
}
else {
// Для других типов просто конвертируем в строку
message = String(rawMessage);
}
}
else {
// Если сообщение не найдено, сериализуем весь объект data
try {
message = JSON.stringify(data);
}
catch (e) {
message = `[Ошибка сериализации ответа]`;
}
}
}
else {
message = `HTTP ошибка ${status}`;
}
enhancedError = new Error(`[HTTP ${status}] ${message}`);
enhancedError.status = status;
enhancedError.data = data;
enhancedError.isApiError = true;
}
else if (error.request) {
// Запрос был сделан, но ответ не получен
message = 'Нет ответа от сервера. Проверьте подключение к интернету.';
enhancedError = new Error(message);
enhancedError.request = error.request;
enhancedError.isNetworkError = true;
}
else {
// Произошла ошибка при настройке запроса
message = error.message || error.toString() || 'Произошла неизвестная ошибка';
enhancedError = new Error(message);
enhancedError.isUnknownError = true;
}
// Сохраняем оригинальную ошибку для отладки
enhancedError.originalError = error;
// Переопределяем toString для лучшего логирования
enhancedError.toString = function () {
return this.message;
};
// Пытаемся обработать ошибку через глобальный обработчик
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;
}
}
exports.HttpClient = HttpClient;
//# sourceMappingURL=http-client.js.map