Fix all major bugs

This commit is contained in:
Jonathan Staab 2022-12-17 15:25:07 -08:00
parent d23742d33e
commit 78a85be7e5
14 changed files with 130 additions and 130 deletions

View File

@ -14,6 +14,7 @@ Coracle is currently in _alpha_ - expect bugs, slow loading times, and rough edg
- [x] Notifications
- [x] Link previews
- [x] Add notes, follows, likes tab to profile
- [ ] Add a coracle relay
- [ ] Mentions - render done, now reference in compose
- [ ] Image uploads
- [ ] An actual readme
@ -40,11 +41,9 @@ Coracle is currently in _alpha_ - expect bugs, slow loading times, and rough edg
- [ ] Check firefox - in dev it won't work, but it should in production
- [ ] Re-implement muffle
- [ ] Move relays to db
- [ ] Make user a livequery instead of a store
- [ ] Figure out if multiple relays congest response times because we wait for all eose
- [ ] Set default relay when storage is empty
- [ ] Are connections closed when a relay is removed?
- [ ] Delete old events
- [ ] Sync accounts to store to avoid loading jank
- [ ] Sync account updates to user for e.g. muffle settings
- https://vitejs.dev/guide/features.html#web-workers
- https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
- https://web.dev/module-workers/

View File

@ -6,7 +6,8 @@ import {filterTags, findReply, findRoot} from 'src/util/nostr'
export const db = new Dexie('coracle/relay')
db.version(3).stores({
db.version(4).stores({
relays: '++url, name',
events: '++id, pubkey, created_at, kind, content, reply, root',
people: '++pubkey, name, about',
tags: '++key, event, value',

View File

@ -38,7 +38,7 @@ const ensureContext = async events => {
if (ids.length > 0) {
promises.push(
pool.fetchEvents([
pool.loadEvents([
{kinds: [1, 5, 7], '#e': ids},
{kinds: [1, 5], ids},
])
@ -107,6 +107,11 @@ const scroller = (filter, delta, onChunk) => {
until -= delta
await onChunk(await getOrLoadChunk(filter, since, until))
// Set a hard cutoff at 3 weeks back
if (since < now() - timedelta(21, 'days')) {
unsub()
}
})
return unsub
@ -142,15 +147,13 @@ const findNote = async (id, giveUp = false) => {
// If we don't have it, try to retrieve it
if (!note) {
console.warning(`Failed to find context for note ${id}`)
console.warn(`Failed to find context for note ${id}`)
if (giveUp) {
return null
}
await ensureContext([
await pool.fetchEvents({ids: [id]}),
])
await ensureContext(await pool.loadEvents({ids: [id]}))
return findNote(id, true)
}

View File

@ -1,4 +1,5 @@
import {uniqBy, prop} from 'ramda'
import {uniqBy, without, prop} from 'ramda'
import {writable} from 'svelte/store'
import {relayPool, getPublicKey} from 'nostr-tools'
import {noop, range} from 'hurdak/lib/hurdak'
import {now, randomChoice, timedelta, getLocalJson, setLocalJson} from "src/util/misc"
@ -10,6 +11,16 @@ import {db} from 'src/relay/db'
const pool = relayPool()
const relays = writable([])
const setup = () => {
for (const url of getLocalJson('pool/relays') || []) {
addRelay(url)
}
relays.subscribe($relays => setLocalJson('pool/relays', $relays))
}
class Channel {
constructor(name) {
this.name = name
@ -77,10 +88,12 @@ const getPubkey = () => {
const addRelay = url => {
pool.addRelay(url)
relays.update($r => $r.concat(url))
}
const removeRelay = url => {
pool.removeRelay(url)
relays.update($r => without([url], $r))
}
const setPrivateKey = privkey => {
@ -127,10 +140,6 @@ const syncPersonInfo = async person => {
return await db.people.where('pubkey').equals(person.pubkey).first()
}
const fetchEvents = async filter => {
db.events.process(await req(filter))
}
let syncSub = null
let syncChan = new Channel('sync')
@ -163,7 +172,9 @@ const sync = async person => {
)
}
setup()
export default {
getPubkey, addRelay, removeRelay, setPrivateKey, setPublicKey,
publishEvent, loadEvents, syncPersonInfo, fetchEvents, sync,
publishEvent, loadEvents, syncPersonInfo, sync, relays,
}

View File

@ -8,7 +8,7 @@
import Input from "src/partials/Input.svelte"
import toast from "src/state/toast"
import {dispatch} from "src/state/dispatch"
import {relays, user} from "src/state/app"
import {user} from "src/state/app"
let privkey = ''
let hasExtension = false
@ -32,19 +32,11 @@
}
const logIn = async ({privkey, pubkey}) => {
console.log(1)
const person = await dispatch("user/init", pubkey)
console.log(person)
user.set({...person, pubkey, privkey})
if ($relays.length === 0) {
navigate('/relays')
} else if (user.name) {
navigate('/notes/global')
} else {
navigate('/profile')
}
navigate('/notes/global')
}
const logInWithExtension = async () => {

View File

@ -4,11 +4,13 @@
import Anchor from "src/partials/Anchor.svelte"
import Tabs from "src/partials/Tabs.svelte"
import Notes from "src/views/Notes.svelte"
import {user, relays} from "src/state/app"
import {user} from "src/state/app"
import {timedelta} from 'src/util/misc'
import relay from 'src/relay'
export let activeTab
const relays = relay.pool.relays
const authors = $user ? $user.petnames.map(t => t[1]) : []
const setActiveTab = tab => navigate(`/notes/${tab}`)
</script>

View File

@ -1,20 +1,20 @@
<script>
import {fly} from 'svelte/transition'
import {find, identity, whereEq, reject} from 'ramda'
import {fuzzy} from "src/util/misc"
import Input from "src/partials/Input.svelte"
import {dispatch} from "src/state/dispatch"
import {modal, relays, knownRelays} from "src/state/app"
import {modal} from "src/state/app"
import relay from 'src/relay'
let q = ""
let search
let data
$: data = reject(r => $relays.includes(r.url), $knownRelays || [])
$: search = fuzzy(data, {keys: ["name", "description", "url"]})
const relays = relay.pool.relays
const knownRelays = relay.lq(() => relay.db.relays.toArray())
const join = url => dispatch("relay/join", url)
const leave = url => dispatch("relay/leave", url)
$: search = fuzzy($knownRelays, {keys: ["name", "description", "url"]})
const join = url => relay.pool.addRelay(url)
const leave = url => relay.pool.removeRelay(url)
</script>
<div class="flex justify-center py-8 px-4" in:fly={{y: 20}}>
@ -30,27 +30,29 @@
<i slot="before" class="fa-solid fa-search" />
</Input>
<div class="flex flex-col gap-6 overflow-auto flex-grow -mx-6 px-6">
{#each $relays.map(url => find(whereEq({url}), $knownRelays)).filter(identity) as relay}
{#each ($knownRelays || []) as r}
{#if $relays.includes(r.url)}
<div class="flex gap-2 justify-between">
<div>
<strong>{relay.name || relay.url}</strong>
<p class="text-light">{relay.description || ''}</p>
<strong>{r.name || r.url}</strong>
<p class="text-light">{r.description || ''}</p>
</div>
<a class="underline cursor-pointer" on:click={() => leave(relay.url)}>
<a class="underline cursor-pointer" on:click={() => leave(r.url)}>
Leave
</a>
</div>
{/if}
{/each}
{#if $relays.length > 0}
{#if ($knownRelays || []).length > 0}
<div class="pt-2 mb-2 border-b border-solid border-medium" />
{/if}
{#each search(q).slice(0, 10) as relay}
{#each (search(q) || []).slice(0, 10) as r}
<div class="flex gap-2 justify-between">
<div>
<strong>{relay.name || relay.url}</strong>
<p class="text-light">{relay.description || ''}</p>
<strong>{r.name || r.url}</strong>
<p class="text-light">{r.description || ''}</p>
</div>
<a class="underline cursor-pointer" on:click={() => join(relay.url)}>
<a class="underline cursor-pointer" on:click={() => join(r.url)}>
Join
</a>
</div>

View File

@ -1,4 +1,3 @@
import {uniqBy, prop} from 'ramda'
import {writable, get} from 'svelte/store'
import {navigate} from "svelte-routing"
import {globalHistory} from "svelte-routing/src/history"
@ -32,46 +31,6 @@ userLq.subscribe(person => {
user.update($user => $user ? ({...$user, ...person}) : null)
})
// Keep track of known relays
export const knownRelays = writable((getLocalJson("coracle/knownRelays") || [
{url: "wss://nostr.zebedee.cloud"},
{url: "wss://nostr-pub.wellorder.net"},
{url: "wss://nostr.rocks"},
{url: "wss://nostr-pub.semisol.dev"},
{url: "wss://nostr.drss.io"},
{url: "wss://relay.damus.io"},
{url: "wss://nostr.openchain.fr"},
{url: "wss://nostr.delo.software"},
{url: "wss://relay.nostr.info"},
{url: "wss://nostr.ono.re"},
{url: "wss://relay.grunch.dev"},
{url: "wss://nostr.sandwich.farm"},
{url: "wss://relay.nostr.ch"},
{url: "wss://nostr-relay.wlvs.space"},
]).filter(x => x.url))
knownRelays.subscribe($knownRelays => {
setLocalJson("coracle/knownRelays", $knownRelays)
})
export const registerRelay = async url => {
let json
try {
const res = await fetch(url.replace(/^ws/, 'http'), {
headers: {
Accept: 'application/nostr_json',
},
})
json = await res.json()
} catch (e) {
json = {}
}
knownRelays.update($xs => uniqBy(prop('url'), $xs.concat({...json, url})))
}
// Keep track of which relays we're subscribed to
export const relays = writable(getLocalJson("coracle/relays") || [])
@ -139,3 +98,40 @@ export const alerts = writable({
alerts.subscribe($alerts => {
setLocalJson("coracle/alerts", $alerts)
})
// Relays
const defaultRelays = [
"wss://nostr.zebedee.cloud",
"wss://nostr-pub.wellorder.net",
"wss://relay.damus.io",
"wss://relay.grunch.dev",
"wss://nostr.sandwich.farm",
"wss://relay.nostr.ch",
"wss://nostr-relay.wlvs.space",
]
export const registerRelay = async url => {
const {dufflepudUrl} = get(settings)
let json
try {
const res = await fetch(dufflepudUrl + '/relay/info', {
method: 'POST',
body: JSON.stringify({url}),
headers: {
'Content-Type': 'application/json',
},
})
json = await res.json()
} catch (e) {
json = {}
}
relay.db.relays.put({...json, url})
}
for (const url of defaultRelays) {
registerRelay(url)
}

View File

@ -1,8 +1,6 @@
import {identity, isNil, uniqBy, last, without} from 'ramda'
import {get} from 'svelte/store'
import {identity, isNil, uniqBy, last} from 'ramda'
import {first, defmulti} from "hurdak/lib/hurdak"
import relay from 'src/relay'
import {relays} from 'src/state/app'
// Commands are processed in two layers:
// - App-oriented commands are created via dispatch
@ -12,7 +10,10 @@ import {relays} from 'src/state/app'
export const dispatch = defmulti("dispatch", identity)
dispatch.addMethod("user/init", (topic, pubkey) => {
return relay.pool.syncUserInfo({pubkey})
// Hardcode one to get them started
relay.pool.addRelay("wss://nostr.zebedee.cloud")
return relay.pool.syncPersonInfo({pubkey})
})
dispatch.addMethod("user/update", async (topic, updates) => {
@ -27,14 +28,6 @@ dispatch.addMethod("user/muffle", async (topic, muffle) => {
await relay.pool.publishEvent(makeEvent(12165, '', muffle))
})
dispatch.addMethod("relay/join", async (topic, url) => {
relays.update(r => r.concat(url))
})
dispatch.addMethod("relay/leave", (topic, url) => {
relays.update(r => without([url], r))
})
dispatch.addMethod("room/create", async (topic, room) => {
const event = makeEvent(40, JSON.stringify(room))
@ -104,7 +97,7 @@ export const copyTags = (e, newTags = []) => {
}
export const t = (type, content, marker) => {
const tag = [type, content, first(get(relays))]
const tag = [type, content, first(Object.keys(relay.pool.relays))]
if (!isNil(marker)) {
tag.push(marker)

View File

@ -64,7 +64,7 @@ export const createScroller = loadMore => {
await loadMore()
}
await sleep(100)
await sleep(1000)
if (!done) {
requestAnimationFrame(check)

View File

@ -1,26 +1,35 @@
<script>
import {onDestroy} from 'svelte'
import {fly} from 'svelte/transition'
import {propEq} from 'ramda'
import {uniqBy, identity, prop} from 'ramda'
import {timedelta} from 'src/util/misc'
import Note from "src/views/Note.svelte"
import {findReply} from 'src/util/nostr'
import relay from 'src/relay'
export let author
const notes = relay.lq(async () => {
const reactions = await relay.db.events
.where('pubkey').equals(author).filter(propEq('kind', 7)).toArray()
const filter = {kinds: [7], authors: [author]}
const delta = timedelta(1, 'days')
return Promise.all(reactions.map(r => relay.findNote(findReply(r))))
})
let notes
onDestroy(relay.scroller(filter, delta, async chunk => {
notes = relay.lq(async () => {
const notes = await Promise.all(chunk.map(r => relay.findNote(findReply(r))))
return uniqBy(prop('id'), notes.filter(identity))
})
}))
</script>
{#if $notes}
<ul class="py-4 flex flex-col gap-2 max-w-xl m-auto">
{#each $notes as n (n.id)}
<li><Note interactive noReply note={n} depth={1} /></li>
<li><Note note={n} depth={1} /></li>
{:else}
<li class="p-20 text-center" in:fly={{y: 20}}>No notes found.</li>
{/each}
</ul>
{/if}

View File

@ -12,7 +12,7 @@
const values = {
// Scale up to integers for each choice we have
muffle: switcher(Math.round(getMuffleValue($modal.user) * 3), muffleOptions),
muffle: switcher(Math.round(getMuffleValue($modal.person) * 3), muffleOptions),
}
const save = async e => {
@ -21,8 +21,8 @@
// Scale back down to a decimal based on string value
const muffleValue = muffleOptions.indexOf(values.muffle) / 3
const muffle = $user.muffle
.filter(x => x[1] !== $modal.user.pubkey)
.concat([t("p", $modal.user.pubkey, muffleValue.toString())])
.filter(x => x[1] !== $modal.person.pubkey)
.concat([t("p", $modal.person.pubkey, muffleValue.toString())])
.filter(x => last(x) !== "1")
dispatch('user/muffle', muffle)

View File

@ -27,7 +27,7 @@
</script>
<ul class="py-8 flex flex-col gap-2 max-w-xl m-auto">
{#each results as e (e.id)}
{#each results.slice(0, 50) as e (e.id)}
<li in:fly={{y: 20}}>
<Note interactive note={e} />
</li>

View File

@ -6,37 +6,29 @@
export let q
let results = []
let search
const search = relay.lq(async () => {
return fuzzy(await relay.db.people.toArray(), {keys: ["name", "about", "pubkey"]})
})
const people = relay.lq(() => relay.db.people.toArray())
$: {
if ($search) {
Promise.all(
$search(q).map(n => relay.findNote(n.id))
).then(notes => {
results = notes
})
}
}
$: search = fuzzy($people || [], {keys: ["name", "about", "pubkey"]})
</script>
{#if search}
<ul class="py-8 flex flex-col gap-2 max-w-xl m-auto">
{#each results as e (e.pubkey)}
{#if e.pubkey !== $user.pubkey}
{#each search(q) as p (p.pubkey)}
{#if p.pubkey !== $user.pubkey}
<li in:fly={{y: 20}}>
<a href="/people/{e.pubkey}/notes" class="flex gap-4 my-4">
<a href="/people/{p.pubkey}/notes" class="flex gap-4 my-4">
<div
class="overflow-hidden w-12 h-12 rounded-full bg-cover bg-center shrink-0 border border-solid border-white"
style="background-image: url({e.picture})" />
style="background-image: url({p.picture})" />
<div class="flex-grow">
<h1 class="text-2xl">{e.name || e.pubkey.slice(0, 8)}</h1>
<p>{e.about || ''}</p>
<h1 class="text-2xl">{p.name || p.pubkey.slice(0, 8)}</h1>
<p>{p.about || ''}</p>
</div>
</a>
<li>
{/if}
{/each}
</ul>
{/if}