diff --git a/src/components/AsyncLoadButton.vue b/src/components/AsyncLoadButton.vue
new file mode 100644
index 0000000..add7477
--- /dev/null
+++ b/src/components/AsyncLoadButton.vue
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/ButtonLoadMore.vue b/src/components/ButtonLoadMore.vue
deleted file mode 100644
index 84ed04c..0000000
--- a/src/components/ButtonLoadMore.vue
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/src/components/Post/ListPost.vue b/src/components/Post/ListPost.vue
index 51dc0e5..5db1013 100644
--- a/src/components/Post/ListPost.vue
+++ b/src/components/Post/ListPost.vue
@@ -128,7 +128,7 @@ export default {
},
},
mounted() {
- const updateInterval = Date.now() / 1000 - this.note.createdAt >= 3600 // 1h
+ const updateInterval = DateUtils.now() - this.note.createdAt >= 3600 // 1h
? 3600 // 1h
: 60 // 1m
this.refreshTimer = setInterval(() => this.refreshCounter++, updateInterval * 1000)
diff --git a/src/nostr/FetchQueue.js b/src/nostr/FetchQueue.js
index 3a2229d..d81f787 100644
--- a/src/nostr/FetchQueue.js
+++ b/src/nostr/FetchQueue.js
@@ -5,21 +5,19 @@ export default class FetchQueue extends Observable {
super()
this.client = client
this.subId = subId
+ this.sub = null
this.fnGetId = fnGetId
this.fnCreateFilter = fnCreateFilter
this.throttle = opts.throttle || 250
- this.batchSize = opts.batchSize || 50
- this.retryDelay = opts.retryDelay || 5000
- this.maxRetries = opts.maxRetries || 3
+ this.batchSize = opts.batchSize || 250
+ this.timeout = opts.timeout || 3000
+ this.maxAttempts = opts.maxAttempts || 2
this.queue = {}
this.failed = {}
this.fetching = false
this.fetchQueued = false
- this.retryInterval = null
-
- // XXX
- setInterval(() => this.failed = {}, 10000)
+ this.fetchTimeout = null
}
add(id) {
@@ -37,16 +35,19 @@ export default class FetchQueue extends Observable {
fetch() {
this.fetchQueued = false
- if (this.retryInterval) clearInterval(this.retryInterval)
+ if (this.fetchTimeout) clearTimeout(this.fetchTimeout)
const ids = Object.keys(this.queue).slice(0, this.batchSize)
- if (!ids.length) return
+ if (!ids.length) {
+ this.fetching = false
+ return
+ }
// Remove ids that we have tried too many times.
const filteredIds = []
for (const id of ids) {
this.queue[id]++
- if (this.queue[id] > this.maxRetries) {
+ if (this.queue[id] > this.maxAttempts) {
console.warn(`Failed to fetch ${this.subId} ${id}`)
this.failed[id] = true
delete this.queue[id]
@@ -55,12 +56,15 @@ export default class FetchQueue extends Observable {
}
}
- if (!filteredIds.length) return
+ if (!filteredIds.length) {
+ this.fetching = false
+ return
+ }
- // console.log(`Fetching ${filteredIds.length}/${Object.keys(this.queue).length} ${this.subId}s`, ids)
+ console.log(`Fetching ${this.subId}s ${filteredIds.length}/${Object.keys(this.queue).length} `)
this.fetching = true
- this.retryInterval = setInterval(this.fetch.bind(this), this.retryDelay)
+ this.fetchTimeout = setTimeout(this.fetch.bind(this), this.timeout)
// XXX Needed for some relays?
//this.client.unsubscribe(this.subId)
@@ -78,19 +82,19 @@ export default class FetchQueue extends Observable {
this.emit('event', event, relay)
if (Object.keys(this.queue).length === 0) {
- if (this.retryInterval) clearInterval(this.retryInterval)
+ if (this.fetchTimeout) clearTimeout(this.fetchTimeout)
this.fetching = false
sub.close()
} else if (filteredIds.length === 0) {
this.fetch()
}
})
- sub.on('complete', () => {
+ sub.on('end', () => {
if (this.fetching && Object.keys(this.queue).length > 0) {
this.fetch()
} else {
console.log('[COMPLETE]', this)
- if (this.retryInterval) clearInterval(this.retryInterval)
+ if (this.fetchTimeout) clearTimeout(this.fetchTimeout)
this.fetching = false
sub.close()
}
diff --git a/src/nostr/NostrClient.js b/src/nostr/NostrClient.js
index eed402f..a8105d3 100644
--- a/src/nostr/NostrClient.js
+++ b/src/nostr/NostrClient.js
@@ -25,8 +25,8 @@ export default class NostrClient {
return this.connectedRelays().some(relay => relay.url === url)
}
- subscribe(filters, subId = null) {
- return this.pool.subscribe(filters, subId)
+ subscribe(filters, subId = null, closeAfter = CloseAfter.NEVER) {
+ return this.pool.subscribe(filters, subId, closeAfter)
}
unsubscribe(subId) {
@@ -37,47 +37,45 @@ export default class NostrClient {
return this.pool.publish(event)
}
- // fetchSingle(filters) {
- // const filtersWithLimit = Object.assign({}, filters, {limit: 1})
- // return new Promise(resolve => {
- // const sub = this.pool.subscribe(filters)
- // sub.on('event')
- // this.client.subscribe(
- // filtersWithLimit,
- // (event, relay) => {
- // resolve(this.addEvent(event, relay))
- // },
- // {
- // closeAfter: 'single'
- // }
- // )
- // })
- // }
-
- fetchMultiple(filters, limit = 100, timeout = 5000) {
+ fetch(filters, opts = {}) {
return new Promise(resolve => {
- const objects = {}
- const filtersWithLimit = Object.assign({}, filters, {limit})
- const sub = this.pool.subscribe(filtersWithLimit, null, CloseAfter.EOSE)
+ const events = {}
+ const sub = this.pool.subscribe(filters, opts.subId, CloseAfter.EOSE)
const timer = setTimeout(() => {
sub.close()
- resolve(Object.values(objects))
- }, timeout)
+ resolve(Object.values(events))
+ }, opts.timeout || 4000)
sub.on('event', event => {
- objects[event.id] = event
+ events[event.id] = event
})
- sub.on('complete', () => {
+ sub.on('end', () => {
sub.close()
clearTimeout(timer)
- resolve(Object.values(objects))
+ resolve(Object.values(events))
})
sub.on('close', () => {
clearTimeout(timer)
- resolve(Object.values(objects))
+ resolve(Object.values(events))
})
})
}
+ stream(filters, eventCallback, endCallback = () => {}, opts = {}) {
+ const events = {}
+ const sub = this.pool.subscribe(filters, opts.subId)
+ const timer = setTimeout(() => {
+ endCallback(Object.values(events))
+ }, opts.timeout || 5000)
+ sub.on('event', event => {
+ events[event.id] = event
+ })
+ sub.on('end', () => {
+ clearTimeout(timer)
+ endCallback(Object.values(events))
+ })
+ return sub
+ }
+
onNotice(relay, message) {
console.warn(`[NOTICE] from ${relay}: ${message}`)
}
diff --git a/src/nostr/NostrStore.js b/src/nostr/NostrStore.js
index 5d366c8..8138a7a 100644
--- a/src/nostr/NostrStore.js
+++ b/src/nostr/NostrStore.js
@@ -8,15 +8,20 @@ import {useProfileStore} from 'src/nostr/store/ProfileStore'
import {useContactStore} from 'src/nostr/store/ContactStore'
import {useSettingsStore} from 'stores/Settings'
import {useStatStore} from 'src/nostr/store/StatStore'
+import {Observable} from 'src/nostr/utils'
+import {CloseAfter} from 'src/nostr/Relay'
+import DateUtils from 'src/utils/DateUtils'
-export const Feeds = {
- GLOBAL: {
- name: 'global',
- filters: {
- kinds: [EventKind.NOTE], // TODO Deletions
- },
- initialFetchSize: 100,
- },
+class Stream extends Observable {
+ constructor(sub) {
+ super()
+ this.sub = sub
+ sub.on('close', this.emit.bind(this, 'close'))
+ }
+
+ close(relay = null) {
+ this.sub.close(relay)
+ }
}
const eventQueue = (client, subId) => new FetchQueue(
@@ -76,10 +81,10 @@ export const useNostrStore = defineStore('nostr', {
if (relay?.url) {
if (this.seenBy[event.id]) {
- this.seenBy[event.id][relay.url] = Date.now()
+ this.seenBy[event.id][relay.url] = DateUtils.now()
} else {
this.seenBy[event.id] = {
- [relay.url]: Date.now()
+ [relay.url]: DateUtils.now()
}
const stats = useStatStore()
@@ -107,7 +112,8 @@ export const useNostrStore = defineStore('nostr', {
case EventKind.DELETE:
break
case EventKind.SHARE:
- break
+ // TODO
+ return event
case EventKind.REACTION: {
const notes = useNoteStore()
return notes.addEvent(event)
@@ -162,7 +168,7 @@ export const useNostrStore = defineStore('nostr', {
},
fetchNotesByAuthor(pubkey, limit = 100) {
- return this.fetchMultiple(
+ return this.fetch(
{
kinds: [EventKind.NOTE],
authors: [pubkey],
@@ -187,7 +193,7 @@ export const useNostrStore = defineStore('nostr', {
fetchFollowers(pubkey, opts = {}) {
const limit = opts.limit || 500
- return this.fetchMultiple(
+ return this.fetch(
{
kinds: [EventKind.CONTACT],
'#p': [pubkey],
@@ -204,7 +210,7 @@ export const useNostrStore = defineStore('nostr', {
},
fetchReactionsTo(id, limit = 500) {
- return this.fetchMultiple(
+ return this.fetch(
{
kinds: [EventKind.REACTION],
'#e': [id],
@@ -221,7 +227,7 @@ export const useNostrStore = defineStore('nostr', {
},
fetchReactionsByAuthor(pubkey, limit = 500) {
- return this.fetchMultiple(
+ return this.fetch(
{
kinds: [EventKind.REACTION],
authors: [pubkey],
@@ -230,47 +236,98 @@ export const useNostrStore = defineStore('nostr', {
)
},
- async fetchMultiple(filters, limit = 100, timeout = 5000) {
- const events = await this.client.fetchMultiple(filters, limit, timeout)
- return events.map(event => this.addEvent(event))
+ async fetch(filters, opts = {}) {
+ return new Promise(resolve => {
+ const events = {}
+ const sub = this.client.subscribe(filters, opts.subId, CloseAfter.EOSE)
+
+ const timer = setTimeout(() => {
+ const values = Object.values(events)
+ console.log(`[TIMEOUT] fetch ${sub.subId} (${values.length})`, filters)
+ sub.close()
+ resolve(values)
+ }, opts.timeout || 4000)
+
+ sub.on('end', () => {
+ const values = Object.values(events)
+ console.log(`[COMPLETE] fetch ${sub.subId} (${values.length})`, filters)
+ sub.close()
+ clearTimeout(timer)
+ resolve(values)
+ })
+ sub.on('close', () => {
+ clearTimeout(timer)
+ resolve(Object.values(events))
+ })
+ sub.on('event', (event, relay) => {
+ const object = this.addEvent(event, relay)
+ if (!object) {
+ console.warn('Discarding event', event)
+ return
+ }
+ events[event.id] = object
+ })
+ })
},
- streamThread(rootId, eventCallback, initialFetchCompleteCallback) {
- return this.streamEvents(
+ stream(filters, opts = {}) {
+ let objects = {}
+ const sub = this.client.subscribe(filters, opts.subId)
+ const stream = new Stream(sub)
+
+ const timer = setTimeout(() => {
+ const values = Object.values(objects)
+ console.log(`[TIMEOUT] stream ${sub.subId} (${values.length})`, filters)
+ stream.emit('init', values)
+ objects = null
+ }, opts.timeout || 5000)
+
+ sub.on('end', () => {
+ clearTimeout(timer)
+ const values = Object.values(objects)
+ console.log(`[COMPLETE] stream ${sub.subId} (${values.length})`, filters)
+ stream.emit('init', values)
+ objects = null
+ })
+ sub.on('event', (event, relay) => {
+ const known = this.hasEvent(event.id)
+ const object = this.addEvent(event, relay)
+ if (!object) {
+ console.warn('Discarding event', event)
+ return
+ }
+ if (known) return
+
+ if (!objects) {
+ stream.emit('update', object, relay, stream)
+ } else {
+ objects[event.id] = object
+ }
+ })
+
+ return stream
+ },
+
+ streamThread(rootId) {
+ return this.stream(
{
- kinds: [EventKind.NOTE],
+ kinds: [EventKind.NOTE, EventKind.REACTION, EventKind.SHARE],
'#e': [rootId],
+ limit: 500,
},
- 500,
- eventCallback,
- initialFetchCompleteCallback,
{
subId: `thread:${rootId}`,
}
)
},
- streamFeed(feed, eventCallback, initialFetchCompleteCallback) {
- return this.streamEvents(
- feed.filters,
- feed.initialFetchSize,
- eventCallback,
- initialFetchCompleteCallback,
- {
- subId: feed.name,
- }
- )
- },
-
- streamNotifications(pubkey, eventCallback, initialFetchCompleteCallback) {
- return this.streamEvents(
+ streamNotifications(pubkey) {
+ return this.stream(
{
kinds: [EventKind.NOTE, EventKind.REACTION], // TODO SHARE, CONTACT
'#p': [pubkey],
+ limit: 50,
},
- 50,
- eventCallback,
- initialFetchCompleteCallback,
{
subId: `notifications:${pubkey}`,
}
@@ -315,39 +372,5 @@ export const useNostrStore = defineStore('nostr', {
this.client.unsubscribe(subId)
}
},
-
- streamEvents(filters, initialFetchSize, eventCallback, initialFetchCompleteCallback, opts) {
- const filtersWithLimit = Object.assign({}, filters, {limit: initialFetchSize})
-
- let initialFetchComplete = false
-
- const sub = this.client.subscribe(filtersWithLimit, opts.subId || null)
- const timer = setTimeout(() => {
- console.log(`[TIMEOUT] ${sub.subId}, intialFetchComplete=${initialFetchComplete}`)
- if (!initialFetchComplete) {
- initialFetchComplete = true
- if (initialFetchCompleteCallback) initialFetchCompleteCallback()
- }
- }, opts.timeout || 3000)
- sub.on('event', (event, relay) => {
- const known = this.hasEvent(event.id)
- const obj = this.addEvent(event, relay)
- if (!obj || known) return
- if (eventCallback) eventCallback(obj, relay)
- })
- sub.on('eose', (relay, subId) => {
- console.log(`[EOSE] ${subId} ${relay} ${this.client.connectedRelays().length}`)
- })
- sub.on('complete', () => {
- console.log(`[COMPLETE] ${sub.subId}, intialFetchComplete=${initialFetchComplete}`)
- if (!initialFetchComplete) {
- initialFetchComplete = true
- clearTimeout(timer)
- if (initialFetchCompleteCallback) initialFetchCompleteCallback()
- }
- })
-
- return sub
- }
},
})
diff --git a/src/nostr/RelayPool.js b/src/nostr/RelayPool.js
index c7ec167..880608a 100644
--- a/src/nostr/RelayPool.js
+++ b/src/nostr/RelayPool.js
@@ -45,7 +45,7 @@ class MultiSubscription extends Observable {
if (!sub.eoseSeen) {
sub.eoseSeen = true
if (Object.values(this.subs).every(sub => sub.eoseSeen)) {
- this.emit('complete', this.subId)
+ this.emit('end', this.subId)
}
}
}
@@ -103,6 +103,7 @@ export default class ReplayPool extends Observable {
subscribe(filters, subId = null, closeAfter = CloseAfter.NEVER) {
if (!subId) subId = `sub${this.nextSubId++}`
+ console.log(`[SUBSCRIBE] ${subId}`, filters)
const sub = new MultiSubscription(subId, [])
sub.on('close', this.unsubscribe.bind(this, subId))
@@ -123,6 +124,7 @@ export default class ReplayPool extends Observable {
unsubscribe(subId) {
const sub = this.subs[subId]
if (!sub) return
+ console.log(`[UNSUBSCRIBE] ${subId}`)
sub.sub.close()
delete this.subs[subId]
}
diff --git a/src/nostr/model/Event.js b/src/nostr/model/Event.js
index 9ac2980..302c1eb 100644
--- a/src/nostr/model/Event.js
+++ b/src/nostr/model/Event.js
@@ -1,4 +1,5 @@
import {getEventHash} from 'nostr-tools'
+import DateUtils from 'src/utils/DateUtils'
export const EventKind = {
METADATA: 0,
@@ -71,7 +72,7 @@ export default class Event {
}
static fresh(opts) {
- opts.createdAt = opts.createdAt || Math.floor(Date.now() / 1000)
+ opts.createdAt = opts.createdAt || DateUtils.now()
return new Event(opts)
}
diff --git a/src/nostr/store/ContactStore.js b/src/nostr/store/ContactStore.js
index 9ac12a7..307950f 100644
--- a/src/nostr/store/ContactStore.js
+++ b/src/nostr/store/ContactStore.js
@@ -20,7 +20,7 @@ export const useContactStore = defineStore('contact', {
const existingContacts = this.contacts[event.pubkey]
if (existingContacts && existingContacts.lastUpdatedAt >= event.createdAt) {
- return
+ return existingContacts
}
const newContacts = []
diff --git a/src/pages/Feed.vue b/src/pages/Feed.vue
index 80e8820..d18e77c 100644
--- a/src/pages/Feed.vue
+++ b/src/pages/Feed.vue
@@ -31,11 +31,10 @@
-
@@ -45,11 +44,11 @@
-
-
-
-
-
+
@@ -60,11 +59,23 @@ import PageHeader from 'components/PageHeader.vue'
import PostEditor from 'components/CreatePost/PostEditor.vue'
import Thread from 'components/Post/Thread.vue'
import BaseIcon from 'components/BaseIcon/index.vue'
-import ButtonLoadMore from 'components/ButtonLoadMore.vue'
+import AsyncLoadButton from 'components/AsyncLoadButton.vue'
import ListPlaceholder from 'components/ListPlaceholder.vue'
import {useAppStore} from 'stores/App'
-import {useNostrStore, Feeds} from 'src/nostr/NostrStore'
+import {useNostrStore} from 'src/nostr/NostrStore'
import Defer from 'src/utils/Defer'
+import {EventKind} from 'src/nostr/model/Event'
+import DateUtils from 'src/utils/DateUtils'
+
+const Feeds = {
+ global: {
+ name: 'global',
+ filters: {
+ kinds: [EventKind.NOTE], // TODO Deletions
+ limit: 20,
+ },
+ },
+}
const feedOrder = (a, b) => b[0].createdAt - a[0].createdAt
@@ -75,10 +86,10 @@ export default defineComponent({
PostEditor,
Thread,
BaseIcon,
- ButtonLoadMore,
+ AsyncLoadButton,
ListPlaceholder,
},
- mixins: [Defer()],
+ mixins: [Defer(2000)],
setup() {
return {
app: useAppStore(),
@@ -92,7 +103,6 @@ export default defineComponent({
selectedFeed: 'global',
loading: true,
recentlyLoaded: true,
- sub: null,
}
},
computed: {
@@ -114,57 +124,73 @@ export default defineComponent({
initFeed(feedId) {
if (this.feeds[feedId]) return
+ const filters = Feeds[feedId].filters
+ const stream = this.nostr.stream(
+ filters,
+ {subId: `feed:${feedId}`}
+ )
+ stream.on('init', events => {
+ const items = events.map(event => [event]) // TODO Single element thread
+ items.sort(feedOrder)
+ this.feeds[feedId].items = items.slice(0, filters.limit)
+ this.loading = false
+
+ // Wait a bit before showing the first unreads
+ setTimeout(() => this.recentlyLoaded = false, 5000)
+ })
+ stream.on('update', event => {
+ this.feeds[feedId].unreads.push([event]) // TODO Single element thread
+ })
+
this.feeds[feedId] = {
items: [],
unreads: [],
+ stream,
}
-
- let initialFetchComplete = false
- let initialItems = []
-
- console.log(`subscribing to feed ${feedId}`, this.feeds[feedId])
-
- this.sub = this.nostr.streamFeed(
- Feeds[feedId.toUpperCase()],
- event => {
- const target = initialFetchComplete
- ? this.feeds[feedId].unreads
- : initialItems
- target.push([event]) // FIXME Single element thread
- },
- () => {
- initialItems.sort(feedOrder)
- this.feeds[feedId].items = initialItems.slice(0, 50)
- initialFetchComplete = true
- this.loading = false
-
- // Wait a bit before showing the first unreads
- setTimeout(() => this.recentlyLoaded = false, 5000)
- }
- )
},
switchFeed(feedId) {
this.initFeed(feedId)
this.selectedFeed = feedId
},
loadUnreads() {
- this.loading = true
+ // TODO Deduplicate feed items
const items = this.feedUnreads.concat(this.feedItems)
- items.sort(feedOrder)
+ //items.sort(feedOrder)
this.activeFeed.items = items
this.activeFeed.unreads = []
- this.loading = false
// Wait a bit before showing unreads again
this.recentlyLoaded = true
setTimeout(() => this.recentlyLoaded = false, 5000)
+
+ return true
+ },
+ async loadOlder() {
+ const until = this.feedItems[this.feedItems.length - 1]?.[0]?.createdAt || DateUtils.now()
+ console.log('until', new Date(until * 1000))
+ const filters = Object.assign({}, Feeds[this.selectedFeed].filters, {until})
+
+ const older = await this.nostr.fetch(filters, {subId: `feed:${this.selectedFeed}-older`})
+ const items = older.map(event => [event]).sort(feedOrder)
+
+ console.log('got items', older)
+
+ console.log('length before', this.activeFeed.items.length)
+ // TODO Deduplicate feed items
+ this.activeFeed.items = this.feedItems.concat(items)
+
+ console.log('length after', this.activeFeed.items.length)
+
+ return older
},
},
mounted() {
this.initFeed(this.selectedFeed)
},
unmounted() {
- if (this.sub) this.nostr.cancelStream(this.sub)
+ for (const feed of Object.values(this.feeds)) {
+ feed.stream.close()
+ }
}
})
@@ -179,7 +205,7 @@ export default defineComponent({
border-bottom: $border-dark;
min-height: 6px;
}
- > .load-more:last-child {
+ > .async-load-button:last-child {
border-bottom: 0;
}
}
diff --git a/src/pages/Notifications.vue b/src/pages/Notifications.vue
index 8c742b3..f933f57 100644
--- a/src/pages/Notifications.vue
+++ b/src/pages/Notifications.vue
@@ -32,30 +32,24 @@ export default {
data() {
return {
notifications: [],
+ stream: null,
loading: true,
}
},
methods: {
},
mounted() {
- const read = []
- const unread = []
- this.nostr.streamNotifications(
- this.app.myPubkey,
- event => {
- if (event.createdAt >= this.settings.notificationsLastRead) {
- unread.push(event)
- } else {
- read.push(event)
- }
- },
- () => {
- unread.sort(NoteOrder.CREATION_DATE_DESC)
- this.notifications = unread
- this.loading = false
- }
- )
- }
+ this.stream = this.nostr.streamNotifications(this.app.myPubkey)
+ this.stream.on('init', events => {
+ events.sort(NoteOrder.CREATION_DATE_DESC)
+ this.notifications = events
+ this.loading = false
+ })
+ this.stream.on('update', event => this.notifications.unshift(event))
+ },
+ unmounted() {
+ if (this.stream) this.stream.close()
+ },
}
diff --git a/src/pages/Thread.vue b/src/pages/Thread.vue
index b982492..c416a78 100644
--- a/src/pages/Thread.vue
+++ b/src/pages/Thread.vue
@@ -4,6 +4,7 @@
{}, // TODO
- this.buildThread.bind(this)
- )
+ this.stream = this.nostr.streamThread(this.rootId)
+ this.stream.on('init', this.buildThread.bind(this))
},
- cancelStream() {
- if (!this.subId) return
- this.nostr.cancelStream(this.subId)
- this.subId = null
+ closeStream() {
+ if (!this.stream) return
+ this.stream.close()
+ this.stream = null
},
buildThread() {
@@ -235,7 +233,7 @@ export default defineComponent({
},
unmounted() {
console.log('unmounted', this.subId)
- this.cancelStream()
+ this.closeStream()
this.resizeObserver.disconnect()
}
})
diff --git a/src/utils/DateUtils.js b/src/utils/DateUtils.js
index e747b71..4891ad4 100644
--- a/src/utils/DateUtils.js
+++ b/src/utils/DateUtils.js
@@ -17,6 +17,10 @@ const MONTHS = [
]
export default class DateUtils {
+ static now() {
+ return Math.floor(Date.now() / 1000)
+ }
+
static formatDate(timestamp) {
const date = new Date(timestamp * 1000)
const month = MONTHS[date.getMonth()] // TODO i18n
@@ -49,8 +53,7 @@ export default class DateUtils {
}
static formatFromNowShort(timestamp) {
- const now = Date.now()
- const diff = Math.round(Math.max(now - (timestamp * 1000), 0) / 1000)
+ const diff = Math.max(DateUtils.now() - timestamp, 0)
const formatDiff = (unit, factor, offset) => Math.max(Math.floor((diff + (unit * offset)) / (unit * factor)), 1)
if (diff < 45) return `${formatDiff(1, 1, 0)}s`
diff --git a/src/utils/Nip05.js b/src/utils/Nip05.js
index ce24867..c522727 100644
--- a/src/utils/Nip05.js
+++ b/src/utils/Nip05.js
@@ -9,9 +9,10 @@ export default class Nip05 {
try {
const res = await fetch(url)
const json = await res.json()
+ if (!json || !json.names) return
return json.names[user]
} catch (e) {
- console.warn(`Failed to fetch NIP05 data for ${nip05Id}`, e)
+ //console.warn(`Failed to fetch NIP05 data for ${nip05Id}`, e)
}
}