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.
- package/README.md +12 -12
- 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 +215 -194
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -63
- package/dist/index.d.ts +66 -63
- package/dist/index.js +197 -179
- 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 +20 -26
- package/dist/rest/index.cjs.map +1 -1
- package/dist/rest/index.d.cts +7 -7
- package/dist/rest/index.d.ts +7 -7
- package/dist/rest/index.js +14 -20
- 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-BLR8eVJV.js → events-TfhKh26b.js} +770 -490
- package/dist/shared/events-TfhKh26b.js.map +1 -0
- package/dist/shared/{websocket-C_eI4H2o.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-DO0F5YSm.d.cts → render-BszcKF7S.d.cts} +430 -313
- package/dist/shared/{render-C6HRPs10.js → render-CiL1VS0b.js} +925 -582
- package/dist/shared/render-CiL1VS0b.js.map +1 -0
- package/dist/shared/{render-mcuELiYi.cjs → render-DNTESBSl.cjs} +967 -618
- package/dist/shared/render-DNTESBSl.cjs.map +1 -0
- package/dist/shared/{render-CgwKdOzu.d.ts → render-DVB7T8pm.d.ts} +430 -313
- 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-IU0xN9wX.js → url-B3ocFlHj.js} +434 -408
- package/dist/shared/url-B3ocFlHj.js.map +1 -0
- package/dist/shared/{url-DTfZ2toq.d.cts → url-Be9jlWRv.d.cts} +1228 -1188
- package/dist/shared/{url-DTfZ2toq.d.ts → url-Be9jlWRv.d.ts} +1228 -1188
- package/dist/shared/{url-BaMCQpYH.cjs → url-CFN1sT9S.cjs} +497 -465
- 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-CG8oCQ9F.cjs +0 -108
- package/dist/shared/auth-provider-CG8oCQ9F.cjs.map +0 -1
- package/dist/shared/auth-provider-mYqxsSVa.js +0 -91
- package/dist/shared/auth-provider-mYqxsSVa.js.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-C6HRPs10.js.map +0 -1
- package/dist/shared/render-mcuELiYi.cjs.map +0 -1
- package/dist/shared/url-BaMCQpYH.cjs.map +0 -1
- package/dist/shared/url-IU0xN9wX.js.map +0 -1
- package/dist/shared/websocket-BLR8eVJV.js.map +0 -1
- package/dist/shared/websocket-C_eI4H2o.cjs.map +0 -1
- package/dist/shared/websocket-DF7XIMiX.d.cts +0 -562
- package/dist/shared/websocket-DYKBr8HF.d.ts +0 -562
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 { C as utcStampToIso, D as
|
|
5
|
-
import {
|
|
6
|
-
import { c as
|
|
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
|
});
|
|
@@ -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: () =>
|
|
1112
|
+
onAccountChange: () => featureHost?.stop()
|
|
1087
1113
|
})
|
|
1088
1114
|
});
|
|
1089
|
-
this.#features =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
* Устанавливает предметный модуль
|
|
1210
|
+
* Устанавливает предметный модуль с общей сессией и обработкой запросов клиента.
|
|
1149
1211
|
*
|
|
1150
|
-
* Сервисы, операции и бакеты
|
|
1151
|
-
*
|
|
1212
|
+
* Сервисы, операции и бакеты регистрируются до синхронного `setup()`. Возвращает
|
|
1213
|
+
* типизированный API модуля.
|
|
1152
1214
|
*/
|
|
1153
1215
|
install(feature) {
|
|
1154
1216
|
return this.#features.install(feature);
|
|
1155
1217
|
}
|
|
1156
1218
|
/**
|
|
1157
|
-
* Устанавливает
|
|
1219
|
+
* Устанавливает модуль и добавляет его API в свойство клиента только для чтения.
|
|
1158
1220
|
*
|
|
1159
1221
|
* Возвращаемое пересечение сохраняет тип уже подключённых свойств, поэтому вызовы можно
|
|
1160
|
-
* объединять в цепочку: `new ItdClient().withFeature('
|
|
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
|
-
/** Имена установленных
|
|
1238
|
+
/** Имена установленных модулей в порядке установки. */
|
|
1177
1239
|
featureNames() {
|
|
1178
1240
|
return this.#features.names();
|
|
1179
1241
|
}
|
|
1180
|
-
/** Установлен ли
|
|
1242
|
+
/** Установлен ли модуль с таким именем. */
|
|
1181
1243
|
hasFeature(name) {
|
|
1182
1244
|
return this.#features.has(name);
|
|
1183
1245
|
}
|
|
1184
1246
|
/**
|
|
1185
1247
|
* Подключает плагин.
|
|
1186
1248
|
*
|
|
1187
|
-
* Плагин может
|
|
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
|
-
* Метод дожидается активных обработчиков
|
|
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
|
-
*
|
|
1395
|
+
* событийных каналов завершаются с {@link ItdStateError}. Повторные вызовы возвращают
|
|
1396
1396
|
* тот же результат очистки.
|
|
1397
1397
|
*
|
|
1398
|
-
* Ожидание
|
|
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` — со всеми ресурсами, плагинами и
|
|
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.#
|
|
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.#
|
|
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
|
-
|
|
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
|
-
/** Не теряет ошибку фонового
|
|
1904
|
-
#
|
|
1905
|
-
const message = `Не удалось
|
|
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,
|
|
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
|