Add relay members list and room join/leave events
This commit is contained in:
+11
-21
@@ -35,7 +35,7 @@ import {
|
||||
RELAYS,
|
||||
FOLLOWS,
|
||||
REACTION,
|
||||
AUTH_JOIN,
|
||||
RELAY_JOIN,
|
||||
ROOMS,
|
||||
COMMENT,
|
||||
ALERT_EMAIL,
|
||||
@@ -97,15 +97,14 @@ import type {SettingsValues, Alert} from "@app/core/state"
|
||||
import {
|
||||
SETTINGS,
|
||||
PROTECTED,
|
||||
userMembership,
|
||||
INDEXER_RELAYS,
|
||||
NOTIFIER_PUBKEY,
|
||||
NOTIFIER_RELAY,
|
||||
DEFAULT_BLOSSOM_SERVERS,
|
||||
userRoomsByUrl,
|
||||
userSpaceUrls,
|
||||
userSettingsValues,
|
||||
userInboxRelays,
|
||||
getMembershipUrls,
|
||||
userGroupSelections,
|
||||
} from "@app/core/state"
|
||||
import {loadAlertStatuses} from "@app/core/requests"
|
||||
import {platform, platformName, getPushInfo} from "@app/util/push"
|
||||
@@ -175,7 +174,7 @@ export const broadcastUserData = async (relays: string[]) => {
|
||||
// List updates
|
||||
|
||||
export const addSpaceMembership = async (url: string) => {
|
||||
const list = get(userMembership) || makeList({kind: ROOMS})
|
||||
const list = get(userGroupSelections) || makeList({kind: ROOMS})
|
||||
const event = await addToListPublicly(list, ["r", url]).reconcile(nip44EncryptToSelf)
|
||||
const relays = uniq([...Router.get().FromUser().getUrls(), ...getRelayTagValues(event.tags)])
|
||||
|
||||
@@ -183,7 +182,7 @@ export const addSpaceMembership = async (url: string) => {
|
||||
}
|
||||
|
||||
export const removeSpaceMembership = async (url: string) => {
|
||||
const list = get(userMembership) || makeList({kind: ROOMS})
|
||||
const list = get(userGroupSelections) || makeList({kind: ROOMS})
|
||||
const pred = (t: string[]) => t[t[0] === "r" ? 1 : 2] === url
|
||||
const event = await removeFromListByPredicate(list, pred).reconcile(nip44EncryptToSelf)
|
||||
const relays = uniq([url, ...Router.get().FromUser().getUrls(), ...getRelayTagValues(event.tags)])
|
||||
@@ -192,7 +191,7 @@ export const removeSpaceMembership = async (url: string) => {
|
||||
}
|
||||
|
||||
export const addRoomMembership = async (url: string, room: string) => {
|
||||
const list = get(userMembership) || makeList({kind: ROOMS})
|
||||
const list = get(userGroupSelections) || makeList({kind: ROOMS})
|
||||
const newTags = [
|
||||
["r", url],
|
||||
["group", room, url],
|
||||
@@ -204,7 +203,7 @@ export const addRoomMembership = async (url: string, room: string) => {
|
||||
}
|
||||
|
||||
export const removeRoomMembership = async (url: string, room: string) => {
|
||||
const list = get(userMembership) || makeList({kind: ROOMS})
|
||||
const list = get(userGroupSelections) || makeList({kind: ROOMS})
|
||||
const pred = (t: string[]) => equals(["group", room, url], t.slice(0, 3))
|
||||
const event = await removeFromListByPredicate(list, pred).reconcile(nip44EncryptToSelf)
|
||||
const relays = uniq([url, ...Router.get().FromUser().getUrls(), ...getRelayTagValues(event.tags)])
|
||||
@@ -226,12 +225,7 @@ export const setRelayPolicy = (url: string, read: boolean, write: boolean) => {
|
||||
|
||||
return publishThunk({
|
||||
event: makeEvent(list.kind, {tags}),
|
||||
relays: [
|
||||
url,
|
||||
...INDEXER_RELAYS,
|
||||
...Router.get().FromUser().getUrls(),
|
||||
...userRoomsByUrl.get().keys(),
|
||||
],
|
||||
relays: [url, ...INDEXER_RELAYS, ...Router.get().FromUser().getUrls(), ...get(userSpaceUrls)],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -248,11 +242,7 @@ export const setInboxRelayPolicy = (url: string, enabled: boolean) => {
|
||||
|
||||
return publishThunk({
|
||||
event: makeEvent(list.kind, {tags}),
|
||||
relays: [
|
||||
...INDEXER_RELAYS,
|
||||
...Router.get().FromUser().getUrls(),
|
||||
...userRoomsByUrl.get().keys(),
|
||||
],
|
||||
relays: [...INDEXER_RELAYS, ...Router.get().FromUser().getUrls(), ...get(userSpaceUrls)],
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -632,7 +622,7 @@ export type JoinRequestParams = {
|
||||
}
|
||||
|
||||
export const makeJoinRequest = (params: JoinRequestParams) =>
|
||||
makeEvent(AUTH_JOIN, {tags: [["claim", params.claim]]})
|
||||
makeEvent(RELAY_JOIN, {tags: [["claim", params.claim]]})
|
||||
|
||||
export const publishJoinRequest = (params: JoinRequestParams) =>
|
||||
publishThunk({event: makeJoinRequest(params), relays: [params.url]})
|
||||
@@ -781,7 +771,7 @@ export const updateProfile = async ({
|
||||
}) => {
|
||||
const router = Router.get()
|
||||
const template = isPublishedProfile(profile) ? editProfile(profile) : createProfile(profile)
|
||||
const scenarios = [router.FromRelays(getMembershipUrls(userMembership.get()))]
|
||||
const scenarios = [router.FromRelays(get(userSpaceUrls))]
|
||||
|
||||
if (shouldBroadcast) {
|
||||
scenarios.push(router.FromUser(), router.Index())
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from "@welshman/lib"
|
||||
import {
|
||||
EVENT_TIME,
|
||||
AUTH_INVITE,
|
||||
RELAY_INVITE,
|
||||
ALERT_EMAIL,
|
||||
ALERT_WEB,
|
||||
ALERT_IOS,
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
import type {TrustedEvent, Filter, List} from "@welshman/util"
|
||||
import {feedFromFilters, makeRelayFeed, makeIntersectionFeed} from "@welshman/feeds"
|
||||
import {load, request} from "@welshman/net"
|
||||
import {repository, makeFeedController, loadRelay} from "@welshman/app"
|
||||
import {repository, makeFeedController, loadRelay, tracker} from "@welshman/app"
|
||||
import {createScroller} from "@lib/html"
|
||||
import {daysBetween} from "@lib/util"
|
||||
import {NOTIFIER_RELAY, getEventsForUrl} from "@app/core/state"
|
||||
@@ -93,7 +93,7 @@ export const makeFeed = ({
|
||||
}
|
||||
|
||||
for (const event of added) {
|
||||
if (matchFilters(filters, event)) {
|
||||
if (matchFilters(filters, event) && tracker.getRelays(event.id).has(url)) {
|
||||
insertEvent(event)
|
||||
}
|
||||
}
|
||||
@@ -267,7 +267,7 @@ export const discoverRelays = (lists: List[]) =>
|
||||
)
|
||||
|
||||
export const requestRelayClaim = async (url: string) => {
|
||||
const filters = [{kinds: [AUTH_INVITE], limit: 1}]
|
||||
const filters = [{kinds: [RELAY_INVITE], limit: 1}]
|
||||
const events = await load({filters, relays: [url]})
|
||||
|
||||
if (events.length > 0) {
|
||||
|
||||
+300
-181
@@ -4,7 +4,9 @@ import {get, derived, writable} from "svelte/store"
|
||||
import * as nip19 from "nostr-tools/nip19"
|
||||
import {
|
||||
on,
|
||||
spec,
|
||||
call,
|
||||
first,
|
||||
assoc,
|
||||
remove,
|
||||
uniqBy,
|
||||
@@ -12,9 +14,7 @@ import {
|
||||
sort,
|
||||
prop,
|
||||
uniq,
|
||||
nth,
|
||||
pushToMapKey,
|
||||
nthEq,
|
||||
shuffle,
|
||||
parseJson,
|
||||
fromPairs,
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
groupBy,
|
||||
always,
|
||||
tryCatch,
|
||||
last,
|
||||
} from "@welshman/lib"
|
||||
import type {Socket} from "@welshman/net"
|
||||
import {
|
||||
@@ -46,49 +45,56 @@ import {
|
||||
} from "@welshman/store"
|
||||
import {isKindFeed, findFeed} from "@welshman/feeds"
|
||||
import {
|
||||
getIdFilters,
|
||||
WRAP,
|
||||
DELETE,
|
||||
ALERT_ANDROID,
|
||||
ALERT_EMAIL,
|
||||
ALERT_IOS,
|
||||
ALERT_STATUS,
|
||||
ALERT_WEB,
|
||||
APP_DATA,
|
||||
CLIENT_AUTH,
|
||||
AUTH_JOIN,
|
||||
REACTION,
|
||||
ZAP_REQUEST,
|
||||
ZAP_RESPONSE,
|
||||
DIRECT_MESSAGE,
|
||||
COMMENT,
|
||||
DELETE,
|
||||
DIRECT_MESSAGE_FILE,
|
||||
ROOM_META,
|
||||
DIRECT_MESSAGE,
|
||||
EVENT_TIME,
|
||||
MESSAGE,
|
||||
REACTION,
|
||||
RELAY_ADD_MEMBER,
|
||||
RELAY_JOIN,
|
||||
RELAY_LEAVE,
|
||||
RELAY_MEMBERS,
|
||||
RELAY_REMOVE_MEMBER,
|
||||
REPORT,
|
||||
ROOM_ADD_MEMBER,
|
||||
ROOM_CREATE_PERMISSION,
|
||||
ROOM_JOIN,
|
||||
ROOM_LEAVE,
|
||||
ROOM_MEMBERS,
|
||||
ROOM_META,
|
||||
ROOM_REMOVE_MEMBER,
|
||||
ROOMS,
|
||||
THREAD,
|
||||
COMMENT,
|
||||
REPORT,
|
||||
ROOM_JOIN,
|
||||
ROOM_ADD_USER,
|
||||
ROOM_REMOVE_USER,
|
||||
ROOM_CREATE_PERMISSION,
|
||||
ALERT_EMAIL,
|
||||
ALERT_WEB,
|
||||
ALERT_IOS,
|
||||
ALERT_ANDROID,
|
||||
ALERT_STATUS,
|
||||
APP_DATA,
|
||||
WRAP,
|
||||
ZAP_GOAL,
|
||||
EVENT_TIME,
|
||||
getGroupTags,
|
||||
getRelayTagValues,
|
||||
getPubkeyTagValues,
|
||||
displayProfile,
|
||||
readList,
|
||||
getListTags,
|
||||
ZAP_REQUEST,
|
||||
ZAP_RESPONSE,
|
||||
asDecryptedEvent,
|
||||
normalizeRelayUrl,
|
||||
displayProfile,
|
||||
getGroupTags,
|
||||
getIdFilters,
|
||||
getListTags,
|
||||
getPubkeyTagValues,
|
||||
getRelaysFromList,
|
||||
getRelayTagValues,
|
||||
getTag,
|
||||
getTagValue,
|
||||
getTagValues,
|
||||
verifyEvent,
|
||||
isRelayUrl,
|
||||
makeEvent,
|
||||
normalizeRelayUrl,
|
||||
readList,
|
||||
RelayMode,
|
||||
getRelaysFromList,
|
||||
verifyEvent,
|
||||
} from "@welshman/util"
|
||||
import type {TrustedEvent, PublishedList, List, Filter} from "@welshman/util"
|
||||
import {decrypt} from "@welshman/signer"
|
||||
@@ -111,6 +117,9 @@ import {
|
||||
publishThunk,
|
||||
userRelaySelections,
|
||||
userInboxRelaySelections,
|
||||
deriveRelay,
|
||||
makeUserData,
|
||||
makeUserLoader,
|
||||
} from "@welshman/app"
|
||||
import type {Thunk, Relay} from "@welshman/app"
|
||||
|
||||
@@ -158,7 +167,7 @@ export const DUFFLEPUD_URL = "https://dufflepud.onrender.com"
|
||||
|
||||
export const NIP46_PERMS =
|
||||
"nip44_encrypt,nip44_decrypt," +
|
||||
[CLIENT_AUTH, AUTH_JOIN, MESSAGE, THREAD, COMMENT, ROOMS, WRAP, REACTION, ZAP_REQUEST]
|
||||
[CLIENT_AUTH, RELAY_JOIN, MESSAGE, THREAD, COMMENT, ROOMS, WRAP, REACTION, ZAP_REQUEST]
|
||||
.map(k => `sign_event:${k}`)
|
||||
.join(",")
|
||||
|
||||
@@ -191,7 +200,7 @@ export const entityLink = (entity: string) => `https://coracle.social/${entity}`
|
||||
export const pubkeyLink = (pubkey: string, relays = Router.get().FromPubkeys([pubkey]).getUrls()) =>
|
||||
entityLink(nip19.nprofileEncode({pubkey, relays}))
|
||||
|
||||
export const defaultPubkeys = derived(userFollows, $userFollows => {
|
||||
export const bootstrapPubkeys = derived(userFollows, $userFollows => {
|
||||
const appPubkeys = DEFAULT_PUBKEYS.split(",")
|
||||
const userPubkeys = shuffle(getPubkeyTagValues(getListTags($userFollows)))
|
||||
|
||||
@@ -221,29 +230,31 @@ export const deriveEvent = (idOrAddress: string, hints: string[] = []) => {
|
||||
)
|
||||
}
|
||||
|
||||
export const getUrlsForEvent = derived([trackerStore, thunks], ([$tracker, $thunks]) => {
|
||||
const getThunksByEventId = memoize(() => {
|
||||
const thunksByEventId = new Map<string, Thunk[]>()
|
||||
export const getUrlsForEvent = withGetter(
|
||||
derived([trackerStore, thunks], ([$tracker, $thunks]) => {
|
||||
const getThunksByEventId = memoize(() => {
|
||||
const thunksByEventId = new Map<string, Thunk[]>()
|
||||
|
||||
for (const thunk of $thunks) {
|
||||
pushToMapKey(thunksByEventId, thunk.event.id, thunk)
|
||||
}
|
||||
|
||||
return thunksByEventId
|
||||
})
|
||||
|
||||
return (id: string) => {
|
||||
const urls = Array.from($tracker.getRelays(id))
|
||||
|
||||
for (const thunk of getThunksByEventId().get(id) || []) {
|
||||
for (const url of thunk.options.relays) {
|
||||
urls.push(url)
|
||||
for (const thunk of $thunks) {
|
||||
pushToMapKey(thunksByEventId, thunk.event.id, thunk)
|
||||
}
|
||||
}
|
||||
|
||||
return uniq(urls)
|
||||
}
|
||||
})
|
||||
return thunksByEventId
|
||||
})
|
||||
|
||||
return (id: string) => {
|
||||
const urls = Array.from($tracker.getRelays(id))
|
||||
|
||||
for (const thunk of getThunksByEventId().get(id) || []) {
|
||||
for (const url of thunk.options.relays) {
|
||||
urls.push(url)
|
||||
}
|
||||
}
|
||||
|
||||
return uniq(urls)
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
export const getEventsForUrl = (url: string, filters: Filter[]) => {
|
||||
const ids = uniq([
|
||||
@@ -266,6 +277,11 @@ export const deriveEventsForUrl = (url: string, filters: Filter[]) =>
|
||||
return repository.query(filters.map(assoc("ids", ids)))
|
||||
})
|
||||
|
||||
export const deriveSignedEventsForUrl = (url: string, filters: Filter[]) =>
|
||||
derived([deriveEventsForUrl(url, filters), deriveRelay(url)], ([$events, $relay]) =>
|
||||
$relay?.profile ? $events.filter(spec({pubkey: $relay.profile.self})) : [],
|
||||
)
|
||||
|
||||
// Context
|
||||
|
||||
appContext.dufflepudUrl = DUFFLEPUD_URL
|
||||
@@ -295,6 +311,15 @@ export const MESSAGE_FILTER = {kinds: MESSAGE_KINDS}
|
||||
|
||||
export const COMMENT_FILTER = makeCommentFilter(MESSAGE_KINDS)
|
||||
|
||||
export const MEMBERSHIP_KINDS = [
|
||||
ROOM_ADD_MEMBER,
|
||||
ROOM_REMOVE_MEMBER,
|
||||
RELAY_ADD_MEMBER,
|
||||
RELAY_REMOVE_MEMBER,
|
||||
]
|
||||
|
||||
export const MEMBERSHIP_FILTER = {kinds: MEMBERSHIP_KINDS}
|
||||
|
||||
// Settings
|
||||
|
||||
export const SETTINGS = "flotilla/settings"
|
||||
@@ -348,6 +373,19 @@ export const {
|
||||
load: makeOutboxLoader(APP_DATA, {"#d": [SETTINGS]}),
|
||||
})
|
||||
|
||||
export const userSettings = makeUserData({
|
||||
mapStore: settingsByPubkey,
|
||||
loadItem: loadSettings,
|
||||
})
|
||||
|
||||
export const loadUserSettings = makeUserLoader(loadSettings)
|
||||
|
||||
export const userSettingsValues = withGetter(
|
||||
derived(userSettings, $s => $s?.values || defaultSettings),
|
||||
)
|
||||
|
||||
export const getSetting = <T>(key: keyof Settings["values"]) => userSettingsValues.get()[key] as T
|
||||
|
||||
// Relays sending events with empty signatures that the user has to choose to trust
|
||||
|
||||
export const relaysPendingTrust = withGetter(writable<string[]>([]))
|
||||
@@ -428,64 +466,6 @@ export const alertStatuses = withGetter(
|
||||
export const deriveAlertStatus = (address: string) =>
|
||||
derived(alertStatuses, statuses => statuses.find(s => getTagValue("d", s.event.tags) === address))
|
||||
|
||||
// Membership
|
||||
|
||||
export const hasMembershipUrl = (list: List | undefined, url: string) =>
|
||||
getListTags(list).some(t => {
|
||||
if (t[0] === "r") return t[1] === url
|
||||
if (t[0] === "group") return t[2] === url
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
export const getMembershipUrls = (list?: List) => {
|
||||
const tags = getListTags(list)
|
||||
|
||||
return sort(
|
||||
uniq([...getRelayTagValues(tags), ...getGroupTags(tags).map(nth(2))]).map(url =>
|
||||
normalizeRelayUrl(url),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
export const getMembershipRooms = (list?: List) =>
|
||||
getGroupTags(getListTags(list)).map(([_, room, url, name = ""]) => ({url, room, name}))
|
||||
|
||||
export const getMembershipRoomsByUrl = (url: string, list?: List) =>
|
||||
sort(getGroupTags(getListTags(list)).filter(nthEq(2, url)).map(nth(1)))
|
||||
|
||||
export const memberships = deriveEventsMapped<PublishedList>(repository, {
|
||||
filters: [{kinds: [ROOMS]}],
|
||||
itemToEvent: item => item.event,
|
||||
eventToItem: (event: TrustedEvent) => readList(asDecryptedEvent(event)),
|
||||
})
|
||||
|
||||
export const {
|
||||
indexStore: membershipsByPubkey,
|
||||
deriveItem: deriveMembership,
|
||||
loadItem: loadMembership,
|
||||
} = collection({
|
||||
name: "memberships",
|
||||
store: memberships,
|
||||
getKey: list => list.event.pubkey,
|
||||
load: makeOutboxLoader(ROOMS),
|
||||
})
|
||||
|
||||
export const membersByUrl = derived(
|
||||
[defaultPubkeys, membershipsByPubkey],
|
||||
([$defaultPubkeys, $membershipsByPubkey]) => {
|
||||
const $membersByUrl = new Map<string, Set<string>>()
|
||||
|
||||
for (const pubkey of $defaultPubkeys) {
|
||||
for (const url of getMembershipUrls($membershipsByPubkey.get(pubkey))) {
|
||||
addToMapKey($membersByUrl, url, pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
return $membersByUrl
|
||||
},
|
||||
)
|
||||
|
||||
// Chats
|
||||
|
||||
export const chatMessages = deriveEvents(repository, {
|
||||
@@ -556,12 +536,10 @@ export const chatSearch = derived(chats, $chats =>
|
||||
}),
|
||||
)
|
||||
|
||||
// Messages
|
||||
// Channels
|
||||
|
||||
export const messages = deriveEvents(repository, {filters: [{kinds: [MESSAGE]}]})
|
||||
|
||||
// Channels
|
||||
|
||||
export type Channel = {
|
||||
id: string
|
||||
url: string
|
||||
@@ -645,98 +623,243 @@ export const displayChannel = (url: string, room: string) =>
|
||||
export const roomComparator = (url: string) => (room: string) =>
|
||||
displayChannel(url, room).toLowerCase()
|
||||
|
||||
// User stuff
|
||||
// User space/room selections
|
||||
|
||||
export const userSettings = withGetter(
|
||||
derived([pubkey, settingsByPubkey], ([$pubkey, $settingsByPubkey]) => {
|
||||
if (!$pubkey) return undefined
|
||||
export const groupSelections = deriveEventsMapped<PublishedList>(repository, {
|
||||
filters: [{kinds: [ROOMS]}],
|
||||
itemToEvent: item => item.event,
|
||||
eventToItem: (event: TrustedEvent) => readList(asDecryptedEvent(event)),
|
||||
})
|
||||
|
||||
loadSettings($pubkey)
|
||||
export const {
|
||||
indexStore: groupSelectionsByPubkey,
|
||||
deriveItem: deriveGroupSelections,
|
||||
loadItem: loadGroupSelections,
|
||||
} = collection({
|
||||
name: "groupSelections",
|
||||
store: groupSelections,
|
||||
getKey: list => list.event.pubkey,
|
||||
load: makeOutboxLoader(ROOMS),
|
||||
})
|
||||
|
||||
return $settingsByPubkey.get($pubkey)
|
||||
}),
|
||||
)
|
||||
export const groupSelectionsPubkeysByUrl = derived(groupSelections, $groupSelections => {
|
||||
const result = new Map<string, Set<string>>()
|
||||
|
||||
export const userSettingsValues = withGetter(
|
||||
derived(userSettings, $s => $s?.values || defaultSettings),
|
||||
)
|
||||
|
||||
export const getSetting = <T>(key: keyof Settings["values"]) => userSettingsValues.get()[key] as T
|
||||
|
||||
export const userMembership = withGetter(
|
||||
derived([pubkey, membershipsByPubkey], ([$pubkey, $membershipsByPubkey]) => {
|
||||
if (!$pubkey) return undefined
|
||||
|
||||
loadMembership($pubkey)
|
||||
|
||||
return $membershipsByPubkey.get($pubkey)
|
||||
}),
|
||||
)
|
||||
|
||||
export const userRoomsByUrl = withGetter(
|
||||
derived([userMembership, channelsById], ([$userMembership, $channelsById]) => {
|
||||
const tags = getListTags($userMembership)
|
||||
const $userRoomsByUrl = new Map<string, Set<string>>()
|
||||
for (const list of $groupSelections) {
|
||||
const tags = getListTags(list)
|
||||
|
||||
for (const url of getRelayTagValues(tags)) {
|
||||
$userRoomsByUrl.set(normalizeRelayUrl(url), new Set())
|
||||
addToMapKey(result, url, list.event.pubkey)
|
||||
}
|
||||
|
||||
for (const [_, room, url] of getGroupTags(tags)) {
|
||||
for (const tag of getGroupTags(tags)) {
|
||||
const url = tag[2] || ""
|
||||
|
||||
if (isRelayUrl(url)) {
|
||||
addToMapKey(result, url, list.event.pubkey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
export const getSpaceUrlsFromGroupSelections = ($groupSelections: List | undefined) => {
|
||||
const tags = getListTags($groupSelections)
|
||||
const urls = getRelayTagValues(tags)
|
||||
|
||||
for (const tag of getGroupTags(tags)) {
|
||||
const url = tag[2] || ""
|
||||
|
||||
if (isRelayUrl(url)) {
|
||||
urls.push(url)
|
||||
}
|
||||
}
|
||||
|
||||
return uniq(urls.map(normalizeRelayUrl))
|
||||
}
|
||||
|
||||
export const getSpaceRoomsFromGroupSelections = (
|
||||
url: string,
|
||||
$groupSelections: List | undefined,
|
||||
) => {
|
||||
const rooms: string[] = []
|
||||
|
||||
for (const [_, room, relay] of getGroupTags(getListTags($groupSelections))) {
|
||||
if (url === relay) {
|
||||
rooms.push(room)
|
||||
}
|
||||
}
|
||||
|
||||
return sortBy(roomComparator(url), rooms)
|
||||
}
|
||||
|
||||
export const userGroupSelections = makeUserData({
|
||||
mapStore: groupSelectionsByPubkey,
|
||||
loadItem: loadGroupSelections,
|
||||
})
|
||||
|
||||
export const loadUserGroupSelections = makeUserLoader(loadGroupSelections)
|
||||
|
||||
export const userSpaceUrls = derived(userGroupSelections, getSpaceUrlsFromGroupSelections)
|
||||
|
||||
export const deriveUserRooms = (url: string) =>
|
||||
derived([userGroupSelections, channelsById], ([$userGroupSelections, $channelsById]) => {
|
||||
const rooms: string[] = []
|
||||
|
||||
for (const room of getSpaceRoomsFromGroupSelections(url, $userGroupSelections)) {
|
||||
if ($channelsById.has(makeChannelId(url, room))) {
|
||||
addToMapKey($userRoomsByUrl, normalizeRelayUrl(url), room)
|
||||
rooms.push(room)
|
||||
}
|
||||
}
|
||||
|
||||
return $userRoomsByUrl
|
||||
}),
|
||||
)
|
||||
|
||||
export const deriveUserRooms = (url: string) =>
|
||||
derived(userRoomsByUrl, $userRoomsByUrl =>
|
||||
sortBy(roomComparator(url), uniq(Array.from($userRoomsByUrl.get(url) || []))),
|
||||
)
|
||||
return sortBy(roomComparator(url), rooms)
|
||||
})
|
||||
|
||||
export const deriveOtherRooms = (url: string) =>
|
||||
derived([deriveUserRooms(url), channelsByUrl], ([$userRooms, $channelsByUrl]) =>
|
||||
sortBy(
|
||||
roomComparator(url),
|
||||
($channelsByUrl.get(url) || []).filter(c => !$userRooms.includes(c.room)).map(c => c.room),
|
||||
),
|
||||
derived([deriveUserRooms(url), channelsByUrl], ([$userRooms, $channelsByUrl]) => {
|
||||
const rooms: string[] = []
|
||||
|
||||
for (const {room} of $channelsByUrl.get(url) || []) {
|
||||
if (!$userRooms.includes(room)) {
|
||||
rooms.push(room)
|
||||
}
|
||||
}
|
||||
|
||||
return sortBy(roomComparator(url), rooms)
|
||||
})
|
||||
|
||||
// Space/room memberships
|
||||
|
||||
export const deriveSpaceMembers = (url: string) =>
|
||||
derived(
|
||||
deriveSignedEventsForUrl(url, [
|
||||
{kinds: [RELAY_ADD_MEMBER, RELAY_REMOVE_MEMBER, RELAY_MEMBERS]},
|
||||
]),
|
||||
$events => {
|
||||
const membersEvent = $events.find(spec({kind: RELAY_MEMBERS}))
|
||||
|
||||
if (membersEvent) {
|
||||
return getTagValues("member", membersEvent.tags)
|
||||
}
|
||||
|
||||
const members = new Set()
|
||||
|
||||
for (const event of $events) {
|
||||
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)
|
||||
},
|
||||
)
|
||||
|
||||
export const deriveRoomMembers = (url: string, room: string) =>
|
||||
derived(
|
||||
deriveEventsForUrl(url, [
|
||||
{kinds: [ROOM_ADD_MEMBER, ROOM_REMOVE_MEMBER, ROOM_MEMBERS], "#h": [room]},
|
||||
]),
|
||||
$events => {
|
||||
const membersEvent = $events.find(spec({kind: ROOM_MEMBERS}))
|
||||
|
||||
if (membersEvent) {
|
||||
return getPubkeyTagValues(membersEvent.tags)
|
||||
}
|
||||
|
||||
const members = new Set()
|
||||
|
||||
for (const event of $events) {
|
||||
const pubkeys = getPubkeyTagValues(event.tags)
|
||||
|
||||
if (event.kind === ROOM_ADD_MEMBER) {
|
||||
for (const pubkey of pubkeys) {
|
||||
members.add(pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
if (event.kind === ROOM_REMOVE_MEMBER) {
|
||||
for (const pubkey of pubkeys) {
|
||||
members.delete(pubkey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(members)
|
||||
},
|
||||
)
|
||||
|
||||
// User membership status
|
||||
|
||||
export enum MembershipStatus {
|
||||
Initial,
|
||||
Pending,
|
||||
Granted,
|
||||
}
|
||||
|
||||
export const deriveUserMembershipStatus = (url: string, room: string) =>
|
||||
export const deriveUserSpaceMembershipStatus = (url: string) =>
|
||||
derived(
|
||||
[
|
||||
pubkey,
|
||||
deriveEventsForUrl(url, [
|
||||
{kinds: [ROOM_JOIN, ROOM_ADD_USER, ROOM_REMOVE_USER], "#h": [room]},
|
||||
]),
|
||||
deriveSpaceMembers(url),
|
||||
deriveEventsForUrl(url, [{kinds: [RELAY_JOIN, RELAY_LEAVE]}]),
|
||||
],
|
||||
([$pubkey, $events]) => {
|
||||
let status = MembershipStatus.Initial
|
||||
([$pubkey, $members, $events]) => {
|
||||
const isMember = $members.includes($pubkey)
|
||||
|
||||
for (const event of $events) {
|
||||
if (event.kind === ROOM_JOIN && event.pubkey === $pubkey) {
|
||||
status = MembershipStatus.Pending
|
||||
if (event.pubkey !== $pubkey) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (event.kind === ROOM_REMOVE_USER && getTagValues("p", event.tags).includes($pubkey!)) {
|
||||
break
|
||||
if (event.kind === RELAY_JOIN) {
|
||||
return isMember ? MembershipStatus.Granted : MembershipStatus.Pending
|
||||
}
|
||||
|
||||
if (event.kind === ROOM_ADD_USER && getTagValues("p", event.tags).includes($pubkey!)) {
|
||||
return MembershipStatus.Granted
|
||||
if (event.kind === RELAY_LEAVE) {
|
||||
return MembershipStatus.Initial
|
||||
}
|
||||
}
|
||||
|
||||
return status
|
||||
return isMember ? MembershipStatus.Granted : MembershipStatus.Initial
|
||||
},
|
||||
)
|
||||
|
||||
export const deriveUserRoomMembershipStatus = (url: string, room: string) =>
|
||||
derived(
|
||||
[
|
||||
pubkey,
|
||||
deriveRoomMembers(url, room),
|
||||
deriveEventsForUrl(url, [{kinds: [ROOM_JOIN, ROOM_LEAVE], "#h": [room]}]),
|
||||
],
|
||||
([$pubkey, $members, $events]) => {
|
||||
const isMember = $members.includes($pubkey)
|
||||
|
||||
for (const event of $events) {
|
||||
if (event.pubkey !== $pubkey) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (event.kind === ROOM_JOIN) {
|
||||
return isMember ? MembershipStatus.Granted : MembershipStatus.Pending
|
||||
}
|
||||
|
||||
if (event.kind === ROOM_LEAVE) {
|
||||
return MembershipStatus.Initial
|
||||
}
|
||||
}
|
||||
|
||||
return isMember ? MembershipStatus.Granted : MembershipStatus.Initial
|
||||
},
|
||||
)
|
||||
|
||||
@@ -744,13 +867,9 @@ export const deriveUserCanCreateRoom = (url: string) =>
|
||||
derived(
|
||||
[pubkey, deriveEventsForUrl(url, [{kinds: [ROOM_CREATE_PERMISSION]}])],
|
||||
([$pubkey, $events]) => {
|
||||
const latest = last($events)
|
||||
const event = first($events)
|
||||
|
||||
if (!latest) {
|
||||
return true
|
||||
}
|
||||
|
||||
return getTagValues("p", latest.tags).includes($pubkey!)
|
||||
return event ? getPubkeyTagValues(event.tags).includes($pubkey!) : true
|
||||
},
|
||||
)
|
||||
|
||||
@@ -853,7 +972,7 @@ export const deriveRelayAuthError = (url: string, claim = "") => {
|
||||
|
||||
// Attempt to join the relay
|
||||
const thunk = publishThunk({
|
||||
event: makeEvent(AUTH_JOIN, {tags: [["claim", claim]]}),
|
||||
event: makeEvent(RELAY_JOIN, {tags: [["claim", claim]]}),
|
||||
relays: [url],
|
||||
})
|
||||
|
||||
|
||||
+18
-20
@@ -19,10 +19,9 @@ import {
|
||||
getRelayTagValues,
|
||||
WRAP,
|
||||
ROOM_META,
|
||||
ROOM_ADD_USER,
|
||||
ROOM_REMOVE_USER,
|
||||
ROOM_ADD_MEMBER,
|
||||
ROOM_REMOVE_MEMBER,
|
||||
isSignedEvent,
|
||||
normalizeRelayUrl,
|
||||
} from "@welshman/util"
|
||||
import type {Filter, TrustedEvent} from "@welshman/util"
|
||||
import {request, load, pull} from "@welshman/net"
|
||||
@@ -45,13 +44,14 @@ import {
|
||||
import {
|
||||
MESSAGE_FILTER,
|
||||
COMMENT_FILTER,
|
||||
MEMBERSHIP_FILTER,
|
||||
INDEXER_RELAYS,
|
||||
REACTION_KINDS,
|
||||
loadSettings,
|
||||
userMembership,
|
||||
defaultPubkeys,
|
||||
loadGroupSelections,
|
||||
userSpaceUrls,
|
||||
bootstrapPubkeys,
|
||||
decodeRelay,
|
||||
loadMembership,
|
||||
getUrlsForEvent,
|
||||
} from "@app/core/state"
|
||||
import {loadAlerts, loadAlertStatuses} from "@app/core/requests"
|
||||
@@ -102,15 +102,15 @@ const syncRelays = () => {
|
||||
}
|
||||
})
|
||||
|
||||
const unsubscribeMembership = userMembership.subscribe($l => {
|
||||
for (const url of getRelayTagValues(getListTags($l))) {
|
||||
const unsubscribeSpaceUrls = userSpaceUrls.subscribe(urls => {
|
||||
for (const url of urls) {
|
||||
loadRelay(url)
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
unsubscribePage()
|
||||
unsubscribeMembership()
|
||||
unsubscribeSpaceUrls()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ const syncUserData = () => {
|
||||
loadAlertStatuses($pubkey)
|
||||
loadBlossomServers($pubkey)
|
||||
loadFollows($pubkey)
|
||||
loadMembership($pubkey)
|
||||
loadGroupSelections($pubkey)
|
||||
loadMutes($pubkey)
|
||||
loadProfile($pubkey)
|
||||
loadSettings($pubkey)
|
||||
@@ -139,13 +139,13 @@ const syncUserData = () => {
|
||||
})
|
||||
|
||||
const unsubscribeFollows = userFollows.subscribe(async $l => {
|
||||
for (const pubkeys of chunk(10, get(defaultPubkeys))) {
|
||||
for (const pubkeys of chunk(10, get(bootstrapPubkeys))) {
|
||||
// This isn't urgent, avoid clogging other stuff up
|
||||
await sleep(1000)
|
||||
|
||||
for (const pk of pubkeys) {
|
||||
loadRelaySelections(pk).then(() => {
|
||||
loadMembership(pk)
|
||||
loadGroupSelections(pk)
|
||||
loadProfile(pk)
|
||||
loadFollows(pk)
|
||||
loadMutes(pk)
|
||||
@@ -177,14 +177,14 @@ const syncMembership = (url: string) => {
|
||||
pullConservatively({
|
||||
relays: [url],
|
||||
signal: controller.signal,
|
||||
filters: [MESSAGE_FILTER, COMMENT_FILTER].map(assoc("since", ago(MONTH))),
|
||||
filters: [MESSAGE_FILTER, COMMENT_FILTER, MEMBERSHIP_FILTER].map(assoc("since", ago(MONTH))),
|
||||
})
|
||||
|
||||
// Listen for new events
|
||||
request({
|
||||
relays: [url],
|
||||
signal: controller.signal,
|
||||
filters: [MESSAGE_FILTER, COMMENT_FILTER].map(assoc("since", now())),
|
||||
filters: [MESSAGE_FILTER, COMMENT_FILTER, MEMBERSHIP_FILTER].map(assoc("since", now())),
|
||||
})
|
||||
|
||||
return () => controller.abort()
|
||||
@@ -193,9 +193,7 @@ const syncMembership = (url: string) => {
|
||||
const syncMemberships = () => {
|
||||
const unsubscribersByUrl = new Map<string, Unsubscriber>()
|
||||
|
||||
const unsubscribeMembership = userMembership.subscribe($l => {
|
||||
const urls = getRelayTagValues(getListTags($l)).map(normalizeRelayUrl)
|
||||
|
||||
const unsubscribeSpaceUrls = userSpaceUrls.subscribe(urls => {
|
||||
// stop syncing removed spaces
|
||||
for (const [url, unsubscribe] of unsubscribersByUrl.entries()) {
|
||||
if (!urls.includes(url)) {
|
||||
@@ -214,7 +212,7 @@ const syncMemberships = () => {
|
||||
|
||||
return () => {
|
||||
Array.from(unsubscribersByUrl.values()).forEach(call)
|
||||
unsubscribeMembership()
|
||||
unsubscribeSpaceUrls()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +229,7 @@ const syncSpace = (url: string) => {
|
||||
signal: controller.signal,
|
||||
filters: [
|
||||
{
|
||||
kinds: [ROOM_ADD_USER, ROOM_REMOVE_USER],
|
||||
kinds: [ROOM_ADD_MEMBER, ROOM_REMOVE_MEMBER],
|
||||
"#p": [$pubkey],
|
||||
},
|
||||
],
|
||||
@@ -244,7 +242,7 @@ const syncSpace = (url: string) => {
|
||||
signal: controller.signal,
|
||||
filters: [
|
||||
{
|
||||
kinds: [ROOM_ADD_USER, ROOM_REMOVE_USER, ...REACTION_KINDS],
|
||||
kinds: [ROOM_ADD_MEMBER, ROOM_REMOVE_MEMBER, ...REACTION_KINDS],
|
||||
since: now(),
|
||||
},
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user