mbt-api-client 1.1.0 → 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 -189
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -841
- package/dist/index.d.ts +1 -841
- package/dist/index.js +1 -185
- package/dist/index.js.map +1 -1
- package/package.json +25 -22
package/dist/index.d.ts
CHANGED
|
@@ -1,923 +1,83 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { AxiosRequestConfig, AxiosInstance } from 'axios';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* Модуль управления обновлением токенов
|
|
6
|
-
*
|
|
7
|
-
* Реализует механизм обновления JWT токенов с очередью запросов.
|
|
8
|
-
* Предотвращает множественные одновременные запросы на обновление токена,
|
|
9
|
-
* когда несколько параллельных API-запросов получают ошибку 401.
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```typescript
|
|
13
|
-
* // Создание менеджера с кастомными функциями
|
|
14
|
-
* const tokenManager = createTokenRefreshManager({
|
|
15
|
-
* refreshTokenFn: async () => {
|
|
16
|
-
* const response = await fetch('/api/auth/refresh');
|
|
17
|
-
* const data = await response.json();
|
|
18
|
-
* return { ok: true, accessToken: data.accessToken };
|
|
19
|
-
* },
|
|
20
|
-
* onNavigateToLogin: () => router.push('/login'),
|
|
21
|
-
* });
|
|
22
|
-
*
|
|
23
|
-
* // Использование с RTK Query или другими клиентами
|
|
24
|
-
* if (response.status === 401) {
|
|
25
|
-
* const newToken = await tokenManager.handle401();
|
|
26
|
-
* // Повторить запрос с новым токеном
|
|
27
|
-
* }
|
|
28
|
-
* ```
|
|
29
|
-
*
|
|
30
|
-
* @module token-refresh
|
|
31
|
-
*/
|
|
32
|
-
/**
|
|
33
|
-
* Конфигурация менеджера обновления токенов
|
|
34
|
-
*
|
|
35
|
-
* @example
|
|
36
|
-
* ```typescript
|
|
37
|
-
* const config: TokenRefreshConfig = {
|
|
38
|
-
* refreshTokenFn: async () => {
|
|
39
|
-
* // Ваша логика обновления токена
|
|
40
|
-
* return { ok: true, accessToken: 'new-token' };
|
|
41
|
-
* },
|
|
42
|
-
* onNavigateToLogin: () => window.location.href = '/login',
|
|
43
|
-
* getAccessToken: () => localStorage.getItem('accessToken'),
|
|
44
|
-
* getRefreshToken: () => localStorage.getItem('refreshToken'),
|
|
45
|
-
* setTokens: (access, refresh) => {
|
|
46
|
-
* localStorage.setItem('accessToken', access);
|
|
47
|
-
* if (refresh) localStorage.setItem('refreshToken', refresh);
|
|
48
|
-
* },
|
|
49
|
-
* clearTokens: () => {
|
|
50
|
-
* localStorage.removeItem('accessToken');
|
|
51
|
-
* localStorage.removeItem('refreshToken');
|
|
52
|
-
* },
|
|
53
|
-
* };
|
|
54
|
-
* ```
|
|
55
|
-
*/
|
|
56
4
|
interface TokenRefreshConfig {
|
|
57
|
-
/**
|
|
58
|
-
* Функция для выполнения запроса на обновление токена
|
|
59
|
-
*
|
|
60
|
-
* Должна вернуть объект с `ok: true` и новым `accessToken` при успехе,
|
|
61
|
-
* или `ok: false` при ошибке.
|
|
62
|
-
*
|
|
63
|
-
* @returns Промис с результатом обновления токена
|
|
64
|
-
*
|
|
65
|
-
* @example
|
|
66
|
-
* ```typescript
|
|
67
|
-
* refreshTokenFn: async () => {
|
|
68
|
-
* try {
|
|
69
|
-
* const response = await axios.post('/auth/refresh-token', {}, {
|
|
70
|
-
* headers: { Authorization: `Bearer ${refreshToken}` }
|
|
71
|
-
* });
|
|
72
|
-
* return {
|
|
73
|
-
* ok: true,
|
|
74
|
-
* accessToken: response.data.accessToken,
|
|
75
|
-
* refreshToken: response.data.refreshToken,
|
|
76
|
-
* };
|
|
77
|
-
* } catch {
|
|
78
|
-
* return { ok: false };
|
|
79
|
-
* }
|
|
80
|
-
* }
|
|
81
|
-
* ```
|
|
82
|
-
*/
|
|
83
5
|
refreshTokenFn: () => Promise<TokenRefreshResult>;
|
|
84
|
-
/**
|
|
85
|
-
* Колбэк для перенаправления на страницу логина
|
|
86
|
-
*
|
|
87
|
-
* Вызывается когда:
|
|
88
|
-
* - Токен невалиден (message: 'Invalid token')
|
|
89
|
-
* - Обновление токена не удалось
|
|
90
|
-
* - Refresh token истёк
|
|
91
|
-
*
|
|
92
|
-
* @default Очищает localStorage и редиректит на '#/login'
|
|
93
|
-
*
|
|
94
|
-
* @example
|
|
95
|
-
* ```typescript
|
|
96
|
-
* onNavigateToLogin: () => {
|
|
97
|
-
* // Для React Router
|
|
98
|
-
* navigate('/login');
|
|
99
|
-
* // Или для Next.js
|
|
100
|
-
* router.push('/login');
|
|
101
|
-
* }
|
|
102
|
-
* ```
|
|
103
|
-
*/
|
|
104
6
|
onNavigateToLogin?: () => void;
|
|
105
|
-
/**
|
|
106
|
-
* Функция получения текущего access токена
|
|
107
|
-
*
|
|
108
|
-
* @default () => localStorage.getItem('accessToken')
|
|
109
|
-
*
|
|
110
|
-
* @example
|
|
111
|
-
* ```typescript
|
|
112
|
-
* // Для хранения в Redux
|
|
113
|
-
* getAccessToken: () => store.getState().auth.accessToken
|
|
114
|
-
* ```
|
|
115
|
-
*/
|
|
116
7
|
getAccessToken?: () => string | null;
|
|
117
|
-
/**
|
|
118
|
-
* Функция получения текущего refresh токена
|
|
119
|
-
*
|
|
120
|
-
* @default () => localStorage.getItem('refreshToken')
|
|
121
|
-
*/
|
|
122
8
|
getRefreshToken?: () => string | null;
|
|
123
|
-
/**
|
|
124
|
-
* Функция сохранения новых токенов после обновления
|
|
125
|
-
*
|
|
126
|
-
* @param accessToken - Новый access токен
|
|
127
|
-
* @param refreshToken - Новый refresh токен (опционально)
|
|
128
|
-
*
|
|
129
|
-
* @default Сохраняет в localStorage
|
|
130
|
-
*
|
|
131
|
-
* @example
|
|
132
|
-
* ```typescript
|
|
133
|
-
* // Для хранения в Redux
|
|
134
|
-
* setTokens: (access, refresh) => {
|
|
135
|
-
* store.dispatch(setAuthTokens({ accessToken: access, refreshToken: refresh }));
|
|
136
|
-
* }
|
|
137
|
-
* ```
|
|
138
|
-
*/
|
|
139
9
|
setTokens?: (accessToken: string, refreshToken?: string) => void;
|
|
140
|
-
/**
|
|
141
|
-
* Функция очистки токенов (при выходе или ошибке авторизации)
|
|
142
|
-
*
|
|
143
|
-
* @default Удаляет 'accessToken' и 'refreshToken' из localStorage
|
|
144
|
-
*/
|
|
145
10
|
clearTokens?: () => void;
|
|
146
11
|
}
|
|
147
|
-
/**
|
|
148
|
-
* Результат операции обновления токена
|
|
149
|
-
*
|
|
150
|
-
* @example
|
|
151
|
-
* ```typescript
|
|
152
|
-
* // Успешное обновление
|
|
153
|
-
* const success: TokenRefreshResult = {
|
|
154
|
-
* ok: true,
|
|
155
|
-
* accessToken: 'eyJhbGciOiJIUzI1...',
|
|
156
|
-
* refreshToken: 'dGhpcyBpcyBhIHJl...',
|
|
157
|
-
* };
|
|
158
|
-
*
|
|
159
|
-
* // Ошибка обновления
|
|
160
|
-
* const failure: TokenRefreshResult = { ok: false };
|
|
161
|
-
* ```
|
|
162
|
-
*/
|
|
163
12
|
interface TokenRefreshResult {
|
|
164
|
-
/**
|
|
165
|
-
* Успешность операции обновления
|
|
166
|
-
*
|
|
167
|
-
* `true` - токен успешно обновлён
|
|
168
|
-
* `false` - ошибка обновления (пользователь будет перенаправлен на логин)
|
|
169
|
-
*/
|
|
170
13
|
ok: boolean;
|
|
171
|
-
/**
|
|
172
|
-
* Новый access токен (только при `ok: true`)
|
|
173
|
-
*/
|
|
174
14
|
accessToken?: string;
|
|
175
|
-
/**
|
|
176
|
-
* Новый refresh токен (опционально, только при `ok: true`)
|
|
177
|
-
*/
|
|
178
15
|
refreshToken?: string;
|
|
179
16
|
}
|
|
180
|
-
/**
|
|
181
|
-
* Менеджер обновления токенов с очередью запросов
|
|
182
|
-
*
|
|
183
|
-
* Решает проблему одновременного обновления токена при множественных
|
|
184
|
-
* параллельных запросах, получивших 401 ошибку.
|
|
185
|
-
*
|
|
186
|
-
* **Как это работает:**
|
|
187
|
-
* 1. Первый запрос с 401 инициирует обновление токена
|
|
188
|
-
* 2. Последующие запросы с 401 добавляются в очередь ожидания
|
|
189
|
-
* 3. После успешного обновления все запросы в очереди получают новый токен
|
|
190
|
-
* 4. При ошибке обновления все запросы получают ошибку
|
|
191
|
-
*
|
|
192
|
-
* @example
|
|
193
|
-
* ```typescript
|
|
194
|
-
* // Создание менеджера
|
|
195
|
-
* const tokenManager = new TokenRefreshManager({
|
|
196
|
-
* refreshTokenFn: async () => {
|
|
197
|
-
* const res = await fetch('/api/refresh');
|
|
198
|
-
* const data = await res.json();
|
|
199
|
-
* return { ok: true, accessToken: data.token };
|
|
200
|
-
* },
|
|
201
|
-
* });
|
|
202
|
-
*
|
|
203
|
-
* // Использование в axios interceptor
|
|
204
|
-
* axios.interceptors.response.use(
|
|
205
|
-
* response => response,
|
|
206
|
-
* async error => {
|
|
207
|
-
* if (error.response?.status === 401) {
|
|
208
|
-
* const newToken = await tokenManager.handle401();
|
|
209
|
-
* if (newToken) {
|
|
210
|
-
* error.config.headers.Authorization = `Bearer ${newToken}`;
|
|
211
|
-
* return axios(error.config);
|
|
212
|
-
* }
|
|
213
|
-
* }
|
|
214
|
-
* return Promise.reject(error);
|
|
215
|
-
* }
|
|
216
|
-
* );
|
|
217
|
-
* ```
|
|
218
|
-
*/
|
|
219
17
|
declare class TokenRefreshManager {
|
|
220
18
|
private isRefreshing;
|
|
221
19
|
private refreshPromise;
|
|
222
20
|
private queuedRequests;
|
|
223
21
|
private config;
|
|
224
|
-
/**
|
|
225
|
-
* Создаёт новый экземпляр TokenRefreshManager
|
|
226
|
-
*
|
|
227
|
-
* @param config - Конфигурация менеджера
|
|
228
|
-
*
|
|
229
|
-
* @example
|
|
230
|
-
* ```typescript
|
|
231
|
-
* const manager = new TokenRefreshManager({
|
|
232
|
-
* refreshTokenFn: myRefreshFunction,
|
|
233
|
-
* onNavigateToLogin: () => router.push('/login'),
|
|
234
|
-
* });
|
|
235
|
-
* ```
|
|
236
|
-
*/
|
|
237
22
|
constructor(config: TokenRefreshConfig);
|
|
238
|
-
/**
|
|
239
|
-
* Получает текущий access токен
|
|
240
|
-
*
|
|
241
|
-
* @returns Access токен или null, если токен отсутствует
|
|
242
|
-
*
|
|
243
|
-
* @example
|
|
244
|
-
* ```typescript
|
|
245
|
-
* const token = manager.getAccessToken();
|
|
246
|
-
* if (token) {
|
|
247
|
-
* headers.Authorization = `Bearer ${token}`;
|
|
248
|
-
* }
|
|
249
|
-
* ```
|
|
250
|
-
*/
|
|
251
23
|
getAccessToken(): string | null;
|
|
252
|
-
/**
|
|
253
|
-
* Получает текущий refresh токен
|
|
254
|
-
*
|
|
255
|
-
* @returns Refresh токен или null, если токен отсутствует
|
|
256
|
-
*/
|
|
257
24
|
getRefreshToken(): string | null;
|
|
258
|
-
/**
|
|
259
|
-
* Проверяет, идёт ли в данный момент процесс обновления токена
|
|
260
|
-
*
|
|
261
|
-
* Используйте для определения, нужно ли ставить запрос в очередь
|
|
262
|
-
* или инициировать новое обновление.
|
|
263
|
-
*
|
|
264
|
-
* @returns `true` если обновление в процессе, `false` если нет
|
|
265
|
-
*
|
|
266
|
-
* @example
|
|
267
|
-
* ```typescript
|
|
268
|
-
* if (manager.isRefreshInProgress()) {
|
|
269
|
-
* // Ждём завершения текущего обновления
|
|
270
|
-
* const newToken = await manager.waitForTokenRefresh();
|
|
271
|
-
* } else {
|
|
272
|
-
* // Инициируем новое обновление
|
|
273
|
-
* await manager.refreshToken();
|
|
274
|
-
* }
|
|
275
|
-
* ```
|
|
276
|
-
*/
|
|
277
25
|
isRefreshInProgress(): boolean;
|
|
278
|
-
/**
|
|
279
|
-
* Добавляет запрос в очередь ожидания обновления токена
|
|
280
|
-
*
|
|
281
|
-
* Возвращает промис, который разрешится с новым токеном после
|
|
282
|
-
* успешного обновления или будет отклонён при ошибке.
|
|
283
|
-
*
|
|
284
|
-
* @returns Промис с новым access токеном
|
|
285
|
-
*
|
|
286
|
-
* @example
|
|
287
|
-
* ```typescript
|
|
288
|
-
* // В response interceptor при множественных 401
|
|
289
|
-
* if (manager.isRefreshInProgress()) {
|
|
290
|
-
* try {
|
|
291
|
-
* const newToken = await manager.waitForTokenRefresh();
|
|
292
|
-
* // Повторить запрос с новым токеном
|
|
293
|
-
* } catch {
|
|
294
|
-
* // Обновление не удалось
|
|
295
|
-
* }
|
|
296
|
-
* }
|
|
297
|
-
* ```
|
|
298
|
-
*/
|
|
299
26
|
waitForTokenRefresh(): Promise<string>;
|
|
300
|
-
/**
|
|
301
|
-
* Выполняет обновление токена
|
|
302
|
-
*
|
|
303
|
-
* Если обновление уже в процессе, вернёт существующий промис.
|
|
304
|
-
* После обновления уведомляет все запросы в очереди.
|
|
305
|
-
*
|
|
306
|
-
* @returns Промис с результатом обновления
|
|
307
|
-
*
|
|
308
|
-
* @example
|
|
309
|
-
* ```typescript
|
|
310
|
-
* const result = await manager.refreshToken();
|
|
311
|
-
* if (result.ok) {
|
|
312
|
-
* console.log('Новый токен:', result.accessToken);
|
|
313
|
-
* } else {
|
|
314
|
-
* console.log('Ошибка обновления токена');
|
|
315
|
-
* }
|
|
316
|
-
* ```
|
|
317
|
-
*/
|
|
318
27
|
refreshToken(): Promise<TokenRefreshResult>;
|
|
319
|
-
/**
|
|
320
|
-
* Обрабатывает ошибку 401 — либо обновляет токен, либо перенаправляет на логин
|
|
321
|
-
*
|
|
322
|
-
* Основной метод для использования в interceptors. Автоматически:
|
|
323
|
-
* - Перенаправляет на логин при невалидном токене
|
|
324
|
-
* - Ставит запрос в очередь, если обновление уже идёт
|
|
325
|
-
* - Инициирует обновление, если ещё не запущено
|
|
326
|
-
*
|
|
327
|
-
* @param isInvalidToken - Если true, токен считается невалидным и будет выполнен переход на логин
|
|
328
|
-
* @returns Промис с новым access токеном или null при ошибке
|
|
329
|
-
*
|
|
330
|
-
* @example
|
|
331
|
-
* ```typescript
|
|
332
|
-
* // В axios response interceptor
|
|
333
|
-
* if (error.response?.status === 401) {
|
|
334
|
-
* const isInvalid = error.response.data?.message === 'Invalid token';
|
|
335
|
-
* const newToken = await manager.handle401(isInvalid);
|
|
336
|
-
*
|
|
337
|
-
* if (newToken) {
|
|
338
|
-
* // Повторить запрос с новым токеном
|
|
339
|
-
* error.config.headers.Authorization = `Bearer ${newToken}`;
|
|
340
|
-
* return axios(error.config);
|
|
341
|
-
* }
|
|
342
|
-
* // newToken === null означает переход на логин
|
|
343
|
-
* }
|
|
344
|
-
* ```
|
|
345
|
-
*/
|
|
346
28
|
handle401(isInvalidToken?: boolean): Promise<string | null>;
|
|
347
|
-
/**
|
|
348
|
-
* Принудительно перенаправляет на страницу логина
|
|
349
|
-
*
|
|
350
|
-
* Вызывает колбэк onNavigateToLogin из конфигурации.
|
|
351
|
-
*
|
|
352
|
-
* @example
|
|
353
|
-
* ```typescript
|
|
354
|
-
* // При необходимости принудительного выхода
|
|
355
|
-
* manager.navigateToLogin();
|
|
356
|
-
* ```
|
|
357
|
-
*/
|
|
358
29
|
navigateToLogin(): void;
|
|
359
|
-
/**
|
|
360
|
-
* Сбрасывает внутреннее состояние менеджера
|
|
361
|
-
*
|
|
362
|
-
* Полезно для тестирования или при необходимости
|
|
363
|
-
* принудительно сбросить очередь запросов.
|
|
364
|
-
*
|
|
365
|
-
* @example
|
|
366
|
-
* ```typescript
|
|
367
|
-
* // В тестах
|
|
368
|
-
* beforeEach(() => {
|
|
369
|
-
* manager.reset();
|
|
370
|
-
* });
|
|
371
|
-
* ```
|
|
372
|
-
*/
|
|
373
30
|
reset(): void;
|
|
374
31
|
private performRefresh;
|
|
375
32
|
private notifyQueueSuccess;
|
|
376
33
|
private notifyQueueFailure;
|
|
377
34
|
}
|
|
378
|
-
/**
|
|
379
|
-
* Создаёт новый экземпляр TokenRefreshManager
|
|
380
|
-
*
|
|
381
|
-
* Фабричная функция для создания менеджера обновления токенов.
|
|
382
|
-
* Предпочтительный способ создания вместо прямого вызова конструктора.
|
|
383
|
-
*
|
|
384
|
-
* @param config - Конфигурация менеджера
|
|
385
|
-
* @returns Новый экземпляр TokenRefreshManager
|
|
386
|
-
*
|
|
387
|
-
* @example
|
|
388
|
-
* ```typescript
|
|
389
|
-
* import { createTokenRefreshManager } from 'mbt-api-client';
|
|
390
|
-
*
|
|
391
|
-
* const tokenManager = createTokenRefreshManager({
|
|
392
|
-
* refreshTokenFn: async () => {
|
|
393
|
-
* const response = await fetch('/api/auth/refresh', {
|
|
394
|
-
* method: 'POST',
|
|
395
|
-
* headers: {
|
|
396
|
-
* Authorization: `Bearer ${localStorage.getItem('refreshToken')}`,
|
|
397
|
-
* },
|
|
398
|
-
* });
|
|
399
|
-
*
|
|
400
|
-
* if (!response.ok) {
|
|
401
|
-
* return { ok: false };
|
|
402
|
-
* }
|
|
403
|
-
*
|
|
404
|
-
* const data = await response.json();
|
|
405
|
-
* return {
|
|
406
|
-
* ok: true,
|
|
407
|
-
* accessToken: data.accessToken,
|
|
408
|
-
* refreshToken: data.refreshToken,
|
|
409
|
-
* };
|
|
410
|
-
* },
|
|
411
|
-
* onNavigateToLogin: () => {
|
|
412
|
-
* window.location.href = '/login';
|
|
413
|
-
* },
|
|
414
|
-
* });
|
|
415
|
-
*
|
|
416
|
-
* // Использование
|
|
417
|
-
* const newToken = await tokenManager.handle401();
|
|
418
|
-
* ```
|
|
419
|
-
*/
|
|
420
35
|
declare const createTokenRefreshManager: (config: TokenRefreshConfig) => TokenRefreshManager;
|
|
421
36
|
|
|
422
|
-
/**
|
|
423
|
-
* URL-адреса микросервисов API
|
|
424
|
-
*
|
|
425
|
-
* @example
|
|
426
|
-
* ```typescript
|
|
427
|
-
* const urls: ApiClientUrls = {
|
|
428
|
-
* authService: 'https://auth.example.com',
|
|
429
|
-
* userProgressService: 'https://progress.example.com',
|
|
430
|
-
* recommendationService: 'https://rec.example.com',
|
|
431
|
-
* partnerManager: 'https://partners.example.com',
|
|
432
|
-
* puzzleController: 'https://puzzles.example.com',
|
|
433
|
-
* monolith: 'https://api.example.com',
|
|
434
|
-
* };
|
|
435
|
-
* ```
|
|
436
|
-
*/
|
|
437
37
|
interface ApiClientUrls {
|
|
438
|
-
/** URL сервиса авторизации (логин, регистрация, refresh токенов) */
|
|
439
38
|
authService: string;
|
|
440
|
-
/** URL сервиса прогресса пользователя */
|
|
441
39
|
userProgressService: string;
|
|
442
|
-
/** URL сервиса рекомендаций */
|
|
443
40
|
recommendationService: string;
|
|
444
|
-
/** URL сервиса управления партнёрами */
|
|
445
41
|
partnerManager: string;
|
|
446
|
-
/** URL сервиса управления приключениями */
|
|
447
42
|
adventureManager: string;
|
|
448
|
-
/** URL сервиса агрегации данных для клиента */
|
|
449
43
|
bff: string;
|
|
450
|
-
/** URL контроллера пазлов/задач */
|
|
451
44
|
puzzleController: string;
|
|
452
|
-
/** URL контроллера микромодов */
|
|
453
45
|
micromodeController: string;
|
|
454
|
-
/** URL сервиса управления пользователями */
|
|
455
46
|
userManager: string;
|
|
456
|
-
/** URL основного монолитного сервиса */
|
|
457
47
|
monolith: string;
|
|
458
48
|
}
|
|
459
|
-
/**
|
|
460
|
-
* Конфигурация API клиента
|
|
461
|
-
*
|
|
462
|
-
* @example
|
|
463
|
-
* ```typescript
|
|
464
|
-
* const config: ApiClientConfig = {
|
|
465
|
-
* urls: {
|
|
466
|
-
* authService: 'https://auth.example.com',
|
|
467
|
-
* userProgressService: 'https://progress.example.com',
|
|
468
|
-
* // ... остальные URL
|
|
469
|
-
* },
|
|
470
|
-
* onNavigateToLogin: () => {
|
|
471
|
-
* // Для React Router
|
|
472
|
-
* navigate('/login');
|
|
473
|
-
* },
|
|
474
|
-
* timeout: 15000,
|
|
475
|
-
* decodeJwt: (token) => {
|
|
476
|
-
* // Кастомный декодер JWT
|
|
477
|
-
* return jwtDecode(token);
|
|
478
|
-
* },
|
|
479
|
-
* };
|
|
480
|
-
* ```
|
|
481
|
-
*/
|
|
482
49
|
interface ApiClientConfig {
|
|
483
|
-
/**
|
|
484
|
-
* URL-адреса микросервисов
|
|
485
|
-
*/
|
|
486
50
|
urls: ApiClientUrls;
|
|
487
|
-
/**
|
|
488
|
-
* Колбэк для перенаправления на страницу логина
|
|
489
|
-
*
|
|
490
|
-
* Вызывается при:
|
|
491
|
-
* - Ошибке 401 с невалидным токеном
|
|
492
|
-
* - Ошибке 403 (доступ запрещён)
|
|
493
|
-
* - Неудачном обновлении токена
|
|
494
|
-
*
|
|
495
|
-
* @default Очищает localStorage и редиректит на '#/login'
|
|
496
|
-
*
|
|
497
|
-
* @example
|
|
498
|
-
* ```typescript
|
|
499
|
-
* // React Router
|
|
500
|
-
* onNavigateToLogin: () => navigate('/login')
|
|
501
|
-
*
|
|
502
|
-
* // Next.js
|
|
503
|
-
* onNavigateToLogin: () => router.push('/login')
|
|
504
|
-
*
|
|
505
|
-
* // Vanilla JS
|
|
506
|
-
* onNavigateToLogin: () => { window.location.href = '/login' }
|
|
507
|
-
* ```
|
|
508
|
-
*/
|
|
509
51
|
onNavigateToLogin?: () => void;
|
|
510
|
-
/**
|
|
511
|
-
* Таймаут запросов в миллисекундах
|
|
512
|
-
*
|
|
513
|
-
* @default 10000 (10 секунд)
|
|
514
|
-
*/
|
|
515
52
|
timeout?: number;
|
|
516
|
-
/**
|
|
517
|
-
* Функция декодирования JWT токена для извлечения userId
|
|
518
|
-
*
|
|
519
|
-
* Используется для добавления заголовка X-User-Id к запросам.
|
|
520
|
-
* Если не указана, используется встроенный base64 декодер.
|
|
521
|
-
*
|
|
522
|
-
* @param token - JWT токен для декодирования
|
|
523
|
-
* @returns Объект с userId или null при ошибке декодирования
|
|
524
|
-
*
|
|
525
|
-
* @default Встроенный base64 декодер
|
|
526
|
-
*
|
|
527
|
-
* @example
|
|
528
|
-
* ```typescript
|
|
529
|
-
* // С библиотекой jwt-decode
|
|
530
|
-
* import { jwtDecode } from 'jwt-decode';
|
|
531
|
-
*
|
|
532
|
-
* decodeJwt: (token) => {
|
|
533
|
-
* try {
|
|
534
|
-
* return jwtDecode<{ userId: string }>(token);
|
|
535
|
-
* } catch {
|
|
536
|
-
* return null;
|
|
537
|
-
* }
|
|
538
|
-
* }
|
|
539
|
-
* ```
|
|
540
|
-
*/
|
|
541
53
|
decodeJwt?: (token: string) => {
|
|
542
54
|
userId?: string;
|
|
543
55
|
} | null;
|
|
544
56
|
}
|
|
545
|
-
/**
|
|
546
|
-
* Обёртка над axios для выполнения HTTP-запросов
|
|
547
|
-
*
|
|
548
|
-
* Предоставляет типизированные методы для всех HTTP-операций.
|
|
549
|
-
* Автоматически извлекает `data` из ответа axios.
|
|
550
|
-
*
|
|
551
|
-
* @example
|
|
552
|
-
* ```typescript
|
|
553
|
-
* interface User {
|
|
554
|
-
* id: string;
|
|
555
|
-
* name: string;
|
|
556
|
-
* email: string;
|
|
557
|
-
* }
|
|
558
|
-
*
|
|
559
|
-
* // GET запрос с типизацией
|
|
560
|
-
* const user = await apiService.get<User>('/users/me');
|
|
561
|
-
* console.log(user.name);
|
|
562
|
-
*
|
|
563
|
-
* // POST запрос
|
|
564
|
-
* const newUser = await apiService.post<User>('/users', {
|
|
565
|
-
* name: 'John',
|
|
566
|
-
* email: 'john@example.com',
|
|
567
|
-
* });
|
|
568
|
-
*
|
|
569
|
-
* // С дополнительными параметрами axios
|
|
570
|
-
* const data = await apiService.get<Data>('/endpoint', {
|
|
571
|
-
* params: { page: 1, limit: 10 },
|
|
572
|
-
* headers: { 'X-Custom-Header': 'value' },
|
|
573
|
-
* });
|
|
574
|
-
* ```
|
|
575
|
-
*/
|
|
576
57
|
interface ApiService {
|
|
577
|
-
/**
|
|
578
|
-
* Выполняет GET-запрос
|
|
579
|
-
*
|
|
580
|
-
* Автоматически добавляет cache-buster параметр `_t` для предотвращения кэширования.
|
|
581
|
-
*
|
|
582
|
-
* @typeParam T - Тип данных ответа
|
|
583
|
-
* @param url - URL эндпоинта (относительно baseURL сервиса)
|
|
584
|
-
* @param config - Дополнительные параметры axios (headers, params и т.д.)
|
|
585
|
-
* @returns Промис с данными ответа
|
|
586
|
-
*
|
|
587
|
-
* @example
|
|
588
|
-
* ```typescript
|
|
589
|
-
* // Простой GET
|
|
590
|
-
* const users = await service.get<User[]>('/users');
|
|
591
|
-
*
|
|
592
|
-
* // С query параметрами
|
|
593
|
-
* const filtered = await service.get<User[]>('/users', {
|
|
594
|
-
* params: { role: 'admin', active: true }
|
|
595
|
-
* });
|
|
596
|
-
* ```
|
|
597
|
-
*/
|
|
598
58
|
get: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>;
|
|
599
|
-
/**
|
|
600
|
-
* Выполняет POST-запрос
|
|
601
|
-
*
|
|
602
|
-
* @typeParam T - Тип данных ответа
|
|
603
|
-
* @param url - URL эндпоинта (относительно baseURL сервиса)
|
|
604
|
-
* @param data - Тело запроса (будет сериализовано в JSON)
|
|
605
|
-
* @param config - Дополнительные параметры axios
|
|
606
|
-
* @returns Промис с данными ответа
|
|
607
|
-
*
|
|
608
|
-
* @example
|
|
609
|
-
* ```typescript
|
|
610
|
-
* const newUser = await service.post<User>('/users', {
|
|
611
|
-
* name: 'John Doe',
|
|
612
|
-
* email: 'john@example.com',
|
|
613
|
-
* });
|
|
614
|
-
* ```
|
|
615
|
-
*/
|
|
616
59
|
post: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>;
|
|
617
|
-
/**
|
|
618
|
-
* Выполняет PUT-запрос
|
|
619
|
-
*
|
|
620
|
-
* @typeParam T - Тип данных ответа
|
|
621
|
-
* @param url - URL эндпоинта
|
|
622
|
-
* @param data - Тело запроса для обновления
|
|
623
|
-
* @param config - Дополнительные параметры axios
|
|
624
|
-
* @returns Промис с данными ответа
|
|
625
|
-
*
|
|
626
|
-
* @example
|
|
627
|
-
* ```typescript
|
|
628
|
-
* const updated = await service.put<User>('/users/123', {
|
|
629
|
-
* name: 'Jane Doe',
|
|
630
|
-
* });
|
|
631
|
-
* ```
|
|
632
|
-
*/
|
|
633
60
|
put: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>;
|
|
634
|
-
/**
|
|
635
|
-
* Выполняет PATCH-запрос
|
|
636
|
-
*
|
|
637
|
-
* @typeParam T - Тип данных ответа
|
|
638
|
-
* @param url - URL эндпоинта
|
|
639
|
-
* @param data - Тело запроса для частичного обновления
|
|
640
|
-
* @param config - Дополнительные параметры axios
|
|
641
|
-
* @returns Промис с данными ответа
|
|
642
|
-
*
|
|
643
|
-
* @example
|
|
644
|
-
* ```typescript
|
|
645
|
-
* const updated = await service.patch<User>('/users/123', {
|
|
646
|
-
* name: 'Jane Doe',
|
|
647
|
-
* });
|
|
648
|
-
* ```
|
|
649
|
-
*/
|
|
650
61
|
patch: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>;
|
|
651
|
-
/**
|
|
652
|
-
* Выполняет DELETE-запрос
|
|
653
|
-
*
|
|
654
|
-
* @typeParam T - Тип данных ответа
|
|
655
|
-
* @param url - URL эндпоинта
|
|
656
|
-
* @param config - Дополнительные параметры axios
|
|
657
|
-
* @returns Промис с данными ответа
|
|
658
|
-
*
|
|
659
|
-
* @example
|
|
660
|
-
* ```typescript
|
|
661
|
-
* await service.delete<void>('/users/123');
|
|
662
|
-
* ```
|
|
663
|
-
*/
|
|
664
62
|
delete: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>;
|
|
665
|
-
/**
|
|
666
|
-
* Доступ к нативному экземпляру axios
|
|
667
|
-
*
|
|
668
|
-
* Используйте для добавления кастомных interceptors или
|
|
669
|
-
* выполнения нестандартных запросов.
|
|
670
|
-
*
|
|
671
|
-
* @example
|
|
672
|
-
* ```typescript
|
|
673
|
-
* // Добавление кастомного interceptor
|
|
674
|
-
* service.axiosInstance.interceptors.request.use(config => {
|
|
675
|
-
* config.headers['X-Request-Id'] = generateId();
|
|
676
|
-
* return config;
|
|
677
|
-
* });
|
|
678
|
-
* ```
|
|
679
|
-
*/
|
|
680
63
|
axiosInstance: AxiosInstance;
|
|
681
64
|
}
|
|
682
|
-
/**
|
|
683
|
-
* Главный объект API клиента
|
|
684
|
-
*
|
|
685
|
-
* Содержит настроенные сервисы для каждого микросервиса
|
|
686
|
-
* и менеджер обновления токенов.
|
|
687
|
-
*
|
|
688
|
-
* @example
|
|
689
|
-
* ```typescript
|
|
690
|
-
* const apiClient = createApiClient({ urls: {...} });
|
|
691
|
-
*
|
|
692
|
-
* // Использование сервисов
|
|
693
|
-
* const user = await apiClient.userProgressService.get('/me');
|
|
694
|
-
* const puzzles = await apiClient.puzzleController.get('/puzzles');
|
|
695
|
-
*
|
|
696
|
-
* // Работа с токенами
|
|
697
|
-
* const currentToken = apiClient.tokenRefreshManager.getAccessToken();
|
|
698
|
-
* ```
|
|
699
|
-
*/
|
|
700
65
|
interface ApiClient {
|
|
701
|
-
/**
|
|
702
|
-
* Сервис авторизации
|
|
703
|
-
*
|
|
704
|
-
* Используется для логина, регистрации, обновления токенов.
|
|
705
|
-
*/
|
|
706
66
|
authService: ApiService;
|
|
707
|
-
/**
|
|
708
|
-
* Сервис прогресса пользователя
|
|
709
|
-
*
|
|
710
|
-
* Используется для получения и обновления прогресса обучения.
|
|
711
|
-
*/
|
|
712
67
|
userProgressService: ApiService;
|
|
713
|
-
/**
|
|
714
|
-
* Сервис рекомендаций
|
|
715
|
-
*
|
|
716
|
-
* Используется для получения персонализированных рекомендаций.
|
|
717
|
-
*/
|
|
718
68
|
recommendationService: ApiService;
|
|
719
|
-
/**
|
|
720
|
-
* Сервис управления партнёрами
|
|
721
|
-
*/
|
|
722
69
|
partnerManager: ApiService;
|
|
723
|
-
/**
|
|
724
|
-
* Сервис управления приключениями
|
|
725
|
-
*/
|
|
726
70
|
adventureManager: ApiService;
|
|
727
|
-
/**
|
|
728
|
-
* Сервис агрегации данных для клиента
|
|
729
|
-
*/
|
|
730
71
|
bff: ApiService;
|
|
731
|
-
/**
|
|
732
|
-
* Контроллер пазлов/задач
|
|
733
|
-
*
|
|
734
|
-
* Используется для работы с учебными задачами и пазлами.
|
|
735
|
-
*/
|
|
736
72
|
puzzleController: ApiService;
|
|
737
|
-
/**
|
|
738
|
-
* Контроллер микромодов
|
|
739
|
-
*/
|
|
740
73
|
micromodeController: ApiService;
|
|
741
|
-
/**
|
|
742
|
-
* Сервис управления пользователями
|
|
743
|
-
*/
|
|
744
74
|
userManager: ApiService;
|
|
745
|
-
/**
|
|
746
|
-
* Основной монолитный сервис
|
|
747
|
-
*
|
|
748
|
-
* Используется для запросов, не выделенных в отдельные микросервисы.
|
|
749
|
-
*/
|
|
750
75
|
monolith: ApiService;
|
|
751
|
-
/**
|
|
752
|
-
* Менеджер обновления токенов
|
|
753
|
-
*
|
|
754
|
-
* Предоставляет доступ к функциям работы с токенами.
|
|
755
|
-
* Можно использовать с RTK Query или другими HTTP-клиентами.
|
|
756
|
-
*
|
|
757
|
-
* @example
|
|
758
|
-
* ```typescript
|
|
759
|
-
* // Получение текущего токена
|
|
760
|
-
* const token = apiClient.tokenRefreshManager.getAccessToken();
|
|
761
|
-
*
|
|
762
|
-
* // Обработка 401 в кастомном клиенте
|
|
763
|
-
* if (response.status === 401) {
|
|
764
|
-
* const newToken = await apiClient.tokenRefreshManager.handle401();
|
|
765
|
-
* }
|
|
766
|
-
* ```
|
|
767
|
-
*/
|
|
768
76
|
tokenRefreshManager: TokenRefreshManager;
|
|
769
77
|
}
|
|
770
|
-
/**
|
|
771
|
-
* Создаёт настроенный API клиент
|
|
772
|
-
*
|
|
773
|
-
* Фабричная функция, которая создаёт полностью настроенный API клиент
|
|
774
|
-
* с поддержкой автоматического обновления токенов, обработкой ошибок
|
|
775
|
-
* авторизации и типизированными методами запросов.
|
|
776
|
-
*
|
|
777
|
-
* **Особенности:**
|
|
778
|
-
* - Автоматическое добавление Authorization заголовка
|
|
779
|
-
* - Автоматическое обновление токена при 401 ошибке
|
|
780
|
-
* - Очередь запросов при одновременном обновлении токена
|
|
781
|
-
* - Добавление X-User-Id заголовка из JWT
|
|
782
|
-
* - Cache-busting для GET запросов
|
|
783
|
-
*
|
|
784
|
-
* @param config - Конфигурация клиента
|
|
785
|
-
* @returns Настроенный API клиент
|
|
786
|
-
*
|
|
787
|
-
* @example
|
|
788
|
-
* ```typescript
|
|
789
|
-
* import { createApiClient } from 'mbt-api-client';
|
|
790
|
-
*
|
|
791
|
-
* const apiClient = createApiClient({
|
|
792
|
-
* urls: {
|
|
793
|
-
* authService: 'https://auth.example.com',
|
|
794
|
-
* userProgressService: 'https://progress.example.com',
|
|
795
|
-
* recommendationService: 'https://rec.example.com',
|
|
796
|
-
* partnerManager: 'https://partners.example.com',
|
|
797
|
-
* puzzleController: 'https://puzzles.example.com',
|
|
798
|
-
* monolith: 'https://api.example.com',
|
|
799
|
-
* },
|
|
800
|
-
* onNavigateToLogin: () => {
|
|
801
|
-
* // Очистка состояния и редирект
|
|
802
|
-
* store.dispatch(logout());
|
|
803
|
-
* navigate('/login');
|
|
804
|
-
* },
|
|
805
|
-
* timeout: 15000, // 15 секунд
|
|
806
|
-
* });
|
|
807
|
-
*
|
|
808
|
-
* // Использование
|
|
809
|
-
* try {
|
|
810
|
-
* const user = await apiClient.userProgressService.get<User>('/me');
|
|
811
|
-
* console.log('Пользователь:', user);
|
|
812
|
-
* } catch (error) {
|
|
813
|
-
* console.error('Ошибка:', error);
|
|
814
|
-
* }
|
|
815
|
-
* ```
|
|
816
|
-
*/
|
|
817
78
|
declare const createApiClient: (config: ApiClientConfig) => ApiClient;
|
|
818
|
-
/**
|
|
819
|
-
* React Context для API клиента
|
|
820
|
-
*
|
|
821
|
-
* Используется внутри ApiClientProvider для передачи клиента
|
|
822
|
-
* через дерево компонентов.
|
|
823
|
-
*
|
|
824
|
-
* @example
|
|
825
|
-
* ```typescript
|
|
826
|
-
* // Для кастомных случаев (обычно используйте ApiClientProvider)
|
|
827
|
-
* <ApiClientContext.Provider value={apiClient}>
|
|
828
|
-
* <App />
|
|
829
|
-
* </ApiClientContext.Provider>
|
|
830
|
-
* ```
|
|
831
|
-
*/
|
|
832
79
|
declare const ApiClientContext: react.Context<ApiClient | null>;
|
|
833
|
-
/**
|
|
834
|
-
* React хук для доступа к API клиенту
|
|
835
|
-
*
|
|
836
|
-
* Получает API клиент из контекста. Должен использоваться внутри
|
|
837
|
-
* компонента, обёрнутого в ApiClientProvider.
|
|
838
|
-
*
|
|
839
|
-
* @returns API клиент с типизированными сервисами
|
|
840
|
-
* @throws Error если используется вне ApiClientProvider
|
|
841
|
-
*
|
|
842
|
-
* @example
|
|
843
|
-
* ```typescript
|
|
844
|
-
* import { useApiClient } from 'mbt-api-client';
|
|
845
|
-
*
|
|
846
|
-
* function UserProfile() {
|
|
847
|
-
* const { userProgressService } = useApiClient();
|
|
848
|
-
* const [user, setUser] = useState<User | null>(null);
|
|
849
|
-
*
|
|
850
|
-
* useEffect(() => {
|
|
851
|
-
* userProgressService.get<User>('/me')
|
|
852
|
-
* .then(setUser)
|
|
853
|
-
* .catch(console.error);
|
|
854
|
-
* }, []);
|
|
855
|
-
*
|
|
856
|
-
* return <div>{user?.name}</div>;
|
|
857
|
-
* }
|
|
858
|
-
*
|
|
859
|
-
* // С деструктуризацией нескольких сервисов
|
|
860
|
-
* function Dashboard() {
|
|
861
|
-
* const {
|
|
862
|
-
* userProgressService,
|
|
863
|
-
* recommendationService,
|
|
864
|
-
* puzzleController,
|
|
865
|
-
* } = useApiClient();
|
|
866
|
-
*
|
|
867
|
-
* // Параллельные запросы
|
|
868
|
-
* const [progress, recommendations, puzzles] = await Promise.all([
|
|
869
|
-
* userProgressService.get('/progress'),
|
|
870
|
-
* recommendationService.get('/for-me'),
|
|
871
|
-
* puzzleController.get('/puzzles'),
|
|
872
|
-
* ]);
|
|
873
|
-
* }
|
|
874
|
-
* ```
|
|
875
|
-
*/
|
|
876
80
|
declare const useApiClient: () => ApiClient;
|
|
877
|
-
/**
|
|
878
|
-
* React Provider для API клиента
|
|
879
|
-
*
|
|
880
|
-
* Оборачивает приложение для предоставления доступа к API клиенту
|
|
881
|
-
* через хук useApiClient.
|
|
882
|
-
*
|
|
883
|
-
* @example
|
|
884
|
-
* ```typescript
|
|
885
|
-
* import { createApiClient, ApiClientProvider } from 'mbt-api-client';
|
|
886
|
-
*
|
|
887
|
-
* // Создание клиента (обычно в отдельном файле)
|
|
888
|
-
* const apiClient = createApiClient({
|
|
889
|
-
* urls: { ... },
|
|
890
|
-
* });
|
|
891
|
-
*
|
|
892
|
-
* // В корне приложения
|
|
893
|
-
* function App() {
|
|
894
|
-
* return (
|
|
895
|
-
* <ApiClientProvider value={apiClient}>
|
|
896
|
-
* <Router>
|
|
897
|
-
* <Routes />
|
|
898
|
-
* </Router>
|
|
899
|
-
* </ApiClientProvider>
|
|
900
|
-
* );
|
|
901
|
-
* }
|
|
902
|
-
*
|
|
903
|
-
* // С React Query
|
|
904
|
-
* function App() {
|
|
905
|
-
* return (
|
|
906
|
-
* <QueryClientProvider client={queryClient}>
|
|
907
|
-
* <ApiClientProvider value={apiClient}>
|
|
908
|
-
* <Routes />
|
|
909
|
-
* </ApiClientProvider>
|
|
910
|
-
* </QueryClientProvider>
|
|
911
|
-
* );
|
|
912
|
-
* }
|
|
913
|
-
* ```
|
|
914
|
-
*/
|
|
915
81
|
declare const ApiClientProvider: react.Provider<ApiClient | null>;
|
|
916
82
|
|
|
917
|
-
|
|
918
|
-
type SentryContext = Record<string, string | number | boolean | null | undefined>;
|
|
919
|
-
declare const captureError: (error: unknown, context?: SentryContext) => void;
|
|
920
|
-
declare const captureMessage: (message: string, level?: "info" | "warning" | "error", context?: SentryContext) => void;
|
|
921
|
-
declare const captureBreadcrumb: (message: string, data?: SentryContext) => void;
|
|
922
|
-
|
|
923
|
-
export { type ApiClient, type ApiClientConfig, ApiClientContext, ApiClientProvider, type ApiClientUrls, type ApiService, type SentryContext, type TokenRefreshConfig, TokenRefreshManager, type TokenRefreshResult, captureBreadcrumb, captureError, captureMessage, createApiClient, createTokenRefreshManager, initSentry, useApiClient };
|
|
83
|
+
export { type ApiClient, type ApiClientConfig, ApiClientContext, ApiClientProvider, type ApiClientUrls, type ApiService, type TokenRefreshConfig, TokenRefreshManager, type TokenRefreshResult, createApiClient, createTokenRefreshManager, useApiClient };
|