feat: conversations (#277)

This commit is contained in:
Cody Tseng
2025-04-15 22:33:53 +08:00
committed by GitHub
parent 1c3e54c895
commit 3c81a13acb
25 changed files with 417 additions and 350 deletions

View File

@@ -3,7 +3,6 @@ import { getProfileFromProfileEvent, getRelayListFromRelayListEvent } from '@/li
import { formatPubkey, userIdToPubkey } from '@/lib/pubkey'
import { extractPubkeysFromEventTags } from '@/lib/tag'
import { isLocalNetworkUrl, isWebsocketUrl, normalizeUrl } from '@/lib/url'
import { isSafari } from '@/lib/utils'
import { ISigner, TProfile, TRelayList } from '@/types'
import { sha256 } from '@noble/hashes/sha2'
import DataLoader from 'dataloader'
@@ -157,9 +156,17 @@ class ClientService extends EventTarget {
return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
}
private generateMultipleTimelinesKey(subRequests: { urls: string[]; filter: Filter }[]) {
const keys = subRequests.map(({ urls, filter }) => this.generateTimelineKey(urls, filter))
const encoder = new TextEncoder()
const data = encoder.encode(JSON.stringify(keys.sort()))
const hashBuffer = sha256(data)
const hashArray = Array.from(new Uint8Array(hashBuffer))
return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
}
async subscribeTimeline(
urls: string[],
{ authors, ...filter }: Omit<Filter, 'since' | 'until'> & { limit: number },
subRequests: { urls: string[]; filter: Omit<Filter, 'since' | 'until'> & { limit: number } }[],
{
onEvents,
onNew
@@ -175,65 +182,6 @@ class ClientService extends EventTarget {
needSort?: boolean
} = {}
) {
if (urls.length || !authors?.length) {
return this._subscribeTimeline(
urls.length ? urls : BIG_RELAY_URLS,
filter,
{ onEvents, onNew },
{ startLogin, needSort }
)
}
const subRequests: { urls: string[]; authors: string[] }[] = []
// If many websocket connections are initiated simultaneously, it will be
// very slow on Safari (for unknown reason)
if (authors.length > 5 && isSafari()) {
const pubkey = await this.signer?.getPublicKey()
if (!pubkey) {
subRequests.push({ urls: BIG_RELAY_URLS, authors })
} else {
const relayList = await this.fetchRelayList(pubkey)
const urls = relayList.read.concat(BIG_RELAY_URLS).slice(0, 5)
subRequests.push({ urls, authors })
}
} else {
const relayLists = await this.fetchRelayLists(authors)
const group: Record<string, Set<string>> = {}
relayLists.forEach((relayList, index) => {
relayList.write.slice(0, 4).forEach((url) => {
if (!group[url]) {
group[url] = new Set()
}
group[url].add(authors[index])
})
})
const relayCount = Object.keys(group).length
const coveredCount = new Map<string, number>()
Object.entries(group)
.sort(([, a], [, b]) => b.size - a.size)
.forEach(([url, pubkeys]) => {
if (
relayCount > 10 &&
pubkeys.size < 10 &&
Array.from(pubkeys).every((pubkey) => (coveredCount.get(pubkey) ?? 0) >= 2)
) {
delete group[url]
} else {
pubkeys.forEach((pubkey) => {
coveredCount.set(pubkey, (coveredCount.get(pubkey) ?? 0) + 1)
})
}
})
subRequests.push(
...Object.entries(group).map(([url, authors]) => ({
urls: [url],
authors: Array.from(authors)
}))
)
}
const newEventIdSet = new Set<string>()
const requestCount = subRequests.length
let eventIdSet = new Set<string>()
@@ -241,10 +189,10 @@ class ClientService extends EventTarget {
let eosedCount = 0
const subs = await Promise.all(
subRequests.map(({ urls, authors }) => {
subRequests.map(({ urls, filter }) => {
return this._subscribeTimeline(
urls,
{ ...filter, authors },
filter,
{
onEvents: (_events, _eosed) => {
if (_eosed) {
@@ -265,12 +213,12 @@ class ClientService extends EventTarget {
onNew(evt)
}
},
{ startLogin }
{ startLogin, needSort }
)
})
)
const key = this.generateTimelineKey([], { ...filter, authors })
const key = this.generateMultipleTimelinesKey(subRequests)
this.timelines[key] = subs.map((sub) => sub.timelineKey)
return {