itd-api 0.7.1 → 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.
- package/README.md +14 -14
- package/dist/events/index.cjs +53 -0
- package/dist/events/index.d.cts +3 -0
- package/dist/events/index.d.ts +3 -0
- package/dist/events/index.js +5 -0
- package/dist/index.cjs +269 -175
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -56
- package/dist/index.d.ts +77 -56
- package/dist/index.js +251 -160
- package/dist/index.js.map +1 -1
- package/dist/node/index.cjs +4 -4
- package/dist/node/index.d.cts +1 -1
- package/dist/node/index.d.ts +1 -1
- package/dist/node/index.js +4 -4
- package/dist/rest/index.cjs +72 -14
- package/dist/rest/index.cjs.map +1 -1
- package/dist/rest/index.d.cts +11 -3
- package/dist/rest/index.d.ts +11 -3
- package/dist/rest/index.js +66 -8
- package/dist/rest/index.js.map +1 -1
- package/dist/shared/{errors-DfU8M5eS.cjs → errors-BmP3TKoW.cjs} +3 -3
- package/dist/shared/{errors-DfU8M5eS.cjs.map → errors-BmP3TKoW.cjs.map} +1 -1
- package/dist/shared/{errors-Bhrd2fJd.js → errors-GI10kZxk.js} +3 -3
- package/dist/shared/{errors-Bhrd2fJd.js.map → errors-GI10kZxk.js.map} +1 -1
- package/dist/shared/events-Br2r7coi.d.cts +698 -0
- package/dist/shared/events-QyhQZrnC.d.ts +698 -0
- package/dist/shared/{websocket-CbzB1Leq.js → events-TfhKh26b.js} +770 -490
- package/dist/shared/events-TfhKh26b.js.map +1 -0
- package/dist/shared/{websocket-sYlynrr0.cjs → events-iTteVsd_.cjs} +834 -518
- package/dist/shared/events-iTteVsd_.cjs.map +1 -0
- package/dist/shared/{multi-storage-CkvTUC5m.js → multi-storage-1AEZTczk.js} +4 -4
- package/dist/shared/{multi-storage-CkvTUC5m.js.map → multi-storage-1AEZTczk.js.map} +1 -1
- package/dist/shared/{multi-storage--yTEqiod.cjs → multi-storage-CCUgkqvI.cjs} +6 -6
- package/dist/shared/{multi-storage--yTEqiod.cjs.map → multi-storage-CCUgkqvI.cjs.map} +1 -1
- package/dist/shared/{options-DtATYdLr.js → options-BNP633A5.js} +8 -6
- package/dist/shared/options-BNP633A5.js.map +1 -0
- package/dist/shared/{options-Dg5N3r1V.cjs → options-BUfoHE9G.cjs} +8 -6
- package/dist/shared/options-BUfoHE9G.cjs.map +1 -0
- package/dist/shared/{cookies-DZwFq6kr.cjs → redact-DB9EMGJx.cjs} +301 -2
- package/dist/shared/redact-DB9EMGJx.cjs.map +1 -0
- package/dist/shared/{cookies-tX2sNwxb.js → redact-FNK29Fv-.js} +224 -3
- package/dist/shared/redact-FNK29Fv-.js.map +1 -0
- package/dist/shared/{render-DMp_3Nzk.d.cts → render-BszcKF7S.d.cts} +432 -254
- package/dist/shared/{render-vtLixIiU.js → render-CiL1VS0b.js} +1261 -621
- package/dist/shared/render-CiL1VS0b.js.map +1 -0
- package/dist/shared/{render-DyeHJNBw.cjs → render-DNTESBSl.cjs} +1335 -671
- package/dist/shared/render-DNTESBSl.cjs.map +1 -0
- package/dist/shared/{render-DZrxhC5_.d.ts → render-DVB7T8pm.d.ts} +432 -254
- package/dist/shared/{storage-BPJR_k4-.cjs → storage-BLmXmzKF.cjs} +2 -2
- package/dist/shared/{storage-BPJR_k4-.cjs.map → storage-BLmXmzKF.cjs.map} +1 -1
- package/dist/shared/{storage-D86edNCB.js → storage-CCxn0Kkv.js} +2 -2
- package/dist/shared/{storage-D86edNCB.js.map → storage-CCxn0Kkv.js.map} +1 -1
- package/dist/shared/{url-CYXgxqGx.js → url-B3ocFlHj.js} +491 -425
- package/dist/shared/url-B3ocFlHj.js.map +1 -0
- package/dist/shared/{url-B6-bXHKt.d.ts → url-Be9jlWRv.d.cts} +1233 -1195
- package/dist/shared/{url-B6-bXHKt.d.cts → url-Be9jlWRv.d.ts} +1233 -1195
- package/dist/shared/{url-yjl2c8Ie.cjs → url-CFN1sT9S.cjs} +562 -478
- package/dist/shared/url-CFN1sT9S.cjs.map +1 -0
- package/dist/web/index.cjs +2 -2
- package/dist/web/index.js +2 -2
- package/package.json +16 -15
- package/dist/realtime/index.cjs +0 -165
- package/dist/realtime/index.cjs.map +0 -1
- package/dist/realtime/index.d.cts +0 -51
- package/dist/realtime/index.d.ts +0 -51
- package/dist/realtime/index.js +0 -120
- package/dist/realtime/index.js.map +0 -1
- package/dist/shared/auth-provider-BfogACAb.js +0 -91
- package/dist/shared/auth-provider-BfogACAb.js.map +0 -1
- package/dist/shared/auth-provider-CTJkKfgy.cjs +0 -108
- package/dist/shared/auth-provider-CTJkKfgy.cjs.map +0 -1
- package/dist/shared/cookies-DZwFq6kr.cjs.map +0 -1
- package/dist/shared/cookies-tX2sNwxb.js.map +0 -1
- package/dist/shared/options-Dg5N3r1V.cjs.map +0 -1
- package/dist/shared/options-DtATYdLr.js.map +0 -1
- package/dist/shared/render-DyeHJNBw.cjs.map +0 -1
- package/dist/shared/render-vtLixIiU.js.map +0 -1
- package/dist/shared/url-CYXgxqGx.js.map +0 -1
- package/dist/shared/url-yjl2c8Ie.cjs.map +0 -1
- package/dist/shared/websocket-BMtihD56.d.ts +0 -562
- package/dist/shared/websocket-CbzB1Leq.js.map +0 -1
- package/dist/shared/websocket-D1p32SB2.d.cts +0 -562
- package/dist/shared/websocket-sYlynrr0.cjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,11 +1,63 @@
|
|
|
1
|
-
import { $ as
|
|
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-
|
|
3
|
-
import {
|
|
4
|
-
import { a as
|
|
5
|
-
import { c as
|
|
6
|
-
import {
|
|
7
|
-
import { c as
|
|
8
|
-
import { a as scopedTokenStorage, i as isRestorableSession, n as controlledTokenStorage, r as createMultiTokenStorage, t as MemoryMultiTokenStorage } from "./shared/multi-storage-
|
|
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.
|
|
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
|
-
#
|
|
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,
|
|
272
|
+
constructor(config, http, jar, hooks = {}) {
|
|
226
273
|
this.#config = config;
|
|
227
|
-
this.#
|
|
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
|
-
/** Снимает
|
|
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
|
-
/**
|
|
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 =
|
|
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
|
|
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 (
|
|
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
|
-
|
|
669
|
-
|
|
670
|
-
|
|
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.
|
|
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
|
-
|
|
700
|
-
|
|
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
|
|
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
|
-
|
|
730
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
860
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
937
|
-
|
|
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.
|
|
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.
|
|
978
|
+
return this.voidOperation(AUTH_REVOKE_OTHER_SESSIONS, {
|
|
953
979
|
path: AUTH_PATHS.sessions,
|
|
954
980
|
...options
|
|
955
981
|
});
|
|
@@ -1012,8 +1038,7 @@ function createManagedClient(options, internals) {
|
|
|
1012
1038
|
*/
|
|
1013
1039
|
var ItdClient = class ItdClient {
|
|
1014
1040
|
#runtime;
|
|
1015
|
-
|
|
1016
|
-
#streams = /* @__PURE__ */ new Set();
|
|
1041
|
+
#features;
|
|
1017
1042
|
/** Общий результат терминальной очистки для идемпотентных повторных вызовов. */
|
|
1018
1043
|
#disposePromise;
|
|
1019
1044
|
/** Ресурсы конвейера запросов; создаются при первом обращении. */
|
|
@@ -1074,19 +1099,71 @@ var ItdClient = class ItdClient {
|
|
|
1074
1099
|
return this.#resources.telemetry;
|
|
1075
1100
|
}
|
|
1076
1101
|
constructor(options = {}) {
|
|
1102
|
+
const notificationEventsOptions = resolveNotificationEventsOptions(options.events?.notifications);
|
|
1077
1103
|
const internals = CLIENT_INTERNALS.get(options) ?? {};
|
|
1104
|
+
const catalog = new ExtensibleOperationCatalog(ITD_CATALOG);
|
|
1105
|
+
let featureHost;
|
|
1078
1106
|
this.#runtime = createClientRuntime(options, {
|
|
1079
|
-
catalog
|
|
1107
|
+
catalog,
|
|
1080
1108
|
queues: internals.queues,
|
|
1081
1109
|
assertActive: (action) => assertClientActive(this, action),
|
|
1082
1110
|
auth: (deps) => createItdAuth(options, {
|
|
1083
1111
|
...deps,
|
|
1084
|
-
onAccountChange: () =>
|
|
1112
|
+
onAccountChange: () => featureHost?.stop()
|
|
1085
1113
|
})
|
|
1086
1114
|
});
|
|
1115
|
+
this.#features = featureHost = createFeatureHost(this.#runtime, {
|
|
1116
|
+
catalog,
|
|
1117
|
+
assertActive: (action) => assertClientActive(this, action)
|
|
1118
|
+
});
|
|
1119
|
+
const status = this.#features.install(createStatusFeature());
|
|
1087
1120
|
this.#resources = createResources({
|
|
1088
1121
|
http: this.#runtime.http,
|
|
1089
|
-
|
|
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
|
+
})
|
|
1090
1167
|
});
|
|
1091
1168
|
CLIENT_PLUGIN_REGISTRIES.set(this, this.#runtime.plugins);
|
|
1092
1169
|
}
|
|
@@ -1130,11 +1207,46 @@ var ItdClient = class ItdClient {
|
|
|
1130
1207
|
return this.#runtime.rateLimitState();
|
|
1131
1208
|
}
|
|
1132
1209
|
/**
|
|
1210
|
+
* Устанавливает предметный модуль с общей сессией и обработкой запросов клиента.
|
|
1211
|
+
*
|
|
1212
|
+
* Сервисы, операции и бакеты регистрируются до синхронного `setup()`. Возвращает
|
|
1213
|
+
* типизированный API модуля.
|
|
1214
|
+
*/
|
|
1215
|
+
install(feature) {
|
|
1216
|
+
return this.#features.install(feature);
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Устанавливает модуль и добавляет его API в свойство клиента только для чтения.
|
|
1220
|
+
*
|
|
1221
|
+
* Возвращаемое пересечение сохраняет тип уже подключённых свойств, поэтому вызовы можно
|
|
1222
|
+
* объединять в цепочку: `new ItdClient().withFeature('pixelBattle', pixelBattleFeature)`.
|
|
1223
|
+
*/
|
|
1224
|
+
withFeature(key, feature) {
|
|
1225
|
+
assertClientActive(this, "установить feature");
|
|
1226
|
+
if (typeof key !== "string" || key.trim() === "" || key !== key.trim()) throw new ItdConfigError("Свойство feature должно иметь непустое имя без краевых пробелов");
|
|
1227
|
+
if (key === "then" || key in this) throw new ItdConfigError(`Свойство клиента «${key}» уже занято или зарезервировано`);
|
|
1228
|
+
if (!Object.isExtensible(this)) throw new ItdConfigError("Нельзя опубликовать feature на нерасширяемом клиенте");
|
|
1229
|
+
const api = this.install(feature);
|
|
1230
|
+
Object.defineProperty(this, key, {
|
|
1231
|
+
value: api,
|
|
1232
|
+
enumerable: true,
|
|
1233
|
+
configurable: false,
|
|
1234
|
+
writable: false
|
|
1235
|
+
});
|
|
1236
|
+
return this;
|
|
1237
|
+
}
|
|
1238
|
+
/** Имена установленных модулей в порядке установки. */
|
|
1239
|
+
featureNames() {
|
|
1240
|
+
return this.#features.names();
|
|
1241
|
+
}
|
|
1242
|
+
/** Установлен ли модуль с таким именем. */
|
|
1243
|
+
hasFeature(name) {
|
|
1244
|
+
return this.#features.has(name);
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1133
1247
|
* Подключает плагин.
|
|
1134
1248
|
*
|
|
1135
|
-
* Плагин может
|
|
1136
|
-
* транспортной попытки. Оба контракта охватывают все методы клиента. Подключать плагин
|
|
1137
|
-
* можно в любой момент, но обычно это делают сразу после создания клиента.
|
|
1249
|
+
* Плагин может регистрировать обёртки операций и перехватчики сетевых попыток.
|
|
1138
1250
|
*
|
|
1139
1251
|
* @throws {ItdConfigError} если плагин задан неверно или уже подключён
|
|
1140
1252
|
* @throws {ItdStateError} если клиент уже освобождён через {@link dispose}
|
|
@@ -1237,86 +1349,42 @@ var ItdClient = class ItdClient {
|
|
|
1237
1349
|
return this.#runtime.auth.on(event, listener);
|
|
1238
1350
|
}
|
|
1239
1351
|
/**
|
|
1240
|
-
* Создаёт поток уведомлений в реальном времени.
|
|
1241
|
-
*
|
|
1242
|
-
* Каждый вызов даёт новый независимый поток; обычно он нужен один на приложение.
|
|
1243
|
-
* Соединение поднимается методом `connect()` и держится само. Замена авторизации на токен
|
|
1244
|
-
* другого пользователя завершает все потоки клиента; смена только сессии их не затрагивает.
|
|
1245
|
-
*
|
|
1246
|
-
* @example
|
|
1247
|
-
* ```ts
|
|
1248
|
-
* import { NotificationType } from 'itd-api';
|
|
1249
|
-
*
|
|
1250
|
-
* const stream = itd.realtime();
|
|
1251
|
-
*
|
|
1252
|
-
* stream.onNotification(NotificationType.PostComment, async ({ update }) => {
|
|
1253
|
-
* await handleComment(update.data.notification);
|
|
1254
|
-
* });
|
|
1255
|
-
* stream.on('unreadCount', (count) => setBadge(count));
|
|
1256
|
-
*
|
|
1257
|
-
* await stream.connect();
|
|
1258
|
-
* ```
|
|
1259
|
-
*
|
|
1260
|
-
* @throws {ItdStateError} если клиент уже освобождён через {@link dispose}
|
|
1261
|
-
*/
|
|
1262
|
-
realtime(options = {}) {
|
|
1263
|
-
assertClientActive(this, "создать realtime-поток");
|
|
1264
|
-
let stream;
|
|
1265
|
-
stream = new ItdRealtime({
|
|
1266
|
-
baseUrl: this.#runtime.config.baseUrl,
|
|
1267
|
-
fetch: this.#runtime.config.fetch,
|
|
1268
|
-
request: ({ operationId, path, query, signal }) => this.#runtime.http.operation(operationId, {
|
|
1269
|
-
path,
|
|
1270
|
-
query,
|
|
1271
|
-
signal
|
|
1272
|
-
}),
|
|
1273
|
-
baseHeaders: (url) => this.#runtime.platformHeaders(url),
|
|
1274
|
-
getAuthIdentity: () => this.#runtime.auth.getCurrentAuthIdentity(),
|
|
1275
|
-
getAuthScope: () => this.#runtime.auth.getAuthScope(),
|
|
1276
|
-
getToken: () => this.#runtime.auth.token(),
|
|
1277
|
-
refresh: () => this.#runtime.auth.onUnauthorized(),
|
|
1278
|
-
fetchUnreadCount: () => this.notifications.count(),
|
|
1279
|
-
onConnect: () => this.#streams.add(stream),
|
|
1280
|
-
onClose: () => this.#streams.delete(stream),
|
|
1281
|
-
logger: this.#runtime.config.logger,
|
|
1282
|
-
clock: this.#runtime.config.clock
|
|
1283
|
-
}, options);
|
|
1284
|
-
setRealtimeConnectGuard(stream, () => assertClientActive(this, "подключить realtime-поток"));
|
|
1285
|
-
this.#streams.add(stream);
|
|
1286
|
-
return stream;
|
|
1287
|
-
}
|
|
1288
|
-
/**
|
|
1289
1352
|
* Освобождает ресурсы клиента: закрывает все потоки уведомлений, отправляет открытые
|
|
1290
1353
|
* накопители {@link telemetry}, затем останавливает очередь запросов.
|
|
1291
1354
|
*
|
|
1292
|
-
* Метод дожидается активных обработчиков
|
|
1293
|
-
*
|
|
1294
|
-
* повторным `connect()`.
|
|
1355
|
+
* Метод дожидается завершения активных обработчиков и освобождения ресурсов остановленных
|
|
1356
|
+
* событийных соединений, но не дольше `shutdownTimeout`. После вызова клиентом можно
|
|
1357
|
+
* пользоваться снова; ранее созданный поток можно запустить повторным `connect()`.
|
|
1295
1358
|
*
|
|
1296
1359
|
* Общая очередь, полученная от {@link ItdAccounts}, не останавливается: её гасит сам
|
|
1297
1360
|
* контейнер, когда закрывает все аккаунты разом.
|
|
1298
1361
|
*
|
|
1299
1362
|
* Терминальное освобождение — это {@link dispose}.
|
|
1300
1363
|
*
|
|
1301
|
-
* @throws {ItdStateError} если
|
|
1364
|
+
* @throws {ItdStateError} если остановка событийного соединения не завершилась за отведённый срок
|
|
1302
1365
|
*/
|
|
1303
1366
|
async close() {
|
|
1304
1367
|
return this.#close(false);
|
|
1305
1368
|
}
|
|
1306
1369
|
async #close(disposeCleanup) {
|
|
1307
|
-
const streams = this.#disconnectStreams();
|
|
1308
1370
|
if (disposeCleanup) this.#resources.prepareTelemetryClose();
|
|
1309
|
-
const
|
|
1310
|
-
const deadline = createDeadline(shutdownTimeout, clock);
|
|
1311
|
-
let stuck = [];
|
|
1371
|
+
const errors = [];
|
|
1312
1372
|
try {
|
|
1313
|
-
|
|
1314
|
-
|
|
1373
|
+
try {
|
|
1374
|
+
await this.#features.close();
|
|
1375
|
+
} catch (error) {
|
|
1376
|
+
errors.push(error);
|
|
1377
|
+
}
|
|
1378
|
+
try {
|
|
1379
|
+
await this.#resources.closeTelemetry(disposeCleanup);
|
|
1380
|
+
} catch (error) {
|
|
1381
|
+
errors.push(error);
|
|
1382
|
+
}
|
|
1315
1383
|
} finally {
|
|
1316
|
-
deadline.cancel();
|
|
1317
1384
|
this.#runtime.close();
|
|
1318
1385
|
}
|
|
1319
|
-
if (
|
|
1386
|
+
if (errors.length === 1) throw errors[0];
|
|
1387
|
+
if (errors.length > 1) throw new AggregateError(errors, "Не удалось закрыть клиент");
|
|
1320
1388
|
}
|
|
1321
1389
|
/**
|
|
1322
1390
|
* Окончательно освобождает клиент: выполняет {@link close}, отменяет незавершённые
|
|
@@ -1324,10 +1392,11 @@ var ItdClient = class ItdClient {
|
|
|
1324
1392
|
*
|
|
1325
1393
|
* Терминальное состояние устанавливается сразу при первом вызове. После этого новые
|
|
1326
1394
|
* запросы, подключение плагинов, регистрация сервисов и создание или повторный запуск
|
|
1327
|
-
*
|
|
1395
|
+
* событийных каналов завершаются с {@link ItdStateError}. Повторные вызовы возвращают
|
|
1328
1396
|
* тот же результат очистки.
|
|
1329
1397
|
*
|
|
1330
|
-
* Ожидание
|
|
1398
|
+
* Ожидание остановки событийных соединений и операций плагинов ограничено
|
|
1399
|
+
* `shutdownTimeout`.
|
|
1331
1400
|
*
|
|
1332
1401
|
* @example
|
|
1333
1402
|
* ```ts
|
|
@@ -1349,6 +1418,11 @@ var ItdClient = class ItdClient {
|
|
|
1349
1418
|
} catch (error) {
|
|
1350
1419
|
errors.push(error);
|
|
1351
1420
|
}
|
|
1421
|
+
try {
|
|
1422
|
+
await this.#features.dispose();
|
|
1423
|
+
} catch (error) {
|
|
1424
|
+
errors.push(error);
|
|
1425
|
+
}
|
|
1352
1426
|
try {
|
|
1353
1427
|
await this.#runtime.dispose();
|
|
1354
1428
|
} catch (error) {
|
|
@@ -1356,13 +1430,6 @@ var ItdClient = class ItdClient {
|
|
|
1356
1430
|
}
|
|
1357
1431
|
if (errors.length > 0) throw new AggregateError(errors, "Не удалось освободить клиент");
|
|
1358
1432
|
}
|
|
1359
|
-
/** Завершает потоки до того, как запросы начнут использовать другой аккаунт. */
|
|
1360
|
-
#disconnectStreams() {
|
|
1361
|
-
const streams = [...this.#streams];
|
|
1362
|
-
for (const stream of streams) stream.disconnect();
|
|
1363
|
-
this.#streams.clear();
|
|
1364
|
-
return streams;
|
|
1365
|
-
}
|
|
1366
1433
|
/** Позволяет использовать клиент с `await using`. */
|
|
1367
1434
|
[Symbol.asyncDispose]() {
|
|
1368
1435
|
return this.dispose();
|
|
@@ -1428,6 +1495,25 @@ function createClient(options = {}) {
|
|
|
1428
1495
|
function validateAccountName(name) {
|
|
1429
1496
|
if (typeof name !== "string" || name.trim() === "") throw new ItdConfigError("имя аккаунта должно быть непустой строкой");
|
|
1430
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
|
+
}
|
|
1431
1517
|
const DISPOSED_ACCOUNTS = /* @__PURE__ */ new WeakSet();
|
|
1432
1518
|
function assertAccountsActive(accounts, action) {
|
|
1433
1519
|
if (!DISPOSED_ACCOUNTS.has(accounts)) return;
|
|
@@ -1479,6 +1565,8 @@ var ItdAccounts = class ItdAccounts {
|
|
|
1479
1565
|
#eventUnsubscribers = /* @__PURE__ */ new Map();
|
|
1480
1566
|
/** Плагины для всех: и для уже заведённых аккаунтов, и для будущих. */
|
|
1481
1567
|
#plugins;
|
|
1568
|
+
/** Фабрики модулей для новых и восстанавливаемых аккаунтов. */
|
|
1569
|
+
#features;
|
|
1482
1570
|
/** Имена плагинов, чья асинхронная очистка ещё не завершилась. */
|
|
1483
1571
|
#removingPlugins = /* @__PURE__ */ new Set();
|
|
1484
1572
|
/** Общая очередь. `undefined`, когда у каждого аккаунта своя. */
|
|
@@ -1489,11 +1577,12 @@ var ItdAccounts = class ItdAccounts {
|
|
|
1489
1577
|
/** Общий результат терминальной очистки для идемпотентных повторных вызовов. */
|
|
1490
1578
|
#disposePromise;
|
|
1491
1579
|
constructor(options = {}) {
|
|
1492
|
-
const { storage, plugins, rateLimitScope, ...base } = options;
|
|
1580
|
+
const { storage, plugins, features, rateLimitScope, ...base } = options;
|
|
1493
1581
|
if (rateLimitScope !== void 0 && rateLimitScope !== "account" && rateLimitScope !== "shared") throw new ItdConfigError("rateLimitScope должен быть 'account' или 'shared'");
|
|
1494
1582
|
this.#base = base;
|
|
1495
1583
|
this.#storage = storage ?? new MemoryMultiTokenStorage();
|
|
1496
1584
|
this.#plugins = orderPluginDefinitions(plugins ?? []);
|
|
1585
|
+
this.#features = resolveAccountFeatures(features);
|
|
1497
1586
|
this.#rateLimitScope = rateLimitScope ?? "shared";
|
|
1498
1587
|
const rateLimit = this.#rateLimitScope === "shared" ? resolveRateLimit(base.rateLimit, ITD_CATALOG) : void 0;
|
|
1499
1588
|
this.#queues = rateLimit ? new RequestQueuePool(rateLimit, base.clock ?? systemClock) : void 0;
|
|
@@ -1520,7 +1609,7 @@ var ItdAccounts = class ItdAccounts {
|
|
|
1520
1609
|
/**
|
|
1521
1610
|
* Заводит аккаунт.
|
|
1522
1611
|
*
|
|
1523
|
-
* Возвращается обычный `ItdClient` — со всеми ресурсами, плагинами и
|
|
1612
|
+
* Возвращается обычный `ItdClient` — со всеми ресурсами, плагинами и каналом событий.
|
|
1524
1613
|
* Хранилище ему подставляется само: срез общего по имени аккаунта.
|
|
1525
1614
|
*
|
|
1526
1615
|
* Опция `auth` не обязательна: когда сессия этого аккаунта уже лежит в хранилище,
|
|
@@ -1546,10 +1635,15 @@ var ItdAccounts = class ItdAccounts {
|
|
|
1546
1635
|
try {
|
|
1547
1636
|
client = createManagedClient(this.#mergeOptions(options, storageControl.storage), this.#queues ? { queues: this.#queues } : {});
|
|
1548
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
|
+
}
|
|
1549
1643
|
} catch (error) {
|
|
1550
1644
|
storageControl.revoke();
|
|
1551
1645
|
if (client) client.dispose().catch((cleanupError) => {
|
|
1552
|
-
this.#
|
|
1646
|
+
this.#reportCleanup("неудачного добавления аккаунта", cleanupError);
|
|
1553
1647
|
});
|
|
1554
1648
|
throw error;
|
|
1555
1649
|
}
|
|
@@ -1673,7 +1767,7 @@ var ItdAccounts = class ItdAccounts {
|
|
|
1673
1767
|
}
|
|
1674
1768
|
} catch (error) {
|
|
1675
1769
|
for (const client of installed.reverse()) client.unuse(plugin.name).catch((cleanupError) => {
|
|
1676
|
-
this.#
|
|
1770
|
+
this.#reportCleanup(`отката плагина «${plugin.name}»`, cleanupError);
|
|
1677
1771
|
});
|
|
1678
1772
|
throw error;
|
|
1679
1773
|
}
|
|
@@ -1789,6 +1883,7 @@ var ItdAccounts = class ItdAccounts {
|
|
|
1789
1883
|
Promise.resolve().then(() => this.#queues?.clear())
|
|
1790
1884
|
]);
|
|
1791
1885
|
this.#plugins.splice(0);
|
|
1886
|
+
this.#features.splice(0);
|
|
1792
1887
|
this.#removingPlugins.clear();
|
|
1793
1888
|
const errors = results.filter((result) => result.status === "rejected").map((result) => result.reason);
|
|
1794
1889
|
if (errors.length > 0) throw new AggregateError(errors, "Не удалось освободить аккаунты");
|
|
@@ -1798,11 +1893,7 @@ var ItdAccounts = class ItdAccounts {
|
|
|
1798
1893
|
return this.dispose();
|
|
1799
1894
|
}
|
|
1800
1895
|
static {
|
|
1801
|
-
|
|
1802
|
-
const prototype = ItdAccounts.prototype;
|
|
1803
|
-
prototype[Symbol.for("Symbol.asyncDispose")] = prototype.undefined;
|
|
1804
|
-
delete prototype.undefined;
|
|
1805
|
-
}
|
|
1896
|
+
installAsyncDisposeFallback(ItdAccounts);
|
|
1806
1897
|
}
|
|
1807
1898
|
/**
|
|
1808
1899
|
* Собирает опции клиента: общие, поверх них — аккаунтные, и обязательно свой срез
|
|
@@ -1827,9 +1918,9 @@ var ItdAccounts = class ItdAccounts {
|
|
|
1827
1918
|
storage
|
|
1828
1919
|
};
|
|
1829
1920
|
}
|
|
1830
|
-
/** Не теряет ошибку фонового
|
|
1831
|
-
#
|
|
1832
|
-
const message = `Не удалось
|
|
1921
|
+
/** Не теряет ошибку фонового освобождения ресурсов, которого синхронный API не ждёт. */
|
|
1922
|
+
#reportCleanup(scope, error) {
|
|
1923
|
+
const message = `Не удалось освободить ресурсы после ${scope}`;
|
|
1833
1924
|
if (this.#logger) this.#logger.error(message, error);
|
|
1834
1925
|
else console.error(`[itd-api] ${message}`, error);
|
|
1835
1926
|
}
|
|
@@ -1861,6 +1952,6 @@ function createAccounts(options = {}) {
|
|
|
1861
1952
|
return new ItdAccounts(options);
|
|
1862
1953
|
}
|
|
1863
1954
|
//#endregion
|
|
1864
|
-
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,
|
|
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 };
|
|
1865
1956
|
|
|
1866
1957
|
//# sourceMappingURL=index.js.map
|