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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-storage-mBuCOVZY.cjs","names":["ItdConfigError","#byOrigin","#matching","ItdConfigError","ItdFileError","ItdFileErrorReason","ItdConfigError","ItdFileError","ItdFileErrorReason","ItdConfigError","#store","MemoryKeyValueStore","copySession","ItdConfigError","createKeyValueStore","isEnumerableKeyValueStore","collectKeyValueStoreKeys"],"sources":["../node_modules/set-cookie-parser/lib/set-cookie.js","../src/core/url.ts","../src/core/cookies.ts","../src/core/attachments/contracts.ts","../src/core/attachments/limits.ts","../src/core/attachments/bounded-stream.ts","../src/core/attachments/options.ts","../src/core/multi-storage.ts"],"sourcesContent":["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 { 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","import { parse as parseSetCookie, splitCookiesString } from 'set-cookie-parser';\nimport { originOf } from './url.js';\n\n/** Cookie, сохранённая в jar. */\ninterface StoredCookie {\n name: string;\n value: string;\n path: string;\n /** Момент истечения, мс с начала эпохи. `undefined` — сессионная cookie. */\n expires: number | undefined;\n secure: boolean;\n}\n\n/** Имя cookie-флага «есть refresh-сессия». Ставится сайтом итд.com рядом с refresh-токеном. */\nexport const AUTH_FLAG_COOKIE = 'is_auth';\n\n/**\n * Имя cookie с refresh-токеном.\n *\n * `POST /api/v1/auth/refresh` читает токен **только отсюда** — тело запроса сервер игнорирует.\n * Cookie помечена `HttpOnly`, поэтому в браузере её не прочитать и не выставить; вне браузера\n * она проходит через jar, и туда же кладётся токен, переданный строкой.\n */\nexport const REFRESH_COOKIE = 'refresh_token';\n\n/** Путь, которым сервер ограничивает {@link REFRESH_COOKIE}. */\nexport const REFRESH_COOKIE_PATH = '/api/v1/auth';\n\n/** Разделитель origin и содержимого cookie при сериализации. В origin пробелов не бывает. */\nconst SERIALIZED_SEPARATOR = ' ';\n\n/** Дата в миллисекунды. Некорректная дата считается отсутствующей, а не `NaN`. */\nfunction toTimestamp(date: Date | undefined): number | undefined {\n if (!date) return undefined;\n const time = date.getTime();\n return Number.isFinite(time) ? time : undefined;\n}\n\nfunction pathOf(url: string): string {\n try {\n return new URL(url).pathname || '/';\n } catch {\n return '/';\n }\n}\n\n/**\n * Подходит ли путь cookie запросу.\n *\n * Правило из RFC 6265: путь cookie должен быть префиксом пути запроса, причём граница\n * должна проходить по сегменту — `/api` не подходит запросу `/apiary`.\n */\nfunction pathMatches(cookiePath: string, requestPath: string): boolean {\n if (cookiePath === '/' || cookiePath === requestPath) return true;\n if (!requestPath.startsWith(cookiePath)) return false;\n return cookiePath.endsWith('/') || requestPath[cookiePath.length] === '/';\n}\n\n/**\n * Минимальное хранилище cookie для сред без своего.\n *\n * Refresh-токен итд.com приходит в `Set-Cookie`, а `fetch` вне браузера cookie не хранит —\n * без jar сессию не продлить. В браузере и React Native не используется: там cookie ведёт\n * сама среда.\n *\n * Реализована практическая часть RFC 6265: origin, путь, срок жизни, флаг `Secure`.\n * Доменные cookie для поддоменов намеренно не поддерживаются — API работает с одного хоста.\n */\nexport class CookieJar {\n readonly #byOrigin = new Map<string, Map<string, StoredCookie>>();\n\n /**\n * Забирает `Set-Cookie` из ответа.\n *\n * Использует `Headers.getSetCookie()`, где он есть (Node 20+, undici). В остальных средах\n * заголовки склеены в одну строку, и её нельзя резать по запятой напрямую: запятая есть\n * внутри `Expires=Wed, 09 Jun 2027 …`. Разделением занимается `set-cookie-parser`.\n */\n setFromResponse(url: string, response: Response): void {\n const headers = response.headers as Headers & { getSetCookie?: () => string[] };\n\n const raw =\n typeof headers.getSetCookie === 'function'\n ? headers.getSetCookie()\n : splitCookiesString(headers.get('set-cookie') ?? '');\n\n if (raw.length > 0) this.setFromStrings(url, raw);\n }\n\n /**\n * Кладёт cookie напрямую, минуя `Set-Cookie`.\n *\n * Нужно ровно в одном случае: пользователь передал refresh-токен строкой, а сервер читает\n * его только из cookie. Значение не кодируется — оно уходит в заголовок как есть.\n */\n set(url: string, name: string, value: string, path = '/'): void {\n this.setFromStrings(url, [`${name}=${value}; Path=${path}`]);\n }\n\n /** Сохраняет cookie из готовых строк `Set-Cookie`. */\n setFromStrings(url: string, setCookieStrings: string[]): void {\n const origin = originOf(url);\n if (!origin) return;\n\n const jar = this.#byOrigin.get(origin) ?? new Map<string, StoredCookie>();\n\n for (const parsed of parseSetCookie(setCookieStrings)) {\n // Битую дату (её легко получить, если заголовки склеены неправильно) считаем\n // отсутствующей: сессионная cookie лучше, чем cookie со сроком NaN, которая\n // не истекает и одновременно нигде не находится.\n const expires = toTimestamp(parsed.expires);\n const maxAgeExpires =\n typeof parsed.maxAge === 'number' && Number.isFinite(parsed.maxAge)\n ? Date.now() + parsed.maxAge * 1000\n : undefined;\n\n // Max-Age приоритетнее Expires — так требует RFC 6265.\n const expiresAt = maxAgeExpires ?? expires;\n\n // Удаление cookie сервером: срок в прошлом либо Max-Age=0.\n if (expiresAt !== undefined && expiresAt <= Date.now()) {\n jar.delete(parsed.name);\n continue;\n }\n\n jar.set(parsed.name, {\n name: parsed.name,\n value: parsed.value,\n path: parsed.path ?? '/',\n expires: expiresAt,\n secure: parsed.secure ?? false,\n });\n }\n\n this.#byOrigin.set(origin, jar);\n }\n\n /**\n * Собирает значение заголовка `Cookie` для запроса.\n *\n * @returns строка вида `a=1; b=2` либо `undefined`, если подходящих cookie нет\n */\n getHeader(url: string): string | undefined {\n const cookies = this.#matching(url);\n if (cookies.length === 0) return undefined;\n return cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join('; ');\n }\n\n /**\n * Значение действующей cookie.\n *\n * Нужно, чтобы забрать обновлённый refresh-токен: сервер ротирует его при каждом\n * продлении сессии, и сохранять надо именно новое значение.\n *\n * @param url если указан, учитываются origin, путь и флаг `Secure`\n */\n getValue(name: string, url?: string): string | undefined {\n if (url) return this.#matching(url).find((cookie) => cookie.name === name)?.value;\n\n const now = Date.now();\n for (const jar of this.#byOrigin.values()) {\n const cookie = jar.get(name);\n if (cookie && (cookie.expires === undefined || cookie.expires > now)) return cookie.value;\n }\n return undefined;\n }\n\n /**\n * Есть ли действующая cookie с таким именем.\n *\n * Используется для проверки флага {@link AUTH_FLAG_COOKIE} перед запросом обновления\n * токена: у анонима refresh-сессии нет, и дёргать API незачем.\n */\n has(name: string, url?: string): boolean {\n if (url) return this.#matching(url).some((cookie) => cookie.name === name);\n\n const now = Date.now();\n for (const jar of this.#byOrigin.values()) {\n const cookie = jar.get(name);\n if (cookie && (cookie.expires === undefined || cookie.expires > now)) return true;\n }\n return false;\n }\n\n /** Сохраняет содержимое jar для записи в {@link TokenStorage}. */\n serialize(): string[] {\n const result: string[] = [];\n const now = Date.now();\n\n for (const [origin, jar] of this.#byOrigin) {\n for (const cookie of jar.values()) {\n if (cookie.expires !== undefined && cookie.expires <= now) continue;\n\n const parts = [`${cookie.name}=${cookie.value}`, `Path=${cookie.path}`];\n if (cookie.expires !== undefined) {\n parts.push(`Expires=${new Date(cookie.expires).toUTCString()}`);\n }\n if (cookie.secure) parts.push('Secure');\n\n result.push(`${origin}${SERIALIZED_SEPARATOR}${parts.join('; ')}`);\n }\n }\n\n return result;\n }\n\n /** Восстанавливает jar из результата {@link serialize}. Некорректные записи молча пропускаются. */\n deserialize(entries: readonly string[] | undefined): void {\n if (!entries) return;\n\n for (const entry of entries) {\n const separatorAt = entry.indexOf(SERIALIZED_SEPARATOR);\n if (separatorAt <= 0) continue;\n\n const origin = entry.slice(0, separatorAt);\n const setCookie = entry.slice(separatorAt + 1);\n if (!originOf(origin)) continue;\n\n this.setFromStrings(origin, [setCookie]);\n }\n }\n\n /** Удаляет все cookie. */\n clear(): void {\n this.#byOrigin.clear();\n }\n\n /** Действующие cookie, подходящие запросу: тот же origin, подходящий путь, не истёкшие. */\n #matching(url: string): StoredCookie[] {\n const origin = originOf(url);\n const jar = this.#byOrigin.get(origin);\n if (!jar) return [];\n\n const isSecureRequest = origin.startsWith('https:');\n const requestPath = pathOf(url);\n const now = Date.now();\n const result: StoredCookie[] = [];\n\n for (const cookie of jar.values()) {\n if (cookie.expires !== undefined && cookie.expires <= now) {\n jar.delete(cookie.name);\n continue;\n }\n if (cookie.secure && !isSecureRequest) continue;\n if (!pathMatches(cookie.path, requestPath)) continue;\n\n result.push(cookie);\n }\n\n return result;\n }\n}\n","/** Способ передачи содержимого вложения. */\nexport const FileTransferMode = Object.freeze({\n /** Сначала получить файл целиком, затем отправить его как `Blob`. */\n Buffer: 'buffer',\n /** Передавать данные по мере чтения, не собирая файл целиком в памяти. */\n Stream: 'stream',\n} as const);\nexport type FileTransferMode = (typeof FileTransferMode)[keyof typeof FileTransferMode];\n\n/** Размер очереди потокового вложения по умолчанию — 4 МиБ. */\nexport const DEFAULT_FILE_STREAM_BUFFER_BYTES = 4 * 1024 * 1024;\n\n/** Предел размера файла, скачиваемого по адресу, — 100 МиБ. */\nexport const DEFAULT_URL_FILE_MAX_BYTES = 100 * 1024 * 1024;\n\n/** Что доступно источнику при открытии файла. */\nexport interface FileContext {\n /** Общая отмена текущей попытки, включая её таймаут. */\n signal?: AbortSignal | undefined;\n /** `fetch` клиента — с переданными пользователем настройками. */\n fetch: typeof fetch;\n /** Номер попытки отправки, начиная с 1. */\n attempt: number;\n}\n\n/** Готовое содержимое файла. */\nexport interface FileContent {\n file: Blob | ArrayBuffer | Uint8Array;\n /** Имя влияет на определение MIME, если `contentType` не задан. */\n filename?: string | undefined;\n /** MIME без параметров. Если не указан, определяется по имени или `Blob`. */\n contentType?: string | undefined;\n}\n\n/** Открытый поток файла. */\nexport interface FileStreamContent {\n stream: ReadableStream<Uint8Array>;\n filename?: string | undefined;\n contentType?: string | undefined;\n /** Размер, если он известен до чтения. */\n size?: number | undefined;\n /** Освобождает внешний ресурс после завершения попытки. */\n close?: (() => void | Promise<void>) | undefined;\n}\n\n/** Буферный источник, который вычисляется непосредственно перед первой попыткой. */\nexport interface LazyFile {\n load: (context: FileContext) => FileContent | Promise<FileContent>;\n}\n\n/**\n * Повторяемый потоковый источник.\n *\n * `open` вызывается для каждой попытки отправки. Он обязан возвращать новый поток:\n * уже прочитанный `ReadableStream` повторно использовать нельзя.\n */\nexport interface StreamFile {\n open: (context: FileContext) => FileStreamContent | Promise<FileStreamContent>;\n}\n\n/** Общие настройки потоковой передачи. */\nexport interface FileStreamOptions {\n /** Буферный или потоковый режим. По умолчанию `'buffer'`. */\n mode?: FileTransferMode | undefined;\n /** Максимальный размер файла. Без значения размер не ограничивается. */\n maxBytes?: number | undefined;\n /**\n * Верхняя граница очереди, которой управляет библиотека. По умолчанию 4 МиБ.\n *\n * Сетевой рантайм может иметь собственные внутренние буферы сверх этого значения.\n */\n streamBufferBytes?: number | undefined;\n}\n\n/** Файл, который нужно получить по HTTP(S). */\nexport interface UrlFile extends UrlFileOptions {\n url: string;\n}\n\n/** Настройки файла по HTTP(S). */\nexport interface UrlFileOptions extends FileStreamOptions {\n filename?: string | undefined;\n /** По умолчанию берётся из `Content-Type` ответа. */\n contentType?: string | undefined;\n}\n\n/** Файл для загрузки. Голый поток не принимается, потому что его нельзя повторить. */\nexport type FileInput =\n | Blob\n | ArrayBuffer\n | Uint8Array\n | FileContent\n | UrlFile\n | LazyFile\n | StreamFile;\n\n/** Настройки фабрики пользовательского потока. */\nexport interface FromStreamOptions extends Omit<FileStreamOptions, 'mode'> {\n filename?: string | undefined;\n contentType?: string | undefined;\n /** Размер, если он известен заранее. */\n size?: number | undefined;\n}\n","import { ItdConfigError, ItdFileError, ItdFileErrorReason } from '../errors.js';\n\n/** Проверяет числовую границу до обращения к источнику. */\nexport function optionalBytes(value: number | undefined, name: string): number | undefined {\n if (value === undefined) return undefined;\n if (!Number.isFinite(value) || value < 0 || !Number.isInteger(value)) {\n throw new ItdConfigError(\n `${name} должен быть неотрицательным целым числом, получено: ${value}`,\n );\n }\n return value;\n}\n\n/** Создаёт типизированную ошибку превышения размера вложения. */\nexport function fileTooLarge(url: string | undefined, limit: number, actual: number): ItdFileError {\n const source = url ? ` по адресу ${url}` : '';\n return new ItdFileError(`файл${source} больше предела в ${limit} байт: ${actual}`, {\n reason: ItdFileErrorReason.TooLarge,\n ...(url ? { url } : {}),\n limit,\n actual,\n });\n}\n","import { ItdConfigError, ItdFileError, ItdFileErrorReason } from '../errors.js';\nimport { DEFAULT_FILE_STREAM_BUFFER_BYTES } from './contracts.js';\nimport { fileTooLarge, optionalBytes } from './limits.js';\n\n/** Потоки, на которые уже наложены счётчик размера и backpressure. */\nconst BOUNDED_FILE_STREAMS = new WeakSet<ReadableStream<Uint8Array>>();\n\n/** Настройки ограничивающей поток обёртки. */\nexport interface BoundedFileStreamOptions {\n maxBytes?: number | undefined;\n streamBufferBytes?: number | undefined;\n signal?: AbortSignal | undefined;\n url?: string | undefined;\n retryableRead?: boolean | undefined;\n}\n\n/** Приводит произвольный чанк потока к байтам. */\nfunction asBytes(value: Uint8Array): Uint8Array {\n return value instanceof Uint8Array ? value : new Uint8Array(value);\n}\n\n/** Проверяет значение без обращения к методам потенциально чужого объекта. */\nexport function isReadableByteStream(value: unknown): value is ReadableStream<Uint8Array> {\n return typeof ReadableStream !== 'undefined' && value instanceof ReadableStream;\n}\n\n/**\n * Ограничивает размер и очередь потока. Отмена результирующего потока отменяет исходный.\n *\n * @internal\n */\nexport function boundedFileStream(\n source: ReadableStream<Uint8Array>,\n options: BoundedFileStreamOptions = {},\n): ReadableStream<Uint8Array> {\n const maxBytes = optionalBytes(options.maxBytes, 'maxBytes');\n const streamBufferBytes =\n optionalBytes(\n options.streamBufferBytes ?? DEFAULT_FILE_STREAM_BUFFER_BYTES,\n 'streamBufferBytes',\n ) ?? DEFAULT_FILE_STREAM_BUFFER_BYTES;\n if (streamBufferBytes === 0) {\n throw new ItdConfigError('streamBufferBytes должен быть больше нуля');\n }\n\n const reader = source.getReader();\n let total = 0;\n let finished = false;\n let abortListener: (() => void) | undefined;\n\n const cleanup = () => {\n if (abortListener) options.signal?.removeEventListener('abort', abortListener);\n abortListener = undefined;\n };\n\n const stream = new ReadableStream<Uint8Array>(\n {\n start(controller) {\n if (!options.signal) return;\n abortListener = () => {\n finished = true;\n void reader.cancel(options.signal?.reason).catch(() => {});\n controller.error(options.signal?.reason ?? new DOMException('Aborted', 'AbortError'));\n cleanup();\n };\n if (options.signal.aborted) abortListener();\n else options.signal.addEventListener('abort', abortListener, { once: true });\n },\n async pull(controller) {\n if (finished) return;\n\n try {\n const next = await reader.read();\n if (finished) return;\n if (next.done) {\n finished = true;\n cleanup();\n controller.close();\n return;\n }\n\n const chunk = asBytes(next.value);\n total += chunk.byteLength;\n if (maxBytes !== undefined && total > maxBytes) {\n finished = true;\n await reader.cancel().catch(() => {});\n cleanup();\n controller.error(fileTooLarge(options.url, maxBytes, total));\n return;\n }\n\n controller.enqueue(chunk);\n } catch (error) {\n finished = true;\n cleanup();\n if (error instanceof ItdFileError) {\n controller.error(error);\n return;\n }\n controller.error(\n new ItdFileError('не удалось прочитать поток вложения', {\n reason: ItdFileErrorReason.Read,\n ...(options.url ? { url: options.url } : {}),\n retryable: options.retryableRead ?? false,\n cause: error,\n }),\n );\n }\n },\n async cancel(reason) {\n finished = true;\n cleanup();\n await reader.cancel(reason).catch(() => {});\n },\n },\n {\n highWaterMark: streamBufferBytes,\n size: (chunk) => chunk.byteLength,\n },\n );\n BOUNDED_FILE_STREAMS.add(stream);\n return stream;\n}\n\n/** Наложены ли на поток ограничения библиотеки. @internal */\nexport function isBoundedFileStream(stream: ReadableStream<Uint8Array>): boolean {\n return BOUNDED_FILE_STREAMS.has(stream);\n}\n","import { ItdConfigError } from '../errors.js';\nimport {\n DEFAULT_FILE_STREAM_BUFFER_BYTES,\n type FileStreamOptions,\n FileTransferMode,\n} from './contracts.js';\nimport { optionalBytes } from './limits.js';\n\n/** Проверяет и дополняет настройки потока. @internal */\nexport function resolveFileStreamOptions(\n options: FileStreamOptions,\n defaultMaxBytes?: number,\n): {\n mode: FileTransferMode;\n maxBytes: number | undefined;\n streamBufferBytes: number;\n} {\n const mode = options.mode ?? FileTransferMode.Buffer;\n if (mode !== FileTransferMode.Buffer && mode !== FileTransferMode.Stream) {\n throw new ItdConfigError(`mode вложения должен быть 'buffer' или 'stream', получено: ${mode}`);\n }\n if (mode === FileTransferMode.Stream && typeof ReadableStream === 'undefined') {\n throw new ItdConfigError(\n \"эта среда не поддерживает ReadableStream; используйте mode: 'buffer'\",\n );\n }\n\n const maxBytes = optionalBytes(options.maxBytes ?? defaultMaxBytes, 'maxBytes');\n const streamBufferBytes =\n optionalBytes(\n options.streamBufferBytes ?? DEFAULT_FILE_STREAM_BUFFER_BYTES,\n 'streamBufferBytes',\n ) ?? DEFAULT_FILE_STREAM_BUFFER_BYTES;\n if (streamBufferBytes === 0) {\n throw new ItdConfigError('streamBufferBytes должен быть больше нуля');\n }\n\n return { mode, maxBytes, streamBufferBytes };\n}\n","import { AUTH_FLAG_COOKIE, CookieJar } from './cookies.js';\nimport { ItdConfigError } from './errors.js';\nimport {\n collectKeyValueStoreKeys,\n createKeyValueStore,\n type EnumerableKeyValueStore,\n isEnumerableKeyValueStore,\n MemoryKeyValueStore,\n} from './key-value-store.js';\nimport { copySession, type ItdSession, type TokenStorage } from './storage.js';\n\n/**\n * Хранилище сессий нескольких аккаунтов.\n *\n * Отличается от {@link TokenStorage} тем, что каждый метод получает **имя аккаунта**:\n * так адаптер сам решает, как строить ключ, и Redis, БД или связка ключей получают то,\n * что им нужно. Имя приходит ровно тем, под которым аккаунт заведён в {@link ItdAccounts}:\n * библиотека его не нормализует и не экранирует — префиксы, экранирование и ограничения\n * на длину ключа остаются за адаптером.\n *\n * Все методы могут быть как синхронными, так и асинхронными.\n *\n * @example Хранилище поверх enumerable Redis backend\n * ```ts\n * const storage = createMultiTokenStorage(withNamespace(redisStore, 'itd'));\n * ```\n */\nexport interface MultiTokenStorage {\n /** Прочитать сессию аккаунта. `null`, если её нет. */\n get(account: string): ItdSession | null | Promise<ItdSession | null>;\n /** Сохранить сессию аккаунта целиком. */\n set(account: string, session: ItdSession): void | Promise<void>;\n /** Удалить сессию аккаунта. Вызывается при выходе и при неудачном обновлении токена. */\n clear(account: string): void | Promise<void>;\n /**\n * Имена сохранённых записей — по ним `ItdAccounts.restore()` находит кандидатов\n * после перезапуска процесса.\n *\n * Список ведёт сам адаптер: у файлового и памятного он виден из самой записи,\n * а хранилищу «ключ — значение» придётся держать множество имён рядом с сессиями.\n * Перед восстановлением контейнер читает каждую запись и пропускает оставшийся после\n * выхода одинокий `deviceId`: без токена или refresh-сессии авторизоваться невозможно.\n * Пустой список означает лишь то, что кандидатов нет, — сами записи при этом могут быть\n * доступны по имени.\n */\n accounts(): readonly string[] | Promise<readonly string[]>;\n}\n\n/**\n * Срез мультихранилища как обычное {@link TokenStorage} — в таком виде его получает\n * отдельный `ItdClient`, который про соседние аккаунты ничего не знает.\n */\nexport function scopedTokenStorage(storage: MultiTokenStorage, account: string): TokenStorage {\n return {\n get: () => storage.get(account),\n set: (session) => storage.set(account, session),\n clear: () => storage.clear(account),\n };\n}\n\n/**\n * Можно ли восстановить авторизованный аккаунт из записи.\n *\n * Один `deviceId` не считается сессией: он намеренно переживает выход, но не позволяет\n * выполнить ни одного авторизованного запроса.\n *\n * @internal\n */\nexport function isRestorableSession(session: ItdSession | null): session is ItdSession {\n if (!session) return false;\n if (session.accessToken || session.refreshToken) return true;\n\n const jar = new CookieJar();\n jar.deserialize(session.cookies);\n return jar.has(AUTH_FLAG_COOKIE);\n}\n\n/**\n * Управляемый срез хранилища для клиента аккаунта.\n *\n * После `revoke()` новые записи не доходят до общего хранилища, а `drain()` позволяет\n * дождаться уже начатых. Так удалённый клиент или завершившийся с опозданием refresh\n * не воскресят сессию после `removeAccount(..., { forget: true })`.\n *\n * @internal\n */\nexport interface ControlledTokenStorage {\n storage: TokenStorage;\n revoke(): void;\n drain(): Promise<void>;\n}\n\n/** @internal */\nexport function controlledTokenStorage(\n storage: MultiTokenStorage,\n account: string,\n): ControlledTokenStorage {\n let revoked = false;\n const pending = new Set<Promise<void>>();\n\n const mutate = (operation: () => void | Promise<void>): Promise<void> => {\n if (revoked) return Promise.resolve();\n\n const promise = Promise.resolve().then(operation);\n pending.add(promise);\n void promise.then(\n () => pending.delete(promise),\n () => pending.delete(promise),\n );\n return promise;\n };\n\n return {\n storage: {\n get: () => (revoked ? null : storage.get(account)),\n set: (session) => mutate(() => storage.set(account, session)),\n clear: () => mutate(() => storage.clear(account)),\n },\n revoke() {\n revoked = true;\n },\n async drain() {\n await Promise.all([...pending]);\n },\n };\n}\n\n/**\n * Мультихранилище в памяти процесса — вариант по умолчанию.\n *\n * Сессии теряются при перезапуске. Для долгоживущих ботов возьмите `FileMultiTokenStorage`\n * из `itd-api/node` либо соберите своё через {@link createMultiTokenStorage}.\n */\nexport class MemoryMultiTokenStorage implements MultiTokenStorage {\n readonly #store = new MemoryKeyValueStore<ItdSession>();\n\n constructor(initial?: Readonly<Record<string, ItdSession>> | null) {\n for (const [account, session] of Object.entries(initial ?? {})) {\n this.#store.set(accountKey(account), copySession(session));\n }\n }\n\n get(account: string): ItdSession | null {\n const session = this.#store.get(accountKey(account));\n return session ? copySession(session) : null;\n }\n\n set(account: string, session: ItdSession): void {\n this.#store.set(accountKey(account), copySession(session));\n }\n\n clear(account: string): void {\n this.#store.delete(accountKey(account));\n }\n\n accounts(): string[] {\n return this.#store.keys(ACCOUNT_KEY_PREFIX).map(accountFromKey);\n }\n}\n\nconst ACCOUNT_KEY_PREFIX = 'accounts/';\n\nfunction accountKey(account: string): string {\n return `${ACCOUNT_KEY_PREFIX}${encodeURIComponent(account)}`;\n}\n\nfunction accountFromKey(key: string): string {\n return decodeURIComponent(key.slice(ACCOUNT_KEY_PREFIX.length));\n}\n\n/** Настройки доменного адаптера нескольких сессий. */\nexport interface MultiTokenStorageAdapterOptions {\n /** Префикс ключей аккаунтов. По умолчанию `accounts/`. */\n prefix?: string | undefined;\n}\n\n/**\n * Создаёт доменное хранилище нескольких сессий поверх enumerable key-value backend.\n *\n * Отдельный индекс аккаунтов не используется: `accounts()` перечисляет ключи backend. Поэтому\n * запись сессии не может разойтись с индексом, но backend обязан эффективно поддерживать `keys`.\n */\nexport function createMultiTokenStorage(\n store: EnumerableKeyValueStore<ItdSession>,\n options: MultiTokenStorageAdapterOptions = {},\n): MultiTokenStorage {\n const prefix = options.prefix ?? ACCOUNT_KEY_PREFIX;\n if (typeof prefix !== 'string') {\n throw new ItdConfigError('prefix MultiTokenStorage должен быть строкой');\n }\n const backend = createKeyValueStore(store);\n if (!isEnumerableKeyValueStore(backend)) {\n throw new ItdConfigError('MultiTokenStorage требует KeyValueStore с методом keys()');\n }\n const key = (account: string) => `${prefix}${encodeURIComponent(account)}`;\n return {\n async get(account) {\n const session = await backend.get(key(account));\n return session ? copySession(session) : null;\n },\n set: (account, session) => backend.set(key(account), copySession(session)),\n clear: (account) => backend.delete(key(account)),\n async accounts() {\n const keys = await collectKeyValueStoreKeys(backend, prefix);\n return keys.map((value) => decodeURIComponent(value.slice(prefix.length)));\n },\n };\n}\n"],"x_google_ignoreList":[0],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { r as TokenStorage, t as ItdSession } from "./storage-
|
|
2
|
-
//#region src/core/attachments.d.ts
|
|
1
|
+
import { o as EnumerableKeyValueStore, r as TokenStorage, t as ItdSession } from "./storage-Doe3lpFQ.cjs";
|
|
2
|
+
//#region src/core/attachments/contracts.d.ts
|
|
3
3
|
/** Способ передачи содержимого вложения. */
|
|
4
4
|
declare const FileTransferMode: Readonly<{
|
|
5
5
|
/** Сначала получить файл целиком, затем отправить его как `Blob`. */
|
|
@@ -84,20 +84,6 @@ interface FromStreamOptions extends Omit<FileStreamOptions, 'mode'> {
|
|
|
84
84
|
/** Размер, если он известен заранее. */
|
|
85
85
|
size?: number | undefined;
|
|
86
86
|
}
|
|
87
|
-
/** Создаёт URL-источник в выбранном режиме. */
|
|
88
|
-
declare function fromUrl(url: string, options: UrlFileOptions & {
|
|
89
|
-
mode: typeof FileTransferMode.Stream;
|
|
90
|
-
}): StreamFile;
|
|
91
|
-
declare function fromUrl(url: string, options?: UrlFileOptions & {
|
|
92
|
-
mode?: typeof FileTransferMode.Buffer;
|
|
93
|
-
}): LazyFile;
|
|
94
|
-
declare function fromUrl(url: string, options: UrlFileOptions): LazyFile | StreamFile;
|
|
95
|
-
/**
|
|
96
|
-
* Создаёт повторяемый пользовательский поток.
|
|
97
|
-
*
|
|
98
|
-
* Фабрика вызывается заново для каждой попытки; возвращать один и тот же поток нельзя.
|
|
99
|
-
*/
|
|
100
|
-
declare function fromStream(factory: (context: FileContext) => ReadableStream<Uint8Array> | FileStreamContent | Promise<ReadableStream<Uint8Array> | FileStreamContent>, options?: FromStreamOptions): StreamFile;
|
|
101
87
|
//#endregion
|
|
102
88
|
//#region src/core/multi-storage.d.ts
|
|
103
89
|
/**
|
|
@@ -111,20 +97,9 @@ declare function fromStream(factory: (context: FileContext) => ReadableStream<Ui
|
|
|
111
97
|
*
|
|
112
98
|
* Все методы могут быть как синхронными, так и асинхронными.
|
|
113
99
|
*
|
|
114
|
-
* @example
|
|
100
|
+
* @example Хранилище поверх enumerable Redis backend
|
|
115
101
|
* ```ts
|
|
116
|
-
* const storage = createMultiTokenStorage(
|
|
117
|
-
* get: async (account) => JSON.parse((await redis.get(`itd:session:${account}`)) ?? 'null'),
|
|
118
|
-
* set: async (account, session) => {
|
|
119
|
-
* await redis.set(`itd:session:${account}`, JSON.stringify(session));
|
|
120
|
-
* await redis.sadd('itd:accounts', account);
|
|
121
|
-
* },
|
|
122
|
-
* clear: async (account) => {
|
|
123
|
-
* await redis.del(`itd:session:${account}`);
|
|
124
|
-
* await redis.srem('itd:accounts', account);
|
|
125
|
-
* },
|
|
126
|
-
* accounts: () => redis.smembers('itd:accounts'),
|
|
127
|
-
* });
|
|
102
|
+
* const storage = createMultiTokenStorage(withNamespace(redisStore, 'itd'));
|
|
128
103
|
* ```
|
|
129
104
|
*/
|
|
130
105
|
interface MultiTokenStorage {
|
|
@@ -166,33 +141,18 @@ declare class MemoryMultiTokenStorage implements MultiTokenStorage {
|
|
|
166
141
|
clear(account: string): void;
|
|
167
142
|
accounts(): string[];
|
|
168
143
|
}
|
|
169
|
-
/**
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
declare function createMultiTokenStorage(handlers: MultiTokenStorage): MultiTokenStorage;
|
|
174
|
-
/** Источник, который читается и пишется целиком: файл, ключ в `localStorage`, строка в БД. */
|
|
175
|
-
interface RecordStorageSource {
|
|
176
|
-
/** Прочитать все сессии разом. `null` — записи ещё нет. */
|
|
177
|
-
read(): Promise<Record<string, ItdSession> | null>;
|
|
178
|
-
/** Записать все сессии разом. */
|
|
179
|
-
write(record: Record<string, ItdSession>): Promise<void>;
|
|
180
|
-
/** Вызывается вместо {@link RecordStorageSource.write}, когда не осталось ни одной сессии. */
|
|
181
|
-
remove?(): Promise<void>;
|
|
144
|
+
/** Настройки доменного адаптера нескольких сессий. */
|
|
145
|
+
interface MultiTokenStorageAdapterOptions {
|
|
146
|
+
/** Префикс ключей аккаунтов. По умолчанию `accounts/`. */
|
|
147
|
+
prefix?: string | undefined;
|
|
182
148
|
}
|
|
183
149
|
/**
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
* Решает главную проблему такого способа хранения — **гонку «прочитать, изменить,
|
|
187
|
-
* записать»**: десять аккаунтов пишут в одну запись, и наивная реализация теряла бы
|
|
188
|
-
* чужие сессии. Источник читается один раз, дальше слепок живёт в памяти, а записи
|
|
189
|
-
* выстраиваются в цепочку и идут по очереди.
|
|
150
|
+
* Создаёт доменное хранилище нескольких сессий поверх enumerable key-value backend.
|
|
190
151
|
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
* направленных на один источник в одном процессе.
|
|
152
|
+
* Отдельный индекс аккаунтов не используется: `accounts()` перечисляет ключи backend. Поэтому
|
|
153
|
+
* запись сессии не может разойтись с индексом, но backend обязан эффективно поддерживать `keys`.
|
|
194
154
|
*/
|
|
195
|
-
declare function
|
|
155
|
+
declare function createMultiTokenStorage(store: EnumerableKeyValueStore<ItdSession>, options?: MultiTokenStorageAdapterOptions): MultiTokenStorage;
|
|
196
156
|
//#endregion
|
|
197
|
-
export {
|
|
198
|
-
//# sourceMappingURL=multi-storage-
|
|
157
|
+
export { UrlFile as _, scopedTokenStorage as a, FileContent as c, FileStreamContent as d, FileStreamOptions as f, StreamFile as g, LazyFile as h, createMultiTokenStorage as i, FileContext as l, FromStreamOptions as m, MultiTokenStorage as n, DEFAULT_FILE_STREAM_BUFFER_BYTES as o, FileTransferMode as p, MultiTokenStorageAdapterOptions as r, DEFAULT_URL_FILE_MAX_BYTES as s, MemoryMultiTokenStorage as t, FileInput as u, UrlFileOptions as v };
|
|
158
|
+
//# sourceMappingURL=multi-storage-s_PcWPGH.d.cts.map
|
package/dist/node.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_multi_storage = require("./multi-storage-
|
|
3
|
-
const require_storage = require("./storage-
|
|
2
|
+
const require_multi_storage = require("./multi-storage-mBuCOVZY.cjs");
|
|
3
|
+
const require_storage = require("./storage-dF8Tio5y.cjs");
|
|
4
4
|
//#region src/node.ts
|
|
5
5
|
/**
|
|
6
6
|
* `itd-api/node` — файловая система для Node, Bun и Deno.
|
|
@@ -42,11 +42,12 @@ function fromPath(path, options = {}) {
|
|
|
42
42
|
actual: info.size
|
|
43
43
|
});
|
|
44
44
|
const file = createReadStream(path, {
|
|
45
|
-
highWaterMark: Math.min(resolved.streamBufferBytes,
|
|
45
|
+
highWaterMark: Math.min(resolved.streamBufferBytes, 1048576),
|
|
46
46
|
...signal ? { signal } : {}
|
|
47
47
|
});
|
|
48
|
+
const source = Readable.toWeb(file);
|
|
48
49
|
return {
|
|
49
|
-
stream: require_multi_storage.boundedFileStream(
|
|
50
|
+
stream: require_multi_storage.boundedFileStream(source, {
|
|
50
51
|
...resolved.maxBytes !== void 0 ? { maxBytes: resolved.maxBytes } : {},
|
|
51
52
|
streamBufferBytes: resolved.streamBufferBytes,
|
|
52
53
|
...signal ? { signal } : {}
|
|
@@ -85,25 +86,22 @@ function fromPath(path, options = {}) {
|
|
|
85
86
|
/**
|
|
86
87
|
* Читает и разбирает JSON-файл.
|
|
87
88
|
*
|
|
88
|
-
* Отсутствующий файл означает `
|
|
89
|
-
*
|
|
90
|
-
* а мультихранилище требует строгого разбора: молча затереть файл с несколькими токенами
|
|
91
|
-
* особенно опасно.
|
|
89
|
+
* Отсутствующий файл означает `undefined`. Повреждённый JSON не считается пустым хранилищем:
|
|
90
|
+
* иначе следующая запись могла бы незаметно затереть данные.
|
|
92
91
|
*/
|
|
93
|
-
async function readJsonFile(path
|
|
92
|
+
async function readJsonFile(path) {
|
|
94
93
|
let raw;
|
|
95
94
|
try {
|
|
96
95
|
const { readFile } = await import("node:fs/promises");
|
|
97
96
|
raw = await readFile(path, "utf8");
|
|
98
97
|
} catch (error) {
|
|
99
|
-
if (hasErrorCode(error, "ENOENT")) return
|
|
98
|
+
if (hasErrorCode(error, "ENOENT")) return void 0;
|
|
100
99
|
throw error;
|
|
101
100
|
}
|
|
102
101
|
try {
|
|
103
102
|
return JSON.parse(raw);
|
|
104
103
|
} catch {
|
|
105
|
-
|
|
106
|
-
throw new require_storage.ItdConfigError(`Файл ${path} повреждён: ожидался JSON с сессиями аккаунтов. Исправьте файл или перенесите его перед следующим сохранением.`);
|
|
104
|
+
throw new require_storage.ItdConfigError(`Файл ${path} повреждён: ожидался JSON key-value хранилища. Исправьте файл или перенесите его перед следующим сохранением.`);
|
|
107
105
|
}
|
|
108
106
|
}
|
|
109
107
|
/**
|
|
@@ -131,6 +129,49 @@ async function removeFile(path) {
|
|
|
131
129
|
const { rm } = await import("node:fs/promises");
|
|
132
130
|
await rm(path, { force: true });
|
|
133
131
|
}
|
|
132
|
+
/** Версия общего формата файлового key-value backend. */
|
|
133
|
+
const FILE_STORE_VERSION = 1;
|
|
134
|
+
/**
|
|
135
|
+
* Key-value backend в одном JSON-файле.
|
|
136
|
+
*
|
|
137
|
+
* Изменения сериализуются внутри экземпляра и записываются атомарным переименованием. Несколько
|
|
138
|
+
* экземпляров или процессов, направленных на один путь, требуют внешней синхронизации.
|
|
139
|
+
*/
|
|
140
|
+
var FileKeyValueStore = class {
|
|
141
|
+
#path;
|
|
142
|
+
#inner;
|
|
143
|
+
/** @param path путь к JSON-файлу. Для секретов добавьте его в `.gitignore`. */
|
|
144
|
+
constructor(path) {
|
|
145
|
+
this.#path = path;
|
|
146
|
+
this.#inner = require_storage.createRecordKeyValueStore({
|
|
147
|
+
read: () => this.#read(),
|
|
148
|
+
write: (values) => writeJsonAtomic(path, {
|
|
149
|
+
version: FILE_STORE_VERSION,
|
|
150
|
+
values
|
|
151
|
+
}),
|
|
152
|
+
delete: () => removeFile(path)
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
get(key) {
|
|
156
|
+
return Promise.resolve(this.#inner.get(key));
|
|
157
|
+
}
|
|
158
|
+
set(key, value) {
|
|
159
|
+
return Promise.resolve(this.#inner.set(key, value));
|
|
160
|
+
}
|
|
161
|
+
delete(key) {
|
|
162
|
+
return Promise.resolve(this.#inner.delete(key));
|
|
163
|
+
}
|
|
164
|
+
keys(prefix) {
|
|
165
|
+
return Promise.resolve(this.#inner.keys(prefix));
|
|
166
|
+
}
|
|
167
|
+
async #read() {
|
|
168
|
+
const parsed = await readJsonFile(this.#path);
|
|
169
|
+
if (parsed === void 0) return void 0;
|
|
170
|
+
const envelope = typeof parsed === "object" && parsed !== null ? parsed : void 0;
|
|
171
|
+
if (envelope?.version !== FILE_STORE_VERSION || typeof envelope.values !== "object" || envelope.values === null || Array.isArray(envelope.values)) throw new require_storage.ItdConfigError(`Файл ${this.#path} имеет неподдерживаемый формат: ожидается { version: ${FILE_STORE_VERSION}, values }. Возьмите другой путь, чтобы не перезаписать посторонние данные.`);
|
|
172
|
+
return envelope.values;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
134
175
|
/**
|
|
135
176
|
* Хранит сессию в файле.
|
|
136
177
|
*
|
|
@@ -148,36 +189,21 @@ async function removeFile(path) {
|
|
|
148
189
|
* ```
|
|
149
190
|
*/
|
|
150
191
|
var FileTokenStorage = class {
|
|
151
|
-
#
|
|
152
|
-
/**
|
|
153
|
-
* Цепочка операций с файлом. Запись и удаление выполняются последовательно
|
|
154
|
-
* в порядке вызова; ошибка одной операции не останавливает следующие.
|
|
155
|
-
*/
|
|
156
|
-
#writing = Promise.resolve();
|
|
192
|
+
#inner;
|
|
157
193
|
/** @param path путь к файлу сессии. Добавьте его в `.gitignore`. */
|
|
158
194
|
constructor(path) {
|
|
159
|
-
this.#
|
|
195
|
+
this.#inner = require_storage.createTokenStorage(new FileKeyValueStore(path));
|
|
160
196
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const parsed = await readJsonFile(this.#path);
|
|
164
|
-
return typeof parsed === "object" && parsed !== null ? require_storage.copySession(parsed) : null;
|
|
197
|
+
get() {
|
|
198
|
+
return Promise.resolve(this.#inner.get());
|
|
165
199
|
}
|
|
166
200
|
set(session) {
|
|
167
|
-
|
|
168
|
-
return this.#enqueue(() => writeJsonAtomic(this.#path, snapshot));
|
|
201
|
+
return Promise.resolve(this.#inner.set(session));
|
|
169
202
|
}
|
|
170
203
|
clear() {
|
|
171
|
-
return this.#
|
|
172
|
-
}
|
|
173
|
-
/** Добавляет файловую операцию в очередь. */
|
|
174
|
-
#enqueue(operation) {
|
|
175
|
-
this.#writing = this.#writing.then(operation, operation);
|
|
176
|
-
return this.#writing;
|
|
204
|
+
return Promise.resolve(this.#inner.clear());
|
|
177
205
|
}
|
|
178
206
|
};
|
|
179
|
-
/** Версия формата файла с сессиями нескольких аккаунтов. */
|
|
180
|
-
const SESSIONS_FILE_VERSION = 1;
|
|
181
207
|
/**
|
|
182
208
|
* Хранит сессии нескольких аккаунтов в одном файле.
|
|
183
209
|
*
|
|
@@ -185,8 +211,7 @@ const SESSIONS_FILE_VERSION = 1;
|
|
|
185
211
|
* и без общего слепка с очередью записей они теряли бы сессии друг друга. Как и
|
|
186
212
|
* {@link FileTokenStorage}, пишет через временный файл с правами `0600`.
|
|
187
213
|
*
|
|
188
|
-
*
|
|
189
|
-
* не спутать с однопользовательским, и чужой не будет молча перезаписан.
|
|
214
|
+
* Использует тот же версионированный {@link FileKeyValueStore}, что и одиночное хранилище.
|
|
190
215
|
*
|
|
191
216
|
* @example
|
|
192
217
|
* ```ts
|
|
@@ -197,19 +222,10 @@ const SESSIONS_FILE_VERSION = 1;
|
|
|
197
222
|
* ```
|
|
198
223
|
*/
|
|
199
224
|
var FileMultiTokenStorage = class {
|
|
200
|
-
#path;
|
|
201
225
|
#inner;
|
|
202
226
|
/** @param path путь к файлу сессий. Добавьте его в `.gitignore`. */
|
|
203
227
|
constructor(path) {
|
|
204
|
-
this.#
|
|
205
|
-
this.#inner = require_multi_storage.createRecordMultiStorage({
|
|
206
|
-
read: () => this.#read(),
|
|
207
|
-
write: (accounts) => writeJsonAtomic(path, {
|
|
208
|
-
version: SESSIONS_FILE_VERSION,
|
|
209
|
-
accounts
|
|
210
|
-
}),
|
|
211
|
-
remove: () => removeFile(path)
|
|
212
|
-
});
|
|
228
|
+
this.#inner = require_multi_storage.createMultiTokenStorage(new FileKeyValueStore(path));
|
|
213
229
|
}
|
|
214
230
|
get(account) {
|
|
215
231
|
return Promise.resolve(this.#inner.get(account));
|
|
@@ -223,16 +239,9 @@ var FileMultiTokenStorage = class {
|
|
|
223
239
|
accounts() {
|
|
224
240
|
return Promise.resolve(this.#inner.accounts());
|
|
225
241
|
}
|
|
226
|
-
async #read() {
|
|
227
|
-
const parsed = await readJsonFile(this.#path, true);
|
|
228
|
-
if (parsed === null) return null;
|
|
229
|
-
const envelope = typeof parsed === "object" && parsed !== null ? parsed : void 0;
|
|
230
|
-
const accounts = envelope?.accounts;
|
|
231
|
-
if (envelope?.version !== SESSIONS_FILE_VERSION || typeof accounts !== "object" || accounts === null || Array.isArray(accounts)) throw new require_storage.ItdConfigError(`Файл ${this.#path} имеет неподдерживаемый формат: ожидается { version: ${SESSIONS_FILE_VERSION}, accounts }. Возможно, это файл одиночной сессии или файл другой версии — возьмите отдельный путь, чтобы не перезаписать его.`);
|
|
232
|
-
return accounts;
|
|
233
|
-
}
|
|
234
242
|
};
|
|
235
243
|
//#endregion
|
|
244
|
+
exports.FileKeyValueStore = FileKeyValueStore;
|
|
236
245
|
exports.FileMultiTokenStorage = FileMultiTokenStorage;
|
|
237
246
|
exports.FileTokenStorage = FileTokenStorage;
|
|
238
247
|
exports.fromPath = fromPath;
|
package/dist/node.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.cjs","names":["resolveFileStreamOptions","FileTransferMode","ItdFileError","ItdFileErrorReason","boundedFileStream","ItdConfigError","#path","#writing","copySession","#enqueue","#inner","createRecordMultiStorage","#read"],"sources":["../src/node.ts"],"sourcesContent":["/**\n * `itd-api/node` — файловая система для Node, Bun и Deno.\n *\n * Здесь лежит только то, что требует `node:fs` и потому не может попасть в основной бандл:\n * иначе браузерные сборщики пытались бы разрешить этот модуль. Клиент, аккаунты, билдеры,\n * типы и всё остальное берутся из `itd-api` — одинаково на всех платформах.\n *\n * @example\n * ```ts\n * import { ItdClient } from 'itd-api';\n * import { FileTokenStorage, fromPath } from 'itd-api/node';\n *\n * // Когда сессия уже сохранена, `auth` не нужен — токен возьмётся из хранилища.\n * const itd = new ItdClient({ storage: new FileTokenStorage('./.itd-session.json') });\n *\n * await itd.posts.create((p) => p.content('привет').attach(fromPath('./photo.jpg')));\n * ```\n *\n * @packageDocumentation\n */\n\nimport {\n boundedFileStream,\n type FileStreamOptions,\n FileTransferMode,\n type LazyFile,\n resolveFileStreamOptions,\n type StreamFile,\n} from './core/attachments.js';\nimport { ItdConfigError, ItdFileError, ItdFileErrorReason } from './core/errors.js';\nimport { createRecordMultiStorage, type MultiTokenStorage } from './core/multi-storage.js';\nimport { copySession, type ItdSession, type TokenStorage } from './core/storage.js';\n\n/** Проверяет код системной ошибки без привязки к типам конкретного рантайма. */\nfunction hasErrorCode(error: unknown, code: string): boolean {\n return typeof error === 'object' && error !== null && 'code' in error && error.code === code;\n}\n\n/** Что можно уточнить у файла с диска. */\nexport interface PathFileOptions extends FileStreamOptions {\n /** Имя файла. По умолчанию — имя из пути. */\n filename?: string | undefined;\n /** MIME-тип. По умолчанию определяется по расширению. */\n contentType?: string | undefined;\n}\n\n/**\n * Вложение из файла на диске.\n *\n * Клиент настраивать не нужно: чтение диска приезжает вместе со значением, а не через\n * опцию, поэтому один и тот же клиент принимает и файлы с диска, и всё остальное.\n *\n * По умолчанию файл читается целиком непосредственно перед отправкой. Режим\n * `{ mode: 'stream' }` передаёт его частями и открывает заново при повторной попытке.\n * Модуль `node:fs` подключается динамически.\n *\n * @example\n * ```ts\n * import { ItdClient } from 'itd-api';\n * import { fromPath } from 'itd-api/node';\n *\n * const itd = new ItdClient({ auth });\n *\n * await itd.posts.create((p) =>\n * p.content('смотрите').attach(fromPath('./photo.jpg')),\n * );\n * ```\n */\nexport function fromPath(\n path: string,\n options: PathFileOptions & { mode: typeof FileTransferMode.Stream },\n): StreamFile;\nexport function fromPath(\n path: string,\n options?: PathFileOptions & { mode?: typeof FileTransferMode.Buffer },\n): LazyFile;\nexport function fromPath(path: string, options: PathFileOptions): LazyFile | StreamFile;\nexport function fromPath(path: string, options: PathFileOptions = {}): LazyFile | StreamFile {\n const resolved = resolveFileStreamOptions(options);\n\n if (resolved.mode === FileTransferMode.Stream) {\n return {\n open: async ({ signal }) => {\n const [{ createReadStream }, { stat }, { basename }, { Readable }] = await Promise.all([\n import('node:fs'),\n import('node:fs/promises'),\n import('node:path'),\n import('node:stream'),\n ]);\n const info = await stat(path);\n if (resolved.maxBytes !== undefined && info.size > resolved.maxBytes) {\n throw new ItdFileError(\n `файл ${path} больше предела в ${resolved.maxBytes} байт: ${info.size}`,\n {\n reason: ItdFileErrorReason.TooLarge,\n limit: resolved.maxBytes,\n actual: info.size,\n },\n );\n }\n\n const file = createReadStream(path, {\n highWaterMark: Math.min(resolved.streamBufferBytes, 1024 * 1024),\n ...(signal ? { signal } : {}),\n });\n const source = Readable.toWeb(file) as ReadableStream<Uint8Array>;\n return {\n stream: boundedFileStream(source, {\n ...(resolved.maxBytes !== undefined ? { maxBytes: resolved.maxBytes } : {}),\n streamBufferBytes: resolved.streamBufferBytes,\n ...(signal ? { signal } : {}),\n }),\n filename: options.filename ?? basename(path),\n ...(options.contentType ? { contentType: options.contentType } : {}),\n size: info.size,\n close: () => {\n file.destroy();\n },\n };\n },\n };\n }\n\n return {\n load: async ({ signal }) => {\n const [{ readFile, stat }, { basename }] = await Promise.all([\n import('node:fs/promises'),\n import('node:path'),\n ]);\n if (resolved.maxBytes !== undefined) {\n const info = await stat(path);\n if (info.size > resolved.maxBytes) {\n throw new ItdFileError(\n `файл ${path} больше предела в ${resolved.maxBytes} байт: ${info.size}`,\n {\n reason: ItdFileErrorReason.TooLarge,\n limit: resolved.maxBytes,\n actual: info.size,\n },\n );\n }\n }\n\n const file = new Uint8Array(await readFile(path, signal ? { signal } : undefined));\n if (resolved.maxBytes !== undefined && file.byteLength > resolved.maxBytes) {\n throw new ItdFileError(\n `файл ${path} больше предела в ${resolved.maxBytes} байт: ${file.byteLength}`,\n {\n reason: ItdFileErrorReason.TooLarge,\n limit: resolved.maxBytes,\n actual: file.byteLength,\n },\n );\n }\n\n return {\n file,\n filename: options.filename ?? basename(path),\n ...(options.contentType ? { contentType: options.contentType } : {}),\n };\n },\n };\n}\n\n/**\n * Читает и разбирает JSON-файл.\n *\n * Отсутствующий файл означает `null`. Остальные ошибки файловой системы не скрываются.\n * Одиночное хранилище ради обратной совместимости считает повреждённый JSON пустым,\n * а мультихранилище требует строгого разбора: молча затереть файл с несколькими токенами\n * особенно опасно.\n */\nasync function readJsonFile(path: string, strict = false): Promise<unknown> {\n let raw: string;\n try {\n const { readFile } = await import('node:fs/promises');\n raw = await readFile(path, 'utf8');\n } catch (error) {\n if (hasErrorCode(error, 'ENOENT')) return null;\n throw error;\n }\n\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n if (!strict) return null;\n throw new ItdConfigError(\n `Файл ${path} повреждён: ожидался JSON с сессиями аккаунтов. ` +\n 'Исправьте файл или перенесите его перед следующим сохранением.',\n );\n }\n}\n\n/**\n * Записывает JSON во временный файл и переименовывает его поверх целевого.\n *\n * Права `0600` (чтение и запись только владельцу) — в файле лежат токены. Переименование\n * атомарно, поэтому падение процесса посреди сохранения не оставит повреждённого файла.\n */\nasync function writeJsonAtomic(path: string, value: unknown): Promise<void> {\n const { writeFile, rename, unlink } = await import('node:fs/promises');\n\n // Временный файл уникален на процесс и вызов — иначе параллельные записи (в том числе\n // из разных процессов) делят один `.tmp`.\n const pid = typeof process !== 'undefined' ? process.pid : 0;\n const temporary = `${path}.${pid}.${Math.random().toString(36).slice(2)}.tmp`;\n\n try {\n await writeFile(temporary, JSON.stringify(value, null, 2), { encoding: 'utf8', mode: 0o600 });\n await rename(temporary, path);\n } catch (error) {\n await unlink(temporary).catch(() => {});\n throw error;\n }\n}\n\n/** Удаляет файл. Благодаря `force` отсутствие файла ошибкой не считается. */\nasync function removeFile(path: string): Promise<void> {\n const { rm } = await import('node:fs/promises');\n await rm(path, { force: true });\n}\n\n/**\n * Хранит сессию в файле.\n *\n * Нужна долгоживущим процессам: без неё бот при каждом запуске входит заново — а вход\n * требует решённой капчи, да и серия входов подряд может привести к временной блокировке\n * аккаунта. С сохранённой сессией опция `auth` не нужна вовсе.\n *\n * Файл создаётся с правами `0600` (чтение и запись только владельцу) — в нём лежат токены.\n * Запись идёт через временный файл с последующим переименованием, поэтому падение процесса\n * посреди сохранения не оставит повреждённую сессию.\n *\n * @example\n * ```ts\n * const itd = new ItdClient({ storage: new FileTokenStorage('./.itd-session.json') });\n * ```\n */\nexport class FileTokenStorage implements TokenStorage {\n readonly #path: string;\n /**\n * Цепочка операций с файлом. Запись и удаление выполняются последовательно\n * в порядке вызова; ошибка одной операции не останавливает следующие.\n */\n #writing: Promise<void> = Promise.resolve();\n\n /** @param path путь к файлу сессии. Добавьте его в `.gitignore`. */\n constructor(path: string) {\n this.#path = path;\n }\n\n async get(): Promise<ItdSession | null> {\n // Чтение, начатое сразу после `set()`/`clear()`, должно видеть результат более ранней\n // операции, даже если вызывающий код не сохранил и не дождался её промиса.\n await this.#writing.then(\n () => undefined,\n () => undefined,\n );\n const parsed = await readJsonFile(this.#path);\n return typeof parsed === 'object' && parsed !== null ? copySession(parsed as ItdSession) : null;\n }\n\n set(session: ItdSession): Promise<void> {\n // Пользователь может изменить переданный объект сразу после вызова `set()`.\n // В очередь должен попасть снимок на момент вызова, а не живая ссылка.\n const snapshot = copySession(session);\n return this.#enqueue(() => writeJsonAtomic(this.#path, snapshot));\n }\n\n clear(): Promise<void> {\n return this.#enqueue(() => removeFile(this.#path));\n }\n\n /** Добавляет файловую операцию в очередь. */\n #enqueue(operation: () => Promise<void>): Promise<void> {\n this.#writing = this.#writing.then(operation, operation);\n return this.#writing;\n }\n}\n\n/** Версия формата файла с сессиями нескольких аккаунтов. */\nconst SESSIONS_FILE_VERSION = 1;\n\n/**\n * Хранит сессии нескольких аккаунтов в одном файле.\n *\n * Разбирается с гонкой «прочитать, изменить, записать»: десять аккаунтов пишут в один файл,\n * и без общего слепка с очередью записей они теряли бы сессии друг друга. Как и\n * {@link FileTokenStorage}, пишет через временный файл с правами `0600`.\n *\n * Формат — конверт `{ version, accounts }`, а не голая карта: так файл нескольких аккаунтов\n * не спутать с однопользовательским, и чужой не будет молча перезаписан.\n *\n * @example\n * ```ts\n * const accounts = new ItdAccounts({\n * storage: new FileMultiTokenStorage('./.itd-sessions.json'),\n * });\n * await accounts.restore();\n * ```\n */\nexport class FileMultiTokenStorage implements MultiTokenStorage {\n readonly #path: string;\n readonly #inner: MultiTokenStorage;\n\n /** @param path путь к файлу сессий. Добавьте его в `.gitignore`. */\n constructor(path: string) {\n this.#path = path;\n this.#inner = createRecordMultiStorage({\n read: () => this.#read(),\n write: (accounts) => writeJsonAtomic(path, { version: SESSIONS_FILE_VERSION, accounts }),\n // Последний аккаунт ушёл — файл с пустой картой выглядел бы мусором.\n remove: () => removeFile(path),\n });\n }\n\n get(account: string): Promise<ItdSession | null> {\n return Promise.resolve(this.#inner.get(account));\n }\n\n set(account: string, session: ItdSession): Promise<void> {\n return Promise.resolve(this.#inner.set(account, session));\n }\n\n clear(account: string): Promise<void> {\n return Promise.resolve(this.#inner.clear(account));\n }\n\n accounts(): Promise<readonly string[]> {\n return Promise.resolve(this.#inner.accounts());\n }\n\n async #read(): Promise<Record<string, ItdSession> | null> {\n const parsed = await readJsonFile(this.#path, true);\n if (parsed === null) return null;\n\n const envelope =\n typeof parsed === 'object' && parsed !== null\n ? (parsed as { version?: unknown; accounts?: unknown })\n : undefined;\n const accounts = envelope?.accounts;\n\n if (\n envelope?.version !== SESSIONS_FILE_VERSION ||\n typeof accounts !== 'object' ||\n accounts === null ||\n Array.isArray(accounts)\n ) {\n throw new ItdConfigError(\n `Файл ${this.#path} имеет неподдерживаемый формат: ожидается ` +\n `{ version: ${SESSIONS_FILE_VERSION}, accounts }. ` +\n 'Возможно, это файл одиночной сессии или файл другой версии — ' +\n 'возьмите отдельный путь, чтобы не перезаписать его.',\n );\n }\n\n return accounts as Record<string, ItdSession>;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"node.cjs","names":["resolveFileStreamOptions","FileTransferMode","ItdFileError","ItdFileErrorReason","boundedFileStream","ItdConfigError","#path","#inner","createRecordKeyValueStore","#read","createTokenStorage","createMultiTokenStorage"],"sources":["../src/node.ts"],"sourcesContent":["/**\n * `itd-api/node` — файловая система для Node, Bun и Deno.\n *\n * Здесь лежит только то, что требует `node:fs` и потому не может попасть в основной бандл:\n * иначе браузерные сборщики пытались бы разрешить этот модуль. Клиент, аккаунты, билдеры,\n * типы и всё остальное берутся из `itd-api` — одинаково на всех платформах.\n *\n * @example\n * ```ts\n * import { ItdClient } from 'itd-api';\n * import { FileTokenStorage, fromPath } from 'itd-api/node';\n *\n * // Когда сессия уже сохранена, `auth` не нужен — токен возьмётся из хранилища.\n * const itd = new ItdClient({ storage: new FileTokenStorage('./.itd-session.json') });\n *\n * await itd.posts.create((p) => p.content('привет').attach(fromPath('./photo.jpg')));\n * ```\n *\n * @packageDocumentation\n */\n\nimport { boundedFileStream } from './core/attachments/bounded-stream.js';\nimport {\n type FileStreamOptions,\n FileTransferMode,\n type LazyFile,\n type StreamFile,\n} from './core/attachments/contracts.js';\nimport { resolveFileStreamOptions } from './core/attachments/options.js';\nimport { ItdConfigError, ItdFileError, ItdFileErrorReason } from './core/errors.js';\nimport { createRecordKeyValueStore, type EnumerableKeyValueStore } from './core/key-value-store.js';\nimport { createMultiTokenStorage, type MultiTokenStorage } from './core/multi-storage.js';\nimport { createTokenStorage, type ItdSession, type TokenStorage } from './core/storage.js';\n\n/** Проверяет код системной ошибки без привязки к типам конкретного рантайма. */\nfunction hasErrorCode(error: unknown, code: string): boolean {\n return typeof error === 'object' && error !== null && 'code' in error && error.code === code;\n}\n\n/** Что можно уточнить у файла с диска. */\nexport interface PathFileOptions extends FileStreamOptions {\n /** Имя файла. По умолчанию — имя из пути. */\n filename?: string | undefined;\n /** MIME-тип. По умолчанию определяется по расширению. */\n contentType?: string | undefined;\n}\n\n/**\n * Вложение из файла на диске.\n *\n * Клиент настраивать не нужно: чтение диска приезжает вместе со значением, а не через\n * опцию, поэтому один и тот же клиент принимает и файлы с диска, и всё остальное.\n *\n * По умолчанию файл читается целиком непосредственно перед отправкой. Режим\n * `{ mode: 'stream' }` передаёт его частями и открывает заново при повторной попытке.\n * Модуль `node:fs` подключается динамически.\n *\n * @example\n * ```ts\n * import { ItdClient } from 'itd-api';\n * import { fromPath } from 'itd-api/node';\n *\n * const itd = new ItdClient({ auth });\n *\n * await itd.posts.create((p) =>\n * p.content('смотрите').attach(fromPath('./photo.jpg')),\n * );\n * ```\n */\nexport function fromPath(\n path: string,\n options: PathFileOptions & { mode: typeof FileTransferMode.Stream },\n): StreamFile;\nexport function fromPath(\n path: string,\n options?: PathFileOptions & { mode?: typeof FileTransferMode.Buffer },\n): LazyFile;\nexport function fromPath(path: string, options: PathFileOptions): LazyFile | StreamFile;\nexport function fromPath(path: string, options: PathFileOptions = {}): LazyFile | StreamFile {\n const resolved = resolveFileStreamOptions(options);\n\n if (resolved.mode === FileTransferMode.Stream) {\n return {\n open: async ({ signal }) => {\n const [{ createReadStream }, { stat }, { basename }, { Readable }] = await Promise.all([\n import('node:fs'),\n import('node:fs/promises'),\n import('node:path'),\n import('node:stream'),\n ]);\n const info = await stat(path);\n if (resolved.maxBytes !== undefined && info.size > resolved.maxBytes) {\n throw new ItdFileError(\n `файл ${path} больше предела в ${resolved.maxBytes} байт: ${info.size}`,\n {\n reason: ItdFileErrorReason.TooLarge,\n limit: resolved.maxBytes,\n actual: info.size,\n },\n );\n }\n\n const file = createReadStream(path, {\n highWaterMark: Math.min(resolved.streamBufferBytes, 1024 * 1024),\n ...(signal ? { signal } : {}),\n });\n const source = Readable.toWeb(file) as ReadableStream<Uint8Array>;\n return {\n stream: boundedFileStream(source, {\n ...(resolved.maxBytes !== undefined ? { maxBytes: resolved.maxBytes } : {}),\n streamBufferBytes: resolved.streamBufferBytes,\n ...(signal ? { signal } : {}),\n }),\n filename: options.filename ?? basename(path),\n ...(options.contentType ? { contentType: options.contentType } : {}),\n size: info.size,\n close: () => {\n file.destroy();\n },\n };\n },\n };\n }\n\n return {\n load: async ({ signal }) => {\n const [{ readFile, stat }, { basename }] = await Promise.all([\n import('node:fs/promises'),\n import('node:path'),\n ]);\n if (resolved.maxBytes !== undefined) {\n const info = await stat(path);\n if (info.size > resolved.maxBytes) {\n throw new ItdFileError(\n `файл ${path} больше предела в ${resolved.maxBytes} байт: ${info.size}`,\n {\n reason: ItdFileErrorReason.TooLarge,\n limit: resolved.maxBytes,\n actual: info.size,\n },\n );\n }\n }\n\n const file = new Uint8Array(await readFile(path, signal ? { signal } : undefined));\n if (resolved.maxBytes !== undefined && file.byteLength > resolved.maxBytes) {\n throw new ItdFileError(\n `файл ${path} больше предела в ${resolved.maxBytes} байт: ${file.byteLength}`,\n {\n reason: ItdFileErrorReason.TooLarge,\n limit: resolved.maxBytes,\n actual: file.byteLength,\n },\n );\n }\n\n return {\n file,\n filename: options.filename ?? basename(path),\n ...(options.contentType ? { contentType: options.contentType } : {}),\n };\n },\n };\n}\n\n/**\n * Читает и разбирает JSON-файл.\n *\n * Отсутствующий файл означает `undefined`. Повреждённый JSON не считается пустым хранилищем:\n * иначе следующая запись могла бы незаметно затереть данные.\n */\nasync function readJsonFile(path: string): Promise<unknown> {\n let raw: string;\n try {\n const { readFile } = await import('node:fs/promises');\n raw = await readFile(path, 'utf8');\n } catch (error) {\n if (hasErrorCode(error, 'ENOENT')) return undefined;\n throw error;\n }\n\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n throw new ItdConfigError(\n `Файл ${path} повреждён: ожидался JSON key-value хранилища. ` +\n 'Исправьте файл или перенесите его перед следующим сохранением.',\n );\n }\n}\n\n/**\n * Записывает JSON во временный файл и переименовывает его поверх целевого.\n *\n * Права `0600` (чтение и запись только владельцу) — в файле лежат токены. Переименование\n * атомарно, поэтому падение процесса посреди сохранения не оставит повреждённого файла.\n */\nasync function writeJsonAtomic(path: string, value: unknown): Promise<void> {\n const { writeFile, rename, unlink } = await import('node:fs/promises');\n\n // Временный файл уникален на процесс и вызов — иначе параллельные записи (в том числе\n // из разных процессов) делят один `.tmp`.\n const pid = typeof process !== 'undefined' ? process.pid : 0;\n const temporary = `${path}.${pid}.${Math.random().toString(36).slice(2)}.tmp`;\n\n try {\n await writeFile(temporary, JSON.stringify(value, null, 2), { encoding: 'utf8', mode: 0o600 });\n await rename(temporary, path);\n } catch (error) {\n await unlink(temporary).catch(() => {});\n throw error;\n }\n}\n\n/** Удаляет файл. Благодаря `force` отсутствие файла ошибкой не считается. */\nasync function removeFile(path: string): Promise<void> {\n const { rm } = await import('node:fs/promises');\n await rm(path, { force: true });\n}\n\n/** Версия общего формата файлового key-value backend. */\nconst FILE_STORE_VERSION = 1;\n\n/**\n * Key-value backend в одном JSON-файле.\n *\n * Изменения сериализуются внутри экземпляра и записываются атомарным переименованием. Несколько\n * экземпляров или процессов, направленных на один путь, требуют внешней синхронизации.\n */\nexport class FileKeyValueStore<T> {\n readonly #path: string;\n readonly #inner: EnumerableKeyValueStore<T>;\n\n /** @param path путь к JSON-файлу. Для секретов добавьте его в `.gitignore`. */\n constructor(path: string) {\n this.#path = path;\n this.#inner = createRecordKeyValueStore<T>({\n read: () => this.#read(),\n write: (values) => writeJsonAtomic(path, { version: FILE_STORE_VERSION, values }),\n delete: () => removeFile(path),\n });\n }\n\n get(key: string): Promise<T | undefined> {\n return Promise.resolve(this.#inner.get(key));\n }\n\n set(key: string, value: T): Promise<void> {\n return Promise.resolve(this.#inner.set(key, value));\n }\n\n delete(key: string): Promise<void> {\n return Promise.resolve(this.#inner.delete(key));\n }\n\n keys(prefix?: string): Promise<Iterable<string> | AsyncIterable<string>> {\n return Promise.resolve(this.#inner.keys(prefix));\n }\n\n async #read(): Promise<Readonly<Record<string, T>> | undefined> {\n const parsed = await readJsonFile(this.#path);\n if (parsed === undefined) return undefined;\n const envelope =\n typeof parsed === 'object' && parsed !== null\n ? (parsed as { version?: unknown; values?: unknown })\n : undefined;\n if (\n envelope?.version !== FILE_STORE_VERSION ||\n typeof envelope.values !== 'object' ||\n envelope.values === null ||\n Array.isArray(envelope.values)\n ) {\n throw new ItdConfigError(\n `Файл ${this.#path} имеет неподдерживаемый формат: ожидается ` +\n `{ version: ${FILE_STORE_VERSION}, values }. Возьмите другой путь, чтобы не ` +\n 'перезаписать посторонние данные.',\n );\n }\n return envelope.values as Readonly<Record<string, T>>;\n }\n}\n\n/**\n * Хранит сессию в файле.\n *\n * Нужна долгоживущим процессам: без неё бот при каждом запуске входит заново — а вход\n * требует решённой капчи, да и серия входов подряд может привести к временной блокировке\n * аккаунта. С сохранённой сессией опция `auth` не нужна вовсе.\n *\n * Файл создаётся с правами `0600` (чтение и запись только владельцу) — в нём лежат токены.\n * Запись идёт через временный файл с последующим переименованием, поэтому падение процесса\n * посреди сохранения не оставит повреждённую сессию.\n *\n * @example\n * ```ts\n * const itd = new ItdClient({ storage: new FileTokenStorage('./.itd-session.json') });\n * ```\n */\nexport class FileTokenStorage implements TokenStorage {\n readonly #inner: TokenStorage;\n\n /** @param path путь к файлу сессии. Добавьте его в `.gitignore`. */\n constructor(path: string) {\n this.#inner = createTokenStorage(new FileKeyValueStore<ItdSession>(path));\n }\n\n get(): Promise<ItdSession | null> {\n return Promise.resolve(this.#inner.get());\n }\n\n set(session: ItdSession): Promise<void> {\n return Promise.resolve(this.#inner.set(session));\n }\n\n clear(): Promise<void> {\n return Promise.resolve(this.#inner.clear());\n }\n}\n\n/**\n * Хранит сессии нескольких аккаунтов в одном файле.\n *\n * Разбирается с гонкой «прочитать, изменить, записать»: десять аккаунтов пишут в один файл,\n * и без общего слепка с очередью записей они теряли бы сессии друг друга. Как и\n * {@link FileTokenStorage}, пишет через временный файл с правами `0600`.\n *\n * Использует тот же версионированный {@link FileKeyValueStore}, что и одиночное хранилище.\n *\n * @example\n * ```ts\n * const accounts = new ItdAccounts({\n * storage: new FileMultiTokenStorage('./.itd-sessions.json'),\n * });\n * await accounts.restore();\n * ```\n */\nexport class FileMultiTokenStorage implements MultiTokenStorage {\n readonly #inner: MultiTokenStorage;\n\n /** @param path путь к файлу сессий. Добавьте его в `.gitignore`. */\n constructor(path: string) {\n this.#inner = createMultiTokenStorage(new FileKeyValueStore<ItdSession>(path));\n }\n\n get(account: string): Promise<ItdSession | null> {\n return Promise.resolve(this.#inner.get(account));\n }\n\n set(account: string, session: ItdSession): Promise<void> {\n return Promise.resolve(this.#inner.set(account, session));\n }\n\n clear(account: string): Promise<void> {\n return Promise.resolve(this.#inner.clear(account));\n }\n\n accounts(): Promise<readonly string[]> {\n return Promise.resolve(this.#inner.accounts());\n }\n}\n"],"mappings":"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"}
|
package/dist/node.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { r as TokenStorage, t as ItdSession } from "./storage-
|
|
2
|
-
import {
|
|
1
|
+
import { r as TokenStorage, t as ItdSession } from "./storage-Doe3lpFQ.cjs";
|
|
2
|
+
import { f as FileStreamOptions, g as StreamFile, h as LazyFile, n as MultiTokenStorage, p as FileTransferMode } from "./multi-storage-s_PcWPGH.cjs";
|
|
3
3
|
//#region src/node.d.ts
|
|
4
4
|
/** Что можно уточнить у файла с диска. */
|
|
5
5
|
interface PathFileOptions extends FileStreamOptions {
|
|
@@ -37,6 +37,21 @@ declare function fromPath(path: string, options?: PathFileOptions & {
|
|
|
37
37
|
mode?: typeof FileTransferMode.Buffer;
|
|
38
38
|
}): LazyFile;
|
|
39
39
|
declare function fromPath(path: string, options: PathFileOptions): LazyFile | StreamFile;
|
|
40
|
+
/**
|
|
41
|
+
* Key-value backend в одном JSON-файле.
|
|
42
|
+
*
|
|
43
|
+
* Изменения сериализуются внутри экземпляра и записываются атомарным переименованием. Несколько
|
|
44
|
+
* экземпляров или процессов, направленных на один путь, требуют внешней синхронизации.
|
|
45
|
+
*/
|
|
46
|
+
declare class FileKeyValueStore<T> {
|
|
47
|
+
#private;
|
|
48
|
+
/** @param path путь к JSON-файлу. Для секретов добавьте его в `.gitignore`. */
|
|
49
|
+
constructor(path: string);
|
|
50
|
+
get(key: string): Promise<T | undefined>;
|
|
51
|
+
set(key: string, value: T): Promise<void>;
|
|
52
|
+
delete(key: string): Promise<void>;
|
|
53
|
+
keys(prefix?: string): Promise<Iterable<string> | AsyncIterable<string>>;
|
|
54
|
+
}
|
|
40
55
|
/**
|
|
41
56
|
* Хранит сессию в файле.
|
|
42
57
|
*
|
|
@@ -68,8 +83,7 @@ declare class FileTokenStorage implements TokenStorage {
|
|
|
68
83
|
* и без общего слепка с очередью записей они теряли бы сессии друг друга. Как и
|
|
69
84
|
* {@link FileTokenStorage}, пишет через временный файл с правами `0600`.
|
|
70
85
|
*
|
|
71
|
-
*
|
|
72
|
-
* не спутать с однопользовательским, и чужой не будет молча перезаписан.
|
|
86
|
+
* Использует тот же версионированный {@link FileKeyValueStore}, что и одиночное хранилище.
|
|
73
87
|
*
|
|
74
88
|
* @example
|
|
75
89
|
* ```ts
|
|
@@ -89,5 +103,5 @@ declare class FileMultiTokenStorage implements MultiTokenStorage {
|
|
|
89
103
|
accounts(): Promise<readonly string[]>;
|
|
90
104
|
}
|
|
91
105
|
//#endregion
|
|
92
|
-
export { FileMultiTokenStorage, FileTokenStorage, PathFileOptions, fromPath };
|
|
106
|
+
export { FileKeyValueStore, FileMultiTokenStorage, FileTokenStorage, PathFileOptions, fromPath };
|
|
93
107
|
//# sourceMappingURL=node.d.cts.map
|