chore(git): Merge branch 'main' into issue-11
This commit is contained in:
commit
0cdb5c2136
@ -2,9 +2,9 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="icon" type="image/png" href="/FAVICON.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
<title>SIGit</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
BIN
public/Logo2.png
BIN
public/Logo2.png
Binary file not shown.
Before Width: | Height: | Size: 87 KiB |
BIN
public/logo.png
Normal file
BIN
public/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 72 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
Before Width: | Height: | Size: 1.5 KiB |
@ -21,7 +21,6 @@ import Username from '../username'
|
||||
|
||||
import { Link, useLocation, useNavigate } from 'react-router-dom'
|
||||
import nostrichAvatar from '../../assets/images/avatar.png'
|
||||
import nostrichLogo from '../../assets/images/nostr-logo.jpg'
|
||||
import { NostrController } from '../../controllers'
|
||||
import {
|
||||
appPrivateRoutes,
|
||||
@ -114,7 +113,7 @@ export const AppBar = () => {
|
||||
<Toolbar className={styles.toolbar}>
|
||||
<Box sx={{ display: { xs: 'none', md: 'flex' } }}>
|
||||
<Box className={styles.logoWrapper}>
|
||||
<img src={nostrichLogo} alt='Logo' onClick={() => navigate('/')} />
|
||||
<img src="/logo.png" alt='Logo' onClick={() => navigate('/')} />
|
||||
</Box>
|
||||
|
||||
{isAuthenticated && (
|
||||
@ -149,7 +148,7 @@ export const AppBar = () => {
|
||||
{!isAuthenticated && (
|
||||
<Box className={styles.logoWrapper}>
|
||||
<img
|
||||
src={nostrichLogo}
|
||||
src="/logo.png"
|
||||
alt='Logo'
|
||||
onClick={() => navigate('/')}
|
||||
/>
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
.logoWrapper {
|
||||
height: 50px;
|
||||
width: 100px;
|
||||
width: 155px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
@ -5,11 +5,15 @@ import {
|
||||
kinds,
|
||||
validateEvent,
|
||||
verifyEvent,
|
||||
Event
|
||||
Event,
|
||||
EventTemplate,
|
||||
nip19
|
||||
} from 'nostr-tools'
|
||||
import { ProfileMetadata, RelaySet } from '../types'
|
||||
import { NostrJoiningBlock, ProfileMetadata, RelaySet } from '../types'
|
||||
import { NostrController } from '.'
|
||||
import { toast } from 'react-toastify'
|
||||
import { queryNip05 } from '../utils'
|
||||
import NDK, { NDKEvent, NDKSubscription } from '@nostr-dev-kit/ndk'
|
||||
|
||||
export class MetadataController {
|
||||
private nostrController: NostrController
|
||||
@ -163,5 +167,130 @@ export class MetadataController {
|
||||
})
|
||||
}
|
||||
|
||||
public getNostrJoiningBlockNumber = async (
|
||||
hexKey: string
|
||||
): Promise<NostrJoiningBlock | null> => {
|
||||
const relaySet = await this.findRelayListMetadata(hexKey)
|
||||
|
||||
const userRelays: string[] = []
|
||||
|
||||
// find user's relays
|
||||
if (relaySet.write.length > 0) {
|
||||
userRelays.push(...relaySet.write)
|
||||
} else {
|
||||
const metadata = await this.findMetadata(hexKey)
|
||||
const metadataContent = this.extractProfileMetadataContent(metadata)
|
||||
|
||||
if (metadataContent?.nip05) {
|
||||
const nip05Profile = await queryNip05(metadataContent.nip05)
|
||||
|
||||
if (nip05Profile && nip05Profile.pubkey === hexKey) {
|
||||
userRelays.push(...nip05Profile.relays)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (userRelays.length === 0) return null
|
||||
|
||||
// filter for finding user's first kind 1 event
|
||||
const eventFilter: Filter = {
|
||||
kinds: [kinds.ShortTextNote],
|
||||
authors: [hexKey]
|
||||
}
|
||||
|
||||
const pool = new SimplePool()
|
||||
|
||||
// find user's kind 1 events published on user's relays
|
||||
const events = await pool.querySync(userRelays, eventFilter)
|
||||
if (events && events.length) {
|
||||
// sort events by created_at time in ascending order
|
||||
events.sort((a, b) => a.created_at - b.created_at)
|
||||
|
||||
// get first ever event published on user's relays
|
||||
const event = events[0]
|
||||
const { created_at } = event
|
||||
|
||||
// initialize job request
|
||||
const jobEventTemplate: EventTemplate = {
|
||||
content: '',
|
||||
created_at: Math.round(Date.now() / 1000),
|
||||
kind: 68001,
|
||||
tags: [
|
||||
['i', `${created_at * 1000}`],
|
||||
['j', 'blockChain-block-number']
|
||||
]
|
||||
}
|
||||
|
||||
// sign job request event
|
||||
const jobSignedEvent = await this.nostrController.signEvent(
|
||||
jobEventTemplate
|
||||
)
|
||||
|
||||
const relays = [
|
||||
'wss://relay.damus.io',
|
||||
'wss://relay.primal.net',
|
||||
'wss://relayable.org'
|
||||
]
|
||||
|
||||
// publish job request
|
||||
await this.nostrController.publishEvent(jobSignedEvent, relays)
|
||||
|
||||
console.log('jobSignedEvent :>> ', jobSignedEvent)
|
||||
|
||||
const subscribeWithTimeout = (
|
||||
subscription: NDKSubscription,
|
||||
timeoutMs: number
|
||||
): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const eventHandler = (event: NDKEvent) => {
|
||||
subscription.stop()
|
||||
resolve(event.content)
|
||||
}
|
||||
|
||||
subscription.on('event', eventHandler)
|
||||
|
||||
// Set up a timeout to stop the subscription after a specified time
|
||||
const timeout = setTimeout(() => {
|
||||
subscription.stop() // Stop the subscription
|
||||
reject(new Error('Subscription timed out')) // Reject the promise with a timeout error
|
||||
}, timeoutMs)
|
||||
|
||||
// Handle subscription close event
|
||||
subscription.on('close', () => clearTimeout(timeout))
|
||||
})
|
||||
}
|
||||
|
||||
const dvmNDK = new NDK({
|
||||
explicitRelayUrls: relays
|
||||
})
|
||||
|
||||
await dvmNDK.connect(2000)
|
||||
|
||||
// filter for getting DVM job's result
|
||||
const sub = dvmNDK.subscribe({
|
||||
kinds: [68002 as number],
|
||||
'#e': [jobSignedEvent.id],
|
||||
'#p': [jobSignedEvent.pubkey]
|
||||
})
|
||||
|
||||
// asynchronously get block number from dvm job with 20 seconds timeout
|
||||
const dvmJobResult = await subscribeWithTimeout(sub, 20000)
|
||||
|
||||
const encodedEventPointer = nip19.neventEncode({
|
||||
id: event.id,
|
||||
relays: userRelays,
|
||||
author: event.pubkey,
|
||||
kind: event.kind
|
||||
})
|
||||
|
||||
return {
|
||||
block: parseInt(dvmJobResult),
|
||||
encodedEventPointer
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
public validate = (event: Event) => validateEvent(event) && verifyEvent(event)
|
||||
}
|
||||
|
@ -1,12 +1,20 @@
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
|
||||
import { CircularProgress, List, ListItem, ListSubheader, TextField } from '@mui/material'
|
||||
import {
|
||||
CircularProgress,
|
||||
List,
|
||||
ListItem,
|
||||
ListSubheader,
|
||||
TextField,
|
||||
Typography,
|
||||
useTheme
|
||||
} from '@mui/material'
|
||||
import { UnsignedEvent, nip19, kinds, VerifiedEvent } from 'nostr-tools'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import { toast } from 'react-toastify'
|
||||
import placeholderAvatar from '../../assets/images/nostr-logo.jpg'
|
||||
import { MetadataController, NostrController } from '../../controllers'
|
||||
import { ProfileMetadata } from '../../types'
|
||||
import { NostrJoiningBlock, ProfileMetadata } from '../../types'
|
||||
import styles from './style.module.scss'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { State } from '../../store/rootReducer'
|
||||
@ -19,6 +27,8 @@ import { ApiController } from '../../controllers/ApiController'
|
||||
import { PostMediaResponse } from '../../types/media'
|
||||
|
||||
export const ProfilePage = () => {
|
||||
const theme = useTheme()
|
||||
|
||||
const { npub } = useParams()
|
||||
|
||||
const dispatch: Dispatch = useDispatch()
|
||||
@ -27,6 +37,8 @@ export const ProfilePage = () => {
|
||||
const nostrController = NostrController.getInstance()
|
||||
|
||||
const [pubkey, setPubkey] = useState<string>()
|
||||
const [nostrJoiningBlock, setNostrJoiningBlock] =
|
||||
useState<NostrJoiningBlock | null>(null)
|
||||
const [profileMetadata, setProfileMetadata] = useState<ProfileMetadata>()
|
||||
const [savingProfileMetadata, setSavingProfileMetadata] = useState(false)
|
||||
const [avatarLoading, setAvatarLoading] = useState(false)
|
||||
@ -54,6 +66,18 @@ export const ProfilePage = () => {
|
||||
}, [npub, usersPubkey])
|
||||
|
||||
useEffect(() => {
|
||||
if (pubkey) {
|
||||
metadataController
|
||||
.getNostrJoiningBlockNumber(pubkey)
|
||||
.then((res) => {
|
||||
setNostrJoiningBlock(res)
|
||||
})
|
||||
.catch((err) => {
|
||||
// todo: handle error
|
||||
console.log('err :>> ', err)
|
||||
})
|
||||
}
|
||||
|
||||
if (isUsersOwnProfile && metadataState) {
|
||||
const metadataContent = metadataController.extractProfileMetadataContent(
|
||||
metadataState as VerifiedEvent
|
||||
@ -283,8 +307,7 @@ export const ProfilePage = () => {
|
||||
sx={{
|
||||
marginTop: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 2
|
||||
flexDirection: 'column'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
@ -312,6 +335,21 @@ export const ProfilePage = () => {
|
||||
}}
|
||||
focused
|
||||
/>
|
||||
|
||||
{nostrJoiningBlock && (
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.getContrastText(
|
||||
theme.palette.background.paper
|
||||
)
|
||||
}}
|
||||
component={Link}
|
||||
to={`https://njump.me/${nostrJoiningBlock.encodedEventPointer}`}
|
||||
target='_blank'
|
||||
>
|
||||
On nostr since {nostrJoiningBlock.block.toLocaleString()}
|
||||
</Typography>
|
||||
)}
|
||||
</ListItem>
|
||||
|
||||
{editItem('name', 'Username')}
|
||||
|
@ -12,3 +12,8 @@ export interface RelaySet {
|
||||
read: string[]
|
||||
write: string[]
|
||||
}
|
||||
|
||||
export interface NostrJoiningBlock {
|
||||
block: number
|
||||
encodedEventPointer: string
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ export const sendDM = async (
|
||||
? 'Your signature is requested on the document below!'
|
||||
: 'You have received a signed document.'
|
||||
|
||||
const decryptionUrl = `${window.location.origin}/#${appPrivateRoutes.decryptZip}?file=${fileUrl}&key=${encryptionKey}`
|
||||
const decryptionUrl = `${window.location.origin}/#${appPrivateRoutes.decryptZip}?file=${encodeURIComponent(fileUrl)}&key=${encodeURIComponent(encryptionKey)}`
|
||||
|
||||
const content = `${initialLine}\n\n${decryptionUrl}\n\nDirect download${fileUrl}`
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user