mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-18 05:41:44 +01:00
cleanup relay class
This commit is contained in:
parent
8f226f822a
commit
ef9de96f3f
118
src/classes/batch-kind-loader.ts
Normal file
118
src/classes/batch-kind-loader.ts
Normal file
@ -0,0 +1,118 @@
|
||||
import dayjs from "dayjs";
|
||||
import { Filter, NostrEvent } from "nostr-tools";
|
||||
import _throttle from "lodash.throttle";
|
||||
import debug, { Debugger } from "debug";
|
||||
|
||||
import NostrSubscription from "./nostr-subscription";
|
||||
import EventStore from "./event-store";
|
||||
import { getEventCoordinate } from "../helpers/nostr/events";
|
||||
|
||||
export function createCoordinate(kind: number, pubkey: string, d?: string) {
|
||||
return `${kind}:${pubkey}${d ? ":" + d : ""}`;
|
||||
}
|
||||
|
||||
const RELAY_REQUEST_BATCH_TIME = 500;
|
||||
|
||||
/** This class is ued to batch requests by kind to a single relay */
|
||||
export default class BatchKindLoader {
|
||||
private subscription: NostrSubscription;
|
||||
events = new EventStore();
|
||||
|
||||
private requestNext = new Set<string>();
|
||||
private requested = new Map<string, Date>();
|
||||
|
||||
log: Debugger;
|
||||
|
||||
constructor(relay: string, log?: Debugger) {
|
||||
this.subscription = new NostrSubscription(relay, undefined, `replaceable-event-loader`);
|
||||
|
||||
this.subscription.onEvent.subscribe(this.handleEvent.bind(this));
|
||||
this.subscription.onEOSE.subscribe(this.handleEOSE.bind(this));
|
||||
|
||||
this.log = log || debug("RelayBatchLoader");
|
||||
}
|
||||
|
||||
private handleEvent(event: NostrEvent) {
|
||||
const key = getEventCoordinate(event);
|
||||
|
||||
// remove the key from the waiting list
|
||||
this.requested.delete(key);
|
||||
|
||||
const current = this.events.getEvent(key);
|
||||
if (!current || event.created_at > current.created_at) {
|
||||
this.events.addEvent(event);
|
||||
}
|
||||
}
|
||||
private handleEOSE() {
|
||||
// relays says it has nothing left
|
||||
this.requested.clear();
|
||||
}
|
||||
|
||||
requestEvent(kind: number, pubkey: string, d?: string) {
|
||||
const key = createCoordinate(kind, pubkey, d);
|
||||
const event = this.events.getEvent(key);
|
||||
if (!event) {
|
||||
this.requestNext.add(key);
|
||||
this.updateThrottle();
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
updateThrottle = _throttle(this.update, RELAY_REQUEST_BATCH_TIME);
|
||||
update() {
|
||||
let needsUpdate = false;
|
||||
for (const key of this.requestNext) {
|
||||
if (!this.requested.has(key)) {
|
||||
this.requested.set(key, new Date());
|
||||
needsUpdate = true;
|
||||
}
|
||||
}
|
||||
this.requestNext.clear();
|
||||
|
||||
// prune requests
|
||||
const timeout = dayjs().subtract(1, "minute");
|
||||
for (const [key, date] of this.requested) {
|
||||
if (dayjs(date).isBefore(timeout)) {
|
||||
this.requested.delete(key);
|
||||
needsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
// update the subscription
|
||||
if (needsUpdate) {
|
||||
if (this.requested.size > 0) {
|
||||
const filters: Record<number, Filter> = {};
|
||||
|
||||
for (const [cord] of this.requested) {
|
||||
const [kindStr, pubkey, d] = cord.split(":") as [string, string] | [string, string, string];
|
||||
const kind = parseInt(kindStr);
|
||||
filters[kind] = filters[kind] || { kinds: [kind] };
|
||||
|
||||
const arr = (filters[kind].authors = filters[kind].authors || []);
|
||||
arr.push(pubkey);
|
||||
|
||||
if (d) {
|
||||
const arr = (filters[kind]["#d"] = filters[kind]["#d"] || []);
|
||||
arr.push(d);
|
||||
}
|
||||
}
|
||||
|
||||
const query = Array.from(Object.values(filters));
|
||||
|
||||
this.log(
|
||||
`Updating query`,
|
||||
Array.from(Object.keys(filters))
|
||||
.map((kind: string) => `kind ${kind}: ${filters[parseInt(kind)].authors?.length}`)
|
||||
.join(", "),
|
||||
);
|
||||
this.subscription.setFilters(query);
|
||||
|
||||
if (this.subscription.state !== NostrSubscription.OPEN) {
|
||||
this.subscription.open();
|
||||
}
|
||||
} else if (this.subscription.state === NostrSubscription.OPEN) {
|
||||
this.subscription.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
import { NostrEvent } from "nostr-tools";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
import { getEventUID, sortByDate } from "../helpers/nostr/events";
|
||||
import ControlledObservable from "./controlled-observable";
|
||||
import SuperMap from "./super-map";
|
||||
@ -6,7 +8,9 @@ import deleteEventService from "../services/delete-events";
|
||||
|
||||
export type EventFilter = (event: NostrEvent, store: EventStore) => boolean;
|
||||
|
||||
/** a class used to store and sort events */
|
||||
export default class EventStore {
|
||||
id = nanoid(8);
|
||||
name?: string;
|
||||
events = new Map<string, NostrEvent>();
|
||||
|
||||
@ -34,16 +38,15 @@ export default class EventStore {
|
||||
onClear = new ControlledObservable();
|
||||
|
||||
private handleEvent(event: NostrEvent) {
|
||||
const id = getEventUID(event);
|
||||
const existing = this.events.get(id);
|
||||
const uid = getEventUID(event);
|
||||
const existing = this.events.get(uid);
|
||||
if (!existing || event.created_at > existing.created_at) {
|
||||
this.events.set(id, event);
|
||||
this.events.set(uid, event);
|
||||
this.onEvent.next(event);
|
||||
}
|
||||
}
|
||||
|
||||
addEvent(event: NostrEvent) {
|
||||
const id = getEventUID(event);
|
||||
this.handleEvent(event);
|
||||
}
|
||||
getEvent(id: string) {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import { NostrOutgoingRequest, NostrRequestFilter, RelayQueryMap } from "../types/nostr-query";
|
||||
import Relay, { IncomingEvent } from "./relay";
|
||||
import { NostrRequestFilter, RelayQueryMap } from "../types/nostr-relay";
|
||||
import Relay, { IncomingEvent, OutgoingRequest } from "./relay";
|
||||
import relayPoolService from "../services/relay-pool";
|
||||
import { isFilterEqual, isQueryMapEqual } from "../helpers/nostr/filter";
|
||||
import ControlledObservable from "./controlled-observable";
|
||||
@ -26,14 +26,14 @@ export default class NostrMultiSubscription {
|
||||
this.id = nanoid();
|
||||
this.name = name;
|
||||
}
|
||||
private handleEvent(incomingEvent: IncomingEvent) {
|
||||
private handleMessage(incomingEvent: IncomingEvent) {
|
||||
if (
|
||||
this.state === NostrMultiSubscription.OPEN &&
|
||||
incomingEvent.subId === this.id &&
|
||||
!this.seenEvents.has(incomingEvent.body.id)
|
||||
incomingEvent[1] === this.id &&
|
||||
!this.seenEvents.has(incomingEvent[2].id)
|
||||
) {
|
||||
this.onEvent.next(incomingEvent.body);
|
||||
this.seenEvents.add(incomingEvent.body.id);
|
||||
this.onEvent.next(incomingEvent[2]);
|
||||
this.seenEvents.add(incomingEvent[2].id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ export default class NostrMultiSubscription {
|
||||
/** listen for event and open events from relays */
|
||||
private connectToRelay(relay: Relay) {
|
||||
const subs = this.relaySubs.get(relay);
|
||||
subs.push(relay.onEvent.subscribe(this.handleEvent.bind(this)));
|
||||
subs.push(relay.onEvent.subscribe(this.handleMessage.bind(this)));
|
||||
subs.push(relay.onOpen.subscribe(this.handleRelayConnect.bind(this)));
|
||||
subs.push(relay.onClose.subscribe(this.handleRelayDisconnect.bind(this)));
|
||||
relayPoolService.addClaim(relay.url, this);
|
||||
@ -93,9 +93,7 @@ export default class NostrMultiSubscription {
|
||||
|
||||
for (const relay of this.relays) {
|
||||
const filter = this.queryMap[relay.url];
|
||||
const message: NostrOutgoingRequest = Array.isArray(filter)
|
||||
? ["REQ", this.id, ...filter]
|
||||
: ["REQ", this.id, filter];
|
||||
const message: OutgoingRequest = Array.isArray(filter) ? ["REQ", this.id, ...filter] : ["REQ", this.id, filter];
|
||||
|
||||
const currentFilter = this.relayQueries.get(relay);
|
||||
if (!currentFilter || !isFilterEqual(currentFilter, filter)) {
|
||||
|
@ -14,9 +14,10 @@ export default class NostrPublishAction {
|
||||
relays: string[];
|
||||
event: NostrEvent;
|
||||
|
||||
results = new PersistentSubject<IncomingCommandResult[]>([]);
|
||||
onResult = new ControlledObservable<IncomingCommandResult>();
|
||||
onComplete = createDefer<IncomingCommandResult[]>();
|
||||
results = new PersistentSubject<{ relay: Relay; result: IncomingCommandResult }[]>([]);
|
||||
|
||||
onResult = new ControlledObservable<{ relay: Relay; result: IncomingCommandResult }>();
|
||||
onComplete = createDefer<{ relay: Relay; result: IncomingCommandResult }[]>();
|
||||
|
||||
private remaining = new Set<Relay>();
|
||||
private relayResultSubs = new SuperMap<Relay, ZenObservable.Subscription[]>(() => []);
|
||||
@ -29,38 +30,31 @@ export default class NostrPublishAction {
|
||||
for (const url of relays) {
|
||||
const relay = relayPoolService.requestRelay(url);
|
||||
this.remaining.add(relay);
|
||||
this.relayResultSubs.get(relay).push(relay.onCommandResult.subscribe(this.handleResult.bind(this)));
|
||||
this.relayResultSubs.get(relay).push(
|
||||
relay.onCommandResult.subscribe((result) => {
|
||||
if (result[1] === this.event.id) this.handleResult(result, relay);
|
||||
}),
|
||||
);
|
||||
|
||||
// send event
|
||||
relay.send(["EVENT", event]);
|
||||
}
|
||||
|
||||
setTimeout(this.handleTimeout.bind(this), timeout);
|
||||
}
|
||||
|
||||
private handleResult(result: IncomingCommandResult) {
|
||||
if (result.eventId === this.event.id) {
|
||||
const relay = result.relay;
|
||||
this.results.next([...this.results.value, result]);
|
||||
private handleResult(result: IncomingCommandResult, relay: Relay) {
|
||||
this.results.next([...this.results.value, { relay, result }]);
|
||||
this.onResult.next({ relay, result });
|
||||
|
||||
this.onResult.next(result);
|
||||
|
||||
this.relayResultSubs.get(relay).forEach((s) => s.unsubscribe());
|
||||
this.relayResultSubs.delete(relay);
|
||||
this.remaining.delete(relay);
|
||||
if (this.remaining.size === 0) this.onComplete.resolve(this.results.value);
|
||||
}
|
||||
this.relayResultSubs.get(relay).forEach((s) => s.unsubscribe());
|
||||
this.relayResultSubs.delete(relay);
|
||||
this.remaining.delete(relay);
|
||||
if (this.remaining.size === 0) this.onComplete.resolve(this.results.value);
|
||||
}
|
||||
|
||||
private handleTimeout() {
|
||||
for (const relay of this.remaining) {
|
||||
this.handleResult({
|
||||
message: "Timeout",
|
||||
eventId: this.event.id,
|
||||
status: false,
|
||||
type: "OK",
|
||||
relay,
|
||||
});
|
||||
this.handleResult(["OK", this.event.id, false, "Timeout"], relay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { nanoid } from "nanoid";
|
||||
import { CountResponse, NostrEvent } from "../types/nostr-event";
|
||||
import { NostrRequestFilter } from "../types/nostr-query";
|
||||
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import { NostrRequestFilter } from "../types/nostr-relay";
|
||||
import relayPoolService from "../services/relay-pool";
|
||||
import Relay, { IncomingCount, IncomingEOSE, IncomingEvent } from "./relay";
|
||||
import Relay, { CountResponse, IncomingCount, IncomingEOSE, IncomingEvent } from "./relay";
|
||||
import createDefer from "./deferred";
|
||||
import ControlledObservable from "./controlled-observable";
|
||||
import SuperMap from "./super-map";
|
||||
@ -19,7 +20,6 @@ export default class NostrRequest {
|
||||
state = NostrRequest.IDLE;
|
||||
onEvent = new ControlledObservable<NostrEvent>();
|
||||
onCount = new ControlledObservable<CountResponse>();
|
||||
/** @deprecated */
|
||||
onComplete = createDefer<void>();
|
||||
seenEvents = new Set<string>();
|
||||
|
||||
@ -30,7 +30,7 @@ export default class NostrRequest {
|
||||
|
||||
for (const relay of this.relays) {
|
||||
const subs = this.relaySubs.get(relay);
|
||||
subs.push(relay.onEOSE.subscribe(this.handleEOSE.bind(this)));
|
||||
subs.push(relay.onEOSE.subscribe((m) => this.handleEOSE(m, relay)));
|
||||
subs.push(relay.onEvent.subscribe(this.handleEvent.bind(this)));
|
||||
subs.push(relay.onCount.subscribe(this.handleCount.bind(this)));
|
||||
}
|
||||
@ -38,9 +38,8 @@ export default class NostrRequest {
|
||||
this.timeout = timeout ?? REQUEST_DEFAULT_TIMEOUT;
|
||||
}
|
||||
|
||||
handleEOSE(eose: IncomingEOSE) {
|
||||
if (eose.subId === this.id) {
|
||||
const relay = eose.relay;
|
||||
handleEOSE(message: IncomingEOSE, relay: Relay) {
|
||||
if (message[1] === this.id) {
|
||||
this.relays.delete(relay);
|
||||
relay.send(["CLOSE", this.id]);
|
||||
|
||||
@ -53,19 +52,15 @@ export default class NostrRequest {
|
||||
}
|
||||
}
|
||||
}
|
||||
handleEvent(incomingEvent: IncomingEvent) {
|
||||
if (
|
||||
this.state === NostrRequest.RUNNING &&
|
||||
incomingEvent.subId === this.id &&
|
||||
!this.seenEvents.has(incomingEvent.body.id)
|
||||
) {
|
||||
this.onEvent.next(incomingEvent.body);
|
||||
this.seenEvents.add(incomingEvent.body.id);
|
||||
handleEvent(message: IncomingEvent) {
|
||||
if (this.state === NostrRequest.RUNNING && message[1] === this.id && !this.seenEvents.has(message[2].id)) {
|
||||
this.onEvent.next(message[2]);
|
||||
this.seenEvents.add(message[2].id);
|
||||
}
|
||||
}
|
||||
handleCount(incomingCount: IncomingCount) {
|
||||
if (incomingCount.subId === this.id) {
|
||||
this.onCount.next({ count: incomingCount.count, approximate: incomingCount.approximate });
|
||||
if (incomingCount[1] === this.id) {
|
||||
this.onCount.next(incomingCount[2]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { nanoid } from "nanoid";
|
||||
import { Filter, NostrEvent } from "nostr-tools";
|
||||
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import { NostrOutgoingMessage, NostrRequestFilter } from "../types/nostr-query";
|
||||
import Relay, { IncomingEOSE } from "./relay";
|
||||
import Relay, { IncomingEOSE, OutgoingMessage } from "./relay";
|
||||
import relayPoolService from "../services/relay-pool";
|
||||
import ControlledObservable from "./controlled-observable";
|
||||
|
||||
@ -13,61 +12,58 @@ export default class NostrSubscription {
|
||||
|
||||
id: string;
|
||||
name?: string;
|
||||
query?: NostrRequestFilter;
|
||||
filters?: Filter[];
|
||||
relay: Relay;
|
||||
state = NostrSubscription.INIT;
|
||||
|
||||
onEvent = new ControlledObservable<NostrEvent>();
|
||||
onEOSE = new ControlledObservable<IncomingEOSE>();
|
||||
|
||||
private subs: ZenObservable.Subscription[] = [];
|
||||
|
||||
constructor(relayUrl: string | URL, query?: NostrRequestFilter, name?: string) {
|
||||
constructor(relayUrl: string | URL, filters?: Filter[], name?: string) {
|
||||
this.id = nanoid();
|
||||
this.query = query;
|
||||
this.filters = filters;
|
||||
this.name = name;
|
||||
|
||||
this.relay = relayPoolService.requestRelay(relayUrl);
|
||||
|
||||
this.subs.push(
|
||||
this.relay.onEvent.subscribe((message) => {
|
||||
if (this.state === NostrSubscription.OPEN && message.subId === this.id) {
|
||||
this.onEvent.next(message.body);
|
||||
if (this.state === NostrSubscription.OPEN && message[1] === this.id) {
|
||||
this.onEvent.next(message[2]);
|
||||
}
|
||||
}),
|
||||
);
|
||||
this.subs.push(
|
||||
this.relay.onEOSE.subscribe((eose) => {
|
||||
if (this.state === NostrSubscription.OPEN && eose.subId === this.id) this.onEOSE.next(eose);
|
||||
if (this.state === NostrSubscription.OPEN && eose[1] === this.id) this.onEOSE.next(eose);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
send(message: NostrOutgoingMessage) {
|
||||
send(message: OutgoingMessage) {
|
||||
this.relay.send(message);
|
||||
}
|
||||
setFilters(filters: Filter[]) {
|
||||
this.filters = filters;
|
||||
if (this.state === NostrSubscription.OPEN) {
|
||||
this.send(["REQ", this.id, ...this.filters]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
open() {
|
||||
if (!this.query) throw new Error("cant open without a query");
|
||||
if (!this.filters) throw new Error("cant open without a query");
|
||||
if (this.state === NostrSubscription.OPEN) return this;
|
||||
|
||||
this.state = NostrSubscription.OPEN;
|
||||
if (Array.isArray(this.query)) {
|
||||
this.send(["REQ", this.id, ...this.query]);
|
||||
} else this.send(["REQ", this.id, this.query]);
|
||||
this.send(["REQ", this.id, ...this.filters]);
|
||||
|
||||
relayPoolService.addClaim(this.relay.url, this);
|
||||
|
||||
return this;
|
||||
}
|
||||
setQuery(query: NostrRequestFilter) {
|
||||
this.query = query;
|
||||
if (this.state === NostrSubscription.OPEN) {
|
||||
if (Array.isArray(this.query)) {
|
||||
this.send(["REQ", this.id, ...this.query]);
|
||||
} else this.send(["REQ", this.id, this.query]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
close() {
|
||||
if (this.state !== NostrSubscription.OPEN) return this;
|
||||
|
||||
|
90
src/classes/relay-pool.ts
Normal file
90
src/classes/relay-pool.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import { logger } from "../helpers/debug";
|
||||
import { validateRelayURL } from "../helpers/relay";
|
||||
import { offlineMode } from "../services/offline-mode";
|
||||
import Relay from "./relay";
|
||||
import Subject from "./subject";
|
||||
|
||||
export default class RelayPool {
|
||||
relays = new Map<string, Relay>();
|
||||
onRelayCreated = new Subject<Relay>();
|
||||
|
||||
relayClaims = new Map<string, Set<any>>();
|
||||
|
||||
log = logger.extend("RelayPool");
|
||||
|
||||
getRelays() {
|
||||
return Array.from(this.relays.values());
|
||||
}
|
||||
getRelayClaims(url: string | URL) {
|
||||
url = validateRelayURL(url);
|
||||
const key = url.toString();
|
||||
if (!this.relayClaims.has(key)) {
|
||||
this.relayClaims.set(key, new Set());
|
||||
}
|
||||
return this.relayClaims.get(key) as Set<any>;
|
||||
}
|
||||
|
||||
requestRelay(url: string | URL, connect = true) {
|
||||
url = validateRelayURL(url);
|
||||
const key = url.toString();
|
||||
if (!this.relays.has(key)) {
|
||||
const newRelay = new Relay(key);
|
||||
this.relays.set(key, newRelay);
|
||||
this.onRelayCreated.next(newRelay);
|
||||
}
|
||||
|
||||
const relay = this.relays.get(key) as Relay;
|
||||
if (connect && !relay.okay) {
|
||||
try {
|
||||
relay.open();
|
||||
} catch (e) {
|
||||
this.log(`Failed to connect to ${relay.url}`);
|
||||
this.log(e);
|
||||
}
|
||||
}
|
||||
return relay;
|
||||
}
|
||||
|
||||
pruneRelays() {
|
||||
for (const [url, relay] of this.relays.entries()) {
|
||||
const claims = this.getRelayClaims(url).size;
|
||||
if (claims === 0) {
|
||||
relay.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
reconnectRelays() {
|
||||
if (offlineMode.value) return;
|
||||
|
||||
for (const [url, relay] of this.relays.entries()) {
|
||||
const claims = this.getRelayClaims(url).size;
|
||||
if (!relay.okay && claims > 0) {
|
||||
try {
|
||||
relay.open();
|
||||
} catch (e) {
|
||||
this.log(`Failed to connect to ${relay.url}`);
|
||||
this.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addClaim(url: string | URL, id: any) {
|
||||
url = validateRelayURL(url);
|
||||
const key = url.toString();
|
||||
this.getRelayClaims(key).add(id);
|
||||
}
|
||||
removeClaim(url: string | URL, id: any) {
|
||||
url = validateRelayURL(url);
|
||||
const key = url.toString();
|
||||
this.getRelayClaims(key).delete(id);
|
||||
}
|
||||
|
||||
get connectedCount() {
|
||||
let count = 0;
|
||||
for (const [url, relay] of this.relays.entries()) {
|
||||
if (relay.connected) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
@ -1,40 +1,28 @@
|
||||
import { Filter, NostrEvent } from "nostr-tools";
|
||||
import { offlineMode } from "../services/offline-mode";
|
||||
import relayScoreboardService from "../services/relay-scoreboard";
|
||||
import { RawIncomingNostrEvent, NostrEvent, CountResponse } from "../types/nostr-event";
|
||||
import { NostrOutgoingMessage } from "../types/nostr-query";
|
||||
import ControlledObservable from "./controlled-observable";
|
||||
import createDefer, { Deferred } from "./deferred";
|
||||
import { PersistentSubject } from "./subject";
|
||||
|
||||
export type IncomingEvent = {
|
||||
type: "EVENT";
|
||||
subId: string;
|
||||
body: NostrEvent;
|
||||
relay: Relay;
|
||||
};
|
||||
export type IncomingNotice = {
|
||||
type: "NOTICE";
|
||||
message: string;
|
||||
relay: Relay;
|
||||
};
|
||||
export type IncomingCount = {
|
||||
type: "COUNT";
|
||||
subId: string;
|
||||
relay: Relay;
|
||||
} & CountResponse;
|
||||
export type IncomingEOSE = {
|
||||
type: "EOSE";
|
||||
subId: string;
|
||||
relay: Relay;
|
||||
};
|
||||
export type IncomingCommandResult = {
|
||||
type: "OK";
|
||||
eventId: string;
|
||||
status: boolean;
|
||||
message?: string;
|
||||
relay: Relay;
|
||||
export type CountResponse = {
|
||||
count: number;
|
||||
approximate?: boolean;
|
||||
};
|
||||
|
||||
export type IncomingEvent = ["EVENT", string, NostrEvent];
|
||||
export type IncomingNotice = ["NOTICE", string];
|
||||
export type IncomingCount = ["COUNT", string, CountResponse];
|
||||
export type IncomingEOSE = ["EOSE", string];
|
||||
export type IncomingCommandResult = ["OK", string, boolean] | ["OK", string, boolean, string];
|
||||
export type IncomingMessage = IncomingEvent | IncomingNotice | IncomingCount | IncomingEOSE | IncomingCommandResult;
|
||||
|
||||
export type OutgoingEvent = ["EVENT", NostrEvent];
|
||||
export type OutgoingRequest = ["REQ", string, ...Filter[]];
|
||||
export type OutgoingCount = ["COUNT", string, ...Filter[]];
|
||||
export type OutgoingClose = ["CLOSE", string];
|
||||
export type OutgoingMessage = OutgoingEvent | OutgoingRequest | OutgoingClose | OutgoingCount;
|
||||
|
||||
export enum RelayMode {
|
||||
NONE = 0,
|
||||
READ = 1,
|
||||
@ -46,15 +34,16 @@ const CONNECTION_TIMEOUT = 1000 * 30;
|
||||
|
||||
export default class Relay {
|
||||
url: string;
|
||||
ws?: WebSocket;
|
||||
status = new PersistentSubject<number>(WebSocket.CLOSED);
|
||||
onOpen = new ControlledObservable<Relay>();
|
||||
onClose = new ControlledObservable<Relay>();
|
||||
|
||||
onEvent = new ControlledObservable<IncomingEvent>();
|
||||
onNotice = new ControlledObservable<IncomingNotice>();
|
||||
onCount = new ControlledObservable<IncomingCount>();
|
||||
onEOSE = new ControlledObservable<IncomingEOSE>();
|
||||
onCommandResult = new ControlledObservable<IncomingCommandResult>();
|
||||
ws?: WebSocket;
|
||||
|
||||
private connectionPromises: Deferred<void>[] = [];
|
||||
|
||||
@ -62,7 +51,7 @@ export default class Relay {
|
||||
private ejectTimer?: () => void;
|
||||
private intentionalClose = false;
|
||||
private subscriptionResTimer = new Map<string, () => void>();
|
||||
private queue: NostrOutgoingMessage[] = [];
|
||||
private queue: OutgoingMessage[] = [];
|
||||
|
||||
constructor(url: string) {
|
||||
this.url = url;
|
||||
@ -123,7 +112,7 @@ export default class Relay {
|
||||
};
|
||||
this.ws.onmessage = this.handleMessage.bind(this);
|
||||
}
|
||||
send(json: NostrOutgoingMessage) {
|
||||
send(json: OutgoingMessage) {
|
||||
if (this.connected) {
|
||||
this.ws?.send(JSON.stringify(json));
|
||||
|
||||
@ -185,35 +174,38 @@ export default class Relay {
|
||||
return this.ws?.readyState;
|
||||
}
|
||||
|
||||
handleMessage(event: MessageEvent<string>) {
|
||||
if (!event.data) return;
|
||||
handleMessage(message: MessageEvent<string>) {
|
||||
if (!message.data) return;
|
||||
|
||||
try {
|
||||
const data: RawIncomingNostrEvent = JSON.parse(event.data);
|
||||
const data: IncomingMessage = JSON.parse(message.data);
|
||||
const type = data[0];
|
||||
|
||||
// all messages must have an argument
|
||||
if (!data[1]) return;
|
||||
|
||||
switch (type) {
|
||||
case "EVENT":
|
||||
this.onEvent.next({ relay: this, type, subId: data[1], body: data[2] });
|
||||
this.onEvent.next(data);
|
||||
this.endSubResTimer(data[1]);
|
||||
break;
|
||||
case "NOTICE":
|
||||
this.onNotice.next({ relay: this, type, message: data[1] });
|
||||
this.onNotice.next(data);
|
||||
break;
|
||||
case "COUNT":
|
||||
this.onCount.next({ relay: this, type, subId: data[1], ...data[2] });
|
||||
this.onCount.next(data);
|
||||
break;
|
||||
case "EOSE":
|
||||
this.onEOSE.next({ relay: this, type, subId: data[1] });
|
||||
this.onEOSE.next(data);
|
||||
this.endSubResTimer(data[1]);
|
||||
break;
|
||||
case "OK":
|
||||
this.onCommandResult.next({ relay: this, type, eventId: data[1], status: data[2], message: data[3] });
|
||||
this.onCommandResult.next(data);
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`Relay: Failed to parse event from ${this.url}`);
|
||||
console.log(event.data, e);
|
||||
console.log(`Relay: Failed to parse massage from ${this.url}`);
|
||||
console.log(message.data, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,14 +4,14 @@ import { Filter, matchFilters } from "nostr-tools";
|
||||
import _throttle from "lodash.throttle";
|
||||
|
||||
import { NostrEvent, isATag, isETag } from "../types/nostr-event";
|
||||
import { NostrRequestFilter, RelayQueryMap } from "../types/nostr-query";
|
||||
import { NostrRequestFilter, RelayQueryMap } from "../types/nostr-relay";
|
||||
import NostrRequest from "./nostr-request";
|
||||
import NostrMultiSubscription from "./nostr-multi-subscription";
|
||||
import Subject, { PersistentSubject } from "./subject";
|
||||
import { logger } from "../helpers/debug";
|
||||
import EventStore from "./event-store";
|
||||
import { isReplaceable } from "../helpers/nostr/events";
|
||||
import replaceableEventLoaderService from "../services/replaceable-event-requester";
|
||||
import replaceableEventsService from "../services/replaceable-events";
|
||||
import deleteEventService from "../services/delete-events";
|
||||
import {
|
||||
addQueryToFilter,
|
||||
@ -127,7 +127,7 @@ export default class TimelineLoader {
|
||||
this.name = name;
|
||||
this.log = logger.extend("TimelineLoader:" + name);
|
||||
this.events = new EventStore(name);
|
||||
this.events.connect(replaceableEventLoaderService.events, false);
|
||||
this.events.connect(replaceableEventsService.events, false);
|
||||
|
||||
this.subscription = new NostrMultiSubscription(name);
|
||||
this.subscription.onEvent.subscribe(this.handleEvent.bind(this));
|
||||
@ -147,7 +147,7 @@ export default class TimelineLoader {
|
||||
}
|
||||
private handleEvent(event: NostrEvent, cache = true) {
|
||||
// if this is a replaceable event, mirror it over to the replaceable event service
|
||||
if (isReplaceable(event.kind)) replaceableEventLoaderService.handleEvent(event);
|
||||
if (isReplaceable(event.kind)) replaceableEventsService.handleEvent(event);
|
||||
|
||||
this.events.addEvent(event);
|
||||
if (cache) localRelay.publish(event);
|
||||
|
@ -7,13 +7,13 @@ import RawValue from "./raw-value";
|
||||
import RawJson from "./raw-json";
|
||||
import { useSharableProfileId } from "../../hooks/use-shareable-profile-id";
|
||||
import useUserLNURLMetadata from "../../hooks/use-user-lnurl-metadata";
|
||||
import replaceableEventLoaderService from "../../services/replaceable-event-requester";
|
||||
import replaceableEventsService from "../../services/replaceable-events";
|
||||
|
||||
export default function UserDebugModal({ pubkey, ...props }: { pubkey: string } & Omit<ModalProps, "children">) {
|
||||
const npub = nip19.npubEncode(pubkey);
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
const nprofile = useSharableProfileId(pubkey);
|
||||
const relays = replaceableEventLoaderService.getEvent(kinds.RelayList, pubkey).value;
|
||||
const relays = replaceableEventsService.getEvent(kinds.RelayList, pubkey).value;
|
||||
const tipMetadata = useUserLNURLMetadata(pubkey);
|
||||
|
||||
return (
|
||||
|
@ -3,7 +3,7 @@ import { Link as RouterLink } from "react-router-dom";
|
||||
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import { getListDescription, getListName, isSpecialListKind } from "../../../helpers/nostr/lists";
|
||||
import { createCoordinate } from "../../../services/replaceable-event-requester";
|
||||
import { createCoordinate } from "../../../services/replaceable-events";
|
||||
import { getSharableEventAddress } from "../../../helpers/nip19";
|
||||
import UserAvatarLink from "../../user-avatar-link";
|
||||
import UserLink from "../../user-link";
|
||||
|
@ -30,7 +30,7 @@ import { isValidRelayURL } from "../helpers/relay";
|
||||
import relayScoreboardService from "../services/relay-scoreboard";
|
||||
import { RelayFavicon } from "./relay-favicon";
|
||||
import singleEventService from "../services/single-event";
|
||||
import replaceableEventLoaderService from "../services/replaceable-event-requester";
|
||||
import replaceableEventsService from "../services/replaceable-events";
|
||||
|
||||
function SearchOnRelaysModal({
|
||||
isOpen,
|
||||
@ -53,7 +53,7 @@ function SearchOnRelaysModal({
|
||||
setLoading(true);
|
||||
switch (decode.type) {
|
||||
case "naddr":
|
||||
replaceableEventLoaderService.requestEvent(
|
||||
replaceableEventsService.requestEvent(
|
||||
Array.from(relays),
|
||||
decode.data.kind,
|
||||
decode.data.pubkey,
|
||||
|
@ -21,7 +21,7 @@ import { EmbedEvent } from "../../embed-event";
|
||||
import { ChevronDownIcon, ChevronUpIcon, ExternalLinkIcon } from "../../icons";
|
||||
import useUserCommunitiesList from "../../../hooks/use-user-communities-list";
|
||||
import useCurrentAccount from "../../../hooks/use-current-account";
|
||||
import { createCoordinate } from "../../../services/replaceable-event-requester";
|
||||
import { createCoordinate } from "../../../services/replaceable-events";
|
||||
import relayHintService from "../../../services/event-relay-hint";
|
||||
import { usePublishEvent } from "../../../providers/global/publish-provider";
|
||||
|
||||
|
@ -17,17 +17,17 @@ export function PublishDetails({ pub }: PostResultsProps & Omit<FlexProps, "chil
|
||||
<Flex direction="column" gap="2">
|
||||
<EmbedEvent event={pub.event} />
|
||||
<Progress value={(results.length / pub.relays.length) * 100} size="lg" hasStripe />
|
||||
{results.map((result) => (
|
||||
<Alert key={result.relay.url} status={result.status ? "success" : "warning"}>
|
||||
{results.map(({ result, relay }) => (
|
||||
<Alert key={relay.url} status={result[2] ? "success" : "warning"}>
|
||||
<AlertIcon />
|
||||
<Box>
|
||||
<AlertTitle>
|
||||
<Link as={RouterLink} to={`/r/${encodeURIComponent(result.relay.url)}`}>
|
||||
{result.relay.url}
|
||||
<Link as={RouterLink} to={`/r/${encodeURIComponent(relay.url)}`}>
|
||||
{relay.url}
|
||||
</Link>
|
||||
<RelayPaidTag url={result.relay.url} />
|
||||
<RelayPaidTag url={relay.url} />
|
||||
</AlertTitle>
|
||||
{result.message && <AlertDescription>{result.message}</AlertDescription>}
|
||||
{result[3] && <AlertDescription>{result[3]}</AlertDescription>}
|
||||
</Box>
|
||||
</Alert>
|
||||
))}
|
||||
|
@ -25,8 +25,8 @@ import { PublishContext } from "../providers/global/publish-provider";
|
||||
export function PublishActionStatusTag({ pub, ...props }: { pub: NostrPublishAction } & Omit<TagProps, "children">) {
|
||||
const results = useSubject(pub.results);
|
||||
|
||||
const successful = results.filter((result) => result.status);
|
||||
const failedWithMessage = results.filter((result) => !result.status && result.message);
|
||||
const successful = results.filter(({ result }) => result[2]);
|
||||
const failedWithMessage = results.filter(({ result }) => !result[2] && result[3]);
|
||||
|
||||
let statusIcon = <Spinner size="xs" />;
|
||||
let statusColor: TagProps["colorScheme"] = "blue";
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { EventTemplate, kinds, validateEvent } from "nostr-tools";
|
||||
import { getEventUID } from "nostr-idb";
|
||||
import dayjs from "dayjs";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
import { ATag, DraftNostrEvent, ETag, isATag, isDTag, isETag, isPTag, NostrEvent, Tag } from "../../types/nostr-event";
|
||||
import { getMatchNostrLink } from "../regexp";
|
||||
import { AddressPointer, EventPointer } from "nostr-tools/lib/types/nip19";
|
||||
import { safeJson } from "../parse";
|
||||
import { safeDecode } from "../nip19";
|
||||
import { getEventUID } from "nostr-idb";
|
||||
import { safeRelayUrl, safeRelayUrls } from "../relay";
|
||||
import dayjs from "dayjs";
|
||||
import { nanoid } from "nanoid";
|
||||
import userMailboxesService from "../../services/user-mailboxes";
|
||||
import RelaySet from "../../classes/relay-set";
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import stringify from "json-stringify-deterministic";
|
||||
import { NostrRequestFilter, RelayQueryMap } from "../../types/nostr-query";
|
||||
import { NostrRequestFilter, RelayQueryMap } from "../../types/nostr-relay";
|
||||
import { Filter } from "nostr-tools";
|
||||
import { safeRelayUrls } from "../relay";
|
||||
|
||||
|
@ -2,7 +2,7 @@ import dayjs from "dayjs";
|
||||
import { DraftNostrEvent, NostrEvent, isPTag } from "../../types/nostr-event";
|
||||
import { unique } from "../array";
|
||||
import { ensureNotifyContentMentions } from "./post";
|
||||
import { createCoordinate } from "../../services/replaceable-event-requester";
|
||||
import { createCoordinate } from "../../services/replaceable-events";
|
||||
|
||||
export const STREAM_KIND = 30311;
|
||||
export const STREAM_CHAT_MESSAGE_KIND = 1311;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { SimpleRelay, SubscriptionOptions } from "nostr-idb";
|
||||
import { Filter } from "nostr-tools";
|
||||
|
||||
import { NostrQuery, NostrRequestFilter } from "../types/nostr-query";
|
||||
import { NostrQuery, NostrRequestFilter } from "../types/nostr-relay";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
|
||||
// NOTE: only use this for equality checks and querying
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { RequestOptions } from "../services/replaceable-event-requester";
|
||||
import { RequestOptions } from "../services/replaceable-events";
|
||||
import useSubject from "./use-subject";
|
||||
import channelMetadataService from "../services/channel-metadata";
|
||||
import { ChannelMetadata, safeParseChannelMetadata } from "../helpers/nostr/channel";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useMemo } from "react";
|
||||
import eventCountService from "../services/event-count";
|
||||
import { NostrRequestFilter } from "../types/nostr-query";
|
||||
import { NostrRequestFilter } from "../types/nostr-relay";
|
||||
import useSubject from "./use-subject";
|
||||
|
||||
export default function useEventCount(filter?: NostrRequestFilter, alwaysRequest = false) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useMemo } from "react";
|
||||
import stringify from "json-stringify-deterministic";
|
||||
import eventExistsService from "../services/event-exists";
|
||||
import { NostrRequestFilter } from "../types/nostr-query";
|
||||
import { NostrRequestFilter } from "../types/nostr-relay";
|
||||
import useSubject from "./use-subject";
|
||||
|
||||
export default function useEventExists(filter?: NostrRequestFilter, relays: string[] = [], fallback = true) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import useReplaceableEvent from "./use-replaceable-event";
|
||||
import useCurrentAccount from "./use-current-account";
|
||||
import { USER_EMOJI_LIST_KIND } from "../helpers/nostr/emoji-packs";
|
||||
import { RequestOptions } from "../services/replaceable-event-requester";
|
||||
import { RequestOptions } from "../services/replaceable-events";
|
||||
|
||||
export const FAVORITE_LISTS_IDENTIFIER = "nostrudel-favorite-lists";
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { useReadRelays } from "./use-client-relays";
|
||||
import replaceableEventLoaderService, { RequestOptions } from "../services/replaceable-event-requester";
|
||||
import replaceableEventsService, { RequestOptions } from "../services/replaceable-events";
|
||||
import { CustomAddressPointer, parseCoordinate } from "../helpers/nostr/events";
|
||||
import useSubject from "./use-subject";
|
||||
|
||||
@ -14,7 +14,7 @@ export default function useReplaceableEvent(
|
||||
const sub = useMemo(() => {
|
||||
const parsed = typeof cord === "string" ? parseCoordinate(cord) : cord;
|
||||
if (!parsed) return;
|
||||
return replaceableEventLoaderService.requestEvent(
|
||||
return replaceableEventsService.requestEvent(
|
||||
parsed.relays ? [...readRelays, ...parsed.relays] : readRelays,
|
||||
parsed.kind,
|
||||
parsed.pubkey,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { useReadRelays } from "./use-client-relays";
|
||||
import replaceableEventLoaderService, { RequestOptions } from "../services/replaceable-event-requester";
|
||||
import replaceableEventsService, { RequestOptions } from "../services/replaceable-events";
|
||||
import { CustomAddressPointer, parseCoordinate } from "../helpers/nostr/events";
|
||||
import Subject from "../classes/subject";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
@ -20,7 +20,7 @@ export default function useReplaceableEvents(
|
||||
const parsed = typeof cord === "string" ? parseCoordinate(cord) : cord;
|
||||
if (!parsed) return;
|
||||
subs.push(
|
||||
replaceableEventLoaderService.requestEvent(
|
||||
replaceableEventsService.requestEvent(
|
||||
parsed.relays ? [...readRelays, ...parsed.relays] : readRelays,
|
||||
parsed.kind,
|
||||
parsed.pubkey,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useUnmount } from "react-use";
|
||||
|
||||
import { NostrRequestFilter } from "../types/nostr-query";
|
||||
import { NostrRequestFilter } from "../types/nostr-relay";
|
||||
import timelineCacheService from "../services/timeline-cache";
|
||||
import { EventFilter } from "../classes/timeline-loader";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { BOOKMARK_LIST_KIND, getAddressPointersFromList, getEventPointersFromList } from "../helpers/nostr/lists";
|
||||
import { RequestOptions } from "../services/replaceable-event-requester";
|
||||
import { RequestOptions } from "../services/replaceable-events";
|
||||
import useCurrentAccount from "./use-current-account";
|
||||
import useReplaceableEvent from "./use-replaceable-event";
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { CHANNELS_LIST_KIND, getEventPointersFromList } from "../helpers/nostr/lists";
|
||||
import { RequestOptions } from "../services/replaceable-event-requester";
|
||||
import { RequestOptions } from "../services/replaceable-events";
|
||||
import useCurrentAccount from "./use-current-account";
|
||||
import useReplaceableEvent from "./use-replaceable-event";
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { COMMUNITY_DEFINITION_KIND, SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER } from "../helpers/nostr/communities";
|
||||
import { COMMUNITIES_LIST_KIND, NOTE_LIST_KIND, getAddressPointersFromList } from "../helpers/nostr/lists";
|
||||
import { RequestOptions } from "../services/replaceable-event-requester";
|
||||
import { RequestOptions } from "../services/replaceable-events";
|
||||
import useCurrentAccount from "./use-current-account";
|
||||
import useReplaceableEvent from "./use-replaceable-event";
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { kinds } from "nostr-tools";
|
||||
import useReplaceableEvent from "./use-replaceable-event";
|
||||
import { RequestOptions } from "../services/replaceable-event-requester";
|
||||
import { RequestOptions } from "../services/replaceable-events";
|
||||
|
||||
export default function useUserContactList(
|
||||
pubkey?: string,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useMemo } from "react";
|
||||
import { RequestOptions } from "../services/replaceable-event-requester";
|
||||
import { RequestOptions } from "../services/replaceable-events";
|
||||
import RelaySet from "../classes/relay-set";
|
||||
import useUserContactList from "./use-user-contact-list";
|
||||
import { RelayMode } from "../classes/relay";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import RelaySet from "../classes/relay-set";
|
||||
import { RequestOptions } from "../services/replaceable-event-requester";
|
||||
import { RequestOptions } from "../services/replaceable-events";
|
||||
import userMailboxesService from "../services/user-mailboxes";
|
||||
import { useReadRelays } from "./use-client-relays";
|
||||
import useSubject from "./use-subject";
|
||||
|
@ -2,7 +2,7 @@ import { useMemo } from "react";
|
||||
import userMetadataService from "../services/user-metadata";
|
||||
import { useReadRelays } from "./use-client-relays";
|
||||
import useSubject from "./use-subject";
|
||||
import { RequestOptions } from "../services/replaceable-event-requester";
|
||||
import { RequestOptions } from "../services/replaceable-events";
|
||||
import { COMMON_CONTACT_RELAY } from "../const";
|
||||
|
||||
export function useUserMetadata(pubkey?: string, additionalRelays: Iterable<string> = [], opts: RequestOptions = {}) {
|
||||
|
@ -5,7 +5,7 @@ import useUserMuteList from "./use-user-mute-list";
|
||||
import { getPubkeysFromList } from "../helpers/nostr/lists";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import { STREAM_KIND, getStreamHost } from "../helpers/nostr/stream";
|
||||
import { RequestOptions } from "../services/replaceable-event-requester";
|
||||
import { RequestOptions } from "../services/replaceable-events";
|
||||
|
||||
export default function useUserMuteFilter(pubkey?: string, additionalRelays?: string[], opts?: RequestOptions) {
|
||||
const account = useCurrentAccount();
|
||||
|
@ -1,6 +1,6 @@
|
||||
import useReplaceableEvent from "./use-replaceable-event";
|
||||
import { MUTE_LIST_KIND } from "../helpers/nostr/lists";
|
||||
import { RequestOptions } from "../services/replaceable-event-requester";
|
||||
import { RequestOptions } from "../services/replaceable-events";
|
||||
|
||||
export default function useUserMuteList(
|
||||
pubkey?: string,
|
||||
|
@ -3,7 +3,7 @@ import { useMemo } from "react";
|
||||
import useReplaceableEvent from "./use-replaceable-event";
|
||||
import { PEOPLE_LIST_KIND, getPubkeysFromList } from "../helpers/nostr/lists";
|
||||
import useUserMuteList from "./use-user-mute-list";
|
||||
import { RequestOptions } from "../services/replaceable-event-requester";
|
||||
import { RequestOptions } from "../services/replaceable-events";
|
||||
|
||||
export default function useUserMuteLists(
|
||||
pubkey?: string,
|
||||
|
@ -3,7 +3,7 @@ import { kinds } from "nostr-tools";
|
||||
|
||||
import { getPubkeysFromList } from "../helpers/nostr/lists";
|
||||
import useUserContactList from "./use-user-contact-list";
|
||||
import replaceableEventLoaderService from "../services/replaceable-event-requester";
|
||||
import replaceableEventsService from "../services/replaceable-events";
|
||||
import { useReadRelays } from "./use-client-relays";
|
||||
import useSubjects from "./use-subjects";
|
||||
import userMetadataService from "../services/user-metadata";
|
||||
@ -34,7 +34,7 @@ export default function useUserNetwork(pubkey: string, additionalRelays?: Iterab
|
||||
|
||||
const subjects = useMemo(() => {
|
||||
return contactsPubkeys.map((person) =>
|
||||
replaceableEventLoaderService.requestEvent(readRelays, kinds.Contacts, person.pubkey),
|
||||
replaceableEventsService.requestEvent(readRelays, kinds.Contacts, person.pubkey),
|
||||
);
|
||||
}, [contactsPubkeys, readRelays.urls.join("|")]);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { PIN_LIST_KIND, getEventPointersFromList } from "../helpers/nostr/lists";
|
||||
import { RequestOptions } from "../services/replaceable-event-requester";
|
||||
import { RequestOptions } from "../services/replaceable-events";
|
||||
import useCurrentAccount from "./use-current-account";
|
||||
import useReplaceableEvent from "./use-replaceable-event";
|
||||
|
||||
|
@ -8,7 +8,7 @@ import NostrPublishAction from "../../classes/nostr-publish-action";
|
||||
import clientRelaysService from "../../services/client-relays";
|
||||
import RelaySet from "../../classes/relay-set";
|
||||
import { addPubkeyRelayHints, getAllRelayHints, isReplaceable } from "../../helpers/nostr/events";
|
||||
import replaceableEventLoaderService from "../../services/replaceable-event-requester";
|
||||
import replaceableEventsService from "../../services/replaceable-events";
|
||||
import eventExistsService from "../../services/event-exists";
|
||||
import eventReactionsService from "../../services/event-reactions";
|
||||
import { localRelay } from "../../services/local-relay";
|
||||
@ -66,8 +66,8 @@ export default function PublishProvider({ children }: PropsWithChildren) {
|
||||
const pub = new NostrPublishAction(label, relays, signed);
|
||||
setLog((arr) => arr.concat(pub));
|
||||
|
||||
pub.onResult.subscribe((result) => {
|
||||
if (result.status) handleEventFromRelay(result.relay, signed);
|
||||
pub.onResult.subscribe(({ relay, result }) => {
|
||||
if (result[2]) handleEventFromRelay(relay, signed);
|
||||
});
|
||||
|
||||
// send it to the local relay
|
||||
@ -75,7 +75,7 @@ export default function PublishProvider({ children }: PropsWithChildren) {
|
||||
|
||||
// pass it to other services
|
||||
eventExistsService.handleEvent(signed);
|
||||
if (isReplaceable(signed.kind)) replaceableEventLoaderService.handleEvent(signed);
|
||||
if (isReplaceable(signed.kind)) replaceableEventsService.handleEvent(signed);
|
||||
if (signed.kind === kinds.Reaction) eventReactionsService.handleEvent(signed);
|
||||
if (signed.kind === kinds.EventDeletion) deleteEventService.handleEvent(signed);
|
||||
return pub;
|
||||
|
@ -5,7 +5,7 @@ import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import { getPubkeysFromList } from "../../helpers/nostr/lists";
|
||||
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { NostrQuery } from "../../types/nostr-query";
|
||||
import { NostrQuery } from "../../types/nostr-relay";
|
||||
import useRouteSearchValue from "../../hooks/use-route-search-value";
|
||||
|
||||
export type ListId = "following" | "global" | string;
|
||||
|
@ -7,7 +7,7 @@ import NostrSubscription from "../classes/nostr-subscription";
|
||||
import SuperMap from "../classes/super-map";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import Subject from "../classes/subject";
|
||||
import { NostrQuery } from "../types/nostr-query";
|
||||
import { NostrQuery } from "../types/nostr-relay";
|
||||
import { logger } from "../helpers/debug";
|
||||
import db from "./db";
|
||||
import createDefer, { Deferred } from "../classes/deferred";
|
||||
@ -109,7 +109,7 @@ class ChannelMetadataRelayLoader {
|
||||
};
|
||||
|
||||
if (query["#e"] && query["#e"].length > 0) this.log(`Updating query`, query["#e"].length);
|
||||
this.subscription.setQuery(query);
|
||||
this.subscription.setFilters([query]);
|
||||
|
||||
if (this.subscription.state !== NostrSubscription.OPEN) {
|
||||
this.subscription.open();
|
||||
|
@ -2,7 +2,7 @@ import stringify from "json-stringify-deterministic";
|
||||
|
||||
import Subject from "../classes/subject";
|
||||
import SuperMap from "../classes/super-map";
|
||||
import { NostrRequestFilter } from "../types/nostr-query";
|
||||
import { NostrRequestFilter } from "../types/nostr-relay";
|
||||
import NostrRequest from "../classes/nostr-request";
|
||||
import relayPoolService from "./relay-pool";
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import stringify from "json-stringify-deterministic";
|
||||
|
||||
import Subject from "../classes/subject";
|
||||
import { NostrRequestFilter } from "../types/nostr-query";
|
||||
import { NostrRequestFilter } from "../types/nostr-relay";
|
||||
import SuperMap from "../classes/super-map";
|
||||
import NostrRequest from "../classes/nostr-request";
|
||||
import relayScoreboardService from "./relay-scoreboard";
|
||||
|
@ -2,7 +2,7 @@ import { NostrEvent } from "../types/nostr-event";
|
||||
import { getEventRelays } from "./event-relays";
|
||||
import relayScoreboardService from "./relay-scoreboard";
|
||||
import type { AddressPointer, EventPointer } from "nostr-tools/lib/types/nip19";
|
||||
import { createCoordinate } from "./replaceable-event-requester";
|
||||
import { createCoordinate } from "./replaceable-events";
|
||||
|
||||
function pickBestRelays(relays: string[]) {
|
||||
// ignore local relays
|
||||
|
@ -31,8 +31,8 @@ export function handleEventFromRelay(relay: Relay, event: NostrEvent) {
|
||||
}
|
||||
|
||||
relayPoolService.onRelayCreated.subscribe((relay) => {
|
||||
relay.onEvent.subscribe(({ body: event }) => {
|
||||
handleEventFromRelay(relay, event);
|
||||
relay.onEvent.subscribe((message) => {
|
||||
handleEventFromRelay(relay, message[2]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1,95 +1,7 @@
|
||||
import Relay from "../classes/relay";
|
||||
import Subject from "../classes/subject";
|
||||
import { logger } from "../helpers/debug";
|
||||
import { safeRelayUrl, validateRelayURL } from "../helpers/relay";
|
||||
import RelayPool from "../classes/relay-pool";
|
||||
import { offlineMode } from "./offline-mode";
|
||||
|
||||
export class RelayPoolService {
|
||||
relays = new Map<string, Relay>();
|
||||
relayClaims = new Map<string, Set<any>>();
|
||||
onRelayCreated = new Subject<Relay>();
|
||||
|
||||
log = logger.extend("RelayPool");
|
||||
|
||||
getRelays() {
|
||||
return Array.from(this.relays.values());
|
||||
}
|
||||
getRelayClaims(url: string | URL) {
|
||||
url = validateRelayURL(url);
|
||||
const key = url.toString();
|
||||
if (!this.relayClaims.has(key)) {
|
||||
this.relayClaims.set(key, new Set());
|
||||
}
|
||||
return this.relayClaims.get(key) as Set<any>;
|
||||
}
|
||||
|
||||
requestRelay(url: string | URL, connect = true) {
|
||||
url = validateRelayURL(url);
|
||||
const key = url.toString();
|
||||
if (!this.relays.has(key)) {
|
||||
const newRelay = new Relay(key);
|
||||
this.relays.set(key, newRelay);
|
||||
this.onRelayCreated.next(newRelay);
|
||||
}
|
||||
|
||||
const relay = this.relays.get(key) as Relay;
|
||||
if (connect && !relay.okay) {
|
||||
try {
|
||||
relay.open();
|
||||
} catch (e) {
|
||||
this.log(`Failed to connect to ${relay.url}`);
|
||||
this.log(e);
|
||||
}
|
||||
}
|
||||
return relay;
|
||||
}
|
||||
|
||||
pruneRelays() {
|
||||
for (const [url, relay] of this.relays.entries()) {
|
||||
const claims = this.getRelayClaims(url).size;
|
||||
if (claims === 0) {
|
||||
relay.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
reconnectRelays() {
|
||||
if (offlineMode.value) return;
|
||||
|
||||
for (const [url, relay] of this.relays.entries()) {
|
||||
const claims = this.getRelayClaims(url).size;
|
||||
if (!relay.okay && claims > 0) {
|
||||
try {
|
||||
relay.open();
|
||||
} catch (e) {
|
||||
this.log(`Failed to connect to ${relay.url}`);
|
||||
this.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// id can be anything
|
||||
addClaim(url: string | URL, id: any) {
|
||||
url = validateRelayURL(url);
|
||||
const key = url.toString();
|
||||
this.getRelayClaims(key).add(id);
|
||||
}
|
||||
removeClaim(url: string | URL, id: any) {
|
||||
url = validateRelayURL(url);
|
||||
const key = url.toString();
|
||||
this.getRelayClaims(key).delete(id);
|
||||
}
|
||||
|
||||
get connectedCount() {
|
||||
let count = 0;
|
||||
for (const [url, relay] of this.relays.entries()) {
|
||||
if (relay.connected) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
const relayPoolService = new RelayPoolService();
|
||||
const relayPoolService = new RelayPool();
|
||||
|
||||
setInterval(() => {
|
||||
if (document.visibilityState === "visible") {
|
||||
|
@ -1,12 +1,8 @@
|
||||
import dayjs from "dayjs";
|
||||
import debug, { Debugger } from "debug";
|
||||
import _throttle from "lodash/throttle";
|
||||
import { Filter } from "nostr-tools";
|
||||
|
||||
import NostrSubscription from "../classes/nostr-subscription";
|
||||
import SuperMap from "../classes/super-map";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import { NostrQuery } from "../types/nostr-query";
|
||||
import { logger } from "../helpers/debug";
|
||||
import { nameOrPubkey } from "./user-metadata";
|
||||
import { getEventCoordinate } from "../helpers/nostr/events";
|
||||
@ -15,9 +11,7 @@ import { localRelay } from "./local-relay";
|
||||
import { relayRequest } from "../helpers/relay";
|
||||
import EventStore from "../classes/event-store";
|
||||
import Subject from "../classes/subject";
|
||||
|
||||
type Pubkey = string;
|
||||
type Relay = string;
|
||||
import BatchKindLoader, { createCoordinate } from "../classes/batch-kind-loader";
|
||||
|
||||
export type RequestOptions = {
|
||||
/** Always request the event from the relays */
|
||||
@ -31,123 +25,14 @@ export type RequestOptions = {
|
||||
export function getHumanReadableCoordinate(kind: number, pubkey: string, d?: string) {
|
||||
return `${kind}:${nameOrPubkey(pubkey)}${d ? ":" + d : ""}`;
|
||||
}
|
||||
export function createCoordinate(kind: number, pubkey: string, d?: string) {
|
||||
return `${kind}:${pubkey}${d ? ":" + d : ""}`;
|
||||
}
|
||||
|
||||
const RELAY_REQUEST_BATCH_TIME = 500;
|
||||
|
||||
/** This class is ued to batch requests to a single relay */
|
||||
class ReplaceableEventRelayLoader {
|
||||
private subscription: NostrSubscription;
|
||||
events = new EventStore();
|
||||
|
||||
private requestNext = new Set<string>();
|
||||
private requested = new Map<string, Date>();
|
||||
|
||||
log: Debugger;
|
||||
|
||||
constructor(relay: string, log?: Debugger) {
|
||||
this.subscription = new NostrSubscription(relay, undefined, `replaceable-event-loader`);
|
||||
|
||||
this.subscription.onEvent.subscribe(this.handleEvent.bind(this));
|
||||
this.subscription.onEOSE.subscribe(this.handleEOSE.bind(this));
|
||||
|
||||
this.log = log || debug("misc");
|
||||
}
|
||||
|
||||
private handleEvent(event: NostrEvent) {
|
||||
const cord = getEventCoordinate(event);
|
||||
|
||||
// remove the cord from the waiting list
|
||||
this.requested.delete(cord);
|
||||
|
||||
const current = this.events.getEvent(cord);
|
||||
if (!current || event.created_at > current.created_at) {
|
||||
this.events.addEvent(event);
|
||||
}
|
||||
}
|
||||
private handleEOSE() {
|
||||
// relays says it has nothing left
|
||||
this.requested.clear();
|
||||
}
|
||||
|
||||
requestEvent(kind: number, pubkey: string, d?: string) {
|
||||
const cord = createCoordinate(kind, pubkey, d);
|
||||
const event = this.events.getEvent(cord);
|
||||
if (!event) {
|
||||
this.requestNext.add(cord);
|
||||
this.updateThrottle();
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
updateThrottle = _throttle(this.update, RELAY_REQUEST_BATCH_TIME);
|
||||
update() {
|
||||
let needsUpdate = false;
|
||||
for (const cord of this.requestNext) {
|
||||
if (!this.requested.has(cord)) {
|
||||
this.requested.set(cord, new Date());
|
||||
needsUpdate = true;
|
||||
}
|
||||
}
|
||||
this.requestNext.clear();
|
||||
|
||||
// prune requests
|
||||
const timeout = dayjs().subtract(1, "minute");
|
||||
for (const [cord, date] of this.requested) {
|
||||
if (dayjs(date).isBefore(timeout)) {
|
||||
this.requested.delete(cord);
|
||||
needsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
// update the subscription
|
||||
if (needsUpdate) {
|
||||
if (this.requested.size > 0) {
|
||||
const filters: Record<number, NostrQuery> = {};
|
||||
|
||||
for (const [cord] of this.requested) {
|
||||
const [kindStr, pubkey, d] = cord.split(":") as [string, string] | [string, string, string];
|
||||
const kind = parseInt(kindStr);
|
||||
filters[kind] = filters[kind] || { kinds: [kind] };
|
||||
|
||||
const arr = (filters[kind].authors = filters[kind].authors || []);
|
||||
arr.push(pubkey);
|
||||
|
||||
if (d) {
|
||||
const arr = (filters[kind]["#d"] = filters[kind]["#d"] || []);
|
||||
arr.push(d);
|
||||
}
|
||||
}
|
||||
|
||||
const query = Array.from(Object.values(filters));
|
||||
|
||||
this.log(
|
||||
`Updating query`,
|
||||
Array.from(Object.keys(filters))
|
||||
.map((kind: string) => `kind ${kind}: ${filters[parseInt(kind)].authors?.length}`)
|
||||
.join(", "),
|
||||
);
|
||||
this.subscription.setQuery(query);
|
||||
|
||||
if (this.subscription.state !== NostrSubscription.OPEN) {
|
||||
this.subscription.open();
|
||||
}
|
||||
} else if (this.subscription.state === NostrSubscription.OPEN) {
|
||||
this.subscription.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const READ_CACHE_BATCH_TIME = 250;
|
||||
const WRITE_CACHE_BATCH_TIME = 250;
|
||||
|
||||
class ReplaceableEventLoaderService {
|
||||
private subjects = new SuperMap<Pubkey, Subject<NostrEvent>>(() => new Subject<NostrEvent>());
|
||||
private loaders = new SuperMap<Relay, ReplaceableEventRelayLoader>((relay) => {
|
||||
const loader = new ReplaceableEventRelayLoader(relay, this.log.extend(relay));
|
||||
class ReplaceableEventsService {
|
||||
private subjects = new SuperMap<string, Subject<NostrEvent>>(() => new Subject<NostrEvent>());
|
||||
private loaders = new SuperMap<string, BatchKindLoader>((relay) => {
|
||||
const loader = new BatchKindLoader(relay, this.log.extend(relay));
|
||||
loader.events.onEvent.subscribe((e) => this.handleEvent(e));
|
||||
return loader;
|
||||
});
|
||||
@ -251,11 +136,11 @@ class ReplaceableEventLoaderService {
|
||||
}
|
||||
|
||||
requestEvent(relays: Iterable<string>, kind: number, pubkey: string, d?: string, opts: RequestOptions = {}) {
|
||||
const cord = createCoordinate(kind, pubkey, d);
|
||||
const sub = this.subjects.get(cord);
|
||||
const key = createCoordinate(kind, pubkey, d);
|
||||
const sub = this.subjects.get(key);
|
||||
|
||||
if (!sub.value) {
|
||||
this.loadFromCache(cord).then((loaded) => {
|
||||
this.loadFromCache(key).then((loaded) => {
|
||||
if (!loaded && !sub.value) this.requestEventFromRelays(relays, kind, pubkey, d);
|
||||
});
|
||||
}
|
||||
@ -268,11 +153,11 @@ class ReplaceableEventLoaderService {
|
||||
}
|
||||
}
|
||||
|
||||
const replaceableEventLoaderService = new ReplaceableEventLoaderService();
|
||||
const replaceableEventsService = new ReplaceableEventsService();
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
//@ts-ignore
|
||||
window.replaceableEventLoaderService = replaceableEventLoaderService;
|
||||
window.replaceableEventsService = replaceableEventsService;
|
||||
}
|
||||
|
||||
export default replaceableEventLoaderService;
|
||||
export default replaceableEventsService;
|
@ -5,7 +5,7 @@ import { DraftNostrEvent } from "../../types/nostr-event";
|
||||
import SuperMap from "../../classes/super-map";
|
||||
import { PersistentSubject } from "../../classes/subject";
|
||||
import { AppSettings, defaultSettings, parseAppSettings } from "./migrations";
|
||||
import replaceableEventLoaderService, { RequestOptions } from "../replaceable-event-requester";
|
||||
import replaceableEventsService, { RequestOptions } from "../replaceable-events";
|
||||
|
||||
export const APP_SETTINGS_KIND = 30078;
|
||||
export const SETTING_EVENT_IDENTIFIER = "nostrudel-settings";
|
||||
@ -19,7 +19,7 @@ class UserAppSettings {
|
||||
}
|
||||
requestAppSettings(pubkey: string, relays: Iterable<string>, opts?: RequestOptions) {
|
||||
const sub = this.parsedSubjects.get(pubkey);
|
||||
const requestSub = replaceableEventLoaderService.requestEvent(
|
||||
const requestSub = replaceableEventsService.requestEvent(
|
||||
relays,
|
||||
APP_SETTINGS_KIND,
|
||||
pubkey,
|
||||
|
@ -4,7 +4,7 @@ import { logger } from "../helpers/debug";
|
||||
import accountService from "./account";
|
||||
import clientRelaysService from "./client-relays";
|
||||
import { offlineMode } from "./offline-mode";
|
||||
import replaceableEventLoaderService from "./replaceable-event-requester";
|
||||
import replaceableEventsService from "./replaceable-events";
|
||||
import userAppSettings from "./settings/user-app-settings";
|
||||
import userMailboxesService from "./user-mailboxes";
|
||||
import userMetadataService from "./user-metadata";
|
||||
@ -15,7 +15,7 @@ function loadContactsList() {
|
||||
const account = accountService.current.value!;
|
||||
|
||||
log("Loading contacts list");
|
||||
replaceableEventLoaderService.requestEvent(
|
||||
replaceableEventsService.requestEvent(
|
||||
[...clientRelaysService.readRelays.value, COMMON_CONTACT_RELAY],
|
||||
kinds.Contacts,
|
||||
account.pubkey,
|
||||
|
@ -3,7 +3,7 @@ import { kinds } from "nostr-tools";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import SuperMap from "../classes/super-map";
|
||||
import Subject from "../classes/subject";
|
||||
import replaceableEventLoaderService, { createCoordinate, RequestOptions } from "./replaceable-event-requester";
|
||||
import replaceableEventsService, { createCoordinate, RequestOptions } from "./replaceable-events";
|
||||
import RelaySet from "../classes/relay-set";
|
||||
import { RelayMode } from "../classes/relay";
|
||||
import { relaysFromContactsEvent } from "../helpers/nostr/contacts";
|
||||
@ -30,17 +30,17 @@ function nip65ToUserMailboxes(event: NostrEvent): UserMailboxes {
|
||||
|
||||
class UserMailboxesService {
|
||||
private subjects = new SuperMap<string, Subject<UserMailboxes>>((pubkey) =>
|
||||
replaceableEventLoaderService.getEvent(kinds.RelayList, pubkey).map(nip65ToUserMailboxes),
|
||||
replaceableEventsService.getEvent(kinds.RelayList, pubkey).map(nip65ToUserMailboxes),
|
||||
);
|
||||
getMailboxes(pubkey: string) {
|
||||
return this.subjects.get(pubkey);
|
||||
}
|
||||
requestMailboxes(pubkey: string, relays: Iterable<string>, opts: RequestOptions = {}) {
|
||||
const sub = this.subjects.get(pubkey);
|
||||
replaceableEventLoaderService.requestEvent(relays, kinds.RelayList, pubkey, undefined, opts);
|
||||
replaceableEventsService.requestEvent(relays, kinds.RelayList, pubkey, undefined, opts);
|
||||
|
||||
// also fetch the relays from the users contacts
|
||||
const contactsSub = replaceableEventLoaderService.requestEvent(relays, kinds.Contacts, pubkey, undefined, opts);
|
||||
const contactsSub = replaceableEventsService.requestEvent(relays, kinds.Contacts, pubkey, undefined, opts);
|
||||
sub.connectWithMapper(contactsSub, (event, next, value) => {
|
||||
// NOTE: only use relays from contact list if the user dose not have a NIP-65 relay list
|
||||
const relays = relaysFromContactsEvent(event);
|
||||
@ -61,12 +61,12 @@ class UserMailboxesService {
|
||||
|
||||
async loadFromCache(pubkey: string) {
|
||||
const sub = this.subjects.get(pubkey);
|
||||
await replaceableEventLoaderService.loadFromCache(createCoordinate(kinds.RelayList, pubkey));
|
||||
await replaceableEventsService.loadFromCache(createCoordinate(kinds.RelayList, pubkey));
|
||||
return sub;
|
||||
}
|
||||
|
||||
receiveEvent(event: NostrEvent) {
|
||||
replaceableEventLoaderService.handleEvent(event);
|
||||
replaceableEventsService.handleEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,18 +4,18 @@ import _throttle from "lodash.throttle";
|
||||
import { Kind0ParsedContent, parseKind0Event } from "../helpers/user-metadata";
|
||||
import SuperMap from "../classes/super-map";
|
||||
import Subject from "../classes/subject";
|
||||
import replaceableEventLoaderService, { RequestOptions } from "./replaceable-event-requester";
|
||||
import replaceableEventsService, { RequestOptions } from "./replaceable-events";
|
||||
|
||||
class UserMetadataService {
|
||||
private metadata = new SuperMap<string, Subject<Kind0ParsedContent>>((pubkey) => {
|
||||
return replaceableEventLoaderService.getEvent(0, pubkey).map(parseKind0Event);
|
||||
return replaceableEventsService.getEvent(0, pubkey).map(parseKind0Event);
|
||||
});
|
||||
getSubject(pubkey: string) {
|
||||
return this.metadata.get(pubkey);
|
||||
}
|
||||
requestMetadata(pubkey: string, relays: Iterable<string>, opts: RequestOptions = {}) {
|
||||
const subject = this.metadata.get(pubkey);
|
||||
replaceableEventLoaderService.requestEvent(relays, kinds.Metadata, pubkey, undefined, opts);
|
||||
replaceableEventsService.requestEvent(relays, kinds.Metadata, pubkey, undefined, opts);
|
||||
return subject;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import _throttle from "lodash.throttle";
|
||||
import { getSearchNames } from "../helpers/user-metadata";
|
||||
import db from "./db";
|
||||
import replaceableEventLoaderService from "./replaceable-event-requester";
|
||||
import replaceableEventsService from "./replaceable-events";
|
||||
import userMetadataService from "./user-metadata";
|
||||
|
||||
const WRITE_USER_SEARCH_BATCH_TIME = 500;
|
||||
@ -25,7 +25,7 @@ const writeSearchData = _throttle(async () => {
|
||||
await transaction.done;
|
||||
}, WRITE_USER_SEARCH_BATCH_TIME);
|
||||
|
||||
replaceableEventLoaderService.events.onEvent.subscribe((event) => {
|
||||
replaceableEventsService.events.onEvent.subscribe((event) => {
|
||||
if (event.kind === 0) {
|
||||
writeSearchQueue.add(event.pubkey);
|
||||
writeSearchData();
|
||||
|
@ -12,25 +12,9 @@ export type Tag = string[] | ETag | PTag | RTag | DTag | ATag | ExpirationTag;
|
||||
export type NostrEvent = Omit<NostrToolsNostrEvent, "tags"> & {
|
||||
tags: Tag[];
|
||||
};
|
||||
export type CountResponse = {
|
||||
count: number;
|
||||
approximate?: boolean;
|
||||
};
|
||||
|
||||
export type DraftNostrEvent = Omit<NostrEvent, "pubkey" | "id" | "sig"> & { pubkey?: string; id?: string };
|
||||
|
||||
export type RawIncomingEvent = ["EVENT", string, NostrEvent];
|
||||
export type RawIncomingNotice = ["NOTICE", string];
|
||||
export type RawIncomingCount = ["COUNT", string, CountResponse];
|
||||
export type RawIncomingEOSE = ["EOSE", string];
|
||||
export type RawIncomingCommandResult = ["OK", string, boolean, string];
|
||||
export type RawIncomingNostrEvent =
|
||||
| RawIncomingEvent
|
||||
| RawIncomingNotice
|
||||
| RawIncomingCount
|
||||
| RawIncomingEOSE
|
||||
| RawIncomingCommandResult;
|
||||
|
||||
export function isETag(tag: Tag): tag is ETag {
|
||||
return tag[0] === "e" && tag[1] !== undefined;
|
||||
}
|
||||
|
@ -1,16 +0,0 @@
|
||||
import { Filter } from "nostr-tools";
|
||||
import { NostrEvent } from "./nostr-event";
|
||||
|
||||
export type NostrOutgoingEvent = ["EVENT", NostrEvent];
|
||||
export type NostrOutgoingRequest = ["REQ", string, ...Filter[]];
|
||||
export type NostrOutgoingCount = ["COUNT", string, ...Filter[]];
|
||||
export type NostrOutgoingClose = ["CLOSE", string];
|
||||
|
||||
export type NostrOutgoingMessage = NostrOutgoingEvent | NostrOutgoingRequest | NostrOutgoingClose | NostrOutgoingCount;
|
||||
|
||||
/** @deprecated use Filter instead */
|
||||
export type NostrQuery = Filter;
|
||||
|
||||
export type NostrRequestFilter = Filter | Filter[];
|
||||
|
||||
export type RelayQueryMap = Record<string, NostrRequestFilter>;
|
8
src/types/nostr-relay.ts
Normal file
8
src/types/nostr-relay.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Filter } from "nostr-tools";
|
||||
|
||||
/** @deprecated use Filter instead */
|
||||
export type NostrQuery = Filter;
|
||||
|
||||
export type NostrRequestFilter = Filter | Filter[];
|
||||
|
||||
export type RelayQueryMap = Record<string, NostrRequestFilter>;
|
@ -9,7 +9,7 @@ import { COMMUNITY_DEFINITION_KIND } from "../../helpers/nostr/communities";
|
||||
import { ErrorBoundary } from "../../components/error-boundary";
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import useSubjects from "../../hooks/use-subjects";
|
||||
import replaceableEventLoaderService from "../../services/replaceable-event-requester";
|
||||
import replaceableEventsService from "../../services/replaceable-events";
|
||||
import { COMMUNITIES_LIST_KIND, getCoordinatesFromList } from "../../helpers/nostr/lists";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { ChevronLeftIcon } from "../../components/icons";
|
||||
@ -20,9 +20,7 @@ import { AddressPointer } from "nostr-tools/lib/types/nip19";
|
||||
export function useUsersJoinedCommunitiesLists(pubkeys: string[], additionalRelays?: Iterable<string>) {
|
||||
const readRelays = useReadRelays(additionalRelays);
|
||||
const communityListsSubjects = useMemo(() => {
|
||||
return pubkeys.map((pubkey) =>
|
||||
replaceableEventLoaderService.requestEvent(readRelays, COMMUNITIES_LIST_KIND, pubkey),
|
||||
);
|
||||
return pubkeys.map((pubkey) => replaceableEventsService.requestEvent(readRelays, COMMUNITIES_LIST_KIND, pubkey));
|
||||
}, [pubkeys]);
|
||||
return useSubjects(communityListsSubjects);
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ import {
|
||||
getCommunityMods,
|
||||
getCommunityName,
|
||||
} from "../../helpers/nostr/communities";
|
||||
import { createCoordinate } from "../../services/replaceable-event-requester";
|
||||
import { createCoordinate } from "../../services/replaceable-events";
|
||||
import { getImageSize } from "../../helpers/image";
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
|
@ -29,7 +29,7 @@ import {
|
||||
import { getSharableEventAddress } from "../../../helpers/nip19";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import useReplaceableEvent from "../../../hooks/use-replaceable-event";
|
||||
import { createCoordinate } from "../../../services/replaceable-event-requester";
|
||||
import { createCoordinate } from "../../../services/replaceable-events";
|
||||
import { useRegisterIntersectionEntity } from "../../../providers/local/intersection-observer";
|
||||
import ListFavoriteButton from "./list-favorite-button";
|
||||
import { getEventUID } from "../../../helpers/nostr/events";
|
||||
|
@ -39,7 +39,7 @@ import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import EventStore from "../../../classes/event-store";
|
||||
import NostrRequest from "../../../classes/nostr-request";
|
||||
import { sortByDate } from "../../../helpers/nostr/events";
|
||||
import { NostrQuery } from "../../../types/nostr-query";
|
||||
import { NostrQuery } from "../../../types/nostr-relay";
|
||||
|
||||
ChartJS.register(
|
||||
ArcElement,
|
||||
|
@ -2,7 +2,7 @@ import { useMemo } from "react";
|
||||
import { Card, CardBody, CardHeader, CardProps, Heading, Image, LinkBox, LinkOverlay } from "@chakra-ui/react";
|
||||
|
||||
import { useReadRelays } from "../../../hooks/use-client-relays";
|
||||
import replaceableEventLoaderService from "../../../services/replaceable-event-requester";
|
||||
import replaceableEventsService from "../../../services/replaceable-events";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import { NoteContents } from "../../../components/note/text-note-contents";
|
||||
import { isATag } from "../../../types/nostr-event";
|
||||
@ -15,7 +15,7 @@ export const STREAMER_CARD_TYPE = 37777;
|
||||
|
||||
function useStreamerCardsCords(pubkey: string, relays: Iterable<string>) {
|
||||
const sub = useMemo(
|
||||
() => replaceableEventLoaderService.requestEvent(relays, STREAMER_CARDS_TYPE, pubkey),
|
||||
() => replaceableEventsService.requestEvent(relays, STREAMER_CARDS_TYPE, pubkey),
|
||||
[pubkey, relays],
|
||||
);
|
||||
const streamerCards = useSubject(sub);
|
||||
|
@ -12,7 +12,7 @@ import PeopleListSelection from "../../components/people-list-selection/people-l
|
||||
import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider";
|
||||
import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status";
|
||||
import useParsedStreams from "../../hooks/use-parsed-streams";
|
||||
import { NostrRequestFilter } from "../../types/nostr-query";
|
||||
import { NostrRequestFilter } from "../../types/nostr-relay";
|
||||
import { useAppTitle } from "../../hooks/use-app-title";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
@ -31,7 +31,7 @@ import StreamSummaryContent from "../components/stream-summary-content";
|
||||
import { ChevronLeftIcon, ExternalLinkIcon } from "../../../components/icons";
|
||||
import useSetColorMode from "../../../hooks/use-set-color-mode";
|
||||
import { CopyIconButton } from "../../../components/copy-icon-button";
|
||||
import replaceableEventLoaderService from "../../../services/replaceable-event-requester";
|
||||
import replaceableEventsService from "../../../services/replaceable-events";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import StreamerCards from "../components/streamer-cards";
|
||||
import { useAppTitle } from "../../../hooks/use-app-title";
|
||||
@ -251,7 +251,7 @@ export default function StreamView() {
|
||||
if (parsed.data.kind !== STREAM_KIND) throw new Error("Invalid stream kind");
|
||||
|
||||
const addrRelays = parsed.data.relays ?? [];
|
||||
return replaceableEventLoaderService.requestEvent(
|
||||
return replaceableEventsService.requestEvent(
|
||||
unique([...readRelays, ...streamRelays, ...addrRelays]),
|
||||
parsed.data.kind,
|
||||
parsed.data.pubkey,
|
||||
|
@ -6,7 +6,7 @@ import { ParsedStream, STREAM_CHAT_MESSAGE_KIND, getATag } from "../../../../hel
|
||||
import useTimelineLoader from "../../../../hooks/use-timeline-loader";
|
||||
import { NostrEvent } from "../../../../types/nostr-event";
|
||||
import useStreamGoal from "../../../../hooks/use-stream-goal";
|
||||
import { NostrQuery } from "../../../../types/nostr-query";
|
||||
import { NostrQuery } from "../../../../types/nostr-relay";
|
||||
import useUserMuteFilter from "../../../../hooks/use-user-mute-filter";
|
||||
import useClientSideMuteFilter from "../../../../hooks/use-client-side-mute-filter";
|
||||
import { useReadRelays } from "../../../../hooks/use-client-relays";
|
||||
|
@ -19,7 +19,7 @@ import { useUsersMetadata } from "../../hooks/use-user-network";
|
||||
import { MUTE_LIST_KIND, getPubkeysFromList, isPubkeyInList } from "../../helpers/nostr/lists";
|
||||
import useUserContactList from "../../hooks/use-user-contact-list";
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import replaceableEventLoaderService from "../../services/replaceable-event-requester";
|
||||
import replaceableEventsService from "../../services/replaceable-events";
|
||||
import useSubjects from "../../hooks/use-subjects";
|
||||
import { useUserMetadata } from "../../hooks/use-user-metadata";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@ -28,7 +28,7 @@ import { ChevronLeftIcon } from "../../components/icons";
|
||||
export function useUsersMuteLists(pubkeys: string[], additionalRelays?: Iterable<string>) {
|
||||
const readRelays = useReadRelays(additionalRelays);
|
||||
const muteListSubjects = useMemo(() => {
|
||||
return pubkeys.map((pubkey) => replaceableEventLoaderService.requestEvent(readRelays, MUTE_LIST_KIND, pubkey));
|
||||
return pubkeys.map((pubkey) => replaceableEventsService.requestEvent(readRelays, MUTE_LIST_KIND, pubkey));
|
||||
}, [pubkeys]);
|
||||
return useSubjects(muteListSubjects);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user