reconnect timeline on mount

This commit is contained in:
hzrd149 2024-05-14 13:58:53 -05:00
parent 8266e4f72a
commit 66b94d954d
9 changed files with 23 additions and 189 deletions

View File

@ -42,8 +42,8 @@
"@uiw/react-codemirror": "^4.21.21",
"@webscopeio/react-textarea-autocomplete": "^4.9.2",
"bech32": "^2.0.0",
"blossom-client-sdk": "^0.6.0",
"blossom-drive-sdk": "^0.3.0",
"blossom-client-sdk": "^0.7.0",
"blossom-drive-sdk": "^0.4.0",
"blurhash": "^2.0.5",
"chart.js": "^4.4.1",
"cheerio": "^1.0.0-rc.12",

View File

@ -91,7 +91,7 @@ export default class MultiSubscription {
// else open and update subscriptions
for (const relay of this.relays) {
let subscription = this.subscriptions.get(relay);
if (!subscription || !isFilterEqual(subscription.filters, this.filters)) {
if (!subscription || !isFilterEqual(subscription.filters, this.filters) || subscription.closed) {
if (!subscription) {
subscription = new PersistentSubscription(relay, {
onevent: (event) => this.handleEvent(event),
@ -118,7 +118,10 @@ export default class MultiSubscription {
}
// update cache sub filters if they changed
if (this.cacheSubscription && !isFilterEqual(this.cacheSubscription.filters, this.filters)) {
if (
this.cacheSubscription &&
(!isFilterEqual(this.cacheSubscription.filters, this.filters) || this.cacheSubscription.closed)
) {
this.cacheSubscription.filters = this.filters;
this.cacheSubscription.update();
}

View File

@ -4,7 +4,6 @@ import { kinds } from "nostr-tools";
import { NostrEvent } from "../../../../types/nostr-event";
import { RepostIcon } from "../../../icons";
import useEventCount from "../../../../hooks/use-event-count";
import useEventExists from "../../../../hooks/use-event-exists";
import useCurrentAccount from "../../../../hooks/use-current-account";
import RepostModal from "./repost-modal";
@ -12,10 +11,8 @@ export default function RepostButton({ event }: { event: NostrEvent }) {
const { isOpen, onClose, onOpen } = useDisclosure();
const account = useCurrentAccount();
const hasReposted = useEventExists(
const hasReposted = useEventCount(
account ? { "#e": [event.id], kinds: [kinds.Repost, kinds.GenericRepost], authors: [account.pubkey] } : undefined,
[], //only check the cache
false,
);
const repostCount = useEventCount({ "#e": [event.id], kinds: [kinds.Repost, kinds.GenericRepost] });

View File

@ -1,13 +0,0 @@
import { useMemo } from "react";
import stringify from "json-stringify-deterministic";
import eventExistsService from "../services/event-exists";
import { NostrRequestFilter } from "../types/nostr-relay";
import useSubject from "./use-subject";
export default function useEventExists(filter?: NostrRequestFilter, relays: string[] = [], fallback = true) {
const sub = useMemo(
() => filter && eventExistsService.requestExists(filter, relays),
[stringify(filter), relays.join("|")],
);
return useSubject(sub) ?? fallback;
}

View File

@ -9,7 +9,6 @@ import clientRelaysService from "../../services/client-relays";
import RelaySet from "../../classes/relay-set";
import { getAllRelayHints, isReplaceable } from "../../helpers/nostr/event";
import replaceableEventsService from "../../services/replaceable-events";
import eventExistsService from "../../services/event-exists";
import eventReactionsService from "../../services/event-reactions";
import { localRelay } from "../../services/local-relay";
import { handleEventFromRelay } from "../../services/event-relays";
@ -95,7 +94,6 @@ export default function PublishProvider({ children }: PropsWithChildren) {
if (localRelay) localRelay.publish(signed);
// pass it to other services
eventExistsService.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);

View File

@ -33,7 +33,6 @@ class EventCountService {
}
}
/** @deprecated */
const eventCountService = new EventCountService();
if (import.meta.env.DEV) {

View File

@ -1,108 +0,0 @@
import stringify from "json-stringify-deterministic";
import Subject from "../classes/subject";
import { NostrRequestFilter } from "../types/nostr-relay";
import SuperMap from "../classes/super-map";
import relayScoreboardService from "./relay-scoreboard";
import { logger } from "../helpers/debug";
import { matchFilter, matchFilters } from "nostr-tools";
import { NostrEvent } from "../types/nostr-event";
import { relayRequest } from "../helpers/relay";
import { localRelay } from "./local-relay";
import relayPoolService from "./relay-pool";
function hashFilter(filter: NostrRequestFilter) {
return stringify(filter);
}
class EventExistsService {
log = logger.extend("EventExistsService");
answers = new SuperMap<string, Subject<boolean>>(() => new Subject());
private filters = new Map<string, NostrRequestFilter>();
asked = new SuperMap<string, Set<string>>(() => new Set());
pending = new SuperMap<string, Set<string>>(() => new Set());
getExists(filter: NostrRequestFilter) {
const key = hashFilter(filter);
return this.answers.get(key);
}
requestExists(filter: NostrRequestFilter, relays: string[]) {
const key = hashFilter(filter);
const sub = this.answers.get(key);
const asked = this.asked.get(key);
const pending = this.pending.get(key);
if (!this.filters.has(key)) this.filters.set(key, filter);
if (sub.value !== true) {
const p = localRelay ? relayRequest(localRelay, Array.isArray(filter) ? filter : [filter]) : Promise.resolve([]);
p.then((cached) => {
if (cached.length > 0) {
for (const e of cached) this.handleEvent(e, false);
} else {
for (const url of relays) {
if (!asked.has(url) && !pending.has(url)) {
pending.add(url);
}
}
}
});
}
return sub;
}
nextRequests() {
for (const [key, relays] of this.pending) {
const filter = JSON.parse(key) as NostrRequestFilter;
const nextRelay = relayScoreboardService.getRankedRelays(Array.from(relays))[0];
if (!nextRelay) continue;
relays.delete(nextRelay);
(async () => {
const subject = this.answers.get(key);
const limitFilter = Array.isArray(filter) ? filter.map((f) => ({ ...f, limit: 1 })) : [{ ...filter, limit: 1 }];
const subscription = relayPoolService.requestRelay(nextRelay).subscribe(limitFilter, {
eoseTimeout: 500,
onevent: () => {
this.log("Found event for", filter);
subject.next(true);
this.pending.delete(key);
},
oneose: () => {
if (subject.value === undefined && this.asked.get(key).size > this.pending.get(key).size) {
this.log("Could not find event for", filter);
subject.next(false);
}
subscription.close();
},
});
})();
}
}
handleEvent(event: NostrEvent, cache = true) {
for (const [key, filter] of this.filters) {
const doesMatch = Array.isArray(filter) ? matchFilters(filter, event) : matchFilter(filter, event);
if (doesMatch && this.answers.get(key).value !== true) {
this.answers.get(key).next(true);
}
}
if (cache && localRelay) localRelay.publish(event);
}
}
const eventExistsService = new EventExistsService();
setInterval(() => eventExistsService.nextRequests(), 1000);
if (import.meta.env.DEV) {
// @ts-ignore
window.eventExistsService = eventExistsService;
}
export default eventExistsService;

View File

@ -1,4 +1,4 @@
import { Suspense, useMemo, useState } from "react";
import { Suspense, useState } from "react";
import {
Flex,
FormControl,
@ -25,7 +25,6 @@ import {
Tabs,
useDisclosure,
} from "@chakra-ui/react";
import { kinds } from "nostr-tools";
import { Outlet, useMatches, useNavigate } from "react-router-dom";
import useUserMetadata from "../../hooks/use-user-metadata";
@ -38,11 +37,6 @@ import { unique } from "../../helpers/array";
import { RelayFavicon } from "../../components/relay-favicon";
import Header from "./components/header";
import { ErrorBoundary } from "../../components/error-boundary";
import useEventExists from "../../hooks/use-event-exists";
import { STEMSTR_TRACK_KIND } from "../../helpers/nostr/stemstr";
import { STREAM_KIND } from "../../helpers/nostr/stream";
import { TORRENT_KIND } from "../../helpers/nostr/torrents";
import { GOAL_KIND } from "../../helpers/nostr/goal";
import useParamsProfilePointer from "../../hooks/use-params-pubkey-pointer";
import useUserMailboxes from "../../hooks/use-user-mailboxes";
@ -84,40 +78,10 @@ const UserView = () => {
const metadata = useUserMetadata(pubkey, userTopRelays, { alwaysRequest: true });
useAppTitle(getDisplayName(metadata, pubkey));
const hasTorrents = useEventExists({ kinds: [TORRENT_KIND], authors: [pubkey] }, readRelays);
const hasGoals = useEventExists({ kinds: [GOAL_KIND], authors: [pubkey] }, readRelays);
const hasTracks = useEventExists({ kinds: [STEMSTR_TRACK_KIND], authors: [pubkey] }, [
"wss://relay.stemstr.app",
...readRelays,
]);
const hasArticles = useEventExists({ kinds: [kinds.LongFormArticle], authors: [pubkey] }, readRelays);
const hasStreams = useEventExists({ kinds: [STREAM_KIND], authors: [pubkey] }, [
"wss://relay.snort.social",
"wss://nos.lol",
"wss://relay.damus.io",
"wss://nostr.wine",
...readRelays,
]);
const filteredTabs = useMemo(
() =>
tabs.filter((tab) => {
if (tab.path === "tracks" && hasTracks === false) return false;
if (tab.path === "articles" && hasArticles === false) return false;
if (tab.path === "streams" && hasStreams === false) return false;
if (tab.path === "torrents" && hasTorrents === false) return false;
if (tab.path === "goals" && hasGoals === false) return false;
return true;
}),
[hasTracks, hasArticles, hasStreams, hasTorrents, hasGoals, tabs],
);
const matches = useMatches();
const lastMatch = matches[matches.length - 1];
const activeTab = filteredTabs.indexOf(
filteredTabs.find((t) => lastMatch.pathname.endsWith(t.path)) ?? filteredTabs[0],
);
const activeTab = tabs.indexOf(tabs.find((t) => lastMatch.pathname.endsWith(t.path)) ?? tabs[0]);
return (
<>
@ -130,12 +94,12 @@ const UserView = () => {
flexGrow="1"
isLazy
index={activeTab}
onChange={(v) => navigate(filteredTabs[v].path, { replace: true })}
onChange={(v) => navigate(tabs[v].path, { replace: true })}
colorScheme="primary"
h="full"
>
<TabList overflowX="auto" overflowY="hidden" flexShrink={0}>
{filteredTabs.map(({ label }) => (
{tabs.map(({ label }) => (
<Tab key={label} whiteSpace="pre">
{label}
</Tab>
@ -143,7 +107,7 @@ const UserView = () => {
</TabList>
<TabPanels>
{filteredTabs.map(({ label }) => (
{tabs.map(({ label }) => (
<TabPanel key={label} p={0}>
<ErrorBoundary>
<Suspense fallback={<Spinner />}>

View File

@ -3419,28 +3419,22 @@ better-path-resolve@1.0.0:
resolved "https://registry.yarnpkg.com/bezier-js/-/bezier-js-6.1.4.tgz#c7828f6c8900562b69d5040afb881bcbdad82001"
integrity sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==
blossom-client-sdk@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/blossom-client-sdk/-/blossom-client-sdk-0.5.1.tgz#9b07ed2ebd29903268a44ea1b7f1b54d2edace46"
integrity sha512-RuT+uyNLVToSivANQrrA2Kmio8l0Z2lEIs3e7OwBnd/gxcl+KoINwuZ/JVjsquOPNcjkXBVbsvmRzJnesmkjWg==
blossom-client-sdk@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/blossom-client-sdk/-/blossom-client-sdk-0.7.0.tgz#e9224f333c953d57cdfdf2aa41fbd99435da27a2"
integrity sha512-xG0HiuhFcK6UpmYjJ4vRPm3APMrRf+MQDfZWlNRTxs2gEETfqbhYm5pCl2hPfLjpEcFSDXgr3sLCh6C77ABKgg==
dependencies:
"@noble/hashes" "^1.4.0"
cross-fetch "^4.0.0"
blossom-client-sdk@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/blossom-client-sdk/-/blossom-client-sdk-0.6.0.tgz#e5fc2b17f3d76e3b8bb3d31a535db294ebce5a07"
integrity sha512-m1dAIQF0WceEvpxbl349IUeNTay6w/QF7RAUlELIFxES7fbRQB0dkbVhT58y+SW+tTQvgMfuPmMAAsghrGwGfg==
dependencies:
cross-fetch "^4.0.0"
blossom-drive-sdk@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/blossom-drive-sdk/-/blossom-drive-sdk-0.3.0.tgz#35ab1afebbc8c79bd86af10628291b3e06935f35"
integrity sha512-K2mhnIO1WVSb7Ua9wcgv4ra3rnoIfp1X8oKdHOFNNosuCVI5dBZumDvYJlrn4ZztE9UZslIqlb/swr0PZcGmgw==
blossom-drive-sdk@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/blossom-drive-sdk/-/blossom-drive-sdk-0.4.0.tgz#c24a7dda7a540e9fda21372a34c5c764411f434b"
integrity sha512-bDOTFtsoT40+NbGYteG50PaKR0wDnMeCN4BmMDI5R/zkbvAz4Qh+OC3zVbsbZekQPM9L5tAH+wuF+Qe/vh3s7Q==
dependencies:
"@noble/hashes" "^1.4.0"
"@scure/base" "^1.1.6"
blossom-client-sdk "^0.5.1"
blossom-client-sdk "^0.7.0"
eventemitter3 "^5.0.1"
mime "^4.0.1"
nanoid "^5.0.6"