performance improvements to timeline loader

don't verify events from cache relay
This commit is contained in:
hzrd149
2024-04-12 20:30:34 -05:00
parent fba68bc221
commit 71ae6739e2
15 changed files with 163 additions and 138 deletions

View File

@@ -1,5 +1,5 @@
import { Debugger } from "debug"; import { Debugger } from "debug";
import { Filter, NostrEvent, Relay, matchFilters } from "nostr-tools"; import { Filter, NostrEvent, matchFilters } from "nostr-tools";
import _throttle from "lodash.throttle"; import _throttle from "lodash.throttle";
import Subject from "./subject"; import Subject from "./subject";
@@ -9,13 +9,14 @@ import deleteEventService from "../services/delete-events";
import { mergeFilter } from "../helpers/nostr/filter"; import { mergeFilter } from "../helpers/nostr/filter";
import { isATag, isETag } from "../types/nostr-event"; import { isATag, isETag } from "../types/nostr-event";
import relayPoolService from "../services/relay-pool"; import relayPoolService from "../services/relay-pool";
import { SimpleRelay } from "nostr-idb";
const DEFAULT_CHUNK_SIZE = 100; const DEFAULT_CHUNK_SIZE = 100;
export type EventFilter = (event: NostrEvent, store: EventStore) => boolean; export type EventFilter = (event: NostrEvent, store: EventStore) => boolean;
export default class ChunkedRequest { export default class ChunkedRequest {
relay: Relay; relay: SimpleRelay;
filters: Filter[]; filters: Filter[];
chunkSize = DEFAULT_CHUNK_SIZE; chunkSize = DEFAULT_CHUNK_SIZE;
private log: Debugger; private log: Debugger;
@@ -28,7 +29,7 @@ export default class ChunkedRequest {
onChunkFinish = new Subject<number>(); onChunkFinish = new Subject<number>();
constructor(relay: Relay, filters: Filter[], log?: Debugger) { constructor(relay: SimpleRelay, filters: Filter[], log?: Debugger) {
this.relay = relay; this.relay = relay;
this.filters = filters; this.filters = filters;
@@ -47,9 +48,10 @@ export default class ChunkedRequest {
filters = mergeFilter(filters, { until: oldestEvent.created_at - 1 }); filters = mergeFilter(filters, { until: oldestEvent.created_at - 1 });
} }
relayPoolService.addClaim(this.relay, this); relayPoolService.addClaim(this.relay.url, this);
let gotEvents = 0; let gotEvents = 0;
if (filters.length === 0) debugger;
const sub = this.relay.subscribe(filters, { const sub = this.relay.subscribe(filters, {
onevent: (event) => { onevent: (event) => {
this.handleEvent(event); this.handleEvent(event);
@@ -63,7 +65,7 @@ export default class ChunkedRequest {
} else this.log(`Got ${gotEvents} events`); } else this.log(`Got ${gotEvents} events`);
this.onChunkFinish.next(gotEvents); this.onChunkFinish.next(gotEvents);
sub.close(); sub.close();
relayPoolService.removeClaim(this.relay, this); relayPoolService.removeClaim(this.relay.url, this);
}, },
}); });
} }

View File

@@ -1,11 +1,10 @@
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { NostrEvent } from "../types/nostr-event"; import { NostrEvent } from "../types/nostr-event";
import { RelayQueryMap } from "../types/nostr-relay";
import relayPoolService from "../services/relay-pool"; import relayPoolService from "../services/relay-pool";
import { isFilterEqual, isQueryMapEqual } from "../helpers/nostr/filter"; import { isFilterEqual } from "../helpers/nostr/filter";
import ControlledObservable from "./controlled-observable"; import ControlledObservable from "./controlled-observable";
import { Relay, Subscription } from "nostr-tools"; import { Filter, Relay, Subscription } from "nostr-tools";
export default class NostrMultiSubscription { export default class NostrMultiSubscription {
static INIT = "initial"; static INIT = "initial";
@@ -14,7 +13,7 @@ export default class NostrMultiSubscription {
id: string; id: string;
name?: string; name?: string;
queryMap: RelayQueryMap = {}; filters: Filter[] = [];
relays: Relay[] = []; relays: Relay[] = [];
subscriptions = new Map<Relay, Subscription>(); subscriptions = new Map<Relay, Subscription>();
@@ -38,32 +37,40 @@ export default class NostrMultiSubscription {
} }
private handleRemoveRelay(relay: Relay) { private handleRemoveRelay(relay: Relay) {
relayPoolService.removeClaim(relay.url, this); relayPoolService.removeClaim(relay.url, this);
// close subscription
const sub = this.subscriptions.get(relay);
if (sub && !sub.closed) {
sub.close();
this.subscriptions.delete(relay);
}
} }
setQueryMap(queryMap: RelayQueryMap) { setFilters(filters: Filter[]) {
if (isQueryMapEqual(this.queryMap, queryMap)) return; if (isFilterEqual(this.filters, filters)) return;
this.filters = filters;
this.updateSubscriptions();
}
setRelays(relays: Iterable<string>) {
// add and remove relays // add and remove relays
for (const url of Object.keys(queryMap)) { for (const url of relays) {
if (!this.queryMap[url]) { if (!this.relays.some((r) => r.url === url)) {
if (this.relays.some((r) => r.url === url)) continue;
// add relay // add relay
const relay = relayPoolService.requestRelay(url); const relay = relayPoolService.requestRelay(url);
this.relays.push(relay); this.relays.push(relay);
this.handleAddRelay(relay); this.handleAddRelay(relay);
} }
} }
for (const url of Object.keys(this.queryMap)) {
if (!queryMap[url]) { const urlArr = Array.from(relays);
const relay = this.relays.find((r) => r.url === url); for (const relay of this.relays) {
if (!relay) continue; if (!urlArr.includes(relay.url)) {
this.relays = this.relays.filter((r) => r !== relay); this.relays = this.relays.filter((r) => r !== relay);
this.handleRemoveRelay(relay); this.handleRemoveRelay(relay);
} }
} }
this.queryMap = queryMap;
this.updateSubscriptions(); this.updateSubscriptions();
} }
@@ -79,7 +86,7 @@ export default class NostrMultiSubscription {
// else open and update subscriptions // else open and update subscriptions
for (const relay of this.relays) { for (const relay of this.relays) {
const filters = this.queryMap[relay.url]; const filters = this.filters;
let subscription = this.subscriptions.get(relay); let subscription = this.subscriptions.get(relay);
if (!subscription || !isFilterEqual(subscription.filters, filters)) { if (!subscription || !isFilterEqual(subscription.filters, filters)) {
@@ -87,6 +94,7 @@ export default class NostrMultiSubscription {
subscription.filters = filters; subscription.filters = filters;
subscription.fire(); subscription.fire();
} else { } else {
if (filters.length === 0) debugger;
subscription = relay.subscribe(filters, { subscription = relay.subscribe(filters, {
onevent: (event) => this.handleEvent(event), onevent: (event) => this.handleEvent(event),
onclose: () => { onclose: () => {

View File

@@ -1,12 +1,12 @@
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { NostrEvent, Relay } from "nostr-tools"; import { NostrEvent, AbstractRelay } from "nostr-tools";
import relayPoolService from "../services/relay-pool"; import relayPoolService from "../services/relay-pool";
import createDefer from "./deferred"; import createDefer from "./deferred";
import { PersistentSubject } from "./subject"; import { PersistentSubject } from "./subject";
import ControlledObservable from "./controlled-observable"; import ControlledObservable from "./controlled-observable";
type Result = { relay: Relay; success: boolean; message: string }; type Result = { relay: AbstractRelay; success: boolean; message: string };
export default class NostrPublishAction { export default class NostrPublishAction {
id = nanoid(); id = nanoid();
@@ -19,7 +19,7 @@ export default class NostrPublishAction {
onResult = new ControlledObservable<Result>(); onResult = new ControlledObservable<Result>();
onComplete = createDefer<Result[]>(); onComplete = createDefer<Result[]>();
private remaining = new Set<Relay>(); private remaining = new Set<AbstractRelay>();
constructor(label: string, relays: Iterable<string>, event: NostrEvent, timeout: number = 5000) { constructor(label: string, relays: Iterable<string>, event: NostrEvent, timeout: number = 5000) {
this.label = label; this.label = label;
@@ -41,7 +41,7 @@ export default class NostrPublishAction {
setTimeout(this.handleTimeout.bind(this), timeout); setTimeout(this.handleTimeout.bind(this), timeout);
} }
private handleResult(id: string, success: boolean, message: string, relay: Relay) { private handleResult(id: string, success: boolean, message: string, relay: AbstractRelay) {
const result: Result = { relay, success, message }; const result: Result = { relay, success, message };
this.results.next([...this.results.value, result]); this.results.next([...this.results.value, result]);
this.onResult.next(result); this.onResult.next(result);

View File

@@ -1,12 +1,12 @@
import { Relay } from "nostr-tools"; import { AbstractRelay, verifyEvent } from "nostr-tools";
import { logger } from "../helpers/debug"; import { logger } from "../helpers/debug";
import { validateRelayURL } from "../helpers/relay"; import { validateRelayURL } from "../helpers/relay";
import { offlineMode } from "../services/offline-mode"; import { offlineMode } from "../services/offline-mode";
import Subject from "./subject"; import Subject from "./subject";
export default class RelayPool { export default class RelayPool {
relays = new Map<string, Relay>(); relays = new Map<string, AbstractRelay>();
onRelayCreated = new Subject<Relay>(); onRelayCreated = new Subject<AbstractRelay>();
relayClaims = new Map<string, Set<any>>(); relayClaims = new Map<string, Set<any>>();
@@ -28,12 +28,12 @@ export default class RelayPool {
url = validateRelayURL(url); url = validateRelayURL(url);
const key = url.toString(); const key = url.toString();
if (!this.relays.has(key)) { if (!this.relays.has(key)) {
const newRelay = new Relay(key); const newRelay = new AbstractRelay(key, { verifyEvent });
this.relays.set(key, newRelay); this.relays.set(key, newRelay);
this.onRelayCreated.next(newRelay); this.onRelayCreated.next(newRelay);
} }
const relay = this.relays.get(key) as Relay; const relay = this.relays.get(key) as AbstractRelay;
if (connect && !relay.connected) { if (connect && !relay.connected) {
try { try {
relay.connect(); relay.connect();
@@ -69,13 +69,17 @@ export default class RelayPool {
} }
} }
addClaim(relay: string | URL | Relay, id: any) { addClaim(relay: string | URL, id: any) {
const key = relay instanceof Relay ? relay.url : validateRelayURL(relay).toString(); try {
this.getRelayClaims(key).add(id); const key = validateRelayURL(relay).toString();
this.getRelayClaims(key).add(id);
} catch (error) {}
} }
removeClaim(relay: string | URL | Relay, id: any) { removeClaim(relay: string | URL, id: any) {
const key = relay instanceof Relay ? relay.url : validateRelayURL(relay).toString(); try {
this.getRelayClaims(key).delete(id); const key = validateRelayURL(relay).toString();
this.getRelayClaims(key).delete(id);
} catch (error) {}
} }
get connectedCount() { get connectedCount() {

View File

@@ -3,16 +3,14 @@ import { Debugger } from "debug";
import { Filter, NostrEvent } from "nostr-tools"; import { Filter, NostrEvent } from "nostr-tools";
import _throttle from "lodash.throttle"; import _throttle from "lodash.throttle";
import { RelayQueryMap } from "../types/nostr-relay";
import NostrMultiSubscription from "./nostr-multi-subscription"; import NostrMultiSubscription from "./nostr-multi-subscription";
import { PersistentSubject } from "./subject"; import { PersistentSubject } from "./subject";
import { logger } from "../helpers/debug"; import { logger } from "../helpers/debug";
import EventStore from "./event-store"; import EventStore from "./event-store";
import { isReplaceable } from "../helpers/nostr/event"; import { isReplaceable } from "../helpers/nostr/event";
import replaceableEventsService from "../services/replaceable-events"; import replaceableEventsService from "../services/replaceable-events";
import { mergeFilter, isFilterEqual, isQueryMapEqual, mapQueryMap, stringifyFilter } from "../helpers/nostr/filter"; import { mergeFilter, isFilterEqual } from "../helpers/nostr/filter";
import { localRelay } from "../services/local-relay"; import { localRelay } from "../services/local-relay";
import { relayRequest } from "../helpers/relay";
import SuperMap from "./super-map"; import SuperMap from "./super-map";
import ChunkedRequest from "./chunked-request"; import ChunkedRequest from "./chunked-request";
import relayPoolService from "../services/relay-pool"; import relayPoolService from "../services/relay-pool";
@@ -23,7 +21,8 @@ export type EventFilter = (event: NostrEvent, store: EventStore) => boolean;
export default class TimelineLoader { export default class TimelineLoader {
cursor = dayjs().unix(); cursor = dayjs().unix();
queryMap: RelayQueryMap = {}; filters: Filter[] = [];
relays: string[] = [];
events: EventStore; events: EventStore;
timeline = new PersistentSubject<NostrEvent[]>([]); timeline = new PersistentSubject<NostrEvent[]>([]);
@@ -37,6 +36,7 @@ export default class TimelineLoader {
private log: Debugger; private log: Debugger;
private subscription: NostrMultiSubscription; private subscription: NostrMultiSubscription;
private cacheChunkLoader: ChunkedRequest | null = null;
private chunkLoaders = new Map<string, ChunkedRequest>(); private chunkLoaders = new Map<string, ChunkedRequest>();
constructor(name: string) { constructor(name: string) {
@@ -87,65 +87,62 @@ export default class TimelineLoader {
this.chunkLoaderSubs.delete(loader); this.chunkLoaderSubs.delete(loader);
} }
private loadQueriesFromCache(queryMap: RelayQueryMap) { setFilters(filters: Filter[]) {
const queries: Record<string, Filter[]> = {}; if (isFilterEqual(this.filters, filters)) return;
for (const [url, filters] of Object.entries(queryMap)) {
const key = stringifyFilter(filters); this.log("Set filters", filters);
if (!queries[key]) queries[key] = Array.isArray(filters) ? filters : [filters];
// recreate all chunk loaders
for (const url of this.relays) {
const loader = this.chunkLoaders.get(url);
if (loader) {
this.disconnectToChunkLoader(loader);
this.chunkLoaders.delete(url);
}
const chunkLoader = new ChunkedRequest(relayPoolService.requestRelay(url), filters, this.log.extend(url));
this.chunkLoaders.set(url, chunkLoader);
this.connectToChunkLoader(chunkLoader);
} }
for (const filters of Object.values(queries)) { // set filters
relayRequest(localRelay, filters).then((events) => { this.filters = filters;
for (const e of events) this.handleEvent(e, false);
}); // recreate cache chunk loader
} if (this.cacheChunkLoader) this.disconnectToChunkLoader(this.cacheChunkLoader);
this.cacheChunkLoader = new ChunkedRequest(localRelay, this.filters, this.log.extend("local-relay"));
this.connectToChunkLoader(this.cacheChunkLoader);
// update the live subscription query map and add limit
this.subscription.setFilters(mergeFilter(filters, { limit: BLOCK_SIZE / 2 }));
} }
setQueryMap(queryMap: RelayQueryMap) { setRelays(relays: Iterable<string>) {
if (isQueryMapEqual(this.queryMap, queryMap)) return; this.relays = Array.from(relays);
this.log("set query map", queryMap); // remove chunk loaders
for (const url of relays) {
// remove relays const loader = this.chunkLoaders.get(url);
for (const relay of Object.keys(this.queryMap)) {
const loader = this.chunkLoaders.get(relay);
if (!loader) continue; if (!loader) continue;
if (!queryMap[relay]) { if (!this.relays.includes(url)) {
this.disconnectToChunkLoader(loader); this.disconnectToChunkLoader(loader);
this.chunkLoaders.delete(relay); this.chunkLoaders.delete(url);
} }
} }
for (const [relay, filter] of Object.entries(queryMap)) { // create chunk loaders only if filters are set
// remove outdated loaders if (this.filters.length > 0) {
if (this.queryMap[relay] && !isFilterEqual(this.queryMap[relay], filter)) { for (const url of relays) {
const old = this.chunkLoaders.get(relay)!; if (!this.chunkLoaders.has(url)) {
this.disconnectToChunkLoader(old); const loader = new ChunkedRequest(relayPoolService.requestRelay(url), this.filters, this.log.extend(url));
this.chunkLoaders.delete(relay); this.chunkLoaders.set(url, loader);
} this.connectToChunkLoader(loader);
}
if (!this.chunkLoaders.has(relay)) {
const loader = new ChunkedRequest(
relayPoolService.requestRelay(relay),
Array.isArray(filter) ? filter : [filter],
this.log.extend(relay),
);
this.chunkLoaders.set(relay, loader);
this.connectToChunkLoader(loader);
} }
} }
this.queryMap = queryMap; // update live subscription
this.subscription.setRelays(relays);
// load all filters from cache relay
this.loadQueriesFromCache(queryMap);
// update the subscription query map and add limit
this.subscription.setQueryMap(
mapQueryMap(this.queryMap, (filter) => mergeFilter(filter, { limit: BLOCK_SIZE / 2 })),
);
this.triggerChunkLoad();
} }
setEventFilter(filter?: EventFilter) { setEventFilter(filter?: EventFilter) {
@@ -157,30 +154,48 @@ export default class TimelineLoader {
this.triggerChunkLoad(); this.triggerChunkLoad();
} }
private getAllLoaders() {
return this.cacheChunkLoader
? [...this.chunkLoaders.values(), this.cacheChunkLoader]
: Array.from(this.chunkLoaders.values());
}
triggerChunkLoad() { triggerChunkLoad() {
let triggeredLoad = false; let triggeredLoad = false;
for (const [relay, loader] of this.chunkLoaders) { const loaders = this.getAllLoaders();
for (const loader of loaders) {
// skip loader if its already loading or complete
if (loader.complete || loader.loading) continue; if (loader.complete || loader.loading) continue;
const event = loader.getLastEvent(this.loadNextBlockBuffer, this.eventFilter); const event = loader.getLastEvent(this.loadNextBlockBuffer, this.eventFilter);
if (!event || event.created_at >= this.cursor) { if (!event || event.created_at >= this.cursor) {
loader.loadNextChunk(); loader.loadNextChunk();
triggeredLoad = true; triggeredLoad = true;
} }
} }
if (triggeredLoad) this.updateLoading(); if (triggeredLoad) this.updateLoading();
} }
loadAllNextChunks() { loadAllNextChunks() {
let triggeredLoad = false; let triggeredLoad = false;
for (const [relay, loader] of this.chunkLoaders) { const loaders = this.getAllLoaders();
for (const loader of loaders) {
// skip loader if its already loading or complete
if (loader.complete || loader.loading) continue; if (loader.complete || loader.loading) continue;
loader.loadNextChunk(); loader.loadNextChunk();
triggeredLoad = true; triggeredLoad = true;
} }
if (triggeredLoad) this.updateLoading(); if (triggeredLoad) this.updateLoading();
} }
private updateLoading() { private updateLoading() {
for (const [relay, loader] of this.chunkLoaders) { const loaders = this.getAllLoaders();
for (const loader of loaders) {
if (loader.loading) { if (loader.loading) {
if (!this.loading.value) { if (!this.loading.value) {
this.loading.next(true); this.loading.next(true);
@@ -191,7 +206,9 @@ export default class TimelineLoader {
if (this.loading.value) this.loading.next(false); if (this.loading.value) this.loading.next(false);
} }
private updateComplete() { private updateComplete() {
for (const [relay, loader] of this.chunkLoaders) { const loaders = this.getAllLoaders();
for (const loader of loaders) {
if (!loader.complete) { if (!loader.complete) {
this.complete.next(false); this.complete.next(false);
return; return;
@@ -213,8 +230,10 @@ export default class TimelineLoader {
} }
reset() { reset() {
this.cursor = dayjs().unix(); this.cursor = dayjs().unix();
for (const [_, loader] of this.chunkLoaders) this.disconnectToChunkLoader(loader); const loaders = this.getAllLoaders();
for (const loader of loaders) this.disconnectToChunkLoader(loader);
this.chunkLoaders.clear(); this.chunkLoaders.clear();
this.cacheChunkLoader = null;
this.forgetEvents(); this.forgetEvents();
} }
@@ -222,8 +241,10 @@ export default class TimelineLoader {
cleanup() { cleanup() {
this.close(); this.close();
for (const [_, loader] of this.chunkLoaders) this.disconnectToChunkLoader(loader); const loaders = this.getAllLoaders();
for (const loader of loaders) this.disconnectToChunkLoader(loader);
this.chunkLoaders.clear(); this.chunkLoaders.clear();
this.cacheChunkLoader = null;
this.events.cleanup(); this.events.cleanup();
} }

View File

@@ -20,7 +20,7 @@ export function renderWavlakeUrl(match: URL) {
frameBorder="0" frameBorder="0"
title="Wavlake Embed" title="Wavlake Embed"
src={embedUrl.toString()} src={embedUrl.toString()}
style={{ width: "100%", height: 354, maxWidth: 573, ...setZIndex }} style={{ width: "100%", height: 400, maxWidth: 600, ...setZIndex }}
></iframe> ></iframe>
); );
} }

View File

@@ -2,9 +2,9 @@ import { Badge, useForceUpdate } from "@chakra-ui/react";
import { useInterval } from "react-use"; import { useInterval } from "react-use";
import relayPoolService from "../services/relay-pool"; import relayPoolService from "../services/relay-pool";
import { Relay } from "nostr-tools"; import { AbstractRelay } from "nostr-tools";
const getStatusText = (relay: Relay) => { const getStatusText = (relay: AbstractRelay) => {
// if (relay.connecting) return "Connecting..."; // if (relay.connecting) return "Connecting...";
if (relay.connected) return "Connected"; if (relay.connected) return "Connected";
// if (relay.closing) return "Disconnecting..."; // if (relay.closing) return "Disconnecting...";
@@ -12,7 +12,7 @@ const getStatusText = (relay: Relay) => {
return "Disconnected"; return "Disconnected";
// return "Unused"; // return "Unused";
}; };
const getStatusColor = (relay: Relay) => { const getStatusColor = (relay: AbstractRelay) => {
// if (relay.connecting) return "yellow"; // if (relay.connecting) return "yellow";
if (relay.connected) return "green"; if (relay.connected) return "green";
// if (relay.closing) return "yellow"; // if (relay.closing) return "yellow";

View File

@@ -77,7 +77,7 @@ function EventRow({
export default function TimelineHealth({ timeline }: { timeline: TimelineLoader }) { export default function TimelineHealth({ timeline }: { timeline: TimelineLoader }) {
const events = useSubject(timeline.timeline); const events = useSubject(timeline.timeline);
const relays = Array.from(Object.keys(timeline.queryMap)); const relays = Array.from(Object.keys(timeline.relays));
return ( return (
<> <>

View File

@@ -1,7 +1,5 @@
import stringify from "json-stringify-deterministic"; import stringify from "json-stringify-deterministic";
import { RelayQueryMap } from "../../types/nostr-relay";
import { Filter } from "nostr-tools"; import { Filter } from "nostr-tools";
import { safeRelayUrls } from "../relay";
export function mergeFilter(filter: Filter, query: Filter): Filter; export function mergeFilter(filter: Filter, query: Filter): Filter;
export function mergeFilter(filter: Filter[], query: Filter): Filter[]; export function mergeFilter(filter: Filter[], query: Filter): Filter[];
@@ -18,19 +16,3 @@ export function stringifyFilter(filter: Filter | Filter[]) {
export function isFilterEqual(a: Filter | Filter[], b: Filter | Filter[]) { export function isFilterEqual(a: Filter | Filter[], b: Filter | Filter[]) {
return stringifyFilter(a) === stringifyFilter(b); return stringifyFilter(a) === stringifyFilter(b);
} }
export function isQueryMapEqual(a: RelayQueryMap, b: RelayQueryMap) {
return stringify(a) === stringify(b);
}
export function mapQueryMap(queryMap: RelayQueryMap, fn: (filters: Filter[]) => Filter[]) {
const newMap: RelayQueryMap = {};
for (const [relay, filters] of Object.entries(queryMap)) newMap[relay] = fn(filters);
return newMap;
}
export function createSimpleQueryMap(relays: Iterable<string>, filters: Filter | Filter[]) {
const map: RelayQueryMap = {};
for (const relay of safeRelayUrls(relays)) map[relay] = Array.isArray(filters) ? filters : [filters];
return map;
}

View File

@@ -1,11 +1,9 @@
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import { useUnmount } from "react-use"; import { useUnmount } from "react-use";
import { NostrEvent } from "nostr-tools"; import { Filter, NostrEvent } from "nostr-tools";
import { NostrRequestFilter } from "../types/nostr-relay";
import timelineCacheService from "../services/timeline-cache"; import timelineCacheService from "../services/timeline-cache";
import { EventFilter } from "../classes/timeline-loader"; import { EventFilter } from "../classes/timeline-loader";
import { createSimpleQueryMap } from "../helpers/nostr/filter";
type Options = { type Options = {
/** @deprecated */ /** @deprecated */
@@ -18,17 +16,23 @@ type Options = {
export default function useTimelineLoader( export default function useTimelineLoader(
key: string, key: string,
relays: Iterable<string>, relays: Iterable<string>,
query: NostrRequestFilter | undefined, filters: Filter | Filter[] | undefined,
opts?: Options, opts?: Options,
) { ) {
const timeline = useMemo(() => timelineCacheService.createTimeline(key), [key]); const timeline = useMemo(() => timelineCacheService.createTimeline(key), [key]);
useEffect(() => { useEffect(() => {
if (query) { timeline.setRelays(relays);
timeline.setQueryMap(createSimpleQueryMap(relays, query)); timeline.triggerChunkLoad();
}, [Array.from(relays).join("|")]);
useEffect(() => {
if (filters) {
timeline.setFilters(Array.isArray(filters) ? filters : [filters]);
timeline.open(); timeline.open();
timeline.triggerChunkLoad();
} else timeline.close(); } else timeline.close();
}, [timeline, JSON.stringify(query), Array.from(relays).join("|")]); }, [timeline, JSON.stringify(filters)]);
useEffect(() => { useEffect(() => {
timeline.setEventFilter(opts?.eventFilter); timeline.setEventFilter(opts?.eventFilter);

View File

@@ -1,4 +1,4 @@
import { Relay } from "nostr-tools"; import { AbstractRelay } from "nostr-tools";
import { PersistentSubject } from "../classes/subject"; import { PersistentSubject } from "../classes/subject";
import { getEventUID } from "../helpers/nostr/event"; import { getEventUID } from "../helpers/nostr/event";
import { NostrEvent } from "../types/nostr-event"; import { NostrEvent } from "../types/nostr-event";
@@ -22,7 +22,7 @@ function addRelay(id: string, relay: string) {
} }
} }
export function handleEventFromRelay(relay: Relay, event: NostrEvent) { export function handleEventFromRelay(relay: AbstractRelay, event: NostrEvent) {
const uid = getEventUID(event); const uid = getEventUID(event);
addRelay(uid, relay.url); addRelay(uid, relay.url);

View File

@@ -1,9 +1,13 @@
import { CacheRelay, openDB } from "nostr-idb"; import { CacheRelay, openDB } from "nostr-idb";
import { Relay } from "nostr-tools"; import { AbstractRelay, NostrEvent, VerifiedEvent, verifiedSymbol } from "nostr-tools";
import { logger } from "../helpers/debug"; import { logger } from "../helpers/debug";
import { safeRelayUrl } from "../helpers/relay"; import { safeRelayUrl } from "../helpers/relay";
import WasmRelay from "./wasm-relay"; import WasmRelay from "./wasm-relay";
function fakeVerify(event: NostrEvent): event is VerifiedEvent {
return (event[verifiedSymbol] = true);
}
// save the local relay from query params to localStorage // save the local relay from query params to localStorage
const params = new URLSearchParams(location.search); const params = new URLSearchParams(location.search);
const paramRelay = params.get("localRelay"); const paramRelay = params.get("localRelay");
@@ -17,7 +21,7 @@ if (paramRelay) {
export const NOSTR_RELAY_TRAY_URL = "ws://localhost:4869/"; export const NOSTR_RELAY_TRAY_URL = "ws://localhost:4869/";
export async function checkNostrRelayTray() { export async function checkNostrRelayTray() {
return new Promise((res) => { return new Promise((res) => {
const test = new Relay(NOSTR_RELAY_TRAY_URL); const test = new AbstractRelay(NOSTR_RELAY_TRAY_URL, { verifyEvent: fakeVerify });
test test
.connect() .connect()
.then(() => { .then(() => {
@@ -44,13 +48,15 @@ async function createRelay() {
} else if (localRelayURL.startsWith("nostr-idb://")) { } else if (localRelayURL.startsWith("nostr-idb://")) {
return createInternalRelay(); return createInternalRelay();
} else if (safeRelayUrl(localRelayURL)) { } else if (safeRelayUrl(localRelayURL)) {
return new Relay(safeRelayUrl(localRelayURL)!); return new AbstractRelay(safeRelayUrl(localRelayURL)!, { verifyEvent: fakeVerify });
} }
} else if (window.satellite) { } else if (window.satellite) {
return new Relay(await window.satellite.getLocalRelay()); return new AbstractRelay(await window.satellite.getLocalRelay(), { verifyEvent: fakeVerify });
} else if (window.CACHE_RELAY_ENABLED) { } else if (window.CACHE_RELAY_ENABLED) {
const protocol = location.protocol === "https:" ? "wss:" : "ws:"; const protocol = location.protocol === "https:" ? "wss:" : "ws:";
return new Relay(new URL(protocol + location.host + "/local-relay").toString()); return new AbstractRelay(new URL(protocol + location.host + "/local-relay").toString(), {
verifyEvent: fakeVerify,
});
} }
return createInternalRelay(); return createInternalRelay();
} }

View File

@@ -5,7 +5,6 @@ import { bytesToHex, hexToBytes } from "@noble/hashes/utils";
import NostrMultiSubscription from "../classes/nostr-multi-subscription"; import NostrMultiSubscription from "../classes/nostr-multi-subscription";
import { getPubkeyFromDecodeResult, isHexKey, normalizeToHexPubkey } from "../helpers/nip19"; import { getPubkeyFromDecodeResult, isHexKey, normalizeToHexPubkey } from "../helpers/nip19";
import { createSimpleQueryMap } from "../helpers/nostr/filter";
import { logger } from "../helpers/debug"; import { logger } from "../helpers/debug";
import { DraftNostrEvent, NostrEvent, isPTag } from "../types/nostr-event"; import { DraftNostrEvent, NostrEvent, isPTag } from "../types/nostr-event";
import createDefer, { Deferred } from "../classes/deferred"; import createDefer, { Deferred } from "../classes/deferred";
@@ -84,12 +83,13 @@ export class NostrConnectClient {
this.publicKey = getPublicKey(hexToBytes(this.secretKey)); this.publicKey = getPublicKey(hexToBytes(this.secretKey));
this.sub.onEvent.subscribe((e) => this.handleEvent(e)); this.sub.onEvent.subscribe((e) => this.handleEvent(e));
this.sub.setQueryMap( this.sub.setRelays(this.relays);
createSimpleQueryMap(this.relays, { this.sub.setFilters([
{
kinds: [kinds.NostrConnect, 24134], kinds: [kinds.NostrConnect, 24134],
"#p": [this.publicKey], "#p": [this.publicKey],
}), },
); ]);
} }
async open() { async open() {

View File

@@ -4,5 +4,3 @@ import { Filter } from "nostr-tools";
export type NostrQuery = Filter; export type NostrQuery = Filter;
export type NostrRequestFilter = Filter | Filter[]; export type NostrRequestFilter = Filter | Filter[];
export type RelayQueryMap = Record<string, Filter[]>;

View File

@@ -15,7 +15,7 @@ import {
Text, Text,
useDisclosure, useDisclosure,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { NostrEvent, Relay, Subscription } from "nostr-tools"; import { AbstractRelay, NostrEvent, Relay, Subscription } from "nostr-tools";
import { useLocalStorage } from "react-use"; import { useLocalStorage } from "react-use";
import { Subscription as IDBSubscription, CacheRelay } from "nostr-idb"; import { Subscription as IDBSubscription, CacheRelay } from "nostr-idb";
import _throttle from "lodash.throttle"; import _throttle from "lodash.throttle";
@@ -86,7 +86,7 @@ export default function EventConsoleView() {
if (sub) sub.close(); if (sub) sub.close();
let r: Relay | CacheRelay | WasmRelay = localRelay; let r: Relay | AbstractRelay | CacheRelay | WasmRelay = localRelay;
if (queryRelay.isOpen) { if (queryRelay.isOpen) {
const url = validateRelayURL(relayURL); const url = validateRelayURL(relayURL);
if (!relay || relay.url !== url.toString()) { if (!relay || relay.url !== url.toString()) {