itd-api 0.9.3 → 0.10.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/README.md +9 -8
- package/dist/events/index.cjs +2 -2
- package/dist/events/index.d.cts +2 -2
- package/dist/events/index.d.ts +2 -2
- package/dist/events/index.js +2 -2
- package/dist/index.cjs +452 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +229 -49
- package/dist/index.d.ts +229 -49
- package/dist/index.js +448 -44
- package/dist/index.js.map +1 -1
- package/dist/rest/index.cjs +2 -2
- package/dist/rest/index.d.cts +2 -2
- package/dist/rest/index.d.ts +2 -2
- package/dist/rest/index.js +2 -2
- package/dist/shared/{events-BPxBF0gQ.d.cts → events-CZXU16qU.d.cts} +2 -2
- package/dist/shared/{events-BzjI6pn5.js → events-Cipe-Fm9.js} +3 -3
- package/dist/shared/{events-BzjI6pn5.js.map → events-Cipe-Fm9.js.map} +1 -1
- package/dist/shared/{events-DnwfcLxS.d.ts → events-D3CSlpXR.d.ts} +2 -2
- package/dist/shared/{events-C1FudmSe.cjs → events-MtRNe_TF.cjs} +8 -2
- package/dist/shared/{events-C1FudmSe.cjs.map → events-MtRNe_TF.cjs.map} +1 -1
- package/dist/shared/{render-uFW2kDxp.d.ts → render-Cs6AOXbD.d.ts} +2 -2
- package/dist/shared/{render-CtOpp9Pm.js → render-CubTTof_.js} +2 -2
- package/dist/shared/{render-CtOpp9Pm.js.map → render-CubTTof_.js.map} +1 -1
- package/dist/shared/{render-C1F_QWYD.d.cts → render-bZwOk3CC.d.cts} +2 -2
- package/dist/shared/{render-Omy1drH1.cjs → render-dscKp9CC.cjs} +2 -2
- package/dist/shared/{render-Omy1drH1.cjs.map → render-dscKp9CC.cjs.map} +1 -1
- package/dist/shared/{url-3Ts6EEHQ.js → url-CntTEAxu.js} +65 -3
- package/dist/shared/url-CntTEAxu.js.map +1 -0
- package/dist/shared/{url-D6wsXm1T.cjs → url-DgTYZN_v.cjs} +100 -2
- package/dist/shared/url-DgTYZN_v.cjs.map +1 -0
- package/dist/shared/{url-DubjkRR9.d.cts → url-Dum7r_FT.d.cts} +70 -3
- package/dist/shared/{url-DubjkRR9.d.ts → url-Dum7r_FT.d.ts} +70 -3
- package/package.json +2 -1
- package/dist/shared/url-3Ts6EEHQ.js.map +0 -1
- package/dist/shared/url-D6wsXm1T.cjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { A as ViewReason, B as BUCKET_LIMITS, C as ItdErrorCode, D as ReportTargetType, E as ReportReason, F as defineBuiltInOperation, G as pickArray, H as createClientRuntime, I as isBuiltInOperationId, L as operationBucket, M as WallAccess, N as ITD_CATALOG, O as ServiceState, P as OPERATIONS, R as operationMethod, S as InteractionType, T as NotificationType, U as createApiError, V as DEFAULT_RATE_LIMIT_BUCKET, X as RetrySafety, Y as pickString, _ as CaptchaType, at as orderPluginDefinitions, b as FeedTab, c as normalizeNotification, ct as STATUS_SERVICE, d as canonicalNotificationType, dt as isRecord, et as createRequestAbortScope, f as isKnownNotificationType, ft as requireOptionalBoolean, g as CaptchaField, gt as installAsyncDisposeFallback, h as CaptchaChoice, ht as systemClock, it as assertPluginRemovable, j as ViewSource, k as SpanType, lt as resolveRateLimit, m as AttachmentType, n as formatNotificationText, o as passthroughOperation, ot as DEFAULT_BASE_URL, p as AccessType, pt as RateLimitPacing, rt as RequestQueuePool, s as voidOperation, t as resolveNotificationUrl, tt as requestAbortError, ut as LIBRARY_VERSION, v as CommentSort, w as LikesVisibility, x as IncidentKind, y as EventChannelStatus, z as operationRetrySafety } from "./shared/url-CntTEAxu.js";
|
|
2
2
|
import { A as isItdStateError, C as isItdError, D as isItdPhoneVerificationError, E as isItdNotFoundError, O as isItdRateLimitError, S as isItdConflictError, T as isItdForbiddenError, _ as ItdStateError, a as ItdConfigError, b as isItdApiError, c as ItdErrorKind, d as ItdForbiddenError, f as ItdNetworkError, g as ItdServerError, h as ItdRateLimitError, i as ItdAuthError, j as isItdValidationError, k as isItdServerError, l as ItdFileError, m as ItdPhoneVerificationError, n as ItdApiError, o as ItdConflictError, p as ItdNotFoundError, r as ItdApiErrorKind, s as ItdError, t as ItdAbortError, u as ItdFileErrorReason, v as ItdTimeoutError, w as isItdFileError, x as isItdAuthError, y as ItdValidationError } from "./shared/errors-GI10kZxk.js";
|
|
3
|
-
import { c as REFRESH_COOKIE, l as REFRESH_COOKIE_PATH, s as CookieJar, u as isSerializedCookieEntry, v as RuntimeMode, y as createDeviceId } from "./shared/redact-DJV0XM3n.js";
|
|
4
|
-
import { A as utcStampToIso, C as AUDIO_MIME_TYPES, D as isBuilder, E as comment, F as mapPage, I as createFeatureHost, L as ExtensibleOperationCatalog, M as BaseResource, N as PaginationMode, O as createStatusFeature, P as Paginator, S as ALLOWED_MIME_TYPES, T as VIDEO_MIME_TYPES, _ as poll, a as createShopOrderAccessStorage, b as parseHtml, c as ShopOrderStatus, d as isMyProfile, f as fromStream, g as post, h as report, i as MemoryShopOrderAccessStorage, j as NotificationsResource, k as toDate, l as ShopProductCategory, m as createResources, n as renderSpans, o as statusDays, p as fromUrl, r as createShopFeature, s as ShopConsentKind, t as SpanRenderFormat, u as ShopProductStatus, v as autoSpans, w as IMAGE_MIME_TYPES, x as parseMarkdown, y as markup } from "./shared/render-
|
|
3
|
+
import { c as REFRESH_COOKIE, h as joinUrl, l as REFRESH_COOKIE_PATH, s as CookieJar, u as isSerializedCookieEntry, v as RuntimeMode, y as createDeviceId } from "./shared/redact-DJV0XM3n.js";
|
|
4
|
+
import { A as utcStampToIso, C as AUDIO_MIME_TYPES, D as isBuilder, E as comment, F as mapPage, I as createFeatureHost, L as ExtensibleOperationCatalog, M as BaseResource, N as PaginationMode, O as createStatusFeature, P as Paginator, S as ALLOWED_MIME_TYPES, T as VIDEO_MIME_TYPES, _ as poll, a as createShopOrderAccessStorage, b as parseHtml, c as ShopOrderStatus, d as isMyProfile, f as fromStream, g as post, h as report, i as MemoryShopOrderAccessStorage, j as NotificationsResource, k as toDate, l as ShopProductCategory, m as createResources, n as renderSpans, o as statusDays, p as fromUrl, r as createShopFeature, s as ShopConsentKind, t as SpanRenderFormat, u as ShopProductStatus, v as autoSpans, w as IMAGE_MIME_TYPES, x as parseMarkdown, y as markup } from "./shared/render-CubTTof_.js";
|
|
5
5
|
import { c as FileTransferMode } from "./shared/options-i4LrKfqx.js";
|
|
6
|
-
import { _ as
|
|
6
|
+
import { _ as Emitter, a as WebSocketTransport, c as NotificationEventsTransport, d as NotificationUpdateOrigin, f as NotificationUpdateType, g as runEventMiddleware, h as UnauthorizedStreamError, i as createNotificationEventsClient, l as resolveNotificationEventsOptions, m as EventChannel, n as EventRouter, o as EVENT_OPERATIONS, p as createParser, r as NotificationEventsClient, s as NotificationEvents, t as EventComposer, u as setNotificationEventsConnectGuard, v as reportListenerError } from "./shared/events-Cipe-Fm9.js";
|
|
7
7
|
import { a as isEnumerableKeyValueStore, i as createRecordKeyValueStore, o as withCodec, r as createKeyValueStore, s as withNamespace, t as MemoryKeyValueStore } from "./shared/key-value-store-Bt2PSMxY.js";
|
|
8
8
|
import { n as copySession, r as createTokenStorage, t as MemoryTokenStorage } from "./shared/storage-DiDe0PZM.js";
|
|
9
9
|
import { a as scopedTokenStorage, i as isRestorableSession, n as controlledTokenStorage, r as createMultiTokenStorage, t as MemoryMultiTokenStorage } from "./shared/multi-storage-C3Ngtop1.js";
|
|
@@ -31,12 +31,37 @@ const SignInStatus = Object.freeze({
|
|
|
31
31
|
Authenticated: "authenticated",
|
|
32
32
|
OtpRequired: "otp_required"
|
|
33
33
|
});
|
|
34
|
+
/**
|
|
35
|
+
* Поле тела запроса по умолчанию для известных провайдеров.
|
|
36
|
+
*
|
|
37
|
+
* Используется, когда провайдера не спрашивали. Ответ `captchaProvider()` важнее этой таблицы.
|
|
38
|
+
*/
|
|
39
|
+
const CAPTCHA_FIELDS = Object.freeze({
|
|
40
|
+
[CaptchaType.Itd]: CaptchaField.Itd,
|
|
41
|
+
[CaptchaType.Cloudflare]: CaptchaField.Cloudflare
|
|
42
|
+
});
|
|
43
|
+
/** Состояния, которые может вернуть проверка QR-входа. */
|
|
44
|
+
const QrLoginStatus = Object.freeze({
|
|
45
|
+
Pending: "pending",
|
|
46
|
+
Scanned: "scanned",
|
|
47
|
+
CaptchaRequired: "captcha_required",
|
|
48
|
+
Authorized: "authorized",
|
|
49
|
+
Rejected: "rejected"
|
|
50
|
+
});
|
|
51
|
+
/** Состояния, приходящие из потокового наблюдения за QR-входом. */
|
|
52
|
+
const QrLoginStreamStatus = Object.freeze({
|
|
53
|
+
Pending: "pending",
|
|
54
|
+
Scanned: "scanned",
|
|
55
|
+
Approved: "approved",
|
|
56
|
+
Rejected: "rejected"
|
|
57
|
+
});
|
|
34
58
|
function requireString(body, key, message) {
|
|
35
59
|
const value = pickString(body, key);
|
|
36
60
|
if (!value) throw new ItdConfigError(message);
|
|
37
61
|
return value;
|
|
38
62
|
}
|
|
39
63
|
const AUTH_CHECK = passthroughOperation("auth.check");
|
|
64
|
+
const AUTH_CAPTCHA_PROVIDER = passthroughOperation("auth.captchaProvider");
|
|
40
65
|
const AUTH_SIGN_UP = defineBuiltInOperation("auth.signUp", (body) => requireString(body, "flowToken", "Сервер не вернул flowToken при регистрации"));
|
|
41
66
|
const AUTH_SIGN_IN = defineBuiltInOperation("auth.signIn", (body) => {
|
|
42
67
|
const accessToken = pickString(body, "accessToken");
|
|
@@ -58,6 +83,96 @@ const AUTH_CHANGE_PASSWORD = voidOperation("auth.changePassword");
|
|
|
58
83
|
const AUTH_SESSIONS = defineBuiltInOperation("auth.sessions", (body) => pickArray(body, "sessions"));
|
|
59
84
|
const AUTH_REVOKE_SESSION = voidOperation("auth.revokeSession");
|
|
60
85
|
const AUTH_REVOKE_OTHER_SESSIONS = voidOperation("auth.revokeOtherSessions");
|
|
86
|
+
const AUTH_QR_START = passthroughOperation("auth.qrStart");
|
|
87
|
+
const AUTH_QR_CLAIM = passthroughOperation("auth.qrClaim");
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/session/captcha.ts
|
|
90
|
+
function requireCaptchaText(value, field) {
|
|
91
|
+
if (value !== void 0 && (typeof value !== "string" || value.trim() === "")) throw new ItdConfigError(`${field} должен быть непустой строкой`);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Проверяет форму источника токена и приводит его к объекту.
|
|
95
|
+
*
|
|
96
|
+
* Сам объект сохраняется как есть: `getToken` вызывается на нём, чтобы источнику остался
|
|
97
|
+
* доступен собственный `this`.
|
|
98
|
+
*
|
|
99
|
+
* @throws {ItdConfigError} при некорректных значениях
|
|
100
|
+
*/
|
|
101
|
+
function resolveCaptchaSolver(captcha) {
|
|
102
|
+
if (captcha === void 0) return void 0;
|
|
103
|
+
if (typeof captcha === "function") return { getToken: captcha };
|
|
104
|
+
if (!isRecord(captcha)) throw new ItdConfigError("captcha должен быть функцией либо объектом с getToken");
|
|
105
|
+
const { getToken, type, field } = captcha;
|
|
106
|
+
if (typeof getToken !== "function") throw new ItdConfigError("captcha.getToken должен быть функцией");
|
|
107
|
+
requireCaptchaText(type, "captcha.type");
|
|
108
|
+
requireCaptchaText(field, "captcha.field");
|
|
109
|
+
return captcha;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Проверяет форму готового токена капчи.
|
|
113
|
+
*
|
|
114
|
+
* @param path имя опции или аргумента для текста ошибки
|
|
115
|
+
* @throws {ItdConfigError} если токен или его тип не указаны
|
|
116
|
+
*/
|
|
117
|
+
function validateCaptchaToken(captcha, path) {
|
|
118
|
+
if (captcha === void 0) return;
|
|
119
|
+
if (!isRecord(captcha)) throw new ItdConfigError(`${path} должен быть объектом { type, token }`);
|
|
120
|
+
const { type, token, field } = captcha;
|
|
121
|
+
if (typeof token !== "string" || token.trim() === "") throw new ItdConfigError(`${path}.token должен быть непустой строкой`);
|
|
122
|
+
if (typeof type !== "string" || type.trim() === "") throw new ItdConfigError(`${path}.type должен быть непустой строкой`);
|
|
123
|
+
requireCaptchaText(field, `${path}.field`);
|
|
124
|
+
}
|
|
125
|
+
/** Разбирает ответ `GET /api/v1/auth/captcha/provider` для автоматического входа. */
|
|
126
|
+
function readCaptchaProvider(body) {
|
|
127
|
+
const config = isRecord(body) ? body : void 0;
|
|
128
|
+
const provider = config?.provider;
|
|
129
|
+
const field = config?.field;
|
|
130
|
+
if (typeof provider !== "string" || provider.trim() === "" || typeof field !== "string" || field.trim() === "") throw new ItdConfigError("Сервер вернул неподдерживаемую конфигурацию капчи");
|
|
131
|
+
return {
|
|
132
|
+
provider,
|
|
133
|
+
field
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/** Куда класть токен: настройка, иначе умолчание провайдера. */
|
|
137
|
+
function resolveField(type, configured) {
|
|
138
|
+
const field = configured ?? CAPTCHA_FIELDS[type];
|
|
139
|
+
if (field) return field;
|
|
140
|
+
throw new ItdConfigError(`Неизвестно, в каком поле сервер ждёт токен капчи «${type}». Передайте captcha.field либо оставьте captcha.type = ${CaptchaChoice.Auto}, чтобы поле назвал сервер.`);
|
|
141
|
+
}
|
|
142
|
+
/** Спрашивает токен у источника и убеждается, что он непустой. */
|
|
143
|
+
async function requireSolvedToken(solver, type) {
|
|
144
|
+
const token = await solver.getToken(type);
|
|
145
|
+
if (typeof token === "string" && token.trim() !== "") return token;
|
|
146
|
+
throw new ItdConfigError(`Источник капчи не вернул токен для провайдера «${type}». Проверьте captcha.getToken.`);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Готовит фрагмент тела запроса, взяв токен у источника.
|
|
150
|
+
*
|
|
151
|
+
* При `CaptchaChoice.Auto` провайдер спрашивается у сервера — тогда и тип виджета,
|
|
152
|
+
* и имя поля приходят оттуда. Явно названный тип обходится без этого запроса.
|
|
153
|
+
*
|
|
154
|
+
* @param askProvider как спросить активного провайдера; вызывается, только если это нужно
|
|
155
|
+
* @throws {ItdConfigError} если токен получить не удалось
|
|
156
|
+
*/
|
|
157
|
+
async function solveCaptchaBody(solver, askProvider) {
|
|
158
|
+
const choice = solver.type ?? CaptchaChoice.Auto;
|
|
159
|
+
if (choice === CaptchaChoice.Auto) {
|
|
160
|
+
const { provider, field } = await askProvider();
|
|
161
|
+
return { [field]: await requireSolvedToken(solver, provider) };
|
|
162
|
+
}
|
|
163
|
+
return { [resolveField(choice, solver.field)]: await requireSolvedToken(solver, choice) };
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Готовит фрагмент тела из готового токена.
|
|
167
|
+
*
|
|
168
|
+
* Поле берётся из самого токена либо из умолчания провайдера; запроса к серверу нет.
|
|
169
|
+
*
|
|
170
|
+
* @throws {ItdConfigError} если токен или его тип не указаны
|
|
171
|
+
*/
|
|
172
|
+
function captchaBody(captcha) {
|
|
173
|
+
validateCaptchaToken(captcha, "captcha");
|
|
174
|
+
return { [resolveField(captcha.type, captcha.field)]: captcha.token };
|
|
175
|
+
}
|
|
61
176
|
//#endregion
|
|
62
177
|
//#region src/session/jwt.ts
|
|
63
178
|
/**
|
|
@@ -130,11 +245,10 @@ function validateAuth(auth) {
|
|
|
130
245
|
return { ...auth };
|
|
131
246
|
}
|
|
132
247
|
if ("email" in auth || "password" in auth) {
|
|
133
|
-
const { email, password,
|
|
248
|
+
const { email, password, captcha } = auth;
|
|
134
249
|
if (typeof email !== "string" || email.trim() === "") throw new ItdConfigError("auth.email должен быть непустой строкой");
|
|
135
250
|
if (typeof password !== "string" || password === "") throw new ItdConfigError("auth.password должен быть непустой строкой");
|
|
136
|
-
|
|
137
|
-
if (turnstileToken !== void 0 && (typeof turnstileToken !== "string" || turnstileToken.trim() === "")) throw new ItdConfigError("auth.turnstileToken должен быть непустой строкой");
|
|
251
|
+
validateCaptchaToken(captcha, "auth.captcha");
|
|
138
252
|
return { ...auth };
|
|
139
253
|
}
|
|
140
254
|
throw new ItdConfigError("auth не распознан. Ожидается строка с accessToken либо объект { accessToken }, { email, password } или { getToken }");
|
|
@@ -163,6 +277,7 @@ function resolveSessionConfig(options, runtime) {
|
|
|
163
277
|
return {
|
|
164
278
|
...runtime,
|
|
165
279
|
auth: validateAuth(options.auth),
|
|
280
|
+
captcha: resolveCaptchaSolver(options.captcha),
|
|
166
281
|
storage: resolveStorage(options.storage),
|
|
167
282
|
deviceId: options.deviceId,
|
|
168
283
|
autoRefresh: options.autoRefresh ?? true,
|
|
@@ -181,6 +296,7 @@ function createItdAuth(options, deps) {
|
|
|
181
296
|
}
|
|
182
297
|
/** Пути эндпоинтов авторизации. */
|
|
183
298
|
const AUTH_PATHS = {
|
|
299
|
+
captchaProvider: "/api/v1/auth/captcha/provider",
|
|
184
300
|
signUp: "/api/v1/auth/sign-up",
|
|
185
301
|
signIn: "/api/v1/auth/sign-in",
|
|
186
302
|
verifyOtp: "/api/v1/auth/verify-otp",
|
|
@@ -190,7 +306,10 @@ const AUTH_PATHS = {
|
|
|
190
306
|
forgotPassword: "/api/v1/auth/forgot-password",
|
|
191
307
|
resetPassword: "/api/v1/auth/reset-password",
|
|
192
308
|
changePassword: "/api/v1/auth/change-password",
|
|
193
|
-
sessions: "/api/v1/auth/sessions"
|
|
309
|
+
sessions: "/api/v1/auth/sessions",
|
|
310
|
+
qrStart: "/api/v1/auth/qr/start",
|
|
311
|
+
qrClaim: "/api/v1/auth/qr/claim",
|
|
312
|
+
qrStream: "/api/v1/auth/qr/stream"
|
|
194
313
|
};
|
|
195
314
|
/**
|
|
196
315
|
* Публичный ключ Cloudflare Turnstile платформы итд.com.
|
|
@@ -200,14 +319,15 @@ const AUTH_PATHS = {
|
|
|
200
319
|
*
|
|
201
320
|
* Ключ привязан к домену: на чужом origin Cloudflare отказывает виджету с кодом `110200`.
|
|
202
321
|
* Поэтому отрисовать его может только код, выполняемый на самом итд.com. Остальным
|
|
203
|
-
* подходит `@itd-api/
|
|
322
|
+
* подходит `@itd-api/captcha`, готовый токен из другого источника или вовсе вход
|
|
204
323
|
* без капчи — по сохранённой сессии либо по токенам, взятым в браузере.
|
|
205
324
|
*
|
|
206
325
|
* @example
|
|
207
326
|
* ```ts
|
|
208
327
|
* turnstile.render('#captcha', {
|
|
209
328
|
* sitekey: TURNSTILE_SITE_KEY,
|
|
210
|
-
* callback: (
|
|
329
|
+
* callback: (token) =>
|
|
330
|
+
* itd.auth.signIn({ email, password, captcha: { type: CaptchaType.Cloudflare, token } }),
|
|
211
331
|
* });
|
|
212
332
|
* ```
|
|
213
333
|
*/
|
|
@@ -329,6 +449,8 @@ var AuthManager = class {
|
|
|
329
449
|
*/
|
|
330
450
|
#authEpoch = 0;
|
|
331
451
|
#externalReadGeneration = 0;
|
|
452
|
+
/** Разовый токен из `auth.captcha` тратится ровно на один запрос. */
|
|
453
|
+
#configCaptchaSpent = false;
|
|
332
454
|
#disposed = false;
|
|
333
455
|
#persistence = Promise.resolve();
|
|
334
456
|
constructor(config, http, jar, hooks = {}) {
|
|
@@ -625,6 +747,13 @@ var AuthManager = class {
|
|
|
625
747
|
}
|
|
626
748
|
/** Сохраняет токен, полученный извне, — например после подтверждения OTP. */
|
|
627
749
|
async setAccessToken(accessToken, expectedRevision = this.#authEpoch, cookies) {
|
|
750
|
+
return this.#storeAccessToken(accessToken, expectedRevision, cookies, false);
|
|
751
|
+
}
|
|
752
|
+
/** Атомарно принимает токен и cookie завершённого входа как новую сессию. @internal */
|
|
753
|
+
async commitAuthFlow(accessToken, cookies, expectedRevision = this.#authEpoch) {
|
|
754
|
+
return this.#storeAccessToken(accessToken, expectedRevision, cookies, true);
|
|
755
|
+
}
|
|
756
|
+
async #storeAccessToken(accessToken, expectedRevision, cookies, replaceSession) {
|
|
628
757
|
this.#assertActive();
|
|
629
758
|
if (typeof accessToken !== "string" || accessToken.trim() === "") throw new ItdConfigError("accessToken должен быть непустой строкой");
|
|
630
759
|
await this.#loadSession();
|
|
@@ -634,7 +763,7 @@ var AuthManager = class {
|
|
|
634
763
|
this.#invalidateInFlight();
|
|
635
764
|
this.#transitionAuth(accessToken);
|
|
636
765
|
const saved = this.#saveSession({
|
|
637
|
-
...this.#session ?? {},
|
|
766
|
+
...replaceSession ? {} : this.#session ?? {},
|
|
638
767
|
accessToken,
|
|
639
768
|
obtainedAt: this.#config.clock.now()
|
|
640
769
|
});
|
|
@@ -850,28 +979,50 @@ var AuthManager = class {
|
|
|
850
979
|
return promise;
|
|
851
980
|
}
|
|
852
981
|
/**
|
|
853
|
-
*
|
|
982
|
+
* Готовит фрагмент тела с токеном капчи для запроса, который её требует.
|
|
854
983
|
*
|
|
855
|
-
*
|
|
856
|
-
*
|
|
984
|
+
* Источники в порядке убывания приоритета: токен, переданный в сам вызов; разовый токен
|
|
985
|
+
* из `auth.captcha`; опция `captcha`. Когда ни одного нет, фрагмент пустой: нужна ли
|
|
986
|
+
* капча этому запросу, решает сервер.
|
|
987
|
+
*
|
|
988
|
+
* @param explicit готовый токен, переданный в вызов
|
|
989
|
+
* @throws {ItdConfigError} если токен неполный или источник его не вернул
|
|
857
990
|
*/
|
|
858
|
-
async
|
|
859
|
-
if (
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
if (
|
|
864
|
-
|
|
991
|
+
async captchaBody(explicit) {
|
|
992
|
+
if (explicit) return captchaBody(explicit);
|
|
993
|
+
const configured = this.#takeConfiguredCaptcha();
|
|
994
|
+
if (configured) return captchaBody(configured);
|
|
995
|
+
const solver = this.#config.captcha;
|
|
996
|
+
if (solver) return solveCaptchaBody(solver, () => this.#resolveCaptchaProvider());
|
|
997
|
+
this.#config.logger?.debug("Запрос уходит без токена капчи: не передан аргумент captcha и не задана опция captcha");
|
|
998
|
+
return {};
|
|
999
|
+
}
|
|
1000
|
+
/** Отдаёт разовый токен из `auth.captcha`, помечая его потраченным. */
|
|
1001
|
+
#takeConfiguredCaptcha() {
|
|
1002
|
+
if (this.#configCaptchaSpent) return void 0;
|
|
1003
|
+
const auth = this.#config.auth;
|
|
1004
|
+
const captcha = isRecord(auth) ? auth.captcha : void 0;
|
|
1005
|
+
if (!captcha) return void 0;
|
|
1006
|
+
this.#configCaptchaSpent = true;
|
|
1007
|
+
return captcha;
|
|
1008
|
+
}
|
|
1009
|
+
/** Спрашивает у сервера активного провайдера и поле, в котором он ждёт токен. */
|
|
1010
|
+
async #resolveCaptchaProvider() {
|
|
1011
|
+
return readCaptchaProvider(await this.#http.execute(AUTH_CAPTCHA_PROVIDER, {
|
|
1012
|
+
path: AUTH_PATHS.captchaProvider,
|
|
1013
|
+
skipAuth: true,
|
|
1014
|
+
skipAuthRefresh: true
|
|
1015
|
+
}));
|
|
865
1016
|
}
|
|
866
1017
|
async #performSignIn(credentials, revision) {
|
|
867
|
-
const
|
|
1018
|
+
const captcha = await this.captchaBody();
|
|
868
1019
|
const flowCookies = new CookieJar();
|
|
869
1020
|
const result = await this.#http.execute(AUTH_SIGN_IN, {
|
|
870
1021
|
path: AUTH_PATHS.signIn,
|
|
871
1022
|
body: {
|
|
872
1023
|
email: credentials.email,
|
|
873
1024
|
password: credentials.password,
|
|
874
|
-
|
|
1025
|
+
...captcha
|
|
875
1026
|
},
|
|
876
1027
|
skipAuth: true,
|
|
877
1028
|
skipAuthRefresh: true,
|
|
@@ -895,6 +1046,125 @@ var AuthManager = class {
|
|
|
895
1046
|
}
|
|
896
1047
|
};
|
|
897
1048
|
//#endregion
|
|
1049
|
+
//#region src/session/qr-stream.ts
|
|
1050
|
+
const QR_STREAM_STATUSES = new Set(Object.values(QrLoginStreamStatus));
|
|
1051
|
+
function readQrLoginStreamEvent(data) {
|
|
1052
|
+
let value;
|
|
1053
|
+
try {
|
|
1054
|
+
value = JSON.parse(data);
|
|
1055
|
+
} catch {
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
if (!isRecord(value)) return void 0;
|
|
1059
|
+
const event = value;
|
|
1060
|
+
if (typeof event.status !== "string" || !QR_STREAM_STATUSES.has(event.status)) return;
|
|
1061
|
+
return {
|
|
1062
|
+
status: event.status,
|
|
1063
|
+
...typeof event.expiresIn === "number" && Number.isFinite(event.expiresIn) ? { expiresIn: event.expiresIn } : {}
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
/** Читает короткоживущий SSE-поток QR-входа и последовательно доставляет его события. */
|
|
1067
|
+
async function consumeQrLoginStream(body, onEvent, signal) {
|
|
1068
|
+
const reader = body.getReader();
|
|
1069
|
+
const decoder = new TextDecoder();
|
|
1070
|
+
let delivery = Promise.resolve();
|
|
1071
|
+
let completed = false;
|
|
1072
|
+
const cancel = () => {
|
|
1073
|
+
reader.cancel(signal?.reason).catch(() => {});
|
|
1074
|
+
};
|
|
1075
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
1076
|
+
const parser = createParser({ onEvent(message) {
|
|
1077
|
+
const event = readQrLoginStreamEvent(message.data);
|
|
1078
|
+
if (!event) return;
|
|
1079
|
+
delivery = delivery.then(() => onEvent(event));
|
|
1080
|
+
} });
|
|
1081
|
+
try {
|
|
1082
|
+
for (;;) {
|
|
1083
|
+
const { done, value } = await reader.read();
|
|
1084
|
+
if (done) break;
|
|
1085
|
+
parser.feed(decoder.decode(value, { stream: true }));
|
|
1086
|
+
await delivery;
|
|
1087
|
+
}
|
|
1088
|
+
parser.feed(decoder.decode());
|
|
1089
|
+
await delivery;
|
|
1090
|
+
if (signal?.aborted) throw signal.reason;
|
|
1091
|
+
completed = true;
|
|
1092
|
+
} finally {
|
|
1093
|
+
signal?.removeEventListener("abort", cancel);
|
|
1094
|
+
if (!completed) await reader.cancel().catch(() => {});
|
|
1095
|
+
reader.releaseLock?.();
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
/** Открывает изолированный HTTP/SSE-канал QR-входа. @internal */
|
|
1099
|
+
async function openQrLoginStream(connection, cookies, input, onEvent, signal) {
|
|
1100
|
+
connection.assertActive?.("открыть QR-поток");
|
|
1101
|
+
const path = AUTH_PATHS.qrStream;
|
|
1102
|
+
const method = "POST";
|
|
1103
|
+
const url = joinUrl(connection.baseUrl, path);
|
|
1104
|
+
const abort = createRequestAbortScope(signal, connection.signal, 0, connection.clock);
|
|
1105
|
+
try {
|
|
1106
|
+
const headers = await connection.baseHeaders(url);
|
|
1107
|
+
headers.delete("Authorization");
|
|
1108
|
+
headers.delete("Cookie");
|
|
1109
|
+
headers.set("Accept", "text/event-stream");
|
|
1110
|
+
headers.set("Content-Type", "application/json");
|
|
1111
|
+
const cookie = cookies.getHeader(url);
|
|
1112
|
+
if (cookie) headers.set("Cookie", cookie);
|
|
1113
|
+
let response;
|
|
1114
|
+
try {
|
|
1115
|
+
response = await connection.fetch(url, {
|
|
1116
|
+
method,
|
|
1117
|
+
headers,
|
|
1118
|
+
body: JSON.stringify(input),
|
|
1119
|
+
credentials: "include",
|
|
1120
|
+
signal: abort.signal
|
|
1121
|
+
});
|
|
1122
|
+
} catch (error) {
|
|
1123
|
+
const failure = new ItdNetworkError(`Не удалось открыть QR-поток: ${String(error)}`, {
|
|
1124
|
+
method,
|
|
1125
|
+
path,
|
|
1126
|
+
cause: error
|
|
1127
|
+
});
|
|
1128
|
+
throw requestAbortError(abort, {
|
|
1129
|
+
timeout: 0,
|
|
1130
|
+
method,
|
|
1131
|
+
path
|
|
1132
|
+
}, failure);
|
|
1133
|
+
}
|
|
1134
|
+
cookies.setFromResponse(response.url || url, response);
|
|
1135
|
+
if (!response.ok) {
|
|
1136
|
+
const text = await response.text();
|
|
1137
|
+
let body = text;
|
|
1138
|
+
try {
|
|
1139
|
+
body = JSON.parse(text);
|
|
1140
|
+
} catch {}
|
|
1141
|
+
throw createApiError({
|
|
1142
|
+
method,
|
|
1143
|
+
path,
|
|
1144
|
+
status: response.status,
|
|
1145
|
+
statusText: response.statusText,
|
|
1146
|
+
headers: response.headers,
|
|
1147
|
+
response,
|
|
1148
|
+
body,
|
|
1149
|
+
now: connection.clock.now()
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
if (!response.body) throw new ItdNetworkError("Сервер не вернул тело QR-потока", {
|
|
1153
|
+
method,
|
|
1154
|
+
path
|
|
1155
|
+
});
|
|
1156
|
+
await consumeQrLoginStream(response.body, onEvent, abort.signal);
|
|
1157
|
+
} catch (error) {
|
|
1158
|
+
throw requestAbortError(abort, {
|
|
1159
|
+
timeout: 0,
|
|
1160
|
+
method,
|
|
1161
|
+
path
|
|
1162
|
+
}, error);
|
|
1163
|
+
} finally {
|
|
1164
|
+
abort.cleanup();
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
//#endregion
|
|
898
1168
|
//#region src/resources/auth.ts
|
|
899
1169
|
/**
|
|
900
1170
|
* Результат входа.
|
|
@@ -909,9 +1179,36 @@ var AuthManager = class {
|
|
|
909
1179
|
*/
|
|
910
1180
|
var AuthResource = class extends BaseResource {
|
|
911
1181
|
#auth;
|
|
1182
|
+
#connection;
|
|
1183
|
+
#qrFlows = /* @__PURE__ */ new Map();
|
|
912
1184
|
constructor(http, deps) {
|
|
913
1185
|
super(http);
|
|
914
1186
|
this.#auth = deps.auth;
|
|
1187
|
+
this.#connection = deps.connection;
|
|
1188
|
+
}
|
|
1189
|
+
#pruneQrFlows() {
|
|
1190
|
+
const now = this.#connection.clock.now();
|
|
1191
|
+
for (const [qrId, flow] of this.#qrFlows) if (flow.expiresAt !== void 0 && flow.expiresAt <= now) this.#qrFlows.delete(qrId);
|
|
1192
|
+
}
|
|
1193
|
+
#qrFlow(qrId) {
|
|
1194
|
+
this.#pruneQrFlows();
|
|
1195
|
+
const existing = this.#qrFlows.get(qrId);
|
|
1196
|
+
if (existing) return existing;
|
|
1197
|
+
const flow = {
|
|
1198
|
+
cookies: this.#auth.createCookieFlow(false),
|
|
1199
|
+
expiresAt: void 0,
|
|
1200
|
+
captchaRequired: false
|
|
1201
|
+
};
|
|
1202
|
+
this.#qrFlows.set(qrId, flow);
|
|
1203
|
+
return flow;
|
|
1204
|
+
}
|
|
1205
|
+
#touchQrFlow(flow, expiresIn) {
|
|
1206
|
+
if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn)) return;
|
|
1207
|
+
const expiresAt = this.#connection.clock.now() + Math.max(0, expiresIn) * 1e3;
|
|
1208
|
+
flow.expiresAt = flow.expiresAt === void 0 ? expiresAt : Math.min(flow.expiresAt, expiresAt);
|
|
1209
|
+
}
|
|
1210
|
+
#forgetTerminalQrError(qrId, error) {
|
|
1211
|
+
if (isItdApiError(error) && error.hasCode("QR_EXPIRED", "QR_ALREADY_USED", "QR_TOKEN_MISMATCH")) this.#qrFlows.delete(qrId);
|
|
915
1212
|
}
|
|
916
1213
|
/**
|
|
917
1214
|
* Проверяет состояние авторизации и возвращает текущего пользователя.
|
|
@@ -925,14 +1222,109 @@ var AuthResource = class extends BaseResource {
|
|
|
925
1222
|
});
|
|
926
1223
|
}
|
|
927
1224
|
/**
|
|
1225
|
+
* Узнаёт активного провайдера капчи и поле токена.
|
|
1226
|
+
*
|
|
1227
|
+
* Сервер может переключить провайдера или переименовать поле без выпуска новой версии SDK.
|
|
1228
|
+
* Автоматическому входу этот вызов не нужен — клиент делает его сам.
|
|
1229
|
+
*/
|
|
1230
|
+
captchaProvider(options = {}) {
|
|
1231
|
+
return this.http.execute(AUTH_CAPTCHA_PROVIDER, {
|
|
1232
|
+
path: AUTH_PATHS.captchaProvider,
|
|
1233
|
+
skipAuth: true,
|
|
1234
|
+
skipAuthRefresh: true,
|
|
1235
|
+
...options
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
/** Создаёт короткоживущую сессию QR-входа. */
|
|
1239
|
+
async startQrLogin(options = {}) {
|
|
1240
|
+
const cookies = this.#auth.createCookieFlow(false);
|
|
1241
|
+
const result = await this.http.execute(AUTH_QR_START, {
|
|
1242
|
+
path: AUTH_PATHS.qrStart,
|
|
1243
|
+
skipAuth: true,
|
|
1244
|
+
skipAuthRefresh: true,
|
|
1245
|
+
cookieJar: cookies,
|
|
1246
|
+
...options
|
|
1247
|
+
});
|
|
1248
|
+
if (typeof result.qrId === "string" && result.qrId !== "") {
|
|
1249
|
+
const flow = {
|
|
1250
|
+
cookies,
|
|
1251
|
+
expiresAt: void 0,
|
|
1252
|
+
captchaRequired: false
|
|
1253
|
+
};
|
|
1254
|
+
this.#touchQrFlow(flow, result.expiresIn);
|
|
1255
|
+
this.#qrFlows.set(result.qrId, flow);
|
|
1256
|
+
}
|
|
1257
|
+
return result;
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Проверяет QR-сессию и завершает вход после подтверждения на другом устройстве.
|
|
1261
|
+
*
|
|
1262
|
+
* До подтверждения возвращает промежуточный статус. Токен капчи добывается, только когда
|
|
1263
|
+
* сервер о ней попросил — статусом `captcha_required` на прошлой проверке, — либо когда
|
|
1264
|
+
* передан в сам вызов. Полученный `accessToken` автоматически сохраняется в клиенте.
|
|
1265
|
+
*/
|
|
1266
|
+
async claimQrLogin(input, options = {}) {
|
|
1267
|
+
const revision = this.#auth.revision();
|
|
1268
|
+
const flow = this.#qrFlow(input.qrId);
|
|
1269
|
+
try {
|
|
1270
|
+
const { captcha, ...rest } = input;
|
|
1271
|
+
const needsCaptcha = captcha !== void 0 || flow.captchaRequired;
|
|
1272
|
+
const result = await this.http.execute(AUTH_QR_CLAIM, {
|
|
1273
|
+
path: AUTH_PATHS.qrClaim,
|
|
1274
|
+
body: {
|
|
1275
|
+
...rest,
|
|
1276
|
+
...needsCaptcha ? await this.#auth.captchaBody(captcha) : {}
|
|
1277
|
+
},
|
|
1278
|
+
skipAuth: true,
|
|
1279
|
+
skipAuthRefresh: true,
|
|
1280
|
+
cookieJar: flow.cookies,
|
|
1281
|
+
...options
|
|
1282
|
+
});
|
|
1283
|
+
this.#touchQrFlow(flow, result.expiresIn);
|
|
1284
|
+
if (result.status === QrLoginStatus.CaptchaRequired) flow.captchaRequired = true;
|
|
1285
|
+
if (result.status === QrLoginStatus.Authorized) {
|
|
1286
|
+
await this.#auth.commitAuthFlow(result.accessToken, flow.cookies, revision);
|
|
1287
|
+
this.#qrFlows.delete(input.qrId);
|
|
1288
|
+
} else if (result.status === QrLoginStatus.Rejected) this.#qrFlows.delete(input.qrId);
|
|
1289
|
+
return result;
|
|
1290
|
+
} catch (error) {
|
|
1291
|
+
this.#forgetTerminalQrError(input.qrId, error);
|
|
1292
|
+
throw error;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
/**
|
|
1296
|
+
* Слушает состояния QR-входа до закрытия ответа или отмены `signal`.
|
|
1297
|
+
*
|
|
1298
|
+
* Событие `approved` означает, что подтверждение завершено: после него вызовите
|
|
1299
|
+
* {@link claimQrLogin}, чтобы получить и сохранить access token.
|
|
1300
|
+
*/
|
|
1301
|
+
async streamQrLogin(input, onEvent, options = {}) {
|
|
1302
|
+
if (typeof onEvent !== "function") throw new ItdConfigError("onEvent QR-потока должен быть функцией");
|
|
1303
|
+
const flow = this.#qrFlow(input.qrId);
|
|
1304
|
+
try {
|
|
1305
|
+
await openQrLoginStream(this.#connection, flow.cookies, input, async (event) => {
|
|
1306
|
+
this.#touchQrFlow(flow, event.expiresIn);
|
|
1307
|
+
if (event.status === QrLoginStreamStatus.Rejected) this.#qrFlows.delete(input.qrId);
|
|
1308
|
+
await onEvent(event);
|
|
1309
|
+
}, options.signal);
|
|
1310
|
+
} catch (error) {
|
|
1311
|
+
this.#forgetTerminalQrError(input.qrId, error);
|
|
1312
|
+
throw error;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
/**
|
|
928
1316
|
* Регистрирует аккаунт и запускает подтверждение по коду.
|
|
929
1317
|
*
|
|
930
1318
|
* @returns `flowToken`, который нужно передать в {@link verifyOtp}
|
|
931
1319
|
*/
|
|
932
|
-
signUp(credentials, options = {}) {
|
|
1320
|
+
async signUp(credentials, options = {}) {
|
|
1321
|
+
const { captcha, ...rest } = credentials;
|
|
933
1322
|
return this.http.execute(AUTH_SIGN_UP, {
|
|
934
1323
|
path: AUTH_PATHS.signUp,
|
|
935
|
-
body:
|
|
1324
|
+
body: {
|
|
1325
|
+
...rest,
|
|
1326
|
+
...await this.#auth.captchaBody(captcha)
|
|
1327
|
+
},
|
|
936
1328
|
skipAuth: true,
|
|
937
1329
|
skipAuthRefresh: true,
|
|
938
1330
|
...options
|
|
@@ -946,20 +1338,25 @@ var AuthResource = class extends BaseResource {
|
|
|
946
1338
|
*
|
|
947
1339
|
* При успешном входе токен сохраняется в клиенте автоматически.
|
|
948
1340
|
*
|
|
949
|
-
* @param credentials email, пароль и
|
|
1341
|
+
* @param credentials email, пароль и токен капчи — см. {@link CredentialsWithCaptcha}
|
|
950
1342
|
*/
|
|
951
1343
|
async signIn(credentials, options = {}) {
|
|
1344
|
+
const { captcha, ...rest } = credentials;
|
|
952
1345
|
const revision = this.#auth.revision();
|
|
953
1346
|
const cookies = this.#auth.createCookieFlow(false);
|
|
1347
|
+
const body = {
|
|
1348
|
+
...rest,
|
|
1349
|
+
...await this.#auth.captchaBody(captcha)
|
|
1350
|
+
};
|
|
954
1351
|
const result = await this.http.execute(AUTH_SIGN_IN, {
|
|
955
1352
|
path: AUTH_PATHS.signIn,
|
|
956
|
-
body
|
|
1353
|
+
body,
|
|
957
1354
|
skipAuth: true,
|
|
958
1355
|
skipAuthRefresh: true,
|
|
959
1356
|
cookieJar: cookies,
|
|
960
1357
|
...options
|
|
961
1358
|
});
|
|
962
|
-
if (result.status === SignInStatus.Authenticated) await this.#auth.
|
|
1359
|
+
if (result.status === SignInStatus.Authenticated) await this.#auth.commitAuthFlow(result.accessToken, cookies, revision);
|
|
963
1360
|
else this.#auth.commitCookieFlow(cookies, revision);
|
|
964
1361
|
return result;
|
|
965
1362
|
}
|
|
@@ -979,7 +1376,7 @@ var AuthResource = class extends BaseResource {
|
|
|
979
1376
|
cookieJar: cookies,
|
|
980
1377
|
...options
|
|
981
1378
|
});
|
|
982
|
-
await this.#auth.
|
|
1379
|
+
await this.#auth.commitAuthFlow(accessToken, cookies, revision);
|
|
983
1380
|
return accessToken;
|
|
984
1381
|
}
|
|
985
1382
|
/** Отправляет код подтверждения повторно. */
|
|
@@ -1081,10 +1478,13 @@ var AuthResource = class extends BaseResource {
|
|
|
1081
1478
|
*
|
|
1082
1479
|
* @returns `flowToken`, который нужно передать в {@link resetPassword}
|
|
1083
1480
|
*/
|
|
1084
|
-
forgotPassword(input, options = {}) {
|
|
1481
|
+
async forgotPassword(input, options = {}) {
|
|
1085
1482
|
return this.http.execute(AUTH_FORGOT_PASSWORD, {
|
|
1086
1483
|
path: AUTH_PATHS.forgotPassword,
|
|
1087
|
-
body:
|
|
1484
|
+
body: {
|
|
1485
|
+
email: input.email,
|
|
1486
|
+
...await this.#auth.captchaBody(input.captcha)
|
|
1487
|
+
},
|
|
1088
1488
|
skipAuth: true,
|
|
1089
1489
|
skipAuthRefresh: true,
|
|
1090
1490
|
...options
|
|
@@ -1115,23 +1515,21 @@ var AuthResource = class extends BaseResource {
|
|
|
1115
1515
|
* ```ts
|
|
1116
1516
|
* await itd.auth.resetPasswordWithOtp({
|
|
1117
1517
|
* email,
|
|
1118
|
-
*
|
|
1518
|
+
* captcha: { type: provider, token, field },
|
|
1119
1519
|
* newPassword,
|
|
1120
1520
|
* getOtp: () => rl.question('Код из письма: '),
|
|
1121
1521
|
* });
|
|
1122
1522
|
* ```
|
|
1123
1523
|
*/
|
|
1124
1524
|
async resetPasswordWithOtp(input, options = {}) {
|
|
1125
|
-
const
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
}, options);
|
|
1129
|
-
const otp = await input.getOtp();
|
|
1525
|
+
const { getOtp, newPassword, ...request } = input;
|
|
1526
|
+
const flowToken = await this.forgotPassword(request, options);
|
|
1527
|
+
const otp = await getOtp();
|
|
1130
1528
|
await this.resetPassword({
|
|
1131
1529
|
email: input.email,
|
|
1132
1530
|
otp,
|
|
1133
1531
|
flowToken,
|
|
1134
|
-
newPassword
|
|
1532
|
+
newPassword
|
|
1135
1533
|
}, options);
|
|
1136
1534
|
}
|
|
1137
1535
|
/**
|
|
@@ -1220,8 +1618,9 @@ function createManagedClient(options, internals) {
|
|
|
1220
1618
|
* const itd = new ItdClient({
|
|
1221
1619
|
* // `auth` не обязателен: когда хранилище уже содержит сессию, токен берётся оттуда,
|
|
1222
1620
|
* // а истёкший продлевается сам. Здесь он нужен на первый запуск.
|
|
1223
|
-
*
|
|
1224
|
-
*
|
|
1621
|
+
* auth: { email, password },
|
|
1622
|
+
* // Капчу входа решает пакет @itd-api/captcha — см. CaptchaSolver.
|
|
1623
|
+
* captcha: createCaptchaSolver(),
|
|
1225
1624
|
* storage: new FileTokenStorage('./.itd-session.json'),
|
|
1226
1625
|
* rateLimit: { concurrency: 4, rps: 8 },
|
|
1227
1626
|
* });
|
|
@@ -1242,7 +1641,10 @@ var ItdClient = class ItdClient {
|
|
|
1242
1641
|
#auth;
|
|
1243
1642
|
/** Авторизация, сессии и пароли. */
|
|
1244
1643
|
get auth() {
|
|
1245
|
-
this.#auth ??= new AuthResource(this.#runtime.http, {
|
|
1644
|
+
this.#auth ??= new AuthResource(this.#runtime.http, {
|
|
1645
|
+
auth: this.#runtime.auth,
|
|
1646
|
+
connection: this.#runtime.connection()
|
|
1647
|
+
});
|
|
1246
1648
|
return this.#auth;
|
|
1247
1649
|
}
|
|
1248
1650
|
/** Профили, подписки, блокировки, приватность. */
|
|
@@ -1732,6 +2134,8 @@ function assertAccountsActive(accounts, action) {
|
|
|
1732
2134
|
*
|
|
1733
2135
|
* await using accounts = new ItdAccounts({
|
|
1734
2136
|
* storage: new FileMultiTokenStorage('./.itd-sessions.json'),
|
|
2137
|
+
* // Общие опции достаются каждому аккаунту: один источник токена капчи на всех.
|
|
2138
|
+
* captcha: createCaptchaSolver(),
|
|
1735
2139
|
* rateLimit: { concurrency: 4 },
|
|
1736
2140
|
* });
|
|
1737
2141
|
*
|
|
@@ -1739,7 +2143,7 @@ function assertAccountsActive(accounts, action) {
|
|
|
1739
2143
|
* await accounts.restore();
|
|
1740
2144
|
*
|
|
1741
2145
|
* if (!accounts.has('kiow')) {
|
|
1742
|
-
* accounts.addAccount('kiow', { auth: { email, password
|
|
2146
|
+
* accounts.addAccount('kiow', { auth: { email, password } });
|
|
1743
2147
|
* }
|
|
1744
2148
|
*
|
|
1745
2149
|
* await accounts.account('kiow').posts.create({ content: 'привет' });
|
|
@@ -1818,7 +2222,7 @@ var ItdAccounts = class ItdAccounts {
|
|
|
1818
2222
|
*
|
|
1819
2223
|
* @example
|
|
1820
2224
|
* ```ts
|
|
1821
|
-
* accounts.addAccount('bot', { auth: { email, password
|
|
2225
|
+
* accounts.addAccount('bot', { auth: { email, password } });
|
|
1822
2226
|
* accounts.addAccount('reader', { auth: '<accessToken>' });
|
|
1823
2227
|
* accounts.addAccount('через-прокси', { fetch: proxyFetch('socks5://…') });
|
|
1824
2228
|
* ```
|
|
@@ -2151,6 +2555,6 @@ function createAccounts(options = {}) {
|
|
|
2151
2555
|
return new ItdAccounts(options);
|
|
2152
2556
|
}
|
|
2153
2557
|
//#endregion
|
|
2154
|
-
export { ALLOWED_MIME_TYPES, AUDIO_MIME_TYPES, AccessType, AttachmentType, BUCKET_LIMITS, CommentSort, DEFAULT_BASE_URL, DEFAULT_RATE_LIMIT_BUCKET, EventChannel, EventChannelStatus, EventComposer, EventRouter, FeedTab, FileTransferMode, IMAGE_MIME_TYPES, IncidentKind, InteractionType, ItdAbortError, ItdAccounts, ItdApiError, ItdApiErrorKind, ItdAuthError, ItdClient, ItdConfigError, ItdConflictError, ItdError, ItdErrorCode, ItdErrorKind, ItdFileError, ItdFileErrorReason, ItdForbiddenError, ItdNetworkError, ItdNotFoundError, ItdPhoneVerificationError, ItdRateLimitError, ItdServerError, ItdStateError, ItdTimeoutError, ItdValidationError, LIBRARY_VERSION, LikesVisibility, MemoryKeyValueStore, MemoryMultiTokenStorage, MemoryShopOrderAccessStorage, MemoryTokenStorage, NotificationEvents, NotificationEventsClient, NotificationEventsTransport, NotificationType, NotificationUpdateOrigin, NotificationUpdateType, OPERATIONS, PaginationMode, Paginator, RateLimitPacing, ReportReason, ReportTargetType, RetrySafety, RuntimeMode, STATUS_SERVICE, ServiceState, ShopConsentKind, ShopOrderStatus, ShopProductCategory, ShopProductStatus, SignInStatus, SpanRenderFormat, SpanType, TURNSTILE_SITE_KEY, UnauthorizedStreamError, VIDEO_MIME_TYPES, ViewReason, ViewSource, WallAccess, WebSocketTransport, autoSpans, canonicalNotificationType, comment, createAccounts, createClient, createKeyValueStore, createMultiTokenStorage, createNotificationEventsClient, createRecordKeyValueStore, createShopFeature, createShopOrderAccessStorage, createTokenStorage, formatNotificationText, fromStream, fromUrl, isBuilder, isBuiltInOperationId, isEnumerableKeyValueStore, isItdApiError, isItdAuthError, isItdConflictError, isItdError, isItdFileError, isItdForbiddenError, isItdNotFoundError, isItdPhoneVerificationError, isItdRateLimitError, isItdServerError, isItdStateError, isItdValidationError, isKnownNotificationType, isMyProfile, mapPage, markup, normalizeNotification, operationBucket, operationMethod, operationRetrySafety, parseHtml, parseMarkdown, poll, post, renderSpans, report, resolveNotificationUrl, runEventMiddleware, scopedTokenStorage, statusDays, systemClock, toDate, utcStampToIso, withCodec, withNamespace };
|
|
2558
|
+
export { ALLOWED_MIME_TYPES, AUDIO_MIME_TYPES, AccessType, AttachmentType, BUCKET_LIMITS, CAPTCHA_FIELDS, CaptchaChoice, CaptchaField, CaptchaType, CommentSort, DEFAULT_BASE_URL, DEFAULT_RATE_LIMIT_BUCKET, EventChannel, EventChannelStatus, EventComposer, EventRouter, FeedTab, FileTransferMode, IMAGE_MIME_TYPES, IncidentKind, InteractionType, ItdAbortError, ItdAccounts, ItdApiError, ItdApiErrorKind, ItdAuthError, ItdClient, ItdConfigError, ItdConflictError, ItdError, ItdErrorCode, ItdErrorKind, ItdFileError, ItdFileErrorReason, ItdForbiddenError, ItdNetworkError, ItdNotFoundError, ItdPhoneVerificationError, ItdRateLimitError, ItdServerError, ItdStateError, ItdTimeoutError, ItdValidationError, LIBRARY_VERSION, LikesVisibility, MemoryKeyValueStore, MemoryMultiTokenStorage, MemoryShopOrderAccessStorage, MemoryTokenStorage, NotificationEvents, NotificationEventsClient, NotificationEventsTransport, NotificationType, NotificationUpdateOrigin, NotificationUpdateType, OPERATIONS, PaginationMode, Paginator, QrLoginStatus, QrLoginStreamStatus, RateLimitPacing, ReportReason, ReportTargetType, RetrySafety, RuntimeMode, STATUS_SERVICE, ServiceState, ShopConsentKind, ShopOrderStatus, ShopProductCategory, ShopProductStatus, SignInStatus, SpanRenderFormat, SpanType, TURNSTILE_SITE_KEY, UnauthorizedStreamError, VIDEO_MIME_TYPES, ViewReason, ViewSource, WallAccess, WebSocketTransport, autoSpans, canonicalNotificationType, comment, createAccounts, createClient, createKeyValueStore, createMultiTokenStorage, createNotificationEventsClient, createRecordKeyValueStore, createShopFeature, createShopOrderAccessStorage, createTokenStorage, formatNotificationText, fromStream, fromUrl, isBuilder, isBuiltInOperationId, isEnumerableKeyValueStore, isItdApiError, isItdAuthError, isItdConflictError, isItdError, isItdFileError, isItdForbiddenError, isItdNotFoundError, isItdPhoneVerificationError, isItdRateLimitError, isItdServerError, isItdStateError, isItdValidationError, isKnownNotificationType, isMyProfile, mapPage, markup, normalizeNotification, operationBucket, operationMethod, operationRetrySafety, parseHtml, parseMarkdown, poll, post, renderSpans, report, resolveNotificationUrl, runEventMiddleware, scopedTokenStorage, statusDays, systemClock, toDate, utcStampToIso, withCodec, withNamespace };
|
|
2155
2559
|
|
|
2156
2560
|
//# sourceMappingURL=index.js.map
|