Compare commits
68 Commits
b7bd922af3
...
19b815e528
Author | SHA1 | Date | |
---|---|---|---|
19b815e528 | |||
54047740f9 | |||
7f411f09a7 | |||
849e47da00 | |||
bb323be87c | |||
fd2f179273 | |||
4559f16d86 | |||
d6f92accb0 | |||
ee03cc545e | |||
70e525357c | |||
3eed2964a0 | |||
3a0f155010 | |||
1d1986f082 | |||
25764c7ab4 | |||
cc382f0726 | |||
9dd190d65b | |||
c3dacbe111 | |||
897daaa1fa | |||
ed90168e5d | |||
7f5fd4534f | |||
7f172178a1 | |||
1116867224 | |||
db9cf9d20c | |||
58c457b62c | |||
b6846c0006 | |||
8deb5bd7cd | |||
7b7f23a779 | |||
db4a202363 | |||
3a507246ca | |||
f09d9b2378 | |||
fe9f282984 | |||
aa4637dd0d | |||
23a04faad8 | |||
ad2ec070be | |||
d610c79cad | |||
6c5ed3a69c | |||
862012e405 | |||
8689c7f753 | |||
a3c45b504e | |||
da30dba368 | |||
|
51e2ab6f8a | ||
|
9091bbc251 | ||
331759de5c | |||
995c7ce293 | |||
532cdaed8e | |||
67d545de2f | |||
637e26bf35 | |||
110621a125 | |||
59e153595a | |||
0b79ebd909 | |||
e2dbed2b03 | |||
7c26edf84e | |||
2d7bb234f4 | |||
c4d50293ff | |||
89971fb176 | |||
acad24dc06 | |||
|
55abe814c9 | ||
e33996c1f9 | |||
6ba3b6ec89 | |||
aa8214d015 | |||
e48a396990 | |||
79e14d45a1 | |||
64e8ebba85 | |||
5dc8d53503 | |||
86a16c13ce | |||
7c027825cd | |||
8e71592d88 | |||
75a715d002 |
497
package-lock.json
generated
497
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "sigit",
|
"name": "sigit",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0-beta",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "sigit",
|
"name": "sigit",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0-beta",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "AGPL-3.0-or-later ",
|
"license": "AGPL-3.0-or-later ",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -32,6 +32,7 @@
|
|||||||
"jszip": "3.10.1",
|
"jszip": "3.10.1",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"mui-file-input": "4.0.4",
|
"mui-file-input": "4.0.4",
|
||||||
|
"nostr-login": "^1.6.6",
|
||||||
"nostr-tools": "2.7.0",
|
"nostr-tools": "2.7.0",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"pdfjs-dist": "^4.4.168",
|
"pdfjs-dist": "^4.4.168",
|
||||||
@ -614,13 +615,14 @@
|
|||||||
"integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww=="
|
"integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww=="
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||||
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
|
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"aix"
|
"aix"
|
||||||
@ -630,13 +632,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm": {
|
"node_modules/@esbuild/android-arm": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
|
||||||
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
|
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"android"
|
"android"
|
||||||
@ -646,13 +649,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm64": {
|
"node_modules/@esbuild/android-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
|
||||||
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
|
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"android"
|
"android"
|
||||||
@ -662,13 +666,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-x64": {
|
"node_modules/@esbuild/android-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
|
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"android"
|
"android"
|
||||||
@ -678,13 +683,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-arm64": {
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
|
||||||
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
|
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
@ -694,13 +700,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-x64": {
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
|
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
@ -710,13 +717,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-arm64": {
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
|
||||||
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
|
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"freebsd"
|
"freebsd"
|
||||||
@ -726,13 +734,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-x64": {
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
|
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"freebsd"
|
"freebsd"
|
||||||
@ -742,13 +751,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm": {
|
"node_modules/@esbuild/linux-arm": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
|
||||||
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
|
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
@ -758,13 +768,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm64": {
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
|
||||||
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
|
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
@ -774,13 +785,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ia32": {
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
|
||||||
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
|
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
@ -790,13 +802,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-loong64": {
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
|
||||||
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
|
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
@ -806,13 +819,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-mips64el": {
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
|
||||||
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
|
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
@ -822,13 +836,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ppc64": {
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
|
||||||
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
|
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
@ -838,13 +853,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-riscv64": {
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
|
||||||
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
|
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
@ -854,13 +870,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-s390x": {
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
|
||||||
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
|
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
@ -870,13 +887,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-x64": {
|
"node_modules/@esbuild/linux-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
|
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
@ -886,13 +904,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-x64": {
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
|
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"netbsd"
|
"netbsd"
|
||||||
@ -902,13 +921,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-x64": {
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
|
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"openbsd"
|
"openbsd"
|
||||||
@ -918,13 +938,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/sunos-x64": {
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
|
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"sunos"
|
"sunos"
|
||||||
@ -934,13 +955,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-arm64": {
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
|
||||||
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
|
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
@ -950,13 +972,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-ia32": {
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
|
||||||
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
|
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
@ -966,13 +989,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-x64": {
|
"node_modules/@esbuild/win32-x64": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
|
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
@ -1833,208 +1857,224 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.18.0",
|
"version": "4.22.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.5.tgz",
|
||||||
"integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==",
|
"integrity": "sha512-SU5cvamg0Eyu/F+kLeMXS7GoahL+OoizlclVFX3l5Ql6yNlywJJ0OuqTzUx0v+aHhPHEB/56CT06GQrRrGNYww==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"android"
|
"android"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.18.0",
|
"version": "4.22.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.5.tgz",
|
||||||
"integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==",
|
"integrity": "sha512-S4pit5BP6E5R5C8S6tgU/drvgjtYW76FBuG6+ibG3tMvlD1h9LHVF9KmlmaUBQ8Obou7hEyS+0w+IR/VtxwNMQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"android"
|
"android"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.18.0",
|
"version": "4.22.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.5.tgz",
|
||||||
"integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==",
|
"integrity": "sha512-250ZGg4ipTL0TGvLlfACkIxS9+KLtIbn7BCZjsZj88zSg2Lvu3Xdw6dhAhfe/FjjXPVNCtcSp+WZjVsD3a/Zlw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.18.0",
|
"version": "4.22.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.5.tgz",
|
||||||
"integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==",
|
"integrity": "sha512-D8brJEFg5D+QxFcW6jYANu+Rr9SlKtTenmsX5hOSzNYVrK5oLAEMTUgKWYJP+wdKyCdeSwnapLsn+OVRFycuQg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.18.0",
|
"version": "4.22.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.5.tgz",
|
||||||
"integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==",
|
"integrity": "sha512-PNqXYmdNFyWNg0ma5LdY8wP+eQfdvyaBAojAXgO7/gs0Q/6TQJVXAXe8gwW9URjbS0YAammur0fynYGiWsKlXw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
"version": "4.18.0",
|
"version": "4.22.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.5.tgz",
|
||||||
"integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==",
|
"integrity": "sha512-kSSCZOKz3HqlrEuwKd9TYv7vxPYD77vHSUvM2y0YaTGnFc8AdI5TTQRrM1yIp3tXCKrSL9A7JLoILjtad5t8pQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.18.0",
|
"version": "4.22.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.5.tgz",
|
||||||
"integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==",
|
"integrity": "sha512-oTXQeJHRbOnwRnRffb6bmqmUugz0glXaPyspp4gbQOPVApdpRrY/j7KP3lr7M8kTfQTyrBUzFjj5EuHAhqH4/w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.18.0",
|
"version": "4.22.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.5.tgz",
|
||||||
"integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==",
|
"integrity": "sha512-qnOTIIs6tIGFKCHdhYitgC2XQ2X25InIbZFor5wh+mALH84qnFHvc+vmWUpyX97B0hNvwNUL4B+MB8vJvH65Fw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||||
"version": "4.18.0",
|
"version": "4.22.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.5.tgz",
|
||||||
"integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==",
|
"integrity": "sha512-TMYu+DUdNlgBXING13rHSfUc3Ky5nLPbWs4bFnT+R6Vu3OvXkTkixvvBKk8uO4MT5Ab6lC3U7x8S8El2q5o56w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.18.0",
|
"version": "4.22.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.5.tgz",
|
||||||
"integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==",
|
"integrity": "sha512-PTQq1Kz22ZRvuhr3uURH+U/Q/a0pbxJoICGSprNLAoBEkyD3Sh9qP5I0Asn0y0wejXQBbsVMRZRxlbGFD9OK4A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
"version": "4.18.0",
|
"version": "4.22.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.5.tgz",
|
||||||
"integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==",
|
"integrity": "sha512-bR5nCojtpuMss6TDEmf/jnBnzlo+6n1UhgwqUvRoe4VIotC7FG1IKkyJbwsT7JDsF2jxR+NTnuOwiGv0hLyDoQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.18.0",
|
"version": "4.22.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.5.tgz",
|
||||||
"integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==",
|
"integrity": "sha512-N0jPPhHjGShcB9/XXZQWuWBKZQnC1F36Ce3sDqWpujsGjDz/CQtOL9LgTrJ+rJC8MJeesMWrMWVLKKNR/tMOCA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.18.0",
|
"version": "4.22.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.5.tgz",
|
||||||
"integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==",
|
"integrity": "sha512-uBa2e28ohzNNwjr6Uxm4XyaA1M/8aTgfF2T7UIlElLaeXkgpmIJ2EitVNQxjO9xLLLy60YqAgKn/AqSpCUkE9g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
"version": "4.18.0",
|
"version": "4.22.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.5.tgz",
|
||||||
"integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==",
|
"integrity": "sha512-RXT8S1HP8AFN/Kr3tg4fuYrNxZ/pZf1HemC5Tsddc6HzgGnJm0+Lh5rAHJkDuW3StI0ynNXukidROMXYl6ew8w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.18.0",
|
"version": "4.22.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.5.tgz",
|
||||||
"integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==",
|
"integrity": "sha512-ElTYOh50InL8kzyUD6XsnPit7jYCKrphmddKAe1/Ytt74apOxDq5YEcbsiKs0fR3vff3jEneMM+3I7jbqaMyBg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.18.0",
|
"version": "4.22.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.5.tgz",
|
||||||
"integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==",
|
"integrity": "sha512-+lvL/4mQxSV8MukpkKyyvfwhH266COcWlXE/1qxwN08ajovta3459zrjLghYMgDerlzNwLAcFpvU+WWE5y6nAQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
@ -2192,10 +2232,11 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/estree": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||||
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
|
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/file-saver": {
|
"node_modules/@types/file-saver": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
@ -3380,11 +3421,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.20.2",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
||||||
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
|
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"esbuild": "bin/esbuild"
|
"esbuild": "bin/esbuild"
|
||||||
},
|
},
|
||||||
@ -3392,29 +3434,29 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@esbuild/aix-ppc64": "0.20.2",
|
"@esbuild/aix-ppc64": "0.21.5",
|
||||||
"@esbuild/android-arm": "0.20.2",
|
"@esbuild/android-arm": "0.21.5",
|
||||||
"@esbuild/android-arm64": "0.20.2",
|
"@esbuild/android-arm64": "0.21.5",
|
||||||
"@esbuild/android-x64": "0.20.2",
|
"@esbuild/android-x64": "0.21.5",
|
||||||
"@esbuild/darwin-arm64": "0.20.2",
|
"@esbuild/darwin-arm64": "0.21.5",
|
||||||
"@esbuild/darwin-x64": "0.20.2",
|
"@esbuild/darwin-x64": "0.21.5",
|
||||||
"@esbuild/freebsd-arm64": "0.20.2",
|
"@esbuild/freebsd-arm64": "0.21.5",
|
||||||
"@esbuild/freebsd-x64": "0.20.2",
|
"@esbuild/freebsd-x64": "0.21.5",
|
||||||
"@esbuild/linux-arm": "0.20.2",
|
"@esbuild/linux-arm": "0.21.5",
|
||||||
"@esbuild/linux-arm64": "0.20.2",
|
"@esbuild/linux-arm64": "0.21.5",
|
||||||
"@esbuild/linux-ia32": "0.20.2",
|
"@esbuild/linux-ia32": "0.21.5",
|
||||||
"@esbuild/linux-loong64": "0.20.2",
|
"@esbuild/linux-loong64": "0.21.5",
|
||||||
"@esbuild/linux-mips64el": "0.20.2",
|
"@esbuild/linux-mips64el": "0.21.5",
|
||||||
"@esbuild/linux-ppc64": "0.20.2",
|
"@esbuild/linux-ppc64": "0.21.5",
|
||||||
"@esbuild/linux-riscv64": "0.20.2",
|
"@esbuild/linux-riscv64": "0.21.5",
|
||||||
"@esbuild/linux-s390x": "0.20.2",
|
"@esbuild/linux-s390x": "0.21.5",
|
||||||
"@esbuild/linux-x64": "0.20.2",
|
"@esbuild/linux-x64": "0.21.5",
|
||||||
"@esbuild/netbsd-x64": "0.20.2",
|
"@esbuild/netbsd-x64": "0.21.5",
|
||||||
"@esbuild/openbsd-x64": "0.20.2",
|
"@esbuild/openbsd-x64": "0.21.5",
|
||||||
"@esbuild/sunos-x64": "0.20.2",
|
"@esbuild/sunos-x64": "0.21.5",
|
||||||
"@esbuild/win32-arm64": "0.20.2",
|
"@esbuild/win32-arm64": "0.21.5",
|
||||||
"@esbuild/win32-ia32": "0.20.2",
|
"@esbuild/win32-ia32": "0.21.5",
|
||||||
"@esbuild/win32-x64": "0.20.2"
|
"@esbuild/win32-x64": "0.21.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/escalade": {
|
"node_modules/escalade": {
|
||||||
@ -4930,9 +4972,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/micromatch": {
|
"node_modules/micromatch": {
|
||||||
"version": "4.0.7",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||||
"integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
|
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -5236,6 +5278,72 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nostr-login": {
|
||||||
|
"version": "1.6.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/nostr-login/-/nostr-login-1.6.6.tgz",
|
||||||
|
"integrity": "sha512-XOpB9nG3Qgt7iea7gA1zn4TaTfUKCKGdCHKwErqLPtMk/q1Rhkzj5cq/66iU0WqC6mSiwENfTy1p4qaM7HzMtg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@nostr-dev-kit/ndk": "^2.3.1",
|
||||||
|
"nostr-tools": "^1.17.0",
|
||||||
|
"tseep": "^1.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/nostr-login/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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/nostr-login/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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@noble/hashes": "1.3.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/nostr-login/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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/nostr-login/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==",
|
||||||
|
"license": "Unlicense",
|
||||||
|
"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/nostr-tools": {
|
"node_modules/nostr-tools": {
|
||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.7.0.tgz",
|
||||||
@ -5545,10 +5653,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
|
||||||
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
|
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
@ -5576,9 +5685,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.38",
|
"version": "8.4.47",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
|
||||||
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
|
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -5594,10 +5703,11 @@
|
|||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.7",
|
"nanoid": "^3.3.7",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.1.0",
|
||||||
"source-map-js": "^1.2.0"
|
"source-map-js": "^1.2.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
@ -6172,12 +6282,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.18.0",
|
"version": "4.22.5",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.5.tgz",
|
||||||
"integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==",
|
"integrity": "sha512-WoinX7GeQOFMGznEcWA1WrTQCd/tpEbMkc3nuMs9BT0CPjMdSjPMTVClwWd4pgSQwJdP65SK9mTCNvItlr5o7w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "1.0.5"
|
"@types/estree": "1.0.6"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"rollup": "dist/bin/rollup"
|
"rollup": "dist/bin/rollup"
|
||||||
@ -6187,22 +6298,22 @@
|
|||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.18.0",
|
"@rollup/rollup-android-arm-eabi": "4.22.5",
|
||||||
"@rollup/rollup-android-arm64": "4.18.0",
|
"@rollup/rollup-android-arm64": "4.22.5",
|
||||||
"@rollup/rollup-darwin-arm64": "4.18.0",
|
"@rollup/rollup-darwin-arm64": "4.22.5",
|
||||||
"@rollup/rollup-darwin-x64": "4.18.0",
|
"@rollup/rollup-darwin-x64": "4.22.5",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.18.0",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.22.5",
|
||||||
"@rollup/rollup-linux-arm-musleabihf": "4.18.0",
|
"@rollup/rollup-linux-arm-musleabihf": "4.22.5",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.18.0",
|
"@rollup/rollup-linux-arm64-gnu": "4.22.5",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.18.0",
|
"@rollup/rollup-linux-arm64-musl": "4.22.5",
|
||||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.18.0",
|
"@rollup/rollup-linux-powerpc64le-gnu": "4.22.5",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.18.0",
|
"@rollup/rollup-linux-riscv64-gnu": "4.22.5",
|
||||||
"@rollup/rollup-linux-s390x-gnu": "4.18.0",
|
"@rollup/rollup-linux-s390x-gnu": "4.22.5",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.18.0",
|
"@rollup/rollup-linux-x64-gnu": "4.22.5",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.18.0",
|
"@rollup/rollup-linux-x64-musl": "4.22.5",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.18.0",
|
"@rollup/rollup-win32-arm64-msvc": "4.22.5",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.18.0",
|
"@rollup/rollup-win32-ia32-msvc": "4.22.5",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.18.0",
|
"@rollup/rollup-win32-x64-msvc": "4.22.5",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -6432,10 +6543,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@ -6930,14 +7042,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.2.12",
|
"version": "5.4.8",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz",
|
||||||
"integrity": "sha512-/gC8GxzxMK5ntBwb48pR32GGhENnjtY30G4A0jemunsBkiEZFw60s8InGpN8gkhHEkjnRK1aSAxeQgwvFhUHAA==",
|
"integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.20.1",
|
"esbuild": "^0.21.3",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.43",
|
||||||
"rollup": "^4.13.0"
|
"rollup": "^4.20.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"vite": "bin/vite.js"
|
"vite": "bin/vite.js"
|
||||||
@ -6956,6 +7069,7 @@
|
|||||||
"less": "*",
|
"less": "*",
|
||||||
"lightningcss": "^1.21.0",
|
"lightningcss": "^1.21.0",
|
||||||
"sass": "*",
|
"sass": "*",
|
||||||
|
"sass-embedded": "*",
|
||||||
"stylus": "*",
|
"stylus": "*",
|
||||||
"sugarss": "*",
|
"sugarss": "*",
|
||||||
"terser": "^5.4.0"
|
"terser": "^5.4.0"
|
||||||
@ -6973,6 +7087,9 @@
|
|||||||
"sass": {
|
"sass": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"sass-embedded": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"stylus": {
|
"stylus": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "sigit",
|
"name": "sigit",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0-beta",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"homepage": "https://sigit.io/",
|
"homepage": "https://sigit.io/",
|
||||||
"license": "AGPL-3.0-or-later ",
|
"license": "AGPL-3.0-or-later ",
|
||||||
@ -42,6 +42,7 @@
|
|||||||
"jszip": "3.10.1",
|
"jszip": "3.10.1",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"mui-file-input": "4.0.4",
|
"mui-file-input": "4.0.4",
|
||||||
|
"nostr-login": "^1.6.6",
|
||||||
"nostr-tools": "2.7.0",
|
"nostr-tools": "2.7.0",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"pdfjs-dist": "^4.4.168",
|
"pdfjs-dist": "^4.4.168",
|
||||||
|
21
src/App.tsx
21
src/App.tsx
@ -1,7 +1,7 @@
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useSelector } from 'react-redux'
|
import { useAppSelector } from './hooks/store'
|
||||||
import { Navigate, Route, Routes } from 'react-router-dom'
|
import { Navigate, Route, Routes } from 'react-router-dom'
|
||||||
import { AuthController, NostrController } from './controllers'
|
import { AuthController } from './controllers'
|
||||||
import { MainLayout } from './layouts/Main'
|
import { MainLayout } from './layouts/Main'
|
||||||
import {
|
import {
|
||||||
appPrivateRoutes,
|
appPrivateRoutes,
|
||||||
@ -10,12 +10,10 @@ import {
|
|||||||
publicRoutes,
|
publicRoutes,
|
||||||
recursiveRouteRenderer
|
recursiveRouteRenderer
|
||||||
} from './routes'
|
} from './routes'
|
||||||
import { State } from './store/rootReducer'
|
|
||||||
import { getNsecBunkerDelegatedKey, saveNsecBunkerDelegatedKey } from './utils'
|
|
||||||
import './App.scss'
|
import './App.scss'
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const authState = useSelector((state: State) => state.auth)
|
const authState = useAppSelector((state) => state.auth)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (window.location.hostname === '0.0.0.0') {
|
if (window.location.hostname === '0.0.0.0') {
|
||||||
@ -25,23 +23,10 @@ const App = () => {
|
|||||||
window.location.hostname = 'localhost'
|
window.location.hostname = 'localhost'
|
||||||
}
|
}
|
||||||
|
|
||||||
generateBunkerDelegatedKey()
|
|
||||||
|
|
||||||
const authController = new AuthController()
|
const authController = new AuthController()
|
||||||
authController.checkSession()
|
authController.checkSession()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const generateBunkerDelegatedKey = () => {
|
|
||||||
const existingKey = getNsecBunkerDelegatedKey()
|
|
||||||
|
|
||||||
if (!existingKey) {
|
|
||||||
const nostrController = NostrController.getInstance()
|
|
||||||
const newDelegatedKey = nostrController.generateDelegatedKey()
|
|
||||||
|
|
||||||
saveNsecBunkerDelegatedKey(newDelegatedKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleRootRedirect = () => {
|
const handleRootRedirect = () => {
|
||||||
if (authState.loggedIn) return appPrivateRoutes.homePage
|
if (authState.loggedIn) return appPrivateRoutes.homePage
|
||||||
const callbackPathEncoded = btoa(
|
const callbackPathEncoded = btoa(
|
||||||
|
@ -9,51 +9,36 @@ import {
|
|||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
|
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useAppSelector } from '../../hooks/store'
|
||||||
import {
|
|
||||||
setAuthState,
|
|
||||||
setMetadataEvent,
|
|
||||||
userLogOutAction
|
|
||||||
} from '../../store/actions'
|
|
||||||
import { State } from '../../store/rootReducer'
|
|
||||||
import { Dispatch } from '../../store/store'
|
|
||||||
import Username from '../username'
|
import Username from '../username'
|
||||||
|
|
||||||
import { Link, useNavigate } from 'react-router-dom'
|
import { Link, useNavigate } from 'react-router-dom'
|
||||||
import { MetadataController, NostrController } from '../../controllers'
|
|
||||||
import {
|
import {
|
||||||
appPublicRoutes,
|
appPublicRoutes,
|
||||||
appPrivateRoutes,
|
appPrivateRoutes,
|
||||||
getProfileRoute
|
getProfileRoute
|
||||||
} from '../../routes'
|
} from '../../routes'
|
||||||
import {
|
import { getProfileUsername, hexToNpub } from '../../utils'
|
||||||
clearAuthToken,
|
|
||||||
getProfileUsername,
|
|
||||||
hexToNpub,
|
|
||||||
saveNsecBunkerDelegatedKey
|
|
||||||
} from '../../utils'
|
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
import { setUserRobotImage } from '../../store/userRobotImage/action'
|
|
||||||
import { Container } from '../Container'
|
import { Container } from '../Container'
|
||||||
import { ButtonIcon } from '../ButtonIcon'
|
import { ButtonIcon } from '../ButtonIcon'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import { faClose } from '@fortawesome/free-solid-svg-icons'
|
import { faClose } from '@fortawesome/free-solid-svg-icons'
|
||||||
import useMediaQuery from '@mui/material/useMediaQuery'
|
import useMediaQuery from '@mui/material/useMediaQuery'
|
||||||
|
import { useLogout } from '../../hooks/useLogout'
|
||||||
|
|
||||||
const metadataController = MetadataController.getInstance()
|
import { launch as launchNostrLoginDialog } from 'nostr-login'
|
||||||
|
|
||||||
export const AppBar = () => {
|
export const AppBar = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const logout = useLogout()
|
||||||
const dispatch: Dispatch = useDispatch()
|
|
||||||
|
|
||||||
const [username, setUsername] = useState('')
|
const [username, setUsername] = useState('')
|
||||||
const [userAvatar, setUserAvatar] = useState('')
|
const [userAvatar, setUserAvatar] = useState('')
|
||||||
const [anchorElUser, setAnchorElUser] = useState<null | HTMLElement>(null)
|
const [anchorElUser, setAnchorElUser] = useState<null | HTMLElement>(null)
|
||||||
|
|
||||||
const authState = useSelector((state: State) => state.auth)
|
const authState = useAppSelector((state) => state.auth)
|
||||||
const metadataState = useSelector((state: State) => state.metadata)
|
const metadataState = useAppSelector((state) => state.metadata)
|
||||||
const userRobotImage = useSelector((state: State) => state.userRobotImage)
|
const userRobotImage = useAppSelector((state) => state.userRobotImage)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (metadataState) {
|
if (metadataState) {
|
||||||
@ -94,28 +79,7 @@ export const AppBar = () => {
|
|||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
handleCloseUserMenu()
|
handleCloseUserMenu()
|
||||||
dispatch(
|
logout()
|
||||||
setAuthState({
|
|
||||||
keyPair: undefined,
|
|
||||||
loggedIn: false,
|
|
||||||
usersPubkey: undefined,
|
|
||||||
loginMethod: undefined,
|
|
||||||
nsecBunkerPubkey: undefined
|
|
||||||
})
|
|
||||||
)
|
|
||||||
dispatch(setMetadataEvent(metadataController.getEmptyMetadataEvent()))
|
|
||||||
dispatch(setUserRobotImage(null))
|
|
||||||
|
|
||||||
// clear authToken saved in local storage
|
|
||||||
clearAuthToken()
|
|
||||||
|
|
||||||
dispatch(userLogOutAction())
|
|
||||||
|
|
||||||
// update nsecBunker delegated key after logout
|
|
||||||
const nostrController = NostrController.getInstance()
|
|
||||||
const newDelegatedKey = nostrController.generateDelegatedKey()
|
|
||||||
saveNsecBunkerDelegatedKey(newDelegatedKey)
|
|
||||||
|
|
||||||
navigate('/')
|
navigate('/')
|
||||||
}
|
}
|
||||||
const isAuthenticated = authState?.loggedIn === true
|
const isAuthenticated = authState?.loggedIn === true
|
||||||
@ -133,7 +97,7 @@ export const AppBar = () => {
|
|||||||
<Container>
|
<Container>
|
||||||
<div className={styles.bannerInner}>
|
<div className={styles.bannerInner}>
|
||||||
<p className={styles.bannerText}>
|
<p className={styles.bannerText}>
|
||||||
SIGit is currently Alpha software (available for internal
|
SIGit is currently Beta software (available for user experience
|
||||||
testing), use at your own risk!
|
testing), use at your own risk!
|
||||||
</p>
|
</p>
|
||||||
<Button
|
<Button
|
||||||
@ -165,7 +129,7 @@ export const AppBar = () => {
|
|||||||
<Button
|
<Button
|
||||||
startIcon={<ButtonIcon />}
|
startIcon={<ButtonIcon />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(appPublicRoutes.nostr)
|
launchNostrLoginDialog()
|
||||||
}}
|
}}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
>
|
>
|
||||||
|
@ -22,11 +22,16 @@ import { useSigitMeta } from '../../hooks/useSigitMeta'
|
|||||||
import { extractFileExtensions } from '../../utils/file'
|
import { extractFileExtensions } from '../../utils/file'
|
||||||
|
|
||||||
type SigitProps = {
|
type SigitProps = {
|
||||||
|
sigitCreateId: string
|
||||||
meta: Meta
|
meta: Meta
|
||||||
parsedMeta: SigitCardDisplayInfo
|
parsedMeta: SigitCardDisplayInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DisplaySigit = ({ meta, parsedMeta }: SigitProps) => {
|
export const DisplaySigit = ({
|
||||||
|
meta,
|
||||||
|
parsedMeta,
|
||||||
|
sigitCreateId: sigitCreateId
|
||||||
|
}: SigitProps) => {
|
||||||
const { title, createdAt, submittedBy, signers, signedStatus, isValid } =
|
const { title, createdAt, submittedBy, signers, signedStatus, isValid } =
|
||||||
parsedMeta
|
parsedMeta
|
||||||
|
|
||||||
@ -35,15 +40,19 @@ export const DisplaySigit = ({ meta, parsedMeta }: SigitProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.itemWrapper}>
|
<div className={styles.itemWrapper}>
|
||||||
<Link
|
{signedStatus === SigitStatus.Complete && (
|
||||||
to={
|
<Link
|
||||||
signedStatus === SigitStatus.Complete
|
to={appPublicRoutes.verify}
|
||||||
? appPublicRoutes.verify
|
state={{ meta }}
|
||||||
: appPrivateRoutes.sign
|
className={styles.insetLink}
|
||||||
}
|
></Link>
|
||||||
state={{ meta }}
|
)}
|
||||||
className={styles.insetLink}
|
{signedStatus !== SigitStatus.Complete && (
|
||||||
></Link>
|
<Link
|
||||||
|
to={`${appPrivateRoutes.sign}/${sigitCreateId}`}
|
||||||
|
className={styles.insetLink}
|
||||||
|
></Link>
|
||||||
|
)}
|
||||||
<p className={`line-clamp-2 ${styles.title}`}>{title}</p>
|
<p className={`line-clamp-2 ${styles.title}`}>{title}</p>
|
||||||
<div className={styles.users}>
|
<div className={styles.users}>
|
||||||
{submittedBy && (
|
{submittedBy && (
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
.drawingRectangle {
|
.drawingRectangle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
outline: 1px solid #01aaad;
|
outline: 1px solid #01aaad;
|
||||||
z-index: 50;
|
z-index: 40;
|
||||||
background-color: #01aaad4b;
|
background-color: #01aaad4b;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -121,7 +121,7 @@ export const Footer = () =>
|
|||||||
</Container>
|
</Container>
|
||||||
<div className={`${styles.borderTop} ${styles.credits}`}>
|
<div className={`${styles.borderTop} ${styles.credits}`}>
|
||||||
Built by
|
Built by
|
||||||
<a href="https://nostrdev.com/" target="_blank">
|
<a rel="noopener" href="https://nostrdev.com/" target="_blank">
|
||||||
Nostr Dev
|
Nostr Dev
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
2024.
|
2024.
|
||||||
|
@ -7,7 +7,8 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
z-index: 9999;
|
z-index: 50;
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { CurrentUserMark } from '../../types/mark.ts'
|
import { CurrentUserMark } from '../../types/mark.ts'
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
|
|
||||||
import { MARK_TYPE_TRANSLATION, NEXT, SIGN } from '../../utils/const.ts'
|
|
||||||
import {
|
import {
|
||||||
findNextIncompleteCurrentUserMark,
|
findNextIncompleteCurrentUserMark,
|
||||||
|
getToolboxLabelByMarkType,
|
||||||
isCurrentUserMarksComplete,
|
isCurrentUserMarksComplete,
|
||||||
isCurrentValueLast
|
isCurrentValueLast
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
@ -32,7 +31,6 @@ const MarkFormField = ({
|
|||||||
handleCurrentUserMarkChange
|
handleCurrentUserMarkChange
|
||||||
}: MarkFormFieldProps) => {
|
}: MarkFormFieldProps) => {
|
||||||
const [displayActions, setDisplayActions] = useState(true)
|
const [displayActions, setDisplayActions] = useState(true)
|
||||||
const getSubmitButtonText = () => (isReadyToSign() ? SIGN : NEXT)
|
|
||||||
const isReadyToSign = () =>
|
const isReadyToSign = () =>
|
||||||
isCurrentUserMarksComplete(currentUserMarks) ||
|
isCurrentUserMarksComplete(currentUserMarks) ||
|
||||||
isCurrentValueLast(currentUserMarks, selectedMark, selectedMarkValue)
|
isCurrentValueLast(currentUserMarks, selectedMark, selectedMarkValue)
|
||||||
@ -54,6 +52,7 @@ const MarkFormField = ({
|
|||||||
: handleCurrentUserMarkChange(findNext()!)
|
: handleCurrentUserMarkChange(findNext()!)
|
||||||
}
|
}
|
||||||
const toggleActions = () => setDisplayActions(!displayActions)
|
const toggleActions = () => setDisplayActions(!displayActions)
|
||||||
|
const markLabel = getToolboxLabelByMarkType(selectedMark.mark.type)
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.trigger}>
|
<div className={styles.trigger}>
|
||||||
@ -61,6 +60,7 @@ const MarkFormField = ({
|
|||||||
onClick={toggleActions}
|
onClick={toggleActions}
|
||||||
className={styles.triggerBtn}
|
className={styles.triggerBtn}
|
||||||
type="button"
|
type="button"
|
||||||
|
title="Toggle"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@ -78,22 +78,20 @@ const MarkFormField = ({
|
|||||||
<div className={styles.actionsWrapper}>
|
<div className={styles.actionsWrapper}>
|
||||||
<div className={styles.actionsTop}>
|
<div className={styles.actionsTop}>
|
||||||
<div className={styles.actionsTopInfo}>
|
<div className={styles.actionsTopInfo}>
|
||||||
<p className={styles.actionsTopInfoText}>Add your signature</p>
|
<p className={styles.actionsTopInfoText}>Add {markLabel}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.inputWrapper}>
|
<div className={styles.inputWrapper}>
|
||||||
<form onSubmit={(e) => handleFormSubmit(e)}>
|
<form onSubmit={(e) => handleFormSubmit(e)}>
|
||||||
<input
|
<input
|
||||||
className={styles.input}
|
className={styles.input}
|
||||||
placeholder={
|
placeholder={markLabel}
|
||||||
MARK_TYPE_TRANSLATION[selectedMark.mark.type.valueOf()]
|
|
||||||
}
|
|
||||||
onChange={handleSelectedMarkValueChange}
|
onChange={handleSelectedMarkValueChange}
|
||||||
value={selectedMarkValue}
|
value={selectedMarkValue}
|
||||||
/>
|
/>
|
||||||
<div className={styles.actionsBottom}>
|
<div className={styles.actionsBottom}>
|
||||||
<button type="submit" className={styles.submitButton}>
|
<button type="submit" className={styles.submitButton}>
|
||||||
{getSubmitButtonText()}
|
NEXT
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
left: 5px;
|
left: 5px;
|
||||||
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
z-index: 1000;
|
z-index: 40;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
transition: ease 0.2s;
|
transition: ease 0.2s;
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
.otherUserMarksDisplay {
|
.otherUserMarksDisplay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 50;
|
z-index: 40;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
fromUnixTimestamp,
|
fromUnixTimestamp,
|
||||||
hexToNpub,
|
hexToNpub,
|
||||||
npubToHex,
|
npubToHex,
|
||||||
|
SigitStatus,
|
||||||
SignStatus
|
SignStatus
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
import { useSigitMeta } from '../../hooks/useSigitMeta'
|
import { useSigitMeta } from '../../hooks/useSigitMeta'
|
||||||
@ -15,15 +16,16 @@ import {
|
|||||||
faCalendar,
|
faCalendar,
|
||||||
faCalendarCheck,
|
faCalendarCheck,
|
||||||
faCalendarPlus,
|
faCalendarPlus,
|
||||||
|
faCheck,
|
||||||
|
faClock,
|
||||||
faEye,
|
faEye,
|
||||||
faFile,
|
faFile,
|
||||||
faFileCircleExclamation
|
faFileCircleExclamation
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
import { getExtensionIconLabel } from '../getExtensionIconLabel'
|
import { getExtensionIconLabel } from '../getExtensionIconLabel'
|
||||||
import { useSelector } from 'react-redux'
|
import { useAppSelector } from '../../hooks/store'
|
||||||
import { State } from '../../store/rootReducer'
|
|
||||||
import { DisplaySigner } from '../DisplaySigner'
|
import { DisplaySigner } from '../DisplaySigner'
|
||||||
import { Meta } from '../../types'
|
import { Meta, OpenTimestamp } from '../../types'
|
||||||
import { extractFileExtensions } from '../../utils/file'
|
import { extractFileExtensions } from '../../utils/file'
|
||||||
import { UserAvatar } from '../UserAvatar'
|
import { UserAvatar } from '../UserAvatar'
|
||||||
|
|
||||||
@ -43,15 +45,61 @@ export const UsersDetails = ({ meta }: UsersDetailsProps) => {
|
|||||||
completedAt,
|
completedAt,
|
||||||
parsedSignatureEvents,
|
parsedSignatureEvents,
|
||||||
signedStatus,
|
signedStatus,
|
||||||
isValid
|
isValid,
|
||||||
|
id,
|
||||||
|
timestamps
|
||||||
} = useSigitMeta(meta)
|
} = useSigitMeta(meta)
|
||||||
const { usersPubkey } = useSelector((state: State) => state.auth)
|
const { usersPubkey } = useAppSelector((state) => state.auth)
|
||||||
const userCanSign =
|
const userCanSign =
|
||||||
typeof usersPubkey !== 'undefined' &&
|
typeof usersPubkey !== 'undefined' &&
|
||||||
signers.includes(hexToNpub(usersPubkey))
|
signers.includes(hexToNpub(usersPubkey))
|
||||||
|
|
||||||
const { extensions, isSame } = extractFileExtensions(Object.keys(fileHashes))
|
const { extensions, isSame } = extractFileExtensions(Object.keys(fileHashes))
|
||||||
|
|
||||||
|
const isTimestampVerified = (
|
||||||
|
timestamps: OpenTimestamp[],
|
||||||
|
nostrId: string
|
||||||
|
): boolean => {
|
||||||
|
const matched = timestamps.find((t) => t.nostrId === nostrId)
|
||||||
|
return !!(matched && matched.verification)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getOpenTimestampsInfo = (
|
||||||
|
timestamps: OpenTimestamp[],
|
||||||
|
nostrId: string
|
||||||
|
) => {
|
||||||
|
if (isTimestampVerified(timestamps, nostrId)) {
|
||||||
|
return <FontAwesomeIcon icon={faCheck} />
|
||||||
|
} else {
|
||||||
|
return <FontAwesomeIcon icon={faClock} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCompletedOpenTimestampsInfo = (timestamp: OpenTimestamp) => {
|
||||||
|
if (timestamp.verification) {
|
||||||
|
return <FontAwesomeIcon icon={faCheck} />
|
||||||
|
} else {
|
||||||
|
return <FontAwesomeIcon icon={faClock} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTimestampTooltipTitle = (label: string, isVerified: boolean) => {
|
||||||
|
return `${label} / Open Timestamp ${isVerified ? 'Verified' : 'Pending'}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const isUserSignatureTimestampVerified = () => {
|
||||||
|
if (
|
||||||
|
userCanSign &&
|
||||||
|
hexToNpub(usersPubkey) in parsedSignatureEvents &&
|
||||||
|
timestamps &&
|
||||||
|
timestamps.length > 0
|
||||||
|
) {
|
||||||
|
const nostrId = parsedSignatureEvents[hexToNpub(usersPubkey)].id
|
||||||
|
return isTimestampVerified(timestamps, nostrId)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return submittedBy ? (
|
return submittedBy ? (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.section}>
|
<div className={styles.section}>
|
||||||
@ -116,19 +164,36 @@ export const UsersDetails = ({ meta }: UsersDetailsProps) => {
|
|||||||
<p>Details</p>
|
<p>Details</p>
|
||||||
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={'Publication date'}
|
title={getTimestampTooltipTitle(
|
||||||
|
'Publication date',
|
||||||
|
!!(timestamps && id && isTimestampVerified(timestamps, id))
|
||||||
|
)}
|
||||||
placement="top"
|
placement="top"
|
||||||
arrow
|
arrow
|
||||||
disableInteractive
|
disableInteractive
|
||||||
>
|
>
|
||||||
<span className={styles.detailsItem}>
|
<span className={styles.detailsItem}>
|
||||||
<FontAwesomeIcon icon={faCalendarPlus} />{' '}
|
<FontAwesomeIcon icon={faCalendarPlus} />{' '}
|
||||||
{createdAt ? formatTimestamp(createdAt) : <>—</>}
|
{createdAt ? formatTimestamp(createdAt) : <>—</>}{' '}
|
||||||
|
{timestamps && timestamps.length > 0 && id && (
|
||||||
|
<span className={styles.ticket}>
|
||||||
|
{getOpenTimestampsInfo(timestamps, id)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={'Completion date'}
|
title={getTimestampTooltipTitle(
|
||||||
|
'Completion date',
|
||||||
|
!!(
|
||||||
|
signedStatus === SigitStatus.Complete &&
|
||||||
|
completedAt &&
|
||||||
|
timestamps &&
|
||||||
|
timestamps.length > 0 &&
|
||||||
|
timestamps[timestamps.length - 1].verification
|
||||||
|
)
|
||||||
|
)}
|
||||||
placement="top"
|
placement="top"
|
||||||
arrow
|
arrow
|
||||||
disableInteractive
|
disableInteractive
|
||||||
@ -136,13 +201,26 @@ export const UsersDetails = ({ meta }: UsersDetailsProps) => {
|
|||||||
<span className={styles.detailsItem}>
|
<span className={styles.detailsItem}>
|
||||||
<FontAwesomeIcon icon={faCalendarCheck} />{' '}
|
<FontAwesomeIcon icon={faCalendarCheck} />{' '}
|
||||||
{completedAt ? formatTimestamp(completedAt) : <>—</>}
|
{completedAt ? formatTimestamp(completedAt) : <>—</>}
|
||||||
|
{signedStatus === SigitStatus.Complete &&
|
||||||
|
completedAt &&
|
||||||
|
timestamps &&
|
||||||
|
timestamps.length > 0 && (
|
||||||
|
<span className={styles.ticket}>
|
||||||
|
{getCompletedOpenTimestampsInfo(
|
||||||
|
timestamps[timestamps.length - 1]
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{/* User signed date */}
|
{/* User signed date */}
|
||||||
{userCanSign ? (
|
{userCanSign ? (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={'Your signature date'}
|
title={getTimestampTooltipTitle(
|
||||||
|
'Your signature date',
|
||||||
|
isUserSignatureTimestampVerified()
|
||||||
|
)}
|
||||||
placement="top"
|
placement="top"
|
||||||
arrow
|
arrow
|
||||||
disableInteractive
|
disableInteractive
|
||||||
@ -162,6 +240,16 @@ export const UsersDetails = ({ meta }: UsersDetailsProps) => {
|
|||||||
) : (
|
) : (
|
||||||
<>—</>
|
<>—</>
|
||||||
)}
|
)}
|
||||||
|
{hexToNpub(usersPubkey) in parsedSignatureEvents &&
|
||||||
|
timestamps &&
|
||||||
|
timestamps.length > 0 && (
|
||||||
|
<span className={styles.ticket}>
|
||||||
|
{getOpenTimestampsInfo(
|
||||||
|
timestamps,
|
||||||
|
parsedSignatureEvents[hexToNpub(usersPubkey)].id
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -31,8 +31,6 @@
|
|||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
justify-content: start;
|
|
||||||
|
|
||||||
> :first-child {
|
> :first-child {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
@ -44,3 +42,7 @@
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ticket {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Typography } from '@mui/material'
|
import { Typography } from '@mui/material'
|
||||||
import { useSelector } from 'react-redux'
|
import { useAppSelector } from '../hooks/store'
|
||||||
import { State } from '../store/rootReducer'
|
|
||||||
|
|
||||||
import styles from './username.module.scss'
|
import styles from './username.module.scss'
|
||||||
import { AvatarIconButton } from './UserAvatarIconButton'
|
import { AvatarIconButton } from './UserAvatarIconButton'
|
||||||
@ -16,7 +15,7 @@ type Props = {
|
|||||||
* Clicking will open the menu.
|
* Clicking will open the menu.
|
||||||
*/
|
*/
|
||||||
const Username = ({ username, avatarContent, handleClick }: Props) => {
|
const Username = ({ username, avatarContent, handleClick }: Props) => {
|
||||||
const hexKey = useSelector((state: State) => state.auth.usersPubkey)
|
const hexKey = useAppSelector((state) => state.auth.usersPubkey)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
|
@ -31,7 +31,7 @@ export class AuthController {
|
|||||||
/**
|
/**
|
||||||
* Function will authenticate user by signing an auth event
|
* Function will authenticate user by signing an auth event
|
||||||
* which is done by calling the sign() function, where appropriate
|
* which is done by calling the sign() function, where appropriate
|
||||||
* method will be chosen (extension, nsecbunker or keys)
|
* method will be chosen (extension or keys)
|
||||||
*
|
*
|
||||||
* @param pubkey of the user trying to login
|
* @param pubkey of the user trying to login
|
||||||
* @returns url to redirect if authentication successfull
|
* @returns url to redirect if authentication successfull
|
||||||
@ -57,12 +57,15 @@ export class AuthController {
|
|||||||
|
|
||||||
// Nostr uses unix timestamps
|
// Nostr uses unix timestamps
|
||||||
const timestamp = unixNow()
|
const timestamp = unixNow()
|
||||||
const { hostname } = window.location
|
const { href } = window.location
|
||||||
|
|
||||||
const authEvent: EventTemplate = {
|
const authEvent: EventTemplate = {
|
||||||
kind: 27235,
|
kind: 27235,
|
||||||
tags: [],
|
tags: [
|
||||||
content: `${hostname}-${timestamp}`,
|
['u', href],
|
||||||
|
['method', 'GET']
|
||||||
|
],
|
||||||
|
content: '',
|
||||||
created_at: timestamp
|
created_at: timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +86,7 @@ export class AuthController {
|
|||||||
return Promise.resolve(appPrivateRoutes.relays)
|
return Promise.resolve(appPrivateRoutes.relays)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (store.getState().auth?.loggedIn) {
|
if (store.getState().auth.loggedIn) {
|
||||||
if (!compareObjects(store.getState().relays?.map, relayMap.map))
|
if (!compareObjects(store.getState().relays?.map, relayMap.map))
|
||||||
store.dispatch(setRelayMapAction(relayMap.map))
|
store.dispatch(setRelayMapAction(relayMap.map))
|
||||||
}
|
}
|
||||||
|
@ -1,194 +1,24 @@
|
|||||||
import NDK, {
|
import { EventTemplate, UnsignedEvent } from 'nostr-tools'
|
||||||
NDKEvent,
|
import { WindowNostr } from 'nostr-tools/nip07'
|
||||||
NDKNip46Signer,
|
|
||||||
NDKPrivateKeySigner,
|
|
||||||
NDKUser,
|
|
||||||
NostrEvent
|
|
||||||
} from '@nostr-dev-kit/ndk'
|
|
||||||
import {
|
|
||||||
Event,
|
|
||||||
EventTemplate,
|
|
||||||
UnsignedEvent,
|
|
||||||
finalizeEvent,
|
|
||||||
nip04,
|
|
||||||
nip19,
|
|
||||||
nip44
|
|
||||||
} from 'nostr-tools'
|
|
||||||
import { EventEmitter } from 'tseep'
|
import { EventEmitter } from 'tseep'
|
||||||
import { updateNsecbunkerPubkey } from '../store/actions'
|
|
||||||
import { AuthState, LoginMethods } from '../store/auth/types'
|
|
||||||
import store from '../store/store'
|
import store from '../store/store'
|
||||||
import { SignedEvent } from '../types'
|
import { SignedEvent } from '../types'
|
||||||
import { getNsecBunkerDelegatedKey, verifySignedEvent } from '../utils'
|
import { LoginMethodContext } from '../services/LoginMethodStrategy/loginMethodContext'
|
||||||
|
|
||||||
export class NostrController extends EventEmitter {
|
export class NostrController extends EventEmitter {
|
||||||
private static instance: NostrController
|
private static instance: NostrController
|
||||||
|
|
||||||
private bunkerNDK: NDK | undefined
|
|
||||||
private remoteSigner: NDKNip46Signer | undefined
|
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
private getNostrObject = () => {
|
private getNostrObject = () => {
|
||||||
// fix: this is not picking up type declaration from src/system/index.d.ts
|
if (window.nostr) return window.nostr as WindowNostr
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
if (window.nostr) return window.nostr as any
|
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`window.nostr object not present. Make sure you have an nostr extension installed/working properly.`
|
`window.nostr object not present. Make sure you have an nostr extension installed/working properly.`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
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<NDKNip46Signer> => {
|
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
public static getInstance(): NostrController {
|
||||||
if (!NostrController.instance) {
|
if (!NostrController.instance) {
|
||||||
NostrController.instance = new NostrController()
|
NostrController.instance = new NostrController()
|
||||||
@ -206,60 +36,11 @@ export class NostrController extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
nip44Encrypt = async (receiver: string, content: string) => {
|
nip44Encrypt = async (receiver: string, content: string) => {
|
||||||
// Retrieve the current login method from the application's redux state.
|
// Retrieve the current login method from the application's redux state.
|
||||||
const loginMethod = (store.getState().auth as AuthState).loginMethod
|
const loginMethod = store.getState().auth.loginMethod
|
||||||
|
const context = new LoginMethodContext(loginMethod)
|
||||||
|
|
||||||
// Handle encryption when the login method is via an extension.
|
// Handle encryption when the login method is via an extension.
|
||||||
if (loginMethod === LoginMethods.extension) {
|
return await context.nip44Encrypt(receiver, content)
|
||||||
const nostr = this.getNostrObject()
|
|
||||||
|
|
||||||
// Check if the nostr object supports NIP-44 encryption.
|
|
||||||
if (!nostr.nip44) {
|
|
||||||
throw new Error(
|
|
||||||
`Your nostr extension does not support nip44 encryption & decryption`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encrypt the content using NIP-44 provided by the nostr extension.
|
|
||||||
const encrypted = await nostr.nip44.encrypt(receiver, content)
|
|
||||||
return encrypted as string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle encryption when the login method is via a private key.
|
|
||||||
if (loginMethod === LoginMethods.privateKey) {
|
|
||||||
const keys = (store.getState().auth as AuthState).keyPair
|
|
||||||
|
|
||||||
// Check if the private and public key pair is available.
|
|
||||||
if (!keys) {
|
|
||||||
throw new Error(
|
|
||||||
`Login method is ${LoginMethods.privateKey} but private & public key pair is not found.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode the private key.
|
|
||||||
const { private: nsec } = keys
|
|
||||||
const privateKey = nip19.decode(nsec).data as Uint8Array
|
|
||||||
|
|
||||||
// Generate the conversation key using NIP-44 utilities.
|
|
||||||
const nip44ConversationKey = nip44.v2.utils.getConversationKey(
|
|
||||||
privateKey,
|
|
||||||
receiver
|
|
||||||
)
|
|
||||||
|
|
||||||
// Encrypt the content using the generated conversation key.
|
|
||||||
const encrypted = nip44.v2.encrypt(content, nip44ConversationKey)
|
|
||||||
|
|
||||||
return encrypted
|
|
||||||
}
|
|
||||||
|
|
||||||
// Throw an error if the login method is nsecBunker (not supported).
|
|
||||||
if (loginMethod === LoginMethods.nsecBunker) {
|
|
||||||
throw new Error(
|
|
||||||
`nip44 encryption is not yet supported for login method '${LoginMethods.nsecBunker}'`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Throw an error if the login method is undefined or unsupported.
|
|
||||||
throw new Error('Login method is undefined')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -272,180 +53,33 @@ export class NostrController extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
nip44Decrypt = async (sender: string, content: string) => {
|
nip44Decrypt = async (sender: string, content: string) => {
|
||||||
// Retrieve the current login method from the application's redux state.
|
// Retrieve the current login method from the application's redux state.
|
||||||
const loginMethod = (store.getState().auth as AuthState).loginMethod
|
const loginMethod = store.getState().auth.loginMethod
|
||||||
|
const context = new LoginMethodContext(loginMethod)
|
||||||
|
|
||||||
// Handle decryption when the login method is via an extension.
|
// Handle decryption
|
||||||
if (loginMethod === LoginMethods.extension) {
|
return await context.nip44Decrypt(sender, content)
|
||||||
const nostr = this.getNostrObject()
|
|
||||||
|
|
||||||
// Check if the nostr object supports NIP-44 decryption.
|
|
||||||
if (!nostr.nip44) {
|
|
||||||
throw new Error(
|
|
||||||
`Your nostr extension does not support nip44 encryption & decryption`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrypt the content using NIP-44 provided by the nostr extension.
|
|
||||||
const decrypted = await nostr.nip44.decrypt(sender, content)
|
|
||||||
return decrypted as string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle decryption when the login method is via a private key.
|
|
||||||
if (loginMethod === LoginMethods.privateKey) {
|
|
||||||
const keys = (store.getState().auth as AuthState).keyPair
|
|
||||||
|
|
||||||
// Check if the private and public key pair is available.
|
|
||||||
if (!keys) {
|
|
||||||
throw new Error(
|
|
||||||
`Login method is ${LoginMethods.privateKey} but private & public key pair is not found.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode the private key.
|
|
||||||
const { private: nsec } = keys
|
|
||||||
const privateKey = nip19.decode(nsec).data as Uint8Array
|
|
||||||
|
|
||||||
// Generate the conversation key using NIP-44 utilities.
|
|
||||||
const nip44ConversationKey = nip44.v2.utils.getConversationKey(
|
|
||||||
privateKey,
|
|
||||||
sender
|
|
||||||
)
|
|
||||||
|
|
||||||
// Decrypt the content using the generated conversation key.
|
|
||||||
const decrypted = nip44.v2.decrypt(content, nip44ConversationKey)
|
|
||||||
|
|
||||||
return decrypted
|
|
||||||
}
|
|
||||||
|
|
||||||
// Throw an error if the login method is nsecBunker (not supported).
|
|
||||||
if (loginMethod === LoginMethods.nsecBunker) {
|
|
||||||
throw new Error(
|
|
||||||
`nip44 decryption is not yet supported for login method '${LoginMethods.nsecBunker}'`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Throw an error if the login method is undefined or unsupported.
|
|
||||||
throw new Error('Login method is undefined')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signs an event with private key (if it is present in local storage) or
|
* Signs an event with private key (if it is present in local storage) or
|
||||||
* with browser extension (if it is present) or
|
* with browser extension (if it is present)
|
||||||
* with nSecBunker instance.
|
|
||||||
* @param event - unsigned nostr event.
|
* @param event - unsigned nostr event.
|
||||||
* @returns - a promised that is resolved with signed nostr event.
|
* @returns - a promised that is resolved with signed nostr event.
|
||||||
*/
|
*/
|
||||||
signEvent = async (
|
signEvent = async (
|
||||||
event: UnsignedEvent | EventTemplate
|
event: UnsignedEvent | EventTemplate
|
||||||
): Promise<SignedEvent> => {
|
): Promise<SignedEvent> => {
|
||||||
const loginMethod = (store.getState().auth as AuthState).loginMethod
|
const loginMethod = store.getState().auth.loginMethod
|
||||||
|
const context = new LoginMethodContext(loginMethod)
|
||||||
|
|
||||||
if (!loginMethod) {
|
return await context.signEvent(event)
|
||||||
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: nsec } = keys
|
|
||||||
const privateKey = nip19.decode(nsec).data as Uint8Array
|
|
||||||
|
|
||||||
const signedEvent = finalizeEvent(event, privateKey)
|
|
||||||
|
|
||||||
verifySignedEvent(signedEvent)
|
|
||||||
|
|
||||||
return Promise.resolve(signedEvent)
|
|
||||||
} else if (loginMethod === LoginMethods.extension) {
|
|
||||||
const nostr = this.getNostrObject()
|
|
||||||
|
|
||||||
return (await nostr
|
|
||||||
.signEvent(event as NostrEvent)
|
|
||||||
.catch((err: unknown) => {
|
|
||||||
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`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nip04Encrypt = async (receiver: string, content: string): Promise<string> => {
|
nip04Encrypt = async (receiver: string, content: string): Promise<string> => {
|
||||||
const loginMethod = (store.getState().auth as AuthState).loginMethod
|
const loginMethod = store.getState().auth.loginMethod
|
||||||
|
const context = new LoginMethodContext(loginMethod)
|
||||||
|
|
||||||
if (loginMethod === LoginMethods.extension) {
|
return await context.nip04Encrypt(receiver, content)
|
||||||
const nostr = this.getNostrObject()
|
|
||||||
|
|
||||||
if (!nostr.nip04) {
|
|
||||||
throw new Error(
|
|
||||||
`Your nostr extension does not support nip04 encryption & decryption`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const encrypted = await nostr.nip04.encrypt(receiver, content)
|
|
||||||
return encrypted
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loginMethod === LoginMethods.privateKey) {
|
|
||||||
const keys = (store.getState().auth as AuthState).keyPair
|
|
||||||
|
|
||||||
if (!keys) {
|
|
||||||
throw new Error(
|
|
||||||
`Login method is ${LoginMethods.privateKey} but private & public key pair is not found.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const { private: nsec } = keys
|
|
||||||
const privateKey = nip19.decode(nsec).data as Uint8Array
|
|
||||||
|
|
||||||
const encrypted = await nip04.encrypt(privateKey, receiver, content)
|
|
||||||
return encrypted
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loginMethod === LoginMethods.nsecBunker) {
|
|
||||||
const user = new NDKUser({ pubkey: receiver })
|
|
||||||
|
|
||||||
this.remoteSigner?.on('authUrl', (authUrl) => {
|
|
||||||
this.emit('nsecbunker-auth', authUrl)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!this.remoteSigner) throw new Error('Remote signer is undefined.')
|
|
||||||
const encrypted = await this.remoteSigner.encrypt(user, content)
|
|
||||||
|
|
||||||
return encrypted
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('Login method is undefined')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -456,51 +90,10 @@ export class NostrController extends EventEmitter {
|
|||||||
* @returns A promise that resolves to the decrypted content.
|
* @returns A promise that resolves to the decrypted content.
|
||||||
*/
|
*/
|
||||||
nip04Decrypt = async (sender: string, content: string): Promise<string> => {
|
nip04Decrypt = async (sender: string, content: string): Promise<string> => {
|
||||||
const loginMethod = (store.getState().auth as AuthState).loginMethod
|
const loginMethod = store.getState().auth.loginMethod
|
||||||
|
const context = new LoginMethodContext(loginMethod)
|
||||||
|
|
||||||
if (loginMethod === LoginMethods.extension) {
|
return await context.nip04Decrypt(sender, content)
|
||||||
const nostr = this.getNostrObject()
|
|
||||||
|
|
||||||
if (!nostr.nip04) {
|
|
||||||
throw new Error(
|
|
||||||
`Your nostr extension does not support nip04 encryption & decryption`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const decrypted = await nostr.nip04.decrypt(sender, content)
|
|
||||||
return decrypted
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loginMethod === LoginMethods.privateKey) {
|
|
||||||
const keys = (store.getState().auth as AuthState).keyPair
|
|
||||||
|
|
||||||
if (!keys) {
|
|
||||||
throw new Error(
|
|
||||||
`Login method is ${LoginMethods.privateKey} but private & public key pair is not found.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const { private: nsec } = keys
|
|
||||||
const privateKey = nip19.decode(nsec).data as Uint8Array
|
|
||||||
|
|
||||||
const decrypted = await nip04.decrypt(privateKey, sender, content)
|
|
||||||
return decrypted
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loginMethod === LoginMethods.nsecBunker) {
|
|
||||||
const user = new NDKUser({ pubkey: sender })
|
|
||||||
|
|
||||||
this.remoteSigner?.on('authUrl', (authUrl) => {
|
|
||||||
this.emit('nsecbunker-auth', authUrl)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!this.remoteSigner) throw new Error('Remote signer is undefined.')
|
|
||||||
const decrypted = await this.remoteSigner.decrypt(user, content)
|
|
||||||
|
|
||||||
return decrypted
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('Login method is undefined')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -523,12 +116,4 @@ export class NostrController extends EventEmitter {
|
|||||||
|
|
||||||
return Promise.resolve(pubKey)
|
return Promise.resolve(pubKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates NDK Private Signer
|
|
||||||
* @returns nSecBunker delegated key
|
|
||||||
*/
|
|
||||||
generateDelegatedKey = (): string => {
|
|
||||||
return NDKPrivateKeySigner.generate().privateKey!
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
26
src/hooks/useLogout.tsx
Normal file
26
src/hooks/useLogout.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { logout as nostrLogout } from 'nostr-login'
|
||||||
|
import { clear } from '../utils/localStorage'
|
||||||
|
import { userLogOutAction } from '../store/actions'
|
||||||
|
import { LoginMethod } from '../store/auth/types'
|
||||||
|
import { useAppDispatch, useAppSelector } from './store'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
export const useLogout = () => {
|
||||||
|
const loginMethod = useAppSelector((state) => state.auth?.loginMethod)
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
|
const logout = useCallback(() => {
|
||||||
|
// Log out of the nostr-login
|
||||||
|
if (loginMethod === LoginMethod.nostrLogin) {
|
||||||
|
nostrLogout()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset redux state with the logout
|
||||||
|
dispatch(userLogOutAction())
|
||||||
|
|
||||||
|
// Clear the local storage states
|
||||||
|
clear()
|
||||||
|
}, [dispatch, loginMethod])
|
||||||
|
|
||||||
|
return logout
|
||||||
|
}
|
@ -19,7 +19,6 @@ import { toast } from 'react-toastify'
|
|||||||
import { verifyEvent } from 'nostr-tools'
|
import { verifyEvent } from 'nostr-tools'
|
||||||
import { Event } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
import store from '../store/store'
|
import store from '../store/store'
|
||||||
import { AuthState } from '../store/auth/types'
|
|
||||||
import { NostrController } from '../controllers'
|
import { NostrController } from '../controllers'
|
||||||
import { MetaParseError } from '../types/errors/MetaParseError'
|
import { MetaParseError } from '../types/errors/MetaParseError'
|
||||||
|
|
||||||
@ -146,7 +145,7 @@ export const useSigitMeta = (meta: Meta): FlatMeta => {
|
|||||||
if (meta.keys) {
|
if (meta.keys) {
|
||||||
const { sender, keys } = meta.keys
|
const { sender, keys } = meta.keys
|
||||||
// Retrieve the user's public key from the state
|
// Retrieve the user's public key from the state
|
||||||
const usersPubkey = (store.getState().auth as AuthState).usersPubkey!
|
const usersPubkey = store.getState().auth.usersPubkey!
|
||||||
const usersNpub = hexToNpub(usersPubkey)
|
const usersNpub = hexToNpub(usersPubkey)
|
||||||
|
|
||||||
// Check if the user's public key is in the keys object
|
// Check if the user's public key is in the keys object
|
||||||
|
@ -27,7 +27,7 @@ body {
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
top: 80px;
|
top: 80px;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
z-index: 100;
|
z-index: 40;
|
||||||
}
|
}
|
||||||
|
|
||||||
#root {
|
#root {
|
||||||
|
@ -1,87 +1,160 @@
|
|||||||
import { Event, kinds } from 'nostr-tools'
|
import { Event, getPublicKey, kinds, nip19 } from 'nostr-tools'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { Outlet, useNavigate, useSearchParams } from 'react-router-dom'
|
||||||
import { Outlet } from 'react-router-dom'
|
|
||||||
import { AppBar } from '../components/AppBar/AppBar'
|
import { AppBar } from '../components/AppBar/AppBar'
|
||||||
import { LoadingSpinner } from '../components/LoadingSpinner'
|
import { LoadingSpinner } from '../components/LoadingSpinner'
|
||||||
import { MetadataController, NostrController } from '../controllers'
|
import {
|
||||||
|
AuthController,
|
||||||
|
MetadataController,
|
||||||
|
NostrController
|
||||||
|
} from '../controllers'
|
||||||
import {
|
import {
|
||||||
restoreState,
|
restoreState,
|
||||||
setAuthState,
|
|
||||||
setMetadataEvent,
|
setMetadataEvent,
|
||||||
|
updateKeyPair,
|
||||||
|
updateLoginMethod,
|
||||||
|
updateNostrLoginAuthMethod,
|
||||||
updateUserAppData
|
updateUserAppData
|
||||||
} from '../store/actions'
|
} from '../store/actions'
|
||||||
import { LoginMethods } from '../store/auth/types'
|
|
||||||
import { State } from '../store/rootReducer'
|
|
||||||
import { Dispatch } from '../store/store'
|
|
||||||
import { setUserRobotImage } from '../store/userRobotImage/action'
|
import { setUserRobotImage } from '../store/userRobotImage/action'
|
||||||
import {
|
import {
|
||||||
clearAuthToken,
|
|
||||||
clearState,
|
|
||||||
getRoboHashPicture,
|
getRoboHashPicture,
|
||||||
getUsersAppData,
|
getUsersAppData,
|
||||||
loadState,
|
loadState,
|
||||||
saveNsecBunkerDelegatedKey,
|
|
||||||
subscribeForSigits
|
subscribeForSigits
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { useAppSelector } from '../hooks'
|
import { useAppDispatch, useAppSelector } from '../hooks'
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
|
import { useLogout } from '../hooks/useLogout'
|
||||||
|
import { LoginMethod } from '../store/auth/types'
|
||||||
|
import { NostrLoginAuthOptions } from 'nostr-login/dist/types'
|
||||||
|
import { init as initNostrLogin } from 'nostr-login'
|
||||||
|
|
||||||
export const MainLayout = () => {
|
export const MainLayout = () => {
|
||||||
const dispatch: Dispatch = useDispatch()
|
const [searchParams, setSearchParams] = useSearchParams()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
const logout = useLogout()
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState(`Loading App`)
|
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState(`Loading App`)
|
||||||
const authState = useSelector((state: State) => state.auth)
|
const isLoggedIn = useAppSelector((state) => state.auth?.loggedIn)
|
||||||
|
const authState = useAppSelector((state) => state.auth)
|
||||||
const usersAppData = useAppSelector((state) => state.userAppData)
|
const usersAppData = useAppSelector((state) => state.userAppData)
|
||||||
|
|
||||||
// Ref to track if `subscribeForSigits` has been called
|
// Ref to track if `subscribeForSigits` has been called
|
||||||
const hasSubscribed = useRef(false)
|
const hasSubscribed = useRef(false)
|
||||||
|
|
||||||
|
const navigateAfterLogin = (path: string) => {
|
||||||
|
const callbackPath = searchParams.get('callbackPath')
|
||||||
|
|
||||||
|
if (callbackPath) {
|
||||||
|
// base64 decoded path
|
||||||
|
const path = atob(callbackPath)
|
||||||
|
navigate(path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
navigate(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
const login = useCallback(async () => {
|
||||||
|
const nostrController = NostrController.getInstance()
|
||||||
|
const authController = new AuthController()
|
||||||
|
const pubkey = await nostrController.capturePublicKey()
|
||||||
|
|
||||||
|
dispatch(updateLoginMethod(LoginMethod.nostrLogin))
|
||||||
|
|
||||||
|
const redirectPath =
|
||||||
|
await authController.authAndGetMetadataAndRelaysMap(pubkey)
|
||||||
|
|
||||||
|
if (redirectPath) {
|
||||||
|
navigateAfterLogin(redirectPath)
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [dispatch])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Developer login with ?nsec= (not recommended)
|
||||||
|
const nsec = searchParams.get('nsec')
|
||||||
|
if (!nsec) return
|
||||||
|
|
||||||
|
// Clear nsec from the url immediately
|
||||||
|
searchParams.delete('nsec')
|
||||||
|
setSearchParams(searchParams)
|
||||||
|
|
||||||
|
if (!authState?.loggedIn) {
|
||||||
|
if (!nsec.startsWith('nsec')) {
|
||||||
|
console.error('Invalid format, use private key (nsec)')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const privateKey = nip19.decode(nsec).data as Uint8Array
|
||||||
|
if (!privateKey) {
|
||||||
|
console.error('Failed to convert the private key.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const publickey = getPublicKey(privateKey)
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
updateKeyPair({
|
||||||
|
private: nsec,
|
||||||
|
public: publickey
|
||||||
|
})
|
||||||
|
)
|
||||||
|
dispatch(updateLoginMethod(LoginMethod.privateKey))
|
||||||
|
|
||||||
|
const authController = new AuthController()
|
||||||
|
authController
|
||||||
|
.authAndGetMetadataAndRelaysMap(publickey)
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('Error occurred in authentication: ' + err)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error decoding the nsec. ${err}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [dispatch, searchParams])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleNostrAuth = (_: string, opts: NostrLoginAuthOptions) => {
|
||||||
|
if (opts.type === 'login' || opts.type === 'signup') {
|
||||||
|
dispatch(updateNostrLoginAuthMethod(opts.method))
|
||||||
|
login()
|
||||||
|
} else if (opts.type === 'logout') {
|
||||||
|
// Clear `subscribeForSigits` as called after the logout
|
||||||
|
hasSubscribed.current = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the nostr-login
|
||||||
|
initNostrLogin({
|
||||||
|
methods: ['connect', 'extension', 'local'],
|
||||||
|
noBanner: true,
|
||||||
|
onAuth: handleNostrAuth
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error('Failed to initialize Nostr-Login', error)
|
||||||
|
})
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [dispatch])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const metadataController = MetadataController.getInstance()
|
const metadataController = MetadataController.getInstance()
|
||||||
|
|
||||||
const logout = () => {
|
|
||||||
dispatch(
|
|
||||||
setAuthState({
|
|
||||||
keyPair: undefined,
|
|
||||||
loggedIn: false,
|
|
||||||
usersPubkey: undefined,
|
|
||||||
loginMethod: undefined,
|
|
||||||
nsecBunkerPubkey: undefined
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
dispatch(setMetadataEvent(metadataController.getEmptyMetadataEvent()))
|
|
||||||
|
|
||||||
// clear authToken saved in local storage
|
|
||||||
clearAuthToken()
|
|
||||||
clearState()
|
|
||||||
|
|
||||||
// update nsecBunker delegated key
|
|
||||||
const newDelegatedKey =
|
|
||||||
NostrController.getInstance().generateDelegatedKey()
|
|
||||||
saveNsecBunkerDelegatedKey(newDelegatedKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
const restoredState = loadState()
|
const restoredState = loadState()
|
||||||
if (restoredState) {
|
if (restoredState) {
|
||||||
dispatch(restoreState(restoredState))
|
dispatch(restoreState(restoredState))
|
||||||
|
|
||||||
const { loggedIn, loginMethod, usersPubkey, nsecBunkerRelays } =
|
const { loggedIn, loginMethod, usersPubkey } = restoredState.auth
|
||||||
restoredState.auth
|
|
||||||
|
|
||||||
if (loggedIn) {
|
if (loggedIn) {
|
||||||
if (!loginMethod || !usersPubkey) return logout()
|
if (!loginMethod || !usersPubkey) return logout()
|
||||||
|
|
||||||
if (loginMethod === LoginMethods.nsecBunker) {
|
// Update user profile metadata, old state might be outdated
|
||||||
if (!nsecBunkerRelays) return logout()
|
|
||||||
|
|
||||||
const nostrController = NostrController.getInstance()
|
|
||||||
nostrController.nsecBunkerInit(nsecBunkerRelays).then(() => {
|
|
||||||
nostrController.createNsecBunkerSigner(usersPubkey)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleMetadataEvent = (event: Event) => {
|
const handleMetadataEvent = (event: Event) => {
|
||||||
dispatch(setMetadataEvent(event))
|
dispatch(setMetadataEvent(event))
|
||||||
}
|
}
|
||||||
@ -101,10 +174,14 @@ export const MainLayout = () => {
|
|||||||
} else {
|
} else {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [dispatch])
|
}, [dispatch])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe for the sigits
|
||||||
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (authState.loggedIn && usersAppData) {
|
if (authState && isLoggedIn && usersAppData) {
|
||||||
const pubkey = authState.usersPubkey || authState.keyPair?.public
|
const pubkey = authState.usersPubkey || authState.keyPair?.public
|
||||||
|
|
||||||
if (pubkey && !hasSubscribed.current) {
|
if (pubkey && !hasSubscribed.current) {
|
||||||
@ -116,7 +193,7 @@ export const MainLayout = () => {
|
|||||||
hasSubscribed.current = true
|
hasSubscribed.current = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [authState, usersAppData])
|
}, [authState, isLoggedIn, usersAppData])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When authState change user logged in / or app reloaded
|
* When authState change user logged in / or app reloaded
|
||||||
@ -124,7 +201,7 @@ export const MainLayout = () => {
|
|||||||
* so that avatar will be consistent across the app when kind 0 is empty
|
* so that avatar will be consistent across the app when kind 0 is empty
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (authState && authState.loggedIn) {
|
if (authState && isLoggedIn) {
|
||||||
const pubkey = authState.usersPubkey || authState.keyPair?.public
|
const pubkey = authState.usersPubkey || authState.keyPair?.public
|
||||||
|
|
||||||
if (pubkey) {
|
if (pubkey) {
|
||||||
@ -141,7 +218,8 @@ export const MainLayout = () => {
|
|||||||
})
|
})
|
||||||
.finally(() => setIsLoading(false))
|
.finally(() => setIsLoading(false))
|
||||||
}
|
}
|
||||||
}, [authState, dispatch])
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [dispatch, isLoggedIn])
|
||||||
|
|
||||||
if (isLoading) return <LoadingSpinner desc={loadingSpinnerDesc} />
|
if (isLoading) return <LoadingSpinner desc={loadingSpinnerDesc} />
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ export const Modal = () => {
|
|||||||
{ to: appPublicRoutes.register, title: 'Register', label: 'Register' },
|
{ to: appPublicRoutes.register, title: 'Register', label: 'Register' },
|
||||||
{
|
{
|
||||||
to: appPublicRoutes.nostr,
|
to: appPublicRoutes.nostr,
|
||||||
title: 'Login',
|
title: 'Nostr Login',
|
||||||
sx: { padding: '10px' },
|
sx: { padding: '10px' },
|
||||||
label: <img src={nostrImage} width="25" alt="nostr logo" height="25" />
|
label: <img src={nostrImage} width="25" alt="nostr logo" height="25" />
|
||||||
}
|
}
|
||||||
|
@ -8,14 +8,13 @@ import { useEffect, useRef, useState } from 'react'
|
|||||||
import { DndProvider, useDrag, useDrop } from 'react-dnd'
|
import { DndProvider, useDrag, useDrop } from 'react-dnd'
|
||||||
import { MultiBackend } from 'react-dnd-multi-backend'
|
import { MultiBackend } from 'react-dnd-multi-backend'
|
||||||
import { HTML5toTouch } from 'rdndmb-html5-to-touch'
|
import { HTML5toTouch } from 'rdndmb-html5-to-touch'
|
||||||
import { useSelector } from 'react-redux'
|
import { useAppSelector } from '../../hooks/store'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
||||||
import { UserAvatar } from '../../components/UserAvatar'
|
import { UserAvatar } from '../../components/UserAvatar'
|
||||||
import { MetadataController, NostrController } from '../../controllers'
|
import { MetadataController, NostrController } from '../../controllers'
|
||||||
import { appPrivateRoutes } from '../../routes'
|
import { appPrivateRoutes } from '../../routes'
|
||||||
import { State } from '../../store/rootReducer'
|
|
||||||
import {
|
import {
|
||||||
CreateSignatureEventContent,
|
CreateSignatureEventContent,
|
||||||
Meta,
|
Meta,
|
||||||
@ -76,8 +75,6 @@ export const CreatePage = () => {
|
|||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
||||||
|
|
||||||
const [authUrl, setAuthUrl] = useState<string>()
|
|
||||||
|
|
||||||
const [title, setTitle] = useState(`sigit_${formatTimestamp(Date.now())}`)
|
const [title, setTitle] = useState(`sigit_${formatTimestamp(Date.now())}`)
|
||||||
|
|
||||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([])
|
const [selectedFiles, setSelectedFiles] = useState<File[]>([])
|
||||||
@ -102,7 +99,7 @@ export const CreatePage = () => {
|
|||||||
const signers = users.filter((u) => u.role === UserRole.signer)
|
const signers = users.filter((u) => u.role === UserRole.signer)
|
||||||
const viewers = users.filter((u) => u.role === UserRole.viewer)
|
const viewers = users.filter((u) => u.role === UserRole.viewer)
|
||||||
|
|
||||||
const usersPubkey = useSelector((state: State) => state.auth.usersPubkey)
|
const usersPubkey = useAppSelector((state) => state.auth.usersPubkey)
|
||||||
|
|
||||||
const nostrController = NostrController.getInstance()
|
const nostrController = NostrController.getInstance()
|
||||||
|
|
||||||
@ -185,10 +182,6 @@ export const CreatePage = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [metadata, users])
|
}, [metadata, users])
|
||||||
// Set up event listener for authentication event
|
|
||||||
nostrController.on('nsecbunker-auth', (url) => {
|
|
||||||
setAuthUrl(url)
|
|
||||||
})
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (uploadedFiles) {
|
if (uploadedFiles) {
|
||||||
@ -255,17 +248,22 @@ export const CreatePage = () => {
|
|||||||
const input = userInput.toLowerCase()
|
const input = userInput.toLowerCase()
|
||||||
|
|
||||||
if (input.startsWith('npub')) {
|
if (input.startsWith('npub')) {
|
||||||
const pubkey = npubToHex(input)
|
return handleAddNpubUser(input)
|
||||||
if (pubkey) {
|
|
||||||
addUser(pubkey)
|
|
||||||
setUserInput('')
|
|
||||||
} else {
|
|
||||||
setError('Provided npub is not valid. Please enter correct npub.')
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.includes('@')) {
|
if (input.includes('@')) {
|
||||||
|
return await handleAddNip05User(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user enters the domain (w/o @) assume it's the "root" and append _@
|
||||||
|
// https://github.com/nostr-protocol/nips/blob/master/05.md#showing-just-the-domain-as-an-identifier
|
||||||
|
if (input.includes('.')) {
|
||||||
|
return await handleAddNip05User(`_@${input}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
setError('Invalid input! Make sure to provide correct npub or nip05.')
|
||||||
|
|
||||||
|
async function handleAddNip05User(input: string) {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
setLoadingSpinnerDesc('Querying for nip05')
|
setLoadingSpinnerDesc('Querying for nip05')
|
||||||
const nip05Profile = await queryNip05(input)
|
const nip05Profile = await queryNip05(input)
|
||||||
@ -288,7 +286,16 @@ export const CreatePage = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setError('Invalid input! Make sure to provide correct npub or nip05.')
|
function handleAddNpubUser(input: string) {
|
||||||
|
const pubkey = npubToHex(input)
|
||||||
|
if (pubkey) {
|
||||||
|
addUser(pubkey)
|
||||||
|
setUserInput('')
|
||||||
|
} else {
|
||||||
|
setError('Provided npub is not valid. Please enter correct npub.')
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUserRoleChange = (role: UserRole, pubkey: string) => {
|
const handleUserRoleChange = (role: UserRole, pubkey: string) => {
|
||||||
@ -778,17 +785,6 @@ export const CreatePage = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authUrl) {
|
|
||||||
return (
|
|
||||||
<iframe
|
|
||||||
title="Nsecbunker auth"
|
|
||||||
src={authUrl}
|
|
||||||
width="100%"
|
|
||||||
height="500px"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
||||||
|
@ -257,6 +257,7 @@ export const HomePage = () => {
|
|||||||
.map((key) => (
|
.map((key) => (
|
||||||
<DisplaySigit
|
<DisplaySigit
|
||||||
key={`sigit-${key}`}
|
key={`sigit-${key}`}
|
||||||
|
sigitCreateId={key}
|
||||||
parsedMeta={parsedSigits[key]}
|
parsedMeta={parsedSigits[key]}
|
||||||
meta={sigits[key]}
|
meta={sigits[key]}
|
||||||
/>
|
/>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Box, Button } from '@mui/material'
|
import { Box, Button } from '@mui/material'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { Outlet, useLocation, useNavigate } from 'react-router-dom'
|
import { Outlet, useLocation } from 'react-router-dom'
|
||||||
import { appPublicRoutes } from '../../routes'
|
|
||||||
import { saveVisitedLink } from '../../utils'
|
import { saveVisitedLink } from '../../utils'
|
||||||
import { CardComponent } from '../../components/Landing/CardComponent/CardComponent'
|
import { CardComponent } from '../../components/Landing/CardComponent/CardComponent'
|
||||||
import { Container } from '../../components/Container'
|
import { Container } from '../../components/Container'
|
||||||
@ -20,13 +19,13 @@ import {
|
|||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
import { FontAwesomeIconStack } from '../../components/FontAwesomeIconStack'
|
import { FontAwesomeIconStack } from '../../components/FontAwesomeIconStack'
|
||||||
import { Footer } from '../../components/Footer/Footer'
|
import { Footer } from '../../components/Footer/Footer'
|
||||||
|
import { launch as launchNostrLoginDialog } from 'nostr-login'
|
||||||
|
|
||||||
export const LandingPage = () => {
|
export const LandingPage = () => {
|
||||||
const navigate = useNavigate()
|
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
const onSignInClick = async () => {
|
const onSignInClick = async () => {
|
||||||
navigate(appPublicRoutes.nostr)
|
launchNostrLoginDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
const cards = [
|
const cards = [
|
||||||
@ -35,7 +34,7 @@ export const LandingPage = () => {
|
|||||||
title: <>Open Source</>,
|
title: <>Open Source</>,
|
||||||
description: (
|
description: (
|
||||||
<>
|
<>
|
||||||
Code is MIT licenced and available at{' '}
|
Code is AGPL licenced and available at{' '}
|
||||||
<a href="https://git.nostrdev.com/sigit/sigit.io">
|
<a href="https://git.nostrdev.com/sigit/sigit.io">
|
||||||
https://git.nostrdev.com/sigit/sigit.io
|
https://git.nostrdev.com/sigit/sigit.io
|
||||||
</a>
|
</a>
|
||||||
@ -120,9 +119,7 @@ export const LandingPage = () => {
|
|||||||
<Container className={styles.container}>
|
<Container className={styles.container}>
|
||||||
<img className={styles.logo} src="/logo.svg" alt="Logo" width={300} />
|
<img className={styles.logo} src="/logo.svg" alt="Logo" width={300} />
|
||||||
<div className={styles.titleSection}>
|
<div className={styles.titleSection}>
|
||||||
<h1 className={styles.title}>
|
<h1 className={styles.title}>Secure & Private Agreements</h1>
|
||||||
Secure & Private Document Signing
|
|
||||||
</h1>
|
|
||||||
<p className={styles.subTitle}>
|
<p className={styles.subTitle}>
|
||||||
An open-source and self-hostable solution for secure document
|
An open-source and self-hostable solution for secure document
|
||||||
signing and verification.
|
signing and verification.
|
||||||
|
@ -12,6 +12,11 @@ export const Login = () => {
|
|||||||
margin="dense"
|
margin="dense"
|
||||||
autoComplete="username"
|
autoComplete="username"
|
||||||
disabled
|
disabled
|
||||||
|
sx={{
|
||||||
|
input: {
|
||||||
|
cursor: 'not-allowed'
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label="Password"
|
label="Password"
|
||||||
@ -20,6 +25,11 @@ export const Login = () => {
|
|||||||
margin="dense"
|
margin="dense"
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
disabled
|
disabled
|
||||||
|
sx={{
|
||||||
|
input: {
|
||||||
|
cursor: 'not-allowed'
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button variant="contained" fullWidth disabled>
|
<Button variant="contained" fullWidth disabled>
|
||||||
|
@ -1,48 +1,43 @@
|
|||||||
|
import { launch as launchNostrLoginDialog } from 'nostr-login'
|
||||||
|
|
||||||
import { Button, Divider, TextField } from '@mui/material'
|
import { Button, Divider, TextField } from '@mui/material'
|
||||||
import { getPublicKey, nip19 } from 'nostr-tools'
|
import { getPublicKey, nip19 } from 'nostr-tools'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useDispatch } from 'react-redux'
|
import { useAppDispatch } from '../../hooks/store'
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom'
|
import { useNavigate, useSearchParams } from 'react-router-dom'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
||||||
import {
|
import { AuthController } from '../../controllers'
|
||||||
AuthController,
|
import { updateKeyPair, updateLoginMethod } from '../../store/actions'
|
||||||
MetadataController,
|
import { LoginMethod } from '../../store/auth/types'
|
||||||
NostrController
|
|
||||||
} from '../../controllers'
|
|
||||||
import {
|
|
||||||
updateKeyPair,
|
|
||||||
updateLoginMethod,
|
|
||||||
updateNsecbunkerPubkey,
|
|
||||||
updateNsecbunkerRelays
|
|
||||||
} from '../../store/actions'
|
|
||||||
import { LoginMethods } from '../../store/auth/types'
|
|
||||||
import { Dispatch } from '../../store/store'
|
|
||||||
import { npubToHex, queryNip05, timeout } from '../../utils'
|
|
||||||
import { hexToBytes } from '@noble/hashes/utils'
|
import { hexToBytes } from '@noble/hashes/utils'
|
||||||
import { NIP05_REGEX } from '../../constants'
|
|
||||||
|
|
||||||
import styles from './styles.module.scss'
|
import styles from './styles.module.scss'
|
||||||
|
|
||||||
import { TimeoutError } from '../../types/errors/TimeoutError'
|
|
||||||
const EXTENSION_LOGIN_DELAY_SECONDS = 5
|
|
||||||
const EXTENSION_LOGIN_TIMEOUT_SECONDS = EXTENSION_LOGIN_DELAY_SECONDS + 55
|
|
||||||
|
|
||||||
export const Nostr = () => {
|
export const Nostr = () => {
|
||||||
const [searchParams] = useSearchParams()
|
const [searchParams] = useSearchParams()
|
||||||
|
|
||||||
const dispatch: Dispatch = useDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const authController = new AuthController()
|
const authController = new AuthController()
|
||||||
const metadataController = MetadataController.getInstance()
|
|
||||||
const nostrController = NostrController.getInstance()
|
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
|
||||||
const [isExtensionSlow, setIsExtensionSlow] = useState(false)
|
|
||||||
const [inputValue, setInputValue] = useState('')
|
const [inputValue, setInputValue] = useState('')
|
||||||
const [authUrl, setAuthUrl] = useState<string>()
|
|
||||||
|
const navigateAfterLogin = (path: string) => {
|
||||||
|
const callbackPath = searchParams.get('callbackPath')
|
||||||
|
|
||||||
|
if (callbackPath) {
|
||||||
|
// base64 decoded path
|
||||||
|
const path = atob(callbackPath)
|
||||||
|
navigate(path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
navigate(path)
|
||||||
|
}
|
||||||
|
|
||||||
const [isNostrExtensionAvailable, setIsNostrExtensionAvailable] =
|
const [isNostrExtensionAvailable, setIsNostrExtensionAvailable] =
|
||||||
useState(false)
|
useState(false)
|
||||||
@ -63,59 +58,6 @@ export const Nostr = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const navigateAfterLogin = (path: string) => {
|
|
||||||
const callbackPath = searchParams.get('callbackPath')
|
|
||||||
|
|
||||||
if (callbackPath) {
|
|
||||||
// base64 decoded path
|
|
||||||
const path = atob(callbackPath)
|
|
||||||
navigate(path)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
navigate(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
const loginWithExtension = async () => {
|
|
||||||
let waitTimeout: number | undefined
|
|
||||||
try {
|
|
||||||
// Wait EXTENSION_LOGIN_DELAY_SECONDS before showing extension delay message
|
|
||||||
waitTimeout = window.setTimeout(() => {
|
|
||||||
setIsExtensionSlow(true)
|
|
||||||
}, EXTENSION_LOGIN_DELAY_SECONDS * 1000)
|
|
||||||
|
|
||||||
setIsLoading(true)
|
|
||||||
setLoadingSpinnerDesc('Capturing pubkey from nostr extension')
|
|
||||||
|
|
||||||
const pubkey = await nostrController.capturePublicKey()
|
|
||||||
dispatch(updateLoginMethod(LoginMethods.extension))
|
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Authenticating and finding metadata')
|
|
||||||
const redirectPath = await Promise.race([
|
|
||||||
authController.authAndGetMetadataAndRelaysMap(pubkey),
|
|
||||||
timeout(EXTENSION_LOGIN_TIMEOUT_SECONDS * 1000)
|
|
||||||
])
|
|
||||||
|
|
||||||
if (redirectPath) {
|
|
||||||
navigateAfterLogin(redirectPath)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof TimeoutError) {
|
|
||||||
// Just log the error, no toast, user has already been notified with the loading screen
|
|
||||||
console.error("Extension didn't respond in time")
|
|
||||||
} else {
|
|
||||||
toast.error('Error capturing public key from nostr extension: ' + error)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
// Clear the wait timeout so we don't change the state unnecessarily
|
|
||||||
window.clearTimeout(waitTimeout)
|
|
||||||
|
|
||||||
setIsLoading(false)
|
|
||||||
setLoadingSpinnerDesc('')
|
|
||||||
setIsExtensionSlow(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login with NSEC or HEX private key
|
* Login with NSEC or HEX private key
|
||||||
* @param privateKey in HEX format
|
* @param privateKey in HEX format
|
||||||
@ -151,7 +93,7 @@ export const Nostr = () => {
|
|||||||
public: publickey
|
public: publickey
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
dispatch(updateLoginMethod(LoginMethods.privateKey))
|
dispatch(updateLoginMethod(LoginMethod.privateKey))
|
||||||
|
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
setLoadingSpinnerDesc('Authenticating and finding metadata')
|
setLoadingSpinnerDesc('Authenticating and finding metadata')
|
||||||
@ -169,182 +111,10 @@ export const Nostr = () => {
|
|||||||
setLoadingSpinnerDesc('')
|
setLoadingSpinnerDesc('')
|
||||||
}
|
}
|
||||||
|
|
||||||
const loginWithNsecBunker = async () => {
|
|
||||||
let relays: string[] | undefined
|
|
||||||
let pubkey: string | undefined
|
|
||||||
|
|
||||||
setIsLoading(true)
|
|
||||||
|
|
||||||
const displayError = (message: string) => {
|
|
||||||
toast.error(message)
|
|
||||||
setIsLoading(false)
|
|
||||||
setLoadingSpinnerDesc('')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inputValue.match(NIP05_REGEX)) {
|
|
||||||
const nip05Profile = await queryNip05(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) {
|
|
||||||
return displayError('metadata not found!')
|
|
||||||
}
|
|
||||||
|
|
||||||
const metadataContent =
|
|
||||||
metadataController.extractProfileMetadataContent(metadataEvent)
|
|
||||||
|
|
||||||
if (!metadataContent?.nip05) {
|
|
||||||
return displayError('nip05 not present in metadata')
|
|
||||||
}
|
|
||||||
|
|
||||||
const nip05Profile = await queryNip05(inputValue).catch((err) => {
|
|
||||||
toast.error('An error occurred while querying nip05 profile: ' + err)
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
if (nip05Profile) {
|
|
||||||
if (nip05Profile.pubkey !== pubkey) {
|
|
||||||
return displayError(
|
|
||||||
'pubkey in nip05 does not match with provided npub'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
relays = nip05Profile.relays
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!relays || relays.length === 0) {
|
|
||||||
return displayError('No relay found for nsecbunker')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pubkey) {
|
|
||||||
return displayError('pubkey not found')
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Initializing nsecBunker')
|
|
||||||
await nostrController.nsecBunkerInit(relays)
|
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Creating nsecbunker singer')
|
|
||||||
await nostrController
|
|
||||||
.createNsecBunkerSigner(pubkey)
|
|
||||||
.then(async (signer) => {
|
|
||||||
signer.on('authUrl', (url: string) => {
|
|
||||||
setAuthUrl(url)
|
|
||||||
})
|
|
||||||
|
|
||||||
dispatch(updateLoginMethod(LoginMethods.nsecBunker))
|
|
||||||
dispatch(updateNsecbunkerPubkey(pubkey))
|
|
||||||
dispatch(updateNsecbunkerRelays(relays))
|
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Authenticating and finding metadata')
|
|
||||||
|
|
||||||
const redirectPath = await authController
|
|
||||||
.authAndGetMetadataAndRelaysMap(pubkey!)
|
|
||||||
.catch((err) => {
|
|
||||||
toast.error('Error occurred in authentication: ' + err)
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
if (redirectPath) navigateAfterLogin(redirectPath)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
toast.error(
|
|
||||||
'An error occurred while creating nsecbunker signer: ' + err
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setIsLoading(false)
|
|
||||||
setLoadingSpinnerDesc('')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const loginWithBunkerConnectionString = async () => {
|
|
||||||
// Extract the key
|
|
||||||
const keyStartIndex = inputValue.indexOf('bunker://') + 'bunker://'.length
|
|
||||||
const keyEndIndex = inputValue.indexOf('?relay=')
|
|
||||||
const key = inputValue.substring(keyStartIndex, keyEndIndex)
|
|
||||||
|
|
||||||
const pubkey = npubToHex(key)
|
|
||||||
|
|
||||||
if (!pubkey) {
|
|
||||||
toast.error('Invalid pubkey in bunker connection string.')
|
|
||||||
setIsLoading(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the relay value
|
|
||||||
const relayIndex = inputValue.indexOf('relay=')
|
|
||||||
const relay = inputValue.substring(
|
|
||||||
relayIndex + 'relay='.length,
|
|
||||||
inputValue.length
|
|
||||||
)
|
|
||||||
|
|
||||||
setIsLoading(true)
|
|
||||||
setLoadingSpinnerDesc('Initializing bunker NDK')
|
|
||||||
|
|
||||||
await nostrController.nsecBunkerInit([relay])
|
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Creating remote signer')
|
|
||||||
await nostrController
|
|
||||||
.createNsecBunkerSigner(pubkey)
|
|
||||||
.then(async (signer) => {
|
|
||||||
signer.on('authUrl', (url: string) => {
|
|
||||||
setAuthUrl(url)
|
|
||||||
})
|
|
||||||
|
|
||||||
dispatch(updateLoginMethod(LoginMethods.nsecBunker))
|
|
||||||
dispatch(updateNsecbunkerPubkey(pubkey))
|
|
||||||
dispatch(updateNsecbunkerRelays([relay]))
|
|
||||||
|
|
||||||
setLoadingSpinnerDesc('Authenticating and finding metadata')
|
|
||||||
|
|
||||||
const redirectPath = await authController
|
|
||||||
.authAndGetMetadataAndRelaysMap(pubkey!)
|
|
||||||
.catch((err) => {
|
|
||||||
toast.error('Error occurred in authentication: ' + err)
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
if (redirectPath) navigateAfterLogin(redirectPath)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
toast.error(
|
|
||||||
'An error occurred while creating nsecbunker signer: ' + err
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setIsLoading(false)
|
|
||||||
setLoadingSpinnerDesc('')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const login = () => {
|
const login = () => {
|
||||||
if (inputValue.startsWith('bunker://')) {
|
|
||||||
return loginWithBunkerConnectionString()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inputValue.startsWith('nsec')) {
|
if (inputValue.startsWith('nsec')) {
|
||||||
return loginWithNsec()
|
return loginWithNsec()
|
||||||
}
|
}
|
||||||
if (inputValue.startsWith('npub')) {
|
|
||||||
return loginWithNsecBunker()
|
|
||||||
}
|
|
||||||
if (inputValue.match(NIP05_REGEX)) {
|
|
||||||
return loginWithNsecBunker()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if maybe hex nsec
|
// Check if maybe hex nsec
|
||||||
try {
|
try {
|
||||||
@ -356,64 +126,33 @@ export const Nostr = () => {
|
|||||||
console.warn('err', err)
|
console.warn('err', err)
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.error(
|
toast.error('Invalid format, please use: private key (hex or nsec)')
|
||||||
'Invalid format, please use: private key (hex), nsec..., bunker:// or nip05 format.'
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authUrl) {
|
|
||||||
return (
|
|
||||||
<iframe
|
|
||||||
title="Nsecbunker auth"
|
|
||||||
src={authUrl}
|
|
||||||
width="100%"
|
|
||||||
height="500px"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isLoading && (
|
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
||||||
<LoadingSpinner desc={loadingSpinnerDesc}>
|
|
||||||
{isExtensionSlow && (
|
|
||||||
<>
|
|
||||||
<p>
|
|
||||||
Your nostr extension is not responding. Check these
|
|
||||||
alternatives:{' '}
|
|
||||||
<a href="https://github.com/aljazceru/awesome-nostr?tab=readme-ov-file#nip-07-browser-extensions">
|
|
||||||
https://github.com/aljazceru/awesome-nostr
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<br />
|
|
||||||
<Button
|
|
||||||
fullWidth
|
|
||||||
variant="contained"
|
|
||||||
onClick={() => {
|
|
||||||
setLoadingSpinnerDesc('')
|
|
||||||
setIsLoading(false)
|
|
||||||
setIsExtensionSlow(false)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Close
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</LoadingSpinner>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isNostrExtensionAvailable && (
|
{isNostrExtensionAvailable && (
|
||||||
<>
|
<>
|
||||||
<label className={styles.label} htmlFor="extension-login">
|
<label className={styles.label} htmlFor="extension-login">
|
||||||
Login by using a browser extension
|
Login by using a{' '}
|
||||||
|
<a
|
||||||
|
rel="noopener"
|
||||||
|
href="https://github.com/nostrband/nostr-login"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
nostr-login
|
||||||
|
</a>
|
||||||
</label>
|
</label>
|
||||||
<Button
|
<Button
|
||||||
id="extension-login"
|
id="nostr-login"
|
||||||
onClick={loginWithExtension}
|
|
||||||
variant="contained"
|
variant="contained"
|
||||||
|
onClick={() => {
|
||||||
|
launchNostrLoginDialog()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Extension Login
|
Nostr Login
|
||||||
</Button>
|
</Button>
|
||||||
<Divider
|
<Divider
|
||||||
sx={{
|
sx={{
|
||||||
@ -424,16 +163,18 @@ export const Nostr = () => {
|
|||||||
</Divider>
|
</Divider>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<TextField
|
<form autoComplete="off">
|
||||||
onKeyDown={handleInputKeyDown}
|
<TextField
|
||||||
label="nip05 login / nip46 bunker string"
|
onKeyDown={handleInputKeyDown}
|
||||||
helperText="Private key (Not recommended)"
|
label="Private key (Not recommended)"
|
||||||
value={inputValue}
|
type="password"
|
||||||
onChange={(e) => setInputValue(e.target.value)}
|
autoComplete="off"
|
||||||
fullWidth
|
value={inputValue}
|
||||||
margin="dense"
|
onChange={(e) => setInputValue(e.target.value)}
|
||||||
/>
|
fullWidth
|
||||||
|
margin="dense"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
<Button
|
<Button
|
||||||
disabled={!inputValue}
|
disabled={!inputValue}
|
||||||
onClick={login}
|
onClick={login}
|
||||||
|
@ -3,13 +3,12 @@ import EditIcon from '@mui/icons-material/Edit'
|
|||||||
import { Box, IconButton, SxProps, Theme, Typography } from '@mui/material'
|
import { Box, IconButton, SxProps, Theme, Typography } from '@mui/material'
|
||||||
import { Event, VerifiedEvent, kinds, nip19 } from 'nostr-tools'
|
import { Event, VerifiedEvent, kinds, nip19 } from 'nostr-tools'
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import { useSelector } from 'react-redux'
|
import { useAppSelector } from '../../hooks/store'
|
||||||
import { Link, useNavigate, useParams } from 'react-router-dom'
|
import { Link, useNavigate, useParams } from 'react-router-dom'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
||||||
import { MetadataController } from '../../controllers'
|
import { MetadataController } from '../../controllers'
|
||||||
import { getProfileSettingsRoute } from '../../routes'
|
import { getProfileSettingsRoute } from '../../routes'
|
||||||
import { State } from '../../store/rootReducer'
|
|
||||||
import { NostrJoiningBlock, ProfileMetadata } from '../../types'
|
import { NostrJoiningBlock, ProfileMetadata } from '../../types'
|
||||||
import {
|
import {
|
||||||
getNostrJoiningBlockNumber,
|
getNostrJoiningBlockNumber,
|
||||||
@ -33,9 +32,9 @@ export const ProfilePage = () => {
|
|||||||
const [nostrJoiningBlock, setNostrJoiningBlock] =
|
const [nostrJoiningBlock, setNostrJoiningBlock] =
|
||||||
useState<NostrJoiningBlock | null>(null)
|
useState<NostrJoiningBlock | null>(null)
|
||||||
const [profileMetadata, setProfileMetadata] = useState<ProfileMetadata>()
|
const [profileMetadata, setProfileMetadata] = useState<ProfileMetadata>()
|
||||||
const metadataState = useSelector((state: State) => state.metadata)
|
const metadataState = useAppSelector((state) => state.metadata)
|
||||||
const { usersPubkey } = useSelector((state: State) => state.auth)
|
const { usersPubkey } = useAppSelector((state) => state.auth)
|
||||||
const userRobotImage = useSelector((state: State) => state.userRobotImage)
|
const userRobotImage = useAppSelector((state) => state.userRobotImage)
|
||||||
|
|
||||||
const [isUsersOwnProfile, setIsUsersOwnProfile] = useState(false)
|
const [isUsersOwnProfile, setIsUsersOwnProfile] = useState(false)
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
color: black
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left {
|
.left {
|
||||||
@ -51,7 +51,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.image-placeholder {
|
.image-placeholder {
|
||||||
width: 150px;
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
@ -99,4 +100,4 @@
|
|||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,11 @@ export const Register = () => {
|
|||||||
margin="dense"
|
margin="dense"
|
||||||
autoComplete="username"
|
autoComplete="username"
|
||||||
disabled
|
disabled
|
||||||
|
sx={{
|
||||||
|
input: {
|
||||||
|
cursor: 'not-allowed'
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label="Password"
|
label="Password"
|
||||||
@ -21,6 +26,11 @@ export const Register = () => {
|
|||||||
type="password"
|
type="password"
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
disabled
|
disabled
|
||||||
|
sx={{
|
||||||
|
input: {
|
||||||
|
cursor: 'not-allowed'
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label="Confirm password"
|
label="Confirm password"
|
||||||
@ -30,6 +40,11 @@ export const Register = () => {
|
|||||||
type="password"
|
type="password"
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
disabled
|
disabled
|
||||||
|
sx={{
|
||||||
|
input: {
|
||||||
|
cursor: 'not-allowed'
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button variant="contained" fullWidth disabled>
|
<Button variant="contained" fullWidth disabled>
|
||||||
|
@ -2,25 +2,22 @@ import AccountCircleIcon from '@mui/icons-material/AccountCircle'
|
|||||||
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'
|
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'
|
||||||
import CachedIcon from '@mui/icons-material/Cached'
|
import CachedIcon from '@mui/icons-material/Cached'
|
||||||
import RouterIcon from '@mui/icons-material/Router'
|
import RouterIcon from '@mui/icons-material/Router'
|
||||||
import { useTheme } from '@mui/material'
|
import { ListItem, useTheme } from '@mui/material'
|
||||||
import List from '@mui/material/List'
|
import List from '@mui/material/List'
|
||||||
import ListItemButton from '@mui/material/ListItemButton'
|
|
||||||
import ListItemIcon from '@mui/material/ListItemIcon'
|
import ListItemIcon from '@mui/material/ListItemIcon'
|
||||||
import ListItemText from '@mui/material/ListItemText'
|
import ListItemText from '@mui/material/ListItemText'
|
||||||
import ListSubheader from '@mui/material/ListSubheader'
|
import ListSubheader from '@mui/material/ListSubheader'
|
||||||
import { useSelector } from 'react-redux'
|
import { useAppSelector } from '../../hooks/store'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { appPrivateRoutes, getProfileSettingsRoute } from '../../routes'
|
import { appPrivateRoutes, getProfileSettingsRoute } from '../../routes'
|
||||||
import { State } from '../../store/rootReducer'
|
|
||||||
import { Container } from '../../components/Container'
|
import { Container } from '../../components/Container'
|
||||||
import { Footer } from '../../components/Footer/Footer'
|
import { Footer } from '../../components/Footer/Footer'
|
||||||
|
import ExtensionIcon from '@mui/icons-material/Extension'
|
||||||
|
import { LoginMethod } from '../../store/auth/types'
|
||||||
|
|
||||||
export const SettingsPage = () => {
|
export const SettingsPage = () => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
|
const { usersPubkey, loginMethod } = useAppSelector((state) => state.auth)
|
||||||
const navigate = useNavigate()
|
|
||||||
const usersPubkey = useSelector((state: State) => state.auth.usersPubkey)
|
|
||||||
|
|
||||||
const listItem = (label: string, disabled = false) => {
|
const listItem = (label: string, disabled = false) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -57,43 +54,40 @@ export const SettingsPage = () => {
|
|||||||
fontSize: '1.5rem',
|
fontSize: '1.5rem',
|
||||||
borderBottom: '0.5px solid',
|
borderBottom: '0.5px solid',
|
||||||
paddingBottom: 2,
|
paddingBottom: 2,
|
||||||
paddingTop: 2
|
paddingTop: 2,
|
||||||
|
zIndex: 2
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Settings
|
Settings
|
||||||
</ListSubheader>
|
</ListSubheader>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ListItemButton
|
<ListItem component={Link} to={getProfileSettingsRoute(usersPubkey!)}>
|
||||||
onClick={() => {
|
|
||||||
navigate(getProfileSettingsRoute(usersPubkey!))
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<AccountCircleIcon />
|
<AccountCircleIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
{listItem('Profile')}
|
{listItem('Profile')}
|
||||||
</ListItemButton>
|
</ListItem>
|
||||||
<ListItemButton
|
<ListItem component={Link} to={appPrivateRoutes.relays}>
|
||||||
onClick={() => {
|
|
||||||
navigate(appPrivateRoutes.relays)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<RouterIcon />
|
<RouterIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
{listItem('Relays')}
|
{listItem('Relays')}
|
||||||
</ListItemButton>
|
</ListItem>
|
||||||
<ListItemButton
|
<ListItem component={Link} to={appPrivateRoutes.cacheSettings}>
|
||||||
onClick={() => {
|
|
||||||
navigate(appPrivateRoutes.cacheSettings)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<CachedIcon />
|
<CachedIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
{listItem('Local Cache')}
|
{listItem('Local Cache')}
|
||||||
</ListItemButton>
|
</ListItem>
|
||||||
|
{loginMethod === LoginMethod.nostrLogin && (
|
||||||
|
<ListItem component={Link} to={appPrivateRoutes.nostrLogin}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ExtensionIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
{listItem('Nostr Login')}
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
</List>
|
</List>
|
||||||
</Container>
|
</Container>
|
||||||
<Footer />
|
<Footer />
|
||||||
|
3
src/pages/settings/cache/index.tsx
vendored
3
src/pages/settings/cache/index.tsx
vendored
@ -66,7 +66,8 @@ export const CacheSettingsPage = () => {
|
|||||||
fontSize: '1.5rem',
|
fontSize: '1.5rem',
|
||||||
borderBottom: '0.5px solid',
|
borderBottom: '0.5px solid',
|
||||||
paddingBottom: 2,
|
paddingBottom: 2,
|
||||||
paddingTop: 2
|
paddingTop: 2,
|
||||||
|
zIndex: 2
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Cache Setting
|
Cache Setting
|
||||||
|
78
src/pages/settings/nostrLogin/index.tsx
Normal file
78
src/pages/settings/nostrLogin/index.tsx
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import {
|
||||||
|
List,
|
||||||
|
ListItemButton,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
ListSubheader,
|
||||||
|
useTheme
|
||||||
|
} from '@mui/material'
|
||||||
|
import { launch as launchNostrLoginDialog } from 'nostr-login'
|
||||||
|
import { Container } from '../../../components/Container'
|
||||||
|
import PeopleIcon from '@mui/icons-material/People'
|
||||||
|
import ImportExportIcon from '@mui/icons-material/ImportExport'
|
||||||
|
import { useAppSelector } from '../../../hooks/store'
|
||||||
|
import { NostrLoginAuthMethod } from '../../../store/auth/types'
|
||||||
|
|
||||||
|
export const NostrLoginPage = () => {
|
||||||
|
const theme = useTheme()
|
||||||
|
const nostrLoginAuthMethod = useAppSelector(
|
||||||
|
(state) => state.auth?.nostrLoginAuthMethod
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<List
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
bgcolor: 'background.paper'
|
||||||
|
}}
|
||||||
|
subheader={
|
||||||
|
<ListSubheader
|
||||||
|
sx={{
|
||||||
|
fontSize: '1.5rem',
|
||||||
|
borderBottom: '0.5px solid',
|
||||||
|
paddingBottom: 2,
|
||||||
|
paddingTop: 2,
|
||||||
|
zIndex: 2
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Nostr Settings
|
||||||
|
</ListSubheader>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ListItemButton
|
||||||
|
onClick={() => {
|
||||||
|
launchNostrLoginDialog('switch-account')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<PeopleIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
primary={'Nostr Login Accounts'}
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.text.primary
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ListItemButton>
|
||||||
|
{nostrLoginAuthMethod === NostrLoginAuthMethod.Local && (
|
||||||
|
<ListItemButton
|
||||||
|
onClick={() => {
|
||||||
|
launchNostrLoginDialog('import')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ImportExportIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
primary={'Import / Export Keys'}
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.text.primary
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ListItemButton>
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
@ -18,13 +18,13 @@ import { toast } from 'react-toastify'
|
|||||||
import { MetadataController, NostrController } from '../../../controllers'
|
import { MetadataController, NostrController } from '../../../controllers'
|
||||||
import { NostrJoiningBlock, ProfileMetadata } from '../../../types'
|
import { NostrJoiningBlock, ProfileMetadata } from '../../../types'
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useAppDispatch, useAppSelector } from '../../../hooks/store'
|
||||||
import { State } from '../../../store/rootReducer'
|
|
||||||
import { LoadingButton } from '@mui/lab'
|
import { LoadingButton } from '@mui/lab'
|
||||||
import { Dispatch } from '../../../store/store'
|
import { Dispatch } from '../../../store/store'
|
||||||
import { setMetadataEvent } from '../../../store/actions'
|
import { setMetadataEvent } from '../../../store/actions'
|
||||||
import { LoadingSpinner } from '../../../components/LoadingSpinner'
|
import { LoadingSpinner } from '../../../components/LoadingSpinner'
|
||||||
import { LoginMethods } from '../../../store/auth/types'
|
import { LoginMethod, NostrLoginAuthMethod } from '../../../store/auth/types'
|
||||||
import { SmartToy } from '@mui/icons-material'
|
import { SmartToy } from '@mui/icons-material'
|
||||||
import {
|
import {
|
||||||
getNostrJoiningBlockNumber,
|
getNostrJoiningBlockNumber,
|
||||||
@ -33,13 +33,15 @@ import {
|
|||||||
} from '../../../utils'
|
} from '../../../utils'
|
||||||
import { Container } from '../../../components/Container'
|
import { Container } from '../../../components/Container'
|
||||||
import { Footer } from '../../../components/Footer/Footer'
|
import { Footer } from '../../../components/Footer/Footer'
|
||||||
|
import LaunchIcon from '@mui/icons-material/Launch'
|
||||||
|
import { launch as launchNostrLoginDialog } from 'nostr-login'
|
||||||
|
|
||||||
export const ProfileSettingsPage = () => {
|
export const ProfileSettingsPage = () => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
|
|
||||||
const { npub } = useParams()
|
const { npub } = useParams()
|
||||||
|
|
||||||
const dispatch: Dispatch = useDispatch()
|
const dispatch: Dispatch = useAppDispatch()
|
||||||
|
|
||||||
const metadataController = MetadataController.getInstance()
|
const metadataController = MetadataController.getInstance()
|
||||||
const nostrController = NostrController.getInstance()
|
const nostrController = NostrController.getInstance()
|
||||||
@ -49,10 +51,12 @@ export const ProfileSettingsPage = () => {
|
|||||||
useState<NostrJoiningBlock | null>(null)
|
useState<NostrJoiningBlock | null>(null)
|
||||||
const [profileMetadata, setProfileMetadata] = useState<ProfileMetadata>()
|
const [profileMetadata, setProfileMetadata] = useState<ProfileMetadata>()
|
||||||
const [savingProfileMetadata, setSavingProfileMetadata] = useState(false)
|
const [savingProfileMetadata, setSavingProfileMetadata] = useState(false)
|
||||||
const metadataState = useSelector((state: State) => state.metadata)
|
const metadataState = useAppSelector((state) => state.metadata)
|
||||||
const keys = useSelector((state: State) => state.auth?.keyPair)
|
const keys = useAppSelector((state) => state.auth?.keyPair)
|
||||||
const { usersPubkey, loginMethod } = useSelector((state: State) => state.auth)
|
const { usersPubkey, loginMethod, nostrLoginAuthMethod } = useAppSelector(
|
||||||
const userRobotImage = useSelector((state: State) => state.userRobotImage)
|
(state) => state.auth
|
||||||
|
)
|
||||||
|
const userRobotImage = useAppSelector((state) => state.userRobotImage)
|
||||||
|
|
||||||
const [isUsersOwnProfile, setIsUsersOwnProfile] = useState(false)
|
const [isUsersOwnProfile, setIsUsersOwnProfile] = useState(false)
|
||||||
|
|
||||||
@ -287,7 +291,8 @@ export const ProfileSettingsPage = () => {
|
|||||||
sx={{
|
sx={{
|
||||||
paddingBottom: 1,
|
paddingBottom: 1,
|
||||||
paddingTop: 1,
|
paddingTop: 1,
|
||||||
fontSize: '1.5rem'
|
fontSize: '1.5rem',
|
||||||
|
zIndex: 2
|
||||||
}}
|
}}
|
||||||
className={styles.subHeader}
|
className={styles.subHeader}
|
||||||
>
|
>
|
||||||
@ -363,7 +368,7 @@ export const ProfileSettingsPage = () => {
|
|||||||
<>
|
<>
|
||||||
{usersPubkey &&
|
{usersPubkey &&
|
||||||
copyItem(nip19.npubEncode(usersPubkey), 'Public Key')}
|
copyItem(nip19.npubEncode(usersPubkey), 'Public Key')}
|
||||||
{loginMethod === LoginMethods.privateKey &&
|
{loginMethod === LoginMethod.privateKey &&
|
||||||
keys &&
|
keys &&
|
||||||
keys.private &&
|
keys.private &&
|
||||||
copyItem(
|
copyItem(
|
||||||
@ -373,6 +378,33 @@ export const ProfileSettingsPage = () => {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{isUsersOwnProfile && (
|
||||||
|
<>
|
||||||
|
{loginMethod === LoginMethod.nostrLogin &&
|
||||||
|
nostrLoginAuthMethod === NostrLoginAuthMethod.Local && (
|
||||||
|
<ListItem
|
||||||
|
sx={{ marginTop: 1 }}
|
||||||
|
onClick={() => {
|
||||||
|
launchNostrLoginDialog('import')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
label="Private Key (nostr-login)"
|
||||||
|
defaultValue="••••••••••••••••••••••••••••••••••••••••••••••••••"
|
||||||
|
size="small"
|
||||||
|
className={styles.textField}
|
||||||
|
disabled
|
||||||
|
type={'password'}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<LaunchIcon className={styles.copyItem} />
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</List>
|
</List>
|
||||||
|
@ -6,13 +6,12 @@ import _ from 'lodash'
|
|||||||
import { MuiFileInput } from 'mui-file-input'
|
import { MuiFileInput } from 'mui-file-input'
|
||||||
import { Event, verifyEvent } from 'nostr-tools'
|
import { Event, verifyEvent } from 'nostr-tools'
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useSelector } from 'react-redux'
|
import { useAppSelector } from '../../hooks/store'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
||||||
import { NostrController } from '../../controllers'
|
import { NostrController } from '../../controllers'
|
||||||
import { appPublicRoutes } from '../../routes'
|
import { appPublicRoutes } from '../../routes'
|
||||||
import { State } from '../../store/rootReducer'
|
|
||||||
import { CreateSignatureEventContent, Meta, SignedEvent } from '../../types'
|
import { CreateSignatureEventContent, Meta, SignedEvent } from '../../types'
|
||||||
import {
|
import {
|
||||||
decryptArrayBuffer,
|
decryptArrayBuffer,
|
||||||
@ -55,6 +54,7 @@ import {
|
|||||||
} from '../../utils/file.ts'
|
} from '../../utils/file.ts'
|
||||||
import { ARRAY_BUFFER, DEFLATE } from '../../utils/const.ts'
|
import { ARRAY_BUFFER, DEFLATE } from '../../utils/const.ts'
|
||||||
import { generateTimestamp } from '../../utils/opentimestamps.ts'
|
import { generateTimestamp } from '../../utils/opentimestamps.ts'
|
||||||
|
|
||||||
enum SignedStatus {
|
enum SignedStatus {
|
||||||
Fully_Signed,
|
Fully_Signed,
|
||||||
User_Is_Next_Signer,
|
User_Is_Next_Signer,
|
||||||
@ -64,17 +64,39 @@ enum SignedStatus {
|
|||||||
export const SignPage = () => {
|
export const SignPage = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const params = useParams()
|
||||||
|
|
||||||
|
const usersAppData = useAppSelector((state) => state.userAppData)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Received from `location.state`
|
||||||
|
*
|
||||||
* uploadedZip will be received from home page when a user uploads a sigit zip wrapper that contains keys.json
|
* uploadedZip will be received from home page when a user uploads a sigit zip wrapper that contains keys.json
|
||||||
* arrayBuffer will be received in navigation from create page in offline mode
|
* arrayBuffer (decryptedArrayBuffer) will be received in navigation from create page in offline mode
|
||||||
* meta will be received in navigation from create & home page in online mode
|
* meta (metaInNavState) will be received in navigation from create & home page in online mode
|
||||||
*/
|
*/
|
||||||
const {
|
let metaInNavState = location?.state?.meta || undefined
|
||||||
meta: metaInNavState,
|
const { arrayBuffer: decryptedArrayBuffer, uploadedZip } = location.state || {
|
||||||
arrayBuffer: decryptedArrayBuffer,
|
decryptedArrayBuffer: undefined,
|
||||||
uploadedZip
|
uploadedZip: undefined
|
||||||
} = location.state || {}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If userAppData (redux) is available, and we have the route param (sigit id)
|
||||||
|
* which is actually a `createEventId`, we will fetch a `sigit`
|
||||||
|
* based on the provided route ID and set fetched `sigit` to the `metaInNavState`
|
||||||
|
*/
|
||||||
|
if (usersAppData) {
|
||||||
|
const sigitCreateId = params.id
|
||||||
|
|
||||||
|
if (sigitCreateId) {
|
||||||
|
const sigit = usersAppData.sigits[sigitCreateId]
|
||||||
|
|
||||||
|
if (sigit) {
|
||||||
|
metaInNavState = sigit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const [displayInput, setDisplayInput] = useState(false)
|
const [displayInput, setDisplayInput] = useState(false)
|
||||||
|
|
||||||
@ -107,9 +129,8 @@ export const SignPage = () => {
|
|||||||
// This state variable indicates whether the logged-in user is a signer, a creator, or neither.
|
// This state variable indicates whether the logged-in user is a signer, a creator, or neither.
|
||||||
const [isSignerOrCreator, setIsSignerOrCreator] = useState(false)
|
const [isSignerOrCreator, setIsSignerOrCreator] = useState(false)
|
||||||
|
|
||||||
const usersPubkey = useSelector((state: State) => state.auth.usersPubkey)
|
const usersPubkey = useAppSelector((state) => state.auth.usersPubkey)
|
||||||
|
|
||||||
const [authUrl, setAuthUrl] = useState<string>()
|
|
||||||
const nostrController = NostrController.getInstance()
|
const nostrController = NostrController.getInstance()
|
||||||
const [currentUserMarks, setCurrentUserMarks] = useState<CurrentUserMark[]>(
|
const [currentUserMarks, setCurrentUserMarks] = useState<CurrentUserMark[]>(
|
||||||
[]
|
[]
|
||||||
@ -273,11 +294,6 @@ export const SignPage = () => {
|
|||||||
const { keys, sender } = parsedKeysJson
|
const { keys, sender } = parsedKeysJson
|
||||||
|
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
// Set up event listener for authentication event
|
|
||||||
nostrController.on('nsecbunker-auth', (url) => {
|
|
||||||
setAuthUrl(url)
|
|
||||||
})
|
|
||||||
|
|
||||||
// decrypt the encryptionKey, with timeout (duration = 60 seconds)
|
// decrypt the encryptionKey, with timeout (duration = 60 seconds)
|
||||||
const encryptionKey = await Promise.race([
|
const encryptionKey = await Promise.race([
|
||||||
nostrController.nip04Decrypt(sender, key),
|
nostrController.nip04Decrypt(sender, key),
|
||||||
@ -290,9 +306,6 @@ export const SignPage = () => {
|
|||||||
console.log('err :>> ', err)
|
console.log('err :>> ', err)
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
.finally(() => {
|
|
||||||
setAuthUrl(undefined) // Clear authentication URL
|
|
||||||
})
|
|
||||||
|
|
||||||
// Return if encryption failed
|
// Return if encryption failed
|
||||||
if (!encryptionKey) continue
|
if (!encryptionKey) continue
|
||||||
@ -524,7 +537,11 @@ export const SignPage = () => {
|
|||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
const arrayBuffer = await decrypt(selectedFile)
|
const arrayBuffer = await decrypt(selectedFile)
|
||||||
|
|
||||||
if (!arrayBuffer) return
|
if (!arrayBuffer) {
|
||||||
|
setIsLoading(false)
|
||||||
|
toast.error('Error decrypting file')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
handleDecryptedArrayBuffer(arrayBuffer)
|
handleDecryptedArrayBuffer(arrayBuffer)
|
||||||
}
|
}
|
||||||
@ -767,14 +784,9 @@ export const SignPage = () => {
|
|||||||
2
|
2
|
||||||
)
|
)
|
||||||
|
|
||||||
const zip = new JSZip()
|
const zip = await getZipWithFiles(meta, files)
|
||||||
|
|
||||||
zip.file('meta.json', stringifiedMeta)
|
zip.file('meta.json', stringifiedMeta)
|
||||||
|
|
||||||
for (const [fileName, file] of Object.entries(files)) {
|
|
||||||
zip.file(`files/${fileName}`, await file.arrayBuffer())
|
|
||||||
}
|
|
||||||
|
|
||||||
const arrayBuffer = await zip
|
const arrayBuffer = await zip
|
||||||
.generateAsync({
|
.generateAsync({
|
||||||
type: 'arraybuffer',
|
type: 'arraybuffer',
|
||||||
@ -800,19 +812,14 @@ export const SignPage = () => {
|
|||||||
navigate(appPublicRoutes.verify)
|
navigate(appPublicRoutes.verify)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleExportSigit = async () => {
|
const handleEncryptedExport = async () => {
|
||||||
if (Object.entries(files).length === 0 || !meta) return
|
if (Object.entries(files).length === 0 || !meta) return
|
||||||
|
|
||||||
const zip = new JSZip()
|
|
||||||
|
|
||||||
const stringifiedMeta = JSON.stringify(meta, null, 2)
|
const stringifiedMeta = JSON.stringify(meta, null, 2)
|
||||||
|
const zip = await getZipWithFiles(meta, files)
|
||||||
|
|
||||||
zip.file('meta.json', stringifiedMeta)
|
zip.file('meta.json', stringifiedMeta)
|
||||||
|
|
||||||
for (const [fileName, file] of Object.entries(files)) {
|
|
||||||
zip.file(`files/${fileName}`, await file.arrayBuffer())
|
|
||||||
}
|
|
||||||
|
|
||||||
const arrayBuffer = await zip
|
const arrayBuffer = await zip
|
||||||
.generateAsync({
|
.generateAsync({
|
||||||
type: 'arraybuffer',
|
type: 'arraybuffer',
|
||||||
@ -874,17 +881,6 @@ export const SignPage = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authUrl) {
|
|
||||||
return (
|
|
||||||
<iframe
|
|
||||||
title="Nsecbunker auth"
|
|
||||||
src={authUrl}
|
|
||||||
width="100%"
|
|
||||||
height="500px"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <LoadingSpinner desc={loadingSpinnerDesc} />
|
return <LoadingSpinner desc={loadingSpinnerDesc} />
|
||||||
}
|
}
|
||||||
@ -950,7 +946,7 @@ export const SignPage = () => {
|
|||||||
{signedStatus === SignedStatus.Fully_Signed && (
|
{signedStatus === SignedStatus.Fully_Signed && (
|
||||||
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
|
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
|
||||||
<Button onClick={handleExport} variant="contained">
|
<Button onClick={handleExport} variant="contained">
|
||||||
Export
|
Export Sigit
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
@ -965,8 +961,8 @@ export const SignPage = () => {
|
|||||||
|
|
||||||
{isSignerOrCreator && (
|
{isSignerOrCreator && (
|
||||||
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
|
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
|
||||||
<Button onClick={handleExportSigit} variant="contained">
|
<Button onClick={handleEncryptedExport} variant="contained">
|
||||||
Export Sigit
|
Export Encrypted Sigit
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
border-bottom: 0.5px solid;
|
border-bottom: 0.5px solid;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filesWrapper {
|
.filesWrapper {
|
||||||
@ -62,7 +63,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
//z-index: 200;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fixedBottomForm input[type='text'] {
|
.fixedBottomForm input[type='text'] {
|
||||||
|
@ -14,7 +14,6 @@ import {
|
|||||||
} from '../../types'
|
} from '../../types'
|
||||||
import {
|
import {
|
||||||
decryptArrayBuffer,
|
decryptArrayBuffer,
|
||||||
extractMarksFromSignedMeta,
|
|
||||||
getHash,
|
getHash,
|
||||||
hexToNpub,
|
hexToNpub,
|
||||||
unixNow,
|
unixNow,
|
||||||
@ -29,15 +28,8 @@ import {
|
|||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import {
|
import { FONT_SIZE, FONT_TYPE, inPx } from '../../utils/pdf.ts'
|
||||||
addMarks,
|
import { useAppSelector } from '../../hooks/store'
|
||||||
FONT_SIZE,
|
|
||||||
FONT_TYPE,
|
|
||||||
groupMarksByFileNamePage,
|
|
||||||
inPx
|
|
||||||
} from '../../utils/pdf.ts'
|
|
||||||
import { State } from '../../store/rootReducer.ts'
|
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { getLastSignersSig } from '../../utils/sign.ts'
|
import { getLastSignersSig } from '../../utils/sign.ts'
|
||||||
import { saveAs } from 'file-saver'
|
import { saveAs } from 'file-saver'
|
||||||
import { Container } from '../../components/Container'
|
import { Container } from '../../components/Container'
|
||||||
@ -48,7 +40,11 @@ import FileList from '../../components/FileList'
|
|||||||
import { CurrentUserFile } from '../../types/file.ts'
|
import { CurrentUserFile } from '../../types/file.ts'
|
||||||
import { Mark } from '../../types/mark.ts'
|
import { Mark } from '../../types/mark.ts'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { convertToSigitFile, SigitFile } from '../../utils/file.ts'
|
import {
|
||||||
|
convertToSigitFile,
|
||||||
|
getZipWithFiles,
|
||||||
|
SigitFile
|
||||||
|
} from '../../utils/file.ts'
|
||||||
import { FileDivider } from '../../components/FileDivider.tsx'
|
import { FileDivider } from '../../components/FileDivider.tsx'
|
||||||
import { ExtensionFileBox } from '../../components/ExtensionFileBox.tsx'
|
import { ExtensionFileBox } from '../../components/ExtensionFileBox.tsx'
|
||||||
import { useScale } from '../../hooks/useScale.tsx'
|
import { useScale } from '../../hooks/useScale.tsx'
|
||||||
@ -164,7 +160,7 @@ const SlimPdfView = ({
|
|||||||
|
|
||||||
export const VerifyPage = () => {
|
export const VerifyPage = () => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const usersPubkey = useSelector((state: State) => state.auth.usersPubkey)
|
const usersPubkey = useAppSelector((state) => state.auth.usersPubkey)
|
||||||
|
|
||||||
const nostrController = NostrController.getInstance()
|
const nostrController = NostrController.getInstance()
|
||||||
|
|
||||||
@ -298,7 +294,9 @@ export const VerifyPage = () => {
|
|||||||
return timestamp
|
return timestamp
|
||||||
})
|
})
|
||||||
|
|
||||||
if (upgradedUserTimestamps.length > 0) {
|
console.log('upgraded timestamps: ', upgradedTimestamps)
|
||||||
|
|
||||||
|
if (upgradedUserTimestamps.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,6 +325,7 @@ export const VerifyPage = () => {
|
|||||||
updatedMeta.modifiedAt = unixNow()
|
updatedMeta.modifiedAt = unixNow()
|
||||||
|
|
||||||
const updatedEvent = await updateUsersAppData(updatedMeta)
|
const updatedEvent = await updateUsersAppData(updatedMeta)
|
||||||
|
console.log('updated event: ', updatedEvent)
|
||||||
if (!updatedEvent) return
|
if (!updatedEvent) return
|
||||||
|
|
||||||
const userSet = new Set<`npub1${string}`>()
|
const userSet = new Set<`npub1${string}`>()
|
||||||
@ -523,7 +522,7 @@ export const VerifyPage = () => {
|
|||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleExport = async () => {
|
const handleMarkedExport = async () => {
|
||||||
if (Object.entries(files).length === 0 || !meta || !usersPubkey) return
|
if (Object.entries(files).length === 0 || !meta || !usersPubkey) return
|
||||||
|
|
||||||
const usersNpub = hexToNpub(usersPubkey)
|
const usersNpub = hexToNpub(usersPubkey)
|
||||||
@ -553,22 +552,9 @@ export const VerifyPage = () => {
|
|||||||
const updatedMeta = { ...meta, exportSignature }
|
const updatedMeta = { ...meta, exportSignature }
|
||||||
const stringifiedMeta = JSON.stringify(updatedMeta, null, 2)
|
const stringifiedMeta = JSON.stringify(updatedMeta, null, 2)
|
||||||
|
|
||||||
const zip = new JSZip()
|
const zip = await getZipWithFiles(updatedMeta, files)
|
||||||
zip.file('meta.json', stringifiedMeta)
|
zip.file('meta.json', stringifiedMeta)
|
||||||
|
|
||||||
const marks = extractMarksFromSignedMeta(updatedMeta)
|
|
||||||
const marksByPage = groupMarksByFileNamePage(marks)
|
|
||||||
|
|
||||||
for (const [fileName, file] of Object.entries(files)) {
|
|
||||||
if (file.isPdf) {
|
|
||||||
// Draw marks into PDF file and generate a brand new blob
|
|
||||||
const blob = await addMarks(file, marksByPage[fileName])
|
|
||||||
zip.file(`files/${fileName}`, blob)
|
|
||||||
} else {
|
|
||||||
zip.file(`files/${fileName}`, file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const arrayBuffer = await zip
|
const arrayBuffer = await zip
|
||||||
.generateAsync({
|
.generateAsync({
|
||||||
type: 'arraybuffer',
|
type: 'arraybuffer',
|
||||||
@ -635,7 +621,7 @@ export const VerifyPage = () => {
|
|||||||
)}
|
)}
|
||||||
currentFile={currentFile}
|
currentFile={currentFile}
|
||||||
setCurrentFile={setCurrentFile}
|
setCurrentFile={setCurrentFile}
|
||||||
handleDownload={handleExport}
|
handleDownload={handleMarkedExport}
|
||||||
downloadLabel="Download Sigit"
|
downloadLabel="Download Sigit"
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
import { Modal } from '../layouts/modal'
|
|
||||||
import { CreatePage } from '../pages/create'
|
import { CreatePage } from '../pages/create'
|
||||||
import { HomePage } from '../pages/home'
|
import { HomePage } from '../pages/home'
|
||||||
import { LandingPage } from '../pages/landing'
|
import { LandingPage } from '../pages/landing'
|
||||||
import { Login } from '../pages/login'
|
|
||||||
import { Nostr } from '../pages/nostr'
|
|
||||||
import { ProfilePage } from '../pages/profile'
|
import { ProfilePage } from '../pages/profile'
|
||||||
import { Register } from '../pages/register'
|
|
||||||
import { SettingsPage } from '../pages/settings/Settings'
|
import { SettingsPage } from '../pages/settings/Settings'
|
||||||
import { CacheSettingsPage } from '../pages/settings/cache'
|
import { CacheSettingsPage } from '../pages/settings/cache'
|
||||||
|
import { NostrLoginPage } from '../pages/settings/nostrLogin'
|
||||||
import { ProfileSettingsPage } from '../pages/settings/profile'
|
import { ProfileSettingsPage } from '../pages/settings/profile'
|
||||||
import { RelaysPage } from '../pages/settings/relays'
|
import { RelaysPage } from '../pages/settings/relays'
|
||||||
import { SignPage } from '../pages/sign'
|
import { SignPage } from '../pages/sign'
|
||||||
@ -22,7 +19,8 @@ export const appPrivateRoutes = {
|
|||||||
settings: '/settings',
|
settings: '/settings',
|
||||||
profileSettings: '/settings/profile/:npub',
|
profileSettings: '/settings/profile/:npub',
|
||||||
cacheSettings: '/settings/cache',
|
cacheSettings: '/settings/cache',
|
||||||
relays: '/settings/relays'
|
relays: '/settings/relays',
|
||||||
|
nostrLogin: '/settings/nostrLogin'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const appPublicRoutes = {
|
export const appPublicRoutes = {
|
||||||
@ -85,29 +83,7 @@ export const publicRoutes: PublicRouteProps[] = [
|
|||||||
{
|
{
|
||||||
path: appPublicRoutes.landingPage,
|
path: appPublicRoutes.landingPage,
|
||||||
hiddenWhenLoggedIn: true,
|
hiddenWhenLoggedIn: true,
|
||||||
element: <LandingPage />,
|
element: <LandingPage />
|
||||||
children: [
|
|
||||||
{
|
|
||||||
element: <Modal />,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: appPublicRoutes.login,
|
|
||||||
hiddenWhenLoggedIn: true,
|
|
||||||
element: <Login />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: appPublicRoutes.register,
|
|
||||||
hiddenWhenLoggedIn: true,
|
|
||||||
element: <Register />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: appPublicRoutes.nostr,
|
|
||||||
hiddenWhenLoggedIn: true,
|
|
||||||
element: <Nostr />
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: appPublicRoutes.profile,
|
path: appPublicRoutes.profile,
|
||||||
@ -129,7 +105,7 @@ export const privateRoutes = [
|
|||||||
element: <CreatePage />
|
element: <CreatePage />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: appPrivateRoutes.sign,
|
path: `${appPrivateRoutes.sign}/:id?`,
|
||||||
element: <SignPage />
|
element: <SignPage />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -147,5 +123,9 @@ export const privateRoutes = [
|
|||||||
{
|
{
|
||||||
path: appPrivateRoutes.relays,
|
path: appPrivateRoutes.relays,
|
||||||
element: <RelaysPage />
|
element: <RelaysPage />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: appPrivateRoutes.nostrLogin,
|
||||||
|
element: <NostrLoginPage />
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
81
src/services/LoginMethodStrategy/NostrLoginStrategy.ts
Normal file
81
src/services/LoginMethodStrategy/NostrLoginStrategy.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import { Event, UnsignedEvent, EventTemplate, NostrEvent } from 'nostr-tools'
|
||||||
|
import { SignedEvent } from '../../types'
|
||||||
|
import { LoginMethodStrategy } from './loginMethodStrategy'
|
||||||
|
import { WindowNostr } from 'nostr-tools/nip07'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login Method Strategy when using nostr-login package.
|
||||||
|
*
|
||||||
|
* This class extends {@link LoginMethodStrategy base strategy} and implements all login method operations
|
||||||
|
* @see {@link LoginMethodStrategy}
|
||||||
|
*/
|
||||||
|
export class NostrLoginStrategy extends LoginMethodStrategy {
|
||||||
|
private nostr: WindowNostr
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
if (!window.nostr) {
|
||||||
|
throw new Error(
|
||||||
|
`window.nostr object not present. Make sure you have an nostr extension installed/working properly.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.nostr = window.nostr as WindowNostr
|
||||||
|
}
|
||||||
|
|
||||||
|
async nip04Encrypt(receiver: string, content: string): Promise<string> {
|
||||||
|
if (!this.nostr.nip04) {
|
||||||
|
throw new Error(
|
||||||
|
`Your nostr extension does not support nip04 encryption & decryption`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const encrypted = await this.nostr.nip04.encrypt(receiver, content)
|
||||||
|
return encrypted
|
||||||
|
}
|
||||||
|
|
||||||
|
async nip04Decrypt(sender: string, content: string): Promise<string> {
|
||||||
|
if (!this.nostr.nip04) {
|
||||||
|
throw new Error(
|
||||||
|
`Your nostr extension does not support nip04 encryption & decryption`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const decrypted = await this.nostr.nip04.decrypt(sender, content)
|
||||||
|
return decrypted
|
||||||
|
}
|
||||||
|
|
||||||
|
async nip44Encrypt(receiver: string, content: string): Promise<string> {
|
||||||
|
if (!this.nostr.nip44) {
|
||||||
|
throw new Error(
|
||||||
|
`Your nostr extension does not support nip44 encryption & decryption`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt the content using NIP-44 provided by the nostr extension.
|
||||||
|
const encrypted = await this.nostr.nip44.encrypt(receiver, content)
|
||||||
|
return encrypted as string
|
||||||
|
}
|
||||||
|
|
||||||
|
async nip44Decrypt(sender: string, content: string): Promise<string> {
|
||||||
|
if (!this.nostr.nip44) {
|
||||||
|
throw new Error(
|
||||||
|
`Your nostr extension does not support nip44 encryption & decryption`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt the content using NIP-44 provided by the nostr extension.
|
||||||
|
const decrypted = await this.nostr.nip44.decrypt(sender, content)
|
||||||
|
return decrypted as string
|
||||||
|
}
|
||||||
|
|
||||||
|
async signEvent(event: UnsignedEvent | EventTemplate): Promise<SignedEvent> {
|
||||||
|
return (await this.nostr
|
||||||
|
.signEvent(event as NostrEvent)
|
||||||
|
.catch((err: unknown) => {
|
||||||
|
console.log('Error while signing event: ', err)
|
||||||
|
|
||||||
|
throw err
|
||||||
|
})) as Event
|
||||||
|
}
|
||||||
|
}
|
124
src/services/LoginMethodStrategy/PrivateKeyStrategy.ts
Normal file
124
src/services/LoginMethodStrategy/PrivateKeyStrategy.ts
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import {
|
||||||
|
UnsignedEvent,
|
||||||
|
EventTemplate,
|
||||||
|
nip19,
|
||||||
|
nip44,
|
||||||
|
finalizeEvent,
|
||||||
|
nip04
|
||||||
|
} from 'nostr-tools'
|
||||||
|
import { SignedEvent } from '../../types'
|
||||||
|
import store from '../../store/store'
|
||||||
|
import { LoginMethod } from '../../store/auth/types'
|
||||||
|
import { LoginMethodStrategy } from './loginMethodStrategy'
|
||||||
|
import { verifySignedEvent } from '../../utils/nostr'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login Method Strategy when using dev private key login.
|
||||||
|
*
|
||||||
|
* This class extends {@link LoginMethodStrategy base strategy} and implements all login method operations
|
||||||
|
* @see {@link LoginMethodStrategy}
|
||||||
|
*/
|
||||||
|
export class PrivateKeyStrategy extends LoginMethodStrategy {
|
||||||
|
async nip04Encrypt(receiver: string, content: string): Promise<string> {
|
||||||
|
const keys = store.getState().auth.keyPair
|
||||||
|
|
||||||
|
if (!keys) {
|
||||||
|
throw new Error(
|
||||||
|
`Login method is ${LoginMethod.privateKey} but private & public key pair is not found.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { private: nsec } = keys
|
||||||
|
const privateKey = nip19.decode(nsec).data as Uint8Array
|
||||||
|
|
||||||
|
const encrypted = await nip04.encrypt(privateKey, receiver, content)
|
||||||
|
return encrypted
|
||||||
|
}
|
||||||
|
|
||||||
|
async nip04Decrypt(sender: string, content: string): Promise<string> {
|
||||||
|
const keys = store.getState().auth.keyPair
|
||||||
|
|
||||||
|
if (!keys) {
|
||||||
|
throw new Error(
|
||||||
|
`Login method is ${LoginMethod.privateKey} but private & public key pair is not found.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { private: nsec } = keys
|
||||||
|
const privateKey = nip19.decode(nsec).data as Uint8Array
|
||||||
|
|
||||||
|
const decrypted = await nip04.decrypt(privateKey, sender, content)
|
||||||
|
return decrypted
|
||||||
|
}
|
||||||
|
|
||||||
|
async nip44Encrypt(receiver: string, content: string): Promise<string> {
|
||||||
|
const keys = store.getState().auth.keyPair
|
||||||
|
|
||||||
|
// Check if the private and public key pair is available.
|
||||||
|
if (!keys) {
|
||||||
|
throw new Error(
|
||||||
|
`Login method is ${LoginMethod.privateKey} but private & public key pair is not found.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the private key.
|
||||||
|
const { private: nsec } = keys
|
||||||
|
const privateKey = nip19.decode(nsec).data as Uint8Array
|
||||||
|
|
||||||
|
// Generate the conversation key using NIP-44 utilities.
|
||||||
|
const nip44ConversationKey = nip44.v2.utils.getConversationKey(
|
||||||
|
privateKey,
|
||||||
|
receiver
|
||||||
|
)
|
||||||
|
|
||||||
|
// Encrypt the content using the generated conversation key.
|
||||||
|
const encrypted = nip44.v2.encrypt(content, nip44ConversationKey)
|
||||||
|
|
||||||
|
return encrypted
|
||||||
|
}
|
||||||
|
|
||||||
|
async nip44Decrypt(sender: string, content: string): Promise<string> {
|
||||||
|
const keys = store.getState().auth.keyPair
|
||||||
|
|
||||||
|
// Check if the private and public key pair is available.
|
||||||
|
if (!keys) {
|
||||||
|
throw new Error(
|
||||||
|
`Login method is ${LoginMethod.privateKey} but private & public key pair is not found.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the private key.
|
||||||
|
const { private: nsec } = keys
|
||||||
|
const privateKey = nip19.decode(nsec).data as Uint8Array
|
||||||
|
|
||||||
|
// Generate the conversation key using NIP-44 utilities.
|
||||||
|
const nip44ConversationKey = nip44.v2.utils.getConversationKey(
|
||||||
|
privateKey,
|
||||||
|
sender
|
||||||
|
)
|
||||||
|
|
||||||
|
// Decrypt the content using the generated conversation key.
|
||||||
|
const decrypted = nip44.v2.decrypt(content, nip44ConversationKey)
|
||||||
|
|
||||||
|
return decrypted
|
||||||
|
}
|
||||||
|
|
||||||
|
async signEvent(event: UnsignedEvent | EventTemplate): Promise<SignedEvent> {
|
||||||
|
const keys = store.getState().auth.keyPair
|
||||||
|
|
||||||
|
if (!keys) {
|
||||||
|
return Promise.reject(
|
||||||
|
`Login method is ${LoginMethod.privateKey}, but keys are not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { private: nsec } = keys
|
||||||
|
const privateKey = nip19.decode(nsec).data as Uint8Array
|
||||||
|
|
||||||
|
const signedEvent = finalizeEvent(event, privateKey)
|
||||||
|
|
||||||
|
verifySignedEvent(signedEvent)
|
||||||
|
|
||||||
|
return Promise.resolve(signedEvent)
|
||||||
|
}
|
||||||
|
}
|
50
src/services/LoginMethodStrategy/loginMethodContext.ts
Normal file
50
src/services/LoginMethodStrategy/loginMethodContext.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { UnsignedEvent, EventTemplate } from 'nostr-tools'
|
||||||
|
import { SignedEvent } from '../../types'
|
||||||
|
import {
|
||||||
|
LoginMethodStrategy,
|
||||||
|
LoginMethodOperations
|
||||||
|
} from './loginMethodStrategy'
|
||||||
|
import { LoginMethod } from '../../store/auth/types'
|
||||||
|
import { NostrLoginStrategy } from './NostrLoginStrategy'
|
||||||
|
import { PrivateKeyStrategy } from './PrivateKeyStrategy'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is a context provider and helper class. This MUST be instantiated and used as an entry point for any of the {@link LoginMethodOperations LoginMethodOperations}
|
||||||
|
* @constructor Takes {@link LoginMethod LoginMethod} as an argument and sets the correct strategy
|
||||||
|
*
|
||||||
|
* @see {@link LoginMethod}
|
||||||
|
* @see {@link LoginMethodOperations}
|
||||||
|
*/
|
||||||
|
export class LoginMethodContext implements LoginMethodOperations {
|
||||||
|
private strategy: LoginMethodStrategy
|
||||||
|
|
||||||
|
constructor(loginMethod?: LoginMethod) {
|
||||||
|
switch (loginMethod) {
|
||||||
|
case LoginMethod.nostrLogin:
|
||||||
|
this.strategy = new NostrLoginStrategy()
|
||||||
|
break
|
||||||
|
case LoginMethod.privateKey:
|
||||||
|
this.strategy = new PrivateKeyStrategy()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
this.strategy = new LoginMethodStrategy()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nip04Encrypt(receiver: string, content: string): Promise<string> {
|
||||||
|
return this.strategy.nip04Encrypt(receiver, content)
|
||||||
|
}
|
||||||
|
nip04Decrypt(sender: string, content: string): Promise<string> {
|
||||||
|
return this.strategy.nip04Decrypt(sender, content)
|
||||||
|
}
|
||||||
|
nip44Encrypt(receiver: string, content: string): Promise<string> {
|
||||||
|
return this.strategy.nip44Encrypt(receiver, content)
|
||||||
|
}
|
||||||
|
nip44Decrypt(sender: string, content: string): Promise<string> {
|
||||||
|
return this.strategy.nip44Decrypt(sender, content)
|
||||||
|
}
|
||||||
|
signEvent(event: UnsignedEvent | EventTemplate): Promise<SignedEvent> {
|
||||||
|
return this.strategy.signEvent(event)
|
||||||
|
}
|
||||||
|
}
|
38
src/services/LoginMethodStrategy/loginMethodStrategy.ts
Normal file
38
src/services/LoginMethodStrategy/loginMethodStrategy.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
import { EventTemplate, UnsignedEvent } from 'nostr-tools'
|
||||||
|
import { SignedEvent } from '../../types/nostr'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface holds all operations that are dependant on the login method and is used as the basis for the login strategies.
|
||||||
|
*/
|
||||||
|
export interface LoginMethodOperations {
|
||||||
|
nip04Encrypt(receiver: string, content: string): Promise<string>
|
||||||
|
nip04Decrypt(sender: string, content: string): Promise<string>
|
||||||
|
nip44Encrypt(receiver: string, content: string): Promise<string>
|
||||||
|
nip44Decrypt(sender: string, content: string): Promise<string>
|
||||||
|
signEvent(event: UnsignedEvent | EventTemplate): Promise<SignedEvent>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the fallback class that provides base implementation for the {@link LoginMethodOperations login method operations} . Only used to throw errors in case when the LoginMethod is missing (login context not set).
|
||||||
|
* @see {@link LoginMethodOperations}
|
||||||
|
*/
|
||||||
|
export class LoginMethodStrategy implements LoginMethodOperations {
|
||||||
|
async nip04Encrypt(_receiver: string, _content: string): Promise<string> {
|
||||||
|
throw new Error('Login method strategy is undefined')
|
||||||
|
}
|
||||||
|
async nip04Decrypt(_sender: string, _content: string): Promise<string> {
|
||||||
|
throw new Error('Login method strategy is undefined')
|
||||||
|
}
|
||||||
|
async nip44Encrypt(_receiver: string, _content: string): Promise<string> {
|
||||||
|
throw new Error('Login method strategy is undefined')
|
||||||
|
}
|
||||||
|
async nip44Decrypt(_sender: string, _content: string): Promise<string> {
|
||||||
|
throw new Error('Login method strategy is undefined')
|
||||||
|
}
|
||||||
|
async signEvent(_event: UnsignedEvent | EventTemplate): Promise<SignedEvent> {
|
||||||
|
return Promise.reject(
|
||||||
|
`We could not sign the event, none of the signing methods are available`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -4,9 +4,8 @@ export const USER_LOGOUT = 'USER_LOGOUT'
|
|||||||
|
|
||||||
export const SET_AUTH_STATE = 'SET_AUTH_STATE'
|
export const SET_AUTH_STATE = 'SET_AUTH_STATE'
|
||||||
export const UPDATE_LOGIN_METHOD = 'UPDATE_LOGIN_METHOD'
|
export const UPDATE_LOGIN_METHOD = 'UPDATE_LOGIN_METHOD'
|
||||||
|
export const UPDATE_NOSTR_LOGIN_AUTH_METHOD = 'UPDATE_NOSTR_LOGIN_AUTH_METHOD'
|
||||||
export const UPDATE_KEYPAIR = 'UPDATE_KEYPAIR'
|
export const UPDATE_KEYPAIR = 'UPDATE_KEYPAIR'
|
||||||
export const UPDATE_NSECBUNKER_PUBKEY = 'UPDATE_NSECBUNKER_PUBKEY'
|
|
||||||
export const UPDATE_NSECBUNKER_RELAYS = 'UPDATE_NSECBUNKER_RELAYS'
|
|
||||||
|
|
||||||
export const SET_METADATA_EVENT = 'SET_METADATA_EVENT'
|
export const SET_METADATA_EVENT = 'SET_METADATA_EVENT'
|
||||||
|
|
||||||
|
@ -2,12 +2,12 @@ import * as ActionTypes from '../actionTypes'
|
|||||||
import {
|
import {
|
||||||
AuthState,
|
AuthState,
|
||||||
Keys,
|
Keys,
|
||||||
LoginMethods,
|
LoginMethod,
|
||||||
SetAuthState,
|
SetAuthState,
|
||||||
UpdateKeyPair,
|
UpdateKeyPair,
|
||||||
UpdateLoginMethod,
|
UpdateLoginMethod,
|
||||||
UpdateNsecBunkerPubkey,
|
NostrLoginAuthMethod,
|
||||||
UpdateNsecBunkerRelays
|
UpdateNostrLoginAuthMethod
|
||||||
} from './types'
|
} from './types'
|
||||||
|
|
||||||
export const setAuthState = (payload: AuthState): SetAuthState => ({
|
export const setAuthState = (payload: AuthState): SetAuthState => ({
|
||||||
@ -16,27 +16,20 @@ export const setAuthState = (payload: AuthState): SetAuthState => ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const updateLoginMethod = (
|
export const updateLoginMethod = (
|
||||||
payload: LoginMethods | undefined
|
payload: LoginMethod | undefined
|
||||||
): UpdateLoginMethod => ({
|
): UpdateLoginMethod => ({
|
||||||
type: ActionTypes.UPDATE_LOGIN_METHOD,
|
type: ActionTypes.UPDATE_LOGIN_METHOD,
|
||||||
payload
|
payload
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const updateNostrLoginAuthMethod = (
|
||||||
|
payload: NostrLoginAuthMethod | undefined
|
||||||
|
): UpdateNostrLoginAuthMethod => ({
|
||||||
|
type: ActionTypes.UPDATE_NOSTR_LOGIN_AUTH_METHOD,
|
||||||
|
payload
|
||||||
|
})
|
||||||
|
|
||||||
export const updateKeyPair = (payload: Keys | undefined): UpdateKeyPair => ({
|
export const updateKeyPair = (payload: Keys | undefined): UpdateKeyPair => ({
|
||||||
type: ActionTypes.UPDATE_KEYPAIR,
|
type: ActionTypes.UPDATE_KEYPAIR,
|
||||||
payload
|
payload
|
||||||
})
|
})
|
||||||
|
|
||||||
export const updateNsecbunkerPubkey = (
|
|
||||||
payload: string | undefined
|
|
||||||
): UpdateNsecBunkerPubkey => ({
|
|
||||||
type: ActionTypes.UPDATE_NSECBUNKER_PUBKEY,
|
|
||||||
payload
|
|
||||||
})
|
|
||||||
|
|
||||||
export const updateNsecbunkerRelays = (
|
|
||||||
payload: string[] | undefined
|
|
||||||
): UpdateNsecBunkerRelays => ({
|
|
||||||
type: ActionTypes.UPDATE_NSECBUNKER_RELAYS,
|
|
||||||
payload
|
|
||||||
})
|
|
||||||
|
@ -8,16 +8,15 @@ const initialState: AuthState = {
|
|||||||
const reducer = (
|
const reducer = (
|
||||||
state = initialState,
|
state = initialState,
|
||||||
action: AuthDispatchTypes
|
action: AuthDispatchTypes
|
||||||
): AuthState | null => {
|
): AuthState => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ActionTypes.SET_AUTH_STATE: {
|
case ActionTypes.SET_AUTH_STATE: {
|
||||||
const { loginMethod, keyPair, nsecBunkerPubkey, nsecBunkerRelays } = state
|
const { loginMethod, nostrLoginAuthMethod, keyPair } = state
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loginMethod,
|
loginMethod,
|
||||||
|
nostrLoginAuthMethod,
|
||||||
keyPair,
|
keyPair,
|
||||||
nsecBunkerPubkey,
|
|
||||||
nsecBunkerRelays,
|
|
||||||
...action.payload
|
...action.payload
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -30,6 +29,15 @@ const reducer = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case ActionTypes.UPDATE_NOSTR_LOGIN_AUTH_METHOD: {
|
||||||
|
const { payload } = action
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
nostrLoginAuthMethod: payload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case ActionTypes.UPDATE_KEYPAIR: {
|
case ActionTypes.UPDATE_KEYPAIR: {
|
||||||
const { payload } = action
|
const { payload } = action
|
||||||
|
|
||||||
@ -39,26 +47,8 @@ const reducer = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case ActionTypes.UPDATE_NSECBUNKER_PUBKEY: {
|
|
||||||
const { payload } = action
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
nsecBunkerPubkey: payload
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case ActionTypes.UPDATE_NSECBUNKER_RELAYS: {
|
|
||||||
const { payload } = action
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
nsecBunkerRelays: payload
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case ActionTypes.RESTORE_STATE:
|
case ActionTypes.RESTORE_STATE:
|
||||||
return action.payload.auth
|
return action.payload.auth || initialState
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state
|
return state
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
import * as ActionTypes from '../actionTypes'
|
import * as ActionTypes from '../actionTypes'
|
||||||
import { RestoreState, UserLogout } from '../actions'
|
import { RestoreState, UserLogout } from '../actions'
|
||||||
|
|
||||||
export enum LoginMethods {
|
export enum NostrLoginAuthMethod {
|
||||||
extension = 'extension',
|
Connect = 'connect',
|
||||||
|
ReadOnly = 'readOnly',
|
||||||
|
Extension = 'extension',
|
||||||
|
Local = 'local',
|
||||||
|
OTP = 'otp'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum LoginMethod {
|
||||||
|
nostrLogin = 'nostrLogin',
|
||||||
privateKey = 'privateKey',
|
privateKey = 'privateKey',
|
||||||
nsecBunker = 'nsecBunker',
|
|
||||||
register = 'register'
|
register = 'register'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,10 +23,21 @@ export interface Keys {
|
|||||||
export interface AuthState {
|
export interface AuthState {
|
||||||
loggedIn: boolean
|
loggedIn: boolean
|
||||||
usersPubkey?: string
|
usersPubkey?: string
|
||||||
loginMethod?: LoginMethods
|
/**
|
||||||
|
* sigit login {@link LoginMethod methods }
|
||||||
|
* @see {@link LoginMethod}
|
||||||
|
*/
|
||||||
|
loginMethod?: LoginMethod
|
||||||
|
/**
|
||||||
|
* nostr-login package specific {@link NostrLoginAuthMethod method }
|
||||||
|
* @see {@link NostrLoginAuthMethod}
|
||||||
|
*/
|
||||||
|
nostrLoginAuthMethod?: NostrLoginAuthMethod
|
||||||
|
/**
|
||||||
|
* {@link Keys keyPair} for user auth (usually only public is available)
|
||||||
|
* @see {@link Keys}
|
||||||
|
*/
|
||||||
keyPair?: Keys
|
keyPair?: Keys
|
||||||
nsecBunkerPubkey?: string
|
|
||||||
nsecBunkerRelays?: string[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SetAuthState {
|
export interface SetAuthState {
|
||||||
@ -29,7 +47,12 @@ export interface SetAuthState {
|
|||||||
|
|
||||||
export interface UpdateLoginMethod {
|
export interface UpdateLoginMethod {
|
||||||
type: typeof ActionTypes.UPDATE_LOGIN_METHOD
|
type: typeof ActionTypes.UPDATE_LOGIN_METHOD
|
||||||
payload: LoginMethods | undefined
|
payload: LoginMethod | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateNostrLoginAuthMethod {
|
||||||
|
type: typeof ActionTypes.UPDATE_NOSTR_LOGIN_AUTH_METHOD
|
||||||
|
payload: NostrLoginAuthMethod | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateKeyPair {
|
export interface UpdateKeyPair {
|
||||||
@ -37,21 +60,10 @@ export interface UpdateKeyPair {
|
|||||||
payload: Keys | undefined
|
payload: Keys | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateNsecBunkerPubkey {
|
|
||||||
type: typeof ActionTypes.UPDATE_NSECBUNKER_PUBKEY
|
|
||||||
payload: string | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpdateNsecBunkerRelays {
|
|
||||||
type: typeof ActionTypes.UPDATE_NSECBUNKER_RELAYS
|
|
||||||
payload: string[] | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AuthDispatchTypes =
|
export type AuthDispatchTypes =
|
||||||
| RestoreState
|
| RestoreState
|
||||||
| SetAuthState
|
| SetAuthState
|
||||||
| UpdateLoginMethod
|
| UpdateLoginMethod
|
||||||
|
| UpdateNostrLoginAuthMethod
|
||||||
| UpdateKeyPair
|
| UpdateKeyPair
|
||||||
| UpdateNsecBunkerPubkey
|
|
||||||
| UpdateNsecBunkerRelays
|
|
||||||
| UserLogout
|
| UserLogout
|
||||||
|
@ -15,7 +15,7 @@ const reducer = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
case ActionTypes.RESTORE_STATE:
|
case ActionTypes.RESTORE_STATE:
|
||||||
return action.payload.metadata || null
|
return action.payload.metadata || initialState
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state
|
return state
|
||||||
|
@ -10,7 +10,7 @@ const initialState: RelaysState = {
|
|||||||
const reducer = (
|
const reducer = (
|
||||||
state = initialState,
|
state = initialState,
|
||||||
action: RelaysDispatchTypes
|
action: RelaysDispatchTypes
|
||||||
): RelaysState | null => {
|
): RelaysState => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ActionTypes.SET_RELAY_MAP:
|
case ActionTypes.SET_RELAY_MAP:
|
||||||
return { ...state, map: action.payload, mapUpdated: Date.now() }
|
return { ...state, map: action.payload, mapUpdated: Date.now() }
|
||||||
@ -25,7 +25,7 @@ const reducer = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
case ActionTypes.RESTORE_STATE:
|
case ActionTypes.RESTORE_STATE:
|
||||||
return action.payload.relays
|
return action.payload.relays || initialState
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state
|
return state
|
||||||
|
@ -12,7 +12,7 @@ const reducer = (
|
|||||||
return action.payload
|
return action.payload
|
||||||
|
|
||||||
case ActionTypes.RESTORE_STATE:
|
case ActionTypes.RESTORE_STATE:
|
||||||
return action.payload.userRobotImage
|
return action.payload.userRobotImage || initialState
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state
|
return state
|
||||||
|
@ -60,10 +60,24 @@ export interface OpenTimestampUpgradeVerifyResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface UserAppData {
|
export interface UserAppData {
|
||||||
sigits: { [key: string]: Meta } // key will be id of create signature
|
/**
|
||||||
processedGiftWraps: string[] // an array of ids of processed gift wrapped events
|
* Key will be id of create signature
|
||||||
keyPair?: Keys // this key pair is used for blossom requests authentication
|
*/
|
||||||
blossomUrls: string[] // array for storing Urls for the files that stores all the sigits and processedGiftWraps on blossom
|
sigits: { [key: string]: Meta }
|
||||||
|
/**
|
||||||
|
* An array of ids of processed gift wrapped events
|
||||||
|
*/
|
||||||
|
processedGiftWraps: string[]
|
||||||
|
/**
|
||||||
|
* Generated ephemeral key pair (https://docs.sigit.io/#/technical?id=storing-app-data).
|
||||||
|
* This {@link Keys key pair} is used for blossom requests authentication.
|
||||||
|
* @see {@link Keys}
|
||||||
|
*/
|
||||||
|
keyPair?: Keys
|
||||||
|
/**
|
||||||
|
* Array for storing Urls for the files that stores all the sigits and processedGiftWraps on blossom.
|
||||||
|
*/
|
||||||
|
blossomUrls: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DocSignatureEvent extends Event {
|
export interface DocSignatureEvent extends Event {
|
||||||
|
@ -1,11 +1,4 @@
|
|||||||
import { MarkType } from '../types/drawing.ts'
|
|
||||||
|
|
||||||
export const EMPTY: string = ''
|
export const EMPTY: string = ''
|
||||||
export const MARK_TYPE_TRANSLATION: { [key: string]: string } = {
|
|
||||||
[MarkType.FULLNAME.valueOf()]: 'Full Name'
|
|
||||||
}
|
|
||||||
export const SIGN: string = 'Sign'
|
|
||||||
export const NEXT: string = 'Next'
|
|
||||||
export const ARRAY_BUFFER = 'arraybuffer'
|
export const ARRAY_BUFFER = 'arraybuffer'
|
||||||
export const DEFLATE = 'DEFLATE'
|
export const DEFLATE = 'DEFLATE'
|
||||||
|
|
||||||
|
@ -19,14 +19,14 @@ export const getZipWithFiles = async (
|
|||||||
const marksByFileNamePage = groupMarksByFileNamePage(marks)
|
const marksByFileNamePage = groupMarksByFileNamePage(marks)
|
||||||
|
|
||||||
for (const [fileName, file] of Object.entries(files)) {
|
for (const [fileName, file] of Object.entries(files)) {
|
||||||
if (file.isPdf) {
|
// Handle PDF Files, add marks
|
||||||
// Handle PDF Files
|
if (file.isPdf && fileName in marksByFileNamePage) {
|
||||||
const blob = await addMarks(file, marksByFileNamePage[fileName])
|
const blob = await addMarks(file, marksByFileNamePage[fileName])
|
||||||
zip.file(`files/${fileName}`, blob)
|
zip.file(`marked/${fileName}`, blob)
|
||||||
} else {
|
|
||||||
// Handle other files
|
|
||||||
zip.file(`files/${fileName}`, file)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save original files
|
||||||
|
zip.file(`files/${fileName}`, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
return zip
|
return zip
|
||||||
|
@ -26,14 +26,6 @@ export const clearState = () => {
|
|||||||
localStorage.removeItem('state')
|
localStorage.removeItem('state')
|
||||||
}
|
}
|
||||||
|
|
||||||
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) => {
|
export const saveVisitedLink = (pathname: string, search: string) => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'visitedLink',
|
'visitedLink',
|
||||||
@ -69,3 +61,8 @@ export const getAuthToken = () => {
|
|||||||
export const clearAuthToken = () => {
|
export const clearAuthToken = () => {
|
||||||
localStorage.removeItem('authToken')
|
localStorage.removeItem('authToken')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const clear = () => {
|
||||||
|
clearAuthToken()
|
||||||
|
clearState()
|
||||||
|
}
|
||||||
|
@ -153,6 +153,11 @@ const findOtherUserMarks = (marks: Mark[], pubkey: string): Mark[] => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_TOOLBOX: DrawTool[] = [
|
export const DEFAULT_TOOLBOX: DrawTool[] = [
|
||||||
|
{
|
||||||
|
identifier: MarkType.TEXT,
|
||||||
|
icon: faT,
|
||||||
|
label: 'Text'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
identifier: MarkType.FULLNAME,
|
identifier: MarkType.FULLNAME,
|
||||||
icon: faIdCard,
|
icon: faIdCard,
|
||||||
@ -177,11 +182,6 @@ export const DEFAULT_TOOLBOX: DrawTool[] = [
|
|||||||
label: 'Date Time',
|
label: 'Date Time',
|
||||||
isComingSoon: true
|
isComingSoon: true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
identifier: MarkType.TEXT,
|
|
||||||
icon: faT,
|
|
||||||
label: 'Text'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
identifier: MarkType.NUMBER,
|
identifier: MarkType.NUMBER,
|
||||||
icon: fa1,
|
icon: fa1,
|
||||||
|
@ -10,7 +10,6 @@ import {
|
|||||||
} from 'nostr-tools'
|
} from 'nostr-tools'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { NostrController } from '../controllers'
|
import { NostrController } from '../controllers'
|
||||||
import { AuthState } from '../store/auth/types'
|
|
||||||
import store from '../store/store'
|
import store from '../store/store'
|
||||||
import { CreateSignatureEventContent, Meta } from '../types'
|
import { CreateSignatureEventContent, Meta } from '../types'
|
||||||
import { hexToNpub, unixNow } from './nostr'
|
import { hexToNpub, unixNow } from './nostr'
|
||||||
@ -232,7 +231,7 @@ export const extractZipUrlAndEncryptionKey = async (meta: Meta) => {
|
|||||||
const zipUrl = createSignatureContent.zipUrl
|
const zipUrl = createSignatureContent.zipUrl
|
||||||
|
|
||||||
// Retrieve the user's public key from the state
|
// Retrieve the user's public key from the state
|
||||||
const usersPubkey = (store.getState().auth as AuthState).usersPubkey!
|
const usersPubkey = store.getState().auth.usersPubkey!
|
||||||
const usersNpub = hexToNpub(usersPubkey)
|
const usersNpub = hexToNpub(usersPubkey)
|
||||||
|
|
||||||
// Return null if the metadata does not contain keys
|
// Return null if the metadata does not contain keys
|
||||||
|
@ -27,8 +27,7 @@ import {
|
|||||||
updateProcessedGiftWraps,
|
updateProcessedGiftWraps,
|
||||||
updateUserAppData as updateUserAppDataAction
|
updateUserAppData as updateUserAppDataAction
|
||||||
} from '../store/actions'
|
} from '../store/actions'
|
||||||
import { AuthState, Keys } from '../store/auth/types'
|
import { Keys } from '../store/auth/types'
|
||||||
import { RelaysState } from '../store/relays/types'
|
|
||||||
import store from '../store/store'
|
import store from '../store/store'
|
||||||
import { Meta, ProfileMetadata, SignedEvent, UserAppData } from '../types'
|
import { Meta, ProfileMetadata, SignedEvent, UserAppData } from '../types'
|
||||||
import { getDefaultRelayMap } from './relays'
|
import { getDefaultRelayMap } from './relays'
|
||||||
@ -41,7 +40,7 @@ import { SIGIT_BLOSSOM } from './const.ts'
|
|||||||
* Generates a `d` tag for userAppData
|
* Generates a `d` tag for userAppData
|
||||||
*/
|
*/
|
||||||
const getDTagForUserAppData = async (): Promise<string | null> => {
|
const getDTagForUserAppData = async (): Promise<string | null> => {
|
||||||
const isLoggedIn = store.getState().auth?.loggedIn
|
const isLoggedIn = store.getState().auth.loggedIn
|
||||||
const pubkey = store.getState().auth?.usersPubkey
|
const pubkey = store.getState().auth?.usersPubkey
|
||||||
|
|
||||||
if (!isLoggedIn || !pubkey) {
|
if (!isLoggedIn || !pubkey) {
|
||||||
@ -360,7 +359,7 @@ export const getUsersAppData = async (): Promise<UserAppData | null> => {
|
|||||||
const relays: string[] = []
|
const relays: string[] = []
|
||||||
|
|
||||||
// Retrieve the user's public key and relay map from the Redux store
|
// Retrieve the user's public key and relay map from the Redux store
|
||||||
const usersPubkey = (store.getState().auth as AuthState).usersPubkey!
|
const usersPubkey = store.getState().auth.usersPubkey!
|
||||||
const relayMap = store.getState().relays?.map
|
const relayMap = store.getState().relays?.map
|
||||||
|
|
||||||
// Check if relayMap is undefined in the Redux store
|
// Check if relayMap is undefined in the Redux store
|
||||||
@ -426,6 +425,7 @@ export const getUsersAppData = async (): Promise<UserAppData | null> => {
|
|||||||
|
|
||||||
// Handle case where the encrypted content is an empty object
|
// Handle case where the encrypted content is an empty object
|
||||||
if (encryptedContent === '{}') {
|
if (encryptedContent === '{}') {
|
||||||
|
// Generate ephemeral key pair
|
||||||
const secret = generateSecretKey()
|
const secret = generateSecretKey()
|
||||||
const pubKey = getPublicKey(secret)
|
const pubKey = getPublicKey(secret)
|
||||||
|
|
||||||
@ -570,7 +570,7 @@ export const updateUsersAppData = async (meta: Meta) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const usersPubkey = (store.getState().auth as AuthState).usersPubkey!
|
const usersPubkey = store.getState().auth.usersPubkey!
|
||||||
|
|
||||||
// encrypt content for storing in kind 30078 event
|
// encrypt content for storing in kind 30078 event
|
||||||
const nostrController = NostrController.getInstance()
|
const nostrController = NostrController.getInstance()
|
||||||
@ -617,8 +617,7 @@ export const updateUsersAppData = async (meta: Meta) => {
|
|||||||
|
|
||||||
if (!signedEvent) return null
|
if (!signedEvent) return null
|
||||||
|
|
||||||
const relayMap =
|
const relayMap = store.getState().relays.map || getDefaultRelayMap()
|
||||||
(store.getState().relays as RelaysState).map || getDefaultRelayMap()
|
|
||||||
const writeRelays = Object.keys(relayMap).filter((key) => relayMap[key].write)
|
const writeRelays = Object.keys(relayMap).filter((key) => relayMap[key].write)
|
||||||
|
|
||||||
const publishResult = await Promise.race([
|
const publishResult = await Promise.race([
|
||||||
@ -876,8 +875,11 @@ export const subscribeForSigits = async (pubkey: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const processReceivedEvent = async (event: Event, difficulty: number = 5) => {
|
const processReceivedEvent = async (event: Event, difficulty: number = 5) => {
|
||||||
const processedEvents = (store.getState().userAppData as UserAppData)
|
const processedEvents = store.getState().userAppData?.processedGiftWraps
|
||||||
.processedGiftWraps
|
|
||||||
|
// Abort processing if userAppData is undefined
|
||||||
|
if (!processedEvents) return
|
||||||
|
|
||||||
if (processedEvents.includes(event.id)) return
|
if (processedEvents.includes(event.id)) return
|
||||||
|
|
||||||
store.dispatch(updateProcessedGiftWraps([...processedEvents, event.id]))
|
store.dispatch(updateProcessedGiftWraps([...processedEvents, event.id]))
|
||||||
@ -928,7 +930,7 @@ const processReceivedEvent = async (event: Event, difficulty: number = 5) => {
|
|||||||
*/
|
*/
|
||||||
export const sendNotification = async (receiver: string, meta: Meta) => {
|
export const sendNotification = async (receiver: string, meta: Meta) => {
|
||||||
// Retrieve the user's public key from the state
|
// Retrieve the user's public key from the state
|
||||||
const usersPubkey = (store.getState().auth as AuthState).usersPubkey!
|
const usersPubkey = store.getState().auth.usersPubkey!
|
||||||
|
|
||||||
// Create an unsigned event object with the provided metadata
|
// Create an unsigned event object with the provided metadata
|
||||||
const unsignedEvent: UnsignedEvent = {
|
const unsignedEvent: UnsignedEvent = {
|
||||||
|
Loading…
Reference in New Issue
Block a user