diff --git a/src/app/components/RoomDetail.svelte b/src/app/components/RoomDetail.svelte
index 80688c7c..3115b3d5 100644
--- a/src/app/components/RoomDetail.svelte
+++ b/src/app/components/RoomDetail.svelte
@@ -243,7 +243,7 @@
{/if}
- {#if $members.length > 0}
+ {#if $members !== undefined && $members.length > 0}
Members:
@@ -251,6 +251,10 @@
+ {:else if $members === undefined}
+
+ Member list not available from this relay
+
{/if}
Room Settings
diff --git a/src/app/components/RoomMembers.svelte b/src/app/components/RoomMembers.svelte
index e9dd1beb..8ff478b9 100644
--- a/src/app/components/RoomMembers.svelte
+++ b/src/app/components/RoomMembers.svelte
@@ -73,34 +73,44 @@
- {#each $members as pubkey (pubkey)}
-
-
-
-
-
- {#if menuPubkey === pubkey}
-
-
- -
-
-
-
-
- {/if}
+ {#if $members === undefined}
+
+ Member list not available from this relay
+
+ {:else if $members.length === 0}
+
+ No members yet
+
+ {:else}
+ {#each $members as pubkey (pubkey)}
+
+
+
+
+
+ {#if menuPubkey === pubkey}
+
+
+ -
+
+
+
+
+ {/if}
+
-
- {/each}
+ {/each}
+ {/if}
diff --git a/src/app/components/RoomMembersAdd.svelte b/src/app/components/RoomMembersAdd.svelte
index 63397e3d..e02954ad 100644
--- a/src/app/components/RoomMembersAdd.svelte
+++ b/src/app/components/RoomMembersAdd.svelte
@@ -56,6 +56,11 @@
}
const onSubmit = async () => {
+ if (!$spaceMembers) {
+ addMembers()
+ return
+ }
+
const pubkeysSnapshot = $state.snapshot(pubkeys)
const nonSpaceMembers = pubkeysSnapshot.filter(pubkey => !$spaceMembers.includes(pubkey))
diff --git a/src/app/components/SpaceMembers.svelte b/src/app/components/SpaceMembers.svelte
index a3eae54c..d8b311b6 100644
--- a/src/app/components/SpaceMembers.svelte
+++ b/src/app/components/SpaceMembers.svelte
@@ -112,46 +112,58 @@
{/if}
{/if}
- {#each $members as pubkey (pubkey)}
-
-
-
- {#if canBan || canUnallow}
-
-
- {#if menuPubkey === pubkey}
-
-
- {#if canUnallow}
- -
-
-
- {/if}
- {#if canBan}
- -
-
-
- {/if}
-
-
- {/if}
-
- {/if}
-
+ {#if $members === undefined}
+
+ Member list not available from this space
- {/each}
+ {:else if $members.length === 0}
+
+ No members yet
+
+ {:else}
+ {#each $members as pubkey (pubkey)}
+
+
+
+ {#if canBan || canUnallow}
+
+
+ {#if menuPubkey === pubkey}
+
+
+ {#if canUnallow}
+ -
+
+
+ {/if}
+ {#if canBan}
+ -
+
+
+ {/if}
+
+
+ {/if}
+
+ {/if}
+
+
+ {/each}
+ {/if}
diff --git a/src/app/components/SpaceMenu.svelte b/src/app/components/SpaceMenu.svelte
index 782e3509..09a12ba8 100644
--- a/src/app/components/SpaceMenu.svelte
+++ b/src/app/components/SpaceMenu.svelte
@@ -181,7 +181,11 @@
{#if $userIsAdmin}
diff --git a/src/app/core/commands.ts b/src/app/core/commands.ts
index 9f99c659..97a866d3 100644
--- a/src/app/core/commands.ts
+++ b/src/app/core/commands.ts
@@ -766,9 +766,10 @@ export const addSpaceMembers = async (
pubkeys: string[],
): Promise => {
const spaceMembers = get(deriveSpaceMembers(url))
+
const results = await Promise.all(
pubkeys
- .filter(pubkey => !spaceMembers.includes(pubkey))
+ .filter(pubkey => !spaceMembers || !spaceMembers.includes(pubkey))
.map(pubkey =>
manageRelay(url, {
method: ManagementMethod.AllowPubkey,
diff --git a/src/app/core/state.ts b/src/app/core/state.ts
index 767b16a9..78e8576c 100644
--- a/src/app/core/state.ts
+++ b/src/app/core/state.ts
@@ -808,36 +808,49 @@ export const deriveOtherRooms = (url: string) =>
// Space/room memberships
-const getSpaceMembers = (_url: string, events: TrustedEvent[]) => {
- const members = new Set()
+export const deriveSpaceMembers = (url: string) =>
+ derived(deriveRelaySignedEvents(url, [{kinds: [RELAY_MEMBERS]}]), ([event]) =>
+ uniq(getTagValues("member", event?.tags ?? [])),
+ )
- for (const event of sortEventsAsc(events)) {
- if (event.kind === RELAY_MEMBERS) {
- members.clear()
+export const deriveRoomMembers = (url: string, h: string) => {
+ const filters: Filter[] = [{kinds: [ROOM_MEMBERS], "#d": [h]}]
- for (const pubkey of uniq(getTagValues("member", event.tags))) {
- members.add(pubkey)
- }
+ return derived(deriveEventsForUrl(url, filters), ([event]) =>
+ uniq(getPubkeyTagValues(event?.tags ?? [])),
+ )
+}
- continue
+export type BannedPubkeyItem = {
+ pubkey: string
+ reason: string
+}
+
+export const spaceBannedPubkeyItems = new Map()
+
+export const deriveSpaceBannedPubkeyItems = (url: string) => {
+ const store = writable(spaceBannedPubkeyItems.get(url) || [])
+
+ manageRelay(url, {method: ManagementMethod.ListBannedPubkeys, params: []}).then(res => {
+ spaceBannedPubkeyItems.set(url, res.result)
+ store.set(res.result)
+ })
+
+ return store
+}
+
+export const deriveRoomAdmins = (url: string, h: string) => {
+ const filters: Filter[] = [{kinds: [ROOM_ADMINS], "#d": [h]}]
+
+ return derived(deriveEventsForUrl(url, filters), $events => {
+ const adminsEvent = first($events)
+
+ if (adminsEvent) {
+ return getPubkeyTagValues(adminsEvent.tags)
}
- const pubkeys = getPubkeyTagValues(event.tags)
-
- if (event.kind === RELAY_ADD_MEMBER) {
- for (const pubkey of pubkeys) {
- members.add(pubkey)
- }
- }
-
- if (event.kind === RELAY_REMOVE_MEMBER) {
- for (const pubkey of pubkeys) {
- members.delete(pubkey)
- }
- }
- }
-
- return Array.from(members)
+ return []
+ })
}
const getRoomMembers = (_url: string, h: string, events: TrustedEvent[]) => {
@@ -876,53 +889,6 @@ const getRoomMembers = (_url: string, h: string, events: TrustedEvent[]) => {
return Array.from(members)
}
-export const deriveSpaceMembers = (url: string) =>
- derived(
- deriveRelaySignedEvents(url, [{kinds: [RELAY_ADD_MEMBER, RELAY_REMOVE_MEMBER, RELAY_MEMBERS]}]),
- $events => getSpaceMembers(url, $events),
- )
-
-export type BannedPubkeyItem = {
- pubkey: string
- reason: string
-}
-
-export const spaceBannedPubkeyItems = new Map()
-
-export const deriveSpaceBannedPubkeyItems = (url: string) => {
- const store = writable(spaceBannedPubkeyItems.get(url) || [])
-
- manageRelay(url, {method: ManagementMethod.ListBannedPubkeys, params: []}).then(res => {
- spaceBannedPubkeyItems.set(url, res.result)
- store.set(res.result)
- })
-
- return store
-}
-
-export const deriveRoomMembers = (url: string, h: string) => {
- const filters: Filter[] = [
- {kinds: [ROOM_MEMBERS], "#d": [h]},
- {kinds: [ROOM_ADD_MEMBER, ROOM_REMOVE_MEMBER], "#h": [h]},
- ]
-
- return derived(deriveEventsForUrl(url, filters), $events => getRoomMembers(url, h, $events))
-}
-
-export const deriveRoomAdmins = (url: string, h: string) => {
- const filters: Filter[] = [{kinds: [ROOM_ADMINS], "#d": [h]}]
-
- return derived(deriveEventsForUrl(url, filters), $events => {
- const adminsEvent = first($events)
-
- if (adminsEvent) {
- return getPubkeyTagValues(adminsEvent.tags)
- }
-
- return []
- })
-}
-
// Action items (admin review queue)
// const pendingJoins: TrustedEvent[] = []
@@ -1019,19 +985,49 @@ export const deriveUserIsSpaceAdmin = memoize((url?: string) => {
})
export const deriveUserSpaceMembershipStatus = (url: string) => {
- const filters: Filter[] = [{kinds: [RELAY_JOIN, RELAY_LEAVE]}]
+ // Fetch member list and user add/remove events directly in this derivation.
+ const memberListFilters: Filter[] = [{kinds: [RELAY_MEMBERS]}]
+ const userEventFilters: Filter[] = [{kinds: [RELAY_ADD_MEMBER, RELAY_REMOVE_MEMBER]}]
return derived(
[
pubkey,
- deriveSpaceMembers(url),
- deriveEventsForUrl(url, filters),
+ deriveRelaySignedEvents(url, memberListFilters),
+ deriveRelaySignedEvents(url, userEventFilters),
+ deriveEventsForUrl(url, [{kinds: [RELAY_JOIN, RELAY_LEAVE]}]),
deriveUserIsSpaceAdmin(url),
],
- ([$pubkey, $members, $events, $isAdmin]) => {
- const isMember = $members.includes($pubkey!) || $isAdmin
+ ([$pubkey, $memberListEvents, $userAddRemoveEvents, $joinLeaveEvents, $isAdmin]) => {
+ // If admin, always granted.
+ if ($isAdmin) {
+ return MembershipStatus.Granted
+ }
- for (const event of $events) {
+ const membersEvent = $memberListEvents.find(spec({kind: RELAY_MEMBERS}))
+ const memberList = membersEvent ? uniq(getTagValues("member", membersEvent.tags)) : undefined
+
+ let isMember = false
+
+ if (memberList) {
+ // Member list exists - check if user is in it.
+ isMember = memberList.includes($pubkey!)
+ } else {
+ // No member list available - replay the user's add/remove history.
+ for (const event of sortBy(e => e.created_at, $userAddRemoveEvents)) {
+ if (event.pubkey !== $pubkey) {
+ continue
+ }
+
+ if (event.kind === RELAY_ADD_MEMBER) {
+ isMember = true
+ } else if (event.kind === RELAY_REMOVE_MEMBER) {
+ isMember = false
+ }
+ }
+ }
+
+ for (const event of $joinLeaveEvents) {
+ // Join events indicate pending or granted status, leave resets to initial.
if (event.pubkey !== $pubkey) {
continue
}
@@ -1057,19 +1053,46 @@ export const deriveUserIsRoomAdmin = (url: string, h: string) =>
)
export const deriveUserRoomMembershipStatus = (url: string, h: string) => {
- const filters: Filter[] = [{kinds: [ROOM_JOIN, ROOM_LEAVE], "#h": [h]}]
+ // Fetch the room member list and the current user's add/remove events.
+ const userEventFilters: Filter[] = [{kinds: [ROOM_ADD_MEMBER, ROOM_REMOVE_MEMBER], "#h": [h]}]
+ const joinLeaveFilters: Filter[] = [{kinds: [ROOM_JOIN, ROOM_LEAVE], "#h": [h]}]
return derived(
[
pubkey,
deriveRoomMembers(url, h),
- deriveEventsForUrl(url, filters),
+ deriveEventsForUrl(url, userEventFilters),
+ deriveEventsForUrl(url, joinLeaveFilters),
deriveUserIsRoomAdmin(url, h),
],
- ([$pubkey, $members, $events, $isAdmin]) => {
- const isMember = $members.includes($pubkey!) || $isAdmin
+ ([$pubkey, $memberList, $userAddRemoveEvents, $joinLeaveEvents, $isAdmin]) => {
+ // If admin of this room's space, always granted.
+ if ($isAdmin) {
+ return MembershipStatus.Granted
+ }
- for (const event of $events) {
+ let isMember = false
+
+ if ($memberList) {
+ // Member list exists - check if user is in it.
+ isMember = $memberList.includes($pubkey!)
+ } else {
+ // No member list available - replay the user's add/remove history.
+ for (const event of sortEventsAsc($userAddRemoveEvents)) {
+ if (event.pubkey !== $pubkey) {
+ continue
+ }
+
+ if (event.kind === ROOM_ADD_MEMBER) {
+ isMember = true
+ } else if (event.kind === ROOM_REMOVE_MEMBER) {
+ isMember = false
+ }
+ }
+ }
+
+ for (const event of $joinLeaveEvents) {
+ // Join events indicate pending or granted status, leave resets to initial.
if (event.pubkey !== $pubkey) {
continue
}
diff --git a/src/app/core/sync.ts b/src/app/core/sync.ts
index 3d660553..ff1f33c9 100644
--- a/src/app/core/sync.ts
+++ b/src/app/core/sync.ts
@@ -284,7 +284,7 @@ const syncSpace = (url: string) => {
{kinds: [MESSAGE, ...CONTENT_KINDS], since, "#h": [room]},
makeCommentFilter(CONTENT_KINDS, {since, "#h": [room]}),
{
- kinds: [ROOM_DELETE, ROOM_JOIN, ROOM_LEAVE, ROOM_ADD_MEMBER, ROOM_REMOVE_MEMBER],
+ kinds: [ROOM_DELETE, ROOM_JOIN, ROOM_LEAVE],
"#h": [room],
},
{kinds: [PollResponse], since},
@@ -293,15 +293,15 @@ const syncSpace = (url: string) => {
}
}
- const relayKinds = [RELAY_MEMBERS, RELAY_ADD_MEMBER, RELAY_REMOVE_MEMBER]
+ const relayKinds = [RELAY_MEMBERS]
const roomMetaKinds = [ROOM_META, ROOM_ADMINS, ROOM_MEMBERS, LIVEKIT_PARTICIPANTS]
- const roomMemberKinds = [ROOM_DELETE, ROOM_JOIN, ROOM_LEAVE, ROOM_ADD_MEMBER, ROOM_REMOVE_MEMBER]
+ const roomDeleteKinds = [ROOM_DELETE, ROOM_JOIN, ROOM_LEAVE]
pullAndListen({
url,
signal: controller.signal,
filters: [
- {kinds: [...relayKinds, ...roomMetaKinds, ...roomMemberKinds, ...CONTENT_KINDS, MESSAGE]},
+ {kinds: [...relayKinds, ...roomMetaKinds, ...roomDeleteKinds, ...CONTENT_KINDS, MESSAGE]},
makeCommentFilter(CONTENT_KINDS, {since}),
{kinds: [PollResponse], since},
],
diff --git a/src/app/editor/index.ts b/src/app/editor/index.ts
index c4e5df3f..9a787c4b 100644
--- a/src/app/editor/index.ts
+++ b/src/app/editor/index.ts
@@ -79,7 +79,7 @@ export const makeEditor = async ({
getValue: (profile: PublishedProfile) => profile.event.pubkey,
sortFn: ({score = 1, item}) => {
const wotScore = getWotGraph().get(item.event.pubkey) || 0
- const membershipScale = $spaceMembers.includes(item.event.pubkey) ? 2 : 1
+ const membershipScale = $spaceMembers?.includes(item.event.pubkey) ? 2 : 1
return dec(score) * inc(wotScore / getMaxWot()) * membershipScale
},