itd-api 0.7.2 → 0.8.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 (84) hide show
  1. package/README.md +12 -12
  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 +215 -194
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +66 -63
  9. package/dist/index.d.ts +66 -63
  10. package/dist/index.js +197 -179
  11. package/dist/index.js.map +1 -1
  12. package/dist/node/index.cjs +4 -4
  13. package/dist/node/index.d.cts +1 -1
  14. package/dist/node/index.d.ts +1 -1
  15. package/dist/node/index.js +4 -4
  16. package/dist/rest/index.cjs +20 -26
  17. package/dist/rest/index.cjs.map +1 -1
  18. package/dist/rest/index.d.cts +7 -7
  19. package/dist/rest/index.d.ts +7 -7
  20. package/dist/rest/index.js +14 -20
  21. package/dist/rest/index.js.map +1 -1
  22. package/dist/shared/{errors-DfU8M5eS.cjs → errors-BmP3TKoW.cjs} +3 -3
  23. package/dist/shared/{errors-DfU8M5eS.cjs.map → errors-BmP3TKoW.cjs.map} +1 -1
  24. package/dist/shared/{errors-Bhrd2fJd.js → errors-GI10kZxk.js} +3 -3
  25. package/dist/shared/{errors-Bhrd2fJd.js.map → errors-GI10kZxk.js.map} +1 -1
  26. package/dist/shared/events-Br2r7coi.d.cts +698 -0
  27. package/dist/shared/events-QyhQZrnC.d.ts +698 -0
  28. package/dist/shared/{websocket-BLR8eVJV.js → events-TfhKh26b.js} +770 -490
  29. package/dist/shared/events-TfhKh26b.js.map +1 -0
  30. package/dist/shared/{websocket-C_eI4H2o.cjs → events-iTteVsd_.cjs} +834 -518
  31. package/dist/shared/events-iTteVsd_.cjs.map +1 -0
  32. package/dist/shared/{multi-storage-CkvTUC5m.js → multi-storage-1AEZTczk.js} +4 -4
  33. package/dist/shared/{multi-storage-CkvTUC5m.js.map → multi-storage-1AEZTczk.js.map} +1 -1
  34. package/dist/shared/{multi-storage--yTEqiod.cjs → multi-storage-CCUgkqvI.cjs} +6 -6
  35. package/dist/shared/{multi-storage--yTEqiod.cjs.map → multi-storage-CCUgkqvI.cjs.map} +1 -1
  36. package/dist/shared/{options-DtATYdLr.js → options-BNP633A5.js} +8 -6
  37. package/dist/shared/options-BNP633A5.js.map +1 -0
  38. package/dist/shared/{options-Dg5N3r1V.cjs → options-BUfoHE9G.cjs} +8 -6
  39. package/dist/shared/options-BUfoHE9G.cjs.map +1 -0
  40. package/dist/shared/{cookies-DZwFq6kr.cjs → redact-DB9EMGJx.cjs} +301 -2
  41. package/dist/shared/redact-DB9EMGJx.cjs.map +1 -0
  42. package/dist/shared/{cookies-tX2sNwxb.js → redact-FNK29Fv-.js} +224 -3
  43. package/dist/shared/redact-FNK29Fv-.js.map +1 -0
  44. package/dist/shared/{render-DO0F5YSm.d.cts → render-BszcKF7S.d.cts} +430 -313
  45. package/dist/shared/{render-C6HRPs10.js → render-CiL1VS0b.js} +925 -582
  46. package/dist/shared/render-CiL1VS0b.js.map +1 -0
  47. package/dist/shared/{render-mcuELiYi.cjs → render-DNTESBSl.cjs} +967 -618
  48. package/dist/shared/render-DNTESBSl.cjs.map +1 -0
  49. package/dist/shared/{render-CgwKdOzu.d.ts → render-DVB7T8pm.d.ts} +430 -313
  50. package/dist/shared/{storage-BPJR_k4-.cjs → storage-BLmXmzKF.cjs} +2 -2
  51. package/dist/shared/{storage-BPJR_k4-.cjs.map → storage-BLmXmzKF.cjs.map} +1 -1
  52. package/dist/shared/{storage-D86edNCB.js → storage-CCxn0Kkv.js} +2 -2
  53. package/dist/shared/{storage-D86edNCB.js.map → storage-CCxn0Kkv.js.map} +1 -1
  54. package/dist/shared/{url-IU0xN9wX.js → url-B3ocFlHj.js} +434 -408
  55. package/dist/shared/url-B3ocFlHj.js.map +1 -0
  56. package/dist/shared/{url-DTfZ2toq.d.cts → url-Be9jlWRv.d.cts} +1228 -1188
  57. package/dist/shared/{url-DTfZ2toq.d.ts → url-Be9jlWRv.d.ts} +1228 -1188
  58. package/dist/shared/{url-BaMCQpYH.cjs → url-CFN1sT9S.cjs} +497 -465
  59. package/dist/shared/url-CFN1sT9S.cjs.map +1 -0
  60. package/dist/web/index.cjs +2 -2
  61. package/dist/web/index.js +2 -2
  62. package/package.json +16 -15
  63. package/dist/realtime/index.cjs +0 -165
  64. package/dist/realtime/index.cjs.map +0 -1
  65. package/dist/realtime/index.d.cts +0 -51
  66. package/dist/realtime/index.d.ts +0 -51
  67. package/dist/realtime/index.js +0 -120
  68. package/dist/realtime/index.js.map +0 -1
  69. package/dist/shared/auth-provider-CG8oCQ9F.cjs +0 -108
  70. package/dist/shared/auth-provider-CG8oCQ9F.cjs.map +0 -1
  71. package/dist/shared/auth-provider-mYqxsSVa.js +0 -91
  72. package/dist/shared/auth-provider-mYqxsSVa.js.map +0 -1
  73. package/dist/shared/cookies-DZwFq6kr.cjs.map +0 -1
  74. package/dist/shared/cookies-tX2sNwxb.js.map +0 -1
  75. package/dist/shared/options-Dg5N3r1V.cjs.map +0 -1
  76. package/dist/shared/options-DtATYdLr.js.map +0 -1
  77. package/dist/shared/render-C6HRPs10.js.map +0 -1
  78. package/dist/shared/render-mcuELiYi.cjs.map +0 -1
  79. package/dist/shared/url-BaMCQpYH.cjs.map +0 -1
  80. package/dist/shared/url-IU0xN9wX.js.map +0 -1
  81. package/dist/shared/websocket-BLR8eVJV.js.map +0 -1
  82. package/dist/shared/websocket-C_eI4H2o.cjs.map +0 -1
  83. package/dist/shared/websocket-DF7XIMiX.d.cts +0 -562
  84. package/dist/shared/websocket-DYKBr8HF.d.ts +0 -562
package/dist/index.js CHANGED
@@ -1,11 +1,63 @@
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 DEFAULT_BASE_URL, 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 orderPluginDefinitions, R as DEFAULT_RATE_LIMIT_BUCKET, S as NotificationType, T as ServiceState, V as pickArray, X as RequestQueuePool, Z as assertPluginRemovable, _ as FeedTab, at as requireOptionalBoolean, b as ItdErrorCode, c as normalizeNotification, ct as systemClock, d as canonicalNotificationType, f as isKnownNotificationType, g as EventChannelStatus, h as CommentSort, it as isRecord, j as OPERATIONS, k as WallAccess, lt as installAsyncDisposeFallback, m as AttachmentType, n as formatNotificationText, nt as resolveRateLimit, o as passthroughOperation, ot as RateLimitPacing, p as AccessType, rt as LIBRARY_VERSION, s as voidOperation, t as resolveNotificationUrl, tt as STATUS_SERVICE, v as IncidentKind, w as ReportTargetType, x as LikesVisibility, y as InteractionType, z as createClientRuntime } from "./shared/url-B3ocFlHj.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-FNK29Fv-.js";
4
+ import { A as ExtensibleOperationCatalog, C as utcStampToIso, D as Paginator, E as PaginationMode, O as mapPage, S as toDate, T as BaseResource, _ 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 createFeatureHost, 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 NotificationsResource, x as createStatusFeature, y as comment } from "./shared/render-CiL1VS0b.js";
5
+ import { c as FileTransferMode } from "./shared/options-BNP633A5.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-TfhKh26b.js";
7
+ 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-CCxn0Kkv.js";
8
+ import { a as scopedTokenStorage, i as isRestorableSession, n as controlledTokenStorage, r as createMultiTokenStorage, t as MemoryMultiTokenStorage } from "./shared/multi-storage-1AEZTczk.js";
9
+ //#region src/notifications-api.ts
10
+ var ClientNotifications = class extends NotificationsResource {
11
+ #createEvents;
12
+ #events;
13
+ constructor(http, createEvents) {
14
+ super(http);
15
+ this.#createEvents = createEvents;
16
+ }
17
+ get events() {
18
+ this.#events ??= this.#createEvents(this);
19
+ return this.#events;
20
+ }
21
+ };
22
+ /** Создаёт API уведомлений полного клиента. @internal */
23
+ function createNotificationsApi(http, createEvents) {
24
+ return new ClientNotifications(http, createEvents);
25
+ }
26
+ //#endregion
27
+ //#region src/operations/auth.ts
28
+ /** Чем закончился запрос входа. */
29
+ const SignInStatus = Object.freeze({
30
+ Authenticated: "authenticated",
31
+ OtpRequired: "otp_required"
32
+ });
33
+ function requireString(body, key, message) {
34
+ const value = pickString(body, key);
35
+ if (!value) throw new ItdConfigError(message);
36
+ return value;
37
+ }
38
+ const AUTH_CHECK = passthroughOperation("auth.check");
39
+ const AUTH_SIGN_UP = defineBuiltInOperation("auth.signUp", (body) => requireString(body, "flowToken", "Сервер не вернул flowToken при регистрации"));
40
+ const AUTH_SIGN_IN = defineBuiltInOperation("auth.signIn", (body) => {
41
+ const accessToken = pickString(body, "accessToken");
42
+ return accessToken ? {
43
+ status: SignInStatus.Authenticated,
44
+ accessToken
45
+ } : {
46
+ status: SignInStatus.OtpRequired,
47
+ flowToken: pickString(body, "flowToken")
48
+ };
49
+ });
50
+ const AUTH_VERIFY_OTP = defineBuiltInOperation("auth.verifyOtp", (body) => requireString(body, "accessToken", "Сервер не вернул accessToken после подтверждения кода"));
51
+ const AUTH_REFRESH = defineBuiltInOperation("auth.refresh", (body) => pickString(body, "accessToken"));
52
+ const AUTH_RESEND_OTP = voidOperation("auth.resendOtp");
53
+ const AUTH_LOGOUT = voidOperation("auth.logout");
54
+ const AUTH_FORGOT_PASSWORD = defineBuiltInOperation("auth.forgotPassword", (body) => requireString(body, "flowToken", "Сервер не вернул flowToken при запросе сброса пароля"));
55
+ const AUTH_RESET_PASSWORD = voidOperation("auth.resetPassword");
56
+ const AUTH_CHANGE_PASSWORD = voidOperation("auth.changePassword");
57
+ const AUTH_SESSIONS = defineBuiltInOperation("auth.sessions", (body) => pickArray(body, "sessions"));
58
+ const AUTH_REVOKE_SESSION = voidOperation("auth.revokeSession");
59
+ const AUTH_REVOKE_OTHER_SESSIONS = voidOperation("auth.revokeOtherSessions");
60
+ //#endregion
9
61
  //#region src/session/jwt.ts
10
62
  /**
11
63
  * Разбирает строку base64url.
@@ -122,7 +174,7 @@ function resolveSessionConfig(options, runtime) {
122
174
  * достижимой из любой сборки, включая анонимную.
123
175
  */
124
176
  function createItdAuth(options, deps) {
125
- return new AuthManager(resolveSessionConfig(options, deps.config), deps.handler, deps.cookies, { onAccountChange: deps.onAccountChange });
177
+ return new AuthManager(resolveSessionConfig(options, deps.config), deps.http, deps.cookies, { onAccountChange: deps.onAccountChange });
126
178
  }
127
179
  /** Пути эндпоинтов авторизации. */
128
180
  const AUTH_PATHS = {
@@ -157,11 +209,6 @@ const AUTH_PATHS = {
157
209
  * ```
158
210
  */
159
211
  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
212
  /** Убирает поле, которое сохраняли промежуточные версии с поддержкой `getUserId()`. */
166
213
  function withoutLegacyUserId(session) {
167
214
  const clean = { ...session };
@@ -183,7 +230,7 @@ function nextAuthScope() {
183
230
  */
184
231
  var AuthManager = class {
185
232
  #config;
186
- #send;
233
+ #http;
187
234
  #jar;
188
235
  #emitter;
189
236
  #hooks;
@@ -222,9 +269,9 @@ var AuthManager = class {
222
269
  * воскресить уже очищенную сессию поверх `signOut`.
223
270
  */
224
271
  #authEpoch = 0;
225
- constructor(config, send, jar, hooks = {}) {
272
+ constructor(config, http, jar, hooks = {}) {
226
273
  this.#config = config;
227
- this.#send = send;
274
+ this.#http = http;
228
275
  this.#jar = jar;
229
276
  this.#hooks = hooks;
230
277
  this.#emitter = new Emitter((error) => reportListenerError(config.logger, "авторизации", error));
@@ -237,7 +284,7 @@ var AuthManager = class {
237
284
  get once() {
238
285
  return this.#emitter.once.bind(this.#emitter);
239
286
  }
240
- /** Снимает lifecycle-подписки при терминальном освобождении владельца. @internal */
287
+ /** Снимает служебные подписки при окончательном освобождении владельца. @internal */
241
288
  dispose() {
242
289
  this.#invalidateInFlight();
243
290
  this.#emitter.removeAllListeners();
@@ -293,7 +340,7 @@ var AuthManager = class {
293
340
  this.#externalIdentity = current;
294
341
  return token;
295
342
  }
296
- /** Меняет fallback и завершает realtime, только если фактически сменился аккаунт. */
343
+ /** Обновляет резервный идентификатор и закрывает события при смене аккаунта. */
297
344
  #transitionAuth(accessToken, rotateFallback = true) {
298
345
  const knownPrevious = this.#session !== void 0;
299
346
  const previous = this.#identityForToken(this.#session?.accessToken);
@@ -565,13 +612,11 @@ var AuthManager = class {
565
612
  if (!this.#hasRefreshSession()) return this.#reloginOrNull();
566
613
  const epoch = this.#authEpoch;
567
614
  try {
568
- const accessToken = readAccessToken(await this.#send({
569
- operationId: "auth.refresh",
570
- method: operationMethod("auth.refresh"),
615
+ const accessToken = await this.#http.execute(AUTH_REFRESH, {
571
616
  path: AUTH_PATHS.refresh,
572
617
  skipAuth: true,
573
618
  skipAuthRefresh: true
574
- }));
619
+ });
575
620
  if (!accessToken) return this.#reloginOrNull();
576
621
  if (this.#authEpoch !== epoch) return this.#session?.accessToken ?? null;
577
622
  const rotated = this.#jar.getValue(REFRESH_COOKIE, this.#config.baseUrl + REFRESH_COOKIE_PATH);
@@ -638,9 +683,7 @@ var AuthManager = class {
638
683
  }
639
684
  async #performSignIn(credentials) {
640
685
  const turnstileToken = await this.#resolveTurnstileToken(credentials);
641
- const accessToken = readAccessToken(await this.#send({
642
- operationId: "auth.signIn",
643
- method: operationMethod("auth.signIn"),
686
+ const result = await this.#http.execute(AUTH_SIGN_IN, {
644
687
  path: AUTH_PATHS.signIn,
645
688
  body: {
646
689
  email: credentials.email,
@@ -649,8 +692,9 @@ var AuthManager = class {
649
692
  },
650
693
  skipAuth: true,
651
694
  skipAuthRefresh: true
652
- }));
653
- if (!accessToken) throw new ItdConfigError("Вход по email и паролю требует подтверждения кодом из письма. Автоматический вход невозможен: воспользуйтесь itd.auth.signInWithOtp() и передайте полученный accessToken в конфигурацию клиента.");
695
+ });
696
+ if (result.status !== SignInStatus.Authenticated) throw new ItdConfigError("Вход по email и паролю требует подтверждения кодом из письма. Автоматический вход невозможен: воспользуйтесь itd.auth.signInWithOtp() и передайте полученный accessToken в конфигурацию клиента.");
697
+ const accessToken = result.accessToken;
654
698
  this.#invalidateInFlight();
655
699
  this.#transitionAuth(accessToken);
656
700
  await this.#saveSession({
@@ -664,11 +708,12 @@ var AuthManager = class {
664
708
  };
665
709
  //#endregion
666
710
  //#region src/resources/auth.ts
667
- /** Чем закончился вход: сразу токеном или запросом кода подтверждения. */
668
- const SignInStatus = Object.freeze({
669
- Authenticated: "authenticated",
670
- OtpRequired: "otp_required"
671
- });
711
+ /**
712
+ * Результат входа.
713
+ *
714
+ * Сервер может как сразу выдать токен, так и потребовать код подтверждения — размеченное
715
+ * объединение делает оба случая явными.
716
+ */
672
717
  /**
673
718
  * Авторизация, сессии и пароли.
674
719
  *
@@ -686,7 +731,7 @@ var AuthResource = class extends BaseResource {
686
731
  * Без авторизации возвращает `authenticated: false` и `user: null`.
687
732
  */
688
733
  check(options = {}) {
689
- return this.http.operation("auth.check", {
734
+ return this.http.execute(AUTH_CHECK, {
690
735
  path: "/api/profile",
691
736
  ...options
692
737
  });
@@ -696,17 +741,14 @@ var AuthResource = class extends BaseResource {
696
741
  *
697
742
  * @returns `flowToken`, который нужно передать в {@link verifyOtp}
698
743
  */
699
- async signUp(credentials, options = {}) {
700
- const body = await this.http.operation("auth.signUp", {
744
+ signUp(credentials, options = {}) {
745
+ return this.http.execute(AUTH_SIGN_UP, {
701
746
  path: AUTH_PATHS.signUp,
702
747
  body: credentials,
703
748
  skipAuth: true,
704
749
  skipAuthRefresh: true,
705
750
  ...options
706
751
  });
707
- const flowToken = pickString(body, "flowToken");
708
- if (!flowToken) throw new ItdConfigError("Сервер не вернул flowToken при регистрации");
709
- return flowToken;
710
752
  }
711
753
  /**
712
754
  * Выполняет вход.
@@ -719,25 +761,15 @@ var AuthResource = class extends BaseResource {
719
761
  * @param credentials email, пароль и обязательный токен капчи — см. {@link CaptchaCredentials}
720
762
  */
721
763
  async signIn(credentials, options = {}) {
722
- const body = await this.http.operation("auth.signIn", {
764
+ const result = await this.http.execute(AUTH_SIGN_IN, {
723
765
  path: AUTH_PATHS.signIn,
724
766
  body: credentials,
725
767
  skipAuth: true,
726
768
  skipAuthRefresh: true,
727
769
  ...options
728
770
  });
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
- };
771
+ if (result.status === SignInStatus.Authenticated) await this.#auth.setAccessToken(result.accessToken);
772
+ return result;
741
773
  }
742
774
  /**
743
775
  * Подтверждает вход кодом из письма.
@@ -745,21 +777,19 @@ var AuthResource = class extends BaseResource {
745
777
  * Полученный токен сохраняется в клиенте автоматически.
746
778
  */
747
779
  async verifyOtp(input, options = {}) {
748
- const body = await this.http.operation("auth.verifyOtp", {
780
+ const accessToken = await this.http.execute(AUTH_VERIFY_OTP, {
749
781
  path: AUTH_PATHS.verifyOtp,
750
782
  body: input,
751
783
  skipAuth: true,
752
784
  skipAuthRefresh: true,
753
785
  ...options
754
786
  });
755
- const accessToken = pickString(body, "accessToken");
756
- if (!accessToken) throw new ItdConfigError("Сервер не вернул accessToken после подтверждения кода");
757
787
  await this.#auth.setAccessToken(accessToken);
758
788
  return accessToken;
759
789
  }
760
790
  /** Отправляет код подтверждения повторно. */
761
791
  resendOtp(input, options = {}) {
762
- return this.http.operation("auth.resendOtp", {
792
+ return this.voidOperation(AUTH_RESEND_OTP, {
763
793
  path: AUTH_PATHS.resendOtp,
764
794
  body: input,
765
795
  skipAuth: true,
@@ -828,7 +858,7 @@ var AuthResource = class extends BaseResource {
828
858
  }
829
859
  /** Завершает текущую сессию на сервере и очищает локальную. */
830
860
  async logout(options = {}) {
831
- await this.http.operation("auth.logout", {
861
+ await this.voidOperation(AUTH_LOGOUT, {
832
862
  path: AUTH_PATHS.logout,
833
863
  skipAuthRefresh: true,
834
864
  ...options
@@ -856,17 +886,14 @@ var AuthResource = class extends BaseResource {
856
886
  *
857
887
  * @returns `flowToken`, который нужно передать в {@link resetPassword}
858
888
  */
859
- async forgotPassword(input, options = {}) {
860
- const body = await this.http.operation("auth.forgotPassword", {
889
+ forgotPassword(input, options = {}) {
890
+ return this.http.execute(AUTH_FORGOT_PASSWORD, {
861
891
  path: AUTH_PATHS.forgotPassword,
862
892
  body: input,
863
893
  skipAuth: true,
864
894
  skipAuthRefresh: true,
865
895
  ...options
866
896
  });
867
- const flowToken = pickString(body, "flowToken");
868
- if (!flowToken) throw new ItdConfigError("Сервер не вернул flowToken при запросе сброса пароля");
869
- return flowToken;
870
897
  }
871
898
  /**
872
899
  * Устанавливает новый пароль по коду из письма.
@@ -875,7 +902,7 @@ var AuthResource = class extends BaseResource {
875
902
  * при нехватке любого отвечает `422`.
876
903
  */
877
904
  resetPassword(input, options = {}) {
878
- return this.http.operation("auth.resetPassword", {
905
+ return this.voidOperation(AUTH_RESET_PASSWORD, {
879
906
  path: AUTH_PATHS.resetPassword,
880
907
  body: input,
881
908
  skipAuth: true,
@@ -923,7 +950,7 @@ var AuthResource = class extends BaseResource {
923
950
  * ```
924
951
  */
925
952
  changePassword(input, options = {}) {
926
- return this.http.operation("auth.changePassword", {
953
+ return this.voidOperation(AUTH_CHANGE_PASSWORD, {
927
954
  path: AUTH_PATHS.changePassword,
928
955
  body: {
929
956
  currentPassword: input.currentPassword,
@@ -933,23 +960,22 @@ var AuthResource = class extends BaseResource {
933
960
  });
934
961
  }
935
962
  /** Загружает список активных сессий. У текущей поле `isCurrent` равно `true`. */
936
- async sessions(options = {}) {
937
- const body = await this.http.operation("auth.sessions", {
963
+ sessions(options = {}) {
964
+ return this.http.execute(AUTH_SESSIONS, {
938
965
  path: AUTH_PATHS.sessions,
939
966
  ...options
940
967
  });
941
- return pickArray(body, "sessions");
942
968
  }
943
969
  /** Завершает указанную сессию. */
944
970
  revokeSession(sessionId, options = {}) {
945
- return this.http.operation("auth.revokeSession", {
971
+ return this.voidOperation(AUTH_REVOKE_SESSION, {
946
972
  path: `${AUTH_PATHS.sessions}/${encodeURIComponent(sessionId)}`,
947
973
  ...options
948
974
  });
949
975
  }
950
976
  /** Завершает все сессии, кроме текущей. */
951
977
  revokeOtherSessions(options = {}) {
952
- return this.http.operation("auth.revokeOtherSessions", {
978
+ return this.voidOperation(AUTH_REVOKE_OTHER_SESSIONS, {
953
979
  path: AUTH_PATHS.sessions,
954
980
  ...options
955
981
  });
@@ -1013,8 +1039,6 @@ function createManagedClient(options, internals) {
1013
1039
  var ItdClient = class ItdClient {
1014
1040
  #runtime;
1015
1041
  #features;
1016
- /** Порождённые потоки уведомлений — чтобы `close()` мог закрыть их разом. */
1017
- #streams = /* @__PURE__ */ new Set();
1018
1042
  /** Общий результат терминальной очистки для идемпотентных повторных вызовов. */
1019
1043
  #disposePromise;
1020
1044
  /** Ресурсы конвейера запросов; создаются при первом обращении. */
@@ -1075,33 +1099,71 @@ var ItdClient = class ItdClient {
1075
1099
  return this.#resources.telemetry;
1076
1100
  }
1077
1101
  constructor(options = {}) {
1102
+ const notificationEventsOptions = resolveNotificationEventsOptions(options.events?.notifications);
1078
1103
  const internals = CLIENT_INTERNALS.get(options) ?? {};
1079
1104
  const catalog = new ExtensibleOperationCatalog(ITD_CATALOG);
1105
+ let featureHost;
1080
1106
  this.#runtime = createClientRuntime(options, {
1081
1107
  catalog,
1082
1108
  queues: internals.queues,
1083
1109
  assertActive: (action) => assertClientActive(this, action),
1084
1110
  auth: (deps) => createItdAuth(options, {
1085
1111
  ...deps,
1086
- onAccountChange: () => this.#disconnectStreams()
1112
+ onAccountChange: () => featureHost?.stop()
1087
1113
  })
1088
1114
  });
1089
- this.#features = new FeatureRegistry({
1090
- http: this.#runtime.http,
1091
- services: this.#runtime.services,
1092
- serviceOverrides: this.#runtime.config.services,
1115
+ this.#features = featureHost = createFeatureHost(this.#runtime, {
1093
1116
  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)
1117
+ assertActive: (action) => assertClientActive(this, action)
1099
1118
  });
1100
1119
  const status = this.#features.install(createStatusFeature());
1101
1120
  this.#resources = createResources({
1102
1121
  http: this.#runtime.http,
1103
- fetch: this.#runtime.config.fetch,
1104
- status
1122
+ files: this.#features.files,
1123
+ status,
1124
+ createNotifications: (http) => createNotificationsApi(http, (notifications) => {
1125
+ let events;
1126
+ let lifecycleGeneration = 0;
1127
+ let lifecycleActive = false;
1128
+ let unregister;
1129
+ const releaseAfterDrain = (generation) => {
1130
+ events.drain().then(() => {
1131
+ if (lifecycleActive || generation !== lifecycleGeneration) return;
1132
+ unregister?.();
1133
+ unregister = void 0;
1134
+ });
1135
+ };
1136
+ events = new NotificationEvents({
1137
+ connection: this.#runtime.connection(),
1138
+ request: ({ operationId, path, query, signal }) => this.#runtime.http.execute(EVENT_OPERATIONS[operationId], {
1139
+ path,
1140
+ query,
1141
+ signal
1142
+ }),
1143
+ getAuthIdentity: () => this.#runtime.auth.getCurrentAuthIdentity(),
1144
+ getAuthScope: () => this.#runtime.auth.getAuthScope(),
1145
+ fetchUnreadCount: (signal) => notifications.count({ signal }),
1146
+ onConnect: () => {
1147
+ lifecycleGeneration += 1;
1148
+ unregister ??= this.#features.manage({
1149
+ kind: `notifications:${events.transport}`,
1150
+ stop: () => {
1151
+ if (lifecycleActive) events.disconnect();
1152
+ },
1153
+ drain: () => events.drain()
1154
+ });
1155
+ lifecycleActive = true;
1156
+ },
1157
+ onClose: () => {
1158
+ lifecycleActive = false;
1159
+ releaseAfterDrain(++lifecycleGeneration);
1160
+ },
1161
+ logger: this.#runtime.config.logger,
1162
+ clock: this.#runtime.config.clock
1163
+ }, notificationEventsOptions);
1164
+ setNotificationEventsConnectGuard(events, () => assertClientActive(this, "подключить канал событий уведомлений"));
1165
+ return events;
1166
+ })
1105
1167
  });
1106
1168
  CLIENT_PLUGIN_REGISTRIES.set(this, this.#runtime.plugins);
1107
1169
  }
@@ -1145,19 +1207,19 @@ var ItdClient = class ItdClient {
1145
1207
  return this.#runtime.rateLimitState();
1146
1208
  }
1147
1209
  /**
1148
- * Устанавливает предметный модуль поверх общей сессии и request pipeline клиента.
1210
+ * Устанавливает предметный модуль с общей сессией и обработкой запросов клиента.
1149
1211
  *
1150
- * Сервисы, операции и бакеты feature регистрируются до синхронного `setup()`. Возвращаемое
1151
- * значение — типизированный API модуля; повторное имя feature отклоняется.
1212
+ * Сервисы, операции и бакеты регистрируются до синхронного `setup()`. Возвращает
1213
+ * типизированный API модуля.
1152
1214
  */
1153
1215
  install(feature) {
1154
1216
  return this.#features.install(feature);
1155
1217
  }
1156
1218
  /**
1157
- * Устанавливает feature и публикует его API как readonly-свойство этого же клиента.
1219
+ * Устанавливает модуль и добавляет его API в свойство клиента только для чтения.
1158
1220
  *
1159
1221
  * Возвращаемое пересечение сохраняет тип уже подключённых свойств, поэтому вызовы можно
1160
- * объединять в цепочку: `new ItdClient().withFeature('chats', chatsFeature)`.
1222
+ * объединять в цепочку: `new ItdClient().withFeature('pixelBattle', pixelBattleFeature)`.
1161
1223
  */
1162
1224
  withFeature(key, feature) {
1163
1225
  assertClientActive(this, "установить feature");
@@ -1173,20 +1235,18 @@ var ItdClient = class ItdClient {
1173
1235
  });
1174
1236
  return this;
1175
1237
  }
1176
- /** Имена установленных feature в порядке установки. */
1238
+ /** Имена установленных модулей в порядке установки. */
1177
1239
  featureNames() {
1178
1240
  return this.#features.names();
1179
1241
  }
1180
- /** Установлен ли feature с таким именем. */
1242
+ /** Установлен ли модуль с таким именем. */
1181
1243
  hasFeature(name) {
1182
1244
  return this.#features.has(name);
1183
1245
  }
1184
1246
  /**
1185
1247
  * Подключает плагин.
1186
1248
  *
1187
- * Плагин может независимо регистрировать transformer логической операции и interceptor
1188
- * транспортной попытки. Оба контракта охватывают все методы клиента. Подключать плагин
1189
- * можно в любой момент, но обычно это делают сразу после создания клиента.
1249
+ * Плагин может регистрировать обёртки операций и перехватчики сетевых попыток.
1190
1250
  *
1191
1251
  * @throws {ItdConfigError} если плагин задан неверно или уже подключён
1192
1252
  * @throws {ItdStateError} если клиент уже освобождён через {@link dispose}
@@ -1289,85 +1349,27 @@ var ItdClient = class ItdClient {
1289
1349
  return this.#runtime.auth.on(event, listener);
1290
1350
  }
1291
1351
  /**
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
1352
  * Освобождает ресурсы клиента: закрывает все потоки уведомлений, отправляет открытые
1342
1353
  * накопители {@link telemetry}, затем останавливает очередь запросов.
1343
1354
  *
1344
- * Метод дожидается активных обработчиков потока, но не дольше `shutdownTimeout`. После
1345
- * вызова клиентом можно пользоваться снова; ранее созданный поток можно запустить
1346
- * повторным `connect()`.
1355
+ * Метод дожидается завершения активных обработчиков и освобождения ресурсов остановленных
1356
+ * событийных соединений, но не дольше `shutdownTimeout`. После вызова клиентом можно
1357
+ * пользоваться снова; ранее созданный поток можно запустить повторным `connect()`.
1347
1358
  *
1348
1359
  * Общая очередь, полученная от {@link ItdAccounts}, не останавливается: её гасит сам
1349
1360
  * контейнер, когда закрывает все аккаунты разом.
1350
1361
  *
1351
1362
  * Терминальное освобождение — это {@link dispose}.
1352
1363
  *
1353
- * @throws {ItdStateError} если обработчики потока не завершились за отведённый срок
1364
+ * @throws {ItdStateError} если остановка событийного соединения не завершилась за отведённый срок
1354
1365
  */
1355
1366
  async close() {
1356
1367
  return this.#close(false);
1357
1368
  }
1358
1369
  async #close(disposeCleanup) {
1359
- const streams = this.#disconnectStreams();
1360
1370
  if (disposeCleanup) this.#resources.prepareTelemetryClose();
1361
- const { shutdownTimeout, clock } = this.#runtime.config;
1362
- const deadline = createDeadline(shutdownTimeout, clock);
1363
- let stuck = [];
1364
1371
  const errors = [];
1365
1372
  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
1373
  try {
1372
1374
  await this.#features.close();
1373
1375
  } catch (error) {
@@ -1379,10 +1381,8 @@ var ItdClient = class ItdClient {
1379
1381
  errors.push(error);
1380
1382
  }
1381
1383
  } finally {
1382
- deadline.cancel();
1383
1384
  this.#runtime.close();
1384
1385
  }
1385
- if (stuck.length > 0) errors.push(new ItdStateError(`обработчики потоков (${stuck.map((stream) => stream.transport).join(", ")}) не завершились за ${shutdownTimeout} мс; ожидание прекращено`));
1386
1386
  if (errors.length === 1) throw errors[0];
1387
1387
  if (errors.length > 1) throw new AggregateError(errors, "Не удалось закрыть клиент");
1388
1388
  }
@@ -1392,10 +1392,11 @@ var ItdClient = class ItdClient {
1392
1392
  *
1393
1393
  * Терминальное состояние устанавливается сразу при первом вызове. После этого новые
1394
1394
  * запросы, подключение плагинов, регистрация сервисов и создание или повторный запуск
1395
- * realtime-потоков завершаются с {@link ItdStateError}. Повторные вызовы возвращают
1395
+ * событийных каналов завершаются с {@link ItdStateError}. Повторные вызовы возвращают
1396
1396
  * тот же результат очистки.
1397
1397
  *
1398
- * Ожидание обработчиков потока и операций плагинов ограничено `shutdownTimeout`.
1398
+ * Ожидание остановки событийных соединений и операций плагинов ограничено
1399
+ * `shutdownTimeout`.
1399
1400
  *
1400
1401
  * @example
1401
1402
  * ```ts
@@ -1429,13 +1430,6 @@ var ItdClient = class ItdClient {
1429
1430
  }
1430
1431
  if (errors.length > 0) throw new AggregateError(errors, "Не удалось освободить клиент");
1431
1432
  }
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
1433
  /** Позволяет использовать клиент с `await using`. */
1440
1434
  [Symbol.asyncDispose]() {
1441
1435
  return this.dispose();
@@ -1501,6 +1495,25 @@ function createClient(options = {}) {
1501
1495
  function validateAccountName(name) {
1502
1496
  if (typeof name !== "string" || name.trim() === "") throw new ItdConfigError("имя аккаунта должно быть непустой строкой");
1503
1497
  }
1498
+ /** Проверяет фабрики до создания первого аккаунта. */
1499
+ function resolveAccountFeatures(features) {
1500
+ if (features === void 0) return [];
1501
+ if (!Array.isArray(features)) throw new ItdConfigError("features должен быть массивом фабрик подключаемых модулей");
1502
+ const keys = /* @__PURE__ */ new Set();
1503
+ return features.map((feature, index) => {
1504
+ if (typeof feature !== "object" || feature === null || typeof feature.create !== "function") throw new ItdConfigError(`features[${index}] должен предоставлять create()`);
1505
+ if (feature.key !== void 0) {
1506
+ if (typeof feature.key !== "string" || feature.key.trim() === "" || feature.key !== feature.key.trim()) throw new ItdConfigError(`features[${index}].key должен быть непустой строкой без пробелов`);
1507
+ if (keys.has(feature.key)) throw new ItdConfigError(`Свойство подключаемого модуля «${feature.key}» объявлено повторно`);
1508
+ keys.add(feature.key);
1509
+ }
1510
+ const create = feature.create.bind(feature);
1511
+ return Object.freeze({
1512
+ ...feature.key === void 0 ? {} : { key: feature.key },
1513
+ create
1514
+ });
1515
+ });
1516
+ }
1504
1517
  const DISPOSED_ACCOUNTS = /* @__PURE__ */ new WeakSet();
1505
1518
  function assertAccountsActive(accounts, action) {
1506
1519
  if (!DISPOSED_ACCOUNTS.has(accounts)) return;
@@ -1552,6 +1565,8 @@ var ItdAccounts = class ItdAccounts {
1552
1565
  #eventUnsubscribers = /* @__PURE__ */ new Map();
1553
1566
  /** Плагины для всех: и для уже заведённых аккаунтов, и для будущих. */
1554
1567
  #plugins;
1568
+ /** Фабрики модулей для новых и восстанавливаемых аккаунтов. */
1569
+ #features;
1555
1570
  /** Имена плагинов, чья асинхронная очистка ещё не завершилась. */
1556
1571
  #removingPlugins = /* @__PURE__ */ new Set();
1557
1572
  /** Общая очередь. `undefined`, когда у каждого аккаунта своя. */
@@ -1562,11 +1577,12 @@ var ItdAccounts = class ItdAccounts {
1562
1577
  /** Общий результат терминальной очистки для идемпотентных повторных вызовов. */
1563
1578
  #disposePromise;
1564
1579
  constructor(options = {}) {
1565
- const { storage, plugins, rateLimitScope, ...base } = options;
1580
+ const { storage, plugins, features, rateLimitScope, ...base } = options;
1566
1581
  if (rateLimitScope !== void 0 && rateLimitScope !== "account" && rateLimitScope !== "shared") throw new ItdConfigError("rateLimitScope должен быть 'account' или 'shared'");
1567
1582
  this.#base = base;
1568
1583
  this.#storage = storage ?? new MemoryMultiTokenStorage();
1569
1584
  this.#plugins = orderPluginDefinitions(plugins ?? []);
1585
+ this.#features = resolveAccountFeatures(features);
1570
1586
  this.#rateLimitScope = rateLimitScope ?? "shared";
1571
1587
  const rateLimit = this.#rateLimitScope === "shared" ? resolveRateLimit(base.rateLimit, ITD_CATALOG) : void 0;
1572
1588
  this.#queues = rateLimit ? new RequestQueuePool(rateLimit, base.clock ?? systemClock) : void 0;
@@ -1593,7 +1609,7 @@ var ItdAccounts = class ItdAccounts {
1593
1609
  /**
1594
1610
  * Заводит аккаунт.
1595
1611
  *
1596
- * Возвращается обычный `ItdClient` — со всеми ресурсами, плагинами и `realtime()`.
1612
+ * Возвращается обычный `ItdClient` — со всеми ресурсами, плагинами и каналом событий.
1597
1613
  * Хранилище ему подставляется само: срез общего по имени аккаунта.
1598
1614
  *
1599
1615
  * Опция `auth` не обязательна: когда сессия этого аккаунта уже лежит в хранилище,
@@ -1619,10 +1635,15 @@ var ItdAccounts = class ItdAccounts {
1619
1635
  try {
1620
1636
  client = createManagedClient(this.#mergeOptions(options, storageControl.storage), this.#queues ? { queues: this.#queues } : {});
1621
1637
  for (const plugin of this.#plugins) client.use(plugin);
1638
+ for (const feature of this.#features) {
1639
+ const manifest = feature.create();
1640
+ if (feature.key === void 0) client.install(manifest);
1641
+ else client.withFeature(feature.key, manifest);
1642
+ }
1622
1643
  } catch (error) {
1623
1644
  storageControl.revoke();
1624
1645
  if (client) client.dispose().catch((cleanupError) => {
1625
- this.#reportPluginCleanup("неудачного добавления аккаунта", cleanupError);
1646
+ this.#reportCleanup("неудачного добавления аккаунта", cleanupError);
1626
1647
  });
1627
1648
  throw error;
1628
1649
  }
@@ -1746,7 +1767,7 @@ var ItdAccounts = class ItdAccounts {
1746
1767
  }
1747
1768
  } catch (error) {
1748
1769
  for (const client of installed.reverse()) client.unuse(plugin.name).catch((cleanupError) => {
1749
- this.#reportPluginCleanup(`отката плагина «${plugin.name}»`, cleanupError);
1770
+ this.#reportCleanup(`отката плагина «${plugin.name}»`, cleanupError);
1750
1771
  });
1751
1772
  throw error;
1752
1773
  }
@@ -1862,6 +1883,7 @@ var ItdAccounts = class ItdAccounts {
1862
1883
  Promise.resolve().then(() => this.#queues?.clear())
1863
1884
  ]);
1864
1885
  this.#plugins.splice(0);
1886
+ this.#features.splice(0);
1865
1887
  this.#removingPlugins.clear();
1866
1888
  const errors = results.filter((result) => result.status === "rejected").map((result) => result.reason);
1867
1889
  if (errors.length > 0) throw new AggregateError(errors, "Не удалось освободить аккаунты");
@@ -1871,11 +1893,7 @@ var ItdAccounts = class ItdAccounts {
1871
1893
  return this.dispose();
1872
1894
  }
1873
1895
  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
- }
1896
+ installAsyncDisposeFallback(ItdAccounts);
1879
1897
  }
1880
1898
  /**
1881
1899
  * Собирает опции клиента: общие, поверх них — аккаунтные, и обязательно свой срез
@@ -1900,9 +1918,9 @@ var ItdAccounts = class ItdAccounts {
1900
1918
  storage
1901
1919
  };
1902
1920
  }
1903
- /** Не теряет ошибку фонового teardown, который синхронный API не может await-нуть. */
1904
- #reportPluginCleanup(scope, error) {
1905
- const message = `Не удалось завершить teardown после ${scope}`;
1921
+ /** Не теряет ошибку фонового освобождения ресурсов, которого синхронный API не ждёт. */
1922
+ #reportCleanup(scope, error) {
1923
+ const message = `Не удалось освободить ресурсы после ${scope}`;
1906
1924
  if (this.#logger) this.#logger.error(message, error);
1907
1925
  else console.error(`[itd-api] ${message}`, error);
1908
1926
  }
@@ -1934,6 +1952,6 @@ function createAccounts(options = {}) {
1934
1952
  return new ItdAccounts(options);
1935
1953
  }
1936
1954
  //#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 };
1955
+ 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, MemoryTokenStorage, NotificationEvents, NotificationEventsClient, NotificationEventsTransport, NotificationType, NotificationUpdateOrigin, NotificationUpdateType, OPERATIONS, PaginationMode, Paginator, RateLimitPacing, 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, createNotificationEventsClient, 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, runEventMiddleware, scopedTokenStorage, statusDays, systemClock, toDate, utcStampToIso, withCodec, withNamespace };
1938
1956
 
1939
1957
  //# sourceMappingURL=index.js.map