diff --git a/.changeset/great-terms-flash.md b/.changeset/great-terms-flash.md new file mode 100644 index 000000000..cea28dfe0 --- /dev/null +++ b/.changeset/great-terms-flash.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Improve display of unknown events diff --git a/.changeset/thick-bobcats-sing.md b/.changeset/thick-bobcats-sing.md new file mode 100644 index 000000000..46d6cd20e --- /dev/null +++ b/.changeset/thick-bobcats-sing.md @@ -0,0 +1,5 @@ +--- +"nostrudel": patch +--- + +Upgrade nostr-tools to v2 diff --git a/docker-compose.yaml b/docker-compose.yaml index 2ff47a0d9..606c4fcb7 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -6,6 +6,8 @@ volumes: services: relay: image: scsibug/nostr-rs-relay:0.8.13 + ports: + - 5000:8080 volumes: - data:/usr/src/app/db app: diff --git a/package.json b/package.json index 0db2c7e16..288887b55 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,8 @@ "match-sorter": "^6.3.1", "nanoid": "^5.0.4", "ngeohash": "^0.6.3", - "nostr-tools": "^1.17.0", + "nostr-idb": "^0.2.0", + "nostr-tools": "^2.1.3", "react": "^18.2.0", "react-chartjs-2": "^5.2.0", "react-dom": "^18.2.0", diff --git a/src/classes/nostr-multi-subscription.ts b/src/classes/nostr-multi-subscription.ts index a0c254742..ff6c66e85 100644 --- a/src/classes/nostr-multi-subscription.ts +++ b/src/classes/nostr-multi-subscription.ts @@ -1,15 +1,11 @@ import { nanoid } from "nanoid"; -import stringify from "json-stringify-deterministic"; import { Subject } from "./subject"; import { NostrEvent } from "../types/nostr-event"; import { NostrOutgoingRequest, NostrRequestFilter, RelayQueryMap } from "../types/nostr-query"; import Relay, { IncomingEvent } from "./relay"; import relayPoolService from "../services/relay-pool"; - -function isFilterEqual(a: NostrRequestFilter, b: NostrRequestFilter) { - return stringify(a) === stringify(b); -} +import { isFilterEqual } from "../helpers/nostr/filter"; export default class NostrMultiSubscription { static INIT = "initial"; diff --git a/src/classes/timeline-loader.ts b/src/classes/timeline-loader.ts index 6bf89af96..7d123c32c 100644 --- a/src/classes/timeline-loader.ts +++ b/src/classes/timeline-loader.ts @@ -11,7 +11,10 @@ import EventStore from "./event-store"; import { isReplaceable } from "../helpers/nostr/events"; import replaceableEventLoaderService from "../services/replaceable-event-requester"; import deleteEventService from "../services/delete-events"; -import { addQueryToFilter, isFilterEqual, mapQueryMap } from "../helpers/nostr/filter"; +import { addQueryToFilter, isFilterEqual, mapQueryMap, stringifyFilter } from "../helpers/nostr/filter"; +import { localCacheRelay } from "../services/local-cache-relay"; +import { SimpleSubscription } from "nostr-idb"; +import { Filter } from "nostr-tools"; const BLOCK_SIZE = 100; @@ -106,6 +109,7 @@ export default class TimelineLoader { name: string; private log: Debugger; private subscription: NostrMultiSubscription; + private cacheSubscription?: SimpleSubscription; private blockLoaders = new Map(); @@ -131,12 +135,12 @@ export default class TimelineLoader { this.timeline.next(this.events.getSortedEvents().filter((e) => filter(e, this.events))); } else this.timeline.next(this.events.getSortedEvents()); } - private handleEvent(event: NostrEvent) { + private handleEvent(event: NostrEvent, cache = true) { // if this is a replaceable event, mirror it over to the replaceable event service - if (isReplaceable(event.kind)) { - replaceableEventLoaderService.handleEvent(event); - } + if (isReplaceable(event.kind)) replaceableEventLoaderService.handleEvent(event); + this.events.addEvent(event); + if (cache) localCacheRelay.publish(event); } private handleDeleteEvent(deleteEvent: NostrEvent) { const cord = deleteEvent.tags.find(isATag)?.[1]; @@ -158,6 +162,21 @@ export default class TimelineLoader { loader.onBlockFinish.unsubscribe(this.updateComplete, this); } + private loadQueriesFromCache(queryMap: RelayQueryMap) { + const queries: Record = {}; + for (const [url, filters] of Object.entries(queryMap)) { + const key = stringifyFilter(filters); + if (!queries[key]) queries[key] = Array.isArray(filters) ? filters : [filters]; + } + + for (const filters of Object.values(queries)) { + const sub: SimpleSubscription = localCacheRelay.subscribe(filters, { + onevent: (e) => this.handleEvent(e, false), + oneose: () => sub.close(), + }); + } + } + setQueryMap(queryMap: RelayQueryMap) { if (isFilterEqual(this.queryMap, queryMap)) return; @@ -190,6 +209,9 @@ export default class TimelineLoader { this.queryMap = queryMap; + // load all filters from cache relay + this.loadQueriesFromCache(queryMap); + // update the subscription query map and add limit this.subscription.setQueryMap( mapQueryMap(this.queryMap, (filter) => addQueryToFilter(filter, { limit: BLOCK_SIZE / 2 })), diff --git a/src/components/debug-modals/user-debug-modal.tsx b/src/components/debug-modals/user-debug-modal.tsx index b3b43a0fa..e6ec9dbc2 100644 --- a/src/components/debug-modals/user-debug-modal.tsx +++ b/src/components/debug-modals/user-debug-modal.tsx @@ -1,6 +1,6 @@ import { Flex, Modal, ModalBody, ModalCloseButton, ModalContent, ModalOverlay } from "@chakra-ui/react"; import { ModalProps } from "@chakra-ui/react"; -import { Kind, nip19 } from "nostr-tools"; +import { kinds, nip19 } from "nostr-tools"; import { useUserMetadata } from "../../hooks/use-user-metadata"; import RawValue from "./raw-value"; @@ -13,7 +13,7 @@ export default function UserDebugModal({ pubkey, ...props }: { pubkey: string } const npub = nip19.npubEncode(pubkey); const metadata = useUserMetadata(pubkey); const nprofile = useSharableProfileId(pubkey); - const relays = replaceableEventLoaderService.getEvent(Kind.RelayList, pubkey).value; + const relays = replaceableEventLoaderService.getEvent(kinds.RelayList, pubkey).value; const tipMetadata = useUserLNURLMetadata(pubkey); return ( diff --git a/src/components/embed-event/event-types/embedded-unknown.tsx b/src/components/embed-event/event-types/embedded-unknown.tsx index 8a0b734c1..6c9363921 100644 --- a/src/components/embed-event/event-types/embedded-unknown.tsx +++ b/src/components/embed-event/event-types/embedded-unknown.tsx @@ -1,27 +1,109 @@ -import { useMemo } from "react"; -import { Box, Button, Card, CardBody, CardHeader, CardProps, Flex, Link, Text, useDisclosure } from "@chakra-ui/react"; +import { MouseEventHandler, useCallback, useMemo } from "react"; +import { + Box, + BoxProps, + Button, + ButtonGroup, + Card, + CardBody, + CardHeader, + CardProps, + Flex, + IconButton, + Link, + Text, + useDisclosure, +} from "@chakra-ui/react"; +import { Link as RouterLink } from "react-router-dom"; +import { nip19 } from "nostr-tools"; import { getSharableEventAddress } from "../../../helpers/nip19"; -import { NostrEvent } from "../../../types/nostr-event"; +import { NostrEvent, Tag, isATag, isETag, isPTag } from "../../../types/nostr-event"; import UserAvatarLink from "../../user-avatar-link"; import UserLink from "../../user-link"; -import { truncatedId } from "../../../helpers/nostr/events"; +import { aTagToAddressPointer, eTagToEventPointer } from "../../../helpers/nostr/events"; import { buildAppSelectUrl } from "../../../helpers/nostr/apps"; import { UserDnsIdentityIcon } from "../../user-dns-identity-icon"; import { embedEmoji, embedNostrHashtags, embedNostrLinks, - embedNostrMentions, renderGenericUrl, renderImageUrl, renderVideoUrl, } from "../../embed-types"; import { EmbedableContent, embedUrls } from "../../../helpers/embeds"; import Timestamp from "../../timestamp"; -import { CodeIcon } from "../../icons"; +import { CodeIcon, ExternalLinkIcon } from "../../icons"; import NoteDebugModal from "../../debug-modals/note-debug-modal"; import { renderAudioUrl } from "../../embed-types/audio"; +import { EmbedEventPointer } from ".."; + +function EventTag({ tag }: { tag: Tag }) { + const expand = useDisclosure(); + const content = `[${tag[0]}] ${tag.slice(1).join(", ")}`; + const props = { + fontWeight: "bold", + fontFamily: "monospace", + fontSize: "1.2em", + isTruncated: true, + color: "GrayText", + }; + + const toggle = useCallback( + (e) => { + e.preventDefault(); + expand.onToggle(); + }, + [expand.onToggle], + ); + + if (isETag(tag) && tag[1]) { + const pointer = eTagToEventPointer(tag); + return ( + <> + + {content} + + {expand.isOpen && } + + ); + } else if (isATag(tag) && tag[1]) { + const pointer = aTagToAddressPointer(tag); + return ( + <> + + {content} + + {expand.isOpen && } + + ); + } else if (isPTag(tag) && tag[1]) { + const pubkey = tag[1]; + return ( + <> + + {content} + + {expand.isOpen && ( + + + + +
+ +
+
+ )} + + ); + } else + return ( + + {content} + + ); +} export default function EmbeddedUnknown({ event, ...props }: Omit & { event: NostrEvent }) { const debugModal = useDisclosure(); @@ -29,16 +111,15 @@ export default function EmbeddedUnknown({ event, ...props }: Omit t[0] === "alt")?.[1]; const content = useMemo(() => { - let jsx: EmbedableContent = [alt || event.content]; + let jsx: EmbedableContent = [event.content]; jsx = embedNostrLinks(jsx); - jsx = embedNostrMentions(jsx, event); jsx = embedNostrHashtags(jsx, event); jsx = embedEmoji(jsx, event); jsx = embedUrls(jsx, [renderImageUrl, renderVideoUrl, renderAudioUrl, renderGenericUrl]); return jsx; - }, [event.content, alt]); + }, [event.content]); return ( <> @@ -47,23 +128,41 @@ export default function EmbeddedUnknown({ event, ...props }: Omit - - - + kind: {event.kind} + + + + } + aria-label="Raw Event" + size="sm" + variant="outline" + onClick={debugModal.onOpen} + /> + - - Kind: {event.kind} - - {address && truncatedId(address)} - - - - + {alt && ( + + {alt} + + )} + {content} + + {event.tags.map((tag, i) => ( + + ))} + {debugModal.isOpen && } diff --git a/src/components/embed-event/index.tsx b/src/components/embed-event/index.tsx index 245b959cb..6d1dbcd9f 100644 --- a/src/components/embed-event/index.tsx +++ b/src/components/embed-event/index.tsx @@ -1,7 +1,7 @@ -import { lazy } from "react"; +import { Suspense, lazy } from "react"; import type { DecodeResult } from "nostr-tools/lib/types/nip19"; -import { CardProps } from "@chakra-ui/react"; -import { Kind, nip19 } from "nostr-tools"; +import { CardProps, Spinner } from "@chakra-ui/react"; +import { kinds, nip19 } from "nostr-tools"; import EmbeddedNote from "./event-types/embedded-note"; import useSingleEvent from "../../hooks/use-single-event"; @@ -51,46 +51,50 @@ export function EmbedEvent({ goalProps, ...cardProps }: Omit & { event: NostrEvent } & EmbedProps) { - switch (event.kind) { - case Kind.Text: - return ; - case Kind.Reaction: - return ; - case Kind.EncryptedDirectMessage: - return ; - case STREAM_KIND: - return ; - case GOAL_KIND: - return ; - case EMOJI_PACK_KIND: - return ; - case PEOPLE_LIST_KIND: - case NOTE_LIST_KIND: - case BOOKMARK_LIST_KIND: - case COMMUNITIES_LIST_KIND: - case CHANNELS_LIST_KIND: - return ; - case Kind.Article: - return ; - case Kind.BadgeDefinition: - return ; - case STREAM_CHAT_MESSAGE_KIND: - return ; - case COMMUNITY_DEFINITION_KIND: - return ; - case STEMSTR_TRACK_KIND: - return ; - case TORRENT_KIND: - return ; - case TORRENT_COMMENT_KIND: - return ; - case FLARE_VIDEO_KIND: - return ; - case Kind.ChannelCreation: - return ; - } + const renderContent = () => { + switch (event.kind) { + case kinds.ShortTextNote: + return ; + case kinds.Reaction: + return ; + case kinds.EncryptedDirectMessage: + return ; + case STREAM_KIND: + return ; + case GOAL_KIND: + return ; + case EMOJI_PACK_KIND: + return ; + case PEOPLE_LIST_KIND: + case NOTE_LIST_KIND: + case BOOKMARK_LIST_KIND: + case COMMUNITIES_LIST_KIND: + case CHANNELS_LIST_KIND: + return ; + case kinds.LongFormArticle: + return ; + case kinds.BadgeDefinition: + return ; + case STREAM_CHAT_MESSAGE_KIND: + return ; + case COMMUNITY_DEFINITION_KIND: + return ; + case STEMSTR_TRACK_KIND: + return ; + case TORRENT_KIND: + return ; + case TORRENT_COMMENT_KIND: + return ; + case FLARE_VIDEO_KIND: + return ; + case kinds.ChannelCreation: + return ; + } - return ; + return ; + }; + + return }>{renderContent()}; } export function EmbedEventPointer({ pointer, ...props }: { pointer: DecodeResult } & EmbedProps) { diff --git a/src/components/event-interactions-modal/repost-details.tsx b/src/components/event-interactions-modal/repost-details.tsx index bcbed8261..c313c0a39 100644 --- a/src/components/event-interactions-modal/repost-details.tsx +++ b/src/components/event-interactions-modal/repost-details.tsx @@ -1,5 +1,5 @@ -import { Button, Flex, SimpleGrid, SimpleGridProps, Text, useDisclosure } from "@chakra-ui/react"; -import { Kind } from "nostr-tools"; +import { Flex, Text } from "@chakra-ui/react"; +import { kinds } from "nostr-tools"; import { NostrEvent } from "../../types/nostr-event"; import UserAvatarLink from "../user-avatar-link"; @@ -11,7 +11,7 @@ import Timestamp from "../timestamp"; export default function RepostDetails({ event }: { event: NostrEvent }) { const readRelays = useReadRelayUrls(); - const timeline = useTimelineLoader(`${event.id}-reposts`, readRelays, { kinds: [Kind.Repost], "#e": [event.id] }); + const timeline = useTimelineLoader(`${event.id}-reposts`, readRelays, { kinds: [kinds.Repost], "#e": [event.id] }); const reposts = useSubject(timeline.timeline); diff --git a/src/components/event-verification-icon.tsx b/src/components/event-verification-icon.tsx index c870a99be..9cb14c387 100644 --- a/src/components/event-verification-icon.tsx +++ b/src/components/event-verification-icon.tsx @@ -1,5 +1,5 @@ import { memo } from "react"; -import { verifySignature } from "nostr-tools"; +import { verifyEvent } from "nostr-tools"; import { NostrEvent } from "../types/nostr-event"; import { CheckIcon, VerificationFailed } from "./icons"; @@ -9,7 +9,7 @@ function EventVerificationIcon({ event }: { event: NostrEvent }) { const { showSignatureVerification } = useAppSettings(); if (!showSignatureVerification) return null; - if (!verifySignature(event)) { + if (!verifyEvent(event)) { return ; } return ; diff --git a/src/components/event-zap-modal/index.tsx b/src/components/event-zap-modal/index.tsx index 78a2f05d4..a26bdf848 100644 --- a/src/components/event-zap-modal/index.tsx +++ b/src/components/event-zap-modal/index.tsx @@ -9,11 +9,10 @@ import { ModalProps, } from "@chakra-ui/react"; import dayjs from "dayjs"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { DraftNostrEvent, NostrEvent, isDTag } from "../../types/nostr-event"; import clientRelaysService from "../../services/client-relays"; -import { getEventRelays } from "../../services/event-relays"; import { getZapSplits } from "../../helpers/nostr/zaps"; import { unique } from "../../helpers/array"; import { RelayMode } from "../../classes/relay"; @@ -76,7 +75,7 @@ async function getPayRequestForPubkey( // create zap request const zapRequest: DraftNostrEvent = { - kind: Kind.ZapRequest, + kind: kinds.ZapRequest, created_at: dayjs().unix(), content: comment ?? "", tags: [ diff --git a/src/components/layout/ghost-toolbar.tsx b/src/components/layout/ghost-toolbar.tsx index 875335830..5a33b7614 100644 --- a/src/components/layout/ghost-toolbar.tsx +++ b/src/components/layout/ghost-toolbar.tsx @@ -1,6 +1,6 @@ import { useCallback, useState } from "react"; import { Box, Card, CloseButton, Divider, Flex, FlexProps, Spacer, Text } from "@chakra-ui/react"; -import { Kind, nip18, nip19, nip25 } from "nostr-tools"; +import { kinds, nip18, nip19, nip25 } from "nostr-tools"; import { useNavigate } from "react-router-dom"; import { useInterval } from "react-use"; import dayjs from "dayjs"; @@ -19,24 +19,24 @@ import { getSharableEventAddress } from "../../helpers/nip19"; import { safeRelayUrls } from "../../helpers/url"; const kindColors: Record = { - [Kind.Text]: "blue.500", - [Kind.RecommendRelay]: "pink", - [Kind.EncryptedDirectMessage]: "orange.500", - [Kind.Repost]: "yellow", - [Kind.Reaction]: "green.500", - [Kind.Article]: "purple.500", + [kinds.ShortTextNote]: "blue.500", + [kinds.RecommendRelay]: "pink", + [kinds.EncryptedDirectMessage]: "orange.500", + [kinds.Repost]: "yellow", + [kinds.Reaction]: "green.500", + [kinds.LongFormArticle]: "purple.500", }; function EventChunk({ event, ...props }: { event: NostrEvent } & Omit) { const navigate = useNavigate(); const handleClick = useCallback(() => { switch (event.kind) { - case Kind.Reaction: { + case kinds.Reaction: { const pointer = nip25.getReactedEventPointer(event); if (pointer) navigate(`/l/${nip19.neventEncode(pointer)}`); return; } - case Kind.Repost: { + case kinds.Repost: { const pointer = nip18.getRepostedEventPointer(event); if (pointer?.relays) pointer.relays = safeRelayUrls(pointer.relays); if (pointer) navigate(`/l/${nip19.neventEncode(pointer)}`); @@ -48,11 +48,11 @@ function EventChunk({ event, ...props }: { event: NostrEvent } & Omit { switch (event.kind) { - case Kind.Text: + case kinds.ShortTextNote: return "Note"; - case Kind.Reaction: + case kinds.Reaction: return "Reaction"; - case Kind.EncryptedDirectMessage: + case kinds.EncryptedDirectMessage: return "Direct Message"; } }; diff --git a/src/components/layout/nav-items.tsx b/src/components/layout/nav-items.tsx index 546062131..4a300a9de 100644 --- a/src/components/layout/nav-items.tsx +++ b/src/components/layout/nav-items.tsx @@ -49,6 +49,7 @@ export default function NavItems() { else if (location.pathname.startsWith("/dm")) active = "dm"; else if (location.pathname.startsWith("/streams")) active = "streams"; else if (location.pathname.startsWith("/relays")) active = "relays"; + else if (location.pathname.startsWith("/r/")) active = "relays"; else if (location.pathname.startsWith("/lists")) active = "lists"; else if (location.pathname.startsWith("/communities")) active = "communities"; else if (location.pathname.startsWith("/channels")) active = "channels"; diff --git a/src/components/note/components/repost-modal.tsx b/src/components/note/components/repost-modal.tsx index 9a3f582ed..1d7e38a48 100644 --- a/src/components/note/components/repost-modal.tsx +++ b/src/components/note/components/repost-modal.tsx @@ -13,7 +13,7 @@ import { useDisclosure, useToast, } from "@chakra-ui/react"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import dayjs from "dayjs"; import type { AddressPointer } from "nostr-tools/lib/types/nip19"; @@ -34,7 +34,7 @@ function buildRepost(event: NostrEvent): DraftNostrEvent { tags.push(["e", event.id, hint ?? ""]); return { - kind: Kind.Repost, + kind: kinds.Repost, tags, content: JSON.stringify(event), created_at: dayjs().unix(), diff --git a/src/components/post-modal/index.tsx b/src/components/post-modal/index.tsx index f53b4c011..eadd1f0a8 100644 --- a/src/components/post-modal/index.tsx +++ b/src/components/post-modal/index.tsx @@ -26,7 +26,7 @@ import { } from "@chakra-ui/react"; import dayjs from "dayjs"; import { useForm } from "react-hook-form"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { ChevronDownIcon, ChevronUpIcon, UploadImageIcon } from "../icons"; import NostrPublishAction from "../../classes/nostr-publish-action"; @@ -124,7 +124,7 @@ export default function PostModal({ let updatedDraft = finalizeNote({ content: content, - kind: Kind.Text, + kind: kinds.ShortTextNote, tags: [], created_at: dayjs().unix(), }); diff --git a/src/components/timeline-page/generic-note-timeline/repost-note.tsx b/src/components/timeline-page/generic-note-timeline/repost-note.tsx index baa4372df..ae83a007e 100644 --- a/src/components/timeline-page/generic-note-timeline/repost-note.tsx +++ b/src/components/timeline-page/generic-note-timeline/repost-note.tsx @@ -1,6 +1,6 @@ import { useRef } from "react"; import { Flex, Heading, Link, SkeletonText, Text } from "@chakra-ui/react"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { Link as RouterLink } from "react-router-dom"; import { isETag, NostrEvent } from "../../../types/nostr-event"; @@ -60,7 +60,7 @@ export default function RepostNote({ event }: { event: NostrEvent }) { {!note ? ( - ) : note.kind === Kind.Text ? ( + ) : note.kind === kinds.ShortTextNote ? ( // NOTE: tell the note not to register itself with the intersection observer. since this is an older note it will break the order of the timeline ) : ( diff --git a/src/components/timeline-page/generic-note-timeline/timeline-item.tsx b/src/components/timeline-page/generic-note-timeline/timeline-item.tsx index a2aa22957..805f321f3 100644 --- a/src/components/timeline-page/generic-note-timeline/timeline-item.tsx +++ b/src/components/timeline-page/generic-note-timeline/timeline-item.tsx @@ -1,6 +1,6 @@ import { ReactNode, memo, useRef } from "react"; -import { Kind } from "nostr-tools"; -import { Box, BreadcrumbLink, Text } from "@chakra-ui/react"; +import { kinds } from "nostr-tools"; +import { Box, Text } from "@chakra-ui/react"; import { ErrorBoundary } from "../../error-boundary"; import ReplyNote from "./reply-note"; @@ -23,22 +23,22 @@ function TimelineItem({ event, visible, minHeight }: { event: NostrEvent; visibl let content: ReactNode | null = null; switch (event.kind) { - case Kind.Text: + case kinds.ShortTextNote: content = isReply(event) ? : ; break; - case Kind.Repost: + case kinds.Repost: content = ; break; - case Kind.Article: + case kinds.LongFormArticle: content = ; break; case STREAM_KIND: content = ; break; - case Kind.RecommendRelay: + case kinds.RecommendRelay: content = ; break; - case Kind.BadgeAward: + case kinds.BadgeAward: content = ; break; case FLARE_VIDEO_KIND: diff --git a/src/components/timeline-page/media-timeline/index.tsx b/src/components/timeline-page/media-timeline/index.tsx index d4670e4e0..e912f671d 100644 --- a/src/components/timeline-page/media-timeline/index.tsx +++ b/src/components/timeline-page/media-timeline/index.tsx @@ -1,5 +1,5 @@ import { useMemo, useRef } from "react"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { Photo } from "react-photo-album"; import TimelineLoader from "../../../classes/timeline-loader"; @@ -45,7 +45,7 @@ export default function MediaTimeline({ timeline }: { timeline: TimelineLoader } var images: PhotoWithEvent[] = []; for (const event of events) { - if (event.kind === Kind.Repost) continue; + if (event.kind === kinds.Repost) continue; const urls = event.content.matchAll(getMatchLink()); let i = 0; diff --git a/src/helpers/nip19.ts b/src/helpers/nip19.ts index 4a5ca44b7..26b794490 100644 --- a/src/helpers/nip19.ts +++ b/src/helpers/nip19.ts @@ -2,7 +2,6 @@ import { getPublicKey, nip19 } from "nostr-tools"; import { NostrEvent, Tag, isATag, isDTag, isETag, isPTag } from "../types/nostr-event"; import { isReplaceable } from "./nostr/events"; -import { DecodeResult } from "nostr-tools/lib/types/nip19"; import relayHintService from "../services/event-relay-hint"; export function isHex(str?: string) { @@ -52,7 +51,7 @@ export function getSharableEventAddress(event: NostrEvent) { } } -export function encodePointer(pointer: DecodeResult) { +export function encodePointer(pointer: nip19.DecodeResult) { switch (pointer.type) { case "naddr": return nip19.naddrEncode(pointer.data); @@ -71,7 +70,7 @@ export function encodePointer(pointer: DecodeResult) { } } -export function getPointerFromTag(tag: Tag): DecodeResult | null { +export function getPointerFromTag(tag: Tag): nip19.DecodeResult | null { if (isETag(tag)) { if (!tag[1]) return null; return { diff --git a/src/helpers/nostr/communities.ts b/src/helpers/nostr/communities.ts index a00a9251f..c581099cc 100644 --- a/src/helpers/nostr/communities.ts +++ b/src/helpers/nostr/communities.ts @@ -1,4 +1,4 @@ -import { validateEvent } from "nostr-tools"; +import { kinds, validateEvent } from "nostr-tools"; import { NostrEvent, isATag, isDTag, isETag, isPTag } from "../../types/nostr-event"; import { getMatchLink, getMatchNostrLink } from "../regexp"; import { ReactionGroup } from "./reactions"; @@ -6,8 +6,8 @@ import { parseCoordinate } from "./events"; /** @deprecated */ export const SUBSCRIBED_COMMUNITIES_LIST_IDENTIFIER = "communities"; -export const COMMUNITY_DEFINITION_KIND = 34550; -export const COMMUNITY_APPROVAL_KIND = 4550; +export const COMMUNITY_DEFINITION_KIND = kinds.CommunityDefinition; +export const COMMUNITY_APPROVAL_KIND = kinds.CommunityPostApproval; export function getCommunityName(community: NostrEvent) { const name = community.tags.find(isDTag)?.[1]; diff --git a/src/helpers/nostr/events.ts b/src/helpers/nostr/events.ts index bb178ccee..728091a60 100644 --- a/src/helpers/nostr/events.ts +++ b/src/helpers/nostr/events.ts @@ -1,4 +1,4 @@ -import { Kind, nip19, validateEvent } from "nostr-tools"; +import { kinds, validateEvent } from "nostr-tools"; import { ATag, DraftNostrEvent, ETag, isATag, isDTag, isETag, NostrEvent, RTag, Tag } from "../../types/nostr-event"; import { RelayConfig, RelayMode } from "../../classes/relay"; @@ -12,9 +12,8 @@ export function truncatedId(str: string, keep = 6) { return str.substring(0, keep) + "..." + str.substring(str.length - keep); } -// based on replaceable kinds from https://github.com/nostr-protocol/nips/blob/master/01.md#kinds export function isReplaceable(kind: number) { - return (kind >= 30000 && kind < 40000) || kind === 0 || kind === 3 || kind === 41 || (kind >= 10000 && kind < 20000); + return kinds.isReplaceableKind(kind) || kinds.isParameterizedReplaceableKind(kind); } export function pointerMatchEvent(event: NostrEvent, pointer: AddressPointer | EventPointer) { @@ -42,7 +41,7 @@ export function getEventUID(event: NostrEvent) { } export function isReply(event: NostrEvent | DraftNostrEvent) { - if (event.kind === Kind.Repost) return false; + if (event.kind === kinds.Repost) return false; // TODO: update this to only look for a "root" or "reply" tag return !!getReferences(event).reply; } @@ -51,7 +50,7 @@ export function isMentionedInContent(event: NostrEvent | DraftNostrEvent, pubkey } export function isRepost(event: NostrEvent | DraftNostrEvent) { - if (event.kind === Kind.Repost) return true; + if (event.kind === kinds.Repost) return true; const match = event.content.match(getMatchNostrLink()); return match && match[0].length === event.content.length; diff --git a/src/helpers/nostr/filter.ts b/src/helpers/nostr/filter.ts index 11492b3e6..08495e125 100644 --- a/src/helpers/nostr/filter.ts +++ b/src/helpers/nostr/filter.ts @@ -1,6 +1,6 @@ import stringify from "json-stringify-deterministic"; import { NostrQuery, NostrRequestFilter, RelayQueryMap } from "../../types/nostr-query"; -import localCacheRelayService, { LOCAL_CACHE_RELAY } from "../../services/local-cache-relay"; +import { LOCAL_CACHE_RELAY, LOCAL_CACHE_RELAY_ENABLED } from "../../services/local-cache-relay"; export function addQueryToFilter(filter: NostrRequestFilter, query: NostrQuery) { if (Array.isArray(filter)) { @@ -9,8 +9,11 @@ export function addQueryToFilter(filter: NostrRequestFilter, query: NostrQuery) return { ...filter, ...query }; } +export function stringifyFilter(filter: NostrRequestFilter) { + return stringify(filter); +} export function isFilterEqual(a: NostrRequestFilter, b: NostrRequestFilter) { - return stringify(a) === stringify(b); + return stringifyFilter(a) === stringifyFilter(b); } export function mapQueryMap(queryMap: RelayQueryMap, fn: (filter: NostrRequestFilter) => NostrRequestFilter) { @@ -23,7 +26,7 @@ export function createSimpleQueryMap(relays: string[], filter: NostrRequestFilte const map: RelayQueryMap = {}; // if the local cache relay is enabled, also ask it - if (localCacheRelayService.enabled) { + if (LOCAL_CACHE_RELAY_ENABLED) { map[LOCAL_CACHE_RELAY] = filter; } diff --git a/src/helpers/nostr/lists.ts b/src/helpers/nostr/lists.ts index f753730ed..9570fb134 100644 --- a/src/helpers/nostr/lists.ts +++ b/src/helpers/nostr/lists.ts @@ -1,5 +1,5 @@ import dayjs from "dayjs"; -import { Kind, nip19 } from "nostr-tools"; +import { kinds, nip19 } from "nostr-tools"; import { DraftNostrEvent, NostrEvent, PTag, isATag, isDTag, isETag, isPTag, isRTag } from "../../types/nostr-event"; import { parseCoordinate } from "./events"; @@ -15,7 +15,7 @@ export const NOTE_LIST_KIND = 30001; export const BOOKMARK_LIST_SET_KIND = 30003; export function getListName(event: NostrEvent) { - if (event.kind === Kind.Contacts) return "Following"; + if (event.kind === kinds.Contacts) return "Following"; if (event.kind === MUTE_LIST_KIND) return "Mute"; if (event.kind === PIN_LIST_KIND) return "Pins"; if (event.kind === BOOKMARK_LIST_KIND) return "Bookmarks"; @@ -38,7 +38,7 @@ export function isJunkList(event: NostrEvent) { } export function isSpecialListKind(kind: number) { return ( - kind === Kind.Contacts || + kind === kinds.Contacts || kind === MUTE_LIST_KIND || kind === PIN_LIST_KIND || kind === BOOKMARK_LIST_KIND || @@ -93,7 +93,7 @@ export function createEmptyContactList(): DraftNostrEvent { created_at: dayjs().unix(), content: "", tags: [], - kind: Kind.Contacts, + kind: kinds.Contacts, }; } diff --git a/src/helpers/nostr/reactions.ts b/src/helpers/nostr/reactions.ts index 5fea08c01..9807c6df9 100644 --- a/src/helpers/nostr/reactions.ts +++ b/src/helpers/nostr/reactions.ts @@ -1,4 +1,4 @@ -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { DraftNostrEvent, NostrEvent, Tag } from "../../types/nostr-event"; import dayjs from "dayjs"; import { getEventCoordinate, isReplaceable } from "./events"; @@ -27,7 +27,7 @@ export function draftEventReaction(event: NostrEvent, emoji = "+", url?: string) ["p", event.pubkey], ]; const draft: DraftNostrEvent = { - kind: Kind.Reaction, + kind: kinds.Reaction, content: url ? ":" + emoji + ":" : emoji, tags: isReplaceable(event.kind) ? [...tags, ["a", getEventCoordinate(event)]] : tags, created_at: dayjs().unix(), diff --git a/src/helpers/relay.ts b/src/helpers/relay.ts index b198695fa..62b0b1fcc 100644 --- a/src/helpers/relay.ts +++ b/src/helpers/relay.ts @@ -1,6 +1,9 @@ +import { SimpleRelay, SimpleSubscription, SimpleSubscriptionOptions } from "nostr-idb"; import { RelayConfig } from "../classes/relay"; import { NostrQuery, NostrRequestFilter } from "../types/nostr-query"; import { safeRelayUrl } from "./url"; +import { Filter } from "nostr-tools"; +import { NostrEvent } from "../types/nostr-event"; export function normalizeRelayConfigs(relays: RelayConfig[]) { const seen: string[] = []; @@ -52,3 +55,18 @@ export function splitQueryByPubkeys(query: NostrQuery, relayPubkeyMap: Record((res) => { + const events: NostrEvent[] = []; + const sub: SimpleSubscription = relay.subscribe(filters, { + ...opts, + onevent: (e) => events.push(e), + oneose: () => { + sub.close(); + res(events); + }, + onclose: () => res(events), + }); + }); +} diff --git a/src/hooks/use-thread-timeline-loader.ts b/src/hooks/use-thread-timeline-loader.ts index 10c8b445d..faec24066 100644 --- a/src/hooks/use-thread-timeline-loader.ts +++ b/src/hooks/use-thread-timeline-loader.ts @@ -1,5 +1,5 @@ import { useEffect, useMemo } from "react"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import useSubject from "./use-subject"; import useSingleEvent from "./use-single-event"; @@ -12,7 +12,7 @@ import { unique } from "../helpers/array"; export default function useThreadTimelineLoader( focusedEvent: NostrEvent | undefined, relays: string[], - kind: number = Kind.Text, + kind: number = kinds.ShortTextNote, ) { const refs = focusedEvent && getReferences(focusedEvent); const rootId = refs?.root?.e?.id || focusedEvent?.id; diff --git a/src/hooks/use-user-contact-list.ts b/src/hooks/use-user-contact-list.ts index cb7070281..e4d658c36 100644 --- a/src/hooks/use-user-contact-list.ts +++ b/src/hooks/use-user-contact-list.ts @@ -1,4 +1,4 @@ -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import useReplaceableEvent from "./use-replaceable-event"; import { RequestOptions } from "../services/replaceable-event-requester"; @@ -7,5 +7,5 @@ export default function useUserContactList( additionalRelays: string[] = [], opts: RequestOptions = {}, ) { - return useReplaceableEvent(pubkey && { kind: Kind.Contacts, pubkey }, additionalRelays, opts); + return useReplaceableEvent(pubkey && { kind: kinds.Contacts, pubkey }, additionalRelays, opts); } diff --git a/src/hooks/use-user-network.ts b/src/hooks/use-user-network.ts index ba9d97c49..3f6c5affd 100644 --- a/src/hooks/use-user-network.ts +++ b/src/hooks/use-user-network.ts @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { getPubkeysFromList } from "../helpers/nostr/lists"; import useUserContactList from "./use-user-contact-list"; @@ -34,7 +34,7 @@ export default function useUserNetwork(pubkey: string, additionalRelays: string[ const subjects = useMemo(() => { return contactsPubkeys.map((person) => - replaceableEventLoaderService.requestEvent(readRelays, Kind.Contacts, person.pubkey), + replaceableEventLoaderService.requestEvent(readRelays, kinds.Contacts, person.pubkey), ); }, [contactsPubkeys, readRelays.join("|")]); diff --git a/src/hooks/use-user-profile-badges.ts b/src/hooks/use-user-profile-badges.ts index 69a56aee5..669ac2cc1 100644 --- a/src/hooks/use-user-profile-badges.ts +++ b/src/hooks/use-user-profile-badges.ts @@ -1,4 +1,4 @@ -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import useReplaceableEvent from "./use-replaceable-event"; import { PROFILE_BADGES_IDENTIFIER, parseProfileBadges } from "../helpers/nostr/badges"; @@ -8,14 +8,20 @@ import { getEventCoordinate } from "../helpers/nostr/events"; import { NostrEvent } from "../types/nostr-event"; export default function useUserProfileBadges(pubkey: string, additionalRelays: string[] = []) { - const profileBadgesEvent = useReplaceableEvent({ - pubkey, - kind: Kind.ProfileBadge, - identifier: PROFILE_BADGES_IDENTIFIER, - }); + const profileBadgesEvent = useReplaceableEvent( + { + pubkey, + kind: kinds.ProfileBadges, + identifier: PROFILE_BADGES_IDENTIFIER, + }, + additionalRelays, + ); const parsed = profileBadgesEvent ? parseProfileBadges(profileBadgesEvent) : []; - const badges = useReplaceableEvents(parsed.map((b) => b.badgeCord)); + const badges = useReplaceableEvents( + parsed.map((b) => b.badgeCord), + additionalRelays, + ); const awardEvents = useSingleEvents(parsed.map((b) => b.awardEventId)); const final: { badge: NostrEvent; award: NostrEvent }[] = []; diff --git a/src/providers/global/dm-timeline.tsx b/src/providers/global/dm-timeline.tsx index b9661b347..18cb19f87 100644 --- a/src/providers/global/dm-timeline.tsx +++ b/src/providers/global/dm-timeline.tsx @@ -1,5 +1,5 @@ import { PropsWithChildren, createContext, useCallback, useContext, useMemo } from "react"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { useReadRelayUrls } from "../../hooks/use-client-relays"; import useCurrentAccount from "../../hooks/use-current-account"; @@ -7,7 +7,6 @@ import TimelineLoader from "../../classes/timeline-loader"; import { NostrEvent } from "../../types/nostr-event"; import useClientSideMuteFilter from "../../hooks/use-client-side-mute-filter"; import useTimelineLoader from "../../hooks/use-timeline-loader"; -import { TORRENT_COMMENT_KIND } from "../../helpers/nostr/torrents"; type DMTimelineContextType = { timeline?: TimelineLoader; @@ -40,8 +39,8 @@ export default function DMTimelineProvider({ children }: PropsWithChildren) { inbox, account?.pubkey ? [ - { authors: [account.pubkey], kinds: [Kind.EncryptedDirectMessage] }, - { "#p": [account.pubkey], kinds: [Kind.EncryptedDirectMessage] }, + { authors: [account.pubkey], kinds: [kinds.EncryptedDirectMessage] }, + { "#p": [account.pubkey], kinds: [kinds.EncryptedDirectMessage] }, ] : undefined, { eventFilter }, diff --git a/src/providers/global/notification-timeline.tsx b/src/providers/global/notification-timeline.tsx index a9216f5d9..bcc340234 100644 --- a/src/providers/global/notification-timeline.tsx +++ b/src/providers/global/notification-timeline.tsx @@ -1,5 +1,5 @@ import { PropsWithChildren, createContext, useCallback, useContext, useMemo } from "react"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { useReadRelayUrls } from "../../hooks/use-client-relays"; import useCurrentAccount from "../../hooks/use-current-account"; @@ -41,7 +41,14 @@ export default function NotificationTimelineProvider({ children }: PropsWithChil account?.pubkey ? { "#p": [account.pubkey], - kinds: [Kind.Text, Kind.Repost, Kind.Reaction, Kind.Zap, TORRENT_COMMENT_KIND, Kind.Article], + kinds: [ + kinds.ShortTextNote, + kinds.Repost, + kinds.Reaction, + kinds.Zap, + TORRENT_COMMENT_KIND, + kinds.LongFormArticle, + ], } : undefined, { eventFilter }, diff --git a/src/providers/local/people-list-provider.tsx b/src/providers/local/people-list-provider.tsx index 38d6807ba..a5e4e3356 100644 --- a/src/providers/local/people-list-provider.tsx +++ b/src/providers/local/people-list-provider.tsx @@ -1,5 +1,5 @@ import { PropsWithChildren, createContext, useCallback, useContext, useMemo } from "react"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import useCurrentAccount from "../../hooks/use-current-account"; import { getPubkeysFromList } from "../../helpers/nostr/lists"; @@ -34,7 +34,7 @@ function useListCoordinate(listId: ListId) { const account = useCurrentAccount(); return useMemo(() => { - if (listId === "following") return account ? `${Kind.Contacts}:${account.pubkey}` : undefined; + if (listId === "following") return account ? `${kinds.Contacts}:${account.pubkey}` : undefined; if (listId === "global") return undefined; return listId; }, [listId, account]); diff --git a/src/providers/route/delete-event-provider.tsx b/src/providers/route/delete-event-provider.tsx index 08bc2acda..b14fbb8b9 100644 --- a/src/providers/route/delete-event-provider.tsx +++ b/src/providers/route/delete-event-provider.tsx @@ -20,7 +20,7 @@ import { Text, useToast, } from "@chakra-ui/react"; -import { Event, Kind } from "nostr-tools"; +import { Event, kinds } from "nostr-tools"; import dayjs from "dayjs"; import useCurrentAccount from "../../hooks/use-current-account"; @@ -80,7 +80,7 @@ export default function DeleteEventProvider({ children }: PropsWithChildren) { } const draft = { - kind: Kind.EventDeletion, + kind: kinds.EventDeletion, tags, content: reason, created_at: dayjs().unix(), diff --git a/src/services/amber-signer.ts b/src/services/amber-signer.ts index 8b4e85c4f..cdc7fc70c 100644 --- a/src/services/amber-signer.ts +++ b/src/services/amber-signer.ts @@ -1,4 +1,4 @@ -import { getEventHash, nip19, verifySignature } from "nostr-tools"; +import { getEventHash, nip19, verifyEvent } from "nostr-tools"; import createDefer, { Deferred } from "../classes/deferred"; import { getPubkeyFromDecodeResult, isHex, isHexKey } from "../helpers/nip19"; @@ -78,7 +78,7 @@ async function signEvent(draft: DraftNostrEvent & { pubkey: string }): Promise 0) { const query: NostrQuery = { - kinds: [Kind.ChannelMetadata], + kinds: [kinds.ChannelMetadata], "#e": Array.from(this.requested.keys()), }; @@ -231,7 +231,7 @@ class ChannelMetadataService { const sub = this.metadata.get(channelId); const relayUrls = Array.from(relays); - if (localCacheRelayService.enabled) { + if (LOCAL_CACHE_RELAY_ENABLED) { relayUrls.unshift(LOCAL_CACHE_RELAY); } for (const relay of relayUrls) { diff --git a/src/services/db/index.ts b/src/services/db/index.ts index 397a2bbca..b0ee5ad98 100644 --- a/src/services/db/index.ts +++ b/src/services/db/index.ts @@ -1,6 +1,9 @@ import { openDB, deleteDB, IDBPDatabase, IDBPTransaction } from "idb"; +import { clearDB } from "nostr-idb"; + import { SchemaV1, SchemaV2, SchemaV3, SchemaV4, SchemaV5, SchemaV6, SchemaV7 } from "./schema"; import { logger } from "../../helpers/debug"; +import { localCacheDatabase } from "../local-cache-relay"; const log = logger.extend("Database"); @@ -169,6 +172,9 @@ const db = await openDB(dbName, version, { log("Open"); export async function clearCacheData() { + log("Clearing nostr-idb"); + await clearDB(localCacheDatabase); + log("Clearing replaceableEvents"); await db.clear("replaceableEvents"); @@ -195,6 +201,7 @@ export async function deleteDatabase() { db.close(); log("Deleting"); await deleteDB(dbName); + await deleteDB("events"); window.location.reload(); } diff --git a/src/services/delete-events.ts b/src/services/delete-events.ts index a2873e952..c914a9119 100644 --- a/src/services/delete-events.ts +++ b/src/services/delete-events.ts @@ -1,4 +1,4 @@ -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import Subject from "../classes/subject"; import { getEventUID } from "../helpers/nostr/events"; @@ -7,7 +7,7 @@ import { NostrEvent } from "../types/nostr-event"; const deleteEventStream = new Subject(); function handleEvent(deleteEvent: NostrEvent) { - if (deleteEvent.kind !== Kind.EventDeletion) return; + if (deleteEvent.kind !== kinds.EventDeletion) return; deleteEventStream.next(deleteEvent); } diff --git a/src/services/event-exists.ts b/src/services/event-exists.ts index b855c9736..2c81e427d 100644 --- a/src/services/event-exists.ts +++ b/src/services/event-exists.ts @@ -8,7 +8,7 @@ import relayScoreboardService from "./relay-scoreboard"; import { logger } from "../helpers/debug"; import { matchFilter, matchFilters } from "nostr-tools"; import { NostrEvent } from "../types/nostr-event"; -import localCacheRelayService, { LOCAL_CACHE_RELAY } from "./local-cache-relay"; +import { LOCAL_CACHE_RELAY, LOCAL_CACHE_RELAY_ENABLED } from "./local-cache-relay"; function hashFilter(filter: NostrRequestFilter) { // const encoder = new TextEncoder(); @@ -43,7 +43,7 @@ class EventExistsService { if (sub.value !== true) { const relayUrls = Array.from(relays); - if (localCacheRelayService.enabled) relayUrls.unshift(LOCAL_CACHE_RELAY); + if (LOCAL_CACHE_RELAY_ENABLED) relayUrls.unshift(LOCAL_CACHE_RELAY); for (const url of relayUrls) { if (!asked.has(url) && !pending.has(url)) { diff --git a/src/services/event-reactions.ts b/src/services/event-reactions.ts index d63e0efe4..c080afa52 100644 --- a/src/services/event-reactions.ts +++ b/src/services/event-reactions.ts @@ -1,4 +1,4 @@ -import { Kind, nip25 } from "nostr-tools"; +import { kinds, nip25 } from "nostr-tools"; import NostrRequest from "../classes/nostr-request"; import Subject from "../classes/subject"; @@ -25,7 +25,7 @@ class EventReactionsService { } handleEvent(event: NostrEvent) { - if (event.kind !== Kind.Reaction) return; + if (event.kind !== kinds.Reaction) return; const pointer = nip25.getReactedEventPointer(event); if (!pointer?.id) return; @@ -51,7 +51,7 @@ class EventReactionsService { for (const [relay, ids] of Object.entries(idsFromRelays)) { const request = new NostrRequest([relay]); request.onEvent.subscribe(this.handleEvent, this); - request.start({ "#e": ids, kinds: [Kind.Reaction] }); + request.start({ "#e": ids, kinds: [kinds.Reaction] }); } this.pending.clear(); } diff --git a/src/services/event-zaps.ts b/src/services/event-zaps.ts index 4fe9ea639..9f0bd60ef 100644 --- a/src/services/event-zaps.ts +++ b/src/services/event-zaps.ts @@ -1,4 +1,4 @@ -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import NostrRequest from "../classes/nostr-request"; import Subject from "../classes/subject"; @@ -27,7 +27,7 @@ class EventZapsService { } handleEvent(event: NostrEvent) { - if (event.kind !== Kind.Zap) return; + if (event.kind !== kinds.Zap) return; const eventUID = event.tags.find(isETag)?.[1] ?? event.tags.find(isATag)?.[1]; if (!eventUID) return; @@ -58,10 +58,10 @@ class EventZapsService { const queries: NostrRequestFilter = []; if (eventIds.length > 0) { - queries.push({ "#e": eventIds, kinds: [Kind.Zap] }); + queries.push({ "#e": eventIds, kinds: [kinds.Zap] }); } if (coordinates.length > 0) { - queries.push({ "#a": coordinates, kinds: [Kind.Zap] }); + queries.push({ "#a": coordinates, kinds: [kinds.Zap] }); } request.start(queries); diff --git a/src/services/local-cache-relay.ts b/src/services/local-cache-relay.ts index 2cd328199..c58cba6a8 100644 --- a/src/services/local-cache-relay.ts +++ b/src/services/local-cache-relay.ts @@ -1,8 +1,9 @@ +import { CacheRelay, openDB } from "nostr-idb"; +import { Relay } from "nostr-tools"; import { logger } from "../helpers/debug"; -import { NostrEvent } from "../types/nostr-event"; -import relayPoolService from "./relay-pool"; import _throttle from "lodash.throttle"; +const log = logger.extend(`LocalCacheRelay`); const params = new URLSearchParams(location.search); const paramRelay = params.get("cacheRelay"); @@ -17,60 +18,34 @@ const storedCacheRelayURL = localStorage.getItem("cacheRelay"); const url = (storedCacheRelayURL && new URL(storedCacheRelayURL)) || new URL("/cache-relay", location.href); url.protocol = url.protocol === "https:" ? "wss:" : "ws:"; -export const CACHE_RELAY_ENABLED = !!window.CACHE_RELAY_ENABLED || !!localStorage.getItem("cacheRelay"); +export const LOCAL_CACHE_RELAY_ENABLED = !!window.CACHE_RELAY_ENABLED || !!localStorage.getItem("cacheRelay"); export const LOCAL_CACHE_RELAY = url.toString(); -const wroteEvents = new Set(); -const writeQueue: NostrEvent[] = []; +export const localCacheDatabase = await openDB(); -const BATCH_WRITE = 100; - -const log = logger.extend(`LocalCacheRelay`); -async function flush() { - for (let i = 0; i < BATCH_WRITE; i++) { - const e = writeQueue.pop(); - if (!e) continue; - relayPoolService.requestRelay(LOCAL_CACHE_RELAY).send(["EVENT", e]); - } -} -function report() { - if (writeQueue.length) { - log(`${writeQueue.length} events in write queue`); +function createRelay() { + if (LOCAL_CACHE_RELAY_ENABLED) { + log(`Using ${LOCAL_CACHE_RELAY}`); + return new Relay(LOCAL_CACHE_RELAY); + } else { + log(`Using IndexedDB`); + return new CacheRelay(localCacheDatabase); } } -function addToQueue(e: NostrEvent) { - if (!CACHE_RELAY_ENABLED) return; - if (!wroteEvents.has(e.id)) { - wroteEvents.add(e.id); - writeQueue.push(e); - } -} +export const localCacheRelay = createRelay(); -if (CACHE_RELAY_ENABLED) { - log("Enabled"); - relayPoolService.onRelayCreated.subscribe((relay) => { - if (relay.url !== LOCAL_CACHE_RELAY) { - relay.onEvent.subscribe((incomingEvent) => addToQueue(incomingEvent.body)); - } - }); -} - -const localCacheRelayService = { - enabled: CACHE_RELAY_ENABLED, - addToQueue, -}; +// connect without waiting +localCacheRelay.connect().then(() => { + log("Connected"); +}); +// keep the relay connection alive setInterval(() => { - if (CACHE_RELAY_ENABLED) flush(); -}, 1000); -setInterval(() => { - if (CACHE_RELAY_ENABLED) report(); -}, 1000 * 10); + if (!localCacheRelay.connected) localCacheRelay.connect().then(() => log("Reconnected")); +}, 1000 * 5); if (import.meta.env.DEV) { //@ts-ignore - window.localCacheRelayService = localCacheRelayService; + window.localCacheRelay = localCacheRelay; } - -export default localCacheRelayService; diff --git a/src/services/nostr-connect.ts b/src/services/nostr-connect.ts index f95cb1d15..47c461aa7 100644 --- a/src/services/nostr-connect.ts +++ b/src/services/nostr-connect.ts @@ -1,4 +1,4 @@ -import { finishEvent, generatePrivateKey, getPublicKey, nip04, nip19 } from "nostr-tools"; +import { finalizeEvent, generateSecretKey, getPublicKey, nip04, nip19 } from "nostr-tools"; import dayjs from "dayjs"; import { nanoid } from "nanoid"; @@ -10,6 +10,7 @@ import { DraftNostrEvent, NostrEvent, isPTag } from "../types/nostr-event"; import createDefer, { Deferred } from "../classes/deferred"; import { truncatedId } from "../helpers/nostr/events"; import { NostrConnectAccount } from "./account"; +import { bytesToHex, hexToBytes } from "@noble/hashes/utils"; export enum NostrConnectMethod { Connect = "connect", @@ -60,8 +61,8 @@ export class NostrConnectClient { this.pubkey = pubkey; this.relays = relays; - this.secretKey = secretKey || generatePrivateKey(); - this.publicKey = getPublicKey(this.secretKey); + this.secretKey = secretKey || bytesToHex(generateSecretKey()); + this.publicKey = getPublicKey(hexToBytes(this.secretKey)); this.sub.onEvent.subscribe(this.handleEvent, this); this.sub.setQueryMap(createSimpleQueryMap(this.relays, { kinds: [24133], "#p": [this.publicKey] })); @@ -99,14 +100,14 @@ export class NostrConnectClient { } private createEvent(content: string) { - return finishEvent( + return finalizeEvent( { kind: 24133, created_at: dayjs().unix(), tags: [["p", this.pubkey]], content, }, - this.secretKey, + hexToBytes(this.secretKey), ); } private async makeRequest( diff --git a/src/services/relay-scoreboard.ts b/src/services/relay-scoreboard.ts index 3a2483d6b..f8a2b932b 100644 --- a/src/services/relay-scoreboard.ts +++ b/src/services/relay-scoreboard.ts @@ -255,7 +255,9 @@ class RelayScoreboardService { const relayScoreboardService = new RelayScoreboardService(); -relayScoreboardService.loadStats(); +setTimeout(() => { + relayScoreboardService.loadStats(); +}, 0); setInterval(() => { relayScoreboardService.saveStats(); diff --git a/src/services/replaceable-event-requester.ts b/src/services/replaceable-event-requester.ts index 83498cf55..67a316a5e 100644 --- a/src/services/replaceable-event-requester.ts +++ b/src/services/replaceable-event-requester.ts @@ -12,7 +12,7 @@ import db from "./db"; import { nameOrPubkey } from "./user-metadata"; import { getEventCoordinate } from "../helpers/nostr/events"; import createDefer, { Deferred } from "../classes/deferred"; -import localCacheRelayService, { LOCAL_CACHE_RELAY } from "./local-cache-relay"; +import { LOCAL_CACHE_RELAY, LOCAL_CACHE_RELAY_ENABLED, localCacheRelay } from "./local-cache-relay"; type Pubkey = string; type Relay = string; @@ -33,7 +33,7 @@ export function createCoordinate(kind: number, pubkey: string, d?: string) { return `${kind}:${pubkey}${d ? ":" + d : ""}`; } -const RELAY_REQUEST_BATCH_TIME = 1000; +const RELAY_REQUEST_BATCH_TIME = 500; /** This class is ued to batch requests to a single relay */ class ReplaceableEventRelayLoader { @@ -167,7 +167,9 @@ class ReplaceableEventLoaderService { const current = sub.value; if (!current || event.created_at > current.created_at) { sub.next(event); - if (saveToCache) this.saveToCache(cord, event); + if (saveToCache) { + this.saveToCache(cord, event); + } } } @@ -223,6 +225,8 @@ class ReplaceableEventLoaderService { this.dbLog(`Writing ${this.writeCacheQueue.size} events to database`); const transaction = db.transaction("replaceableEvents", "readwrite"); for (const [cord, event] of this.writeCacheQueue) { + localCacheRelay.publish(event); + // TODO: remove this transaction.objectStore("replaceableEvents").put({ addr: cord, event, created: dayjs().unix() }); } this.writeCacheQueue.clear(); @@ -234,6 +238,7 @@ class ReplaceableEventLoaderService { this.writeToCacheThrottle(); } + /** @deprecated */ async pruneDatabaseCache() { const keys = await db.getAllKeysFromIndex( "replaceableEvents", @@ -255,7 +260,8 @@ class ReplaceableEventLoaderService { const sub = this.events.get(cord); const relayUrls = Array.from(relays); - if (localCacheRelayService.enabled) relayUrls.unshift(LOCAL_CACHE_RELAY); + // TODO: use localCacheRelay instead + if (LOCAL_CACHE_RELAY_ENABLED) relayUrls.unshift(LOCAL_CACHE_RELAY); for (const relay of relayUrls) { const request = this.loaders.get(relay).requestEvent(kind, pubkey, d); diff --git a/src/services/signing.tsx b/src/services/signing.tsx index f8ba230b0..9d9741951 100644 --- a/src/services/signing.tsx +++ b/src/services/signing.tsx @@ -1,4 +1,4 @@ -import { nip04, getPublicKey, finishEvent } from "nostr-tools"; +import { nip04, getPublicKey, finalizeEvent } from "nostr-tools"; import { DraftNostrEvent, NostrEvent } from "../types/nostr-event"; import { Account } from "./account"; @@ -6,6 +6,7 @@ import db from "./db"; import serialPortService from "./serial-port"; import amberSignerService from "./amber-signer"; import nostrConnectService from "./nostr-connect"; +import { hexToBytes } from "@noble/hashes/utils"; const decryptedKeys = new Map(); @@ -54,7 +55,7 @@ class SigningService { const encrypted = await window.crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, encode.encode(secKey)); // add key to cache - decryptedKeys.set(getPublicKey(secKey), secKey); + decryptedKeys.set(getPublicKey(hexToBytes(secKey)), secKey); return { secKey: encrypted, @@ -91,8 +92,8 @@ class SigningService { switch (account.type) { case "local": { const secKey = await this.decryptSecKey(account); - const tmpDraft = { ...draft, pubkey: getPublicKey(secKey) }; - const event = finishEvent(tmpDraft, secKey) as NostrEvent; + const tmpDraft = { ...draft, pubkey: getPublicKey(hexToBytes(secKey)) }; + const event = finalizeEvent(tmpDraft, hexToBytes(secKey)) as NostrEvent; return event; } case "extension": diff --git a/src/services/single-event.ts b/src/services/single-event.ts index 0ee8f4bbb..daffa2a69 100644 --- a/src/services/single-event.ts +++ b/src/services/single-event.ts @@ -5,36 +5,55 @@ import Subject from "../classes/subject"; import SuperMap from "../classes/super-map"; import { safeRelayUrls } from "../helpers/url"; import { NostrEvent } from "../types/nostr-event"; -import localCacheRelayService, { LOCAL_CACHE_RELAY } from "./local-cache-relay"; +import { localCacheRelay } from "./local-cache-relay"; +import { relayRequest } from "../helpers/relay"; +import { logger } from "../helpers/debug"; -const RELAY_REQUEST_BATCH_TIME = 1000; +const RELAY_REQUEST_BATCH_TIME = 500; class SingleEventService { private cache = new SuperMap>(() => new Subject()); pending = new Map(); + log = logger.extend("SingleEvent"); requestEvent(id: string, relays: string[]) { const subject = this.cache.get(id); if (subject.value) return subject; const newUrls = safeRelayUrls(relays); - if (localCacheRelayService.enabled) newUrls.push(LOCAL_CACHE_RELAY); this.pending.set(id, this.pending.get(id)?.concat(newUrls) ?? newUrls); this.batchRequestsThrottle(); return subject; } - handleEvent(event: NostrEvent) { + handleEvent(event: NostrEvent, cache = true) { this.cache.get(event.id).next(event); + + if (cache) localCacheRelay.publish(event); } private batchRequestsThrottle = _throttle(this.batchRequests, RELAY_REQUEST_BATCH_TIME); - batchRequests() { + async batchRequests() { if (this.pending.size === 0) return; + const ids = Array.from(this.pending.keys()); + const loaded: string[] = []; + + // load from cache relay + const fromCache = await relayRequest(localCacheRelay, [{ ids }]); + + for (const e of fromCache) { + this.handleEvent(e, false); + loaded.push(e.id); + } + + if (loaded.length > 0) this.log(`Loaded ${loaded.length} from cache instead of relays`); + const idsFromRelays: Record = {}; for (const [id, relays] of this.pending) { + if (loaded.includes(id)) continue; + for (const relay of relays) { idsFromRelays[relay] = idsFromRelays[relay] ?? []; idsFromRelays[relay].push(id); diff --git a/src/services/user-contacts.ts b/src/services/user-contacts.ts index 2f7af1009..40e012ec1 100644 --- a/src/services/user-contacts.ts +++ b/src/services/user-contacts.ts @@ -1,3 +1,5 @@ +import { kinds } from "nostr-tools"; + import { isPTag, NostrEvent } from "../types/nostr-event"; import { safeJson } from "../helpers/parse"; import SuperMap from "../classes/super-map"; @@ -5,7 +7,6 @@ import Subject from "../classes/subject"; import { RelayConfig, RelayMode } from "../classes/relay"; import { normalizeRelayConfigs } from "../helpers/relay"; import replaceableEventLoaderService, { RequestOptions } from "./replaceable-event-requester"; -import { Kind } from "nostr-tools"; export type UserContacts = { pubkey: string; @@ -58,7 +59,7 @@ class UserContactsService { requestContacts(pubkey: string, relays: string[], opts?: RequestOptions) { const sub = this.subjects.get(pubkey); - const requestSub = replaceableEventLoaderService.requestEvent(relays, Kind.Contacts, pubkey, undefined, opts); + const requestSub = replaceableEventLoaderService.requestEvent(relays, kinds.Contacts, pubkey, undefined, opts); sub.connectWithHandler(requestSub, (event, next) => next(parseContacts(event))); diff --git a/src/services/user-metadata.ts b/src/services/user-metadata.ts index a9a079b32..926a683b9 100644 --- a/src/services/user-metadata.ts +++ b/src/services/user-metadata.ts @@ -1,5 +1,5 @@ import db from "./db"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import _throttle from "lodash.throttle"; import { NostrEvent } from "../types/nostr-event"; @@ -26,7 +26,7 @@ class UserMetadataService { } requestMetadata(pubkey: string, relays: string[], opts: RequestOptions = {}) { const sub = this.parsedSubjects.get(pubkey); - const requestSub = replaceableEventLoaderService.requestEvent(relays, Kind.Metadata, pubkey, undefined, opts); + const requestSub = replaceableEventLoaderService.requestEvent(relays, kinds.Metadata, pubkey, undefined, opts); sub.connectWithHandler(requestSub, (event, next) => next(parseKind0Event(event))); return sub; } diff --git a/src/services/user-relays.ts b/src/services/user-relays.ts index f0a70096b..b4ad210ae 100644 --- a/src/services/user-relays.ts +++ b/src/services/user-relays.ts @@ -1,4 +1,4 @@ -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { isRTag, NostrEvent } from "../types/nostr-event"; import { RelayConfig } from "../classes/relay"; @@ -30,7 +30,7 @@ class UserRelaysService { } requestRelays(pubkey: string, relays: string[], opts: RequestOptions = {}) { const sub = this.subjects.get(pubkey); - const requestSub = replaceableEventLoaderService.requestEvent(relays, Kind.RelayList, pubkey, undefined, opts); + const requestSub = replaceableEventLoaderService.requestEvent(relays, kinds.RelayList, pubkey, undefined, opts); sub.connectWithHandler(requestSub, (event, next) => next(parseRelaysEvent(event))); // also fetch the relays from the users contacts @@ -49,9 +49,9 @@ class UserRelaysService { const sub = this.subjects.get(pubkey); // load from cache - await replaceableEventLoaderService.loadFromCache(createCoordinate(Kind.RelayList, pubkey)); + await replaceableEventLoaderService.loadFromCache(createCoordinate(kinds.RelayList, pubkey)); - const requestSub = replaceableEventLoaderService.getEvent(Kind.RelayList, pubkey); + const requestSub = replaceableEventLoaderService.getEvent(kinds.RelayList, pubkey); sub.connectWithHandler(requestSub, (event, next) => next(parseRelaysEvent(event))); } diff --git a/src/types/nostr-event.ts b/src/types/nostr-event.ts index 7f31c53cb..3e4fdaee0 100644 --- a/src/types/nostr-event.ts +++ b/src/types/nostr-event.ts @@ -1,3 +1,5 @@ +import type { NostrEvent as NostrToolsNostrEvent } from "nostr-tools"; + export type ETag = ["e", string] | ["e", string, string] | ["e", string, string, string]; export type ATag = ["a", string] | ["a", string, string] | ["e", string, string, string]; export type PTag = ["p", string] | ["p", string, string] | ["p", string, string, string]; @@ -7,14 +9,8 @@ export type ExpirationTag = ["expiration", string]; export type EmojiTag = ["emoji", string, string]; export type Tag = string[] | ETag | PTag | RTag | DTag | ATag | ExpirationTag; -export type NostrEvent = { - id: string; - pubkey: string; - created_at: number; - kind: number; +export type NostrEvent = Omit & { tags: Tag[]; - content: string; - sig: string; }; export type CountResponse = { count: number; diff --git a/src/views/badges/badge-details.tsx b/src/views/badges/badge-details.tsx index e66f8962d..b05581a11 100644 --- a/src/views/badges/badge-details.tsx +++ b/src/views/badges/badge-details.tsx @@ -1,5 +1,5 @@ import { useNavigate } from "react-router-dom"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { Button, Flex, @@ -89,7 +89,7 @@ function BadgeDetailsPage({ badge }: { badge: NostrEvent }) { const coordinate = getEventCoordinate(badge); const awardsTimeline = useTimelineLoader(`${coordinate}-awards`, readRelays, { "#a": [coordinate], - kinds: [Kind.BadgeAward], + kinds: [kinds.BadgeAward], }); if (!badge) return ; diff --git a/src/views/badges/browse.tsx b/src/views/badges/browse.tsx index d0f216a94..9a3643423 100644 --- a/src/views/badges/browse.tsx +++ b/src/views/badges/browse.tsx @@ -1,5 +1,5 @@ import { Flex, SimpleGrid } from "@chakra-ui/react"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider"; import PeopleListSelection from "../../components/people-list-selection/people-list-selection"; @@ -19,7 +19,7 @@ function BadgesBrowsePage() { const timeline = useTimelineLoader( `${listId}-badges`, readRelays, - filter ? { ...filter, kinds: [Kind.BadgeDefinition] } : undefined, + filter ? { ...filter, kinds: [kinds.BadgeDefinition] } : undefined, ); const lists = useSubject(timeline.timeline); diff --git a/src/views/badges/components/badge-card.tsx b/src/views/badges/components/badge-card.tsx index 9c98a1249..d17b6ce45 100644 --- a/src/views/badges/components/badge-card.tsx +++ b/src/views/badges/components/badge-card.tsx @@ -1,6 +1,7 @@ import { memo, useRef } from "react"; import { Link as RouterLink, useNavigate } from "react-router-dom"; import { ButtonGroup, Card, CardBody, CardHeader, CardProps, Flex, Heading, Image, Link, Text } from "@chakra-ui/react"; +import { kinds } from "nostr-tools"; import UserAvatarLink from "../../../components/user-avatar-link"; import UserLink from "../../../components/user-link"; @@ -12,7 +13,6 @@ import BadgeMenu from "./badge-menu"; import { getBadgeImage, getBadgeName } from "../../../helpers/nostr/badges"; import Timestamp from "../../../components/timestamp"; import useEventCount from "../../../hooks/use-event-count"; -import { Kind } from "nostr-tools"; function BadgeCard({ badge, ...props }: Omit & { badge: NostrEvent }) { const naddr = getSharableEventAddress(badge); @@ -23,7 +23,7 @@ function BadgeCard({ badge, ...props }: Omit & { badge: N const ref = useRef(null); useRegisterIntersectionEntity(ref, getEventUID(badge)); - const timesAwarded = useEventCount({ kinds: [Kind.BadgeAward], "#a": [getEventCoordinate(badge)] }); + const timesAwarded = useEventCount({ kinds: [kinds.BadgeAward], "#a": [getEventCoordinate(badge)] }); return ( diff --git a/src/views/badges/index.tsx b/src/views/badges/index.tsx index 6cb57a5fd..9720ff2ab 100644 --- a/src/views/badges/index.tsx +++ b/src/views/badges/index.tsx @@ -1,7 +1,7 @@ import { useCallback } from "react"; import { Button, Flex, Heading, Image, Link, Spacer } from "@chakra-ui/react"; import { Link as RouterLink } from "react-router-dom"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { ExternalLinkIcon } from "../../components/icons"; import VerticalPageLayout from "../../components/vertical-page-layout"; @@ -33,7 +33,7 @@ function BadgesPage() { readRelays, { "#p": filter?.authors, - kinds: [Kind.BadgeAward], + kinds: [kinds.BadgeAward], }, { eventFilter }, ); diff --git a/src/views/channels/channel.tsx b/src/views/channels/channel.tsx index 596a82950..f7bd5082d 100644 --- a/src/views/channels/channel.tsx +++ b/src/views/channels/channel.tsx @@ -1,7 +1,7 @@ import { memo, useCallback, useMemo } from "react"; import { useNavigate } from "react-router-dom"; import { Button, Flex, Heading, Spacer, Spinner, useDisclosure } from "@chakra-ui/react"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import useSingleEvent from "../../hooks/use-single-event"; import { ErrorBoundary } from "../../components/error-boundary"; @@ -61,7 +61,7 @@ function ChannelPage({ channel }: { channel: NostrEvent }) { `${channel.id}-chat-messages`, relays, { - kinds: [Kind.ChannelMessage], + kinds: [kinds.ChannelMessage], "#e": [channel.id], }, { eventFilter }, diff --git a/src/views/channels/components/send-message-form.tsx b/src/views/channels/components/send-message-form.tsx index 53cb11fe0..28a21e460 100644 --- a/src/views/channels/components/send-message-form.tsx +++ b/src/views/channels/components/send-message-form.tsx @@ -1,7 +1,7 @@ import { useRef, useState } from "react"; import { useForm } from "react-hook-form"; import dayjs from "dayjs"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { Button, Flex, FlexProps, Heading, useToast } from "@chakra-ui/react"; import { useSigningContext } from "../../../providers/global/signing-provider"; @@ -40,7 +40,7 @@ export default function ChannelMessageForm({ if (!values.content) return; let draft: DraftNostrEvent = { - kind: Kind.ChannelMessage, + kind: kinds.ChannelMessage, content: values.content, tags: [["e", channel.id]], created_at: dayjs().unix(), diff --git a/src/views/channels/index.tsx b/src/views/channels/index.tsx index 9725526b0..fd98207ad 100644 --- a/src/views/channels/index.tsx +++ b/src/views/channels/index.tsx @@ -1,6 +1,6 @@ -import { Kind, nip19 } from "nostr-tools"; -import { Box, Card, CardBody, CardHeader, Flex, LinkBox, Text } from "@chakra-ui/react"; -import { Link as RouterLink } from "react-router-dom"; +import { useCallback } from "react"; +import { kinds } from "nostr-tools"; +import { Flex } from "@chakra-ui/react"; import useTimelineLoader from "../../hooks/use-timeline-loader"; import RelaySelectionProvider, { useRelaySelectionContext } from "../../providers/local/relay-selection-provider"; @@ -11,7 +11,6 @@ import IntersectionObserverProvider from "../../providers/local/intersection-obs import { NostrEvent } from "../../types/nostr-event"; import { ErrorBoundary } from "../../components/error-boundary"; import RelaySelectionButton from "../../components/relay-selection/relay-selection-button"; -import { useCallback, useRef } from "react"; import useClientSideMuteFilter from "../../hooks/use-client-side-mute-filter"; import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider"; import PeopleListSelection from "../../components/people-list-selection/people-list-selection"; @@ -32,7 +31,7 @@ function ChannelsHomePage() { const timeline = useTimelineLoader( `${listId}-channels`, relays, - filter ? { ...filter, kinds: [Kind.ChannelCreation] } : undefined, + filter ? { ...filter, kinds: [kinds.ChannelCreation] } : undefined, { eventFilter }, ); const channels = useSubject(timeline.timeline); diff --git a/src/views/communities/hooks/use-count-community-post.ts b/src/views/communities/hooks/use-count-community-post.ts index 35a35a092..644c59101 100644 --- a/src/views/communities/hooks/use-count-community-post.ts +++ b/src/views/communities/hooks/use-count-community-post.ts @@ -1,5 +1,5 @@ import dayjs from "dayjs"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { NostrEvent } from "../../../types/nostr-event"; import useEventCount from "../../../hooks/use-event-count"; @@ -9,5 +9,5 @@ export default function useCountCommunityPosts( community: NostrEvent, since: number = dayjs().subtract(1, "month").unix(), ) { - return useEventCount({ "#a": [getEventCoordinate(community)], kinds: [Kind.Text], since }); + return useEventCount({ "#a": [getEventCoordinate(community)], kinds: [kinds.ShortTextNote], since }); } diff --git a/src/views/communities/index.tsx b/src/views/communities/index.tsx index bc377192c..b1e08ccab 100644 --- a/src/views/communities/index.tsx +++ b/src/views/communities/index.tsx @@ -1,4 +1,3 @@ -import { Kind } from "nostr-tools"; import { useMemo } from "react"; import { Button, @@ -13,12 +12,12 @@ import { Flex, Heading, Link, - SimpleGrid, Switch, Text, useDisclosure, useToast, } from "@chakra-ui/react"; +import { kinds } from "nostr-tools"; import { Link as RouterLink, useNavigate } from "react-router-dom"; import { Navigate } from "react-router-dom"; import dayjs from "dayjs"; @@ -110,7 +109,7 @@ function CommunitiesHomePage() { readRelays, communityCoordinates.length > 0 ? { - kinds: [Kind.Text, Kind.Repost, COMMUNITY_APPROVAL_KIND], + kinds: [kinds.ShortTextNote, kinds.Repost, COMMUNITY_APPROVAL_KIND], "#a": communityCoordinates.map((p) => createCoordinate(p.kind, p.pubkey, p.identifier)), } : undefined, diff --git a/src/views/community/community-home.tsx b/src/views/community/community-home.tsx index 15444c523..d8304b941 100644 --- a/src/views/community/community-home.tsx +++ b/src/views/community/community-home.tsx @@ -1,7 +1,7 @@ import { useContext } from "react"; import { Button, ButtonGroup, Divider, Flex, Heading, Text, useDisclosure } from "@chakra-ui/react"; import { Outlet, Link as RouterLink, useLocation } from "react-router-dom"; -import { Kind, nip19 } from "nostr-tools"; +import { kinds, nip19 } from "nostr-tools"; import { getCommunityRelays as getCommunityRelays, @@ -52,7 +52,7 @@ export default function CommunityHomePage({ community }: { community: NostrEvent const communityRelays = getCommunityRelays(community); const readRelays = useReadRelayUrls(communityRelays); const timeline = useTimelineLoader(`${getEventUID(community)}-timeline`, readRelays, { - kinds: [Kind.Text, Kind.Repost, COMMUNITY_APPROVAL_KIND], + kinds: [kinds.ShortTextNote, kinds.Repost, COMMUNITY_APPROVAL_KIND], "#a": [communityCoordinate], }); diff --git a/src/views/community/components/community-post.tsx b/src/views/community/components/community-post.tsx index 77a76f7a6..858b788de 100644 --- a/src/views/community/components/community-post.tsx +++ b/src/views/community/components/community-post.tsx @@ -14,7 +14,7 @@ import { } from "@chakra-ui/react"; import { Link as RouterLink } from "react-router-dom"; import dayjs from "dayjs"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { NostrEvent, isETag } from "../../../types/nostr-event"; import { getEventCommunityPointer, getPostSubject } from "../../../helpers/nostr/communities"; @@ -175,9 +175,9 @@ export function CommunityRepostPost({ export default function CommunityPost({ event, ...props }: Omit & CommunityPostPropTypes) { switch (event.kind) { - case Kind.Text: + case kinds.ShortTextNote: return ; - case Kind.Repost: + case kinds.Repost: return ; } return null; diff --git a/src/views/dms/chat.tsx b/src/views/dms/chat.tsx index 4607eab2e..7ccb9bae5 100644 --- a/src/views/dms/chat.tsx +++ b/src/views/dms/chat.tsx @@ -1,12 +1,11 @@ import { memo, useCallback, useContext, useEffect, useMemo, useState } from "react"; import { Button, ButtonGroup, Card, Flex, IconButton } from "@chakra-ui/react"; -import { Kind, nip19 } from "nostr-tools"; -import { UNSAFE_DataRouterContext, useLocation, useNavigate, useParams } from "react-router-dom"; +import { UNSAFE_DataRouterContext, useLocation, useNavigate } from "react-router-dom"; +import { kinds } from "nostr-tools"; import { ChevronLeftIcon, ThreadIcon } from "../../components/icons"; import UserAvatar from "../../components/user-avatar"; import UserLink from "../../components/user-link"; -import { isHexKey } from "../../helpers/nip19"; import useSubject from "../../hooks/use-subject"; import RequireCurrentAccount from "../../providers/route/require-current-account"; import useTimelineLoader from "../../hooks/use-timeline-loader"; @@ -74,12 +73,12 @@ function DirectMessageChatPage({ pubkey }: { pubkey: string }) { const myInbox = useReadRelayUrls(); const timeline = useTimelineLoader(`${pubkey}-${account.pubkey}-messages`, myInbox, [ { - kinds: [Kind.EncryptedDirectMessage], + kinds: [kinds.EncryptedDirectMessage], "#p": [account.pubkey], authors: [pubkey], }, { - kinds: [Kind.EncryptedDirectMessage], + kinds: [kinds.EncryptedDirectMessage], "#p": [pubkey], authors: [account.pubkey], }, diff --git a/src/views/dms/components/send-message-form.tsx b/src/views/dms/components/send-message-form.tsx index f9de13fe9..712e7b579 100644 --- a/src/views/dms/components/send-message-form.tsx +++ b/src/views/dms/components/send-message-form.tsx @@ -1,7 +1,7 @@ import { useRef, useState } from "react"; import { useForm } from "react-hook-form"; import dayjs from "dayjs"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { Button, Flex, FlexProps, Heading, useToast } from "@chakra-ui/react"; import { useSigningContext } from "../../../providers/global/signing-provider"; @@ -47,7 +47,7 @@ export default function SendMessageForm({ const encrypted = await requestEncrypt(values.content, pubkey); const event: DraftNostrEvent = { - kind: Kind.EncryptedDirectMessage, + kind: kinds.EncryptedDirectMessage, content: encrypted, tags: [["p", pubkey]], created_at: dayjs().unix(), diff --git a/src/views/home/index.tsx b/src/views/home/index.tsx index c9576cff4..21351b3cf 100644 --- a/src/views/home/index.tsx +++ b/src/views/home/index.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo } from "react"; import { Flex, useDisclosure } from "@chakra-ui/react"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { isReply, isRepost } from "../../helpers/nostr/events"; import useTimelineLoader from "../../hooks/use-timeline-loader"; @@ -15,7 +15,7 @@ import useClientSideMuteFilter from "../../hooks/use-client-side-mute-filter"; import NoteFilterTypeButtons from "../../components/note-filter-type-buttons"; import KindSelectionProvider, { useKindSelectionContext } from "../../providers/local/kind-selection-provider"; -const defaultKinds = [Kind.Text, Kind.Repost, Kind.Article, Kind.RecommendRelay, Kind.BadgeAward]; +const defaultKinds = [kinds.ShortTextNote, kinds.Repost, kinds.LongFormArticle, kinds.RecommendRelay, kinds.BadgeAward]; function HomePage() { const showReplies = useDisclosure({ defaultIsOpen: localStorage.getItem("show-replies") === "true" }); diff --git a/src/views/link/index.tsx b/src/views/link/index.tsx index 10ee49189..f09fd1a07 100644 --- a/src/views/link/index.tsx +++ b/src/views/link/index.tsx @@ -1,6 +1,6 @@ import { Alert, AlertIcon, AlertTitle } from "@chakra-ui/react"; import { Navigate, useParams } from "react-router-dom"; -import { Kind, nip19 } from "nostr-tools"; +import { kinds, nip19 } from "nostr-tools"; import { STREAM_KIND } from "../../helpers/nostr/stream"; import { EMOJI_PACK_KIND } from "../../helpers/nostr/emoji-packs"; import { NOTE_LIST_KIND, PEOPLE_LIST_KIND } from "../../helpers/nostr/lists"; @@ -36,11 +36,11 @@ function NostrLinkPage() { if (decoded.data.kind === EMOJI_PACK_KIND) return ; if (decoded.data.kind === NOTE_LIST_KIND) return ; if (decoded.data.kind === PEOPLE_LIST_KIND) return ; - if (decoded.data.kind === Kind.BadgeDefinition) return ; + if (decoded.data.kind === kinds.BadgeDefinition) return ; if (decoded.data.kind === COMMUNITY_DEFINITION_KIND) return ; if (decoded.data.kind === FLARE_VIDEO_KIND) return ; - if (decoded.data.kind === Kind.ChannelCreation) return ; - if (decoded.data.kind === Kind.Text) return ; + if (decoded.data.kind === kinds.ChannelCreation) return ; + if (decoded.data.kind === kinds.ShortTextNote) return ; // if there is no kind redirect to the thread view return ; } diff --git a/src/views/lists/components/list-card.tsx b/src/views/lists/components/list-card.tsx index 07679dc17..58b153626 100644 --- a/src/views/lists/components/list-card.tsx +++ b/src/views/lists/components/list-card.tsx @@ -13,7 +13,7 @@ import { SimpleGrid, Text, } from "@chakra-ui/react"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import UserAvatarLink from "../../../components/user-avatar-link"; import UserLink from "../../../components/user-link"; @@ -48,7 +48,7 @@ export function ListCardContent({ list, ...props }: Omit const notes = getEventPointersFromList(list); const coordinates = getAddressPointersFromList(list); const communities = coordinates.filter((cord) => cord.kind === COMMUNITY_DEFINITION_KIND); - const articles = coordinates.filter((cord) => cord.kind === Kind.Article); + const articles = coordinates.filter((cord) => cord.kind === kinds.LongFormArticle); const references = getReferencesFromList(list); return ( diff --git a/src/views/lists/components/list-feed-button.tsx b/src/views/lists/components/list-feed-button.tsx index 27917bed4..39bc15401 100644 --- a/src/views/lists/components/list-feed-button.tsx +++ b/src/views/lists/components/list-feed-button.tsx @@ -1,13 +1,13 @@ import { Button, ButtonProps } from "@chakra-ui/react"; import { Link as RouterLink } from "react-router-dom"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { NostrEvent } from "../../../types/nostr-event"; import { getEventCoordinate } from "../../../helpers/nostr/events"; import { PEOPLE_LIST_KIND } from "../../../helpers/nostr/lists"; export default function ListFeedButton({ list, ...props }: { list: NostrEvent } & Omit) { - const shouldShowFeedButton = list.kind === PEOPLE_LIST_KIND || list.kind === Kind.Contacts; + const shouldShowFeedButton = list.kind === PEOPLE_LIST_KIND || list.kind === kinds.Contacts; if (!shouldShowFeedButton) return null; diff --git a/src/views/lists/index.tsx b/src/views/lists/index.tsx index 12dbbfa30..adf48f804 100644 --- a/src/views/lists/index.tsx +++ b/src/views/lists/index.tsx @@ -1,6 +1,6 @@ import { Button, Divider, Flex, Heading, Image, Link, SimpleGrid, Spacer, useDisclosure } from "@chakra-ui/react"; import { useNavigate, Link as RouterLink, Navigate } from "react-router-dom"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import useCurrentAccount from "../../hooks/use-current-account"; import { ExternalLinkIcon, PlusCircleIcon } from "../../components/icons"; @@ -55,7 +55,7 @@ function ListsHomePage() { Special lists - + diff --git a/src/views/lists/list/index.tsx b/src/views/lists/list/index.tsx index ddd7a8e90..fe53a5855 100644 --- a/src/views/lists/list/index.tsx +++ b/src/views/lists/list/index.tsx @@ -1,5 +1,5 @@ import { useNavigate } from "react-router-dom"; -import { Kind, nip19 } from "nostr-tools"; +import { kinds, nip19 } from "nostr-tools"; import type { DecodeResult } from "nostr-tools/lib/types/nip19"; import { Box, Button, Flex, Heading, SimpleGrid, Spacer, Spinner, Text } from "@chakra-ui/react"; @@ -49,7 +49,7 @@ function ListPage({ list }: { list: NostrEvent }) { const notes = getEventPointersFromList(list); const coordinates = getAddressPointersFromList(list); const communities = coordinates.filter((cord) => cord.kind === COMMUNITY_DEFINITION_KIND); - const articles = coordinates.filter((cord) => cord.kind === Kind.Article); + const articles = coordinates.filter((cord) => cord.kind === kinds.LongFormArticle); const references = getReferencesFromList(list); return ( diff --git a/src/views/map/index.tsx b/src/views/map/index.tsx index 6c79e50ec..b128842c1 100644 --- a/src/views/map/index.tsx +++ b/src/views/map/index.tsx @@ -1,7 +1,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { useNavigate, useSearchParams } from "react-router-dom"; import { Box, Button, Flex } from "@chakra-ui/react"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import ngeohash from "ngeohash"; import "leaflet/dist/leaflet.css"; import L from "leaflet"; @@ -123,7 +123,7 @@ export default function MapView() { const timeline = useTimelineLoader( "geo-events", readRelays, - cells.length > 0 ? { "#g": cells, kinds: [Kind.Text] } : undefined, + cells.length > 0 ? { "#g": cells, kinds: [kinds.ShortTextNote] } : undefined, ); const setCellsFromMap = useCallback(() => { diff --git a/src/views/map/timeline.tsx b/src/views/map/timeline.tsx index 983cf94e1..f1c9406ff 100644 --- a/src/views/map/timeline.tsx +++ b/src/views/map/timeline.tsx @@ -1,5 +1,6 @@ -import { Kind } from "nostr-tools"; import React from "react"; +import { kinds } from "nostr-tools"; + import { ErrorBoundary } from "../../components/error-boundary"; import useSubject from "../../hooks/use-subject"; import StreamNote from "../../components/timeline-page/generic-note-timeline/stream-note"; @@ -10,7 +11,7 @@ import { NostrEvent } from "../../types/nostr-event"; const RenderEvent = React.memo(({ event, focused }: { event: NostrEvent; focused?: boolean }) => { switch (event.kind) { - case Kind.Text: + case kinds.ShortTextNote: return ; case STREAM_KIND: return ; diff --git a/src/views/notifications/index.tsx b/src/views/notifications/index.tsx index 957a58fee..411b4002d 100644 --- a/src/views/notifications/index.tsx +++ b/src/views/notifications/index.tsx @@ -1,6 +1,6 @@ import { memo, useEffect, useMemo, useRef } from "react"; import { Button, ButtonGroup, Flex, useDisclosure } from "@chakra-ui/react"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { Link as RouterLink } from "react-router-dom"; import RequireCurrentAccount from "../../providers/route/require-current-account"; @@ -61,15 +61,15 @@ const NotificationsTimeline = memo( const filteredEvents = useMemo( () => throttledEvents.filter((e) => { - if (peoplePubkeys && e.kind !== Kind.Zap && !peoplePubkeys.includes(e.pubkey)) return false; + if (peoplePubkeys && e.kind !== kinds.Zap && !peoplePubkeys.includes(e.pubkey)) return false; - if (e.kind === Kind.Text) { + if (e.kind === kinds.ShortTextNote) { if (!showReplies && isReply(e)) return false; if (!showMentions && !isReply(e)) return false; } - if (!showReactions && e.kind === Kind.Reaction) return false; - if (!showReposts && e.kind === Kind.Repost) return false; - if (!showZaps && e.kind === Kind.Zap) return false; + if (!showReactions && e.kind === kinds.Reaction) return false; + if (!showReposts && e.kind === kinds.Repost) return false; + if (!showZaps && e.kind === kinds.Zap) return false; return true; }), diff --git a/src/views/notifications/notification-item.tsx b/src/views/notifications/notification-item.tsx index 91576b268..989689e5e 100644 --- a/src/views/notifications/notification-item.tsx +++ b/src/views/notifications/notification-item.tsx @@ -1,6 +1,6 @@ import { ReactNode, forwardRef, memo, useMemo, useRef } from "react"; import { AvatarGroup, Flex, IconButton, IconButtonProps, Text, useDisclosure } from "@chakra-ui/react"; -import { Kind, nip18, nip25 } from "nostr-tools"; +import { kinds, nip18, nip25 } from "nostr-tools"; import useCurrentAccount from "../../hooks/use-current-account"; import { NostrEvent, isATag, isETag } from "../../types/nostr-event"; @@ -86,7 +86,7 @@ const ReactionNotification = forwardRef(( if (!pointer || (account?.pubkey && pointer.author !== account.pubkey)) return null; const reactedEvent = useSingleEvent(pointer.id, pointer.relays); - if (reactedEvent?.kind === Kind.EncryptedDirectMessage) return null; + if (reactedEvent?.kind === kinds.EncryptedDirectMessage) return null; return ( }> @@ -156,18 +156,18 @@ const NotificationItem = ({ event }: { event: NostrEvent }) => { let content: ReactNode | null = null; switch (event.kind) { - case Kind.Text: + case kinds.ShortTextNote: case TORRENT_COMMENT_KIND: - case Kind.Article: + case kinds.LongFormArticle: content = ; break; - case Kind.Reaction: + case kinds.Reaction: content = ; break; - case Kind.Repost: + case kinds.Repost: content = ; break; - case Kind.Zap: + case kinds.Zap: content = ; break; default: diff --git a/src/views/notifications/threads.tsx b/src/views/notifications/threads.tsx index 4cdf0ec76..b6a16bf64 100644 --- a/src/views/notifications/threads.tsx +++ b/src/views/notifications/threads.tsx @@ -1,5 +1,5 @@ import { MouseEventHandler, useCallback, useMemo, useRef } from "react"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { Link as RouterLink, useNavigate } from "react-router-dom"; import useCurrentAccount from "../../hooks/use-current-account"; @@ -29,7 +29,7 @@ import IntersectionObserverProvider, { import { getEventUID } from "../../helpers/nostr/events"; import { useNavigateInDrawer } from "../../providers/drawer-sub-view-provider"; -const THREAD_KINDS = [Kind.Text, TORRENT_COMMENT_KIND]; +const THREAD_KINDS = [kinds.ShortTextNote, TORRENT_COMMENT_KIND]; function ReplyEntry({ event }: { event: NostrEvent }) { const navigate = useNavigateInDrawer(); diff --git a/src/views/relays/relay/relay-notes.tsx b/src/views/relays/relay/relay-notes.tsx index e8eedbc3d..7c149ce13 100644 --- a/src/views/relays/relay/relay-notes.tsx +++ b/src/views/relays/relay/relay-notes.tsx @@ -1,6 +1,6 @@ -import { useCallback, useMemo } from "react"; +import { useCallback } from "react"; import { Flex, Spacer, useDisclosure } from "@chakra-ui/react"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { isReply, isRepost } from "../../../helpers/nostr/events"; import { useAppTitle } from "../../../hooks/use-app-title"; @@ -10,7 +10,6 @@ import TimelinePage, { useTimelinePageEventFilter } from "../../../components/ti import TimelineViewTypeButtons from "../../../components/timeline-page/timeline-view-type"; import PeopleListSelection from "../../../components/people-list-selection/people-list-selection"; import { usePeopleListContext } from "../../../providers/local/people-list-provider"; -import { NostrRequestFilter } from "../../../types/nostr-query"; import useClientSideMuteFilter from "../../../hooks/use-client-side-mute-filter"; import NoteFilterTypeButtons from "../../../components/note-filter-type-buttons"; @@ -20,7 +19,7 @@ export default function RelayNotes({ relay }: { relay: string }) { const showReposts = useDisclosure({ defaultIsOpen: true }); const { filter } = usePeopleListContext(); - const kinds = [Kind.Text]; + const k = [kinds.ShortTextNote]; const timelineEventFilter = useTimelinePageEventFilter(); const muteFilter = useClientSideMuteFilter(); @@ -33,7 +32,7 @@ export default function RelayNotes({ relay }: { relay: string }) { }, [timelineEventFilter, showReplies.isOpen, showReposts.isOpen, muteFilter], ); - const timeline = useTimelineLoader(`${relay}-notes`, [relay], filter ? { ...filter, kinds } : undefined, { + const timeline = useTimelineLoader(`${relay}-notes`, [relay], filter ? { ...filter, kinds: k } : undefined, { eventFilter, }); diff --git a/src/views/search/article-results.tsx b/src/views/search/article-results.tsx index 896a30f22..0ae2f4563 100644 --- a/src/views/search/article-results.tsx +++ b/src/views/search/article-results.tsx @@ -1,4 +1,4 @@ -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { useRelaySelectionRelays } from "../../providers/local/relay-selection-provider"; import useTimelineLoader from "../../hooks/use-timeline-loader"; @@ -14,7 +14,7 @@ export default function ArticleSearchResults({ search }: { search: string }) { const timeline = useTimelineLoader( `${listId ?? "global"}-${search}-article-search`, searchRelays, - search ? { search: search, kinds: [Kind.Article], ...filter } : undefined, + search ? { search: search, kinds: [kinds.LongFormArticle], ...filter } : undefined, ); const callback = useTimelineCurserIntersectionCallback(timeline); diff --git a/src/views/search/note-results.tsx b/src/views/search/note-results.tsx index 3737f9807..7c654e907 100644 --- a/src/views/search/note-results.tsx +++ b/src/views/search/note-results.tsx @@ -1,4 +1,4 @@ -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { useRelaySelectionRelays } from "../../providers/local/relay-selection-provider"; import useTimelineLoader from "../../hooks/use-timeline-loader"; @@ -14,7 +14,7 @@ export default function NoteSearchResults({ search }: { search: string }) { const timeline = useTimelineLoader( `${listId ?? "global"}-${search}-note-search`, searchRelays, - search ? { search: search, kinds: [Kind.Text], ...filter } : undefined, + search ? { search: search, kinds: [kinds.ShortTextNote], ...filter } : undefined, ); const callback = useTimelineCurserIntersectionCallback(timeline); diff --git a/src/views/search/profile-results.tsx b/src/views/search/profile-results.tsx index 2f4ec3e2e..e51596671 100644 --- a/src/views/search/profile-results.tsx +++ b/src/views/search/profile-results.tsx @@ -1,6 +1,7 @@ import { useEffect, useMemo, useState } from "react"; import { Box, Text } from "@chakra-ui/react"; import { useAsync } from "react-use"; +import { kinds } from "nostr-tools"; import { NostrEvent } from "../../types/nostr-event"; import { parseKind0Event } from "../../helpers/user-metadata"; @@ -13,7 +14,6 @@ import UserLink from "../../components/user-link"; import trustedUserStatsService, { NostrBandUserStats } from "../../services/trusted-user-stats"; import { useRelaySelectionRelays } from "../../providers/local/relay-selection-provider"; import useTimelineLoader from "../../hooks/use-timeline-loader"; -import { Kind } from "nostr-tools"; import useSubject from "../../hooks/use-subject"; import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback"; import IntersectionObserverProvider from "../../providers/local/intersection-observer"; @@ -57,7 +57,7 @@ export default function ProfileSearchResults({ search }: { search: string }) { const timeline = useTimelineLoader( `${listId ?? "global"}-${search}-profile-search`, searchRelays, - search ? { search: search, kinds: [Kind.Metadata], ...filter } : undefined, + search ? { search: search, kinds: [kinds.Metadata], ...filter } : undefined, ); const profiles = useSubject(timeline?.timeline) ?? []; diff --git a/src/views/settings/database-settings.tsx b/src/views/settings/database-settings.tsx index 3072f4e9c..327613581 100644 --- a/src/views/settings/database-settings.tsx +++ b/src/views/settings/database-settings.tsx @@ -9,47 +9,25 @@ import { ButtonGroup, Text, } from "@chakra-ui/react"; -import db, { clearCacheData, deleteDatabase } from "../../services/db"; -import { DatabaseIcon } from "../../components/icons"; import { useAsync } from "react-use"; +import { countEvents, countEventsByKind } from "nostr-idb"; -// copied from https://stackoverflow.com/a/39906526 -const units = ["bytes", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]; -function niceBytes(x: number) { - let l = 0, - n = x || 0; - while (n >= 1024 && ++l) { - n = n / 1024; - } - return n.toFixed(n < 10 && l > 0 ? 1 : 0) + " " + units[l]; -} +import { clearCacheData, deleteDatabase } from "../../services/db"; +import { DatabaseIcon } from "../../components/icons"; +import { localCacheDatabase } from "../../services/local-cache-relay"; function DatabaseStats() { - const { value: estimatedStorage } = useAsync(async () => await window.navigator?.storage?.estimate?.(), []); - - const { value: replaceableEventCount } = useAsync(async () => { - const keys = await db.getAllKeys("replaceableEvents"); - return keys.length; - }, []); - const { value: relayInfoCount } = useAsync(async () => { - const keys = await db.getAllKeys("relayInfo"); - return keys.length; - }, []); - const { value: nip05Count } = useAsync(async () => { - const keys = await db.getAllKeys("dnsIdentifiers"); - return keys.length; - }, []); + const { value: count } = useAsync(async () => await countEvents(localCacheDatabase), []); + const { value: kinds } = useAsync(async () => await countEventsByKind(localCacheDatabase), []); return ( <> - {replaceableEventCount} cached replaceable events - {relayInfoCount} cached relay info - {nip05Count} cached NIP-05 IDs - {estimatedStorage ? ( - - {niceBytes(estimatedStorage?.usage ?? 0)} / {niceBytes(estimatedStorage?.quota ?? 0)} Used - - ) : null} + {count} cached events + + {Object.entries(kinds || {}) + .map(([kind, count]) => `${kind} (${count})`) + .join(", ")} + ); } @@ -82,7 +60,7 @@ export default function DatabaseSettings() { - + diff --git a/src/views/signin/nsec.tsx b/src/views/signin/nsec.tsx index 65fb93171..6c189f735 100644 --- a/src/views/signin/nsec.tsx +++ b/src/views/signin/nsec.tsx @@ -15,13 +15,15 @@ import { InputRightElement, Link, } from "@chakra-ui/react"; +import { generateSecretKey, getPublicKey, nip19 } from "nostr-tools"; import { useNavigate } from "react-router-dom"; + import { RelayUrlInput } from "../../components/relay-url-input"; -import { isHex, normalizeToHexPubkey, safeDecode } from "../../helpers/nip19"; +import { isHex, safeDecode } from "../../helpers/nip19"; import accountService from "../../services/account"; -import { generatePrivateKey, getPublicKey, nip19 } from "nostr-tools"; import signingService from "../../services/signing"; import { COMMON_CONTACT_RELAY } from "../../const"; +import { bytesToHex, hexToBytes } from "@noble/hashes/utils"; export default function LoginNsecView() { const navigate = useNavigate(); @@ -36,9 +38,9 @@ export default function LoginNsecView() { const [npub, setNpub] = useState(""); const generateNewKey = useCallback(() => { - const hex = generatePrivateKey(); + const hex = generateSecretKey(); const pubkey = getPublicKey(hex); - setHexKey(hex); + setHexKey(bytesToHex(hex)); setInputValue(nip19.nsecEncode(hex)); setNpub(nip19.npubEncode(pubkey)); setShow(true); @@ -53,11 +55,11 @@ export default function LoginNsecView() { if (isHex(e.target.value)) hex = e.target.value; else { const decode = safeDecode(e.target.value); - if (decode && decode.type === "nsec") hex = decode.data; + if (decode && decode.type === "nsec") hex = bytesToHex(decode.data); } if (hex) { - const pubkey = getPublicKey(hex); + const pubkey = getPublicKey(hexToBytes(hex)); setHexKey(hex); setNpub(nip19.npubEncode(pubkey)); setError(false); @@ -75,7 +77,7 @@ export default function LoginNsecView() { e.preventDefault(); if (!hexKey) return; - const pubkey = getPublicKey(hexKey); + const pubkey = getPublicKey(hexToBytes(hexKey)); const encrypted = await signingService.encryptSecKey(hexKey); accountService.addAccount({ type: "local", pubkey, relays: [relayUrl], ...encrypted, readonly: false }); diff --git a/src/views/signup/backup-step.tsx b/src/views/signup/backup-step.tsx index d7c4d9694..7bf734b4b 100644 --- a/src/views/signup/backup-step.tsx +++ b/src/views/signup/backup-step.tsx @@ -15,6 +15,7 @@ import { containerProps } from "./common"; import { CopyIconButton } from "../../components/copy-icon-button"; import styled from "@emotion/styled"; import { useState } from "react"; +import { hexToBytes } from "@noble/hashes/utils"; const Blockquote = styled.figure` padding: var(--chakra-sizes-2) var(--chakra-sizes-4); @@ -48,7 +49,7 @@ const Blockquote = styled.figure` `; export default function BackupStep({ secretKey, onConfirm }: { secretKey: string; onConfirm: () => void }) { - const nsec = nip19.nsecEncode(secretKey); + const nsec = nip19.nsecEncode(hexToBytes(secretKey)); const [confirmed, setConfirmed] = useState(false); const [last4, setLast4] = useState(""); diff --git a/src/views/signup/create-step.tsx b/src/views/signup/create-step.tsx index d7305f121..56c5ed7b6 100644 --- a/src/views/signup/create-step.tsx +++ b/src/views/signup/create-step.tsx @@ -1,10 +1,11 @@ import { useEffect, useState } from "react"; -import { generatePrivateKey, finishEvent, Kind, getPublicKey } from "nostr-tools"; -import { Avatar, Box, Button, Flex, Heading, Text, useToast } from "@chakra-ui/react"; +import { getPublicKey, generateSecretKey, finalizeEvent, kinds } from "nostr-tools"; +import { Avatar, Button, Flex, Heading, Text, useToast } from "@chakra-ui/react"; +import { bytesToHex } from "@noble/hashes/utils"; +import dayjs from "dayjs"; import { Kind0ParsedContent } from "../../helpers/user-metadata"; import { containerProps } from "./common"; -import dayjs from "dayjs"; import { nostrBuildUploadImage } from "../../helpers/nostr-build"; import NostrPublishAction from "../../classes/nostr-publish-action"; import accountService from "../../services/account"; @@ -41,18 +42,18 @@ export default function CreateStep({ const createProfile = async () => { setLoading(true); try { - const hex = generatePrivateKey(); + const hex = generateSecretKey(); const uploaded = profileImage - ? await nostrBuildUploadImage(profileImage, async (draft) => finishEvent(draft, hex)) + ? await nostrBuildUploadImage(profileImage, async (draft) => finalizeEvent(draft, hex)) : undefined; // create profile - const kind0 = finishEvent( + const kind0 = finalizeEvent( { content: JSON.stringify({ ...metadata, picture: uploaded?.url }), created_at: dayjs().unix(), - kind: Kind.Metadata, + kind: kinds.Metadata, tags: [], }, hex, @@ -62,14 +63,14 @@ export default function CreateStep({ // login const pubkey = getPublicKey(hex); - const encrypted = await signingService.encryptSecKey(hex); + const encrypted = await signingService.encryptSecKey(bytesToHex(hex)); accountService.addAccount({ type: "local", pubkey, relays, ...encrypted, readonly: false }); accountService.switchAccount(pubkey); // set relays await clientRelaysService.postUpdatedRelays(relays.map((url) => ({ url, mode: RelayMode.ALL }))); - onSubmit(hex); + onSubmit(bytesToHex(hex)); } catch (e) { if (e instanceof Error) toast({ description: e.message, status: "error" }); } diff --git a/src/views/streams/components/stream-share-button.tsx b/src/views/streams/components/stream-share-button.tsx index 9da59b124..d82924ae0 100644 --- a/src/views/streams/components/stream-share-button.tsx +++ b/src/views/streams/components/stream-share-button.tsx @@ -1,10 +1,7 @@ import { useContext } from "react"; import { Button, ButtonProps } from "@chakra-ui/react"; -import { Kind } from "nostr-tools"; -import dayjs from "dayjs"; import { getSharableEventAddress } from "../../../helpers/nip19"; -import { DraftNostrEvent } from "../../../types/nostr-event"; import { PostModalContext } from "../../../providers/route/post-modal-provider"; import { RepostIcon } from "../../../components/icons"; import { ParsedStream } from "../../../helpers/nostr/stream"; diff --git a/src/views/streams/dashboard/zaps-card.tsx b/src/views/streams/dashboard/zaps-card.tsx index 7eae0c032..aad1449fb 100644 --- a/src/views/streams/dashboard/zaps-card.tsx +++ b/src/views/streams/dashboard/zaps-card.tsx @@ -1,6 +1,6 @@ import { memo } from "react"; import { Flex } from "@chakra-ui/react"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import useSubject from "../../../hooks/use-subject"; import useStreamChatTimeline from "../stream/stream-chat/use-stream-chat-timeline"; @@ -15,7 +15,7 @@ function ZapsCard({ stream }: { stream: ParsedStream }) { const zapMessages = streamChatTimeline.events.getSortedEvents().filter((event) => { if (stream.starts && event.created_at < stream.starts) return false; if (stream.ends && event.created_at > stream.ends) return false; - if (event.kind !== Kind.Zap) return false; + if (event.kind !== kinds.Zap) return false; return true; }); diff --git a/src/views/streams/stream/stream-chat/use-stream-chat-timeline.ts b/src/views/streams/stream/stream-chat/use-stream-chat-timeline.ts index c4643407b..420f18a1f 100644 --- a/src/views/streams/stream/stream-chat/use-stream-chat-timeline.ts +++ b/src/views/streams/stream/stream-chat/use-stream-chat-timeline.ts @@ -1,5 +1,5 @@ import { useCallback, useMemo } from "react"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { getEventUID } from "../../../../helpers/nostr/events"; import { ParsedStream, STREAM_CHAT_MESSAGE_KIND, getATag } from "../../../../helpers/nostr/stream"; @@ -30,14 +30,14 @@ export default function useStreamChatTimeline(stream: ParsedStream) { const query = useMemo(() => { const streamQuery: NostrQuery = { "#a": [getATag(stream)], - kinds: [STREAM_CHAT_MESSAGE_KIND, Kind.Zap], + kinds: [STREAM_CHAT_MESSAGE_KIND, kinds.Zap], }; if (goal) { return [ streamQuery, // also get zaps to goal - { "#e": [goal.id], kinds: [Kind.Zap] }, + { "#e": [goal.id], kinds: [kinds.Zap] }, ]; } return streamQuery; diff --git a/src/views/thread/components/reply-form.tsx b/src/views/thread/components/reply-form.tsx index 0e20a8bb4..e25434b9c 100644 --- a/src/views/thread/components/reply-form.tsx +++ b/src/views/thread/components/reply-form.tsx @@ -2,7 +2,7 @@ import { useCallback, useMemo, useRef, useState } from "react"; import { Box, Button, ButtonGroup, Flex, IconButton, VisuallyHiddenInput, useToast } from "@chakra-ui/react"; import { useForm } from "react-hook-form"; import { useThrottle } from "react-use"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import dayjs from "dayjs"; import { NostrEvent } from "../../../types/nostr-event"; @@ -34,7 +34,7 @@ export type ReplyFormProps = { onSubmitted?: (event: NostrEvent) => void; }; -export default function ReplyForm({ item, onCancel, onSubmitted, replyKind = Kind.Text }: ReplyFormProps) { +export default function ReplyForm({ item, onCancel, onSubmitted, replyKind = kinds.ShortTextNote }: ReplyFormProps) { const toast = useToast(); const account = useCurrentAccount(); const emojis = useContextEmojis(); diff --git a/src/views/tools/dm-timeline.tsx b/src/views/tools/dm-timeline.tsx index e413e0b74..a2a0f8f85 100644 --- a/src/views/tools/dm-timeline.tsx +++ b/src/views/tools/dm-timeline.tsx @@ -1,6 +1,6 @@ import { Button, Flex } from "@chakra-ui/react"; import { memo, useCallback, useRef } from "react"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import VerticalPageLayout from "../../components/vertical-page-layout"; import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider"; @@ -49,11 +49,11 @@ export function DMTimelinePage() { ? [ { ...filter, - kinds: [Kind.EncryptedDirectMessage], + kinds: [kinds.EncryptedDirectMessage], }, - { "#p": filter.authors, kinds: [Kind.EncryptedDirectMessage] }, + { "#p": filter.authors, kinds: [kinds.EncryptedDirectMessage] }, ] - : { kinds: [Kind.EncryptedDirectMessage] }, + : { kinds: [kinds.EncryptedDirectMessage] }, { eventFilter }, ); diff --git a/src/views/tools/network-dm-graph.tsx b/src/views/tools/network-dm-graph.tsx index 932ed81a6..82e126380 100644 --- a/src/views/tools/network-dm-graph.tsx +++ b/src/views/tools/network-dm-graph.tsx @@ -2,8 +2,9 @@ import { useEffect, useMemo, useState } from "react"; import { Box, Button, Flex, Input, Text } from "@chakra-ui/react"; import AutoSizer from "react-virtualized-auto-sizer"; import ForceGraph, { LinkObject, NodeObject } from "react-force-graph-3d"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import dayjs from "dayjs"; +import { useNavigate } from "react-router-dom"; import { Group, Mesh, @@ -29,7 +30,6 @@ import RelaySelectionButton from "../../components/relay-selection/relay-selecti import { useDebounce } from "react-use"; import useSubject from "../../hooks/use-subject"; import { ChevronLeftIcon } from "../../components/icons"; -import { useNavigate } from "react-router-dom"; type NodeType = { id: string; image?: string; name?: string }; @@ -57,7 +57,7 @@ function NetworkDMGraphPage() { request.onEvent.subscribe(store.addEvent, store); request.start({ authors: contactsPubkeys, - kinds: [Kind.EncryptedDirectMessage], + kinds: [kinds.EncryptedDirectMessage], since, until, }); diff --git a/src/views/tools/unknown-event-feed.tsx b/src/views/tools/unknown-event-feed.tsx index c9f4c2519..75ace8040 100644 --- a/src/views/tools/unknown-event-feed.tsx +++ b/src/views/tools/unknown-event-feed.tsx @@ -2,6 +2,8 @@ // Also this can be used as a way of discovering apps when NIP-89 is implemented import { Button, Flex } from "@chakra-ui/react"; import { memo, useCallback, useRef } from "react"; +import { useNavigate } from "react-router-dom"; +import { kinds } from "nostr-tools"; import VerticalPageLayout from "../../components/vertical-page-layout"; import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider"; @@ -15,11 +17,9 @@ import IntersectionObserverProvider, { import useSubject from "../../hooks/use-subject"; import { NostrEvent } from "../../types/nostr-event"; import { ChevronLeftIcon } from "../../components/icons"; -import { useNavigate } from "react-router-dom"; import useClientSideMuteFilter from "../../hooks/use-client-side-mute-filter"; import { getEventUID } from "../../helpers/nostr/events"; import { EmbedEvent } from "../../components/embed-event"; -import { Kind } from "nostr-tools"; import { STREAM_CHAT_MESSAGE_KIND, STREAM_KIND } from "../../helpers/nostr/stream"; import { BOOKMARK_LIST_KIND, @@ -42,19 +42,20 @@ const UnknownEvent = memo(({ event }: { event: NostrEvent }) => { }); const commonTimelineKinds = [ - Kind.Text, - Kind.Article, - Kind.Repost, - Kind.Reaction, - Kind.BadgeAward, - Kind.BadgeDefinition, + kinds.ShortTextNote, + kinds.LongFormArticle, + kinds.Repost, + kinds.Reaction, + kinds.BadgeAward, + kinds.BadgeDefinition, STREAM_KIND, - Kind.Contacts, - Kind.Metadata, - Kind.EncryptedDirectMessage, + kinds.Contacts, + kinds.Metadata, + kinds.EncryptedDirectMessage, MUTE_LIST_KIND, STREAM_CHAT_MESSAGE_KIND, - Kind.EventDeletion, + kinds.EventDeletion, + kinds.CommunityPostApproval, BOOKMARK_LIST_KIND, BOOKMARK_LIST_SET_KIND, PEOPLE_LIST_KIND, diff --git a/src/views/torrents/index.tsx b/src/views/torrents/index.tsx index 54d5ed1f7..db2b33cb0 100644 --- a/src/views/torrents/index.tsx +++ b/src/views/torrents/index.tsx @@ -1,7 +1,8 @@ import { ChangeEventHandler, useCallback, useMemo, useState } from "react"; import { Alert, Button, Flex, Spacer, Table, TableContainer, Tbody, Th, Thead, Tr, useToast } from "@chakra-ui/react"; import { Link as RouterLink, useNavigate } from "react-router-dom"; -import { generatePrivateKey, getPublicKey } from "nostr-tools"; +import { generateSecretKey, getPublicKey } from "nostr-tools"; +import { bytesToHex } from "@noble/hashes/utils"; import PeopleListSelection from "../../components/people-list-selection/people-list-selection"; import VerticalPageLayout from "../../components/vertical-page-layout"; @@ -32,8 +33,8 @@ function Warning() { const createAnonAccount = async () => { setLoading(true); try { - const secKey = generatePrivateKey(); - const encrypted = await signingService.encryptSecKey(secKey); + const secKey = generateSecretKey(); + const encrypted = await signingService.encryptSecKey(bytesToHex(secKey)); const pubkey = getPublicKey(secKey); accountService.addAccount({ type: "local", ...encrypted, pubkey, readonly: false }); accountService.switchAccount(pubkey); diff --git a/src/views/user/about/user-stats-accordion.tsx b/src/views/user/about/user-stats-accordion.tsx index 646434622..c755461cb 100644 --- a/src/views/user/about/user-stats-accordion.tsx +++ b/src/views/user/about/user-stats-accordion.tsx @@ -14,7 +14,7 @@ import { Text, } from "@chakra-ui/react"; import { useAsync } from "react-use"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { readablizeSats } from "../../../helpers/bolt11"; import trustedUserStatsService from "../../../services/trusted-user-stats"; @@ -29,7 +29,7 @@ export default function UserStatsAccordion({ pubkey }: { pubkey: string }) { const contacts = useUserContactList(pubkey, contextRelays); const { value: stats } = useAsync(() => trustedUserStatsService.getUserStats(pubkey), [pubkey]); - const followerCount = useEventCount({ "#p": [pubkey], kinds: [Kind.Contacts] }); + const followerCount = useEventCount({ "#p": [pubkey], kinds: [kinds.Contacts] }); return ( diff --git a/src/views/user/articles.tsx b/src/views/user/articles.tsx index 154c70040..d542195e9 100644 --- a/src/views/user/articles.tsx +++ b/src/views/user/articles.tsx @@ -1,5 +1,5 @@ import { useOutletContext } from "react-router-dom"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { useAdditionalRelayContext } from "../../providers/local/additional-relay-context"; import useTimelineLoader from "../../hooks/use-timeline-loader"; @@ -16,7 +16,7 @@ export default function UserArticlesTab() { const timeline = useTimelineLoader(pubkey + "-articles", readRelays, { authors: [pubkey], - kinds: [Kind.Article], + kinds: [kinds.LongFormArticle], }); const articles = useSubject(timeline.timeline); diff --git a/src/views/user/dms.tsx b/src/views/user/dms.tsx index 56defb1a0..6ba242680 100644 --- a/src/views/user/dms.tsx +++ b/src/views/user/dms.tsx @@ -1,6 +1,6 @@ import { useRef } from "react"; import { Flex, Text } from "@chakra-ui/react"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { useOutletContext } from "react-router-dom"; import useTimelineLoader from "../../hooks/use-timeline-loader"; @@ -69,9 +69,9 @@ export default function UserDMsTab() { const timeline = useTimelineLoader(pubkey + "-articles", readRelays, [ { authors: [pubkey], - kinds: [Kind.EncryptedDirectMessage], + kinds: [kinds.EncryptedDirectMessage], }, - { "#p": [pubkey], kinds: [Kind.EncryptedDirectMessage] }, + { "#p": [pubkey], kinds: [kinds.EncryptedDirectMessage] }, ]); const dms = useSubject(timeline.timeline); diff --git a/src/views/user/followers.tsx b/src/views/user/followers.tsx index e741269dc..c46d9be4c 100644 --- a/src/views/user/followers.tsx +++ b/src/views/user/followers.tsx @@ -1,6 +1,6 @@ import { Flex, SimpleGrid } from "@chakra-ui/react"; import { useOutletContext } from "react-router-dom"; -import { Event, Kind } from "nostr-tools"; +import { Event, kinds } from "nostr-tools"; import { useReadRelayUrls } from "../../hooks/use-client-relays"; import useTimelineLoader from "../../hooks/use-timeline-loader"; @@ -33,7 +33,7 @@ export default function UserFollowersTab() { const timeline = useTimelineLoader(`${pubkey}-followers`, readRelays, { "#p": [pubkey], - kinds: [Kind.Contacts], + kinds: [kinds.Contacts], }); const lists = useSubject(timeline.timeline); diff --git a/src/views/user/index.tsx b/src/views/user/index.tsx index 54427dced..1ddf9e400 100644 --- a/src/views/user/index.tsx +++ b/src/views/user/index.tsx @@ -25,12 +25,11 @@ import { Tabs, useDisclosure, } from "@chakra-ui/react"; -import { Kind, nip19 } from "nostr-tools"; +import { kinds } from "nostr-tools"; -import { Outlet, useMatches, useNavigate, useParams } from "react-router-dom"; +import { Outlet, useMatches, useNavigate } from "react-router-dom"; import { useUserMetadata } from "../../hooks/use-user-metadata"; import { getUserDisplayName } from "../../helpers/user-metadata"; -import { isHexKey } from "../../helpers/nip19"; import { useAppTitle } from "../../hooks/use-app-title"; import { useReadRelayUrls } from "../../hooks/use-client-relays"; import relayScoreboardService from "../../services/relay-scoreboard"; @@ -98,7 +97,7 @@ const UserView = () => { "wss://relay.stemstr.app", ...readRelays, ]); - const hasArticles = useEventExists({ kinds: [Kind.Article], authors: [pubkey] }, readRelays); + const hasArticles = useEventExists({ kinds: [kinds.LongFormArticle], authors: [pubkey] }, readRelays); const hasStreams = useEventExists({ kinds: [STREAM_KIND], authors: [pubkey] }, [ "wss://relay.snort.social", "wss://nos.lol", diff --git a/src/views/user/lists.tsx b/src/views/user/lists.tsx index 22ed5249c..5b46593da 100644 --- a/src/views/user/lists.tsx +++ b/src/views/user/lists.tsx @@ -1,6 +1,7 @@ import { useCallback } from "react"; import { useOutletContext } from "react-router-dom"; -import { Divider, Heading, SimpleGrid } from "@chakra-ui/react"; +import { Heading, SimpleGrid } from "@chakra-ui/react"; +import { kinds } from "nostr-tools"; import { useAdditionalRelayContext } from "../../providers/local/additional-relay-context"; import useTimelineLoader from "../../hooks/use-timeline-loader"; @@ -17,7 +18,6 @@ import { getEventUID } from "../../helpers/nostr/events"; import ListCard from "../lists/components/list-card"; import IntersectionObserverProvider from "../../providers/local/intersection-observer"; import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback"; -import { Kind } from "nostr-tools"; import VerticalPageLayout from "../../components/vertical-page-layout"; import { NostrEvent } from "../../types/nostr-event"; import UserName from "../../components/user-name"; @@ -59,7 +59,7 @@ export default function UserListsTab() { Special lists - + diff --git a/src/views/user/notes.tsx b/src/views/user/notes.tsx index 9cadcb853..3111e785c 100644 --- a/src/views/user/notes.tsx +++ b/src/views/user/notes.tsx @@ -1,7 +1,7 @@ import { useCallback } from "react"; import { Flex, Spacer } from "@chakra-ui/react"; import { useOutletContext } from "react-router-dom"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { isReply, isRepost, truncatedId } from "../../helpers/nostr/events"; import { useAdditionalRelayContext } from "../../providers/local/additional-relay-context"; @@ -35,7 +35,7 @@ export default function UserNotesTab() { readRelays, { authors: [pubkey], - kinds: [Kind.Text, Kind.Repost, Kind.Article, STREAM_KIND, 2], + kinds: [kinds.ShortTextNote, kinds.Repost, kinds.LongFormArticle, STREAM_KIND, 2], }, { eventFilter }, ); diff --git a/src/views/user/reports.tsx b/src/views/user/reports.tsx index 86f6a5caf..283d10d56 100644 --- a/src/views/user/reports.tsx +++ b/src/views/user/reports.tsx @@ -1,7 +1,7 @@ import { useRef } from "react"; import { Flex, Text } from "@chakra-ui/react"; import { useOutletContext } from "react-router-dom"; -import { Kind } from "nostr-tools"; +import { kinds } from "nostr-tools"; import { NoteLink } from "../../components/note-link"; import UserLink from "../../components/user-link"; @@ -55,11 +55,11 @@ export default function UserReportsTab() { const timeline = useTimelineLoader(`${pubkey}-reports`, contextRelays, [ { authors: [pubkey], - kinds: [Kind.Report], + kinds: [kinds.Report], }, { "#p": [pubkey], - kinds: [Kind.Report], + kinds: [kinds.Report], }, ]); diff --git a/yarn.lock b/yarn.lock index b18ab1c99..5ed32b54d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2376,6 +2376,13 @@ dependencies: "@noble/hashes" "1.3.1" +"@noble/curves@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== + dependencies: + "@noble/hashes" "1.3.2" + "@noble/curves@^1.0.0", "@noble/curves@~1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.3.0.tgz#01be46da4fd195822dab821e72f71bf4aeec635e" @@ -2388,6 +2395,11 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== +"@noble/hashes@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== + "@noble/hashes@1.3.3", "@noble/hashes@^1.3.2", "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.1", "@noble/hashes@~1.3.2": version "1.3.3" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" @@ -5250,6 +5262,14 @@ normalize-package-data@^2.5.0: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" +nostr-idb@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/nostr-idb/-/nostr-idb-0.2.0.tgz#50b74b078333d187be871c3d9086dfaebfa92183" + integrity sha512-BLqLemCzGR88Wa2gVPobmsdWondpDvbMwSgPsCjZlvflQNXpmCXCN1xJbq0j+RyC88guciW3D6yj7+477xg/9g== + dependencies: + idb "^8.0.0" + nostr-tools "^1.17.0" + nostr-tools@^1.17.0: version "1.17.0" resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-1.17.0.tgz#b6f62e32fedfd9e68ec0a7ce57f74c44fc768e8c" @@ -5262,6 +5282,25 @@ nostr-tools@^1.17.0: "@scure/bip32" "1.3.1" "@scure/bip39" "1.2.1" +nostr-tools@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-2.1.3.tgz#424a0dcf329862163ec8914b7c0139f3bc26d70f" + integrity sha512-WqfX4A9aVJhyO2Mu4sL0YqjnGRu9hfSKWPjO3WU4lcdhVkDB2EYoHtwIYQoJEYCjGlyMIlZpVoiTn+eHGeJQSQ== + dependencies: + "@noble/ciphers" "0.2.0" + "@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@v0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/nostr-wasm/-/nostr-wasm-0.1.0.tgz#17af486745feb2b7dd29503fdd81613a24058d94" + integrity sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA== + nth-check@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d"