From 087ea6f2b84be9927327d632fef548c130070453 Mon Sep 17 00:00:00 2001 From: konard Date: Mon, 27 Apr 2026 23:51:41 +0000 Subject: [PATCH 1/4] Initial commit with task details Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: https://github.com/xlabtg/Teleton-Client/issues/135 --- .gitkeep | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitkeep b/.gitkeep index 27c1d38..53b0383 100644 --- a/.gitkeep +++ b/.gitkeep @@ -1 +1,2 @@ -# .gitkeep file auto-generated at 2026-04-27T00:02:26.981Z for PR creation at branch issue-27-e169ead3bf4d for issue https://github.com/xlabtg/Teleton-Client/issues/27 \ No newline at end of file +# .gitkeep file auto-generated at 2026-04-27T00:02:26.981Z for PR creation at branch issue-27-e169ead3bf4d for issue https://github.com/xlabtg/Teleton-Client/issues/27 +# Updated: 2026-04-27T23:51:41.022Z \ No newline at end of file From 6272d910029e6cfce70ccb0a015765a8c694bd5b Mon Sep 17 00:00:00 2001 From: konard Date: Tue, 28 Apr 2026 00:16:19 +0000 Subject: [PATCH 2/4] feat(web): implement alpha client --- .gitignore | 5 + README.md | 3 +- docs/screenshots/web-alpha-auth.png | Bin 0 -> 50434 bytes .../screenshots/web-alpha-settings-mobile.png | Bin 0 -> 33006 bytes test/web-alpha-release.test.mjs | 93 + web/.env.example | 6 + web/README.md | 67 + web/deploy.sh | 13 + web/index.html | 15 + web/package-lock.json | 3018 +++++++++++++++++ web/package.json | 38 + web/postcss.config.js | 6 + web/public/icons/icon-192.png | Bin 0 -> 2383 bytes web/public/icons/icon-512.png | Bin 0 -> 8551 bytes web/public/manifest.webmanifest | 54 + web/public/offline.html | 44 + web/public/service-worker.js | 45 + web/public/tonconnect-manifest.json | 5 + web/scripts/copy-tdweb-assets.mjs | 19 + web/scripts/generate-icons.mjs | 137 + web/src/app/App.tsx | 151 + web/src/features/agent/AgentPanel.tsx | 147 + web/src/features/auth/AuthScreen.tsx | 162 + web/src/features/chat/ChatScreen.tsx | 40 + web/src/features/settings/SettingsScreen.tsx | 130 + web/src/features/ton/TonPanel.tsx | 118 + web/src/index.css | 45 + web/src/main.tsx | 21 + web/src/services/agent.service.ts | 237 ++ web/src/services/crypto.service.ts | 164 + web/src/services/proxy.service.ts | 96 + web/src/services/tdlib.service.ts | 300 ++ web/src/shared/pwa/registerServiceWorker.ts | 11 + web/src/shared/store/useTeletonStore.ts | 370 ++ web/src/shared/types.ts | 77 + web/src/vite-env.d.ts | 22 + web/src/widgets/ChatList.tsx | 38 + web/src/widgets/InputBar.tsx | 41 + web/src/widgets/MessageWindow.tsx | 40 + web/tailwind.config.ts | 22 + web/tests/services.test.ts | 83 + web/tsconfig.json | 21 + web/vite.config.ts | 41 + 43 files changed, 5944 insertions(+), 1 deletion(-) create mode 100644 docs/screenshots/web-alpha-auth.png create mode 100644 docs/screenshots/web-alpha-settings-mobile.png create mode 100644 test/web-alpha-release.test.mjs create mode 100644 web/.env.example create mode 100644 web/README.md create mode 100755 web/deploy.sh create mode 100644 web/index.html create mode 100644 web/package-lock.json create mode 100644 web/package.json create mode 100644 web/postcss.config.js create mode 100644 web/public/icons/icon-192.png create mode 100644 web/public/icons/icon-512.png create mode 100644 web/public/manifest.webmanifest create mode 100644 web/public/offline.html create mode 100644 web/public/service-worker.js create mode 100644 web/public/tonconnect-manifest.json create mode 100644 web/scripts/copy-tdweb-assets.mjs create mode 100644 web/scripts/generate-icons.mjs create mode 100644 web/src/app/App.tsx create mode 100644 web/src/features/agent/AgentPanel.tsx create mode 100644 web/src/features/auth/AuthScreen.tsx create mode 100644 web/src/features/chat/ChatScreen.tsx create mode 100644 web/src/features/settings/SettingsScreen.tsx create mode 100644 web/src/features/ton/TonPanel.tsx create mode 100644 web/src/index.css create mode 100644 web/src/main.tsx create mode 100644 web/src/services/agent.service.ts create mode 100644 web/src/services/crypto.service.ts create mode 100644 web/src/services/proxy.service.ts create mode 100644 web/src/services/tdlib.service.ts create mode 100644 web/src/shared/pwa/registerServiceWorker.ts create mode 100644 web/src/shared/store/useTeletonStore.ts create mode 100644 web/src/shared/types.ts create mode 100644 web/src/vite-env.d.ts create mode 100644 web/src/widgets/ChatList.tsx create mode 100644 web/src/widgets/InputBar.tsx create mode 100644 web/src/widgets/MessageWindow.tsx create mode 100644 web/tailwind.config.ts create mode 100644 web/tests/services.test.ts create mode 100644 web/tsconfig.json create mode 100644 web/vite.config.ts diff --git a/.gitignore b/.gitignore index 11a499a..06c378b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,14 @@ node_modules/ coverage/ dist/ +*.tsbuildinfo ci-logs/ *.log .DS_Store .env .env.* !.env.example +web/public/tdweb.js +web/public/*.worker.js +web/public/*.wasm +web/public/*.mem diff --git a/README.md b/README.md index 6b4bef9..f4daabb 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ This repository is in the foundation phase. The current implementation establish - Desktop wrapper contract for the Electron stack, runnable debug artifact metadata for Linux, macOS, and Windows, tray menu behavior, system notifications, shortcuts, autostart, protocol routing, and DMG/EXE/AppImage packaging targets. - Responsive tablet layout contract for chats, settings, agent, and wallet views, including shared breakpoints, portrait and landscape navigation modes, split-pane frames, and narrow-tablet sheet fallback behavior. - PWA wrapper contract for the web app manifest, service worker strategy, offline shell validation, update behavior, installability metadata, and browser fallbacks for unsupported native capabilities. +- Alpha Web client in `web/` with React 18, Vite, TypeScript, TailwindCSS, TDWeb service bindings, encrypted opt-in settings/session storage, proxy configuration, Teleton Agent controls, and TON operation panels. - Local Teleton Agent runtime supervisor contract with mock lifecycle tests for start, stop, health, resource monitoring, and logs. - Teleton Agent action notification contract for proposals, starts, completions, approval-required states, and failures with settings-aware delivery and redacted lock-screen text. - Teleton Agent action history contract for redacted action records, retention filtering, rollback eligibility, and irreversible action markers. @@ -81,7 +82,7 @@ The project is intended to evolve through these layers: 4. TON blockchain integrations for wallet, transfers, swaps, NFTs, staking, and DNS. 5. Security and privacy controls for credentials, user consent, and auditability. -See `SECURITY.md`, `docs/architecture.md`, `docs/backlog.md`, `docs/tdlib-adapter.md`, `docs/android-wrapper.md`, `docs/ios-wrapper.md`, `docs/desktop-wrapper.md`, `docs/tablet-layout.md`, `docs/web-pwa-wrapper.md`, `docs/security-audit.md`, `docs/license-matrix.md`, `docs/release-strategy.md`, `docs/release-packaging.md`, and `docs/release-readiness.md` for the current foundation plan. The agent settings, local runtime, input action map, Android wrapper, iOS wrapper, desktop wrapper, tablet layout, PWA, message database encryption, secure data deletion, security policy, security audit, license matrix, release packaging, and release readiness sections record the shared settings UI contract, supported runtime directions, platform execution boundaries, shortcut and gesture behavior, hardware security key capability checks, responsive tablet behavior, web installability behavior, vulnerability reporting expectations, credential rotation expectations, secure storage review requirements, upstream license obligations, source publication review, protected signing boundaries, human release approval, and remaining native packaging implementation gaps for Android, iOS, desktop, and web wrappers. +See `SECURITY.md`, `docs/architecture.md`, `docs/backlog.md`, `docs/tdlib-adapter.md`, `docs/android-wrapper.md`, `docs/ios-wrapper.md`, `docs/desktop-wrapper.md`, `docs/tablet-layout.md`, `docs/web-pwa-wrapper.md`, `web/README.md`, `docs/security-audit.md`, `docs/license-matrix.md`, `docs/release-strategy.md`, `docs/release-packaging.md`, and `docs/release-readiness.md` for the current foundation plan and alpha web client. The agent settings, local runtime, input action map, Android wrapper, iOS wrapper, desktop wrapper, tablet layout, PWA, message database encryption, secure data deletion, security policy, security audit, license matrix, release packaging, and release readiness sections record the shared settings UI contract, supported runtime directions, platform execution boundaries, shortcut and gesture behavior, hardware security key capability checks, responsive tablet behavior, web installability behavior, vulnerability reporting expectations, credential rotation expectations, secure storage review requirements, upstream license obligations, source publication review, protected signing boundaries, human release approval, and remaining native packaging implementation gaps for Android, iOS, desktop, and web wrappers. ## Contribution Templates diff --git a/docs/screenshots/web-alpha-auth.png b/docs/screenshots/web-alpha-auth.png new file mode 100644 index 0000000000000000000000000000000000000000..aff58b452f5657328f3424464440365ee618ce9b GIT binary patch literal 50434 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 literal 0 HcmV?d00001 diff --git a/docs/screenshots/web-alpha-settings-mobile.png b/docs/screenshots/web-alpha-settings-mobile.png new file mode 100644 index 0000000000000000000000000000000000000000..747d81bb5c236ed5bfc5ec16035f71238791be36 GIT binary patch literal 33006 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
*z4LumF{R05 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 AMgRZ+ literal 0 HcmV?d00001 diff --git a/test/web-alpha-release.test.mjs b/test/web-alpha-release.test.mjs new file mode 100644 index 0000000..7277dfb --- /dev/null +++ b/test/web-alpha-release.test.mjs @@ -0,0 +1,93 @@ +import assert from 'node:assert/strict'; +import { existsSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; +import { test } from 'node:test'; + +const root = new URL('../', import.meta.url); + +function pathFor(relativePath) { + return new URL(relativePath, root); +} + +async function readJson(relativePath) { + return JSON.parse(await readFile(pathFor(relativePath), 'utf8')); +} + +test('alpha web client release project is scaffolded with the required runtime contracts', async () => { + const requiredFiles = [ + 'web/package.json', + 'web/index.html', + 'web/vite.config.ts', + 'web/.env.example', + 'web/README.md', + 'web/deploy.sh', + 'web/public/manifest.webmanifest', + 'web/public/service-worker.js', + 'web/public/offline.html', + 'web/public/icons/icon-192.png', + 'web/public/icons/icon-512.png', + 'web/src/app/App.tsx', + 'web/src/services/tdlib.service.ts', + 'web/src/services/agent.service.ts', + 'web/src/services/proxy.service.ts', + 'web/src/services/crypto.service.ts', + 'web/src/shared/store/useTeletonStore.ts', + 'web/src/widgets/ChatList.tsx', + 'web/src/widgets/MessageWindow.tsx', + 'web/src/widgets/InputBar.tsx', + 'web/tests/services.test.ts' + ]; + + for (const requiredFile of requiredFiles) { + assert.equal(existsSync(pathFor(requiredFile)), true, `${requiredFile} is required for the alpha web release`); + } + + const packageJson = await readJson('web/package.json'); + assert.equal(packageJson.scripts.dev, 'vite --host 0.0.0.0'); + assert.equal(packageJson.scripts.build, 'tsc -b && vite build'); + assert.equal(packageJson.scripts.test, 'vitest run'); + + for (const dependency of ['@tonconnect/ui-react', 'react', 'react-router-dom', 'tdweb', 'zustand']) { + assert.ok(packageJson.dependencies[dependency], `web/package.json must depend on ${dependency}`); + } + + assert.ok(packageJson.devDependencies.tailwindcss, 'web/package.json must configure TailwindCSS'); + + const envExample = await readFile(pathFor('web/.env.example'), 'utf8'); + assert.match(envExample, /VITE_TELEGRAM_API_ID=/); + assert.match(envExample, /VITE_TELEGRAM_API_HASH=/); + assert.match(envExample, /VITE_TELETON_AGENT_WS_URL=ws:\/\/localhost:8765/); + assert.match(envExample, /VITE_TELETON_AGENT_MANAGEMENT_URL=https:\/\/localhost:7778/); + + const tdlibService = await readFile(pathFor('web/src/services/tdlib.service.ts'), 'utf8'); + assert.match(tdlibService, /authPhone/); + assert.match(tdlibService, /authCode/); + assert.match(tdlibService, /getChats/); + assert.match(tdlibService, /sendMessage/); + assert.match(tdlibService, /setProxy/); + assert.match(tdlibService, /tdweb/); + assert.match(tdlibService, /loadTdClientConstructor/); + + const agentService = await readFile(pathFor('web/src/services/agent.service.ts'), 'utf8'); + assert.match(agentService, /jsonrpc: '2\.0'/); + assert.match(agentService, /agent\.enable/); + assert.match(agentService, /agent\.disable/); + assert.match(agentService, /ton\.getBalance/); + assert.match(agentService, /ton\.sendTx/); + assert.match(agentService, /\/v1\/agent\/status/); + + const cryptoService = await readFile(pathFor('web/src/services/crypto.service.ts'), 'utf8'); + assert.match(cryptoService, /AES-GCM/); + assert.match(cryptoService, /indexedDB/); + assert.match(cryptoService, /persistEncryptedSession/); + assert.match(cryptoService, /consent !== true/); + + const store = await readFile(pathFor('web/src/shared/store/useTeletonStore.ts'), 'utf8'); + assert.doesNotMatch(store, /localStorage\.setItem\([^)]*session/i, 'Telegram sessions must not be persisted to localStorage'); + + const webReadme = await readFile(pathFor('web/README.md'), 'utf8'); + assert.match(webReadme, /npm install/); + assert.match(webReadme, /npm run build/); + assert.match(webReadme, /teleton-agent/i); + assert.match(webReadme, /encrypted/i); +}); diff --git a/web/.env.example b/web/.env.example new file mode 100644 index 0000000..6a0e713 --- /dev/null +++ b/web/.env.example @@ -0,0 +1,6 @@ +VITE_TELEGRAM_API_ID= +VITE_TELEGRAM_API_HASH= +VITE_TDLIB_LOG_VERBOSITY=2 +VITE_TELETON_AGENT_WS_URL=ws://localhost:8765 +VITE_TELETON_AGENT_MANAGEMENT_URL=https://localhost:7778 +VITE_TONCONNECT_MANIFEST_URL=/tonconnect-manifest.json diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000..51c1cb5 --- /dev/null +++ b/web/README.md @@ -0,0 +1,67 @@ +# Teleton Client Web Alpha + +This directory contains the React 18 + Vite web client for issue `#135`. + +## Start + +```sh +npm install +cp .env.example .env +npm run dev +``` + +Open `http://localhost:5173/app/`. + +## Required Environment + +Set Telegram API credentials before using live TDLib authentication: + +```sh +VITE_TELEGRAM_API_ID=123456 +VITE_TELEGRAM_API_HASH=your_hash +``` + +The default Teleton Agent endpoints are: + +```sh +VITE_TELETON_AGENT_WS_URL=ws://localhost:8765 +VITE_TELETON_AGENT_MANAGEMENT_URL=https://localhost:7778 +``` + +The WebSocket endpoint implements the issue contract with JSON-RPC 2.0 methods: + +- `agent.enable({ session, userId })` +- `agent.disable()` +- `ton.getBalance({ address })` +- `ton.sendTx({ to, amount, comment })` + +The Management API status button calls the current `teleton-agent` HTTPS API at `/v1/agent/status` with an optional `tltn_` Bearer key. + +## TDWeb Assets + +`npm install` runs `scripts/copy-tdweb-assets.mjs`, copying `node_modules/tdweb/dist/*` into `web/public/`. +Those generated WASM, worker, and memory files are intentionally not committed because they are large upstream runtime artifacts. + +## Storage + +Telegram session data is runtime-only by default. The sign-in screen can persist an encrypted session marker only after explicit user consent. + +Proxy and agent settings are saved as AES-256-GCM envelopes in `localStorage`; the non-extractable browser key is stored in IndexedDB. Raw Telegram API hashes, proxy secrets, and agent API keys must stay out of source control. + +## Checks + +```sh +npm run test +npm run build +npm run preview +``` + +`npm audit --omit=dev` currently reports a moderate `tdweb -> uuid` advisory with no upstream fix available in `tdweb@1.8.0`. Keep that dependency under release review before publishing the alpha. + +## Deploy + +```sh +./deploy.sh +``` + +The script builds the app and uses `vercel` or `wrangler` when either CLI is installed. diff --git a/web/deploy.sh b/web/deploy.sh new file mode 100755 index 0000000..e081587 --- /dev/null +++ b/web/deploy.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env sh +set -eu + +npm install +npm run build + +if command -v vercel >/dev/null 2>&1; then + vercel deploy --prod dist +elif command -v wrangler >/dev/null 2>&1; then + wrangler pages deploy dist --project-name teleton-client-web +else + printf '%s\n' "Build is ready in web/dist. Install vercel or wrangler to deploy from this script." +fi diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..fca5af5 --- /dev/null +++ b/web/index.html @@ -0,0 +1,15 @@ + + + + + + + + + Teleton Client + + +
+ + + diff --git a/web/package-lock.json b/web/package-lock.json new file mode 100644 index 0000000..7024100 --- /dev/null +++ b/web/package-lock.json @@ -0,0 +1,3018 @@ +{ + "name": "teleton-client-web", + "version": "0.1.0-alpha.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "teleton-client-web", + "version": "0.1.0-alpha.0", + "hasInstallScript": true, + "dependencies": { + "@tonconnect/ui-react": "^2.4.4", + "lucide-react": "^1.11.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^7.14.2", + "tdweb": "^1.8.0", + "zustand": "^5.0.12" + }, + "devDependencies": { + "@types/react": "^18.3.27", + "@types/react-dom": "^18.3.7", + "@vitejs/plugin-react": "^6.0.1", + "autoprefixer": "^10.4.23", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.19", + "typescript": "^5.9.3", + "vite": "^8.0.10", + "vitest": "^4.1.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.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/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.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "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/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", + "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", + "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", + "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", + "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", + "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", + "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", + "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", + "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", + "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.7", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz", + "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tonconnect/isomorphic-eventsource": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@tonconnect/isomorphic-eventsource/-/isomorphic-eventsource-0.0.2.tgz", + "integrity": "sha512-B4UoIjPi0QkvIzZH5fV3BQLWrqSYABdrzZQSI9sJA9aA+iC0ohOzFwVVGXanlxeDAy1bcvPbb29f6sVUk0UnnQ==", + "license": "Apache-2.0", + "dependencies": { + "eventsource": "^2.0.2" + } + }, + "node_modules/@tonconnect/isomorphic-fetch": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@tonconnect/isomorphic-fetch/-/isomorphic-fetch-0.0.3.tgz", + "integrity": "sha512-jIg5nTrDwnite4fXao3dD83eCpTvInTjZon/rZZrIftIegh4XxyVb5G2mpMqXrVGk1e8SVXm3Kj5OtfMplQs0w==", + "license": "Apache-2.0", + "dependencies": { + "node-fetch": "^2.6.9" + } + }, + "node_modules/@tonconnect/protocol": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@tonconnect/protocol/-/protocol-2.4.0.tgz", + "integrity": "sha512-3xg6sMWIrSgW7/f7iPgOb2BI3LaMScjDMqfu20fcEMbOVvNFk3TGAUKK5cJ3pfvUINsyLgPoylcIbPap37jXiA==", + "license": "Apache-2.0", + "dependencies": { + "tweetnacl": "^1.0.3", + "tweetnacl-util": "^0.15.1" + } + }, + "node_modules/@tonconnect/sdk": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@tonconnect/sdk/-/sdk-3.4.1.tgz", + "integrity": "sha512-eVH8erAFout89gXYHXua/es+mmLPiW1r7ng9hgHKYQv85HLq8/zzQEkbJpbTlnIuQ6CJ/QKchjMsIgYz3BYCUQ==", + "license": "Apache-2.0", + "dependencies": { + "@tonconnect/isomorphic-eventsource": "0.0.2", + "@tonconnect/isomorphic-fetch": "0.0.3", + "@tonconnect/protocol": "2.4.0" + } + }, + "node_modules/@tonconnect/ui": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@tonconnect/ui/-/ui-2.4.4.tgz", + "integrity": "sha512-XgIHAkdimDk+YeRDPsQp7eOz2lM4aScYL3hiTP1Z9YgWRVROjEqTbw5ZP9pt6rgmYTzL26QpqhI6uNTFdYNFIA==", + "license": "Apache-2.0", + "dependencies": { + "@tonconnect/sdk": "3.4.1", + "classnames": "^2.5.1", + "csstype": "^3.1.3", + "deepmerge": "^4.3.1", + "ua-parser-js": "^1.0.35" + } + }, + "node_modules/@tonconnect/ui-react": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@tonconnect/ui-react/-/ui-react-2.4.4.tgz", + "integrity": "sha512-4qpAABZK+nsJSQPGAVsj6xPtQ6HkqGIn/M984dNgp5eqVoaVHti0rCMOqcTutb3BScAvWLJoo3hONqNIg2fuEQ==", + "license": "Apache-2.0", + "dependencies": { + "@tonconnect/ui": "2.4.4" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz", + "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-rc.7" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", + "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", + "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.5", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", + "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", + "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.5", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", + "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.5", + "@vitest/utils": "4.1.5", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", + "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", + "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.5", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/autoprefixer": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.2", + "caniuse-lite": "^1.0.30001787", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.23", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.23.tgz", + "integrity": "sha512-xwVXGqevyKPsiuQdLj+dZMVjidjJV508TBqexND5HrF89cGdCYCJFB3qhcxRHSeMctdCfbR1jrxBajhDy7o29g==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/broadcast-channel": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-2.3.4.tgz", + "integrity": "sha512-cx1/dSb6KZ9HW1VtlqM/HLPjrdyzkKoteVmUpLXEpra00mDQW/F9ieDkoavuZMoh9/hC/6OplGzCERsZBfz/Wg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.6.2", + "detect-node": "^2.0.4", + "js-sha3": "0.8.0", + "microseconds": "0.1.0", + "nano-time": "1.0.0", + "rimraf": "2.6.3", + "unload": "2.2.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "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.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001791", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", + "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/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "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/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.344", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz", + "integrity": "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==", + "dev": true, + "license": "ISC" + }, + "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-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, + "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/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "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==", + "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/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", + "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/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "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.", + "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==", + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-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/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "license": "Apache-2.0", + "dependencies": { + "lie": "3.1.1" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lucide-react": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.11.0.tgz", + "integrity": "sha512-UOhjdztXCgdBReRcIhsvz2siIBogfv/lhJEIViCpLt924dO+GDms9T7DNoucI23s6kEPpe988m5N0D2ajnzb2g==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "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/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/microseconds": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.1.0.tgz", + "integrity": "sha512-yF2K4aHXKxO4OGhW7Ek2KLgKEAFbSblBLKlF6KzwQUhjK7+uAzatRr6fZ82bftdnuDQrkBHAJp5s8quj1ME3wA==", + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nano-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz", + "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==", + "license": "ISC", + "dependencies": { + "big-integer": "^1.6.16" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-releases": { + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "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==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "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": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", + "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", + "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.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "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/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-router": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.2.tgz", + "integrity": "sha512-yCqNne6I8IB6rVCH7XUvlBK7/QKyqypBFGv+8dj4QBFJiiRX+FG7/nkdAvGElyvVZ/HQP5N19wzteuTARXi5Gw==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.14.2.tgz", + "integrity": "sha512-YZcM5ES8jJSM+KrJ9BdvHHqlnGTg5tH3sC5ChFRj4inosKctdyzBDhOyyHdGk597q2OT6NTrCA1OvB/YDwfekQ==", + "license": "MIT", + "dependencies": { + "react-router": "7.14.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "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", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", + "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.127.0", + "@rolldown/pluginutils": "1.0.0-rc.17" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.17", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", + "@rolldown/binding-darwin-x64": "1.0.0-rc.17", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" + } + }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", + "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", + "dev": true, + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "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", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "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/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tdweb": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/tdweb/-/tdweb-1.8.0.tgz", + "integrity": "sha512-FIu48oWAUK2pAh/mp8q/faZMcik8qwzmdxl+p6gbNhEfjF/lYadDEgFBXOv/Xp5er19nFrZayIMYz3QxWBLaYw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.4.3", + "broadcast-channel": "^2.1.12", + "localforage": "^1.7.3", + "uuid": "^3.3.2" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", + "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "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/tinyglobby/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/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "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", + "optional": true + }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "license": "Unlicense" + }, + "node_modules/tweetnacl-util": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", + "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==", + "license": "Unlicense" + }, + "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/ua-parser-js": { + "version": "1.0.41", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz", + "integrity": "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/unload": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", + "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.6.2", + "detect-node": "^2.0.4" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "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/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/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/vite": { + "version": "8.0.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", + "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.10", + "rolldown": "1.0.0-rc.17", + "tinyglobby": "^0.2.16" + }, + "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", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.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 + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "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/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", + "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.5", + "@vitest/mocker": "4.1.5", + "@vitest/pretty-format": "4.1.5", + "@vitest/runner": "4.1.5", + "@vitest/snapshot": "4.1.5", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.5", + "@vitest/browser-preview": "4.1.5", + "@vitest/browser-webdriverio": "4.1.5", + "@vitest/coverage-istanbul": "4.1.5", + "@vitest/coverage-v8": "4.1.5", + "@vitest/ui": "4.1.5", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "license": "ISC" + }, + "node_modules/zustand": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz", + "integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..6c96ea4 --- /dev/null +++ b/web/package.json @@ -0,0 +1,38 @@ +{ + "name": "teleton-client-web", + "version": "0.1.0-alpha.0", + "private": true, + "type": "module", + "description": "Alpha web client for Teleton Client with TDLib, proxy, Teleton Agent, and TON workflows.", + "scripts": { + "dev": "vite --host 0.0.0.0", + "build": "tsc -b && vite build", + "preview": "vite preview --host 0.0.0.0", + "test": "vitest run", + "postinstall": "node scripts/copy-tdweb-assets.mjs", + "generate:icons": "node scripts/generate-icons.mjs" + }, + "dependencies": { + "@tonconnect/ui-react": "^2.4.4", + "lucide-react": "^1.11.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^7.14.2", + "tdweb": "^1.8.0", + "zustand": "^5.0.12" + }, + "devDependencies": { + "@types/react": "^18.3.27", + "@types/react-dom": "^18.3.7", + "@vitejs/plugin-react": "^6.0.1", + "autoprefixer": "^10.4.23", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.19", + "typescript": "^5.9.3", + "vite": "^8.0.10", + "vitest": "^4.1.5" + }, + "engines": { + "node": ">=20" + } +} diff --git a/web/postcss.config.js b/web/postcss.config.js new file mode 100644 index 0000000..ba80730 --- /dev/null +++ b/web/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {} + } +}; diff --git a/web/public/icons/icon-192.png b/web/public/icons/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..faa89dd4beb623fd7c20bea0336ef03b260459cd GIT binary patch literal 2383 zcmZWrdsI_L9-iC;5?)CF0Tm%39t|yM1kM%}Xd)W~Ed_PmqOxn$s`xdBnhGt72@kox1;8V4nR3bMeGlx9GIAa+owju2{O;Pb zquAw{Nf9y2H?3-vBrUtMGQ#=MkMr$qIqCU4JLi+=my<^9Vtnu|9jMw7Q``YJ^E3@S<9TVf{Ztr+K z-=sfZbi%QH@ZDo^`SuSBlW!MobF{R_F#L)R)$Si3Xj|`|>^PCGdug|8e*Rjm9!(gv z=M@!Bykit-&yd3`LPQ;M4zhuqtg^_E@v-V&bR1X1^$%RlLO^!P|3W38ZQZ>lUo_QF zcmJ9N%v^&Hbp${!abz(1-0IB0;BmV}#Q|F9aF6_c(KkFmnPbc30%R%-Qs6v@50PE+0E0#(~k8;KU?(jTFPC~6BbOQ`+GtfcOs$Pr{F zid^R$L@v)+blEaAm}i}e*j9mGn3QK1=_&K5^@T=+6uT1 z6UjAPlLyqs+}5<1;FC5#~nK-p*PJci&)zTgJKKmo9gq zkeI7200(q-7VB=0iLW3J?gAT*lh0V;n$!Lu)gNXq+P?KmH!*?SN4D}xoUvHVX02Z|nZ0Q)#ye~XC7hrC*(fc!131olR`|mqcP-|0 z7sL7#PumkQF_r}mkWxXl9^}6Gkt6s03dS~x6voOO6Fz`m2Rz}&c8sTaFNQ_nZ$f7r z9eZ0y-UPYMOKf4gPB+dn8gf9fvyCm(EOc`a7mi`PG8DOX?ZSQcf)yJm+ZNziJ;!QZ zn*}g(3>-aq{c2~78YGf^f+H6|ZfHthXADXt!%IAHclx4z_dFHTaU8HjNjEz6D2cxD zOBj!u1<{ztUeVo}$Ocx5SyceUX>r&*>Ktft z`UBQi?3zB6m%@)(%m(V8eYaUerc92wN@oJ*X1B%?CLSOU)f_+vV7y9$FENYG<0?4Y z4bx8l-QY(=(n6LnvCF{@hX>=XVKsfCXi#U4>s&sSk3ZPU6<5NWU@_0jz?vAaANFWT zH_SMc;L}GymsJWN5-fuh7T`g^chDujGVG~vXP%F+x&4<}DHb(HT&e^Akbz~?F|gSM zJ-Ugja7}^SOA#t(wQL15-$Xi-=7on67=1qst&Wm~7_U^m8K{{3ev=9ldOOJwuu`7b zjZ_Z!6AQHIz~buX_ov#VSTeeRf_1EGW+}k_1?&E$Q1_a*wUQApaXjqy3nY3jFTiGJ zwIlvIEHoW(%mM`;PXJ4(XM7@~_z;348f>J(Fmn;{9Rya#Z6xsgJ-wa!&QPQHI|z~2%#2B5isquVbAgvLOH9c9B=U5W^3k(4>OwA${Gosm(*Bg@K$&*xy&@8 z+-k_?M49{9D|%GkW!tTWR5&Z(h^uwHZ+r2Nd7C;-JcR(dsbUzV&>t4tPL_+)sQQW6 zx6C4ZEZj@Xr|wP`j6JQ@D5|(jyQIw55C^tUN}-6pNgrP0YGapi{@zr@%cUHr0EakIkY6ZjG4E`IY9+cbrzYbzFaO^QZjN zaW3_xAup!&LW0BegFc{6p0nEe=%H7V&=9YPXOsf*H;ZvT~C~vk3EQsrt+1#BHiyr1}nTOkk9O-?}=5s!sXuYgfEi=I9^#RD;I?N!mAIRCcv zvjILzj)h3aqPIno5iCNUM?ERrGix$&jATN&=nNhy0#l}dz~vtUt;is7^xl) zZFOTtdh-L}#B}BS07l0n5!;*bWME26&sk;j&(4gdPXePl#PCDaEtDhjN?=)1n(}<&h6DcvKFCUN literal 0 HcmV?d00001 diff --git a/web/public/icons/icon-512.png b/web/public/icons/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..34905a3b3706cf320de824c2dd01e03f854f6801 GIT binary patch literal 8551 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 + + + + + + Teleton Client Offline + + + +
+

Teleton Client is offline

+

Live Telegram, agent, proxy, and TON actions need network access or a configured local bridge.

+
+ + diff --git a/web/public/service-worker.js b/web/public/service-worker.js new file mode 100644 index 0000000..9a4be39 --- /dev/null +++ b/web/public/service-worker.js @@ -0,0 +1,45 @@ +const BUILD_ID = 'teleton-web-alpha-0.1.0'; +const SHELL_CACHE = `teleton-shell-${BUILD_ID}`; +const PRECACHE_ASSETS = ['/app/', '/offline.html', '/manifest.webmanifest', '/icons/icon-192.png', '/icons/icon-512.png']; + +self.addEventListener('install', (event) => { + event.waitUntil( + caches + .open(SHELL_CACHE) + .then((cache) => cache.addAll(PRECACHE_ASSETS)) + .then(() => self.skipWaiting()) + ); +}); + +self.addEventListener('activate', (event) => { + event.waitUntil( + caches + .keys() + .then((keys) => Promise.all(keys.filter((key) => key.startsWith('teleton-shell-') && key !== SHELL_CACHE).map((key) => caches.delete(key)))) + .then(() => self.clients.claim()) + ); +}); + +self.addEventListener('fetch', (event) => { + const url = new URL(event.request.url); + + if (url.pathname.startsWith('/api/') || url.pathname.startsWith('/v1/') || url.pathname.startsWith('/media/')) { + event.respondWith(fetch(event.request)); + return; + } + + if (event.request.mode === 'navigate') { + event.respondWith( + fetch(event.request) + .then((response) => { + const clone = response.clone(); + caches.open(SHELL_CACHE).then((cache) => cache.put('/app/', clone)); + return response; + }) + .catch(() => caches.match('/offline.html')) + ); + return; + } + + event.respondWith(caches.match(event.request).then((cached) => cached || fetch(event.request))); +}); diff --git a/web/public/tonconnect-manifest.json b/web/public/tonconnect-manifest.json new file mode 100644 index 0000000..9276cad --- /dev/null +++ b/web/public/tonconnect-manifest.json @@ -0,0 +1,5 @@ +{ + "url": "https://client.teleton.dev", + "name": "Teleton Client", + "iconUrl": "https://client.teleton.dev/icons/icon-512.png" +} diff --git a/web/scripts/copy-tdweb-assets.mjs b/web/scripts/copy-tdweb-assets.mjs new file mode 100644 index 0000000..bb5d94c --- /dev/null +++ b/web/scripts/copy-tdweb-assets.mjs @@ -0,0 +1,19 @@ +#!/usr/bin/env node +import { cp, mkdir } from 'node:fs/promises'; +import { existsSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const here = dirname(fileURLToPath(import.meta.url)); +const webRoot = resolve(here, '..'); +const source = resolve(webRoot, 'node_modules/tdweb/dist'); +const target = resolve(webRoot, 'public'); + +if (!existsSync(source)) { + console.warn('tdweb dist assets are not available yet; run npm install in web/ to copy them.'); + process.exit(0); +} + +await mkdir(target, { recursive: true }); +await cp(source, target, { recursive: true }); +console.log(`Copied tdweb runtime assets to ${target}`); diff --git a/web/scripts/generate-icons.mjs b/web/scripts/generate-icons.mjs new file mode 100644 index 0000000..2543640 --- /dev/null +++ b/web/scripts/generate-icons.mjs @@ -0,0 +1,137 @@ +#!/usr/bin/env node +import { mkdir, writeFile } from 'node:fs/promises'; +import { dirname, resolve } from 'node:path'; +import { deflateSync } from 'node:zlib'; + +const outputDir = resolve('public/icons'); +const sizes = [192, 512]; + +const palette = { + ink: [16, 32, 39, 255], + teal: [31, 122, 140, 255], + mint: [45, 154, 115, 255], + paper: [245, 247, 246, 255], + saffron: [196, 124, 33, 255] +}; + +function crc32(buffer) { + let crc = 0xffffffff; + + for (const byte of buffer) { + crc ^= byte; + for (let index = 0; index < 8; index += 1) { + crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1)); + } + } + + return (crc ^ 0xffffffff) >>> 0; +} + +function chunk(type, data = Buffer.alloc(0)) { + const typeBuffer = Buffer.from(type); + const length = Buffer.alloc(4); + length.writeUInt32BE(data.length, 0); + const checksum = Buffer.alloc(4); + checksum.writeUInt32BE(crc32(Buffer.concat([typeBuffer, data])), 0); + return Buffer.concat([length, typeBuffer, data, checksum]); +} + +function encodePng(width, height, rgba) { + const header = Buffer.alloc(13); + header.writeUInt32BE(width, 0); + header.writeUInt32BE(height, 4); + header[8] = 8; + header[9] = 6; + header[10] = 0; + header[11] = 0; + header[12] = 0; + + const rows = Buffer.alloc((width * 4 + 1) * height); + for (let y = 0; y < height; y += 1) { + const rowOffset = y * (width * 4 + 1); + rows[rowOffset] = 0; + rgba.copy(rows, rowOffset + 1, y * width * 4, (y + 1) * width * 4); + } + + return Buffer.concat([ + Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]), + chunk('IHDR', header), + chunk('IDAT', deflateSync(rows, { level: 9 })), + chunk('IEND') + ]); +} + +function blend(base, overlay, alpha) { + return base.map((value, index) => { + if (index === 3) return 255; + return Math.round(value * (1 - alpha) + overlay[index] * alpha); + }); +} + +function setPixel(buffer, width, x, y, color) { + if (x < 0 || y < 0 || x >= width || y >= width) return; + const offset = (Math.floor(y) * width + Math.floor(x)) * 4; + buffer[offset] = color[0]; + buffer[offset + 1] = color[1]; + buffer[offset + 2] = color[2]; + buffer[offset + 3] = color[3]; +} + +function fillRoundedRect(buffer, width, x, y, w, h, radius, color) { + for (let py = y; py < y + h; py += 1) { + for (let px = x; px < x + w; px += 1) { + const left = px < x + radius; + const right = px >= x + w - radius; + const top = py < y + radius; + const bottom = py >= y + h - radius; + + if ((left || right) && (top || bottom)) { + const cx = left ? x + radius : x + w - radius - 1; + const cy = top ? y + radius : y + h - radius - 1; + if ((px - cx) ** 2 + (py - cy) ** 2 > radius ** 2) continue; + } + + setPixel(buffer, width, px, py, color); + } + } +} + +function fillDiamond(buffer, width, centerX, centerY, radius, color) { + for (let py = centerY - radius; py <= centerY + radius; py += 1) { + for (let px = centerX - radius; px <= centerX + radius; px += 1) { + if (Math.abs(px - centerX) + Math.abs(py - centerY) <= radius) { + setPixel(buffer, width, px, py, color); + } + } + } +} + +function generateIcon(size) { + const buffer = Buffer.alloc(size * size * 4); + + for (let y = 0; y < size; y += 1) { + for (let x = 0; x < size; x += 1) { + const diagonal = (x + y) / (size * 2); + const color = blend(palette.teal, palette.ink, diagonal * 0.44); + setPixel(buffer, size, x, y, color); + } + } + + fillRoundedRect(buffer, size, size * 0.16, size * 0.2, size * 0.68, size * 0.48, size * 0.08, palette.paper); + fillDiamond(buffer, size, size * 0.71, size * 0.72, size * 0.13, palette.mint); + fillDiamond(buffer, size, size * 0.71, size * 0.72, size * 0.07, palette.paper); + fillRoundedRect(buffer, size, size * 0.25, size * 0.32, size * 0.34, size * 0.045, size * 0.02, palette.teal); + fillRoundedRect(buffer, size, size * 0.25, size * 0.43, size * 0.43, size * 0.045, size * 0.02, palette.saffron); + fillRoundedRect(buffer, size, size * 0.25, size * 0.54, size * 0.25, size * 0.045, size * 0.02, palette.mint); + + return encodePng(size, size, buffer); +} + +await mkdir(outputDir, { recursive: true }); + +for (const size of sizes) { + const target = resolve(outputDir, `icon-${size}.png`); + await mkdir(dirname(target), { recursive: true }); + await writeFile(target, generateIcon(size)); + console.log(`Wrote ${target}`); +} diff --git a/web/src/app/App.tsx b/web/src/app/App.tsx new file mode 100644 index 0000000..f775d51 --- /dev/null +++ b/web/src/app/App.tsx @@ -0,0 +1,151 @@ +import { TonConnectUIProvider } from '@tonconnect/ui-react'; +import { Bot, MessageCircle, Settings, WalletCards, X } from 'lucide-react'; +import { useEffect } from 'react'; +import { useSearchParams } from 'react-router-dom'; + +import { AgentPanel } from '../features/agent/AgentPanel'; +import { AuthScreen } from '../features/auth/AuthScreen'; +import { ChatScreen } from '../features/chat/ChatScreen'; +import { SettingsScreen } from '../features/settings/SettingsScreen'; +import { TonPanel } from '../features/ton/TonPanel'; +import { useTeletonStore } from '../shared/store/useTeletonStore'; + +const views = [ + { id: 'chats', label: 'Chats', icon: MessageCircle }, + { id: 'agent', label: 'Agent', icon: Bot }, + { id: 'ton', label: 'TON', icon: WalletCards }, + { id: 'settings', label: 'Settings', icon: Settings } +] as const; + +type ViewId = (typeof views)[number]['id']; + +function isView(value: string | null): value is ViewId { + return views.some((view) => view.id === value); +} + +function NoticeStack() { + const notices = useTeletonStore((state) => state.notices); + const dismissNotice = useTeletonStore((state) => state.dismissNotice); + + return ( +
+ {notices.map((entry) => ( +
+ +

{entry.message}

+ +
+ ))} +
+ ); +} + +export function App() { + const [searchParams, setSearchParams] = useSearchParams(); + const bootstrap = useTeletonStore((state) => state.bootstrap); + const authStatus = useTeletonStore((state) => state.authStatus); + const view = isView(searchParams.get('view')) ? searchParams.get('view') : 'chats'; + + useEffect(() => { + void bootstrap(); + }, [bootstrap]); + + const setView = (next: ViewId) => { + setSearchParams({ view: next }); + }; + + return ( + +
+ +
+ + +
+ {view === 'chats' && (authStatus === 'ready' ? : )} + {view === 'agent' && } + {view === 'ton' && } + {view === 'settings' && } +
+ + +
+
+
+ ); +} diff --git a/web/src/features/agent/AgentPanel.tsx b/web/src/features/agent/AgentPanel.tsx new file mode 100644 index 0000000..6cf3ad5 --- /dev/null +++ b/web/src/features/agent/AgentPanel.tsx @@ -0,0 +1,147 @@ +import { Activity, Bot, Cable, Power, ShieldCheck } from 'lucide-react'; +import { ChangeEvent } from 'react'; + +import { useTeletonStore } from '../../shared/store/useTeletonStore'; + +export function AgentPanel() { + const agent = useTeletonStore((state) => state.agent); + const agentStatus = useTeletonStore((state) => state.agentStatus); + const setAgentSettings = useTeletonStore((state) => state.setAgentSettings); + const saveSettings = useTeletonStore((state) => state.saveSettings); + const connectAgent = useTeletonStore((state) => state.connectAgent); + const disconnectAgent = useTeletonStore((state) => state.disconnectAgent); + const enableAgent = useTeletonStore((state) => state.enableAgent); + const disableAgent = useTeletonStore((state) => state.disableAgent); + const checkManagementStatus = useTeletonStore((state) => state.checkManagementStatus); + + const update = (event: ChangeEvent) => { + setAgentSettings({ + ...agent, + [event.target.name]: event.target.type === 'checkbox' ? event.target.checked : event.target.value + }); + }; + + return ( +
+
+
+
+ + + +
+

Teleton Agent

+

{agentStatus.connection}

+
+
+ +
+ + + + +
+ +
+ + + + +
+
+ +
+

Runtime

+
+
+
Connection
+
{agentStatus.connection}
+
+
+
Lifecycle
+
{agentStatus.lifecycle ?? 'unknown'}
+
+
+
Uptime
+
{agentStatus.uptime ? `${Math.floor(agentStatus.uptime)}s` : 'none'}
+
+
+ {agentStatus.error &&

{agentStatus.error}

} +
+ + +
+
+
+
+ ); +} diff --git a/web/src/features/auth/AuthScreen.tsx b/web/src/features/auth/AuthScreen.tsx new file mode 100644 index 0000000..fa31a13 --- /dev/null +++ b/web/src/features/auth/AuthScreen.tsx @@ -0,0 +1,162 @@ +import { KeyRound, Loader2, Phone, ShieldCheck } from 'lucide-react'; +import { FormEvent, useEffect, useState } from 'react'; + +import { persistEncryptedSession } from '../../services/crypto.service'; +import { useTeletonStore } from '../../shared/store/useTeletonStore'; + +export function AuthScreen() { + const authStatus = useTeletonStore((state) => state.authStatus); + const authError = useTeletonStore((state) => state.authError); + const sessionId = useTeletonStore((state) => state.sessionId); + const initializeTelegram = useTeletonStore((state) => state.initializeTelegram); + const submitPhone = useTeletonStore((state) => state.submitPhone); + const submitCode = useTeletonStore((state) => state.submitCode); + const submitPassword = useTeletonStore((state) => state.submitPassword); + const [phoneNumber, setPhoneNumber] = useState(''); + const [code, setCode] = useState(''); + const [password, setPassword] = useState(''); + const [rememberSession, setRememberSession] = useState(false); + const [persisted, setPersisted] = useState(false); + + useEffect(() => { + if (authStatus === 'idle') void initializeTelegram(); + }, [authStatus, initializeTelegram]); + + useEffect(() => { + if (authStatus === 'ready' && rememberSession && !persisted) { + void persistEncryptedSession({ sessionId, savedAt: new Date().toISOString() }, { consent: true }).then(() => + setPersisted(true) + ); + } + }, [authStatus, persisted, rememberSession, sessionId]); + + const submit = (event: FormEvent) => { + event.preventDefault(); + + if (authStatus === 'phone-required') void submitPhone(phoneNumber); + if (authStatus === 'code-required') void submitCode(code); + if (authStatus === 'password-required') void submitPassword(password); + }; + + const icon = + authStatus === 'phone-required' ? ( + + ) : authStatus === 'password-required' ? ( + + ) : ( + + ); + + return ( +
+
+
+ +
+

Teleton Client

+

Alpha Web

+
+
+ +
+ {[ + ['TDLib', authStatus === 'error' ? 'blocked' : authStatus === 'initializing' ? 'starting' : 'ready'], + ['Agent', 'optional'], + ['TON', 'confirmable'] + ].map(([label, value]) => ( +
+ {label} + {value} +
+ ))} +
+ +
+
+
+
+
+
+ +
+
+
+ {icon} +
+

Telegram sign in

+

{authStatus === 'initializing' ? 'Initializing TDLib' : authStatus}

+
+
+ + {authStatus === 'initializing' && ( +
+ + Starting web TDLib runtime +
+ )} + + {authStatus === 'phone-required' && ( + + )} + + {authStatus === 'code-required' && ( + + )} + + {authStatus === 'password-required' && ( + + )} + + + + {authError &&

{authError}

} + + +
+
+
+ ); +} diff --git a/web/src/features/chat/ChatScreen.tsx b/web/src/features/chat/ChatScreen.tsx new file mode 100644 index 0000000..4acf0a8 --- /dev/null +++ b/web/src/features/chat/ChatScreen.tsx @@ -0,0 +1,40 @@ +import { RefreshCw } from 'lucide-react'; + +import { useTeletonStore } from '../../shared/store/useTeletonStore'; +import { ChatList } from '../../widgets/ChatList'; +import { InputBar } from '../../widgets/InputBar'; +import { MessageWindow } from '../../widgets/MessageWindow'; + +export function ChatScreen() { + const chats = useTeletonStore((state) => state.chats); + const selectedChatId = useTeletonStore((state) => state.selectedChatId); + const messages = useTeletonStore((state) => (selectedChatId ? state.messagesByChat[selectedChatId] ?? [] : [])); + const selectChat = useTeletonStore((state) => state.selectChat); + const loadChats = useTeletonStore((state) => state.loadChats); + const sendMessage = useTeletonStore((state) => state.sendMessage); + const selectedChat = chats.find((chat) => chat.id === selectedChatId); + + return ( +
+
+
+

Chats

+ +
+ void selectChat(chatId)} /> +
+
+ + void sendMessage(text)} /> +
+
+ ); +} diff --git a/web/src/features/settings/SettingsScreen.tsx b/web/src/features/settings/SettingsScreen.tsx new file mode 100644 index 0000000..cb99c11 --- /dev/null +++ b/web/src/features/settings/SettingsScreen.tsx @@ -0,0 +1,130 @@ +import { Eraser, Save, SlidersHorizontal } from 'lucide-react'; +import { ChangeEvent } from 'react'; + +import { useTeletonStore } from '../../shared/store/useTeletonStore'; +import type { ProxySettings } from '../../shared/types'; + +export function SettingsScreen() { + const proxy = useTeletonStore((state) => state.proxy); + const setProxyDraft = useTeletonStore((state) => state.setProxyDraft); + const applyProxy = useTeletonStore((state) => state.applyProxy); + const saveSettings = useTeletonStore((state) => state.saveSettings); + const clearSettings = useTeletonStore((state) => state.clearSettings); + + const updateProxy = (event: ChangeEvent) => { + const next: ProxySettings = { + ...proxy, + [event.target.name]: + event.target instanceof HTMLInputElement && event.target.type === 'checkbox' + ? event.target.checked + : event.target.name === 'port' + ? Number(event.target.value) + : event.target.value + }; + setProxyDraft(next); + }; + + return ( +
+
+
+ + + +
+

Settings

+

Proxy and encrypted browser state

+
+
+ +
+ + + + + + + +
+ +
+ + + +
+
+
+ ); +} diff --git a/web/src/features/ton/TonPanel.tsx b/web/src/features/ton/TonPanel.tsx new file mode 100644 index 0000000..f3ea3ac --- /dev/null +++ b/web/src/features/ton/TonPanel.tsx @@ -0,0 +1,118 @@ +import { TonConnectButton } from '@tonconnect/ui-react'; +import { Send, WalletCards } from 'lucide-react'; +import { FormEvent, useState } from 'react'; + +import { useTeletonStore } from '../../shared/store/useTeletonStore'; + +export function TonPanel() { + const tonBalance = useTeletonStore((state) => state.tonBalance); + const getTonBalance = useTeletonStore((state) => state.getTonBalance); + const sendTonTx = useTeletonStore((state) => state.sendTonTx); + const [address, setAddress] = useState(''); + const [to, setTo] = useState(''); + const [amount, setAmount] = useState(''); + const [comment, setComment] = useState(''); + const [confirmation, setConfirmation] = useState(false); + const [txHash, setTxHash] = useState(''); + + const requestBalance = (event: FormEvent) => { + event.preventDefault(); + if (address.trim()) void getTonBalance(address.trim()); + }; + + const submitTx = (event: FormEvent) => { + event.preventDefault(); + if (!confirmation || !to.trim() || !amount.trim()) return; + + void sendTonTx({ to: to.trim(), amount: amount.trim(), comment: comment.trim() || undefined }).then(setTxHash); + }; + + return ( +
+
+
+
+
+ + + +
+

TON

+

Agent-backed operations

+
+
+ +
+ +
+ + +
+ +
+ + + + + +
+
+ +
+

Wallet State

+
+
+
Balance
+
+ {tonBalance ? `${tonBalance.balance} ${tonBalance.currency}` : 'none'} +
+
+
+
Address
+
{tonBalance?.address ?? 'none'}
+
+
+
Transaction
+
{txHash || 'none'}
+
+
+
+
+
+ ); +} diff --git a/web/src/index.css b/web/src/index.css new file mode 100644 index 0000000..77d20c7 --- /dev/null +++ b/web/src/index.css @@ -0,0 +1,45 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + color: #102027; + background: #f5f7f6; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +* { + box-sizing: border-box; +} + +body { + min-width: 320px; + min-height: 100vh; + margin: 0; +} + +button, +input, +select, +textarea { + font: inherit; +} + +button:focus-visible, +a:focus-visible, +input:focus-visible, +select:focus-visible, +textarea:focus-visible { + outline: 3px solid rgb(31 122 140 / 0.36); + outline-offset: 2px; +} + +.scrollbar-thin { + scrollbar-width: thin; + scrollbar-color: #9ab1ad transparent; +} diff --git a/web/src/main.tsx b/web/src/main.tsx new file mode 100644 index 0000000..cd9f476 --- /dev/null +++ b/web/src/main.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; + +import { App } from './app/App'; +import './index.css'; +import { registerServiceWorker } from './shared/pwa/registerServiceWorker'; + +if (window.location.pathname === '/') { + window.history.replaceState(null, '', '/app/'); +} + +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + + + +); + +registerServiceWorker(); diff --git a/web/src/services/agent.service.ts b/web/src/services/agent.service.ts new file mode 100644 index 0000000..8304acc --- /dev/null +++ b/web/src/services/agent.service.ts @@ -0,0 +1,237 @@ +import type { AgentStatus, TonBalance, TonTransactionDraft } from '../shared/types'; + +export interface JsonRpcRequest { + jsonrpc: '2.0'; + id: string; + method: string; + params?: TParams; +} + +interface JsonRpcSuccess { + jsonrpc: '2.0'; + id: string; + result: TResult; +} + +interface JsonRpcFailure { + jsonrpc: '2.0'; + id: string; + error: { + code: number; + message: string; + data?: unknown; + }; +} + +type JsonRpcResponse = JsonRpcSuccess | JsonRpcFailure; + +interface PendingRequest { + resolve: (value: unknown) => void; + reject: (error: Error) => void; + timer: number; +} + +export interface AgentEnableParams { + session: string; + userId?: string | number; +} + +export interface AgentManagementOptions { + baseUrl: string; + apiKey?: string; +} + +const DEFAULT_RPC_TIMEOUT_MS = 10_000; + +function randomId() { + if ('crypto' in globalThis && 'randomUUID' in globalThis.crypto) { + return globalThis.crypto.randomUUID(); + } + + return `rpc_${Date.now()}_${Math.random().toString(16).slice(2)}`; +} + +function normalizeBaseUrl(value: string) { + return value.trim().replace(/\/+$/, ''); +} + +function problemMessage(payload: unknown, fallback: string) { + if (payload && typeof payload === 'object') { + const detail = 'detail' in payload ? String(payload.detail) : ''; + const title = 'title' in payload ? String(payload.title) : ''; + return detail || title || fallback; + } + + return fallback; +} + +export function createJsonRpcRequest(method: string, params?: TParams, id = randomId()): JsonRpcRequest { + return params === undefined ? { jsonrpc: '2.0', id, method } : { jsonrpc: '2.0', id, method, params }; +} + +export function parseJsonRpcResponse(payload: string): JsonRpcResponse { + const parsed = JSON.parse(payload) as JsonRpcResponse; + + if (!parsed || parsed.jsonrpc !== '2.0' || typeof parsed.id !== 'string') { + throw new Error('Invalid JSON-RPC 2.0 response.'); + } + + return parsed; +} + +export class TeletonAgentService { + private socket: WebSocket | null = null; + private pending = new Map(); + + get connected() { + return this.socket?.readyState === WebSocket.OPEN; + } + + async connect(url: string) { + this.disconnect(); + + await new Promise((resolve, reject) => { + const socket = new WebSocket(url); + this.socket = socket; + + socket.addEventListener('open', () => resolve(), { once: true }); + socket.addEventListener( + 'error', + () => { + reject(new Error('Unable to connect to Teleton Agent WebSocket.')); + }, + { once: true } + ); + socket.addEventListener('message', (event) => this.handleMessage(event.data)); + socket.addEventListener('close', () => this.rejectPending('Teleton Agent WebSocket closed.')); + }); + } + + disconnect() { + if (this.socket) { + this.socket.close(); + this.socket = null; + } + + this.rejectPending('Teleton Agent WebSocket disconnected.'); + } + + enable(params: AgentEnableParams) { + return this.call<{ success: boolean }>('agent.enable', params); + } + + disable() { + return this.call<{ success: boolean }>('agent.disable'); + } + + getTonBalance(address: string) { + return this.call('ton.getBalance', { address }); + } + + sendTx(draft: TonTransactionDraft) { + return this.call<{ txHash: string }>('ton.sendTx', draft); + } + + private call(method: string, params?: unknown, timeoutMs = DEFAULT_RPC_TIMEOUT_MS) { + if (!this.socket || this.socket.readyState !== WebSocket.OPEN) { + return Promise.reject(new Error('Teleton Agent is not connected.')); + } + + const request = createJsonRpcRequest(method, params); + + return new Promise((resolve, reject) => { + const timer = window.setTimeout(() => { + this.pending.delete(request.id); + reject(new Error(`${method} timed out after ${timeoutMs}ms.`)); + }, timeoutMs); + + this.pending.set(request.id, { + resolve: (value) => resolve(value as TResult), + reject, + timer + }); + + this.socket?.send(JSON.stringify(request)); + }); + } + + private handleMessage(data: unknown) { + if (typeof data !== 'string') { + return; + } + + let response: JsonRpcResponse; + + try { + response = parseJsonRpcResponse(data); + } catch { + return; + } + + const pending = this.pending.get(response.id); + if (!pending) return; + + window.clearTimeout(pending.timer); + this.pending.delete(response.id); + + if ('error' in response) { + pending.reject(new Error(response.error.message)); + return; + } + + pending.resolve(response.result); + } + + private rejectPending(message: string) { + for (const request of this.pending.values()) { + window.clearTimeout(request.timer); + request.reject(new Error(message)); + } + + this.pending.clear(); + } +} + +export async function getManagementAgentStatus(options: AgentManagementOptions): Promise { + const baseUrl = normalizeBaseUrl(options.baseUrl); + const response = await fetch(`${baseUrl}/v1/agent/status`, { + headers: options.apiKey ? { Authorization: `Bearer ${options.apiKey}` } : undefined + }); + + if (!response.ok) { + let body: unknown = null; + try { + body = await response.json(); + } catch { + body = null; + } + + throw new Error(problemMessage(body, `Management API returned ${response.status}.`)); + } + + const payload = (await response.json()) as { state?: AgentStatus['lifecycle']; uptime?: number; error?: string | null }; + return { + connection: 'connected', + lifecycle: payload.state, + uptime: payload.uptime, + error: payload.error ?? undefined + }; +} + +export async function validateManagementApi(options: AgentManagementOptions) { + const baseUrl = normalizeBaseUrl(options.baseUrl); + const response = await fetch(`${baseUrl}/v1/auth/validate`, { + method: 'POST', + headers: options.apiKey ? { Authorization: `Bearer ${options.apiKey}` } : undefined + }); + + if (!response.ok) { + return { valid: false, message: `Management API returned ${response.status}.` }; + } + + const payload = (await response.json()) as { valid?: boolean; keyPrefix?: string }; + return { + valid: payload.valid === true, + keyPrefix: payload.keyPrefix + }; +} diff --git a/web/src/services/crypto.service.ts b/web/src/services/crypto.service.ts new file mode 100644 index 0000000..00b5fd9 --- /dev/null +++ b/web/src/services/crypto.service.ts @@ -0,0 +1,164 @@ +import type { PersistedSettings } from '../shared/types'; + +interface EncryptedEnvelope { + version: 1; + algorithm: 'AES-GCM'; + keyRef: string; + iv: string; + ciphertext: string; + createdAt: string; +} + +const KEY_DB = 'teleton.web.crypto.v1'; +const KEY_STORE = 'keys'; +const RECORD_STORE = 'records'; +const SETTINGS_KEY_REF = 'teleton.web.settings.v1'; +const SESSION_RECORD_ID = 'teleton.web.session.v1'; +const SETTINGS_STORAGE_KEY = 'teleton.web.settings.encrypted'; + +function requireBrowserStorage() { + if (!('indexedDB' in globalThis) || !('crypto' in globalThis) || !globalThis.crypto.subtle) { + throw new Error('Encrypted browser storage is unavailable.'); + } +} + +function toBase64Url(bytes: Uint8Array) { + const binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join(''); + return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); +} + +function fromBase64Url(value: string) { + const padded = value.replace(/-/g, '+').replace(/_/g, '/').padEnd(Math.ceil(value.length / 4) * 4, '='); + const binary = atob(padded); + const bytes = new Uint8Array(binary.length); + + for (let index = 0; index < binary.length; index += 1) { + bytes[index] = binary.charCodeAt(index); + } + + return bytes; +} + +function openDb() { + requireBrowserStorage(); + + return new Promise((resolve, reject) => { + const request = indexedDB.open(KEY_DB, 1); + + request.onupgradeneeded = () => { + const db = request.result; + if (!db.objectStoreNames.contains(KEY_STORE)) db.createObjectStore(KEY_STORE); + if (!db.objectStoreNames.contains(RECORD_STORE)) db.createObjectStore(RECORD_STORE); + }; + + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error ?? new Error('Unable to open encrypted storage.')); + }); +} + +async function idbGet(storeName: string, key: string) { + const db = await openDb(); + + return new Promise((resolve, reject) => { + const tx = db.transaction(storeName, 'readonly'); + const request = tx.objectStore(storeName).get(key); + request.onsuccess = () => resolve(request.result as T | undefined); + request.onerror = () => reject(request.error ?? new Error(`Unable to read ${storeName}.`)); + tx.oncomplete = () => db.close(); + }); +} + +async function idbSet(storeName: string, key: string, value: T) { + const db = await openDb(); + + return new Promise((resolve, reject) => { + const tx = db.transaction(storeName, 'readwrite'); + const request = tx.objectStore(storeName).put(value, key); + request.onsuccess = () => resolve(); + request.onerror = () => reject(request.error ?? new Error(`Unable to write ${storeName}.`)); + tx.oncomplete = () => db.close(); + }); +} + +async function idbDelete(storeName: string, key: string) { + const db = await openDb(); + + return new Promise((resolve, reject) => { + const tx = db.transaction(storeName, 'readwrite'); + const request = tx.objectStore(storeName).delete(key); + request.onsuccess = () => resolve(); + request.onerror = () => reject(request.error ?? new Error(`Unable to delete ${storeName}.`)); + tx.oncomplete = () => db.close(); + }); +} + +async function getOrCreateKey(keyRef = SETTINGS_KEY_REF) { + const existing = await idbGet(KEY_STORE, keyRef); + if (existing) return existing; + + const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']); + await idbSet(KEY_STORE, keyRef, key); + return key; +} + +export async function encryptJson(value: unknown, keyRef = SETTINGS_KEY_REF): Promise { + const key = await getOrCreateKey(keyRef); + const iv = crypto.getRandomValues(new Uint8Array(12)); + const encoded = new TextEncoder().encode(JSON.stringify(value)); + const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, encoded)); + + return { + version: 1, + algorithm: 'AES-GCM', + keyRef, + iv: toBase64Url(iv), + ciphertext: toBase64Url(ciphertext), + createdAt: new Date().toISOString() + }; +} + +export async function decryptJson(envelope: EncryptedEnvelope): Promise { + if (envelope.algorithm !== 'AES-GCM' || envelope.version !== 1) { + throw new Error('Unsupported encrypted envelope.'); + } + + const key = await getOrCreateKey(envelope.keyRef); + const iv = fromBase64Url(envelope.iv); + const ciphertext = fromBase64Url(envelope.ciphertext); + const plaintext = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext); + return JSON.parse(new TextDecoder().decode(plaintext)) as T; +} + +export async function saveEncryptedSettings(settings: PersistedSettings) { + const envelope = await encryptJson(settings); + localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(envelope)); +} + +export async function loadEncryptedSettings() { + const raw = localStorage.getItem(SETTINGS_STORAGE_KEY); + if (!raw) return null; + + return decryptJson(JSON.parse(raw) as EncryptedEnvelope); +} + +export async function clearEncryptedSettings() { + localStorage.removeItem(SETTINGS_STORAGE_KEY); +} + +export async function persistEncryptedSession(session: unknown, options: { consent: boolean }) { + if (options.consent !== true) { + throw new Error('Explicit consent is required before persisting a Telegram session.'); + } + + const envelope = await encryptJson(session, 'teleton.web.session.key.v1'); + await idbSet(RECORD_STORE, SESSION_RECORD_ID, envelope); +} + +export async function loadEncryptedSession() { + const envelope = await idbGet(RECORD_STORE, SESSION_RECORD_ID); + return envelope ? decryptJson(envelope) : null; +} + +export async function clearEncryptedSession() { + await idbDelete(RECORD_STORE, SESSION_RECORD_ID); +} diff --git a/web/src/services/proxy.service.ts b/web/src/services/proxy.service.ts new file mode 100644 index 0000000..190bfd7 --- /dev/null +++ b/web/src/services/proxy.service.ts @@ -0,0 +1,96 @@ +import type { ProxySettings } from '../shared/types'; + +export interface ProxyValidationResult { + valid: boolean; + errors: string[]; + normalized?: ProxySettings; +} + +const DEFAULT_PROXY: ProxySettings = { + enabled: false, + type: 'none', + host: '', + port: 0 +}; + +function normalizeHost(value: string) { + return value.trim().replace(/^https?:\/\//i, '').replace(/\/+$/, ''); +} + +function isValidHost(value: string) { + if (!value) return false; + if (value.includes('/') || value.includes('@')) return false; + return /^[A-Za-z0-9.-]+$/.test(value) || /^\[[0-9A-Fa-f:.]+\]$/.test(value); +} + +function isValidPort(value: number) { + return Number.isInteger(value) && value >= 1 && value <= 65535; +} + +function isSafeSecretRef(value?: string) { + if (!value) return false; + return /^(env|keychain|keystore|secret):[A-Za-z0-9_.:/-]+$/.test(value); +} + +export function defaultProxySettings(): ProxySettings { + return { ...DEFAULT_PROXY }; +} + +export function validateProxySettings(input: ProxySettings): ProxyValidationResult { + const normalized: ProxySettings = { + enabled: Boolean(input.enabled), + type: input.enabled ? input.type : 'none', + host: normalizeHost(input.host ?? ''), + port: Number(input.port ?? 0), + username: input.username?.trim() || undefined, + password: input.password || undefined, + secret: input.secret?.trim() || undefined + }; + const errors: string[] = []; + + if (!normalized.enabled || normalized.type === 'none') { + return { + valid: true, + errors: [], + normalized: { ...DEFAULT_PROXY } + }; + } + + if (!['socks5', 'mtproto'].includes(normalized.type)) { + errors.push('Select SOCKS5 or MTProto.'); + } + + if (!isValidHost(normalized.host)) { + errors.push('Enter a proxy host without a scheme or path.'); + } + + if (!isValidPort(normalized.port)) { + errors.push('Use a proxy port from 1 to 65535.'); + } + + if (normalized.type === 'mtproto' && !isSafeSecretRef(normalized.secret)) { + errors.push('Use a secure reference for the MTProto secret.'); + } + + if (normalized.type === 'socks5' && normalized.password && !normalized.username) { + errors.push('Set a SOCKS5 username when a password is provided.'); + } + + return { + valid: errors.length === 0, + errors, + normalized + }; +} + +export function serializeProxyForDiagnostics(proxy: ProxySettings) { + return { + enabled: proxy.enabled, + type: proxy.type, + host: proxy.host, + port: proxy.port, + hasUsername: Boolean(proxy.username), + hasPassword: Boolean(proxy.password), + hasSecret: Boolean(proxy.secret) + }; +} diff --git a/web/src/services/tdlib.service.ts b/web/src/services/tdlib.service.ts new file mode 100644 index 0000000..d87b0fd --- /dev/null +++ b/web/src/services/tdlib.service.ts @@ -0,0 +1,300 @@ +import type { ChatMessage, ChatSummary, ProxySettings } from '../shared/types'; +import { validateProxySettings } from './proxy.service'; + +interface TdlibOptions { + apiId: number; + apiHash: string; + logVerbosityLevel?: number; + onUpdate?: (update: Record) => void; +} + +interface TdClientConstructor { + new (options: Record): { + send(query: Record): Promise>; + close?: () => void; + }; +} + +declare global { + interface Window { + tdweb?: TdClientConstructor; + } +} + +const TDWEB_SCRIPT = '/tdweb.js'; + +function appendScript(src: string) { + return new Promise((resolve, reject) => { + const existing = document.querySelector(`script[src="${src}"]`); + if (existing) { + resolve(); + return; + } + + const script = document.createElement('script'); + script.src = src; + script.async = true; + script.onload = () => resolve(); + script.onerror = () => reject(new Error(`Unable to load ${src}. Run npm install in web/ to copy tdweb assets.`)); + document.head.append(script); + }); +} + +async function loadTdClientConstructor(): Promise { + if (window.tdweb) return window.tdweb; + + try { + await appendScript(TDWEB_SCRIPT); + if (window.tdweb) return window.tdweb; + } catch { + // Fall through to the npm module path for test/build environments that provide a bundler shim. + } + + const tdwebModule = await import(/* @vite-ignore */ 'tdweb'); + const candidate = tdwebModule.default as unknown; + if (typeof candidate === 'function') return candidate as TdClientConstructor; + + throw new Error('tdweb did not expose a TdClient constructor.'); +} + +function textFromMessage(message: Record | undefined) { + const content = message?.content as Record | undefined; + const text = content?.text as Record | undefined; + const value = text?.text; + return typeof value === 'string' ? value : ''; +} + +function normalizeChat(chat: Record): ChatSummary { + const lastMessage = chat.last_message as Record | undefined; + + return { + id: Number(chat.id), + title: String(chat.title ?? 'Untitled chat'), + unreadCount: Number(chat.unread_count ?? 0), + lastMessage: textFromMessage(lastMessage), + updatedAt: Number(lastMessage?.date ?? 0) + }; +} + +function normalizeMessage(chatId: number, message: Record, userId?: number): ChatMessage { + const senderId = message.sender_id as Record | undefined; + const senderUserId = Number(senderId?.user_id ?? 0); + + return { + id: Number(message.id), + chatId, + sender: senderUserId && senderUserId === userId ? 'me' : 'them', + text: textFromMessage(message), + createdAt: Number(message.date ?? Date.now() / 1000) * 1000 + }; +} + +export class TdlibService { + private client: InstanceType | null = null; + private apiId = 0; + private apiHash = ''; + private currentUserId?: number; + private onUpdate?: (update: Record) => void; + private parametersSent = false; + + get initialized() { + return Boolean(this.client); + } + + async init(options: TdlibOptions) { + if (!Number.isInteger(options.apiId) || options.apiId <= 0 || !options.apiHash.trim()) { + throw new Error('Telegram API credentials are required.'); + } + + this.apiId = options.apiId; + this.apiHash = options.apiHash.trim(); + this.onUpdate = options.onUpdate; + + const TdClient = await loadTdClientConstructor(); + this.client = new TdClient({ + instanceName: 'teleton-web-alpha', + mode: 'wasm', + useDatabase: true, + readOnly: false, + jsLogVerbosityLevel: 'warning', + logVerbosityLevel: options.logVerbosityLevel ?? 2, + onUpdate: (update: Record) => { + void this.handleUpdate(update); + this.onUpdate?.(update); + } + }); + + await this.send({ '@type': 'getAuthorizationState' }); + } + + async authPhone(phoneNumber: string) { + await this.ensureParameters(); + return this.send({ + '@type': 'setAuthenticationPhoneNumber', + phone_number: phoneNumber.trim(), + settings: { + '@type': 'phoneNumberAuthenticationSettings', + allow_flash_call: false, + allow_missed_call: false, + is_current_phone_number: false, + allow_sms_retriever_api: false + } + }); + } + + authCode(code: string) { + return this.send({ + '@type': 'checkAuthenticationCode', + code: code.trim() + }); + } + + authPassword(password: string) { + return this.send({ + '@type': 'checkAuthenticationPassword', + password + }); + } + + async getChats(limit = 30): Promise { + const result = await this.send({ + '@type': 'getChats', + chat_list: { '@type': 'chatListMain' }, + limit + }); + const chatIds = Array.isArray(result.chat_ids) ? result.chat_ids.slice(0, limit) : []; + const chats = await Promise.all(chatIds.map((id) => this.send({ '@type': 'getChat', chat_id: id }))); + return chats.map(normalizeChat).sort((left, right) => (right.updatedAt ?? 0) - (left.updatedAt ?? 0)); + } + + async getMessages(chatId: number, limit = 40): Promise { + const result = await this.send({ + '@type': 'getChatHistory', + chat_id: chatId, + from_message_id: 0, + offset: 0, + limit, + only_local: false + }); + const messages = Array.isArray(result.messages) ? result.messages : []; + return messages.map((message) => normalizeMessage(chatId, message as Record, this.currentUserId)).reverse(); + } + + sendMessage(chatId: number, text: string) { + return this.send({ + '@type': 'sendMessage', + chat_id: chatId, + input_message_content: { + '@type': 'inputMessageText', + text: { + '@type': 'formattedText', + text, + entities: [] + }, + disable_web_page_preview: true, + clear_draft: true + } + }); + } + + async setProxy(proxy: ProxySettings) { + const validation = validateProxySettings(proxy); + if (!validation.valid || !validation.normalized) { + throw new Error(validation.errors.join(' ')); + } + + if (!validation.normalized.enabled) { + return this.send({ '@type': 'disableProxy' }); + } + + const type = + validation.normalized.type === 'socks5' + ? { + '@type': 'proxyTypeSocks5', + username: validation.normalized.username ?? '', + password: validation.normalized.password ?? '' + } + : { + '@type': 'proxyTypeMtproto', + secret: validation.normalized.secret ?? '' + }; + + const added = await this.send({ + '@type': 'addProxy', + server: validation.normalized.host, + port: validation.normalized.port, + enable: true, + type + }); + + const proxyId = Number(added.id ?? 0); + if (proxyId) { + await this.send({ '@type': 'enableProxy', proxy_id: proxyId }); + } + + return added; + } + + close() { + this.client?.close?.(); + this.client = null; + } + + private async handleUpdate(update: Record) { + const type = update['@type']; + const authorizationState = update.authorization_state as Record | undefined; + + if (type === 'updateAuthorizationState' && authorizationState?.['@type'] === 'authorizationStateWaitTdlibParameters') { + await this.ensureParameters(); + } + + if (type === 'updateAuthorizationState' && authorizationState?.['@type'] === 'authorizationStateWaitEncryptionKey') { + await this.send({ '@type': 'checkDatabaseEncryptionKey', encryption_key: '' }); + } + + if (type === 'updateAuthorizationState' && authorizationState?.['@type'] === 'authorizationStateReady') { + const me = await this.send({ '@type': 'getMe' }); + this.currentUserId = Number(me.id ?? 0); + } + } + + private async ensureParameters() { + if (this.parametersSent) { + return { '@type': 'ok' }; + } + + this.parametersSent = true; + + try { + return await this.send({ + '@type': 'setTdlibParameters', + use_test_dc: false, + database_directory: 'teleton-web-db', + files_directory: 'teleton-web-files', + use_file_database: true, + use_chat_info_database: true, + use_message_database: true, + use_secret_chats: false, + api_id: this.apiId, + api_hash: this.apiHash, + system_language_code: navigator.language || 'en', + device_model: 'Teleton Web', + system_version: navigator.userAgent, + application_version: '0.1.0-alpha.0', + enable_storage_optimizer: true, + ignore_file_names: false + }); + } catch (error) { + this.parametersSent = false; + throw error; + } + } + + private send(query: Record) { + if (!this.client) { + throw new Error('TDLib is not initialized.'); + } + + return this.client.send(query); + } +} diff --git a/web/src/shared/pwa/registerServiceWorker.ts b/web/src/shared/pwa/registerServiceWorker.ts new file mode 100644 index 0000000..20f5c04 --- /dev/null +++ b/web/src/shared/pwa/registerServiceWorker.ts @@ -0,0 +1,11 @@ +export function registerServiceWorker() { + if (!('serviceWorker' in navigator) || import.meta.env.DEV) { + return; + } + + window.addEventListener('load', () => { + navigator.serviceWorker.register('/service-worker.js', { scope: '/' }).catch((error) => { + console.warn('Teleton service worker registration failed', error); + }); + }); +} diff --git a/web/src/shared/store/useTeletonStore.ts b/web/src/shared/store/useTeletonStore.ts new file mode 100644 index 0000000..7ebf0d1 --- /dev/null +++ b/web/src/shared/store/useTeletonStore.ts @@ -0,0 +1,370 @@ +import { create } from 'zustand'; + +import { TeletonAgentService, getManagementAgentStatus } from '../../services/agent.service'; +import { clearEncryptedSettings, loadEncryptedSettings, saveEncryptedSettings } from '../../services/crypto.service'; +import { defaultProxySettings, validateProxySettings } from '../../services/proxy.service'; +import { TdlibService } from '../../services/tdlib.service'; +import type { + AgentSettings, + AgentStatus, + AuthStatus, + ChatMessage, + ChatSummary, + PersistedSettings, + ProxySettings, + TonBalance, + TonTransactionDraft, + UiNotice +} from '../types'; + +interface TeletonState { + authStatus: AuthStatus; + authError?: string; + sessionId: string; + chats: ChatSummary[]; + messagesByChat: Record; + selectedChatId?: number; + proxy: ProxySettings; + agent: AgentSettings; + agentStatus: AgentStatus; + tonBalance?: TonBalance; + notices: UiNotice[]; + bootstrapped: boolean; + bootstrap: () => Promise; + initializeTelegram: () => Promise; + submitPhone: (phoneNumber: string) => Promise; + submitCode: (code: string) => Promise; + submitPassword: (password: string) => Promise; + loadChats: () => Promise; + selectChat: (chatId: number) => Promise; + sendMessage: (text: string) => Promise; + setProxyDraft: (proxy: ProxySettings) => void; + applyProxy: () => Promise; + setAgentSettings: (settings: AgentSettings) => void; + saveSettings: () => Promise; + clearSettings: () => Promise; + connectAgent: () => Promise; + disconnectAgent: () => void; + enableAgent: () => Promise; + disableAgent: () => Promise; + checkManagementStatus: () => Promise; + getTonBalance: (address: string) => Promise; + sendTonTx: (draft: TonTransactionDraft) => Promise; + dismissNotice: (id: string) => void; +} + +const tdlibService = new TdlibService(); +const agentService = new TeletonAgentService(); + +function defaultAgentSettings(): AgentSettings { + return { + wsUrl: import.meta.env.VITE_TELETON_AGENT_WS_URL || 'ws://localhost:8765', + managementUrl: import.meta.env.VITE_TELETON_AGENT_MANAGEMENT_URL || 'https://localhost:7778', + enabled: false + }; +} + +function createSessionId() { + if ('crypto' in globalThis && 'randomUUID' in globalThis.crypto) { + return globalThis.crypto.randomUUID(); + } + + return `session_${Date.now()}_${Math.random().toString(16).slice(2)}`; +} + +function notice(tone: UiNotice['tone'], message: string): UiNotice { + return { + id: `${Date.now()}_${Math.random().toString(16).slice(2)}`, + tone, + message + }; +} + +function authStatusFromTdlibState(stateType: unknown): AuthStatus | null { + switch (stateType) { + case 'authorizationStateWaitPhoneNumber': + return 'phone-required'; + case 'authorizationStateWaitCode': + return 'code-required'; + case 'authorizationStateWaitPassword': + return 'password-required'; + case 'authorizationStateReady': + return 'ready'; + case 'authorizationStateClosed': + return 'idle'; + default: + return null; + } +} + +function addNotice(set: (partial: Partial) => void, get: () => TeletonState, entry: UiNotice) { + set({ notices: [entry, ...get().notices].slice(0, 5) }); +} + +export const useTeletonStore = create((set, get) => ({ + authStatus: 'idle', + sessionId: createSessionId(), + chats: [], + messagesByChat: {}, + proxy: defaultProxySettings(), + agent: defaultAgentSettings(), + agentStatus: { connection: 'disconnected' }, + notices: [], + bootstrapped: false, + + async bootstrap() { + try { + const stored = await loadEncryptedSettings(); + if (stored) { + set({ + proxy: stored.proxy, + agent: stored.agent, + bootstrapped: true + }); + return; + } + } catch (error) { + addNotice(set, get, notice('warning', error instanceof Error ? error.message : 'Encrypted settings could not be loaded.')); + } + + set({ bootstrapped: true }); + }, + + async initializeTelegram() { + set({ authStatus: 'initializing', authError: undefined }); + + const apiId = Number(import.meta.env.VITE_TELEGRAM_API_ID); + const apiHash = import.meta.env.VITE_TELEGRAM_API_HASH?.trim() ?? ''; + + try { + await tdlibService.init({ + apiId, + apiHash, + logVerbosityLevel: Number(import.meta.env.VITE_TDLIB_LOG_VERBOSITY ?? 2), + onUpdate: (update) => { + const authorizationState = update.authorization_state as Record | undefined; + const nextAuthStatus = authStatusFromTdlibState(authorizationState?.['@type']); + + if (nextAuthStatus) { + set({ authStatus: nextAuthStatus }); + if (nextAuthStatus === 'ready') void get().loadChats(); + } + + if (update['@type'] === 'updateNewMessage') { + const message = update.message as ChatMessage | undefined; + if (message?.chatId) { + set({ + messagesByChat: { + ...get().messagesByChat, + [message.chatId]: [...(get().messagesByChat[message.chatId] ?? []), message] + } + }); + } + } + } + }); + + set({ authStatus: 'phone-required' }); + } catch (error) { + const message = error instanceof Error ? error.message : 'TDLib initialization failed.'; + set({ authStatus: 'error', authError: message }); + addNotice(set, get, notice('error', message)); + } + }, + + async submitPhone(phoneNumber) { + try { + await tdlibService.authPhone(phoneNumber); + set({ authStatus: 'code-required', authError: undefined }); + } catch (error) { + const message = error instanceof Error ? error.message : 'Phone authentication failed.'; + set({ authStatus: 'error', authError: message }); + addNotice(set, get, notice('error', message)); + } + }, + + async submitCode(code) { + try { + await tdlibService.authCode(code); + set({ authError: undefined }); + } catch (error) { + const message = error instanceof Error ? error.message : 'Code authentication failed.'; + set({ authStatus: 'code-required', authError: message }); + addNotice(set, get, notice('error', message)); + } + }, + + async submitPassword(password) { + try { + await tdlibService.authPassword(password); + set({ authError: undefined }); + } catch (error) { + const message = error instanceof Error ? error.message : 'Password authentication failed.'; + set({ authStatus: 'password-required', authError: message }); + addNotice(set, get, notice('error', message)); + } + }, + + async loadChats() { + try { + const chats = await tdlibService.getChats(); + set({ + chats, + selectedChatId: get().selectedChatId ?? chats[0]?.id + }); + } catch (error) { + addNotice(set, get, notice('warning', error instanceof Error ? error.message : 'Chats could not be loaded.')); + } + }, + + async selectChat(chatId) { + set({ selectedChatId: chatId }); + + try { + const messages = await tdlibService.getMessages(chatId); + set({ + messagesByChat: { + ...get().messagesByChat, + [chatId]: messages + } + }); + } catch (error) { + addNotice(set, get, notice('warning', error instanceof Error ? error.message : 'Messages could not be loaded.')); + } + }, + + async sendMessage(text) { + const selectedChatId = get().selectedChatId; + if (!selectedChatId || !text.trim()) return; + + const optimistic: ChatMessage = { + id: Date.now(), + chatId: selectedChatId, + sender: 'me', + text: text.trim(), + createdAt: Date.now(), + pending: true + }; + + set({ + messagesByChat: { + ...get().messagesByChat, + [selectedChatId]: [...(get().messagesByChat[selectedChatId] ?? []), optimistic] + } + }); + + try { + await tdlibService.sendMessage(selectedChatId, text.trim()); + } catch (error) { + addNotice(set, get, notice('error', error instanceof Error ? error.message : 'Message send failed.')); + } + }, + + setProxyDraft(proxy) { + set({ proxy }); + }, + + async applyProxy() { + const validation = validateProxySettings(get().proxy); + if (!validation.valid || !validation.normalized) { + addNotice(set, get, notice('error', validation.errors.join(' '))); + return; + } + + set({ proxy: validation.normalized }); + + try { + if (tdlibService.initialized) { + await tdlibService.setProxy(validation.normalized); + } + await get().saveSettings(); + addNotice(set, get, notice('success', 'Proxy settings applied.')); + } catch (error) { + addNotice(set, get, notice('error', error instanceof Error ? error.message : 'Proxy settings failed.')); + } + }, + + setAgentSettings(settings) { + set({ agent: settings }); + }, + + async saveSettings() { + const persisted: PersistedSettings = { + proxy: get().proxy, + agent: get().agent, + consentedAt: new Date().toISOString() + }; + await saveEncryptedSettings(persisted); + }, + + async clearSettings() { + await clearEncryptedSettings(); + set({ + proxy: defaultProxySettings(), + agent: defaultAgentSettings() + }); + addNotice(set, get, notice('success', 'Encrypted settings cleared.')); + }, + + async connectAgent() { + set({ agentStatus: { connection: 'connecting' } }); + + try { + await agentService.connect(get().agent.wsUrl); + set({ agentStatus: { connection: 'connected' } }); + } catch (error) { + const message = error instanceof Error ? error.message : 'Agent connection failed.'; + set({ agentStatus: { connection: 'error', error: message } }); + addNotice(set, get, notice('error', message)); + } + }, + + disconnectAgent() { + agentService.disconnect(); + set({ agentStatus: { connection: 'disconnected' } }); + }, + + async enableAgent() { + if (!agentService.connected) await get().connectAgent(); + await agentService.enable({ session: get().sessionId }); + set({ agent: { ...get().agent, enabled: true } }); + await get().saveSettings(); + }, + + async disableAgent() { + if (agentService.connected) await agentService.disable(); + set({ agent: { ...get().agent, enabled: false } }); + await get().saveSettings(); + }, + + async checkManagementStatus() { + try { + const status = await getManagementAgentStatus({ + baseUrl: get().agent.managementUrl, + apiKey: get().agent.managementApiKey + }); + set({ agentStatus: status }); + } catch (error) { + const message = error instanceof Error ? error.message : 'Management API status failed.'; + set({ agentStatus: { connection: 'error', error: message } }); + addNotice(set, get, notice('warning', message)); + } + }, + + async getTonBalance(address) { + if (!agentService.connected) await get().connectAgent(); + const balance = await agentService.getTonBalance(address); + set({ tonBalance: balance }); + }, + + async sendTonTx(draft) { + if (!agentService.connected) await get().connectAgent(); + const result = await agentService.sendTx(draft); + addNotice(set, get, notice('success', `Transaction submitted: ${result.txHash}`)); + return result.txHash; + }, + + dismissNotice(id) { + set({ notices: get().notices.filter((entry) => entry.id !== id) }); + } +})); diff --git a/web/src/shared/types.ts b/web/src/shared/types.ts new file mode 100644 index 0000000..1611276 --- /dev/null +++ b/web/src/shared/types.ts @@ -0,0 +1,77 @@ +export type AuthStatus = + | 'idle' + | 'initializing' + | 'phone-required' + | 'code-required' + | 'password-required' + | 'ready' + | 'error'; + +export interface ChatSummary { + id: number; + title: string; + unreadCount: number; + lastMessage?: string; + updatedAt?: number; +} + +export interface ChatMessage { + id: number; + chatId: number; + sender: 'me' | 'them' | 'system'; + text: string; + createdAt: number; + pending?: boolean; +} + +export type ProxyType = 'none' | 'socks5' | 'mtproto'; + +export interface ProxySettings { + enabled: boolean; + type: ProxyType; + host: string; + port: number; + username?: string; + password?: string; + secret?: string; +} + +export interface AgentSettings { + wsUrl: string; + managementUrl: string; + managementApiKey?: string; + enabled: boolean; +} + +export type AgentConnectionState = 'disconnected' | 'connecting' | 'connected' | 'error'; + +export interface AgentStatus { + connection: AgentConnectionState; + lifecycle?: 'stopped' | 'starting' | 'running' | 'stopping'; + error?: string; + uptime?: number; +} + +export interface TonBalance { + address: string; + balance: string; + currency: 'TON'; +} + +export interface TonTransactionDraft { + to: string; + amount: string; + comment?: string; +} + +export interface UiNotice { + id: string; + tone: 'info' | 'success' | 'warning' | 'error'; + message: string; +} + +export interface PersistedSettings { + proxy: ProxySettings; + agent: AgentSettings; + consentedAt: string; +} diff --git a/web/src/vite-env.d.ts b/web/src/vite-env.d.ts new file mode 100644 index 0000000..2367739 --- /dev/null +++ b/web/src/vite-env.d.ts @@ -0,0 +1,22 @@ +/// + +interface ImportMetaEnv { + readonly VITE_TELEGRAM_API_ID?: string; + readonly VITE_TELEGRAM_API_HASH?: string; + readonly VITE_TDLIB_LOG_VERBOSITY?: string; + readonly VITE_TELETON_AGENT_WS_URL?: string; + readonly VITE_TELETON_AGENT_MANAGEMENT_URL?: string; + readonly VITE_TONCONNECT_MANIFEST_URL?: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} + +declare module 'tdweb' { + export default class TdClient { + constructor(options: Record); + send(query: Record): Promise>; + close(): void; + } +} diff --git a/web/src/widgets/ChatList.tsx b/web/src/widgets/ChatList.tsx new file mode 100644 index 0000000..76f6607 --- /dev/null +++ b/web/src/widgets/ChatList.tsx @@ -0,0 +1,38 @@ +import type { ChatSummary } from '../shared/types'; + +interface ChatListProps { + chats: ChatSummary[]; + selectedChatId?: number; + onSelect: (chatId: number) => void; +} + +export function ChatList({ chats, selectedChatId, onSelect }: ChatListProps) { + if (chats.length === 0) { + return
No chats loaded.
; + } + + return ( +
+ {chats.map((chat) => ( + + ))} +
+ ); +} diff --git a/web/src/widgets/InputBar.tsx b/web/src/widgets/InputBar.tsx new file mode 100644 index 0000000..5493cf8 --- /dev/null +++ b/web/src/widgets/InputBar.tsx @@ -0,0 +1,41 @@ +import { Send } from 'lucide-react'; +import { FormEvent, useState } from 'react'; + +interface InputBarProps { + disabled?: boolean; + onSend: (text: string) => void; +} + +export function InputBar({ disabled, onSend }: InputBarProps) { + const [text, setText] = useState(''); + + const submit = (event: FormEvent) => { + event.preventDefault(); + const value = text.trim(); + if (!value) return; + onSend(value); + setText(''); + }; + + return ( +
+