From 6f8830a77ccf40eed0c11ccc65ae5d736dfb981d Mon Sep 17 00:00:00 2001 From: SwiftHawk Date: Sat, 8 Jun 2024 00:37:03 +0500 Subject: [PATCH 01/17] fix: home screen style fixed for mobile view --- src/pages/home/index.tsx | 124 +++++++++++++++++++++++-------- src/pages/home/style.module.scss | 6 +- 2 files changed, 95 insertions(+), 35 deletions(-) diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 0afb321..9cf4508 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -5,7 +5,7 @@ import { PersonOutline, Upload } from '@mui/icons-material' -import { Box, Button, Typography } from '@mui/material' +import { Box, Button, Tooltip, Typography } from '@mui/material' import { useNavigate } from 'react-router-dom' import { appPrivateRoutes } from '../../routes' import styles from './style.module.scss' @@ -19,7 +19,16 @@ export const HomePage = () => { Sigits - + {/* This is for desktop view */} + + {/* This is for mobile view */} + + + + + + + + + + + + + - - - ) } const PlaceHolder = () => { return ( - - - - - - Title - - - - Sigit - - - - 07 Jun 10:23 AM + + + + + Title + + + + Sigit + + + + 07 Jun 10:23 AM + + + + + + Sent + placeholder@sigit.io - - - - Sent - - placeholder@sigit.io - - - - Awaiting - - placeholder@sigit.io - + + + Awaiting + + placeholder@sigit.io diff --git a/src/pages/home/style.module.scss b/src/pages/home/style.module.scss index 69c5019..e722d70 100644 --- a/src/pages/home/style.module.scss +++ b/src/pages/home/style.module.scss @@ -15,7 +15,6 @@ } .actionButtons { - display: flex; justify-content: center; align-items: center; gap: 10px; @@ -25,6 +24,7 @@ .submissions { display: flex; flex-direction: column; + gap: 10px; .item { display: flex; @@ -33,10 +33,10 @@ .titleBox { display: flex; - flex-direction: column; align-items: flex-start; + justify-content: space-between; padding: 10px; - background-color: #e7e2df99; + background-color: #cdc8c499; border-top-left-radius: inherit; border-bottom-left-radius: inherit; From c530abd298e9340169b7bde8cae160d9c579bb63 Mon Sep 17 00:00:00 2001 From: SwiftHawk Date: Mon, 10 Jun 2024 18:10:43 +0500 Subject: [PATCH 02/17] chore(refactor): refactor handle create function --- src/pages/create/index.tsx | 298 ++++++++++++++++++++++++------------- 1 file changed, 194 insertions(+), 104 deletions(-) diff --git a/src/pages/create/index.tsx b/src/pages/create/index.tsx index 3789411..98525a4 100644 --- a/src/pages/create/index.tsx +++ b/src/pages/create/index.tsx @@ -221,57 +221,67 @@ export const CreatePage = () => { ) } - const handleCreate = async () => { + // Validate inputs before proceeding + const validateInputs = (): boolean => { if (!title.trim()) { toast.error('Title can not be empty') - return + return false } if (users.length === 0) { toast.error( - 'No signer/viewer is provided. At least add one signer or viewer.' + 'No signer/viewer is provided. At least add one signer or viewer.' ) - return + return false } if (selectedFiles.length === 0) { toast.error('No file is selected. Select at least 1 file') - return + return false } - setIsLoading(true) - setLoadingSpinnerDesc('Generating hashes for files') + return true + } + // Handle errors during file arrayBuffer conversion + const handleFileError = (file: File) => (err: any) => { + console.log( + `Error while getting arrayBuffer of file ${file.name} :>> `, + err + ) + toast.error( + err.message || `Error while getting arrayBuffer of file ${file.name}` + ) + return null + } + + // Generate hash for each selected file + const generateFileHashes = async (): Promise<{ + [key: string]: string + } | null> => { const fileHashes: { [key: string]: string } = {} - // generating file hashes for (const file of selectedFiles) { - const arraybuffer = await file.arrayBuffer().catch((err) => { - console.log( - `err while getting arrayBuffer of file ${file.name} :>> `, - err - ) - toast.error( - err.message || `err while getting arrayBuffer of file ${file.name}` - ) - return null - }) - - if (!arraybuffer) return + const arraybuffer = await file.arrayBuffer().catch(handleFileError(file)) + if (!arraybuffer) return null const hash = await getHash(arraybuffer) - if (!hash) { - setIsLoading(false) - return + return null } fileHashes[file.name] = hash } + return fileHashes + } + + // Create a zip file with the selected files and sign the event + const createZipFile = async (fileHashes: { + [key: string]: string + }): Promise<{ zip: JSZip; createSignature: string } | null> => { const zip = new JSZip() - // zipping files selectedFiles.forEach((file) => { zip.file(`files/${file.name}`, file) }) @@ -280,6 +290,7 @@ export const CreatePage = () => { const viewers = users.filter((user) => user.role === UserRole.viewer) setLoadingSpinnerDesc('Signing nostr event') + const createSignature = await signEventForMetaFile( JSON.stringify({ signers: signers.map((signer) => hexToNpub(signer.pubkey)), @@ -290,12 +301,27 @@ export const CreatePage = () => { setIsLoading ) - if (!createSignature) return + if (!createSignature) return null + try { + return { + zip, + createSignature: JSON.stringify(createSignature, null, 2) + } + } catch (error) { + return null + } + } + + // Add metadata and file hashes to the zip file + const addMetaToZip = async ( + zip: JSZip, + createSignature: string + ): Promise => { // create content for meta file const meta: Meta = { title, - createSignature: JSON.stringify(createSignature, null, 2), + createSignature, docSignatures: {} } @@ -304,112 +330,176 @@ export const CreatePage = () => { zip.file('meta.json', stringifiedMeta) const metaHash = await getHash(stringifiedMeta) - if (!metaHash) return + if (!metaHash) return null const metaHashJson = { [usersPubkey!]: metaHash } zip.file('hashes.json', JSON.stringify(metaHashJson, null, 2)) + return metaHash } catch (err) { console.error(err) toast.error('An error occurred in converting meta json to string') - return + return null } + } + // Handle errors during zip file generation + const handleZipError = (err: any) => { + console.log('Error in zip:>> ', err) + setIsLoading(false) + toast.error(err.message || 'Error occurred in generating zip file') + return null + } + + // Generate the zip file + const generateZipFile = async (zip: JSZip): Promise => { setLoadingSpinnerDesc('Generating zip file') 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 + compressionOptions: { level: 6 } }) + .catch(handleZipError) + return arraybuffer + } + + // Encrypt the zip file with the generated encryption key + const encryptZipFile = async ( + arraybuffer: ArrayBuffer, + encryptionKey: string + ): Promise => { + setLoadingSpinnerDesc('Encrypting zip file') + return encryptArrayBuffer(arraybuffer, encryptionKey).finally(() => + setIsLoading(false) + ) + } + + // Handle file upload and further actions based on online/offline status + const handleFileUpload = async (blob: Blob, encryptionKey: string) => { + if (await isOnline()) { + const fileUrl = await uploadFile(blob) + + if (!fileUrl) return + + await sendDMs(fileUrl, encryptionKey) + setIsLoading(false) + + navigate( + `${appPrivateRoutes.sign}?file=${encodeURIComponent(fileUrl)}&key=${encodeURIComponent(encryptionKey)}` + ) + } else { + handleOffline(blob, encryptionKey) + } + } + + // Handle errors during file upload + const handleUploadError = (err: any) => { + console.log('Error in upload:>> ', err) + setIsLoading(false) + toast.error(err.message || 'Error occurred in uploading zip file') + return null + } + + // Upload the file to the storage and send DMs to signers/viewers + const uploadFile = async (blob: Blob): Promise => { + 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(handleUploadError) + + return fileUrl + } + + // Send DMs to signers and viewers with the file URL and encryption key + const sendDMs = async (fileUrl: string, encryptionKey: string) => { + setLoadingSpinnerDesc('Sending DM to signers/viewers') + + const signers = users.filter((user) => user.role === UserRole.signer) + const viewers = users.filter((user) => user.role === UserRole.viewer) + + if (signers.length > 0) { + await sendDM( + fileUrl, + encryptionKey, + signers[0].pubkey, + nostrController, + true, + setAuthUrl + ) + } else { + for (const viewer of viewers) { + await sendDM( + fileUrl, + encryptionKey, + viewer.pubkey, + nostrController, + false, + setAuthUrl + ) + } + } + } + + // Manage offline scenarios for signing or viewing the file + const handleOffline = (blob: Blob, encryptionKey: string) => { + const signers = users.filter((user) => user.role === UserRole.signer) + + if (signers[0] && signers[0].pubkey === usersPubkey) { + // Create a File object with the Blob data for offline signing + const file = new File([blob], `compressed.sigit`, { + type: 'application/sigit' + }) + navigate(appPrivateRoutes.sign, { state: { file, encryptionKey } }) + } else { + // Save the file and show encryption key for offline viewing + saveAs(blob, 'request.sigit') + setTextToCopy(encryptionKey) + setOpenCopyModel(true) + } + } + + const handleCreate = async () => { + if (!validateInputs()) return + + setIsLoading(true) + setLoadingSpinnerDesc('Generating hashes for files') + + const fileHashes = await generateFileHashes() + if (!fileHashes) return + + const createZipResponse = await createZipFile(fileHashes) + if (!createZipResponse) return + + const { zip, createSignature } = createZipResponse + + const metaHash = await addMetaToZip(zip, createSignature) + if (!metaHash) return + + setLoadingSpinnerDesc('Generating zip file') + + const arraybuffer = await generateZipFile(zip) if (!arraybuffer) return const encryptionKey = await generateEncryptionKey() setLoadingSpinnerDesc('Encrypting zip file') - const encryptedArrayBuffer = await encryptArrayBuffer( + const encryptedArrayBuffer = await encryptZipFile( arraybuffer, encryptionKey - ).finally(() => setIsLoading(false)) - + ) const blob = new Blob([encryptedArrayBuffer]) - if (await isOnline()) { - 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 - - 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 - await sendDM( - fileUrl, - encryptionKey, - viewer.pubkey, - nostrController, - false, - setAuthUrl - ) - } - } - setIsLoading(false) - - navigate( - `${appPrivateRoutes.sign}?file=${encodeURIComponent( - fileUrl - )}&key=${encodeURIComponent(encryptionKey)}` - ) - } else { - if (signers[0] && signers[0].pubkey === usersPubkey) { - // Create a File object with the Blob data - const file = new File([blob], `compressed.sigit`, { - type: 'application/sigit' - }) - - navigate(appPrivateRoutes.sign, { state: { file, encryptionKey } }) - } else { - saveAs(blob, 'request.sigit') - setTextToCopy(encryptionKey) - setOpenCopyModel(true) - } - } + return await handleFileUpload(blob, encryptionKey) } if (authUrl) { From b145624f4cf34c86cbde5c9d0aff76c00547bf27 Mon Sep 17 00:00:00 2001 From: SwiftHawk Date: Tue, 11 Jun 2024 16:49:10 +0500 Subject: [PATCH 03/17] chore(refactor): break handle sign function into smaller chunks --- src/pages/sign/index.tsx | 298 +++++++++++++++++++++++---------------- 1 file changed, 179 insertions(+), 119 deletions(-) diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx index 1833c77..7ffc1ec 100644 --- a/src/pages/sign/index.tsx +++ b/src/pages/sign/index.tsx @@ -34,6 +34,7 @@ import { CreateSignatureEventContent, Meta, ProfileMetadata, + SignedEvent, SignedEventContent, User, UserRole @@ -366,26 +367,15 @@ export const SignPage = () => { setIsLoading(true) setLoadingSpinnerDesc('parsing hashes.json file') - const hashesFileContent = await readContentOfZipEntry( - zip, - 'hashes.json', - 'string' - ) + const hashesFileContent = await readHashesFile() + if (!hashesFileContent) return if (!hashesFileContent) { setIsLoading(false) return } - let hashes = await parseJson(hashesFileContent).catch((err) => { - console.log('err in parsing the content of hashes.json :>> ', err) - toast.error( - err.message || 'error occurred in parsing the content of hashes.json' - ) - setIsLoading(false) - return null - }) - + const hashes = await parseHashes(hashesFileContent) if (!hashes) return setLoadingSpinnerDesc('Generating hashes for files') @@ -395,51 +385,21 @@ export const SignPage = () => { const prevSig = getPrevSignersSig(hexToNpub(usersPubkey!)) if (!prevSig) return - const signedEvent = await signEventForMetaFile( - JSON.stringify({ - prevSig - }), - nostrController, - setIsLoading - ) - + const signedEvent = await signEventForMeta(prevSig) if (!signedEvent) return - const metaCopy = _.cloneDeep(meta) + const updatedMeta = updateMetaSignatures(meta, signedEvent) - metaCopy.docSignatures = { - ...metaCopy.docSignatures, - [hexToNpub(signedEvent.pubkey)]: JSON.stringify(signedEvent, null, 2) - } - - const stringifiedMeta = JSON.stringify(metaCopy, null, 2) + const stringifiedMeta = JSON.stringify(updatedMeta, null, 2) zip.file('meta.json', stringifiedMeta) const metaHash = await getHash(stringifiedMeta) if (!metaHash) return - hashes = { - ...hashes, - [usersPubkey!]: metaHash - } - - zip.file('hashes.json', JSON.stringify(hashes, null, 2)) - - 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 - }) + const updatedHashes = updateHashes(hashes, metaHash) + zip.file('hashes.json', JSON.stringify(updatedHashes, null, 2)) + const arrayBuffer = await generateZipArrayBuffer(zip) if (!arrayBuffer) return const key = await generateEncryptionKey() @@ -450,80 +410,180 @@ export const SignPage = () => { const blob = new Blob([encryptedArrayBuffer]) if (await isOnline()) { - 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 - - // 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 (submittedBy) { - userSet.add(hexToNpub(submittedBy)) - } - - signers.forEach((signer) => { - userSet.add(signer) - }) - - viewers.forEach((viewer) => { - userSet.add(viewer) - }) - - const users = Array.from(userSet) - - 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(nextSigner)!, - nostrController, - true, - setAuthUrl - ) - } - - setIsLoading(false) - - // update search params with updated file url and encryption key - setSearchParams({ - file: fileUrl, - key: key - }) + await handleOnlineFlow(blob, key) } else { handleDecryptedArrayBuffer(arrayBuffer).finally(() => setIsLoading(false)) } } + // Read the content of the hashes.json file + const readHashesFile = async (): Promise => { + return await readContentOfZipEntry(zip!, 'hashes.json', 'string').catch( + (err) => { + console.log('Error reading hashes.json file:', err) + setIsLoading(false) + return null + } + ) + } + + // Parse the JSON content of the hashes file + const parseHashes = async ( + hashesFileContent: string + ): Promise | null> => { + return await parseJson>(hashesFileContent).catch( + (err) => { + console.log('Error parsing hashes.json content:', err) + toast.error(err.message || 'Error parsing hashes.json content') + setIsLoading(false) + return null + } + ) + } + + // Sign the event for the meta file + const signEventForMeta = async (prevSig: string) => { + return await signEventForMetaFile( + JSON.stringify({ prevSig }), + nostrController, + setIsLoading + ) + } + + // Update the meta signatures + const updateMetaSignatures = (meta: Meta, signedEvent: SignedEvent): Meta => { + const metaCopy = _.cloneDeep(meta) + metaCopy.docSignatures = { + ...metaCopy.docSignatures, + [hexToNpub(signedEvent.pubkey)]: JSON.stringify(signedEvent, null, 2) + } + return metaCopy + } + + // Update the hashes with the new meta hash + const updateHashes = ( + hashes: Record, + metaHash: string + ): Record => { + return { + ...hashes, + [usersPubkey!]: metaHash + } + } + + // Generate the zip array buffer + const generateZipArrayBuffer = async ( + zip: JSZip + ): Promise => { + return await zip + .generateAsync({ + type: 'arraybuffer', + compression: 'DEFLATE', + compressionOptions: { + level: 6 + } + }) + .catch((err) => { + console.log('Error generating zip file:', err) + setIsLoading(false) + toast.error(err.message || 'Error generating zip file') + return null + }) + } + + // Handle the online flow: upload file and send DMs + const handleOnlineFlow = async (blob: Blob, key: string) => { + const fileUrl = await uploadZipFile(blob) + if (!fileUrl) return + + const isLastSigner = checkIsLastSigner(signers) + + if (isLastSigner) { + await sendDMToAllUsers(fileUrl, key) + } else { + await sendDMToNextSigner(fileUrl, key) + } + + setIsLoading(false) + + // Update search params with updated file URL and encryption key + setSearchParams({ + file: fileUrl, + key: key + }) + } + + // Upload the zip file to file storage + const uploadZipFile = async (blob: Blob): Promise => { + 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('Error uploading file:', err) + setIsLoading(false) + toast.error(err.message || 'Error uploading file') + return null + }) + + return fileUrl + } + + // Check if the current user is the last signer + const checkIsLastSigner = (signers: string[]): boolean => { + const usersNpub = hexToNpub(usersPubkey!) + const lastSignerIndex = signers.length - 1 + const signerIndex = signers.indexOf(usersNpub) + return signerIndex === lastSignerIndex + } + + // Send DM to all users (signers and viewers) + const sendDMToAllUsers = async (fileUrl: string, key: string) => { + const userSet = new Set<`npub1${string}`>() + + if (submittedBy) { + userSet.add(hexToNpub(submittedBy)) + } + + signers.forEach((signer) => { + userSet.add(signer) + }) + + viewers.forEach((viewer) => { + userSet.add(viewer) + }) + + const users = Array.from(userSet) + + for (const user of users) { + await sendDM( + fileUrl, + key, + npubToHex(user)!, + nostrController, + false, + setAuthUrl + ) + } + } + + // Send DM to the next signer + const sendDMToNextSigner = async (fileUrl: string, key: string) => { + const usersNpub = hexToNpub(usersPubkey!) + const signerIndex = signers.indexOf(usersNpub) + const nextSigner = signers[signerIndex + 1] + await sendDM( + fileUrl, + key, + npubToHex(nextSigner)!, + nostrController, + true, + setAuthUrl + ) + } + const handleExport = async () => { if (!meta || !zip || !usersPubkey) return From ded8304c669c257b5b782829ffb37be101af9cdd Mon Sep 17 00:00:00 2001 From: SwiftHawk Date: Wed, 12 Jun 2024 15:02:26 +0500 Subject: [PATCH 04/17] fix: sigit's wrapper zip should contain keys.json file --- src/controllers/NostrController.ts | 48 ++++++ src/pages/create/index.tsx | 119 +++++++------ src/pages/sign/index.tsx | 258 +++++++++++++++++++++-------- src/utils/misc.ts | 69 ++++++-- 4 files changed, 364 insertions(+), 130 deletions(-) diff --git a/src/controllers/NostrController.ts b/src/controllers/NostrController.ts index 049f99a..920d894 100644 --- a/src/controllers/NostrController.ts +++ b/src/controllers/NostrController.ts @@ -378,6 +378,54 @@ export class NostrController extends EventEmitter { throw new Error('Login method is undefined') } + nip04Decrypt = async (sender: string, content: string) => { + const loginMethod = (store.getState().auth as AuthState).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') + } + /** * Function will capture the public key from the nostr extension or if no extension present * function wil capture the public key from the local storage diff --git a/src/pages/create/index.tsx b/src/pages/create/index.tsx index 98525a4..c445936 100644 --- a/src/pages/create/index.tsx +++ b/src/pages/create/index.tsx @@ -33,6 +33,7 @@ import { Meta, ProfileMetadata, User, UserRole } from '../../types' import { encryptArrayBuffer, generateEncryptionKey, + generateKeysFile, getHash, hexToNpub, isOnline, @@ -49,15 +50,12 @@ 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' import { Event, kinds } from 'nostr-tools' 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() @@ -374,26 +372,68 @@ export const CreatePage = () => { encryptionKey: string ): Promise => { setLoadingSpinnerDesc('Encrypting zip file') - return encryptArrayBuffer(arraybuffer, encryptionKey).finally(() => - setIsLoading(false) + return encryptArrayBuffer(arraybuffer, encryptionKey) + } + + // create final zip file + const createFinalZipFile = async ( + encryptedArrayBuffer: ArrayBuffer, + encryptionKey: string + ): Promise => { + // Get the current timestamp in seconds + const unixNow = Math.floor(Date.now() / 1000) + const blob = new Blob([encryptedArrayBuffer]) + // Create a File object with the Blob data + const file = new File([blob], `compressed.sigit`, { + type: 'application/sigit' + }) + + const firstSigner = users.filter((user) => user.role === UserRole.signer)[0] + + const keysFileContent = await generateKeysFile( + [firstSigner.pubkey], + encryptionKey ) + if (!keysFileContent) return null + + const zip = new JSZip() + zip.file(`compressed.sigit`, file) + zip.file('keys.json', keysFileContent) + + const arraybuffer = await zip + .generateAsync({ + type: 'arraybuffer', + compression: 'DEFLATE', + compressionOptions: { level: 6 } + }) + .catch(handleZipError) + + if (!arraybuffer) return null + + const finalZipFile = new File( + [new Blob([arraybuffer])], + `${unixNow}.sigit.zip`, + { + type: 'application/zip' + } + ) + + return finalZipFile } // Handle file upload and further actions based on online/offline status - const handleFileUpload = async (blob: Blob, encryptionKey: string) => { + const handleFileUpload = async (file: File, arrayBuffer: ArrayBuffer) => { if (await isOnline()) { - const fileUrl = await uploadFile(blob) + const fileUrl = await uploadFile(file) if (!fileUrl) return - await sendDMs(fileUrl, encryptionKey) + await sendDMs(fileUrl) setIsLoading(false) - navigate( - `${appPrivateRoutes.sign}?file=${encodeURIComponent(fileUrl)}&key=${encodeURIComponent(encryptionKey)}` - ) + navigate(appPrivateRoutes.sign, { state: { arrayBuffer } }) } else { - handleOffline(blob, encryptionKey) + handleOffline(file, arrayBuffer) } } @@ -406,11 +446,11 @@ export const CreatePage = () => { } // Upload the file to the storage and send DMs to signers/viewers - const uploadFile = async (blob: Blob): Promise => { + const uploadFile = async (file: File): Promise => { setIsLoading(true) setLoadingSpinnerDesc('Uploading zip file to file storage.') - const fileUrl = await uploadToFileStorage(blob, nostrController) + const fileUrl = await uploadToFileStorage(file, nostrController) .then((url) => { toast.success('zip file uploaded to file storage') return url @@ -420,8 +460,8 @@ export const CreatePage = () => { return fileUrl } - // Send DMs to signers and viewers with the file URL and encryption key - const sendDMs = async (fileUrl: string, encryptionKey: string) => { + // Send DMs to signers and viewers with the file URL + const sendDMs = async (fileUrl: string) => { setLoadingSpinnerDesc('Sending DM to signers/viewers') const signers = users.filter((user) => user.role === UserRole.signer) @@ -430,7 +470,6 @@ export const CreatePage = () => { if (signers.length > 0) { await sendDM( fileUrl, - encryptionKey, signers[0].pubkey, nostrController, true, @@ -438,34 +477,15 @@ export const CreatePage = () => { ) } else { for (const viewer of viewers) { - await sendDM( - fileUrl, - encryptionKey, - viewer.pubkey, - nostrController, - false, - setAuthUrl - ) + await sendDM(fileUrl, viewer.pubkey, nostrController, false, setAuthUrl) } } } // Manage offline scenarios for signing or viewing the file - const handleOffline = (blob: Blob, encryptionKey: string) => { - const signers = users.filter((user) => user.role === UserRole.signer) - - if (signers[0] && signers[0].pubkey === usersPubkey) { - // Create a File object with the Blob data for offline signing - const file = new File([blob], `compressed.sigit`, { - type: 'application/sigit' - }) - navigate(appPrivateRoutes.sign, { state: { file, encryptionKey } }) - } else { - // Save the file and show encryption key for offline viewing - saveAs(blob, 'request.sigit') - setTextToCopy(encryptionKey) - setOpenCopyModel(true) - } + const handleOffline = (file: File, arrayBuffer: ArrayBuffer) => { + saveAs(file, 'request.sigit.zip') + navigate(appPrivateRoutes.sign, { state: { arrayBuffer } }) } const handleCreate = async () => { @@ -497,9 +517,15 @@ export const CreatePage = () => { arraybuffer, encryptionKey ) - const blob = new Blob([encryptedArrayBuffer]) - return await handleFileUpload(blob, encryptionKey) + const finalZipFile = await createFinalZipFile( + encryptedArrayBuffer, + encryptionKey + ) + + if (!finalZipFile) return + + return await handleFileUpload(finalZipFile, arraybuffer) } if (authUrl) { @@ -596,15 +622,6 @@ 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 7ffc1ec..715bdcc 100644 --- a/src/pages/sign/index.tsx +++ b/src/pages/sign/index.tsx @@ -10,7 +10,6 @@ import { TableCell, TableHead, TableRow, - TextField, Tooltip, Typography, useTheme @@ -52,7 +51,8 @@ import { shorten, signEventForMetaFile, uploadToFileStorage, - isOnline + isOnline, + generateKeysFile } from '../../utils' import styles from './style.module.scss' import { @@ -71,14 +71,13 @@ enum SignedStatus { export const SignPage = () => { const navigate = useNavigate() const location = useLocation() - const { file, encryptionKey: encKey } = location.state || {} + const { arrayBuffer: decryptedArrayBuffer } = location.state || {} - const [searchParams, setSearchParams] = useSearchParams() + const [searchParams] = useSearchParams() const [displayInput, setDisplayInput] = useState(false) const [selectedFile, setSelectedFile] = useState(null) - const [encryptionKey, setEncryptionKey] = useState('') const [zip, setZip] = useState() @@ -194,9 +193,8 @@ export const SignPage = () => { useEffect(() => { const fileUrl = searchParams.get('file') - const key = searchParams.get('key') - if (fileUrl && key) { + if (fileUrl) { setIsLoading(true) setLoadingSpinnerDesc('Fetching file from file server') @@ -208,7 +206,7 @@ export const SignPage = () => { const fileName = fileUrl.split('/').pop() const file = new File([res.data], fileName!) - decrypt(file, decodeURIComponent(key)).then((arrayBuffer) => { + decrypt(file).then((arrayBuffer) => { if (arrayBuffer) handleDecryptedArrayBuffer(arrayBuffer) }) }) @@ -221,40 +219,109 @@ export const SignPage = () => { .finally(() => { setIsLoading(false) }) - } else if (file && encKey) { - decrypt(file, decodeURIComponent(encKey)) - .then((arrayBuffer) => { - if (arrayBuffer) handleDecryptedArrayBuffer(arrayBuffer) - }) - .catch((err) => { - console.error(`error occurred in decryption`, err) - toast.error(err.message || `error occurred in decryption`) - }) - .finally(() => { - setIsLoading(false) - }) + } else if (decryptedArrayBuffer) { + handleDecryptedArrayBuffer(decryptedArrayBuffer).finally(() => + setIsLoading(false) + ) } else { setIsLoading(false) setDisplayInput(true) } - }, [searchParams, file, encKey]) + }, [searchParams, decryptedArrayBuffer]) - const decrypt = async (file: File, key: string) => { + const parseKeysJson = async (zip: JSZip) => { + const keysFileContent = await readContentOfZipEntry( + zip, + 'keys.json', + 'string' + ) + + if (!keysFileContent) return null + + const parsedJSON = await parseJson<{ sender: string; keys: string[] }>( + keysFileContent + ).catch((err) => { + console.log(`Error parsing content of keys.json:`, err) + toast.error(err.message || `Error parsing content of keys.json`) + return null + }) + + return parsedJSON + } + + const decrypt = async (file: File) => { setLoadingSpinnerDesc('Decrypting file') - const encryptedArrayBuffer = await file.arrayBuffer() + const zip = await JSZip.loadAsync(file).catch((err) => { + console.log('err in loading zip file :>> ', err) + toast.error(err.message || 'An error occurred in loading zip file.') + return null + }) + if (!zip) return - const arrayBuffer = await decryptArrayBuffer(encryptedArrayBuffer, key) - .catch((err) => { - console.log('err in decryption:>> ', err) - toast.error(err.message || 'An error occurred in decrypting file.') - return null - }) - .finally(() => { - setIsLoading(false) + const parsedKeysJson = await parseKeysJson(zip) + if (!parsedKeysJson) return + + const encryptedArrayBuffer = await readContentOfZipEntry( + zip, + 'compressed.sigit', + 'arraybuffer' + ) + + if (!encryptedArrayBuffer) return + + const { keys, sender } = parsedKeysJson + + for (const key of keys) { + // Set up event listener for authentication event + nostrController.on('nsecbunker-auth', (url) => { + setAuthUrl(url) }) - return arrayBuffer + // Set up timeout promise to handle encryption timeout + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => { + reject(new Error('Timeout occurred')) + }, 60000) // Timeout duration = 60 seconds + }) + + // decrypt the encryptionKey, with timeout + const encryptionKey = await Promise.race([ + nostrController.nip04Decrypt(sender, key), + timeoutPromise + ]) + .then((res) => { + return res + }) + .catch((err) => { + console.log('err :>> ', err) + return null + }) + .finally(() => { + setAuthUrl(undefined) // Clear authentication URL + }) + + console.log('encryptionKey :>> ', encryptionKey) + + // Return if encryption failed + if (!encryptionKey) continue + + const arrayBuffer = await decryptArrayBuffer( + encryptedArrayBuffer, + encryptionKey + ) + .catch((err) => { + console.log('err in decryption:>> ', err) + return null + }) + .finally(() => { + setIsLoading(false) + }) + + if (arrayBuffer) return arrayBuffer + } + + return null } const handleDecryptedArrayBuffer = async (arrayBuffer: ArrayBuffer) => { @@ -348,13 +415,10 @@ export const SignPage = () => { } const handleDecrypt = async () => { - if (!selectedFile || !encryptionKey) return + if (!selectedFile) return setIsLoading(true) - const arrayBuffer = await decrypt( - selectedFile, - decodeURIComponent(encryptionKey) - ) + const arrayBuffer = await decrypt(selectedFile) if (!arrayBuffer) return @@ -407,13 +471,15 @@ export const SignPage = () => { setLoadingSpinnerDesc('Encrypting zip file') const encryptedArrayBuffer = await encryptArrayBuffer(arrayBuffer, key) - const blob = new Blob([encryptedArrayBuffer]) + const finalZipFile = await createFinalZipFile(encryptedArrayBuffer, key) + + if (!finalZipFile) return if (await isOnline()) { - await handleOnlineFlow(blob, key) - } else { - handleDecryptedArrayBuffer(arrayBuffer).finally(() => setIsLoading(false)) + await handleOnlineFlow(finalZipFile) } + + handleDecryptedArrayBuffer(arrayBuffer).finally(() => setIsLoading(false)) } // Read the content of the hashes.json file @@ -491,32 +557,101 @@ export const SignPage = () => { }) } + // create final zip file + const createFinalZipFile = async ( + encryptedArrayBuffer: ArrayBuffer, + encryptionKey: string + ): Promise => { + // Get the current timestamp in seconds + const unixNow = Math.floor(Date.now() / 1000) + const blob = new Blob([encryptedArrayBuffer]) + // Create a File object with the Blob data + const file = new File([blob], `compressed.sigit`, { + type: 'application/sigit' + }) + + const isLastSigner = checkIsLastSigner(signers) + + const userSet = new Set() + + if (isLastSigner) { + if (submittedBy) { + userSet.add(submittedBy) + } + + signers.forEach((signer) => { + userSet.add(npubToHex(signer)!) + }) + + viewers.forEach((viewer) => { + userSet.add(npubToHex(viewer)!) + }) + } else { + const usersNpub = hexToNpub(usersPubkey!) + const signerIndex = signers.indexOf(usersNpub) + const nextSigner = signers[signerIndex + 1] + userSet.add(npubToHex(nextSigner)!) + } + + const keysFileContent = await generateKeysFile( + Array.from(userSet), + encryptionKey + ) + if (!keysFileContent) return null + + const zip = new JSZip() + zip.file(`compressed.sigit`, file) + zip.file('keys.json', keysFileContent) + + const arraybuffer = await zip + .generateAsync({ + type: 'arraybuffer', + compression: 'DEFLATE', + compressionOptions: { level: 6 } + }) + .catch(handleZipError) + + if (!arraybuffer) return null + + const finalZipFile = new File( + [new Blob([arraybuffer])], + `${unixNow}.sigit.zip`, + { + type: 'application/zip' + } + ) + + return finalZipFile + } + + // Handle errors during zip file generation + const handleZipError = (err: any) => { + console.log('Error in zip:>> ', err) + setIsLoading(false) + toast.error(err.message || 'Error occurred in generating zip file') + return null + } + // Handle the online flow: upload file and send DMs - const handleOnlineFlow = async (blob: Blob, key: string) => { - const fileUrl = await uploadZipFile(blob) + const handleOnlineFlow = async (file: File) => { + const fileUrl = await uploadZipFile(file) if (!fileUrl) return const isLastSigner = checkIsLastSigner(signers) if (isLastSigner) { - await sendDMToAllUsers(fileUrl, key) + await sendDMToAllUsers(fileUrl) } else { - await sendDMToNextSigner(fileUrl, key) + await sendDMToNextSigner(fileUrl) } setIsLoading(false) - - // Update search params with updated file URL and encryption key - setSearchParams({ - file: fileUrl, - key: key - }) } // Upload the zip file to file storage - const uploadZipFile = async (blob: Blob): Promise => { + const uploadZipFile = async (file: File): Promise => { setLoadingSpinnerDesc('Uploading zip file to file storage.') - const fileUrl = await uploadToFileStorage(blob, nostrController) + const fileUrl = await uploadToFileStorage(file, nostrController) .then((url) => { toast.success('Zip file uploaded to file storage') return url @@ -540,7 +675,7 @@ export const SignPage = () => { } // Send DM to all users (signers and viewers) - const sendDMToAllUsers = async (fileUrl: string, key: string) => { + const sendDMToAllUsers = async (fileUrl: string) => { const userSet = new Set<`npub1${string}`>() if (submittedBy) { @@ -560,7 +695,6 @@ export const SignPage = () => { for (const user of users) { await sendDM( fileUrl, - key, npubToHex(user)!, nostrController, false, @@ -570,13 +704,12 @@ export const SignPage = () => { } // Send DM to the next signer - const sendDMToNextSigner = async (fileUrl: string, key: string) => { + const sendDMToNextSigner = async (fileUrl: string) => { const usersNpub = hexToNpub(usersPubkey!) const signerIndex = signers.indexOf(usersNpub) const nextSigner = signers[signerIndex + 1] await sendDM( fileUrl, - key, npubToHex(nextSigner)!, nostrController, true, @@ -771,18 +904,9 @@ export const SignPage = () => { value={selectedFile} onChange={(value) => setSelectedFile(value)} /> - - {selectedFile && ( - setEncryptionKey(e.target.value)} - /> - )} - {selectedFile && encryptionKey && ( + {selectedFile && ( - - )} + {/* todo: In offline mode export sigit is not visible after last signer has signed*/} + {isSignerOrCreator && ( + + + + )} )} diff --git a/src/utils/misc.ts b/src/utils/misc.ts index a0e1d30..fc53dff 100644 --- a/src/utils/misc.ts +++ b/src/utils/misc.ts @@ -56,6 +56,7 @@ export const uploadToFileStorage = async ( /** * Sends a Direct Message (DM) to a recipient, encrypting the content and handling authentication. * @param fileUrl The URL of the encrypted zip file to be included in the DM. + * @param encryptionKey The encryption key used to decrypt the zip file to be included in the DM. * @param pubkey The public key of the recipient. * @param nostrController The NostrController instance for handling authentication and encryption. * @param isSigner Boolean indicating whether the recipient is a signer or viewer. @@ -63,6 +64,7 @@ export const uploadToFileStorage = async ( */ export const sendDM = async ( fileUrl: string, + encryptionKey: string, pubkey: string, nostrController: NostrController, isSigner: boolean, @@ -75,7 +77,9 @@ export const sendDM = async ( const decryptionUrl = `${window.location.origin}/#${ appPrivateRoutes.sign - }?file=${encodeURIComponent(fileUrl)}` + }?file=${encodeURIComponent(fileUrl)}&key=${encodeURIComponent( + encryptionKey + )}` const content = `${initialLine}\n\n${decryptionUrl}` From 92b62a3cbed8461cbbb25cb841bec9063f11e90d Mon Sep 17 00:00:00 2001 From: SwiftHawk Date: Thu, 13 Jun 2024 11:47:28 +0500 Subject: [PATCH 08/17] feat: navigate to different pages based on uploaded file --- src/pages/create/index.tsx | 11 ++++++- src/pages/home/index.tsx | 60 ++++++++++++++++++++++++++++++++++++-- src/pages/sign/index.tsx | 20 +++++++++++-- src/pages/verify/index.tsx | 13 +++++++-- 4 files changed, 96 insertions(+), 8 deletions(-) diff --git a/src/pages/create/index.tsx b/src/pages/create/index.tsx index 24538fb..84ad3bf 100644 --- a/src/pages/create/index.tsx +++ b/src/pages/create/index.tsx @@ -22,7 +22,7 @@ import JSZip from 'jszip' import { MuiFileInput } from 'mui-file-input' import { useEffect, useRef, useState } from 'react' import { useSelector } from 'react-redux' -import { useNavigate } from 'react-router-dom' +import { useLocation, useNavigate } from 'react-router-dom' import { toast } from 'react-toastify' import { LoadingSpinner } from '../../components/LoadingSpinner' import { UserComponent } from '../../components/username' @@ -54,6 +54,9 @@ import { Event, kinds } from 'nostr-tools' export const CreatePage = () => { const navigate = useNavigate() + const location = useLocation() + const { uploadedFile } = location.state || {} + const [isLoading, setIsLoading] = useState(false) const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('') @@ -72,6 +75,12 @@ export const CreatePage = () => { const nostrController = NostrController.getInstance() + useEffect(() => { + if (uploadedFile) { + setSelectedFiles([uploadedFile]) + } + }, [uploadedFile]) + useEffect(() => { if (usersPubkey) { setUsers((prev) => { diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 9cf4508..181b67d 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -7,11 +7,61 @@ import { } from '@mui/icons-material' import { Box, Button, Tooltip, Typography } from '@mui/material' import { useNavigate } from 'react-router-dom' -import { appPrivateRoutes } from '../../routes' +import { appPrivateRoutes, appPublicRoutes } from '../../routes' import styles from './style.module.scss' +import { useRef } from 'react' +import JSZip from 'jszip' +import { toast } from 'react-toastify' export const HomePage = () => { const navigate = useNavigate() + const fileInputRef = useRef(null) + + const handleUploadClick = () => { + if (fileInputRef.current) { + fileInputRef.current.click() + } + } + + const handleFileChange = async ( + event: React.ChangeEvent + ) => { + const file = event.target.files?.[0] + if (file) { + // Check if the file extension is .sigit.zip + const fileName = file.name + const fileExtension = fileName.slice(-10) // ".sigit.zip" has 10 characters + if (fileExtension === '.sigit.zip') { + const zip = await JSZip.loadAsync(file).catch((err) => { + console.log('err in loading zip file :>> ', err) + toast.error(err.message || 'An error occurred in loading zip file.') + return null + }) + + if (!zip) return + + // navigate to sign page if zip contains keys.json + if ('keys.json' in zip.files) { + return navigate(appPrivateRoutes.sign, { + state: { uploadedZip: file } + }) + } + + // navigate to verify page if zip contains meta.json + if ('meta.json' in zip.files) { + return navigate(appPublicRoutes.verify, { + state: { uploadedZip: file } + }) + } + + toast.error('Invalid zip file') + return + } + + // navigate to create page + navigate(appPrivateRoutes.create, { state: { uploadedFile: file } }) + } + } return ( @@ -29,10 +79,16 @@ export const HomePage = () => { } }} > + diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx index 2a8ca9a..b49d11e 100644 --- a/src/pages/sign/index.tsx +++ b/src/pages/sign/index.tsx @@ -40,7 +40,8 @@ enum SignedStatus { export const SignPage = () => { const navigate = useNavigate() const location = useLocation() - const { arrayBuffer: decryptedArrayBuffer } = location.state || {} + const { arrayBuffer: decryptedArrayBuffer, uploadedZip } = + location.state || {} const [searchParams, setSearchParams] = useSearchParams() @@ -200,11 +201,23 @@ export const SignPage = () => { handleDecryptedArrayBuffer(decryptedArrayBuffer).finally(() => setIsLoading(false) ) + } else if (uploadedZip) { + decrypt(uploadedZip) + .then((arrayBuffer) => { + if (arrayBuffer) handleDecryptedArrayBuffer(arrayBuffer) + }) + .catch((err) => { + console.error(`error occurred in decryption`, err) + toast.error(err.message || `error occurred in decryption`) + }) + .finally(() => { + setIsLoading(false) + }) } else { setIsLoading(false) setDisplayInput(true) } - }, [searchParams, decryptedArrayBuffer]) + }, [searchParams, decryptedArrayBuffer, uploadedZip]) const parseKeysJson = async (zip: JSZip) => { const keysFileContent = await readContentOfZipEntry( @@ -768,7 +781,8 @@ export const SignPage = () => { if (!arrayBuffer) return const blob = new Blob([arrayBuffer]) - saveAs(blob, 'exported.zip') + const unixNow = Math.floor(Date.now() / 1000) + saveAs(blob, `exported-${unixNow}.sigit.zip`) setIsLoading(false) diff --git a/src/pages/verify/index.tsx b/src/pages/verify/index.tsx index a8e0d1f..53aea4a 100644 --- a/src/pages/verify/index.tsx +++ b/src/pages/verify/index.tsx @@ -32,14 +32,17 @@ import { } from '../../utils' import styles from './style.module.scss' import { Cancel, CheckCircle } from '@mui/icons-material' +import { useLocation } from 'react-router-dom' export const VerifyPage = () => { const theme = useTheme() - const textColor = theme.palette.getContrastText( theme.palette.background.paper ) + const location = useLocation() + const { uploadedZip } = location.state || {} + const [isLoading, setIsLoading] = useState(false) const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('') @@ -62,6 +65,12 @@ export const VerifyPage = () => { {} ) + useEffect(() => { + if (uploadedZip) { + setSelectedFile(uploadedZip) + } + }, [uploadedZip]) + useEffect(() => { if (zip) { const generateCurrentFileHashes = async () => { @@ -364,7 +373,7 @@ export const VerifyPage = () => { onChange={(value) => setSelectedFile(value)} InputProps={{ inputProps: { - accept: '.zip' + accept: '.sigit.zip' } }} /> From 29654a9b910f54dd25acade07ba1eb01ed8cf563 Mon Sep 17 00:00:00 2001 From: SwiftHawk Date: Thu, 13 Jun 2024 14:58:54 +0500 Subject: [PATCH 09/17] chore: add comments --- src/controllers/NostrController.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/controllers/NostrController.ts b/src/controllers/NostrController.ts index 920d894..ecc9cc9 100644 --- a/src/controllers/NostrController.ts +++ b/src/controllers/NostrController.ts @@ -378,6 +378,13 @@ export class NostrController extends EventEmitter { throw new Error('Login method is undefined') } + /** + * Decrypts a given content based on the current login method. + * + * @param sender - The sender's public key. + * @param content - The encrypted content to decrypt. + * @returns A promise that resolves to the decrypted content. + */ nip04Decrypt = async (sender: string, content: string) => { const loginMethod = (store.getState().auth as AuthState).loginMethod From bb37a27321cd9b26537b8fbbe2b39902b6c85fc4 Mon Sep 17 00:00:00 2001 From: SIGit Date: Tue, 18 Jun 2024 15:00:21 +0100 Subject: [PATCH 10/17] feat: nostr.json --- public/.well-known/nostr.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 public/.well-known/nostr.json diff --git a/public/.well-known/nostr.json b/public/.well-known/nostr.json new file mode 100644 index 0000000..6dd4dd9 --- /dev/null +++ b/public/.well-known/nostr.json @@ -0,0 +1,15 @@ +{ + "names": { + "_": "6bf1024c6336093b632db2da21b65a44c7d82830454fb4d75634ba281e161c90" + }, + "relays": { + "6bf1024c6336093b632db2da21b65a44c7d82830454fb4d75634ba281e161c90": [ + "wss://brb.io", + "wss://nostr.v0l.io", + "wss://nostr.coinos.io", + "wss://rsslay.nostr.net", + "wss://relay.current.fyi", + "wss://nos.io" + ] + } +} \ No newline at end of file From 970c5f5e8bfc0129283fe14c7e6fa3c9a8a35ea4 Mon Sep 17 00:00:00 2001 From: SIGit Date: Tue, 18 Jun 2024 15:21:17 +0100 Subject: [PATCH 11/17] fix: include hidden folders in surfer upload --- .gitea/workflows/release-production.yaml | 2 +- .gitea/workflows/release-staging.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/release-production.yaml b/.gitea/workflows/release-production.yaml index 334eb11..a7d470c 100644 --- a/.gitea/workflows/release-production.yaml +++ b/.gitea/workflows/release-production.yaml @@ -29,4 +29,4 @@ jobs: - name: Release Build run: | npm -g install cloudron-surfer - surfer put --token ${{ secrets.CLOUDRON_SURFER_TOKEN }} --server sigit.io dist/* / + surfer put --token ${{ secrets.CLOUDRON_SURFER_TOKEN }} --server sigit.io dist/. / diff --git a/.gitea/workflows/release-staging.yaml b/.gitea/workflows/release-staging.yaml index 0467f63..e3783e9 100644 --- a/.gitea/workflows/release-staging.yaml +++ b/.gitea/workflows/release-staging.yaml @@ -29,4 +29,4 @@ jobs: - name: Release Build run: | npm -g install cloudron-surfer - surfer put --token ${{ secrets.STAGING_CLOUDRON_SURFER_TOKEN }} --server staging.sigit.io dist/* / + surfer put --token ${{ secrets.STAGING_CLOUDRON_SURFER_TOKEN }} --server staging.sigit.io dist/. / From 24916c58068bbe9e5dfc76cbf00c955add6744e4 Mon Sep 17 00:00:00 2001 From: SIGit Date: Tue, 18 Jun 2024 15:23:22 +0100 Subject: [PATCH 12/17] fix: push all files take 2 --- .gitea/workflows/release-production.yaml | 2 +- .gitea/workflows/release-staging.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/release-production.yaml b/.gitea/workflows/release-production.yaml index a7d470c..f1e7184 100644 --- a/.gitea/workflows/release-production.yaml +++ b/.gitea/workflows/release-production.yaml @@ -29,4 +29,4 @@ jobs: - name: Release Build run: | npm -g install cloudron-surfer - surfer put --token ${{ secrets.CLOUDRON_SURFER_TOKEN }} --server sigit.io dist/. / + surfer put --token ${{ secrets.CLOUDRON_SURFER_TOKEN }} --server sigit.io dist/ / diff --git a/.gitea/workflows/release-staging.yaml b/.gitea/workflows/release-staging.yaml index e3783e9..47a99fb 100644 --- a/.gitea/workflows/release-staging.yaml +++ b/.gitea/workflows/release-staging.yaml @@ -29,4 +29,4 @@ jobs: - name: Release Build run: | npm -g install cloudron-surfer - surfer put --token ${{ secrets.STAGING_CLOUDRON_SURFER_TOKEN }} --server staging.sigit.io dist/. / + surfer put --token ${{ secrets.STAGING_CLOUDRON_SURFER_TOKEN }} --server staging.sigit.io dist/ / From 02f250c76eb6d1b7fb410394c88397e37dd527e4 Mon Sep 17 00:00:00 2001 From: SIGit Date: Tue, 18 Jun 2024 15:30:16 +0100 Subject: [PATCH 13/17] fix: take 3 all files --- .gitea/workflows/release-production.yaml | 3 ++- .gitea/workflows/release-staging.yaml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/release-production.yaml b/.gitea/workflows/release-production.yaml index f1e7184..e2dd110 100644 --- a/.gitea/workflows/release-production.yaml +++ b/.gitea/workflows/release-production.yaml @@ -29,4 +29,5 @@ jobs: - name: Release Build run: | npm -g install cloudron-surfer - surfer put --token ${{ secrets.CLOUDRON_SURFER_TOKEN }} --server sigit.io dist/ / + cd dist + surfer put --token ${{ secrets.CLOUDRON_SURFER_TOKEN }} --server sigit.io . / diff --git a/.gitea/workflows/release-staging.yaml b/.gitea/workflows/release-staging.yaml index 47a99fb..c93f053 100644 --- a/.gitea/workflows/release-staging.yaml +++ b/.gitea/workflows/release-staging.yaml @@ -29,4 +29,5 @@ jobs: - name: Release Build run: | npm -g install cloudron-surfer - surfer put --token ${{ secrets.STAGING_CLOUDRON_SURFER_TOKEN }} --server staging.sigit.io dist/ / + cd dist + surfer put --token ${{ secrets.STAGING_CLOUDRON_SURFER_TOKEN }} --server staging.sigit.io . / From abf9c3e4fd7b61d5f8794714484cb8a0c542d6e7 Mon Sep 17 00:00:00 2001 From: SIGit Date: Tue, 18 Jun 2024 15:36:27 +0100 Subject: [PATCH 14/17] fix: take 4 (all files) --- .gitea/workflows/release-production.yaml | 4 ++-- .gitea/workflows/release-staging.yaml | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/release-production.yaml b/.gitea/workflows/release-production.yaml index e2dd110..98c3c31 100644 --- a/.gitea/workflows/release-production.yaml +++ b/.gitea/workflows/release-production.yaml @@ -29,5 +29,5 @@ jobs: - name: Release Build run: | npm -g install cloudron-surfer - cd dist - surfer put --token ${{ secrets.CLOUDRON_SURFER_TOKEN }} --server sigit.io . / + surfer config --token ${{ secrets.CLOUDRON_SURFER_TOKEN }} --server sigit.io + surfer put . diff --git a/.gitea/workflows/release-staging.yaml b/.gitea/workflows/release-staging.yaml index c93f053..3b32404 100644 --- a/.gitea/workflows/release-staging.yaml +++ b/.gitea/workflows/release-staging.yaml @@ -30,4 +30,5 @@ jobs: run: | npm -g install cloudron-surfer cd dist - surfer put --token ${{ secrets.STAGING_CLOUDRON_SURFER_TOKEN }} --server staging.sigit.io . / + surfer config --token ${{ secrets.STAGING_CLOUDRON_SURFER_TOKEN }} --server staging.sigit.io + surfer put . From ea3f61897c7ab8601547e3b61a6bdd833a19ad12 Mon Sep 17 00:00:00 2001 From: SIGit Date: Tue, 18 Jun 2024 15:39:35 +0100 Subject: [PATCH 15/17] fix: take 5 files --- .gitea/workflows/release-production.yaml | 2 +- .gitea/workflows/release-staging.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/release-production.yaml b/.gitea/workflows/release-production.yaml index 98c3c31..a8dda7c 100644 --- a/.gitea/workflows/release-production.yaml +++ b/.gitea/workflows/release-production.yaml @@ -30,4 +30,4 @@ jobs: run: | npm -g install cloudron-surfer surfer config --token ${{ secrets.CLOUDRON_SURFER_TOKEN }} --server sigit.io - surfer put . + surfer put . / diff --git a/.gitea/workflows/release-staging.yaml b/.gitea/workflows/release-staging.yaml index 3b32404..42f2562 100644 --- a/.gitea/workflows/release-staging.yaml +++ b/.gitea/workflows/release-staging.yaml @@ -31,4 +31,4 @@ jobs: npm -g install cloudron-surfer cd dist surfer config --token ${{ secrets.STAGING_CLOUDRON_SURFER_TOKEN }} --server staging.sigit.io - surfer put . + surfer put . / From 400d192fb0441bbe772d44a457f4e96a4d42d11f Mon Sep 17 00:00:00 2001 From: SIGit Date: Wed, 19 Jun 2024 11:53:16 +0100 Subject: [PATCH 16/17] fix: take 6 --- .gitea/workflows/release-production.yaml | 3 ++- .gitea/workflows/release-staging.yaml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/release-production.yaml b/.gitea/workflows/release-production.yaml index a8dda7c..d9529ad 100644 --- a/.gitea/workflows/release-production.yaml +++ b/.gitea/workflows/release-production.yaml @@ -30,4 +30,5 @@ jobs: run: | npm -g install cloudron-surfer surfer config --token ${{ secrets.CLOUDRON_SURFER_TOKEN }} --server sigit.io - surfer put . / + surfer put dist/* / --all -d + surfer put dist/.well-known / --all diff --git a/.gitea/workflows/release-staging.yaml b/.gitea/workflows/release-staging.yaml index 42f2562..0b2434a 100644 --- a/.gitea/workflows/release-staging.yaml +++ b/.gitea/workflows/release-staging.yaml @@ -31,4 +31,5 @@ jobs: npm -g install cloudron-surfer cd dist surfer config --token ${{ secrets.STAGING_CLOUDRON_SURFER_TOKEN }} --server staging.sigit.io - surfer put . / + surfer put dist/* / --all -d + surfer put dist/.well-known / --all From 3f944bdf73103e6a0152c32bc788d363c323f42b Mon Sep 17 00:00:00 2001 From: SIGit Date: Wed, 19 Jun 2024 12:03:25 +0100 Subject: [PATCH 17/17] fix: take 7 --- .gitea/workflows/release-staging.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitea/workflows/release-staging.yaml b/.gitea/workflows/release-staging.yaml index 0b2434a..793c70c 100644 --- a/.gitea/workflows/release-staging.yaml +++ b/.gitea/workflows/release-staging.yaml @@ -29,7 +29,6 @@ jobs: - name: Release Build run: | npm -g install cloudron-surfer - cd dist surfer config --token ${{ secrets.STAGING_CLOUDRON_SURFER_TOKEN }} --server staging.sigit.io surfer put dist/* / --all -d surfer put dist/.well-known / --all