Compare commits
No commits in common. "e4791562705656fdcb55b772a84e9bbaa523253d" and "58408eb309be741b3459f75fe689f086606244e3" have entirely different histories.
e479156270
...
58408eb309
@ -5,15 +5,11 @@ import {
|
|||||||
kinds,
|
kinds,
|
||||||
validateEvent,
|
validateEvent,
|
||||||
verifyEvent,
|
verifyEvent,
|
||||||
Event,
|
Event
|
||||||
EventTemplate,
|
|
||||||
nip19
|
|
||||||
} from 'nostr-tools'
|
} from 'nostr-tools'
|
||||||
import { NostrJoiningBlock, ProfileMetadata, RelaySet } from '../types'
|
import { ProfileMetadata, RelaySet } from '../types'
|
||||||
import { NostrController } from '.'
|
import { NostrController } from '.'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { queryNip05 } from '../utils'
|
|
||||||
import NDK, { NDKEvent, NDKSubscription } from '@nostr-dev-kit/ndk'
|
|
||||||
|
|
||||||
export class MetadataController {
|
export class MetadataController {
|
||||||
private nostrController: NostrController
|
private nostrController: NostrController
|
||||||
@ -167,130 +163,5 @@ 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)
|
public validate = (event: Event) => validateEvent(event) && verifyEvent(event)
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,12 @@
|
|||||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
|
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
|
||||||
import {
|
import { List, ListItem, ListSubheader, TextField } from '@mui/material'
|
||||||
List,
|
|
||||||
ListItem,
|
|
||||||
ListSubheader,
|
|
||||||
TextField,
|
|
||||||
Typography,
|
|
||||||
useTheme
|
|
||||||
} from '@mui/material'
|
|
||||||
import { UnsignedEvent, nip19, kinds, VerifiedEvent } from 'nostr-tools'
|
import { UnsignedEvent, nip19, kinds, VerifiedEvent } from 'nostr-tools'
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import { Link, useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import placeholderAvatar from '../../assets/images/nostr-logo.jpg'
|
import placeholderAvatar from '../../assets/images/nostr-logo.jpg'
|
||||||
import { MetadataController, NostrController } from '../../controllers'
|
import { MetadataController, NostrController } from '../../controllers'
|
||||||
import { NostrJoiningBlock, ProfileMetadata } from '../../types'
|
import { ProfileMetadata } from '../../types'
|
||||||
import styles from './style.module.scss'
|
import styles from './style.module.scss'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import { State } from '../../store/rootReducer'
|
import { State } from '../../store/rootReducer'
|
||||||
@ -24,8 +17,6 @@ import { LoadingSpinner } from '../../components/LoadingSpinner'
|
|||||||
import { LoginMethods } from '../../store/auth/types'
|
import { LoginMethods } from '../../store/auth/types'
|
||||||
|
|
||||||
export const ProfilePage = () => {
|
export const ProfilePage = () => {
|
||||||
const theme = useTheme()
|
|
||||||
|
|
||||||
const { npub } = useParams()
|
const { npub } = useParams()
|
||||||
|
|
||||||
const dispatch: Dispatch = useDispatch()
|
const dispatch: Dispatch = useDispatch()
|
||||||
@ -34,8 +25,6 @@ export const ProfilePage = () => {
|
|||||||
const nostrController = NostrController.getInstance()
|
const nostrController = NostrController.getInstance()
|
||||||
|
|
||||||
const [pubkey, setPubkey] = useState<string>()
|
const [pubkey, setPubkey] = useState<string>()
|
||||||
const [nostrJoiningBlock, setNostrJoiningBlock] =
|
|
||||||
useState<NostrJoiningBlock | null>(null)
|
|
||||||
const [profileMetadata, setProfileMetadata] = useState<ProfileMetadata>()
|
const [profileMetadata, setProfileMetadata] = useState<ProfileMetadata>()
|
||||||
const [savingProfileMetadata, setSavingProfileMetadata] = useState(false)
|
const [savingProfileMetadata, setSavingProfileMetadata] = useState(false)
|
||||||
const metadataState = useSelector((state: State) => state.metadata)
|
const metadataState = useSelector((state: State) => state.metadata)
|
||||||
@ -61,18 +50,6 @@ export const ProfilePage = () => {
|
|||||||
}, [npub, usersPubkey])
|
}, [npub, usersPubkey])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pubkey) {
|
|
||||||
metadataController
|
|
||||||
.getNostrJoiningBlockNumber(pubkey)
|
|
||||||
.then((res) => {
|
|
||||||
setNostrJoiningBlock(res)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
// todo: handle error
|
|
||||||
console.log('err :>> ', err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isUsersOwnProfile && metadataState) {
|
if (isUsersOwnProfile && metadataState) {
|
||||||
const metadataContent = metadataController.extractProfileMetadataContent(
|
const metadataContent = metadataController.extractProfileMetadataContent(
|
||||||
metadataState as VerifiedEvent
|
metadataState as VerifiedEvent
|
||||||
@ -229,7 +206,8 @@ export const ProfilePage = () => {
|
|||||||
sx={{
|
sx={{
|
||||||
marginTop: 1,
|
marginTop: 1,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column'
|
flexDirection: 'column',
|
||||||
|
gap: 2
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@ -240,20 +218,6 @@ export const ProfilePage = () => {
|
|||||||
src={profileMetadata.picture || placeholderAvatar}
|
src={profileMetadata.picture || placeholderAvatar}
|
||||||
alt='Profile Image'
|
alt='Profile Image'
|
||||||
/>
|
/>
|
||||||
{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>
|
</ListItem>
|
||||||
|
|
||||||
{editItem('name', 'Username')}
|
{editItem('name', 'Username')}
|
||||||
|
@ -12,8 +12,3 @@ export interface RelaySet {
|
|||||||
read: string[]
|
read: string[]
|
||||||
write: string[]
|
write: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NostrJoiningBlock {
|
|
||||||
block: number
|
|
||||||
encodedEventPointer: string
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user