mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-09-20 13:01:07 +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;
|
return this;
|
||||||
}
|
}
|
||||||
|
disconnectAll() {
|
||||||
|
for (const [connectable, listener] of this.upstream) {
|
||||||
|
this.disconnect(connectable);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PersistentSubject<Value> extends Subject<Value> implements ConnectableApi<Value> {
|
export class PersistentSubject<Value> extends Subject<Value> implements ConnectableApi<Value> {
|
||||||
|
@@ -1,22 +1,17 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { RelayConfig } from "../classes/relay";
|
|
||||||
import { normalizeRelayConfigs } from "../helpers/relay";
|
import { normalizeRelayConfigs } from "../helpers/relay";
|
||||||
import { useUserContacts } from "./use-user-contacts";
|
import userRelaysFallbackService from "../services/user-relays-fallback";
|
||||||
import { useUserRelays } from "./use-user-relays";
|
import { useReadRelayUrls } from "./use-client-relays";
|
||||||
|
import useSubject from "./use-subject";
|
||||||
|
|
||||||
export default function useFallbackUserRelays(pubkey: string, alwaysFetch = false) {
|
export default function useFallbackUserRelays(pubkey: string, additionalRelays: string[] = [], alwaysFetch = false) {
|
||||||
const contacts = useUserContacts(pubkey, [], alwaysFetch);
|
const readRelays = useReadRelayUrls(additionalRelays);
|
||||||
const userRelays = useUserRelays(pubkey, [], alwaysFetch);
|
|
||||||
|
|
||||||
return useMemo(() => {
|
const observable = useMemo(
|
||||||
let relays: RelayConfig[] = userRelays?.relays ?? [];
|
() => 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
|
return userRelays ? normalizeRelayConfigs(userRelays.relays) : [];
|
||||||
if (relays.length === 0 && contacts) {
|
|
||||||
relays = contacts.relays;
|
|
||||||
}
|
|
||||||
|
|
||||||
// normalize relay urls and remove bad ones
|
|
||||||
return normalizeRelayConfigs(relays);
|
|
||||||
}, [userRelays, contacts]);
|
|
||||||
}
|
}
|
||||||
|
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 { SuperMap } from "../classes/super-map";
|
||||||
import Subject from "../classes/subject";
|
import Subject from "../classes/subject";
|
||||||
import { RelayConfig, RelayMode } from "../classes/relay";
|
import { RelayConfig, RelayMode } from "../classes/relay";
|
||||||
|
import { normalizeRelayConfigs } from "../helpers/relay";
|
||||||
|
|
||||||
export type UserContacts = {
|
export type UserContacts = {
|
||||||
pubkey: string;
|
pubkey: string;
|
||||||
@@ -15,7 +16,7 @@ export type UserContacts = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type RelayJson = Record<string, { read: boolean; write: boolean }>;
|
type RelayJson = Record<string, { read: boolean; write: boolean }>;
|
||||||
function relayJsonToRelayConfig(relayJson: RelayJson) {
|
function relayJsonToRelayConfig(relayJson: RelayJson): RelayConfig[] {
|
||||||
try {
|
try {
|
||||||
return Array.from(Object.entries(relayJson)).map(([url, opts]) => ({
|
return Array.from(Object.entries(relayJson)).map(([url, opts]) => ({
|
||||||
url,
|
url,
|
||||||
@@ -27,7 +28,7 @@ function relayJsonToRelayConfig(relayJson: RelayJson) {
|
|||||||
|
|
||||||
function parseContacts(event: NostrEvent): UserContacts {
|
function parseContacts(event: NostrEvent): UserContacts {
|
||||||
const relayJson = safeJson(event.content, {}) as RelayJson;
|
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 pubkeys = event.tags.filter(isPTag).map((tag) => tag[1]);
|
||||||
const contactRelay = event.tags.filter(isPTag).reduce((dir, tag) => {
|
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>());
|
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) {
|
requestMetadata(pubkey: string, relays: string[], alwaysRequest = false) {
|
||||||
const sub = this.parsedSubjects.get(pubkey);
|
const sub = this.parsedSubjects.get(pubkey);
|
||||||
const requestSub = this.requester.requestEvent(pubkey, relays, alwaysRequest);
|
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 { CachedPubkeyEventRequester } from "../classes/cached-pubkey-event-requester";
|
||||||
import { SuperMap } from "../classes/super-map";
|
import { SuperMap } from "../classes/super-map";
|
||||||
import Subject from "../classes/subject";
|
import Subject from "../classes/subject";
|
||||||
|
import { normalizeRelayConfigs } from "../helpers/relay";
|
||||||
|
|
||||||
export type UserRelays = {
|
export type UserRelays = {
|
||||||
pubkey: string;
|
pubkey: string;
|
||||||
@@ -15,7 +16,7 @@ export type UserRelays = {
|
|||||||
function parseRelaysEvent(event: NostrEvent): UserRelays {
|
function parseRelaysEvent(event: NostrEvent): UserRelays {
|
||||||
return {
|
return {
|
||||||
pubkey: event.pubkey,
|
pubkey: event.pubkey,
|
||||||
relays: event.tags.filter(isRTag).map(parseRTag),
|
relays: normalizeRelayConfigs(event.tags.filter(isRTag).map(parseRTag)),
|
||||||
created_at: event.created_at,
|
created_at: event.created_at,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -36,6 +37,9 @@ class UserRelaysService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private subjects = new SuperMap<string, Subject<UserRelays>>(() => new Subject<UserRelays>());
|
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) {
|
requestRelays(pubkey: string, relays: string[], alwaysRequest = false) {
|
||||||
const sub = this.subjects.get(pubkey);
|
const sub = this.subjects.get(pubkey);
|
||||||
const requestSub = this.requester.requestEvent(pubkey, relays, alwaysRequest);
|
const requestSub = this.requester.requestEvent(pubkey, relays, alwaysRequest);
|
||||||
|
Reference in New Issue
Block a user