diff --git a/packages/app/package.json b/packages/app/package.json index 1d68eff0..d14e6a8d 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -12,7 +12,6 @@ "@reduxjs/toolkit": "^1.9.1", "@scure/bip32": "^1.3.0", "@scure/bip39": "^1.1.1", - "@snort/nostr": "^1.0.0", "@szhsin/react-menu": "^3.3.1", "@void-cat/api": "^1.0.4", "base32-decode": "^1.0.0", diff --git a/packages/app/src/Cache/DMCache.ts b/packages/app/src/Cache/DMCache.ts index a147559f..cacfd379 100644 --- a/packages/app/src/Cache/DMCache.ts +++ b/packages/app/src/Cache/DMCache.ts @@ -1,4 +1,4 @@ -import { RawEvent } from "@snort/nostr"; +import { RawEvent } from "System"; import { db } from "Db"; import FeedCache from "./FeedCache"; diff --git a/packages/app/src/Cache/index.ts b/packages/app/src/Cache/index.ts index f5f13a68..02941f3d 100644 --- a/packages/app/src/Cache/index.ts +++ b/packages/app/src/Cache/index.ts @@ -1,4 +1,4 @@ -import { HexKey, RawEvent, UserMetadata } from "@snort/nostr"; +import { HexKey, RawEvent, UserMetadata } from "System"; import { hexToBech32, unixNowMs } from "SnortUtils"; import { DmCache } from "./DMCache"; import { InteractionCache } from "./EventInteractionCache"; diff --git a/packages/app/src/Const.ts b/packages/app/src/Const.ts index 5c287e5b..e16d2262 100644 --- a/packages/app/src/Const.ts +++ b/packages/app/src/Const.ts @@ -1,4 +1,6 @@ -import { RelaySettings } from "@snort/nostr"; +import { UserRelays } from "Cache/UserRelayCache"; +import { NostrSystem, RelaySettings } from "System"; +import { ProfileLoaderService } from "System/ProfileCache"; /** * Add-on api for snort features diff --git a/packages/app/src/Db/index.ts b/packages/app/src/Db/index.ts index df0fe111..25f0e788 100644 --- a/packages/app/src/Db/index.ts +++ b/packages/app/src/Db/index.ts @@ -1,5 +1,5 @@ import Dexie, { Table } from "dexie"; -import { FullRelaySettings, HexKey, RawEvent, u256 } from "@snort/nostr"; +import { FullRelaySettings, HexKey, RawEvent, u256 } from "System"; import { MetadataCache } from "Cache"; export const NAME = "snortDB"; diff --git a/packages/app/src/Element/Avatar.tsx b/packages/app/src/Element/Avatar.tsx index 04a0fa0d..576ef169 100644 --- a/packages/app/src/Element/Avatar.tsx +++ b/packages/app/src/Element/Avatar.tsx @@ -2,7 +2,7 @@ import "./Avatar.css"; import Nostrich from "nostrich.webp"; import { CSSProperties, useEffect, useState } from "react"; -import type { UserMetadata } from "@snort/nostr"; +import type { UserMetadata } from "System"; import useImgProxy from "Hooks/useImgProxy"; diff --git a/packages/app/src/Element/BadgeList.tsx b/packages/app/src/Element/BadgeList.tsx index 83b494f1..e007b685 100644 --- a/packages/app/src/Element/BadgeList.tsx +++ b/packages/app/src/Element/BadgeList.tsx @@ -3,7 +3,7 @@ import "./BadgeList.css"; import { useState } from "react"; import { FormattedMessage } from "react-intl"; -import { TaggedRawEvent } from "@snort/nostr"; +import { TaggedRawEvent } from "System"; import { ProxyImg } from "Element/ProxyImg"; import Icon from "Icons/Icon"; diff --git a/packages/app/src/Element/BlockButton.tsx b/packages/app/src/Element/BlockButton.tsx index e908192d..10c4859f 100644 --- a/packages/app/src/Element/BlockButton.tsx +++ b/packages/app/src/Element/BlockButton.tsx @@ -1,5 +1,5 @@ import { FormattedMessage } from "react-intl"; -import { HexKey } from "@snort/nostr"; +import { HexKey } from "System"; import useModeration from "Hooks/useModeration"; import messages from "./messages"; diff --git a/packages/app/src/Element/Bookmarks.tsx b/packages/app/src/Element/Bookmarks.tsx index dda75eec..04680f64 100644 --- a/packages/app/src/Element/Bookmarks.tsx +++ b/packages/app/src/Element/Bookmarks.tsx @@ -1,6 +1,6 @@ import { useState, useMemo, ChangeEvent } from "react"; import { FormattedMessage } from "react-intl"; -import { HexKey, TaggedRawEvent } from "@snort/nostr"; +import { HexKey, TaggedRawEvent } from "System"; import Note from "Element/Note"; import useLogin from "Hooks/useLogin"; diff --git a/packages/app/src/Element/DM.tsx b/packages/app/src/Element/DM.tsx index b893075d..de058fee 100644 --- a/packages/app/src/Element/DM.tsx +++ b/packages/app/src/Element/DM.tsx @@ -2,7 +2,7 @@ import "./DM.css"; import { useEffect, useState } from "react"; import { useIntl } from "react-intl"; import { useInView } from "react-intersection-observer"; -import { TaggedRawEvent } from "@snort/nostr"; +import { TaggedRawEvent } from "System"; import useEventPublisher from "Feed/EventPublisher"; import NoteTime from "Element/NoteTime"; diff --git a/packages/app/src/Element/DmWindow.tsx b/packages/app/src/Element/DmWindow.tsx index 2347e571..827984b7 100644 --- a/packages/app/src/Element/DmWindow.tsx +++ b/packages/app/src/Element/DmWindow.tsx @@ -1,6 +1,6 @@ import "./DmWindow.css"; import { useEffect, useMemo, useRef } from "react"; -import { TaggedRawEvent } from "@snort/nostr"; +import { TaggedRawEvent } from "System"; import ProfileImage from "Element/ProfileImage"; import DM from "Element/DM"; diff --git a/packages/app/src/Element/FollowButton.tsx b/packages/app/src/Element/FollowButton.tsx index eb20789c..e284bc83 100644 --- a/packages/app/src/Element/FollowButton.tsx +++ b/packages/app/src/Element/FollowButton.tsx @@ -1,6 +1,6 @@ import "./FollowButton.css"; import { FormattedMessage } from "react-intl"; -import { HexKey } from "@snort/nostr"; +import { HexKey } from "System"; import useEventPublisher from "Feed/EventPublisher"; import { parseId } from "SnortUtils"; diff --git a/packages/app/src/Element/FollowListBase.tsx b/packages/app/src/Element/FollowListBase.tsx index 122f6c4d..e98ef131 100644 --- a/packages/app/src/Element/FollowListBase.tsx +++ b/packages/app/src/Element/FollowListBase.tsx @@ -1,6 +1,6 @@ import { ReactNode } from "react"; import { FormattedMessage } from "react-intl"; -import { HexKey } from "@snort/nostr"; +import { HexKey } from "System"; import useEventPublisher from "Feed/EventPublisher"; import ProfilePreview from "Element/ProfilePreview"; diff --git a/packages/app/src/Element/Mention.tsx b/packages/app/src/Element/Mention.tsx index 63783f01..b7a8af60 100644 --- a/packages/app/src/Element/Mention.tsx +++ b/packages/app/src/Element/Mention.tsx @@ -1,6 +1,6 @@ import { useMemo } from "react"; import { Link } from "react-router-dom"; -import { HexKey } from "@snort/nostr"; +import { HexKey } from "System"; import { useUserProfile } from "Hooks/useUserProfile"; import { profileLink } from "SnortUtils"; diff --git a/packages/app/src/Element/MuteButton.tsx b/packages/app/src/Element/MuteButton.tsx index 7a8d05eb..7f8d492f 100644 --- a/packages/app/src/Element/MuteButton.tsx +++ b/packages/app/src/Element/MuteButton.tsx @@ -1,5 +1,5 @@ import { FormattedMessage } from "react-intl"; -import { HexKey } from "@snort/nostr"; +import { HexKey } from "System"; import useModeration from "Hooks/useModeration"; import messages from "./messages"; diff --git a/packages/app/src/Element/MutedList.tsx b/packages/app/src/Element/MutedList.tsx index 4616182a..6fa9044c 100644 --- a/packages/app/src/Element/MutedList.tsx +++ b/packages/app/src/Element/MutedList.tsx @@ -1,5 +1,5 @@ import { FormattedMessage } from "react-intl"; -import { HexKey } from "@snort/nostr"; +import { HexKey } from "System"; import MuteButton from "Element/MuteButton"; import ProfilePreview from "Element/ProfilePreview"; import useModeration from "Hooks/useModeration"; diff --git a/packages/app/src/Element/Nip05.tsx b/packages/app/src/Element/Nip05.tsx index 4d1fb286..e3e14170 100644 --- a/packages/app/src/Element/Nip05.tsx +++ b/packages/app/src/Element/Nip05.tsx @@ -1,5 +1,5 @@ import "./Nip05.css"; -import { HexKey } from "@snort/nostr"; +import { HexKey } from "System"; import Icon from "Icons/Icon"; import { useUserProfile } from "Hooks/useUserProfile"; diff --git a/packages/app/src/Element/Nip5Service.tsx b/packages/app/src/Element/Nip5Service.tsx index 7bc93d3e..122c249b 100644 --- a/packages/app/src/Element/Nip5Service.tsx +++ b/packages/app/src/Element/Nip5Service.tsx @@ -1,7 +1,7 @@ import { useEffect, useMemo, useState, ChangeEvent } from "react"; import { useIntl, FormattedMessage } from "react-intl"; import { useNavigate } from "react-router-dom"; -import { UserMetadata } from "@snort/nostr"; +import { UserMetadata } from "System"; import { unwrap } from "SnortUtils"; import { formatShort } from "Number"; diff --git a/packages/app/src/Element/NostrFileHeader.tsx b/packages/app/src/Element/NostrFileHeader.tsx index 277d71da..49531b0c 100644 --- a/packages/app/src/Element/NostrFileHeader.tsx +++ b/packages/app/src/Element/NostrFileHeader.tsx @@ -1,5 +1,5 @@ import { FormattedMessage } from "react-intl"; -import { RawEvent } from "@snort/nostr"; +import { RawEvent } from "System"; import { findTag, NostrLink } from "SnortUtils"; import useEventFeed from "Feed/EventFeed"; diff --git a/packages/app/src/Element/NostrLink.tsx b/packages/app/src/Element/NostrLink.tsx index d87abcec..64efbde8 100644 --- a/packages/app/src/Element/NostrLink.tsx +++ b/packages/app/src/Element/NostrLink.tsx @@ -1,5 +1,5 @@ import { Link } from "react-router-dom"; -import { NostrPrefix } from "@snort/nostr"; +import { NostrPrefix } from "System"; import Mention from "Element/Mention"; import { parseNostrLink } from "SnortUtils"; diff --git a/packages/app/src/Element/Note.tsx b/packages/app/src/Element/Note.tsx index 03f4f9d0..5a582f6a 100644 --- a/packages/app/src/Element/Note.tsx +++ b/packages/app/src/Element/Note.tsx @@ -3,7 +3,7 @@ import React, { useMemo, useState, useLayoutEffect, ReactNode } from "react"; import { useNavigate, Link } from "react-router-dom"; import { useInView } from "react-intersection-observer"; import { useIntl, FormattedMessage } from "react-intl"; -import { TaggedRawEvent, HexKey, EventKind, NostrPrefix, Lists } from "@snort/nostr"; +import { TaggedRawEvent, HexKey, EventKind, NostrPrefix, Lists } from "System"; import useEventPublisher from "Feed/EventPublisher"; import Icon from "Icons/Icon"; diff --git a/packages/app/src/Element/NoteCreator.tsx b/packages/app/src/Element/NoteCreator.tsx index df08f85f..6a4a2a4a 100644 --- a/packages/app/src/Element/NoteCreator.tsx +++ b/packages/app/src/Element/NoteCreator.tsx @@ -1,7 +1,7 @@ import "./NoteCreator.css"; import { FormattedMessage, useIntl } from "react-intl"; import { useDispatch, useSelector } from "react-redux"; -import { encodeTLV, EventKind, NostrPrefix, TaggedRawEvent } from "@snort/nostr"; +import { encodeTLV, EventKind, NostrPrefix, TaggedRawEvent } from "System"; import Icon from "Icons/Icon"; import useEventPublisher from "Feed/EventPublisher"; diff --git a/packages/app/src/Element/NoteFooter.tsx b/packages/app/src/Element/NoteFooter.tsx index 6347f37d..25fef378 100644 --- a/packages/app/src/Element/NoteFooter.tsx +++ b/packages/app/src/Element/NoteFooter.tsx @@ -3,7 +3,7 @@ import { useSelector, useDispatch } from "react-redux"; import { useIntl, FormattedMessage } from "react-intl"; import { Menu, MenuItem } from "@szhsin/react-menu"; import { useLongPress } from "use-long-press"; -import { TaggedRawEvent, HexKey, u256, encodeTLV, NostrPrefix, Lists } from "@snort/nostr"; +import { TaggedRawEvent, HexKey, u256, encodeTLV, NostrPrefix, Lists } from "System"; import Icon from "Icons/Icon"; import Spinner from "Icons/Spinner"; diff --git a/packages/app/src/Element/NoteReaction.tsx b/packages/app/src/Element/NoteReaction.tsx index aee50f87..236b3772 100644 --- a/packages/app/src/Element/NoteReaction.tsx +++ b/packages/app/src/Element/NoteReaction.tsx @@ -1,7 +1,7 @@ import "./NoteReaction.css"; import { Link } from "react-router-dom"; import { useMemo } from "react"; -import { EventKind, RawEvent, TaggedRawEvent, NostrPrefix } from "@snort/nostr"; +import { EventKind, RawEvent, TaggedRawEvent, NostrPrefix } from "System"; import Note from "Element/Note"; import ProfileImage from "Element/ProfileImage"; diff --git a/packages/app/src/Element/Poll.tsx b/packages/app/src/Element/Poll.tsx index cb44e29b..bb29730e 100644 --- a/packages/app/src/Element/Poll.tsx +++ b/packages/app/src/Element/Poll.tsx @@ -1,4 +1,4 @@ -import { TaggedRawEvent } from "@snort/nostr"; +import { TaggedRawEvent } from "System"; import { useState } from "react"; import { FormattedMessage, FormattedNumber, useIntl } from "react-intl"; diff --git a/packages/app/src/Element/ProfileImage.tsx b/packages/app/src/Element/ProfileImage.tsx index 184e5e0a..96c5e15a 100644 --- a/packages/app/src/Element/ProfileImage.tsx +++ b/packages/app/src/Element/ProfileImage.tsx @@ -1,7 +1,7 @@ import "./ProfileImage.css"; import React, { useMemo } from "react"; -import { HexKey, NostrPrefix } from "@snort/nostr"; +import { HexKey, NostrPrefix } from "System"; import { useUserProfile } from "Hooks/useUserProfile"; import { hexToBech32, profileLink } from "SnortUtils"; diff --git a/packages/app/src/Element/ProfilePreview.tsx b/packages/app/src/Element/ProfilePreview.tsx index 0d927bb2..15f02c05 100644 --- a/packages/app/src/Element/ProfilePreview.tsx +++ b/packages/app/src/Element/ProfilePreview.tsx @@ -4,7 +4,7 @@ import { ReactNode } from "react"; import ProfileImage from "Element/ProfileImage"; import FollowButton from "Element/FollowButton"; import { useUserProfile } from "Hooks/useUserProfile"; -import { HexKey } from "@snort/nostr"; +import { HexKey } from "System"; import { useInView } from "react-intersection-observer"; export interface ProfilePreviewProps { diff --git a/packages/app/src/Element/PubkeyList.tsx b/packages/app/src/Element/PubkeyList.tsx index 5ef6fa39..17a3b161 100644 --- a/packages/app/src/Element/PubkeyList.tsx +++ b/packages/app/src/Element/PubkeyList.tsx @@ -1,4 +1,4 @@ -import { RawEvent } from "@snort/nostr"; +import { RawEvent } from "System"; import { dedupe } from "SnortUtils"; import FollowListBase from "./FollowListBase"; diff --git a/packages/app/src/Element/Reactions.tsx b/packages/app/src/Element/Reactions.tsx index ab8340f7..2dcfbe30 100644 --- a/packages/app/src/Element/Reactions.tsx +++ b/packages/app/src/Element/Reactions.tsx @@ -2,7 +2,7 @@ import "./Reactions.css"; import { useState, useMemo, useEffect } from "react"; import { useIntl, FormattedMessage } from "react-intl"; -import { TaggedRawEvent } from "@snort/nostr"; +import { TaggedRawEvent } from "System"; import { formatShort } from "Number"; import Icon from "Icons/Icon"; diff --git a/packages/app/src/Element/Relay.tsx b/packages/app/src/Element/Relay.tsx index fcc2b912..e4779802 100644 --- a/packages/app/src/Element/Relay.tsx +++ b/packages/app/src/Element/Relay.tsx @@ -2,10 +2,10 @@ import "./Relay.css"; import { useMemo } from "react"; import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router-dom"; -import { RelaySettings } from "@snort/nostr"; +import { RelaySettings } from "System"; import useRelayState from "Feed/RelayState"; -import { System } from "System"; +import { System } from "index"; import { getRelayName, unixNowMs, unwrap } from "SnortUtils"; import useLogin from "Hooks/useLogin"; import { setRelays } from "Login"; diff --git a/packages/app/src/Element/RelaysMetadata.tsx b/packages/app/src/Element/RelaysMetadata.tsx index 4152c677..8973a428 100644 --- a/packages/app/src/Element/RelaysMetadata.tsx +++ b/packages/app/src/Element/RelaysMetadata.tsx @@ -2,7 +2,7 @@ import "./RelaysMetadata.css"; import Nostrich from "nostrich.webp"; import { useState } from "react"; -import { FullRelaySettings } from "@snort/nostr"; +import { FullRelaySettings } from "System"; import Icon from "Icons/Icon"; const RelayFavicon = ({ url }: { url: string }) => { diff --git a/packages/app/src/Element/SendSats.tsx b/packages/app/src/Element/SendSats.tsx index cbb3dde2..93d8e240 100644 --- a/packages/app/src/Element/SendSats.tsx +++ b/packages/app/src/Element/SendSats.tsx @@ -1,8 +1,9 @@ import "./SendSats.css"; import React, { useEffect, useMemo, useState } from "react"; import { useIntl, FormattedMessage } from "react-intl"; -import { HexKey, RawEvent } from "@snort/nostr"; +import { HexKey, RawEvent } from "System"; +import { System } from "index"; import { formatShort } from "Number"; import Icon from "Icons/Icon"; import useEventPublisher from "Feed/EventPublisher"; @@ -133,7 +134,7 @@ export default function SendSats(props: SendSatsProps) { const randomKey = generateRandomKey(); console.debug("Generated new key for zap: ", randomKey); - const publisher = new EventPublisher(randomKey.publicKey, randomKey.privateKey); + const publisher = new EventPublisher(System, randomKey.publicKey, randomKey.privateKey); zap = await publisher.zap(amount * 1000, author, relays, note, comment, eb => eb.tag(["anon", ""])); } else { zap = await publisher.zap(amount * 1000, author, relays, note, comment); diff --git a/packages/app/src/Element/SubDebug.tsx b/packages/app/src/Element/SubDebug.tsx index db948ca6..66982fba 100644 --- a/packages/app/src/Element/SubDebug.tsx +++ b/packages/app/src/Element/SubDebug.tsx @@ -3,11 +3,11 @@ import { useState } from "react"; import useRelayState from "Feed/RelayState"; import Tabs, { Tab } from "Element/Tabs"; -import { System } from "System"; import { unwrap } from "SnortUtils"; import useSystemState from "Hooks/useSystemState"; -import { RawReqFilter } from "@snort/nostr"; +import { RawReqFilter } from "System"; import { useCopy } from "useCopy"; +import { System } from "index"; function RelayInfo({ id }: { id: string }) { const state = useRelayState(id); diff --git a/packages/app/src/Element/SuggestedProfiles.tsx b/packages/app/src/Element/SuggestedProfiles.tsx index 5449260d..5cb06808 100644 --- a/packages/app/src/Element/SuggestedProfiles.tsx +++ b/packages/app/src/Element/SuggestedProfiles.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { HexKey, NostrPrefix } from "@snort/nostr"; +import { HexKey, NostrPrefix } from "System"; import { FormattedMessage } from "react-intl"; import FollowListBase from "Element/FollowListBase"; diff --git a/packages/app/src/Element/Text.tsx b/packages/app/src/Element/Text.tsx index 22ae3cf7..89ede775 100644 --- a/packages/app/src/Element/Text.tsx +++ b/packages/app/src/Element/Text.tsx @@ -1,7 +1,7 @@ import "./Text.css"; import { useMemo } from "react"; import { Link, useLocation } from "react-router-dom"; -import { HexKey, NostrPrefix } from "@snort/nostr"; +import { HexKey, NostrPrefix } from "System"; import { MentionRegex, InvoiceRegex, HashtagRegex, CashuRegex } from "Const"; import { eventLink, hexToBech32, splitByUrl, validateNostrLink } from "SnortUtils"; diff --git a/packages/app/src/Element/Textarea.tsx b/packages/app/src/Element/Textarea.tsx index ed22224a..c92c497e 100644 --- a/packages/app/src/Element/Textarea.tsx +++ b/packages/app/src/Element/Textarea.tsx @@ -4,7 +4,7 @@ import "./Textarea.css"; import { useIntl } from "react-intl"; import ReactTextareaAutocomplete from "@webscopeio/react-textarea-autocomplete"; import TextareaAutosize from "react-textarea-autosize"; -import { NostrPrefix } from "@snort/nostr"; +import { NostrPrefix } from "System"; import Avatar from "Element/Avatar"; import Nip05 from "Element/Nip05"; diff --git a/packages/app/src/Element/Thread.tsx b/packages/app/src/Element/Thread.tsx index 8013d027..2d252382 100644 --- a/packages/app/src/Element/Thread.tsx +++ b/packages/app/src/Element/Thread.tsx @@ -2,7 +2,7 @@ import "./Thread.css"; import { useMemo, useState, ReactNode } from "react"; import { useIntl } from "react-intl"; import { useNavigate, useLocation, Link, useParams } from "react-router-dom"; -import { TaggedRawEvent, u256, EventKind, NostrPrefix } from "@snort/nostr"; +import { TaggedRawEvent, u256, EventKind, NostrPrefix } from "System"; import { EventExt, Thread as ThreadInfo } from "System/EventExt"; import { eventLink, unwrap, getReactions, parseNostrLink, getAllReactions, findTag } from "SnortUtils"; diff --git a/packages/app/src/Element/Timeline.tsx b/packages/app/src/Element/Timeline.tsx index af28e335..1159b1b9 100644 --- a/packages/app/src/Element/Timeline.tsx +++ b/packages/app/src/Element/Timeline.tsx @@ -2,7 +2,7 @@ import "./Timeline.css"; import { FormattedMessage } from "react-intl"; import { useCallback, useMemo } from "react"; import { useInView } from "react-intersection-observer"; -import { TaggedRawEvent, EventKind, u256 } from "@snort/nostr"; +import { TaggedRawEvent, EventKind, u256 } from "System"; import Icon from "Icons/Icon"; import { dedupeByPubkey, findTag, tagFilterOfTextRepost } from "SnortUtils"; diff --git a/packages/app/src/Element/TrendingPosts.tsx b/packages/app/src/Element/TrendingPosts.tsx index b2434498..98ceee3d 100644 --- a/packages/app/src/Element/TrendingPosts.tsx +++ b/packages/app/src/Element/TrendingPosts.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { RawEvent, TaggedRawEvent } from "@snort/nostr"; +import { RawEvent, TaggedRawEvent } from "System"; import { FormattedMessage } from "react-intl"; import PageSpinner from "Element/PageSpinner"; diff --git a/packages/app/src/Element/TrendingUsers.tsx b/packages/app/src/Element/TrendingUsers.tsx index 477cca1f..9460b8ab 100644 --- a/packages/app/src/Element/TrendingUsers.tsx +++ b/packages/app/src/Element/TrendingUsers.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { HexKey } from "@snort/nostr"; +import { HexKey } from "System"; import { FormattedMessage } from "react-intl"; import FollowListBase from "Element/FollowListBase"; diff --git a/packages/app/src/Element/Username.tsx b/packages/app/src/Element/Username.tsx index 76b5078f..47a12ca7 100644 --- a/packages/app/src/Element/Username.tsx +++ b/packages/app/src/Element/Username.tsx @@ -1,7 +1,7 @@ import { MouseEvent } from "react"; import { useNavigate, Link } from "react-router-dom"; -import { HexKey } from "@snort/nostr"; +import { HexKey } from "System"; import { useUserProfile } from "Hooks/useUserProfile"; import { profileLink } from "SnortUtils"; diff --git a/packages/app/src/Element/WriteDm.tsx b/packages/app/src/Element/WriteDm.tsx index 37228edb..4a594f2e 100644 --- a/packages/app/src/Element/WriteDm.tsx +++ b/packages/app/src/Element/WriteDm.tsx @@ -1,4 +1,4 @@ -import { encodeTLV, NostrPrefix, RawEvent } from "@snort/nostr"; +import { encodeTLV, NostrPrefix, RawEvent } from "System"; import useEventPublisher from "Feed/EventPublisher"; import Icon from "Icons/Icon"; import Spinner from "Icons/Spinner"; diff --git a/packages/app/src/Element/Zap.tsx b/packages/app/src/Element/Zap.tsx index 4916ccaf..6a84212b 100644 --- a/packages/app/src/Element/Zap.tsx +++ b/packages/app/src/Element/Zap.tsx @@ -1,7 +1,7 @@ import "./Zap.css"; import { useMemo } from "react"; import { FormattedMessage, useIntl } from "react-intl"; -import { HexKey, TaggedRawEvent } from "@snort/nostr"; +import { HexKey, TaggedRawEvent } from "System"; import { decodeInvoice, InvoiceDetails, sha256, unwrap } from "SnortUtils"; import { formatShort } from "Number"; diff --git a/packages/app/src/Element/ZapButton.tsx b/packages/app/src/Element/ZapButton.tsx index 4d7cdd1a..ef460d7d 100644 --- a/packages/app/src/Element/ZapButton.tsx +++ b/packages/app/src/Element/ZapButton.tsx @@ -1,6 +1,6 @@ import "./ZapButton.css"; import { useState } from "react"; -import { HexKey } from "@snort/nostr"; +import { HexKey } from "System"; import { useUserProfile } from "Hooks/useUserProfile"; import SendSats from "Element/SendSats"; diff --git a/packages/app/src/Element/ZapstrEmbed.tsx b/packages/app/src/Element/ZapstrEmbed.tsx index b8cb0e0c..84068e39 100644 --- a/packages/app/src/Element/ZapstrEmbed.tsx +++ b/packages/app/src/Element/ZapstrEmbed.tsx @@ -1,6 +1,6 @@ import "./ZapstrEmbed.css"; import { Link } from "react-router-dom"; -import { encodeTLV, NostrPrefix, RawEvent } from "@snort/nostr"; +import { encodeTLV, NostrPrefix, RawEvent } from "System"; import { ProxyImg } from "Element/ProxyImg"; import ProfileImage from "Element/ProfileImage"; diff --git a/packages/app/src/External/NostrBand.ts b/packages/app/src/External/NostrBand.ts index 930edeb0..eeb00c9a 100644 --- a/packages/app/src/External/NostrBand.ts +++ b/packages/app/src/External/NostrBand.ts @@ -1,4 +1,4 @@ -import { RawEvent } from "@snort/nostr"; +import { RawEvent } from "System"; export interface TrendingUser { pubkey: string; diff --git a/packages/app/src/Feed/BadgesFeed.ts b/packages/app/src/Feed/BadgesFeed.ts index 194150ac..baf0bacf 100644 --- a/packages/app/src/Feed/BadgesFeed.ts +++ b/packages/app/src/Feed/BadgesFeed.ts @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import { EventKind, HexKey, Lists } from "@snort/nostr"; +import { EventKind, HexKey, Lists } from "System"; import { unwrap, findTag, chunks } from "SnortUtils"; import { RequestBuilder } from "System"; diff --git a/packages/app/src/Feed/BookmarkFeed.tsx b/packages/app/src/Feed/BookmarkFeed.tsx index 3412a75a..647cd5b0 100644 --- a/packages/app/src/Feed/BookmarkFeed.tsx +++ b/packages/app/src/Feed/BookmarkFeed.tsx @@ -1,4 +1,4 @@ -import { HexKey, Lists } from "@snort/nostr"; +import { HexKey, Lists } from "System"; import useNotelistSubscription from "Hooks/useNotelistSubscription"; import useLogin from "Hooks/useLogin"; diff --git a/packages/app/src/Feed/EventFeed.ts b/packages/app/src/Feed/EventFeed.ts index fca62014..6ee13f7a 100644 --- a/packages/app/src/Feed/EventFeed.ts +++ b/packages/app/src/Feed/EventFeed.ts @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import { NostrPrefix } from "@snort/nostr"; +import { NostrPrefix } from "System"; import useRequestBuilder from "Hooks/useRequestBuilder"; import { RequestBuilder, ReplaceableNoteStore } from "System"; diff --git a/packages/app/src/Feed/EventPublisher.ts b/packages/app/src/Feed/EventPublisher.ts index 475ae29d..e2eef45c 100644 --- a/packages/app/src/Feed/EventPublisher.ts +++ b/packages/app/src/Feed/EventPublisher.ts @@ -1,12 +1,13 @@ import { useMemo } from "react"; import useLogin from "Hooks/useLogin"; import { EventPublisher } from "System/EventPublisher"; +import { System } from "index"; export default function useEventPublisher() { const { publicKey, privateKey } = useLogin(); return useMemo(() => { if (publicKey) { - return new EventPublisher(publicKey, privateKey); + return new EventPublisher(System, publicKey, privateKey); } }, [publicKey, privateKey]); } diff --git a/packages/app/src/Feed/FollowersFeed.ts b/packages/app/src/Feed/FollowersFeed.ts index 6acc1ec4..e8095bce 100644 --- a/packages/app/src/Feed/FollowersFeed.ts +++ b/packages/app/src/Feed/FollowersFeed.ts @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import { HexKey, EventKind } from "@snort/nostr"; +import { HexKey, EventKind } from "System"; import { PubkeyReplaceableNoteStore, RequestBuilder } from "System"; import useRequestBuilder from "Hooks/useRequestBuilder"; diff --git a/packages/app/src/Feed/FollowsFeed.ts b/packages/app/src/Feed/FollowsFeed.ts index 499af525..b20bef55 100644 --- a/packages/app/src/Feed/FollowsFeed.ts +++ b/packages/app/src/Feed/FollowsFeed.ts @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import { HexKey, TaggedRawEvent, EventKind } from "@snort/nostr"; +import { HexKey, TaggedRawEvent, EventKind } from "System"; import { PubkeyReplaceableNoteStore, RequestBuilder } from "System"; import useRequestBuilder from "Hooks/useRequestBuilder"; diff --git a/packages/app/src/Feed/LoginFeed.ts b/packages/app/src/Feed/LoginFeed.ts index 9f329067..bf214d7d 100644 --- a/packages/app/src/Feed/LoginFeed.ts +++ b/packages/app/src/Feed/LoginFeed.ts @@ -1,5 +1,5 @@ import { useEffect, useMemo } from "react"; -import { TaggedRawEvent, Lists, EventKind } from "@snort/nostr"; +import { TaggedRawEvent, Lists, EventKind } from "System"; import debug from "debug"; import { bech32ToHex, getNewest, getNewestEventTagsByKey, unwrap } from "SnortUtils"; diff --git a/packages/app/src/Feed/MuteList.ts b/packages/app/src/Feed/MuteList.ts index d7ac989e..3338c496 100644 --- a/packages/app/src/Feed/MuteList.ts +++ b/packages/app/src/Feed/MuteList.ts @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import { HexKey, TaggedRawEvent, Lists, EventKind } from "@snort/nostr"; +import { HexKey, TaggedRawEvent, Lists, EventKind } from "System"; import { getNewest } from "SnortUtils"; import { ParameterizedReplaceableNoteStore, RequestBuilder } from "System"; diff --git a/packages/app/src/Feed/PinnedFeed.tsx b/packages/app/src/Feed/PinnedFeed.tsx index f8c6b5af..f55fe40d 100644 --- a/packages/app/src/Feed/PinnedFeed.tsx +++ b/packages/app/src/Feed/PinnedFeed.tsx @@ -1,4 +1,4 @@ -import { HexKey, Lists } from "@snort/nostr"; +import { HexKey, Lists } from "System"; import useNotelistSubscription from "Hooks/useNotelistSubscription"; import useLogin from "Hooks/useLogin"; diff --git a/packages/app/src/Feed/RelayState.ts b/packages/app/src/Feed/RelayState.ts index beef8218..830ccb66 100644 --- a/packages/app/src/Feed/RelayState.ts +++ b/packages/app/src/Feed/RelayState.ts @@ -1,6 +1,6 @@ import { useSyncExternalStore } from "react"; -import { StateSnapshot } from "@snort/nostr"; -import { System } from "System"; +import { StateSnapshot } from "System"; +import { System } from "index"; const noop = () => { return () => undefined; diff --git a/packages/app/src/Feed/RelaysFeed.tsx b/packages/app/src/Feed/RelaysFeed.tsx index 4d6d30cb..677f5586 100644 --- a/packages/app/src/Feed/RelaysFeed.tsx +++ b/packages/app/src/Feed/RelaysFeed.tsx @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import { HexKey, FullRelaySettings, EventKind } from "@snort/nostr"; +import { HexKey, FullRelaySettings, EventKind } from "System"; import { RequestBuilder } from "System"; import { ReplaceableNoteStore } from "System/NoteCollection"; diff --git a/packages/app/src/Feed/RelaysFeedFollows.tsx b/packages/app/src/Feed/RelaysFeedFollows.tsx index 42566875..128fe11f 100644 --- a/packages/app/src/Feed/RelaysFeedFollows.tsx +++ b/packages/app/src/Feed/RelaysFeedFollows.tsx @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import { HexKey, FullRelaySettings, TaggedRawEvent, RelaySettings, EventKind } from "@snort/nostr"; +import { HexKey, FullRelaySettings, TaggedRawEvent, RelaySettings, EventKind } from "System"; import debug from "debug"; import { sanitizeRelayUrl } from "SnortUtils"; diff --git a/packages/app/src/Feed/ThreadFeed.ts b/packages/app/src/Feed/ThreadFeed.ts index f7a41f7a..adbc14f9 100644 --- a/packages/app/src/Feed/ThreadFeed.ts +++ b/packages/app/src/Feed/ThreadFeed.ts @@ -1,5 +1,5 @@ import { useEffect, useMemo, useState } from "react"; -import { u256, EventKind } from "@snort/nostr"; +import { u256, EventKind } from "System"; import { appendDedupe, NostrLink } from "SnortUtils"; import { FlatNoteStore, RequestBuilder } from "System"; diff --git a/packages/app/src/Feed/TimelineFeed.ts b/packages/app/src/Feed/TimelineFeed.ts index f4edf359..8fb8693f 100644 --- a/packages/app/src/Feed/TimelineFeed.ts +++ b/packages/app/src/Feed/TimelineFeed.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect, useMemo, useState } from "react"; -import { EventKind, u256 } from "@snort/nostr"; +import { EventKind, u256 } from "System"; import { unixNow, unwrap, tagFilterOfTextRepost } from "SnortUtils"; import { FlatNoteStore, RequestBuilder } from "System"; diff --git a/packages/app/src/Feed/ZapsFeed.ts b/packages/app/src/Feed/ZapsFeed.ts index 38819589..88694a5e 100644 --- a/packages/app/src/Feed/ZapsFeed.ts +++ b/packages/app/src/Feed/ZapsFeed.ts @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import { HexKey, EventKind } from "@snort/nostr"; +import { HexKey, EventKind } from "System"; import { parseZap } from "Element/Zap"; import { FlatNoteStore, RequestBuilder } from "System"; diff --git a/packages/app/src/Hooks/useInteractionCache.tsx b/packages/app/src/Hooks/useInteractionCache.tsx index 999331e2..b75e9bf0 100644 --- a/packages/app/src/Hooks/useInteractionCache.tsx +++ b/packages/app/src/Hooks/useInteractionCache.tsx @@ -1,5 +1,5 @@ import { useSyncExternalStore } from "react"; -import { HexKey, u256 } from "@snort/nostr"; +import { HexKey, u256 } from "System"; import { InteractionCache } from "Cache/EventInteractionCache"; import { EventInteraction } from "Db"; diff --git a/packages/app/src/Hooks/useModeration.tsx b/packages/app/src/Hooks/useModeration.tsx index f4532240..2cbd3c7a 100644 --- a/packages/app/src/Hooks/useModeration.tsx +++ b/packages/app/src/Hooks/useModeration.tsx @@ -1,4 +1,4 @@ -import { HexKey } from "@snort/nostr"; +import { HexKey } from "System"; import useEventPublisher from "Feed/EventPublisher"; import useLogin from "Hooks/useLogin"; import { setBlocked, setMuted } from "Login"; diff --git a/packages/app/src/Hooks/useNotelistSubscription.ts b/packages/app/src/Hooks/useNotelistSubscription.ts index 5e82d6a6..55aa7f95 100644 --- a/packages/app/src/Hooks/useNotelistSubscription.ts +++ b/packages/app/src/Hooks/useNotelistSubscription.ts @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import { HexKey, Lists, EventKind } from "@snort/nostr"; +import { HexKey, Lists, EventKind } from "System"; import { FlatNoteStore, ParameterizedReplaceableNoteStore, RequestBuilder } from "System"; import useRequestBuilder from "Hooks/useRequestBuilder"; diff --git a/packages/app/src/Hooks/useRequestBuilder.tsx b/packages/app/src/Hooks/useRequestBuilder.tsx index cf8ae450..70d28030 100644 --- a/packages/app/src/Hooks/useRequestBuilder.tsx +++ b/packages/app/src/Hooks/useRequestBuilder.tsx @@ -1,7 +1,8 @@ import { useSyncExternalStore } from "react"; -import { RequestBuilder, System } from "System"; +import { RequestBuilder } from "System"; import { EmptySnapshot, NoteStore, StoreSnapshot } from "System/NoteCollection"; import { unwrap } from "SnortUtils"; +import { System } from "index"; const useRequestBuilder = >( type: { new (): TStore }, diff --git a/packages/app/src/Hooks/useSystemState.tsx b/packages/app/src/Hooks/useSystemState.tsx index a9aa8b94..971675be 100644 --- a/packages/app/src/Hooks/useSystemState.tsx +++ b/packages/app/src/Hooks/useSystemState.tsx @@ -1,5 +1,6 @@ import { useSyncExternalStore } from "react"; -import { System, SystemSnapshot } from "System"; +import { SystemSnapshot } from "System"; +import { System } from "index"; export default function useSystemState() { return useSyncExternalStore( diff --git a/packages/app/src/Hooks/useUserProfile.ts b/packages/app/src/Hooks/useUserProfile.ts index f04245a6..42d68310 100644 --- a/packages/app/src/Hooks/useUserProfile.ts +++ b/packages/app/src/Hooks/useUserProfile.ts @@ -1,9 +1,9 @@ import { useEffect, useSyncExternalStore } from "react"; -import { HexKey } from "@snort/nostr"; +import { HexKey } from "System"; import { MetadataCache } from "Cache"; import { UserCache } from "Cache/UserCache"; -import { ProfileLoader } from "System/ProfileCache"; +import { ProfileLoader } from "index"; export function useUserProfile(pubKey?: HexKey): MetadataCache | undefined { const user = useSyncExternalStore( diff --git a/packages/app/src/LNURL.ts b/packages/app/src/LNURL.ts index 1c3c3e01..9a0e75d7 100644 --- a/packages/app/src/LNURL.ts +++ b/packages/app/src/LNURL.ts @@ -1,4 +1,4 @@ -import { HexKey, RawEvent } from "@snort/nostr"; +import { HexKey, RawEvent } from "System"; import { EmailRegex } from "Const"; import { bech32ToText, unwrap } from "SnortUtils"; diff --git a/packages/app/src/Login/Functions.ts b/packages/app/src/Login/Functions.ts index 112b3fbd..ac9c37f2 100644 --- a/packages/app/src/Login/Functions.ts +++ b/packages/app/src/Login/Functions.ts @@ -1,4 +1,4 @@ -import { HexKey, RelaySettings } from "@snort/nostr"; +import { HexKey, RelaySettings } from "System"; import * as secp from "@noble/curves/secp256k1"; import * as utils from "@noble/curves/abstract/utils"; @@ -8,6 +8,7 @@ import { generateBip39Entropy, entropyToPrivateKey } from "nip6"; import { bech32ToHex, dedupeById, randomSample, sanitizeRelayUrl, unixNowMs, unwrap } from "SnortUtils"; import { SubscriptionEvent } from "Subscription"; import { EventPublisher } from "System/EventPublisher"; +import { System } from "index"; export function setRelays(state: LoginSession, relays: Record, createdAt: number) { if (state.relays.timestamp >= createdAt) { @@ -78,7 +79,7 @@ export async function generateNewLogin() { } const publicKey = utils.bytesToHex(secp.schnorr.getPublicKey(privateKey)); - const publisher = new EventPublisher(publicKey, privateKey); + const publisher = new EventPublisher(System, publicKey, privateKey); const ev = await publisher.contactList([bech32ToHex(SnortPubKey), publicKey], newRelays); publisher.broadcast(ev); diff --git a/packages/app/src/Login/LoginSession.ts b/packages/app/src/Login/LoginSession.ts index ab7dd421..95736dd6 100644 --- a/packages/app/src/Login/LoginSession.ts +++ b/packages/app/src/Login/LoginSession.ts @@ -1,4 +1,4 @@ -import { HexKey, RelaySettings, u256 } from "@snort/nostr"; +import { HexKey, RelaySettings, u256 } from "System"; import { UserPreferences } from "Login"; import { SubscriptionEvent } from "Subscription"; diff --git a/packages/app/src/Login/MultiAccountStore.ts b/packages/app/src/Login/MultiAccountStore.ts index a7c5d5be..ddbb4a02 100644 --- a/packages/app/src/Login/MultiAccountStore.ts +++ b/packages/app/src/Login/MultiAccountStore.ts @@ -1,7 +1,7 @@ import * as secp from "@noble/curves/secp256k1"; import * as utils from "@noble/curves/abstract/utils"; -import { HexKey, RelaySettings } from "@snort/nostr"; +import { HexKey, RelaySettings } from "System"; import { DefaultRelays } from "Const"; import ExternalStore from "ExternalStore"; diff --git a/packages/app/src/Nip05/SnortServiceProvider.ts b/packages/app/src/Nip05/SnortServiceProvider.ts index c4d58a1d..fe23ef2a 100644 --- a/packages/app/src/Nip05/SnortServiceProvider.ts +++ b/packages/app/src/Nip05/SnortServiceProvider.ts @@ -1,4 +1,4 @@ -import { EventKind } from "@snort/nostr"; +import { EventKind } from "System"; import { EventPublisher } from "System/EventPublisher"; import { ServiceError, ServiceProvider } from "./ServiceProvider"; diff --git a/packages/app/src/Notifications.ts b/packages/app/src/Notifications.ts index 01a6b746..09360995 100644 --- a/packages/app/src/Notifications.ts +++ b/packages/app/src/Notifications.ts @@ -1,7 +1,6 @@ import Nostrich from "nostrich.webp"; -import { TaggedRawEvent } from "@snort/nostr"; -import { EventKind } from "@snort/nostr"; +import { TaggedRawEvent, EventKind } from "System"; import { MetadataCache } from "Cache"; import { getDisplayName } from "Element/ProfileImage"; import { MentionRegex } from "Const"; diff --git a/packages/app/src/Pages/DonatePage.tsx b/packages/app/src/Pages/DonatePage.tsx index f5334df3..b871f7ac 100644 --- a/packages/app/src/Pages/DonatePage.tsx +++ b/packages/app/src/Pages/DonatePage.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { FormattedMessage } from "react-intl"; -import { HexKey } from "@snort/nostr"; +import { HexKey } from "System"; import { ApiHost, KieranPubKey, SnortPubKey } from "Const"; import ProfilePreview from "Element/ProfilePreview"; diff --git a/packages/app/src/Pages/Layout.tsx b/packages/app/src/Pages/Layout.tsx index 10a5e0e0..76dc35a7 100644 --- a/packages/app/src/Pages/Layout.tsx +++ b/packages/app/src/Pages/Layout.tsx @@ -9,7 +9,7 @@ import messages from "./messages"; import Icon from "Icons/Icon"; import { RootState } from "State/Store"; import { setShow, reset } from "State/NoteCreator"; -import { System } from "System"; +import { System } from "index"; import useLoginFeed from "Feed/LoginFeed"; import { totalUnread } from "Pages/MessagesPage"; import useModeration from "Hooks/useModeration"; diff --git a/packages/app/src/Pages/LoginPage.tsx b/packages/app/src/Pages/LoginPage.tsx index 3f64c537..faf6e957 100644 --- a/packages/app/src/Pages/LoginPage.tsx +++ b/packages/app/src/Pages/LoginPage.tsx @@ -3,7 +3,7 @@ import "./LoginPage.css"; import { CSSProperties, useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import { useIntl, FormattedMessage } from "react-intl"; -import { HexKey } from "@snort/nostr"; +import { HexKey } from "System"; import { bech32ToHex, unwrap } from "SnortUtils"; import ZapButton from "Element/ZapButton"; diff --git a/packages/app/src/Pages/MessagesPage.tsx b/packages/app/src/Pages/MessagesPage.tsx index 15ca8d5f..831fddd3 100644 --- a/packages/app/src/Pages/MessagesPage.tsx +++ b/packages/app/src/Pages/MessagesPage.tsx @@ -1,7 +1,7 @@ import React, { useMemo, useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import { useNavigate } from "react-router-dom"; -import { HexKey, RawEvent, NostrPrefix } from "@snort/nostr"; +import { HexKey, RawEvent, NostrPrefix } from "System"; import UnreadCount from "Element/UnreadCount"; import ProfileImage, { getDisplayName } from "Element/ProfileImage"; diff --git a/packages/app/src/Pages/NostrLinkHandler.tsx b/packages/app/src/Pages/NostrLinkHandler.tsx index 7c977ab6..bc2397e0 100644 --- a/packages/app/src/Pages/NostrLinkHandler.tsx +++ b/packages/app/src/Pages/NostrLinkHandler.tsx @@ -1,4 +1,4 @@ -import { NostrPrefix } from "@snort/nostr"; +import { NostrPrefix } from "System"; import { useEffect, useState } from "react"; import { FormattedMessage } from "react-intl"; import { useNavigate, useParams } from "react-router-dom"; @@ -6,7 +6,7 @@ import { useNavigate, useParams } from "react-router-dom"; import Spinner from "Icons/Spinner"; import { parseNostrLink, profileLink } from "SnortUtils"; import { getNip05PubKey } from "Pages/LoginPage"; -import { System } from "System"; +import { System } from "index"; export default function NostrLinkHandler() { const params = useParams(); diff --git a/packages/app/src/Pages/ProfilePage.tsx b/packages/app/src/Pages/ProfilePage.tsx index c58fc6b7..25adf365 100644 --- a/packages/app/src/Pages/ProfilePage.tsx +++ b/packages/app/src/Pages/ProfilePage.tsx @@ -2,7 +2,7 @@ import "./ProfilePage.css"; import { useEffect, useState } from "react"; import { useIntl, FormattedMessage } from "react-intl"; import { useNavigate, useParams } from "react-router-dom"; -import { encodeTLV, EventKind, HexKey, NostrPrefix } from "@snort/nostr"; +import { encodeTLV, EventKind, HexKey, NostrPrefix } from "System"; import { parseNostrLink, getReactions, unwrap } from "SnortUtils"; import { formatShort } from "Number"; diff --git a/packages/app/src/Pages/Root.tsx b/packages/app/src/Pages/Root.tsx index 2ca863b8..10de36e0 100644 --- a/packages/app/src/Pages/Root.tsx +++ b/packages/app/src/Pages/Root.tsx @@ -5,7 +5,7 @@ import { useIntl, FormattedMessage } from "react-intl"; import Tabs, { Tab } from "Element/Tabs"; import Timeline from "Element/Timeline"; -import { System } from "System"; +import { System } from "index"; import { TimelineSubject } from "Feed/TimelineFeed"; import { debounce, getRelayName, sha256, unixNow, unwrap } from "SnortUtils"; import useLogin from "Hooks/useLogin"; diff --git a/packages/app/src/Pages/SearchPage.tsx b/packages/app/src/Pages/SearchPage.tsx index 7e18cd66..f7268402 100644 --- a/packages/app/src/Pages/SearchPage.tsx +++ b/packages/app/src/Pages/SearchPage.tsx @@ -4,9 +4,8 @@ import Timeline from "Element/Timeline"; import { Tab, TabElement } from "Element/Tabs"; import { useEffect, useState } from "react"; import { debounce } from "SnortUtils"; -import { router } from "index"; +import { System, router } from "index"; import { SearchRelays } from "Const"; -import { System } from "System"; import TrendingUsers from "Element/TrendingUsers"; import TrendingNotes from "Element/TrendingPosts"; diff --git a/packages/app/src/Pages/ZapPool.tsx b/packages/app/src/Pages/ZapPool.tsx index ca753c0d..431d0af3 100644 --- a/packages/app/src/Pages/ZapPool.tsx +++ b/packages/app/src/Pages/ZapPool.tsx @@ -6,13 +6,13 @@ import { FormattedMessage, FormattedNumber } from "react-intl"; import { SnortPubKey } from "Const"; import ProfilePreview from "Element/ProfilePreview"; import useLogin from "Hooks/useLogin"; -import { System } from "System"; import { UploaderServices } from "Upload"; import { bech32ToHex, getRelayName, unwrap } from "SnortUtils"; import { ZapPoolController, ZapPoolRecipient, ZapPoolRecipientType } from "ZapPoolController"; import { useUserProfile } from "Hooks/useUserProfile"; import AsyncButton from "Element/AsyncButton"; import { useWallet } from "Wallet"; +import { System } from "index"; const DataProviders = [ { diff --git a/packages/app/src/Pages/settings/Keys.tsx b/packages/app/src/Pages/settings/Keys.tsx index 6882fdde..d8871bcc 100644 --- a/packages/app/src/Pages/settings/Keys.tsx +++ b/packages/app/src/Pages/settings/Keys.tsx @@ -1,6 +1,6 @@ import "./Keys.css"; import { FormattedMessage } from "react-intl"; -import { encodeTLV, NostrPrefix } from "@snort/nostr"; +import { encodeTLV, NostrPrefix } from "System"; import Copy from "Element/Copy"; import useLogin from "Hooks/useLogin"; diff --git a/packages/app/src/Pages/settings/RelayInfo.tsx b/packages/app/src/Pages/settings/RelayInfo.tsx index a02cca5d..fc6d9a2f 100644 --- a/packages/app/src/Pages/settings/RelayInfo.tsx +++ b/packages/app/src/Pages/settings/RelayInfo.tsx @@ -3,7 +3,7 @@ import ProfilePreview from "Element/ProfilePreview"; import useRelayState from "Feed/RelayState"; import { useNavigate, useParams } from "react-router-dom"; import { parseId, unwrap } from "SnortUtils"; -import { System } from "System"; +import { System } from "index"; import { removeRelay } from "Login"; import useLogin from "Hooks/useLogin"; diff --git a/packages/app/src/Pages/settings/Relays.tsx b/packages/app/src/Pages/settings/Relays.tsx index 0cf5b6b2..52f992bf 100644 --- a/packages/app/src/Pages/settings/Relays.tsx +++ b/packages/app/src/Pages/settings/Relays.tsx @@ -4,7 +4,7 @@ import { FormattedMessage } from "react-intl"; import { randomSample, unixNowMs } from "SnortUtils"; import Relay from "Element/Relay"; import useEventPublisher from "Feed/EventPublisher"; -import { System } from "System"; +import { System } from "index"; import useLogin from "Hooks/useLogin"; import { setRelays } from "Login"; diff --git a/packages/app/src/SnortApi.ts b/packages/app/src/SnortApi.ts index 404e7d78..75dde20a 100644 --- a/packages/app/src/SnortApi.ts +++ b/packages/app/src/SnortApi.ts @@ -1,4 +1,4 @@ -import { EventKind } from "@snort/nostr"; +import { EventKind } from "System"; import { ApiHost } from "Const"; import { SubscriptionType } from "Subscription"; import { EventPublisher } from "System/EventPublisher"; diff --git a/packages/app/src/SnortUtils/Utils.test.ts b/packages/app/src/SnortUtils/Utils.test.ts index 83f10ff1..e40c6979 100644 --- a/packages/app/src/SnortUtils/Utils.test.ts +++ b/packages/app/src/SnortUtils/Utils.test.ts @@ -1,4 +1,4 @@ -import { NostrPrefix } from "@snort/nostr"; +import { NostrPrefix } from "System"; import { parseNostrLink, tryParseNostrLink } from "."; import { splitByUrl, magnetURIDecode, getRelayName } from "."; import { describe, expect } from "@jest/globals"; diff --git a/packages/app/src/SnortUtils/index.ts b/packages/app/src/SnortUtils/index.ts index 7147666f..42980866 100644 --- a/packages/app/src/SnortUtils/index.ts +++ b/packages/app/src/SnortUtils/index.ts @@ -16,7 +16,7 @@ import { decodeTLV, TLVEntryType, RawEvent, -} from "@snort/nostr"; +} from "System"; import { MetadataCache } from "Cache"; import NostrLink from "Element/NostrLink"; diff --git a/packages/app/src/State/NoteCreator.ts b/packages/app/src/State/NoteCreator.ts index 43aaee19..e4941bb6 100644 --- a/packages/app/src/State/NoteCreator.ts +++ b/packages/app/src/State/NoteCreator.ts @@ -1,5 +1,5 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; -import { RawEvent, TaggedRawEvent } from "@snort/nostr"; +import { RawEvent, TaggedRawEvent } from "System"; interface NoteCreatorStore { show: boolean; diff --git a/packages/app/src/State/ReBroadcast.ts b/packages/app/src/State/ReBroadcast.ts index acb1ddec..97b12d57 100644 --- a/packages/app/src/State/ReBroadcast.ts +++ b/packages/app/src/State/ReBroadcast.ts @@ -1,5 +1,5 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; -import { RawEvent } from "@snort/nostr"; +import { RawEvent } from "System"; interface ReBroadcastStore { show: boolean; diff --git a/packages/app/src/System/Connection.ts b/packages/app/src/System/Connection.ts new file mode 100644 index 00000000..56727d58 --- /dev/null +++ b/packages/app/src/System/Connection.ts @@ -0,0 +1,448 @@ +import { v4 as uuid } from "uuid"; + +import { DefaultConnectTimeout } from "./Const"; +import { ConnectionStats } from "./ConnectionStats"; +import { RawEvent, ReqCommand, TaggedRawEvent, u256 } from "./Nostr"; +import { RelayInfo } from "./RelayInfo"; +import { unwrap } from "./Util"; + +export type CustomHook = (state: Readonly) => void; +export type AuthHandler = (challenge: string, relay: string) => Promise; + +/** + * Relay settings + */ +export interface RelaySettings { + read: boolean; + write: boolean; +} + +/** + * Snapshot of connection stats + */ +export interface StateSnapshot { + connected: boolean; + disconnects: number; + avgLatency: number; + events: { + received: number; + send: number; + }; + info?: RelayInfo; + pendingRequests: Array; + activeRequests: Array; + id: string; +} + +export class Connection { + Id: string; + Address: string; + Socket: WebSocket | null = null; + + PendingRaw: Array = []; + PendingRequests: Array<{ + cmd: ReqCommand; + cb: () => void; + }> = []; + ActiveRequests = new Set(); + + Settings: RelaySettings; + Info?: RelayInfo; + ConnectTimeout: number = DefaultConnectTimeout; + Stats: ConnectionStats = new ConnectionStats(); + StateHooks: Map = new Map(); + HasStateChange: boolean = true; + CurrentState: StateSnapshot; + LastState: Readonly; + IsClosed: boolean; + ReconnectTimer: ReturnType | null; + EventsCallback: Map void>; + OnConnected?: () => void; + OnEvent?: (sub: string, e: TaggedRawEvent) => void; + OnEose?: (sub: string) => void; + OnDisconnect?: (id: string) => void; + Auth?: AuthHandler; + AwaitingAuth: Map; + Authed = false; + Ephemeral: boolean; + EphemeralTimeout: ReturnType | undefined; + Down = true; + + constructor(addr: string, options: RelaySettings, auth?: AuthHandler, ephemeral: boolean = false) { + this.Id = uuid(); + this.Address = addr; + this.Settings = options; + this.CurrentState = { + connected: false, + disconnects: 0, + avgLatency: 0, + events: { + received: 0, + send: 0, + }, + } as StateSnapshot; + this.LastState = Object.freeze({ ...this.CurrentState }); + this.IsClosed = false; + this.ReconnectTimer = null; + this.EventsCallback = new Map(); + this.AwaitingAuth = new Map(); + this.Auth = auth; + this.Ephemeral = ephemeral; + } + + ResetEphemeralTimeout() { + if (this.EphemeralTimeout) { + clearTimeout(this.EphemeralTimeout); + } + if (this.Ephemeral) { + this.EphemeralTimeout = setTimeout(() => { + this.Close(); + }, 30_000); + } + } + + async Connect() { + try { + if (this.Info === undefined) { + const u = new URL(this.Address); + const rsp = await fetch(`${u.protocol === "wss:" ? "https:" : "http:"}//${u.host}`, { + headers: { + accept: "application/nostr+json", + }, + }); + if (rsp.ok) { + const data = await rsp.json(); + for (const [k, v] of Object.entries(data)) { + if (v === "unset" || v === "" || v === "~") { + data[k] = undefined; + } + } + this.Info = data; + } + } + } catch (e) { + console.warn("Could not load relay information", e); + } + + if (this.Socket) { + this.Id = uuid(); + this.Socket.onopen = null; + this.Socket.onmessage = null; + this.Socket.onerror = null; + this.Socket.onclose = null; + } + this.IsClosed = false; + this.Socket = new WebSocket(this.Address); + this.Socket.onopen = () => this.OnOpen(); + this.Socket.onmessage = e => this.OnMessage(e); + this.Socket.onerror = e => this.OnError(e); + this.Socket.onclose = e => this.OnClose(e); + } + + Close() { + this.IsClosed = true; + if (this.ReconnectTimer !== null) { + clearTimeout(this.ReconnectTimer); + this.ReconnectTimer = null; + } + this.Socket?.close(); + this.#UpdateState(); + } + + OnOpen() { + this.ConnectTimeout = DefaultConnectTimeout; + console.log(`[${this.Address}] Open!`); + this.Down = false; + if (this.Ephemeral) { + this.ResetEphemeralTimeout(); + } + this.OnConnected?.(); + this.#sendPendingRaw(); + } + + OnClose(e: CloseEvent) { + if (!this.IsClosed) { + this.ConnectTimeout = this.ConnectTimeout * 2; + console.log( + `[${this.Address}] Closed (${e.reason}), trying again in ${(this.ConnectTimeout / 1000) + .toFixed(0) + .toLocaleString()} sec` + ); + this.ReconnectTimer = setTimeout(() => { + this.Connect(); + }, this.ConnectTimeout); + this.Stats.Disconnects++; + } else { + console.log(`[${this.Address}] Closed!`); + this.ReconnectTimer = null; + } + + this.OnDisconnect?.(this.Id); + this.#ResetQueues(); + // reset connection Id on disconnect, for query-tracking + this.Id = uuid(); + this.#UpdateState(); + } + + OnMessage(e: MessageEvent) { + if (e.data.length > 0) { + const msg = JSON.parse(e.data); + const tag = msg[0]; + switch (tag) { + case "AUTH": { + this._OnAuthAsync(msg[1]) + .then(() => this.#sendPendingRaw()) + .catch(console.error); + this.Stats.EventsReceived++; + this.#UpdateState(); + break; + } + case "EVENT": { + this.OnEvent?.(msg[1], { + ...msg[2], + relays: [this.Address], + }); + this.Stats.EventsReceived++; + this.#UpdateState(); + break; + } + case "EOSE": { + this.OnEose?.(msg[1]); + break; + } + case "OK": { + // feedback to broadcast call + console.debug(`${this.Address} OK: `, msg); + const id = msg[1]; + if (this.EventsCallback.has(id)) { + const cb = unwrap(this.EventsCallback.get(id)); + this.EventsCallback.delete(id); + cb(msg); + } + break; + } + case "NOTICE": { + console.warn(`[${this.Address}] NOTICE: ${msg[1]}`); + break; + } + default: { + console.warn(`Unknown tag: ${tag}`); + break; + } + } + } + } + + OnError(e: Event) { + console.error(e); + this.#UpdateState(); + } + + /** + * Send event on this connection + */ + SendEvent(e: RawEvent) { + if (!this.Settings.write) { + return; + } + const req = ["EVENT", e]; + this.#SendJson(req); + this.Stats.EventsSent++; + this.#UpdateState(); + } + + /** + * Send event on this connection and wait for OK response + */ + async SendAsync(e: RawEvent, timeout = 5000) { + return new Promise(resolve => { + if (!this.Settings.write) { + resolve(); + return; + } + const t = setTimeout(() => { + resolve(); + }, timeout); + this.EventsCallback.set(e.id, () => { + clearTimeout(t); + resolve(); + }); + + const req = ["EVENT", e]; + this.#SendJson(req); + this.Stats.EventsSent++; + this.#UpdateState(); + }); + } + + /** + * Hook status for connection + */ + StatusHook(fnHook: CustomHook) { + const id = uuid(); + this.StateHooks.set(id, fnHook); + return () => { + this.StateHooks.delete(id); + }; + } + + /** + * Returns the current state of this connection + */ + GetState() { + if (this.HasStateChange) { + this.LastState = Object.freeze({ ...this.CurrentState }); + this.HasStateChange = false; + } + return this.LastState; + } + + /** + * Using relay document to determine if this relay supports a feature + */ + SupportsNip(n: number) { + return this.Info?.supported_nips?.some(a => a === n) ?? false; + } + + /** + * Queue or send command to the relay + * @param cmd The REQ to send to the server + */ + QueueReq(cmd: ReqCommand, cbSent: () => void) { + if (this.ActiveRequests.size >= this.#maxSubscriptions) { + this.PendingRequests.push({ + cmd, + cb: cbSent, + }); + console.debug("Queuing:", this.Address, cmd); + } else { + this.ActiveRequests.add(cmd[1]); + this.#SendJson(cmd); + cbSent(); + } + this.#UpdateState(); + } + + CloseReq(id: string) { + if (this.ActiveRequests.delete(id)) { + this.#SendJson(["CLOSE", id]); + this.OnEose?.(id); + this.#SendQueuedRequests(); + } + this.#UpdateState(); + } + + #SendQueuedRequests() { + const canSend = this.#maxSubscriptions - this.ActiveRequests.size; + if (canSend > 0) { + for (let x = 0; x < canSend; x++) { + const p = this.PendingRequests.shift(); + if (p) { + this.ActiveRequests.add(p.cmd[1]); + this.#SendJson(p.cmd); + p.cb(); + console.debug("Sent pending REQ", this.Address, p.cmd); + } + } + } + } + + #ResetQueues() { + this.ActiveRequests.clear(); + this.PendingRequests = []; + this.PendingRaw = []; + this.#UpdateState(); + } + + #UpdateState() { + this.CurrentState.connected = this.Socket?.readyState === WebSocket.OPEN; + this.CurrentState.events.received = this.Stats.EventsReceived; + this.CurrentState.events.send = this.Stats.EventsSent; + this.CurrentState.avgLatency = + this.Stats.Latency.length > 0 ? this.Stats.Latency.reduce((acc, v) => acc + v, 0) / this.Stats.Latency.length : 0; + this.CurrentState.disconnects = this.Stats.Disconnects; + this.CurrentState.info = this.Info; + this.CurrentState.id = this.Id; + this.CurrentState.pendingRequests = [...this.PendingRequests.map(a => a.cmd[1])]; + this.CurrentState.activeRequests = [...this.ActiveRequests]; + this.Stats.Latency = this.Stats.Latency.slice(-20); // trim + this.HasStateChange = true; + this.#NotifyState(); + } + + #NotifyState() { + const state = this.GetState(); + for (const [, h] of this.StateHooks) { + h(state); + } + } + + #SendJson(obj: object) { + const authPending = !this.Authed && (this.AwaitingAuth.size > 0 || this.Info?.limitation?.auth_required === true); + if (this.Socket?.readyState !== WebSocket.OPEN || authPending) { + this.PendingRaw.push(obj); + if (this.Socket?.readyState === WebSocket.CLOSED && this.Ephemeral && this.IsClosed) { + this.Connect(); + } + return false; + } + + this.#sendPendingRaw(); + this.#sendOnWire(obj); + } + + #sendPendingRaw() { + while (this.PendingRaw.length > 0) { + const next = this.PendingRaw.shift(); + if (next) { + this.#sendOnWire(next); + } + } + } + + #sendOnWire(obj: unknown) { + if (this.Socket?.readyState !== WebSocket.OPEN) { + throw new Error(`Socket is not open, state is ${this.Socket?.readyState}`); + } + const json = JSON.stringify(obj); + this.Socket.send(json); + return true; + } + + async _OnAuthAsync(challenge: string): Promise { + const authCleanup = () => { + this.AwaitingAuth.delete(challenge); + }; + if (!this.Auth) { + throw new Error("Auth hook not registered"); + } + this.AwaitingAuth.set(challenge, true); + const authEvent = await this.Auth(challenge, this.Address); + return new Promise(resolve => { + if (!authEvent) { + authCleanup(); + return Promise.reject("no event"); + } + + const t = setTimeout(() => { + authCleanup(); + resolve(); + }, 10_000); + + this.EventsCallback.set(authEvent.id, (msg: boolean[]) => { + clearTimeout(t); + authCleanup(); + if (msg.length > 3 && msg[2] === true) { + this.Authed = true; + } + resolve(); + }); + + this.#sendOnWire(["AUTH", authEvent]); + }); + } + + get #maxSubscriptions() { + return this.Info?.limitation?.max_subscriptions ?? 25; + } +} diff --git a/packages/app/src/System/ConnectionStats.ts b/packages/app/src/System/ConnectionStats.ts new file mode 100644 index 00000000..d1248f2d --- /dev/null +++ b/packages/app/src/System/ConnectionStats.ts @@ -0,0 +1,34 @@ +/** + * Stats class for tracking metrics per connection + */ +export class ConnectionStats { + /** + * Last n records of how long between REQ->EOSE + */ + Latency: number[] = []; + + /** + * Total number of REQ's sent on this connection + */ + Subs: number = 0; + + /** + * Count of REQ which took too long and where abandoned + */ + SubsTimeout: number = 0; + + /** + * Total number of EVENT messages received + */ + EventsReceived: number = 0; + + /** + * Total number of EVENT messages sent + */ + EventsSent: number = 0; + + /** + * Total number of times this connection was lost + */ + Disconnects: number = 0; +} diff --git a/packages/app/src/System/Const.ts b/packages/app/src/System/Const.ts new file mode 100644 index 00000000..e3502ce0 --- /dev/null +++ b/packages/app/src/System/Const.ts @@ -0,0 +1,4 @@ +/** + * Websocket re-connect timeout + */ +export const DefaultConnectTimeout = 2000; diff --git a/packages/app/src/System/EventBuilder.ts b/packages/app/src/System/EventBuilder.ts index 0c353d77..ea7c1df8 100644 --- a/packages/app/src/System/EventBuilder.ts +++ b/packages/app/src/System/EventBuilder.ts @@ -1,4 +1,4 @@ -import { EventKind, HexKey, NostrPrefix, RawEvent } from "@snort/nostr"; +import { EventKind, HexKey, NostrPrefix, RawEvent } from "System"; import { HashtagRegex } from "Const"; import { getPublicKey, parseNostrLink, unixNow } from "SnortUtils"; import { EventExt } from "./EventExt"; diff --git a/packages/app/src/System/EventExt.ts b/packages/app/src/System/EventExt.ts index 305e34db..ca2b34ee 100644 --- a/packages/app/src/System/EventExt.ts +++ b/packages/app/src/System/EventExt.ts @@ -1,6 +1,6 @@ import * as secp from "@noble/curves/secp256k1"; import * as utils from "@noble/curves/abstract/utils"; -import { EventKind, HexKey, RawEvent, Tag } from "@snort/nostr"; +import { EventKind, HexKey, RawEvent, Tag } from "System"; import base64 from "@protobufjs/base64"; import { sha256, unixNow } from "SnortUtils"; diff --git a/packages/app/src/System/EventKind.ts b/packages/app/src/System/EventKind.ts new file mode 100644 index 00000000..512a1f0a --- /dev/null +++ b/packages/app/src/System/EventKind.ts @@ -0,0 +1,29 @@ +enum EventKind { + Unknown = -1, + SetMetadata = 0, + TextNote = 1, + RecommendServer = 2, + ContactList = 3, // NIP-02 + DirectMessage = 4, // NIP-04 + Deletion = 5, // NIP-09 + Repost = 6, // NIP-18 + Reaction = 7, // NIP-25 + BadgeAward = 8, // NIP-58 + SnortSubscriptions = 1000, // NIP-XX + Polls = 6969, // NIP-69 + FileHeader = 1063, // NIP-94 + Relays = 10002, // NIP-65 + Ephemeral = 20_000, + Auth = 22242, // NIP-42 + PubkeyLists = 30000, // NIP-51a + NoteLists = 30001, // NIP-51b + TagLists = 30002, // NIP-51c + Badge = 30009, // NIP-58 + ProfileBadges = 30008, // NIP-58 + ZapstrTrack = 31337, + ZapRequest = 9734, // NIP 57 + ZapReceipt = 9735, // NIP 57 + HttpAuthentication = 27235, // NIP XX - HTTP Authentication +} + +export default EventKind; diff --git a/packages/app/src/System/EventPublisher.ts b/packages/app/src/System/EventPublisher.ts index 68e556dc..e77f4533 100644 --- a/packages/app/src/System/EventPublisher.ts +++ b/packages/app/src/System/EventPublisher.ts @@ -10,10 +10,9 @@ import { TaggedRawEvent, u256, UserMetadata, -} from "@snort/nostr"; +} from "System"; import { DefaultRelays } from "Const"; -import { System } from "System"; import { unwrap } from "SnortUtils"; import { EventBuilder } from "./EventBuilder"; import { EventExt } from "./EventExt"; @@ -23,11 +22,34 @@ const Nip7Queue: Array = []; processWorkQueue(Nip7Queue); export type EventBuilderHook = (ev: EventBuilder) => EventBuilder; +declare global { + interface Window { + nostr?: { + getPublicKey: () => Promise; + signEvent: (event: T) => Promise; + + getRelays?: () => Promise>; + + nip04?: { + encrypt?: (pubkey: HexKey, plaintext: string) => Promise; + decrypt?: (pubkey: HexKey, ciphertext: string) => Promise; + }; + }; + } +} + +interface SystemInterface { + BroadcastEvent(ev: RawEvent): void; + WriteOnceToRelay(relay: string, ev: RawEvent): Promise; +} + export class EventPublisher { + #system: SystemInterface; #pubKey: string; #privateKey?: string; - constructor(pubKey: string, privKey?: string) { + constructor(system: SystemInterface, pubKey: string, privKey?: string) { + this.#system = system; if (privKey) { this.#privateKey = privKey; this.#pubKey = utils.bytesToHex(secp.schnorr.getPublicKey(privKey)); @@ -97,7 +119,7 @@ export class EventPublisher { broadcast(ev: RawEvent) { console.debug(ev); - System.BroadcastEvent(ev); + this.#system.BroadcastEvent(ev); } /** @@ -107,7 +129,7 @@ export class EventPublisher { */ broadcastForBootstrap(ev: RawEvent) { for (const [k] of DefaultRelays) { - System.WriteOnceToRelay(k, ev); + this.#system.WriteOnceToRelay(k, ev); } } @@ -116,7 +138,7 @@ export class EventPublisher { */ broadcastAll(ev: RawEvent, relays: string[]) { for (const k of relays) { - System.WriteOnceToRelay(k, ev); + this.#system.WriteOnceToRelay(k, ev); } } diff --git a/packages/app/src/System/GossipModel.ts b/packages/app/src/System/GossipModel.ts index 914f57a6..3a6db619 100644 --- a/packages/app/src/System/GossipModel.ts +++ b/packages/app/src/System/GossipModel.ts @@ -1,4 +1,4 @@ -import { FullRelaySettings, RawReqFilter } from "@snort/nostr"; +import { FullRelaySettings, RawReqFilter } from "System"; import { unwrap } from "SnortUtils"; import debug from "debug"; diff --git a/packages/app/src/System/Links.ts b/packages/app/src/System/Links.ts new file mode 100644 index 00000000..fd854032 --- /dev/null +++ b/packages/app/src/System/Links.ts @@ -0,0 +1,88 @@ +import * as utils from "@noble/curves/abstract/utils"; +import { bech32 } from "bech32"; +import { HexKey } from "./Nostr"; + +export enum NostrPrefix { + PublicKey = "npub", + PrivateKey = "nsec", + Note = "note", + + // TLV prefixes + Profile = "nprofile", + Event = "nevent", + Relay = "nrelay", + Address = "naddr", +} + +export enum TLVEntryType { + Special = 0, + Relay = 1, + Author = 2, + Kind = 3, +} + +export interface TLVEntry { + type: TLVEntryType; + length: number; + value: string | HexKey | number; +} + +export function encodeTLV(prefix: NostrPrefix, id: string, relays?: string[], kind?: number, author?: string) { + const enc = new TextEncoder(); + const buf = prefix === NostrPrefix.Address ? enc.encode(id) : utils.hexToBytes(id); + + const tl0 = [0, buf.length, ...buf]; + const tl1 = + relays + ?.map(a => { + const data = enc.encode(a); + return [1, data.length, ...data]; + }) + .flat() ?? []; + + const tl2 = author ? [2, 32, ...utils.hexToBytes(author)] : []; + const tl3 = kind ? [3, 4, ...new Uint8Array(new Uint32Array([kind]).buffer).reverse()] : []; + + return bech32.encode(prefix, bech32.toWords([...tl0, ...tl1, ...tl2, ...tl3]), 1_000); +} + +export function decodeTLV(str: string) { + const decoded = bech32.decode(str, 1_000); + const data = bech32.fromWords(decoded.words); + + const entries: TLVEntry[] = []; + let x = 0; + while (x < data.length) { + const t = data[x]; + const l = data[x + 1]; + const v = data.slice(x + 2, x + 2 + l); + entries.push({ + type: t, + length: l, + value: decodeTLVEntry(t, decoded.prefix, new Uint8Array(v)), + }); + x += 2 + l; + } + return entries; +} + +function decodeTLVEntry(type: TLVEntryType, prefix: string, data: Uint8Array) { + switch (type) { + case TLVEntryType.Special: { + if (prefix === NostrPrefix.Address) { + return new TextDecoder("ASCII").decode(data); + } else { + return utils.bytesToHex(data); + } + } + case TLVEntryType.Author: { + return utils.bytesToHex(data); + } + case TLVEntryType.Kind: { + return new Uint32Array(new Uint8Array(data.reverse()).buffer)[0]; + } + case TLVEntryType.Relay: { + return new TextDecoder("ASCII").decode(data); + } + } +} diff --git a/packages/app/src/System/Nips.ts b/packages/app/src/System/Nips.ts new file mode 100644 index 00000000..decdfa3c --- /dev/null +++ b/packages/app/src/System/Nips.ts @@ -0,0 +1,3 @@ +export enum Nips { + Search = 50, +} diff --git a/packages/app/src/System/Nostr.ts b/packages/app/src/System/Nostr.ts new file mode 100644 index 00000000..8e36ae3b --- /dev/null +++ b/packages/app/src/System/Nostr.ts @@ -0,0 +1,84 @@ +import { RelaySettings } from "./Connection"; + +export type RawEvent = { + id: u256; + pubkey: HexKey; + created_at: number; + kind: number; + tags: Array>; + content: string; + sig: string; +}; + +export interface TaggedRawEvent extends RawEvent { + /** + * A list of relays this event was seen on + */ + relays: string[]; +} + +/** + * Basic raw key as hex + */ +export type HexKey = string; + +/** + * Optional HexKey + */ +export type MaybeHexKey = HexKey | undefined; + +/** + * A 256bit hex id + */ +export type u256 = string; + +export type ReqCommand = [cmd: "REQ", id: string, ...filters: Array]; + +/** + * Raw REQ filter object + */ +export type RawReqFilter = { + ids?: u256[]; + authors?: u256[]; + kinds?: number[]; + "#e"?: u256[]; + "#p"?: u256[]; + "#t"?: string[]; + "#d"?: string[]; + "#r"?: string[]; + search?: string; + since?: number; + until?: number; + limit?: number; +}; + +/** + * Medatadata event content + */ +export type UserMetadata = { + name?: string; + display_name?: string; + about?: string; + picture?: string; + website?: string; + banner?: string; + nip05?: string; + lud06?: string; + lud16?: string; +}; + +/** + * NIP-51 list types + */ +export enum Lists { + Muted = "mute", + Pinned = "pin", + Bookmarked = "bookmark", + Followed = "follow", + Badges = "profile_badges", +} + +export interface FullRelaySettings { + url: string; + settings: RelaySettings; +} diff --git a/packages/app/src/System/NoteCollection.test.ts b/packages/app/src/System/NoteCollection.test.ts index 8df3559a..00eb6086 100644 --- a/packages/app/src/System/NoteCollection.test.ts +++ b/packages/app/src/System/NoteCollection.test.ts @@ -1,4 +1,4 @@ -import { TaggedRawEvent } from "@snort/nostr"; +import { TaggedRawEvent } from "System"; import { describe, expect } from "@jest/globals"; import { FlatNoteStore, ReplaceableNoteStore } from "./NoteCollection"; diff --git a/packages/app/src/System/NoteCollection.ts b/packages/app/src/System/NoteCollection.ts index da7efa64..b740cb96 100644 --- a/packages/app/src/System/NoteCollection.ts +++ b/packages/app/src/System/NoteCollection.ts @@ -1,4 +1,4 @@ -import { TaggedRawEvent, u256 } from "@snort/nostr"; +import { TaggedRawEvent, u256 } from "System"; import { appendDedupe, findTag } from "SnortUtils"; export interface StoreSnapshot { diff --git a/packages/app/src/System/ProfileCache.ts b/packages/app/src/System/ProfileCache.ts index 00a6fb55..da5fe5e8 100644 --- a/packages/app/src/System/ProfileCache.ts +++ b/packages/app/src/System/ProfileCache.ts @@ -1,12 +1,14 @@ -import { EventKind, HexKey, TaggedRawEvent } from "@snort/nostr"; +import { EventKind, HexKey, NostrSystem, TaggedRawEvent } from "System"; import { ProfileCacheExpire } from "Const"; import { mapEventToProfile, MetadataCache } from "Cache"; import { UserCache } from "Cache/UserCache"; -import { PubkeyReplaceableNoteStore, RequestBuilder, System } from "System"; +import { PubkeyReplaceableNoteStore, RequestBuilder } from "System"; import { unixNowMs } from "SnortUtils"; import debug from "debug"; -class ProfileLoaderService { +export class ProfileLoaderService { + #system: NostrSystem; + /** * List of pubkeys to fetch metadata for */ @@ -14,7 +16,8 @@ class ProfileLoaderService { readonly #log = debug("ProfileCache"); - constructor() { + constructor(system: NostrSystem) { + this.#system = system; this.#FetchMetadata(); } @@ -70,7 +73,7 @@ class ProfileLoaderService { .authors([...missing]); const newProfiles = new Set(); - const q = System.Query(PubkeyReplaceableNoteStore, sub); + const q = this.#system.Query(PubkeyReplaceableNoteStore, sub); // never release this callback, it will stop firing anyway after eose const releaseOnEvent = q.onEvent(async e => { for (const pe of e) { @@ -118,5 +121,3 @@ class ProfileLoaderService { setTimeout(() => this.#FetchMetadata(), 500); } } - -export const ProfileLoader = new ProfileLoaderService(); diff --git a/packages/app/src/System/Query.test.ts b/packages/app/src/System/Query.test.ts index cc4dbb1b..d6617896 100644 --- a/packages/app/src/System/Query.test.ts +++ b/packages/app/src/System/Query.test.ts @@ -1,4 +1,4 @@ -import { Connection } from "@snort/nostr"; +import { Connection } from "System"; import { describe, expect } from "@jest/globals"; import { Query, QueryBase } from "./Query"; import { getRandomValues } from "crypto"; diff --git a/packages/app/src/System/Query.ts b/packages/app/src/System/Query.ts index b3090dd9..4521d1b6 100644 --- a/packages/app/src/System/Query.ts +++ b/packages/app/src/System/Query.ts @@ -1,6 +1,6 @@ import { v4 as uuid } from "uuid"; import debug from "debug"; -import { Connection, RawReqFilter, Nips } from "@snort/nostr"; +import { Connection, RawReqFilter, Nips } from "System"; import { unixNowMs, unwrap } from "SnortUtils"; import { NoteStore } from "./NoteCollection"; import { mergeSimilar } from "./RequestMerger"; diff --git a/packages/app/src/System/RelayInfo.ts b/packages/app/src/System/RelayInfo.ts new file mode 100644 index 00000000..984af50b --- /dev/null +++ b/packages/app/src/System/RelayInfo.ts @@ -0,0 +1,16 @@ +export interface RelayInfo { + name?: string; + description?: string; + pubkey?: string; + contact?: string; + supported_nips?: number[]; + software?: string; + version?: string; + limitation?: { + payment_required: boolean; + max_subscriptions: number; + max_filters: number; + max_event_tags: number; + auth_required: boolean; + }; +} diff --git a/packages/app/src/System/RequestBuilder.ts b/packages/app/src/System/RequestBuilder.ts index 74e923a1..56a49e85 100644 --- a/packages/app/src/System/RequestBuilder.ts +++ b/packages/app/src/System/RequestBuilder.ts @@ -1,4 +1,4 @@ -import { RawReqFilter, u256, HexKey, EventKind } from "@snort/nostr"; +import { RawReqFilter, u256, HexKey, EventKind } from "System"; import { appendDedupe, dedupe } from "SnortUtils"; import { QueryBase } from "./Query"; import { diffFilters } from "./RequestSplitter"; @@ -245,7 +245,7 @@ export class RequestFilterBuilder { return [ { filters: [this.filter], - relay: "*", + relay: "", strategy: RequestStrategy.DefaultRelays, }, ]; diff --git a/packages/app/src/System/RequestMerger.test.ts b/packages/app/src/System/RequestMerger.test.ts index a3b308a6..4eac9ab7 100644 --- a/packages/app/src/System/RequestMerger.test.ts +++ b/packages/app/src/System/RequestMerger.test.ts @@ -1,5 +1,5 @@ -import { RawReqFilter } from "@snort/nostr"; -import { mergeSimilar } from "./RequestMerger"; +import { RawReqFilter } from "System"; +import { filterIncludes, mergeSimilar } from "./RequestMerger"; describe("RequestMerger", () => { it("should simple merge authors", () => { @@ -41,4 +41,16 @@ describe("RequestMerger", () => { }, ]); }); + + it("filterIncludes", () => { + const bigger = { + authors: ["a", "b", "c"], + since: 99, + } as RawReqFilter; + const smaller = { + authors: ["c"], + since: 100, + } as RawReqFilter; + expect(filterIncludes(bigger, smaller)).toBe(true); + }); }); diff --git a/packages/app/src/System/RequestMerger.ts b/packages/app/src/System/RequestMerger.ts index de12b992..79cfe1e8 100644 --- a/packages/app/src/System/RequestMerger.ts +++ b/packages/app/src/System/RequestMerger.ts @@ -1,4 +1,4 @@ -import { RawReqFilter } from "@snort/nostr"; +import { RawReqFilter } from "System"; export function mergeSimilar(filters: Array): Array { const hasCriticalKeySet = (a: RawReqFilter) => { @@ -28,3 +28,31 @@ function simpleMerge(filters: Array) { return result as RawReqFilter; } + +/** + * Check if a filter includes another filter, as in the bigger filter will include the same results as the samller filter + * @param bigger + * @param smaller + * @returns + */ +export function filterIncludes(bigger: RawReqFilter, smaller: RawReqFilter) { + const outside = bigger as Record | number>; + for (const [k, v] of Object.entries(smaller)) { + if (outside[k] === undefined) { + return false; + } + if (Array.isArray(v) && v.some(a => !(outside[k] as Array).includes(a))) { + return false; + } + if (typeof v === "number") { + if (k === "since" && (outside[k] as number) > v) { + return false; + } + if (k === "until" && (outside[k] as number) < v) { + return false; + } + // limit cannot be checked and is ignored + } + } + return true; +} diff --git a/packages/app/src/System/RequestSplitter.test.ts b/packages/app/src/System/RequestSplitter.test.ts index 49c2d0cb..979a0330 100644 --- a/packages/app/src/System/RequestSplitter.test.ts +++ b/packages/app/src/System/RequestSplitter.test.ts @@ -1,4 +1,4 @@ -import { RawReqFilter } from "@snort/nostr"; +import { RawReqFilter } from "System"; import { describe, expect } from "@jest/globals"; import { diffFilters } from "./RequestSplitter"; diff --git a/packages/app/src/System/RequestSplitter.ts b/packages/app/src/System/RequestSplitter.ts index 69fb17fe..44fc2f91 100644 --- a/packages/app/src/System/RequestSplitter.ts +++ b/packages/app/src/System/RequestSplitter.ts @@ -1,4 +1,4 @@ -import { RawReqFilter } from "@snort/nostr"; +import { RawReqFilter } from "System"; // Critical keys changing means the entire filter has changed export const CriticalKeys = ["since", "until", "limit"]; @@ -19,7 +19,7 @@ export function diffFilters(a: Array, b: Array) { const thisArray = v as Array; const added = thisArray.filter(a => !prevArray?.includes(a)); // support adding new values to array, removing values is ignored since we only care about getting new values - result[i] = { ...result[i], [k]: added.length === 0 ? prevArray ?? thisArray : added }; + result[i] = { ...result[i], [k]: added.length === 0 ? prevArray ?? [] : added }; if (added.length > 0) { anyChanged = true; } diff --git a/packages/app/src/System/Tag.ts b/packages/app/src/System/Tag.ts new file mode 100644 index 00000000..a006be49 --- /dev/null +++ b/packages/app/src/System/Tag.ts @@ -0,0 +1,88 @@ +import { HexKey, u256 } from "./Nostr"; +import { unwrap } from "./Util"; + +export default class Tag { + Original: string[]; + Key: string; + Event?: u256; + PubKey?: HexKey; + Relay?: string; + Marker?: string; + Hashtag?: string; + DTag?: string; + ATag?: string; + Index: number; + Invalid: boolean; + LNURL?: string; + + constructor(tag: string[], index: number) { + this.Original = tag; + this.Key = tag[0]; + this.Index = index; + this.Invalid = false; + + switch (this.Key) { + case "e": { + // ["e", , , ] + this.Event = tag[1]; + this.Relay = tag.length > 2 ? tag[2] : undefined; + this.Marker = tag.length > 3 ? tag[3] : undefined; + if (!this.Event) { + this.Invalid = true; + } + break; + } + case "p": { + // ["p", ] + this.PubKey = tag[1]; + if (!this.PubKey) { + this.Invalid = true; + } + break; + } + case "d": { + this.DTag = tag[1]; + break; + } + case "a": { + this.ATag = tag[1]; + break; + } + case "t": { + this.Hashtag = tag[1]; + break; + } + case "delegation": { + this.PubKey = tag[1]; + break; + } + case "zap": { + this.LNURL = tag[1]; + break; + } + } + } + + ToObject(): string[] | null { + switch (this.Key) { + case "e": { + let ret = ["e", this.Event, this.Relay, this.Marker]; + const trimEnd = ret.reverse().findIndex(a => a !== undefined); + ret = ret.reverse().slice(0, ret.length - trimEnd); + return ret; + } + case "p": { + return this.PubKey ? ["p", this.PubKey] : null; + } + case "t": { + return ["t", unwrap(this.Hashtag)]; + } + case "d": { + return ["d", unwrap(this.DTag)]; + } + default: { + return this.Original; + } + } + } +} diff --git a/packages/app/src/System/Util.ts b/packages/app/src/System/Util.ts new file mode 100644 index 00000000..6d50c763 --- /dev/null +++ b/packages/app/src/System/Util.ts @@ -0,0 +1,34 @@ +import * as utils from "@noble/curves/abstract/utils"; +import { bech32 } from "bech32"; + +export function unwrap(v: T | undefined | null): T { + if (v === undefined || v === null) { + throw new Error("missing value"); + } + return v; +} + +/** + * Convert hex to bech32 + */ +export function hexToBech32(hrp: string, hex?: string) { + if (typeof hex !== "string" || hex.length === 0 || hex.length % 2 !== 0) { + return ""; + } + + try { + const buf = utils.hexToBytes(hex); + return bech32.encode(hrp, bech32.toWords(buf)); + } catch (e) { + console.warn("Invalid hex", hex, e); + return ""; + } +} + +export function sanitizeRelayUrl(url: string) { + try { + return new URL(url).toString(); + } catch { + // ignore + } +} diff --git a/packages/app/src/System/index.ts b/packages/app/src/System/index.ts index 239321c5..f6e75275 100644 --- a/packages/app/src/System/index.ts +++ b/packages/app/src/System/index.ts @@ -1,7 +1,8 @@ -import { AuthHandler, TaggedRawEvent, RelaySettings, Connection, RawReqFilter, RawEvent } from "@snort/nostr"; import debug from "debug"; import { sanitizeRelayUrl, unixNowMs, unwrap } from "SnortUtils"; +import { RawEvent, RawReqFilter, TaggedRawEvent } from "./Nostr"; +import { AuthHandler, Connection, RelaySettings, StateSnapshot } from "./Connection"; import { RequestBuilder } from "./RequestBuilder"; import { EventBuilder } from "./EventBuilder"; import { @@ -11,11 +12,16 @@ import { ParameterizedReplaceableNoteStore, ReplaceableNoteStore, } from "./NoteCollection"; -import { diffFilters } from "./RequestSplitter"; import { Query, QueryBase } from "./Query"; -import { splitAllByWriteRelays } from "./GossipModel"; import ExternalStore from "ExternalStore"; -import { UserRelays } from "Cache/UserRelayCache"; +import { RelayCache } from "./GossipModel"; + +export { default as EventKind } from "./EventKind"; +export * from "./Nostr"; +export * from "./Links"; +export { default as Tag } from "./Tag"; +export * from "./Nips"; +export * from "./RelayInfo"; export { NoteStore, @@ -26,6 +32,10 @@ export { ReplaceableNoteStore, Query, EventBuilder, + AuthHandler, + Connection, + RelaySettings, + StateSnapshot, }; export interface SystemSnapshot { @@ -60,13 +70,12 @@ export class NostrSystem extends ExternalStore { HandleAuth?: AuthHandler; #log = debug("System"); - #relayCache = { - get: (pk?: string) => UserRelays.getFromCache(pk)?.relays, - }; + #relayCache: RelayCache; - constructor() { + constructor(relayCache: RelayCache) { super(); this.Sockets = new Map(); + this.#relayCache = relayCache; this.#cleanup(); } @@ -202,15 +211,11 @@ export class NostrSystem extends ExternalStore { return existing.feed as Readonly; } else { for (const subQ of filters) { - this.SendQuery( - existing, - { - id: `${existing.id}-${existing.subQueryCounter++}`, - filters: subQ.filters, - relays: [subQ.relay], - }, - (q, s, c) => q.sendToRelay(c, s) - ); + this.SendQuery(existing, { + id: `${existing.id}-${existing.subQueryCounter++}`, + filters: subQ.filters, + relays: subQ.relay ? [subQ.relay] : undefined, + }); } this.notifyChange(); return existing.feed as Readonly; @@ -226,15 +231,11 @@ export class NostrSystem extends ExternalStore { this.Queries.set(req.id, q); for (const subQ of filters) { - this.SendQuery( - q, - { - id: `${q.id}-${q.subQueryCounter++}`, - filters: subQ.filters, - relays: [subQ.relay], - }, - (q, s, c) => q.sendToRelay(c, s) - ); + this.SendQuery(q, { + id: `${q.id}-${q.subQueryCounter++}`, + filters: subQ.filters, + relays: subQ.relay ? [subQ.relay] : undefined, + }); } this.notifyChange(); return q.feed as Readonly; @@ -248,17 +249,17 @@ export class NostrSystem extends ExternalStore { } } - async SendQuery(q: Query, qSend: QueryBase, qSender: (q: Query, qSend: QueryBase, c: Connection) => void) { + async SendQuery(q: Query, qSend: QueryBase) { if (qSend.relays && qSend.relays.length > 0) { for (const r of qSend.relays) { this.#log("Sending query to %s %O", r, qSend); const s = this.Sockets.get(r); if (s) { - qSender(q, qSend, s); + q.sendToRelay(s, qSend); } else { const nc = await this.ConnectEphemeralRelay(r); if (nc) { - qSender(q, qSend, nc); + q.sendToRelay(nc, qSend); } else { console.warn("Failed to connect to new relay for:", r, q); } @@ -267,7 +268,7 @@ export class NostrSystem extends ExternalStore { } else { for (const [, s] of this.Sockets) { if (!s.Ephemeral) { - qSender(q, qSend, s); + q.sendToRelay(s, qSend); } } } @@ -329,5 +330,3 @@ export class NostrSystem extends ExternalStore { setTimeout(() => this.#cleanup(), 1_000); } } - -export const System = new NostrSystem(); diff --git a/packages/app/src/Upload/VoidCat.ts b/packages/app/src/Upload/VoidCat.ts index 60a46952..269d57cc 100644 --- a/packages/app/src/Upload/VoidCat.ts +++ b/packages/app/src/Upload/VoidCat.ts @@ -1,4 +1,4 @@ -import { EventKind } from "@snort/nostr"; +import { EventKind } from "System"; import { VoidApi } from "@void-cat/api"; import { FileExtensionRegex, VoidCatHost } from "Const"; diff --git a/packages/app/src/Upload/index.ts b/packages/app/src/Upload/index.ts index 623f0ad4..9ea2cbcc 100644 --- a/packages/app/src/Upload/index.ts +++ b/packages/app/src/Upload/index.ts @@ -1,5 +1,5 @@ import useLogin from "Hooks/useLogin"; -import { RawEvent } from "@snort/nostr"; +import { RawEvent } from "System"; import NostrBuild from "Upload/NostrBuild"; import VoidCat from "Upload/VoidCat"; diff --git a/packages/app/src/Wallet/NostrWalletConnect.ts b/packages/app/src/Wallet/NostrWalletConnect.ts index 5979b957..b447e81d 100644 --- a/packages/app/src/Wallet/NostrWalletConnect.ts +++ b/packages/app/src/Wallet/NostrWalletConnect.ts @@ -1,4 +1,4 @@ -import { Connection, EventKind, RawEvent } from "@snort/nostr"; +import { Connection, EventKind, RawEvent } from "System"; import { EventBuilder } from "System"; import { EventExt } from "System/EventExt"; import { LNWallet, WalletError, WalletErrorCode, WalletInfo, WalletInvoice, WalletInvoiceState } from "Wallet"; diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index 9ab1b151..3561d7e8 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -35,7 +35,21 @@ import DebugPage from "Pages/Debug"; import { db } from "Db"; import { preload } from "Cache"; import { LoginStore } from "Login"; -import { System } from "System"; +import { UserRelays } from "Cache/UserRelayCache"; +import { NostrSystem } from "System"; +import { ProfileLoaderService } from "System/ProfileCache"; + +/** + * Singleton nostr system + */ +export const System = new NostrSystem({ + get: pk => UserRelays.getFromCache(pk)?.relays, +}); + +/** + * Singleton user profile loader + */ +export const ProfileLoader = new ProfileLoaderService(System); // @ts-expect-error Setting webpack nonce window.__webpack_nonce__ = "ZmlhdGphZiBzYWlkIHNub3J0LnNvY2lhbCBpcyBwcmV0dHkgZ29vZCwgd2UgbWFkZSBpdCE=";