Add support for blocked relays

This commit is contained in:
Jon Staab
2026-01-16 13:10:48 -08:00
parent 3f914d02cc
commit 87bb62b359
6 changed files with 88 additions and 68 deletions
+1
View File
@@ -15,6 +15,7 @@
* Fix memory leak, notification badge not showing * Fix memory leak, notification badge not showing
* Improve space join flow * Improve space join flow
* Fix opening images in fullscreen dialog * Fix opening images in fullscreen dialog
* Add support for blocked relays
# 1.6.2 # 1.6.2
-40
View File
@@ -52,10 +52,8 @@ import {
removeFromListByPredicate, removeFromListByPredicate,
getTag, getTag,
getListTags, getListTags,
getRelayTags,
getRelayTagValues, getRelayTagValues,
toNostrURI, toNostrURI,
getRelaysFromList,
RelayMode, RelayMode,
getAddress, getAddress,
getTagValue, getTagValue,
@@ -80,8 +78,6 @@ import {
publishThunk, publishThunk,
tagEvent, tagEvent,
tagEventForReaction, tagEventForReaction,
userRelayList,
userMessagingRelayList,
nip44EncryptToSelf, nip44EncryptToSelf,
dropSession, dropSession,
tagEventForComment, tagEventForComment,
@@ -208,42 +204,6 @@ export const removeRoomMembership = async (url: string, h: string) => {
return publishThunk({event, relays}) return publishThunk({event, relays})
} }
export const setRelayPolicy = (url: string, read: boolean, write: boolean) => {
const list = get(userRelayList) || makeList({kind: RELAYS})
const tags = getRelayTags(getListTags(list)).filter(t => normalizeRelayUrl(t[1]) !== url)
if (read && write) {
tags.push(["r", url])
} else if (read) {
tags.push(["r", url, "read"])
} else if (write) {
tags.push(["r", url, "write"])
}
return publishThunk({
event: makeEvent(list.kind, {tags}),
relays: [url, ...INDEXER_RELAYS, ...Router.get().FromUser().getUrls(), ...get(userSpaceUrls)],
})
}
export const setMessagingRelayPolicy = (url: string, enabled: boolean) => {
const list = get(userMessagingRelayList) || makeList({kind: MESSAGING_RELAYS})
// Only update messaging policies if they already exist or we're adding them
if (enabled || getRelaysFromList(list).includes(url)) {
const tags = getRelayTags(getListTags(list)).filter(t => normalizeRelayUrl(t[1]) !== url)
if (enabled) {
tags.push(["relay", url])
}
return publishThunk({
event: makeEvent(list.kind, {tags}),
relays: [...INDEXER_RELAYS, ...Router.get().FromUser().getUrls(), ...get(userSpaceUrls)],
})
}
}
// Relay access // Relay access
export const canEnforceNip70 = async (url: string) => { export const canEnforceNip70 = async (url: string) => {
+9 -7
View File
@@ -41,6 +41,7 @@ import {
loadRelayList, loadRelayList,
loadMessagingRelayList, loadMessagingRelayList,
loadBlossomServerList, loadBlossomServerList,
loadBlockedRelayList,
loadFollowList, loadFollowList,
loadMuteList, loadMuteList,
loadProfile, loadProfile,
@@ -215,6 +216,7 @@ const syncUserData = () => {
loadAlerts($userRelayList.event.pubkey) loadAlerts($userRelayList.event.pubkey)
loadAlertStatuses($userRelayList.event.pubkey) loadAlertStatuses($userRelayList.event.pubkey)
loadBlossomServerList($userRelayList.event.pubkey) loadBlossomServerList($userRelayList.event.pubkey)
loadBlockedRelayList($userRelayList.event.pubkey)
loadFollowList($userRelayList.event.pubkey) loadFollowList($userRelayList.event.pubkey)
loadGroupList($userRelayList.event.pubkey) loadGroupList($userRelayList.event.pubkey)
loadMuteList($userRelayList.event.pubkey) loadMuteList($userRelayList.event.pubkey)
@@ -229,13 +231,13 @@ const syncUserData = () => {
await sleep(1000) await sleep(1000)
await Promise.all( await Promise.all(
pubkeys.map(async pk => { pubkeys.flatMap(pk => [
await loadRelayList(pk) loadRelayList(pk),
await loadGroupList(pk) loadGroupList(pk),
await loadProfile(pk) loadProfile(pk),
await loadFollowList(pk) loadFollowList(pk),
await loadMuteList(pk) loadMuteList(pk),
}), ]),
) )
} }
}) })
+18 -1
View File
@@ -1,4 +1,5 @@
import {on, always, call, dissoc, assoc, uniq} from "@welshman/lib" import {on, always, call, dissoc, assoc, uniq} from "@welshman/lib"
import {RelayMode} from "@welshman/util"
import type {Socket, RelayMessage, ClientMessage} from "@welshman/net" import type {Socket, RelayMessage, ClientMessage} from "@welshman/net"
import { import {
makeSocketPolicyAuth, makeSocketPolicyAuth,
@@ -13,7 +14,7 @@ import {
isClientNegOpen, isClientNegOpen,
isClientNegClose, isClientNegClose,
} from "@welshman/net" } from "@welshman/net"
import {sign} from "@welshman/app" import {sign, pubkey, getPubkeyRelays} from "@welshman/app"
import { import {
userSettingsValues, userSettingsValues,
getSetting, getSetting,
@@ -23,6 +24,22 @@ import {
export const authPolicy = makeSocketPolicyAuth({sign, shouldAuth: always(true)}) export const authPolicy = makeSocketPolicyAuth({sign, shouldAuth: always(true)})
export const blockPolicy = (socket: Socket) => {
const previousOpen = socket.open
socket.open = () => {
const $pubkey = pubkey.get()
if (!$pubkey || !getPubkeyRelays($pubkey, RelayMode.Blocked).includes(socket.url)) {
return previousOpen()
}
}
return () => {
socket.open = previousOpen
}
}
export const trustPolicy = (socket: Socket) => { export const trustPolicy = (socket: Socket) => {
const buffer: RelayMessage[] = [] const buffer: RelayMessage[] = []
+2 -2
View File
@@ -26,7 +26,7 @@
import {setupHistory} from "@app/util/history" import {setupHistory} from "@app/util/history"
import {setupTracking} from "@app/util/tracking" import {setupTracking} from "@app/util/tracking"
import {setupAnalytics} from "@app/util/analytics" import {setupAnalytics} from "@app/util/analytics"
import {authPolicy, trustPolicy, mostlyRestrictedPolicy} from "@app/util/policies" import {authPolicy, blockPolicy, trustPolicy, mostlyRestrictedPolicy} from "@app/util/policies"
import {kv, db} from "@app/core/storage" import {kv, db} from "@app/core/storage"
import {userSettingsValues} from "@app/core/state" import {userSettingsValues} from "@app/core/state"
import {syncApplicationData} from "@app/core/sync" import {syncApplicationData} from "@app/core/sync"
@@ -43,7 +43,7 @@
const {children} = $props() const {children} = $props()
const policies = [authPolicy, trustPolicy, mostlyRestrictedPolicy] const policies = [authPolicy, blockPolicy, trustPolicy, mostlyRestrictedPolicy]
// Add stuff to window for convenience // Add stuff to window for convenience
Object.assign(window, { Object.assign(window, {
+58 -18
View File
@@ -1,6 +1,16 @@
<script lang="ts"> <script lang="ts">
import {onMount} from "svelte" import {onMount} from "svelte"
import {pubkey, getRelayLists, getMessagingRelayLists, derivePubkeyRelays} from "@welshman/app" import {
pubkey,
getRelayLists,
derivePubkeyRelays,
addRelay,
removeRelay,
addBlockedRelay,
removeBlockedRelay,
addMessagingRelay,
removeMessagingRelay,
} from "@welshman/app"
import {RelayMode} from "@welshman/util" import {RelayMode} from "@welshman/util"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
@@ -9,43 +19,42 @@
import RelayAdd from "@app/components/RelayAdd.svelte" import RelayAdd from "@app/components/RelayAdd.svelte"
import {pushModal} from "@app/util/modal" import {pushModal} from "@app/util/modal"
import {discoverRelays} from "@app/core/requests" import {discoverRelays} from "@app/core/requests"
import {setRelayPolicy, setMessagingRelayPolicy} from "@app/core/commands"
import Globus from "@assets/icons/globus.svg?dataurl" import Globus from "@assets/icons/globus.svg?dataurl"
import Inbox from "@assets/icons/inbox.svg?dataurl" import Inbox from "@assets/icons/inbox.svg?dataurl"
import Mailbox from "@assets/icons/mailbox.svg?dataurl" import Mailbox from "@assets/icons/mailbox.svg?dataurl"
import ForbiddenCircle from "@assets/icons/forbidden-circle.svg?dataurl"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl" import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import AddCircle from "@assets/icons/add-circle.svg?dataurl" import AddCircle from "@assets/icons/add-circle.svg?dataurl"
const readRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Read) const readRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Read)
const writeRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Write) const writeRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Write)
const blockedRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Blocked)
const messagingRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Messaging) const messagingRelayUrls = derivePubkeyRelays($pubkey!, RelayMode.Messaging)
const addReadRelay = () => const addReadRelays = () =>
pushModal(RelayAdd, { pushModal(RelayAdd, {
relays: readRelayUrls, relays: readRelayUrls,
addRelay: (url: string) => setRelayPolicy(url, true, $writeRelayUrls.includes(url)), addRelay: (url: string) => addRelay(url, RelayMode.Read),
}) })
const addWriteRelay = () => const addWriteRelays = () =>
pushModal(RelayAdd, { pushModal(RelayAdd, {
relays: writeRelayUrls, relays: writeRelayUrls,
addRelay: (url: string) => setRelayPolicy(url, $readRelayUrls.includes(url), true), addRelay: (url: string) => addRelay(url, RelayMode.Write),
}) })
const addMessagingRelay = () => const addBlockedRelays = () =>
pushModal(RelayAdd, { pushModal(RelayAdd, {relays: blockedRelayUrls, addRelay: addBlockedRelay})
relays: messagingRelayUrls,
addRelay: (url: string) => setMessagingRelayPolicy(url, true),
})
const removeReadRelay = (url: string) => setRelayPolicy(url, false, $writeRelayUrls.includes(url)) const addMessagingRelays = () =>
pushModal(RelayAdd, {relays: messagingRelayUrls, addRelay: addMessagingRelay})
const removeWriteRelay = (url: string) => setRelayPolicy(url, $readRelayUrls.includes(url), false) const removeReadRelay = (url: string) => removeRelay(url, RelayMode.Read)
const removeMessagingRelay = (url: string) => setMessagingRelayPolicy(url, false) const removeWriteRelay = (url: string) => removeRelay(url, RelayMode.Write)
onMount(() => { onMount(() => {
discoverRelays([...getRelayLists(), ...getMessagingRelayLists()]) discoverRelays(getRelayLists())
}) })
</script> </script>
@@ -77,7 +86,7 @@
{:else} {:else}
<p class="text-center text-sm">No relays found</p> <p class="text-center text-sm">No relays found</p>
{/each} {/each}
<Button class="btn btn-primary mt-2" onclick={addWriteRelay}> <Button class="btn btn-primary mt-2" onclick={addWriteRelays}>
<Icon icon={AddCircle} /> <Icon icon={AddCircle} />
Add Relay Add Relay
</Button> </Button>
@@ -109,7 +118,7 @@
{:else} {:else}
<p class="text-center text-sm">No relays found</p> <p class="text-center text-sm">No relays found</p>
{/each} {/each}
<Button class="btn btn-primary mt-2" onclick={addReadRelay}> <Button class="btn btn-primary mt-2" onclick={addReadRelays}>
<Icon icon={AddCircle} /> <Icon icon={AddCircle} />
Add Relay Add Relay
</Button> </Button>
@@ -142,7 +151,38 @@
{:else} {:else}
<p class="text-center text-sm">No relays found</p> <p class="text-center text-sm">No relays found</p>
{/each} {/each}
<Button class="btn btn-primary mt-2" onclick={addMessagingRelay}> <Button class="btn btn-primary mt-2" onclick={addMessagingRelays}>
<Icon icon={AddCircle} />
Add Relay
</Button>
</div>
</Collapse>
<Collapse class="card2 bg-alt column gap-4 shadow-md">
{#snippet title()}
<h2 class="flex items-center gap-3 text-xl">
<Icon icon={ForbiddenCircle} />
Blocked Relays
</h2>
{/snippet}
{#snippet description()}
<p class="text-sm">
These relays will never be connected to by clients supporting this policy.
</p>
{/snippet}
<div class="column gap-2">
{#each $blockedRelayUrls.sort() as url (url)}
<RelayItem {url}>
<Button
class="tooltip flex items-center"
data-tip="Stop using this relay"
onclick={() => removeBlockedRelay(url)}>
<Icon icon={CloseCircle} />
</Button>
</RelayItem>
{:else}
<p class="text-center text-sm">No relays found</p>
{/each}
<Button class="btn btn-primary mt-2" onclick={addBlockedRelays}>
<Icon icon={AddCircle} /> <Icon icon={AddCircle} />
Add Relay Add Relay
</Button> </Button>