cleanup route parsing

This commit is contained in:
hzrd149 2023-12-23 11:17:09 -06:00
parent e40c2e160d
commit 621b3ac510
18 changed files with 127 additions and 155 deletions

View File

@ -187,16 +187,18 @@ export function pointerToATag(pointer: AddressPointer): ATag {
return relay ? ["a", coordinate, relay] : ["a", coordinate];
}
export type CustomEventPointer = Omit<AddressPointer, "identifier"> & {
export type CustomAddressPointer = Omit<AddressPointer, "identifier"> & {
identifier?: string;
};
export function parseCoordinate(a: string): CustomEventPointer | null;
export function parseCoordinate(a: string, requireD: false): CustomEventPointer | null;
export function parseCoordinate(a: string): CustomAddressPointer | null;
export function parseCoordinate(a: string, requireD: false): CustomAddressPointer | null;
export function parseCoordinate(a: string, requireD: true): AddressPointer | null;
export function parseCoordinate(a: string, requireD: false, silent: false): CustomEventPointer;
export function parseCoordinate(a: string, requireD: false, silent: false): CustomAddressPointer;
export function parseCoordinate(a: string, requireD: true, silent: false): AddressPointer;
export function parseCoordinate(a: string, requireD = false, silent = true): CustomEventPointer | null {
export function parseCoordinate(a: string, requireD: true, silent: true): AddressPointer | null;
export function parseCoordinate(a: string, requireD: false, silent: true): CustomAddressPointer | null;
export function parseCoordinate(a: string, requireD = false, silent = true): CustomAddressPointer | null {
const parts = a.split(":") as (string | undefined)[];
const kind = parts[0] && parseInt(parts[0]);
const pubkey = parts[1];

View File

@ -0,0 +1,33 @@
import { useParams } from "react-router-dom";
import { nip19 } from "nostr-tools";
import type { AddressPointer } from "nostr-tools/lib/types/nip19";
import { CustomAddressPointer, parseCoordinate } from "../helpers/nostr/events";
export default function useParamsAddressPointer(key: string): AddressPointer;
export default function useParamsAddressPointer(key: string, requireD: true): AddressPointer;
export default function useParamsAddressPointer(key: string, requireD: false): CustomAddressPointer;
export default function useParamsAddressPointer(key: string, requireD: boolean = true): AddressPointer {
const params = useParams();
const value = params[key] as string;
if (!value) throw new Error(`Missing ${key} in route`);
if (value.includes(":")) {
if (requireD) {
return parseCoordinate(value, true, false);
} else {
// @ts-ignore
return parseCoordinate(value, false, false);
}
}
// its not a coordinate, try to parse the nip19
const pointer = nip19.decode(value as string);
switch (pointer.type) {
case "naddr":
return pointer.data;
default:
throw new Error(`Unknown type ${pointer.type}`);
}
}

View File

@ -0,0 +1,23 @@
import { useParams } from "react-router-dom";
import { nip19 } from "nostr-tools";
import type { EventPointer } from "nostr-tools/lib/types/nip19";
import { isHexKey } from "../helpers/nip19";
export default function useParamsEventPointer(key: string): EventPointer {
const params = useParams();
const value = params[key] as string;
if (!value) throw new Error(`Missing ${key} in route`);
if (isHexKey(value)) return { id: value, relays: [] };
const pointer = nip19.decode(value as string);
switch (pointer.type) {
case "note":
return { id: pointer.data as string, relays: [] };
case "nevent":
return pointer.data;
default:
throw new Error(`Unknown type ${pointer.type}`);
}
}

View File

@ -0,0 +1,22 @@
import { useParams } from "react-router-dom";
import { nip19 } from "nostr-tools";
import type { ProfilePointer } from "nostr-tools/lib/types/nip19";
import { isHexKey } from "../helpers/nip19";
export default function useParamsProfilePointer(key: string = "pubkey"): ProfilePointer {
const params = useParams();
const value = params[key] as string;
if (!value) throw new Error(`Missing ${key} in route`);
if (isHexKey(value)) return { pubkey: value, relays: [] };
const pointer = nip19.decode(value);
switch (pointer.type) {
case "npub":
return { pubkey: pointer.data as string, relays: [] };
case "nprofile":
return pointer.data;
default:
throw new Error(`Unknown type ${pointer.type}`);
}
}

View File

@ -2,11 +2,11 @@ import { useMemo } from "react";
import { useReadRelayUrls } from "./use-client-relays";
import replaceableEventLoaderService, { RequestOptions } from "../services/replaceable-event-requester";
import { CustomEventPointer, parseCoordinate } from "../helpers/nostr/events";
import { CustomAddressPointer, parseCoordinate } from "../helpers/nostr/events";
import useSubject from "./use-subject";
export default function useReplaceableEvent(
cord: string | CustomEventPointer | undefined,
cord: string | CustomAddressPointer | undefined,
additionalRelays: string[] = [],
opts: RequestOptions = {},
) {

View File

@ -2,13 +2,13 @@ import { useMemo } from "react";
import { useReadRelayUrls } from "./use-client-relays";
import replaceableEventLoaderService, { RequestOptions } from "../services/replaceable-event-requester";
import { CustomEventPointer, parseCoordinate } from "../helpers/nostr/events";
import { CustomAddressPointer, parseCoordinate } from "../helpers/nostr/events";
import Subject from "../classes/subject";
import { NostrEvent } from "../types/nostr-event";
import useSubjects from "./use-subjects";
export default function useReplaceableEvents(
coordinates: string[] | CustomEventPointer[] | undefined,
coordinates: string[] | CustomAddressPointer[] | undefined,
additionalRelays: string[] = [],
opts: RequestOptions = {},
) {

View File

@ -1,5 +1,5 @@
import { useNavigate, useParams } from "react-router-dom";
import { Kind, nip19 } from "nostr-tools";
import { useNavigate } from "react-router-dom";
import { Kind } from "nostr-tools";
import {
Button,
Flex,
@ -35,6 +35,7 @@ import VerticalPageLayout from "../../components/vertical-page-layout";
import BadgeAwardCard from "./components/badge-award-card";
import TimelineLoader from "../../classes/timeline-loader";
import { ErrorBoundary } from "../../components/error-boundary";
import useParamsAddressPointer from "../../hooks/use-params-address-pointer";
function BadgeActivityTab({ timeline }: { timeline: TimelineLoader }) {
const awards = useSubject(timeline.timeline);
@ -154,15 +155,8 @@ function BadgeDetailsPage({ badge }: { badge: NostrEvent }) {
);
}
function useBadgeCoordinate() {
const { naddr } = useParams() as { naddr: string };
const parsed = nip19.decode(naddr);
if (parsed.type !== "naddr") throw new Error(`Unknown type ${parsed.type}`);
return parsed.data;
}
export default function BadgeDetailsView() {
const pointer = useBadgeCoordinate();
const pointer = useParamsAddressPointer("naddr");
const badge = useReplaceableEvent(pointer);
if (!badge) return <Spinner />;

View File

@ -1,9 +1,8 @@
import { memo, useCallback, useMemo } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useNavigate } from "react-router-dom";
import { Button, Flex, Heading, Spacer, Spinner, useDisclosure } from "@chakra-ui/react";
import { Kind } from "nostr-tools";
import { safeDecode } from "../../helpers/nip19";
import useSingleEvent from "../../hooks/use-single-event";
import { ErrorBoundary } from "../../components/error-boundary";
import { NostrEvent } from "../../types/nostr-event";
@ -25,6 +24,7 @@ import { groupMessages } from "../../helpers/nostr/dms";
import ChannelMessageBlock from "./components/channel-message-block";
import TimelineActionAndStatus from "../../components/timeline-page/timeline-action-and-status";
import ChannelMessageForm from "./components/send-message-form";
import useParamsEventPointer from "../../hooks/use-params-event-pointer";
const ChannelChatLog = memo(({ timeline, channel }: { timeline: TimelineLoader; channel: NostrEvent }) => {
const messages = useSubject(timeline.timeline);
@ -109,14 +109,8 @@ function ChannelPage({ channel }: { channel: NostrEvent }) {
}
export default function ChannelView() {
const { id } = useParams() as { id: string };
const parsed = useMemo(() => {
const result = safeDecode(id);
if (!result) return;
if (result.type === "note") return { id: result.data };
if (result.type === "nevent") return result.data;
}, [id]);
const channel = useSingleEvent(parsed?.id, parsed?.relays ?? []);
const pointer = useParamsEventPointer("id");
const channel = useSingleEvent(pointer?.id, pointer?.relays);
if (!channel) return <Spinner />;

View File

@ -24,6 +24,7 @@ import ThreadsProvider from "../../providers/thread-provider";
import { useRouterMarker } from "../../providers/drawer-sub-view-provider";
import TimelineLoader from "../../classes/timeline-loader";
import DirectMessageBlock from "./components/direct-message-block";
import useParamsProfilePointer from "../../hooks/use-params-pubkey-pointer";
/** This is broken out from DirectMessageChatPage for performance reasons. Don't use outside of file */
const ChatLog = memo(({ timeline }: { timeline: TimelineLoader }) => {
@ -142,25 +143,8 @@ function DirectMessageChatPage({ pubkey }: { pubkey: string }) {
);
}
function useUserPointer() {
const { pubkey } = useParams() as { pubkey: string };
if (isHexKey(pubkey)) return { pubkey, relays: [] };
const pointer = nip19.decode(pubkey);
switch (pointer.type) {
case "npub":
return { pubkey: pointer.data as string, relays: [] };
case "nprofile":
const d = pointer.data as nip19.ProfilePointer;
return { pubkey: d.pubkey, relays: d.relays ?? [] };
default:
throw new Error(`Unknown type ${pointer.type}`);
}
}
export default function DirectMessageChatView() {
const { pubkey } = useUserPointer();
const { pubkey } = useParamsProfilePointer();
return (
<RequireCurrentAccount>

View File

@ -13,8 +13,8 @@ import {
useToast,
} from "@chakra-ui/react";
import { ChevronLeftIcon } from "@chakra-ui/icons";
import { nip19 } from "nostr-tools";
import dayjs from "dayjs";
import { useNavigate } from "react-router-dom";
import {
DMV_CONTENT_DISCOVERY_JOB_KIND,
@ -32,7 +32,6 @@ import useSubject from "../../hooks/use-subject";
import useTimelineLoader from "../../hooks/use-timeline-loader";
import { useReadRelayUrls } from "../../hooks/use-client-relays";
import { useUserRelays } from "../../hooks/use-user-relays";
import { useNavigate, useParams } from "react-router-dom";
import { useSigningContext } from "../../providers/signing-provider";
import useCurrentAccount from "../../hooks/use-current-account";
import RequireCurrentAccount from "../../providers/require-current-account";
@ -40,8 +39,8 @@ import { CodeIcon } from "../../components/icons";
import { unique } from "../../helpers/array";
import DebugChains from "./components/debug-chains";
import Feed from "./components/feed";
import { parseCoordinate } from "../../helpers/nostr/events";
import { AddressPointer } from "nostr-tools/lib/types/nip19";
import useParamsAddressPointer from "../../hooks/use-params-address-pointer";
function DVMFeedPage({ pointer }: { pointer: AddressPointer }) {
const [since] = useState(() => dayjs().subtract(1, "hour").unix());
@ -124,20 +123,8 @@ function DVMFeedPage({ pointer }: { pointer: AddressPointer }) {
);
}
function useDVMCoordinate() {
const { addr } = useParams() as { addr: string };
if (addr.includes(":")) {
const parsed = parseCoordinate(addr, true);
if (!parsed) throw new Error("Bad coordinate");
return parsed;
}
const parsed = nip19.decode(addr);
if (parsed.type !== "naddr") throw new Error(`Unknown type ${parsed.type}`);
return parsed.data;
}
export default function DVMFeedView() {
const pointer = useDVMCoordinate();
const pointer = useParamsAddressPointer("addr");
return (
<RequireCurrentAccount>

View File

@ -1,6 +1,5 @@
import { useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { nip19 } from "nostr-tools";
import { useNavigate } from "react-router-dom";
import { useForm } from "react-hook-form";
import { useThrottle } from "react-use";
import dayjs from "dayjs";
@ -8,7 +7,6 @@ import dayjs from "dayjs";
import {
Button,
ButtonGroup,
Divider,
Flex,
Heading,
Image,
@ -39,6 +37,7 @@ import UserAvatarLink from "../../components/user-avatar-link";
import NoteZapButton from "../../components/note/note-zap-button";
import { QuoteRepostButton } from "../../components/note/components/quote-repost-button";
import Timestamp from "../../components/timestamp";
import useParamsAddressPointer from "../../hooks/use-params-address-pointer";
function AddEmojiForm({ onAdd }: { onAdd: (values: { name: string; url: string }) => void }) {
const { register, handleSubmit, watch, getValues, reset } = useForm({
@ -220,15 +219,8 @@ function EmojiPackPage({ pack }: { pack: NostrEvent }) {
);
}
function useListCoordinate() {
const { addr } = useParams() as { addr: string };
const parsed = nip19.decode(addr);
if (parsed.type !== "naddr") throw new Error(`Unknown type ${parsed.type}`);
return parsed.data;
}
export default function EmojiPackView() {
const coordinate = useListCoordinate();
const coordinate = useParamsAddressPointer("addr");
const pack = useReplaceableEvent(coordinate);
if (!pack) {

View File

@ -1,5 +1,4 @@
import { useNavigate, useParams } from "react-router-dom";
import { nip19 } from "nostr-tools";
import { useNavigate } from "react-router-dom";
import { Button, ButtonGroup, Divider, Flex, Heading, Spacer, Spinner } from "@chakra-ui/react";
import { ChevronLeftIcon } from "../../components/icons";
@ -7,8 +6,6 @@ import GoalMenu from "./components/goal-menu";
import { getGoalAmount, getGoalName } from "../../helpers/nostr/goal";
import GoalProgress from "./components/goal-progress";
import useSingleEvent from "../../hooks/use-single-event";
import { isHexKey } from "../../helpers/nip19";
import { EventPointer } from "nostr-tools/lib/types/nip19";
import UserAvatar from "../../components/user-avatar";
import UserLink from "../../components/user-link";
import GoalContents from "./components/goal-contents";
@ -16,19 +13,11 @@ import GoalZapList from "./components/goal-zap-list";
import { readablizeSats } from "../../helpers/bolt11";
import GoalZapButton from "./components/goal-zap-button";
import VerticalPageLayout from "../../components/vertical-page-layout";
function useGoalPointerFromParams(): EventPointer {
const { id } = useParams() as { id: string };
if (isHexKey(id)) return { id };
const parsed = nip19.decode(id);
if (parsed.type === "nevent") return parsed.data;
if (parsed.type === "note") return { id: parsed.data };
throw new Error("bad goal id");
}
import useParamsEventPointer from "../../hooks/use-params-event-pointer";
export default function GoalDetailsView() {
const navigate = useNavigate();
const pointer = useGoalPointerFromParams();
const pointer = useParamsEventPointer("id");
const goal = useSingleEvent(pointer.id, pointer.relays);
if (!goal) return <Spinner />;

View File

@ -1,12 +1,12 @@
import { useNavigate, useParams } from "react-router-dom";
import { useNavigate } from "react-router-dom";
import { Kind, nip19 } from "nostr-tools";
import type { DecodeResult } from "nostr-tools/lib/types/nip19";
import { Box, Button, Flex, Heading, SimpleGrid, Spacer, Text } from "@chakra-ui/react";
import UserLink from "../../components/user-link";
import { Box, Button, Flex, Heading, SimpleGrid, Spacer, Text } from "@chakra-ui/react";
import { ChevronLeftIcon } from "../../components/icons";
import useCurrentAccount from "../../hooks/use-current-account";
import { useDeleteEventContext } from "../../providers/delete-event-provider";
import { parseCoordinate } from "../../helpers/nostr/events";
import {
getEventsFromList,
getListDescription,
@ -27,23 +27,9 @@ import VerticalPageLayout from "../../components/vertical-page-layout";
import { COMMUNITY_DEFINITION_KIND } from "../../helpers/nostr/communities";
import { EmbedEvent, EmbedEventPointer } from "../../components/embed-event";
import { encodePointer } from "../../helpers/nip19";
import { DecodeResult } from "nostr-tools/lib/types/nip19";
import useSingleEvent from "../../hooks/use-single-event";
import UserAvatarLink from "../../components/user-avatar-link";
function useListCoordinate() {
const { addr } = useParams() as { addr: string };
if (addr.includes(":")) {
const parsed = parseCoordinate(addr);
if (!parsed) throw new Error("Bad coordinate");
return parsed;
}
const parsed = nip19.decode(addr);
if (parsed.type !== "naddr") throw new Error(`Unknown type ${parsed.type}`);
return parsed.data;
}
import useParamsAddressPointer from "../../hooks/use-params-address-pointer";
function BookmarkedEvent({ id, relays }: { id: string; relays?: string[] }) {
const event = useSingleEvent(id, relays);
@ -53,16 +39,16 @@ function BookmarkedEvent({ id, relays }: { id: string; relays?: string[] }) {
export default function ListDetailsView() {
const navigate = useNavigate();
const coordinate = useListCoordinate();
const pointer = useParamsAddressPointer("addr");
const { deleteEvent } = useDeleteEventContext();
const account = useCurrentAccount();
const list = useReplaceableEvent(coordinate, [], { alwaysRequest: true });
const list = useReplaceableEvent(pointer, [], { alwaysRequest: true });
if (!list)
return (
<>
Looking for list "{coordinate.identifier}" created by <UserLink pubkey={coordinate.pubkey} />
Looking for list "{pointer.identifier}" created by <UserLink pubkey={pointer.pubkey} />
</>
);

View File

@ -0,0 +1,5 @@
import useParamsEventPointer from "../../hooks/use-params-event-pointer";
export default function NoteTransformView() {
const pointer = useParamsEventPointer("id");
}

View File

@ -42,8 +42,6 @@ import StreamSatsPerMinute from "../components/stream-sats-per-minute";
import { UserEmojiProvider } from "../../../providers/emoji-provider";
import StreamStatusBadge from "../components/status-badge";
import ChatMessageForm from "./stream-chat/stream-chat-form";
import useStreamChatTimeline from "./stream-chat/use-stream-chat-timeline";
import UserSearchDirectoryProvider from "../../../providers/user-directory-provider";
import StreamChatLog from "./stream-chat/chat-log";
import TopZappers from "../components/top-zappers";
import StreamHashtags from "../components/stream-hashtags";

View File

@ -13,6 +13,7 @@ import IntersectionObserverProvider from "../../providers/intersection-observer"
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
import useThreadTimelineLoader from "../../hooks/use-thread-timeline-loader";
import useSingleEvent from "../../hooks/use-single-event";
import useParamsEventPointer from "../../hooks/use-params-event-pointer";
function ThreadPage({ thread, rootId, focusId }: { thread: Map<string, ThreadItem>; rootId: string; focusId: string }) {
const isRoot = rootId === focusId;
@ -61,23 +62,8 @@ function ThreadPage({ thread, rootId, focusId }: { thread: Map<string, ThreadIte
);
}
function useNotePointer() {
const { id } = useParams() as { id: string };
if (isHexKey(id)) return { id, relays: [] };
const pointer = nip19.decode(id);
switch (pointer.type) {
case "note":
return { id: pointer.data as string, relays: [] };
case "nevent":
return { id: pointer.data.id, relays: pointer.data.relays ?? [] };
default:
throw new Error(`Unknown type ${pointer.type}`);
}
}
export default function ThreadView() {
const pointer = useNotePointer();
const pointer = useParamsEventPointer("id");
const readRelays = useReadRelayUrls(pointer.relays);
const focusedEvent = useSingleEvent(pointer.id, pointer.relays);

View File

@ -13,16 +13,13 @@ import {
Tbody,
Td,
Text,
Textarea,
Th,
Thead,
Tr,
useDisclosure,
} from "@chakra-ui/react";
import { useParams } from "react-router-dom";
import useSingleEvent from "../../hooks/use-single-event";
import { safeDecode } from "../../helpers/nip19";
import VerticalPageLayout from "../../components/vertical-page-layout";
import { NostrEvent } from "../../types/nostr-event";
import { ErrorBoundary } from "../../components/error-boundary";
@ -46,6 +43,7 @@ import TorrentComments from "./components/torrents-comments";
import ReplyForm from "../thread/components/reply-form";
import { getReferences } from "../../helpers/nostr/events";
import MessageTextCircle01 from "../../components/icons/message-text-circle-01";
import useParamsEventPointer from "../../hooks/use-params-event-pointer";
function TorrentDetailsPage({ torrent }: { torrent: NostrEvent }) {
const files = getTorrentFiles(torrent);
@ -141,14 +139,8 @@ function TorrentDetailsPage({ torrent }: { torrent: NostrEvent }) {
}
export default function TorrentDetailsView() {
const { id } = useParams() as { id: string };
const parsed = useMemo(() => {
const result = safeDecode(id);
if (!result) return;
if (result.type === "note") return { id: result.data };
if (result.type === "nevent") return result.data;
}, [id]);
const torrent = useSingleEvent(parsed?.id, parsed?.relays ?? []);
const pointer = useParamsEventPointer("id");
const torrent = useSingleEvent(pointer?.id, pointer?.relays ?? []);
if (!torrent) return <Spinner />;

View File

@ -46,6 +46,7 @@ import { STEMSTR_TRACK_KIND } from "../../helpers/nostr/stemstr";
import { STREAM_KIND } from "../../helpers/nostr/stream";
import { TORRENT_KIND } from "../../helpers/nostr/torrents";
import { GOAL_KIND } from "../../helpers/nostr/goal";
import useParamsProfilePointer from "../../hooks/use-params-pubkey-pointer";
const tabs = [
{ label: "About", path: "about" },
@ -66,22 +67,6 @@ const tabs = [
{ label: "Muted by", path: "muted-by" },
];
function useUserPointer() {
const { pubkey } = useParams() as { pubkey: string };
if (isHexKey(pubkey)) return { pubkey, relays: [] };
const pointer = nip19.decode(pubkey);
switch (pointer.type) {
case "npub":
return { pubkey: pointer.data as string, relays: [] };
case "nprofile":
const d = pointer.data as nip19.ProfilePointer;
return { pubkey: d.pubkey, relays: d.relays ?? [] };
default:
throw new Error(`Unknown type ${pointer.type}`);
}
}
function useUserTopRelays(pubkey: string, count: number = 4) {
const readRelays = useReadRelayUrls();
// get user relays
@ -96,7 +81,7 @@ function useUserTopRelays(pubkey: string, count: number = 4) {
}
const UserView = () => {
const { pubkey, relays: pointerRelays } = useUserPointer();
const { pubkey, relays: pointerRelays = [] } = useParamsProfilePointer();
const navigate = useNavigate();
const [relayCount, setRelayCount] = useState(4);
const userTopRelays = useUserTopRelays(pubkey, relayCount);