mbt-api-client 1.0.4 → 1.1.1
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/.ai/agent-examples/README.md +77 -0
- package/.ai/agent-reference/README.md +43 -0
- package/.ai/agent-rules/README.md +32 -0
- package/.ai/agent-rules/maintenance.md +20 -0
- package/AGENTS.md +34 -0
- package/README.md +83 -0
- package/dist/index.cjs +1 -142
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -834
- package/dist/index.d.ts +0 -834
- package/dist/index.js +1 -142
- package/dist/index.js.map +1 -1
- package/package.json +26 -21
package/dist/index.js
CHANGED
|
@@ -20,19 +20,6 @@ var defaultNavigateToLogin = () => {
|
|
|
20
20
|
window.location.href = "#/login";
|
|
21
21
|
};
|
|
22
22
|
var TokenRefreshManager = class {
|
|
23
|
-
/**
|
|
24
|
-
* Создаёт новый экземпляр TokenRefreshManager
|
|
25
|
-
*
|
|
26
|
-
* @param config - Конфигурация менеджера
|
|
27
|
-
*
|
|
28
|
-
* @example
|
|
29
|
-
* ```typescript
|
|
30
|
-
* const manager = new TokenRefreshManager({
|
|
31
|
-
* refreshTokenFn: myRefreshFunction,
|
|
32
|
-
* onNavigateToLogin: () => router.push('/login'),
|
|
33
|
-
* });
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
23
|
constructor(config) {
|
|
37
24
|
this.isRefreshing = false;
|
|
38
25
|
this.refreshPromise = null;
|
|
@@ -46,96 +33,20 @@ var TokenRefreshManager = class {
|
|
|
46
33
|
clearTokens: config.clearTokens ?? defaultClearTokens
|
|
47
34
|
};
|
|
48
35
|
}
|
|
49
|
-
/**
|
|
50
|
-
* Получает текущий access токен
|
|
51
|
-
*
|
|
52
|
-
* @returns Access токен или null, если токен отсутствует
|
|
53
|
-
*
|
|
54
|
-
* @example
|
|
55
|
-
* ```typescript
|
|
56
|
-
* const token = manager.getAccessToken();
|
|
57
|
-
* if (token) {
|
|
58
|
-
* headers.Authorization = `Bearer ${token}`;
|
|
59
|
-
* }
|
|
60
|
-
* ```
|
|
61
|
-
*/
|
|
62
36
|
getAccessToken() {
|
|
63
37
|
return this.config.getAccessToken();
|
|
64
38
|
}
|
|
65
|
-
/**
|
|
66
|
-
* Получает текущий refresh токен
|
|
67
|
-
*
|
|
68
|
-
* @returns Refresh токен или null, если токен отсутствует
|
|
69
|
-
*/
|
|
70
39
|
getRefreshToken() {
|
|
71
40
|
return this.config.getRefreshToken();
|
|
72
41
|
}
|
|
73
|
-
/**
|
|
74
|
-
* Проверяет, идёт ли в данный момент процесс обновления токена
|
|
75
|
-
*
|
|
76
|
-
* Используйте для определения, нужно ли ставить запрос в очередь
|
|
77
|
-
* или инициировать новое обновление.
|
|
78
|
-
*
|
|
79
|
-
* @returns `true` если обновление в процессе, `false` если нет
|
|
80
|
-
*
|
|
81
|
-
* @example
|
|
82
|
-
* ```typescript
|
|
83
|
-
* if (manager.isRefreshInProgress()) {
|
|
84
|
-
* // Ждём завершения текущего обновления
|
|
85
|
-
* const newToken = await manager.waitForTokenRefresh();
|
|
86
|
-
* } else {
|
|
87
|
-
* // Инициируем новое обновление
|
|
88
|
-
* await manager.refreshToken();
|
|
89
|
-
* }
|
|
90
|
-
* ```
|
|
91
|
-
*/
|
|
92
42
|
isRefreshInProgress() {
|
|
93
43
|
return this.isRefreshing;
|
|
94
44
|
}
|
|
95
|
-
/**
|
|
96
|
-
* Добавляет запрос в очередь ожидания обновления токена
|
|
97
|
-
*
|
|
98
|
-
* Возвращает промис, который разрешится с новым токеном после
|
|
99
|
-
* успешного обновления или будет отклонён при ошибке.
|
|
100
|
-
*
|
|
101
|
-
* @returns Промис с новым access токеном
|
|
102
|
-
*
|
|
103
|
-
* @example
|
|
104
|
-
* ```typescript
|
|
105
|
-
* // В response interceptor при множественных 401
|
|
106
|
-
* if (manager.isRefreshInProgress()) {
|
|
107
|
-
* try {
|
|
108
|
-
* const newToken = await manager.waitForTokenRefresh();
|
|
109
|
-
* // Повторить запрос с новым токеном
|
|
110
|
-
* } catch {
|
|
111
|
-
* // Обновление не удалось
|
|
112
|
-
* }
|
|
113
|
-
* }
|
|
114
|
-
* ```
|
|
115
|
-
*/
|
|
116
45
|
waitForTokenRefresh() {
|
|
117
46
|
return new Promise((resolve, reject) => {
|
|
118
47
|
this.queuedRequests.push({ resolve, reject });
|
|
119
48
|
});
|
|
120
49
|
}
|
|
121
|
-
/**
|
|
122
|
-
* Выполняет обновление токена
|
|
123
|
-
*
|
|
124
|
-
* Если обновление уже в процессе, вернёт существующий промис.
|
|
125
|
-
* После обновления уведомляет все запросы в очереди.
|
|
126
|
-
*
|
|
127
|
-
* @returns Промис с результатом обновления
|
|
128
|
-
*
|
|
129
|
-
* @example
|
|
130
|
-
* ```typescript
|
|
131
|
-
* const result = await manager.refreshToken();
|
|
132
|
-
* if (result.ok) {
|
|
133
|
-
* console.log('Новый токен:', result.accessToken);
|
|
134
|
-
* } else {
|
|
135
|
-
* console.log('Ошибка обновления токена');
|
|
136
|
-
* }
|
|
137
|
-
* ```
|
|
138
|
-
*/
|
|
139
50
|
async refreshToken() {
|
|
140
51
|
if (this.refreshPromise) {
|
|
141
52
|
return this.refreshPromise;
|
|
@@ -148,33 +59,6 @@ var TokenRefreshManager = class {
|
|
|
148
59
|
});
|
|
149
60
|
return this.refreshPromise;
|
|
150
61
|
}
|
|
151
|
-
/**
|
|
152
|
-
* Обрабатывает ошибку 401 — либо обновляет токен, либо перенаправляет на логин
|
|
153
|
-
*
|
|
154
|
-
* Основной метод для использования в interceptors. Автоматически:
|
|
155
|
-
* - Перенаправляет на логин при невалидном токене
|
|
156
|
-
* - Ставит запрос в очередь, если обновление уже идёт
|
|
157
|
-
* - Инициирует обновление, если ещё не запущено
|
|
158
|
-
*
|
|
159
|
-
* @param isInvalidToken - Если true, токен считается невалидным и будет выполнен переход на логин
|
|
160
|
-
* @returns Промис с новым access токеном или null при ошибке
|
|
161
|
-
*
|
|
162
|
-
* @example
|
|
163
|
-
* ```typescript
|
|
164
|
-
* // В axios response interceptor
|
|
165
|
-
* if (error.response?.status === 401) {
|
|
166
|
-
* const isInvalid = error.response.data?.message === 'Invalid token';
|
|
167
|
-
* const newToken = await manager.handle401(isInvalid);
|
|
168
|
-
*
|
|
169
|
-
* if (newToken) {
|
|
170
|
-
* // Повторить запрос с новым токеном
|
|
171
|
-
* error.config.headers.Authorization = `Bearer ${newToken}`;
|
|
172
|
-
* return axios(error.config);
|
|
173
|
-
* }
|
|
174
|
-
* // newToken === null означает переход на логин
|
|
175
|
-
* }
|
|
176
|
-
* ```
|
|
177
|
-
*/
|
|
178
62
|
async handle401(isInvalidToken = false) {
|
|
179
63
|
if (isInvalidToken) {
|
|
180
64
|
this.config.onNavigateToLogin();
|
|
@@ -190,34 +74,9 @@ var TokenRefreshManager = class {
|
|
|
190
74
|
}
|
|
191
75
|
return result.accessToken || this.config.getAccessToken();
|
|
192
76
|
}
|
|
193
|
-
/**
|
|
194
|
-
* Принудительно перенаправляет на страницу логина
|
|
195
|
-
*
|
|
196
|
-
* Вызывает колбэк onNavigateToLogin из конфигурации.
|
|
197
|
-
*
|
|
198
|
-
* @example
|
|
199
|
-
* ```typescript
|
|
200
|
-
* // При необходимости принудительного выхода
|
|
201
|
-
* manager.navigateToLogin();
|
|
202
|
-
* ```
|
|
203
|
-
*/
|
|
204
77
|
navigateToLogin() {
|
|
205
78
|
this.config.onNavigateToLogin();
|
|
206
79
|
}
|
|
207
|
-
/**
|
|
208
|
-
* Сбрасывает внутреннее состояние менеджера
|
|
209
|
-
*
|
|
210
|
-
* Полезно для тестирования или при необходимости
|
|
211
|
-
* принудительно сбросить очередь запросов.
|
|
212
|
-
*
|
|
213
|
-
* @example
|
|
214
|
-
* ```typescript
|
|
215
|
-
* // В тестах
|
|
216
|
-
* beforeEach(() => {
|
|
217
|
-
* manager.reset();
|
|
218
|
-
* });
|
|
219
|
-
* ```
|
|
220
|
-
*/
|
|
221
80
|
reset() {
|
|
222
81
|
this.isRefreshing = false;
|
|
223
82
|
this.refreshPromise = null;
|
|
@@ -265,7 +124,7 @@ var defaultDecodeJwt = (token) => {
|
|
|
265
124
|
const base64Url = token.split(".")[1];
|
|
266
125
|
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
|
|
267
126
|
const jsonPayload = decodeURIComponent(
|
|
268
|
-
atob(base64).split("").map((c) =>
|
|
127
|
+
atob(base64).split("").map((c) => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`).join("")
|
|
269
128
|
);
|
|
270
129
|
return JSON.parse(jsonPayload);
|
|
271
130
|
} catch {
|
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 micromodeController: string;\n\n /** URL сервиса управления пользователями */\n userManager: 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 micromodeController: ApiService;\n\n /**\n * Сервис управления пользователями\n */\n userManager: 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({\n ...error,\n message: 'Failed to refresh token',\n });\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({\n ...error,\n message: 'Failed to refresh token',\n });\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 micromodeControllerAxiosInstance = createAxiosInstance(\n urls.micromodeController,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const userManagerAxiosInstance = createAxiosInstance(\n urls.userManager,\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 micromodeController: createApiService(micromodeControllerAxiosInstance),\n userManager: createApiService(userManagerAxiosInstance),\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":["import 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\nexport interface ApiClientUrls {\n authService: string;\n\n userProgressService: string;\n\n recommendationService: string;\n\n partnerManager: string;\n\n adventureManager: string;\n\n bff: string;\n\n puzzleController: string;\n\n micromodeController: string;\n\n userManager: string;\n\n monolith: string;\n}\n\nexport interface ApiClientConfig {\n urls: ApiClientUrls;\n\n onNavigateToLogin?: () => void;\n\n timeout?: number;\n\n decodeJwt?: (token: string) => { userId?: string } | null;\n}\n\nexport interface ApiService {\n get: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>;\n\n post: <T>(\n url: string,\n data?: unknown,\n config?: AxiosRequestConfig,\n ) => Promise<T>;\n\n put: <T>(\n url: string,\n data?: unknown,\n config?: AxiosRequestConfig,\n ) => Promise<T>;\n\n patch: <T>(\n url: string,\n data?: unknown,\n config?: AxiosRequestConfig,\n ) => Promise<T>;\n\n delete: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>;\n\n axiosInstance: AxiosInstance;\n}\n\nexport interface ApiClient {\n authService: ApiService;\n\n userProgressService: ApiService;\n\n recommendationService: ApiService;\n\n partnerManager: ApiService;\n\n adventureManager: ApiService;\n\n bff: ApiService;\n\n puzzleController: ApiService;\n\n micromodeController: ApiService;\n\n userManager: ApiService;\n\n monolith: ApiService;\n\n tokenRefreshManager: TokenRefreshManager;\n}\n\nconst RETRY_KEY = Symbol('retry');\n\ninterface ExtendedAxiosRequestConfig extends InternalAxiosRequestConfig {\n [RETRY_KEY]?: boolean;\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\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 // Refresh requests must use the refresh token, while all other requests\n // use the current access token.\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 // A retried request that still gets a 401 should stop here instead of\n // entering a refresh loop.\n if (originalRequest[RETRY_KEY]) {\n onNavigateToLogin();\n return Promise.reject(error);\n }\n\n // When a refresh is already running, wait for that result and replay\n // the request instead of starting another refresh call.\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({\n ...error,\n message: 'Failed to refresh token',\n });\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({\n ...error,\n message: 'Failed to refresh token',\n });\n }\n\n return Promise.reject(error);\n },\n );\n\n return instance;\n};\n\nconst createApiService = (axiosInstance: AxiosInstance): ApiService => {\n return {\n get: <T>(url: string, config?: AxiosRequestConfig): Promise<T> => {\n // GET requests always receive a cache-buster so stale cached responses do\n // not leak into app state.\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\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 micromodeControllerAxiosInstance = createAxiosInstance(\n urls.micromodeController,\n tokenRefreshManager,\n onNavigateToLogin,\n timeout,\n decodeJwt,\n );\n\n const userManagerAxiosInstance = createAxiosInstance(\n urls.userManager,\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 micromodeController: createApiService(micromodeControllerAxiosInstance),\n userManager: createApiService(userManagerAxiosInstance),\n monolith: createApiService(monolithAxiosInstance),\n tokenRefreshManager,\n };\n};\n\nexport const ApiClientContext = createContext<ApiClient | null>(null);\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\nexport const ApiClientProvider = ApiClientContext.Provider;\n\nexport {\n TokenRefreshManager,\n createTokenRefreshManager,\n type TokenRefreshConfig,\n type TokenRefreshResult,\n} from './token-refresh';\n","export interface TokenRefreshConfig {\n refreshTokenFn: () => Promise<TokenRefreshResult>;\n\n onNavigateToLogin?: () => void;\n\n getAccessToken?: () => string | null;\n\n getRefreshToken?: () => string | null;\n\n setTokens?: (accessToken: string, refreshToken?: string) => void;\n\n clearTokens?: () => void;\n}\n\nexport interface TokenRefreshResult {\n ok: boolean;\n\n accessToken?: string;\n\n refreshToken?: string;\n}\n\nexport interface QueuedRequest<T = unknown> {\n resolve: (value: T) => void;\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\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 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 getAccessToken(): string | null {\n return this.config.getAccessToken();\n }\n\n getRefreshToken(): string | null {\n return this.config.getRefreshToken();\n }\n\n isRefreshInProgress(): boolean {\n return this.isRefreshing;\n }\n\n waitForTokenRefresh(): Promise<string> {\n return new Promise((resolve, reject) => {\n this.queuedRequests.push({ resolve, reject });\n });\n }\n\n async refreshToken(): Promise<TokenRefreshResult> {\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n this.isRefreshing = true;\n\n // Keep a shared in-flight promise so concurrent 401 handlers all wait for\n // the same refresh attempt.\n this.refreshPromise = this.performRefresh().finally(() => {\n // Clear the cached promise on the next tick so callers can still await\n // the current result while a later refresh attempt can start cleanly.\n setTimeout(() => {\n this.refreshPromise = null;\n }, 100);\n });\n\n return this.refreshPromise;\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 navigateToLogin(): void {\n this.config.onNavigateToLogin();\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\nexport const createTokenRefreshManager = (\n config: TokenRefreshConfig,\n): TokenRefreshManager => {\n return new TokenRefreshManager(config);\n};\n"],"mappings":"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","names":["defaultNavigateToLogin","retryConfig"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mbt-api-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "API client with token refresh queue for mbt",
|
|
5
|
+
"packageManager": "pnpm@10.28.0",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"main": "./dist/index.cjs",
|
|
7
8
|
"module": "./dist/index.js",
|
|
@@ -14,40 +15,44 @@
|
|
|
14
15
|
}
|
|
15
16
|
},
|
|
16
17
|
"files": [
|
|
17
|
-
"dist"
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"AGENTS.md",
|
|
21
|
+
".ai"
|
|
18
22
|
],
|
|
19
23
|
"scripts": {
|
|
20
24
|
"build": "tsup",
|
|
21
25
|
"dev": "tsup --watch",
|
|
22
|
-
"lint": "eslint
|
|
23
|
-
"lint:fix": "eslint
|
|
24
|
-
"format": "prettier --write
|
|
25
|
-
"test": "vitest run",
|
|
26
|
-
"test:watch": "vitest",
|
|
27
|
-
"
|
|
26
|
+
"lint": "eslint . && prettier --check .",
|
|
27
|
+
"lint:fix": "eslint . --fix && prettier --write .",
|
|
28
|
+
"format": "prettier --write .",
|
|
29
|
+
"test": "vitest run --passWithNoTests",
|
|
30
|
+
"test:watch": "vitest --passWithNoTests",
|
|
31
|
+
"verify:packaged-guidance": "node scripts/verify-packaged-guidance.mjs",
|
|
32
|
+
"prepublishOnly": "pnpm build && pnpm verify:packaged-guidance"
|
|
28
33
|
},
|
|
29
34
|
"peerDependencies": {
|
|
30
|
-
"
|
|
31
|
-
"
|
|
35
|
+
"axios": ">=1.0.0",
|
|
36
|
+
"react": ">=17.0.0"
|
|
32
37
|
},
|
|
33
38
|
"devDependencies": {
|
|
39
|
+
"@eslint/js": "^9.39.4",
|
|
34
40
|
"@types/react": "^18.3.3",
|
|
35
|
-
"@
|
|
36
|
-
"@typescript-eslint/parser": "^7.16.0",
|
|
37
|
-
"@vitest/coverage-v8": "^4.0.16",
|
|
41
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
38
42
|
"axios": "^1.13.2",
|
|
39
43
|
"axios-mock-adapter": "^2.1.0",
|
|
40
|
-
"eslint": "^
|
|
41
|
-
"eslint-config-prettier": "^
|
|
42
|
-
"eslint-plugin-import": "^2.
|
|
43
|
-
"eslint-plugin-
|
|
44
|
-
"
|
|
45
|
-
"jsdom": "^
|
|
46
|
-
"prettier": "^3.
|
|
44
|
+
"eslint": "^9.39.4",
|
|
45
|
+
"eslint-config-prettier": "^10.1.8",
|
|
46
|
+
"eslint-plugin-import": "^2.32.0",
|
|
47
|
+
"eslint-plugin-unused-imports": "^4.4.1",
|
|
48
|
+
"globals": "^17.4.0",
|
|
49
|
+
"jsdom": "^29.0.1",
|
|
50
|
+
"prettier": "^3.8.1",
|
|
47
51
|
"react": "^18.3.1",
|
|
48
52
|
"tsup": "^8.0.0",
|
|
49
53
|
"typescript": "^5.5.4",
|
|
50
|
-
"
|
|
54
|
+
"typescript-eslint": "^8.57.2",
|
|
55
|
+
"vitest": "^4.1.2"
|
|
51
56
|
},
|
|
52
57
|
"keywords": [
|
|
53
58
|
"api-client",
|