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.
@@ -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
@@ -33,12 +33,8 @@ __export(index_exports, {
33
33
  ApiClientContext: () => ApiClientContext,
34
34
  ApiClientProvider: () => ApiClientProvider,
35
35
  TokenRefreshManager: () => TokenRefreshManager,
36
- captureBreadcrumb: () => captureBreadcrumb,
37
- captureError: () => captureError,
38
- captureMessage: () => captureMessage2,
39
36
  createApiClient: () => createApiClient,
40
37
  createTokenRefreshManager: () => createTokenRefreshManager,
41
- initSentry: () => initSentry,
42
38
  useApiClient: () => useApiClient
43
39
  });
44
40
  module.exports = __toCommonJS(index_exports);
@@ -65,19 +61,6 @@ var defaultNavigateToLogin = () => {
65
61
  window.location.href = "#/login";
66
62
  };
67
63
  var TokenRefreshManager = class {
68
- /**
69
- * Создаёт новый экземпляр TokenRefreshManager
70
- *
71
- * @param config - Конфигурация менеджера
72
- *
73
- * @example
74
- * ```typescript
75
- * const manager = new TokenRefreshManager({
76
- * refreshTokenFn: myRefreshFunction,
77
- * onNavigateToLogin: () => router.push('/login'),
78
- * });
79
- * ```
80
- */
81
64
  constructor(config) {
82
65
  this.isRefreshing = false;
83
66
  this.refreshPromise = null;
@@ -91,96 +74,20 @@ var TokenRefreshManager = class {
91
74
  clearTokens: config.clearTokens ?? defaultClearTokens
92
75
  };
93
76
  }
94
- /**
95
- * Получает текущий access токен
96
- *
97
- * @returns Access токен или null, если токен отсутствует
98
- *
99
- * @example
100
- * ```typescript
101
- * const token = manager.getAccessToken();
102
- * if (token) {
103
- * headers.Authorization = `Bearer ${token}`;
104
- * }
105
- * ```
106
- */
107
77
  getAccessToken() {
108
78
  return this.config.getAccessToken();
109
79
  }
110
- /**
111
- * Получает текущий refresh токен
112
- *
113
- * @returns Refresh токен или null, если токен отсутствует
114
- */
115
80
  getRefreshToken() {
116
81
  return this.config.getRefreshToken();
117
82
  }
118
- /**
119
- * Проверяет, идёт ли в данный момент процесс обновления токена
120
- *
121
- * Используйте для определения, нужно ли ставить запрос в очередь
122
- * или инициировать новое обновление.
123
- *
124
- * @returns `true` если обновление в процессе, `false` если нет
125
- *
126
- * @example
127
- * ```typescript
128
- * if (manager.isRefreshInProgress()) {
129
- * // Ждём завершения текущего обновления
130
- * const newToken = await manager.waitForTokenRefresh();
131
- * } else {
132
- * // Инициируем новое обновление
133
- * await manager.refreshToken();
134
- * }
135
- * ```
136
- */
137
83
  isRefreshInProgress() {
138
84
  return this.isRefreshing;
139
85
  }
140
- /**
141
- * Добавляет запрос в очередь ожидания обновления токена
142
- *
143
- * Возвращает промис, который разрешится с новым токеном после
144
- * успешного обновления или будет отклонён при ошибке.
145
- *
146
- * @returns Промис с новым access токеном
147
- *
148
- * @example
149
- * ```typescript
150
- * // В response interceptor при множественных 401
151
- * if (manager.isRefreshInProgress()) {
152
- * try {
153
- * const newToken = await manager.waitForTokenRefresh();
154
- * // Повторить запрос с новым токеном
155
- * } catch {
156
- * // Обновление не удалось
157
- * }
158
- * }
159
- * ```
160
- */
161
86
  waitForTokenRefresh() {
162
87
  return new Promise((resolve, reject) => {
163
88
  this.queuedRequests.push({ resolve, reject });
164
89
  });
165
90
  }
166
- /**
167
- * Выполняет обновление токена
168
- *
169
- * Если обновление уже в процессе, вернёт существующий промис.
170
- * После обновления уведомляет все запросы в очереди.
171
- *
172
- * @returns Промис с результатом обновления
173
- *
174
- * @example
175
- * ```typescript
176
- * const result = await manager.refreshToken();
177
- * if (result.ok) {
178
- * console.log('Новый токен:', result.accessToken);
179
- * } else {
180
- * console.log('Ошибка обновления токена');
181
- * }
182
- * ```
183
- */
184
91
  async refreshToken() {
185
92
  if (this.refreshPromise) {
186
93
  return this.refreshPromise;
@@ -193,33 +100,6 @@ var TokenRefreshManager = class {
193
100
  });
194
101
  return this.refreshPromise;
195
102
  }
196
- /**
197
- * Обрабатывает ошибку 401 — либо обновляет токен, либо перенаправляет на логин
198
- *
199
- * Основной метод для использования в interceptors. Автоматически:
200
- * - Перенаправляет на логин при невалидном токене
201
- * - Ставит запрос в очередь, если обновление уже идёт
202
- * - Инициирует обновление, если ещё не запущено
203
- *
204
- * @param isInvalidToken - Если true, токен считается невалидным и будет выполнен переход на логин
205
- * @returns Промис с новым access токеном или null при ошибке
206
- *
207
- * @example
208
- * ```typescript
209
- * // В axios response interceptor
210
- * if (error.response?.status === 401) {
211
- * const isInvalid = error.response.data?.message === 'Invalid token';
212
- * const newToken = await manager.handle401(isInvalid);
213
- *
214
- * if (newToken) {
215
- * // Повторить запрос с новым токеном
216
- * error.config.headers.Authorization = `Bearer ${newToken}`;
217
- * return axios(error.config);
218
- * }
219
- * // newToken === null означает переход на логин
220
- * }
221
- * ```
222
- */
223
103
  async handle401(isInvalidToken = false) {
224
104
  if (isInvalidToken) {
225
105
  this.config.onNavigateToLogin();
@@ -235,34 +115,9 @@ var TokenRefreshManager = class {
235
115
  }
236
116
  return result.accessToken || this.config.getAccessToken();
237
117
  }
238
- /**
239
- * Принудительно перенаправляет на страницу логина
240
- *
241
- * Вызывает колбэк onNavigateToLogin из конфигурации.
242
- *
243
- * @example
244
- * ```typescript
245
- * // При необходимости принудительного выхода
246
- * manager.navigateToLogin();
247
- * ```
248
- */
249
118
  navigateToLogin() {
250
119
  this.config.onNavigateToLogin();
251
120
  }
252
- /**
253
- * Сбрасывает внутреннее состояние менеджера
254
- *
255
- * Полезно для тестирования или при необходимости
256
- * принудительно сбросить очередь запросов.
257
- *
258
- * @example
259
- * ```typescript
260
- * // В тестах
261
- * beforeEach(() => {
262
- * manager.reset();
263
- * });
264
- * ```
265
- */
266
121
  reset() {
267
122
  this.isRefreshing = false;
268
123
  this.refreshPromise = null;
@@ -310,7 +165,7 @@ var defaultDecodeJwt = (token) => {
310
165
  const base64Url = token.split(".")[1];
311
166
  const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
312
167
  const jsonPayload = decodeURIComponent(
313
- 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("")
314
169
  );
315
170
  return JSON.parse(jsonPayload);
316
171
  } catch {
@@ -592,56 +447,13 @@ var useApiClient = () => {
592
447
  return context;
593
448
  };
594
449
  var ApiClientProvider = ApiClientContext.Provider;
595
-
596
- // src/sentry.ts
597
- var Sentry = __toESM(require("@sentry/browser"), 1);
598
- var isInitialized = false;
599
- var initSentry = (dsn) => {
600
- if (isInitialized) {
601
- return;
602
- }
603
- Sentry.init({
604
- dsn,
605
- defaultIntegrations: false
606
- });
607
- isInitialized = true;
608
- };
609
- var captureError = (error, context) => {
610
- Sentry.withScope((scope) => {
611
- if (context) {
612
- scope.setContext("puzzle", context);
613
- }
614
- scope.setLevel("error");
615
- Sentry.captureException(error);
616
- });
617
- };
618
- var captureMessage2 = (message, level = "error", context) => {
619
- Sentry.withScope((scope) => {
620
- if (context) {
621
- scope.setContext("puzzle", context);
622
- }
623
- scope.setLevel(level);
624
- Sentry.captureMessage(message);
625
- });
626
- };
627
- var captureBreadcrumb = (message, data) => {
628
- Sentry.addBreadcrumb({
629
- message,
630
- data,
631
- level: "info"
632
- });
633
- };
634
450
  // Annotate the CommonJS export names for ESM import in node:
635
451
  0 && (module.exports = {
636
452
  ApiClientContext,
637
453
  ApiClientProvider,
638
454
  TokenRefreshManager,
639
- captureBreadcrumb,
640
- captureError,
641
- captureMessage,
642
455
  createApiClient,
643
456
  createTokenRefreshManager,
644
- initSentry,
645
457
  useApiClient
646
458
  });
647
459
  //# sourceMappingURL=index.cjs.map