itd-api 0.7.0 → 0.7.2
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 +2 -2
- package/dist/index.cjs +540 -9806
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +280 -4981
- package/dist/index.d.ts +280 -4981
- package/dist/index.js +407 -9673
- package/dist/index.js.map +1 -1
- package/dist/{node.cjs → node/index.cjs} +16 -14
- package/dist/node/index.cjs.map +1 -0
- package/dist/{node.d.cts → node/index.d.cts} +4 -3
- package/dist/{node.d.ts → node/index.d.ts} +4 -3
- package/dist/{node.js → node/index.js} +5 -3
- package/dist/node/index.js.map +1 -0
- package/dist/realtime/index.cjs +165 -0
- package/dist/realtime/index.cjs.map +1 -0
- package/dist/realtime/index.d.cts +51 -0
- package/dist/realtime/index.d.ts +51 -0
- package/dist/realtime/index.js +120 -0
- package/dist/realtime/index.js.map +1 -0
- package/dist/rest/index.cjs +398 -0
- package/dist/rest/index.cjs.map +1 -0
- package/dist/rest/index.d.cts +146 -0
- package/dist/rest/index.d.ts +146 -0
- package/dist/rest/index.js +302 -0
- package/dist/rest/index.js.map +1 -0
- package/dist/shared/auth-provider-CG8oCQ9F.cjs +108 -0
- package/dist/shared/auth-provider-CG8oCQ9F.cjs.map +1 -0
- package/dist/shared/auth-provider-mYqxsSVa.js +91 -0
- package/dist/shared/auth-provider-mYqxsSVa.js.map +1 -0
- package/dist/shared/contracts-BoT7msmq.d.cts +84 -0
- package/dist/shared/contracts-BoT7msmq.d.ts +84 -0
- package/dist/{multi-storage-Bf84xiO8.cjs → shared/cookies-DZwFq6kr.cjs} +98 -429
- package/dist/shared/cookies-DZwFq6kr.cjs.map +1 -0
- package/dist/{multi-storage-BUZaLAPO.js → shared/cookies-tX2sNwxb.js} +99 -352
- package/dist/shared/cookies-tX2sNwxb.js.map +1 -0
- package/dist/{storage-IHdXw52v.js → shared/errors-Bhrd2fJd.js} +2 -229
- package/dist/shared/errors-Bhrd2fJd.js.map +1 -0
- package/dist/{storage-DnzZPS_9.cjs → shared/errors-DfU8M5eS.cjs} +1 -288
- package/dist/shared/errors-DfU8M5eS.cjs.map +1 -0
- package/dist/shared/multi-storage--yTEqiod.cjs +150 -0
- package/dist/shared/multi-storage--yTEqiod.cjs.map +1 -0
- package/dist/shared/multi-storage-CjAPB5Kq.d.cts +72 -0
- package/dist/shared/multi-storage-CkvTUC5m.js +121 -0
- package/dist/shared/multi-storage-CkvTUC5m.js.map +1 -0
- package/dist/shared/multi-storage-DccjD7Ww.d.ts +72 -0
- package/dist/shared/options-Dg5N3r1V.cjs +189 -0
- package/dist/shared/options-Dg5N3r1V.cjs.map +1 -0
- package/dist/shared/options-DtATYdLr.js +142 -0
- package/dist/shared/options-DtATYdLr.js.map +1 -0
- package/dist/shared/render-C6HRPs10.js +4289 -0
- package/dist/shared/render-C6HRPs10.js.map +1 -0
- package/dist/shared/render-CgwKdOzu.d.ts +2311 -0
- package/dist/shared/render-DO0F5YSm.d.cts +2311 -0
- package/dist/shared/render-mcuELiYi.cjs +4462 -0
- package/dist/shared/render-mcuELiYi.cjs.map +1 -0
- package/dist/shared/storage-BPJR_k4-.cjs +290 -0
- package/dist/shared/storage-BPJR_k4-.cjs.map +1 -0
- package/dist/{storage-BqMxs76Y.d.ts → shared/storage-C_eICCep.d.cts} +2 -2
- package/dist/{storage-BqMxs76Y.d.cts → shared/storage-C_eICCep.d.ts} +2 -2
- package/dist/shared/storage-D86edNCB.js +231 -0
- package/dist/shared/storage-D86edNCB.js.map +1 -0
- package/dist/shared/url-BaMCQpYH.cjs +4084 -0
- package/dist/shared/url-BaMCQpYH.cjs.map +1 -0
- package/dist/shared/url-DTfZ2toq.d.cts +2081 -0
- package/dist/shared/url-DTfZ2toq.d.ts +2081 -0
- package/dist/shared/url-IU0xN9wX.js +3713 -0
- package/dist/shared/url-IU0xN9wX.js.map +1 -0
- package/dist/shared/websocket-BLR8eVJV.js +1874 -0
- package/dist/shared/websocket-BLR8eVJV.js.map +1 -0
- package/dist/shared/websocket-C_eI4H2o.cjs +1945 -0
- package/dist/shared/websocket-C_eI4H2o.cjs.map +1 -0
- package/dist/shared/websocket-DF7XIMiX.d.cts +562 -0
- package/dist/shared/websocket-DYKBr8HF.d.ts +562 -0
- package/dist/{web.cjs → web/index.cjs} +4 -3
- package/dist/web/index.cjs.map +1 -0
- package/dist/{web.d.cts → web/index.d.cts} +2 -2
- package/dist/{web.d.ts → web/index.d.ts} +2 -2
- package/dist/{web.js → web/index.js} +3 -2
- package/dist/web/index.js.map +1 -0
- package/package.json +40 -12
- package/dist/multi-storage-BUZaLAPO.js.map +0 -1
- package/dist/multi-storage-BUoEoW8f.d.cts +0 -154
- package/dist/multi-storage-Bf84xiO8.cjs.map +0 -1
- package/dist/multi-storage-CQSlI_kn.d.ts +0 -154
- package/dist/node.cjs.map +0 -1
- package/dist/node.js.map +0 -1
- package/dist/storage-DnzZPS_9.cjs.map +0 -1
- package/dist/storage-IHdXw52v.js.map +0 -1
- package/dist/web.cjs.map +0 -1
- package/dist/web.js.map +0 -1
|
@@ -1,4 +1,100 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as ItdConfigError } from "./errors-Bhrd2fJd.js";
|
|
2
|
+
//#region src/core/url.ts
|
|
3
|
+
/**
|
|
4
|
+
* Собирает строку запроса.
|
|
5
|
+
*
|
|
6
|
+
* Правила:
|
|
7
|
+
* - `undefined` и `null` пропускаются целиком — не нужно чистить объект перед вызовом;
|
|
8
|
+
* - `boolean` превращается в `true` / `false`;
|
|
9
|
+
* - массив повторяет ключ (`ids=1&ids=2`).
|
|
10
|
+
*
|
|
11
|
+
* @returns строка вида `?a=1&b=2` либо пустая строка, если параметров нет
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* buildQuery({ tab: 'popular', limit: 20, cursor: undefined });
|
|
16
|
+
* // '?tab=popular&limit=20'
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
function buildQuery(params) {
|
|
20
|
+
if (!params) return "";
|
|
21
|
+
const search = new URLSearchParams();
|
|
22
|
+
for (const [key, value] of Object.entries(params)) {
|
|
23
|
+
if (value === void 0 || value === null) continue;
|
|
24
|
+
if (Array.isArray(value)) {
|
|
25
|
+
for (const item of value) {
|
|
26
|
+
if (item === void 0 || item === null) continue;
|
|
27
|
+
search.append(key, String(item));
|
|
28
|
+
}
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
search.append(key, String(value));
|
|
32
|
+
}
|
|
33
|
+
const query = search.toString();
|
|
34
|
+
return query ? `?${query}` : "";
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Кодирует значение для подстановки в путь.
|
|
38
|
+
*
|
|
39
|
+
* Нужно для сегментов, которые могут содержать что угодно, — прежде всего хэштегов
|
|
40
|
+
* в `/api/hashtags/{tag}/posts`.
|
|
41
|
+
*
|
|
42
|
+
* @throws {ItdConfigError} если значение пустое
|
|
43
|
+
*/
|
|
44
|
+
function encodePathSegment(value, name = "параметр пути") {
|
|
45
|
+
if (typeof value !== "string" || value.trim() === "") throw new ItdConfigError(`${name} должен быть непустой строкой, получено: ${JSON.stringify(value)}`);
|
|
46
|
+
return encodeURIComponent(value);
|
|
47
|
+
}
|
|
48
|
+
/** Хост из URL в нижнем регистре. Пустая строка, если разобрать не удалось. */
|
|
49
|
+
function hostOf(url) {
|
|
50
|
+
try {
|
|
51
|
+
return new URL(url).hostname.toLowerCase();
|
|
52
|
+
} catch {
|
|
53
|
+
return "";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Origin из URL — схема, хост и порт. Пустая строка, если разобрать не удалось. */
|
|
57
|
+
function originOf(url) {
|
|
58
|
+
try {
|
|
59
|
+
return new URL(url).origin;
|
|
60
|
+
} catch {
|
|
61
|
+
return "";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Тот же хост либо его поддомен. Принимает хосты, а не адреса. */
|
|
65
|
+
function isSameSite(primaryHost, host) {
|
|
66
|
+
if (!primaryHost || !host) return false;
|
|
67
|
+
return host === primaryHost || host.endsWith(`.${primaryHost}`);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Склеивает базовый URL и путь.
|
|
71
|
+
*
|
|
72
|
+
* Завершающий слэш пути сохраняется: он значим для `/api/notifications/`
|
|
73
|
+
* и `/api/v1/subscription/`, без него сервер отвечает ошибкой.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* joinUrl('https://xn--d1ah4a.com/', '/api/notifications/');
|
|
78
|
+
* // 'https://xn--d1ah4a.com/api/notifications/'
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
function joinUrl(baseUrl, path) {
|
|
82
|
+
return `${baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
83
|
+
}
|
|
84
|
+
/** Приводит базовый URL к каноничному виду и проверяет, что он вообще похож на URL. */
|
|
85
|
+
function normalizeBaseUrl(baseUrl) {
|
|
86
|
+
if (typeof baseUrl !== "string" || baseUrl.trim() === "") throw new ItdConfigError(`baseUrl должен быть непустой строкой с абсолютным URL, получено: ${JSON.stringify(baseUrl)}`);
|
|
87
|
+
let parsed;
|
|
88
|
+
try {
|
|
89
|
+
parsed = new URL(baseUrl);
|
|
90
|
+
} catch {
|
|
91
|
+
throw new ItdConfigError(`baseUrl должен быть абсолютным URL, получено: ${JSON.stringify(baseUrl)}`);
|
|
92
|
+
}
|
|
93
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new ItdConfigError(`baseUrl должен использовать http или https, получено: ${parsed.protocol}`);
|
|
94
|
+
if (parsed.username || parsed.password || parsed.search || parsed.hash) throw new ItdConfigError("baseUrl не должен содержать логин, пароль, query-параметры или fragment");
|
|
95
|
+
return parsed.origin + (parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/$/, ""));
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
2
98
|
//#region node_modules/set-cookie-parser/lib/set-cookie.js
|
|
3
99
|
var defaultParseOptions = {
|
|
4
100
|
decodeValues: true,
|
|
@@ -139,102 +235,6 @@ parseSetCookie.parse = parseSetCookie;
|
|
|
139
235
|
parseSetCookie.parseString = parseString;
|
|
140
236
|
parseSetCookie.splitCookiesString = splitCookiesString;
|
|
141
237
|
//#endregion
|
|
142
|
-
//#region src/core/url.ts
|
|
143
|
-
/**
|
|
144
|
-
* Собирает строку запроса.
|
|
145
|
-
*
|
|
146
|
-
* Правила:
|
|
147
|
-
* - `undefined` и `null` пропускаются целиком — не нужно чистить объект перед вызовом;
|
|
148
|
-
* - `boolean` превращается в `true` / `false`;
|
|
149
|
-
* - массив повторяет ключ (`ids=1&ids=2`).
|
|
150
|
-
*
|
|
151
|
-
* @returns строка вида `?a=1&b=2` либо пустая строка, если параметров нет
|
|
152
|
-
*
|
|
153
|
-
* @example
|
|
154
|
-
* ```ts
|
|
155
|
-
* buildQuery({ tab: 'popular', limit: 20, cursor: undefined });
|
|
156
|
-
* // '?tab=popular&limit=20'
|
|
157
|
-
* ```
|
|
158
|
-
*/
|
|
159
|
-
function buildQuery(params) {
|
|
160
|
-
if (!params) return "";
|
|
161
|
-
const search = new URLSearchParams();
|
|
162
|
-
for (const [key, value] of Object.entries(params)) {
|
|
163
|
-
if (value === void 0 || value === null) continue;
|
|
164
|
-
if (Array.isArray(value)) {
|
|
165
|
-
for (const item of value) {
|
|
166
|
-
if (item === void 0 || item === null) continue;
|
|
167
|
-
search.append(key, String(item));
|
|
168
|
-
}
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
search.append(key, String(value));
|
|
172
|
-
}
|
|
173
|
-
const query = search.toString();
|
|
174
|
-
return query ? `?${query}` : "";
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Кодирует значение для подстановки в путь.
|
|
178
|
-
*
|
|
179
|
-
* Нужно для сегментов, которые могут содержать что угодно, — прежде всего хэштегов
|
|
180
|
-
* в `/api/hashtags/{tag}/posts`.
|
|
181
|
-
*
|
|
182
|
-
* @throws {ItdConfigError} если значение пустое
|
|
183
|
-
*/
|
|
184
|
-
function encodePathSegment(value, name = "параметр пути") {
|
|
185
|
-
if (typeof value !== "string" || value.trim() === "") throw new ItdConfigError(`${name} должен быть непустой строкой, получено: ${JSON.stringify(value)}`);
|
|
186
|
-
return encodeURIComponent(value);
|
|
187
|
-
}
|
|
188
|
-
/** Хост из URL в нижнем регистре. Пустая строка, если разобрать не удалось. */
|
|
189
|
-
function hostOf(url) {
|
|
190
|
-
try {
|
|
191
|
-
return new URL(url).hostname.toLowerCase();
|
|
192
|
-
} catch {
|
|
193
|
-
return "";
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
/** Origin из URL — схема, хост и порт. Пустая строка, если разобрать не удалось. */
|
|
197
|
-
function originOf(url) {
|
|
198
|
-
try {
|
|
199
|
-
return new URL(url).origin;
|
|
200
|
-
} catch {
|
|
201
|
-
return "";
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
/** Тот же хост либо его поддомен. Принимает хосты, а не адреса. */
|
|
205
|
-
function isSameSite(primaryHost, host) {
|
|
206
|
-
if (!primaryHost || !host) return false;
|
|
207
|
-
return host === primaryHost || host.endsWith(`.${primaryHost}`);
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Склеивает базовый URL и путь.
|
|
211
|
-
*
|
|
212
|
-
* Завершающий слэш пути сохраняется: он значим для `/api/notifications/`
|
|
213
|
-
* и `/api/v1/subscription/`, без него сервер отвечает ошибкой.
|
|
214
|
-
*
|
|
215
|
-
* @example
|
|
216
|
-
* ```ts
|
|
217
|
-
* joinUrl('https://xn--d1ah4a.com/', '/api/notifications/');
|
|
218
|
-
* // 'https://xn--d1ah4a.com/api/notifications/'
|
|
219
|
-
* ```
|
|
220
|
-
*/
|
|
221
|
-
function joinUrl(baseUrl, path) {
|
|
222
|
-
return `${baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
223
|
-
}
|
|
224
|
-
/** Приводит базовый URL к каноничному виду и проверяет, что он вообще похож на URL. */
|
|
225
|
-
function normalizeBaseUrl(baseUrl) {
|
|
226
|
-
if (typeof baseUrl !== "string" || baseUrl.trim() === "") throw new ItdConfigError(`baseUrl должен быть непустой строкой с абсолютным URL, получено: ${JSON.stringify(baseUrl)}`);
|
|
227
|
-
let parsed;
|
|
228
|
-
try {
|
|
229
|
-
parsed = new URL(baseUrl);
|
|
230
|
-
} catch {
|
|
231
|
-
throw new ItdConfigError(`baseUrl должен быть абсолютным URL, получено: ${JSON.stringify(baseUrl)}`);
|
|
232
|
-
}
|
|
233
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new ItdConfigError(`baseUrl должен использовать http или https, получено: ${parsed.protocol}`);
|
|
234
|
-
if (parsed.username || parsed.password || parsed.search || parsed.hash) throw new ItdConfigError("baseUrl не должен содержать логин, пароль, query-параметры или fragment");
|
|
235
|
-
return parsed.origin + (parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/$/, ""));
|
|
236
|
-
}
|
|
237
|
-
//#endregion
|
|
238
238
|
//#region src/core/cookies.ts
|
|
239
239
|
/** Имя cookie-флага «есть refresh-сессия». Ставится сайтом итд.com рядом с refresh-токеном. */
|
|
240
240
|
const AUTH_FLAG_COOKIE = "is_auth";
|
|
@@ -421,259 +421,6 @@ var CookieJar = class {
|
|
|
421
421
|
}
|
|
422
422
|
};
|
|
423
423
|
//#endregion
|
|
424
|
-
|
|
425
|
-
/** Способ передачи содержимого вложения. */
|
|
426
|
-
const FileTransferMode = Object.freeze({
|
|
427
|
-
/** Сначала получить файл целиком, затем отправить его как `Blob`. */
|
|
428
|
-
Buffer: "buffer",
|
|
429
|
-
/** Передавать данные по мере чтения, не собирая файл целиком в памяти. */
|
|
430
|
-
Stream: "stream"
|
|
431
|
-
});
|
|
432
|
-
/** Предел размера файла, скачиваемого по адресу, — 100 МиБ. */
|
|
433
|
-
const DEFAULT_URL_FILE_MAX_BYTES = 104857600;
|
|
434
|
-
//#endregion
|
|
435
|
-
//#region src/core/attachments/limits.ts
|
|
436
|
-
/** Проверяет числовую границу до обращения к источнику. */
|
|
437
|
-
function optionalBytes(value, name) {
|
|
438
|
-
if (value === void 0) return void 0;
|
|
439
|
-
if (!Number.isFinite(value) || value < 0 || !Number.isInteger(value)) throw new ItdConfigError(`${name} должен быть неотрицательным целым числом, получено: ${value}`);
|
|
440
|
-
return value;
|
|
441
|
-
}
|
|
442
|
-
/** Создаёт типизированную ошибку превышения размера вложения. */
|
|
443
|
-
function fileTooLarge(url, limit, actual) {
|
|
444
|
-
const source = url ? ` по адресу ${url}` : "";
|
|
445
|
-
return new ItdFileError(`файл${source} больше предела в ${limit} байт: ${actual}`, {
|
|
446
|
-
reason: ItdFileErrorReason.TooLarge,
|
|
447
|
-
...url ? { url } : {},
|
|
448
|
-
limit,
|
|
449
|
-
actual
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
//#endregion
|
|
453
|
-
//#region src/core/attachments/bounded-stream.ts
|
|
454
|
-
/** Потоки, на которые уже наложены счётчик размера и backpressure. */
|
|
455
|
-
const BOUNDED_FILE_STREAMS = /* @__PURE__ */ new WeakSet();
|
|
456
|
-
/** Приводит произвольный чанк потока к байтам. */
|
|
457
|
-
function asBytes(value) {
|
|
458
|
-
return value instanceof Uint8Array ? value : new Uint8Array(value);
|
|
459
|
-
}
|
|
460
|
-
/** Проверяет значение без обращения к методам потенциально чужого объекта. */
|
|
461
|
-
function isReadableByteStream(value) {
|
|
462
|
-
return typeof ReadableStream !== "undefined" && value instanceof ReadableStream;
|
|
463
|
-
}
|
|
464
|
-
/**
|
|
465
|
-
* Ограничивает размер и очередь потока. Отмена результирующего потока отменяет исходный.
|
|
466
|
-
*
|
|
467
|
-
* @internal
|
|
468
|
-
*/
|
|
469
|
-
function boundedFileStream(source, options = {}) {
|
|
470
|
-
const maxBytes = optionalBytes(options.maxBytes, "maxBytes");
|
|
471
|
-
const streamBufferBytes = optionalBytes(options.streamBufferBytes ?? 4194304, "streamBufferBytes") ?? 4194304;
|
|
472
|
-
if (streamBufferBytes === 0) throw new ItdConfigError("streamBufferBytes должен быть больше нуля");
|
|
473
|
-
const reader = source.getReader();
|
|
474
|
-
let total = 0;
|
|
475
|
-
let finished = false;
|
|
476
|
-
let abortListener;
|
|
477
|
-
const cleanup = () => {
|
|
478
|
-
if (abortListener) options.signal?.removeEventListener("abort", abortListener);
|
|
479
|
-
abortListener = void 0;
|
|
480
|
-
};
|
|
481
|
-
const stream = new ReadableStream({
|
|
482
|
-
start(controller) {
|
|
483
|
-
if (!options.signal) return;
|
|
484
|
-
abortListener = () => {
|
|
485
|
-
finished = true;
|
|
486
|
-
reader.cancel(options.signal?.reason).catch(() => {});
|
|
487
|
-
controller.error(options.signal?.reason ?? new DOMException("Aborted", "AbortError"));
|
|
488
|
-
cleanup();
|
|
489
|
-
};
|
|
490
|
-
if (options.signal.aborted) abortListener();
|
|
491
|
-
else options.signal.addEventListener("abort", abortListener, { once: true });
|
|
492
|
-
},
|
|
493
|
-
async pull(controller) {
|
|
494
|
-
if (finished) return;
|
|
495
|
-
try {
|
|
496
|
-
const next = await reader.read();
|
|
497
|
-
if (finished) return;
|
|
498
|
-
if (next.done) {
|
|
499
|
-
finished = true;
|
|
500
|
-
cleanup();
|
|
501
|
-
controller.close();
|
|
502
|
-
return;
|
|
503
|
-
}
|
|
504
|
-
const chunk = asBytes(next.value);
|
|
505
|
-
total += chunk.byteLength;
|
|
506
|
-
if (maxBytes !== void 0 && total > maxBytes) {
|
|
507
|
-
finished = true;
|
|
508
|
-
await reader.cancel().catch(() => {});
|
|
509
|
-
cleanup();
|
|
510
|
-
controller.error(fileTooLarge(options.url, maxBytes, total));
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
|
-
controller.enqueue(chunk);
|
|
514
|
-
} catch (error) {
|
|
515
|
-
finished = true;
|
|
516
|
-
cleanup();
|
|
517
|
-
if (error instanceof ItdFileError) {
|
|
518
|
-
controller.error(error);
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
controller.error(new ItdFileError("не удалось прочитать поток вложения", {
|
|
522
|
-
reason: ItdFileErrorReason.Read,
|
|
523
|
-
...options.url ? { url: options.url } : {},
|
|
524
|
-
retryable: options.retryableRead ?? false,
|
|
525
|
-
cause: error
|
|
526
|
-
}));
|
|
527
|
-
}
|
|
528
|
-
},
|
|
529
|
-
async cancel(reason) {
|
|
530
|
-
finished = true;
|
|
531
|
-
cleanup();
|
|
532
|
-
await reader.cancel(reason).catch(() => {});
|
|
533
|
-
}
|
|
534
|
-
}, {
|
|
535
|
-
highWaterMark: streamBufferBytes,
|
|
536
|
-
size: (chunk) => chunk.byteLength
|
|
537
|
-
});
|
|
538
|
-
BOUNDED_FILE_STREAMS.add(stream);
|
|
539
|
-
return stream;
|
|
540
|
-
}
|
|
541
|
-
/** Наложены ли на поток ограничения библиотеки. @internal */
|
|
542
|
-
function isBoundedFileStream(stream) {
|
|
543
|
-
return BOUNDED_FILE_STREAMS.has(stream);
|
|
544
|
-
}
|
|
545
|
-
//#endregion
|
|
546
|
-
//#region src/core/attachments/options.ts
|
|
547
|
-
/** Проверяет и дополняет настройки потока. @internal */
|
|
548
|
-
function resolveFileStreamOptions(options, defaultMaxBytes) {
|
|
549
|
-
const mode = options.mode ?? FileTransferMode.Buffer;
|
|
550
|
-
if (mode !== FileTransferMode.Buffer && mode !== FileTransferMode.Stream) throw new ItdConfigError(`mode вложения должен быть 'buffer' или 'stream', получено: ${mode}`);
|
|
551
|
-
if (mode === FileTransferMode.Stream && typeof ReadableStream === "undefined") throw new ItdConfigError("эта среда не поддерживает ReadableStream; используйте mode: 'buffer'");
|
|
552
|
-
const maxBytes = optionalBytes(options.maxBytes ?? defaultMaxBytes, "maxBytes");
|
|
553
|
-
const streamBufferBytes = optionalBytes(options.streamBufferBytes ?? 4194304, "streamBufferBytes") ?? 4194304;
|
|
554
|
-
if (streamBufferBytes === 0) throw new ItdConfigError("streamBufferBytes должен быть больше нуля");
|
|
555
|
-
return {
|
|
556
|
-
mode,
|
|
557
|
-
maxBytes,
|
|
558
|
-
streamBufferBytes
|
|
559
|
-
};
|
|
560
|
-
}
|
|
561
|
-
//#endregion
|
|
562
|
-
//#region src/core/multi-storage.ts
|
|
563
|
-
/**
|
|
564
|
-
* Срез мультихранилища как обычное {@link TokenStorage} — в таком виде его получает
|
|
565
|
-
* отдельный `ItdClient`, который про соседние аккаунты ничего не знает.
|
|
566
|
-
*/
|
|
567
|
-
function scopedTokenStorage(storage, account) {
|
|
568
|
-
return {
|
|
569
|
-
get: () => storage.get(account),
|
|
570
|
-
set: (session) => storage.set(account, session),
|
|
571
|
-
clear: () => storage.clear(account)
|
|
572
|
-
};
|
|
573
|
-
}
|
|
574
|
-
/**
|
|
575
|
-
* Можно ли восстановить авторизованный аккаунт из записи.
|
|
576
|
-
*
|
|
577
|
-
* Один `deviceId` не считается сессией: он намеренно переживает выход, но не позволяет
|
|
578
|
-
* выполнить ни одного авторизованного запроса.
|
|
579
|
-
*
|
|
580
|
-
* @internal
|
|
581
|
-
*/
|
|
582
|
-
function isRestorableSession(session) {
|
|
583
|
-
if (!session) return false;
|
|
584
|
-
if (session.accessToken || session.refreshToken) return true;
|
|
585
|
-
const jar = new CookieJar();
|
|
586
|
-
jar.deserialize(session.cookies);
|
|
587
|
-
return jar.has(AUTH_FLAG_COOKIE);
|
|
588
|
-
}
|
|
589
|
-
/** @internal */
|
|
590
|
-
function controlledTokenStorage(storage, account) {
|
|
591
|
-
let revoked = false;
|
|
592
|
-
const pending = /* @__PURE__ */ new Set();
|
|
593
|
-
const mutate = (operation) => {
|
|
594
|
-
if (revoked) return Promise.resolve();
|
|
595
|
-
const promise = Promise.resolve().then(operation);
|
|
596
|
-
pending.add(promise);
|
|
597
|
-
promise.then(() => pending.delete(promise), () => pending.delete(promise));
|
|
598
|
-
return promise;
|
|
599
|
-
};
|
|
600
|
-
return {
|
|
601
|
-
storage: {
|
|
602
|
-
get: () => revoked ? null : storage.get(account),
|
|
603
|
-
set: (session) => mutate(() => storage.set(account, session)),
|
|
604
|
-
clear: () => mutate(() => storage.clear(account))
|
|
605
|
-
},
|
|
606
|
-
revoke() {
|
|
607
|
-
revoked = true;
|
|
608
|
-
},
|
|
609
|
-
async drain() {
|
|
610
|
-
await Promise.all([...pending]);
|
|
611
|
-
}
|
|
612
|
-
};
|
|
613
|
-
}
|
|
614
|
-
/**
|
|
615
|
-
* Мультихранилище в памяти процесса — вариант по умолчанию.
|
|
616
|
-
*
|
|
617
|
-
* Сессии теряются при перезапуске. Для долгоживущих ботов возьмите `FileMultiTokenStorage`
|
|
618
|
-
* из `itd-api/node` либо соберите своё через {@link createMultiTokenStorage}.
|
|
619
|
-
*/
|
|
620
|
-
var MemoryMultiTokenStorage = class {
|
|
621
|
-
#store = new MemoryKeyValueStore();
|
|
622
|
-
constructor(initial) {
|
|
623
|
-
for (const [account, session] of Object.entries(initial ?? {})) this.#store.set(accountKey(account), copySession(session));
|
|
624
|
-
}
|
|
625
|
-
get(account) {
|
|
626
|
-
const session = this.#store.get(accountKey(account));
|
|
627
|
-
return session ? copySession(session) : null;
|
|
628
|
-
}
|
|
629
|
-
set(account, session) {
|
|
630
|
-
this.#store.set(accountKey(account), copySession(session));
|
|
631
|
-
}
|
|
632
|
-
clear(account) {
|
|
633
|
-
this.#store.delete(accountKey(account));
|
|
634
|
-
}
|
|
635
|
-
accounts() {
|
|
636
|
-
return this.#store.keys(ACCOUNT_KEY_PREFIX).flatMap((key) => readAccountName(key.slice(ACCOUNT_KEY_PREFIX.length)) ?? []);
|
|
637
|
-
}
|
|
638
|
-
};
|
|
639
|
-
const ACCOUNT_KEY_PREFIX = "accounts/";
|
|
640
|
-
function accountKey(account) {
|
|
641
|
-
return `${ACCOUNT_KEY_PREFIX}${encodeURIComponent(account)}`;
|
|
642
|
-
}
|
|
643
|
-
/** Читает имя аккаунта из ключа. `undefined` — ключ не декодируется и запись пропускается. */
|
|
644
|
-
function readAccountName(encoded) {
|
|
645
|
-
try {
|
|
646
|
-
return decodeURIComponent(encoded);
|
|
647
|
-
} catch {
|
|
648
|
-
console.warn(`[itd-api] запись хранилища пропущена: ключ ${JSON.stringify(encoded)} не декодируется`);
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
/**
|
|
653
|
-
* Создаёт доменное хранилище нескольких сессий поверх enumerable key-value backend.
|
|
654
|
-
*
|
|
655
|
-
* Отдельный индекс аккаунтов не используется: `accounts()` перечисляет ключи backend. Поэтому
|
|
656
|
-
* запись сессии не может разойтись с индексом, но backend обязан эффективно поддерживать `keys`.
|
|
657
|
-
*/
|
|
658
|
-
function createMultiTokenStorage(store, options = {}) {
|
|
659
|
-
const prefix = options.prefix ?? ACCOUNT_KEY_PREFIX;
|
|
660
|
-
if (typeof prefix !== "string") throw new ItdConfigError("prefix MultiTokenStorage должен быть строкой");
|
|
661
|
-
const backend = createKeyValueStore(store);
|
|
662
|
-
if (!isEnumerableKeyValueStore(backend)) throw new ItdConfigError("MultiTokenStorage требует KeyValueStore с методом keys()");
|
|
663
|
-
const key = (account) => `${prefix}${encodeURIComponent(account)}`;
|
|
664
|
-
return {
|
|
665
|
-
async get(account) {
|
|
666
|
-
const session = await backend.get(key(account));
|
|
667
|
-
return session ? copySession(session) : null;
|
|
668
|
-
},
|
|
669
|
-
set: (account, session) => backend.set(key(account), copySession(session)),
|
|
670
|
-
clear: (account) => backend.delete(key(account)),
|
|
671
|
-
async accounts() {
|
|
672
|
-
return (await collectKeyValueStoreKeys(backend, prefix)).flatMap((value) => readAccountName(value.slice(prefix.length)) ?? []);
|
|
673
|
-
}
|
|
674
|
-
};
|
|
675
|
-
}
|
|
676
|
-
//#endregion
|
|
677
|
-
export { normalizeBaseUrl as C, joinUrl as S, REFRESH_COOKIE_PATH as _, scopedTokenStorage as a, hostOf as b, isBoundedFileStream as c, optionalBytes as d, DEFAULT_URL_FILE_MAX_BYTES as f, REFRESH_COOKIE as g, CookieJar as h, isRestorableSession as i, isReadableByteStream as l, AUTH_FLAG_COOKIE as m, controlledTokenStorage as n, resolveFileStreamOptions as o, FileTransferMode as p, createMultiTokenStorage as r, boundedFileStream as s, MemoryMultiTokenStorage as t, fileTooLarge as u, buildQuery as v, originOf as w, isSameSite as x, encodePathSegment as y };
|
|
424
|
+
export { buildQuery as a, isSameSite as c, originOf as d, REFRESH_COOKIE_PATH as i, joinUrl as l, CookieJar as n, encodePathSegment as o, REFRESH_COOKIE as r, hostOf as s, AUTH_FLAG_COOKIE as t, normalizeBaseUrl as u };
|
|
678
425
|
|
|
679
|
-
//# sourceMappingURL=
|
|
426
|
+
//# sourceMappingURL=cookies-tX2sNwxb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookies-tX2sNwxb.js","names":["#byOrigin","#matching"],"sources":["../../src/core/url.ts","../../node_modules/set-cookie-parser/lib/set-cookie.js","../../src/core/cookies.ts"],"sourcesContent":["import { ItdConfigError } from './errors.js';\n\n/** Значение параметра запроса. `undefined` и `null` в строку не попадают. */\nexport type QueryValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | readonly (string | number | boolean)[];\n\n/** Параметры строки запроса. */\nexport type QueryParams = Record<string, QueryValue>;\n\n/**\n * Собирает строку запроса.\n *\n * Правила:\n * - `undefined` и `null` пропускаются целиком — не нужно чистить объект перед вызовом;\n * - `boolean` превращается в `true` / `false`;\n * - массив повторяет ключ (`ids=1&ids=2`).\n *\n * @returns строка вида `?a=1&b=2` либо пустая строка, если параметров нет\n *\n * @example\n * ```ts\n * buildQuery({ tab: 'popular', limit: 20, cursor: undefined });\n * // '?tab=popular&limit=20'\n * ```\n */\nexport function buildQuery(params?: QueryParams): string {\n if (!params) return '';\n\n const search = new URLSearchParams();\n\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue;\n\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item === undefined || item === null) continue;\n search.append(key, String(item));\n }\n continue;\n }\n\n search.append(key, String(value));\n }\n\n const query = search.toString();\n return query ? `?${query}` : '';\n}\n\n/**\n * Кодирует значение для подстановки в путь.\n *\n * Нужно для сегментов, которые могут содержать что угодно, — прежде всего хэштегов\n * в `/api/hashtags/{tag}/posts`.\n *\n * @throws {ItdConfigError} если значение пустое\n */\nexport function encodePathSegment(value: string, name = 'параметр пути'): string {\n if (typeof value !== 'string' || value.trim() === '') {\n throw new ItdConfigError(\n `${name} должен быть непустой строкой, получено: ${JSON.stringify(value)}`,\n );\n }\n return encodeURIComponent(value);\n}\n\n/** Хост из URL в нижнем регистре. Пустая строка, если разобрать не удалось. */\nexport function hostOf(url: string): string {\n try {\n return new URL(url).hostname.toLowerCase();\n } catch {\n return '';\n }\n}\n\n/** Origin из URL — схема, хост и порт. Пустая строка, если разобрать не удалось. */\nexport function originOf(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n return '';\n }\n}\n\n/** Тот же хост либо его поддомен. Принимает хосты, а не адреса. */\nexport function isSameSite(primaryHost: string, host: string): boolean {\n if (!primaryHost || !host) return false;\n return host === primaryHost || host.endsWith(`.${primaryHost}`);\n}\n\n/**\n * Склеивает базовый URL и путь.\n *\n * Завершающий слэш пути сохраняется: он значим для `/api/notifications/`\n * и `/api/v1/subscription/`, без него сервер отвечает ошибкой.\n *\n * @example\n * ```ts\n * joinUrl('https://xn--d1ah4a.com/', '/api/notifications/');\n * // 'https://xn--d1ah4a.com/api/notifications/'\n * ```\n */\nexport function joinUrl(baseUrl: string, path: string): string {\n const base = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n const suffix = path.startsWith('/') ? path : `/${path}`;\n return `${base}${suffix}`;\n}\n\n/** Приводит базовый URL к каноничному виду и проверяет, что он вообще похож на URL. */\nexport function normalizeBaseUrl(baseUrl: string): string {\n if (typeof baseUrl !== 'string' || baseUrl.trim() === '') {\n throw new ItdConfigError(\n `baseUrl должен быть непустой строкой с абсолютным URL, получено: ${JSON.stringify(baseUrl)}`,\n );\n }\n\n let parsed: URL;\n try {\n parsed = new URL(baseUrl);\n } catch {\n throw new ItdConfigError(\n `baseUrl должен быть абсолютным URL, получено: ${JSON.stringify(baseUrl)}`,\n );\n }\n\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new ItdConfigError(\n `baseUrl должен использовать http или https, получено: ${parsed.protocol}`,\n );\n }\n\n if (parsed.username || parsed.password || parsed.search || parsed.hash) {\n throw new ItdConfigError(\n 'baseUrl не должен содержать логин, пароль, query-параметры или fragment',\n );\n }\n\n return parsed.origin + (parsed.pathname === '/' ? '' : parsed.pathname.replace(/\\/$/, ''));\n}\n","var defaultParseOptions = {\n decodeValues: true,\n map: false,\n silent: false,\n split: \"auto\", // auto = split strings but not arrays\n};\n\nfunction isForbiddenKey(key) {\n return typeof key !== \"string\" || key in {};\n}\n\nfunction createNullObj() {\n return Object.create(null);\n}\n\nfunction isNonEmptyString(str) {\n return typeof str === \"string\" && !!str.trim();\n}\n\nfunction parseString(setCookieValue, options) {\n var parts = setCookieValue.split(\";\").filter(isNonEmptyString);\n\n var nameValuePairStr = parts.shift();\n if (!nameValuePairStr) {\n return null;\n }\n var parsed = parseNameValuePair(nameValuePairStr);\n var name = parsed.name;\n var value = parsed.value;\n\n options = options\n ? Object.assign({}, defaultParseOptions, options)\n : defaultParseOptions;\n\n if (isForbiddenKey(name)) {\n return null;\n }\n\n try {\n value = options.decodeValues ? decodeURIComponent(value) : value; // decode cookie value\n } catch (e) {\n console.error(\n \"set-cookie-parser: failed to decode cookie value. Set options.decodeValues=false to disable decoding.\",\n e\n );\n }\n\n var cookie = createNullObj();\n cookie.name = name;\n cookie.value = value;\n\n parts.forEach(function (part) {\n var sides = part.split(\"=\");\n var key = sides.shift().trim().toLowerCase();\n if (isForbiddenKey(key)) {\n return;\n }\n var value = sides.join(\"=\").trim();\n if (key === \"expires\") {\n cookie.expires = new Date(value);\n } else if (key === \"max-age\") {\n var n = parseInt(value, 10);\n if (!Number.isNaN(n)) cookie.maxAge = n;\n } else if (key === \"secure\") {\n cookie.secure = true;\n } else if (key === \"httponly\") {\n cookie.httpOnly = true;\n } else if (key === \"samesite\") {\n cookie.sameSite = value;\n } else if (key === \"partitioned\") {\n cookie.partitioned = true;\n } else if (key) {\n cookie[key] = value;\n }\n });\n\n return cookie;\n}\n\nfunction parseNameValuePair(nameValuePairStr) {\n // Parses name-value-pair according to rfc6265bis draft\n\n var name = \"\";\n var value = \"\";\n var nameValueArr = nameValuePairStr.split(\"=\");\n if (nameValueArr.length > 1) {\n name = nameValueArr.shift();\n value = nameValueArr.join(\"=\"); // everything after the first =, joined by a \"=\" if there was more than one part\n } else {\n value = nameValuePairStr;\n }\n\n return { name: name, value: value };\n}\n\nfunction parseSetCookie(input, options) {\n options = options\n ? Object.assign({}, defaultParseOptions, options)\n : defaultParseOptions;\n\n if (!input) {\n if (!options.map) {\n return [];\n } else {\n return createNullObj();\n }\n }\n\n if (input.headers) {\n if (typeof input.headers.getSetCookie === \"function\") {\n // for fetch responses - they combine headers of the same type in the headers array,\n // but getSetCookie returns an uncombined array\n input = input.headers.getSetCookie();\n } else if (input.headers[\"set-cookie\"]) {\n // fast-path for node.js (which automatically normalizes header names to lower-case)\n input = input.headers[\"set-cookie\"];\n } else {\n // slow-path for other environments - see #25\n var sch =\n input.headers[\n Object.keys(input.headers).find(function (key) {\n return key.toLowerCase() === \"set-cookie\";\n })\n ];\n // warn if called on a request-like object with a cookie header rather than a set-cookie header - see #34, 36\n if (!sch && input.headers.cookie && !options.silent) {\n console.warn(\n \"Warning: set-cookie-parser appears to have been called on a request object. It is designed to parse Set-Cookie headers from responses, not Cookie headers from requests. Set the option {silent: true} to suppress this warning.\"\n );\n }\n input = sch;\n }\n }\n\n var split = options.split;\n var isArray = Array.isArray(input);\n\n if (split === \"auto\") {\n split = !isArray;\n }\n\n if (!isArray) {\n input = [input];\n }\n\n input = input.filter(isNonEmptyString);\n\n if (split) {\n input = input.map(splitCookiesString).flat();\n }\n\n if (!options.map) {\n return input\n .map(function (str) {\n return parseString(str, options);\n })\n .filter(Boolean);\n } else {\n var cookies = createNullObj();\n return input.reduce(function (cookies, str) {\n var cookie = parseString(str, options);\n if (cookie && !isForbiddenKey(cookie.name)) {\n cookies[cookie.name] = cookie;\n }\n return cookies;\n }, cookies);\n }\n}\n\n/*\n Set-Cookie header field-values are sometimes comma joined in one string. This splits them without choking on commas\n that are within a single set-cookie field-value, such as in the Expires portion.\n\n This is uncommon, but explicitly allowed - see https://tools.ietf.org/html/rfc2616#section-4.2\n Node.js does this for every header *except* set-cookie - see https://github.com/nodejs/node/blob/d5e363b77ebaf1caf67cd7528224b651c86815c1/lib/_http_incoming.js#L128\n React Native's fetch does this for *every* header, including set-cookie.\n\n Based on: https://github.com/google/j2objc/commit/16820fdbc8f76ca0c33472810ce0cb03d20efe25\n Credits to: https://github.com/tomball for original and https://github.com/chrusart for JavaScript implementation\n*/\nfunction splitCookiesString(cookiesString) {\n if (Array.isArray(cookiesString)) {\n return cookiesString;\n }\n if (typeof cookiesString !== \"string\") {\n return [];\n }\n\n var cookiesStrings = [];\n var pos = 0;\n var start;\n var ch;\n var lastComma;\n var nextStart;\n var cookiesSeparatorFound;\n\n function skipWhitespace() {\n while (pos < cookiesString.length && /\\s/.test(cookiesString.charAt(pos))) {\n pos += 1;\n }\n return pos < cookiesString.length;\n }\n\n function notSpecialChar() {\n ch = cookiesString.charAt(pos);\n\n return ch !== \"=\" && ch !== \";\" && ch !== \",\";\n }\n\n while (pos < cookiesString.length) {\n start = pos;\n cookiesSeparatorFound = false;\n\n while (skipWhitespace()) {\n ch = cookiesString.charAt(pos);\n if (ch === \",\") {\n // ',' is a cookie separator if we have later first '=', not ';' or ','\n lastComma = pos;\n pos += 1;\n\n skipWhitespace();\n nextStart = pos;\n\n while (pos < cookiesString.length && notSpecialChar()) {\n pos += 1;\n }\n\n // currently special character\n if (pos < cookiesString.length && cookiesString.charAt(pos) === \"=\") {\n // we found cookies separator\n cookiesSeparatorFound = true;\n // pos is inside the next cookie, so back up and return it.\n pos = nextStart;\n cookiesStrings.push(cookiesString.substring(start, lastComma));\n start = pos;\n } else {\n // in param ',' or param separator ';',\n // we continue from that comma\n pos = lastComma + 1;\n }\n } else {\n pos += 1;\n }\n }\n\n if (!cookiesSeparatorFound || pos >= cookiesString.length) {\n cookiesStrings.push(cookiesString.substring(start, cookiesString.length));\n }\n }\n\n return cookiesStrings;\n}\n\n// named export for CJS\nparseSetCookie.parseSetCookie = parseSetCookie;\n// for backwards compatibility\nparseSetCookie.parse = parseSetCookie;\nparseSetCookie.parseString = parseString;\nparseSetCookie.splitCookiesString = splitCookiesString;\n\n// EXPORTS\n// (this section is replaced by build-cjs.js)\n\n// named export for ESM\nexport { parseSetCookie };\n// for backwards compatibility\nexport default parseSetCookie;\nexport { parseSetCookie as parse, parseString, splitCookiesString };\n","import { parse as parseSetCookie, splitCookiesString } from 'set-cookie-parser';\nimport { originOf } from './url.js';\n\n/** Cookie, сохранённая в jar. */\ninterface StoredCookie {\n name: string;\n value: string;\n path: string;\n /** Момент истечения, мс с начала эпохи. `undefined` — сессионная cookie. */\n expires: number | undefined;\n secure: boolean;\n}\n\n/** Имя cookie-флага «есть refresh-сессия». Ставится сайтом итд.com рядом с refresh-токеном. */\nexport const AUTH_FLAG_COOKIE = 'is_auth';\n\n/**\n * Имя cookie с refresh-токеном.\n *\n * `POST /api/v1/auth/refresh` читает токен **только отсюда** — тело запроса сервер игнорирует.\n * Cookie помечена `HttpOnly`, поэтому в браузере её не прочитать и не выставить; вне браузера\n * она проходит через jar, и туда же кладётся токен, переданный строкой.\n */\nexport const REFRESH_COOKIE = 'refresh_token';\n\n/** Путь, которым сервер ограничивает {@link REFRESH_COOKIE}. */\nexport const REFRESH_COOKIE_PATH = '/api/v1/auth';\n\n/** Разделитель origin и содержимого cookie при сериализации. В origin пробелов не бывает. */\nconst SERIALIZED_SEPARATOR = ' ';\n\n/** Дата в миллисекунды. Некорректная дата считается отсутствующей, а не `NaN`. */\nfunction toTimestamp(date: Date | undefined): number | undefined {\n if (!date) return undefined;\n const time = date.getTime();\n return Number.isFinite(time) ? time : undefined;\n}\n\nfunction pathOf(url: string): string {\n try {\n return new URL(url).pathname || '/';\n } catch {\n return '/';\n }\n}\n\n/**\n * Подходит ли путь cookie запросу.\n *\n * Правило из RFC 6265: путь cookie должен быть префиксом пути запроса, причём граница\n * должна проходить по сегменту — `/api` не подходит запросу `/apiary`.\n */\nfunction pathMatches(cookiePath: string, requestPath: string): boolean {\n if (cookiePath === '/' || cookiePath === requestPath) return true;\n if (!requestPath.startsWith(cookiePath)) return false;\n return cookiePath.endsWith('/') || requestPath[cookiePath.length] === '/';\n}\n\n/**\n * Минимальное хранилище cookie для сред без своего.\n *\n * Refresh-токен итд.com приходит в `Set-Cookie`, а `fetch` вне браузера cookie не хранит —\n * без jar сессию не продлить. В браузере и React Native не используется: там cookie ведёт\n * сама среда.\n *\n * Реализована практическая часть RFC 6265: origin, путь, срок жизни, флаг `Secure`.\n * Доменные cookie для поддоменов намеренно не поддерживаются — API работает с одного хоста.\n */\nexport class CookieJar {\n readonly #byOrigin = new Map<string, Map<string, StoredCookie>>();\n\n /**\n * Забирает `Set-Cookie` из ответа.\n *\n * Использует `Headers.getSetCookie()`, где он есть (Node 20+, undici). В остальных средах\n * заголовки склеены в одну строку, и её нельзя резать по запятой напрямую: запятая есть\n * внутри `Expires=Wed, 09 Jun 2027 …`. Разделением занимается `set-cookie-parser`.\n */\n setFromResponse(url: string, response: Response): void {\n const headers = response.headers as Headers & { getSetCookie?: () => string[] };\n\n const raw =\n typeof headers.getSetCookie === 'function'\n ? headers.getSetCookie()\n : splitCookiesString(headers.get('set-cookie') ?? '');\n\n if (raw.length > 0) this.setFromStrings(url, raw);\n }\n\n /**\n * Кладёт cookie напрямую, минуя `Set-Cookie`.\n *\n * Нужно ровно в одном случае: пользователь передал refresh-токен строкой, а сервер читает\n * его только из cookie. Значение не кодируется — оно уходит в заголовок как есть.\n */\n set(url: string, name: string, value: string, path = '/'): void {\n this.setFromStrings(url, [`${name}=${value}; Path=${path}`]);\n }\n\n /** Сохраняет cookie из готовых строк `Set-Cookie`. */\n setFromStrings(url: string, setCookieStrings: string[]): void {\n const origin = originOf(url);\n if (!origin) return;\n\n const jar = this.#byOrigin.get(origin) ?? new Map<string, StoredCookie>();\n\n for (const parsed of parseSetCookie(setCookieStrings)) {\n // Битую дату (её легко получить, если заголовки склеены неправильно) считаем\n // отсутствующей: сессионная cookie лучше, чем cookie со сроком NaN, которая\n // не истекает и одновременно нигде не находится.\n const expires = toTimestamp(parsed.expires);\n const maxAgeExpires =\n typeof parsed.maxAge === 'number' && Number.isFinite(parsed.maxAge)\n ? Date.now() + parsed.maxAge * 1000\n : undefined;\n\n // Max-Age приоритетнее Expires — так требует RFC 6265.\n const expiresAt = maxAgeExpires ?? expires;\n\n // Удаление cookie сервером: срок в прошлом либо Max-Age=0.\n if (expiresAt !== undefined && expiresAt <= Date.now()) {\n jar.delete(parsed.name);\n continue;\n }\n\n jar.set(parsed.name, {\n name: parsed.name,\n value: parsed.value,\n path: parsed.path ?? '/',\n expires: expiresAt,\n secure: parsed.secure ?? false,\n });\n }\n\n this.#byOrigin.set(origin, jar);\n }\n\n /**\n * Собирает значение заголовка `Cookie` для запроса.\n *\n * @returns строка вида `a=1; b=2` либо `undefined`, если подходящих cookie нет\n */\n getHeader(url: string): string | undefined {\n const cookies = this.#matching(url);\n if (cookies.length === 0) return undefined;\n return cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join('; ');\n }\n\n /**\n * Значение действующей cookie.\n *\n * Нужно, чтобы забрать обновлённый refresh-токен: сервер ротирует его при каждом\n * продлении сессии, и сохранять надо именно новое значение.\n *\n * @param url если указан, учитываются origin, путь и флаг `Secure`\n */\n getValue(name: string, url?: string): string | undefined {\n if (url) return this.#matching(url).find((cookie) => cookie.name === name)?.value;\n\n const now = Date.now();\n for (const jar of this.#byOrigin.values()) {\n const cookie = jar.get(name);\n if (cookie && (cookie.expires === undefined || cookie.expires > now)) return cookie.value;\n }\n return undefined;\n }\n\n /**\n * Есть ли действующая cookie с таким именем.\n *\n * Используется для проверки флага {@link AUTH_FLAG_COOKIE} перед запросом обновления\n * токена: у анонима refresh-сессии нет, и дёргать API незачем.\n */\n has(name: string, url?: string): boolean {\n if (url) return this.#matching(url).some((cookie) => cookie.name === name);\n\n const now = Date.now();\n for (const jar of this.#byOrigin.values()) {\n const cookie = jar.get(name);\n if (cookie && (cookie.expires === undefined || cookie.expires > now)) return true;\n }\n return false;\n }\n\n /** Сохраняет содержимое jar для записи в {@link TokenStorage}. */\n serialize(): string[] {\n const result: string[] = [];\n const now = Date.now();\n\n for (const [origin, jar] of this.#byOrigin) {\n for (const cookie of jar.values()) {\n if (cookie.expires !== undefined && cookie.expires <= now) continue;\n\n const parts = [`${cookie.name}=${cookie.value}`, `Path=${cookie.path}`];\n if (cookie.expires !== undefined) {\n parts.push(`Expires=${new Date(cookie.expires).toUTCString()}`);\n }\n if (cookie.secure) parts.push('Secure');\n\n result.push(`${origin}${SERIALIZED_SEPARATOR}${parts.join('; ')}`);\n }\n }\n\n return result;\n }\n\n /** Восстанавливает jar из результата {@link serialize}. Некорректные записи молча пропускаются. */\n deserialize(entries: readonly string[] | undefined): void {\n if (!entries) return;\n\n for (const entry of entries) {\n const separatorAt = entry.indexOf(SERIALIZED_SEPARATOR);\n if (separatorAt <= 0) continue;\n\n const origin = entry.slice(0, separatorAt);\n const setCookie = entry.slice(separatorAt + 1);\n if (!originOf(origin)) continue;\n\n this.setFromStrings(origin, [setCookie]);\n }\n }\n\n /** Удаляет все cookie. */\n clear(): void {\n this.#byOrigin.clear();\n }\n\n /** Действующие cookie, подходящие запросу: тот же origin, подходящий путь, не истёкшие. */\n #matching(url: string): StoredCookie[] {\n const origin = originOf(url);\n const jar = this.#byOrigin.get(origin);\n if (!jar) return [];\n\n const isSecureRequest = origin.startsWith('https:');\n const requestPath = pathOf(url);\n const now = Date.now();\n const result: StoredCookie[] = [];\n\n for (const cookie of jar.values()) {\n if (cookie.expires !== undefined && cookie.expires <= now) {\n jar.delete(cookie.name);\n continue;\n }\n if (cookie.secure && !isSecureRequest) continue;\n if (!pathMatches(cookie.path, requestPath)) continue;\n\n result.push(cookie);\n }\n\n return result;\n }\n}\n"],"x_google_ignoreList":[1],"mappings":"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"}
|