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
|
@@ -588,8 +588,15 @@ var ItdErrorCode = Object.freeze({
|
|
|
588
588
|
BUSINESS_RULE_VIOLATION: "BUSINESS_RULE_VIOLATION",
|
|
589
589
|
RATE_LIMIT_EXCEEDED: "RATE_LIMIT_EXCEEDED",
|
|
590
590
|
UNKNOWN_ERROR: "UNKNOWN_ERROR",
|
|
591
|
+
/** Сервер отвечает так на `404`, `ENTITY_NOT_FOUND` в этом случае не приходит. */
|
|
592
|
+
NOT_FOUND: "NOT_FOUND",
|
|
593
|
+
/** На практике не приходит: вместо него сервер шлёт `TURNSTILE_VERIFICATION_FAILED`. */
|
|
591
594
|
CAPTCHA_FAILED: "CAPTCHA_FAILED",
|
|
595
|
+
/** Капча не пройдена: токен Turnstile недействителен, просрочен или уже использован. */
|
|
596
|
+
TURNSTILE_VERIFICATION_FAILED: "TURNSTILE_VERIFICATION_FAILED",
|
|
592
597
|
OTP_INVALID: "OTP_INVALID",
|
|
598
|
+
/** `flowToken` неизвестен или просрочен — поток подтверждения нужно начинать заново. */
|
|
599
|
+
INVALID_FLOW_TOKEN: "INVALID_FLOW_TOKEN",
|
|
593
600
|
ACCOUNT_DEACTIVATED: "ACCOUNT_DEACTIVATED",
|
|
594
601
|
ACCOUNT_EMAIL_DOMAIN_NOT_ALLOWED: "ACCOUNT_EMAIL_DOMAIN_NOT_ALLOWED",
|
|
595
602
|
ACCOUNT_INVALID_CREDENTIALS: "ACCOUNT_INVALID_CREDENTIALS",
|
|
@@ -598,6 +605,10 @@ var ItdErrorCode = Object.freeze({
|
|
|
598
605
|
SESSION_EXPIRED: "SESSION_EXPIRED",
|
|
599
606
|
SESSION_REVOKED: "SESSION_REVOKED",
|
|
600
607
|
SESSION_INVALID_REFRESH_TOKEN: "SESSION_INVALID_REFRESH_TOKEN",
|
|
608
|
+
/** Запрос обновления пришёл без cookie `refresh_token` — продлевать нечего. */
|
|
609
|
+
REFRESH_TOKEN_MISSING: "REFRESH_TOKEN_MISSING",
|
|
610
|
+
/** Cookie `refresh_token` есть, но сессии за ней уже нет: отозвана или истекла. */
|
|
611
|
+
SESSION_NOT_FOUND: "SESSION_NOT_FOUND",
|
|
601
612
|
MISSING_FLOW_TOKEN: "MISSING_FLOW_TOKEN",
|
|
602
613
|
PROFILE_USERNAME_TAKEN: "PROFILE_USERNAME_TAKEN",
|
|
603
614
|
PROFILE_RESTRICTION_ACTIVE: "PROFILE_RESTRICTION_ACTIVE",
|
|
@@ -861,6 +872,8 @@ parseSetCookie.splitCookiesString = splitCookiesString;
|
|
|
861
872
|
|
|
862
873
|
// src/core/cookies.ts
|
|
863
874
|
var AUTH_FLAG_COOKIE = "is_auth";
|
|
875
|
+
var REFRESH_COOKIE = "refresh_token";
|
|
876
|
+
var REFRESH_COOKIE_PATH = "/api/v1/auth";
|
|
864
877
|
var SERIALIZED_SEPARATOR = " ";
|
|
865
878
|
function originOf(url) {
|
|
866
879
|
try {
|
|
@@ -900,6 +913,15 @@ var CookieJar = class {
|
|
|
900
913
|
const raw = typeof headers.getSetCookie === "function" ? headers.getSetCookie() : splitCookiesString(headers.get("set-cookie") ?? "");
|
|
901
914
|
if (raw.length > 0) this.setFromStrings(url, raw);
|
|
902
915
|
}
|
|
916
|
+
/**
|
|
917
|
+
* Кладёт cookie напрямую, минуя `Set-Cookie`.
|
|
918
|
+
*
|
|
919
|
+
* Нужно ровно в одном случае: пользователь передал refresh-токен строкой, а сервер читает
|
|
920
|
+
* его только из cookie. Значение не кодируется — оно уходит в заголовок как есть.
|
|
921
|
+
*/
|
|
922
|
+
set(url, name, value, path = "/") {
|
|
923
|
+
this.setFromStrings(url, [`${name}=${value}; Path=${path}`]);
|
|
924
|
+
}
|
|
903
925
|
/** Сохраняет cookie из готовых строк `Set-Cookie`. */
|
|
904
926
|
setFromStrings(url, setCookieStrings) {
|
|
905
927
|
const origin = originOf(url);
|
|
@@ -933,6 +955,23 @@ var CookieJar = class {
|
|
|
933
955
|
if (cookies.length === 0) return void 0;
|
|
934
956
|
return cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join("; ");
|
|
935
957
|
}
|
|
958
|
+
/**
|
|
959
|
+
* Значение действующей cookie.
|
|
960
|
+
*
|
|
961
|
+
* Нужно, чтобы забрать обновлённый refresh-токен: сервер ротирует его при каждом
|
|
962
|
+
* продлении сессии, и сохранять надо именно новое значение.
|
|
963
|
+
*
|
|
964
|
+
* @param url если указан, учитываются origin, путь и флаг `Secure`
|
|
965
|
+
*/
|
|
966
|
+
getValue(name, url) {
|
|
967
|
+
if (url) return this.#matching(url).find((cookie) => cookie.name === name)?.value;
|
|
968
|
+
const now = Date.now();
|
|
969
|
+
for (const jar of this.#byOrigin.values()) {
|
|
970
|
+
const cookie = jar.get(name);
|
|
971
|
+
if (cookie && (cookie.expires === void 0 || cookie.expires > now)) return cookie.value;
|
|
972
|
+
}
|
|
973
|
+
return void 0;
|
|
974
|
+
}
|
|
936
975
|
/**
|
|
937
976
|
* Есть ли действующая cookie с таким именем.
|
|
938
977
|
*
|
|
@@ -1061,11 +1100,67 @@ var Emitter = class {
|
|
|
1061
1100
|
}
|
|
1062
1101
|
};
|
|
1063
1102
|
|
|
1103
|
+
// src/core/runtime.ts
|
|
1104
|
+
function detectRuntime() {
|
|
1105
|
+
const nav = globalThis.navigator;
|
|
1106
|
+
if (nav?.product === "ReactNative") return "react-native";
|
|
1107
|
+
if (typeof document !== "undefined") return "browser";
|
|
1108
|
+
return "server";
|
|
1109
|
+
}
|
|
1110
|
+
function shouldUseCookieJar(mode) {
|
|
1111
|
+
if (mode === "browser") return false;
|
|
1112
|
+
if (mode === "server") return true;
|
|
1113
|
+
return detectRuntime() === "server";
|
|
1114
|
+
}
|
|
1115
|
+
function shouldSendCredentials(mode) {
|
|
1116
|
+
if (mode === "browser") return true;
|
|
1117
|
+
if (mode === "server") return false;
|
|
1118
|
+
return detectRuntime() === "browser";
|
|
1119
|
+
}
|
|
1120
|
+
function resolveFetch(custom) {
|
|
1121
|
+
if (custom) return custom;
|
|
1122
|
+
if (typeof globalThis.fetch === "function") {
|
|
1123
|
+
return globalThis.fetch.bind(globalThis);
|
|
1124
|
+
}
|
|
1125
|
+
throw new ItdConfigError(
|
|
1126
|
+
"\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."
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
function supportsStreamingBody() {
|
|
1130
|
+
return typeof ReadableStream !== "undefined" && typeof TextDecoder !== "undefined";
|
|
1131
|
+
}
|
|
1132
|
+
function createDeviceId() {
|
|
1133
|
+
const webCrypto = globalThis.crypto;
|
|
1134
|
+
if (typeof webCrypto?.randomUUID === "function") return webCrypto.randomUUID();
|
|
1135
|
+
const bytes = new Uint8Array(16);
|
|
1136
|
+
if (typeof webCrypto?.getRandomValues === "function") webCrypto.getRandomValues(bytes);
|
|
1137
|
+
else for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
1138
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
1139
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
1140
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1141
|
+
return [
|
|
1142
|
+
hex.slice(0, 8),
|
|
1143
|
+
hex.slice(8, 12),
|
|
1144
|
+
hex.slice(12, 16),
|
|
1145
|
+
hex.slice(16, 20),
|
|
1146
|
+
hex.slice(20, 32)
|
|
1147
|
+
].join("-");
|
|
1148
|
+
}
|
|
1149
|
+
function hasLocalStorage() {
|
|
1150
|
+
try {
|
|
1151
|
+
return typeof globalThis.localStorage !== "undefined" && globalThis.localStorage !== null;
|
|
1152
|
+
} catch {
|
|
1153
|
+
return false;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1064
1157
|
// src/core/auth.ts
|
|
1065
1158
|
var AUTH_PATHS = {
|
|
1066
1159
|
signIn: "/api/v1/auth/sign-in",
|
|
1067
1160
|
refresh: "/api/v1/auth/refresh"
|
|
1068
1161
|
};
|
|
1162
|
+
var TURNSTILE_SITE_KEY = "0x4AAAAAACHhxczw6fJGwPBg";
|
|
1163
|
+
var DEVICE_ID_HEADER = "X-Device-Id";
|
|
1069
1164
|
function readAccessToken(payload) {
|
|
1070
1165
|
if (typeof payload !== "object" || payload === null) return void 0;
|
|
1071
1166
|
const token = payload.accessToken;
|
|
@@ -1082,6 +1177,13 @@ var AuthManager = class {
|
|
|
1082
1177
|
#refreshing = null;
|
|
1083
1178
|
/** Общий промис входа по логину и паролю. */
|
|
1084
1179
|
#signingIn = null;
|
|
1180
|
+
/**
|
|
1181
|
+
* Идентификатор устройства.
|
|
1182
|
+
*
|
|
1183
|
+
* Держится отдельно от сессии намеренно: выход из аккаунта не меняет устройство,
|
|
1184
|
+
* поэтому `clear()` его не трогает.
|
|
1185
|
+
*/
|
|
1186
|
+
#deviceId;
|
|
1085
1187
|
constructor(config, http, jar) {
|
|
1086
1188
|
this.#config = config;
|
|
1087
1189
|
this.#http = http;
|
|
@@ -1112,6 +1214,23 @@ var AuthManager = class {
|
|
|
1112
1214
|
const token = await this.getAccessToken();
|
|
1113
1215
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
1114
1216
|
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Идентификатор устройства для заголовка `X-Device-Id`.
|
|
1219
|
+
*
|
|
1220
|
+
* Заводится один раз и сохраняется в сессии, чтобы пережить перезапуск процесса:
|
|
1221
|
+
* сервер связывает с ним запись в списке сессий, и плавающее значение плодило бы
|
|
1222
|
+
* по новой сессии на каждый старт.
|
|
1223
|
+
*/
|
|
1224
|
+
async getDeviceId() {
|
|
1225
|
+
if (this.#deviceId) return this.#deviceId;
|
|
1226
|
+
const session = await this.#loadSession();
|
|
1227
|
+
const deviceId = this.#config.deviceId ?? session?.deviceId ?? createDeviceId();
|
|
1228
|
+
this.#deviceId = deviceId;
|
|
1229
|
+
if (session?.deviceId !== deviceId) {
|
|
1230
|
+
await this.#saveSession({ ...session ?? {}, deviceId });
|
|
1231
|
+
}
|
|
1232
|
+
return deviceId;
|
|
1233
|
+
}
|
|
1115
1234
|
/**
|
|
1116
1235
|
* Текущий токен доступа.
|
|
1117
1236
|
*
|
|
@@ -1127,7 +1246,7 @@ var AuthManager = class {
|
|
|
1127
1246
|
return await auth.getToken() ?? null;
|
|
1128
1247
|
}
|
|
1129
1248
|
if (typeof auth === "object" && "email" in auth) {
|
|
1130
|
-
return this.#signInWithCredentials(auth
|
|
1249
|
+
return this.#signInWithCredentials(auth);
|
|
1131
1250
|
}
|
|
1132
1251
|
return null;
|
|
1133
1252
|
}
|
|
@@ -1147,12 +1266,19 @@ var AuthManager = class {
|
|
|
1147
1266
|
return false;
|
|
1148
1267
|
}
|
|
1149
1268
|
}
|
|
1150
|
-
/**
|
|
1269
|
+
/**
|
|
1270
|
+
* Ошибка «сессию продлить нечем».
|
|
1271
|
+
*
|
|
1272
|
+
* Возникает, только когда обновление даже не начиналось: нет ни cookie `is_auth`,
|
|
1273
|
+
* ни refresh-токена. Если сервер ответил отказом, наружу уходит **его** ошибка —
|
|
1274
|
+
* подменять её этой значило бы прятать причину (`REFRESH_TOKEN_MISSING`,
|
|
1275
|
+
* `SESSION_NOT_FOUND`, `SESSION_REVOKED` — разные поводы и разные действия).
|
|
1276
|
+
*/
|
|
1151
1277
|
#noRefreshSessionError() {
|
|
1152
1278
|
return new ItdAuthError({
|
|
1153
1279
|
status: 401,
|
|
1154
1280
|
code: "SESSION_EXPIRED",
|
|
1155
|
-
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 \
|
|
1281
|
+
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.",
|
|
1156
1282
|
method: "POST",
|
|
1157
1283
|
path: AUTH_PATHS.refresh,
|
|
1158
1284
|
raw: void 0
|
|
@@ -1182,13 +1308,21 @@ var AuthManager = class {
|
|
|
1182
1308
|
/** Заменяет сессию целиком. */
|
|
1183
1309
|
async setSession(session) {
|
|
1184
1310
|
this.#jar.deserialize(session.cookies);
|
|
1311
|
+
this.#deviceId ??= session.deviceId;
|
|
1185
1312
|
await this.#saveSession(session);
|
|
1313
|
+
this.#seedRefreshCookie();
|
|
1186
1314
|
}
|
|
1187
|
-
/**
|
|
1315
|
+
/**
|
|
1316
|
+
* Забывает сессию и cookie. Сетевой запрос не выполняется.
|
|
1317
|
+
*
|
|
1318
|
+
* Идентификатор устройства выход переживает: иначе каждая пара «выход — вход» плодила бы
|
|
1319
|
+
* новую запись в списке сессий.
|
|
1320
|
+
*/
|
|
1188
1321
|
async clear() {
|
|
1189
1322
|
this.#session = null;
|
|
1190
1323
|
this.#jar.clear();
|
|
1191
1324
|
await this.#config.storage.clear();
|
|
1325
|
+
if (this.#deviceId) await this.#saveSession({ deviceId: this.#deviceId });
|
|
1192
1326
|
this.#emitter.emit("signOut", void 0);
|
|
1193
1327
|
}
|
|
1194
1328
|
async #loadSession() {
|
|
@@ -1201,8 +1335,23 @@ var AuthManager = class {
|
|
|
1201
1335
|
accessToken: stored.accessToken ?? fromConfig.accessToken,
|
|
1202
1336
|
refreshToken: stored.refreshToken ?? fromConfig.refreshToken
|
|
1203
1337
|
} : stored ?? fromConfig;
|
|
1338
|
+
this.#seedRefreshCookie();
|
|
1204
1339
|
return this.#session;
|
|
1205
1340
|
}
|
|
1341
|
+
/**
|
|
1342
|
+
* Кладёт refresh-токен в jar как cookie `refresh_token`.
|
|
1343
|
+
*
|
|
1344
|
+
* `POST /api/v1/auth/refresh` читает токен только из cookie, поэтому переданный строкой
|
|
1345
|
+
* приходится превращать в неё. В браузере это невозможно — cookie помечена `HttpOnly`,
|
|
1346
|
+
* и там обновление работает только на той, что поставил сам сервер.
|
|
1347
|
+
*/
|
|
1348
|
+
#seedRefreshCookie() {
|
|
1349
|
+
if (!this.#config.useCookieJar) return;
|
|
1350
|
+
const refreshToken = this.#session?.refreshToken;
|
|
1351
|
+
if (!refreshToken) return;
|
|
1352
|
+
if (this.#jar.has(REFRESH_COOKIE)) return;
|
|
1353
|
+
this.#jar.set(this.#config.baseUrl, REFRESH_COOKIE, refreshToken, REFRESH_COOKIE_PATH);
|
|
1354
|
+
}
|
|
1206
1355
|
#sessionFromConfig(auth) {
|
|
1207
1356
|
if (!auth) return null;
|
|
1208
1357
|
if (typeof auth === "string") return { accessToken: auth, obtainedAt: Date.now() };
|
|
@@ -1217,7 +1366,12 @@ var AuthManager = class {
|
|
|
1217
1366
|
}
|
|
1218
1367
|
async #saveSession(session) {
|
|
1219
1368
|
const cookies = this.#config.useCookieJar ? this.#jar.serialize() : void 0;
|
|
1220
|
-
const
|
|
1369
|
+
const deviceId = session.deviceId ?? this.#deviceId;
|
|
1370
|
+
const next = {
|
|
1371
|
+
...session,
|
|
1372
|
+
...cookies?.length ? { cookies } : {},
|
|
1373
|
+
...deviceId ? { deviceId } : {}
|
|
1374
|
+
};
|
|
1221
1375
|
this.#session = next;
|
|
1222
1376
|
await this.#config.storage.set(next);
|
|
1223
1377
|
}
|
|
@@ -1243,17 +1397,22 @@ var AuthManager = class {
|
|
|
1243
1397
|
const payload = await this.#http.request({
|
|
1244
1398
|
method: "POST",
|
|
1245
1399
|
path: AUTH_PATHS.refresh,
|
|
1246
|
-
//
|
|
1400
|
+
// Тела нет намеренно: сервер читает refresh-токен только из cookie — см.
|
|
1401
|
+
// #seedRefreshCookie. По той же причине не нужен и устаревший Bearer.
|
|
1247
1402
|
skipAuth: true,
|
|
1248
1403
|
// Без этого 401 на самом обновлении вызвал бы новое обновление — и так по кругу.
|
|
1249
|
-
skipAuthRefresh: true
|
|
1250
|
-
...this.#session?.refreshToken ? { body: { refreshToken: this.#session.refreshToken } } : {}
|
|
1404
|
+
skipAuthRefresh: true
|
|
1251
1405
|
});
|
|
1252
1406
|
const accessToken = readAccessToken(payload);
|
|
1253
1407
|
if (!accessToken) return this.#reloginOrNull();
|
|
1408
|
+
const rotated = this.#jar.getValue(
|
|
1409
|
+
REFRESH_COOKIE,
|
|
1410
|
+
this.#config.baseUrl + REFRESH_COOKIE_PATH
|
|
1411
|
+
);
|
|
1254
1412
|
await this.#saveSession({
|
|
1255
1413
|
...this.#session ?? {},
|
|
1256
1414
|
accessToken,
|
|
1415
|
+
...rotated ? { refreshToken: rotated } : {},
|
|
1257
1416
|
obtainedAt: Date.now()
|
|
1258
1417
|
});
|
|
1259
1418
|
this.#emitter.emit("tokens", { accessToken });
|
|
@@ -1261,8 +1420,11 @@ var AuthManager = class {
|
|
|
1261
1420
|
} catch (error) {
|
|
1262
1421
|
if (error instanceof ItdApiError) {
|
|
1263
1422
|
this.#session = null;
|
|
1423
|
+
this.#jar.clear();
|
|
1264
1424
|
await this.#config.storage.clear();
|
|
1265
|
-
|
|
1425
|
+
const relogged = await this.#reloginOrNull();
|
|
1426
|
+
if (relogged !== null) return relogged;
|
|
1427
|
+
throw error;
|
|
1266
1428
|
}
|
|
1267
1429
|
throw error;
|
|
1268
1430
|
}
|
|
@@ -1274,7 +1436,7 @@ var AuthManager = class {
|
|
|
1274
1436
|
return null;
|
|
1275
1437
|
}
|
|
1276
1438
|
try {
|
|
1277
|
-
return await this.#signInWithCredentials(auth
|
|
1439
|
+
return await this.#signInWithCredentials(auth);
|
|
1278
1440
|
} catch {
|
|
1279
1441
|
return null;
|
|
1280
1442
|
}
|
|
@@ -1285,19 +1447,36 @@ var AuthManager = class {
|
|
|
1285
1447
|
* Параллельные вызовы объединяются: одновременный старт нескольких запросов не должен
|
|
1286
1448
|
* приводить к нескольким попыткам входа и блокировке аккаунта.
|
|
1287
1449
|
*/
|
|
1288
|
-
#signInWithCredentials(
|
|
1450
|
+
#signInWithCredentials(credentials) {
|
|
1289
1451
|
if (this.#signingIn) return this.#signingIn;
|
|
1290
|
-
const promise = this.#performSignIn(
|
|
1452
|
+
const promise = this.#performSignIn(credentials).finally(() => {
|
|
1291
1453
|
this.#signingIn = null;
|
|
1292
1454
|
});
|
|
1293
1455
|
this.#signingIn = promise;
|
|
1294
1456
|
return promise;
|
|
1295
1457
|
}
|
|
1296
|
-
|
|
1458
|
+
/**
|
|
1459
|
+
* Берёт токен капчи для входа.
|
|
1460
|
+
*
|
|
1461
|
+
* `getTurnstileToken` приоритетнее готовой строки: токен Turnstile одноразовый и живёт
|
|
1462
|
+
* несколько минут, поэтому при повторном входе через сутки годится только свежий.
|
|
1463
|
+
*/
|
|
1464
|
+
async #resolveTurnstileToken(credentials) {
|
|
1465
|
+
if (credentials.getTurnstileToken) {
|
|
1466
|
+
const token = await credentials.getTurnstileToken();
|
|
1467
|
+
if (token) return token;
|
|
1468
|
+
}
|
|
1469
|
+
if (credentials.turnstileToken) return credentials.turnstileToken;
|
|
1470
|
+
throw new ItdConfigError(
|
|
1471
|
+
"\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."
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
async #performSignIn(credentials) {
|
|
1475
|
+
const turnstileToken = await this.#resolveTurnstileToken(credentials);
|
|
1297
1476
|
const payload = await this.#http.request({
|
|
1298
1477
|
method: "POST",
|
|
1299
1478
|
path: AUTH_PATHS.signIn,
|
|
1300
|
-
body: { email, password },
|
|
1479
|
+
body: { email: credentials.email, password: credentials.password, turnstileToken },
|
|
1301
1480
|
skipAuth: true,
|
|
1302
1481
|
skipAuthRefresh: true
|
|
1303
1482
|
});
|
|
@@ -1314,43 +1493,6 @@ var AuthManager = class {
|
|
|
1314
1493
|
}
|
|
1315
1494
|
};
|
|
1316
1495
|
|
|
1317
|
-
// src/core/runtime.ts
|
|
1318
|
-
function detectRuntime() {
|
|
1319
|
-
const nav = globalThis.navigator;
|
|
1320
|
-
if (nav?.product === "ReactNative") return "react-native";
|
|
1321
|
-
if (typeof document !== "undefined") return "browser";
|
|
1322
|
-
return "server";
|
|
1323
|
-
}
|
|
1324
|
-
function shouldUseCookieJar(mode) {
|
|
1325
|
-
if (mode === "browser") return false;
|
|
1326
|
-
if (mode === "server") return true;
|
|
1327
|
-
return detectRuntime() === "server";
|
|
1328
|
-
}
|
|
1329
|
-
function shouldSendCredentials(mode) {
|
|
1330
|
-
if (mode === "browser") return true;
|
|
1331
|
-
if (mode === "server") return false;
|
|
1332
|
-
return detectRuntime() === "browser";
|
|
1333
|
-
}
|
|
1334
|
-
function resolveFetch(custom) {
|
|
1335
|
-
if (custom) return custom;
|
|
1336
|
-
if (typeof globalThis.fetch === "function") {
|
|
1337
|
-
return globalThis.fetch.bind(globalThis);
|
|
1338
|
-
}
|
|
1339
|
-
throw new ItdConfigError(
|
|
1340
|
-
"\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."
|
|
1341
|
-
);
|
|
1342
|
-
}
|
|
1343
|
-
function supportsStreamingBody() {
|
|
1344
|
-
return typeof ReadableStream !== "undefined" && typeof TextDecoder !== "undefined";
|
|
1345
|
-
}
|
|
1346
|
-
function hasLocalStorage() {
|
|
1347
|
-
try {
|
|
1348
|
-
return typeof globalThis.localStorage !== "undefined" && globalThis.localStorage !== null;
|
|
1349
|
-
} catch {
|
|
1350
|
-
return false;
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
1496
|
// src/core/storage.ts
|
|
1355
1497
|
var MemoryTokenStorage = class {
|
|
1356
1498
|
#session = null;
|
|
@@ -1465,6 +1607,8 @@ function normalizeBaseUrl(baseUrl) {
|
|
|
1465
1607
|
// src/core/config.ts
|
|
1466
1608
|
var DEFAULT_BASE_URL = "https://xn--d1ah4a.com";
|
|
1467
1609
|
var DEFAULT_TIMEOUT = 3e4;
|
|
1610
|
+
var LIBRARY_VERSION = "0.0.2";
|
|
1611
|
+
var DEFAULT_USER_AGENT = `Mozilla/5.0 (compatible; itd-api/${LIBRARY_VERSION}; +https://github.com/KiowDev/itd-api)`;
|
|
1468
1612
|
var DEFAULT_RATE_LIMIT_DELAYS = Object.freeze([1e3, 5e3, 3e4, 6e4, 9e4]);
|
|
1469
1613
|
function requirePositive(value, name) {
|
|
1470
1614
|
if (!Number.isFinite(value) || value < 0) {
|
|
@@ -1556,13 +1700,19 @@ function validateAuth(auth) {
|
|
|
1556
1700
|
return auth;
|
|
1557
1701
|
}
|
|
1558
1702
|
if ("email" in auth || "password" in auth) {
|
|
1559
|
-
const { email, password } = auth;
|
|
1703
|
+
const { email, password, turnstileToken, getTurnstileToken } = auth;
|
|
1560
1704
|
if (typeof email !== "string" || email.trim() === "") {
|
|
1561
1705
|
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");
|
|
1562
1706
|
}
|
|
1563
1707
|
if (typeof password !== "string" || password === "") {
|
|
1564
1708
|
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");
|
|
1565
1709
|
}
|
|
1710
|
+
if (getTurnstileToken !== void 0 && typeof getTurnstileToken !== "function") {
|
|
1711
|
+
throw new ItdConfigError("auth.getTurnstileToken \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0444\u0443\u043D\u043A\u0446\u0438\u0435\u0439");
|
|
1712
|
+
}
|
|
1713
|
+
if (turnstileToken !== void 0 && (typeof turnstileToken !== "string" || turnstileToken.trim() === "")) {
|
|
1714
|
+
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");
|
|
1715
|
+
}
|
|
1566
1716
|
return auth;
|
|
1567
1717
|
}
|
|
1568
1718
|
throw new ItdConfigError(
|
|
@@ -1575,6 +1725,9 @@ function resolveConfig(options = {}) {
|
|
|
1575
1725
|
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}`);
|
|
1576
1726
|
}
|
|
1577
1727
|
const timeout = requirePositive(options.timeout ?? DEFAULT_TIMEOUT, "timeout");
|
|
1728
|
+
if (options.deviceId !== void 0 && (typeof options.deviceId !== "string" || options.deviceId.trim() === "")) {
|
|
1729
|
+
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");
|
|
1730
|
+
}
|
|
1578
1731
|
return {
|
|
1579
1732
|
baseUrl: normalizeBaseUrl(options.baseUrl ?? DEFAULT_BASE_URL),
|
|
1580
1733
|
auth: validateAuth(options.auth),
|
|
@@ -1588,12 +1741,61 @@ function resolveConfig(options = {}) {
|
|
|
1588
1741
|
hooks: options.hooks ?? {},
|
|
1589
1742
|
logger: options.logger === true ? consoleLogger() : options.logger || void 0,
|
|
1590
1743
|
headers: { ...options.headers },
|
|
1744
|
+
deviceId: options.deviceId,
|
|
1745
|
+
// `false` — способ не слать заголовок вовсе; строка заменяет умолчание.
|
|
1746
|
+
userAgent: options.userAgent === false ? void 0 : options.userAgent ?? DEFAULT_USER_AGENT,
|
|
1591
1747
|
mode,
|
|
1592
1748
|
useCookieJar: shouldUseCookieJar(mode),
|
|
1593
1749
|
sendCredentials: shouldSendCredentials(mode)
|
|
1594
1750
|
};
|
|
1595
1751
|
}
|
|
1596
1752
|
|
|
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
|
+
"currentpassword",
|
|
1762
|
+
"flowtoken",
|
|
1763
|
+
"token",
|
|
1764
|
+
"turnstiletoken",
|
|
1765
|
+
"otp"
|
|
1766
|
+
]);
|
|
1767
|
+
function maskSecret(value) {
|
|
1768
|
+
if (value.length <= 8) return "\u2026";
|
|
1769
|
+
return `${value.slice(0, 4)}\u2026(${value.length})\u2026${value.slice(-3)}`;
|
|
1770
|
+
}
|
|
1771
|
+
function redactHeaders(headers) {
|
|
1772
|
+
const result = {};
|
|
1773
|
+
headers.forEach((value, name) => {
|
|
1774
|
+
if (SECRET_HEADERS.has(name.toLowerCase())) {
|
|
1775
|
+
const spaceAt = value.indexOf(" ");
|
|
1776
|
+
result[name] = spaceAt > 0 ? `${value.slice(0, spaceAt)} ${maskSecret(value.slice(spaceAt + 1))}` : maskSecret(value);
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
result[name] = value;
|
|
1780
|
+
});
|
|
1781
|
+
return result;
|
|
1782
|
+
}
|
|
1783
|
+
function redactBody(body) {
|
|
1784
|
+
if (body === null || body === void 0) return body;
|
|
1785
|
+
if (typeof FormData !== "undefined" && body instanceof FormData) return "[FormData]";
|
|
1786
|
+
if (typeof Blob !== "undefined" && body instanceof Blob) return "[Blob]";
|
|
1787
|
+
if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) return "[binary]";
|
|
1788
|
+
if (Array.isArray(body)) return body.map(redactBody);
|
|
1789
|
+
if (typeof body === "object") {
|
|
1790
|
+
const result = {};
|
|
1791
|
+
for (const [key, value] of Object.entries(body)) {
|
|
1792
|
+
result[key] = SECRET_FIELDS.has(key.toLowerCase()) ? "[\u0441\u043A\u0440\u044B\u0442\u043E]" : redactBody(value);
|
|
1793
|
+
}
|
|
1794
|
+
return result;
|
|
1795
|
+
}
|
|
1796
|
+
return body;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1597
1799
|
// src/core/error-factory.ts
|
|
1598
1800
|
function isRecord(value) {
|
|
1599
1801
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -1709,6 +1911,10 @@ var CODE_TO_CLASS = {
|
|
|
1709
1911
|
SESSION_EXPIRED: ItdAuthError,
|
|
1710
1912
|
SESSION_REVOKED: ItdAuthError,
|
|
1711
1913
|
SESSION_INVALID_REFRESH_TOKEN: ItdAuthError,
|
|
1914
|
+
// Оба приходят с `/auth/refresh`: первый — когда cookie refresh_token не долетела,
|
|
1915
|
+
// второй — когда она есть, но сессия за ней уже мертва.
|
|
1916
|
+
REFRESH_TOKEN_MISSING: ItdAuthError,
|
|
1917
|
+
SESSION_NOT_FOUND: ItdAuthError,
|
|
1712
1918
|
ACCOUNT_INVALID_CREDENTIALS: ItdAuthError,
|
|
1713
1919
|
ACCESS_DENIED: ItdForbiddenError,
|
|
1714
1920
|
ENTITY_NOT_FOUND: ItdNotFoundError,
|
|
@@ -1726,6 +1932,10 @@ function classByStatus(status) {
|
|
|
1726
1932
|
if (status >= 500) return ItdServerError;
|
|
1727
1933
|
return ItdApiError;
|
|
1728
1934
|
}
|
|
1935
|
+
function safeRawBody(body) {
|
|
1936
|
+
if (!isRecord(body) || body.type !== "validation" || !isRecord(body.found)) return body;
|
|
1937
|
+
return { ...body, found: redactBody(body.found) };
|
|
1938
|
+
}
|
|
1729
1939
|
function createApiError(context) {
|
|
1730
1940
|
const parsed = parseErrorBody(context.body, context.status, context.statusText);
|
|
1731
1941
|
const rateLimit = readRateLimit(context.headers);
|
|
@@ -1741,7 +1951,7 @@ function createApiError(context) {
|
|
|
1741
1951
|
requestId: getRequestId(context.headers),
|
|
1742
1952
|
method: context.method,
|
|
1743
1953
|
path: context.path,
|
|
1744
|
-
raw: context.body,
|
|
1954
|
+
raw: safeRawBody(context.body),
|
|
1745
1955
|
response: context.response,
|
|
1746
1956
|
retryAfter: parseRetryAfter(context.headers?.get("retry-after"))
|
|
1747
1957
|
};
|
|
@@ -1752,50 +1962,6 @@ function createApiError(context) {
|
|
|
1752
1962
|
return new Ctor(init);
|
|
1753
1963
|
}
|
|
1754
1964
|
|
|
1755
|
-
// src/core/redact.ts
|
|
1756
|
-
var SECRET_HEADERS = /* @__PURE__ */ new Set(["authorization", "cookie", "set-cookie", "x-api-key"]);
|
|
1757
|
-
var SECRET_FIELDS = /* @__PURE__ */ new Set([
|
|
1758
|
-
"password",
|
|
1759
|
-
"oldpassword",
|
|
1760
|
-
"newpassword",
|
|
1761
|
-
"accesstoken",
|
|
1762
|
-
"refreshtoken",
|
|
1763
|
-
"flowtoken",
|
|
1764
|
-
"token",
|
|
1765
|
-
"otp"
|
|
1766
|
-
]);
|
|
1767
|
-
function maskSecret(value) {
|
|
1768
|
-
if (value.length <= 8) return "\u2026";
|
|
1769
|
-
return `${value.slice(0, 4)}\u2026(${value.length})\u2026${value.slice(-3)}`;
|
|
1770
|
-
}
|
|
1771
|
-
function redactHeaders(headers) {
|
|
1772
|
-
const result = {};
|
|
1773
|
-
headers.forEach((value, name) => {
|
|
1774
|
-
if (SECRET_HEADERS.has(name.toLowerCase())) {
|
|
1775
|
-
const spaceAt = value.indexOf(" ");
|
|
1776
|
-
result[name] = spaceAt > 0 ? `${value.slice(0, spaceAt)} ${maskSecret(value.slice(spaceAt + 1))}` : maskSecret(value);
|
|
1777
|
-
return;
|
|
1778
|
-
}
|
|
1779
|
-
result[name] = value;
|
|
1780
|
-
});
|
|
1781
|
-
return result;
|
|
1782
|
-
}
|
|
1783
|
-
function redactBody(body) {
|
|
1784
|
-
if (body === null || body === void 0) return body;
|
|
1785
|
-
if (typeof FormData !== "undefined" && body instanceof FormData) return "[FormData]";
|
|
1786
|
-
if (typeof Blob !== "undefined" && body instanceof Blob) return "[Blob]";
|
|
1787
|
-
if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) return "[binary]";
|
|
1788
|
-
if (Array.isArray(body)) return body.map(redactBody);
|
|
1789
|
-
if (typeof body === "object") {
|
|
1790
|
-
const result = {};
|
|
1791
|
-
for (const [key, value] of Object.entries(body)) {
|
|
1792
|
-
result[key] = SECRET_FIELDS.has(key.toLowerCase()) ? "[\u0441\u043A\u0440\u044B\u0442\u043E]" : redactBody(value);
|
|
1793
|
-
}
|
|
1794
|
-
return result;
|
|
1795
|
-
}
|
|
1796
|
-
return body;
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
1965
|
// src/core/unwrap.ts
|
|
1800
1966
|
function unwrapData(body) {
|
|
1801
1967
|
if (typeof body !== "object" || body === null || Array.isArray(body)) return body;
|
|
@@ -1947,6 +2113,11 @@ var HttpClient = class {
|
|
|
1947
2113
|
async #buildHeaders(options, url) {
|
|
1948
2114
|
const headers = new Headers();
|
|
1949
2115
|
headers.set("Accept", "application/json");
|
|
2116
|
+
headers.set("X-Requested-With", "XMLHttpRequest");
|
|
2117
|
+
if (this.#config.userAgent) setHeader(headers, "User-Agent", this.#config.userAgent);
|
|
2118
|
+
if (this.#collaborators.getDeviceId) {
|
|
2119
|
+
setHeader(headers, "X-Device-Id", await this.#collaborators.getDeviceId());
|
|
2120
|
+
}
|
|
1950
2121
|
for (const [name, value] of Object.entries(this.#config.headers))
|
|
1951
2122
|
setHeader(headers, name, value);
|
|
1952
2123
|
if (!options.skipAuth && this.#collaborators.getAuthHeaders) {
|
|
@@ -3020,6 +3191,8 @@ var AuthResource = class extends BaseResource {
|
|
|
3020
3191
|
* тогда продолжайте через {@link verifyOtp} либо воспользуйтесь {@link signInWithOtp}.
|
|
3021
3192
|
*
|
|
3022
3193
|
* При успешном входе токен сохраняется в клиенте автоматически.
|
|
3194
|
+
*
|
|
3195
|
+
* @param credentials email, пароль и обязательный токен капчи — см. {@link CaptchaCredentials}
|
|
3023
3196
|
*/
|
|
3024
3197
|
async signIn(credentials, options = {}) {
|
|
3025
3198
|
const body = await this.http.request({
|
|
@@ -3097,7 +3270,15 @@ var AuthResource = class extends BaseResource {
|
|
|
3097
3270
|
);
|
|
3098
3271
|
}
|
|
3099
3272
|
const otp = await getOtp();
|
|
3100
|
-
return this.verifyOtp(
|
|
3273
|
+
return this.verifyOtp(
|
|
3274
|
+
{
|
|
3275
|
+
email: credentials.email,
|
|
3276
|
+
password: credentials.password,
|
|
3277
|
+
otp,
|
|
3278
|
+
flowToken: result.flowToken
|
|
3279
|
+
},
|
|
3280
|
+
options
|
|
3281
|
+
);
|
|
3101
3282
|
}
|
|
3102
3283
|
/**
|
|
3103
3284
|
* Обновляет токен доступа.
|
|
@@ -3128,32 +3309,48 @@ var AuthResource = class extends BaseResource {
|
|
|
3128
3309
|
});
|
|
3129
3310
|
await this.#auth.clear();
|
|
3130
3311
|
}
|
|
3131
|
-
/**
|
|
3312
|
+
/**
|
|
3313
|
+
* Завершает все сессии пользователя и очищает локальную.
|
|
3314
|
+
*
|
|
3315
|
+
* Собран из двух запросов, потому что единого эндпоинта на сервере нет:
|
|
3316
|
+
* `POST /api/v1/auth/logout-all` отвечает `404`. Сначала отзываются все прочие сессии
|
|
3317
|
+
* (`DELETE /api/v1/auth/sessions`), затем завершается текущая — в обратном порядке
|
|
3318
|
+
* отзывать было бы уже нечем.
|
|
3319
|
+
*/
|
|
3132
3320
|
async logoutAll(options = {}) {
|
|
3133
|
-
await this.
|
|
3134
|
-
|
|
3135
|
-
path: "/api/v1/auth/logout-all",
|
|
3136
|
-
skipAuthRefresh: true,
|
|
3137
|
-
...this.requestOptions(options)
|
|
3138
|
-
});
|
|
3139
|
-
await this.#auth.clear();
|
|
3321
|
+
await this.revokeOtherSessions(options);
|
|
3322
|
+
await this.logout(options);
|
|
3140
3323
|
}
|
|
3141
3324
|
/** Забывает сессию локально, не обращаясь к серверу. */
|
|
3142
3325
|
signOut() {
|
|
3143
3326
|
return this.#auth.clear();
|
|
3144
3327
|
}
|
|
3145
|
-
/**
|
|
3146
|
-
|
|
3147
|
-
|
|
3328
|
+
/**
|
|
3329
|
+
* Запрашивает письмо с кодом для сброса пароля.
|
|
3330
|
+
*
|
|
3331
|
+
* @returns `flowToken`, который нужно передать в {@link resetPassword}
|
|
3332
|
+
*/
|
|
3333
|
+
async forgotPassword(input, options = {}) {
|
|
3334
|
+
const body = await this.http.request({
|
|
3148
3335
|
method: "POST",
|
|
3149
3336
|
path: "/api/v1/auth/forgot-password",
|
|
3150
|
-
body:
|
|
3337
|
+
body: input,
|
|
3151
3338
|
skipAuth: true,
|
|
3152
3339
|
skipAuthRefresh: true,
|
|
3153
3340
|
...this.requestOptions(options)
|
|
3154
3341
|
});
|
|
3342
|
+
const flowToken = pickString(body, "flowToken");
|
|
3343
|
+
if (!flowToken) {
|
|
3344
|
+
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");
|
|
3345
|
+
}
|
|
3346
|
+
return flowToken;
|
|
3155
3347
|
}
|
|
3156
|
-
/**
|
|
3348
|
+
/**
|
|
3349
|
+
* Устанавливает новый пароль по коду из письма.
|
|
3350
|
+
*
|
|
3351
|
+
* Сервер ждёт все четыре поля сразу — `email`, `otp`, `flowToken` и `newPassword`;
|
|
3352
|
+
* при нехватке любого отвечает `422`.
|
|
3353
|
+
*/
|
|
3157
3354
|
resetPassword(input, options = {}) {
|
|
3158
3355
|
return this.http.request({
|
|
3159
3356
|
method: "POST",
|
|
@@ -3164,12 +3361,45 @@ var AuthResource = class extends BaseResource {
|
|
|
3164
3361
|
...this.requestOptions(options)
|
|
3165
3362
|
});
|
|
3166
3363
|
}
|
|
3167
|
-
/**
|
|
3364
|
+
/**
|
|
3365
|
+
* Полный сброс пароля с кодом из письма.
|
|
3366
|
+
*
|
|
3367
|
+
* Тот же приём, что и {@link signInWithOtp}: код запрашивается функцией `getOtp`,
|
|
3368
|
+
* остальное библиотека делает сама.
|
|
3369
|
+
*
|
|
3370
|
+
* @example
|
|
3371
|
+
* ```ts
|
|
3372
|
+
* await itd.auth.resetPasswordWithOtp({
|
|
3373
|
+
* email,
|
|
3374
|
+
* turnstileToken,
|
|
3375
|
+
* newPassword,
|
|
3376
|
+
* getOtp: () => rl.question('Код из письма: '),
|
|
3377
|
+
* });
|
|
3378
|
+
* ```
|
|
3379
|
+
*/
|
|
3380
|
+
async resetPasswordWithOtp(input, options = {}) {
|
|
3381
|
+
const flowToken = await this.forgotPassword(
|
|
3382
|
+
{ email: input.email, turnstileToken: input.turnstileToken },
|
|
3383
|
+
options
|
|
3384
|
+
);
|
|
3385
|
+
const otp = await input.getOtp();
|
|
3386
|
+
await this.resetPassword(
|
|
3387
|
+
{ email: input.email, otp, flowToken, newPassword: input.newPassword },
|
|
3388
|
+
options
|
|
3389
|
+
);
|
|
3390
|
+
}
|
|
3391
|
+
/**
|
|
3392
|
+
* Меняет пароль. Требует действующей сессии.
|
|
3393
|
+
*
|
|
3394
|
+
* При неверном текущем пароле сервер отвечает `ACCOUNT_CURRENT_PASSWORD_INCORRECT`.
|
|
3395
|
+
*/
|
|
3168
3396
|
changePassword(input, options = {}) {
|
|
3169
3397
|
return this.http.request({
|
|
3170
3398
|
method: "POST",
|
|
3171
3399
|
path: "/api/v1/auth/change-password",
|
|
3172
|
-
|
|
3400
|
+
// Текущий пароль уходит под двумя именами: какое из них ждёт сервер, снаружи
|
|
3401
|
+
// не проверить, а лишнее поле он игнорирует.
|
|
3402
|
+
body: { ...input, currentPassword: input.oldPassword },
|
|
3173
3403
|
...this.requestOptions(options)
|
|
3174
3404
|
});
|
|
3175
3405
|
}
|
|
@@ -4552,6 +4782,7 @@ var ItdClient = class {
|
|
|
4552
4782
|
this.#queue = this.#config.rateLimit ? new RequestQueue(this.#config.rateLimit) : void 0;
|
|
4553
4783
|
this.#http.setCollaborators({
|
|
4554
4784
|
getAuthHeaders: () => this.#authManager.getAuthHeaders(),
|
|
4785
|
+
getDeviceId: () => this.#authManager.getDeviceId(),
|
|
4555
4786
|
onUnauthorized: () => this.#authManager.onUnauthorized(),
|
|
4556
4787
|
getCookieHeader: (url) => this.#jar.getHeader(url),
|
|
4557
4788
|
saveCookies: (url, response) => this.#jar.setFromResponse(url, response),
|
|
@@ -4822,10 +5053,14 @@ function toDate(value) {
|
|
|
4822
5053
|
|
|
4823
5054
|
exports.ALLOWED_MIME_TYPES = ALLOWED_MIME_TYPES;
|
|
4824
5055
|
exports.AUDIO_MIME_TYPES = AUDIO_MIME_TYPES;
|
|
5056
|
+
exports.AUTH_FLAG_COOKIE = AUTH_FLAG_COOKIE;
|
|
5057
|
+
exports.AUTH_PATHS = AUTH_PATHS;
|
|
4825
5058
|
exports.AttachmentType = AttachmentType;
|
|
4826
5059
|
exports.CommentSort = CommentSort;
|
|
4827
5060
|
exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
|
|
4828
5061
|
exports.DEFAULT_TIMEOUT = DEFAULT_TIMEOUT;
|
|
5062
|
+
exports.DEFAULT_USER_AGENT = DEFAULT_USER_AGENT;
|
|
5063
|
+
exports.DEVICE_ID_HEADER = DEVICE_ID_HEADER;
|
|
4829
5064
|
exports.FeedTab = FeedTab;
|
|
4830
5065
|
exports.IMAGE_MIME_TYPES = IMAGE_MIME_TYPES;
|
|
4831
5066
|
exports.ItdAbortError = ItdAbortError;
|
|
@@ -4845,6 +5080,7 @@ exports.ItdRealtime = ItdRealtime;
|
|
|
4845
5080
|
exports.ItdServerError = ItdServerError;
|
|
4846
5081
|
exports.ItdTimeoutError = ItdTimeoutError;
|
|
4847
5082
|
exports.ItdValidationError = ItdValidationError;
|
|
5083
|
+
exports.LIBRARY_VERSION = LIBRARY_VERSION;
|
|
4848
5084
|
exports.LikesVisibility = LikesVisibility;
|
|
4849
5085
|
exports.LocalStorageTokenStorage = LocalStorageTokenStorage;
|
|
4850
5086
|
exports.MAX_RECONNECT_ATTEMPTS = MAX_RECONNECT_ATTEMPTS;
|
|
@@ -4854,10 +5090,13 @@ exports.NotificationType = NotificationType;
|
|
|
4854
5090
|
exports.Paginator = Paginator;
|
|
4855
5091
|
exports.RECONNECT_BACKOFF = RECONNECT_BACKOFF;
|
|
4856
5092
|
exports.RECONNECT_JITTER = RECONNECT_JITTER;
|
|
5093
|
+
exports.REFRESH_COOKIE = REFRESH_COOKIE;
|
|
5094
|
+
exports.REFRESH_COOKIE_PATH = REFRESH_COOKIE_PATH;
|
|
4857
5095
|
exports.RealtimeStatus = RealtimeStatus;
|
|
4858
5096
|
exports.ReportReason = ReportReason;
|
|
4859
5097
|
exports.ReportTargetType = ReportTargetType;
|
|
4860
5098
|
exports.STREAM_PATH = STREAM_PATH;
|
|
5099
|
+
exports.TURNSTILE_SITE_KEY = TURNSTILE_SITE_KEY;
|
|
4861
5100
|
exports.VIDEO_MIME_TYPES = VIDEO_MIME_TYPES;
|
|
4862
5101
|
exports.WallAccess = WallAccess;
|
|
4863
5102
|
exports.canonicalNotificationType = canonicalNotificationType;
|
|
@@ -4881,5 +5120,5 @@ exports.readUnreadCountEvent = readUnreadCountEvent;
|
|
|
4881
5120
|
exports.report = report;
|
|
4882
5121
|
exports.resolveNotificationUrl = resolveNotificationUrl;
|
|
4883
5122
|
exports.toDate = toDate;
|
|
4884
|
-
//# sourceMappingURL=chunk-
|
|
4885
|
-
//# sourceMappingURL=chunk-
|
|
5123
|
+
//# sourceMappingURL=chunk-3ODOEKQO.cjs.map
|
|
5124
|
+
//# sourceMappingURL=chunk-3ODOEKQO.cjs.map
|