diff --git a/.vscode/mcp.json b/.vscode/mcp.json index a808733..600e2de 100644 --- a/.vscode/mcp.json +++ b/.vscode/mcp.json @@ -6,14 +6,21 @@ }, "ESLint": { "type": "stdio", - "command": "npx", - "args": ["@eslint/mcp@latest"] + "command": "pnpm", + "args": [ + "dlx", + "--package=jiti", + "--package=@eslint/mcp@latest", + "-s", + "mcp" + ] }, "io.github.upstash/context7": { "type": "stdio", - "command": "npx", + "command": "pnpm", "args": [ - "-y", + "dlx", + "-s", "@upstash/context7-mcp@latest", "--api-key", "${input:CONTEXT7_API_KEY}" @@ -21,26 +28,13 @@ }, "io.github.ChromeDevTools/chrome-devtools-mcp": { "type": "stdio", - "command": "npx", - "args": ["chrome-devtools-mcp@0.10.2"] - }, - "oraios/serena": { - "type": "stdio", - "command": "uvx", - "args": [ - "--from", - "git+https://github.com/oraios/serena", - "serena", - "start-mcp-server", - "serena==latest", - "--context", - "ide-assistant" - ] + "command": "pnpm", + "args": ["dlx", "-s", "chrome-devtools-mcp@0.10.2"] }, "socket-mcp": { "type": "stdio", - "command": "npx", - "args": ["@socketsecurity/mcp@latest"], + "command": "pnpm", + "args": ["dlx", "-s", "@socketsecurity/mcp@latest"], "env": { "SOCKET_API_KEY": "${input:socket_api_key}" } diff --git a/.vscode/settings.example.json b/.vscode/settings.example.json index 02ff469..7bf29b2 100644 --- a/.vscode/settings.example.json +++ b/.vscode/settings.example.json @@ -7,5 +7,6 @@ "svelte.language-server.runtime": "node", "eslint.validate": ["javascript", "typescript", "svelte"], "typescript.tsdk": "./node_modules/typescript/lib", - "typescript.enablePromptUseWorkspaceTsdk": true + "typescript.enablePromptUseWorkspaceTsdk": true, + "typescript.tsserver.experimental.enableProjectDiagnostics": true } diff --git a/AGENTS.md b/AGENTS.md index 2db9ea2..ddafa9b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -183,39 +183,6 @@ Lints specified files and returns errors/warnings. Use this to validate your cod **Usage:** `mcp_eslint_lint-files` with an array of absolute file paths. -### 4. Serena MCP Server (`mcp_oraios_serena_*`) - -Intelligent code navigation and symbolic editing: - -#### Code Exploration - -- `get_symbols_overview` - Get high-level view of symbols in a file (use this FIRST) -- `find_symbol` - Find and read specific symbols by name path -- `find_referencing_symbols` - Find where symbols are used -- `search_for_pattern` - Flexible regex-based code search - -#### Code Editing - -- `replace_symbol_body` - Replace entire symbol body (methods, classes, etc.) -- `insert_after_symbol` - Insert code after a symbol -- `insert_before_symbol` - Insert code before a symbol -- `rename_symbol` - Rename symbols throughout codebase - -#### Memory Management - -- `write_memory` - Save project information for future reference -- `read_memory` - Retrieve saved project context -- `edit_memory` - Update existing memories -- `delete_memory` - Remove outdated memories - -#### Project Management - -- `activate_project` - Switch between registered projects -- `get_current_config` - View current configuration - -> [!TIP] -> Use symbolic tools to read only necessary code. Start with `get_symbols_overview` before reading full files. - ### 5. Socket MCP Server (`mcp__extension_so_depscore`) Dependency security and quality scoring: @@ -504,14 +471,14 @@ Since Svelte 5.25, you can reassign `$derived` values to temporarily override th When using SvelteKit remote functions, you can use `.updates()` with `.withOverride()` to optimistically update the cache of a query. ```typescript -import { getPosts, createPost } from '$lib/remote/posts.remote'; +import { getPosts, createPost } from "$lib/remote/posts.remote"; async function handleSubmit() { - const newPost = { id: 'temp', title: 'New Post' }; + const newPost = { id: "temp", title: "New Post" }; // Optimistically update the getPosts query cache await createPost(newPost).updates( - getPosts().withOverride((posts) => [newPost, ...posts]) + getPosts().withOverride((posts) => [newPost, ...posts]), ); } ``` diff --git a/check_identity.ts b/check_identity.ts deleted file mode 100644 index 1a853ca..0000000 --- a/check_identity.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { fetchUserIdentity } from "./src/lib/server/federation.ts"; - -async function testIdentity() { - console.log("=== Identity Fetch Verification ==="); - const handle = "@bob:localhost:5174"; - const requestingDomain = "localhost:5173"; - - try { - console.log(`Fetching identity for ${handle} from ${requestingDomain}...`); - const identity = await fetchUserIdentity(handle, requestingDomain); - - console.log("Result:"); - console.log(JSON.stringify(identity, null, 2)); - - if (identity && identity.publicKey) { - console.log( - "\nPublic Key First 10 chars:", - identity.publicKey.slice(0, 10), - ); - } else { - console.log("\n❌ No Public Key found!"); - } - } catch (e) { - console.error("❌ Error fetching identity:", e); - } -} - -testIdentity(); diff --git a/check_keypair.ts b/check_keypair.ts deleted file mode 100644 index 63f7ae4..0000000 --- a/check_keypair.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Database } from "bun:sqlite"; // or better, just use drizzle or direct sqlite driver if available -// Actually, I can rely on the app's db module if I run with vite-node -import { db } from "./src/lib/server/db/index.ts"; -import { users } from "./src/lib/server/db/schema.ts"; -import { eq } from "drizzle-orm"; -import { - encryptKeyForDevice, - decryptKeyForDevice, - generateNoteKey, -} from "./src/lib/crypto.ts"; - -async function checkKeys() { - console.log("=== Keypair Consistency Check ==="); - - // 1. Get Bob's keys - const bob = await db.query.users.findFirst({ - where: eq(users.username, "bob"), - }); - - if (!bob) { - console.error("❌ Bob not found in DB!"); - return; - } - - console.log("Bob:", bob.id); - console.log("Public Key: ", bob.publicKey); - console.log("Private Key:", bob.privateKeyEncrypted); - - if (!bob.publicKey || !bob.privateKeyEncrypted) { - console.error("❌ Missing keys!"); - return; - } - - // 2. Test Keypair - const secret = generateNoteKey(); - console.log("\nTest Secret:", secret); - - try { - // Encrypt to Bob's Public Key - const envelope = encryptKeyForDevice(secret, bob.publicKey); - console.log("Encrypted Envelope:", envelope); - - // Decrypt with Bob's Private Key - const decrypted = decryptKeyForDevice(envelope, bob.privateKeyEncrypted); - console.log("Decrypted Secret: ", decrypted); - - if (decrypted === secret) { - console.log("\n✅ SUCCESS: Stored keys are a valid pair!"); - } else { - console.error("\n❌ FAILURE: Decrypted secret does not match!"); - } - } catch (e) { - console.error("\n❌ CRITICAL ERROR during test:", e); - } -} - -checkKeys(); diff --git a/package.json b/package.json index 9c9c95d..e494b7a 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@oslojs/crypto": "^1.0.1", "@oslojs/encoding": "^1.1.0", "@sveltejs/adapter-auto": "^7.0.0", - "@sveltejs/kit": "^2.49.0", + "@sveltejs/kit": "^2.49.2", "@sveltejs/vite-plugin-svelte": "^6.2.1", "@tailwindcss/typography": "^0.5.19", "@tailwindcss/vite": "^4.1.17", @@ -36,6 +36,7 @@ "eslint": "^9.39.1", "eslint-plugin-svelte": "^3.13.0", "globals": "^16.5.0", + "jiti": "^2.6.1", "playwright": "^1.56.1", "prettier": "^3.6.2", "prettier-plugin-svelte": "^3.4.0", @@ -64,6 +65,8 @@ "@codemirror/theme-one-dark": "^6.1.3", "@codemirror/view": "^6.38.8", "@effect/platform": "^0.93.3", + "@hpke/chacha20poly1305": "^1.7.1", + "@hpke/core": "^1.7.5", "@lezer/highlight": "^1.2.3", "@lezer/markdown": "^1.6.0", "@lucide/svelte": "^0.554.0", @@ -76,10 +79,6 @@ "@milkdown/preset-gfm": "^7.17.1", "@milkdown/theme-nord": "^7.17.1", "@milkdown/utils": "^7.17.1", - "@modelcontextprotocol/sdk": "^1.22.0", - "@noble/ciphers": "^2.1.1", - "@noble/curves": "^2.0.1", - "@noble/hashes": "^2.0.1", "@node-rs/argon2": "^2.0.2", "@prosemark/core": "^0.0.4", "@prosemark/paste-rich-text": "^0.0.2", @@ -97,7 +96,9 @@ "katex": "^0.16.25", "loro-codemirror": "^0.3.3", "loro-crdt": "^1.10.0", - "svelte": "^5.44.0", + "runed": "^0.37.0", + "svelte": "^5.45.8", + "temporal-polyfill": "^0.3.0", "tiptap-markdown": "^0.9.0", "typescript-svelte-plugin": "^0.3.50" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bd49989..7b062e3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,12 @@ importers: '@effect/platform': specifier: ^0.93.3 version: 0.93.6(effect@3.19.9) + '@hpke/chacha20poly1305': + specifier: ^1.7.1 + version: 1.7.1 + '@hpke/core': + specifier: ^1.7.5 + version: 1.7.5 '@lezer/highlight': specifier: ^1.2.3 version: 1.2.3 @@ -58,7 +64,7 @@ importers: version: 1.6.1 '@lucide/svelte': specifier: ^0.554.0 - version: 0.554.0(svelte@5.45.7) + version: 0.554.0(svelte@5.45.8) '@milkdown-lab/plugin-split-editing': specifier: ^1.3.1 version: 1.3.1(@milkdown/core@7.17.3)(@milkdown/prose@7.17.3) @@ -86,18 +92,6 @@ importers: '@milkdown/utils': specifier: ^7.17.1 version: 7.17.3 - '@modelcontextprotocol/sdk': - specifier: ^1.22.0 - version: 1.24.3(zod@4.1.13) - '@noble/ciphers': - specifier: ^2.1.1 - version: 2.1.1 - '@noble/curves': - specifier: ^2.0.1 - version: 2.0.1 - '@noble/hashes': - specifier: ^2.0.1 - version: 2.0.1 '@node-rs/argon2': specifier: ^2.0.2 version: 2.0.2 @@ -149,15 +143,21 @@ importers: loro-crdt: specifier: ^1.10.0 version: 1.10.3 + runed: + specifier: ^0.37.0 + version: 0.37.0(@sveltejs/kit@2.49.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.45.8)(zod@4.1.13) svelte: - specifier: ^5.44.0 - version: 5.45.7 + specifier: ^5.45.8 + version: 5.45.8 + temporal-polyfill: + specifier: ^0.3.0 + version: 0.3.0 tiptap-markdown: specifier: ^0.9.0 version: 0.9.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0)) typescript-svelte-plugin: specifier: ^0.3.50 - version: 0.3.50(svelte@5.45.7)(typescript@5.9.3) + version: 0.3.50(svelte@5.45.8)(typescript@5.9.3) devDependencies: '@effect/language-service': specifier: ^0.56.0 @@ -179,13 +179,13 @@ importers: version: 1.1.0 '@sveltejs/adapter-auto': specifier: ^7.0.0 - version: 7.0.0(@sveltejs/kit@2.49.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.7)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.45.7)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2))) + version: 7.0.0(@sveltejs/kit@2.49.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2))) '@sveltejs/kit': - specifier: ^2.49.0 - version: 2.49.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.7)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.45.7)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)) + specifier: ^2.49.2 + version: 2.49.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)) '@sveltejs/vite-plugin-svelte': specifier: ^6.2.1 - version: 6.2.1(svelte@5.45.7)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)) + version: 6.2.1(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)) '@tailwindcss/typography': specifier: ^0.5.19 version: 0.5.19(tailwindcss@4.1.17) @@ -206,10 +206,13 @@ importers: version: 9.39.1(jiti@2.6.1) eslint-plugin-svelte: specifier: ^3.13.0 - version: 3.13.1(eslint@9.39.1(jiti@2.6.1))(svelte@5.45.7) + version: 3.13.1(eslint@9.39.1(jiti@2.6.1))(svelte@5.45.8) globals: specifier: ^16.5.0 version: 16.5.0 + jiti: + specifier: ^2.6.1 + version: 2.6.1 playwright: specifier: ^1.56.1 version: 1.57.0 @@ -218,13 +221,13 @@ importers: version: 3.7.4 prettier-plugin-svelte: specifier: ^3.4.0 - version: 3.4.0(prettier@3.7.4)(svelte@5.45.7) + version: 3.4.0(prettier@3.7.4)(svelte@5.45.8) prettier-plugin-tailwindcss: specifier: ^0.7.1 - version: 0.7.2(prettier-plugin-svelte@3.4.0(prettier@3.7.4)(svelte@5.45.7))(prettier@3.7.4) + version: 0.7.2(prettier-plugin-svelte@3.4.0(prettier@3.7.4)(svelte@5.45.8))(prettier@3.7.4) svelte-check: specifier: ^4.3.4 - version: 4.3.4(picomatch@4.0.3)(svelte@5.45.7)(typescript@5.9.3) + version: 4.3.4(picomatch@4.0.3)(svelte@5.45.8)(typescript@5.9.3) tailwindcss: specifier: ^4.1.17 version: 4.1.17 @@ -729,6 +732,18 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@hpke/chacha20poly1305@1.7.1': + resolution: {integrity: sha512-Zp8IwRIkdCucu877wCNqDp3B8yOhAnAah/YDDkO94pPr/KKV7IGnBbpwIjDB3BsAySWBMrhhdE0JKYw3N4FCag==} + engines: {node: '>=16.0.0'} + + '@hpke/common@1.8.1': + resolution: {integrity: sha512-PSI4QSxH8XDli0TqAsWycVfrLLCM/bBe+hVlJwtuJJiKIvCaFS3CXX/WtRfJceLJye9NHc2J7GvHVCY9B1BEbA==} + engines: {node: '>=16.0.0'} + + '@hpke/core@1.7.5': + resolution: {integrity: sha512-4xfckZuPaIodeu0HpuTRIdtmajhRHXM/6rjS2N62Ns9aOCkGbbeYRwktqR3bUScuhCwyEBsEQqtIh9f0iLP3WQ==} + engines: {node: '>=16.0.0'} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -923,16 +938,6 @@ packages: '@mixmark-io/domino@2.2.0': resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==} - '@modelcontextprotocol/sdk@1.24.3': - resolution: {integrity: sha512-YgSHW29fuzKKAHTGe9zjNoo+yF8KaQPzDC2W9Pv41E7/57IfY+AMGJ/aDFlgTLcVVELoggKE4syABCE75u3NCw==} - engines: {node: '>=18'} - peerDependencies: - '@cfworker/json-schema': ^4.1.1 - zod: ^3.25 || ^4.0 - peerDependenciesMeta: - '@cfworker/json-schema': - optional: true - '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} cpu: [arm64] @@ -969,18 +974,6 @@ packages: '@neon-rs/load@0.0.4': resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==} - '@noble/ciphers@2.1.1': - resolution: {integrity: sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw==} - engines: {node: '>= 20.19.0'} - - '@noble/curves@2.0.1': - resolution: {integrity: sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==} - engines: {node: '>= 20.19.0'} - - '@noble/hashes@2.0.1': - resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} - engines: {node: '>= 20.19.0'} - '@node-rs/argon2-android-arm-eabi@2.0.2': resolution: {integrity: sha512-DV/H8p/jt40lrao5z5g6nM9dPNPGEHL+aK6Iy/og+dbL503Uj0AHLqj1Hk9aVUSCNnsDdUEKp4TVMi0YakDYKw==} engines: {node: '>= 10'} @@ -1242,8 +1235,8 @@ packages: peerDependencies: '@sveltejs/kit': ^2.0.0 - '@sveltejs/kit@2.49.1': - resolution: {integrity: sha512-vByReCTTdlNM80vva8alAQC80HcOiHLkd8XAxIiKghKSHcqeNfyhp3VsYAV8VSiPKu4Jc8wWCfsZNAIvd1uCqA==} + '@sveltejs/kit@2.49.2': + resolution: {integrity: sha512-Vp3zX/qlwerQmHMP6x0Ry1oY7eKKRcOWGc2P59srOp4zcqyn+etJyQpELgOi4+ZSUgteX8Y387NuwruLgGXLUQ==} engines: {node: '>=18.13'} hasBin: true peerDependencies: @@ -1700,10 +1693,6 @@ packages: resolution: {integrity: sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - accepts@2.0.0: - resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} - engines: {node: '>= 0.6'} - acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1714,14 +1703,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - ajv-formats@3.0.1: - resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -1756,10 +1737,6 @@ packages: bind-event-listener@3.0.0: resolution: {integrity: sha512-PJvH288AWQhKs2v9zyfYdPzlPqf5bXbGMmhmUIY9x4dAUGIWgomO771oBQNwJnMQSnUIXhKu6sgzpBRXTlvb8Q==} - body-parser@2.2.1: - resolution: {integrity: sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==} - engines: {node: '>=18'} - brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -1769,18 +1746,6 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} - - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -1824,30 +1789,10 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - content-disposition@1.0.1: - resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} - engines: {node: '>=18'} - - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - - cookie-signature@1.2.2: - resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} - engines: {node: '>=6.6.0'} - cookie@0.6.0: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} - cookie@0.7.2: - resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} - engines: {node: '>= 0.6'} - - cors@2.8.5: - resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} - engines: {node: '>= 0.10'} - crelt@1.0.6: resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} @@ -1889,10 +1834,6 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} - dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -2014,23 +1955,12 @@ packages: sqlite3: optional: true - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - effect@3.19.9: resolution: {integrity: sha512-taMXnfG/p+j7AmMOHHQaCHvjqwu9QBO3cxuZqL2dMG/yWcEMw0ZHruHe9B49OxtfKH/vKKDDKRhZ+1GJ2p5R5w==} emojilib@2.4.0: resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} - encodeurl@2.0.0: - resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} - engines: {node: '>= 0.8'} - enhanced-resolve@5.18.3: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} @@ -2039,18 +1969,6 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} - esbuild-register@3.6.0: resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} peerDependencies: @@ -2066,9 +1984,6 @@ packages: engines: {node: '>=18'} hasBin: true - escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -2135,28 +2050,6 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} - - eventsource-parser@3.0.6: - resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} - engines: {node: '>=18.0.0'} - - eventsource@3.0.7: - resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} - engines: {node: '>=18.0.0'} - - express-rate-limit@7.5.1: - resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==} - engines: {node: '>= 16'} - peerDependencies: - express: '>= 4.11' - - express@5.2.1: - resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} - engines: {node: '>= 18'} - extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -2193,10 +2086,6 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} - finalhandler@2.1.1: - resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} - engines: {node: '>= 18.0.0'} - find-my-way-ts@0.1.6: resolution: {integrity: sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==} @@ -2215,14 +2104,6 @@ packages: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} - forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} - - fresh@2.0.0: - resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} - engines: {node: '>= 0.8'} - fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2233,17 +2114,6 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - get-tsconfig@4.13.0: resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} @@ -2259,10 +2129,6 @@ packages: resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} engines: {node: '>=18'} - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -2270,22 +2136,6 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - http-errors@2.0.1: - resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} - engines: {node: '>= 0.8'} - - iconv-lite@0.7.0: - resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} - engines: {node: '>=0.10.0'} - ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -2302,13 +2152,6 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -2321,9 +2164,6 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} - is-promise@4.0.0: - resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - is-reference@3.0.3: resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} @@ -2334,9 +2174,6 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true - jose@6.1.3: - resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} - js-base64@3.7.8: resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} @@ -2485,6 +2322,10 @@ packages: loro-crdt@1.10.3: resolution: {integrity: sha512-vzWkVw7mWrKTilPjrgAhhzjAyOn3/DaUPJxdK9lunpEI1Y+uQMDBt/pEtRiovKFtGXo4tUVfULnFc7H/ufGwkQ==} + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -2498,10 +2339,6 @@ packages: markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - mdast-util-definitions@6.0.0: resolution: {integrity: sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==} @@ -2541,14 +2378,6 @@ packages: mdurl@2.0.0: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} - media-typer@1.1.0: - resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} - engines: {node: '>= 0.8'} - - merge-descriptors@2.0.0: - resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} - engines: {node: '>=18'} - micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -2633,14 +2462,6 @@ packages: micromark@4.0.2: resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} - mime-db@1.54.0: - resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} - engines: {node: '>= 0.6'} - - mime-types@3.0.2: - resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} - engines: {node: '>=18'} - minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -2682,10 +2503,6 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - negotiator@1.0.0: - resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} - engines: {node: '>= 0.6'} - node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} @@ -2703,21 +2520,6 @@ packages: resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} hasBin: true - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -2737,10 +2539,6 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2749,9 +2547,6 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - path-to-regexp@8.3.0: - resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2759,10 +2554,6 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} - pkce-challenge@5.0.1: - resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} - engines: {node: '>=16.20.0'} - playwright-core@1.57.0: resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==} engines: {node: '>=18'} @@ -2943,10 +2734,6 @@ packages: prosemirror-view@1.41.4: resolution: {integrity: sha512-WkKgnyjNncri03Gjaz3IFWvCAE94XoiEgvtr0/r2Xw7R8/IjK3sKLSiDoCHWcsXSAinVaKlGRZDvMCsF1kbzjA==} - proxy-addr@2.0.7: - resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} - engines: {node: '>= 0.10'} - punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} @@ -2958,21 +2745,9 @@ packages: pure-rand@6.1.0: resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} - qs@6.14.0: - resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} - engines: {node: '>=0.6'} - raf-schd@4.0.3: resolution: {integrity: sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==} - range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} - - raw-body@3.0.2: - resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} - engines: {node: '>= 0.10'} - readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -3011,17 +2786,22 @@ packages: rope-sequence@1.3.4: resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} - router@2.2.0: - resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} - engines: {node: '>= 18'} + runed@0.37.0: + resolution: {integrity: sha512-zphHjvLZEpcJiV3jezT96SnNwePaUIEd1HEMuPGZ6DwOMao9S2ZAUCYJPKquRM5J22AwAOpGj0KmxOkQdkBfwQ==} + peerDependencies: + '@sveltejs/kit': ^2.21.0 + svelte: ^5.7.0 + zod: ^4.1.0 + peerDependenciesMeta: + '@sveltejs/kit': + optional: true + zod: + optional: true sade@1.8.1: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - scule@1.3.0: resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} @@ -3030,20 +2810,9 @@ packages: engines: {node: '>=10'} hasBin: true - send@1.2.0: - resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} - engines: {node: '>= 18'} - - serve-static@2.2.0: - resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} - engines: {node: '>= 18'} - set-cookie-parser@2.7.2: resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -3052,22 +2821,6 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} - sirv@3.0.2: resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} engines: {node: '>=18'} @@ -3087,10 +2840,6 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} - statuses@2.0.2: - resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} - engines: {node: '>= 0.8'} - strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -3125,8 +2874,8 @@ packages: svelte: ^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0 typescript: ^4.9.4 || ^5.0.0 - svelte@5.45.7: - resolution: {integrity: sha512-A+vXOLTjErrPRkpbY0h+cZuXjRQS8RZeyF8cx8IU513ORL7ld8o9exQAYAabh8NgNW5bLcIKhrSNwgpj4ykb+w==} + svelte@5.45.8: + resolution: {integrity: sha512-1Jh7FwVh/2Uxg0T7SeE1qFKMhwYH45b2v53bcZpW7qHa6O8iU1ByEj56PF0IQ6dU4HE5gRkic6h+vx+tclHeiw==} engines: {node: '>=18'} tailwindcss@4.1.17: @@ -3136,6 +2885,12 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} + temporal-polyfill@0.3.0: + resolution: {integrity: sha512-qNsTkX9K8hi+FHDfHmf22e/OGuXmfBm9RqNismxBrnSmZVJKegQ+HYYXT+R7Ha8F/YSm2Y34vmzD4cxMu2u95g==} + + temporal-spec@0.3.0: + resolution: {integrity: sha512-n+noVpIqz4hYgFSMOSiINNOUOMFtV5cZQNCmmszA6GiVFVRt3G7AqVyhXjhCSmowvQn+NsGn+jMDMKJYHd3bSQ==} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -3145,10 +2900,6 @@ packages: peerDependencies: '@tiptap/core': ^3.0.1 - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} - totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} @@ -3172,10 +2923,6 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-is@2.0.1: - resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} - engines: {node: '>= 0.6'} - typescript-eslint@8.49.0: resolution: {integrity: sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3216,10 +2963,6 @@ packages: unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -3234,10 +2977,6 @@ packages: resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} hasBin: true - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - vfile-message@4.0.3: resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} @@ -3323,9 +3062,6 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.18.3: resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} engines: {node: '>=10.0.0'} @@ -3349,11 +3085,6 @@ packages: zimmerframe@1.1.4: resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} - zod-to-json-schema@3.25.0: - resolution: {integrity: sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==} - peerDependencies: - zod: ^3.25 || ^4 - zod@4.1.13: resolution: {integrity: sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==} @@ -3875,6 +3606,16 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 + '@hpke/chacha20poly1305@1.7.1': + dependencies: + '@hpke/common': 1.8.1 + + '@hpke/common@1.8.1': {} + + '@hpke/core@1.7.5': + dependencies: + '@hpke/common': 1.8.1 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -4060,9 +3801,9 @@ snapshots: '@libsql/win32-x64-msvc@0.5.22': optional: true - '@lucide/svelte@0.554.0(svelte@5.45.7)': + '@lucide/svelte@0.554.0(svelte@5.45.8)': dependencies: - svelte: 5.45.7 + svelte: 5.45.8 '@marijn/find-cluster-break@1.0.2': {} @@ -4194,25 +3935,6 @@ snapshots: '@mixmark-io/domino@2.2.0': {} - '@modelcontextprotocol/sdk@1.24.3(zod@4.1.13)': - dependencies: - ajv: 8.17.1 - ajv-formats: 3.0.1(ajv@8.17.1) - content-type: 1.0.5 - cors: 2.8.5 - cross-spawn: 7.0.6 - eventsource: 3.0.7 - eventsource-parser: 3.0.6 - express: 5.2.1 - express-rate-limit: 7.5.1(express@5.2.1) - jose: 6.1.3 - pkce-challenge: 5.0.1 - raw-body: 3.0.2 - zod: 4.1.13 - zod-to-json-schema: 3.25.0(zod@4.1.13) - transitivePeerDependencies: - - supports-color - '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': optional: true @@ -4240,14 +3962,6 @@ snapshots: '@neon-rs/load@0.0.4': {} - '@noble/ciphers@2.1.1': {} - - '@noble/curves@2.0.1': - dependencies: - '@noble/hashes': 2.0.1 - - '@noble/hashes@2.0.1': {} - '@node-rs/argon2-android-arm-eabi@2.0.2': optional: true @@ -4445,15 +4159,15 @@ snapshots: dependencies: acorn: 8.15.0 - '@sveltejs/adapter-auto@7.0.0(@sveltejs/kit@2.49.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.7)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.45.7)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)))': + '@sveltejs/adapter-auto@7.0.0(@sveltejs/kit@2.49.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)))': dependencies: - '@sveltejs/kit': 2.49.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.7)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.45.7)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)) + '@sveltejs/kit': 2.49.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)) - '@sveltejs/kit@2.49.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.7)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.45.7)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2))': + '@sveltejs/kit@2.49.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2))': dependencies: '@standard-schema/spec': 1.0.0 '@sveltejs/acorn-typescript': 1.0.8(acorn@8.15.0) - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.45.7)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)) '@types/cookie': 0.6.0 acorn: 8.15.0 cookie: 0.6.0 @@ -4465,25 +4179,25 @@ snapshots: sade: 1.8.1 set-cookie-parser: 2.7.2 sirv: 3.0.2 - svelte: 5.45.7 + svelte: 5.45.8 vite: 7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2) - '@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.7)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.45.7)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.45.7)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)) debug: 4.4.3 - svelte: 5.45.7 + svelte: 5.45.8 vite: 7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.7)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2))': + '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.7)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.45.7)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)) + '@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)) debug: 4.4.3 deepmerge: 4.3.1 magic-string: 0.30.21 - svelte: 5.45.7 + svelte: 5.45.8 vite: 7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2) vitefu: 1.1.1(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)) transitivePeerDependencies: @@ -4923,21 +4637,12 @@ snapshots: '@typescript-eslint/types': 8.49.0 eslint-visitor-keys: 4.2.1 - accepts@2.0.0: - dependencies: - mime-types: 3.0.2 - negotiator: 1.0.0 - acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 acorn@8.15.0: {} - ajv-formats@3.0.1(ajv@8.17.1): - optionalDependencies: - ajv: 8.17.1 - ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -4970,20 +4675,6 @@ snapshots: bind-event-listener@3.0.0: {} - body-parser@2.2.1: - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 4.4.3 - http-errors: 2.0.1 - iconv-lite: 0.7.0 - on-finished: 2.4.1 - qs: 6.14.0 - raw-body: 3.0.2 - type-is: 2.0.1 - transitivePeerDependencies: - - supports-color - brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -4995,18 +4686,6 @@ snapshots: buffer-from@1.1.2: {} - bytes@3.1.2: {} - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 - callsites@3.1.0: {} ccount@2.0.1: {} @@ -5046,21 +4725,8 @@ snapshots: concat-map@0.0.1: {} - content-disposition@1.0.1: {} - - content-type@1.0.5: {} - - cookie-signature@1.2.2: {} - cookie@0.6.0: {} - cookie@0.7.2: {} - - cors@2.8.5: - dependencies: - object-assign: 4.1.1 - vary: 1.1.2 - crelt@1.0.6: {} cross-spawn@7.0.6: @@ -5089,8 +4755,6 @@ snapshots: deepmerge@4.3.1: {} - depd@2.0.0: {} - dequal@2.0.3: {} detect-libc@2.0.2: {} @@ -5122,14 +4786,6 @@ snapshots: optionalDependencies: '@libsql/client': 0.15.15 - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - ee-first@1.1.1: {} - effect@3.19.9: dependencies: '@standard-schema/spec': 1.0.0 @@ -5137,8 +4793,6 @@ snapshots: emojilib@2.4.0: {} - encodeurl@2.0.0: {} - enhanced-resolve@5.18.3: dependencies: graceful-fs: 4.2.11 @@ -5146,14 +4800,6 @@ snapshots: entities@4.5.0: {} - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - - es-object-atoms@1.1.1: - dependencies: - es-errors: 1.3.0 - esbuild-register@3.6.0(esbuild@0.25.12): dependencies: debug: 4.4.3 @@ -5215,13 +4861,11 @@ snapshots: '@esbuild/win32-ia32': 0.25.12 '@esbuild/win32-x64': 0.25.12 - escape-html@1.0.3: {} - escape-string-regexp@4.0.0: {} escape-string-regexp@5.0.0: {} - eslint-plugin-svelte@3.13.1(eslint@9.39.1(jiti@2.6.1))(svelte@5.45.7): + eslint-plugin-svelte@3.13.1(eslint@9.39.1(jiti@2.6.1))(svelte@5.45.8): dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) '@jridgewell/sourcemap-codec': 1.5.5 @@ -5233,9 +4877,9 @@ snapshots: postcss-load-config: 3.1.4(postcss@8.5.6) postcss-safe-parser: 7.0.1(postcss@8.5.6) semver: 7.7.3 - svelte-eslint-parser: 1.4.1(svelte@5.45.7) + svelte-eslint-parser: 1.4.1(svelte@5.45.8) optionalDependencies: - svelte: 5.45.7 + svelte: 5.45.8 transitivePeerDependencies: - ts-node @@ -5313,51 +4957,6 @@ snapshots: esutils@2.0.3: {} - etag@1.8.1: {} - - eventsource-parser@3.0.6: {} - - eventsource@3.0.7: - dependencies: - eventsource-parser: 3.0.6 - - express-rate-limit@7.5.1(express@5.2.1): - dependencies: - express: 5.2.1 - - express@5.2.1: - dependencies: - accepts: 2.0.0 - body-parser: 2.2.1 - content-disposition: 1.0.1 - content-type: 1.0.5 - cookie: 0.7.2 - cookie-signature: 1.2.2 - debug: 4.4.3 - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 2.1.1 - fresh: 2.0.0 - http-errors: 2.0.1 - merge-descriptors: 2.0.0 - mime-types: 3.0.2 - on-finished: 2.4.1 - once: 1.4.0 - parseurl: 1.3.3 - proxy-addr: 2.0.7 - qs: 6.14.0 - range-parser: 1.2.1 - router: 2.2.0 - send: 1.2.0 - serve-static: 2.2.0 - statuses: 2.0.2 - type-is: 2.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - extend@3.0.2: {} fast-check@3.23.2: @@ -5385,17 +4984,6 @@ snapshots: dependencies: flat-cache: 4.0.1 - finalhandler@2.1.1: - dependencies: - debug: 4.4.3 - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - find-my-way-ts@0.1.6: {} find-up@5.0.0: @@ -5414,36 +5002,12 @@ snapshots: dependencies: fetch-blob: 3.2.0 - forwarded@0.2.0: {} - - fresh@2.0.0: {} - fsevents@2.3.2: optional: true fsevents@2.3.3: optional: true - function-bind@1.1.2: {} - - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - math-intrinsics: 1.1.0 - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 - get-tsconfig@4.13.0: dependencies: resolve-pkg-maps: 1.0.0 @@ -5456,30 +5020,10 @@ snapshots: globals@16.5.0: {} - gopd@1.2.0: {} - graceful-fs@4.2.11: {} has-flag@4.0.0: {} - has-symbols@1.1.0: {} - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - - http-errors@2.0.1: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.2 - toidentifier: 1.0.1 - - iconv-lite@0.7.0: - dependencies: - safer-buffer: 2.1.2 - ignore@5.3.2: {} ignore@7.0.5: {} @@ -5491,10 +5035,6 @@ snapshots: imurmurhash@0.1.4: {} - inherits@2.0.4: {} - - ipaddr.js@1.9.1: {} - is-extglob@2.1.1: {} is-glob@4.0.3: @@ -5503,8 +5043,6 @@ snapshots: is-plain-obj@4.1.0: {} - is-promise@4.0.0: {} - is-reference@3.0.3: dependencies: '@types/estree': 1.0.8 @@ -5513,8 +5051,6 @@ snapshots: jiti@2.6.1: {} - jose@6.1.3: {} - js-base64@3.7.8: {} js-yaml@4.1.1: @@ -5638,6 +5174,8 @@ snapshots: loro-crdt@1.10.3: {} + lz-string@1.5.0: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -5655,8 +5193,6 @@ snapshots: markdown-table@3.0.4: {} - math-intrinsics@1.1.0: {} - mdast-util-definitions@6.0.0: dependencies: '@types/mdast': 4.0.4 @@ -5767,10 +5303,6 @@ snapshots: mdurl@2.0.0: {} - media-typer@1.1.0: {} - - merge-descriptors@2.0.0: {} - micromark-core-commonmark@2.0.3: dependencies: decode-named-character-reference: 1.2.0 @@ -5962,12 +5494,6 @@ snapshots: transitivePeerDependencies: - supports-color - mime-db@1.54.0: {} - - mime-types@3.0.2: - dependencies: - mime-db: 1.54.0 - minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -6006,8 +5532,6 @@ snapshots: natural-compare@1.4.0: {} - negotiator@1.0.0: {} - node-domexception@1.0.0: {} node-emoji@2.2.0: @@ -6028,18 +5552,6 @@ snapshots: detect-libc: 2.1.2 optional: true - object-assign@4.1.1: {} - - object-inspect@1.13.4: {} - - on-finished@2.4.1: - dependencies: - ee-first: 1.1.1 - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -6063,20 +5575,14 @@ snapshots: dependencies: callsites: 3.1.0 - parseurl@1.3.3: {} - path-exists@4.0.0: {} path-key@3.1.1: {} - path-to-regexp@8.3.0: {} - picocolors@1.1.1: {} picomatch@4.0.3: {} - pkce-challenge@5.0.1: {} - playwright-core@1.57.0: {} playwright@1.57.0: @@ -6118,16 +5624,16 @@ snapshots: prelude-ls@1.2.1: {} - prettier-plugin-svelte@3.4.0(prettier@3.7.4)(svelte@5.45.7): + prettier-plugin-svelte@3.4.0(prettier@3.7.4)(svelte@5.45.8): dependencies: prettier: 3.7.4 - svelte: 5.45.7 + svelte: 5.45.8 - prettier-plugin-tailwindcss@0.7.2(prettier-plugin-svelte@3.4.0(prettier@3.7.4)(svelte@5.45.7))(prettier@3.7.4): + prettier-plugin-tailwindcss@0.7.2(prettier-plugin-svelte@3.4.0(prettier@3.7.4)(svelte@5.45.8))(prettier@3.7.4): dependencies: prettier: 3.7.4 optionalDependencies: - prettier-plugin-svelte: 3.4.0(prettier@3.7.4)(svelte@5.45.7) + prettier-plugin-svelte: 3.4.0(prettier@3.7.4)(svelte@5.45.8) prettier@3.7.4: {} @@ -6241,32 +5747,14 @@ snapshots: prosemirror-state: 1.4.4 prosemirror-transform: 1.10.5 - proxy-addr@2.0.7: - dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 - punycode.js@2.3.1: {} punycode@2.3.1: {} pure-rand@6.1.0: {} - qs@6.14.0: - dependencies: - side-channel: 1.1.0 - raf-schd@4.0.3: {} - range-parser@1.2.1: {} - - raw-body@3.0.2: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.1 - iconv-lite: 0.7.0 - unpipe: 1.0.0 - readdirp@4.1.2: {} remark-gfm@4.0.1: @@ -6346,89 +5834,32 @@ snapshots: rope-sequence@1.3.4: {} - router@2.2.0: + runed@0.37.0(@sveltejs/kit@2.49.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.45.8)(zod@4.1.13): dependencies: - debug: 4.4.3 - depd: 2.0.0 - is-promise: 4.0.0 - parseurl: 1.3.3 - path-to-regexp: 8.3.0 - transitivePeerDependencies: - - supports-color + dequal: 2.0.3 + esm-env: 1.2.2 + lz-string: 1.5.0 + svelte: 5.45.8 + optionalDependencies: + '@sveltejs/kit': 2.49.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.45.8)(vite@7.2.7(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)) + zod: 4.1.13 sade@1.8.1: dependencies: mri: 1.2.0 - safer-buffer@2.1.2: {} - scule@1.3.0: {} semver@7.7.3: {} - send@1.2.0: - dependencies: - debug: 4.4.3 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 2.0.0 - http-errors: 2.0.1 - mime-types: 3.0.2 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - - serve-static@2.2.0: - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 1.2.0 - transitivePeerDependencies: - - supports-color - set-cookie-parser@2.7.2: {} - setprototypeof@1.2.0: {} - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} - side-channel-list@1.0.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - - side-channel-map@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - - side-channel-weakmap@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 - - side-channel@1.1.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.0 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 - sirv@3.0.2: dependencies: '@polka/url': 1.0.0-next.29 @@ -6448,8 +5879,6 @@ snapshots: source-map@0.6.1: {} - statuses@2.0.2: {} - strip-json-comments@3.1.1: {} style-mod@4.1.3: {} @@ -6458,19 +5887,19 @@ snapshots: dependencies: has-flag: 4.0.0 - svelte-check@4.3.4(picomatch@4.0.3)(svelte@5.45.7)(typescript@5.9.3): + svelte-check@4.3.4(picomatch@4.0.3)(svelte@5.45.8)(typescript@5.9.3): dependencies: '@jridgewell/trace-mapping': 0.3.31 chokidar: 4.0.3 fdir: 6.5.0(picomatch@4.0.3) picocolors: 1.1.1 sade: 1.8.1 - svelte: 5.45.7 + svelte: 5.45.8 typescript: 5.9.3 transitivePeerDependencies: - picomatch - svelte-eslint-parser@1.4.1(svelte@5.45.7): + svelte-eslint-parser@1.4.1(svelte@5.45.8): dependencies: eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -6479,16 +5908,16 @@ snapshots: postcss-scss: 4.0.9(postcss@8.5.6) postcss-selector-parser: 7.1.1 optionalDependencies: - svelte: 5.45.7 + svelte: 5.45.8 - svelte2tsx@0.7.45(svelte@5.45.7)(typescript@5.9.3): + svelte2tsx@0.7.45(svelte@5.45.8)(typescript@5.9.3): dependencies: dedent-js: 1.0.1 scule: 1.3.0 - svelte: 5.45.7 + svelte: 5.45.8 typescript: 5.9.3 - svelte@5.45.7: + svelte@5.45.8: dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 @@ -6510,6 +5939,12 @@ snapshots: tapable@2.3.0: {} + temporal-polyfill@0.3.0: + dependencies: + temporal-spec: 0.3.0 + + temporal-spec@0.3.0: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) @@ -6523,8 +5958,6 @@ snapshots: markdown-it-task-lists: 2.1.1 prosemirror-markdown: 1.13.2 - toidentifier@1.0.1: {} - totalist@3.0.1: {} trough@2.2.0: {} @@ -6544,12 +5977,6 @@ snapshots: dependencies: prelude-ls: 1.2.1 - type-is@2.0.1: - dependencies: - content-type: 1.0.5 - media-typer: 1.1.0 - mime-types: 3.0.2 - typescript-eslint@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3): dependencies: '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) @@ -6561,10 +5988,10 @@ snapshots: transitivePeerDependencies: - supports-color - typescript-svelte-plugin@0.3.50(svelte@5.45.7)(typescript@5.9.3): + typescript-svelte-plugin@0.3.50(svelte@5.45.8)(typescript@5.9.3): dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - svelte2tsx: 0.7.45(svelte@5.45.7)(typescript@5.9.3) + svelte2tsx: 0.7.45(svelte@5.45.8)(typescript@5.9.3) transitivePeerDependencies: - svelte - typescript @@ -6606,8 +6033,6 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 - unpipe@1.0.0: {} - uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -6618,8 +6043,6 @@ snapshots: uuid@11.1.0: {} - vary@1.1.2: {} - vfile-message@4.0.3: dependencies: '@types/unist': 3.0.3 @@ -6678,8 +6101,6 @@ snapshots: word-wrap@1.2.5: {} - wrappy@1.0.2: {} - ws@8.18.3: {} yaml@1.10.2: {} @@ -6688,10 +6109,7 @@ snapshots: zimmerframe@1.1.4: {} - zod-to-json-schema@3.25.0(zod@4.1.13): - dependencies: - zod: 4.1.13 - - zod@4.1.13: {} + zod@4.1.13: + optional: true zwitch@2.0.4: {} diff --git a/reproduce_crypto.ts b/reproduce_crypto.ts deleted file mode 100644 index 75f7283..0000000 --- a/reproduce_crypto.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { - generateEncryptionKeyPair, - encryptKeyForDevice, - decryptKeyForDevice, - generateNoteKey, -} from "./src/lib/crypto.ts"; - -async function testCrypto() { - console.log("=== Crypto Verification Start ==="); - - // 1. Generate Identity (User B) - console.log("1. Generating User B keys..."); - const userB = await generateEncryptionKeyPair(); - console.log(" User B Public: ", userB.publicKey); - console.log(" User B Private:", userB.privateKey); - - // 2. Generate Note Key (Server A) - console.log("\n2. Generating Note Key..."); - const noteKey = generateNoteKey(); - console.log(" Note Key (Original):", noteKey); - console.log(" Note Key Length: ", noteKey.length); - - // 3. Encrypt for User B (Server A action) - console.log("\n3. Encrypting for User B..."); - try { - const envelope = encryptKeyForDevice(noteKey, userB.publicKey); - console.log(" Envelope: ", envelope); - console.log(" Envelope Length:", envelope.length); - - // 4. Decrypt as User B (Client B action) - console.log("\n4. Decrypting as User B..."); - const decryptedKey = decryptKeyForDevice(envelope, userB.privateKey); - console.log(" Decrypted Key: ", decryptedKey); - - if (decryptedKey === noteKey) { - console.log("\n✅ SUCCESS: Keys match!"); - } else { - console.error("\n❌ FAILURE: Keys do not match!"); - console.error("Expected:", noteKey); - console.error("Got: ", decryptedKey); - } - } catch (e) { - console.error("\n❌ CRITICAL ERROR:", e); - } -} - -testCrypto(); diff --git a/scripts/check_db.ts b/scripts/check_db.ts index bf18e12..2febdc3 100644 --- a/scripts/check_db.ts +++ b/scripts/check_db.ts @@ -1,25 +1,29 @@ +import "dotenv/config"; + +import { sql, count } from "drizzle-orm"; +import { users } from "$lib/server/db/schema.ts"; import { createClient } from "@libsql/client"; +import { drizzle } from "drizzle-orm/libsql"; +import * as schema from "../src/lib/server/db/schema.ts"; -const dbPath = process.env.DATABASE_URL || "file:local.db"; -console.log(`Checking database: ${dbPath}`); +if (!process.env["DATABASE_URL"]) throw new Error("DATABASE_URL is not set"); const client = createClient({ - url: dbPath, + url: process.env["DATABASE_URL"], }); -async function main() { - try { - const result = await client.execute("PRAGMA table_info(users);"); - console.log("Users table columns:"); - result.rows.forEach((row) => { - console.log(`- ${row.name} (${row.type})`); - }); +const db = drizzle(client, { schema }); - const count = await client.execute("SELECT count(*) as count FROM users;"); - console.log(`User count: ${count.rows[0].count}`); - } catch (e) { - console.error("Error:", e); - } -} +try { + const result = await db.run(sql`PRAGMA tabdrizzle, le_info(users);`); + console.log("Users table columns:"); + result.rows.forEach((row) => { + const r = row as unknown as { name: string; type: string }; + console.log(`- ${r.name} (${r.type})`); + }); -main(); + const [userCount] = await db.select({ count: count() }).from(users); + console.log(`User count: ${String(userCount?.count ?? 0)}`); +} catch (e) { + console.error("Error:", e); +} diff --git a/scripts/check_identity.ts b/scripts/check_identity.ts new file mode 100644 index 0000000..af5d605 --- /dev/null +++ b/scripts/check_identity.ts @@ -0,0 +1,24 @@ +import { fetchUserIdentity } from "../src/lib/server/federation.ts"; + +console.log("=== Identity Fetch Verification ==="); +const handle = "@bob:localhost:5174"; +const requestingDomain = "localhost:5173"; + +try { + console.log(`Fetching identity for ${handle} from ${requestingDomain}...`); + const identity = await fetchUserIdentity(handle, requestingDomain); + + console.log("Result:"); + console.log(JSON.stringify(identity, null, 2)); + + if (identity?.publicKey) { + console.log( + "\nPublic Key First 10 chars:", + identity.publicKey.slice(0, 10), + ); + } else { + console.log("\n❌ No Public Key found!"); + } +} catch (e) { + console.error("❌ Error fetching identity:", e); +} diff --git a/scripts/check_keypair.ts b/scripts/check_keypair.ts new file mode 100644 index 0000000..e215985 --- /dev/null +++ b/scripts/check_keypair.ts @@ -0,0 +1,65 @@ +import "dotenv/config"; +import { users } from "../src/lib/server/db/schema.ts"; +import { eq } from "drizzle-orm"; +import { + encryptKeyForDevice, + decryptKeyForDevice, + generateNoteKey, +} from "../src/lib/crypto.ts"; +import { createClient } from "@libsql/client"; +import { drizzle } from "drizzle-orm/libsql"; +import * as schema from "../src/lib/server/db/schema.ts"; + +if (!process.env["DATABASE_URL"]) throw new Error("DATABASE_URL is not set"); + +const client = createClient({ + url: process.env["DATABASE_URL"], +}); + +const db = drizzle(client, { schema }); + +console.log("=== Keypair ConsiscreateClient, tency Check ==="); + +// 1. Get Bob's keys +const bob = await db.query.users.findFirst({ + where: eq(users.username, "bob"), +}); + +if (!bob) { + console.error("❌ Bob not found in DB!"); + process.exit(1); +} + +console.log("Bob:", bob.id); +console.log("Public Key: ", bob.publicKey); +console.log("Private Key:", bob.privateKeyEncrypted); + +if (!bob.publicKey || !bob.privateKeyEncrypted) { + console.error("❌ Missing keys!"); + process.exit(1); +} + +// 2. Test Keypair +const secret = generateNoteKey(); +console.log("\nTest Secret:", secret); + +try { + // Encrypt to Bob's Public Key + const envelope = await encryptKeyForDevice(secret, bob.publicKey); + console.log("Encrypted Envelope:", envelope); + + // Decrypt with Bob's Private Key + const decrypted = await decryptKeyForDevice( + envelope, + bob.privateKeyEncrypted, + ); + console.log("Decrypted Secret: ", decrypted); + + if (decrypted === secret) { + console.log("\n✅ SUCCESS: Stored keys are a valid pair!"); + } else { + console.error("\n❌ FAILURE: Decrypted secret does not match!"); + } +} catch (e) { + console.error("\n❌ CRITICAL ERROR during test:", e); +} diff --git a/scripts/logout.ts b/scripts/logout.ts index 7949ede..ae63816 100644 --- a/scripts/logout.ts +++ b/scripts/logout.ts @@ -1,27 +1,22 @@ import "dotenv/config"; import { createClient } from "@libsql/client"; import { drizzle } from "drizzle-orm/libsql"; -import { sessions } from "../src/lib/server/db/schema.js"; +import * as schema from "../src/lib/server/db/schema.ts"; -async function main() { - const url = process.env.DATABASE_URL; - if (!url) { - console.error("DATABASE_URL is not set in .env"); - process.exit(1); - } +if (!process.env["DATABASE_URL"]) throw new Error("DATABASE_URL is not set"); - const client = createClient({ url }); - const db = drizzle(client); +const client = createClient({ + url: process.env["DATABASE_URL"], +}); - console.log("Clearing all sessions..."); - try { - const result = await db.delete(sessions); - console.log("Successfully deleted all sessions."); - } catch (error) { - console.error("Error clearing sessions:", error); - process.exit(1); - } - process.exit(0); -} +const db = drizzle(client, { schema }); -main(); +console.log("Clearing all sessions..."); +try { + await db.delete(schema.sessions); + console.log("Successfully deleted all sessions."); +} catch (error) { + console.error("Error clearing sessions:", error); + process.exit(1); +} +process.exit(0); diff --git a/scripts/reproduce_crypto.ts b/scripts/reproduce_crypto.ts new file mode 100644 index 0000000..f8a2b34 --- /dev/null +++ b/scripts/reproduce_crypto.ts @@ -0,0 +1,43 @@ +import { + generateEncryptionKeyPair, + encryptKeyForDevice, + decryptKeyForDevice, + generateNoteKey, +} from "../src/lib/crypto.ts"; + +console.log("=== Crypto Verification Start ==="); + +// 1. Generate Identity (User B) +console.log("1. Generating User B keys..."); +const userB = await generateEncryptionKeyPair(); +console.log(" User B Public: ", userB.publicKey); +console.log(" User B Private:", userB.privateKey); + +// 2. Generate Note Key (Server A) +console.log("\n2. Generating Note Key..."); +const noteKey = generateNoteKey(); +console.log(" Note Key (Original):", noteKey); +console.log(" Note Key Length: ", noteKey.length); + +// 3. Encrypt for User B (Server A action) +console.log("\n3. Encrypting for User B..."); +try { + const envelope = await encryptKeyForDevice(noteKey, userB.publicKey); + console.log(" Envelope: ", envelope); + console.log(" Envelope Length:", envelope.length); + + // 4. Decrypt as User B (Client B action) + console.log("\n4. Decrypting as User B..."); + const decryptedKey = await decryptKeyForDevice(envelope, userB.privateKey); + console.log(" Decrypted Key: ", decryptedKey); + + if (decryptedKey === noteKey) { + console.log("\n✅ SUCCESS: Keys match!"); + } else { + console.error("\n❌ FAILURE: Keys do not match!"); + console.error("Expected:", noteKey); + console.error("Got: ", decryptedKey); + } +} catch (e) { + console.error("\n❌ CRITICAL ERROR:", e); +} diff --git a/scripts/test-crypto.js b/scripts/test-crypto.js deleted file mode 100644 index 6ab23f4..0000000 --- a/scripts/test-crypto.js +++ /dev/null @@ -1,64 +0,0 @@ -import { pbkdf2 } from "@noble/hashes/pbkdf2.js"; -import { sha256 } from "@noble/hashes/sha2.js"; -import { xchacha20poly1305 } from "@noble/ciphers/chacha.js"; -import { randomBytes } from "crypto"; // Node generic - -// Mimic browser primitives -const encodeBase64 = (arr) => Buffer.from(arr).toString("base64"); -const decodeBase64 = (str) => new Uint8Array(Buffer.from(str, "base64")); -const getRandomBytes = (len) => new Uint8Array(randomBytes(len)); - -async function encryptWithPassword(dataBase64, password) { - const data = decodeBase64(dataBase64); - const salt = getRandomBytes(16); - // Derive key from password - const kek = pbkdf2(sha256, password, salt, { c: 600000, dkLen: 32 }); - - // Encrypt - const nonce = getRandomBytes(24); - const chacha = xchacha20poly1305(kek, nonce); - const ciphertext = chacha.encrypt(data); - - // Pack: salt + nonce + ciphertext - const result = new Uint8Array(16 + 24 + ciphertext.length); - result.set(salt, 0); - result.set(nonce, 16); - result.set(ciphertext, 40); - - return encodeBase64(result); -} - -async function decryptWithPassword(encryptedBase64, password) { - const encrypted = decodeBase64(encryptedBase64); - const salt = encrypted.slice(0, 16); - const nonce = encrypted.slice(16, 40); - const ciphertext = encrypted.slice(40); - - const kek = pbkdf2(sha256, password, salt, { c: 600000, dkLen: 32 }); - const chacha = xchacha20poly1305(kek, nonce); - const data = chacha.decrypt(ciphertext); - return encodeBase64(data); -} - -async function test() { - const rawData = "This is a secret key"; - const rawBase64 = encodeBase64(new TextEncoder().encode(rawData)); - const pass = "password123"; - - console.log("Original:", rawData); - const encrypted = await encryptWithPassword(rawBase64, pass); - console.log("Encrypted:", encrypted); - - const decryptedBase64 = await decryptWithPassword(encrypted, pass); - const decrypted = new TextDecoder().decode(decodeBase64(decryptedBase64)); - console.log("Decrypted:", decrypted); - - if (rawData === decrypted) { - console.log("PASS"); - } else { - console.error("FAIL"); - process.exit(1); - } -} - -test(); diff --git a/scripts/test-crypto.ts b/scripts/test-crypto.ts new file mode 100644 index 0000000..8486126 --- /dev/null +++ b/scripts/test-crypto.ts @@ -0,0 +1,60 @@ +import { pbkdf2 } from "@noble/hashes/pbkdf2.js"; +import { sha256 } from "@noble/hashes/sha2.js"; +import { xchacha20poly1305 } from "@noble/ciphers/chacha.js"; + +function encryptWithPassword( + dataBase64: string, + password: string, +): Uint8Array { + const data = Uint8Array.from(dataBase64); + const salt = crypto.getRandomValues(new Uint8Array(16)); + // Derive key from password + const kek = pbkdf2(sha256, password, salt, { c: 600000, dkLen: 32 }); + + // Encrypt + const nonce = crypto.getRandomValues(new Uint8Array(24)); + const chacha = xchacha20poly1305(kek, nonce); + const ciphertext = chacha.encrypt(data); + + // Pack: salt + nonce + ciphertext + const result = new Uint8Array(16 + 24 + ciphertext.length); + result.set(salt, 0); + result.set(nonce, 16); + result.set(ciphertext, 40); + + return Uint8Array.from(result); +} + +function decryptWithPassword( + encryptedBase64: Uint8Array, + password: string, +): Uint8Array { + const salt = encryptedBase64.slice(0, 16); + const nonce = encryptedBase64.slice(16, 40); + const ciphertext = encryptedBase64.slice(40); + + const kek = pbkdf2(sha256, password, salt, { c: 600000, dkLen: 32 }); + const chacha = xchacha20poly1305(kek, nonce); + const data = chacha.decrypt(ciphertext); + + return Uint8Array.from(data); +} + +const rawData = "This is a secret key"; +const rawBase64 = new TextEncoder().encode(rawData).toBase64(); +const pass = "password123"; + +console.log("Original:", rawData); +const encrypted = encryptWithPassword(rawBase64, pass); +console.log("Encrypted:", encrypted); + +const decryptedBase64 = decryptWithPassword(encrypted, pass); +const decrypted = new TextDecoder().decode(decryptedBase64); +console.log("Decrypted:", decrypted); + +if (rawData === decrypted) { + console.log("PASS"); +} else { + console.error("FAIL"); + process.exit(1); +} diff --git a/scripts/verify_alice_key.ts b/scripts/verify_alice_key.ts new file mode 100644 index 0000000..7698325 --- /dev/null +++ b/scripts/verify_alice_key.ts @@ -0,0 +1,15 @@ +import { decryptKeyForDevice } from "../src/lib/crypto.ts"; + +const alicePriv = "8yxKreQsh2I8gQiL4GsHQQs4LSJGlOVVDETkIU6NB2c="; +const noteKeyEnc = + "c1i1h1NO8WN3buoPWrmY++GQFulEtokhhNnJshR/ygNwiNo96p/spl3lJKJt42D6LeeaBEBjdM1Ioq3iJ3kzLXhKX8kNYOll1KMkAJCoUiRbr6HG2+DCEd1xeT6fixowjKc8BCXeTfc="; + +console.log("=== Verifying Alice's Decryption ==="); +try { + const raw = await decryptKeyForDevice(noteKeyEnc, alicePriv); + console.log("✅ SUCCESS!"); + console.log("Raw Key:", raw); + console.log("Length:", raw.length); // Should be 44 +} catch (e) { + console.error("❌ FAILED:", e); +} diff --git a/scripts/verify_keys.ts b/scripts/verify_keys.ts new file mode 100644 index 0000000..ec5f406 --- /dev/null +++ b/scripts/verify_keys.ts @@ -0,0 +1,31 @@ +import { + encryptKeyForDevice, + decryptKeyForDevice, + generateNoteKey, +} from "../src/lib/crypto.ts"; + +// I will replace these with values from the DB +const publicKey = "ouOeCZu2NN+erXNttehhxtnIBwdFgkANhxkJtrwqNCg="; +const privateKey = "H/UgylrpmcHHpYuhanuLDUOd/VAzWouh75xyEXUxLh8="; + +if (publicKey.includes("REPLACE")) { + console.error("Please replace placeholder keys!"); + process.exit(1); +} + +console.log("=== Verifying Keys ==="); +console.log("Pub:", publicKey.slice(0, 10) + "..."); + +const secret = generateNoteKey(); +try { + const envelope = await encryptKeyForDevice(secret, publicKey); + const decrypted = await decryptKeyForDevice(envelope, privateKey); + + if (decrypted === secret) { + console.log("✅ SUCCESS: Keys work!"); + } else { + console.error("❌ FAILURE: Decryption mismatch"); + } +} catch (e) { + console.error("❌ ERROR:", e); +} diff --git a/server-a-identity.json b/server-a-identity.json index 2ffba5d..4a6294c 100644 --- a/server-a-identity.json +++ b/server-a-identity.json @@ -2,4 +2,4 @@ "publicKey": "lIOFN8n4HkE5DXjzFsa+xYL9CtFTedbe7/rQR0Kr0uA=", "privateKey": "wRgtdxuggx7rIlRbe4+A1W46bQ2AB8LZX5PIyshVM/w=", "domain": "localhost:5173" -} \ No newline at end of file +} diff --git a/server-b-identity.json b/server-b-identity.json index 088d10c..2f802bd 100644 --- a/server-b-identity.json +++ b/server-b-identity.json @@ -2,4 +2,4 @@ "publicKey": "xG1+klHbBRobqxsz8oJ2ty2Km4hMHzd+y8A6Btw5E1k=", "privateKey": "UCzPB9k/xfFFYuJVPmY2Za1Jvuw8o7E4FnH+PBwyLJc=", "domain": "localhost:5174" -} \ No newline at end of file +} diff --git a/server-identity.json b/server-identity.json index 915c462..5e88553 100644 --- a/server-identity.json +++ b/server-identity.json @@ -4,4 +4,4 @@ "domain": "localhost:5174", "encryptionPublicKey": "YTrup6VwXOzS1hDP/DBzr7YwW5IFlvN0mNtz01Bvgyo=", "encryptionPrivateKey": "W6IS4SujT5QLmoKJ23KHLrKlGxxdkgDz/gjpm6g20aI=" -} \ No newline at end of file +} diff --git a/src/lib/components/ConfirmationModal.svelte b/src/lib/components/ConfirmationModal.svelte index 7afe2b6..653840e 100644 --- a/src/lib/components/ConfirmationModal.svelte +++ b/src/lib/components/ConfirmationModal.svelte @@ -1,5 +1,5 @@ -{#if isOpen} + { + isOpen = false; + }} +>
Version History
{/if} - - - -{/if} + +
+ + + + + diff --git a/src/lib/components/MembersModal.svelte b/src/lib/components/MembersModal.svelte index 2bbf0ef..57b3b4c 100644 --- a/src/lib/components/MembersModal.svelte +++ b/src/lib/components/MembersModal.svelte @@ -1,5 +1,6 @@ - - - -
- - - {#if isOpen} -
- - - {#if isOwner} - - -
- - - {:else} -
- - - {/if} -
- {/if} -
diff --git a/src/lib/components/ProfilePicture.svelte b/src/lib/components/ProfilePicture.svelte index 4237a18..4094981 100644 --- a/src/lib/components/ProfilePicture.svelte +++ b/src/lib/components/ProfilePicture.svelte @@ -7,7 +7,7 @@
- {name[0]?.toUpperCase()} + {name.toUpperCase()}
diff --git a/src/lib/components/ShareModal.svelte b/src/lib/components/ShareModal.svelte index 35ceb5a..9d6c4b4 100644 --- a/src/lib/components/ShareModal.svelte +++ b/src/lib/components/ShareModal.svelte @@ -5,14 +5,12 @@ Lock, Globe, UserPlus, - Loader2, Check, + LoaderCircle, } from "@lucide/svelte"; import { decryptKey, encryptWithPassword } from "$lib/crypto"; - // ... - interface Props { isOpen: boolean; noteId: string; @@ -55,8 +53,8 @@ const res = await fetch(`/api/notes/${noteId}/share`); if (res.ok) { const data = await res.json(); - accessLevel = data.accessLevel || "private"; - invitedUsers = data.invitedUsers || []; + accessLevel = data.accessLevel ?? "private"; + invitedUsers = data.invitedUsers ?? []; } else if (res.status !== 404) { // 404 is fine - just means no settings yet const text = await res.text(); @@ -347,7 +345,7 @@ {#if invitedUsers.length > 0}
- {#each invitedUsers as user} + {#each invitedUsers as user (user)}
@@ -417,7 +415,7 @@ disabled={saving || loading} > {#if saving} - + Saving... {:else if success} diff --git a/src/lib/components/Sidebar.svelte b/src/lib/components/Sidebar.svelte index 895aaf4..d25efc7 100644 --- a/src/lib/components/Sidebar.svelte +++ b/src/lib/components/Sidebar.svelte @@ -1,5 +1,4 @@
@@ -260,16 +255,12 @@ - (isHistoryOpen = false)} - /> + n.id === noteId)?.encryptedKey} + noteEncryptedKey={notesList.find((n) => n.id === noteId)?.encryptedKey} isOpen={isShareOpen} onClose={() => (isShareOpen = false)} /> diff --git a/src/lib/components/codemirror/Toolbar.svelte b/src/lib/components/codemirror/Toolbar.svelte index d2ed228..4f06078 100644 --- a/src/lib/components/codemirror/Toolbar.svelte +++ b/src/lib/components/codemirror/Toolbar.svelte @@ -1,16 +1,10 @@
- {#if sidebarCtx?.isCollapsed} + {#if sidebarCtx.isCollapsed}
{/if} - - {#each sortedGroups as group, index (index)} - {#if shouldShowAsButtons(index)} -
- {#each group.tools as tool (tool.title)} - {@const Icon = tool.icon} - - {/each} -
-
+ + {#each sortedGroups as group, i (group.label ?? group.priority)} + {@const isLast = i === sortedGroups.length - 1} + {@const priorityClass = + group.priority === 1 + ? "" + : group.priority === 2 + ? "hidden @md:flex" + : "hidden @lg:flex"} +
+ {#each group.tools as tool (tool.title)} + {@const Icon = tool.icon} + + {/each} +
+ {#if !isLast} +
{/if} {/each} - - {#if collapsedGroups.length > 0} - diff --git a/src/lib/components/sidebar-context.ts b/src/lib/components/sidebar-context.ts index bd8c70c..f874446 100644 --- a/src/lib/components/sidebar-context.ts +++ b/src/lib/components/sidebar-context.ts @@ -1,6 +1,9 @@ -export const SIDEBAR_CONTEXT_KEY = Symbol("sidebar-context"); +import { createContext } from "svelte"; export interface SidebarContext { isCollapsed: boolean; toggleSidebar: () => void; } + +export const [getSidebarContext, setSidebarContext] = + createContext(); diff --git a/src/lib/crypto.ts b/src/lib/crypto.ts index 97f678b..16c1121 100644 --- a/src/lib/crypto.ts +++ b/src/lib/crypto.ts @@ -1,16 +1,11 @@ -import { ed25519, x25519 } from "@noble/curves/ed25519.js"; -import { xchacha20poly1305 } from "@noble/ciphers/chacha.js"; -import { encodeBase64, decodeBase64 } from "@oslojs/encoding"; -import { hkdf } from "@noble/hashes/hkdf.js"; -import { sha256 } from "@noble/hashes/sha2.js"; - -// Use built-in randomBytes -function getRandomBytes(len: number) { - if (typeof globalThis.crypto !== "undefined") { - return globalThis.crypto.getRandomValues(new Uint8Array(len)); - } - throw new Error("WebCrypto not available"); -} +import { Chacha20Poly1305 } from "@hpke/chacha20poly1305"; +import { CipherSuite, DhkemX25519HkdfSha256, HkdfSha256 } from "@hpke/core"; + +const suite = new CipherSuite({ + kem: new DhkemX25519HkdfSha256(), + kdf: new HkdfSha256(), + aead: new Chacha20Poly1305(), +}); // ---------------------------------------------------------------------------- // Types @@ -31,147 +26,178 @@ export interface DeviceKeys { // ---------------------------------------------------------------------------- export async function generateSigningKeyPair(): Promise { - const priv = ed25519.utils.randomSecretKey(); - const pub = await ed25519.getPublicKey(priv); + const keyPair = await crypto.subtle.generateKey("Ed25519", true, [ + "sign", + "verify", + ]); + + const pub = await crypto.subtle.exportKey("raw", keyPair.publicKey); + const priv = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey); + return { - publicKey: encodeBase64(pub), - privateKey: encodeBase64(priv), + publicKey: new Uint8Array(pub).toBase64(), + privateKey: new Uint8Array(priv).toBase64(), }; } export async function sign( - message: Uint8Array, + message: Uint8Array, privateKeyBase64: string, ): Promise { - const priv = decodeBase64(privateKeyBase64); - const signature = await ed25519.sign(message, priv); - return encodeBase64(signature); + const privBytes = Uint8Array.fromBase64(privateKeyBase64); + const privKey = await crypto.subtle.importKey( + "pkcs8", + privBytes, + "Ed25519", + false, + ["sign"], + ); + const signature = await crypto.subtle.sign("Ed25519", privKey, message); + return new Uint8Array(signature).toBase64(); } export async function verify( signatureBase64: string, - message: Uint8Array, + message: Uint8Array, publicKeyBase64: string, ): Promise { - const sig = decodeBase64(signatureBase64); - const pub = decodeBase64(publicKeyBase64); - return ed25519.verify(sig, message, pub); + const sigBytes = Uint8Array.fromBase64(signatureBase64); + const pubBytes = Uint8Array.fromBase64(publicKeyBase64); + const pubKey = await crypto.subtle.importKey( + "raw", + pubBytes, + "Ed25519", + false, + ["verify"], + ); + return crypto.subtle.verify("Ed25519", pubKey, sigBytes, message); } // ---------------------------------------------------------------------------- -// Key Exchange / Encryption (X25519 + XChaCha20Poly1305) +// Key Exchange / Encryption (HPKE: X25519 + HKDF + ChaCha20Poly1305) // ---------------------------------------------------------------------------- export async function generateEncryptionKeyPair(): Promise { - const priv = x25519.utils.randomSecretKey(); - const pub = x25519.getPublicKey(priv); + const keyPair = await suite.kem.generateKeyPair(); + const pub = await suite.kem.serializePublicKey(keyPair.publicKey); + const priv = await suite.kem.serializePrivateKey(keyPair.privateKey); + return { - publicKey: encodeBase64(pub), - privateKey: encodeBase64(priv), + publicKey: new Uint8Array(pub).toBase64(), + privateKey: new Uint8Array(priv).toBase64(), }; } // Generate a random 32-byte key for the document export function generateNoteKey(): string { - const key = getRandomBytes(32); - return encodeBase64(key); + const key = globalThis.crypto.getRandomValues(new Uint8Array(32)); + return key.toBase64(); } /** - * Encrypts the document key for a specific recipient device. - * Uses an ephemeral key pair for the sender (anonymous). - * Format: [ephemeral_pub (32)] + [nonce (24)] + [ciphertext] + * Encrypts the document key for a specific recipient device using HPKE. + * Format: [enc (32)] + [ciphertext] */ -export function encryptKeyForDevice( +export async function encryptKeyForDevice( noteKeyBase64: string, recipientPublicKeyBase64: string, -): string { - const noteKey = decodeBase64(noteKeyBase64); - const recipientPub = decodeBase64(recipientPublicKeyBase64); - - // 1. Generate ephemeral sender key - const ephemeralPriv = x25519.utils.randomSecretKey(); - const ephemeralPub = x25519.getPublicKey(ephemeralPriv); - - // 2. ECDH Shared Secret - const sharedSecret = x25519.getSharedSecret(ephemeralPriv, recipientPub); - - // 3. HKDF to derive symmetric key - const info = new TextEncoder().encode("notes-app-key-encryption"); - const derivedKey = hkdf(sha256, sharedSecret, undefined, info, 32); - - // 4. Encrypt note key with XChaCha20Poly1305 - const nonce = getRandomBytes(24); - const chacha = xchacha20poly1305(derivedKey, nonce); - const ciphertext = chacha.encrypt(noteKey); - - // 5. Pack: ephemeralPub (32) + nonce (24) + ciphertext - const result = new Uint8Array(32 + 24 + ciphertext.length); - result.set(ephemeralPub, 0); - result.set(nonce, 32); - result.set(ciphertext, 56); - - const encoded = encodeBase64(result); - return encoded; +): Promise { + const noteKey = Uint8Array.fromBase64(noteKeyBase64); + const recipientPub = Uint8Array.fromBase64(recipientPublicKeyBase64); + + const recipientPublicKey = await suite.kem.importKey( + "raw", + recipientPub.buffer, + true, + ); + + const { ct, enc } = await suite.seal({ recipientPublicKey }, noteKey.buffer); + + // Pack: enc (32) + ct + const result = new Uint8Array(enc.byteLength + ct.byteLength); + result.set(new Uint8Array(enc), 0); + result.set(new Uint8Array(ct), enc.byteLength); + + return result.toBase64(); } -export function decryptKeyForDevice( +export async function decryptKeyForDevice( encryptedEnvelopeBase64: string, devicePrivateKeyBase64: string, -): string { - const envelope = decodeBase64(encryptedEnvelopeBase64); - const devicePriv = decodeBase64(devicePrivateKeyBase64); +): Promise { + const envelope = Uint8Array.fromBase64(encryptedEnvelopeBase64); + const devicePriv = Uint8Array.fromBase64(devicePrivateKeyBase64); - if (envelope.length < 56) throw new Error("Envelope too short"); + if (envelope.length < 32) throw new Error("Envelope too short"); - const ephemeralPub = envelope.slice(0, 32); - const nonce = envelope.slice(32, 56); - const ciphertext = envelope.slice(56); + const enc = envelope.slice(0, 32); + const ct = envelope.slice(32); - const sharedSecret = x25519.getSharedSecret(devicePriv, ephemeralPub); - const info = new TextEncoder().encode("notes-app-key-encryption"); - const derivedKey = hkdf(sha256, sharedSecret, undefined, info, 32); + const recipientKey = await suite.kem.importKey( + "raw", + devicePriv.buffer, + false, + ); - const chacha = xchacha20poly1305(derivedKey, nonce); - const noteKey = chacha.decrypt(ciphertext); + const noteKey = await suite.open( + { recipientKey, enc: enc.buffer }, + ct.buffer, + ); - return encodeBase64(noteKey); + return new Uint8Array(noteKey).toBase64(); } // ---------------------------------------------------------------------------- -// Content Encryption (XChaCha20Poly1305) +// Content Encryption (ChaCha20Poly1305) // ---------------------------------------------------------------------------- -export function encryptData( +export async function encryptData( data: Uint8Array, noteKeyBase64: string, -): Uint8Array { - const key = decodeBase64(noteKeyBase64); - - // Per message nonce - const nonce = getRandomBytes(24); - const chacha = xchacha20poly1305(key, nonce); - const ciphertext = chacha.encrypt(data); +): Promise { + const key = Uint8Array.fromBase64(noteKeyBase64); + const aead = new Chacha20Poly1305(); + const ctx = aead.createEncryptionContext(key.buffer); + + // Per message nonce (12 bytes for standard ChaCha20Poly1305) + const nonce = crypto.getRandomValues(new Uint8Array(12)); + const ciphertext = await ctx.seal( + nonce.buffer, + data.buffer.slice( + data.byteOffset, + data.byteOffset + data.byteLength, + ) as ArrayBuffer, + new ArrayBuffer(0), + ); // Prepend nonce - const result = new Uint8Array(24 + ciphertext.length); + const result = new Uint8Array(12 + ciphertext.byteLength); result.set(nonce, 0); - result.set(ciphertext, 24); + result.set(new Uint8Array(ciphertext), 12); return result; } -export function decryptData( +export async function decryptData( encrypted: Uint8Array, noteKeyBase64: string, -): Uint8Array { - const key = decodeBase64(noteKeyBase64); - - // Extract IV from first 24 bytes - const nonce = encrypted.slice(0, 24); - const ciphertext = encrypted.slice(24); - - const chacha = xchacha20poly1305(key, nonce); - return chacha.decrypt(ciphertext); +): Promise { + const key = Uint8Array.fromBase64(noteKeyBase64); + const aead = new Chacha20Poly1305(); + const ctx = aead.createEncryptionContext(key.buffer); + + // Extract IV from first 12 bytes + const nonce = encrypted.slice(0, 12); + const ciphertext = encrypted.slice(12); + + const decrypted = await ctx.open( + nonce.buffer, + ciphertext.buffer.slice( + ciphertext.byteOffset, + ciphertext.byteOffset + ciphertext.byteLength, + ), + new ArrayBuffer(0), + ); + return new Uint8Array(decrypted); } export const encryptKeyForUser = encryptKeyForDevice; @@ -179,53 +205,100 @@ export const decryptKey = decryptKeyForDevice; export const generateUserKeys = generateEncryptionKeyPair; // ---------------------------------------------------------------------------- -// Password Encryption (PBKDF2 + XChaCha20Poly1305) +// Password Encryption (PBKDF2 + ChaCha20Poly1305) // ---------------------------------------------------------------------------- -import { pbkdf2 } from "@noble/hashes/pbkdf2.js"; - export async function encryptWithPassword( dataBase64: string, password: string, ): Promise { - const data = decodeBase64(dataBase64); - const salt = getRandomBytes(16); - // Derive key from password - const kek = pbkdf2(sha256, password, salt, { c: 600000, dkLen: 32 }); - - // Encrypt - const nonce = getRandomBytes(24); - const chacha = xchacha20poly1305(kek, nonce); - const ciphertext = chacha.encrypt(data); + const data = Uint8Array.fromBase64(dataBase64); + const salt = globalThis.crypto.getRandomValues(new Uint8Array(16)); - // Pack: salt(16) + nonce(24) + ciphertext - const result = new Uint8Array(16 + 24 + ciphertext.length); + // Derive key from password + const passwordKey = await crypto.subtle.importKey( + "raw", + new TextEncoder().encode(password), + "PBKDF2", + false, + ["deriveBits"], + ); + + const kekBits = await crypto.subtle.deriveBits( + { + name: "PBKDF2", + salt, + iterations: 600000, + hash: "SHA-256", + }, + passwordKey, + 256, // 32 bytes + ); + + const aead = new Chacha20Poly1305(); + const ctx = aead.createEncryptionContext(kekBits); + + const nonce = globalThis.crypto.getRandomValues(new Uint8Array(12)); + const ciphertext = await ctx.seal( + nonce.buffer, + data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength), + new ArrayBuffer(0), + ); + + // Pack: salt(16) + nonce(12) + ciphertext + const result = new Uint8Array(16 + 12 + ciphertext.byteLength); result.set(salt, 0); result.set(nonce, 16); - result.set(ciphertext, 40); + result.set(new Uint8Array(ciphertext), 28); - return encodeBase64(result); + return result.toBase64(); } export async function decryptWithPassword( encryptedBase64: string, password: string, ): Promise { - const encrypted = decodeBase64(encryptedBase64); + const encrypted = Uint8Array.fromBase64(encryptedBase64); - if (encrypted.length < 40) throw new Error("Encrypted data too short"); + if (encrypted.length < 28) throw new Error("Encrypted data too short"); const salt = encrypted.slice(0, 16); - const nonce = encrypted.slice(16, 40); - const ciphertext = encrypted.slice(40); - - const kek = pbkdf2(sha256, password, salt, { c: 600000, dkLen: 32 }); - const chacha = xchacha20poly1305(kek, nonce); + const nonce = encrypted.slice(16, 28); + const ciphertext = encrypted.slice(28); + + const passwordKey = await crypto.subtle.importKey( + "raw", + new TextEncoder().encode(password), + "PBKDF2", + false, + ["deriveBits"], + ); + + const kekBits = await crypto.subtle.deriveBits( + { + name: "PBKDF2", + salt, + iterations: 600000, + hash: "SHA-256", + }, + passwordKey, + 256, + ); + + const aead = new Chacha20Poly1305(); + const ctx = aead.createEncryptionContext(kekBits); try { - const data = chacha.decrypt(ciphertext); - return encodeBase64(data); - } catch (e) { + const data = await ctx.open( + nonce.buffer, + ciphertext.buffer.slice( + ciphertext.byteOffset, + ciphertext.byteOffset + ciphertext.byteLength, + ), + new ArrayBuffer(0), + ); + return new Uint8Array(data).toBase64(); + } catch { throw new Error("Incorrect password or corrupted data"); } } diff --git a/src/lib/loro.ts b/src/lib/loro.svelte.ts similarity index 73% rename from src/lib/loro.ts rename to src/lib/loro.svelte.ts index 948ecec..7232135 100644 --- a/src/lib/loro.ts +++ b/src/lib/loro.svelte.ts @@ -1,11 +1,10 @@ import { decryptData, encryptData } from "$lib/crypto"; import { encodeBase64, decodeBase64 } from "@oslojs/encoding"; import { syncSchemaJson } from "$lib/remote/notes.schemas.ts"; -import { sync } from "$lib/remote/sync.remote.ts"; import { Schema } from "effect"; import { LoroDoc, LoroText } from "loro-crdt"; import { unawaited } from "./unawaited.ts"; -import { writable, type Writable } from "svelte/store"; +import { Temporal } from "temporal-polyfill"; export type ConnectionState = | "connected" @@ -25,7 +24,7 @@ export class LoroNoteManager { #onUpdate: (snapshot: string) => void | Promise; #eventSource: EventSource | null = null; #isSyncing = false; - connectionState: Writable = writable("disconnected"); + connectionState: ConnectionState = $state("disconnected"); #retryTimeout: NodeJS.Timeout | null = null; static getTextFromDoc(this: void, doc: LoroDoc): LoroText { @@ -43,6 +42,7 @@ export class LoroNoteManager { this.#noteId = noteId; this.#noteKey = noteKey; this.doc = new LoroDoc(); + this.doc.setRecordTimestamp(true); this.#text = LoroNoteManager.getTextFromDoc(this.doc); this.#onUpdate = onUpdate; @@ -104,7 +104,7 @@ export class LoroNoteManager { this.#retryTimeout = null; } this.#isSyncing = false; - this.connectionState.set("disconnected"); + this.connectionState = "disconnected"; } /** @@ -116,14 +116,14 @@ export class LoroNoteManager { startSync(): void { if (this.#isSyncing) return; this.#isSyncing = true; - this.connectionState.set("connecting"); + this.connectionState = "connecting"; // Use SSE endpoint this.#eventSource = new EventSource(`/client/doc/${this.#noteId}/events`); this.#eventSource.onopen = () => { console.log("[Loro] SSE Connected"); - this.connectionState.set("connected"); + this.connectionState = "connected"; // Clear any retry loop if we succeeded if (this.#retryTimeout) { clearTimeout(this.#retryTimeout); @@ -132,19 +132,13 @@ export class LoroNoteManager { }; this.#eventSource.onmessage = (event: MessageEvent): void => { - // console.debug("[Loro] Received SSE message:", event.data.slice(0, 100)); + console.debug("[Loro] Received SSE message:", event.data.slice(0, 100)); try { - const ops = JSON.parse(event.data); + const ops = Schema.decodeUnknownSync(syncSchemaJson)(event.data); if (!Array.isArray(ops)) return; for (const op of ops) { - // op.payload is encrypted blob (base64) - // Loro import expects Uint8Array? - // Wait, op.payload is base64 string provided by server. - // Loro import expects Uint8Array. - // Loro import expects Uint8Array. - // Support both 'payload' (DB/PubSub normalized) and 'encrypted_payload' (Raw API) - const base64 = op.payload || op.encrypted_payload; + const base64 = op.payload ?? op.encrypted_payload; if (!base64) { console.warn("[Loro] Received op without payload:", op); continue; @@ -160,26 +154,26 @@ export class LoroNoteManager { this.#eventSource.onopen = () => { console.log("[Loro] SSE connected"); - this.connectionState.set("connected"); + this.connectionState = "connected"; }; this.#eventSource.onerror = (error) => { console.error("SSE connection error:", error); // Browser will auto-reconnect usually, but let's be explicit about state if (this.#eventSource?.readyState === EventSource.CLOSED) { - this.connectionState.set("disconnected"); + this.connectionState = "disconnected"; this.#isSyncing = false; // Try to reconnect? this.#scheduleReconnect(); } else if (this.#eventSource?.readyState === EventSource.CONNECTING) { - this.connectionState.set("reconnecting"); + this.connectionState = "reconnecting"; } }; } #scheduleReconnect() { if (this.#retryTimeout) return; - this.connectionState.set("reconnecting"); + this.connectionState = "reconnecting"; console.log("[Loro] Scheduling reconnect in 3s..."); this.#retryTimeout = setTimeout(() => { this.#retryTimeout = null; @@ -193,20 +187,8 @@ export class LoroNoteManager { */ async #sendUpdate(update: Uint8Array) { try { - const opId = this.doc.peerId; // Wait, op ID needs to be unique? - // Loro update is a blob. We wrap it in an Op structure? - // Server expects: { op: { op_id, actor_id, lamport_ts, encrypted_payload, signature } } - // Client generates these? - // Loro `update` is a patch. We treat it as one "Op"? - // We need `actor_id` (peerId). - // `lamport_ts`: does Loro expose generic lamport? `doc.oplog.vv`? - // Or we just use client timestamp/counter? - // Loro updates are CRDT blobs. - // For federation Op Log, we wrap the blob. - const payload = encodeBase64(update); - const actorId = this.doc.peerIdStr; // string? - // Loro API check: `doc.peerIdStr` exists. + const actorId = this.doc.peerIdStr; // Mock Op structure const op = { @@ -261,30 +243,43 @@ export class LoroNoteManager { /** * Get version history with user attribution - * Returns an array of version snapshots + * Returns an array of version snapshots traversing the oplog */ - getHistory(): Array<{ - version: number; - timestamp: Date; - preview: string; - }> { - const history: Array<{ - version: number; - timestamp: Date; - preview: string; - }> = []; - - // Get current version - const currentVersion = this.doc.version(); + getHistory(): HistoryEntry[] { + const history: HistoryEntry[] = []; + + // Get all changes from the oplog + const changes = this.doc.getAllChanges(); const currentText = this.#text.toString(); - // For now, just return the current version - // In a full implementation, you'd traverse the oplog - history.push({ - version: currentVersion.get(this.doc.peerId) ?? 0, - timestamp: new Date(), - preview: currentText.slice(0, 100), - }); + // Traverse all changes from all peers + for (const [peerId, peerChanges] of changes) { + for (const change of peerChanges) { + history.push({ + version: change.lamport, + // Loro timestamps are in seconds, convert to milliseconds + timestamp: change.timestamp + ? Temporal.Instant.fromEpochMilliseconds(change.timestamp * 1000) + : Temporal.Instant.fromEpochMilliseconds(0), + preview: currentText.slice(0, 100), + peerId, + }); + } + } + + // Sort by lamport timestamp descending (most recent first) + history.sort((a, b) => b.version - a.version); + + // If no changes found, return current state as fallback + if (history.length === 0) { + const currentVersion = this.doc.version(); + history.push({ + version: currentVersion.get(this.doc.peerId) ?? 0, + timestamp: Temporal.Now.instant(), + preview: currentText.slice(0, 100), + peerId: this.doc.peerId.toString(), + }); + } return history; } @@ -296,3 +291,10 @@ export class LoroNoteManager { return this.doc.subscribe(callback); } } + +export interface HistoryEntry { + version: number; + timestamp: Temporal.Instant; + preview: string; + peerId: string; +} diff --git a/src/lib/noteId.ts b/src/lib/noteId.ts index d6a83be..fe30255 100644 --- a/src/lib/noteId.ts +++ b/src/lib/noteId.ts @@ -1,7 +1,6 @@ /** * Utilities for creating and parsing domain-prefixed note IDs * Format: {base64url(origin)}~{uuid} - * Example: bG9jYWxob3N0OjUxNzM~2472a017-f681-4fdd-bd46-80207dc3c5fb */ /** @@ -9,10 +8,11 @@ */ export function createNoteId(serverDomain: string): string { const uuid = crypto.randomUUID(); - const domainB64 = btoa(serverDomain) - .replace(/\+/g, "-") - .replace(/\//g, "_") - .replace(/=/g, ""); + + const domainB64 = Uint8Array.from(serverDomain, (c) => + c.charCodeAt(0), + ).toBase64({ alphabet: "base64url", omitPadding: true }); + return `${domainB64}~${uuid}`; } @@ -24,20 +24,18 @@ export function parseNoteId(id: string): { uuid: string; fullId: string; } { - if (!id.includes("~")) { - throw new Error(`Invalid portable ID format: ${id} (missing ~)`); - } + const sepIndex = id.indexOf("~"); + if (sepIndex === -1) throw new Error(`Invalid portable ID format: ${id}`); - const parts = id.split("~"); - const domainB64 = parts[0]; - const uuid = parts[1]; + const domainB64 = id.slice(0, sepIndex); + const uuid = id.slice(sepIndex + 1); - if (!domainB64 || !uuid) { - throw new Error(`Malformed portable ID: ${id}`); - } + if (!domainB64 || !uuid) throw new Error(`Malformed portable ID: ${id}`); - const padded = domainB64 + "=".repeat((4 - (domainB64.length % 4)) % 4); - const origin = atob(padded.replace(/-/g, "+").replace(/_/g, "/")); + // Decode base64url directly to string without intermediate array + const origin = new TextDecoder().decode( + Uint8Array.fromBase64(domainB64, { alphabet: "base64url" }), + ); return { origin, uuid, fullId: id }; } @@ -49,9 +47,7 @@ export function isLocalNote(noteId: string, currentDomain: string): boolean { try { const { origin } = parseNoteId(noteId); return !origin || origin === currentDomain; - } catch (e) { - // If it's malformed, it's definitely not a valid local note?? - // Or maybe we should assume false. + } catch { return false; } } diff --git a/src/lib/remote/accounts.remote.ts b/src/lib/remote/accounts.remote.ts index 0521fe5..f8245c1 100644 --- a/src/lib/remote/accounts.remote.ts +++ b/src/lib/remote/accounts.remote.ts @@ -84,10 +84,10 @@ export const login = form( auth.setSessionTokenCookie(cookies, sessionToken, session.expiresAt); // Redirect to the original destination if provided, otherwise go home - const redirectTo = url.searchParams.get("redirectTo") || "/"; + const redirectTo = url.searchParams.get("redirectTo") ?? "/"; // Validate redirectTo to prevent open redirect attacks const safeRedirect = redirectTo.startsWith("/") ? redirectTo : "/"; - throw redirect(302, safeRedirect); + redirect(302, safeRedirect); }, ); @@ -121,7 +121,7 @@ export const signup = form( } catch { error(500, "An error has occurred"); } - throw redirect(302, "/"); + redirect(302, "/"); }, ); diff --git a/src/lib/remote/federation.remote.ts b/src/lib/remote/federation.remote.ts index 3aa1d96..2cd83ce 100644 --- a/src/lib/remote/federation.remote.ts +++ b/src/lib/remote/federation.remote.ts @@ -1,12 +1,11 @@ import { command, getRequestEvent } from "$app/server"; import { db } from "$lib/server/db/index.ts"; import { documents, members } from "$lib/server/db/schema.ts"; -import { requireLogin } from "$lib/server/auth.ts"; import { error } from "@sveltejs/kit"; import { env } from "$env/dynamic/private"; import { parseNoteId } from "$lib/noteId.ts"; import { getServerIdentity } from "$lib/server/identity.ts"; -import { sign } from "$lib/crypto.ts"; +import { encryptKeyForUser, sign } from "$lib/crypto.ts"; import { eq } from "drizzle-orm"; import { Schema } from "effect"; @@ -25,11 +24,11 @@ export const joinFederatedNote = command( console.log(" originServer:", originServer); const event = getRequestEvent(); - const user = event?.locals.user; + const user = event.locals.user; try { - const currentDomain = env["SERVER_DOMAIN"] || "localhost:5173"; - const { uuid, origin } = parseNoteId(noteId); + const currentDomain = env["SERVER_DOMAIN"] ?? "localhost:5173"; + const { uuid } = parseNoteId(noteId); // Skip DB check if no user if (user) { @@ -39,17 +38,15 @@ export const joinFederatedNote = command( where: eq(documents.id, noteId), with: { members: { - where: (members) => eq(members.userId, user!.id), + where: (members) => eq(members.userId, user.id), }, }, }); - if (!existingDoc) { - existingDoc = await db.query.documents.findFirst({ - where: eq(documents.id, uuid), - with: { members: { where: (m) => eq(m.userId, user!.id) } }, - }); - } + existingDoc ??= await db.query.documents.findFirst({ + where: eq(documents.id, uuid), + with: { members: { where: (m) => eq(m.userId, user.id) } }, + }); const memberEntry = existingDoc?.members[0]; const hasKey = !!memberEntry?.encryptedKeyEnvelope; @@ -128,8 +125,7 @@ export const joinFederatedNote = command( if (joinData.rawKey) { console.log(" [Federation] Note is Open Public. Using Raw Key."); - if (user && user.publicKey) { - const { encryptKeyForUser } = await import("$lib/crypto"); + if (user?.publicKey) { encryptedKeyEnvelope = await encryptKeyForUser( joinData.rawKey, user.publicKey, @@ -145,9 +141,9 @@ export const joinFederatedNote = command( // Normal E2EE Envelope Logic let myEnvelope = joinData.envelopes?.find( (e: any) => - (user && e.user_id === userHandle) || - (user && e.user_id === user.id) || - (user && e.user_id === `@${user.username}`) || + (user && e.user_id === userHandle) ?? + (user && e.user_id === user.id) ?? + (user && e.user_id === `@${user.username}`) ?? (user && e.user_id === user.username), ); @@ -170,23 +166,22 @@ export const joinFederatedNote = command( } // Store document metadata locally - // ... (existing db insert logic) await db .insert(documents) .values({ id: noteId, hostServer: originServer, - ownerId: joinData.ownerId || user.id, - title: joinData.title || "Federated Note", - accessLevel: joinData.accessLevel || "private", + ownerId: joinData.ownerId ?? user.id, + title: joinData.title ?? "Federated Note", + accessLevel: joinData.accessLevel ?? "private", createdAt: new Date(), updatedAt: new Date(), }) .onConflictDoUpdate({ target: documents.id, set: { - title: joinData.title || "Federated Note", - accessLevel: joinData.accessLevel || "private", + title: joinData.title ?? "Federated Note", + accessLevel: joinData.accessLevel ?? "private", updatedAt: new Date(), }, }); @@ -198,7 +193,7 @@ export const joinFederatedNote = command( docId: noteId, userId: user.id, deviceId: "default", - role: joinData.role || "writer", + role: joinData.role ?? "writer", encryptedKeyEnvelope: encryptedKeyEnvelope, createdAt: new Date(), }) @@ -206,7 +201,7 @@ export const joinFederatedNote = command( target: [members.docId, members.userId, members.deviceId], set: { encryptedKeyEnvelope: encryptedKeyEnvelope, - role: joinData.role || "writer", + role: joinData.role ?? "writer", }, }); } diff --git a/src/lib/remote/notes.remote.ts b/src/lib/remote/notes.remote.ts index baa6ea0..959aa61 100644 --- a/src/lib/remote/notes.remote.ts +++ b/src/lib/remote/notes.remote.ts @@ -38,7 +38,7 @@ export const getNotes = query(async (): Promise => { order: n.order, createdAt: new Date(n.createdAt), updatedAt: new Date(n.updatedAt), - serverEncryptedKey: n.document?.serverEncryptedKey || null, + serverEncryptedKey: n.document.serverEncryptedKey ?? null, }) satisfies NoteOrFolder, ); @@ -56,21 +56,18 @@ export const getNotes = query(async (): Promise => { // Filter out any that might overlap with owned notes (though normally shouldn't) // And map to NoteOrFolder for (const m of memberships) { - if (!m.document) continue; - // Check if already in list (owned notes might be in members too?) if (notesList.some((n) => n.id === m.document.id)) continue; // Map to NoteOrFolder structure // We treat them as root-level notes for now (parentId: null) // We get encryptedKey from the member envelope - // We get encryptedKey from the member envelope if (m.encryptedKeyEnvelope) { - let documentKey = m.encryptedKeyEnvelope; + const documentKey = m.encryptedKeyEnvelope; notesList.push({ id: m.document.id, - title: m.document.title || "Shared Note", + title: m.document.title ?? "Shared Note", ownerId: m.document.ownerId, encryptedKey: documentKey, isFolder: false, // Default for shared docs @@ -97,7 +94,7 @@ export interface SharedNote { } export const getSharedNotes = query(async (): Promise => { - const { user } = requireLogin(); + requireLogin(); // Get documents from remote servers (not local) const sharedDocs = await db.query.documents.findMany({ @@ -106,7 +103,7 @@ export const getSharedNotes = query(async (): Promise => { return sharedDocs.map((doc) => ({ id: doc.id, - title: doc.title || "Untitled", + title: doc.title ?? "Untitled", hostServer: doc.hostServer, ownerId: doc.ownerId, accessLevel: doc.accessLevel, @@ -130,7 +127,7 @@ export const createNote = command( error(400, "Missing required fields"); } - const serverDomain = env["SERVER_DOMAIN"] || "localhost:5173"; + const serverDomain = env["SERVER_DOMAIN"] ?? "localhost:5173"; const id = createNoteId(serverDomain); // Dual-write to documents table to support federatedOps diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts index 837ffb0..70764d3 100644 --- a/src/lib/server/db/schema.ts +++ b/src/lib/server/db/schema.ts @@ -2,7 +2,6 @@ import { sqliteTable, text, integer, - blob, primaryKey, } from "drizzle-orm/sqlite-core"; @@ -70,9 +69,7 @@ export const members = sqliteTable( .notNull() .$defaultFn(() => new Date()), }, - (t) => ({ - pk: primaryKey({ columns: [t.docId, t.userId, t.deviceId] }), - }), + (t) => [primaryKey({ columns: [t.docId, t.userId, t.deviceId] })], ); export const federatedOps = sqliteTable("federated_ops", { diff --git a/src/lib/server/federation.ts b/src/lib/server/federation.ts index 3cacce0..191310a 100644 --- a/src/lib/server/federation.ts +++ b/src/lib/server/federation.ts @@ -2,16 +2,16 @@ * Federation utilities for cross-server communication */ -import { encryptKeyForDevice, decryptKeyForDevice } from "$lib/crypto"; +import { encryptKeyForDevice } from "$lib/crypto"; export interface RemoteUserIdentity { id: string; handle: string; publicKey: string | null; - devices: Array<{ + devices: { device_id: string; public_key: string; - }>; + }[]; } /** @@ -32,7 +32,7 @@ export async function fetchUserIdentity( if (cleanHandle.includes(":")) { // Federated handle: user:domain.com const parts = cleanHandle.split(":"); - username = parts[0] || ""; + username = parts[0] ?? ""; domain = parts.slice(1).join(":"); // Handle domain:port } else { // Local handle or just username @@ -54,12 +54,14 @@ export async function fetchUserIdentity( }); if (!res.ok) { - console.error(`Failed to fetch identity for ${handle}: ${res.status}`); + console.error( + `Failed to fetch identity for ${handle}: ${res.status.toFixed()}`, + ); return null; } - const data = await res.json(); - return data as RemoteUserIdentity; + const data = (await res.json()) as unknown as RemoteUserIdentity; + return data; } catch (err) { console.error(`Error fetching identity for ${handle}:`, err); return null; @@ -70,12 +72,12 @@ export async function fetchUserIdentity( * Encrypt a document key for a remote user * Uses the user's primary public key (or first device key if no user key) */ -export function encryptDocumentKeyForUser( +export async function encryptDocumentKeyForUser( documentKey: string, identity: RemoteUserIdentity, -): string | null { +): Promise { // Prefer user's main public key, fallback to first device - const publicKey = identity.publicKey || identity.devices[0]?.public_key; + const publicKey = identity.publicKey ?? identity.devices[0]?.public_key; if (!publicKey) { console.error(`No public key found for user ${identity.handle}`); @@ -83,7 +85,7 @@ export function encryptDocumentKeyForUser( } try { - return encryptKeyForDevice(documentKey, publicKey); + return await encryptKeyForDevice(documentKey, publicKey); } catch (err) { console.error(`Failed to encrypt key for ${identity.handle}:`, err); return null; @@ -98,17 +100,17 @@ export async function generateKeyEnvelopesForUsers( userHandles: string[], requestingDomain: string, ): Promise< - Array<{ + { user_id: string; encrypted_key: string; device_id: string; - }> + }[] > { - const envelopes: Array<{ + const envelopes: { user_id: string; encrypted_key: string; device_id: string; - }> = []; + }[] = []; for (const handle of userHandles) { const identity = await fetchUserIdentity(handle, requestingDomain); @@ -119,7 +121,10 @@ export async function generateKeyEnvelopesForUsers( // Generate envelope for user's main key if (identity.publicKey) { - const encryptedKey = encryptDocumentKeyForUser(documentKey, identity); + const encryptedKey = await encryptDocumentKeyForUser( + documentKey, + identity, + ); if (encryptedKey) { envelopes.push({ user_id: identity.handle, @@ -132,7 +137,7 @@ export async function generateKeyEnvelopesForUsers( // Optionally generate envelopes for each device for (const device of identity.devices) { try { - const encryptedKey = encryptKeyForDevice( + const encryptedKey = await encryptKeyForDevice( documentKey, device.public_key, ); diff --git a/src/lib/server/identity.ts b/src/lib/server/identity.ts index 837fb3c..deba7fd 100644 --- a/src/lib/server/identity.ts +++ b/src/lib/server/identity.ts @@ -1,34 +1,39 @@ +import { env } from "$env/dynamic/private"; import { generateSigningKeyPair, generateEncryptionKeyPair, sign, } from "$lib/crypto"; -import fs from "node:fs"; -import path from "node:path"; +import { Schema } from "effect"; +import fs from "node:fs/promises"; -const IDENTITY_FILE = - process.env["SERVER_IDENTITY_FILE"] || "server-identity.json"; +const IDENTITY_FILE = env["SERVER_IDENTITY_FILE"] ?? "server-identity.json"; -interface ServerIdentity { - publicKey: string; // Ed25519 (Signing) - privateKey: string; - encryptionPublicKey: string; // X25519 (Broker Encryption) - encryptionPrivateKey: string; - domain: string; -} +const ServerIdentitySchema = Schema.Struct({ + publicKey: Schema.String, // Ed25519 (Signing) + privateKey: Schema.String, + encryptionPublicKey: Schema.String, // X25519 (Broker Encryption) + encryptionPrivateKey: Schema.String, + domain: Schema.String, +}).pipe(Schema.mutable); + +type ServerIdentity = typeof ServerIdentitySchema.Type; + +const serverIdentityJson = Schema.parseJson(ServerIdentitySchema); // Singleton identity +// Gosh plz no... +// TODO: remove this abomination. let identity: ServerIdentity | null = null; export async function getServerIdentity(): Promise { if (identity) return identity; - // TODO: Determine domain dynamically or from config - const domain = process.env["SERVER_DOMAIN"] || "localhost:5173"; + const domain = env["SERVER_DOMAIN"] ?? "localhost:5173"; - if (fs.existsSync(IDENTITY_FILE)) { - const data = fs.readFileSync(IDENTITY_FILE, "utf-8"); - const loaded = JSON.parse(data); + try { + const data = await fs.readFile(IDENTITY_FILE, "utf-8"); + const loaded = Schema.decodeUnknownSync(serverIdentityJson)(data); // Backwards compatibility: Generate encryption keys if missing if (loaded.publicKey && !loaded.encryptionPublicKey) { @@ -36,14 +41,15 @@ export async function getServerIdentity(): Promise { const encParams = await generateEncryptionKeyPair(); loaded.encryptionPublicKey = encParams.publicKey; loaded.encryptionPrivateKey = encParams.privateKey; - fs.writeFileSync(IDENTITY_FILE, JSON.stringify(loaded, null, 2)); + await fs.writeFile(IDENTITY_FILE, JSON.stringify(loaded, null, 2)); } identity = loaded; - if (identity) { - identity.domain = domain; - return identity; - } + identity.domain = domain; + + return identity; + } catch { + console.warn("No existing server identity found, generating new one..."); } // Generate new @@ -59,7 +65,7 @@ export async function getServerIdentity(): Promise { domain, }; - fs.writeFileSync(IDENTITY_FILE, JSON.stringify(identity, null, 2)); + await fs.writeFile(IDENTITY_FILE, JSON.stringify(identity, null, 2)); return identity; } diff --git a/src/lib/server/pubsub.ts b/src/lib/server/pubsub.ts index 334e130..d666c80 100644 --- a/src/lib/server/pubsub.ts +++ b/src/lib/server/pubsub.ts @@ -7,11 +7,11 @@ class NotePubSub extends EventEmitter { this.setMaxListeners(1000); } - publish(docId: string, data: any) { + publish(docId: string, data: unknown) { this.emit(`op:${docId}`, data); } - subscribe(docId: string, callback: (data: any) => void) { + subscribe(docId: string, callback: (data: unknown) => void) { const eventName = `op:${docId}`; this.on(eventName, callback); return () => this.off(eventName, callback); diff --git a/src/lib/utils/time.ts b/src/lib/utils/time.ts new file mode 100644 index 0000000..d78932b --- /dev/null +++ b/src/lib/utils/time.ts @@ -0,0 +1,28 @@ +import { Temporal } from "temporal-polyfill"; + +const rtf = new Intl.RelativeTimeFormat(undefined, { numeric: "auto" }); + +export function formatRelativeTime(then: Temporal.Instant): string { + const now = Temporal.Now.zonedDateTimeISO(); + const thenZoned = then.toZonedDateTimeISO(now.timeZoneId); + + // If before today's midnight, show days ago using Intl + const daysDiff = thenZoned.toPlainDate().until(now.toPlainDate()).days; + if (daysDiff >= 1) { + return rtf.format(-daysDiff, "day"); + } + + // Otherwise show hours/minutes/seconds using Intl + const duration = now.toInstant().since(then, { + largestUnit: "hour", + smallestUnit: "second", + }); + + if (duration.hours >= 1) { + return rtf.format(-duration.hours, "hour"); + } + if (duration.minutes >= 1) { + return rtf.format(-duration.minutes, "minute"); + } + return rtf.format(-duration.seconds, "second"); +} diff --git a/src/routes/(auth)/login/+page.svelte b/src/routes/(auth)/login/+page.svelte index 5423b21..27776e9 100644 --- a/src/routes/(auth)/login/+page.svelte +++ b/src/routes/(auth)/login/+page.svelte @@ -16,7 +16,19 @@
{/each} -
+ { + // Cache password for the layout to use for decryption + if (_password) { + sessionStorage.setItem("notes_temp_password", _password); + } + + // TODO: do we even need this? + await submit(); + })} + >