mbt-api-client 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -3
- package/dist/index.d.ts +17 -3
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -427,6 +427,7 @@ var createApiService = (axiosInstance) => {
|
|
|
427
427
|
},
|
|
428
428
|
post: (url, data, config) => axiosInstance.post(url, data, config).then((response) => response.data),
|
|
429
429
|
put: (url, data, config) => axiosInstance.put(url, data, config).then((response) => response.data),
|
|
430
|
+
patch: (url, data, config) => axiosInstance.patch(url, data, config).then((response) => response.data),
|
|
430
431
|
delete: (url, config) => axiosInstance.delete(url, config).then((response) => response.data),
|
|
431
432
|
axiosInstance
|
|
432
433
|
};
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/api-client.ts","../src/token-refresh.ts"],"sourcesContent":["/**\n * MBT API Client\n *\n * Библиотека для работы с API MobaTrainer.\n * Предоставляет типизированный axios-клиент с автоматическим обновлением токенов.\n *\n * @packageDocumentation\n *\n * @example\n * ## Быстрый старт\n *\n * ```typescript\n * import {\n * createApiClient,\n * ApiClientProvider,\n * useApiClient,\n * } from 'mbt-api-client';\n *\n * // 1. Создайте клиент\n * const apiClient = createApiClient({\n * urls: {\n * authService: 'https://auth.example.com',\n * userProgressService: 'https://progress.example.com',\n * recommendationService: 'https://rec.example.com',\n * partnerManager: 'https://partners.example.com',\n * puzzleController: 'https://puzzles.example.com',\n * monolith: 'https://api.example.com',\n * },\n * onNavigateToLogin: () => navigate('/login'),\n * });\n *\n * // 2. Оберните приложение в Provider\n * <ApiClientProvider value={apiClient}>\n * <App />\n * </ApiClientProvider>\n *\n * // 3. Используйте в компонентах\n * function MyComponent() {\n * const { userProgressService } = useApiClient();\n *\n * const fetchData = async () => {\n * const user = await userProgressService.get<User>('/me');\n * };\n * }\n * ```\n *\n * ## Использование с RTK Query\n *\n * ```typescript\n * import { createTokenRefreshManager } from 'mbt-api-client';\n *\n * const tokenManager = createTokenRefreshManager({\n * refreshTokenFn: async () => {\n * // Ваша логика обновления\n * return { ok: true, accessToken: 'new-token' };\n * },\n * });\n *\n * // В baseQueryWithReauth\n * if (result.error?.status === 401) {\n * const newToken = await tokenManager.handle401();\n * if (newToken) {\n * // Повторить запрос\n * }\n * }\n * ```\n */\n\n// Основные функции и компоненты\nexport {\n createApiClient,\n ApiClientContext,\n ApiClientProvider,\n useApiClient,\n} from './api-client';\n\n// Типы API клиента\nexport type {\n ApiClient,\n ApiClientConfig,\n ApiClientUrls,\n ApiService,\n} from './api-client';\n\n// Token Refresh Manager\nexport {\n TokenRefreshManager,\n createTokenRefreshManager,\n} from './token-refresh';\n\n// Типы Token Refresh\nexport type { TokenRefreshConfig, TokenRefreshResult } from './token-refresh';\n","/**\n * Модуль API клиента\n *\n * Предоставляет настроенный axios-клиент с автоматическим обновлением токенов,\n * обработкой ошибок авторизации и React Context для интеграции.\n *\n * @example\n * ```typescript\n * import { createApiClient, ApiClientProvider, useApiClient } from 'mbt-api-client';\n *\n * // Создание клиента\n * const apiClient = createApiClient({\n * urls: {\n * authService: 'https://api.example.com/auth',\n * userProgressService: 'https://api.example.com/progress',\n * recommendationService: 'https://api.example.com/recommendations',\n * partnerManager: 'https://api.example.com/partners',\n * puzzleController: 'https://api.example.com/puzzles',\n * monolith: 'https://api.example.com',\n * },\n * onNavigateToLogin: () => navigate('/login'),\n * timeout: 15000,\n * });\n *\n * // В корне приложения\n * <ApiClientProvider value={apiClient}>\n * <App />\n * </ApiClientProvider>\n *\n * // В компонентах\n * const { userProgressService } = useApiClient();\n * const userData = await userProgressService.get('/users/me');\n * ```\n *\n * @module api-client\n */\n\nimport axios, {\n AxiosInstance,\n AxiosRequestConfig,\n AxiosError,\n InternalAxiosRequestConfig,\n} from 'axios';\nimport { createContext, useContext } from 'react';\nimport {\n TokenRefreshManager,\n createTokenRefreshManager,\n TokenRefreshResult,\n} from './token-refresh';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * URL-адреса микросервисов API\n *\n * @example\n * ```typescript\n * const urls: ApiClientUrls = {\n * authService: 'https://auth.example.com',\n * userProgressService: 'https://progress.example.com',\n * recommendationService: 'https://rec.example.com',\n * partnerManager: 'https://partners.example.com',\n * puzzleController: 'https://puzzles.example.com',\n * monolith: 'https://api.example.com',\n * };\n * ```\n */\nexport interface ApiClientUrls {\n /** URL сервиса авторизации (логин, регистрация, refresh токенов) */\n authService: string;\n\n /** URL сервиса прогресса пользователя */\n userProgressService: string;\n\n /** URL сервиса рекомендаций */\n recommendationService: string;\n\n /** URL сервиса управления партнёрами */\n partnerManager: string;\n\n /** URL сервиса управления приключениями */\n adventureManager: string;\n\n /** URL сервиса агрегации данных для клиента */\n bff: string;\n\n /** URL контроллера пазлов/задач */\n puzzleController: string;\n\n /** URL основного монолитного сервиса */\n monolith: string;\n}\n\n/**\n * Конфигурация API клиента\n *\n * @example\n * ```typescript\n * const config: ApiClientConfig = {\n * urls: {\n * authService: 'https://auth.example.com',\n * userProgressService: 'https://progress.example.com',\n * // ... остальные URL\n * },\n * onNavigateToLogin: () => {\n * // Для React Router\n * navigate('/login');\n * },\n * timeout: 15000,\n * decodeJwt: (token) => {\n * // Кастомный декодер JWT\n * return jwtDecode(token);\n * },\n * };\n * ```\n */\nexport interface ApiClientConfig {\n /**\n * URL-адреса микросервисов\n */\n urls: ApiClientUrls;\n\n /**\n * Колбэк для перенаправления на страницу логина\n *\n * Вызывается при:\n * - Ошибке 401 с невалидным токеном\n * - Ошибке 403 (доступ запрещён)\n * - Неудачном обновлении токена\n *\n * @default Очищает localStorage и редиректит на '#/login'\n *\n * @example\n * ```typescript\n * // React Router\n * onNavigateToLogin: () => navigate('/login')\n *\n * // Next.js\n * onNavigateToLogin: () => router.push('/login')\n *\n * // Vanilla JS\n * onNavigateToLogin: () => { window.location.href = '/login' }\n * ```\n */\n onNavigateToLogin?: () => void;\n\n /**\n * Таймаут запросов в миллисекундах\n *\n * @default 10000 (10 секунд)\n */\n timeout?: number;\n\n /**\n * Функция декодирования JWT токена для извлечения userId\n *\n * Используется для добавления заголовка X-User-Id к запросам.\n * Если не указана, используется встроенный base64 декодер.\n *\n * @param token - JWT токен для декодирования\n * @returns Объект с userId или null при ошибке декодирования\n *\n * @default Встроенный base64 декодер\n *\n * @example\n * ```typescript\n * // С библиотекой jwt-decode\n * import { jwtDecode } from 'jwt-decode';\n *\n * decodeJwt: (token) => {\n * try {\n * return jwtDecode<{ userId: string }>(token);\n * } catch {\n * return null;\n * }\n * }\n * ```\n */\n decodeJwt?: (token: string) => { userId?: string } | null;\n}\n\n/**\n * Обёртка над axios для выполнения HTTP-запросов\n *\n * Предоставляет типизированные методы для всех HTTP-операций.\n * Автоматически извлекает `data` из ответа axios.\n *\n * @example\n * ```typescript\n * interface User {\n * id: string;\n * name: string;\n * email: string;\n * }\n *\n * // GET запрос с типизацией\n * const user = await apiService.get<User>('/users/me');\n * console.log(user.name);\n *\n * // POST запрос\n * const newUser = await apiService.post<User>('/users', {\n * name: 'John',\n * email: 'john@example.com',\n * });\n *\n * // С дополнительными параметрами axios\n * const data = await apiService.get<Data>('/endpoint', {\n * params: { page: 1, limit: 10 },\n * headers: { 'X-Custom-Header': 'value' },\n * });\n * ```\n */\nexport interface ApiService {\n /**\n * Выполняет GET-запрос\n *\n * Автоматически добавляет cache-buster параметр `_t` для предотвращения кэширования.\n *\n * @typeParam T - Тип данных ответа\n * @param url - URL эндпоинта (относительно baseURL сервиса)\n * @param config - Дополнительные параметры axios (headers, params и т.д.)\n * @returns Промис с данными ответа\n *\n * @example\n * ```typescript\n * // Простой GET\n * const users = await service.get<User[]>('/users');\n *\n * // С query параметрами\n * const filtered = await service.get<User[]>('/users', {\n * params: { role: 'admin', active: true }\n * });\n * ```\n */\n get: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>;\n\n /**\n * Выполняет POST-запрос\n *\n * @typeParam T - Тип данных ответа\n * @param url - URL эндпоинта (относительно baseURL сервиса)\n * @param data - Тело запроса (будет сериализовано в JSON)\n * @param config - Дополнительные параметры axios\n * @returns Промис с данными ответа\n *\n * @example\n * ```typescript\n * const newUser = await service.post<User>('/users', {\n * name: 'John Doe',\n * email: 'john@example.com',\n * });\n * ```\n */\n post: <T>(\n url: string,\n data?: unknown,\n config?: AxiosRequestConfig,\n ) => Promise<T>;\n\n /**\n * Выполняет PUT-запрос\n *\n * @typeParam T - Тип данных ответа\n * @param url - URL эндпоинта\n * @param data - Тело запроса для обновления\n * @param config - Дополнительные параметры axios\n * @returns Промис с данными ответа\n *\n * @example\n * ```typescript\n * const updated = await service.put<User>('/users/123', {\n * name: 'Jane Doe',\n * });\n * ```\n */\n put: <T>(\n url: string,\n data?: unknown,\n config?: AxiosRequestConfig,\n ) => Promise<T>;\n\n /**\n * Выполняет DELETE-запрос\n *\n * @typeParam T - Тип данных ответа\n * @param url - URL эндпоинта\n * @param config - Дополнительные параметры axios\n * @returns Промис с данными ответа\n *\n * @example\n * ```typescript\n * await service.delete<void>('/users/123');\n * ```\n */\n delete: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>;\n\n /**\n * Доступ к нативному экземпляру axios\n *\n * Используйте для добавления кастомных interceptors или\n * выполнения нестандартных запросов.\n *\n * @example\n * ```typescript\n * // Добавление кастомного interceptor\n * service.axiosInstance.interceptors.request.use(config => {\n * config.headers['X-Request-Id'] = generateId();\n * return config;\n * });\n *\n * // Выполнение PATCH запроса\n * await service.axiosInstance.patch('/users/123', { name: 'New Name' });\n * ```\n */\n axiosInstance: AxiosInstance;\n}\n\n/**\n * Главный объект API клиента\n *\n * Содержит настроенные сервисы для каждого микросервиса\n * и менеджер обновления токенов.\n *\n * @example\n * ```typescript\n * const apiClient = createApiClient({ urls: {...} });\n *\n * // Использование сервисов\n * const user = await apiClient.userProgressService.get('/me');\n * const puzzles = await apiClient.puzzleController.get('/puzzles');\n *\n * // Работа с токенами\n * const currentToken = apiClient.tokenRefreshManager.getAccessToken();\n * ```\n */\nexport interface ApiClient {\n /**\n * Сервис авторизации\n *\n * Используется для логина, регистрации, обновления токенов.\n */\n authService: ApiService;\n\n /**\n * Сервис прогресса пользователя\n *\n * Используется для получения и обновления прогресса обучения.\n */\n userProgressService: ApiService;\n\n /**\n * Сервис рекомендаций\n *\n * Используется для получения персонализированных рекомендаций.\n */\n recommendationService: ApiService;\n\n /**\n * Сервис управления партнёрами\n */\n partnerManager: ApiService;\n\n /**\n * Сервис управления приключениями\n */\n adventureManager: ApiService;\n\n /**\n * Сервис агрегации данных для клиента\n */\n bff: ApiService;\n\n /**\n * Контроллер пазлов/задач\n *\n * Используется для работы с учебными задачами и пазлами.\n */\n puzzleController: ApiService;\n\n /**\n * Основной монолитный сервис\n *\n * Используется для запросов, не выделенных в отдельные микросервисы.\n */\n monolith: ApiService;\n\n /**\n * Менеджер обновления токенов\n *\n * Предоставляет доступ к функциям работы с токенами.\n * Можно использовать с RTK Query или другими HTTP-клиентами.\n *\n * @example\n * ```typescript\n * // Получение текущего токена\n * const token = apiClient.tokenRefreshManager.getAccessToken();\n *\n * // Обработка 401 в кастомном клиенте\n * if (response.status === 401) {\n * const newToken = await apiClient.tokenRefreshManager.handle401();\n * }\n * ```\n */\n tokenRefreshManager: TokenRefreshManager;\n}\n\n// ============================================================================\n// Retry Key Symbol\n// ============================================================================\n\nconst RETRY_KEY = Symbol('retry');\n\ninterface ExtendedAxiosRequestConfig extends InternalAxiosRequestConfig {\n [RETRY_KEY]?: boolean;\n}\n\n// ============================================================================\n// Default implementations\n// ============================================================================\n\nconst defaultNavigateToLogin = () => {\n localStorage.removeItem('accessToken');\n localStorage.removeItem('refreshToken');\n window.location.href = '#/login';\n};\n\nconst defaultDecodeJwt = (token: string): { userId?: string } | null => {\n try {\n const base64Url = token.split('.')[1];\n const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');\n const jsonPayload = decodeURIComponent(\n atob(base64)\n .split('')\n .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))\n .join(''),\n );\n return JSON.parse(jsonPayload);\n } catch {\n return null;\n }\n};\n\n// ============================================================================\n// Create Axios Instance with Interceptors\n// ============================================================================\n\nconst createAxiosInstance = (\n baseUrl: string,\n tokenRefreshManager: TokenRefreshManager,\n onNavigateToLogin: () => void,\n timeout: number,\n decodeJwt: (token: string) => { userId?: string } | null,\n): AxiosInstance => {\n const instance = axios.create({\n baseURL: baseUrl,\n timeout,\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n instance.interceptors.request.use(\n requestConfig => {\n const isRefreshRequest =\n requestConfig.url?.includes('auth/refresh-token');\n const accessToken = tokenRefreshManager.getAccessToken();\n const refreshToken = tokenRefreshManager.getRefreshToken();\n\n const token = isRefreshRequest ? refreshToken : accessToken;\n if (token) {\n Object.assign(requestConfig.headers, {\n Authorization: `Bearer ${token}`,\n });\n }\n\n const decodedToken = decodeJwt(accessToken || '');\n if (decodedToken?.userId) {\n Object.assign(requestConfig.headers, {\n 'X-User-Id': decodedToken.userId,\n });\n }\n\n return requestConfig;\n },\n error => Promise.reject(error),\n );\n\n instance.interceptors.response.use(\n response => response,\n async (error: AxiosError) => {\n const originalRequest = error.config as ExtendedAxiosRequestConfig;\n\n if (!originalRequest) {\n return Promise.reject(error);\n }\n\n const isRefreshRequest =\n originalRequest.url?.includes('auth/refresh-token');\n\n if (error.response?.status === 401) {\n const responseData = error.response?.data as\n | { message?: string }\n | undefined;\n const isInvalidToken = responseData?.message === 'Invalid token';\n\n if (isInvalidToken) {\n onNavigateToLogin();\n return Promise.reject(error);\n }\n\n if (isRefreshRequest) {\n onNavigateToLogin();\n return Promise.reject(error);\n }\n\n if (originalRequest[RETRY_KEY]) {\n onNavigateToLogin();\n return Promise.reject(error);\n }\n\n if (tokenRefreshManager.isRefreshInProgress()) {\n try {\n const newToken = await tokenRefreshManager.waitForTokenRefresh();\n const retryConfig = { ...originalRequest };\n Object.assign(retryConfig.headers, {\n Authorization: `Bearer ${newToken}`,\n });\n return instance(retryConfig);\n } catch {\n return Promise.reject(error);\n }\n }\n\n const retryConfig: ExtendedAxiosRequestConfig = {\n ...originalRequest,\n [RETRY_KEY]: true,\n };\n\n try {\n const newToken = await tokenRefreshManager.handle401(false);\n\n if (!newToken) {\n return Promise.reject(new Error('Failed to refresh token'));\n }\n\n Object.assign(retryConfig.headers, {\n Authorization: `Bearer ${newToken}`,\n });\n return instance(retryConfig);\n } catch (refreshError) {\n onNavigateToLogin();\n return Promise.reject(refreshError);\n }\n }\n\n if (error.response?.status === 403) {\n onNavigateToLogin();\n return Promise.reject(error);\n }\n\n if (error.response?.status === 400 && isRefreshRequest) {\n await new Promise<void>(resolve => {\n setTimeout(resolve, 1000);\n });\n\n const newToken = tokenRefreshManager.getAccessToken();\n if (newToken) {\n const retryConfig = { ...originalRequest };\n Object.assign(retryConfig.headers, {\n Authorization: `Bearer ${newToken}`,\n });\n return instance(retryConfig);\n }\n\n onNavigateToLogin();\n return Promise.reject(new Error('Failed to refresh token'));\n }\n\n return Promise.reject(error);\n },\n );\n\n return instance;\n};\n\n// ============================================================================\n// Create API Service Wrapper\n// ============================================================================\n\nconst createApiService = (axiosInstance: AxiosInstance): ApiService => {\n return {\n get: <T>(url: string, config?: AxiosRequestConfig): Promise<T> => {\n const cacheBuster = `${url.includes('?') ? '&' : '?'}_t=${Date.now()}`;\n return axiosInstance\n .get(url + cacheBuster, config)\n .then(response => response.data);\n },\n post: <T>(\n url: string,\n data?: unknown,\n config?: AxiosRequestConfig,\n ): Promise<T> =>\n axiosInstance.post(url, data, config).then(response => response.data),\n put: <T>(\n url: string,\n data?: unknown,\n config?: AxiosRequestConfig,\n ): Promise<T> =>\n axiosInstance.put(url, data, config).then(response => response.data),\n delete: <T>(url: string, config?: AxiosRequestConfig): Promise<T> =>\n axiosInstance.delete(url, config).then(response => response.data),\n axiosInstance,\n };\n};\n\n// ============================================================================\n// Main Factory Function\n// ============================================================================\n\n/**\n * Создаёт настроенный API клиент\n *\n * Фабричная функция, которая создаёт полностью настроенный API клиент\n * с поддержкой автоматического обновления токенов, обработкой ошибок\n * авторизации и типизированными методами запросов.\n *\n * **Особенности:**\n * - Автоматическое добавление Authorization заголовка\n * - Автоматическое обновление токена при 401 ошибке\n * - Очередь запросов при одновременном обновлении токена\n * - Добавление X-User-Id заголовка из JWT\n * - Cache-busting для GET запросов\n *\n * @param config - Конфигурация клиента\n * @returns Настроенный API клиент\n *\n * @example\n * ```typescript\n * import { createApiClient } from 'mbt-api-client';\n *\n * const apiClient = createApiClient({\n * urls: {\n * authService: 'https://auth.example.com',\n * userProgressService: 'https://progress.example.com',\n * recommendationService: 'https://rec.example.com',\n * partnerManager: 'https://partners.example.com',\n * puzzleController: 'https://puzzles.example.com',\n * monolith: 'https://api.example.com',\n * },\n * onNavigateToLogin: () => {\n * // Очистка состояния и редирект\n * store.dispatch(logout());\n * navigate('/login');\n * },\n * timeout: 15000, // 15 секунд\n * });\n *\n * // Использование\n * try {\n * const user = await apiClient.userProgressService.get<User>('/me');\n * console.log('Пользователь:', user);\n * } catch (error) {\n * console.error('Ошибка:', error);\n * }\n * ```\n */\nexport const createApiClient = (config: ApiClientConfig): ApiClient => {\n const {\n urls,\n onNavigateToLogin = defaultNavigateToLogin,\n timeout = 10000,\n decodeJwt = defaultDecodeJwt,\n } = config;\n\n const authAxiosInstance = axios.create({\n baseURL: urls.authService,\n timeout,\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n const refreshTokenFn = async (): Promise<TokenRefreshResult> => {\n try {\n const refreshToken = localStorage.getItem('refreshToken');\n if (!refreshToken) {\n return { ok: false };\n }\n\n const response = await authAxiosInstance.post(\n '/auth/refresh-token',\n {},\n {\n headers: {\n Authorization: `Bearer ${refreshToken}`,\n },\n },\n );\n\n const { accessToken, refreshToken: newRefreshToken } = response.data;\n\n return {\n ok: true,\n accessToken,\n refreshToken: newRefreshToken,\n };\n } catch {\n return { ok: false };\n }\n };\n\n const tokenRefreshManager = createTokenRefreshManager({\n refreshTokenFn,\n onNavigateToLogin,\n });\n\n authAxiosInstance.interceptors.request.use(\n requestConfig => {\n const isRefreshRequest =\n requestConfig.url?.includes('auth/refresh-token');\n const accessToken = tokenRefreshManager.getAccessToken();\n const refreshToken = tokenRefreshManager.getRefreshToken();\n\n const token = isRefreshRequest ? refreshToken : accessToken;\n if (token && !requestConfig.headers.Authorization) {\n Object.assign(requestConfig.headers, {\n Authorization: `Bearer ${token}`,\n });\n }\n\n const decodedToken = decodeJwt(accessToken || '');\n if (decodedToken?.userId) {\n Object.assign(requestConfig.headers, {\n 'X-User-Id': decodedToken.userId,\n });\n }\n\n return requestConfig;\n },\n error => Promise.reject(error),\n );\n\n const userProgressAxiosInstance = createAxiosInstance(\n urls.userProgressService,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const recommendationAxiosInstance = createAxiosInstance(\n urls.recommendationService,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const partnerManagerAxiosInstance = createAxiosInstance(\n urls.partnerManager,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const adventureManagerAxiosInstance = createAxiosInstance(\n urls.adventureManager,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const bffAxiosInstance = createAxiosInstance(\n urls.bff,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const puzzleControllerAxiosInstance = createAxiosInstance(\n urls.puzzleController,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const monolithAxiosInstance = createAxiosInstance(\n urls.monolith,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n return {\n authService: createApiService(authAxiosInstance),\n userProgressService: createApiService(userProgressAxiosInstance),\n recommendationService: createApiService(recommendationAxiosInstance),\n partnerManager: createApiService(partnerManagerAxiosInstance),\n adventureManager: createApiService(adventureManagerAxiosInstance),\n bff: createApiService(bffAxiosInstance),\n puzzleController: createApiService(puzzleControllerAxiosInstance),\n monolith: createApiService(monolithAxiosInstance),\n tokenRefreshManager,\n };\n};\n\n// ============================================================================\n// React Context\n// ============================================================================\n\n/**\n * React Context для API клиента\n *\n * Используется внутри ApiClientProvider для передачи клиента\n * через дерево компонентов.\n *\n * @example\n * ```typescript\n * // Для кастомных случаев (обычно используйте ApiClientProvider)\n * <ApiClientContext.Provider value={apiClient}>\n * <App />\n * </ApiClientContext.Provider>\n * ```\n */\nexport const ApiClientContext = createContext<ApiClient | null>(null);\n\n/**\n * React хук для доступа к API клиенту\n *\n * Получает API клиент из контекста. Должен использоваться внутри\n * компонента, обёрнутого в ApiClientProvider.\n *\n * @returns API клиент с типизированными сервисами\n * @throws Error если используется вне ApiClientProvider\n *\n * @example\n * ```typescript\n * import { useApiClient } from 'mbt-api-client';\n *\n * function UserProfile() {\n * const { userProgressService } = useApiClient();\n * const [user, setUser] = useState<User | null>(null);\n *\n * useEffect(() => {\n * userProgressService.get<User>('/me')\n * .then(setUser)\n * .catch(console.error);\n * }, []);\n *\n * return <div>{user?.name}</div>;\n * }\n *\n * // С деструктуризацией нескольких сервисов\n * function Dashboard() {\n * const {\n * userProgressService,\n * recommendationService,\n * puzzleController,\n * } = useApiClient();\n *\n * // Параллельные запросы\n * const [progress, recommendations, puzzles] = await Promise.all([\n * userProgressService.get('/progress'),\n * recommendationService.get('/for-me'),\n * puzzleController.get('/puzzles'),\n * ]);\n * }\n * ```\n */\nexport const useApiClient = (): ApiClient => {\n const context = useContext(ApiClientContext);\n if (!context) {\n throw new Error('useApiClient must be used within ApiClientProvider');\n }\n return context;\n};\n\n/**\n * React Provider для API клиента\n *\n * Оборачивает приложение для предоставления доступа к API клиенту\n * через хук useApiClient.\n *\n * @example\n * ```typescript\n * import { createApiClient, ApiClientProvider } from 'mbt-api-client';\n *\n * // Создание клиента (обычно в отдельном файле)\n * const apiClient = createApiClient({\n * urls: { ... },\n * });\n *\n * // В корне приложения\n * function App() {\n * return (\n * <ApiClientProvider value={apiClient}>\n * <Router>\n * <Routes />\n * </Router>\n * </ApiClientProvider>\n * );\n * }\n *\n * // С React Query\n * function App() {\n * return (\n * <QueryClientProvider client={queryClient}>\n * <ApiClientProvider value={apiClient}>\n * <Routes />\n * </ApiClientProvider>\n * </QueryClientProvider>\n * );\n * }\n * ```\n */\nexport const ApiClientProvider = ApiClientContext.Provider;\n\n// ============================================================================\n// Re-exports\n// ============================================================================\n\nexport {\n TokenRefreshManager,\n createTokenRefreshManager,\n type TokenRefreshConfig,\n type TokenRefreshResult,\n} from './token-refresh';\n","/**\n * Модуль управления обновлением токенов\n *\n * Реализует механизм обновления JWT токенов с очередью запросов.\n * Предотвращает множественные одновременные запросы на обновление токена,\n * когда несколько параллельных API-запросов получают ошибку 401.\n *\n * @example\n * ```typescript\n * // Создание менеджера с кастомными функциями\n * const tokenManager = createTokenRefreshManager({\n * refreshTokenFn: async () => {\n * const response = await fetch('/api/auth/refresh');\n * const data = await response.json();\n * return { ok: true, accessToken: data.accessToken };\n * },\n * onNavigateToLogin: () => router.push('/login'),\n * });\n *\n * // Использование с RTK Query или другими клиентами\n * if (response.status === 401) {\n * const newToken = await tokenManager.handle401();\n * // Повторить запрос с новым токеном\n * }\n * ```\n *\n * @module token-refresh\n */\n\n/**\n * Конфигурация менеджера обновления токенов\n *\n * @example\n * ```typescript\n * const config: TokenRefreshConfig = {\n * refreshTokenFn: async () => {\n * // Ваша логика обновления токена\n * return { ok: true, accessToken: 'new-token' };\n * },\n * onNavigateToLogin: () => window.location.href = '/login',\n * getAccessToken: () => localStorage.getItem('accessToken'),\n * getRefreshToken: () => localStorage.getItem('refreshToken'),\n * setTokens: (access, refresh) => {\n * localStorage.setItem('accessToken', access);\n * if (refresh) localStorage.setItem('refreshToken', refresh);\n * },\n * clearTokens: () => {\n * localStorage.removeItem('accessToken');\n * localStorage.removeItem('refreshToken');\n * },\n * };\n * ```\n */\nexport interface TokenRefreshConfig {\n /**\n * Функция для выполнения запроса на обновление токена\n *\n * Должна вернуть объект с `ok: true` и новым `accessToken` при успехе,\n * или `ok: false` при ошибке.\n *\n * @returns Промис с результатом обновления токена\n *\n * @example\n * ```typescript\n * refreshTokenFn: async () => {\n * try {\n * const response = await axios.post('/auth/refresh-token', {}, {\n * headers: { Authorization: `Bearer ${refreshToken}` }\n * });\n * return {\n * ok: true,\n * accessToken: response.data.accessToken,\n * refreshToken: response.data.refreshToken,\n * };\n * } catch {\n * return { ok: false };\n * }\n * }\n * ```\n */\n refreshTokenFn: () => Promise<TokenRefreshResult>;\n\n /**\n * Колбэк для перенаправления на страницу логина\n *\n * Вызывается когда:\n * - Токен невалиден (message: 'Invalid token')\n * - Обновление токена не удалось\n * - Refresh token истёк\n *\n * @default Очищает localStorage и редиректит на '#/login'\n *\n * @example\n * ```typescript\n * onNavigateToLogin: () => {\n * // Для React Router\n * navigate('/login');\n * // Или для Next.js\n * router.push('/login');\n * }\n * ```\n */\n onNavigateToLogin?: () => void;\n\n /**\n * Функция получения текущего access токена\n *\n * @default () => localStorage.getItem('accessToken')\n *\n * @example\n * ```typescript\n * // Для хранения в Redux\n * getAccessToken: () => store.getState().auth.accessToken\n * ```\n */\n getAccessToken?: () => string | null;\n\n /**\n * Функция получения текущего refresh токена\n *\n * @default () => localStorage.getItem('refreshToken')\n */\n getRefreshToken?: () => string | null;\n\n /**\n * Функция сохранения новых токенов после обновления\n *\n * @param accessToken - Новый access токен\n * @param refreshToken - Новый refresh токен (опционально)\n *\n * @default Сохраняет в localStorage\n *\n * @example\n * ```typescript\n * // Для хранения в Redux\n * setTokens: (access, refresh) => {\n * store.dispatch(setAuthTokens({ accessToken: access, refreshToken: refresh }));\n * }\n * ```\n */\n setTokens?: (accessToken: string, refreshToken?: string) => void;\n\n /**\n * Функция очистки токенов (при выходе или ошибке авторизации)\n *\n * @default Удаляет 'accessToken' и 'refreshToken' из localStorage\n */\n clearTokens?: () => void;\n}\n\n/**\n * Результат операции обновления токена\n *\n * @example\n * ```typescript\n * // Успешное обновление\n * const success: TokenRefreshResult = {\n * ok: true,\n * accessToken: 'eyJhbGciOiJIUzI1...',\n * refreshToken: 'dGhpcyBpcyBhIHJl...',\n * };\n *\n * // Ошибка обновления\n * const failure: TokenRefreshResult = { ok: false };\n * ```\n */\nexport interface TokenRefreshResult {\n /**\n * Успешность операции обновления\n *\n * `true` - токен успешно обновлён\n * `false` - ошибка обновления (пользователь будет перенаправлен на логин)\n */\n ok: boolean;\n\n /**\n * Новый access токен (только при `ok: true`)\n */\n accessToken?: string;\n\n /**\n * Новый refresh токен (опционально, только при `ok: true`)\n */\n refreshToken?: string;\n}\n\n/**\n * Интерфейс запроса в очереди ожидания обновления токена\n * @internal\n */\nexport interface QueuedRequest<T = unknown> {\n /** Функция разрешения промиса с новым токеном */\n resolve: (value: T) => void;\n /** Функция отклонения промиса при ошибке */\n reject: (error: unknown) => void;\n}\n\nconst defaultGetAccessToken = (): string | null =>\n localStorage.getItem('accessToken');\n\nconst defaultGetRefreshToken = (): string | null =>\n localStorage.getItem('refreshToken');\n\nconst defaultSetTokens = (accessToken: string, refreshToken?: string): void => {\n localStorage.setItem('accessToken', accessToken);\n if (refreshToken) {\n localStorage.setItem('refreshToken', refreshToken);\n }\n};\n\nconst defaultClearTokens = (): void => {\n localStorage.removeItem('accessToken');\n localStorage.removeItem('refreshToken');\n};\n\nconst defaultNavigateToLogin = (): void => {\n defaultClearTokens();\n window.location.href = '#/login';\n};\n\n/**\n * Менеджер обновления токенов с очередью запросов\n *\n * Решает проблему одновременного обновления токена при множественных\n * параллельных запросах, получивших 401 ошибку.\n *\n * **Как это работает:**\n * 1. Первый запрос с 401 инициирует обновление токена\n * 2. Последующие запросы с 401 добавляются в очередь ожидания\n * 3. После успешного обновления все запросы в очереди получают новый токен\n * 4. При ошибке обновления все запросы получают ошибку\n *\n * @example\n * ```typescript\n * // Создание менеджера\n * const tokenManager = new TokenRefreshManager({\n * refreshTokenFn: async () => {\n * const res = await fetch('/api/refresh');\n * const data = await res.json();\n * return { ok: true, accessToken: data.token };\n * },\n * });\n *\n * // Использование в axios interceptor\n * axios.interceptors.response.use(\n * response => response,\n * async error => {\n * if (error.response?.status === 401) {\n * const newToken = await tokenManager.handle401();\n * if (newToken) {\n * error.config.headers.Authorization = `Bearer ${newToken}`;\n * return axios(error.config);\n * }\n * }\n * return Promise.reject(error);\n * }\n * );\n * ```\n */\nexport class TokenRefreshManager {\n private isRefreshing = false;\n private refreshPromise: Promise<TokenRefreshResult> | null = null;\n private queuedRequests: QueuedRequest<string>[] = [];\n private config: Required<TokenRefreshConfig>;\n\n /**\n * Создаёт новый экземпляр TokenRefreshManager\n *\n * @param config - Конфигурация менеджера\n *\n * @example\n * ```typescript\n * const manager = new TokenRefreshManager({\n * refreshTokenFn: myRefreshFunction,\n * onNavigateToLogin: () => router.push('/login'),\n * });\n * ```\n */\n constructor(config: TokenRefreshConfig) {\n this.config = {\n refreshTokenFn: config.refreshTokenFn,\n onNavigateToLogin: config.onNavigateToLogin ?? defaultNavigateToLogin,\n getAccessToken: config.getAccessToken ?? defaultGetAccessToken,\n getRefreshToken: config.getRefreshToken ?? defaultGetRefreshToken,\n setTokens: config.setTokens ?? defaultSetTokens,\n clearTokens: config.clearTokens ?? defaultClearTokens,\n };\n }\n\n /**\n * Получает текущий access токен\n *\n * @returns Access токен или null, если токен отсутствует\n *\n * @example\n * ```typescript\n * const token = manager.getAccessToken();\n * if (token) {\n * headers.Authorization = `Bearer ${token}`;\n * }\n * ```\n */\n getAccessToken(): string | null {\n return this.config.getAccessToken();\n }\n\n /**\n * Получает текущий refresh токен\n *\n * @returns Refresh токен или null, если токен отсутствует\n */\n getRefreshToken(): string | null {\n return this.config.getRefreshToken();\n }\n\n /**\n * Проверяет, идёт ли в данный момент процесс обновления токена\n *\n * Используйте для определения, нужно ли ставить запрос в очередь\n * или инициировать новое обновление.\n *\n * @returns `true` если обновление в процессе, `false` если нет\n *\n * @example\n * ```typescript\n * if (manager.isRefreshInProgress()) {\n * // Ждём завершения текущего обновления\n * const newToken = await manager.waitForTokenRefresh();\n * } else {\n * // Инициируем новое обновление\n * await manager.refreshToken();\n * }\n * ```\n */\n isRefreshInProgress(): boolean {\n return this.isRefreshing;\n }\n\n /**\n * Добавляет запрос в очередь ожидания обновления токена\n *\n * Возвращает промис, который разрешится с новым токеном после\n * успешного обновления или будет отклонён при ошибке.\n *\n * @returns Промис с новым access токеном\n *\n * @example\n * ```typescript\n * // В response interceptor при множественных 401\n * if (manager.isRefreshInProgress()) {\n * try {\n * const newToken = await manager.waitForTokenRefresh();\n * // Повторить запрос с новым токеном\n * } catch {\n * // Обновление не удалось\n * }\n * }\n * ```\n */\n waitForTokenRefresh(): Promise<string> {\n return new Promise((resolve, reject) => {\n this.queuedRequests.push({ resolve, reject });\n });\n }\n\n /**\n * Выполняет обновление токена\n *\n * Если обновление уже в процессе, вернёт существующий промис.\n * После обновления уведомляет все запросы в очереди.\n *\n * @returns Промис с результатом обновления\n *\n * @example\n * ```typescript\n * const result = await manager.refreshToken();\n * if (result.ok) {\n * console.log('Новый токен:', result.accessToken);\n * } else {\n * console.log('Ошибка обновления токена');\n * }\n * ```\n */\n async refreshToken(): Promise<TokenRefreshResult> {\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n this.isRefreshing = true;\n\n this.refreshPromise = this.performRefresh().finally(() => {\n setTimeout(() => {\n this.refreshPromise = null;\n }, 100);\n });\n\n return this.refreshPromise;\n }\n\n /**\n * Обрабатывает ошибку 401 — либо обновляет токен, либо перенаправляет на логин\n *\n * Основной метод для использования в interceptors. Автоматически:\n * - Перенаправляет на логин при невалидном токене\n * - Ставит запрос в очередь, если обновление уже идёт\n * - Инициирует обновление, если ещё не запущено\n *\n * @param isInvalidToken - Если true, токен считается невалидным и будет выполнен переход на логин\n * @returns Промис с новым access токеном или null при ошибке\n *\n * @example\n * ```typescript\n * // В axios response interceptor\n * if (error.response?.status === 401) {\n * const isInvalid = error.response.data?.message === 'Invalid token';\n * const newToken = await manager.handle401(isInvalid);\n *\n * if (newToken) {\n * // Повторить запрос с новым токеном\n * error.config.headers.Authorization = `Bearer ${newToken}`;\n * return axios(error.config);\n * }\n * // newToken === null означает переход на логин\n * }\n * ```\n */\n async handle401(isInvalidToken = false): Promise<string | null> {\n if (isInvalidToken) {\n this.config.onNavigateToLogin();\n return null;\n }\n\n if (this.isRefreshing) {\n return this.waitForTokenRefresh();\n }\n\n const result = await this.refreshToken();\n\n if (!result.ok) {\n this.config.onNavigateToLogin();\n return null;\n }\n\n return result.accessToken || this.config.getAccessToken();\n }\n\n /**\n * Принудительно перенаправляет на страницу логина\n *\n * Вызывает колбэк onNavigateToLogin из конфигурации.\n *\n * @example\n * ```typescript\n * // При необходимости принудительного выхода\n * manager.navigateToLogin();\n * ```\n */\n navigateToLogin(): void {\n this.config.onNavigateToLogin();\n }\n\n /**\n * Сбрасывает внутреннее состояние менеджера\n *\n * Полезно для тестирования или при необходимости\n * принудительно сбросить очередь запросов.\n *\n * @example\n * ```typescript\n * // В тестах\n * beforeEach(() => {\n * manager.reset();\n * });\n * ```\n */\n reset(): void {\n this.isRefreshing = false;\n this.refreshPromise = null;\n this.queuedRequests = [];\n }\n\n private async performRefresh(): Promise<TokenRefreshResult> {\n try {\n const result = await this.config.refreshTokenFn();\n\n if (result.ok && result.accessToken) {\n this.config.setTokens(result.accessToken, result.refreshToken);\n this.notifyQueueSuccess(result.accessToken);\n } else {\n this.notifyQueueFailure(new Error('Token refresh failed'));\n }\n\n this.isRefreshing = false;\n return result;\n } catch (error) {\n this.isRefreshing = false;\n this.notifyQueueFailure(error);\n return { ok: false };\n }\n }\n\n private notifyQueueSuccess(newToken: string): void {\n this.queuedRequests.forEach(({ resolve }) => resolve(newToken));\n this.queuedRequests = [];\n }\n\n private notifyQueueFailure(error: unknown): void {\n this.queuedRequests.forEach(({ reject }) => reject(error));\n this.queuedRequests = [];\n }\n}\n\n/**\n * Создаёт новый экземпляр TokenRefreshManager\n *\n * Фабричная функция для создания менеджера обновления токенов.\n * Предпочтительный способ создания вместо прямого вызова конструктора.\n *\n * @param config - Конфигурация менеджера\n * @returns Новый экземпляр TokenRefreshManager\n *\n * @example\n * ```typescript\n * import { createTokenRefreshManager } from 'mbt-api-client';\n *\n * const tokenManager = createTokenRefreshManager({\n * refreshTokenFn: async () => {\n * const response = await fetch('/api/auth/refresh', {\n * method: 'POST',\n * headers: {\n * Authorization: `Bearer ${localStorage.getItem('refreshToken')}`,\n * },\n * });\n *\n * if (!response.ok) {\n * return { ok: false };\n * }\n *\n * const data = await response.json();\n * return {\n * ok: true,\n * accessToken: data.accessToken,\n * refreshToken: data.refreshToken,\n * };\n * },\n * onNavigateToLogin: () => {\n * window.location.href = '/login';\n * },\n * });\n *\n * // Использование\n * const newToken = await tokenManager.handle401();\n * ```\n */\nexport const createTokenRefreshManager = (\n config: TokenRefreshConfig,\n): TokenRefreshManager => {\n return new TokenRefreshManager(config);\n};\n"],"mappings":"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","names":["defaultNavigateToLogin","axios","retryConfig"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/api-client.ts","../src/token-refresh.ts"],"sourcesContent":["/**\n * MBT API Client\n *\n * Библиотека для работы с API MobaTrainer.\n * Предоставляет типизированный axios-клиент с автоматическим обновлением токенов.\n *\n * @packageDocumentation\n *\n * @example\n * ## Быстрый старт\n *\n * ```typescript\n * import {\n * createApiClient,\n * ApiClientProvider,\n * useApiClient,\n * } from 'mbt-api-client';\n *\n * // 1. Создайте клиент\n * const apiClient = createApiClient({\n * urls: {\n * authService: 'https://auth.example.com',\n * userProgressService: 'https://progress.example.com',\n * recommendationService: 'https://rec.example.com',\n * partnerManager: 'https://partners.example.com',\n * puzzleController: 'https://puzzles.example.com',\n * monolith: 'https://api.example.com',\n * },\n * onNavigateToLogin: () => navigate('/login'),\n * });\n *\n * // 2. Оберните приложение в Provider\n * <ApiClientProvider value={apiClient}>\n * <App />\n * </ApiClientProvider>\n *\n * // 3. Используйте в компонентах\n * function MyComponent() {\n * const { userProgressService } = useApiClient();\n *\n * const fetchData = async () => {\n * const user = await userProgressService.get<User>('/me');\n * };\n * }\n * ```\n *\n * ## Использование с RTK Query\n *\n * ```typescript\n * import { createTokenRefreshManager } from 'mbt-api-client';\n *\n * const tokenManager = createTokenRefreshManager({\n * refreshTokenFn: async () => {\n * // Ваша логика обновления\n * return { ok: true, accessToken: 'new-token' };\n * },\n * });\n *\n * // В baseQueryWithReauth\n * if (result.error?.status === 401) {\n * const newToken = await tokenManager.handle401();\n * if (newToken) {\n * // Повторить запрос\n * }\n * }\n * ```\n */\n\n// Основные функции и компоненты\nexport {\n createApiClient,\n ApiClientContext,\n ApiClientProvider,\n useApiClient,\n} from './api-client';\n\n// Типы API клиента\nexport type {\n ApiClient,\n ApiClientConfig,\n ApiClientUrls,\n ApiService,\n} from './api-client';\n\n// Token Refresh Manager\nexport {\n TokenRefreshManager,\n createTokenRefreshManager,\n} from './token-refresh';\n\n// Типы Token Refresh\nexport type { TokenRefreshConfig, TokenRefreshResult } from './token-refresh';\n","/**\n * Модуль API клиента\n *\n * Предоставляет настроенный axios-клиент с автоматическим обновлением токенов,\n * обработкой ошибок авторизации и React Context для интеграции.\n *\n * @example\n * ```typescript\n * import { createApiClient, ApiClientProvider, useApiClient } from 'mbt-api-client';\n *\n * // Создание клиента\n * const apiClient = createApiClient({\n * urls: {\n * authService: 'https://api.example.com/auth',\n * userProgressService: 'https://api.example.com/progress',\n * recommendationService: 'https://api.example.com/recommendations',\n * partnerManager: 'https://api.example.com/partners',\n * puzzleController: 'https://api.example.com/puzzles',\n * monolith: 'https://api.example.com',\n * },\n * onNavigateToLogin: () => navigate('/login'),\n * timeout: 15000,\n * });\n *\n * // В корне приложения\n * <ApiClientProvider value={apiClient}>\n * <App />\n * </ApiClientProvider>\n *\n * // В компонентах\n * const { userProgressService } = useApiClient();\n * const userData = await userProgressService.get('/users/me');\n * ```\n *\n * @module api-client\n */\n\nimport axios, {\n AxiosInstance,\n AxiosRequestConfig,\n AxiosError,\n InternalAxiosRequestConfig,\n} from 'axios';\nimport { createContext, useContext } from 'react';\nimport {\n TokenRefreshManager,\n createTokenRefreshManager,\n TokenRefreshResult,\n} from './token-refresh';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * URL-адреса микросервисов API\n *\n * @example\n * ```typescript\n * const urls: ApiClientUrls = {\n * authService: 'https://auth.example.com',\n * userProgressService: 'https://progress.example.com',\n * recommendationService: 'https://rec.example.com',\n * partnerManager: 'https://partners.example.com',\n * puzzleController: 'https://puzzles.example.com',\n * monolith: 'https://api.example.com',\n * };\n * ```\n */\nexport interface ApiClientUrls {\n /** URL сервиса авторизации (логин, регистрация, refresh токенов) */\n authService: string;\n\n /** URL сервиса прогресса пользователя */\n userProgressService: string;\n\n /** URL сервиса рекомендаций */\n recommendationService: string;\n\n /** URL сервиса управления партнёрами */\n partnerManager: string;\n\n /** URL сервиса управления приключениями */\n adventureManager: string;\n\n /** URL сервиса агрегации данных для клиента */\n bff: string;\n\n /** URL контроллера пазлов/задач */\n puzzleController: string;\n\n /** URL основного монолитного сервиса */\n monolith: string;\n}\n\n/**\n * Конфигурация API клиента\n *\n * @example\n * ```typescript\n * const config: ApiClientConfig = {\n * urls: {\n * authService: 'https://auth.example.com',\n * userProgressService: 'https://progress.example.com',\n * // ... остальные URL\n * },\n * onNavigateToLogin: () => {\n * // Для React Router\n * navigate('/login');\n * },\n * timeout: 15000,\n * decodeJwt: (token) => {\n * // Кастомный декодер JWT\n * return jwtDecode(token);\n * },\n * };\n * ```\n */\nexport interface ApiClientConfig {\n /**\n * URL-адреса микросервисов\n */\n urls: ApiClientUrls;\n\n /**\n * Колбэк для перенаправления на страницу логина\n *\n * Вызывается при:\n * - Ошибке 401 с невалидным токеном\n * - Ошибке 403 (доступ запрещён)\n * - Неудачном обновлении токена\n *\n * @default Очищает localStorage и редиректит на '#/login'\n *\n * @example\n * ```typescript\n * // React Router\n * onNavigateToLogin: () => navigate('/login')\n *\n * // Next.js\n * onNavigateToLogin: () => router.push('/login')\n *\n * // Vanilla JS\n * onNavigateToLogin: () => { window.location.href = '/login' }\n * ```\n */\n onNavigateToLogin?: () => void;\n\n /**\n * Таймаут запросов в миллисекундах\n *\n * @default 10000 (10 секунд)\n */\n timeout?: number;\n\n /**\n * Функция декодирования JWT токена для извлечения userId\n *\n * Используется для добавления заголовка X-User-Id к запросам.\n * Если не указана, используется встроенный base64 декодер.\n *\n * @param token - JWT токен для декодирования\n * @returns Объект с userId или null при ошибке декодирования\n *\n * @default Встроенный base64 декодер\n *\n * @example\n * ```typescript\n * // С библиотекой jwt-decode\n * import { jwtDecode } from 'jwt-decode';\n *\n * decodeJwt: (token) => {\n * try {\n * return jwtDecode<{ userId: string }>(token);\n * } catch {\n * return null;\n * }\n * }\n * ```\n */\n decodeJwt?: (token: string) => { userId?: string } | null;\n}\n\n/**\n * Обёртка над axios для выполнения HTTP-запросов\n *\n * Предоставляет типизированные методы для всех HTTP-операций.\n * Автоматически извлекает `data` из ответа axios.\n *\n * @example\n * ```typescript\n * interface User {\n * id: string;\n * name: string;\n * email: string;\n * }\n *\n * // GET запрос с типизацией\n * const user = await apiService.get<User>('/users/me');\n * console.log(user.name);\n *\n * // POST запрос\n * const newUser = await apiService.post<User>('/users', {\n * name: 'John',\n * email: 'john@example.com',\n * });\n *\n * // С дополнительными параметрами axios\n * const data = await apiService.get<Data>('/endpoint', {\n * params: { page: 1, limit: 10 },\n * headers: { 'X-Custom-Header': 'value' },\n * });\n * ```\n */\nexport interface ApiService {\n /**\n * Выполняет GET-запрос\n *\n * Автоматически добавляет cache-buster параметр `_t` для предотвращения кэширования.\n *\n * @typeParam T - Тип данных ответа\n * @param url - URL эндпоинта (относительно baseURL сервиса)\n * @param config - Дополнительные параметры axios (headers, params и т.д.)\n * @returns Промис с данными ответа\n *\n * @example\n * ```typescript\n * // Простой GET\n * const users = await service.get<User[]>('/users');\n *\n * // С query параметрами\n * const filtered = await service.get<User[]>('/users', {\n * params: { role: 'admin', active: true }\n * });\n * ```\n */\n get: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>;\n\n /**\n * Выполняет POST-запрос\n *\n * @typeParam T - Тип данных ответа\n * @param url - URL эндпоинта (относительно baseURL сервиса)\n * @param data - Тело запроса (будет сериализовано в JSON)\n * @param config - Дополнительные параметры axios\n * @returns Промис с данными ответа\n *\n * @example\n * ```typescript\n * const newUser = await service.post<User>('/users', {\n * name: 'John Doe',\n * email: 'john@example.com',\n * });\n * ```\n */\n post: <T>(\n url: string,\n data?: unknown,\n config?: AxiosRequestConfig,\n ) => Promise<T>;\n\n /**\n * Выполняет PUT-запрос\n *\n * @typeParam T - Тип данных ответа\n * @param url - URL эндпоинта\n * @param data - Тело запроса для обновления\n * @param config - Дополнительные параметры axios\n * @returns Промис с данными ответа\n *\n * @example\n * ```typescript\n * const updated = await service.put<User>('/users/123', {\n * name: 'Jane Doe',\n * });\n * ```\n */\n put: <T>(\n url: string,\n data?: unknown,\n config?: AxiosRequestConfig,\n ) => Promise<T>;\n\n /**\n * Выполняет PATCH-запрос\n *\n * @typeParam T - Тип данных ответа\n * @param url - URL эндпоинта\n * @param data - Тело запроса для частичного обновления\n * @param config - Дополнительные параметры axios\n * @returns Промис с данными ответа\n *\n * @example\n * ```typescript\n * const updated = await service.patch<User>('/users/123', {\n * name: 'Jane Doe',\n * });\n * ```\n */\n patch: <T>(\n url: string,\n data?: unknown,\n config?: AxiosRequestConfig,\n ) => Promise<T>;\n\n /**\n * Выполняет DELETE-запрос\n *\n * @typeParam T - Тип данных ответа\n * @param url - URL эндпоинта\n * @param config - Дополнительные параметры axios\n * @returns Промис с данными ответа\n *\n * @example\n * ```typescript\n * await service.delete<void>('/users/123');\n * ```\n */\n delete: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>;\n\n /**\n * Доступ к нативному экземпляру axios\n *\n * Используйте для добавления кастомных interceptors или\n * выполнения нестандартных запросов.\n *\n * @example\n * ```typescript\n * // Добавление кастомного interceptor\n * service.axiosInstance.interceptors.request.use(config => {\n * config.headers['X-Request-Id'] = generateId();\n * return config;\n * });\n * ```\n */\n axiosInstance: AxiosInstance;\n}\n\n/**\n * Главный объект API клиента\n *\n * Содержит настроенные сервисы для каждого микросервиса\n * и менеджер обновления токенов.\n *\n * @example\n * ```typescript\n * const apiClient = createApiClient({ urls: {...} });\n *\n * // Использование сервисов\n * const user = await apiClient.userProgressService.get('/me');\n * const puzzles = await apiClient.puzzleController.get('/puzzles');\n *\n * // Работа с токенами\n * const currentToken = apiClient.tokenRefreshManager.getAccessToken();\n * ```\n */\nexport interface ApiClient {\n /**\n * Сервис авторизации\n *\n * Используется для логина, регистрации, обновления токенов.\n */\n authService: ApiService;\n\n /**\n * Сервис прогресса пользователя\n *\n * Используется для получения и обновления прогресса обучения.\n */\n userProgressService: ApiService;\n\n /**\n * Сервис рекомендаций\n *\n * Используется для получения персонализированных рекомендаций.\n */\n recommendationService: ApiService;\n\n /**\n * Сервис управления партнёрами\n */\n partnerManager: ApiService;\n\n /**\n * Сервис управления приключениями\n */\n adventureManager: ApiService;\n\n /**\n * Сервис агрегации данных для клиента\n */\n bff: ApiService;\n\n /**\n * Контроллер пазлов/задач\n *\n * Используется для работы с учебными задачами и пазлами.\n */\n puzzleController: ApiService;\n\n /**\n * Основной монолитный сервис\n *\n * Используется для запросов, не выделенных в отдельные микросервисы.\n */\n monolith: ApiService;\n\n /**\n * Менеджер обновления токенов\n *\n * Предоставляет доступ к функциям работы с токенами.\n * Можно использовать с RTK Query или другими HTTP-клиентами.\n *\n * @example\n * ```typescript\n * // Получение текущего токена\n * const token = apiClient.tokenRefreshManager.getAccessToken();\n *\n * // Обработка 401 в кастомном клиенте\n * if (response.status === 401) {\n * const newToken = await apiClient.tokenRefreshManager.handle401();\n * }\n * ```\n */\n tokenRefreshManager: TokenRefreshManager;\n}\n\n// ============================================================================\n// Retry Key Symbol\n// ============================================================================\n\nconst RETRY_KEY = Symbol('retry');\n\ninterface ExtendedAxiosRequestConfig extends InternalAxiosRequestConfig {\n [RETRY_KEY]?: boolean;\n}\n\n// ============================================================================\n// Default implementations\n// ============================================================================\n\nconst defaultNavigateToLogin = () => {\n localStorage.removeItem('accessToken');\n localStorage.removeItem('refreshToken');\n window.location.href = '#/login';\n};\n\nconst defaultDecodeJwt = (token: string): { userId?: string } | null => {\n try {\n const base64Url = token.split('.')[1];\n const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');\n const jsonPayload = decodeURIComponent(\n atob(base64)\n .split('')\n .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))\n .join(''),\n );\n return JSON.parse(jsonPayload);\n } catch {\n return null;\n }\n};\n\n// ============================================================================\n// Create Axios Instance with Interceptors\n// ============================================================================\n\nconst createAxiosInstance = (\n baseUrl: string,\n tokenRefreshManager: TokenRefreshManager,\n onNavigateToLogin: () => void,\n timeout: number,\n decodeJwt: (token: string) => { userId?: string } | null,\n): AxiosInstance => {\n const instance = axios.create({\n baseURL: baseUrl,\n timeout,\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n instance.interceptors.request.use(\n requestConfig => {\n const isRefreshRequest =\n requestConfig.url?.includes('auth/refresh-token');\n const accessToken = tokenRefreshManager.getAccessToken();\n const refreshToken = tokenRefreshManager.getRefreshToken();\n\n const token = isRefreshRequest ? refreshToken : accessToken;\n if (token) {\n Object.assign(requestConfig.headers, {\n Authorization: `Bearer ${token}`,\n });\n }\n\n const decodedToken = decodeJwt(accessToken || '');\n if (decodedToken?.userId) {\n Object.assign(requestConfig.headers, {\n 'X-User-Id': decodedToken.userId,\n });\n }\n\n return requestConfig;\n },\n error => Promise.reject(error),\n );\n\n instance.interceptors.response.use(\n response => response,\n async (error: AxiosError) => {\n const originalRequest = error.config as ExtendedAxiosRequestConfig;\n\n if (!originalRequest) {\n return Promise.reject(error);\n }\n\n const isRefreshRequest =\n originalRequest.url?.includes('auth/refresh-token');\n\n if (error.response?.status === 401) {\n const responseData = error.response?.data as\n | { message?: string }\n | undefined;\n const isInvalidToken = responseData?.message === 'Invalid token';\n\n if (isInvalidToken) {\n onNavigateToLogin();\n return Promise.reject(error);\n }\n\n if (isRefreshRequest) {\n onNavigateToLogin();\n return Promise.reject(error);\n }\n\n if (originalRequest[RETRY_KEY]) {\n onNavigateToLogin();\n return Promise.reject(error);\n }\n\n if (tokenRefreshManager.isRefreshInProgress()) {\n try {\n const newToken = await tokenRefreshManager.waitForTokenRefresh();\n const retryConfig = { ...originalRequest };\n Object.assign(retryConfig.headers, {\n Authorization: `Bearer ${newToken}`,\n });\n return instance(retryConfig);\n } catch {\n return Promise.reject(error);\n }\n }\n\n const retryConfig: ExtendedAxiosRequestConfig = {\n ...originalRequest,\n [RETRY_KEY]: true,\n };\n\n try {\n const newToken = await tokenRefreshManager.handle401(false);\n\n if (!newToken) {\n return Promise.reject(new Error('Failed to refresh token'));\n }\n\n Object.assign(retryConfig.headers, {\n Authorization: `Bearer ${newToken}`,\n });\n return instance(retryConfig);\n } catch (refreshError) {\n onNavigateToLogin();\n return Promise.reject(refreshError);\n }\n }\n\n if (error.response?.status === 403) {\n onNavigateToLogin();\n return Promise.reject(error);\n }\n\n if (error.response?.status === 400 && isRefreshRequest) {\n await new Promise<void>(resolve => {\n setTimeout(resolve, 1000);\n });\n\n const newToken = tokenRefreshManager.getAccessToken();\n if (newToken) {\n const retryConfig = { ...originalRequest };\n Object.assign(retryConfig.headers, {\n Authorization: `Bearer ${newToken}`,\n });\n return instance(retryConfig);\n }\n\n onNavigateToLogin();\n return Promise.reject(new Error('Failed to refresh token'));\n }\n\n return Promise.reject(error);\n },\n );\n\n return instance;\n};\n\n// ============================================================================\n// Create API Service Wrapper\n// ============================================================================\n\nconst createApiService = (axiosInstance: AxiosInstance): ApiService => {\n return {\n get: <T>(url: string, config?: AxiosRequestConfig): Promise<T> => {\n const cacheBuster = `${url.includes('?') ? '&' : '?'}_t=${Date.now()}`;\n return axiosInstance\n .get(url + cacheBuster, config)\n .then(response => response.data);\n },\n post: <T>(\n url: string,\n data?: unknown,\n config?: AxiosRequestConfig,\n ): Promise<T> =>\n axiosInstance.post(url, data, config).then(response => response.data),\n put: <T>(\n url: string,\n data?: unknown,\n config?: AxiosRequestConfig,\n ): Promise<T> =>\n axiosInstance.put(url, data, config).then(response => response.data),\n patch: <T>(\n url: string,\n data?: unknown,\n config?: AxiosRequestConfig,\n ): Promise<T> =>\n axiosInstance.patch(url, data, config).then(response => response.data),\n delete: <T>(url: string, config?: AxiosRequestConfig): Promise<T> =>\n axiosInstance.delete(url, config).then(response => response.data),\n axiosInstance,\n };\n};\n\n// ============================================================================\n// Main Factory Function\n// ============================================================================\n\n/**\n * Создаёт настроенный API клиент\n *\n * Фабричная функция, которая создаёт полностью настроенный API клиент\n * с поддержкой автоматического обновления токенов, обработкой ошибок\n * авторизации и типизированными методами запросов.\n *\n * **Особенности:**\n * - Автоматическое добавление Authorization заголовка\n * - Автоматическое обновление токена при 401 ошибке\n * - Очередь запросов при одновременном обновлении токена\n * - Добавление X-User-Id заголовка из JWT\n * - Cache-busting для GET запросов\n *\n * @param config - Конфигурация клиента\n * @returns Настроенный API клиент\n *\n * @example\n * ```typescript\n * import { createApiClient } from 'mbt-api-client';\n *\n * const apiClient = createApiClient({\n * urls: {\n * authService: 'https://auth.example.com',\n * userProgressService: 'https://progress.example.com',\n * recommendationService: 'https://rec.example.com',\n * partnerManager: 'https://partners.example.com',\n * puzzleController: 'https://puzzles.example.com',\n * monolith: 'https://api.example.com',\n * },\n * onNavigateToLogin: () => {\n * // Очистка состояния и редирект\n * store.dispatch(logout());\n * navigate('/login');\n * },\n * timeout: 15000, // 15 секунд\n * });\n *\n * // Использование\n * try {\n * const user = await apiClient.userProgressService.get<User>('/me');\n * console.log('Пользователь:', user);\n * } catch (error) {\n * console.error('Ошибка:', error);\n * }\n * ```\n */\nexport const createApiClient = (config: ApiClientConfig): ApiClient => {\n const {\n urls,\n onNavigateToLogin = defaultNavigateToLogin,\n timeout = 10000,\n decodeJwt = defaultDecodeJwt,\n } = config;\n\n const authAxiosInstance = axios.create({\n baseURL: urls.authService,\n timeout,\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n const refreshTokenFn = async (): Promise<TokenRefreshResult> => {\n try {\n const refreshToken = localStorage.getItem('refreshToken');\n if (!refreshToken) {\n return { ok: false };\n }\n\n const response = await authAxiosInstance.post(\n '/auth/refresh-token',\n {},\n {\n headers: {\n Authorization: `Bearer ${refreshToken}`,\n },\n },\n );\n\n const { accessToken, refreshToken: newRefreshToken } = response.data;\n\n return {\n ok: true,\n accessToken,\n refreshToken: newRefreshToken,\n };\n } catch {\n return { ok: false };\n }\n };\n\n const tokenRefreshManager = createTokenRefreshManager({\n refreshTokenFn,\n onNavigateToLogin,\n });\n\n authAxiosInstance.interceptors.request.use(\n requestConfig => {\n const isRefreshRequest =\n requestConfig.url?.includes('auth/refresh-token');\n const accessToken = tokenRefreshManager.getAccessToken();\n const refreshToken = tokenRefreshManager.getRefreshToken();\n\n const token = isRefreshRequest ? refreshToken : accessToken;\n if (token && !requestConfig.headers.Authorization) {\n Object.assign(requestConfig.headers, {\n Authorization: `Bearer ${token}`,\n });\n }\n\n const decodedToken = decodeJwt(accessToken || '');\n if (decodedToken?.userId) {\n Object.assign(requestConfig.headers, {\n 'X-User-Id': decodedToken.userId,\n });\n }\n\n return requestConfig;\n },\n error => Promise.reject(error),\n );\n\n const userProgressAxiosInstance = createAxiosInstance(\n urls.userProgressService,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const recommendationAxiosInstance = createAxiosInstance(\n urls.recommendationService,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const partnerManagerAxiosInstance = createAxiosInstance(\n urls.partnerManager,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const adventureManagerAxiosInstance = createAxiosInstance(\n urls.adventureManager,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const bffAxiosInstance = createAxiosInstance(\n urls.bff,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const puzzleControllerAxiosInstance = createAxiosInstance(\n urls.puzzleController,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const monolithAxiosInstance = createAxiosInstance(\n urls.monolith,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n return {\n authService: createApiService(authAxiosInstance),\n userProgressService: createApiService(userProgressAxiosInstance),\n recommendationService: createApiService(recommendationAxiosInstance),\n partnerManager: createApiService(partnerManagerAxiosInstance),\n adventureManager: createApiService(adventureManagerAxiosInstance),\n bff: createApiService(bffAxiosInstance),\n puzzleController: createApiService(puzzleControllerAxiosInstance),\n monolith: createApiService(monolithAxiosInstance),\n tokenRefreshManager,\n };\n};\n\n// ============================================================================\n// React Context\n// ============================================================================\n\n/**\n * React Context для API клиента\n *\n * Используется внутри ApiClientProvider для передачи клиента\n * через дерево компонентов.\n *\n * @example\n * ```typescript\n * // Для кастомных случаев (обычно используйте ApiClientProvider)\n * <ApiClientContext.Provider value={apiClient}>\n * <App />\n * </ApiClientContext.Provider>\n * ```\n */\nexport const ApiClientContext = createContext<ApiClient | null>(null);\n\n/**\n * React хук для доступа к API клиенту\n *\n * Получает API клиент из контекста. Должен использоваться внутри\n * компонента, обёрнутого в ApiClientProvider.\n *\n * @returns API клиент с типизированными сервисами\n * @throws Error если используется вне ApiClientProvider\n *\n * @example\n * ```typescript\n * import { useApiClient } from 'mbt-api-client';\n *\n * function UserProfile() {\n * const { userProgressService } = useApiClient();\n * const [user, setUser] = useState<User | null>(null);\n *\n * useEffect(() => {\n * userProgressService.get<User>('/me')\n * .then(setUser)\n * .catch(console.error);\n * }, []);\n *\n * return <div>{user?.name}</div>;\n * }\n *\n * // С деструктуризацией нескольких сервисов\n * function Dashboard() {\n * const {\n * userProgressService,\n * recommendationService,\n * puzzleController,\n * } = useApiClient();\n *\n * // Параллельные запросы\n * const [progress, recommendations, puzzles] = await Promise.all([\n * userProgressService.get('/progress'),\n * recommendationService.get('/for-me'),\n * puzzleController.get('/puzzles'),\n * ]);\n * }\n * ```\n */\nexport const useApiClient = (): ApiClient => {\n const context = useContext(ApiClientContext);\n if (!context) {\n throw new Error('useApiClient must be used within ApiClientProvider');\n }\n return context;\n};\n\n/**\n * React Provider для API клиента\n *\n * Оборачивает приложение для предоставления доступа к API клиенту\n * через хук useApiClient.\n *\n * @example\n * ```typescript\n * import { createApiClient, ApiClientProvider } from 'mbt-api-client';\n *\n * // Создание клиента (обычно в отдельном файле)\n * const apiClient = createApiClient({\n * urls: { ... },\n * });\n *\n * // В корне приложения\n * function App() {\n * return (\n * <ApiClientProvider value={apiClient}>\n * <Router>\n * <Routes />\n * </Router>\n * </ApiClientProvider>\n * );\n * }\n *\n * // С React Query\n * function App() {\n * return (\n * <QueryClientProvider client={queryClient}>\n * <ApiClientProvider value={apiClient}>\n * <Routes />\n * </ApiClientProvider>\n * </QueryClientProvider>\n * );\n * }\n * ```\n */\nexport const ApiClientProvider = ApiClientContext.Provider;\n\n// ============================================================================\n// Re-exports\n// ============================================================================\n\nexport {\n TokenRefreshManager,\n createTokenRefreshManager,\n type TokenRefreshConfig,\n type TokenRefreshResult,\n} from './token-refresh';\n","/**\n * Модуль управления обновлением токенов\n *\n * Реализует механизм обновления JWT токенов с очередью запросов.\n * Предотвращает множественные одновременные запросы на обновление токена,\n * когда несколько параллельных API-запросов получают ошибку 401.\n *\n * @example\n * ```typescript\n * // Создание менеджера с кастомными функциями\n * const tokenManager = createTokenRefreshManager({\n * refreshTokenFn: async () => {\n * const response = await fetch('/api/auth/refresh');\n * const data = await response.json();\n * return { ok: true, accessToken: data.accessToken };\n * },\n * onNavigateToLogin: () => router.push('/login'),\n * });\n *\n * // Использование с RTK Query или другими клиентами\n * if (response.status === 401) {\n * const newToken = await tokenManager.handle401();\n * // Повторить запрос с новым токеном\n * }\n * ```\n *\n * @module token-refresh\n */\n\n/**\n * Конфигурация менеджера обновления токенов\n *\n * @example\n * ```typescript\n * const config: TokenRefreshConfig = {\n * refreshTokenFn: async () => {\n * // Ваша логика обновления токена\n * return { ok: true, accessToken: 'new-token' };\n * },\n * onNavigateToLogin: () => window.location.href = '/login',\n * getAccessToken: () => localStorage.getItem('accessToken'),\n * getRefreshToken: () => localStorage.getItem('refreshToken'),\n * setTokens: (access, refresh) => {\n * localStorage.setItem('accessToken', access);\n * if (refresh) localStorage.setItem('refreshToken', refresh);\n * },\n * clearTokens: () => {\n * localStorage.removeItem('accessToken');\n * localStorage.removeItem('refreshToken');\n * },\n * };\n * ```\n */\nexport interface TokenRefreshConfig {\n /**\n * Функция для выполнения запроса на обновление токена\n *\n * Должна вернуть объект с `ok: true` и новым `accessToken` при успехе,\n * или `ok: false` при ошибке.\n *\n * @returns Промис с результатом обновления токена\n *\n * @example\n * ```typescript\n * refreshTokenFn: async () => {\n * try {\n * const response = await axios.post('/auth/refresh-token', {}, {\n * headers: { Authorization: `Bearer ${refreshToken}` }\n * });\n * return {\n * ok: true,\n * accessToken: response.data.accessToken,\n * refreshToken: response.data.refreshToken,\n * };\n * } catch {\n * return { ok: false };\n * }\n * }\n * ```\n */\n refreshTokenFn: () => Promise<TokenRefreshResult>;\n\n /**\n * Колбэк для перенаправления на страницу логина\n *\n * Вызывается когда:\n * - Токен невалиден (message: 'Invalid token')\n * - Обновление токена не удалось\n * - Refresh token истёк\n *\n * @default Очищает localStorage и редиректит на '#/login'\n *\n * @example\n * ```typescript\n * onNavigateToLogin: () => {\n * // Для React Router\n * navigate('/login');\n * // Или для Next.js\n * router.push('/login');\n * }\n * ```\n */\n onNavigateToLogin?: () => void;\n\n /**\n * Функция получения текущего access токена\n *\n * @default () => localStorage.getItem('accessToken')\n *\n * @example\n * ```typescript\n * // Для хранения в Redux\n * getAccessToken: () => store.getState().auth.accessToken\n * ```\n */\n getAccessToken?: () => string | null;\n\n /**\n * Функция получения текущего refresh токена\n *\n * @default () => localStorage.getItem('refreshToken')\n */\n getRefreshToken?: () => string | null;\n\n /**\n * Функция сохранения новых токенов после обновления\n *\n * @param accessToken - Новый access токен\n * @param refreshToken - Новый refresh токен (опционально)\n *\n * @default Сохраняет в localStorage\n *\n * @example\n * ```typescript\n * // Для хранения в Redux\n * setTokens: (access, refresh) => {\n * store.dispatch(setAuthTokens({ accessToken: access, refreshToken: refresh }));\n * }\n * ```\n */\n setTokens?: (accessToken: string, refreshToken?: string) => void;\n\n /**\n * Функция очистки токенов (при выходе или ошибке авторизации)\n *\n * @default Удаляет 'accessToken' и 'refreshToken' из localStorage\n */\n clearTokens?: () => void;\n}\n\n/**\n * Результат операции обновления токена\n *\n * @example\n * ```typescript\n * // Успешное обновление\n * const success: TokenRefreshResult = {\n * ok: true,\n * accessToken: 'eyJhbGciOiJIUzI1...',\n * refreshToken: 'dGhpcyBpcyBhIHJl...',\n * };\n *\n * // Ошибка обновления\n * const failure: TokenRefreshResult = { ok: false };\n * ```\n */\nexport interface TokenRefreshResult {\n /**\n * Успешность операции обновления\n *\n * `true` - токен успешно обновлён\n * `false` - ошибка обновления (пользователь будет перенаправлен на логин)\n */\n ok: boolean;\n\n /**\n * Новый access токен (только при `ok: true`)\n */\n accessToken?: string;\n\n /**\n * Новый refresh токен (опционально, только при `ok: true`)\n */\n refreshToken?: string;\n}\n\n/**\n * Интерфейс запроса в очереди ожидания обновления токена\n * @internal\n */\nexport interface QueuedRequest<T = unknown> {\n /** Функция разрешения промиса с новым токеном */\n resolve: (value: T) => void;\n /** Функция отклонения промиса при ошибке */\n reject: (error: unknown) => void;\n}\n\nconst defaultGetAccessToken = (): string | null =>\n localStorage.getItem('accessToken');\n\nconst defaultGetRefreshToken = (): string | null =>\n localStorage.getItem('refreshToken');\n\nconst defaultSetTokens = (accessToken: string, refreshToken?: string): void => {\n localStorage.setItem('accessToken', accessToken);\n if (refreshToken) {\n localStorage.setItem('refreshToken', refreshToken);\n }\n};\n\nconst defaultClearTokens = (): void => {\n localStorage.removeItem('accessToken');\n localStorage.removeItem('refreshToken');\n};\n\nconst defaultNavigateToLogin = (): void => {\n defaultClearTokens();\n window.location.href = '#/login';\n};\n\n/**\n * Менеджер обновления токенов с очередью запросов\n *\n * Решает проблему одновременного обновления токена при множественных\n * параллельных запросах, получивших 401 ошибку.\n *\n * **Как это работает:**\n * 1. Первый запрос с 401 инициирует обновление токена\n * 2. Последующие запросы с 401 добавляются в очередь ожидания\n * 3. После успешного обновления все запросы в очереди получают новый токен\n * 4. При ошибке обновления все запросы получают ошибку\n *\n * @example\n * ```typescript\n * // Создание менеджера\n * const tokenManager = new TokenRefreshManager({\n * refreshTokenFn: async () => {\n * const res = await fetch('/api/refresh');\n * const data = await res.json();\n * return { ok: true, accessToken: data.token };\n * },\n * });\n *\n * // Использование в axios interceptor\n * axios.interceptors.response.use(\n * response => response,\n * async error => {\n * if (error.response?.status === 401) {\n * const newToken = await tokenManager.handle401();\n * if (newToken) {\n * error.config.headers.Authorization = `Bearer ${newToken}`;\n * return axios(error.config);\n * }\n * }\n * return Promise.reject(error);\n * }\n * );\n * ```\n */\nexport class TokenRefreshManager {\n private isRefreshing = false;\n private refreshPromise: Promise<TokenRefreshResult> | null = null;\n private queuedRequests: QueuedRequest<string>[] = [];\n private config: Required<TokenRefreshConfig>;\n\n /**\n * Создаёт новый экземпляр TokenRefreshManager\n *\n * @param config - Конфигурация менеджера\n *\n * @example\n * ```typescript\n * const manager = new TokenRefreshManager({\n * refreshTokenFn: myRefreshFunction,\n * onNavigateToLogin: () => router.push('/login'),\n * });\n * ```\n */\n constructor(config: TokenRefreshConfig) {\n this.config = {\n refreshTokenFn: config.refreshTokenFn,\n onNavigateToLogin: config.onNavigateToLogin ?? defaultNavigateToLogin,\n getAccessToken: config.getAccessToken ?? defaultGetAccessToken,\n getRefreshToken: config.getRefreshToken ?? defaultGetRefreshToken,\n setTokens: config.setTokens ?? defaultSetTokens,\n clearTokens: config.clearTokens ?? defaultClearTokens,\n };\n }\n\n /**\n * Получает текущий access токен\n *\n * @returns Access токен или null, если токен отсутствует\n *\n * @example\n * ```typescript\n * const token = manager.getAccessToken();\n * if (token) {\n * headers.Authorization = `Bearer ${token}`;\n * }\n * ```\n */\n getAccessToken(): string | null {\n return this.config.getAccessToken();\n }\n\n /**\n * Получает текущий refresh токен\n *\n * @returns Refresh токен или null, если токен отсутствует\n */\n getRefreshToken(): string | null {\n return this.config.getRefreshToken();\n }\n\n /**\n * Проверяет, идёт ли в данный момент процесс обновления токена\n *\n * Используйте для определения, нужно ли ставить запрос в очередь\n * или инициировать новое обновление.\n *\n * @returns `true` если обновление в процессе, `false` если нет\n *\n * @example\n * ```typescript\n * if (manager.isRefreshInProgress()) {\n * // Ждём завершения текущего обновления\n * const newToken = await manager.waitForTokenRefresh();\n * } else {\n * // Инициируем новое обновление\n * await manager.refreshToken();\n * }\n * ```\n */\n isRefreshInProgress(): boolean {\n return this.isRefreshing;\n }\n\n /**\n * Добавляет запрос в очередь ожидания обновления токена\n *\n * Возвращает промис, который разрешится с новым токеном после\n * успешного обновления или будет отклонён при ошибке.\n *\n * @returns Промис с новым access токеном\n *\n * @example\n * ```typescript\n * // В response interceptor при множественных 401\n * if (manager.isRefreshInProgress()) {\n * try {\n * const newToken = await manager.waitForTokenRefresh();\n * // Повторить запрос с новым токеном\n * } catch {\n * // Обновление не удалось\n * }\n * }\n * ```\n */\n waitForTokenRefresh(): Promise<string> {\n return new Promise((resolve, reject) => {\n this.queuedRequests.push({ resolve, reject });\n });\n }\n\n /**\n * Выполняет обновление токена\n *\n * Если обновление уже в процессе, вернёт существующий промис.\n * После обновления уведомляет все запросы в очереди.\n *\n * @returns Промис с результатом обновления\n *\n * @example\n * ```typescript\n * const result = await manager.refreshToken();\n * if (result.ok) {\n * console.log('Новый токен:', result.accessToken);\n * } else {\n * console.log('Ошибка обновления токена');\n * }\n * ```\n */\n async refreshToken(): Promise<TokenRefreshResult> {\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n this.isRefreshing = true;\n\n this.refreshPromise = this.performRefresh().finally(() => {\n setTimeout(() => {\n this.refreshPromise = null;\n }, 100);\n });\n\n return this.refreshPromise;\n }\n\n /**\n * Обрабатывает ошибку 401 — либо обновляет токен, либо перенаправляет на логин\n *\n * Основной метод для использования в interceptors. Автоматически:\n * - Перенаправляет на логин при невалидном токене\n * - Ставит запрос в очередь, если обновление уже идёт\n * - Инициирует обновление, если ещё не запущено\n *\n * @param isInvalidToken - Если true, токен считается невалидным и будет выполнен переход на логин\n * @returns Промис с новым access токеном или null при ошибке\n *\n * @example\n * ```typescript\n * // В axios response interceptor\n * if (error.response?.status === 401) {\n * const isInvalid = error.response.data?.message === 'Invalid token';\n * const newToken = await manager.handle401(isInvalid);\n *\n * if (newToken) {\n * // Повторить запрос с новым токеном\n * error.config.headers.Authorization = `Bearer ${newToken}`;\n * return axios(error.config);\n * }\n * // newToken === null означает переход на логин\n * }\n * ```\n */\n async handle401(isInvalidToken = false): Promise<string | null> {\n if (isInvalidToken) {\n this.config.onNavigateToLogin();\n return null;\n }\n\n if (this.isRefreshing) {\n return this.waitForTokenRefresh();\n }\n\n const result = await this.refreshToken();\n\n if (!result.ok) {\n this.config.onNavigateToLogin();\n return null;\n }\n\n return result.accessToken || this.config.getAccessToken();\n }\n\n /**\n * Принудительно перенаправляет на страницу логина\n *\n * Вызывает колбэк onNavigateToLogin из конфигурации.\n *\n * @example\n * ```typescript\n * // При необходимости принудительного выхода\n * manager.navigateToLogin();\n * ```\n */\n navigateToLogin(): void {\n this.config.onNavigateToLogin();\n }\n\n /**\n * Сбрасывает внутреннее состояние менеджера\n *\n * Полезно для тестирования или при необходимости\n * принудительно сбросить очередь запросов.\n *\n * @example\n * ```typescript\n * // В тестах\n * beforeEach(() => {\n * manager.reset();\n * });\n * ```\n */\n reset(): void {\n this.isRefreshing = false;\n this.refreshPromise = null;\n this.queuedRequests = [];\n }\n\n private async performRefresh(): Promise<TokenRefreshResult> {\n try {\n const result = await this.config.refreshTokenFn();\n\n if (result.ok && result.accessToken) {\n this.config.setTokens(result.accessToken, result.refreshToken);\n this.notifyQueueSuccess(result.accessToken);\n } else {\n this.notifyQueueFailure(new Error('Token refresh failed'));\n }\n\n this.isRefreshing = false;\n return result;\n } catch (error) {\n this.isRefreshing = false;\n this.notifyQueueFailure(error);\n return { ok: false };\n }\n }\n\n private notifyQueueSuccess(newToken: string): void {\n this.queuedRequests.forEach(({ resolve }) => resolve(newToken));\n this.queuedRequests = [];\n }\n\n private notifyQueueFailure(error: unknown): void {\n this.queuedRequests.forEach(({ reject }) => reject(error));\n this.queuedRequests = [];\n }\n}\n\n/**\n * Создаёт новый экземпляр TokenRefreshManager\n *\n * Фабричная функция для создания менеджера обновления токенов.\n * Предпочтительный способ создания вместо прямого вызова конструктора.\n *\n * @param config - Конфигурация менеджера\n * @returns Новый экземпляр TokenRefreshManager\n *\n * @example\n * ```typescript\n * import { createTokenRefreshManager } from 'mbt-api-client';\n *\n * const tokenManager = createTokenRefreshManager({\n * refreshTokenFn: async () => {\n * const response = await fetch('/api/auth/refresh', {\n * method: 'POST',\n * headers: {\n * Authorization: `Bearer ${localStorage.getItem('refreshToken')}`,\n * },\n * });\n *\n * if (!response.ok) {\n * return { ok: false };\n * }\n *\n * const data = await response.json();\n * return {\n * ok: true,\n * accessToken: data.accessToken,\n * refreshToken: data.refreshToken,\n * };\n * },\n * onNavigateToLogin: () => {\n * window.location.href = '/login';\n * },\n * });\n *\n * // Использование\n * const newToken = await tokenManager.handle401();\n * ```\n */\nexport const createTokenRefreshManager = (\n config: TokenRefreshConfig,\n): TokenRefreshManager => {\n return new TokenRefreshManager(config);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACqCA,mBAKO;AACP,mBAA0C;;;AC0J1C,IAAM,wBAAwB,MAC5B,aAAa,QAAQ,aAAa;AAEpC,IAAM,yBAAyB,MAC7B,aAAa,QAAQ,cAAc;AAErC,IAAM,mBAAmB,CAAC,aAAqB,iBAAgC;AAC7E,eAAa,QAAQ,eAAe,WAAW;AAC/C,MAAI,cAAc;AAChB,iBAAa,QAAQ,gBAAgB,YAAY;AAAA,EACnD;AACF;AAEA,IAAM,qBAAqB,MAAY;AACrC,eAAa,WAAW,aAAa;AACrC,eAAa,WAAW,cAAc;AACxC;AAEA,IAAM,yBAAyB,MAAY;AACzC,qBAAmB;AACnB,SAAO,SAAS,OAAO;AACzB;AAyCO,IAAM,sBAAN,MAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmB/B,YAAY,QAA4B;AAlBxC,SAAQ,eAAe;AACvB,SAAQ,iBAAqD;AAC7D,SAAQ,iBAA0C,CAAC;AAiBjD,SAAK,SAAS;AAAA,MACZ,gBAAgB,OAAO;AAAA,MACvB,mBAAmB,OAAO,qBAAqB;AAAA,MAC/C,gBAAgB,OAAO,kBAAkB;AAAA,MACzC,iBAAiB,OAAO,mBAAmB;AAAA,MAC3C,WAAW,OAAO,aAAa;AAAA,MAC/B,aAAa,OAAO,eAAe;AAAA,IACrC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,iBAAgC;AAC9B,WAAO,KAAK,OAAO,eAAe;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,kBAAiC;AAC/B,WAAO,KAAK,OAAO,gBAAgB;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,sBAA+B;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuBA,sBAAuC;AACrC,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,eAAe,KAAK,EAAE,SAAS,OAAO,CAAC;AAAA,IAC9C,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,MAAM,eAA4C;AAChD,QAAI,KAAK,gBAAgB;AACvB,aAAO,KAAK;AAAA,IACd;AAEA,SAAK,eAAe;AAEpB,SAAK,iBAAiB,KAAK,eAAe,EAAE,QAAQ,MAAM;AACxD,iBAAW,MAAM;AACf,aAAK,iBAAiB;AAAA,MACxB,GAAG,GAAG;AAAA,IACR,CAAC;AAED,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA6BA,MAAM,UAAU,iBAAiB,OAA+B;AAC9D,QAAI,gBAAgB;AAClB,WAAK,OAAO,kBAAkB;AAC9B,aAAO;AAAA,IACT;AAEA,QAAI,KAAK,cAAc;AACrB,aAAO,KAAK,oBAAoB;AAAA,IAClC;AAEA,UAAM,SAAS,MAAM,KAAK,aAAa;AAEvC,QAAI,CAAC,OAAO,IAAI;AACd,WAAK,OAAO,kBAAkB;AAC9B,aAAO;AAAA,IACT;AAEA,WAAO,OAAO,eAAe,KAAK,OAAO,eAAe;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,kBAAwB;AACtB,SAAK,OAAO,kBAAkB;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,QAAc;AACZ,SAAK,eAAe;AACpB,SAAK,iBAAiB;AACtB,SAAK,iBAAiB,CAAC;AAAA,EACzB;AAAA,EAEA,MAAc,iBAA8C;AAC1D,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,OAAO,eAAe;AAEhD,UAAI,OAAO,MAAM,OAAO,aAAa;AACnC,aAAK,OAAO,UAAU,OAAO,aAAa,OAAO,YAAY;AAC7D,aAAK,mBAAmB,OAAO,WAAW;AAAA,MAC5C,OAAO;AACL,aAAK,mBAAmB,IAAI,MAAM,sBAAsB,CAAC;AAAA,MAC3D;AAEA,WAAK,eAAe;AACpB,aAAO;AAAA,IACT,SAAS,OAAO;AACd,WAAK,eAAe;AACpB,WAAK,mBAAmB,KAAK;AAC7B,aAAO,EAAE,IAAI,MAAM;AAAA,IACrB;AAAA,EACF;AAAA,EAEQ,mBAAmB,UAAwB;AACjD,SAAK,eAAe,QAAQ,CAAC,EAAE,QAAQ,MAAM,QAAQ,QAAQ,CAAC;AAC9D,SAAK,iBAAiB,CAAC;AAAA,EACzB;AAAA,EAEQ,mBAAmB,OAAsB;AAC/C,SAAK,eAAe,QAAQ,CAAC,EAAE,OAAO,MAAM,OAAO,KAAK,CAAC;AACzD,SAAK,iBAAiB,CAAC;AAAA,EACzB;AACF;AA4CO,IAAM,4BAA4B,CACvC,WACwB;AACxB,SAAO,IAAI,oBAAoB,MAAM;AACvC;;;AD/HA,IAAM,YAAY,uBAAO,OAAO;AAUhC,IAAMA,0BAAyB,MAAM;AACnC,eAAa,WAAW,aAAa;AACrC,eAAa,WAAW,cAAc;AACtC,SAAO,SAAS,OAAO;AACzB;AAEA,IAAM,mBAAmB,CAAC,UAA8C;AACtE,MAAI;AACF,UAAM,YAAY,MAAM,MAAM,GAAG,EAAE,CAAC;AACpC,UAAM,SAAS,UAAU,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AAC7D,UAAM,cAAc;AAAA,MAClB,KAAK,MAAM,EACR,MAAM,EAAE,EACR,IAAI,OAAK,OAAO,OAAO,EAAE,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE,CAAC,EAC9D,KAAK,EAAE;AAAA,IACZ;AACA,WAAO,KAAK,MAAM,WAAW;AAAA,EAC/B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,IAAM,sBAAsB,CAC1B,SACA,qBACA,mBACA,SACA,cACkB;AAClB,QAAM,WAAW,aAAAC,QAAM,OAAO;AAAA,IAC5B,SAAS;AAAA,IACT;AAAA,IACA,SAAS;AAAA,MACP,gBAAgB;AAAA,IAClB;AAAA,EACF,CAAC;AAED,WAAS,aAAa,QAAQ;AAAA,IAC5B,mBAAiB;AACf,YAAM,mBACJ,cAAc,KAAK,SAAS,oBAAoB;AAClD,YAAM,cAAc,oBAAoB,eAAe;AACvD,YAAM,eAAe,oBAAoB,gBAAgB;AAEzD,YAAM,QAAQ,mBAAmB,eAAe;AAChD,UAAI,OAAO;AACT,eAAO,OAAO,cAAc,SAAS;AAAA,UACnC,eAAe,UAAU,KAAK;AAAA,QAChC,CAAC;AAAA,MACH;AAEA,YAAM,eAAe,UAAU,eAAe,EAAE;AAChD,UAAI,cAAc,QAAQ;AACxB,eAAO,OAAO,cAAc,SAAS;AAAA,UACnC,aAAa,aAAa;AAAA,QAC5B,CAAC;AAAA,MACH;AAEA,aAAO;AAAA,IACT;AAAA,IACA,WAAS,QAAQ,OAAO,KAAK;AAAA,EAC/B;AAEA,WAAS,aAAa,SAAS;AAAA,IAC7B,cAAY;AAAA,IACZ,OAAO,UAAsB;AAC3B,YAAM,kBAAkB,MAAM;AAE9B,UAAI,CAAC,iBAAiB;AACpB,eAAO,QAAQ,OAAO,KAAK;AAAA,MAC7B;AAEA,YAAM,mBACJ,gBAAgB,KAAK,SAAS,oBAAoB;AAEpD,UAAI,MAAM,UAAU,WAAW,KAAK;AAClC,cAAM,eAAe,MAAM,UAAU;AAGrC,cAAM,iBAAiB,cAAc,YAAY;AAEjD,YAAI,gBAAgB;AAClB,4BAAkB;AAClB,iBAAO,QAAQ,OAAO,KAAK;AAAA,QAC7B;AAEA,YAAI,kBAAkB;AACpB,4BAAkB;AAClB,iBAAO,QAAQ,OAAO,KAAK;AAAA,QAC7B;AAEA,YAAI,gBAAgB,SAAS,GAAG;AAC9B,4BAAkB;AAClB,iBAAO,QAAQ,OAAO,KAAK;AAAA,QAC7B;AAEA,YAAI,oBAAoB,oBAAoB,GAAG;AAC7C,cAAI;AACF,kBAAM,WAAW,MAAM,oBAAoB,oBAAoB;AAC/D,kBAAMC,eAAc,EAAE,GAAG,gBAAgB;AACzC,mBAAO,OAAOA,aAAY,SAAS;AAAA,cACjC,eAAe,UAAU,QAAQ;AAAA,YACnC,CAAC;AACD,mBAAO,SAASA,YAAW;AAAA,UAC7B,QAAQ;AACN,mBAAO,QAAQ,OAAO,KAAK;AAAA,UAC7B;AAAA,QACF;AAEA,cAAM,cAA0C;AAAA,UAC9C,GAAG;AAAA,UACH,CAAC,SAAS,GAAG;AAAA,QACf;AAEA,YAAI;AACF,gBAAM,WAAW,MAAM,oBAAoB,UAAU,KAAK;AAE1D,cAAI,CAAC,UAAU;AACb,mBAAO,QAAQ,OAAO,IAAI,MAAM,yBAAyB,CAAC;AAAA,UAC5D;AAEA,iBAAO,OAAO,YAAY,SAAS;AAAA,YACjC,eAAe,UAAU,QAAQ;AAAA,UACnC,CAAC;AACD,iBAAO,SAAS,WAAW;AAAA,QAC7B,SAAS,cAAc;AACrB,4BAAkB;AAClB,iBAAO,QAAQ,OAAO,YAAY;AAAA,QACpC;AAAA,MACF;AAEA,UAAI,MAAM,UAAU,WAAW,KAAK;AAClC,0BAAkB;AAClB,eAAO,QAAQ,OAAO,KAAK;AAAA,MAC7B;AAEA,UAAI,MAAM,UAAU,WAAW,OAAO,kBAAkB;AACtD,cAAM,IAAI,QAAc,aAAW;AACjC,qBAAW,SAAS,GAAI;AAAA,QAC1B,CAAC;AAED,cAAM,WAAW,oBAAoB,eAAe;AACpD,YAAI,UAAU;AACZ,gBAAM,cAAc,EAAE,GAAG,gBAAgB;AACzC,iBAAO,OAAO,YAAY,SAAS;AAAA,YACjC,eAAe,UAAU,QAAQ;AAAA,UACnC,CAAC;AACD,iBAAO,SAAS,WAAW;AAAA,QAC7B;AAEA,0BAAkB;AAClB,eAAO,QAAQ,OAAO,IAAI,MAAM,yBAAyB,CAAC;AAAA,MAC5D;AAEA,aAAO,QAAQ,OAAO,KAAK;AAAA,IAC7B;AAAA,EACF;AAEA,SAAO;AACT;AAMA,IAAM,mBAAmB,CAAC,kBAA6C;AACrE,SAAO;AAAA,IACL,KAAK,CAAI,KAAa,WAA4C;AAChE,YAAM,cAAc,GAAG,IAAI,SAAS,GAAG,IAAI,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;AACpE,aAAO,cACJ,IAAI,MAAM,aAAa,MAAM,EAC7B,KAAK,cAAY,SAAS,IAAI;AAAA,IACnC;AAAA,IACA,MAAM,CACJ,KACA,MACA,WAEA,cAAc,KAAK,KAAK,MAAM,MAAM,EAAE,KAAK,cAAY,SAAS,IAAI;AAAA,IACtE,KAAK,CACH,KACA,MACA,WAEA,cAAc,IAAI,KAAK,MAAM,MAAM,EAAE,KAAK,cAAY,SAAS,IAAI;AAAA,IACrE,OAAO,CACL,KACA,MACA,WAEA,cAAc,MAAM,KAAK,MAAM,MAAM,EAAE,KAAK,cAAY,SAAS,IAAI;AAAA,IACvE,QAAQ,CAAI,KAAa,WACvB,cAAc,OAAO,KAAK,MAAM,EAAE,KAAK,cAAY,SAAS,IAAI;AAAA,IAClE;AAAA,EACF;AACF;AAqDO,IAAM,kBAAkB,CAAC,WAAuC;AACrE,QAAM;AAAA,IACJ;AAAA,IACA,oBAAoBF;AAAA,IACpB,UAAU;AAAA,IACV,YAAY;AAAA,EACd,IAAI;AAEJ,QAAM,oBAAoB,aAAAC,QAAM,OAAO;AAAA,IACrC,SAAS,KAAK;AAAA,IACd;AAAA,IACA,SAAS;AAAA,MACP,gBAAgB;AAAA,IAClB;AAAA,EACF,CAAC;AAED,QAAM,iBAAiB,YAAyC;AAC9D,QAAI;AACF,YAAM,eAAe,aAAa,QAAQ,cAAc;AACxD,UAAI,CAAC,cAAc;AACjB,eAAO,EAAE,IAAI,MAAM;AAAA,MACrB;AAEA,YAAM,WAAW,MAAM,kBAAkB;AAAA,QACvC;AAAA,QACA,CAAC;AAAA,QACD;AAAA,UACE,SAAS;AAAA,YACP,eAAe,UAAU,YAAY;AAAA,UACvC;AAAA,QACF;AAAA,MACF;AAEA,YAAM,EAAE,aAAa,cAAc,gBAAgB,IAAI,SAAS;AAEhE,aAAO;AAAA,QACL,IAAI;AAAA,QACJ;AAAA,QACA,cAAc;AAAA,MAChB;AAAA,IACF,QAAQ;AACN,aAAO,EAAE,IAAI,MAAM;AAAA,IACrB;AAAA,EACF;AAEA,QAAM,sBAAsB,0BAA0B;AAAA,IACpD;AAAA,IACA;AAAA,EACF,CAAC;AAED,oBAAkB,aAAa,QAAQ;AAAA,IACrC,mBAAiB;AACf,YAAM,mBACJ,cAAc,KAAK,SAAS,oBAAoB;AAClD,YAAM,cAAc,oBAAoB,eAAe;AACvD,YAAM,eAAe,oBAAoB,gBAAgB;AAEzD,YAAM,QAAQ,mBAAmB,eAAe;AAChD,UAAI,SAAS,CAAC,cAAc,QAAQ,eAAe;AACjD,eAAO,OAAO,cAAc,SAAS;AAAA,UACnC,eAAe,UAAU,KAAK;AAAA,QAChC,CAAC;AAAA,MACH;AAEA,YAAM,eAAe,UAAU,eAAe,EAAE;AAChD,UAAI,cAAc,QAAQ;AACxB,eAAO,OAAO,cAAc,SAAS;AAAA,UACnC,aAAa,aAAa;AAAA,QAC5B,CAAC;AAAA,MACH;AAEA,aAAO;AAAA,IACT;AAAA,IACA,WAAS,QAAQ,OAAO,KAAK;AAAA,EAC/B;AAEA,QAAM,4BAA4B;AAAA,IAChC,KAAK;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,8BAA8B;AAAA,IAClC,KAAK;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,8BAA8B;AAAA,IAClC,KAAK;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,gCAAgC;AAAA,IACpC,KAAK;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,mBAAmB;AAAA,IACvB,KAAK;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,gCAAgC;AAAA,IACpC,KAAK;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,wBAAwB;AAAA,IAC5B,KAAK;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AAAA,IACL,aAAa,iBAAiB,iBAAiB;AAAA,IAC/C,qBAAqB,iBAAiB,yBAAyB;AAAA,IAC/D,uBAAuB,iBAAiB,2BAA2B;AAAA,IACnE,gBAAgB,iBAAiB,2BAA2B;AAAA,IAC5D,kBAAkB,iBAAiB,6BAA6B;AAAA,IAChE,KAAK,iBAAiB,gBAAgB;AAAA,IACtC,kBAAkB,iBAAiB,6BAA6B;AAAA,IAChE,UAAU,iBAAiB,qBAAqB;AAAA,IAChD;AAAA,EACF;AACF;AAoBO,IAAM,uBAAmB,4BAAgC,IAAI;AA6C7D,IAAM,eAAe,MAAiB;AAC3C,QAAM,cAAU,yBAAW,gBAAgB;AAC3C,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,oDAAoD;AAAA,EACtE;AACA,SAAO;AACT;AAwCO,IAAM,oBAAoB,iBAAiB;","names":["defaultNavigateToLogin","axios","retryConfig"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -627,6 +627,23 @@ interface ApiService {
|
|
|
627
627
|
* ```
|
|
628
628
|
*/
|
|
629
629
|
put: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>;
|
|
630
|
+
/**
|
|
631
|
+
* Выполняет PATCH-запрос
|
|
632
|
+
*
|
|
633
|
+
* @typeParam T - Тип данных ответа
|
|
634
|
+
* @param url - URL эндпоинта
|
|
635
|
+
* @param data - Тело запроса для частичного обновления
|
|
636
|
+
* @param config - Дополнительные параметры axios
|
|
637
|
+
* @returns Промис с данными ответа
|
|
638
|
+
*
|
|
639
|
+
* @example
|
|
640
|
+
* ```typescript
|
|
641
|
+
* const updated = await service.patch<User>('/users/123', {
|
|
642
|
+
* name: 'Jane Doe',
|
|
643
|
+
* });
|
|
644
|
+
* ```
|
|
645
|
+
*/
|
|
646
|
+
patch: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>;
|
|
630
647
|
/**
|
|
631
648
|
* Выполняет DELETE-запрос
|
|
632
649
|
*
|
|
@@ -654,9 +671,6 @@ interface ApiService {
|
|
|
654
671
|
* config.headers['X-Request-Id'] = generateId();
|
|
655
672
|
* return config;
|
|
656
673
|
* });
|
|
657
|
-
*
|
|
658
|
-
* // Выполнение PATCH запроса
|
|
659
|
-
* await service.axiosInstance.patch('/users/123', { name: 'New Name' });
|
|
660
674
|
* ```
|
|
661
675
|
*/
|
|
662
676
|
axiosInstance: AxiosInstance;
|
package/dist/index.d.ts
CHANGED
|
@@ -627,6 +627,23 @@ interface ApiService {
|
|
|
627
627
|
* ```
|
|
628
628
|
*/
|
|
629
629
|
put: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>;
|
|
630
|
+
/**
|
|
631
|
+
* Выполняет PATCH-запрос
|
|
632
|
+
*
|
|
633
|
+
* @typeParam T - Тип данных ответа
|
|
634
|
+
* @param url - URL эндпоинта
|
|
635
|
+
* @param data - Тело запроса для частичного обновления
|
|
636
|
+
* @param config - Дополнительные параметры axios
|
|
637
|
+
* @returns Промис с данными ответа
|
|
638
|
+
*
|
|
639
|
+
* @example
|
|
640
|
+
* ```typescript
|
|
641
|
+
* const updated = await service.patch<User>('/users/123', {
|
|
642
|
+
* name: 'Jane Doe',
|
|
643
|
+
* });
|
|
644
|
+
* ```
|
|
645
|
+
*/
|
|
646
|
+
patch: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>;
|
|
630
647
|
/**
|
|
631
648
|
* Выполняет DELETE-запрос
|
|
632
649
|
*
|
|
@@ -654,9 +671,6 @@ interface ApiService {
|
|
|
654
671
|
* config.headers['X-Request-Id'] = generateId();
|
|
655
672
|
* return config;
|
|
656
673
|
* });
|
|
657
|
-
*
|
|
658
|
-
* // Выполнение PATCH запроса
|
|
659
|
-
* await service.axiosInstance.patch('/users/123', { name: 'New Name' });
|
|
660
674
|
* ```
|
|
661
675
|
*/
|
|
662
676
|
axiosInstance: AxiosInstance;
|
package/dist/index.js
CHANGED
|
@@ -386,6 +386,7 @@ var createApiService = (axiosInstance) => {
|
|
|
386
386
|
},
|
|
387
387
|
post: (url, data, config) => axiosInstance.post(url, data, config).then((response) => response.data),
|
|
388
388
|
put: (url, data, config) => axiosInstance.put(url, data, config).then((response) => response.data),
|
|
389
|
+
patch: (url, data, config) => axiosInstance.patch(url, data, config).then((response) => response.data),
|
|
389
390
|
delete: (url, config) => axiosInstance.delete(url, config).then((response) => response.data),
|
|
390
391
|
axiosInstance
|
|
391
392
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/api-client.ts","../src/token-refresh.ts"],"sourcesContent":["/**\n * Модуль API клиента\n *\n * Предоставляет настроенный axios-клиент с автоматическим обновлением токенов,\n * обработкой ошибок авторизации и React Context для интеграции.\n *\n * @example\n * ```typescript\n * import { createApiClient, ApiClientProvider, useApiClient } from 'mbt-api-client';\n *\n * // Создание клиента\n * const apiClient = createApiClient({\n * urls: {\n * authService: 'https://api.example.com/auth',\n * userProgressService: 'https://api.example.com/progress',\n * recommendationService: 'https://api.example.com/recommendations',\n * partnerManager: 'https://api.example.com/partners',\n * puzzleController: 'https://api.example.com/puzzles',\n * monolith: 'https://api.example.com',\n * },\n * onNavigateToLogin: () => navigate('/login'),\n * timeout: 15000,\n * });\n *\n * // В корне приложения\n * <ApiClientProvider value={apiClient}>\n * <App />\n * </ApiClientProvider>\n *\n * // В компонентах\n * const { userProgressService } = useApiClient();\n * const userData = await userProgressService.get('/users/me');\n * ```\n *\n * @module api-client\n */\n\nimport axios, {\n AxiosInstance,\n AxiosRequestConfig,\n AxiosError,\n InternalAxiosRequestConfig,\n} from 'axios';\nimport { createContext, useContext } from 'react';\nimport {\n TokenRefreshManager,\n createTokenRefreshManager,\n TokenRefreshResult,\n} from './token-refresh';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * URL-адреса микросервисов API\n *\n * @example\n * ```typescript\n * const urls: ApiClientUrls = {\n * authService: 'https://auth.example.com',\n * userProgressService: 'https://progress.example.com',\n * recommendationService: 'https://rec.example.com',\n * partnerManager: 'https://partners.example.com',\n * puzzleController: 'https://puzzles.example.com',\n * monolith: 'https://api.example.com',\n * };\n * ```\n */\nexport interface ApiClientUrls {\n /** URL сервиса авторизации (логин, регистрация, refresh токенов) */\n authService: string;\n\n /** URL сервиса прогресса пользователя */\n userProgressService: string;\n\n /** URL сервиса рекомендаций */\n recommendationService: string;\n\n /** URL сервиса управления партнёрами */\n partnerManager: string;\n\n /** URL сервиса управления приключениями */\n adventureManager: string;\n\n /** URL сервиса агрегации данных для клиента */\n bff: string;\n\n /** URL контроллера пазлов/задач */\n puzzleController: string;\n\n /** URL основного монолитного сервиса */\n monolith: string;\n}\n\n/**\n * Конфигурация API клиента\n *\n * @example\n * ```typescript\n * const config: ApiClientConfig = {\n * urls: {\n * authService: 'https://auth.example.com',\n * userProgressService: 'https://progress.example.com',\n * // ... остальные URL\n * },\n * onNavigateToLogin: () => {\n * // Для React Router\n * navigate('/login');\n * },\n * timeout: 15000,\n * decodeJwt: (token) => {\n * // Кастомный декодер JWT\n * return jwtDecode(token);\n * },\n * };\n * ```\n */\nexport interface ApiClientConfig {\n /**\n * URL-адреса микросервисов\n */\n urls: ApiClientUrls;\n\n /**\n * Колбэк для перенаправления на страницу логина\n *\n * Вызывается при:\n * - Ошибке 401 с невалидным токеном\n * - Ошибке 403 (доступ запрещён)\n * - Неудачном обновлении токена\n *\n * @default Очищает localStorage и редиректит на '#/login'\n *\n * @example\n * ```typescript\n * // React Router\n * onNavigateToLogin: () => navigate('/login')\n *\n * // Next.js\n * onNavigateToLogin: () => router.push('/login')\n *\n * // Vanilla JS\n * onNavigateToLogin: () => { window.location.href = '/login' }\n * ```\n */\n onNavigateToLogin?: () => void;\n\n /**\n * Таймаут запросов в миллисекундах\n *\n * @default 10000 (10 секунд)\n */\n timeout?: number;\n\n /**\n * Функция декодирования JWT токена для извлечения userId\n *\n * Используется для добавления заголовка X-User-Id к запросам.\n * Если не указана, используется встроенный base64 декодер.\n *\n * @param token - JWT токен для декодирования\n * @returns Объект с userId или null при ошибке декодирования\n *\n * @default Встроенный base64 декодер\n *\n * @example\n * ```typescript\n * // С библиотекой jwt-decode\n * import { jwtDecode } from 'jwt-decode';\n *\n * decodeJwt: (token) => {\n * try {\n * return jwtDecode<{ userId: string }>(token);\n * } catch {\n * return null;\n * }\n * }\n * ```\n */\n decodeJwt?: (token: string) => { userId?: string } | null;\n}\n\n/**\n * Обёртка над axios для выполнения HTTP-запросов\n *\n * Предоставляет типизированные методы для всех HTTP-операций.\n * Автоматически извлекает `data` из ответа axios.\n *\n * @example\n * ```typescript\n * interface User {\n * id: string;\n * name: string;\n * email: string;\n * }\n *\n * // GET запрос с типизацией\n * const user = await apiService.get<User>('/users/me');\n * console.log(user.name);\n *\n * // POST запрос\n * const newUser = await apiService.post<User>('/users', {\n * name: 'John',\n * email: 'john@example.com',\n * });\n *\n * // С дополнительными параметрами axios\n * const data = await apiService.get<Data>('/endpoint', {\n * params: { page: 1, limit: 10 },\n * headers: { 'X-Custom-Header': 'value' },\n * });\n * ```\n */\nexport interface ApiService {\n /**\n * Выполняет GET-запрос\n *\n * Автоматически добавляет cache-buster параметр `_t` для предотвращения кэширования.\n *\n * @typeParam T - Тип данных ответа\n * @param url - URL эндпоинта (относительно baseURL сервиса)\n * @param config - Дополнительные параметры axios (headers, params и т.д.)\n * @returns Промис с данными ответа\n *\n * @example\n * ```typescript\n * // Простой GET\n * const users = await service.get<User[]>('/users');\n *\n * // С query параметрами\n * const filtered = await service.get<User[]>('/users', {\n * params: { role: 'admin', active: true }\n * });\n * ```\n */\n get: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>;\n\n /**\n * Выполняет POST-запрос\n *\n * @typeParam T - Тип данных ответа\n * @param url - URL эндпоинта (относительно baseURL сервиса)\n * @param data - Тело запроса (будет сериализовано в JSON)\n * @param config - Дополнительные параметры axios\n * @returns Промис с данными ответа\n *\n * @example\n * ```typescript\n * const newUser = await service.post<User>('/users', {\n * name: 'John Doe',\n * email: 'john@example.com',\n * });\n * ```\n */\n post: <T>(\n url: string,\n data?: unknown,\n config?: AxiosRequestConfig,\n ) => Promise<T>;\n\n /**\n * Выполняет PUT-запрос\n *\n * @typeParam T - Тип данных ответа\n * @param url - URL эндпоинта\n * @param data - Тело запроса для обновления\n * @param config - Дополнительные параметры axios\n * @returns Промис с данными ответа\n *\n * @example\n * ```typescript\n * const updated = await service.put<User>('/users/123', {\n * name: 'Jane Doe',\n * });\n * ```\n */\n put: <T>(\n url: string,\n data?: unknown,\n config?: AxiosRequestConfig,\n ) => Promise<T>;\n\n /**\n * Выполняет DELETE-запрос\n *\n * @typeParam T - Тип данных ответа\n * @param url - URL эндпоинта\n * @param config - Дополнительные параметры axios\n * @returns Промис с данными ответа\n *\n * @example\n * ```typescript\n * await service.delete<void>('/users/123');\n * ```\n */\n delete: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>;\n\n /**\n * Доступ к нативному экземпляру axios\n *\n * Используйте для добавления кастомных interceptors или\n * выполнения нестандартных запросов.\n *\n * @example\n * ```typescript\n * // Добавление кастомного interceptor\n * service.axiosInstance.interceptors.request.use(config => {\n * config.headers['X-Request-Id'] = generateId();\n * return config;\n * });\n *\n * // Выполнение PATCH запроса\n * await service.axiosInstance.patch('/users/123', { name: 'New Name' });\n * ```\n */\n axiosInstance: AxiosInstance;\n}\n\n/**\n * Главный объект API клиента\n *\n * Содержит настроенные сервисы для каждого микросервиса\n * и менеджер обновления токенов.\n *\n * @example\n * ```typescript\n * const apiClient = createApiClient({ urls: {...} });\n *\n * // Использование сервисов\n * const user = await apiClient.userProgressService.get('/me');\n * const puzzles = await apiClient.puzzleController.get('/puzzles');\n *\n * // Работа с токенами\n * const currentToken = apiClient.tokenRefreshManager.getAccessToken();\n * ```\n */\nexport interface ApiClient {\n /**\n * Сервис авторизации\n *\n * Используется для логина, регистрации, обновления токенов.\n */\n authService: ApiService;\n\n /**\n * Сервис прогресса пользователя\n *\n * Используется для получения и обновления прогресса обучения.\n */\n userProgressService: ApiService;\n\n /**\n * Сервис рекомендаций\n *\n * Используется для получения персонализированных рекомендаций.\n */\n recommendationService: ApiService;\n\n /**\n * Сервис управления партнёрами\n */\n partnerManager: ApiService;\n\n /**\n * Сервис управления приключениями\n */\n adventureManager: ApiService;\n\n /**\n * Сервис агрегации данных для клиента\n */\n bff: ApiService;\n\n /**\n * Контроллер пазлов/задач\n *\n * Используется для работы с учебными задачами и пазлами.\n */\n puzzleController: ApiService;\n\n /**\n * Основной монолитный сервис\n *\n * Используется для запросов, не выделенных в отдельные микросервисы.\n */\n monolith: ApiService;\n\n /**\n * Менеджер обновления токенов\n *\n * Предоставляет доступ к функциям работы с токенами.\n * Можно использовать с RTK Query или другими HTTP-клиентами.\n *\n * @example\n * ```typescript\n * // Получение текущего токена\n * const token = apiClient.tokenRefreshManager.getAccessToken();\n *\n * // Обработка 401 в кастомном клиенте\n * if (response.status === 401) {\n * const newToken = await apiClient.tokenRefreshManager.handle401();\n * }\n * ```\n */\n tokenRefreshManager: TokenRefreshManager;\n}\n\n// ============================================================================\n// Retry Key Symbol\n// ============================================================================\n\nconst RETRY_KEY = Symbol('retry');\n\ninterface ExtendedAxiosRequestConfig extends InternalAxiosRequestConfig {\n [RETRY_KEY]?: boolean;\n}\n\n// ============================================================================\n// Default implementations\n// ============================================================================\n\nconst defaultNavigateToLogin = () => {\n localStorage.removeItem('accessToken');\n localStorage.removeItem('refreshToken');\n window.location.href = '#/login';\n};\n\nconst defaultDecodeJwt = (token: string): { userId?: string } | null => {\n try {\n const base64Url = token.split('.')[1];\n const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');\n const jsonPayload = decodeURIComponent(\n atob(base64)\n .split('')\n .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))\n .join(''),\n );\n return JSON.parse(jsonPayload);\n } catch {\n return null;\n }\n};\n\n// ============================================================================\n// Create Axios Instance with Interceptors\n// ============================================================================\n\nconst createAxiosInstance = (\n baseUrl: string,\n tokenRefreshManager: TokenRefreshManager,\n onNavigateToLogin: () => void,\n timeout: number,\n decodeJwt: (token: string) => { userId?: string } | null,\n): AxiosInstance => {\n const instance = axios.create({\n baseURL: baseUrl,\n timeout,\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n instance.interceptors.request.use(\n requestConfig => {\n const isRefreshRequest =\n requestConfig.url?.includes('auth/refresh-token');\n const accessToken = tokenRefreshManager.getAccessToken();\n const refreshToken = tokenRefreshManager.getRefreshToken();\n\n const token = isRefreshRequest ? refreshToken : accessToken;\n if (token) {\n Object.assign(requestConfig.headers, {\n Authorization: `Bearer ${token}`,\n });\n }\n\n const decodedToken = decodeJwt(accessToken || '');\n if (decodedToken?.userId) {\n Object.assign(requestConfig.headers, {\n 'X-User-Id': decodedToken.userId,\n });\n }\n\n return requestConfig;\n },\n error => Promise.reject(error),\n );\n\n instance.interceptors.response.use(\n response => response,\n async (error: AxiosError) => {\n const originalRequest = error.config as ExtendedAxiosRequestConfig;\n\n if (!originalRequest) {\n return Promise.reject(error);\n }\n\n const isRefreshRequest =\n originalRequest.url?.includes('auth/refresh-token');\n\n if (error.response?.status === 401) {\n const responseData = error.response?.data as\n | { message?: string }\n | undefined;\n const isInvalidToken = responseData?.message === 'Invalid token';\n\n if (isInvalidToken) {\n onNavigateToLogin();\n return Promise.reject(error);\n }\n\n if (isRefreshRequest) {\n onNavigateToLogin();\n return Promise.reject(error);\n }\n\n if (originalRequest[RETRY_KEY]) {\n onNavigateToLogin();\n return Promise.reject(error);\n }\n\n if (tokenRefreshManager.isRefreshInProgress()) {\n try {\n const newToken = await tokenRefreshManager.waitForTokenRefresh();\n const retryConfig = { ...originalRequest };\n Object.assign(retryConfig.headers, {\n Authorization: `Bearer ${newToken}`,\n });\n return instance(retryConfig);\n } catch {\n return Promise.reject(error);\n }\n }\n\n const retryConfig: ExtendedAxiosRequestConfig = {\n ...originalRequest,\n [RETRY_KEY]: true,\n };\n\n try {\n const newToken = await tokenRefreshManager.handle401(false);\n\n if (!newToken) {\n return Promise.reject(new Error('Failed to refresh token'));\n }\n\n Object.assign(retryConfig.headers, {\n Authorization: `Bearer ${newToken}`,\n });\n return instance(retryConfig);\n } catch (refreshError) {\n onNavigateToLogin();\n return Promise.reject(refreshError);\n }\n }\n\n if (error.response?.status === 403) {\n onNavigateToLogin();\n return Promise.reject(error);\n }\n\n if (error.response?.status === 400 && isRefreshRequest) {\n await new Promise<void>(resolve => {\n setTimeout(resolve, 1000);\n });\n\n const newToken = tokenRefreshManager.getAccessToken();\n if (newToken) {\n const retryConfig = { ...originalRequest };\n Object.assign(retryConfig.headers, {\n Authorization: `Bearer ${newToken}`,\n });\n return instance(retryConfig);\n }\n\n onNavigateToLogin();\n return Promise.reject(new Error('Failed to refresh token'));\n }\n\n return Promise.reject(error);\n },\n );\n\n return instance;\n};\n\n// ============================================================================\n// Create API Service Wrapper\n// ============================================================================\n\nconst createApiService = (axiosInstance: AxiosInstance): ApiService => {\n return {\n get: <T>(url: string, config?: AxiosRequestConfig): Promise<T> => {\n const cacheBuster = `${url.includes('?') ? '&' : '?'}_t=${Date.now()}`;\n return axiosInstance\n .get(url + cacheBuster, config)\n .then(response => response.data);\n },\n post: <T>(\n url: string,\n data?: unknown,\n config?: AxiosRequestConfig,\n ): Promise<T> =>\n axiosInstance.post(url, data, config).then(response => response.data),\n put: <T>(\n url: string,\n data?: unknown,\n config?: AxiosRequestConfig,\n ): Promise<T> =>\n axiosInstance.put(url, data, config).then(response => response.data),\n delete: <T>(url: string, config?: AxiosRequestConfig): Promise<T> =>\n axiosInstance.delete(url, config).then(response => response.data),\n axiosInstance,\n };\n};\n\n// ============================================================================\n// Main Factory Function\n// ============================================================================\n\n/**\n * Создаёт настроенный API клиент\n *\n * Фабричная функция, которая создаёт полностью настроенный API клиент\n * с поддержкой автоматического обновления токенов, обработкой ошибок\n * авторизации и типизированными методами запросов.\n *\n * **Особенности:**\n * - Автоматическое добавление Authorization заголовка\n * - Автоматическое обновление токена при 401 ошибке\n * - Очередь запросов при одновременном обновлении токена\n * - Добавление X-User-Id заголовка из JWT\n * - Cache-busting для GET запросов\n *\n * @param config - Конфигурация клиента\n * @returns Настроенный API клиент\n *\n * @example\n * ```typescript\n * import { createApiClient } from 'mbt-api-client';\n *\n * const apiClient = createApiClient({\n * urls: {\n * authService: 'https://auth.example.com',\n * userProgressService: 'https://progress.example.com',\n * recommendationService: 'https://rec.example.com',\n * partnerManager: 'https://partners.example.com',\n * puzzleController: 'https://puzzles.example.com',\n * monolith: 'https://api.example.com',\n * },\n * onNavigateToLogin: () => {\n * // Очистка состояния и редирект\n * store.dispatch(logout());\n * navigate('/login');\n * },\n * timeout: 15000, // 15 секунд\n * });\n *\n * // Использование\n * try {\n * const user = await apiClient.userProgressService.get<User>('/me');\n * console.log('Пользователь:', user);\n * } catch (error) {\n * console.error('Ошибка:', error);\n * }\n * ```\n */\nexport const createApiClient = (config: ApiClientConfig): ApiClient => {\n const {\n urls,\n onNavigateToLogin = defaultNavigateToLogin,\n timeout = 10000,\n decodeJwt = defaultDecodeJwt,\n } = config;\n\n const authAxiosInstance = axios.create({\n baseURL: urls.authService,\n timeout,\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n const refreshTokenFn = async (): Promise<TokenRefreshResult> => {\n try {\n const refreshToken = localStorage.getItem('refreshToken');\n if (!refreshToken) {\n return { ok: false };\n }\n\n const response = await authAxiosInstance.post(\n '/auth/refresh-token',\n {},\n {\n headers: {\n Authorization: `Bearer ${refreshToken}`,\n },\n },\n );\n\n const { accessToken, refreshToken: newRefreshToken } = response.data;\n\n return {\n ok: true,\n accessToken,\n refreshToken: newRefreshToken,\n };\n } catch {\n return { ok: false };\n }\n };\n\n const tokenRefreshManager = createTokenRefreshManager({\n refreshTokenFn,\n onNavigateToLogin,\n });\n\n authAxiosInstance.interceptors.request.use(\n requestConfig => {\n const isRefreshRequest =\n requestConfig.url?.includes('auth/refresh-token');\n const accessToken = tokenRefreshManager.getAccessToken();\n const refreshToken = tokenRefreshManager.getRefreshToken();\n\n const token = isRefreshRequest ? refreshToken : accessToken;\n if (token && !requestConfig.headers.Authorization) {\n Object.assign(requestConfig.headers, {\n Authorization: `Bearer ${token}`,\n });\n }\n\n const decodedToken = decodeJwt(accessToken || '');\n if (decodedToken?.userId) {\n Object.assign(requestConfig.headers, {\n 'X-User-Id': decodedToken.userId,\n });\n }\n\n return requestConfig;\n },\n error => Promise.reject(error),\n );\n\n const userProgressAxiosInstance = createAxiosInstance(\n urls.userProgressService,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const recommendationAxiosInstance = createAxiosInstance(\n urls.recommendationService,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const partnerManagerAxiosInstance = createAxiosInstance(\n urls.partnerManager,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const adventureManagerAxiosInstance = createAxiosInstance(\n urls.adventureManager,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const bffAxiosInstance = createAxiosInstance(\n urls.bff,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const puzzleControllerAxiosInstance = createAxiosInstance(\n urls.puzzleController,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const monolithAxiosInstance = createAxiosInstance(\n urls.monolith,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n return {\n authService: createApiService(authAxiosInstance),\n userProgressService: createApiService(userProgressAxiosInstance),\n recommendationService: createApiService(recommendationAxiosInstance),\n partnerManager: createApiService(partnerManagerAxiosInstance),\n adventureManager: createApiService(adventureManagerAxiosInstance),\n bff: createApiService(bffAxiosInstance),\n puzzleController: createApiService(puzzleControllerAxiosInstance),\n monolith: createApiService(monolithAxiosInstance),\n tokenRefreshManager,\n };\n};\n\n// ============================================================================\n// React Context\n// ============================================================================\n\n/**\n * React Context для API клиента\n *\n * Используется внутри ApiClientProvider для передачи клиента\n * через дерево компонентов.\n *\n * @example\n * ```typescript\n * // Для кастомных случаев (обычно используйте ApiClientProvider)\n * <ApiClientContext.Provider value={apiClient}>\n * <App />\n * </ApiClientContext.Provider>\n * ```\n */\nexport const ApiClientContext = createContext<ApiClient | null>(null);\n\n/**\n * React хук для доступа к API клиенту\n *\n * Получает API клиент из контекста. Должен использоваться внутри\n * компонента, обёрнутого в ApiClientProvider.\n *\n * @returns API клиент с типизированными сервисами\n * @throws Error если используется вне ApiClientProvider\n *\n * @example\n * ```typescript\n * import { useApiClient } from 'mbt-api-client';\n *\n * function UserProfile() {\n * const { userProgressService } = useApiClient();\n * const [user, setUser] = useState<User | null>(null);\n *\n * useEffect(() => {\n * userProgressService.get<User>('/me')\n * .then(setUser)\n * .catch(console.error);\n * }, []);\n *\n * return <div>{user?.name}</div>;\n * }\n *\n * // С деструктуризацией нескольких сервисов\n * function Dashboard() {\n * const {\n * userProgressService,\n * recommendationService,\n * puzzleController,\n * } = useApiClient();\n *\n * // Параллельные запросы\n * const [progress, recommendations, puzzles] = await Promise.all([\n * userProgressService.get('/progress'),\n * recommendationService.get('/for-me'),\n * puzzleController.get('/puzzles'),\n * ]);\n * }\n * ```\n */\nexport const useApiClient = (): ApiClient => {\n const context = useContext(ApiClientContext);\n if (!context) {\n throw new Error('useApiClient must be used within ApiClientProvider');\n }\n return context;\n};\n\n/**\n * React Provider для API клиента\n *\n * Оборачивает приложение для предоставления доступа к API клиенту\n * через хук useApiClient.\n *\n * @example\n * ```typescript\n * import { createApiClient, ApiClientProvider } from 'mbt-api-client';\n *\n * // Создание клиента (обычно в отдельном файле)\n * const apiClient = createApiClient({\n * urls: { ... },\n * });\n *\n * // В корне приложения\n * function App() {\n * return (\n * <ApiClientProvider value={apiClient}>\n * <Router>\n * <Routes />\n * </Router>\n * </ApiClientProvider>\n * );\n * }\n *\n * // С React Query\n * function App() {\n * return (\n * <QueryClientProvider client={queryClient}>\n * <ApiClientProvider value={apiClient}>\n * <Routes />\n * </ApiClientProvider>\n * </QueryClientProvider>\n * );\n * }\n * ```\n */\nexport const ApiClientProvider = ApiClientContext.Provider;\n\n// ============================================================================\n// Re-exports\n// ============================================================================\n\nexport {\n TokenRefreshManager,\n createTokenRefreshManager,\n type TokenRefreshConfig,\n type TokenRefreshResult,\n} from './token-refresh';\n","/**\n * Модуль управления обновлением токенов\n *\n * Реализует механизм обновления JWT токенов с очередью запросов.\n * Предотвращает множественные одновременные запросы на обновление токена,\n * когда несколько параллельных API-запросов получают ошибку 401.\n *\n * @example\n * ```typescript\n * // Создание менеджера с кастомными функциями\n * const tokenManager = createTokenRefreshManager({\n * refreshTokenFn: async () => {\n * const response = await fetch('/api/auth/refresh');\n * const data = await response.json();\n * return { ok: true, accessToken: data.accessToken };\n * },\n * onNavigateToLogin: () => router.push('/login'),\n * });\n *\n * // Использование с RTK Query или другими клиентами\n * if (response.status === 401) {\n * const newToken = await tokenManager.handle401();\n * // Повторить запрос с новым токеном\n * }\n * ```\n *\n * @module token-refresh\n */\n\n/**\n * Конфигурация менеджера обновления токенов\n *\n * @example\n * ```typescript\n * const config: TokenRefreshConfig = {\n * refreshTokenFn: async () => {\n * // Ваша логика обновления токена\n * return { ok: true, accessToken: 'new-token' };\n * },\n * onNavigateToLogin: () => window.location.href = '/login',\n * getAccessToken: () => localStorage.getItem('accessToken'),\n * getRefreshToken: () => localStorage.getItem('refreshToken'),\n * setTokens: (access, refresh) => {\n * localStorage.setItem('accessToken', access);\n * if (refresh) localStorage.setItem('refreshToken', refresh);\n * },\n * clearTokens: () => {\n * localStorage.removeItem('accessToken');\n * localStorage.removeItem('refreshToken');\n * },\n * };\n * ```\n */\nexport interface TokenRefreshConfig {\n /**\n * Функция для выполнения запроса на обновление токена\n *\n * Должна вернуть объект с `ok: true` и новым `accessToken` при успехе,\n * или `ok: false` при ошибке.\n *\n * @returns Промис с результатом обновления токена\n *\n * @example\n * ```typescript\n * refreshTokenFn: async () => {\n * try {\n * const response = await axios.post('/auth/refresh-token', {}, {\n * headers: { Authorization: `Bearer ${refreshToken}` }\n * });\n * return {\n * ok: true,\n * accessToken: response.data.accessToken,\n * refreshToken: response.data.refreshToken,\n * };\n * } catch {\n * return { ok: false };\n * }\n * }\n * ```\n */\n refreshTokenFn: () => Promise<TokenRefreshResult>;\n\n /**\n * Колбэк для перенаправления на страницу логина\n *\n * Вызывается когда:\n * - Токен невалиден (message: 'Invalid token')\n * - Обновление токена не удалось\n * - Refresh token истёк\n *\n * @default Очищает localStorage и редиректит на '#/login'\n *\n * @example\n * ```typescript\n * onNavigateToLogin: () => {\n * // Для React Router\n * navigate('/login');\n * // Или для Next.js\n * router.push('/login');\n * }\n * ```\n */\n onNavigateToLogin?: () => void;\n\n /**\n * Функция получения текущего access токена\n *\n * @default () => localStorage.getItem('accessToken')\n *\n * @example\n * ```typescript\n * // Для хранения в Redux\n * getAccessToken: () => store.getState().auth.accessToken\n * ```\n */\n getAccessToken?: () => string | null;\n\n /**\n * Функция получения текущего refresh токена\n *\n * @default () => localStorage.getItem('refreshToken')\n */\n getRefreshToken?: () => string | null;\n\n /**\n * Функция сохранения новых токенов после обновления\n *\n * @param accessToken - Новый access токен\n * @param refreshToken - Новый refresh токен (опционально)\n *\n * @default Сохраняет в localStorage\n *\n * @example\n * ```typescript\n * // Для хранения в Redux\n * setTokens: (access, refresh) => {\n * store.dispatch(setAuthTokens({ accessToken: access, refreshToken: refresh }));\n * }\n * ```\n */\n setTokens?: (accessToken: string, refreshToken?: string) => void;\n\n /**\n * Функция очистки токенов (при выходе или ошибке авторизации)\n *\n * @default Удаляет 'accessToken' и 'refreshToken' из localStorage\n */\n clearTokens?: () => void;\n}\n\n/**\n * Результат операции обновления токена\n *\n * @example\n * ```typescript\n * // Успешное обновление\n * const success: TokenRefreshResult = {\n * ok: true,\n * accessToken: 'eyJhbGciOiJIUzI1...',\n * refreshToken: 'dGhpcyBpcyBhIHJl...',\n * };\n *\n * // Ошибка обновления\n * const failure: TokenRefreshResult = { ok: false };\n * ```\n */\nexport interface TokenRefreshResult {\n /**\n * Успешность операции обновления\n *\n * `true` - токен успешно обновлён\n * `false` - ошибка обновления (пользователь будет перенаправлен на логин)\n */\n ok: boolean;\n\n /**\n * Новый access токен (только при `ok: true`)\n */\n accessToken?: string;\n\n /**\n * Новый refresh токен (опционально, только при `ok: true`)\n */\n refreshToken?: string;\n}\n\n/**\n * Интерфейс запроса в очереди ожидания обновления токена\n * @internal\n */\nexport interface QueuedRequest<T = unknown> {\n /** Функция разрешения промиса с новым токеном */\n resolve: (value: T) => void;\n /** Функция отклонения промиса при ошибке */\n reject: (error: unknown) => void;\n}\n\nconst defaultGetAccessToken = (): string | null =>\n localStorage.getItem('accessToken');\n\nconst defaultGetRefreshToken = (): string | null =>\n localStorage.getItem('refreshToken');\n\nconst defaultSetTokens = (accessToken: string, refreshToken?: string): void => {\n localStorage.setItem('accessToken', accessToken);\n if (refreshToken) {\n localStorage.setItem('refreshToken', refreshToken);\n }\n};\n\nconst defaultClearTokens = (): void => {\n localStorage.removeItem('accessToken');\n localStorage.removeItem('refreshToken');\n};\n\nconst defaultNavigateToLogin = (): void => {\n defaultClearTokens();\n window.location.href = '#/login';\n};\n\n/**\n * Менеджер обновления токенов с очередью запросов\n *\n * Решает проблему одновременного обновления токена при множественных\n * параллельных запросах, получивших 401 ошибку.\n *\n * **Как это работает:**\n * 1. Первый запрос с 401 инициирует обновление токена\n * 2. Последующие запросы с 401 добавляются в очередь ожидания\n * 3. После успешного обновления все запросы в очереди получают новый токен\n * 4. При ошибке обновления все запросы получают ошибку\n *\n * @example\n * ```typescript\n * // Создание менеджера\n * const tokenManager = new TokenRefreshManager({\n * refreshTokenFn: async () => {\n * const res = await fetch('/api/refresh');\n * const data = await res.json();\n * return { ok: true, accessToken: data.token };\n * },\n * });\n *\n * // Использование в axios interceptor\n * axios.interceptors.response.use(\n * response => response,\n * async error => {\n * if (error.response?.status === 401) {\n * const newToken = await tokenManager.handle401();\n * if (newToken) {\n * error.config.headers.Authorization = `Bearer ${newToken}`;\n * return axios(error.config);\n * }\n * }\n * return Promise.reject(error);\n * }\n * );\n * ```\n */\nexport class TokenRefreshManager {\n private isRefreshing = false;\n private refreshPromise: Promise<TokenRefreshResult> | null = null;\n private queuedRequests: QueuedRequest<string>[] = [];\n private config: Required<TokenRefreshConfig>;\n\n /**\n * Создаёт новый экземпляр TokenRefreshManager\n *\n * @param config - Конфигурация менеджера\n *\n * @example\n * ```typescript\n * const manager = new TokenRefreshManager({\n * refreshTokenFn: myRefreshFunction,\n * onNavigateToLogin: () => router.push('/login'),\n * });\n * ```\n */\n constructor(config: TokenRefreshConfig) {\n this.config = {\n refreshTokenFn: config.refreshTokenFn,\n onNavigateToLogin: config.onNavigateToLogin ?? defaultNavigateToLogin,\n getAccessToken: config.getAccessToken ?? defaultGetAccessToken,\n getRefreshToken: config.getRefreshToken ?? defaultGetRefreshToken,\n setTokens: config.setTokens ?? defaultSetTokens,\n clearTokens: config.clearTokens ?? defaultClearTokens,\n };\n }\n\n /**\n * Получает текущий access токен\n *\n * @returns Access токен или null, если токен отсутствует\n *\n * @example\n * ```typescript\n * const token = manager.getAccessToken();\n * if (token) {\n * headers.Authorization = `Bearer ${token}`;\n * }\n * ```\n */\n getAccessToken(): string | null {\n return this.config.getAccessToken();\n }\n\n /**\n * Получает текущий refresh токен\n *\n * @returns Refresh токен или null, если токен отсутствует\n */\n getRefreshToken(): string | null {\n return this.config.getRefreshToken();\n }\n\n /**\n * Проверяет, идёт ли в данный момент процесс обновления токена\n *\n * Используйте для определения, нужно ли ставить запрос в очередь\n * или инициировать новое обновление.\n *\n * @returns `true` если обновление в процессе, `false` если нет\n *\n * @example\n * ```typescript\n * if (manager.isRefreshInProgress()) {\n * // Ждём завершения текущего обновления\n * const newToken = await manager.waitForTokenRefresh();\n * } else {\n * // Инициируем новое обновление\n * await manager.refreshToken();\n * }\n * ```\n */\n isRefreshInProgress(): boolean {\n return this.isRefreshing;\n }\n\n /**\n * Добавляет запрос в очередь ожидания обновления токена\n *\n * Возвращает промис, который разрешится с новым токеном после\n * успешного обновления или будет отклонён при ошибке.\n *\n * @returns Промис с новым access токеном\n *\n * @example\n * ```typescript\n * // В response interceptor при множественных 401\n * if (manager.isRefreshInProgress()) {\n * try {\n * const newToken = await manager.waitForTokenRefresh();\n * // Повторить запрос с новым токеном\n * } catch {\n * // Обновление не удалось\n * }\n * }\n * ```\n */\n waitForTokenRefresh(): Promise<string> {\n return new Promise((resolve, reject) => {\n this.queuedRequests.push({ resolve, reject });\n });\n }\n\n /**\n * Выполняет обновление токена\n *\n * Если обновление уже в процессе, вернёт существующий промис.\n * После обновления уведомляет все запросы в очереди.\n *\n * @returns Промис с результатом обновления\n *\n * @example\n * ```typescript\n * const result = await manager.refreshToken();\n * if (result.ok) {\n * console.log('Новый токен:', result.accessToken);\n * } else {\n * console.log('Ошибка обновления токена');\n * }\n * ```\n */\n async refreshToken(): Promise<TokenRefreshResult> {\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n this.isRefreshing = true;\n\n this.refreshPromise = this.performRefresh().finally(() => {\n setTimeout(() => {\n this.refreshPromise = null;\n }, 100);\n });\n\n return this.refreshPromise;\n }\n\n /**\n * Обрабатывает ошибку 401 — либо обновляет токен, либо перенаправляет на логин\n *\n * Основной метод для использования в interceptors. Автоматически:\n * - Перенаправляет на логин при невалидном токене\n * - Ставит запрос в очередь, если обновление уже идёт\n * - Инициирует обновление, если ещё не запущено\n *\n * @param isInvalidToken - Если true, токен считается невалидным и будет выполнен переход на логин\n * @returns Промис с новым access токеном или null при ошибке\n *\n * @example\n * ```typescript\n * // В axios response interceptor\n * if (error.response?.status === 401) {\n * const isInvalid = error.response.data?.message === 'Invalid token';\n * const newToken = await manager.handle401(isInvalid);\n *\n * if (newToken) {\n * // Повторить запрос с новым токеном\n * error.config.headers.Authorization = `Bearer ${newToken}`;\n * return axios(error.config);\n * }\n * // newToken === null означает переход на логин\n * }\n * ```\n */\n async handle401(isInvalidToken = false): Promise<string | null> {\n if (isInvalidToken) {\n this.config.onNavigateToLogin();\n return null;\n }\n\n if (this.isRefreshing) {\n return this.waitForTokenRefresh();\n }\n\n const result = await this.refreshToken();\n\n if (!result.ok) {\n this.config.onNavigateToLogin();\n return null;\n }\n\n return result.accessToken || this.config.getAccessToken();\n }\n\n /**\n * Принудительно перенаправляет на страницу логина\n *\n * Вызывает колбэк onNavigateToLogin из конфигурации.\n *\n * @example\n * ```typescript\n * // При необходимости принудительного выхода\n * manager.navigateToLogin();\n * ```\n */\n navigateToLogin(): void {\n this.config.onNavigateToLogin();\n }\n\n /**\n * Сбрасывает внутреннее состояние менеджера\n *\n * Полезно для тестирования или при необходимости\n * принудительно сбросить очередь запросов.\n *\n * @example\n * ```typescript\n * // В тестах\n * beforeEach(() => {\n * manager.reset();\n * });\n * ```\n */\n reset(): void {\n this.isRefreshing = false;\n this.refreshPromise = null;\n this.queuedRequests = [];\n }\n\n private async performRefresh(): Promise<TokenRefreshResult> {\n try {\n const result = await this.config.refreshTokenFn();\n\n if (result.ok && result.accessToken) {\n this.config.setTokens(result.accessToken, result.refreshToken);\n this.notifyQueueSuccess(result.accessToken);\n } else {\n this.notifyQueueFailure(new Error('Token refresh failed'));\n }\n\n this.isRefreshing = false;\n return result;\n } catch (error) {\n this.isRefreshing = false;\n this.notifyQueueFailure(error);\n return { ok: false };\n }\n }\n\n private notifyQueueSuccess(newToken: string): void {\n this.queuedRequests.forEach(({ resolve }) => resolve(newToken));\n this.queuedRequests = [];\n }\n\n private notifyQueueFailure(error: unknown): void {\n this.queuedRequests.forEach(({ reject }) => reject(error));\n this.queuedRequests = [];\n }\n}\n\n/**\n * Создаёт новый экземпляр TokenRefreshManager\n *\n * Фабричная функция для создания менеджера обновления токенов.\n * Предпочтительный способ создания вместо прямого вызова конструктора.\n *\n * @param config - Конфигурация менеджера\n * @returns Новый экземпляр TokenRefreshManager\n *\n * @example\n * ```typescript\n * import { createTokenRefreshManager } from 'mbt-api-client';\n *\n * const tokenManager = createTokenRefreshManager({\n * refreshTokenFn: async () => {\n * const response = await fetch('/api/auth/refresh', {\n * method: 'POST',\n * headers: {\n * Authorization: `Bearer ${localStorage.getItem('refreshToken')}`,\n * },\n * });\n *\n * if (!response.ok) {\n * return { ok: false };\n * }\n *\n * const data = await response.json();\n * return {\n * ok: true,\n * accessToken: data.accessToken,\n * refreshToken: data.refreshToken,\n * };\n * },\n * onNavigateToLogin: () => {\n * window.location.href = '/login';\n * },\n * });\n *\n * // Использование\n * const newToken = await tokenManager.handle401();\n * ```\n */\nexport const createTokenRefreshManager = (\n config: TokenRefreshConfig,\n): TokenRefreshManager => {\n return new TokenRefreshManager(config);\n};\n"],"mappings":"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","names":["defaultNavigateToLogin","retryConfig"]}
|
|
1
|
+
{"version":3,"sources":["../src/api-client.ts","../src/token-refresh.ts"],"sourcesContent":["/**\n * Модуль API клиента\n *\n * Предоставляет настроенный axios-клиент с автоматическим обновлением токенов,\n * обработкой ошибок авторизации и React Context для интеграции.\n *\n * @example\n * ```typescript\n * import { createApiClient, ApiClientProvider, useApiClient } from 'mbt-api-client';\n *\n * // Создание клиента\n * const apiClient = createApiClient({\n * urls: {\n * authService: 'https://api.example.com/auth',\n * userProgressService: 'https://api.example.com/progress',\n * recommendationService: 'https://api.example.com/recommendations',\n * partnerManager: 'https://api.example.com/partners',\n * puzzleController: 'https://api.example.com/puzzles',\n * monolith: 'https://api.example.com',\n * },\n * onNavigateToLogin: () => navigate('/login'),\n * timeout: 15000,\n * });\n *\n * // В корне приложения\n * <ApiClientProvider value={apiClient}>\n * <App />\n * </ApiClientProvider>\n *\n * // В компонентах\n * const { userProgressService } = useApiClient();\n * const userData = await userProgressService.get('/users/me');\n * ```\n *\n * @module api-client\n */\n\nimport axios, {\n AxiosInstance,\n AxiosRequestConfig,\n AxiosError,\n InternalAxiosRequestConfig,\n} from 'axios';\nimport { createContext, useContext } from 'react';\nimport {\n TokenRefreshManager,\n createTokenRefreshManager,\n TokenRefreshResult,\n} from './token-refresh';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * URL-адреса микросервисов API\n *\n * @example\n * ```typescript\n * const urls: ApiClientUrls = {\n * authService: 'https://auth.example.com',\n * userProgressService: 'https://progress.example.com',\n * recommendationService: 'https://rec.example.com',\n * partnerManager: 'https://partners.example.com',\n * puzzleController: 'https://puzzles.example.com',\n * monolith: 'https://api.example.com',\n * };\n * ```\n */\nexport interface ApiClientUrls {\n /** URL сервиса авторизации (логин, регистрация, refresh токенов) */\n authService: string;\n\n /** URL сервиса прогресса пользователя */\n userProgressService: string;\n\n /** URL сервиса рекомендаций */\n recommendationService: string;\n\n /** URL сервиса управления партнёрами */\n partnerManager: string;\n\n /** URL сервиса управления приключениями */\n adventureManager: string;\n\n /** URL сервиса агрегации данных для клиента */\n bff: string;\n\n /** URL контроллера пазлов/задач */\n puzzleController: string;\n\n /** URL основного монолитного сервиса */\n monolith: string;\n}\n\n/**\n * Конфигурация API клиента\n *\n * @example\n * ```typescript\n * const config: ApiClientConfig = {\n * urls: {\n * authService: 'https://auth.example.com',\n * userProgressService: 'https://progress.example.com',\n * // ... остальные URL\n * },\n * onNavigateToLogin: () => {\n * // Для React Router\n * navigate('/login');\n * },\n * timeout: 15000,\n * decodeJwt: (token) => {\n * // Кастомный декодер JWT\n * return jwtDecode(token);\n * },\n * };\n * ```\n */\nexport interface ApiClientConfig {\n /**\n * URL-адреса микросервисов\n */\n urls: ApiClientUrls;\n\n /**\n * Колбэк для перенаправления на страницу логина\n *\n * Вызывается при:\n * - Ошибке 401 с невалидным токеном\n * - Ошибке 403 (доступ запрещён)\n * - Неудачном обновлении токена\n *\n * @default Очищает localStorage и редиректит на '#/login'\n *\n * @example\n * ```typescript\n * // React Router\n * onNavigateToLogin: () => navigate('/login')\n *\n * // Next.js\n * onNavigateToLogin: () => router.push('/login')\n *\n * // Vanilla JS\n * onNavigateToLogin: () => { window.location.href = '/login' }\n * ```\n */\n onNavigateToLogin?: () => void;\n\n /**\n * Таймаут запросов в миллисекундах\n *\n * @default 10000 (10 секунд)\n */\n timeout?: number;\n\n /**\n * Функция декодирования JWT токена для извлечения userId\n *\n * Используется для добавления заголовка X-User-Id к запросам.\n * Если не указана, используется встроенный base64 декодер.\n *\n * @param token - JWT токен для декодирования\n * @returns Объект с userId или null при ошибке декодирования\n *\n * @default Встроенный base64 декодер\n *\n * @example\n * ```typescript\n * // С библиотекой jwt-decode\n * import { jwtDecode } from 'jwt-decode';\n *\n * decodeJwt: (token) => {\n * try {\n * return jwtDecode<{ userId: string }>(token);\n * } catch {\n * return null;\n * }\n * }\n * ```\n */\n decodeJwt?: (token: string) => { userId?: string } | null;\n}\n\n/**\n * Обёртка над axios для выполнения HTTP-запросов\n *\n * Предоставляет типизированные методы для всех HTTP-операций.\n * Автоматически извлекает `data` из ответа axios.\n *\n * @example\n * ```typescript\n * interface User {\n * id: string;\n * name: string;\n * email: string;\n * }\n *\n * // GET запрос с типизацией\n * const user = await apiService.get<User>('/users/me');\n * console.log(user.name);\n *\n * // POST запрос\n * const newUser = await apiService.post<User>('/users', {\n * name: 'John',\n * email: 'john@example.com',\n * });\n *\n * // С дополнительными параметрами axios\n * const data = await apiService.get<Data>('/endpoint', {\n * params: { page: 1, limit: 10 },\n * headers: { 'X-Custom-Header': 'value' },\n * });\n * ```\n */\nexport interface ApiService {\n /**\n * Выполняет GET-запрос\n *\n * Автоматически добавляет cache-buster параметр `_t` для предотвращения кэширования.\n *\n * @typeParam T - Тип данных ответа\n * @param url - URL эндпоинта (относительно baseURL сервиса)\n * @param config - Дополнительные параметры axios (headers, params и т.д.)\n * @returns Промис с данными ответа\n *\n * @example\n * ```typescript\n * // Простой GET\n * const users = await service.get<User[]>('/users');\n *\n * // С query параметрами\n * const filtered = await service.get<User[]>('/users', {\n * params: { role: 'admin', active: true }\n * });\n * ```\n */\n get: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>;\n\n /**\n * Выполняет POST-запрос\n *\n * @typeParam T - Тип данных ответа\n * @param url - URL эндпоинта (относительно baseURL сервиса)\n * @param data - Тело запроса (будет сериализовано в JSON)\n * @param config - Дополнительные параметры axios\n * @returns Промис с данными ответа\n *\n * @example\n * ```typescript\n * const newUser = await service.post<User>('/users', {\n * name: 'John Doe',\n * email: 'john@example.com',\n * });\n * ```\n */\n post: <T>(\n url: string,\n data?: unknown,\n config?: AxiosRequestConfig,\n ) => Promise<T>;\n\n /**\n * Выполняет PUT-запрос\n *\n * @typeParam T - Тип данных ответа\n * @param url - URL эндпоинта\n * @param data - Тело запроса для обновления\n * @param config - Дополнительные параметры axios\n * @returns Промис с данными ответа\n *\n * @example\n * ```typescript\n * const updated = await service.put<User>('/users/123', {\n * name: 'Jane Doe',\n * });\n * ```\n */\n put: <T>(\n url: string,\n data?: unknown,\n config?: AxiosRequestConfig,\n ) => Promise<T>;\n\n /**\n * Выполняет PATCH-запрос\n *\n * @typeParam T - Тип данных ответа\n * @param url - URL эндпоинта\n * @param data - Тело запроса для частичного обновления\n * @param config - Дополнительные параметры axios\n * @returns Промис с данными ответа\n *\n * @example\n * ```typescript\n * const updated = await service.patch<User>('/users/123', {\n * name: 'Jane Doe',\n * });\n * ```\n */\n patch: <T>(\n url: string,\n data?: unknown,\n config?: AxiosRequestConfig,\n ) => Promise<T>;\n\n /**\n * Выполняет DELETE-запрос\n *\n * @typeParam T - Тип данных ответа\n * @param url - URL эндпоинта\n * @param config - Дополнительные параметры axios\n * @returns Промис с данными ответа\n *\n * @example\n * ```typescript\n * await service.delete<void>('/users/123');\n * ```\n */\n delete: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>;\n\n /**\n * Доступ к нативному экземпляру axios\n *\n * Используйте для добавления кастомных interceptors или\n * выполнения нестандартных запросов.\n *\n * @example\n * ```typescript\n * // Добавление кастомного interceptor\n * service.axiosInstance.interceptors.request.use(config => {\n * config.headers['X-Request-Id'] = generateId();\n * return config;\n * });\n * ```\n */\n axiosInstance: AxiosInstance;\n}\n\n/**\n * Главный объект API клиента\n *\n * Содержит настроенные сервисы для каждого микросервиса\n * и менеджер обновления токенов.\n *\n * @example\n * ```typescript\n * const apiClient = createApiClient({ urls: {...} });\n *\n * // Использование сервисов\n * const user = await apiClient.userProgressService.get('/me');\n * const puzzles = await apiClient.puzzleController.get('/puzzles');\n *\n * // Работа с токенами\n * const currentToken = apiClient.tokenRefreshManager.getAccessToken();\n * ```\n */\nexport interface ApiClient {\n /**\n * Сервис авторизации\n *\n * Используется для логина, регистрации, обновления токенов.\n */\n authService: ApiService;\n\n /**\n * Сервис прогресса пользователя\n *\n * Используется для получения и обновления прогресса обучения.\n */\n userProgressService: ApiService;\n\n /**\n * Сервис рекомендаций\n *\n * Используется для получения персонализированных рекомендаций.\n */\n recommendationService: ApiService;\n\n /**\n * Сервис управления партнёрами\n */\n partnerManager: ApiService;\n\n /**\n * Сервис управления приключениями\n */\n adventureManager: ApiService;\n\n /**\n * Сервис агрегации данных для клиента\n */\n bff: ApiService;\n\n /**\n * Контроллер пазлов/задач\n *\n * Используется для работы с учебными задачами и пазлами.\n */\n puzzleController: ApiService;\n\n /**\n * Основной монолитный сервис\n *\n * Используется для запросов, не выделенных в отдельные микросервисы.\n */\n monolith: ApiService;\n\n /**\n * Менеджер обновления токенов\n *\n * Предоставляет доступ к функциям работы с токенами.\n * Можно использовать с RTK Query или другими HTTP-клиентами.\n *\n * @example\n * ```typescript\n * // Получение текущего токена\n * const token = apiClient.tokenRefreshManager.getAccessToken();\n *\n * // Обработка 401 в кастомном клиенте\n * if (response.status === 401) {\n * const newToken = await apiClient.tokenRefreshManager.handle401();\n * }\n * ```\n */\n tokenRefreshManager: TokenRefreshManager;\n}\n\n// ============================================================================\n// Retry Key Symbol\n// ============================================================================\n\nconst RETRY_KEY = Symbol('retry');\n\ninterface ExtendedAxiosRequestConfig extends InternalAxiosRequestConfig {\n [RETRY_KEY]?: boolean;\n}\n\n// ============================================================================\n// Default implementations\n// ============================================================================\n\nconst defaultNavigateToLogin = () => {\n localStorage.removeItem('accessToken');\n localStorage.removeItem('refreshToken');\n window.location.href = '#/login';\n};\n\nconst defaultDecodeJwt = (token: string): { userId?: string } | null => {\n try {\n const base64Url = token.split('.')[1];\n const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');\n const jsonPayload = decodeURIComponent(\n atob(base64)\n .split('')\n .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))\n .join(''),\n );\n return JSON.parse(jsonPayload);\n } catch {\n return null;\n }\n};\n\n// ============================================================================\n// Create Axios Instance with Interceptors\n// ============================================================================\n\nconst createAxiosInstance = (\n baseUrl: string,\n tokenRefreshManager: TokenRefreshManager,\n onNavigateToLogin: () => void,\n timeout: number,\n decodeJwt: (token: string) => { userId?: string } | null,\n): AxiosInstance => {\n const instance = axios.create({\n baseURL: baseUrl,\n timeout,\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n instance.interceptors.request.use(\n requestConfig => {\n const isRefreshRequest =\n requestConfig.url?.includes('auth/refresh-token');\n const accessToken = tokenRefreshManager.getAccessToken();\n const refreshToken = tokenRefreshManager.getRefreshToken();\n\n const token = isRefreshRequest ? refreshToken : accessToken;\n if (token) {\n Object.assign(requestConfig.headers, {\n Authorization: `Bearer ${token}`,\n });\n }\n\n const decodedToken = decodeJwt(accessToken || '');\n if (decodedToken?.userId) {\n Object.assign(requestConfig.headers, {\n 'X-User-Id': decodedToken.userId,\n });\n }\n\n return requestConfig;\n },\n error => Promise.reject(error),\n );\n\n instance.interceptors.response.use(\n response => response,\n async (error: AxiosError) => {\n const originalRequest = error.config as ExtendedAxiosRequestConfig;\n\n if (!originalRequest) {\n return Promise.reject(error);\n }\n\n const isRefreshRequest =\n originalRequest.url?.includes('auth/refresh-token');\n\n if (error.response?.status === 401) {\n const responseData = error.response?.data as\n | { message?: string }\n | undefined;\n const isInvalidToken = responseData?.message === 'Invalid token';\n\n if (isInvalidToken) {\n onNavigateToLogin();\n return Promise.reject(error);\n }\n\n if (isRefreshRequest) {\n onNavigateToLogin();\n return Promise.reject(error);\n }\n\n if (originalRequest[RETRY_KEY]) {\n onNavigateToLogin();\n return Promise.reject(error);\n }\n\n if (tokenRefreshManager.isRefreshInProgress()) {\n try {\n const newToken = await tokenRefreshManager.waitForTokenRefresh();\n const retryConfig = { ...originalRequest };\n Object.assign(retryConfig.headers, {\n Authorization: `Bearer ${newToken}`,\n });\n return instance(retryConfig);\n } catch {\n return Promise.reject(error);\n }\n }\n\n const retryConfig: ExtendedAxiosRequestConfig = {\n ...originalRequest,\n [RETRY_KEY]: true,\n };\n\n try {\n const newToken = await tokenRefreshManager.handle401(false);\n\n if (!newToken) {\n return Promise.reject(new Error('Failed to refresh token'));\n }\n\n Object.assign(retryConfig.headers, {\n Authorization: `Bearer ${newToken}`,\n });\n return instance(retryConfig);\n } catch (refreshError) {\n onNavigateToLogin();\n return Promise.reject(refreshError);\n }\n }\n\n if (error.response?.status === 403) {\n onNavigateToLogin();\n return Promise.reject(error);\n }\n\n if (error.response?.status === 400 && isRefreshRequest) {\n await new Promise<void>(resolve => {\n setTimeout(resolve, 1000);\n });\n\n const newToken = tokenRefreshManager.getAccessToken();\n if (newToken) {\n const retryConfig = { ...originalRequest };\n Object.assign(retryConfig.headers, {\n Authorization: `Bearer ${newToken}`,\n });\n return instance(retryConfig);\n }\n\n onNavigateToLogin();\n return Promise.reject(new Error('Failed to refresh token'));\n }\n\n return Promise.reject(error);\n },\n );\n\n return instance;\n};\n\n// ============================================================================\n// Create API Service Wrapper\n// ============================================================================\n\nconst createApiService = (axiosInstance: AxiosInstance): ApiService => {\n return {\n get: <T>(url: string, config?: AxiosRequestConfig): Promise<T> => {\n const cacheBuster = `${url.includes('?') ? '&' : '?'}_t=${Date.now()}`;\n return axiosInstance\n .get(url + cacheBuster, config)\n .then(response => response.data);\n },\n post: <T>(\n url: string,\n data?: unknown,\n config?: AxiosRequestConfig,\n ): Promise<T> =>\n axiosInstance.post(url, data, config).then(response => response.data),\n put: <T>(\n url: string,\n data?: unknown,\n config?: AxiosRequestConfig,\n ): Promise<T> =>\n axiosInstance.put(url, data, config).then(response => response.data),\n patch: <T>(\n url: string,\n data?: unknown,\n config?: AxiosRequestConfig,\n ): Promise<T> =>\n axiosInstance.patch(url, data, config).then(response => response.data),\n delete: <T>(url: string, config?: AxiosRequestConfig): Promise<T> =>\n axiosInstance.delete(url, config).then(response => response.data),\n axiosInstance,\n };\n};\n\n// ============================================================================\n// Main Factory Function\n// ============================================================================\n\n/**\n * Создаёт настроенный API клиент\n *\n * Фабричная функция, которая создаёт полностью настроенный API клиент\n * с поддержкой автоматического обновления токенов, обработкой ошибок\n * авторизации и типизированными методами запросов.\n *\n * **Особенности:**\n * - Автоматическое добавление Authorization заголовка\n * - Автоматическое обновление токена при 401 ошибке\n * - Очередь запросов при одновременном обновлении токена\n * - Добавление X-User-Id заголовка из JWT\n * - Cache-busting для GET запросов\n *\n * @param config - Конфигурация клиента\n * @returns Настроенный API клиент\n *\n * @example\n * ```typescript\n * import { createApiClient } from 'mbt-api-client';\n *\n * const apiClient = createApiClient({\n * urls: {\n * authService: 'https://auth.example.com',\n * userProgressService: 'https://progress.example.com',\n * recommendationService: 'https://rec.example.com',\n * partnerManager: 'https://partners.example.com',\n * puzzleController: 'https://puzzles.example.com',\n * monolith: 'https://api.example.com',\n * },\n * onNavigateToLogin: () => {\n * // Очистка состояния и редирект\n * store.dispatch(logout());\n * navigate('/login');\n * },\n * timeout: 15000, // 15 секунд\n * });\n *\n * // Использование\n * try {\n * const user = await apiClient.userProgressService.get<User>('/me');\n * console.log('Пользователь:', user);\n * } catch (error) {\n * console.error('Ошибка:', error);\n * }\n * ```\n */\nexport const createApiClient = (config: ApiClientConfig): ApiClient => {\n const {\n urls,\n onNavigateToLogin = defaultNavigateToLogin,\n timeout = 10000,\n decodeJwt = defaultDecodeJwt,\n } = config;\n\n const authAxiosInstance = axios.create({\n baseURL: urls.authService,\n timeout,\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n const refreshTokenFn = async (): Promise<TokenRefreshResult> => {\n try {\n const refreshToken = localStorage.getItem('refreshToken');\n if (!refreshToken) {\n return { ok: false };\n }\n\n const response = await authAxiosInstance.post(\n '/auth/refresh-token',\n {},\n {\n headers: {\n Authorization: `Bearer ${refreshToken}`,\n },\n },\n );\n\n const { accessToken, refreshToken: newRefreshToken } = response.data;\n\n return {\n ok: true,\n accessToken,\n refreshToken: newRefreshToken,\n };\n } catch {\n return { ok: false };\n }\n };\n\n const tokenRefreshManager = createTokenRefreshManager({\n refreshTokenFn,\n onNavigateToLogin,\n });\n\n authAxiosInstance.interceptors.request.use(\n requestConfig => {\n const isRefreshRequest =\n requestConfig.url?.includes('auth/refresh-token');\n const accessToken = tokenRefreshManager.getAccessToken();\n const refreshToken = tokenRefreshManager.getRefreshToken();\n\n const token = isRefreshRequest ? refreshToken : accessToken;\n if (token && !requestConfig.headers.Authorization) {\n Object.assign(requestConfig.headers, {\n Authorization: `Bearer ${token}`,\n });\n }\n\n const decodedToken = decodeJwt(accessToken || '');\n if (decodedToken?.userId) {\n Object.assign(requestConfig.headers, {\n 'X-User-Id': decodedToken.userId,\n });\n }\n\n return requestConfig;\n },\n error => Promise.reject(error),\n );\n\n const userProgressAxiosInstance = createAxiosInstance(\n urls.userProgressService,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const recommendationAxiosInstance = createAxiosInstance(\n urls.recommendationService,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const partnerManagerAxiosInstance = createAxiosInstance(\n urls.partnerManager,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const adventureManagerAxiosInstance = createAxiosInstance(\n urls.adventureManager,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const bffAxiosInstance = createAxiosInstance(\n urls.bff,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const puzzleControllerAxiosInstance = createAxiosInstance(\n urls.puzzleController,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const monolithAxiosInstance = createAxiosInstance(\n urls.monolith,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n return {\n authService: createApiService(authAxiosInstance),\n userProgressService: createApiService(userProgressAxiosInstance),\n recommendationService: createApiService(recommendationAxiosInstance),\n partnerManager: createApiService(partnerManagerAxiosInstance),\n adventureManager: createApiService(adventureManagerAxiosInstance),\n bff: createApiService(bffAxiosInstance),\n puzzleController: createApiService(puzzleControllerAxiosInstance),\n monolith: createApiService(monolithAxiosInstance),\n tokenRefreshManager,\n };\n};\n\n// ============================================================================\n// React Context\n// ============================================================================\n\n/**\n * React Context для API клиента\n *\n * Используется внутри ApiClientProvider для передачи клиента\n * через дерево компонентов.\n *\n * @example\n * ```typescript\n * // Для кастомных случаев (обычно используйте ApiClientProvider)\n * <ApiClientContext.Provider value={apiClient}>\n * <App />\n * </ApiClientContext.Provider>\n * ```\n */\nexport const ApiClientContext = createContext<ApiClient | null>(null);\n\n/**\n * React хук для доступа к API клиенту\n *\n * Получает API клиент из контекста. Должен использоваться внутри\n * компонента, обёрнутого в ApiClientProvider.\n *\n * @returns API клиент с типизированными сервисами\n * @throws Error если используется вне ApiClientProvider\n *\n * @example\n * ```typescript\n * import { useApiClient } from 'mbt-api-client';\n *\n * function UserProfile() {\n * const { userProgressService } = useApiClient();\n * const [user, setUser] = useState<User | null>(null);\n *\n * useEffect(() => {\n * userProgressService.get<User>('/me')\n * .then(setUser)\n * .catch(console.error);\n * }, []);\n *\n * return <div>{user?.name}</div>;\n * }\n *\n * // С деструктуризацией нескольких сервисов\n * function Dashboard() {\n * const {\n * userProgressService,\n * recommendationService,\n * puzzleController,\n * } = useApiClient();\n *\n * // Параллельные запросы\n * const [progress, recommendations, puzzles] = await Promise.all([\n * userProgressService.get('/progress'),\n * recommendationService.get('/for-me'),\n * puzzleController.get('/puzzles'),\n * ]);\n * }\n * ```\n */\nexport const useApiClient = (): ApiClient => {\n const context = useContext(ApiClientContext);\n if (!context) {\n throw new Error('useApiClient must be used within ApiClientProvider');\n }\n return context;\n};\n\n/**\n * React Provider для API клиента\n *\n * Оборачивает приложение для предоставления доступа к API клиенту\n * через хук useApiClient.\n *\n * @example\n * ```typescript\n * import { createApiClient, ApiClientProvider } from 'mbt-api-client';\n *\n * // Создание клиента (обычно в отдельном файле)\n * const apiClient = createApiClient({\n * urls: { ... },\n * });\n *\n * // В корне приложения\n * function App() {\n * return (\n * <ApiClientProvider value={apiClient}>\n * <Router>\n * <Routes />\n * </Router>\n * </ApiClientProvider>\n * );\n * }\n *\n * // С React Query\n * function App() {\n * return (\n * <QueryClientProvider client={queryClient}>\n * <ApiClientProvider value={apiClient}>\n * <Routes />\n * </ApiClientProvider>\n * </QueryClientProvider>\n * );\n * }\n * ```\n */\nexport const ApiClientProvider = ApiClientContext.Provider;\n\n// ============================================================================\n// Re-exports\n// ============================================================================\n\nexport {\n TokenRefreshManager,\n createTokenRefreshManager,\n type TokenRefreshConfig,\n type TokenRefreshResult,\n} from './token-refresh';\n","/**\n * Модуль управления обновлением токенов\n *\n * Реализует механизм обновления JWT токенов с очередью запросов.\n * Предотвращает множественные одновременные запросы на обновление токена,\n * когда несколько параллельных API-запросов получают ошибку 401.\n *\n * @example\n * ```typescript\n * // Создание менеджера с кастомными функциями\n * const tokenManager = createTokenRefreshManager({\n * refreshTokenFn: async () => {\n * const response = await fetch('/api/auth/refresh');\n * const data = await response.json();\n * return { ok: true, accessToken: data.accessToken };\n * },\n * onNavigateToLogin: () => router.push('/login'),\n * });\n *\n * // Использование с RTK Query или другими клиентами\n * if (response.status === 401) {\n * const newToken = await tokenManager.handle401();\n * // Повторить запрос с новым токеном\n * }\n * ```\n *\n * @module token-refresh\n */\n\n/**\n * Конфигурация менеджера обновления токенов\n *\n * @example\n * ```typescript\n * const config: TokenRefreshConfig = {\n * refreshTokenFn: async () => {\n * // Ваша логика обновления токена\n * return { ok: true, accessToken: 'new-token' };\n * },\n * onNavigateToLogin: () => window.location.href = '/login',\n * getAccessToken: () => localStorage.getItem('accessToken'),\n * getRefreshToken: () => localStorage.getItem('refreshToken'),\n * setTokens: (access, refresh) => {\n * localStorage.setItem('accessToken', access);\n * if (refresh) localStorage.setItem('refreshToken', refresh);\n * },\n * clearTokens: () => {\n * localStorage.removeItem('accessToken');\n * localStorage.removeItem('refreshToken');\n * },\n * };\n * ```\n */\nexport interface TokenRefreshConfig {\n /**\n * Функция для выполнения запроса на обновление токена\n *\n * Должна вернуть объект с `ok: true` и новым `accessToken` при успехе,\n * или `ok: false` при ошибке.\n *\n * @returns Промис с результатом обновления токена\n *\n * @example\n * ```typescript\n * refreshTokenFn: async () => {\n * try {\n * const response = await axios.post('/auth/refresh-token', {}, {\n * headers: { Authorization: `Bearer ${refreshToken}` }\n * });\n * return {\n * ok: true,\n * accessToken: response.data.accessToken,\n * refreshToken: response.data.refreshToken,\n * };\n * } catch {\n * return { ok: false };\n * }\n * }\n * ```\n */\n refreshTokenFn: () => Promise<TokenRefreshResult>;\n\n /**\n * Колбэк для перенаправления на страницу логина\n *\n * Вызывается когда:\n * - Токен невалиден (message: 'Invalid token')\n * - Обновление токена не удалось\n * - Refresh token истёк\n *\n * @default Очищает localStorage и редиректит на '#/login'\n *\n * @example\n * ```typescript\n * onNavigateToLogin: () => {\n * // Для React Router\n * navigate('/login');\n * // Или для Next.js\n * router.push('/login');\n * }\n * ```\n */\n onNavigateToLogin?: () => void;\n\n /**\n * Функция получения текущего access токена\n *\n * @default () => localStorage.getItem('accessToken')\n *\n * @example\n * ```typescript\n * // Для хранения в Redux\n * getAccessToken: () => store.getState().auth.accessToken\n * ```\n */\n getAccessToken?: () => string | null;\n\n /**\n * Функция получения текущего refresh токена\n *\n * @default () => localStorage.getItem('refreshToken')\n */\n getRefreshToken?: () => string | null;\n\n /**\n * Функция сохранения новых токенов после обновления\n *\n * @param accessToken - Новый access токен\n * @param refreshToken - Новый refresh токен (опционально)\n *\n * @default Сохраняет в localStorage\n *\n * @example\n * ```typescript\n * // Для хранения в Redux\n * setTokens: (access, refresh) => {\n * store.dispatch(setAuthTokens({ accessToken: access, refreshToken: refresh }));\n * }\n * ```\n */\n setTokens?: (accessToken: string, refreshToken?: string) => void;\n\n /**\n * Функция очистки токенов (при выходе или ошибке авторизации)\n *\n * @default Удаляет 'accessToken' и 'refreshToken' из localStorage\n */\n clearTokens?: () => void;\n}\n\n/**\n * Результат операции обновления токена\n *\n * @example\n * ```typescript\n * // Успешное обновление\n * const success: TokenRefreshResult = {\n * ok: true,\n * accessToken: 'eyJhbGciOiJIUzI1...',\n * refreshToken: 'dGhpcyBpcyBhIHJl...',\n * };\n *\n * // Ошибка обновления\n * const failure: TokenRefreshResult = { ok: false };\n * ```\n */\nexport interface TokenRefreshResult {\n /**\n * Успешность операции обновления\n *\n * `true` - токен успешно обновлён\n * `false` - ошибка обновления (пользователь будет перенаправлен на логин)\n */\n ok: boolean;\n\n /**\n * Новый access токен (только при `ok: true`)\n */\n accessToken?: string;\n\n /**\n * Новый refresh токен (опционально, только при `ok: true`)\n */\n refreshToken?: string;\n}\n\n/**\n * Интерфейс запроса в очереди ожидания обновления токена\n * @internal\n */\nexport interface QueuedRequest<T = unknown> {\n /** Функция разрешения промиса с новым токеном */\n resolve: (value: T) => void;\n /** Функция отклонения промиса при ошибке */\n reject: (error: unknown) => void;\n}\n\nconst defaultGetAccessToken = (): string | null =>\n localStorage.getItem('accessToken');\n\nconst defaultGetRefreshToken = (): string | null =>\n localStorage.getItem('refreshToken');\n\nconst defaultSetTokens = (accessToken: string, refreshToken?: string): void => {\n localStorage.setItem('accessToken', accessToken);\n if (refreshToken) {\n localStorage.setItem('refreshToken', refreshToken);\n }\n};\n\nconst defaultClearTokens = (): void => {\n localStorage.removeItem('accessToken');\n localStorage.removeItem('refreshToken');\n};\n\nconst defaultNavigateToLogin = (): void => {\n defaultClearTokens();\n window.location.href = '#/login';\n};\n\n/**\n * Менеджер обновления токенов с очередью запросов\n *\n * Решает проблему одновременного обновления токена при множественных\n * параллельных запросах, получивших 401 ошибку.\n *\n * **Как это работает:**\n * 1. Первый запрос с 401 инициирует обновление токена\n * 2. Последующие запросы с 401 добавляются в очередь ожидания\n * 3. После успешного обновления все запросы в очереди получают новый токен\n * 4. При ошибке обновления все запросы получают ошибку\n *\n * @example\n * ```typescript\n * // Создание менеджера\n * const tokenManager = new TokenRefreshManager({\n * refreshTokenFn: async () => {\n * const res = await fetch('/api/refresh');\n * const data = await res.json();\n * return { ok: true, accessToken: data.token };\n * },\n * });\n *\n * // Использование в axios interceptor\n * axios.interceptors.response.use(\n * response => response,\n * async error => {\n * if (error.response?.status === 401) {\n * const newToken = await tokenManager.handle401();\n * if (newToken) {\n * error.config.headers.Authorization = `Bearer ${newToken}`;\n * return axios(error.config);\n * }\n * }\n * return Promise.reject(error);\n * }\n * );\n * ```\n */\nexport class TokenRefreshManager {\n private isRefreshing = false;\n private refreshPromise: Promise<TokenRefreshResult> | null = null;\n private queuedRequests: QueuedRequest<string>[] = [];\n private config: Required<TokenRefreshConfig>;\n\n /**\n * Создаёт новый экземпляр TokenRefreshManager\n *\n * @param config - Конфигурация менеджера\n *\n * @example\n * ```typescript\n * const manager = new TokenRefreshManager({\n * refreshTokenFn: myRefreshFunction,\n * onNavigateToLogin: () => router.push('/login'),\n * });\n * ```\n */\n constructor(config: TokenRefreshConfig) {\n this.config = {\n refreshTokenFn: config.refreshTokenFn,\n onNavigateToLogin: config.onNavigateToLogin ?? defaultNavigateToLogin,\n getAccessToken: config.getAccessToken ?? defaultGetAccessToken,\n getRefreshToken: config.getRefreshToken ?? defaultGetRefreshToken,\n setTokens: config.setTokens ?? defaultSetTokens,\n clearTokens: config.clearTokens ?? defaultClearTokens,\n };\n }\n\n /**\n * Получает текущий access токен\n *\n * @returns Access токен или null, если токен отсутствует\n *\n * @example\n * ```typescript\n * const token = manager.getAccessToken();\n * if (token) {\n * headers.Authorization = `Bearer ${token}`;\n * }\n * ```\n */\n getAccessToken(): string | null {\n return this.config.getAccessToken();\n }\n\n /**\n * Получает текущий refresh токен\n *\n * @returns Refresh токен или null, если токен отсутствует\n */\n getRefreshToken(): string | null {\n return this.config.getRefreshToken();\n }\n\n /**\n * Проверяет, идёт ли в данный момент процесс обновления токена\n *\n * Используйте для определения, нужно ли ставить запрос в очередь\n * или инициировать новое обновление.\n *\n * @returns `true` если обновление в процессе, `false` если нет\n *\n * @example\n * ```typescript\n * if (manager.isRefreshInProgress()) {\n * // Ждём завершения текущего обновления\n * const newToken = await manager.waitForTokenRefresh();\n * } else {\n * // Инициируем новое обновление\n * await manager.refreshToken();\n * }\n * ```\n */\n isRefreshInProgress(): boolean {\n return this.isRefreshing;\n }\n\n /**\n * Добавляет запрос в очередь ожидания обновления токена\n *\n * Возвращает промис, который разрешится с новым токеном после\n * успешного обновления или будет отклонён при ошибке.\n *\n * @returns Промис с новым access токеном\n *\n * @example\n * ```typescript\n * // В response interceptor при множественных 401\n * if (manager.isRefreshInProgress()) {\n * try {\n * const newToken = await manager.waitForTokenRefresh();\n * // Повторить запрос с новым токеном\n * } catch {\n * // Обновление не удалось\n * }\n * }\n * ```\n */\n waitForTokenRefresh(): Promise<string> {\n return new Promise((resolve, reject) => {\n this.queuedRequests.push({ resolve, reject });\n });\n }\n\n /**\n * Выполняет обновление токена\n *\n * Если обновление уже в процессе, вернёт существующий промис.\n * После обновления уведомляет все запросы в очереди.\n *\n * @returns Промис с результатом обновления\n *\n * @example\n * ```typescript\n * const result = await manager.refreshToken();\n * if (result.ok) {\n * console.log('Новый токен:', result.accessToken);\n * } else {\n * console.log('Ошибка обновления токена');\n * }\n * ```\n */\n async refreshToken(): Promise<TokenRefreshResult> {\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n this.isRefreshing = true;\n\n this.refreshPromise = this.performRefresh().finally(() => {\n setTimeout(() => {\n this.refreshPromise = null;\n }, 100);\n });\n\n return this.refreshPromise;\n }\n\n /**\n * Обрабатывает ошибку 401 — либо обновляет токен, либо перенаправляет на логин\n *\n * Основной метод для использования в interceptors. Автоматически:\n * - Перенаправляет на логин при невалидном токене\n * - Ставит запрос в очередь, если обновление уже идёт\n * - Инициирует обновление, если ещё не запущено\n *\n * @param isInvalidToken - Если true, токен считается невалидным и будет выполнен переход на логин\n * @returns Промис с новым access токеном или null при ошибке\n *\n * @example\n * ```typescript\n * // В axios response interceptor\n * if (error.response?.status === 401) {\n * const isInvalid = error.response.data?.message === 'Invalid token';\n * const newToken = await manager.handle401(isInvalid);\n *\n * if (newToken) {\n * // Повторить запрос с новым токеном\n * error.config.headers.Authorization = `Bearer ${newToken}`;\n * return axios(error.config);\n * }\n * // newToken === null означает переход на логин\n * }\n * ```\n */\n async handle401(isInvalidToken = false): Promise<string | null> {\n if (isInvalidToken) {\n this.config.onNavigateToLogin();\n return null;\n }\n\n if (this.isRefreshing) {\n return this.waitForTokenRefresh();\n }\n\n const result = await this.refreshToken();\n\n if (!result.ok) {\n this.config.onNavigateToLogin();\n return null;\n }\n\n return result.accessToken || this.config.getAccessToken();\n }\n\n /**\n * Принудительно перенаправляет на страницу логина\n *\n * Вызывает колбэк onNavigateToLogin из конфигурации.\n *\n * @example\n * ```typescript\n * // При необходимости принудительного выхода\n * manager.navigateToLogin();\n * ```\n */\n navigateToLogin(): void {\n this.config.onNavigateToLogin();\n }\n\n /**\n * Сбрасывает внутреннее состояние менеджера\n *\n * Полезно для тестирования или при необходимости\n * принудительно сбросить очередь запросов.\n *\n * @example\n * ```typescript\n * // В тестах\n * beforeEach(() => {\n * manager.reset();\n * });\n * ```\n */\n reset(): void {\n this.isRefreshing = false;\n this.refreshPromise = null;\n this.queuedRequests = [];\n }\n\n private async performRefresh(): Promise<TokenRefreshResult> {\n try {\n const result = await this.config.refreshTokenFn();\n\n if (result.ok && result.accessToken) {\n this.config.setTokens(result.accessToken, result.refreshToken);\n this.notifyQueueSuccess(result.accessToken);\n } else {\n this.notifyQueueFailure(new Error('Token refresh failed'));\n }\n\n this.isRefreshing = false;\n return result;\n } catch (error) {\n this.isRefreshing = false;\n this.notifyQueueFailure(error);\n return { ok: false };\n }\n }\n\n private notifyQueueSuccess(newToken: string): void {\n this.queuedRequests.forEach(({ resolve }) => resolve(newToken));\n this.queuedRequests = [];\n }\n\n private notifyQueueFailure(error: unknown): void {\n this.queuedRequests.forEach(({ reject }) => reject(error));\n this.queuedRequests = [];\n }\n}\n\n/**\n * Создаёт новый экземпляр TokenRefreshManager\n *\n * Фабричная функция для создания менеджера обновления токенов.\n * Предпочтительный способ создания вместо прямого вызова конструктора.\n *\n * @param config - Конфигурация менеджера\n * @returns Новый экземпляр TokenRefreshManager\n *\n * @example\n * ```typescript\n * import { createTokenRefreshManager } from 'mbt-api-client';\n *\n * const tokenManager = createTokenRefreshManager({\n * refreshTokenFn: async () => {\n * const response = await fetch('/api/auth/refresh', {\n * method: 'POST',\n * headers: {\n * Authorization: `Bearer ${localStorage.getItem('refreshToken')}`,\n * },\n * });\n *\n * if (!response.ok) {\n * return { ok: false };\n * }\n *\n * const data = await response.json();\n * return {\n * ok: true,\n * accessToken: data.accessToken,\n * refreshToken: data.refreshToken,\n * };\n * },\n * onNavigateToLogin: () => {\n * window.location.href = '/login';\n * },\n * });\n *\n * // Использование\n * const newToken = await tokenManager.handle401();\n * ```\n */\nexport const createTokenRefreshManager = (\n config: TokenRefreshConfig,\n): TokenRefreshManager => {\n return new TokenRefreshManager(config);\n};\n"],"mappings":"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","names":["defaultNavigateToLogin","retryConfig"]}
|