mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-09-26 19:47:25 +02:00
fix infinite loop with multi-subscription
This commit is contained in:
@@ -75,8 +75,8 @@
|
||||
"react-diff-viewer-continued": "^3.4.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-error-boundary": "^4.0.11",
|
||||
"react-force-graph-2d": "^1.25.1",
|
||||
"react-force-graph-3d": "^1.23.1",
|
||||
"react-force-graph-2d": "^1.25.4",
|
||||
"react-force-graph-3d": "^1.24.2",
|
||||
"react-hook-form": "^7.45.4",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-mosaic-component": "^6.1.0",
|
||||
@@ -90,8 +90,8 @@
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remark-wiki-link": "^2.0.1",
|
||||
"three": "^0.160.0",
|
||||
"three-spritetext": "^1.8.1",
|
||||
"three-stdlib": "^2.29.4",
|
||||
"three-spritetext": "^1.8.2",
|
||||
"three-stdlib": "^2.29.11",
|
||||
"webln": "^0.3.2",
|
||||
"workbox-core": "7.0.0",
|
||||
"workbox-precaching": "7.0.0",
|
||||
|
@@ -33,6 +33,7 @@ export default class ChunkedRequest {
|
||||
/** set to true when the next chunk produces 0 events */
|
||||
complete = false;
|
||||
|
||||
private lastChunkIdx = 0;
|
||||
onChunkFinish = new Subject<number>();
|
||||
|
||||
constructor(relay: SimpleRelay | AbstractRelay, filters: Filter[], log?: Debugger) {
|
||||
@@ -72,6 +73,8 @@ export default class ChunkedRequest {
|
||||
this.process.active = true;
|
||||
await new Promise<number>((res) => {
|
||||
const sub = this.relay.subscribe(filters, {
|
||||
// @ts-expect-error
|
||||
id: this.id + "-" + this.lastChunkIdx++,
|
||||
onevent: (event) => {
|
||||
this.handleEvent(event);
|
||||
gotEvents++;
|
||||
|
@@ -23,7 +23,10 @@ export default class MultiSubscription {
|
||||
|
||||
relays = new Set<AbstractRelay>();
|
||||
subscriptions = new Map<AbstractRelay, PersistentSubscription>();
|
||||
|
||||
useCache = true;
|
||||
cacheSubscription: PersistentSubscription | null = null;
|
||||
onCacheEvent = new ControlledObservable<NostrEvent>();
|
||||
|
||||
state = MultiSubscription.CLOSED;
|
||||
onEvent = new ControlledObservable<NostrEvent>();
|
||||
@@ -38,9 +41,12 @@ export default class MultiSubscription {
|
||||
|
||||
processManager.registerProcess(this.process);
|
||||
}
|
||||
private handleEvent(event: NostrEvent) {
|
||||
private handleEvent(event: NostrEvent, fromCache = false) {
|
||||
if (this.seenEvents.has(event.id)) return;
|
||||
this.onEvent.next(event);
|
||||
|
||||
if (fromCache) this.onCacheEvent.next(event);
|
||||
else this.onEvent.next(event);
|
||||
|
||||
this.seenEvents.add(event.id);
|
||||
}
|
||||
|
||||
@@ -102,18 +108,22 @@ export default class MultiSubscription {
|
||||
}
|
||||
}
|
||||
|
||||
// create cache sub if it does not exist
|
||||
if (!this.cacheSubscription && localRelay) {
|
||||
this.cacheSubscription = new PersistentSubscription(localRelay as AbstractRelay, {
|
||||
onevent: (event) => this.handleEvent(event),
|
||||
});
|
||||
this.process.addChild(this.cacheSubscription.process);
|
||||
}
|
||||
if (this.useCache) {
|
||||
// create cache sub if it does not exist
|
||||
if (!this.cacheSubscription && localRelay) {
|
||||
this.cacheSubscription = new PersistentSubscription(localRelay as AbstractRelay, {
|
||||
onevent: (event) => this.handleEvent(event, true),
|
||||
});
|
||||
this.process.addChild(this.cacheSubscription.process);
|
||||
}
|
||||
|
||||
// update cache sub filters if they changed
|
||||
if (this.cacheSubscription && !isFilterEqual(this.cacheSubscription.filters, this.filters)) {
|
||||
this.cacheSubscription.filters = this.filters;
|
||||
this.cacheSubscription.update();
|
||||
// update cache sub filters if they changed
|
||||
if (this.cacheSubscription && !isFilterEqual(this.cacheSubscription.filters, this.filters)) {
|
||||
this.cacheSubscription.filters = this.filters;
|
||||
this.cacheSubscription.update();
|
||||
}
|
||||
} else if (this.cacheSubscription?.closed === false) {
|
||||
this.cacheSubscription.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -55,6 +55,7 @@ export default class TimelineLoader {
|
||||
|
||||
this.subscription = new MultiSubscription(name);
|
||||
this.subscription.onEvent.subscribe(this.handleEvent.bind(this));
|
||||
this.subscription.onCacheEvent.subscribe((event) => this.handleEvent(event, true));
|
||||
this.process.addChild(this.subscription.process);
|
||||
|
||||
// update the timeline when there are new events
|
||||
@@ -72,12 +73,16 @@ export default class TimelineLoader {
|
||||
this.timeline.next(this.events.getSortedEvents().filter((e) => filter(e, this.events)));
|
||||
} else this.timeline.next(this.events.getSortedEvents());
|
||||
}
|
||||
private handleEvent(event: NostrEvent, cache = true) {
|
||||
|
||||
private seenInCache = new Set<string>();
|
||||
private handleEvent(event: NostrEvent, fromCache = false) {
|
||||
// if this is a replaceable event, mirror it over to the replaceable event service
|
||||
if (isReplaceable(event.kind)) replaceableEventsService.handleEvent(event);
|
||||
|
||||
this.events.addEvent(event);
|
||||
if (cache && localRelay) localRelay.publish(event);
|
||||
if (!fromCache && localRelay && !this.seenInCache.has(event.id)) localRelay.publish(event);
|
||||
|
||||
if (fromCache) this.seenInCache.add(event.id);
|
||||
}
|
||||
private handleChunkFinished() {
|
||||
this.updateLoading();
|
||||
|
@@ -26,8 +26,6 @@ export function getHumanReadableCoordinate(kind: number, pubkey: string, d?: str
|
||||
return `${kind}:${truncateId(pubkey)}${d ? ":" + d : ""}`;
|
||||
}
|
||||
|
||||
const WRITE_CACHE_BATCH_TIME = 250;
|
||||
|
||||
class ReplaceableEventsService {
|
||||
process: Process;
|
||||
|
||||
@@ -53,13 +51,14 @@ class ReplaceableEventsService {
|
||||
|
||||
if (localRelay) {
|
||||
this.cacheLoader = new BatchKindLoader(localRelay as AbstractRelay, this.log.extend("cache-relay"));
|
||||
this.cacheLoader.events.onEvent.subscribe((e) => this.handleEvent(e, false));
|
||||
this.cacheLoader.events.onEvent.subscribe((e) => this.handleEvent(e, true));
|
||||
this.process.addChild(this.cacheLoader.process);
|
||||
}
|
||||
}
|
||||
|
||||
handleEvent(event: NostrEvent, saveToCache = true) {
|
||||
if (!alwaysVerify(event)) return;
|
||||
private seenInCache = new Set<string>();
|
||||
handleEvent(event: NostrEvent, fromCache = false) {
|
||||
if (!fromCache && !alwaysVerify(event)) return;
|
||||
const cord = getEventCoordinate(event);
|
||||
|
||||
const subject = this.subjects.get(cord);
|
||||
@@ -67,30 +66,17 @@ class ReplaceableEventsService {
|
||||
if (!current || event.created_at > current.created_at) {
|
||||
subject.next(event);
|
||||
this.events.addEvent(event);
|
||||
if (saveToCache) this.saveToCache(cord, event);
|
||||
|
||||
if (!fromCache && localRelay && !this.seenInCache.has(event.id)) localRelay.publish(event);
|
||||
}
|
||||
|
||||
if (fromCache) this.seenInCache.add(event.id);
|
||||
}
|
||||
|
||||
getEvent(kind: number, pubkey: string, d?: string) {
|
||||
return this.subjects.get(createCoordinate(kind, pubkey, d));
|
||||
}
|
||||
|
||||
private writeCacheQueue = new Map<string, NostrEvent>();
|
||||
private writeToCacheThrottle = _throttle(this.writeToCache, WRITE_CACHE_BATCH_TIME);
|
||||
private async writeToCache() {
|
||||
if (this.writeCacheQueue.size === 0) return;
|
||||
|
||||
if (localRelay) {
|
||||
this.log(`Sending ${this.writeCacheQueue.size} events to cache relay`);
|
||||
for (const [_, event] of this.writeCacheQueue) localRelay.publish(event);
|
||||
}
|
||||
this.writeCacheQueue.clear();
|
||||
}
|
||||
private async saveToCache(cord: string, event: NostrEvent) {
|
||||
this.writeCacheQueue.set(cord, event);
|
||||
this.writeToCacheThrottle();
|
||||
}
|
||||
|
||||
private requestEventFromRelays(
|
||||
urls: Iterable<string | URL | AbstractRelay>,
|
||||
kind: number,
|
||||
|
@@ -73,7 +73,7 @@ class SingleEventService {
|
||||
.requestEvent(id)
|
||||
.then((cached) => {
|
||||
this.loadingFromCache.delete(id);
|
||||
if (cached) this.handleEvent(cached, false);
|
||||
if (cached) this.handleEvent(cached, true);
|
||||
else this.loadEventFromRelays(id);
|
||||
});
|
||||
}
|
||||
@@ -82,11 +82,11 @@ class SingleEventService {
|
||||
return subject;
|
||||
}
|
||||
|
||||
handleEvent(event: NostrEvent, cache = true) {
|
||||
handleEvent(event: NostrEvent, fromCache = false) {
|
||||
this.events.addEvent(event);
|
||||
this.pendingRelays.delete(event.id);
|
||||
|
||||
if (cache && localRelay) localRelay.publish(event);
|
||||
if (!fromCache && localRelay) localRelay.publish(event);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,17 +1,9 @@
|
||||
import { useMemo } from "react";
|
||||
import { Box, Button, Flex, Text } from "@chakra-ui/react";
|
||||
import AutoSizer from "react-virtualized-auto-sizer";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import ForceGraph, { LinkObject, NodeObject } from "react-force-graph-3d";
|
||||
import {
|
||||
Group,
|
||||
Mesh,
|
||||
MeshBasicMaterial,
|
||||
SRGBColorSpace,
|
||||
SphereGeometry,
|
||||
Sprite,
|
||||
SpriteMaterial,
|
||||
TextureLoader,
|
||||
} from "three";
|
||||
import { Mesh, MeshBasicMaterial, SRGBColorSpace, SphereGeometry, Sprite, SpriteMaterial, TextureLoader } from "three";
|
||||
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import RequireCurrentAccount from "../../providers/route/require-current-account";
|
||||
@@ -22,7 +14,6 @@ import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
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";
|
||||
import { ChevronLeftIcon } from "../../components/icons";
|
||||
|
||||
export function useUsersMuteLists(pubkeys: string[], additionalRelays?: Iterable<string>) {
|
||||
@@ -62,7 +53,7 @@ function NetworkGraphPage() {
|
||||
|
||||
const metadata = usersMetadata[pubkey];
|
||||
if (metadata) {
|
||||
node.image = metadata.picture;
|
||||
node.image = metadata.picture || metadata.image;
|
||||
node.name = metadata.name;
|
||||
}
|
||||
|
||||
@@ -105,25 +96,18 @@ function NetworkGraphPage() {
|
||||
linkCurvature={0.25}
|
||||
nodeThreeObject={(node: NodeType) => {
|
||||
if (!node.image) {
|
||||
return new Mesh(new SphereGeometry(5, 12, 6), new MeshBasicMaterial({ color: 0xaa0f0f }));
|
||||
return new Mesh(
|
||||
new SphereGeometry(5, 12, 6),
|
||||
new MeshBasicMaterial({ color: parseInt(node.id.slice(0, 6), 16) }),
|
||||
);
|
||||
}
|
||||
|
||||
const group = new Group();
|
||||
|
||||
const imgTexture = new TextureLoader().load(node.image);
|
||||
imgTexture.colorSpace = SRGBColorSpace;
|
||||
const material = new SpriteMaterial({ map: imgTexture });
|
||||
const sprite = new Sprite(material);
|
||||
sprite.scale.set(10, 10, 10);
|
||||
|
||||
group.children.push(sprite);
|
||||
|
||||
// if (node.name) {
|
||||
// const text = new SpriteText(node.name, 8, "ffffff");
|
||||
// text.position.set(0, 0, 16);
|
||||
// group.children.push(text);
|
||||
// }
|
||||
|
||||
return sprite;
|
||||
}}
|
||||
/>
|
||||
|
26
yarn.lock
26
yarn.lock
@@ -6688,19 +6688,19 @@ react-focus-lock@^2.9.4:
|
||||
use-callback-ref "^1.3.0"
|
||||
use-sidecar "^1.1.2"
|
||||
|
||||
react-force-graph-2d@^1.25.1:
|
||||
version "1.25.3"
|
||||
resolved "https://registry.yarnpkg.com/react-force-graph-2d/-/react-force-graph-2d-1.25.3.tgz#9ed1f4bafd0a9bc3e8ed98d5b1c584b728ac856f"
|
||||
integrity sha512-BFgZ8Mbq03fEO3Xss9hi1LAReu0LatEVpqJlrm9023p6rJMaUIrGWfVPQUx7ObjMfVBfuq0JXvCcCEpo+FmO1Q==
|
||||
react-force-graph-2d@^1.25.4:
|
||||
version "1.25.4"
|
||||
resolved "https://registry.yarnpkg.com/react-force-graph-2d/-/react-force-graph-2d-1.25.4.tgz#91f9e8169d0eeb6a7e36c36dd99da5128702b776"
|
||||
integrity sha512-Y1xwa79PHVZUedfa/TO+Cboq2hIc1flA1z4o1oraOu6qMS0r421vNpfjWhJPR6qJonNme3tzeVt5boEA7Ue8sg==
|
||||
dependencies:
|
||||
force-graph "1"
|
||||
prop-types "15"
|
||||
react-kapsule "2"
|
||||
|
||||
react-force-graph-3d@^1.23.1:
|
||||
version "1.24.1"
|
||||
resolved "https://registry.yarnpkg.com/react-force-graph-3d/-/react-force-graph-3d-1.24.1.tgz#5a4d39578b7253a96efbdc3a02c98a26901ca751"
|
||||
integrity sha512-Qx3z397VueK2KMjxETx7HRBqFFjEejjsFLgzCbne4Op+0+SlfGjwqxOCopAdAqcK6D+SQ0mhzTUCWw74Sr5wGA==
|
||||
react-force-graph-3d@^1.24.2:
|
||||
version "1.24.2"
|
||||
resolved "https://registry.yarnpkg.com/react-force-graph-3d/-/react-force-graph-3d-1.24.2.tgz#e94d14981e06f3a0b5b4a5f5d69888a2ff88ee35"
|
||||
integrity sha512-/tZ0BywYuj35Q84AH2WN+Cx0RIygnN5F1+EvsdAqsAMoIJ0xl4L/9aD/pwjCoWfFqi3w5wR2DQuitDXeTayZnQ==
|
||||
dependencies:
|
||||
"3d-force-graph" "^1.73"
|
||||
prop-types "15"
|
||||
@@ -7592,15 +7592,15 @@ three-render-objects@^1.29:
|
||||
kapsule "1"
|
||||
polished "4"
|
||||
|
||||
three-spritetext@^1.8.1:
|
||||
three-spritetext@^1.8.2:
|
||||
version "1.8.2"
|
||||
resolved "https://registry.yarnpkg.com/three-spritetext/-/three-spritetext-1.8.2.tgz#62d8106e579274c65d86df2b56dda46b13fa97cc"
|
||||
integrity sha512-OYjyAhWnQ6+7CPKjnpq3JQM+Lpr6cSOppCtbOOzF1IbCauGkoDFvbAnxYd0LVxEsIO2RALXXScg2eX+R6CAfyA==
|
||||
|
||||
three-stdlib@^2.29.4:
|
||||
version "2.29.4"
|
||||
resolved "https://registry.yarnpkg.com/three-stdlib/-/three-stdlib-2.29.4.tgz#6e8741f6a2d435d15ed73f3a14dd149660d4ce51"
|
||||
integrity sha512-XNzGCrz/uAk9XoLwd35eN7dQyI4ggXZTeqjcN034YdYBpBlNO9kmLHehl/0Nw9jCelblB7jla+unHAOIyLyV6Q==
|
||||
three-stdlib@^2.29.11:
|
||||
version "2.29.11"
|
||||
resolved "https://registry.yarnpkg.com/three-stdlib/-/three-stdlib-2.29.11.tgz#9bec17887464e8f4fe36d41e879655b16411c686"
|
||||
integrity sha512-vSyUavKCwOJQd2ch9IHhyJVx6eNG3y+z3/LulqHM7OLMy81OqnmIrvc2b4phKr/c1aVjHRNG2X7JOklfrDDcmQ==
|
||||
dependencies:
|
||||
"@types/draco3d" "^1.4.0"
|
||||
"@types/offscreencanvas" "^2019.6.4"
|
||||
|
Reference in New Issue
Block a user