Monitor relay connections for restricted responses and show error to user

This commit is contained in:
Jon Staab
2025-09-03 15:29:57 -07:00
parent 4001e877b4
commit 95698813c6
9 changed files with 226 additions and 75 deletions
+74 -1
View File
@@ -20,6 +20,8 @@
ago,
WEEK,
TaskQueue,
assoc,
dissoc,
} from "@welshman/lib"
import type {TrustedEvent, StampedEvent} from "@welshman/util"
import {
@@ -40,13 +42,18 @@
getRelaysFromList,
} from "@welshman/util"
import {Nip46Broker, makeSecret} from "@welshman/signer"
import type {Socket, RelayMessage} from "@welshman/net"
import type {Socket, RelayMessage, ClientMessage} from "@welshman/net"
import {
request,
defaultSocketPolicies,
makeSocketPolicyAuth,
SocketEvent,
isRelayEvent,
isRelayOk,
isRelayClosed,
isClientReq,
isClientEvent,
isClientClose,
} from "@welshman/net"
import {
loadRelay,
@@ -87,6 +94,7 @@
ensureUnwrapped,
canDecrypt,
getSetting,
relaysMostlyRestricted,
} from "@app/core/state"
import {loadUserData, listenForNotifications} from "@app/core/requests"
import {theme} from "@app/util/theme"
@@ -296,6 +304,71 @@
}),
]
return () => {
unsubscribers.forEach(call)
}
},
function monitorRestrictedResponses(socket: Socket) {
let total = 0
let restricted = 0
let error = ""
const pending = new Set<string>()
const updateStatus = () =>
relaysMostlyRestricted.update(
restricted > total / 2 ? assoc(socket.url, error) : dissoc(socket.url),
)
const unsubscribers = [
on(socket, SocketEvent.Receive, (message: RelayMessage) => {
if (isRelayOk(message)) {
const [_, id, ok, details = ""] = message
if (pending.has(id)) {
pending.delete(id)
if (!ok && details.startsWith("restricted: ")) {
restricted++
error = details
updateStatus()
}
}
}
if (isRelayClosed(message)) {
const [_, id, details = ""] = message
if (pending.has(id)) {
pending.delete(id)
if (details.startsWith("restricted: ")) {
restricted++
error = details
updateStatus()
}
}
}
}),
on(socket, SocketEvent.Send, (message: ClientMessage) => {
if (isClientReq(message)) {
total++
pending.add(message[1])
updateStatus()
}
if (isClientEvent(message)) {
total++
pending.add(message[1].id)
updateStatus()
}
if (isClientClose(message)) {
pending.delete(message[1])
}
}),
]
return () => {
unsubscribers.forEach(call)
}
+31 -27
View File
@@ -2,8 +2,9 @@
import type {Snippet} from "svelte"
import {onMount} from "svelte"
import {page} from "$app/stores"
import {ago, MONTH} from "@welshman/lib"
import {ROOM_META, EVENT_TIME, THREAD, COMMENT, MESSAGE} from "@welshman/util"
import {ago, sleep, once, MONTH} from "@welshman/lib"
import {ROOM_META, EVENT_TIME, THREAD, COMMENT, MESSAGE, displayRelayUrl} from "@welshman/util"
import {SocketStatus} from "@welshman/net"
import Page from "@lib/components/Page.svelte"
import Dialog from "@lib/components/Dialog.svelte"
import SecondaryNav from "@lib/components/SecondaryNav.svelte"
@@ -13,8 +14,13 @@
import {pushToast} from "@app/util/toast"
import {pushModal} from "@app/util/modal"
import {setChecked} from "@app/util/notifications"
import {checkRelayConnection, checkRelayAuth, checkRelayAccess} from "@app/core/commands"
import {decodeRelay, userRoomsByUrl, relaysPendingTrust} from "@app/core/state"
import {
decodeRelay,
deriveRelayAuthError,
relaysPendingTrust,
deriveSocket,
userRoomsByUrl,
} from "@app/core/state"
import {pullConservatively} from "@app/core/requests"
import {notifications} from "@app/util/notifications"
@@ -28,21 +34,11 @@
const rooms = Array.from($userRoomsByUrl.get(url) || [])
const checkConnection = async (signal: AbortSignal) => {
const connectionError = await checkRelayConnection(url)
const socket = deriveSocket(url)
if (connectionError) {
return pushToast({theme: "error", message: connectionError})
}
const authError = deriveRelayAuthError(url)
const [authError, accessError] = await Promise.all([checkRelayAuth(url), checkRelayAccess(url)])
const error = authError || accessError
if (error && !signal.aborted) {
pushModal(SpaceAuthError, {url, error})
}
}
const showAuthError = once(() => pushModal(SpaceAuthError, {url, error: $authError}))
// We have to watch this one, since on mobile the badge will be visible when active
$effect(() => {
@@ -51,17 +47,29 @@
}
})
onMount(() => {
const relays = [url]
const since = ago(MONTH)
const controller = new AbortController()
// Watch for relay errors and notify the user
$effect(() => {
if ($authError) {
showAuthError()
}
})
checkConnection(controller.signal)
onMount(() => {
const since = ago(MONTH)
sleep(2000).then(() => {
if ($socket.status !== SocketStatus.Open) {
pushToast({
theme: "error",
message: `Failed to connect to ${displayRelayUrl(url)}`,
})
}
})
// Load group meta, threads, calendar events, comments, and recent messages
// for user rooms to help with a quick page transition
pullConservatively({
relays,
relays: [url],
filters: [
{kinds: [ROOM_META]},
{kinds: [THREAD, EVENT_TIME, MESSAGE], since},
@@ -69,10 +77,6 @@
...rooms.map(room => ({kinds: [MESSAGE], "#h": [room], since})),
],
})
return () => {
controller.abort()
}
})
</script>