itd-api 0.10.3 → 0.11.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.
Files changed (79) hide show
  1. package/README.md +2 -1
  2. package/dist/events/index.cjs +27 -25
  3. package/dist/events/index.d.cts +3 -3
  4. package/dist/events/index.d.ts +3 -3
  5. package/dist/events/index.js +5 -5
  6. package/dist/index.cjs +136 -116
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +18 -7
  9. package/dist/index.d.ts +18 -7
  10. package/dist/index.js +46 -30
  11. package/dist/index.js.map +1 -1
  12. package/dist/node/index.cjs +13 -13
  13. package/dist/node/index.cjs.map +1 -1
  14. package/dist/node/index.js +5 -5
  15. package/dist/rest/index.cjs +43 -40
  16. package/dist/rest/index.cjs.map +1 -1
  17. package/dist/rest/index.d.cts +3 -3
  18. package/dist/rest/index.d.ts +3 -3
  19. package/dist/rest/index.js +6 -6
  20. package/dist/shared/{events-BVfqWyBw.cjs → events-C6tmj8aW.cjs} +63 -59
  21. package/dist/shared/events-C6tmj8aW.cjs.map +1 -0
  22. package/dist/shared/{events-Cp7ITueA.d.ts → events-CAb9G7Sf.d.ts} +2 -2
  23. package/dist/shared/{events-Cwj7UPAa.js → events-MWNcnfrq.js} +19 -15
  24. package/dist/shared/events-MWNcnfrq.js.map +1 -0
  25. package/dist/shared/{events-D0Wtt-If.d.cts → events-Sc75x6rT.d.cts} +2 -2
  26. package/dist/shared/{key-value-store-B5GYVEYZ.cjs → key-value-store-DYSPsqwq.cjs} +11 -14
  27. package/dist/shared/key-value-store-DYSPsqwq.cjs.map +1 -0
  28. package/dist/shared/{key-value-store-Bt2PSMxY.js → key-value-store-w1Dn5Pjb.js} +5 -8
  29. package/dist/shared/key-value-store-w1Dn5Pjb.js.map +1 -0
  30. package/dist/shared/{multi-storage-C3Ngtop1.js → multi-storage-BLBrGN9Q.js} +6 -6
  31. package/dist/shared/multi-storage-BLBrGN9Q.js.map +1 -0
  32. package/dist/shared/{multi-storage-s-fPtjoy.cjs → multi-storage-C7-Gz6Z7.cjs} +8 -8
  33. package/dist/shared/multi-storage-C7-Gz6Z7.cjs.map +1 -0
  34. package/dist/shared/{options-DjEtbVaf.cjs → options-5tywTYId.cjs} +13 -13
  35. package/dist/shared/{options-DjEtbVaf.cjs.map → options-5tywTYId.cjs.map} +1 -1
  36. package/dist/shared/{options-i4LrKfqx.js → options-BerKicCs.js} +3 -3
  37. package/dist/shared/{options-i4LrKfqx.js.map → options-BerKicCs.js.map} +1 -1
  38. package/dist/shared/{redact-DJV0XM3n.js → redact-C5oxIvcc.js} +79 -18
  39. package/dist/shared/redact-C5oxIvcc.js.map +1 -0
  40. package/dist/shared/{redact-lAmck1Z6.cjs → redact-DoC7lSsp.cjs} +109 -24
  41. package/dist/shared/redact-DoC7lSsp.cjs.map +1 -0
  42. package/dist/shared/{render-DRmuHoEy.d.ts → render-CBroJPcV.d.ts} +178 -252
  43. package/dist/shared/{render-CJO_L2CD.d.cts → render-DM7AhTZW.d.cts} +178 -252
  44. package/dist/shared/{render-BT74nC1l.js → render-DRh5_aRe.js} +128 -135
  45. package/dist/shared/render-DRh5_aRe.js.map +1 -0
  46. package/dist/shared/{render-CYJSDypQ.cjs → render-Dnc84B4M.cjs} +234 -241
  47. package/dist/shared/render-Dnc84B4M.cjs.map +1 -0
  48. package/dist/shared/{storage-DiDe0PZM.js → storage-86wiHeXD.js} +3 -3
  49. package/dist/shared/{storage-DiDe0PZM.js.map → storage-86wiHeXD.js.map} +1 -1
  50. package/dist/shared/{storage-CZJZoaz9.cjs → storage-CIjejZDt.cjs} +4 -4
  51. package/dist/shared/{storage-CZJZoaz9.cjs.map → storage-CIjejZDt.cjs.map} +1 -1
  52. package/dist/shared/{url-BLnX-LYt.d.cts → url-BOi-_mJT.d.cts} +143 -79
  53. package/dist/shared/{url-BLnX-LYt.d.ts → url-BOi-_mJT.d.ts} +143 -79
  54. package/dist/shared/{url-DS0-lrl-.js → url-Bg61sk_Y.js} +741 -598
  55. package/dist/shared/url-Bg61sk_Y.js.map +1 -0
  56. package/dist/shared/{url-BnO1aWsd.cjs → url-DTgkCWv5.cjs} +848 -717
  57. package/dist/shared/url-DTgkCWv5.cjs.map +1 -0
  58. package/dist/shared/{errors-BmP3TKoW.cjs → validate-CFx2dISs.cjs} +84 -5
  59. package/dist/shared/validate-CFx2dISs.cjs.map +1 -0
  60. package/dist/shared/{errors-GI10kZxk.js → validate-D7_kq3g5.js} +55 -6
  61. package/dist/shared/validate-D7_kq3g5.js.map +1 -0
  62. package/dist/web/index.cjs +4 -4
  63. package/dist/web/index.cjs.map +1 -1
  64. package/dist/web/index.js +3 -3
  65. package/package.json +1 -1
  66. package/dist/shared/errors-BmP3TKoW.cjs.map +0 -1
  67. package/dist/shared/errors-GI10kZxk.js.map +0 -1
  68. package/dist/shared/events-BVfqWyBw.cjs.map +0 -1
  69. package/dist/shared/events-Cwj7UPAa.js.map +0 -1
  70. package/dist/shared/key-value-store-B5GYVEYZ.cjs.map +0 -1
  71. package/dist/shared/key-value-store-Bt2PSMxY.js.map +0 -1
  72. package/dist/shared/multi-storage-C3Ngtop1.js.map +0 -1
  73. package/dist/shared/multi-storage-s-fPtjoy.cjs.map +0 -1
  74. package/dist/shared/redact-DJV0XM3n.js.map +0 -1
  75. package/dist/shared/redact-lAmck1Z6.cjs.map +0 -1
  76. package/dist/shared/render-BT74nC1l.js.map +0 -1
  77. package/dist/shared/render-CYJSDypQ.cjs.map +0 -1
  78. package/dist/shared/url-BnO1aWsd.cjs.map +0 -1
  79. package/dist/shared/url-DS0-lrl-.js.map +0 -1
@@ -1,4 +1,63 @@
1
- import { a as ItdConfigError } from "./errors-GI10kZxk.js";
1
+ import { l as ItdConfigError, t as isObjectLike } from "./validate-D7_kq3g5.js";
2
+ //#region src/core/logger.ts
3
+ /** Уровень записи журнала. */
4
+ const LogLevel = Object.freeze({
5
+ Debug: "debug",
6
+ Info: "info",
7
+ Warn: "warn",
8
+ Error: "error"
9
+ });
10
+ /** Уровень встроенного логгера по умолчанию: `logger: true`. */
11
+ const DEFAULT_LOG_LEVEL = LogLevel.Info;
12
+ const LEVEL_RANK = {
13
+ debug: 0,
14
+ info: 1,
15
+ warn: 2,
16
+ error: 3
17
+ };
18
+ const LEVELS = Object.values(LogLevel);
19
+ function isLogLevel(value) {
20
+ return typeof value === "string" && LEVELS.includes(value);
21
+ }
22
+ /** Логгер поверх `console`, пропускающий записи с уровня `level` и выше. */
23
+ function consoleLogger(level = DEFAULT_LOG_LEVEL) {
24
+ const threshold = LEVEL_RANK[level];
25
+ const enabled = (method) => LEVEL_RANK[method] >= threshold;
26
+ const write = (method) => (message, ...args) => {
27
+ if (enabled(method)) console[method](`[itd-api] ${message}`, ...args);
28
+ };
29
+ return {
30
+ debug: write(LogLevel.Debug),
31
+ info: write(LogLevel.Info),
32
+ warn: write(LogLevel.Warn),
33
+ error: write(LogLevel.Error),
34
+ isLevelEnabled: enabled
35
+ };
36
+ }
37
+ /** Стоит ли готовить детали записи: логгер без `isLevelEnabled` считается пишущим всё. */
38
+ function isLevelEnabled(logger, level) {
39
+ return logger.isLevelEnabled?.(level) ?? true;
40
+ }
41
+ /**
42
+ * Логгер для записей, которым нельзя пропасть без настроенного логгера: ошибки чужих
43
+ * обработчиков и нечитаемые записи хранилища. Всё остальное библиотека без логгера не пишет.
44
+ */
45
+ const fallbackLogger = consoleLogger(LogLevel.Warn);
46
+ /**
47
+ * Приводит опцию `logger` к логгеру.
48
+ *
49
+ * @throws {ItdConfigError} при некорректном значении
50
+ */
51
+ function resolveLogger(logger) {
52
+ if (logger === void 0 || logger === false) return void 0;
53
+ if (logger === true) return consoleLogger();
54
+ if (isLogLevel(logger)) return consoleLogger(logger);
55
+ if (!isObjectLike(logger)) throw new ItdConfigError("logger должен быть boolean, уровнем LogLevel или объектом Logger");
56
+ for (const method of LEVELS) if (typeof logger[method] !== "function") throw new ItdConfigError(`logger.${method} должен быть функцией`);
57
+ if (logger.isLevelEnabled !== void 0 && typeof logger.isLevelEnabled !== "function") throw new ItdConfigError("logger.isLevelEnabled должен быть функцией");
58
+ return logger;
59
+ }
60
+ //#endregion
2
61
  //#region src/core/runtime.ts
3
62
  /**
4
63
  * Как библиотека обращается с cookie.
@@ -384,13 +443,6 @@ function toTimestamp(date) {
384
443
  const time = date.getTime();
385
444
  return Number.isFinite(time) ? time : void 0;
386
445
  }
387
- function pathOf(url) {
388
- try {
389
- return new URL(url).pathname || "/";
390
- } catch {
391
- return "/";
392
- }
393
- }
394
446
  /**
395
447
  * Подходит ли путь cookie запросу.
396
448
  *
@@ -417,14 +469,18 @@ var CookieJar = class CookieJar {
417
469
  /** Независимая копия для auth-flow, который ещё не получил право на commit. */
418
470
  clone() {
419
471
  const copy = new CookieJar();
420
- copy.deserialize(this.serialize());
472
+ copy.#adopt(this);
421
473
  return copy;
422
474
  }
423
475
  /** Атомарно с точки зрения синхронных читателей заменяет содержимое снимком другого jar. */
424
476
  replaceWith(source) {
425
- const entries = source.serialize();
426
- this.clear();
427
- this.deserialize(entries);
477
+ if (source === this) return;
478
+ this.#byOrigin.clear();
479
+ this.#adopt(source);
480
+ }
481
+ /** Копирует карты cookie другого jar; сами записи неизменяемы и делятся по ссылке. */
482
+ #adopt(source) {
483
+ for (const [origin, jar] of source.#byOrigin) this.#byOrigin.set(origin, new Map(jar));
428
484
  }
429
485
  /**
430
486
  * Забирает `Set-Cookie` из ответа.
@@ -540,11 +596,16 @@ var CookieJar = class CookieJar {
540
596
  }
541
597
  /** Действующие cookie, подходящие запросу: тот же origin, подходящий путь, не истёкшие. */
542
598
  #matching(url) {
543
- const origin = originOf(url);
544
- const jar = this.#byOrigin.get(origin);
599
+ let parsed;
600
+ try {
601
+ parsed = new URL(url);
602
+ } catch {
603
+ return [];
604
+ }
605
+ const jar = this.#byOrigin.get(parsed.origin);
545
606
  if (!jar) return [];
546
- const isSecureRequest = origin.startsWith("https:");
547
- const requestPath = pathOf(url);
607
+ const isSecureRequest = parsed.protocol === "https:";
608
+ const requestPath = parsed.pathname || "/";
548
609
  const now = Date.now();
549
610
  const result = [];
550
611
  for (const cookie of jar.values()) {
@@ -678,6 +739,6 @@ function redactBody(body) {
678
739
  return body;
679
740
  }
680
741
  //#endregion
681
- export { shouldSendCredentials as C, resolveFetch as S, supportsStreamingBody as T, originOf as _, redactUrl as a, isBlob as b, REFRESH_COOKIE as c, buildQuery as d, encodePathSegment as f, normalizeBaseUrl as g, joinUrl as h, redactHeaders as i, REFRESH_COOKIE_PATH as l, isSameSite as m, redactBody as n, AUTH_FLAG_COOKIE as o, hostOf as p, redactErrorCause as r, CookieJar as s, maskSecret as t, isSerializedCookieEntry as u, RuntimeMode as v, shouldUseCookieJar as w, isFile as x, createDeviceId as y };
742
+ export { shouldSendCredentials as C, fallbackLogger as D, LogLevel as E, isLevelEnabled as O, resolveFetch as S, supportsStreamingBody as T, originOf as _, redactUrl as a, isBlob as b, REFRESH_COOKIE as c, buildQuery as d, encodePathSegment as f, normalizeBaseUrl as g, joinUrl as h, redactHeaders as i, resolveLogger as k, REFRESH_COOKIE_PATH as l, isSameSite as m, redactBody as n, AUTH_FLAG_COOKIE as o, hostOf as p, redactErrorCause as r, CookieJar as s, maskSecret as t, isSerializedCookieEntry as u, RuntimeMode as v, shouldUseCookieJar as w, isFile as x, createDeviceId as y };
682
743
 
683
- //# sourceMappingURL=redact-DJV0XM3n.js.map
744
+ //# sourceMappingURL=redact-C5oxIvcc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact-C5oxIvcc.js","names":["#byOrigin","#adopt","#matching"],"sources":["../../src/core/logger.ts","../../src/core/runtime.ts","../../src/core/url.ts","../../node_modules/set-cookie-parser/lib/set-cookie.js","../../src/core/cookies.ts","../../src/core/redact.ts"],"sourcesContent":["import { ItdConfigError } from './errors.js';\nimport { isObjectLike } from './validate.js';\n\n/**\n * Логгер библиотеки. Совместим с `console`: к сообщению прилагается не больше одного\n * аргумента — объект с деталями либо ошибка.\n */\nexport interface Logger {\n debug(message: string, ...args: unknown[]): void;\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n /** Пишет ли логгер этот уровень; без метода считается, что пишет все. Есть у winston и pino. */\n isLevelEnabled?(level: LogLevel): boolean;\n}\n\n/** Уровень записи журнала. */\nexport const LogLevel = Object.freeze({\n Debug: 'debug',\n Info: 'info',\n Warn: 'warn',\n Error: 'error',\n} as const);\nexport type LogLevel = (typeof LogLevel)[keyof typeof LogLevel];\n\n/** Уровень встроенного логгера по умолчанию: `logger: true`. */\nconst DEFAULT_LOG_LEVEL: LogLevel = LogLevel.Info;\n\nconst LEVEL_RANK: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3 };\n\nconst LEVELS = Object.values(LogLevel);\n\nfunction isLogLevel(value: unknown): value is LogLevel {\n return typeof value === 'string' && (LEVELS as string[]).includes(value);\n}\n\n/** Логгер поверх `console`, пропускающий записи с уровня `level` и выше. */\nexport function consoleLogger(level: LogLevel = DEFAULT_LOG_LEVEL): Logger {\n const threshold = LEVEL_RANK[level];\n const enabled = (method: LogLevel): boolean => LEVEL_RANK[method] >= threshold;\n const write =\n (method: LogLevel) =>\n (message: string, ...args: unknown[]): void => {\n if (enabled(method)) console[method](`[itd-api] ${message}`, ...args);\n };\n\n return {\n debug: write(LogLevel.Debug),\n info: write(LogLevel.Info),\n warn: write(LogLevel.Warn),\n error: write(LogLevel.Error),\n isLevelEnabled: enabled,\n };\n}\n\n/** Стоит ли готовить детали записи: логгер без `isLevelEnabled` считается пишущим всё. */\nexport function isLevelEnabled(logger: Logger, level: LogLevel): boolean {\n return logger.isLevelEnabled?.(level) ?? true;\n}\n\n/**\n * Логгер для записей, которым нельзя пропасть без настроенного логгера: ошибки чужих\n * обработчиков и нечитаемые записи хранилища. Всё остальное библиотека без логгера не пишет.\n */\nexport const fallbackLogger: Logger = consoleLogger(LogLevel.Warn);\n\n/**\n * Приводит опцию `logger` к логгеру.\n *\n * @throws {ItdConfigError} при некорректном значении\n */\nexport function resolveLogger(logger: Logger | LogLevel | boolean | undefined): Logger | undefined {\n if (logger === undefined || logger === false) return undefined;\n if (logger === true) return consoleLogger();\n if (isLogLevel(logger)) return consoleLogger(logger);\n if (!isObjectLike(logger)) {\n throw new ItdConfigError('logger должен быть boolean, уровнем LogLevel или объектом Logger');\n }\n\n for (const method of LEVELS) {\n if (typeof logger[method] !== 'function') {\n throw new ItdConfigError(`logger.${method} должен быть функцией`);\n }\n }\n if (logger.isLevelEnabled !== undefined && typeof logger.isLevelEnabled !== 'function') {\n throw new ItdConfigError('logger.isLevelEnabled должен быть функцией');\n }\n return logger;\n}\n","import { ItdConfigError } from './errors.js';\n\n/**\n * Как библиотека обращается с cookie.\n *\n * - `browser` — cookie ведёт браузер, запросы уходят с `credentials: 'include'`;\n * - `server` — cookie ведёт встроенный jar, заголовок `Cookie` подставляется вручную;\n * - `auto` — определяется по среде исполнения (значение по умолчанию).\n */\nexport const RuntimeMode = Object.freeze({\n /** Определяется по среде исполнения. Значение по умолчанию. */\n Auto: 'auto',\n /** Cookie ведёт браузер, запросы уходят с `credentials: 'include'`. */\n Browser: 'browser',\n /** Cookie ведёт встроенный jar, заголовок `Cookie` подставляется вручную. */\n Server: 'server',\n} as const);\nexport type RuntimeMode = (typeof RuntimeMode)[keyof typeof RuntimeMode];\n\n/** Распознанная среда исполнения. */\nexport const DetectedRuntime = Object.freeze({\n Browser: 'browser',\n /** Есть `window`, но нет `document`; cookie ведёт нативный сетевой слой. */\n ReactNative: 'react-native',\n Server: 'server',\n} as const);\nexport type DetectedRuntime = (typeof DetectedRuntime)[keyof typeof DetectedRuntime];\n\n/**\n * Определяет среду исполнения.\n *\n * React Native выделен отдельно намеренно: там есть `window`, но нет `document`, а cookie\n * хранит нативный сетевой слой (`NSHTTPCookieStorage` / `OkHttp`). Свой jar в этой среде\n * привёл бы к отправке cookie дважды.\n */\nexport function detectRuntime(): DetectedRuntime {\n const nav = (globalThis as { navigator?: { product?: string } }).navigator;\n if (nav?.product === 'ReactNative') return DetectedRuntime.ReactNative;\n\n if (typeof document !== 'undefined') return DetectedRuntime.Browser;\n\n return DetectedRuntime.Server;\n}\n\n/**\n * Нужно ли вести собственный cookie-jar.\n *\n * Только в серверных средах: в браузере `Set-Cookie` из JS не читается и не нужен,\n * в React Native cookie ведёт нативный слой.\n */\nexport function shouldUseCookieJar(mode: RuntimeMode): boolean {\n if (mode === RuntimeMode.Browser) return false;\n if (mode === RuntimeMode.Server) return true;\n return detectRuntime() === DetectedRuntime.Server;\n}\n\n/**\n * Нужно ли отправлять запросы с `credentials: 'include'`.\n *\n * Только в браузере: именно так refresh-cookie попадает в запрос `POST /api/v1/auth/refresh`.\n * Требует настроенного CORS на стороне итд.com — если его нет, укажите свой прокси в `baseUrl`.\n */\nexport function shouldSendCredentials(mode: RuntimeMode): boolean {\n if (mode === RuntimeMode.Browser) return true;\n if (mode === RuntimeMode.Server) return false;\n return detectRuntime() === DetectedRuntime.Browser;\n}\n\n/**\n * Возвращает реализацию `fetch`.\n *\n * @param custom реализация из конфигурации клиента, если пользователь её передал\n * @throws {ItdConfigError} если `fetch` недоступен — например, на Node ниже 18\n */\nexport function resolveFetch(custom?: typeof fetch): typeof fetch {\n if (custom !== undefined) {\n if (typeof custom !== 'function') {\n throw new ItdConfigError('fetch должен быть функцией');\n }\n return custom;\n }\n\n if (typeof globalThis.fetch === 'function') {\n // Привязка к globalThis обязательна: в некоторых средах несвязанный fetch бросает\n // «Illegal invocation».\n return globalThis.fetch.bind(globalThis);\n }\n\n throw new ItdConfigError(\n 'В этой среде нет глобального fetch. Обновитесь до Node 18+ либо передайте свою ' +\n 'реализацию через опцию fetch.',\n );\n}\n\n/**\n * Проверки бинарных типов, безопасные в любой среде.\n *\n * Обычный `instanceof` здесь недостаточен: конструктора может не быть вовсе, и тогда\n * проверка падает с `ReferenceError`, а не возвращает `false`. `Blob` есть в Node с 18.0,\n * но `File` стал глобальным только в Node 20 — при заявленной поддержке Node 18 голый\n * `instanceof File` ронял бы любую загрузку.\n */\nexport function isBlob(value: unknown): value is Blob {\n return typeof Blob !== 'undefined' && value instanceof Blob;\n}\n\n/** @see {@link isBlob} */\nexport function isFile(value: unknown): value is File {\n return typeof File !== 'undefined' && value instanceof File;\n}\n\n/** Доступно ли потоковое чтение тела ответа — от этого зависит, сработает ли SSE. */\nexport function supportsStreamingBody(): boolean {\n return typeof ReadableStream !== 'undefined' && typeof TextDecoder !== 'undefined';\n}\n\n/**\n * Создаёт идентификатор устройства для заголовка `X-Device-Id` — UUID v4.\n *\n * `crypto.randomUUID` есть в Node 19+, браузерах и Deno, но отсутствует в Node 18 вне\n * защищённого контекста, поэтому предусмотрен запасной путь.\n */\nexport function createDeviceId(): string {\n const webCrypto = (globalThis as { crypto?: Crypto }).crypto;\n\n if (typeof webCrypto?.randomUUID === 'function') return webCrypto.randomUUID();\n\n // Форма UUID v4 собирается вручную: серверу важна стабильность значения, а не его энтропия.\n const bytes = new Uint8Array(16);\n if (typeof webCrypto?.getRandomValues === 'function') webCrypto.getRandomValues(bytes);\n else for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);\n\n // biome-ignore lint/style/noNonNullAssertion: длина массива фиксирована выше\n bytes[6] = (bytes[6]! & 0x0f) | 0x40;\n // biome-ignore lint/style/noNonNullAssertion: длина массива фиксирована выше\n bytes[8] = (bytes[8]! & 0x3f) | 0x80;\n\n const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');\n\n return [\n hex.slice(0, 8),\n hex.slice(8, 12),\n hex.slice(12, 16),\n hex.slice(16, 20),\n hex.slice(20, 32),\n ].join('-');\n}\n","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. Запись неизменяема: копии jar делят её по ссылке. */\ninterface StoredCookie {\n readonly name: string;\n readonly value: string;\n readonly path: string;\n /** Момент истечения, мс с начала эпохи. `undefined` — сессионная cookie. */\n readonly expires: number | undefined;\n readonly 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 = ' ';\nconst COOKIE_NAME = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/;\n\n/** Проверяет одну запись, созданную {@link CookieJar.serialize}. @internal */\nexport function isSerializedCookieEntry(entry: string): boolean {\n const separatorAt = entry.indexOf(SERIALIZED_SEPARATOR);\n if (separatorAt <= 0) return false;\n\n const origin = entry.slice(0, separatorAt);\n if (originOf(origin) !== origin) return false;\n\n const parsed = parseSetCookie([entry.slice(separatorAt + 1)]);\n return parsed.length === 1 && COOKIE_NAME.test(parsed[0]?.name ?? '');\n}\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\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 /** Независимая копия для auth-flow, который ещё не получил право на commit. */\n clone(): CookieJar {\n const copy = new CookieJar();\n copy.#adopt(this);\n return copy;\n }\n\n /** Атомарно с точки зрения синхронных читателей заменяет содержимое снимком другого jar. */\n replaceWith(source: CookieJar): void {\n if (source === this) return;\n this.#byOrigin.clear();\n this.#adopt(source);\n }\n\n /** Копирует карты cookie другого jar; сами записи неизменяемы и делятся по ссылке. */\n #adopt(source: CookieJar): void {\n for (const [origin, jar] of source.#byOrigin) this.#byOrigin.set(origin, new Map(jar));\n }\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 if (!isSerializedCookieEntry(entry)) continue;\n const separatorAt = entry.indexOf(SERIALIZED_SEPARATOR);\n\n const origin = entry.slice(0, separatorAt);\n const setCookie = entry.slice(separatorAt + 1);\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 let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return [];\n }\n const jar = this.#byOrigin.get(parsed.origin);\n if (!jar) return [];\n\n const isSecureRequest = parsed.protocol === 'https:';\n const requestPath = parsed.pathname || '/';\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","import { isBlob } from './runtime.js';\n\n/** Заголовки, значение которых нельзя писать в лог целиком. */\nconst SECRET_HEADERS = new Set([\n 'authorization',\n 'cookie',\n 'set-cookie',\n 'x-api-key',\n 'x-order-token',\n]);\n\n/** Поля тела запроса, значение которых нельзя писать в лог. */\nconst SECRET_FIELDS = new Set([\n 'password',\n 'oldpassword',\n 'newpassword',\n 'accesstoken',\n 'refreshtoken',\n 'currentpassword',\n 'flowtoken',\n 'token',\n 'turnstiletoken',\n 'otp',\n 'pass',\n 'email',\n 'phone',\n 'address',\n 'orderaccesstoken',\n]);\n\n/** Query-параметры, содержащие секреты. */\nconst SECRET_QUERY_PARAMS = new Set(['token', 'access_token', 'c']);\n\n/**\n * Прячет середину секрета, оставляя концы для сопоставления.\n *\n * @example\n * ```ts\n * maskSecret('eyJhbGciOiJIUzI1NiJ9.abc'); // 'eyJh…(24)…abc'\n * ```\n */\nexport function maskSecret(value: string): string {\n if (value.length <= 8) return '…';\n return `${value.slice(0, 4)}…(${value.length})…${value.slice(-3)}`;\n}\n\n/** Маскирует секретные query-параметры URL перед записью в логи и ошибки. */\nexport function redactUrl(url: string): string {\n try {\n const parsed = new URL(url);\n const entries = [...parsed.searchParams.entries()];\n parsed.search = '';\n for (const [name, value] of entries) {\n parsed.searchParams.append(\n name,\n SECRET_QUERY_PARAMS.has(name.toLowerCase()) ? maskSecret(value) : value,\n );\n }\n return parsed.toString();\n } catch {\n return url.replace(/([?&](?:token|access_token|c)=)[^&#]*/gi, '$1[скрыто]');\n }\n}\n\n/** Маскирует подписанные адреса внутри произвольного диагностического текста. */\nexport function redactUrlsInText(text: string): string {\n return text.replace(/https?:\\/\\/[^\\s\"'<>]+/gi, (url) => redactUrl(url));\n}\n\n/**\n * Создаёт безопасную причину ошибки для публичной цепочки `cause`.\n *\n * Чужие объекты не сохраняются: их поля и пользовательские методы нельзя надёжно\n * сериализовать без риска вывести секрет. Для Error и строк остаются тип и сообщение,\n * но все встреченные в сообщении HTTP(S)-адреса проходят маскирование.\n */\nexport function redactErrorCause(error: unknown): Error | undefined {\n const message =\n error instanceof Error ? error.message : typeof error === 'string' ? error : undefined;\n if (message === undefined) return undefined;\n\n const safeMessage = redactUrlsInText(message);\n const safe = error instanceof TypeError ? new TypeError(safeMessage) : new Error(safeMessage);\n if (error instanceof Error && !(error instanceof TypeError)) safe.name = error.name;\n return safe;\n}\n\n/**\n * Готовит заголовки к записи в лог.\n *\n * `Authorization` и `Cookie` маскируются: логи часто уезжают в системы сбора,\n * и токен доступа в них попадать не должен.\n */\nexport function redactHeaders(headers: Headers): Record<string, string> {\n const result: Record<string, string> = {};\n\n headers.forEach((value, name) => {\n if (SECRET_HEADERS.has(name.toLowerCase())) {\n // У Authorization маскируем только сам токен, схему («Bearer») оставляем видимой.\n const spaceAt = value.indexOf(' ');\n result[name] =\n spaceAt > 0\n ? `${value.slice(0, spaceAt)} ${maskSecret(value.slice(spaceAt + 1))}`\n : maskSecret(value);\n return;\n }\n result[name] = value;\n });\n\n return result;\n}\n\n/**\n * Готовит тело запроса к записи в лог: пароли, токены и коды OTP заменяются заглушкой.\n *\n * Обходит вложенные объекты и массивы. `FormData` и бинарные тела не раскрываются вовсе.\n */\nexport function redactBody(body: unknown): unknown {\n if (body === null || body === undefined) return body;\n\n if (typeof FormData !== 'undefined' && body instanceof FormData) return '[FormData]';\n if (isBlob(body)) return '[Blob]';\n if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) return '[binary]';\n\n if (Array.isArray(body)) return body.map(redactBody);\n\n if (typeof body === 'object') {\n const result: Record<string, unknown> = {};\n const entries = Object.entries(body);\n const containsEmail = entries.some(([key]) => key.toLowerCase() === 'email');\n for (const [key, value] of entries) {\n const normalized = key.toLowerCase();\n const secret = SECRET_FIELDS.has(normalized) || (normalized === 'code' && containsEmail);\n result[key] = secret ? '[скрыто]' : redactBody(value);\n }\n return result;\n }\n\n return body;\n}\n"],"x_google_ignoreList":[3],"mappings":"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"}
@@ -1,4 +1,63 @@
1
- const require_errors = require("./errors-BmP3TKoW.cjs");
1
+ const require_validate = require("./validate-CFx2dISs.cjs");
2
+ //#region src/core/logger.ts
3
+ /** Уровень записи журнала. */
4
+ const LogLevel = Object.freeze({
5
+ Debug: "debug",
6
+ Info: "info",
7
+ Warn: "warn",
8
+ Error: "error"
9
+ });
10
+ /** Уровень встроенного логгера по умолчанию: `logger: true`. */
11
+ const DEFAULT_LOG_LEVEL = LogLevel.Info;
12
+ const LEVEL_RANK = {
13
+ debug: 0,
14
+ info: 1,
15
+ warn: 2,
16
+ error: 3
17
+ };
18
+ const LEVELS = Object.values(LogLevel);
19
+ function isLogLevel(value) {
20
+ return typeof value === "string" && LEVELS.includes(value);
21
+ }
22
+ /** Логгер поверх `console`, пропускающий записи с уровня `level` и выше. */
23
+ function consoleLogger(level = DEFAULT_LOG_LEVEL) {
24
+ const threshold = LEVEL_RANK[level];
25
+ const enabled = (method) => LEVEL_RANK[method] >= threshold;
26
+ const write = (method) => (message, ...args) => {
27
+ if (enabled(method)) console[method](`[itd-api] ${message}`, ...args);
28
+ };
29
+ return {
30
+ debug: write(LogLevel.Debug),
31
+ info: write(LogLevel.Info),
32
+ warn: write(LogLevel.Warn),
33
+ error: write(LogLevel.Error),
34
+ isLevelEnabled: enabled
35
+ };
36
+ }
37
+ /** Стоит ли готовить детали записи: логгер без `isLevelEnabled` считается пишущим всё. */
38
+ function isLevelEnabled(logger, level) {
39
+ return logger.isLevelEnabled?.(level) ?? true;
40
+ }
41
+ /**
42
+ * Логгер для записей, которым нельзя пропасть без настроенного логгера: ошибки чужих
43
+ * обработчиков и нечитаемые записи хранилища. Всё остальное библиотека без логгера не пишет.
44
+ */
45
+ const fallbackLogger = consoleLogger(LogLevel.Warn);
46
+ /**
47
+ * Приводит опцию `logger` к логгеру.
48
+ *
49
+ * @throws {ItdConfigError} при некорректном значении
50
+ */
51
+ function resolveLogger(logger) {
52
+ if (logger === void 0 || logger === false) return void 0;
53
+ if (logger === true) return consoleLogger();
54
+ if (isLogLevel(logger)) return consoleLogger(logger);
55
+ if (!require_validate.isObjectLike(logger)) throw new require_validate.ItdConfigError("logger должен быть boolean, уровнем LogLevel или объектом Logger");
56
+ for (const method of LEVELS) if (typeof logger[method] !== "function") throw new require_validate.ItdConfigError(`logger.${method} должен быть функцией`);
57
+ if (logger.isLevelEnabled !== void 0 && typeof logger.isLevelEnabled !== "function") throw new require_validate.ItdConfigError("logger.isLevelEnabled должен быть функцией");
58
+ return logger;
59
+ }
60
+ //#endregion
2
61
  //#region src/core/runtime.ts
3
62
  /**
4
63
  * Как библиотека обращается с cookie.
@@ -64,11 +123,11 @@ function shouldSendCredentials(mode) {
64
123
  */
65
124
  function resolveFetch(custom) {
66
125
  if (custom !== void 0) {
67
- if (typeof custom !== "function") throw new require_errors.ItdConfigError("fetch должен быть функцией");
126
+ if (typeof custom !== "function") throw new require_validate.ItdConfigError("fetch должен быть функцией");
68
127
  return custom;
69
128
  }
70
129
  if (typeof globalThis.fetch === "function") return globalThis.fetch.bind(globalThis);
71
- throw new require_errors.ItdConfigError("В этой среде нет глобального fetch. Обновитесь до Node 18+ либо передайте свою реализацию через опцию fetch.");
130
+ throw new require_validate.ItdConfigError("В этой среде нет глобального fetch. Обновитесь до Node 18+ либо передайте свою реализацию через опцию fetch.");
72
131
  }
73
132
  /**
74
133
  * Проверки бинарных типов, безопасные в любой среде.
@@ -156,7 +215,7 @@ function buildQuery(params) {
156
215
  * @throws {ItdConfigError} если значение пустое
157
216
  */
158
217
  function encodePathSegment(value, name = "параметр пути") {
159
- if (typeof value !== "string" || value.trim() === "") throw new require_errors.ItdConfigError(`${name} должен быть непустой строкой, получено: ${JSON.stringify(value)}`);
218
+ if (typeof value !== "string" || value.trim() === "") throw new require_validate.ItdConfigError(`${name} должен быть непустой строкой, получено: ${JSON.stringify(value)}`);
160
219
  return encodeURIComponent(value);
161
220
  }
162
221
  /** Хост из URL в нижнем регистре. Пустая строка, если разобрать не удалось. */
@@ -197,15 +256,15 @@ function joinUrl(baseUrl, path) {
197
256
  }
198
257
  /** Приводит базовый URL к каноничному виду и проверяет, что он вообще похож на URL. */
199
258
  function normalizeBaseUrl(baseUrl) {
200
- if (typeof baseUrl !== "string" || baseUrl.trim() === "") throw new require_errors.ItdConfigError(`baseUrl должен быть непустой строкой с абсолютным URL, получено: ${JSON.stringify(baseUrl)}`);
259
+ if (typeof baseUrl !== "string" || baseUrl.trim() === "") throw new require_validate.ItdConfigError(`baseUrl должен быть непустой строкой с абсолютным URL, получено: ${JSON.stringify(baseUrl)}`);
201
260
  let parsed;
202
261
  try {
203
262
  parsed = new URL(baseUrl);
204
263
  } catch {
205
- throw new require_errors.ItdConfigError(`baseUrl должен быть абсолютным URL, получено: ${JSON.stringify(baseUrl)}`);
264
+ throw new require_validate.ItdConfigError(`baseUrl должен быть абсолютным URL, получено: ${JSON.stringify(baseUrl)}`);
206
265
  }
207
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new require_errors.ItdConfigError(`baseUrl должен использовать http или https, получено: ${parsed.protocol}`);
208
- if (parsed.username || parsed.password || parsed.search || parsed.hash) throw new require_errors.ItdConfigError("baseUrl не должен содержать логин, пароль, query-параметры или fragment");
266
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new require_validate.ItdConfigError(`baseUrl должен использовать http или https, получено: ${parsed.protocol}`);
267
+ if (parsed.username || parsed.password || parsed.search || parsed.hash) throw new require_validate.ItdConfigError("baseUrl не должен содержать логин, пароль, query-параметры или fragment");
209
268
  return parsed.origin + (parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/$/, ""));
210
269
  }
211
270
  //#endregion
@@ -384,13 +443,6 @@ function toTimestamp(date) {
384
443
  const time = date.getTime();
385
444
  return Number.isFinite(time) ? time : void 0;
386
445
  }
387
- function pathOf(url) {
388
- try {
389
- return new URL(url).pathname || "/";
390
- } catch {
391
- return "/";
392
- }
393
- }
394
446
  /**
395
447
  * Подходит ли путь cookie запросу.
396
448
  *
@@ -417,14 +469,18 @@ var CookieJar = class CookieJar {
417
469
  /** Независимая копия для auth-flow, который ещё не получил право на commit. */
418
470
  clone() {
419
471
  const copy = new CookieJar();
420
- copy.deserialize(this.serialize());
472
+ copy.#adopt(this);
421
473
  return copy;
422
474
  }
423
475
  /** Атомарно с точки зрения синхронных читателей заменяет содержимое снимком другого jar. */
424
476
  replaceWith(source) {
425
- const entries = source.serialize();
426
- this.clear();
427
- this.deserialize(entries);
477
+ if (source === this) return;
478
+ this.#byOrigin.clear();
479
+ this.#adopt(source);
480
+ }
481
+ /** Копирует карты cookie другого jar; сами записи неизменяемы и делятся по ссылке. */
482
+ #adopt(source) {
483
+ for (const [origin, jar] of source.#byOrigin) this.#byOrigin.set(origin, new Map(jar));
428
484
  }
429
485
  /**
430
486
  * Забирает `Set-Cookie` из ответа.
@@ -540,11 +596,16 @@ var CookieJar = class CookieJar {
540
596
  }
541
597
  /** Действующие cookie, подходящие запросу: тот же origin, подходящий путь, не истёкшие. */
542
598
  #matching(url) {
543
- const origin = originOf(url);
544
- const jar = this.#byOrigin.get(origin);
599
+ let parsed;
600
+ try {
601
+ parsed = new URL(url);
602
+ } catch {
603
+ return [];
604
+ }
605
+ const jar = this.#byOrigin.get(parsed.origin);
545
606
  if (!jar) return [];
546
- const isSecureRequest = origin.startsWith("https:");
547
- const requestPath = pathOf(url);
607
+ const isSecureRequest = parsed.protocol === "https:";
608
+ const requestPath = parsed.pathname || "/";
548
609
  const now = Date.now();
549
610
  const result = [];
550
611
  for (const cookie of jar.values()) {
@@ -690,6 +751,12 @@ Object.defineProperty(exports, "CookieJar", {
690
751
  return CookieJar;
691
752
  }
692
753
  });
754
+ Object.defineProperty(exports, "LogLevel", {
755
+ enumerable: true,
756
+ get: function() {
757
+ return LogLevel;
758
+ }
759
+ });
693
760
  Object.defineProperty(exports, "REFRESH_COOKIE", {
694
761
  enumerable: true,
695
762
  get: function() {
@@ -726,6 +793,12 @@ Object.defineProperty(exports, "encodePathSegment", {
726
793
  return encodePathSegment;
727
794
  }
728
795
  });
796
+ Object.defineProperty(exports, "fallbackLogger", {
797
+ enumerable: true,
798
+ get: function() {
799
+ return fallbackLogger;
800
+ }
801
+ });
729
802
  Object.defineProperty(exports, "hostOf", {
730
803
  enumerable: true,
731
804
  get: function() {
@@ -744,6 +817,12 @@ Object.defineProperty(exports, "isFile", {
744
817
  return isFile;
745
818
  }
746
819
  });
820
+ Object.defineProperty(exports, "isLevelEnabled", {
821
+ enumerable: true,
822
+ get: function() {
823
+ return isLevelEnabled;
824
+ }
825
+ });
747
826
  Object.defineProperty(exports, "isSameSite", {
748
827
  enumerable: true,
749
828
  get: function() {
@@ -810,6 +889,12 @@ Object.defineProperty(exports, "resolveFetch", {
810
889
  return resolveFetch;
811
890
  }
812
891
  });
892
+ Object.defineProperty(exports, "resolveLogger", {
893
+ enumerable: true,
894
+ get: function() {
895
+ return resolveLogger;
896
+ }
897
+ });
813
898
  Object.defineProperty(exports, "shouldSendCredentials", {
814
899
  enumerable: true,
815
900
  get: function() {
@@ -829,4 +914,4 @@ Object.defineProperty(exports, "supportsStreamingBody", {
829
914
  }
830
915
  });
831
916
 
832
- //# sourceMappingURL=redact-lAmck1Z6.cjs.map
917
+ //# sourceMappingURL=redact-DoC7lSsp.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact-DoC7lSsp.cjs","names":["isObjectLike","ItdConfigError","ItdConfigError","ItdConfigError","#byOrigin","#adopt","#matching"],"sources":["../../src/core/logger.ts","../../src/core/runtime.ts","../../src/core/url.ts","../../node_modules/set-cookie-parser/lib/set-cookie.js","../../src/core/cookies.ts","../../src/core/redact.ts"],"sourcesContent":["import { ItdConfigError } from './errors.js';\nimport { isObjectLike } from './validate.js';\n\n/**\n * Логгер библиотеки. Совместим с `console`: к сообщению прилагается не больше одного\n * аргумента — объект с деталями либо ошибка.\n */\nexport interface Logger {\n debug(message: string, ...args: unknown[]): void;\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n /** Пишет ли логгер этот уровень; без метода считается, что пишет все. Есть у winston и pino. */\n isLevelEnabled?(level: LogLevel): boolean;\n}\n\n/** Уровень записи журнала. */\nexport const LogLevel = Object.freeze({\n Debug: 'debug',\n Info: 'info',\n Warn: 'warn',\n Error: 'error',\n} as const);\nexport type LogLevel = (typeof LogLevel)[keyof typeof LogLevel];\n\n/** Уровень встроенного логгера по умолчанию: `logger: true`. */\nconst DEFAULT_LOG_LEVEL: LogLevel = LogLevel.Info;\n\nconst LEVEL_RANK: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3 };\n\nconst LEVELS = Object.values(LogLevel);\n\nfunction isLogLevel(value: unknown): value is LogLevel {\n return typeof value === 'string' && (LEVELS as string[]).includes(value);\n}\n\n/** Логгер поверх `console`, пропускающий записи с уровня `level` и выше. */\nexport function consoleLogger(level: LogLevel = DEFAULT_LOG_LEVEL): Logger {\n const threshold = LEVEL_RANK[level];\n const enabled = (method: LogLevel): boolean => LEVEL_RANK[method] >= threshold;\n const write =\n (method: LogLevel) =>\n (message: string, ...args: unknown[]): void => {\n if (enabled(method)) console[method](`[itd-api] ${message}`, ...args);\n };\n\n return {\n debug: write(LogLevel.Debug),\n info: write(LogLevel.Info),\n warn: write(LogLevel.Warn),\n error: write(LogLevel.Error),\n isLevelEnabled: enabled,\n };\n}\n\n/** Стоит ли готовить детали записи: логгер без `isLevelEnabled` считается пишущим всё. */\nexport function isLevelEnabled(logger: Logger, level: LogLevel): boolean {\n return logger.isLevelEnabled?.(level) ?? true;\n}\n\n/**\n * Логгер для записей, которым нельзя пропасть без настроенного логгера: ошибки чужих\n * обработчиков и нечитаемые записи хранилища. Всё остальное библиотека без логгера не пишет.\n */\nexport const fallbackLogger: Logger = consoleLogger(LogLevel.Warn);\n\n/**\n * Приводит опцию `logger` к логгеру.\n *\n * @throws {ItdConfigError} при некорректном значении\n */\nexport function resolveLogger(logger: Logger | LogLevel | boolean | undefined): Logger | undefined {\n if (logger === undefined || logger === false) return undefined;\n if (logger === true) return consoleLogger();\n if (isLogLevel(logger)) return consoleLogger(logger);\n if (!isObjectLike(logger)) {\n throw new ItdConfigError('logger должен быть boolean, уровнем LogLevel или объектом Logger');\n }\n\n for (const method of LEVELS) {\n if (typeof logger[method] !== 'function') {\n throw new ItdConfigError(`logger.${method} должен быть функцией`);\n }\n }\n if (logger.isLevelEnabled !== undefined && typeof logger.isLevelEnabled !== 'function') {\n throw new ItdConfigError('logger.isLevelEnabled должен быть функцией');\n }\n return logger;\n}\n","import { ItdConfigError } from './errors.js';\n\n/**\n * Как библиотека обращается с cookie.\n *\n * - `browser` — cookie ведёт браузер, запросы уходят с `credentials: 'include'`;\n * - `server` — cookie ведёт встроенный jar, заголовок `Cookie` подставляется вручную;\n * - `auto` — определяется по среде исполнения (значение по умолчанию).\n */\nexport const RuntimeMode = Object.freeze({\n /** Определяется по среде исполнения. Значение по умолчанию. */\n Auto: 'auto',\n /** Cookie ведёт браузер, запросы уходят с `credentials: 'include'`. */\n Browser: 'browser',\n /** Cookie ведёт встроенный jar, заголовок `Cookie` подставляется вручную. */\n Server: 'server',\n} as const);\nexport type RuntimeMode = (typeof RuntimeMode)[keyof typeof RuntimeMode];\n\n/** Распознанная среда исполнения. */\nexport const DetectedRuntime = Object.freeze({\n Browser: 'browser',\n /** Есть `window`, но нет `document`; cookie ведёт нативный сетевой слой. */\n ReactNative: 'react-native',\n Server: 'server',\n} as const);\nexport type DetectedRuntime = (typeof DetectedRuntime)[keyof typeof DetectedRuntime];\n\n/**\n * Определяет среду исполнения.\n *\n * React Native выделен отдельно намеренно: там есть `window`, но нет `document`, а cookie\n * хранит нативный сетевой слой (`NSHTTPCookieStorage` / `OkHttp`). Свой jar в этой среде\n * привёл бы к отправке cookie дважды.\n */\nexport function detectRuntime(): DetectedRuntime {\n const nav = (globalThis as { navigator?: { product?: string } }).navigator;\n if (nav?.product === 'ReactNative') return DetectedRuntime.ReactNative;\n\n if (typeof document !== 'undefined') return DetectedRuntime.Browser;\n\n return DetectedRuntime.Server;\n}\n\n/**\n * Нужно ли вести собственный cookie-jar.\n *\n * Только в серверных средах: в браузере `Set-Cookie` из JS не читается и не нужен,\n * в React Native cookie ведёт нативный слой.\n */\nexport function shouldUseCookieJar(mode: RuntimeMode): boolean {\n if (mode === RuntimeMode.Browser) return false;\n if (mode === RuntimeMode.Server) return true;\n return detectRuntime() === DetectedRuntime.Server;\n}\n\n/**\n * Нужно ли отправлять запросы с `credentials: 'include'`.\n *\n * Только в браузере: именно так refresh-cookie попадает в запрос `POST /api/v1/auth/refresh`.\n * Требует настроенного CORS на стороне итд.com — если его нет, укажите свой прокси в `baseUrl`.\n */\nexport function shouldSendCredentials(mode: RuntimeMode): boolean {\n if (mode === RuntimeMode.Browser) return true;\n if (mode === RuntimeMode.Server) return false;\n return detectRuntime() === DetectedRuntime.Browser;\n}\n\n/**\n * Возвращает реализацию `fetch`.\n *\n * @param custom реализация из конфигурации клиента, если пользователь её передал\n * @throws {ItdConfigError} если `fetch` недоступен — например, на Node ниже 18\n */\nexport function resolveFetch(custom?: typeof fetch): typeof fetch {\n if (custom !== undefined) {\n if (typeof custom !== 'function') {\n throw new ItdConfigError('fetch должен быть функцией');\n }\n return custom;\n }\n\n if (typeof globalThis.fetch === 'function') {\n // Привязка к globalThis обязательна: в некоторых средах несвязанный fetch бросает\n // «Illegal invocation».\n return globalThis.fetch.bind(globalThis);\n }\n\n throw new ItdConfigError(\n 'В этой среде нет глобального fetch. Обновитесь до Node 18+ либо передайте свою ' +\n 'реализацию через опцию fetch.',\n );\n}\n\n/**\n * Проверки бинарных типов, безопасные в любой среде.\n *\n * Обычный `instanceof` здесь недостаточен: конструктора может не быть вовсе, и тогда\n * проверка падает с `ReferenceError`, а не возвращает `false`. `Blob` есть в Node с 18.0,\n * но `File` стал глобальным только в Node 20 — при заявленной поддержке Node 18 голый\n * `instanceof File` ронял бы любую загрузку.\n */\nexport function isBlob(value: unknown): value is Blob {\n return typeof Blob !== 'undefined' && value instanceof Blob;\n}\n\n/** @see {@link isBlob} */\nexport function isFile(value: unknown): value is File {\n return typeof File !== 'undefined' && value instanceof File;\n}\n\n/** Доступно ли потоковое чтение тела ответа — от этого зависит, сработает ли SSE. */\nexport function supportsStreamingBody(): boolean {\n return typeof ReadableStream !== 'undefined' && typeof TextDecoder !== 'undefined';\n}\n\n/**\n * Создаёт идентификатор устройства для заголовка `X-Device-Id` — UUID v4.\n *\n * `crypto.randomUUID` есть в Node 19+, браузерах и Deno, но отсутствует в Node 18 вне\n * защищённого контекста, поэтому предусмотрен запасной путь.\n */\nexport function createDeviceId(): string {\n const webCrypto = (globalThis as { crypto?: Crypto }).crypto;\n\n if (typeof webCrypto?.randomUUID === 'function') return webCrypto.randomUUID();\n\n // Форма UUID v4 собирается вручную: серверу важна стабильность значения, а не его энтропия.\n const bytes = new Uint8Array(16);\n if (typeof webCrypto?.getRandomValues === 'function') webCrypto.getRandomValues(bytes);\n else for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);\n\n // biome-ignore lint/style/noNonNullAssertion: длина массива фиксирована выше\n bytes[6] = (bytes[6]! & 0x0f) | 0x40;\n // biome-ignore lint/style/noNonNullAssertion: длина массива фиксирована выше\n bytes[8] = (bytes[8]! & 0x3f) | 0x80;\n\n const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');\n\n return [\n hex.slice(0, 8),\n hex.slice(8, 12),\n hex.slice(12, 16),\n hex.slice(16, 20),\n hex.slice(20, 32),\n ].join('-');\n}\n","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. Запись неизменяема: копии jar делят её по ссылке. */\ninterface StoredCookie {\n readonly name: string;\n readonly value: string;\n readonly path: string;\n /** Момент истечения, мс с начала эпохи. `undefined` — сессионная cookie. */\n readonly expires: number | undefined;\n readonly 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 = ' ';\nconst COOKIE_NAME = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/;\n\n/** Проверяет одну запись, созданную {@link CookieJar.serialize}. @internal */\nexport function isSerializedCookieEntry(entry: string): boolean {\n const separatorAt = entry.indexOf(SERIALIZED_SEPARATOR);\n if (separatorAt <= 0) return false;\n\n const origin = entry.slice(0, separatorAt);\n if (originOf(origin) !== origin) return false;\n\n const parsed = parseSetCookie([entry.slice(separatorAt + 1)]);\n return parsed.length === 1 && COOKIE_NAME.test(parsed[0]?.name ?? '');\n}\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\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 /** Независимая копия для auth-flow, который ещё не получил право на commit. */\n clone(): CookieJar {\n const copy = new CookieJar();\n copy.#adopt(this);\n return copy;\n }\n\n /** Атомарно с точки зрения синхронных читателей заменяет содержимое снимком другого jar. */\n replaceWith(source: CookieJar): void {\n if (source === this) return;\n this.#byOrigin.clear();\n this.#adopt(source);\n }\n\n /** Копирует карты cookie другого jar; сами записи неизменяемы и делятся по ссылке. */\n #adopt(source: CookieJar): void {\n for (const [origin, jar] of source.#byOrigin) this.#byOrigin.set(origin, new Map(jar));\n }\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 if (!isSerializedCookieEntry(entry)) continue;\n const separatorAt = entry.indexOf(SERIALIZED_SEPARATOR);\n\n const origin = entry.slice(0, separatorAt);\n const setCookie = entry.slice(separatorAt + 1);\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 let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return [];\n }\n const jar = this.#byOrigin.get(parsed.origin);\n if (!jar) return [];\n\n const isSecureRequest = parsed.protocol === 'https:';\n const requestPath = parsed.pathname || '/';\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","import { isBlob } from './runtime.js';\n\n/** Заголовки, значение которых нельзя писать в лог целиком. */\nconst SECRET_HEADERS = new Set([\n 'authorization',\n 'cookie',\n 'set-cookie',\n 'x-api-key',\n 'x-order-token',\n]);\n\n/** Поля тела запроса, значение которых нельзя писать в лог. */\nconst SECRET_FIELDS = new Set([\n 'password',\n 'oldpassword',\n 'newpassword',\n 'accesstoken',\n 'refreshtoken',\n 'currentpassword',\n 'flowtoken',\n 'token',\n 'turnstiletoken',\n 'otp',\n 'pass',\n 'email',\n 'phone',\n 'address',\n 'orderaccesstoken',\n]);\n\n/** Query-параметры, содержащие секреты. */\nconst SECRET_QUERY_PARAMS = new Set(['token', 'access_token', 'c']);\n\n/**\n * Прячет середину секрета, оставляя концы для сопоставления.\n *\n * @example\n * ```ts\n * maskSecret('eyJhbGciOiJIUzI1NiJ9.abc'); // 'eyJh…(24)…abc'\n * ```\n */\nexport function maskSecret(value: string): string {\n if (value.length <= 8) return '…';\n return `${value.slice(0, 4)}…(${value.length})…${value.slice(-3)}`;\n}\n\n/** Маскирует секретные query-параметры URL перед записью в логи и ошибки. */\nexport function redactUrl(url: string): string {\n try {\n const parsed = new URL(url);\n const entries = [...parsed.searchParams.entries()];\n parsed.search = '';\n for (const [name, value] of entries) {\n parsed.searchParams.append(\n name,\n SECRET_QUERY_PARAMS.has(name.toLowerCase()) ? maskSecret(value) : value,\n );\n }\n return parsed.toString();\n } catch {\n return url.replace(/([?&](?:token|access_token|c)=)[^&#]*/gi, '$1[скрыто]');\n }\n}\n\n/** Маскирует подписанные адреса внутри произвольного диагностического текста. */\nexport function redactUrlsInText(text: string): string {\n return text.replace(/https?:\\/\\/[^\\s\"'<>]+/gi, (url) => redactUrl(url));\n}\n\n/**\n * Создаёт безопасную причину ошибки для публичной цепочки `cause`.\n *\n * Чужие объекты не сохраняются: их поля и пользовательские методы нельзя надёжно\n * сериализовать без риска вывести секрет. Для Error и строк остаются тип и сообщение,\n * но все встреченные в сообщении HTTP(S)-адреса проходят маскирование.\n */\nexport function redactErrorCause(error: unknown): Error | undefined {\n const message =\n error instanceof Error ? error.message : typeof error === 'string' ? error : undefined;\n if (message === undefined) return undefined;\n\n const safeMessage = redactUrlsInText(message);\n const safe = error instanceof TypeError ? new TypeError(safeMessage) : new Error(safeMessage);\n if (error instanceof Error && !(error instanceof TypeError)) safe.name = error.name;\n return safe;\n}\n\n/**\n * Готовит заголовки к записи в лог.\n *\n * `Authorization` и `Cookie` маскируются: логи часто уезжают в системы сбора,\n * и токен доступа в них попадать не должен.\n */\nexport function redactHeaders(headers: Headers): Record<string, string> {\n const result: Record<string, string> = {};\n\n headers.forEach((value, name) => {\n if (SECRET_HEADERS.has(name.toLowerCase())) {\n // У Authorization маскируем только сам токен, схему («Bearer») оставляем видимой.\n const spaceAt = value.indexOf(' ');\n result[name] =\n spaceAt > 0\n ? `${value.slice(0, spaceAt)} ${maskSecret(value.slice(spaceAt + 1))}`\n : maskSecret(value);\n return;\n }\n result[name] = value;\n });\n\n return result;\n}\n\n/**\n * Готовит тело запроса к записи в лог: пароли, токены и коды OTP заменяются заглушкой.\n *\n * Обходит вложенные объекты и массивы. `FormData` и бинарные тела не раскрываются вовсе.\n */\nexport function redactBody(body: unknown): unknown {\n if (body === null || body === undefined) return body;\n\n if (typeof FormData !== 'undefined' && body instanceof FormData) return '[FormData]';\n if (isBlob(body)) return '[Blob]';\n if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) return '[binary]';\n\n if (Array.isArray(body)) return body.map(redactBody);\n\n if (typeof body === 'object') {\n const result: Record<string, unknown> = {};\n const entries = Object.entries(body);\n const containsEmail = entries.some(([key]) => key.toLowerCase() === 'email');\n for (const [key, value] of entries) {\n const normalized = key.toLowerCase();\n const secret = SECRET_FIELDS.has(normalized) || (normalized === 'code' && containsEmail);\n result[key] = secret ? '[скрыто]' : redactBody(value);\n }\n return result;\n }\n\n return body;\n}\n"],"x_google_ignoreList":[3],"mappings":"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"}