diff --git a/src/components/copyModal.tsx b/src/components/copyModal.tsx
new file mode 100644
index 0000000..858b424
--- /dev/null
+++ b/src/components/copyModal.tsx
@@ -0,0 +1,27 @@
+import { Dialog, DialogContent, DialogTitle } from '@mui/material'
+import { CopyToClipboard } from './copyToClipboard'
+
+interface CopyModalProps {
+ open: boolean
+ handleClose: () => void
+ title: string
+ textToCopy: string
+}
+
+export const CopyModal = ({
+ open,
+ handleClose,
+ title,
+ textToCopy
+}: CopyModalProps) => {
+ return (
+
+ )
+}
+
+export default CopyModal
diff --git a/src/components/copyToClipboard.tsx b/src/components/copyToClipboard.tsx
new file mode 100644
index 0000000..79c01c4
--- /dev/null
+++ b/src/components/copyToClipboard.tsx
@@ -0,0 +1,51 @@
+import { ContentCopy } from '@mui/icons-material/'
+import { Box, IconButton, Typography } from '@mui/material'
+import { toast } from 'react-toastify'
+
+type Props = {
+ textToCopy: string
+}
+
+export const CopyToClipboard = ({ textToCopy }: Props) => {
+ const handleCopyClick = () => {
+ navigator.clipboard.writeText(textToCopy)
+ toast.success('Copied to clipboard', {
+ autoClose: 1000,
+ hideProgressBar: true
+ })
+ }
+
+ return (
+
+ {
+ e.stopPropagation()
+ handleCopyClick()
+ }}
+ component="label"
+ sx={{
+ flex: '1',
+ overflow: 'auto',
+ whiteSpace: 'nowrap',
+ cursor: 'pointer'
+ }}
+ >
+ {textToCopy}
+
+ {
+ e.stopPropagation()
+ handleCopyClick()
+ }}
+ >
+
+
+
+ )
+}
diff --git a/src/controllers/AuthController.ts b/src/controllers/AuthController.ts
index 1b7dc0b..e0d2d79 100644
--- a/src/controllers/AuthController.ts
+++ b/src/controllers/AuthController.ts
@@ -58,7 +58,7 @@ export class AuthController {
const { hostname } = window.location
const authEvent: EventTemplate = {
- kind: 1,
+ kind: 27235,
tags: [],
content: `${hostname}-${timestamp}`,
created_at: timestamp
diff --git a/src/controllers/MetadataController.ts b/src/controllers/MetadataController.ts
index dca3050..94f5899 100644
--- a/src/controllers/MetadataController.ts
+++ b/src/controllers/MetadataController.ts
@@ -206,15 +206,15 @@ export class MetadataController {
if (userRelays.length === 0) return null
- // filter for finding user's first kind 1 event
+ // filter for finding user's first kind 0 event
const eventFilter: Filter = {
- kinds: [kinds.ShortTextNote],
+ kinds: [kinds.Metadata],
authors: [hexKey]
}
const pool = new SimplePool()
- // find user's kind 1 events published on user's relays
+ // find user's kind 0 events published on user's relays
const events = await pool.querySync(userRelays, eventFilter)
if (events && events.length) {
// sort events by created_at time in ascending order
diff --git a/src/pages/create/index.tsx b/src/pages/create/index.tsx
index 9acb138..4289525 100644
--- a/src/pages/create/index.tsx
+++ b/src/pages/create/index.tsx
@@ -47,11 +47,15 @@ import { DndProvider } from 'react-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'
import type { Identifier, XYCoord } from 'dnd-core'
import { useDrag, useDrop } from 'react-dnd'
+import saveAs from 'file-saver'
+import CopyModal from '../../components/copyModal'
export const CreatePage = () => {
const navigate = useNavigate()
const [isLoading, setIsLoading] = useState(false)
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
+ const [openCopyModal, setOpenCopyModel] = useState(false)
+ const [textToCopy, setTextToCopy] = useState('')
const [authUrl, setAuthUrl] = useState()
@@ -329,58 +333,65 @@ export const CreatePage = () => {
const encryptedArrayBuffer = await encryptArrayBuffer(
arraybuffer,
encryptionKey
- )
+ ).finally(() => setIsLoading(false))
const blob = new Blob([encryptedArrayBuffer])
- setLoadingSpinnerDesc('Uploading zip file to file storage.')
- const fileUrl = await uploadToFileStorage(blob, nostrController)
- .then((url) => {
- toast.success('zip file uploaded to file storage')
- return url
- })
- .catch((err) => {
- console.log('err in upload:>> ', err)
- setIsLoading(false)
- toast.error(err.message || 'Error occurred in uploading zip file')
- return null
- })
+ if (navigator.onLine) {
+ setIsLoading(true)
+ setLoadingSpinnerDesc('Uploading zip file to file storage.')
+ const fileUrl = await uploadToFileStorage(blob, nostrController)
+ .then((url) => {
+ toast.success('zip file uploaded to file storage')
+ return url
+ })
+ .catch((err) => {
+ console.log('err in upload:>> ', err)
+ setIsLoading(false)
+ toast.error(err.message || 'Error occurred in uploading zip file')
+ return null
+ })
- if (!fileUrl) return
+ if (!fileUrl) return
- setLoadingSpinnerDesc('Sending DM to signers/viewers')
+ setLoadingSpinnerDesc('Sending DM to signers/viewers')
- // send DM to first signer if exists
- if (signers.length > 0) {
- await sendDM(
- fileUrl,
- encryptionKey,
- signers[0].pubkey,
- nostrController,
- true,
- setAuthUrl
- )
- } else {
- // send DM to all viewers if no signer
- for (const viewer of viewers) {
- // todo: execute in parallel
+ // send DM to first signer if exists
+ if (signers.length > 0) {
await sendDM(
fileUrl,
encryptionKey,
- viewer.pubkey,
+ signers[0].pubkey,
nostrController,
- false,
+ true,
setAuthUrl
)
+ } else {
+ // send DM to all viewers if no signer
+ for (const viewer of viewers) {
+ // todo: execute in parallel
+ await sendDM(
+ fileUrl,
+ encryptionKey,
+ viewer.pubkey,
+ nostrController,
+ false,
+ setAuthUrl
+ )
+ }
}
- }
- setIsLoading(false)
+ setIsLoading(false)
- navigate(
- `${appPrivateRoutes.sign}?file=${encodeURIComponent(
- fileUrl
- )}&key=${encodeURIComponent(encryptionKey)}`
- )
+ navigate(
+ `${appPrivateRoutes.sign}?file=${encodeURIComponent(
+ fileUrl
+ )}&key=${encodeURIComponent(encryptionKey)}`
+ )
+ } else {
+ saveAs(blob, 'request.sigit')
+ setTextToCopy(encryptionKey)
+ setOpenCopyModel(true)
+ }
}
if (authUrl) {
@@ -471,6 +482,15 @@ export const CreatePage = () => {
>
)}
+ {
+ setOpenCopyModel(false)
+ navigate(appPrivateRoutes.sign)
+ }}
+ title="Decryption key for Sigit file"
+ textToCopy={textToCopy}
+ />
>
)
}
diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx
index aa26270..269264f 100644
--- a/src/pages/sign/index.tsx
+++ b/src/pages/sign/index.tsx
@@ -59,7 +59,7 @@ import {
Download,
HourglassTop
} from '@mui/icons-material'
-
+import CopyModal from '../../components/copyModal'
enum SignedStatus {
Fully_Signed,
User_Is_Next_Signer,
@@ -79,6 +79,8 @@ export const SignPage = () => {
const [isLoading, setIsLoading] = useState(true)
const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
+ const [openCopyModal, setOpenCopyModel] = useState(false)
+ const [textToCopy, setTextToCopy] = useState('')
const [meta, setMeta] = useState(null)
const [signedStatus, setSignedStatus] = useState()
@@ -98,6 +100,9 @@ export const SignPage = () => {
const [nextSinger, setNextSinger] = useState()
+ // This state variable indicates whether the logged-in user is a signer, a creator, or neither.
+ const [isSignerOrCreator, setIsSignerOrCreator] = useState(false)
+
const usersPubkey = useSelector((state: State) => state.auth.usersPubkey)
const [authUrl, setAuthUrl] = useState()
@@ -167,7 +172,20 @@ export const SignPage = () => {
// there's no signer just viewers. So its fully signed
setSignedStatus(SignedStatus.Fully_Signed)
}
- }, [signers, signedBy, usersPubkey])
+
+ // Determine and set the status of the user
+ if (submittedBy && usersPubkey && submittedBy === usersPubkey) {
+ // If the submission was made by the user, set the status to true
+ setIsSignerOrCreator(true)
+ } else if (usersPubkey) {
+ // Convert the user's public key from hex to npub format
+ const usersNpub = hexToNpub(usersPubkey)
+ if (signers.includes(usersNpub)) {
+ // If the user's npub is in the list of signers, set the status to true
+ setIsSignerOrCreator(true)
+ }
+ }
+ }, [signers, signedBy, usersPubkey, submittedBy])
useEffect(() => {
const fileUrl = searchParams.get('file')
@@ -236,6 +254,7 @@ export const SignPage = () => {
if (!zip) return
setZip(zip)
+ setDisplayInput(false)
setLoadingSpinnerDesc('Parsing meta.json')
@@ -414,75 +433,79 @@ export const SignPage = () => {
const blob = new Blob([encryptedArrayBuffer])
- setLoadingSpinnerDesc('Uploading zip file to file storage.')
- const fileUrl = await uploadToFileStorage(blob, nostrController)
- .then((url) => {
- toast.success('zip file uploaded to file storage')
- return url
- })
- .catch((err) => {
- console.log('err in upload:>> ', err)
- setIsLoading(false)
- toast.error(err.message || 'Error occurred in uploading zip file')
- return null
- })
+ if (navigator.onLine) {
+ setLoadingSpinnerDesc('Uploading zip file to file storage.')
+ const fileUrl = await uploadToFileStorage(blob, nostrController)
+ .then((url) => {
+ toast.success('zip file uploaded to file storage')
+ return url
+ })
+ .catch((err) => {
+ console.log('err in upload:>> ', err)
+ setIsLoading(false)
+ toast.error(err.message || 'Error occurred in uploading zip file')
+ return null
+ })
- if (!fileUrl) return
+ if (!fileUrl) return
- // check if the current user is the last signer
- const usersNpub = hexToNpub(usersPubkey!)
- const lastSignerIndex = signers.length - 1
- const signerIndex = signers.indexOf(usersNpub)
- const isLastSigner = signerIndex === lastSignerIndex
+ // check if the current user is the last signer
+ const usersNpub = hexToNpub(usersPubkey!)
+ const lastSignerIndex = signers.length - 1
+ const signerIndex = signers.indexOf(usersNpub)
+ const isLastSigner = signerIndex === lastSignerIndex
- // if current user is the last signer, then send DMs to all signers and viewers
- if (isLastSigner) {
- const userSet = new Set<`npub1${string}`>()
+ // if current user is the last signer, then send DMs to all signers and viewers
+ if (isLastSigner) {
+ const userSet = new Set<`npub1${string}`>()
- if (submittedBy) {
- userSet.add(hexToNpub(submittedBy))
- }
+ if (submittedBy) {
+ userSet.add(hexToNpub(submittedBy))
+ }
- signers.forEach((signer) => {
- userSet.add(signer)
- })
+ signers.forEach((signer) => {
+ userSet.add(signer)
+ })
- viewers.forEach((viewer) => {
- userSet.add(viewer)
- })
+ viewers.forEach((viewer) => {
+ userSet.add(viewer)
+ })
- const users = Array.from(userSet)
+ const users = Array.from(userSet)
- for (const user of users) {
- // todo: execute in parallel
+ for (const user of users) {
+ // todo: execute in parallel
+ await sendDM(
+ fileUrl,
+ key,
+ npubToHex(user)!,
+ nostrController,
+ false,
+ setAuthUrl
+ )
+ }
+ } else {
+ const nextSigner = signers[signerIndex + 1]
await sendDM(
fileUrl,
key,
- npubToHex(user)!,
+ npubToHex(nextSigner)!,
nostrController,
- false,
+ true,
setAuthUrl
)
}
+
+ setIsLoading(false)
+
+ // update search params with updated file url and encryption key
+ setSearchParams({
+ file: fileUrl,
+ key: key
+ })
} else {
- const nextSigner = signers[signerIndex + 1]
- await sendDM(
- fileUrl,
- key,
- npubToHex(nextSigner)!,
- nostrController,
- true,
- setAuthUrl
- )
+ handleDecryptedArrayBuffer(arrayBuffer).finally(() => setIsLoading(false))
}
-
- setIsLoading(false)
-
- // update search params with updated file url and encryption key
- setSearchParams({
- file: fileUrl,
- key: key
- })
}
const handleExport = async () => {
@@ -549,6 +572,37 @@ export const SignPage = () => {
navigate(appPrivateRoutes.verify)
}
+ const handleExportSigit = async () => {
+ if (!zip) return
+
+ const arrayBuffer = await zip
+ .generateAsync({
+ type: 'arraybuffer',
+ compression: 'DEFLATE',
+ compressionOptions: {
+ level: 6
+ }
+ })
+ .catch((err) => {
+ console.log('err in zip:>> ', err)
+ setIsLoading(false)
+ toast.error(err.message || 'Error occurred in generating zip file')
+ return null
+ })
+
+ if (!arrayBuffer) return
+
+ const key = await generateEncryptionKey()
+
+ setLoadingSpinnerDesc('Encrypting zip file')
+ const encryptedArrayBuffer = await encryptArrayBuffer(arrayBuffer, key)
+ const blob = new Blob([encryptedArrayBuffer])
+ saveAs(blob, 'exported.sigit')
+
+ setTextToCopy(key)
+ setOpenCopyModel(true)
+ }
+
/**
* This function accepts an npub of a signer and return the signature of its previous signer.
* This prevSig will be used in the content of the provided signer's signedEvent
@@ -637,6 +691,7 @@ export const SignPage = () => {
setSelectedFile(value)}
/>
@@ -675,6 +730,7 @@ export const SignPage = () => {
nextSigner={nextSinger}
getPrevSignersSig={getPrevSignersSig}
/>
+
{signedStatus === SignedStatus.Fully_Signed && (
)}
+
+ {isSignerOrCreator &&
+ signedStatus === SignedStatus.User_Is_Not_Next_Signer && (
+
+
+
+ )}
>
)}
+ setOpenCopyModel(false)}
+ title="Decryption key for Sigit file"
+ textToCopy={textToCopy}
+ />
>
)
}
diff --git a/src/utils/misc.ts b/src/utils/misc.ts
index 56ba1e6..03b2fa6 100644
--- a/src/utils/misc.ts
+++ b/src/utils/misc.ts
@@ -189,7 +189,7 @@ export const signEventForMetaFile = async (
) => {
// Construct the event metadata for the meta file
const event: EventTemplate = {
- kind: 1, // Event type for meta file
+ kind: 27235, // Event type for meta file
content: content, // content for event
created_at: Math.floor(Date.now() / 1000), // Current timestamp
tags: []