itd-api 0.9.1 → 0.9.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 (63) hide show
  1. package/dist/events/index.cjs +3 -4
  2. package/dist/events/index.d.cts +3 -3
  3. package/dist/events/index.d.ts +3 -3
  4. package/dist/events/index.js +4 -4
  5. package/dist/index.cjs +198 -39
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.cts +11 -65
  8. package/dist/index.d.ts +11 -65
  9. package/dist/index.js +199 -39
  10. package/dist/index.js.map +1 -1
  11. package/dist/node/index.cjs +3 -3
  12. package/dist/node/index.js +3 -3
  13. package/dist/rest/index.cjs +4 -5
  14. package/dist/rest/index.d.cts +3 -3
  15. package/dist/rest/index.d.ts +3 -3
  16. package/dist/rest/index.js +5 -5
  17. package/dist/shared/{events-BDdJyH2p.d.ts → events-BCn5LRnT.d.cts} +3 -5
  18. package/dist/shared/{events-D6y0Hxyz.js → events-BIBO2Jif.js} +4 -4
  19. package/dist/shared/{events-D6y0Hxyz.js.map → events-BIBO2Jif.js.map} +1 -1
  20. package/dist/shared/{events-BqFkwwTw.cjs → events-BsRm_Fba.cjs} +3 -9
  21. package/dist/shared/{events-BqFkwwTw.cjs.map → events-BsRm_Fba.cjs.map} +1 -1
  22. package/dist/shared/{events-BQyH5-S0.d.cts → events-t1uGgpkf.d.ts} +3 -5
  23. package/dist/shared/{multi-storage-SoW05uzO.js → multi-storage-C3Ngtop1.js} +13 -8
  24. package/dist/shared/multi-storage-C3Ngtop1.js.map +1 -0
  25. package/dist/shared/{multi-storage-CDKLa956.cjs → multi-storage-s-fPtjoy.cjs} +13 -8
  26. package/dist/shared/multi-storage-s-fPtjoy.cjs.map +1 -0
  27. package/dist/shared/{options-DOJYtoti.cjs → options-DjEtbVaf.cjs} +2 -2
  28. package/dist/shared/{options-DOJYtoti.cjs.map → options-DjEtbVaf.cjs.map} +1 -1
  29. package/dist/shared/{options-DhgGg7Ms.js → options-i4LrKfqx.js} +2 -2
  30. package/dist/shared/{options-DhgGg7Ms.js.map → options-i4LrKfqx.js.map} +1 -1
  31. package/dist/shared/{redact-Ba5iSVYZ.js → redact-DJV0XM3n.js} +40 -15
  32. package/dist/shared/redact-DJV0XM3n.js.map +1 -0
  33. package/dist/shared/{redact-BP00URmQ.cjs → redact-lAmck1Z6.cjs} +45 -14
  34. package/dist/shared/redact-lAmck1Z6.cjs.map +1 -0
  35. package/dist/shared/{render-sb1VJhUA.js → render--ZXmYEf-.js} +127 -96
  36. package/dist/shared/render--ZXmYEf-.js.map +1 -0
  37. package/dist/shared/{render-CZ_-F2Tl.d.ts → render-BdnBV-aT.d.ts} +82 -7
  38. package/dist/shared/{render-BU1_wex8.d.cts → render-DA83gS0f.d.cts} +82 -7
  39. package/dist/shared/{render-DAEeCMk9.cjs → render-DqVzx68r.cjs} +125 -100
  40. package/dist/shared/render-DqVzx68r.cjs.map +1 -0
  41. package/dist/shared/{storage-QNVxzpDB.cjs → storage-CZJZoaz9.cjs} +4 -2
  42. package/dist/shared/storage-CZJZoaz9.cjs.map +1 -0
  43. package/dist/shared/{storage-CfHKzVHf.js → storage-DiDe0PZM.js} +4 -2
  44. package/dist/shared/storage-DiDe0PZM.js.map +1 -0
  45. package/dist/shared/{url-CZZcyPI0.d.ts → url-Dbj6V6fk.d.cts} +4 -2
  46. package/dist/shared/{url-CZZcyPI0.d.cts → url-Dbj6V6fk.d.ts} +4 -2
  47. package/dist/shared/{url-CzHqE-Fg.cjs → url-Doc0XXW_.cjs} +274 -86
  48. package/dist/shared/url-Doc0XXW_.cjs.map +1 -0
  49. package/dist/shared/{url-Dsr4c5r5.js → url-i2f-X1hX.js} +274 -86
  50. package/dist/shared/url-i2f-X1hX.js.map +1 -0
  51. package/dist/web/index.cjs +1 -1
  52. package/dist/web/index.js +1 -1
  53. package/package.json +4 -4
  54. package/dist/shared/multi-storage-CDKLa956.cjs.map +0 -1
  55. package/dist/shared/multi-storage-SoW05uzO.js.map +0 -1
  56. package/dist/shared/redact-BP00URmQ.cjs.map +0 -1
  57. package/dist/shared/redact-Ba5iSVYZ.js.map +0 -1
  58. package/dist/shared/render-DAEeCMk9.cjs.map +0 -1
  59. package/dist/shared/render-sb1VJhUA.js.map +0 -1
  60. package/dist/shared/storage-CfHKzVHf.js.map +0 -1
  61. package/dist/shared/storage-QNVxzpDB.cjs.map +0 -1
  62. package/dist/shared/url-CzHqE-Fg.cjs.map +0 -1
  63. package/dist/shared/url-Dsr4c5r5.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
- import { $ as orderPluginDefinitions, A as ITD_CATALOG, C as ReportReason, D as ViewReason, E as SpanType, F as operationMethod, G as pickString, I as operationRetrySafety, K as RetrySafety, L as BUCKET_LIMITS, M as defineBuiltInOperation, N as isBuiltInOperationId, O as ViewSource, P as operationBucket, Q as assertPluginRemovable, R as DEFAULT_RATE_LIMIT_BUCKET, S as NotificationType, T as ServiceState, V as pickArray, Z as RequestQueuePool, _ as FeedTab, at as isRecord, b as ItdErrorCode, c as normalizeNotification, d as canonicalNotificationType, et as DEFAULT_BASE_URL, f as isKnownNotificationType, g as EventChannelStatus, h as CommentSort, it as LIBRARY_VERSION, j as OPERATIONS, k as WallAccess, lt as systemClock, m as AttachmentType, n as formatNotificationText, nt as STATUS_SERVICE, o as passthroughOperation, ot as requireOptionalBoolean, p as AccessType, rt as resolveRateLimit, s as voidOperation, st as RateLimitPacing, t as resolveNotificationUrl, ut as installAsyncDisposeFallback, v as IncidentKind, w as ReportTargetType, x as LikesVisibility, y as InteractionType, z as createClientRuntime } from "./shared/url-Dsr4c5r5.js";
1
+ import { $ as orderPluginDefinitions, A as ITD_CATALOG, C as ReportReason, D as ViewReason, E as SpanType, F as operationMethod, G as pickString, I as operationRetrySafety, K as RetrySafety, L as BUCKET_LIMITS, M as defineBuiltInOperation, N as isBuiltInOperationId, O as ViewSource, P as operationBucket, Q as assertPluginRemovable, R as DEFAULT_RATE_LIMIT_BUCKET, S as NotificationType, T as ServiceState, V as pickArray, Z as RequestQueuePool, _ as FeedTab, at as isRecord, b as ItdErrorCode, c as normalizeNotification, d as canonicalNotificationType, et as DEFAULT_BASE_URL, f as isKnownNotificationType, g as EventChannelStatus, h as CommentSort, it as LIBRARY_VERSION, j as OPERATIONS, k as WallAccess, lt as systemClock, m as AttachmentType, n as formatNotificationText, nt as STATUS_SERVICE, o as passthroughOperation, ot as requireOptionalBoolean, p as AccessType, rt as resolveRateLimit, s as voidOperation, st as RateLimitPacing, t as resolveNotificationUrl, ut as installAsyncDisposeFallback, v as IncidentKind, w as ReportTargetType, x as LikesVisibility, y as InteractionType, z as createClientRuntime } from "./shared/url-i2f-X1hX.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 { _ as RuntimeMode, c as REFRESH_COOKIE, l as REFRESH_COOKIE_PATH, v as createDeviceId } from "./shared/redact-Ba5iSVYZ.js";
4
- import { A as toDate, C as ALLOWED_MIME_TYPES, D as comment, E as VIDEO_MIME_TYPES, F as Paginator, I as mapPage, L as createFeatureHost, M as NotificationsResource, N as BaseResource, O as isBuilder, P as PaginationMode, R as ExtensibleOperationCatalog, S as parseMarkdown, T as IMAGE_MIME_TYPES, _ as post, a as createMemoryShopOrderAccessStorage, b as markup, c as ShopConsentKind, d as ShopProductStatus, f as isMyProfile, g as report, h as createResources, i as MemoryShopOrderAccessStorage, j as utcStampToIso, k as createStatusFeature, l as ShopOrderStatus, m as fromUrl, n as renderSpans, o as createShopOrderAccessStorage, p as fromStream, r as createShopFeature, s as statusDays, t as SpanRenderFormat, u as ShopProductCategory, v as poll, w as AUDIO_MIME_TYPES, x as parseHtml, y as autoSpans } from "./shared/render-sb1VJhUA.js";
5
- import { c as FileTransferMode } from "./shared/options-DhgGg7Ms.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, n as EventRouter, o as EVENT_OPERATIONS, p as EventChannel, r as NotificationEventsClient, s as NotificationEvents, t as EventComposer, u as setNotificationEventsConnectGuard, v as reportListenerError } from "./shared/events-D6y0Hxyz.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--ZXmYEf-.js";
5
+ import { c as FileTransferMode } from "./shared/options-i4LrKfqx.js";
6
+ import { _ as reportListenerError, a as WebSocketTransport, c as NotificationEventsTransport, d as NotificationUpdateOrigin, f as NotificationUpdateType, g as Emitter, h as runEventMiddleware, i as createNotificationEventsClient, l as resolveNotificationEventsOptions, m as UnauthorizedStreamError, n as EventRouter, o as EVENT_OPERATIONS, p as EventChannel, r as NotificationEventsClient, s as NotificationEvents, t as EventComposer, u as setNotificationEventsConnectGuard } from "./shared/events-BIBO2Jif.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
- import { n as copySession, r as createTokenStorage, t as MemoryTokenStorage } from "./shared/storage-CfHKzVHf.js";
9
- import { a as scopedTokenStorage, i as isRestorableSession, n as controlledTokenStorage, r as createMultiTokenStorage, t as MemoryMultiTokenStorage } from "./shared/multi-storage-SoW05uzO.js";
8
+ import { n as copySession, r as createTokenStorage, t as MemoryTokenStorage } from "./shared/storage-DiDe0PZM.js";
9
+ import { a as scopedTokenStorage, i as isRestorableSession, n as controlledTokenStorage, r as createMultiTokenStorage, t as MemoryMultiTokenStorage } from "./shared/multi-storage-C3Ngtop1.js";
10
10
  //#region src/notifications-api.ts
11
11
  var ClientNotifications = class extends NotificationsResource {
12
12
  #createEvents;
@@ -49,7 +49,7 @@ const AUTH_SIGN_IN = defineBuiltInOperation("auth.signIn", (body) => {
49
49
  };
50
50
  });
51
51
  const AUTH_VERIFY_OTP = defineBuiltInOperation("auth.verifyOtp", (body) => requireString(body, "accessToken", "Сервер не вернул accessToken после подтверждения кода"));
52
- const AUTH_REFRESH = defineBuiltInOperation("auth.refresh", (body) => pickString(body, "accessToken"));
52
+ const AUTH_REFRESH = defineBuiltInOperation("auth.refresh", (body) => requireString(body, "accessToken", "Сервер не вернул accessToken при обновлении сессии"));
53
53
  const AUTH_RESEND_OTP = voidOperation("auth.resendOtp");
54
54
  const AUTH_LOGOUT = voidOperation("auth.logout");
55
55
  const AUTH_FORGOT_PASSWORD = defineBuiltInOperation("auth.forgotPassword", (body) => requireString(body, "flowToken", "Сервер не вернул flowToken при запросе сброса пароля"));
@@ -119,12 +119,18 @@ function validateAuth(auth) {
119
119
  return auth;
120
120
  }
121
121
  if (typeof auth !== "object" || auth === null) throw new ItdConfigError(`auth должен быть строкой с токеном или объектом, получено: ${typeof auth}`);
122
+ if ([
123
+ "getToken" in auth,
124
+ "accessToken" in auth || "refreshToken" in auth,
125
+ "email" in auth || "password" in auth
126
+ ].filter(Boolean).length > 1) throw new ItdConfigError("auth должен описывать ровно один способ авторизации: getToken, accessToken или email/password");
122
127
  if ("getToken" in auth) {
123
128
  if (typeof auth.getToken !== "function") throw new ItdConfigError("auth.getToken должен быть функцией");
124
129
  return { ...auth };
125
130
  }
126
131
  if ("accessToken" in auth) {
127
132
  if (typeof auth.accessToken !== "string" || auth.accessToken.trim() === "") throw new ItdConfigError("auth.accessToken должен быть непустой строкой");
133
+ if (auth.refreshToken !== void 0 && (typeof auth.refreshToken !== "string" || auth.refreshToken.trim() === "")) throw new ItdConfigError("auth.refreshToken должен быть непустой строкой");
128
134
  return { ...auth };
129
135
  }
130
136
  if ("email" in auth || "password" in auth) {
@@ -212,10 +218,67 @@ const AUTH_PATHS = {
212
218
  const TURNSTILE_SITE_KEY = "0x4AAAAAACHhxczw6fJGwPBg";
213
219
  /** Убирает поле, которое сохраняли промежуточные версии с поддержкой `getUserId()`. */
214
220
  function withoutLegacyUserId(session) {
215
- const clean = { ...session };
221
+ const clean = copySession(session);
216
222
  delete clean.userId;
217
223
  return clean;
218
224
  }
225
+ function normalizeSessionValue(value, source, mode = "strict", logger) {
226
+ const tolerant = mode === "stored";
227
+ if (!isRecord(value)) {
228
+ if (tolerant) {
229
+ logger?.warn(`${source} вернула не объект сессии; сохранённая запись пропущена`);
230
+ return null;
231
+ }
232
+ throw new ItdConfigError(`${source} должна быть объектом сессии`);
233
+ }
234
+ const record = value;
235
+ const normalized = { ...record };
236
+ delete normalized.userId;
237
+ const normalizeString = (field) => {
238
+ const item = record[field];
239
+ if (item === void 0) {
240
+ delete normalized[field];
241
+ return;
242
+ }
243
+ if (item !== void 0 && (typeof item !== "string" || item.trim() === "")) {
244
+ if (tolerant) {
245
+ logger?.warn(`${source}.${field} повреждена; поле сохранённой сессии пропущено`);
246
+ delete normalized[field];
247
+ return;
248
+ }
249
+ throw new ItdConfigError(`${source}.${field} должна быть непустой строкой`);
250
+ }
251
+ normalized[field] = item;
252
+ };
253
+ normalizeString("accessToken");
254
+ normalizeString("refreshToken");
255
+ normalizeString("deviceId");
256
+ if (record.obtainedAt === void 0) delete normalized.obtainedAt;
257
+ else if (typeof record.obtainedAt !== "number" || !Number.isFinite(record.obtainedAt)) {
258
+ if (!tolerant) throw new ItdConfigError(`${source}.obtainedAt должна быть конечным числом`);
259
+ else {
260
+ logger?.warn(`${source}.obtainedAt повреждена; поле сохранённой сессии пропущено`);
261
+ delete normalized.obtainedAt;
262
+ }
263
+ }
264
+ if (record.cookies === void 0) delete normalized.cookies;
265
+ else if (!Array.isArray(record.cookies)) {
266
+ if (tolerant) {
267
+ logger?.warn(`${source}.cookies повреждена; поле сохранённой сессии пропущено`);
268
+ delete normalized.cookies;
269
+ } else throw new ItdConfigError(`${source}.cookies должна быть массивом`);
270
+ } else {
271
+ const valid = record.cookies.filter((cookie) => typeof cookie === "string" && isSerializedCookieEntry(cookie));
272
+ if (valid.length > 0) normalized.cookies = valid;
273
+ else delete normalized.cookies;
274
+ }
275
+ return normalized;
276
+ }
277
+ function normalizeSession(value, source) {
278
+ const normalized = normalizeSessionValue(value, source);
279
+ if (!normalized) throw new ItdConfigError(`${source} должна быть объектом сессии`);
280
+ return normalized;
281
+ }
219
282
  let authScopeSequence = 0;
220
283
  function nextAuthScope() {
221
284
  authScopeSequence += 1;
@@ -270,6 +333,9 @@ var AuthManager = class {
270
333
  * воскресить уже очищенную сессию поверх `signOut`.
271
334
  */
272
335
  #authEpoch = 0;
336
+ #externalReadGeneration = 0;
337
+ #disposed = false;
338
+ #persistence = Promise.resolve();
273
339
  constructor(config, http, jar, hooks = {}) {
274
340
  this.#config = config;
275
341
  this.#http = http;
@@ -279,14 +345,23 @@ var AuthManager = class {
279
345
  }
280
346
  /** Подписка на события авторизации. */
281
347
  get on() {
282
- return this.#emitter.on.bind(this.#emitter);
348
+ return ((...args) => {
349
+ this.#assertActive();
350
+ return this.#emitter.on(...args);
351
+ });
283
352
  }
284
353
  /** Подписка на одно срабатывание. */
285
354
  get once() {
286
- return this.#emitter.once.bind(this.#emitter);
355
+ return ((...args) => {
356
+ this.#assertActive();
357
+ return this.#emitter.once(...args);
358
+ });
287
359
  }
288
360
  /** Снимает служебные подписки при окончательном освобождении владельца. @internal */
289
361
  dispose() {
362
+ if (this.#disposed) return;
363
+ this.#disposed = true;
364
+ this.#externalReadGeneration += 1;
290
365
  this.#invalidateInFlight();
291
366
  this.#emitter.removeAllListeners();
292
367
  }
@@ -301,6 +376,7 @@ var AuthManager = class {
301
376
  }
302
377
  /** Загружает сессию и возвращает идентификаторы аккаунта и сессии для плагинов. */
303
378
  async getAuthIdentity() {
379
+ this.#assertActive();
304
380
  const session = await this.#loadSession();
305
381
  if (session?.accessToken) return this.#identityForToken(session.accessToken);
306
382
  const auth = this.#config.auth;
@@ -321,6 +397,30 @@ var AuthManager = class {
321
397
  /** Отмечает смену владельца авторизации — обесценивает результат идущего обновления. */
322
398
  #invalidateInFlight() {
323
399
  this.#authEpoch += 1;
400
+ this.#externalReadGeneration += 1;
401
+ }
402
+ /** Ревизия владельца сессии для условного commit внешнего auth-flow. @internal */
403
+ revision() {
404
+ this.#assertActive();
405
+ return this.#authEpoch;
406
+ }
407
+ /** Создаёт изолированный jar для сетевого auth-flow. @internal */
408
+ createCookieFlow(inherit = true) {
409
+ this.#assertActive();
410
+ return inherit ? this.#jar.clone() : new CookieJar();
411
+ }
412
+ /** Применяет cookies только если владелец сессии не сменился. @internal */
413
+ commitCookieFlow(cookies, expectedRevision) {
414
+ this.#assertActive();
415
+ if (this.#authEpoch !== expectedRevision) return false;
416
+ this.#jar.replaceWith(cookies);
417
+ return true;
418
+ }
419
+ #assertActive() {
420
+ if (this.#disposed) throw new ItdStateError("Менеджер авторизации уже освобождён");
421
+ }
422
+ #currentAccessToken() {
423
+ return this.#session?.accessToken ?? null;
324
424
  }
325
425
  #identityForToken(accessToken) {
326
426
  const token = accessToken ? readTokenIdentity(accessToken) : {};
@@ -330,7 +430,11 @@ var AuthManager = class {
330
430
  };
331
431
  }
332
432
  async #readExternalToken(getToken) {
433
+ this.#assertActive();
434
+ const generation = ++this.#externalReadGeneration;
333
435
  const token = await getToken() ?? null;
436
+ this.#assertActive();
437
+ if (generation !== this.#externalReadGeneration) return token;
334
438
  const current = this.#identityForToken(token ?? void 0);
335
439
  if (this.#externalToken !== void 0 && this.#externalToken !== token) {
336
440
  this.#rotateAuthScope();
@@ -361,6 +465,7 @@ var AuthManager = class {
361
465
  * ответ был бы `false` даже при полностью рабочей сохранённой сессии.
362
466
  */
363
467
  async hasRefreshSession() {
468
+ this.#assertActive();
364
469
  await this.#loadSession();
365
470
  return this.#hasRefreshSession();
366
471
  }
@@ -377,6 +482,7 @@ var AuthManager = class {
377
482
  * обязаны завершиться до захвата слота очереди.
378
483
  */
379
484
  async prepare() {
485
+ this.#assertActive();
380
486
  await this.token();
381
487
  }
382
488
  /**
@@ -396,6 +502,7 @@ var AuthManager = class {
396
502
  * через `itd.auth.refresh()`, а библиотека просто пробрасывает {@link ItdAuthError}.
397
503
  */
398
504
  recover() {
505
+ this.#assertActive();
399
506
  return this.#config.autoRefresh ? this.onUnauthorized() : Promise.resolve(false);
400
507
  }
401
508
  /**
@@ -406,6 +513,7 @@ var AuthManager = class {
406
513
  * по новой сессии на каждый старт.
407
514
  */
408
515
  deviceId() {
516
+ this.#assertActive();
409
517
  if (this.#deviceId) return Promise.resolve(this.#deviceId);
410
518
  this.#deviceIdLoading ??= this.#resolveDeviceId().finally(() => {
411
519
  this.#deviceIdLoading = null;
@@ -413,9 +521,12 @@ var AuthManager = class {
413
521
  return this.#deviceIdLoading;
414
522
  }
415
523
  async #resolveDeviceId() {
524
+ const revision = this.#authEpoch;
416
525
  const session = await this.#loadSession();
526
+ this.#assertActive();
417
527
  const deviceId = this.#config.deviceId ?? session?.deviceId ?? createDeviceId();
418
528
  this.#deviceId = deviceId;
529
+ if (this.#authEpoch !== revision) return deviceId;
419
530
  if (session?.deviceId !== deviceId) await this.#saveSession({
420
531
  ...session ?? {},
421
532
  deviceId
@@ -429,6 +540,7 @@ var AuthManager = class {
429
540
  * и пароль, первый же запрос сам заведёт сессию.
430
541
  */
431
542
  async token() {
543
+ this.#assertActive();
432
544
  const session = await this.#loadSession();
433
545
  if (session?.accessToken) return session.accessToken;
434
546
  const auth = this.#config.auth;
@@ -443,6 +555,7 @@ var AuthManager = class {
443
555
  * @returns `true`, если токен обновлён и запрос имеет смысл повторить
444
556
  */
445
557
  async onUnauthorized() {
558
+ this.#assertActive();
446
559
  try {
447
560
  if (await this.#refreshDeduplicated() !== null) return true;
448
561
  this.#emitter.emit("authError", { error: this.#noRefreshSessionError() });
@@ -478,24 +591,33 @@ var AuthManager = class {
478
591
  * @throws {ItdAuthError} если обновить сессию не удалось
479
592
  */
480
593
  async refresh() {
594
+ this.#assertActive();
481
595
  const token = await this.#refreshDeduplicated();
482
596
  if (token === null) throw this.#noRefreshSessionError();
483
597
  return token;
484
598
  }
485
599
  /** Сохраняет токен, полученный извне, — например после подтверждения OTP. */
486
- async setAccessToken(accessToken) {
600
+ async setAccessToken(accessToken, expectedRevision = this.#authEpoch, cookies) {
601
+ this.#assertActive();
602
+ if (typeof accessToken !== "string" || accessToken.trim() === "") throw new ItdConfigError("accessToken должен быть непустой строкой");
487
603
  await this.#loadSession();
604
+ this.#assertActive();
605
+ if (this.#authEpoch !== expectedRevision) return false;
606
+ if (cookies) this.#jar.replaceWith(cookies);
488
607
  this.#invalidateInFlight();
489
608
  this.#transitionAuth(accessToken);
490
- await this.#saveSession({
609
+ const saved = this.#saveSession({
491
610
  ...this.#session ?? {},
492
611
  accessToken,
493
612
  obtainedAt: this.#config.clock.now()
494
613
  });
495
614
  this.#emitter.emit("tokens", { accessToken });
615
+ await saved;
616
+ return true;
496
617
  }
497
618
  /** Текущая сессия целиком. Полезно, чтобы сохранить её самому. */
498
619
  async getSession() {
620
+ this.#assertActive();
499
621
  const session = await this.#loadSession();
500
622
  return session ? copySession(session) : null;
501
623
  }
@@ -506,20 +628,24 @@ var AuthManager = class {
506
628
  * токена идентификатор прежнего владельца остаться не может.
507
629
  */
508
630
  async getUserId() {
631
+ this.#assertActive();
509
632
  const session = await this.#loadSession();
510
633
  return session?.accessToken ? readTokenSubject(session.accessToken) : void 0;
511
634
  }
512
635
  /** Заменяет сессию и связанные с ней cookie целиком. */
513
636
  async setSession(session) {
637
+ this.#assertActive();
638
+ const normalized = normalizeSession(session, "session");
514
639
  await this.#loadSession();
640
+ this.#assertActive();
515
641
  this.#invalidateInFlight();
516
- this.#transitionAuth(session.accessToken);
642
+ this.#transitionAuth(normalized.accessToken);
517
643
  this.#jar.clear();
518
- this.#jar.deserialize(session.cookies);
519
- if (session.deviceId) this.#deviceId = session.deviceId;
520
- this.#session = session;
644
+ this.#jar.deserialize(normalized.cookies);
645
+ if (normalized.deviceId) this.#deviceId = normalized.deviceId;
646
+ this.#session = normalized;
521
647
  this.#seedRefreshCookie();
522
- await this.#saveSession(session);
648
+ await this.#saveSession(normalized);
523
649
  }
524
650
  /**
525
651
  * Забывает сессию и cookie. Сетевой запрос не выполняется.
@@ -528,14 +654,19 @@ var AuthManager = class {
528
654
  * новую запись в списке сессий.
529
655
  */
530
656
  async clear() {
657
+ this.#assertActive();
531
658
  await this.#loadSession();
659
+ this.#assertActive();
532
660
  this.#invalidateInFlight();
533
661
  this.#transitionAuth(void 0);
534
- this.#session = null;
535
662
  this.#jar.clear();
536
- await this.#config.storage.clear();
537
- if (this.#deviceId) await this.#saveSession({ deviceId: this.#deviceId });
663
+ const retained = this.#deviceId ? { deviceId: this.#deviceId } : null;
664
+ this.#session = retained;
538
665
  this.#emitter.emit("signOut", void 0);
666
+ await this.#enqueuePersistence(async () => {
667
+ await this.#config.storage.clear();
668
+ if (retained) await this.#config.storage.set(copySession(retained));
669
+ });
539
670
  }
540
671
  #loadSession() {
541
672
  if (this.#session !== void 0) return Promise.resolve(this.#session);
@@ -546,7 +677,8 @@ var AuthManager = class {
546
677
  }
547
678
  async #performLoad() {
548
679
  const loaded = await this.#config.storage.get() ?? null;
549
- const stored = loaded ? withoutLegacyUserId(loaded) : null;
680
+ this.#assertActive();
681
+ const stored = loaded === null ? null : normalizeSessionValue(loaded, "storage.get()", "stored", this.#config.logger);
550
682
  if (stored?.cookies) this.#jar.deserialize(stored.cookies);
551
683
  const fromConfig = this.#sessionFromConfig(this.#config.auth);
552
684
  this.#session = stored && fromConfig ? {
@@ -568,7 +700,7 @@ var AuthManager = class {
568
700
  if (!this.#config.useCookieJar) return;
569
701
  const refreshToken = this.#session?.refreshToken;
570
702
  if (!refreshToken) return;
571
- if (this.#jar.has("refresh_token")) return;
703
+ if (this.#jar.has("refresh_token", this.#config.baseUrl + "/api/v1/auth")) return;
572
704
  this.#jar.set(this.#config.baseUrl, REFRESH_COOKIE, refreshToken, REFRESH_COOKIE_PATH);
573
705
  }
574
706
  #sessionFromConfig(auth) {
@@ -587,13 +719,19 @@ var AuthManager = class {
587
719
  async #saveSession(session) {
588
720
  const cookies = this.#config.useCookieJar ? this.#jar.serialize() : void 0;
589
721
  const deviceId = session.deviceId ?? this.#deviceId;
722
+ const { cookies: _staleCookies, ...withoutCookies } = copySession(session);
590
723
  const next = withoutLegacyUserId({
591
- ...session,
724
+ ...withoutCookies,
592
725
  ...cookies?.length ? { cookies } : {},
593
726
  ...deviceId ? { deviceId } : {}
594
727
  });
595
728
  this.#session = next;
596
- await this.#config.storage.set(next);
729
+ await this.#enqueuePersistence(() => this.#config.storage.set(copySession(next)));
730
+ }
731
+ #enqueuePersistence(operation) {
732
+ const pending = this.#persistence.then(operation, operation);
733
+ this.#persistence = pending.catch(() => {});
734
+ return pending;
597
735
  }
598
736
  /**
599
737
  * Обновление с дедупликацией.
@@ -609,34 +747,41 @@ var AuthManager = class {
609
747
  return promise;
610
748
  }
611
749
  async #performRefresh() {
750
+ this.#assertActive();
612
751
  await this.#loadSession();
613
752
  if (!this.#hasRefreshSession()) return this.#reloginOrNull();
614
753
  const epoch = this.#authEpoch;
754
+ const flowCookies = this.#jar.clone();
615
755
  try {
616
756
  const accessToken = await this.#http.execute(AUTH_REFRESH, {
617
757
  path: AUTH_PATHS.refresh,
618
758
  skipAuth: true,
619
- skipAuthRefresh: true
759
+ skipAuthRefresh: true,
760
+ cookieJar: flowCookies
620
761
  });
621
- if (!accessToken) return this.#reloginOrNull();
622
762
  if (this.#authEpoch !== epoch) return this.#session?.accessToken ?? null;
763
+ this.#jar.replaceWith(flowCookies);
623
764
  const rotated = this.#jar.getValue(REFRESH_COOKIE, this.#config.baseUrl + REFRESH_COOKIE_PATH);
624
765
  this.#transitionAuth(accessToken, false);
625
- await this.#saveSession({
766
+ const saved = this.#saveSession({
626
767
  ...this.#session ?? {},
627
768
  accessToken,
628
769
  ...rotated ? { refreshToken: rotated } : {},
629
770
  obtainedAt: this.#config.clock.now()
630
771
  });
631
772
  this.#emitter.emit("tokens", { accessToken });
773
+ await saved;
632
774
  return accessToken;
633
775
  } catch (error) {
634
- if (error instanceof ItdApiError) {
776
+ if (error instanceof ItdAuthError) {
635
777
  if (this.#authEpoch !== epoch) return this.#session?.accessToken ?? null;
778
+ this.#invalidateInFlight();
779
+ const clearedRevision = this.#authEpoch;
636
780
  this.#transitionAuth(void 0);
637
781
  this.#session = null;
638
782
  this.#jar.clear();
639
- await this.#config.storage.clear();
783
+ await this.#enqueuePersistence(() => this.#config.storage.clear());
784
+ if (this.#authEpoch !== clearedRevision) return this.#currentAccessToken();
640
785
  const relogged = await this.#reloginOrNull();
641
786
  if (relogged !== null) return relogged;
642
787
  throw error;
@@ -650,7 +795,8 @@ var AuthManager = class {
650
795
  if (!this.#config.reloginOnRefreshFailure || !auth || typeof auth !== "object" || !("email" in auth)) return null;
651
796
  try {
652
797
  return await this.#signInWithCredentials(auth);
653
- } catch {
798
+ } catch (error) {
799
+ this.#config.logger?.warn("Автоматический повторный вход не удался", error);
654
800
  return null;
655
801
  }
656
802
  }
@@ -662,7 +808,8 @@ var AuthManager = class {
662
808
  */
663
809
  #signInWithCredentials(credentials) {
664
810
  if (this.#signingIn) return this.#signingIn;
665
- const promise = this.#performSignIn(credentials).finally(() => {
811
+ const revision = this.#authEpoch;
812
+ const promise = this.#performSignIn(credentials, revision).finally(() => {
666
813
  this.#signingIn = null;
667
814
  });
668
815
  this.#signingIn = promise;
@@ -682,8 +829,9 @@ var AuthManager = class {
682
829
  if (credentials.turnstileToken) return credentials.turnstileToken;
683
830
  throw new ItdConfigError("Вход по email и паролю требует токен капчи Cloudflare Turnstile: без него сервер отвечает 422. Передайте auth.getTurnstileToken (источник свежего токена) либо разовый auth.turnstileToken. Ключ виджета — TURNSTILE_SITE_KEY. В Node токен умеет добывать отдельный пакет: npm i @itd-api/turnstile, затем getTurnstileToken: createTurnstileSolver().");
684
831
  }
685
- async #performSignIn(credentials) {
832
+ async #performSignIn(credentials, revision) {
686
833
  const turnstileToken = await this.#resolveTurnstileToken(credentials);
834
+ const flowCookies = new CookieJar();
687
835
  const result = await this.#http.execute(AUTH_SIGN_IN, {
688
836
  path: AUTH_PATHS.signIn,
689
837
  body: {
@@ -692,18 +840,23 @@ var AuthManager = class {
692
840
  turnstileToken
693
841
  },
694
842
  skipAuth: true,
695
- skipAuthRefresh: true
843
+ skipAuthRefresh: true,
844
+ cookieJar: flowCookies
696
845
  });
697
846
  if (result.status !== SignInStatus.Authenticated) throw new ItdConfigError("Вход по email и паролю требует подтверждения кодом из письма. Автоматический вход невозможен: воспользуйтесь itd.auth.signInWithOtp() и передайте полученный accessToken в конфигурацию клиента.");
698
847
  const accessToken = result.accessToken;
848
+ this.#assertActive();
849
+ if (this.#authEpoch !== revision) return this.#session?.accessToken ?? null;
850
+ this.#jar.replaceWith(flowCookies);
699
851
  this.#invalidateInFlight();
700
852
  this.#transitionAuth(accessToken);
701
- await this.#saveSession({
853
+ const saved = this.#saveSession({
702
854
  accessToken,
703
855
  obtainedAt: this.#config.clock.now()
704
856
  });
705
857
  this.#emitter.emit("tokens", { accessToken });
706
858
  this.#emitter.emit("signIn", { accessToken });
859
+ await saved;
707
860
  return accessToken;
708
861
  }
709
862
  };
@@ -762,14 +915,18 @@ var AuthResource = class extends BaseResource {
762
915
  * @param credentials email, пароль и обязательный токен капчи — см. {@link CaptchaCredentials}
763
916
  */
764
917
  async signIn(credentials, options = {}) {
918
+ const revision = this.#auth.revision();
919
+ const cookies = this.#auth.createCookieFlow(false);
765
920
  const result = await this.http.execute(AUTH_SIGN_IN, {
766
921
  path: AUTH_PATHS.signIn,
767
922
  body: credentials,
768
923
  skipAuth: true,
769
924
  skipAuthRefresh: true,
925
+ cookieJar: cookies,
770
926
  ...options
771
927
  });
772
- if (result.status === SignInStatus.Authenticated) await this.#auth.setAccessToken(result.accessToken);
928
+ if (result.status === SignInStatus.Authenticated) await this.#auth.setAccessToken(result.accessToken, revision, cookies);
929
+ else this.#auth.commitCookieFlow(cookies, revision);
773
930
  return result;
774
931
  }
775
932
  /**
@@ -778,14 +935,17 @@ var AuthResource = class extends BaseResource {
778
935
  * Полученный токен сохраняется в клиенте автоматически.
779
936
  */
780
937
  async verifyOtp(input, options = {}) {
938
+ const revision = this.#auth.revision();
939
+ const cookies = this.#auth.createCookieFlow();
781
940
  const accessToken = await this.http.execute(AUTH_VERIFY_OTP, {
782
941
  path: AUTH_PATHS.verifyOtp,
783
942
  body: input,
784
943
  skipAuth: true,
785
944
  skipAuthRefresh: true,
945
+ cookieJar: cookies,
786
946
  ...options
787
947
  });
788
- await this.#auth.setAccessToken(accessToken);
948
+ await this.#auth.setAccessToken(accessToken, revision, cookies);
789
949
  return accessToken;
790
950
  }
791
951
  /** Отправляет код подтверждения повторно. */
@@ -1704,7 +1864,7 @@ var ItdAccounts = class ItdAccounts {
1704
1864
  })));
1705
1865
  const added = [];
1706
1866
  for (const { name, session } of sessions) {
1707
- if (!isRestorableSession(session)) continue;
1867
+ if (!isRestorableSession(session, this.#base.baseUrl ?? "https://xn--d1ah4a.com")) continue;
1708
1868
  if (this.#clients.has(name) || this.#removing.has(name)) continue;
1709
1869
  this.addAccount(name);
1710
1870
  added.push(name);
@@ -1957,6 +2117,6 @@ function createAccounts(options = {}) {
1957
2117
  return new ItdAccounts(options);
1958
2118
  }
1959
2119
  //#endregion
1960
- 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, createMemoryShopOrderAccessStorage, 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 };
2120
+ 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 };
1961
2121
 
1962
2122
  //# sourceMappingURL=index.js.map