cq

Distributed social media platform
git clone git://git.finwo.net/app/cq
Log | Files | Refs

commit 861cc9c86cfeab19e844cd24f3884a5d97c9f291
parent 746bda9041244de0127f438b78fd1a5328397952
Author: finwo <finwo@pm.me>
Date:   Sat, 13 Sep 2025 18:18:08 +0200

Creating account seed+name combo

Diffstat:
Mpackage-lock.json | 400+++++++++++++++++++++++++++++++------------------------------------------------
Mpackages/app/esbuild.js | 9+++++++++
Mpackages/app/package.json | 5++++-
Mpackages/app/src/component/app.tsx | 2+-
Apackages/app/src/component/screen-account-create.tsx | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apackages/app/src/component/screen-account-select.tsx | 32++++++++++++++++++++++++++++++++
Apackages/app/src/component/screen-home.tsx | 20++++++++++++++++++++
Mpackages/app/src/global.css | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/app/src/main.ts | 12+++++++++++-
Apackages/app/src/util/opfs.ts | 16++++++++++++++++
10 files changed, 419 insertions(+), 248 deletions(-)

diff --git a/package-lock.json b/package-lock.json @@ -454,6 +454,25 @@ "node": ">=18" } }, + "node_modules/@jspm/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@jspm/core/-/core-2.1.0.tgz", + "integrity": "sha512-3sRl+pkyFY/kLmHl0cgHiFp2xEqErA8N3ECjMs7serSUBmoJ70lBa0PG5t0IM6WJgdZNyyI0R8YFfi5wM8+mzg==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -505,53 +524,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/@types/mithril": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@types/mithril/-/mithril-2.2.7.tgz", + "integrity": "sha512-uetxoYizBMHPELl6DSZUfO6Q/aOm+h0NUCv9bVAX2iAxfrdBSOvU9KKFl+McTtxR13F+BReYLY814pJsZvnSxg==", "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": ">= 8" + "node": ">=0.4.0" } }, "node_modules/app": { "resolved": "packages/app", "link": true }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", + "node_modules/bip39": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz", + "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==", + "license": "ISC", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@noble/hashes": "^1.2.0" } }, "node_modules/braces": { @@ -567,35 +570,10 @@ "node": ">=8" } }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", "dev": true, "license": "MIT" }, @@ -637,24 +615,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/esbuild": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", @@ -697,6 +657,31 @@ "@esbuild/win32-x64": "0.25.9" } }, + "node_modules/esbuild-plugins-node-modules-polyfill": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/esbuild-plugins-node-modules-polyfill/-/esbuild-plugins-node-modules-polyfill-1.7.1.tgz", + "integrity": "sha512-IEaUhaS1RukGGcatDzqJmR+AzUWJ2upTJZP2i7FzR37Iw5Lk0LReCTnWnPMWnGG9lO4MWTGKEGGLWEOPegTRJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jspm/core": "^2.1.0", + "local-pkg": "^1.1.1", + "resolve.exports": "^2.0.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.14.0 <=0.25.x" + } + }, + "node_modules/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -737,21 +722,6 @@ "node": ">=8" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -793,16 +763,6 @@ "dev": true, "license": "ISC" }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/ignore": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", @@ -813,26 +773,6 @@ "node": ">= 4" } }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -879,6 +819,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -903,69 +861,42 @@ "node": ">=8.6" } }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/mithril": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/mithril/-/mithril-2.3.7.tgz", "integrity": "sha512-igRr/o79tNDTL2wa4ta16V7B0A0KdtjPcRVBdw2BMHF0c08xpCk4GxN+Ws04NYq2kGDBA0En1QHF/dgCw1oIIw==", "license": "MIT" }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nodemon": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", - "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", "dev": true, "license": "MIT", "dependencies": { - "chokidar": "^3.5.2", - "debug": "^4", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=0.10.0" + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" } }, "node_modules/p-event": { @@ -1039,6 +970,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -1052,11 +990,33 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], "license": "MIT" }, "node_modules/queue-microtask": { @@ -1080,17 +1040,14 @@ ], "license": "MIT" }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", "dev": true, "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, "engines": { - "node": ">=8.10.0" + "node": ">=10" } }, "node_modules/reusify": { @@ -1128,32 +1085,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/slash": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", @@ -1167,19 +1098,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -1193,20 +1111,10 @@ "node": ">=8.0" } }, - "node_modules/touch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", - "dev": true, - "license": "ISC", - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", "dev": true, "license": "MIT" }, @@ -1227,12 +1135,14 @@ "version": "0.0.1", "license": "MIT", "dependencies": { + "bip39": "^3.1.0", "mithril": "^2.3.7" }, "devDependencies": { + "@types/mithril": "^2.2.7", "cpy": "^12.0.1", "esbuild": "^0.25.9", - "nodemon": "^3.1.10" + "esbuild-plugins-node-modules-polyfill": "^1.7.1" } } } diff --git a/packages/app/esbuild.js b/packages/app/esbuild.js @@ -5,6 +5,7 @@ const fs = require('node:fs'); const { dirname } = require('node:path'); const { fileURLToPath } = require('node:url'); const esbuild = require('esbuild'); +const { nodeModulesPolyfillPlugin } = require('esbuild-plugins-node-modules-polyfill'); // const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -35,6 +36,14 @@ const config = { '.woff' : 'dataurl', '.woff2': 'dataurl', }, + + plugins: [ + nodeModulesPolyfillPlugin({ + globals: { + Buffer: true + } + }) + ], }; const buildList = []; diff --git a/packages/app/package.json b/packages/app/package.json @@ -14,10 +14,13 @@ "license": "MIT", "description": "", "devDependencies": { + "@types/mithril": "^2.2.7", "cpy": "^12.0.1", - "esbuild": "^0.25.9" + "esbuild": "^0.25.9", + "esbuild-plugins-node-modules-polyfill": "^1.7.1" }, "dependencies": { + "bip39": "^3.1.0", "mithril": "^2.3.7" } } diff --git a/packages/app/src/component/app.tsx b/packages/app/src/component/app.tsx @@ -1,5 +1,5 @@ export default { - view: function() { + view() { return ( <div> Hello World diff --git a/packages/app/src/component/screen-account-create.tsx b/packages/app/src/component/screen-account-create.tsx @@ -0,0 +1,115 @@ +import { generateMnemonic } from 'bip39'; +import { Vnode } from 'mithril'; + +import { getDirectoryHandle, getFileHandle } from '../util/opfs'; + +type _Vnode = Vnode<{}, { + accountId : typeof AccountCreateScreen['accountId' ], + accountName : typeof AccountCreateScreen['accountName'], + seedPhrase : typeof AccountCreateScreen['seedPhrase' ], + step : typeof AccountCreateScreen['step' ], + steps : typeof AccountCreateScreen['steps' ], + handler : typeof AccountCreateScreen['handler' ], +}>; + +export const AccountCreateScreen = { + routePath: '/account/create', + + accountId : '', + accountName: '', + seedPhrase : '', + + handler: { + setAccountName(vnode: _Vnode) { + return () => { + const el = document.getElementById('accountName') as HTMLInputElement; + vnode.state.accountName = el.value; + vnode.state.step++; + }; + }, + goto: (vnode: _Vnode, step) => { + return () => vnode.state.step = step; + }, + confirmPhrase: (vnode: _Vnode) => { + return async () => { + const el = document.getElementById('phraseConfirm') as HTMLTextAreaElement; + if (el.value !== vnode.state.seedPhrase) { + alert("Phrase incorrect"); + return; + } + vnode.state.step++; + + const accountSeedHandle = await getFileHandle(`/accounts/${vnode.state.accountId}/seed.txt`, { create: true }) + const accountSeedWritable = await accountSeedHandle.createWritable(); + await accountSeedWritable.write(vnode.state.seedPhrase); + await accountSeedWritable.close(); + + const accountConfigHandle = await getFileHandle(`/accounts/${vnode.state.accountId}/config.json`, { create: true }) + const accountConfigWritable = await accountConfigHandle.createWritable(); + await accountConfigWritable.write(JSON.stringify({ + name: vnode.state.accountName + }, null, 2)); + await accountConfigWritable.close(); + } + }, + taGrow() { + const el = this as HTMLTextAreaElement; + el.style.height = (Math.max(el.scrollHeight, 38)+2)+'px'; + } + }, + + step: 0, + steps: [ + (vnode: _Vnode) => ( + <div class="main"> + <p> + Give your new account a name you'll recognize (not public) + </p> + <input id="accountName" value={vnode.state.accountName} style="display: block; background: transparent; padding: 0.5em 0px; color: inherit; border-width: medium medium 2px; border-style: none none solid; border-color: currentcolor currentcolor rgb(255, 255, 255); border-image: none;width: calc(100% - 2rem);" type="text"/> + <button onclick={vnode.state.handler.setAccountName(vnode)}>Next</button> + </div> + ), + (vnode: _Vnode) => ( + <div class="main"> + <p> + Here's your new account's seed phrase. Please write it down and store it securely: + </p> + <pre style="white-space:break-spaces;"> + {vnode.state.seedPhrase} + </pre> + <button onclick={vnode.state.handler.goto(vnode, 0)}>Back</button> + <button onclick={vnode.state.handler.goto(vnode, 2)}>Next</button> + </div> + ), + (vnode: _Vnode) => ( + <div class="main"> + <p> + Please repeat the seed phrase to confirm you got it stored: + </p> + <textarea id="phraseConfirm" style="display:block;width:calc(100% - 2rem);background:transparent;padding:0.5em 0;color:inherit;border:none;border-bottom:2px solid #FFF;height:40px;" onkeyup={vnode.state.handler.taGrow} onkeydown={vnode.state.handler.taGrow}></textarea> + <button onclick={vnode.state.handler.goto(vnode, 1)}>Back</button> + <button onclick={vnode.state.handler.confirmPhrase(vnode)}>Next</button> + </div> + ), + (vnode: _Vnode) => ( + <div class="main"> + Building account... + </div> + ) + ], + + async oninit(vnode: _Vnode) { + vnode.state.seedPhrase = generateMnemonic(); + + vnode.state.accountId = ''; + while(vnode.state.accountId.length < 32) { + vnode.state.accountId += Math.random().toString(36).slice(2); + } + vnode.state.accountId = vnode.state.accountId.slice(0,32); + }, + + view(vnode: _Vnode) { + return vnode.state.steps[vnode.state.step](vnode); + }, + +}; diff --git a/packages/app/src/component/screen-account-select.tsx b/packages/app/src/component/screen-account-select.tsx @@ -0,0 +1,32 @@ +import { AccountCreateScreen } from './screen-account-create.tsx'; + +import { getDirectoryHandle } from '../util/opfs'; + +export const AccountSelectScreen = { + routePath: '/account/select', + + accounts: [], + + async oninit(vnode) { + vnode.state.accounts = []; + + const accountsHandle = await getDirectoryHandle('/accounts'); + for await (const [name, handle] of accountsHandle.entries()) { + vnode.state.accounts.push(name); + } + + if (!vnode.state.accounts.length) { + document.location.href = `#!${AccountCreateScreen.routePath}`; + return; + } + + }, + + view() { + return ( + <div> + ACCOUNT SELECT + </div> + ); + }, +}; diff --git a/packages/app/src/component/screen-home.tsx b/packages/app/src/component/screen-home.tsx @@ -0,0 +1,20 @@ +import { AccountSelectScreen } from "./screen-account-select"; + +export default { + oninit() { + + // Redirect to account selection if not there + if (!localStorage.selectedAccount) { + document.location.href = `#!${AccountSelectScreen.routePath}`; + return; + } + + console.log("INIT HOME"); + }, + + view() { + return ( + <a href="#!/app">Enter!</a> + ); + }, +}; diff --git a/packages/app/src/global.css b/packages/app/src/global.css @@ -2,4 +2,60 @@ background: #000000; color: #FFFFFF; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + font-size: x-large; +} + +* { + box-sizing: border-box; + font-size: inherit; +} + +body { + margin: 0; + padding: 0; +} + +h1 { font-size: 2.0rem; } +h2 { font-size: 1.6rem; } +h3 { font-size: 1.2rem; } +h4 { font-size: 1.0rem; } +h5 { font-size: 0.8rem; } +h6 { font-size: 0.6rem; } + +h1, h2, h3, h4, h5, h6, legend, label { + font-weight: 500; +} + +a[href] { + color: inherit; + text-decoration: underline; +} + + +input:focus, +select:focus, +textarea:focus, +button:focus { + outline: none; +} + +button { + border: 2px solid #FFFFFF; + background: #FFFFFF; + color: #000000; + margin: 1em; + padding: 0.25em 0.5em; + cursor: pointer; + &:active { + background: #000000; + color: #FFFFFF; + } +} + +.main { + max-width: 640px; + margin: 0 auto; + > * { + margin: 1rem; + } } diff --git a/packages/app/src/main.ts b/packages/app/src/main.ts @@ -1,2 +1,12 @@ globalThis.m = require('mithril'); -m.mount(document.body, require('./component/app.tsx').default); + +import { AccountCreateScreen } from './component/screen-account-create.tsx'; +import { AccountSelectScreen } from './component/screen-account-select.tsx'; + + +m.route(document.body, "/", { + "/" : require('./component/screen-home.tsx' ).default, + "/app" : require('./component/app.tsx' ).default, + [AccountSelectScreen.routePath]: AccountSelectScreen, + [AccountCreateScreen.routePath]: AccountCreateScreen, +}); diff --git a/packages/app/src/util/opfs.ts b/packages/app/src/util/opfs.ts @@ -0,0 +1,16 @@ +export async function getDirectoryHandle(path: string) { + const tokens = path.split('/') as string[]; + if (tokens[0] == '') tokens.shift(); + let reference = await navigator.storage.getDirectory(); + while(tokens.length) reference = await reference.getDirectoryHandle(tokens.shift() as string, { create: true }) + return reference; +} + +export async function getFileHandle(path: string, options?: FileSystemGetFileOptions) { + const tokens = path.split('/') as string[]; + if (tokens[0] == '') tokens.shift(); + const filename = tokens.pop() as string; + let reference = await navigator.storage.getDirectory(); + while(tokens.length) reference = await reference.getDirectoryHandle(tokens.shift() as string, { create: true }) + return await reference.getFileHandle(filename, options); +}