issue-25 #42

Merged
s merged 12 commits from issue-25 into main 2024-05-16 06:45:59 +00:00
17 changed files with 110 additions and 98 deletions
Showing only changes of commit ad9c88a4f5 - Show all commits

View File

@ -1 +1 @@
VITE_MOST_POPULAR_RELAYS=wss://relay.damus.io wss://eden.nostr.land wss://nos.lol wss://relay.snort.social wss://relay.current.fyi wss://brb.io wss://nostr.orangepill.dev wss://nostr-pub.wellorder.net wss://nostr.bitcoiner.social wss://nostr.wine wss://nostr.oxtr.dev wss://relay.nostr.bg wss://nostr.mom wss://nostr.fmt.wiz.biz wss://relay.nostr.band wss://nostr-pub.semisol.dev wss://nostr.milou.lol wss://puravida.nostr.land wss://nostr.onsats.org wss://relay.nostr.info wss://offchain.pub wss://relay.orangepill.dev wss://no.str.cr wss://atlas.nostr.land wss://nostr.zebedee.cloud wss://nostr-relay.wlvs.space wss://relay.nostrati.com wss://relay.nostr.com.au wss://nostr.inosta.cc wss://nostr.rocks VITE_MOST_POPULAR_RELAYS=wss://relay.damus.io wss://eden.nostr.land wss://nos.lol wss://relay.snort.social wss://relay.current.fyi wss://brb.io wss://nostr.orangepill.dev wss://nostr-pub.wellorder.net wss://nostr.wine wss://nostr.oxtr.dev wss://relay.nostr.bg wss://nostr.mom wss://nostr.fmt.wiz.biz wss://relay.nostr.band wss://nostr-pub.semisol.dev wss://nostr.milou.lol wss://puravida.nostr.land wss://nostr.onsats.org wss://relay.nostr.info wss://offchain.pub wss://relay.orangepill.dev wss://no.str.cr wss://atlas.nostr.land wss://nostr.zebedee.cloud wss://nostr-relay.wlvs.space wss://relay.nostrati.com wss://relay.nostr.com.au wss://nostr.inosta.cc wss://nostr.rocks

7
.prettierrc Normal file
View File

@ -0,0 +1,7 @@
{
"trailingComma": "none",
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"endOfLine": "auto"
}

View File

@ -9,6 +9,8 @@
"build": "tsc && vite build", "build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint . --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "lint:fix": "eslint . --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"formatter:check": "npx prettier --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"",
"formatter:fix": "npx prettier --write \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {

View File

@ -67,7 +67,7 @@ const App = () => {
})} })}
<Route <Route
path='*' path="*"
element={ element={
<Navigate <Navigate
to={ to={

View File

@ -87,10 +87,10 @@ export const AppBar = () => {
const isAuthenticated = authState?.loggedIn === true const isAuthenticated = authState?.loggedIn === true
return ( return (
<AppBarMui position='fixed' className={styles.AppBar}> <AppBarMui position="fixed" className={styles.AppBar}>
<Toolbar className={styles.toolbar}> <Toolbar className={styles.toolbar}>
<Box className={styles.logoWrapper}> <Box className={styles.logoWrapper}>
<img src='/logo.png' alt='Logo' onClick={() => navigate('/')} /> <img src="/logo.png" alt="Logo" onClick={() => navigate('/')} />
</Box> </Box>
<Box className={styles.rightSideBox}> <Box className={styles.rightSideBox}>
@ -99,7 +99,7 @@ export const AppBar = () => {
onClick={() => { onClick={() => {
navigate(appPublicRoutes.login) navigate(appPublicRoutes.login)
}} }}
variant='contained' variant="contained"
> >
Sign in Sign in
</Button> </Button>
@ -113,7 +113,7 @@ export const AppBar = () => {
handleClick={handleOpenUserMenu} handleClick={handleOpenUserMenu}
/> />
<Menu <Menu
id='menu-appbar' id="menu-appbar"
anchorEl={anchorElUser} anchorEl={anchorElUser}
anchorOrigin={{ anchorOrigin={{
vertical: 'bottom', vertical: 'bottom',
@ -133,7 +133,7 @@ export const AppBar = () => {
display: { md: 'none' } display: { md: 'none' }
}} }}
> >
<Typography variant='h6'>{username}</Typography> <Typography variant="h6">{username}</Typography>
</MenuItem> </MenuItem>
<MenuItem <MenuItem
onClick={handleProfile} onClick={handleProfile}
@ -145,7 +145,7 @@ export const AppBar = () => {
</MenuItem> </MenuItem>
<Link <Link
to={appPublicRoutes.help} to={appPublicRoutes.help}
target='_blank' target="_blank"
style={{ color: 'inherit', textDecoration: 'inherit' }} style={{ color: 'inherit', textDecoration: 'inherit' }}
> >
<MenuItem <MenuItem

View File

@ -13,16 +13,16 @@ const Username = ({ username, avatarContent, handleClick }: Props) => {
return ( return (
<IconButton <IconButton
aria-label='account of current user' aria-label="account of current user"
aria-controls='menu-appbar' aria-controls="menu-appbar"
aria-haspopup='true' aria-haspopup="true"
onClick={handleClick} onClick={handleClick}
color='inherit' color="inherit"
> >
<img <img
src={avatarContent} src={avatarContent}
alt='user-avatar' alt="user-avatar"
className='profile-image' className="profile-image"
style={{ style={{
borderWidth: '3px', borderWidth: '3px',
borderStyle: hexKey ? 'solid' : 'none', borderStyle: hexKey ? 'solid' : 'none',
@ -30,7 +30,7 @@ const Username = ({ username, avatarContent, handleClick }: Props) => {
}} }}
/> />
<Typography <Typography
variant='h6' variant="h6"
sx={{ sx={{
color: '#3e3e3e', color: '#3e3e3e',
padding: '0 8px', padding: '0 8px',

View File

@ -152,9 +152,8 @@ export class MetadataController {
created_at: timestamp created_at: timestamp
} }
signedMetadataEvent = await this.nostrController.signEvent( signedMetadataEvent =
newMetadataEvent await this.nostrController.signEvent(newMetadataEvent)
)
} }
await this.nostrController await this.nostrController
@ -222,9 +221,8 @@ export class MetadataController {
} }
// sign job request event // sign job request event
const jobSignedEvent = await this.nostrController.signEvent( const jobSignedEvent =
jobEventTemplate await this.nostrController.signEvent(jobEventTemplate)
)
const relays = [ const relays = [
'wss://relay.damus.io', 'wss://relay.damus.io',

View File

@ -219,12 +219,10 @@ export class NostrController extends EventEmitter {
results.forEach((res, index) => { results.forEach((res, index) => {
if (res.status === 'rejected') { if (res.status === 'rejected') {
failedPublishes.push( failedPublishes.push({
{
relay: relays[index], relay: relays[index],
error: res.reason.message error: res.reason.message
} })
)
} }
}) })

View File

@ -59,13 +59,13 @@ export const MainLayout = () => {
setIsLoading(false) setIsLoading(false)
}, [dispatch]) }, [dispatch])
if (isLoading) return <LoadingSpinner desc='Loading App' /> if (isLoading) return <LoadingSpinner desc="Loading App" />
return ( return (
<> <>
<AppBar /> <AppBar />
<Box className='main'> <Box className="main">
<Container <Container
sx={{ sx={{
position: 'relative', position: 'relative',

View File

@ -360,10 +360,10 @@ export const CreatePage = () => {
if (authUrl) { if (authUrl) {
return ( return (
<iframe <iframe
title='Nsecbunker auth' title="Nsecbunker auth"
src={authUrl} src={authUrl}
width='100%' width="100%"
height='500px' height="500px"
/> />
) )
} }
@ -372,13 +372,13 @@ export const CreatePage = () => {
<> <>
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />} {isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
<Box className={styles.container}> <Box className={styles.container}>
<Typography component='label' variant='h6'> <Typography component="label" variant="h6">
Select files Select files
</Typography> </Typography>
<MuiFileInput <MuiFileInput
multiple multiple
placeholder='Choose Files' placeholder="Choose Files"
value={selectedFiles} value={selectedFiles}
onChange={(value) => handleSelectFiles(value)} onChange={(value) => handleSelectFiles(value)}
/> />
@ -386,7 +386,7 @@ export const CreatePage = () => {
<ul> <ul>
{selectedFiles.map((file, index) => ( {selectedFiles.map((file, index) => (
<li key={index}> <li key={index}>
<Typography component='label'>{file.name}</Typography> <Typography component="label">{file.name}</Typography>
<IconButton onClick={() => handleRemoveFile(file)}> <IconButton onClick={() => handleRemoveFile(file)}>
<Clear style={{ color: 'red' }} />{' '} <Clear style={{ color: 'red' }} />{' '}
</IconButton> </IconButton>
@ -396,24 +396,24 @@ export const CreatePage = () => {
{displayUserInput && ( {displayUserInput && (
<> <>
<Typography component='label' variant='h6'> <Typography component="label" variant="h6">
Select signers and viewers Select signers and viewers
</Typography> </Typography>
<Box className={styles.inputBlock}> <Box className={styles.inputBlock}>
<TextField <TextField
label='nip05 / npub' label="nip05 / npub"
value={userInput} value={userInput}
onChange={(e) => setUserInput(e.target.value)} onChange={(e) => setUserInput(e.target.value)}
helperText={error} helperText={error}
error={!!error} error={!!error}
/> />
<FormControl fullWidth> <FormControl fullWidth>
<InputLabel id='select-role-label'>Role</InputLabel> <InputLabel id="select-role-label">Role</InputLabel>
<Select <Select
labelId='select-role-label' labelId="select-role-label"
id='demo-simple-select' id="demo-simple-select"
value={userRole} value={userRole}
label='Role' label="Role"
onChange={(e) => setUserRole(e.target.value as UserRole)} onChange={(e) => setUserRole(e.target.value as UserRole)}
> >
<MenuItem value={UserRole.signer}>{UserRole.signer}</MenuItem> <MenuItem value={UserRole.signer}>{UserRole.signer}</MenuItem>
@ -425,7 +425,7 @@ export const CreatePage = () => {
<Button <Button
disabled={!userInput} disabled={!userInput}
onClick={handleAddUser} onClick={handleAddUser}
variant='contained' variant="contained"
> >
Add Add
</Button> </Button>
@ -437,7 +437,7 @@ export const CreatePage = () => {
handleRemoveUser={handleRemoveUser} handleRemoveUser={handleRemoveUser}
/> />
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}> <Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
<Button onClick={handleCreate} variant='contained'> <Button onClick={handleCreate} variant="contained">
Create Create
</Button> </Button>
</Box> </Box>
@ -515,8 +515,8 @@ const DisplayUser = ({
<img <img
onError={imageLoadError} onError={imageLoadError}
src={userMeta?.picture || roboUrl} src={userMeta?.picture || roboUrl}
alt='Profile Image' alt="Profile Image"
className='profile-image' className="profile-image"
style={{ style={{
borderWidth: '3px', borderWidth: '3px',
borderStyle: 'solid', borderStyle: 'solid',
@ -524,7 +524,7 @@ const DisplayUser = ({
}} }}
/> />
<Link to={getProfileRoute(user.pubkey)}> <Link to={getProfileRoute(user.pubkey)}>
<Typography component='label' className={styles.name}> <Typography component="label" className={styles.name}>
{userMeta?.display_name || {userMeta?.display_name ||
userMeta?.name || userMeta?.name ||
shorten(npub)} shorten(npub)}
@ -549,7 +549,7 @@ const DisplayUser = ({
</Select> </Select>
</TableCell> </TableCell>
<TableCell> <TableCell>
<Tooltip title='Remove User' arrow> <Tooltip title="Remove User" arrow>
<IconButton onClick={() => handleRemoveUser(user.pubkey)}> <IconButton onClick={() => handleRemoveUser(user.pubkey)}>
<Clear style={{ color: 'red' }} /> <Clear style={{ color: 'red' }} />
</IconButton> </IconButton>

View File

@ -10,7 +10,7 @@ export const HomePage = () => {
<Box className={styles.container}> <Box className={styles.container}>
<Button <Button
onClick={() => navigate(appPrivateRoutes.create)} onClick={() => navigate(appPrivateRoutes.create)}
variant='contained' variant="contained"
> >
Create Create
</Button> </Button>
@ -22,7 +22,7 @@ export const HomePage = () => {
</Button> </Button>
<Button <Button
onClick={() => navigate(appPrivateRoutes.verify)} onClick={() => navigate(appPrivateRoutes.verify)}
variant='contained' variant="contained"
> >
Verify Verify
</Button> </Button>

View File

@ -57,9 +57,9 @@ export const LandingPage = () => {
? theme.palette.getContrastText(bodyBackgroundColor) ? theme.palette.getContrastText(bodyBackgroundColor)
: '' : ''
}} }}
variant='h4' variant="h4"
> >
What is Nostr? What is SIGit?
</Typography> </Typography>
<Typography <Typography
sx={{ sx={{
@ -67,26 +67,28 @@ export const LandingPage = () => {
? theme.palette.getContrastText(bodyBackgroundColor) ? theme.palette.getContrastText(bodyBackgroundColor)
: '' : ''
}} }}
variant='body1' variant="body1"
> >
Nostr is a decentralised messaging protocol where YOU own your SIGit is an open-source and self-hostable solution for secure
identity. To get started, you must have an existing{' '} document signing and verification. Code is MIT licenced and
available at{' '}
<a <a
className='bold-link' className="bold-link"
target='_blank' target="_blank"
href='https://nostr.com/' href="https://git.sigit.io/sig/it"
> >
Nostr account https://git.sigit.io/sig/it
</a> </a>
. .
<br /> <br />
<br /> <br />
No email required - all notifications are made using the nQuiz SIGit lets you Create, Sign and Verify signature packs from any
relay. device with a browser.
<br /> <br />
<br /> <br />
If you no longer wish to hear from us, simply remove Unlike other solutions, SIGit is totally private - files are
relay.nquiz.io from your list of relays. encrypted locally, and valid packs can only be exported by named
recipients.
</Typography> </Typography>
</Box> </Box>
</Box> </Box>
@ -95,7 +97,7 @@ export const LandingPage = () => {
<div className={styles.loginBottomBar}> <div className={styles.loginBottomBar}>
<Button <Button
className={styles.loginBtn} className={styles.loginBtn}
variant='contained' variant="contained"
onClick={onSignInClick} onClick={onSignInClick}
> >
GET STARTED GET STARTED

View File

@ -53,9 +53,8 @@ export const Login = () => {
dispatch(updateLoginMethod(LoginMethods.extension)) dispatch(updateLoginMethod(LoginMethods.extension))
setLoadingSpinnerDesc('Authenticating and finding metadata') setLoadingSpinnerDesc('Authenticating and finding metadata')
const redirectPath = await authController.authenticateAndFindMetadata( const redirectPath =
pubkey await authController.authenticateAndFindMetadata(pubkey)
)
navigate(redirectPath) navigate(redirectPath)
}) })
@ -290,10 +289,10 @@ export const Login = () => {
if (authUrl) { if (authUrl) {
return ( return (
<iframe <iframe
title='Nsecbunker auth' title="Nsecbunker auth"
src={authUrl} src={authUrl}
width='100%' width="100%"
height='500px' height="500px"
/> />
) )
} }
@ -302,21 +301,21 @@ export const Login = () => {
<> <>
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />} {isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
<div className={styles.loginPage}> <div className={styles.loginPage}>
<Typography variant='h4'>Welcome to Sigit</Typography> <Typography variant="h4">Welcome to Sigit</Typography>
<TextField <TextField
label='nip05 / npub / nsec / bunker connx string' label="nip05 login / nip46 bunker string / nsec"
value={inputValue} value={inputValue}
onChange={(e) => setInputValue(e.target.value)} onChange={(e) => setInputValue(e.target.value)}
sx={{ width: '100%', mt: 2 }} sx={{ width: '100%', mt: 2 }}
/> />
{isNostrExtensionAvailable && ( {isNostrExtensionAvailable && (
<Button onClick={loginWithExtension} variant='text'> <Button onClick={loginWithExtension} variant="text">
Login with extension Login with extension
</Button> </Button>
)} )}
<Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}> <Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}>
<Button disabled={!inputValue} onClick={login} variant='contained'> <Button disabled={!inputValue} onClick={login} variant="contained">
Login Login
</Button> </Button>
</Box> </Box>

View File

@ -127,7 +127,7 @@ export const ProfilePage = () => {
label={label} label={label}
id={label.split(' ').join('-')} id={label.split(' ').join('-')}
value={profileMetadata![key] || ''} value={profileMetadata![key] || ''}
size='small' size="small"
multiline={multiline} multiline={multiline}
rows={rows} rows={rows}
className={styles.textField} className={styles.textField}
@ -166,7 +166,7 @@ export const ProfilePage = () => {
label={label} label={label}
id={label.split(' ').join('-')} id={label.split(' ').join('-')}
defaultValue={value} defaultValue={value}
size='small' size="small"
className={styles.textField} className={styles.textField}
disabled disabled
type={isPassword ? 'password' : 'text'} type={isPassword ? 'password' : 'text'}
@ -236,12 +236,17 @@ export const ProfilePage = () => {
const robohashButton = () => { const robohashButton = () => {
if (profileMetadata?.picture?.includes('robohash')) return null if (profileMetadata?.picture?.includes('robohash')) return null
return <Tooltip title="Generate a robohash avatar"> return (
{avatarLoading ? <CircularProgress style={{padding: 8}} size={22}/> <Tooltip title="Generate a robohash avatar">
: <IconButton onClick={generateRobotAvatar}> {avatarLoading ? (
<CircularProgress style={{ padding: 8 }} size={22} />
) : (
<IconButton onClick={generateRobotAvatar}>
<SmartToy /> <SmartToy />
</IconButton>} </IconButton>
)}
</Tooltip> </Tooltip>
)
} }
return ( return (
@ -284,7 +289,7 @@ export const ProfilePage = () => {
}} }}
className={styles.img} className={styles.img}
src={profileMetadata.picture || placeholderAvatar} src={profileMetadata.picture || placeholderAvatar}
alt='Profile Image' alt="Profile Image"
/> />
{nostrJoiningBlock && ( {nostrJoiningBlock && (
@ -296,7 +301,7 @@ export const ProfilePage = () => {
}} }}
component={Link} component={Link}
to={`https://njump.me/${nostrJoiningBlock.encodedEventPointer}`} to={`https://njump.me/${nostrJoiningBlock.encodedEventPointer}`}
target='_blank' target="_blank"
> >
On nostr since {nostrJoiningBlock.block.toLocaleString()} On nostr since {nostrJoiningBlock.block.toLocaleString()}
</Typography> </Typography>
@ -333,7 +338,7 @@ export const ProfilePage = () => {
{isUsersOwnProfile && ( {isUsersOwnProfile && (
<LoadingButton <LoadingButton
loading={savingProfileMetadata} loading={savingProfileMetadata}
variant='contained' variant="contained"
onClick={handleSaveMetadata} onClick={handleSaveMetadata}
> >
SAVE SAVE

View File

@ -1,17 +1,18 @@
export class DecryptionError extends Error { export class DecryptionError extends Error {
public message: string = '' public message: string = ''
constructor( constructor(public inputError: any) {
public inputError: any
) {
super() super()
if (inputError.message.toLowerCase().includes('expected')) { if (inputError.message.toLowerCase().includes('expected')) {
this.message = `The decryption key length or format is invalid.` this.message = `The decryption key length or format is invalid.`
} else if (inputError.message.includes('The JWK "alg" member was inconsistent')) { } else if (
inputError.message.includes('The JWK "alg" member was inconsistent')
) {
this.message = `The decryption key is invalid.` this.message = `The decryption key is invalid.`
} else { } else {
this.message = inputError.message || 'An error occurred while decrypting file.' this.message =
inputError.message || 'An error occurred while decrypting file.'
} }
this.name = 'DecryptionError' this.name = 'DecryptionError'

View File

@ -18,8 +18,8 @@ export const uploadToFileStorage = async (
const unixNow = Math.floor(Date.now() / 1000) const unixNow = Math.floor(Date.now() / 1000)
// Create a File object with the Blob data // Create a File object with the Blob data
const file = new File([blob], `zipped-${unixNow}.zip`, { const file = new File([blob], `compressed-${unixNow}.sigit`, {
type: 'application/zip' type: 'application/sigit'
}) })
// Define event metadata for authorization // Define event metadata for authorization
@ -39,13 +39,13 @@ export const uploadToFileStorage = async (
const authEvent = await nostrController.signEvent(event) const authEvent = await nostrController.signEvent(event)
// URL of the file storage service // URL of the file storage service
const FILE_STORAGE_URL = 'https://blossom.sigit.io' const FILE_STORAGE_URL = 'https://blossom.sigit.io' // REFACTOR: should be an env
// Upload the file to the file storage service using Axios // Upload the file to the file storage service using Axios
const response = await axios.put(`${FILE_STORAGE_URL}/upload`, file, { const response = await axios.put(`${FILE_STORAGE_URL}/upload`, file, {
headers: { headers: {
Authorization: 'Nostr ' + btoa(JSON.stringify(authEvent)), // Set authorization header Authorization: 'Nostr ' + btoa(JSON.stringify(authEvent)), // Set authorization header
'Content-Type': 'application/zip' // Set content type header 'Content-Type': 'application/sigit' // Set content type header
} }
}) })