itd-api 0.9.3 → 0.10.1
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 +9 -8
- package/dist/events/index.cjs +2 -2
- package/dist/events/index.d.cts +2 -2
- package/dist/events/index.d.ts +2 -2
- package/dist/events/index.js +2 -2
- package/dist/index.cjs +452 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +229 -49
- package/dist/index.d.ts +229 -49
- package/dist/index.js +448 -44
- package/dist/index.js.map +1 -1
- package/dist/rest/index.cjs +2 -2
- package/dist/rest/index.d.cts +2 -2
- package/dist/rest/index.d.ts +2 -2
- package/dist/rest/index.js +2 -2
- package/dist/shared/{events-BPxBF0gQ.d.cts → events-CZXU16qU.d.cts} +2 -2
- package/dist/shared/{events-BzjI6pn5.js → events-Cipe-Fm9.js} +3 -3
- package/dist/shared/{events-BzjI6pn5.js.map → events-Cipe-Fm9.js.map} +1 -1
- package/dist/shared/{events-DnwfcLxS.d.ts → events-D3CSlpXR.d.ts} +2 -2
- package/dist/shared/{events-C1FudmSe.cjs → events-MtRNe_TF.cjs} +8 -2
- package/dist/shared/{events-C1FudmSe.cjs.map → events-MtRNe_TF.cjs.map} +1 -1
- package/dist/shared/{render-uFW2kDxp.d.ts → render-Cs6AOXbD.d.ts} +2 -2
- package/dist/shared/{render-CtOpp9Pm.js → render-CubTTof_.js} +2 -2
- package/dist/shared/{render-CtOpp9Pm.js.map → render-CubTTof_.js.map} +1 -1
- package/dist/shared/{render-C1F_QWYD.d.cts → render-bZwOk3CC.d.cts} +2 -2
- package/dist/shared/{render-Omy1drH1.cjs → render-dscKp9CC.cjs} +2 -2
- package/dist/shared/{render-Omy1drH1.cjs.map → render-dscKp9CC.cjs.map} +1 -1
- package/dist/shared/{url-3Ts6EEHQ.js → url-CntTEAxu.js} +65 -3
- package/dist/shared/url-CntTEAxu.js.map +1 -0
- package/dist/shared/{url-D6wsXm1T.cjs → url-DgTYZN_v.cjs} +100 -2
- package/dist/shared/url-DgTYZN_v.cjs.map +1 -0
- package/dist/shared/{url-DubjkRR9.d.cts → url-Dum7r_FT.d.cts} +70 -3
- package/dist/shared/{url-DubjkRR9.d.ts → url-Dum7r_FT.d.ts} +70 -3
- package/package.json +2 -1
- package/dist/shared/url-3Ts6EEHQ.js.map +0 -1
- package/dist/shared/url-D6wsXm1T.cjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_url = require("./shared/url-
|
|
2
|
+
const require_url = require("./shared/url-DgTYZN_v.cjs");
|
|
3
3
|
const require_errors = require("./shared/errors-BmP3TKoW.cjs");
|
|
4
4
|
const require_redact = require("./shared/redact-lAmck1Z6.cjs");
|
|
5
|
-
const require_render = require("./shared/render-
|
|
5
|
+
const require_render = require("./shared/render-dscKp9CC.cjs");
|
|
6
6
|
const require_options = require("./shared/options-DjEtbVaf.cjs");
|
|
7
|
-
const require_events = require("./shared/events-
|
|
7
|
+
const require_events = require("./shared/events-MtRNe_TF.cjs");
|
|
8
8
|
const require_key_value_store = require("./shared/key-value-store-B5GYVEYZ.cjs");
|
|
9
9
|
const require_storage = require("./shared/storage-CZJZoaz9.cjs");
|
|
10
10
|
const require_multi_storage = require("./shared/multi-storage-s-fPtjoy.cjs");
|
|
@@ -32,12 +32,37 @@ const SignInStatus = Object.freeze({
|
|
|
32
32
|
Authenticated: "authenticated",
|
|
33
33
|
OtpRequired: "otp_required"
|
|
34
34
|
});
|
|
35
|
+
/**
|
|
36
|
+
* Поле тела запроса по умолчанию для известных провайдеров.
|
|
37
|
+
*
|
|
38
|
+
* Используется, когда провайдера не спрашивали. Ответ `captchaProvider()` важнее этой таблицы.
|
|
39
|
+
*/
|
|
40
|
+
const CAPTCHA_FIELDS = Object.freeze({
|
|
41
|
+
[require_url.CaptchaType.Itd]: require_url.CaptchaField.Itd,
|
|
42
|
+
[require_url.CaptchaType.Cloudflare]: require_url.CaptchaField.Cloudflare
|
|
43
|
+
});
|
|
44
|
+
/** Состояния, которые может вернуть проверка QR-входа. */
|
|
45
|
+
const QrLoginStatus = Object.freeze({
|
|
46
|
+
Pending: "pending",
|
|
47
|
+
Scanned: "scanned",
|
|
48
|
+
CaptchaRequired: "captcha_required",
|
|
49
|
+
Authorized: "authorized",
|
|
50
|
+
Rejected: "rejected"
|
|
51
|
+
});
|
|
52
|
+
/** Состояния, приходящие из потокового наблюдения за QR-входом. */
|
|
53
|
+
const QrLoginStreamStatus = Object.freeze({
|
|
54
|
+
Pending: "pending",
|
|
55
|
+
Scanned: "scanned",
|
|
56
|
+
Approved: "approved",
|
|
57
|
+
Rejected: "rejected"
|
|
58
|
+
});
|
|
35
59
|
function requireString(body, key, message) {
|
|
36
60
|
const value = require_url.pickString(body, key);
|
|
37
61
|
if (!value) throw new require_errors.ItdConfigError(message);
|
|
38
62
|
return value;
|
|
39
63
|
}
|
|
40
64
|
const AUTH_CHECK = require_url.passthroughOperation("auth.check");
|
|
65
|
+
const AUTH_CAPTCHA_PROVIDER = require_url.passthroughOperation("auth.captchaProvider");
|
|
41
66
|
const AUTH_SIGN_UP = require_url.defineBuiltInOperation("auth.signUp", (body) => requireString(body, "flowToken", "Сервер не вернул flowToken при регистрации"));
|
|
42
67
|
const AUTH_SIGN_IN = require_url.defineBuiltInOperation("auth.signIn", (body) => {
|
|
43
68
|
const accessToken = require_url.pickString(body, "accessToken");
|
|
@@ -59,6 +84,96 @@ const AUTH_CHANGE_PASSWORD = require_url.voidOperation("auth.changePassword");
|
|
|
59
84
|
const AUTH_SESSIONS = require_url.defineBuiltInOperation("auth.sessions", (body) => require_url.pickArray(body, "sessions"));
|
|
60
85
|
const AUTH_REVOKE_SESSION = require_url.voidOperation("auth.revokeSession");
|
|
61
86
|
const AUTH_REVOKE_OTHER_SESSIONS = require_url.voidOperation("auth.revokeOtherSessions");
|
|
87
|
+
const AUTH_QR_START = require_url.passthroughOperation("auth.qrStart");
|
|
88
|
+
const AUTH_QR_CLAIM = require_url.passthroughOperation("auth.qrClaim");
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/session/captcha.ts
|
|
91
|
+
function requireCaptchaText(value, field) {
|
|
92
|
+
if (value !== void 0 && (typeof value !== "string" || value.trim() === "")) throw new require_errors.ItdConfigError(`${field} должен быть непустой строкой`);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Проверяет форму источника токена и приводит его к объекту.
|
|
96
|
+
*
|
|
97
|
+
* Сам объект сохраняется как есть: `getToken` вызывается на нём, чтобы источнику остался
|
|
98
|
+
* доступен собственный `this`.
|
|
99
|
+
*
|
|
100
|
+
* @throws {ItdConfigError} при некорректных значениях
|
|
101
|
+
*/
|
|
102
|
+
function resolveCaptchaSolver(captcha) {
|
|
103
|
+
if (captcha === void 0) return void 0;
|
|
104
|
+
if (typeof captcha === "function") return { getToken: captcha };
|
|
105
|
+
if (!require_url.isRecord$1(captcha)) throw new require_errors.ItdConfigError("captcha должен быть функцией либо объектом с getToken");
|
|
106
|
+
const { getToken, type, field } = captcha;
|
|
107
|
+
if (typeof getToken !== "function") throw new require_errors.ItdConfigError("captcha.getToken должен быть функцией");
|
|
108
|
+
requireCaptchaText(type, "captcha.type");
|
|
109
|
+
requireCaptchaText(field, "captcha.field");
|
|
110
|
+
return captcha;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Проверяет форму готового токена капчи.
|
|
114
|
+
*
|
|
115
|
+
* @param path имя опции или аргумента для текста ошибки
|
|
116
|
+
* @throws {ItdConfigError} если токен или его тип не указаны
|
|
117
|
+
*/
|
|
118
|
+
function validateCaptchaToken(captcha, path) {
|
|
119
|
+
if (captcha === void 0) return;
|
|
120
|
+
if (!require_url.isRecord$1(captcha)) throw new require_errors.ItdConfigError(`${path} должен быть объектом { type, token }`);
|
|
121
|
+
const { type, token, field } = captcha;
|
|
122
|
+
if (typeof token !== "string" || token.trim() === "") throw new require_errors.ItdConfigError(`${path}.token должен быть непустой строкой`);
|
|
123
|
+
if (typeof type !== "string" || type.trim() === "") throw new require_errors.ItdConfigError(`${path}.type должен быть непустой строкой`);
|
|
124
|
+
requireCaptchaText(field, `${path}.field`);
|
|
125
|
+
}
|
|
126
|
+
/** Разбирает ответ `GET /api/v1/auth/captcha/provider` для автоматического входа. */
|
|
127
|
+
function readCaptchaProvider(body) {
|
|
128
|
+
const config = require_url.isRecord$1(body) ? body : void 0;
|
|
129
|
+
const provider = config?.provider;
|
|
130
|
+
const field = config?.field;
|
|
131
|
+
if (typeof provider !== "string" || provider.trim() === "" || typeof field !== "string" || field.trim() === "") throw new require_errors.ItdConfigError("Сервер вернул неподдерживаемую конфигурацию капчи");
|
|
132
|
+
return {
|
|
133
|
+
provider,
|
|
134
|
+
field
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/** Куда класть токен: настройка, иначе умолчание провайдера. */
|
|
138
|
+
function resolveField(type, configured) {
|
|
139
|
+
const field = configured ?? CAPTCHA_FIELDS[type];
|
|
140
|
+
if (field) return field;
|
|
141
|
+
throw new require_errors.ItdConfigError(`Неизвестно, в каком поле сервер ждёт токен капчи «${type}». Передайте captcha.field либо оставьте captcha.type = ${require_url.CaptchaChoice.Auto}, чтобы поле назвал сервер.`);
|
|
142
|
+
}
|
|
143
|
+
/** Спрашивает токен у источника и убеждается, что он непустой. */
|
|
144
|
+
async function requireSolvedToken(solver, type) {
|
|
145
|
+
const token = await solver.getToken(type);
|
|
146
|
+
if (typeof token === "string" && token.trim() !== "") return token;
|
|
147
|
+
throw new require_errors.ItdConfigError(`Источник капчи не вернул токен для провайдера «${type}». Проверьте captcha.getToken.`);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Готовит фрагмент тела запроса, взяв токен у источника.
|
|
151
|
+
*
|
|
152
|
+
* При `CaptchaChoice.Auto` провайдер спрашивается у сервера — тогда и тип виджета,
|
|
153
|
+
* и имя поля приходят оттуда. Явно названный тип обходится без этого запроса.
|
|
154
|
+
*
|
|
155
|
+
* @param askProvider как спросить активного провайдера; вызывается, только если это нужно
|
|
156
|
+
* @throws {ItdConfigError} если токен получить не удалось
|
|
157
|
+
*/
|
|
158
|
+
async function solveCaptchaBody(solver, askProvider) {
|
|
159
|
+
const choice = solver.type ?? require_url.CaptchaChoice.Auto;
|
|
160
|
+
if (choice === require_url.CaptchaChoice.Auto) {
|
|
161
|
+
const { provider, field } = await askProvider();
|
|
162
|
+
return { [field]: await requireSolvedToken(solver, provider) };
|
|
163
|
+
}
|
|
164
|
+
return { [resolveField(choice, solver.field)]: await requireSolvedToken(solver, choice) };
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Готовит фрагмент тела из готового токена.
|
|
168
|
+
*
|
|
169
|
+
* Поле берётся из самого токена либо из умолчания провайдера; запроса к серверу нет.
|
|
170
|
+
*
|
|
171
|
+
* @throws {ItdConfigError} если токен или его тип не указаны
|
|
172
|
+
*/
|
|
173
|
+
function captchaBody(captcha) {
|
|
174
|
+
validateCaptchaToken(captcha, "captcha");
|
|
175
|
+
return { [resolveField(captcha.type, captcha.field)]: captcha.token };
|
|
176
|
+
}
|
|
62
177
|
//#endregion
|
|
63
178
|
//#region src/session/jwt.ts
|
|
64
179
|
/**
|
|
@@ -131,11 +246,10 @@ function validateAuth(auth) {
|
|
|
131
246
|
return { ...auth };
|
|
132
247
|
}
|
|
133
248
|
if ("email" in auth || "password" in auth) {
|
|
134
|
-
const { email, password,
|
|
249
|
+
const { email, password, captcha } = auth;
|
|
135
250
|
if (typeof email !== "string" || email.trim() === "") throw new require_errors.ItdConfigError("auth.email должен быть непустой строкой");
|
|
136
251
|
if (typeof password !== "string" || password === "") throw new require_errors.ItdConfigError("auth.password должен быть непустой строкой");
|
|
137
|
-
|
|
138
|
-
if (turnstileToken !== void 0 && (typeof turnstileToken !== "string" || turnstileToken.trim() === "")) throw new require_errors.ItdConfigError("auth.turnstileToken должен быть непустой строкой");
|
|
252
|
+
validateCaptchaToken(captcha, "auth.captcha");
|
|
139
253
|
return { ...auth };
|
|
140
254
|
}
|
|
141
255
|
throw new require_errors.ItdConfigError("auth не распознан. Ожидается строка с accessToken либо объект { accessToken }, { email, password } или { getToken }");
|
|
@@ -164,6 +278,7 @@ function resolveSessionConfig(options, runtime) {
|
|
|
164
278
|
return {
|
|
165
279
|
...runtime,
|
|
166
280
|
auth: validateAuth(options.auth),
|
|
281
|
+
captcha: resolveCaptchaSolver(options.captcha),
|
|
167
282
|
storage: resolveStorage(options.storage),
|
|
168
283
|
deviceId: options.deviceId,
|
|
169
284
|
autoRefresh: options.autoRefresh ?? true,
|
|
@@ -182,6 +297,7 @@ function createItdAuth(options, deps) {
|
|
|
182
297
|
}
|
|
183
298
|
/** Пути эндпоинтов авторизации. */
|
|
184
299
|
const AUTH_PATHS = {
|
|
300
|
+
captchaProvider: "/api/v1/auth/captcha/provider",
|
|
185
301
|
signUp: "/api/v1/auth/sign-up",
|
|
186
302
|
signIn: "/api/v1/auth/sign-in",
|
|
187
303
|
verifyOtp: "/api/v1/auth/verify-otp",
|
|
@@ -191,7 +307,10 @@ const AUTH_PATHS = {
|
|
|
191
307
|
forgotPassword: "/api/v1/auth/forgot-password",
|
|
192
308
|
resetPassword: "/api/v1/auth/reset-password",
|
|
193
309
|
changePassword: "/api/v1/auth/change-password",
|
|
194
|
-
sessions: "/api/v1/auth/sessions"
|
|
310
|
+
sessions: "/api/v1/auth/sessions",
|
|
311
|
+
qrStart: "/api/v1/auth/qr/start",
|
|
312
|
+
qrClaim: "/api/v1/auth/qr/claim",
|
|
313
|
+
qrStream: "/api/v1/auth/qr/stream"
|
|
195
314
|
};
|
|
196
315
|
/**
|
|
197
316
|
* Публичный ключ Cloudflare Turnstile платформы итд.com.
|
|
@@ -201,14 +320,15 @@ const AUTH_PATHS = {
|
|
|
201
320
|
*
|
|
202
321
|
* Ключ привязан к домену: на чужом origin Cloudflare отказывает виджету с кодом `110200`.
|
|
203
322
|
* Поэтому отрисовать его может только код, выполняемый на самом итд.com. Остальным
|
|
204
|
-
* подходит `@itd-api/
|
|
323
|
+
* подходит `@itd-api/captcha`, готовый токен из другого источника или вовсе вход
|
|
205
324
|
* без капчи — по сохранённой сессии либо по токенам, взятым в браузере.
|
|
206
325
|
*
|
|
207
326
|
* @example
|
|
208
327
|
* ```ts
|
|
209
328
|
* turnstile.render('#captcha', {
|
|
210
329
|
* sitekey: TURNSTILE_SITE_KEY,
|
|
211
|
-
* callback: (
|
|
330
|
+
* callback: (token) =>
|
|
331
|
+
* itd.auth.signIn({ email, password, captcha: { type: CaptchaType.Cloudflare, token } }),
|
|
212
332
|
* });
|
|
213
333
|
* ```
|
|
214
334
|
*/
|
|
@@ -330,6 +450,8 @@ var AuthManager = class {
|
|
|
330
450
|
*/
|
|
331
451
|
#authEpoch = 0;
|
|
332
452
|
#externalReadGeneration = 0;
|
|
453
|
+
/** Разовый токен из `auth.captcha` тратится ровно на один запрос. */
|
|
454
|
+
#configCaptchaSpent = false;
|
|
333
455
|
#disposed = false;
|
|
334
456
|
#persistence = Promise.resolve();
|
|
335
457
|
constructor(config, http, jar, hooks = {}) {
|
|
@@ -626,6 +748,13 @@ var AuthManager = class {
|
|
|
626
748
|
}
|
|
627
749
|
/** Сохраняет токен, полученный извне, — например после подтверждения OTP. */
|
|
628
750
|
async setAccessToken(accessToken, expectedRevision = this.#authEpoch, cookies) {
|
|
751
|
+
return this.#storeAccessToken(accessToken, expectedRevision, cookies, false);
|
|
752
|
+
}
|
|
753
|
+
/** Атомарно принимает токен и cookie завершённого входа как новую сессию. @internal */
|
|
754
|
+
async commitAuthFlow(accessToken, cookies, expectedRevision = this.#authEpoch) {
|
|
755
|
+
return this.#storeAccessToken(accessToken, expectedRevision, cookies, true);
|
|
756
|
+
}
|
|
757
|
+
async #storeAccessToken(accessToken, expectedRevision, cookies, replaceSession) {
|
|
629
758
|
this.#assertActive();
|
|
630
759
|
if (typeof accessToken !== "string" || accessToken.trim() === "") throw new require_errors.ItdConfigError("accessToken должен быть непустой строкой");
|
|
631
760
|
await this.#loadSession();
|
|
@@ -635,7 +764,7 @@ var AuthManager = class {
|
|
|
635
764
|
this.#invalidateInFlight();
|
|
636
765
|
this.#transitionAuth(accessToken);
|
|
637
766
|
const saved = this.#saveSession({
|
|
638
|
-
...this.#session ?? {},
|
|
767
|
+
...replaceSession ? {} : this.#session ?? {},
|
|
639
768
|
accessToken,
|
|
640
769
|
obtainedAt: this.#config.clock.now()
|
|
641
770
|
});
|
|
@@ -851,28 +980,50 @@ var AuthManager = class {
|
|
|
851
980
|
return promise;
|
|
852
981
|
}
|
|
853
982
|
/**
|
|
854
|
-
*
|
|
983
|
+
* Готовит фрагмент тела с токеном капчи для запроса, который её требует.
|
|
855
984
|
*
|
|
856
|
-
*
|
|
857
|
-
*
|
|
985
|
+
* Источники в порядке убывания приоритета: токен, переданный в сам вызов; разовый токен
|
|
986
|
+
* из `auth.captcha`; опция `captcha`. Когда ни одного нет, фрагмент пустой: нужна ли
|
|
987
|
+
* капча этому запросу, решает сервер.
|
|
988
|
+
*
|
|
989
|
+
* @param explicit готовый токен, переданный в вызов
|
|
990
|
+
* @throws {ItdConfigError} если токен неполный или источник его не вернул
|
|
858
991
|
*/
|
|
859
|
-
async
|
|
860
|
-
if (
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
if (
|
|
865
|
-
|
|
992
|
+
async captchaBody(explicit) {
|
|
993
|
+
if (explicit) return captchaBody(explicit);
|
|
994
|
+
const configured = this.#takeConfiguredCaptcha();
|
|
995
|
+
if (configured) return captchaBody(configured);
|
|
996
|
+
const solver = this.#config.captcha;
|
|
997
|
+
if (solver) return solveCaptchaBody(solver, () => this.#resolveCaptchaProvider());
|
|
998
|
+
this.#config.logger?.debug("Запрос уходит без токена капчи: не передан аргумент captcha и не задана опция captcha");
|
|
999
|
+
return {};
|
|
1000
|
+
}
|
|
1001
|
+
/** Отдаёт разовый токен из `auth.captcha`, помечая его потраченным. */
|
|
1002
|
+
#takeConfiguredCaptcha() {
|
|
1003
|
+
if (this.#configCaptchaSpent) return void 0;
|
|
1004
|
+
const auth = this.#config.auth;
|
|
1005
|
+
const captcha = require_url.isRecord$1(auth) ? auth.captcha : void 0;
|
|
1006
|
+
if (!captcha) return void 0;
|
|
1007
|
+
this.#configCaptchaSpent = true;
|
|
1008
|
+
return captcha;
|
|
1009
|
+
}
|
|
1010
|
+
/** Спрашивает у сервера активного провайдера и поле, в котором он ждёт токен. */
|
|
1011
|
+
async #resolveCaptchaProvider() {
|
|
1012
|
+
return readCaptchaProvider(await this.#http.execute(AUTH_CAPTCHA_PROVIDER, {
|
|
1013
|
+
path: AUTH_PATHS.captchaProvider,
|
|
1014
|
+
skipAuth: true,
|
|
1015
|
+
skipAuthRefresh: true
|
|
1016
|
+
}));
|
|
866
1017
|
}
|
|
867
1018
|
async #performSignIn(credentials, revision) {
|
|
868
|
-
const
|
|
1019
|
+
const captcha = await this.captchaBody();
|
|
869
1020
|
const flowCookies = new require_redact.CookieJar();
|
|
870
1021
|
const result = await this.#http.execute(AUTH_SIGN_IN, {
|
|
871
1022
|
path: AUTH_PATHS.signIn,
|
|
872
1023
|
body: {
|
|
873
1024
|
email: credentials.email,
|
|
874
1025
|
password: credentials.password,
|
|
875
|
-
|
|
1026
|
+
...captcha
|
|
876
1027
|
},
|
|
877
1028
|
skipAuth: true,
|
|
878
1029
|
skipAuthRefresh: true,
|
|
@@ -896,6 +1047,125 @@ var AuthManager = class {
|
|
|
896
1047
|
}
|
|
897
1048
|
};
|
|
898
1049
|
//#endregion
|
|
1050
|
+
//#region src/session/qr-stream.ts
|
|
1051
|
+
const QR_STREAM_STATUSES = new Set(Object.values(QrLoginStreamStatus));
|
|
1052
|
+
function readQrLoginStreamEvent(data) {
|
|
1053
|
+
let value;
|
|
1054
|
+
try {
|
|
1055
|
+
value = JSON.parse(data);
|
|
1056
|
+
} catch {
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
if (!require_url.isRecord$1(value)) return void 0;
|
|
1060
|
+
const event = value;
|
|
1061
|
+
if (typeof event.status !== "string" || !QR_STREAM_STATUSES.has(event.status)) return;
|
|
1062
|
+
return {
|
|
1063
|
+
status: event.status,
|
|
1064
|
+
...typeof event.expiresIn === "number" && Number.isFinite(event.expiresIn) ? { expiresIn: event.expiresIn } : {}
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
/** Читает короткоживущий SSE-поток QR-входа и последовательно доставляет его события. */
|
|
1068
|
+
async function consumeQrLoginStream(body, onEvent, signal) {
|
|
1069
|
+
const reader = body.getReader();
|
|
1070
|
+
const decoder = new TextDecoder();
|
|
1071
|
+
let delivery = Promise.resolve();
|
|
1072
|
+
let completed = false;
|
|
1073
|
+
const cancel = () => {
|
|
1074
|
+
reader.cancel(signal?.reason).catch(() => {});
|
|
1075
|
+
};
|
|
1076
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
1077
|
+
const parser = require_events.createParser({ onEvent(message) {
|
|
1078
|
+
const event = readQrLoginStreamEvent(message.data);
|
|
1079
|
+
if (!event) return;
|
|
1080
|
+
delivery = delivery.then(() => onEvent(event));
|
|
1081
|
+
} });
|
|
1082
|
+
try {
|
|
1083
|
+
for (;;) {
|
|
1084
|
+
const { done, value } = await reader.read();
|
|
1085
|
+
if (done) break;
|
|
1086
|
+
parser.feed(decoder.decode(value, { stream: true }));
|
|
1087
|
+
await delivery;
|
|
1088
|
+
}
|
|
1089
|
+
parser.feed(decoder.decode());
|
|
1090
|
+
await delivery;
|
|
1091
|
+
if (signal?.aborted) throw signal.reason;
|
|
1092
|
+
completed = true;
|
|
1093
|
+
} finally {
|
|
1094
|
+
signal?.removeEventListener("abort", cancel);
|
|
1095
|
+
if (!completed) await reader.cancel().catch(() => {});
|
|
1096
|
+
reader.releaseLock?.();
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
/** Открывает изолированный HTTP/SSE-канал QR-входа. @internal */
|
|
1100
|
+
async function openQrLoginStream(connection, cookies, input, onEvent, signal) {
|
|
1101
|
+
connection.assertActive?.("открыть QR-поток");
|
|
1102
|
+
const path = AUTH_PATHS.qrStream;
|
|
1103
|
+
const method = "POST";
|
|
1104
|
+
const url = require_redact.joinUrl(connection.baseUrl, path);
|
|
1105
|
+
const abort = require_url.createRequestAbortScope(signal, connection.signal, 0, connection.clock);
|
|
1106
|
+
try {
|
|
1107
|
+
const headers = await connection.baseHeaders(url);
|
|
1108
|
+
headers.delete("Authorization");
|
|
1109
|
+
headers.delete("Cookie");
|
|
1110
|
+
headers.set("Accept", "text/event-stream");
|
|
1111
|
+
headers.set("Content-Type", "application/json");
|
|
1112
|
+
const cookie = cookies.getHeader(url);
|
|
1113
|
+
if (cookie) headers.set("Cookie", cookie);
|
|
1114
|
+
let response;
|
|
1115
|
+
try {
|
|
1116
|
+
response = await connection.fetch(url, {
|
|
1117
|
+
method,
|
|
1118
|
+
headers,
|
|
1119
|
+
body: JSON.stringify(input),
|
|
1120
|
+
credentials: "include",
|
|
1121
|
+
signal: abort.signal
|
|
1122
|
+
});
|
|
1123
|
+
} catch (error) {
|
|
1124
|
+
const failure = new require_errors.ItdNetworkError(`Не удалось открыть QR-поток: ${String(error)}`, {
|
|
1125
|
+
method,
|
|
1126
|
+
path,
|
|
1127
|
+
cause: error
|
|
1128
|
+
});
|
|
1129
|
+
throw require_url.requestAbortError(abort, {
|
|
1130
|
+
timeout: 0,
|
|
1131
|
+
method,
|
|
1132
|
+
path
|
|
1133
|
+
}, failure);
|
|
1134
|
+
}
|
|
1135
|
+
cookies.setFromResponse(response.url || url, response);
|
|
1136
|
+
if (!response.ok) {
|
|
1137
|
+
const text = await response.text();
|
|
1138
|
+
let body = text;
|
|
1139
|
+
try {
|
|
1140
|
+
body = JSON.parse(text);
|
|
1141
|
+
} catch {}
|
|
1142
|
+
throw require_url.createApiError({
|
|
1143
|
+
method,
|
|
1144
|
+
path,
|
|
1145
|
+
status: response.status,
|
|
1146
|
+
statusText: response.statusText,
|
|
1147
|
+
headers: response.headers,
|
|
1148
|
+
response,
|
|
1149
|
+
body,
|
|
1150
|
+
now: connection.clock.now()
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
if (!response.body) throw new require_errors.ItdNetworkError("Сервер не вернул тело QR-потока", {
|
|
1154
|
+
method,
|
|
1155
|
+
path
|
|
1156
|
+
});
|
|
1157
|
+
await consumeQrLoginStream(response.body, onEvent, abort.signal);
|
|
1158
|
+
} catch (error) {
|
|
1159
|
+
throw require_url.requestAbortError(abort, {
|
|
1160
|
+
timeout: 0,
|
|
1161
|
+
method,
|
|
1162
|
+
path
|
|
1163
|
+
}, error);
|
|
1164
|
+
} finally {
|
|
1165
|
+
abort.cleanup();
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
//#endregion
|
|
899
1169
|
//#region src/resources/auth.ts
|
|
900
1170
|
/**
|
|
901
1171
|
* Результат входа.
|
|
@@ -910,9 +1180,36 @@ var AuthManager = class {
|
|
|
910
1180
|
*/
|
|
911
1181
|
var AuthResource = class extends require_render.BaseResource {
|
|
912
1182
|
#auth;
|
|
1183
|
+
#connection;
|
|
1184
|
+
#qrFlows = /* @__PURE__ */ new Map();
|
|
913
1185
|
constructor(http, deps) {
|
|
914
1186
|
super(http);
|
|
915
1187
|
this.#auth = deps.auth;
|
|
1188
|
+
this.#connection = deps.connection;
|
|
1189
|
+
}
|
|
1190
|
+
#pruneQrFlows() {
|
|
1191
|
+
const now = this.#connection.clock.now();
|
|
1192
|
+
for (const [qrId, flow] of this.#qrFlows) if (flow.expiresAt !== void 0 && flow.expiresAt <= now) this.#qrFlows.delete(qrId);
|
|
1193
|
+
}
|
|
1194
|
+
#qrFlow(qrId) {
|
|
1195
|
+
this.#pruneQrFlows();
|
|
1196
|
+
const existing = this.#qrFlows.get(qrId);
|
|
1197
|
+
if (existing) return existing;
|
|
1198
|
+
const flow = {
|
|
1199
|
+
cookies: this.#auth.createCookieFlow(false),
|
|
1200
|
+
expiresAt: void 0,
|
|
1201
|
+
captchaRequired: false
|
|
1202
|
+
};
|
|
1203
|
+
this.#qrFlows.set(qrId, flow);
|
|
1204
|
+
return flow;
|
|
1205
|
+
}
|
|
1206
|
+
#touchQrFlow(flow, expiresIn) {
|
|
1207
|
+
if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn)) return;
|
|
1208
|
+
const expiresAt = this.#connection.clock.now() + Math.max(0, expiresIn) * 1e3;
|
|
1209
|
+
flow.expiresAt = flow.expiresAt === void 0 ? expiresAt : Math.min(flow.expiresAt, expiresAt);
|
|
1210
|
+
}
|
|
1211
|
+
#forgetTerminalQrError(qrId, error) {
|
|
1212
|
+
if (require_errors.isItdApiError(error) && error.hasCode("QR_EXPIRED", "QR_ALREADY_USED", "QR_TOKEN_MISMATCH")) this.#qrFlows.delete(qrId);
|
|
916
1213
|
}
|
|
917
1214
|
/**
|
|
918
1215
|
* Проверяет состояние авторизации и возвращает текущего пользователя.
|
|
@@ -926,14 +1223,109 @@ var AuthResource = class extends require_render.BaseResource {
|
|
|
926
1223
|
});
|
|
927
1224
|
}
|
|
928
1225
|
/**
|
|
1226
|
+
* Узнаёт активного провайдера капчи и поле токена.
|
|
1227
|
+
*
|
|
1228
|
+
* Сервер может переключить провайдера или переименовать поле без выпуска новой версии SDK.
|
|
1229
|
+
* Автоматическому входу этот вызов не нужен — клиент делает его сам.
|
|
1230
|
+
*/
|
|
1231
|
+
captchaProvider(options = {}) {
|
|
1232
|
+
return this.http.execute(AUTH_CAPTCHA_PROVIDER, {
|
|
1233
|
+
path: AUTH_PATHS.captchaProvider,
|
|
1234
|
+
skipAuth: true,
|
|
1235
|
+
skipAuthRefresh: true,
|
|
1236
|
+
...options
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
/** Создаёт короткоживущую сессию QR-входа. */
|
|
1240
|
+
async startQrLogin(options = {}) {
|
|
1241
|
+
const cookies = this.#auth.createCookieFlow(false);
|
|
1242
|
+
const result = await this.http.execute(AUTH_QR_START, {
|
|
1243
|
+
path: AUTH_PATHS.qrStart,
|
|
1244
|
+
skipAuth: true,
|
|
1245
|
+
skipAuthRefresh: true,
|
|
1246
|
+
cookieJar: cookies,
|
|
1247
|
+
...options
|
|
1248
|
+
});
|
|
1249
|
+
if (typeof result.qrId === "string" && result.qrId !== "") {
|
|
1250
|
+
const flow = {
|
|
1251
|
+
cookies,
|
|
1252
|
+
expiresAt: void 0,
|
|
1253
|
+
captchaRequired: false
|
|
1254
|
+
};
|
|
1255
|
+
this.#touchQrFlow(flow, result.expiresIn);
|
|
1256
|
+
this.#qrFlows.set(result.qrId, flow);
|
|
1257
|
+
}
|
|
1258
|
+
return result;
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Проверяет QR-сессию и завершает вход после подтверждения на другом устройстве.
|
|
1262
|
+
*
|
|
1263
|
+
* До подтверждения возвращает промежуточный статус. Токен капчи добывается, только когда
|
|
1264
|
+
* сервер о ней попросил — статусом `captcha_required` на прошлой проверке, — либо когда
|
|
1265
|
+
* передан в сам вызов. Полученный `accessToken` автоматически сохраняется в клиенте.
|
|
1266
|
+
*/
|
|
1267
|
+
async claimQrLogin(input, options = {}) {
|
|
1268
|
+
const revision = this.#auth.revision();
|
|
1269
|
+
const flow = this.#qrFlow(input.qrId);
|
|
1270
|
+
try {
|
|
1271
|
+
const { captcha, ...rest } = input;
|
|
1272
|
+
const needsCaptcha = captcha !== void 0 || flow.captchaRequired;
|
|
1273
|
+
const result = await this.http.execute(AUTH_QR_CLAIM, {
|
|
1274
|
+
path: AUTH_PATHS.qrClaim,
|
|
1275
|
+
body: {
|
|
1276
|
+
...rest,
|
|
1277
|
+
...needsCaptcha ? await this.#auth.captchaBody(captcha) : {}
|
|
1278
|
+
},
|
|
1279
|
+
skipAuth: true,
|
|
1280
|
+
skipAuthRefresh: true,
|
|
1281
|
+
cookieJar: flow.cookies,
|
|
1282
|
+
...options
|
|
1283
|
+
});
|
|
1284
|
+
this.#touchQrFlow(flow, result.expiresIn);
|
|
1285
|
+
if (result.status === QrLoginStatus.CaptchaRequired) flow.captchaRequired = true;
|
|
1286
|
+
if (result.status === QrLoginStatus.Authorized) {
|
|
1287
|
+
await this.#auth.commitAuthFlow(result.accessToken, flow.cookies, revision);
|
|
1288
|
+
this.#qrFlows.delete(input.qrId);
|
|
1289
|
+
} else if (result.status === QrLoginStatus.Rejected) this.#qrFlows.delete(input.qrId);
|
|
1290
|
+
return result;
|
|
1291
|
+
} catch (error) {
|
|
1292
|
+
this.#forgetTerminalQrError(input.qrId, error);
|
|
1293
|
+
throw error;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Слушает состояния QR-входа до закрытия ответа или отмены `signal`.
|
|
1298
|
+
*
|
|
1299
|
+
* Событие `approved` означает, что подтверждение завершено: после него вызовите
|
|
1300
|
+
* {@link claimQrLogin}, чтобы получить и сохранить access token.
|
|
1301
|
+
*/
|
|
1302
|
+
async streamQrLogin(input, onEvent, options = {}) {
|
|
1303
|
+
if (typeof onEvent !== "function") throw new require_errors.ItdConfigError("onEvent QR-потока должен быть функцией");
|
|
1304
|
+
const flow = this.#qrFlow(input.qrId);
|
|
1305
|
+
try {
|
|
1306
|
+
await openQrLoginStream(this.#connection, flow.cookies, input, async (event) => {
|
|
1307
|
+
this.#touchQrFlow(flow, event.expiresIn);
|
|
1308
|
+
if (event.status === QrLoginStreamStatus.Rejected) this.#qrFlows.delete(input.qrId);
|
|
1309
|
+
await onEvent(event);
|
|
1310
|
+
}, options.signal);
|
|
1311
|
+
} catch (error) {
|
|
1312
|
+
this.#forgetTerminalQrError(input.qrId, error);
|
|
1313
|
+
throw error;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
929
1317
|
* Регистрирует аккаунт и запускает подтверждение по коду.
|
|
930
1318
|
*
|
|
931
1319
|
* @returns `flowToken`, который нужно передать в {@link verifyOtp}
|
|
932
1320
|
*/
|
|
933
|
-
signUp(credentials, options = {}) {
|
|
1321
|
+
async signUp(credentials, options = {}) {
|
|
1322
|
+
const { captcha, ...rest } = credentials;
|
|
934
1323
|
return this.http.execute(AUTH_SIGN_UP, {
|
|
935
1324
|
path: AUTH_PATHS.signUp,
|
|
936
|
-
body:
|
|
1325
|
+
body: {
|
|
1326
|
+
...rest,
|
|
1327
|
+
...await this.#auth.captchaBody(captcha)
|
|
1328
|
+
},
|
|
937
1329
|
skipAuth: true,
|
|
938
1330
|
skipAuthRefresh: true,
|
|
939
1331
|
...options
|
|
@@ -947,20 +1339,25 @@ var AuthResource = class extends require_render.BaseResource {
|
|
|
947
1339
|
*
|
|
948
1340
|
* При успешном входе токен сохраняется в клиенте автоматически.
|
|
949
1341
|
*
|
|
950
|
-
* @param credentials email, пароль и
|
|
1342
|
+
* @param credentials email, пароль и токен капчи — см. {@link CredentialsWithCaptcha}
|
|
951
1343
|
*/
|
|
952
1344
|
async signIn(credentials, options = {}) {
|
|
1345
|
+
const { captcha, ...rest } = credentials;
|
|
953
1346
|
const revision = this.#auth.revision();
|
|
954
1347
|
const cookies = this.#auth.createCookieFlow(false);
|
|
1348
|
+
const body = {
|
|
1349
|
+
...rest,
|
|
1350
|
+
...await this.#auth.captchaBody(captcha)
|
|
1351
|
+
};
|
|
955
1352
|
const result = await this.http.execute(AUTH_SIGN_IN, {
|
|
956
1353
|
path: AUTH_PATHS.signIn,
|
|
957
|
-
body
|
|
1354
|
+
body,
|
|
958
1355
|
skipAuth: true,
|
|
959
1356
|
skipAuthRefresh: true,
|
|
960
1357
|
cookieJar: cookies,
|
|
961
1358
|
...options
|
|
962
1359
|
});
|
|
963
|
-
if (result.status === SignInStatus.Authenticated) await this.#auth.
|
|
1360
|
+
if (result.status === SignInStatus.Authenticated) await this.#auth.commitAuthFlow(result.accessToken, cookies, revision);
|
|
964
1361
|
else this.#auth.commitCookieFlow(cookies, revision);
|
|
965
1362
|
return result;
|
|
966
1363
|
}
|
|
@@ -980,7 +1377,7 @@ var AuthResource = class extends require_render.BaseResource {
|
|
|
980
1377
|
cookieJar: cookies,
|
|
981
1378
|
...options
|
|
982
1379
|
});
|
|
983
|
-
await this.#auth.
|
|
1380
|
+
await this.#auth.commitAuthFlow(accessToken, cookies, revision);
|
|
984
1381
|
return accessToken;
|
|
985
1382
|
}
|
|
986
1383
|
/** Отправляет код подтверждения повторно. */
|
|
@@ -1082,10 +1479,13 @@ var AuthResource = class extends require_render.BaseResource {
|
|
|
1082
1479
|
*
|
|
1083
1480
|
* @returns `flowToken`, который нужно передать в {@link resetPassword}
|
|
1084
1481
|
*/
|
|
1085
|
-
forgotPassword(input, options = {}) {
|
|
1482
|
+
async forgotPassword(input, options = {}) {
|
|
1086
1483
|
return this.http.execute(AUTH_FORGOT_PASSWORD, {
|
|
1087
1484
|
path: AUTH_PATHS.forgotPassword,
|
|
1088
|
-
body:
|
|
1485
|
+
body: {
|
|
1486
|
+
email: input.email,
|
|
1487
|
+
...await this.#auth.captchaBody(input.captcha)
|
|
1488
|
+
},
|
|
1089
1489
|
skipAuth: true,
|
|
1090
1490
|
skipAuthRefresh: true,
|
|
1091
1491
|
...options
|
|
@@ -1116,23 +1516,21 @@ var AuthResource = class extends require_render.BaseResource {
|
|
|
1116
1516
|
* ```ts
|
|
1117
1517
|
* await itd.auth.resetPasswordWithOtp({
|
|
1118
1518
|
* email,
|
|
1119
|
-
*
|
|
1519
|
+
* captcha: { type: provider, token, field },
|
|
1120
1520
|
* newPassword,
|
|
1121
1521
|
* getOtp: () => rl.question('Код из письма: '),
|
|
1122
1522
|
* });
|
|
1123
1523
|
* ```
|
|
1124
1524
|
*/
|
|
1125
1525
|
async resetPasswordWithOtp(input, options = {}) {
|
|
1126
|
-
const
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
}, options);
|
|
1130
|
-
const otp = await input.getOtp();
|
|
1526
|
+
const { getOtp, newPassword, ...request } = input;
|
|
1527
|
+
const flowToken = await this.forgotPassword(request, options);
|
|
1528
|
+
const otp = await getOtp();
|
|
1131
1529
|
await this.resetPassword({
|
|
1132
1530
|
email: input.email,
|
|
1133
1531
|
otp,
|
|
1134
1532
|
flowToken,
|
|
1135
|
-
newPassword
|
|
1533
|
+
newPassword
|
|
1136
1534
|
}, options);
|
|
1137
1535
|
}
|
|
1138
1536
|
/**
|
|
@@ -1221,8 +1619,9 @@ function createManagedClient(options, internals) {
|
|
|
1221
1619
|
* const itd = new ItdClient({
|
|
1222
1620
|
* // `auth` не обязателен: когда хранилище уже содержит сессию, токен берётся оттуда,
|
|
1223
1621
|
* // а истёкший продлевается сам. Здесь он нужен на первый запуск.
|
|
1224
|
-
*
|
|
1225
|
-
*
|
|
1622
|
+
* auth: { email, password },
|
|
1623
|
+
* // Капчу входа решает пакет @itd-api/captcha — см. CaptchaSolver.
|
|
1624
|
+
* captcha: createCaptchaSolver(),
|
|
1226
1625
|
* storage: new FileTokenStorage('./.itd-session.json'),
|
|
1227
1626
|
* rateLimit: { concurrency: 4, rps: 8 },
|
|
1228
1627
|
* });
|
|
@@ -1243,7 +1642,10 @@ var ItdClient = class ItdClient {
|
|
|
1243
1642
|
#auth;
|
|
1244
1643
|
/** Авторизация, сессии и пароли. */
|
|
1245
1644
|
get auth() {
|
|
1246
|
-
this.#auth ??= new AuthResource(this.#runtime.http, {
|
|
1645
|
+
this.#auth ??= new AuthResource(this.#runtime.http, {
|
|
1646
|
+
auth: this.#runtime.auth,
|
|
1647
|
+
connection: this.#runtime.connection()
|
|
1648
|
+
});
|
|
1247
1649
|
return this.#auth;
|
|
1248
1650
|
}
|
|
1249
1651
|
/** Профили, подписки, блокировки, приватность. */
|
|
@@ -1733,6 +2135,8 @@ function assertAccountsActive(accounts, action) {
|
|
|
1733
2135
|
*
|
|
1734
2136
|
* await using accounts = new ItdAccounts({
|
|
1735
2137
|
* storage: new FileMultiTokenStorage('./.itd-sessions.json'),
|
|
2138
|
+
* // Общие опции достаются каждому аккаунту: один источник токена капчи на всех.
|
|
2139
|
+
* captcha: createCaptchaSolver(),
|
|
1736
2140
|
* rateLimit: { concurrency: 4 },
|
|
1737
2141
|
* });
|
|
1738
2142
|
*
|
|
@@ -1740,7 +2144,7 @@ function assertAccountsActive(accounts, action) {
|
|
|
1740
2144
|
* await accounts.restore();
|
|
1741
2145
|
*
|
|
1742
2146
|
* if (!accounts.has('kiow')) {
|
|
1743
|
-
* accounts.addAccount('kiow', { auth: { email, password
|
|
2147
|
+
* accounts.addAccount('kiow', { auth: { email, password } });
|
|
1744
2148
|
* }
|
|
1745
2149
|
*
|
|
1746
2150
|
* await accounts.account('kiow').posts.create({ content: 'привет' });
|
|
@@ -1819,7 +2223,7 @@ var ItdAccounts = class ItdAccounts {
|
|
|
1819
2223
|
*
|
|
1820
2224
|
* @example
|
|
1821
2225
|
* ```ts
|
|
1822
|
-
* accounts.addAccount('bot', { auth: { email, password
|
|
2226
|
+
* accounts.addAccount('bot', { auth: { email, password } });
|
|
1823
2227
|
* accounts.addAccount('reader', { auth: '<accessToken>' });
|
|
1824
2228
|
* accounts.addAccount('через-прокси', { fetch: proxyFetch('socks5://…') });
|
|
1825
2229
|
* ```
|
|
@@ -2157,6 +2561,10 @@ exports.AUDIO_MIME_TYPES = require_render.AUDIO_MIME_TYPES;
|
|
|
2157
2561
|
exports.AccessType = require_url.AccessType;
|
|
2158
2562
|
exports.AttachmentType = require_url.AttachmentType;
|
|
2159
2563
|
exports.BUCKET_LIMITS = require_url.BUCKET_LIMITS;
|
|
2564
|
+
exports.CAPTCHA_FIELDS = CAPTCHA_FIELDS;
|
|
2565
|
+
exports.CaptchaChoice = require_url.CaptchaChoice;
|
|
2566
|
+
exports.CaptchaField = require_url.CaptchaField;
|
|
2567
|
+
exports.CaptchaType = require_url.CaptchaType;
|
|
2160
2568
|
exports.CommentSort = require_url.CommentSort;
|
|
2161
2569
|
exports.DEFAULT_BASE_URL = require_url.DEFAULT_BASE_URL;
|
|
2162
2570
|
exports.DEFAULT_RATE_LIMIT_BUCKET = require_url.DEFAULT_RATE_LIMIT_BUCKET;
|
|
@@ -2206,6 +2614,8 @@ exports.NotificationUpdateType = require_events.NotificationUpdateType;
|
|
|
2206
2614
|
exports.OPERATIONS = require_url.OPERATIONS;
|
|
2207
2615
|
exports.PaginationMode = require_render.PaginationMode;
|
|
2208
2616
|
exports.Paginator = require_render.Paginator;
|
|
2617
|
+
exports.QrLoginStatus = QrLoginStatus;
|
|
2618
|
+
exports.QrLoginStreamStatus = QrLoginStreamStatus;
|
|
2209
2619
|
exports.RateLimitPacing = require_url.RateLimitPacing;
|
|
2210
2620
|
exports.ReportReason = require_url.ReportReason;
|
|
2211
2621
|
exports.ReportTargetType = require_url.ReportTargetType;
|