diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..6b61434 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +VITE_MOST_POPULAR_RELAYS=wss://relay.damus.io wss://eden.nostr.land wss://nos.lol wss://relay.snort.social wss://relay.current.fyi wss://brb.io wss://nostr.orangepill.dev wss://nostr-pub.wellorder.net wss://nostr.bitcoiner.social wss://nostr.wine wss://nostr.oxtr.dev wss://relay.nostr.bg wss://nostr.mom wss://nostr.fmt.wiz.biz wss://relay.nostr.band wss://nostr-pub.semisol.dev wss://nostr.milou.lol wss://puravida.nostr.land wss://nostr.onsats.org wss://relay.nostr.info wss://offchain.pub wss://relay.orangepill.dev wss://no.str.cr wss://atlas.nostr.land wss://nostr.zebedee.cloud wss://nostr-relay.wlvs.space wss://relay.nostrati.com wss://relay.nostr.com.au wss://nostr.inosta.cc wss://nostr.rocks \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs index d6c9537..adc902d 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -4,7 +4,7 @@ module.exports = { extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', - 'plugin:react-hooks/recommended', + 'plugin:react-hooks/recommended' ], ignorePatterns: ['dist', '.eslintrc.cjs'], parser: '@typescript-eslint/parser', @@ -12,7 +12,8 @@ module.exports = { rules: { 'react-refresh/only-export-components': [ 'warn', - { allowConstantExport: true }, + { allowConstantExport: true } ], - }, + '@typescript-eslint/no-explicit-any': 'warn' + } } diff --git a/package-lock.json b/package-lock.json index 0ec8f3e..3c60c20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,24 @@ "name": "web", "version": "0.0.0", "dependencies": { + "@emotion/react": "11.11.4", + "@emotion/styled": "11.11.0", + "@mui/icons-material": "5.15.11", + "@mui/material": "5.15.11", + "@nostr-dev-kit/ndk": "2.5.0", + "@reduxjs/toolkit": "2.2.1", + "axios": "1.6.7", + "lodash": "4.17.21", + "nostr-tools": "2.3.1", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-redux": "9.1.0", + "react-router-dom": "6.22.1", + "react-toastify": "10.0.4", + "redux": "5.0.1" }, "devDependencies": { + "@types/lodash": "4.14.202", "@types/react": "^18.2.56", "@types/react-dom": "^18.2.19", "@typescript-eslint/eslint-plugin": "^7.0.2", @@ -51,7 +65,6 @@ "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", - "dev": true, "dependencies": { "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" @@ -186,7 +199,6 @@ "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", - "dev": true, "dependencies": { "@babel/types": "^7.22.15" }, @@ -250,7 +262,6 @@ "version": "7.23.4", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -259,7 +270,6 @@ "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -291,7 +301,6 @@ "version": "7.23.4", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", - "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", @@ -343,6 +352,17 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", + "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.23.9", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", @@ -382,7 +402,6 @@ "version": "7.23.9", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", - "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", @@ -414,6 +433,155 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", + "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/serialize": "^1.1.2", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emotion/cache": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", + "dependencies": { + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/react": { + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz", + "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/cache": "^11.11.0", + "@emotion/serialize": "^1.1.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.3.tgz", + "integrity": "sha512-iD4D6QVZFDhcbH0RAG1uVu1CwVLMWUkCvAqqlewO/rxf8+87yIBAlt4+AxMiiKPLs5hFc0owNk/sLLAOROw3cA==", + "dependencies": { + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/unitless": "^0.8.1", + "@emotion/utils": "^1.2.1", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" + }, + "node_modules/@emotion/styled": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz", + "integrity": "sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/is-prop-valid": "^1.2.1", + "@emotion/serialize": "^1.1.2", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", + "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.19.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", @@ -875,6 +1043,40 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", + "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", + "dependencies": { + "@floating-ui/utils": "^0.2.1" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", + "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", + "dependencies": { + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", + "dependencies": { + "@floating-ui/dom": "^1.6.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -978,6 +1180,303 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mui/base": { + "version": "5.0.0-beta.37", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.37.tgz", + "integrity": "sha512-/o3anbb+DeCng8jNsd3704XtmmLDZju1Fo8R2o7ugrVtPQ/QpcqddwKNzKPZwa0J5T8YNW3ZVuHyQgbTnQLisQ==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@floating-ui/react-dom": "^2.0.8", + "@mui/types": "^7.2.13", + "@mui/utils": "^5.15.11", + "@popperjs/core": "^2.11.8", + "clsx": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "5.15.11", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.11.tgz", + "integrity": "sha512-JVrJ9Jo4gyU707ujnRzmE8ABBWpXd6FwL9GYULmwZRtfPg89ggXs/S3MStQkpJ1JRWfdLL6S5syXmgQGq5EDAw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "5.15.11", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.11.tgz", + "integrity": "sha512-R5ZoQqnKpd+5Ew7mBygTFLxgYsQHPhgR3TDXSgIHYIjGzYuyPLmGLSdcPUoMdi6kxiYqHlpPj4NJxlbaFD0UHA==", + "dependencies": { + "@babel/runtime": "^7.23.9" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "5.15.11", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.11.tgz", + "integrity": "sha512-FA3eEuEZaDaxgN3CgfXezMWbCZ4VCeU/sv0F0/PK5n42qIgsPVD6q+j71qS7/62sp6wRFMHtDMpXRlN+tT/7NA==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/base": "5.0.0-beta.37", + "@mui/core-downloads-tracker": "^5.15.11", + "@mui/system": "^5.15.11", + "@mui/types": "^7.2.13", + "@mui/utils": "^5.15.11", + "@types/react-transition-group": "^4.4.10", + "clsx": "^2.1.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^18.2.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "5.15.11", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.11.tgz", + "integrity": "sha512-jY/696SnSxSzO1u86Thym7ky5T9CgfidU3NFJjguldqK4f3Z5S97amZ6nffg8gTD0HBjY9scB+4ekqDEUmxZOA==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/utils": "^5.15.11", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "5.15.11", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.11.tgz", + "integrity": "sha512-So21AhAngqo07ces4S/JpX5UaMU2RHXpEA6hNzI6IQjd/1usMPxpgK8wkGgTe3JKmC2KDmH8cvoycq5H3Ii7/w==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@emotion/cache": "^11.11.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "5.15.11", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.11.tgz", + "integrity": "sha512-9j35suLFq+MgJo5ktVSHPbkjDLRMBCV17NMBdEQurh6oWyGnLM4uhU4QGZZQ75o0vuhjJghOCA1jkO3+79wKsA==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/private-theming": "^5.15.11", + "@mui/styled-engine": "^5.15.11", + "@mui/types": "^7.2.13", + "@mui/utils": "^5.15.11", + "clsx": "^2.1.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.13", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.13.tgz", + "integrity": "sha512-qP9OgacN62s+l8rdDhSFRe05HWtLLJ5TGclC9I1+tQngbssu0m2dmFZs+Px53AcOs9fD7TbYd4gc9AXzVqO/+g==", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "5.15.11", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.11.tgz", + "integrity": "sha512-D6bwqprUa9Stf8ft0dcMqWyWDKEo7D+6pB1k8WajbqlYIRA8J8Kw9Ra7PSZKKePGBGWO+/xxrX1U8HpG/aXQCw==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@types/prop-types": "^15.7.11", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@noble/ciphers": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.5.1.tgz", + "integrity": "sha512-aNE06lbe36ifvMbbWvmmF/8jx6EQPu2HVg70V95T+iGjOuYwPpAccwAQc2HlXO2D0aiQ3zavbMga4jjWnrpiPA==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/secp256k1": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-2.0.0.tgz", + "integrity": "sha512-rUGBd95e2a45rlmFTqQJYEFA4/gdIARFfuTuTqLglz0PZ6AKyzyXsEZZq7UZn8hZsvaBgpCzKKBJizT2cJERXw==", + "funding": [ + { + "type": "individual", + "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", @@ -1013,6 +1512,104 @@ "node": ">= 8" } }, + "node_modules/@nostr-dev-kit/ndk": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-2.5.0.tgz", + "integrity": "sha512-A2nRgjjLScDhGZGPWx8xUIJM66dJWScdWQoCn/tI1Gtwpple+C2Jp7C9t3mb0oF3bwd2nsV6qwS//wdrH8QvYQ==", + "dependencies": { + "@noble/hashes": "^1.3.1", + "@noble/secp256k1": "^2.0.0", + "@scure/base": "^1.1.1", + "debug": "^4.3.4", + "light-bolt11-decoder": "^3.0.0", + "node-fetch": "^3.3.1", + "nostr-tools": "^1.15.0", + "tseep": "^1.1.1", + "typescript-lru-cache": "^2.0.0", + "utf8-buffer": "^1.0.0", + "websocket-polyfill": "^0.0.3" + } + }, + "node_modules/@nostr-dev-kit/ndk/node_modules/@noble/ciphers": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.2.0.tgz", + "integrity": "sha512-6YBxJDAapHSdd3bLDv6x2wRPwq4QFMUaB3HvljNBUTThDd12eSm7/3F+2lnfzx2jvM+S6Nsy0jEt9QbPqSwqRw==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nostr-dev-kit/ndk/node_modules/@noble/curves": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", + "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "dependencies": { + "@noble/hashes": "1.3.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nostr-dev-kit/ndk/node_modules/nostr-tools": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-1.17.0.tgz", + "integrity": "sha512-LZmR8GEWKZeElbFV5Xte75dOeE9EFUW/QLI1Ncn3JKn0kFddDKEfBbFN8Mu4TMs+L4HR/WTPha2l+PPuRnJcMw==", + "dependencies": { + "@noble/ciphers": "0.2.0", + "@noble/curves": "1.1.0", + "@noble/hashes": "1.3.1", + "@scure/base": "1.1.1", + "@scure/bip32": "1.3.1", + "@scure/bip39": "1.2.1" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.1.tgz", + "integrity": "sha512-8CREoqJovQW/5I4yvvijm/emUiCCmcs4Ev4XPWd4mizSO+dD3g5G6w34QK5AGeNrSH7qM8Fl66j4vuV7dpOdkw==", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.0.1" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@remix-run/router": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.1.tgz", + "integrity": "sha512-zcU0gM3z+3iqj8UX45AmWY810l3oUmXM7uH4dt5xtzvMhRtYVhKGOmgOd1877dOPPepfCjUv57w+syamWIYe7w==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", @@ -1182,6 +1779,53 @@ "win32" ] }, + "node_modules/@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@scure/bip32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.1.tgz", + "integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==", + "dependencies": { + "@noble/curves": "~1.1.0", + "@noble/hashes": "~1.3.1", + "@scure/base": "~1.1.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/curves": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", + "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "dependencies": { + "@noble/hashes": "1.3.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz", + "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", + "dependencies": { + "@noble/hashes": "~1.3.0", + "@scure/base": "~1.1.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -1259,6 +1903,12 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.14.202", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", + "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==", + "dev": true + }, "node_modules/@types/node": { "version": "20.11.20", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.20.tgz", @@ -1269,17 +1919,20 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, "node_modules/@types/prop-types": { "version": "15.7.11", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", - "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", - "dev": true + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" }, "node_modules/@types/react": { "version": "18.2.60", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.60.tgz", "integrity": "sha512-dfiPj9+k20jJrLGOu9Nf6eqxm2EyJRrq2NvwOFsfbb7sFExZ9WELPs67UImHj3Ayxg8ruTtKtNnbjaF8olPq0A==", - "dev": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -1295,11 +1948,18 @@ "@types/react": "*" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/scheduler": { "version": "0.16.8", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", - "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", - "dev": true + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" }, "node_modules/@types/semver": { "version": "7.5.8", @@ -1307,6 +1967,11 @@ "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.1.0.tgz", @@ -1581,7 +2246,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -1623,6 +2287,35 @@ "node": ">=8" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "dependencies": { + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1691,11 +2384,22 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bufferutil": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz", + "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -1733,7 +2437,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -1779,11 +2482,18 @@ "node": ">= 6" } }, + "node_modules/clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -1791,8 +2501,18 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } }, "node_modules/concat-map": { "version": "0.0.1", @@ -1806,6 +2526,21 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -1829,14 +2564,29 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dependencies": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -1855,6 +2605,14 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -1888,12 +2646,63 @@ "node": ">=6.0.0" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.682", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.682.tgz", "integrity": "sha512-oCglfs8yYKs9RQjJFOHonSnhikPK3y+0SvSYc/YpYJV//6rqc0/hbwd0c7vgK4vrl6y2gJAwjkhkSGWK+z4KRA==", "dev": true }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es5-ext": { + "version": "0.10.63", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.63.tgz", + "integrity": "sha512-hUCZd2Byj/mNKjfP9jXrdVZ62B8KuA/VoK7X8nUh5qT+AxDmcbvZz041oDVZdbIN1qW6XY9VDNwzkvKnZvK2TQ==", + "hasInstallScript": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dependencies": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, "node_modules/esbuild": { "version": "0.19.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", @@ -1945,7 +2754,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, "engines": { "node": ">=0.8.0" } @@ -2173,6 +2981,25 @@ "node": ">=8" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esniff/node_modules/type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -2232,6 +3059,28 @@ "node": ">=0.10.0" } }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/ext/node_modules/type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2287,6 +3136,28 @@ "reusify": "^1.0.4" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -2311,6 +3182,11 @@ "node": ">=8" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2347,6 +3223,49 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2367,6 +3286,14 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2469,11 +3396,34 @@ "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, "engines": { "node": ">=4" } }, + "node_modules/hasown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -2483,6 +3433,15 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.3.tgz", + "integrity": "sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/immutable": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", @@ -2493,7 +3452,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -2530,6 +3488,11 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -2542,6 +3505,17 @@ "node": ">=8" } }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2581,6 +3555,11 @@ "node": ">=8" } }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2622,6 +3601,11 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2668,6 +3652,19 @@ "node": ">= 0.8.0" } }, + "node_modules/light-bolt11-decoder": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/light-bolt11-decoder/-/light-bolt11-decoder-3.0.0.tgz", + "integrity": "sha512-AKvOigD2pmC8ktnn2TIqdJu0K0qk6ukUmTvHwF3JNkm8uWCqt18Ijn33A/a7gaRZ4PghJ59X+8+MXrzLKdBTmQ==", + "dependencies": { + "@scure/base": "1.1.1" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2683,6 +3680,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -2737,6 +3739,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", @@ -2755,8 +3776,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/nanoid": { "version": "3.3.7", @@ -2782,6 +3802,56 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", + "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -2797,6 +3867,44 @@ "node": ">=0.10.0" } }, + "node_modules/nostr-tools": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.3.1.tgz", + "integrity": "sha512-qjKx2C3EzwiQOe2LPSPyCnp07pGz1pWaWjDXcm+L2y2c8iTECbvlzujDANm3nJUjWL5+LVRUVDovTZ1a/DC4Bg==", + "dependencies": { + "@noble/ciphers": "^0.5.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.1", + "@scure/base": "1.1.1", + "@scure/bip32": "1.3.1", + "@scure/bip39": "1.2.1" + }, + "optionalDependencies": { + "nostr-wasm": "v0.1.0" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/nostr-wasm": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/nostr-wasm/-/nostr-wasm-0.1.0.tgz", + "integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==", + "optional": true + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2857,7 +3965,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -2865,6 +3972,23 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2892,11 +4016,15 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, "engines": { "node": ">=8" } @@ -2975,6 +4103,26 @@ "node": ">= 0.8.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3027,6 +4175,37 @@ "react": "^18.2.0" } }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/react-redux": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.0.tgz", + "integrity": "sha512-6qoDzIO+gbrza8h3hjMA9aq4nwVFCKFtY2iLxCtVT38Swyy2C/dJCGBXHeHLtx6qlg/8qzc2MrhOeduf5K32wQ==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "react-native": ">=0.69", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -3036,6 +4215,63 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.22.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.1.tgz", + "integrity": "sha512-0pdoRGwLtemnJqn1K0XHUbnKiX0S4X8CgvVVmHGOWmofESj31msHo/1YiqcJWK7Wxfq2a4uvvtS01KAQyWK/CQ==", + "dependencies": { + "@remix-run/router": "1.15.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.22.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.1.tgz", + "integrity": "sha512-iwMyyyrbL7zkKY7MRjOVRy+TMnS/OPusaFVxM2P11x9dzSzGmLsebkCvYirGq0DWB9K9hOspHYYtDz33gE5Duw==", + "dependencies": { + "@remix-run/router": "1.15.1", + "react-router": "6.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-toastify": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.4.tgz", + "integrity": "sha512-etR3RgueY8pe88SA67wLm8rJmL1h+CLqUGHuAoNsseW35oTGJEri6eBTyaXnFKNQ80v/eO10hBYLgz036XRGgA==", + "dependencies": { + "clsx": "^2.1.0" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -3048,11 +4284,49 @@ "node": ">=8.10.0" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/reselect": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.0.tgz", + "integrity": "sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg==" + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -3225,6 +4499,14 @@ "node": ">=8" } }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", @@ -3258,11 +4540,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, "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, "dependencies": { "has-flag": "^3.0.0" }, @@ -3270,6 +4556,17 @@ "node": ">=4" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -3280,7 +4577,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, "engines": { "node": ">=4" } @@ -3363,6 +4659,21 @@ } } }, + "node_modules/tseep": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/tseep/-/tseep-1.2.1.tgz", + "integrity": "sha512-VFnsNcPGC4qFJ1nxbIPSjTmtRZOhlqLmtwRqtLVos8mbRHki8HO9cy9Z1e89EiWyxFmq6LBviI9TQjijxw/mEw==" + }, + "node_modules/tstl": { + "version": "2.5.13", + "resolved": "https://registry.npmjs.org/tstl/-/tstl-2.5.13.tgz", + "integrity": "sha512-h9wayHHFI5+yqt8iau0vqH96cTNhezhZ/Fk/hrIdpfkiMu3lg9nzyvMfs5bIdX51IVzZO6DudLqhkL/rVXpT6g==" + }, + "node_modules/type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3387,11 +4698,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, "node_modules/typescript": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3400,6 +4719,11 @@ "node": ">=14.17" } }, + "node_modules/typescript-lru-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/typescript-lru-cache/-/typescript-lru-cache-2.0.0.tgz", + "integrity": "sha512-Jp57Qyy8wXeMkdNuZiglE6v2Cypg13eDA1chHwDG6kq51X7gk4K7P7HaDdzZKCxkegXkVHNcPD0n5aW6OZH3aA==" + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -3446,6 +4770,34 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/utf8-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utf8-buffer/-/utf8-buffer-1.0.0.tgz", + "integrity": "sha512-ueuhzvWnp5JU5CiGSY4WdKbiN/PO2AZ/lpeLiz2l38qwdLy/cW40XobgyuIWucNyum0B33bVB0owjFCeGBSLqg==", + "engines": { + "node": ">=8" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -3507,6 +4859,52 @@ } } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/websocket": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", + "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", + "dependencies": { + "bufferutil": "^4.0.1", + "debug": "^2.2.0", + "es5-ext": "^0.10.50", + "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", + "yaeti": "^0.0.6" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/websocket-polyfill": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/websocket-polyfill/-/websocket-polyfill-0.0.3.tgz", + "integrity": "sha512-pF3kR8Uaoau78MpUmFfzbIRxXj9PeQrCuPepGE6JIsfsJ/o/iXr07Q2iQNzKSSblQJ0FiGWlS64N4pVSm+O3Dg==", + "dependencies": { + "tstl": "^2.0.7", + "websocket": "^1.0.28" + } + }, + "node_modules/websocket/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/websocket/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3528,12 +4926,28 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", + "engines": { + "node": ">=0.10.32" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 5d30442..77acffb 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,24 @@ "preview": "vite preview" }, "dependencies": { + "@emotion/react": "11.11.4", + "@emotion/styled": "11.11.0", + "@mui/icons-material": "5.15.11", + "@mui/material": "5.15.11", + "@nostr-dev-kit/ndk": "2.5.0", + "@reduxjs/toolkit": "2.2.1", + "axios": "1.6.7", + "lodash": "4.17.21", + "nostr-tools": "2.3.1", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-redux": "9.1.0", + "react-router-dom": "6.22.1", + "react-toastify": "10.0.4", + "redux": "5.0.1" }, "devDependencies": { + "@types/lodash": "4.14.202", "@types/react": "^18.2.56", "@types/react-dom": "^18.2.19", "@typescript-eslint/eslint-plugin": "^7.0.2", diff --git a/src/App.tsx b/src/App.tsx index afe48ac..6581758 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,34 +1,73 @@ -import { useState } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from '/vite.svg' -import './App.css' +import { useEffect } from 'react' +import { useSelector } from 'react-redux' +import { Navigate, Route, Routes } from 'react-router-dom' +import { NostrController } from './controllers' +import { appPrivateRoutes, privateRoutes, publicRoutes } from './routes' +import { State } from './store/rootReducer' +import { getNsecBunkerDelegatedKey, saveNsecBunkerDelegatedKey } from './utils' +import { MainLayout } from './layouts/Main' +import { LandingPage } from './pages/landing/LandingPage' -function App() { - const [count, setCount] = useState(0) +const App = () => { + const authState = useSelector((state: State) => state.auth) + + useEffect(() => { + generateBunkerDelegatedKey() + }, []) + + const generateBunkerDelegatedKey = () => { + const existingKey = getNsecBunkerDelegatedKey() + + if (!existingKey) { + const nostrController = NostrController.getInstance() + const newDelegatedKey = nostrController.generateDelegatedKey() + + saveNsecBunkerDelegatedKey(newDelegatedKey) + } + } return ( - <> -
- - Vite logo - - - React logo - -
-

Vite + React

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

- + + }> + {authState?.loggedIn && ( + } + /> + )} + {authState?.loggedIn && + privateRoutes.map((route, index) => ( + + ))} + {publicRoutes.map((route, index) => { + if (authState?.loggedIn) { + if (!route.hiddenWhenLoggedIn) { + return ( + + ) + } + } else { + return ( + + ) + } + })} + {!authState || + (!authState.loggedIn && } />)} + + ) } diff --git a/src/assets/avenir-font/AvenirLTStd-Black.otf b/src/assets/avenir-font/AvenirLTStd-Black.otf new file mode 100644 index 0000000..714fdfb Binary files /dev/null and b/src/assets/avenir-font/AvenirLTStd-Black.otf differ diff --git a/src/assets/avenir-font/AvenirLTStd-Book.otf b/src/assets/avenir-font/AvenirLTStd-Book.otf new file mode 100644 index 0000000..52ab53e Binary files /dev/null and b/src/assets/avenir-font/AvenirLTStd-Book.otf differ diff --git a/src/assets/avenir-font/AvenirLTStd-Roman.otf b/src/assets/avenir-font/AvenirLTStd-Roman.otf new file mode 100644 index 0000000..de238e6 Binary files /dev/null and b/src/assets/avenir-font/AvenirLTStd-Roman.otf differ diff --git a/src/assets/images/avatar.png b/src/assets/images/avatar.png new file mode 100644 index 0000000..9d00060 Binary files /dev/null and b/src/assets/images/avatar.png differ diff --git a/src/assets/images/nostr-logo.jpg b/src/assets/images/nostr-logo.jpg new file mode 100644 index 0000000..98b4623 Binary files /dev/null and b/src/assets/images/nostr-logo.jpg differ diff --git a/src/assets/react.svg b/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/colors.scss b/src/colors.scss new file mode 100644 index 0000000..d00f37b --- /dev/null +++ b/src/colors.scss @@ -0,0 +1,50 @@ +$primary-main: var(--mui-palette-primary-main); +$primary-light: var(--mui-palette-primary-light); +$primary-dark: var(--mui-palette-primary-dark); + +$box-shadow-color: rgba(0, 0, 0, 0.1); +$border-color: #27323c; + +$page-background-color: #f4f4fb; +$modal-input-background: #f4f4fb; +$background-color: #fff; + +$text-color: #3e3e3e; +$input-text-color: #717171; +$info-text-color: #4169e1; + +$icon-color: #c1c1c1; +$zap-icon-color: #ffd700; + +$btn-background-color: $primary-main; +$btn-color: #fff; + +$progress-box-background-color: $primary-main; +$progress-box-inner-background-color: $primary-dark; +$rank-progress-background-color: #445c76; + +$correct-answer-background-color: $primary-light; +$incorrect-answer-background-color: #e84d67; +$incorrect-answer-label-color: #fff; + +$checkbox-border-color: #ccc; +$checkbox-hover-border-color: #888; +$checkbox-checked-background-color: $primary-main; +$checkbox-checked-color: #fff; + +$comment-notice-background-color: #0d6efd; +$comment-notice-color: #fff; +$comment-divider-color: #eee; + +$activated-topic-background-color: var(--mui-palette-info-dark); + +$error-msg-color: red; +$suggestion-box-heading-color: #dfe0e0; + +$callOut-success-background: $primary-light; +$callOut-success-color: $primary-dark; + +$review-feedback-correct: #178b13; +$review-feedback-incorrect: #d82222; +$review-feedback-neutral: #f39220; +$review-feedback-selected-color: #fff; diff --git a/src/components/AppBar/AppBar.tsx b/src/components/AppBar/AppBar.tsx new file mode 100644 index 0000000..5e95007 --- /dev/null +++ b/src/components/AppBar/AppBar.tsx @@ -0,0 +1,155 @@ +import { + AppBar as AppBarMui, + Box, + Button, + Menu, + MenuItem, + Toolbar, + Typography +} from '@mui/material' +import { useEffect, useState } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { setAuthState } from '../../store/actions' +import { State } from '../../store/rootReducer' +import { Dispatch } from '../../store/store' +import Username from '../username' + +import { Link, useNavigate } from 'react-router-dom' +import nostrichAvatar from '../../assets/images/avatar.png' +import nostrichLogo from '../../assets/images/nostr-logo.jpg' +import { appPublicRoutes } from '../../routes' +import { shorten } from '../../utils' +import styles from './style.module.scss' + +export const AppBar = () => { + const navigate = useNavigate() + const dispatch: Dispatch = useDispatch() + + const [username, setUsername] = useState('') + const [userAvatar, setUserAvatar] = useState(nostrichAvatar) + const [anchorElUser, setAnchorElUser] = useState(null) + + const authState = useSelector((state: State) => state.auth) + const metadataState = useSelector((state: State) => state.metadata) + + useEffect(() => { + if (metadataState && metadataState.content) { + const { picture, display_name, name } = JSON.parse(metadataState.content) + + if (picture) setUserAvatar(picture) + + setUsername(shorten(display_name || name || '', 7)) + } + }, [metadataState]) + + const handleOpenUserMenu = (event: React.MouseEvent) => { + setAnchorElUser(event.currentTarget) + } + + const handleCloseUserMenu = () => { + setAnchorElUser(null) + } + + const handleLogout = () => { + dispatch( + setAuthState({ + loggedIn: false, + loginMethod: undefined + }) + ) + + navigate('/') + } + const isAuthenticated = authState?.loggedIn === true + + return ( + + + +
+ Logo navigate('/')} /> +
+ + {!isAuthenticated && ( + + + + )} +
+ + {authState?.loggedIn && ( +
+ + + + {username} + + + + Help + + + + Logout + + +
+ )} +
+
+ ) +} diff --git a/src/components/AppBar/style.module.scss b/src/components/AppBar/style.module.scss new file mode 100644 index 0000000..7423310 --- /dev/null +++ b/src/components/AppBar/style.module.scss @@ -0,0 +1,56 @@ +@import '../../colors.scss'; + +.AppBar { + background-color: $background-color !important; + z-index: 1400 !important; + + .AppBarIcon { + background-color: $btn-background-color !important; + margin-right: 10px; + } + + .logoWrapper { + display: flex; + align-items: center; + height: 100%; + padding: 13px 0 13px 0; + box-sizing: border-box; + } + + .AppBarLogoWrapper { + height: 60px; + width: 100px; + cursor: pointer; + justify-content: center; + align-items: center; + + .logoWrapper { + margin-left: 20px; + + img { + max-height: 100%; + max-width: 150px; + } + } + } + + .AppBarUnAuth { + height: 60px; + cursor: pointer; + justify-content: space-between; + align-items: center; + display: flex; + width: 100%; + + .logoWrapper { + img { + max-height: 100%; + max-width: 120px; + } + } + + .loginBtn { + margin-right: 16px; + } + } +} diff --git a/src/components/LoadingSpinner/index.tsx b/src/components/LoadingSpinner/index.tsx new file mode 100644 index 0000000..3c57bff --- /dev/null +++ b/src/components/LoadingSpinner/index.tsx @@ -0,0 +1,18 @@ +import styles from './style.module.scss' + +interface Props { + desc: string +} + +export const LoadingSpinner = (props: Props) => { + const { desc } = props + + return ( +
+
+
+ {desc && {desc}} +
+
+ ) +} diff --git a/src/components/LoadingSpinner/style.module.scss b/src/components/LoadingSpinner/style.module.scss new file mode 100644 index 0000000..b83b70b --- /dev/null +++ b/src/components/LoadingSpinner/style.module.scss @@ -0,0 +1,37 @@ +.loadingSpinnerOverlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background-color: rgba(0, 0, 0, 0.5); + z-index: 9999; + + .loadingSpinnerContainer { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } + + .loadingSpinner { + border: 4px solid #f3f3f3; + border-top: 4px solid #3498db; + border-radius: 50%; + width: 40px; + height: 40px; + animation: spin 1s linear infinite; + } +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/src/components/username.tsx b/src/components/username.tsx new file mode 100644 index 0000000..001382b --- /dev/null +++ b/src/components/username.tsx @@ -0,0 +1,33 @@ +import { Typography, IconButton } from '@mui/material' + +type Props = { + username: string + avatarContent: string + handleClick: (event: React.MouseEvent) => void +} + +const Username = ({ username, avatarContent, handleClick }: Props) => { + return ( + + user-avatar + + {username} + + + ) +} + +export default Username diff --git a/src/controllers/AuthController.ts b/src/controllers/AuthController.ts new file mode 100644 index 0000000..0cc8b02 --- /dev/null +++ b/src/controllers/AuthController.ts @@ -0,0 +1,66 @@ +import { EventTemplate } from 'nostr-tools' +import { MetadataController, NostrController } from '.' +import { setAuthState, setMetadataEvent } from '../store/actions' +import store from '../store/store' +import { getVisitedLink } from '../utils' +import { appPrivateRoutes } from '../routes' + +export class AuthController { + private nostrController: NostrController + private metadataController: MetadataController + + constructor() { + this.nostrController = NostrController.getInstance() + this.metadataController = new MetadataController() + } + + /** + * Function will authenticate user by signing an auth event + * which is done by calling the sign() function, where appropriate + * method will be chosen (extension, nsecbunker or keys) + * + * @param pubkey of the user trying to login + * @returns url to redirect if authentication successfull + * or error if otherwise + */ + async authenticateAndFindMetadata(pubkey: string) { + this.metadataController + .findMetadata(pubkey) + .then((event) => { + store.dispatch(setMetadataEvent(event)) + }) + .catch((err) => { + console.error('Error occurred while finding metadata', err) + }) + + // Nostr uses unix timestamps + const timestamp = Math.floor(Date.now() / 1000) + const { hostname } = window.location + + const authEvent: EventTemplate = { + kind: 1, + tags: [], + content: `${hostname}-${timestamp}`, + created_at: timestamp + } + + await this.nostrController.signEvent(authEvent) + + store.dispatch( + setAuthState({ + loggedIn: true + }) + ) + + const visitedLink = getVisitedLink() + + if (visitedLink) { + const { pathname, search } = visitedLink + + return Promise.resolve(`${pathname}${search}`) + } else { + // Navigate user in + return Promise.resolve(appPrivateRoutes.homePage) + } + } +} diff --git a/src/controllers/MetadataController.ts b/src/controllers/MetadataController.ts new file mode 100644 index 0000000..3f99a5d --- /dev/null +++ b/src/controllers/MetadataController.ts @@ -0,0 +1,53 @@ +import { + Filter, + SimplePool, + VerifiedEvent, + kinds, + validateEvent, + verifyEvent +} from 'nostr-tools' + +export class MetadataController { + constructor() {} + + public findMetadata = async (hexKey: string) => { + const mostPopularRelays = import.meta.env.VITE_MOST_POPULAR_RELAYS + const hardcodedPopularRelays = (mostPopularRelays || '').split(' ') + const specialMetadataRelay = 'wss://purplepag.es' + + const relays = [...hardcodedPopularRelays, specialMetadataRelay] + + const eventFilter: Filter = { + kinds: [kinds.Metadata], + authors: [hexKey] + } + + const pool = new SimplePool() + + const events = await pool.querySync(relays, eventFilter).catch((err) => { + console.error(err) + return null + }) + + if (events && events.length) { + events.sort((a, b) => b.created_at - a.created_at) + + for (const event of events) { + if (validateEvent(event) && verifyEvent(event)) { + return event + } + } + } + + throw new Error('Mo metadata found.') + } + + public extractProfileMetadataContent = (event: VerifiedEvent) => { + try { + return JSON.parse(event.content) + } catch (error) { + console.log('error in parsing metadata event content :>> ', error) + return null + } + } +} diff --git a/src/controllers/NostrController.ts b/src/controllers/NostrController.ts new file mode 100644 index 0000000..e0c9882 --- /dev/null +++ b/src/controllers/NostrController.ts @@ -0,0 +1,292 @@ +import NDK, { + NDKEvent, + NDKNip46Signer, + NDKPrivateKeySigner, + NostrEvent +} from '@nostr-dev-kit/ndk' +import { + Event, + EventTemplate, + UnsignedEvent, + finalizeEvent, + nip19 +} from 'nostr-tools' +import { updateNsecbunkerPubkey } from '../store/actions' +import { AuthState, LoginMethods } from '../store/auth/types' +import store from '../store/store' +import { SignedEvent } from '../types' +import { getNsecBunkerDelegatedKey, verifySignedEvent } from '../utils' + +export class NostrController { + private static instance: NostrController + + private bunkerNDK: NDK | undefined + private remoteSigner: NDKNip46Signer | undefined + + private constructor() {} + + public nsecBunkerInit = async (relays: string[]) => { + // Don't reinstantiate bunker NDK if exists with same relays + if ( + this.bunkerNDK && + this.bunkerNDK.explicitRelayUrls?.length === relays.length && + this.bunkerNDK.explicitRelayUrls?.every((relay) => relays.includes(relay)) + ) + return + + this.bunkerNDK = new NDK({ + explicitRelayUrls: relays + }) + + try { + await this.bunkerNDK + .connect(2000) + .then(() => { + console.log( + `Successfully connected to the nsecBunker relays: ${relays.join( + ',' + )}` + ) + }) + .catch((err) => { + console.error( + `Error connecting to the nsecBunker relays: ${relays.join( + ',' + )} ${err}` + ) + }) + } catch (err) { + console.error(err) + } + } + + /** + * Creates nSecBunker signer instance for the given npub + * Or if npub omitted it will return existing signer + * If neither, error will be thrown + * @param npub nPub / public key in hex format + * @returns nsecBunker Signer instance + */ + public createNsecBunkerSigner = async ( + npub: string | undefined + ): Promise => { + const nsecBunkerDelegatedKey = getNsecBunkerDelegatedKey() + + return new Promise((resolve, reject) => { + if (!nsecBunkerDelegatedKey) { + reject('nsecBunker delegated key is not found in the browser.') + return + } + const localSigner = new NDKPrivateKeySigner(nsecBunkerDelegatedKey) + + if (!npub) { + if (this.remoteSigner) resolve(this.remoteSigner) + + const npubFromStorage = (store.getState().auth as AuthState) + .nsecBunkerPubkey + + if (npubFromStorage) { + npub = npubFromStorage + } else { + reject( + 'No signer instance present, no npub provided by user or found in the browser.' + ) + return + } + } else { + store.dispatch(updateNsecbunkerPubkey(npub)) + } + + // Pubkey of a key pair stored in nsecbunker that will be used to sign event with + const appPubkeyOrToken = npub.includes('npub') + ? npub + : nip19.npubEncode(npub) + + /** + * When creating and NDK instance we create new connection to the relay + * To prevent too much connections and hitting rate limits, if npub against which we sign + * we will reuse existing instance. Otherwise we will create new NDK and signer instance. + */ + if (!this.remoteSigner || this.remoteSigner?.remotePubkey !== npub) { + this.remoteSigner = new NDKNip46Signer( + this.bunkerNDK!, + appPubkeyOrToken, + localSigner + ) + } + + /** + * when nsecbunker-delegated-key is regenerated we have to reinitialize the remote signer + */ + if (this.remoteSigner.localSigner !== localSigner) { + this.remoteSigner = new NDKNip46Signer( + this.bunkerNDK!, + appPubkeyOrToken, + localSigner + ) + } + + this.remoteSigner.on('authUrl', (url: string) => { + window.open(url, '_blank') + }) + + resolve(this.remoteSigner) + }) + } + + /** + * Signs the nostr event and returns the sig and id or full raw nostr event + * @param npub stored in nsecBunker to sign with + * @param event to be signed + * @param returnFullEvent whether to return full raw nostr event or just SIG and ID values + */ + public signWithNsecBunker = async ( + npub: string | undefined, + event: NostrEvent, + returnFullEvent = true + ): Promise<{ id: string; sig: string } | NostrEvent> => { + return new Promise((resolve, reject) => { + this.createNsecBunkerSigner(npub) + .then(async (signer) => { + const ndkEvent = new NDKEvent(undefined, event) + + const timeout = setTimeout(() => { + reject('Timeout occurred while waiting for event signing') + }, 60000) // 60000 ms (1 min) = 1000 * 60 + + await ndkEvent.sign(signer).catch((err) => { + clearTimeout(timeout) + reject(err) + return + }) + + clearTimeout(timeout) + + if (returnFullEvent) { + resolve(ndkEvent.rawEvent()) + } else { + resolve({ + id: ndkEvent.id, + sig: ndkEvent.sig! + }) + } + }) + .catch((err) => { + reject(err) + }) + }) + } + + public static getInstance(): NostrController { + if (!NostrController.instance) { + NostrController.instance = new NostrController() + } + return NostrController.instance + } + + /** + * Signs an event with private key (if it is present in local storage) or + * with browser extension (if it is present) or + * with nSecBunker instance. + * @param event - unsigned nostr event. + * @returns - a promised that is resolved with signed nostr event. + */ + signEvent = async ( + event: UnsignedEvent | EventTemplate + ): Promise => { + const loginMethod = (store.getState().auth as AuthState).loginMethod + + if (!loginMethod) { + return Promise.reject('No login method found in the browser storage') + } + + if (loginMethod === LoginMethods.nsecBunker) { + // Check if nsecBunker is available + if (!this.bunkerNDK) { + return Promise.reject( + `Login method is ${loginMethod} but bunkerNDK is not created` + ) + } + + if (!this.remoteSigner) { + return Promise.reject( + `Login method is ${loginMethod} but bunkerNDK is not created` + ) + } + + const signedEvent = await this.signWithNsecBunker( + '', + event as NostrEvent + ).catch((err) => { + throw err + }) + + return Promise.resolve(signedEvent as SignedEvent) + } else if (loginMethod === LoginMethods.privateKey) { + const keys = (store.getState().auth as AuthState).keyPair + + if (!keys) { + return Promise.reject( + `Login method is ${loginMethod}, but keys are not found` + ) + } + + const { private: secretKey } = keys + const nsec = new TextEncoder().encode(secretKey) + + const signedEvent = finalizeEvent(event, nsec) + + verifySignedEvent(signedEvent) + + return Promise.resolve(signedEvent) + } else if (loginMethod === LoginMethods.extension) { + if (!window.nostr) { + return Promise.reject( + `Login method is ${loginMethod} but window.nostr is not present. Make sure extension is working properly` + ) + } + + return (await window.nostr + .signEvent(event as NostrEvent) + .catch((err: any) => { + console.log('Error while signing event: ', err) + + throw err + })) as Event + } else { + return Promise.reject( + `We could not sign the event, none of the signing methods are available` + ) + } + } + + /** + * Function will capture the public key from the nostr extension or if no extension present + * function wil capture the public key from the local storage + */ + capturePublicKey = async (): Promise => { + if (window.nostr) { + const pubKey = await window.nostr.getPublicKey().catch((err: any) => { + return Promise.reject(err.message) + }) + + if (!pubKey) { + return Promise.reject('Error getting public key, user canceled') + } + + return Promise.resolve(pubKey) + } + + return Promise.reject( + 'window.nostr object not present. Make sure you have an nostr extension installed.' + ) + } + + /** + * Generates NDK Private Signer + * @returns nSecBunker delegated key + */ + generateDelegatedKey = (): string => { + return NDKPrivateKeySigner.generate().privateKey! + } +} diff --git a/src/controllers/index.ts b/src/controllers/index.ts new file mode 100644 index 0000000..47cba11 --- /dev/null +++ b/src/controllers/index.ts @@ -0,0 +1,3 @@ +export * from './AuthController' +export * from './MetadataController' +export * from './NostrController' diff --git a/src/index.css b/src/index.css index 6119ad9..e8046ed 100644 --- a/src/index.css +++ b/src/index.css @@ -11,6 +11,7 @@ text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; } a { @@ -24,10 +25,12 @@ a:hover { body { margin: 0; - display: flex; - place-items: center; + /* display: flex; + place-items: center; */ min-width: 320px; min-height: 100vh; + background-color: #f4f4fb; + overflow-wrap: break-word; } h1 { @@ -46,12 +49,12 @@ button { cursor: pointer; transition: border-color 0.25s; } -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; + +.qrzap { + position: fixed; + top: 80px; + right: 20px; + z-index: 100; } @media (prefers-color-scheme: light) { @@ -66,3 +69,104 @@ button:focus-visible { background-color: #f9f9f9; } } + +.main { + padding: 64px 0; +} + +.hide-mobile { + @media (max-width: 639px) { + display: none !important; + } +} + +.hide-desktop { + @media (min-width: 639px) { + display: none !important; + } +} + +/* + * when this class is assigned to a component, user will not be able to select and copy the content from that component + */ +.no-select { + user-select: none; +} + +.no-privilege { + display: flex; + justify-content: center; + margin-top: 20px; + color: red; +} + +.quiz-btn { + color: #fff !important; + border-radius: 8px !important; + border: 1px solid transparent !important; + padding: 0.6em 1.2em !important; + font-size: 1em !important; + font-weight: 500 !important; +} + +Button { + border-radius: 8px !important; + + &:disabled { + /* background-color: gray !important; */ + /* color: black !important; */ + cursor: not-allowed !important; + } +} + +/* Style
 tag of markdown editor affected by prism theme */
+pre[class*='language-'][class*='w-md-editor-text-pre'] {
+  padding: 0;
+  line-height: normal;
+}
+
+/* Style  tag of markdown editor affected by prism theme */
+code[class*='language-'][class*='code-highlight'] {
+  color: #3e3e3e;
+}
+
+.bold-link {
+  color: inherit !important;
+  font-weight: bold;
+}
+
+button:disabled {
+  opacity: 0.6;
+  cursor: not-allowed;
+}
+
+.dashed-field.Mui-focused fieldset {
+  border-color: unset !important;
+}
+
+.dashed-field fieldset {
+  border-style: dashed !important;
+}
+
+.pointer {
+  cursor: pointer;
+}
+
+.force-pointer {
+  cursor: pointer !important;
+}
+
+.inherit-color {
+  color: inherit;
+}
+
+.inherit-color-force {
+  color: inherit !important;
+}
+
+.profile-image {
+  width: 44px;
+  height: 44px;
+  border-radius: 50%;
+  overflow: hidden;
+}
diff --git a/src/layouts/Main.tsx b/src/layouts/Main.tsx
new file mode 100644
index 0000000..f356b63
--- /dev/null
+++ b/src/layouts/Main.tsx
@@ -0,0 +1,33 @@
+import { Box } from '@mui/material'
+import Container from '@mui/material/Container'
+import { useEffect } from 'react'
+import { useDispatch } from 'react-redux'
+import { Outlet } from 'react-router-dom'
+import { AppBar } from '../components/AppBar/AppBar'
+import { restoreState } from '../store/actions'
+import { loadState } from '../utils'
+
+export const MainLayout = () => {
+  const dispatch = useDispatch()
+
+  useEffect(() => {
+    const restoredState = loadState()
+    if (restoredState) dispatch(restoreState(restoredState))
+  }, [dispatch])
+
+  return (
+    <>
+      
+
+      
+        
+          
+        
+      
+    
+  )
+}
diff --git a/src/layouts/style.module.scss b/src/layouts/style.module.scss
new file mode 100644
index 0000000..8d92532
--- /dev/null
+++ b/src/layouts/style.module.scss
@@ -0,0 +1,22 @@
+@import '../colors.scss';
+
+@font-face {
+  font-family: 'Avenir';
+  font-weight: normal;
+  src: local('Avenir'),
+    url(../assets/avenir-font/AvenirLTStd-Roman.otf) format('opentype');
+}
+
+@font-face {
+  font-family: 'Avenir';
+  font-weight: bold;
+  src: local('Avenir'),
+    url(../assets/avenir-font/AvenirLTStd-Black.otf) format('opentype');
+}
+
+@font-face {
+  font-family: 'Avenir';
+  font-weight: lighter;
+  src: local('Avenir'),
+    url(../assets/avenir-font/AvenirLTStd-Book.otf) format('opentype');
+}
diff --git a/src/main.tsx b/src/main.tsx
index 3d7150d..967317b 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,10 +1,30 @@
+import _ from 'lodash'
 import React from 'react'
 import ReactDOM from 'react-dom/client'
+import { Provider } from 'react-redux'
+import { BrowserRouter } from 'react-router-dom'
+import { ToastContainer } from 'react-toastify'
+import 'react-toastify/dist/ReactToastify.css'
 import App from './App.tsx'
 import './index.css'
+import store from './store/store.ts'
+import { saveState } from './utils'
+
+store.subscribe(
+  _.throttle(() => {
+    saveState({
+      auth: store.getState().auth
+    })
+  }, 1000)
+)
 
 ReactDOM.createRoot(document.getElementById('root')!).render(
   
-    
-  ,
+    
+      
+        
+        
+      
+    
+  
 )
diff --git a/src/pages/landing/LandingPage.tsx b/src/pages/landing/LandingPage.tsx
new file mode 100644
index 0000000..5a03ddb
--- /dev/null
+++ b/src/pages/landing/LandingPage.tsx
@@ -0,0 +1,108 @@
+import { Box, Button, Typography, useTheme } from '@mui/material'
+import { useEffect } from 'react'
+import { useSelector } from 'react-redux'
+import { useLocation, useNavigate } from 'react-router-dom'
+import { appPublicRoutes } from '../../routes'
+import { State } from '../../store/rootReducer'
+import { saveVisitedLink } from '../../utils'
+import styles from './style.module.scss'
+
+const bodyBackgroundColor = document.body.style.backgroundColor
+
+export const LandingPage = () => {
+  const authState = useSelector((state: State) => state.auth)
+  const navigate = useNavigate()
+  const location = useLocation()
+
+  const theme = useTheme()
+
+  useEffect(() => {
+    saveVisitedLink(location.pathname, location.search)
+  }, [location])
+
+  const onSignInClick = async () => {
+    navigate(appPublicRoutes.login)
+  }
+
+  return (
+    <>
+      
+ + + + What is Nostr? + + + Nostr is a decentralised messaging protocol where YOU own your + identity. To get started, you must have an existing{' '} + + Nostr account + + . +
+
+ No email required - all notifications are made using the nQuiz + relay. +
+
+ If you no longer wish to hear from us, simply remove + relay.nquiz.io from your list of relays. +
+
+
+ + {!authState?.loggedIn && ( +
+ +
+ )} +
+ + ) +} diff --git a/src/pages/landing/style.module.scss b/src/pages/landing/style.module.scss new file mode 100644 index 0000000..a0e123f --- /dev/null +++ b/src/pages/landing/style.module.scss @@ -0,0 +1,29 @@ +@import '../../colors.scss'; + +.landingPage { + display: flex; + flex-direction: column; + align-items: center; + color: $text-color; + min-height: 80vh; + justify-content: center; + padding-top: 20px; + + .loginBottomBar { + display: flex; + justify-content: center; + align-items: center; + padding: 10px; + background-color: rgba(255, 255, 255, 0.7); + border-top: 1px solid rgba(0, 0, 0, 0.096); + position: fixed; + bottom: 0; + left: 0; + right: 0; + + .loginBtn { + // margin-top: 20px; + min-width: 200px; + } + } +} diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx new file mode 100644 index 0000000..3208281 --- /dev/null +++ b/src/pages/login/index.tsx @@ -0,0 +1,240 @@ +import { Box, Button, TextField, Typography } from '@mui/material' +import { getPublicKey, nip05, nip19 } from 'nostr-tools' +import { useEffect, useState } from 'react' +import { useDispatch } from 'react-redux' +import { toast } from 'react-toastify' +import { LoadingSpinner } from '../../components/LoadingSpinner' +import { + AuthController, + MetadataController, + NostrController +} from '../../controllers' +import { + updateKeyPair, + updateLoginMethod, + updateNsecbunkerPubkey +} from '../../store/actions' +import { LoginMethods } from '../../store/auth/types' +import { Dispatch } from '../../store/store' +import { nsecToHex } from '../../utils' +import styles from './style.module.scss' +import { useNavigate } from 'react-router-dom' + +export const Login = () => { + const dispatch: Dispatch = useDispatch() + const navigate = useNavigate() + + const authController = new AuthController() + const metadataController = new MetadataController() + const nostrController = NostrController.getInstance() + + const [isLoading, setIsLoading] = useState(false) + const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('') + const [inputValue, setInputValue] = useState('') + + const [isNostrExtensionAvailable, setIsNostrExtensionAvailable] = + useState(false) + + useEffect(() => { + setTimeout(() => { + setIsNostrExtensionAvailable(!!window.nostr) + }, 500) + }, []) + + const loginWithExtension = async () => { + setIsLoading(true) + setLoadingSpinnerDesc('Capturing pubkey from nostr extension') + + nostrController + .capturePublicKey() + .then(async (pubkey) => { + dispatch(updateLoginMethod(LoginMethods.extension)) + + setLoadingSpinnerDesc('Authenticating and finding metadata') + const redirectPath = await authController.authenticateAndFindMetadata( + pubkey + ) + + navigate(redirectPath) + }) + .catch((err) => { + toast.error('Error capturing public key from nostr extension: ' + err) + }) + .finally(() => { + setIsLoading(false) + setLoadingSpinnerDesc('') + }) + } + + const loginWithNsec = async () => { + const hexPrivateKey = nsecToHex(inputValue) + + if (!hexPrivateKey) { + toast.error( + 'Snap, we failed to convert the private key you provided. Please make sure key is valid.' + ) + setIsLoading(false) + return + } + + const publickey = getPublicKey(new TextEncoder().encode(hexPrivateKey)) + + dispatch( + updateKeyPair({ + private: hexPrivateKey, + public: publickey + }) + ) + dispatch(updateLoginMethod(LoginMethods.privateKey)) + + setIsLoading(true) + setLoadingSpinnerDesc('Authenticating and finding metadata') + + const redirectPath = await authController + .authenticateAndFindMetadata(publickey) + .catch((err) => { + toast.error('Error occurred in authentication: ' + err) + return null + }) + + if (redirectPath) navigate(redirectPath) + + setIsLoading(false) + setLoadingSpinnerDesc('') + } + + const loginWithNsecBunker = async () => { + let relays: string[] | undefined + let pubkey: string | undefined + + setIsLoading(true) + + if (inputValue.includes('@')) { + const nip05Profile = await nip05.queryProfile(inputValue).catch((err) => { + toast.error('An error occurred while querying nip05 profile: ' + err) + return null + }) + + if (nip05Profile) { + pubkey = nip05Profile.pubkey + relays = nip05Profile.relays + } + } else if (inputValue.startsWith('npub')) { + pubkey = nip19.decode(inputValue).data as string + const metadataEvent = await metadataController + .findMetadata(pubkey) + .catch(() => { + return null + }) + + if (!metadataEvent) { + toast.error('metadata not found!') + return + } + + const metadataContent = + metadataController.extractProfileMetadataContent(metadataEvent) + + if (!metadataContent.nip05) { + toast.error('nip05 not present in metadata') + return + } + + const nip05Profile = await nip05.queryProfile(inputValue).catch((err) => { + toast.error('An error occurred while querying nip05 profile: ' + err) + return null + }) + + if (nip05Profile) { + if (nip05Profile.pubkey !== pubkey) { + toast.error('pubkey in nip05 does not match with provided npub') + return + } + + relays = nip05Profile.relays + } + } + + if (!relays || relays.length === 0) { + toast.error('No relay found for nsecbunker') + return + } + + if (!pubkey) { + toast.error('pubkey not found') + return + } + + setLoadingSpinnerDesc('Initializing nsecBunker') + await nostrController.nsecBunkerInit(relays) + + setLoadingSpinnerDesc('Creating nsecbunker singer') + await nostrController + .createNsecBunkerSigner(pubkey) + .then(async () => { + dispatch(updateLoginMethod(LoginMethods.privateKey)) + dispatch(updateNsecbunkerPubkey(pubkey)) + + setLoadingSpinnerDesc('Authenticating and finding metadata') + + const redirectPath = await authController + .authenticateAndFindMetadata(pubkey!) + .catch((err) => { + toast.error('Error occurred in authentication: ' + err) + return null + }) + + if (redirectPath) navigate(redirectPath) + }) + .catch((err) => { + toast.error( + 'An error occurred while creating nsecbunker signer: ' + err + ) + }) + .finally(() => { + setIsLoading(false) + setLoadingSpinnerDesc('') + }) + } + + const login = () => { + if (inputValue.startsWith('nsec')) { + return loginWithNsec() + } + if (inputValue.startsWith('npub')) { + return loginWithNsecBunker() + } + if (inputValue.includes('@')) { + return loginWithNsecBunker() + } + + toast.error('Invalid Input!') + return + } + + return ( + <> + {isLoading && } +
+ Welcome to Sigit + setInputValue(e.target.value)} + sx={{ width: '100%', mt: 2 }} + /> + {isNostrExtensionAvailable && ( + + )} + + + + +
+ + ) +} diff --git a/src/pages/login/style.module.scss b/src/pages/login/style.module.scss new file mode 100644 index 0000000..3c3c569 --- /dev/null +++ b/src/pages/login/style.module.scss @@ -0,0 +1,6 @@ +@import '../../colors.scss'; + +.loginPage { + color: $text-color; + margin-top: 20px; +} diff --git a/src/routes/index.tsx b/src/routes/index.tsx new file mode 100644 index 0000000..59cfa39 --- /dev/null +++ b/src/routes/index.tsx @@ -0,0 +1,26 @@ +import { LandingPage } from '../pages/landing/LandingPage' +import { Login } from '../pages/login' + +export const appPublicRoutes = { + login: '/login', + help: 'https://help.sigit.io' +} + +export const appPrivateRoutes = { + homePage: '/' +} + +export const publicRoutes = [ + { + path: appPublicRoutes.login, + hiddenWhenLoggedIn: true, + element: + } +] + +export const privateRoutes = [ + { + path: appPrivateRoutes.homePage, + element: + } +] diff --git a/src/store/actionTypes.ts b/src/store/actionTypes.ts new file mode 100644 index 0000000..f1d649f --- /dev/null +++ b/src/store/actionTypes.ts @@ -0,0 +1,8 @@ +export const RESTORE_STATE = 'RESTORE_STATE' + +export const SET_AUTH_STATE = 'SET_AUTH_STATE' +export const UPDATE_LOGIN_METHOD = 'UPDATE_LOGIN_METHOD' +export const UPDATE_KEYPAIR = 'UPDATE_KEYPAIR' +export const UPDATE_NSECBUNKER_PUBKEY = 'UPDATE_NSECBUNKER_PUBKEY' + +export const SET_METADATA_EVENT = 'SET_METADATA_EVENT' diff --git a/src/store/actions.ts b/src/store/actions.ts new file mode 100644 index 0000000..3bf716b --- /dev/null +++ b/src/store/actions.ts @@ -0,0 +1,17 @@ +import * as ActionTypes from './actionTypes' +import { State } from './rootReducer' + +export * from './auth/action' +export * from './metadata/action' + +export const restoreState = (payload: State) => { + return { + type: ActionTypes.RESTORE_STATE, + payload + } +} + +export interface RestoreState { + type: typeof ActionTypes.RESTORE_STATE + payload: State +} diff --git a/src/store/auth/action.ts b/src/store/auth/action.ts new file mode 100644 index 0000000..ac9e39d --- /dev/null +++ b/src/store/auth/action.ts @@ -0,0 +1,34 @@ +import * as ActionTypes from '../actionTypes' +import { + AuthState, + Keys, + LoginMethods, + SetAuthState, + UpdateKeyPair, + UpdateLoginMethod, + UpdateNsecBunkerPubkey +} from './types' + +export const setAuthState = (payload: AuthState): SetAuthState => ({ + type: ActionTypes.SET_AUTH_STATE, + payload +}) + +export const updateLoginMethod = ( + payload: LoginMethods | undefined +): UpdateLoginMethod => ({ + type: ActionTypes.UPDATE_LOGIN_METHOD, + payload +}) + +export const updateKeyPair = (payload: Keys | undefined): UpdateKeyPair => ({ + type: ActionTypes.UPDATE_KEYPAIR, + payload +}) + +export const updateNsecbunkerPubkey = ( + payload: string | undefined +): UpdateNsecBunkerPubkey => ({ + type: ActionTypes.UPDATE_NSECBUNKER_PUBKEY, + payload +}) diff --git a/src/store/auth/reducer.ts b/src/store/auth/reducer.ts new file mode 100644 index 0000000..e384076 --- /dev/null +++ b/src/store/auth/reducer.ts @@ -0,0 +1,55 @@ +import * as ActionTypes from '../actionTypes' +import { AuthDispatchTypes, AuthState } from './types' + +const initialState: AuthState = { + loggedIn: false +} + +const reducer = ( + state = initialState, + action: AuthDispatchTypes +): AuthState | null => { + switch (action.type) { + case ActionTypes.SET_AUTH_STATE: { + const { loginMethod, keyPair, nsecBunkerPubkey } = state + + return { + loginMethod, + keyPair, + nsecBunkerPubkey, + ...action.payload + } + } + case ActionTypes.UPDATE_LOGIN_METHOD: { + const { payload } = action + + return { + ...state, + loginMethod: payload + } + } + + case ActionTypes.UPDATE_KEYPAIR: { + const { payload } = action + + return { + ...state, + keyPair: payload + } + } + + case ActionTypes.UPDATE_NSECBUNKER_PUBKEY: { + const { payload } = action + + return { + ...state, + nsecBunkerPubkey: payload + } + } + + default: + return state + } +} + +export default reducer diff --git a/src/store/auth/types.ts b/src/store/auth/types.ts new file mode 100644 index 0000000..12d35f3 --- /dev/null +++ b/src/store/auth/types.ts @@ -0,0 +1,46 @@ +import * as ActionTypes from '../actionTypes' + +export enum LoginMethods { + extension = 'extension', + privateKey = 'privateKey', + nsecBunker = 'nsecBunker', + register = 'register' +} + +export interface Keys { + private: string + public: string +} + +export interface AuthState { + loggedIn: boolean + loginMethod?: LoginMethods + keyPair?: Keys + nsecBunkerPubkey?: string +} + +export interface SetAuthState { + type: typeof ActionTypes.SET_AUTH_STATE + payload: AuthState +} + +export interface UpdateLoginMethod { + type: typeof ActionTypes.UPDATE_LOGIN_METHOD + payload: LoginMethods | undefined +} + +export interface UpdateKeyPair { + type: typeof ActionTypes.UPDATE_KEYPAIR + payload: Keys | undefined +} + +export interface UpdateNsecBunkerPubkey { + type: typeof ActionTypes.UPDATE_NSECBUNKER_PUBKEY + payload: string | undefined +} + +export type AuthDispatchTypes = + | SetAuthState + | UpdateLoginMethod + | UpdateKeyPair + | UpdateNsecBunkerPubkey diff --git a/src/store/metadata/action.ts b/src/store/metadata/action.ts new file mode 100644 index 0000000..a36561a --- /dev/null +++ b/src/store/metadata/action.ts @@ -0,0 +1,8 @@ +import * as ActionTypes from '../actionTypes' +import { SetMetadataEvent } from './types' +import { Event } from 'nostr-tools' + +export const setMetadataEvent = (payload: Event): SetMetadataEvent => ({ + type: ActionTypes.SET_METADATA_EVENT, + payload +}) diff --git a/src/store/metadata/reducer.ts b/src/store/metadata/reducer.ts new file mode 100644 index 0000000..9223ef3 --- /dev/null +++ b/src/store/metadata/reducer.ts @@ -0,0 +1,25 @@ +import * as ActionTypes from '../actionTypes' +import { MetadataDispatchTypes } from './types' +import { Event } from 'nostr-tools' + +const initialState: Event | null = null + +const reducer = ( + state = initialState, + action: MetadataDispatchTypes +): Event | null => { + switch (action.type) { + case ActionTypes.SET_METADATA_EVENT: + return { + ...action.payload + } + + case ActionTypes.RESTORE_STATE: + return action.payload.metadata || null + + default: + return state + } +} + +export default reducer diff --git a/src/store/metadata/types.ts b/src/store/metadata/types.ts new file mode 100644 index 0000000..cbc38ef --- /dev/null +++ b/src/store/metadata/types.ts @@ -0,0 +1,10 @@ +import * as ActionTypes from '../actionTypes' +import { Event } from 'nostr-tools' +import { RestoreState } from '../actions' + +export interface SetMetadataEvent { + type: typeof ActionTypes.SET_METADATA_EVENT + payload: Event +} + +export type MetadataDispatchTypes = SetMetadataEvent | RestoreState diff --git a/src/store/rootReducer.ts b/src/store/rootReducer.ts new file mode 100644 index 0000000..f56217f --- /dev/null +++ b/src/store/rootReducer.ts @@ -0,0 +1,15 @@ +import { Event } from 'nostr-tools' +import { combineReducers } from 'redux' +import authReducer from './auth/reducer' +import { AuthState } from './auth/types' +import metadataReducer from './metadata/reducer' + +export interface State { + auth: AuthState + metadata?: Event +} + +export default combineReducers({ + auth: authReducer, + metadata: metadataReducer +}) diff --git a/src/store/store.ts b/src/store/store.ts new file mode 100644 index 0000000..20d9b66 --- /dev/null +++ b/src/store/store.ts @@ -0,0 +1,8 @@ +import { configureStore } from '@reduxjs/toolkit' +import rootReducer from './rootReducer' + +const store = configureStore({ reducer: rootReducer }) + +export default store + +export type Dispatch = typeof store.dispatch diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..e69da0f --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1 @@ +export * from './nostr' diff --git a/src/types/nostr.ts b/src/types/nostr.ts new file mode 100644 index 0000000..00e5ade --- /dev/null +++ b/src/types/nostr.ts @@ -0,0 +1,9 @@ +export interface SignedEvent { + kind: number + tags: string[][] + content: string + created_at: number + pubkey: string + id: string + sig: string +} diff --git a/src/types/system/index.ts b/src/types/system/index.ts new file mode 100644 index 0000000..74b97a9 --- /dev/null +++ b/src/types/system/index.ts @@ -0,0 +1,8 @@ +declare global { + interface Window { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + nostr: any + } +} + +export {} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..4a57e8a --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,3 @@ +export * from './localStorage' +export * from './nostr' +export * from './string' diff --git a/src/utils/localStorage.ts b/src/utils/localStorage.ts new file mode 100644 index 0000000..5e0c285 --- /dev/null +++ b/src/utils/localStorage.ts @@ -0,0 +1,55 @@ +import { State } from '../store/rootReducer' + +export const saveState = (state: object) => { + try { + const serializedState = JSON.stringify(state) + + localStorage.setItem('state', serializedState) + } catch (err) { + console.log(`Error while saving state. Error: `, err) + } +} + +export const loadState = (): State | undefined => { + try { + const serializedState = localStorage.getItem('state') + + if (serializedState === null) return undefined + + return JSON.parse(serializedState) + } catch (err) { + return undefined + } +} + +export const saveNsecBunkerDelegatedKey = (privateKey: string) => { + localStorage.setItem('nsecbunker-delegated-key', privateKey) +} + +export const getNsecBunkerDelegatedKey = () => { + return localStorage.getItem('nsecbunker-delegated-key') +} + +export const saveVisitedLink = (pathname: string, search: string) => { + localStorage.setItem( + 'visitedLink', + JSON.stringify({ + pathname, + search + }) + ) +} + +export const getVisitedLink = () => { + const visitedLink = localStorage.getItem('visitedLink') + if (!visitedLink) return null + + try { + return JSON.parse(visitedLink) as { + pathname: string + search: string + } + } catch { + return null + } +} diff --git a/src/utils/nostr.ts b/src/utils/nostr.ts new file mode 100644 index 0000000..0689360 --- /dev/null +++ b/src/utils/nostr.ts @@ -0,0 +1,73 @@ +import { nip05, nip19, verifyEvent } from 'nostr-tools' +import { SignedEvent } from '../types' + +/** + * @param hexKey hex private or public key + * @returns whether or not is key valid + */ +const validateHex = (hexKey: string) => { + return hexKey.match(/^[a-f0-9]{64}$/) +} + +/** + * NPUB provided - it will convert NPUB to HEX + * NIP-05 provided - it will query NIP-05 profile and return HEX key if found + * HEX provided - it will return HEX + * + * @param pubKey in NPUB, NIP-05 or HEX format + * @returns HEX format + */ +export const pubToHex = async (pubKey: string): Promise => { + // If key is NIP-05 + if (pubKey.indexOf('@') !== -1) + return Promise.resolve( + (await nip05.queryProfile(pubKey).then((res) => res?.pubkey)) || null + ) + + // If key is NPUB + if (pubKey.startsWith('npub')) { + try { + return nip19.decode(pubKey).data as string + } catch (error) { + return Promise.resolve(null) + } + } + + // valid hex key + if (validateHex(pubKey)) return Promise.resolve(pubKey) + + // Not a valid hex key + return Promise.resolve(null) +} + +/** + * If NSEC key is provided function will convert it to HEX + * If HEX key Is provided function will validate the HEX format and return + * + * @param nsec or private key in HEX format + */ +export const nsecToHex = (nsec: string): string | null => { + // If key is NSEC + if (nsec.startsWith('nsec')) { + try { + return nip19.decode(nsec).data as string + } catch (error) { + return null + } + } + + // since it's not NSEC key we check if it's a valid hex key + if (validateHex(nsec)) return nsec + + return null +} + +export const verifySignedEvent = (event: SignedEvent) => { + const isGood = verifyEvent(event) + + if (!isGood) { + throw new Error( + 'Signed event did not pass verification. Check sig, id and pubkey.' + ) + } +} diff --git a/src/utils/string.ts b/src/utils/string.ts new file mode 100644 index 0000000..877a142 --- /dev/null +++ b/src/utils/string.ts @@ -0,0 +1,9 @@ +export const shorten = (str: string, offset = 9) => { + // return original string if it is not long enough + if (str.length < offset * 2 + 4) return str + + return `${str.slice(0, offset)}...${str.slice( + str.length - offset, + str.length + )}` +} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 11f02fe..0f27c1a 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1 +1,9 @@ /// + +interface ImportMetaEnv { + readonly VITE_MOST_POPULAR_RELAYS: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +}