mirror of
https://github.com/styppo/hamstr.git
synced 2024-10-18 05:23:28 +00:00
* Add profile settings
* Add NIP05 support
This commit is contained in:
parent
18ea594080
commit
efc9af8bc1
@ -79,13 +79,13 @@ md.use(subscript)
|
|||||||
trimmed.endsWith('.jpg') ||
|
trimmed.endsWith('.jpg') ||
|
||||||
trimmed.endsWith('.svg')
|
trimmed.endsWith('.svg')
|
||||||
) {
|
) {
|
||||||
return `<img src="${src}" crossorigin async loading='lazy' style="max-width: 90%; max-height: 50vh;">`
|
return `<img src="${src}" loading='lazy' style="max-width: 90%; max-height: 50vh;">`
|
||||||
} else if (
|
} else if (
|
||||||
trimmed.endsWith('.mp4') ||
|
trimmed.endsWith('.mp4') ||
|
||||||
trimmed.endsWith('.webm') ||
|
trimmed.endsWith('.webm') ||
|
||||||
trimmed.endsWith('.ogg')
|
trimmed.endsWith('.ogg')
|
||||||
) {
|
) {
|
||||||
return `<video src="${src}" controls crossorigin async style="max-width: 90%; max-height: 50vh;"></video>`
|
return `<video src="${src}" controls style="max-width: 90%; max-height: 50vh;"></video>`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
182
src/components/Settings/ProfileSettings.vue
Normal file
182
src/components/Settings/ProfileSettings.vue
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
<template>
|
||||||
|
<!-- <div class="profile-card">-->
|
||||||
|
<!-- <UserAvatar :pubkey="pubkey" class="profile-card-avatar" />-->
|
||||||
|
<!-- <div class="profile-card-content">-->
|
||||||
|
<!-- <p><UserName :pubkey="pubkey" two-line header show-verified /></p>-->
|
||||||
|
<!-- <p class="about">{{ about }}</p>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<q-form class="profile-settings">
|
||||||
|
<h3>Profile</h3>
|
||||||
|
<div class="input">
|
||||||
|
<q-input v-model="name" label="Name" maxlength="64" autogrow dense />
|
||||||
|
</div>
|
||||||
|
<div class="input">
|
||||||
|
<q-input v-model="about" label="About" maxlength="150" autogrow dense />
|
||||||
|
</div>
|
||||||
|
<div class="input">
|
||||||
|
<q-input v-model="picture" label="Picture URL" autogrow dense />
|
||||||
|
<img :src="picture" class="picture-preview" loading="lazy" />
|
||||||
|
</div>
|
||||||
|
<div class="input">
|
||||||
|
<q-input v-model="nip05" label="NIP05 Identifier" autogrow dense />
|
||||||
|
</div>
|
||||||
|
<div class="buttons">
|
||||||
|
<q-btn type="submit" :disable="!changed" label="Save" flat rounded color="primary" />
|
||||||
|
<q-btn label="Reset" :disable="!changed" flat rounded @click="updateData" />
|
||||||
|
<!-- <button type="submit" class="btn btn-sm btn-primary">Save</button>-->
|
||||||
|
<!-- <button class="btn btn-sm" @click="updateData">Reset</button>-->
|
||||||
|
</div>
|
||||||
|
</q-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {useNostrStore} from 'src/nostr/NostrStore'
|
||||||
|
import {useAppStore} from 'stores/App'
|
||||||
|
// import UserAvatar from 'components/User/UserAvatar.vue'
|
||||||
|
// import UserName from 'components/User/UserName.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ProfileSettings',
|
||||||
|
components: {},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
app: useAppStore(),
|
||||||
|
nostr: useNostrStore(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
name: '',
|
||||||
|
about: '',
|
||||||
|
picture: '',
|
||||||
|
nip05: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
pubkey() {
|
||||||
|
return this.app.myPubkey
|
||||||
|
},
|
||||||
|
profile() {
|
||||||
|
return this.nostr.getProfile(this.pubkey)
|
||||||
|
},
|
||||||
|
changed() {
|
||||||
|
return this.name !== (this.profile?.name || '')
|
||||||
|
|| this.about !== (this.profile?.about || '')
|
||||||
|
|| this.picture !== (this.profile?.picture || '')
|
||||||
|
|| this.nip05 !== (this.profile?.nip05?.url || '')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateData() {
|
||||||
|
this.name = (this.profile?.name || '')
|
||||||
|
this.about = (this.profile?.about || '')
|
||||||
|
this.picture = (this.profile?.picture || '')
|
||||||
|
this.nip05 = (this.profile?.nip05?.url || '')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
profile() {
|
||||||
|
this.updateData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.updateData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "assets/theme/colors.scss";
|
||||||
|
|
||||||
|
//.profile-card {
|
||||||
|
// display: flex;
|
||||||
|
// border-radius: 1rem;
|
||||||
|
// background-color: rgba($color: $color-dark-gray, $alpha: 0.2);
|
||||||
|
// padding: .5rem 1rem;
|
||||||
|
// margin-bottom: 1rem;
|
||||||
|
// &-avatar {
|
||||||
|
// height: 128px;
|
||||||
|
// width: 128px;
|
||||||
|
// margin-right: 1rem;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
.profile-settings {
|
||||||
|
background-color: rgba($color: $color-dark-gray, $alpha: 0.1);
|
||||||
|
border-radius: 1rem;
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
padding: 1rem;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
border-bottom: $border-dark;
|
||||||
|
}
|
||||||
|
.input {
|
||||||
|
position: relative;
|
||||||
|
//padding: .5rem .5rem .5rem 1rem;
|
||||||
|
transition: 200ms ease;
|
||||||
|
//border-bottom: $border-dark;
|
||||||
|
//input {
|
||||||
|
// color: #fff;
|
||||||
|
// font-weight: 500;
|
||||||
|
// width: 100%;
|
||||||
|
// outline: none;
|
||||||
|
// background-color: transparent;
|
||||||
|
// border: 0;
|
||||||
|
// padding: 0;
|
||||||
|
//}
|
||||||
|
&:hover, &.focused {
|
||||||
|
background-color: rgba($color: $color-light-gray, $alpha: 0.2);
|
||||||
|
}
|
||||||
|
&:first-child {
|
||||||
|
border-radius: 1rem 1rem 0 0;
|
||||||
|
}
|
||||||
|
.picture-preview {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
background-color: rgba($color: $color-dark-gray, $alpha: 0.15);
|
||||||
|
border-radius: 0 0 1rem 1rem;
|
||||||
|
padding: .35rem;
|
||||||
|
button {
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
button + button {
|
||||||
|
margin-right: .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="scss">
|
||||||
|
@import "assets/theme/colors.scss";
|
||||||
|
|
||||||
|
.profile-settings .input {
|
||||||
|
.q-field__label {
|
||||||
|
color: $color-light-gray;
|
||||||
|
margin: 0 1rem;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
color: #fff;
|
||||||
|
padding: 0 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.q-field__control:before {
|
||||||
|
border-bottom: $border-dark;
|
||||||
|
}
|
||||||
|
.q-field__control-container {
|
||||||
|
padding-top: 20px !important;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
}
|
||||||
|
.q-field--dense .q-field__label {
|
||||||
|
top: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="relay-settings">
|
<div class="relay-settings">
|
||||||
|
<h3>Relays</h3>
|
||||||
<div v-for="relay in settings.relays" :key="relay" class="relay">
|
<div v-for="relay in settings.relays" :key="relay" class="relay">
|
||||||
<span class="relay-url">{{ relay }}</span>
|
<span class="relay-url">{{ relay }}</span>
|
||||||
<!-- <q-icon v-if="isConnected(relay)" icon="fiber_manual_record" size="sm" class="connected" />-->
|
<!-- <q-icon v-if="isConnected(relay)" icon="fiber_manual_record" size="sm" class="connected" />-->
|
||||||
@ -85,6 +86,12 @@ export default {
|
|||||||
.relay-settings {
|
.relay-settings {
|
||||||
background-color: rgba($color: $color-dark-gray, $alpha: 0.1);
|
background-color: rgba($color: $color-dark-gray, $alpha: 0.1);
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
padding: 1rem;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
border-bottom: $border-dark;
|
||||||
|
}
|
||||||
.relay {
|
.relay {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -122,6 +129,7 @@ export default {
|
|||||||
outline: none;
|
outline: none;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
&:hover, &.focused {
|
&:hover, &.focused {
|
||||||
background-color: rgba($color: $color-light-gray, $alpha: 0.2);
|
background-color: rgba($color: $color-light-gray, $alpha: 0.2);
|
||||||
|
@ -1,44 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-dialog v-model="NIP05Dialog">
|
<span v-if="verified" class="nip05-badge">
|
||||||
<q-card class="flex column no-wrap" style="max-height: 90%">
|
<q-icon name="verified" :size="size" color="primary">
|
||||||
<div class="flex row justify-end">
|
<q-tooltip>NIP05 verified</q-tooltip>
|
||||||
<q-btn icon="close" flat dense v-close-popup/>
|
</q-icon>
|
||||||
</div>
|
<span class="nip05-badge-text">{{ nip05 }}</span>
|
||||||
<div class="overflow-auto">
|
</span>
|
||||||
<q-card-section>
|
|
||||||
<div class="text-subtitle1 flex row overflow-auto items-end q-gutter-sm">
|
|
||||||
NIP05 identifier
|
|
||||||
<a :href="NIP05Link" target="_">{{ NIP05Link }}</a>
|
|
||||||
</div>
|
|
||||||
<pre v-if="NIP05Loaded">{{ NIP05Formatted }}</pre>
|
|
||||||
<q-inner-loading :showing="!NIP05Loaded">
|
|
||||||
<q-spinner-orbit color="accent" size="2rem" />
|
|
||||||
</q-inner-loading>
|
|
||||||
<div>
|
|
||||||
Learn how to get NIP05 verified <a href="https://gist.github.com/metasikander/609a538e6a03b2f67e5c8de625baed3e" target='_'>here</a>
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
|
||||||
</div>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
<q-btn
|
|
||||||
v-if="$store.getters.NIP05Id(pubkey)"
|
|
||||||
icon="verified"
|
|
||||||
flat
|
|
||||||
dense
|
|
||||||
:size="size"
|
|
||||||
class="no-padding"
|
|
||||||
clickable
|
|
||||||
@click.stop="openNIP05"
|
|
||||||
>
|
|
||||||
<q-tooltip>
|
|
||||||
NIP05 verified
|
|
||||||
</q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import fetch from 'cross-fetch'
|
import {useNostrStore} from 'src/nostr/NostrStore'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Nip05Badge',
|
name: 'Nip05Badge',
|
||||||
@ -49,51 +19,47 @@ export default {
|
|||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'xs'
|
default: '14px'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
nostr: useNostrStore(),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
NIP05Dialog: false,
|
verified: false,
|
||||||
NIP05Data: {},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
NIP05Link() {
|
profile() {
|
||||||
// let [name, domain] = this.$store.getters
|
return this.nostr.getProfile(this.pubkey)
|
||||||
// .NIP05Id(this.pubkey)
|
|
||||||
// .split('@')
|
|
||||||
// if (!domain) {
|
|
||||||
// domain = name
|
|
||||||
// name = '_'
|
|
||||||
// }
|
|
||||||
// return `https://${domain}/.well-known/nostr.json?name=${name}`
|
|
||||||
return 'TODO'
|
|
||||||
},
|
},
|
||||||
NIP05Formatted() {
|
nip05() {
|
||||||
return this.json(this.NIP05Data)
|
if (!this.profile?.nip05.url) return
|
||||||
},
|
return this.profile.nip05.url
|
||||||
NIP05Loaded() {
|
.split('@')
|
||||||
if (Object.keys(this.NIP05Data).length) return true
|
.filter(part => part !== '_' && part !== this.profile.name)
|
||||||
return false
|
.join('@')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
methods: {
|
async profile() {
|
||||||
openNIP05() {
|
this.verified = await this.profile?.isNip05Verified()
|
||||||
this.loadNIP05Data()
|
}
|
||||||
this.NIP05Dialog = !this.NIP05Dialog
|
},
|
||||||
},
|
async mounted() {
|
||||||
|
this.verified = await this.profile?.isNip05Verified()
|
||||||
async loadNIP05Data() {
|
|
||||||
try {
|
|
||||||
this.NIP05Data = await (await fetch(this.NIP05Link)).json()
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(`Failed to fetch NIP05 identifier ${this.NIP05Link}`, e)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.nip05-badge {
|
||||||
|
font-size: 12px;
|
||||||
|
&-text {
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
:src="avatarUrl"
|
:src="avatarUrl"
|
||||||
ref="image"
|
ref="image"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
crossorigin
|
|
||||||
@error.once="onFetchFailed"
|
@error.once="onFetchFailed"
|
||||||
/>
|
/>
|
||||||
<Identicon
|
<Identicon
|
||||||
|
@ -4,7 +4,9 @@
|
|||||||
:class="{'two-line': twoLine, clickable, header}"
|
:class="{'two-line': twoLine, clickable, header}"
|
||||||
>
|
>
|
||||||
<a @click="clickable && goToProfile(pubkey)">
|
<a @click="clickable && goToProfile(pubkey)">
|
||||||
<span v-if="profile?.name" class="name">{{ profile.name }}</span>
|
<span v-if="profile?.name" class="name">
|
||||||
|
{{ profile.name }}
|
||||||
|
</span>
|
||||||
<!-- <q-icon v-if="showFollowing && isFollow" name="visibility" color="secondary">-->
|
<!-- <q-icon v-if="showFollowing && isFollow" name="visibility" color="secondary">-->
|
||||||
<!-- <q-tooltip>-->
|
<!-- <q-tooltip>-->
|
||||||
<!-- following-->
|
<!-- following-->
|
||||||
@ -13,12 +15,7 @@
|
|||||||
<Bech32Label v-if="twoLine || !profile?.name" prefix="npub" :hex="pubkey" class="pubkey" />
|
<Bech32Label v-if="twoLine || !profile?.name" prefix="npub" :hex="pubkey" class="pubkey" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<span v-if="showVerified && profile?.nip05?.verified">
|
<Nip05Badge :pubkey="pubkey" />
|
||||||
<Nip05Badge :pubkey="pubkey" />
|
|
||||||
<span style="opacity: .9; font-size: 90%; font-weight: 300; line-height: 90%">
|
|
||||||
{{ niceNip05 }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -57,7 +54,7 @@ export default {
|
|||||||
},
|
},
|
||||||
showVerified: {
|
showVerified: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
@ -69,17 +66,11 @@ export default {
|
|||||||
profile() {
|
profile() {
|
||||||
return this.nostr.getProfile(this.pubkey)
|
return this.nostr.getProfile(this.pubkey)
|
||||||
},
|
},
|
||||||
niceNip05() {
|
|
||||||
return this.profile.nip05.url
|
|
||||||
.split('@')
|
|
||||||
.filter(part => part !== '_' && part !== this.profile.name)
|
|
||||||
.join('@')
|
|
||||||
},
|
|
||||||
isFollow() {
|
isFollow() {
|
||||||
// FIXME
|
// FIXME
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ export default class FetchQueue extends Observable {
|
|||||||
delete this.queue[id]
|
delete this.queue[id]
|
||||||
filteredIds.splice(filteredIds.indexOf(id), 1)
|
filteredIds.splice(filteredIds.indexOf(id), 1)
|
||||||
|
|
||||||
console.log(`Fetched ${this.subId} ${id}, ${filteredIds.length} remaining`)
|
// console.log(`Fetched ${this.subId} ${id}, ${filteredIds.length} remaining`)
|
||||||
|
|
||||||
this.emit('event', event, relay)
|
this.emit('event', event, relay)
|
||||||
|
|
||||||
|
@ -68,6 +68,8 @@ export const useNostrStore = defineStore('nostr', {
|
|||||||
|
|
||||||
this.contactQueue = contactQueue(this.client, 'queue')
|
this.contactQueue = contactQueue(this.client, 'queue')
|
||||||
this.contactQueue.on('event', this.addEvent.bind(this))
|
this.contactQueue.on('event', this.addEvent.bind(this))
|
||||||
|
|
||||||
|
this.getProfiles(Object.keys(settings.accounts))
|
||||||
},
|
},
|
||||||
|
|
||||||
addEvent(event, relay = null) {
|
addEvent(event, relay = null) {
|
||||||
@ -132,10 +134,18 @@ export const useNostrStore = defineStore('nostr', {
|
|||||||
return profile
|
return profile
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getProfiles(pubkeys) {
|
||||||
|
const profiles = []
|
||||||
|
for (const pubkey of pubkeys) {
|
||||||
|
profiles.push(this.getProfile(pubkey))
|
||||||
|
}
|
||||||
|
return profiles
|
||||||
|
},
|
||||||
|
|
||||||
getNote(id) {
|
getNote(id) {
|
||||||
const notes = useNoteStore()
|
const notes = useNoteStore()
|
||||||
let note = notes.get(id)
|
let note = notes.get(id)
|
||||||
if (!note) this.noteQueue.add(id)
|
if (!note && !this.hasEvent(id)) this.noteQueue.add(id)
|
||||||
return note
|
return note
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -152,8 +162,7 @@ export const useNostrStore = defineStore('nostr', {
|
|||||||
return notes.getNotesByAuthor(pubkey, order)
|
return notes.getNotesByAuthor(pubkey, order)
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchNotesByAuthor(pubkey, opts = {}) {
|
fetchNotesByAuthor(pubkey, limit = 100) {
|
||||||
const limit = opts.limit || 100
|
|
||||||
return this.fetchMultiple(
|
return this.fetchMultiple(
|
||||||
{
|
{
|
||||||
kinds: [EventKind.NOTE],
|
kinds: [EventKind.NOTE],
|
||||||
@ -320,11 +329,12 @@ export const useNostrStore = defineStore('nostr', {
|
|||||||
|
|
||||||
const sub = this.client.subscribe(filtersWithLimit, opts.subId || null)
|
const sub = this.client.subscribe(filtersWithLimit, opts.subId || null)
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
|
console.log(`[TIMEOUT] ${sub.subId}, intialFetchComplete=${initialFetchComplete}`)
|
||||||
if (!initialFetchComplete) {
|
if (!initialFetchComplete) {
|
||||||
initialFetchComplete = true
|
initialFetchComplete = true
|
||||||
if (initialFetchCompleteCallback) initialFetchCompleteCallback()
|
if (initialFetchCompleteCallback) initialFetchCompleteCallback()
|
||||||
}
|
}
|
||||||
}, opts.timeout || 5000)
|
}, opts.timeout || 3000)
|
||||||
sub.on('event', (event, relay) => {
|
sub.on('event', (event, relay) => {
|
||||||
const known = this.hasEvent(event.id)
|
const known = this.hasEvent(event.id)
|
||||||
const obj = this.addEvent(event, relay)
|
const obj = this.addEvent(event, relay)
|
||||||
@ -332,13 +342,18 @@ export const useNostrStore = defineStore('nostr', {
|
|||||||
|
|
||||||
if (eventCallback) eventCallback(obj, relay)
|
if (eventCallback) eventCallback(obj, relay)
|
||||||
|
|
||||||
if (++numEventsSeen >= initialFetchSize && !initialFetchComplete) {
|
// if (++numEventsSeen >= initialFetchSize && !initialFetchComplete) {
|
||||||
initialFetchComplete = true
|
// console.log(`[EVENTS_SEEN] ${sub.subId} ${numEventsSeen}, intialFetchComplete=${initialFetchComplete}`)
|
||||||
clearTimeout(timer)
|
// initialFetchComplete = true
|
||||||
if (initialFetchCompleteCallback) initialFetchCompleteCallback()
|
// clearTimeout(timer)
|
||||||
}
|
// if (initialFetchCompleteCallback) initialFetchCompleteCallback()
|
||||||
|
// }
|
||||||
|
})
|
||||||
|
sub.on('eose', (relay, subId) => {
|
||||||
|
console.log(`[EOSE] ${subId} ${relay} ${this.client.connectedRelays().length}`)
|
||||||
})
|
})
|
||||||
sub.on('complete', () => {
|
sub.on('complete', () => {
|
||||||
|
console.log(`[COMPLETE] ${sub.subId}, intialFetchComplete=${initialFetchComplete}`)
|
||||||
if (!initialFetchComplete) {
|
if (!initialFetchComplete) {
|
||||||
initialFetchComplete = true
|
initialFetchComplete = true
|
||||||
clearTimeout(timer)
|
clearTimeout(timer)
|
||||||
|
@ -59,10 +59,12 @@ class MultiSubscription extends Observable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class ReplayPool extends Observable {
|
export default class ReplayPool extends Observable {
|
||||||
constructor(urls) {
|
constructor(urls, minRelays = 5) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this.relays = {}
|
this.relays = {}
|
||||||
|
this.minRelays = Math.min(minRelays, urls.length)
|
||||||
|
|
||||||
this.subs = {}
|
this.subs = {}
|
||||||
this.nextSubId = 0
|
this.nextSubId = 0
|
||||||
|
|
||||||
@ -106,9 +108,15 @@ export default class ReplayPool extends Observable {
|
|||||||
sub.on('close', this.unsubscribe.bind(this, subId))
|
sub.on('close', this.unsubscribe.bind(this, subId))
|
||||||
|
|
||||||
this.subs[subId] = {sub, filters, closeAfter}
|
this.subs[subId] = {sub, filters, closeAfter}
|
||||||
for (const relay of this.connectedRelays()) {
|
const connectedRelays = this.connectedRelays()
|
||||||
sub.add(relay.subscribe(filters, subId, closeAfter))
|
if (connectedRelays.length >= this.minRelays) {
|
||||||
|
for (const relay of connectedRelays) {
|
||||||
|
sub.add(relay.subscribe(filters, subId, closeAfter))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.subs[subId].pending = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return sub
|
return sub
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,12 +143,28 @@ export default class ReplayPool extends Observable {
|
|||||||
return Object.values(this.relays).filter(relay => relay.isConnected())
|
return Object.values(this.relays).filter(relay => relay.isConnected())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
numConnectedRelays() {
|
||||||
|
return this.connectedRelays().length
|
||||||
|
}
|
||||||
|
|
||||||
onOpen(relay) {
|
onOpen(relay) {
|
||||||
console.log(`Connected to ${relay}`, relay)
|
console.log(`Connected to ${relay}`, relay)
|
||||||
|
|
||||||
for (const subId of Object.keys(this.subs)) {
|
for (const subId of Object.keys(this.subs)) {
|
||||||
const sub = this.subs[subId]
|
const sub = this.subs[subId]
|
||||||
sub.sub.add(relay.subscribe(sub.filters, subId, sub.closeAfter))
|
const connectedRelays = this.connectedRelays()
|
||||||
|
if (connectedRelays.length >= this.minRelays) {
|
||||||
|
if (sub.pending) {
|
||||||
|
sub.pending = false
|
||||||
|
for (const relay of connectedRelays) {
|
||||||
|
sub.sub.add(relay.subscribe(sub.filters, subId, sub.closeAfter))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sub.sub.add(relay.subscribe(sub.filters, subId, sub.closeAfter))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.emit('open', relay)
|
this.emit('open', relay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import {EventKind} from 'src/nostr/model/Event'
|
import {EventKind} from 'src/nostr/model/Event'
|
||||||
|
import Nip05 from 'src/utils/Nip05'
|
||||||
|
|
||||||
export default class Profile {
|
export default class Profile {
|
||||||
constructor(pubkey, lastUpdatedAt, metadata) {
|
constructor(pubkey, lastUpdatedAt, metadata) {
|
||||||
@ -10,7 +11,7 @@ export default class Profile {
|
|||||||
this.picture = metadata.picture
|
this.picture = metadata.picture
|
||||||
this.nip05 = {
|
this.nip05 = {
|
||||||
url: metadata.nip05,
|
url: metadata.nip05,
|
||||||
verified: false,
|
verified: null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,4 +25,20 @@ export default class Profile {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async isNip05Verified() {
|
||||||
|
if (this.nip05.verified !== null) {
|
||||||
|
return this.nip05.verified
|
||||||
|
}
|
||||||
|
if (!this.nip05.url) { // TODO more validation
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const pubkey = await Nip05.fetchPubkey(this.nip05.url)
|
||||||
|
this.nip05.verified = pubkey && pubkey === this.pubkey
|
||||||
|
} catch (e) {
|
||||||
|
this.nip05.verified = false
|
||||||
|
}
|
||||||
|
return this.nip05.verified
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<PageHeader />
|
<PageHeader />
|
||||||
<div class="settings">
|
<div class="settings">
|
||||||
<h3>Relays</h3>
|
<ProfileSettings />
|
||||||
<RelaySettings />
|
<RelaySettings />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -9,18 +9,23 @@
|
|||||||
<script>
|
<script>
|
||||||
import PageHeader from 'components/PageHeader.vue'
|
import PageHeader from 'components/PageHeader.vue'
|
||||||
import RelaySettings from 'components/Settings/RelaySettings.vue'
|
import RelaySettings from 'components/Settings/RelaySettings.vue'
|
||||||
|
import ProfileSettings from 'components/Settings/ProfileSettings.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
components: {
|
components: {
|
||||||
PageHeader,
|
PageHeader,
|
||||||
|
ProfileSettings,
|
||||||
RelaySettings
|
RelaySettings
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style lang="scss" scoped>
|
||||||
.settings {
|
.settings {
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
|
> * + * {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<strong>{{ contacts?.length || 0 }}</strong> Following
|
<strong>{{ contacts?.length || 0 }}</strong> Following
|
||||||
</a>
|
</a>
|
||||||
<a @click="goToFollowers('followers')">
|
<a @click="goToFollowers('followers')">
|
||||||
<strong>{{ followers?.length || 0 }}</strong> Followers
|
<strong>{{ `${followers?.length}+` || 0 }}</strong> Followers
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -151,8 +151,8 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// FIXME
|
// FIXME
|
||||||
this.nostr.fetchNotesByAuthor(this.pubkey)
|
this.nostr.fetchNotesByAuthor(this.pubkey, 50)
|
||||||
this.nostr.fetchReactionsByAuthor(this.pubkey, 100)
|
this.nostr.fetchReactionsByAuthor(this.pubkey, 50)
|
||||||
this.nostr.fetchFollowers(this.pubkey, 1000)
|
this.nostr.fetchFollowers(this.pubkey, 1000)
|
||||||
this.stream = this.nostr.streamFullProfile(this.pubkey)
|
this.stream = this.nostr.streamFullProfile(this.pubkey)
|
||||||
},
|
},
|
||||||
|
17
src/utils/Nip05.js
Normal file
17
src/utils/Nip05.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import fetch from 'cross-fetch'
|
||||||
|
|
||||||
|
export default class Nip05 {
|
||||||
|
static async fetchPubkey(nip05Id) {
|
||||||
|
const [user, host] = nip05Id.split('@')
|
||||||
|
const url = `https://${host}/.well-known/nostr.json?name=${user}`
|
||||||
|
try {
|
||||||
|
const res = await fetch(url)
|
||||||
|
const json = await res.json()
|
||||||
|
console.log('nip05 data', json)
|
||||||
|
return json.names[user]
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed to fetch NIP05 data for ${nip05Id}`, e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user