Refactor: use relay-provided member lists as source of truth #191

Merged
hodlbod merged 8 commits from :dev into dev 2026-04-13 21:12:51 +00:00
10 changed files with 33 additions and 54 deletions
Showing only changes of commit 8d1e4e3235 - Show all commits
+2 -2
View File
@@ -33,7 +33,7 @@
import RoomImage from "@app/components/RoomImage.svelte"
import {
deriveRoom,
deriveRoomMemberList,
deriveRoomMembers,
deriveUserIsRoomAdmin,
deriveUserRoomMembershipStatus,
deriveUserRooms,
@@ -57,7 +57,7 @@
const {url, h}: Props = $props()
const room = deriveRoom(url, h)
const members = deriveRoomMemberList(url, h)
const members = deriveRoomMembers(url, h)
const userIsAdmin = deriveUserIsRoomAdmin(url, h)
const membershipStatus = deriveUserRoomMembershipStatus(url, h)
const userRooms = deriveUserRooms(url)
+2 -2
View File
@@ -18,7 +18,7 @@
import Profile from "@app/components/Profile.svelte"
import RoomName from "@app/components/RoomName.svelte"
import RoomMembersAdd from "@app/components/RoomMembersAdd.svelte"
import {deriveRoom, deriveRoomMemberList, deriveUserIsRoomAdmin} from "@app/core/state"
import {deriveRoom, deriveRoomMembers, deriveUserIsRoomAdmin} from "@app/core/state"
import {pushModal} from "@app/util/modal"
import {pushToast} from "@app/util/toast"
@@ -30,7 +30,7 @@
const {url, h}: Props = $props()
const room = deriveRoom(url, h)
const members = deriveRoomMemberList(url, h)
const members = deriveRoomMembers(url, h)
const userIsAdmin = deriveUserIsRoomAdmin(url, h)
const back = () => history.back()
+3 -7
View File
@@ -20,7 +20,7 @@
import ProfileMultiSelect from "@app/components/ProfileMultiSelect.svelte"
import {pushToast} from "@app/util/toast"
import {pushModal} from "@app/util/modal"
import {deriveRoom, deriveRelayMemberList} from "@app/core/state"
import {deriveRoom, getSpaceMembers} from "@app/core/state"
import {addRoomMembers} from "@app/core/commands"
interface Props {
@@ -31,7 +31,7 @@
const {url, h}: Props = $props()
const room = deriveRoom(url, h)
const spaceMembers = deriveRelayMemberList(url)
const spaceMembers = getSpaceMembers(url)
const back = () => history.back()
@@ -56,12 +56,8 @@
}
const onSubmit = async () => {
// Space member list is required to add members to a room
if (!$spaceMembers) {
pushToast({
theme: "error",
message: "Cannot add members: space member list not available from this relay",
})
addMembers()
return
}
1
+3 -3
View File
@@ -22,7 +22,7 @@
import SpaceMembersAdd from "@app/components/SpaceMembersAdd.svelte"
import SpaceMembersBanned from "@app/components/SpaceMembersBanned.svelte"
import {
deriveRelayMemberList,
getSpaceMembers,
deriveSpaceBannedPubkeyItems,
deriveUserIsSpaceAdmin,
deriveSupportedMethods,
@@ -36,7 +36,7 @@
const {url}: Props = $props()
const members = deriveRelayMemberList(url)
const members = getSpaceMembers(url)
const bans = deriveSpaceBannedPubkeyItems(url)
const userIsAdmin = deriveUserIsSpaceAdmin(url)
const supportedMethods = deriveSupportedMethods(url)
@@ -114,7 +114,7 @@
<div class="flex flex-col gap-2">
{#if $members === undefined}
<div class="card2 bg-base-200 p-4">
<span class="text-error">Member list not available from this relay</span>
<span class="text-error">Member list not available from this space</span>
hodlbod marked this conversation as resolved Outdated
Outdated
Review

Use "space" instead of "relay"

Use "space" instead of "relay"
</div>
{:else if $members.length === 0}
<div class="card2 bg-base-200 p-4">
+3 -3
View File
@@ -44,7 +44,7 @@
import {
ENABLE_ZAPS,
CONTENT_KINDS,
deriveRelayMemberList,
getSpaceMembers,
deriveUserRooms,
deriveOtherRooms,
deriveOtherVoiceRooms,
@@ -76,7 +76,7 @@
const userRooms = deriveUserRooms(url)
const otherRooms = deriveOtherRooms(url)
const otherVoiceRooms = deriveOtherVoiceRooms(url)
const members = deriveRelayMemberList(url)
const members = getSpaceMembers(url)
const userIsAdmin = deriveUserIsSpaceAdmin(url)
const actionItems = deriveSpaceActionItems(url)
@@ -182,7 +182,7 @@
<Button onclick={showMembers}>
<Icon icon={UserRounded} />
{#if $members === undefined}
View Members (unavailable)
View Members
Outdated
Review

Remove (unavailable)

Remove `(unavailable)`
{:else}
View Members ({$members.length})
{/if}
+3 -8
View File
@@ -95,7 +95,7 @@ import {
stripPrefix,
relaysMostlyRestricted,
deriveSocket,
deriveRelayMemberList,
getSpaceMembers,
} from "@app/core/state"
// Utils
@@ -765,16 +765,11 @@ export const addSpaceMembers = async (
url: string,
pubkeys: string[],
): Promise<string | undefined> => {
const spaceMembers = get(deriveRelayMemberList(url))
// Cannot add members without access to the member list
if (spaceMembers === undefined) {
return "Member list not available from this relay"
}
const spaceMembers = get(getSpaceMembers(url))
const results = await Promise.all(
pubkeys
.filter(pubkey => !spaceMembers.includes(pubkey))
.filter(pubkey => spaceMembers && !spaceMembers.includes(pubkey))
.map(pubkey =>
hodlbod marked this conversation as resolved Outdated
Outdated
Review

Don't fail hard, just send all pubkeys (change the filter line to .filter(pubkey => spaceMembers && !spaceMembers.includes(pubkey)))

Don't fail hard, just send all pubkeys (change the filter line to `.filter(pubkey => spaceMembers && !spaceMembers.includes(pubkey))`)
manageRelay(url, {
method: ManagementMethod.AllowPubkey,
+9 -12
View File
@@ -8,7 +8,6 @@ import {
on,
gt,
max,
find,
spec,
call,
first,
@@ -809,19 +808,17 @@ export const deriveOtherRooms = (url: string) =>
// Space/room memberships
hodlbod marked this conversation as resolved Outdated
Outdated
Review

Let's just keep calling this getSpaceMembers. I was thinking of using the list directly, but it's more convenient to return the uniqe list of pubkeys. However, you might change the body to:

([event]) => uniq(getTagValues("member", event?.tags ?? []))

Same thing in deriveRoomMemberList (which should be deriveRoomMembers).

Let's just keep calling this `getSpaceMembers`. I was thinking of using the list directly, but it's more convenient to return the uniqe list of pubkeys. However, you might change the body to: ```typescript ([event]) => uniq(getTagValues("member", event?.tags ?? [])) ``` Same thing in deriveRoomMemberList (which should be deriveRoomMembers).
export const deriveRelayMemberList = (url: string) =>
derived(deriveRelaySignedEvents(url, [{kinds: [RELAY_MEMBERS]}]), $events => {
const membersEvent = $events.find(spec({kind: RELAY_MEMBERS}))
return membersEvent ? uniq(getTagValues("member", membersEvent.tags)) : undefined
})
export const getSpaceMembers = (url: string) =>
hodlbod marked this conversation as resolved Outdated
Outdated
Review

This should be renamed to deriveSpaceMembers since it returns a svelte store, not the value itself.

This should be renamed to deriveSpaceMembers since it returns a svelte store, not the value itself.
derived(deriveRelaySignedEvents(url, [{kinds: [RELAY_MEMBERS]}]), ([event]) =>
uniq(getTagValues("member", event?.tags ?? [])),
)
export const deriveRoomMemberList = (url: string, h: string) => {
export const deriveRoomMembers = (url: string, h: string) => {
const filters: Filter[] = [{kinds: [ROOM_MEMBERS], "#d": [h]}]
return derived(deriveEventsForUrl(url, filters), $events => {
const membersEvent = find(spec({kind: ROOM_MEMBERS}), $events)
return membersEvent ? uniq(getPubkeyTagValues(membersEvent.tags)) : undefined
})
return derived(deriveEventsForUrl(url, filters), ([event]) =>
uniq(getPubkeyTagValues(event?.tags ?? [])),
)
}
export type BannedPubkeyItem = {
@@ -1063,7 +1060,7 @@ export const deriveUserRoomMembershipStatus = (url: string, h: string) => {
return derived(
[
pubkey,
deriveRoomMemberList(url, h),
deriveRoomMembers(url, h),
deriveEventsForUrl(url, userEventFilters),
deriveEventsForUrl(url, joinLeaveFilters),
deriveUserIsRoomAdmin(url, h),
+4 -6
View File
@@ -174,9 +174,8 @@ const syncUserSpaceMembership = (url: string) => {
url,
signal: controller.signal,
filters: [
// Keep current-user membership history so status replay stays deterministic.
{kinds: [RELAY_ADD_MEMBER], "#p": [$pubkey]},
{kinds: [RELAY_REMOVE_MEMBER], "#p": [$pubkey]},
{kinds: [RELAY_ADD_MEMBER], "#p": [$pubkey], limit: 1},
{kinds: [RELAY_REMOVE_MEMBER], "#p": [$pubkey], limit: 1},
{kinds: [ROOM_CREATE_PERMISSION], "#p": [$pubkey], limit: 1},
Outdated
Review

Keep the limit, we want that

Keep the limit, we want that
],
})
@@ -194,9 +193,8 @@ const syncUserRoomMembership = (url: string, h: string) => {
url,
signal: controller.signal,
filters: [
// Keep current-user membership history so status replay stays deterministic.
{kinds: [ROOM_ADD_MEMBER], "#p": [$pubkey], "#h": [h]},
{kinds: [ROOM_REMOVE_MEMBER], "#p": [$pubkey], "#h": [h]},
{kinds: [ROOM_ADD_MEMBER], "#p": [$pubkey], "#h": [h], limit: 1},
{kinds: [ROOM_REMOVE_MEMBER], "#p": [$pubkey], "#h": [h], limit: 1},
],
})
Outdated
Review

Keep the limit, remove the comment

Keep the limit, remove the comment
}
1
+2 -9
View File
@@ -28,13 +28,7 @@ import {RoomReferenceExtension} from "@app/editor/RoomReferenceExtension"
import RoomSuggestion from "@app/editor/RoomSuggestion.svelte"
import {NativeClipboardPasteExtension} from "@app/editor/clipboard"
import {uploadFile} from "@app/core/commands"
import {
deriveRelayMemberList,
makeRoomId,
splitRoomId,
userSpaceUrls,
roomsByUrl,
} from "@app/core/state"
import {getSpaceMembers, makeRoomId, splitRoomId, userSpaceUrls, roomsByUrl} from "@app/core/state"
import {pushToast} from "@app/util/toast"
export const makeEditor = async ({
@@ -64,7 +58,7 @@ export const makeEditor = async ({
[
throttled(800, profiles),
throttled(800, handlesByNip05),
throttled(800, deriveRelayMemberList(url || "")),
throttled(800, getSpaceMembers(url || "")),
],
([$profiles, $handlesByNip05, $spaceMembers]) => {
// Remove invalid nip05's from profiles
1
@@ -79,7 +73,6 @@ export const makeEditor = async ({
getValue: (profile: PublishedProfile) => profile.event.pubkey,
sortFn: ({score = 1, item}) => {
const wotScore = getWotGraph().get(item.event.pubkey) || 0
// Boost score for space members. If member list is unavailable, this falls through to 1x multiplier
const membershipScale = $spaceMembers?.includes(item.event.pubkey) ? 2 : 1
return dec(score) * inc(wotScore / getMaxWot()) * membershipScale
+2 -2
View File
@@ -26,7 +26,7 @@
import RoomCompose from "@app/components/RoomCompose.svelte"
import RoomComposeEdit from "@src/app/components/RoomComposeEdit.svelte"
import RoomComposeParent from "@app/components/RoomComposeParent.svelte"
import {userSettingsValues, decodeRelay, PROTECTED, CONTENT_KINDS} from "@app/core/state"
import {userSettingsValues, decodeRelay, PROTECTED} from "@app/core/state"
import {prependParent, canEnforceNip70, publishDelete} from "@app/core/commands"
import {checked} from "@app/util/notifications"
import {pushToast} from "@app/util/toast"
@@ -258,7 +258,7 @@
url,
at: at || now(),
element: element!,
filters: [{kinds: [...CONTENT_KINDS, MESSAGE]}],
filters: [{kinds: [MESSAGE, RELAY_ADD_MEMBER]}],
Outdated
Review

This is incorrect, it should be the way it was before.

This is incorrect, it should be the way it was before.
onBackwardExhausted: () => {
loadingBackward = false
},