mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-29 16:31:04 +00:00
Fix some bugs
This commit is contained in:
parent
2869bf23fd
commit
4d5cddbc4e
@ -119,19 +119,17 @@
|
||||
|
||||
const profileOptions = peopleWithName.derived($people =>
|
||||
$people
|
||||
.filter(person => person.pubkey !== $session.pubkey)
|
||||
.filter(person => person.pubkey !== $session?.pubkey)
|
||||
.map(person => {
|
||||
const {
|
||||
pubkey,
|
||||
profile: {name, display_name},
|
||||
handle: {address},
|
||||
} = person
|
||||
const {pubkey, profile, handle} = person
|
||||
|
||||
return {
|
||||
person,
|
||||
id: pubkey,
|
||||
type: "profile",
|
||||
text: "@" + [name, address, display_name].filter(identity).join(" "),
|
||||
text:
|
||||
"@" +
|
||||
[profile?.name, handle?.address, profile?.display_name].filter(identity).join(" "),
|
||||
}
|
||||
})
|
||||
)
|
||||
|
@ -13,8 +13,14 @@
|
||||
import RelayFeed from "src/app/shared/RelayFeed.svelte"
|
||||
import Note from "src/app/shared/Note.svelte"
|
||||
import type {DynamicFilter} from "src/engine2"
|
||||
import {session, getSetting, searchableRelays, mergeHints, getPubkeyHints} from "src/engine2"
|
||||
import {compileFilter} from "src/app/state"
|
||||
import {
|
||||
session,
|
||||
compileFilter,
|
||||
getSetting,
|
||||
searchableRelays,
|
||||
mergeHints,
|
||||
getPubkeyHints,
|
||||
} from "src/engine2"
|
||||
|
||||
export let relays = []
|
||||
export let filter = {} as DynamicFilter
|
||||
@ -56,7 +62,7 @@
|
||||
}
|
||||
|
||||
const limit = getSetting("relay_limit")
|
||||
const authors = (compileFilter(filter).authors || []).concat($session.pubkey)
|
||||
const authors = (compileFilter(filter).authors || []).concat($session?.pubkey)
|
||||
const hints = authors.map(pubkey => getPubkeyHints(limit, pubkey, "write"))
|
||||
|
||||
return mergeHints(limit, hints)
|
||||
|
@ -4,7 +4,7 @@
|
||||
import {tweened} from "svelte/motion"
|
||||
import {find, reject, identity, propEq, sum, pluck, sortBy} from "ramda"
|
||||
import {stringToHue, formatSats, hsl} from "src/util/misc"
|
||||
import {isLike, toNostrURI} from "src/util/nostr"
|
||||
import {isLike, fromDisplayEvent, toNostrURI} from "src/util/nostr"
|
||||
import {quantify} from "hurdak"
|
||||
import {modal} from "src/partials/state"
|
||||
import Popover from "src/partials/Popover.svelte"
|
||||
@ -54,8 +54,10 @@
|
||||
|
||||
const muteNote = () => mute("e", note.id)
|
||||
|
||||
const react = content => {
|
||||
like = publishReaction(note, content).event
|
||||
const react = async content => {
|
||||
const pub = await publishReaction(note, content)
|
||||
|
||||
like = pub.event
|
||||
}
|
||||
|
||||
const deleteReaction = e => {
|
||||
@ -71,8 +73,9 @@
|
||||
|
||||
const broadcast = () => {
|
||||
const relays = getUserRelayUrls("write")
|
||||
const event = fromDisplayEvent(note)
|
||||
|
||||
Publisher.publish({event: note, relays}).on("progress", toastProgress)
|
||||
Publisher.publish({event, relays}).on("progress", toastProgress)
|
||||
}
|
||||
|
||||
let like, likes, allLikes, zap, zaps
|
||||
@ -81,12 +84,12 @@
|
||||
|
||||
$: disableActions = !$canSign || muted
|
||||
$: likes = note.reactions.filter(n => isLike(n.content))
|
||||
$: like = like || find(propEq("pubkey", $session.pubkey), likes)
|
||||
$: like = like || find(propEq("pubkey", $session?.pubkey), likes)
|
||||
$: allLikes = like ? likes.filter(n => n.id !== like?.id).concat(like) : likes
|
||||
$: $likesCount = allLikes.length
|
||||
|
||||
$: zaps = processZaps(note.zaps, note.pubkey)
|
||||
$: zap = zap || find((z: ZapEvent) => z.request.pubkey === $session.pubkey, zaps)
|
||||
$: zap = zap || find((z: ZapEvent) => z.request.pubkey === $session?.pubkey, zaps)
|
||||
|
||||
$: $zapsTotal =
|
||||
sum(
|
||||
@ -97,7 +100,7 @@
|
||||
)
|
||||
) / 1000
|
||||
|
||||
$: canZap = $person?.zapper && note.pubkey !== $session.pubkey
|
||||
$: canZap = $person?.zapper && note.pubkey !== $session?.pubkey
|
||||
$: $repliesCount = note.replies.length
|
||||
|
||||
$: {
|
||||
@ -139,7 +142,7 @@
|
||||
</button>
|
||||
<button
|
||||
class={cx("relative w-16 pt-1 text-left transition-all hover:pb-1 hover:pt-0", {
|
||||
"pointer-events-none opacity-50": disableActions || note.pubkey === $session.pubkey,
|
||||
"pointer-events-none opacity-50": disableActions || note.pubkey === $session?.pubkey,
|
||||
"text-accent": like,
|
||||
})}
|
||||
on:click={() => (like ? deleteReaction(like) : react("+"))}>
|
||||
|
@ -42,14 +42,16 @@
|
||||
data.mentions = without([pubkey], data.mentions)
|
||||
}
|
||||
|
||||
const getContent = () => (reply.parse() + "\n" + data.image).trim()
|
||||
const getContent = () => (reply.parse() + "\n" + (data.image || "")).trim()
|
||||
|
||||
const send = async () => {
|
||||
const content = getContent()
|
||||
const tags = data.mentions.map(mention)
|
||||
|
||||
if (content) {
|
||||
publishReply(parent, content, tags).on("progress", toastProgress)
|
||||
const pub = await publishReply(parent, content, tags)
|
||||
|
||||
pub.on("progress", toastProgress)
|
||||
|
||||
reset()
|
||||
}
|
||||
|
@ -1,15 +1,14 @@
|
||||
import type {Filter} from "nostr-tools"
|
||||
import Bugsnag from "@bugsnag/js"
|
||||
import {nip19} from "nostr-tools"
|
||||
import {navigate} from "svelte-routing"
|
||||
import {writable} from "svelte/store"
|
||||
import {omit, path, filter, pluck, sortBy, slice} from "ramda"
|
||||
import {hash, union, sleep, doPipe, shuffle} from "hurdak"
|
||||
import {path, filter, pluck, sortBy, slice} from "ramda"
|
||||
import {hash, union, sleep, doPipe} from "hurdak"
|
||||
import {warn} from "src/util/logger"
|
||||
import {now} from "src/util/misc"
|
||||
import {userKinds, noteKinds} from "src/util/nostr"
|
||||
import {modal, toast} from "src/partials/state"
|
||||
import type {DynamicFilter, Event} from "src/engine2"
|
||||
import type {Event} from "src/engine2"
|
||||
import {
|
||||
env,
|
||||
pool,
|
||||
@ -18,7 +17,6 @@ import {
|
||||
loadPubkeys,
|
||||
channels,
|
||||
follows,
|
||||
network,
|
||||
subscribe,
|
||||
getUserRelayUrls,
|
||||
getSetting,
|
||||
@ -154,7 +152,7 @@ export const loadAppData = async () => {
|
||||
const {pubkey} = session.get()
|
||||
|
||||
// Make sure the user and their follows are loaded
|
||||
await loadPubkeys(pubkey, {force: true, kinds: userKinds})
|
||||
await loadPubkeys([pubkey], {force: true, kinds: userKinds})
|
||||
|
||||
// Load deletes
|
||||
loadDeletes()
|
||||
@ -221,20 +219,3 @@ export const toastProgress = progress => {
|
||||
|
||||
toast.show("info", payload, pending.size ? null : 8)
|
||||
}
|
||||
|
||||
// Feeds
|
||||
|
||||
export const getAuthorsWithDefaults = (pubkeys: string[]) =>
|
||||
shuffle(pubkeys.length > 0 ? pubkeys : (env.get().DEFAULT_FOLLOWS as string[])).slice(0, 1024)
|
||||
|
||||
export const compileFilter = (filter: DynamicFilter): Filter => {
|
||||
if (filter.authors === "global") {
|
||||
filter = omit(["authors"], filter)
|
||||
} else if (filter.authors === "follows") {
|
||||
filter = {...filter, authors: getAuthorsWithDefaults(follows.get())}
|
||||
} else if (filter.authors === "network") {
|
||||
filter = {...filter, authors: getAuthorsWithDefaults(network.get())}
|
||||
}
|
||||
|
||||
return filter as Filter
|
||||
}
|
||||
|
@ -14,8 +14,14 @@
|
||||
import Heading from "src/partials/Heading.svelte"
|
||||
import ImageCircle from "src/partials/ImageCircle.svelte"
|
||||
import type {Person, Event} from "src/engine2"
|
||||
import {getUserRelayUrls, loadPubkeys, load, displayHandle, derivePerson} from "src/engine2"
|
||||
import {compileFilter} from "src/app/state"
|
||||
import {
|
||||
getUserRelayUrls,
|
||||
compileFilter,
|
||||
loadPubkeys,
|
||||
load,
|
||||
displayHandle,
|
||||
derivePerson,
|
||||
} from "src/engine2"
|
||||
|
||||
const getColumns = xs => {
|
||||
const cols = [[], []]
|
||||
|
@ -34,7 +34,11 @@
|
||||
|
||||
const edit = () => modal.push({type: "chat/edit", channel: $channel})
|
||||
|
||||
const sendMessage = content => publishNip28Message(id, content).result
|
||||
const sendMessage = async content => {
|
||||
const pub = await publishNip28Message(id, content)
|
||||
|
||||
return pub.result
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
const sub = listenForNip28Messages(id)
|
||||
@ -69,7 +73,7 @@
|
||||
<div class="flex h-12 flex-col pt-px">
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<div class="flex gap-2">
|
||||
{#if $channel?.nip28?.owner === $session.pubkey}
|
||||
{#if $channel?.nip28?.owner === $session?.pubkey}
|
||||
<button class="cursor-pointer text-sm" on:click={edit}>
|
||||
<i class="fa-solid fa-edit" /> Edit
|
||||
</button>
|
||||
|
@ -20,11 +20,11 @@
|
||||
const {id, ...content} = channel
|
||||
|
||||
if (id) {
|
||||
const pub = publishNip28ChannelUpdate(id, content)
|
||||
const pub = await publishNip28ChannelUpdate(id, content)
|
||||
|
||||
pub.on("progress", toastProgress)
|
||||
} else {
|
||||
const pub = publishNip28ChannelCreate(content)
|
||||
const pub = await publishNip28ChannelCreate(content)
|
||||
|
||||
joinNip28Channel(pub.event.id)
|
||||
}
|
||||
|
@ -22,8 +22,8 @@
|
||||
sortChannels,
|
||||
nip28ChannelsWithMeta,
|
||||
loadPubkeys,
|
||||
getPubkeysWithDefaults,
|
||||
} from "src/engine2"
|
||||
import {getAuthorsWithDefaults} from "src/app/state"
|
||||
|
||||
let q = ""
|
||||
let results = []
|
||||
@ -54,46 +54,42 @@
|
||||
document.title = "Chat"
|
||||
|
||||
onMount(() => {
|
||||
const subs = []
|
||||
const relays = getPubkeyHints(3, $stateKey, "read")
|
||||
const authors = getAuthorsWithDefaults($follows)
|
||||
const authors = getPubkeysWithDefaults($follows)
|
||||
const since = now() - seconds(1, "day")
|
||||
const filters = [
|
||||
{kinds: [40, 41], authors, limit: 100},
|
||||
{limit: 100, kinds: [42], since, authors},
|
||||
{kinds: [42], since, authors, limit: 100},
|
||||
] as Filter[]
|
||||
|
||||
if ($session.pubkey) {
|
||||
if ($session) {
|
||||
filters.push({kinds: [40, 41], authors: [$session.pubkey]})
|
||||
}
|
||||
|
||||
// Pull some relevant channels by grabbing recent messages
|
||||
subs.push(
|
||||
load({
|
||||
relays,
|
||||
filters,
|
||||
onEvent: batch(500, (events: Event[]) => {
|
||||
const channelIds = uniq(
|
||||
events.filter(e => e.kind === 42).map(e => Tags.from(e).getMeta("e"))
|
||||
)
|
||||
load({
|
||||
relays,
|
||||
filters,
|
||||
onEvent: batch(500, (events: Event[]) => {
|
||||
const channelIds = uniq(
|
||||
events.filter(e => e.kind === 42).map(e => Tags.from(e).getMeta("e"))
|
||||
)
|
||||
|
||||
loadPubkeys(pluck("pubkey", events))
|
||||
loadPubkeys(pluck("pubkey", events))
|
||||
|
||||
subs.push(
|
||||
load({
|
||||
relays,
|
||||
filters: [
|
||||
{kinds: [40], ids: channelIds},
|
||||
{kinds: [41], "#e": channelIds},
|
||||
],
|
||||
})
|
||||
)
|
||||
}),
|
||||
})
|
||||
)
|
||||
if (channelIds.length > 0) {
|
||||
load({
|
||||
relays,
|
||||
filters: [
|
||||
{kinds: [40], ids: channelIds},
|
||||
{kinds: [41], "#e": channelIds},
|
||||
],
|
||||
})
|
||||
}
|
||||
}),
|
||||
})
|
||||
|
||||
return () => {
|
||||
subs.map(s => s.close())
|
||||
scroller.stop()
|
||||
}
|
||||
})
|
||||
|
@ -4,7 +4,7 @@
|
||||
import {fly} from "src/util/transition"
|
||||
import {ellipsize} from "hurdak"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import {canSign, imgproxy, joinNip28Channel, leaveNip28Channel} from "src/engine2"
|
||||
import {canSign, hasNewMessages, imgproxy, joinNip28Channel, leaveNip28Channel} from "src/engine2"
|
||||
|
||||
export let channel
|
||||
|
||||
@ -14,10 +14,11 @@
|
||||
|
||||
// Accommodate data urls from legacy
|
||||
const picture =
|
||||
channel.picture?.length > 500 ? channel.picture : imgproxy(channel.picture, {w: 112, h: 112})
|
||||
channel.meta?.picture?.length > 500
|
||||
? channel.meta.picture
|
||||
: imgproxy(channel.meta.picture, {w: 112, h: 112})
|
||||
|
||||
$: notify =
|
||||
channel.nip28.joined && (channel.last_checked || channel.last_sent) < channel.last_received
|
||||
$: showBadge = channel.nip28.joined && hasNewMessages(channel)
|
||||
</script>
|
||||
|
||||
<button
|
||||
@ -27,13 +28,13 @@
|
||||
<div
|
||||
class="h-14 w-14 shrink-0 overflow-hidden rounded-full border border-solid border-white bg-cover bg-center"
|
||||
style={`background-image: url(${picture})`} />
|
||||
{#if notify}
|
||||
{#if showBadge}
|
||||
<div class="absolute left-2 top-2 h-2 w-2 rounded bg-accent" />
|
||||
{/if}
|
||||
<div class="flex min-w-0 flex-grow flex-col justify-start gap-2">
|
||||
<div class="flex flex-grow items-start justify-between gap-2">
|
||||
<h2 class="text-lg">
|
||||
{channel.name || ""}
|
||||
{channel.meta?.name || ""}
|
||||
</h2>
|
||||
{#if channel.nip28.joined}
|
||||
<Anchor theme="button" killEvent class="flex items-center gap-2" on:click={leave}>
|
||||
@ -47,9 +48,9 @@
|
||||
</Anchor>
|
||||
{/if}
|
||||
</div>
|
||||
{#if channel.about}
|
||||
{#if channel.meta?.about}
|
||||
<p class="text-start text-gray-1">
|
||||
{ellipsize(channel.about, 300)}
|
||||
{ellipsize(channel.meta.about, 300)}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import {map, sortBy} from "ramda"
|
||||
import {map, identity, sortBy} from "ramda"
|
||||
import {quantify} from "hurdak"
|
||||
import {Tags} from "src/util/nostr"
|
||||
import {modal} from "src/partials/state"
|
||||
@ -11,7 +11,15 @@
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import NoteById from "src/app/shared/NoteById.svelte"
|
||||
import PersonBadgeSmall from "src/app/shared/PersonBadgeSmall.svelte"
|
||||
import {session, labels, getUserRelayUrls, follows, subscribe} from "src/engine2"
|
||||
import {
|
||||
session,
|
||||
getSetting,
|
||||
getPubkeysWithDefaults,
|
||||
labels,
|
||||
getPubkeyHints,
|
||||
follows,
|
||||
subscribe,
|
||||
} from "src/engine2"
|
||||
|
||||
type LabelGroup = {
|
||||
label: string
|
||||
@ -61,12 +69,12 @@
|
||||
|
||||
onMount(() => {
|
||||
const sub = subscribe({
|
||||
relays: getUserRelayUrls("read"),
|
||||
relays: getPubkeyHints(getSetting("relay_limit"), $session?.pubkey, "read"),
|
||||
filters: [
|
||||
{
|
||||
kinds: [1985],
|
||||
"#L": ["#t", "ugc"],
|
||||
authors: $follows.concat($session.pubkey),
|
||||
authors: getPubkeysWithDefaults($follows).concat($session?.pubkey).filter(identity),
|
||||
},
|
||||
],
|
||||
})
|
||||
|
@ -6,6 +6,7 @@
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import Heading from "src/partials/Heading.svelte"
|
||||
import {withExtension, loginWithExtension} from "src/engine2"
|
||||
import {boot} from "src/app/state"
|
||||
|
||||
const nip07 = "https://github.com/nostr-protocol/nips/blob/master/07.md"
|
||||
|
||||
@ -13,6 +14,7 @@
|
||||
withExtension(async ext => {
|
||||
if (ext) {
|
||||
loginWithExtension(await ext.getPublicKey())
|
||||
boot()
|
||||
} else {
|
||||
modal.push({type: "login/privkey"})
|
||||
}
|
||||
|
@ -1,26 +1,21 @@
|
||||
<script lang="ts">
|
||||
import {filter, whereEq, complement, pluck, prop} from "ramda"
|
||||
import {toTitle, seconds, batch} from "hurdak"
|
||||
import {now} from "src/util/misc"
|
||||
import {filter, whereEq, complement, prop} from "ramda"
|
||||
import {toTitle} from "hurdak"
|
||||
import {navigate} from "svelte-routing"
|
||||
import Tabs from "src/partials/Tabs.svelte"
|
||||
import Popover from "src/partials/Popover.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import MessagesListItem from "src/app/views/MessagesListItem.svelte"
|
||||
import {
|
||||
session,
|
||||
channels,
|
||||
load,
|
||||
loadPubkeys,
|
||||
hasNewNip04Messages,
|
||||
getUserRelayUrls,
|
||||
sortChannels,
|
||||
nip04MarkAllRead,
|
||||
loadAllNip04Messages,
|
||||
} from "src/engine2"
|
||||
|
||||
export let activeTab = "conversations"
|
||||
|
||||
const since = now() - seconds(90, "day")
|
||||
const nip04Channels = channels.derived(filter(whereEq({type: "nip04"})))
|
||||
const accepted = nip04Channels.derived(filter(prop("last_sent")))
|
||||
const requests = nip04Channels.derived(filter(complement(prop("last_sent"))))
|
||||
@ -32,16 +27,7 @@
|
||||
badge: (tab === "conversations" ? $accepted : $requests).length,
|
||||
})
|
||||
|
||||
load({
|
||||
relays: getUserRelayUrls("read"),
|
||||
filters: [
|
||||
{kinds: [4], authors: [$session.pubkey], since},
|
||||
{kinds: [4], "#p": [$session.pubkey], since},
|
||||
],
|
||||
onEvent: batch(1000, events => {
|
||||
loadPubkeys(pluck("pubkey", events))
|
||||
}),
|
||||
})
|
||||
loadAllNip04Messages()
|
||||
|
||||
document.title = "Direct Messages"
|
||||
</script>
|
||||
|
@ -6,6 +6,7 @@
|
||||
import {fly} from "src/util/transition"
|
||||
import {writable} from "svelte/store"
|
||||
import {annotateMedia} from "src/util/misc"
|
||||
import {fromDisplayEvent} from "src/util/nostr"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Compose from "src/app/shared/Compose.svelte"
|
||||
import ImageInput from "src/partials/ImageInput.svelte"
|
||||
@ -45,10 +46,15 @@
|
||||
tags.push(mention(quote.pubkey))
|
||||
|
||||
// Re-broadcast the note we're quoting
|
||||
Publisher.publish({relays: $relays, event: quote})
|
||||
Publisher.publish({
|
||||
relays: $relays,
|
||||
event: fromDisplayEvent(quote),
|
||||
})
|
||||
}
|
||||
|
||||
publishNote(content, tags, $relays).on("progress", toastProgress)
|
||||
const pub = await publishNote(content, tags, $relays)
|
||||
|
||||
pub.on("progress", toastProgress)
|
||||
|
||||
modal.clear()
|
||||
}
|
||||
|
@ -39,19 +39,18 @@
|
||||
// If our note came from a feed, we can preload context
|
||||
context.hydrate([displayNote], depth)
|
||||
|
||||
await load({
|
||||
load({
|
||||
filters: [{ids: [note.id]}],
|
||||
relays: selectHints(getSetting("relay_limit"), relays),
|
||||
onEvent: e => {
|
||||
context.addContext([e], {depth})
|
||||
|
||||
displayNote = first(context.applyContext([e]))
|
||||
loading = false
|
||||
},
|
||||
})
|
||||
|
||||
info("NoteDetail", displayNote)
|
||||
|
||||
loading = false
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
|
@ -16,8 +16,10 @@
|
||||
const pseudUrl =
|
||||
"https://www.coindesk.com/markets/2020/06/29/many-bitcoin-developers-are-choosing-to-use-pseudonyms-for-good-reason/"
|
||||
|
||||
const submit = () => {
|
||||
publishProfile(values).on("progress", toastProgress)
|
||||
const submit = async () => {
|
||||
const pub = await publishProfile(values)
|
||||
|
||||
pub.on("progress", toastProgress)
|
||||
|
||||
navigate(routes.person($session.pubkey))
|
||||
}
|
||||
|
@ -6,9 +6,9 @@
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import Heading from "src/partials/Heading.svelte"
|
||||
import {env, settings, publishSettings} from "src/engine2"
|
||||
import {env, getSettings, publishSettings} from "src/engine2"
|
||||
|
||||
let values = {...settings.get()}
|
||||
let values = getSettings()
|
||||
|
||||
const submit = () => {
|
||||
publishSettings(values)
|
||||
|
@ -1,3 +1,4 @@
|
||||
import {fromDisplayEvent} from "src/util/nostr"
|
||||
import {getSetting, getPublishHints} from "src/engine2/queries"
|
||||
import {publishEvent, getReplyTags} from "./util"
|
||||
import {Publisher} from "./publisher"
|
||||
@ -11,7 +12,7 @@ export const publishReply = (parent, content, tags = []) => {
|
||||
const relays = getPublishHints(getSetting("relay_limit"), parent)
|
||||
|
||||
// Re-broadcast the note we're replying to
|
||||
Publisher.publish({relays, event: parent})
|
||||
Publisher.publish({relays, event: fromDisplayEvent(parent)})
|
||||
|
||||
return publishEvent(1, {relays, content, tags: [...tags, ...getReplyTags(parent, true)]})
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import {assoc, whereEq, when, map} from "ramda"
|
||||
import {createMapOf} from "hurdak"
|
||||
import {now} from "src/util/misc"
|
||||
import {appDataKeys} from "src/util/nostr"
|
||||
import {EventKind} from "src/engine2/model"
|
||||
import {channels} from "src/engine2/state"
|
||||
import {user, nip04, getInboxHints, getSetting} from "src/engine2/queries"
|
||||
import {setAppData} from "./nip78"
|
||||
@ -10,7 +11,7 @@ import {publishEvent} from "./util"
|
||||
export const publishNip04Message = async (recipient, content, tags = [], relays = null) => {
|
||||
const pubkeys = [recipient, user.get().pubkey]
|
||||
|
||||
return publishEvent(4, {
|
||||
return publishEvent(EventKind.Nip04Message, {
|
||||
relays: relays || getInboxHints(getSetting("relay_limit"), pubkeys),
|
||||
content: await nip04.get().encryptAsUser(content, recipient),
|
||||
tags: [...tags, ["p", recipient]],
|
||||
|
@ -1,3 +1,4 @@
|
||||
import {fromDisplayEvent} from "src/util/nostr"
|
||||
import {getSetting, getPublishHints} from "src/engine2/queries"
|
||||
import {publishEvent, getReplyTags} from "./util"
|
||||
import {Publisher} from "./publisher"
|
||||
@ -6,7 +7,7 @@ export const publishReaction = (parent, content = "", tags = []) => {
|
||||
const relays = getPublishHints(getSetting("relay_limit"), parent)
|
||||
|
||||
// Re-broadcast the note we're reacting to
|
||||
Publisher.publish({relays, event: parent})
|
||||
Publisher.publish({relays, event: fromDisplayEvent(parent)})
|
||||
|
||||
return publishEvent(7, {relays, content, tags: [...tags, ...getReplyTags(parent)]})
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ import {buildEvent} from "./util"
|
||||
import {Publisher} from "./publisher"
|
||||
|
||||
// Use an ephemeral private key for user privacy
|
||||
export const publishReport = (content = "", tags = [], relays = null) =>
|
||||
export const publishReport = async (content = "", tags = [], relays = null) =>
|
||||
Publisher.publish({
|
||||
relays: relays || getUserRelayUrls("write"),
|
||||
event: signer.get().signWithKey(buildEvent(1984, {content, tags}), generatePrivateKey()),
|
||||
event: await signer.get().signWithKey(buildEvent(1984, {content, tags}), generatePrivateKey()),
|
||||
})
|
||||
|
@ -1,21 +1,19 @@
|
||||
import {appDataKeys} from "src/util/nostr"
|
||||
import {settings} from "src/engine2/state"
|
||||
import {canSign} from "src/engine2/queries"
|
||||
import {nip04, user} from "src/engine2/queries"
|
||||
import {settings, session} from "src/engine2/state"
|
||||
import {canSign, nip04} from "src/engine2/queries"
|
||||
import {publishEvent} from "./util"
|
||||
|
||||
export const setAppData = async (d: string, data: any) => {
|
||||
const {pubkey} = user.get()
|
||||
const json = JSON.stringify(data)
|
||||
const content = await nip04.get().encryptAsUser(json, pubkey)
|
||||
if (canSign.get()) {
|
||||
const {pubkey} = session.get()
|
||||
const json = JSON.stringify(data)
|
||||
const content = await nip04.get().encryptAsUser(json, pubkey)
|
||||
|
||||
return publishEvent(30078, {content, tags: [["d", d]]})
|
||||
return publishEvent(30078, {content, tags: [["d", d]]})
|
||||
}
|
||||
}
|
||||
|
||||
export const publishSettings = async (updates: Record<string, any>) => {
|
||||
settings.update($settings => ({...$settings, ...updates}))
|
||||
|
||||
if (canSign.get()) {
|
||||
setAppData(appDataKeys.USER_SETTINGS, settings.get())
|
||||
}
|
||||
setAppData(appDataKeys.USER_SETTINGS, settings.get())
|
||||
}
|
||||
|
@ -15,7 +15,10 @@ export type EventOpts = {
|
||||
tags?: string[][]
|
||||
}
|
||||
|
||||
export function buildEvent(kind: number, {content = "", tags = [], created_at = null}: EventOpts) {
|
||||
export const buildEvent = (
|
||||
kind: number,
|
||||
{content = "", tags = [], created_at = null}: EventOpts
|
||||
) => {
|
||||
return {kind, content, tags, created_at: created_at || now()}
|
||||
}
|
||||
|
||||
@ -23,10 +26,14 @@ export type PublishOpts = EventOpts & {
|
||||
relays?: string[]
|
||||
}
|
||||
|
||||
export function publishEvent(kind: number, {relays, content = "", tags = []}: PublishOpts) {
|
||||
export const publishEvent = async (
|
||||
kind: number,
|
||||
{relays, content = "", tags = []}: PublishOpts
|
||||
) => {
|
||||
return Publisher.publish({
|
||||
timeout: 5000,
|
||||
relays: relays || getUserRelayUrls("write"),
|
||||
event: signer.get().signAsUser(
|
||||
event: await signer.get().signAsUser(
|
||||
buildEvent(kind, {
|
||||
content,
|
||||
tags: uniqTags([...tags, tagsFromContent(content)]),
|
||||
|
@ -2,6 +2,59 @@ import type {Event as NostrToolsEvent, UnsignedEvent} from "nostr-tools"
|
||||
|
||||
// Message types
|
||||
|
||||
export enum EventKind {
|
||||
Profile = 0,
|
||||
Note = 1,
|
||||
RecommendRelay = 2,
|
||||
Petnames = 3,
|
||||
Nip04Message = 4,
|
||||
Delete = 5,
|
||||
Repost = 6,
|
||||
Reaction = 7,
|
||||
BadgeAward = 8,
|
||||
GenericRepost = 16,
|
||||
ChannelCreation = 40,
|
||||
ChannelMetadata = 41,
|
||||
ChannelMessage = 42,
|
||||
ChannelHideMessage = 43,
|
||||
ChannelMuteUser = 44,
|
||||
FileMetadata = 1063,
|
||||
LiveChatMessage = 1311,
|
||||
Report = 1984,
|
||||
Label = 1985,
|
||||
CommunityPostApproval = 4550,
|
||||
ZapGoal = 9041,
|
||||
ZapRequest = 9734,
|
||||
Zap = 9735,
|
||||
MuteList = 10000,
|
||||
PinList = 10001,
|
||||
RelayList = 10002,
|
||||
WalletInfo = 13194,
|
||||
ClientAuth = 22242,
|
||||
WalletRequest = 23194,
|
||||
WalletResponse = 23195,
|
||||
NostrConnect = 24133,
|
||||
HTTPAuth = 27235,
|
||||
PeopleList = 30000,
|
||||
BookmarkList = 30001,
|
||||
ProfileBadges = 30008,
|
||||
BadgeDefinition = 30009,
|
||||
Post = 30023,
|
||||
PostDraft = 30024,
|
||||
AppData = 30078,
|
||||
LiveEvent = 30311,
|
||||
UserStatus = 30315,
|
||||
Classified = 30402,
|
||||
ClassifiedDraft = 30403,
|
||||
CalendarEventDate = 31922,
|
||||
CalendarEventTime = 31923,
|
||||
Calendar = 31924,
|
||||
CalendarRSVP = 31925,
|
||||
HandlerRecommendation = 31989,
|
||||
HandlerInformation = 31990,
|
||||
CommunityDefinition = 34550,
|
||||
}
|
||||
|
||||
export type NostrEvent = NostrToolsEvent
|
||||
|
||||
export type Event = Omit<NostrToolsEvent, "kind"> & {
|
||||
@ -14,6 +67,18 @@ export type Rumor = UnsignedEvent & {
|
||||
id: string
|
||||
}
|
||||
|
||||
export type ZapEvent = Event & {
|
||||
invoiceAmount: number
|
||||
request: Event
|
||||
}
|
||||
|
||||
export type DisplayEvent = Event & {
|
||||
zaps: Event[]
|
||||
replies: DisplayEvent[]
|
||||
reactions: Event[]
|
||||
matchesFilter?: boolean
|
||||
}
|
||||
|
||||
export type Filter = {
|
||||
ids?: string[]
|
||||
kinds?: number[]
|
||||
@ -39,18 +104,6 @@ export type Session = {
|
||||
bunkerToken?: string
|
||||
}
|
||||
|
||||
export type ZapEvent = Event & {
|
||||
invoiceAmount: number
|
||||
request: Event
|
||||
}
|
||||
|
||||
export type DisplayEvent = Event & {
|
||||
zaps: Event[]
|
||||
replies: DisplayEvent[]
|
||||
reactions: Event[]
|
||||
matchesFilter?: boolean
|
||||
}
|
||||
|
||||
export type RelayInfo = {
|
||||
contact?: string
|
||||
description?: string
|
||||
@ -147,6 +200,11 @@ export type Topic = {
|
||||
last_seen?: number
|
||||
}
|
||||
|
||||
export type Delete = {
|
||||
value: string
|
||||
created_at: number
|
||||
}
|
||||
|
||||
export type List = {
|
||||
name: string
|
||||
naddr: string
|
||||
|
@ -3,22 +3,22 @@ import type {Event} from "src/engine2/model"
|
||||
import {session, events, alerts} from "src/engine2/state"
|
||||
import {projections} from "src/engine2/projections/core"
|
||||
|
||||
const isMention = (e: Event) => Tags.from(e).pubkeys().includes(session.get().pubkey)
|
||||
const isMention = (e: Event) => Tags.from(e).pubkeys().includes(session.get()?.pubkey)
|
||||
|
||||
const isUserEvent = (id: string) => events.key(id).get()?.pubkey === session.get().pubkey
|
||||
const isUserEvent = (id: string) => events.key(id).get()?.pubkey === session.get()?.pubkey
|
||||
|
||||
const isDescendant = (e: Event) => isUserEvent(findRootId(e))
|
||||
|
||||
const isReply = (e: Event) => isUserEvent(findReplyId(e))
|
||||
|
||||
const handleNotification = (e: Event) => {
|
||||
const $pubkey = session.get().pubkey
|
||||
const $session = session.get()
|
||||
|
||||
if (!$pubkey || e.pubkey === $pubkey) {
|
||||
if (!$session || e.pubkey === $session.pubkey) {
|
||||
return
|
||||
}
|
||||
|
||||
alerts.key(e.id).set({...e, recipient: $pubkey})
|
||||
alerts.key(e.id).set({...e, recipient: $session.pubkey})
|
||||
}
|
||||
|
||||
noteKinds.forEach(kind => {
|
||||
|
@ -23,5 +23,5 @@ export const updateKey = (key, timestamp, updates, modify = (a: any) => a) => {
|
||||
}
|
||||
}
|
||||
|
||||
key.set(record)
|
||||
key.set(modify(record))
|
||||
}
|
||||
|
@ -1,34 +1,44 @@
|
||||
import {uniq, prop, uniqBy} from "ramda"
|
||||
import {tryFunc} from "hurdak"
|
||||
import {tryFunc, sleep} from "hurdak"
|
||||
import {tryJson} from "src/util/misc"
|
||||
import {Tags, appDataKeys} from "src/util/nostr"
|
||||
import type {Channel} from "src/engine2/model"
|
||||
import {EventKind} from "src/engine2/model"
|
||||
import {channels} from "src/engine2/state"
|
||||
import {user, nip04, canSign} from "src/engine2/queries"
|
||||
import {projections} from "src/engine2/projections/core"
|
||||
|
||||
projections.addHandler(30078, async e => {
|
||||
if (canSign.get() && Tags.from(e).getMeta("d") === appDataKeys.NIP04_LAST_CHECKED) {
|
||||
await tryJson(async () => {
|
||||
const payload = await nip04.get().decryptAsUser(e.content, user.get().pubkey)
|
||||
channels.mapStore.updateAsync(async $channels => {
|
||||
await tryJson(async () => {
|
||||
const payload = JSON.parse(await nip04.get().decryptAsUser(e.content, user.get().pubkey))
|
||||
|
||||
for (const [id, ts] of Object.entries(payload) as [string, number][]) {
|
||||
// Ignore weird old stuff
|
||||
if (id.includes('/')) {
|
||||
continue
|
||||
for (const [id, ts] of Object.entries(payload) as [string, number][]) {
|
||||
// Ignore weird old stuff
|
||||
if (id === "undefined" || id.includes("/")) {
|
||||
continue
|
||||
}
|
||||
|
||||
const channel =
|
||||
$channels.get(id) || ({id, type: "nip04", relays: e.seen_on, messages: []} as Channel)
|
||||
|
||||
$channels.set(id, {
|
||||
...channel,
|
||||
last_checked: Math.max(ts, channel.last_checked || 0),
|
||||
})
|
||||
|
||||
// No need to lock up the UI decrypting a bunch of stuff
|
||||
await sleep(16)
|
||||
}
|
||||
})
|
||||
|
||||
const channel = channels.key(id)
|
||||
|
||||
channel.merge({
|
||||
last_checked: Math.max(ts, channel.get()?.last_checked || 0),
|
||||
})
|
||||
}
|
||||
return $channels
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
projections.addHandler(4, async e => {
|
||||
projections.addHandler(EventKind.Nip04Message, async e => {
|
||||
if (!canSign.get()) {
|
||||
return
|
||||
}
|
||||
|
@ -6,9 +6,7 @@ import {projections, updateKey} from "src/engine2/projections/core"
|
||||
|
||||
projections.addHandler(0, e => {
|
||||
tryJson(async () => {
|
||||
const {
|
||||
kind0: {nip05: address},
|
||||
} = JSON.parse(e.content)
|
||||
const {nip05: address} = JSON.parse(e.content)
|
||||
|
||||
if (!address) {
|
||||
return
|
||||
|
@ -8,6 +8,6 @@ projections.addHandler(5, e => {
|
||||
.values()
|
||||
.all()
|
||||
.forEach(value => {
|
||||
deletes.key(value).set({value})
|
||||
deletes.key(value).set({value, created_at: e.created_at})
|
||||
})
|
||||
})
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {prop, map, assocPath, pluck, last, uniqBy, uniq} from "ramda"
|
||||
import {prop, pipe, assoc, map, assocPath, pluck, last, uniqBy, uniq} from "ramda"
|
||||
import {Tags, appDataKeys} from "src/util/nostr"
|
||||
import {tryJson} from "src/util/misc"
|
||||
import type {Event, Channel} from "src/engine2/model"
|
||||
@ -15,7 +15,7 @@ projections.addHandler(40, (e: Event) => {
|
||||
channels.key(e.id),
|
||||
e.created_at,
|
||||
{meta, relays},
|
||||
assocPath(["nip28", "owner"], e.pubkey)
|
||||
pipe(assoc("type", "nip28"), assocPath(["nip28", "owner"], e.pubkey))
|
||||
)
|
||||
}
|
||||
})
|
||||
@ -48,7 +48,7 @@ projections.addHandler(30078, async (e: Event) => {
|
||||
|
||||
if (Tags.from(e).getMeta("d") === appDataKeys.NIP28_ROOMS_JOINED) {
|
||||
await tryJson(async () => {
|
||||
const channelIds = await nip04.get().decryptAsUser(e.content, e.pubkey)
|
||||
const channelIds = JSON.parse(await nip04.get().decryptAsUser(e.content, e.pubkey))
|
||||
|
||||
// Just a bug from when I was building the feature, remove someday
|
||||
if (!Array.isArray(channelIds)) {
|
||||
@ -71,7 +71,7 @@ projections.addHandler(30078, async (e: Event) => {
|
||||
|
||||
if (Tags.from(e).getMeta("d") === appDataKeys.NIP28_LAST_CHECKED) {
|
||||
await tryJson(async () => {
|
||||
const payload = await nip04.get().decryptAsUser(e.content, e.pubkey)
|
||||
const payload = JSON.parse(await nip04.get().decryptAsUser(e.content, e.pubkey))
|
||||
|
||||
for (const key of Object.keys(payload)) {
|
||||
// Backwards compat from when we used to prefix id/pubkey
|
||||
|
@ -6,9 +6,7 @@ import {projections, updateKey} from "src/engine2/projections/core"
|
||||
|
||||
projections.addHandler(0, e => {
|
||||
tryJson(async () => {
|
||||
const {
|
||||
kind0: {lud16, lud06},
|
||||
} = JSON.parse(e.content)
|
||||
const {lud16, lud06} = JSON.parse(e.content)
|
||||
const address = (lud16 || lud06 || "").toLowerCase()
|
||||
|
||||
if (!address) {
|
||||
|
@ -11,7 +11,7 @@ projections.addHandler(30078, e => {
|
||||
e.created_at > getSetting("updated_at")
|
||||
) {
|
||||
tryJson(async () => {
|
||||
const updates = await nip04.get().decryptAsUser(e.content, user.get().pubkey)
|
||||
const updates = JSON.parse(await nip04.get().decryptAsUser(e.content, user.get().pubkey))
|
||||
|
||||
settings.update($settings => ({
|
||||
...$settings,
|
||||
|
@ -3,7 +3,8 @@ import {Plex, Relays, Executor} from "paravel"
|
||||
import {error, warn} from "src/util/logger"
|
||||
import {normalizeRelayUrl} from "src/util/nostr"
|
||||
import {writable} from "src/engine2/util"
|
||||
import {env, pool, settings} from "src/engine2/state"
|
||||
import {env, pool} from "src/engine2/state"
|
||||
import {getSetting} from "src/engine2/queries"
|
||||
|
||||
export const authHandler = writable(null)
|
||||
|
||||
@ -30,7 +31,7 @@ export const getUrls = (relays: string[]) => {
|
||||
export const getExecutor = (urls: string[], {bypassBoot = false} = {}) => {
|
||||
let target
|
||||
|
||||
const {multiplextr_url: muxUrl} = settings.get()
|
||||
const muxUrl = getSetting("multiplextr_url")
|
||||
|
||||
// Try to use our multiplexer, but if it fails to connect fall back to relays. If
|
||||
// we're only connecting to a single relay, just do it directly, unless we already
|
||||
|
@ -6,7 +6,7 @@ import type {Person} from "src/engine2/model"
|
||||
import {topics, people} from "src/engine2/state"
|
||||
|
||||
export const peopleWithName = people.derived($people =>
|
||||
$people.filter(({profile: p}) => p.name || p.nip05 || p.display_name)
|
||||
$people.filter(({profile: p}) => p?.name || p?.nip05 || p?.display_name)
|
||||
)
|
||||
|
||||
export const derivePerson = pubkey => people.key(pubkey).derived(defaultTo({pubkey}))
|
||||
|
@ -3,7 +3,7 @@ import {difference} from "hurdak"
|
||||
import {people} from "src/engine2/state"
|
||||
import {user} from "src/engine2/queries"
|
||||
|
||||
export const follows = user.derived($user => ($user.petnames || []).map(nth(1)) as string[])
|
||||
export const follows = user.derived($user => ($user?.petnames || []).map(nth(1)) as string[])
|
||||
|
||||
export const followsSet = follows.derived($follows => new Set($follows))
|
||||
|
||||
|
@ -5,7 +5,7 @@ import {findReplyAndRootIds} from "src/util/nostr"
|
||||
import {session, lists} from "src/engine2/state"
|
||||
import {user} from "src/engine2/queries/session"
|
||||
|
||||
export const mutes = user.derived($user => ($user.mutes || []).map(nth(1)))
|
||||
export const mutes = user.derived($user => ($user?.mutes || []).map(nth(1)))
|
||||
|
||||
export const mutesSet = mutes.derived($mutes => new Set($mutes))
|
||||
|
||||
|
@ -1,6 +1,19 @@
|
||||
import {settings} from "src/engine2/state"
|
||||
import {prop} from "ramda"
|
||||
import {env, settings} from "src/engine2/state"
|
||||
|
||||
export const getSetting = k => settings.get()[k]
|
||||
export const getDefaultSettings = () => ({
|
||||
relay_limit: 10,
|
||||
default_zap: 21,
|
||||
show_media: true,
|
||||
report_analytics: true,
|
||||
imgproxy_url: env.get().IMGPROXY_URL,
|
||||
dufflepud_url: env.get().DUFFLEPUD_URL,
|
||||
multiplextr_url: env.get().MULTIPLEXTR_URL,
|
||||
})
|
||||
|
||||
export const getSettings = () => ({...getDefaultSettings(), ...settings.get()})
|
||||
|
||||
export const getSetting = k => prop(k, getSettings())
|
||||
|
||||
export const imgproxy = (url: string, {w = 640, h = 1024} = {}) => {
|
||||
const base = getSetting("imgproxy_url")
|
||||
|
@ -1,4 +1,3 @@
|
||||
import {whereEq} from "ramda"
|
||||
import {nip19} from "nostr-tools"
|
||||
import {derived} from "src/engine2/util/store"
|
||||
import {session, people} from "src/engine2/state"
|
||||
@ -24,11 +23,11 @@ export const stateKey = session.derived($s => $s?.pubkey || "anonymous")
|
||||
|
||||
export const user = derived([session, people.mapStore], ([$s, $p]) => $p.get($s?.pubkey))
|
||||
|
||||
export const canSign = session.derived(({method}) =>
|
||||
["bunker", "privkey", "extension"].includes(method)
|
||||
export const canSign = session.derived($session =>
|
||||
["bunker", "privkey", "extension"].includes($session?.method)
|
||||
)
|
||||
|
||||
export const canUseGiftWrap = session.derived(whereEq({method: "privkey"}))
|
||||
export const canUseGiftWrap = session.derived($session => $session?.method === "privkey")
|
||||
|
||||
export const ndk = derived([session, ndkInstances], ([$session, $instances]) => {
|
||||
if (!$session?.bunkerToken) {
|
||||
|
@ -7,8 +7,9 @@ import {findReplyAndRootIds, findReplyId, findRootId, Tags, noteKinds} from "src
|
||||
import {collection} from "src/engine2/util/store"
|
||||
import type {Collection} from "src/engine2/util/store"
|
||||
import type {Event, DisplayEvent, Filter} from "src/engine2/model"
|
||||
import {settings, env} from "src/engine2/state"
|
||||
import {env} from "src/engine2/state"
|
||||
import {
|
||||
getSetting,
|
||||
mergeHints,
|
||||
isEventMuted,
|
||||
getReplyHints,
|
||||
@ -84,16 +85,12 @@ export class ContextLoader {
|
||||
return events
|
||||
}
|
||||
|
||||
getRelayLimit() {
|
||||
return settings.get().relay_limit
|
||||
}
|
||||
|
||||
mergeHints(groups: string[][]) {
|
||||
if (this.opts.relays) {
|
||||
return this.opts.relays
|
||||
}
|
||||
|
||||
return mergeHints(this.getRelayLimit(), groups)
|
||||
return mergeHints(getSetting("relay_limit"), groups)
|
||||
}
|
||||
|
||||
applyContext = (notes: Event[], {substituteParents = false, alreadySeen = new Set()} = {}) => {
|
||||
@ -180,11 +177,11 @@ export class ContextLoader {
|
||||
const {root, reply} = findReplyAndRootIds(e)
|
||||
|
||||
if (reply && !this.seen.has(reply)) {
|
||||
info.push({id: reply, hints: getParentHints(this.getRelayLimit(), e)})
|
||||
info.push({id: reply, hints: getParentHints(getSetting("relay_limit"), e)})
|
||||
}
|
||||
|
||||
if (root && !this.seen.has(root)) {
|
||||
info.push({id: findRootId(e), hints: getRootHints(this.getRelayLimit(), e)})
|
||||
info.push({id: findRootId(e), hints: getRootHints(getSetting("relay_limit"), e)})
|
||||
}
|
||||
|
||||
return info
|
||||
@ -220,7 +217,7 @@ export class ContextLoader {
|
||||
|
||||
for (const c of chunk(256, events)) {
|
||||
load({
|
||||
relays: this.mergeHints(c.map(e => getReplyHints(this.getRelayLimit(), e))),
|
||||
relays: this.mergeHints(c.map(e => getReplyHints(getSetting("relay_limit"), e))),
|
||||
filters: [{kinds: this.getReplyKinds(), "#e": pluck("id", c as Event[])}],
|
||||
onEvent: batch(100, (context: Event[]) => this.addContext(context, {depth: depth - 1})),
|
||||
})
|
||||
@ -228,7 +225,7 @@ export class ContextLoader {
|
||||
}
|
||||
})
|
||||
|
||||
listenForContext = throttle(5000, () => {
|
||||
listenForContext = throttle(10_000, () => {
|
||||
if (this.stopped) {
|
||||
return
|
||||
}
|
||||
@ -248,7 +245,7 @@ export class ContextLoader {
|
||||
for (const c of chunk(256, findNotes(this.data.get()))) {
|
||||
this.addSubs([
|
||||
subscribe({
|
||||
relays: this.mergeHints(c.map(e => getReplyHints(this.getRelayLimit(), e))),
|
||||
relays: this.mergeHints(c.map(e => getReplyHints(getSetting("relay_limit"), e))),
|
||||
filters: [{kinds: this.getReplyKinds(), "#e": pluck("id", c), since: now()}],
|
||||
onEvent: batch(100, (context: Event[]) => this.addContext(context, {depth: 2})),
|
||||
}),
|
||||
|
20
src/engine2/requests/filter.ts
Normal file
20
src/engine2/requests/filter.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import {omit} from "ramda"
|
||||
import {shuffle} from "hurdak"
|
||||
import type {DynamicFilter, Filter} from "src/engine2/model"
|
||||
import {env} from "src/engine2/state"
|
||||
import {follows, network} from "src/engine2/queries"
|
||||
|
||||
export const getPubkeysWithDefaults = (pubkeys: string[]) =>
|
||||
shuffle(pubkeys.length > 0 ? pubkeys : (env.get().DEFAULT_FOLLOWS as string[])).slice(0, 1024)
|
||||
|
||||
export const compileFilter = (filter: DynamicFilter): Filter => {
|
||||
if (filter.authors === "global") {
|
||||
filter = omit(["authors"], filter)
|
||||
} else if (filter.authors === "follows") {
|
||||
filter = {...filter, authors: getPubkeysWithDefaults(follows.get())}
|
||||
} else if (filter.authors === "network") {
|
||||
filter = {...filter, authors: getPubkeysWithDefaults(network.get())}
|
||||
}
|
||||
|
||||
return filter as Filter
|
||||
}
|
@ -2,6 +2,7 @@ export * from "./context"
|
||||
export * from "./cursor"
|
||||
export * from "./feed"
|
||||
export * from "./pubkeys"
|
||||
export * from "./filter"
|
||||
export * from "./count"
|
||||
export * from "./load"
|
||||
export * from "./subscription"
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {matchFilters} from "nostr-tools"
|
||||
import {prop, groupBy, uniq} from "ramda"
|
||||
import {batch} from "hurdak"
|
||||
import {defer} from "hurdak"
|
||||
import {pushToKey} from "src/util/misc"
|
||||
import {subscribe} from "./subscription"
|
||||
import type {Event, Filter} from "src/engine2/model"
|
||||
|
||||
@ -12,16 +13,17 @@ export type LoadOpts = {
|
||||
onClose?: (events: Event[]) => void
|
||||
}
|
||||
|
||||
export const calculateGroup = ({since, until, ...filter}: Filter) => {
|
||||
export type LoadItem = {
|
||||
request: LoadOpts
|
||||
result: ReturnType<typeof defer>
|
||||
}
|
||||
|
||||
export const calculateGroup = ({limit, since, until, ...filter}: Filter) => {
|
||||
const group = Object.keys(filter)
|
||||
|
||||
if (since) {
|
||||
group.push(`since:${since}`)
|
||||
}
|
||||
|
||||
if (until) {
|
||||
group.push(`until:${until}`)
|
||||
}
|
||||
if (since) group.push(`since:${since}`)
|
||||
if (limit) group.push(`limit:${limit}`)
|
||||
if (until) group.push(`until:${until}`)
|
||||
|
||||
return group.sort().join("-")
|
||||
}
|
||||
@ -33,7 +35,11 @@ export const combineFilters = filters => {
|
||||
const newFilter = {}
|
||||
|
||||
for (const k of Object.keys(group[0])) {
|
||||
newFilter[k] = uniq(group.flatMap(prop(k)))
|
||||
if (["since", "until", "limit"].includes(k)) {
|
||||
newFilter[k] = group[0][k]
|
||||
} else {
|
||||
newFilter[k] = uniq(group.flatMap(prop(k)))
|
||||
}
|
||||
}
|
||||
|
||||
result.push(newFilter)
|
||||
@ -42,35 +48,59 @@ export const combineFilters = filters => {
|
||||
return result
|
||||
}
|
||||
|
||||
export const load = batch(500, (requests: LoadOpts[]) => {
|
||||
const relays = uniq(requests.flatMap(prop("relays")))
|
||||
const filters = combineFilters(requests.flatMap(prop("filters")))
|
||||
const queue = []
|
||||
|
||||
const sub = subscribe({relays, filters, timeout: 30_000})
|
||||
export const execute = () => {
|
||||
const itemsByRelay = {}
|
||||
for (const item of queue.splice(0)) {
|
||||
for (const url of item.request.relays) {
|
||||
pushToKey(itemsByRelay, url, item)
|
||||
}
|
||||
}
|
||||
|
||||
sub.on("event", (e: Event) => {
|
||||
for (const req of requests) {
|
||||
if (!req.onEvent) {
|
||||
continue
|
||||
// Group by relay, then by filter
|
||||
for (const [url, items] of Object.entries(itemsByRelay) as [string, LoadItem[]][]) {
|
||||
const filters = combineFilters(items.flatMap(item => item.request.filters))
|
||||
|
||||
const sub = subscribe({
|
||||
filters,
|
||||
relays: [url],
|
||||
timeout: 15000,
|
||||
onEvent: e => {
|
||||
for (const {request} of items) {
|
||||
if (request.onEvent && matchFilters(request.filters, e)) {
|
||||
request.onEvent(e)
|
||||
}
|
||||
}
|
||||
},
|
||||
onEose: url => {
|
||||
for (const {request} of items) {
|
||||
request.onEose?.(url)
|
||||
}
|
||||
},
|
||||
onClose: events => {
|
||||
for (const {request} of items) {
|
||||
request.onClose?.(events)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
sub.result.then(events => {
|
||||
for (const item of items) {
|
||||
item.result.resolve(events)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (matchFilters(req.filters, e)) {
|
||||
req.onEvent(e)
|
||||
}
|
||||
}
|
||||
})
|
||||
export const load = (request: LoadOpts) => {
|
||||
const result = defer()
|
||||
|
||||
sub.on("eose", url => {
|
||||
for (const req of requests) {
|
||||
req.onEose?.(url)
|
||||
}
|
||||
})
|
||||
if (queue.length === 0) {
|
||||
setTimeout(execute, 500)
|
||||
}
|
||||
|
||||
sub.on("close", events => {
|
||||
for (const req of requests) {
|
||||
req.onClose?.(events)
|
||||
}
|
||||
})
|
||||
queue.push({request, result})
|
||||
|
||||
return sub.result
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
@ -1,14 +1,37 @@
|
||||
import {user, getInboxHints, getSetting} from "src/engine2/queries"
|
||||
import {pluck} from "ramda"
|
||||
import {batch, seconds} from "hurdak"
|
||||
import {now} from "src/util/misc"
|
||||
import {EventKind} from "src/engine2/model"
|
||||
import {session} from "src/engine2/state"
|
||||
import {getInboxHints, getUserRelayUrls, getSetting} from "src/engine2/queries"
|
||||
import {load} from "./load"
|
||||
import {loadPubkeys} from "./pubkeys"
|
||||
import {subscribe} from "./subscription"
|
||||
|
||||
export function loadAllNip04Messages() {
|
||||
const {pubkey} = session.get()
|
||||
const since = now() - seconds(90, "day")
|
||||
|
||||
load({
|
||||
relays: getUserRelayUrls("read"),
|
||||
filters: [
|
||||
{kinds: [4], authors: [pubkey], since},
|
||||
{kinds: [4], "#p": [pubkey], since},
|
||||
],
|
||||
onEvent: batch(1000, events => {
|
||||
loadPubkeys(pluck("pubkey", events))
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
export function listenForNip04Messages(contactPubkey: string) {
|
||||
const {pubkey: userPubkey} = user.get()
|
||||
const {pubkey: userPubkey} = session.get()
|
||||
|
||||
return subscribe({
|
||||
relays: getInboxHints(getSetting("relay_limit"), [contactPubkey, userPubkey]),
|
||||
filters: [
|
||||
{kinds: [4], authors: [userPubkey], "#p": [contactPubkey]},
|
||||
{kinds: [4], authors: [contactPubkey], "#p": [userPubkey]},
|
||||
{kinds: [EventKind.Nip04Message], authors: [userPubkey], "#p": [contactPubkey]},
|
||||
{kinds: [EventKind.Nip04Message], authors: [contactPubkey], "#p": [userPubkey]},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import {getUserRelayUrls} from "src/engine2/queries"
|
||||
import {load} from "./load"
|
||||
|
||||
export const loadDeletes = () => {
|
||||
const since = sumBy(prop("value"), deletes.get())
|
||||
const since = sumBy(prop("created_at"), deletes.get().filter(prop("created_at"))) || 0
|
||||
const authors = Object.values(sessions.get()).map(prop("pubkey"))
|
||||
|
||||
return load({
|
||||
|
@ -1,10 +1,10 @@
|
||||
import {without, uniq} from "ramda"
|
||||
import {chunk, seconds, ensurePlural} from "hurdak"
|
||||
import {chunk, seconds} from "hurdak"
|
||||
import {personKinds, appDataKeys} from "src/util/nostr"
|
||||
import {now} from "src/util/misc"
|
||||
import type {Filter} from "src/engine2/model"
|
||||
import {people, settings} from "src/engine2/state"
|
||||
import {mergeHints, getPubkeyHints} from "src/engine2/queries"
|
||||
import {people} from "src/engine2/state"
|
||||
import {getSetting, mergeHints, getPubkeyHints} from "src/engine2/queries"
|
||||
import {load} from "./load"
|
||||
|
||||
export type LoadPeopleOpts = {
|
||||
@ -28,7 +28,7 @@ export const getStalePubkeys = (pubkeys: string[]) => {
|
||||
|
||||
const key = people.key(pubkey)
|
||||
|
||||
if (key.get()?.last_fetched || 0 > since) {
|
||||
if ((key.get()?.last_fetched || 0) > since) {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -41,10 +41,9 @@ export const getStalePubkeys = (pubkeys: string[]) => {
|
||||
}
|
||||
|
||||
export const loadPubkeys = async (
|
||||
pubkeyGroups: string | string[],
|
||||
rawPubkeys: string[],
|
||||
{relays, force, kinds = personKinds}: LoadPeopleOpts = {}
|
||||
) => {
|
||||
const rawPubkeys = ensurePlural(pubkeyGroups).reduce((a, b) => a.concat(b), [])
|
||||
const pubkeys = force ? uniq(rawPubkeys) : getStalePubkeys(rawPubkeys)
|
||||
|
||||
const getChunkRelays = (chunk: string[]) => {
|
||||
@ -52,7 +51,7 @@ export const loadPubkeys = async (
|
||||
return relays
|
||||
}
|
||||
|
||||
const {relay_limit: limit} = settings.get()
|
||||
const limit = getSetting("relay_limit")
|
||||
|
||||
return mergeHints(
|
||||
limit,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {Pool} from "paravel"
|
||||
import {collection, writable} from "src/engine2/util/store"
|
||||
import type {Event, Session, Channel, Topic, List, Person, Relay} from "src/engine2/model"
|
||||
import type {Event, Delete, Session, Channel, Topic, List, Person, Relay} from "src/engine2/model"
|
||||
|
||||
// Synchronous stores
|
||||
|
||||
@ -14,7 +14,7 @@ export const alertsLastChecked = writable(0)
|
||||
|
||||
export const alerts = collection<Event & {recipient: string}>("id")
|
||||
export const events = collection<Event>("id")
|
||||
export const deletes = collection<{value: string}>("value")
|
||||
export const deletes = collection<Delete>("value")
|
||||
export const labels = collection<Event>("id")
|
||||
export const topics = collection<Topic>("name")
|
||||
export const lists = collection<List>("naddr")
|
||||
|
@ -225,7 +225,7 @@ export class Storage {
|
||||
if (window.indexedDB) {
|
||||
const dbConfig = indexedDBAdapters.map(adapter => adapter.getIndexedDBConfig())
|
||||
|
||||
this.db = new IndexedDB("nostr-engine/Storage", 2, dbConfig)
|
||||
this.db = new IndexedDB("nostr-engine/Storage", 4, dbConfig)
|
||||
|
||||
window.addEventListener("beforeunload", () => this.close())
|
||||
|
||||
|
@ -41,6 +41,10 @@ export class Writable<T> implements Readable<T> {
|
||||
this.set(f(this.value))
|
||||
}
|
||||
|
||||
async updateAsync(f: (v: T) => Promise<T>) {
|
||||
this.set(await f(this.value))
|
||||
}
|
||||
|
||||
subscribe(f: Subscriber<T>) {
|
||||
this.subs.push(f)
|
||||
|
||||
@ -149,7 +153,11 @@ export class Key<T extends R> implements Readable<T> {
|
||||
}
|
||||
|
||||
// Make sure the pk always get set on the record
|
||||
m.set(this.key, f({...m.get(this.key), [this.pk]: this.key}))
|
||||
const {pk, key} = this
|
||||
const oldValue = {...m.get(key), [pk]: key}
|
||||
const newValue = {...f(oldValue), [pk]: key}
|
||||
|
||||
m.set(this.key, newValue)
|
||||
|
||||
return m
|
||||
})
|
||||
@ -192,6 +200,11 @@ export class Collection<T extends R> implements Readable<T[]> {
|
||||
update = (f: (v: T[]) => T[]) =>
|
||||
this.mapStore.update(m => new Map(f(Array.from(m.values())).map(x => [x[this.pk], x])))
|
||||
|
||||
updateAsync = async (f: (v: T[]) => Promise<T[]>) =>
|
||||
this.mapStore.updateAsync(
|
||||
async m => new Map((await f(Array.from(m.values()))).map(x => [x[this.pk], x]))
|
||||
)
|
||||
|
||||
reject = (f: (v: T) => boolean) => this.update(reject(f))
|
||||
|
||||
filter = (f: (v: T) => boolean) => this.update(filter(f))
|
||||
|
@ -73,12 +73,12 @@
|
||||
// Group messages so we're only showing the person once per chunk
|
||||
$: {
|
||||
if (groupedMessages?.length === messages.length) {
|
||||
scroller.stop()
|
||||
scroller?.stop()
|
||||
}
|
||||
|
||||
const result = reverse(
|
||||
sortBy(prop("created_at"), messages).reduce((mx, m) => {
|
||||
return mx.concat({...m, showProfile: m.pubkey !== last(mx).pubkey})
|
||||
return mx.concat({...m, showProfile: m.pubkey !== last(mx)?.pubkey})
|
||||
}, [])
|
||||
)
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {nip19} from "nostr-tools"
|
||||
import {is, fromPairs, mergeLeft, last, identity, prop, flatten, uniq} from "ramda"
|
||||
import {omit, is, fromPairs, mergeLeft, last, identity, prop, flatten, uniq} from "ramda"
|
||||
import {ensurePlural, between, mapVals, tryFunc, avg, first} from "hurdak"
|
||||
import type {Filter, Event, DisplayEvent} from "src/engine2/model"
|
||||
import {tryJson} from "src/util/misc"
|
||||
@ -162,6 +162,9 @@ export const asDisplayEvent = (event: Event): DisplayEvent => ({
|
||||
...event,
|
||||
})
|
||||
|
||||
export const fromDisplayEvent = e =>
|
||||
omit(["replies", "reactions", "zaps", "matchesFilter"], e) as Event
|
||||
|
||||
export const toHex = (data: string): string | null => {
|
||||
if (data.match(/[a-zA-Z0-9]{64}/)) {
|
||||
return data
|
||||
|
Loading…
Reference in New Issue
Block a user