mbt-api-client 1.0.0

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.
@@ -0,0 +1,879 @@
1
+ import * as react from 'react';
2
+ import { AxiosRequestConfig, AxiosInstance } from 'axios';
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
+ 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
+ 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
+ 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
+ getAccessToken?: () => string | null;
117
+ /**
118
+ * Функция получения текущего refresh токена
119
+ *
120
+ * @default () => localStorage.getItem('refreshToken')
121
+ */
122
+ 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
+ setTokens?: (accessToken: string, refreshToken?: string) => void;
140
+ /**
141
+ * Функция очистки токенов (при выходе или ошибке авторизации)
142
+ *
143
+ * @default Удаляет 'accessToken' и 'refreshToken' из localStorage
144
+ */
145
+ clearTokens?: () => void;
146
+ }
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
+ interface TokenRefreshResult {
164
+ /**
165
+ * Успешность операции обновления
166
+ *
167
+ * `true` - токен успешно обновлён
168
+ * `false` - ошибка обновления (пользователь будет перенаправлен на логин)
169
+ */
170
+ ok: boolean;
171
+ /**
172
+ * Новый access токен (только при `ok: true`)
173
+ */
174
+ accessToken?: string;
175
+ /**
176
+ * Новый refresh токен (опционально, только при `ok: true`)
177
+ */
178
+ refreshToken?: string;
179
+ }
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
+ declare class TokenRefreshManager {
220
+ private isRefreshing;
221
+ private refreshPromise;
222
+ private queuedRequests;
223
+ 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
+ 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
+ getAccessToken(): string | null;
252
+ /**
253
+ * Получает текущий refresh токен
254
+ *
255
+ * @returns Refresh токен или null, если токен отсутствует
256
+ */
257
+ 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
+ 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
+ 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
+ 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
+ handle401(isInvalidToken?: boolean): Promise<string | null>;
347
+ /**
348
+ * Принудительно перенаправляет на страницу логина
349
+ *
350
+ * Вызывает колбэк onNavigateToLogin из конфигурации.
351
+ *
352
+ * @example
353
+ * ```typescript
354
+ * // При необходимости принудительного выхода
355
+ * manager.navigateToLogin();
356
+ * ```
357
+ */
358
+ navigateToLogin(): void;
359
+ /**
360
+ * Сбрасывает внутреннее состояние менеджера
361
+ *
362
+ * Полезно для тестирования или при необходимости
363
+ * принудительно сбросить очередь запросов.
364
+ *
365
+ * @example
366
+ * ```typescript
367
+ * // В тестах
368
+ * beforeEach(() => {
369
+ * manager.reset();
370
+ * });
371
+ * ```
372
+ */
373
+ reset(): void;
374
+ private performRefresh;
375
+ private notifyQueueSuccess;
376
+ private notifyQueueFailure;
377
+ }
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
+ declare const createTokenRefreshManager: (config: TokenRefreshConfig) => TokenRefreshManager;
421
+
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
+ interface ApiClientUrls {
438
+ /** URL сервиса авторизации (логин, регистрация, refresh токенов) */
439
+ authService: string;
440
+ /** URL сервиса прогресса пользователя */
441
+ userProgressService: string;
442
+ /** URL сервиса рекомендаций */
443
+ recommendationService: string;
444
+ /** URL сервиса управления партнёрами */
445
+ partnerManager: string;
446
+ /** URL контроллера пазлов/задач */
447
+ puzzleController: string;
448
+ /** URL основного монолитного сервиса */
449
+ monolith: string;
450
+ }
451
+ /**
452
+ * Конфигурация API клиента
453
+ *
454
+ * @example
455
+ * ```typescript
456
+ * const config: ApiClientConfig = {
457
+ * urls: {
458
+ * authService: 'https://auth.example.com',
459
+ * userProgressService: 'https://progress.example.com',
460
+ * // ... остальные URL
461
+ * },
462
+ * onNavigateToLogin: () => {
463
+ * // Для React Router
464
+ * navigate('/login');
465
+ * },
466
+ * timeout: 15000,
467
+ * decodeJwt: (token) => {
468
+ * // Кастомный декодер JWT
469
+ * return jwtDecode(token);
470
+ * },
471
+ * };
472
+ * ```
473
+ */
474
+ interface ApiClientConfig {
475
+ /**
476
+ * URL-адреса микросервисов
477
+ */
478
+ urls: ApiClientUrls;
479
+ /**
480
+ * Колбэк для перенаправления на страницу логина
481
+ *
482
+ * Вызывается при:
483
+ * - Ошибке 401 с невалидным токеном
484
+ * - Ошибке 403 (доступ запрещён)
485
+ * - Неудачном обновлении токена
486
+ *
487
+ * @default Очищает localStorage и редиректит на '#/login'
488
+ *
489
+ * @example
490
+ * ```typescript
491
+ * // React Router
492
+ * onNavigateToLogin: () => navigate('/login')
493
+ *
494
+ * // Next.js
495
+ * onNavigateToLogin: () => router.push('/login')
496
+ *
497
+ * // Vanilla JS
498
+ * onNavigateToLogin: () => { window.location.href = '/login' }
499
+ * ```
500
+ */
501
+ onNavigateToLogin?: () => void;
502
+ /**
503
+ * Таймаут запросов в миллисекундах
504
+ *
505
+ * @default 10000 (10 секунд)
506
+ */
507
+ timeout?: number;
508
+ /**
509
+ * Функция декодирования JWT токена для извлечения userId
510
+ *
511
+ * Используется для добавления заголовка X-User-Id к запросам.
512
+ * Если не указана, используется встроенный base64 декодер.
513
+ *
514
+ * @param token - JWT токен для декодирования
515
+ * @returns Объект с userId или null при ошибке декодирования
516
+ *
517
+ * @default Встроенный base64 декодер
518
+ *
519
+ * @example
520
+ * ```typescript
521
+ * // С библиотекой jwt-decode
522
+ * import { jwtDecode } from 'jwt-decode';
523
+ *
524
+ * decodeJwt: (token) => {
525
+ * try {
526
+ * return jwtDecode<{ userId: string }>(token);
527
+ * } catch {
528
+ * return null;
529
+ * }
530
+ * }
531
+ * ```
532
+ */
533
+ decodeJwt?: (token: string) => {
534
+ userId?: string;
535
+ } | null;
536
+ }
537
+ /**
538
+ * Обёртка над axios для выполнения HTTP-запросов
539
+ *
540
+ * Предоставляет типизированные методы для всех HTTP-операций.
541
+ * Автоматически извлекает `data` из ответа axios.
542
+ *
543
+ * @example
544
+ * ```typescript
545
+ * interface User {
546
+ * id: string;
547
+ * name: string;
548
+ * email: string;
549
+ * }
550
+ *
551
+ * // GET запрос с типизацией
552
+ * const user = await apiService.get<User>('/users/me');
553
+ * console.log(user.name);
554
+ *
555
+ * // POST запрос
556
+ * const newUser = await apiService.post<User>('/users', {
557
+ * name: 'John',
558
+ * email: 'john@example.com',
559
+ * });
560
+ *
561
+ * // С дополнительными параметрами axios
562
+ * const data = await apiService.get<Data>('/endpoint', {
563
+ * params: { page: 1, limit: 10 },
564
+ * headers: { 'X-Custom-Header': 'value' },
565
+ * });
566
+ * ```
567
+ */
568
+ interface ApiService {
569
+ /**
570
+ * Выполняет GET-запрос
571
+ *
572
+ * Автоматически добавляет cache-buster параметр `_t` для предотвращения кэширования.
573
+ *
574
+ * @typeParam T - Тип данных ответа
575
+ * @param url - URL эндпоинта (относительно baseURL сервиса)
576
+ * @param config - Дополнительные параметры axios (headers, params и т.д.)
577
+ * @returns Промис с данными ответа
578
+ *
579
+ * @example
580
+ * ```typescript
581
+ * // Простой GET
582
+ * const users = await service.get<User[]>('/users');
583
+ *
584
+ * // С query параметрами
585
+ * const filtered = await service.get<User[]>('/users', {
586
+ * params: { role: 'admin', active: true }
587
+ * });
588
+ * ```
589
+ */
590
+ get: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>;
591
+ /**
592
+ * Выполняет POST-запрос
593
+ *
594
+ * @typeParam T - Тип данных ответа
595
+ * @param url - URL эндпоинта (относительно baseURL сервиса)
596
+ * @param data - Тело запроса (будет сериализовано в JSON)
597
+ * @param config - Дополнительные параметры axios
598
+ * @returns Промис с данными ответа
599
+ *
600
+ * @example
601
+ * ```typescript
602
+ * const newUser = await service.post<User>('/users', {
603
+ * name: 'John Doe',
604
+ * email: 'john@example.com',
605
+ * });
606
+ * ```
607
+ */
608
+ post: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>;
609
+ /**
610
+ * Выполняет PUT-запрос
611
+ *
612
+ * @typeParam T - Тип данных ответа
613
+ * @param url - URL эндпоинта
614
+ * @param data - Тело запроса для обновления
615
+ * @param config - Дополнительные параметры axios
616
+ * @returns Промис с данными ответа
617
+ *
618
+ * @example
619
+ * ```typescript
620
+ * const updated = await service.put<User>('/users/123', {
621
+ * name: 'Jane Doe',
622
+ * });
623
+ * ```
624
+ */
625
+ put: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>;
626
+ /**
627
+ * Выполняет DELETE-запрос
628
+ *
629
+ * @typeParam T - Тип данных ответа
630
+ * @param url - URL эндпоинта
631
+ * @param config - Дополнительные параметры axios
632
+ * @returns Промис с данными ответа
633
+ *
634
+ * @example
635
+ * ```typescript
636
+ * await service.delete<void>('/users/123');
637
+ * ```
638
+ */
639
+ delete: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>;
640
+ /**
641
+ * Доступ к нативному экземпляру axios
642
+ *
643
+ * Используйте для добавления кастомных interceptors или
644
+ * выполнения нестандартных запросов.
645
+ *
646
+ * @example
647
+ * ```typescript
648
+ * // Добавление кастомного interceptor
649
+ * service.axiosInstance.interceptors.request.use(config => {
650
+ * config.headers['X-Request-Id'] = generateId();
651
+ * return config;
652
+ * });
653
+ *
654
+ * // Выполнение PATCH запроса
655
+ * await service.axiosInstance.patch('/users/123', { name: 'New Name' });
656
+ * ```
657
+ */
658
+ axiosInstance: AxiosInstance;
659
+ }
660
+ /**
661
+ * Главный объект API клиента
662
+ *
663
+ * Содержит настроенные сервисы для каждого микросервиса
664
+ * и менеджер обновления токенов.
665
+ *
666
+ * @example
667
+ * ```typescript
668
+ * const apiClient = createApiClient({ urls: {...} });
669
+ *
670
+ * // Использование сервисов
671
+ * const user = await apiClient.userProgressService.get('/me');
672
+ * const puzzles = await apiClient.puzzleController.get('/puzzles');
673
+ *
674
+ * // Работа с токенами
675
+ * const currentToken = apiClient.tokenRefreshManager.getAccessToken();
676
+ * ```
677
+ */
678
+ interface ApiClient {
679
+ /**
680
+ * Сервис авторизации
681
+ *
682
+ * Используется для логина, регистрации, обновления токенов.
683
+ */
684
+ authService: ApiService;
685
+ /**
686
+ * Сервис прогресса пользователя
687
+ *
688
+ * Используется для получения и обновления прогресса обучения.
689
+ */
690
+ userProgressService: ApiService;
691
+ /**
692
+ * Сервис рекомендаций
693
+ *
694
+ * Используется для получения персонализированных рекомендаций.
695
+ */
696
+ recommendationService: ApiService;
697
+ /**
698
+ * Сервис управления партнёрами
699
+ */
700
+ partnerManager: ApiService;
701
+ /**
702
+ * Контроллер пазлов/задач
703
+ *
704
+ * Используется для работы с учебными задачами и пазлами.
705
+ */
706
+ puzzleController: ApiService;
707
+ /**
708
+ * Основной монолитный сервис
709
+ *
710
+ * Используется для запросов, не выделенных в отдельные микросервисы.
711
+ */
712
+ monolith: ApiService;
713
+ /**
714
+ * Менеджер обновления токенов
715
+ *
716
+ * Предоставляет доступ к функциям работы с токенами.
717
+ * Можно использовать с RTK Query или другими HTTP-клиентами.
718
+ *
719
+ * @example
720
+ * ```typescript
721
+ * // Получение текущего токена
722
+ * const token = apiClient.tokenRefreshManager.getAccessToken();
723
+ *
724
+ * // Обработка 401 в кастомном клиенте
725
+ * if (response.status === 401) {
726
+ * const newToken = await apiClient.tokenRefreshManager.handle401();
727
+ * }
728
+ * ```
729
+ */
730
+ tokenRefreshManager: TokenRefreshManager;
731
+ }
732
+ /**
733
+ * Создаёт настроенный API клиент
734
+ *
735
+ * Фабричная функция, которая создаёт полностью настроенный API клиент
736
+ * с поддержкой автоматического обновления токенов, обработкой ошибок
737
+ * авторизации и типизированными методами запросов.
738
+ *
739
+ * **Особенности:**
740
+ * - Автоматическое добавление Authorization заголовка
741
+ * - Автоматическое обновление токена при 401 ошибке
742
+ * - Очередь запросов при одновременном обновлении токена
743
+ * - Добавление X-User-Id заголовка из JWT
744
+ * - Cache-busting для GET запросов
745
+ *
746
+ * @param config - Конфигурация клиента
747
+ * @returns Настроенный API клиент
748
+ *
749
+ * @example
750
+ * ```typescript
751
+ * import { createApiClient } from 'mbt-api-client';
752
+ *
753
+ * const apiClient = createApiClient({
754
+ * urls: {
755
+ * authService: 'https://auth.example.com',
756
+ * userProgressService: 'https://progress.example.com',
757
+ * recommendationService: 'https://rec.example.com',
758
+ * partnerManager: 'https://partners.example.com',
759
+ * puzzleController: 'https://puzzles.example.com',
760
+ * monolith: 'https://api.example.com',
761
+ * },
762
+ * onNavigateToLogin: () => {
763
+ * // Очистка состояния и редирект
764
+ * store.dispatch(logout());
765
+ * navigate('/login');
766
+ * },
767
+ * timeout: 15000, // 15 секунд
768
+ * });
769
+ *
770
+ * // Использование
771
+ * try {
772
+ * const user = await apiClient.userProgressService.get<User>('/me');
773
+ * console.log('Пользователь:', user);
774
+ * } catch (error) {
775
+ * console.error('Ошибка:', error);
776
+ * }
777
+ * ```
778
+ */
779
+ declare const createApiClient: (config: ApiClientConfig) => ApiClient;
780
+ /**
781
+ * React Context для API клиента
782
+ *
783
+ * Используется внутри ApiClientProvider для передачи клиента
784
+ * через дерево компонентов.
785
+ *
786
+ * @example
787
+ * ```typescript
788
+ * // Для кастомных случаев (обычно используйте ApiClientProvider)
789
+ * <ApiClientContext.Provider value={apiClient}>
790
+ * <App />
791
+ * </ApiClientContext.Provider>
792
+ * ```
793
+ */
794
+ declare const ApiClientContext: react.Context<ApiClient | null>;
795
+ /**
796
+ * React хук для доступа к API клиенту
797
+ *
798
+ * Получает API клиент из контекста. Должен использоваться внутри
799
+ * компонента, обёрнутого в ApiClientProvider.
800
+ *
801
+ * @returns API клиент с типизированными сервисами
802
+ * @throws Error если используется вне ApiClientProvider
803
+ *
804
+ * @example
805
+ * ```typescript
806
+ * import { useApiClient } from 'mbt-api-client';
807
+ *
808
+ * function UserProfile() {
809
+ * const { userProgressService } = useApiClient();
810
+ * const [user, setUser] = useState<User | null>(null);
811
+ *
812
+ * useEffect(() => {
813
+ * userProgressService.get<User>('/me')
814
+ * .then(setUser)
815
+ * .catch(console.error);
816
+ * }, []);
817
+ *
818
+ * return <div>{user?.name}</div>;
819
+ * }
820
+ *
821
+ * // С деструктуризацией нескольких сервисов
822
+ * function Dashboard() {
823
+ * const {
824
+ * userProgressService,
825
+ * recommendationService,
826
+ * puzzleController,
827
+ * } = useApiClient();
828
+ *
829
+ * // Параллельные запросы
830
+ * const [progress, recommendations, puzzles] = await Promise.all([
831
+ * userProgressService.get('/progress'),
832
+ * recommendationService.get('/for-me'),
833
+ * puzzleController.get('/puzzles'),
834
+ * ]);
835
+ * }
836
+ * ```
837
+ */
838
+ declare const useApiClient: () => ApiClient;
839
+ /**
840
+ * React Provider для API клиента
841
+ *
842
+ * Оборачивает приложение для предоставления доступа к API клиенту
843
+ * через хук useApiClient.
844
+ *
845
+ * @example
846
+ * ```typescript
847
+ * import { createApiClient, ApiClientProvider } from 'mbt-api-client';
848
+ *
849
+ * // Создание клиента (обычно в отдельном файле)
850
+ * const apiClient = createApiClient({
851
+ * urls: { ... },
852
+ * });
853
+ *
854
+ * // В корне приложения
855
+ * function App() {
856
+ * return (
857
+ * <ApiClientProvider value={apiClient}>
858
+ * <Router>
859
+ * <Routes />
860
+ * </Router>
861
+ * </ApiClientProvider>
862
+ * );
863
+ * }
864
+ *
865
+ * // С React Query
866
+ * function App() {
867
+ * return (
868
+ * <QueryClientProvider client={queryClient}>
869
+ * <ApiClientProvider value={apiClient}>
870
+ * <Routes />
871
+ * </ApiClientProvider>
872
+ * </QueryClientProvider>
873
+ * );
874
+ * }
875
+ * ```
876
+ */
877
+ declare const ApiClientProvider: react.Provider<ApiClient | null>;
878
+
879
+ export { type ApiClient, type ApiClientConfig, ApiClientContext, ApiClientProvider, type ApiClientUrls, type ApiService, type TokenRefreshConfig, TokenRefreshManager, type TokenRefreshResult, createApiClient, createTokenRefreshManager, useApiClient };