Merge pull request 'feat: add the ability to create and sign while user is offline' (#85) from issue-73 into staging
All checks were successful
Release to Staging / build_and_release (push) Successful in 1m4s

Reviewed-on: https://git.sigit.io/sig/it/pulls/85
Reviewed-by: Y <yury@4gl.io>
This commit is contained in:
s 2024-05-28 10:27:40 +00:00
commit 47c545b7f1
7 changed files with 266 additions and 97 deletions

View File

@ -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 (
<Dialog open={open} onClose={handleClose} maxWidth="xs">
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<CopyToClipboard textToCopy={textToCopy} />
</DialogContent>
</Dialog>
)
}
export default CopyModal

View File

@ -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 (
<Box
sx={{
display: 'flex',
alignItems: 'center',
width: '100%'
}}
>
<Typography
onClick={(e) => {
e.stopPropagation()
handleCopyClick()
}}
component="label"
sx={{
flex: '1',
overflow: 'auto',
whiteSpace: 'nowrap',
cursor: 'pointer'
}}
>
{textToCopy}
</Typography>
<IconButton
onClick={(e) => {
e.stopPropagation()
handleCopyClick()
}}
>
<ContentCopy />
</IconButton>
</Box>
)
}

View File

@ -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

View File

@ -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

View File

@ -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<string>()
@ -329,10 +333,12 @@ export const CreatePage = () => {
const encryptedArrayBuffer = await encryptArrayBuffer(
arraybuffer,
encryptionKey
)
).finally(() => setIsLoading(false))
const blob = new Blob([encryptedArrayBuffer])
if (navigator.onLine) {
setIsLoading(true)
setLoadingSpinnerDesc('Uploading zip file to file storage.')
const fileUrl = await uploadToFileStorage(blob, nostrController)
.then((url) => {
@ -381,6 +387,11 @@ export const CreatePage = () => {
fileUrl
)}&key=${encodeURIComponent(encryptionKey)}`
)
} else {
saveAs(blob, 'request.sigit')
setTextToCopy(encryptionKey)
setOpenCopyModel(true)
}
}
if (authUrl) {
@ -471,6 +482,15 @@ export const CreatePage = () => {
</>
)}
</Box>
<CopyModal
open={openCopyModal}
handleClose={() => {
setOpenCopyModel(false)
navigate(appPrivateRoutes.sign)
}}
title="Decryption key for Sigit file"
textToCopy={textToCopy}
/>
</>
)
}

View File

@ -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<Meta | null>(null)
const [signedStatus, setSignedStatus] = useState<SignedStatus>()
@ -98,6 +100,9 @@ export const SignPage = () => {
const [nextSinger, setNextSinger] = useState<string>()
// 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<string>()
@ -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,6 +433,7 @@ export const SignPage = () => {
const blob = new Blob([encryptedArrayBuffer])
if (navigator.onLine) {
setLoadingSpinnerDesc('Uploading zip file to file storage.')
const fileUrl = await uploadToFileStorage(blob, nostrController)
.then((url) => {
@ -483,6 +503,9 @@ export const SignPage = () => {
file: fileUrl,
key: key
})
} else {
handleDecryptedArrayBuffer(arrayBuffer).finally(() => setIsLoading(false))
}
}
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 = () => {
<Box className={styles.inputBlock}>
<MuiFileInput
placeholder="Select file"
inputProps={{ accept: '.sigit' }}
value={selectedFile}
onChange={(value) => setSelectedFile(value)}
/>
@ -675,6 +730,7 @@ export const SignPage = () => {
nextSigner={nextSinger}
getPrevSignersSig={getPrevSignersSig}
/>
{signedStatus === SignedStatus.Fully_Signed && (
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
<Button onClick={handleExport} variant="contained">
@ -690,9 +746,24 @@ export const SignPage = () => {
</Button>
</Box>
)}
{isSignerOrCreator &&
signedStatus === SignedStatus.User_Is_Not_Next_Signer && (
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
<Button onClick={handleExportSigit} variant="contained">
Export Sigit
</Button>
</Box>
)}
</>
)}
</Box>
<CopyModal
open={openCopyModal}
handleClose={() => setOpenCopyModel(false)}
title="Decryption key for Sigit file"
textToCopy={textToCopy}
/>
</>
)
}

View File

@ -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: []