itd-api 0.7.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +72 -27
  2. package/dist/events/index.cjs +53 -0
  3. package/dist/events/index.d.cts +3 -0
  4. package/dist/events/index.d.ts +3 -0
  5. package/dist/events/index.js +5 -0
  6. package/dist/index.cjs +234 -200
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +70 -64
  9. package/dist/index.d.ts +70 -64
  10. package/dist/index.js +202 -179
  11. package/dist/index.js.map +1 -1
  12. package/dist/node/index.cjs +6 -5
  13. package/dist/node/index.cjs.map +1 -1
  14. package/dist/node/index.d.cts +2 -2
  15. package/dist/node/index.d.ts +2 -2
  16. package/dist/node/index.js +5 -4
  17. package/dist/node/index.js.map +1 -1
  18. package/dist/rest/index.cjs +32 -26
  19. package/dist/rest/index.cjs.map +1 -1
  20. package/dist/rest/index.d.cts +9 -7
  21. package/dist/rest/index.d.ts +9 -7
  22. package/dist/rest/index.js +19 -21
  23. package/dist/rest/index.js.map +1 -1
  24. package/dist/shared/{errors-DfU8M5eS.cjs → errors-BmP3TKoW.cjs} +3 -3
  25. package/dist/shared/{errors-DfU8M5eS.cjs.map → errors-BmP3TKoW.cjs.map} +1 -1
  26. package/dist/shared/{errors-Bhrd2fJd.js → errors-GI10kZxk.js} +3 -3
  27. package/dist/shared/{errors-Bhrd2fJd.js.map → errors-GI10kZxk.js.map} +1 -1
  28. package/dist/shared/{websocket-BLR8eVJV.js → events-C5wnTXPT.js} +770 -490
  29. package/dist/shared/events-C5wnTXPT.js.map +1 -0
  30. package/dist/shared/events-CqtkP65d.d.ts +698 -0
  31. package/dist/shared/events-DGAWlCaI.d.cts +698 -0
  32. package/dist/shared/{websocket-C_eI4H2o.cjs → events-FZfnEez0.cjs} +834 -518
  33. package/dist/shared/events-FZfnEez0.cjs.map +1 -0
  34. package/dist/shared/{storage-BPJR_k4-.cjs → key-value-store-B5GYVEYZ.cjs} +2 -86
  35. package/dist/shared/key-value-store-B5GYVEYZ.cjs.map +1 -0
  36. package/dist/shared/{storage-D86edNCB.js → key-value-store-Bt2PSMxY.js} +3 -69
  37. package/dist/shared/key-value-store-Bt2PSMxY.js.map +1 -0
  38. package/dist/shared/key-value-store-COZaNHZS.d.cts +69 -0
  39. package/dist/shared/key-value-store-COZaNHZS.d.ts +69 -0
  40. package/dist/shared/{multi-storage-DccjD7Ww.d.ts → multi-storage-BEL7tcKn.d.cts} +3 -2
  41. package/dist/shared/{multi-storage--yTEqiod.cjs → multi-storage-CDKLa956.cjs} +11 -10
  42. package/dist/shared/{multi-storage--yTEqiod.cjs.map → multi-storage-CDKLa956.cjs.map} +1 -1
  43. package/dist/shared/{multi-storage-CjAPB5Kq.d.cts → multi-storage-LkljOSzA.d.ts} +3 -2
  44. package/dist/shared/{multi-storage-CkvTUC5m.js → multi-storage-SoW05uzO.js} +5 -4
  45. package/dist/shared/{multi-storage-CkvTUC5m.js.map → multi-storage-SoW05uzO.js.map} +1 -1
  46. package/dist/shared/{options-Dg5N3r1V.cjs → options-DOJYtoti.cjs} +8 -6
  47. package/dist/shared/options-DOJYtoti.cjs.map +1 -0
  48. package/dist/shared/{options-DtATYdLr.js → options-DhgGg7Ms.js} +8 -6
  49. package/dist/shared/options-DhgGg7Ms.js.map +1 -0
  50. package/dist/shared/{cookies-DZwFq6kr.cjs → redact-BP00URmQ.cjs} +312 -2
  51. package/dist/shared/redact-BP00URmQ.cjs.map +1 -0
  52. package/dist/shared/{cookies-tX2sNwxb.js → redact-Ba5iSVYZ.js} +235 -3
  53. package/dist/shared/redact-Ba5iSVYZ.js.map +1 -0
  54. package/dist/shared/{render-CgwKdOzu.d.ts → render-B4qis7Sb.d.ts} +775 -313
  55. package/dist/shared/{render-DO0F5YSm.d.cts → render-DDwy2Ame.d.cts} +775 -313
  56. package/dist/shared/{render-mcuELiYi.cjs → render-Dqi8Owqk.cjs} +1464 -662
  57. package/dist/shared/render-Dqi8Owqk.cjs.map +1 -0
  58. package/dist/shared/{render-C6HRPs10.js → render-hiWFh2s4.js} +1366 -618
  59. package/dist/shared/render-hiWFh2s4.js.map +1 -0
  60. package/dist/shared/storage-BQrtKon9.d.cts +86 -0
  61. package/dist/shared/storage-CTRGT5wF.d.ts +86 -0
  62. package/dist/shared/storage-CfHKzVHf.js +71 -0
  63. package/dist/shared/storage-CfHKzVHf.js.map +1 -0
  64. package/dist/shared/storage-QNVxzpDB.cjs +88 -0
  65. package/dist/shared/storage-QNVxzpDB.cjs.map +1 -0
  66. package/dist/shared/{url-BaMCQpYH.cjs → url-Bsb3xE5U.cjs} +577 -466
  67. package/dist/shared/url-Bsb3xE5U.cjs.map +1 -0
  68. package/dist/shared/{url-IU0xN9wX.js → url-DP33mp3s.js} +508 -409
  69. package/dist/shared/url-DP33mp3s.js.map +1 -0
  70. package/dist/shared/{url-DTfZ2toq.d.ts → url-XBsMdAcv.d.cts} +1313 -1200
  71. package/dist/shared/{url-DTfZ2toq.d.cts → url-XBsMdAcv.d.ts} +1313 -1200
  72. package/dist/web/index.cjs +5 -4
  73. package/dist/web/index.cjs.map +1 -1
  74. package/dist/web/index.d.cts +1 -1
  75. package/dist/web/index.d.ts +1 -1
  76. package/dist/web/index.js +3 -2
  77. package/dist/web/index.js.map +1 -1
  78. package/package.json +16 -15
  79. package/dist/realtime/index.cjs +0 -165
  80. package/dist/realtime/index.cjs.map +0 -1
  81. package/dist/realtime/index.d.cts +0 -51
  82. package/dist/realtime/index.d.ts +0 -51
  83. package/dist/realtime/index.js +0 -120
  84. package/dist/realtime/index.js.map +0 -1
  85. package/dist/shared/auth-provider-CG8oCQ9F.cjs +0 -108
  86. package/dist/shared/auth-provider-CG8oCQ9F.cjs.map +0 -1
  87. package/dist/shared/auth-provider-mYqxsSVa.js +0 -91
  88. package/dist/shared/auth-provider-mYqxsSVa.js.map +0 -1
  89. package/dist/shared/cookies-DZwFq6kr.cjs.map +0 -1
  90. package/dist/shared/cookies-tX2sNwxb.js.map +0 -1
  91. package/dist/shared/options-Dg5N3r1V.cjs.map +0 -1
  92. package/dist/shared/options-DtATYdLr.js.map +0 -1
  93. package/dist/shared/render-C6HRPs10.js.map +0 -1
  94. package/dist/shared/render-mcuELiYi.cjs.map +0 -1
  95. package/dist/shared/storage-BPJR_k4-.cjs.map +0 -1
  96. package/dist/shared/storage-C_eICCep.d.cts +0 -152
  97. package/dist/shared/storage-C_eICCep.d.ts +0 -152
  98. package/dist/shared/storage-D86edNCB.js.map +0 -1
  99. package/dist/shared/url-BaMCQpYH.cjs.map +0 -1
  100. package/dist/shared/url-IU0xN9wX.js.map +0 -1
  101. package/dist/shared/websocket-BLR8eVJV.js.map +0 -1
  102. package/dist/shared/websocket-C_eI4H2o.cjs.map +0 -1
  103. package/dist/shared/websocket-DF7XIMiX.d.cts +0 -562
  104. package/dist/shared/websocket-DYKBr8HF.d.ts +0 -562
package/dist/index.js CHANGED
@@ -1,11 +1,64 @@
1
- import { $ as requireOptionalBoolean, A as operationRetrySafety, C as ViewSource, D as isBuiltInOperationId, E as OPERATIONS, F as pickArray, G as assertPluginRemovable, H as RetrySafety, K as orderPluginDefinitions, M as DEFAULT_RATE_LIMIT_BUCKET, N as createClientRuntime, O as operationBucket, Q as isRecord, S as ViewReason, T as ITD_CATALOG, W as RequestQueuePool, X as resolveRateLimit, Y as STATUS_SERVICE, Z as LIBRARY_VERSION, _ as RealtimeStatus, b as ServiceState, c as AccessType, ct as installAsyncDisposeFallback, d as FeedTab, et as RateLimitPacing, f as IncidentKind, g as NotificationType, h as LikesVisibility, j as BUCKET_LIMITS, k as operationMethod, l as AttachmentType, m as ItdErrorCode, n as formatNotificationText, nt as createDeviceId, o as canonicalNotificationType, ot as createDeadline, p as InteractionType, q as DEFAULT_BASE_URL, r as normalizeNotification, s as isKnownNotificationType, st as systemClock, t as resolveNotificationUrl, tt as RuntimeMode, u as CommentSort, v as ReportReason, w as WallAccess, x as SpanType, y as ReportTargetType, z as pickString } from "./shared/url-IU0xN9wX.js";
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-Bhrd2fJd.js";
3
- import { i as REFRESH_COOKIE_PATH, r as REFRESH_COOKIE } from "./shared/cookies-tX2sNwxb.js";
4
- import { C as utcStampToIso, D as mapPage, E as Paginator, O as FeatureRegistry, S as toDate, T as PaginationMode, _ as IMAGE_MIME_TYPES, a as fromStream, b as isBuilder, c as report, d as autoSpans, f as markup, g as AUDIO_MIME_TYPES, h as ALLOWED_MIME_TYPES, i as isMyProfile, k as ExtensibleOperationCatalog, l as post, m as parseMarkdown, n as renderSpans, o as fromUrl, p as parseHtml, r as statusDays, s as createResources, t as SpanRenderFormat, u as poll, v as VIDEO_MIME_TYPES, w as BaseResource, x as createStatusFeature, y as comment } from "./shared/render-C6HRPs10.js";
5
- import { a as RealtimeTransportKind, c as RealtimeUpdateType, d as Emitter, f as reportListenerError, i as ItdRealtime, l as UnauthorizedStreamError, n as RealtimeComposer, o as setRealtimeConnectGuard, r as RealtimeRouter, s as RealtimeUpdateOrigin, t as WebSocketTransport, u as runRealtimeMiddleware } from "./shared/websocket-BLR8eVJV.js";
6
- import { c as isEnumerableKeyValueStore, i as MemoryKeyValueStore, l as withCodec, n as copySession, o as createKeyValueStore, r as createTokenStorage, s as createRecordKeyValueStore, t as MemoryTokenStorage, u as withNamespace } from "./shared/storage-D86edNCB.js";
7
- import { c as FileTransferMode } from "./shared/options-DtATYdLr.js";
8
- import { a as scopedTokenStorage, i as isRestorableSession, n as controlledTokenStorage, r as createMultiTokenStorage, t as MemoryMultiTokenStorage } from "./shared/multi-storage-CkvTUC5m.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-DP33mp3s.js";
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-hiWFh2s4.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-C5wnTXPT.js";
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";
10
+ //#region src/notifications-api.ts
11
+ var ClientNotifications = class extends NotificationsResource {
12
+ #createEvents;
13
+ #events;
14
+ constructor(http, createEvents) {
15
+ super(http);
16
+ this.#createEvents = createEvents;
17
+ }
18
+ get events() {
19
+ this.#events ??= this.#createEvents(this);
20
+ return this.#events;
21
+ }
22
+ };
23
+ /** Создаёт API уведомлений полного клиента. @internal */
24
+ function createNotificationsApi(http, createEvents) {
25
+ return new ClientNotifications(http, createEvents);
26
+ }
27
+ //#endregion
28
+ //#region src/operations/auth.ts
29
+ /** Чем закончился запрос входа. */
30
+ const SignInStatus = Object.freeze({
31
+ Authenticated: "authenticated",
32
+ OtpRequired: "otp_required"
33
+ });
34
+ function requireString(body, key, message) {
35
+ const value = pickString(body, key);
36
+ if (!value) throw new ItdConfigError(message);
37
+ return value;
38
+ }
39
+ const AUTH_CHECK = passthroughOperation("auth.check");
40
+ const AUTH_SIGN_UP = defineBuiltInOperation("auth.signUp", (body) => requireString(body, "flowToken", "Сервер не вернул flowToken при регистрации"));
41
+ const AUTH_SIGN_IN = defineBuiltInOperation("auth.signIn", (body) => {
42
+ const accessToken = pickString(body, "accessToken");
43
+ return accessToken ? {
44
+ status: SignInStatus.Authenticated,
45
+ accessToken
46
+ } : {
47
+ status: SignInStatus.OtpRequired,
48
+ flowToken: pickString(body, "flowToken")
49
+ };
50
+ });
51
+ const AUTH_VERIFY_OTP = defineBuiltInOperation("auth.verifyOtp", (body) => requireString(body, "accessToken", "Сервер не вернул accessToken после подтверждения кода"));
52
+ const AUTH_REFRESH = defineBuiltInOperation("auth.refresh", (body) => pickString(body, "accessToken"));
53
+ const AUTH_RESEND_OTP = voidOperation("auth.resendOtp");
54
+ const AUTH_LOGOUT = voidOperation("auth.logout");
55
+ const AUTH_FORGOT_PASSWORD = defineBuiltInOperation("auth.forgotPassword", (body) => requireString(body, "flowToken", "Сервер не вернул flowToken при запросе сброса пароля"));
56
+ const AUTH_RESET_PASSWORD = voidOperation("auth.resetPassword");
57
+ const AUTH_CHANGE_PASSWORD = voidOperation("auth.changePassword");
58
+ const AUTH_SESSIONS = defineBuiltInOperation("auth.sessions", (body) => pickArray(body, "sessions"));
59
+ const AUTH_REVOKE_SESSION = voidOperation("auth.revokeSession");
60
+ const AUTH_REVOKE_OTHER_SESSIONS = voidOperation("auth.revokeOtherSessions");
61
+ //#endregion
9
62
  //#region src/session/jwt.ts
10
63
  /**
11
64
  * Разбирает строку base64url.
@@ -122,7 +175,7 @@ function resolveSessionConfig(options, runtime) {
122
175
  * достижимой из любой сборки, включая анонимную.
123
176
  */
124
177
  function createItdAuth(options, deps) {
125
- return new AuthManager(resolveSessionConfig(options, deps.config), deps.handler, deps.cookies, { onAccountChange: deps.onAccountChange });
178
+ return new AuthManager(resolveSessionConfig(options, deps.config), deps.http, deps.cookies, { onAccountChange: deps.onAccountChange });
126
179
  }
127
180
  /** Пути эндпоинтов авторизации. */
128
181
  const AUTH_PATHS = {
@@ -157,11 +210,6 @@ const AUTH_PATHS = {
157
210
  * ```
158
211
  */
159
212
  const TURNSTILE_SITE_KEY = "0x4AAAAAACHhxczw6fJGwPBg";
160
- function readAccessToken(payload) {
161
- if (typeof payload !== "object" || payload === null) return void 0;
162
- const token = payload.accessToken;
163
- return typeof token === "string" && token.length > 0 ? token : void 0;
164
- }
165
213
  /** Убирает поле, которое сохраняли промежуточные версии с поддержкой `getUserId()`. */
166
214
  function withoutLegacyUserId(session) {
167
215
  const clean = { ...session };
@@ -183,7 +231,7 @@ function nextAuthScope() {
183
231
  */
184
232
  var AuthManager = class {
185
233
  #config;
186
- #send;
234
+ #http;
187
235
  #jar;
188
236
  #emitter;
189
237
  #hooks;
@@ -222,9 +270,9 @@ var AuthManager = class {
222
270
  * воскресить уже очищенную сессию поверх `signOut`.
223
271
  */
224
272
  #authEpoch = 0;
225
- constructor(config, send, jar, hooks = {}) {
273
+ constructor(config, http, jar, hooks = {}) {
226
274
  this.#config = config;
227
- this.#send = send;
275
+ this.#http = http;
228
276
  this.#jar = jar;
229
277
  this.#hooks = hooks;
230
278
  this.#emitter = new Emitter((error) => reportListenerError(config.logger, "авторизации", error));
@@ -237,7 +285,7 @@ var AuthManager = class {
237
285
  get once() {
238
286
  return this.#emitter.once.bind(this.#emitter);
239
287
  }
240
- /** Снимает lifecycle-подписки при терминальном освобождении владельца. @internal */
288
+ /** Снимает служебные подписки при окончательном освобождении владельца. @internal */
241
289
  dispose() {
242
290
  this.#invalidateInFlight();
243
291
  this.#emitter.removeAllListeners();
@@ -293,7 +341,7 @@ var AuthManager = class {
293
341
  this.#externalIdentity = current;
294
342
  return token;
295
343
  }
296
- /** Меняет fallback и завершает realtime, только если фактически сменился аккаунт. */
344
+ /** Обновляет резервный идентификатор и закрывает события при смене аккаунта. */
297
345
  #transitionAuth(accessToken, rotateFallback = true) {
298
346
  const knownPrevious = this.#session !== void 0;
299
347
  const previous = this.#identityForToken(this.#session?.accessToken);
@@ -565,13 +613,11 @@ var AuthManager = class {
565
613
  if (!this.#hasRefreshSession()) return this.#reloginOrNull();
566
614
  const epoch = this.#authEpoch;
567
615
  try {
568
- const accessToken = readAccessToken(await this.#send({
569
- operationId: "auth.refresh",
570
- method: operationMethod("auth.refresh"),
616
+ const accessToken = await this.#http.execute(AUTH_REFRESH, {
571
617
  path: AUTH_PATHS.refresh,
572
618
  skipAuth: true,
573
619
  skipAuthRefresh: true
574
- }));
620
+ });
575
621
  if (!accessToken) return this.#reloginOrNull();
576
622
  if (this.#authEpoch !== epoch) return this.#session?.accessToken ?? null;
577
623
  const rotated = this.#jar.getValue(REFRESH_COOKIE, this.#config.baseUrl + REFRESH_COOKIE_PATH);
@@ -638,9 +684,7 @@ var AuthManager = class {
638
684
  }
639
685
  async #performSignIn(credentials) {
640
686
  const turnstileToken = await this.#resolveTurnstileToken(credentials);
641
- const accessToken = readAccessToken(await this.#send({
642
- operationId: "auth.signIn",
643
- method: operationMethod("auth.signIn"),
687
+ const result = await this.#http.execute(AUTH_SIGN_IN, {
644
688
  path: AUTH_PATHS.signIn,
645
689
  body: {
646
690
  email: credentials.email,
@@ -649,8 +693,9 @@ var AuthManager = class {
649
693
  },
650
694
  skipAuth: true,
651
695
  skipAuthRefresh: true
652
- }));
653
- if (!accessToken) throw new ItdConfigError("Вход по email и паролю требует подтверждения кодом из письма. Автоматический вход невозможен: воспользуйтесь itd.auth.signInWithOtp() и передайте полученный accessToken в конфигурацию клиента.");
696
+ });
697
+ if (result.status !== SignInStatus.Authenticated) throw new ItdConfigError("Вход по email и паролю требует подтверждения кодом из письма. Автоматический вход невозможен: воспользуйтесь itd.auth.signInWithOtp() и передайте полученный accessToken в конфигурацию клиента.");
698
+ const accessToken = result.accessToken;
654
699
  this.#invalidateInFlight();
655
700
  this.#transitionAuth(accessToken);
656
701
  await this.#saveSession({
@@ -664,11 +709,12 @@ var AuthManager = class {
664
709
  };
665
710
  //#endregion
666
711
  //#region src/resources/auth.ts
667
- /** Чем закончился вход: сразу токеном или запросом кода подтверждения. */
668
- const SignInStatus = Object.freeze({
669
- Authenticated: "authenticated",
670
- OtpRequired: "otp_required"
671
- });
712
+ /**
713
+ * Результат входа.
714
+ *
715
+ * Сервер может как сразу выдать токен, так и потребовать код подтверждения — размеченное
716
+ * объединение делает оба случая явными.
717
+ */
672
718
  /**
673
719
  * Авторизация, сессии и пароли.
674
720
  *
@@ -686,7 +732,7 @@ var AuthResource = class extends BaseResource {
686
732
  * Без авторизации возвращает `authenticated: false` и `user: null`.
687
733
  */
688
734
  check(options = {}) {
689
- return this.http.operation("auth.check", {
735
+ return this.http.execute(AUTH_CHECK, {
690
736
  path: "/api/profile",
691
737
  ...options
692
738
  });
@@ -696,17 +742,14 @@ var AuthResource = class extends BaseResource {
696
742
  *
697
743
  * @returns `flowToken`, который нужно передать в {@link verifyOtp}
698
744
  */
699
- async signUp(credentials, options = {}) {
700
- const body = await this.http.operation("auth.signUp", {
745
+ signUp(credentials, options = {}) {
746
+ return this.http.execute(AUTH_SIGN_UP, {
701
747
  path: AUTH_PATHS.signUp,
702
748
  body: credentials,
703
749
  skipAuth: true,
704
750
  skipAuthRefresh: true,
705
751
  ...options
706
752
  });
707
- const flowToken = pickString(body, "flowToken");
708
- if (!flowToken) throw new ItdConfigError("Сервер не вернул flowToken при регистрации");
709
- return flowToken;
710
753
  }
711
754
  /**
712
755
  * Выполняет вход.
@@ -719,25 +762,15 @@ var AuthResource = class extends BaseResource {
719
762
  * @param credentials email, пароль и обязательный токен капчи — см. {@link CaptchaCredentials}
720
763
  */
721
764
  async signIn(credentials, options = {}) {
722
- const body = await this.http.operation("auth.signIn", {
765
+ const result = await this.http.execute(AUTH_SIGN_IN, {
723
766
  path: AUTH_PATHS.signIn,
724
767
  body: credentials,
725
768
  skipAuth: true,
726
769
  skipAuthRefresh: true,
727
770
  ...options
728
771
  });
729
- const accessToken = pickString(body, "accessToken");
730
- if (accessToken) {
731
- await this.#auth.setAccessToken(accessToken);
732
- return {
733
- status: SignInStatus.Authenticated,
734
- accessToken
735
- };
736
- }
737
- return {
738
- status: SignInStatus.OtpRequired,
739
- flowToken: pickString(body, "flowToken")
740
- };
772
+ if (result.status === SignInStatus.Authenticated) await this.#auth.setAccessToken(result.accessToken);
773
+ return result;
741
774
  }
742
775
  /**
743
776
  * Подтверждает вход кодом из письма.
@@ -745,21 +778,19 @@ var AuthResource = class extends BaseResource {
745
778
  * Полученный токен сохраняется в клиенте автоматически.
746
779
  */
747
780
  async verifyOtp(input, options = {}) {
748
- const body = await this.http.operation("auth.verifyOtp", {
781
+ const accessToken = await this.http.execute(AUTH_VERIFY_OTP, {
749
782
  path: AUTH_PATHS.verifyOtp,
750
783
  body: input,
751
784
  skipAuth: true,
752
785
  skipAuthRefresh: true,
753
786
  ...options
754
787
  });
755
- const accessToken = pickString(body, "accessToken");
756
- if (!accessToken) throw new ItdConfigError("Сервер не вернул accessToken после подтверждения кода");
757
788
  await this.#auth.setAccessToken(accessToken);
758
789
  return accessToken;
759
790
  }
760
791
  /** Отправляет код подтверждения повторно. */
761
792
  resendOtp(input, options = {}) {
762
- return this.http.operation("auth.resendOtp", {
793
+ return this.voidOperation(AUTH_RESEND_OTP, {
763
794
  path: AUTH_PATHS.resendOtp,
764
795
  body: input,
765
796
  skipAuth: true,
@@ -828,7 +859,7 @@ var AuthResource = class extends BaseResource {
828
859
  }
829
860
  /** Завершает текущую сессию на сервере и очищает локальную. */
830
861
  async logout(options = {}) {
831
- await this.http.operation("auth.logout", {
862
+ await this.voidOperation(AUTH_LOGOUT, {
832
863
  path: AUTH_PATHS.logout,
833
864
  skipAuthRefresh: true,
834
865
  ...options
@@ -856,17 +887,14 @@ var AuthResource = class extends BaseResource {
856
887
  *
857
888
  * @returns `flowToken`, который нужно передать в {@link resetPassword}
858
889
  */
859
- async forgotPassword(input, options = {}) {
860
- const body = await this.http.operation("auth.forgotPassword", {
890
+ forgotPassword(input, options = {}) {
891
+ return this.http.execute(AUTH_FORGOT_PASSWORD, {
861
892
  path: AUTH_PATHS.forgotPassword,
862
893
  body: input,
863
894
  skipAuth: true,
864
895
  skipAuthRefresh: true,
865
896
  ...options
866
897
  });
867
- const flowToken = pickString(body, "flowToken");
868
- if (!flowToken) throw new ItdConfigError("Сервер не вернул flowToken при запросе сброса пароля");
869
- return flowToken;
870
898
  }
871
899
  /**
872
900
  * Устанавливает новый пароль по коду из письма.
@@ -875,7 +903,7 @@ var AuthResource = class extends BaseResource {
875
903
  * при нехватке любого отвечает `422`.
876
904
  */
877
905
  resetPassword(input, options = {}) {
878
- return this.http.operation("auth.resetPassword", {
906
+ return this.voidOperation(AUTH_RESET_PASSWORD, {
879
907
  path: AUTH_PATHS.resetPassword,
880
908
  body: input,
881
909
  skipAuth: true,
@@ -923,7 +951,7 @@ var AuthResource = class extends BaseResource {
923
951
  * ```
924
952
  */
925
953
  changePassword(input, options = {}) {
926
- return this.http.operation("auth.changePassword", {
954
+ return this.voidOperation(AUTH_CHANGE_PASSWORD, {
927
955
  path: AUTH_PATHS.changePassword,
928
956
  body: {
929
957
  currentPassword: input.currentPassword,
@@ -933,23 +961,22 @@ var AuthResource = class extends BaseResource {
933
961
  });
934
962
  }
935
963
  /** Загружает список активных сессий. У текущей поле `isCurrent` равно `true`. */
936
- async sessions(options = {}) {
937
- const body = await this.http.operation("auth.sessions", {
964
+ sessions(options = {}) {
965
+ return this.http.execute(AUTH_SESSIONS, {
938
966
  path: AUTH_PATHS.sessions,
939
967
  ...options
940
968
  });
941
- return pickArray(body, "sessions");
942
969
  }
943
970
  /** Завершает указанную сессию. */
944
971
  revokeSession(sessionId, options = {}) {
945
- return this.http.operation("auth.revokeSession", {
972
+ return this.voidOperation(AUTH_REVOKE_SESSION, {
946
973
  path: `${AUTH_PATHS.sessions}/${encodeURIComponent(sessionId)}`,
947
974
  ...options
948
975
  });
949
976
  }
950
977
  /** Завершает все сессии, кроме текущей. */
951
978
  revokeOtherSessions(options = {}) {
952
- return this.http.operation("auth.revokeOtherSessions", {
979
+ return this.voidOperation(AUTH_REVOKE_OTHER_SESSIONS, {
953
980
  path: AUTH_PATHS.sessions,
954
981
  ...options
955
982
  });
@@ -1013,8 +1040,6 @@ function createManagedClient(options, internals) {
1013
1040
  var ItdClient = class ItdClient {
1014
1041
  #runtime;
1015
1042
  #features;
1016
- /** Порождённые потоки уведомлений — чтобы `close()` мог закрыть их разом. */
1017
- #streams = /* @__PURE__ */ new Set();
1018
1043
  /** Общий результат терминальной очистки для идемпотентных повторных вызовов. */
1019
1044
  #disposePromise;
1020
1045
  /** Ресурсы конвейера запросов; создаются при первом обращении. */
@@ -1074,34 +1099,76 @@ var ItdClient = class ItdClient {
1074
1099
  get telemetry() {
1075
1100
  return this.#resources.telemetry;
1076
1101
  }
1102
+ /** Каталог, доставка и заказы магазина ИТД. */
1103
+ get shop() {
1104
+ return this.#resources.shop;
1105
+ }
1077
1106
  constructor(options = {}) {
1107
+ const notificationEventsOptions = resolveNotificationEventsOptions(options.events?.notifications);
1078
1108
  const internals = CLIENT_INTERNALS.get(options) ?? {};
1079
1109
  const catalog = new ExtensibleOperationCatalog(ITD_CATALOG);
1110
+ let featureHost;
1080
1111
  this.#runtime = createClientRuntime(options, {
1081
1112
  catalog,
1082
1113
  queues: internals.queues,
1083
1114
  assertActive: (action) => assertClientActive(this, action),
1084
1115
  auth: (deps) => createItdAuth(options, {
1085
1116
  ...deps,
1086
- onAccountChange: () => this.#disconnectStreams()
1117
+ onAccountChange: () => featureHost?.stop()
1087
1118
  })
1088
1119
  });
1089
- this.#features = new FeatureRegistry({
1090
- http: this.#runtime.http,
1091
- services: this.#runtime.services,
1092
- serviceOverrides: this.#runtime.config.services,
1120
+ this.#features = featureHost = createFeatureHost(this.#runtime, {
1093
1121
  catalog,
1094
- baseUrl: this.#runtime.config.baseUrl,
1095
- clock: this.#runtime.config.clock,
1096
- logger: this.#runtime.config.logger,
1097
- assertActive: (action) => assertClientActive(this, action),
1098
- registerBucket: (name, definition) => this.#runtime.registerRateLimitBucket(name, definition)
1122
+ assertActive: (action) => assertClientActive(this, action)
1099
1123
  });
1100
1124
  const status = this.#features.install(createStatusFeature());
1101
1125
  this.#resources = createResources({
1102
1126
  http: this.#runtime.http,
1103
- fetch: this.#runtime.config.fetch,
1104
- status
1127
+ files: this.#features.files,
1128
+ status,
1129
+ createNotifications: (http) => createNotificationsApi(http, (notifications) => {
1130
+ let events;
1131
+ let lifecycleGeneration = 0;
1132
+ let lifecycleActive = false;
1133
+ let unregister;
1134
+ const releaseAfterDrain = (generation) => {
1135
+ events.drain().then(() => {
1136
+ if (lifecycleActive || generation !== lifecycleGeneration) return;
1137
+ unregister?.();
1138
+ unregister = void 0;
1139
+ });
1140
+ };
1141
+ events = new NotificationEvents({
1142
+ connection: this.#runtime.connection(),
1143
+ request: ({ operationId, path, query, signal }) => this.#runtime.http.execute(EVENT_OPERATIONS[operationId], {
1144
+ path,
1145
+ query,
1146
+ signal
1147
+ }),
1148
+ getAuthIdentity: () => this.#runtime.auth.getCurrentAuthIdentity(),
1149
+ getAuthScope: () => this.#runtime.auth.getAuthScope(),
1150
+ fetchUnreadCount: (signal) => notifications.count({ signal }),
1151
+ onConnect: () => {
1152
+ lifecycleGeneration += 1;
1153
+ unregister ??= this.#features.manage({
1154
+ kind: `notifications:${events.transport}`,
1155
+ stop: () => {
1156
+ if (lifecycleActive) events.disconnect();
1157
+ },
1158
+ drain: () => events.drain()
1159
+ });
1160
+ lifecycleActive = true;
1161
+ },
1162
+ onClose: () => {
1163
+ lifecycleActive = false;
1164
+ releaseAfterDrain(++lifecycleGeneration);
1165
+ },
1166
+ logger: this.#runtime.config.logger,
1167
+ clock: this.#runtime.config.clock
1168
+ }, notificationEventsOptions);
1169
+ setNotificationEventsConnectGuard(events, () => assertClientActive(this, "подключить канал событий уведомлений"));
1170
+ return events;
1171
+ })
1105
1172
  });
1106
1173
  CLIENT_PLUGIN_REGISTRIES.set(this, this.#runtime.plugins);
1107
1174
  }
@@ -1145,19 +1212,19 @@ var ItdClient = class ItdClient {
1145
1212
  return this.#runtime.rateLimitState();
1146
1213
  }
1147
1214
  /**
1148
- * Устанавливает предметный модуль поверх общей сессии и request pipeline клиента.
1215
+ * Устанавливает предметный модуль с общей сессией и обработкой запросов клиента.
1149
1216
  *
1150
- * Сервисы, операции и бакеты feature регистрируются до синхронного `setup()`. Возвращаемое
1151
- * значение — типизированный API модуля; повторное имя feature отклоняется.
1217
+ * Сервисы, операции и бакеты регистрируются до синхронного `setup()`. Возвращает
1218
+ * типизированный API модуля.
1152
1219
  */
1153
1220
  install(feature) {
1154
1221
  return this.#features.install(feature);
1155
1222
  }
1156
1223
  /**
1157
- * Устанавливает feature и публикует его API как readonly-свойство этого же клиента.
1224
+ * Устанавливает модуль и добавляет его API в свойство клиента только для чтения.
1158
1225
  *
1159
1226
  * Возвращаемое пересечение сохраняет тип уже подключённых свойств, поэтому вызовы можно
1160
- * объединять в цепочку: `new ItdClient().withFeature('chats', chatsFeature)`.
1227
+ * объединять в цепочку: `new ItdClient().withFeature('pixelBattle', pixelBattleFeature)`.
1161
1228
  */
1162
1229
  withFeature(key, feature) {
1163
1230
  assertClientActive(this, "установить feature");
@@ -1173,20 +1240,18 @@ var ItdClient = class ItdClient {
1173
1240
  });
1174
1241
  return this;
1175
1242
  }
1176
- /** Имена установленных feature в порядке установки. */
1243
+ /** Имена установленных модулей в порядке установки. */
1177
1244
  featureNames() {
1178
1245
  return this.#features.names();
1179
1246
  }
1180
- /** Установлен ли feature с таким именем. */
1247
+ /** Установлен ли модуль с таким именем. */
1181
1248
  hasFeature(name) {
1182
1249
  return this.#features.has(name);
1183
1250
  }
1184
1251
  /**
1185
1252
  * Подключает плагин.
1186
1253
  *
1187
- * Плагин может независимо регистрировать transformer логической операции и interceptor
1188
- * транспортной попытки. Оба контракта охватывают все методы клиента. Подключать плагин
1189
- * можно в любой момент, но обычно это делают сразу после создания клиента.
1254
+ * Плагин может регистрировать обёртки операций и перехватчики сетевых попыток.
1190
1255
  *
1191
1256
  * @throws {ItdConfigError} если плагин задан неверно или уже подключён
1192
1257
  * @throws {ItdStateError} если клиент уже освобождён через {@link dispose}
@@ -1289,85 +1354,27 @@ var ItdClient = class ItdClient {
1289
1354
  return this.#runtime.auth.on(event, listener);
1290
1355
  }
1291
1356
  /**
1292
- * Создаёт поток уведомлений в реальном времени.
1293
- *
1294
- * Каждый вызов даёт новый независимый поток; обычно он нужен один на приложение.
1295
- * Соединение поднимается методом `connect()` и держится само. Замена авторизации на токен
1296
- * другого пользователя завершает все потоки клиента; смена только сессии их не затрагивает.
1297
- *
1298
- * @example
1299
- * ```ts
1300
- * import { NotificationType } from 'itd-api';
1301
- *
1302
- * const stream = itd.realtime();
1303
- *
1304
- * stream.onNotification(NotificationType.PostComment, async ({ update }) => {
1305
- * await handleComment(update.data.notification);
1306
- * });
1307
- * stream.on('unreadCount', (count) => setBadge(count));
1308
- *
1309
- * await stream.connect();
1310
- * ```
1311
- *
1312
- * @throws {ItdStateError} если клиент уже освобождён через {@link dispose}
1313
- */
1314
- realtime(options = {}) {
1315
- assertClientActive(this, "создать realtime-поток");
1316
- let stream;
1317
- stream = new ItdRealtime({
1318
- baseUrl: this.#runtime.config.baseUrl,
1319
- fetch: this.#runtime.config.fetch,
1320
- request: ({ operationId, path, query, signal }) => this.#runtime.http.operation(operationId, {
1321
- path,
1322
- query,
1323
- signal
1324
- }),
1325
- baseHeaders: (url) => this.#runtime.platformHeaders(url),
1326
- getAuthIdentity: () => this.#runtime.auth.getCurrentAuthIdentity(),
1327
- getAuthScope: () => this.#runtime.auth.getAuthScope(),
1328
- getToken: () => this.#runtime.auth.token(),
1329
- refresh: () => this.#runtime.auth.onUnauthorized(),
1330
- fetchUnreadCount: () => this.notifications.count(),
1331
- onConnect: () => this.#streams.add(stream),
1332
- onClose: () => this.#streams.delete(stream),
1333
- logger: this.#runtime.config.logger,
1334
- clock: this.#runtime.config.clock
1335
- }, options);
1336
- setRealtimeConnectGuard(stream, () => assertClientActive(this, "подключить realtime-поток"));
1337
- this.#streams.add(stream);
1338
- return stream;
1339
- }
1340
- /**
1341
1357
  * Освобождает ресурсы клиента: закрывает все потоки уведомлений, отправляет открытые
1342
1358
  * накопители {@link telemetry}, затем останавливает очередь запросов.
1343
1359
  *
1344
- * Метод дожидается активных обработчиков потока, но не дольше `shutdownTimeout`. После
1345
- * вызова клиентом можно пользоваться снова; ранее созданный поток можно запустить
1346
- * повторным `connect()`.
1360
+ * Метод дожидается завершения активных обработчиков и освобождения ресурсов остановленных
1361
+ * событийных соединений, но не дольше `shutdownTimeout`. После вызова клиентом можно
1362
+ * пользоваться снова; ранее созданный поток можно запустить повторным `connect()`.
1347
1363
  *
1348
1364
  * Общая очередь, полученная от {@link ItdAccounts}, не останавливается: её гасит сам
1349
1365
  * контейнер, когда закрывает все аккаунты разом.
1350
1366
  *
1351
1367
  * Терминальное освобождение — это {@link dispose}.
1352
1368
  *
1353
- * @throws {ItdStateError} если обработчики потока не завершились за отведённый срок
1369
+ * @throws {ItdStateError} если остановка событийного соединения не завершилась за отведённый срок
1354
1370
  */
1355
1371
  async close() {
1356
1372
  return this.#close(false);
1357
1373
  }
1358
1374
  async #close(disposeCleanup) {
1359
- const streams = this.#disconnectStreams();
1360
1375
  if (disposeCleanup) this.#resources.prepareTelemetryClose();
1361
- const { shutdownTimeout, clock } = this.#runtime.config;
1362
- const deadline = createDeadline(shutdownTimeout, clock);
1363
- let stuck = [];
1364
1376
  const errors = [];
1365
1377
  try {
1366
- try {
1367
- stuck = (await Promise.all(streams.map(async (stream) => await deadline.wait(stream.drain()) ? void 0 : stream))).filter((stream) => stream !== void 0);
1368
- } catch (error) {
1369
- errors.push(error);
1370
- }
1371
1378
  try {
1372
1379
  await this.#features.close();
1373
1380
  } catch (error) {
@@ -1379,10 +1386,8 @@ var ItdClient = class ItdClient {
1379
1386
  errors.push(error);
1380
1387
  }
1381
1388
  } finally {
1382
- deadline.cancel();
1383
1389
  this.#runtime.close();
1384
1390
  }
1385
- if (stuck.length > 0) errors.push(new ItdStateError(`обработчики потоков (${stuck.map((stream) => stream.transport).join(", ")}) не завершились за ${shutdownTimeout} мс; ожидание прекращено`));
1386
1391
  if (errors.length === 1) throw errors[0];
1387
1392
  if (errors.length > 1) throw new AggregateError(errors, "Не удалось закрыть клиент");
1388
1393
  }
@@ -1392,10 +1397,11 @@ var ItdClient = class ItdClient {
1392
1397
  *
1393
1398
  * Терминальное состояние устанавливается сразу при первом вызове. После этого новые
1394
1399
  * запросы, подключение плагинов, регистрация сервисов и создание или повторный запуск
1395
- * realtime-потоков завершаются с {@link ItdStateError}. Повторные вызовы возвращают
1400
+ * событийных каналов завершаются с {@link ItdStateError}. Повторные вызовы возвращают
1396
1401
  * тот же результат очистки.
1397
1402
  *
1398
- * Ожидание обработчиков потока и операций плагинов ограничено `shutdownTimeout`.
1403
+ * Ожидание остановки событийных соединений и операций плагинов ограничено
1404
+ * `shutdownTimeout`.
1399
1405
  *
1400
1406
  * @example
1401
1407
  * ```ts
@@ -1429,13 +1435,6 @@ var ItdClient = class ItdClient {
1429
1435
  }
1430
1436
  if (errors.length > 0) throw new AggregateError(errors, "Не удалось освободить клиент");
1431
1437
  }
1432
- /** Завершает потоки до того, как запросы начнут использовать другой аккаунт. */
1433
- #disconnectStreams() {
1434
- const streams = [...this.#streams];
1435
- for (const stream of streams) stream.disconnect();
1436
- this.#streams.clear();
1437
- return streams;
1438
- }
1439
1438
  /** Позволяет использовать клиент с `await using`. */
1440
1439
  [Symbol.asyncDispose]() {
1441
1440
  return this.dispose();
@@ -1501,6 +1500,25 @@ function createClient(options = {}) {
1501
1500
  function validateAccountName(name) {
1502
1501
  if (typeof name !== "string" || name.trim() === "") throw new ItdConfigError("имя аккаунта должно быть непустой строкой");
1503
1502
  }
1503
+ /** Проверяет фабрики до создания первого аккаунта. */
1504
+ function resolveAccountFeatures(features) {
1505
+ if (features === void 0) return [];
1506
+ if (!Array.isArray(features)) throw new ItdConfigError("features должен быть массивом фабрик подключаемых модулей");
1507
+ const keys = /* @__PURE__ */ new Set();
1508
+ return features.map((feature, index) => {
1509
+ if (typeof feature !== "object" || feature === null || typeof feature.create !== "function") throw new ItdConfigError(`features[${index}] должен предоставлять create()`);
1510
+ if (feature.key !== void 0) {
1511
+ if (typeof feature.key !== "string" || feature.key.trim() === "" || feature.key !== feature.key.trim()) throw new ItdConfigError(`features[${index}].key должен быть непустой строкой без пробелов`);
1512
+ if (keys.has(feature.key)) throw new ItdConfigError(`Свойство подключаемого модуля «${feature.key}» объявлено повторно`);
1513
+ keys.add(feature.key);
1514
+ }
1515
+ const create = feature.create.bind(feature);
1516
+ return Object.freeze({
1517
+ ...feature.key === void 0 ? {} : { key: feature.key },
1518
+ create
1519
+ });
1520
+ });
1521
+ }
1504
1522
  const DISPOSED_ACCOUNTS = /* @__PURE__ */ new WeakSet();
1505
1523
  function assertAccountsActive(accounts, action) {
1506
1524
  if (!DISPOSED_ACCOUNTS.has(accounts)) return;
@@ -1552,6 +1570,8 @@ var ItdAccounts = class ItdAccounts {
1552
1570
  #eventUnsubscribers = /* @__PURE__ */ new Map();
1553
1571
  /** Плагины для всех: и для уже заведённых аккаунтов, и для будущих. */
1554
1572
  #plugins;
1573
+ /** Фабрики модулей для новых и восстанавливаемых аккаунтов. */
1574
+ #features;
1555
1575
  /** Имена плагинов, чья асинхронная очистка ещё не завершилась. */
1556
1576
  #removingPlugins = /* @__PURE__ */ new Set();
1557
1577
  /** Общая очередь. `undefined`, когда у каждого аккаунта своя. */
@@ -1562,11 +1582,12 @@ var ItdAccounts = class ItdAccounts {
1562
1582
  /** Общий результат терминальной очистки для идемпотентных повторных вызовов. */
1563
1583
  #disposePromise;
1564
1584
  constructor(options = {}) {
1565
- const { storage, plugins, rateLimitScope, ...base } = options;
1585
+ const { storage, plugins, features, rateLimitScope, ...base } = options;
1566
1586
  if (rateLimitScope !== void 0 && rateLimitScope !== "account" && rateLimitScope !== "shared") throw new ItdConfigError("rateLimitScope должен быть 'account' или 'shared'");
1567
1587
  this.#base = base;
1568
1588
  this.#storage = storage ?? new MemoryMultiTokenStorage();
1569
1589
  this.#plugins = orderPluginDefinitions(plugins ?? []);
1590
+ this.#features = resolveAccountFeatures(features);
1570
1591
  this.#rateLimitScope = rateLimitScope ?? "shared";
1571
1592
  const rateLimit = this.#rateLimitScope === "shared" ? resolveRateLimit(base.rateLimit, ITD_CATALOG) : void 0;
1572
1593
  this.#queues = rateLimit ? new RequestQueuePool(rateLimit, base.clock ?? systemClock) : void 0;
@@ -1593,7 +1614,7 @@ var ItdAccounts = class ItdAccounts {
1593
1614
  /**
1594
1615
  * Заводит аккаунт.
1595
1616
  *
1596
- * Возвращается обычный `ItdClient` — со всеми ресурсами, плагинами и `realtime()`.
1617
+ * Возвращается обычный `ItdClient` — со всеми ресурсами, плагинами и каналом событий.
1597
1618
  * Хранилище ему подставляется само: срез общего по имени аккаунта.
1598
1619
  *
1599
1620
  * Опция `auth` не обязательна: когда сессия этого аккаунта уже лежит в хранилище,
@@ -1619,10 +1640,15 @@ var ItdAccounts = class ItdAccounts {
1619
1640
  try {
1620
1641
  client = createManagedClient(this.#mergeOptions(options, storageControl.storage), this.#queues ? { queues: this.#queues } : {});
1621
1642
  for (const plugin of this.#plugins) client.use(plugin);
1643
+ for (const feature of this.#features) {
1644
+ const manifest = feature.create();
1645
+ if (feature.key === void 0) client.install(manifest);
1646
+ else client.withFeature(feature.key, manifest);
1647
+ }
1622
1648
  } catch (error) {
1623
1649
  storageControl.revoke();
1624
1650
  if (client) client.dispose().catch((cleanupError) => {
1625
- this.#reportPluginCleanup("неудачного добавления аккаунта", cleanupError);
1651
+ this.#reportCleanup("неудачного добавления аккаунта", cleanupError);
1626
1652
  });
1627
1653
  throw error;
1628
1654
  }
@@ -1746,7 +1772,7 @@ var ItdAccounts = class ItdAccounts {
1746
1772
  }
1747
1773
  } catch (error) {
1748
1774
  for (const client of installed.reverse()) client.unuse(plugin.name).catch((cleanupError) => {
1749
- this.#reportPluginCleanup(`отката плагина «${plugin.name}»`, cleanupError);
1775
+ this.#reportCleanup(`отката плагина «${plugin.name}»`, cleanupError);
1750
1776
  });
1751
1777
  throw error;
1752
1778
  }
@@ -1862,6 +1888,7 @@ var ItdAccounts = class ItdAccounts {
1862
1888
  Promise.resolve().then(() => this.#queues?.clear())
1863
1889
  ]);
1864
1890
  this.#plugins.splice(0);
1891
+ this.#features.splice(0);
1865
1892
  this.#removingPlugins.clear();
1866
1893
  const errors = results.filter((result) => result.status === "rejected").map((result) => result.reason);
1867
1894
  if (errors.length > 0) throw new AggregateError(errors, "Не удалось освободить аккаунты");
@@ -1871,11 +1898,7 @@ var ItdAccounts = class ItdAccounts {
1871
1898
  return this.dispose();
1872
1899
  }
1873
1900
  static {
1874
- if (typeof Symbol.asyncDispose !== "symbol") {
1875
- const prototype = ItdAccounts.prototype;
1876
- prototype[Symbol.for("Symbol.asyncDispose")] = prototype.undefined;
1877
- delete prototype.undefined;
1878
- }
1901
+ installAsyncDisposeFallback(ItdAccounts);
1879
1902
  }
1880
1903
  /**
1881
1904
  * Собирает опции клиента: общие, поверх них — аккаунтные, и обязательно свой срез
@@ -1900,9 +1923,9 @@ var ItdAccounts = class ItdAccounts {
1900
1923
  storage
1901
1924
  };
1902
1925
  }
1903
- /** Не теряет ошибку фонового teardown, который синхронный API не может await-нуть. */
1904
- #reportPluginCleanup(scope, error) {
1905
- const message = `Не удалось завершить teardown после ${scope}`;
1926
+ /** Не теряет ошибку фонового освобождения ресурсов, которого синхронный API не ждёт. */
1927
+ #reportCleanup(scope, error) {
1928
+ const message = `Не удалось освободить ресурсы после ${scope}`;
1906
1929
  if (this.#logger) this.#logger.error(message, error);
1907
1930
  else console.error(`[itd-api] ${message}`, error);
1908
1931
  }
@@ -1934,6 +1957,6 @@ function createAccounts(options = {}) {
1934
1957
  return new ItdAccounts(options);
1935
1958
  }
1936
1959
  //#endregion
1937
- export { ALLOWED_MIME_TYPES, AUDIO_MIME_TYPES, AccessType, AttachmentType, BUCKET_LIMITS, CommentSort, DEFAULT_BASE_URL, DEFAULT_RATE_LIMIT_BUCKET, 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, ItdRealtime, ItdServerError, ItdStateError, ItdTimeoutError, ItdValidationError, LIBRARY_VERSION, LikesVisibility, MemoryKeyValueStore, MemoryMultiTokenStorage, MemoryTokenStorage, NotificationType, OPERATIONS, PaginationMode, Paginator, RateLimitPacing, RealtimeComposer, RealtimeRouter, RealtimeStatus, RealtimeTransportKind, RealtimeUpdateOrigin, RealtimeUpdateType, ReportReason, ReportTargetType, RetrySafety, RuntimeMode, STATUS_SERVICE, ServiceState, SignInStatus, SpanRenderFormat, SpanType, TURNSTILE_SITE_KEY, UnauthorizedStreamError, VIDEO_MIME_TYPES, ViewReason, ViewSource, WallAccess, WebSocketTransport, autoSpans, canonicalNotificationType, comment, createAccounts, createClient, createKeyValueStore, createMultiTokenStorage, createRecordKeyValueStore, 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, runRealtimeMiddleware, scopedTokenStorage, statusDays, systemClock, toDate, utcStampToIso, withCodec, withNamespace };
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 };
1938
1961
 
1939
1962
  //# sourceMappingURL=index.js.map