mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-09-19 03:51:34 +02:00
setup basic pubkey relay assignment service
This commit is contained in:
@@ -87,6 +87,11 @@ export class Subject<Value> implements Connectable<Value> {
|
||||
}
|
||||
return this;
|
||||
}
|
||||
disconnectAll() {
|
||||
for (const [connectable, listener] of this.upstream) {
|
||||
this.disconnect(connectable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class PersistentSubject<Value> extends Subject<Value> implements ConnectableApi<Value> {
|
||||
|
@@ -1,22 +1,17 @@
|
||||
import { useMemo } from "react";
|
||||
import { RelayConfig } from "../classes/relay";
|
||||
import { normalizeRelayConfigs } from "../helpers/relay";
|
||||
import { useUserContacts } from "./use-user-contacts";
|
||||
import { useUserRelays } from "./use-user-relays";
|
||||
import userRelaysFallbackService from "../services/user-relays-fallback";
|
||||
import { useReadRelayUrls } from "./use-client-relays";
|
||||
import useSubject from "./use-subject";
|
||||
|
||||
export default function useFallbackUserRelays(pubkey: string, alwaysFetch = false) {
|
||||
const contacts = useUserContacts(pubkey, [], alwaysFetch);
|
||||
const userRelays = useUserRelays(pubkey, [], alwaysFetch);
|
||||
export default function useFallbackUserRelays(pubkey: string, additionalRelays: string[] = [], alwaysFetch = false) {
|
||||
const readRelays = useReadRelayUrls(additionalRelays);
|
||||
|
||||
return useMemo(() => {
|
||||
let relays: RelayConfig[] = userRelays?.relays ?? [];
|
||||
const observable = useMemo(
|
||||
() => userRelaysFallbackService.requestRelays(pubkey, readRelays, alwaysFetch),
|
||||
[pubkey, readRelays.join("|"), alwaysFetch]
|
||||
);
|
||||
const userRelays = useSubject(observable);
|
||||
|
||||
// use the relays stored in contacts if there are no relay config
|
||||
if (relays.length === 0 && contacts) {
|
||||
relays = contacts.relays;
|
||||
}
|
||||
|
||||
// normalize relay urls and remove bad ones
|
||||
return normalizeRelayConfigs(relays);
|
||||
}, [userRelays, contacts]);
|
||||
return userRelays ? normalizeRelayConfigs(userRelays.relays) : [];
|
||||
}
|
||||
|
115
src/services/pubkey-relay-assignment.ts
Normal file
115
src/services/pubkey-relay-assignment.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { RelayMode } from "../classes/relay";
|
||||
import Subject, { PersistentSubject } from "../classes/subject";
|
||||
import { SuperMap } from "../classes/super-map";
|
||||
import { unique } from "../helpers/array";
|
||||
import accountService from "./account";
|
||||
import clientRelaysService from "./client-relays";
|
||||
import relayScoreboardService from "./relay-scoreboard";
|
||||
import userContactsService, { UserContacts } from "./user-contacts";
|
||||
import { UserRelays } from "./user-relays";
|
||||
import userRelaysFallbackService from "./user-relays-fallback";
|
||||
|
||||
type pubkey = string;
|
||||
type relay = string;
|
||||
|
||||
class PubkeyRelayAssignmentService {
|
||||
pubkeys = new Map<pubkey, relay[]>();
|
||||
pubkeyRelays = new SuperMap<string, Subject<UserRelays>>(() => new Subject());
|
||||
assignments = new PersistentSubject<Record<pubkey, relay[]>>({});
|
||||
|
||||
constructor() {
|
||||
let sub: Subject<UserContacts>;
|
||||
|
||||
accountService.current.subscribe((account) => {
|
||||
if (sub) {
|
||||
sub.unsubscribe(this.handleUserContacts, this);
|
||||
}
|
||||
if (account) {
|
||||
this.pubkeys.clear();
|
||||
this.pubkeyRelays.clear();
|
||||
const contactsSub = userContactsService.requestContacts(account.pubkey, account.relays ?? []);
|
||||
contactsSub.subscribe(this.handleUserContacts, this);
|
||||
sub = contactsSub;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private handleUserContacts(contacts: UserContacts) {
|
||||
for (const pubkey of contacts.contacts) {
|
||||
const relay = contacts.contactRelay[pubkey];
|
||||
pubkeyRelayAssignmentService.addPubkey(pubkey, relay ? [relay] : []);
|
||||
}
|
||||
}
|
||||
|
||||
addPubkey(pubkey: string, relays: string[] = []) {
|
||||
if (this.pubkeys.has(pubkey)) return;
|
||||
this.pubkeys.set(pubkey, relays);
|
||||
|
||||
const readRelays = clientRelaysService.getReadUrls();
|
||||
const subject = userRelaysFallbackService.requestRelays(pubkey, unique([...readRelays, ...relays]));
|
||||
this.pubkeyRelays.set(pubkey, subject);
|
||||
// subject.subscribe(this.updateAssignments, this);
|
||||
}
|
||||
removePubkey(pubkey: string) {
|
||||
if (!this.pubkeys.has(pubkey)) return;
|
||||
|
||||
this.pubkeys.delete(pubkey);
|
||||
this.pubkeyRelays.delete(pubkey);
|
||||
}
|
||||
|
||||
updateAssignments() {
|
||||
const allRelays = new Set<relay>();
|
||||
|
||||
for (const [pubkey, userRelays] of this.pubkeyRelays) {
|
||||
if (!userRelays.value) continue;
|
||||
for (const relayConfig of userRelays.value.relays) {
|
||||
// only use relays the users are writing to
|
||||
if (relayConfig.mode & RelayMode.WRITE) {
|
||||
allRelays.add(relayConfig.url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const relayScores = new Map<relay, number>();
|
||||
for (const relay of allRelays) {
|
||||
relayScores.set(relay, relayScoreboardService.getRelayScore(relay));
|
||||
}
|
||||
|
||||
const readRelays = clientRelaysService.getReadUrls();
|
||||
const assignments: Record<pubkey, relay[]> = {};
|
||||
for (const [pubkey] of this.pubkeys) {
|
||||
let userRelays =
|
||||
this.pubkeyRelays
|
||||
.get(pubkey)
|
||||
.value?.relays.filter((r) => r.mode & RelayMode.WRITE)
|
||||
.map((r) => r.url) ?? [];
|
||||
|
||||
if (userRelays.length === 0) userRelays = Array.from(readRelays);
|
||||
|
||||
const rankedOptions = Array.from(userRelays).sort(
|
||||
(a, b) => (relayScores.get(b) ?? 0) - (relayScores.get(a) ?? 0)
|
||||
);
|
||||
|
||||
assignments[pubkey] = rankedOptions.slice(0, 3);
|
||||
|
||||
for (const relay of assignments[pubkey]) {
|
||||
relayScores.set(relay, (relayScores.get(relay) ?? 0) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
this.assignments.next(assignments);
|
||||
}
|
||||
}
|
||||
|
||||
const pubkeyRelayAssignmentService = new PubkeyRelayAssignmentService();
|
||||
|
||||
setInterval(() => {
|
||||
pubkeyRelayAssignmentService.updateAssignments();
|
||||
}, 1000 * 5);
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
//@ts-ignore
|
||||
window.pubkeyRelayAssignmentService = pubkeyRelayAssignmentService;
|
||||
}
|
||||
|
||||
export default pubkeyRelayAssignmentService;
|
@@ -5,6 +5,7 @@ import { CachedPubkeyEventRequester } from "../classes/cached-pubkey-event-reque
|
||||
import { SuperMap } from "../classes/super-map";
|
||||
import Subject from "../classes/subject";
|
||||
import { RelayConfig, RelayMode } from "../classes/relay";
|
||||
import { normalizeRelayConfigs } from "../helpers/relay";
|
||||
|
||||
export type UserContacts = {
|
||||
pubkey: string;
|
||||
@@ -15,7 +16,7 @@ export type UserContacts = {
|
||||
};
|
||||
|
||||
type RelayJson = Record<string, { read: boolean; write: boolean }>;
|
||||
function relayJsonToRelayConfig(relayJson: RelayJson) {
|
||||
function relayJsonToRelayConfig(relayJson: RelayJson): RelayConfig[] {
|
||||
try {
|
||||
return Array.from(Object.entries(relayJson)).map(([url, opts]) => ({
|
||||
url,
|
||||
@@ -27,7 +28,7 @@ function relayJsonToRelayConfig(relayJson: RelayJson) {
|
||||
|
||||
function parseContacts(event: NostrEvent): UserContacts {
|
||||
const relayJson = safeJson(event.content, {}) as RelayJson;
|
||||
const relays = relayJsonToRelayConfig(relayJson);
|
||||
const relays = normalizeRelayConfigs(relayJsonToRelayConfig(relayJson));
|
||||
|
||||
const pubkeys = event.tags.filter(isPTag).map((tag) => tag[1]);
|
||||
const contactRelay = event.tags.filter(isPTag).reduce((dir, tag) => {
|
||||
|
@@ -21,6 +21,9 @@ class UserMetadataService {
|
||||
}
|
||||
|
||||
private parsedSubjects = new SuperMap<string, Subject<Kind0ParsedContent>>(() => new Subject<Kind0ParsedContent>());
|
||||
getSubject(pubkey: string) {
|
||||
return this.parsedSubjects.get(pubkey);
|
||||
}
|
||||
requestMetadata(pubkey: string, relays: string[], alwaysRequest = false) {
|
||||
const sub = this.parsedSubjects.get(pubkey);
|
||||
const requestSub = this.requester.requestEvent(pubkey, relays, alwaysRequest);
|
||||
|
41
src/services/user-relays-fallback.ts
Normal file
41
src/services/user-relays-fallback.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import Subject from "../classes/subject";
|
||||
import { normalizeRelayConfigs } from "../helpers/relay";
|
||||
import userContactsService from "./user-contacts";
|
||||
import userRelaysService, { UserRelays } from "./user-relays";
|
||||
|
||||
class UserRelaysFallbackService {
|
||||
subjects = new Map<string, Subject<UserRelays>>();
|
||||
|
||||
requestRelays(pubkey: string, relays: string[], alwaysFetch = false) {
|
||||
let subject = this.subjects.get(pubkey);
|
||||
if (!subject) {
|
||||
subject = new Subject();
|
||||
this.subjects.set(pubkey, subject);
|
||||
|
||||
subject.connectWithHandler(userRelaysService.getSubject(pubkey), (userRelays, next, value) => {
|
||||
if (!value || userRelays.created_at > value.created_at) {
|
||||
next(userRelays);
|
||||
}
|
||||
});
|
||||
subject.connectWithHandler(userContactsService.getSubject(pubkey), (contacts, next, value) => {
|
||||
if (!value || contacts.created_at > value.created_at) {
|
||||
next({ pubkey: contacts.pubkey, relays: contacts.relays, created_at: contacts.created_at });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
userRelaysService.requestRelays(pubkey, relays, alwaysFetch);
|
||||
userContactsService.requestContacts(pubkey, relays, alwaysFetch);
|
||||
|
||||
return subject;
|
||||
}
|
||||
}
|
||||
|
||||
const userRelaysFallbackService = new UserRelaysFallbackService();
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
// @ts-ignore
|
||||
window.userRelaysFallbackService = userRelaysFallbackService;
|
||||
}
|
||||
|
||||
export default userRelaysFallbackService;
|
@@ -5,6 +5,7 @@ import { parseRTag } from "../helpers/nostr-event";
|
||||
import { CachedPubkeyEventRequester } from "../classes/cached-pubkey-event-requester";
|
||||
import { SuperMap } from "../classes/super-map";
|
||||
import Subject from "../classes/subject";
|
||||
import { normalizeRelayConfigs } from "../helpers/relay";
|
||||
|
||||
export type UserRelays = {
|
||||
pubkey: string;
|
||||
@@ -15,7 +16,7 @@ export type UserRelays = {
|
||||
function parseRelaysEvent(event: NostrEvent): UserRelays {
|
||||
return {
|
||||
pubkey: event.pubkey,
|
||||
relays: event.tags.filter(isRTag).map(parseRTag),
|
||||
relays: normalizeRelayConfigs(event.tags.filter(isRTag).map(parseRTag)),
|
||||
created_at: event.created_at,
|
||||
};
|
||||
}
|
||||
@@ -36,6 +37,9 @@ class UserRelaysService {
|
||||
}
|
||||
|
||||
private subjects = new SuperMap<string, Subject<UserRelays>>(() => new Subject<UserRelays>());
|
||||
getSubject(pubkey: string) {
|
||||
return this.subjects.get(pubkey);
|
||||
}
|
||||
requestRelays(pubkey: string, relays: string[], alwaysRequest = false) {
|
||||
const sub = this.subjects.get(pubkey);
|
||||
const requestSub = this.requester.requestEvent(pubkey, relays, alwaysRequest);
|
||||
|
Reference in New Issue
Block a user