commit 635c920be7eddb714ab59e0c998e6da955e7671a Author: Ivan Vasilev Date: Thu Sep 3 03:48:00 2026 +0300 Initial commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..cf640d5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f355ce3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,64 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +jobs: + check: + name: Линт и типы + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 24 + cache: npm + - run: npm ci + - run: npm run lint + - run: npm run typecheck + - run: npm run format:check + + build: + name: Сборка (${{ matrix.os }}) + needs: check + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [macos-latest, windows-latest, ubuntu-latest] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 24 + cache: npm + + - run: npm ci + + # Без этого шага Windows-сборка уезжает без winws.exe: extraResources в + # electron-builder.yml просто копирует resources/zapret, а он у чистого чекаута пуст. + - run: node scripts/fetch-zapret.mjs + + # Подпись не настроена: артефакты неподписанные. + # Чтобы подписывать — добавьте секреты и передайте их в env: + # macOS: CSC_LINK, CSC_KEY_PASSWORD (+ APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD, APPLE_TEAM_ID и notarize: true) + # Windows: CSC_LINK, CSC_KEY_PASSWORD + - name: Build + run: npx --no-install electron-vite build && npx --no-install electron-builder --publish never + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/upload-artifact@v4 + with: + name: rknboost-${{ matrix.os }} + path: | + release/*.dmg + release/*.zip + release/*.exe + release/*.AppImage + release/*.deb + if-no-files-found: error + retention-days: 7 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ad33fff --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +node_modules +dist +out +.DS_Store +.eslintcache +*.log* +release +resources/zapret +.idea +.vscode diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..9c6b791 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +out +dist +pnpm-lock.yaml +LICENSE.md +tsconfig.json +tsconfig.*.json diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000..35893b3 --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,4 @@ +singleQuote: true +semi: false +printWidth: 100 +trailingComma: none diff --git a/README.md b/README.md new file mode 100644 index 0000000..b9041df --- /dev/null +++ b/README.md @@ -0,0 +1,173 @@ +# RKNboost + + + +## Требования + +- Node.js 20+ (проверено на 24) +- npm 10+ + +## Команды + +```bash +npm install # зависимости + скачивание бинарника Electron +npm run dev # dev-режим: HMR для UI, авто-рестарт main-процесса +npm run start # запуск собранной версии без упаковки +npm run build # typecheck + сборка main/preload/renderer в out/ +npm run lint # ESLint +npm run format # Prettier +npm run typecheck # tsc для node- и web-частей отдельно + +npm run build:unpack # быстрая проверка: приложение без установщика (release/*-unpacked) +npm run build:mac # .dmg + .zip (x64 + arm64) +npm run build:win # NSIS .exe (x64; arm64 не собираем — нет подписанного WinDivert) +npm run build:linux # AppImage + .deb +``` + +> Установщики под чужую ОС локально собрать нельзя — для этого есть CI +> (`.github/workflows/ci.yml` собирает все три платформы и кладёт артефакты в Actions). + +> Если после `npm install` команда `npm run dev` падает с `Error: Electron uninstall` — +> бинарник Electron не докачался; исправляется повторным запуском +> `node node_modules/electron/install.js`. + +## Структура + +``` +src/ + main/ # Node-процесс: окна, меню, доступ к ОС + index.ts lifecycle приложения, single instance + window.ts createWindow / createPopupWindow, настройки безопасности + ipc.ts все ipcMain.handle + preload/ # мост между main и renderer (sandbox, contextBridge) + shared/ # ipc-contract.ts — типы каналов, общие для всех процессов + renderer/ # React-приложение (обычный браузерный код, без Node API) +build/ # иконки и entitlements для electron-builder +resources/ # файлы, попадающие в собранное приложение (иконка окна) +out/ # результат electron-vite build +release/ # готовые установщики +``` + +## Как добавить свой IPC-канал + +1. Опишите канал в `src/shared/ipc-contract.ts` (аргументы и результат). +2. Зарегистрируйте обработчик в `src/main/ipc.ts` через хелпер `handle(...)` — типы проверятся. +3. Добавьте метод в объект `api` в `src/preload/index.ts`. +4. Вызывайте из React как `window.api.мойМетод()` — тип подтянется автоматически. + +События из main в renderer описываются в `IpcEvents` и подписываются через `subscribe(...)` +(пример — `onThemeChanged`, возвращает функцию отписки для `useEffect`). + +## Что показывают демо-карточки + +| Карточка | Каналы | Что демонстрирует | +| ---------------- | ----------------------------------------- | ------------------------------------------------------------- | +| Система | `app:info` | версии Electron/Chromium/Node, платформа, путь к userData | +| IPC ping | `ping` | минимальный invoke/handle | +| Нативный диалог | `dialog:open-file` | системный выбор файла + `fs.stat` в main | +| Всплывающее окно | `dialog:message-box`, `window:open-popup` | модальный диалог ОС и второе `BrowserWindow` | +| Тема оформления | `theme:*` | `nativeTheme` как источник правды, реакция на смену темы в ОС | + +Тайтлбар системный на всех платформах — окно создаётся без `frame`/`titleBarStyle`, рамку +и кнопки свернуть/развернуть/закрыть рисует сама ОС. + +## Безопасность + +- `sandbox: true`, `contextIsolation: true`, `nodeIntegration: false`; +- renderer не имеет доступа к `ipcRenderer` — только к методам из `window.api`; +- `setWindowOpenHandler` и `will-navigate` не дают уйти на внешний origin, ссылки открываются + в системном браузере, причём только с протоколами из `ALLOWED_EXTERNAL_PROTOCOLS`; +- в `index.html` задан CSP без внешних источников. + +## Иконки + +`build/icon.png` (1024×1024) — исходник, из него electron-builder делает иконки для всех платформ; +`build/icon.icns` и `build/icon.ico` лежат рядом как готовые варианты. `resources/icon.png` — +иконка окна для Linux. Замените эти файлы своими, имена оставьте прежними. + +## Подпись и нотаризация + +По умолчанию выключены (`notarize: false`), сборки неподписанные — их можно запускать локально, +но macOS покажет предупреждение Gatekeeper при переносе на другую машину. + +Чтобы включить: + +- **macOS**: переменные окружения `CSC_LINK` (base64 сертификата Developer ID) и `CSC_KEY_PASSWORD`, + плюс `APPLE_ID`, `APPLE_APP_SPECIFIC_PASSWORD`, `APPLE_TEAM_ID` и `notarize: true` + в `electron-builder.yml`. +- **Windows**: `CSC_LINK` и `CSC_KEY_PASSWORD` с .pfx-сертификатом. + +В CI эти значения передаются секретами GitHub — место уже отмечено комментарием в +`.github/workflows/ci.yml`. + +## Обход блокировок (zapret) + +Движки и данные — из двух сторонних проектов, оба MIT, оба скачиваются `npm run zapret:fetch` +(вызывается автоматически из `npm run build`) в `resources/zapret/` (в git не хранится): + +- **[bol-van/zapret](https://github.com/bol-van/zapret)** — сами движки: `tpws` (macOS, SOCKS5-прокси + без root) и `winws.exe` + драйвер **WinDivert** (Windows, служба с правами администратора). + WinDivert — LGPLv3/GPLv2 на выбор; лицензии обоих проектов кладутся рядом с бинарниками + (`LICENSE.zapret.txt`, `LICENSE.WinDivert.txt`). +- **[Flowseal/zapret-discord-youtube](https://github.com/Flowseal/zapret-discord-youtube)** — + списки сайтов (`resources/zapret/lists/`) и fake-пакеты (`resources/zapret/fakes/`). Версия + зафиксирована по sha коммита в `scripts/fetch-zapret.mjs` (`FLOWSEAL_COMMIT`), лицензия — + `LICENSE.flowseal.txt`. + +Свои сгенерированные стратегии для Windows (`src/main/zapret/strategies.win32.generated.ts`, +22 варианта) — не батники: `scripts/import-flowseal-strategies.mjs` разбирает `general*.bat` +из того же репозитория Flowseal и переносит аргументы `winws` в TypeScript. Файл коммитится; +перегенерировать и свериться через `git diff` нужно вручную при обновлении `FLOWSEAL_COMMIT`. +На macOS `tpws` не умеет ни fake-пакетов, ни UDP — там свои профили +(`src/main/zapret/strategies.ts`, раздел `DARWIN_DEFINITIONS`) поверх тех же списков. + +Списки сайтов пользователь может редактировать полностью (включая встроенные) — рабочая копия +живёт в `userData/zapret-lists` и переживает обновление приложения; `tpws`/`winws` сами +перечитывают список при изменении файла, перезапуск обхода не нужен (`src/main/zapret/lists.ts`). + +### Windows: служба и права + +`winws` требует драйвер WinDivert и права администратора, поэтому на Windows он работает не как +обычный дочерний процесс, а как служба Windows `rknboost-zapret` (`src/main/zapret/service.win32.ts`). +Устанавливает и настраивает её само приложение — не инсталлятор, — потому что аргументы `winws` +зависят от выбранной пользователем стратегии. + +- **Приложение запрашивает права администратора при каждом запуске** + (`win.requestedExecutionLevel: requireAdministrator` в `electron-builder.yml`). Поэтому все + действия со службой выполняются напрямую, без отдельных запросов UAC по ходу работы. + Если приложение почему-то запущено без прав (dev-режим — там манифеста нет), включается + запасной путь: `outer.ps1` через `Start-Process -Verb RunAs` поднимает само приложение + с флагом `--elevated-service-task`, и UAC запрашивается на него, а не на `powershell.exe` — + в окне UAC пользователь должен видеть имя приложения, а не постороннюю программу. + При установке службы обычным пользователям выдаётся право на `start`/`stop` именно этой + службы (`sc sdset`), так что и без прав включение-выключение обхода работает. +- **Автозапуск с системой** включён по умолчанию (`sc config start= auto`) — обход работает и + после перезагрузки, не дожидаясь запуска приложения. Отключается переключателем в настройках + (тоже через UAC). Выключение обхода кнопкой на главном экране действует только до следующей + перезагрузки, если автозапуск остался включён — служба поднимется вместе с системой заново. +- **arm64 не поддерживается**: у WinDivert нет подписанной сборки под arm64 (только x64 через + эмуляцию на Windows 11), поэтому `electron-builder.yml` собирает Windows-инсталлятор только + для x64. +- Установщик — **per-user** (ставится в `%LocalAppData%`, один клик, без UAC на саму установку). + + > ⚠️ Известный компромисс. Каталог установки доступен на запись обычному пользователю, а exe + > при этом требует администратора — значит подмена `RKNboost.exe` даёт злоумышленнику права + > администратора с подтверждения самого пользователя. Это осознанный выбор владельца проекта + > в пользу быстрой установки без UAC. Безопасный вариант — `perMachine: true`, тогда exe лежит + > в защищённом от записи `Program Files`, но установка снова требует UAC и мастера. + + Служба работает от `LocalSystem`, и её `binPath` не должен указывать в каталог, куда обычный + пользователь может писать без прав администратора — поэтому при первом включении обхода + приложение копирует `winws.exe` и WinDivert в защищённый ACL'ом + каталог в `%ProgramData%` (`protectedWinwsPath()` в `src/main/zapret/paths.ts`) и указывает + службе именно на эту копию, а не на оригинал в папке установки. При удалении приложения + деинсталлятор (`build/installer.nsh`) сам останавливает и снимает службу вместе с этой копией. + +- На одну машину служба одна: её конфиг (`%ProgramData%\rknboost\zapret\strategy.cfg`) хранит + стратегию того пользователя, который последним её менял, — с несколькими пользователями на + одной машине это разделяется некорректно. + +## Авто-обновления + +Не подключены. Если понадобятся: `npm i electron-updater`, добавить секцию `publish` +в `electron-builder.yml` и вызвать `autoUpdater.checkForUpdatesAndNotify()` в `src/main/index.ts`. diff --git a/build/entitlements.mac.plist b/build/entitlements.mac.plist new file mode 100644 index 0000000..38c887b --- /dev/null +++ b/build/entitlements.mac.plist @@ -0,0 +1,12 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.allow-dyld-environment-variables + + + diff --git a/build/icon.icns b/build/icon.icns new file mode 100644 index 0000000..28644aa Binary files /dev/null and b/build/icon.icns differ diff --git a/build/icon.ico b/build/icon.ico new file mode 100644 index 0000000..72c391e Binary files /dev/null and b/build/icon.ico differ diff --git a/build/icon.png b/build/icon.png new file mode 100644 index 0000000..cf9e8b2 Binary files /dev/null and b/build/icon.png differ diff --git a/build/installer.nsh b/build/installer.nsh new file mode 100644 index 0000000..a1ddbef --- /dev/null +++ b/build/installer.nsh @@ -0,0 +1,16 @@ +; Служба rknboost-zapret не ставится этим установщиком — её создаёт и настраивает само +; приложение при первом включении обхода (src/main/zapret/service.win32.ts), потому что +; аргументы winws зависят от выбранной пользователем стратегии. Но снять её при удалении +; приложения обязаны мы: иначе в системе остаётся служба, указывающая на удалённый winws.exe. +; +; Установка теперь per-user (perMachine: false в electron-builder.yml) — сам деинсталлятор +; запускается БЕЗ прав администратора, а sc delete и удаление %ProgramData% их требуют. +; Поэтому именно для этого шага отдельно запрашиваем UAC (тем же приёмом Start-Process +; -Verb RunAs, что и src/main/zapret/service.win32.ts), а не поднимаем весь деинсталлятор — +; удаление файлов приложения из %LocalAppData% в повышении не нуждается. + +!macro customUnInstall + DetailPrint "Останавливаю службу обхода блокировок..." + nsExec::Exec `powershell.exe -NoProfile -Command "try { Start-Process powershell -Verb RunAs -Wait -ArgumentList @('-NoProfile','-Command','sc.exe stop rknboost-zapret; sc.exe delete rknboost-zapret; Remove-Item -Recurse -Force $$env:ProgramData\rknboost -ErrorAction SilentlyContinue') } catch {}"` + Pop $0 +!macroend diff --git a/design.md b/design.md new file mode 100644 index 0000000..0bf57fd --- /dev/null +++ b/design.md @@ -0,0 +1,313 @@ +# Дизайн-документ — Forgetting + +Тёплая фиолетово-кремовая палитра в системе ролей Material 3. +Настроение: спокойный, премиальный, чуть «бутиковый» интерфейс — не типичный холодный «техно». + +Документ единый и покрывает обе платформы. **Часть I (основы)** — палитра, роли, типографика, формы, состояния, доступность, тёмная тема — одинакова для десктопа и мобильного. **Часть II** описывает общий язык компонентов. **Части III и IV** — компоновку, которая на платформах различается. Язык дизайна один; меняется только раскладка. + +--- + +# Часть I. Основы (общие для десктопа и мобильного) + +## 1. Палитра + +### Базовые цвета +| Назначение | HEX | Заметка | +|---|---|---| +| Фиолетовый (акцент) | `#a96bff` | HSL 275°, 100%, 71% — светлый, для тёмной темы и акцентов | +| Фиолетовый (основной) | `#6a45b8` | тон ~40 — заливка кнопок, активные элементы | +| Тёмно-фиолетовый (текст) | `#5a2da8` | текст на светло-фиолетовом контейнере | +| Крем (фон) | `#faf6f0` | основная поверхность | +| Крем (исходный) | `#f6f0ea` / `#ece0d1` | более тёплые/тёмные оттенки нейтрала | +| Оранжевый (исходный) | `#ffb76b` | стартовый цвет, ушёл в роль `tertiary` | + +### Связь оттенков +Крем `#ece0d1` (тон ~33°) и оранжевый `#ffb76b` (тон ~31°) — родственники по тёплому углу спектра. +Фиолетовый — холодный акцент в противовес тёплому нейтралу. Отсюда баланс «тёплая база + прохладный акцент». + +--- + +## 2. Роли Material 3 + +| Роль MD3 | Светлая тема | Текст на ней (`on-`) | +|---|---|---| +| `primary` | `#6a45b8` | `#ffffff` | +| `primary-container` | `#ebddff` | `#5a2da8` | +| `secondary-container` | `#ebddff` | `#5a2da8` | +| `tertiary-container` | `#ffdcad` | `#7a4a14` | +| `surface` | `#faf6f0` | `#3a2e22` | +| `surface-container` | `#f0e8dd` | `#3a2e22` | +| `surface-container-high` | `#ece2d3` | `#3a2e22` | +| `surface-bright` (рабочая зона) | `#fdfaf6` | `#3a2e22` | +| `outline` | `#d8cdbe` | — | +| `outline-variant` | `#ece2d3` | — | +| `error` | `#ba1a1a` | `#ffffff` | +| `error-container` | `#ffdad6` | `#410002` | + +### Статусные цвета (тёплый подтон) +| Статус | Фон чипа | Текст | Точка | +|---|---|---|---| +| Онлайн / успех | `#c0dd97` | `#2c4d0a` | `#639922` | +| Офлайн / нейтраль | `#e4dac9` | `#7a6a55` | `#a99a85` | +| Ошибка | `#ffdad6` | `#410002` | `#ba1a1a` | + +> Принцип: `primary` (тёмный `#6a45b8`) — для заливок и текста по контрасту. +> Светлый `#a96bff` держим как акцент/иконки/тёмную тему, не как фон под белый текст. + +--- + +## 3. Типографика + +- Один шрифт без засечек на весь интерфейс (system-ui / Inter / Roboto). +- Два веса: 400 (обычный) и 500 (акцент). Без 600–700. +- **Sentence case** везде, в том числе в заголовках таблиц. Без Title Case и КАПСА. +- Моноширинный шрифт для технических данных: IP-адреса, VPN-ключи, rx/tx. + +Шкала: +| Элемент | Размер | Вес | +|---|---|---| +| Заголовок раздела | 20px | 500 | +| Подзаголовок / название карточки | 15–16px | 500 | +| Основной текст | 14px | 400 | +| Заголовки таблицы | 13px | 500 | +| Данные таблицы | 13px | 400 | +| Подписи / hint | 12px | 400 | + +--- + +## 4. Скругления (MD3 shape scale) + +| Элемент | Радиус | +|---|---| +| Кнопки, чипы, инпуты (pill) | `100px` | +| Карточки / контейнеры | `24px` | +| Внутренние блоки таблицы / метрики | `16px` | +| Иконка-логотип | `8–9px` | +| Аватар, точка статуса | `50%` | + +> Скругления только полные (по всем сторонам). Односторонних бордюров с radius не делаем. + +--- + +## 5. Состояния и взаимодействие (MD3 state layers) + +Поверх элемента — полупрозрачный слой акцентного цвета: +- Hover: ~8% непрозрачности. +- Focus / pressed: ~12%. +- Для фиолетовых элементов слой фиолетовый, для нейтральных — тёмный нейтрал. + +Элевация — через тональные поверхности (светлее/темнее крем), а не через тени. Иерархия строится тональностью: рабочая зона данных светлее крема, крем — для фона и хрома. + +**Опора на платформу.** +- На десктопе (указатель) hover-слой работает как первичная обратная связь. +- На тач-устройствах hover не срабатывает — обратная связь строится на нажатии и выборе, не на наведении. + +**Инертные vs действенные элементы.** +- Навигационные строки и статичные строки данных — **инертны** (без эффекта вдавливания); активное состояние показывается заливкой-таблеткой (`primary-container`). +- Реальные действия (кнопки, переключатель, QR, удаление) могут иметь лёгкий pressed-эффект через state layer акцентного цвета (~12%). + +--- + +## 6. Доступность + +- Контраст фиолетового `#a96bff` на креме ≈ 2,6:1 — годится только для крупных элементов; для мелкого текста по фиолетовому брать `#5a2da8` или темнее (≥ 4,5:1). +- Основной текст всегда тёмно-коричневый `#3a2e22`, не чисто чёрный и не фиолетовый. Светлый акцентный фиолетовый под мелкий текст не использовать. +- Минимальный размер шрифта — 12px; технические данные (IP, ключи, rx/tx) — моноширинным. +- Тач-таргеты ≥ 44px (кнопки действий, гамбургер, переключатель, пункты меню). +- Иконки-кнопки без подписи получают доступное имя (`aria-label`): меню, удаление и т.п. +- Один шрифт, два веса (400 / 500), sentence case везде. + +--- + +## 7. Тёмная тема + +Крем в тёмную тему не переносится — нужен отдельный набор. Направление — **тёплый уголь**: тёмные поверхности с тем же тёплым нейтральным подтоном, что и крем в светлой теме (тон ~30–40°), а не баклажановые. Так фирменный баланс «тёплая база + прохладный фиолетовый акцент» сохраняется и в темноте: акцент остаётся холодным фиолетовым на тёплой тёмной базе, как и в светлой. + +Фиолетовый акцент — общий со светлой темой по смыслу, но осветлён под тёмный фон. + +| Роль | Тёмная тема | Текст на ней (`on-`) | +|---|---|---| +| `surface` | `#1a1611` | `#ede2d4` | +| `surface-container` | `#251f18` | `#ede2d4` | +| `surface-container-high` | `#302820` | `#ede2d4` | +| `surface-bright` (рабочая зона) | `#2b251d` | `#ede2d4` | +| `primary` | `#cbb3ff` (светлый фиолетовый) | `#381b6c` | +| `primary-container` | `#52389e` | `#ebddff` | +| `secondary-container` | `#483a55` | `#ebddff` | +| `outline` | `#4d4334` | — | +| `outline-variant` | `#342c20` | — | +| `error` | `#ffb4ab` | `#5c1414` | +| `error-container` | `#5c1414` | `#ffb4ab` | +| текст / `on-surface` | `#ede2d4` | — | +| приглушённый текст | `#b6a994` | — | + +### Статусные цвета (тёмная тема) +Фон и текст осветлены/переинвертированы под тёмный фон, подтон тот же тёплый. + +| Статус | Фон чипа | Текст | Точка | +|---|---|---|---| +| Онлайн / успех | `#2d4413` | `#c8e3a0` | `#8fc24f` | +| Офлайн / нейтраль | `#34291c` | `#d0c0a4` | `#b0a085` | +| Ошибка | `#5c1414` | `#ffb4ab` | `#ff5449` | + +> Значения — рабочая отправная точка. Контраст текста на поверхностях стоит прогнать через проверку (цель ≥ 4,5:1 для мелкого текста) перед фиксацией. +> +> Альтернатива на будущее: «баклажан» — поверхности тонируются фиолетовым (`surface` `#18121d`, `container` `#221a29`, `bright` `#2a2031`, текст `#ece0e9`, приглушённый `#b3a4bd`, `outline` `#4c4356`). Каноничный MD3-путь, звучит «премиальнее», но база уходит в холодный тон. + +--- + +# Часть II. Компоненты (общий язык) + +Эти компоненты используют одни и те же роли на обеих платформах; различается только их расположение в раскладке. + +### Кнопки +- **Главное действие** (Создать пользователя): заливка `primary` `#6a45b8`, текст белый (`on-primary`), pill. +- **Вторичное** (Обновить): tonal — фон `secondary-container` `#ebddff`, текст `#5a2da8`, pill. +- **Опасное** (Выход, удаление): контур `#e6c4bc` / роль `error`, текст `#b34a3a`, без заливки, pill. +- Иконка слева от текста, gap 6–8px. + +### Поля ввода и поиска +- Pill-инпут, фон `surface-bright` `#fdfaf6`, бордюр `outline` `#d8cdbe`, плейсхолдер приглушённым. +- Поле поиска — с иконкой поиска слева. + +### Чипы статуса +- Pill, маленькая цветная точка + подпись. Цвета — из таблицы статусов (успех / нейтраль / ошибка), текст соответствующей роли `on-`. + +--- + +# Часть III. Десктоп + +### Компоновка +- Многоколоночная сетка. +- Заголовок раздела — в шапке контента. + +### Боковое меню (постоянная панель) +- Фиксированная ширина ~240px, всегда на экране. +- Фон `#f0e8dd` (`surface-container`, на тон темнее основного — мягкое разделение без жёсткой границы). +- Логотип сверху, навигация ниже, «Выход» прижат к низу (`margin-top: auto`). +- Активный пункт: pill-контейнер `#ebddff` (`primary-container`) + текст `#5a2da8`. +- Неактивные: текст `#5a4a3a`, при наведении — лёгкая фиолетовая подсветка (state layer). + +### Таблица пользователей +- Контейнер `surface-bright` `#fdfaf6`, скругление 16px, бордюр `#ece2d3` (`outline-variant`). +- Шапка на тоне `#f0e8dd`, текст среднего веса `#5a4a3a`. +- Разделители строк — `0.5px solid #ece2d3`. +- Колонки: `#`, Статус, Имя, IP, rx, tx, действия. +- IP и Key — моноширинным; длинный ключ обрезаем многоточием. +- Действия — иконки: копировать `#8a6bff`, удалить `#b34a3a`. + +--- + +# Часть IV. Мобильный + +## 1. Принципы адаптации +1. Одна колонка. Контент течёт вертикально на ширине телефона (рабочая ширина ~360px, поля по 16px). +2. Никаких таблиц с горизонтальной прокруткой — табличные данные разворачиваются в карточки. +3. Боковое меню десктопа превращается в модальный navigation drawer (паттерн Material 3). +4. Главное действие всегда в зоне большого пальца — кнопки полноширинные или парные, высота тача ≥ 44px. +5. Те же роли поверхностей и те же shape-токены, что на десктопе. Меняется компоновка, не язык дизайна. + +## 2. Навигация и drawer + +### Модель +Приложение имеет один основной раздел («Пользователи»). На десктопе это постоянная боковая панель ~240px; на мобильном — **модальный drawer**, скрытый по умолчанию. + +### Открытие и закрытие (важно по MD3) +- Открытие — **только тапом по иконке-гамбургеру** в верхнем баре. +- Закрытие — тап по затемнению (scrim) или свайп панели к её левому краю (срабатывает при сдвиге ~40px+). +- Открытие edge-swipe'ом от края экрана **не используется**: жест конфликтует с системным «назад» (predictive back) на Android. + +### Поведение +- Drawer выезжает слева, поверх контента, с затемнением под ним. +- Затемнение блокирует взаимодействие с экраном, пока панель открыта. +- Анимация — горизонтальный сдвиг панели + плавное появление scrim (быстрый, «решительный» easing, ~250–280 мс); затемнение и сдвиг анимируются вместе. + +### Содержимое панели +Поверхность панели — `surface-container` (на тон темнее основного экрана), мягкое разделение без жёсткой границы. Скругление контейнера 24px, правые углы. + +Сверху вниз: +1. **Шапка** — вордмарк приложения (роль заголовка) + версия (роль приглушённого текста), внутренний отступ. +2. **Тонкий разделитель** (`outline`, 0.5px). +3. **Навигация** — пункты-строки с ведущей иконкой 22px и подписью 15px, gap 16px. + - Активный пункт: pill-контейнер `primary-container` во всю ширину; текст, иконка и счётчик — `on-primary-container`, вес 500. + - Неактивный пункт: подпись и иконка — основной текст (тёмный нейтрал), без подсветки при наведении. + - Строки навигации **инертны при нажатии** — без эффекта вдавливания, выделяет только активное состояние. +4. **«Выход»** — прижат к низу (`margin-top: auto`), стиль опасного действия (контур, без заливки), pill, иконка слева. + +### Замечание про объём меню +Длинный список разделов (как у Gmail-паттерна) оправдан только при 5+ назначениях. У приложения раздел один, поэтому drawer минимальный: шапка → активный пункт → «Выход» внизу. Доп. пункты добавляются только под реальные экраны. + +## 3. Верхний бар (top app bar) +- Слева — иконка-гамбургер (кнопка-таргет ≥ 42px, круглая зона нажатия). +- Рядом — заголовок раздела (роль заголовка, 20px / 500). +- Бар без заливки и без тени; высота поверхности задаётся фоном экрана (`surface`). + +## 4. Экран «Пользователи» +Порядок блоков сверху вниз: верхний бар → карточка сервера → «Добавить сервер» → форма создания → счётчик → список пользователей. + +### 4.1. Карточка VPN-сервера +- Поверхность — `surface-bright`, скругление 24px. +- **Активная обводка** — рамка 2px роли `primary` (приглушённый светло-фиолетовый вариант): сигнал, что сервер выбран/активен. +- Слева — компактный **переключатель** (pill-трек, маленький круглый ползунок). Состояние «вкл» отражается цветом трека из роли статуса «успех» и положением ползунка. +- По центру — название сервера (роль подзаголовка, 16px / 500) и IP **моноширинным** (роль приглушённого текста). +- Справа — нейтральный чип счётчика пиров (роль статуса «нейтраль», pill). + +### 4.2. «Добавить сервер» +- Полноширинная кнопка-«призрак»: пунктирный контур (`outline`), без заливки, иконка `+` слева, приглушённый текст. Pill (100px). + +### 4.3. Форма создания пользователя +- Поле ввода: pill-инпут на `surface-bright`, бордюр `outline`, плейсхолдер приглушённым. Полная ширина. +- Под ним пара кнопок в ряд (50/50): + - **Создать** — главное действие: заливка `primary`, текст `on-primary`, иконка `+`, pill. + - **Обновить** — вторичное: tonal (`secondary-container` / `on-secondary-container`), иконка обновления, pill. + +### 4.4. Список пользователей +- Над списком — счётчик («N пользователей») приглушённым текстом 12px. +- Десктопная таблица (`#`, Статус, Имя, IP, rx, tx, действия) на мобильном **разворачивается в карточки** — по одной на пользователя. + +### 4.5. Карточка пользователя +Поверхность `surface-bright`, бордюр `outline-variant`, скругление 24px. Внутри: +1. **Верхняя строка** — слева порядковый `#N` (приглушённый) + имя (16px / 500); справа чип статуса «онлайн» (роль статуса «успех»: точка + подпись, pill). +2. **Строка IP** — иконка-глобус + адрес моноширинным. +3. **Два блока метрик** (rx / tx) в ряд 50/50: внутренние блоки на `surface-container`, скругление 16px. Сверху подпись со стрелкой (↓ rx / ↑ tx) приглушённым 12px, под ней значение моноширинным 18px / 500. +4. **Действия** — ряд: кнопка «QR» (контур `outline`, иконка QR акцентным фиолетовым) на всю доступную ширину + квадратная кнопка удаления (контур роли «ошибка», иконка корзины — `error`). + +## 5. Сводка мобильных компонентов (через роли и токены) + +| Компонент | Поверхность / контур | Форма | Текст | +|---|---|---|---| +| Карточка сервера | `surface-bright` + рамка `primary` 2px | 24px | подзаголовок + mono | +| Карточка пользователя | `surface-bright` + `outline-variant` | 24px | подзаголовок + mono | +| Блок метрики (rx/tx) | `surface-container` | 16px | подпись + mono | +| Кнопка «Создать» | заливка `primary` | pill 100px | `on-primary`, 500 | +| Кнопка «Обновить» | tonal `secondary-container` | pill 100px | `on-…`, 500 | +| «Добавить сервер» | пунктир `outline` | pill 100px | приглушённый | +| «Выход» | контур (опасное) | pill 100px | роль опасного | +| Поле ввода | `surface-bright` + `outline` | pill 100px | основной / плейсхолдер | +| Чип статуса | роль статуса (успех/нейтраль/ошибка) | pill 100px | соответствующий `on-` | +| Активный пункт меню | `primary-container` | pill 100px | `on-primary-container`, 500 | + +--- + +# Отличия десктоп / мобильный (кратко) + +| Аспект | Десктоп | Мобильный | +|---|---|---| +| Навигация | постоянная боковая панель ~240px | модальный drawer (гамбургер) | +| Список пользователей | таблица с колонками | карточки, по одной на пользователя | +| rx / tx | ячейки таблицы | два блока-метрики внутри карточки | +| Действия в строке | иконки в конце строки | ряд кнопок внизу карточки | +| Ширина | многоколоночная сетка | одна колонка, поля 16px | +| Заголовок раздела | в шапке контента | в верхнем баре рядом с гамбургером | + +--- + +# Принципы (итог) + +1. Тёплая база + прохладный фиолетовый акцент. Один язык дизайна на обеих платформах — меняется компоновка, не палитра. +2. Цвет кодирует смысл (статус, действие), а не порядок. +3. Иерархия — тональностью поверхности, не тенями. Рабочая зона данных светлее (почти белая), крем — для фона и хрома. +4. Sentence case, два веса шрифта, без капса. Технические данные — моноширинным. +5. Главное действие — залито фиолетовым; вторичное — tonal; опасное — контур. +6. Таблицы → карточки, rx/tx → блоки-метрики на мобильном; одна колонка, крупные тач-таргеты, действия в зоне большого пальца. +7. Drawer открывается гамбургером, закрывается scrim/свайпом; edge-swipe не задействуем. Hover — опора десктопа; на тач-устройствах обратная связь на нажатии/выборе. \ No newline at end of file diff --git a/electron-builder.yml b/electron-builder.yml new file mode 100644 index 0000000..9ac8e4a --- /dev/null +++ b/electron-builder.yml @@ -0,0 +1,91 @@ +appId: com.rknboost.app +productName: RKNboost +directories: + buildResources: build + output: release + +files: + - '!**/.vscode/*' + - '!src/*' + - '!electron.vite.config.{js,ts,mjs,cjs}' + - '!{.eslintcache,eslint.config.mjs,.prettierignore,.prettierrc.yaml,README.md}' + - '!{.env,.env.*,.npmrc,pnpm-lock.yaml,yarn.lock}' + - '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}' + - '!.github/*' + # zapret идёт отдельно через extraResources ниже — не дублировать его в app.asar. + - '!resources/zapret/**' +asarUnpack: + - 'resources/**' + +# Бинарники zapret (npm run zapret:fetch) кладём рядом с приложением, а не в asar — +# это исполняемые файлы, которые main-процесс запускает через spawn. +extraResources: + - from: resources/zapret + to: zapret + +win: + executableName: RKNboost + # Приложение запрашивает права администратора при КАЖДОМ запуске (манифест exe). + # Внимание на компромисс: установка при этом остаётся per-user, то есть exe лежит в + # %LocalAppData%\Programs\RKNboost — папке, доступной на запись обычному пользователю. + # Значит подмена этого exe даёт злоумышленнику администратора с подтверждения самого + # пользователя. Это осознанный выбор владельца проекта; безопасный вариант — вернуть + # perMachine: true, чтобы exe лежал в защищённом от записи Program Files. + requestedExecutionLevel: requireAdministrator + target: + # arm64 не собираем: WinDivert (драйвер для winws — src/main/zapret/engine.win32.ts) + # не имеет подписанной сборки под arm64, только x64 через эмуляцию (docs/windows.md + # в поставке zapret) — arm64-инсталлятор был бы заведомо нерабочим. + - target: nsis + arch: [x64] +nsis: + # Быстрая установка per-user, без UAC и без мастера: ставится в %LocalAppData%, куда + # обычный пользователь и так пишет без повышения прав. Раньше здесь стоял perMachine — + # это была защита от того, что служба (LocalSystem) ссылалась бы на winws.exe прямо + # в папке установки. Теперь вместо этого при первом включении обхода приложение сама + # копирует winws.exe в защищённый ACL'ом каталог в %ProgramData% (см. applyServiceConfig + # в src/main/zapret/service.win32.ts) — сама установка приложения per-user безопасна. + oneClick: true + perMachine: false + # Удалять %APPDATA%\rknboost (списки сайтов, метка первого запуска) вместе с приложением. + # По умолчанию electron-builder этого НЕ делает, и данные оставались бы в системе после + # удаления. Обновление приложения под это не попадает — в шаблоне деинсталлятора стоит + # проверка `ifNot isUpdated`, так что отредактированные списки переживают обновление. + deleteAppDataOnUninstall: true + include: build/installer.nsh + artifactName: ${name}-${version}-setup-${arch}.${ext} + shortcutName: ${productName} + uninstallDisplayName: ${productName} + createDesktopShortcut: always + +mac: + category: public.app-category.productivity + target: + - target: dmg + arch: [x64, arm64] + - target: zip + arch: [x64, arm64] + entitlements: build/entitlements.mac.plist + entitlementsInherit: build/entitlements.mac.plist + hardenedRuntime: true + # tpws — сторонний Mach-O внутри .app; hardened runtime требует, чтобы electron-builder + # подписал и его тем же Developer ID, иначе Gatekeeper убьёт процесс при первом spawn. + binaries: + - Contents/Resources/zapret/darwin/tpws + # Подпись и нотаризация выключены: без Apple Developer ID сборка иначе падает. + # Как включить — см. раздел «Подпись» в README.md. + notarize: false +dmg: + artifactName: ${name}-${version}-${arch}.${ext} + +linux: + target: + - AppImage + - deb + maintainer: RKNboost + category: Utility + synopsis: Кроссплатформенное приложение на Electron +appImage: + artifactName: ${name}-${version}-${arch}.${ext} + +npmRebuild: false diff --git a/electron.vite.config.ts b/electron.vite.config.ts new file mode 100644 index 0000000..7f2d1ed --- /dev/null +++ b/electron.vite.config.ts @@ -0,0 +1,33 @@ +import { resolve } from 'node:path' +import { defineConfig } from 'electron-vite' +import react from '@vitejs/plugin-react' + +// Три независимые сборки: main (Node), preload (sandbox) и renderer (браузер). +export default defineConfig({ + main: { + build: { + rollupOptions: { + input: { index: resolve('src/main/index.ts') } + } + } + }, + preload: { + build: { + rollupOptions: { + input: { index: resolve('src/preload/index.ts') }, + // preload запускается в sandbox: всё, кроме самого electron, должно быть + // вкомпилировано в бандл — require() там недоступен. + external: ['electron'] + } + } + }, + renderer: { + resolve: { + alias: { + '@renderer': resolve('src/renderer/src'), + '@shared': resolve('src/shared') + } + }, + plugins: [react()] + } +}) diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..f2cd47d --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,38 @@ +import js from '@eslint/js' +import globals from 'globals' +import tseslint from 'typescript-eslint' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import prettier from 'eslint-config-prettier' + +export default tseslint.config( + { ignores: ['node_modules', 'out', 'dist', 'release', '.vscode'] }, + js.configs.recommended, + tseslint.configs.recommended, + { + files: [ + 'src/main/**/*.ts', + 'src/preload/**/*.ts', + 'electron.vite.config.ts', + 'scripts/**/*.mjs' + ], + languageOptions: { + globals: { ...globals.node } + } + }, + { + files: ['src/renderer/**/*.{ts,tsx}'], + languageOptions: { + globals: { ...globals.browser } + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }] + } + }, + prettier +) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e177904 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,7078 @@ +{ + "name": "rknboost", + "version": "0.4.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "rknboost", + "version": "0.4.0", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@electron-toolkit/utils": "^4.0.0" + }, + "devDependencies": { + "@electron-toolkit/tsconfig": "^2.0.0", + "@eslint/js": "^10.0.1", + "@types/node": "^24.9.0", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "@vitejs/plugin-react": "^5.2.0", + "electron": "^44.1.1", + "electron-builder": "^26.15.3", + "electron-vite": "^5.0.0", + "eslint": "^10.9.1", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "prettier": "^3.9.6", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.69.0", + "vite": "^7.3.6" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz", + "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@electron-internal/extract-zip": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@electron-internal/extract-zip/-/extract-zip-1.0.5.tgz", + "integrity": "sha512-+bqFCP98pLI0Tt0XQo1TmlXtwjWchISndDOxCkEcIuUgXWpBnLyRI+2DU+mesvnMMX6L1XDqYNA0lXNDHd/yiA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@electron-toolkit/tsconfig": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@electron-toolkit/tsconfig/-/tsconfig-2.0.0.tgz", + "integrity": "sha512-AdPsP770WhW7b260h13SHMdmjEEHJL6xFtgi3jwgdsSQbJOkJLeNnnpZW9qxTPCvmRI6vmdzWz5K3gibFS6SNg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/node": "*" + } + }, + "node_modules/@electron-toolkit/utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@electron-toolkit/utils/-/utils-4.0.0.tgz", + "integrity": "sha512-qXSntwEzluSzKl4z5yFNBknmPGjPa3zFhE4mp9+h0cgokY5ornAeP+CJQDBhKsL1S58aOQfcwkD3NwLZCl+64g==", + "license": "MIT", + "peerDependencies": { + "electron": ">=13.0.0" + } + }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/asar/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@electron/asar/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/fuses": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.8.0.tgz", + "integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.1", + "fs-extra": "^9.0.1", + "minimist": "^1.2.5" + }, + "bin": { + "electron-fuses": "dist/bin.js" + } + }, + "node_modules/@electron/fuses/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/get": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-5.1.0.tgz", + "integrity": "sha512-3kSBtG8ObcTVfXanm5vVJ6UnBLEVmVsRk1M+vGqCuMBV+XLCbJYuWQful+yIy0GQDsSlK0kHEriEHn7SPk4EnA==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^3.0.0", + "graceful-fs": "^4.2.11", + "progress": "^2.0.3", + "semver": "^7.6.3", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=22.12.0" + }, + "optionalDependencies": { + "undici": "^7.24.4" + } + }, + "node_modules/@electron/get/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/notarize": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", + "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/osx-sign": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.3.tgz", + "integrity": "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@electron/rebuild": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.2.0.tgz", + "integrity": "sha512-RKL/O+jGoXJMxrx/5771y1n0xTKmFuOYGO3gMmwypBM6rsH0kou0mswwdXA2JrhIkE4xyC7v9vGk0n6NPzgOxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.1.1", + "node-abi": "^4.2.0", + "node-api-version": "^0.2.1", + "node-gyp": "^12.2.0", + "read-binary-file-arch": "^1.0.6" + }, + "bin": { + "electron-rebuild": "lib/cli.js" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@electron/universal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz", + "integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.3.1", + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.3.1", + "dir-compare": "^4.2.0", + "fs-extra": "^11.1.1", + "minimatch": "^9.0.3", + "plist": "^3.1.0" + }, + "engines": { + "node": ">=16.4" + } + }, + "node_modules/@electron/universal/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/universal/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", + "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/universal/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@electron/windows-sign": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz", + "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "dependencies": { + "cross-dirname": "^0.1.0", + "debug": "^4.3.4", + "fs-extra": "^11.1.1", + "minimist": "^1.2.8", + "postject": "^1.0.0-alpha.6" + }, + "bin": { + "electron-windows-sign": "bin/electron-windows-sign.js" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/windows-sign/node_modules/fs-extra": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", + "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", + "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@noble/hashes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", + "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.9.4.tgz", + "integrity": "sha512-GjzePcT9Iw8NzeOPf73iNS9xM+TBhd/FilAfP+RQGkTMQJTVWtytN3JHJACCjf/ABNau5S7mS3g+DcuxmRgYEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/webcrypto": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.7.1.tgz", + "integrity": "sha512-ODOov0sGMJMf3jPonOkgGqPknTsu+DdQ7kD++gz8aI+aFMOMHFbWAA2taqXXVTdP+OTOQR/znGvSpmkeI0WTYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/utils": "^2.0.2", + "tslib": "^2.8.1", + "webcrypto-core": "^1.9.2" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.1.tgz", + "integrity": "sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.1.tgz", + "integrity": "sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.1.tgz", + "integrity": "sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.1.tgz", + "integrity": "sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.1.tgz", + "integrity": "sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.1.tgz", + "integrity": "sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.1.tgz", + "integrity": "sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.1.tgz", + "integrity": "sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.1.tgz", + "integrity": "sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.1.tgz", + "integrity": "sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.1.tgz", + "integrity": "sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.1.tgz", + "integrity": "sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.1.tgz", + "integrity": "sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.1.tgz", + "integrity": "sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.1.tgz", + "integrity": "sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.1.tgz", + "integrity": "sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.1.tgz", + "integrity": "sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.1.tgz", + "integrity": "sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.1.tgz", + "integrity": "sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.1.tgz", + "integrity": "sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.1.tgz", + "integrity": "sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.1.tgz", + "integrity": "sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.1.tgz", + "integrity": "sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.1.tgz", + "integrity": "sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.1.tgz", + "integrity": "sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.69.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.69.0.tgz", + "integrity": "sha512-t5jQTKPIgVW1PE6dR6H6Qz5gm8zjMlX5/2gRaOGd9eO6V7J+tQc6iWKukEe7dY8u9HyYasQ0yfF0/FSSTEO2gA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.69.0", + "@typescript-eslint/type-utils": "8.69.0", + "@typescript-eslint/utils": "8.69.0", + "@typescript-eslint/visitor-keys": "8.69.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.69.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.8.tgz", + "integrity": "sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.69.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.69.0.tgz", + "integrity": "sha512-l4b0DhWioGg6Gt2ebGlvfkFMOjRsauxtsnDRwUSRX1qHq3HdTfQHV8wW9zEXeciai6HfeaKOedQn2Zoofx3WBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.69.0", + "@typescript-eslint/types": "8.69.0", + "@typescript-eslint/typescript-estree": "8.69.0", + "@typescript-eslint/visitor-keys": "8.69.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.69.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.69.0.tgz", + "integrity": "sha512-yi4obFrHMmnsesWehHbkg9zMA7Jt8cXT+mKM08G999pH1yT6nqgsHx7MYm0uY1wAj8CqiBXYRJ7WAT0QdQHQXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.69.0", + "@typescript-eslint/types": "^8.69.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.69.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.69.0.tgz", + "integrity": "sha512-ewfspqWvSxKSOaplqAUNbaSFO0eB6w1EtQ+esfYFRm3614Ty4uNtExkcbgd6nWsXphbqKyf9ZYdbZdv2xEoWEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.69.0", + "@typescript-eslint/visitor-keys": "8.69.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.69.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.69.0.tgz", + "integrity": "sha512-xNqK7YTDZsLniQMV/4rpFR8Z5JlqeRvVjuG1YgF/mdPVH84HSD19L8CczMA0qg2RfwEV231GHH3VnToJDo4MfQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.69.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.69.0.tgz", + "integrity": "sha512-ZfoJAVg3JZndQEpEl9petVlxau3lRuElc4HRMuAlLCf8to04/iHz692RUSNmXKDjEuJmIL+KZ2/BsOcBc16dsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.69.0", + "@typescript-eslint/typescript-estree": "8.69.0", + "@typescript-eslint/utils": "8.69.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.69.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.69.0.tgz", + "integrity": "sha512-K3VrubUPhlo9VDBS6QdI8YB5j7ClpqLRdefcz6PFrhnwicehBweqQ9Evhl4l+FYz0HdDmMqIiSX0aldGRYtDCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.69.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.69.0.tgz", + "integrity": "sha512-AdFkgqck3Vudb/kWnxlyafU/4aBhHrbQ9locP2N4psXTy5mOBg0SHJumnLvx7r6g1gV4DKvUFwV2nJZBoqOD8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.69.0", + "@typescript-eslint/tsconfig-utils": "8.69.0", + "@typescript-eslint/types": "8.69.0", + "@typescript-eslint/visitor-keys": "8.69.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.69.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.69.0.tgz", + "integrity": "sha512-tUbx60BBqQa31kXF5MCsOOLL5E/WzUuxIn7YpAvq+eaUlqvk8/NXnXMBNAdLCr0icjkzem7iUA5QqWHe/hJ1aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.69.0", + "@typescript-eslint/types": "8.69.0", + "@typescript-eslint/typescript-estree": "8.69.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.69.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.69.0.tgz", + "integrity": "sha512-+rmdgPA+EXkNgKYvHvFfhrs35utXbwaC5PGpDquSXcoXQDKUA5UjV0LmTucG/4JXkM31BTu4TilHtrN8IVBe8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.69.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", + "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-rc.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.15", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.15.tgz", + "integrity": "sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/app-builder-lib": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.15.3.tgz", + "integrity": "sha512-2VnyWkqsP5v5XbBhL3tD5Syx8iNPBYsoU7kY4S2fz7wg8Rj/nztWKCUzGKaFRTv0Xwf3/H058CR1Kvtd/3lRow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "3.4.1", + "@electron/fuses": "^1.8.0", + "@electron/get": "^3.0.0", + "@electron/notarize": "2.5.0", + "@electron/osx-sign": "1.3.3", + "@electron/rebuild": "^4.0.4", + "@electron/universal": "2.0.3", + "@malept/flatpak-bundler": "^0.4.0", + "@noble/hashes": "^2.2.0", + "@peculiar/webcrypto": "^1.7.1", + "@types/fs-extra": "9.0.13", + "ajv": "^8.18.0", + "asn1js": "^3.0.10", + "async-exit-hook": "^2.0.1", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chromium-pickle-js": "^0.2.0", + "ci-info": "4.3.1", + "debug": "^4.3.4", + "dotenv": "^16.4.5", + "dotenv-expand": "^11.0.6", + "ejs": "^3.1.8", + "electron-publish": "26.15.3", + "fs-extra": "^10.1.0", + "hosted-git-info": "^4.1.0", + "isbinaryfile": "^5.0.0", + "jiti": "^2.4.2", + "js-yaml": "^4.1.0", + "json5": "^2.2.3", + "lazy-val": "^1.0.5", + "minimatch": "^10.2.5", + "pkijs": "^3.4.0", + "plist": "3.1.0", + "proper-lockfile": "^4.1.2", + "resedit": "^1.7.0", + "semver": "~7.7.3", + "tar": "^7.5.7", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0", + "unzipper": "^0.12.3", + "which": "^5.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dmg-builder": "26.15.3", + "electron-builder-squirrel-windows": "26.15.3" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-3.1.0.tgz", + "integrity": "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/app-builder-lib/node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/app-builder-lib/node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/app-builder-lib/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/app-builder-lib/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/app-builder-lib/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz", + "integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/builder-util": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.15.3.tgz", + "integrity": "sha512-q2hn7Mbo2nFNkVekPiHFx6Nfo3hURmES3tfBn+k5Pqxl2RkmP3QGqZUhH/q9Pch/4G05NRhPjDlVj1O8q4Txvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.6", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.6", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "js-yaml": "^4.1.0", + "sanitize-filename": "^1.6.3", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.7.0.tgz", + "integrity": "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-dirname": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", + "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/dir-compare": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", + "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.5", + "p-limit": "^3.1.0 " + } + }, + "node_modules/dir-compare/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/dir-compare/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/dir-compare/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dmg-builder": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.15.3.tgz", + "integrity": "sha512-O3zJUFUYHJKgzPqioHxfxzBzlSC1eXCSr79gMSBKBP5AgjjpmrydMsMLotEg9fAJF36vdUncb+4ndRNxoPdlSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "fs-extra": "^10.1.0", + "js-yaml": "^4.1.0" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron": { + "version": "44.1.1", + "resolved": "https://registry.npmjs.org/electron/-/electron-44.1.1.tgz", + "integrity": "sha512-N2WCq2sbOkqQgvXJYx2lS6UiO8bF+Yr67trDnS6JKa2WxTCRQsAjGl57SUtdW9h6r5PlduBFjIhxhgd3dzv1hg==", + "license": "MIT", + "dependencies": { + "@electron-internal/extract-zip": "^1.0.1", + "@electron/get": "^5.0.0", + "@types/node": "^24.9.0" + }, + "bin": { + "electron": "cli.js", + "install-electron": "install.js" + }, + "engines": { + "node": ">= 22.12.0" + } + }, + "node_modules/electron-builder": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.15.3.tgz", + "integrity": "sha512-a1KM5heqS3gQCZzizXEI8RjJy3QVogULPdeSknt76uLDpBIW/HDGsMg/XgP0riP6PI9COsRvFITKKGDqA8fJxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "dmg-builder": "26.15.3", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "simple-update-notifier": "2.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/electron-builder-squirrel-windows": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.15.3.tgz", + "integrity": "sha512-Jc19XPV9y9+2bAdZPkXuVNGNIEFBq9poHC61l8Kv6FdK7DRG3+Ic0rerC0DXOaeHNz8yW0fg/JnF8GQROOF5MA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "electron-winstaller": "5.4.0" + } + }, + "node_modules/electron-publish": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.15.3.tgz", + "integrity": "sha512-g/2bn8YTavY4cuS5F+jOS7zmZbXXBV8KZ8yHKfJjFPoKtzBqrpCdNPxBd3tqdBwP7BVd0lGzf7Bk2s0KesWZ4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^9.0.11", + "aws4": "^1.13.2", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "form-data": "^4.0.5", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.420", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.420.tgz", + "integrity": "sha512-2yD6XreGusOfNV+dUcvipJEXc3n/n7fgr7996aszTG+YY5E4mqM4tOq/3uhP129cazL9YHbVWSpc79ePotWtPA==", + "dev": true, + "license": "ISC" + }, + "node_modules/electron-vite": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/electron-vite/-/electron-vite-5.0.0.tgz", + "integrity": "sha512-OHp/vjdlubNlhNkPkL/+3JD34ii5ov7M0GpuXEVdQeqdQ3ulvVR7Dg/rNBLfS5XPIFwgoBLDf9sjjrL+CuDyRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.4", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "cac": "^6.7.14", + "esbuild": "^0.25.11", + "magic-string": "^0.30.19", + "picocolors": "^1.1.1" + }, + "bin": { + "electron-vite": "bin/electron-vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@swc/core": "^1.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + } + } + }, + "node_modules/electron-winstaller": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz", + "integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@electron/asar": "^3.2.1", + "debug": "^4.1.1", + "fs-extra": "^7.0.1", + "lodash": "^4.17.21", + "temp": "^0.9.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "@electron/windows-sign": "^1.1.2" + } + }, + "node_modules/electron-winstaller/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/electron-winstaller/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "peer": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-winstaller/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.9.1.tgz", + "integrity": "sha512-9VaAkDURekixUQJy0oJYl2DcN6oKMfxay7XzaGYAWQwsb6qfKf+x76R2k1L8kb1boc+FyCAaTA9GmiKaaiaF+A==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", + "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-agent/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isbinaryfile": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-abi": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.35.0.tgz", + "integrity": "sha512-ymk4aIzxdPopw2giv8Fs1Ec6vybGkjmyxUwVqhkI4MCy2tVfXdkOGGWieWVjL0THgH+7a8lRdevyupoYj3Js/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-api-version": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", + "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + } + }, + "node_modules/node-api-version/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz", + "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "undici": "^6.25.0", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp/node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/node-gyp/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp/node_modules/undici": { + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz", + "integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.54", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", + "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pe-library": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz", + "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkijs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.0.tgz", + "integrity": "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@noble/hashes": "1.4.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/pkijs/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/plist": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postject": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", + "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "commander": "^9.4.0" + }, + "bin": { + "postject": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/postject/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.2.0.tgz", + "integrity": "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-binary-file-arch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", + "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "bin": { + "read-binary-file-arch": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resedit": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz", + "integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pe-library": "^0.4.1" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/rollup": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.1.tgz", + "integrity": "sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.1", + "@rollup/rollup-android-arm64": "4.63.1", + "@rollup/rollup-darwin-arm64": "4.63.1", + "@rollup/rollup-darwin-x64": "4.63.1", + "@rollup/rollup-freebsd-arm64": "4.63.1", + "@rollup/rollup-freebsd-x64": "4.63.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.1", + "@rollup/rollup-linux-arm-musleabihf": "4.63.1", + "@rollup/rollup-linux-arm64-gnu": "4.63.1", + "@rollup/rollup-linux-arm64-musl": "4.63.1", + "@rollup/rollup-linux-loong64-gnu": "4.63.1", + "@rollup/rollup-linux-loong64-musl": "4.63.1", + "@rollup/rollup-linux-ppc64-gnu": "4.63.1", + "@rollup/rollup-linux-ppc64-musl": "4.63.1", + "@rollup/rollup-linux-riscv64-gnu": "4.63.1", + "@rollup/rollup-linux-riscv64-musl": "4.63.1", + "@rollup/rollup-linux-s390x-gnu": "4.63.1", + "@rollup/rollup-linux-x64-gnu": "4.63.1", + "@rollup/rollup-linux-x64-musl": "4.63.1", + "@rollup/rollup-openbsd-x64": "4.63.1", + "@rollup/rollup-openharmony-arm64": "4.63.1", + "@rollup/rollup-win32-arm64-msvc": "4.63.1", + "@rollup/rollup-win32-ia32-msvc": "4.63.1", + "@rollup/rollup-win32-x64-gnu": "4.63.1", + "@rollup/rollup-win32-x64-msvc": "4.63.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.4.tgz", + "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/stat-mode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "7.5.22", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/temp": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/temp-file": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", + "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^10.0.0" + } + }, + "node_modules/tiny-async-pool": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz", + "integrity": "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^5.5.0" + } + }, + "node_modules/tiny-async-pool/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.69.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.69.0.tgz", + "integrity": "sha512-B3MltX0VqjUBNEe3b3sSuiRbfa6XrfHFtBiPamjT5AsW/dfq+y+bc0wyuS9DxAS1LyzCxRp2+rxzpLUvqM2BvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.69.0", + "@typescript-eslint/parser": "8.69.0", + "@typescript-eslint/typescript-estree": "8.69.0", + "@typescript-eslint/utils": "8.69.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/undici": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", + "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unzipper": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.5.tgz", + "integrity": "sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "~3.7.2", + "duplexer2": "~0.1.4", + "fs-extra": "11.3.1", + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" + } + }, + "node_modules/unzipper/node_modules/fs-extra": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", + "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", + "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/webcrypto-core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.9.2.tgz", + "integrity": "sha512-gsXecm82UQNlTBURJGuqOWy1Ww08S3kZUcr3aOJS02Pk0xLtkfeUAVC0u0xhgdonFme80edSJUIJyuvL/7250Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.4.tgz", + "integrity": "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3f3f60b --- /dev/null +++ b/package.json @@ -0,0 +1,55 @@ +{ + "name": "rknboost", + "version": "0.4.0", + "description": "Кроссплатформенное десктопное приложение на Electron + React + TypeScript", + "main": "./out/main/index.js", + "author": "RKNboost", + "license": "MIT", + "private": true, + "engines": { + "node": ">=20" + }, + "scripts": { + "dev": "electron-vite dev", + "start": "electron-vite preview", + "build": "npm run zapret:fetch && npm run typecheck && electron-vite build", + "zapret:fetch": "node scripts/fetch-zapret.mjs", + "typecheck": "npm run typecheck:node && npm run typecheck:web", + "typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false", + "typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "format": "prettier --write .", + "format:check": "prettier --check .", + "build:unpack": "npm run build && electron-builder --dir", + "build:mac": "npm run build && electron-builder --mac", + "build:win": "npm run build && electron-builder --win", + "build:linux": "npm run build && electron-builder --linux", + "postinstall": "electron-builder install-app-deps" + }, + "dependencies": { + "@electron-toolkit/utils": "^4.0.0" + }, + "devDependencies": { + "@electron-toolkit/tsconfig": "^2.0.0", + "@eslint/js": "^10.0.1", + "@types/node": "^24.9.0", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "@vitejs/plugin-react": "^5.2.0", + "electron": "^44.1.1", + "electron-builder": "^26.15.3", + "electron-vite": "^5.0.0", + "eslint": "^10.9.1", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "prettier": "^3.9.6", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.69.0", + "vite": "^7.3.6" + } +} diff --git a/resources/icon.png b/resources/icon.png new file mode 100644 index 0000000..cf9e8b2 Binary files /dev/null and b/resources/icon.png differ diff --git a/scripts/fetch-zapret.mjs b/scripts/fetch-zapret.mjs new file mode 100644 index 0000000..b2e92e5 --- /dev/null +++ b/scripts/fetch-zapret.mjs @@ -0,0 +1,215 @@ +#!/usr/bin/env node +/** + * Кладёт бинарники и данные zapret в resources/zapret/ — оттуда electron-builder + * положит их в сборку через extraResources. + * + * Два источника: + * • bol-van/zapret — движки (tpws, winws.exe + WinDivert), версия пинована и + * сверяется по sha256sum.txt из релиза; + * • Flowseal/zapret-discord-youtube — подобранные сообществом списки доменов и + * стратегии winws (переносятся в код генератором `import-flowseal-strategies.mjs`, + * здесь просто данные: списки + fake-пакеты). У Flowseal нет sha256sum.txt — + * версия фиксируется по sha коммита, на который указывает тег. + * + * Каталог resources/zapret в git не хранится: запустите `npm run zapret:fetch` + * (или просто `npm run build` — он вызывает скрипт сам). + */ + +import { createHash } from 'node:crypto' +import { execFileSync } from 'node:child_process' +import { mkdir, mkdtemp, copyFile, readFile, writeFile, rm, chmod } from 'node:fs/promises' +import { existsSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { dirname, join, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const ZAPRET_VERSION = 'v72.13' +const WINDIVERT_VERSION = 'v2.2.2' + +/** Тег Flowseal, зафиксированный по sha коммита (проверено: `git/refs/tags/1.10.2`). */ +const FLOWSEAL_VERSION = '1.10.2' +const FLOWSEAL_COMMIT = 'dfd8e613b099676cf2aa7b474ee5923801514dec' + +const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const DEST = join(ROOT, 'resources', 'zapret') + +const RELEASE = `https://github.com/bol-van/zapret/releases/download/${ZAPRET_VERSION}` +const ARCHIVE = `zapret-${ZAPRET_VERSION}.tar.gz` +/** Корневой каталог внутри архива — с ним же начинаются пути в sha256sum.txt. */ +const PREFIX = `zapret-${ZAPRET_VERSION}` + +/** Что забираем из архива bol-van/zapret: путь внутри архива → путь внутри resources/zapret. */ +const FILES = { + 'binaries/mac64/tpws': 'darwin/tpws', + 'binaries/windows-x86_64/winws.exe': 'win32/x64/winws.exe', + 'binaries/windows-x86_64/cygwin1.dll': 'win32/x64/cygwin1.dll', + 'binaries/windows-x86_64/WinDivert.dll': 'win32/x64/WinDivert.dll', + 'binaries/windows-x86_64/WinDivert64.sys': 'win32/x64/WinDivert64.sys', + 'docs/LICENSE.txt': 'LICENSE.zapret.txt' +} + +/** Исполняемый бит нужен только там, где мы сами запускаем бинарник. */ +const EXECUTABLE = new Set(['darwin/tpws']) + +/** + * Списки доменов Flowseal → наши короткие имена (используются и в `lists.ts`, и + * в сгенерированных стратегиях). `*-user.txt` не берём — у нас все списки и так + * редактируемые, отдельного пользовательского слоя не нужно. + */ +const FLOWSEAL_LISTS = { + 'lists/list-general.txt': 'lists/general.txt', + 'lists/list-google.txt': 'lists/google.txt', + 'lists/list-exclude.txt': 'lists/exclude.txt', + 'lists/ipset-exclude.txt': 'lists/ipset-exclude.txt', + 'lists/ipset-all.txt': 'lists/ipset-all.txt' +} + +/** Fake-пакеты, реально используемые хотя бы одной стратегией general*.bat. */ +const FLOWSEAL_FAKES = [ + 'ACTIVE_DISCORD_UDP.bin', + 'ACTIVE_GAME_UDP.bin', + 'quic_initial_4pda_to.bin', + 'quic_initial_www_google_com.bin', + 'stun.bin', + 'stun2.bin', + 'tls_clienthello_4pda_to.bin', + 'tls_clienthello_max_ru.bin', + 'tls_clienthello_sochi_park.bin', + 'tls_clienthello_www_google_com.bin' +] + +async function download(url) { + const response = await fetch(url, { redirect: 'follow' }) + if (!response.ok) throw new Error(`${response.status} ${response.statusText} — ${url}`) + return Buffer.from(await response.arrayBuffer()) +} + +function sha256(buffer) { + return createHash('sha256').update(buffer).digest('hex') +} + +/** sha256sum.txt: строки вида " zapret-v72.13/binaries/mac64/tpws". */ +function parseChecksums(text) { + const checksums = new Map() + for (const line of text.split('\n')) { + const match = /^([0-9a-f]{64})\s+(\S+)$/.exec(line.trim()) + if (match) checksums.set(match[2], match[1]) + } + return checksums +} + +async function fetchZapretEngines(work) { + console.log(`zapret ${ZAPRET_VERSION}: скачиваю ${ARCHIVE}`) + const archivePath = join(work, ARCHIVE) + await writeFile(archivePath, await download(`${RELEASE}/${ARCHIVE}`)) + + const checksums = parseChecksums((await download(`${RELEASE}/sha256sum.txt`)).toString('utf8')) + + // tar есть на macOS, Linux и в Windows 10 1803+; распаковываем только нужные пути. + execFileSync( + 'tar', + ['-xzf', archivePath, '-C', work, ...Object.keys(FILES).map((p) => `${PREFIX}/${p}`)], + { stdio: ['ignore', 'ignore', 'inherit'] } + ) + + for (const [source, target] of Object.entries(FILES)) { + const extracted = join(work, PREFIX, source) + const content = await readFile(extracted) + + // Лицензии в sha256sum.txt не попадают — там только binaries/**. + const expected = checksums.get(`${PREFIX}/${source}`) + if (expected) { + const actual = sha256(content) + if (actual !== expected) { + throw new Error( + `не совпала контрольная сумма ${source}\n ожидалось: ${expected}\n получено: ${actual}` + ) + } + } + + const destination = join(DEST, target) + await mkdir(dirname(destination), { recursive: true }) + await copyFile(extracted, destination) + if (EXECUTABLE.has(target)) await chmod(destination, 0o755) + + console.log(` ${target}${expected ? ' ✓ sha256' : ''}`) + } + + // Лицензия WinDivert лежит в своём репозитории, в архив zapret не входит. + const windivertLicense = await download( + `https://raw.githubusercontent.com/basil00/WinDivert/${WINDIVERT_VERSION}/LICENSE` + ) + await writeFile(join(DEST, 'LICENSE.WinDivert.txt'), windivertLicense) + console.log(' LICENSE.WinDivert.txt') +} + +async function fetchFlowsealData(work) { + console.log( + `Flowseal ${FLOWSEAL_VERSION} (commit ${FLOWSEAL_COMMIT.slice(0, 12)}): скачиваю списки и fake-пакеты` + ) + + const archivePath = join(work, 'flowseal.tar.gz') + // Тарбол по sha коммита, а не по имени тега — тег можно переставить, sha неизменен. + await writeFile( + archivePath, + await download( + `https://codeload.github.com/Flowseal/zapret-discord-youtube/tar.gz/${FLOWSEAL_COMMIT}` + ) + ) + + const paths = [ + ...Object.keys(FLOWSEAL_LISTS), + ...FLOWSEAL_FAKES.map((name) => `bin/${name}`), + 'LICENSE.txt' + ] + + // GitHub codeload архивирует репозиторий в каталог "-/". + const prefixOut = execFileSync('tar', ['-tzf', archivePath], { encoding: 'utf8' }) + .split('\n')[0] + .split('/')[0] + + execFileSync('tar', ['-xzf', archivePath, '-C', work, ...paths.map((p) => `${prefixOut}/${p}`)], { + stdio: ['ignore', 'ignore', 'inherit'] + }) + + for (const [source, target] of Object.entries(FLOWSEAL_LISTS)) { + const destination = join(DEST, target) + await mkdir(dirname(destination), { recursive: true }) + await copyFile(join(work, prefixOut, source), destination) + console.log(` ${target}`) + } + + const fakesDir = join(DEST, 'fakes') + await mkdir(fakesDir, { recursive: true }) + for (const name of FLOWSEAL_FAKES) { + await copyFile(join(work, prefixOut, 'bin', name), join(fakesDir, name)) + console.log(` fakes/${name}`) + } + + await copyFile(join(work, prefixOut, 'LICENSE.txt'), join(DEST, 'LICENSE.flowseal.txt')) + console.log(' LICENSE.flowseal.txt') +} + +async function main() { + const work = await mkdtemp(join(tmpdir(), 'zapret-')) + + try { + await fetchZapretEngines(work) + await fetchFlowsealData(work) + console.log(`Готово: ${DEST}`) + } finally { + await rm(work, { recursive: true, force: true }) + } +} + +// Повторная сборка не должна каждый раз ходить в сеть. +if ( + process.argv.includes('--force') || + !existsSync(join(DEST, 'darwin', 'tpws')) || + !existsSync(join(DEST, 'win32', 'x64', 'winws.exe')) || + !existsSync(join(DEST, 'lists', 'general.txt')) +) { + await main() +} else { + console.log(`zapret уже распакован в ${DEST} (перекачать: npm run zapret:fetch -- --force)`) +} diff --git a/scripts/import-flowseal-strategies.mjs b/scripts/import-flowseal-strategies.mjs new file mode 100644 index 0000000..65c252e --- /dev/null +++ b/scripts/import-flowseal-strategies.mjs @@ -0,0 +1,188 @@ +#!/usr/bin/env node +/** + * Разовый (перезапускаемый) генератор: превращает `general*.bat` из + * Flowseal/zapret-discord-youtube в `src/main/zapret/strategies.win32.generated.ts`. + * + * Зачем отдельным скриптом, а не частью `fetch-zapret.mjs`: вывод коммитится в + * репозиторий — в рантайме и в сборке .bat-файлов быть не должно, только сгенерированный + * TypeScript. Запускать вручную при обновлении `FLOWSEAL_COMMIT` в `fetch-zapret.mjs`, + * проверяя результат через `git diff`, а не доверяя вслепую (формат батников может + * измениться — тогда парсер должен упасть, а не тихо сгенерировать мусор). + * + * Формат batch-файлов Flowseal строго регулярный (проверено на всех 22 вариантах): + * start "zapret: %~n0" /min "%BIN%winws.exe" --arg1 --arg2 ^ + * --arg3 ^ + * ... + * --argN + * Это одна команда, продолжающаяся через `^` в конце строки — не программа, а + * длинная командная строка winws. Переносим только сами аргументы. + */ + +import { execFileSync } from 'node:child_process' +import { mkdtemp, readFile, rm, readdir, writeFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { dirname, join, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +// Держим в синхроне с `fetch-zapret.mjs` — версия данных должна совпадать. +const FLOWSEAL_COMMIT = 'dfd8e613b099676cf2aa7b474ee5923801514dec' +const FLOWSEAL_VERSION = '1.10.2' + +const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const OUTPUT = join(ROOT, 'src', 'main', 'zapret', 'strategies.win32.generated.ts') + +/** Списки Flowseal → наши короткие имена (совпадает с `FLOWSEAL_LISTS` в fetch-zapret.mjs). */ +const LIST_FILES = { + 'list-general.txt': 'general', + 'list-google.txt': 'google', + 'list-exclude.txt': 'exclude', + 'ipset-exclude.txt': 'ipset-exclude', + 'ipset-all.txt': 'ipset-all' +} + +/** Пользовательские дубликаты списков — у нас все списки и так редактируемые, лишние. */ +const DROP_LIST_SUFFIXES = ['-user.txt'] + +/** Игровой фильтр Flowseal по умолчанию выключен: `GameFilterTCP=12`, `GameFilterUDP=12`. */ +const GAME_FILTER_DISABLED = '12' + +function idFromFilename(filename) { + // "general.bat" → "general" / "Стандартная"; "general (ALT2).bat" → "alt2" / "ALT2" + const match = /^general(?: \((.+)\))?\.bat$/.exec(filename) + if (!match) throw new Error(`Неожиданное имя файла стратегии: ${filename}`) + const variant = match[1] + if (!variant) return { id: 'general', name: 'Стандартная' } + const id = variant.toLowerCase().replace(/\s+/g, '-') + return { id, name: variant } +} + +/** Разбивает командную строку на токены, учитывая кавычки, без внешней библиотеки. */ +function tokenize(command) { + const tokens = command.match(/(?:[^\s"]|"[^"]*")+/g) ?? [] + return tokens.map((token) => token.replace(/^"(.*)"$/, '$1')) +} + +function resolvePlaceholders(token) { + for (const [flowsealName, ourName] of Object.entries(LIST_FILES)) { + if (token.includes(`%LISTS%${flowsealName}`)) { + return token.replace(`%LISTS%${flowsealName}`, `{LISTS}/${ourName}.txt`) + } + } + const binMatch = /%BIN%([A-Za-z0-9_.]+\.bin)/.exec(token) + if (binMatch) return token.replace(binMatch[0], `{FAKES}/${binMatch[1]}`) + + return token + .replaceAll('%GameFilterTCP%', GAME_FILTER_DISABLED) + .replaceAll('%GameFilterUDP%', GAME_FILTER_DISABLED) +} + +function isDroppedUserListArg(token) { + return DROP_LIST_SUFFIXES.some((suffix) => token.includes(`%LISTS%`) && token.includes(suffix)) +} + +function parseBatFile(filename, content) { + const marker = 'winws.exe"' + const start = content.indexOf(marker) + if (start === -1) throw new Error(`${filename}: не нашёл запуск winws.exe`) + + // Дальше до конца файла — одна командная строка, продолжающаяся через "^\n". + const rest = content.slice(start + marker.length) + const joined = rest.replace(/\^\r?\n/g, ' ') + + const tokens = tokenize(joined) + .map((t) => t.trim()) + .filter(Boolean) + .filter((t) => !isDroppedUserListArg(t)) + // Кавычки в .bat защищали путь от разбора cmd.exe при запуске через `start`. + // Мы передаём argv напрямую в spawn (без шелла) — кавычки внутри значения + // остались бы буквальными символами в имени файла, поэтому убираем везде. + .map((t) => t.replaceAll('"', '')) + .map(resolvePlaceholders) + + if (tokens.length < 5) + throw new Error(`${filename}: подозрительно мало аргументов (${tokens.length})`) + + const { id, name } = idFromFilename(filename) + return { id, name, args: tokens } +} + +async function main() { + const work = await mkdtemp(join(tmpdir(), 'flowseal-strategies-')) + + try { + console.log( + `Flowseal ${FLOWSEAL_VERSION} (commit ${FLOWSEAL_COMMIT.slice(0, 12)}): скачиваю batch-файлы` + ) + const archivePath = join(work, 'flowseal.tar.gz') + const response = await fetch( + `https://codeload.github.com/Flowseal/zapret-discord-youtube/tar.gz/${FLOWSEAL_COMMIT}` + ) + if (!response.ok) throw new Error(`${response.status} ${response.statusText}`) + await writeFile(archivePath, Buffer.from(await response.arrayBuffer())) + + const prefix = execFileSync('tar', ['-tzf', archivePath], { encoding: 'utf8' }) + .split('\n')[0] + .split('/')[0] + execFileSync('tar', ['-xzf', archivePath, '-C', work], { + stdio: ['ignore', 'ignore', 'inherit'] + }) + + const srcDir = join(work, prefix) + const batFiles = (await readdir(srcDir)).filter( + (name) => name.startsWith('general') && name.endsWith('.bat') + ) + batFiles.sort() + + if (batFiles.length === 0) + throw new Error('Не нашлось ни одного general*.bat — формат репозитория изменился') + + const strategies = [] + for (const filename of batFiles) { + const content = await readFile(join(srcDir, filename), 'utf8') + strategies.push(parseBatFile(filename, content)) + console.log( + ` ${filename} → ${strategies.at(-1).id} (${strategies.at(-1).args.length} аргументов)` + ) + } + + const ids = new Set(strategies.map((s) => s.id)) + if (ids.size !== strategies.length) + throw new Error('Столкновение id — два файла дали одинаковый идентификатор') + + const body = strategies + .map( + (s) => + ` {\n id: ${JSON.stringify(s.id)},\n name: ${JSON.stringify(s.name)},\n args: [\n${s.args + .map((a) => ` ${JSON.stringify(a)}`) + .join(',\n')}\n ]\n }` + ) + .join(',\n') + + const output = `/** + * СГЕНЕРИРОВАНО \`scripts/import-flowseal-strategies.mjs\` из + * Flowseal/zapret-discord-youtube ${FLOWSEAL_VERSION} (commit ${FLOWSEAL_COMMIT}). + * Не редактировать руками — перезапустить генератор и закоммитить результат. + * + * \`{LISTS}\` и \`{FAKES}\` — плейсхолдеры путей, резолвятся в \`strategies.ts\` + * через \`resolvePlaceholders()\` в фактические пути userData/resources. + */ + +export interface GeneratedWin32Strategy { + id: string + name: string + args: string[] +} + +export const WIN32_STRATEGIES: readonly GeneratedWin32Strategy[] = [ +${body} +] +` + + await writeFile(OUTPUT, output) + console.log(`Готово: ${OUTPUT} (${strategies.length} стратегий)`) + } finally { + await rm(work, { recursive: true, force: true }) + } +} + +await main() diff --git a/src/main/first-run.ts b/src/main/first-run.ts new file mode 100644 index 0000000..8f6769c --- /dev/null +++ b/src/main/first-run.ts @@ -0,0 +1,55 @@ +import { access, writeFile } from 'node:fs/promises' +import { join } from 'node:path' +import { app, dialog, type BrowserWindow } from 'electron' +import { GITHUB_URL } from '../shared/links' +import { openExternal } from './window' + +/** + * Метка «приветствие уже показывали». Лежит в userData, поэтому переживает обновление + * приложения — окно показывается один раз за всё время, а не после каждой установки. + */ +function markerPath(): string { + return join(app.getPath('userData'), 'first-run.json') +} + +async function alreadyShown(): Promise { + try { + await access(markerPath()) + return true + } catch { + return false + } +} + +/** + * Приветственное окно при первом запуске: просьба поддержать проект звёздочкой. + * + * Рисует его сама ОС (`dialog.showMessageBox`), а не HTML-страница: так окно получается + * маленьким, модальным к главному и выглядит системным, а не как ещё один экран приложения. + */ +export async function showFirstRunSupportDialog(window: BrowserWindow): Promise { + if (await alreadyShown()) return + + // Метку ставим ДО показа, а не после ответа: если приложение закроют прямо на диалоге, + // лучше не показать его больше никогда, чем спрашивать при каждом запуске. + try { + await writeFile(markerPath(), JSON.stringify({ shownAt: new Date().toISOString() }), 'utf8') + } catch { + // Не смогли записать метку — покажем окно ещё раз в следующий запуск. Не повод падать. + } + + const { response } = await dialog.showMessageBox(window, { + type: 'info', + buttons: ['Перейти', 'Закрыть'], + defaultId: 0, + cancelId: 1, + noLink: true, + title: 'Поддержите RKNboost', + message: 'Поддержите проект', + detail: 'Поддержите проект звездочкой на GitHub, и расскажите друзьям :З' + }) + + // openExternal (window.ts) проверяет протокол по белому списку — ссылка уходит + // в системный браузер, а не открывается внутри приложения. + if (response === 0) openExternal(GITHUB_URL) +} diff --git a/src/main/index.ts b/src/main/index.ts new file mode 100644 index 0000000..2466a12 --- /dev/null +++ b/src/main/index.ts @@ -0,0 +1,77 @@ +import { app, BrowserWindow } from 'electron' +import { electronApp, optimizer } from '@electron-toolkit/utils' +import { createWindow } from './window' +import { showFirstRunSupportDialog } from './first-run' +import { registerIpcHandlers } from './ipc' +import { disposeZapret, recoverZapret } from './zapret' +import { elevatedTaskDir, runElevatedServiceTask } from './zapret/elevated-task.win32' + +const APP_ID = 'com.rknboost.app' + +// Приложение может быть запущено само собой через UAC как «повышенный помощник» — тогда +// оно не открывает окно и не занимает единственный экземпляр (иначе отдало бы фокус уже +// запущенной обычной копии и вышло, не сделав работу), а выполняет задачу службы и выходит. +const elevatedTask = process.platform === 'win32' ? elevatedTaskDir(process.argv) : null + +if (elevatedTask) { + void app.whenReady().then(async () => { + app.exit(await runElevatedServiceTask(elevatedTask)) + }) +} else if (!app.requestSingleInstanceLock()) { + // Одна копия приложения на систему: второй запуск отдаёт фокус уже открытому окну. + app.quit() +} else { + app.on('second-instance', () => { + const [window] = BrowserWindow.getAllWindows() + if (!window) { + createWindow() + return + } + if (window.isMinimized()) window.restore() + window.focus() + }) + + app.whenReady().then(() => { + // Нужно Windows для группировки в панели задач и уведомлений. + electronApp.setAppUserModelId(APP_ID) + + // F12 открывает DevTools в dev-режиме, Ctrl/Cmd+R не перезагружает прод-сборку. + app.on('browser-window-created', (_event, window) => { + optimizer.watchWindowShortcuts(window) + }) + + registerIpcHandlers() + + // На macOS чинит прокси, оставшийся от аварийно завершённого прошлого запуска, + // до того как окно вообще откроется. + void recoverZapret().then(() => { + const window = createWindow() + // Ждём первого кадра: диалог должен появиться поверх готового окна, а не пустого. + window.once('ready-to-show', () => void showFirstRunSupportDialog(window)) + }) + + // macOS: клик по иконке в доке при закрытых окнах открывает окно заново. + app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) createWindow() + }) + }) + + // Windows / Linux: закрыли последнее окно — вышли. macOS: приложение живёт до Cmd+Q. + app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit() + } + }) + + // Гасим tpws/winws и откатываем системный прокси до фактического выхода — + // иначе процесс останется висеть, а на macOS ещё и интернет пропадёт. + let zapretCleaned = false + app.on('before-quit', (event) => { + if (zapretCleaned) return + event.preventDefault() + void disposeZapret().finally(() => { + zapretCleaned = true + app.quit() + }) + }) +} diff --git a/src/main/ipc.ts b/src/main/ipc.ts new file mode 100644 index 0000000..40a612c --- /dev/null +++ b/src/main/ipc.ts @@ -0,0 +1,78 @@ +import { BrowserWindow, ipcMain, nativeTheme, type IpcMainInvokeEvent } from 'electron' +import type { IpcChannel, IpcHandlers, ThemeState } from '../shared/ipc-contract' +import { uninstallApp } from './uninstall.win32' +import { + clearZapretAutoHostlist, + resetZapretList, + saveZapretList, + setZapretAutoStart, + setZapretStrategy, + setZapretSystemProxy, + startZapret, + stopZapret, + zapretAutoHostlist, + zapretDiagnostics, + zapretLists, + zapretStatus, + zapretStrategies +} from './zapret' + +/** + * Обёртка над `ipcMain.handle`, которая сверяет имя канала, аргументы и результат + * с контрактом из `src/shared/ipc-contract.ts`. + */ +function handle( + channel: K, + handler: ( + event: IpcMainInvokeEvent, + ...args: Parameters + ) => ReturnType | Promise> +): void { + ipcMain.handle(channel, handler as Parameters[1]) +} + +/** Окно, из которого пришёл запрос (а не «первое попавшееся») — для диалога подтверждения. */ +function windowOf(event: IpcMainInvokeEvent): BrowserWindow | null { + return BrowserWindow.fromWebContents(event.sender) +} + +function themeState(): ThemeState { + return { + source: nativeTheme.themeSource, + shouldUseDarkColors: nativeTheme.shouldUseDarkColors + } +} + +export function registerIpcHandlers(): void { + handle('theme:get', () => themeState()) + + // Кнопка «Удалить приложение» есть в интерфейсе только на Windows (SettingsView.tsx), + // но канал регистрируем всегда — так и должно быть по контракту, просто на других + // платформах он никогда не вызывается. + handle('app:uninstall', (event) => { + if (process.platform === 'win32') void uninstallApp(windowOf(event)) + }) + + handle('zapret:status', () => zapretStatus()) + handle('zapret:strategies', () => zapretStrategies()) + handle('zapret:start', (_event, strategyId) => startZapret(strategyId)) + handle('zapret:stop', () => stopZapret()) + handle('zapret:set-system-proxy', (_event, enabled) => setZapretSystemProxy(enabled)) + handle('zapret:set-strategy', (_event, strategyId) => setZapretStrategy(strategyId)) + handle('zapret:set-autostart', (_event, enabled) => setZapretAutoStart(enabled)) + + handle('zapret:lists', () => zapretLists()) + handle('zapret:list-save', (_event, id, entries) => saveZapretList(id, entries)) + handle('zapret:list-reset', (_event, id) => resetZapretList(id)) + handle('zapret:auto-hostlist', () => zapretAutoHostlist()) + handle('zapret:auto-hostlist-clear', () => clearZapretAutoHostlist()) + handle('zapret:diagnose', () => zapretDiagnostics()) + + // Системная тема поменялась (например, пользователь включил тёмный режим в ОС) — + // сообщаем всем открытым окнам. + nativeTheme.on('updated', () => { + for (const window of BrowserWindow.getAllWindows()) { + window.webContents.send('theme:changed', themeState()) + } + }) +} diff --git a/src/main/uninstall.win32.ts b/src/main/uninstall.win32.ts new file mode 100644 index 0000000..16bb97e --- /dev/null +++ b/src/main/uninstall.win32.ts @@ -0,0 +1,145 @@ +import { execFile, spawn } from 'node:child_process' +import { promisify } from 'node:util' +import { + app, + dialog, + type BrowserWindow, + type MessageBoxOptions, + type MessageBoxReturnValue +} from 'electron' + +const run = promisify(execFile) + +/** dialog.showMessageBox без родительского окна — отдельная перегрузка, без окна её и зовём. */ +function showDialog( + window: BrowserWindow | null, + options: MessageBoxOptions +): Promise { + return window ? dialog.showMessageBox(window, options) : dialog.showMessageBox(options) +} + +/** + * Установщик (per-user, `perMachine: false` в electron-builder.yml) регистрирует + * деинсталлятор в HKCU, не в HKLM — читать оттуда можно без прав администратора. + * Ищем по DisplayName, а не по фиксированному GUID/пути: имя ключа реестра + * electron-builder генерирует детерминированно из `appId`, но полагаться на точное + * значение — лишний повод сломаться при следующей пересборке. + */ +interface UninstallCommand { + command: string + args: string[] +} + +/** + * `UninstallString` записывает сам electron-builder (templates/nsis/include/installer.nsh:122) + * строкой `'"$2" $0'`, где `$2` — путь к деинсталлятору, а `$0` — `/currentuser` для + * per-user установки (или `/allusers`). То есть путь в кавычках И аргумент после него: + * + * "C:\Users\...\AppData\Local\Programs\RKNboost\Uninstall RKNboost.exe" /currentuser + * + * Аргумент обязателен: без него деинсталлятор не знает, из какой ветки реестра сниматься. + * Разбираем строку на команду и аргументы, а не пытаемся скормить её `spawn` целиком — + * иначе весь текст вместе с кавычками уходит в имя файла и получается ENOENT. + */ +function parseUninstallString(raw: string): UninstallCommand | null { + const trimmed = raw.trim() + if (!trimmed) return null + + const quoted = /^"([^"]+)"\s*(.*)$/.exec(trimmed) + if (quoted) { + return { command: quoted[1], args: quoted[2].split(/\s+/).filter(Boolean) } + } + + // Без кавычек путь бывает только если в нём нет пробелов — тогда первое слово и есть команда. + const [command, ...args] = trimmed.split(/\s+/) + return command ? { command, args } : null +} + +/** + * Установщик (per-user, `perMachine: false` в electron-builder.yml) регистрирует + * деинсталлятор в HKCU, не в HKLM — читать оттуда можно без прав администратора. + * Ищем по DisplayName, а не по фиксированному GUID/пути: имя ключа реестра + * electron-builder генерирует детерминированно из `appId`, но полагаться на точное + * значение — лишний повод сломаться при следующей пересборке. + */ +async function findUninstallCommand(): Promise { + const script = + "Get-ChildItem 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall' " + + '| Get-ItemProperty ' + + "| Where-Object { $_.DisplayName -eq 'RKNboost' } " + + '| Select-Object -First 1 -ExpandProperty UninstallString' + + let stdout: string + try { + ;({ stdout } = await run('powershell.exe', ['-NoProfile', '-Command', script])) + } catch { + return null + } + + return parseUninstallString(stdout) +} + +/** + * `spawn` сообщает о ненайденном файле не исключением, а событием `error` на уже + * созданном объекте — без этого обработчика ENOENT всплывал бы необработанным + * отказом промиса в main-процессе. Выходим только после события `spawn`, то есть + * когда деинсталлятор точно стартовал: закрыться раньше значит не закрыться вовсе. + */ +function launchUninstaller({ command, args }: UninstallCommand): Promise { + return new Promise((resolve, reject) => { + // detached: деинсталлятор должен пережить закрытие текущего процесса. + const child = spawn(command, args, { detached: true, stdio: 'ignore' }) + child.once('error', reject) + child.once('spawn', () => { + child.unref() + resolve() + }) + }) +} + +/** + * Подтверждение нативным диалогом ОС (не веб-страницей — так надёжнее для + * действия, которое стирает и приложение, и списки сайтов пользователя), + * затем запуск деинсталлятора и выход — деинсталлятор не может удалить + * запущенное приложение. + */ +export async function uninstallApp(window: BrowserWindow | null): Promise { + const { response } = await showDialog(window, { + type: 'warning', + buttons: ['Отмена', 'Удалить'], + defaultId: 0, + cancelId: 0, + noLink: true, + title: 'Удалить RKNboost', + message: 'Удалить RKNboost с этого компьютера?', + detail: + 'Приложение закроется и запустится стандартный деинсталлятор Windows. ' + + 'Вместе с ним будут удалены списки сайтов и служба обхода блокировок. ' + + 'Windows может запросить права администратора.' + }) + if (response !== 1) return + + const uninstall = await findUninstallCommand() + if (!uninstall) { + await showDialog(window, { + type: 'error', + title: 'Не удалось найти деинсталлятор', + message: 'Удалите приложение вручную через «Параметры Windows → Приложения».' + }) + return + } + + try { + await launchUninstaller(uninstall) + } catch (error) { + await showDialog(window, { + type: 'error', + title: 'Не удалось запустить деинсталлятор', + message: 'Удалите приложение вручную через «Параметры Windows → Приложения».', + detail: error instanceof Error ? error.message : String(error) + }) + return + } + + app.quit() +} diff --git a/src/main/window.ts b/src/main/window.ts new file mode 100644 index 0000000..f165952 --- /dev/null +++ b/src/main/window.ts @@ -0,0 +1,77 @@ +import { BrowserWindow, shell } from 'electron' +import { join } from 'node:path' +import { is } from '@electron-toolkit/utils' +import icon from '../../resources/icon.png?asset' +import { ALLOWED_EXTERNAL_PROTOCOLS } from '../shared/ipc-contract' + +function protocolOf(url: string): string | null { + try { + return new URL(url).protocol + } catch { + return null + } +} + +/** Открыть ссылку во внешнем браузере, но только если протокол в белом списке. */ +export function openExternal(url: string): void { + const protocol = protocolOf(url) + + if (protocol && (ALLOWED_EXTERNAL_PROTOCOLS as readonly string[]).includes(protocol)) { + void shell.openExternal(url) + } else { + console.warn(`[security] заблокирована внешняя ссылка: ${url}`) + } +} + +/** Главное (и единственное) окно приложения. */ +export function createWindow(): BrowserWindow { + const window = new BrowserWindow({ + width: 440, + height: 660, + minWidth: 360, + // С запасом на самое высокое обычное состояние главного экрана (ошибка + три + // результата проверки), чтобы полоса прокрутки в карточке не появлялась на ровном месте. + minHeight: 560, + show: false, + autoHideMenuBar: true, + backgroundColor: '#1a1611', + // Иконка нужна только Linux — на macOS и Windows её берут из собранного бандла. + ...(process.platform === 'linux' ? { icon } : {}), + webPreferences: { + preload: join(__dirname, '../preload/index.js'), + // Значения по умолчанию у Electron уже безопасные, но фиксируем их явно, + // чтобы случайная правка была заметна в ревью. + sandbox: true, + contextIsolation: true, + nodeIntegration: false, + webSecurity: true + } + }) + + // Показываем окно только когда отрисован первый кадр — без «белой вспышки». + window.on('ready-to-show', () => window.show()) + + // window.open(...) и target="_blank" уходят в системный браузер, а не в новое окно Electron. + window.webContents.setWindowOpenHandler(({ url }) => { + openExternal(url) + return { action: 'deny' } + }) + + // Запрещаем renderer'у уходить с локальной страницы (защита от навигации на чужой origin). + window.webContents.on('will-navigate', (event, url) => { + const devServerUrl = process.env['ELECTRON_RENDERER_URL'] + const isDevServer = Boolean(devServerUrl && url.startsWith(devServerUrl)) + if (!isDevServer && !url.startsWith('file://')) { + event.preventDefault() + openExternal(url) + } + }) + + if (is.dev && process.env['ELECTRON_RENDERER_URL']) { + void window.loadURL(process.env['ELECTRON_RENDERER_URL']) + } else { + void window.loadFile(join(__dirname, '../renderer/index.html')) + } + + return window +} diff --git a/src/main/zapret/diagnostics.ts b/src/main/zapret/diagnostics.ts new file mode 100644 index 0000000..7b659ea --- /dev/null +++ b/src/main/zapret/diagnostics.ts @@ -0,0 +1,205 @@ +import { connect as netConnect, type Socket } from 'node:net' +import { connect as tlsConnect } from 'node:tls' +import { net } from 'electron' +import type { ZapretDiagnosticResult, ZapretStatus } from '../../shared/ipc-contract' + +// 6 секунд не хватало: десинк с `--dpi-desync-repeats` заметно удлиняет установку соединения, +// и медленный, но живой сайт успевал попасть в «Недоступно». +const TIMEOUT_MS = 10000 +const TARGET_PORT = 443 +const RETRY_DELAY_MS = 300 + +interface DiagnosticTarget { + id: string + label: string + host: string +} + +/** Сайты, которые нужно проверять в первую очередь — по просьбе пользователя. */ +const TARGETS: DiagnosticTarget[] = [ + { id: 'instagram', label: 'Instagram', host: 'instagram.com' }, + { id: 'discord', label: 'Discord', host: 'discord.com' }, + { id: 'youtube', label: 'YouTube', host: 'youtube.com' } +] + +/** + * Расшифровка кодов Chromium: без неё в интерфейсе у всех трёх сайтов одинаковое + * «Недоступно», и по нему невозможно отличить «DPI режет» от «домен не резолвится» + * или «антивирус перехватывает HTTPS» — а лечатся они совершенно по-разному. + */ +const ERROR_HINTS: [pattern: RegExp, hint: string][] = [ + [/ERR_NAME_NOT_RESOLVED|ERR_NAME_RESOLUTION_FAILED/, 'домен не резолвится — блокировка по DNS'], + [/ERR_INTERNET_DISCONNECTED|ERR_NETWORK_CHANGED/, 'нет подключения к интернету'], + [/ERR_CONNECTION_RESET|ERR_CONNECTION_CLOSED/, 'соединение сброшено — похоже на DPI'], + [/ERR_CONNECTION_TIMED_OUT|ERR_TIMED_OUT/, 'таймаут — пакеты теряются молча'], + [/ERR_CONNECTION_REFUSED/, 'соединение отклонено'], + [/ERR_CERT_|ERR_SSL_/, 'сертификат не принят — вероятно, антивирус перехватывает HTTPS'], + [/ERR_PROXY_/, 'мешает системный прокси'] +] + +function describeNetError(message: string): string { + const code = /net::(ERR_[A-Z_]+)/.exec(message)?.[1] ?? message + const hint = ERROR_HINTS.find(([pattern]) => pattern.test(message))?.[1] + return hint ? `${hint} (${code})` : code +} + +/** + * SOCKS5 CONNECT без авторизации — ровно то, что понимает tpws в режиме `--socks`. + * Три сообщения: приветствие (метод «без авторизации»), запрос на домен, ответ сервера. + */ +function socksConnect(socket: Socket, host: string, port: number): Promise { + return new Promise((resolve, reject) => { + const hostBuf = Buffer.from(host, 'utf8') + + socket.once('data', (greeting: Buffer) => { + if (greeting[0] !== 0x05 || greeting[1] !== 0x00) { + reject(new Error('SOCKS5: сервер отклонил метод авторизации')) + return + } + + socket.once('data', (reply: Buffer) => { + if (reply[1] !== 0x00) { + reject(new Error(`SOCKS5: код ошибки 0x${reply[1].toString(16)}`)) + return + } + resolve() + }) + + socket.write( + Buffer.concat([ + Buffer.from([0x05, 0x01, 0x00, 0x03, hostBuf.length]), + hostBuf, + Buffer.from([port >> 8, port & 0xff]) + ]) + ) + }) + + socket.write(Buffer.from([0x05, 0x01, 0x00])) + }) +} + +/** + * Прямая проверка — путь для Windows (десинк общесистемный, через WinDivert) и для + * выключенного обхода. + * + * Намеренно через `net` Electron, то есть через сетевой стек Chromium, а НЕ через + * `node:tls`. Тест должен отвечать на вопрос «откроется ли этот сайт в браузере», а + * Node-стек отвечает на другой: у него собственный список корневых сертификатов (мимо + * хранилища Windows — и любой антивирус с проверкой HTTPS ломает проверку на ровном + * месте), собственный резолвер (мимо DoH) и заметно более простой ClientHello, чем у + * браузера, — а стратегии zapret настраивают именно под браузерный. Из-за этого проверка + * показывала «Недоступно» на всех трёх сайтах, которые в браузере при включённом обходе + * открывались нормально. + */ +function checkDirect(host: string): Promise { + return new Promise((resolve, reject) => { + const start = Date.now() + // HEAD, а не GET: нас интересует только сам факт, что до сайта дошли и он ответил, + // тело страницы качать незачем. redirect: 'manual' — редирект (например, + // instagram.com → www.instagram.com) это тоже ответ, гнаться за ним не нужно. + const request = net.request({ method: 'HEAD', url: `https://${host}/`, redirect: 'manual' }) + let settled = false + + const finish = (error: Error | null): void => { + if (settled) return + settled = true + clearTimeout(timer) + request.abort() + if (error) reject(new Error(describeNetError(error.message))) + else resolve(Date.now() - start) + } + + const timer = setTimeout( + () => finish(new Error(`net::ERR_TIMED_OUT ${TIMEOUT_MS} мс`)), + TIMEOUT_MS + ) + + request.on('response', () => finish(null)) + request.on('redirect', () => finish(null)) + request.on('error', finish) + request.end() + }) +} + +/** Тот же успех, но через локальный SOCKS5 tpws — путь для macOS, пока обход включён. */ +function checkViaSocks(host: string, socksHost: string, socksPort: number): Promise { + return new Promise((resolve, reject) => { + const start = Date.now() + const socket = netConnect({ host: socksHost, port: socksPort }) + let settled = false + + const finish = (error: Error | null): void => { + if (settled) return + settled = true + socket.destroy() + if (error) reject(error) + else resolve(Date.now() - start) + } + + socket.setTimeout(TIMEOUT_MS, () => finish(new Error(`Таймаут ${TIMEOUT_MS} мс`))) + socket.once('error', finish) + socket.once('connect', () => { + socksConnect(socket, host, TARGET_PORT) + .then(() => { + const tlsSocket = tlsConnect({ socket, servername: host }) + tlsSocket.once('secureConnect', () => finish(null)) + tlsSocket.once('error', finish) + }) + .catch(finish) + }) + }) +} + +function delay(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +/** + * Одна повторная попытка. Десинк срабатывает не всегда с первого соединения, и браузер это + * скрывает: он переустанавливает соединение сам, а пользователь видит только итог — сайт + * открылся. Проверка в одну попытку получалась строже браузера и писала «Недоступно» там, + * где сайт на самом деле открывается. Ошибку второй попытки отдаём наружу как есть — если + * не сработало дважды, показать причину важнее, чем спрятать её за повтором. + */ +async function withRetry(attempt: () => Promise): Promise { + try { + return await attempt() + } catch { + await delay(RETRY_DELAY_MS) + return attempt() + } +} + +/** + * Успех означает «до сайта дошли и он ответил» — этого достаточно, чтобы отличить + * «DPI оборвал соединение» от «соединение установилось», разбирать ответ незачем. + * + * `status.socksAddress` заполнен только на macOS и только пока обход запущен — во всех + * остальных случаях (Windows, обход выключен, платформа не поддерживается) бьём напрямую: + * на Windows десинк уже общесистемный через WinDivert, а без обхода тест просто показывает + * то, что есть сейчас. + */ +export async function runDiagnostics(status: ZapretStatus): Promise { + const socksMatch = status.socksAddress ? /^(.+):(\d+)$/.exec(status.socksAddress) : null + + return Promise.all( + TARGETS.map(async (target) => { + const attempt = (): Promise => + socksMatch + ? checkViaSocks(target.host, socksMatch[1], Number(socksMatch[2])) + : checkDirect(target.host) + + try { + const ms = await withRetry(attempt) + return { ...target, ok: true, ms, error: null } + } catch (error) { + return { + ...target, + ok: false, + ms: null, + error: error instanceof Error ? error.message : String(error) + } + } + }) + ) +} diff --git a/src/main/zapret/elevated-task.win32.ts b/src/main/zapret/elevated-task.win32.ts new file mode 100644 index 0000000..346aa45 --- /dev/null +++ b/src/main/zapret/elevated-task.win32.ts @@ -0,0 +1,90 @@ +import { execFile } from 'node:child_process' +import { join } from 'node:path' +import { promisify } from 'node:util' + +const run = promisify(execFile) + +/** + * Аргумент, по которому приложение понимает, что его запустили не как приложение, + * а как «повышенного помощника»: отработать задачу службы и выйти, без окна. + */ +export const ELEVATED_TASK_FLAG = '--elevated-service-task' + +/** + * Имена файлов внутри временного каталога задачи. Каталог готовит неповышенная сторона + * (`runElevated` в service.win32.ts), читает — повышенная, поэтому имена держим в одном + * месте, а не дублируем строками с обеих сторон. + */ +export const TASK_FILES = { + worker: 'worker.ps1', + payload: 'payload.json', + result: 'result.json' +} as const + +let elevationCheck: Promise | null = null + +/** + * Запущены ли мы уже от администратора. В собранном приложении это всегда так + * (`requestedExecutionLevel: requireAdministrator` в electron-builder.yml), но проверяем + * фактически, а не по предположению: в dev-режиме манифеста нет, а от неверного ответа + * зависит, ждать пользователю запрос UAC или нет. Ответ не меняется в течение работы + * процесса, поэтому считаем его один раз. + */ +export function isElevated(): Promise { + elevationCheck ??= run('powershell.exe', [ + '-NoProfile', + '-Command', + '[Security.Principal.WindowsPrincipal]::new(' + + '[Security.Principal.WindowsIdentity]::GetCurrent()' + + ').IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)' + ]) + .then(({ stdout }) => stdout.trim().toLowerCase() === 'true') + // Не смогли выяснить — считаем, что прав нет: лишний запрос UAC не страшен, + // а вот молча провалившаяся настройка службы — страшна. + .catch(() => false) + + return elevationCheck +} + +/** Каталог задачи из argv, если приложение запустили как помощника. Иначе null. */ +export function elevatedTaskDir(argv: readonly string[]): string | null { + const index = argv.indexOf(ELEVATED_TASK_FLAG) + if (index === -1) return null + return argv[index + 1] ?? null +} + +/** + * Выполняется уже с правами администратора: UAC запросили на нас самих, поэтому + * дочернему powershell повышаться не нужно — он наследует права. + * + * Смысл всей этой конструкции в том, ЧТО пользователь видит в окне UAC. Раньше приложение + * повышало powershell.exe, и Windows честно показывала «Windows PowerShell» с подписью + * Microsoft — запрос выглядел так, будто права просит не то, что запросил пользователь. + * Теперь повышается сам RKNboost.exe, и в запросе стоит имя приложения. + * + * Сам worker.ps1 при этом не изменился — вся логика sc/icacls осталась ровно та же, + * поменялось только то, кого повышают. + */ +export async function runElevatedServiceTask(taskDir: string): Promise { + try { + await run( + 'powershell.exe', + [ + '-NoProfile', + '-ExecutionPolicy', + 'Bypass', + '-File', + join(taskDir, TASK_FILES.worker), + '-PayloadPath', + join(taskDir, TASK_FILES.payload), + '-ResultPath', + join(taskDir, TASK_FILES.result) + ], + { windowsHide: true } + ) + return 0 + } catch (error) { + const code = (error as { code?: unknown } | undefined)?.code + return typeof code === 'number' ? code : 1 + } +} diff --git a/src/main/zapret/engine.darwin.ts b/src/main/zapret/engine.darwin.ts new file mode 100644 index 0000000..57053e9 --- /dev/null +++ b/src/main/zapret/engine.darwin.ts @@ -0,0 +1,243 @@ +import { spawn, type ChildProcessByStdio } from 'node:child_process' +import type { Readable } from 'node:stream' +import { createConnection, createServer } from 'node:net' +import type { ZapretStatus } from '../../shared/ipc-contract' +import { ZapretEngine } from './engine' +import { assertExecutable, tpwsPath } from './paths' +import { applySystemProxy, hasStaleSnapshot, restoreSystemProxy } from './proxy.darwin' +import { defaultStrategyId, tpwsArgs } from './strategies' + +const HOST = '127.0.0.1' +const PREFERRED_PORT = 1080 +const READY_TIMEOUT_MS = 3000 +const STOP_TIMEOUT_MS = 3000 + +/** Свободен ли порт на localhost. */ +function isPortFree(port: number): Promise { + return new Promise((resolve) => { + const server = createServer() + server.once('error', () => resolve(false)) + server.listen(port, HOST, () => server.close(() => resolve(true))) + }) +} + +async function pickPort(): Promise { + for (let port = PREFERRED_PORT; port < PREFERRED_PORT + 20; port += 1) { + if (await isPortFree(port)) return port + } + throw new Error('Не нашлось свободного порта для socks-прокси.') +} + +/** У tpws нет сигнала готовности — просто ждём, пока порт начнёт принимать соединения. */ +function canConnect(port: number): Promise { + return new Promise((resolve) => { + const socket = createConnection({ host: HOST, port }) + const finish = (ok: boolean): void => { + socket.destroy() + resolve(ok) + } + socket.once('connect', () => finish(true)) + socket.once('error', () => finish(false)) + }) +} + +async function waitUntilReady(port: number, isAlive: () => boolean): Promise { + const deadline = Date.now() + READY_TIMEOUT_MS + + while (Date.now() < deadline) { + if (!isAlive()) return // процесс уже умер, ошибку соберёт вызывающий код + if (await canConnect(port)) return + await new Promise((resolve) => setTimeout(resolve, 100)) + } + + throw new Error(`tpws не начал слушать ${HOST}:${port} за ${READY_TIMEOUT_MS} мс.`) +} + +/** + * macOS: tpws поднимается обычным пользовательским процессом в режиме socks5 + * (документация zapret: «Режим --socks не требует повышенных привилегий»), + * а трафик заворачивается системной настройкой прокси. + * + * Важное ограничение платформы: tpws не умеет UDP, поэтому QUIC не обходится. + * Практика это скрадывает — при системном socks-прокси браузеры сами уходят с QUIC на TCP. + */ +export class DarwinEngine extends ZapretEngine { + private child: ChildProcessByStdio | null = null + private port = PREFERRED_PORT + /** Просили ли включить системный прокси. Переживает перезапуск tpws. */ + private wantSystemProxy = true + + constructor() { + super('tpws-socks', defaultStrategyId('darwin')) + } + + /** + * Прошлый запуск мог не снять прокси (например, kill -9). Тогда система смотрит + * в мёртвый socks и интернета нет вовсе — чиним до того, как показать окно. + */ + async recoverFromCrash(): Promise { + if (!(await hasStaleSnapshot())) return + + this.appendLog( + 'info', + 'Найдены незакрытые настройки прокси от прошлого запуска — восстанавливаю.' + ) + try { + await restoreSystemProxy() + } catch (error) { + this.appendLog('error', error instanceof Error ? error.message : String(error)) + } + } + + async start(strategyId: string): Promise { + if (this.child) await this.stop() + + const binary = tpwsPath() + await assertExecutable(binary) + + this.patch({ state: 'starting', strategyId, error: null }) + + try { + this.port = await pickPort() + const child = spawn(binary, tpwsArgs(strategyId, this.port), { + stdio: ['ignore', 'pipe', 'pipe'] + }) + this.child = child + + // Вывод tpws идёт в stderr целиком, включая обычные сообщения о старте. + child.stdout.setEncoding('utf8') + child.stderr.setEncoding('utf8') + child.stdout.on('data', (chunk: string) => this.appendLog('info', chunk)) + + let stderrTail = '' + child.stderr.on('data', (chunk: string) => { + stderrTail = `${stderrTail}${chunk}`.slice(-2000) + this.appendLog('info', chunk) + }) + + child.on('exit', (code, signal) => { + this.child = null + // Плановую остановку обрабатывает stop(), здесь ловим только падение. + if (this.status.state === 'stopping' || this.status.state === 'stopped') return + void this.handleCrash(code, signal, stderrTail) + }) + + await waitUntilReady(this.port, () => this.child !== null) + + if (!this.child) { + throw new Error(stderrTail.trim() || 'tpws завершился сразу после запуска.') + } + + if (this.wantSystemProxy) { + await applySystemProxy(HOST, this.port) + } + + return this.patch({ + state: 'running', + startedAt: new Date().toISOString(), + error: null, + socksAddress: `${HOST}:${this.port}`, + systemProxyApplied: this.wantSystemProxy + }) + } catch (error) { + await this.cleanup() + const message = error instanceof Error ? error.message : String(error) + this.appendLog('error', message) + return this.patch({ + state: 'error', + error: message, + startedAt: null, + socksAddress: null, + systemProxyApplied: false + }) + } + } + + async stop(): Promise { + if (!this.child && !this.status.systemProxyApplied) { + return this.patch({ state: 'stopped', startedAt: null, socksAddress: null }) + } + + this.patch({ state: 'stopping' }) + await this.cleanup() + + return this.patch({ + state: 'stopped', + startedAt: null, + error: null, + socksAddress: null, + systemProxyApplied: false + }) + } + + async setSystemProxy(enabled: boolean): Promise { + this.wantSystemProxy = enabled + + // Пока обход выключен, менять нечего — запомнили выбор и всё. + if (this.status.state !== 'running') return this.patch({ systemProxyApplied: false }) + + if (enabled) { + await applySystemProxy(HOST, this.port) + } else { + await restoreSystemProxy() + } + return this.patch({ systemProxyApplied: enabled }) + } + + async dispose(): Promise { + await this.cleanup() + } + + /** + * Порядок важен: сначала снимаем прокси, потом гасим tpws. Наоборот нельзя — + * между шагами система осталась бы с прокси, который уже никто не слушает. + */ + private async cleanup(): Promise { + try { + await restoreSystemProxy() + } catch (error) { + this.appendLog('error', error instanceof Error ? error.message : String(error)) + } + + const child = this.child + if (!child) return + this.child = null + + child.kill('SIGTERM') + await new Promise((resolve) => { + const timer = setTimeout(() => { + child.kill('SIGKILL') + resolve() + }, STOP_TIMEOUT_MS) + + child.once('exit', () => { + clearTimeout(timer) + resolve() + }) + }) + } + + private async handleCrash( + code: number | null, + signal: NodeJS.Signals | null, + stderrTail: string + ): Promise { + const reason = signal ? `сигнал ${signal}` : `код ${code}` + const details = stderrTail.trim().split('\n').slice(-3).join(' ') + + // Прокси обязательно снять: иначе система указывает на мёртвый socks и сети не будет. + try { + await restoreSystemProxy() + } catch (error) { + this.appendLog('error', error instanceof Error ? error.message : String(error)) + } + + this.patch({ + state: 'error', + error: `tpws неожиданно завершился (${reason}). ${details}`.trim(), + startedAt: null, + socksAddress: null, + systemProxyApplied: false + }) + } +} diff --git a/src/main/zapret/engine.ts b/src/main/zapret/engine.ts new file mode 100644 index 0000000..c608bdc --- /dev/null +++ b/src/main/zapret/engine.ts @@ -0,0 +1,114 @@ +import { BrowserWindow } from 'electron' +import type { + ZapretBackend, + ZapretLogLine, + ZapretStatus, + ZapretState +} from '../../shared/ipc-contract' +/** Сколько строк вывода бинарника держим для UI. */ +const LOG_LIMIT = 200 + +/** Общая часть обоих движков: хранение статуса и рассылка событий в окна. */ +export abstract class ZapretEngine { + protected status: ZapretStatus + private readonly log: ZapretLogLine[] = [] + + constructor(backend: ZapretBackend, defaultStrategyId: string) { + this.status = { + state: 'stopped', + backend, + strategyId: defaultStrategyId, + startedAt: null, + error: null, + socksAddress: null, + systemProxyApplied: false, + serviceInstalled: false, + autoStart: false + } + } + + getStatus(): ZapretStatus { + return { ...this.status } + } + + getLog(): ZapretLogLine[] { + return [...this.log] + } + + abstract start(strategyId: string): Promise + abstract stop(): Promise + /** Вызывается при выходе из приложения: вернуть систему в исходное состояние. */ + abstract dispose(): Promise + + /** macOS-специфика; на других платформах переопределять не нужно. */ + async setSystemProxy(enabled: boolean): Promise { + void enabled + throw new Error('Системный прокси на этой платформе не настраивается.') + } + + /** + * Выключен — просто запоминаем выбор (никаких системных действий и никакого UAC). + * Работает — перезапускаем с новой стратегией через `start()`; на Windows тот сам + * остановит службу перед перенастройкой (см. `Win32Engine.start()`), потому что менять + * конфигурацию работающей службы нельзя. + */ + async setStrategy(strategyId: string): Promise { + if (this.status.state !== 'running') { + return this.patch({ strategyId }) + } + return this.start(strategyId) + } + + /** Windows-специфика (автозапуск службы); на других платформах переопределять не нужно. */ + async setAutoStart(enabled: boolean): Promise { + void enabled + throw new Error('Автозапуск на этой платформе не настраивается.') + } + + protected patch(changes: Partial): ZapretStatus { + this.status = { ...this.status, ...changes } + this.broadcast('zapret:status-changed', this.status) + return this.getStatus() + } + + protected setState(state: ZapretState, error: string | null = null): ZapretStatus { + return this.patch({ state, error }) + } + + /** Вывод бинарника: копим кольцевым буфером и сразу отдаём в UI. */ + protected appendLog(level: ZapretLogLine['level'], chunk: string): void { + for (const raw of chunk.split('\n')) { + const line = raw.trimEnd() + if (!line) continue + + const entry: ZapretLogLine = { level, line } + this.log.push(entry) + if (this.log.length > LOG_LIMIT) this.log.shift() + this.broadcast('zapret:log', entry) + } + } + + private broadcast(channel: string, payload: T): void { + for (const window of BrowserWindow.getAllWindows()) { + if (!window.isDestroyed()) window.webContents.send(channel, payload) + } + } +} + +/** Заглушка для платформ, под которые мы бинарники не собираем (Linux). */ +export class UnsupportedEngine extends ZapretEngine { + constructor() { + super('unsupported', '') + this.status.error = 'Обход блокировок доступен только на macOS и Windows.' + } + + async start(): Promise { + throw new Error('Обход блокировок доступен только на macOS и Windows.') + } + + async stop(): Promise { + return this.getStatus() + } + + async dispose(): Promise {} +} diff --git a/src/main/zapret/engine.win32.ts b/src/main/zapret/engine.win32.ts new file mode 100644 index 0000000..5545519 --- /dev/null +++ b/src/main/zapret/engine.win32.ts @@ -0,0 +1,197 @@ +import { execFile } from 'node:child_process' +import { mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { promisify } from 'node:util' +import type { ZapretStatus } from '../../shared/ipc-contract' +import { ZapretEngine } from './engine' +import { assertExecutable, protectedWinwsPath, serviceConfigPath, winwsPath } from './paths' +import { + applyServiceConfig, + queryService, + setServiceAutoStart, + startService, + stopService, + type ServiceInfo +} from './service.win32' +import { defaultStrategyId, winwsConfigFile, type WinwsConfig } from './strategies' + +const run = promisify(execFile) + +/** Первая строка `strategy.cfg` — из `winwsConfigFile()`, формат: `--comment=rknboost::`. */ +async function readInstalledMarkerLine(): Promise { + try { + const content = await readFile(serviceConfigPath(), 'utf8') + return content.split('\n')[0]?.trim() ?? null + } catch { + return null + } +} + +/** + * Служба всегда запускает защищённую копию winws.exe (protectedWinwsPath), а не оригинал + * из папки установки приложения (winwsPath) — значит переустановку/обновление приложения + * по самому пути службы не отследить, как раньше. Вместо этого сравниваем размер и время + * изменения оригинала с уже скопированным файлом: разошлись — обновление zapret вышло + * позже последней копии, нужно копировать заново (а значит и UAC). + */ +async function binariesNeedCopy(): Promise { + try { + const [source, copy] = await Promise.all([stat(winwsPath()), stat(protectedWinwsPath())]) + return source.size !== copy.size || source.mtimeMs > copy.mtimeMs + } catch { + return true + } +} + +/** + * Перенастройка (а значит и UAC) нужна, если службы ещё нет, если защищённая копия + * winws.exe отстала от оригинала в папке приложения, или если аргументы стратегии + * изменились (маркер в `strategy.cfg` не совпадает с тем, что сгенерировали сейчас). + */ +async function needsReconfigure(config: WinwsConfig, info: ServiceInfo | null): Promise { + if (!info) return true + if (await binariesNeedCopy()) return true + return (await readInstalledMarkerLine()) !== config.marker +} + +/** + * Windows: winws требует драйвер WinDivert и права администратора, поэтому работает как служба + * Windows (`rknboost-zapret`), а не как обычный дочерний процесс приложения. + * + * UAC запрашивается только при первой установке службы и при смене аргументов (новая стратегия, + * переключение автозапуска) — этим занимается `service.win32.ts`. Обычные включение/выключение + * идут через `sc start`/`sc stop`, на которые интерактивным пользователям выдаётся право при + * установке (`sc sdset`), поэтому UAC для них не нужен. + */ +export class Win32Engine extends ZapretEngine { + constructor() { + super('winws-service', defaultStrategyId('win32')) + // Решение по умолчанию для первой установки: обход должен работать и после перезагрузки, + // не дожидаясь запуска приложения. Дальше пользователь может выключить переключателем. + this.status.autoStart = true + } + + /** + * Вызывается из `recoverZapret()` до открытия окна: подхватывает состояние службы, + * которая могла работать ещё при загрузке системы (автозапуск), — иначе UI показал бы + * «Выключен» при уже работающем обходе. + */ + async sync(): Promise { + const info = await queryService().catch(() => null) + if (!info) { + return this.patch({ serviceInstalled: false, state: 'stopped', startedAt: null }) + } + + const installedLine = await readInstalledMarkerLine() + const match = installedLine ? /^--comment=rknboost:([^:]+):/.exec(installedLine) : null + const strategyId = match?.[1] ?? this.status.strategyId + + return this.patch({ + serviceInstalled: true, + autoStart: info.startMode === 'Auto', + strategyId, + state: info.state === 'Running' ? 'running' : 'stopped', + // Точный момент запуска нам недоступен — служба могла подняться ещё при загрузке + // системы, до старта приложения. Берём момент обнаружения, лишь бы поле не пустовало. + startedAt: info.state === 'Running' ? new Date().toISOString() : null, + error: null + }) + } + + async start(strategyId: string): Promise { + this.patch({ state: 'starting', strategyId, error: null }) + + let config: WinwsConfig + try { + config = winwsConfigFile(strategyId) + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + return this.patch({ state: 'error', error: message, startedAt: null }) + } + + try { + await assertExecutable(winwsPath()) + + const info = await queryService().catch(() => null) + if (await needsReconfigure(config, info)) { + // Перенастраивать работающую службу нельзя — её сначала надо остановить, иначе + // ломается всё сразу: работающий winws держит открытым свой же winws.exe, и + // Copy-Item в защищённый каталог падает на занятом файле; --dry-run упирается в + // тот же именованный mutex WinDivert («A copy of winws is already running with + // the same filter»); а sc config применяется только к СЛЕДУЮЩЕМУ запуску, так что + // служба молча продолжила бы работать со старой стратегией. + if (info?.state === 'Running') await stopService() + + // --dry-run трогает mutex WinDivert, а не только парсит аргументы, поэтому зовём его + // здесь — после остановки и только когда действительно собираемся писать новый конфиг, + // а не когда просто подтверждаем уже работающую стратегию. + await this.dryRun(config.body) + await applyServiceConfig({ configBody: config.body, autoStart: this.status.autoStart }) + } + + const running = await startService() + + return this.patch({ + state: 'running', + startedAt: new Date().toISOString(), + error: null, + serviceInstalled: true, + autoStart: running?.startMode === 'Auto' + }) + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + this.appendLog('error', message) + return this.patch({ state: 'error', error: message, startedAt: null }) + } + } + + async stop(): Promise { + this.patch({ state: 'stopping' }) + try { + await stopService() + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + this.appendLog('error', message) + return this.patch({ state: 'error', error: message }) + } + return this.patch({ state: 'stopped', startedAt: null, error: null }) + } + + /** + * Только тип автозапуска службы — если она ещё не установлена, лишь запоминаем выбор + * до первого включения (никакого UAC на пустом месте). + */ + async setAutoStart(enabled: boolean): Promise { + if (!this.status.serviceInstalled) { + return this.patch({ autoStart: enabled }) + } + await setServiceAutoStart(enabled) + return this.patch({ autoStart: enabled }) + } + + async dispose(): Promise { + // Служба живёт своей жизнью в системе — выход приложения её не останавливает. + } + + /** + * `winws.exe --dry-run @<файл>` только проверяет аргументы и завершается без открытия + * WinDivert — не требует прав администратора. Ошибку в стратегии видно до запроса UAC, + * а не после него. + */ + private async dryRun(configBody: string): Promise { + const dir = await mkdtemp(join(tmpdir(), 'rknboost-dryrun-')) + const configPath = join(dir, 'dry-run.cfg') + try { + await writeFile(configPath, configBody, 'utf8') + await run(winwsPath(), ['--dry-run', `@${configPath}`]) + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + throw new Error(`Стратегия содержит недопустимые параметры winws: ${message}`, { + cause: error + }) + } finally { + await rm(dir, { recursive: true, force: true }) + } + } +} diff --git a/src/main/zapret/index.ts b/src/main/zapret/index.ts new file mode 100644 index 0000000..f3655e8 --- /dev/null +++ b/src/main/zapret/index.ts @@ -0,0 +1,110 @@ +import { readFile, rm } from 'node:fs/promises' +import type { + ZapretDiagnosticResult, + ZapretList, + ZapretListId, + ZapretLogLine, + ZapretStatus, + ZapretStrategy +} from '../../shared/ipc-contract' +import { runDiagnostics } from './diagnostics' +import { DarwinEngine } from './engine.darwin' +import { ZapretEngine, UnsupportedEngine } from './engine' +import { Win32Engine } from './engine.win32' +import { readAllLists, resetList, seedLists, writeList } from './lists' +import { autoHostlistPath } from './paths' +import { listStrategies } from './strategies' + +function createEngine(): ZapretEngine { + switch (process.platform) { + case 'darwin': + return new DarwinEngine() + case 'win32': + return new Win32Engine() + default: + return new UnsupportedEngine() + } +} + +const engine = createEngine() + +/** + * Готовим списки сайтов и синхронизируем состояние с системой — на macOS чиним прокси + * от прошлого аварийного завершения, на Windows подхватываем службу, которая могла работать + * ещё до открытия окна (автозапуск с системой). Оба шага нужно сделать до открытия окна. + */ +export async function recoverZapret(): Promise { + await seedLists() + if (engine instanceof DarwinEngine) await engine.recoverFromCrash() + if (engine instanceof Win32Engine) await engine.sync() +} + +export function zapretStatus(): ZapretStatus { + return engine.getStatus() +} + +export function zapretStrategies(): ZapretStrategy[] { + return listStrategies(process.platform) +} + +export function zapretLog(): ZapretLogLine[] { + return engine.getLog() +} + +export function startZapret(strategyId: string): Promise { + return engine.start(strategyId) +} + +export function stopZapret(): Promise { + return engine.stop() +} + +export function setZapretSystemProxy(enabled: boolean): Promise { + return engine.setSystemProxy(enabled) +} + +export function setZapretStrategy(strategyId: string): Promise { + return engine.setStrategy(strategyId) +} + +export function setZapretAutoStart(enabled: boolean): Promise { + return engine.setAutoStart(enabled) +} + +/** Вызывать при выходе из приложения — гасит процесс и откатывает системные настройки. */ +export function disposeZapret(): Promise { + return engine.dispose() +} + +export function zapretLists(): Promise { + return readAllLists() +} + +export function saveZapretList(id: ZapretListId, entries: string[]): Promise { + return writeList(id, entries) +} + +export function resetZapretList(id: ZapretListId): Promise { + return resetList(id) +} + +/** Домены, которые zapret нашёл сам через `--hostlist-auto` (только macOS-профили). */ +export async function zapretAutoHostlist(): Promise { + try { + const content = await readFile(autoHostlistPath(), 'utf8') + return content + .split('\n') + .map((line) => line.trim()) + .filter(Boolean) + } catch { + return [] + } +} + +export async function clearZapretAutoHostlist(): Promise { + await rm(autoHostlistPath(), { force: true }) +} + +export function zapretDiagnostics(): Promise { + return runDiagnostics(engine.getStatus()) +} diff --git a/src/main/zapret/lists.ts b/src/main/zapret/lists.ts new file mode 100644 index 0000000..9ac3e13 --- /dev/null +++ b/src/main/zapret/lists.ts @@ -0,0 +1,151 @@ +import { createHash, randomUUID } from 'node:crypto' +import { mkdir, readFile, rename, writeFile } from 'node:fs/promises' +import { join } from 'node:path' +import type { ZapretList, ZapretListId } from '../../shared/ipc-contract' +import { bundledListsDir, userListsDir } from './paths' + +/** + * Списки сайтов — данные, а не аргументы запуска. Пользователь правит их + * полностью свободно (это его решение), включая встроенные из Flowseal. + * Рабочая копия лежит в userData и переживает обновление приложения; встроенная + * версия в `resources/zapret/lists` только читается — от неё берём исходное + * содержимое при первом запуске и при «Сбросить к встроенному». + * + * Важно: и tpws, и winws сами перечитывают список, если у файла изменились + * mtime или размер («automatically reload lists if their modification time or + * file size is changed», docs/readme zapret) — перезапускать обход после правки + * списка не нужно. Чтобы это гарантированно сработало (а не поймать читателя + * на файле, который ещё не дописан), пишем во временный файл и делаем `rename` — + * это меняет mtime одним атомарным шагом. + */ + +const FILE_NAMES: Record = { + general: 'general.txt', + google: 'google.txt', + exclude: 'exclude.txt', + 'ipset-exclude': 'ipset-exclude.txt', + 'ipset-all': 'ipset-all.txt' +} + +export const LIST_IDS = Object.keys(FILE_NAMES) as ZapretListId[] + +/** Списки доменов (--hostlist=...) — в отличие от ipset-exclude/ipset-all, где строки это IP/CIDR. */ +const HOSTLIST_IDS: ZapretListId[] = ['general', 'google', 'exclude'] + +function sha256(text: string): string { + return createHash('sha256').update(text).digest('hex') +} + +function parseEntries(content: string): string[] { + return content + .split('\n') + .map((line) => line.trim()) + .filter((line) => line.length > 0 && !line.startsWith('#')) +} + +function serializeEntries(entries: string[]): string { + return entries.join('\n') + (entries.length > 0 ? '\n' : '') +} + +const DOMAIN_RE = /^\^?[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/ +const IP_CIDR_RE = /^([0-9a-f:]+|\d{1,3}(\.\d{1,3}){3})(\/\d{1,3})?$/i + +/** Понятная ошибка вместо того, чтобы молча положить в список мусор, который tpws пропустит мимо ушей. */ +function validateEntry(id: ZapretListId, raw: string): string { + const entry = raw.trim().toLowerCase() + if (!entry) throw new Error('Пустая строка в списке.') + + const isHostlist = HOSTLIST_IDS.includes(id) + const pattern = isHostlist ? DOMAIN_RE : IP_CIDR_RE + if (!pattern.test(entry)) { + throw new Error( + isHostlist + ? `«${raw}» не похоже на домен (пример: example.com или ^example.com для точного совпадения).` + : `«${raw}» не похоже на IP-адрес или подсеть (пример: 192.168.0.0/16).` + ) + } + return entry +} + +function bundledPath(id: ZapretListId): string { + return join(bundledListsDir(), FILE_NAMES[id]) +} + +function userPath(id: ZapretListId): string { + return join(userListsDir(), FILE_NAMES[id]) +} + +/** Атомарная запись: временный файл в том же каталоге (для atomic rename на одной ФС) + rename. */ +async function writeAtomic(path: string, content: string): Promise { + const tmp = `${path}.${randomUUID()}.tmp` + await writeFile(tmp, content, 'utf8') + await rename(tmp, path) +} + +async function readBundled(id: ZapretListId): Promise { + return readFile(bundledPath(id), 'utf8') +} + +async function fileExists(path: string): Promise { + try { + await readFile(path) + return true + } catch { + return false + } +} + +/** Копирует встроенные списки в userData при первом запуске. Идемпотентно — уже существующие не трогает. */ +export async function seedLists(): Promise { + await mkdir(userListsDir(), { recursive: true }) + for (const id of LIST_IDS) { + if (await fileExists(userPath(id))) continue + await writeAtomic(userPath(id), await readBundled(id)) + } +} + +async function toZapretList(id: ZapretListId): Promise { + const [userContent, bundledContent] = await Promise.all([ + readFile(userPath(id), 'utf8').catch(() => readBundled(id)), + readBundled(id) + ]) + + return { + id, + entries: parseEntries(userContent), + // Список не тронут пользователем относительно текущей встроенной версии. + isDefault: sha256(userContent) === sha256(bundledContent) + } +} + +export async function readList(id: ZapretListId): Promise { + return toZapretList(id) +} + +export async function readAllLists(): Promise { + return Promise.all(LIST_IDS.map(readList)) +} + +export async function writeList(id: ZapretListId, rawEntries: string[]): Promise { + const seen = new Set() + const entries: string[] = [] + for (const raw of rawEntries) { + const entry = validateEntry(id, raw) + if (seen.has(entry)) continue + seen.add(entry) + entries.push(entry) + } + + await writeAtomic(userPath(id), serializeEntries(entries)) + return toZapretList(id) +} + +export async function resetList(id: ZapretListId): Promise { + await writeAtomic(userPath(id), await readBundled(id)) + return toZapretList(id) +} + +/** Путь к рабочему списку — нужен `strategies.ts`, чтобы подставить его в аргументы движка. */ +export function listFilePath(id: ZapretListId): string { + return userPath(id) +} diff --git a/src/main/zapret/paths.ts b/src/main/zapret/paths.ts new file mode 100644 index 0000000..ffd3960 --- /dev/null +++ b/src/main/zapret/paths.ts @@ -0,0 +1,94 @@ +import { app } from 'electron' +import { access, constants } from 'node:fs/promises' +import { join, resolve } from 'node:path' + +/** + * Бинарники zapret кладёт `scripts/fetch-zapret.mjs` в `resources/zapret`. + * В упакованном приложении electron-builder переносит их в `extraResources`, + * то есть в `process.resourcesPath/zapret` (вне asar — запускать код из архива нельзя). + */ +function zapretRoot(): string { + return app.isPackaged + ? join(process.resourcesPath, 'zapret') + : resolve(app.getAppPath(), 'resources', 'zapret') +} + +/** macOS: universal-бинарник tpws (arm64 + x86_64). */ +export function tpwsPath(): string { + return join(zapretRoot(), 'darwin', 'tpws') +} + +/** Windows: winws.exe рядом с cygwin1.dll и WinDivert. */ +export function winwsPath(): string { + return join(zapretRoot(), 'win32', 'x64', 'winws.exe') +} + +/** Встроенные списки (из Flowseal/zapret-discord-youtube, см. scripts/fetch-zapret.mjs) — только для чтения. */ +export function bundledListsDir(): string { + return join(zapretRoot(), 'lists') +} + +/** + * Рабочая копия списков — здесь их правит пользователь. Живёт вне сборки + * (userData), чтобы пережить обновление приложения: встроенные списки в + * `bundledListsDir()` при апдейте перезатираются, а эта копия — нет. + */ +export function userListsDir(): string { + return join(app.getPath('userData'), 'zapret-lists') +} + +/** Fake-пакеты (TLS ClientHello, QUIC initial, ...) для стратегий winws. */ +export function fakesDir(): string { + return join(zapretRoot(), 'fakes') +} + +/** + * Windows: служба `rknboost-zapret` работает от LocalSystem, поэтому её конфиг не может + * жить в userData обычного пользователя — нужен каталог машинного уровня. `%ProgramData%` + * почти всегда задан; `C:\ProgramData` — тот же путь, на который он указывает по умолчанию. + */ +export function serviceDataDir(): string { + return join(process.env.ProgramData ?? 'C:\\ProgramData', 'rknboost', 'zapret') +} + +/** Файл аргументов `winws.exe @strategy.cfg` — пишется только повышенным скриптом службы. */ +export function serviceConfigPath(): string { + return join(serviceDataDir(), 'strategy.cfg') +} + +/** + * Защищённая копия `winws.exe` (+ cygwin1.dll, WinDivert.dll, WinDivert64.sys) — на неё, + * а не на оригинал из папки установки приложения, указывает служба. Установка теперь + * per-user (`%LocalAppData%`), эта папка доступна на запись обычному пользователю — + * если бы SYSTEM-служба запускала winws.exe прямо оттуда, кто угодно, работающий под + * тем же пользователем, мог бы подменить бинарник и получить код с правами SYSTEM. + * Копию (как и `strategy.cfg`) кладёт и защищает ACL только повышенный скрипт + * (`applyServiceConfig` в service.win32.ts), рядом, в том же serviceDataDir(). + */ +export function protectedWinwsPath(): string { + return join(serviceDataDir(), 'winws.exe') +} + +/** + * Список, который zapret пополняет сам (`--hostlist-auto`): при похожей на блокировку + * ошибке домен дописывается сюда и на лету попадает под десинк, без участия пользователя. + * Не входит в `ZapretListId` — это нередактируемый журнал находок, а не список на правку. + */ +export function autoHostlistPath(): string { + return join(userListsDir(), 'auto.txt') +} + +/** + * Понятная ошибка вместо ENOENT из глубины spawn: без этой проверки пользователь + * увидит «spawn ... ENOENT» и не поймёт, что надо запустить `npm run zapret:fetch`. + */ +export async function assertExecutable(path: string): Promise { + try { + await access(path, constants.X_OK) + } catch { + throw new Error( + `Не найден исполняемый файл zapret: ${path}\n` + + 'Скачайте бинарники командой «npm run zapret:fetch».' + ) + } +} diff --git a/src/main/zapret/proxy.darwin.ts b/src/main/zapret/proxy.darwin.ts new file mode 100644 index 0000000..5a95d1a --- /dev/null +++ b/src/main/zapret/proxy.darwin.ts @@ -0,0 +1,167 @@ +import { app } from 'electron' +import { execFile } from 'node:child_process' +import { readFile, writeFile, rm } from 'node:fs/promises' +import { join } from 'node:path' +import { promisify } from 'node:util' + +const run = promisify(execFile) + +/** + * Домены и адреса, которые не должны идти через прокси. Без этого списка отвалится + * всё локальное: tpws по документации запрещает подключения к тому же хосту, + * на котором работает сам, включая localhost. + */ +const BYPASS = ['*.local', '169.254/16', 'localhost', '127.0.0.1', '::1'] + +interface ServiceProxyState { + service: string + enabled: boolean + server: string + port: string + bypass: string[] +} + +/** Что было до нас — чтобы вернуть настройки ровно в исходное состояние. */ +interface ProxySnapshot { + takenAt: string + services: ServiceProxyState[] +} + +function snapshotPath(): string { + return join(app.getPath('userData'), 'proxy-snapshot.json') +} + +/** + * `networksetup` требует членства в группе admin. На обычном Mac пользователь в ней + * состоит и пароль не спрашивается. Если же включена политика «требовать пароль + * администратора для системных настроек», команда падает — тогда повторяем её через + * системный диалог авторизации. + */ +async function networksetup(args: string[]): Promise { + try { + const { stdout } = await run('/usr/sbin/networksetup', args) + return stdout + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + if (!/root|denied|permission/i.test(message)) throw error + + const command = ['/usr/sbin/networksetup', ...args] + .map((part) => `'${part.replaceAll("'", `'\\''`)}'`) + .join(' ') + + try { + const { stdout } = await run('/usr/bin/osascript', [ + '-e', + `do shell script "${command.replaceAll('\\', '\\\\').replaceAll('"', '\\"')}" with administrator privileges` + ]) + return stdout + } catch (elevationError) { + const text = elevationError instanceof Error ? elevationError.message : String(elevationError) + if (text.includes('-128')) + throw new Error('Изменение системного прокси отменено.', { cause: elevationError }) + throw new Error(`Не удалось изменить настройки прокси: ${text}`, { cause: elevationError }) + } + } +} + +/** Сетевые сервисы, кроме выключенных: у тех `networksetup` ставит «*» перед именем. */ +async function activeServices(): Promise { + const stdout = await networksetup(['-listallnetworkservices']) + return stdout + .split('\n') + .slice(1) // первая строка — пояснение про звёздочку + .map((line) => line.trim()) + .filter((line) => line.length > 0 && !line.startsWith('*')) +} + +async function readServiceState(service: string): Promise { + const stdout = await networksetup(['-getsocksfirewallproxy', service]) + const value = (key: string): string => + new RegExp(`^${key}:\\s*(.*)$`, 'm').exec(stdout)?.[1]?.trim() ?? '' + + const bypassOutput = await networksetup(['-getproxybypassdomains', service]) + // Пустой список macOS печатает фразой «There aren't any bypass domains…», + // а в доменах пробелов не бывает — по этому и отличаем. + const bypass = bypassOutput + .split('\n') + .map((line) => line.trim()) + .filter((line) => line.length > 0 && !line.includes(' ')) + + return { + service, + enabled: value('Enabled').toLowerCase() === 'yes', + server: value('Server'), + port: value('Port'), + bypass + } +} + +async function saveSnapshot(snapshot: ProxySnapshot): Promise { + await writeFile(snapshotPath(), JSON.stringify(snapshot, null, 2), 'utf8') +} + +async function loadSnapshot(): Promise { + try { + return JSON.parse(await readFile(snapshotPath(), 'utf8')) as ProxySnapshot + } catch { + return null + } +} + +async function restoreService(state: ServiceProxyState): Promise { + if (state.enabled && state.server) { + await networksetup([ + '-setsocksfirewallproxy', + state.service, + state.server, + state.port || '1080' + ]) + await networksetup(['-setsocksfirewallproxystate', state.service, 'on']) + } else { + await networksetup(['-setsocksfirewallproxystate', state.service, 'off']) + } + + // Пустой список сбрасывается специальным словом Empty — иначе останется наш. + const bypass = state.bypass.length > 0 ? state.bypass : ['Empty'] + await networksetup(['-setproxybypassdomains', state.service, ...bypass]) +} + +/** Прописать socks-прокси во все активные сетевые сервисы, запомнив прежнее состояние. */ +export async function applySystemProxy(host: string, port: number): Promise { + const services = await activeServices() + if (services.length === 0) throw new Error('Не найдено ни одного активного сетевого сервиса.') + + // Снимок делаем до первой правки и сразу пишем на диск: если приложение + // упадёт посреди применения, следующий запуск сможет всё вернуть. + const snapshot: ProxySnapshot = { + takenAt: new Date().toISOString(), + services: await Promise.all(services.map(readServiceState)) + } + await saveSnapshot(snapshot) + + for (const service of services) { + await networksetup(['-setsocksfirewallproxy', service, host, String(port)]) + await networksetup(['-setproxybypassdomains', service, ...BYPASS]) + await networksetup(['-setsocksfirewallproxystate', service, 'on']) + } +} + +/** Вернуть настройки прокси в состояние из снимка. Без снимка ничего не трогаем. */ +export async function restoreSystemProxy(): Promise { + const snapshot = await loadSnapshot() + if (!snapshot) return false + + for (const state of snapshot.services) { + await restoreService(state) + } + await rm(snapshotPath(), { force: true }) + return true +} + +/** + * Снимок остался с прошлого запуска — значит приложение завершилось аварийно, + * не сняв прокси, и система сейчас смотрит в несуществующий socks. Чиним на старте. + */ +export async function hasStaleSnapshot(): Promise { + return (await loadSnapshot()) !== null +} diff --git a/src/main/zapret/service.win32.ts b/src/main/zapret/service.win32.ts new file mode 100644 index 0000000..7792eed --- /dev/null +++ b/src/main/zapret/service.win32.ts @@ -0,0 +1,471 @@ +import { execFile } from 'node:child_process' +import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { promisify } from 'node:util' +import { app } from 'electron' +import { + ELEVATED_TASK_FLAG, + TASK_FILES, + isElevated, + runElevatedServiceTask +} from './elevated-task.win32' +import { serviceConfigPath, serviceDataDir, winwsPath } from './paths' + +const run = promisify(execFile) + +export const SERVICE_NAME = 'rknboost-zapret' + +const POLL_INTERVAL_MS = 300 +const POLL_TIMEOUT_MS = 10000 + +/** Код возврата Windows для «пользователь отклонил запрос UAC» (ERROR_CANCELLED). */ +const UAC_CANCELLED_CODE = 1223 + +/** + * `sc.exe` выходит с самим кодом ошибки Win32, и это единственный надёжный способ их + * различать: текст сообщения локализован, на русской Windows никакой `/not started/` + * не сматчится. + */ +const ERROR_SERVICE_ALREADY_RUNNING = 1056 +const ERROR_SERVICE_NOT_ACTIVE = 1062 + +export type ServiceState = 'Running' | 'Stopped' | 'StartPending' | 'StopPending' | 'Other' +export type ServiceStartMode = 'Auto' | 'Manual' | 'Disabled' | 'Other' + +export interface ServiceInfo { + state: ServiceState + startMode: ServiceStartMode + /** Значение `PathName` из CIM — то, что реально прописано в `binPath` службы. */ + binPath: string +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +function exitCodeOf(error: unknown): number { + const code = (error as { code?: number | string } | undefined)?.code + return typeof code === 'number' ? code : 1 +} + +function normalizeState(raw: string | undefined): ServiceState { + switch (raw) { + case 'Running': + return 'Running' + case 'Stopped': + return 'Stopped' + case 'Start Pending': + return 'StartPending' + case 'Stop Pending': + return 'StopPending' + default: + return 'Other' + } +} + +function normalizeStartMode(raw: string | undefined): ServiceStartMode { + switch (raw) { + case 'Auto': + return 'Auto' + case 'Manual': + return 'Manual' + case 'Disabled': + return 'Disabled' + default: + return 'Other' + } +} + +/** + * Опрос через `Get-CimInstance Win32_Service`, а не парсинг вывода `sc query` — у CIM + * `State`/`StartMode` это английские константы независимо от языка Windows, а `sc` печатает + * локализованный текст, который парсить надёжно нельзя. + */ +export async function queryService(): Promise { + const script = + `Get-CimInstance Win32_Service -Filter "Name='${SERVICE_NAME}'" | ` + + 'Select-Object State,StartMode,PathName | ConvertTo-Json -Compress' + + let stdout: string + try { + ;({ stdout } = await run('powershell.exe', ['-NoProfile', '-Command', script])) + } catch (error) { + throw new Error( + `Не удалось опросить службу: ${error instanceof Error ? error.message : String(error)}`, + { cause: error } + ) + } + + const trimmed = stdout.trim() + if (!trimmed || trimmed === 'null') return null + + const raw = JSON.parse(trimmed) as { State?: string; StartMode?: string; PathName?: string } + return { + state: normalizeState(raw.State), + startMode: normalizeStartMode(raw.StartMode), + binPath: raw.PathName ?? '' + } +} + +function explainScError(error: unknown, verb: string): string { + const message = error instanceof Error ? error.message : String(error) + if (/access is denied/i.test(message)) { + return ( + `Не удалось ${verb} службу: отказано в доступе. Похоже, права для обычных пользователей ` + + 'не выданы — переустановите приложение.' + ) + } + return `Не удалось ${verb} службу: ${message}` +} + +async function pollUntil( + predicate: (info: ServiceInfo | null) => boolean +): Promise { + const deadline = Date.now() + POLL_TIMEOUT_MS + for (;;) { + const info = await queryService() + if (predicate(info)) return info + if (Date.now() >= deadline) return info + await sleep(POLL_INTERVAL_MS) + } +} + +/** + * `sc start` только просит SCM перевести службу в running и возвращается сразу — если не + * дождаться реального состояния, свежепадающая служба (например, WinDivert не загрузился) + * выглядела бы как успех. + */ +export async function startService(): Promise { + try { + await run('sc.exe', ['start', SERVICE_NAME]) + } catch (error) { + // «Служба уже запущена» — цель достигнута, не ошибка: так бывает, когда пользователь + // выбрал в списке ту же стратегию, что уже работает, и перенастройка не понадобилась. + if (exitCodeOf(error) !== ERROR_SERVICE_ALREADY_RUNNING) { + throw new Error(explainScError(error, 'запустить'), { cause: error }) + } + } + + const info = await pollUntil((i) => i?.state === 'Running' || i?.state === 'Stopped') + if (info?.state !== 'Running') { + throw new Error( + `Служба запущена системой, но не перешла в рабочее состояние (текущее: ` + + `${info?.state ?? 'неизвестно'}). Возможная причина — не загрузился драйвер WinDivert ` + + '(включён Secure Boot, драйвер без действующей подписи, или Windows 7 без нужного обновления).' + ) + } + return info +} + +export async function stopService(): Promise { + try { + await run('sc.exe', ['stop', SERVICE_NAME]) + } catch (error) { + // «Служба не запущена» — не ошибка с точки зрения пользователя, тот же паттерн, что в + // engine.darwin.ts. Раньше это ловилось регуляркой по тексту `/not.*started/` — на русской + // Windows она не срабатывала никогда, и остановка уже остановленной службы падала ошибкой. + if (exitCodeOf(error) !== ERROR_SERVICE_NOT_ACTIVE) { + throw new Error(explainScError(error, 'остановить'), { cause: error }) + } + } + // `pollUntil` возвращает последнее состояние и по истечении таймаута, независимо от предиката, + // поэтому результат обязательно проверяем. Иначе «остановка», не уложившаяся в POLL_TIMEOUT_MS, + // молча сходила бы за успешную — и вызывающий код (перенастройка стратегии в engine.win32.ts) + // пошёл бы копировать winws.exe поверх ещё работающего, ровно в ту ошибку, от которой + // остановка и защищает. + const info = await pollUntil((i) => i === null || i.state === 'Stopped') + if (info && info.state !== 'Stopped') { + throw new Error( + `Служба не остановилась за ${POLL_TIMEOUT_MS / 1000} с (состояние: ${info.state}).` + ) + } + return info +} + +/** + * PowerShell 5.1 (то, что стоит на Windows по умолчанию) требует BOM, чтобы правильно + * прочитать кириллицу в UTF-8 файле скрипта — без BOM она превращается в кракозябры. + */ +const UTF8_BOM = '\uFEFF' + +function withBom(script: string): string { + return UTF8_BOM + script +} + +/** + * Выполняется без прав администратора: запрашивает повышение через `Start-Process -Verb RunAs` + * и ждёт завершения. Если пользователь отклонил UAC, `Start-Process` бросает исключение — + * ловим его и выходим с ERROR_CANCELLED, чтобы вызывающий код отличил отказ от реальной ошибки. + * + * Повышаем САМО приложение (`$AppPath` с флагом `--elevated-service-task`), а не powershell.exe: + * Windows пишет в окне UAC имя того файла, который повышают, и запрос от «Windows PowerShell» + * с подписью Microsoft выглядит так, будто права просит что-то постороннее. Повышенный + * экземпляр приложения уже сам, без второго UAC, запускает worker.ps1 из того же каталога + * (см. runElevatedServiceTask в elevated-task.win32.ts). + * + * `$AppDir` нужен только в dev-режиме: там `$AppPath` — это electron.exe, которому каталог + * приложения передаётся первым аргументом. В собранном приложении он пустой. + */ +const OUTER_SCRIPT = ` +param( + [Parameter(Mandatory=$true)][string]$AppPath, + [Parameter(Mandatory=$true)][string]$TaskDir, + [string]$AppDir = '' +) + +try { + $argList = @() + if ($AppDir) { $argList += $AppDir } + $argList += @('${ELEVATED_TASK_FLAG}', $TaskDir) + + $p = Start-Process -FilePath $AppPath -Verb RunAs -Wait -PassThru -ArgumentList $argList + exit $p.ExitCode +} +catch { + exit ${UAC_CANCELLED_CODE} +} +` + +/** + * Работает от администратора. Три действия: установить/перенастроить службу (`install`), + * поменять только тип автозапуска (`setAutoStart`), снять службу целиком (`remove`). + * Результат пишет в `$ResultPath` — код возврата процесса тут ненадёжен (PowerShell не всегда + * пробрасывает его наружу через несколько уровней Start-Process), а JSON-файл однозначен. + */ +const WORKER_SCRIPT = ` +param( + [Parameter(Mandatory=$true)][string]$PayloadPath, + [Parameter(Mandatory=$true)][string]$ResultPath +) + +$ErrorActionPreference = 'Stop' +$ServiceName = '${SERVICE_NAME}' + +function Write-Result($ok, $errorMessage) { + $obj = @{ ok = $ok; error = $errorMessage } + $json = $obj | ConvertTo-Json -Compress + [System.IO.File]::WriteAllText($ResultPath, $json, (New-Object System.Text.UTF8Encoding($false))) +} + +# Прямой вызов "& sc.exe create ... binPath= $binPath" ломает значения с embedded-кавычками: +# PowerShell (& / call operator, и точно так же Start-Process -ArgumentList) перепаковывает +# аргументы по-своему и режет уже готовую строку binPath на два токена по пробелу перед "@" — +# sc.exe получает лишний позиционный аргумент и падает с кодом 1639 (Invalid command line), +# хотя та же строка, набранная руками в cmd.exe, разбирается верно. ProcessStartInfo.Arguments — +# нижний уровень (.NET передаёт эту строку в CreateProcess как есть, без повторной токенизации +# самим PowerShell) — единственный способ гарантированно передать её той же строкой. +function Invoke-ScRaw($rawArgs) { + $psi = New-Object System.Diagnostics.ProcessStartInfo + $psi.FileName = 'sc.exe' + $psi.Arguments = $rawArgs + $psi.UseShellExecute = $false + $psi.RedirectStandardOutput = $true + $psi.RedirectStandardError = $true + $psi.CreateNoWindow = $true + $proc = [System.Diagnostics.Process]::Start($psi) + $stdout = $proc.StandardOutput.ReadToEnd() + $stderr = $proc.StandardError.ReadToEnd() + $proc.WaitForExit() + return [pscustomobject]@{ ExitCode = $proc.ExitCode; Output = ("$stdout $stderr").Trim() } +} + +try { + $payload = Get-Content -Raw -Path $PayloadPath | ConvertFrom-Json + + switch ($payload.action) { + 'install' { + $dir = $payload.serviceDataDir + New-Item -ItemType Directory -Path $dir -Force | Out-Null + + # Снимаем наследование прав каталога: конфиг читает служба от SYSTEM, писать в него + # должны только SYSTEM и администраторы — иначе обычный пользователь мог бы подменить + # аргументы winws, которые выполняются с правами SYSTEM. + & icacls $dir /inheritance:r | Out-Null + & icacls $dir /grant '*S-1-5-18:(OI)(CI)F' | Out-Null + & icacls $dir /grant '*S-1-5-32-544:(OI)(CI)F' | Out-Null + & icacls $dir /grant '*S-1-5-32-545:(OI)(CI)RX' | Out-Null + + $configPath = $payload.configPath + # UTF-8 без BOM — этого требует сам winws при чтении @ (docs/windows.md zapret). + $utf8NoBom = New-Object System.Text.UTF8Encoding($false) + [System.IO.File]::WriteAllText($configPath, $payload.configBody, $utf8NoBom) + + # Служба должна ссылаться на копию winws.exe в этом же защищённом каталоге, а не на + # оригинал в папке установки приложения: установка теперь per-user (%LocalAppData%), + # эта папка доступна на запись обычному пользователю — SYSTEM-служба, ссылающаяся туда, + # была бы локальным повышением привилегий. Копируем winws.exe и рядом лежащие + # cygwin1.dll/WinDivert.dll/WinDivert64.sys, от них он зависит при запуске. + $sourceDir = Split-Path -Parent $payload.winwsPath + $protectedWinws = Join-Path $dir 'winws.exe' + foreach ($name in @('winws.exe', 'cygwin1.dll', 'WinDivert.dll', 'WinDivert64.sys')) { + Copy-Item -Path (Join-Path $sourceDir $name) -Destination (Join-Path $dir $name) -Force + } + + # sc.exe читает binPath как ОДИН аргумент — значит всю строку нужно взять в одну пару + # кавычек, а внутренние кавычки экранировать как \\" (без этого CommandLineToArgvW режет + # значение на два токена по пробелу перед "@", и sc create падает с кодом 1639). + # Тот же приём — в service_create.cmd из zapret-win-bundle. + $binPath = '"\\"' + $protectedWinws + '\\" @\\"' + $configPath + '\\""' + $startType = if ($payload.autoStart) { 'auto' } else { 'demand' } + + & sc.exe query $ServiceName | Out-Null + $serviceExists = ($LASTEXITCODE -eq 0) + + if ($serviceExists) { + $scArgs = 'config ' + $ServiceName + ' binPath= ' + $binPath + ' start= ' + $startType + $result = Invoke-ScRaw $scArgs + if ($result.ExitCode -ne 0) { throw "sc config завершился с кодом $($result.ExitCode) : $($result.Output)" } + } else { + $scArgs = 'create ' + $ServiceName + ' binPath= ' + $binPath + ' start= ' + $startType + ' DisplayName= "RKNboost: обход блокировок"' + $result = Invoke-ScRaw $scArgs + if ($result.ExitCode -ne 0) { throw "sc create завершился с кодом $($result.ExitCode) : $($result.Output)" } + } + & sc.exe description $ServiceName 'Служба RKNboost: обходит блокировки через winws/WinDivert.' | Out-Null + + # Разрешаем интерактивным пользователям запуск/остановку/чтение статуса службы, но не + # смену конфигурации и не смену прав — иначе любой пользователь на машине мог бы подменить + # аргументы, с которыми служба запускает winws от имени SYSTEM. + $sddlLines = & sc.exe sdshow $ServiceName + $sddl = ($sddlLines | Where-Object { $_ -match '^D:' } | Select-Object -First 1) + if (-not $sddl) { throw 'sc sdshow не вернул дескриптор безопасности' } + $sddl = $sddl -replace '\\(A;;[^;]*;;;IU\\)', '' + $sddl = $sddl -replace '^D:', 'D:(A;;CCLCSWRPWPLORC;;;IU)' + $sdsetOut = & sc.exe sdset $ServiceName $sddl 2>&1 | Out-String + if ($LASTEXITCODE -ne 0) { throw "sc sdset завершился с кодом $LASTEXITCODE : $($sdsetOut.Trim())" } + + Write-Result $true $null + } + 'setAutoStart' { + $startType = if ($payload.autoStart) { 'auto' } else { 'demand' } + $out = & sc.exe config $ServiceName start= $startType 2>&1 | Out-String + if ($LASTEXITCODE -ne 0) { throw "sc config завершился с кодом $LASTEXITCODE : $($out.Trim())" } + Write-Result $true $null + } + 'remove' { + & sc.exe stop $ServiceName 2>$null | Out-Null + Start-Sleep -Milliseconds 500 + & sc.exe delete $ServiceName | Out-Null + if (Test-Path $payload.serviceDataDir) { + Remove-Item -Path $payload.serviceDataDir -Recurse -Force + } + Write-Result $true $null + } + default { + throw "Неизвестное действие: $($payload.action)" + } + } +} +catch { + Write-Result $false $_.Exception.Message + exit 1 +} +` + +async function readResult(path: string): Promise<{ ok: boolean; error: string | null } | null> { + try { + return JSON.parse(await readFile(path, 'utf8')) as { ok: boolean; error: string | null } + } catch { + return null + } +} + +/** + * Пишет payload во временный каталог и выполняет `worker.ps1`. + * + * Если приложение уже запущено от администратора (в собранном виде — всегда), worker + * запускается напрямую: ни UAC, ни лишних процессов. Иначе — не более одного запроса UAC + * на всё действие: `outer.ps1` (без прав) через `Start-Process -Verb RunAs` поднимает + * приложение с флагом задачи, а уже оно запускает worker. + * + * Результат в обоих случаях читаем из `result.json`, а не из кода возврата процесса. + */ +async function runElevated(payload: Record): Promise { + const dir = await mkdtemp(join(tmpdir(), 'rknboost-svc-')) + const workerPath = join(dir, TASK_FILES.worker) + const outerPath = join(dir, 'outer.ps1') + const payloadPath = join(dir, TASK_FILES.payload) + const resultPath = join(dir, TASK_FILES.result) + + try { + await writeFile(workerPath, withBom(WORKER_SCRIPT), 'utf8') + await writeFile(payloadPath, JSON.stringify(payload), 'utf8') + + let exitCode = 0 + if (await isElevated()) { + // Приложение и так запущено от администратора (в собранном виде — всегда, см. + // requestedExecutionLevel в electron-builder.yml). Повышать нечего: запускаем worker + // напрямую, без outer.ps1 и без второго экземпляра приложения на каждое действие. + exitCode = await runElevatedServiceTask(dir) + } else { + await writeFile(outerPath, withBom(OUTER_SCRIPT), 'utf8') + try { + await run('powershell.exe', [ + '-NoProfile', + '-ExecutionPolicy', + 'Bypass', + '-File', + outerPath, + '-AppPath', + process.execPath, + '-TaskDir', + dir, + // В dev electron.exe нужен каталог приложения первым аргументом; в сборке — нет. + ...(app.isPackaged ? [] : ['-AppDir', app.getAppPath()]) + ]) + } catch (error) { + exitCode = exitCodeOf(error) + } + } + + if (exitCode === UAC_CANCELLED_CODE) { + throw new Error('Установка отменена — запрос на права администратора был отклонён.') + } + + const result = await readResult(resultPath) + if (!result?.ok) { + throw new Error( + result?.error + ? `Не удалось настроить службу: ${result.error}` + : `Настройка службы завершилась с ошибкой (код ${exitCode}).` + ) + } + } finally { + await rm(dir, { recursive: true, force: true }) + } +} + +export interface ApplyServiceConfigOptions { + /** Тело `strategy.cfg` — из `winwsConfigFile()` в `strategies.ts`. */ + configBody: string + autoStart: boolean +} + +/** + * Установить службу (если её ещё нет) или перенастроить существующую — один запрос UAC. + * Заодно копирует `winws.exe` + WinDivert из папки установки приложения (per-user, + * доступна на запись обычному пользователю) в защищённый каталог службы — см. + * `protectedWinwsPath()` в `paths.ts` — и указывает binPath именно туда. + * Саму службу не запускает — вызывающий код (`Win32Engine.start()`) делает это отдельным + * вызовом `startService()`, который уже не требует прав благодаря ACL, выставленному здесь. + */ +export async function applyServiceConfig(options: ApplyServiceConfigOptions): Promise { + await runElevated({ + action: 'install', + serviceDataDir: serviceDataDir(), + configPath: serviceConfigPath(), + configBody: options.configBody, + winwsPath: winwsPath(), + autoStart: options.autoStart + }) +} + +/** Только тип автозапуска — не трогает аргументы winws, отдельный (более лёгкий) UAC-запрос. */ +export async function setServiceAutoStart(autoStart: boolean): Promise { + await runElevated({ action: 'setAutoStart', autoStart }) +} + +/** Используется деинсталлятором (`build/installer.nsh`) и на случай ручной переустановки. */ +export async function removeService(): Promise { + await runElevated({ action: 'remove', serviceDataDir: serviceDataDir() }) +} diff --git a/src/main/zapret/strategies.ts b/src/main/zapret/strategies.ts new file mode 100644 index 0000000..b7b7f5e --- /dev/null +++ b/src/main/zapret/strategies.ts @@ -0,0 +1,247 @@ +import { createHash } from 'node:crypto' +import type { ZapretStrategy } from '../../shared/ipc-contract' +import { autoHostlistPath, fakesDir, userListsDir } from './paths' +import { WIN32_STRATEGIES } from './strategies.win32.generated' + +/** + * Пресеты обхода. + * + * Универсального набора параметров не существует: что сработает, зависит от DPI + * конкретного провайдера. + * + * `tpws` (macOS) — прокси уровня TCP-соединений, а не пакетов: умеет только резать/ + * переупорядочивать сегменты (`--split-pos`, `--disorder`, `--oob`, `--tlsrec`), но не + * умеет ни fake-пакетов, ни UDP/QUIC вообще. Профили ниже — свои, под это ограничение. + * + * `winws` (Windows) работает на уровне пакетов через WinDivert. Его пресеты — 22 варианта + * из Flowseal/zapret-discord-youtube, перенесённые в `strategies.win32.generated.ts` + * генератором `scripts/import-flowseal-strategies.mjs` (не батниками — кодом). + * + * Оба движка используют одни и те же списки сайтов (`src/main/zapret/lists.ts`): + * `{LISTS}/.txt` — путь резолвится в `resolvePlaceholders()` ниже. + */ +interface DarwinStrategyDefinition extends ZapretStrategy { + /** Аргументы tpws после общих `--socks --port=...`; плейсхолдеры ещё не резолвлены. */ + tpws: string[] +} + +const LISTS_PLACEHOLDER = '{LISTS}' +const FAKES_PLACEHOLDER = '{FAKES}' +const AUTO_PLACEHOLDER = '{AUTO}' + +/** `--hostlist-auto` заставляет zapret самому копить сюда домены, похожие на заблокированные. */ +const AUTO_HOSTLIST_ARGS = [`--hostlist-auto=${AUTO_PLACEHOLDER}`] + +const DARWIN_DEFINITIONS: DarwinStrategyDefinition[] = [ + { + id: 'split', + name: 'Разрез + переупорядочивание', + description: 'Базовый вариант из поставки zapret: разрез TLS ClientHello по середине SNI.', + platforms: ['darwin'], + tpws: [ + '--filter-tcp=80', + `--hostlist=${LISTS_PLACEHOLDER}/general.txt`, + `--hostlist=${LISTS_PLACEHOLDER}/google.txt`, + `--hostlist-exclude=${LISTS_PLACEHOLDER}/exclude.txt`, + '--methodeol', + '--new', + '--filter-tcp=443', + `--hostlist=${LISTS_PLACEHOLDER}/general.txt`, + `--hostlist=${LISTS_PLACEHOLDER}/google.txt`, + `--hostlist-exclude=${LISTS_PLACEHOLDER}/exclude.txt`, + ...AUTO_HOSTLIST_ARGS, + '--split-pos=1,midsld', + '--disorder' + ] + }, + { + id: 'multisplit', + name: 'Многократный разрез', + description: 'Больше точек разреза. Помогает, когда DPI склеивает сегменты обратно.', + platforms: ['darwin'], + tpws: [ + '--filter-tcp=80', + `--hostlist=${LISTS_PLACEHOLDER}/general.txt`, + `--hostlist=${LISTS_PLACEHOLDER}/google.txt`, + `--hostlist-exclude=${LISTS_PLACEHOLDER}/exclude.txt`, + '--methodeol', + '--new', + '--filter-tcp=443', + `--hostlist=${LISTS_PLACEHOLDER}/general.txt`, + `--hostlist=${LISTS_PLACEHOLDER}/google.txt`, + `--hostlist-exclude=${LISTS_PLACEHOLDER}/exclude.txt`, + ...AUTO_HOSTLIST_ARGS, + // --oob здесь не добавляем: tpws отказывается совмещать его с --disorder вне Linux. + '--split-pos=1,midsld,host+1', + '--disorder' + ] + }, + { + id: 'tlsrec', + name: 'Разрез TLS-записи', + description: 'Другой способ разреза (--tlsrec) — стоит попробовать, если «Разрез» не помогает.', + platforms: ['darwin'], + tpws: [ + '--filter-tcp=80', + `--hostlist=${LISTS_PLACEHOLDER}/general.txt`, + `--hostlist=${LISTS_PLACEHOLDER}/google.txt`, + `--hostlist-exclude=${LISTS_PLACEHOLDER}/exclude.txt`, + '--methodeol', + '--new', + '--filter-tcp=443', + `--hostlist=${LISTS_PLACEHOLDER}/general.txt`, + `--hostlist=${LISTS_PLACEHOLDER}/google.txt`, + `--hostlist-exclude=${LISTS_PLACEHOLDER}/exclude.txt`, + ...AUTO_HOSTLIST_ARGS, + '--split-pos=1', + '--tlsrec=midsld' + ] + }, + { + id: 'oob', + name: 'Разрез с OOB-байтом', + description: 'Внеполосный байт вместо переупорядочивания — третий вариант на выбор.', + platforms: ['darwin'], + tpws: [ + '--filter-tcp=80', + `--hostlist=${LISTS_PLACEHOLDER}/general.txt`, + `--hostlist=${LISTS_PLACEHOLDER}/google.txt`, + `--hostlist-exclude=${LISTS_PLACEHOLDER}/exclude.txt`, + '--methodeol', + '--new', + '--filter-tcp=443', + `--hostlist=${LISTS_PLACEHOLDER}/general.txt`, + `--hostlist=${LISTS_PLACEHOLDER}/google.txt`, + `--hostlist-exclude=${LISTS_PLACEHOLDER}/exclude.txt`, + ...AUTO_HOSTLIST_ARGS, + '--split-pos=1,midsld', + '--oob' + ] + }, + { + id: 'all-traffic', + name: 'Весь трафик, без списков', + description: + 'Разрез применяется ко всем сайтам, а не только из списка «Обходить». Диагностика.', + platforms: ['darwin'], + tpws: [ + '--filter-tcp=80', + '--methodeol', + '--new', + '--filter-tcp=443', + '--split-pos=1,midsld', + '--disorder' + ] + }, + { + id: 'passthrough', + name: 'Без изменения трафика', + description: + 'Прокси работает, но пакеты не трогает. Нужен, чтобы отделить проблемы сети от стратегии.', + platforms: ['darwin'], + tpws: [] + } +] + +/** Стратегия по умолчанию — своя на каждой платформе, наборы id не пересекаются. */ +export function defaultStrategyId(platform: NodeJS.Platform): string { + return platform === 'win32' ? 'general' : 'split' +} + +function resolvePlaceholders(arg: string): string { + return arg + .replaceAll(LISTS_PLACEHOLDER, userListsDir()) + .replaceAll(FAKES_PLACEHOLDER, fakesDir()) + .replaceAll(AUTO_PLACEHOLDER, autoHostlistPath()) +} + +/** Пресеты, применимые к текущей платформе. */ +export function listStrategies(platform: NodeJS.Platform): ZapretStrategy[] { + if (platform === 'darwin') { + return DARWIN_DEFINITIONS.map(({ id, name, description, platforms }) => ({ + id, + name, + description, + platforms + })) + } + if (platform === 'win32') { + return WIN32_STRATEGIES.map(({ id, name }) => ({ + id, + name, + // У Flowseal нет описаний стратегий — только имена вариантов, подобранных под провайдера. + description: 'Стратегия из подборки Flowseal/zapret-discord-youtube.', + platforms: ['win32'] + })) + } + return [] +} + +function findDarwin(strategyId: string): DarwinStrategyDefinition { + const strategy = DARWIN_DEFINITIONS.find((item) => item.id === strategyId) + if (!strategy) throw new Error(`Неизвестная стратегия: ${strategyId}`) + return strategy +} + +/** Полная командная строка tpws в режиме socks5 на localhost. */ +export function tpwsArgs(strategyId: string, port: number): string[] { + return [ + '--socks', + `--port=${port}`, + '--bind-addr=127.0.0.1', + '--maxconn=512', + ...findDarwin(strategyId).tpws.map(resolvePlaceholders) + ] +} + +/** Аргументы winws — служба читает их из файла как `winws.exe @strategy.txt`. */ +export function winwsArgs(strategyId: string): string[] { + const strategy = WIN32_STRATEGIES.find((item) => item.id === strategyId) + if (!strategy) throw new Error(`Неизвестная стратегия: ${strategyId}`) + return strategy.args.map(resolvePlaceholders) +} + +/** Читает служба: `winws.exe @strategy.cfg`, ограничение размера у самого winws — 16 КБ. */ +const MAX_CONFIG_FILE_BYTES = 16000 + +/** + * winws разбирает файл конфигурации через POSIX `wordexp()` (см. `nfq/nfqws.c` в поставке + * zapret) — без кавычек он режет аргумент по пробелам и съедает обратные слэши в путях вида + * `C:\Users\...`. Каждый аргумент поэтому кладём в одинарные кавычки, а `'` внутри значения + * экранируем как `'\''` — тем же приёмом, что и `networksetup()` в `proxy.darwin.ts` для shell. + */ +function quoteConfigArg(arg: string): string { + return `'${arg.replaceAll("'", `'\\''`)}'` +} + +/** + * Первая строка — маркер: по нему `Win32Engine.sync()` узнаёт, какая стратегия уже установлена + * в службу, не перечитывая все аргументы. Сам маркер — штатная опция `--comment` winws, + * в работу процесса не вмешивается. + */ +function configMarker(strategyId: string, body: string): string { + const hash = createHash('sha256').update(body).digest('hex').slice(0, 12) + return `--comment=rknboost:${strategyId}:${hash}` +} + +export interface WinwsConfig { + /** Содержимое `strategy.cfg` — UTF-8 без BOM, как требует winws (docs/windows.md zapret). */ + body: string + /** Маркер первой строки — сравнить с уже установленным, чтобы понять, нужна ли перенастройка. */ + marker: string +} + +/** Рендерит стратегию в файл аргументов для `winws.exe @` — см. `WinwsConfig`. */ +export function winwsConfigFile(strategyId: string): WinwsConfig { + const args = winwsArgs(strategyId).map(quoteConfigArg).join('\n') + const marker = configMarker(strategyId, args) + const body = `${marker}\n${args}\n` + + if (Buffer.byteLength(body, 'utf8') > MAX_CONFIG_FILE_BYTES) { + throw new Error( + `Стратегия «${strategyId}» не помещается в лимит winws на файл конфигурации (16 КБ).` + ) + } + + return { body, marker } +} diff --git a/src/main/zapret/strategies.win32.generated.ts b/src/main/zapret/strategies.win32.generated.ts new file mode 100644 index 0000000..64170b4 --- /dev/null +++ b/src/main/zapret/strategies.win32.generated.ts @@ -0,0 +1,2011 @@ +/** + * СГЕНЕРИРОВАНО `scripts/import-flowseal-strategies.mjs` из + * Flowseal/zapret-discord-youtube 1.10.2 (commit dfd8e613b099676cf2aa7b474ee5923801514dec). + * Не редактировать руками — перезапустить генератор и закоммитить результат. + * + * `{LISTS}` и `{FAKES}` — плейсхолдеры путей, резолвятся в `strategies.ts` + * через `resolvePlaceholders()` в фактические пути userData/resources. + */ + +export interface GeneratedWin32Strategy { + id: string + name: string + args: string[] +} + +export const WIN32_STRATEGIES: readonly GeneratedWin32Strategy[] = [ + { + id: 'alt', + name: 'ALT', + args: [ + '--wf-tcp=80,443,2053,2083,2087,2096,8443,12', + '--wf-udp=443,19294-19344,50000-50100,12', + '--filter-udp=443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-udp=19294-19344,50000-50100', + '--filter-l7=discord,stun', + '--dpi-desync=fake', + '--dpi-desync-fake-discord={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-fake-stun={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-repeats=6', + '--new', + '--filter-tcp=2053,2083,2087,2096,8443', + '--hostlist-domains=discord.media', + '--dpi-desync=fake,fakedsplit', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=ts', + '--dpi-desync-fakedsplit-pattern=0x00', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=443', + '--hostlist={LISTS}/google.txt', + '--ip-id=zero', + '--dpi-desync=fake,fakedsplit', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=ts', + '--dpi-desync-fakedsplit-pattern=0x00', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=80,443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,fakedsplit', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=ts', + '--dpi-desync-fakedsplit-pattern=0x00', + '--dpi-desync-fake-tls={FAKES}/stun.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-tcp=80,443,8443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,fakedsplit', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=ts', + '--dpi-desync-fakedsplit-pattern=0x00', + '--dpi-desync-fake-tls={FAKES}/stun.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-tcp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,fakedsplit', + '--dpi-desync-repeats=6', + '--dpi-desync-any-protocol=1', + '--dpi-desync-cutoff=n4', + '--dpi-desync-fooling=ts', + '--dpi-desync-fakedsplit-pattern=0x00', + '--dpi-desync-fake-tls={FAKES}/stun.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-unknown={FAKES}/stun.bin', + '--dpi-desync-fake-unknown={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-udp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=12', + '--dpi-desync-any-protocol=1', + '--dpi-desync-fake-unknown-udp={FAKES}/ACTIVE_GAME_UDP.bin', + '--dpi-desync-cutoff=n3' + ] + }, + { + id: 'alt10', + name: 'ALT10', + args: [ + '--wf-tcp=80,443,2053,2083,2087,2096,8443,12', + '--wf-udp=443,19294-19344,50000-50100,12', + '--filter-udp=443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-udp=19294-19344,50000-50100', + '--filter-l7=discord,stun', + '--dpi-desync=fake', + '--dpi-desync-fake-discord={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-fake-stun={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-repeats=6', + '--new', + '--filter-tcp=2053,2083,2087,2096,8443', + '--hostlist-domains=discord.media', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=ts', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-tls-mod=none', + '--new', + '--filter-tcp=443', + '--hostlist={LISTS}/google.txt', + '--ip-id=zero', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=ts', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=80,443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=ts', + '--dpi-desync-fake-tls={FAKES}/stun.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_4pda_to.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-tcp=80,443,8443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=ts', + '--dpi-desync-fake-tls={FAKES}/stun.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_4pda_to.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-tcp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-any-protocol=1', + '--dpi-desync-cutoff=n3', + '--dpi-desync-fooling=ts', + '--dpi-desync-fake-tls={FAKES}/stun.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_4pda_to.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-unknown={FAKES}/stun.bin', + '--dpi-desync-fake-unknown={FAKES}/tls_clienthello_4pda_to.bin', + '--new', + '--filter-udp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=12', + '--dpi-desync-any-protocol=1', + '--dpi-desync-fake-unknown-udp={FAKES}/ACTIVE_GAME_UDP.bin', + '--dpi-desync-cutoff=n2' + ] + }, + { + id: 'alt11', + name: 'ALT11', + args: [ + '--wf-tcp=80,443,2053,2083,2087,2096,8443,12', + '--wf-udp=443,19294-19344,50000-50100,12', + '--filter-udp=443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=11', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-udp=19294-19344,50000-50100', + '--filter-l7=discord,stun', + '--dpi-desync=fake', + '--dpi-desync-fake-discord={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-fake-stun={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-repeats=6', + '--new', + '--filter-tcp=2053,2083,2087,2096,8443', + '--hostlist-domains=discord.media', + '--dpi-desync=fake,multisplit', + '--dpi-desync-split-seqovl=681', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=ts', + '--dpi-desync-repeats=8', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=443', + '--hostlist={LISTS}/google.txt', + '--ip-id=zero', + '--dpi-desync=fake,multisplit', + '--dpi-desync-split-seqovl=681', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=ts', + '--dpi-desync-repeats=8', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=80,443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,multisplit', + '--dpi-desync-split-seqovl=664', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=ts', + '--dpi-desync-repeats=8', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-tls={FAKES}/stun2.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=11', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-tcp=80,443,8443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,multisplit', + '--dpi-desync-split-seqovl=664', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=ts', + '--dpi-desync-repeats=8', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-tls={FAKES}/stun2.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-tcp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,multisplit', + '--dpi-desync-any-protocol=1', + '--dpi-desync-cutoff=n4', + '--dpi-desync-split-seqovl=664', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=ts', + '--dpi-desync-repeats=8', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-tls={FAKES}/stun2.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-unknown={FAKES}/stun2.bin', + '--dpi-desync-fake-unknown={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=10', + '--dpi-desync-any-protocol=1', + '--dpi-desync-fake-unknown-udp={FAKES}/ACTIVE_GAME_UDP.bin', + '--dpi-desync-cutoff=n4' + ] + }, + { + id: 'alt12', + name: 'ALT12', + args: [ + '--wf-tcp=80,443,2053,2083,2087,2096,8443,12', + '--wf-udp=443,19294-19344,50000-50100,12', + '--filter-udp=443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=11', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-udp=19294-19344,50000-50100', + '--filter-l7=discord,stun', + '--dpi-desync=fake', + '--dpi-desync-fake-discord={FAKES}/stun.bin', + '--dpi-desync-fake-discord={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-fake-stun={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-repeats=3', + '--new', + '--filter-tcp=2053,2083,2087,2096,8443', + '--hostlist-domains=discord.media', + '--dpi-desync=fake,multisplit', + '--dpi-desync-split-seqovl=681', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=ts', + '--dpi-desync-repeats=8', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=443', + '--hostlist={LISTS}/google.txt', + '--ip-id=zero', + '--dpi-desync=hostfakesplit', + '--dpi-desync-fooling=ts', + '--dpi-desync-hostfakesplit-mod=host=www.google.com', + '--new', + '--filter-tcp=80,443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,multisplit', + '--dpi-desync-split-seqovl=664', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=ts', + '--dpi-desync-repeats=8', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-tls={FAKES}/stun.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=11', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-tcp=80,443,8443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,multisplit', + '--dpi-desync-split-seqovl=664', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=ts', + '--dpi-desync-repeats=8', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-tls={FAKES}/stun.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-tcp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,multisplit', + '--dpi-desync-any-protocol=1', + '--dpi-desync-cutoff=n4', + '--dpi-desync-split-seqovl=664', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=ts', + '--dpi-desync-repeats=8', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-tls={FAKES}/stun.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-unknown={FAKES}/stun.bin', + '--dpi-desync-fake-unknown={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=10', + '--dpi-desync-any-protocol=1', + '--dpi-desync-fake-unknown-udp={FAKES}/ACTIVE_GAME_UDP.bin', + '--dpi-desync-cutoff=n4' + ] + }, + { + id: 'alt13', + name: 'ALT13', + args: [ + '--wf-tcp=80,443,2053,2083,2087,2096,8443,12', + '--wf-udp=443,19294-19344,50000-50100,12', + '--filter-udp=443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=11', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-udp=19294-19344,50000-50100', + '--filter-l7=discord,stun', + '--dpi-desync=fake', + '--dpi-desync-fake-discord={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-fake-stun={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-repeats=5', + '--new', + '--filter-tcp=2053,2083,2087,2096,8443', + '--hostlist-domains=discord.media', + '--dpi-desync=fake,multisplit', + '--dpi-desync-split-seqovl=681', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=ts', + '--dpi-desync-repeats=7', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=443', + '--hostlist={LISTS}/google.txt', + '--dpi-desync=hostfakesplit', + '--dpi-desync-fooling=ts', + '--dpi-desync-hostfakesplit-mod=host=www.google.com', + '--new', + '--filter-tcp=80,443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,hostfakesplit', + '--dpi-desync-fooling=ts', + '--dpi-desync-hostfakesplit-mod=host=mail.ru,altorder=1', + '--dpi-desync-repeats=5', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_sochi_park.bin', + '--dpi-desync-fake-tls={FAKES}/stun2.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_sochi_park.bin', + '--new', + '--filter-udp=443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=11', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-tcp=80,443,8443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,hostfakesplit', + '--dpi-desync-fooling=ts', + '--dpi-desync-hostfakesplit-mod=host=mail.ru,altorder=1', + '--dpi-desync-repeats=5', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_sochi_park.bin', + '--dpi-desync-fake-tls={FAKES}/stun2.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_sochi_park.bin', + '--new', + '--filter-tcp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,hostfakesplit', + '--dpi-desync-any-protocol=1', + '--dpi-desync-cutoff=n4', + '--dpi-desync-fooling=ts', + '--dpi-desync-hostfakesplit-mod=host=mail.ru,altorder=1', + '--dpi-desync-repeats=5', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_sochi_park.bin', + '--dpi-desync-fake-tls={FAKES}/stun2.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_sochi_park.bin', + '--dpi-desync-fake-unknown={FAKES}/tls_clienthello_sochi_park.bin', + '--dpi-desync-fake-unknown={FAKES}/stun2.bin', + '--new', + '--filter-udp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=10', + '--dpi-desync-any-protocol=1', + '--dpi-desync-fake-unknown-udp={FAKES}/ACTIVE_GAME_UDP.bin', + '--dpi-desync-cutoff=n4' + ] + }, + { + id: 'alt2', + name: 'ALT2', + args: [ + '--wf-tcp=80,443,2053,2083,2087,2096,8443,12', + '--wf-udp=443,19294-19344,50000-50100,12', + '--filter-udp=443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-udp=19294-19344,50000-50100', + '--filter-l7=discord,stun', + '--dpi-desync=fake', + '--dpi-desync-fake-discord={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-fake-stun={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-repeats=6', + '--new', + '--filter-tcp=2053,2083,2087,2096,8443', + '--hostlist-domains=discord.media', + '--dpi-desync=multisplit', + '--dpi-desync-split-seqovl=652', + '--dpi-desync-split-pos=2', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=443', + '--hostlist={LISTS}/google.txt', + '--ip-id=zero', + '--dpi-desync=multisplit', + '--dpi-desync-split-seqovl=652', + '--dpi-desync-split-pos=2', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=80,443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=multisplit', + '--dpi-desync-split-seqovl=652', + '--dpi-desync-split-pos=2', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-udp=443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-tcp=80,443,8443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=multisplit', + '--dpi-desync-split-seqovl=652', + '--dpi-desync-split-pos=2', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=multisplit', + '--dpi-desync-any-protocol=1', + '--dpi-desync-cutoff=n3', + '--dpi-desync-split-seqovl=652', + '--dpi-desync-split-pos=2', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-udp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=12', + '--dpi-desync-any-protocol=1', + '--dpi-desync-fake-unknown-udp={FAKES}/ACTIVE_GAME_UDP.bin', + '--dpi-desync-cutoff=n2' + ] + }, + { + id: 'alt3', + name: 'ALT3', + args: [ + '--wf-tcp=80,443,2053,2083,2087,2096,8443,12', + '--wf-udp=443,19294-19344,50000-50100,12', + '--filter-udp=443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-udp=19294-19344,50000-50100', + '--filter-l7=discord,stun', + '--dpi-desync=fake', + '--dpi-desync-fake-discord={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-fake-stun={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-repeats=6', + '--new', + '--filter-tcp=2053,2083,2087,2096,8443', + '--hostlist-domains=discord.media', + '--dpi-desync=fake,hostfakesplit', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com', + '--dpi-desync-hostfakesplit-mod=host=www.google.com,altorder=1', + '--dpi-desync-fooling=ts', + '--new', + '--filter-tcp=443', + '--hostlist={LISTS}/google.txt', + '--ip-id=zero', + '--dpi-desync=fake,hostfakesplit', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com', + '--dpi-desync-hostfakesplit-mod=host=www.google.com,altorder=1', + '--dpi-desync-fooling=ts', + '--new', + '--filter-tcp=80,443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,hostfakesplit', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=ya.ru', + '--dpi-desync-hostfakesplit-mod=host=ya.ru,altorder=1', + '--dpi-desync-fooling=ts', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-tcp=80,443,8443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,hostfakesplit', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=ya.ru', + '--dpi-desync-hostfakesplit-mod=host=ya.ru,altorder=1', + '--dpi-desync-fooling=ts', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-tcp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,hostfakesplit', + '--dpi-desync-any-protocol=1', + '--dpi-desync-cutoff=n4', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=ya.ru', + '--dpi-desync-hostfakesplit-mod=host=ya.ru,altorder=1', + '--dpi-desync-fooling=ts', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-unknown={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=10', + '--dpi-desync-any-protocol=1', + '--dpi-desync-fake-unknown-udp={FAKES}/ACTIVE_GAME_UDP.bin', + '--dpi-desync-cutoff=n4' + ] + }, + { + id: 'alt4', + name: 'ALT4', + args: [ + '--wf-tcp=80,443,2053,2083,2087,2096,8443,12', + '--wf-udp=443,19294-19344,50000-50100,12', + '--filter-udp=443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-udp=19294-19344,50000-50100', + '--filter-l7=discord,stun', + '--dpi-desync=fake', + '--dpi-desync-fake-discord={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-fake-stun={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-repeats=6', + '--new', + '--filter-tcp=2053,2083,2087,2096,8443', + '--hostlist-domains=discord.media', + '--dpi-desync=fake,multisplit', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=1000', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=443', + '--hostlist={LISTS}/google.txt', + '--ip-id=zero', + '--dpi-desync=fake,multisplit', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=1000', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=80,443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,multisplit', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=1000', + '--dpi-desync-fake-tls={FAKES}/stun.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-tcp=80,443,8443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,multisplit', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=1000', + '--dpi-desync-fake-tls={FAKES}/stun.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-tcp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,multisplit', + '--dpi-desync-repeats=6', + '--dpi-desync-any-protocol=1', + '--dpi-desync-cutoff=n3', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=1000', + '--dpi-desync-fake-tls={FAKES}/stun.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-unknown={FAKES}/stun.bin', + '--dpi-desync-fake-unknown={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-udp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=10', + '--dpi-desync-any-protocol=1', + '--dpi-desync-fake-unknown-udp={FAKES}/ACTIVE_GAME_UDP.bin', + '--dpi-desync-cutoff=n2' + ] + }, + { + id: 'alt5', + name: 'ALT5', + args: [ + '--wf-tcp=80,443,2053,2083,2087,2096,8443,12', + '--wf-udp=443,19294-19344,50000-50100,12', + '--filter-udp=443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-udp=19294-19344,50000-50100', + '--filter-l7=discord,stun', + '--dpi-desync=fake', + '--dpi-desync-fake-discord={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-fake-stun={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-repeats=6', + '--new', + '--filter-l3=ipv4', + '--filter-tcp=80,443,2053,2083,2087,2096,8443', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=syndata,multidisorder', + '--new', + '--filter-tcp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=syndata,multidisorder', + '--dpi-desync-any-protocol=1', + '--dpi-desync-cutoff=n4', + '--new', + '--filter-udp=443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-udp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=14', + '--dpi-desync-any-protocol=1', + '--dpi-desync-fake-unknown-udp={FAKES}/ACTIVE_GAME_UDP.bin', + '--dpi-desync-cutoff=n3' + ] + }, + { + id: 'alt6', + name: 'ALT6', + args: [ + '--wf-tcp=80,443,2053,2083,2087,2096,8443,12', + '--wf-udp=443,19294-19344,50000-50100,12', + '--filter-udp=443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-udp=19294-19344,50000-50100', + '--filter-l7=discord,stun', + '--dpi-desync=fake', + '--dpi-desync-fake-discord={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-fake-stun={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-repeats=6', + '--new', + '--filter-tcp=2053,2083,2087,2096,8443', + '--hostlist-domains=discord.media', + '--dpi-desync=multisplit', + '--dpi-desync-split-seqovl=681', + '--dpi-desync-split-pos=1', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=443', + '--hostlist={LISTS}/google.txt', + '--ip-id=zero', + '--dpi-desync=multisplit', + '--dpi-desync-split-seqovl=681', + '--dpi-desync-split-pos=1', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=80,443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=multisplit', + '--dpi-desync-split-seqovl=681', + '--dpi-desync-split-pos=1', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-udp=443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-tcp=80,443,8443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=multisplit', + '--dpi-desync-split-seqovl=681', + '--dpi-desync-split-pos=1', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=multisplit', + '--dpi-desync-any-protocol=1', + '--dpi-desync-cutoff=n3', + '--dpi-desync-split-seqovl=681', + '--dpi-desync-split-pos=1', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-udp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=12', + '--dpi-desync-any-protocol=1', + '--dpi-desync-fake-unknown-udp={FAKES}/ACTIVE_GAME_UDP.bin', + '--dpi-desync-cutoff=n2' + ] + }, + { + id: 'alt7', + name: 'ALT7', + args: [ + '--wf-tcp=80,443,2053,2083,2087,2096,8443,12', + '--wf-udp=443,19294-19344,50000-50100,12', + '--filter-udp=443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-udp=19294-19344,50000-50100', + '--filter-l7=discord,stun', + '--dpi-desync=fake', + '--dpi-desync-fake-discord={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-fake-stun={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-repeats=6', + '--new', + '--filter-tcp=2053,2083,2087,2096,8443', + '--hostlist-domains=discord.media', + '--dpi-desync=multisplit', + '--dpi-desync-split-pos=2,sniext+1', + '--dpi-desync-split-seqovl=679', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=443', + '--hostlist={LISTS}/google.txt', + '--ip-id=zero', + '--dpi-desync=multisplit', + '--dpi-desync-split-pos=2,sniext+1', + '--dpi-desync-split-seqovl=679', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=80,443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=multisplit', + '--dpi-desync-split-pos=2,sniext+1', + '--dpi-desync-split-seqovl=679', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-udp=443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-tcp=80,443,8443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=syndata', + '--new', + '--filter-tcp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=syndata', + '--dpi-desync-any-protocol=1', + '--dpi-desync-cutoff=n4', + '--new', + '--filter-udp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=12', + '--dpi-desync-any-protocol=1', + '--dpi-desync-fake-unknown-udp={FAKES}/ACTIVE_GAME_UDP.bin', + '--dpi-desync-cutoff=n2' + ] + }, + { + id: 'alt8', + name: 'ALT8', + args: [ + '--wf-tcp=80,443,2053,2083,2087,2096,8443,12', + '--wf-udp=443,19294-19344,50000-50100,12', + '--filter-udp=443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-udp=19294-19344,50000-50100', + '--filter-l7=discord,stun', + '--dpi-desync=fake', + '--dpi-desync-fake-discord={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-fake-stun={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-repeats=6', + '--new', + '--filter-tcp=2053,2083,2087,2096,8443', + '--hostlist-domains=discord.media', + '--dpi-desync=fake', + '--dpi-desync-fake-tls-mod=none', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=2', + '--new', + '--filter-tcp=443', + '--hostlist={LISTS}/google.txt', + '--ip-id=zero', + '--dpi-desync=fake', + '--dpi-desync-fake-tls-mod=none', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=2', + '--new', + '--filter-tcp=80,443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-fake-tls-mod=none', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=2', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-tcp=80,443,8443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-fake-tls-mod=none', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=2', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-tcp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-any-protocol=1', + '--dpi-desync-cutoff=n3', + '--dpi-desync-fake-tls-mod=none', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=2', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-unknown={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=12', + '--dpi-desync-any-protocol=1', + '--dpi-desync-fake-unknown-udp={FAKES}/ACTIVE_GAME_UDP.bin', + '--dpi-desync-cutoff=n2' + ] + }, + { + id: 'alt9', + name: 'ALT9', + args: [ + '--wf-tcp=80,443,2053,2083,2087,2096,8443,12', + '--wf-udp=443,19294-19344,50000-50100,12', + '--filter-udp=443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-udp=19294-19344,50000-50100', + '--filter-l7=discord,stun', + '--dpi-desync=fake', + '--dpi-desync-fake-discord={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-fake-stun={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-repeats=6', + '--new', + '--filter-tcp=2053,2083,2087,2096,8443', + '--hostlist-domains=discord.media', + '--dpi-desync=hostfakesplit', + '--dpi-desync-repeats=4', + '--dpi-desync-fooling=ts', + '--dpi-desync-hostfakesplit-mod=host=www.google.com', + '--new', + '--filter-tcp=443', + '--hostlist={LISTS}/google.txt', + '--ip-id=zero', + '--dpi-desync=hostfakesplit', + '--dpi-desync-repeats=4', + '--dpi-desync-fooling=ts', + '--dpi-desync-hostfakesplit-mod=host=www.google.com', + '--new', + '--filter-tcp=80,443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=hostfakesplit', + '--dpi-desync-repeats=4', + '--dpi-desync-fooling=ts,md5sig', + '--dpi-desync-hostfakesplit-mod=host=ozon.ru', + '--new', + '--filter-udp=443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-tcp=80,443,8443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=hostfakesplit', + '--dpi-desync-repeats=4', + '--dpi-desync-fooling=ts', + '--dpi-desync-hostfakesplit-mod=host=ozon.ru', + '--new', + '--filter-tcp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=hostfakesplit', + '--dpi-desync-repeats=4', + '--dpi-desync-any-protocol=1', + '--dpi-desync-cutoff=n3', + '--dpi-desync-fooling=ts', + '--dpi-desync-hostfakesplit-mod=host=ozon.ru', + '--new', + '--filter-udp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=12', + '--dpi-desync-any-protocol=1', + '--dpi-desync-fake-unknown-udp={FAKES}/ACTIVE_GAME_UDP.bin', + '--dpi-desync-cutoff=n2' + ] + }, + { + id: 'exp', + name: 'EXP', + args: [ + '--wf-tcp=80,443,2053,2083,2087,2096,8443,12', + '--wf-udp=443,19294-19344,50000-50100,12', + '--filter-l7=quic', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=11', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-udp=19294-19344,50000-50100', + '--filter-l7=discord,stun,unknown', + '--dpi-desync=fake', + '--dpi-desync-any-protocol=1', + '--dpi-desync-fake-discord={FAKES}/quic_initial_www_google_com.bin', + '--dpi-desync-fake-discord={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-fake-stun={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-fake-unknown-udp={FAKES}/quic_initial_www_google_com.bin', + '--dpi-desync-fake-unknown-udp={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-repeats=4', + '--dpi-desync-cutoff=n4', + '--new', + '--filter-tcp=2053,2083,2087,2096,8443', + '--hostlist-domains=discord.media', + '--dpi-desync=fake,multisplit', + '--dpi-desync-split-seqovl=681', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=ts', + '--dpi-desync-repeats=8', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=443', + '--hostlist={LISTS}/google.txt', + '--ip-id=zero', + '--dpi-desync=hostfakesplit', + '--dpi-desync-fooling=ts', + '--dpi-desync-hostfakesplit-mod=host=www.google.com', + '--new', + '--filter-tcp=80,443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,multisplit', + '--dpi-desync-split-seqovl=480', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=ts', + '--dpi-desync-repeats=4', + '--dpi-desync-split-seqovl-pattern={FAKES}/stun2.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=11', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-tcp=80,443,8443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--hostlist-exclude-domains=fonts.googleapis.com', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,multisplit', + '--dpi-desync-split-seqovl=480', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=ts', + '--dpi-desync-repeats=4', + '--dpi-desync-split-seqovl-pattern={FAKES}/stun2.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-tcp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,multisplit', + '--dpi-desync-any-protocol=1', + '--dpi-desync-cutoff=n4', + '--dpi-desync-split-seqovl=664', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=ts', + '--dpi-desync-repeats=8', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-tls={FAKES}/stun2.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=5', + '--dpi-desync-any-protocol=1', + '--dpi-desync-fake-unknown-udp={FAKES}/quic_initial_4pda_to.bin', + '--dpi-desync-fake-unknown-udp={FAKES}/ACTIVE_GAME_UDP.bin', + '--dpi-desync-cutoff=n4' + ] + }, + { + id: 'fake-tls-auto-alt', + name: 'FAKE TLS AUTO ALT', + args: [ + '--wf-tcp=80,443,2053,2083,2087,2096,8443,12', + '--wf-udp=443,19294-19344,50000-50100,12', + '--filter-udp=443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=11', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-udp=19294-19344,50000-50100', + '--filter-l7=discord,stun', + '--dpi-desync=fake', + '--dpi-desync-fake-discord={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-fake-stun={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-repeats=6', + '--new', + '--filter-tcp=2053,2083,2087,2096,8443', + '--hostlist-domains=discord.media', + '--dpi-desync=fake,fakedsplit', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=2', + '--dpi-desync-repeats=8', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com', + '--new', + '--filter-tcp=443', + '--hostlist={LISTS}/google.txt', + '--ip-id=zero', + '--dpi-desync=fake,fakedsplit', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=2', + '--dpi-desync-repeats=8', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com', + '--new', + '--filter-tcp=80,443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,fakedsplit', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=2', + '--dpi-desync-repeats=8', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=11', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-tcp=80,443,8443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,fakedsplit', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=2', + '--dpi-desync-repeats=8', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-tcp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,fakedsplit', + '--dpi-desync-any-protocol=1', + '--dpi-desync-cutoff=n3', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=2', + '--dpi-desync-repeats=8', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-unknown={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=10', + '--dpi-desync-any-protocol=1', + '--dpi-desync-fake-unknown-udp={FAKES}/ACTIVE_GAME_UDP.bin', + '--dpi-desync-cutoff=n2' + ] + }, + { + id: 'fake-tls-auto-alt2', + name: 'FAKE TLS AUTO ALT2', + args: [ + '--wf-tcp=80,443,2053,2083,2087,2096,8443,12', + '--wf-udp=443,19294-19344,50000-50100,12', + '--filter-udp=443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=11', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-udp=19294-19344,50000-50100', + '--filter-l7=discord,stun', + '--dpi-desync=fake', + '--dpi-desync-fake-discord={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-fake-stun={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-repeats=6', + '--new', + '--filter-tcp=2053,2083,2087,2096,8443', + '--hostlist-domains=discord.media', + '--dpi-desync=fake,multisplit', + '--dpi-desync-split-seqovl=681', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=10000000', + '--dpi-desync-repeats=8', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com', + '--new', + '--filter-tcp=443', + '--hostlist={LISTS}/google.txt', + '--ip-id=zero', + '--dpi-desync=fake,multisplit', + '--dpi-desync-split-seqovl=681', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=10000000', + '--dpi-desync-repeats=8', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com', + '--new', + '--filter-tcp=80,443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,multisplit', + '--dpi-desync-split-seqovl=681', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=10000000', + '--dpi-desync-repeats=8', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=11', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-tcp=80,443,8443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,multisplit', + '--dpi-desync-split-seqovl=681', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=10000000', + '--dpi-desync-repeats=8', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-tcp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,multisplit', + '--dpi-desync-any-protocol=1', + '--dpi-desync-cutoff=n3', + '--dpi-desync-split-seqovl=681', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=10000000', + '--dpi-desync-repeats=8', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-unknown={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=10', + '--dpi-desync-any-protocol=1', + '--dpi-desync-fake-unknown-udp={FAKES}/ACTIVE_GAME_UDP.bin', + '--dpi-desync-cutoff=n2' + ] + }, + { + id: 'fake-tls-auto-alt3', + name: 'FAKE TLS AUTO ALT3', + args: [ + '--wf-tcp=80,443,2053,2083,2087,2096,8443,12', + '--wf-udp=443,19294-19344,50000-50100,12', + '--filter-udp=443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=11', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-udp=19294-19344,50000-50100', + '--filter-l7=discord,stun', + '--dpi-desync=fake', + '--dpi-desync-fake-discord={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-fake-stun={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-repeats=6', + '--new', + '--filter-tcp=2053,2083,2087,2096,8443', + '--hostlist-domains=discord.media', + '--dpi-desync=fake,multisplit', + '--dpi-desync-split-seqovl=681', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=ts', + '--dpi-desync-repeats=8', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com', + '--new', + '--filter-tcp=443', + '--hostlist={LISTS}/google.txt', + '--ip-id=zero', + '--dpi-desync=fake,multisplit', + '--dpi-desync-split-seqovl=681', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=ts', + '--dpi-desync-repeats=8', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com', + '--new', + '--filter-tcp=80,443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,multisplit', + '--dpi-desync-split-seqovl=681', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=ts', + '--dpi-desync-repeats=8', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=11', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-tcp=80,443,8443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,multisplit', + '--dpi-desync-split-seqovl=681', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=ts', + '--dpi-desync-repeats=8', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-tcp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,multisplit', + '--dpi-desync-any-protocol=1', + '--dpi-desync-cutoff=n4', + '--dpi-desync-split-seqovl=681', + '--dpi-desync-split-pos=1', + '--dpi-desync-fooling=ts', + '--dpi-desync-repeats=8', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-unknown={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=10', + '--dpi-desync-any-protocol=1', + '--dpi-desync-fake-unknown-udp={FAKES}/ACTIVE_GAME_UDP.bin', + '--dpi-desync-cutoff=n3' + ] + }, + { + id: 'fake-tls-auto', + name: 'FAKE TLS AUTO', + args: [ + '--wf-tcp=80,443,2053,2083,2087,2096,8443,12', + '--wf-udp=443,19294-19344,50000-50100,12', + '--filter-udp=443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=11', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-udp=19294-19344,50000-50100', + '--filter-l7=discord,stun', + '--dpi-desync=fake', + '--dpi-desync-fake-discord={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-fake-stun={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-repeats=6', + '--new', + '--filter-tcp=2053,2083,2087,2096,8443', + '--hostlist-domains=discord.media', + '--dpi-desync=fake,multidisorder', + '--dpi-desync-split-pos=1,midsld', + '--dpi-desync-repeats=11', + '--dpi-desync-fooling=badseq', + '--dpi-desync-fake-tls=0x00000000', + '--dpi-desync-fake-tls=^!', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com', + '--new', + '--filter-tcp=443', + '--hostlist={LISTS}/google.txt', + '--ip-id=zero', + '--dpi-desync=fake,multidisorder', + '--dpi-desync-split-pos=1,midsld', + '--dpi-desync-repeats=11', + '--dpi-desync-fooling=badseq', + '--dpi-desync-fake-tls=0x00000000', + '--dpi-desync-fake-tls=^!', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com', + '--new', + '--filter-tcp=80,443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,multidisorder', + '--dpi-desync-split-pos=1,midsld', + '--dpi-desync-repeats=11', + '--dpi-desync-fooling=badseq', + '--dpi-desync-fake-tls=0x00000000', + '--dpi-desync-fake-tls=^!', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=11', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-tcp=80,443,8443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,multidisorder', + '--dpi-desync-split-pos=1,midsld', + '--dpi-desync-repeats=11', + '--dpi-desync-fooling=badseq', + '--dpi-desync-fake-tls=0x00000000', + '--dpi-desync-fake-tls=^!', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-tcp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake,multidisorder', + '--dpi-desync-any-protocol=1', + '--dpi-desync-cutoff=n4', + '--dpi-desync-split-pos=1,midsld', + '--dpi-desync-repeats=11', + '--dpi-desync-fooling=badseq', + '--dpi-desync-fake-tls=0x00000000', + '--dpi-desync-fake-tls=^!', + '--dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-unknown=0x00000000', + '--dpi-desync-fake-unknown={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=10', + '--dpi-desync-any-protocol=1', + '--dpi-desync-fake-unknown-udp={FAKES}/ACTIVE_GAME_UDP.bin', + '--dpi-desync-cutoff=n2' + ] + }, + { + id: 'simple-fake-alt', + name: 'SIMPLE FAKE ALT', + args: [ + '--wf-tcp=80,443,2053,2083,2087,2096,8443,12', + '--wf-udp=443,19294-19344,50000-50100,12', + '--filter-udp=443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-udp=19294-19344,50000-50100', + '--filter-l7=discord,stun', + '--dpi-desync=fake', + '--dpi-desync-fake-discord={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-fake-stun={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-repeats=6', + '--new', + '--filter-tcp=2053,2083,2087,2096,8443', + '--hostlist-domains=discord.media', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=2', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=443', + '--hostlist={LISTS}/google.txt', + '--ip-id=zero', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=2', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=80,443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=2', + '--dpi-desync-fake-tls={FAKES}/stun2.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-tcp=80,443,8443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=2', + '--dpi-desync-fake-tls={FAKES}/stun2.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-tcp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-any-protocol=1', + '--dpi-desync-cutoff=n3', + '--dpi-desync-fooling=badseq', + '--dpi-desync-badseq-increment=2', + '--dpi-desync-fake-tls={FAKES}/stun2.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-unknown={FAKES}/stun2.bin', + '--dpi-desync-fake-unknown={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-udp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=10', + '--dpi-desync-any-protocol=1', + '--dpi-desync-fake-unknown-udp={FAKES}/ACTIVE_GAME_UDP.bin', + '--dpi-desync-cutoff=n2' + ] + }, + { + id: 'simple-fake-alt2', + name: 'SIMPLE FAKE ALT2', + args: [ + '--wf-tcp=80,443,2053,2083,2087,2096,8443,12', + '--wf-udp=443,19294-19344,50000-50100,12', + '--filter-udp=443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-udp=19294-19344,50000-50100', + '--filter-l7=discord,stun', + '--dpi-desync=fake', + '--dpi-desync-fake-discord={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-fake-stun={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-repeats=6', + '--new', + '--filter-tcp=2053,2083,2087,2096,8443', + '--hostlist-domains=discord.media', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=ts', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=443', + '--hostlist={LISTS}/google.txt', + '--ip-id=zero', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=ts', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=80,443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=ts', + '--dpi-desync-fake-tls={FAKES}/stun2.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-tcp=80,443,8443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=ts', + '--dpi-desync-fake-tls={FAKES}/stun2.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-tcp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-any-protocol=1', + '--dpi-desync-cutoff=n5', + '--dpi-desync-fooling=ts', + '--dpi-desync-fake-tls={FAKES}/stun2.bin', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-unknown={FAKES}/stun2.bin', + '--dpi-desync-fake-unknown={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=12', + '--dpi-desync-any-protocol=1', + '--dpi-desync-fake-unknown-udp={FAKES}/ACTIVE_GAME_UDP.bin', + '--dpi-desync-cutoff=n3' + ] + }, + { + id: 'simple-fake', + name: 'SIMPLE FAKE', + args: [ + '--wf-tcp=80,443,2053,2083,2087,2096,8443,12', + '--wf-udp=443,19294-19344,50000-50100,12', + '--filter-udp=443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-udp=19294-19344,50000-50100', + '--filter-l7=discord,stun', + '--dpi-desync=fake', + '--dpi-desync-fake-discord={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-fake-stun={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-repeats=6', + '--new', + '--filter-tcp=2053,2083,2087,2096,8443', + '--hostlist-domains=discord.media', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=ts', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=443', + '--hostlist={LISTS}/google.txt', + '--ip-id=zero', + '--dpi-desync=hostfakesplit', + '--dpi-desync-fooling=ts', + '--dpi-desync-hostfakesplit-mod=host=www.google.com', + '--new', + '--filter-tcp=80,443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=ts', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-udp=443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-tcp=80,443,8443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fooling=ts', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--new', + '--filter-tcp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-any-protocol=1', + '--dpi-desync-cutoff=n4', + '--dpi-desync-fooling=ts', + '--dpi-desync-fake-tls={FAKES}/tls_clienthello_www_google_com.bin', + '--dpi-desync-fake-http={FAKES}/tls_clienthello_max_ru.bin', + '--dpi-desync-fake-unknown={FAKES}/stun2.bin', + '--dpi-desync-fake-unknown={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-udp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=12', + '--dpi-desync-any-protocol=1', + '--dpi-desync-fake-unknown-udp={FAKES}/ACTIVE_GAME_UDP.bin', + '--dpi-desync-cutoff=n3' + ] + }, + { + id: 'general', + name: 'Стандартная', + args: [ + '--wf-tcp=80,443,2053,2083,2087,2096,8443,12', + '--wf-udp=443,19294-19344,50000-50100,12', + '--filter-udp=443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-udp=19294-19344,50000-50100', + '--filter-l7=discord,stun', + '--dpi-desync=fake', + '--dpi-desync-fake-discord={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-fake-stun={FAKES}/ACTIVE_DISCORD_UDP.bin', + '--dpi-desync-repeats=6', + '--new', + '--filter-tcp=2053,2083,2087,2096,8443', + '--hostlist-domains=discord.media', + '--dpi-desync=multisplit', + '--dpi-desync-split-seqovl=681', + '--dpi-desync-split-pos=1', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=443', + '--hostlist={LISTS}/google.txt', + '--ip-id=zero', + '--dpi-desync=multisplit', + '--dpi-desync-split-seqovl=681', + '--dpi-desync-split-pos=1', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_www_google_com.bin', + '--new', + '--filter-tcp=80,443', + '--hostlist={LISTS}/general.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=multisplit', + '--dpi-desync-split-seqovl=568', + '--dpi-desync-split-pos=1', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_4pda_to.bin', + '--new', + '--filter-udp=443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=6', + '--dpi-desync-fake-quic={FAKES}/quic_initial_www_google_com.bin', + '--new', + '--filter-tcp=80,443,8443', + '--ipset={LISTS}/ipset-all.txt', + '--hostlist-exclude={LISTS}/exclude.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=multisplit', + '--dpi-desync-split-seqovl=568', + '--dpi-desync-split-pos=1', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_4pda_to.bin', + '--new', + '--filter-tcp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=multisplit', + '--dpi-desync-any-protocol=1', + '--dpi-desync-cutoff=n3', + '--dpi-desync-split-seqovl=568', + '--dpi-desync-split-pos=1', + '--dpi-desync-split-seqovl-pattern={FAKES}/tls_clienthello_4pda_to.bin', + '--new', + '--filter-udp=12', + '--ipset={LISTS}/ipset-all.txt', + '--ipset-exclude={LISTS}/ipset-exclude.txt', + '--dpi-desync=fake', + '--dpi-desync-repeats=12', + '--dpi-desync-any-protocol=1', + '--dpi-desync-fake-unknown-udp={FAKES}/ACTIVE_GAME_UDP.bin', + '--dpi-desync-cutoff=n2' + ] + } +] diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts new file mode 100644 index 0000000..2e27271 --- /dev/null +++ b/src/preload/index.d.ts @@ -0,0 +1,9 @@ +import type { RendererApi } from './index' + +declare global { + interface Window { + api: RendererApi + } +} + +export {} diff --git a/src/preload/index.ts b/src/preload/index.ts new file mode 100644 index 0000000..68e6bea --- /dev/null +++ b/src/preload/index.ts @@ -0,0 +1,79 @@ +import { contextBridge, ipcRenderer, type IpcRendererEvent } from 'electron' +import type { + IpcChannel, + IpcEventChannel, + IpcEvents, + IpcHandlers, + ThemeState, + ZapretDiagnosticResult, + ZapretList, + ZapretListId, + ZapretLogLine, + ZapretStatus, + ZapretStrategy +} from '../shared/ipc-contract' + +/** Типизированный `invoke`: канал, аргументы и результат берутся из контракта. */ +function invoke( + channel: K, + ...args: Parameters +): Promise> { + return ipcRenderer.invoke(channel, ...args) as Promise> +} + +/** Подписка на событие main → renderer. Возвращает функцию отписки (для useEffect). */ +function subscribe( + channel: K, + listener: (payload: IpcEvents[K]) => void +): () => void { + const wrapped = (_event: IpcRendererEvent, payload: IpcEvents[K]): void => listener(payload) + ipcRenderer.on(channel, wrapped) + return () => { + ipcRenderer.off(channel, wrapped) + } +} + +/** + * Единственное, что видит renderer. Никакого прямого доступа к `ipcRenderer`, + * к Node API или к файловой системе — только эти методы. + */ +const api = { + /** 'darwin' | 'win32' | 'linux' — renderer'у нужно, например, чтобы показывать автозапуск + * службы только на Windows (см. SettingsView.tsx). */ + platform: process.platform, + + getTheme: (): Promise => invoke('theme:get'), + onThemeChanged: (listener: (state: ThemeState) => void): (() => void) => + subscribe('theme:changed', listener), + + /** Windows: подтверждает диалогом ОС, запускает деинсталлятор и закрывает приложение. */ + uninstallApp: (): Promise => invoke('app:uninstall'), + + getZapretStatus: (): Promise => invoke('zapret:status'), + getZapretStrategies: (): Promise => invoke('zapret:strategies'), + startZapret: (strategyId: string): Promise => invoke('zapret:start', strategyId), + stopZapret: (): Promise => invoke('zapret:stop'), + setZapretSystemProxy: (enabled: boolean): Promise => + invoke('zapret:set-system-proxy', enabled), + setZapretStrategy: (strategyId: string): Promise => + invoke('zapret:set-strategy', strategyId), + setZapretAutoStart: (enabled: boolean): Promise => + invoke('zapret:set-autostart', enabled), + onZapretStatusChanged: (listener: (status: ZapretStatus) => void): (() => void) => + subscribe('zapret:status-changed', listener), + onZapretLog: (listener: (entry: ZapretLogLine) => void): (() => void) => + subscribe('zapret:log', listener), + + getZapretLists: (): Promise => invoke('zapret:lists'), + saveZapretList: (id: ZapretListId, entries: string[]): Promise => + invoke('zapret:list-save', id, entries), + resetZapretList: (id: ZapretListId): Promise => invoke('zapret:list-reset', id), + getZapretAutoHostlist: (): Promise => invoke('zapret:auto-hostlist'), + clearZapretAutoHostlist: (): Promise => invoke('zapret:auto-hostlist-clear'), + + runZapretDiagnostics: (): Promise => invoke('zapret:diagnose') +} + +export type RendererApi = typeof api + +contextBridge.exposeInMainWorld('api', api) diff --git a/src/renderer/index.html b/src/renderer/index.html new file mode 100644 index 0000000..d240480 --- /dev/null +++ b/src/renderer/index.html @@ -0,0 +1,18 @@ + + + + + RKNboost + + + + + +
+ + + diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx new file mode 100644 index 0000000..e0d46a0 --- /dev/null +++ b/src/renderer/src/App.tsx @@ -0,0 +1,54 @@ +import { useState } from 'react' +import SettingsView from './components/SettingsView' +import ZapretCard from './components/ZapretCard' +import { useTheme } from './hooks/useTheme' +import { useZapret } from './hooks/useZapret' + +export default function App(): React.JSX.Element { + useTheme() + const { status, setAutoStart } = useZapret() + const [showSettings, setShowSettings] = useState(false) + + return ( +
+ {showSettings ? ( + setShowSettings(false)} + status={status} + onSetAutoStart={setAutoStart} + /> + ) : ( + <> +
+ RKNboost + +
+ + + + )} +
+ ) +} diff --git a/src/renderer/src/assets/main.css b/src/renderer/src/assets/main.css new file mode 100644 index 0000000..f7e9a09 --- /dev/null +++ b/src/renderer/src/assets/main.css @@ -0,0 +1,641 @@ +/** + * Дизайн-система из design.md: роли Material 3, тёплая кремовая база + * и прохладный фиолетовый акцент. + * + * Правила, которые тут зашиты: + * • иерархия — тональностью поверхностей, а не тенями (теней нет вовсе); + * • скругления только из shape-шкалы (pill / 24 / 16 / 9); + * • два веса шрифта (400 и 500), sentence case, технические данные — моноширинным; + * • состояния — полупрозрачный state layer поверх роли (hover 8%, focus/pressed 12%). + */ + +:root { + /* --- Роли Material 3, светлая тема (design.md §2) --- */ + --primary: #6a45b8; + --on-primary: #ffffff; + --primary-container: #ebddff; + --on-primary-container: #5a2da8; + --secondary-container: #ebddff; + --on-secondary-container: #5a2da8; + /* Светлый фиолетовый: только крупные элементы и иконки, под мелкий текст не годится. */ + --accent: #a96bff; + + --surface: #faf6f0; + --on-surface: #3a2e22; + --surface-container: #f0e8dd; + --surface-container-high: #ece2d3; + --surface-bright: #fdfaf6; + + /* Текст: тёмно-коричневый, не чёрный и не фиолетовый (design.md §6). */ + --on-surface-variant: #5a4a3a; + --text-muted: #7a6a55; + + --outline: #d8cdbe; + --outline-variant: #ece2d3; + + --error: #ba1a1a; + --on-error: #ffffff; + --error-outline: #e6c4bc; + --on-error-outline: #b34a3a; + + /* --- Статусные цвета, тёплый подтон (design.md §2) --- */ + --status-success-bg: #c0dd97; + --status-success-fg: #2c4d0a; + --status-success-dot: #639922; + --status-neutral-bg: #e4dac9; + --status-neutral-fg: #7a6a55; + --status-neutral-dot: #a99a85; + --status-error-bg: #ffdad6; + --status-error-fg: #410002; + --status-error-dot: #ba1a1a; + + /* --- Форма (design.md §4) --- */ + --shape-pill: 100px; + --shape-card: 24px; + --shape-inner: 16px; + --shape-icon: 9px; + + --font-sans: Inter, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif; + --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace; + + color-scheme: light; +} + +/* Тёплый уголь, а не баклажан: тот же тёплый подтон, что у крема (design.md §7). */ +:root.dark { + --primary: #cbb3ff; + --on-primary: #381b6c; + --primary-container: #52389e; + --on-primary-container: #ebddff; + --secondary-container: #483a55; + --on-secondary-container: #ebddff; + --accent: #cbb3ff; + + --surface: #1a1611; + --on-surface: #ede2d4; + --surface-container: #251f18; + --surface-container-high: #302820; + --surface-bright: #2b251d; + + --on-surface-variant: #cfc2ae; + --text-muted: #b6a994; + + --outline: #4d4334; + --outline-variant: #342c20; + + --error: #ffb4ab; + --on-error: #5c1414; + --error-outline: #6e2a24; + --on-error-outline: #ffb4ab; + + /* --- Статусные цвета, тёмная тема (design.md §7) --- */ + --status-success-bg: #2d4413; + --status-success-fg: #c8e3a0; + --status-success-dot: #8fc24f; + --status-neutral-bg: #34291c; + --status-neutral-fg: #d0c0a4; + --status-neutral-dot: #b0a085; + --status-error-bg: #5c1414; + --status-error-fg: #ffb4ab; + --status-error-dot: #ff5449; + + color-scheme: dark; +} + +* { + box-sizing: border-box; +} + +html, +body, +#root { + height: 100%; +} + +/* Контент занимает всю высоту окна — оно маленькое (400×300), пустой хвост + снизу иначе выглядит перекошенным. */ +#root { + display: flex; + flex-direction: column; +} + +body { + margin: 0; + background: var(--surface); + color: var(--on-surface); + font-family: var(--font-sans); + font-size: 14px; + font-weight: 400; + line-height: 1.5; + -webkit-font-smoothing: antialiased; +} + +::selection { + background: var(--primary-container); + color: var(--on-primary-container); +} + +/* Общее кольцо фокуса — тот же 12%-слой, что и у нажатия. */ +:focus-visible { + outline: 2px solid var(--primary); + outline-offset: 2px; +} + +/* ---------- Контент ---------- */ + +/* Прокрутки на уровне оболочки нет: главный экран рассчитан на то, чтобы умещаться + в окно целиком. Прокручиваются только настройки и редактор списка — там содержимое + заведомо длиннее окна (правила ниже, .settings / .list-editor). */ +.content { + flex: 1 1 auto; + display: flex; + flex-direction: column; + padding: 20px; + overflow: hidden; + /* Без этого flex-элемент не даёт себя сжать ниже размера содержимого, + и внутренняя прокрутка настроек не включается. */ + min-height: 0; +} + +/* Шапка приложения: вордмарк слева, версия справа (design.md, шапка drawer-панели). */ +.app-header { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 12px; + margin-bottom: 16px; +} + +.app-header__name { + font-size: 20px; + font-weight: 500; +} + +.app-header__settings { + display: grid; + place-items: center; + width: 28px; + height: 28px; + padding: 0; + border: none; + border-radius: var(--shape-icon); + background: transparent; + color: var(--text-muted); + font-size: 15px; + cursor: pointer; +} + +.app-header__settings:hover { + background: color-mix(in srgb, var(--on-surface) 8%, transparent); + color: var(--on-surface); +} + +.app-header__settings:active { + background: color-mix(in srgb, var(--on-surface) 12%, transparent); +} + +/* Рабочая зона — светлее крема; разделение тональностью плюс тонкий контур. */ +.card { + padding: 20px; + background: var(--surface-bright); + border: 1px solid var(--outline-variant); + border-radius: var(--shape-card); +} + +.card h2 { + margin: 0 0 4px; + font-size: 16px; + font-weight: 500; +} + +/* ---------- Карточка обхода блокировок (design.md §4.1, «карточка VPN-сервера») ---------- */ + +.zapret-card { + display: flex; + flex-direction: column; + gap: 16px; + /* Предохранитель. В обычных состояниях карточка умещается целиком и никакой полосы + не видно — ради этого прокрутка и убрана с .content. Но текст ошибки приходит из + системы и по длине не ограничен, поэтому переполнение забирает на себя карточка, + а не оболочка: в патологическом случае пользователь увидит полосу внутри карточки, + а не обрезанное содержимое. */ + flex: 0 1 auto; + min-height: 0; + overflow-y: auto; +} + +/* Текст ошибки приходит из системы и по длине ничем не ограничен (например, вывод sc.exe). + Без потолка он растянул бы карточку и вытолкнул содержимое за пределы окна — а прокрутки + на главном экране нет, значит это было бы обрезано. Ограничиваем и прокручиваем сам блок + ошибки: главный экран при этом остаётся фиксированной высоты при любой длине ошибки. */ +.zapret-card .list-editor__error { + max-height: 96px; + overflow-y: auto; +} + +.zapret-card__head { + display: flex; + align-items: center; + gap: 14px; +} + +.zapret-card__title { + flex: 1 1 auto; + min-width: 0; + margin: 0; +} + +/* Кнопка включения — отдельно от названия и статуса, во всю ширину карточки + (design.md, часть II: главное действие — заливка primary, pill). */ +.toggle-button { + width: 100%; + min-height: 44px; + padding: 0 20px; + border: 1px solid transparent; + border-radius: var(--shape-pill); + background: var(--primary); + color: var(--on-primary); + font: inherit; + font-weight: 500; + cursor: pointer; +} + +.toggle-button:hover { + background: color-mix(in srgb, var(--on-primary) 8%, var(--primary)); +} + +.toggle-button:active { + background: color-mix(in srgb, var(--on-primary) 12%, var(--primary)); +} + +/* Обход уже активен — приглашаем выключить менее настойчивым, тональным цветом. */ +.toggle-button--on { + background: var(--secondary-container); + color: var(--on-secondary-container); +} + +.toggle-button--on:hover { + background: color-mix(in srgb, var(--on-secondary-container) 8%, var(--secondary-container)); +} + +.toggle-button--on:active { + background: color-mix(in srgb, var(--on-secondary-container) 12%, var(--secondary-container)); +} + +.toggle-button:disabled { + cursor: default; + opacity: 0.6; +} + +/* Чип статуса: точка + подпись, роль зависит от состояния (design.md, статусные цвета). */ +.chip { + display: inline-flex; + flex: 0 0 auto; + align-items: center; + gap: 6px; + padding: 4px 12px; + border-radius: var(--shape-pill); + font-size: 12px; + font-weight: 500; + white-space: nowrap; +} + +.chip::before { + content: ''; + width: 8px; + height: 8px; + border-radius: 50%; +} + +.chip--success { + background: var(--status-success-bg); + color: var(--status-success-fg); +} + +.chip--success::before { + background: var(--status-success-dot); +} + +.chip--neutral { + background: var(--status-neutral-bg); + color: var(--status-neutral-fg); +} + +.chip--neutral::before { + background: var(--status-neutral-dot); +} + +.chip--error { + background: var(--status-error-bg); + color: var(--status-error-fg); +} + +.chip--error::before { + background: var(--status-error-dot); +} + +/* Убираемая версия чипа — список сайтов (design.md: состояние hover/active тем же слоем). */ +.chip--removable { + gap: 8px; + background: var(--surface-container-high); + color: var(--on-surface-variant); +} + +.chip--removable::before { + content: none; +} + +.chip__remove { + display: grid; + place-items: center; + width: 16px; + height: 16px; + padding: 0; + border: none; + border-radius: 50%; + background: transparent; + color: inherit; + font-size: 10px; + line-height: 1; + cursor: pointer; +} + +.chip__remove:hover { + background: color-mix(in srgb, var(--on-surface) 12%, transparent); +} + +/* ---------- Настройки: стратегия + списки сайтов ---------- */ + +/* Прокрутка живёт здесь, а не на .content: списки сайтов и настройки длиннее окна, + а главный экран прокручиваться не должен. */ +.settings, +.list-editor { + display: flex; + flex-direction: column; + gap: 14px; + flex: 1 1 auto; + min-height: 0; + overflow-y: auto; + /* Полоса прокрутки не должна наезжать на карточки и кнопку удаления. */ + padding-right: 4px; + margin-right: -4px; +} + +.settings__back-row { + display: flex; + align-items: center; + gap: 10px; +} + +.settings__back { + padding: 4px 10px 4px 4px; + border: none; + border-radius: var(--shape-pill); + background: transparent; + color: var(--text-muted); + font: inherit; + cursor: pointer; +} + +.settings__back:hover { + color: var(--on-surface); +} + +.settings__title { + margin: 0; + font-size: 16px; + font-weight: 500; +} + +.settings__field { + display: flex; + flex-direction: column; + gap: 6px; +} + +.settings__label { + color: var(--text-muted); + font-size: 12px; + font-weight: 500; +} + +.select { + height: 40px; + padding: 0 12px; + border: 1px solid var(--outline); + border-radius: var(--shape-inner); + background: var(--surface-bright); + color: var(--on-surface); + font: inherit; +} + +.settings__group { + display: flex; + flex-direction: column; + gap: 2px; +} + +.settings-row { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 4px; + border: none; + border-top: 1px solid var(--outline-variant); + background: transparent; + color: var(--on-surface); + font: inherit; + text-align: left; + cursor: pointer; +} + +.settings-row:hover { + background: color-mix(in srgb, var(--on-surface) 4%, transparent); +} + +.settings-row--static { + cursor: default; +} + +.settings-row--static:hover { + background: transparent; +} + +.diagnostic__label { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; + padding-right: 12px; +} + +.diagnostic__error { + color: var(--text-muted); + font-size: 12px; + line-height: 1.3; +} + +.settings-row__count { + display: flex; + align-items: center; + gap: 8px; + color: var(--text-muted); + font-size: 13px; +} + +.settings-row__clear { + padding: 2px 10px; + border: 1px solid var(--outline); + border-radius: var(--shape-pill); + background: transparent; + color: var(--on-surface-variant); + font-size: 12px; + cursor: pointer; +} + +.settings-row__clear:hover { + background: color-mix(in srgb, var(--on-surface) 8%, transparent); +} + +.settings__support { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; +} + +.settings__support-link { + width: 100%; +} + +.settings__support-hint { + align-self: stretch; + margin: 0; + color: var(--text-muted); + font-size: 12px; + text-align: center; +} + +.settings__version { + margin: 0; + color: var(--text-muted); + font-size: 12px; + font-family: var(--font-mono); + text-align: left; +} + +.list-editor__hint { + margin: 0; + color: var(--text-muted); + font-size: 12px; +} + +.list-editor__chips { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.list-editor__empty { + margin: 0; + color: var(--text-muted); + font-size: 13px; +} + +.list-editor__add-row { + display: flex; + gap: 8px; +} + +.text-input { + flex: 1 1 auto; + min-width: 0; + height: 36px; + padding: 0 12px; + border: 1px solid var(--outline); + border-radius: var(--shape-inner); + background: var(--surface-bright); + color: var(--on-surface); + font: inherit; +} + +.action--outline { + flex: 0 0 auto; + height: 36px; + padding: 0 16px; + border: 1px solid var(--primary); + border-radius: var(--shape-pill); + background: transparent; + color: var(--primary); + font: inherit; + font-weight: 500; + cursor: pointer; +} + +.action--outline:hover { + background: color-mix(in srgb, var(--primary) 8%, transparent); +} + +/* .action--outline на вместо +

{LIST_TITLES[list.id]}

+ + +

{LIST_HINTS[list.id]}

+ +
+ {list.entries.length === 0 &&

Список пуст.

} + {list.entries.map((entry) => ( + + {entry} + + + ))} +
+ +
+ setDraft(event.target.value)} + onKeyDown={(event) => { + if (event.key === 'Enter') add() + }} + /> + +
+ + {error &&

{error}

} + + + + ) +} diff --git a/src/renderer/src/components/SettingsView.tsx b/src/renderer/src/components/SettingsView.tsx new file mode 100644 index 0000000..39a7637 --- /dev/null +++ b/src/renderer/src/components/SettingsView.tsx @@ -0,0 +1,137 @@ +import { useState } from 'react' +import type { ZapretListId, ZapretStatus } from '@shared/ipc-contract' +import { GITHUB_URL } from '@shared/links' +import { useZapretLists } from '../hooks/useZapretLists' +import { LIST_TITLES } from '../lib/zapretListMeta' +import ListEditor from './ListEditor' + +/** + * Настройки: списки сайтов + автозапуск (Windows). Выбор стратегии и проверка + * соединения переехали на главный экран (ZapretCard.tsx) — рядом с кнопкой + * включения, где ими и пользуются. Отдельный экран за шестерёнкой в шапке. + */ +const isWindows = window.api.platform === 'win32' +const APP_CHANNEL = 'beta' +// Держать в синхроне с "version" в package.json — оттуда её берёт electron-builder +// для имени установщика и для «Установленные приложения» в Windows. +const APP_VERSION = '0.4.0' + +export default function SettingsView({ + onBack, + status, + onSetAutoStart +}: { + onBack: () => void + status: ZapretStatus | null + onSetAutoStart: (enabled: boolean) => Promise +}): React.JSX.Element { + const { lists, autoHostlist, save, reset, clearAuto } = useZapretLists() + const [openListId, setOpenListId] = useState(null) + + const openList = lists.find((list) => list.id === openListId) + if (openList) { + return ( + setOpenListId(null)} + onSave={async (entries) => { + await save(openList.id, entries) + }} + onReset={async () => { + await reset(openList.id) + }} + /> + ) + } + + return ( +
+
+ +

Настройки

+
+ + {isWindows && ( +
+ Автозапуск + +

+ Меняется через запрос прав администратора. Если выключить обход кнопкой на главном + экране, при включённом автозапуске он вернётся не раньше следующей перезагрузки. +

+
+ )} + +
+ Сайты + + {lists.map((list) => ( + + ))} + + {/* На Windows ни одна из 22 стратегий Flowseal не использует --hostlist-auto — счётчик + всегда пуст, показывать его смысла нет. */} + {!isWindows && ( +
+ Найдено автоматически + + {autoHostlist.length} + {autoHostlist.length > 0 && ( + + )} + +
+ )} +
+ +
+

Поддержка

+ {/* Ссылка уходит в системный браузер: will-navigate в window.ts перехватывает + переход и отдаёт его openExternal с проверкой протокола. */} +
+ GitHub + +

Поддержите звёздочкой

+
+ +

+ {APP_CHANNEL}-{APP_VERSION}-{window.api.platform} +

+ + {isWindows && ( + + )} +
+ ) +} diff --git a/src/renderer/src/components/ZapretCard.tsx b/src/renderer/src/components/ZapretCard.tsx new file mode 100644 index 0000000..1200954 --- /dev/null +++ b/src/renderer/src/components/ZapretCard.tsx @@ -0,0 +1,103 @@ +import { useZapret } from '../hooks/useZapret' +import { useZapretDiagnostics } from '../hooks/useZapretDiagnostics' + +/** Карточка обхода блокировок: статус и включение сверху, выбор стратегии и проверка соединения ниже. */ +export default function ZapretCard(): React.JSX.Element { + const { status, strategies, start, stop, setStrategy, busy } = useZapret() + const { results, running: testing, run: runTest } = useZapretDiagnostics() + + const running = status?.state === 'running' || status?.state === 'starting' + const strategyId = status?.strategyId ?? strategies[0]?.id + + const toggle = (): void => { + if (!strategyId) return + void (running ? stop() : start(strategyId)) + } + + const chip = ((): { className: string; label: string } => { + switch (status?.state) { + case 'running': + return { className: 'chip--success', label: 'Активен' } + case 'starting': + case 'stopping': + return { className: 'chip--neutral', label: 'Переключение…' } + case 'error': + return { className: 'chip--error', label: 'Ошибка' } + default: + return { className: 'chip--neutral', label: 'Выключен' } + } + })() + + const toggleLabel = + status?.state === 'stopping' ? 'Выключение…' : running ? 'Выключить' : 'Включить' + + return ( +
+
+

Обход блокировок

+ {chip.label} +
+ + + + {status?.state === 'error' && status.error && ( +

{status.error}

+ )} + +
+ Стратегия + {/* На время переключения список заблокирован: смена стратегии на работающем + обходе останавливает и перенастраивает службу, и второй выбор посреди этого + наложился бы на первый. */} + +
+ + + + {results.length > 0 && ( +
+ {results.map((result) => ( +
+ {/* Причину показываем текстом, а не только подсказкой при наведении: + «Недоступно» само по себе не говорит, что чинить. */} + + {result.label} + {!result.ok && result.error && ( + {result.error} + )} + + + {result.ok ? `${result.ms} мс` : 'Недоступно'} + +
+ ))} +
+ )} +
+ ) +} diff --git a/src/renderer/src/env.d.ts b/src/renderer/src/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/renderer/src/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/src/renderer/src/hooks/useTheme.ts b/src/renderer/src/hooks/useTheme.ts new file mode 100644 index 0000000..12bff64 --- /dev/null +++ b/src/renderer/src/hooks/useTheme.ts @@ -0,0 +1,18 @@ +import { useEffect } from 'react' + +/** + * Синхронизирует окно с системной темой: следит за `nativeTheme` main-процесса + * и вешает класс `.dark` на . Ручного переключателя нет — приложение + * всегда следует настройке ОС. + */ +export function useTheme(): void { + useEffect(() => { + void window.api.getTheme().then((theme) => { + document.documentElement.classList.toggle('dark', theme.shouldUseDarkColors) + }) + + return window.api.onThemeChanged((theme) => { + document.documentElement.classList.toggle('dark', theme.shouldUseDarkColors) + }) + }, []) +} diff --git a/src/renderer/src/hooks/useZapret.ts b/src/renderer/src/hooks/useZapret.ts new file mode 100644 index 0000000..f6f5cd5 --- /dev/null +++ b/src/renderer/src/hooks/useZapret.ts @@ -0,0 +1,80 @@ +import { useCallback, useEffect, useState } from 'react' +import type { ZapretLogLine, ZapretStatus, ZapretStrategy } from '@shared/ipc-contract' + +const LOG_LIMIT = 50 + +/** + * Статус обхода блокировок + список пресетов, в синхроне с main-процессом. + * Тот же паттерн, что и `useTheme`: получить текущее значение, подписаться + * на изменения, вернуть функцию отписки из useEffect. + */ +export function useZapret(): { + status: ZapretStatus | null + strategies: ZapretStrategy[] + log: ZapretLogLine[] + start: (strategyId: string) => Promise + stop: () => Promise + setSystemProxy: (enabled: boolean) => Promise + setStrategy: (strategyId: string) => Promise + setAutoStart: (enabled: boolean) => Promise + busy: boolean +} { + const [status, setStatus] = useState(null) + const [strategies, setStrategies] = useState([]) + const [log, setLog] = useState([]) + const [busy, setBusy] = useState(false) + + useEffect(() => { + void window.api.getZapretStatus().then(setStatus) + void window.api.getZapretStrategies().then(setStrategies) + return window.api.onZapretStatusChanged(setStatus) + }, []) + + useEffect(() => { + return window.api.onZapretLog((entry) => { + setLog((previous) => [...previous, entry].slice(-LOG_LIMIT)) + }) + }, []) + + const start = useCallback(async (strategyId: string) => { + setBusy(true) + try { + setStatus(await window.api.startZapret(strategyId)) + } finally { + setBusy(false) + } + }, []) + + const stop = useCallback(async () => { + setBusy(true) + try { + setStatus(await window.api.stopZapret()) + } finally { + setBusy(false) + } + }, []) + + const setSystemProxy = useCallback(async (enabled: boolean) => { + setStatus(await window.api.setZapretSystemProxy(enabled)) + }, []) + + const setStrategy = useCallback(async (strategyId: string) => { + setBusy(true) + try { + setStatus(await window.api.setZapretStrategy(strategyId)) + } finally { + setBusy(false) + } + }, []) + + const setAutoStart = useCallback(async (enabled: boolean) => { + setBusy(true) + try { + setStatus(await window.api.setZapretAutoStart(enabled)) + } finally { + setBusy(false) + } + }, []) + + return { status, strategies, log, start, stop, setSystemProxy, setStrategy, setAutoStart, busy } +} diff --git a/src/renderer/src/hooks/useZapretDiagnostics.ts b/src/renderer/src/hooks/useZapretDiagnostics.ts new file mode 100644 index 0000000..84fc089 --- /dev/null +++ b/src/renderer/src/hooks/useZapretDiagnostics.ts @@ -0,0 +1,23 @@ +import { useCallback, useState } from 'react' +import type { ZapretDiagnosticResult } from '@shared/ipc-contract' + +/** Проверка соединения по клику — не авто-подписка, как в useZapret/useZapretLists. */ +export function useZapretDiagnostics(): { + results: ZapretDiagnosticResult[] + running: boolean + run: () => Promise +} { + const [results, setResults] = useState([]) + const [running, setRunning] = useState(false) + + const run = useCallback(async () => { + setRunning(true) + try { + setResults(await window.api.runZapretDiagnostics()) + } finally { + setRunning(false) + } + }, []) + + return { results, running, run } +} diff --git a/src/renderer/src/hooks/useZapretLists.ts b/src/renderer/src/hooks/useZapretLists.ts new file mode 100644 index 0000000..ea91864 --- /dev/null +++ b/src/renderer/src/hooks/useZapretLists.ts @@ -0,0 +1,48 @@ +import { useCallback, useEffect, useState } from 'react' +import type { ZapretList, ZapretListId } from '@shared/ipc-contract' + +/** + * Списки сайтов + журнал автообнаружения, в синхроне с main-процессом. + * Тот же паттерн, что и `useZapret`/`useTheme`. + */ +export function useZapretLists(): { + lists: ZapretList[] + autoHostlist: string[] + save: (id: ZapretListId, entries: string[]) => Promise + reset: (id: ZapretListId) => Promise + clearAuto: () => Promise + loading: boolean +} { + const [lists, setLists] = useState([]) + const [autoHostlist, setAutoHostlist] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + void Promise.all([window.api.getZapretLists(), window.api.getZapretAutoHostlist()]).then( + ([nextLists, nextAuto]) => { + setLists(nextLists) + setAutoHostlist(nextAuto) + setLoading(false) + } + ) + }, []) + + const save = useCallback(async (id: ZapretListId, entries: string[]) => { + const updated = await window.api.saveZapretList(id, entries) + setLists((previous) => previous.map((list) => (list.id === id ? updated : list))) + return updated + }, []) + + const reset = useCallback(async (id: ZapretListId) => { + const updated = await window.api.resetZapretList(id) + setLists((previous) => previous.map((list) => (list.id === id ? updated : list))) + return updated + }, []) + + const clearAuto = useCallback(async () => { + await window.api.clearZapretAutoHostlist() + setAutoHostlist([]) + }, []) + + return { lists, autoHostlist, save, reset, clearAuto, loading } +} diff --git a/src/renderer/src/lib/zapretListMeta.ts b/src/renderer/src/lib/zapretListMeta.ts new file mode 100644 index 0000000..e3c4cb6 --- /dev/null +++ b/src/renderer/src/lib/zapretListMeta.ts @@ -0,0 +1,19 @@ +import type { ZapretListId } from '@shared/ipc-contract' + +export const LIST_TITLES: Record = { + general: 'Обходить', + google: 'YouTube / Google', + exclude: 'Не трогать', + 'ipset-exclude': 'Исключённые подсети', + 'ipset-all': 'Подсети (IP)' +} + +export const LIST_HINTS: Record = { + general: 'Обход применяется только к этим сайтам.', + google: 'Отдельный список — YouTube и Google обрабатываются иначе, чем остальные сайты.', + exclude: 'Эти сайты обход не трогает никогда — банки и похожие сервисы стоит оставить здесь.', + 'ipset-exclude': 'Подсети, которые обход тоже не трогает (локальная сеть и т. п.).', + 'ipset-all': + 'Windows: адреса, к которым обход применяется по IP, а не по имени сайта — на случай, ' + + 'если один хост прячет за собой сразу несколько доменов.' +} diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx new file mode 100644 index 0000000..685cd94 --- /dev/null +++ b/src/renderer/src/main.tsx @@ -0,0 +1,13 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import App from './App' +import './assets/main.css' + +const container = document.getElementById('root') +if (!container) throw new Error('Не найден #root в index.html') + +createRoot(container).render( + + + +) diff --git a/src/shared/ipc-contract.ts b/src/shared/ipc-contract.ts new file mode 100644 index 0000000..947e868 --- /dev/null +++ b/src/shared/ipc-contract.ts @@ -0,0 +1,140 @@ +/** + * Единый контракт общения main ↔ renderer. + * + * Файл импортируется всеми тремя процессами (main, preload, renderer), поэтому + * здесь не должно быть импортов из `electron` или из DOM — только типы и константы. + * + * Чтобы добавить новый канал: + * 1. описать его в `IpcHandlers` (аргументы + результат); + * 2. зарегистрировать обработчик в `src/main/ipc.ts` (TS не даст ошибиться в типах); + * 3. пробросить метод наружу в `src/preload/index.ts`. + */ + +export type ThemeSource = 'system' | 'light' | 'dark' + +export interface ThemeState { + /** Что выбрал пользователь. */ + source: ThemeSource + /** Что реально показывать сейчас (учитывает системную тему). */ + shouldUseDarkColors: boolean +} + +/** Состояние обхода блокировок. */ +export type ZapretState = 'stopped' | 'starting' | 'running' | 'stopping' | 'error' + +/** + * Чем именно обходим на этой платформе: + * • `tpws-socks` — macOS: локальный socks5-прокси, права не нужны; + * • `winws-service` — Windows: служба с драйвером WinDivert; + * • `unsupported` — платформа без поддержки (Linux у нас не собран). + */ +export type ZapretBackend = 'tpws-socks' | 'winws-service' | 'unsupported' + +export interface ZapretStatus { + state: ZapretState + backend: ZapretBackend + /** Выбранный пресет из `zapret:strategies`. */ + strategyId: string + /** Когда обход включён, ISO-8601. */ + startedAt: string | null + /** Текст последней ошибки — показываем пользователю как есть. */ + error: string | null + /** macOS: адрес socks5-прокси, пока обход работает. */ + socksAddress: string | null + /** macOS: прописан ли прокси в системных настройках сети. */ + systemProxyApplied: boolean + /** Windows: установлена ли служба (её ставит и настраивает само приложение при первом включении). */ + serviceInstalled: boolean + /** Windows: стоит ли служба на автозапуск с системой (`sc config start=`). */ + autoStart: boolean +} + +/** Готовый набор параметров обхода. Подбирается под провайдера. */ +export interface ZapretStrategy { + id: string + name: string + description: string + platforms: NodeJS.Platform[] +} + +export interface ZapretLogLine { + level: 'info' | 'error' + line: string +} + +/** + * Списки сайтов, применяемые обоими движками (см. `src/main/zapret/lists.ts`). + * Встроенное содержимое берётся из Flowseal/zapret-discord-youtube (MIT) — пользователь + * может редактировать любой список полностью, включая встроенные. + */ +export type ZapretListId = 'general' | 'google' | 'exclude' | 'ipset-exclude' | 'ipset-all' + +export interface ZapretList { + id: ZapretListId + /** Домены (general/google/exclude) или IP/CIDR (ipset-exclude/ipset-all), уже без пустых строк и комментариев. */ + entries: string[] + /** Совпадает ли содержимое с текущей встроенной версией — пользователь ничего не менял. */ + isDefault: boolean +} + +/** + * Результат проверки соединения (`src/main/zapret/diagnostics.ts`): успех — это успешный + * TLS-хендшейк до хоста через текущий бэкенд обхода, не полноценный HTTP-запрос. + */ +export interface ZapretDiagnosticResult { + id: string + label: string + host: string + ok: boolean + ms: number | null + error: string | null +} + +/** + * Каналы вида renderer → main → ответ (`ipcRenderer.invoke` / `ipcMain.handle`). + * Ключ — имя канала, значение — сигнатура обработчика. + */ +export interface IpcHandlers { + 'theme:get': () => ThemeState + + /** Windows: подтверждает диалогом ОС, запускает деинсталлятор и закрывает приложение. */ + 'app:uninstall': () => void + + 'zapret:status': () => ZapretStatus + 'zapret:strategies': () => ZapretStrategy[] + 'zapret:start': (strategyId: string) => ZapretStatus + 'zapret:stop': () => ZapretStatus + /** macOS: прописать/убрать socks-прокси в системных настройках сети. */ + 'zapret:set-system-proxy': (enabled: boolean) => ZapretStatus + /** + * Выбор пресета. Если обход выключен — просто запоминается. Если включён — на macOS + * перезапускает tpws, на Windows перенастраивает службу (потребуется UAC). + */ + 'zapret:set-strategy': (strategyId: string) => ZapretStatus + /** Windows: автозапуск службы с системой (`sc config start=`); требует UAC. */ + 'zapret:set-autostart': (enabled: boolean) => ZapretStatus + + 'zapret:lists': () => ZapretList[] + 'zapret:list-save': (id: ZapretListId, entries: string[]) => ZapretList + 'zapret:list-reset': (id: ZapretListId) => ZapretList + /** Список, который zapret пополняет сам через --hostlist-auto (только macOS-профили). */ + 'zapret:auto-hostlist': () => string[] + 'zapret:auto-hostlist-clear': () => void + + /** Проверка соединения: успешный TLS-хендшейк до нескольких сайтов через текущий бэкенд. */ + 'zapret:diagnose': () => ZapretDiagnosticResult[] +} + +export type IpcChannel = keyof IpcHandlers + +/** Каналы вида main → renderer (`webContents.send` / `ipcRenderer.on`). */ +export interface IpcEvents { + 'theme:changed': ThemeState + 'zapret:status-changed': ZapretStatus + 'zapret:log': ZapretLogLine +} + +export type IpcEventChannel = keyof IpcEvents + +/** Разрешённые протоколы для `shell.openExternal` — всё остальное main отклоняет. */ +export const ALLOWED_EXTERNAL_PROTOCOLS = ['https:', 'mailto:'] as const diff --git a/src/shared/links.ts b/src/shared/links.ts new file mode 100644 index 0000000..3cb6f36 --- /dev/null +++ b/src/shared/links.ts @@ -0,0 +1,9 @@ +/** + * Адрес репозитория — одно место на всё приложение. Отсюда его берут и кнопка «GitHub» + * в настройках (`SettingsView.tsx`), и приветственное окно при первом запуске + * (`src/main/first-run.ts`). + * + * TODO: заглушка. Пока ведёт на главную GitHub — заменить на настоящий адрес репозитория, + * когда он появится. Менять только здесь, оба места подхватят автоматически. + */ +export const GITHUB_URL = 'https://github.com' diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..31bac6e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,4 @@ +{ + "files": [], + "references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.web.json" }] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..e38eb6f --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,13 @@ +{ + "extends": "@electron-toolkit/tsconfig/tsconfig.node.json", + "include": [ + "electron.vite.config.*", + "src/main/**/*", + "src/preload/**/*", + "src/shared/**/*" + ], + "compilerOptions": { + "composite": true, + "types": ["electron-vite/node"] + } +} diff --git a/tsconfig.web.json b/tsconfig.web.json new file mode 100644 index 0000000..c537dda --- /dev/null +++ b/tsconfig.web.json @@ -0,0 +1,18 @@ +{ + "extends": "@electron-toolkit/tsconfig/tsconfig.web.json", + "include": [ + "src/renderer/src/env.d.ts", + "src/renderer/src/**/*", + "src/preload/*.d.ts", + "src/shared/**/*" + ], + "compilerOptions": { + "composite": true, + "jsx": "react-jsx", + "baseUrl": ".", + "paths": { + "@renderer/*": ["src/renderer/src/*"], + "@shared/*": ["src/shared/*"] + } + } +}