store-sigits and update working flow #116
@ -67,6 +67,7 @@ export const UserComponent = ({ pubkey, name, image }: UserProps) => {
|
|||||||
|
|
||||||
const handleClick = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
const handleClick = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
// navigate to user's profile
|
||||||
navigate(getProfileRoute(pubkey))
|
navigate(getProfileRoute(pubkey))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,95 +304,135 @@ export class NostrController extends EventEmitter {
|
|||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts the given content for the specified receiver using NIP-44 encryption.
|
||||||
|
*
|
||||||
|
* @param receiver The public key of the receiver.
|
||||||
|
* @param content The content to be encrypted.
|
||||||
|
* @returns The encrypted content as a string.
|
||||||
|
* @throws Error if the nostr extension does not support NIP-44, if the private key pair is not found, or if the login method is unsupported.
|
||||||
|
*/
|
||||||
nip44Encrypt = async (receiver: string, content: string) => {
|
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 loginMethod = (store.getState().auth as AuthState).loginMethod
|
||||||
|
|
||||||
|
// Handle encryption when the login method is via an extension.
|
||||||
if (loginMethod === LoginMethods.extension) {
|
if (loginMethod === LoginMethods.extension) {
|
||||||
const nostr = this.getNostrObject()
|
const nostr = this.getNostrObject()
|
||||||
|
|
||||||
|
// Check if the nostr object supports NIP-44 encryption.
|
||||||
if (!nostr.nip44) {
|
if (!nostr.nip44) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Your nostr extension does not support nip44 encryption & decryption`
|
`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)
|
const encrypted = await nostr.nip44.encrypt(receiver, content)
|
||||||
return encrypted as string
|
return encrypted as string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle encryption when the login method is via a private key.
|
||||||
if (loginMethod === LoginMethods.privateKey) {
|
if (loginMethod === LoginMethods.privateKey) {
|
||||||
const keys = (store.getState().auth as AuthState).keyPair
|
const keys = (store.getState().auth as AuthState).keyPair
|
||||||
|
|
||||||
|
// Check if the private and public key pair is available.
|
||||||
if (!keys) {
|
if (!keys) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Login method is ${LoginMethods.privateKey} but private & public key pair is not found.`
|
`Login method is ${LoginMethods.privateKey} but private & public key pair is not found.`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decode the private key.
|
||||||
const { private: nsec } = keys
|
const { private: nsec } = keys
|
||||||
const privateKey = nip19.decode(nsec).data as Uint8Array
|
const privateKey = nip19.decode(nsec).data as Uint8Array
|
||||||
|
|
||||||
|
// Generate the conversation key using NIP-44 utilities.
|
||||||
const nip44ConversationKey = nip44.v2.utils.getConversationKey(
|
const nip44ConversationKey = nip44.v2.utils.getConversationKey(
|
||||||
privateKey,
|
privateKey,
|
||||||
receiver
|
receiver
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Encrypt the content using the generated conversation key.
|
||||||
const encrypted = nip44.v2.encrypt(content, nip44ConversationKey)
|
const encrypted = nip44.v2.encrypt(content, nip44ConversationKey)
|
||||||
|
|
||||||
return encrypted
|
return encrypted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Throw an error if the login method is nsecBunker (not supported).
|
||||||
if (loginMethod === LoginMethods.nsecBunker) {
|
if (loginMethod === LoginMethods.nsecBunker) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`nip44 encryption is not yet supported for login method '${LoginMethods.nsecBunker}'`
|
`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')
|
throw new Error('Login method is undefined')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts the given content from the specified sender using NIP-44 decryption.
|
||||||
|
*
|
||||||
|
* @param sender The public key of the sender.
|
||||||
|
* @param content The encrypted content to be decrypted.
|
||||||
|
* @returns The decrypted content as a string.
|
||||||
|
* @throws Error if the nostr extension does not support NIP-44, if the private key pair is not found, or if the login method is unsupported.
|
||||||
|
*/
|
||||||
nip44Decrypt = async (sender: string, content: string) => {
|
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 loginMethod = (store.getState().auth as AuthState).loginMethod
|
||||||
|
|
||||||
|
// Handle decryption when the login method is via an extension.
|
||||||
if (loginMethod === LoginMethods.extension) {
|
if (loginMethod === LoginMethods.extension) {
|
||||||
const nostr = this.getNostrObject()
|
const nostr = this.getNostrObject()
|
||||||
|
|
||||||
|
// Check if the nostr object supports NIP-44 decryption.
|
||||||
if (!nostr.nip44) {
|
if (!nostr.nip44) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Your nostr extension does not support nip44 encryption & decryption`
|
`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)
|
const decrypted = await nostr.nip44.decrypt(sender, content)
|
||||||
return decrypted as string
|
return decrypted as string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle decryption when the login method is via a private key.
|
||||||
if (loginMethod === LoginMethods.privateKey) {
|
if (loginMethod === LoginMethods.privateKey) {
|
||||||
const keys = (store.getState().auth as AuthState).keyPair
|
const keys = (store.getState().auth as AuthState).keyPair
|
||||||
|
|
||||||
|
// Check if the private and public key pair is available.
|
||||||
if (!keys) {
|
if (!keys) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Login method is ${LoginMethods.privateKey} but private & public key pair is not found.`
|
`Login method is ${LoginMethods.privateKey} but private & public key pair is not found.`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decode the private key.
|
||||||
const { private: nsec } = keys
|
const { private: nsec } = keys
|
||||||
const privateKey = nip19.decode(nsec).data as Uint8Array
|
const privateKey = nip19.decode(nsec).data as Uint8Array
|
||||||
|
|
||||||
|
// Generate the conversation key using NIP-44 utilities.
|
||||||
const nip44ConversationKey = nip44.v2.utils.getConversationKey(
|
const nip44ConversationKey = nip44.v2.utils.getConversationKey(
|
||||||
privateKey,
|
privateKey,
|
||||||
sender
|
sender
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Decrypt the content using the generated conversation key.
|
||||||
const decrypted = nip44.v2.decrypt(content, nip44ConversationKey)
|
const decrypted = nip44.v2.decrypt(content, nip44ConversationKey)
|
||||||
|
|
||||||
return decrypted
|
return decrypted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Throw an error if the login method is nsecBunker (not supported).
|
||||||
if (loginMethod === LoginMethods.nsecBunker) {
|
if (loginMethod === LoginMethods.nsecBunker) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`nip44 decryption is not yet supported for login method '${LoginMethods.nsecBunker}'`
|
`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')
|
throw new Error('Login method is undefined')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ import { toast } from 'react-toastify'
|
|||||||
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
||||||
import { UserComponent } from '../../components/username'
|
import { UserComponent } from '../../components/username'
|
||||||
import { MetadataController, NostrController } from '../../controllers'
|
import { MetadataController, NostrController } from '../../controllers'
|
||||||
|
import { appPrivateRoutes } from '../../routes'
|
||||||
import { State } from '../../store/rootReducer'
|
import { State } from '../../store/rootReducer'
|
||||||
import {
|
import {
|
||||||
CreateSignatureEventContent,
|
CreateSignatureEventContent,
|
||||||
@ -42,6 +43,7 @@ import {
|
|||||||
} from '../../types'
|
} from '../../types'
|
||||||
import {
|
import {
|
||||||
encryptArrayBuffer,
|
encryptArrayBuffer,
|
||||||
|
formatTimestamp,
|
||||||
generateEncryptionKey,
|
generateEncryptionKey,
|
||||||
generateKeys,
|
generateKeys,
|
||||||
generateKeysFile,
|
generateKeysFile,
|
||||||
@ -58,24 +60,6 @@ import {
|
|||||||
uploadToFileStorage
|
uploadToFileStorage
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
import { appPrivateRoutes } from '../../routes'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function to get the current timestamp in YYMMDD:HHMMSS format
|
|
||||||
*/
|
|
||||||
const getFormattedTimestamp = () => {
|
|
||||||
const now = new Date()
|
|
||||||
const padZero = (num: number) => (num < 10 ? '0' + num : num)
|
|
||||||
|
|
||||||
const year = now.getFullYear()
|
|
||||||
const month = padZero(now.getMonth() + 1) // Months are zero-indexed
|
|
||||||
const day = padZero(now.getDate())
|
|
||||||
const hours = padZero(now.getHours())
|
|
||||||
const minutes = padZero(now.getMinutes())
|
|
||||||
const seconds = padZero(now.getSeconds())
|
|
||||||
|
|
||||||
return `${year}-${month}-${day}_${hours}:${minutes}:${seconds}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CreatePage = () => {
|
export const CreatePage = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@ -87,7 +71,7 @@ export const CreatePage = () => {
|
|||||||
|
|
||||||
const [authUrl, setAuthUrl] = useState<string>()
|
const [authUrl, setAuthUrl] = useState<string>()
|
||||||
|
|
||||||
const [title, setTitle] = useState(`sigit_${getFormattedTimestamp()}`)
|
const [title, setTitle] = useState(`sigit_${formatTimestamp(Date.now())}`)
|
||||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([])
|
const [selectedFiles, setSelectedFiles] = useState<File[]>([])
|
||||||
|
|
||||||
const [userInput, setUserInput] = useState('')
|
const [userInput, setUserInput] = useState('')
|
||||||
|
@ -42,6 +42,12 @@ enum SignedStatus {
|
|||||||
export const SignPage = () => {
|
export const SignPage = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
const {
|
||||||
meta: metaInNavState,
|
meta: metaInNavState,
|
||||||
arrayBuffer: decryptedArrayBuffer,
|
arrayBuffer: decryptedArrayBuffer,
|
||||||
|
@ -44,6 +44,10 @@ export const VerifyPage = () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
/**
|
||||||
|
* uploadedZip will be received from home page when a user uploads a sigit zip wrapper that contains meta.json
|
||||||
|
* meta will be received in navigation from create & home page in online mode
|
||||||
|
*/
|
||||||
const { uploadedZip, meta: metaInNavState } = location.state || {}
|
const { uploadedZip, meta: metaInNavState } = location.state || {}
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
@ -181,10 +181,18 @@ export const generateKeys = async (
|
|||||||
return { sender: getPublicKey(privateKey), keys }
|
return { sender: getPublicKey(privateKey), keys }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to extract the ZIP URL and encryption key from the provided metadata.
|
||||||
|
* @param meta - The metadata object containing the create signature and encryption keys.
|
||||||
|
* @returns A promise that resolves to an object containing the create signature event,
|
||||||
|
* create signature content, ZIP URL, and decrypted encryption key.
|
||||||
|
*/
|
||||||
export const extractZipUrlAndEncryptionKey = async (meta: Meta) => {
|
export const extractZipUrlAndEncryptionKey = async (meta: Meta) => {
|
||||||
|
// Parse the create signature event from the metadata
|
||||||
const createSignatureEvent = await parseJson<Event>(
|
const createSignatureEvent = await parseJson<Event>(
|
||||||
meta.createSignature
|
meta.createSignature
|
||||||
).catch((err) => {
|
).catch((err) => {
|
||||||
|
// Log and display an error message if parsing fails
|
||||||
console.log('err in parsing the createSignature event:>> ', err)
|
console.log('err in parsing the createSignature event:>> ', err)
|
||||||
toast.error(
|
toast.error(
|
||||||
err.message || 'error occurred in parsing the create signature event'
|
err.message || 'error occurred in parsing the create signature event'
|
||||||
@ -192,17 +200,21 @@ export const extractZipUrlAndEncryptionKey = async (meta: Meta) => {
|
|||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Return null if the create signature event could not be parsed
|
||||||
if (!createSignatureEvent) return null
|
if (!createSignatureEvent) return null
|
||||||
|
|
||||||
|
// Verify the validity of the create signature event
|
||||||
const isValidCreateSignature = verifyEvent(createSignatureEvent)
|
const isValidCreateSignature = verifyEvent(createSignatureEvent)
|
||||||
if (!isValidCreateSignature) {
|
if (!isValidCreateSignature) {
|
||||||
toast.error('Create signature is invalid')
|
toast.error('Create signature is invalid')
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse the content of the create signature event
|
||||||
const createSignatureContent = await parseJson<CreateSignatureEventContent>(
|
const createSignatureContent = await parseJson<CreateSignatureEventContent>(
|
||||||
createSignatureEvent.content
|
createSignatureEvent.content
|
||||||
).catch((err) => {
|
).catch((err) => {
|
||||||
|
// Log and display an error message if parsing fails
|
||||||
console.log(`err in parsing the createSignature event's content :>> `, err)
|
console.log(`err in parsing the createSignature event's content :>> `, err)
|
||||||
toast.error(
|
toast.error(
|
||||||
`error occurred in parsing the create signature event's content`
|
`error occurred in parsing the create signature event's content`
|
||||||
@ -210,29 +222,38 @@ export const extractZipUrlAndEncryptionKey = async (meta: Meta) => {
|
|||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Return null if the create signature content could not be parsed
|
||||||
if (!createSignatureContent) return null
|
if (!createSignatureContent) return null
|
||||||
|
|
||||||
|
// Extract the ZIP URL from the create signature content
|
||||||
const zipUrl = createSignatureContent.zipUrl
|
const zipUrl = createSignatureContent.zipUrl
|
||||||
|
|
||||||
|
// Retrieve the user's public key from the state
|
||||||
const usersPubkey = (store.getState().auth as AuthState).usersPubkey!
|
const usersPubkey = (store.getState().auth as AuthState).usersPubkey!
|
||||||
const usersNpub = hexToNpub(usersPubkey)
|
const usersNpub = hexToNpub(usersPubkey)
|
||||||
|
|
||||||
|
// Return null if the metadata does not contain keys
|
||||||
if (!meta.keys) return null
|
if (!meta.keys) return null
|
||||||
|
|
||||||
const { sender, keys } = meta.keys
|
const { sender, keys } = meta.keys
|
||||||
|
|
||||||
|
// Check if the user's public key is in the keys object
|
||||||
if (usersNpub in keys) {
|
if (usersNpub in keys) {
|
||||||
|
// Instantiate the NostrController to decrypt the encryption key
|
||||||
const nostrController = NostrController.getInstance()
|
const nostrController = NostrController.getInstance()
|
||||||
const decrypted = await nostrController
|
const decrypted = await nostrController
|
||||||
.nip04Decrypt(sender, keys[usersNpub])
|
.nip04Decrypt(sender, keys[usersNpub])
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
// Log and display an error message if decryption fails
|
||||||
console.log('An error occurred in decrypting encryption key', err)
|
console.log('An error occurred in decrypting encryption key', err)
|
||||||
toast.error('An error occurred in decrypting encryption key')
|
toast.error('An error occurred in decrypting encryption key')
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Return null if the encryption key could not be decrypted
|
||||||
if (!decrypted) return null
|
if (!decrypted) return null
|
||||||
|
|
||||||
|
// Return the parsed and decrypted data
|
||||||
return {
|
return {
|
||||||
createSignatureEvent,
|
createSignatureEvent,
|
||||||
createSignatureContent,
|
createSignatureContent,
|
||||||
|
@ -493,8 +493,10 @@ export const updateUsersAppData = async (meta: Meta) => {
|
|||||||
|
|
||||||
if (!newBlossomUrl) return null
|
if (!newBlossomUrl) return null
|
||||||
|
|
||||||
|
// insert new blossom url at the start of the array
|
||||||
blossomUrls.unshift(newBlossomUrl)
|
blossomUrls.unshift(newBlossomUrl)
|
||||||
|
|
||||||
|
// only keep last 10 blossom urls, delete older ones
|
||||||
if (blossomUrls.length > 10) {
|
if (blossomUrls.length > 10) {
|
||||||
const filesToDelete = blossomUrls.splice(10)
|
const filesToDelete = blossomUrls.splice(10)
|
||||||
filesToDelete.forEach((url) => {
|
filesToDelete.forEach((url) => {
|
||||||
@ -509,6 +511,7 @@ export const updateUsersAppData = async (meta: Meta) => {
|
|||||||
|
|
||||||
const usersPubkey = (store.getState().auth as AuthState).usersPubkey!
|
const usersPubkey = (store.getState().auth as AuthState).usersPubkey!
|
||||||
|
|
||||||
|
// encrypt content for storing in kind 30078 event
|
||||||
const nostrController = NostrController.getInstance()
|
const nostrController = NostrController.getInstance()
|
||||||
const encryptedContent = await nostrController
|
const encryptedContent = await nostrController
|
||||||
.nip04Encrypt(
|
.nip04Encrypt(
|
||||||
@ -624,24 +627,37 @@ const deleteBlossomFile = async (url: string, privateKey: string) => {
|
|||||||
console.log('response.data :>> ', response.data)
|
console.log('response.data :>> ', response.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to upload user application data to the Blossom server.
|
||||||
|
* @param sigits - An object containing metadata for the user application data.
|
||||||
|
* @param processedGiftWraps - An array of processed gift wrap IDs.
|
||||||
|
* @param privateKey - The private key used for encryption.
|
||||||
|
* @returns A promise that resolves to the URL of the uploaded file.
|
||||||
|
*/
|
||||||
const uploadUserAppDataToBlossom = async (
|
const uploadUserAppDataToBlossom = async (
|
||||||
sigits: { [key: string]: Meta },
|
sigits: { [key: string]: Meta },
|
||||||
processedGiftWraps: string[],
|
processedGiftWraps: string[],
|
||||||
privateKey: string
|
privateKey: string
|
||||||
) => {
|
) => {
|
||||||
|
// Create an object containing the sigits and processed gift wraps
|
||||||
const obj = {
|
const obj = {
|
||||||
sigits,
|
sigits,
|
||||||
processedGiftWraps
|
processedGiftWraps
|
||||||
}
|
}
|
||||||
|
// Convert the object to a JSON string
|
||||||
const stringified = JSON.stringify(obj)
|
const stringified = JSON.stringify(obj)
|
||||||
|
|
||||||
|
// Convert the private key from hex to bytes
|
||||||
const secretKey = hexToBytes(privateKey)
|
const secretKey = hexToBytes(privateKey)
|
||||||
|
// Encrypt the JSON string using the secret key
|
||||||
const encrypted = nip44.v2.encrypt(
|
const encrypted = nip44.v2.encrypt(
|
||||||
stringified,
|
stringified,
|
||||||
nip44ConversationKey(secretKey, getPublicKey(secretKey))
|
nip44ConversationKey(secretKey, getPublicKey(secretKey))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Create a blob from the encrypted data
|
||||||
const blob = new Blob([encrypted], { type: 'application/octet-stream' })
|
const blob = new Blob([encrypted], { type: 'application/octet-stream' })
|
||||||
|
// Create a file from the blob
|
||||||
const file = new File([blob], 'encrypted.txt', {
|
const file = new File([blob], 'encrypted.txt', {
|
||||||
type: 'application/octet-stream'
|
type: 'application/octet-stream'
|
||||||
})
|
})
|
||||||
@ -659,8 +675,10 @@ const uploadUserAppDataToBlossom = async (
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Finalize the event with the private key
|
||||||
const authEvent = finalizeEvent(event, hexToBytes(privateKey))
|
const authEvent = finalizeEvent(event, hexToBytes(privateKey))
|
||||||
|
|
||||||
|
// URL of the file storage service
|
||||||
const FILE_STORAGE_URL = 'https://blossom.sigit.io'
|
const FILE_STORAGE_URL = 'https://blossom.sigit.io'
|
||||||
|
|
||||||
// Upload the file to the file storage service using Axios
|
// Upload the file to the file storage service using Axios
|
||||||
@ -674,22 +692,34 @@ const uploadUserAppDataToBlossom = async (
|
|||||||
return response.data.url as string
|
return response.data.url as string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to retrieve and decrypt user application data from Blossom server.
|
||||||
|
* @param url - The URL to fetch the encrypted data from.
|
||||||
|
* @param privateKey - The private key used for decryption.
|
||||||
|
* @returns A promise that resolves to the decrypted and parsed user application data.
|
||||||
|
*/
|
||||||
const getUserAppDataFromBlossom = async (url: string, privateKey: string) => {
|
const getUserAppDataFromBlossom = async (url: string, privateKey: string) => {
|
||||||
|
// Initialize errorCode to track HTTP error codes
|
||||||
let errorCode = 0
|
let errorCode = 0
|
||||||
|
|
||||||
|
// Fetch the encrypted data from the provided URL
|
||||||
const encrypted = await axios
|
const encrypted = await axios
|
||||||
.get(url, {
|
.get(url, {
|
||||||
responseType: 'blob'
|
responseType: 'blob' // Expect a blob response
|
||||||
})
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
|
// Convert the blob response to a File object
|
||||||
const file = new File([res.data], 'encrypted.txt')
|
const file = new File([res.data], 'encrypted.txt')
|
||||||
|
// Read the text content from the file
|
||||||
const text = await file.text()
|
const text = await file.text()
|
||||||
return text
|
return text
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
// Log and display an error message if the request fails
|
||||||
console.error(`error occurred in getting file from ${url}`, err)
|
console.error(`error occurred in getting file from ${url}`, err)
|
||||||
toast.error(err.message || `error occurred in getting file from ${url}`)
|
toast.error(err.message || `error occurred in getting file from ${url}`)
|
||||||
|
|
||||||
|
// Set errorCode to the HTTP status code if available
|
||||||
if (err.request) {
|
if (err.request) {
|
||||||
const { status } = err.request
|
const { status } = err.request
|
||||||
errorCode = status
|
errorCode = status
|
||||||
@ -698,6 +728,7 @@ const getUserAppDataFromBlossom = async (url: string, privateKey: string) => {
|
|||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Return a default value if the requested resource is not found (404)
|
||||||
if (errorCode === 404) {
|
if (errorCode === 404) {
|
||||||
return {
|
return {
|
||||||
sigits: {},
|
sigits: {},
|
||||||
@ -705,20 +736,26 @@ const getUserAppDataFromBlossom = async (url: string, privateKey: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return null if the encrypted data could not be retrieved
|
||||||
if (!encrypted) return null
|
if (!encrypted) return null
|
||||||
|
|
||||||
|
// Convert the private key from hex to bytes
|
||||||
const secret = hexToBytes(privateKey)
|
const secret = hexToBytes(privateKey)
|
||||||
|
// Get the public key corresponding to the private key
|
||||||
const pubkey = getPublicKey(secret)
|
const pubkey = getPublicKey(secret)
|
||||||
|
|
||||||
|
// Decrypt the encrypted data using the secret and public key
|
||||||
const decrypted = nip44.v2.decrypt(
|
const decrypted = nip44.v2.decrypt(
|
||||||
encrypted,
|
encrypted,
|
||||||
nip44ConversationKey(secret, pubkey)
|
nip44ConversationKey(secret, pubkey)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Parse the decrypted JSON content
|
||||||
const parsedContent = await parseJson<{
|
const parsedContent = await parseJson<{
|
||||||
sigits: { [key: string]: Meta }
|
sigits: { [key: string]: Meta }
|
||||||
processedGiftWraps: string[]
|
processedGiftWraps: string[]
|
||||||
}>(decrypted).catch((err) => {
|
}>(decrypted).catch((err) => {
|
||||||
|
// Log and display an error message if parsing fails
|
||||||
console.log(
|
console.log(
|
||||||
'An error occurred in parsing the user app data content from blossom server',
|
'An error occurred in parsing the user app data content from blossom server',
|
||||||
err
|
err
|
||||||
@ -729,15 +766,22 @@ const getUserAppDataFromBlossom = async (url: string, privateKey: string) => {
|
|||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Return the parsed content
|
||||||
return parsedContent
|
return parsedContent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to subscribe to sigits notifications for a specified public key.
|
||||||
|
* @param pubkey - The public key to subscribe to.
|
||||||
|
* @returns A promise that resolves when the subscription is successful.
|
||||||
|
*/
|
||||||
export const subscribeForSigits = async (pubkey: string) => {
|
export const subscribeForSigits = async (pubkey: string) => {
|
||||||
// Get relay list metadata
|
// Instantiate the MetadataController to retrieve relay list metadata
|
||||||
const metadataController = new MetadataController()
|
const metadataController = new MetadataController()
|
||||||
const relaySet = await metadataController
|
const relaySet = await metadataController
|
||||||
.findRelayListMetadata(pubkey)
|
.findRelayListMetadata(pubkey)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
// Log an error if retrieving relay list metadata fails
|
||||||
console.log(
|
console.log(
|
||||||
`An error occurred while finding relay list metadata for ${hexToNpub(pubkey)}`,
|
`An error occurred while finding relay list metadata for ${hexToNpub(pubkey)}`,
|
||||||
err
|
err
|
||||||
@ -751,15 +795,20 @@ export const subscribeForSigits = async (pubkey: string) => {
|
|||||||
// Ensure relay list is not empty
|
// Ensure relay list is not empty
|
||||||
if (relaySet.read.length === 0) return
|
if (relaySet.read.length === 0) return
|
||||||
|
|
||||||
|
// Define the filter for the subscription
|
||||||
const filter: Filter = {
|
const filter: Filter = {
|
||||||
kinds: [1059],
|
kinds: [1059],
|
||||||
'#p': [pubkey]
|
'#p': [pubkey]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Instantiate a new SimplePool for the subscription
|
||||||
const pool = new SimplePool()
|
const pool = new SimplePool()
|
||||||
|
|
||||||
|
// Subscribe to the specified relays with the defined filter
|
||||||
return pool.subscribeMany(relaySet.read, [filter], {
|
return pool.subscribeMany(relaySet.read, [filter], {
|
||||||
|
// Define a callback function to handle received events
|
||||||
onevent: (event) => {
|
onevent: (event) => {
|
||||||
processReceivedEvent(event)
|
processReceivedEvent(event) // Process the received event
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -810,9 +859,16 @@ const processReceivedEvent = async (event: Event, difficulty: number = 5) => {
|
|||||||
updateUsersAppData(meta)
|
updateUsersAppData(meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to send a notification to a specified receiver.
|
||||||
|
* @param receiver - The recipient's public key.
|
||||||
|
* @param meta - Metadata associated with the notification.
|
||||||
|
*/
|
||||||
export const sendNotification = async (receiver: string, meta: Meta) => {
|
export const sendNotification = async (receiver: string, meta: Meta) => {
|
||||||
|
// Retrieve the user's public key from the state
|
||||||
const usersPubkey = (store.getState().auth as AuthState).usersPubkey!
|
const usersPubkey = (store.getState().auth as AuthState).usersPubkey!
|
||||||
|
|
||||||
|
// Create an unsigned event object with the provided metadata
|
||||||
const unsignedEvent: UnsignedEvent = {
|
const unsignedEvent: UnsignedEvent = {
|
||||||
kind: 938,
|
kind: 938,
|
||||||
pubkey: usersPubkey,
|
pubkey: usersPubkey,
|
||||||
@ -821,13 +877,15 @@ export const sendNotification = async (receiver: string, meta: Meta) => {
|
|||||||
created_at: now()
|
created_at: now()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wrap the unsigned event with the receiver's information
|
||||||
const wrappedEvent = createWrap(unsignedEvent, receiver)
|
const wrappedEvent = createWrap(unsignedEvent, receiver)
|
||||||
|
|
||||||
// Get relay list metadata
|
// Instantiate the MetadataController to retrieve relay list metadata
|
||||||
const metadataController = new MetadataController()
|
const metadataController = new MetadataController()
|
||||||
const relaySet = await metadataController
|
const relaySet = await metadataController
|
||||||
.findRelayListMetadata(receiver)
|
.findRelayListMetadata(receiver)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
// Log an error if retrieving relay list metadata fails
|
||||||
console.log(
|
console.log(
|
||||||
`An error occurred while finding relay list metadata for ${hexToNpub(receiver)}`,
|
`An error occurred while finding relay list metadata for ${hexToNpub(receiver)}`,
|
||||||
err
|
err
|
||||||
@ -845,10 +903,12 @@ export const sendNotification = async (receiver: string, meta: Meta) => {
|
|||||||
// Publish the notification event to the recipient's read relays
|
// Publish the notification event to the recipient's read relays
|
||||||
const nostrController = NostrController.getInstance()
|
const nostrController = NostrController.getInstance()
|
||||||
|
|
||||||
|
// Attempt to publish the event to the relays, with a timeout of 2 minutes
|
||||||
await Promise.race([
|
await Promise.race([
|
||||||
nostrController.publishEvent(wrappedEvent, relaySet.read),
|
nostrController.publishEvent(wrappedEvent, relaySet.read),
|
||||||
timeout(1000 * 60 * 2)
|
timeout(1000 * 60 * 2)
|
||||||
]).catch((err) => {
|
]).catch((err) => {
|
||||||
|
// Log an error if publishing the notification event fails
|
||||||
console.log(
|
console.log(
|
||||||
`An error occurred while publishing notification event for ${hexToNpub(receiver)}`,
|
`An error occurred while publishing notification event for ${hexToNpub(receiver)}`,
|
||||||
err
|
err
|
||||||
|
Loading…
Reference in New Issue
Block a user