itd-api 0.3.0 → 0.5.0
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 +3 -2
- package/dist/index.cjs +3329 -1795
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1640 -1092
- package/dist/index.d.ts +1640 -1092
- package/dist/index.js +3249 -1730
- package/dist/index.js.map +1 -1
- package/dist/{multi-storage-CyMe404l.js → multi-storage-B0r0AInH.js} +151 -281
- package/dist/multi-storage-B0r0AInH.js.map +1 -0
- package/dist/{multi-storage-BhcA2Izn.d.ts → multi-storage-CLqmzq07.d.ts} +14 -54
- package/dist/{multi-storage-D1keK2Op.cjs → multi-storage-mBuCOVZY.cjs} +188 -294
- package/dist/multi-storage-mBuCOVZY.cjs.map +1 -0
- package/dist/{multi-storage-NDqzRQcD.d.cts → multi-storage-s_PcWPGH.d.cts} +14 -54
- package/dist/node.cjs +62 -53
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +19 -5
- package/dist/node.d.ts +19 -5
- package/dist/node.js +62 -54
- package/dist/node.js.map +1 -1
- package/dist/{storage-D9tfHx7Z.js → storage-DWrK3Z4M.js} +201 -18
- package/dist/storage-DWrK3Z4M.js.map +1 -0
- package/dist/storage-Doe3lpFQ.d.cts +152 -0
- package/dist/storage-Doe3lpFQ.d.ts +152 -0
- package/dist/{storage-ycBqLBRB.cjs → storage-dF8Tio5y.cjs} +254 -17
- package/dist/storage-dF8Tio5y.cjs.map +1 -0
- package/dist/web.cjs +143 -40
- package/dist/web.cjs.map +1 -1
- package/dist/web.d.cts +40 -3
- package/dist/web.d.ts +40 -3
- package/dist/web.js +141 -41
- package/dist/web.js.map +1 -1
- package/package.json +3 -3
- package/dist/multi-storage-CyMe404l.js.map +0 -1
- package/dist/multi-storage-D1keK2Op.cjs.map +0 -1
- package/dist/runtime-CFEsf-jD.cjs +0 -185
- package/dist/runtime-CFEsf-jD.cjs.map +0 -1
- package/dist/runtime-DHxDn8gf.js +0 -126
- package/dist/runtime-DHxDn8gf.js.map +0 -1
- package/dist/storage-BjNRlkbE.d.cts +0 -82
- package/dist/storage-BjNRlkbE.d.ts +0 -82
- package/dist/storage-D9tfHx7Z.js.map +0 -1
- package/dist/storage-ycBqLBRB.cjs.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_storage = require("./storage-
|
|
1
|
+
const require_storage = require("./storage-dF8Tio5y.cjs");
|
|
2
2
|
//#region node_modules/set-cookie-parser/lib/set-cookie.js
|
|
3
3
|
var defaultParseOptions = {
|
|
4
4
|
decodeValues: true,
|
|
@@ -139,6 +139,102 @@ parseSetCookie.parse = parseSetCookie;
|
|
|
139
139
|
parseSetCookie.parseString = parseString;
|
|
140
140
|
parseSetCookie.splitCookiesString = splitCookiesString;
|
|
141
141
|
//#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 require_storage.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 require_storage.ItdConfigError(`baseUrl должен быть непустой строкой с абсолютным URL, получено: ${JSON.stringify(baseUrl)}`);
|
|
227
|
+
let parsed;
|
|
228
|
+
try {
|
|
229
|
+
parsed = new URL(baseUrl);
|
|
230
|
+
} catch {
|
|
231
|
+
throw new require_storage.ItdConfigError(`baseUrl должен быть абсолютным URL, получено: ${JSON.stringify(baseUrl)}`);
|
|
232
|
+
}
|
|
233
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new require_storage.ItdConfigError(`baseUrl должен использовать http или https, получено: ${parsed.protocol}`);
|
|
234
|
+
if (parsed.username || parsed.password || parsed.search || parsed.hash) throw new require_storage.ItdConfigError("baseUrl не должен содержать логин, пароль, query-параметры или fragment");
|
|
235
|
+
return parsed.origin + (parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/$/, ""));
|
|
236
|
+
}
|
|
237
|
+
//#endregion
|
|
142
238
|
//#region src/core/cookies.ts
|
|
143
239
|
/** Имя cookie-флага «есть refresh-сессия». Ставится сайтом итд.com рядом с refresh-токеном. */
|
|
144
240
|
const AUTH_FLAG_COOKIE = "is_auth";
|
|
@@ -154,13 +250,6 @@ const REFRESH_COOKIE = "refresh_token";
|
|
|
154
250
|
const REFRESH_COOKIE_PATH = "/api/v1/auth";
|
|
155
251
|
/** Разделитель origin и содержимого cookie при сериализации. В origin пробелов не бывает. */
|
|
156
252
|
const SERIALIZED_SEPARATOR = " ";
|
|
157
|
-
function originOf(url) {
|
|
158
|
-
try {
|
|
159
|
-
return new URL(url).origin;
|
|
160
|
-
} catch {
|
|
161
|
-
return "";
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
253
|
/** Дата в миллисекунды. Некорректная дата считается отсутствующей, а не `NaN`. */
|
|
165
254
|
function toTimestamp(date) {
|
|
166
255
|
if (!date) return void 0;
|
|
@@ -332,7 +421,7 @@ var CookieJar = class {
|
|
|
332
421
|
}
|
|
333
422
|
};
|
|
334
423
|
//#endregion
|
|
335
|
-
//#region src/core/attachments.ts
|
|
424
|
+
//#region src/core/attachments/contracts.ts
|
|
336
425
|
/** Способ передачи содержимого вложения. */
|
|
337
426
|
const FileTransferMode = Object.freeze({
|
|
338
427
|
/** Сначала получить файл целиком, затем отправить его как `Blob`. */
|
|
@@ -341,112 +430,36 @@ const FileTransferMode = Object.freeze({
|
|
|
341
430
|
Stream: "stream"
|
|
342
431
|
});
|
|
343
432
|
/** Размер очереди потокового вложения по умолчанию — 4 МиБ. */
|
|
344
|
-
const DEFAULT_FILE_STREAM_BUFFER_BYTES =
|
|
433
|
+
const DEFAULT_FILE_STREAM_BUFFER_BYTES = 4194304;
|
|
345
434
|
/** Предел размера файла, скачиваемого по адресу, — 100 МиБ. */
|
|
346
|
-
const DEFAULT_URL_FILE_MAX_BYTES =
|
|
347
|
-
|
|
348
|
-
|
|
435
|
+
const DEFAULT_URL_FILE_MAX_BYTES = 104857600;
|
|
436
|
+
//#endregion
|
|
437
|
+
//#region src/core/attachments/limits.ts
|
|
349
438
|
/** Проверяет числовую границу до обращения к источнику. */
|
|
350
439
|
function optionalBytes(value, name) {
|
|
351
440
|
if (value === void 0) return void 0;
|
|
352
441
|
if (!Number.isFinite(value) || value < 0 || !Number.isInteger(value)) throw new require_storage.ItdConfigError(`${name} должен быть неотрицательным целым числом, получено: ${value}`);
|
|
353
442
|
return value;
|
|
354
443
|
}
|
|
355
|
-
/**
|
|
356
|
-
function
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
if (mode === FileTransferMode.Stream && typeof ReadableStream === "undefined") throw new require_storage.ItdConfigError("эта среда не поддерживает ReadableStream; используйте mode: 'buffer'");
|
|
360
|
-
const maxBytes = optionalBytes(options.maxBytes ?? defaultMaxBytes, "maxBytes");
|
|
361
|
-
const streamBufferBytes = optionalBytes(options.streamBufferBytes ?? 4194304, "streamBufferBytes") ?? 4194304;
|
|
362
|
-
if (streamBufferBytes === 0) throw new require_storage.ItdConfigError("streamBufferBytes должен быть больше нуля");
|
|
363
|
-
return {
|
|
364
|
-
mode,
|
|
365
|
-
maxBytes,
|
|
366
|
-
streamBufferBytes
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
/** Убирает параметры MIME и приводит его к форме для сравнения. */
|
|
370
|
-
function normalizeMimeType(contentType) {
|
|
371
|
-
return contentType?.split(";", 1)[0]?.trim().toLowerCase() || void 0;
|
|
372
|
-
}
|
|
373
|
-
/** Достаёт имя файла из пути URL. */
|
|
374
|
-
function filenameFromUrl(url) {
|
|
375
|
-
const last = url.pathname.split("/").pop();
|
|
376
|
-
if (!last) return void 0;
|
|
377
|
-
try {
|
|
378
|
-
return decodeURIComponent(last) || void 0;
|
|
379
|
-
} catch {
|
|
380
|
-
return last;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
/** Создаёт ошибку превышения размера. */
|
|
384
|
-
function tooLarge(url, limit, actual) {
|
|
385
|
-
return new require_storage.ItdFileError(`файл${url ? ` по адресу ${url}` : ""} больше предела в ${limit} байт: ${actual}`, {
|
|
444
|
+
/** Создаёт типизированную ошибку превышения размера вложения. */
|
|
445
|
+
function fileTooLarge(url, limit, actual) {
|
|
446
|
+
const source = url ? ` по адресу ${url}` : "";
|
|
447
|
+
return new require_storage.ItdFileError(`файл${source} больше предела в ${limit} байт: ${actual}`, {
|
|
386
448
|
reason: require_storage.ItdFileErrorReason.TooLarge,
|
|
387
449
|
...url ? { url } : {},
|
|
388
450
|
limit,
|
|
389
451
|
actual
|
|
390
452
|
});
|
|
391
453
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
const size = Number(header);
|
|
397
|
-
if (!Number.isFinite(size) || size < 0 || !Number.isInteger(size)) return void 0;
|
|
398
|
-
if (maxBytes !== void 0 && size > maxBytes) throw tooLarge(url, maxBytes, size);
|
|
399
|
-
return size;
|
|
400
|
-
}
|
|
401
|
-
/** Получает HTTP-ответ источника и проверяет его статус. */
|
|
402
|
-
async function fetchFile(target, options, context) {
|
|
403
|
-
let requested;
|
|
404
|
-
try {
|
|
405
|
-
requested = new URL(target);
|
|
406
|
-
} catch {
|
|
407
|
-
throw new require_storage.ItdConfigError(`«${target}» не разбирается как адрес`);
|
|
408
|
-
}
|
|
409
|
-
if (requested.protocol !== "http:" && requested.protocol !== "https:") throw new require_storage.ItdConfigError(`вложение по адресу поддерживает только http и https, получено: ${requested.protocol}`);
|
|
410
|
-
let response;
|
|
411
|
-
try {
|
|
412
|
-
response = await context.fetch(requested, { ...context.signal ? { signal: context.signal } : {} });
|
|
413
|
-
} catch (error) {
|
|
414
|
-
if (context.signal?.aborted || error instanceof Error && error.name === "AbortError") throw error;
|
|
415
|
-
throw new require_storage.ItdFileError(`не удалось получить файл по адресу ${requested.href}`, {
|
|
416
|
-
reason: require_storage.ItdFileErrorReason.Network,
|
|
417
|
-
url: requested.href,
|
|
418
|
-
retryable: true,
|
|
419
|
-
cause: error
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
const finalUrl = response.url ? new URL(response.url) : requested;
|
|
423
|
-
if (!response.ok) {
|
|
424
|
-
await response.body?.cancel().catch(() => {});
|
|
425
|
-
throw new require_storage.ItdFileError(`источник ${finalUrl.href} ответил статусом ${response.status}`, {
|
|
426
|
-
reason: require_storage.ItdFileErrorReason.Http,
|
|
427
|
-
url: finalUrl.href,
|
|
428
|
-
status: response.status,
|
|
429
|
-
retryable: response.status === 408 || response.status === 429 || response.status >= 500
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
const { maxBytes } = resolveFileStreamOptions(options, DEFAULT_URL_FILE_MAX_BYTES);
|
|
433
|
-
let size;
|
|
434
|
-
try {
|
|
435
|
-
size = declaredSize(response, maxBytes, finalUrl.href);
|
|
436
|
-
} catch (error) {
|
|
437
|
-
await response.body?.cancel().catch(() => {});
|
|
438
|
-
throw error;
|
|
439
|
-
}
|
|
440
|
-
return {
|
|
441
|
-
response,
|
|
442
|
-
url: finalUrl,
|
|
443
|
-
size
|
|
444
|
-
};
|
|
445
|
-
}
|
|
454
|
+
//#endregion
|
|
455
|
+
//#region src/core/attachments/bounded-stream.ts
|
|
456
|
+
/** Потоки, на которые уже наложены счётчик размера и backpressure. */
|
|
457
|
+
const BOUNDED_FILE_STREAMS = /* @__PURE__ */ new WeakSet();
|
|
446
458
|
/** Приводит произвольный чанк потока к байтам. */
|
|
447
459
|
function asBytes(value) {
|
|
448
460
|
return value instanceof Uint8Array ? value : new Uint8Array(value);
|
|
449
461
|
}
|
|
462
|
+
/** Проверяет значение без обращения к методам потенциально чужого объекта. */
|
|
450
463
|
function isReadableByteStream(value) {
|
|
451
464
|
return typeof ReadableStream !== "undefined" && value instanceof ReadableStream;
|
|
452
465
|
}
|
|
@@ -496,7 +509,7 @@ function boundedFileStream(source, options = {}) {
|
|
|
496
509
|
finished = true;
|
|
497
510
|
await reader.cancel().catch(() => {});
|
|
498
511
|
cleanup();
|
|
499
|
-
controller.error(
|
|
512
|
+
controller.error(fileTooLarge(options.url, maxBytes, total));
|
|
500
513
|
return;
|
|
501
514
|
}
|
|
502
515
|
controller.enqueue(chunk);
|
|
@@ -531,119 +544,22 @@ function boundedFileStream(source, options = {}) {
|
|
|
531
544
|
function isBoundedFileStream(stream) {
|
|
532
545
|
return BOUNDED_FILE_STREAMS.has(stream);
|
|
533
546
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
const
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
streamBufferBytes,
|
|
545
|
-
...signal ? { signal } : {},
|
|
546
|
-
url,
|
|
547
|
-
retryableRead: true
|
|
548
|
-
}).getReader();
|
|
549
|
-
try {
|
|
550
|
-
for (;;) {
|
|
551
|
-
const next = await reader.read();
|
|
552
|
-
if (next.done) break;
|
|
553
|
-
chunks.push(next.value);
|
|
554
|
-
}
|
|
555
|
-
} finally {
|
|
556
|
-
reader.releaseLock();
|
|
557
|
-
}
|
|
558
|
-
return new Blob(chunks.map((chunk) => Uint8Array.from(chunk).buffer));
|
|
559
|
-
}
|
|
560
|
-
/** Скачивает файл целиком с ограничением размера. @internal */
|
|
561
|
-
async function downloadFile(target, options, context) {
|
|
562
|
-
const resolved = resolveFileStreamOptions(options, DEFAULT_URL_FILE_MAX_BYTES);
|
|
563
|
-
const { response, url } = await fetchFile(target, options, context);
|
|
564
|
-
const blob = await responseBlob(response, url.href, resolved.maxBytes, resolved.streamBufferBytes, context.signal);
|
|
565
|
-
const contentType = normalizeMimeType(options.contentType ?? response.headers.get("content-type") ?? void 0);
|
|
566
|
-
const filename = options.filename ?? filenameFromUrl(url);
|
|
567
|
-
return {
|
|
568
|
-
file: new Blob([blob], { type: contentType ?? "" }),
|
|
569
|
-
...filename ? { filename } : {},
|
|
570
|
-
...contentType ? { contentType } : {}
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
/** Открывает HTTP-ответ как ограниченный поток. @internal */
|
|
574
|
-
async function openUrlFile(target, options, context) {
|
|
575
|
-
const resolved = resolveFileStreamOptions(options, DEFAULT_URL_FILE_MAX_BYTES);
|
|
576
|
-
const { response, url, size } = await fetchFile(target, options, context);
|
|
577
|
-
if (!response.body) throw new require_storage.ItdFileError(`источник ${url.href} не предоставил потоковое тело`, {
|
|
578
|
-
reason: require_storage.ItdFileErrorReason.StreamUnavailable,
|
|
579
|
-
url: url.href
|
|
580
|
-
});
|
|
581
|
-
const stream = boundedFileStream(response.body, {
|
|
582
|
-
...resolved.maxBytes !== void 0 ? { maxBytes: resolved.maxBytes } : {},
|
|
583
|
-
streamBufferBytes: resolved.streamBufferBytes,
|
|
584
|
-
...context.signal ? { signal: context.signal } : {},
|
|
585
|
-
url: url.href,
|
|
586
|
-
retryableRead: true
|
|
587
|
-
});
|
|
588
|
-
const filename = options.filename ?? filenameFromUrl(url);
|
|
589
|
-
const contentType = normalizeMimeType(options.contentType ?? response.headers.get("content-type") ?? void 0);
|
|
547
|
+
//#endregion
|
|
548
|
+
//#region src/core/attachments/options.ts
|
|
549
|
+
/** Проверяет и дополняет настройки потока. @internal */
|
|
550
|
+
function resolveFileStreamOptions(options, defaultMaxBytes) {
|
|
551
|
+
const mode = options.mode ?? FileTransferMode.Buffer;
|
|
552
|
+
if (mode !== FileTransferMode.Buffer && mode !== FileTransferMode.Stream) throw new require_storage.ItdConfigError(`mode вложения должен быть 'buffer' или 'stream', получено: ${mode}`);
|
|
553
|
+
if (mode === FileTransferMode.Stream && typeof ReadableStream === "undefined") throw new require_storage.ItdConfigError("эта среда не поддерживает ReadableStream; используйте mode: 'buffer'");
|
|
554
|
+
const maxBytes = optionalBytes(options.maxBytes ?? defaultMaxBytes, "maxBytes");
|
|
555
|
+
const streamBufferBytes = optionalBytes(options.streamBufferBytes ?? 4194304, "streamBufferBytes") ?? 4194304;
|
|
556
|
+
if (streamBufferBytes === 0) throw new require_storage.ItdConfigError("streamBufferBytes должен быть больше нуля");
|
|
590
557
|
return {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
...size !== void 0 ? { size } : {},
|
|
595
|
-
close: () => stream.cancel().catch(() => {})
|
|
558
|
+
mode,
|
|
559
|
+
maxBytes,
|
|
560
|
+
streamBufferBytes
|
|
596
561
|
};
|
|
597
562
|
}
|
|
598
|
-
function fromUrl(url, options = {}) {
|
|
599
|
-
if (resolveFileStreamOptions(options, 104857600).mode === FileTransferMode.Stream) return { open: (context) => openUrlFile(url, options, context) };
|
|
600
|
-
return { load: (context) => downloadFile(url, options, context) };
|
|
601
|
-
}
|
|
602
|
-
/**
|
|
603
|
-
* Создаёт повторяемый пользовательский поток.
|
|
604
|
-
*
|
|
605
|
-
* Фабрика вызывается заново для каждой попытки; возвращать один и тот же поток нельзя.
|
|
606
|
-
*/
|
|
607
|
-
function fromStream(factory, options = {}) {
|
|
608
|
-
const resolved = resolveFileStreamOptions({
|
|
609
|
-
...options,
|
|
610
|
-
mode: FileTransferMode.Stream
|
|
611
|
-
}, void 0);
|
|
612
|
-
optionalBytes(options.size, "size");
|
|
613
|
-
return { open: async (context) => {
|
|
614
|
-
let opened;
|
|
615
|
-
try {
|
|
616
|
-
opened = await factory(context);
|
|
617
|
-
} catch (error) {
|
|
618
|
-
if (error instanceof require_storage.ItdFileError || error instanceof require_storage.ItdConfigError || context.signal?.aborted) throw error;
|
|
619
|
-
throw new require_storage.ItdFileError("не удалось открыть поток вложения", {
|
|
620
|
-
reason: require_storage.ItdFileErrorReason.Read,
|
|
621
|
-
retryable: true,
|
|
622
|
-
cause: error
|
|
623
|
-
});
|
|
624
|
-
}
|
|
625
|
-
const content = isReadableByteStream(opened) ? { stream: opened } : opened;
|
|
626
|
-
if (!content || !isReadableByteStream(content.stream)) throw new require_storage.ItdConfigError("fromStream должен вернуть ReadableStream или { stream }");
|
|
627
|
-
const size = options.size ?? content.size;
|
|
628
|
-
optionalBytes(size, "size");
|
|
629
|
-
if (resolved.maxBytes !== void 0 && size !== void 0 && size > resolved.maxBytes) {
|
|
630
|
-
await content.close?.();
|
|
631
|
-
throw tooLarge(void 0, resolved.maxBytes, size);
|
|
632
|
-
}
|
|
633
|
-
return {
|
|
634
|
-
stream: boundedFileStream(content.stream, {
|
|
635
|
-
...resolved.maxBytes !== void 0 ? { maxBytes: resolved.maxBytes } : {},
|
|
636
|
-
streamBufferBytes: resolved.streamBufferBytes,
|
|
637
|
-
...context.signal ? { signal: context.signal } : {},
|
|
638
|
-
retryableRead: true
|
|
639
|
-
}),
|
|
640
|
-
...options.filename ?? content.filename ? { filename: options.filename ?? content.filename } : {},
|
|
641
|
-
...normalizeMimeType(options.contentType ?? content.contentType) ? { contentType: normalizeMimeType(options.contentType ?? content.contentType) } : {},
|
|
642
|
-
...size !== void 0 ? { size } : {},
|
|
643
|
-
...content.close ? { close: content.close } : {}
|
|
644
|
-
};
|
|
645
|
-
} };
|
|
646
|
-
}
|
|
647
563
|
//#endregion
|
|
648
564
|
//#region src/core/multi-storage.ts
|
|
649
565
|
/**
|
|
@@ -704,98 +620,52 @@ function controlledTokenStorage(storage, account) {
|
|
|
704
620
|
* из `itd-api/node` либо соберите своё через {@link createMultiTokenStorage}.
|
|
705
621
|
*/
|
|
706
622
|
var MemoryMultiTokenStorage = class {
|
|
707
|
-
#
|
|
623
|
+
#store = new require_storage.MemoryKeyValueStore();
|
|
708
624
|
constructor(initial) {
|
|
709
|
-
for (const [account, session] of Object.entries(initial ?? {})) this.#
|
|
625
|
+
for (const [account, session] of Object.entries(initial ?? {})) this.#store.set(accountKey(account), require_storage.copySession(session));
|
|
710
626
|
}
|
|
711
627
|
get(account) {
|
|
712
|
-
const session = this.#
|
|
628
|
+
const session = this.#store.get(accountKey(account));
|
|
713
629
|
return session ? require_storage.copySession(session) : null;
|
|
714
630
|
}
|
|
715
631
|
set(account, session) {
|
|
716
|
-
this.#
|
|
632
|
+
this.#store.set(accountKey(account), require_storage.copySession(session));
|
|
717
633
|
}
|
|
718
634
|
clear(account) {
|
|
719
|
-
this.#
|
|
635
|
+
this.#store.delete(accountKey(account));
|
|
720
636
|
}
|
|
721
637
|
accounts() {
|
|
722
|
-
return
|
|
638
|
+
return this.#store.keys(ACCOUNT_KEY_PREFIX).map(accountFromKey);
|
|
723
639
|
}
|
|
724
640
|
};
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
*/
|
|
729
|
-
function createMultiTokenStorage(handlers) {
|
|
730
|
-
return handlers;
|
|
731
|
-
}
|
|
732
|
-
/** Создаёт запись без прототипа: имена `__proto__` и `constructor` остаются обычными ключами. */
|
|
733
|
-
function emptySessionRecord() {
|
|
734
|
-
return Object.create(null);
|
|
641
|
+
const ACCOUNT_KEY_PREFIX = "accounts/";
|
|
642
|
+
function accountKey(account) {
|
|
643
|
+
return `${ACCOUNT_KEY_PREFIX}${encodeURIComponent(account)}`;
|
|
735
644
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
const normalized = emptySessionRecord();
|
|
739
|
-
for (const [account, session] of Object.entries(record ?? {})) normalized[account] = require_storage.copySession(session);
|
|
740
|
-
return normalized;
|
|
645
|
+
function accountFromKey(key) {
|
|
646
|
+
return decodeURIComponent(key.slice(9));
|
|
741
647
|
}
|
|
742
648
|
/**
|
|
743
|
-
*
|
|
649
|
+
* Создаёт доменное хранилище нескольких сессий поверх enumerable key-value backend.
|
|
744
650
|
*
|
|
745
|
-
*
|
|
746
|
-
*
|
|
747
|
-
* чужие сессии. Источник читается один раз, дальше слепок живёт в памяти, а записи
|
|
748
|
-
* выстраиваются в цепочку и идут по очереди.
|
|
749
|
-
*
|
|
750
|
-
* Внутри процесса этого достаточно. Несколько процессов, пишущих в одну запись,
|
|
751
|
-
* по-прежнему затирают друг друга — как и несколько экземпляров этого адаптера,
|
|
752
|
-
* направленных на один источник в одном процессе.
|
|
651
|
+
* Отдельный индекс аккаунтов не используется: `accounts()` перечисляет ключи backend. Поэтому
|
|
652
|
+
* запись сессии не может разойтись с индексом, но backend обязан эффективно поддерживать `keys`.
|
|
753
653
|
*/
|
|
754
|
-
function
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
let writing = Promise.resolve();
|
|
761
|
-
const load = async () => {
|
|
762
|
-
if (snapshot !== void 0) return snapshot;
|
|
763
|
-
loading ??= source.read().then((value) => {
|
|
764
|
-
snapshot = normalizeSessionRecord(value);
|
|
765
|
-
return snapshot;
|
|
766
|
-
}).finally(() => {
|
|
767
|
-
loading = null;
|
|
768
|
-
});
|
|
769
|
-
return loading;
|
|
770
|
-
};
|
|
771
|
-
const flush = () => {
|
|
772
|
-
const current = normalizeSessionRecord(snapshot ?? null);
|
|
773
|
-
const operation = async () => {
|
|
774
|
-
if (source.remove && Object.keys(current).length === 0) await source.remove();
|
|
775
|
-
else await source.write(current);
|
|
776
|
-
};
|
|
777
|
-
writing = writing.then(operation, operation);
|
|
778
|
-
return writing;
|
|
779
|
-
};
|
|
654
|
+
function createMultiTokenStorage(store, options = {}) {
|
|
655
|
+
const prefix = options.prefix ?? ACCOUNT_KEY_PREFIX;
|
|
656
|
+
if (typeof prefix !== "string") throw new require_storage.ItdConfigError("prefix MultiTokenStorage должен быть строкой");
|
|
657
|
+
const backend = require_storage.createKeyValueStore(store);
|
|
658
|
+
if (!require_storage.isEnumerableKeyValueStore(backend)) throw new require_storage.ItdConfigError("MultiTokenStorage требует KeyValueStore с методом keys()");
|
|
659
|
+
const key = (account) => `${prefix}${encodeURIComponent(account)}`;
|
|
780
660
|
return {
|
|
781
661
|
async get(account) {
|
|
782
|
-
const
|
|
783
|
-
const session = Object.hasOwn(current, account) ? current[account] : void 0;
|
|
662
|
+
const session = await backend.get(key(account));
|
|
784
663
|
return session ? require_storage.copySession(session) : null;
|
|
785
664
|
},
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
current[account] = require_storage.copySession(session);
|
|
789
|
-
await flush();
|
|
790
|
-
},
|
|
791
|
-
async clear(account) {
|
|
792
|
-
const current = await load();
|
|
793
|
-
if (!Object.hasOwn(current, account)) return;
|
|
794
|
-
delete current[account];
|
|
795
|
-
await flush();
|
|
796
|
-
},
|
|
665
|
+
set: (account, session) => backend.set(key(account), require_storage.copySession(session)),
|
|
666
|
+
clear: (account) => backend.delete(key(account)),
|
|
797
667
|
async accounts() {
|
|
798
|
-
return
|
|
668
|
+
return (await require_storage.collectKeyValueStoreKeys(backend, prefix)).map((value) => decodeURIComponent(value.slice(prefix.length)));
|
|
799
669
|
}
|
|
800
670
|
};
|
|
801
671
|
}
|
|
@@ -854,6 +724,12 @@ Object.defineProperty(exports, "boundedFileStream", {
|
|
|
854
724
|
return boundedFileStream;
|
|
855
725
|
}
|
|
856
726
|
});
|
|
727
|
+
Object.defineProperty(exports, "buildQuery", {
|
|
728
|
+
enumerable: true,
|
|
729
|
+
get: function() {
|
|
730
|
+
return buildQuery;
|
|
731
|
+
}
|
|
732
|
+
});
|
|
857
733
|
Object.defineProperty(exports, "controlledTokenStorage", {
|
|
858
734
|
enumerable: true,
|
|
859
735
|
get: function() {
|
|
@@ -866,34 +742,34 @@ Object.defineProperty(exports, "createMultiTokenStorage", {
|
|
|
866
742
|
return createMultiTokenStorage;
|
|
867
743
|
}
|
|
868
744
|
});
|
|
869
|
-
Object.defineProperty(exports, "
|
|
745
|
+
Object.defineProperty(exports, "encodePathSegment", {
|
|
870
746
|
enumerable: true,
|
|
871
747
|
get: function() {
|
|
872
|
-
return
|
|
748
|
+
return encodePathSegment;
|
|
873
749
|
}
|
|
874
750
|
});
|
|
875
|
-
Object.defineProperty(exports, "
|
|
751
|
+
Object.defineProperty(exports, "fileTooLarge", {
|
|
876
752
|
enumerable: true,
|
|
877
753
|
get: function() {
|
|
878
|
-
return
|
|
754
|
+
return fileTooLarge;
|
|
879
755
|
}
|
|
880
756
|
});
|
|
881
|
-
Object.defineProperty(exports, "
|
|
757
|
+
Object.defineProperty(exports, "hostOf", {
|
|
882
758
|
enumerable: true,
|
|
883
759
|
get: function() {
|
|
884
|
-
return
|
|
760
|
+
return hostOf;
|
|
885
761
|
}
|
|
886
762
|
});
|
|
887
|
-
Object.defineProperty(exports, "
|
|
763
|
+
Object.defineProperty(exports, "isBoundedFileStream", {
|
|
888
764
|
enumerable: true,
|
|
889
765
|
get: function() {
|
|
890
|
-
return
|
|
766
|
+
return isBoundedFileStream;
|
|
891
767
|
}
|
|
892
768
|
});
|
|
893
|
-
Object.defineProperty(exports, "
|
|
769
|
+
Object.defineProperty(exports, "isReadableByteStream", {
|
|
894
770
|
enumerable: true,
|
|
895
771
|
get: function() {
|
|
896
|
-
return
|
|
772
|
+
return isReadableByteStream;
|
|
897
773
|
}
|
|
898
774
|
});
|
|
899
775
|
Object.defineProperty(exports, "isRestorableSession", {
|
|
@@ -902,16 +778,34 @@ Object.defineProperty(exports, "isRestorableSession", {
|
|
|
902
778
|
return isRestorableSession;
|
|
903
779
|
}
|
|
904
780
|
});
|
|
905
|
-
Object.defineProperty(exports, "
|
|
781
|
+
Object.defineProperty(exports, "isSameSite", {
|
|
782
|
+
enumerable: true,
|
|
783
|
+
get: function() {
|
|
784
|
+
return isSameSite;
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
Object.defineProperty(exports, "joinUrl", {
|
|
788
|
+
enumerable: true,
|
|
789
|
+
get: function() {
|
|
790
|
+
return joinUrl;
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
Object.defineProperty(exports, "normalizeBaseUrl", {
|
|
794
|
+
enumerable: true,
|
|
795
|
+
get: function() {
|
|
796
|
+
return normalizeBaseUrl;
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
Object.defineProperty(exports, "optionalBytes", {
|
|
906
800
|
enumerable: true,
|
|
907
801
|
get: function() {
|
|
908
|
-
return
|
|
802
|
+
return optionalBytes;
|
|
909
803
|
}
|
|
910
804
|
});
|
|
911
|
-
Object.defineProperty(exports, "
|
|
805
|
+
Object.defineProperty(exports, "originOf", {
|
|
912
806
|
enumerable: true,
|
|
913
807
|
get: function() {
|
|
914
|
-
return
|
|
808
|
+
return originOf;
|
|
915
809
|
}
|
|
916
810
|
});
|
|
917
811
|
Object.defineProperty(exports, "resolveFileStreamOptions", {
|
|
@@ -927,4 +821,4 @@ Object.defineProperty(exports, "scopedTokenStorage", {
|
|
|
927
821
|
}
|
|
928
822
|
});
|
|
929
823
|
|
|
930
|
-
//# sourceMappingURL=multi-storage-
|
|
824
|
+
//# sourceMappingURL=multi-storage-mBuCOVZY.cjs.map
|