Move core logic out into applesauce packages

This commit is contained in:
hzrd149 2024-09-23 22:45:06 -05:00
parent 6e6baa7a98
commit 4d0d770db2
82 changed files with 390 additions and 271 deletions

View File

@ -0,0 +1,5 @@
---
"nostrudel": minor
---
Move core logic out into applesauce packages

View File

@ -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",

View File

@ -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);

View File

@ -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]) {

View File

@ -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);

View File

@ -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) {

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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(

View File

@ -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 () => {

View File

@ -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 = {

View File

@ -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);

View File

@ -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>

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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" />}
</>
);

View File

@ -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();

View File

@ -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);

View File

@ -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 (

View File

@ -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]);

View File

@ -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">

View File

@ -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;

View File

@ -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}

View File

@ -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;

View File

@ -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) {

View File

@ -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);

View File

@ -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 (

View File

@ -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");

View File

@ -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) {

View File

@ -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);
}

View 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;
}

View File

@ -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 };
}

View File

@ -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]);

View File

@ -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);
}

View File

@ -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) ?? [];
}

View File

@ -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);
}

View File

@ -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),

View File

@ -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;
}

View File

@ -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;
}

View 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);
}

View File

@ -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] },

View File

@ -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]);

View File

@ -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);

View File

@ -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();

View File

@ -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);
}

View File

@ -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 || []);

View 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;
}

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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 });

View File

@ -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 (

View File

@ -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}>

View File

@ -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);

View File

@ -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`,

View File

@ -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],

View File

@ -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();

View File

@ -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 ||

View File

@ -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);

View File

@ -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);

View File

@ -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>(
() => ({

View File

@ -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);

View File

@ -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>
);

View File

@ -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]);

View File

@ -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);

View File

@ -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],

View File

@ -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

View File

@ -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: "" },

View File

@ -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);

View File

@ -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) : []),

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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();

View File

@ -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>

View File

@ -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"