From 39294a2f0cbd1668cd6dbb318998419111ae1363 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Thu, 17 Apr 2025 15:51:49 -0700 Subject: [PATCH] auth in progress --- package-lock.json | 1922 ++++++++++++++++++++++- package.json | 5 +- src/App.tsx | 145 +- src/components/NotificationsDisplay.tsx | 105 ++ src/components/auth/LinkDevice.tsx | 103 ++ src/components/auth/Loading.tsx | 18 + src/components/auth/Login.tsx | 188 +++ src/components/auth/Profile.tsx | 50 + src/components/auth/ProtectedRoute.tsx | 23 + src/components/auth/Register.tsx | 64 + src/context/AuthContext.tsx | 149 ++ src/context/FileSystemContext.tsx | 158 ++ src/context/NotificationContext.tsx | 111 ++ src/css/auth.css | 176 +++ src/css/loading.css | 32 + src/lib/auth/Login.tsx | 0 src/lib/auth/account.ts | 193 +++ src/lib/auth/authService.ts | 186 +++ src/lib/auth/backup.ts | 15 + src/lib/auth/crypto.ts | 197 +++ src/lib/auth/linking.ts | 24 + src/lib/auth/types.ts | 25 + src/lib/utils/asyncDebounce.ts | 188 +++ src/lib/utils/browser.ts | 187 +++ src/routes/Auth.tsx | 44 + src/ui/AuthDialog.tsx | 123 ++ src/ui/CustomToolbar.tsx | 113 ++ 27 files changed, 4514 insertions(+), 30 deletions(-) create mode 100644 src/components/NotificationsDisplay.tsx create mode 100644 src/components/auth/LinkDevice.tsx create mode 100644 src/components/auth/Loading.tsx create mode 100644 src/components/auth/Login.tsx create mode 100644 src/components/auth/Profile.tsx create mode 100644 src/components/auth/ProtectedRoute.tsx create mode 100644 src/components/auth/Register.tsx create mode 100644 src/context/AuthContext.tsx create mode 100644 src/context/FileSystemContext.tsx create mode 100644 src/context/NotificationContext.tsx create mode 100644 src/css/auth.css create mode 100644 src/css/loading.css create mode 100644 src/lib/auth/Login.tsx create mode 100644 src/lib/auth/account.ts create mode 100644 src/lib/auth/authService.ts create mode 100644 src/lib/auth/backup.ts create mode 100644 src/lib/auth/crypto.ts create mode 100644 src/lib/auth/linking.ts create mode 100644 src/lib/auth/types.ts create mode 100644 src/lib/utils/asyncDebounce.ts create mode 100644 src/lib/utils/browser.ts create mode 100644 src/routes/Auth.tsx create mode 100644 src/ui/AuthDialog.tsx diff --git a/package-lock.json b/package-lock.json index 3334d41..f148043 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@anthropic-ai/sdk": "^0.33.1", "@daily-co/daily-js": "^0.60.0", "@daily-co/daily-react": "^0.20.0", + "@oddjs/odd": "^0.37.2", "@tldraw/assets": "^3.6.0", "@tldraw/sync": "^3.6.0", "@tldraw/sync-core": "^3.6.0", @@ -31,6 +32,7 @@ "jspdf": "^2.5.2", "lodash.throttle": "^4.1.1", "marked": "^15.0.4", + "one-webcrypto": "^1.0.3", "openai": "^4.79.3", "rbush": "^4.0.1", "react": "^18.2.0", @@ -39,7 +41,8 @@ "react-router-dom": "^7.0.2", "recoil": "^0.7.7", "tldraw": "^3.6.0", - "vercel": "^39.1.1" + "vercel": "^39.1.1", + "webnative": "^0.36.3" }, "devDependencies": { "@cloudflare/types": "^6.0.0", @@ -471,6 +474,21 @@ "license": "MIT", "optional": true }, + "node_modules/@chainsafe/is-ip": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@chainsafe/is-ip/-/is-ip-2.1.0.tgz", + "integrity": "sha512-KIjt+6IfysQ4GCv66xihEitBjvhU/bixbbbFxdJ1sqCp4uJ0wuZiYBPhksZoy4lfaF0k9cwNzY5upEW/VWdw3w==", + "license": "MIT" + }, + "node_modules/@chainsafe/netmask": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@chainsafe/netmask/-/netmask-2.0.0.tgz", + "integrity": "sha512-I3Z+6SWUoaljh3TBzCnCxjlUyN8tA+NAk5L6m9IxvCf1BENQTePzPMis97CoN/iMW1St3WN+AWCCRp+TTBRiDg==", + "license": "MIT", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1" + } + }, "node_modules/@cloudflare/intl-types": { "version": "1.5.6", "resolved": "https://registry.npmjs.org/@cloudflare/intl-types/-/intl-types-1.5.6.tgz", @@ -1618,6 +1636,53 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@ipld/dag-cbor": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-8.0.1.tgz", + "integrity": "sha512-mHRuzgGXNk0Y5W7nNQdN37qJiig1Kdgf92icBVFRUNtBc9Ezl5DIdWfiGWBucHBrhqPBncxoH3As9cHPIRozxA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "cborg": "^1.6.0", + "multiformats": "^11.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@ipld/dag-cbor/node_modules/multiformats": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-11.0.2.tgz", + "integrity": "sha512-b5mYMkOkARIuVZCpvijFj9a6m5wMVLC7cf/jIPd5D/ARDOfLC5+IFkbgDXQgcU2goIsTD/O9NY4DI/Mt4OGvlg==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@ipld/dag-pb": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-3.0.2.tgz", + "integrity": "sha512-ge+llKU/CNc6rX5ZcUhCrPXJjKjN1DsolDOJ99zOsousGOhepoIgvT01iAP8s7QN9QFciOE+a1jHdccs+CyhBA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^11.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@ipld/dag-pb/node_modules/multiformats": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-11.0.2.tgz", + "integrity": "sha512-b5mYMkOkARIuVZCpvijFj9a6m5wMVLC7cf/jIPd5D/ARDOfLC5+IFkbgDXQgcU2goIsTD/O9NY4DI/Mt4OGvlg==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", @@ -1669,6 +1734,371 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "license": "MIT" + }, + "node_modules/@libp2p/interface-connection": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@libp2p/interface-connection/-/interface-connection-4.0.0.tgz", + "integrity": "sha512-6xx/NmEc84HX7QmsjSC3hHredQYjHv4Dkf4G27adAPf+qN+vnPxmQ7gaTnk243a0++DOFTbZ2gKX/15G2B6SRg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface-peer-id": "^2.0.0", + "@libp2p/interfaces": "^3.0.0", + "@multiformats/multiaddr": "^12.0.0", + "it-stream-types": "^1.0.4", + "uint8arraylist": "^2.1.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-connection/node_modules/@libp2p/interface-peer-id": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@libp2p/interface-peer-id/-/interface-peer-id-2.0.2.tgz", + "integrity": "sha512-9pZp9zhTDoVwzRmp0Wtxw0Yfa//Yc0GqBCJi3EznBDE6HGIAVvppR91wSh2knt/0eYg0AQj7Y35VSesUTzMCUg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^11.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-connection/node_modules/@multiformats/multiaddr": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.4.0.tgz", + "integrity": "sha512-FL7yBTLijJ5JkO044BGb2msf+uJLrwpD6jD6TkXlbjA9N12+18HT40jvd4o5vL4LOJMc86dPX6tGtk/uI9kYKg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "@chainsafe/netmask": "^2.0.0", + "@multiformats/dns": "^1.0.3", + "multiformats": "^13.0.0", + "uint8-varint": "^2.0.1", + "uint8arrays": "^5.0.0" + } + }, + "node_modules/@libp2p/interface-connection/node_modules/@multiformats/multiaddr/node_modules/multiformats": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.3.2.tgz", + "integrity": "sha512-qbB0CQDt3QKfiAzZ5ZYjLFOs+zW43vA4uyM8g27PeEuXZybUOFyjrVdP93HPBHMoglibwfkdVwbzfUq8qGcH6g==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@libp2p/interface-connection/node_modules/multiformats": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-11.0.2.tgz", + "integrity": "sha512-b5mYMkOkARIuVZCpvijFj9a6m5wMVLC7cf/jIPd5D/ARDOfLC5+IFkbgDXQgcU2goIsTD/O9NY4DI/Mt4OGvlg==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-connection/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, + "node_modules/@libp2p/interface-connection/node_modules/uint8arrays/node_modules/multiformats": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.3.2.tgz", + "integrity": "sha512-qbB0CQDt3QKfiAzZ5ZYjLFOs+zW43vA4uyM8g27PeEuXZybUOFyjrVdP93HPBHMoglibwfkdVwbzfUq8qGcH6g==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@libp2p/interface-keychain": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@libp2p/interface-keychain/-/interface-keychain-1.0.8.tgz", + "integrity": "sha512-JqI7mMthIafP8cGhhsmIs/M0Ey+ivHLcpzqbVVzMFiFVi1dC03R7EHlalcaPn8yaLSvlmI0MqjC8lJYuvlFjfw==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^10.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-keys": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@libp2p/interface-keys/-/interface-keys-1.0.8.tgz", + "integrity": "sha512-CJ1SlrwuoHMquhEEWS77E+4vv7hwB7XORkqzGQrPQmA9MRdIEZRS64bA4JqCLUDa4ltH0l+U1vp0oZHLT67NEA==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-peer-id": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@libp2p/interface-peer-id/-/interface-peer-id-1.1.2.tgz", + "integrity": "sha512-S5iyVzG2EUgxm4NLe8W4ya9kpKuGfHs7Wbbos0wOUB4GXsbIKgOOxIr4yf+xGFgtEBaoximvlLkpob6dn8VFgA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^10.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-peer-info": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@libp2p/interface-peer-info/-/interface-peer-info-1.0.10.tgz", + "integrity": "sha512-HQlo8NwQjMyamCHJrnILEZz+YwEOXCB2sIIw3slIrhVUYeYlTaia1R6d9umaAeLHa255Zmdm4qGH8rJLRqhCcg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface-peer-id": "^2.0.0", + "@multiformats/multiaddr": "^12.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-peer-info/node_modules/@libp2p/interface-peer-id": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@libp2p/interface-peer-id/-/interface-peer-id-2.0.2.tgz", + "integrity": "sha512-9pZp9zhTDoVwzRmp0Wtxw0Yfa//Yc0GqBCJi3EznBDE6HGIAVvppR91wSh2knt/0eYg0AQj7Y35VSesUTzMCUg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^11.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-peer-info/node_modules/@multiformats/multiaddr": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.4.0.tgz", + "integrity": "sha512-FL7yBTLijJ5JkO044BGb2msf+uJLrwpD6jD6TkXlbjA9N12+18HT40jvd4o5vL4LOJMc86dPX6tGtk/uI9kYKg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "@chainsafe/netmask": "^2.0.0", + "@multiformats/dns": "^1.0.3", + "multiformats": "^13.0.0", + "uint8-varint": "^2.0.1", + "uint8arrays": "^5.0.0" + } + }, + "node_modules/@libp2p/interface-peer-info/node_modules/@multiformats/multiaddr/node_modules/multiformats": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.3.2.tgz", + "integrity": "sha512-qbB0CQDt3QKfiAzZ5ZYjLFOs+zW43vA4uyM8g27PeEuXZybUOFyjrVdP93HPBHMoglibwfkdVwbzfUq8qGcH6g==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@libp2p/interface-peer-info/node_modules/multiformats": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-11.0.2.tgz", + "integrity": "sha512-b5mYMkOkARIuVZCpvijFj9a6m5wMVLC7cf/jIPd5D/ARDOfLC5+IFkbgDXQgcU2goIsTD/O9NY4DI/Mt4OGvlg==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-peer-info/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, + "node_modules/@libp2p/interface-peer-info/node_modules/uint8arrays/node_modules/multiformats": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.3.2.tgz", + "integrity": "sha512-qbB0CQDt3QKfiAzZ5ZYjLFOs+zW43vA4uyM8g27PeEuXZybUOFyjrVdP93HPBHMoglibwfkdVwbzfUq8qGcH6g==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@libp2p/interface-pubsub": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@libp2p/interface-pubsub/-/interface-pubsub-3.0.7.tgz", + "integrity": "sha512-+c74EVUBTfw2sx1GE/z/IjsYO6dhur+ukF0knAppeZsRQ1Kgg6K5R3eECtT28fC6dBWLjFpAvW/7QGfiDAL4RA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface-connection": "^4.0.0", + "@libp2p/interface-peer-id": "^2.0.0", + "@libp2p/interfaces": "^3.0.0", + "it-pushable": "^3.0.0", + "uint8arraylist": "^2.1.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-pubsub/node_modules/@libp2p/interface-peer-id": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@libp2p/interface-peer-id/-/interface-peer-id-2.0.2.tgz", + "integrity": "sha512-9pZp9zhTDoVwzRmp0Wtxw0Yfa//Yc0GqBCJi3EznBDE6HGIAVvppR91wSh2knt/0eYg0AQj7Y35VSesUTzMCUg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^11.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interface-pubsub/node_modules/multiformats": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-11.0.2.tgz", + "integrity": "sha512-b5mYMkOkARIuVZCpvijFj9a6m5wMVLC7cf/jIPd5D/ARDOfLC5+IFkbgDXQgcU2goIsTD/O9NY4DI/Mt4OGvlg==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/interfaces": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@libp2p/interfaces/-/interfaces-3.3.2.tgz", + "integrity": "sha512-p/M7plbrxLzuQchvNwww1Was7ZeGE2NaOFulMaZBYIihU8z3fhaV+a033OqnC/0NTX/yhfdNOG7znhYq3XoR/g==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/logger": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-2.1.1.tgz", + "integrity": "sha512-2UbzDPctg3cPupF6jrv6abQnAUTrbLybNOj0rmmrdGm1cN2HJ1o/hBu0sXuq4KF9P1h/eVRn1HIRbVIEKnEJrA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface-peer-id": "^2.0.2", + "@multiformats/multiaddr": "^12.1.3", + "debug": "^4.3.4", + "interface-datastore": "^8.2.0", + "multiformats": "^11.0.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/logger/node_modules/@libp2p/interface-peer-id": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@libp2p/interface-peer-id/-/interface-peer-id-2.0.2.tgz", + "integrity": "sha512-9pZp9zhTDoVwzRmp0Wtxw0Yfa//Yc0GqBCJi3EznBDE6HGIAVvppR91wSh2knt/0eYg0AQj7Y35VSesUTzMCUg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^11.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/logger/node_modules/@multiformats/multiaddr": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-12.4.0.tgz", + "integrity": "sha512-FL7yBTLijJ5JkO044BGb2msf+uJLrwpD6jD6TkXlbjA9N12+18HT40jvd4o5vL4LOJMc86dPX6tGtk/uI9kYKg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "@chainsafe/netmask": "^2.0.0", + "@multiformats/dns": "^1.0.3", + "multiformats": "^13.0.0", + "uint8-varint": "^2.0.1", + "uint8arrays": "^5.0.0" + } + }, + "node_modules/@libp2p/logger/node_modules/@multiformats/multiaddr/node_modules/multiformats": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.3.2.tgz", + "integrity": "sha512-qbB0CQDt3QKfiAzZ5ZYjLFOs+zW43vA4uyM8g27PeEuXZybUOFyjrVdP93HPBHMoglibwfkdVwbzfUq8qGcH6g==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@libp2p/logger/node_modules/interface-datastore": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-8.3.1.tgz", + "integrity": "sha512-3r0ETmHIi6HmvM5sc09QQiCD3gUfwtEM/AAChOyAd/UAKT69uk8LXfTSUBufbUIO/dU65Vj8nb9O6QjwW8vDSQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "interface-store": "^6.0.0", + "uint8arrays": "^5.1.0" + } + }, + "node_modules/@libp2p/logger/node_modules/interface-store": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-6.0.2.tgz", + "integrity": "sha512-KSFCXtBlNoG0hzwNa0RmhHtrdhzexp+S+UY2s0rWTBJyfdEIgn6i6Zl9otVqrcFYbYrneBT7hbmHQ8gE0C3umA==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@libp2p/logger/node_modules/multiformats": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-11.0.2.tgz", + "integrity": "sha512-b5mYMkOkARIuVZCpvijFj9a6m5wMVLC7cf/jIPd5D/ARDOfLC5+IFkbgDXQgcU2goIsTD/O9NY4DI/Mt4OGvlg==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/logger/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, + "node_modules/@libp2p/logger/node_modules/uint8arrays/node_modules/multiformats": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.3.2.tgz", + "integrity": "sha512-qbB0CQDt3QKfiAzZ5ZYjLFOs+zW43vA4uyM8g27PeEuXZybUOFyjrVdP93HPBHMoglibwfkdVwbzfUq8qGcH6g==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@libp2p/peer-id": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-1.1.18.tgz", + "integrity": "sha512-Zh3gzbrQZKDMLpoJAJB8gdGtyYFSBKV0dU5vflQ18/7MJDJmjsgKO+sJTYi72yN5sWREs1eGKMhxLo+N1ust5w==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/interface-peer-id": "^1.0.0", + "err-code": "^3.0.1", + "multiformats": "^10.0.0", + "uint8arrays": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@libp2p/peer-id/node_modules/uint8arrays": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-4.0.10.tgz", + "integrity": "sha512-AnJNUGGDJAgFw/eWu/Xb9zrVKEGlwJJCaeInlf3BkecE/zcTobk5YXYIPNQJO1q5Hh1QZrQQHf0JvcHqz2hqoA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^12.0.1" + } + }, + "node_modules/@libp2p/peer-id/node_modules/uint8arrays/node_modules/multiformats": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.1.3.tgz", + "integrity": "sha512-eajQ/ZH7qXZQR2AgtfpmSMizQzmyYVmCql7pdhldPuYQi4atACekbJaQplk6dWyIi10jCaFnd6pqvcEFXjbaJw==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/@mapbox/node-pre-gyp": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", @@ -1803,6 +2233,105 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" }, + "node_modules/@multiformats/dns": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.6.tgz", + "integrity": "sha512-nt/5UqjMPtyvkG9BQYdJ4GfLK3nMqGpFZOzf4hAmIa0sJh2LlS9YKXZ4FgwBDsaHvzZqR/rUFIywIc7pkHNNuw==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@types/dns-packet": "^5.6.5", + "buffer": "^6.0.3", + "dns-packet": "^5.6.1", + "hashlru": "^2.3.0", + "p-queue": "^8.0.1", + "progress-events": "^1.0.0", + "uint8arrays": "^5.0.2" + } + }, + "node_modules/@multiformats/dns/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/@multiformats/dns/node_modules/multiformats": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.3.2.tgz", + "integrity": "sha512-qbB0CQDt3QKfiAzZ5ZYjLFOs+zW43vA4uyM8g27PeEuXZybUOFyjrVdP93HPBHMoglibwfkdVwbzfUq8qGcH6g==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/@multiformats/dns/node_modules/p-queue": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.0.tgz", + "integrity": "sha512-mxLDbbGIBEXTJL0zEx8JIylaj3xQ7Z/7eEVjcF9fJX4DBiH9oqe+oahYnlKKxm0Ci9TlWTyhSHgygxMxjIB2jw==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@multiformats/dns/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, + "node_modules/@multiformats/multiaddr": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-11.6.1.tgz", + "integrity": "sha512-doST0+aB7/3dGK9+U5y3mtF3jq85KGbke1QiH0KE1F5mGQ9y56mFebTeu2D9FNOm+OT6UHb8Ss8vbSnpGjeLNw==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "dns-over-http-resolver": "^2.1.0", + "err-code": "^3.0.1", + "multiformats": "^11.0.0", + "uint8arrays": "^4.0.2", + "varint": "^6.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@multiformats/multiaddr/node_modules/multiformats": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-11.0.2.tgz", + "integrity": "sha512-b5mYMkOkARIuVZCpvijFj9a6m5wMVLC7cf/jIPd5D/ARDOfLC5+IFkbgDXQgcU2goIsTD/O9NY4DI/Mt4OGvlg==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@multiformats/multiaddr/node_modules/uint8arrays": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-4.0.10.tgz", + "integrity": "sha512-AnJNUGGDJAgFw/eWu/Xb9zrVKEGlwJJCaeInlf3BkecE/zcTobk5YXYIPNQJO1q5Hh1QZrQQHf0JvcHqz2hqoA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^12.0.1" + } + }, + "node_modules/@multiformats/multiaddr/node_modules/uint8arrays/node_modules/multiformats": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.1.3.tgz", + "integrity": "sha512-eajQ/ZH7qXZQR2AgtfpmSMizQzmyYVmCql7pdhldPuYQi4atACekbJaQplk6dWyIi10jCaFnd6pqvcEFXjbaJw==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1838,6 +2367,38 @@ "node": ">= 8" } }, + "node_modules/@oddjs/odd": { + "version": "0.37.2", + "resolved": "https://registry.npmjs.org/@oddjs/odd/-/odd-0.37.2.tgz", + "integrity": "sha512-ot5cpfHCfq8r9AXAxNACgmSSjLjEm1PJj2AOGrmOFiG0jYgD530h9pZc7G0keNIQJNk6YbZxCOddk0XfiwU01A==", + "license": "Apache-2.0", + "dependencies": { + "@ipld/dag-cbor": "^8.0.0", + "@ipld/dag-pb": "^3.0.1", + "@libp2p/interface-keys": "^1.0.4", + "@libp2p/peer-id": "^1.1.17", + "@multiformats/multiaddr": "^11.1.0", + "blockstore-core": "^2.0.2", + "blockstore-datastore-adapter": "^4.0.0", + "datastore-core": "^8.0.2", + "datastore-level": "^9.0.4", + "events": "^3.3.0", + "fission-bloom-filters": "1.7.1", + "ipfs-core-types": "0.13.0", + "ipfs-repo": "^16.0.0", + "keystore-idb": "^0.15.5", + "localforage": "^1.10.0", + "multiformats": "^10.0.2", + "one-webcrypto": "^1.0.3", + "throttle-debounce": "^3.0.1", + "tweetnacl": "^1.0.3", + "uint8arrays": "^3.0.0", + "wnfs": "0.1.7" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@opentelemetry/api": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", @@ -1847,6 +2408,70 @@ "node": ">=8.0.0" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, "node_modules/@radix-ui/number": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", @@ -3309,6 +3934,15 @@ "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==", "license": "MIT" }, + "node_modules/@types/dns-packet": { + "version": "5.6.5", + "resolved": "https://registry.npmjs.org/@types/dns-packet/-/dns-packet-5.6.5.tgz", + "integrity": "sha512-qXOC7XLOEe43ehtWJCMnQXvgcIpv6rPmQ1jXT98Ad8A3TB1Ue50jsCbSSSyuazScEuZ/Q026vHbrOTVkmwA+7Q==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/dompurify": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-2.4.0.tgz", @@ -4076,6 +4710,24 @@ "node": ">=6.5" } }, + "node_modules/abstract-level": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-1.0.4.tgz", + "integrity": "sha512-eUP/6pbXBkMbXFdx4IH2fVgvB7M0JvR7/lIL33zcs0IBcwjdzSSl31TOJsaCzmKSSDF9h8QYSOJux4Nd4YJqFg==", + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "catering": "^2.1.0", + "is-buffer": "^2.0.5", + "level-supports": "^4.0.0", + "level-transcoder": "^1.0.1", + "module-error": "^1.0.1", + "queue-microtask": "^1.2.3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/acorn": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", @@ -4341,6 +4993,26 @@ "node": ">= 0.6.0" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/bcp-47-match": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", @@ -4367,6 +5039,55 @@ "dev": true, "license": "MIT" }, + "node_modules/blockstore-core": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/blockstore-core/-/blockstore-core-2.0.2.tgz", + "integrity": "sha512-ALry3rBp2pTEi4F/usjCJGRluAKYFWI9Np7uE0pZHfDeScMJSj/fDkHEWvY80tPYu4kj03sLKRDGJlZH+V7VzQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "err-code": "^3.0.1", + "interface-blockstore": "^3.0.0", + "interface-store": "^3.0.0", + "it-all": "^1.0.4", + "it-drain": "^1.0.4", + "it-filter": "^1.0.2", + "it-take": "^1.0.1", + "multiformats": "^10.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/blockstore-datastore-adapter": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/blockstore-datastore-adapter/-/blockstore-datastore-adapter-4.0.0.tgz", + "integrity": "sha512-vzy2lgLb7PQ0qopuZk6B+syRULdUt9w/ffNl7EXcvGZLS5+VoUmh4Agdp1OVuoaMEfXoEqIvCaPXi/v3829vBg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "blockstore-core": "^2.0.0", + "err-code": "^3.0.1", + "interface-blockstore": "^3.0.0", + "interface-datastore": "^7.0.0", + "it-drain": "^2.0.0", + "it-pushable": "^3.1.0", + "multiformats": "^10.0.1" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/blockstore-datastore-adapter/node_modules/it-drain": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/it-drain/-/it-drain-2.0.1.tgz", + "integrity": "sha512-ESuHV6MLUNxuSy0vGZpKhSRjW0ixczN1FhbVy7eGJHjX6U2qiiXTyMvDc0z/w+nifOOwPyI5DT9Rc3o9IaGqEQ==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -4401,6 +5122,18 @@ "node": ">=8" } }, + "node_modules/browser-level": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browser-level/-/browser-level-1.0.1.tgz", + "integrity": "sha512-XECYKJ+Dbzw0lbydyQuJzwNXtOpbMSq737qxJN11sIRTErOMShvDpbzTlgju7orJKvx4epULolZAuJGLzCmWRQ==", + "license": "MIT", + "dependencies": { + "abstract-level": "^1.0.2", + "catering": "^2.1.1", + "module-error": "^1.0.2", + "run-parallel-limit": "^1.1.0" + } + }, "node_modules/browser-process-hrtime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", @@ -4452,6 +5185,30 @@ "node": ">= 0.4.0" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -4524,6 +5281,24 @@ "license": "MIT", "optional": true }, + "node_modules/catering": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", + "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cborg": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/cborg/-/cborg-1.10.2.tgz", + "integrity": "sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug==", + "license": "Apache-2.0", + "bin": { + "cborg": "cli.js" + } + }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", @@ -4649,6 +5424,23 @@ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "license": "MIT" }, + "node_modules/classic-level": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/classic-level/-/classic-level-1.4.1.tgz", + "integrity": "sha512-qGx/KJl3bvtOHrGau2WklEZuXhS3zme+jf+fsu6Ej7W7IP/C49v7KNlWIsT1jZu0YnfzSIYDGcEWpCa1wKGWXQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "abstract-level": "^1.0.2", + "catering": "^2.1.0", + "module-error": "^1.0.1", + "napi-macros": "^2.2.2", + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/classnames": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", @@ -4945,6 +5737,12 @@ "dev": true, "license": "MIT" }, + "node_modules/cuint": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", + "integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==", + "license": "MIT" + }, "node_modules/cytoscape": { "version": "3.30.4", "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.30.4.tgz", @@ -5475,6 +6273,129 @@ "node": ">=12" } }, + "node_modules/datastore-core": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/datastore-core/-/datastore-core-8.0.4.tgz", + "integrity": "sha512-oBA6a024NFXJOTu+w9nLAimfy4wCYUhdE/5XQGtdKt1BmCVtPYW10GORvVT3pdZBcse6k/mVcBl+hjkXIlm65A==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@libp2p/logger": "^2.0.0", + "err-code": "^3.0.1", + "interface-datastore": "^7.0.0", + "it-all": "^2.0.0", + "it-drain": "^2.0.0", + "it-filter": "^2.0.0", + "it-map": "^2.0.0", + "it-merge": "^2.0.0", + "it-pipe": "^2.0.3", + "it-pushable": "^3.0.0", + "it-take": "^2.0.0", + "uint8arrays": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/datastore-core/node_modules/it-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/it-all/-/it-all-2.0.1.tgz", + "integrity": "sha512-9UuJcCRZsboz+HBQTNOau80Dw+ryGaHYFP/cPYzFBJBFcfDathMYnhHk4t52en9+fcyDGPTdLB+lFc1wzQIroA==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/datastore-core/node_modules/it-drain": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/it-drain/-/it-drain-2.0.1.tgz", + "integrity": "sha512-ESuHV6MLUNxuSy0vGZpKhSRjW0ixczN1FhbVy7eGJHjX6U2qiiXTyMvDc0z/w+nifOOwPyI5DT9Rc3o9IaGqEQ==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/datastore-core/node_modules/it-filter": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/it-filter/-/it-filter-2.0.2.tgz", + "integrity": "sha512-gocw1F3siqupegsOzZ78rAc9C+sYlQbI2af/TmzgdrR613MyEJHbvfwBf12XRekGG907kqXSOGKPlxzJa6XV1Q==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/datastore-core/node_modules/it-take": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/it-take/-/it-take-2.0.1.tgz", + "integrity": "sha512-DL7kpZNjuoeSTnB9dMAJ0Z3m2T29LRRAU+HIgkiQM+1jH3m8l9e/1xpWs8JHTlbKivbqSFrQMTc8KVcaQNmsaA==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/datastore-core/node_modules/multiformats": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.1.3.tgz", + "integrity": "sha512-eajQ/ZH7qXZQR2AgtfpmSMizQzmyYVmCql7pdhldPuYQi4atACekbJaQplk6dWyIi10jCaFnd6pqvcEFXjbaJw==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/datastore-core/node_modules/uint8arrays": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-4.0.10.tgz", + "integrity": "sha512-AnJNUGGDJAgFw/eWu/Xb9zrVKEGlwJJCaeInlf3BkecE/zcTobk5YXYIPNQJO1q5Hh1QZrQQHf0JvcHqz2hqoA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^12.0.1" + } + }, + "node_modules/datastore-level": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/datastore-level/-/datastore-level-9.0.4.tgz", + "integrity": "sha512-HKf2tVVWywdidI+94z0B5NLx4J94wTLCT1tYXXxJ58MK/Y5rdX8WVRp9XmZaODS70uxpNC8/UrvWr0iTBZwkUA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "abstract-level": "^1.0.3", + "datastore-core": "^8.0.1", + "interface-datastore": "^7.0.0", + "it-filter": "^2.0.0", + "it-map": "^2.0.0", + "it-sort": "^2.0.0", + "it-take": "^2.0.0", + "level": "^8.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/datastore-level/node_modules/it-filter": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/it-filter/-/it-filter-2.0.2.tgz", + "integrity": "sha512-gocw1F3siqupegsOzZ78rAc9C+sYlQbI2af/TmzgdrR613MyEJHbvfwBf12XRekGG907kqXSOGKPlxzJa6XV1Q==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/datastore-level/node_modules/it-take": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/it-take/-/it-take-2.0.1.tgz", + "integrity": "sha512-DL7kpZNjuoeSTnB9dMAJ0Z3m2T29LRRAU+HIgkiQM+1jH3m8l9e/1xpWs8JHTlbKivbqSFrQMTc8KVcaQNmsaA==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", @@ -5624,6 +6545,30 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/dns-over-http-resolver": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/dns-over-http-resolver/-/dns-over-http-resolver-2.1.3.tgz", + "integrity": "sha512-zjRYFhq+CsxPAouQWzOsxNMvEN+SHisjzhX8EMxd2Y0EG3thvn6wXQgMJLnTDImkhe4jhLbOQpXtL10nALBOSA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "debug": "^4.3.1", + "native-fetch": "^4.0.2", + "receptacle": "^1.3.2", + "undici": "^5.12.0" + } + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/domexception": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", @@ -5723,6 +6668,12 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", + "license": "MIT" + }, "node_modules/es-module-lexer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", @@ -6359,6 +7310,28 @@ "node": ">=8" } }, + "node_modules/fission-bloom-filters": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/fission-bloom-filters/-/fission-bloom-filters-1.7.1.tgz", + "integrity": "sha512-AAVWxwqgSDK+/3Tn2kx+a9j/ND/pyVNVZgn/rL5pfQaX7w0qfP81PlLCNKhM4XKOhcg1kFXNcoWkQKg3MyyULw==", + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "is-buffer": "^2.0.4", + "lodash": "^4.17.15", + "lodash.eq": "^4.0.0", + "lodash.indexof": "^4.0.5", + "reflect-metadata": "^0.1.13", + "seedrandom": "^3.0.5", + "xxhashjs": "^0.2.2" + } + }, + "node_modules/fnv1a": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fnv1a/-/fnv1a-1.1.1.tgz", + "integrity": "sha512-S2HviLR9UyNbt8R+vU6YeQtL8RliPwez9DQEVba5MAvN3Od+RSgKUSL2+qveOMt3owIeBukKoRu2enoOck5uag==", + "license": "MIT" + }, "node_modules/form-data": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", @@ -6648,6 +7621,12 @@ "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", "license": "ISC" }, + "node_modules/hashlru": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/hashlru/-/hashlru-2.3.0.tgz", + "integrity": "sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A==", + "license": "MIT" + }, "node_modules/hast-util-from-html": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", @@ -7072,6 +8051,26 @@ "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", "license": "ISC" }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", @@ -7101,6 +8100,82 @@ "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", "license": "MIT" }, + "node_modules/interface-blockstore": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/interface-blockstore/-/interface-blockstore-3.0.2.tgz", + "integrity": "sha512-lJXCyu3CwidOvNjkJARwCmoxl/HNX/mrfMxtyq5e/pVZA1SrlTj5lvb4LBYbfoynzewGUPcUU4DEUaXoLKliHQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "interface-store": "^3.0.0", + "multiformats": "^10.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/interface-datastore": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-7.0.4.tgz", + "integrity": "sha512-Q8LZS/jfFFHz6XyZazLTAc078SSCoa27ZPBOfobWdpDiFO7FqPA2yskitUJIhaCgxNK8C+/lMBUTBNfVIDvLiw==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "interface-store": "^3.0.0", + "nanoid": "^4.0.0", + "uint8arrays": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/interface-datastore/node_modules/multiformats": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.1.3.tgz", + "integrity": "sha512-eajQ/ZH7qXZQR2AgtfpmSMizQzmyYVmCql7pdhldPuYQi4atACekbJaQplk6dWyIi10jCaFnd6pqvcEFXjbaJw==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/interface-datastore/node_modules/nanoid": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/interface-datastore/node_modules/uint8arrays": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-4.0.10.tgz", + "integrity": "sha512-AnJNUGGDJAgFw/eWu/Xb9zrVKEGlwJJCaeInlf3BkecE/zcTobk5YXYIPNQJO1q5Hh1QZrQQHf0JvcHqz2hqoA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^12.0.1" + } + }, + "node_modules/interface-store": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-3.0.4.tgz", + "integrity": "sha512-OjHUuGXbH4eXSBx1TF1tTySvjLldPLzRSYYXJwrEQI+XfH5JWYZofr0gVMV4F8XTwC+4V7jomDYkvGRmDSRKqQ==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -7121,6 +8196,174 @@ "fp-ts": "^2.5.0" } }, + "node_modules/ipfs-core-types": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/ipfs-core-types/-/ipfs-core-types-0.13.0.tgz", + "integrity": "sha512-IIKS9v2D5KIqReZMbyuCStI4FRyIbRA9nD3fji1KgKJPiic1N3iGe2jL4hy4Y3FQ30VbheWJ9jAROwMyvqxYNA==", + "deprecated": "js-IPFS has been deprecated in favour of Helia - please see https://github.com/ipfs/js-ipfs/issues/4336 for details", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@ipld/dag-pb": "^3.0.0", + "@libp2p/interface-keychain": "^1.0.3", + "@libp2p/interface-peer-id": "^1.0.4", + "@libp2p/interface-peer-info": "^1.0.2", + "@libp2p/interface-pubsub": "^3.0.0", + "@multiformats/multiaddr": "^11.0.0", + "@types/node": "^18.0.0", + "interface-datastore": "^7.0.0", + "ipfs-unixfs": "^8.0.0", + "multiformats": "^10.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/ipfs-core-types/node_modules/@types/node": { + "version": "18.19.86", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.86.tgz", + "integrity": "sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/ipfs-repo": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/ipfs-repo/-/ipfs-repo-16.0.0.tgz", + "integrity": "sha512-CYlHO3MK1CNfuCkRyLxXB9pKj2nx4yomH92DilhwDW+Et4rQ/8279RgmEh5nFNf7BgvIvYPE+3hVErGbVytS5Q==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@ipld/dag-pb": "^3.0.0", + "bytes": "^3.1.0", + "cborg": "^1.3.4", + "datastore-core": "^8.0.1", + "debug": "^4.1.0", + "err-code": "^3.0.1", + "interface-blockstore": "^3.0.0", + "interface-datastore": "^7.0.0", + "ipfs-repo-migrations": "^14.0.0", + "it-drain": "^2.0.0", + "it-filter": "^2.0.0", + "it-first": "^2.0.0", + "it-map": "^2.0.0", + "it-merge": "^2.0.0", + "it-parallel-batch": "^2.0.0", + "it-pipe": "^2.0.4", + "it-pushable": "^3.1.0", + "just-safe-get": "^4.1.1", + "just-safe-set": "^4.1.1", + "merge-options": "^3.0.4", + "mortice": "^3.0.0", + "multiformats": "^10.0.1", + "p-queue": "^7.3.0", + "proper-lockfile": "^4.0.0", + "quick-lru": "^6.1.1", + "sort-keys": "^5.0.0", + "uint8arrays": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/ipfs-repo-migrations": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/ipfs-repo-migrations/-/ipfs-repo-migrations-14.0.1.tgz", + "integrity": "sha512-wE22g05hzxegCWMhNj7deagCLsKPcNf8KmK1QN4WMob0kuZ4kDxCg7fusM68tGrOnhE+Ll/AVHseFlzmoU/ZbQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@ipld/dag-pb": "^3.0.0", + "@multiformats/multiaddr": "^11.0.0", + "cborg": "^1.3.4", + "datastore-core": "^8.0.1", + "debug": "^4.1.0", + "fnv1a": "^1.0.1", + "interface-blockstore": "^3.0.0", + "interface-datastore": "^7.0.0", + "it-length": "^2.0.0", + "multiformats": "^10.0.1", + "protobufjs": "^7.0.0", + "uint8arrays": "^4.0.2", + "varint": "^6.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/ipfs-repo-migrations/node_modules/uint8arrays": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-4.0.10.tgz", + "integrity": "sha512-AnJNUGGDJAgFw/eWu/Xb9zrVKEGlwJJCaeInlf3BkecE/zcTobk5YXYIPNQJO1q5Hh1QZrQQHf0JvcHqz2hqoA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^12.0.1" + } + }, + "node_modules/ipfs-repo-migrations/node_modules/uint8arrays/node_modules/multiformats": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.1.3.tgz", + "integrity": "sha512-eajQ/ZH7qXZQR2AgtfpmSMizQzmyYVmCql7pdhldPuYQi4atACekbJaQplk6dWyIi10jCaFnd6pqvcEFXjbaJw==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/ipfs-repo/node_modules/it-drain": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/it-drain/-/it-drain-2.0.1.tgz", + "integrity": "sha512-ESuHV6MLUNxuSy0vGZpKhSRjW0ixczN1FhbVy7eGJHjX6U2qiiXTyMvDc0z/w+nifOOwPyI5DT9Rc3o9IaGqEQ==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/ipfs-repo/node_modules/it-filter": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/it-filter/-/it-filter-2.0.2.tgz", + "integrity": "sha512-gocw1F3siqupegsOzZ78rAc9C+sYlQbI2af/TmzgdrR613MyEJHbvfwBf12XRekGG907kqXSOGKPlxzJa6XV1Q==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/ipfs-repo/node_modules/uint8arrays": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-4.0.10.tgz", + "integrity": "sha512-AnJNUGGDJAgFw/eWu/Xb9zrVKEGlwJJCaeInlf3BkecE/zcTobk5YXYIPNQJO1q5Hh1QZrQQHf0JvcHqz2hqoA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^12.0.1" + } + }, + "node_modules/ipfs-repo/node_modules/uint8arrays/node_modules/multiformats": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.1.3.tgz", + "integrity": "sha512-eajQ/ZH7qXZQR2AgtfpmSMizQzmyYVmCql7pdhldPuYQi4atACekbJaQplk6dWyIi10jCaFnd6pqvcEFXjbaJw==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/ipfs-unixfs": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-8.0.0.tgz", + "integrity": "sha512-PAHtfyjiFs2PZBbeft5QRyXpVOvZ2zsGqID+zVRla7fjC1zRTqJkrGY9h6dF03ldGv/mSmFlNZh479qPC6aZKg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "err-code": "^3.0.1", + "protobufjs": "^7.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", @@ -7153,6 +8396,29 @@ "license": "MIT", "optional": true }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/is-decimal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", @@ -7272,6 +8538,153 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/it-all": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/it-all/-/it-all-1.0.6.tgz", + "integrity": "sha512-3cmCc6Heqe3uWi3CVM/k51fa/XbMFpQVzFoDsV0IZNHSQDyAXl3c4MjHkFX5kF3922OGj7Myv1nSEUgRtcuM1A==", + "license": "ISC" + }, + "node_modules/it-batch": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/it-batch/-/it-batch-2.0.1.tgz", + "integrity": "sha512-2gWFuPzamh9Dh3pW+OKjc7UwJ41W4Eu2AinVAfXDMfrC5gXfm3b1TF+1UzsygBUgKBugnxnGP+/fFRyn+9y1mQ==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-drain": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/it-drain/-/it-drain-1.0.5.tgz", + "integrity": "sha512-r/GjkiW1bZswC04TNmUnLxa6uovme7KKwPhc+cb1hHU65E3AByypHH6Pm91WHuvqfFsm+9ws0kPtDBV3/8vmIg==", + "license": "ISC" + }, + "node_modules/it-filter": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/it-filter/-/it-filter-1.0.3.tgz", + "integrity": "sha512-EI3HpzUrKjTH01miLHWmhNWy3Xpbx4OXMXltgrNprL5lDpF3giVpHIouFpr5l+evXw6aOfxhnt01BIB+4VQA+w==", + "license": "ISC" + }, + "node_modules/it-first": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/it-first/-/it-first-2.0.1.tgz", + "integrity": "sha512-noC1oEQcWZZMUwq7VWxHNLML43dM+5bviZpfmkxkXlvBe60z7AFRqpZSga9uQBo792jKv9otnn1IjA4zwgNARw==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-length": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/it-length/-/it-length-2.0.1.tgz", + "integrity": "sha512-BynaPOK4UwcQX2Z+kqsQygXUNW9NZswfTnscfP7MLhFvVhRYbYJv8XH+09/Qwf8ktk65QdsGoVnDmQUCUGCyvg==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/it-map/-/it-map-2.0.1.tgz", + "integrity": "sha512-a2GcYDHiAh/eSU628xlvB56LA98luXZnniH2GlD0IdBzf15shEq9rBeb0Rg3o1SWtNILUAwqmQxEXcewGCdvmQ==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-merge": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/it-merge/-/it-merge-2.0.1.tgz", + "integrity": "sha512-ItoBy3dPlNKnhjHR8e7nfabfZzH4Jy2OMPvayYH3XHy4YNqSVKmWTIxhz7KX4UMBsLChlIJZ+5j6csJgrYGQtw==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "it-pushable": "^3.1.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-parallel-batch": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/it-parallel-batch/-/it-parallel-batch-2.0.1.tgz", + "integrity": "sha512-tXh567/JfDGJ90Zi//H9HkL7kY27ARp0jf2vu2jUI6PUVBWfsoT+gC4eT41/b4+wkJXSGgT8ZHnivAOlMfcNjA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "it-batch": "^2.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-pipe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/it-pipe/-/it-pipe-2.0.5.tgz", + "integrity": "sha512-y85nW1N6zoiTnkidr2EAyC+ZVzc7Mwt2p+xt2a2ooG1ThFakSpNw1Kxm+7F13Aivru96brJhjQVRQNU+w0yozw==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "it-merge": "^2.0.0", + "it-pushable": "^3.1.0", + "it-stream-types": "^1.0.3" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-pushable": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/it-pushable/-/it-pushable-3.2.3.tgz", + "integrity": "sha512-gzYnXYK8Y5t5b/BnJUr7glfQLO4U5vyb05gPx/TyTw+4Bv1zM9gFk4YsOrnulWefMewlphCjKkakFvj1y99Tcg==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "p-defer": "^4.0.0" + } + }, + "node_modules/it-sort": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/it-sort/-/it-sort-2.0.1.tgz", + "integrity": "sha512-9f4jKOTHfxc/FJpg/wwuQ+j+88i+sfNGKsu2HukAKymm71/XDnBFtOAOzaimko3YIhmn/ERwnfEKrsYLykxw9A==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "it-all": "^2.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-sort/node_modules/it-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/it-all/-/it-all-2.0.1.tgz", + "integrity": "sha512-9UuJcCRZsboz+HBQTNOau80Dw+ryGaHYFP/cPYzFBJBFcfDathMYnhHk4t52en9+fcyDGPTdLB+lFc1wzQIroA==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-stream-types": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/it-stream-types/-/it-stream-types-1.0.5.tgz", + "integrity": "sha512-I88Ka1nHgfX62e5mi5LLL+oueqz7Ltg0bUdtsUKDe9SoUqbQPf2Mp5kxDTe9pNhHQGs4pvYPAINwuZ1HAt42TA==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-take": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/it-take/-/it-take-1.0.2.tgz", + "integrity": "sha512-u7I6qhhxH7pSevcYNaMECtkvZW365ARqAIt9K+xjdK1B2WUDEjQSfETkOCT8bxFq/59LqrN3cMLUtTgmDBaygw==", + "license": "ISC" + }, "node_modules/itty-router": { "version": "5.0.18", "resolved": "https://registry.npmjs.org/itty-router/-/itty-router-5.0.18.tgz", @@ -7469,6 +8882,32 @@ "html2canvas": "^1.0.0-rc.5" } }, + "node_modules/just-safe-get": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/just-safe-get/-/just-safe-get-4.2.0.tgz", + "integrity": "sha512-+tS4Bvgr/FnmYxOGbwziJ8I2BFk+cP1gQHm6rm7zo61w1SbxBwWGEq/Ryy9Gb6bvnloPq6pz7Bmm4a0rjTNlXA==", + "license": "MIT" + }, + "node_modules/just-safe-set": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-safe-set/-/just-safe-set-4.2.1.tgz", + "integrity": "sha512-La5CP41Ycv52+E4g7w1sRV8XXk7Sp8a/TwWQAYQKn6RsQz1FD4Z/rDRRmqV3wJznS1MDF3YxK7BCudX1J8FxLg==", + "license": "MIT" + }, + "node_modules/keystore-idb": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/keystore-idb/-/keystore-idb-0.15.5.tgz", + "integrity": "sha512-7bcUAnY5iD0+N75odQVTCs8mhXBW+yLt9/HH8+VUrl44FGllpAhu7q3/w9QpNMHxLQv3OXs1fsA042CAviN79Q==", + "license": "Apache-2.0", + "dependencies": { + "localforage": "^1.10.0", + "one-webcrypto": "^1.0.3", + "uint8arrays": "^3.0.0" + }, + "engines": { + "node": ">=10.21.0" + } + }, "node_modules/khroma": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", @@ -7491,6 +8930,46 @@ "license": "MIT", "optional": true }, + "node_modules/level": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/level/-/level-8.0.1.tgz", + "integrity": "sha512-oPBGkheysuw7DmzFQYyFe8NAia5jFLAgEnkgWnK3OXAuJr8qFT+xBQIwokAZPME2bhPFzS8hlYcL16m8UZrtwQ==", + "license": "MIT", + "dependencies": { + "abstract-level": "^1.0.4", + "browser-level": "^1.0.1", + "classic-level": "^1.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/level" + } + }, + "node_modules/level-supports": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-4.0.1.tgz", + "integrity": "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/level-transcoder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/level-transcoder/-/level-transcoder-1.0.1.tgz", + "integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==", + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "module-error": "^1.0.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/lie": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", @@ -7513,7 +8992,6 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, "license": "MIT" }, "node_modules/lodash-es": { @@ -7523,6 +9001,18 @@ "license": "MIT", "optional": true }, + "node_modules/lodash.eq": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lodash.eq/-/lodash.eq-4.0.0.tgz", + "integrity": "sha512-vbrJpXL6kQNG6TkInxX12DZRfuYVllSxhwYqjYB78g2zF3UI15nFO/0AgmZnZRnaQ38sZtjCiVjGr2rnKt4v0g==", + "license": "MIT" + }, + "node_modules/lodash.indexof": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/lodash.indexof/-/lodash.indexof-4.0.5.tgz", + "integrity": "sha512-t9wLWMQsawdVmf6/IcAgVGqAJkNzYVcn4BHYZKTPW//l7N5Oq7Bq138BaVk19agcsPZePcidSgTTw4NqS1nUAw==", + "license": "MIT" + }, "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -7541,6 +9031,12 @@ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "license": "MIT" }, + "node_modules/long": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.1.tgz", + "integrity": "sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng==", + "license": "Apache-2.0" + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -7917,6 +9413,27 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "license": "MIT", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge-options/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -8701,6 +10218,48 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/module-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/module-error/-/module-error-1.0.2.tgz", + "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/mortice": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/mortice/-/mortice-3.0.6.tgz", + "integrity": "sha512-xUjsTQreX8rO3pHuGYDZ3PY/sEiONIzqzjLeog5akdY4bz9TlDDuvYlU8fm+6qnm4rnpa6AFxLhsfSBThLijdA==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "observable-webworkers": "^2.0.1", + "p-queue": "^8.0.1", + "p-timeout": "^6.0.0" + } + }, + "node_modules/mortice/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/mortice/node_modules/p-queue": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.0.tgz", + "integrity": "sha512-mxLDbbGIBEXTJL0zEx8JIylaj3xQ7Z/7eEVjcF9fJX4DBiH9oqe+oahYnlKKxm0Ci9TlWTyhSHgygxMxjIB2jw==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -8716,6 +10275,16 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/multiformats": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-10.0.3.tgz", + "integrity": "sha512-K2yGSmstS/oEmYiEIieHb53jJCaqp4ERPDQAYrm5sV3UUrVDZeshJQCK6GHAKyIGufU1vAcbS0PdAAZmC7Tzcw==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/mustache": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", @@ -8753,6 +10322,21 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-macros": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.2.2.tgz", + "integrity": "sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==", + "license": "MIT" + }, + "node_modules/native-fetch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/native-fetch/-/native-fetch-4.0.2.tgz", + "integrity": "sha512-4QcVlKFtv2EYVS5MBgsGX5+NWKtbDbIECdUXDBGDMAZXq3Jkv9zf+y8iS7Ub8fEdga3GpYeazp9gauNqXHJOCg==", + "license": "MIT", + "peerDependencies": { + "undici": "*" + } + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -8906,6 +10490,16 @@ "node": ">=0.10.0" } }, + "node_modules/observable-webworkers": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/observable-webworkers/-/observable-webworkers-2.0.1.tgz", + "integrity": "sha512-JI1vB0u3pZjoQKOK1ROWzp0ygxSi7Yb0iR+7UNsw4/Zn4cQ0P3R7XL38zac/Dy2tEA7Lg88/wIJTjF8vYXZ0uw==", + "license": "Apache-2.0 OR MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/ohash": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", @@ -8922,6 +10516,12 @@ "wrappy": "1" } }, + "node_modules/one-webcrypto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/one-webcrypto/-/one-webcrypto-1.0.3.tgz", + "integrity": "sha512-fu9ywBVBPx0gS9K0etIROTiCkvI5S1TDjFsYFb3rC1ewFxeOqsbzq7aIMBHsYfrTHBcGXJaONXXjTl8B01cW1Q==", + "license": "MIT" + }, "node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -8985,6 +10585,18 @@ "node": ">= 6.0" } }, + "node_modules/p-defer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-4.0.1.tgz", + "integrity": "sha512-Mr5KC5efvAK5VUptYEIopP1bakB85k2IWXaRC0rsh1uwn1L6M0LVml8OIQ4Gudg4oyZakf7FmeRLkMMtZW1i5A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-finally": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", @@ -8994,6 +10606,52 @@ "node": ">=8" } }, + "node_modules/p-queue": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-7.4.1.tgz", + "integrity": "sha512-vRpMXmIkYF2/1hLBKisKeVYJZ8S2tZ0zEAmIJgdVKP2nq0nh4qCdf8bgw+ZgKrkh71AOCaqzwbJJk1WtdcF3VA==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^5.0.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/p-queue/node_modules/p-timeout": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.1.0.tgz", + "integrity": "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse-entities": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", @@ -9179,12 +10837,35 @@ "dev": true, "license": "Unlicense" }, + "node_modules/progress-events": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/progress-events/-/progress-events-1.0.1.tgz", + "integrity": "sha512-MOzLIwhpt64KIVN64h1MwdKWiyKFNc/S6BoYKPIVUHFg0/eIEyBulhWCgn678v/4c0ri3FdGuzXymNCv02MUIw==", + "license": "Apache-2.0 OR MIT" + }, "node_modules/promisepipe": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/promisepipe/-/promisepipe-3.0.0.tgz", "integrity": "sha512-V6TbZDJ/ZswevgkDNpGt/YqNCiZP9ASfgU+p83uJE6NrGtvSGoOcHLiDCqkMs2+yg7F5qHdLV8d0aS8O26G/KA==", "license": "MIT" }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, "node_modules/property-information": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.0.0.tgz", @@ -9195,6 +10876,30 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/protobufjs": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.0.tgz", + "integrity": "sha512-Z2E/kOY1QjoMlCytmexzYfDm/w5fKAiRwpSzGtdnXW1zC88Z2yXazHHrOtwCzn+7wSxyE8PYM4rvVcMphF9sOA==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", @@ -9252,6 +10957,18 @@ ], "license": "MIT" }, + "node_modules/quick-lru": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.2.tgz", + "integrity": "sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/quickselect": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", @@ -9518,6 +11235,15 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/receptacle": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/receptacle/-/receptacle-1.3.2.tgz", + "integrity": "sha512-HrsFvqZZheusncQRiEE7GatOAETrARKV/lnfYicIm8lbvp/JQOdADOfhjBd2DajvoszEyxSM6RlAAIZgEoeu/A==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/recoil": { "version": "0.7.7", "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", @@ -9538,6 +11264,12 @@ } } }, + "node_modules/reflect-metadata": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", + "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==", + "license": "Apache-2.0" + }, "node_modules/refractor": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/refractor/-/refractor-4.9.0.tgz", @@ -9890,6 +11622,15 @@ "node": ">=8" } }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -10042,6 +11783,29 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/run-parallel-limit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz", + "integrity": "sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/rw": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", @@ -10125,6 +11889,12 @@ "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", "license": "BSD-3-Clause" }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==", + "license": "MIT" + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -10264,6 +12034,21 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/sort-keys": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-5.1.0.tgz", + "integrity": "sha512-aSbHV0DaBcr7u0PVHXzM6NbZNAtrr9sF6+Qfs9UUVG7Ll3jQ6hHi8F/xqIIcn2rvIVbr0v/2zyjSdwSV47AgLQ==", + "license": "MIT", + "dependencies": { + "is-plain-obj": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -10557,6 +12342,15 @@ "utrie": "^1.0.2" } }, + "node_modules/throttle-debounce": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz", + "integrity": "sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/throttleit": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz", @@ -10780,6 +12574,12 @@ "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", "license": "ISC" }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "license": "Unlicense" + }, "node_modules/typescript": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", @@ -10807,6 +12607,70 @@ "integrity": "sha512-R8375j0qwXyIu/7R0tjdF06/sElHqbmdmWC9M2qQHpEVbvE4I5+38KJI7LUUmQMp7NVq4tKHiBMkT0NFM453Ig==", "license": "MIT" }, + "node_modules/uint8-varint": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/uint8-varint/-/uint8-varint-2.0.4.tgz", + "integrity": "sha512-FwpTa7ZGA/f/EssWAb5/YV6pHgVF1fViKdW8cWaEarjB8t7NyofSWBdOTyFPaGuUG4gx3v1O3PQ8etsiOs3lcw==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8arraylist": "^2.0.0", + "uint8arrays": "^5.0.0" + } + }, + "node_modules/uint8-varint/node_modules/multiformats": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.3.2.tgz", + "integrity": "sha512-qbB0CQDt3QKfiAzZ5ZYjLFOs+zW43vA4uyM8g27PeEuXZybUOFyjrVdP93HPBHMoglibwfkdVwbzfUq8qGcH6g==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/uint8-varint/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, + "node_modules/uint8arraylist": { + "version": "2.4.8", + "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-2.4.8.tgz", + "integrity": "sha512-vc1PlGOzglLF0eae1M8mLRTBivsvrGsdmJ5RbK3e+QRvRLOZfZhQROTwH/OfyF3+ZVUg9/8hE8bmKP2CvP9quQ==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "uint8arrays": "^5.0.1" + } + }, + "node_modules/uint8arraylist/node_modules/multiformats": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.3.2.tgz", + "integrity": "sha512-qbB0CQDt3QKfiAzZ5ZYjLFOs+zW43vA4uyM8g27PeEuXZybUOFyjrVdP93HPBHMoglibwfkdVwbzfUq8qGcH6g==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/uint8arraylist/node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, + "node_modules/uint8arrays": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", + "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", + "license": "MIT", + "dependencies": { + "multiformats": "^9.4.2" + } + }, + "node_modules/uint8arrays/node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "license": "(Apache-2.0 AND MIT)" + }, "node_modules/undici": { "version": "5.28.4", "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", @@ -11092,6 +12956,12 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "license": "MIT" }, + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", + "license": "MIT" + }, "node_modules/vercel": { "version": "39.2.2", "resolved": "https://registry.npmjs.org/vercel/-/vercel-39.2.2.tgz", @@ -11337,6 +13207,39 @@ "node": ">=12" } }, + "node_modules/webnative": { + "version": "0.36.3", + "resolved": "https://registry.npmjs.org/webnative/-/webnative-0.36.3.tgz", + "integrity": "sha512-MucN6ydnyY5E8GczuARAWXSOn3+yjXKSLNTIPeJhcFmZpxPBDRfpZ0SpKJjKWtVLNiEaUQibeiKsIYDfij/wIQ==", + "deprecated": "webnative has been renamed to @oddjs/odd. Upgrade to @oddjs/odd.", + "license": "Apache-2.0", + "dependencies": { + "@ipld/dag-cbor": "^8.0.0", + "@ipld/dag-pb": "^3.0.1", + "@libp2p/interface-keys": "^1.0.4", + "@libp2p/peer-id": "^1.1.17", + "@multiformats/multiaddr": "^11.1.0", + "blockstore-core": "^2.0.2", + "blockstore-datastore-adapter": "^4.0.0", + "datastore-core": "^8.0.2", + "datastore-level": "^9.0.4", + "events": "^3.3.0", + "fission-bloom-filters": "1.7.1", + "ipfs-core-types": "0.13.0", + "ipfs-repo": "^16.0.0", + "keystore-idb": "^0.15.5", + "localforage": "^1.10.0", + "multiformats": "^10.0.2", + "one-webcrypto": "^1.0.3", + "throttle-debounce": "^3.0.1", + "tweetnacl": "^1.0.3", + "uint8arrays": "^3.0.0", + "wnfs": "0.1.7" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/whatwg-encoding": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", @@ -11395,6 +13298,12 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, + "node_modules/wnfs": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/wnfs/-/wnfs-0.1.7.tgz", + "integrity": "sha512-WTadILZSNX7Ti+jy1QgqGtWp0pLHvPAG+ERsNWge2DuR8P8x+U/CM9QjYqJb7wqBkbSoboZgeBspetybIzNQgw==", + "license": "Apache-2.0" + }, "node_modules/workerd": { "version": "1.20250310.0", "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250310.0.tgz", @@ -11957,6 +13866,15 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "license": "MIT" }, + "node_modules/xxhashjs": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", + "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", + "license": "MIT", + "dependencies": { + "cuint": "^0.2.2" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index b6801a8..fc92fab 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@anthropic-ai/sdk": "^0.33.1", "@daily-co/daily-js": "^0.60.0", "@daily-co/daily-react": "^0.20.0", + "@oddjs/odd": "^0.37.2", "@tldraw/assets": "^3.6.0", "@tldraw/sync": "^3.6.0", "@tldraw/sync-core": "^3.6.0", @@ -38,6 +39,7 @@ "jspdf": "^2.5.2", "lodash.throttle": "^4.1.1", "marked": "^15.0.4", + "one-webcrypto": "^1.0.3", "openai": "^4.79.3", "rbush": "^4.0.1", "react": "^18.2.0", @@ -46,7 +48,8 @@ "react-router-dom": "^7.0.2", "recoil": "^0.7.7", "tldraw": "^3.6.0", - "vercel": "^39.1.1" + "vercel": "^39.1.1", + "webnative": "^0.36.3" }, "devDependencies": { "@cloudflare/types": "^6.0.0", diff --git a/src/App.tsx b/src/App.tsx index f3a05b3..dea708d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,33 +1,124 @@ -import { inject } from "@vercel/analytics" -import "tldraw/tldraw.css" -import "@/css/style.css" -import { Default } from "@/routes/Default" -import { BrowserRouter, Route, Routes } from "react-router-dom" -import { Contact } from "@/routes/Contact" -import { Board } from "./routes/Board" -import { Inbox } from "./routes/Inbox" -import { createRoot } from "react-dom/client" -import { DailyProvider } from "@daily-co/daily-react" -import Daily from "@daily-co/daily-js" +import { inject } from "@vercel/analytics"; +import "tldraw/tldraw.css"; +import "@/css/style.css"; +import "@/styles/auth.css"; // Import auth styles +import { Default } from "@/routes/Default"; +import { BrowserRouter, Route, Routes, Navigate } from "react-router-dom"; +import { Contact } from "@/routes/Contact"; +import { Board } from "./routes/Board"; +import { Inbox } from "./routes/Inbox"; +import { createRoot } from "react-dom/client"; +import { DailyProvider } from "@daily-co/daily-react"; +import Daily from "@daily-co/daily-js"; +import { useState, useEffect } from 'react'; -inject() +// Import React Context providers +import { AuthProvider, useAuth } from './context/AuthContext'; +import { FileSystemProvider } from './context/FileSystemContext'; +import { NotificationProvider } from './context/NotificationContext'; +import NotificationsDisplay from './components/NotificationsDisplay'; -const callObject = Daily.createCallObject() +// Import auth components +import Login from './components/auth/Login'; + +inject(); + +const callObject = Daily.createCallObject(); + +/** + * Protected Route component + * Redirects to login if user is not authenticated + */ +const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { + const { session } = useAuth(); + const [isInitialized, setIsInitialized] = useState(false); + + // Wait for authentication to initialize before rendering + useEffect(() => { + if (!session.loading) { + setIsInitialized(true); + } + }, [session.loading]); + + if (!isInitialized) { + return
Loading...
; + } + + // Redirect to login if not authenticated + if (!session.authed) { + return ; + } + + // Render the protected content + return <>{children}; +}; + +/** + * Auth page - renders login/register component + */ +const AuthPage = () => { + const { session } = useAuth(); + + // Redirect to home if already authenticated + if (session.authed) { + return ; + } -function App() { return ( - - - - } /> - } /> - } /> - } /> - - - - ) -} +
+ window.location.href = '/'} /> +
+ ); +}; -createRoot(document.getElementById("root")!).render() +/** + * Main App with context providers + */ +const AppWithProviders = () => { + return ( + + + + + + {/* Display notifications */} + + + + {/* Auth routes */} + } /> + + {/* Protected routes */} + + + + } /> + + + + } /> + + + + } /> + + + + } /> + + + + + + + ); +}; +// Initialize the app +createRoot(document.getElementById("root")!).render(); + +export default AppWithProviders; \ No newline at end of file diff --git a/src/components/NotificationsDisplay.tsx b/src/components/NotificationsDisplay.tsx new file mode 100644 index 0000000..7c2fb4a --- /dev/null +++ b/src/components/NotificationsDisplay.tsx @@ -0,0 +1,105 @@ +import React, { useEffect, useState } from 'react'; +import { useNotifications, Notification } from '../context/NotificationContext'; + +/** + * Component to display a single notification + */ +const NotificationItem: React.FC<{ + notification: Notification; + onClose: (id: string) => void; +}> = ({ notification, onClose }) => { + const [isExiting, setIsExiting] = useState(false); + const exitDuration = 300; // ms for exit animation + + // Set up automatic dismissal based on notification timeout + useEffect(() => { + if (notification.timeout > 0) { + const timer = setTimeout(() => { + setIsExiting(true); + + // Wait for exit animation before removing + setTimeout(() => { + onClose(notification.id); + }, exitDuration); + }, notification.timeout); + + return () => clearTimeout(timer); + } + }, [notification, onClose]); + + // Handle manual close + const handleClose = () => { + setIsExiting(true); + + // Wait for exit animation before removing + setTimeout(() => { + onClose(notification.id); + }, exitDuration); + }; + + // Determine icon based on notification type + const getIcon = () => { + switch (notification.type) { + case 'success': + return '✓'; + case 'error': + return '✕'; + case 'warning': + return '⚠'; + case 'info': + default: + return 'ℹ'; + } + }; + + return ( +
+
+ {getIcon()} +
+ +
+ {notification.msg} +
+ + +
+ ); +}; + +/** + * Component that displays all active notifications + */ +const NotificationsDisplay: React.FC = () => { + const { notifications, removeNotification } = useNotifications(); + + // Don't render anything if there are no notifications + if (notifications.length === 0) { + return null; + } + + return ( +
+ {notifications.map((notification) => ( + + ))} +
+ ); +}; + +export default NotificationsDisplay; \ No newline at end of file diff --git a/src/components/auth/LinkDevice.tsx b/src/components/auth/LinkDevice.tsx new file mode 100644 index 0000000..1134881 --- /dev/null +++ b/src/components/auth/LinkDevice.tsx @@ -0,0 +1,103 @@ +import React, { useState, useEffect } from 'react' +import { useNavigate } from 'react-router-dom' +import { createAccountLinkingConsumer } from '../../lib/auth/linking' +import * as account from '@oddjs/odd/account' +import { useAuth } from '../../context/AuthContext' +import { useNotifications } from '../../context/NotificationContext' + +const LinkDevice: React.FC = () => { + const [username, setUsername] = useState('') + const [displayPin, setDisplayPin] = useState('') + const [view, setView] = useState<'enter-username' | 'show-pin' | 'load-filesystem'>('enter-username') + const [accountLinkingConsumer, setAccountLinkingConsumer] = useState(null) + const navigate = useNavigate() + const { login } = useAuth() + const { addNotification } = useNotifications() + + const initAccountLinkingConsumer = async () => { + try { + const consumer = await createAccountLinkingConsumer(username) + setAccountLinkingConsumer(consumer) + + consumer.on('challenge', ({ pin }: { pin: number[] }) => { + setDisplayPin(pin.join('')) + setView('show-pin') + }) + + consumer.on('link', async ({ approved, username }: { approved: boolean, username: string }) => { + if (approved) { + setView('load-filesystem') + + const success = await login(username) + + if (success) { + addNotification("You're now connected!", "success") + navigate('/') + } else { + addNotification("Connection successful but login failed", "error") + navigate('/login') + } + } else { + addNotification('The connection attempt was cancelled', "warning") + navigate('/') + } + }) + } catch (error) { + console.error('Error initializing account linking consumer:', error) + addNotification('Failed to initialize device linking', "error") + } + } + + const handleSubmitUsername = (e: React.FormEvent) => { + e.preventDefault() + initAccountLinkingConsumer() + } + + // Clean up consumer on unmount + useEffect(() => { + return () => { + if (accountLinkingConsumer) { + accountLinkingConsumer.destroy() + } + } + }, [accountLinkingConsumer]) + + return ( +
+ {view === 'enter-username' && ( + <> +

Link a New Device

+
+
+ + setUsername(e.target.value)} + required + /> +
+ +
+ + )} + + {view === 'show-pin' && ( +
+

Enter this PIN on your other device

+
{displayPin}
+
+ )} + + {view === 'load-filesystem' && ( +
+

Loading your filesystem...

+

Please wait while we connect to your account.

+
+ )} +
+ ) +} + +export default LinkDevice \ No newline at end of file diff --git a/src/components/auth/Loading.tsx b/src/components/auth/Loading.tsx new file mode 100644 index 0000000..d877aed --- /dev/null +++ b/src/components/auth/Loading.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +interface LoadingProps { + message?: string; +} + +const Loading: React.FC = ({ message = 'Loading...' }) => { + return ( +
+
+
+
+

{message}

+
+ ); +}; + +export default Loading; \ No newline at end of file diff --git a/src/components/auth/Login.tsx b/src/components/auth/Login.tsx new file mode 100644 index 0000000..5dcf4d0 --- /dev/null +++ b/src/components/auth/Login.tsx @@ -0,0 +1,188 @@ +import React, { useState, useEffect } from 'react'; +import { isUsernameValid, isUsernameAvailable } from '../../lib/auth/account'; +import { useAuth } from '../../context/AuthContext'; +import { useNotifications } from '../../context/NotificationContext'; + +interface LoginProps { + onSuccess?: () => void; +} + +/** + * Combined Login/Register component + * + * Handles both login and registration flows based on user selection + */ +const Login: React.FC = ({ onSuccess }) => { + const [username, setUsername] = useState(''); + const [isRegistering, setIsRegistering] = useState(false); + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [usernameValid, setUsernameValid] = useState(null); + const [usernameAvailable, setUsernameAvailable] = useState(null); + const [isCheckingUsername, setIsCheckingUsername] = useState(false); + + const { login, register } = useAuth(); + const { addNotification } = useNotifications(); + + /** + * Validate username when it changes and we're in registration mode + */ + useEffect(() => { + if (!isRegistering || !username || username.length < 3) { + setUsernameValid(null); + setUsernameAvailable(null); + return; + } + + const validateUsername = async () => { + setIsCheckingUsername(true); + + try { + // Check username validity + const valid = await isUsernameValid(username); + setUsernameValid(valid); + + if (!valid) { + setUsernameAvailable(null); + setIsCheckingUsername(false); + return; + } + + // Check username availability + const available = await isUsernameAvailable(username); + setUsernameAvailable(available); + } catch (error) { + console.error('Username validation error:', error); + setUsernameValid(false); + setUsernameAvailable(null); + } finally { + setIsCheckingUsername(false); + } + }; + + validateUsername(); + }, [username, isRegistering]); + + /** + * Handle form submission for both login and registration + */ + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(null); + setIsLoading(true); + + try { + if (isRegistering) { + // Registration flow + if (!usernameValid) { + setError('Invalid username format'); + setIsLoading(false); + return; + } + + if (!usernameAvailable) { + setError('Username is already taken'); + setIsLoading(false); + return; + } + + const success = await register(username); + if (success) { + addNotification(`Welcome, ${username}! Your account has been created.`, 'success'); + if (onSuccess) onSuccess(); + } else { + setError('Registration failed'); + addNotification('Registration failed. Please try again.', 'error'); + } + } else { + // Login flow + const success = await login(username); + if (success) { + addNotification(`Welcome back, ${username}!`, 'success'); + if (onSuccess) onSuccess(); + } else { + setError('User not found or login failed'); + addNotification('Login failed. Please check your username.', 'error'); + } + } + } catch (err) { + console.error('Authentication error:', err); + setError('An unexpected error occurred'); + addNotification('Authentication error. Please try again later.', 'error'); + } finally { + setIsLoading(false); + } + }; + + return ( +
+

{isRegistering ? 'Create Account' : 'Sign In'}

+ +
+
+ + setUsername(e.target.value)} + placeholder="Enter username" + required + disabled={isLoading} + autoComplete="username" + minLength={3} + maxLength={20} + /> + + {/* Username validation feedback */} + {isRegistering && username.length >= 3 && ( +
+ {isCheckingUsername && ( + Checking username... + )} + + {!isCheckingUsername && usernameValid === false && ( + + Username must be 3-20 characters and contain only letters, numbers, underscores, or hyphens + + )} + + {!isCheckingUsername && usernameValid === true && usernameAvailable === false && ( + Username is already taken + )} + + {!isCheckingUsername && usernameValid === true && usernameAvailable === true && ( + Username is available + )} +
+ )} +
+ + {error &&
{error}
} + + +
+ +
+ +
+
+ ); +}; + +export default Login; \ No newline at end of file diff --git a/src/components/auth/Profile.tsx b/src/components/auth/Profile.tsx new file mode 100644 index 0000000..f50c939 --- /dev/null +++ b/src/components/auth/Profile.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { useAuth } from '../../../src/context/AuthContext'; +import { clearSession } from '../../lib/init'; + +interface ProfileProps { + onLogout?: () => void; +} + +export const Profile: React.FC = ({ onLogout }) => { + const { session, updateSession } = useAuth(); + + const handleLogout = () => { + // Clear the session + clearSession(); + + // Update the auth context + updateSession({ + username: '', + authed: false, + backupCreated: null, + }); + + // Call the onLogout callback if provided + if (onLogout) onLogout(); + }; + + if (!session.authed || !session.username) { + return null; + } + + return ( +
+
+

Welcome, {session.username}!

+
+ +
+ +
+ + {!session.backupCreated && ( +
+

Remember to back up your encryption keys to prevent data loss!

+
+ )} +
+ ); +}; \ No newline at end of file diff --git a/src/components/auth/ProtectedRoute.tsx b/src/components/auth/ProtectedRoute.tsx new file mode 100644 index 0000000..06daeb9 --- /dev/null +++ b/src/components/auth/ProtectedRoute.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { useAuth } from '../../../src/context/AuthContext'; + +interface ProtectedRouteProps { + children: React.ReactNode; +} + +export const ProtectedRoute: React.FC = ({ children }) => { + const { session } = useAuth(); + + if (session.loading) { + // Show loading indicator while authentication is being checked + return ( +
+

Checking authentication...

+
+ ); + } + + // For board routes, we'll allow access even if not authenticated + // The auth button in the toolbar will handle authentication + return <>{children}; +}; \ No newline at end of file diff --git a/src/components/auth/Register.tsx b/src/components/auth/Register.tsx new file mode 100644 index 0000000..9ae42b0 --- /dev/null +++ b/src/components/auth/Register.tsx @@ -0,0 +1,64 @@ +import React, { useState } from 'react' +import { register } from '../../lib/auth/account' + +const Register: React.FC = () => { + const [username, setUsername] = useState('') + const [checkingUsername, setCheckingUsername] = useState(false) + const [initializingFilesystem, setInitializingFilesystem] = useState(false) + const [error, setError] = useState(null) + + const handleRegister = async (e: React.FormEvent) => { + e.preventDefault() + + if (checkingUsername) { + return + } + + setInitializingFilesystem(true) + setError(null) + + try { + const success = await register(username) + + if (!success) { + setError('Registration failed. Username may be taken.') + setInitializingFilesystem(false) + } + } catch (err) { + setError('An error occurred during registration') + setInitializingFilesystem(false) + console.error(err) + } + } + + return ( +
+

Create an Account

+ +
+
+ + setUsername(e.target.value)} + disabled={initializingFilesystem} + required + /> +
+ + {error &&
{error}
} + + +
+
+ ) +} + +export default Register \ No newline at end of file diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx new file mode 100644 index 0000000..7134222 --- /dev/null +++ b/src/context/AuthContext.tsx @@ -0,0 +1,149 @@ +import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'; +import type FileSystem from '@oddjs/odd/fs/index'; +import { Session, SessionError } from '../lib/auth/types'; +import { AuthService } from '../lib/auth/authService'; + +interface AuthContextType { + session: Session; + setSession: (updatedSession: Partial) => void; + fileSystem: FileSystem | null; + setFileSystem: (fs: FileSystem | null) => void; + initialize: () => Promise; + login: (username: string) => Promise; + register: (username: string) => Promise; + logout: () => Promise; +} + +const initialSession: Session = { + username: '', + authed: false, + loading: true, + backupCreated: null +}; + +const AuthContext = createContext(undefined); + +export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const [session, setSessionState] = useState(initialSession); + const [fileSystem, setFileSystemState] = useState(null); + + // Update session with partial data + const setSession = (updatedSession: Partial) => { + setSessionState(prev => ({ ...prev, ...updatedSession })); + }; + + // Set file system + const setFileSystem = (fs: FileSystem | null) => { + setFileSystemState(fs); + }; + + /** + * Initialize the authentication state + */ + const initialize = async (): Promise => { + setSession({ loading: true }); + + try { + const { session: newSession, fileSystem: newFs } = await AuthService.initialize(); + setSession(newSession); + setFileSystem(newFs); + } catch (error) { + setSession({ + loading: false, + authed: false, + error: error as SessionError + }); + } + }; + + /** + * Login with a username + */ + const login = async (username: string): Promise => { + setSession({ loading: true }); + + const result = await AuthService.login(username); + + if (result.success && result.session && result.fileSystem) { + setSession(result.session); + setFileSystem(result.fileSystem); + return true; + } else { + setSession({ + loading: false, + error: result.error as SessionError + }); + return false; + } + }; + + /** + * Register a new user + */ + const register = async (username: string): Promise => { + setSession({ loading: true }); + + const result = await AuthService.register(username); + + if (result.success && result.session && result.fileSystem) { + setSession(result.session); + setFileSystem(result.fileSystem); + return true; + } else { + setSession({ + loading: false, + error: result.error as SessionError + }); + return false; + } + }; + + /** + * Logout the current user + */ + const logout = async (): Promise => { + try { + await AuthService.logout(); + setSession({ + username: '', + authed: false, + loading: false, + backupCreated: null + }); + setFileSystem(null); + } catch (error) { + console.error('Logout error:', error); + throw error; + } + }; + + // Initialize on mount + useEffect(() => { + initialize(); + }, []); + + const contextValue: AuthContextType = { + session, + setSession, + fileSystem, + setFileSystem, + initialize, + login, + register, + logout + }; + + return ( + + {children} + + ); +}; + +export const useAuth = (): AuthContextType => { + const context = useContext(AuthContext); + if (context === undefined) { + throw new Error('useAuth must be used within an AuthProvider'); + } + return context; +}; \ No newline at end of file diff --git a/src/context/FileSystemContext.tsx b/src/context/FileSystemContext.tsx new file mode 100644 index 0000000..9cac971 --- /dev/null +++ b/src/context/FileSystemContext.tsx @@ -0,0 +1,158 @@ +import React, { createContext, useContext, useState, ReactNode } from 'react'; +import type * as webnative from 'webnative'; +import type FileSystem from 'webnative/fs/index'; + +/** + * File system context interface + */ +interface FileSystemContextType { + fs: FileSystem | null; + setFs: (fs: FileSystem | null) => void; + isReady: boolean; +} + +// Create context with a default undefined value +const FileSystemContext = createContext(undefined); + +/** + * FileSystemProvider component + * + * Provides access to the webnative filesystem throughout the application. + */ +export const FileSystemProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const [fs, setFs] = useState(null); + + // File system is ready when it's not null + const isReady = fs !== null; + + return ( + + {children} + + ); +}; + +/** + * Hook to access the file system context + * + * @returns The file system context + * @throws Error if used outside of FileSystemProvider + */ +export const useFileSystem = (): FileSystemContextType => { + const context = useContext(FileSystemContext); + if (context === undefined) { + throw new Error('useFileSystem must be used within a FileSystemProvider'); + } + return context; +}; + +/** + * Directory paths used in the application + */ +export const DIRECTORIES = { + PUBLIC: { + ROOT: ['public'], + GALLERY: ['public', 'gallery'], + DOCUMENTS: ['public', 'documents'] + }, + PRIVATE: { + ROOT: ['private'], + GALLERY: ['private', 'gallery'], + SETTINGS: ['private', 'settings'], + DOCUMENTS: ['private', 'documents'] + } +}; + +/** + * Common filesystem operations + * + * @param fs The filesystem instance + * @returns An object with filesystem utility functions + */ +export const createFileSystemUtils = (fs: FileSystem) => { + return { + /** + * Creates a directory if it doesn't exist + * + * @param path Array of path segments + */ + ensureDirectory: async (path: string[]): Promise => { + const dirPath = webnative.path.directory(...path); + const exists = await fs.exists(dirPath); + if (!exists) { + await fs.mkdir(dirPath); + } + }, + + /** + * Writes a file to the filesystem + * + * @param path Array of path segments + * @param fileName The name of the file + * @param content The content to write + */ + writeFile: async (path: string[], fileName: string, content: Blob | string): Promise => { + const filePath = webnative.path.file(...path, fileName); + await fs.write(filePath, content); + await fs.publish(); + }, + + /** + * Reads a file from the filesystem + * + * @param path Array of path segments + * @param fileName The name of the file + * @returns The file content + */ + readFile: async (path: string[], fileName: string): Promise => { + const filePath = webnative.path.file(...path, fileName); + const exists = await fs.exists(filePath); + if (!exists) { + throw new Error(`File doesn't exist: ${filePath}`); + } + return await fs.read(filePath); + }, + + /** + * Checks if a file exists + * + * @param path Array of path segments + * @param fileName The name of the file + * @returns Boolean indicating if the file exists + */ + fileExists: async (path: string[], fileName: string): Promise => { + const filePath = webnative.path.file(...path, fileName); + return await fs.exists(filePath); + }, + + /** + * Lists files in a directory + * + * @param path Array of path segments + * @returns Object with file names as keys + */ + listDirectory: async (path: string[]): Promise> => { + const dirPath = webnative.path.directory(...path); + const exists = await fs.exists(dirPath); + if (!exists) { + return {}; + } + return await fs.ls(dirPath); + } + }; +}; + +/** + * Hook to use filesystem utilities + * + * @returns Filesystem utilities or null if filesystem is not ready + */ +export const useFileSystemUtils = () => { + const { fs, isReady } = useFileSystem(); + + if (!isReady || !fs) { + return null; + } + + return createFileSystemUtils(fs); +}; \ No newline at end of file diff --git a/src/context/NotificationContext.tsx b/src/context/NotificationContext.tsx new file mode 100644 index 0000000..6658e77 --- /dev/null +++ b/src/context/NotificationContext.tsx @@ -0,0 +1,111 @@ +import React, { createContext, useContext, useState, useCallback, ReactNode } from 'react'; + +/** + * Types of notifications supported by the system + */ +export type NotificationType = 'success' | 'error' | 'info' | 'warning'; + +/** + * Notification object structure + */ +export type Notification = { + id: string; + msg: string; + type: NotificationType; + timeout: number; +}; + +/** + * Interface for the notification context + */ +interface NotificationContextType { + notifications: Notification[]; + addNotification: (msg: string, type?: NotificationType, timeout?: number) => string; + removeNotification: (id: string) => void; + clearAllNotifications: () => void; +} + +// Create context with a default undefined value +const NotificationContext = createContext(undefined); + +/** + * NotificationProvider component - provides notification functionality to the app + */ +export const NotificationProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const [notifications, setNotifications] = useState([]); + + /** + * Remove a notification by ID + */ + const removeNotification = useCallback((id: string) => { + setNotifications(current => current.filter(notification => notification.id !== id)); + }, []); + + /** + * Add a new notification + * @param msg The message to display + * @param type The type of notification (success, error, info, warning) + * @param timeout Time in ms before notification is automatically removed + * @returns The ID of the created notification + */ + const addNotification = useCallback( + (msg: string, type: NotificationType = 'info', timeout: number = 5000): string => { + // Create a unique ID for the notification + const id = crypto.randomUUID(); + + // Add notification to the array + setNotifications(current => [ + ...current, + { + id, + msg, + type, + timeout, + } + ]); + + // Set up automatic removal after timeout + if (timeout > 0) { + setTimeout(() => { + removeNotification(id); + }, timeout); + } + + // Return the notification ID for reference + return id; + }, + [removeNotification] + ); + + /** + * Clear all current notifications + */ + const clearAllNotifications = useCallback(() => { + setNotifications([]); + }, []); + + // Create the context value with all functions and state + const contextValue: NotificationContextType = { + notifications, + addNotification, + removeNotification, + clearAllNotifications + }; + + return ( + + {children} + + ); +}; + +/** + * Hook to access the notification context + */ +export const useNotifications = (): NotificationContextType => { + const context = useContext(NotificationContext); + if (context === undefined) { + throw new Error('useNotifications must be used within a NotificationProvider'); + } + return context; +}; \ No newline at end of file diff --git a/src/css/auth.css b/src/css/auth.css new file mode 100644 index 0000000..41528c7 --- /dev/null +++ b/src/css/auth.css @@ -0,0 +1,176 @@ +/* Authentication Page Styles */ +.auth-page { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + background-color: #f5f5f5; + padding: 20px; + } + + .auth-container { + background-color: white; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + padding: 30px; + width: 100%; + max-width: 400px; + } + + .auth-container h2 { + margin-top: 0; + margin-bottom: 24px; + text-align: center; + color: #333; + font-size: 24px; + } + + .form-group { + margin-bottom: 20px; + } + + .form-group label { + display: block; + margin-bottom: 6px; + font-weight: 500; + color: #555; + } + + .form-group input { + width: 100%; + padding: 10px 12px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 16px; + transition: border-color 0.2s; + } + + .form-group input:focus { + border-color: #6366f1; + outline: none; + box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2); + } + + .error-message { + color: #dc2626; + margin-bottom: 20px; + font-size: 14px; + background-color: #fee2e2; + padding: 8px 12px; + border-radius: 4px; + border-left: 3px solid #dc2626; + } + + .auth-button { + width: 100%; + background-color: #6366f1; + color: white; + border: none; + border-radius: 4px; + padding: 12px 16px; + font-size: 16px; + font-weight: 500; + cursor: pointer; + transition: background-color 0.2s; + } + + .auth-button:hover { + background-color: #4f46e5; + } + + .auth-button:disabled { + background-color: #9ca3af; + cursor: not-allowed; + } + + .auth-toggle { + margin-top: 20px; + text-align: center; + } + + .auth-toggle button { + background: none; + border: none; + color: #6366f1; + font-size: 14px; + cursor: pointer; + text-decoration: underline; + } + + .auth-toggle button:hover { + color: #4f46e5; + } + + .auth-toggle button:disabled { + color: #9ca3af; + cursor: not-allowed; + text-decoration: none; + } + + .auth-container.loading, + .auth-container.error { + text-align: center; + padding: 40px 30px; + } + + .auth-loading { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + background-color: #f5f5f5; + } + + /* Profile Component Styles */ + .profile-container { + background-color: white; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + padding: 20px; + margin-bottom: 20px; + } + + .profile-header { + margin-bottom: 16px; + } + + .profile-header h3 { + margin: 0; + color: #333; + font-size: 18px; + } + + .profile-actions { + display: flex; + justify-content: flex-end; + } + + .logout-button { + background-color: #ef4444; + color: white; + border: none; + border-radius: 4px; + padding: 8px 16px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: background-color 0.2s; + } + + .logout-button:hover { + background-color: #dc2626; + } + + .backup-reminder { + margin-top: 16px; + padding: 12px; + background-color: #fffbeb; + border-radius: 4px; + border-left: 3px solid #f59e0b; + } + + .backup-reminder p { + margin: 0; + color: #92400e; + font-size: 14px; + } \ No newline at end of file diff --git a/src/css/loading.css b/src/css/loading.css new file mode 100644 index 0000000..0d6f49a --- /dev/null +++ b/src/css/loading.css @@ -0,0 +1,32 @@ +.loading-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; + width: 100%; + } + + .loading-spinner { + margin-bottom: 1rem; + } + + .spinner { + width: 40px; + height: 40px; + border: 4px solid rgba(0, 0, 0, 0.1); + border-radius: 50%; + border-top-color: #3498db; + animation: spin 1s ease-in-out infinite; + } + + .loading-message { + font-size: 1.2rem; + color: #333; + } + + @keyframes spin { + to { + transform: rotate(360deg); + } + } \ No newline at end of file diff --git a/src/lib/auth/Login.tsx b/src/lib/auth/Login.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/lib/auth/account.ts b/src/lib/auth/account.ts new file mode 100644 index 0000000..7dc57d7 --- /dev/null +++ b/src/lib/auth/account.ts @@ -0,0 +1,193 @@ +import * as odd from '@oddjs/odd'; +import type FileSystem from '@oddjs/odd/fs/index'; +import { asyncDebounce } from '../utils/asyncDebounce'; +import * as browser from '../utils/browser'; +import { DIRECTORIES } from '../../context/FileSystemContext'; + +/** + * Constants for filesystem paths + */ +export const ACCOUNT_SETTINGS_DIR = ['private', 'settings']; +export const GALLERY_DIRS = { + PUBLIC: ['public', 'gallery'], + PRIVATE: ['private', 'gallery'] +}; +export const AREAS = { + PUBLIC: 'public', + PRIVATE: 'private' +}; + +/** + * Checks if a username is valid according to ODD's rules + * @param username The username to check + * @returns A boolean indicating if the username is valid + */ +export const isUsernameValid = async (username: string): Promise => { + console.log('Checking if username is valid:', username); + try { + const isValid = await odd.account.isUsernameValid(username); + console.log('Username validity check result:', isValid); + return isValid; + } catch (error) { + console.error('Error checking username validity:', error); + return false; + } +}; + +/** + * Debounced function to check if a username is available + */ +const debouncedIsUsernameAvailable = asyncDebounce( + odd.account.isUsernameAvailable, + 300 +); + +/** + * Checks if a username is available + * @param username The username to check + * @returns A boolean indicating if the username is available + */ +export const isUsernameAvailable = async ( + username: string +): Promise => { + console.log('Checking if username is available:', username); + try { + // In a local development environment, simulate the availability check + // by checking if the username exists in localStorage + if (browser.isBrowser()) { + const isAvailable = browser.isUsernameAvailable(username); + console.log('Username availability check result:', isAvailable); + return isAvailable; + } else { + // If not in a browser (SSR), use the ODD API + const isAvailable = await debouncedIsUsernameAvailable(username); + console.log('Username availability check result:', isAvailable); + return isAvailable; + } + } catch (error) { + console.error('Error checking username availability:', error); + return false; + } +}; + +/** + * Create additional directories and files needed by the app + * @param fs FileSystem + */ +export const initializeFilesystem = async (fs: FileSystem): Promise => { + try { + // Create required directories + console.log('Creating required directories...'); + + // Public directories + await fs.mkdir(odd.path.directory(...DIRECTORIES.PUBLIC.ROOT)); + await fs.mkdir(odd.path.directory(...DIRECTORIES.PUBLIC.GALLERY)); + await fs.mkdir(odd.path.directory(...DIRECTORIES.PUBLIC.DOCUMENTS)); + + // Private directories + await fs.mkdir(odd.path.directory(...DIRECTORIES.PRIVATE.ROOT)); + await fs.mkdir(odd.path.directory(...DIRECTORIES.PRIVATE.GALLERY)); + await fs.mkdir(odd.path.directory(...DIRECTORIES.PRIVATE.SETTINGS)); + await fs.mkdir(odd.path.directory(...DIRECTORIES.PRIVATE.DOCUMENTS)); + + console.log('Filesystem initialized successfully'); + } catch (error) { + console.error('Error during filesystem initialization:', error); + throw error; + } +}; + +/** + * Checks data root for a username with retries + * @param username The username to check + */ +export const checkDataRoot = async (username: string): Promise => { + console.log('Looking up data root for username:', username); + let dataRoot = await odd.dataRoot.lookup(username); + console.log('Initial data root lookup result:', dataRoot ? 'found' : 'not found'); + + if (dataRoot) return; + + console.log('Data root not found, starting retry process...'); + return new Promise((resolve, reject) => { + const maxRetries = 20; + let attempt = 0; + + const dataRootInterval = setInterval(async () => { + console.warn(`Could not fetch filesystem data root. Retrying (${attempt + 1}/${maxRetries})`); + + dataRoot = await odd.dataRoot.lookup(username); + console.log(`Retry ${attempt + 1} result:`, dataRoot ? 'found' : 'not found'); + + if (!dataRoot && attempt < maxRetries) { + attempt++; + return; + } + + console.log(`Retry process completed. Data root ${dataRoot ? 'found' : 'not found'} after ${attempt + 1} attempts`); + clearInterval(dataRootInterval); + + if (dataRoot) { + resolve(); + } else { + reject(new Error(`Data root not found after ${maxRetries} attempts`)); + } + }, 500); + }); +}; + +/** + * Generate a cryptographic key pair and store in localStorage during registration + * @param username The username being registered + */ +export const generateUserCredentials = async (username: string): Promise => { + if (!browser.isBrowser()) return false; + + try { + console.log('Generating cryptographic keys for user...'); + // Generate a key pair using Web Crypto API + const keyPair = await browser.generateKeyPair(); + + if (!keyPair) { + console.error('Failed to generate key pair'); + return false; + } + + // Export the public key + const publicKeyBase64 = await browser.exportPublicKey(keyPair.publicKey); + + if (!publicKeyBase64) { + console.error('Failed to export public key'); + return false; + } + + console.log('Keys generated successfully'); + + // Store the username and public key + browser.addRegisteredUser(username); + browser.storePublicKey(username, publicKeyBase64); + + return true; + } catch (error) { + console.error('Error generating user credentials:', error); + return false; + } +}; + +/** + * Validate a user's stored credentials (for development mode) + * @param username The username to validate + */ +export const validateStoredCredentials = (username: string): boolean => { + if (!browser.isBrowser()) return false; + + try { + const users = browser.getRegisteredUsers(); + const publicKey = browser.getPublicKey(username); + + return users.includes(username) && !!publicKey; + } catch (error) { + console.error('Error validating stored credentials:', error); + return false; + } +}; \ No newline at end of file diff --git a/src/lib/auth/authService.ts b/src/lib/auth/authService.ts new file mode 100644 index 0000000..ea5a5f2 --- /dev/null +++ b/src/lib/auth/authService.ts @@ -0,0 +1,186 @@ +import * as odd from '@oddjs/odd'; +import type FileSystem from '@oddjs/odd/fs/index'; +import { checkDataRoot, initializeFilesystem, isUsernameValid, isUsernameAvailable } from './account'; +import { getBackupStatus } from './backup'; +import { Session } from './types'; + +export class AuthService { + /** + * Initialize the authentication state + */ + static async initialize(): Promise<{ + session: Session; + fileSystem: FileSystem | null; + }> { + console.log('Initializing authentication...'); + try { + // Call the ODD program function to get current auth state + const program = await odd.program({ + namespace: { creator: 'mycrozine', name: 'app' } + }); + + let session: Session; + let fileSystem: FileSystem | null = null; + + if (program.session) { + // User is authenticated + fileSystem = program.session.fs; + const backupStatus = await getBackupStatus(fileSystem); + session = { + username: program.session.username, + authed: true, + loading: false, + backupCreated: backupStatus.created + }; + } else { + // User is not authenticated + session = { + username: '', + authed: false, + loading: false, + backupCreated: null + }; + } + + return { session, fileSystem }; + } catch (error) { + console.error('Authentication initialization error:', error); + return { + session: { + username: '', + authed: false, + loading: false, + backupCreated: null, + error: String(error) + }, + fileSystem: null + }; + } + } + + /** + * Login with a username + */ + static async login(username: string): Promise<{ + success: boolean; + session?: Session; + fileSystem?: FileSystem; + error?: string; + }> { + try { + // Attempt to load the account + const program = await odd.program({ + namespace: { creator: 'mycrozine', name: 'app' }, + username + }); + + if (program.session) { + const fs = program.session.fs; + const backupStatus = await getBackupStatus(fs); + + return { + success: true, + session: { + username, + authed: true, + loading: false, + backupCreated: backupStatus.created + }, + fileSystem: fs + }; + } else { + return { + success: false, + error: 'Failed to authenticate' + }; + } + } catch (error) { + console.error('Login error:', error); + return { + success: false, + error: String(error) + }; + } + } + + /** + * Register a new user + */ + static async register(username: string): Promise<{ + success: boolean; + session?: Session; + fileSystem?: FileSystem; + error?: string; + }> { + try { + // Validate username + const valid = await isUsernameValid(username); + if (!valid) { + return { + success: false, + error: 'Invalid username format' + }; + } + + // Check availability + const available = await isUsernameAvailable(username); + if (!available) { + return { + success: false, + error: 'Username is already taken' + }; + } + + // Register the user + const program = await odd.program({ + namespace: { creator: 'mycrozine', name: 'app' }, + username + }); + + if (program.session) { + const fs = program.session.fs; + + // Initialize filesystem with required directories + await initializeFilesystem(fs); + + // Check backup status + const backupStatus = await getBackupStatus(fs); + + return { + success: true, + session: { + username, + authed: true, + loading: false, + backupCreated: backupStatus.created + }, + fileSystem: fs + }; + } else { + return { + success: false, + error: 'Failed to create account' + }; + } + } catch (error) { + console.error('Registration error:', error); + return { + success: false, + error: String(error) + }; + } + } + + /** + * Logout the current user + */ + static async logout(): Promise { + try { + await odd.session.destroy(); + return true; + } catch (error) { + console.error('Logout error:', error); + return false; + } + } +} \ No newline at end of file diff --git a/src/lib/auth/backup.ts b/src/lib/auth/backup.ts new file mode 100644 index 0000000..d452c34 --- /dev/null +++ b/src/lib/auth/backup.ts @@ -0,0 +1,15 @@ +import type * as odd from '@oddjs/odd' + +export type BackupStatus = { + created: boolean | null +} + +export const getBackupStatus = async (fs: odd.FileSystem): Promise => { + try { + const backupStatus = await fs.exists(odd.path.backups()) + return { created: backupStatus } + } catch (error) { + console.error('Error checking backup status:', error) + return { created: null } + } +} \ No newline at end of file diff --git a/src/lib/auth/crypto.ts b/src/lib/auth/crypto.ts new file mode 100644 index 0000000..8994195 --- /dev/null +++ b/src/lib/auth/crypto.ts @@ -0,0 +1,197 @@ +// This module contains browser-specific WebCrypto API utilities + +// Check if we're in a browser environment +export const isBrowser = (): boolean => typeof window !== 'undefined'; + +// Get registered users from localStorage +export const getRegisteredUsers = (): string[] => { + if (!isBrowser()) return []; + try { + return JSON.parse(window.localStorage.getItem('registeredUsers') || '[]'); + } catch (error) { + console.error('Error getting registered users:', error); + return []; + } +}; + +// Add a user to the registered users list +export const addRegisteredUser = (username: string): void => { + if (!isBrowser()) return; + try { + const users = getRegisteredUsers(); + if (!users.includes(username)) { + users.push(username); + window.localStorage.setItem('registeredUsers', JSON.stringify(users)); + } + } catch (error) { + console.error('Error adding registered user:', error); + } +}; + +// Check if a username is available +export const isUsernameAvailable = async (username: string): Promise => { + console.log('Checking if username is available:', username); + + try { + // Get the list of registered users + const users = getRegisteredUsers(); + + // Check if the username is already taken + const isAvailable = !users.includes(username); + + console.log('Username availability result:', isAvailable); + return isAvailable; + } catch (error) { + console.error('Error checking username availability:', error); + return false; + } +}; + +// Check if username is valid format (letters, numbers, underscores, hyphens) +export const isUsernameValid = (username: string): boolean => { + const usernameRegex = /^[a-zA-Z0-9_-]{3,20}$/; + return usernameRegex.test(username); +}; + +// Store a public key for a user +export const storePublicKey = (username: string, publicKey: string): void => { + if (!isBrowser()) return; + try { + window.localStorage.setItem(`${username}_publicKey`, publicKey); + } catch (error) { + console.error('Error storing public key:', error); + } +}; + +// Get a user's public key +export const getPublicKey = (username: string): string | null => { + if (!isBrowser()) return null; + try { + return window.localStorage.getItem(`${username}_publicKey`); + } catch (error) { + console.error('Error getting public key:', error); + return null; + } +}; + +// Generate a key pair using Web Crypto API +export const generateKeyPair = async (): Promise => { + if (!isBrowser()) return null; + try { + return await window.crypto.subtle.generateKey( + { + name: 'ECDSA', + namedCurve: 'P-256', + }, + true, + ['sign', 'verify'] + ); + } catch (error) { + console.error('Error generating key pair:', error); + return null; + } +}; + +// Export a public key to a base64 string +export const exportPublicKey = async (publicKey: CryptoKey): Promise => { + if (!isBrowser()) return null; + try { + const publicKeyBuffer = await window.crypto.subtle.exportKey( + 'raw', + publicKey + ); + + return btoa( + String.fromCharCode.apply(null, Array.from(new Uint8Array(publicKeyBuffer))) + ); + } catch (error) { + console.error('Error exporting public key:', error); + return null; + } +}; + +// Import a public key from a base64 string +export const importPublicKey = async (base64Key: string): Promise => { + if (!isBrowser()) return null; + try { + const binaryString = atob(base64Key); + const len = binaryString.length; + const bytes = new Uint8Array(len); + + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + return await window.crypto.subtle.importKey( + 'raw', + bytes, + { + name: 'ECDSA', + namedCurve: 'P-256', + }, + true, + ['verify'] + ); + } catch (error) { + console.error('Error importing public key:', error); + return null; + } +}; + +// Sign data with a private key +export const signData = async (privateKey: CryptoKey, data: string): Promise => { + if (!isBrowser()) return null; + try { + const encoder = new TextEncoder(); + const encodedData = encoder.encode(data); + + const signature = await window.crypto.subtle.sign( + { + name: 'ECDSA', + hash: { name: 'SHA-256' }, + }, + privateKey, + encodedData + ); + + return btoa( + String.fromCharCode.apply(null, Array.from(new Uint8Array(signature))) + ); + } catch (error) { + console.error('Error signing data:', error); + return null; + } +}; + +// Verify a signature +export const verifySignature = async ( + publicKey: CryptoKey, + signature: string, + data: string +): Promise => { + if (!isBrowser()) return false; + try { + const encoder = new TextEncoder(); + const encodedData = encoder.encode(data); + + const binarySignature = atob(signature); + const signatureBytes = new Uint8Array(binarySignature.length); + + for (let i = 0; i < binarySignature.length; i++) { + signatureBytes[i] = binarySignature.charCodeAt(i); + } + + return await window.crypto.subtle.verify( + { + name: 'ECDSA', + hash: { name: 'SHA-256' }, + }, + publicKey, + signatureBytes, + encodedData + ); + } catch (error) { + console.error('Error verifying signature:', error); + return false; + } +}; \ No newline at end of file diff --git a/src/lib/auth/linking.ts b/src/lib/auth/linking.ts new file mode 100644 index 0000000..f382af0 --- /dev/null +++ b/src/lib/auth/linking.ts @@ -0,0 +1,24 @@ +import * as odd from '@oddjs/odd'; +import * as account from '@oddjs/odd/account'; + +/** + * Creates an account linking consumer for the specified username + * @param username The username to create a consumer for + * @returns A Promise resolving to an AccountLinkingConsumer + */ +export const createAccountLinkingConsumer = async ( + username: string +): Promise => { + return await odd.account.createConsumer({ username }); +}; + +/** + * Creates an account linking producer for the specified username + * @param username The username to create a producer for + * @returns A Promise resolving to an AccountLinkingProducer + */ +export const createAccountLinkingProducer = async ( + username: string +): Promise => { + return await odd.account.createProducer({ username }); +}; \ No newline at end of file diff --git a/src/lib/auth/types.ts b/src/lib/auth/types.ts new file mode 100644 index 0000000..2e79491 --- /dev/null +++ b/src/lib/auth/types.ts @@ -0,0 +1,25 @@ +export interface Session { + username: string; + authed: boolean; + loading: boolean; + backupCreated: boolean | null; + error?: string; +} + +export enum SessionError { + PROGRAM_FAILURE = 'PROGRAM_FAILURE', + FILESYSTEM_INIT_FAILURE = 'FILESYSTEM_INIT_FAILURE', + DATAROOT_NOT_FOUND = 'DATAROOT_NOT_FOUND', + UNKNOWN = 'UNKNOWN' +} + +export const errorToMessage = (error: SessionError): string | undefined => { + switch (error) { + case 'Insecure Context': + return `This application requires a secure context (HTTPS)`; + + case 'Unsupported Browser': + return `Your browser does not support the required features`; + } +}; + \ No newline at end of file diff --git a/src/lib/utils/asyncDebounce.ts b/src/lib/utils/asyncDebounce.ts new file mode 100644 index 0000000..c61c7f8 --- /dev/null +++ b/src/lib/utils/asyncDebounce.ts @@ -0,0 +1,188 @@ +/** + * Creates a debounced version of an async function. + * + * A debounced function will only execute after a specified delay has passed + * without the function being called again. This is particularly useful for + * functions that make API calls in response to user input, to avoid making + * too many calls when a user is actively typing or interacting. + * + * @param fn The async function to debounce + * @param wait The time to wait in milliseconds before the function is called + * @returns A debounced version of the input function + * + * @example + * // Create a debounced version of an API call function + * const debouncedFetch = asyncDebounce(fetchFromAPI, 300); + * + * // Use the debounced function in an input handler + * const handleInputChange = (e) => { + * debouncedFetch(e.target.value) + * .then(result => setData(result)) + * .catch(error => setError(error)); + * }; + */ +export function asyncDebounce( + fn: (...args: A) => Promise, + wait: number + ): (...args: A) => Promise { + let lastTimeoutId: ReturnType | undefined = undefined; + + return (...args: A): Promise => { + // Clear any existing timeout to cancel pending executions + clearTimeout(lastTimeoutId); + + // Return a promise that will resolve with the function's result + return new Promise((resolve, reject) => { + // Create a new timeout + const currentTimeoutId = setTimeout(async () => { + try { + // Only execute if this is still the most recent timeout + if (currentTimeoutId === lastTimeoutId) { + const result = await fn(...args); + resolve(result); + } + } catch (err) { + reject(err); + } + }, wait); + + // Store the current timeout ID + lastTimeoutId = currentTimeoutId; + }); + }; + } + + /** + * Throttles an async function to be called at most once per specified period. + * + * Unlike debounce which resets the timer on each call, throttle will ensure the + * function is called at most once in the specified period, regardless of how many + * times the throttled function is called. + * + * @param fn The async function to throttle + * @param limit The minimum time in milliseconds between function executions + * @returns A throttled version of the input function + * + * @example + * // Create a throttled version of an API call function + * const throttledSave = asyncThrottle(saveToAPI, 1000); + * + * // Use the throttled function in an input handler + * const handleInputChange = (e) => { + * throttledSave(e.target.value) + * .then(() => setSaveStatus('Saved')) + * .catch(error => setSaveStatus('Error saving')); + * }; + */ + export function asyncThrottle( + fn: (...args: A) => Promise, + limit: number + ): (...args: A) => Promise { + let lastRun = 0; + let lastPromise: Promise | null = null; + let pending = false; + let lastArgs: A | null = null; + + const execute = async (...args: A): Promise => { + lastRun = Date.now(); + pending = false; + return await fn(...args); + }; + + return (...args: A): Promise => { + lastArgs = args; + + // If we're not pending and it's been longer than the limit since the last run, + // execute immediately + if (!pending && Date.now() - lastRun >= limit) { + return execute(...args); + } + + // If we don't have a promise or we're not pending, create a new promise + if (!lastPromise || !pending) { + pending = true; + lastPromise = new Promise((resolve, reject) => { + setTimeout(async () => { + try { + // Make sure we're using the most recent args + if (lastArgs) { + const result = await execute(...lastArgs); + resolve(result); + } + } catch (err) { + reject(err); + } + }, limit - (Date.now() - lastRun)); + }); + } + + return lastPromise; + }; + } + + /** + * Extracts a search parameter from a URL and removes it from the URL. + * + * Useful for handling one-time parameters like auth tokens or invite codes. + * + * @param url The URL object + * @param param The parameter name to extract + * @returns The parameter value or null if not found + * + * @example + * // Extract an invite code from the current URL + * const url = new URL(window.location.href); + * const inviteCode = extractSearchParam(url, 'invite'); + * // The parameter is now removed from the URL + */ + export const extractSearchParam = (url: URL, param: string): string | null => { + // Get the parameter value + const val = url.searchParams.get(param); + + // Remove the parameter from the URL + url.searchParams.delete(param); + + // Update the browser history to reflect the URL change without reloading + if (typeof history !== 'undefined') { + history.replaceState(null, document.title, url.toString()); + } + + return val; + }; + + /** + * Checks if a function execution is taking too long and returns a timeout result if so. + * + * @param fn The async function to execute with timeout + * @param timeout The maximum time in milliseconds to wait + * @param timeoutResult The result to return if timeout occurs + * @returns The function result or timeout result + * + * @example + * // Execute a function with a 5-second timeout + * const result = await withTimeout( + * fetchDataFromSlowAPI, + * 5000, + * { error: 'Request timed out' } + * ); + */ + export async function withTimeout( + fn: () => Promise, + timeout: number, + timeoutResult: R + ): Promise { + let timeoutId: ReturnType; + + const timeoutPromise = new Promise((resolve) => { + timeoutId = setTimeout(() => resolve(timeoutResult), timeout); + }); + + try { + const result = await Promise.race([fn(), timeoutPromise]); + clearTimeout(timeoutId); + return result; + } catch (error) { + clearTimeout(timeoutId); + throw error; + } + } \ No newline at end of file diff --git a/src/lib/utils/browser.ts b/src/lib/utils/browser.ts new file mode 100644 index 0000000..4db2e32 --- /dev/null +++ b/src/lib/utils/browser.ts @@ -0,0 +1,187 @@ +/** + * Browser-specific utility functions + * + * This module contains browser-specific functionality for environment detection + * and other browser-related operations. + */ + +/** + * Check if we're in a browser environment + */ +export const isBrowser = (): boolean => typeof window !== 'undefined'; + +/** + * Check if the browser supports the required features for the application + */ +export const checkBrowserSupport = (): boolean => { + if (!isBrowser()) return false; + + // Check for IndexedDB support + const hasIndexedDB = typeof window.indexedDB !== 'undefined'; + + // Check for WebCrypto API support + const hasWebCrypto = typeof window.crypto !== 'undefined' && + typeof window.crypto.subtle !== 'undefined'; + + // Check for other required browser features + const hasLocalStorage = typeof window.localStorage !== 'undefined'; + const hasServiceWorker = 'serviceWorker' in navigator; + + return hasIndexedDB && hasWebCrypto && hasLocalStorage && hasServiceWorker; +}; + +/** + * Check if we're in a secure context (HTTPS) + */ +export const isSecureContext = (): boolean => { + if (!isBrowser()) return false; + return window.isSecureContext; +}; + +/** + * Get a URL parameter value + * @param name The parameter name + * @returns The parameter value or null if not found + */ +export const getUrlParameter = (name: string): string | null => { + if (!isBrowser()) return null; + + const urlParams = new URLSearchParams(window.location.search); + return urlParams.get(name); +}; + +/** + * Set a cookie + * @param name The cookie name + * @param value The cookie value + * @param days Number of days until expiration + */ +export const setCookie = (name: string, value: string, days: number = 7): void => { + if (!isBrowser()) return; + + const expires = new Date(); + expires.setTime(expires.getTime() + days * 24 * 60 * 60 * 1000); + document.cookie = `${name}=${value};expires=${expires.toUTCString()};path=/;SameSite=Strict`; +}; + +/** + * Get a cookie value + * @param name The cookie name + * @returns The cookie value or null if not found + */ +export const getCookie = (name: string): string | null => { + if (!isBrowser()) return null; + + const nameEQ = `${name}=`; + const ca = document.cookie.split(';'); + + for (let i = 0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0) === ' ') c = c.substring(1, c.length); + if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length); + } + + return null; +}; + +/** + * Delete a cookie + * @param name The cookie name + */ +export const deleteCookie = (name: string): void => { + if (!isBrowser()) return; + document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;SameSite=Strict`; +}; + +/** + * Check if the device is mobile + */ +export const isMobileDevice = (): boolean => { + if (!isBrowser()) return false; + + return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); +}; + +/** + * Get the browser name + */ +export const getBrowserName = (): string => { + if (!isBrowser()) return 'unknown'; + + const userAgent = navigator.userAgent; + + if (userAgent.indexOf('Firefox') > -1) return 'Firefox'; + if (userAgent.indexOf('Chrome') > -1) return 'Chrome'; + if (userAgent.indexOf('Safari') > -1) return 'Safari'; + if (userAgent.indexOf('Edge') > -1) return 'Edge'; + if (userAgent.indexOf('MSIE') > -1 || userAgent.indexOf('Trident') > -1) return 'Internet Explorer'; + + return 'unknown'; +}; + +/** + * Check if local storage is available + */ +export const isLocalStorageAvailable = (): boolean => { + if (!isBrowser()) return false; + + try { + const test = '__test__'; + localStorage.setItem(test, test); + localStorage.removeItem(test); + return true; + } catch (e) { + return false; + } +}; + +/** + * Safely get an item from local storage + * @param key The storage key + * @returns The stored value or null if not found + */ +export const getLocalStorageItem = (key: string): string | null => { + if (!isBrowser() || !isLocalStorageAvailable()) return null; + + try { + return localStorage.getItem(key); + } catch (error) { + console.error('Error getting item from localStorage:', error); + return null; + } +}; + +/** + * Safely set an item in local storage + * @param key The storage key + * @param value The value to store + * @returns True if successful, false otherwise + */ +export const setLocalStorageItem = (key: string, value: string): boolean => { + if (!isBrowser() || !isLocalStorageAvailable()) return false; + + try { + localStorage.setItem(key, value); + return true; + } catch (error) { + console.error('Error setting item in localStorage:', error); + return false; + } +}; + +/** + * Safely remove an item from local storage + * @param key The storage key + * @returns True if successful, false otherwise + */ +export const removeLocalStorageItem = (key: string): boolean => { + if (!isBrowser() || !isLocalStorageAvailable()) return false; + + try { + localStorage.removeItem(key); + return true; + } catch (error) { + console.error('Error removing item from localStorage:', error); + return false; + } +}; \ No newline at end of file diff --git a/src/routes/Auth.tsx b/src/routes/Auth.tsx new file mode 100644 index 0000000..7af3a36 --- /dev/null +++ b/src/routes/Auth.tsx @@ -0,0 +1,44 @@ +import React, { useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Login } from '../components/auth/Login'; +import { useAuth } from '../context/AuthContext'; +import { errorToMessage } from '../lib/auth/types'; + +export const Auth: React.FC = () => { + const { session } = useAuth(); + const navigate = useNavigate(); + + // Redirect to home if already authenticated + useEffect(() => { + if (session.authed) { + navigate('/'); + } + }, [session.authed, navigate]); + + if (session.loading) { + return ( +
+
+

Loading authentication system...

+
+
+ ); + } + + if (session.error) { + return ( +
+
+

Authentication Error

+

{errorToMessage(session.error)}

+
+
+ ); + } + + return ( +
+ navigate('/')} /> +
+ ); +}; \ No newline at end of file diff --git a/src/ui/AuthDialog.tsx b/src/ui/AuthDialog.tsx new file mode 100644 index 0000000..bd343d3 --- /dev/null +++ b/src/ui/AuthDialog.tsx @@ -0,0 +1,123 @@ +import { + TLUiDialogProps, + TldrawUiButton, + TldrawUiButtonLabel, + TldrawUiDialogBody, + TldrawUiDialogCloseButton, + TldrawUiDialogFooter, + TldrawUiDialogHeader, + TldrawUiDialogTitle, + TldrawUiInput, + useDialogs + } from "tldraw" + import React, { useState, useEffect, useRef, FormEvent } from "react" + import { useAuth } from "../context/AuthContext" + + interface AuthDialogProps extends TLUiDialogProps { + autoFocus?: boolean + } + + export function AuthDialog({ onClose, autoFocus = false }: AuthDialogProps) { + const [username, setUsername] = useState('') + const [isRegistering, setIsRegistering] = useState(false) + const [error, setError] = useState(null) + const [isLoading, setIsLoading] = useState(false) + const { login, register } = useAuth() + const { removeDialog } = useDialogs() + const inputRef = useRef(null) + + useEffect(() => { + if (autoFocus && inputRef.current) { + setTimeout(() => { + inputRef.current?.focus() + }, 100) + } + }, [autoFocus]) + + const handleSubmit = async () => { + if (!username.trim()) { + setError('Username is required') + return + } + + setError(null) + setIsLoading(true) + + try { + let success = false + + if (isRegistering) { + success = await register(username) + } else { + success = await login(username) + } + + if (success) { + removeDialog("auth") + if (onClose) onClose() + } else { + setError(isRegistering ? 'Registration failed' : 'Login failed') + } + } catch (err) { + console.error('Authentication error:', err) + setError('An unexpected error occurred') + } finally { + setIsLoading(false) + } + } + + // Handle form submission (triggered by Enter key or submit button) + const handleFormSubmit = (e: FormEvent) => { + e.preventDefault() + handleSubmit() + } + + return ( + <> + + {isRegistering ? 'Create Account' : 'Sign In'} + + + +
+
+
+ + +
+ + {error &&
{error}
} + +
+ setIsRegistering(!isRegistering)} + disabled={isLoading} + > + + {isRegistering ? 'Already have an account?' : 'Need an account?'} + + + + + + {isLoading ? 'Processing...' : isRegistering ? 'Register' : 'Login'} + + +
+
+
+
+ + ) + } \ No newline at end of file diff --git a/src/ui/CustomToolbar.tsx b/src/ui/CustomToolbar.tsx index 8b98576..5ffa73e 100644 --- a/src/ui/CustomToolbar.tsx +++ b/src/ui/CustomToolbar.tsx @@ -5,6 +5,8 @@ import { useEditor } from "tldraw" import { useState, useEffect } from "react" import { useDialogs } from "tldraw" import { SettingsDialog } from "./SettingsDialog" +import { AuthDialog } from "./AuthDialog" +import { useAuth, clearSession } from "../context/AuthContext" export function CustomToolbar() { const editor = useEditor() @@ -12,6 +14,8 @@ export function CustomToolbar() { const [isReady, setIsReady] = useState(false) const [hasApiKey, setHasApiKey] = useState(false) const { addDialog, removeDialog } = useDialogs() + const { session, updateSession } = useAuth() + const [showProfilePopup, setShowProfilePopup] = useState(false) useEffect(() => { if (editor && tools) { @@ -51,6 +55,21 @@ export function CustomToolbar() { return () => clearInterval(interval) }, []) + const handleLogout = () => { + // Clear the session + clearSession() + + // Update the auth context + updateSession({ + username: '', + authed: false, + backupCreated: null, + }) + + // Close the popup + setShowProfilePopup(false) + } + if (!isReady) return null return ( @@ -107,6 +126,100 @@ export function CustomToolbar() { > Keys {hasApiKey ? "✅" : "❌"} + +
+ + + {showProfilePopup && session.authed && ( +
+
+ Hello, {session.username}! +
+ + {!session.backupCreated && ( +
+ Remember to back up your encryption keys to prevent data loss! +
+ )} + + +
+ )} +