From 46546e121f8374a146238eb2da2d349630723842 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Mon, 26 Jan 2026 09:17:01 +0000 Subject: [PATCH] Refactor: Move webpeer from lib to main repository. --- .eslintrc | 1 + eslint.config.mjs | 2 +- src/apps/webpeer/.gitignore | 24 + src/apps/webpeer/README.md | 30 + src/apps/webpeer/deno.lock | 1101 +++++++++++++++++++++ src/apps/webpeer/index.html | 17 + src/apps/webpeer/package.json | 25 + src/apps/webpeer/public/icon.svg | 52 + src/apps/webpeer/public/manifest.json | 26 + src/apps/webpeer/src/App.svelte | 5 + src/apps/webpeer/src/CommandsShim.ts | 23 + src/apps/webpeer/src/P2PReplicatorShim.ts | 364 +++++++ src/apps/webpeer/src/SyncMain.svelte | 112 +++ src/apps/webpeer/src/UITest.svelte | 74 ++ src/apps/webpeer/src/app.css | 112 +++ src/apps/webpeer/src/assets/svelte.svg | 1 + src/apps/webpeer/src/main.ts | 9 + src/apps/webpeer/src/uitest.ts | 9 + src/apps/webpeer/src/vite-env.d.ts | 2 + src/apps/webpeer/svelte.config.js | 7 + src/apps/webpeer/tsconfig.app.json | 25 + src/apps/webpeer/tsconfig.json | 4 + src/apps/webpeer/tsconfig.node.json | 28 + src/apps/webpeer/ui.html | 16 + src/apps/webpeer/vite.config.ts | 24 + test/shell/p2p-start.sh | 2 +- tsconfig.json | 9 +- vite.config.ts | 5 +- 28 files changed, 2097 insertions(+), 12 deletions(-) create mode 100644 src/apps/webpeer/.gitignore create mode 100644 src/apps/webpeer/README.md create mode 100644 src/apps/webpeer/deno.lock create mode 100644 src/apps/webpeer/index.html create mode 100644 src/apps/webpeer/package.json create mode 100644 src/apps/webpeer/public/icon.svg create mode 100644 src/apps/webpeer/public/manifest.json create mode 100644 src/apps/webpeer/src/App.svelte create mode 100644 src/apps/webpeer/src/CommandsShim.ts create mode 100644 src/apps/webpeer/src/P2PReplicatorShim.ts create mode 100644 src/apps/webpeer/src/SyncMain.svelte create mode 100644 src/apps/webpeer/src/UITest.svelte create mode 100644 src/apps/webpeer/src/app.css create mode 100644 src/apps/webpeer/src/assets/svelte.svg create mode 100644 src/apps/webpeer/src/main.ts create mode 100644 src/apps/webpeer/src/uitest.ts create mode 100644 src/apps/webpeer/src/vite-env.d.ts create mode 100644 src/apps/webpeer/svelte.config.js create mode 100644 src/apps/webpeer/tsconfig.app.json create mode 100644 src/apps/webpeer/tsconfig.json create mode 100644 src/apps/webpeer/tsconfig.node.json create mode 100644 src/apps/webpeer/ui.html create mode 100644 src/apps/webpeer/vite.config.ts diff --git a/.eslintrc b/.eslintrc index 0f74a9b..f55fe98 100644 --- a/.eslintrc +++ b/.eslintrc @@ -26,6 +26,7 @@ "**/tests.ts", "**/**test.ts", "**/**.test.ts", + "src/apps/**", "esbuild.*.mjs", "terser.*.mjs" ], diff --git a/eslint.config.mjs b/eslint.config.mjs index 7dd29b2..b2fbf76 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -40,7 +40,7 @@ export default [ "src/lib/test", "src/lib/src/cli", "**/main.js", - "src/lib/apps/webpeer/*", + "src/apps/**/*", ".prettierrc.*.mjs", ".prettierrc.mjs", "*.config.mjs" diff --git a/src/apps/webpeer/.gitignore b/src/apps/webpeer/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/src/apps/webpeer/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/src/apps/webpeer/README.md b/src/apps/webpeer/README.md new file mode 100644 index 0000000..81a2af5 --- /dev/null +++ b/src/apps/webpeer/README.md @@ -0,0 +1,30 @@ +# A pseudo client for Self-hosted LiveSync Peer-to-Peer Sync mode + +## What is it for? + +This is a pseudo client for the Self-hosted LiveSync Peer-to-Peer Sync mode. It is a simple pure-client-side web-application that can be connected to the Self-hosted LiveSync in peer-to-peer. + +As long as you have a browser, it starts up, so if you leave it opened some device, it can replace your existing remote servers such as CouchDB. + +> [!IMPORTANT] +> Of course, it has not been fully tested. Rather, it was created to be tested. + +This pseudo client actually receives the data from other devices, and sends if some device requests it. However, it does not store **files** in the local storage. If you want to purge the data, please purge the browser's cache and indexedDB, local storage, etc. + +## How to use it? + +We can build the application by running the following command: + +```bash +$ deno task build +``` + +Then, open the `dist/index.html` in the browser. It can be configured as the same as the Self-hosted LiveSync (Same components are used[^1]). + +## Some notes + +I will launch this application in the github pages later, so will be able to use it without building it. However, that shares the origin. Hence, the application that your have built and deployed would be more secure. + + +[^1]: Congrats! I made it modular. Finally... + diff --git a/src/apps/webpeer/deno.lock b/src/apps/webpeer/deno.lock new file mode 100644 index 0000000..e908f88 --- /dev/null +++ b/src/apps/webpeer/deno.lock @@ -0,0 +1,1101 @@ +{ + "version": "5", + "specifiers": { + "npm:@sveltejs/vite-plugin-svelte@^6.2.1": "6.2.4_svelte@5.41.1__acorn@8.15.0_vite@7.3.1__picomatch@4.0.3", + "npm:@tsconfig/svelte@^5.0.5": "5.0.6", + "npm:eslint-plugin-svelte@^3.12.4": "3.14.0_eslint@9.39.2_svelte@5.41.1__acorn@8.15.0_postcss@8.5.6", + "npm:svelte-check@^4.3.3": "4.3.5_svelte@5.41.1__acorn@8.15.0_typescript@5.9.3", + "npm:svelte@5.41.1": "5.41.1_acorn@8.15.0", + "npm:typescript@5.9.3": "5.9.3", + "npm:vite@^7.3.0": "7.3.1_picomatch@4.0.3" + }, + "npm": { + "@esbuild/aix-ppc64@0.27.2": { + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "os": ["aix"], + "cpu": ["ppc64"] + }, + "@esbuild/android-arm64@0.27.2": { + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@esbuild/android-arm@0.27.2": { + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "os": ["android"], + "cpu": ["arm"] + }, + "@esbuild/android-x64@0.27.2": { + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "os": ["android"], + "cpu": ["x64"] + }, + "@esbuild/darwin-arm64@0.27.2": { + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@esbuild/darwin-x64@0.27.2": { + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@esbuild/freebsd-arm64@0.27.2": { + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "os": ["freebsd"], + "cpu": ["arm64"] + }, + "@esbuild/freebsd-x64@0.27.2": { + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@esbuild/linux-arm64@0.27.2": { + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@esbuild/linux-arm@0.27.2": { + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@esbuild/linux-ia32@0.27.2": { + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "os": ["linux"], + "cpu": ["ia32"] + }, + "@esbuild/linux-loong64@0.27.2": { + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@esbuild/linux-mips64el@0.27.2": { + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "os": ["linux"], + "cpu": ["mips64el"] + }, + "@esbuild/linux-ppc64@0.27.2": { + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@esbuild/linux-riscv64@0.27.2": { + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@esbuild/linux-s390x@0.27.2": { + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@esbuild/linux-x64@0.27.2": { + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@esbuild/netbsd-arm64@0.27.2": { + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "os": ["netbsd"], + "cpu": ["arm64"] + }, + "@esbuild/netbsd-x64@0.27.2": { + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "os": ["netbsd"], + "cpu": ["x64"] + }, + "@esbuild/openbsd-arm64@0.27.2": { + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "os": ["openbsd"], + "cpu": ["arm64"] + }, + "@esbuild/openbsd-x64@0.27.2": { + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "os": ["openbsd"], + "cpu": ["x64"] + }, + "@esbuild/openharmony-arm64@0.27.2": { + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "os": ["openharmony"], + "cpu": ["arm64"] + }, + "@esbuild/sunos-x64@0.27.2": { + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "os": ["sunos"], + "cpu": ["x64"] + }, + "@esbuild/win32-arm64@0.27.2": { + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@esbuild/win32-ia32@0.27.2": { + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@esbuild/win32-x64@0.27.2": { + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@eslint-community/eslint-utils@4.9.1_eslint@9.39.2": { + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dependencies": [ + "eslint", + "eslint-visitor-keys@3.4.3" + ] + }, + "@eslint-community/regexpp@4.12.2": { + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==" + }, + "@eslint/config-array@0.21.1": { + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dependencies": [ + "@eslint/object-schema", + "debug", + "minimatch" + ] + }, + "@eslint/config-helpers@0.4.2": { + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dependencies": [ + "@eslint/core" + ] + }, + "@eslint/core@0.17.0": { + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dependencies": [ + "@types/json-schema" + ] + }, + "@eslint/eslintrc@3.3.3": { + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dependencies": [ + "ajv", + "debug", + "espree", + "globals@14.0.0", + "ignore", + "import-fresh", + "js-yaml", + "minimatch", + "strip-json-comments" + ] + }, + "@eslint/js@9.39.2": { + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==" + }, + "@eslint/object-schema@2.1.7": { + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==" + }, + "@eslint/plugin-kit@0.4.1": { + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dependencies": [ + "@eslint/core", + "levn" + ] + }, + "@humanfs/core@0.19.1": { + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==" + }, + "@humanfs/node@0.16.7": { + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dependencies": [ + "@humanfs/core", + "@humanwhocodes/retry" + ] + }, + "@humanwhocodes/module-importer@1.0.1": { + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==" + }, + "@humanwhocodes/retry@0.4.3": { + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==" + }, + "@jridgewell/gen-mapping@0.3.13": { + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dependencies": [ + "@jridgewell/sourcemap-codec", + "@jridgewell/trace-mapping" + ] + }, + "@jridgewell/remapping@2.3.5": { + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dependencies": [ + "@jridgewell/gen-mapping", + "@jridgewell/trace-mapping" + ] + }, + "@jridgewell/resolve-uri@3.1.2": { + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" + }, + "@jridgewell/sourcemap-codec@1.5.5": { + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" + }, + "@jridgewell/trace-mapping@0.3.31": { + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dependencies": [ + "@jridgewell/resolve-uri", + "@jridgewell/sourcemap-codec" + ] + }, + "@rollup/rollup-android-arm-eabi@4.56.0": { + "integrity": "sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw==", + "os": ["android"], + "cpu": ["arm"] + }, + "@rollup/rollup-android-arm64@4.56.0": { + "integrity": "sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@rollup/rollup-darwin-arm64@4.56.0": { + "integrity": "sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@rollup/rollup-darwin-x64@4.56.0": { + "integrity": "sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@rollup/rollup-freebsd-arm64@4.56.0": { + "integrity": "sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ==", + "os": ["freebsd"], + "cpu": ["arm64"] + }, + "@rollup/rollup-freebsd-x64@4.56.0": { + "integrity": "sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@rollup/rollup-linux-arm-gnueabihf@4.56.0": { + "integrity": "sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@rollup/rollup-linux-arm-musleabihf@4.56.0": { + "integrity": "sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@rollup/rollup-linux-arm64-gnu@4.56.0": { + "integrity": "sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@rollup/rollup-linux-arm64-musl@4.56.0": { + "integrity": "sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@rollup/rollup-linux-loong64-gnu@4.56.0": { + "integrity": "sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@rollup/rollup-linux-loong64-musl@4.56.0": { + "integrity": "sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@rollup/rollup-linux-ppc64-gnu@4.56.0": { + "integrity": "sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@rollup/rollup-linux-ppc64-musl@4.56.0": { + "integrity": "sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@rollup/rollup-linux-riscv64-gnu@4.56.0": { + "integrity": "sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@rollup/rollup-linux-riscv64-musl@4.56.0": { + "integrity": "sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@rollup/rollup-linux-s390x-gnu@4.56.0": { + "integrity": "sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@rollup/rollup-linux-x64-gnu@4.56.0": { + "integrity": "sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@rollup/rollup-linux-x64-musl@4.56.0": { + "integrity": "sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@rollup/rollup-openbsd-x64@4.56.0": { + "integrity": "sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==", + "os": ["openbsd"], + "cpu": ["x64"] + }, + "@rollup/rollup-openharmony-arm64@4.56.0": { + "integrity": "sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ==", + "os": ["openharmony"], + "cpu": ["arm64"] + }, + "@rollup/rollup-win32-arm64-msvc@4.56.0": { + "integrity": "sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@rollup/rollup-win32-ia32-msvc@4.56.0": { + "integrity": "sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@rollup/rollup-win32-x64-gnu@4.56.0": { + "integrity": "sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@rollup/rollup-win32-x64-msvc@4.56.0": { + "integrity": "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@sveltejs/acorn-typescript@1.0.8_acorn@8.15.0": { + "integrity": "sha512-esgN+54+q0NjB0Y/4BomT9samII7jGwNy/2a3wNZbT2A2RpmXsXwUt24LvLhx6jUq2gVk4cWEvcRO6MFQbOfNA==", + "dependencies": [ + "acorn" + ] + }, + "@sveltejs/vite-plugin-svelte-inspector@5.0.2_@sveltejs+vite-plugin-svelte@6.2.4__svelte@5.41.1___acorn@8.15.0__vite@7.3.1___picomatch@4.0.3_svelte@5.41.1__acorn@8.15.0_vite@7.3.1__picomatch@4.0.3": { + "integrity": "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==", + "dependencies": [ + "@sveltejs/vite-plugin-svelte", + "obug", + "svelte", + "vite" + ] + }, + "@sveltejs/vite-plugin-svelte@6.2.4_svelte@5.41.1__acorn@8.15.0_vite@7.3.1__picomatch@4.0.3": { + "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", + "dependencies": [ + "@sveltejs/vite-plugin-svelte-inspector", + "deepmerge", + "magic-string", + "obug", + "svelte", + "vite", + "vitefu" + ] + }, + "@tsconfig/svelte@5.0.6": { + "integrity": "sha512-yGxYL0I9eETH1/DR9qVJey4DAsCdeau4a9wYPKuXfEhm8lFO8wg+LLYJjIpAm6Fw7HSlhepPhYPDop75485yWQ==" + }, + "@types/estree@1.0.8": { + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" + }, + "@types/json-schema@7.0.15": { + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + }, + "acorn-jsx@5.3.2_acorn@8.15.0": { + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dependencies": [ + "acorn" + ] + }, + "acorn@8.15.0": { + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "bin": true + }, + "ajv@6.12.6": { + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": [ + "fast-deep-equal", + "fast-json-stable-stringify", + "json-schema-traverse", + "uri-js" + ] + }, + "ansi-styles@4.3.0": { + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": [ + "color-convert" + ] + }, + "argparse@2.0.1": { + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "aria-query@5.3.2": { + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==" + }, + "axobject-query@4.1.0": { + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==" + }, + "balanced-match@1.0.2": { + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "brace-expansion@1.1.12": { + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dependencies": [ + "balanced-match", + "concat-map" + ] + }, + "callsites@3.1.0": { + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, + "chalk@4.1.2": { + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": [ + "ansi-styles", + "supports-color" + ] + }, + "chokidar@4.0.3": { + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dependencies": [ + "readdirp" + ] + }, + "clsx@2.1.1": { + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" + }, + "color-convert@2.0.1": { + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": [ + "color-name" + ] + }, + "color-name@1.1.4": { + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "concat-map@0.0.1": { + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "cross-spawn@7.0.6": { + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dependencies": [ + "path-key", + "shebang-command", + "which" + ] + }, + "cssesc@3.0.0": { + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "bin": true + }, + "debug@4.4.3": { + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": [ + "ms" + ] + }, + "deep-is@0.1.4": { + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "deepmerge@4.3.1": { + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" + }, + "esbuild@0.27.2": { + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "optionalDependencies": [ + "@esbuild/aix-ppc64", + "@esbuild/android-arm", + "@esbuild/android-arm64", + "@esbuild/android-x64", + "@esbuild/darwin-arm64", + "@esbuild/darwin-x64", + "@esbuild/freebsd-arm64", + "@esbuild/freebsd-x64", + "@esbuild/linux-arm", + "@esbuild/linux-arm64", + "@esbuild/linux-ia32", + "@esbuild/linux-loong64", + "@esbuild/linux-mips64el", + "@esbuild/linux-ppc64", + "@esbuild/linux-riscv64", + "@esbuild/linux-s390x", + "@esbuild/linux-x64", + "@esbuild/netbsd-arm64", + "@esbuild/netbsd-x64", + "@esbuild/openbsd-arm64", + "@esbuild/openbsd-x64", + "@esbuild/openharmony-arm64", + "@esbuild/sunos-x64", + "@esbuild/win32-arm64", + "@esbuild/win32-ia32", + "@esbuild/win32-x64" + ], + "scripts": true, + "bin": true + }, + "escape-string-regexp@4.0.0": { + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "eslint-plugin-svelte@3.14.0_eslint@9.39.2_svelte@5.41.1__acorn@8.15.0_postcss@8.5.6": { + "integrity": "sha512-Isw0GvaMm0yHxAj71edAdGFh28ufYs+6rk2KlbbZphnqZAzrH3Se3t12IFh2H9+1F/jlDhBBL4oiOJmLqmYX0g==", + "dependencies": [ + "@eslint-community/eslint-utils", + "@jridgewell/sourcemap-codec", + "eslint", + "esutils", + "globals@16.5.0", + "known-css-properties", + "postcss", + "postcss-load-config", + "postcss-safe-parser", + "semver", + "svelte", + "svelte-eslint-parser" + ], + "optionalPeers": [ + "svelte" + ] + }, + "eslint-scope@8.4.0": { + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dependencies": [ + "esrecurse", + "estraverse" + ] + }, + "eslint-visitor-keys@3.4.3": { + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==" + }, + "eslint-visitor-keys@4.2.1": { + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==" + }, + "eslint@9.39.2": { + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dependencies": [ + "@eslint-community/eslint-utils", + "@eslint-community/regexpp", + "@eslint/config-array", + "@eslint/config-helpers", + "@eslint/core", + "@eslint/eslintrc", + "@eslint/js", + "@eslint/plugin-kit", + "@humanfs/node", + "@humanwhocodes/module-importer", + "@humanwhocodes/retry", + "@types/estree", + "ajv", + "chalk", + "cross-spawn", + "debug", + "escape-string-regexp", + "eslint-scope", + "eslint-visitor-keys@4.2.1", + "espree", + "esquery", + "esutils", + "fast-deep-equal", + "file-entry-cache", + "find-up", + "glob-parent", + "ignore", + "imurmurhash", + "is-glob", + "json-stable-stringify-without-jsonify", + "lodash.merge", + "minimatch", + "natural-compare", + "optionator" + ], + "bin": true + }, + "esm-env@1.2.2": { + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==" + }, + "espree@10.4.0_acorn@8.15.0": { + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dependencies": [ + "acorn", + "acorn-jsx", + "eslint-visitor-keys@4.2.1" + ] + }, + "esquery@1.7.0": { + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dependencies": [ + "estraverse" + ] + }, + "esrap@2.2.2": { + "integrity": "sha512-zA6497ha+qKvoWIK+WM9NAh5ni17sKZKhbS5B3PoYbBvaYHZWoS33zmFybmyqpn07RLUxSmn+RCls2/XF+d0oQ==", + "dependencies": [ + "@jridgewell/sourcemap-codec" + ] + }, + "esrecurse@4.3.0": { + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dependencies": [ + "estraverse" + ] + }, + "estraverse@5.3.0": { + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + }, + "esutils@2.0.3": { + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "fast-deep-equal@3.1.3": { + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-json-stable-stringify@2.1.0": { + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-levenshtein@2.0.6": { + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "fdir@6.5.0_picomatch@4.0.3": { + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dependencies": [ + "picomatch" + ], + "optionalPeers": [ + "picomatch" + ] + }, + "file-entry-cache@8.0.0": { + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dependencies": [ + "flat-cache" + ] + }, + "find-up@5.0.0": { + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": [ + "locate-path", + "path-exists" + ] + }, + "flat-cache@4.0.1": { + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dependencies": [ + "flatted", + "keyv" + ] + }, + "flatted@3.3.3": { + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==" + }, + "fsevents@2.3.3": { + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "os": ["darwin"], + "scripts": true + }, + "glob-parent@6.0.2": { + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": [ + "is-glob" + ] + }, + "globals@14.0.0": { + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==" + }, + "globals@16.5.0": { + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==" + }, + "has-flag@4.0.0": { + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "ignore@5.3.2": { + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==" + }, + "import-fresh@3.3.1": { + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dependencies": [ + "parent-module", + "resolve-from" + ] + }, + "imurmurhash@0.1.4": { + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" + }, + "is-extglob@2.1.1": { + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + }, + "is-glob@4.0.3": { + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": [ + "is-extglob" + ] + }, + "is-reference@3.0.3": { + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dependencies": [ + "@types/estree" + ] + }, + "isexe@2.0.0": { + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "js-yaml@4.1.1": { + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dependencies": [ + "argparse" + ], + "bin": true + }, + "json-buffer@3.0.1": { + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "json-schema-traverse@0.4.1": { + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stable-stringify-without-jsonify@1.0.1": { + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, + "keyv@4.5.4": { + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dependencies": [ + "json-buffer" + ] + }, + "known-css-properties@0.37.0": { + "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==" + }, + "levn@0.4.1": { + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dependencies": [ + "prelude-ls", + "type-check" + ] + }, + "lilconfig@2.1.0": { + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==" + }, + "locate-character@3.0.0": { + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==" + }, + "locate-path@6.0.0": { + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": [ + "p-locate" + ] + }, + "lodash.merge@4.6.2": { + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "magic-string@0.30.21": { + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dependencies": [ + "@jridgewell/sourcemap-codec" + ] + }, + "minimatch@3.1.2": { + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": [ + "brace-expansion" + ] + }, + "mri@1.2.0": { + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==" + }, + "ms@2.1.3": { + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "nanoid@3.3.11": { + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "bin": true + }, + "natural-compare@1.4.0": { + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, + "obug@2.1.1": { + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==" + }, + "optionator@0.9.4": { + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dependencies": [ + "deep-is", + "fast-levenshtein", + "levn", + "prelude-ls", + "type-check", + "word-wrap" + ] + }, + "p-limit@3.1.0": { + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": [ + "yocto-queue" + ] + }, + "p-locate@5.0.0": { + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": [ + "p-limit" + ] + }, + "parent-module@1.0.1": { + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": [ + "callsites" + ] + }, + "path-exists@4.0.0": { + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "path-key@3.1.1": { + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "picocolors@1.1.1": { + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "picomatch@4.0.3": { + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==" + }, + "postcss-load-config@3.1.4_postcss@8.5.6": { + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dependencies": [ + "lilconfig", + "postcss", + "yaml" + ], + "optionalPeers": [ + "postcss" + ] + }, + "postcss-safe-parser@7.0.1_postcss@8.5.6": { + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", + "dependencies": [ + "postcss" + ] + }, + "postcss-scss@4.0.9_postcss@8.5.6": { + "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", + "dependencies": [ + "postcss" + ] + }, + "postcss-selector-parser@7.1.1": { + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "dependencies": [ + "cssesc", + "util-deprecate" + ] + }, + "postcss@8.5.6": { + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dependencies": [ + "nanoid", + "picocolors", + "source-map-js" + ] + }, + "prelude-ls@1.2.1": { + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" + }, + "punycode@2.3.1": { + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" + }, + "readdirp@4.1.2": { + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==" + }, + "resolve-from@4.0.0": { + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, + "rollup@4.56.0": { + "integrity": "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==", + "dependencies": [ + "@types/estree" + ], + "optionalDependencies": [ + "@rollup/rollup-android-arm-eabi", + "@rollup/rollup-android-arm64", + "@rollup/rollup-darwin-arm64", + "@rollup/rollup-darwin-x64", + "@rollup/rollup-freebsd-arm64", + "@rollup/rollup-freebsd-x64", + "@rollup/rollup-linux-arm-gnueabihf", + "@rollup/rollup-linux-arm-musleabihf", + "@rollup/rollup-linux-arm64-gnu", + "@rollup/rollup-linux-arm64-musl", + "@rollup/rollup-linux-loong64-gnu", + "@rollup/rollup-linux-loong64-musl", + "@rollup/rollup-linux-ppc64-gnu", + "@rollup/rollup-linux-ppc64-musl", + "@rollup/rollup-linux-riscv64-gnu", + "@rollup/rollup-linux-riscv64-musl", + "@rollup/rollup-linux-s390x-gnu", + "@rollup/rollup-linux-x64-gnu", + "@rollup/rollup-linux-x64-musl", + "@rollup/rollup-openbsd-x64", + "@rollup/rollup-openharmony-arm64", + "@rollup/rollup-win32-arm64-msvc", + "@rollup/rollup-win32-ia32-msvc", + "@rollup/rollup-win32-x64-gnu", + "@rollup/rollup-win32-x64-msvc", + "fsevents" + ], + "bin": true + }, + "sade@1.8.1": { + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dependencies": [ + "mri" + ] + }, + "semver@7.7.3": { + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "bin": true + }, + "shebang-command@2.0.0": { + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": [ + "shebang-regex" + ] + }, + "shebang-regex@3.0.0": { + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "source-map-js@1.2.1": { + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" + }, + "strip-json-comments@3.1.1": { + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + }, + "supports-color@7.2.0": { + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": [ + "has-flag" + ] + }, + "svelte-check@4.3.5_svelte@5.41.1__acorn@8.15.0_typescript@5.9.3": { + "integrity": "sha512-e4VWZETyXaKGhpkxOXP+B/d0Fp/zKViZoJmneZWe/05Y2aqSKj3YN2nLfYPJBQ87WEiY4BQCQ9hWGu9mPT1a1Q==", + "dependencies": [ + "@jridgewell/trace-mapping", + "chokidar", + "fdir", + "picocolors", + "sade", + "svelte", + "typescript" + ], + "bin": true + }, + "svelte-eslint-parser@1.4.1_svelte@5.41.1__acorn@8.15.0_postcss@8.5.6": { + "integrity": "sha512-1eqkfQ93goAhjAXxZiu1SaKI9+0/sxp4JIWQwUpsz7ybehRE5L8dNuz7Iry7K22R47p5/+s9EM+38nHV2OlgXA==", + "dependencies": [ + "eslint-scope", + "eslint-visitor-keys@4.2.1", + "espree", + "postcss", + "postcss-scss", + "postcss-selector-parser", + "svelte" + ], + "optionalPeers": [ + "svelte" + ] + }, + "svelte@5.41.1_acorn@8.15.0": { + "integrity": "sha512-0a/huwc8e2es+7KFi70esqsReRfRbrT8h1cJSY/+z1lF0yKM6TT+//HYu28Yxstr50H7ifaqZRDGd0KuKDxP7w==", + "dependencies": [ + "@jridgewell/remapping", + "@jridgewell/sourcemap-codec", + "@sveltejs/acorn-typescript", + "@types/estree", + "acorn", + "aria-query", + "axobject-query", + "clsx", + "esm-env", + "esrap", + "is-reference", + "locate-character", + "magic-string", + "zimmerframe" + ] + }, + "tinyglobby@0.2.15_picomatch@4.0.3": { + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dependencies": [ + "fdir", + "picomatch" + ] + }, + "type-check@0.4.0": { + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dependencies": [ + "prelude-ls" + ] + }, + "typescript@5.9.3": { + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "bin": true + }, + "uri-js@4.4.1": { + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": [ + "punycode" + ] + }, + "util-deprecate@1.0.2": { + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "vite@7.3.1_picomatch@4.0.3": { + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dependencies": [ + "esbuild", + "fdir", + "picomatch", + "postcss", + "rollup", + "tinyglobby" + ], + "optionalDependencies": [ + "fsevents" + ], + "bin": true + }, + "vitefu@1.1.1_vite@7.3.1__picomatch@4.0.3": { + "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", + "dependencies": [ + "vite" + ], + "optionalPeers": [ + "vite" + ] + }, + "which@2.0.2": { + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": [ + "isexe" + ], + "bin": true + }, + "word-wrap@1.2.5": { + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==" + }, + "yaml@1.10.2": { + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" + }, + "yocto-queue@0.1.0": { + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + }, + "zimmerframe@1.1.4": { + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==" + } + }, + "workspace": { + "packageJson": { + "dependencies": [ + "npm:@sveltejs/vite-plugin-svelte@^6.2.1", + "npm:@tsconfig/svelte@^5.0.5", + "npm:eslint-plugin-svelte@^3.12.4", + "npm:svelte-check@^4.3.3", + "npm:svelte@5.41.1", + "npm:typescript@5.9.3", + "npm:vite@^7.3.0" + ] + } + } +} diff --git a/src/apps/webpeer/index.html b/src/apps/webpeer/index.html new file mode 100644 index 0000000..1753155 --- /dev/null +++ b/src/apps/webpeer/index.html @@ -0,0 +1,17 @@ + + + + + + + + + Peer-to-Peer Daemon on Browser + + + +
+ + + + \ No newline at end of file diff --git a/src/apps/webpeer/package.json b/src/apps/webpeer/package.json new file mode 100644 index 0000000..dd12898 --- /dev/null +++ b/src/apps/webpeer/package.json @@ -0,0 +1,25 @@ +{ + "name": "webpeer", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json" + }, + "dependencies": {}, + "devDependencies": { + "eslint-plugin-svelte": "^3.12.4", + "@sveltejs/vite-plugin-svelte": "^6.2.1", + "@tsconfig/svelte": "^5.0.5", + "svelte": "5.41.1", + "svelte-check": "^4.3.3", + "typescript": "5.9.3", + "vite": "^7.3.0" + }, + "imports": { + "../../src/worker/bgWorker.ts": "../../src/worker/bgWorker.mock.ts" + } +} diff --git a/src/apps/webpeer/public/icon.svg b/src/apps/webpeer/public/icon.svg new file mode 100644 index 0000000..a7fd775 --- /dev/null +++ b/src/apps/webpeer/public/icon.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + diff --git a/src/apps/webpeer/public/manifest.json b/src/apps/webpeer/public/manifest.json new file mode 100644 index 0000000..39cb501 --- /dev/null +++ b/src/apps/webpeer/public/manifest.json @@ -0,0 +1,26 @@ +{ + "name": "WebPeer - Pseudo client for Self-hosted LiveSync Peer-to-Peer Replication", + "short_name": "WepPeer", + "description": "A web-based pseudo peer-to-peer replication client using as like server for background sync.", + "start_url": "./", + "display": "standalone", + "background_color": "#fff", + "theme_color": "#fff", + "orientation": "any", + "icons": [ + { + "src": "./icon.svg", + "sizes": "512x512", + "type": "image/svg+xml", + "maskable": true + } + ], + "additional_icons": [ + { + "src": "./icon.svg", + "sizes": "512x512", + "type": "image/svg+xml", + "maskable": true + } + ] +} diff --git a/src/apps/webpeer/src/App.svelte b/src/apps/webpeer/src/App.svelte new file mode 100644 index 0000000..bf32f50 --- /dev/null +++ b/src/apps/webpeer/src/App.svelte @@ -0,0 +1,5 @@ + + + diff --git a/src/apps/webpeer/src/CommandsShim.ts b/src/apps/webpeer/src/CommandsShim.ts new file mode 100644 index 0000000..0f53cd1 --- /dev/null +++ b/src/apps/webpeer/src/CommandsShim.ts @@ -0,0 +1,23 @@ +import { LOG_LEVEL_VERBOSE } from "@lib/common/types"; + +import { defaultLoggerEnv, setGlobalLogFunction } from "@lib/common/logger"; +import { writable } from "svelte/store"; + +export const logs = writable([] as string[]); + +let _logs = [] as string[]; + +const maxLines = 10000; +setGlobalLogFunction((msg, level) => { + console.log(msg); + const msgstr = typeof msg === "string" ? msg : JSON.stringify(msg); + const strLog = `${new Date().toISOString()}\u2001${msgstr}`; + _logs.push(strLog); + if (_logs.length > maxLines) { + _logs = _logs.slice(_logs.length - maxLines); + } + logs.set(_logs); +}); +defaultLoggerEnv.minLogLevel = LOG_LEVEL_VERBOSE; + +export const storeP2PStatusLine = writable(""); diff --git a/src/apps/webpeer/src/P2PReplicatorShim.ts b/src/apps/webpeer/src/P2PReplicatorShim.ts new file mode 100644 index 0000000..c548fd1 --- /dev/null +++ b/src/apps/webpeer/src/P2PReplicatorShim.ts @@ -0,0 +1,364 @@ +import { PouchDB } from "@lib/pouchdb/pouchdb-browser"; +import { + type EntryDoc, + type LOG_LEVEL, + type P2PSyncSetting, + LOG_LEVEL_NOTICE, + LOG_LEVEL_VERBOSE, + P2P_DEFAULT_SETTINGS, + REMOTE_P2P, +} from "@lib/common/types"; +import { eventHub } from "@lib/hub/hub"; + +import type { Confirm } from "@lib/interfaces/Confirm"; +import { LOG_LEVEL_INFO, Logger } from "@lib/common/logger"; +import { storeP2PStatusLine } from "./CommandsShim"; +import { + EVENT_P2P_PEER_SHOW_EXTRA_MENU, + type CommandShim, + type PeerStatus, + type PluginShim, +} from "@lib/replication/trystero/P2PReplicatorPaneCommon"; +import { + closeP2PReplicator, + openP2PReplicator, + P2PLogCollector, + type P2PReplicatorBase, +} from "@lib/replication/trystero/P2PReplicatorCore"; +import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase"; +import { reactiveSource } from "octagonal-wheels/dataobject/reactive_v2"; +import { EVENT_SETTING_SAVED } from "@lib/events/coreEvents"; +import { unique } from "octagonal-wheels/collection"; +import { BrowserServiceHub } from "@lib/services/BrowserServices"; +import { TrysteroReplicator } from "@lib/replication/trystero/TrysteroReplicator"; +import { SETTING_KEY_P2P_DEVICE_NAME } from "@lib/common/types"; +import { ServiceContext } from "@lib/services/base/ServiceBase"; +import type { InjectableServiceHub } from "@lib/services/InjectableServices"; +import { Menu } from "@/lib/src/services/implements/browser/Menu"; + +function addToList(item: string, list: string) { + return unique( + list + .split(",") + .map((e) => e.trim()) + .concat(item) + .filter((p) => p) + ).join(","); +} +function removeFromList(item: string, list: string) { + return list + .split(",") + .map((e) => e.trim()) + .filter((p) => p !== item) + .filter((p) => p) + .join(","); +} + +export class P2PReplicatorShim implements P2PReplicatorBase, CommandShim { + storeP2PStatusLine = reactiveSource(""); + plugin!: PluginShim; + // environment!: IEnvironment; + confirm!: Confirm; + // simpleStoreAPI!: ISimpleStoreAPI; + db?: PouchDB.Database; + services: InjectableServiceHub; + + getDB() { + if (!this.db) { + throw new Error("DB not initialized"); + } + return this.db; + } + _simpleStore!: SimpleStore; + async closeDB() { + if (this.db) { + await this.db.close(); + this.db = undefined; + } + } + constructor() { + const browserServiceHub = new BrowserServiceHub(); + this.services = browserServiceHub; + this.services.vault.getVaultName.setHandler(() => "p2p-livesync-web-peer"); + } + async init() { + // const { simpleStoreAPI } = await getWrappedSynchromesh(); + // this.confirm = confirm; + this.confirm = this.services.UI.confirm; + // this.environment = environment; + + if (this.db) { + try { + await this.closeDB(); + } catch (ex) { + Logger("Error closing db", LOG_LEVEL_VERBOSE); + Logger(ex, LOG_LEVEL_VERBOSE); + } + } + + const repStore = this.services.database.openSimpleStore("p2p-livesync-web-peer"); + this._simpleStore = repStore; + let _settings = (await repStore.get("settings")) || ({ ...P2P_DEFAULT_SETTINGS } as P2PSyncSetting); + + this.plugin = { + saveSettings: async () => { + await repStore.set("settings", _settings); + eventHub.emitEvent(EVENT_SETTING_SAVED, _settings); + }, + get settings() { + return _settings; + }, + set settings(newSettings: P2PSyncSetting) { + _settings = { ..._settings, ...newSettings }; + }, + rebuilder: null, + services: this.services, + // $$scheduleAppReload: () => {}, + // $$getVaultName: () => "p2p-livesync-web-peer", + }; + // const deviceName = this.getDeviceName(); + const database_name = this.settings.P2P_AppID + "-" + this.settings.P2P_roomID + "p2p-livesync-web-peer"; + this.db = new PouchDB(database_name); + setTimeout(() => { + if (this.settings.P2P_AutoStart && this.settings.P2P_Enabled) { + void this.open(); + } + }, 1000); + return this; + } + get settings() { + return this.plugin.settings; + } + _log(msg: any, level?: LOG_LEVEL): void { + Logger(msg, level); + } + _notice(msg: string, key?: string): void { + Logger(msg, LOG_LEVEL_NOTICE, key); + } + getSettings(): P2PSyncSetting { + return this.settings; + } + simpleStore(): SimpleStore { + return this._simpleStore; + } + handleReplicatedDocuments(docs: EntryDoc[]): Promise { + // No op. This is a client and does not need to process the docs + return Promise.resolve(); + } + + getPluginShim() { + return {}; + } + getConfig(key: string) { + const vaultName = this.services.vault.getVaultName(); + const dbKey = `${vaultName}-${key}`; + return localStorage.getItem(dbKey); + } + setConfig(key: string, value: string) { + const vaultName = this.services.vault.getVaultName(); + const dbKey = `${vaultName}-${key}`; + localStorage.setItem(dbKey, value); + } + + getDeviceName(): string { + return this.getConfig(SETTING_KEY_P2P_DEVICE_NAME) ?? this.plugin.services.vault.getVaultName(); + } + getPlatform(): string { + return "pseudo-replicator"; + } + m?: Menu; + afterConstructor(): void { + eventHub.onEvent(EVENT_P2P_PEER_SHOW_EXTRA_MENU, ({ peer, event }) => { + if (this.m) { + this.m.hide(); + } + this.m = new Menu() + .addItem((item) => item.setTitle("📥 Only Fetch").onClick(() => this.replicateFrom(peer))) + .addItem((item) => item.setTitle("📤 Only Send").onClick(() => this.replicateTo(peer))) + .addSeparator() + // .addItem((item) => { + // item.setTitle("🔧 Get Configuration").onClick(async () => { + // await this.getRemoteConfig(peer); + // }); + // }) + // .addSeparator() + .addItem((item) => { + const mark = peer.syncOnConnect ? "checkmark" : null; + item.setTitle("Toggle Sync on connect") + .onClick(async () => { + await this.toggleProp(peer, "syncOnConnect"); + }) + .setIcon(mark); + }) + .addItem((item) => { + const mark = peer.watchOnConnect ? "checkmark" : null; + item.setTitle("Toggle Watch on connect") + .onClick(async () => { + await this.toggleProp(peer, "watchOnConnect"); + }) + .setIcon(mark); + }) + .addItem((item) => { + const mark = peer.syncOnReplicationCommand ? "checkmark" : null; + item.setTitle("Toggle Sync on `Replicate now` command") + .onClick(async () => { + await this.toggleProp(peer, "syncOnReplicationCommand"); + }) + .setIcon(mark); + }); + void this.m.showAtPosition({ x: event.x, y: event.y }); + }); + this.p2pLogCollector.p2pReplicationLine.onChanged((line) => { + storeP2PStatusLine.set(line.value); + }); + } + + _replicatorInstance?: TrysteroReplicator; + p2pLogCollector = new P2PLogCollector(); + async open() { + await openP2PReplicator(this); + } + async close() { + await closeP2PReplicator(this); + } + enableBroadcastCastings() { + return this?._replicatorInstance?.enableBroadcastChanges(); + } + disableBroadcastCastings() { + return this?._replicatorInstance?.disableBroadcastChanges(); + } + + async initialiseP2PReplicator(): Promise { + await this.init(); + try { + if (this._replicatorInstance) { + await this._replicatorInstance.close(); + this._replicatorInstance = undefined; + } + + if (!this.settings.P2P_AppID) { + this.settings.P2P_AppID = P2P_DEFAULT_SETTINGS.P2P_AppID; + } + const getInitialDeviceName = () => + this.getConfig(SETTING_KEY_P2P_DEVICE_NAME) || this.services.vault.getVaultName(); + + const getSettings = () => this.settings; + const store = () => this.simpleStore(); + const getDB = () => this.getDB(); + + const getConfirm = () => this.confirm; + const getPlatform = () => this.getPlatform(); + const env = { + get db() { + return getDB(); + }, + get confirm() { + return getConfirm(); + }, + get deviceName() { + return getInitialDeviceName(); + }, + get platform() { + return getPlatform(); + }, + get settings() { + return getSettings(); + }, + processReplicatedDocs: async (docs: EntryDoc[]): Promise => { + await this.handleReplicatedDocuments(docs); + // No op. This is a client and does not need to process the docs + }, + get simpleStore() { + return store(); + }, + }; + this._replicatorInstance = new TrysteroReplicator(env); + return this._replicatorInstance; + } catch (e) { + this._log( + e instanceof Error ? e.message : "Something occurred on Initialising P2P Replicator", + LOG_LEVEL_INFO + ); + this._log(e, LOG_LEVEL_VERBOSE); + throw e; + } + } + + get replicator() { + return this._replicatorInstance!; + } + async replicateFrom(peer: PeerStatus) { + await this.replicator.replicateFrom(peer.peerId); + } + async replicateTo(peer: PeerStatus) { + await this.replicator.requestSynchroniseToPeer(peer.peerId); + } + async getRemoteConfig(peer: PeerStatus) { + Logger( + `Requesting remote config for ${peer.name}. Please input the passphrase on the remote device`, + LOG_LEVEL_NOTICE + ); + const remoteConfig = await this.replicator.getRemoteConfig(peer.peerId); + if (remoteConfig) { + Logger(`Remote config for ${peer.name} is retrieved successfully`); + const DROP = "Yes, and drop local database"; + const KEEP = "Yes, but keep local database"; + const CANCEL = "No, cancel"; + const yn = await this.confirm.askSelectStringDialogue( + `Do you really want to apply the remote config? This will overwrite your current config immediately and restart. + And you can also drop the local database to rebuild from the remote device.`, + [DROP, KEEP, CANCEL] as const, + { + defaultAction: CANCEL, + title: "Apply Remote Config ", + } + ); + if (yn === DROP || yn === KEEP) { + if (yn === DROP) { + if (remoteConfig.remoteType !== REMOTE_P2P) { + const yn2 = await this.confirm.askYesNoDialog( + `Do you want to set the remote type to "P2P Sync" to rebuild by "P2P replication"?`, + { + title: "Rebuild from remote device", + } + ); + if (yn2 === "yes") { + remoteConfig.remoteType = REMOTE_P2P; + remoteConfig.P2P_RebuildFrom = peer.name; + } + } + } + this.plugin.settings = remoteConfig; + await this.plugin.saveSettings(); + if (yn === DROP) { + await this.plugin.rebuilder.scheduleFetch(); + } else { + await this.plugin.services.appLifecycle.scheduleRestart(); + } + } else { + Logger(`Cancelled\nRemote config for ${peer.name} is not applied`, LOG_LEVEL_NOTICE); + } + } else { + Logger(`Cannot retrieve remote config for ${peer.peerId}`); + } + } + + async toggleProp(peer: PeerStatus, prop: "syncOnConnect" | "watchOnConnect" | "syncOnReplicationCommand") { + const settingMap = { + syncOnConnect: "P2P_AutoSyncPeers", + watchOnConnect: "P2P_AutoWatchPeers", + syncOnReplicationCommand: "P2P_SyncOnReplication", + } as const; + + const targetSetting = settingMap[prop]; + if (peer[prop]) { + this.plugin.settings[targetSetting] = removeFromList(peer.name, this.plugin.settings[targetSetting]); + await this.plugin.saveSettings(); + } else { + this.plugin.settings[targetSetting] = addToList(peer.name, this.plugin.settings[targetSetting]); + await this.plugin.saveSettings(); + } + } +} + +export const cmdSyncShim = new P2PReplicatorShim(); diff --git a/src/apps/webpeer/src/SyncMain.svelte b/src/apps/webpeer/src/SyncMain.svelte new file mode 100644 index 0000000..853ce86 --- /dev/null +++ b/src/apps/webpeer/src/SyncMain.svelte @@ -0,0 +1,112 @@ + + +
+
+ {#await synchronised then cmdSync} + + {:catch error} +

{error.message}

+ {/await} +
+
+
+ {statusLine} +
+
+ {#each $logs as log} +

{log}

+ {/each} +
+
+
+ + diff --git a/src/apps/webpeer/src/UITest.svelte b/src/apps/webpeer/src/UITest.svelte new file mode 100644 index 0000000..8cd83fc --- /dev/null +++ b/src/apps/webpeer/src/UITest.svelte @@ -0,0 +1,74 @@ + + +
+

UI Test

+
+
+ + → {result} +
+
+ + → {resultPassword} +
+
+ +
+
+
diff --git a/src/apps/webpeer/src/app.css b/src/apps/webpeer/src/app.css new file mode 100644 index 0000000..85b8da6 --- /dev/null +++ b/src/apps/webpeer/src/app.css @@ -0,0 +1,112 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + --background-primary: #ffffff; + --background-primary-alt: #e9e9e9; + --size-4-1: 0.25em; + --tag-background: #f0f0f0; + --tag-border-width: 1px; + --tag-border-color: #cfffdd; + --background-modifier-success: #d4f3e9; + --background-secondary: #f0f0f0; + --background-modifier-error: #f8d7da; + --background-modifier-error-hover: #f5c6cb; + --interactive-accent: #007bff; + --interactive-accent-hover: #0056b3; + --text-normal: #333; + --text-warning: #f0ad4e; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +body { + margin: 0; + padding: 0; + display: flex; + min-width: 320px; + min-height: 100vh; + box-sizing: border-box; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +.card { + padding: 2em; +} + +#app { + margin: 0; + padding: 0; + text-align: center; + display: flex; + flex-grow: 1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} + +button:hover { + border-color: #646cff; +} + +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +input, +select { + border-radius: 8px; + border: 1px solid #1a1a1a; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + transition: border-color 0.25s; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} \ No newline at end of file diff --git a/src/apps/webpeer/src/assets/svelte.svg b/src/apps/webpeer/src/assets/svelte.svg new file mode 100644 index 0000000..c5e0848 --- /dev/null +++ b/src/apps/webpeer/src/assets/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/apps/webpeer/src/main.ts b/src/apps/webpeer/src/main.ts new file mode 100644 index 0000000..befae65 --- /dev/null +++ b/src/apps/webpeer/src/main.ts @@ -0,0 +1,9 @@ +import { mount } from "svelte"; +import "./app.css"; +import App from "./App.svelte"; + +const app = mount(App, { + target: document.getElementById("app")!, +}); + +export default app; diff --git a/src/apps/webpeer/src/uitest.ts b/src/apps/webpeer/src/uitest.ts new file mode 100644 index 0000000..04132b0 --- /dev/null +++ b/src/apps/webpeer/src/uitest.ts @@ -0,0 +1,9 @@ +import { mount } from "svelte"; +import "./app.css"; +import App from "./UITest.svelte"; + +const app = mount(App, { + target: document.getElementById("app")!, +}); + +export default app; diff --git a/src/apps/webpeer/src/vite-env.d.ts b/src/apps/webpeer/src/vite-env.d.ts new file mode 100644 index 0000000..4078e74 --- /dev/null +++ b/src/apps/webpeer/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/src/apps/webpeer/svelte.config.js b/src/apps/webpeer/svelte.config.js new file mode 100644 index 0000000..9a3adfb --- /dev/null +++ b/src/apps/webpeer/svelte.config.js @@ -0,0 +1,7 @@ +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; + +export default { + // Consult https://svelte.dev/docs#compile-time-svelte-preprocess + // for more information about preprocessors + preprocess: vitePreprocess(), +}; diff --git a/src/apps/webpeer/tsconfig.app.json b/src/apps/webpeer/tsconfig.app.json new file mode 100644 index 0000000..5bd9975 --- /dev/null +++ b/src/apps/webpeer/tsconfig.app.json @@ -0,0 +1,25 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "sourceRoot": "../", + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "resolveJsonModule": true, + /** + * Typecheck JS in `.svelte` and `.js` files by default. + * Disable checkJs if you'd like to use dynamic types in JS. + * Note that setting allowJs false does not prevent the use + * of JS in `.svelte` files. + */ + "allowJs": true, + "checkJs": true, + "isolatedModules": true, + "moduleDetection": "force", + "paths": { + "@/*": ["../../*"], + "@lib/*": ["../../lib/src/*"] + } + }, + "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] +} diff --git a/src/apps/webpeer/tsconfig.json b/src/apps/webpeer/tsconfig.json new file mode 100644 index 0000000..eb69b0d --- /dev/null +++ b/src/apps/webpeer/tsconfig.json @@ -0,0 +1,4 @@ +{ + "files": [], + "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }] +} diff --git a/src/apps/webpeer/tsconfig.node.json b/src/apps/webpeer/tsconfig.node.json new file mode 100644 index 0000000..c5f047b --- /dev/null +++ b/src/apps/webpeer/tsconfig.node.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + "paths": { + "@/*": ["../../*"], + "@lib/*": ["../../lib/src/*"] + } + }, + "include": ["vite.config.ts"] +} diff --git a/src/apps/webpeer/ui.html b/src/apps/webpeer/ui.html new file mode 100644 index 0000000..631df86 --- /dev/null +++ b/src/apps/webpeer/ui.html @@ -0,0 +1,16 @@ + + + + + + + + Peer-to-Peer Daemon on Browser + + + +
+ + + + \ No newline at end of file diff --git a/src/apps/webpeer/vite.config.ts b/src/apps/webpeer/vite.config.ts new file mode 100644 index 0000000..e7ded0b --- /dev/null +++ b/src/apps/webpeer/vite.config.ts @@ -0,0 +1,24 @@ +import { defineConfig } from "vite"; +import { svelte } from "@sveltejs/vite-plugin-svelte"; +import path from "node:path"; +// https://vite.dev/config/ +export default defineConfig({ + plugins: [svelte()], + resolve: { + alias: { + "@": path.resolve(__dirname, "../../"), + "@lib": path.resolve(__dirname, "../../lib/src"), + }, + }, + base: "./", + build: { + outDir: "dist", + emptyOutDir: true, + rollupOptions: { + input: { + index: "index.html", + // uitest: "uitest.html", + }, + }, + }, +}); diff --git a/test/shell/p2p-start.sh b/test/shell/p2p-start.sh index f875a61..b4218e0 100755 --- a/test/shell/p2p-start.sh +++ b/test/shell/p2p-start.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e script_dir=$(dirname "$0") -webpeer_dir=$script_dir/../../src/lib/apps/webpeer +webpeer_dir=$script_dir/../../src/apps/webpeer docker run -d --name relay-test -p 4000:8080 scsibug/nostr-rs-relay:latest npm run --prefix $webpeer_dir build diff --git a/tsconfig.json b/tsconfig.json index 15968e6..1ee9b7a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,12 +24,5 @@ } }, "include": ["**/*.ts", "test/**/*.test.ts"], - "exclude": [ - "pouchdb-browser-webpack", - "utils", - "src/lib/apps", - "src/**/*.test.ts", - "**/_test/**", - "src/**/*.test.ts" - ] + "exclude": ["pouchdb-browser-webpack", "utils", "src/apps", "src/**/*.test.ts", "**/_test/**", "src/**/*.test.ts"] } diff --git a/vite.config.ts b/vite.config.ts index 6150f9a..85ea529 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,3 +1,4 @@ -import { defineConfig } from "vite"; +import { defineConfig, mergeConfig } from "vitest/config"; +import viteConfig from "./vitest.config.common"; -export default defineConfig({}); +export default mergeConfig(viteConfig, defineConfig({}));