mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-17 21:31:43 +01:00
Move core logic out into applesauce packages
This commit is contained in:
parent
6e6baa7a98
commit
4d0d770db2
5
.changeset/soft-seahorses-tie.md
Normal file
5
.changeset/soft-seahorses-tie.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Move core logic out into applesauce packages
|
@ -33,6 +33,7 @@
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@getalby/bitcoin-connect": "^3.6.2",
|
||||
"@getalby/bitcoin-connect-react": "^3.6.2",
|
||||
"@noble/ciphers": "^1.0.0",
|
||||
"@noble/curves": "^1.3.0",
|
||||
"@noble/hashes": "^1.3.2",
|
||||
"@noble/secp256k1": "^1.7.0",
|
||||
@ -41,6 +42,7 @@
|
||||
"@uiw/codemirror-theme-github": "^4.23.0",
|
||||
"@uiw/react-codemirror": "^4.23.0",
|
||||
"@webscopeio/react-textarea-autocomplete": "^4.9.2",
|
||||
"applesauce-core": "link:../applesauce/packages/core",
|
||||
"bech32": "^2.0.0",
|
||||
"blossom-client-sdk": "^0.7.0",
|
||||
"blossom-drive-sdk": "^0.4.0",
|
||||
|
@ -9,6 +9,7 @@ import Process from "./process";
|
||||
import BracketsX from "../components/icons/brackets-x";
|
||||
import processManager from "../services/process-manager";
|
||||
import createDefer, { Deferred } from "./deferred";
|
||||
import { eventStore } from "../services/event-store";
|
||||
|
||||
/** This class is used to batch requests for single events from a relay */
|
||||
export default class BatchEventLoader {
|
||||
@ -72,6 +73,8 @@ export default class BatchEventLoader {
|
||||
);
|
||||
|
||||
private handleEvent(event: NostrEvent) {
|
||||
event = eventStore.add(event, this.relay.url);
|
||||
|
||||
const key = event.id;
|
||||
|
||||
this.events.addEvent(event);
|
||||
|
@ -11,6 +11,7 @@ import createDefer, { Deferred } from "./deferred";
|
||||
import Dataflow04 from "../components/icons/dataflow-04";
|
||||
import SuperMap from "./super-map";
|
||||
import Subject from "./subject";
|
||||
import { eventStore } from "../services/event-store";
|
||||
|
||||
/** Batches requests for events with #d tags from a single relay */
|
||||
export default class BatchIdentifierLoader {
|
||||
@ -81,6 +82,8 @@ export default class BatchIdentifierLoader {
|
||||
);
|
||||
|
||||
handleEvent(event: NostrEvent) {
|
||||
event = eventStore.add(event, this.relay.url);
|
||||
|
||||
// add event to cache
|
||||
for (const tag of event.tags) {
|
||||
if (tag[0] === "d" && tag[1]) {
|
||||
|
@ -10,6 +10,7 @@ import Process from "./process";
|
||||
import BracketsX from "../components/icons/brackets-x";
|
||||
import processManager from "../services/process-manager";
|
||||
import createDefer, { Deferred } from "./deferred";
|
||||
import { eventStore } from "../services/event-store";
|
||||
|
||||
export function createCoordinate(kind: number, pubkey: string, d?: string) {
|
||||
return `${kind}:${pubkey}${d ? ":" + d : ""}`;
|
||||
@ -78,6 +79,8 @@ export default class BatchKindPubkeyLoader {
|
||||
);
|
||||
|
||||
private handleEvent(event: NostrEvent) {
|
||||
event = eventStore.add(event, this.relay.url);
|
||||
|
||||
const key = getEventUID(event);
|
||||
|
||||
const defer = this.pending.get(key);
|
||||
|
@ -10,6 +10,7 @@ import createDefer, { Deferred } from "./deferred";
|
||||
import Dataflow04 from "../components/icons/dataflow-04";
|
||||
import SuperMap from "./super-map";
|
||||
import Subject from "./subject";
|
||||
import { eventStore } from "../services/event-store";
|
||||
|
||||
/** Batches requests for events that reference another event (via #e tag) from a single relay */
|
||||
export default class BatchRelationLoader {
|
||||
@ -79,6 +80,8 @@ export default class BatchRelationLoader {
|
||||
);
|
||||
|
||||
handleEvent(event: NostrEvent) {
|
||||
event = eventStore.add(event, this.relay.url);
|
||||
|
||||
// add event to cache
|
||||
const updateIds = new Set<string>();
|
||||
for (const tag of event.tags) {
|
||||
|
@ -15,6 +15,7 @@ import relayPoolService from "../services/relay-pool";
|
||||
import Process from "./process";
|
||||
import processManager from "../services/process-manager";
|
||||
import LayersThree01 from "../components/icons/layers-three-01";
|
||||
import { eventStore } from "../services/event-store";
|
||||
|
||||
const DEFAULT_CHUNK_SIZE = 100;
|
||||
|
||||
@ -111,6 +112,9 @@ export default class ChunkedRequest {
|
||||
|
||||
private handleEvent(event: NostrEvent) {
|
||||
if (!matchFilters(this.filters, event)) return;
|
||||
|
||||
event = eventStore.add(event, this.relay.url);
|
||||
|
||||
return this.events.addEvent(event);
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ import Process from "./process";
|
||||
import Dataflow01 from "../components/icons/dataflow-01";
|
||||
import processManager from "../services/process-manager";
|
||||
import { localRelay } from "../services/local-relay";
|
||||
import { eventStore } from "../services/event-store";
|
||||
|
||||
export default class MultiSubscription {
|
||||
static OPEN = "open";
|
||||
@ -94,7 +95,7 @@ export default class MultiSubscription {
|
||||
if (!subscription || !isFilterEqual(subscription.filters, this.filters) || subscription.closed) {
|
||||
if (!subscription) {
|
||||
subscription = new PersistentSubscription(relay, {
|
||||
onevent: (event) => this.handleEvent(event),
|
||||
onevent: (event) => this.handleEvent(eventStore.add(event, relay.url)),
|
||||
});
|
||||
|
||||
this.process.addChild(subscription.process);
|
||||
@ -114,7 +115,7 @@ 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, true),
|
||||
onevent: (event) => this.handleEvent(eventStore.add(event, localRelay!.url), true),
|
||||
});
|
||||
this.process.addChild(this.cacheSubscription.process);
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { NostrEvent, kinds, nip18, nip25 } from "nostr-tools";
|
||||
import _throttle from "lodash.throttle";
|
||||
import { throttle, stateful, StatefulObservable } from "applesauce-core/observable";
|
||||
|
||||
import EventStore from "./event-store";
|
||||
import { PersistentSubject } from "./subject";
|
||||
import { getThreadReferences, isPTagMentionedInContent, isReply, isRepost } from "../helpers/nostr/event";
|
||||
import { getParsedZap } from "../helpers/nostr/zaps";
|
||||
import singleEventService from "../services/single-event";
|
||||
@ -11,8 +10,8 @@ import clientRelaysService from "../services/client-relays";
|
||||
import { getPubkeysMentionedInContent } from "../helpers/nostr/post";
|
||||
import { TORRENT_COMMENT_KIND } from "../helpers/nostr/torrents";
|
||||
import { STREAM_CHAT_MESSAGE_KIND } from "../helpers/nostr/stream";
|
||||
import replaceableEventsService from "../services/replaceable-events";
|
||||
import { MUTE_LIST_KIND, getPubkeysFromList } from "../helpers/nostr/lists";
|
||||
import { eventStore, queryStore } from "../services/event-store";
|
||||
|
||||
export const typeSymbol = Symbol("notificationType");
|
||||
|
||||
@ -26,23 +25,40 @@ export enum NotificationType {
|
||||
export type CategorizedEvent = NostrEvent & { [typeSymbol]?: NotificationType };
|
||||
|
||||
export default class AccountNotifications {
|
||||
store: EventStore;
|
||||
pubkey: string;
|
||||
private subs: ZenObservable.Subscription[] = [];
|
||||
|
||||
timeline = new PersistentSubject<CategorizedEvent[]>([]);
|
||||
// timeline = new PersistentSubject<CategorizedEvent[]>([]);
|
||||
timeline: StatefulObservable<CategorizedEvent[]>;
|
||||
|
||||
constructor(pubkey: string, store: EventStore) {
|
||||
this.store = store;
|
||||
constructor(pubkey: string) {
|
||||
this.pubkey = pubkey;
|
||||
|
||||
this.subs.push(store.onEvent.subscribe(this.handleEvent.bind(this)));
|
||||
|
||||
for (const [_, event] of store.events) this.handleEvent(event);
|
||||
this.timeline = stateful(
|
||||
throttle(
|
||||
queryStore.getTimeline([
|
||||
{
|
||||
"#p": [pubkey],
|
||||
kinds: [
|
||||
kinds.ShortTextNote,
|
||||
kinds.Repost,
|
||||
kinds.GenericRepost,
|
||||
kinds.Reaction,
|
||||
kinds.Zap,
|
||||
TORRENT_COMMENT_KIND,
|
||||
kinds.LongFormArticle,
|
||||
],
|
||||
},
|
||||
]),
|
||||
50,
|
||||
).map((events) => events.map(this.handleEvent.bind(this)).filter(this.filterEvent.bind(this))),
|
||||
);
|
||||
}
|
||||
|
||||
private categorizeEvent(event: NostrEvent): CategorizedEvent {
|
||||
const e = event as CategorizedEvent;
|
||||
|
||||
if (e[typeSymbol]) return e;
|
||||
|
||||
if (event.kind === kinds.Zap) {
|
||||
e[typeSymbol] = NotificationType.Zap;
|
||||
} else if (event.kind === kinds.Reaction) {
|
||||
@ -69,84 +85,70 @@ export default class AccountNotifications {
|
||||
handleEvent(event: NostrEvent) {
|
||||
const e = this.categorizeEvent(event);
|
||||
|
||||
const getAndSubscribe = (eventId: string, relays?: string[]) => {
|
||||
const subject = singleEventService.requestEvent(
|
||||
eventId,
|
||||
RelaySet.from(clientRelaysService.readRelays.value, relays),
|
||||
);
|
||||
|
||||
subject.once(this.throttleUpdateTimeline);
|
||||
return subject.value;
|
||||
const loadEvent = (eventId: string, relays?: string[]) => {
|
||||
singleEventService.requestEvent(eventId, RelaySet.from(clientRelaysService.readRelays.value, relays));
|
||||
};
|
||||
|
||||
switch (e[typeSymbol]) {
|
||||
case NotificationType.Reply:
|
||||
const refs = getThreadReferences(e);
|
||||
if (refs.reply?.e?.id) getAndSubscribe(refs.reply.e.id, refs.reply.e.relays);
|
||||
if (refs.reply?.e?.id) loadEvent(refs.reply.e.id, refs.reply.e.relays);
|
||||
break;
|
||||
case NotificationType.Reaction: {
|
||||
const pointer = nip25.getReactedEventPointer(e);
|
||||
if (pointer?.id) getAndSubscribe(pointer.id, pointer.relays);
|
||||
if (pointer?.id) loadEvent(pointer.id, pointer.relays);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
throttleUpdateTimeline = _throttle(this.updateTimeline.bind(this), 200);
|
||||
updateTimeline() {
|
||||
const muteList = replaceableEventsService.getEvent(MUTE_LIST_KIND, this.pubkey).value;
|
||||
private filterEvent(event: CategorizedEvent) {
|
||||
if (!Object.hasOwn(event, typeSymbol)) return false;
|
||||
|
||||
// ignore if muted
|
||||
// TODO: this should be moved somewhere more performant
|
||||
const muteList = eventStore.getReplaceable(MUTE_LIST_KIND, this.pubkey);
|
||||
const mutedPubkeys = muteList ? getPubkeysFromList(muteList).map((p) => p.pubkey) : [];
|
||||
if (mutedPubkeys.includes(event.pubkey)) return false;
|
||||
|
||||
const sorted = this.store.getSortedEvents();
|
||||
// ignore if own
|
||||
if (event.pubkey === this.pubkey) return false;
|
||||
|
||||
const timeline: CategorizedEvent[] = [];
|
||||
for (const event of sorted) {
|
||||
if (!Object.hasOwn(event, typeSymbol)) continue;
|
||||
if (mutedPubkeys.includes(event.pubkey)) continue;
|
||||
if (event.pubkey === this.pubkey) continue;
|
||||
const e = event as CategorizedEvent;
|
||||
const e = event as CategorizedEvent;
|
||||
|
||||
switch (e[typeSymbol]) {
|
||||
case NotificationType.Reply:
|
||||
const refs = getThreadReferences(e);
|
||||
if (!refs.reply?.e?.id) break;
|
||||
if (refs.reply?.e?.author && refs.reply?.e?.author !== this.pubkey) break;
|
||||
const parent = singleEventService.getSubject(refs.reply.e.id).value;
|
||||
if (!parent || parent.pubkey !== this.pubkey) break;
|
||||
timeline.push(e);
|
||||
break;
|
||||
case NotificationType.Mention:
|
||||
timeline.push(e);
|
||||
break;
|
||||
case NotificationType.Repost: {
|
||||
const pointer = nip18.getRepostedEventPointer(e);
|
||||
if (pointer?.author !== this.pubkey) break;
|
||||
timeline.push(e);
|
||||
break;
|
||||
}
|
||||
case NotificationType.Reaction: {
|
||||
const pointer = nip25.getReactedEventPointer(e);
|
||||
if (!pointer) break;
|
||||
if (pointer.author !== this.pubkey) break;
|
||||
if (pointer.kind === kinds.EncryptedDirectMessage) break;
|
||||
const parent = singleEventService.getSubject(pointer.id).value;
|
||||
if (parent && parent.kind === kinds.EncryptedDirectMessage) break;
|
||||
timeline.push(e);
|
||||
break;
|
||||
}
|
||||
case NotificationType.Zap:
|
||||
const parsed = getParsedZap(e, true, true);
|
||||
if (parsed instanceof Error) break;
|
||||
if (!parsed.payment.amount) break;
|
||||
timeline.push(e);
|
||||
break;
|
||||
switch (e[typeSymbol]) {
|
||||
case NotificationType.Reply:
|
||||
const refs = getThreadReferences(e);
|
||||
if (!refs.reply?.e?.id) return false;
|
||||
if (refs.reply?.e?.author && refs.reply?.e?.author !== this.pubkey) return false;
|
||||
const parent = eventStore.getEvent(refs.reply.e.id);
|
||||
if (!parent || parent.pubkey !== this.pubkey) return false;
|
||||
break;
|
||||
case NotificationType.Mention:
|
||||
break;
|
||||
case NotificationType.Repost: {
|
||||
const pointer = nip18.getRepostedEventPointer(e);
|
||||
if (pointer?.author !== this.pubkey) return false;
|
||||
break;
|
||||
}
|
||||
case NotificationType.Reaction: {
|
||||
const pointer = nip25.getReactedEventPointer(e);
|
||||
if (!pointer) return false;
|
||||
if (pointer.author !== this.pubkey) return false;
|
||||
if (pointer.kind === kinds.EncryptedDirectMessage) return false;
|
||||
const parent = eventStore.getEvent(pointer.id);
|
||||
if (parent && parent.kind === kinds.EncryptedDirectMessage) return false;
|
||||
break;
|
||||
}
|
||||
case NotificationType.Zap:
|
||||
const parsed = getParsedZap(e, true, true);
|
||||
if (parsed instanceof Error) return false;
|
||||
if (!parsed.payment.amount) return false;
|
||||
break;
|
||||
}
|
||||
this.timeline.next(timeline);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
for (const sub of this.subs) sub.unsubscribe();
|
||||
this.subs = [];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import relayPoolService from "../services/relay-pool";
|
||||
import Process from "./process";
|
||||
import AlignHorizontalCentre02 from "../components/icons/align-horizontal-centre-02";
|
||||
import processManager from "../services/process-manager";
|
||||
import { eventStore } from "../services/event-store";
|
||||
|
||||
const BLOCK_SIZE = 100;
|
||||
|
||||
@ -81,6 +82,8 @@ export default class TimelineLoader {
|
||||
// if this is a replaceable event, mirror it over to the replaceable event service
|
||||
if (isReplaceable(event.kind)) replaceableEventsService.handleEvent(event);
|
||||
|
||||
eventStore.add(event);
|
||||
|
||||
this.events.addEvent(event);
|
||||
if (!fromCache && this.useCache && localRelay && !this.seenInCache.has(event.id)) localRelay.publish(event);
|
||||
|
||||
|
@ -4,14 +4,14 @@ import { Box, Button, ButtonGroup, Card, CardProps, Heading, IconButton, Link }
|
||||
import { getDecodedToken, Token, CashuMint } from "@cashu/cashu-ts";
|
||||
|
||||
import { CopyIconButton } from "../copy-icon-button";
|
||||
import useUserMetadata from "../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../hooks/use-user-profile";
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import { ECashIcon, WalletIcon } from "../icons";
|
||||
import { getMint } from "../../services/cashu-mints";
|
||||
|
||||
function RedeemButton({ token }: { token: string }) {
|
||||
const account = useCurrentAccount()!;
|
||||
const metadata = useUserMetadata(account.pubkey);
|
||||
const metadata = useUserProfile(account.pubkey);
|
||||
|
||||
const lnurl = metadata?.lud16 ?? "";
|
||||
const url = `https://redeem.cashu.me?token=${encodeURIComponent(token)}&lightning=${encodeURIComponent(
|
||||
|
@ -4,7 +4,7 @@ import { kinds, nip19 } from "nostr-tools";
|
||||
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { QuoteEventIcon } from "../icons";
|
||||
import useUserMetadata from "../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../hooks/use-user-profile";
|
||||
import { PostModalContext } from "../../providers/route/post-modal-provider";
|
||||
import relayHintService from "../../services/event-relay-hint";
|
||||
import { getParsedZap } from "../../helpers/nostr/zaps";
|
||||
@ -12,7 +12,7 @@ import { getParsedZap } from "../../helpers/nostr/zaps";
|
||||
export default function QuoteEventMenuItem({ event }: { event: NostrEvent }) {
|
||||
const toast = useToast();
|
||||
const address = useMemo(() => relayHintService.getSharableEventAddress(event), [event]);
|
||||
const metadata = useUserMetadata(event.pubkey);
|
||||
const metadata = useUserProfile(event.pubkey);
|
||||
const { openModal } = useContext(PostModalContext);
|
||||
|
||||
const share = useCallback(async () => {
|
||||
|
@ -3,14 +3,14 @@ import { MenuItem, useToast } from "@chakra-ui/react";
|
||||
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { ShareIcon } from "../icons";
|
||||
import useUserMetadata from "../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../hooks/use-user-profile";
|
||||
import { getDisplayName } from "../../helpers/nostr/user-metadata";
|
||||
import useShareableEventAddress from "../../hooks/use-shareable-event-address";
|
||||
|
||||
export default function ShareLinkMenuItem({ event }: { event: NostrEvent }) {
|
||||
const toast = useToast();
|
||||
const address = useShareableEventAddress(event);
|
||||
const metadata = useUserMetadata(event.pubkey);
|
||||
const metadata = useUserProfile(event.pubkey);
|
||||
|
||||
const handleClick = useCallback(async () => {
|
||||
const data: ShareData = {
|
||||
|
@ -17,10 +17,10 @@ import { nip19 } from "nostr-tools";
|
||||
|
||||
import UserAvatar from "./user/user-avatar";
|
||||
import { getDisplayName } from "../helpers/nostr/user-metadata";
|
||||
import useUserMetadata from "../hooks/use-user-metadata";
|
||||
import useUserProfile from "../hooks/use-user-profile";
|
||||
|
||||
function UserTag({ pubkey, ...props }: { pubkey: string } & Omit<TagProps, "children">) {
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
const metadata = useUserProfile(pubkey);
|
||||
const npub = nip19.npubEncode(pubkey);
|
||||
|
||||
const displayName = getDisplayName(metadata, pubkey);
|
||||
|
@ -16,9 +16,11 @@ import {
|
||||
Code,
|
||||
AccordionPanelProps,
|
||||
Button,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { ModalProps } from "@chakra-ui/react";
|
||||
import { getSeenRelays } from "applesauce-core/helpers";
|
||||
import { nip19 } from "nostr-tools";
|
||||
|
||||
import { getContentPointers, getContentTagRefs, getThreadReferences } from "../../helpers/nostr/event";
|
||||
@ -29,6 +31,7 @@ import DebugEventTags from "./event-tags";
|
||||
import relayHintService from "../../services/event-relay-hint";
|
||||
import { usePublishEvent } from "../../providers/global/publish-provider";
|
||||
import { EditIcon } from "../icons";
|
||||
import { RelayFavicon } from "../relay-favicon";
|
||||
|
||||
function Section({
|
||||
label,
|
||||
@ -142,6 +145,12 @@ export default function EventDebugModal({ event, ...props }: { event: NostrEvent
|
||||
<JsonCode data={getContentTagRefs(event.content, event.tags)} />
|
||||
</Section>
|
||||
<Section label="Relays">
|
||||
<Text>Seen on:</Text>
|
||||
{Array.from(getSeenRelays(event) ?? []).map((url) => (
|
||||
<Text gap="1" key={url}>
|
||||
<RelayFavicon relay={url} size="xs" /> {url}
|
||||
</Text>
|
||||
))}
|
||||
<Button onClick={broadcast} mr="auto" colorScheme="primary" isLoading={loading}>
|
||||
Broadcast
|
||||
</Button>
|
||||
|
@ -2,7 +2,7 @@ import { Flex, Modal, ModalBody, ModalCloseButton, ModalContent, ModalOverlay }
|
||||
import { ModalProps } from "@chakra-ui/react";
|
||||
import { kinds, nip19 } from "nostr-tools";
|
||||
|
||||
import useUserMetadata from "../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../hooks/use-user-profile";
|
||||
import RawValue from "./raw-value";
|
||||
import RawJson from "./raw-json";
|
||||
import { useSharableProfileId } from "../../hooks/use-shareable-profile-id";
|
||||
@ -11,7 +11,7 @@ import replaceableEventsService from "../../services/replaceable-events";
|
||||
|
||||
export default function UserDebugModal({ pubkey, ...props }: { pubkey: string } & Omit<ModalProps, "children">) {
|
||||
const npub = nip19.npubEncode(pubkey);
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
const metadata = useUserProfile(pubkey);
|
||||
const nprofile = useSharableProfileId(pubkey);
|
||||
const relays = replaceableEventsService.getEvent(kinds.RelayList, pubkey).value;
|
||||
const tipMetadata = useUserLNURLMetadata(pubkey);
|
||||
|
@ -9,7 +9,7 @@ import { useAddReaction } from "./common-hooks";
|
||||
|
||||
export default function EventReactionButtons({ event, max }: { event: NostrEvent; max?: number }) {
|
||||
const account = useCurrentAccount();
|
||||
const reactions = useEventReactions(event.id) ?? [];
|
||||
const reactions = useEventReactions(event) ?? [];
|
||||
const grouped = useMemo(() => groupReactions(reactions), [reactions]);
|
||||
|
||||
const addReaction = useAddReaction(event, grouped);
|
||||
|
@ -13,7 +13,7 @@ export default function SimpleDislikeButton({
|
||||
...props
|
||||
}: Omit<ButtonProps, "children"> & { event: NostrEvent }) {
|
||||
const account = useCurrentAccount();
|
||||
const reactions = useEventReactions(event.id) ?? [];
|
||||
const reactions = useEventReactions(event) ?? [];
|
||||
const grouped = useMemo(() => groupReactions(reactions), [reactions]);
|
||||
|
||||
const addReaction = useAddReaction(event, grouped);
|
||||
|
@ -10,7 +10,7 @@ import { ButtonProps } from "@chakra-ui/react";
|
||||
|
||||
export default function SimpleLikeButton({ event, ...props }: Omit<ButtonProps, "children"> & { event: NostrEvent }) {
|
||||
const account = useCurrentAccount();
|
||||
const reactions = useEventReactions(event.id) ?? [];
|
||||
const reactions = useEventReactions(event) ?? [];
|
||||
const grouped = useMemo(() => groupReactions(reactions), [reactions]);
|
||||
|
||||
const addReaction = useAddReaction(event, grouped);
|
||||
|
@ -4,7 +4,7 @@ import { Box, Button, ButtonGroup, Flex, IconButton, Text, useDisclosure } from
|
||||
|
||||
import { getDisplayName } from "../../helpers/nostr/user-metadata";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import useUserMetadata from "../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../hooks/use-user-profile";
|
||||
import accountService from "../../services/account";
|
||||
import { AddIcon, ChevronDownIcon, ChevronUpIcon } from "../icons";
|
||||
import UserAvatar from "../user/user-avatar";
|
||||
@ -14,7 +14,7 @@ import { Account } from "../../classes/accounts/account";
|
||||
|
||||
function AccountItem({ account, onClick }: { account: Account; onClick?: () => void }) {
|
||||
const pubkey = account.pubkey;
|
||||
const metadata = useUserMetadata(pubkey, []);
|
||||
const metadata = useUserProfile(pubkey, []);
|
||||
|
||||
const handleClick = () => {
|
||||
accountService.switchAccount(pubkey);
|
||||
@ -48,7 +48,7 @@ export default function AccountSwitcher() {
|
||||
const navigate = useNavigate();
|
||||
const account = useCurrentAccount()!;
|
||||
const { isOpen, onToggle, onClose } = useDisclosure();
|
||||
const metadata = useUserMetadata(account.pubkey);
|
||||
const metadata = useUserProfile(account.pubkey);
|
||||
const accounts = useSubject(accountService.accounts);
|
||||
|
||||
const otherAccounts = accounts.filter((acc) => acc.pubkey !== account?.pubkey);
|
||||
|
@ -26,7 +26,7 @@ export default function MessageBubble({
|
||||
renderContent,
|
||||
...props
|
||||
}: MessageBubbleProps) {
|
||||
const reactions = useEventReactions(message.id) ?? [];
|
||||
const reactions = useEventReactions(message) ?? [];
|
||||
const hasReactions = reactions.length > 0;
|
||||
|
||||
let actionPosition = showHeader ? "header" : "inline";
|
||||
@ -38,7 +38,7 @@ export default function MessageBubble({
|
||||
<>
|
||||
<NoteZapButton event={message} />
|
||||
<AddReactionButton event={message} portal />
|
||||
{showThreadButton && <IconThreadButton event={message} label="Open Thread" />}
|
||||
{showThreadButton && <IconThreadButton event={message} aria-label="Open Thread" />}
|
||||
</>
|
||||
);
|
||||
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { Button, IconButton } from "@chakra-ui/react";
|
||||
import { Button, IconButton, IconButtonProps } from "@chakra-ui/react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { NostrEvent } from "nostr-tools";
|
||||
|
||||
import UserAvatar from "../user/user-avatar";
|
||||
import { Thread } from "../../providers/local/thread-provider";
|
||||
import { ChevronRightIcon, ThreadIcon } from "../icons";
|
||||
import { IconButtonProps } from "yet-another-react-lightbox";
|
||||
|
||||
export default function ThreadButton({ thread }: { thread: Thread }) {
|
||||
const navigate = useNavigate();
|
||||
|
@ -25,7 +25,7 @@ export default function AddReactionButton({
|
||||
...props
|
||||
}: { event: NostrEvent; portal?: boolean } & Omit<ButtonProps, "children">) {
|
||||
const publish = usePublishEvent();
|
||||
const reactions = useEventReactions(getEventUID(event)) ?? [];
|
||||
const reactions = useEventReactions(event) ?? [];
|
||||
const [popover, setPopover] = useBoolean();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
@ -7,7 +7,7 @@ import { useBreakpointValue } from "../../../../providers/global/breakpoint-prov
|
||||
import useEventReactions from "../../../../hooks/use-event-reactions";
|
||||
|
||||
export default function NoteReactions({ event, ...props }: Omit<ButtonGroupProps, "children"> & { event: NostrEvent }) {
|
||||
const reactions = useEventReactions(event.id) ?? [];
|
||||
const reactions = useEventReactions(event) ?? [];
|
||||
const max = useBreakpointValue({ base: undefined, md: 4 });
|
||||
|
||||
return (
|
||||
|
@ -23,7 +23,7 @@ export default function EventVoteButtons({
|
||||
}) {
|
||||
const account = useCurrentAccount();
|
||||
const publish = usePublishEvent();
|
||||
const reactions = useEventReactions(event.id);
|
||||
const reactions = useEventReactions(event);
|
||||
const additionalRelays = useAdditionalRelayContext();
|
||||
|
||||
const grouped = useMemo(() => groupReactions(reactions ?? []), [reactions]);
|
||||
|
@ -7,11 +7,11 @@ import { nip19 } from "nostr-tools";
|
||||
|
||||
import { useUserSearchDirectoryContext } from "../../providers/global/user-directory-provider";
|
||||
import UserAvatar from "../user/user-avatar";
|
||||
import useUserMetadata from "../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../hooks/use-user-profile";
|
||||
import { getDisplayName } from "../../helpers/nostr/user-metadata";
|
||||
|
||||
function UserOption({ pubkey }: { pubkey: string }) {
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
const metadata = useUserProfile(pubkey);
|
||||
|
||||
return (
|
||||
<Flex as={RouterLink} to={`/u/${nip19.npubEncode(pubkey)}`} p="2" gap="2" alignItems="center">
|
||||
|
@ -2,11 +2,11 @@ import { useMemo } from "react";
|
||||
import { Box, BoxProps } from "@chakra-ui/react";
|
||||
|
||||
import { EmbedableContent, embedUrls } from "../../helpers/embeds";
|
||||
import useUserMetadata from "../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../hooks/use-user-profile";
|
||||
import { embedNostrLinks, renderGenericUrl } from "../external-embeds";
|
||||
|
||||
export default function UserAbout({ pubkey, ...props }: { pubkey: string } & Omit<BoxProps, "children">) {
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
const metadata = useUserProfile(pubkey);
|
||||
|
||||
const aboutContent = useMemo(() => {
|
||||
if (!metadata?.about) return null;
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { forwardRef, memo, useMemo } from "react";
|
||||
import { Avatar, AvatarProps } from "@chakra-ui/react";
|
||||
import { useAsync } from "react-use";
|
||||
import styled from "@emotion/styled";
|
||||
|
||||
import useUserMetadata from "../../hooks/use-user-metadata";
|
||||
import { getIdenticon } from "../../helpers/identicon";
|
||||
import { safeUrl } from "../../helpers/parse";
|
||||
import { Kind0ParsedContent, getDisplayName } from "../../helpers/nostr/user-metadata";
|
||||
@ -10,8 +10,8 @@ import useAppSettings from "../../hooks/use-app-settings";
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import { buildImageProxyURL } from "../../helpers/image";
|
||||
import UserDnsIdentityIcon from "./user-dns-identity-icon";
|
||||
import styled from "@emotion/styled";
|
||||
import useUserMuteList from "../../hooks/use-user-mute-list";
|
||||
import useUserProfile from "../../hooks/use-user-profile";
|
||||
|
||||
export const UserIdenticon = memo(({ pubkey }: { pubkey: string }) => {
|
||||
const { value: identicon } = useAsync(() => getIdenticon(pubkey), [pubkey]);
|
||||
@ -33,7 +33,7 @@ export type UserAvatarProps = Omit<MetadataAvatarProps, "pubkey" | "metadata"> &
|
||||
};
|
||||
export const UserAvatar = forwardRef<HTMLDivElement, UserAvatarProps>(
|
||||
({ pubkey, noProxy, relay, size, ...props }, ref) => {
|
||||
const metadata = useUserMetadata(pubkey, relay ? [relay] : undefined);
|
||||
const profile = useUserProfile(pubkey, relay ? [relay] : undefined);
|
||||
const account = useCurrentAccount();
|
||||
const muteList = useUserMuteList(account?.pubkey);
|
||||
|
||||
@ -42,7 +42,7 @@ export const UserAvatar = forwardRef<HTMLDivElement, UserAvatarProps>(
|
||||
return (
|
||||
<MetadataAvatar
|
||||
pubkey={pubkey}
|
||||
metadata={muted ? undefined : metadata}
|
||||
metadata={muted ? undefined : profile}
|
||||
noProxy={noProxy}
|
||||
ref={ref}
|
||||
size={size}
|
||||
|
@ -2,11 +2,11 @@ import { forwardRef } from "react";
|
||||
import { IconProps } from "@chakra-ui/react";
|
||||
|
||||
import useDnsIdentity from "../../hooks/use-dns-identity";
|
||||
import useUserMetadata from "../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../hooks/use-user-profile";
|
||||
import { VerificationFailed, VerificationMissing, VerifiedIcon } from "../icons";
|
||||
|
||||
const UserDnsIdentityIcon = forwardRef<SVGSVGElement, { pubkey: string } & IconProps>(({ pubkey, ...props }, ref) => {
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
const metadata = useUserProfile(pubkey);
|
||||
const identity = useDnsIdentity(metadata?.nip05);
|
||||
|
||||
if (!metadata?.nip05) return null;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Text, TextProps, Tooltip } from "@chakra-ui/react";
|
||||
|
||||
import useUserMetadata from "../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../hooks/use-user-profile";
|
||||
import UserDnsIdentityIcon from "./user-dns-identity-icon";
|
||||
|
||||
export default function UserDnsIdentity({
|
||||
@ -8,7 +8,7 @@ export default function UserDnsIdentity({
|
||||
onlyIcon,
|
||||
...props
|
||||
}: { pubkey: string; onlyIcon?: boolean } & Omit<TextProps, "children">) {
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
const metadata = useUserProfile(pubkey);
|
||||
if (!metadata?.nip05) return null;
|
||||
|
||||
if (onlyIcon) {
|
||||
|
@ -3,7 +3,7 @@ import { Link as RouterLink } from "react-router-dom";
|
||||
import { nip19 } from "nostr-tools";
|
||||
|
||||
import { getDisplayName } from "../../helpers/nostr/user-metadata";
|
||||
import useUserMetadata from "../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../hooks/use-user-profile";
|
||||
import useAppSettings from "../../hooks/use-app-settings";
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
@ -16,7 +16,7 @@ export type UserLinkProps = LinkProps & {
|
||||
};
|
||||
|
||||
export default function UserLink({ pubkey, showAt, tab, ...props }: UserLinkProps) {
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
const metadata = useUserProfile(pubkey);
|
||||
const account = useCurrentAccount();
|
||||
const { hideUsernames, removeEmojisInUsernames, showPubkeyColor } = useAppSettings();
|
||||
const color = "#" + pubkey.slice(0, 6);
|
||||
|
@ -2,11 +2,11 @@ import { memo } from "react";
|
||||
import { Text, TextProps } from "@chakra-ui/react";
|
||||
|
||||
import { getDisplayName } from "../../helpers/nostr/user-metadata";
|
||||
import useUserMetadata from "../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../hooks/use-user-profile";
|
||||
import useAppSettings from "../../hooks/use-app-settings";
|
||||
|
||||
function UserName({ pubkey, ...props }: Omit<TextProps, "children"> & { pubkey: string }) {
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
const metadata = useUserProfile(pubkey);
|
||||
const { hideUsernames, removeEmojisInUsernames } = useAppSettings();
|
||||
|
||||
return (
|
||||
|
@ -1,5 +1,6 @@
|
||||
import debug from "debug";
|
||||
|
||||
if (!localStorage.getItem("debug") && import.meta.env.DEV) debug.enable("noStrudel,noStrudel:*");
|
||||
if (!localStorage.getItem("debug") && import.meta.env.DEV)
|
||||
debug.enable("noStrudel,noStrudel:*,applesauce,applesauce:*");
|
||||
|
||||
export const logger = debug("noStrudel");
|
||||
|
@ -12,7 +12,7 @@ export async function getZapEndpoint(metadata: Kind0ParsedContent): Promise<null
|
||||
let lnurl: string = "";
|
||||
let { lud06, lud16 } = metadata;
|
||||
if (lud06) {
|
||||
let { words } = bech32.decode(lud06, 1000);
|
||||
let { words } = bech32.decode(lud06 as `${string}1${string}`, 1000);
|
||||
let data = bech32.fromWords(words);
|
||||
lnurl = utils.utf8Decoder.decode(data);
|
||||
} else if (lud16) {
|
||||
|
@ -1,15 +1,23 @@
|
||||
import { useMemo } from "react";
|
||||
import { NostrEvent } from "nostr-tools";
|
||||
import { getEventUID } from "applesauce-core/helpers";
|
||||
|
||||
import eventReactionsService from "../services/event-reactions";
|
||||
import { useReadRelays } from "./use-client-relays";
|
||||
import useSubject from "./use-subject";
|
||||
import { queryStore } from "../services/event-store";
|
||||
import { useObservable } from "./use-observable";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function useEventReactions(eventId: string, additionalRelays?: Iterable<string>, alwaysRequest = true) {
|
||||
export default function useEventReactions(
|
||||
event: NostrEvent,
|
||||
additionalRelays?: Iterable<string>,
|
||||
alwaysRequest = true,
|
||||
) {
|
||||
const relays = useReadRelays(additionalRelays);
|
||||
|
||||
const subject = useMemo(
|
||||
() => eventReactionsService.requestReactions(eventId, relays, alwaysRequest),
|
||||
[eventId, relays.urls.join("|"), alwaysRequest],
|
||||
);
|
||||
useEffect(() => {
|
||||
eventReactionsService.requestReactions(getEventUID(event), relays, alwaysRequest);
|
||||
}, [event, relays, alwaysRequest]);
|
||||
|
||||
return useSubject(subject);
|
||||
const observable = queryStore.getReactions(event);
|
||||
return useObservable(observable);
|
||||
}
|
||||
|
17
src/hooks/use-observable.ts
Normal file
17
src/hooks/use-observable.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { useState } from "react";
|
||||
import { isStateful } from "applesauce-core/observable";
|
||||
import { useIsomorphicLayoutEffect } from "react-use";
|
||||
import Observable from "zen-observable";
|
||||
|
||||
export function useObservable<T>(observable?: Observable<T>): T | undefined {
|
||||
const [value, update] = useState<T | undefined>(observable && isStateful(observable) ? observable.value : undefined);
|
||||
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
if (!observable) return;
|
||||
|
||||
const s = observable.subscribe(update);
|
||||
return () => s.unsubscribe();
|
||||
}, [observable]);
|
||||
|
||||
return value;
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { RelayMode } from "../classes/relay";
|
||||
import useCurrentAccount from "./use-current-account";
|
||||
import useUserMailboxes from "./use-user-mailboxes";
|
||||
import { addRelayModeToMailbox, removeRelayModeFromMailbox } from "../helpers/nostr/mailbox";
|
||||
import { usePublishEvent } from "../providers/global/publish-provider";
|
||||
|
||||
export default function useRelayMailboxActions(relay: string) {
|
||||
const publish = usePublishEvent();
|
||||
const account = useCurrentAccount();
|
||||
const { event, inbox, outbox } = useUserMailboxes(account?.pubkey, undefined, { alwaysRequest: true }) || {};
|
||||
|
||||
const addMode = useCallback(
|
||||
async (mode: RelayMode) => {
|
||||
let draft = addRelayModeToMailbox(event ?? undefined, relay, mode);
|
||||
await publish("Add Relay", draft);
|
||||
},
|
||||
[publish, event],
|
||||
);
|
||||
const removeMode = useCallback(
|
||||
async (mode: RelayMode) => {
|
||||
let draft = removeRelayModeFromMailbox(event ?? undefined, relay, mode);
|
||||
await publish("Remove Relay", draft);
|
||||
},
|
||||
[publish, event],
|
||||
);
|
||||
|
||||
return { inbox, outbox, addMode, removeMode };
|
||||
}
|
@ -9,7 +9,7 @@ export function useSharableProfileId(pubkey: string, relayCount = 2) {
|
||||
const mailboxes = useUserMailboxes(pubkey);
|
||||
|
||||
return useMemo(() => {
|
||||
const ranked = relayScoreboardService.getRankedRelays(mailboxes?.outbox.urls);
|
||||
const ranked = relayScoreboardService.getRankedRelays(mailboxes?.outboxes);
|
||||
const onlyTwo = ranked.slice(0, relayCount);
|
||||
return onlyTwo.length > 0 ? nip19.nprofileEncode({ pubkey, relays: onlyTwo }) : nip19.npubEncode(pubkey);
|
||||
}, [mailboxes]);
|
||||
|
@ -1,13 +1,17 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
import singleEventService from "../services/single-event";
|
||||
import { useReadRelays } from "./use-client-relays";
|
||||
import { useMemo } from "react";
|
||||
import useSubject from "./use-subject";
|
||||
import { queryStore } from "../services/event-store";
|
||||
import { useObservable } from "./use-observable";
|
||||
|
||||
export default function useSingleEvent(id?: string, additionalRelays?: Iterable<string>) {
|
||||
const readRelays = useReadRelays(additionalRelays);
|
||||
const subject = useMemo(() => {
|
||||
if (id) return singleEventService.requestEvent(id, readRelays);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) singleEventService.requestEvent(id, readRelays);
|
||||
}, [id, readRelays.urls.join("|")]);
|
||||
|
||||
return useSubject(subject);
|
||||
const observable = id ? queryStore.getEvent(id) : undefined;
|
||||
return useObservable(observable);
|
||||
}
|
||||
|
@ -1,14 +1,20 @@
|
||||
import { useMemo } from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
|
||||
import singleEventService from "../services/single-event";
|
||||
import { useReadRelays } from "./use-client-relays";
|
||||
import useSubjects from "./use-subjects";
|
||||
import { eventStore } from "../services/event-store";
|
||||
import { useObservable } from "./use-observable";
|
||||
|
||||
export default function useSingleEvents(ids?: string[], additionalRelays?: Iterable<string>) {
|
||||
const readRelays = useReadRelays(additionalRelays);
|
||||
const subjects = useMemo(() => {
|
||||
return ids?.map((id) => singleEventService.requestEvent(id, readRelays)) ?? [];
|
||||
useEffect(() => {
|
||||
if (!ids) return;
|
||||
|
||||
for (const id of ids) {
|
||||
singleEventService.requestEvent(id, readRelays);
|
||||
}
|
||||
}, [ids, readRelays.urls.join("|")]);
|
||||
|
||||
return useSubjects(subjects);
|
||||
const observable = useMemo(() => (ids ? eventStore.timeline([{ ids }]) : undefined), [ids?.join("|")]);
|
||||
return useObservable(observable) ?? [];
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import useDnsIdentity from "./use-dns-identity";
|
||||
import useUserMetadata from "./use-user-metadata";
|
||||
import useUserProfile from "./use-user-profile";
|
||||
|
||||
export function useUserDNSIdentity(pubkey?: string) {
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
const metadata = useUserProfile(pubkey);
|
||||
return useDnsIdentity(metadata?.nip05);
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { useAsync } from "react-use";
|
||||
import useUserMetadata from "./use-user-metadata";
|
||||
import useUserProfile from "./use-user-profile";
|
||||
import lnurlMetadataService from "../services/lnurl-metadata";
|
||||
|
||||
export default function useUserLNURLMetadata(pubkey: string) {
|
||||
const userMetadata = useUserMetadata(pubkey);
|
||||
const userMetadata = useUserProfile(pubkey);
|
||||
const address = userMetadata?.lud16 || userMetadata?.lud06;
|
||||
const { value: metadata } = useAsync(
|
||||
async () => (address ? lnurlMetadataService.requestMetadata(address) : undefined),
|
||||
|
@ -3,7 +3,8 @@ import { COMMON_CONTACT_RELAY } from "../const";
|
||||
import { RequestOptions } from "../services/replaceable-events";
|
||||
import userMailboxesService from "../services/user-mailboxes";
|
||||
import { useReadRelays } from "./use-client-relays";
|
||||
import useSubject from "./use-subject";
|
||||
import { queryStore } from "../services/event-store";
|
||||
import { useObservable } from "./use-observable";
|
||||
|
||||
export default function useUserMailboxes(
|
||||
pubkey?: string,
|
||||
@ -11,13 +12,16 @@ export default function useUserMailboxes(
|
||||
opts?: RequestOptions,
|
||||
) {
|
||||
const readRelays = useReadRelays([...additionalRelays, COMMON_CONTACT_RELAY]);
|
||||
const sub = pubkey ? userMailboxesService.requestMailboxes(pubkey, readRelays, opts) : undefined;
|
||||
const value = useSubject(sub);
|
||||
return value;
|
||||
if (pubkey) {
|
||||
userMailboxesService.requestMailboxes(pubkey, readRelays, opts);
|
||||
}
|
||||
|
||||
const observable = pubkey ? queryStore.getMailboxes(pubkey) : undefined;
|
||||
return useObservable(observable);
|
||||
}
|
||||
export function useUserInbox(pubkey?: string, additionalRelays: Iterable<string> = [], opts?: RequestOptions) {
|
||||
return useUserMailboxes(pubkey, additionalRelays, opts)?.inbox ?? new RelaySet();
|
||||
return useUserMailboxes(pubkey, additionalRelays, opts)?.inboxes;
|
||||
}
|
||||
export function useUserOutbox(pubkey?: string, additionalRelays: Iterable<string> = [], opts?: RequestOptions) {
|
||||
return useUserMailboxes(pubkey, additionalRelays, opts)?.outbox ?? new RelaySet();
|
||||
return useUserMailboxes(pubkey, additionalRelays, opts)?.outboxes;
|
||||
}
|
||||
|
@ -1,21 +0,0 @@
|
||||
import { useMemo } from "react";
|
||||
|
||||
import userMetadataService from "../services/user-metadata";
|
||||
import { useReadRelays } from "./use-client-relays";
|
||||
import useSubject from "./use-subject";
|
||||
import { RequestOptions } from "../services/replaceable-events";
|
||||
import { COMMON_CONTACT_RELAYS } from "../const";
|
||||
|
||||
export default function useUserMetadata(pubkey?: string, additionalRelays?: Iterable<string>, opts?: RequestOptions) {
|
||||
const readRelays = useReadRelays(
|
||||
additionalRelays ? [...additionalRelays, ...COMMON_CONTACT_RELAYS] : COMMON_CONTACT_RELAYS,
|
||||
);
|
||||
|
||||
const subject = useMemo(
|
||||
() => (pubkey ? userMetadataService.requestMetadata(pubkey, readRelays, opts) : undefined),
|
||||
[pubkey, readRelays],
|
||||
);
|
||||
const metadata = useSubject(subject);
|
||||
|
||||
return metadata;
|
||||
}
|
21
src/hooks/use-user-profile.ts
Normal file
21
src/hooks/use-user-profile.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
import userMetadataService from "../services/user-metadata";
|
||||
import { useReadRelays } from "./use-client-relays";
|
||||
import { RequestOptions } from "../services/replaceable-events";
|
||||
import { COMMON_CONTACT_RELAYS } from "../const";
|
||||
import { queryStore } from "../services/event-store";
|
||||
import { useObservable } from "./use-observable";
|
||||
|
||||
export default function useUserProfile(pubkey?: string, additionalRelays?: Iterable<string>, opts?: RequestOptions) {
|
||||
const readRelays = useReadRelays(
|
||||
additionalRelays ? [...additionalRelays, ...COMMON_CONTACT_RELAYS] : COMMON_CONTACT_RELAYS,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (pubkey) userMetadataService.requestMetadata(pubkey, readRelays, opts);
|
||||
}, [pubkey, readRelays]);
|
||||
|
||||
const observable = pubkey ? queryStore.getProfile(pubkey) : undefined;
|
||||
return useObservable(observable);
|
||||
}
|
@ -37,7 +37,7 @@ export default function DMTimelineProvider({ children }: PropsWithChildren) {
|
||||
|
||||
const timeline = useTimelineLoader(
|
||||
`${truncateId(account?.pubkey ?? "anon")}-dms`,
|
||||
inbox,
|
||||
inbox ?? [],
|
||||
account?.pubkey
|
||||
? [
|
||||
{ authors: [account.pubkey], kinds: [kinds.EncryptedDirectMessage] },
|
||||
|
@ -62,7 +62,7 @@ export default function NotificationsProvider({ children }: PropsWithChildren) {
|
||||
|
||||
useEffect(() => {
|
||||
if (!account?.pubkey) return;
|
||||
const n = new AccountNotifications(account.pubkey, timeline.events);
|
||||
const n = new AccountNotifications(account.pubkey);
|
||||
setNotifications(n);
|
||||
if (import.meta.env.DEV) {
|
||||
// @ts-expect-error
|
||||
@ -70,10 +70,9 @@ export default function NotificationsProvider({ children }: PropsWithChildren) {
|
||||
}
|
||||
|
||||
return () => {
|
||||
n.destroy();
|
||||
setNotifications(undefined);
|
||||
};
|
||||
}, [account?.pubkey, timeline.events]);
|
||||
}, [account?.pubkey]);
|
||||
|
||||
const context = useMemo(() => ({ timeline, notifications }), [timeline, notifications]);
|
||||
|
||||
|
@ -15,6 +15,7 @@ import deleteEventService from "../../services/delete-events";
|
||||
import userMailboxesService from "../../services/user-mailboxes";
|
||||
import localSettings from "../../services/local-settings";
|
||||
import { NEVER_ATTACH_CLIENT_TAG, NIP_89_CLIENT_TAG } from "../../const";
|
||||
import { eventStore } from "../../services/event-store";
|
||||
|
||||
type PublishContextType = {
|
||||
log: PublishAction[];
|
||||
@ -115,6 +116,7 @@ export default function PublishProvider({ children }: PropsWithChildren) {
|
||||
if (localRelay) localRelay.publish(signed);
|
||||
|
||||
// pass it to other services
|
||||
eventStore.add(signed);
|
||||
if (isReplaceable(signed.kind)) replaceableEventsService.handleEvent(signed);
|
||||
if (signed.kind === kinds.Reaction) eventReactionsService.handleEvent(signed);
|
||||
if (signed.kind === kinds.EventDeletion) deleteEventService.handleEvent(signed);
|
||||
|
@ -21,7 +21,7 @@ import dayjs from "dayjs";
|
||||
import { useInterval } from "react-use";
|
||||
|
||||
import { getDisplayName } from "../../helpers/nostr/user-metadata";
|
||||
import useUserMetadata from "../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../hooks/use-user-profile";
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import {
|
||||
createEmptyMuteList,
|
||||
@ -52,7 +52,7 @@ export function useMuteModalContext() {
|
||||
}
|
||||
|
||||
function MuteModal({ pubkey, onClose, ...props }: Omit<ModalProps, "children"> & { pubkey: string }) {
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
const metadata = useUserProfile(pubkey);
|
||||
const publish = usePublishEvent();
|
||||
|
||||
const account = useCurrentAccount();
|
||||
|
@ -12,6 +12,7 @@ import { LightningIcon } from "../components/icons";
|
||||
import processManager from "./process-manager";
|
||||
import BatchRelationLoader from "../classes/batch-relation-loader";
|
||||
import { logger } from "../helpers/debug";
|
||||
import { eventStore } from "./event-store";
|
||||
|
||||
class EventReactionsService {
|
||||
log = logger.extend("EventReactionsService");
|
||||
@ -74,6 +75,8 @@ class EventReactionsService {
|
||||
}
|
||||
|
||||
handleEvent(event: NostrEvent) {
|
||||
eventStore.add(event);
|
||||
|
||||
// pretend it came from the local relay
|
||||
if (localRelay) this.loaders.get(localRelay as AbstractRelay).handleEvent(event);
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ import type { AddressPointer, EventPointer } from "nostr-tools/nip19";
|
||||
import { NostrEvent, isDTag } from "../types/nostr-event";
|
||||
import relayScoreboardService from "./relay-scoreboard";
|
||||
import userMailboxesService from "./user-mailboxes";
|
||||
import singleEventService from "./single-event";
|
||||
import { isReplaceable } from "../helpers/nostr/event";
|
||||
import { eventStore } from "./event-store";
|
||||
|
||||
function pickBestRelays(relays: Iterable<string>) {
|
||||
// ignore local relays
|
||||
@ -20,7 +20,7 @@ function getAddressPointerRelayHint(pointer: AddressPointer): string | undefined
|
||||
|
||||
function getEventPointerRelayHints(pointerOrId: string | EventPointer): string[] {
|
||||
if (typeof pointerOrId === "string") {
|
||||
const event = singleEventService.getSubject(pointerOrId).value;
|
||||
const event = eventStore.getEvent(pointerOrId);
|
||||
if (event) {
|
||||
const authorRelays = userMailboxesService.getMailboxes(event.pubkey).value;
|
||||
return pickBestRelays(authorRelays?.outbox || []);
|
||||
|
11
src/services/event-store.ts
Normal file
11
src/services/event-store.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { EventStore, QueryStore } from "applesauce-core";
|
||||
|
||||
export const eventStore = new EventStore();
|
||||
export const queryStore = new QueryStore(eventStore);
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
// @ts-expect-error
|
||||
window.eventStore = eventStore;
|
||||
// @ts-expect-error
|
||||
window.queryStore = queryStore;
|
||||
}
|
@ -15,6 +15,7 @@ import { alwaysVerify } from "./verify-event";
|
||||
import { truncateId } from "../helpers/string";
|
||||
import processManager from "./process-manager";
|
||||
import UserSquare from "../components/icons/user-square";
|
||||
import { eventStore } from "./event-store";
|
||||
|
||||
export type RequestOptions = {
|
||||
/** Always request the event from the relays */
|
||||
@ -62,6 +63,8 @@ class ReplaceableEventsService {
|
||||
if (!fromCache && !alwaysVerify(event)) return;
|
||||
const cord = getEventCoordinate(event);
|
||||
|
||||
eventStore.add(event);
|
||||
|
||||
const subject = this.subjects.get(cord);
|
||||
const current = subject.value;
|
||||
if (!current || event.created_at > current.created_at) {
|
||||
|
@ -12,18 +12,19 @@ import processManager from "./process-manager";
|
||||
import Code02 from "../components/icons/code-02";
|
||||
import BatchEventLoader from "../classes/batch-event-loader";
|
||||
import EventStore from "../classes/event-store";
|
||||
import { eventStore } from "./event-store";
|
||||
|
||||
class SingleEventService {
|
||||
process: Process;
|
||||
log = logger.extend("SingleEventService");
|
||||
|
||||
events = new EventStore();
|
||||
subjects = new SuperMap<string, Subject<NostrEvent>>(() => new Subject<NostrEvent>());
|
||||
// events = new EventStore();
|
||||
// subjects = new SuperMap<string, Subject<NostrEvent>>(() => new Subject<NostrEvent>());
|
||||
|
||||
loaders = new SuperMap<AbstractRelay, BatchEventLoader>((relay) => {
|
||||
const loader = new BatchEventLoader(relay, this.log.extend(relay.url));
|
||||
this.process.addChild(loader.process);
|
||||
this.events.connect(loader.events);
|
||||
// this.events.connect(loader.events);
|
||||
return loader;
|
||||
});
|
||||
|
||||
@ -38,14 +39,14 @@ class SingleEventService {
|
||||
processManager.registerProcess(this.process);
|
||||
|
||||
// when an event is added to the store, pass it along to the subjects
|
||||
this.events.onEvent.subscribe((event) => {
|
||||
this.subjects.get(event.id).next(event);
|
||||
});
|
||||
// this.events.onEvent.subscribe((event) => {
|
||||
// this.subjects.get(event.id).next(event);
|
||||
// });
|
||||
}
|
||||
|
||||
getSubject(id: string) {
|
||||
return this.subjects.get(id);
|
||||
}
|
||||
// getSubject(id: string) {
|
||||
// return this.subjects.get(id);
|
||||
// }
|
||||
|
||||
private loadEventFromRelays(id: string) {
|
||||
const relays = this.pendingRelays.get(id);
|
||||
@ -57,8 +58,9 @@ class SingleEventService {
|
||||
|
||||
loadingFromCache = new Set<string>();
|
||||
requestEvent(id: string, urls: Iterable<string | URL | AbstractRelay>) {
|
||||
const subject = this.subjects.get(id);
|
||||
if (subject.value) return subject;
|
||||
if (eventStore.hasEvent(id)) return;
|
||||
// const subject = this.subjects.get(id);
|
||||
// if (subject.value) return subject;
|
||||
|
||||
const relays = relayPoolService.getRelays(urls);
|
||||
for (const relay of relays) this.pendingRelays.get(id).add(relay);
|
||||
@ -79,13 +81,15 @@ class SingleEventService {
|
||||
}
|
||||
} else this.loadEventFromRelays(id);
|
||||
|
||||
return subject;
|
||||
// return subject;
|
||||
}
|
||||
|
||||
handleEvent(event: NostrEvent, fromCache = false) {
|
||||
this.events.addEvent(event);
|
||||
// this.events.addEvent(event);
|
||||
this.pendingRelays.delete(event.id);
|
||||
|
||||
eventStore.add(event);
|
||||
|
||||
if (!fromCache && localRelay) localRelay.publish(event);
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ class WebRtcRelaysService {
|
||||
get pendingOutgoing() {
|
||||
const pending: { call: NostrEvent; peer: NostrWebRTCPeer }[] = [];
|
||||
for (const call of this.calls) {
|
||||
const pubkey = call.tags.find((t) => (t[0] = "p" && t[1]))?.[1];
|
||||
const pubkey = call.tags.find((t) => t[0] === "p" && t[1])?.[1];
|
||||
if (!pubkey) continue;
|
||||
const peer = this.broker.peers.get(pubkey);
|
||||
if (peer && peer.connection.connectionState === "new") pending.push({ call, peer });
|
||||
|
@ -3,7 +3,7 @@ import { Link } from "react-router-dom";
|
||||
import { nip19 } from "nostr-tools";
|
||||
import { Box, BoxProps } from "@chakra-ui/react";
|
||||
|
||||
import useUserMetadata from "../../../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../../../hooks/use-user-profile";
|
||||
|
||||
import { AddressPointer } from "nostr-tools/nip19";
|
||||
import useDVMMetadata from "../../../../hooks/use-dvm-metadata";
|
||||
@ -15,7 +15,7 @@ type DVMAvatarProps = {
|
||||
|
||||
export const DVMAvatar = forwardRef<HTMLDivElement, DVMAvatarProps>(({ pointer, noProxy, ...props }, ref) => {
|
||||
const dvmMetadata = useDVMMetadata(pointer);
|
||||
const userMetadata = useUserMetadata(pointer.pubkey);
|
||||
const userMetadata = useUserProfile(pointer.pubkey);
|
||||
const image = dvmMetadata?.image || userMetadata?.picture || "";
|
||||
|
||||
return (
|
||||
|
@ -2,7 +2,7 @@ import { Link, LinkProps, Text, TextProps } from "@chakra-ui/react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { nip19 } from "nostr-tools";
|
||||
|
||||
import useUserMetadata from "../../../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../../../hooks/use-user-profile";
|
||||
import { getDisplayName } from "../../../../helpers/nostr/user-metadata";
|
||||
import { AddressPointer } from "nostr-tools/nip19";
|
||||
import useDVMMetadata from "../../../../hooks/use-dvm-metadata";
|
||||
@ -15,7 +15,7 @@ export function DVMName({
|
||||
pointer: AddressPointer;
|
||||
}) {
|
||||
const dvmMetadata = useDVMMetadata(pointer);
|
||||
const metadata = useUserMetadata(pointer.pubkey);
|
||||
const metadata = useUserProfile(pointer.pubkey);
|
||||
|
||||
return (
|
||||
<Text as={as} {...props}>
|
||||
|
@ -32,7 +32,7 @@ import { usePublishEvent } from "../../../../providers/global/publish-provider";
|
||||
|
||||
function NextPageButton({ chain, pointer }: { pointer: AddressPointer; chain: ChainedDVMJob[] }) {
|
||||
const publish = usePublishEvent();
|
||||
const dvmRelays = useUserMailboxes(pointer.pubkey)?.relays;
|
||||
const dvmRelays = useUserMailboxes(pointer.pubkey)
|
||||
const readRelays = useReadRelays();
|
||||
|
||||
const lastJob = chain[chain.length - 1];
|
||||
@ -49,7 +49,7 @@ function NextPageButton({ chain, pointer }: { pointer: AddressPointer; chain: Ch
|
||||
],
|
||||
};
|
||||
|
||||
await publish("Next Page", draft, dvmRelays);
|
||||
await publish("Next Page", draft, dvmRelays?.inboxes);
|
||||
};
|
||||
|
||||
const response = getResponseFromDVM(lastJob, pointer.pubkey);
|
||||
|
@ -47,7 +47,7 @@ function DVMFeedPage({ pointer }: { pointer: AddressPointer }) {
|
||||
const account = useCurrentAccount()!;
|
||||
const debugModal = useDisclosure();
|
||||
|
||||
const dvmRelays = useUserMailboxes(pointer.pubkey)?.relays;
|
||||
const dvmRelays = useUserMailboxes(pointer.pubkey)?.outboxes;
|
||||
const readRelays = useReadRelays(dvmRelays);
|
||||
const timeline = useTimelineLoader(
|
||||
`${getHumanReadableCoordinate(pointer.kind, pointer.pubkey, pointer.identifier)}-jobs`,
|
||||
|
@ -88,7 +88,7 @@ function DirectMessageChatPage({ pubkey }: { pubkey: string }) {
|
||||
const mailboxes = useUserMailboxes(account.pubkey);
|
||||
const timeline = useTimelineLoader(
|
||||
`${truncateId(pubkey)}-${truncateId(account.pubkey)}-messages`,
|
||||
RelaySet.from(mailboxes?.inbox, mailboxes?.outbox, otherMailboxes?.inbox, otherMailboxes?.outbox),
|
||||
RelaySet.from(mailboxes?.inboxes, mailboxes?.outboxes, otherMailboxes?.inboxes, otherMailboxes?.outboxes),
|
||||
[
|
||||
{
|
||||
kinds: [kinds.EncryptedDirectMessage],
|
||||
|
@ -56,7 +56,7 @@ export default function SendMessageForm({
|
||||
}
|
||||
|
||||
setLoadingMessage("Signing...");
|
||||
const pub = await publish("Send DM", draft, userMailboxes?.inbox);
|
||||
const pub = await publish("Send DM", draft, userMailboxes?.inboxes);
|
||||
|
||||
if (pub) {
|
||||
clearCache();
|
||||
|
@ -6,17 +6,17 @@ import { getEventUID } from "nostr-idb";
|
||||
|
||||
import KeyboardShortcut from "../../../components/keyboard-shortcut";
|
||||
import { useNotifications } from "../../../providers/global/notifications-provider";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import { NotificationType, typeSymbol } from "../../../classes/notifications";
|
||||
import NotificationItem from "../../notifications/components/notification-item";
|
||||
import { ErrorBoundary } from "../../../components/error-boundary";
|
||||
import { useObservable } from "../../../hooks/use-observable";
|
||||
|
||||
export default function NotificationsCard({ ...props }: Omit<CardProps, "children">) {
|
||||
const navigate = useNavigate();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const events =
|
||||
useSubject(notifications?.timeline)?.filter(
|
||||
useObservable(notifications?.timeline)?.filter(
|
||||
(event) =>
|
||||
event[typeSymbol] === NotificationType.Mention ||
|
||||
event[typeSymbol] === NotificationType.Reply ||
|
||||
|
@ -2,7 +2,7 @@ import { Button, Card, CardBody, CardProps, Flex, Heading, Link } from "@chakra-
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { nip19 } from "nostr-tools";
|
||||
|
||||
import useUserMetadata from "../../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../../hooks/use-user-profile";
|
||||
import UserAvatar from "../../../components/user/user-avatar";
|
||||
import UserDnsIdentity from "../../../components/user/user-dns-identity";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
@ -18,7 +18,7 @@ export type UserCardProps = { pubkey: string; relay?: string; list: NostrEvent }
|
||||
export default function UserCard({ pubkey, relay, list, ...props }: UserCardProps) {
|
||||
const account = useCurrentAccount();
|
||||
const publish = usePublishEvent();
|
||||
const metadata = useUserMetadata(pubkey, relay ? [relay] : []);
|
||||
const metadata = useUserProfile(pubkey, relay ? [relay] : []);
|
||||
|
||||
const handleRemoveFromList = useAsyncErrorHandler(async () => {
|
||||
const draft = listRemovePerson(list, pubkey);
|
||||
|
@ -25,7 +25,7 @@ import { useTimelineDates } from "../../hooks/timeline/use-timeline-dates";
|
||||
import useCacheEntryHeight from "../../hooks/timeline/use-cache-entry-height";
|
||||
import useVimNavigation from "./use-vim-navigation";
|
||||
import { PersistentSubject } from "../../classes/subject";
|
||||
import useRouteStateValue from "../../hooks/use-route-state-value";
|
||||
import { useObservable } from "../../hooks/use-observable";
|
||||
|
||||
function TimeMarker({ date, ids }: { date: Dayjs; ids: string[] }) {
|
||||
const readAll = useCallback(() => {
|
||||
@ -62,7 +62,7 @@ const NotificationsTimeline = memo(
|
||||
const { people } = usePeopleListContext();
|
||||
const peoplePubkeys = useMemo(() => people?.map((p) => p.pubkey), [people]);
|
||||
|
||||
const events = useSubject(notifications?.timeline) ?? [];
|
||||
const events = useObservable(notifications?.timeline) ?? [];
|
||||
|
||||
const cacheKey = useTimelineLocationCacheKey();
|
||||
const numberCache = useNumberCache(cacheKey);
|
||||
|
@ -18,7 +18,7 @@ import { isLNURL } from "../../helpers/lnurl";
|
||||
import { Kind0ParsedContent } from "../../helpers/nostr/user-metadata";
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import useUserMetadata from "../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../hooks/use-user-profile";
|
||||
import dnsIdentityService from "../../services/dns-identity";
|
||||
import { DraftNostrEvent } from "../../types/nostr-event";
|
||||
import lnurlMetadataService from "../../services/lnurl-metadata";
|
||||
@ -202,7 +202,7 @@ export const ProfileEditView = () => {
|
||||
const publish = usePublishEvent();
|
||||
const readRelays = useReadRelays();
|
||||
const account = useCurrentAccount()!;
|
||||
const metadata = useUserMetadata(account.pubkey, readRelays, { alwaysRequest: true });
|
||||
const metadata = useUserProfile(account.pubkey, readRelays, { alwaysRequest: true });
|
||||
|
||||
const defaultValues = useMemo<FormData>(
|
||||
() => ({
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { MouseEventHandler, useCallback, useMemo } from "react";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import { Button, ButtonGroup, Card, CardBody, CardHeader, Flex, Heading, SimpleGrid, Text } from "@chakra-ui/react";
|
||||
import { Button, Card, CardBody, CardHeader, Flex, Heading, SimpleGrid, Text } from "@chakra-ui/react";
|
||||
import { WarningIcon } from "@chakra-ui/icons";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import { offlineMode } from "../../../services/offline-mode";
|
||||
@ -14,13 +15,13 @@ import RelaySet from "../../../classes/relay-set";
|
||||
import { useReadRelays, useWriteRelays } from "../../../hooks/use-client-relays";
|
||||
import useCurrentAccount from "../../../hooks/use-current-account";
|
||||
import RelayControl from "./relay-control";
|
||||
import useUserMailboxes from "../../../hooks/use-user-mailboxes";
|
||||
import { getRelaysFromExt } from "../../../helpers/nip07";
|
||||
import { useUserDNSIdentity } from "../../../hooks/use-user-dns-identity";
|
||||
import useUserContactRelays from "../../../hooks/use-user-contact-relays";
|
||||
import SelectRelaySet from "./select-relay-set";
|
||||
import { safeRelayUrls } from "../../../helpers/relay";
|
||||
import HoverLinkOverlay from "../../../components/hover-link-overlay";
|
||||
import useReplaceableEvent from "../../../hooks/use-replaceable-event";
|
||||
|
||||
const JapaneseRelays = safeRelayUrls([
|
||||
"wss://r.kojira.io",
|
||||
@ -63,7 +64,7 @@ export default function AppRelays() {
|
||||
const readRelays = useReadRelays();
|
||||
const writeRelays = useWriteRelays();
|
||||
const offline = useSubject(offlineMode);
|
||||
const { event: nip65 } = useUserMailboxes(account?.pubkey) ?? {};
|
||||
const nip65 = useReplaceableEvent(account?.pubkey ? { kind: kinds.RelayList, pubkey: account?.pubkey } : undefined);
|
||||
const nip05 = useUserDNSIdentity(account?.pubkey);
|
||||
const contactRelays = useUserContactRelays(account?.pubkey);
|
||||
|
||||
|
@ -17,6 +17,8 @@ import BackButton from "../../../components/router/back-button";
|
||||
import { addRelayModeToMailbox, removeRelayModeFromMailbox } from "../../../helpers/nostr/mailbox";
|
||||
import AddRelayForm from "../app/add-relay-form";
|
||||
import DebugEventButton from "../../../components/debug-modal/debug-event-button";
|
||||
import useReplaceableEvent from "../../../hooks/use-replaceable-event";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
function RelayLine({ relay, mode, list }: { relay: string; mode: RelayMode; list?: NostrEvent }) {
|
||||
const publish = usePublishEvent();
|
||||
@ -47,8 +49,8 @@ function RelayLine({ relay, mode, list }: { relay: string; mode: RelayMode; list
|
||||
function MailboxesPage() {
|
||||
const account = useCurrentAccount()!;
|
||||
const publish = usePublishEvent();
|
||||
const { inbox, outbox, event } =
|
||||
useUserMailboxes(account.pubkey, undefined, { alwaysRequest: true, ignoreCache: true }) || {};
|
||||
const mailboxes = useUserMailboxes(account.pubkey, undefined, { alwaysRequest: true, ignoreCache: true });
|
||||
const event = useReplaceableEvent({ kind: kinds.RelayList, pubkey: account.pubkey });
|
||||
|
||||
const addRelay = useCallback(
|
||||
async (relay: string, mode: RelayMode) => {
|
||||
@ -84,9 +86,11 @@ function MailboxesPage() {
|
||||
<Text fontStyle="italic" mt="-2">
|
||||
These relays are used by other users to send DMs and notes to you
|
||||
</Text>
|
||||
{inbox?.urls
|
||||
{Array.from(mailboxes?.inboxes ?? [])
|
||||
.sort()
|
||||
.map((url) => <RelayLine key={url} relay={url} mode={RelayMode.READ} list={event ?? undefined} />)}
|
||||
.map((url) => (
|
||||
<RelayLine key={url} relay={url} mode={RelayMode.READ} list={event ?? undefined} />
|
||||
))}
|
||||
<AddRelayForm onSubmit={(r) => addRelay(r, RelayMode.READ)} />
|
||||
|
||||
<Flex gap="2" mt="4">
|
||||
@ -96,9 +100,11 @@ function MailboxesPage() {
|
||||
<Text fontStyle="italic" mt="-2">
|
||||
noStrudel will always publish to these relays so other users can find your notes
|
||||
</Text>
|
||||
{outbox?.urls
|
||||
{Array.from(mailboxes?.outboxes ?? [])
|
||||
.sort()
|
||||
.map((url) => <RelayLine key={url} relay={url} mode={RelayMode.WRITE} list={event ?? undefined} />)}
|
||||
.map((url) => (
|
||||
<RelayLine key={url} relay={url} mode={RelayMode.WRITE} list={event ?? undefined} />
|
||||
))}
|
||||
<AddRelayForm onSubmit={(r) => addRelay(r, RelayMode.WRITE)} />
|
||||
</Flex>
|
||||
);
|
||||
|
@ -31,7 +31,7 @@ import { useForm } from "react-hook-form";
|
||||
import dayjs from "dayjs";
|
||||
import { usePublishEvent } from "../../../providers/global/publish-provider";
|
||||
import useCurrentAccount from "../../../hooks/use-current-account";
|
||||
import useUserMetadata from "../../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../../hooks/use-user-profile";
|
||||
import { useAsync } from "react-use";
|
||||
|
||||
function NameForm() {
|
||||
@ -39,7 +39,7 @@ function NameForm() {
|
||||
const { register, handleSubmit, formState, reset } = useForm({ defaultValues: { name: "" }, mode: "all" });
|
||||
|
||||
const { value: pubkey } = useAsync(async () => webRtcRelaysService.broker.signer.getPublicKey());
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
const metadata = useUserProfile(pubkey);
|
||||
useEffect(() => {
|
||||
if (metadata?.name) reset({ name: metadata.name }, { keepDirty: false, keepTouched: false });
|
||||
}, [metadata?.name]);
|
||||
|
@ -8,6 +8,7 @@ import relayPoolService from "../../../services/relay-pool";
|
||||
import ProfileSearchResults from "./profile-results";
|
||||
import NoteSearchResults from "./note-results";
|
||||
import ArticleSearchResults from "./article-results";
|
||||
import { eventStore } from "../../../services/event-store";
|
||||
|
||||
function createSearchAction(url: string | AbstractRelay) {
|
||||
let sub: Subscription | undefined = undefined;
|
||||
@ -33,7 +34,7 @@ function createSearchAction(url: string | AbstractRelay) {
|
||||
if (sub) sub.close();
|
||||
};
|
||||
|
||||
return { search, cancel };
|
||||
return { search, cancel, relay: url };
|
||||
}
|
||||
|
||||
const searchCache = new LRU<NostrEvent[]>(10);
|
||||
@ -61,6 +62,8 @@ export default function SearchResults({ query, relay }: { query: string; relay:
|
||||
search
|
||||
.search([{ search: query, kinds: [kinds.Metadata, kinds.ShortTextNote, kinds.LongFormArticle], limit: 200 }], {
|
||||
onevent: (event) => {
|
||||
event = eventStore.add(event, typeof search.relay === "string" ? search.relay : search.relay.url);
|
||||
|
||||
setResults((arr) => {
|
||||
const newArr = [...arr, event];
|
||||
searchCache.set(query + relay, newArr);
|
||||
|
@ -25,7 +25,7 @@ import { getEventCoordinate } from "../../../helpers/nostr/event";
|
||||
import { MetadataAvatar } from "../../../components/user/user-avatar";
|
||||
import { ErrorBoundary } from "../../../components/error-boundary";
|
||||
import dnsIdentityService from "../../../services/dns-identity";
|
||||
import useUserMetadata from "../../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../../hooks/use-user-profile";
|
||||
import nostrConnectService from "../../../services/nostr-connect";
|
||||
import accountService from "../../../services/account";
|
||||
import { safeRelayUrls } from "../../../helpers/relay";
|
||||
@ -71,7 +71,7 @@ export default function LoginNostrAddressCreate() {
|
||||
const [name, setName] = useState("");
|
||||
const providers = useNip05Providers();
|
||||
const [selected, setSelected] = useState<NostrEvent>();
|
||||
const userMetadata = useUserMetadata(selected?.pubkey);
|
||||
const userMetadata = useUserProfile(selected?.pubkey);
|
||||
const providerMetadata = useMemo<Kind0ParsedContent | undefined>(
|
||||
() => selected && safeJson(selected.content, undefined),
|
||||
[selected],
|
||||
|
@ -2,7 +2,7 @@ import { CloseIcon } from "@chakra-ui/icons";
|
||||
import { Box, IconButton, Text } from "@chakra-ui/react";
|
||||
|
||||
import { getDisplayName } from "../../../helpers/nostr/user-metadata";
|
||||
import useUserMetadata from "../../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../../hooks/use-user-profile";
|
||||
import accountService from "../../../services/account";
|
||||
import UserAvatar from "../../../components/user/user-avatar";
|
||||
import AccountTypeBadge from "../../../components/account-info-badge";
|
||||
@ -11,7 +11,7 @@ import { Account } from "../../../classes/accounts/account";
|
||||
export default function AccountCard({ account }: { account: Account }) {
|
||||
const pubkey = account.pubkey;
|
||||
// this wont load unless the data is cached since there are no relay connections yet
|
||||
const metadata = useUserMetadata(pubkey, []);
|
||||
const metadata = useUserProfile(pubkey, []);
|
||||
|
||||
return (
|
||||
<Box
|
||||
|
@ -21,7 +21,7 @@ export default function ChatMessageForm({ stream, hideZapButton }: { stream: Par
|
||||
const streamRelays = useReadRelays(useAdditionalRelayContext());
|
||||
const hostReadRelays = useUserInbox(stream.host);
|
||||
|
||||
const relays = useMemo(() => unique([...streamRelays, ...hostReadRelays]), [hostReadRelays, streamRelays]);
|
||||
const relays = useMemo(() => unique([...streamRelays, ...(hostReadRelays ?? [])]), [hostReadRelays, streamRelays]);
|
||||
|
||||
const { setValue, handleSubmit, formState, reset, getValues, watch } = useForm({
|
||||
defaultValues: { content: "" },
|
||||
|
@ -22,7 +22,7 @@ import RequireCurrentAccount from "../../providers/route/require-current-account
|
||||
import { useUsersMetadata } from "../../hooks/use-user-network";
|
||||
import { getPubkeysFromList } from "../../helpers/nostr/lists";
|
||||
import useUserContactList from "../../hooks/use-user-contact-list";
|
||||
import useUserMetadata from "../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../hooks/use-user-profile";
|
||||
import EventStore from "../../classes/event-store";
|
||||
import { isPTag } from "../../types/nostr-event";
|
||||
import { ChevronLeftIcon } from "../../components/icons";
|
||||
@ -69,7 +69,7 @@ function NetworkDMGraphPage() {
|
||||
fetchData();
|
||||
}, [relays, store, contactsPubkeys, since, until]);
|
||||
|
||||
const selfMetadata = useUserMetadata(account.pubkey);
|
||||
const selfMetadata = useUserProfile(account.pubkey);
|
||||
const usersMetadata = useUsersMetadata(contactsPubkeys);
|
||||
|
||||
const newEventTrigger = useObservable(store.onEvent);
|
||||
|
@ -13,7 +13,7 @@ import useUserContactList from "../../hooks/use-user-contact-list";
|
||||
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 useUserProfile from "../../hooks/use-user-profile";
|
||||
import { ChevronLeftIcon } from "../../components/icons";
|
||||
|
||||
export function useUsersMuteLists(pubkeys: string[], additionalRelays?: Iterable<string>) {
|
||||
@ -30,7 +30,7 @@ function NetworkGraphPage() {
|
||||
const navigate = useNavigate();
|
||||
const account = useCurrentAccount()!;
|
||||
|
||||
const selfMetadata = useUserMetadata(account.pubkey);
|
||||
const selfMetadata = useUserProfile(account.pubkey);
|
||||
const contacts = useUserContactList(account.pubkey);
|
||||
const contactsPubkeys = useMemo(
|
||||
() => (contacts ? getPubkeysFromList(contacts).map((p) => p.pubkey) : []),
|
||||
|
@ -14,7 +14,7 @@ import TorrentTableRow from "./components/torrent-table-row";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import useUserMetadata from "../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../hooks/use-user-profile";
|
||||
import accountService from "../../services/account";
|
||||
import CategorySelect from "./components/category-select";
|
||||
import useRouteSearchValue from "../../hooks/use-route-search-value";
|
||||
@ -25,7 +25,7 @@ function Warning() {
|
||||
const navigate = useNavigate();
|
||||
const toast = useToast();
|
||||
const account = useCurrentAccount()!;
|
||||
const metadata = useUserMetadata(account.pubkey);
|
||||
const metadata = useUserProfile(account.pubkey);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const createAnonAccount = async () => {
|
||||
setLoading(true);
|
||||
|
@ -24,7 +24,7 @@ import { EmbedableContent, embedUrls } from "../../../helpers/embeds";
|
||||
import { truncatedId } from "../../../helpers/nostr/event";
|
||||
import { parseAddress } from "../../../services/dns-identity";
|
||||
import { useAdditionalRelayContext } from "../../../providers/local/additional-relay-context";
|
||||
import useUserMetadata from "../../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../../hooks/use-user-profile";
|
||||
import { embedNostrLinks, renderGenericUrl } from "../../../components/external-embeds";
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
@ -64,7 +64,7 @@ function buildDescriptionContent(description: string) {
|
||||
}
|
||||
|
||||
function DNSIdentityWarning({ pubkey }: { pubkey: string }) {
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
const metadata = useUserProfile(pubkey);
|
||||
const dnsIdentity = useUserDNSIdentity(pubkey);
|
||||
const parsedNip05 = metadata?.nip05 ? parseAddress(metadata.nip05) : undefined;
|
||||
const nip05URL = parsedNip05
|
||||
@ -105,7 +105,7 @@ export default function UserAboutTab() {
|
||||
const contextRelays = useAdditionalRelayContext();
|
||||
const colorModal = useDisclosure();
|
||||
|
||||
const metadata = useUserMetadata(pubkey, contextRelays);
|
||||
const metadata = useUserProfile(pubkey, contextRelays);
|
||||
const npub = nip19.npubEncode(pubkey);
|
||||
const nprofile = useSharableProfileId(pubkey);
|
||||
const pubkeyColor = "#" + pubkey.slice(0, 6);
|
||||
|
@ -5,7 +5,7 @@ import { EditIcon, GhostIcon } from "../../../components/icons";
|
||||
import UserAvatar from "../../../components/user/user-avatar";
|
||||
import UserDnsIdentity from "../../../components/user/user-dns-identity";
|
||||
import useCurrentAccount from "../../../hooks/use-current-account";
|
||||
import useUserMetadata from "../../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../../hooks/use-user-profile";
|
||||
import { UserProfileMenu } from "./user-profile-menu";
|
||||
import { UserFollowButton } from "../../../components/user/user-follow-button";
|
||||
import accountService from "../../../services/account";
|
||||
@ -20,7 +20,7 @@ export default function Header({
|
||||
showRelaySelectionModal: () => void;
|
||||
}) {
|
||||
const navigate = useNavigate();
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
const metadata = useUserProfile(pubkey);
|
||||
|
||||
const account = useCurrentAccount();
|
||||
const isSelf = pubkey === account?.pubkey;
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
ShareIcon,
|
||||
} from "../../../components/icons";
|
||||
import accountService from "../../../services/account";
|
||||
import useUserMetadata from "../../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../../hooks/use-user-profile";
|
||||
import UserDebugModal from "../../../components/debug-modal/user-debug-modal";
|
||||
import { useSharableProfileId } from "../../../hooks/use-shareable-profile-id";
|
||||
import useUserMuteActions from "../../../hooks/use-user-mute-actions";
|
||||
@ -33,7 +33,7 @@ export const UserProfileMenu = ({
|
||||
}: { pubkey: string; showRelaySelectionModal?: () => void } & Omit<MenuIconButtonProps, "children">) => {
|
||||
const toast = useToast();
|
||||
const account = useCurrentAccount();
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
const metadata = useUserProfile(pubkey);
|
||||
const infoModal = useDisclosure();
|
||||
const sharableId = useSharableProfileId(pubkey);
|
||||
const { isMuted, mute, unmute } = useUserMuteActions(pubkey);
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { IconButton, IconButtonProps, useDisclosure } from "@chakra-ui/react";
|
||||
import useUserMetadata from "../../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../../hooks/use-user-profile";
|
||||
import { LightningIcon } from "../../../components/icons";
|
||||
import ZapModal from "../../../components/event-zap-modal";
|
||||
import { useInvoiceModalContext } from "../../../providers/route/invoice-modal";
|
||||
|
||||
export default function UserZapButton({ pubkey, ...props }: { pubkey: string } & Omit<IconButtonProps, "aria-label">) {
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
const metadata = useUserProfile(pubkey);
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const { requestPay } = useInvoiceModalContext();
|
||||
if (!metadata) return null;
|
||||
|
@ -27,7 +27,7 @@ import {
|
||||
} from "@chakra-ui/react";
|
||||
|
||||
import { Outlet, useMatches, useNavigate } from "react-router-dom";
|
||||
import useUserMetadata from "../../hooks/use-user-metadata";
|
||||
import useUserProfile from "../../hooks/use-user-profile";
|
||||
import { getDisplayName } from "../../helpers/nostr/user-metadata";
|
||||
import { useAppTitle } from "../../hooks/use-app-title";
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
@ -63,7 +63,7 @@ const tabs = [
|
||||
function useUserBestOutbox(pubkey: string, count: number = 4) {
|
||||
const mailbox = useUserMailboxes(pubkey);
|
||||
const relays = useReadRelays();
|
||||
const sorted = relayScoreboardService.getRankedRelays(mailbox?.outbox.size ? mailbox?.outbox : relays);
|
||||
const sorted = relayScoreboardService.getRankedRelays(mailbox?.outboxes.size ? mailbox?.outboxes : relays);
|
||||
return !count ? sorted : sorted.slice(0, count);
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@ const UserView = () => {
|
||||
const relayModal = useDisclosure();
|
||||
const readRelays = unique([...userTopRelays, ...pointerRelays]);
|
||||
|
||||
const metadata = useUserMetadata(pubkey, userTopRelays, { alwaysRequest: true });
|
||||
const metadata = useUserProfile(pubkey, userTopRelays, { alwaysRequest: true });
|
||||
useAppTitle(getDisplayName(metadata, pubkey));
|
||||
|
||||
const matches = useMatches();
|
||||
|
@ -59,7 +59,7 @@ const UserRelaysTab = () => {
|
||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||
const mailboxes = useUserMailboxes(pubkey);
|
||||
|
||||
const readRelays = useReadRelays(mailboxes?.outbox);
|
||||
const readRelays = useReadRelays(mailboxes?.outboxes);
|
||||
const timeline = useTimelineLoader(`${truncateId(pubkey)}-relay-reviews`, readRelays, {
|
||||
authors: [pubkey],
|
||||
kinds: [1985],
|
||||
@ -72,7 +72,7 @@ const UserRelaysTab = () => {
|
||||
|
||||
const otherReviews = reviews.filter((e) => {
|
||||
const url = e.tags.find((t) => t[0] === "r")?.[1];
|
||||
return url && !mailboxes?.relays.has(url);
|
||||
return url && !mailboxes?.inboxes.has(url) && !mailboxes?.outboxes.has(url);
|
||||
});
|
||||
|
||||
return (
|
||||
@ -81,7 +81,7 @@ const UserRelaysTab = () => {
|
||||
Inboxes
|
||||
</Heading>
|
||||
<VStack divider={<StackDivider />} py="2" align="stretch">
|
||||
{mailboxes?.inbox.urls.map((url) => (
|
||||
{Array.from(mailboxes?.inboxes ?? []).map((url) => (
|
||||
<ErrorBoundary>
|
||||
<Relay key={url} url={url} reviews={getRelayReviews(url, reviews)} />
|
||||
</ErrorBoundary>
|
||||
@ -91,7 +91,7 @@ const UserRelaysTab = () => {
|
||||
Outboxes
|
||||
</Heading>
|
||||
<VStack divider={<StackDivider />} py="2" align="stretch">
|
||||
{mailboxes?.outbox.urls.map((url) => (
|
||||
{Array.from(mailboxes?.outboxes ?? []).map((url) => (
|
||||
<ErrorBoundary>
|
||||
<Relay key={url} url={url} reviews={getRelayReviews(url, reviews)} />
|
||||
</ErrorBoundary>
|
||||
|
40
yarn.lock
40
yarn.lock
@ -2549,6 +2549,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-0.5.1.tgz#292f388b69c9ed80d49dca1a5cbfd4ff06852111"
|
||||
integrity sha512-aNE06lbe36ifvMbbWvmmF/8jx6EQPu2HVg70V95T+iGjOuYwPpAccwAQc2HlXO2D0aiQ3zavbMga4jjWnrpiPA==
|
||||
|
||||
"@noble/ciphers@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-1.0.0.tgz#34758a1cbfcd4126880f83e6b1cdeb88785b7970"
|
||||
integrity sha512-wH5EHOmLi0rEazphPbecAzmjd12I6/Yv/SiHdkA9LSycsQk7RuuTp7am5/o62qYr0RScE7Pc9icXGBbsr6cesA==
|
||||
|
||||
"@noble/curves@1.1.0", "@noble/curves@~1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d"
|
||||
@ -3307,6 +3312,10 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
|
||||
dependencies:
|
||||
color-convert "^2.0.1"
|
||||
|
||||
"applesauce-core@link:../applesauce/packages/core":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
argparse@^1.0.7:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
|
||||
@ -4130,6 +4139,13 @@ debug@^4.3.6:
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
debug@^4.3.7:
|
||||
version "4.3.7"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
|
||||
integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
|
||||
dependencies:
|
||||
ms "^2.1.3"
|
||||
|
||||
decamelize-keys@^1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.1.tgz#04a2d523b2f18d80d0158a43b895d56dff8d19d8"
|
||||
@ -6182,6 +6198,11 @@ ms@2.1.2:
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
ms@^2.1.3:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||
|
||||
nano-css@^5.6.1:
|
||||
version "5.6.1"
|
||||
resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.6.1.tgz#964120cb1af6cccaa6d0717a473ccd876b34c197"
|
||||
@ -6211,6 +6232,11 @@ nanoid@^5.0.6:
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.6.tgz#7f99a033aa843e4dcf9778bdaec5eb02f4dc44d5"
|
||||
integrity sha512-rRq0eMHoGZxlvaFOUdK1Ev83Bd1IgzzR+WJ3IbDJ7QOSdAxYjlurSPqFs9s4lJg29RT6nPwizFtJhQS6V5xgiA==
|
||||
|
||||
nanoid@^5.0.7:
|
||||
version "5.0.7"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.7.tgz#6452e8c5a816861fd9d2b898399f7e5fd6944cc6"
|
||||
integrity sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==
|
||||
|
||||
nearley@^2.20.1:
|
||||
version "2.20.1"
|
||||
resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.20.1.tgz#246cd33eff0d012faf197ff6774d7ac78acdd474"
|
||||
@ -6333,6 +6359,20 @@ nostr-tools@^2.7.1:
|
||||
optionalDependencies:
|
||||
nostr-wasm v0.1.0
|
||||
|
||||
nostr-tools@^2.7.2:
|
||||
version "2.7.2"
|
||||
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-2.7.2.tgz#74a6ff543a81da1dcce9563b9317faa17221acce"
|
||||
integrity sha512-Bq3Ug0SZFtgtL1+0wCnAe8AJtI7yx/00/a2nUug9SkhfOwlKS92Tef12iCK9FdwXw+oFZWMtRnSwcLayQso+xA==
|
||||
dependencies:
|
||||
"@noble/ciphers" "^0.5.1"
|
||||
"@noble/curves" "1.2.0"
|
||||
"@noble/hashes" "1.3.1"
|
||||
"@scure/base" "1.1.1"
|
||||
"@scure/bip32" "1.3.1"
|
||||
"@scure/bip39" "1.2.1"
|
||||
optionalDependencies:
|
||||
nostr-wasm v0.1.0
|
||||
|
||||
nostr-wasm@^0.1.0, nostr-wasm@v0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/nostr-wasm/-/nostr-wasm-0.1.0.tgz#17af486745feb2b7dd29503fdd81613a24058d94"
|
||||
|
Loading…
x
Reference in New Issue
Block a user