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.
@@ -0,0 +1,77 @@
1
+ # Agent Examples
2
+
3
+ Use these examples when a consumer app needs package-level usage patterns for `mbt-api-client`.
4
+
5
+ ## App Client Setup
6
+
7
+ ```ts
8
+ import { createApiClient } from 'mbt-api-client';
9
+
10
+ export const apiClient = createApiClient({
11
+ urls: {
12
+ authService: 'https://api.example.com/auth',
13
+ userProgressService: 'https://api.example.com/progress',
14
+ recommendationService: 'https://api.example.com/recommendations',
15
+ partnerManager: 'https://api.example.com/partners',
16
+ adventureManager: 'https://api.example.com/adventures',
17
+ bff: 'https://api.example.com/bff',
18
+ puzzleController: 'https://api.example.com/puzzles',
19
+ micromodeController: 'https://api.example.com/micromodes',
20
+ userManager: 'https://api.example.com/users',
21
+ monolith: 'https://api.example.com',
22
+ },
23
+ onNavigateToLogin: () => {
24
+ window.location.href = '#/login';
25
+ },
26
+ });
27
+ ```
28
+
29
+ ## React Provider
30
+
31
+ ```tsx
32
+ import { ApiClientProvider } from 'mbt-api-client';
33
+
34
+ <ApiClientProvider value={apiClient}>
35
+ <App />
36
+ </ApiClientProvider>;
37
+ ```
38
+
39
+ ## Hook Usage
40
+
41
+ ```tsx
42
+ import { useApiClient } from 'mbt-api-client';
43
+
44
+ function ProfileScreen() {
45
+ const { userManager } = useApiClient();
46
+
47
+ const loadProfile = async () => {
48
+ return userManager.get('/me');
49
+ };
50
+
51
+ return <button onClick={() => void loadProfile()}>Load profile</button>;
52
+ }
53
+ ```
54
+
55
+ ## Standalone Token Refresh Manager
56
+
57
+ ```ts
58
+ import { createTokenRefreshManager } from 'mbt-api-client';
59
+
60
+ const tokenManager = createTokenRefreshManager({
61
+ refreshTokenFn: async () => {
62
+ const refreshToken = localStorage.getItem('refreshToken');
63
+
64
+ if (!refreshToken) {
65
+ return { ok: false };
66
+ }
67
+
68
+ return {
69
+ ok: true,
70
+ accessToken: 'new-access-token',
71
+ refreshToken,
72
+ };
73
+ },
74
+ });
75
+
76
+ const nextToken = await tokenManager.handle401();
77
+ ```
@@ -0,0 +1,43 @@
1
+ # Agent Reference
2
+
3
+ This folder holds factual package reference for `mbt-api-client`.
4
+
5
+ ## Public Exports
6
+
7
+ - `createApiClient`
8
+ - `ApiClientContext`
9
+ - `ApiClientProvider`
10
+ - `useApiClient`
11
+ - `TokenRefreshManager`
12
+ - `createTokenRefreshManager`
13
+
14
+ ## Public Types
15
+
16
+ - `ApiClient`
17
+ - `ApiClientConfig`
18
+ - `ApiClientUrls`
19
+ - `ApiService`
20
+ - `TokenRefreshConfig`
21
+ - `TokenRefreshResult`
22
+
23
+ ## Required `ApiClientUrls` Keys
24
+
25
+ - `authService`
26
+ - `userProgressService`
27
+ - `recommendationService`
28
+ - `partnerManager`
29
+ - `adventureManager`
30
+ - `bff`
31
+ - `puzzleController`
32
+ - `micromodeController`
33
+ - `userManager`
34
+ - `monolith`
35
+
36
+ ## Maintainer Sources
37
+
38
+ - `README.md` - human quick-start
39
+ - `src/api-client.ts` - client, provider, and config truth
40
+ - `src/token-refresh.ts` - token refresh truth
41
+ - `src/index.ts` - public export surface
42
+
43
+ Reference docs should stay factual. Policy and maintenance rules belong in `.ai/agent-rules/`.
@@ -0,0 +1,32 @@
1
+ # Agent Rules
2
+
3
+ These files are the canonical package-owned rules for `mbt-api-client`.
4
+
5
+ ## Rule Map
6
+
7
+ - `.ai/agent-rules/README.md` - normative setup and usage rules.
8
+ - `.ai/agent-rules/maintenance.md` - required rule for keeping docs in sync with public changes.
9
+
10
+ ## Usage Rules
11
+
12
+ - Create one configured API client per app shell unless the consumer has a deliberate multi-client design.
13
+ - Wrap React apps with `ApiClientProvider` when component-level access through `useApiClient` is needed.
14
+ - Supply every required `ApiClientUrls` key when creating the client.
15
+ - Keep login redirect behavior explicit through `onNavigateToLogin` when the consumer app has a custom auth flow.
16
+ - Use `createTokenRefreshManager` when a consumer needs standalone 401 retry orchestration outside the built-in client flow.
17
+ - Keep package examples focused on public setup and service usage, not app-specific Redux or router structure.
18
+ - Use kebab-case for file names and folder names across this repo.
19
+ - Use `pnpm` as the package manager for this repo and keep a single lockfile.
20
+
21
+ ## Required URL Keys
22
+
23
+ - `authService`
24
+ - `userProgressService`
25
+ - `recommendationService`
26
+ - `partnerManager`
27
+ - `adventureManager`
28
+ - `bff`
29
+ - `puzzleController`
30
+ - `micromodeController`
31
+ - `userManager`
32
+ - `monolith`
@@ -0,0 +1,20 @@
1
+ # Maintenance Rule
2
+
3
+ Agents working in `mbt-api` must review and update package guidance whenever a change affects public behavior.
4
+
5
+ ## Update The Docs When
6
+
7
+ - exported functions, types, or providers change
8
+ - required URL keys change
9
+ - token refresh behavior or config changes
10
+ - setup expectations change
11
+ - canonical examples need to change to stay correct
12
+
13
+ ## Required Files To Review
14
+
15
+ - `AGENTS.md`
16
+ - `.ai/agent-rules/README.md`
17
+ - `.ai/agent-reference/README.md`
18
+ - `.ai/agent-examples/README.md`
19
+
20
+ Do not finish public package work without reviewing these files.
package/AGENTS.md ADDED
@@ -0,0 +1,34 @@
1
+ # AGENTS.md
2
+
3
+ ## Purpose
4
+
5
+ - This repo owns the canonical agent guidance for the MBT API client package.
6
+ - Repo name: `mbt-api`
7
+ - npm package name: `mbt-api-client`
8
+ - Consumer apps should read the installed package docs from `node_modules/mbt-api-client/...`.
9
+
10
+ ## Read This First
11
+
12
+ 1. `.ai/agent-rules/README.md`
13
+ 2. `.ai/agent-rules/maintenance.md`
14
+ 3. `.ai/agent-reference/README.md`
15
+ 4. `.ai/agent-examples/README.md`
16
+
17
+ ## Guidance Map
18
+
19
+ - `.ai/agent-rules/README.md` - package-owned setup and usage rules.
20
+ - `.ai/agent-rules/maintenance.md` - required doc-sync rule.
21
+ - `.ai/agent-reference/README.md` - exported surfaces and required config map.
22
+ - `.ai/agent-examples/README.md` - package-safe examples for app setup and usage.
23
+
24
+ ## Maintenance Rule
25
+
26
+ - If a change affects public exports, required URLs, provider setup, token refresh behavior, or example usage, update the guidance docs before considering the work complete.
27
+
28
+ ## Guardrails
29
+
30
+ - Keep docs aligned with the actual `ApiClientUrls` contract.
31
+ - Keep examples focused on public package setup, not one consumer app's internal architecture.
32
+ - Use kebab-case for file names and folder names across this repo.
33
+ - Use `pnpm` as the package manager for this repo.
34
+ - Keep package guidance canonical in `AGENTS.md` and `.ai/` rather than tool-specific wrapper folders.
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # MBT API Client
2
+
3
+ Typed MBT API client with React context integration and token refresh helpers.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add mbt-api-client
9
+ ```
10
+
11
+ ## Package Surfaces
12
+
13
+ - `mbt-api-client` - API client factory, React provider, hooks, and token refresh helpers
14
+
15
+ ## Agent Guidance
16
+
17
+ - `AGENTS.md` is the package entrypoint for coding agents.
18
+ - Canonical rules live in `.ai/agent-rules/`.
19
+ - Factual reference lives in `.ai/agent-reference/`.
20
+ - Canonical usage examples live in `.ai/agent-examples/`.
21
+
22
+ ## Quick Start
23
+
24
+ ```tsx
25
+ import { ApiClientProvider, createApiClient } from 'mbt-api-client';
26
+
27
+ const apiClient = createApiClient({
28
+ urls: {
29
+ authService: 'https://api.example.com/auth',
30
+ userProgressService: 'https://api.example.com/progress',
31
+ recommendationService: 'https://api.example.com/recommendations',
32
+ partnerManager: 'https://api.example.com/partners',
33
+ adventureManager: 'https://api.example.com/adventures',
34
+ bff: 'https://api.example.com/bff',
35
+ puzzleController: 'https://api.example.com/puzzles',
36
+ micromodeController: 'https://api.example.com/micromodes',
37
+ userManager: 'https://api.example.com/users',
38
+ monolith: 'https://api.example.com',
39
+ },
40
+ });
41
+
42
+ <ApiClientProvider value={apiClient}>
43
+ <App />
44
+ </ApiClientProvider>;
45
+ ```
46
+
47
+ ## Public Exports
48
+
49
+ - `createApiClient`
50
+ - `ApiClientContext`
51
+ - `ApiClientProvider`
52
+ - `useApiClient`
53
+ - `TokenRefreshManager`
54
+ - `createTokenRefreshManager`
55
+
56
+ ## Required URL Keys
57
+
58
+ - `authService`
59
+ - `userProgressService`
60
+ - `recommendationService`
61
+ - `partnerManager`
62
+ - `adventureManager`
63
+ - `bff`
64
+ - `puzzleController`
65
+ - `micromodeController`
66
+ - `userManager`
67
+ - `monolith`
68
+
69
+ ## Development
70
+
71
+ ```bash
72
+ pnpm install
73
+ pnpm lint
74
+ pnpm format
75
+ pnpm build
76
+ ```
77
+
78
+ This repo standardizes on `pnpm` only.
79
+
80
+ ## Notes
81
+
82
+ - Package-owned agent guidance is versioned with the library and published with the package.
83
+ - The consumer app should read the installed package docs from `node_modules/mbt-api-client/...`.
package/dist/index.cjs CHANGED
@@ -61,19 +61,6 @@ var defaultNavigateToLogin = () => {
61
61
  window.location.href = "#/login";
62
62
  };
63
63
  var TokenRefreshManager = class {
64
- /**
65
- * Создаёт новый экземпляр TokenRefreshManager
66
- *
67
- * @param config - Конфигурация менеджера
68
- *
69
- * @example
70
- * ```typescript
71
- * const manager = new TokenRefreshManager({
72
- * refreshTokenFn: myRefreshFunction,
73
- * onNavigateToLogin: () => router.push('/login'),
74
- * });
75
- * ```
76
- */
77
64
  constructor(config) {
78
65
  this.isRefreshing = false;
79
66
  this.refreshPromise = null;
@@ -87,96 +74,20 @@ var TokenRefreshManager = class {
87
74
  clearTokens: config.clearTokens ?? defaultClearTokens
88
75
  };
89
76
  }
90
- /**
91
- * Получает текущий access токен
92
- *
93
- * @returns Access токен или null, если токен отсутствует
94
- *
95
- * @example
96
- * ```typescript
97
- * const token = manager.getAccessToken();
98
- * if (token) {
99
- * headers.Authorization = `Bearer ${token}`;
100
- * }
101
- * ```
102
- */
103
77
  getAccessToken() {
104
78
  return this.config.getAccessToken();
105
79
  }
106
- /**
107
- * Получает текущий refresh токен
108
- *
109
- * @returns Refresh токен или null, если токен отсутствует
110
- */
111
80
  getRefreshToken() {
112
81
  return this.config.getRefreshToken();
113
82
  }
114
- /**
115
- * Проверяет, идёт ли в данный момент процесс обновления токена
116
- *
117
- * Используйте для определения, нужно ли ставить запрос в очередь
118
- * или инициировать новое обновление.
119
- *
120
- * @returns `true` если обновление в процессе, `false` если нет
121
- *
122
- * @example
123
- * ```typescript
124
- * if (manager.isRefreshInProgress()) {
125
- * // Ждём завершения текущего обновления
126
- * const newToken = await manager.waitForTokenRefresh();
127
- * } else {
128
- * // Инициируем новое обновление
129
- * await manager.refreshToken();
130
- * }
131
- * ```
132
- */
133
83
  isRefreshInProgress() {
134
84
  return this.isRefreshing;
135
85
  }
136
- /**
137
- * Добавляет запрос в очередь ожидания обновления токена
138
- *
139
- * Возвращает промис, который разрешится с новым токеном после
140
- * успешного обновления или будет отклонён при ошибке.
141
- *
142
- * @returns Промис с новым access токеном
143
- *
144
- * @example
145
- * ```typescript
146
- * // В response interceptor при множественных 401
147
- * if (manager.isRefreshInProgress()) {
148
- * try {
149
- * const newToken = await manager.waitForTokenRefresh();
150
- * // Повторить запрос с новым токеном
151
- * } catch {
152
- * // Обновление не удалось
153
- * }
154
- * }
155
- * ```
156
- */
157
86
  waitForTokenRefresh() {
158
87
  return new Promise((resolve, reject) => {
159
88
  this.queuedRequests.push({ resolve, reject });
160
89
  });
161
90
  }
162
- /**
163
- * Выполняет обновление токена
164
- *
165
- * Если обновление уже в процессе, вернёт существующий промис.
166
- * После обновления уведомляет все запросы в очереди.
167
- *
168
- * @returns Промис с результатом обновления
169
- *
170
- * @example
171
- * ```typescript
172
- * const result = await manager.refreshToken();
173
- * if (result.ok) {
174
- * console.log('Новый токен:', result.accessToken);
175
- * } else {
176
- * console.log('Ошибка обновления токена');
177
- * }
178
- * ```
179
- */
180
91
  async refreshToken() {
181
92
  if (this.refreshPromise) {
182
93
  return this.refreshPromise;
@@ -189,33 +100,6 @@ var TokenRefreshManager = class {
189
100
  });
190
101
  return this.refreshPromise;
191
102
  }
192
- /**
193
- * Обрабатывает ошибку 401 — либо обновляет токен, либо перенаправляет на логин
194
- *
195
- * Основной метод для использования в interceptors. Автоматически:
196
- * - Перенаправляет на логин при невалидном токене
197
- * - Ставит запрос в очередь, если обновление уже идёт
198
- * - Инициирует обновление, если ещё не запущено
199
- *
200
- * @param isInvalidToken - Если true, токен считается невалидным и будет выполнен переход на логин
201
- * @returns Промис с новым access токеном или null при ошибке
202
- *
203
- * @example
204
- * ```typescript
205
- * // В axios response interceptor
206
- * if (error.response?.status === 401) {
207
- * const isInvalid = error.response.data?.message === 'Invalid token';
208
- * const newToken = await manager.handle401(isInvalid);
209
- *
210
- * if (newToken) {
211
- * // Повторить запрос с новым токеном
212
- * error.config.headers.Authorization = `Bearer ${newToken}`;
213
- * return axios(error.config);
214
- * }
215
- * // newToken === null означает переход на логин
216
- * }
217
- * ```
218
- */
219
103
  async handle401(isInvalidToken = false) {
220
104
  if (isInvalidToken) {
221
105
  this.config.onNavigateToLogin();
@@ -231,34 +115,9 @@ var TokenRefreshManager = class {
231
115
  }
232
116
  return result.accessToken || this.config.getAccessToken();
233
117
  }
234
- /**
235
- * Принудительно перенаправляет на страницу логина
236
- *
237
- * Вызывает колбэк onNavigateToLogin из конфигурации.
238
- *
239
- * @example
240
- * ```typescript
241
- * // При необходимости принудительного выхода
242
- * manager.navigateToLogin();
243
- * ```
244
- */
245
118
  navigateToLogin() {
246
119
  this.config.onNavigateToLogin();
247
120
  }
248
- /**
249
- * Сбрасывает внутреннее состояние менеджера
250
- *
251
- * Полезно для тестирования или при необходимости
252
- * принудительно сбросить очередь запросов.
253
- *
254
- * @example
255
- * ```typescript
256
- * // В тестах
257
- * beforeEach(() => {
258
- * manager.reset();
259
- * });
260
- * ```
261
- */
262
121
  reset() {
263
122
  this.isRefreshing = false;
264
123
  this.refreshPromise = null;
@@ -306,7 +165,7 @@ var defaultDecodeJwt = (token) => {
306
165
  const base64Url = token.split(".")[1];
307
166
  const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
308
167
  const jsonPayload = decodeURIComponent(
309
- atob(base64).split("").map((c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2)).join("")
168
+ atob(base64).split("").map((c) => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`).join("")
310
169
  );
311
170
  return JSON.parse(jsonPayload);
312
171
  } catch {