itd-api 0.0.3 → 0.0.4
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 +90 -19
- package/dist/{chunk-JIT55WDH.js → chunk-JV75JWOX.js} +315 -175
- package/dist/chunk-JV75JWOX.js.map +1 -0
- package/dist/{chunk-3ODOEKQO.cjs → chunk-XG43KEYF.cjs} +328 -174
- package/dist/chunk-XG43KEYF.cjs.map +1 -0
- package/dist/{index-BCuk8jNA.d.cts → index-olL_q_yu.d.cts} +176 -33
- package/dist/{index-BCuk8jNA.d.ts → index-olL_q_yu.d.ts} +176 -33
- package/dist/index.cjs +126 -70
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/node.cjs +125 -69
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +8 -12
- package/dist/node.d.ts +8 -12
- package/dist/node.js +2 -2
- package/dist/node.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-3ODOEKQO.cjs.map +0 -1
- package/dist/chunk-JIT55WDH.js.map +0 -1
|
@@ -14,6 +14,30 @@ function resolveInput(input, factory, validate) {
|
|
|
14
14
|
|
|
15
15
|
// src/core/errors.ts
|
|
16
16
|
var ITD_ERROR = /* @__PURE__ */ Symbol.for("itd.error");
|
|
17
|
+
var ItdErrorKind = Object.freeze({
|
|
18
|
+
/** Сервер ответил статусом ≥ 400. */
|
|
19
|
+
Api: "api",
|
|
20
|
+
/** Запрос не дошёл до сервера. */
|
|
21
|
+
Network: "network",
|
|
22
|
+
/** Истёк таймаут запроса. */
|
|
23
|
+
Timeout: "timeout",
|
|
24
|
+
/** Запрос отменён через `AbortSignal`. */
|
|
25
|
+
Abort: "abort",
|
|
26
|
+
/** Некорректная конфигурация или аргументы — обнаружено до обращения к сети. */
|
|
27
|
+
Config: "config"
|
|
28
|
+
});
|
|
29
|
+
var ItdApiErrorKind = Object.freeze({
|
|
30
|
+
/** Ни одна из специализаций не подошла. */
|
|
31
|
+
Generic: "generic",
|
|
32
|
+
Validation: "validation",
|
|
33
|
+
Auth: "auth",
|
|
34
|
+
Forbidden: "forbidden",
|
|
35
|
+
NotFound: "not_found",
|
|
36
|
+
Conflict: "conflict",
|
|
37
|
+
RateLimit: "rate_limit",
|
|
38
|
+
PhoneVerification: "phone_verification",
|
|
39
|
+
Server: "server"
|
|
40
|
+
});
|
|
17
41
|
var ItdError = class extends Error {
|
|
18
42
|
/** @internal */
|
|
19
43
|
[ITD_ERROR] = true;
|
|
@@ -26,6 +50,13 @@ var ItdError = class extends Error {
|
|
|
26
50
|
}
|
|
27
51
|
};
|
|
28
52
|
var ItdApiError = class extends ItdError {
|
|
53
|
+
/**
|
|
54
|
+
* Разновидность ошибки: та же информация, что и класс, но пригодная для сравнения.
|
|
55
|
+
*
|
|
56
|
+
* Позволяет разбирать ошибку через `switch`, а проверкам вроде {@link isItdAuthError} —
|
|
57
|
+
* работать даже когда в проекте оказались две копии библиотеки.
|
|
58
|
+
*/
|
|
59
|
+
apiKind;
|
|
29
60
|
/** HTTP-статус ответа. */
|
|
30
61
|
status;
|
|
31
62
|
/** Строковый код ошибки, например `VALIDATION_ERROR`. */
|
|
@@ -56,9 +87,13 @@ var ItdApiError = class extends ItdError {
|
|
|
56
87
|
rateLimit;
|
|
57
88
|
/** Сколько запросов осталось в окне — заголовок `x-ratelimit-remaining`. */
|
|
58
89
|
rateLimitRemaining;
|
|
59
|
-
|
|
60
|
-
|
|
90
|
+
/**
|
|
91
|
+
* @param apiKind разновидность; подставляется подклассами, снаружи задавать не нужно
|
|
92
|
+
*/
|
|
93
|
+
constructor(init, apiKind = ItdApiErrorKind.Generic) {
|
|
94
|
+
super(ItdErrorKind.Api, init.message);
|
|
61
95
|
this.name = "ItdApiError";
|
|
96
|
+
this.apiKind = apiKind;
|
|
62
97
|
this.status = init.status;
|
|
63
98
|
this.code = init.code;
|
|
64
99
|
this.detail = init.detail;
|
|
@@ -91,37 +126,37 @@ var ItdApiError = class extends ItdError {
|
|
|
91
126
|
};
|
|
92
127
|
var ItdValidationError = class extends ItdApiError {
|
|
93
128
|
constructor(init) {
|
|
94
|
-
super(init);
|
|
129
|
+
super(init, ItdApiErrorKind.Validation);
|
|
95
130
|
this.name = "ItdValidationError";
|
|
96
131
|
}
|
|
97
132
|
};
|
|
98
133
|
var ItdAuthError = class extends ItdApiError {
|
|
99
134
|
constructor(init) {
|
|
100
|
-
super(init);
|
|
135
|
+
super(init, ItdApiErrorKind.Auth);
|
|
101
136
|
this.name = "ItdAuthError";
|
|
102
137
|
}
|
|
103
138
|
};
|
|
104
139
|
var ItdForbiddenError = class extends ItdApiError {
|
|
105
140
|
constructor(init) {
|
|
106
|
-
super(init);
|
|
141
|
+
super(init, ItdApiErrorKind.Forbidden);
|
|
107
142
|
this.name = "ItdForbiddenError";
|
|
108
143
|
}
|
|
109
144
|
};
|
|
110
145
|
var ItdNotFoundError = class extends ItdApiError {
|
|
111
146
|
constructor(init) {
|
|
112
|
-
super(init);
|
|
147
|
+
super(init, ItdApiErrorKind.NotFound);
|
|
113
148
|
this.name = "ItdNotFoundError";
|
|
114
149
|
}
|
|
115
150
|
};
|
|
116
151
|
var ItdConflictError = class extends ItdApiError {
|
|
117
152
|
constructor(init) {
|
|
118
|
-
super(init);
|
|
153
|
+
super(init, ItdApiErrorKind.Conflict);
|
|
119
154
|
this.name = "ItdConflictError";
|
|
120
155
|
}
|
|
121
156
|
};
|
|
122
157
|
var ItdRateLimitError = class extends ItdApiError {
|
|
123
158
|
constructor(init) {
|
|
124
|
-
super(init);
|
|
159
|
+
super(init, ItdApiErrorKind.RateLimit);
|
|
125
160
|
this.name = "ItdRateLimitError";
|
|
126
161
|
}
|
|
127
162
|
};
|
|
@@ -129,14 +164,14 @@ var ItdPhoneVerificationError = class extends ItdApiError {
|
|
|
129
164
|
/** Ссылка на бота подтверждения, если удалось определить идентификатор пользователя. */
|
|
130
165
|
verificationUrl;
|
|
131
166
|
constructor(init) {
|
|
132
|
-
super(init);
|
|
167
|
+
super(init, ItdApiErrorKind.PhoneVerification);
|
|
133
168
|
this.name = "ItdPhoneVerificationError";
|
|
134
169
|
this.verificationUrl = init.userId ? `https://t.me/itd_verification_bot?start=${encodeURIComponent(init.userId)}` : void 0;
|
|
135
170
|
}
|
|
136
171
|
};
|
|
137
172
|
var ItdServerError = class extends ItdApiError {
|
|
138
173
|
constructor(init) {
|
|
139
|
-
super(init);
|
|
174
|
+
super(init, ItdApiErrorKind.Server);
|
|
140
175
|
this.name = "ItdServerError";
|
|
141
176
|
}
|
|
142
177
|
};
|
|
@@ -146,7 +181,7 @@ var ItdNetworkError = class extends ItdError {
|
|
|
146
181
|
/** Путь запроса без базового URL. */
|
|
147
182
|
path;
|
|
148
183
|
constructor(message, init) {
|
|
149
|
-
super(
|
|
184
|
+
super(ItdErrorKind.Network, message, { cause: init.cause });
|
|
150
185
|
this.name = "ItdNetworkError";
|
|
151
186
|
this.method = init.method;
|
|
152
187
|
this.path = init.path;
|
|
@@ -160,7 +195,10 @@ var ItdTimeoutError = class extends ItdError {
|
|
|
160
195
|
/** Путь запроса без базового URL. */
|
|
161
196
|
path;
|
|
162
197
|
constructor(init) {
|
|
163
|
-
super(
|
|
198
|
+
super(
|
|
199
|
+
ItdErrorKind.Timeout,
|
|
200
|
+
`\u0417\u0430\u043F\u0440\u043E\u0441 ${init.method} ${init.path} \u043F\u0440\u0435\u0432\u044B\u0441\u0438\u043B \u0442\u0430\u0439\u043C\u0430\u0443\u0442 ${init.timeout} \u043C\u0441`
|
|
201
|
+
);
|
|
164
202
|
this.name = "ItdTimeoutError";
|
|
165
203
|
this.timeout = init.timeout;
|
|
166
204
|
this.method = init.method;
|
|
@@ -169,13 +207,13 @@ var ItdTimeoutError = class extends ItdError {
|
|
|
169
207
|
};
|
|
170
208
|
var ItdAbortError = class extends ItdError {
|
|
171
209
|
constructor(message = "\u0417\u0430\u043F\u0440\u043E\u0441 \u043E\u0442\u043C\u0435\u043D\u0451\u043D") {
|
|
172
|
-
super(
|
|
210
|
+
super(ItdErrorKind.Abort, message);
|
|
173
211
|
this.name = "ItdAbortError";
|
|
174
212
|
}
|
|
175
213
|
};
|
|
176
214
|
var ItdConfigError = class extends ItdError {
|
|
177
215
|
constructor(message) {
|
|
178
|
-
super(
|
|
216
|
+
super(ItdErrorKind.Config, message);
|
|
179
217
|
this.name = "ItdConfigError";
|
|
180
218
|
}
|
|
181
219
|
};
|
|
@@ -183,16 +221,34 @@ function isItdError(value) {
|
|
|
183
221
|
return typeof value === "object" && value !== null && ITD_ERROR in value;
|
|
184
222
|
}
|
|
185
223
|
function isItdApiError(value) {
|
|
186
|
-
return isItdError(value) && value.kind ===
|
|
224
|
+
return isItdError(value) && value.kind === ItdErrorKind.Api;
|
|
225
|
+
}
|
|
226
|
+
function hasApiKind(value, kind) {
|
|
227
|
+
return isItdApiError(value) && value.apiKind === kind;
|
|
187
228
|
}
|
|
188
229
|
function isItdValidationError(value) {
|
|
189
|
-
return
|
|
230
|
+
return hasApiKind(value, ItdApiErrorKind.Validation);
|
|
190
231
|
}
|
|
191
232
|
function isItdAuthError(value) {
|
|
192
|
-
return
|
|
233
|
+
return hasApiKind(value, ItdApiErrorKind.Auth);
|
|
234
|
+
}
|
|
235
|
+
function isItdForbiddenError(value) {
|
|
236
|
+
return hasApiKind(value, ItdApiErrorKind.Forbidden);
|
|
237
|
+
}
|
|
238
|
+
function isItdNotFoundError(value) {
|
|
239
|
+
return hasApiKind(value, ItdApiErrorKind.NotFound);
|
|
240
|
+
}
|
|
241
|
+
function isItdConflictError(value) {
|
|
242
|
+
return hasApiKind(value, ItdApiErrorKind.Conflict);
|
|
193
243
|
}
|
|
194
244
|
function isItdRateLimitError(value) {
|
|
195
|
-
return
|
|
245
|
+
return hasApiKind(value, ItdApiErrorKind.RateLimit);
|
|
246
|
+
}
|
|
247
|
+
function isItdPhoneVerificationError(value) {
|
|
248
|
+
return hasApiKind(value, ItdApiErrorKind.PhoneVerification);
|
|
249
|
+
}
|
|
250
|
+
function isItdServerError(value) {
|
|
251
|
+
return hasApiKind(value, ItdApiErrorKind.Server);
|
|
196
252
|
}
|
|
197
253
|
|
|
198
254
|
// src/builders/comment.ts
|
|
@@ -622,13 +678,14 @@ var ItdErrorCode = Object.freeze({
|
|
|
622
678
|
|
|
623
679
|
// src/builders/report.ts
|
|
624
680
|
var REASONS = new Set(Object.values(ReportReason));
|
|
681
|
+
var TARGET_TYPES = new Set(Object.values(ReportTargetType));
|
|
625
682
|
function validateReport(input) {
|
|
626
683
|
if (!input?.targetId || typeof input.targetId !== "string") {
|
|
627
684
|
throw new ItdConfigError("\u0416\u0430\u043B\u043E\u0431\u0430 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 \u043E\u0431\u044A\u0435\u043A\u0442\u0430 (targetId)");
|
|
628
685
|
}
|
|
629
|
-
if (
|
|
686
|
+
if (!TARGET_TYPES.has(input.targetType)) {
|
|
630
687
|
throw new ItdConfigError(
|
|
631
|
-
`targetType \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C
|
|
688
|
+
`targetType \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043E\u0434\u043D\u0438\u043C \u0438\u0437 ${[...TARGET_TYPES].join(", ")}, \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043E: ${String(input.targetType)}`
|
|
632
689
|
);
|
|
633
690
|
}
|
|
634
691
|
if (!REASONS.has(input.reason)) {
|
|
@@ -666,11 +723,11 @@ function start(targetType, targetId) {
|
|
|
666
723
|
}
|
|
667
724
|
var report = Object.freeze({
|
|
668
725
|
/** Жалоба на пост. */
|
|
669
|
-
post: (postId) => start(
|
|
726
|
+
post: (postId) => start(ReportTargetType.Post, postId),
|
|
670
727
|
/** Жалоба на комментарий. */
|
|
671
|
-
comment: (commentId) => start(
|
|
728
|
+
comment: (commentId) => start(ReportTargetType.Comment, commentId),
|
|
672
729
|
/** Жалоба на пользователя. */
|
|
673
|
-
user: (userId) => start(
|
|
730
|
+
user: (userId) => start(ReportTargetType.User, userId)
|
|
674
731
|
});
|
|
675
732
|
function resolveReport(input) {
|
|
676
733
|
return resolveInput(input, () => new ReportBuilder({}), validateReport);
|
|
@@ -1099,21 +1156,35 @@ var Emitter = class {
|
|
|
1099
1156
|
};
|
|
1100
1157
|
|
|
1101
1158
|
// src/core/runtime.ts
|
|
1159
|
+
var RuntimeMode = Object.freeze({
|
|
1160
|
+
/** Определяется по среде исполнения. Значение по умолчанию. */
|
|
1161
|
+
Auto: "auto",
|
|
1162
|
+
/** Cookie ведёт браузер, запросы уходят с `credentials: 'include'`. */
|
|
1163
|
+
Browser: "browser",
|
|
1164
|
+
/** Cookie ведёт встроенный jar, заголовок `Cookie` подставляется вручную. */
|
|
1165
|
+
Server: "server"
|
|
1166
|
+
});
|
|
1167
|
+
var DetectedRuntime = Object.freeze({
|
|
1168
|
+
Browser: "browser",
|
|
1169
|
+
/** Есть `window`, но нет `document`; cookie ведёт нативный сетевой слой. */
|
|
1170
|
+
ReactNative: "react-native",
|
|
1171
|
+
Server: "server"
|
|
1172
|
+
});
|
|
1102
1173
|
function detectRuntime() {
|
|
1103
1174
|
const nav = globalThis.navigator;
|
|
1104
|
-
if (nav?.product === "ReactNative") return
|
|
1105
|
-
if (typeof document !== "undefined") return
|
|
1106
|
-
return
|
|
1175
|
+
if (nav?.product === "ReactNative") return DetectedRuntime.ReactNative;
|
|
1176
|
+
if (typeof document !== "undefined") return DetectedRuntime.Browser;
|
|
1177
|
+
return DetectedRuntime.Server;
|
|
1107
1178
|
}
|
|
1108
1179
|
function shouldUseCookieJar(mode) {
|
|
1109
|
-
if (mode ===
|
|
1110
|
-
if (mode ===
|
|
1111
|
-
return detectRuntime() ===
|
|
1180
|
+
if (mode === RuntimeMode.Browser) return false;
|
|
1181
|
+
if (mode === RuntimeMode.Server) return true;
|
|
1182
|
+
return detectRuntime() === DetectedRuntime.Server;
|
|
1112
1183
|
}
|
|
1113
1184
|
function shouldSendCredentials(mode) {
|
|
1114
|
-
if (mode ===
|
|
1115
|
-
if (mode ===
|
|
1116
|
-
return detectRuntime() ===
|
|
1185
|
+
if (mode === RuntimeMode.Browser) return true;
|
|
1186
|
+
if (mode === RuntimeMode.Server) return false;
|
|
1187
|
+
return detectRuntime() === DetectedRuntime.Browser;
|
|
1117
1188
|
}
|
|
1118
1189
|
function resolveFetch(custom) {
|
|
1119
1190
|
if (custom) return custom;
|
|
@@ -1124,6 +1195,12 @@ function resolveFetch(custom) {
|
|
|
1124
1195
|
"\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
1196
|
);
|
|
1126
1197
|
}
|
|
1198
|
+
function isBlob(value) {
|
|
1199
|
+
return typeof Blob !== "undefined" && value instanceof Blob;
|
|
1200
|
+
}
|
|
1201
|
+
function isFile(value) {
|
|
1202
|
+
return typeof File !== "undefined" && value instanceof File;
|
|
1203
|
+
}
|
|
1127
1204
|
function supportsStreamingBody() {
|
|
1128
1205
|
return typeof ReadableStream !== "undefined" && typeof TextDecoder !== "undefined";
|
|
1129
1206
|
}
|
|
@@ -1198,11 +1275,19 @@ var AuthManager = class {
|
|
|
1198
1275
|
/**
|
|
1199
1276
|
* Есть ли признак живой refresh-сессии.
|
|
1200
1277
|
*
|
|
1201
|
-
*
|
|
1202
|
-
*
|
|
1278
|
+
* Рядом с refresh-токеном сервер ставит незакрытую cookie `is_auth` — по ней видно,
|
|
1279
|
+
* что продлевать сессию вообще есть смысл, и API не дёргается у анонимов.
|
|
1203
1280
|
* В браузере cookie ведёт сама среда, поэтому там ответ всегда `true`.
|
|
1281
|
+
*
|
|
1282
|
+
* Асинхронный, потому что признак может лежать в {@link TokenStorage}: до чтения оттуда
|
|
1283
|
+
* ответ был бы `false` даже при полностью рабочей сохранённой сессии.
|
|
1204
1284
|
*/
|
|
1205
|
-
hasRefreshSession() {
|
|
1285
|
+
async hasRefreshSession() {
|
|
1286
|
+
await this.#loadSession();
|
|
1287
|
+
return this.#hasRefreshSession();
|
|
1288
|
+
}
|
|
1289
|
+
/** То же самое, но без чтения хранилища — для вызовов, где сессия уже загружена. */
|
|
1290
|
+
#hasRefreshSession() {
|
|
1206
1291
|
if (!this.#config.useCookieJar) return true;
|
|
1207
1292
|
if (this.#jar.has(AUTH_FLAG_COOKIE)) return true;
|
|
1208
1293
|
return Boolean(this.#session?.refreshToken);
|
|
@@ -1388,7 +1473,7 @@ var AuthManager = class {
|
|
|
1388
1473
|
}
|
|
1389
1474
|
async #performRefresh() {
|
|
1390
1475
|
await this.#loadSession();
|
|
1391
|
-
if (!this
|
|
1476
|
+
if (!this.#hasRefreshSession()) {
|
|
1392
1477
|
return this.#reloginOrNull();
|
|
1393
1478
|
}
|
|
1394
1479
|
try {
|
|
@@ -1399,7 +1484,10 @@ var AuthManager = class {
|
|
|
1399
1484
|
// #seedRefreshCookie. По той же причине не нужен и устаревший Bearer.
|
|
1400
1485
|
skipAuth: true,
|
|
1401
1486
|
// Без этого 401 на самом обновлении вызвал бы новое обновление — и так по кругу.
|
|
1402
|
-
skipAuthRefresh: true
|
|
1487
|
+
skipAuthRefresh: true,
|
|
1488
|
+
// Обновление почти всегда запускается изнутри запроса, который занимает место
|
|
1489
|
+
// в очереди и ждёт его результата. Встать в ту же очередь — значит зависнуть.
|
|
1490
|
+
skipQueue: true
|
|
1403
1491
|
});
|
|
1404
1492
|
const accessToken = readAccessToken(payload);
|
|
1405
1493
|
if (!accessToken) return this.#reloginOrNull();
|
|
@@ -1476,7 +1564,10 @@ var AuthManager = class {
|
|
|
1476
1564
|
path: AUTH_PATHS.signIn,
|
|
1477
1565
|
body: { email: credentials.email, password: credentials.password, turnstileToken },
|
|
1478
1566
|
skipAuth: true,
|
|
1479
|
-
skipAuthRefresh: true
|
|
1567
|
+
skipAuthRefresh: true,
|
|
1568
|
+
// Отложенный вход происходит при сборке заголовков уже начатого запроса — тот держит
|
|
1569
|
+
// место в очереди и ждёт токена. См. `skipQueue` в RawRequestOptions.
|
|
1570
|
+
skipQueue: true
|
|
1480
1571
|
});
|
|
1481
1572
|
const accessToken = readAccessToken(payload);
|
|
1482
1573
|
if (!accessToken) {
|
|
@@ -1605,7 +1696,7 @@ function normalizeBaseUrl(baseUrl) {
|
|
|
1605
1696
|
// src/core/config.ts
|
|
1606
1697
|
var DEFAULT_BASE_URL = "https://xn--d1ah4a.com";
|
|
1607
1698
|
var DEFAULT_TIMEOUT = 3e4;
|
|
1608
|
-
var LIBRARY_VERSION = "0.0.
|
|
1699
|
+
var LIBRARY_VERSION = "0.0.4";
|
|
1609
1700
|
var DEFAULT_USER_AGENT = `Mozilla/5.0 (compatible; itd-api/${LIBRARY_VERSION}; +https://github.com/KiowDev/itd-api)`;
|
|
1610
1701
|
var DEFAULT_RATE_LIMIT_DELAYS = Object.freeze([1e3, 5e3, 3e4, 6e4, 9e4]);
|
|
1611
1702
|
function requirePositive(value, name) {
|
|
@@ -1718,9 +1809,11 @@ function validateAuth(auth) {
|
|
|
1718
1809
|
);
|
|
1719
1810
|
}
|
|
1720
1811
|
function resolveConfig(options = {}) {
|
|
1721
|
-
const mode = options.mode ??
|
|
1722
|
-
if (mode
|
|
1723
|
-
throw new ItdConfigError(
|
|
1812
|
+
const mode = options.mode ?? RuntimeMode.Auto;
|
|
1813
|
+
if (!Object.values(RuntimeMode).includes(mode)) {
|
|
1814
|
+
throw new ItdConfigError(
|
|
1815
|
+
`mode \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043E\u0434\u043D\u0438\u043C \u0438\u0437 ${Object.values(RuntimeMode).join(", ")}, \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043E: ${mode}`
|
|
1816
|
+
);
|
|
1724
1817
|
}
|
|
1725
1818
|
const timeout = requirePositive(options.timeout ?? DEFAULT_TIMEOUT, "timeout");
|
|
1726
1819
|
if (options.deviceId !== void 0 && (typeof options.deviceId !== "string" || options.deviceId.trim() === "")) {
|
|
@@ -1781,7 +1874,7 @@ function redactHeaders(headers) {
|
|
|
1781
1874
|
function redactBody(body) {
|
|
1782
1875
|
if (body === null || body === void 0) return body;
|
|
1783
1876
|
if (typeof FormData !== "undefined" && body instanceof FormData) return "[FormData]";
|
|
1784
|
-
if (
|
|
1877
|
+
if (isBlob(body)) return "[Blob]";
|
|
1785
1878
|
if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) return "[binary]";
|
|
1786
1879
|
if (Array.isArray(body)) return body.map(redactBody);
|
|
1787
1880
|
if (typeof body === "object") {
|
|
@@ -1794,13 +1887,47 @@ function redactBody(body) {
|
|
|
1794
1887
|
return body;
|
|
1795
1888
|
}
|
|
1796
1889
|
|
|
1797
|
-
// src/core/
|
|
1890
|
+
// src/core/unwrap.ts
|
|
1891
|
+
function unwrapData(body) {
|
|
1892
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) return body;
|
|
1893
|
+
const keys = Object.keys(body);
|
|
1894
|
+
if (keys.length !== 1 || keys[0] !== "data") return body;
|
|
1895
|
+
return body.data;
|
|
1896
|
+
}
|
|
1798
1897
|
function isRecord(value) {
|
|
1799
1898
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1800
1899
|
}
|
|
1801
1900
|
function asString(value) {
|
|
1802
1901
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1803
1902
|
}
|
|
1903
|
+
function pickArray(source, field) {
|
|
1904
|
+
if (typeof source !== "object" || source === null) return [];
|
|
1905
|
+
const value = source[field];
|
|
1906
|
+
return Array.isArray(value) ? value : [];
|
|
1907
|
+
}
|
|
1908
|
+
function pickObject(source, field) {
|
|
1909
|
+
if (typeof source !== "object" || source === null) return void 0;
|
|
1910
|
+
const value = source[field];
|
|
1911
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
1912
|
+
return value;
|
|
1913
|
+
}
|
|
1914
|
+
function pickBoolean(source, field, fallback = false) {
|
|
1915
|
+
if (typeof source !== "object" || source === null) return fallback;
|
|
1916
|
+
const value = source[field];
|
|
1917
|
+
return typeof value === "boolean" ? value : fallback;
|
|
1918
|
+
}
|
|
1919
|
+
function pickNumber(source, field, fallback) {
|
|
1920
|
+
if (typeof source !== "object" || source === null) return fallback;
|
|
1921
|
+
const value = source[field];
|
|
1922
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
1923
|
+
}
|
|
1924
|
+
function pickString(source, field) {
|
|
1925
|
+
if (typeof source !== "object" || source === null) return void 0;
|
|
1926
|
+
const value = source[field];
|
|
1927
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
// src/core/error-factory.ts
|
|
1804
1931
|
function collectFieldErrors(source) {
|
|
1805
1932
|
const result = {};
|
|
1806
1933
|
const errors = source.errors;
|
|
@@ -1960,40 +2087,6 @@ function createApiError(context) {
|
|
|
1960
2087
|
return new Ctor(init);
|
|
1961
2088
|
}
|
|
1962
2089
|
|
|
1963
|
-
// src/core/unwrap.ts
|
|
1964
|
-
function unwrapData(body) {
|
|
1965
|
-
if (typeof body !== "object" || body === null || Array.isArray(body)) return body;
|
|
1966
|
-
const keys = Object.keys(body);
|
|
1967
|
-
if (keys.length !== 1 || keys[0] !== "data") return body;
|
|
1968
|
-
return body.data;
|
|
1969
|
-
}
|
|
1970
|
-
function pickArray(source, field) {
|
|
1971
|
-
if (typeof source !== "object" || source === null) return [];
|
|
1972
|
-
const value = source[field];
|
|
1973
|
-
return Array.isArray(value) ? value : [];
|
|
1974
|
-
}
|
|
1975
|
-
function pickObject(source, field) {
|
|
1976
|
-
if (typeof source !== "object" || source === null) return void 0;
|
|
1977
|
-
const value = source[field];
|
|
1978
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
1979
|
-
return value;
|
|
1980
|
-
}
|
|
1981
|
-
function pickBoolean(source, field, fallback = false) {
|
|
1982
|
-
if (typeof source !== "object" || source === null) return fallback;
|
|
1983
|
-
const value = source[field];
|
|
1984
|
-
return typeof value === "boolean" ? value : fallback;
|
|
1985
|
-
}
|
|
1986
|
-
function pickNumber(source, field, fallback) {
|
|
1987
|
-
if (typeof source !== "object" || source === null) return fallback;
|
|
1988
|
-
const value = source[field];
|
|
1989
|
-
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
1990
|
-
}
|
|
1991
|
-
function pickString(source, field) {
|
|
1992
|
-
if (typeof source !== "object" || source === null) return void 0;
|
|
1993
|
-
const value = source[field];
|
|
1994
|
-
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1995
|
-
}
|
|
1996
|
-
|
|
1997
2090
|
// src/core/http.ts
|
|
1998
2091
|
function sleep(ms) {
|
|
1999
2092
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -2009,7 +2102,7 @@ function setHeader(headers, name, value) {
|
|
|
2009
2102
|
}
|
|
2010
2103
|
function isRawBody(body) {
|
|
2011
2104
|
if (typeof body !== "object" || body === null) return typeof body === "string";
|
|
2012
|
-
return typeof FormData !== "undefined" && body instanceof FormData ||
|
|
2105
|
+
return typeof FormData !== "undefined" && body instanceof FormData || isBlob(body) || typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams || typeof ReadableStream !== "undefined" && body instanceof ReadableStream || body instanceof ArrayBuffer || ArrayBuffer.isView(body);
|
|
2013
2106
|
}
|
|
2014
2107
|
async function readBody(response) {
|
|
2015
2108
|
if (response.status === 204 || response.status === 205) return void 0;
|
|
@@ -2079,7 +2172,8 @@ var HttpClient = class {
|
|
|
2079
2172
|
*/
|
|
2080
2173
|
async request(options) {
|
|
2081
2174
|
const task = () => this.#withRetries(options);
|
|
2082
|
-
|
|
2175
|
+
if (!this.#collaborators.schedule || options.skipQueue) return task();
|
|
2176
|
+
return this.#collaborators.schedule(task);
|
|
2083
2177
|
}
|
|
2084
2178
|
async #withRetries(options) {
|
|
2085
2179
|
const method = options.method.toUpperCase();
|
|
@@ -2348,21 +2442,15 @@ function isKnownNotificationType(type) {
|
|
|
2348
2442
|
}
|
|
2349
2443
|
|
|
2350
2444
|
// src/notifications/normalize.ts
|
|
2351
|
-
function isRecord2(value) {
|
|
2352
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2353
|
-
}
|
|
2354
|
-
function asString2(value) {
|
|
2355
|
-
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
2356
|
-
}
|
|
2357
2445
|
function asActor(value) {
|
|
2358
|
-
if (!
|
|
2359
|
-
const id =
|
|
2446
|
+
if (!isRecord(value)) return void 0;
|
|
2447
|
+
const id = asString(value.id);
|
|
2360
2448
|
if (!id) return void 0;
|
|
2361
2449
|
return {
|
|
2362
2450
|
id,
|
|
2363
|
-
username:
|
|
2364
|
-
displayName:
|
|
2365
|
-
avatar:
|
|
2451
|
+
username: asString(value.username) ?? "",
|
|
2452
|
+
displayName: asString(value.displayName) ?? "",
|
|
2453
|
+
avatar: asString(value.avatar) ?? "",
|
|
2366
2454
|
...typeof value.isFollowing === "boolean" ? { isFollowing: value.isFollowing } : {},
|
|
2367
2455
|
...typeof value.isFollowedBy === "boolean" ? { isFollowedBy: value.isFollowedBy } : {}
|
|
2368
2456
|
};
|
|
@@ -2375,33 +2463,34 @@ function readActors(source) {
|
|
|
2375
2463
|
return single ? [single] : [];
|
|
2376
2464
|
}
|
|
2377
2465
|
function normalizeNotification(input) {
|
|
2378
|
-
const source =
|
|
2379
|
-
const payload =
|
|
2380
|
-
const rawType =
|
|
2381
|
-
const createdAt =
|
|
2382
|
-
const readAt =
|
|
2466
|
+
const source = isRecord(input) ? input : {};
|
|
2467
|
+
const payload = isRecord(source.payload) ? source.payload : source;
|
|
2468
|
+
const rawType = asString(payload.type) ?? asString(source.type) ?? "";
|
|
2469
|
+
const createdAt = asString(payload.createdAt) ?? asString(source.createdAt) ?? "";
|
|
2470
|
+
const readAt = asString(payload.readAt) ?? asString(source.readAt);
|
|
2383
2471
|
const isRead = typeof payload.isRead === "boolean" ? payload.isRead : typeof payload.read === "boolean" ? payload.read : Boolean(readAt);
|
|
2384
|
-
const subjectId =
|
|
2385
|
-
const targetId =
|
|
2472
|
+
const subjectId = asString(payload.subjectId);
|
|
2473
|
+
const targetId = asString(payload.targetId);
|
|
2386
2474
|
const subjectIsComment = payload.subjectType === "comment";
|
|
2475
|
+
const clickUrl = asString(payload.clickUrl);
|
|
2387
2476
|
return {
|
|
2388
|
-
id:
|
|
2477
|
+
id: asString(payload.id) ?? asString(source.id) ?? "",
|
|
2389
2478
|
type: canonicalNotificationType(rawType),
|
|
2390
2479
|
rawType,
|
|
2391
|
-
entityId:
|
|
2392
|
-
parentEntityId:
|
|
2480
|
+
entityId: asString(payload.entityId) ?? (subjectIsComment ? subjectId ?? targetId : targetId) ?? null,
|
|
2481
|
+
parentEntityId: asString(payload.parentEntityId) ?? (subjectIsComment ? targetId ?? null : null),
|
|
2393
2482
|
isRead,
|
|
2394
2483
|
actors: readActors(payload),
|
|
2395
2484
|
count: typeof payload.count === "number" && payload.count > 0 ? payload.count : 1,
|
|
2396
|
-
preview:
|
|
2397
|
-
...
|
|
2485
|
+
preview: asString(payload.entityPreview) ?? asString(payload.preview) ?? null,
|
|
2486
|
+
...clickUrl ? { clickUrl } : {},
|
|
2398
2487
|
createdAt,
|
|
2399
|
-
updatedAt:
|
|
2488
|
+
updatedAt: asString(payload.updatedAt) ?? readAt ?? createdAt,
|
|
2400
2489
|
raw: input
|
|
2401
2490
|
};
|
|
2402
2491
|
}
|
|
2403
2492
|
function readNotificationEvent(data) {
|
|
2404
|
-
const source =
|
|
2493
|
+
const source = isRecord(data) ? data : {};
|
|
2405
2494
|
return {
|
|
2406
2495
|
notification: normalizeNotification(data),
|
|
2407
2496
|
unreadCount: typeof source.unreadCount === "number" ? source.unreadCount : void 0,
|
|
@@ -2409,8 +2498,8 @@ function readNotificationEvent(data) {
|
|
|
2409
2498
|
};
|
|
2410
2499
|
}
|
|
2411
2500
|
function readUnreadCountEvent(data) {
|
|
2412
|
-
if (!
|
|
2413
|
-
const payload =
|
|
2501
|
+
if (!isRecord(data)) return void 0;
|
|
2502
|
+
const payload = isRecord(data.payload) ? data.payload : void 0;
|
|
2414
2503
|
if (!payload) return void 0;
|
|
2415
2504
|
return typeof payload.count === "number" ? payload.count : void 0;
|
|
2416
2505
|
}
|
|
@@ -2489,6 +2578,7 @@ var PollTransport = class {
|
|
|
2489
2578
|
}
|
|
2490
2579
|
/** Ждёт следующего опроса, прерываясь при отмене. */
|
|
2491
2580
|
#wait(signal) {
|
|
2581
|
+
if (signal.aborted) return Promise.resolve();
|
|
2492
2582
|
return new Promise((resolve) => {
|
|
2493
2583
|
const timer = setTimeout(finish, this.#interval);
|
|
2494
2584
|
function finish() {
|
|
@@ -2761,6 +2851,14 @@ var SseTransport = class {
|
|
|
2761
2851
|
};
|
|
2762
2852
|
|
|
2763
2853
|
// src/realtime/stream.ts
|
|
2854
|
+
var RealtimeTransportKind = Object.freeze({
|
|
2855
|
+
/** Поток событий, если среда умеет читать тело по частям, иначе опрос. */
|
|
2856
|
+
Auto: "auto",
|
|
2857
|
+
/** Поток `text/event-stream`. */
|
|
2858
|
+
Sse: "sse",
|
|
2859
|
+
/** Периодический опрос REST. */
|
|
2860
|
+
Poll: "poll"
|
|
2861
|
+
});
|
|
2764
2862
|
var ItdRealtime = class {
|
|
2765
2863
|
#deps;
|
|
2766
2864
|
#options;
|
|
@@ -2768,6 +2866,15 @@ var ItdRealtime = class {
|
|
|
2768
2866
|
#transport;
|
|
2769
2867
|
#maxAttempts;
|
|
2770
2868
|
#controller;
|
|
2869
|
+
/**
|
|
2870
|
+
* Хочет ли вызывающий код, чтобы соединение было живо.
|
|
2871
|
+
*
|
|
2872
|
+
* Отдельно от `#controller`, потому что тот появляется только после `await` внутри
|
|
2873
|
+
* {@link connect}. Без этого флага два вызова подряд проскочили бы проверку оба
|
|
2874
|
+
* и подняли два соединения, а `disconnect()` во время ожидания счётчика не был бы
|
|
2875
|
+
* замечен и соединение поднялось бы уже после отмены.
|
|
2876
|
+
*/
|
|
2877
|
+
#wanted = false;
|
|
2771
2878
|
#status = RealtimeStatus.Disconnected;
|
|
2772
2879
|
#attempt = 0;
|
|
2773
2880
|
#timer;
|
|
@@ -2803,7 +2910,8 @@ var ItdRealtime = class {
|
|
|
2803
2910
|
* Возвращает управление сразу после запуска: соединение живёт в фоне.
|
|
2804
2911
|
*/
|
|
2805
2912
|
async connect() {
|
|
2806
|
-
if (this.#
|
|
2913
|
+
if (this.#wanted) return;
|
|
2914
|
+
this.#wanted = true;
|
|
2807
2915
|
this.#attachEnvironmentListeners();
|
|
2808
2916
|
if (this.#options.syncCount !== false) {
|
|
2809
2917
|
try {
|
|
@@ -2812,10 +2920,11 @@ var ItdRealtime = class {
|
|
|
2812
2920
|
this.#deps.logger?.debug("\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C \u0447\u0438\u0441\u043B\u043E \u043D\u0435\u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u043D\u044B\u0445", error);
|
|
2813
2921
|
}
|
|
2814
2922
|
}
|
|
2815
|
-
this.#run();
|
|
2923
|
+
if (this.#wanted) this.#run();
|
|
2816
2924
|
}
|
|
2817
2925
|
/** Закрывает соединение и отменяет запланированные попытки. */
|
|
2818
2926
|
disconnect() {
|
|
2927
|
+
this.#wanted = false;
|
|
2819
2928
|
if (this.#timer !== void 0) {
|
|
2820
2929
|
clearTimeout(this.#timer);
|
|
2821
2930
|
this.#timer = void 0;
|
|
@@ -2832,9 +2941,9 @@ var ItdRealtime = class {
|
|
|
2832
2941
|
this.#emitter.removeAllListeners();
|
|
2833
2942
|
}
|
|
2834
2943
|
#createTransport() {
|
|
2835
|
-
const kind = this.#options.transport ??
|
|
2944
|
+
const kind = this.#options.transport ?? RealtimeTransportKind.Auto;
|
|
2836
2945
|
if (typeof kind === "object") return kind;
|
|
2837
|
-
if (kind ===
|
|
2946
|
+
if (kind === RealtimeTransportKind.Poll || kind === RealtimeTransportKind.Auto && !supportsStreamingBody()) {
|
|
2838
2947
|
return new PollTransport({
|
|
2839
2948
|
...this.#options.pollInterval !== void 0 ? { interval: this.#options.pollInterval } : {}
|
|
2840
2949
|
});
|
|
@@ -2845,6 +2954,7 @@ var ItdRealtime = class {
|
|
|
2845
2954
|
}
|
|
2846
2955
|
/** Запускает попытку подключения; повторы планирует сам. */
|
|
2847
2956
|
#run() {
|
|
2957
|
+
this.#controller?.abort();
|
|
2848
2958
|
const controller = new AbortController();
|
|
2849
2959
|
this.#controller = controller;
|
|
2850
2960
|
this.#setStatus(RealtimeStatus.Connecting);
|
|
@@ -2874,8 +2984,7 @@ var ItdRealtime = class {
|
|
|
2874
2984
|
#handleEvent(name, data) {
|
|
2875
2985
|
this.#emitter.emit("message", { name, data });
|
|
2876
2986
|
if (name === "connected") {
|
|
2877
|
-
|
|
2878
|
-
this.#emitter.emit("ready", { userId });
|
|
2987
|
+
this.#emitter.emit("ready", { userId: pickString(data, "userId") });
|
|
2879
2988
|
return;
|
|
2880
2989
|
}
|
|
2881
2990
|
if (name === "notification") {
|
|
@@ -2963,13 +3072,22 @@ var ItdRealtime = class {
|
|
|
2963
3072
|
};
|
|
2964
3073
|
|
|
2965
3074
|
// src/core/pagination.ts
|
|
3075
|
+
var PaginationMode = Object.freeze({
|
|
3076
|
+
/** Следующая страница запрашивается непрозрачным курсором. */
|
|
3077
|
+
Cursor: "cursor",
|
|
3078
|
+
/** Следующая страница запрашивается номером. */
|
|
3079
|
+
Page: "page",
|
|
3080
|
+
/** Следующая страница запрашивается смещением от начала списка. */
|
|
3081
|
+
Offset: "offset"
|
|
3082
|
+
});
|
|
2966
3083
|
function readItems(body, fields) {
|
|
2967
3084
|
if (Array.isArray(body)) return body;
|
|
2968
3085
|
for (const field of fields) {
|
|
2969
3086
|
const items = pickArray(body, field);
|
|
2970
3087
|
if (items.length > 0) return items;
|
|
2971
3088
|
}
|
|
2972
|
-
|
|
3089
|
+
const primary = fields[0];
|
|
3090
|
+
return primary === void 0 ? [] : pickArray(body, primary);
|
|
2973
3091
|
}
|
|
2974
3092
|
function readCursor(body) {
|
|
2975
3093
|
const pagination = pickObject(body, "pagination");
|
|
@@ -3028,12 +3146,13 @@ function readOffsetPage(body, field, offset) {
|
|
|
3028
3146
|
var Paginator = class {
|
|
3029
3147
|
#options;
|
|
3030
3148
|
#maxPages;
|
|
3031
|
-
#state
|
|
3149
|
+
#state;
|
|
3032
3150
|
#finished = false;
|
|
3033
3151
|
#pagesLoaded = 0;
|
|
3034
3152
|
constructor(options) {
|
|
3035
3153
|
this.#options = options;
|
|
3036
3154
|
this.#maxPages = options.maxPages ?? 1e3;
|
|
3155
|
+
this.#state = options.start ?? {};
|
|
3037
3156
|
}
|
|
3038
3157
|
/**
|
|
3039
3158
|
* Загружает следующую страницу.
|
|
@@ -3099,7 +3218,7 @@ var Paginator = class {
|
|
|
3099
3218
|
this.#finished = true;
|
|
3100
3219
|
return previous;
|
|
3101
3220
|
}
|
|
3102
|
-
if (this.#options.mode ===
|
|
3221
|
+
if (this.#options.mode === PaginationMode.Cursor) {
|
|
3103
3222
|
const cursor = page.nextCursor ?? void 0;
|
|
3104
3223
|
if (!cursor || cursor === previous.cursor) {
|
|
3105
3224
|
this.#finished = true;
|
|
@@ -3107,7 +3226,7 @@ var Paginator = class {
|
|
|
3107
3226
|
}
|
|
3108
3227
|
return { cursor };
|
|
3109
3228
|
}
|
|
3110
|
-
if (this.#options.mode ===
|
|
3229
|
+
if (this.#options.mode === PaginationMode.Page) {
|
|
3111
3230
|
return { page: (previous.page ?? 1) + 1 };
|
|
3112
3231
|
}
|
|
3113
3232
|
return { offset: page.nextOffset ?? (previous.offset ?? 0) + page.items.length };
|
|
@@ -3136,13 +3255,15 @@ var BaseResource = class {
|
|
|
3136
3255
|
*
|
|
3137
3256
|
* @param mode схема пагинации эндпоинта
|
|
3138
3257
|
* @param load загружает одну страницу для указанной позиции
|
|
3258
|
+
* @param options `maxPages` и `signal`, а также `start` — позиция, с которой продолжить
|
|
3139
3259
|
*/
|
|
3140
3260
|
paginate(mode, load, options) {
|
|
3141
3261
|
return new Paginator({
|
|
3142
3262
|
mode,
|
|
3143
3263
|
load,
|
|
3144
3264
|
...options?.maxPages !== void 0 ? { maxPages: options.maxPages } : {},
|
|
3145
|
-
...options?.signal !== void 0 ? { signal: options.signal } : {}
|
|
3265
|
+
...options?.signal !== void 0 ? { signal: options.signal } : {},
|
|
3266
|
+
...options?.start !== void 0 ? { start: options.start } : {}
|
|
3146
3267
|
});
|
|
3147
3268
|
}
|
|
3148
3269
|
};
|
|
@@ -3156,6 +3277,14 @@ function withPageState(query, state) {
|
|
|
3156
3277
|
}
|
|
3157
3278
|
|
|
3158
3279
|
// src/resources/auth.ts
|
|
3280
|
+
var OAuthProvider = Object.freeze({
|
|
3281
|
+
Yandex: "yandex",
|
|
3282
|
+
Google: "google"
|
|
3283
|
+
});
|
|
3284
|
+
var SignInStatus = Object.freeze({
|
|
3285
|
+
Authenticated: "authenticated",
|
|
3286
|
+
OtpRequired: "otp_required"
|
|
3287
|
+
});
|
|
3159
3288
|
var AuthResource = class extends BaseResource {
|
|
3160
3289
|
#auth;
|
|
3161
3290
|
constructor(http, deps) {
|
|
@@ -3204,9 +3333,9 @@ var AuthResource = class extends BaseResource {
|
|
|
3204
3333
|
const accessToken = pickString(body, "accessToken");
|
|
3205
3334
|
if (accessToken) {
|
|
3206
3335
|
await this.#auth.setAccessToken(accessToken);
|
|
3207
|
-
return { status:
|
|
3336
|
+
return { status: SignInStatus.Authenticated, accessToken };
|
|
3208
3337
|
}
|
|
3209
|
-
return { status:
|
|
3338
|
+
return { status: SignInStatus.OtpRequired, flowToken: pickString(body, "flowToken") };
|
|
3210
3339
|
}
|
|
3211
3340
|
/**
|
|
3212
3341
|
* Подтверждает вход кодом из письма.
|
|
@@ -3261,7 +3390,7 @@ var AuthResource = class extends BaseResource {
|
|
|
3261
3390
|
async signInWithOtp(input, options = {}) {
|
|
3262
3391
|
const { getOtp, ...credentials } = input;
|
|
3263
3392
|
const result = await this.signIn(credentials, options);
|
|
3264
|
-
if (result.status ===
|
|
3393
|
+
if (result.status === SignInStatus.Authenticated) return result.accessToken;
|
|
3265
3394
|
if (!result.flowToken) {
|
|
3266
3395
|
throw new ItdConfigError(
|
|
3267
3396
|
"\u0421\u0435\u0440\u0432\u0435\u0440 \u0437\u0430\u043F\u0440\u043E\u0441\u0438\u043B \u043A\u043E\u0434 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F, \u043D\u043E \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B flowToken \u2014 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u0432\u0445\u043E\u0434 \u043D\u0435\u0432\u043E\u0437\u043C\u043E\u0436\u043D\u043E"
|
|
@@ -3290,9 +3419,18 @@ var AuthResource = class extends BaseResource {
|
|
|
3290
3419
|
/**
|
|
3291
3420
|
* Есть ли признак живой сессии обновления.
|
|
3292
3421
|
*
|
|
3293
|
-
* Проверяет cookie `is_auth`, которую сервер ставит рядом с refresh
|
|
3294
|
-
* не дёргать API у неавторизованного
|
|
3295
|
-
* cookie ведёт сама среда, и прочитать её
|
|
3422
|
+
* Проверяет cookie `is_auth`, которую сервер ставит рядом с refresh-токеном, а также
|
|
3423
|
+
* refresh-токен, переданный строкой. Позволяет не дёргать API у неавторизованного
|
|
3424
|
+
* пользователя. В браузере всегда `true`: cookie ведёт сама среда, и прочитать её
|
|
3425
|
+
* из JS нельзя.
|
|
3426
|
+
*
|
|
3427
|
+
* Читает {@link TokenStorage}, поэтому результат верен и до первого запроса.
|
|
3428
|
+
*
|
|
3429
|
+
* @example
|
|
3430
|
+
* ```ts
|
|
3431
|
+
* if (await itd.auth.hasRefreshSession()) await itd.auth.refresh();
|
|
3432
|
+
* else redirectToLogin();
|
|
3433
|
+
* ```
|
|
3296
3434
|
*/
|
|
3297
3435
|
hasRefreshSession() {
|
|
3298
3436
|
return this.#auth.hasRefreshSession();
|
|
@@ -3467,7 +3605,7 @@ var CommentsResource = class extends BaseResource {
|
|
|
3467
3605
|
iterateReplies(commentId, params = {}) {
|
|
3468
3606
|
const path = `/api/comments/${encodePathSegment(commentId, "commentId")}/replies`;
|
|
3469
3607
|
return this.paginate(
|
|
3470
|
-
|
|
3608
|
+
PaginationMode.Page,
|
|
3471
3609
|
async (state) => {
|
|
3472
3610
|
const body = await this.http.request({
|
|
3473
3611
|
method: "GET",
|
|
@@ -3477,7 +3615,7 @@ var CommentsResource = class extends BaseResource {
|
|
|
3477
3615
|
});
|
|
3478
3616
|
return readPagedPage(body, "replies");
|
|
3479
3617
|
},
|
|
3480
|
-
params
|
|
3618
|
+
{ ...params, ...params.page !== void 0 ? { start: { page: params.page } } : {} }
|
|
3481
3619
|
);
|
|
3482
3620
|
}
|
|
3483
3621
|
/**
|
|
@@ -3558,7 +3696,11 @@ var IMAGE_MIME_TYPES = Object.freeze([
|
|
|
3558
3696
|
"image/heic",
|
|
3559
3697
|
"image/heif"
|
|
3560
3698
|
]);
|
|
3561
|
-
var VIDEO_MIME_TYPES = Object.freeze([
|
|
3699
|
+
var VIDEO_MIME_TYPES = Object.freeze([
|
|
3700
|
+
"video/mp4",
|
|
3701
|
+
"video/webm",
|
|
3702
|
+
"video/quicktime"
|
|
3703
|
+
]);
|
|
3562
3704
|
var AUDIO_MIME_TYPES = Object.freeze(["audio/ogg"]);
|
|
3563
3705
|
var ALLOWED_MIME_TYPES = Object.freeze([
|
|
3564
3706
|
...IMAGE_MIME_TYPES,
|
|
@@ -3637,7 +3779,7 @@ var FilesResource = class extends BaseResource {
|
|
|
3637
3779
|
* ```
|
|
3638
3780
|
*/
|
|
3639
3781
|
async upload(input, options = {}) {
|
|
3640
|
-
const prepared = await this
|
|
3782
|
+
const prepared = await this.#prepare(input, options);
|
|
3641
3783
|
const form = new FormData();
|
|
3642
3784
|
form.set("file", prepared.blob, prepared.filename);
|
|
3643
3785
|
return this.http.request({
|
|
@@ -3688,11 +3830,11 @@ var FilesResource = class extends BaseResource {
|
|
|
3688
3830
|
});
|
|
3689
3831
|
}
|
|
3690
3832
|
/** Приводит любой поддерживаемый вход к `Blob` с именем и проверенным типом. */
|
|
3691
|
-
async prepare(input, options) {
|
|
3833
|
+
async #prepare(input, options) {
|
|
3692
3834
|
const { data, filename, contentType } = await this.#normalize(input, options);
|
|
3693
|
-
const type = contentType ?? ((data
|
|
3835
|
+
const type = contentType ?? ((isBlob(data) ? data.type : void 0) || mimeFromFilename(filename));
|
|
3694
3836
|
if (options.validateMime !== false) assertAllowedMime(type || void 0, filename);
|
|
3695
|
-
const blob = data
|
|
3837
|
+
const blob = isBlob(data) && (!type || data.type === type) ? data : new Blob([data], { type: type ?? "" });
|
|
3696
3838
|
return { blob, filename };
|
|
3697
3839
|
}
|
|
3698
3840
|
async #normalize(input, options) {
|
|
@@ -3709,8 +3851,8 @@ var FilesResource = class extends BaseResource {
|
|
|
3709
3851
|
...options.contentType ? { contentType: options.contentType } : {}
|
|
3710
3852
|
};
|
|
3711
3853
|
}
|
|
3712
|
-
if (input instanceof ArrayBuffer || ArrayBuffer.isView(input) || input
|
|
3713
|
-
const fallbackName = input
|
|
3854
|
+
if (input instanceof ArrayBuffer || ArrayBuffer.isView(input) || isBlob(input)) {
|
|
3855
|
+
const fallbackName = isFile(input) ? input.name : options.filename ?? this.#nameFromMime(options.contentType);
|
|
3714
3856
|
return {
|
|
3715
3857
|
data: input,
|
|
3716
3858
|
filename: options.filename ?? fallbackName,
|
|
@@ -3776,7 +3918,7 @@ var HashtagsResource = class extends BaseResource {
|
|
|
3776
3918
|
iteratePosts(tag, params = {}) {
|
|
3777
3919
|
const path = `/api/hashtags/${encodePathSegment(tag, "tag")}/posts`;
|
|
3778
3920
|
return this.paginate(
|
|
3779
|
-
|
|
3921
|
+
PaginationMode.Cursor,
|
|
3780
3922
|
async (state) => {
|
|
3781
3923
|
const body = await this.http.request({
|
|
3782
3924
|
method: "GET",
|
|
@@ -3786,7 +3928,7 @@ var HashtagsResource = class extends BaseResource {
|
|
|
3786
3928
|
});
|
|
3787
3929
|
return readCursorPage(body, "posts");
|
|
3788
3930
|
},
|
|
3789
|
-
params
|
|
3931
|
+
{ ...params, ...params.cursor ? { start: { cursor: params.cursor } } : {} }
|
|
3790
3932
|
);
|
|
3791
3933
|
}
|
|
3792
3934
|
};
|
|
@@ -4017,8 +4159,11 @@ var NotificationsResource = class extends BaseResource {
|
|
|
4017
4159
|
* const next = await itd.notifications.list({ limit: 20, offset: page.nextOffset });
|
|
4018
4160
|
* ```
|
|
4019
4161
|
*/
|
|
4020
|
-
|
|
4021
|
-
|
|
4162
|
+
list(params = {}) {
|
|
4163
|
+
return this.#loadPage(params, params.offset ?? 0);
|
|
4164
|
+
}
|
|
4165
|
+
/** Общая загрузка страницы для {@link list} и {@link iterate}. */
|
|
4166
|
+
async #loadPage(params, offset) {
|
|
4022
4167
|
const body = await this.http.request({
|
|
4023
4168
|
method: "GET",
|
|
4024
4169
|
// Завершающий слэш обязателен: без него сервер отвечает ошибкой.
|
|
@@ -4041,19 +4186,9 @@ var NotificationsResource = class extends BaseResource {
|
|
|
4041
4186
|
*/
|
|
4042
4187
|
iterate(params = {}) {
|
|
4043
4188
|
return this.paginate(
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
const body = await this.http.request({
|
|
4048
|
-
method: "GET",
|
|
4049
|
-
path: "/api/notifications/",
|
|
4050
|
-
query: { limit: params.limit, offset },
|
|
4051
|
-
...this.requestOptions(params)
|
|
4052
|
-
});
|
|
4053
|
-
const page = readOffsetPage(body, "notifications", offset);
|
|
4054
|
-
return { ...page, items: page.items.map(normalizeNotification) };
|
|
4055
|
-
},
|
|
4056
|
-
params
|
|
4189
|
+
PaginationMode.Offset,
|
|
4190
|
+
(state) => this.#loadPage(params, state.offset ?? 0),
|
|
4191
|
+
{ ...params, ...params.offset !== void 0 ? { start: { offset: params.offset } } : {} }
|
|
4057
4192
|
);
|
|
4058
4193
|
}
|
|
4059
4194
|
/** Загружает число непрочитанных уведомлений. */
|
|
@@ -4185,7 +4320,7 @@ var PostsResource = class extends BaseResource {
|
|
|
4185
4320
|
*/
|
|
4186
4321
|
iterate(params = {}) {
|
|
4187
4322
|
return this.paginate(
|
|
4188
|
-
|
|
4323
|
+
PaginationMode.Cursor,
|
|
4189
4324
|
async (state) => {
|
|
4190
4325
|
const body = await this.http.request({
|
|
4191
4326
|
method: "GET",
|
|
@@ -4195,7 +4330,7 @@ var PostsResource = class extends BaseResource {
|
|
|
4195
4330
|
});
|
|
4196
4331
|
return readCursorPage(body, "posts");
|
|
4197
4332
|
},
|
|
4198
|
-
params
|
|
4333
|
+
{ ...params, ...params.cursor ? { start: { cursor: params.cursor } } : {} }
|
|
4199
4334
|
);
|
|
4200
4335
|
}
|
|
4201
4336
|
/**
|
|
@@ -4359,7 +4494,7 @@ var PostsResource = class extends BaseResource {
|
|
|
4359
4494
|
iterateByUser(user, params = {}) {
|
|
4360
4495
|
const path = `/api/posts/user/${encodePathSegment(user, "user")}`;
|
|
4361
4496
|
return this.paginate(
|
|
4362
|
-
|
|
4497
|
+
PaginationMode.Cursor,
|
|
4363
4498
|
async (state) => {
|
|
4364
4499
|
const body = await this.http.request({
|
|
4365
4500
|
method: "GET",
|
|
@@ -4372,7 +4507,7 @@ var PostsResource = class extends BaseResource {
|
|
|
4372
4507
|
});
|
|
4373
4508
|
return readCursorPage(body, "posts");
|
|
4374
4509
|
},
|
|
4375
|
-
params
|
|
4510
|
+
{ ...params, ...params.cursor ? { start: { cursor: params.cursor } } : {} }
|
|
4376
4511
|
);
|
|
4377
4512
|
}
|
|
4378
4513
|
/** Загружает страницу постов, которые пользователь отметил реакцией. */
|
|
@@ -4389,7 +4524,7 @@ var PostsResource = class extends BaseResource {
|
|
|
4389
4524
|
iterateLikedByUser(user, params = {}) {
|
|
4390
4525
|
const path = `/api/posts/user/${encodePathSegment(user, "user")}/liked`;
|
|
4391
4526
|
return this.paginate(
|
|
4392
|
-
|
|
4527
|
+
PaginationMode.Cursor,
|
|
4393
4528
|
async (state) => {
|
|
4394
4529
|
const body = await this.http.request({
|
|
4395
4530
|
method: "GET",
|
|
@@ -4399,7 +4534,7 @@ var PostsResource = class extends BaseResource {
|
|
|
4399
4534
|
});
|
|
4400
4535
|
return readCursorPage(body, "posts");
|
|
4401
4536
|
},
|
|
4402
|
-
params
|
|
4537
|
+
{ ...params, ...params.cursor ? { start: { cursor: params.cursor } } : {} }
|
|
4403
4538
|
);
|
|
4404
4539
|
}
|
|
4405
4540
|
/**
|
|
@@ -4421,7 +4556,7 @@ var PostsResource = class extends BaseResource {
|
|
|
4421
4556
|
iterateComments(postId, params = {}) {
|
|
4422
4557
|
const path = `/api/posts/${encodePathSegment(postId, "postId")}/comments`;
|
|
4423
4558
|
return this.paginate(
|
|
4424
|
-
|
|
4559
|
+
PaginationMode.Cursor,
|
|
4425
4560
|
async (state) => {
|
|
4426
4561
|
const body = await this.http.request({
|
|
4427
4562
|
method: "GET",
|
|
@@ -4431,7 +4566,7 @@ var PostsResource = class extends BaseResource {
|
|
|
4431
4566
|
});
|
|
4432
4567
|
return readFlatCursorPage(body, "comments");
|
|
4433
4568
|
},
|
|
4434
|
-
params
|
|
4569
|
+
{ ...params, ...params.cursor ? { start: { cursor: params.cursor } } : {} }
|
|
4435
4570
|
);
|
|
4436
4571
|
}
|
|
4437
4572
|
/**
|
|
@@ -4709,28 +4844,33 @@ var UsersResource = class extends BaseResource {
|
|
|
4709
4844
|
...this.requestOptions(options)
|
|
4710
4845
|
});
|
|
4711
4846
|
}
|
|
4712
|
-
|
|
4847
|
+
/**
|
|
4848
|
+
* Загружает одну страницу списка пользователей.
|
|
4849
|
+
*
|
|
4850
|
+
* Имена полей перечислены с запасом: списки подписчиков и заблокированных приходят
|
|
4851
|
+
* под `users`, но альтернативное имя ничего не стоит и спасает, если эндпоинт назовёт
|
|
4852
|
+
* список по-своему.
|
|
4853
|
+
*/
|
|
4854
|
+
async #loadUserPage(path, params, state) {
|
|
4713
4855
|
const body = await this.http.request({
|
|
4714
4856
|
method: "GET",
|
|
4715
4857
|
path,
|
|
4716
|
-
query: { limit: params.limit
|
|
4858
|
+
query: withPageState({ limit: params.limit }, state),
|
|
4717
4859
|
...this.requestOptions(params)
|
|
4718
4860
|
});
|
|
4719
|
-
return readPagedPage(body, "users");
|
|
4861
|
+
return readPagedPage(body, "users", "followers", "following", "blocked");
|
|
4862
|
+
}
|
|
4863
|
+
#userPage(path, params) {
|
|
4864
|
+
return this.#loadUserPage(path, params, {
|
|
4865
|
+
...params.page !== void 0 ? { page: params.page } : {}
|
|
4866
|
+
});
|
|
4720
4867
|
}
|
|
4721
4868
|
#userPaginator(path, params) {
|
|
4722
4869
|
return this.paginate(
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
path,
|
|
4728
|
-
query: withPageState({ limit: params.limit }, state),
|
|
4729
|
-
...this.requestOptions(params)
|
|
4730
|
-
});
|
|
4731
|
-
return readPagedPage(body, "users");
|
|
4732
|
-
},
|
|
4733
|
-
params
|
|
4870
|
+
PaginationMode.Page,
|
|
4871
|
+
(state) => this.#loadUserPage(path, params, state),
|
|
4872
|
+
// Без `start` перебор начинался бы с первой страницы, молча игнорируя `page`.
|
|
4873
|
+
{ ...params, ...params.page !== void 0 ? { start: { page: params.page } } : {} }
|
|
4734
4874
|
);
|
|
4735
4875
|
}
|
|
4736
4876
|
};
|
|
@@ -5049,6 +5189,6 @@ function toDate(value) {
|
|
|
5049
5189
|
return Number.isFinite(date.getTime()) ? date : null;
|
|
5050
5190
|
}
|
|
5051
5191
|
|
|
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-
|
|
5054
|
-
//# sourceMappingURL=chunk-
|
|
5192
|
+
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, DetectedRuntime, FeedTab, IMAGE_MIME_TYPES, ItdAbortError, ItdApiError, ItdApiErrorKind, ItdAuthError, ItdClient, ItdConfigError, ItdConflictError, ItdError, ItdErrorCode, ItdErrorKind, ItdForbiddenError, ItdNetworkError, ItdNotFoundError, ItdPhoneVerificationError, ItdRateLimitError, ItdRealtime, ItdServerError, ItdTimeoutError, ItdValidationError, LIBRARY_VERSION, LikesVisibility, LocalStorageTokenStorage, MAX_RECONNECT_ATTEMPTS, MemoryTokenStorage, NOTIFICATION_TYPE_ALIASES, NotificationType, OAuthProvider, PaginationMode, Paginator, RECONNECT_BACKOFF, RECONNECT_JITTER, REFRESH_COOKIE, REFRESH_COOKIE_PATH, RealtimeStatus, RealtimeTransportKind, ReportReason, ReportTargetType, RuntimeMode, STREAM_PATH, SignInStatus, TURNSTILE_SITE_KEY, UnauthorizedStreamError, VIDEO_MIME_TYPES, WallAccess, canonicalNotificationType, comment, createClient, createTokenStorage, formatNotificationText, isBuilder, isItdApiError, isItdAuthError, isItdConflictError, isItdError, isItdForbiddenError, isItdNotFoundError, isItdPhoneVerificationError, isItdRateLimitError, isItdServerError, isItdValidationError, isKnownNotificationType, isMyProfile, normalizeNotification, poll, post, readNotificationEvent, readUnreadCountEvent, report, resolveNotificationUrl, toDate };
|
|
5193
|
+
//# sourceMappingURL=chunk-JV75JWOX.js.map
|
|
5194
|
+
//# sourceMappingURL=chunk-JV75JWOX.js.map
|