@torrow/sdk-auth 0.0.0-stage → 0.6.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.
package/README.md CHANGED
@@ -1,3 +1,320 @@
1
- # Temporary Holding Version
1
+ # `@torrow/sdk-auth`
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Сессия токенов поверх **`@torrow/sdk-client`**: storage, проактивный refresh, single-flight, **`refreshOnUnauthorized`** (default **true**), фасад **`TorrowAuth`**.
4
+
5
+ В snapshot файла/storage: access / refresh / срок access / **`deviceId`**. Тип устройства (`deviceType`) задаёт приложение — в snapshot не пишется.
6
+
7
+ IMAP / stdin / captcha-UI / чтение `process.env` — **не** в пакете (слой app / **`examples/node/_shared`**).
8
+
9
+ ## Выбор режима
10
+
11
+ ```mermaid
12
+ flowchart TD
13
+ need[Нужен клиент] --> kind{Сценарий}
14
+ kind -->|restore| connect["TorrowAuth.connect"]
15
+ kind -->|OTP login| login["TorrowAuth.loginWithOtp"]
16
+ kind -->|Telegram deep-link| link["TorrowAuth.loginWithLink"]
17
+ kind -->|токены от хоста| fromTok["TorrowAuth.fromTokens"]
18
+ kind -->|без входа| anon["TorrowAuth.anonymous"]
19
+ connect --> handle["TorrowAuthResult: client + session"]
20
+ login --> handle
21
+ link --> handle
22
+ fromTok --> handle
23
+ anon --> clientOnly["TorrowClient"]
24
+ ```
25
+
26
+ Флоу / подсистема для агентов: **`docs/agents/auth-flow.md`**. Индекс примеров Node: **`examples/node/auth/README.md`**, **`examples/node/_shared/README.md`**.
27
+
28
+ ## Целевой клиентский код
29
+
30
+ Общая база `{ tokenStorage, deviceInfo }` (+ опционально `appCredentials`) у режимов с сессией.
31
+ Без `appCredentials` — **`DEFAULT_APP_CREDENTIALS`** (`sdk.torrow.net`). Своя пара перекрывает дефолт.
32
+
33
+ ### 1. Restore + OTP при необходимости
34
+
35
+ ```ts
36
+ import { TorrowAuth } from "@torrow/sdk-auth";
37
+ import { FileTokenStorage } from "@torrow/sdk-auth/node";
38
+ import { DeviceTypeValues } from "@torrow/sdk-client";
39
+
40
+ const base = {
41
+ // appCredentials опционален — иначе DEFAULT_APP_CREDENTIALS
42
+ tokenStorage: new FileTokenStorage("./.torrow-session.json"),
43
+ deviceInfo: { deviceId: "pos-terminal-1", deviceType: DeviceTypeValues.Desktop },
44
+ userAgent: "my-app/1.0",
45
+ };
46
+
47
+ let { client, session } = await TorrowAuth.connect(base);
48
+
49
+ if (session.status !== "authenticated") {
50
+ ({ client, session } = await TorrowAuth.loginWithOtp({
51
+ ...base,
52
+ account: { accountType: "Phone", accountName: "+79001234567" },
53
+ onSendCode: (d) => showHint(d.usedMethod, d.codeLength),
54
+ getCode: (ctx) => promptUser(ctx.codeLength),
55
+ }));
56
+ }
57
+
58
+ await client.profiles.list();
59
+ ```
60
+
61
+ Чтобы задать свои креды приложения:
62
+
63
+ ```ts
64
+ appCredentials: { clientId: CLIENT_ID, clientSecret: CLIENT_SECRET },
65
+ ```
66
+
67
+ ### 2. Mobile ID / смена канала / rate-limit
68
+
69
+ ```ts
70
+ import { OtpCodeRequestLimitExceededError } from "@torrow/sdk-auth";
71
+
72
+ try {
73
+ await TorrowAuth.loginWithOtp({
74
+ ...base,
75
+ account: { accountType: "Phone", accountName: phone },
76
+ supportedMethods: ["MtsMobileId", "Sms"],
77
+ preferredMethod: userPickedMethod,
78
+ onSendCode: (d) => {
79
+ // MtsMobileId → UI «подтвердите push»; getCode не вызовется на happy-path
80
+ renderLoginUi(d);
81
+ },
82
+ getCode: (ctx) => waitCodeInput(ctx),
83
+ });
84
+ } catch (e) {
85
+ if (e instanceof OtpCodeRequestLimitExceededError) {
86
+ showRetryTimer(e.minutes, e.seconds);
87
+ } else {
88
+ throw e;
89
+ }
90
+ }
91
+ ```
92
+
93
+ ### 3. Telegram deep-link
94
+
95
+ ```ts
96
+ const { client, session } = await TorrowAuth.loginWithLink({
97
+ ...base,
98
+ onLink: (authUrl) => showQrOrOpen(authUrl),
99
+ signal: abortController.signal,
100
+ });
101
+ ```
102
+
103
+ ### 4. Виджет: токены от хоста
104
+
105
+ ```ts
106
+ import { TorrowAuth, LocalStorageTokenStorage } from "@torrow/sdk-auth";
107
+
108
+ const { client, session } = await TorrowAuth.fromTokens({
109
+ accessToken: hostProvidedAccess,
110
+ refreshToken: hostProvidedRefresh,
111
+ appCredentials: hostProvidedAppCreds,
112
+ tokenStorage: new LocalStorageTokenStorage("torrow.widget.session"),
113
+ deviceInfo: { deviceId: widgetDeviceId(), deviceType: DeviceTypeValues.Web },
114
+ });
115
+
116
+ session.onStatusChange((s) => {
117
+ if (s !== "authenticated") widgetUi.showLoginPrompt();
118
+ });
119
+ ```
120
+
121
+ ### 5. Аноним и выход
122
+
123
+ ```ts
124
+ const publicClient = TorrowAuth.anonymous({ userAgent: "my-widget" });
125
+
126
+ await session.logout(); // revoke refresh + clear storage + onStatusChange("unauthenticated")
127
+ ```
128
+
129
+ ## Сигналы UI
130
+
131
+ | Сигнал от SDK | Что делает App |
132
+ |---------------|----------------|
133
+ | **`onSendCode({ usedMethod: Sms, codeLength, … })`** | Форма/подсказка «код в SMS»; длина поля = `codeLength` |
134
+ | **`onSendCode({ usedMethod: TelegramGateway, codeLength })`** | Форма «код в Telegram» (**не** deep-link) |
135
+ | **`onSendCode({ usedMethod: FlashCall, codeLength })`** | Подсказка «цифры из звонка» |
136
+ | **`onSendCode({ accountType: Email, codeLength })`** | Форма «код на email» |
137
+ | **`onSendCode({ usedMethod: MtsMobileId })`** | Ожидание push **без** keypad (`codeLength` часто `0`) |
138
+ | **`getCode({ codeLength })`** | Дождаться ввода; валидировать по `codeLength` (экран уже из `onSendCode`) |
139
+ | **`onLink(authUrl)`** (`loginWithLink`) | Показать QR / открыть Telegram; **код OTP не вводить** |
140
+ | **`session.onStatusChange`** | Реактивный login-state (`authenticated` / `unauthenticated` / `expired`) |
141
+
142
+ ## Sequence: OTP (`loginWithOtp`)
143
+
144
+ ```mermaid
145
+ sequenceDiagram
146
+ participant User as User
147
+ participant App
148
+ participant SdkAuth as sdk_auth
149
+ participant Auth as client.auth
150
+
151
+ User->>App: телефон или email
152
+ App->>SdkAuth: TorrowAuth.loginWithOtp
153
+ SdkAuth->>Auth: sendCode
154
+ Auth-->>SdkAuth: usedMethod sessionId codeLength
155
+ SdkAuth->>App: onSendCode account usedMethod codeLength
156
+
157
+ alt usedMethod MtsMobileId
158
+ Note over App,User: onSendCode MtsMobileId без keypad
159
+ App->>User: UI ожидание push
160
+ loop пока Pending
161
+ SdkAuth->>Auth: requestToken code empty
162
+ Auth-->>SdkAuth: status Pending
163
+ end
164
+ opt status AwaitingSmsCode
165
+ Note over SdkAuth,App: показать SMS-форму
166
+ SdkAuth->>App: getCode codeLength
167
+ App->>User: форма SMS-кода
168
+ User->>App: ввод SMS-кода
169
+ App-->>SdkAuth: code
170
+ SdkAuth->>Auth: requestToken with code
171
+ end
172
+ else classic Sms FlashCall TelegramGateway Email
173
+ Note over App,User: onSendCode usedMethod в т.ч. TelegramGateway
174
+ App->>User: форма или подсказка под usedMethod
175
+ SdkAuth->>App: getCode codeLength
176
+ User->>App: ввод кода
177
+ App-->>SdkAuth: code
178
+ SdkAuth->>Auth: requestToken with code
179
+ end
180
+
181
+ Auth-->>SdkAuth: TokenReady
182
+ SdkAuth->>SdkAuth: persist TokenStorage
183
+ SdkAuth-->>App: client plus session
184
+ App->>User: вход выполнен
185
+ ```
186
+
187
+ ## Sequence: Telegram (`loginWithLink`)
188
+
189
+ ```mermaid
190
+ sequenceDiagram
191
+ participant User as User
192
+ participant App
193
+ participant SdkAuth as sdk_auth
194
+ participant Auth as client.auth
195
+ participant Bot as TelegramBot
196
+
197
+ User->>App: войти через Telegram
198
+ App->>SdkAuth: TorrowAuth.loginWithLink
199
+ SdkAuth->>Auth: createSession
200
+ Auth-->>SdkAuth: sessionId code authUrl
201
+ SdkAuth->>App: onLink authUrl
202
+ App->>User: QR или открыть authUrl
203
+ User->>Bot: Start
204
+ Bot->>Auth: VerifyAuthSession
205
+ loop пока не ready
206
+ SdkAuth->>Auth: requestToken sessionId code
207
+ Auth-->>SdkAuth: not ready или TokenReady
208
+ end
209
+ SdkAuth->>SdkAuth: persist TokenStorage
210
+ SdkAuth-->>App: client plus session
211
+ App->>User: вход выполнен
212
+ ```
213
+
214
+ ## Sequence: connect / refresh / 401 / logout
215
+
216
+ ```mermaid
217
+ sequenceDiagram
218
+ participant User as User
219
+ participant App
220
+ participant SdkAuth as sdk_auth
221
+ participant Api as TorrowAPI
222
+ participant Auth as client.auth
223
+
224
+ App->>SdkAuth: TorrowAuth.connect
225
+ SdkAuth->>SdkAuth: TokenStorage.load
226
+ alt access свежий
227
+ SdkAuth-->>App: client plus session authenticated
228
+ else есть refreshToken
229
+ SdkAuth->>Auth: refresh
230
+ Auth-->>SdkAuth: TokenReady
231
+ SdkAuth->>SdkAuth: persist
232
+ SdkAuth-->>App: client plus session authenticated
233
+ else нет токенов
234
+ SdkAuth-->>App: session unauthenticated
235
+ Note over App: далее loginWithOtp или loginWithLink
236
+ end
237
+
238
+ User->>App: действие API
239
+ App->>Api: Bearer request
240
+ alt 401 и refreshOnUnauthorized
241
+ Api-->>SdkAuth: 401
242
+ SdkAuth->>Auth: refresh
243
+ Auth-->>SdkAuth: TokenReady
244
+ SdkAuth->>Api: retry once
245
+ Api-->>App: 200
246
+ else access истекает по skew
247
+ SdkAuth->>Auth: proactive refresh
248
+ Auth-->>SdkAuth: TokenReady
249
+ end
250
+
251
+ opt logout
252
+ App->>SdkAuth: session.logout
253
+ SdkAuth->>Auth: revoke
254
+ SdkAuth->>SdkAuth: clear TokenStorage
255
+ SdkAuth->>App: onStatusChange unauthenticated
256
+ end
257
+ ```
258
+
259
+ ## Sequence: fromTokens + onStatusChange
260
+
261
+ ```mermaid
262
+ sequenceDiagram
263
+ participant Host as HostApp
264
+ participant App as Widget
265
+ participant SdkAuth as sdk_auth
266
+
267
+ Host->>App: accessToken refreshToken optional
268
+ App->>SdkAuth: TorrowAuth.fromTokens
269
+ SdkAuth->>SdkAuth: save to TokenStorage
270
+ SdkAuth-->>App: client plus session
271
+ SdkAuth->>App: onStatusChange authenticated
272
+ Note over App,SdkAuth: дальше как connect refresh 401
273
+ opt access истёк без refresh или logout
274
+ SdkAuth->>App: onStatusChange expired or unauthenticated
275
+ App->>Host: запросить новый логин
276
+ end
277
+ ```
278
+
279
+ ## Storage
280
+
281
+ | Класс | Назначение |
282
+ |-------|------------|
283
+ | **`MemoryTokenStorage`** | Тесты, эфемерные процессы, без диска |
284
+ | **`FileTokenStorage`** | Node-only (`node:fs`): JSON-файл — **`@torrow/sdk-auth/node`** (канон для `examples/node/**`) |
285
+ | **`LocalStorageTokenStorage`** | Браузер / виджет: `localStorage` |
286
+ | custom **`TokenStorage`** | IndexedDB, SecureStore, … — `load` / `save` снаружи |
287
+
288
+ **Node vs browser:** главный вход `@torrow/sdk-auth` browser-safe. `FileTokenStorage` — только `import { FileTokenStorage } from "@torrow/sdk-auth/node"` (тянет `node:fs`).
289
+
290
+ **JWT vs integration key:** user JWT обычно с `exp` в payload; opaque/integration key — строка без JWT-формы.
291
+
292
+ - **Opaque (non-JWT-shaped):** меньше 3 сегментов через точку (не `a.b.c`); host TTL через `TorrowAuth.fromTokens({ accessExpiresAtMs })` или бессрочно без TTL
293
+ - **JWT-shaped undecodable (`a.b.c`):** `isAccessStillFresh` всегда false → proactive refresh на `open` / `ensureFresh`, пока есть `refreshToken`; после refresh **без** `expiration` merge сбрасывает snapshot TTL → opaque unlimited → дальнейший proactive refresh не идёт (не refresh-storm)
294
+ - **Valid JWT:** runtime freshness AND-ит `exp`; будущий explicit `accessExpiresAtMs` не оживит просроченный JWT
295
+ - **Opaque host TTL в skew-окне** (`accessTokenSkewMs`, default 30s): refresh без `expiration` сбрасывает snapshot TTL → бессрочный opaque до 401 (лучше refresh-storm)
296
+
297
+ Host: non-JWT-shaped opaque **или** валидный JWT с `exp`.
298
+
299
+ Канон examples: `new FileTokenStorage(path)` + `TorrowAuth.connect` / `loginWithOtp` — без ручного dual deviceInfo provider.
300
+
301
+ ## API (кратко)
302
+
303
+ | API | Роль |
304
+ |-----|------|
305
+ | **`TorrowAuth.connect`** | restore / refresh → `{ client, session }`; **без** OTP |
306
+ | **`TorrowAuth.loginWithOtp`** | sendCode → Mobile ID / classic → persist |
307
+ | **`TorrowAuth.loginWithLink`** | Telegram deep-link → poll → persist |
308
+ | **`TorrowAuth.fromTokens`** | сид access[/refresh] → connect-семантика |
309
+ | **`TorrowAuth.anonymous`** | `TorrowClient` без Bearer |
310
+ | **`TokenSession`** | `status`, `onStatusChange`, `logout`, `requestTokenWithCode`, `refresh`, `client` |
311
+ | **`refreshOnUnauthorized`** | 401 → refresh → один retry (**default true**) |
312
+ | **`onRefreshFailure`** | optional; политика при ошибке refresh (`keep` / `clear` / `throw`); default: non-HTTP / network → `keep`; HTTP 429/502–504 → `keep`; other HTTP → `clear` |
313
+
314
+ `appCredentials: { clientId, clientSecret }` (опционально; иначе **`DEFAULT_APP_CREDENTIALS`**)
315
+ внутри auth-пакета мапится на **`AuthIdentityBasic`**
316
+ (`username`/`password`) → **`TorrowClientConfig.authIdentityBasic`**. Маппер **не** публичный API:
317
+ приложениям достаточно **`appCredentials`** или дефолта; low-level `TorrowClient` собирает Basic сам
318
+ (см. **`examples/node/auth/auth-send-code.ts`**).
319
+
320
+ **401-retry** вызывает только **refresh**, не OTP.
@@ -0,0 +1,350 @@
1
+ import { T as TokenStorage, A as AuthTokenSnapshot, a as TokenSessionOptions, b as TokenSessionStatus, c as TokenSessionStatusListener } from './types-LCuPy3dY.js';
2
+ export { d as AppCredentials, D as DEFAULT_APP_CREDENTIALS, R as RefreshFailureAction } from './types-LCuPy3dY.js';
3
+ import { TorrowClient, DeviceInfo, TokenResp, AuthScope, VerificationMethod, SendCodeAccountType, AuthCaptchaType, AccountType } from '@torrow/sdk-client';
4
+
5
+ /**
6
+ * In-memory реализация {@link TokenStorage}.
7
+ *
8
+ * Тесты, эфемерные процессы, `TorrowAuth.fromTokens` без диска.
9
+ * Для Node-примеров обычно {@link FileTokenStorage}; для браузера —
10
+ * {@link LocalStorageTokenStorage}.
11
+ */
12
+
13
+ /** In-memory хранилище токенов сессии. */
14
+ declare class MemoryTokenStorage implements TokenStorage {
15
+ private snapshot;
16
+ constructor(initial?: AuthTokenSnapshot);
17
+ load(): Promise<AuthTokenSnapshot>;
18
+ save(snapshot: AuthTokenSnapshot): Promise<void>;
19
+ }
20
+
21
+ /**
22
+ * Persist snapshot в браузерный **`localStorage`** — реализация {@link TokenStorage}.
23
+ *
24
+ * Для виджетов / SPA (`TorrowAuth.fromTokens` / `connect`). В Node без
25
+ * `globalThis.localStorage` конструктор бросит. Не путать с {@link FileTokenStorage}.
26
+ */
27
+
28
+ type LocalStorageLike = {
29
+ getItem(key: string): string | null;
30
+ setItem(key: string, value: string): void;
31
+ removeItem?(key: string): void;
32
+ };
33
+ /**
34
+ * Persist snapshot токенов в браузерный `localStorage` (виджет / SPA).
35
+ */
36
+ declare class LocalStorageTokenStorage implements TokenStorage {
37
+ readonly key: string;
38
+ private readonly store;
39
+ constructor(key: string, store?: LocalStorageLike);
40
+ load(): Promise<AuthTokenSnapshot>;
41
+ save(snapshot: AuthTokenSnapshot): Promise<void>;
42
+ }
43
+
44
+ /** Сессия auth ещё не подтверждена (например Telegram до Start у бота). */
45
+ declare class AuthSessionNotReadyError extends Error {
46
+ readonly name = "AuthSessionNotReadyError";
47
+ readonly cause?: unknown;
48
+ constructor(message?: string, cause?: unknown);
49
+ }
50
+
51
+ /**
52
+ * Ядро пользовательской сессии: persist + проактивный refresh + single-flight + 401-retry.
53
+ *
54
+ * Low-level API: {@link TokenSession.open}. Канон для приложений —
55
+ * **`TorrowAuth.connect` / `login*` / `fromTokens`** (см. `torrow-auth.ts`).
56
+ *
57
+ * Логин снаружи: `TorrowAuth.loginWithOtp` / `loginWithLink`, либо
58
+ * `requestTokenWithCode` / `applyTokenResp` на уже открытой сессии.
59
+ * Не путать статус `"unauthenticated"` с `TorrowAuth.anonymous` (клиент без сессии).
60
+ */
61
+
62
+ /**
63
+ * Пользовательская сессия (класс). См. file overview выше.
64
+ */
65
+ declare class TokenSession {
66
+ private readonly opts;
67
+ private tokenSnapshot;
68
+ private ensureFreshMutex;
69
+ private _client;
70
+ private readonly statusListeners;
71
+ private closed;
72
+ /** Set in refreshWithFailurePolicy catch; not cleared on success (see public refresh). */
73
+ private lastRefreshError;
74
+ private constructor();
75
+ static open(opts: TokenSessionOptions): Promise<TokenSession>;
76
+ /** Persist, переданный в `TokenSession.open` / `TorrowAuth.*`. */
77
+ get tokenStorage(): TokenStorage;
78
+ get client(): TorrowClient;
79
+ /** Текущий статус сессии. */
80
+ get status(): TokenSessionStatus;
81
+ /** Подписка на смену статуса; возвращает unsubscribe. Сразу вызывает listener с текущим статусом. */
82
+ onStatusChange(listener: TokenSessionStatusListener): () => void;
83
+ /** Текущий снимок токенов сессии (копия {@link AuthTokenSnapshot}). */
84
+ getTokenSnapshot(): AuthTokenSnapshot;
85
+ /** `deviceInfo` из options open или fallback Desktop + deviceId из tokenSnapshot. */
86
+ getDeviceInfo(): DeviceInfo;
87
+ getAccessToken(): Promise<string | undefined>;
88
+ /**
89
+ * Сохранить ответ `requestToken` / `refresh` (только TokenReady).
90
+ * Сериализован с `ensureFresh` / `logout` через mutex. Дефолт merge намеренно
91
+ * (refresh-shaped); логин — `{ replace: true }`. После `logout` — новая сессия.
92
+ */
93
+ applyTokenResp(tokens: TokenResp, opts?: {
94
+ replace?: boolean;
95
+ }): Promise<void>;
96
+ clearTokens(): Promise<void>;
97
+ /**
98
+ * Revoke refresh (если есть) + очистка storage.
99
+ * Единственное публичное имя выхода из сессии.
100
+ */
101
+ logout(): Promise<void>;
102
+ /**
103
+ * Получить токены по **id auth-сессии** (`sessionId` из `sendCode` / `createSession`)
104
+ * и **OTP-коду доступа**, затем сохранить их в storage сессии.
105
+ *
106
+ * Один вызов `POST /api/v1/auth/token` (`requestToken`) с `{ sessionId, code }`.
107
+ * При успехе (`TokenReady`) — persist; если сессия ещё не готова —
108
+ * {@link AuthSessionNotReadyError} (поллинг для deep-link — внутри `TorrowAuth.loginWithLink`).
109
+ *
110
+ * HTTP вне mutex; persist сериализован через {@link applyTokenResp} (не оборачивать
111
+ * весь метод в `runExclusive` — вложенный mutex с публичным `applyTokenResp`).
112
+ */
113
+ requestTokenWithCode(args: {
114
+ sessionId: string;
115
+ code: string;
116
+ scopes?: AuthScope[];
117
+ signal?: AbortSignal;
118
+ }): Promise<TokenResp>;
119
+ /**
120
+ * Явный refresh (single-flight через общий mutex сессии).
121
+ * Сериализован с `logout` / `ensureFresh` / `applyTokenResp`.
122
+ * При гонке с `logout()`: promise может resolve с `TokenResp`, но snapshot уже очищен —
123
+ * не используй return value; смотри `session.status` / `getTokenSnapshot()`.
124
+ */
125
+ refresh(): Promise<TokenResp>;
126
+ ensureFresh(): Promise<void>;
127
+ private assertNotClosedForWrite;
128
+ /** Caller must hold `runExclusive`. Do not call public `applyTokenResp` from here. */
129
+ private applyTokenRespUnlocked;
130
+ private clearTokensUnlocked;
131
+ /** Caller must hold `runExclusive`. `refreshWithFailurePolicy` skips when no refreshToken; guard here is intentional defense-in-depth. */
132
+ private refreshUnlocked;
133
+ /** Caller must hold `runExclusive`. */
134
+ private refreshWithFailurePolicy;
135
+ private runExclusive;
136
+ private ensureFreshImpl;
137
+ private isAccessStillFresh;
138
+ private computeStatus;
139
+ private emitStatusChange;
140
+ private resolveDeviceInfo;
141
+ /** Identity-клиент с Basic приложения, без Bearer пользователя. */
142
+ identityClient(): TorrowClient;
143
+ private buildClient;
144
+ private persist;
145
+ }
146
+
147
+ /**
148
+ * Внутренний оркестратор OTP-логина (не публичный export целиком; типы UI — да).
149
+ *
150
+ * **`sendCode`** → ветка по **`usedMethod`** (Mobile ID poll | classic код) →
151
+ * **`TokenResp`**. UI снаружи: **`onSendCode`** + **`getCode`**.
152
+ * IMAP / stdin / env — в app / `examples/node/_shared`, не здесь.
153
+ *
154
+ * Вызывается из `TorrowAuth.loginWithOtp`; Mobile ID — `mobile-id.ts`.
155
+ */
156
+
157
+ /**
158
+ * Аргумент **`onSendCode`**: снимок **после** успешного `sendCode` (в основном поля **`SendCodeResp`**).
159
+ * Не путать с телом запроса: в `sendCode` уходят `supportedMethods` / `preferredMethod`
160
+ * (см. {@link RunOtpLoginOptions}); здесь — что сервер **выбрал** и что ещё доступно.
161
+ *
162
+ * Типичный UI: по `usedMethod` показать «ждём SMS» / «код на email» / «подтвердите push»;
163
+ * по `codeLength` — маску поля ввода.
164
+ */
165
+ type OtpSendCodeResult = {
166
+ /** Аккаунт из `SendCodeReq` (телефон / email). */
167
+ accountName: string;
168
+ /** `Phone` \| `Email` из запроса (не путать с `usedMethod`). */
169
+ accountType: SendCodeAccountType;
170
+ /** Id auth-сессии для последующего `requestToken` / Mobile ID poll. */
171
+ sessionId: string;
172
+ /**
173
+ * Фактический канал из **`SendCodeResp.usedMethod`** — ради этого и нужен `onSendCode`.
174
+ * Sms / Email / FlashCall / TelegramGateway / MtsMobileId / …
175
+ */
176
+ usedMethod?: VerificationMethod;
177
+ /** Длина кода для input/маски; у Mobile ID часто `0` (keypad не нужен). */
178
+ codeLength?: number;
179
+ /** Интервал poll при Mobile ID (`SendCodeResp.pollIntervalSeconds`). */
180
+ pollIntervalSeconds?: number;
181
+ /**
182
+ * Из **`SendCodeResp`** (не тело `sendCode`): методы, доступные для этого аккаунта
183
+ * (UX «другой способ»). В запрос передаётся {@link RunOtpLoginOptions.supportedMethods}.
184
+ */
185
+ availableMethods?: VerificationMethod[] | null;
186
+ /** TTL auth-сессии `sendCode` (сек); для Mobile ID — ориентир max wait. */
187
+ sessionLifeTimeInSeconds?: number;
188
+ /** Когда снова можно дернуть `sendCode` (rate-limit hint из ответа). */
189
+ nextAvailableUsageTime?: Date | null;
190
+ };
191
+ /**
192
+ * Аргумент **`getCode`**: только когда нужны цифры (stdin / UI / IMAP снаружи).
193
+ *
194
+ * UI-экран уже выбран в **`onSendCode`** (`usedMethod`). Здесь — собрать код
195
+ * (длина из `codeLength`). На happy-path Mobile ID (push) **`getCode` не вызывается**;
196
+ * SMS-fallback после poll — тот же колбэк, без отдельного `reason`.
197
+ *
198
+ * `accountName` / `accountType` не дублируются — они в options и в `onSendCode`.
199
+ */
200
+ type OtpGetCodeContext = {
201
+ sessionId: string;
202
+ /** Эффективная длина для валидации (Mobile ID SMS fallback: при `0` с сервера → 4). */
203
+ codeLength?: number;
204
+ /** Канал из `sendCode` (опциональная подсказка; экран уже из `onSendCode`). */
205
+ usedMethod?: VerificationMethod;
206
+ };
207
+ /** Превышен лимит `sendCode` (`CODE_REQUEST_LIMIT_EXCEEDED`). */
208
+ declare class OtpCodeRequestLimitExceededError extends Error {
209
+ readonly name = "OtpCodeRequestLimitExceededError";
210
+ readonly minutes?: string;
211
+ readonly seconds?: string;
212
+ readonly traceUrl?: string;
213
+ readonly apiMessage?: string;
214
+ readonly body: unknown;
215
+ constructor(body: unknown);
216
+ }
217
+ /** Нет Basic на `/auth/token` (`AUTH_HEADER_MISSING`). */
218
+ declare class OtpAuthHeaderMissingError extends Error {
219
+ readonly name = "OtpAuthHeaderMissingError";
220
+ readonly body: unknown;
221
+ readonly systemMessage?: string;
222
+ constructor(body: unknown);
223
+ }
224
+
225
+ /**
226
+ * Публичный фасад аутентификации **`TorrowAuth`**.
227
+ *
228
+ * Точка входа приложения: `connect` / `loginWithOtp` / `loginWithLink` /
229
+ * `fromTokens` / `anonymous`. Возвращает {@link TorrowAuthResult}
230
+ * (`{ client, session }`), кроме `anonymous` (только `TorrowClient`).
231
+ *
232
+ * Оркестрация OTP/Telegram — в `otp-login.ts` / `link-login.ts`;
233
+ * persist и refresh — в {@link TokenSession}.
234
+ */
235
+
236
+ /** Результат `TorrowAuth.connect` / `loginWithOtp` / `loginWithLink` / `fromTokens`: API-клиент + сессия. */
237
+ type TorrowAuthResult = {
238
+ client: TorrowClient;
239
+ session: TokenSession;
240
+ };
241
+ /**
242
+ * Общая база опций `TorrowAuth.connect` / `loginWithOtp` / `loginWithLink` / `fromTokens`.
243
+ *
244
+ * Производная от {@link TokenSessionOptions}: те же HTTP-кнопки + storage + app creds,
245
+ * но `deviceInfo` **обязателен**; `onRefreshFailure` пробрасывается в `TokenSession.open`.
246
+ *
247
+ * **Карта:** `TorrowClientConfig` → (Pick HTTP) → `TokenSessionOptions` → **эта база** →
248
+ * `TorrowAuthConnectOptions` / `TorrowAuthLoginWithOtpOptions` / …
249
+ * Приложение обычно передаёт именно `TorrowAuth*Options`, не `TorrowClientConfig`.
250
+ *
251
+ * `appCredentials` опционален: без него — дефолт SDK (`DEFAULT_APP_CREDENTIALS`).
252
+ */
253
+ type TorrowAuthSessionBase = Omit<TokenSessionOptions, "deviceInfo"> & {
254
+ deviceInfo: DeviceInfo;
255
+ };
256
+ /** Опции {@link TorrowAuth.connect}: restore / refresh из `tokenStorage`. */
257
+ type TorrowAuthConnectOptions = TorrowAuthSessionBase;
258
+ /** Аккаунт для OTP `sendCode` — плоско, как OpenAPI `SendCodeReq`. */
259
+ type TorrowAuthAccount = {
260
+ /** `Phone` \| `Email` → `SendCodeReq.type`. */
261
+ accountType: SendCodeAccountType;
262
+ /** Телефон или email → `SendCodeReq.accountName`. */
263
+ accountName: string;
264
+ };
265
+ /** Опции {@link TorrowAuth.loginWithOtp}: connect-база + OTP-колбэки / поля `sendCode`. */
266
+ type TorrowAuthLoginWithOtpOptions = TorrowAuthSessionBase & {
267
+ account: TorrowAuthAccount;
268
+ /**
269
+ * Сигнал UI после `sendCode` (`usedMethod`, `codeLength`, …).
270
+ * До Mobile ID poll и до classic `getCode`. См. {@link OtpSendCodeResult}.
271
+ */
272
+ onSendCode: (result: OtpSendCodeResult) => void | Promise<void>;
273
+ /**
274
+ * Вернуть одноразовый код. Не зовётся на happy-path Mobile ID (push без SMS).
275
+ * См. {@link OtpGetCodeContext}.
276
+ */
277
+ getCode: (ctx: OtpGetCodeContext) => Promise<string>;
278
+ /** → `TokenReq.scopes`. По умолчанию `DefaultAuthScopes`. */
279
+ scopes?: AuthScope[];
280
+ /** → `SendCodeReq.language`. По умолчанию `"ru"`. */
281
+ language?: string;
282
+ /** → `SendCodeReq.regionStorageId`. По умолчанию `70`. */
283
+ regionStorageId?: number;
284
+ /** → `SendCodeReq.captcha` (если шлюз требует). */
285
+ captcha?: string;
286
+ /** → `SendCodeReq.captchaType`. */
287
+ captchaType?: AuthCaptchaType;
288
+ /** → `SendCodeReq.inviteSessionId`. */
289
+ inviteSessionId?: string;
290
+ /**
291
+ * Hint серверу → `SendCodeReq.preferredMethod`.
292
+ * Факт смотри в `onSendCode.usedMethod`.
293
+ */
294
+ preferredMethod?: VerificationMethod;
295
+ /**
296
+ * Capability клиента → `SendCodeReq.supportedMethods`.
297
+ * Без `MtsMobileId` сервер обычно не выберет Mobile ID.
298
+ */
299
+ supportedMethods?: VerificationMethod[];
300
+ /** Отмена `sendCode` / poll / `requestToken`. */
301
+ signal?: AbortSignal;
302
+ };
303
+ /** Опции {@link TorrowAuth.loginWithLink} (Telegram deep-link). */
304
+ type TorrowAuthLoginWithLinkOptions = TorrowAuthSessionBase & {
305
+ /** Показать deep-link / QR (`CreateAuthSessionResp.authUrl`). */
306
+ onLink: (authUrl: string) => void | Promise<void>;
307
+ /** По умолчанию `Telegram`. */
308
+ accountType?: AccountType;
309
+ language?: string;
310
+ regionStorageId?: number;
311
+ captcha?: string;
312
+ inviteSessionId?: string;
313
+ scopes?: AuthScope[];
314
+ /** Интервал poll `requestToken` (мс). По умолчанию 2000. */
315
+ pollIntervalMs?: number;
316
+ /** Max wait poll (сек). Иначе TTL сессии из ответа или 180. */
317
+ pollTimeoutSec?: number;
318
+ signal?: AbortSignal;
319
+ };
320
+ /** Опции {@link TorrowAuth.fromTokens}: сид токенов от хоста в `tokenStorage` → connect. */
321
+ type TorrowAuthFromTokensOptions = TorrowAuthSessionBase & {
322
+ accessToken: string;
323
+ /** Без него refresh недоступен (даже если задан `appCredentials`). */
324
+ refreshToken?: string;
325
+ /**
326
+ * Абсолютное истечение access (мс). Для opaque/integration keys без JWT `exp`.
327
+ * Приоритет **записи в snapshot**: explicit (`> 0` finite) → JWT `exp` → `undefined` (бессрочный ключ).
328
+ * Runtime freshness AND-ит JWT `exp`: будущий explicit TTL не оживит просроченный JWT.
329
+ * Opaque без JWT-формы: explicit TTL или бессрочно без TTL.
330
+ */
331
+ accessExpiresAtMs?: number;
332
+ };
333
+ /**
334
+ * Фасад аутентификации для приложений и виджетов.
335
+ *
336
+ * - {@link TorrowAuth.connect} — restore / refresh
337
+ * - {@link TorrowAuth.loginWithOtp} — OTP (Phone/Email, Mobile ID / classic)
338
+ * - {@link TorrowAuth.loginWithLink} — Telegram deep-link
339
+ * - {@link TorrowAuth.fromTokens} — сид токенов от хоста
340
+ * - {@link TorrowAuth.anonymous} — клиент без Bearer
341
+ */
342
+ declare const TorrowAuth: {
343
+ readonly connect: (options: TorrowAuthConnectOptions) => Promise<TorrowAuthResult>;
344
+ readonly loginWithOtp: (options: TorrowAuthLoginWithOtpOptions) => Promise<TorrowAuthResult>;
345
+ readonly loginWithLink: (options: TorrowAuthLoginWithLinkOptions) => Promise<TorrowAuthResult>;
346
+ readonly fromTokens: (options: TorrowAuthFromTokensOptions) => Promise<TorrowAuthResult>;
347
+ readonly anonymous: (config?: Pick<TokenSessionOptions, "baseUrl" | "fetch" | "defaultHeaders" | "userAgent">) => TorrowClient;
348
+ };
349
+
350
+ export { AuthSessionNotReadyError, AuthTokenSnapshot, LocalStorageTokenStorage, MemoryTokenStorage, OtpAuthHeaderMissingError, OtpCodeRequestLimitExceededError, type OtpGetCodeContext, type OtpSendCodeResult, TokenSession, TokenSessionOptions, TokenSessionStatus, TokenSessionStatusListener, TokenStorage, TorrowAuth, type TorrowAuthAccount, type TorrowAuthConnectOptions, type TorrowAuthFromTokensOptions, type TorrowAuthLoginWithLinkOptions, type TorrowAuthLoginWithOtpOptions, type TorrowAuthResult, type TorrowAuthSessionBase };