mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-18 19:23:40 +00:00
Combine app state into a single file
This commit is contained in:
parent
76b3479b7d
commit
c30b0d069e
@ -13,8 +13,21 @@ const Config = {
|
||||
authHandler: null,
|
||||
}
|
||||
|
||||
type StatsItem = {
|
||||
error: string | null
|
||||
timeouts: number
|
||||
subsCount: number
|
||||
eoseCount: number
|
||||
eoseTimer: number
|
||||
eventsCount: number
|
||||
activeSubsCount: number
|
||||
lastRequest: number
|
||||
openedAt: number
|
||||
closedAt: number
|
||||
}
|
||||
|
||||
const Meta = {
|
||||
stats: {},
|
||||
stats: {} as Record<string, StatsItem>,
|
||||
errors: {},
|
||||
getStats(url) {
|
||||
if (!this.stats[url]) {
|
||||
|
@ -1,24 +0,0 @@
|
||||
import {pluck} from "ramda"
|
||||
import {first} from "hurdak/lib/hurdak"
|
||||
import {writable} from "svelte/store"
|
||||
import pool from "src/agent/pool"
|
||||
import {getUserRelays} from "src/agent/relays"
|
||||
|
||||
export const slowConnections = writable([])
|
||||
|
||||
setInterval(() => {
|
||||
// Only notify about relays the user is actually subscribed to
|
||||
const relayUrls = new Set(pluck("url", getUserRelays()))
|
||||
|
||||
// Prune connections we haven't used in a while
|
||||
Object.entries(pool.Meta.stats)
|
||||
.filter(([url, stats]) => stats.lastRequest < Date.now() - 60_000)
|
||||
.forEach(([url, stats]) => pool.disconnect(url))
|
||||
|
||||
// Alert the user to any heinously slow connections
|
||||
slowConnections.set(
|
||||
Object.keys(pool.Meta.stats).filter(
|
||||
url => relayUrls.has(url) && first(pool.getQuality(url)) < 0.3
|
||||
)
|
||||
)
|
||||
}, 30_000)
|
@ -1,71 +0,0 @@
|
||||
import type {DisplayEvent} from "src/util/types"
|
||||
import {omit, sortBy} from "ramda"
|
||||
import {createMap} from "hurdak/lib/hurdak"
|
||||
import {findReplyId} from "src/util/nostr"
|
||||
import {getUserFollows} from "src/agent/social"
|
||||
import {getUserReadRelays} from "src/agent/relays"
|
||||
import network from "src/agent/network"
|
||||
import keys from "src/agent/keys"
|
||||
import listener from "src/app/listener"
|
||||
import {modal, toast} from "src/partials/state"
|
||||
|
||||
export const loadAppData = async pubkey => {
|
||||
if (getUserReadRelays().length > 0) {
|
||||
// Start our listener, but don't wait for it
|
||||
listener.listen(pubkey)
|
||||
|
||||
// Make sure the user and their network is loaded
|
||||
await network.loadPeople([pubkey], {force: true})
|
||||
await network.loadPeople(getUserFollows())
|
||||
}
|
||||
}
|
||||
|
||||
export const login = (method, key) => {
|
||||
keys.login(method, key)
|
||||
|
||||
modal.set({type: "login/connect", noEscape: true})
|
||||
}
|
||||
|
||||
export const mergeParents = (notes: Array<DisplayEvent>) => {
|
||||
const notesById = createMap("id", notes) as Record<string, DisplayEvent>
|
||||
const childIds = []
|
||||
|
||||
for (const note of Object.values(notesById)) {
|
||||
const parentId = findReplyId(note)
|
||||
|
||||
if (parentId) {
|
||||
childIds.push(note.id)
|
||||
}
|
||||
|
||||
// Add the current note to its parents replies, but only if we found a parent
|
||||
if (notesById[parentId]) {
|
||||
notesById[parentId].replies = notesById[parentId].replies.concat([note])
|
||||
}
|
||||
}
|
||||
|
||||
return sortBy(e => -e.created_at, Object.values(omit(childIds, notesById)))
|
||||
}
|
||||
|
||||
export const publishWithToast = (relays, thunk) =>
|
||||
thunk.publish(relays, ({completed, succeeded, failed, timeouts, pending}) => {
|
||||
let message = `Published to ${succeeded.size}/${relays.length} relays`
|
||||
|
||||
const extra = []
|
||||
if (failed.size > 0) {
|
||||
extra.push(`${failed.size} failed`)
|
||||
}
|
||||
|
||||
if (timeouts.size > 0) {
|
||||
extra.push(`${timeouts.size} timed out`)
|
||||
}
|
||||
|
||||
if (pending.size > 0) {
|
||||
extra.push(`${pending.size} pending`)
|
||||
}
|
||||
|
||||
if (extra.length > 0) {
|
||||
message += ` (${extra.join(", ")})`
|
||||
}
|
||||
|
||||
toast.show("info", message, pending.size ? null : 5)
|
||||
})
|
@ -1,124 +0,0 @@
|
||||
import {sortBy, max, find, pluck, slice, propEq} from "ramda"
|
||||
import {derived} from "svelte/store"
|
||||
import {doPipe} from "hurdak/lib/hurdak"
|
||||
import {synced, now, timedelta} from "src/util/misc"
|
||||
import {Tags, isNotification} from "src/util/nostr"
|
||||
import {getUserReadRelays} from "src/agent/relays"
|
||||
import {notifications, userEvents, contacts, rooms} from "src/agent/db"
|
||||
import {watch} from "src/agent/db"
|
||||
import network from "src/agent/network"
|
||||
import user from "src/agent/user"
|
||||
|
||||
let listener
|
||||
|
||||
// State
|
||||
|
||||
export const lastChecked = synced("app/alerts/lastChecked", {})
|
||||
|
||||
export const newNotifications = derived(
|
||||
[watch("notifications", t => pluck("created_at", t.all()).reduce(max, 0)), lastChecked],
|
||||
([$lastNotification, $lastChecked]) => $lastNotification > ($lastChecked.notifications || 0)
|
||||
)
|
||||
|
||||
export const newDirectMessages = derived(
|
||||
[watch("contacts", t => t.all()), lastChecked],
|
||||
([contacts, $lastChecked]) => Boolean(find(c => c.lastMessage > $lastChecked[c.pubkey], contacts))
|
||||
)
|
||||
|
||||
export const newChatMessages = derived(
|
||||
[watch("rooms", t => t.all()), lastChecked],
|
||||
([rooms, $lastChecked]) => Boolean(find(c => c.lastMessage > $lastChecked[c.pubkey], rooms))
|
||||
)
|
||||
|
||||
// Synchronization from events to state
|
||||
|
||||
const processNotifications = async (pubkey, events) => {
|
||||
notifications.patch(events.filter(e => isNotification(e, pubkey)))
|
||||
}
|
||||
|
||||
const processMessages = async (pubkey, events) => {
|
||||
const messages = events.filter(propEq("kind", 4))
|
||||
|
||||
if (messages.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
lastChecked.update($lastChecked => {
|
||||
for (const message of messages) {
|
||||
if (message.pubkey === pubkey) {
|
||||
const recipient = Tags.from(message).type("p").values().first()
|
||||
|
||||
$lastChecked[recipient] = Math.max($lastChecked[recipient] || 0, message.created_at)
|
||||
contacts.patch({pubkey: recipient, accepted: true})
|
||||
} else {
|
||||
const contact = contacts.get(message.pubkey)
|
||||
const lastMessage = Math.max(contact?.lastMessage || 0, message.created_at)
|
||||
|
||||
contacts.patch({pubkey: message.pubkey, lastMessage})
|
||||
}
|
||||
}
|
||||
|
||||
return $lastChecked
|
||||
})
|
||||
}
|
||||
|
||||
const processChats = async (pubkey, events) => {
|
||||
const messages = events.filter(propEq("kind", 42))
|
||||
|
||||
if (messages.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
lastChecked.update($lastChecked => {
|
||||
for (const message of messages) {
|
||||
const id = Tags.from(message).type("e").values().first()
|
||||
|
||||
if (message.pubkey === pubkey) {
|
||||
$lastChecked[id] = Math.max($lastChecked[id] || 0, message.created_at)
|
||||
} else {
|
||||
const room = rooms.get(id)
|
||||
const lastMessage = Math.max(room?.lastMessage || 0, message.created_at)
|
||||
|
||||
rooms.patch({id, lastMessage})
|
||||
}
|
||||
}
|
||||
|
||||
return $lastChecked
|
||||
})
|
||||
}
|
||||
|
||||
const listen = async pubkey => {
|
||||
// Include an offset so we don't miss notifications on one relay but not another
|
||||
const since = now() - timedelta(30, "days")
|
||||
const roomIds = pluck("id", rooms.all({joined: true}))
|
||||
const eventIds = doPipe(userEvents.all({kind: 1, created_at: {$gt: since}}), [
|
||||
sortBy(e => -e.created_at),
|
||||
slice(0, 256),
|
||||
pluck("id"),
|
||||
])
|
||||
|
||||
if (listener) {
|
||||
listener.unsub()
|
||||
}
|
||||
|
||||
listener = await network.listen({
|
||||
delay: 5000,
|
||||
relays: getUserReadRelays(),
|
||||
filter: [
|
||||
{kinds: [1, 4], authors: [pubkey], since},
|
||||
{kinds: [1, 7, 4, 9735], "#p": [pubkey], since},
|
||||
{kinds: [1, 7, 4, 9735], "#e": eventIds, since},
|
||||
{kinds: [42], "#e": roomIds, since},
|
||||
],
|
||||
onChunk: async events => {
|
||||
events = user.applyMutes(events)
|
||||
|
||||
await network.loadPeople(pluck("pubkey", events))
|
||||
await processNotifications(pubkey, events)
|
||||
await processMessages(pubkey, events)
|
||||
await processChats(pubkey, events)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export default {listen}
|
@ -1,66 +0,0 @@
|
||||
import Bugsnag from "@bugsnag/js"
|
||||
import {nip19} from "nostr-tools"
|
||||
import {writable} from "svelte/store"
|
||||
import {hash} from "src/util/misc"
|
||||
import {warn} from "src/util/logger"
|
||||
import user from "src/agent/user"
|
||||
|
||||
// Routing
|
||||
|
||||
export const routes = {
|
||||
person: (pubkey, tab = "notes") => `/people/${nip19.npubEncode(pubkey)}/${tab}`,
|
||||
}
|
||||
|
||||
// Menu
|
||||
|
||||
export const menuIsOpen = writable(false)
|
||||
|
||||
// Redact long strings, especially hex and bech32 keys which are 64 and 63
|
||||
// characters long, respectively. Put the threshold a little lower in case
|
||||
// someone accidentally enters a key with the last few digits missing
|
||||
const redactErrorInfo = info =>
|
||||
JSON.parse(JSON.stringify(info || null).replace(/\w{60}\w+/g, "[REDACTED]"))
|
||||
|
||||
// Wait for bugsnag to be started in main
|
||||
setTimeout(() => {
|
||||
Bugsnag.addOnError((event: any) => {
|
||||
if (window.location.host.startsWith("localhost")) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!user.getSetting("reportAnalytics")) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Redact individual properties since the event needs to be
|
||||
// mutated, and we don't want to lose the prototype
|
||||
event.context = redactErrorInfo(event.context)
|
||||
event.request = redactErrorInfo(event.request)
|
||||
event.exceptions = redactErrorInfo(event.exceptions)
|
||||
event.breadcrumbs = redactErrorInfo(event.breadcrumbs)
|
||||
|
||||
event.setUser(session)
|
||||
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
const session = Math.random().toString().slice(2)
|
||||
|
||||
export const logUsage = async name => {
|
||||
// Hash the user's pubkey so we can identify unique users without knowing
|
||||
// anything about them
|
||||
const pubkey = user.getPubkey()
|
||||
const ident = pubkey ? hash(pubkey) : "unknown"
|
||||
const {dufflepudUrl, reportAnalytics} = user.getSettings()
|
||||
|
||||
if (reportAnalytics) {
|
||||
try {
|
||||
await fetch(`${dufflepudUrl}/usage/${ident}/${session}/${name}`, {method: "post"})
|
||||
} catch (e) {
|
||||
if (!e.toString().includes("Failed to fetch")) {
|
||||
warn(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -18,9 +18,9 @@
|
||||
import sync from "src/agent/sync"
|
||||
import * as db from "src/agent/db"
|
||||
import user from "src/agent/user"
|
||||
import {loadAppData} from "src/app"
|
||||
import {loadAppData} from "src/app2/state"
|
||||
import {theme, getThemeVariables, modal, openModals} from "src/partials/state"
|
||||
import {logUsage} from "src/app/ui"
|
||||
import {logUsage} from "src/app2/state"
|
||||
import SideNav from "src/app2/SideNav.svelte"
|
||||
import Routes from "src/app2/Routes.svelte"
|
||||
import Toast from "src/app2/Toast.svelte"
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {menuIsOpen} from "src/app/ui"
|
||||
import {menuIsOpen} from "src/app2/state"
|
||||
import {modal} from "src/partials/state"
|
||||
import Modal from "src/partials/Modal.svelte"
|
||||
import Content from "src/partials/Content.svelte"
|
||||
|
@ -4,9 +4,9 @@
|
||||
import {installPrompt} from "src/partials/state"
|
||||
import user from "src/agent/user"
|
||||
import pool from "src/agent/pool"
|
||||
import {routes, menuIsOpen} from "src/app/ui"
|
||||
import {newNotifications, newDirectMessages, newChatMessages} from "src/app/listener"
|
||||
import {slowConnections} from "src/app/connection"
|
||||
import {routes, menuIsOpen} from "src/app2/state"
|
||||
import {newNotifications, newDirectMessages, newChatMessages} from "src/app2/state"
|
||||
import {slowConnections} from "src/app2/state"
|
||||
import PersonCircle from "src/app2/shared/PersonCircle.svelte"
|
||||
|
||||
const {profile, canPublish} = user
|
||||
|
@ -2,8 +2,8 @@
|
||||
import {onMount} from "svelte"
|
||||
import {theme} from "src/partials/state"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import {menuIsOpen} from "src/app/ui"
|
||||
import {newNotifications} from "src/app/listener"
|
||||
import {menuIsOpen} from "src/app2/state"
|
||||
import {newNotifications} from "src/app2/state"
|
||||
|
||||
const toggleMenu = () => menuIsOpen.update(x => !x)
|
||||
const toggleTheme = () => theme.update(t => (t === "dark" ? "light" : "dark"))
|
||||
|
@ -13,7 +13,7 @@
|
||||
import Note from "src/app2/shared/Note.svelte"
|
||||
import user from "src/agent/user"
|
||||
import network from "src/agent/network"
|
||||
import {mergeParents} from "src/app"
|
||||
import {mergeParents} from "src/app2/state"
|
||||
|
||||
export let filter
|
||||
export let relays = []
|
||||
|
@ -18,7 +18,7 @@
|
||||
import {getRelaysForEventParent} from "src/agent/relays"
|
||||
import {getPersonWithFallback} from "src/agent/db"
|
||||
import {watch} from "src/agent/db"
|
||||
import {routes} from "src/app/ui"
|
||||
import {routes} from "src/app2/state"
|
||||
import NoteContent from "src/app2/shared/NoteContent.svelte"
|
||||
|
||||
export let note
|
||||
|
@ -15,7 +15,7 @@
|
||||
import user from "src/agent/user"
|
||||
import network from "src/agent/network"
|
||||
import {getPersonWithFallback} from "src/agent/db"
|
||||
import {routes} from "src/app/ui"
|
||||
import {routes} from "src/app2/state"
|
||||
|
||||
export let note
|
||||
export let maxLength = 700
|
||||
|
@ -12,7 +12,7 @@
|
||||
import {getEventPublishRelays} from "src/agent/relays"
|
||||
import user from "src/agent/user"
|
||||
import cmd from "src/agent/cmd"
|
||||
import {publishWithToast} from "src/app"
|
||||
import {publishWithToast} from "src/app2/state"
|
||||
|
||||
export let note
|
||||
export let borderColor
|
||||
|
@ -2,7 +2,7 @@
|
||||
import {Link} from "svelte-routing"
|
||||
import {killEvent} from "src/util/html"
|
||||
import {displayPerson} from "src/util/nostr"
|
||||
import {routes} from "src/app/ui"
|
||||
import {routes} from "src/app2/state"
|
||||
import PersonCircle from "src/app2/shared/PersonCircle.svelte"
|
||||
|
||||
export let person
|
||||
|
@ -8,7 +8,7 @@
|
||||
import PersonAbout from "src/app2/shared/PersonAbout.svelte"
|
||||
import {getPubkeyWriteRelays, sampleRelays} from "src/agent/relays"
|
||||
import user from "src/agent/user"
|
||||
import {routes} from "src/app/ui"
|
||||
import {routes} from "src/app2/state"
|
||||
|
||||
export let person
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
import {sampleRelays, getPubkeyWriteRelays} from "src/agent/relays"
|
||||
import {getPersonWithFallback} from "src/agent/db"
|
||||
import {watch} from "src/agent/db"
|
||||
import {routes} from "src/app/ui"
|
||||
import {routes} from "src/app2/state"
|
||||
import PersonCircle from "src/app2/shared/PersonCircle.svelte"
|
||||
import PersonAbout from "src/app2/shared/PersonAbout.svelte"
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import pool from "src/agent/pool"
|
||||
import user from "src/agent/user"
|
||||
import {loadAppData} from "src/app"
|
||||
import {loadAppData} from "src/app2/state"
|
||||
|
||||
export let relay
|
||||
export let theme = "gray-8"
|
||||
|
267
src/app2/state.ts
Normal file
267
src/app2/state.ts
Normal file
@ -0,0 +1,267 @@
|
||||
import type {DisplayEvent} from "src/util/types"
|
||||
import Bugsnag from "@bugsnag/js"
|
||||
import {nip19} from "nostr-tools"
|
||||
import {derived} from "svelte/store"
|
||||
import {writable} from "svelte/store"
|
||||
import {omit, pluck, sortBy, max, find, slice, propEq} from "ramda"
|
||||
import {createMap, doPipe, first} from "hurdak/lib/hurdak"
|
||||
import {warn} from "src/util/logger"
|
||||
import {hash} from "src/util/misc"
|
||||
import {synced, now, timedelta} from "src/util/misc"
|
||||
import {Tags, isNotification} from "src/util/nostr"
|
||||
import {findReplyId} from "src/util/nostr"
|
||||
import {modal, toast} from "src/partials/state"
|
||||
import {notifications, watch, userEvents, contacts, rooms} from "src/agent/db"
|
||||
import keys from "src/agent/keys"
|
||||
import network from "src/agent/network"
|
||||
import pool from "src/agent/pool"
|
||||
import {getUserReadRelays, getUserRelays} from "src/agent/relays"
|
||||
import {getUserFollows} from "src/agent/social"
|
||||
import user from "src/agent/user"
|
||||
|
||||
// Routing
|
||||
|
||||
export const routes = {
|
||||
person: (pubkey, tab = "notes") => `/people/${nip19.npubEncode(pubkey)}/${tab}`,
|
||||
}
|
||||
|
||||
// Menu
|
||||
|
||||
export const menuIsOpen = writable(false)
|
||||
|
||||
// Redact long strings, especially hex and bech32 keys which are 64 and 63
|
||||
// characters long, respectively. Put the threshold a little lower in case
|
||||
// someone accidentally enters a key with the last few digits missing
|
||||
const redactErrorInfo = info =>
|
||||
JSON.parse(JSON.stringify(info || null).replace(/\w{60}\w+/g, "[REDACTED]"))
|
||||
|
||||
// Wait for bugsnag to be started in main
|
||||
setTimeout(() => {
|
||||
Bugsnag.addOnError((event: any) => {
|
||||
if (window.location.host.startsWith("localhost")) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!user.getSetting("reportAnalytics")) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Redact individual properties since the event needs to be
|
||||
// mutated, and we don't want to lose the prototype
|
||||
event.context = redactErrorInfo(event.context)
|
||||
event.request = redactErrorInfo(event.request)
|
||||
event.exceptions = redactErrorInfo(event.exceptions)
|
||||
event.breadcrumbs = redactErrorInfo(event.breadcrumbs)
|
||||
|
||||
event.setUser(session)
|
||||
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
const session = Math.random().toString().slice(2)
|
||||
|
||||
export const logUsage = async name => {
|
||||
// Hash the user's pubkey so we can identify unique users without knowing
|
||||
// anything about them
|
||||
const pubkey = user.getPubkey()
|
||||
const ident = pubkey ? hash(pubkey) : "unknown"
|
||||
const {dufflepudUrl, reportAnalytics} = user.getSettings()
|
||||
|
||||
if (reportAnalytics) {
|
||||
try {
|
||||
await fetch(`${dufflepudUrl}/usage/${ident}/${session}/${name}`, {method: "post"})
|
||||
} catch (e) {
|
||||
if (!e.toString().includes("Failed to fetch")) {
|
||||
warn(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// State
|
||||
|
||||
export const lastChecked = synced("app/alerts/lastChecked", {})
|
||||
|
||||
export const newNotifications = derived(
|
||||
[watch("notifications", t => pluck("created_at", t.all()).reduce(max, 0)), lastChecked],
|
||||
([$lastNotification, $lastChecked]) => $lastNotification > ($lastChecked.notifications || 0)
|
||||
)
|
||||
|
||||
export const newDirectMessages = derived(
|
||||
[watch("contacts", t => t.all()), lastChecked],
|
||||
([contacts, $lastChecked]) => Boolean(find(c => c.lastMessage > $lastChecked[c.pubkey], contacts))
|
||||
)
|
||||
|
||||
export const newChatMessages = derived(
|
||||
[watch("rooms", t => t.all()), lastChecked],
|
||||
([rooms, $lastChecked]) => Boolean(find(c => c.lastMessage > $lastChecked[c.pubkey], rooms))
|
||||
)
|
||||
|
||||
// Synchronization from events to state
|
||||
|
||||
const processNotifications = async (pubkey, events) => {
|
||||
notifications.patch(events.filter(e => isNotification(e, pubkey)))
|
||||
}
|
||||
|
||||
const processMessages = async (pubkey, events) => {
|
||||
const messages = events.filter(propEq("kind", 4))
|
||||
|
||||
if (messages.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
lastChecked.update($lastChecked => {
|
||||
for (const message of messages) {
|
||||
if (message.pubkey === pubkey) {
|
||||
const recipient = Tags.from(message).type("p").values().first()
|
||||
|
||||
$lastChecked[recipient] = Math.max($lastChecked[recipient] || 0, message.created_at)
|
||||
contacts.patch({pubkey: recipient, accepted: true})
|
||||
} else {
|
||||
const contact = contacts.get(message.pubkey)
|
||||
const lastMessage = Math.max(contact?.lastMessage || 0, message.created_at)
|
||||
|
||||
contacts.patch({pubkey: message.pubkey, lastMessage})
|
||||
}
|
||||
}
|
||||
|
||||
return $lastChecked
|
||||
})
|
||||
}
|
||||
|
||||
const processChats = async (pubkey, events) => {
|
||||
const messages = events.filter(propEq("kind", 42))
|
||||
|
||||
if (messages.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
lastChecked.update($lastChecked => {
|
||||
for (const message of messages) {
|
||||
const id = Tags.from(message).type("e").values().first()
|
||||
|
||||
if (message.pubkey === pubkey) {
|
||||
$lastChecked[id] = Math.max($lastChecked[id] || 0, message.created_at)
|
||||
} else {
|
||||
const room = rooms.get(id)
|
||||
const lastMessage = Math.max(room?.lastMessage || 0, message.created_at)
|
||||
|
||||
rooms.patch({id, lastMessage})
|
||||
}
|
||||
}
|
||||
|
||||
return $lastChecked
|
||||
})
|
||||
}
|
||||
|
||||
export const listen = async pubkey => {
|
||||
// Include an offset so we don't miss notifications on one relay but not another
|
||||
const since = now() - timedelta(30, "days")
|
||||
const roomIds = pluck("id", rooms.all({joined: true}))
|
||||
const eventIds = doPipe(userEvents.all({kind: 1, created_at: {$gt: since}}), [
|
||||
sortBy(e => -e.created_at),
|
||||
slice(0, 256),
|
||||
pluck("id"),
|
||||
])
|
||||
|
||||
;(listen as any)._listener?.unsub()
|
||||
;(listen as any)._listener = await network.listen({
|
||||
delay: 5000,
|
||||
relays: getUserReadRelays(),
|
||||
filter: [
|
||||
{kinds: [1, 4], authors: [pubkey], since},
|
||||
{kinds: [1, 7, 4, 9735], "#p": [pubkey], since},
|
||||
{kinds: [1, 7, 4, 9735], "#e": eventIds, since},
|
||||
{kinds: [42], "#e": roomIds, since},
|
||||
],
|
||||
onChunk: async events => {
|
||||
events = user.applyMutes(events)
|
||||
|
||||
await network.loadPeople(pluck("pubkey", events))
|
||||
await processNotifications(pubkey, events)
|
||||
await processMessages(pubkey, events)
|
||||
await processChats(pubkey, events)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const slowConnections = writable([])
|
||||
|
||||
setInterval(() => {
|
||||
// Only notify about relays the user is actually subscribed to
|
||||
const relayUrls = new Set(pluck("url", getUserRelays()))
|
||||
|
||||
// Prune connections we haven't used in a while
|
||||
Object.entries(pool.Meta.stats)
|
||||
.filter(([url, stats]) => stats.lastRequest < Date.now() - 60_000)
|
||||
.forEach(([url, stats]) => pool.disconnect(url))
|
||||
|
||||
// Alert the user to any heinously slow connections
|
||||
slowConnections.set(
|
||||
Object.keys(pool.Meta.stats).filter(
|
||||
url => relayUrls.has(url) && first(pool.getQuality(url)) < 0.3
|
||||
)
|
||||
)
|
||||
}, 30_000)
|
||||
|
||||
export const loadAppData = async pubkey => {
|
||||
if (getUserReadRelays().length > 0) {
|
||||
// Start our listener, but don't wait for it
|
||||
listen(pubkey)
|
||||
|
||||
// Make sure the user and their network is loaded
|
||||
await network.loadPeople([pubkey], {force: true})
|
||||
await network.loadPeople(getUserFollows())
|
||||
}
|
||||
}
|
||||
|
||||
export const login = (method, key) => {
|
||||
keys.login(method, key)
|
||||
|
||||
modal.set({type: "login/connect", noEscape: true})
|
||||
}
|
||||
|
||||
export const mergeParents = (notes: Array<DisplayEvent>) => {
|
||||
const notesById = createMap("id", notes) as Record<string, DisplayEvent>
|
||||
const childIds = []
|
||||
|
||||
for (const note of Object.values(notesById)) {
|
||||
const parentId = findReplyId(note)
|
||||
|
||||
if (parentId) {
|
||||
childIds.push(note.id)
|
||||
}
|
||||
|
||||
// Add the current note to its parents replies, but only if we found a parent
|
||||
if (notesById[parentId]) {
|
||||
notesById[parentId].replies = notesById[parentId].replies.concat([note])
|
||||
}
|
||||
}
|
||||
|
||||
return sortBy(e => -e.created_at, Object.values(omit(childIds, notesById)))
|
||||
}
|
||||
|
||||
export const publishWithToast = (relays, thunk) =>
|
||||
thunk.publish(relays, ({completed, succeeded, failed, timeouts, pending}) => {
|
||||
let message = `Published to ${succeeded.size}/${relays.length} relays`
|
||||
|
||||
const extra = []
|
||||
if (failed.size > 0) {
|
||||
extra.push(`${failed.size} failed`)
|
||||
}
|
||||
|
||||
if (timeouts.size > 0) {
|
||||
extra.push(`${timeouts.size} timed out`)
|
||||
}
|
||||
|
||||
if (pending.size > 0) {
|
||||
extra.push(`${pending.size} pending`)
|
||||
}
|
||||
|
||||
if (extra.length > 0) {
|
||||
message += ` (${extra.join(", ")})`
|
||||
}
|
||||
|
||||
toast.show("info", message, pending.size ? null : 5)
|
||||
})
|
@ -13,7 +13,7 @@
|
||||
import network from "src/agent/network"
|
||||
import {watch} from "src/agent/db"
|
||||
import cmd from "src/agent/cmd"
|
||||
import {lastChecked} from "src/app/listener"
|
||||
import {lastChecked} from "src/app2/state"
|
||||
|
||||
export let entity
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
import {getUserWriteRelays} from "src/agent/relays"
|
||||
import {rooms} from "src/agent/db"
|
||||
import cmd from "src/agent/cmd"
|
||||
import {publishWithToast} from "src/app"
|
||||
import {publishWithToast} from "src/app2/state"
|
||||
|
||||
export let room = {name: null, id: null, about: null, picture: null}
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import Heading from "src/partials/Heading.svelte"
|
||||
import user from "src/agent/user"
|
||||
import {login} from "src/app"
|
||||
import {login} from "src/app2/state"
|
||||
|
||||
const nip07 = "https://github.com/nostr-protocol/nips/blob/master/07.md"
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
import network from "src/agent/network"
|
||||
import user from "src/agent/user"
|
||||
import pool from "src/agent/pool"
|
||||
import {loadAppData} from "src/app"
|
||||
import {loadAppData} from "src/app2/state"
|
||||
|
||||
let modal = null
|
||||
let customRelayUrl = null
|
||||
|
@ -6,7 +6,7 @@
|
||||
import Content from "src/partials/Content.svelte"
|
||||
import Heading from "src/partials/Heading.svelte"
|
||||
import keys from "src/agent/keys"
|
||||
import {login} from "src/app"
|
||||
import {login} from "src/app2/state"
|
||||
|
||||
let nsec = ""
|
||||
const nip07 = "https://github.com/nostr-protocol/nips/blob/master/07.md"
|
||||
|
@ -6,7 +6,7 @@
|
||||
import Heading from "src/partials/Heading.svelte"
|
||||
import keys from "src/agent/keys"
|
||||
import {toast} from "src/partials/state"
|
||||
import {login} from "src/app"
|
||||
import {login} from "src/app2/state"
|
||||
|
||||
let npub = ""
|
||||
|
||||
|
@ -14,8 +14,8 @@
|
||||
import keys from "src/agent/keys"
|
||||
import user from "src/agent/user"
|
||||
import cmd from "src/agent/cmd"
|
||||
import {routes} from "src/app/ui"
|
||||
import {lastChecked} from "src/app/listener"
|
||||
import {routes} from "src/app2/state"
|
||||
import {lastChecked} from "src/app2/state"
|
||||
import PersonCircle from "src/app2/shared/PersonCircle.svelte"
|
||||
import PersonAbout from "src/app2/shared/PersonAbout.svelte"
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
import {ellipsize} from "hurdak/lib/hurdak"
|
||||
import {displayPerson} from "src/util/nostr"
|
||||
import {getPersonWithFallback} from "src/agent/db"
|
||||
import {lastChecked} from "src/app/listener"
|
||||
import {lastChecked} from "src/app2/state"
|
||||
import PersonCircle from "src/app2/shared/PersonCircle.svelte"
|
||||
import Card from "src/partials/Card.svelte"
|
||||
|
||||
|
@ -21,7 +21,7 @@
|
||||
import cmd from "src/agent/cmd"
|
||||
import user from "src/agent/user"
|
||||
import {toast, modal} from "src/partials/state"
|
||||
import {publishWithToast} from "src/app"
|
||||
import {publishWithToast} from "src/app2/state"
|
||||
|
||||
export let pubkey = null
|
||||
export let nevent = null
|
||||
|
@ -10,7 +10,7 @@
|
||||
import {watch} from "src/agent/db"
|
||||
import user from "src/agent/user"
|
||||
import {userEvents} from "src/agent/db"
|
||||
import {lastChecked} from "src/app/listener"
|
||||
import {lastChecked} from "src/app2/state"
|
||||
|
||||
let limit = 0
|
||||
let events = null
|
||||
|
@ -18,7 +18,7 @@
|
||||
import pool from "src/agent/pool"
|
||||
import user from "src/agent/user"
|
||||
import keys from "src/agent/keys"
|
||||
import {loadAppData} from "src/app"
|
||||
import {loadAppData} from "src/app2/state"
|
||||
import {modal} from "src/partials/state"
|
||||
|
||||
export let stage
|
||||
|
@ -21,7 +21,7 @@
|
||||
import {sampleRelays, getPubkeyWriteRelays} from "src/agent/relays"
|
||||
import network from "src/agent/network"
|
||||
import {getPersonWithFallback, watch} from "src/agent/db"
|
||||
import {routes} from "src/app/ui"
|
||||
import {routes} from "src/app2/state"
|
||||
import PersonCircle from "src/app2/shared/PersonCircle.svelte"
|
||||
import PersonAbout from "src/app2/shared/PersonAbout.svelte"
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
import Button from "src/partials/Button.svelte"
|
||||
import user from "src/agent/user"
|
||||
import {toast, modal} from "src/partials/state"
|
||||
import {loadAppData} from "src/app"
|
||||
import {loadAppData} from "src/app2/state"
|
||||
|
||||
let url = $modal.url
|
||||
|
||||
|
@ -12,8 +12,8 @@
|
||||
import user from "src/agent/user"
|
||||
import {getUserWriteRelays} from "src/agent/relays"
|
||||
import cmd from "src/agent/cmd"
|
||||
import {routes} from "src/app/ui"
|
||||
import {publishWithToast} from "src/app"
|
||||
import {routes} from "src/app2/state"
|
||||
import {publishWithToast} from "src/app2/state"
|
||||
|
||||
let values = user.getProfile().kind0 || {}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user