mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-09 20:29:17 +02:00
add local relay cache option
cleanup event relay hint methods
This commit is contained in:
parent
9069932fbe
commit
199f208b11
5
.changeset/quick-garlics-work.md
Normal file
5
.changeset/quick-garlics-work.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"nostrudel": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add local relay cache option
|
@ -29,10 +29,14 @@ export default class NostrMultiSubscription {
|
|||||||
this.id = nanoid();
|
this.id = nanoid();
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
private handleEvent(event: IncomingEvent) {
|
private handleEvent(incomingEvent: IncomingEvent) {
|
||||||
if (this.state === NostrMultiSubscription.OPEN && event.subId === this.id && !this.seenEvents.has(event.body.id)) {
|
if (
|
||||||
this.onEvent.next(event.body);
|
this.state === NostrMultiSubscription.OPEN &&
|
||||||
this.seenEvents.add(event.body.id);
|
incomingEvent.subId === this.id &&
|
||||||
|
!this.seenEvents.has(incomingEvent.body.id)
|
||||||
|
) {
|
||||||
|
this.onEvent.next(incomingEvent.body);
|
||||||
|
this.seenEvents.add(incomingEvent.body.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
|
import { nanoid } from "nanoid";
|
||||||
|
|
||||||
import { NostrEvent } from "../types/nostr-event";
|
import { NostrEvent } from "../types/nostr-event";
|
||||||
import { NostrOutgoingMessage, NostrRequestFilter } from "../types/nostr-query";
|
import { NostrOutgoingMessage, NostrRequestFilter } from "../types/nostr-query";
|
||||||
import Relay, { IncomingEOSE } from "./relay";
|
import Relay, { IncomingEOSE } from "./relay";
|
||||||
import relayPoolService from "../services/relay-pool";
|
import relayPoolService from "../services/relay-pool";
|
||||||
import { Subject } from "./subject";
|
import { Subject } from "./subject";
|
||||||
import { nanoid } from "nanoid";
|
|
||||||
|
|
||||||
export default class NostrSubscription {
|
export default class NostrSubscription {
|
||||||
static INIT = "initial";
|
static INIT = "initial";
|
||||||
@ -26,7 +27,9 @@ export default class NostrSubscription {
|
|||||||
this.relay = relayPoolService.requestRelay(relayUrl);
|
this.relay = relayPoolService.requestRelay(relayUrl);
|
||||||
|
|
||||||
this.onEvent.connectWithHandler(this.relay.onEvent, (event, next) => {
|
this.onEvent.connectWithHandler(this.relay.onEvent, (event, next) => {
|
||||||
if (this.state === NostrSubscription.OPEN) next(event.body);
|
if (this.state === NostrSubscription.OPEN) {
|
||||||
|
next(event.body);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
this.onEOSE.connectWithHandler(this.relay.onEOSE, (eose, next) => {
|
this.onEOSE.connectWithHandler(this.relay.onEOSE, (eose, next) => {
|
||||||
if (this.state === NostrSubscription.OPEN) next(eose);
|
if (this.state === NostrSubscription.OPEN) next(eose);
|
||||||
|
@ -9,7 +9,7 @@ import appSettings from "../../../services/settings/app-settings";
|
|||||||
import EventVerificationIcon from "../../event-verification-icon";
|
import EventVerificationIcon from "../../event-verification-icon";
|
||||||
import { TrustProvider } from "../../../providers/trust";
|
import { TrustProvider } from "../../../providers/trust";
|
||||||
import Timestamp from "../../timestamp";
|
import Timestamp from "../../timestamp";
|
||||||
import { getNeventCodeWithRelays } from "../../../helpers/nip19";
|
import { getNeventForEventId } from "../../../helpers/nip19";
|
||||||
import { CompactNoteContent } from "../../compact-note-content";
|
import { CompactNoteContent } from "../../compact-note-content";
|
||||||
import HoverLinkOverlay from "../../hover-link-overlay";
|
import HoverLinkOverlay from "../../hover-link-overlay";
|
||||||
import { getReferences } from "../../../helpers/nostr/events";
|
import { getReferences } from "../../../helpers/nostr/events";
|
||||||
@ -26,7 +26,7 @@ export default function EmbeddedTorrentComment({
|
|||||||
const { showSignatureVerification } = useSubject(appSettings);
|
const { showSignatureVerification } = useSubject(appSettings);
|
||||||
const refs = getReferences(comment);
|
const refs = getReferences(comment);
|
||||||
const torrent = useSingleEvent(refs.rootId, refs.rootRelay ? [refs.rootRelay] : []);
|
const torrent = useSingleEvent(refs.rootId, refs.rootRelay ? [refs.rootRelay] : []);
|
||||||
const linkToTorrent = refs.rootId && `/torrents/${getNeventCodeWithRelays(refs.rootId)}`;
|
const linkToTorrent = refs.rootId && `/torrents/${getNeventForEventId(refs.rootId)}`;
|
||||||
|
|
||||||
const handleClick = useCallback<MouseEventHandler>(
|
const handleClick = useCallback<MouseEventHandler>(
|
||||||
(e) => {
|
(e) => {
|
||||||
|
@ -16,7 +16,7 @@ import {
|
|||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { Link as RouterLink } from "react-router-dom";
|
import { Link as RouterLink } from "react-router-dom";
|
||||||
|
|
||||||
import { getNeventCodeWithRelays } from "../../../helpers/nip19";
|
import { getSharableEventAddress } from "../../../helpers/nip19";
|
||||||
import UserAvatarLink from "../../user-avatar-link";
|
import UserAvatarLink from "../../user-avatar-link";
|
||||||
import { UserLink } from "../../user-link";
|
import { UserLink } from "../../user-link";
|
||||||
import { NostrEvent } from "../../../types/nostr-event";
|
import { NostrEvent } from "../../../types/nostr-event";
|
||||||
@ -29,7 +29,7 @@ import HoverLinkOverlay from "../../hover-link-overlay";
|
|||||||
|
|
||||||
export default function EmbeddedTorrent({ torrent, ...props }: Omit<CardProps, "children"> & { torrent: NostrEvent }) {
|
export default function EmbeddedTorrent({ torrent, ...props }: Omit<CardProps, "children"> & { torrent: NostrEvent }) {
|
||||||
const navigate = useNavigateInDrawer();
|
const navigate = useNavigateInDrawer();
|
||||||
const link = `/torrents/${getNeventCodeWithRelays(torrent.id)}`;
|
const link = `/torrents/${getSharableEventAddress(torrent)}`;
|
||||||
|
|
||||||
const handleClick = useCallback<MouseEventHandler>(
|
const handleClick = useCallback<MouseEventHandler>(
|
||||||
(e) => {
|
(e) => {
|
||||||
|
@ -29,6 +29,7 @@ import accountService from "../../services/account";
|
|||||||
import PayStep from "./pay-step";
|
import PayStep from "./pay-step";
|
||||||
import { getInvoiceFromCallbackUrl } from "../../helpers/lnurl";
|
import { getInvoiceFromCallbackUrl } from "../../helpers/lnurl";
|
||||||
import { UserLink } from "../user-link";
|
import { UserLink } from "../user-link";
|
||||||
|
import relayHintService from "../../services/event-relay-hint";
|
||||||
|
|
||||||
export type PayRequest = { invoice?: string; pubkey: string; error?: any };
|
export type PayRequest = { invoice?: string; pubkey: string; error?: any };
|
||||||
|
|
||||||
@ -69,7 +70,7 @@ async function getPayRequestForPubkey(
|
|||||||
.map((r) => r.url) ?? [],
|
.map((r) => r.url) ?? [],
|
||||||
)
|
)
|
||||||
.slice(0, 4);
|
.slice(0, 4);
|
||||||
const eventRelays = event ? relayScoreboardService.getRankedRelays(getEventRelays(event.id).value).slice(0, 4) : [];
|
const eventRelays = event ? relayHintService.getEventRelayHints(event, 4) : [];
|
||||||
const outbox = relayScoreboardService.getRankedRelays(clientRelaysService.getWriteUrls()).slice(0, 4);
|
const outbox = relayScoreboardService.getRankedRelays(clientRelaysService.getWriteUrls()).slice(0, 4);
|
||||||
const additional = relayScoreboardService.getRankedRelays(additionalRelays);
|
const additional = relayScoreboardService.getRankedRelays(additionalRelays);
|
||||||
|
|
||||||
|
@ -3,14 +3,14 @@ import { Link, LinkProps } from "@chakra-ui/react";
|
|||||||
import { Link as RouterLink } from "react-router-dom";
|
import { Link as RouterLink } from "react-router-dom";
|
||||||
|
|
||||||
import { truncatedId } from "../helpers/nostr/events";
|
import { truncatedId } from "../helpers/nostr/events";
|
||||||
import { getNeventCodeWithRelays } from "../helpers/nip19";
|
import { getNeventForEventId } from "../helpers/nip19";
|
||||||
|
|
||||||
export type NoteLinkProps = LinkProps & {
|
export type NoteLinkProps = LinkProps & {
|
||||||
noteId: string;
|
noteId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NoteLink = ({ children, noteId, color = "blue.500", ...props }: NoteLinkProps) => {
|
export const NoteLink = ({ children, noteId, color = "blue.500", ...props }: NoteLinkProps) => {
|
||||||
const nevent = useMemo(() => getNeventCodeWithRelays(noteId), [noteId]);
|
const nevent = useMemo(() => getNeventForEventId(noteId), [noteId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link as={RouterLink} to={`/n/${nevent}`} color={color} {...props}>
|
<Link as={RouterLink} to={`/n/${nevent}`} color={color} {...props}>
|
||||||
|
@ -13,28 +13,25 @@ import {
|
|||||||
useDisclosure,
|
useDisclosure,
|
||||||
useToast,
|
useToast,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
|
import { Kind } from "nostr-tools";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import type { AddressPointer } from "nostr-tools/lib/types/nip19";
|
||||||
|
|
||||||
import { DraftNostrEvent, NostrEvent } from "../../../types/nostr-event";
|
import { DraftNostrEvent, NostrEvent } from "../../../types/nostr-event";
|
||||||
import { EmbedEvent } from "../../embed-event";
|
import { EmbedEvent } from "../../embed-event";
|
||||||
import { getEventRelays } from "../../../services/event-relays";
|
|
||||||
import relayScoreboardService from "../../../services/relay-scoreboard";
|
|
||||||
import { Kind } from "nostr-tools";
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
import NostrPublishAction from "../../../classes/nostr-publish-action";
|
import NostrPublishAction from "../../../classes/nostr-publish-action";
|
||||||
import clientRelaysService from "../../../services/client-relays";
|
import clientRelaysService from "../../../services/client-relays";
|
||||||
import { useSigningContext } from "../../../providers/signing-provider";
|
import { useSigningContext } from "../../../providers/signing-provider";
|
||||||
import { ChevronDownIcon, ChevronUpIcon, ExternalLinkIcon } from "../../icons";
|
import { ChevronDownIcon, ChevronUpIcon, ExternalLinkIcon } from "../../icons";
|
||||||
import useUserCommunitiesList from "../../../hooks/use-user-communities-list";
|
import useUserCommunitiesList from "../../../hooks/use-user-communities-list";
|
||||||
import useCurrentAccount from "../../../hooks/use-current-account";
|
import useCurrentAccount from "../../../hooks/use-current-account";
|
||||||
import { AddressPointer } from "nostr-tools/lib/types/nip19";
|
|
||||||
import { createCoordinate } from "../../../services/replaceable-event-requester";
|
import { createCoordinate } from "../../../services/replaceable-event-requester";
|
||||||
|
import relayHintService from "../../../services/event-relay-hint";
|
||||||
|
|
||||||
function buildRepost(event: NostrEvent): DraftNostrEvent {
|
function buildRepost(event: NostrEvent): DraftNostrEvent {
|
||||||
const relays = getEventRelays(event.id).value;
|
const hint = relayHintService.getEventRelayHint(event);
|
||||||
const topRelay = relayScoreboardService.getRankedRelays(relays)[0] ?? "";
|
|
||||||
|
|
||||||
const tags: NostrEvent["tags"] = [];
|
const tags: NostrEvent["tags"] = [];
|
||||||
tags.push(["e", event.id, topRelay]);
|
tags.push(["e", event.id, hint ?? ""]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
kind: Kind.Repost,
|
kind: Kind.Repost,
|
||||||
@ -65,6 +62,7 @@ export default function RepostModal({
|
|||||||
draftRepost.tags.push([
|
draftRepost.tags.push([
|
||||||
"a",
|
"a",
|
||||||
createCoordinate(communityPointer.kind, communityPointer.pubkey, communityPointer.identifier),
|
createCoordinate(communityPointer.kind, communityPointer.pubkey, communityPointer.identifier),
|
||||||
|
relayHintService.getAddressPointerRelayHint(communityPointer) ?? "",
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
const signed = await requestSignature(draftRepost);
|
const signed = await requestSignature(draftRepost);
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { bech32 } from "bech32";
|
import { bech32 } from "bech32";
|
||||||
import { getPublicKey, nip19 } from "nostr-tools";
|
import { getPublicKey, nip19 } from "nostr-tools";
|
||||||
import { getEventRelays } from "../services/event-relays";
|
|
||||||
import relayScoreboardService from "../services/relay-scoreboard";
|
|
||||||
import { NostrEvent, Tag, isATag, isDTag, isETag, isPTag } from "../types/nostr-event";
|
import { NostrEvent, Tag, isATag, isDTag, isETag, isPTag } from "../types/nostr-event";
|
||||||
import { getEventUID, isReplaceable } from "./nostr/events";
|
import { isReplaceable } from "./nostr/events";
|
||||||
import { DecodeResult } from "nostr-tools/lib/types/nip19";
|
import { DecodeResult } from "nostr-tools/lib/types/nip19";
|
||||||
|
import relayHintService from "../services/event-relay-hint";
|
||||||
|
|
||||||
export function isHexKey(key?: string) {
|
export function isHexKey(key?: string) {
|
||||||
if (key?.toLowerCase()?.match(/^[0-9a-f]{64}$/)) return true;
|
if (key?.toLowerCase()?.match(/^[0-9a-f]{64}$/)) return true;
|
||||||
@ -47,7 +46,7 @@ export function safeDecode(str: string) {
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPubkey(result?: nip19.DecodeResult) {
|
export function getPubkeyFromDecodeResult(result?: nip19.DecodeResult) {
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
switch (result.type) {
|
switch (result.type) {
|
||||||
case "naddr":
|
case "naddr":
|
||||||
@ -68,26 +67,21 @@ export function normalizeToHex(hex: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getSharableEventAddress(event: NostrEvent) {
|
export function getSharableEventAddress(event: NostrEvent) {
|
||||||
const relays = getEventRelays(getEventUID(event)).value;
|
const relays = relayHintService.getEventRelayHints(event, 2);
|
||||||
const ranked = relayScoreboardService.getRankedRelays(relays);
|
|
||||||
const maxTwo = ranked.slice(0, 2);
|
|
||||||
|
|
||||||
if (isReplaceable(event.kind)) {
|
if (isReplaceable(event.kind)) {
|
||||||
const d = event.tags.find(isDTag)?.[1];
|
const d = event.tags.find(isDTag)?.[1];
|
||||||
if (!d) return null;
|
if (!d) return null;
|
||||||
return nip19.naddrEncode({ kind: event.kind, identifier: d, pubkey: event.pubkey, relays: maxTwo });
|
return nip19.naddrEncode({ kind: event.kind, identifier: d, pubkey: event.pubkey, relays });
|
||||||
} else {
|
} else {
|
||||||
if (maxTwo.length == 2) {
|
return nip19.neventEncode({ id: event.id, kind: event.kind, relays, author: event.pubkey });
|
||||||
return nip19.neventEncode({ id: event.id, kind: event.kind, relays: maxTwo });
|
|
||||||
} else return nip19.neventEncode({ id: event.id, kind: event.kind, relays: maxTwo, author: event.pubkey });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNeventCodeWithRelays(eventId: string) {
|
/** @deprecated use getSharableEventAddress unless required */
|
||||||
const relays = getEventRelays(eventId).value;
|
export function getNeventForEventId(eventId: string, maxRelays = 2) {
|
||||||
const ranked = relayScoreboardService.getRankedRelays(relays);
|
const relays = relayHintService.getEventPointerRelayHints(eventId).slice(0, maxRelays);
|
||||||
const maxTwo = ranked.slice(0, 2);
|
return nip19.neventEncode({ id: eventId, relays });
|
||||||
return nip19.neventEncode({ id: eventId, relays: maxTwo });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function encodePointer(pointer: DecodeResult) {
|
export function encodePointer(pointer: DecodeResult) {
|
||||||
|
@ -193,30 +193,6 @@ export function parseCoordinate(a: string, requireD = false): CustomEventPointer
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function draftAddCoordinate(list: NostrEvent | DraftNostrEvent, coordinate: string, relay?: string) {
|
|
||||||
if (list.tags.some((t) => t[0] === "a" && t[1] === coordinate)) throw new Error("event already in list");
|
|
||||||
|
|
||||||
const draft: DraftNostrEvent = {
|
|
||||||
created_at: dayjs().unix(),
|
|
||||||
kind: list.kind,
|
|
||||||
content: list.content,
|
|
||||||
tags: [...list.tags, relay ? ["a", coordinate, relay] : ["a", coordinate]],
|
|
||||||
};
|
|
||||||
|
|
||||||
return draft;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function draftRemoveCoordinate(list: NostrEvent | DraftNostrEvent, coordinate: string) {
|
|
||||||
const draft: DraftNostrEvent = {
|
|
||||||
created_at: dayjs().unix(),
|
|
||||||
kind: list.kind,
|
|
||||||
content: list.content,
|
|
||||||
tags: list.tags.filter((t) => !(t[0] === "a" && t[1] === coordinate)),
|
|
||||||
};
|
|
||||||
|
|
||||||
return draft;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseHardcodedNoteContent(event: NostrEvent) {
|
export function parseHardcodedNoteContent(event: NostrEvent) {
|
||||||
const json = safeJson(event.content, null);
|
const json = safeJson(event.content, null);
|
||||||
if (!json) return null;
|
if (!json) return null;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import stringify from "json-stringify-deterministic";
|
import stringify from "json-stringify-deterministic";
|
||||||
import { NostrQuery, NostrRequestFilter, RelayQueryMap } from "../../types/nostr-query";
|
import { NostrQuery, NostrRequestFilter, RelayQueryMap } from "../../types/nostr-query";
|
||||||
|
import localCacheRelayService, { LOCAL_CACHE_RELAY } from "../../services/local-cache-relay";
|
||||||
|
|
||||||
export function addQueryToFilter(filter: NostrRequestFilter, query: NostrQuery) {
|
export function addQueryToFilter(filter: NostrRequestFilter, query: NostrQuery) {
|
||||||
if (Array.isArray(filter)) {
|
if (Array.isArray(filter)) {
|
||||||
@ -20,6 +21,13 @@ export function mapQueryMap(queryMap: RelayQueryMap, fn: (filter: NostrRequestFi
|
|||||||
|
|
||||||
export function createSimpleQueryMap(relays: string[], filter: NostrRequestFilter) {
|
export function createSimpleQueryMap(relays: string[], filter: NostrRequestFilter) {
|
||||||
const map: RelayQueryMap = {};
|
const map: RelayQueryMap = {};
|
||||||
|
|
||||||
|
// if the local cache relay is enabled, also ask it
|
||||||
|
if (localCacheRelayService.enabled) {
|
||||||
|
map[LOCAL_CACHE_RELAY] = filter;
|
||||||
|
}
|
||||||
|
|
||||||
for (const relay of relays) map[relay] = filter;
|
for (const relay of relays) map[relay] = filter;
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
@ -149,7 +149,8 @@ export function listAddCoordinate(
|
|||||||
coordinate: string,
|
coordinate: string,
|
||||||
relay?: string,
|
relay?: string,
|
||||||
): DraftNostrEvent {
|
): DraftNostrEvent {
|
||||||
if (list.tags.some((t) => t[0] === "a" && t[1] === coordinate)) throw new Error("coordinate already in list");
|
if (list.tags.some((t) => t[0] === "a" && t[1] === coordinate)) throw new Error("Event already in list");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
created_at: dayjs().unix(),
|
created_at: dayjs().unix(),
|
||||||
kind: list.kind,
|
kind: list.kind,
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import { DraftNostrEvent, NostrEvent, Tag } from "../../types/nostr-event";
|
import { DraftNostrEvent, NostrEvent, Tag } from "../../types/nostr-event";
|
||||||
import { getMatchEmoji, getMatchHashtag } from "../regexp";
|
import { getMatchEmoji, getMatchHashtag } from "../regexp";
|
||||||
import { getReferences } from "./events";
|
import { getReferences } from "./events";
|
||||||
import { getEventRelays } from "../../services/event-relays";
|
import { getPubkeyFromDecodeResult, safeDecode } from "../nip19";
|
||||||
import relayScoreboardService from "../../services/relay-scoreboard";
|
|
||||||
import { getPubkey, safeDecode } from "../nip19";
|
|
||||||
import { Emoji } from "../../providers/emoji-provider";
|
import { Emoji } from "../../providers/emoji-provider";
|
||||||
import { EventSplit } from "./zaps";
|
import { EventSplit } from "./zaps";
|
||||||
import { unique } from "../array";
|
import { unique } from "../array";
|
||||||
|
import relayHintService from "../../services/event-relay-hint";
|
||||||
|
|
||||||
function addTag(tags: Tag[], tag: Tag, overwrite = false) {
|
function addTag(tags: Tag[], tag: Tag, overwrite = false) {
|
||||||
if (tags.some((t) => t[0] === tag[0] && t[1] === tag[1])) {
|
if (tags.some((t) => t[0] === tag[0] && t[1] === tag[1])) {
|
||||||
@ -21,10 +20,9 @@ function addTag(tags: Tag[], tag: Tag, overwrite = false) {
|
|||||||
return [...tags, tag];
|
return [...tags, tag];
|
||||||
}
|
}
|
||||||
function AddEtag(tags: Tag[], eventId: string, type?: string, overwrite = false) {
|
function AddEtag(tags: Tag[], eventId: string, type?: string, overwrite = false) {
|
||||||
const relays = getEventRelays(eventId).value ?? [];
|
const hint = relayHintService.getEventPointerRelayHint(eventId) ?? "";
|
||||||
const top = relayScoreboardService.getRankedRelays(relays)[0] ?? "";
|
|
||||||
|
|
||||||
const tag = type ? ["e", eventId, top, type] : ["e", eventId, top];
|
const tag = type ? ["e", eventId, hint, type] : ["e", eventId, hint];
|
||||||
|
|
||||||
if (tags.some((t) => t[0] === tag[0] && t[1] === tag[1] && t[3] === tag[3])) {
|
if (tags.some((t) => t[0] === tag[0] && t[1] === tag[1] && t[3] === tag[3])) {
|
||||||
if (overwrite) {
|
if (overwrite) {
|
||||||
@ -73,7 +71,7 @@ export function getContentMentions(content: string) {
|
|||||||
Array.from(matched)
|
Array.from(matched)
|
||||||
.map((m) => {
|
.map((m) => {
|
||||||
const parsed = safeDecode(m[1]);
|
const parsed = safeDecode(m[1]);
|
||||||
return parsed && getPubkey(parsed);
|
return parsed && getPubkeyFromDecodeResult(parsed);
|
||||||
})
|
})
|
||||||
.filter(Boolean) as string[],
|
.filter(Boolean) as string[],
|
||||||
);
|
);
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
import { useMemo } from "react";
|
|
||||||
import { RelayConfig } from "../classes/relay";
|
|
||||||
import relayScoreboardService from "../services/relay-scoreboard";
|
|
||||||
|
|
||||||
export default function useRankedRelayConfigs(relays: RelayConfig[]) {
|
|
||||||
return useMemo(() => {
|
|
||||||
const rankedUrls = relayScoreboardService.getRankedRelays(relays.map((r) => r.url));
|
|
||||||
return rankedUrls.map((u) => relays.find((r) => r.url === u) as RelayConfig);
|
|
||||||
}, [relays.join("|")]);
|
|
||||||
}
|
|
@ -1,7 +1,8 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import relayScoreboardService from "../services/relay-scoreboard";
|
|
||||||
import { RelayMode } from "../classes/relay";
|
|
||||||
import { nip19 } from "nostr-tools";
|
import { nip19 } from "nostr-tools";
|
||||||
|
|
||||||
|
import { RelayMode } from "../classes/relay";
|
||||||
|
import relayScoreboardService from "../services/relay-scoreboard";
|
||||||
import { useUserRelays } from "./use-user-relays";
|
import { useUserRelays } from "./use-user-relays";
|
||||||
|
|
||||||
export function useSharableProfileId(pubkey: string, relayCount = 2) {
|
export function useSharableProfileId(pubkey: string, relayCount = 2) {
|
||||||
|
@ -2,8 +2,7 @@ import "./polyfill";
|
|||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import { App } from "./app";
|
import { App } from "./app";
|
||||||
import { GlobalProviders } from "./providers";
|
import { GlobalProviders } from "./providers";
|
||||||
|
import "./services/local-cache-relay";
|
||||||
import "./services/serial-port";
|
|
||||||
|
|
||||||
// setup dayjs
|
// setup dayjs
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
@ -12,6 +12,7 @@ import { logger } from "../helpers/debug";
|
|||||||
import db from "./db";
|
import db from "./db";
|
||||||
import createDefer, { Deferred } from "../classes/deferred";
|
import createDefer, { Deferred } from "../classes/deferred";
|
||||||
import { getChannelPointer } from "../helpers/nostr/channel";
|
import { getChannelPointer } from "../helpers/nostr/channel";
|
||||||
|
import localCacheRelayService, { LOCAL_CACHE_RELAY } from "./local-cache-relay";
|
||||||
|
|
||||||
type Pubkey = string;
|
type Pubkey = string;
|
||||||
type Relay = string;
|
type Relay = string;
|
||||||
@ -25,6 +26,8 @@ export type RequestOptions = {
|
|||||||
// keepAlive?: boolean;
|
// keepAlive?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const RELAY_REQUEST_BATCH_TIME = 1000;
|
||||||
|
|
||||||
/** This class is ued to batch requests to a single relay */
|
/** This class is ued to batch requests to a single relay */
|
||||||
class ChannelMetadataRelayLoader {
|
class ChannelMetadataRelayLoader {
|
||||||
private subscription: NostrSubscription;
|
private subscription: NostrSubscription;
|
||||||
@ -78,7 +81,7 @@ class ChannelMetadataRelayLoader {
|
|||||||
return subject;
|
return subject;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateThrottle = _throttle(this.update, 1000);
|
updateThrottle = _throttle(this.update, RELAY_REQUEST_BATCH_TIME);
|
||||||
update() {
|
update() {
|
||||||
let needsUpdate = false;
|
let needsUpdate = false;
|
||||||
for (const channelId of this.requestNext) {
|
for (const channelId of this.requestNext) {
|
||||||
@ -119,6 +122,9 @@ class ChannelMetadataRelayLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const READ_CACHE_BATCH_TIME = 250;
|
||||||
|
const WRITE_CACHE_BATCH_TIME = 250;
|
||||||
|
|
||||||
/** This is a clone of ReplaceableEventLoaderService to support channel metadata */
|
/** This is a clone of ReplaceableEventLoaderService to support channel metadata */
|
||||||
class ChannelMetadataService {
|
class ChannelMetadataService {
|
||||||
private metadata = new SuperMap<Pubkey, Subject<NostrEvent>>(() => new Subject<NostrEvent>());
|
private metadata = new SuperMap<Pubkey, Subject<NostrEvent>>(() => new Subject<NostrEvent>());
|
||||||
@ -147,7 +153,7 @@ class ChannelMetadataService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private readFromCachePromises = new Map<string, Deferred<boolean>>();
|
private readFromCachePromises = new Map<string, Deferred<boolean>>();
|
||||||
private readFromCacheThrottle = _throttle(this.readFromCache, 1000);
|
private readFromCacheThrottle = _throttle(this.readFromCache, READ_CACHE_BATCH_TIME);
|
||||||
private async readFromCache() {
|
private async readFromCache() {
|
||||||
if (this.readFromCachePromises.size === 0) return;
|
if (this.readFromCachePromises.size === 0) return;
|
||||||
|
|
||||||
@ -187,7 +193,7 @@ class ChannelMetadataService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private writeCacheQueue = new Map<string, NostrEvent>();
|
private writeCacheQueue = new Map<string, NostrEvent>();
|
||||||
private writeToCacheThrottle = _throttle(this.writeToCache, 1000);
|
private writeToCacheThrottle = _throttle(this.writeToCache, WRITE_CACHE_BATCH_TIME);
|
||||||
private async writeToCache() {
|
private async writeToCache() {
|
||||||
if (this.writeCacheQueue.size === 0) return;
|
if (this.writeCacheQueue.size === 0) return;
|
||||||
|
|
||||||
@ -224,7 +230,11 @@ class ChannelMetadataService {
|
|||||||
private requestChannelMetadataFromRelays(relays: string[], channelId: string) {
|
private requestChannelMetadataFromRelays(relays: string[], channelId: string) {
|
||||||
const sub = this.metadata.get(channelId);
|
const sub = this.metadata.get(channelId);
|
||||||
|
|
||||||
for (const relay of relays) {
|
const relayUrls = Array.from(relays);
|
||||||
|
if (localCacheRelayService.enabled) {
|
||||||
|
relayUrls.unshift(LOCAL_CACHE_RELAY);
|
||||||
|
}
|
||||||
|
for (const relay of relayUrls) {
|
||||||
const request = this.loaders.get(relay).requestMetadata(channelId);
|
const request = this.loaders.get(relay).requestMetadata(channelId);
|
||||||
|
|
||||||
sub.connectWithHandler(request, (event, next, current) => {
|
sub.connectWithHandler(request, (event, next, current) => {
|
||||||
|
@ -8,6 +8,7 @@ import relayScoreboardService from "./relay-scoreboard";
|
|||||||
import { logger } from "../helpers/debug";
|
import { logger } from "../helpers/debug";
|
||||||
import { matchFilter, matchFilters } from "nostr-tools";
|
import { matchFilter, matchFilters } from "nostr-tools";
|
||||||
import { NostrEvent } from "../types/nostr-event";
|
import { NostrEvent } from "../types/nostr-event";
|
||||||
|
import localCacheRelayService, { LOCAL_CACHE_RELAY } from "./local-cache-relay";
|
||||||
|
|
||||||
function hashFilter(filter: NostrRequestFilter) {
|
function hashFilter(filter: NostrRequestFilter) {
|
||||||
// const encoder = new TextEncoder();
|
// const encoder = new TextEncoder();
|
||||||
@ -41,7 +42,10 @@ class EventExistsService {
|
|||||||
if (!this.filters.has(key)) this.filters.set(key, filter);
|
if (!this.filters.has(key)) this.filters.set(key, filter);
|
||||||
|
|
||||||
if (sub.value !== true) {
|
if (sub.value !== true) {
|
||||||
for (const url of relays) {
|
const relayUrls = Array.from(relays);
|
||||||
|
if (localCacheRelayService.enabled) relayUrls.unshift(LOCAL_CACHE_RELAY);
|
||||||
|
|
||||||
|
for (const url of relayUrls) {
|
||||||
if (!asked.has(url) && !pending.has(url)) {
|
if (!asked.has(url) && !pending.has(url)) {
|
||||||
pending.add(url);
|
pending.add(url);
|
||||||
}
|
}
|
||||||
|
49
src/services/event-relay-hint.ts
Normal file
49
src/services/event-relay-hint.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
function pickBestRelays(relays: string[]) {
|
||||||
|
// ignore local relays
|
||||||
|
relays = relays.filter((url) => !url.includes("://localhost") && !url.includes("://192.168"));
|
||||||
|
|
||||||
|
return relayScoreboardService.getRankedRelays(relays);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAddressPointerRelayHint(pointer: AddressPointer): string | undefined {
|
||||||
|
let relays = getEventRelays(createCoordinate(pointer.kind, pointer.pubkey, pointer.identifier)).value;
|
||||||
|
return pickBestRelays(relays)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEventPointerRelayHints(pointerOrId: string | EventPointer): string[] {
|
||||||
|
let relays =
|
||||||
|
typeof pointerOrId === "string" ? getEventRelays(pointerOrId).value : getEventRelays(pointerOrId.id).value;
|
||||||
|
return pickBestRelays(relays);
|
||||||
|
}
|
||||||
|
function getEventPointerRelayHint(pointerOrId: string | EventPointer): string | undefined {
|
||||||
|
return getEventPointerRelayHints(pointerOrId)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEventRelayHint(event: NostrEvent): string | undefined {
|
||||||
|
return getEventRelayHints(event, 1)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEventRelayHints(event: NostrEvent, count = 2): string[] {
|
||||||
|
// NOTE: in the future try to use the events authors relays
|
||||||
|
|
||||||
|
let relays = getEventRelays(event.id).value;
|
||||||
|
|
||||||
|
return pickBestRelays(relays).slice(0, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
const relayHintService = {
|
||||||
|
getEventRelayHints,
|
||||||
|
getEventRelayHint,
|
||||||
|
getEventPointerRelayHint,
|
||||||
|
getEventPointerRelayHints,
|
||||||
|
getAddressPointerRelayHint,
|
||||||
|
pickBestRelays,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default relayHintService;
|
62
src/services/local-cache-relay.ts
Normal file
62
src/services/local-cache-relay.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { logger } from "../helpers/debug";
|
||||||
|
import { NostrEvent } from "../types/nostr-event";
|
||||||
|
import relayPoolService from "./relay-pool";
|
||||||
|
import _throttle from "lodash.throttle";
|
||||||
|
|
||||||
|
const enabled = !!localStorage.getItem("enable-cache-relay");
|
||||||
|
export const LOCAL_CACHE_RELAY = "ws://localhost:7000";
|
||||||
|
|
||||||
|
const wroteEvents = new Set<string>();
|
||||||
|
const writeQueue: NostrEvent[] = [];
|
||||||
|
|
||||||
|
const BATCH_WRITE = 100;
|
||||||
|
|
||||||
|
const log = logger.extend(`LocalCacheRelay`);
|
||||||
|
async function flush() {
|
||||||
|
for (let i = 0; i < BATCH_WRITE; i++) {
|
||||||
|
const e = writeQueue.pop();
|
||||||
|
if (!e) continue;
|
||||||
|
relayPoolService.requestRelay(LOCAL_CACHE_RELAY).send(["EVENT", e]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function report() {
|
||||||
|
if (writeQueue.length) {
|
||||||
|
log(`${writeQueue.length} events in write queue`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToQueue(e: NostrEvent) {
|
||||||
|
if (!enabled) return;
|
||||||
|
if (!wroteEvents.has(e.id)) {
|
||||||
|
wroteEvents.add(e.id);
|
||||||
|
writeQueue.push(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enabled) {
|
||||||
|
log("Enabled");
|
||||||
|
relayPoolService.onRelayCreated.subscribe((relay) => {
|
||||||
|
if (relay.url !== LOCAL_CACHE_RELAY) {
|
||||||
|
relay.onEvent.subscribe((incomingEvent) => addToQueue(incomingEvent.body));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const localCacheRelayService = {
|
||||||
|
enabled,
|
||||||
|
addToQueue,
|
||||||
|
};
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
if (enabled) flush();
|
||||||
|
}, 1000);
|
||||||
|
setInterval(() => {
|
||||||
|
if (enabled) report();
|
||||||
|
}, 1000 * 10);
|
||||||
|
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
//@ts-ignore
|
||||||
|
window.localCacheRelayService = localCacheRelayService;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default localCacheRelayService;
|
@ -12,6 +12,7 @@ import db from "./db";
|
|||||||
import { nameOrPubkey } from "./user-metadata";
|
import { nameOrPubkey } from "./user-metadata";
|
||||||
import { getEventCoordinate } from "../helpers/nostr/events";
|
import { getEventCoordinate } from "../helpers/nostr/events";
|
||||||
import createDefer, { Deferred } from "../classes/deferred";
|
import createDefer, { Deferred } from "../classes/deferred";
|
||||||
|
import localCacheRelayService, { LOCAL_CACHE_RELAY } from "./local-cache-relay";
|
||||||
|
|
||||||
type Pubkey = string;
|
type Pubkey = string;
|
||||||
type Relay = string;
|
type Relay = string;
|
||||||
@ -32,6 +33,8 @@ export function createCoordinate(kind: number, pubkey: string, d?: string) {
|
|||||||
return `${kind}:${pubkey}${d ? ":" + d : ""}`;
|
return `${kind}:${pubkey}${d ? ":" + d : ""}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const RELAY_REQUEST_BATCH_TIME = 1000;
|
||||||
|
|
||||||
/** This class is ued to batch requests to a single relay */
|
/** This class is ued to batch requests to a single relay */
|
||||||
class ReplaceableEventRelayLoader {
|
class ReplaceableEventRelayLoader {
|
||||||
private subscription: NostrSubscription;
|
private subscription: NostrSubscription;
|
||||||
@ -85,7 +88,7 @@ class ReplaceableEventRelayLoader {
|
|||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateThrottle = _throttle(this.update, 1000);
|
updateThrottle = _throttle(this.update, RELAY_REQUEST_BATCH_TIME);
|
||||||
update() {
|
update() {
|
||||||
let needsUpdate = false;
|
let needsUpdate = false;
|
||||||
for (const cord of this.requestNext) {
|
for (const cord of this.requestNext) {
|
||||||
@ -144,6 +147,9 @@ class ReplaceableEventRelayLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const READ_CACHE_BATCH_TIME = 250;
|
||||||
|
const WRITE_CACHE_BATCH_TIME = 250;
|
||||||
|
|
||||||
class ReplaceableEventLoaderService {
|
class ReplaceableEventLoaderService {
|
||||||
private events = new SuperMap<Pubkey, Subject<NostrEvent>>(() => new Subject<NostrEvent>());
|
private events = new SuperMap<Pubkey, Subject<NostrEvent>>(() => new Subject<NostrEvent>());
|
||||||
|
|
||||||
@ -170,7 +176,7 @@ class ReplaceableEventLoaderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private readFromCachePromises = new Map<string, Deferred<boolean>>();
|
private readFromCachePromises = new Map<string, Deferred<boolean>>();
|
||||||
private readFromCacheThrottle = _throttle(this.readFromCache, 1000);
|
private readFromCacheThrottle = _throttle(this.readFromCache, READ_CACHE_BATCH_TIME);
|
||||||
private async readFromCache() {
|
private async readFromCache() {
|
||||||
if (this.readFromCachePromises.size === 0) return;
|
if (this.readFromCachePromises.size === 0) return;
|
||||||
|
|
||||||
@ -210,7 +216,7 @@ class ReplaceableEventLoaderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private writeCacheQueue = new Map<string, NostrEvent>();
|
private writeCacheQueue = new Map<string, NostrEvent>();
|
||||||
private writeToCacheThrottle = _throttle(this.writeToCache, 1000);
|
private writeToCacheThrottle = _throttle(this.writeToCache, WRITE_CACHE_BATCH_TIME);
|
||||||
private async writeToCache() {
|
private async writeToCache() {
|
||||||
if (this.writeCacheQueue.size === 0) return;
|
if (this.writeCacheQueue.size === 0) return;
|
||||||
|
|
||||||
@ -248,7 +254,10 @@ class ReplaceableEventLoaderService {
|
|||||||
const cord = createCoordinate(kind, pubkey, d);
|
const cord = createCoordinate(kind, pubkey, d);
|
||||||
const sub = this.events.get(cord);
|
const sub = this.events.get(cord);
|
||||||
|
|
||||||
for (const relay of relays) {
|
const relayUrls = Array.from(relays);
|
||||||
|
if (localCacheRelayService.enabled) relayUrls.unshift(LOCAL_CACHE_RELAY);
|
||||||
|
|
||||||
|
for (const relay of relayUrls) {
|
||||||
const request = this.loaders.get(relay).requestEvent(kind, pubkey, d);
|
const request = this.loaders.get(relay).requestEvent(kind, pubkey, d);
|
||||||
|
|
||||||
sub.connectWithHandler(request, (event, next, current) => {
|
sub.connectWithHandler(request, (event, next, current) => {
|
||||||
|
@ -5,6 +5,9 @@ import Subject from "../classes/subject";
|
|||||||
import SuperMap from "../classes/super-map";
|
import SuperMap from "../classes/super-map";
|
||||||
import { safeRelayUrls } from "../helpers/url";
|
import { safeRelayUrls } from "../helpers/url";
|
||||||
import { NostrEvent } from "../types/nostr-event";
|
import { NostrEvent } from "../types/nostr-event";
|
||||||
|
import localCacheRelayService, { LOCAL_CACHE_RELAY } from "./local-cache-relay";
|
||||||
|
|
||||||
|
const RELAY_REQUEST_BATCH_TIME = 1000;
|
||||||
|
|
||||||
class SingleEventService {
|
class SingleEventService {
|
||||||
private cache = new SuperMap<string, Subject<NostrEvent>>(() => new Subject());
|
private cache = new SuperMap<string, Subject<NostrEvent>>(() => new Subject());
|
||||||
@ -14,7 +17,9 @@ class SingleEventService {
|
|||||||
const subject = this.cache.get(id);
|
const subject = this.cache.get(id);
|
||||||
if (subject.value) return subject;
|
if (subject.value) return subject;
|
||||||
|
|
||||||
this.pending.set(id, this.pending.get(id)?.concat(safeRelayUrls(relays)) ?? safeRelayUrls(relays));
|
const newUrls = safeRelayUrls(relays);
|
||||||
|
if (localCacheRelayService.enabled) newUrls.push(LOCAL_CACHE_RELAY);
|
||||||
|
this.pending.set(id, this.pending.get(id)?.concat(newUrls) ?? newUrls);
|
||||||
this.batchRequestsThrottle();
|
this.batchRequestsThrottle();
|
||||||
|
|
||||||
return subject;
|
return subject;
|
||||||
@ -24,7 +29,7 @@ class SingleEventService {
|
|||||||
this.cache.get(event.id).next(event);
|
this.cache.get(event.id).next(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
private batchRequestsThrottle = _throttle(this.batchRequests, 1000 * 2);
|
private batchRequestsThrottle = _throttle(this.batchRequests, RELAY_REQUEST_BATCH_TIME);
|
||||||
batchRequests() {
|
batchRequests() {
|
||||||
if (this.pending.size === 0) return;
|
if (this.pending.size === 0) return;
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ import SuperMap from "../classes/super-map";
|
|||||||
import Subject from "../classes/subject";
|
import Subject from "../classes/subject";
|
||||||
import replaceableEventLoaderService, { RequestOptions } from "./replaceable-event-requester";
|
import replaceableEventLoaderService, { RequestOptions } from "./replaceable-event-requester";
|
||||||
|
|
||||||
|
const WRITE_USER_SEARCH_BATCH_TIME = 500;
|
||||||
|
|
||||||
class UserMetadataService {
|
class UserMetadataService {
|
||||||
private parsedSubjects = new SuperMap<string, Subject<Kind0ParsedContent>>((pubkey) => {
|
private parsedSubjects = new SuperMap<string, Subject<Kind0ParsedContent>>((pubkey) => {
|
||||||
const sub = new Subject<Kind0ParsedContent>();
|
const sub = new Subject<Kind0ParsedContent>();
|
||||||
@ -34,7 +36,7 @@ class UserMetadataService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private writeSearchQueue = new Set<string>();
|
private writeSearchQueue = new Set<string>();
|
||||||
private writeSearchDataThrottle = _throttle(this.writeSearchData.bind(this));
|
private writeSearchDataThrottle = _throttle(this.writeSearchData.bind(this), WRITE_USER_SEARCH_BATCH_TIME);
|
||||||
private async writeSearchData() {
|
private async writeSearchData() {
|
||||||
if (this.writeSearchQueue.size === 0) return;
|
if (this.writeSearchQueue.size === 0) return;
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import { COMMUNITY_DEFINITION_KIND } from "../../helpers/nostr/communities";
|
|||||||
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
||||||
import { Spinner } from "@chakra-ui/react";
|
import { Spinner } from "@chakra-ui/react";
|
||||||
import CommunityHomePage from "./community-home";
|
import CommunityHomePage from "./community-home";
|
||||||
import { getPubkey, isHexKey, safeDecode } from "../../helpers/nip19";
|
import { getPubkeyFromDecodeResult, isHexKey, safeDecode } from "../../helpers/nip19";
|
||||||
|
|
||||||
function useCommunityPointer() {
|
function useCommunityPointer() {
|
||||||
const { community, pubkey } = useParams();
|
const { community, pubkey } = useParams();
|
||||||
@ -12,7 +12,7 @@ function useCommunityPointer() {
|
|||||||
if (decoded) {
|
if (decoded) {
|
||||||
if (decoded.type === "naddr" && decoded.data.kind === COMMUNITY_DEFINITION_KIND) return decoded.data;
|
if (decoded.type === "naddr" && decoded.data.kind === COMMUNITY_DEFINITION_KIND) return decoded.data;
|
||||||
} else if (community && pubkey) {
|
} else if (community && pubkey) {
|
||||||
const hexPubkey = isHexKey(pubkey) ? pubkey : getPubkey(safeDecode(pubkey));
|
const hexPubkey = isHexKey(pubkey) ? pubkey : getPubkeyFromDecodeResult(safeDecode(pubkey));
|
||||||
if (!hexPubkey) return;
|
if (!hexPubkey) return;
|
||||||
|
|
||||||
return { kind: COMMUNITY_DEFINITION_KIND, pubkey: hexPubkey, identifier: community };
|
return { kind: COMMUNITY_DEFINITION_KIND, pubkey: hexPubkey, identifier: community };
|
||||||
|
@ -4,13 +4,14 @@ import dayjs from "dayjs";
|
|||||||
|
|
||||||
import { DraftNostrEvent, NostrEvent } from "../../../types/nostr-event";
|
import { DraftNostrEvent, NostrEvent } from "../../../types/nostr-event";
|
||||||
import { StarEmptyIcon, StarFullIcon } from "../../../components/icons";
|
import { StarEmptyIcon, StarFullIcon } from "../../../components/icons";
|
||||||
import { draftAddCoordinate, draftRemoveCoordinate, getEventCoordinate } from "../../../helpers/nostr/events";
|
import { getEventCoordinate } from "../../../helpers/nostr/events";
|
||||||
import { useSigningContext } from "../../../providers/signing-provider";
|
import { useSigningContext } from "../../../providers/signing-provider";
|
||||||
import NostrPublishAction from "../../../classes/nostr-publish-action";
|
import NostrPublishAction from "../../../classes/nostr-publish-action";
|
||||||
import clientRelaysService from "../../../services/client-relays";
|
import clientRelaysService from "../../../services/client-relays";
|
||||||
import replaceableEventLoaderService from "../../../services/replaceable-event-requester";
|
import replaceableEventLoaderService from "../../../services/replaceable-event-requester";
|
||||||
import { USER_EMOJI_LIST_KIND } from "../../../helpers/nostr/emoji-packs";
|
import { USER_EMOJI_LIST_KIND } from "../../../helpers/nostr/emoji-packs";
|
||||||
import useFavoriteEmojiPacks from "../../../hooks/use-favorite-emoji-packs";
|
import useFavoriteEmojiPacks from "../../../hooks/use-favorite-emoji-packs";
|
||||||
|
import { listAddCoordinate, listRemoveCoordinate } from "../../../helpers/nostr/lists";
|
||||||
|
|
||||||
export default function EmojiPackFavoriteButton({
|
export default function EmojiPackFavoriteButton({
|
||||||
pack,
|
pack,
|
||||||
@ -33,7 +34,7 @@ export default function EmojiPackFavoriteButton({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const draft = isFavorite ? draftRemoveCoordinate(prev, coordinate) : draftAddCoordinate(prev, coordinate);
|
const draft = isFavorite ? listRemoveCoordinate(prev, coordinate) : listAddCoordinate(prev, coordinate);
|
||||||
const signed = await requestSignature(draft);
|
const signed = await requestSignature(draft);
|
||||||
const pub = new NostrPublishAction(
|
const pub = new NostrPublishAction(
|
||||||
isFavorite ? "Unfavorite Emoji pack" : "Favorite emoji pack",
|
isFavorite ? "Unfavorite Emoji pack" : "Favorite emoji pack",
|
||||||
|
@ -4,13 +4,13 @@ import dayjs from "dayjs";
|
|||||||
|
|
||||||
import { DraftNostrEvent, NostrEvent } from "../../../types/nostr-event";
|
import { DraftNostrEvent, NostrEvent } from "../../../types/nostr-event";
|
||||||
import { StarEmptyIcon, StarFullIcon } from "../../../components/icons";
|
import { StarEmptyIcon, StarFullIcon } from "../../../components/icons";
|
||||||
import { draftAddCoordinate, draftRemoveCoordinate, getEventCoordinate } from "../../../helpers/nostr/events";
|
import { getEventCoordinate } from "../../../helpers/nostr/events";
|
||||||
import { useSigningContext } from "../../../providers/signing-provider";
|
import { useSigningContext } from "../../../providers/signing-provider";
|
||||||
import NostrPublishAction from "../../../classes/nostr-publish-action";
|
import NostrPublishAction from "../../../classes/nostr-publish-action";
|
||||||
import clientRelaysService from "../../../services/client-relays";
|
import clientRelaysService from "../../../services/client-relays";
|
||||||
import replaceableEventLoaderService from "../../../services/replaceable-event-requester";
|
import replaceableEventLoaderService from "../../../services/replaceable-event-requester";
|
||||||
import useFavoriteLists, { FAVORITE_LISTS_IDENTIFIER } from "../../../hooks/use-favorite-lists";
|
import useFavoriteLists, { FAVORITE_LISTS_IDENTIFIER } from "../../../hooks/use-favorite-lists";
|
||||||
import { NOTE_LIST_KIND, isSpecialListKind } from "../../../helpers/nostr/lists";
|
import { NOTE_LIST_KIND, isSpecialListKind, listAddCoordinate, listRemoveCoordinate } from "../../../helpers/nostr/lists";
|
||||||
|
|
||||||
export default function ListFavoriteButton({
|
export default function ListFavoriteButton({
|
||||||
list,
|
list,
|
||||||
@ -38,7 +38,7 @@ export default function ListFavoriteButton({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const draft = isFavorite ? draftRemoveCoordinate(prev, coordinate) : draftAddCoordinate(prev, coordinate);
|
const draft = isFavorite ? listRemoveCoordinate(prev, coordinate) : listAddCoordinate(prev, coordinate);
|
||||||
const signed = await requestSignature(draft);
|
const signed = await requestSignature(draft);
|
||||||
const pub = new NostrPublishAction("Favorite list", clientRelaysService.getWriteUrls(), signed);
|
const pub = new NostrPublishAction("Favorite list", clientRelaysService.getWriteUrls(), signed);
|
||||||
replaceableEventLoaderService.handleEvent(signed);
|
replaceableEventLoaderService.handleEvent(signed);
|
||||||
|
@ -36,7 +36,7 @@ import { UserDnsIdentityIcon } from "../../../components/user-dns-identity-icon"
|
|||||||
import NoteProxyLink from "../../../components/note/components/note-proxy-link";
|
import NoteProxyLink from "../../../components/note/components/note-proxy-link";
|
||||||
import { NoteDetailsButton } from "../../../components/note/components/note-details-button";
|
import { NoteDetailsButton } from "../../../components/note/components/note-details-button";
|
||||||
import EventInteractionDetailsModal from "../../../components/event-interactions-modal";
|
import EventInteractionDetailsModal from "../../../components/event-interactions-modal";
|
||||||
import { getNeventCodeWithRelays } from "../../../helpers/nip19";
|
import { getSharableEventAddress } from "../../../helpers/nip19";
|
||||||
import { useRegisterIntersectionEntity } from "../../../providers/intersection-observer";
|
import { useRegisterIntersectionEntity } from "../../../providers/intersection-observer";
|
||||||
import useAppSettings from "../../../hooks/use-app-settings";
|
import useAppSettings from "../../../hooks/use-app-settings";
|
||||||
import useThreadColorLevelProps from "../../../hooks/use-thread-color-level-props";
|
import useThreadColorLevelProps from "../../../hooks/use-thread-color-level-props";
|
||||||
@ -80,7 +80,7 @@ export const ThreadPost = memo(({ post, initShowReplies, focusId, level = -1 }:
|
|||||||
<UserAvatarLink pubkey={post.event.pubkey} size="sm" />
|
<UserAvatarLink pubkey={post.event.pubkey} size="sm" />
|
||||||
<UserLink pubkey={post.event.pubkey} fontWeight="bold" isTruncated />
|
<UserLink pubkey={post.event.pubkey} fontWeight="bold" isTruncated />
|
||||||
<UserDnsIdentityIcon pubkey={post.event.pubkey} onlyIcon />
|
<UserDnsIdentityIcon pubkey={post.event.pubkey} onlyIcon />
|
||||||
<Link as={RouterLink} whiteSpace="nowrap" color="current" to={`/n/${getNeventCodeWithRelays(post.event.id)}`}>
|
<Link as={RouterLink} whiteSpace="nowrap" color="current" to={`/n/${getSharableEventAddress(post.event)}`}>
|
||||||
<Timestamp timestamp={post.event.created_at} />
|
<Timestamp timestamp={post.event.created_at} />
|
||||||
</Link>
|
</Link>
|
||||||
{replies.length > 0 ? (
|
{replies.length > 0 ? (
|
||||||
|
@ -13,13 +13,28 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
Link,
|
Link,
|
||||||
FormErrorMessage,
|
FormErrorMessage,
|
||||||
|
Code,
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
ModalOverlay,
|
||||||
|
ModalContent,
|
||||||
|
ModalHeader,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalBody,
|
||||||
|
useDisclosure,
|
||||||
|
Text,
|
||||||
|
Heading,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { safeUrl } from "../../helpers/parse";
|
import { safeUrl } from "../../helpers/parse";
|
||||||
import { AppSettings } from "../../services/settings/migrations";
|
import { AppSettings } from "../../services/settings/migrations";
|
||||||
import { PerformanceIcon } from "../../components/icons";
|
import { PerformanceIcon } from "../../components/icons";
|
||||||
|
import { useLocalStorage } from "react-use";
|
||||||
|
import { LOCAL_CACHE_RELAY } from "../../services/local-cache-relay";
|
||||||
|
|
||||||
export default function PerformanceSettings() {
|
export default function PerformanceSettings() {
|
||||||
const { register, formState } = useFormContext<AppSettings>();
|
const { register, formState } = useFormContext<AppSettings>();
|
||||||
|
const [localCacheRelay, setLocalCacheRelay] = useLocalStorage<boolean>("enable-cache-relay");
|
||||||
|
const cacheDetails = useDisclosure();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccordionItem>
|
<AccordionItem>
|
||||||
@ -95,6 +110,58 @@ export default function PerformanceSettings() {
|
|||||||
</Flex>
|
</Flex>
|
||||||
<FormHelperText>Enabled: show signature verification on notes</FormHelperText>
|
<FormHelperText>Enabled: show signature verification on notes</FormHelperText>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
<FormControl>
|
||||||
|
<Flex alignItems="center">
|
||||||
|
<FormLabel htmlFor="localCacheRelay" mb="0">
|
||||||
|
Local Cache Relay
|
||||||
|
</FormLabel>
|
||||||
|
<Switch
|
||||||
|
id="localCacheRelay"
|
||||||
|
isChecked={localCacheRelay}
|
||||||
|
onChange={(e) => setLocalCacheRelay(e.target.checked)}
|
||||||
|
/>
|
||||||
|
<Button onClick={cacheDetails.onOpen} variant="link" ml="4">
|
||||||
|
Details
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
<FormHelperText>Enabled: Use a local relay as a caching service</FormHelperText>
|
||||||
|
|
||||||
|
<Modal isOpen={cacheDetails.isOpen} onClose={cacheDetails.onClose} size="4xl">
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader p="4">Local cache relay</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody px="4" pb="4" pt="0">
|
||||||
|
<Text>
|
||||||
|
When this option is enabled noStrudel will mirror every event it sees to the relay. It will also try
|
||||||
|
to load as much data from the relay first before reaching out to other relays.
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
For security reasons noStrudel will only use <Code>ws://localhost:7000</Code> as the cache relay.
|
||||||
|
</Text>
|
||||||
|
<Heading size="md" mt="2">
|
||||||
|
Linux setup instructions
|
||||||
|
</Heading>
|
||||||
|
<Text>
|
||||||
|
You can run a local relay using{" "}
|
||||||
|
<Link href="https://www.docker.com/get-started/" isExternal>
|
||||||
|
docker
|
||||||
|
</Link>{" "}
|
||||||
|
and{" "}
|
||||||
|
<Link href="https://hub.docker.com/r/scsibug/nostr-rs-relay" isExternal>
|
||||||
|
nostr-rs-relay
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
<Text mt="2">1. Create a folder for the data</Text>
|
||||||
|
<Code>mkdir ~/.nostr-relay/data -p -m 777</Code>
|
||||||
|
<Text mt="2">2. Start the relay</Text>
|
||||||
|
<Code>
|
||||||
|
docker run --rm -it -p 7000:8080 -v ~/.nostr-relay/data:/usr/src/app/db scsibug/nostr-rs-relay
|
||||||
|
</Code>
|
||||||
|
</ModalBody>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</FormControl>
|
||||||
</Flex>
|
</Flex>
|
||||||
</AccordionPanel>
|
</AccordionPanel>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
@ -7,7 +7,7 @@ import { NostrEvent } from "../../../types/nostr-event";
|
|||||||
import Timestamp from "../../../components/timestamp";
|
import Timestamp from "../../../components/timestamp";
|
||||||
import { UserLink } from "../../../components/user-link";
|
import { UserLink } from "../../../components/user-link";
|
||||||
import Magnet from "../../../components/icons/magnet";
|
import Magnet from "../../../components/icons/magnet";
|
||||||
import { getNeventCodeWithRelays } from "../../../helpers/nip19";
|
import { getNeventForEventId } from "../../../helpers/nip19";
|
||||||
import { useRegisterIntersectionEntity } from "../../../providers/intersection-observer";
|
import { useRegisterIntersectionEntity } from "../../../providers/intersection-observer";
|
||||||
import { getEventUID } from "../../../helpers/nostr/events";
|
import { getEventUID } from "../../../helpers/nostr/events";
|
||||||
import { formatBytes } from "../../../helpers/number";
|
import { formatBytes } from "../../../helpers/number";
|
||||||
@ -58,7 +58,7 @@ function TorrentTableRow({ torrent }: { torrent: NostrEvent }) {
|
|||||||
))}
|
))}
|
||||||
</Td>
|
</Td>
|
||||||
<Td>
|
<Td>
|
||||||
<Link as={RouterLink} to={`/torrents/${getNeventCodeWithRelays(torrent.id)}`} isTruncated maxW="lg">
|
<Link as={RouterLink} to={`/torrents/${getNeventForEventId(torrent.id)}`} isTruncated maxW="lg">
|
||||||
{getTorrentTitle(torrent)}
|
{getTorrentTitle(torrent)}
|
||||||
</Link>
|
</Link>
|
||||||
</Td>
|
</Td>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user