mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-29 16:31:04 +00:00
Remove engine entirely
This commit is contained in:
parent
8ba9546fd6
commit
8b9e069f1d
@ -4,16 +4,15 @@
|
||||
|
||||
import type {ComponentType, SvelteComponentTyped} from "svelte"
|
||||
import {nip19} from "nostr-tools"
|
||||
import type {Relay} from "src/engine"
|
||||
import {onMount} from "svelte"
|
||||
import {Router, links} from "svelte-routing"
|
||||
import {globalHistory} from "svelte-routing/src/history"
|
||||
import {isNil, find, last} from "ramda"
|
||||
import {seconds, Fetch, shuffle} from "hurdak"
|
||||
import {tryFetch, hexToBech32, bech32ToHex, now} from "src/util/misc"
|
||||
import {storage, relays, getSetting, dufflepud} from "src/engine2"
|
||||
import type {Relay} from "src/engine2"
|
||||
import {storage, session, stateKey, relays, getSetting, dufflepud} from "src/engine2"
|
||||
import * as engine from "src/engine2"
|
||||
import {Keys} from "src/app/engine"
|
||||
import {loadAppData} from "src/app/state"
|
||||
import {theme, getThemeVariables, appName, modal} from "src/partials/state"
|
||||
import {logUsage} from "src/app/state"
|
||||
@ -106,10 +105,8 @@
|
||||
}
|
||||
})
|
||||
|
||||
const {pubkey} = Keys
|
||||
|
||||
storage.ready.then(() => {
|
||||
if ($pubkey) {
|
||||
if ($session) {
|
||||
loadAppData()
|
||||
}
|
||||
|
||||
@ -147,7 +144,7 @@
|
||||
|
||||
<TypedRouter url={pathname}>
|
||||
<div use:links>
|
||||
{#key $pubkey || "anonymous"}
|
||||
{#key $stateKey}
|
||||
<Routes />
|
||||
<ForegroundButtons />
|
||||
<SideNav />
|
||||
|
@ -2,7 +2,7 @@
|
||||
import {nip19} from "nostr-tools"
|
||||
import {fade} from "src/util/transition"
|
||||
import {modal, location} from "src/partials/state"
|
||||
import {Keys} from "src/app/engine"
|
||||
import {canSign} from "src/engine2"
|
||||
import ForegroundButton from "src/partials/ForegroundButton.svelte"
|
||||
import ForegroundButtons from "src/partials/ForegroundButtons.svelte"
|
||||
|
||||
@ -12,8 +12,6 @@
|
||||
/conversations|channels|chat|relays|keys|settings|logout$/
|
||||
)
|
||||
|
||||
const {canSign} = Keys
|
||||
|
||||
const scrollToTop = () => document.body.scrollIntoView({behavior: "smooth"})
|
||||
|
||||
const createNote = () => {
|
||||
|
@ -7,12 +7,11 @@
|
||||
hasNewNip04Messages,
|
||||
hasNewNip24Messages,
|
||||
hasNewNotfications,
|
||||
canUseGiftWrap,
|
||||
canSign,
|
||||
} from "src/engine2"
|
||||
import {Keys} from "src/app/engine"
|
||||
import {menuIsOpen} from "src/app/state"
|
||||
|
||||
const {canSign, canUseGiftWrap} = Keys
|
||||
|
||||
const toggleTheme = () => theme.update(t => (t === "dark" ? "light" : "dark"))
|
||||
|
||||
const install = () => {
|
||||
|
@ -1,46 +0,0 @@
|
||||
import {identity} from "ramda"
|
||||
import {Engine} from "src/engine"
|
||||
|
||||
const IMGPROXY_URL = import.meta.env.VITE_IMGPROXY_URL
|
||||
|
||||
const DUFFLEPUD_URL = import.meta.env.VITE_DUFFLEPUD_URL
|
||||
|
||||
const MULTIPLEXTR_URL = import.meta.env.VITE_MULTIPLEXTR_URL
|
||||
|
||||
const FORCE_RELAYS = (import.meta.env.VITE_FORCE_RELAYS || "").split(",").filter(identity)
|
||||
|
||||
const COUNT_RELAYS = FORCE_RELAYS.length > 0 ? FORCE_RELAYS : ["wss://rbr.bio"]
|
||||
|
||||
const SEARCH_RELAYS = FORCE_RELAYS.length > 0 ? FORCE_RELAYS : ["wss://relay.nostr.band"]
|
||||
|
||||
const DEFAULT_RELAYS =
|
||||
FORCE_RELAYS.length > 0
|
||||
? FORCE_RELAYS
|
||||
: [
|
||||
"wss://purplepag.es",
|
||||
"wss://relay.damus.io",
|
||||
"wss://relay.nostr.band",
|
||||
"wss://relayable.org",
|
||||
"wss://nostr.wine",
|
||||
]
|
||||
|
||||
const DEFAULT_FOLLOWS = (import.meta.env.VITE_DEFAULT_FOLLOWS || "").split(",").filter(identity)
|
||||
|
||||
const ENABLE_ZAPS = JSON.parse(import.meta.env.VITE_ENABLE_ZAPS)
|
||||
|
||||
const engine = new Engine({
|
||||
DEFAULT_FOLLOWS,
|
||||
IMGPROXY_URL,
|
||||
DUFFLEPUD_URL,
|
||||
MULTIPLEXTR_URL,
|
||||
FORCE_RELAYS,
|
||||
COUNT_RELAYS,
|
||||
SEARCH_RELAYS,
|
||||
DEFAULT_RELAYS,
|
||||
ENABLE_ZAPS,
|
||||
})
|
||||
|
||||
export default engine
|
||||
export const Env = engine.Env
|
||||
export const Events = engine.Events
|
||||
export const Keys = engine.Keys
|
@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import type {DynamicFilter} from "src/engine/types"
|
||||
import {onMount, onDestroy} from "svelte"
|
||||
import {readable} from "svelte/store"
|
||||
import {FeedLoader} from "src/engine2"
|
||||
@ -13,8 +12,8 @@
|
||||
import FeedControls from "src/app/shared/FeedControls.svelte"
|
||||
import RelayFeed from "src/app/shared/RelayFeed.svelte"
|
||||
import Note from "src/app/shared/Note.svelte"
|
||||
import {getSetting, searchableRelays, mergeHints, getPubkeyHints} from "src/engine2"
|
||||
import {Keys} from "src/app/engine"
|
||||
import type {DynamicFilter} from "src/engine2"
|
||||
import {session, getSetting, searchableRelays, mergeHints, getPubkeyHints} from "src/engine2"
|
||||
import {compileFilter} from "src/app/state"
|
||||
|
||||
export let relays = []
|
||||
@ -57,7 +56,7 @@
|
||||
}
|
||||
|
||||
const limit = getSetting("relay_limit")
|
||||
const authors = (compileFilter(filter).authors || []).concat(Keys.pubkey.get())
|
||||
const authors = (compileFilter(filter).authors || []).concat($session.pubkey)
|
||||
const hints = authors.map(pubkey => getPubkeyHints(limit, pubkey, "write"))
|
||||
|
||||
return mergeHints(limit, hints)
|
||||
|
@ -4,14 +4,24 @@
|
||||
import {modal} from "src/partials/state"
|
||||
import Popover from "src/partials/Popover.svelte"
|
||||
import OverflowMenu from "src/partials/OverflowMenu.svelte"
|
||||
import {mute, unmute, follow, unfollow, isMuted, isFollowing} from "src/engine2"
|
||||
import {Env, Keys} from "src/app/engine"
|
||||
import {addToList} from "src/app/state"
|
||||
import {
|
||||
env,
|
||||
loginWithPublicKey,
|
||||
session,
|
||||
mute,
|
||||
unmute,
|
||||
canSign,
|
||||
canUseGiftWrap,
|
||||
follow,
|
||||
unfollow,
|
||||
isMuted,
|
||||
isFollowing,
|
||||
} from "src/engine2"
|
||||
import {addToList, boot} from "src/app/state"
|
||||
|
||||
export let pubkey
|
||||
|
||||
const {canSign, canUseGiftWrap} = Keys
|
||||
const isSelf = Keys.pubkey.get() === pubkey
|
||||
const isSelf = $session?.pubkey === pubkey
|
||||
const npub = nip19.npubEncode(pubkey)
|
||||
const following = isFollowing(pubkey)
|
||||
const muted = isMuted(pubkey)
|
||||
@ -49,7 +59,7 @@
|
||||
actions.push({onClick: loginAsUser, label: "Login as", icon: "right-to-bracket"})
|
||||
}
|
||||
|
||||
if (Env.FORCE_RELAYS.length === 0) {
|
||||
if ($env.FORCE_RELAYS.length === 0) {
|
||||
actions.push({onClick: openProfileInfo, label: "Details", icon: "info"})
|
||||
}
|
||||
|
||||
@ -64,7 +74,8 @@
|
||||
|
||||
const loginAsUser = () => {
|
||||
modal.clear()
|
||||
Keys.login("pubkey", pubkey)
|
||||
loginWithPublicKey(pubkey)
|
||||
boot()
|
||||
}
|
||||
|
||||
const openProfileInfo = () => modal.push({type: "person/info", pubkey})
|
||||
|
@ -6,8 +6,7 @@
|
||||
import {numberFmt} from "src/util/misc"
|
||||
import {modal} from "src/partials/state"
|
||||
import type {Event} from "src/engine2"
|
||||
import {people, count, Subscription, getPubkeyHints} from "src/engine2"
|
||||
import {Keys} from "src/app/engine"
|
||||
import {session, people, count, Subscription, getPubkeyHints} from "src/engine2"
|
||||
|
||||
export let pubkey
|
||||
|
||||
@ -36,7 +35,7 @@
|
||||
sub = new Subscription({
|
||||
timeout: 30_000,
|
||||
ephemeral: true,
|
||||
relays: getPubkeyHints(3, Keys.pubkey.get(), "read"),
|
||||
relays: getPubkeyHints(3, $session?.pubkey, "read"),
|
||||
filters: [{kinds: [3], "#p": [pubkey]}],
|
||||
})
|
||||
|
||||
|
@ -2,8 +2,7 @@
|
||||
import {last, prop} from "ramda"
|
||||
import {modal} from "src/partials/state"
|
||||
import OverflowMenu from "src/partials/OverflowMenu.svelte"
|
||||
import {relays, relayPolicyUrls, addRelay, removeRelay, hasRelay} from "src/engine2"
|
||||
import {Keys} from "src/app/engine"
|
||||
import {canSign, relays, relayPolicyUrls, addRelay, removeRelay, hasRelay} from "src/engine2"
|
||||
import {addToList} from "src/app/state"
|
||||
|
||||
export let relay
|
||||
@ -30,7 +29,7 @@
|
||||
})
|
||||
}
|
||||
|
||||
if (Keys.canSign.get()) {
|
||||
if ($canSign) {
|
||||
actions.push({
|
||||
onClick: () => addToList("r", relay.url),
|
||||
label: "Add to list",
|
||||
|
@ -8,8 +8,7 @@
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import RelayStatus from "src/app/shared/RelayStatus.svelte"
|
||||
import RelayCardActions from "src/app/shared/RelayCardActions.svelte"
|
||||
import {getSetting, displayRelay, setRelayPolicy} from "src/engine2"
|
||||
import {Keys} from "src/app/engine"
|
||||
import {canSign, getSetting, displayRelay, setRelayPolicy} from "src/engine2"
|
||||
|
||||
export let relay
|
||||
export let rating = null
|
||||
@ -50,7 +49,7 @@
|
||||
{#if relay.description}
|
||||
<p>{relay.description}</p>
|
||||
{/if}
|
||||
{#if showControls && Keys.canSign.get()}
|
||||
{#if showControls && $canSign}
|
||||
<div class="-mx-6 my-1 h-px bg-gray-7" />
|
||||
<div class="flex justify-between gap-2">
|
||||
<span>Publish to this relay?</span>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import OverflowMenu from "src/partials/OverflowMenu.svelte"
|
||||
import {addToList} from "src/app/state"
|
||||
import {Keys} from "src/app/engine"
|
||||
import {canSign} from "src/engine2"
|
||||
|
||||
export let topic
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
$: {
|
||||
actions = []
|
||||
|
||||
if (Keys.canSign.get()) {
|
||||
if ($canSign) {
|
||||
actions.push({
|
||||
onClick: () => addToList("t", topic),
|
||||
label: "Add to list",
|
||||
|
@ -1,5 +1,4 @@
|
||||
import type {Filter} from "nostr-tools"
|
||||
import type {DynamicFilter} from "src/engine/types"
|
||||
import Bugsnag from "@bugsnag/js"
|
||||
import {nip19} from "nostr-tools"
|
||||
import {navigate} from "svelte-routing"
|
||||
@ -10,9 +9,11 @@ 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 {Event} from "src/engine2"
|
||||
import type {DynamicFilter, Event} from "src/engine2"
|
||||
import {
|
||||
env,
|
||||
pool,
|
||||
session,
|
||||
loadPubkeys,
|
||||
channels,
|
||||
follows,
|
||||
@ -21,8 +22,8 @@ import {
|
||||
getUserRelayUrls,
|
||||
getSetting,
|
||||
dufflepud,
|
||||
events,
|
||||
} from "src/engine2"
|
||||
import {Events, Env, Keys} from "src/app/engine"
|
||||
|
||||
// Routing
|
||||
|
||||
@ -67,17 +68,17 @@ setTimeout(() => {
|
||||
})
|
||||
})
|
||||
|
||||
const session = Math.random().toString().slice(2)
|
||||
const sessionId = Math.random().toString().slice(2)
|
||||
|
||||
export const logUsage = async (name: string) => {
|
||||
// Hash the user's pubkey so we can identify unique users without knowing
|
||||
// anything about them
|
||||
const pubkey = Keys.pubkey.get()
|
||||
const pubkey = session.get()?.pubkey
|
||||
const ident = pubkey ? hash(pubkey) : "unknown"
|
||||
|
||||
if (getSetting("report_analytics")) {
|
||||
try {
|
||||
await fetch(dufflepud(`usage/${ident}/${session}/${name}`), {method: "post"})
|
||||
await fetch(dufflepud(`usage/${ident}/${sessionId}/${name}`), {method: "post"})
|
||||
} catch (e) {
|
||||
if (!e.toString().includes("Failed to fetch")) {
|
||||
warn(e)
|
||||
@ -115,11 +116,10 @@ let listener
|
||||
let timeout
|
||||
|
||||
export const listenForNotifications = async () => {
|
||||
const pubkey = Keys.pubkey.get()
|
||||
|
||||
const {pubkey} = session.get()
|
||||
const channelIds = pluck("id", channels.get().filter(path(["nip28", "joined"])))
|
||||
|
||||
const eventIds: string[] = doPipe(Events.cache.get(), [
|
||||
const eventIds: string[] = doPipe(events.get(), [
|
||||
filter((e: Event) => noteKinds.includes(e.kind)),
|
||||
sortBy((e: Event) => -e.created_at),
|
||||
slice(0, 256),
|
||||
@ -150,7 +150,7 @@ export const listenForNotifications = async () => {
|
||||
}
|
||||
|
||||
export const loadAppData = async () => {
|
||||
const pubkey = Keys.pubkey.get()
|
||||
const {pubkey} = session.get()
|
||||
|
||||
// Make sure the user and their follows are loaded
|
||||
await loadPubkeys(pubkey, {force: true, kinds: userKinds})
|
||||
@ -162,10 +162,8 @@ export const loadAppData = async () => {
|
||||
listenForNotifications()
|
||||
}
|
||||
|
||||
export const login = async (method: string, key: string | {pubkey: string; token: string}) => {
|
||||
Keys.login(method, key)
|
||||
|
||||
if (Env.FORCE_RELAYS.length > 0) {
|
||||
export const boot = async () => {
|
||||
if (env.get().FORCE_RELAYS.length > 0) {
|
||||
modal.replace({
|
||||
type: "message",
|
||||
message: "Logging you in...",
|
||||
@ -175,7 +173,7 @@ export const login = async (method: string, key: string | {pubkey: string; token
|
||||
|
||||
await Promise.all([
|
||||
sleep(1500),
|
||||
loadPubkeys(Keys.pubkey.get(), {force: true, kinds: userKinds}),
|
||||
loadPubkeys([session.get().pubkey], {force: true, kinds: userKinds}),
|
||||
])
|
||||
|
||||
navigate("/notes")
|
||||
@ -223,7 +221,7 @@ export const toastProgress = progress => {
|
||||
// Feeds
|
||||
|
||||
export const getAuthorsWithDefaults = (pubkeys: string[]) =>
|
||||
shuffle(pubkeys.length > 0 ? pubkeys : (Env.DEFAULT_FOLLOWS as string[])).slice(0, 1024)
|
||||
shuffle(pubkeys.length > 0 ? pubkeys : (env.get().DEFAULT_FOLLOWS as string[])).slice(0, 1024)
|
||||
|
||||
export const compileFilter = (filter: DynamicFilter): Filter => {
|
||||
if (filter.authors === "global") {
|
||||
|
@ -9,17 +9,16 @@
|
||||
import PersonAbout from "src/app/shared/PersonAbout.svelte"
|
||||
import NoteContent from "src/app/shared/NoteContent.svelte"
|
||||
import {
|
||||
session,
|
||||
channels,
|
||||
displayPubkey,
|
||||
createNip24Message,
|
||||
nip24MarkChannelRead,
|
||||
loadNip59Messages,
|
||||
} from "src/engine2"
|
||||
import {Keys} from "src/app/engine"
|
||||
|
||||
export let entity
|
||||
|
||||
const userPubkey = Keys.pubkey.get()
|
||||
const channel = channels.key(entity)
|
||||
const pubkeys = entity.split(",")
|
||||
|
||||
@ -72,15 +71,15 @@
|
||||
slot="message"
|
||||
let:message
|
||||
class={cx("flex overflow-hidden text-ellipsis", {
|
||||
"ml-12 justify-end": message.pubkey === userPubkey,
|
||||
"mr-12": message.pubkey !== userPubkey,
|
||||
"ml-12 justify-end": message.pubkey === $session.pubkey,
|
||||
"mr-12": message.pubkey !== $session.pubkey,
|
||||
})}>
|
||||
<div
|
||||
class={cx("inline-block flex max-w-xl flex-col rounded-2xl px-4 py-2", {
|
||||
"rounded-br-none bg-gray-1 text-gray-8": message.pubkey === userPubkey,
|
||||
"rounded-bl-none bg-gray-7": message.pubkey !== userPubkey,
|
||||
"rounded-br-none bg-gray-1 text-gray-8": message.pubkey === $session.pubkey,
|
||||
"rounded-bl-none bg-gray-7": message.pubkey !== $session.pubkey,
|
||||
})}>
|
||||
{#if message.showProfile && message.pubkey !== userPubkey}
|
||||
{#if message.showProfile && message.pubkey !== $session.pubkey}
|
||||
<Anchor class="mb-1" on:click={() => showPerson(message.pubkey)}>
|
||||
<strong>{displayPubkey(message.pubkey)}</strong>
|
||||
</Anchor>
|
||||
@ -92,8 +91,8 @@
|
||||
</div>
|
||||
<small
|
||||
class="mt-1"
|
||||
class:text-gray-7={message.pubkey === userPubkey}
|
||||
class:text-gray-1={message.pubkey !== userPubkey}>
|
||||
class:text-gray-7={message.pubkey === $session.pubkey}
|
||||
class:text-gray-1={message.pubkey !== $session.pubkey}>
|
||||
{formatTimestamp(message.created_at)}
|
||||
</small>
|
||||
</div>
|
||||
|
@ -10,6 +10,8 @@
|
||||
import PersonBadgeSmall from "src/app/shared/PersonBadgeSmall.svelte"
|
||||
import NoteContent from "src/app/shared/NoteContent.svelte"
|
||||
import {
|
||||
canSign,
|
||||
session,
|
||||
channels,
|
||||
imgproxy,
|
||||
publishNip28Message,
|
||||
@ -18,7 +20,6 @@
|
||||
loadNip28Messages,
|
||||
publishNip28ChannelChecked,
|
||||
} from "src/engine2"
|
||||
import {Keys} from "src/app/engine"
|
||||
|
||||
export let entity
|
||||
|
||||
@ -68,7 +69,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 === Keys.pubkey.get()}
|
||||
{#if $channel?.nip28?.owner === $session.pubkey}
|
||||
<button class="cursor-pointer text-sm" on:click={edit}>
|
||||
<i class="fa-solid fa-edit" /> Edit
|
||||
</button>
|
||||
@ -78,7 +79,7 @@
|
||||
<i class="fa fa-right-from-bracket" />
|
||||
<span>Leave</span>
|
||||
</Anchor>
|
||||
{:else if Keys.canSign.get()}
|
||||
{:else if $canSign}
|
||||
<Anchor theme="button" killEvent class="flex items-center gap-2" on:click={join}>
|
||||
<i class="fa fa-right-to-bracket" />
|
||||
<span>Join</span>
|
||||
|
@ -11,8 +11,7 @@
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import NoteById from "src/app/shared/NoteById.svelte"
|
||||
import PersonBadgeSmall from "src/app/shared/PersonBadgeSmall.svelte"
|
||||
import {labels, getUserRelayUrls, follows, Subscription} from "src/engine2"
|
||||
import {Keys} from "src/app/engine"
|
||||
import {session, labels, getUserRelayUrls, follows, Subscription} from "src/engine2"
|
||||
|
||||
type LabelGroup = {
|
||||
label: string
|
||||
@ -21,8 +20,6 @@
|
||||
authors: string[]
|
||||
}
|
||||
|
||||
const {pubkey} = Keys
|
||||
|
||||
const labelGroups = labels.derived($labels => {
|
||||
const $labelGroups = {}
|
||||
|
||||
@ -69,7 +66,7 @@
|
||||
{
|
||||
kinds: [1985],
|
||||
"#L": ["#t", "ugc"],
|
||||
authors: $follows.concat($pubkey),
|
||||
authors: $follows.concat($session.pubkey),
|
||||
},
|
||||
],
|
||||
})
|
||||
|
@ -1,15 +1,14 @@
|
||||
<script lang="ts">
|
||||
import cx from "classnames"
|
||||
import {filter, propEq} from "ramda"
|
||||
import type {DynamicFilter} from "src/engine/types"
|
||||
import {Tags, noteKinds} from "src/util/nostr"
|
||||
import {modal, theme} from "src/partials/state"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import Popover from "src/partials/Popover.svelte"
|
||||
import Feed from "src/app/shared/Feed.svelte"
|
||||
import {stateKey, follows, lists} from "src/engine2"
|
||||
import {Keys} from "src/app/engine"
|
||||
import type {DynamicFilter} from "src/engine2"
|
||||
import {session, canSign, stateKey, follows, lists} from "src/engine2"
|
||||
|
||||
const userLists = lists.derived(filter(propEq("pubkey", stateKey.get())))
|
||||
|
||||
@ -51,7 +50,7 @@
|
||||
</script>
|
||||
|
||||
<Content>
|
||||
{#if !Keys.pubkey.get()}
|
||||
{#if !$session}
|
||||
<Content size="lg" class="text-center">
|
||||
<p class="text-xl">Don't have an account?</p>
|
||||
<p>
|
||||
@ -62,7 +61,7 @@
|
||||
{#key key}
|
||||
<Feed filter={feedFilter} {relays}>
|
||||
<div slot="controls">
|
||||
{#if Keys.canSign.get()}
|
||||
{#if $canSign}
|
||||
{#if $userLists.length > 0}
|
||||
<Popover placement="bottom" opts={{hideOnClick: true}} theme="transparent">
|
||||
<i slot="trigger" class="fa fa-ellipsis-v cursor-pointer p-2" />
|
||||
|
@ -5,19 +5,18 @@
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import Heading from "src/partials/Heading.svelte"
|
||||
import {login} from "src/app/state"
|
||||
import {withExtension, loginWithExtension} from "src/engine2"
|
||||
|
||||
const nip07 = "https://github.com/nostr-protocol/nips/blob/master/07.md"
|
||||
|
||||
const autoLogIn = async () => {
|
||||
const {nostr} = window as any
|
||||
|
||||
if (nostr) {
|
||||
login("extension", await nostr.getPublicKey())
|
||||
} else {
|
||||
modal.push({type: "login/privkey"})
|
||||
}
|
||||
}
|
||||
const autoLogIn = () =>
|
||||
withExtension(async ext => {
|
||||
if (ext) {
|
||||
loginWithExtension(await ext.getPublicKey())
|
||||
} else {
|
||||
modal.push({type: "login/privkey"})
|
||||
}
|
||||
})
|
||||
|
||||
const signUp = () => {
|
||||
modal.push({type: "onboarding", stage: "intro"})
|
||||
|
@ -5,8 +5,8 @@
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import Heading from "src/partials/Heading.svelte"
|
||||
import {Keys} from "src/app/engine"
|
||||
import {login} from "src/app/state"
|
||||
import {isKeyValid, loginWithNsecBunker} from "src/engine2"
|
||||
import {boot} from "src/app/state"
|
||||
|
||||
let input = ""
|
||||
|
||||
@ -14,8 +14,9 @@
|
||||
const [npub, token] = input.split("#")
|
||||
const pubkey = npub.startsWith("npub") ? toHex(npub) : npub
|
||||
|
||||
if (Keys.isKeyValid(pubkey)) {
|
||||
login("bunker", {pubkey, token})
|
||||
if (isKeyValid(pubkey)) {
|
||||
loginWithNsecBunker(pubkey, token)
|
||||
boot()
|
||||
} else {
|
||||
toast.show("error", "Sorry, but that's an invalid public key.")
|
||||
}
|
||||
|
@ -12,12 +12,9 @@
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Modal from "src/partials/Modal.svelte"
|
||||
import RelayCard from "src/app/shared/RelayCard.svelte"
|
||||
import {relays, pool, loadPubkeys, getUserRelayUrls} from "src/engine2"
|
||||
import {Env, Keys} from "src/app/engine"
|
||||
import {env, session, relays, pool, loadPubkeys, getUserRelayUrls} from "src/engine2"
|
||||
import {loadAppData} from "src/app/state"
|
||||
|
||||
const pubkey = Keys.pubkey.get()
|
||||
|
||||
let modal = null
|
||||
let customRelayUrl = null
|
||||
let searching = true
|
||||
@ -29,7 +26,7 @@
|
||||
uniqBy(
|
||||
prop("url"),
|
||||
// Make sure our hardcoded urls are first, since they're more likely to find a match
|
||||
Env.DEFAULT_RELAYS.map(objOf("url")).concat(shuffle($relays))
|
||||
$env.DEFAULT_RELAYS.map(objOf("url")).concat(shuffle($relays))
|
||||
)
|
||||
)
|
||||
|
||||
@ -57,7 +54,7 @@
|
||||
// Wait a bit before removing the relay to smooth out the ui
|
||||
Promise.all([
|
||||
sleep(1500),
|
||||
loadPubkeys([pubkey], {
|
||||
loadPubkeys([$session.pubkey], {
|
||||
force: true,
|
||||
relays: [relay.url],
|
||||
kinds: userKinds,
|
||||
@ -122,7 +119,7 @@
|
||||
}}>here</Anchor
|
||||
>.
|
||||
</p>
|
||||
{#if Env.FORCE_RELAYS.length > 0}
|
||||
{#if $env.FORCE_RELAYS.length > 0}
|
||||
<Spinner />
|
||||
{:else if Object.values(currentRelays).length > 0}
|
||||
<p>Currently searching:</p>
|
||||
|
@ -6,8 +6,8 @@
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import Heading from "src/partials/Heading.svelte"
|
||||
import {Keys} from "src/app/engine"
|
||||
import {login} from "src/app/state"
|
||||
import {isKeyValid, loginWithPrivateKey} from "src/engine2"
|
||||
import {boot} from "src/app/state"
|
||||
|
||||
let nsec = ""
|
||||
const nip07 = "https://github.com/nostr-protocol/nips/blob/master/07.md"
|
||||
@ -15,8 +15,9 @@
|
||||
const logIn = () => {
|
||||
const privkey = nsec.startsWith("nsec") ? toHex(nsec) : nsec
|
||||
|
||||
if (Keys.isKeyValid(privkey)) {
|
||||
login("privkey", privkey)
|
||||
if (isKeyValid(privkey)) {
|
||||
loginWithPrivateKey(privkey)
|
||||
boot()
|
||||
} else {
|
||||
toast.show("error", "Sorry, but that's an invalid private key.")
|
||||
}
|
||||
|
@ -4,17 +4,18 @@
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import Heading from "src/partials/Heading.svelte"
|
||||
import {Keys} from "src/app/engine"
|
||||
import {toast} from "src/partials/state"
|
||||
import {login} from "src/app/state"
|
||||
import {isKeyValid, loginWithPublicKey} from "src/engine2"
|
||||
import {boot} from "src/app/state"
|
||||
|
||||
let npub = ""
|
||||
|
||||
const logIn = () => {
|
||||
const pubkey = npub.startsWith("npub") ? toHex(npub) : npub
|
||||
|
||||
if (Keys.isKeyValid(pubkey)) {
|
||||
login("pubkey", pubkey)
|
||||
if (isKeyValid(pubkey)) {
|
||||
loginWithPublicKey(pubkey)
|
||||
boot()
|
||||
} else {
|
||||
toast.show("error", "Sorry, but that's an invalid public key.")
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import NoteContent from "src/app/shared/NoteContent.svelte"
|
||||
import {
|
||||
session,
|
||||
channels,
|
||||
derivePerson,
|
||||
displayPerson,
|
||||
@ -14,7 +15,6 @@
|
||||
nip04MarkChannelRead,
|
||||
loadNip04Messages,
|
||||
} from "src/engine2"
|
||||
import {Keys} from "src/app/engine"
|
||||
import {routes} from "src/app/state"
|
||||
import PersonCircle from "src/app/shared/PersonCircle.svelte"
|
||||
import PersonAbout from "src/app/shared/PersonAbout.svelte"
|
||||
@ -68,13 +68,13 @@
|
||||
slot="message"
|
||||
let:message
|
||||
class={cx("flex overflow-hidden text-ellipsis", {
|
||||
"ml-12 justify-end": message.pubkey === Keys.pubkey.get(),
|
||||
"mr-12": message.pubkey !== Keys.pubkey.get(),
|
||||
"ml-12 justify-end": message.pubkey === $session.pubkey,
|
||||
"mr-12": message.pubkey !== $session.pubkey,
|
||||
})}>
|
||||
<div
|
||||
class={cx("inline-block max-w-xl rounded-2xl px-4 py-2", {
|
||||
"rounded-br-none bg-gray-1 text-gray-8": message.pubkey === Keys.pubkey.get(),
|
||||
"rounded-bl-none bg-gray-7": message.pubkey !== Keys.pubkey.get(),
|
||||
"rounded-br-none bg-gray-1 text-gray-8": message.pubkey === $session.pubkey,
|
||||
"rounded-bl-none bg-gray-7": message.pubkey !== $session.pubkey,
|
||||
})}>
|
||||
<div class="break-words">
|
||||
{#if typeof message.content === "string"}
|
||||
@ -83,8 +83,8 @@
|
||||
</div>
|
||||
<small
|
||||
class="mt-1"
|
||||
class:text-gray-7={message.pubkey === Keys.pubkey.get()}
|
||||
class:text-gray-1={message.pubkey !== Keys.pubkey.get()}>
|
||||
class:text-gray-7={message.pubkey === $session.pubkey}
|
||||
class:text-gray-1={message.pubkey !== $session.pubkey}>
|
||||
{formatTimestamp(message.created_at)}
|
||||
</small>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import {pluck} from 'ramda'
|
||||
import type {Event} from 'src/engine/types'
|
||||
import {pluck} from "ramda"
|
||||
import type {Event} from "src/engine2"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import NotificationSection from "src/app/views/NotificationSection.svelte"
|
||||
|
||||
|
@ -11,15 +11,17 @@
|
||||
import OnboardingFollows from "src/app/views/OnboardingFollows.svelte"
|
||||
import OnboardingNote from "src/app/views/OnboardingNote.svelte"
|
||||
import {
|
||||
env,
|
||||
user,
|
||||
mention,
|
||||
session,
|
||||
loadPubkeys,
|
||||
publishNote,
|
||||
publishPetnames,
|
||||
publishProfile,
|
||||
publishRelays,
|
||||
loginWithPrivateKey,
|
||||
} from "src/engine2"
|
||||
import {Env, Keys} from "src/app/engine"
|
||||
import {listenForNotifications} from "src/app/state"
|
||||
import {modal} from "src/partials/state"
|
||||
|
||||
@ -29,14 +31,14 @@
|
||||
const profile = {}
|
||||
|
||||
let petnames = closure(() => {
|
||||
if (Keys.keyState.get().length > 0) {
|
||||
if ($session) {
|
||||
return []
|
||||
}
|
||||
|
||||
const {petnames} = user.get()
|
||||
|
||||
if (petnames.length === 0) {
|
||||
for (const pubkey of Env.DEFAULT_FOLLOWS) {
|
||||
for (const pubkey of $env.DEFAULT_FOLLOWS) {
|
||||
petnames.push(mention(pubkey))
|
||||
}
|
||||
}
|
||||
@ -48,7 +50,7 @@
|
||||
const {relays} = user.get()
|
||||
|
||||
if (relays.length === 0) {
|
||||
for (const url of Env.DEFAULT_RELAYS) {
|
||||
for (const url of $env.DEFAULT_RELAYS) {
|
||||
relays.push({url, read: true, write: true})
|
||||
}
|
||||
}
|
||||
@ -57,7 +59,7 @@
|
||||
})
|
||||
|
||||
const signup = async noteContent => {
|
||||
Keys.login("privkey", privkey)
|
||||
loginWithPrivateKey(privkey)
|
||||
|
||||
// Wait for the published event to go through
|
||||
await publishRelays(relays)
|
||||
@ -82,7 +84,7 @@
|
||||
|
||||
onMount(() => {
|
||||
// Prime our database with some defaults
|
||||
loadPubkeys(Env.DEFAULT_FOLLOWS)
|
||||
loadPubkeys($env.DEFAULT_FOLLOWS)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -6,12 +6,12 @@
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Heading from "src/partials/Heading.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import {Env} from "src/app/engine"
|
||||
import {env} from "src/engine2"
|
||||
|
||||
export let privkey
|
||||
|
||||
const nsec = nip19.nsecEncode(privkey)
|
||||
const nextStage = Env.FORCE_RELAYS.length > 0 ? "follows" : "relays"
|
||||
const nextStage = $env.FORCE_RELAYS.length > 0 ? "follows" : "relays"
|
||||
|
||||
const copyKey = () => {
|
||||
copyToClipboard(nsec)
|
||||
|
@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import type {Relay} from "src/engine"
|
||||
import {pluck, reject, propEq} from "ramda"
|
||||
import {fuzzy} from "src/util/misc"
|
||||
import {modal} from "src/partials/state"
|
||||
@ -8,6 +7,7 @@
|
||||
import Heading from "src/partials/Heading.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import RelayCard from "src/app/shared/RelayCard.svelte"
|
||||
import type {Relay} from "src/engine2"
|
||||
import {relays as knownRelays} from "src/engine2"
|
||||
|
||||
export let relays: Relay[]
|
||||
|
@ -7,18 +7,17 @@
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import Toggle from "src/partials/Toggle.svelte"
|
||||
import Heading from "src/partials/Heading.svelte"
|
||||
import {Keys} from "src/app/engine"
|
||||
import {session} from "src/engine2"
|
||||
import {toast} from "src/partials/state"
|
||||
|
||||
const {current} = Keys
|
||||
const nip07 = "https://github.com/nostr-protocol/nips/blob/master/07.md"
|
||||
const keypairUrl = "https://www.cloudflare.com/learning/ssl/how-does-public-key-encryption-work/"
|
||||
|
||||
let asHex = false
|
||||
|
||||
$: pubkeyDisplay = asHex ? $current?.pubkey : nip19.npubEncode($current.pubkey)
|
||||
$: pubkeyDisplay = asHex ? $session?.pubkey : nip19.npubEncode($session.pubkey)
|
||||
$: privkeyDisplay =
|
||||
asHex || !$current?.privkey ? $current.privkey : nip19.nsecEncode($current.privkey)
|
||||
asHex || !$session?.privkey ? $session.privkey : nip19.nsecEncode($session.privkey)
|
||||
|
||||
const copyKey = (type, value) => {
|
||||
copyToClipboard(value)
|
||||
@ -62,7 +61,7 @@
|
||||
on nostr.
|
||||
</p>
|
||||
</div>
|
||||
{#if $current?.privkey}
|
||||
{#if $session?.privkey}
|
||||
<div class="flex flex-col gap-1">
|
||||
<strong>Private Key</strong>
|
||||
<Input disabled type="password" value={privkeyDisplay}>
|
||||
@ -80,14 +79,14 @@
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $current?.bunkerKey}
|
||||
{#if $session?.bunkerKey}
|
||||
<div class="flex flex-col gap-1">
|
||||
<strong>Bunker Key</strong>
|
||||
<Input disabled type="password" value={$current.bunkerKey}>
|
||||
<Input disabled type="password" value={$session.bunkerKey}>
|
||||
<button
|
||||
slot="after"
|
||||
class="fa-solid fa-copy cursor-pointer"
|
||||
on:click={() => copyKey("bunker", $current.bunkerKey)} />
|
||||
on:click={() => copyKey("bunker", $session.bunkerKey)} />
|
||||
</Input>
|
||||
<p class="text-sm text-gray-1">
|
||||
Your bunker key is used to authorize Coracle with your nsec bunker to sign events on
|
||||
|
@ -1,17 +0,0 @@
|
||||
import type {Env} from "./types"
|
||||
import {Events} from "./components/Events"
|
||||
import {Keys} from "./components/Keys"
|
||||
|
||||
export class Engine {
|
||||
Env: Env
|
||||
Events = new Events()
|
||||
Keys = new Keys()
|
||||
|
||||
constructor(Env: Env) {
|
||||
this.Env = Env
|
||||
|
||||
for (const component of Object.values(this)) {
|
||||
component.initialize?.(this)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
import type {Event} from "src/engine/types"
|
||||
import {pushToKey} from "src/util/misc"
|
||||
import {Worker} from "src/engine/util/Worker"
|
||||
import {collection} from "src/engine/util/store"
|
||||
import type {Engine} from "src/engine/Engine"
|
||||
|
||||
export const ANY_KIND = "Events/ANY_KIND"
|
||||
|
||||
export class Events {
|
||||
handlers = {} as Record<string, Array<(e: Event) => void>>
|
||||
queue = new Worker<Event>()
|
||||
cache = collection<Event>("id")
|
||||
addHandler = (kind: number, f: (e: Event) => void) => pushToKey(this.handlers, kind.toString(), f)
|
||||
|
||||
initialize(engine: Engine) {
|
||||
this.queue.listen(async event => {
|
||||
if (event.pubkey === engine.Keys.pubkey.get()) {
|
||||
this.cache.key(event.id).set(event)
|
||||
}
|
||||
|
||||
for (const handler of this.handlers[ANY_KIND] || []) {
|
||||
await handler(event)
|
||||
}
|
||||
|
||||
for (const handler of this.handlers[event.kind.toString()] || []) {
|
||||
await handler(event)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,157 +0,0 @@
|
||||
import {propEq, equals, prop, defaultTo, find, reject} from "ramda"
|
||||
import type {Event} from "nostr-tools"
|
||||
import {nip19, getPublicKey, getSignature, generatePrivateKey} from "nostr-tools"
|
||||
import NDK, {NDKEvent, NDKNip46Signer, NDKPrivateKeySigner} from "@nostr-dev-kit/ndk"
|
||||
import {switcherFn} from "hurdak"
|
||||
import {writable} from "src/engine/util/store"
|
||||
import type {Session} from "src/engine/types"
|
||||
import type {Engine} from "src/engine/Engine"
|
||||
|
||||
export class Keys {
|
||||
pubkey = writable<string | null>(null)
|
||||
keyState = writable<Session[]>([])
|
||||
|
||||
stateKey = this.pubkey.derived(defaultTo("anonymous"))
|
||||
|
||||
current = this.pubkey.derived(k => this.getSession(k))
|
||||
|
||||
privkey = this.current.derived(prop("privkey"))
|
||||
|
||||
method = this.current.derived(prop("method"))
|
||||
|
||||
canSign = this.method.derived($method => ["bunker", "privkey", "extension"].includes($method))
|
||||
|
||||
canUseGiftWrap = this.method.derived(equals("privkey"))
|
||||
|
||||
getSession = (k: string) => find(propEq("pubkey", k), this.keyState.get())
|
||||
|
||||
setSession = (v: Session) =>
|
||||
this.keyState.update((s: Session[]) => reject(propEq("pubkey", v.pubkey), s).concat(v))
|
||||
|
||||
removeSession = (k: string) =>
|
||||
this.keyState.update((s: Session[]) => reject(propEq("pubkey", k), s))
|
||||
|
||||
withExtension = (() => {
|
||||
let extensionLock = Promise.resolve()
|
||||
|
||||
const getExtension = () => (window as {nostr?: any}).nostr
|
||||
|
||||
return (f: (ext: any) => void) => {
|
||||
extensionLock = extensionLock.catch(e => console.error(e)).then(() => f(getExtension()))
|
||||
|
||||
return extensionLock
|
||||
}
|
||||
})()
|
||||
|
||||
isKeyValid = (key: string) => {
|
||||
// Validate the key before setting it to state by encoding it using bech32.
|
||||
// This will error if invalid (this works whether it's a public or a private key)
|
||||
try {
|
||||
nip19.npubEncode(key)
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
getNDK = (() => {
|
||||
const ndkInstances = new Map()
|
||||
|
||||
const prepareNDK = async (token?: string) => {
|
||||
const {pubkey, bunkerKey} = this.current.get() as Session
|
||||
const localSigner = new NDKPrivateKeySigner(bunkerKey)
|
||||
|
||||
const ndk = new NDK({
|
||||
explicitRelayUrls: [
|
||||
"wss://relay.f7z.io",
|
||||
"wss://relay.damus.io",
|
||||
"wss://relay.nsecbunker.com",
|
||||
],
|
||||
})
|
||||
|
||||
ndk.signer = Object.assign(new NDKNip46Signer(ndk, pubkey, localSigner), {token})
|
||||
|
||||
await ndk.connect(5000)
|
||||
await ndk.signer.blockUntilReady()
|
||||
|
||||
ndkInstances.set(pubkey, ndk)
|
||||
}
|
||||
|
||||
return async (token?: string) => {
|
||||
const {pubkey} = this.current.get() as Session
|
||||
|
||||
if (!ndkInstances.has(pubkey)) {
|
||||
await prepareNDK(token)
|
||||
}
|
||||
|
||||
return ndkInstances.get(pubkey)
|
||||
}
|
||||
})()
|
||||
|
||||
login = (method: string, key: string | {pubkey: string; token: string}) => {
|
||||
let pubkey = null
|
||||
let privkey = null
|
||||
let bunkerKey = null
|
||||
|
||||
if (method === "privkey") {
|
||||
privkey = key as string
|
||||
pubkey = getPublicKey(privkey)
|
||||
} else if (["pubkey", "extension"].includes(method)) {
|
||||
pubkey = key as string
|
||||
} else if (method === "bunker") {
|
||||
pubkey = (key as {pubkey: string}).pubkey
|
||||
bunkerKey = generatePrivateKey()
|
||||
|
||||
this.getNDK((key as {token: string}).token)
|
||||
}
|
||||
|
||||
this.setSession({method, pubkey, privkey, bunkerKey})
|
||||
this.pubkey.set(pubkey)
|
||||
}
|
||||
|
||||
sign = async (event: Event, sk?: string) => {
|
||||
if (sk) {
|
||||
return Object.assign(event, {
|
||||
sig: getSignature(event, sk),
|
||||
})
|
||||
}
|
||||
|
||||
const {method, privkey} = this.current.get()
|
||||
|
||||
console.assert(event.id)
|
||||
console.assert(event.pubkey)
|
||||
console.assert(event.created_at)
|
||||
|
||||
return switcherFn(method, {
|
||||
bunker: async () => {
|
||||
const ndk = await this.getNDK()
|
||||
const ndkEvent = new NDKEvent(ndk, event)
|
||||
|
||||
await ndkEvent.sign(ndk.signer)
|
||||
|
||||
return ndkEvent.rawEvent()
|
||||
},
|
||||
privkey: () => {
|
||||
return Object.assign(event, {
|
||||
sig: getSignature(event, privkey),
|
||||
})
|
||||
},
|
||||
extension: () => this.withExtension(ext => ext.signEvent(event)),
|
||||
})
|
||||
}
|
||||
|
||||
clear = () => {
|
||||
const $pubkey = this.pubkey.get()
|
||||
|
||||
this.pubkey.set(null)
|
||||
|
||||
if ($pubkey) {
|
||||
this.removeSession($pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
initialize(engine: Engine) {
|
||||
// pass
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
export * from "./types"
|
||||
export {Engine} from "./Engine"
|
||||
|
||||
export {Events} from "./components/Events"
|
||||
export {Keys} from "./components/Keys"
|
@ -1,4 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"strict": true
|
||||
}
|
@ -1,183 +0,0 @@
|
||||
import type {Event as NostrToolsEvent} from "nostr-tools"
|
||||
|
||||
export type Event = Omit<NostrToolsEvent, "kind"> & {
|
||||
kind: number
|
||||
seen_on: string[]
|
||||
wrap?: Event
|
||||
}
|
||||
|
||||
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[]
|
||||
authors?: string[]
|
||||
since?: number
|
||||
until?: number
|
||||
limit?: number
|
||||
search?: string
|
||||
[key: `#${string}`]: string[]
|
||||
}
|
||||
|
||||
export type DynamicFilter = Omit<Filter, "authors"> & {
|
||||
authors?: string[] | "follows" | "network" | "global"
|
||||
}
|
||||
|
||||
export type Zapper = {
|
||||
pubkey: string
|
||||
lnurl: string
|
||||
callback: string
|
||||
minSendable: number
|
||||
maxSendable: number
|
||||
nostrPubkey: string
|
||||
created_at: number
|
||||
updated_at: number
|
||||
}
|
||||
|
||||
export type Handle = {
|
||||
profile: Record<string, any>
|
||||
pubkey: string
|
||||
address: string
|
||||
created_at: number
|
||||
updated_at: number
|
||||
}
|
||||
|
||||
export type RelayInfo = {
|
||||
contact?: string
|
||||
description?: string
|
||||
last_checked?: number
|
||||
supported_nips?: number[]
|
||||
limitation?: {
|
||||
payment_required?: boolean
|
||||
auth_required?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export type Relay = {
|
||||
url: string
|
||||
count?: number
|
||||
first_seen?: number
|
||||
info?: RelayInfo
|
||||
}
|
||||
|
||||
export type RelayPolicyEntry = {
|
||||
url: string
|
||||
read: boolean
|
||||
write: boolean
|
||||
}
|
||||
|
||||
export type RelayPolicy = {
|
||||
pubkey: string
|
||||
created_at: number
|
||||
updated_at: number
|
||||
relays: RelayPolicyEntry[]
|
||||
}
|
||||
|
||||
export type GraphEntry = {
|
||||
pubkey: string
|
||||
updated_at: number
|
||||
petnames_updated_at?: number
|
||||
petnames?: string[][]
|
||||
mutes_updated_at?: number
|
||||
mutes?: string[][]
|
||||
}
|
||||
|
||||
export type Profile = {
|
||||
pubkey: string
|
||||
created_at?: number
|
||||
updated_at?: number
|
||||
name?: string
|
||||
nip05?: string
|
||||
lud16?: string
|
||||
about?: string
|
||||
banner?: string
|
||||
picture?: string
|
||||
display_name?: string
|
||||
}
|
||||
|
||||
export type Channel = {
|
||||
id: string
|
||||
name?: string
|
||||
about?: string
|
||||
picture?: string
|
||||
pubkey: string
|
||||
updated_at: number
|
||||
last_sent?: number
|
||||
last_received?: number
|
||||
last_checked?: number
|
||||
joined?: boolean
|
||||
hints: string[]
|
||||
}
|
||||
|
||||
export type Contact = {
|
||||
id: string
|
||||
pubkey: string
|
||||
updated_at: number
|
||||
last_sent?: number
|
||||
last_received?: number
|
||||
last_checked?: number
|
||||
hints: string[]
|
||||
}
|
||||
|
||||
export type Message = {
|
||||
id: string
|
||||
contact?: string
|
||||
channel?: string
|
||||
pubkey: string
|
||||
created_at: number
|
||||
content: string
|
||||
tags: string[][]
|
||||
}
|
||||
|
||||
export type Nip24Channel = {
|
||||
id: string
|
||||
updated_at: number
|
||||
last_sent?: number
|
||||
last_received?: number
|
||||
last_checked?: number
|
||||
hints: string[]
|
||||
}
|
||||
|
||||
export type Topic = {
|
||||
name: string
|
||||
count?: number
|
||||
}
|
||||
|
||||
export type List = {
|
||||
name: string
|
||||
naddr: string
|
||||
pubkey: string
|
||||
tags: string[][]
|
||||
updated_at: number
|
||||
created_at: number
|
||||
deleted_at?: number
|
||||
}
|
||||
|
||||
export type Env = {
|
||||
IMGPROXY_URL: string
|
||||
DUFFLEPUD_URL: string
|
||||
MULTIPLEXTR_URL: string
|
||||
FORCE_RELAYS: string[]
|
||||
COUNT_RELAYS: string[]
|
||||
SEARCH_RELAYS: string[]
|
||||
DEFAULT_RELAYS: string[]
|
||||
ENABLE_ZAPS: boolean
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export type Session = {
|
||||
method: string
|
||||
pubkey: string
|
||||
privkey: string | null
|
||||
bunkerKey: string | null
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
import {verifySignature, matchFilters} from "nostr-tools"
|
||||
import EventEmitter from "events"
|
||||
import {defer, tryFunc} from "hurdak"
|
||||
import type {Executor} from "paravel"
|
||||
import type {Event, Filter} from "src/engine/types"
|
||||
import {warn} from "src/util/logger"
|
||||
|
||||
type SubscriptionOpts = {
|
||||
executor: typeof Executor
|
||||
relays: string[]
|
||||
filters: Filter[]
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
export class Subscription extends EventEmitter {
|
||||
opened = Date.now()
|
||||
closed: number = null
|
||||
result = defer()
|
||||
events = []
|
||||
seen = new Map()
|
||||
eose = new Set()
|
||||
sub: {unsubscribe: () => void} = null
|
||||
id = Math.random().toString().slice(12, 16)
|
||||
|
||||
constructor(readonly opts: SubscriptionOpts) {
|
||||
super()
|
||||
|
||||
if (opts.timeout) {
|
||||
setTimeout(this.close, opts.timeout)
|
||||
}
|
||||
|
||||
this.sub = opts.executor.subscribe(opts.filters, {
|
||||
onEvent: this.onEvent,
|
||||
onEose: this.onEose,
|
||||
})
|
||||
}
|
||||
|
||||
onEvent = (url: string, event: Event) => {
|
||||
const {filters} = this.opts
|
||||
const seen_on = this.seen.get(event.id)
|
||||
|
||||
if (seen_on) {
|
||||
if (!seen_on.includes(url)) {
|
||||
seen_on.push(url)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
event.seen_on = [url]
|
||||
event.content = event.content || ""
|
||||
|
||||
this.seen.set(event.id, event.seen_on)
|
||||
|
||||
if (!tryFunc(() => verifySignature(event))) {
|
||||
warn("Signature verification failed", {event})
|
||||
return
|
||||
}
|
||||
|
||||
if (!matchFilters(filters, event)) {
|
||||
warn("Event failed to match filter", {filters, event})
|
||||
return
|
||||
}
|
||||
|
||||
this.emit("event", event)
|
||||
}
|
||||
|
||||
onEose = (url: string) => {
|
||||
const {timeout, relays} = this.opts
|
||||
|
||||
this.emit("eose", url)
|
||||
|
||||
this.eose.add(url)
|
||||
|
||||
if (timeout && this.eose.size === relays.length) {
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
|
||||
close = () => {
|
||||
if (!this.closed) {
|
||||
this.closed = Date.now()
|
||||
this.result.resolve(this.events)
|
||||
this.sub.unsubscribe()
|
||||
this.opts.executor.target.cleanup()
|
||||
this.emit("close", this.events)
|
||||
this.removeAllListeners()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
export class Worker<T> {
|
||||
buffer: T[]
|
||||
handlers: Array<(x: T) => void>
|
||||
timeout: NodeJS.Timeout | undefined
|
||||
|
||||
constructor() {
|
||||
this.buffer = []
|
||||
this.handlers = []
|
||||
}
|
||||
|
||||
#doWork = async () => {
|
||||
for (const message of this.buffer.splice(0, 50)) {
|
||||
for (const handler of this.handlers) {
|
||||
await handler(message)
|
||||
}
|
||||
}
|
||||
|
||||
this.timeout = undefined
|
||||
|
||||
this.#enqueueWork()
|
||||
}
|
||||
|
||||
#enqueueWork = () => {
|
||||
if (!this.timeout && this.buffer.length > 0) {
|
||||
this.timeout = setTimeout(this.#doWork, 50)
|
||||
}
|
||||
}
|
||||
|
||||
push = (message: T) => {
|
||||
this.buffer.push(message)
|
||||
this.#enqueueWork()
|
||||
}
|
||||
|
||||
listen = (handler: (x: T) => void) => {
|
||||
this.handlers.push(handler)
|
||||
}
|
||||
}
|
@ -1,210 +0,0 @@
|
||||
import {is, reject, filter, map, findIndex, equals} from "ramda"
|
||||
import {ensurePlural} from "hurdak"
|
||||
|
||||
type Invalidator<T> = (value?: T) => void
|
||||
type Derivable = Readable<any> | Readable<any>[]
|
||||
type Subscriber<T> = (value: T) => void
|
||||
type Unsubscriber = () => void
|
||||
type R = Record<string, any>
|
||||
type M<T> = Map<string, T>
|
||||
|
||||
export interface Readable<T> {
|
||||
get: () => T
|
||||
subscribe(this: void, run: Subscriber<T>, invalidate?: Invalidator<T>): Unsubscriber
|
||||
derived: <U>(f: (v: T) => U) => Readable<U>
|
||||
}
|
||||
|
||||
export class Writable<T> implements Readable<T> {
|
||||
private value: T
|
||||
private subs: Subscriber<T>[] = []
|
||||
|
||||
constructor(defaultValue: T) {
|
||||
this.value = defaultValue
|
||||
}
|
||||
|
||||
notify() {
|
||||
for (const sub of this.subs) {
|
||||
sub(this.value)
|
||||
}
|
||||
}
|
||||
|
||||
get() {
|
||||
return this.value
|
||||
}
|
||||
|
||||
set(newValue: T) {
|
||||
this.value = newValue
|
||||
this.notify()
|
||||
}
|
||||
|
||||
update(f: (v: T) => T) {
|
||||
this.set(f(this.value))
|
||||
}
|
||||
|
||||
subscribe(f: Subscriber<T>) {
|
||||
this.subs.push(f)
|
||||
|
||||
f(this.value)
|
||||
|
||||
return () => {
|
||||
const idx = findIndex(equals(f), this.subs)
|
||||
|
||||
this.subs.splice(idx, 1)
|
||||
}
|
||||
}
|
||||
|
||||
derived<U>(f: (v: T) => U): Derived<U> {
|
||||
return new Derived<U>(this, f)
|
||||
}
|
||||
}
|
||||
|
||||
export class Derived<T> implements Readable<T> {
|
||||
private callerSubs: Subscriber<T>[] = []
|
||||
private mySubs: Unsubscriber[] = []
|
||||
private value: T = null
|
||||
private stores: Derivable
|
||||
private getValue: (values: any) => T
|
||||
|
||||
constructor(stores: Derivable, getValue: (values: any) => T) {
|
||||
if (!getValue) {
|
||||
throw new Error(`Invalid derivation function`)
|
||||
}
|
||||
|
||||
this.stores = stores
|
||||
this.getValue = getValue
|
||||
}
|
||||
|
||||
notify() {
|
||||
this.callerSubs.forEach(f => f(this.get()))
|
||||
}
|
||||
|
||||
getInput() {
|
||||
if (is(Array, this.stores)) {
|
||||
return ensurePlural(this.stores).map(s => s.get())
|
||||
} else {
|
||||
return this.stores.get()
|
||||
}
|
||||
}
|
||||
|
||||
get = (): T => this.getValue(this.getInput())
|
||||
|
||||
subscribe(f: Subscriber<T>) {
|
||||
if (this.callerSubs.length === 0) {
|
||||
for (const s of ensurePlural(this.stores)) {
|
||||
this.mySubs.push(s.subscribe(() => this.notify()))
|
||||
}
|
||||
}
|
||||
|
||||
this.callerSubs.push(f)
|
||||
|
||||
f(this.get())
|
||||
|
||||
return () => {
|
||||
const idx = findIndex(equals(f), this.callerSubs)
|
||||
|
||||
this.callerSubs.splice(idx, 1)
|
||||
|
||||
if (this.callerSubs.length === 0) {
|
||||
for (const unsub of this.mySubs.splice(0)) {
|
||||
unsub()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
derived<U>(f: (v: T) => U): Readable<U> {
|
||||
return new Derived(this, f) as Readable<U>
|
||||
}
|
||||
}
|
||||
|
||||
export class Key<T extends R> implements Readable<T> {
|
||||
readonly pk: string
|
||||
readonly key: string
|
||||
private base: Writable<M<T>>
|
||||
private store: Readable<T>
|
||||
|
||||
constructor(base: Writable<M<T>>, pk: string, key: string) {
|
||||
if (!is(Map, base.get())) {
|
||||
throw new Error("`key` can only be used on map collections")
|
||||
}
|
||||
|
||||
this.pk = pk
|
||||
this.key = key
|
||||
this.base = base
|
||||
this.store = base.derived<T>(m => m.get(key) as T)
|
||||
}
|
||||
|
||||
get = () => this.base.get().get(this.key) as T
|
||||
|
||||
subscribe = (f: Subscriber<T>) => this.store.subscribe(f)
|
||||
|
||||
derived = <U>(f: (v: T) => U) => this.store.derived<U>(f)
|
||||
|
||||
exists = () => this.base.get().has(this.key)
|
||||
|
||||
update = (f: (v: T) => T) =>
|
||||
this.base.update((m: M<T>) => {
|
||||
if (!this.key) {
|
||||
throw new Error(`Cannot set key: "${this.key}"`)
|
||||
}
|
||||
|
||||
// Make sure the pk always get set on the record
|
||||
m.set(this.key, f({...m.get(this.key), [this.pk]: this.key}))
|
||||
|
||||
return m
|
||||
})
|
||||
|
||||
set = (v: T) => this.update(() => v)
|
||||
|
||||
merge = (d: Partial<T>) => this.update(v => ({...v, ...d}))
|
||||
|
||||
remove = () =>
|
||||
this.base.update(m => {
|
||||
m.delete(this.key)
|
||||
|
||||
return m
|
||||
})
|
||||
}
|
||||
|
||||
export class Collection<T extends R> implements Readable<T[]> {
|
||||
readonly pk: string
|
||||
readonly mapStore: Writable<M<T>>
|
||||
readonly listStore: Readable<T[]>
|
||||
|
||||
constructor(pk: string) {
|
||||
this.pk = pk
|
||||
this.mapStore = writable(new Map())
|
||||
this.listStore = this.mapStore.derived<T[]>((m: M<T>) => Array.from(m.values()))
|
||||
}
|
||||
|
||||
get = () => this.listStore.get()
|
||||
|
||||
getMap = () => this.mapStore.get()
|
||||
|
||||
subscribe = (f: Subscriber<T[]>) => this.listStore.subscribe(f)
|
||||
|
||||
derived = <U>(f: (v: T[]) => U) => this.listStore.derived<U>(f)
|
||||
|
||||
key = (k: string) => new Key(this.mapStore, this.pk, k)
|
||||
|
||||
set = (xs: T[]) => this.mapStore.set(new Map(xs.map(x => [x[this.pk], x])))
|
||||
|
||||
update = (f: (v: T[]) => T[]) =>
|
||||
this.mapStore.update(m => new Map(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))
|
||||
|
||||
map = (f: (v: T) => T) => this.update(map(f))
|
||||
}
|
||||
|
||||
export const writable = <T>(v: T) => new Writable(v)
|
||||
|
||||
export const derived = <T>(stores: Derivable, getValue: (values: any) => T) =>
|
||||
new Derived(stores, getValue) as Readable<T>
|
||||
|
||||
export const key = <T extends R>(base: Writable<M<T>>, pk: string, key: string) =>
|
||||
new Key<T>(base, pk, key)
|
||||
|
||||
export const collection = <T extends R>(pk: string) => new Collection<T>(pk)
|
@ -1,4 +1,5 @@
|
||||
import {whereEq} from "ramda"
|
||||
import {nip19} from "nostr-tools"
|
||||
import {derived} from "src/engine2/util/store"
|
||||
import {session, people} from "src/engine2/state"
|
||||
import {prepareNdk, ndkInstances} from "./ndk"
|
||||
@ -7,6 +8,18 @@ import {Nip04} from "./nip04"
|
||||
import {Nip44} from "./nip44"
|
||||
import {Nip59} from "./nip59"
|
||||
|
||||
export const isKeyValid = (key: string) => {
|
||||
// Validate the key before setting it to state by encoding it using bech32.
|
||||
// This will error if invalid (this works whether it's a public or a private key)
|
||||
try {
|
||||
nip19.npubEncode(key)
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export const stateKey = session.derived($s => $s?.pubkey || "anonymous")
|
||||
|
||||
export const user = derived([session, people.mapStore], ([$s, $p]) => $p.get($s?.pubkey))
|
||||
|
41
src/main.js
41
src/main.js
@ -1,6 +1,8 @@
|
||||
import "src/app.css"
|
||||
|
||||
import {identity} from "ramda"
|
||||
import Bugsnag from "@bugsnag/js"
|
||||
import {env} from "src/engine2"
|
||||
import App from "src/app/App.svelte"
|
||||
import {installPrompt} from "src/partials/state"
|
||||
|
||||
@ -19,6 +21,45 @@ window.addEventListener("beforeinstallprompt", e => {
|
||||
installPrompt.set(e)
|
||||
})
|
||||
|
||||
const IMGPROXY_URL = import.meta.env.VITE_IMGPROXY_URL
|
||||
|
||||
const DUFFLEPUD_URL = import.meta.env.VITE_DUFFLEPUD_URL
|
||||
|
||||
const MULTIPLEXTR_URL = import.meta.env.VITE_MULTIPLEXTR_URL
|
||||
|
||||
const FORCE_RELAYS = (import.meta.env.VITE_FORCE_RELAYS || "").split(",").filter(identity)
|
||||
|
||||
const COUNT_RELAYS = FORCE_RELAYS.length > 0 ? FORCE_RELAYS : ["wss://rbr.bio"]
|
||||
|
||||
const SEARCH_RELAYS = FORCE_RELAYS.length > 0 ? FORCE_RELAYS : ["wss://relay.nostr.band"]
|
||||
|
||||
const DEFAULT_RELAYS =
|
||||
FORCE_RELAYS.length > 0
|
||||
? FORCE_RELAYS
|
||||
: [
|
||||
"wss://purplepag.es",
|
||||
"wss://relay.damus.io",
|
||||
"wss://relay.nostr.band",
|
||||
"wss://relayable.org",
|
||||
"wss://nostr.wine",
|
||||
]
|
||||
|
||||
const DEFAULT_FOLLOWS = (import.meta.env.VITE_DEFAULT_FOLLOWS || "").split(",").filter(identity)
|
||||
|
||||
const ENABLE_ZAPS = JSON.parse(import.meta.env.VITE_ENABLE_ZAPS)
|
||||
|
||||
env.set({
|
||||
DEFAULT_FOLLOWS,
|
||||
IMGPROXY_URL,
|
||||
DUFFLEPUD_URL,
|
||||
MULTIPLEXTR_URL,
|
||||
FORCE_RELAYS,
|
||||
COUNT_RELAYS,
|
||||
SEARCH_RELAYS,
|
||||
DEFAULT_RELAYS,
|
||||
ENABLE_ZAPS,
|
||||
})
|
||||
|
||||
export default new App({
|
||||
target: document.getElementById("app"),
|
||||
})
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {nip19} from "nostr-tools"
|
||||
import {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/engine/types"
|
||||
import type {Filter, Event, DisplayEvent} from "src/engine2/model"
|
||||
import {tryJson} from "src/util/misc"
|
||||
|
||||
export const noteKinds = [1, 30023, 1063, 9802, 1808]
|
||||
|
Loading…
Reference in New Issue
Block a user