itd-api 0.10.0 → 0.10.2

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.
Files changed (33) hide show
  1. package/dist/events/index.cjs +2 -2
  2. package/dist/events/index.d.cts +2 -2
  3. package/dist/events/index.d.ts +2 -2
  4. package/dist/events/index.js +2 -2
  5. package/dist/index.cjs +175 -59
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.cts +216 -132
  8. package/dist/index.d.ts +216 -132
  9. package/dist/index.js +175 -60
  10. package/dist/index.js.map +1 -1
  11. package/dist/rest/index.cjs +2 -2
  12. package/dist/rest/index.d.cts +2 -2
  13. package/dist/rest/index.d.ts +2 -2
  14. package/dist/rest/index.js +2 -2
  15. package/dist/shared/{events-B0Wtn4cF.js → events-0I_Pixnq.js} +2 -2
  16. package/dist/shared/{events-B0Wtn4cF.js.map → events-0I_Pixnq.js.map} +1 -1
  17. package/dist/shared/{events-BB791aPk.cjs → events-8OLqvKwQ.cjs} +2 -2
  18. package/dist/shared/{events-BB791aPk.cjs.map → events-8OLqvKwQ.cjs.map} +1 -1
  19. package/dist/shared/{events-BNGjJVI3.d.cts → events-Cr1Mc2Gl.d.cts} +2 -2
  20. package/dist/shared/{events-CJO6q93Z.d.ts → events-DXooQkyB.d.ts} +2 -2
  21. package/dist/shared/{render-C1A57W-5.d.cts → render-BxPM234w.d.cts} +29 -3
  22. package/dist/shared/{render-BimHjOhw.cjs → render-CnpKoGO8.cjs} +2 -2
  23. package/dist/shared/{render-BimHjOhw.cjs.map → render-CnpKoGO8.cjs.map} +1 -1
  24. package/dist/shared/{render-DC5nvqcP.d.ts → render-D69bOsxu.d.ts} +29 -3
  25. package/dist/shared/{render-CkhvH_yh.js → render-GQw5m1-o.js} +2 -2
  26. package/dist/shared/{render-CkhvH_yh.js.map → render-GQw5m1-o.js.map} +1 -1
  27. package/dist/shared/{url-CDVgMIq5.cjs → url-BnaioEsz.cjs} +44 -6
  28. package/dist/shared/{url-CDVgMIq5.cjs.map → url-BnaioEsz.cjs.map} +1 -1
  29. package/dist/shared/{url-BA5hEwj2.js → url-D0KJ5pws.js} +39 -7
  30. package/dist/shared/{url-BA5hEwj2.js.map → url-D0KJ5pws.js.map} +1 -1
  31. package/dist/shared/{url-DkMueFAT.d.ts → url-Qv7smwHP.d.cts} +40 -7
  32. package/dist/shared/{url-DkMueFAT.d.cts → url-Qv7smwHP.d.ts} +40 -7
  33. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
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-BA5hEwj2.js";
1
+ import { A as SpanType, B as operationRetrySafety, C as ItdErrorCode, D as ReportReason, E as QrLocationPrecision, F as OPERATIONS, H as DEFAULT_RATE_LIMIT_BUCKET, I as defineBuiltInOperation, K as pickArray, L as isBuiltInOperationId, M as ViewSource, N as WallAccess, O as ReportTargetType, P as ITD_CATALOG, R as operationBucket, S as InteractionType, T as NotificationType, U as createClientRuntime, V as BUCKET_LIMITS, W as createApiError, X as pickString, Z as RetrySafety, _ as CaptchaType, _t as installAsyncDisposeFallback, at as assertPluginRemovable, b as FeedTab, c as normalizeNotification, d as canonicalNotificationType, dt as LIBRARY_VERSION, f as isKnownNotificationType, ft as isRecord, g as CaptchaField, gt as systemClock, h as CaptchaChoice, it as RequestQueuePool, j as ViewReason, k as ServiceState, lt as STATUS_SERVICE, m as AttachmentType, mt as RateLimitPacing, n as formatNotificationText, nt as requestAbortError, o as passthroughOperation, ot as orderPluginDefinitions, p as AccessType, pt as requireOptionalBoolean, s as voidOperation, st as DEFAULT_BASE_URL, t as resolveNotificationUrl, tt as createRequestAbortScope, ut as resolveRateLimit, v as CommentSort, w as LikesVisibility, x as IncidentKind, y as EventChannelStatus, z as operationMethod } from "./shared/url-D0KJ5pws.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
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-CkhvH_yh.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-GQw5m1-o.js";
5
5
  import { c as FileTransferMode } from "./shared/options-i4LrKfqx.js";
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-B0Wtn4cF.js";
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-0I_Pixnq.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";
@@ -62,6 +62,7 @@ function requireString(body, key, message) {
62
62
  }
63
63
  const AUTH_CHECK = passthroughOperation("auth.check");
64
64
  const AUTH_CAPTCHA_PROVIDER = passthroughOperation("auth.captchaProvider");
65
+ const AUTH_CAPTCHA_PAGE = passthroughOperation("auth.captchaPage");
65
66
  const AUTH_SIGN_UP = defineBuiltInOperation("auth.signUp", (body) => requireString(body, "flowToken", "Сервер не вернул flowToken при регистрации"));
66
67
  const AUTH_SIGN_IN = defineBuiltInOperation("auth.signIn", (body) => {
67
68
  const accessToken = pickString(body, "accessToken");
@@ -85,27 +86,45 @@ const AUTH_REVOKE_SESSION = voidOperation("auth.revokeSession");
85
86
  const AUTH_REVOKE_OTHER_SESSIONS = voidOperation("auth.revokeOtherSessions");
86
87
  const AUTH_QR_START = passthroughOperation("auth.qrStart");
87
88
  const AUTH_QR_CLAIM = passthroughOperation("auth.qrClaim");
89
+ const AUTH_QR_SCAN = passthroughOperation("auth.qrScan");
90
+ const AUTH_QR_APPROVE = voidOperation("auth.qrApprove");
91
+ const AUTH_QR_REJECT = voidOperation("auth.qrReject");
88
92
  //#endregion
89
93
  //#region src/session/captcha.ts
90
94
  function requireCaptchaText(value, field) {
91
95
  if (value !== void 0 && (typeof value !== "string" || value.trim() === "")) throw new ItdConfigError(`${field} должен быть непустой строкой`);
92
96
  }
93
97
  /**
94
- * Проверяет форму блока `auth.captcha`.
98
+ * Проверяет форму источника токена и приводит его к объекту.
95
99
  *
96
- * Отсутствие источника токена ошибкой не считается: до входа по паролю дело может и не
97
- * дойти. О нехватке сообщит {@link resolveCaptchaBody} в момент входа.
100
+ * Сам объект сохраняется как есть: `getToken` вызывается на нём, чтобы источнику остался
101
+ * доступен собственный `this`.
98
102
  *
99
103
  * @throws {ItdConfigError} при некорректных значениях
100
104
  */
101
- function validateCaptchaOptions(captcha) {
105
+ function resolveCaptchaSolver(captcha) {
106
+ if (captcha === void 0) return void 0;
107
+ if (typeof captcha === "function") return { getToken: captcha };
108
+ if (!isRecord(captcha)) throw new ItdConfigError("captcha должен быть функцией либо объектом с getToken");
109
+ const { getToken, type, field } = captcha;
110
+ if (typeof getToken !== "function") throw new ItdConfigError("captcha.getToken должен быть функцией");
111
+ requireCaptchaText(type, "captcha.type");
112
+ requireCaptchaText(field, "captcha.field");
113
+ return captcha;
114
+ }
115
+ /**
116
+ * Проверяет форму готового токена капчи.
117
+ *
118
+ * @param path имя опции или аргумента для текста ошибки
119
+ * @throws {ItdConfigError} если токен или его тип не указаны
120
+ */
121
+ function validateCaptchaToken(captcha, path) {
102
122
  if (captcha === void 0) return;
103
- if (!isRecord(captcha)) throw new ItdConfigError("auth.captcha должен быть объектом");
104
- const { type, token, getToken, field } = captcha;
105
- requireCaptchaText(type, "auth.captcha.type");
106
- requireCaptchaText(token, "auth.captcha.token");
107
- requireCaptchaText(field, "auth.captcha.field");
108
- if (getToken !== void 0 && typeof getToken !== "function") throw new ItdConfigError("auth.captcha.getToken должен быть функцией");
123
+ if (!isRecord(captcha)) throw new ItdConfigError(`${path} должен быть объектом { type, token }`);
124
+ const { type, token, field } = captcha;
125
+ if (typeof token !== "string" || token.trim() === "") throw new ItdConfigError(`${path}.token должен быть непустой строкой`);
126
+ if (typeof type !== "string" || type.trim() === "") throw new ItdConfigError(`${path}.type должен быть непустой строкой`);
127
+ requireCaptchaText(field, `${path}.field`);
109
128
  }
110
129
  /** Разбирает ответ `GET /api/v1/auth/captcha/provider` для автоматического входа. */
111
130
  function readCaptchaProvider(body) {
@@ -122,43 +141,40 @@ function readCaptchaProvider(body) {
122
141
  function resolveField(type, configured) {
123
142
  const field = configured ?? CAPTCHA_FIELDS[type];
124
143
  if (field) return field;
125
- throw new ItdConfigError(`Неизвестно, в каком поле сервер ждёт токен капчи «${type}». Передайте auth.captcha.field либо оставьте auth.captcha.type = ${CaptchaChoice.Auto}, чтобы поле назвал сервер.`);
144
+ throw new ItdConfigError(`Неизвестно, в каком поле сервер ждёт токен капчи «${type}». Передайте captcha.field либо оставьте captcha.type = ${CaptchaChoice.Auto}, чтобы поле назвал сервер.`);
126
145
  }
127
- /** Спрашивает свежий токен, затем берёт разовый. */
128
- async function resolveToken(captcha, type) {
129
- const token = await captcha.getToken?.(type) || captcha.token;
130
- if (token) return token;
131
- throw new ItdConfigError(`Источник капчи не вернул токен для провайдера «${type}». Проверьте auth.captcha.getToken.`);
146
+ /** Спрашивает токен у источника и убеждается, что он непустой. */
147
+ async function requireSolvedToken(solver, type) {
148
+ const token = await solver.getToken(type);
149
+ if (typeof token === "string" && token.trim() !== "") return token;
150
+ throw new ItdConfigError(`Источник капчи не вернул токен для провайдера «${type}». Проверьте captcha.getToken.`);
132
151
  }
133
152
  /**
134
- * Готовит фрагмент тела запроса с токеном капчи.
153
+ * Готовит фрагмент тела запроса, взяв токен у источника.
135
154
  *
136
155
  * При `CaptchaChoice.Auto` провайдер спрашивается у сервера — тогда и тип виджета,
137
- * и имя поля приходят оттуда. Явно названный тип решается без этого запроса.
156
+ * и имя поля приходят оттуда. Явно названный тип обходится без этого запроса.
138
157
  *
139
158
  * @param askProvider как спросить активного провайдера; вызывается, только если это нужно
140
- * @throws {ItdConfigError} если капча не настроена или токен не получен
159
+ * @throws {ItdConfigError} если токен получить не удалось
141
160
  */
142
- async function resolveCaptchaBody(captcha, askProvider) {
143
- if (!captcha?.token && !captcha?.getToken) throw new ItdConfigError("Вход по email и паролю требует токен капчи. Передайте auth.captcha.getToken источник свежего токена — либо разовый auth.captcha.token. В Node токен получает пакет @itd-api/captcha.");
144
- const choice = captcha.type ?? CaptchaChoice.Auto;
161
+ async function solveCaptchaBody(solver, askProvider) {
162
+ const choice = solver.type ?? CaptchaChoice.Auto;
145
163
  if (choice === CaptchaChoice.Auto) {
146
164
  const { provider, field } = await askProvider();
147
- return { [field]: await resolveToken(captcha, provider) };
165
+ return { [field]: await requireSolvedToken(solver, provider) };
148
166
  }
149
- return { [resolveField(choice, captcha.field)]: await resolveToken(captcha, choice) };
167
+ return { [resolveField(choice, solver.field)]: await requireSolvedToken(solver, choice) };
150
168
  }
151
169
  /**
152
- * Готовит фрагмент тела для разового вызова с готовым доказательством.
170
+ * Готовит фрагмент тела из готового токена.
153
171
  *
154
- * Поле берётся из самого доказательства либо из умолчания провайдера; запроса к серверу нет.
172
+ * Поле берётся из самого токена либо из умолчания провайдера; запроса к серверу нет.
155
173
  *
156
- * @throws {ItdConfigError} если доказательство неполное
174
+ * @throws {ItdConfigError} если токен или его тип не указаны
157
175
  */
158
176
  function captchaBody(captcha) {
159
- if (!isRecord(captcha)) throw new ItdConfigError("captcha должен быть объектом { type, token }");
160
- if (typeof captcha.token !== "string" || captcha.token.trim() === "") throw new ItdConfigError("captcha.token должен быть непустой строкой");
161
- if (typeof captcha.type !== "string" || captcha.type.trim() === "") throw new ItdConfigError("captcha.type должен быть непустой строкой");
177
+ validateCaptchaToken(captcha, "captcha");
162
178
  return { [resolveField(captcha.type, captcha.field)]: captcha.token };
163
179
  }
164
180
  //#endregion
@@ -236,7 +252,7 @@ function validateAuth(auth) {
236
252
  const { email, password, captcha } = auth;
237
253
  if (typeof email !== "string" || email.trim() === "") throw new ItdConfigError("auth.email должен быть непустой строкой");
238
254
  if (typeof password !== "string" || password === "") throw new ItdConfigError("auth.password должен быть непустой строкой");
239
- validateCaptchaOptions(captcha);
255
+ validateCaptchaToken(captcha, "auth.captcha");
240
256
  return { ...auth };
241
257
  }
242
258
  throw new ItdConfigError("auth не распознан. Ожидается строка с accessToken либо объект { accessToken }, { email, password } или { getToken }");
@@ -265,6 +281,7 @@ function resolveSessionConfig(options, runtime) {
265
281
  return {
266
282
  ...runtime,
267
283
  auth: validateAuth(options.auth),
284
+ captcha: resolveCaptchaSolver(options.captcha),
268
285
  storage: resolveStorage(options.storage),
269
286
  deviceId: options.deviceId,
270
287
  autoRefresh: options.autoRefresh ?? true,
@@ -284,6 +301,7 @@ function createItdAuth(options, deps) {
284
301
  /** Пути эндпоинтов авторизации. */
285
302
  const AUTH_PATHS = {
286
303
  captchaProvider: "/api/v1/auth/captcha/provider",
304
+ captchaPage: "/api/v1/auth/captcha/page",
287
305
  signUp: "/api/v1/auth/sign-up",
288
306
  signIn: "/api/v1/auth/sign-in",
289
307
  verifyOtp: "/api/v1/auth/verify-otp",
@@ -296,7 +314,10 @@ const AUTH_PATHS = {
296
314
  sessions: "/api/v1/auth/sessions",
297
315
  qrStart: "/api/v1/auth/qr/start",
298
316
  qrClaim: "/api/v1/auth/qr/claim",
299
- qrStream: "/api/v1/auth/qr/stream"
317
+ qrStream: "/api/v1/auth/qr/stream",
318
+ qrScan: "/api/v1/auth/qr/scan",
319
+ qrApprove: "/api/v1/auth/qr/approve",
320
+ qrReject: "/api/v1/auth/qr/reject"
300
321
  };
301
322
  /**
302
323
  * Публичный ключ Cloudflare Turnstile платформы итд.com.
@@ -436,6 +457,8 @@ var AuthManager = class {
436
457
  */
437
458
  #authEpoch = 0;
438
459
  #externalReadGeneration = 0;
460
+ /** Разовый токен из `auth.captcha` тратится ровно на один запрос. */
461
+ #configCaptchaSpent = false;
439
462
  #disposed = false;
440
463
  #persistence = Promise.resolve();
441
464
  constructor(config, http, jar, hooks = {}) {
@@ -963,6 +986,34 @@ var AuthManager = class {
963
986
  this.#signingIn = promise;
964
987
  return promise;
965
988
  }
989
+ /**
990
+ * Готовит фрагмент тела с токеном капчи для запроса, который её требует.
991
+ *
992
+ * Источники в порядке убывания приоритета: токен, переданный в сам вызов; разовый токен
993
+ * из `auth.captcha`; опция `captcha`. Когда ни одного нет, фрагмент пустой: нужна ли
994
+ * капча этому запросу, решает сервер.
995
+ *
996
+ * @param explicit готовый токен, переданный в вызов
997
+ * @throws {ItdConfigError} если токен неполный или источник его не вернул
998
+ */
999
+ async captchaBody(explicit) {
1000
+ if (explicit) return captchaBody(explicit);
1001
+ const configured = this.#takeConfiguredCaptcha();
1002
+ if (configured) return captchaBody(configured);
1003
+ const solver = this.#config.captcha;
1004
+ if (solver) return solveCaptchaBody(solver, () => this.#resolveCaptchaProvider());
1005
+ this.#config.logger?.debug("Запрос уходит без токена капчи: не передан аргумент captcha и не задана опция captcha");
1006
+ return {};
1007
+ }
1008
+ /** Отдаёт разовый токен из `auth.captcha`, помечая его потраченным. */
1009
+ #takeConfiguredCaptcha() {
1010
+ if (this.#configCaptchaSpent) return void 0;
1011
+ const auth = this.#config.auth;
1012
+ const captcha = isRecord(auth) ? auth.captcha : void 0;
1013
+ if (!captcha) return void 0;
1014
+ this.#configCaptchaSpent = true;
1015
+ return captcha;
1016
+ }
966
1017
  /** Спрашивает у сервера активного провайдера и поле, в котором он ждёт токен. */
967
1018
  async #resolveCaptchaProvider() {
968
1019
  return readCaptchaProvider(await this.#http.execute(AUTH_CAPTCHA_PROVIDER, {
@@ -972,7 +1023,7 @@ var AuthManager = class {
972
1023
  }));
973
1024
  }
974
1025
  async #performSignIn(credentials, revision) {
975
- const captcha = await resolveCaptchaBody(credentials.captcha, () => this.#resolveCaptchaProvider());
1026
+ const captcha = await this.captchaBody();
976
1027
  const flowCookies = new CookieJar();
977
1028
  const result = await this.#http.execute(AUTH_SIGN_IN, {
978
1029
  path: AUTH_PATHS.signIn,
@@ -1153,7 +1204,8 @@ var AuthResource = class extends BaseResource {
1153
1204
  if (existing) return existing;
1154
1205
  const flow = {
1155
1206
  cookies: this.#auth.createCookieFlow(false),
1156
- expiresAt: void 0
1207
+ expiresAt: void 0,
1208
+ captchaRequired: false
1157
1209
  };
1158
1210
  this.#qrFlows.set(qrId, flow);
1159
1211
  return flow;
@@ -1191,6 +1243,21 @@ var AuthResource = class extends BaseResource {
1191
1243
  ...options
1192
1244
  });
1193
1245
  }
1246
+ /**
1247
+ * Узнаёт активного провайдера капчи вместе с адресом страницы виджета.
1248
+ *
1249
+ * То же, что {@link captchaProvider}, плюс `url` — готовая страница на домене итд.com,
1250
+ * где ключ виджета действителен. Пригодится встроенному браузеру: открыть её и забрать
1251
+ * токен, ничего не собирая самому.
1252
+ */
1253
+ captchaPage(options = {}) {
1254
+ return this.http.execute(AUTH_CAPTCHA_PAGE, {
1255
+ path: AUTH_PATHS.captchaPage,
1256
+ skipAuth: true,
1257
+ skipAuthRefresh: true,
1258
+ ...options
1259
+ });
1260
+ }
1194
1261
  /** Создаёт короткоживущую сессию QR-входа. */
1195
1262
  async startQrLogin(options = {}) {
1196
1263
  const cookies = this.#auth.createCookieFlow(false);
@@ -1204,7 +1271,8 @@ var AuthResource = class extends BaseResource {
1204
1271
  if (typeof result.qrId === "string" && result.qrId !== "") {
1205
1272
  const flow = {
1206
1273
  cookies,
1207
- expiresAt: void 0
1274
+ expiresAt: void 0,
1275
+ captchaRequired: false
1208
1276
  };
1209
1277
  this.#touchQrFlow(flow, result.expiresIn);
1210
1278
  this.#qrFlows.set(result.qrId, flow);
@@ -1214,20 +1282,21 @@ var AuthResource = class extends BaseResource {
1214
1282
  /**
1215
1283
  * Проверяет QR-сессию и завершает вход после подтверждения на другом устройстве.
1216
1284
  *
1217
- * До подтверждения возвращает промежуточный статус. Если сервер потребовал капчу,
1218
- * повторите вызов с `captcha` тип берётся из {@link captchaProvider}.
1219
- * Полученный `accessToken` автоматически сохраняется в клиенте.
1285
+ * До подтверждения возвращает промежуточный статус. Токен капчи добывается, только когда
1286
+ * сервер о ней попросил — статусом `captcha_required` на прошлой проверке, либо когда
1287
+ * передан в сам вызов. Полученный `accessToken` автоматически сохраняется в клиенте.
1220
1288
  */
1221
1289
  async claimQrLogin(input, options = {}) {
1222
1290
  const revision = this.#auth.revision();
1223
1291
  const flow = this.#qrFlow(input.qrId);
1224
1292
  try {
1225
1293
  const { captcha, ...rest } = input;
1294
+ const needsCaptcha = captcha !== void 0 || flow.captchaRequired;
1226
1295
  const result = await this.http.execute(AUTH_QR_CLAIM, {
1227
1296
  path: AUTH_PATHS.qrClaim,
1228
1297
  body: {
1229
1298
  ...rest,
1230
- ...captcha ? captchaBody(captcha) : {}
1299
+ ...needsCaptcha ? await this.#auth.captchaBody(captcha) : {}
1231
1300
  },
1232
1301
  skipAuth: true,
1233
1302
  skipAuthRefresh: true,
@@ -1235,6 +1304,7 @@ var AuthResource = class extends BaseResource {
1235
1304
  ...options
1236
1305
  });
1237
1306
  this.#touchQrFlow(flow, result.expiresIn);
1307
+ if (result.status === QrLoginStatus.CaptchaRequired) flow.captchaRequired = true;
1238
1308
  if (result.status === QrLoginStatus.Authorized) {
1239
1309
  await this.#auth.commitAuthFlow(result.accessToken, flow.cookies, revision);
1240
1310
  this.#qrFlows.delete(input.qrId);
@@ -1266,17 +1336,62 @@ var AuthResource = class extends BaseResource {
1266
1336
  }
1267
1337
  }
1268
1338
  /**
1339
+ * Отмечает QR-код отсканированным и узнаёт, кого впускают.
1340
+ *
1341
+ * Обратная сторона QR-входа: этот и два соседних метода вызывает устройство, где сессия
1342
+ * уже есть. Показывающая сторона увидит статус `scanned`, но вход ещё не состоится —
1343
+ * его завершают {@link approveQrLogin} или {@link rejectQrLogin}.
1344
+ *
1345
+ * Сервер принимает только сессию, созданную мобильным клиентом. Обычный web access token
1346
+ * отклоняется с кодом `QR_APPROVER_NOT_ALLOWED`, даже если добавить мобильные заголовки
1347
+ * уже после выдачи токена.
1348
+ *
1349
+ * @param input секреты из отсканированного кода — см. {@link QrLoginSecrets}
1350
+ * @returns описание устройства, которое просит вход
1351
+ */
1352
+ scanQrLogin(input, options = {}) {
1353
+ return this.http.execute(AUTH_QR_SCAN, {
1354
+ path: AUTH_PATHS.qrScan,
1355
+ body: input,
1356
+ ...options
1357
+ });
1358
+ }
1359
+ /**
1360
+ * Подтверждает вход по QR-коду: показавшее код устройство получит access token.
1361
+ *
1362
+ * Осмысленно после {@link scanQrLogin} — человек должен увидеть, что подтверждает.
1363
+ * Требует мобильную сессию, как и `scanQrLogin()`.
1364
+ */
1365
+ approveQrLogin(input, options = {}) {
1366
+ return this.voidOperation(AUTH_QR_APPROVE, {
1367
+ path: AUTH_PATHS.qrApprove,
1368
+ body: input,
1369
+ ...options
1370
+ });
1371
+ }
1372
+ /**
1373
+ * Отклоняет вход по QR-коду: показавшее код устройство получит статус `rejected`.
1374
+ * Требует мобильную сессию, как и `scanQrLogin()`.
1375
+ */
1376
+ rejectQrLogin(input, options = {}) {
1377
+ return this.voidOperation(AUTH_QR_REJECT, {
1378
+ path: AUTH_PATHS.qrReject,
1379
+ body: input,
1380
+ ...options
1381
+ });
1382
+ }
1383
+ /**
1269
1384
  * Регистрирует аккаунт и запускает подтверждение по коду.
1270
1385
  *
1271
1386
  * @returns `flowToken`, который нужно передать в {@link verifyOtp}
1272
1387
  */
1273
- signUp(credentials, options = {}) {
1388
+ async signUp(credentials, options = {}) {
1274
1389
  const { captcha, ...rest } = credentials;
1275
1390
  return this.http.execute(AUTH_SIGN_UP, {
1276
1391
  path: AUTH_PATHS.signUp,
1277
1392
  body: {
1278
1393
  ...rest,
1279
- ...captchaBody(captcha)
1394
+ ...await this.#auth.captchaBody(captcha)
1280
1395
  },
1281
1396
  skipAuth: true,
1282
1397
  skipAuthRefresh: true,
@@ -1291,18 +1406,19 @@ var AuthResource = class extends BaseResource {
1291
1406
  *
1292
1407
  * При успешном входе токен сохраняется в клиенте автоматически.
1293
1408
  *
1294
- * @param credentials email, пароль и обязательный токен капчи — см. {@link CaptchaCredentials}
1409
+ * @param credentials email, пароль и токен капчи — см. {@link CredentialsWithCaptcha}
1295
1410
  */
1296
1411
  async signIn(credentials, options = {}) {
1297
1412
  const { captcha, ...rest } = credentials;
1298
1413
  const revision = this.#auth.revision();
1299
1414
  const cookies = this.#auth.createCookieFlow(false);
1415
+ const body = {
1416
+ ...rest,
1417
+ ...await this.#auth.captchaBody(captcha)
1418
+ };
1300
1419
  const result = await this.http.execute(AUTH_SIGN_IN, {
1301
1420
  path: AUTH_PATHS.signIn,
1302
- body: {
1303
- ...rest,
1304
- ...captchaBody(captcha)
1305
- },
1421
+ body,
1306
1422
  skipAuth: true,
1307
1423
  skipAuthRefresh: true,
1308
1424
  cookieJar: cookies,
@@ -1430,12 +1546,12 @@ var AuthResource = class extends BaseResource {
1430
1546
  *
1431
1547
  * @returns `flowToken`, который нужно передать в {@link resetPassword}
1432
1548
  */
1433
- forgotPassword(input, options = {}) {
1549
+ async forgotPassword(input, options = {}) {
1434
1550
  return this.http.execute(AUTH_FORGOT_PASSWORD, {
1435
1551
  path: AUTH_PATHS.forgotPassword,
1436
1552
  body: {
1437
1553
  email: input.email,
1438
- ...captchaBody(input.captcha)
1554
+ ...await this.#auth.captchaBody(input.captcha)
1439
1555
  },
1440
1556
  skipAuth: true,
1441
1557
  skipAuthRefresh: true,
@@ -1570,8 +1686,9 @@ function createManagedClient(options, internals) {
1570
1686
  * const itd = new ItdClient({
1571
1687
  * // `auth` не обязателен: когда хранилище уже содержит сессию, токен берётся оттуда,
1572
1688
  * // а истёкший продлевается сам. Здесь он нужен на первый запуск.
1573
- * // Вход по паролю требует токена капчи — см. CaptchaOptions.
1574
- * auth: { email, password, captcha: createCaptchaSolver() },
1689
+ * auth: { email, password },
1690
+ * // Капчу входа решает пакет @itd-api/captcha см. CaptchaSolver.
1691
+ * captcha: createCaptchaSolver(),
1575
1692
  * storage: new FileTokenStorage('./.itd-session.json'),
1576
1693
  * rateLimit: { concurrency: 4, rps: 8 },
1577
1694
  * });
@@ -2085,6 +2202,8 @@ function assertAccountsActive(accounts, action) {
2085
2202
  *
2086
2203
  * await using accounts = new ItdAccounts({
2087
2204
  * storage: new FileMultiTokenStorage('./.itd-sessions.json'),
2205
+ * // Общие опции достаются каждому аккаунту: один источник токена капчи на всех.
2206
+ * captcha: createCaptchaSolver(),
2088
2207
  * rateLimit: { concurrency: 4 },
2089
2208
  * });
2090
2209
  *
@@ -2092,9 +2211,7 @@ function assertAccountsActive(accounts, action) {
2092
2211
  * await accounts.restore();
2093
2212
  *
2094
2213
  * if (!accounts.has('kiow')) {
2095
- * accounts.addAccount('kiow', {
2096
- * auth: { email, password, captcha: createCaptchaSolver() },
2097
- * });
2214
+ * accounts.addAccount('kiow', { auth: { email, password } });
2098
2215
  * }
2099
2216
  *
2100
2217
  * await accounts.account('kiow').posts.create({ content: 'привет' });
@@ -2173,9 +2290,7 @@ var ItdAccounts = class ItdAccounts {
2173
2290
  *
2174
2291
  * @example
2175
2292
  * ```ts
2176
- * accounts.addAccount('bot', {
2177
- * auth: { email, password, captcha: createCaptchaSolver() },
2178
- * });
2293
+ * accounts.addAccount('bot', { auth: { email, password } });
2179
2294
  * accounts.addAccount('reader', { auth: '<accessToken>' });
2180
2295
  * accounts.addAccount('через-прокси', { fetch: proxyFetch('socks5://…') });
2181
2296
  * ```
@@ -2508,6 +2623,6 @@ function createAccounts(options = {}) {
2508
2623
  return new ItdAccounts(options);
2509
2624
  }
2510
2625
  //#endregion
2511
- 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 };
2626
+ 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, QrLocationPrecision, 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 };
2512
2627
 
2513
2628
  //# sourceMappingURL=index.js.map