itd-api 0.0.1 → 0.0.3
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 +57 -6
- package/dist/{chunk-QILCVTJI.cjs → chunk-3ODOEKQO.cjs} +354 -115
- package/dist/chunk-3ODOEKQO.cjs.map +1 -0
- package/dist/{chunk-76C65H5J.js → chunk-JIT55WDH.js} +347 -116
- package/dist/chunk-JIT55WDH.js.map +1 -0
- package/dist/{index-RzyK1gKg.d.cts → index-BCuk8jNA.d.cts} +220 -18
- package/dist/{index-RzyK1gKg.d.ts → index-BCuk8jNA.d.ts} +220 -18
- package/dist/index.cjs +94 -62
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/node.cjs +93 -61
- package/dist/node.d.cts +2 -2
- package/dist/node.d.ts +2 -2
- package/dist/node.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-76C65H5J.js.map +0 -1
- package/dist/chunk-QILCVTJI.cjs.map +0 -1
|
@@ -586,8 +586,15 @@ var ItdErrorCode = Object.freeze({
|
|
|
586
586
|
BUSINESS_RULE_VIOLATION: "BUSINESS_RULE_VIOLATION",
|
|
587
587
|
RATE_LIMIT_EXCEEDED: "RATE_LIMIT_EXCEEDED",
|
|
588
588
|
UNKNOWN_ERROR: "UNKNOWN_ERROR",
|
|
589
|
+
/** Сервер отвечает так на `404`, `ENTITY_NOT_FOUND` в этом случае не приходит. */
|
|
590
|
+
NOT_FOUND: "NOT_FOUND",
|
|
591
|
+
/** На практике не приходит: вместо него сервер шлёт `TURNSTILE_VERIFICATION_FAILED`. */
|
|
589
592
|
CAPTCHA_FAILED: "CAPTCHA_FAILED",
|
|
593
|
+
/** Капча не пройдена: токен Turnstile недействителен, просрочен или уже использован. */
|
|
594
|
+
TURNSTILE_VERIFICATION_FAILED: "TURNSTILE_VERIFICATION_FAILED",
|
|
590
595
|
OTP_INVALID: "OTP_INVALID",
|
|
596
|
+
/** `flowToken` неизвестен или просрочен — поток подтверждения нужно начинать заново. */
|
|
597
|
+
INVALID_FLOW_TOKEN: "INVALID_FLOW_TOKEN",
|
|
591
598
|
ACCOUNT_DEACTIVATED: "ACCOUNT_DEACTIVATED",
|
|
592
599
|
ACCOUNT_EMAIL_DOMAIN_NOT_ALLOWED: "ACCOUNT_EMAIL_DOMAIN_NOT_ALLOWED",
|
|
593
600
|
ACCOUNT_INVALID_CREDENTIALS: "ACCOUNT_INVALID_CREDENTIALS",
|
|
@@ -596,6 +603,10 @@ var ItdErrorCode = Object.freeze({
|
|
|
596
603
|
SESSION_EXPIRED: "SESSION_EXPIRED",
|
|
597
604
|
SESSION_REVOKED: "SESSION_REVOKED",
|
|
598
605
|
SESSION_INVALID_REFRESH_TOKEN: "SESSION_INVALID_REFRESH_TOKEN",
|
|
606
|
+
/** Запрос обновления пришёл без cookie `refresh_token` — продлевать нечего. */
|
|
607
|
+
REFRESH_TOKEN_MISSING: "REFRESH_TOKEN_MISSING",
|
|
608
|
+
/** Cookie `refresh_token` есть, но сессии за ней уже нет: отозвана или истекла. */
|
|
609
|
+
SESSION_NOT_FOUND: "SESSION_NOT_FOUND",
|
|
599
610
|
MISSING_FLOW_TOKEN: "MISSING_FLOW_TOKEN",
|
|
600
611
|
PROFILE_USERNAME_TAKEN: "PROFILE_USERNAME_TAKEN",
|
|
601
612
|
PROFILE_RESTRICTION_ACTIVE: "PROFILE_RESTRICTION_ACTIVE",
|
|
@@ -859,6 +870,8 @@ parseSetCookie.splitCookiesString = splitCookiesString;
|
|
|
859
870
|
|
|
860
871
|
// src/core/cookies.ts
|
|
861
872
|
var AUTH_FLAG_COOKIE = "is_auth";
|
|
873
|
+
var REFRESH_COOKIE = "refresh_token";
|
|
874
|
+
var REFRESH_COOKIE_PATH = "/api/v1/auth";
|
|
862
875
|
var SERIALIZED_SEPARATOR = " ";
|
|
863
876
|
function originOf(url) {
|
|
864
877
|
try {
|
|
@@ -898,6 +911,15 @@ var CookieJar = class {
|
|
|
898
911
|
const raw = typeof headers.getSetCookie === "function" ? headers.getSetCookie() : splitCookiesString(headers.get("set-cookie") ?? "");
|
|
899
912
|
if (raw.length > 0) this.setFromStrings(url, raw);
|
|
900
913
|
}
|
|
914
|
+
/**
|
|
915
|
+
* Кладёт cookie напрямую, минуя `Set-Cookie`.
|
|
916
|
+
*
|
|
917
|
+
* Нужно ровно в одном случае: пользователь передал refresh-токен строкой, а сервер читает
|
|
918
|
+
* его только из cookie. Значение не кодируется — оно уходит в заголовок как есть.
|
|
919
|
+
*/
|
|
920
|
+
set(url, name, value, path = "/") {
|
|
921
|
+
this.setFromStrings(url, [`${name}=${value}; Path=${path}`]);
|
|
922
|
+
}
|
|
901
923
|
/** Сохраняет cookie из готовых строк `Set-Cookie`. */
|
|
902
924
|
setFromStrings(url, setCookieStrings) {
|
|
903
925
|
const origin = originOf(url);
|
|
@@ -931,6 +953,23 @@ var CookieJar = class {
|
|
|
931
953
|
if (cookies.length === 0) return void 0;
|
|
932
954
|
return cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join("; ");
|
|
933
955
|
}
|
|
956
|
+
/**
|
|
957
|
+
* Значение действующей cookie.
|
|
958
|
+
*
|
|
959
|
+
* Нужно, чтобы забрать обновлённый refresh-токен: сервер ротирует его при каждом
|
|
960
|
+
* продлении сессии, и сохранять надо именно новое значение.
|
|
961
|
+
*
|
|
962
|
+
* @param url если указан, учитываются origin, путь и флаг `Secure`
|
|
963
|
+
*/
|
|
964
|
+
getValue(name, url) {
|
|
965
|
+
if (url) return this.#matching(url).find((cookie) => cookie.name === name)?.value;
|
|
966
|
+
const now = Date.now();
|
|
967
|
+
for (const jar of this.#byOrigin.values()) {
|
|
968
|
+
const cookie = jar.get(name);
|
|
969
|
+
if (cookie && (cookie.expires === void 0 || cookie.expires > now)) return cookie.value;
|
|
970
|
+
}
|
|
971
|
+
return void 0;
|
|
972
|
+
}
|
|
934
973
|
/**
|
|
935
974
|
* Есть ли действующая cookie с таким именем.
|
|
936
975
|
*
|
|
@@ -1059,11 +1098,67 @@ var Emitter = class {
|
|
|
1059
1098
|
}
|
|
1060
1099
|
};
|
|
1061
1100
|
|
|
1101
|
+
// src/core/runtime.ts
|
|
1102
|
+
function detectRuntime() {
|
|
1103
|
+
const nav = globalThis.navigator;
|
|
1104
|
+
if (nav?.product === "ReactNative") return "react-native";
|
|
1105
|
+
if (typeof document !== "undefined") return "browser";
|
|
1106
|
+
return "server";
|
|
1107
|
+
}
|
|
1108
|
+
function shouldUseCookieJar(mode) {
|
|
1109
|
+
if (mode === "browser") return false;
|
|
1110
|
+
if (mode === "server") return true;
|
|
1111
|
+
return detectRuntime() === "server";
|
|
1112
|
+
}
|
|
1113
|
+
function shouldSendCredentials(mode) {
|
|
1114
|
+
if (mode === "browser") return true;
|
|
1115
|
+
if (mode === "server") return false;
|
|
1116
|
+
return detectRuntime() === "browser";
|
|
1117
|
+
}
|
|
1118
|
+
function resolveFetch(custom) {
|
|
1119
|
+
if (custom) return custom;
|
|
1120
|
+
if (typeof globalThis.fetch === "function") {
|
|
1121
|
+
return globalThis.fetch.bind(globalThis);
|
|
1122
|
+
}
|
|
1123
|
+
throw new ItdConfigError(
|
|
1124
|
+
"\u0412 \u044D\u0442\u043E\u0439 \u0441\u0440\u0435\u0434\u0435 \u043D\u0435\u0442 \u0433\u043B\u043E\u0431\u0430\u043B\u044C\u043D\u043E\u0433\u043E fetch. \u041E\u0431\u043D\u043E\u0432\u0438\u0442\u0435\u0441\u044C \u0434\u043E Node 18+ \u043B\u0438\u0431\u043E \u043F\u0435\u0440\u0435\u0434\u0430\u0439\u0442\u0435 \u0441\u0432\u043E\u044E \u0440\u0435\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u044E \u0447\u0435\u0440\u0435\u0437 \u043E\u043F\u0446\u0438\u044E fetch."
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
function supportsStreamingBody() {
|
|
1128
|
+
return typeof ReadableStream !== "undefined" && typeof TextDecoder !== "undefined";
|
|
1129
|
+
}
|
|
1130
|
+
function createDeviceId() {
|
|
1131
|
+
const webCrypto = globalThis.crypto;
|
|
1132
|
+
if (typeof webCrypto?.randomUUID === "function") return webCrypto.randomUUID();
|
|
1133
|
+
const bytes = new Uint8Array(16);
|
|
1134
|
+
if (typeof webCrypto?.getRandomValues === "function") webCrypto.getRandomValues(bytes);
|
|
1135
|
+
else for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
1136
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
1137
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
1138
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1139
|
+
return [
|
|
1140
|
+
hex.slice(0, 8),
|
|
1141
|
+
hex.slice(8, 12),
|
|
1142
|
+
hex.slice(12, 16),
|
|
1143
|
+
hex.slice(16, 20),
|
|
1144
|
+
hex.slice(20, 32)
|
|
1145
|
+
].join("-");
|
|
1146
|
+
}
|
|
1147
|
+
function hasLocalStorage() {
|
|
1148
|
+
try {
|
|
1149
|
+
return typeof globalThis.localStorage !== "undefined" && globalThis.localStorage !== null;
|
|
1150
|
+
} catch {
|
|
1151
|
+
return false;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1062
1155
|
// src/core/auth.ts
|
|
1063
1156
|
var AUTH_PATHS = {
|
|
1064
1157
|
signIn: "/api/v1/auth/sign-in",
|
|
1065
1158
|
refresh: "/api/v1/auth/refresh"
|
|
1066
1159
|
};
|
|
1160
|
+
var TURNSTILE_SITE_KEY = "0x4AAAAAACHhxczw6fJGwPBg";
|
|
1161
|
+
var DEVICE_ID_HEADER = "X-Device-Id";
|
|
1067
1162
|
function readAccessToken(payload) {
|
|
1068
1163
|
if (typeof payload !== "object" || payload === null) return void 0;
|
|
1069
1164
|
const token = payload.accessToken;
|
|
@@ -1080,6 +1175,13 @@ var AuthManager = class {
|
|
|
1080
1175
|
#refreshing = null;
|
|
1081
1176
|
/** Общий промис входа по логину и паролю. */
|
|
1082
1177
|
#signingIn = null;
|
|
1178
|
+
/**
|
|
1179
|
+
* Идентификатор устройства.
|
|
1180
|
+
*
|
|
1181
|
+
* Держится отдельно от сессии намеренно: выход из аккаунта не меняет устройство,
|
|
1182
|
+
* поэтому `clear()` его не трогает.
|
|
1183
|
+
*/
|
|
1184
|
+
#deviceId;
|
|
1083
1185
|
constructor(config, http, jar) {
|
|
1084
1186
|
this.#config = config;
|
|
1085
1187
|
this.#http = http;
|
|
@@ -1110,6 +1212,23 @@ var AuthManager = class {
|
|
|
1110
1212
|
const token = await this.getAccessToken();
|
|
1111
1213
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
1112
1214
|
}
|
|
1215
|
+
/**
|
|
1216
|
+
* Идентификатор устройства для заголовка `X-Device-Id`.
|
|
1217
|
+
*
|
|
1218
|
+
* Заводится один раз и сохраняется в сессии, чтобы пережить перезапуск процесса:
|
|
1219
|
+
* сервер связывает с ним запись в списке сессий, и плавающее значение плодило бы
|
|
1220
|
+
* по новой сессии на каждый старт.
|
|
1221
|
+
*/
|
|
1222
|
+
async getDeviceId() {
|
|
1223
|
+
if (this.#deviceId) return this.#deviceId;
|
|
1224
|
+
const session = await this.#loadSession();
|
|
1225
|
+
const deviceId = this.#config.deviceId ?? session?.deviceId ?? createDeviceId();
|
|
1226
|
+
this.#deviceId = deviceId;
|
|
1227
|
+
if (session?.deviceId !== deviceId) {
|
|
1228
|
+
await this.#saveSession({ ...session ?? {}, deviceId });
|
|
1229
|
+
}
|
|
1230
|
+
return deviceId;
|
|
1231
|
+
}
|
|
1113
1232
|
/**
|
|
1114
1233
|
* Текущий токен доступа.
|
|
1115
1234
|
*
|
|
@@ -1125,7 +1244,7 @@ var AuthManager = class {
|
|
|
1125
1244
|
return await auth.getToken() ?? null;
|
|
1126
1245
|
}
|
|
1127
1246
|
if (typeof auth === "object" && "email" in auth) {
|
|
1128
|
-
return this.#signInWithCredentials(auth
|
|
1247
|
+
return this.#signInWithCredentials(auth);
|
|
1129
1248
|
}
|
|
1130
1249
|
return null;
|
|
1131
1250
|
}
|
|
@@ -1145,12 +1264,19 @@ var AuthManager = class {
|
|
|
1145
1264
|
return false;
|
|
1146
1265
|
}
|
|
1147
1266
|
}
|
|
1148
|
-
/**
|
|
1267
|
+
/**
|
|
1268
|
+
* Ошибка «сессию продлить нечем».
|
|
1269
|
+
*
|
|
1270
|
+
* Возникает, только когда обновление даже не начиналось: нет ни cookie `is_auth`,
|
|
1271
|
+
* ни refresh-токена. Если сервер ответил отказом, наружу уходит **его** ошибка —
|
|
1272
|
+
* подменять её этой значило бы прятать причину (`REFRESH_TOKEN_MISSING`,
|
|
1273
|
+
* `SESSION_NOT_FOUND`, `SESSION_REVOKED` — разные поводы и разные действия).
|
|
1274
|
+
*/
|
|
1149
1275
|
#noRefreshSessionError() {
|
|
1150
1276
|
return new ItdAuthError({
|
|
1151
1277
|
status: 401,
|
|
1152
1278
|
code: "SESSION_EXPIRED",
|
|
1153
|
-
message: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u0435\u0441\u0441\u0438\u044E: \u043D\u0435\u0442 \
|
|
1279
|
+
message: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u0435\u0441\u0441\u0438\u044E: \u043D\u0435\u0442 \u043D\u0438 cookie is_auth, \u043D\u0438 refresh-\u0442\u043E\u043A\u0435\u043D\u0430. \u0412\u043E\u0439\u0434\u0438\u0442\u0435 \u0437\u0430\u043D\u043E\u0432\u043E \u043B\u0438\u0431\u043E \u043F\u0435\u0440\u0435\u0434\u0430\u0439\u0442\u0435 refreshToken \u0432 auth.",
|
|
1154
1280
|
method: "POST",
|
|
1155
1281
|
path: AUTH_PATHS.refresh,
|
|
1156
1282
|
raw: void 0
|
|
@@ -1180,13 +1306,21 @@ var AuthManager = class {
|
|
|
1180
1306
|
/** Заменяет сессию целиком. */
|
|
1181
1307
|
async setSession(session) {
|
|
1182
1308
|
this.#jar.deserialize(session.cookies);
|
|
1309
|
+
this.#deviceId ??= session.deviceId;
|
|
1183
1310
|
await this.#saveSession(session);
|
|
1311
|
+
this.#seedRefreshCookie();
|
|
1184
1312
|
}
|
|
1185
|
-
/**
|
|
1313
|
+
/**
|
|
1314
|
+
* Забывает сессию и cookie. Сетевой запрос не выполняется.
|
|
1315
|
+
*
|
|
1316
|
+
* Идентификатор устройства выход переживает: иначе каждая пара «выход — вход» плодила бы
|
|
1317
|
+
* новую запись в списке сессий.
|
|
1318
|
+
*/
|
|
1186
1319
|
async clear() {
|
|
1187
1320
|
this.#session = null;
|
|
1188
1321
|
this.#jar.clear();
|
|
1189
1322
|
await this.#config.storage.clear();
|
|
1323
|
+
if (this.#deviceId) await this.#saveSession({ deviceId: this.#deviceId });
|
|
1190
1324
|
this.#emitter.emit("signOut", void 0);
|
|
1191
1325
|
}
|
|
1192
1326
|
async #loadSession() {
|
|
@@ -1199,8 +1333,23 @@ var AuthManager = class {
|
|
|
1199
1333
|
accessToken: stored.accessToken ?? fromConfig.accessToken,
|
|
1200
1334
|
refreshToken: stored.refreshToken ?? fromConfig.refreshToken
|
|
1201
1335
|
} : stored ?? fromConfig;
|
|
1336
|
+
this.#seedRefreshCookie();
|
|
1202
1337
|
return this.#session;
|
|
1203
1338
|
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Кладёт refresh-токен в jar как cookie `refresh_token`.
|
|
1341
|
+
*
|
|
1342
|
+
* `POST /api/v1/auth/refresh` читает токен только из cookie, поэтому переданный строкой
|
|
1343
|
+
* приходится превращать в неё. В браузере это невозможно — cookie помечена `HttpOnly`,
|
|
1344
|
+
* и там обновление работает только на той, что поставил сам сервер.
|
|
1345
|
+
*/
|
|
1346
|
+
#seedRefreshCookie() {
|
|
1347
|
+
if (!this.#config.useCookieJar) return;
|
|
1348
|
+
const refreshToken = this.#session?.refreshToken;
|
|
1349
|
+
if (!refreshToken) return;
|
|
1350
|
+
if (this.#jar.has(REFRESH_COOKIE)) return;
|
|
1351
|
+
this.#jar.set(this.#config.baseUrl, REFRESH_COOKIE, refreshToken, REFRESH_COOKIE_PATH);
|
|
1352
|
+
}
|
|
1204
1353
|
#sessionFromConfig(auth) {
|
|
1205
1354
|
if (!auth) return null;
|
|
1206
1355
|
if (typeof auth === "string") return { accessToken: auth, obtainedAt: Date.now() };
|
|
@@ -1215,7 +1364,12 @@ var AuthManager = class {
|
|
|
1215
1364
|
}
|
|
1216
1365
|
async #saveSession(session) {
|
|
1217
1366
|
const cookies = this.#config.useCookieJar ? this.#jar.serialize() : void 0;
|
|
1218
|
-
const
|
|
1367
|
+
const deviceId = session.deviceId ?? this.#deviceId;
|
|
1368
|
+
const next = {
|
|
1369
|
+
...session,
|
|
1370
|
+
...cookies?.length ? { cookies } : {},
|
|
1371
|
+
...deviceId ? { deviceId } : {}
|
|
1372
|
+
};
|
|
1219
1373
|
this.#session = next;
|
|
1220
1374
|
await this.#config.storage.set(next);
|
|
1221
1375
|
}
|
|
@@ -1241,17 +1395,22 @@ var AuthManager = class {
|
|
|
1241
1395
|
const payload = await this.#http.request({
|
|
1242
1396
|
method: "POST",
|
|
1243
1397
|
path: AUTH_PATHS.refresh,
|
|
1244
|
-
//
|
|
1398
|
+
// Тела нет намеренно: сервер читает refresh-токен только из cookie — см.
|
|
1399
|
+
// #seedRefreshCookie. По той же причине не нужен и устаревший Bearer.
|
|
1245
1400
|
skipAuth: true,
|
|
1246
1401
|
// Без этого 401 на самом обновлении вызвал бы новое обновление — и так по кругу.
|
|
1247
|
-
skipAuthRefresh: true
|
|
1248
|
-
...this.#session?.refreshToken ? { body: { refreshToken: this.#session.refreshToken } } : {}
|
|
1402
|
+
skipAuthRefresh: true
|
|
1249
1403
|
});
|
|
1250
1404
|
const accessToken = readAccessToken(payload);
|
|
1251
1405
|
if (!accessToken) return this.#reloginOrNull();
|
|
1406
|
+
const rotated = this.#jar.getValue(
|
|
1407
|
+
REFRESH_COOKIE,
|
|
1408
|
+
this.#config.baseUrl + REFRESH_COOKIE_PATH
|
|
1409
|
+
);
|
|
1252
1410
|
await this.#saveSession({
|
|
1253
1411
|
...this.#session ?? {},
|
|
1254
1412
|
accessToken,
|
|
1413
|
+
...rotated ? { refreshToken: rotated } : {},
|
|
1255
1414
|
obtainedAt: Date.now()
|
|
1256
1415
|
});
|
|
1257
1416
|
this.#emitter.emit("tokens", { accessToken });
|
|
@@ -1259,8 +1418,11 @@ var AuthManager = class {
|
|
|
1259
1418
|
} catch (error) {
|
|
1260
1419
|
if (error instanceof ItdApiError) {
|
|
1261
1420
|
this.#session = null;
|
|
1421
|
+
this.#jar.clear();
|
|
1262
1422
|
await this.#config.storage.clear();
|
|
1263
|
-
|
|
1423
|
+
const relogged = await this.#reloginOrNull();
|
|
1424
|
+
if (relogged !== null) return relogged;
|
|
1425
|
+
throw error;
|
|
1264
1426
|
}
|
|
1265
1427
|
throw error;
|
|
1266
1428
|
}
|
|
@@ -1272,7 +1434,7 @@ var AuthManager = class {
|
|
|
1272
1434
|
return null;
|
|
1273
1435
|
}
|
|
1274
1436
|
try {
|
|
1275
|
-
return await this.#signInWithCredentials(auth
|
|
1437
|
+
return await this.#signInWithCredentials(auth);
|
|
1276
1438
|
} catch {
|
|
1277
1439
|
return null;
|
|
1278
1440
|
}
|
|
@@ -1283,19 +1445,36 @@ var AuthManager = class {
|
|
|
1283
1445
|
* Параллельные вызовы объединяются: одновременный старт нескольких запросов не должен
|
|
1284
1446
|
* приводить к нескольким попыткам входа и блокировке аккаунта.
|
|
1285
1447
|
*/
|
|
1286
|
-
#signInWithCredentials(
|
|
1448
|
+
#signInWithCredentials(credentials) {
|
|
1287
1449
|
if (this.#signingIn) return this.#signingIn;
|
|
1288
|
-
const promise = this.#performSignIn(
|
|
1450
|
+
const promise = this.#performSignIn(credentials).finally(() => {
|
|
1289
1451
|
this.#signingIn = null;
|
|
1290
1452
|
});
|
|
1291
1453
|
this.#signingIn = promise;
|
|
1292
1454
|
return promise;
|
|
1293
1455
|
}
|
|
1294
|
-
|
|
1456
|
+
/**
|
|
1457
|
+
* Берёт токен капчи для входа.
|
|
1458
|
+
*
|
|
1459
|
+
* `getTurnstileToken` приоритетнее готовой строки: токен Turnstile одноразовый и живёт
|
|
1460
|
+
* несколько минут, поэтому при повторном входе через сутки годится только свежий.
|
|
1461
|
+
*/
|
|
1462
|
+
async #resolveTurnstileToken(credentials) {
|
|
1463
|
+
if (credentials.getTurnstileToken) {
|
|
1464
|
+
const token = await credentials.getTurnstileToken();
|
|
1465
|
+
if (token) return token;
|
|
1466
|
+
}
|
|
1467
|
+
if (credentials.turnstileToken) return credentials.turnstileToken;
|
|
1468
|
+
throw new ItdConfigError(
|
|
1469
|
+
"\u0412\u0445\u043E\u0434 \u043F\u043E email \u0438 \u043F\u0430\u0440\u043E\u043B\u044E \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0442\u043E\u043A\u0435\u043D \u043A\u0430\u043F\u0447\u0438 Cloudflare Turnstile: \u0431\u0435\u0437 \u043D\u0435\u0433\u043E \u0441\u0435\u0440\u0432\u0435\u0440 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 422. \u041F\u0435\u0440\u0435\u0434\u0430\u0439\u0442\u0435 auth.getTurnstileToken (\u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A \u0441\u0432\u0435\u0436\u0435\u0433\u043E \u0442\u043E\u043A\u0435\u043D\u0430) \u043B\u0438\u0431\u043E \u0440\u0430\u0437\u043E\u0432\u044B\u0439 auth.turnstileToken. \u041A\u043B\u044E\u0447 \u0432\u0438\u0434\u0436\u0435\u0442\u0430 \u2014 TURNSTILE_SITE_KEY."
|
|
1470
|
+
);
|
|
1471
|
+
}
|
|
1472
|
+
async #performSignIn(credentials) {
|
|
1473
|
+
const turnstileToken = await this.#resolveTurnstileToken(credentials);
|
|
1295
1474
|
const payload = await this.#http.request({
|
|
1296
1475
|
method: "POST",
|
|
1297
1476
|
path: AUTH_PATHS.signIn,
|
|
1298
|
-
body: { email, password },
|
|
1477
|
+
body: { email: credentials.email, password: credentials.password, turnstileToken },
|
|
1299
1478
|
skipAuth: true,
|
|
1300
1479
|
skipAuthRefresh: true
|
|
1301
1480
|
});
|
|
@@ -1312,43 +1491,6 @@ var AuthManager = class {
|
|
|
1312
1491
|
}
|
|
1313
1492
|
};
|
|
1314
1493
|
|
|
1315
|
-
// src/core/runtime.ts
|
|
1316
|
-
function detectRuntime() {
|
|
1317
|
-
const nav = globalThis.navigator;
|
|
1318
|
-
if (nav?.product === "ReactNative") return "react-native";
|
|
1319
|
-
if (typeof document !== "undefined") return "browser";
|
|
1320
|
-
return "server";
|
|
1321
|
-
}
|
|
1322
|
-
function shouldUseCookieJar(mode) {
|
|
1323
|
-
if (mode === "browser") return false;
|
|
1324
|
-
if (mode === "server") return true;
|
|
1325
|
-
return detectRuntime() === "server";
|
|
1326
|
-
}
|
|
1327
|
-
function shouldSendCredentials(mode) {
|
|
1328
|
-
if (mode === "browser") return true;
|
|
1329
|
-
if (mode === "server") return false;
|
|
1330
|
-
return detectRuntime() === "browser";
|
|
1331
|
-
}
|
|
1332
|
-
function resolveFetch(custom) {
|
|
1333
|
-
if (custom) return custom;
|
|
1334
|
-
if (typeof globalThis.fetch === "function") {
|
|
1335
|
-
return globalThis.fetch.bind(globalThis);
|
|
1336
|
-
}
|
|
1337
|
-
throw new ItdConfigError(
|
|
1338
|
-
"\u0412 \u044D\u0442\u043E\u0439 \u0441\u0440\u0435\u0434\u0435 \u043D\u0435\u0442 \u0433\u043B\u043E\u0431\u0430\u043B\u044C\u043D\u043E\u0433\u043E fetch. \u041E\u0431\u043D\u043E\u0432\u0438\u0442\u0435\u0441\u044C \u0434\u043E Node 18+ \u043B\u0438\u0431\u043E \u043F\u0435\u0440\u0435\u0434\u0430\u0439\u0442\u0435 \u0441\u0432\u043E\u044E \u0440\u0435\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u044E \u0447\u0435\u0440\u0435\u0437 \u043E\u043F\u0446\u0438\u044E fetch."
|
|
1339
|
-
);
|
|
1340
|
-
}
|
|
1341
|
-
function supportsStreamingBody() {
|
|
1342
|
-
return typeof ReadableStream !== "undefined" && typeof TextDecoder !== "undefined";
|
|
1343
|
-
}
|
|
1344
|
-
function hasLocalStorage() {
|
|
1345
|
-
try {
|
|
1346
|
-
return typeof globalThis.localStorage !== "undefined" && globalThis.localStorage !== null;
|
|
1347
|
-
} catch {
|
|
1348
|
-
return false;
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
1494
|
// src/core/storage.ts
|
|
1353
1495
|
var MemoryTokenStorage = class {
|
|
1354
1496
|
#session = null;
|
|
@@ -1463,6 +1605,8 @@ function normalizeBaseUrl(baseUrl) {
|
|
|
1463
1605
|
// src/core/config.ts
|
|
1464
1606
|
var DEFAULT_BASE_URL = "https://xn--d1ah4a.com";
|
|
1465
1607
|
var DEFAULT_TIMEOUT = 3e4;
|
|
1608
|
+
var LIBRARY_VERSION = "0.0.2";
|
|
1609
|
+
var DEFAULT_USER_AGENT = `Mozilla/5.0 (compatible; itd-api/${LIBRARY_VERSION}; +https://github.com/KiowDev/itd-api)`;
|
|
1466
1610
|
var DEFAULT_RATE_LIMIT_DELAYS = Object.freeze([1e3, 5e3, 3e4, 6e4, 9e4]);
|
|
1467
1611
|
function requirePositive(value, name) {
|
|
1468
1612
|
if (!Number.isFinite(value) || value < 0) {
|
|
@@ -1554,13 +1698,19 @@ function validateAuth(auth) {
|
|
|
1554
1698
|
return auth;
|
|
1555
1699
|
}
|
|
1556
1700
|
if ("email" in auth || "password" in auth) {
|
|
1557
|
-
const { email, password } = auth;
|
|
1701
|
+
const { email, password, turnstileToken, getTurnstileToken } = auth;
|
|
1558
1702
|
if (typeof email !== "string" || email.trim() === "") {
|
|
1559
1703
|
throw new ItdConfigError("auth.email \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043D\u0435\u043F\u0443\u0441\u0442\u043E\u0439 \u0441\u0442\u0440\u043E\u043A\u043E\u0439");
|
|
1560
1704
|
}
|
|
1561
1705
|
if (typeof password !== "string" || password === "") {
|
|
1562
1706
|
throw new ItdConfigError("auth.password \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043D\u0435\u043F\u0443\u0441\u0442\u043E\u0439 \u0441\u0442\u0440\u043E\u043A\u043E\u0439");
|
|
1563
1707
|
}
|
|
1708
|
+
if (getTurnstileToken !== void 0 && typeof getTurnstileToken !== "function") {
|
|
1709
|
+
throw new ItdConfigError("auth.getTurnstileToken \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0444\u0443\u043D\u043A\u0446\u0438\u0435\u0439");
|
|
1710
|
+
}
|
|
1711
|
+
if (turnstileToken !== void 0 && (typeof turnstileToken !== "string" || turnstileToken.trim() === "")) {
|
|
1712
|
+
throw new ItdConfigError("auth.turnstileToken \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043D\u0435\u043F\u0443\u0441\u0442\u043E\u0439 \u0441\u0442\u0440\u043E\u043A\u043E\u0439");
|
|
1713
|
+
}
|
|
1564
1714
|
return auth;
|
|
1565
1715
|
}
|
|
1566
1716
|
throw new ItdConfigError(
|
|
@@ -1573,6 +1723,9 @@ function resolveConfig(options = {}) {
|
|
|
1573
1723
|
throw new ItdConfigError(`mode \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C 'auto', 'browser' \u0438\u043B\u0438 'server', \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043E: ${mode}`);
|
|
1574
1724
|
}
|
|
1575
1725
|
const timeout = requirePositive(options.timeout ?? DEFAULT_TIMEOUT, "timeout");
|
|
1726
|
+
if (options.deviceId !== void 0 && (typeof options.deviceId !== "string" || options.deviceId.trim() === "")) {
|
|
1727
|
+
throw new ItdConfigError("deviceId \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043D\u0435\u043F\u0443\u0441\u0442\u043E\u0439 \u0441\u0442\u0440\u043E\u043A\u043E\u0439");
|
|
1728
|
+
}
|
|
1576
1729
|
return {
|
|
1577
1730
|
baseUrl: normalizeBaseUrl(options.baseUrl ?? DEFAULT_BASE_URL),
|
|
1578
1731
|
auth: validateAuth(options.auth),
|
|
@@ -1586,12 +1739,61 @@ function resolveConfig(options = {}) {
|
|
|
1586
1739
|
hooks: options.hooks ?? {},
|
|
1587
1740
|
logger: options.logger === true ? consoleLogger() : options.logger || void 0,
|
|
1588
1741
|
headers: { ...options.headers },
|
|
1742
|
+
deviceId: options.deviceId,
|
|
1743
|
+
// `false` — способ не слать заголовок вовсе; строка заменяет умолчание.
|
|
1744
|
+
userAgent: options.userAgent === false ? void 0 : options.userAgent ?? DEFAULT_USER_AGENT,
|
|
1589
1745
|
mode,
|
|
1590
1746
|
useCookieJar: shouldUseCookieJar(mode),
|
|
1591
1747
|
sendCredentials: shouldSendCredentials(mode)
|
|
1592
1748
|
};
|
|
1593
1749
|
}
|
|
1594
1750
|
|
|
1751
|
+
// src/core/redact.ts
|
|
1752
|
+
var SECRET_HEADERS = /* @__PURE__ */ new Set(["authorization", "cookie", "set-cookie", "x-api-key"]);
|
|
1753
|
+
var SECRET_FIELDS = /* @__PURE__ */ new Set([
|
|
1754
|
+
"password",
|
|
1755
|
+
"oldpassword",
|
|
1756
|
+
"newpassword",
|
|
1757
|
+
"accesstoken",
|
|
1758
|
+
"refreshtoken",
|
|
1759
|
+
"currentpassword",
|
|
1760
|
+
"flowtoken",
|
|
1761
|
+
"token",
|
|
1762
|
+
"turnstiletoken",
|
|
1763
|
+
"otp"
|
|
1764
|
+
]);
|
|
1765
|
+
function maskSecret(value) {
|
|
1766
|
+
if (value.length <= 8) return "\u2026";
|
|
1767
|
+
return `${value.slice(0, 4)}\u2026(${value.length})\u2026${value.slice(-3)}`;
|
|
1768
|
+
}
|
|
1769
|
+
function redactHeaders(headers) {
|
|
1770
|
+
const result = {};
|
|
1771
|
+
headers.forEach((value, name) => {
|
|
1772
|
+
if (SECRET_HEADERS.has(name.toLowerCase())) {
|
|
1773
|
+
const spaceAt = value.indexOf(" ");
|
|
1774
|
+
result[name] = spaceAt > 0 ? `${value.slice(0, spaceAt)} ${maskSecret(value.slice(spaceAt + 1))}` : maskSecret(value);
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1777
|
+
result[name] = value;
|
|
1778
|
+
});
|
|
1779
|
+
return result;
|
|
1780
|
+
}
|
|
1781
|
+
function redactBody(body) {
|
|
1782
|
+
if (body === null || body === void 0) return body;
|
|
1783
|
+
if (typeof FormData !== "undefined" && body instanceof FormData) return "[FormData]";
|
|
1784
|
+
if (typeof Blob !== "undefined" && body instanceof Blob) return "[Blob]";
|
|
1785
|
+
if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) return "[binary]";
|
|
1786
|
+
if (Array.isArray(body)) return body.map(redactBody);
|
|
1787
|
+
if (typeof body === "object") {
|
|
1788
|
+
const result = {};
|
|
1789
|
+
for (const [key, value] of Object.entries(body)) {
|
|
1790
|
+
result[key] = SECRET_FIELDS.has(key.toLowerCase()) ? "[\u0441\u043A\u0440\u044B\u0442\u043E]" : redactBody(value);
|
|
1791
|
+
}
|
|
1792
|
+
return result;
|
|
1793
|
+
}
|
|
1794
|
+
return body;
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1595
1797
|
// src/core/error-factory.ts
|
|
1596
1798
|
function isRecord(value) {
|
|
1597
1799
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -1707,6 +1909,10 @@ var CODE_TO_CLASS = {
|
|
|
1707
1909
|
SESSION_EXPIRED: ItdAuthError,
|
|
1708
1910
|
SESSION_REVOKED: ItdAuthError,
|
|
1709
1911
|
SESSION_INVALID_REFRESH_TOKEN: ItdAuthError,
|
|
1912
|
+
// Оба приходят с `/auth/refresh`: первый — когда cookie refresh_token не долетела,
|
|
1913
|
+
// второй — когда она есть, но сессия за ней уже мертва.
|
|
1914
|
+
REFRESH_TOKEN_MISSING: ItdAuthError,
|
|
1915
|
+
SESSION_NOT_FOUND: ItdAuthError,
|
|
1710
1916
|
ACCOUNT_INVALID_CREDENTIALS: ItdAuthError,
|
|
1711
1917
|
ACCESS_DENIED: ItdForbiddenError,
|
|
1712
1918
|
ENTITY_NOT_FOUND: ItdNotFoundError,
|
|
@@ -1724,6 +1930,10 @@ function classByStatus(status) {
|
|
|
1724
1930
|
if (status >= 500) return ItdServerError;
|
|
1725
1931
|
return ItdApiError;
|
|
1726
1932
|
}
|
|
1933
|
+
function safeRawBody(body) {
|
|
1934
|
+
if (!isRecord(body) || body.type !== "validation" || !isRecord(body.found)) return body;
|
|
1935
|
+
return { ...body, found: redactBody(body.found) };
|
|
1936
|
+
}
|
|
1727
1937
|
function createApiError(context) {
|
|
1728
1938
|
const parsed = parseErrorBody(context.body, context.status, context.statusText);
|
|
1729
1939
|
const rateLimit = readRateLimit(context.headers);
|
|
@@ -1739,7 +1949,7 @@ function createApiError(context) {
|
|
|
1739
1949
|
requestId: getRequestId(context.headers),
|
|
1740
1950
|
method: context.method,
|
|
1741
1951
|
path: context.path,
|
|
1742
|
-
raw: context.body,
|
|
1952
|
+
raw: safeRawBody(context.body),
|
|
1743
1953
|
response: context.response,
|
|
1744
1954
|
retryAfter: parseRetryAfter(context.headers?.get("retry-after"))
|
|
1745
1955
|
};
|
|
@@ -1750,50 +1960,6 @@ function createApiError(context) {
|
|
|
1750
1960
|
return new Ctor(init);
|
|
1751
1961
|
}
|
|
1752
1962
|
|
|
1753
|
-
// src/core/redact.ts
|
|
1754
|
-
var SECRET_HEADERS = /* @__PURE__ */ new Set(["authorization", "cookie", "set-cookie", "x-api-key"]);
|
|
1755
|
-
var SECRET_FIELDS = /* @__PURE__ */ new Set([
|
|
1756
|
-
"password",
|
|
1757
|
-
"oldpassword",
|
|
1758
|
-
"newpassword",
|
|
1759
|
-
"accesstoken",
|
|
1760
|
-
"refreshtoken",
|
|
1761
|
-
"flowtoken",
|
|
1762
|
-
"token",
|
|
1763
|
-
"otp"
|
|
1764
|
-
]);
|
|
1765
|
-
function maskSecret(value) {
|
|
1766
|
-
if (value.length <= 8) return "\u2026";
|
|
1767
|
-
return `${value.slice(0, 4)}\u2026(${value.length})\u2026${value.slice(-3)}`;
|
|
1768
|
-
}
|
|
1769
|
-
function redactHeaders(headers) {
|
|
1770
|
-
const result = {};
|
|
1771
|
-
headers.forEach((value, name) => {
|
|
1772
|
-
if (SECRET_HEADERS.has(name.toLowerCase())) {
|
|
1773
|
-
const spaceAt = value.indexOf(" ");
|
|
1774
|
-
result[name] = spaceAt > 0 ? `${value.slice(0, spaceAt)} ${maskSecret(value.slice(spaceAt + 1))}` : maskSecret(value);
|
|
1775
|
-
return;
|
|
1776
|
-
}
|
|
1777
|
-
result[name] = value;
|
|
1778
|
-
});
|
|
1779
|
-
return result;
|
|
1780
|
-
}
|
|
1781
|
-
function redactBody(body) {
|
|
1782
|
-
if (body === null || body === void 0) return body;
|
|
1783
|
-
if (typeof FormData !== "undefined" && body instanceof FormData) return "[FormData]";
|
|
1784
|
-
if (typeof Blob !== "undefined" && body instanceof Blob) return "[Blob]";
|
|
1785
|
-
if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) return "[binary]";
|
|
1786
|
-
if (Array.isArray(body)) return body.map(redactBody);
|
|
1787
|
-
if (typeof body === "object") {
|
|
1788
|
-
const result = {};
|
|
1789
|
-
for (const [key, value] of Object.entries(body)) {
|
|
1790
|
-
result[key] = SECRET_FIELDS.has(key.toLowerCase()) ? "[\u0441\u043A\u0440\u044B\u0442\u043E]" : redactBody(value);
|
|
1791
|
-
}
|
|
1792
|
-
return result;
|
|
1793
|
-
}
|
|
1794
|
-
return body;
|
|
1795
|
-
}
|
|
1796
|
-
|
|
1797
1963
|
// src/core/unwrap.ts
|
|
1798
1964
|
function unwrapData(body) {
|
|
1799
1965
|
if (typeof body !== "object" || body === null || Array.isArray(body)) return body;
|
|
@@ -1945,6 +2111,11 @@ var HttpClient = class {
|
|
|
1945
2111
|
async #buildHeaders(options, url) {
|
|
1946
2112
|
const headers = new Headers();
|
|
1947
2113
|
headers.set("Accept", "application/json");
|
|
2114
|
+
headers.set("X-Requested-With", "XMLHttpRequest");
|
|
2115
|
+
if (this.#config.userAgent) setHeader(headers, "User-Agent", this.#config.userAgent);
|
|
2116
|
+
if (this.#collaborators.getDeviceId) {
|
|
2117
|
+
setHeader(headers, "X-Device-Id", await this.#collaborators.getDeviceId());
|
|
2118
|
+
}
|
|
1948
2119
|
for (const [name, value] of Object.entries(this.#config.headers))
|
|
1949
2120
|
setHeader(headers, name, value);
|
|
1950
2121
|
if (!options.skipAuth && this.#collaborators.getAuthHeaders) {
|
|
@@ -3018,6 +3189,8 @@ var AuthResource = class extends BaseResource {
|
|
|
3018
3189
|
* тогда продолжайте через {@link verifyOtp} либо воспользуйтесь {@link signInWithOtp}.
|
|
3019
3190
|
*
|
|
3020
3191
|
* При успешном входе токен сохраняется в клиенте автоматически.
|
|
3192
|
+
*
|
|
3193
|
+
* @param credentials email, пароль и обязательный токен капчи — см. {@link CaptchaCredentials}
|
|
3021
3194
|
*/
|
|
3022
3195
|
async signIn(credentials, options = {}) {
|
|
3023
3196
|
const body = await this.http.request({
|
|
@@ -3095,7 +3268,15 @@ var AuthResource = class extends BaseResource {
|
|
|
3095
3268
|
);
|
|
3096
3269
|
}
|
|
3097
3270
|
const otp = await getOtp();
|
|
3098
|
-
return this.verifyOtp(
|
|
3271
|
+
return this.verifyOtp(
|
|
3272
|
+
{
|
|
3273
|
+
email: credentials.email,
|
|
3274
|
+
password: credentials.password,
|
|
3275
|
+
otp,
|
|
3276
|
+
flowToken: result.flowToken
|
|
3277
|
+
},
|
|
3278
|
+
options
|
|
3279
|
+
);
|
|
3099
3280
|
}
|
|
3100
3281
|
/**
|
|
3101
3282
|
* Обновляет токен доступа.
|
|
@@ -3126,32 +3307,48 @@ var AuthResource = class extends BaseResource {
|
|
|
3126
3307
|
});
|
|
3127
3308
|
await this.#auth.clear();
|
|
3128
3309
|
}
|
|
3129
|
-
/**
|
|
3310
|
+
/**
|
|
3311
|
+
* Завершает все сессии пользователя и очищает локальную.
|
|
3312
|
+
*
|
|
3313
|
+
* Собран из двух запросов, потому что единого эндпоинта на сервере нет:
|
|
3314
|
+
* `POST /api/v1/auth/logout-all` отвечает `404`. Сначала отзываются все прочие сессии
|
|
3315
|
+
* (`DELETE /api/v1/auth/sessions`), затем завершается текущая — в обратном порядке
|
|
3316
|
+
* отзывать было бы уже нечем.
|
|
3317
|
+
*/
|
|
3130
3318
|
async logoutAll(options = {}) {
|
|
3131
|
-
await this.
|
|
3132
|
-
|
|
3133
|
-
path: "/api/v1/auth/logout-all",
|
|
3134
|
-
skipAuthRefresh: true,
|
|
3135
|
-
...this.requestOptions(options)
|
|
3136
|
-
});
|
|
3137
|
-
await this.#auth.clear();
|
|
3319
|
+
await this.revokeOtherSessions(options);
|
|
3320
|
+
await this.logout(options);
|
|
3138
3321
|
}
|
|
3139
3322
|
/** Забывает сессию локально, не обращаясь к серверу. */
|
|
3140
3323
|
signOut() {
|
|
3141
3324
|
return this.#auth.clear();
|
|
3142
3325
|
}
|
|
3143
|
-
/**
|
|
3144
|
-
|
|
3145
|
-
|
|
3326
|
+
/**
|
|
3327
|
+
* Запрашивает письмо с кодом для сброса пароля.
|
|
3328
|
+
*
|
|
3329
|
+
* @returns `flowToken`, который нужно передать в {@link resetPassword}
|
|
3330
|
+
*/
|
|
3331
|
+
async forgotPassword(input, options = {}) {
|
|
3332
|
+
const body = await this.http.request({
|
|
3146
3333
|
method: "POST",
|
|
3147
3334
|
path: "/api/v1/auth/forgot-password",
|
|
3148
|
-
body:
|
|
3335
|
+
body: input,
|
|
3149
3336
|
skipAuth: true,
|
|
3150
3337
|
skipAuthRefresh: true,
|
|
3151
3338
|
...this.requestOptions(options)
|
|
3152
3339
|
});
|
|
3340
|
+
const flowToken = pickString(body, "flowToken");
|
|
3341
|
+
if (!flowToken) {
|
|
3342
|
+
throw new ItdConfigError("\u0421\u0435\u0440\u0432\u0435\u0440 \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B flowToken \u043F\u0440\u0438 \u0437\u0430\u043F\u0440\u043E\u0441\u0435 \u0441\u0431\u0440\u043E\u0441\u0430 \u043F\u0430\u0440\u043E\u043B\u044F");
|
|
3343
|
+
}
|
|
3344
|
+
return flowToken;
|
|
3153
3345
|
}
|
|
3154
|
-
/**
|
|
3346
|
+
/**
|
|
3347
|
+
* Устанавливает новый пароль по коду из письма.
|
|
3348
|
+
*
|
|
3349
|
+
* Сервер ждёт все четыре поля сразу — `email`, `otp`, `flowToken` и `newPassword`;
|
|
3350
|
+
* при нехватке любого отвечает `422`.
|
|
3351
|
+
*/
|
|
3155
3352
|
resetPassword(input, options = {}) {
|
|
3156
3353
|
return this.http.request({
|
|
3157
3354
|
method: "POST",
|
|
@@ -3162,12 +3359,45 @@ var AuthResource = class extends BaseResource {
|
|
|
3162
3359
|
...this.requestOptions(options)
|
|
3163
3360
|
});
|
|
3164
3361
|
}
|
|
3165
|
-
/**
|
|
3362
|
+
/**
|
|
3363
|
+
* Полный сброс пароля с кодом из письма.
|
|
3364
|
+
*
|
|
3365
|
+
* Тот же приём, что и {@link signInWithOtp}: код запрашивается функцией `getOtp`,
|
|
3366
|
+
* остальное библиотека делает сама.
|
|
3367
|
+
*
|
|
3368
|
+
* @example
|
|
3369
|
+
* ```ts
|
|
3370
|
+
* await itd.auth.resetPasswordWithOtp({
|
|
3371
|
+
* email,
|
|
3372
|
+
* turnstileToken,
|
|
3373
|
+
* newPassword,
|
|
3374
|
+
* getOtp: () => rl.question('Код из письма: '),
|
|
3375
|
+
* });
|
|
3376
|
+
* ```
|
|
3377
|
+
*/
|
|
3378
|
+
async resetPasswordWithOtp(input, options = {}) {
|
|
3379
|
+
const flowToken = await this.forgotPassword(
|
|
3380
|
+
{ email: input.email, turnstileToken: input.turnstileToken },
|
|
3381
|
+
options
|
|
3382
|
+
);
|
|
3383
|
+
const otp = await input.getOtp();
|
|
3384
|
+
await this.resetPassword(
|
|
3385
|
+
{ email: input.email, otp, flowToken, newPassword: input.newPassword },
|
|
3386
|
+
options
|
|
3387
|
+
);
|
|
3388
|
+
}
|
|
3389
|
+
/**
|
|
3390
|
+
* Меняет пароль. Требует действующей сессии.
|
|
3391
|
+
*
|
|
3392
|
+
* При неверном текущем пароле сервер отвечает `ACCOUNT_CURRENT_PASSWORD_INCORRECT`.
|
|
3393
|
+
*/
|
|
3166
3394
|
changePassword(input, options = {}) {
|
|
3167
3395
|
return this.http.request({
|
|
3168
3396
|
method: "POST",
|
|
3169
3397
|
path: "/api/v1/auth/change-password",
|
|
3170
|
-
|
|
3398
|
+
// Текущий пароль уходит под двумя именами: какое из них ждёт сервер, снаружи
|
|
3399
|
+
// не проверить, а лишнее поле он игнорирует.
|
|
3400
|
+
body: { ...input, currentPassword: input.oldPassword },
|
|
3171
3401
|
...this.requestOptions(options)
|
|
3172
3402
|
});
|
|
3173
3403
|
}
|
|
@@ -4550,6 +4780,7 @@ var ItdClient = class {
|
|
|
4550
4780
|
this.#queue = this.#config.rateLimit ? new RequestQueue(this.#config.rateLimit) : void 0;
|
|
4551
4781
|
this.#http.setCollaborators({
|
|
4552
4782
|
getAuthHeaders: () => this.#authManager.getAuthHeaders(),
|
|
4783
|
+
getDeviceId: () => this.#authManager.getDeviceId(),
|
|
4553
4784
|
onUnauthorized: () => this.#authManager.onUnauthorized(),
|
|
4554
4785
|
getCookieHeader: (url) => this.#jar.getHeader(url),
|
|
4555
4786
|
saveCookies: (url, response) => this.#jar.setFromResponse(url, response),
|
|
@@ -4818,6 +5049,6 @@ function toDate(value) {
|
|
|
4818
5049
|
return Number.isFinite(date.getTime()) ? date : null;
|
|
4819
5050
|
}
|
|
4820
5051
|
|
|
4821
|
-
export { ALLOWED_MIME_TYPES, AUDIO_MIME_TYPES, AttachmentType, CommentSort, DEFAULT_BASE_URL, DEFAULT_TIMEOUT, FeedTab, IMAGE_MIME_TYPES, ItdAbortError, ItdApiError, ItdAuthError, ItdClient, ItdConfigError, ItdConflictError, ItdError, ItdErrorCode, ItdForbiddenError, ItdNetworkError, ItdNotFoundError, ItdPhoneVerificationError, ItdRateLimitError, ItdRealtime, ItdServerError, ItdTimeoutError, ItdValidationError, LikesVisibility, LocalStorageTokenStorage, MAX_RECONNECT_ATTEMPTS, MemoryTokenStorage, NOTIFICATION_TYPE_ALIASES, NotificationType, Paginator, RECONNECT_BACKOFF, RECONNECT_JITTER, RealtimeStatus, ReportReason, ReportTargetType, STREAM_PATH, VIDEO_MIME_TYPES, WallAccess, canonicalNotificationType, comment, createClient, createTokenStorage, formatNotificationText, isBuilder, isItdApiError, isItdAuthError, isItdError, isItdRateLimitError, isItdValidationError, isKnownNotificationType, isMyProfile, normalizeNotification, poll, post, readNotificationEvent, readUnreadCountEvent, report, resolveNotificationUrl, toDate };
|
|
4822
|
-
//# sourceMappingURL=chunk-
|
|
4823
|
-
//# sourceMappingURL=chunk-
|
|
5052
|
+
export { ALLOWED_MIME_TYPES, AUDIO_MIME_TYPES, AUTH_FLAG_COOKIE, AUTH_PATHS, AttachmentType, CommentSort, DEFAULT_BASE_URL, DEFAULT_TIMEOUT, DEFAULT_USER_AGENT, DEVICE_ID_HEADER, FeedTab, IMAGE_MIME_TYPES, ItdAbortError, ItdApiError, ItdAuthError, ItdClient, ItdConfigError, ItdConflictError, ItdError, ItdErrorCode, ItdForbiddenError, ItdNetworkError, ItdNotFoundError, ItdPhoneVerificationError, ItdRateLimitError, ItdRealtime, ItdServerError, ItdTimeoutError, ItdValidationError, LIBRARY_VERSION, LikesVisibility, LocalStorageTokenStorage, MAX_RECONNECT_ATTEMPTS, MemoryTokenStorage, NOTIFICATION_TYPE_ALIASES, NotificationType, Paginator, RECONNECT_BACKOFF, RECONNECT_JITTER, REFRESH_COOKIE, REFRESH_COOKIE_PATH, RealtimeStatus, ReportReason, ReportTargetType, STREAM_PATH, TURNSTILE_SITE_KEY, VIDEO_MIME_TYPES, WallAccess, canonicalNotificationType, comment, createClient, createTokenStorage, formatNotificationText, isBuilder, isItdApiError, isItdAuthError, isItdError, isItdRateLimitError, isItdValidationError, isKnownNotificationType, isMyProfile, normalizeNotification, poll, post, readNotificationEvent, readUnreadCountEvent, report, resolveNotificationUrl, toDate };
|
|
5053
|
+
//# sourceMappingURL=chunk-JIT55WDH.js.map
|
|
5054
|
+
//# sourceMappingURL=chunk-JIT55WDH.js.map
|