mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-10 12:49:29 +02:00
Merge branch 'full-cache' into next
This commit is contained in:
commit
57bb9bb1dd
5
.changeset/great-terms-flash.md
Normal file
5
.changeset/great-terms-flash.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Improve display of unknown events
|
5
.changeset/thick-bobcats-sing.md
Normal file
5
.changeset/thick-bobcats-sing.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": patch
|
||||
---
|
||||
|
||||
Upgrade nostr-tools to v2
|
@ -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:
|
||||
|
@ -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",
|
||||
|
@ -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";
|
||||
|
@ -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<string, RelayBlockLoader>();
|
||||
|
||||
@ -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<string, Filter[]> = {};
|
||||
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 })),
|
||||
|
@ -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 (
|
||||
|
@ -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<MouseEventHandler>(
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
expand.onToggle();
|
||||
},
|
||||
[expand.onToggle],
|
||||
);
|
||||
|
||||
if (isETag(tag) && tag[1]) {
|
||||
const pointer = eTagToEventPointer(tag);
|
||||
return (
|
||||
<>
|
||||
<Link as={RouterLink} to={`/l/${nip19.neventEncode(pointer)}`} onClick={toggle} {...props}>
|
||||
{content}
|
||||
</Link>
|
||||
{expand.isOpen && <EmbedEventPointer pointer={{ type: "nevent", data: pointer }} />}
|
||||
</>
|
||||
);
|
||||
} else if (isATag(tag) && tag[1]) {
|
||||
const pointer = aTagToAddressPointer(tag);
|
||||
return (
|
||||
<>
|
||||
<Link as={RouterLink} to={`/l/${nip19.naddrEncode(pointer)}`} onClick={toggle} {...props}>
|
||||
{content}
|
||||
</Link>
|
||||
{expand.isOpen && <EmbedEventPointer pointer={{ type: "naddr", data: pointer }} />}
|
||||
</>
|
||||
);
|
||||
} else if (isPTag(tag) && tag[1]) {
|
||||
const pubkey = tag[1];
|
||||
return (
|
||||
<>
|
||||
<Link as={RouterLink} to={`/l/${nip19.npubEncode(pubkey)}`} onClick={toggle} {...props}>
|
||||
{content}
|
||||
</Link>
|
||||
{expand.isOpen && (
|
||||
<Flex gap="4" p="2">
|
||||
<UserAvatarLink pubkey={pubkey} />
|
||||
<Box>
|
||||
<UserLink pubkey={pubkey} fontWeight="bold" />
|
||||
<br />
|
||||
<UserDnsIdentityIcon pubkey={pubkey} />
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
} else
|
||||
return (
|
||||
<Text title={content} {...props}>
|
||||
{content}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
export default function EmbeddedUnknown({ event, ...props }: Omit<CardProps, "children"> & { event: NostrEvent }) {
|
||||
const debugModal = useDisclosure();
|
||||
@ -29,16 +111,15 @@ export default function EmbeddedUnknown({ event, ...props }: Omit<CardProps, "ch
|
||||
|
||||
const alt = event.tags.find((t) => 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<CardProps, "ch
|
||||
<UserAvatarLink pubkey={event.pubkey} size="xs" />
|
||||
<UserLink pubkey={event.pubkey} isTruncated fontWeight="bold" fontSize="md" />
|
||||
<UserDnsIdentityIcon pubkey={event.pubkey} onlyIcon />
|
||||
<Link ml="auto" href={address ? buildAppSelectUrl(address) : ""} isExternal>
|
||||
<Timestamp timestamp={event.created_at} />
|
||||
</Link>
|
||||
<Text>kind: {event.kind}</Text>
|
||||
<Timestamp timestamp={event.created_at} />
|
||||
<ButtonGroup ml="auto">
|
||||
<Button
|
||||
as={Link}
|
||||
size="sm"
|
||||
leftIcon={<ExternalLinkIcon />}
|
||||
isExternal
|
||||
href={address ? buildAppSelectUrl(address) : ""}
|
||||
>
|
||||
Open
|
||||
</Button>
|
||||
<IconButton
|
||||
icon={<CodeIcon />}
|
||||
aria-label="Raw Event"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={debugModal.onOpen}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</CardHeader>
|
||||
<CardBody p="2">
|
||||
<Flex gap="2">
|
||||
<Text>Kind: {event.kind}</Text>
|
||||
<Link href={address ? buildAppSelectUrl(address) : ""} isExternal color="blue.500">
|
||||
{address && truncatedId(address)}
|
||||
</Link>
|
||||
<Button leftIcon={<CodeIcon />} ml="auto" size="sm" variant="outline" onClick={debugModal.onOpen}>
|
||||
View Raw
|
||||
</Button>
|
||||
</Flex>
|
||||
<Box whiteSpace="pre-wrap" noOfLines={5}>
|
||||
{alt && (
|
||||
<Text isTruncated fontStyle="italic">
|
||||
{alt}
|
||||
</Text>
|
||||
)}
|
||||
<Box whiteSpace="pre-wrap" noOfLines={3}>
|
||||
{content}
|
||||
</Box>
|
||||
<Flex direction="column" gap="1" px="2" my="2">
|
||||
{event.tags.map((tag, i) => (
|
||||
<EventTag key={i} tag={tag} />
|
||||
))}
|
||||
</Flex>
|
||||
</CardBody>
|
||||
</Card>
|
||||
{debugModal.isOpen && <NoteDebugModal isOpen={debugModal.isOpen} onClose={debugModal.onClose} event={event} />}
|
||||
|
@ -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<CardProps, "children"> & { event: NostrEvent } & EmbedProps) {
|
||||
switch (event.kind) {
|
||||
case Kind.Text:
|
||||
return <EmbeddedNote event={event} {...cardProps} />;
|
||||
case Kind.Reaction:
|
||||
return <EmbeddedReaction event={event} {...cardProps} />;
|
||||
case Kind.EncryptedDirectMessage:
|
||||
return <EmbeddedDM dm={event} {...cardProps} />;
|
||||
case STREAM_KIND:
|
||||
return <EmbeddedStream event={event} {...cardProps} />;
|
||||
case GOAL_KIND:
|
||||
return <EmbeddedGoal goal={event} {...cardProps} {...goalProps} />;
|
||||
case EMOJI_PACK_KIND:
|
||||
return <EmbeddedEmojiPack pack={event} {...cardProps} />;
|
||||
case PEOPLE_LIST_KIND:
|
||||
case NOTE_LIST_KIND:
|
||||
case BOOKMARK_LIST_KIND:
|
||||
case COMMUNITIES_LIST_KIND:
|
||||
case CHANNELS_LIST_KIND:
|
||||
return <EmbeddedList list={event} {...cardProps} />;
|
||||
case Kind.Article:
|
||||
return <EmbeddedArticle article={event} {...cardProps} />;
|
||||
case Kind.BadgeDefinition:
|
||||
return <EmbeddedBadge badge={event} {...cardProps} />;
|
||||
case STREAM_CHAT_MESSAGE_KIND:
|
||||
return <EmbeddedStreamMessage message={event} {...cardProps} />;
|
||||
case COMMUNITY_DEFINITION_KIND:
|
||||
return <EmbeddedCommunity community={event} {...cardProps} />;
|
||||
case STEMSTR_TRACK_KIND:
|
||||
return <EmbeddedStemstrTrack track={event} {...cardProps} />;
|
||||
case TORRENT_KIND:
|
||||
return <EmbeddedTorrent torrent={event} {...cardProps} />;
|
||||
case TORRENT_COMMENT_KIND:
|
||||
return <EmbeddedTorrentComment comment={event} {...cardProps} />;
|
||||
case FLARE_VIDEO_KIND:
|
||||
return <EmbeddedFlareVideo video={event} {...cardProps} />;
|
||||
case Kind.ChannelCreation:
|
||||
return <EmbeddedChannel channel={event} {...cardProps} />;
|
||||
}
|
||||
const renderContent = () => {
|
||||
switch (event.kind) {
|
||||
case kinds.ShortTextNote:
|
||||
return <EmbeddedNote event={event} {...cardProps} />;
|
||||
case kinds.Reaction:
|
||||
return <EmbeddedReaction event={event} {...cardProps} />;
|
||||
case kinds.EncryptedDirectMessage:
|
||||
return <EmbeddedDM dm={event} {...cardProps} />;
|
||||
case STREAM_KIND:
|
||||
return <EmbeddedStream event={event} {...cardProps} />;
|
||||
case GOAL_KIND:
|
||||
return <EmbeddedGoal goal={event} {...cardProps} {...goalProps} />;
|
||||
case EMOJI_PACK_KIND:
|
||||
return <EmbeddedEmojiPack pack={event} {...cardProps} />;
|
||||
case PEOPLE_LIST_KIND:
|
||||
case NOTE_LIST_KIND:
|
||||
case BOOKMARK_LIST_KIND:
|
||||
case COMMUNITIES_LIST_KIND:
|
||||
case CHANNELS_LIST_KIND:
|
||||
return <EmbeddedList list={event} {...cardProps} />;
|
||||
case kinds.LongFormArticle:
|
||||
return <EmbeddedArticle article={event} {...cardProps} />;
|
||||
case kinds.BadgeDefinition:
|
||||
return <EmbeddedBadge badge={event} {...cardProps} />;
|
||||
case STREAM_CHAT_MESSAGE_KIND:
|
||||
return <EmbeddedStreamMessage message={event} {...cardProps} />;
|
||||
case COMMUNITY_DEFINITION_KIND:
|
||||
return <EmbeddedCommunity community={event} {...cardProps} />;
|
||||
case STEMSTR_TRACK_KIND:
|
||||
return <EmbeddedStemstrTrack track={event} {...cardProps} />;
|
||||
case TORRENT_KIND:
|
||||
return <EmbeddedTorrent torrent={event} {...cardProps} />;
|
||||
case TORRENT_COMMENT_KIND:
|
||||
return <EmbeddedTorrentComment comment={event} {...cardProps} />;
|
||||
case FLARE_VIDEO_KIND:
|
||||
return <EmbeddedFlareVideo video={event} {...cardProps} />;
|
||||
case kinds.ChannelCreation:
|
||||
return <EmbeddedChannel channel={event} {...cardProps} />;
|
||||
}
|
||||
|
||||
return <EmbeddedUnknown event={event} {...cardProps} />;
|
||||
return <EmbeddedUnknown event={event} {...cardProps} />;
|
||||
};
|
||||
|
||||
return <Suspense fallback={<Spinner />}>{renderContent()}</Suspense>;
|
||||
}
|
||||
|
||||
export function EmbedEventPointer({ pointer, ...props }: { pointer: DecodeResult } & EmbedProps) {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 <VerificationFailed color="red.500" />;
|
||||
}
|
||||
return <CheckIcon color="green.500" />;
|
||||
|
@ -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: [
|
||||
|
@ -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<number, FlexProps["bg"]> = {
|
||||
[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<FlexProps, "children">) {
|
||||
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<FlexProps,
|
||||
|
||||
const getTitle = () => {
|
||||
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";
|
||||
}
|
||||
};
|
||||
|
@ -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";
|
||||
|
@ -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(),
|
||||
|
@ -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(),
|
||||
});
|
||||
|
@ -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 }) {
|
||||
</Flex>
|
||||
{!note ? (
|
||||
<SkeletonText />
|
||||
) : 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
|
||||
<Note event={note} showReplyButton registerIntersectionEntity={false} />
|
||||
) : (
|
||||
|
@ -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) ? <ReplyNote event={event} /> : <Note event={event} showReplyButton />;
|
||||
break;
|
||||
case Kind.Repost:
|
||||
case kinds.Repost:
|
||||
content = <RepostNote event={event} />;
|
||||
break;
|
||||
case Kind.Article:
|
||||
case kinds.LongFormArticle:
|
||||
content = <ArticleNote article={event} />;
|
||||
break;
|
||||
case STREAM_KIND:
|
||||
content = <StreamNote event={event} />;
|
||||
break;
|
||||
case Kind.RecommendRelay:
|
||||
case kinds.RecommendRelay:
|
||||
content = <RelayRecommendation event={event} />;
|
||||
break;
|
||||
case Kind.BadgeAward:
|
||||
case kinds.BadgeAward:
|
||||
content = <BadgeAwardCard award={event} />;
|
||||
break;
|
||||
case FLARE_VIDEO_KIND:
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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];
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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(),
|
||||
|
@ -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<st
|
||||
|
||||
return filtersByRelay;
|
||||
}
|
||||
|
||||
export function relayRequest(relay: SimpleRelay, filters: Filter[], opts: SimpleSubscriptionOptions = {}) {
|
||||
return new Promise<NostrEvent[]>((res) => {
|
||||
const events: NostrEvent[] = [];
|
||||
const sub: SimpleSubscription = relay.subscribe(filters, {
|
||||
...opts,
|
||||
onevent: (e) => events.push(e),
|
||||
oneose: () => {
|
||||
sub.close();
|
||||
res(events);
|
||||
},
|
||||
onclose: () => res(events),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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("|")]);
|
||||
|
||||
|
@ -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 }[] = [];
|
||||
|
@ -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 },
|
||||
|
@ -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 },
|
||||
|
@ -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]);
|
||||
|
@ -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(),
|
||||
|
@ -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<N
|
||||
if (!isHex(sig)) throw new Error("Expected hex signature");
|
||||
|
||||
const event: NostrEvent = { ...draftWithId, sig };
|
||||
if (!verifySignature(event)) throw new Error("Invalid signature");
|
||||
if (!verifyEvent(event)) throw new Error("Invalid signature");
|
||||
return event;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import dayjs from "dayjs";
|
||||
import debug, { Debugger } from "debug";
|
||||
import _throttle from "lodash/throttle";
|
||||
import { Kind } from "nostr-tools";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import NostrSubscription from "../classes/nostr-subscription";
|
||||
import SuperMap from "../classes/super-map";
|
||||
@ -12,7 +12,7 @@ import { logger } from "../helpers/debug";
|
||||
import db from "./db";
|
||||
import createDefer, { Deferred } from "../classes/deferred";
|
||||
import { getChannelPointer } from "../helpers/nostr/channel";
|
||||
import localCacheRelayService, { LOCAL_CACHE_RELAY } from "./local-cache-relay";
|
||||
import { LOCAL_CACHE_RELAY, LOCAL_CACHE_RELAY_ENABLED } from "./local-cache-relay";
|
||||
|
||||
type Pubkey = string;
|
||||
type Relay = string;
|
||||
@ -105,7 +105,7 @@ class ChannelMetadataRelayLoader {
|
||||
if (needsUpdate) {
|
||||
if (this.requested.size > 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) {
|
||||
|
@ -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<SchemaV6>(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();
|
||||
}
|
||||
|
||||
|
@ -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<NostrEvent>();
|
||||
|
||||
function handleEvent(deleteEvent: NostrEvent) {
|
||||
if (deleteEvent.kind !== Kind.EventDeletion) return;
|
||||
if (deleteEvent.kind !== kinds.EventDeletion) return;
|
||||
deleteEventStream.next(deleteEvent);
|
||||
}
|
||||
|
||||
|
@ -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)) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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<string>();
|
||||
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;
|
||||
|
@ -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<T extends NostrConnectMethod>(
|
||||
|
@ -255,7 +255,9 @@ class RelayScoreboardService {
|
||||
|
||||
const relayScoreboardService = new RelayScoreboardService();
|
||||
|
||||
relayScoreboardService.loadStats();
|
||||
setTimeout(() => {
|
||||
relayScoreboardService.loadStats();
|
||||
}, 0);
|
||||
|
||||
setInterval(() => {
|
||||
relayScoreboardService.saveStats();
|
||||
|
@ -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);
|
||||
|
@ -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<string, string>();
|
||||
|
||||
@ -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":
|
||||
|
@ -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<string, Subject<NostrEvent>>(() => new Subject());
|
||||
pending = new Map<string, string[]>();
|
||||
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<string, string[]> = {};
|
||||
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);
|
||||
|
@ -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)));
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)));
|
||||
}
|
||||
|
||||
|
@ -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<NostrToolsNostrEvent, "tags"> & {
|
||||
tags: Tag[];
|
||||
content: string;
|
||||
sig: string;
|
||||
};
|
||||
export type CountResponse = {
|
||||
count: number;
|
||||
|
@ -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 <Spinner />;
|
||||
|
@ -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);
|
||||
|
@ -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<CardProps, "children"> & { badge: NostrEvent }) {
|
||||
const naddr = getSharableEventAddress(badge);
|
||||
@ -23,7 +23,7 @@ function BadgeCard({ badge, ...props }: Omit<CardProps, "children"> & { badge: N
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
useRegisterIntersectionEntity(ref, getEventUID(badge));
|
||||
|
||||
const timesAwarded = useEventCount({ kinds: [Kind.BadgeAward], "#a": [getEventCoordinate(badge)] });
|
||||
const timesAwarded = useEventCount({ kinds: [kinds.BadgeAward], "#a": [getEventCoordinate(badge)] });
|
||||
|
||||
return (
|
||||
<Card ref={ref} variant="outline" {...props}>
|
||||
|
@ -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 },
|
||||
);
|
||||
|
@ -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 },
|
||||
|
@ -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(),
|
||||
|
@ -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);
|
||||
|
@ -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 });
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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],
|
||||
});
|
||||
|
||||
|
@ -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<CardProps, "children"> & CommunityPostPropTypes) {
|
||||
switch (event.kind) {
|
||||
case Kind.Text:
|
||||
case kinds.ShortTextNote:
|
||||
return <CommunityTextPost event={event} {...props} />;
|
||||
case Kind.Repost:
|
||||
case kinds.Repost:
|
||||
return <CommunityRepostPost event={event} {...props} />;
|
||||
}
|
||||
return null;
|
||||
|
@ -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],
|
||||
},
|
||||
|
@ -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(),
|
||||
|
@ -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" });
|
||||
|
@ -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 <Navigate to={`/emojis/${cleanLink}`} replace />;
|
||||
if (decoded.data.kind === NOTE_LIST_KIND) return <Navigate to={`/lists/${cleanLink}`} replace />;
|
||||
if (decoded.data.kind === PEOPLE_LIST_KIND) return <Navigate to={`/lists/${cleanLink}`} replace />;
|
||||
if (decoded.data.kind === Kind.BadgeDefinition) return <Navigate to={`/badges/${cleanLink}`} replace />;
|
||||
if (decoded.data.kind === kinds.BadgeDefinition) return <Navigate to={`/badges/${cleanLink}`} replace />;
|
||||
if (decoded.data.kind === COMMUNITY_DEFINITION_KIND) return <Navigate to={`/c/${cleanLink}`} replace />;
|
||||
if (decoded.data.kind === FLARE_VIDEO_KIND) return <Navigate to={`/videos/${cleanLink}`} replace />;
|
||||
if (decoded.data.kind === Kind.ChannelCreation) return <Navigate to={`/channels/${cleanLink}`} replace />;
|
||||
if (decoded.data.kind === Kind.Text) return <Navigate to={`/n/${cleanLink}`} replace />;
|
||||
if (decoded.data.kind === kinds.ChannelCreation) return <Navigate to={`/channels/${cleanLink}`} replace />;
|
||||
if (decoded.data.kind === kinds.ShortTextNote) return <Navigate to={`/n/${cleanLink}`} replace />;
|
||||
// if there is no kind redirect to the thread view
|
||||
return <Navigate to={`/n/${cleanLink}`} replace />;
|
||||
}
|
||||
|
@ -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<CardProps, "children">
|
||||
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 (
|
||||
|
@ -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<ButtonProps, "children">) {
|
||||
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;
|
||||
|
||||
|
@ -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
|
||||
</Heading>
|
||||
<SimpleGrid columns={{ base: 1, lg: 2 }} spacing="2">
|
||||
<ListCard cord={`${Kind.Contacts}:${account.pubkey}`} hideCreator />
|
||||
<ListCard cord={`${kinds.Contacts}:${account.pubkey}`} hideCreator />
|
||||
<ListCard cord={`${MUTE_LIST_KIND}:${account.pubkey}`} hideCreator />
|
||||
<ListCard cord={`${PIN_LIST_KIND}:${account.pubkey}`} hideCreator />
|
||||
<ListCard cord={`${COMMUNITIES_LIST_KIND}:${account.pubkey}`} hideCreator />
|
||||
|
@ -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 (
|
||||
|
@ -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(() => {
|
||||
|
@ -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 <Note event={event} variant={focused ? "elevated" : undefined} />;
|
||||
case STREAM_KIND:
|
||||
return <StreamNote event={event} />;
|
||||
|
@ -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;
|
||||
}),
|
||||
|
@ -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<HTMLDivElement, { event: NostrEvent }>((
|
||||
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 (
|
||||
<NotificationIconEntry ref={ref} icon={<Heart boxSize={8} color="red.400" />}>
|
||||
@ -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 = <NoteNotification event={event} ref={ref} />;
|
||||
break;
|
||||
case Kind.Reaction:
|
||||
case kinds.Reaction:
|
||||
content = <ReactionNotification event={event} ref={ref} />;
|
||||
break;
|
||||
case Kind.Repost:
|
||||
case kinds.Repost:
|
||||
content = <RepostNotification event={event} ref={ref} />;
|
||||
break;
|
||||
case Kind.Zap:
|
||||
case kinds.Zap:
|
||||
content = <ZapNotification event={event} ref={ref} />;
|
||||
break;
|
||||
default:
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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) ?? [];
|
||||
|
@ -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 (
|
||||
<>
|
||||
<Text>{replaceableEventCount} cached replaceable events</Text>
|
||||
<Text>{relayInfoCount} cached relay info</Text>
|
||||
<Text>{nip05Count} cached NIP-05 IDs</Text>
|
||||
{estimatedStorage ? (
|
||||
<Text>
|
||||
{niceBytes(estimatedStorage?.usage ?? 0)} / {niceBytes(estimatedStorage?.quota ?? 0)} Used
|
||||
</Text>
|
||||
) : null}
|
||||
<Text>{count} cached events</Text>
|
||||
<Text>
|
||||
{Object.entries(kinds || {})
|
||||
.map(([kind, count]) => `${kind} (${count})`)
|
||||
.join(", ")}
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -82,7 +60,7 @@ export default function DatabaseSettings() {
|
||||
</h2>
|
||||
<AccordionPanel>
|
||||
<DatabaseStats />
|
||||
<ButtonGroup>
|
||||
<ButtonGroup mt="2">
|
||||
<Button onClick={handleClearData} isLoading={clearing} isDisabled={clearing}>
|
||||
Clear cache data
|
||||
</Button>
|
||||
|
@ -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 });
|
||||
|
@ -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("");
|
||||
|
@ -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" });
|
||||
}
|
||||
|
@ -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";
|
||||
|
@ -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;
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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 },
|
||||
);
|
||||
|
||||
|
@ -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,
|
||||
});
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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 (
|
||||
<Accordion allowMultiple>
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
</Heading>
|
||||
<SimpleGrid columns={{ base: 1, lg: 2, xl: 3 }} spacing="2">
|
||||
<ListCard cord={`${Kind.Contacts}:${pubkey}`} hideCreator />
|
||||
<ListCard cord={`${kinds.Contacts}:${pubkey}`} hideCreator />
|
||||
<ListCard cord={`${MUTE_LIST_KIND}:${pubkey}`} hideCreator />
|
||||
<ListCard cord={`${PIN_LIST_KIND}:${pubkey}`} hideCreator />
|
||||
<ListCard cord={`${BOOKMARK_LIST_KIND}:${pubkey}`} hideCreator />
|
||||
|
@ -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 },
|
||||
);
|
||||
|
@ -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],
|
||||
},
|
||||
]);
|
||||
|
||||
|
39
yarn.lock
39
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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user