From 75a715d002f005e327442015dc322b278f59bc8e Mon Sep 17 00:00:00 2001 From: Stixx Date: Tue, 10 Sep 2024 16:00:48 +0200 Subject: [PATCH 01/41] feat: Add Sigit ID as a path param --- src/components/DisplaySigit/index.tsx | 5 +-- src/pages/home/index.tsx | 1 + src/pages/sign/index.tsx | 51 +++++++++++++++++++++++---- src/routes/index.tsx | 5 +++ 4 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/components/DisplaySigit/index.tsx b/src/components/DisplaySigit/index.tsx index 473a942..00716a3 100644 --- a/src/components/DisplaySigit/index.tsx +++ b/src/components/DisplaySigit/index.tsx @@ -24,11 +24,12 @@ import { useSigitMeta } from '../../hooks/useSigitMeta' import { extractFileExtensions } from '../../utils/file' type SigitProps = { + sigitKey: string meta: Meta parsedMeta: SigitCardDisplayInfo } -export const DisplaySigit = ({ meta, parsedMeta }: SigitProps) => { +export const DisplaySigit = ({ meta, parsedMeta, sigitKey }: SigitProps) => { const { title, createdAt, submittedBy, signers, signedStatus, isValid } = parsedMeta @@ -47,7 +48,7 @@ export const DisplaySigit = ({ meta, parsedMeta }: SigitProps) => { to={ signedStatus === SigitStatus.Complete ? appPublicRoutes.verify - : appPrivateRoutes.sign + : `${appPrivateRoutes.sign}/${sigitKey}` } state={{ meta }} className={styles.insetLink} diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index c93c9f8..3e333d3 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -257,6 +257,7 @@ export const HomePage = () => { .map((key) => ( diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx index 8720954..70d9784 100644 --- a/src/pages/sign/index.tsx +++ b/src/pages/sign/index.tsx @@ -7,7 +7,7 @@ import { MuiFileInput } from 'mui-file-input' import { Event, verifyEvent } from 'nostr-tools' import { useCallback, useEffect, useState } from 'react' import { useSelector } from 'react-redux' -import { useLocation, useNavigate } from 'react-router-dom' +import { useLocation, useNavigate, useParams } from 'react-router-dom' import { toast } from 'react-toastify' import { LoadingSpinner } from '../../components/LoadingSpinner' import { NostrController } from '../../controllers' @@ -53,6 +53,7 @@ import { SigitFile } from '../../utils/file.ts' import { ARRAY_BUFFER, DEFLATE } from '../../utils/const.ts' +import { useAppSelector } from '../../hooks/store.ts' enum SignedStatus { Fully_Signed, User_Is_Next_Signer, @@ -62,17 +63,20 @@ enum SignedStatus { export const SignPage = () => { const navigate = useNavigate() const location = useLocation() + const params = useParams() /** + * Received from `location.state` + * * 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 * meta will be received in navigation from create & home page in online mode */ - const { - meta: metaInNavState, - arrayBuffer: decryptedArrayBuffer, - uploadedZip - } = location.state || {} + const [metaInNavState, setMetaInNavState] = useState() + const [decryptedArrayBuffer, setDecryptedArrayBuffer] = useState< + ArrayBuffer | undefined + >() + const [uploadedZip, setUploadedZip] = useState() const [displayInput, setDisplayInput] = useState(false) @@ -115,6 +119,41 @@ export const SignPage = () => { const [isMarksCompleted, setIsMarksCompleted] = useState(false) const [otherUserMarks, setOtherUserMarks] = useState([]) + const usersAppData = useAppSelector((state) => state.userAppData) + + /** + * When location state changes, update the states in the component + * If sigit id is found in the URL PARAM we will set metaInNavState manually + * after we do the fetching based on the ID + */ + useEffect(() => { + if (location.state) { + const { meta, arrayBuffer, uploadedZip } = location.state + + setMetaInNavState(meta) + setDecryptedArrayBuffer(arrayBuffer) + setUploadedZip(uploadedZip) + } + }, [location.state]) + + useEffect(() => { + // If meta in nav state is populated we will not try to fetch sigit details + // from the redux store + if (metaInNavState) return + + if (usersAppData) { + const sigitId = params.id + + if (sigitId) { + const sigit = usersAppData.sigits[sigitId] + + if (sigit) { + setMetaInNavState(sigit) + } + } + } + }, [usersAppData, metaInNavState, params.id]) + useEffect(() => { if (signers.length > 0) { // check if all signers have signed then its fully signed diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 2d36db1..643017f 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -19,6 +19,7 @@ export const appPrivateRoutes = { homePage: '/', create: '/create', sign: '/sign', + signId: '/sign/:id', settings: '/settings', profileSettings: '/settings/profile/:npub', cacheSettings: '/settings/cache', @@ -132,6 +133,10 @@ export const privateRoutes = [ path: appPrivateRoutes.sign, element: }, + { + path: appPrivateRoutes.signId, + element: + }, { path: appPrivateRoutes.settings, element: From 8e71592d8815471bde6fb74230ba9742308daad8 Mon Sep 17 00:00:00 2001 From: Stixx Date: Wed, 11 Sep 2024 11:59:12 +0200 Subject: [PATCH 02/41] fix: routing, removed useEffect --- src/components/DisplaySigit/index.tsx | 1 - src/pages/sign/index.tsx | 63 ++++++++++----------------- src/routes/index.tsx | 7 +-- 3 files changed, 24 insertions(+), 47 deletions(-) diff --git a/src/components/DisplaySigit/index.tsx b/src/components/DisplaySigit/index.tsx index 00716a3..641e9d6 100644 --- a/src/components/DisplaySigit/index.tsx +++ b/src/components/DisplaySigit/index.tsx @@ -50,7 +50,6 @@ export const DisplaySigit = ({ meta, parsedMeta, sigitKey }: SigitProps) => { ? appPublicRoutes.verify : `${appPrivateRoutes.sign}/${sigitKey}` } - state={{ meta }} className={styles.insetLink} >

{title}

diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx index 70d9784..fa11fb7 100644 --- a/src/pages/sign/index.tsx +++ b/src/pages/sign/index.tsx @@ -65,6 +65,8 @@ export const SignPage = () => { const location = useLocation() const params = useParams() + const usersAppData = useAppSelector((state) => state.userAppData) + /** * Received from `location.state` * @@ -72,11 +74,27 @@ export const SignPage = () => { * arrayBuffer will be received in navigation from create page in offline mode * meta will be received in navigation from create & home page in online mode */ - const [metaInNavState, setMetaInNavState] = useState() - const [decryptedArrayBuffer, setDecryptedArrayBuffer] = useState< - ArrayBuffer | undefined - >() - const [uploadedZip, setUploadedZip] = useState() + const { + meta: metaInNavState, + arrayBuffer: decryptedArrayBuffer, + uploadedZip + } = location.state || { + meta: undefined, + arrayBuffer: undefined, + uploadedZip: undefined + } + + if (usersAppData) { + const sigitId = params.id + + if (sigitId) { + const sigit = usersAppData.sigits[sigitId] + + if (sigit) { + metaInNavState = sigit + } + } + } const [displayInput, setDisplayInput] = useState(false) @@ -119,41 +137,6 @@ export const SignPage = () => { const [isMarksCompleted, setIsMarksCompleted] = useState(false) const [otherUserMarks, setOtherUserMarks] = useState([]) - const usersAppData = useAppSelector((state) => state.userAppData) - - /** - * When location state changes, update the states in the component - * If sigit id is found in the URL PARAM we will set metaInNavState manually - * after we do the fetching based on the ID - */ - useEffect(() => { - if (location.state) { - const { meta, arrayBuffer, uploadedZip } = location.state - - setMetaInNavState(meta) - setDecryptedArrayBuffer(arrayBuffer) - setUploadedZip(uploadedZip) - } - }, [location.state]) - - useEffect(() => { - // If meta in nav state is populated we will not try to fetch sigit details - // from the redux store - if (metaInNavState) return - - if (usersAppData) { - const sigitId = params.id - - if (sigitId) { - const sigit = usersAppData.sigits[sigitId] - - if (sigit) { - setMetaInNavState(sigit) - } - } - } - }, [usersAppData, metaInNavState, params.id]) - useEffect(() => { if (signers.length > 0) { // check if all signers have signed then its fully signed diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 643017f..3e42e78 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -19,7 +19,6 @@ export const appPrivateRoutes = { homePage: '/', create: '/create', sign: '/sign', - signId: '/sign/:id', settings: '/settings', profileSettings: '/settings/profile/:npub', cacheSettings: '/settings/cache', @@ -130,11 +129,7 @@ export const privateRoutes = [ element: }, { - path: appPrivateRoutes.sign, - element: - }, - { - path: appPrivateRoutes.signId, + path: `${appPrivateRoutes.sign}/:id?`, element: }, { From 7c027825cdce6e36ba23b726200d36eb7aa50d70 Mon Sep 17 00:00:00 2001 From: Stixx Date: Wed, 11 Sep 2024 12:03:23 +0200 Subject: [PATCH 03/41] style: lint fix --- src/pages/sign/index.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx index fa11fb7..ef22eb8 100644 --- a/src/pages/sign/index.tsx +++ b/src/pages/sign/index.tsx @@ -74,11 +74,9 @@ export const SignPage = () => { * arrayBuffer will be received in navigation from create page in offline mode * meta will be received in navigation from create & home page in online mode */ - const { - meta: metaInNavState, - arrayBuffer: decryptedArrayBuffer, - uploadedZip - } = location.state || { + let metaInNavState = location.state.meta + + const { arrayBuffer: decryptedArrayBuffer, uploadedZip } = location.state || { meta: undefined, arrayBuffer: undefined, uploadedZip: undefined From 86a16c13ced0b2ce1dcd8470db17c83aebd57042 Mon Sep 17 00:00:00 2001 From: Stixx Date: Wed, 11 Sep 2024 12:29:38 +0200 Subject: [PATCH 04/41] chore: comments and lint (typing) --- src/pages/sign/index.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx index ef22eb8..74194de 100644 --- a/src/pages/sign/index.tsx +++ b/src/pages/sign/index.tsx @@ -71,18 +71,22 @@ export const SignPage = () => { * Received from `location.state` * * 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 - * meta will be received in navigation from create & home page in online mode + * arrayBuffer (decryptedArrayBuffer) will be received in navigation from create page in offline mode + * meta (metaInNavState) will be received in navigation from create & home page in online mode */ - let metaInNavState = location.state.meta - + let metaInNavState = location?.state?.meta || undefined const { arrayBuffer: decryptedArrayBuffer, uploadedZip } = location.state || { - meta: undefined, - arrayBuffer: undefined, + decryptedArrayBuffer: undefined, uploadedZip: undefined } + /** + * If userAppData (redux) is available, and we have route param (sigit id) + * we will fetch a sigit based on the provided route ID and set it + * to the metaInNavState + */ if (usersAppData) { + // Sigit id is a createEventId const sigitId = params.id if (sigitId) { From 5dc8d53503e72bd92a04f4034b6be68929cbc4f5 Mon Sep 17 00:00:00 2001 From: Stixx Date: Wed, 11 Sep 2024 12:33:40 +0200 Subject: [PATCH 05/41] chore: sigitCreateId naming --- src/pages/sign/index.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx index 74194de..47fa7a7 100644 --- a/src/pages/sign/index.tsx +++ b/src/pages/sign/index.tsx @@ -86,11 +86,10 @@ export const SignPage = () => { * to the metaInNavState */ if (usersAppData) { - // Sigit id is a createEventId - const sigitId = params.id + const sigitCreateId = params.id - if (sigitId) { - const sigit = usersAppData.sigits[sigitId] + if (sigitCreateId) { + const sigit = usersAppData.sigits[sigitCreateId] if (sigit) { metaInNavState = sigit From 64e8ebba85313169a451bbfd2258f7ed3b2b14c6 Mon Sep 17 00:00:00 2001 From: Stixx Date: Wed, 11 Sep 2024 13:30:39 +0200 Subject: [PATCH 06/41] chore: renamed sigitKey to sigitCreateId --- src/components/DisplaySigit/index.tsx | 10 +++++++--- src/pages/home/index.tsx | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/DisplaySigit/index.tsx b/src/components/DisplaySigit/index.tsx index 641e9d6..0b02383 100644 --- a/src/components/DisplaySigit/index.tsx +++ b/src/components/DisplaySigit/index.tsx @@ -24,12 +24,16 @@ import { useSigitMeta } from '../../hooks/useSigitMeta' import { extractFileExtensions } from '../../utils/file' type SigitProps = { - sigitKey: string + sigitCreateId: string meta: Meta parsedMeta: SigitCardDisplayInfo } -export const DisplaySigit = ({ meta, parsedMeta, sigitKey }: SigitProps) => { +export const DisplaySigit = ({ + meta, + parsedMeta, + sigitCreateId: sigitCreateId +}: SigitProps) => { const { title, createdAt, submittedBy, signers, signedStatus, isValid } = parsedMeta @@ -48,7 +52,7 @@ export const DisplaySigit = ({ meta, parsedMeta, sigitKey }: SigitProps) => { to={ signedStatus === SigitStatus.Complete ? appPublicRoutes.verify - : `${appPrivateRoutes.sign}/${sigitKey}` + : `${appPrivateRoutes.sign}/${sigitCreateId}` } className={styles.insetLink} > diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 3e333d3..1a29021 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -257,7 +257,7 @@ export const HomePage = () => { .map((key) => ( From 79e14d45a12076ada7f1884c8885d751c9bdb013 Mon Sep 17 00:00:00 2001 From: Stixx Date: Wed, 11 Sep 2024 15:41:42 +0200 Subject: [PATCH 07/41] chore: comment fix --- src/pages/sign/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx index 47fa7a7..f6e2ef4 100644 --- a/src/pages/sign/index.tsx +++ b/src/pages/sign/index.tsx @@ -81,9 +81,9 @@ export const SignPage = () => { } /** - * If userAppData (redux) is available, and we have route param (sigit id) - * we will fetch a sigit based on the provided route ID and set it - * to the metaInNavState + * 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 From e48a3969904c1ec9759a60e9b21f998569ce6b13 Mon Sep 17 00:00:00 2001 From: Stixx Date: Wed, 11 Sep 2024 17:29:47 +0200 Subject: [PATCH 08/41] fix: verify/sign link --- src/components/DisplaySigit/index.tsx | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/components/DisplaySigit/index.tsx b/src/components/DisplaySigit/index.tsx index 0b02383..7d9cd3b 100644 --- a/src/components/DisplaySigit/index.tsx +++ b/src/components/DisplaySigit/index.tsx @@ -48,14 +48,19 @@ export const DisplaySigit = ({ return (
- + {signedStatus === SigitStatus.Complete && ( + + )} + {signedStatus !== SigitStatus.Complete && ( + + )}

{title}

{submittedBy && From 55abe814c967e3e126d40408239f62ac111ba9e7 Mon Sep 17 00:00:00 2001 From: "." <> Date: Sun, 6 Oct 2024 20:28:27 +0100 Subject: [PATCH 09/41] fix: AGPL Licence, closes #197 --- src/pages/landing/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/landing/index.tsx b/src/pages/landing/index.tsx index deae096..2bee544 100644 --- a/src/pages/landing/index.tsx +++ b/src/pages/landing/index.tsx @@ -35,7 +35,7 @@ export const LandingPage = () => { title: <>Open Source, description: ( <> - Code is MIT licenced and available at{' '} + Code is AGPL licenced and available at{' '} https://git.nostrdev.com/sigit/sigit.io From c4d50293ffa80d1b4cc39cb6d7002e187a0a9b33 Mon Sep 17 00:00:00 2001 From: enes Date: Mon, 7 Oct 2024 12:53:43 +0200 Subject: [PATCH 10/41] refactor: toolbox order --- src/utils/mark.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/utils/mark.ts b/src/utils/mark.ts index 0f437c4..b77818a 100644 --- a/src/utils/mark.ts +++ b/src/utils/mark.ts @@ -153,6 +153,11 @@ const findOtherUserMarks = (marks: Mark[], pubkey: string): Mark[] => { } export const DEFAULT_TOOLBOX: DrawTool[] = [ + { + identifier: MarkType.TEXT, + icon: faT, + label: 'Text' + }, { identifier: MarkType.FULLNAME, icon: faIdCard, @@ -177,11 +182,6 @@ export const DEFAULT_TOOLBOX: DrawTool[] = [ label: 'Date Time', isComingSoon: true }, - { - identifier: MarkType.TEXT, - icon: faT, - label: 'Text' - }, { identifier: MarkType.NUMBER, icon: fa1, From 2d7bb234f417daf2a3a4066b1680666bdc07d57b Mon Sep 17 00:00:00 2001 From: enes Date: Mon, 7 Oct 2024 12:59:55 +0200 Subject: [PATCH 11/41] refactor: next on each mark, including the final one --- src/components/MarkFormField/index.tsx | 6 +++--- src/utils/const.ts | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/MarkFormField/index.tsx b/src/components/MarkFormField/index.tsx index e1003a0..02f34cc 100644 --- a/src/components/MarkFormField/index.tsx +++ b/src/components/MarkFormField/index.tsx @@ -1,7 +1,7 @@ import { CurrentUserMark } from '../../types/mark.ts' import styles from './style.module.scss' -import { MARK_TYPE_TRANSLATION, NEXT, SIGN } from '../../utils/const.ts' +import { MARK_TYPE_TRANSLATION } from '../../utils/const.ts' import { findNextIncompleteCurrentUserMark, isCurrentUserMarksComplete, @@ -32,7 +32,6 @@ const MarkFormField = ({ handleCurrentUserMarkChange }: MarkFormFieldProps) => { const [displayActions, setDisplayActions] = useState(true) - const getSubmitButtonText = () => (isReadyToSign() ? SIGN : NEXT) const isReadyToSign = () => isCurrentUserMarksComplete(currentUserMarks) || isCurrentValueLast(currentUserMarks, selectedMark, selectedMarkValue) @@ -61,6 +60,7 @@ const MarkFormField = ({ onClick={toggleActions} className={styles.triggerBtn} type="button" + title="Toggle" >
diff --git a/src/utils/const.ts b/src/utils/const.ts index a512c2f..8e53a46 100644 --- a/src/utils/const.ts +++ b/src/utils/const.ts @@ -4,8 +4,6 @@ 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 DEFLATE = 'DEFLATE' From e2dbed2b0335db7cc0f2f1803a0666ffb4e46ff9 Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 2 Oct 2024 16:28:32 +0200 Subject: [PATCH 12/41] refactor: add nostr-login package, show not-allowed on disabled form fields --- package-lock.json | 493 ++++++++++++++++++++++------------- package.json | 1 + src/layouts/modal/index.tsx | 2 +- src/pages/login/index.tsx | 10 + src/pages/register/index.tsx | 15 ++ 5 files changed, 332 insertions(+), 189 deletions(-) diff --git a/package-lock.json b/package-lock.json index 71e0923..a9fb01a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "jszip": "3.10.1", "lodash": "4.17.21", "mui-file-input": "4.0.4", + "nostr-login": "^1.6.6", "nostr-tools": "2.7.0", "pdf-lib": "^1.17.1", "pdfjs-dist": "^4.4.168", @@ -614,13 +615,14 @@ "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "aix" @@ -630,13 +632,14 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -646,13 +649,14 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -662,13 +666,14 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -678,13 +683,14 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -694,13 +700,14 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -710,13 +717,14 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -726,13 +734,14 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -742,13 +751,14 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -758,13 +768,14 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -774,13 +785,14 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -790,13 +802,14 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -806,13 +819,14 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -822,13 +836,14 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -838,13 +853,14 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -854,13 +870,14 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -870,13 +887,14 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -886,13 +904,14 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -902,13 +921,14 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -918,13 +938,14 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" @@ -934,13 +955,14 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -950,13 +972,14 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -966,13 +989,14 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -1833,208 +1857,224 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", - "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.5.tgz", + "integrity": "sha512-SU5cvamg0Eyu/F+kLeMXS7GoahL+OoizlclVFX3l5Ql6yNlywJJ0OuqTzUx0v+aHhPHEB/56CT06GQrRrGNYww==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", - "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.5.tgz", + "integrity": "sha512-S4pit5BP6E5R5C8S6tgU/drvgjtYW76FBuG6+ibG3tMvlD1h9LHVF9KmlmaUBQ8Obou7hEyS+0w+IR/VtxwNMQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", - "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.5.tgz", + "integrity": "sha512-250ZGg4ipTL0TGvLlfACkIxS9+KLtIbn7BCZjsZj88zSg2Lvu3Xdw6dhAhfe/FjjXPVNCtcSp+WZjVsD3a/Zlw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", - "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.5.tgz", + "integrity": "sha512-D8brJEFg5D+QxFcW6jYANu+Rr9SlKtTenmsX5hOSzNYVrK5oLAEMTUgKWYJP+wdKyCdeSwnapLsn+OVRFycuQg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", - "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.5.tgz", + "integrity": "sha512-PNqXYmdNFyWNg0ma5LdY8wP+eQfdvyaBAojAXgO7/gs0Q/6TQJVXAXe8gwW9URjbS0YAammur0fynYGiWsKlXw==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", - "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.5.tgz", + "integrity": "sha512-kSSCZOKz3HqlrEuwKd9TYv7vxPYD77vHSUvM2y0YaTGnFc8AdI5TTQRrM1yIp3tXCKrSL9A7JLoILjtad5t8pQ==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", - "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.5.tgz", + "integrity": "sha512-oTXQeJHRbOnwRnRffb6bmqmUugz0glXaPyspp4gbQOPVApdpRrY/j7KP3lr7M8kTfQTyrBUzFjj5EuHAhqH4/w==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", - "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.5.tgz", + "integrity": "sha512-qnOTIIs6tIGFKCHdhYitgC2XQ2X25InIbZFor5wh+mALH84qnFHvc+vmWUpyX97B0hNvwNUL4B+MB8vJvH65Fw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", - "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.5.tgz", + "integrity": "sha512-TMYu+DUdNlgBXING13rHSfUc3Ky5nLPbWs4bFnT+R6Vu3OvXkTkixvvBKk8uO4MT5Ab6lC3U7x8S8El2q5o56w==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", - "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.5.tgz", + "integrity": "sha512-PTQq1Kz22ZRvuhr3uURH+U/Q/a0pbxJoICGSprNLAoBEkyD3Sh9qP5I0Asn0y0wejXQBbsVMRZRxlbGFD9OK4A==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", - "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.5.tgz", + "integrity": "sha512-bR5nCojtpuMss6TDEmf/jnBnzlo+6n1UhgwqUvRoe4VIotC7FG1IKkyJbwsT7JDsF2jxR+NTnuOwiGv0hLyDoQ==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", - "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.5.tgz", + "integrity": "sha512-N0jPPhHjGShcB9/XXZQWuWBKZQnC1F36Ce3sDqWpujsGjDz/CQtOL9LgTrJ+rJC8MJeesMWrMWVLKKNR/tMOCA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", - "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.5.tgz", + "integrity": "sha512-uBa2e28ohzNNwjr6Uxm4XyaA1M/8aTgfF2T7UIlElLaeXkgpmIJ2EitVNQxjO9xLLLy60YqAgKn/AqSpCUkE9g==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", - "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.5.tgz", + "integrity": "sha512-RXT8S1HP8AFN/Kr3tg4fuYrNxZ/pZf1HemC5Tsddc6HzgGnJm0+Lh5rAHJkDuW3StI0ynNXukidROMXYl6ew8w==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", - "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.5.tgz", + "integrity": "sha512-ElTYOh50InL8kzyUD6XsnPit7jYCKrphmddKAe1/Ytt74apOxDq5YEcbsiKs0fR3vff3jEneMM+3I7jbqaMyBg==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", - "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.5.tgz", + "integrity": "sha512-+lvL/4mQxSV8MukpkKyyvfwhH266COcWlXE/1qxwN08ajovta3459zrjLghYMgDerlzNwLAcFpvU+WWE5y6nAQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -2192,10 +2232,11 @@ "dev": true }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/file-saver": { "version": "2.0.7", @@ -3380,11 +3421,12 @@ } }, "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -3392,29 +3434,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/escalade": { @@ -4930,9 +4972,9 @@ } }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { @@ -5236,6 +5278,72 @@ "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": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.7.0.tgz", @@ -5545,10 +5653,11 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -5576,9 +5685,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -5594,10 +5703,11 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -6172,12 +6282,13 @@ } }, "node_modules/rollup": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", - "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", + "version": "4.22.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.5.tgz", + "integrity": "sha512-WoinX7GeQOFMGznEcWA1WrTQCd/tpEbMkc3nuMs9BT0CPjMdSjPMTVClwWd4pgSQwJdP65SK9mTCNvItlr5o7w==", "dev": true, + "license": "MIT", "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.6" }, "bin": { "rollup": "dist/bin/rollup" @@ -6187,22 +6298,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.18.0", - "@rollup/rollup-android-arm64": "4.18.0", - "@rollup/rollup-darwin-arm64": "4.18.0", - "@rollup/rollup-darwin-x64": "4.18.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", - "@rollup/rollup-linux-arm-musleabihf": "4.18.0", - "@rollup/rollup-linux-arm64-gnu": "4.18.0", - "@rollup/rollup-linux-arm64-musl": "4.18.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", - "@rollup/rollup-linux-riscv64-gnu": "4.18.0", - "@rollup/rollup-linux-s390x-gnu": "4.18.0", - "@rollup/rollup-linux-x64-gnu": "4.18.0", - "@rollup/rollup-linux-x64-musl": "4.18.0", - "@rollup/rollup-win32-arm64-msvc": "4.18.0", - "@rollup/rollup-win32-ia32-msvc": "4.18.0", - "@rollup/rollup-win32-x64-msvc": "4.18.0", + "@rollup/rollup-android-arm-eabi": "4.22.5", + "@rollup/rollup-android-arm64": "4.22.5", + "@rollup/rollup-darwin-arm64": "4.22.5", + "@rollup/rollup-darwin-x64": "4.22.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.5", + "@rollup/rollup-linux-arm-musleabihf": "4.22.5", + "@rollup/rollup-linux-arm64-gnu": "4.22.5", + "@rollup/rollup-linux-arm64-musl": "4.22.5", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.5", + "@rollup/rollup-linux-riscv64-gnu": "4.22.5", + "@rollup/rollup-linux-s390x-gnu": "4.22.5", + "@rollup/rollup-linux-x64-gnu": "4.22.5", + "@rollup/rollup-linux-x64-musl": "4.22.5", + "@rollup/rollup-win32-arm64-msvc": "4.22.5", + "@rollup/rollup-win32-ia32-msvc": "4.22.5", + "@rollup/rollup-win32-x64-msvc": "4.22.5", "fsevents": "~2.3.2" } }, @@ -6432,10 +6543,11 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -6930,14 +7042,15 @@ } }, "node_modules/vite": { - "version": "5.2.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz", - "integrity": "sha512-/gC8GxzxMK5ntBwb48pR32GGhENnjtY30G4A0jemunsBkiEZFw60s8InGpN8gkhHEkjnRK1aSAxeQgwvFhUHAA==", + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", + "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", "dev": true, + "license": "MIT", "dependencies": { - "esbuild": "^0.20.1", - "postcss": "^8.4.38", - "rollup": "^4.13.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -6956,6 +7069,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -6973,6 +7087,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, diff --git a/package.json b/package.json index a833103..4c9a3d2 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "jszip": "3.10.1", "lodash": "4.17.21", "mui-file-input": "4.0.4", + "nostr-login": "^1.6.6", "nostr-tools": "2.7.0", "pdf-lib": "^1.17.1", "pdfjs-dist": "^4.4.168", diff --git a/src/layouts/modal/index.tsx b/src/layouts/modal/index.tsx index 7b29379..663e890 100644 --- a/src/layouts/modal/index.tsx +++ b/src/layouts/modal/index.tsx @@ -33,7 +33,7 @@ export const Modal = () => { { to: appPublicRoutes.register, title: 'Register', label: 'Register' }, { to: appPublicRoutes.nostr, - title: 'Login', + title: 'Nostr Login', sx: { padding: '10px' }, label: nostr logo } diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx index e6933c6..33b5e0c 100644 --- a/src/pages/login/index.tsx +++ b/src/pages/login/index.tsx @@ -12,6 +12,11 @@ export const Login = () => { margin="dense" autoComplete="username" disabled + sx={{ + input: { + cursor: 'not-allowed' + } + }} /> { margin="dense" autoComplete="current-password" disabled + sx={{ + input: { + cursor: 'not-allowed' + } + }} /> + + or + ({ @@ -22,6 +24,13 @@ export const updateLoginMethod = ( payload }) +export const updateNostrLoginAuthMethod = ( + payload: NostrLoginAuthMethod | undefined +): UpdateNostrLoginAuthMethod => ({ + type: ActionTypes.UPDATE_NOSTR_LOGIN_AUTH_METHOD, + payload +}) + export const updateKeyPair = (payload: Keys | undefined): UpdateKeyPair => ({ type: ActionTypes.UPDATE_KEYPAIR, payload diff --git a/src/store/auth/reducer.ts b/src/store/auth/reducer.ts index dea4ed5..e87662b 100644 --- a/src/store/auth/reducer.ts +++ b/src/store/auth/reducer.ts @@ -30,6 +30,15 @@ const reducer = ( } } + case ActionTypes.UPDATE_NOSTR_LOGIN_AUTH_METHOD: { + const { payload } = action + + return { + ...state, + nostrLoginAuthMethod: payload + } + } + case ActionTypes.UPDATE_KEYPAIR: { const { payload } = action diff --git a/src/store/auth/types.ts b/src/store/auth/types.ts index a134a7b..d83fa87 100644 --- a/src/store/auth/types.ts +++ b/src/store/auth/types.ts @@ -1,6 +1,14 @@ import * as ActionTypes from '../actionTypes' import { RestoreState, UserLogout } from '../actions' +export enum NostrLoginAuthMethod { + Connect = 'connect', + ReadOnly = 'readOnly', + Extension = 'extension', + Local = 'local', + OTP = 'otp' +} + export enum LoginMethods { extension = 'extension', privateKey = 'privateKey', @@ -17,6 +25,7 @@ export interface AuthState { loggedIn: boolean usersPubkey?: string loginMethod?: LoginMethods + nostrLoginAuthMethod?: NostrLoginAuthMethod keyPair?: Keys nsecBunkerPubkey?: string nsecBunkerRelays?: string[] @@ -32,6 +41,11 @@ export interface UpdateLoginMethod { payload: LoginMethods | undefined } +export interface UpdateNostrLoginAuthMethod { + type: typeof ActionTypes.UPDATE_NOSTR_LOGIN_AUTH_METHOD + payload: NostrLoginAuthMethod | undefined +} + export interface UpdateKeyPair { type: typeof ActionTypes.UPDATE_KEYPAIR payload: Keys | undefined @@ -51,6 +65,7 @@ export type AuthDispatchTypes = | RestoreState | SetAuthState | UpdateLoginMethod + | UpdateNostrLoginAuthMethod | UpdateKeyPair | UpdateNsecBunkerPubkey | UpdateNsecBunkerRelays diff --git a/src/store/metadata/reducer.ts b/src/store/metadata/reducer.ts index 9223ef3..e81d675 100644 --- a/src/store/metadata/reducer.ts +++ b/src/store/metadata/reducer.ts @@ -2,7 +2,15 @@ import * as ActionTypes from '../actionTypes' import { MetadataDispatchTypes } from './types' import { Event } from 'nostr-tools' -const initialState: Event | null = null +const initialState: Event = { + content: '', + created_at: 0, + id: '', + kind: 0, + pubkey: '', + sig: '', + tags: [] +} const reducer = ( state = initialState, diff --git a/src/utils/nostr.ts b/src/utils/nostr.ts index 248f71a..d6be22e 100644 --- a/src/utils/nostr.ts +++ b/src/utils/nostr.ts @@ -426,6 +426,8 @@ export const getUsersAppData = async (): Promise => { // Handle case where the encrypted content is an empty object if (encryptedContent === '{}') { + // Generate ephemeral key pair + // https://docs.sigit.io/#/technical?id=storing-app-data const secret = generateSecretKey() const pubKey = getPublicKey(secret) From 637e26bf35c77e866fa4088174898be1682f349f Mon Sep 17 00:00:00 2001 From: enes Date: Sat, 5 Oct 2024 14:56:34 +0200 Subject: [PATCH 16/41] refactor: init nostr-login, login method strategies, remove bunker --- src/App.tsx | 16 +- src/components/AppBar/AppBar.tsx | 47 +- src/controllers/AuthController.ts | 2 +- src/controllers/NostrController.ts | 448 +----------------- src/hooks/useLogout.tsx | 27 ++ src/layouts/Main.tsx | 107 +++-- src/pages/create/index.tsx | 17 - src/pages/nostr/index.tsx | 374 ++------------- src/pages/settings/Settings.tsx | 47 +- src/pages/settings/cache/index.tsx | 3 +- src/pages/settings/nostrLogin/index.tsx | 71 +++ src/pages/settings/profile/index.tsx | 40 +- src/pages/sign/index.tsx | 20 - src/pages/sign/style.module.scss | 1 + src/routes/index.tsx | 8 +- .../LoginMethodStrategy/NostrLoginStrategy.ts | 75 +++ .../LoginMethodStrategy/PrivateKeyStrategy.ts | 118 +++++ .../LoginMethodStrategy/loginMethodContext.ts | 43 ++ .../loginMethodStrategy.ts | 31 ++ src/store/actionTypes.ts | 2 - src/store/auth/action.ts | 20 +- src/store/auth/reducer.ts | 23 +- src/store/auth/types.ts | 23 +- src/utils/localStorage.ts | 13 +- 24 files changed, 577 insertions(+), 999 deletions(-) create mode 100644 src/hooks/useLogout.tsx create mode 100644 src/pages/settings/nostrLogin/index.tsx create mode 100644 src/services/LoginMethodStrategy/NostrLoginStrategy.ts create mode 100644 src/services/LoginMethodStrategy/PrivateKeyStrategy.ts create mode 100644 src/services/LoginMethodStrategy/loginMethodContext.ts create mode 100644 src/services/LoginMethodStrategy/loginMethodStrategy.ts diff --git a/src/App.tsx b/src/App.tsx index 1523e6f..cbb919e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,7 @@ import { useEffect } from 'react' import { useSelector } from 'react-redux' import { Navigate, Route, Routes } from 'react-router-dom' -import { AuthController, NostrController } from './controllers' +import { AuthController } from './controllers' import { MainLayout } from './layouts/Main' import { appPrivateRoutes, @@ -11,7 +11,6 @@ import { recursiveRouteRenderer } from './routes' import { State } from './store/rootReducer' -import { getNsecBunkerDelegatedKey, saveNsecBunkerDelegatedKey } from './utils' import './App.scss' const App = () => { @@ -25,23 +24,10 @@ const App = () => { window.location.hostname = 'localhost' } - generateBunkerDelegatedKey() - const authController = new AuthController() authController.checkSession() }, []) - const generateBunkerDelegatedKey = () => { - const existingKey = getNsecBunkerDelegatedKey() - - if (!existingKey) { - const nostrController = NostrController.getInstance() - const newDelegatedKey = nostrController.generateDelegatedKey() - - saveNsecBunkerDelegatedKey(newDelegatedKey) - } - } - const handleRootRedirect = () => { if (authState.loggedIn) return appPrivateRoutes.homePage const callbackPathEncoded = btoa( diff --git a/src/components/AppBar/AppBar.tsx b/src/components/AppBar/AppBar.tsx index ff68469..084a8f0 100644 --- a/src/components/AppBar/AppBar.tsx +++ b/src/components/AppBar/AppBar.tsx @@ -9,44 +9,28 @@ import { } from '@mui/material' import { useEffect, useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { - setAuthState, - setMetadataEvent, - userLogOutAction -} from '../../store/actions' +import { useSelector } from 'react-redux' import { State } from '../../store/rootReducer' -import { Dispatch } from '../../store/store' import Username from '../username' import { Link, useNavigate } from 'react-router-dom' -import { MetadataController, NostrController } from '../../controllers' import { appPublicRoutes, appPrivateRoutes, getProfileRoute } from '../../routes' -import { - clearAuthToken, - getProfileUsername, - hexToNpub, - saveNsecBunkerDelegatedKey -} from '../../utils' +import { getProfileUsername, hexToNpub } from '../../utils' import styles from './style.module.scss' -import { setUserRobotImage } from '../../store/userRobotImage/action' import { Container } from '../Container' import { ButtonIcon } from '../ButtonIcon' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faClose } from '@fortawesome/free-solid-svg-icons' import useMediaQuery from '@mui/material/useMediaQuery' - -const metadataController = MetadataController.getInstance() +import { useLogout } from '../../hooks/useLogout' export const AppBar = () => { const navigate = useNavigate() - - const dispatch: Dispatch = useDispatch() - + const logout = useLogout() const [username, setUsername] = useState('') const [userAvatar, setUserAvatar] = useState('') const [anchorElUser, setAnchorElUser] = useState(null) @@ -94,28 +78,7 @@ export const AppBar = () => { const handleLogout = () => { handleCloseUserMenu() - dispatch( - 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) - + logout() navigate('/') } const isAuthenticated = authState?.loggedIn === true diff --git a/src/controllers/AuthController.ts b/src/controllers/AuthController.ts index cc8def5..b9d9779 100644 --- a/src/controllers/AuthController.ts +++ b/src/controllers/AuthController.ts @@ -31,7 +31,7 @@ export class AuthController { /** * Function will authenticate user by signing an auth event * which is done by calling the sign() function, where appropriate - * method will be chosen (extension, nsecbunker or keys) + * method will be chosen (extension or keys) * * @param pubkey of the user trying to login * @returns url to redirect if authentication successfull diff --git a/src/controllers/NostrController.ts b/src/controllers/NostrController.ts index 0547ffb..9e47ff3 100644 --- a/src/controllers/NostrController.ts +++ b/src/controllers/NostrController.ts @@ -1,194 +1,25 @@ -import NDK, { - NDKEvent, - NDKNip46Signer, - NDKPrivateKeySigner, - NDKUser, - NostrEvent -} from '@nostr-dev-kit/ndk' -import { - Event, - EventTemplate, - UnsignedEvent, - finalizeEvent, - nip04, - nip19, - nip44 -} from 'nostr-tools' +import { EventTemplate, UnsignedEvent } from 'nostr-tools' +import { WindowNostr } from 'nostr-tools/nip07' import { EventEmitter } from 'tseep' -import { updateNsecbunkerPubkey } from '../store/actions' -import { AuthState, LoginMethods } from '../store/auth/types' +import { AuthState } from '../store/auth/types' import store from '../store/store' import { SignedEvent } from '../types' -import { getNsecBunkerDelegatedKey, verifySignedEvent } from '../utils' +import { LoginMethodContext } from '../services/LoginMethodStrategy/loginMethodContext' export class NostrController extends EventEmitter { private static instance: NostrController - private bunkerNDK: NDK | undefined - private remoteSigner: NDKNip46Signer | undefined - private constructor() { super() } - private getNostrObject = () => { - // fix: this is not picking up type declaration from src/system/index.d.ts - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (window.nostr) return window.nostr as any + if (window.nostr) return window.nostr as WindowNostr throw new Error( `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 => { - 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 { if (!NostrController.instance) { NostrController.instance = new NostrController() @@ -207,59 +38,10 @@ export class NostrController extends EventEmitter { nip44Encrypt = async (receiver: string, content: string) => { // Retrieve the current login method from the application's redux state. const loginMethod = (store.getState().auth as AuthState).loginMethod + const context = new LoginMethodContext(loginMethod) // Handle encryption when the login method is via an extension. - if (loginMethod === LoginMethods.extension) { - 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') + return await context.nip44Encrypt(receiver, content) } /** @@ -273,65 +55,15 @@ export class NostrController extends EventEmitter { nip44Decrypt = async (sender: string, content: string) => { // Retrieve the current login method from the application's redux state. const loginMethod = (store.getState().auth as AuthState).loginMethod + const context = new LoginMethodContext(loginMethod) - // Handle decryption when the login method is via an extension. - if (loginMethod === LoginMethods.extension) { - 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') + // Handle decryption + return await context.nip44EDecrypt(sender, content) } /** * Signs an event with private key (if it is present in local storage) or - * with browser extension (if it is present) or - * with nSecBunker instance. + * with browser extension (if it is present) * @param event - unsigned nostr event. * @returns - a promised that is resolved with signed nostr event. */ @@ -339,113 +71,16 @@ export class NostrController extends EventEmitter { event: UnsignedEvent | EventTemplate ): Promise => { const loginMethod = (store.getState().auth as AuthState).loginMethod + const context = new LoginMethodContext(loginMethod) - if (!loginMethod) { - return Promise.reject('No login method found in the browser storage') - } - - if (loginMethod === LoginMethods.nsecBunker) { - // Check if nsecBunker is available - if (!this.bunkerNDK) { - return Promise.reject( - `Login method is ${loginMethod} but bunkerNDK is not created` - ) - } - - if (!this.remoteSigner) { - return Promise.reject( - `Login method is ${loginMethod} but bunkerNDK is not created` - ) - } - - const signedEvent = await this.signWithNsecBunker( - '', - event as NostrEvent - ).catch((err) => { - throw err - }) - - return Promise.resolve(signedEvent as SignedEvent) - } else if (loginMethod === LoginMethods.privateKey) { - const keys = (store.getState().auth as AuthState).keyPair - - if (!keys) { - return Promise.reject( - `Login method is ${loginMethod}, but keys are not found` - ) - } - - const { private: 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` - ) - } + return await context.signEvent(event) } nip04Encrypt = async (receiver: string, content: string): Promise => { const loginMethod = (store.getState().auth as AuthState).loginMethod + const context = new LoginMethodContext(loginMethod) - if (loginMethod === LoginMethods.extension) { - 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') + return await context.nip04Encrypt(receiver, content) } /** @@ -457,50 +92,9 @@ export class NostrController extends EventEmitter { */ nip04Decrypt = async (sender: string, content: string): Promise => { const loginMethod = (store.getState().auth as AuthState).loginMethod + const context = new LoginMethodContext(loginMethod) - if (loginMethod === LoginMethods.extension) { - 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') + return await context.nip04EDecrypt(sender, content) } /** @@ -523,12 +117,4 @@ export class NostrController extends EventEmitter { return Promise.resolve(pubKey) } - - /** - * Generates NDK Private Signer - * @returns nSecBunker delegated key - */ - generateDelegatedKey = (): string => { - return NDKPrivateKeySigner.generate().privateKey! - } } diff --git a/src/hooks/useLogout.tsx b/src/hooks/useLogout.tsx new file mode 100644 index 0000000..f7f81fd --- /dev/null +++ b/src/hooks/useLogout.tsx @@ -0,0 +1,27 @@ +import { logout as nostrLogout } from 'nostr-login' +import { clear } from '../utils/localStorage' +import { useDispatch } from 'react-redux' +import { Dispatch } from '../store/store' +import { userLogOutAction } from '../store/actions' +import { LoginMethod } from '../store/auth/types' +import { useAppSelector } from './store' + +export const useLogout = () => { + const loginMethod = useAppSelector((state) => state.auth?.loginMethod) + const dispatch: Dispatch = useDispatch() + + const logout = () => { + // 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() + } + + return logout +} diff --git a/src/layouts/Main.tsx b/src/layouts/Main.tsx index 3a1f10e..262e739 100644 --- a/src/layouts/Main.tsx +++ b/src/layouts/Main.tsx @@ -1,34 +1,42 @@ import { Event, kinds } from 'nostr-tools' -import { useEffect, useRef, useState } from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' -import { Outlet } from 'react-router-dom' +import { Outlet, useNavigate, useSearchParams } from 'react-router-dom' import { AppBar } from '../components/AppBar/AppBar' import { LoadingSpinner } from '../components/LoadingSpinner' -import { MetadataController, NostrController } from '../controllers' +import { + AuthController, + MetadataController, + NostrController +} from '../controllers' import { restoreState, - setAuthState, setMetadataEvent, + updateLoginMethod, + updateNostrLoginAuthMethod, updateUserAppData } 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 { - clearAuthToken, - clearState, getRoboHashPicture, getUsersAppData, loadState, - saveNsecBunkerDelegatedKey, subscribeForSigits } from '../utils' import { useAppSelector } from '../hooks' 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 = () => { + const [searchParams] = useSearchParams() + const navigate = useNavigate() const dispatch: Dispatch = useDispatch() + const logout = useLogout() const [isLoading, setIsLoading] = useState(true) const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState(`Loading App`) const authState = useSelector((state: State) => state.auth) @@ -37,51 +45,63 @@ export const MainLayout = () => { // Ref to track if `subscribeForSigits` has been called const hasSubscribed = useRef(false) - useEffect(() => { - const metadataController = MetadataController.getInstance() + const navigateAfterLogin = (path: string) => { + const callbackPath = searchParams.get('callbackPath') - 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) + 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(() => { + const handleNostrAuth = (_: string, opts: NostrLoginAuthOptions) => { + console.log(opts.method) + if (opts.type === 'logout') { + logout() + } else { + dispatch(updateNostrLoginAuthMethod(opts.method)) + login() + } + } + + initNostrLogin({ + darkMode: false, + noBanner: true, + onAuth: handleNostrAuth + }) + + const metadataController = MetadataController.getInstance() + const restoredState = loadState() if (restoredState) { dispatch(restoreState(restoredState)) - const { loggedIn, loginMethod, usersPubkey, nsecBunkerRelays } = - restoredState.auth + const { loggedIn, loginMethod, usersPubkey } = restoredState.auth if (loggedIn) { if (!loginMethod || !usersPubkey) return logout() - if (loginMethod === LoginMethods.nsecBunker) { - if (!nsecBunkerRelays) return logout() - - const nostrController = NostrController.getInstance() - nostrController.nsecBunkerInit(nsecBunkerRelays).then(() => { - nostrController.createNsecBunkerSigner(usersPubkey) - }) - } - const handleMetadataEvent = (event: Event) => { dispatch(setMetadataEvent(event)) } @@ -101,7 +121,8 @@ export const MainLayout = () => { } else { setIsLoading(false) } - }, [dispatch]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) useEffect(() => { if (authState.loggedIn && usersAppData) { diff --git a/src/pages/create/index.tsx b/src/pages/create/index.tsx index 59fdb4f..055b33f 100644 --- a/src/pages/create/index.tsx +++ b/src/pages/create/index.tsx @@ -74,8 +74,6 @@ export const CreatePage = () => { const [isLoading, setIsLoading] = useState(false) const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('') - const [authUrl, setAuthUrl] = useState() - const [title, setTitle] = useState(`sigit_${formatTimestamp(Date.now())}`) const [selectedFiles, setSelectedFiles] = useState([]) @@ -183,10 +181,6 @@ export const CreatePage = () => { } }) }, [metadata, users]) - // Set up event listener for authentication event - nostrController.on('nsecbunker-auth', (url) => { - setAuthUrl(url) - }) useEffect(() => { if (uploadedFiles) { @@ -761,17 +755,6 @@ export const CreatePage = () => { } } - if (authUrl) { - return ( -