diff --git a/.gitignore b/.gitignore index 5bd99317..3fb207a9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ node_modules/ .idea .yarn yarn.lock +dist/ +*.tgz +*.log \ No newline at end of file diff --git a/packages/app/.gitignore b/packages/app/.gitignore index 3a1bea68..3486a8a6 100644 --- a/packages/app/.gitignore +++ b/packages/app/.gitignore @@ -23,3 +23,5 @@ yarn-debug.log* yarn-error.log* .idea + +dist/ diff --git a/packages/app/src/Cache/DMCache.ts b/packages/app/src/Cache/DMCache.ts index 7cbce8df..4041a0fc 100644 --- a/packages/app/src/Cache/DMCache.ts +++ b/packages/app/src/Cache/DMCache.ts @@ -1,4 +1,4 @@ -import { NostrEvent } from "System"; +import { NostrEvent } from "@snort/system"; import { db } from "Db"; import FeedCache from "./FeedCache"; diff --git a/packages/app/src/Cache/UserCache.ts b/packages/app/src/Cache/UserCache.ts index 731a7422..1b9ce04b 100644 --- a/packages/app/src/Cache/UserCache.ts +++ b/packages/app/src/Cache/UserCache.ts @@ -1,6 +1,6 @@ import FeedCache from "Cache/FeedCache"; import { db } from "Db"; -import { MetadataCache } from "Cache"; +import { MetadataCache } from "@snort/system"; import { LNURL } from "LNURL"; import { fetchNip05Pubkey } from "Nip05/Verifier"; diff --git a/packages/app/src/Cache/index.ts b/packages/app/src/Cache/index.ts index 0f0cabb4..ec3f6c0f 100644 --- a/packages/app/src/Cache/index.ts +++ b/packages/app/src/Cache/index.ts @@ -1,57 +1,8 @@ -import { HexKey, NostrEvent, UserMetadata } from "System"; -import { hexToBech32, unixNowMs } from "SnortUtils"; import { DmCache } from "./DMCache"; import { InteractionCache } from "./EventInteractionCache"; import { UserCache } from "./UserCache"; import { UserRelays } from "./UserRelayCache"; -export interface MetadataCache extends UserMetadata { - /** - * When the object was saved in cache - */ - loaded: number; - - /** - * When the source metadata event was created - */ - created: number; - - /** - * The pubkey of the owner of this metadata - */ - pubkey: HexKey; - - /** - * The bech32 encoded pubkey - */ - npub: string; - - /** - * Pubkey of zapper service - */ - zapService?: HexKey; - - /** - * If the nip05 is valid for this user - */ - isNostrAddressValid: boolean; -} - -export function mapEventToProfile(ev: NostrEvent) { - try { - const data: UserMetadata = JSON.parse(ev.content); - return { - ...data, - pubkey: ev.pubkey, - npub: hexToBech32("npub", ev.pubkey), - created: ev.created_at, - loaded: unixNowMs(), - } as MetadataCache; - } catch (e) { - console.error("Failed to parse JSON", ev, e); - } -} - export async function preload(follows?: Array) { const preloads = [ UserCache.preload(follows), diff --git a/packages/app/src/Const.ts b/packages/app/src/Const.ts index e16d2262..db961bc6 100644 --- a/packages/app/src/Const.ts +++ b/packages/app/src/Const.ts @@ -1,6 +1,4 @@ -import { UserRelays } from "Cache/UserRelayCache"; -import { NostrSystem, RelaySettings } from "System"; -import { ProfileLoaderService } from "System/ProfileCache"; +import { RelaySettings } from "@snort/system"; /** * Add-on api for snort features diff --git a/packages/app/src/Db/index.ts b/packages/app/src/Db/index.ts index 9a72e8f3..cb501536 100644 --- a/packages/app/src/Db/index.ts +++ b/packages/app/src/Db/index.ts @@ -1,6 +1,5 @@ import Dexie, { Table } from "dexie"; -import { FullRelaySettings, HexKey, NostrEvent, u256 } from "System"; -import { MetadataCache } from "Cache"; +import { FullRelaySettings, HexKey, NostrEvent, u256, MetadataCache } from "@snort/system"; export const NAME = "snortDB"; export const VERSION = 8; diff --git a/packages/app/src/Element/Avatar.tsx b/packages/app/src/Element/Avatar.tsx index 576ef169..89fef4aa 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 "System"; +import type { UserMetadata } from "@snort/system"; import useImgProxy from "Hooks/useImgProxy"; diff --git a/packages/app/src/Element/BadgeList.tsx b/packages/app/src/Element/BadgeList.tsx index e007b685..36088902 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 "System"; +import { TaggedRawEvent } from "@snort/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 10c4859f..3245d76d 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 "System"; +import { HexKey } from "@snort/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 04680f64..118cf917 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 "System"; +import { HexKey, TaggedRawEvent } from "@snort/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 de058fee..2a8e9efa 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 "System"; +import { TaggedRawEvent } from "@snort/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 827984b7..b56e95c0 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 "System"; +import { TaggedRawEvent } from "@snort/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 e284bc83..72e3fb33 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 "System"; +import { HexKey } from "@snort/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 e98ef131..fc0b09dc 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 "System"; +import { HexKey } from "@snort/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 b7a8af60..9fe4723e 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 "System"; +import { HexKey } from "@snort/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 7f8d492f..b95d1e21 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 "System"; +import { HexKey } from "@snort/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 6fa9044c..b3ecb946 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 "System"; +import { HexKey } from "@snort/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 e3e14170..5e1de7ab 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 "System"; +import { HexKey } from "@snort/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 122c249b..3eb16982 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 "System"; +import { UserMetadata, mapEventToProfile } from "@snort/system"; import { unwrap } from "SnortUtils"; import { formatShort } from "Number"; @@ -22,7 +22,7 @@ import useEventPublisher from "Feed/EventPublisher"; import { debounce } from "SnortUtils"; import useLogin from "Hooks/useLogin"; import SnortServiceProvider from "Nip05/SnortServiceProvider"; -import { mapEventToProfile, UserCache } from "Cache"; +import { UserCache } from "Cache"; import messages from "./messages"; diff --git a/packages/app/src/Element/NostrFileHeader.tsx b/packages/app/src/Element/NostrFileHeader.tsx index 512d09df..b4efdc0d 100644 --- a/packages/app/src/Element/NostrFileHeader.tsx +++ b/packages/app/src/Element/NostrFileHeader.tsx @@ -1,7 +1,7 @@ import { FormattedMessage } from "react-intl"; -import { NostrEvent } from "System"; +import { NostrEvent, NostrLink } from "@snort/system"; -import { findTag, NostrLink } from "SnortUtils"; +import { findTag } from "SnortUtils"; import useEventFeed from "Feed/EventFeed"; import PageSpinner from "Element/PageSpinner"; import Reveal from "Element/Reveal"; diff --git a/packages/app/src/Element/NostrLink.tsx b/packages/app/src/Element/NostrLink.tsx index 64efbde8..28ea734a 100644 --- a/packages/app/src/Element/NostrLink.tsx +++ b/packages/app/src/Element/NostrLink.tsx @@ -1,8 +1,7 @@ import { Link } from "react-router-dom"; -import { NostrPrefix } from "System"; +import { NostrPrefix, parseNostrLink } from "@snort/system"; import Mention from "Element/Mention"; -import { parseNostrLink } from "SnortUtils"; import NoteQuote from "Element/NoteQuote"; export default function NostrLink({ link, depth }: { link: string; depth?: number }) { diff --git a/packages/app/src/Element/Note.tsx b/packages/app/src/Element/Note.tsx index 5a582f6a..10115754 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 "System"; +import { TaggedRawEvent, HexKey, EventKind, NostrPrefix, Lists, EventExt } from "@snort/system"; import useEventPublisher from "Feed/EventPublisher"; import Icon from "Icons/Icon"; @@ -26,7 +26,6 @@ import Reveal from "Element/Reveal"; import useModeration from "Hooks/useModeration"; import { UserCache } from "Cache/UserCache"; import Poll from "Element/Poll"; -import { EventExt } from "System/EventExt"; import useLogin from "Hooks/useLogin"; import { setBookmarked, setPinned } from "Login"; import { NostrFileElement } from "Element/NostrFileHeader"; diff --git a/packages/app/src/Element/NoteCreator.tsx b/packages/app/src/Element/NoteCreator.tsx index 6a4a2a4a..958cc179 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 "System"; +import { encodeTLV, EventKind, NostrPrefix, TaggedRawEvent, EventBuilder } from "@snort/system"; import Icon from "Icons/Icon"; import useEventPublisher from "Feed/EventPublisher"; @@ -31,7 +31,6 @@ import { LNURL } from "LNURL"; import messages from "./messages"; import { ClipboardEventHandler, useState } from "react"; import Spinner from "Icons/Spinner"; -import { EventBuilder } from "System"; import { Menu, MenuItem } from "@szhsin/react-menu"; import { LoginStore } from "Login"; import { getCurrentSubscription } from "Subscription"; diff --git a/packages/app/src/Element/NoteFooter.tsx b/packages/app/src/Element/NoteFooter.tsx index 25fef378..800c4d31 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 "System"; +import { TaggedRawEvent, HexKey, u256, encodeTLV, NostrPrefix, Lists } from "@snort/system"; import Icon from "Icons/Icon"; import Spinner from "Icons/Spinner"; diff --git a/packages/app/src/Element/NoteQuote.tsx b/packages/app/src/Element/NoteQuote.tsx index 31cca7ba..262b1ca6 100644 --- a/packages/app/src/Element/NoteQuote.tsx +++ b/packages/app/src/Element/NoteQuote.tsx @@ -1,5 +1,5 @@ import useEventFeed from "Feed/EventFeed"; -import { NostrLink } from "SnortUtils"; +import { NostrLink } from "@snort/system"; import Note from "Element/Note"; import PageSpinner from "Element/PageSpinner"; diff --git a/packages/app/src/Element/NoteReaction.tsx b/packages/app/src/Element/NoteReaction.tsx index 1849c904..00301dac 100644 --- a/packages/app/src/Element/NoteReaction.tsx +++ b/packages/app/src/Element/NoteReaction.tsx @@ -1,14 +1,13 @@ import "./NoteReaction.css"; import { Link } from "react-router-dom"; import { useMemo } from "react"; -import { EventKind, NostrEvent, TaggedRawEvent, NostrPrefix } from "System"; +import { EventKind, NostrEvent, TaggedRawEvent, NostrPrefix, EventExt } from "@snort/system"; import Note from "Element/Note"; import ProfileImage from "Element/ProfileImage"; import { eventLink, hexToBech32 } from "SnortUtils"; import NoteTime from "Element/NoteTime"; import useModeration from "Hooks/useModeration"; -import { EventExt } from "System/EventExt"; export interface NoteReactionProps { data: TaggedRawEvent; diff --git a/packages/app/src/Element/Poll.tsx b/packages/app/src/Element/Poll.tsx index bb29730e..4ef7dcf9 100644 --- a/packages/app/src/Element/Poll.tsx +++ b/packages/app/src/Element/Poll.tsx @@ -1,4 +1,4 @@ -import { TaggedRawEvent } from "System"; +import { TaggedRawEvent } from "@snort/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 96c5e15a..5745b9c7 100644 --- a/packages/app/src/Element/ProfileImage.tsx +++ b/packages/app/src/Element/ProfileImage.tsx @@ -1,13 +1,12 @@ import "./ProfileImage.css"; import React, { useMemo } from "react"; -import { HexKey, NostrPrefix } from "System"; +import { HexKey, NostrPrefix, MetadataCache } from "@snort/system"; import { useUserProfile } from "Hooks/useUserProfile"; import { hexToBech32, profileLink } from "SnortUtils"; import Avatar from "Element/Avatar"; import Nip05 from "Element/Nip05"; -import { MetadataCache } from "Cache"; import { Link } from "react-router-dom"; export interface ProfileImageProps { diff --git a/packages/app/src/Element/ProfilePreview.tsx b/packages/app/src/Element/ProfilePreview.tsx index 15f02c05..6397e778 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 "System"; +import { HexKey } from "@snort/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 6350af3f..5812e6fa 100644 --- a/packages/app/src/Element/PubkeyList.tsx +++ b/packages/app/src/Element/PubkeyList.tsx @@ -1,4 +1,4 @@ -import { NostrEvent } from "System"; +import { NostrEvent } from "@snort/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 2dcfbe30..8e723277 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 "System"; +import { TaggedRawEvent } from "@snort/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 e79ed1ab..ab3d3de9 100644 --- a/packages/app/src/Element/Relay.tsx +++ b/packages/app/src/Element/Relay.tsx @@ -2,7 +2,7 @@ import "./Relay.css"; import { useMemo } from "react"; import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router-dom"; -import { RelaySettings } from "System"; +import { RelaySettings } from "@snort/system"; import useRelayState from "Feed/RelayState"; import { System } from "index"; diff --git a/packages/app/src/Element/RelaysMetadata.tsx b/packages/app/src/Element/RelaysMetadata.tsx index 8973a428..5df1631e 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 "System"; +import { FullRelaySettings } from "@snort/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 765c5929..1c55269d 100644 --- a/packages/app/src/Element/SendSats.tsx +++ b/packages/app/src/Element/SendSats.tsx @@ -2,7 +2,7 @@ import "./SendSats.css"; import React, { useEffect, useMemo, useState } from "react"; import { useIntl, FormattedMessage } from "react-intl"; -import { HexKey, NostrEvent } from "System"; +import { HexKey, NostrEvent, EventPublisher } from "@snort/system"; import { System } from "index"; import { formatShort } from "Number"; import Icon from "Icons/Icon"; @@ -16,7 +16,6 @@ import { chunks, debounce } from "SnortUtils"; import { useWallet } from "Wallet"; import useLogin from "Hooks/useLogin"; import { generateRandomKey } from "Login"; -import { EventPublisher } from "System/EventPublisher"; import { ZapPoolController } from "ZapPoolController"; import messages from "./messages"; diff --git a/packages/app/src/Element/SubDebug.tsx b/packages/app/src/Element/SubDebug.tsx index 73375741..bf1707fe 100644 --- a/packages/app/src/Element/SubDebug.tsx +++ b/packages/app/src/Element/SubDebug.tsx @@ -5,7 +5,7 @@ import useRelayState from "Feed/RelayState"; import Tabs, { Tab } from "Element/Tabs"; import { unwrap } from "SnortUtils"; import useSystemState from "Hooks/useSystemState"; -import { ReqFilter } from "System"; +import { ReqFilter } from "@snort/system"; import { useCopy } from "useCopy"; import { System } from "index"; diff --git a/packages/app/src/Element/SuggestedProfiles.tsx b/packages/app/src/Element/SuggestedProfiles.tsx index 5cb06808..7d86420c 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 "System"; +import { HexKey, NostrPrefix } from "@snort/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 89ede775..779c35b8 100644 --- a/packages/app/src/Element/Text.tsx +++ b/packages/app/src/Element/Text.tsx @@ -1,10 +1,10 @@ import "./Text.css"; import { useMemo } from "react"; import { Link, useLocation } from "react-router-dom"; -import { HexKey, NostrPrefix } from "System"; +import { HexKey, NostrPrefix, validateNostrLink } from "@snort/system"; import { MentionRegex, InvoiceRegex, HashtagRegex, CashuRegex } from "Const"; -import { eventLink, hexToBech32, splitByUrl, validateNostrLink } from "SnortUtils"; +import { eventLink, hexToBech32, splitByUrl } from "SnortUtils"; import Invoice from "Element/Invoice"; import Hashtag from "Element/Hashtag"; import Mention from "Element/Mention"; diff --git a/packages/app/src/Element/Textarea.tsx b/packages/app/src/Element/Textarea.tsx index c92c497e..352ade10 100644 --- a/packages/app/src/Element/Textarea.tsx +++ b/packages/app/src/Element/Textarea.tsx @@ -4,12 +4,11 @@ import "./Textarea.css"; import { useIntl } from "react-intl"; import ReactTextareaAutocomplete from "@webscopeio/react-textarea-autocomplete"; import TextareaAutosize from "react-textarea-autosize"; -import { NostrPrefix } from "System"; +import { NostrPrefix, MetadataCache } from "@snort/system"; import Avatar from "Element/Avatar"; import Nip05 from "Element/Nip05"; import { hexToBech32 } from "SnortUtils"; -import { MetadataCache } from "Cache"; import { UserCache } from "Cache/UserCache"; import messages from "./messages"; diff --git a/packages/app/src/Element/Thread.tsx b/packages/app/src/Element/Thread.tsx index 2d252382..f30f8966 100644 --- a/packages/app/src/Element/Thread.tsx +++ b/packages/app/src/Element/Thread.tsx @@ -2,10 +2,17 @@ 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 "System"; -import { EventExt, Thread as ThreadInfo } from "System/EventExt"; +import { + TaggedRawEvent, + u256, + EventKind, + NostrPrefix, + EventExt, + Thread as ThreadInfo, + parseNostrLink, +} from "@snort/system"; -import { eventLink, unwrap, getReactions, parseNostrLink, getAllReactions, findTag } from "SnortUtils"; +import { eventLink, unwrap, getReactions, getAllReactions, findTag } from "SnortUtils"; import BackButton from "Element/BackButton"; import Note from "Element/Note"; import NoteGhost from "Element/NoteGhost"; diff --git a/packages/app/src/Element/Timeline.tsx b/packages/app/src/Element/Timeline.tsx index 1159b1b9..6aa8401e 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 "System"; +import { TaggedRawEvent, EventKind, u256 } from "@snort/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 c46c6e73..863174e6 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 { NostrEvent, TaggedRawEvent } from "System"; +import { NostrEvent, TaggedRawEvent } from "@snort/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 9460b8ab..665acc89 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 "System"; +import { HexKey } from "@snort/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 47a12ca7..3235cd7d 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 "System"; +import { HexKey } from "@snort/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 445437bd..e060c1f7 100644 --- a/packages/app/src/Element/WriteDm.tsx +++ b/packages/app/src/Element/WriteDm.tsx @@ -1,4 +1,4 @@ -import { encodeTLV, NostrPrefix, NostrEvent } from "System"; +import { encodeTLV, NostrPrefix, NostrEvent } from "@snort/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 6a84212b..cb59eee1 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 "System"; +import { HexKey, TaggedRawEvent } from "@snort/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 ef460d7d..1910fda1 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 "System"; +import { HexKey } from "@snort/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 d5ca7f01..844b5b67 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, NostrEvent } from "System"; +import { encodeTLV, NostrPrefix, NostrEvent } from "@snort/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 8615b61e..8553a460 100644 --- a/packages/app/src/External/NostrBand.ts +++ b/packages/app/src/External/NostrBand.ts @@ -1,4 +1,4 @@ -import { NostrEvent } from "System"; +import { NostrEvent } from "@snort/system"; export interface TrendingUser { pubkey: string; diff --git a/packages/app/src/Feed/BadgesFeed.ts b/packages/app/src/Feed/BadgesFeed.ts index baf0bacf..8397f8ce 100644 --- a/packages/app/src/Feed/BadgesFeed.ts +++ b/packages/app/src/Feed/BadgesFeed.ts @@ -1,9 +1,7 @@ import { useMemo } from "react"; -import { EventKind, HexKey, Lists } from "System"; +import { EventKind, HexKey, Lists, RequestBuilder, FlatNoteStore, ReplaceableNoteStore } from "@snort/system"; import { unwrap, findTag, chunks } from "SnortUtils"; -import { RequestBuilder } from "System"; -import { FlatNoteStore, ReplaceableNoteStore } from "System/NoteCollection"; import useRequestBuilder from "Hooks/useRequestBuilder"; type BadgeAwards = { diff --git a/packages/app/src/Feed/BookmarkFeed.tsx b/packages/app/src/Feed/BookmarkFeed.tsx index 647cd5b0..2c7da28e 100644 --- a/packages/app/src/Feed/BookmarkFeed.tsx +++ b/packages/app/src/Feed/BookmarkFeed.tsx @@ -1,4 +1,4 @@ -import { HexKey, Lists } from "System"; +import { HexKey, Lists } from "@snort/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 6ee13f7a..2c295271 100644 --- a/packages/app/src/Feed/EventFeed.ts +++ b/packages/app/src/Feed/EventFeed.ts @@ -1,9 +1,8 @@ import { useMemo } from "react"; -import { NostrPrefix } from "System"; +import { NostrPrefix, RequestBuilder, ReplaceableNoteStore, NostrLink } from "@snort/system"; import useRequestBuilder from "Hooks/useRequestBuilder"; -import { RequestBuilder, ReplaceableNoteStore } from "System"; -import { NostrLink, unwrap } from "SnortUtils"; +import { unwrap } from "SnortUtils"; export default function useEventFeed(link: NostrLink) { const sub = useMemo(() => { diff --git a/packages/app/src/Feed/EventPublisher.ts b/packages/app/src/Feed/EventPublisher.ts index e2eef45c..ff238977 100644 --- a/packages/app/src/Feed/EventPublisher.ts +++ b/packages/app/src/Feed/EventPublisher.ts @@ -1,6 +1,6 @@ import { useMemo } from "react"; import useLogin from "Hooks/useLogin"; -import { EventPublisher } from "System/EventPublisher"; +import { EventPublisher } from "@snort/system"; import { System } from "index"; export default function useEventPublisher() { diff --git a/packages/app/src/Feed/FollowersFeed.ts b/packages/app/src/Feed/FollowersFeed.ts index e8095bce..dc2f79b8 100644 --- a/packages/app/src/Feed/FollowersFeed.ts +++ b/packages/app/src/Feed/FollowersFeed.ts @@ -1,7 +1,6 @@ import { useMemo } from "react"; -import { HexKey, EventKind } from "System"; +import { HexKey, EventKind, PubkeyReplaceableNoteStore, RequestBuilder } from "@snort/system"; -import { PubkeyReplaceableNoteStore, RequestBuilder } from "System"; import useRequestBuilder from "Hooks/useRequestBuilder"; export default function useFollowersFeed(pubkey?: HexKey) { diff --git a/packages/app/src/Feed/FollowsFeed.ts b/packages/app/src/Feed/FollowsFeed.ts index b20bef55..5f7b260b 100644 --- a/packages/app/src/Feed/FollowsFeed.ts +++ b/packages/app/src/Feed/FollowsFeed.ts @@ -1,7 +1,6 @@ import { useMemo } from "react"; -import { HexKey, TaggedRawEvent, EventKind } from "System"; +import { HexKey, TaggedRawEvent, EventKind, PubkeyReplaceableNoteStore, RequestBuilder } from "@snort/system"; -import { PubkeyReplaceableNoteStore, RequestBuilder } from "System"; import useRequestBuilder from "Hooks/useRequestBuilder"; import useLogin from "Hooks/useLogin"; diff --git a/packages/app/src/Feed/LoginFeed.ts b/packages/app/src/Feed/LoginFeed.ts index bf214d7d..bf4a71b6 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 "System"; +import { TaggedRawEvent, Lists, EventKind, FlatNoteStore, RequestBuilder } from "@snort/system"; import debug from "debug"; import { bech32ToHex, getNewest, getNewestEventTagsByKey, unwrap } from "SnortUtils"; @@ -7,7 +7,6 @@ import { makeNotification, sendNotification } from "Notifications"; import useEventPublisher from "Feed/EventPublisher"; import { getMutedKeys } from "Feed/MuteList"; import useModeration from "Hooks/useModeration"; -import { FlatNoteStore, RequestBuilder } from "System"; import useRequestBuilder from "Hooks/useRequestBuilder"; import { DmCache } from "Cache"; import useLogin from "Hooks/useLogin"; diff --git a/packages/app/src/Feed/MuteList.ts b/packages/app/src/Feed/MuteList.ts index 3338c496..5e7c75d5 100644 --- a/packages/app/src/Feed/MuteList.ts +++ b/packages/app/src/Feed/MuteList.ts @@ -1,8 +1,14 @@ import { useMemo } from "react"; -import { HexKey, TaggedRawEvent, Lists, EventKind } from "System"; +import { + HexKey, + TaggedRawEvent, + Lists, + EventKind, + ParameterizedReplaceableNoteStore, + RequestBuilder, +} from "@snort/system"; import { getNewest } from "SnortUtils"; -import { ParameterizedReplaceableNoteStore, RequestBuilder } from "System"; import useRequestBuilder from "Hooks/useRequestBuilder"; import useLogin from "Hooks/useLogin"; diff --git a/packages/app/src/Feed/PinnedFeed.tsx b/packages/app/src/Feed/PinnedFeed.tsx index f55fe40d..bd714903 100644 --- a/packages/app/src/Feed/PinnedFeed.tsx +++ b/packages/app/src/Feed/PinnedFeed.tsx @@ -1,4 +1,4 @@ -import { HexKey, Lists } from "System"; +import { HexKey, Lists } from "@snort/system"; import useNotelistSubscription from "Hooks/useNotelistSubscription"; import useLogin from "Hooks/useLogin"; diff --git a/packages/app/src/Feed/RelaysFeed.tsx b/packages/app/src/Feed/RelaysFeed.tsx index 677f5586..e6fe274e 100644 --- a/packages/app/src/Feed/RelaysFeed.tsx +++ b/packages/app/src/Feed/RelaysFeed.tsx @@ -1,8 +1,6 @@ import { useMemo } from "react"; -import { HexKey, FullRelaySettings, EventKind } from "System"; +import { HexKey, FullRelaySettings, EventKind, RequestBuilder, ReplaceableNoteStore } from "@snort/system"; -import { RequestBuilder } from "System"; -import { ReplaceableNoteStore } from "System/NoteCollection"; import useRequestBuilder from "Hooks/useRequestBuilder"; export default function useRelaysFeed(pubkey?: HexKey) { diff --git a/packages/app/src/Feed/RelaysFeedFollows.tsx b/packages/app/src/Feed/RelaysFeedFollows.tsx index 128fe11f..77367f4d 100644 --- a/packages/app/src/Feed/RelaysFeedFollows.tsx +++ b/packages/app/src/Feed/RelaysFeedFollows.tsx @@ -1,9 +1,16 @@ import { useMemo } from "react"; -import { HexKey, FullRelaySettings, TaggedRawEvent, RelaySettings, EventKind } from "System"; +import { + HexKey, + FullRelaySettings, + TaggedRawEvent, + RelaySettings, + EventKind, + PubkeyReplaceableNoteStore, + RequestBuilder, +} from "@snort/system"; import debug from "debug"; import { sanitizeRelayUrl } from "SnortUtils"; -import { PubkeyReplaceableNoteStore, RequestBuilder } from "System"; import useRequestBuilder from "Hooks/useRequestBuilder"; import { UserRelays } from "Cache/UserRelayCache"; diff --git a/packages/app/src/Feed/ThreadFeed.ts b/packages/app/src/Feed/ThreadFeed.ts index adbc14f9..1b0af776 100644 --- a/packages/app/src/Feed/ThreadFeed.ts +++ b/packages/app/src/Feed/ThreadFeed.ts @@ -1,8 +1,7 @@ import { useEffect, useMemo, useState } from "react"; -import { u256, EventKind } from "System"; +import { u256, EventKind, NostrLink, FlatNoteStore, RequestBuilder } from "@snort/system"; -import { appendDedupe, NostrLink } from "SnortUtils"; -import { FlatNoteStore, RequestBuilder } from "System"; +import { appendDedupe } from "SnortUtils"; import useRequestBuilder from "Hooks/useRequestBuilder"; import useLogin from "Hooks/useLogin"; diff --git a/packages/app/src/Feed/TimelineFeed.ts b/packages/app/src/Feed/TimelineFeed.ts index 8fb8693f..9b8e439b 100644 --- a/packages/app/src/Feed/TimelineFeed.ts +++ b/packages/app/src/Feed/TimelineFeed.ts @@ -1,8 +1,7 @@ import { useCallback, useEffect, useMemo, useState } from "react"; -import { EventKind, u256 } from "System"; +import { EventKind, u256, FlatNoteStore, RequestBuilder } from "@snort/system"; import { unixNow, unwrap, tagFilterOfTextRepost } from "SnortUtils"; -import { FlatNoteStore, RequestBuilder } from "System"; import useRequestBuilder from "Hooks/useRequestBuilder"; import useTimelineWindow from "Hooks/useTimelineWindow"; import useLogin from "Hooks/useLogin"; diff --git a/packages/app/src/Feed/ZapsFeed.ts b/packages/app/src/Feed/ZapsFeed.ts index 88694a5e..6fd52ba4 100644 --- a/packages/app/src/Feed/ZapsFeed.ts +++ b/packages/app/src/Feed/ZapsFeed.ts @@ -1,8 +1,7 @@ import { useMemo } from "react"; -import { HexKey, EventKind } from "System"; +import { HexKey, EventKind, FlatNoteStore, RequestBuilder } from "@snort/system"; import { parseZap } from "Element/Zap"; -import { FlatNoteStore, RequestBuilder } from "System"; import useRequestBuilder from "Hooks/useRequestBuilder"; export default function useZapsFeed(pubkey?: HexKey) { diff --git a/packages/app/src/Hooks/useInteractionCache.tsx b/packages/app/src/Hooks/useInteractionCache.tsx index b75e9bf0..46a7521d 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 "System"; +import { HexKey, u256 } from "@snort/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 2cbd3c7a..3a530c5a 100644 --- a/packages/app/src/Hooks/useModeration.tsx +++ b/packages/app/src/Hooks/useModeration.tsx @@ -1,4 +1,4 @@ -import { HexKey } from "System"; +import { HexKey } from "@snort/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 55aa7f95..f9e1eee2 100644 --- a/packages/app/src/Hooks/useNotelistSubscription.ts +++ b/packages/app/src/Hooks/useNotelistSubscription.ts @@ -1,7 +1,13 @@ import { useMemo } from "react"; -import { HexKey, Lists, EventKind } from "System"; +import { + HexKey, + Lists, + EventKind, + FlatNoteStore, + ParameterizedReplaceableNoteStore, + RequestBuilder, +} from "@snort/system"; -import { FlatNoteStore, ParameterizedReplaceableNoteStore, RequestBuilder } from "System"; import useRequestBuilder from "Hooks/useRequestBuilder"; import useLogin from "Hooks/useLogin"; diff --git a/packages/app/src/Hooks/useRequestBuilder.tsx b/packages/app/src/Hooks/useRequestBuilder.tsx index 6d2fb8a7..d982ed9c 100644 --- a/packages/app/src/Hooks/useRequestBuilder.tsx +++ b/packages/app/src/Hooks/useRequestBuilder.tsx @@ -1,6 +1,5 @@ import { useSyncExternalStore } from "react"; -import { RequestBuilder } from "System"; -import { EmptySnapshot, NoteStore, StoreSnapshot } from "System/NoteCollection"; +import { RequestBuilder, EmptySnapshot, NoteStore, StoreSnapshot } from "@snort/system"; import { unwrap } from "SnortUtils"; import { System } from "index"; diff --git a/packages/app/src/Hooks/useSystemState.tsx b/packages/app/src/Hooks/useSystemState.tsx index 971675be..78932907 100644 --- a/packages/app/src/Hooks/useSystemState.tsx +++ b/packages/app/src/Hooks/useSystemState.tsx @@ -1,5 +1,5 @@ import { useSyncExternalStore } from "react"; -import { SystemSnapshot } from "System"; +import { SystemSnapshot } from "@snort/system"; import { System } from "index"; export default function useSystemState() { diff --git a/packages/app/src/Hooks/useUserProfile.ts b/packages/app/src/Hooks/useUserProfile.ts index 42d68310..3909774a 100644 --- a/packages/app/src/Hooks/useUserProfile.ts +++ b/packages/app/src/Hooks/useUserProfile.ts @@ -1,7 +1,6 @@ import { useEffect, useSyncExternalStore } from "react"; -import { HexKey } from "System"; -import { MetadataCache } from "Cache"; +import { HexKey, MetadataCache } from "@snort/system"; import { UserCache } from "Cache/UserCache"; import { ProfileLoader } from "index"; diff --git a/packages/app/src/LNURL.ts b/packages/app/src/LNURL.ts index 4138bb93..42978610 100644 --- a/packages/app/src/LNURL.ts +++ b/packages/app/src/LNURL.ts @@ -1,4 +1,4 @@ -import { HexKey, NostrEvent } from "System"; +import { HexKey, NostrEvent } from "@snort/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 ac9c37f2..1ba6ffcf 100644 --- a/packages/app/src/Login/Functions.ts +++ b/packages/app/src/Login/Functions.ts @@ -1,4 +1,4 @@ -import { HexKey, RelaySettings } from "System"; +import { HexKey, RelaySettings, EventPublisher } from "@snort/system"; import * as secp from "@noble/curves/secp256k1"; import * as utils from "@noble/curves/abstract/utils"; @@ -7,7 +7,6 @@ import { LoginStore, UserPreferences, LoginSession } from "Login"; 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) { diff --git a/packages/app/src/Login/LoginSession.ts b/packages/app/src/Login/LoginSession.ts index 95736dd6..3a7fb288 100644 --- a/packages/app/src/Login/LoginSession.ts +++ b/packages/app/src/Login/LoginSession.ts @@ -1,4 +1,4 @@ -import { HexKey, RelaySettings, u256 } from "System"; +import { HexKey, RelaySettings, u256 } from "@snort/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 ddbb4a02..70faea98 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 "System"; +import { HexKey, RelaySettings } from "@snort/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 fe23ef2a..d6efa314 100644 --- a/packages/app/src/Nip05/SnortServiceProvider.ts +++ b/packages/app/src/Nip05/SnortServiceProvider.ts @@ -1,5 +1,4 @@ -import { EventKind } from "System"; -import { EventPublisher } from "System/EventPublisher"; +import { EventKind, EventPublisher } from "@snort/system"; import { ServiceError, ServiceProvider } from "./ServiceProvider"; export interface ManageHandle { diff --git a/packages/app/src/Notifications.ts b/packages/app/src/Notifications.ts index 09360995..46a4fdef 100644 --- a/packages/app/src/Notifications.ts +++ b/packages/app/src/Notifications.ts @@ -1,7 +1,6 @@ import Nostrich from "nostrich.webp"; -import { TaggedRawEvent, EventKind } from "System"; -import { MetadataCache } from "Cache"; +import { TaggedRawEvent, EventKind, MetadataCache } from "@snort/system"; import { getDisplayName } from "Element/ProfileImage"; import { MentionRegex } from "Const"; import { tagFilterOfTextRepost, unwrap } from "SnortUtils"; diff --git a/packages/app/src/Pages/DonatePage.tsx b/packages/app/src/Pages/DonatePage.tsx index b871f7ac..64517db8 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 "System"; +import { HexKey } from "@snort/system"; import { ApiHost, KieranPubKey, SnortPubKey } from "Const"; import ProfilePreview from "Element/ProfilePreview"; diff --git a/packages/app/src/Pages/LoginPage.tsx b/packages/app/src/Pages/LoginPage.tsx index faf6e957..be38fea4 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 "System"; +import { HexKey } from "@snort/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 34f83c7d..3aa0e6e4 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, NostrEvent, NostrPrefix } from "System"; +import { HexKey, NostrEvent, NostrPrefix } from "@snort/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 3a4d966d..ea8f57c5 100644 --- a/packages/app/src/Pages/NostrLinkHandler.tsx +++ b/packages/app/src/Pages/NostrLinkHandler.tsx @@ -1,10 +1,10 @@ -import { NostrPrefix } from "System"; +import { NostrPrefix, parseNostrLink } from "@snort/system"; import { useEffect, useState } from "react"; import { FormattedMessage } from "react-intl"; import { useNavigate, useParams } from "react-router-dom"; import Spinner from "Icons/Spinner"; -import { parseNostrLink, profileLink } from "SnortUtils"; +import { profileLink } from "SnortUtils"; import { getNip05PubKey } from "Pages/LoginPage"; export default function NostrLinkHandler() { diff --git a/packages/app/src/Pages/ProfilePage.tsx b/packages/app/src/Pages/ProfilePage.tsx index 25adf365..551b0c8f 100644 --- a/packages/app/src/Pages/ProfilePage.tsx +++ b/packages/app/src/Pages/ProfilePage.tsx @@ -2,9 +2,9 @@ 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 "System"; +import { encodeTLV, EventKind, HexKey, NostrPrefix, parseNostrLink } from "@snort/system"; -import { parseNostrLink, getReactions, unwrap } from "SnortUtils"; +import { getReactions, unwrap } from "SnortUtils"; import { formatShort } from "Number"; import Note from "Element/Note"; import Bookmarks from "Element/Bookmarks"; diff --git a/packages/app/src/Pages/new/ProfileSetup.tsx b/packages/app/src/Pages/new/ProfileSetup.tsx index 20d6864d..26f5f23a 100644 --- a/packages/app/src/Pages/new/ProfileSetup.tsx +++ b/packages/app/src/Pages/new/ProfileSetup.tsx @@ -1,12 +1,13 @@ import { useEffect, useState } from "react"; import { useIntl, FormattedMessage } from "react-intl"; import { useNavigate } from "react-router-dom"; +import { mapEventToProfile } from "@snort/system"; import Logo from "Element/Logo"; import useEventPublisher from "Feed/EventPublisher"; import useLogin from "Hooks/useLogin"; import { useUserProfile } from "Hooks/useUserProfile"; -import { mapEventToProfile, UserCache } from "Cache"; +import { UserCache } from "Cache"; import AvatarEditor from "Element/AvatarEditor"; import messages from "./messages"; diff --git a/packages/app/src/Pages/settings/Keys.tsx b/packages/app/src/Pages/settings/Keys.tsx index d8871bcc..00958e95 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 "System"; +import { encodeTLV, NostrPrefix } from "@snort/system"; import Copy from "Element/Copy"; import useLogin from "Hooks/useLogin"; diff --git a/packages/app/src/Pages/settings/Profile.tsx b/packages/app/src/Pages/settings/Profile.tsx index 5ce684e4..1918036c 100644 --- a/packages/app/src/Pages/settings/Profile.tsx +++ b/packages/app/src/Pages/settings/Profile.tsx @@ -3,13 +3,14 @@ import Nostrich from "nostrich.webp"; import { useEffect, useState } from "react"; import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router-dom"; +import { mapEventToProfile } from "@snort/system"; import useEventPublisher from "Feed/EventPublisher"; import { useUserProfile } from "Hooks/useUserProfile"; import { openFile } from "SnortUtils"; import useFileUpload from "Upload"; import AsyncButton from "Element/AsyncButton"; -import { mapEventToProfile, UserCache } from "Cache"; +import { UserCache } from "Cache"; import useLogin from "Hooks/useLogin"; import AvatarEditor from "Element/AvatarEditor"; import Icon from "Icons/Icon"; diff --git a/packages/app/src/Pages/settings/Relays.tsx b/packages/app/src/Pages/settings/Relays.tsx index 78b490c7..d5338309 100644 --- a/packages/app/src/Pages/settings/Relays.tsx +++ b/packages/app/src/Pages/settings/Relays.tsx @@ -23,7 +23,6 @@ const RelaySettingsPage = () => { if (publisher) { const ev = await publisher.contactList(login.follows.item, login.relays.item); publisher.broadcast(ev); - publisher.broadcastForBootstrap(ev); try { const onlineRelays = await fetch("https://api.nostr.watch/v1/online").then(r => r.json()); const relayList = await publisher.relayList(login.relays.item); diff --git a/packages/app/src/SnortApi.ts b/packages/app/src/SnortApi.ts index 75dde20a..498346f1 100644 --- a/packages/app/src/SnortApi.ts +++ b/packages/app/src/SnortApi.ts @@ -1,7 +1,6 @@ -import { EventKind } from "System"; +import { EventKind, EventPublisher } from "@snort/system"; import { ApiHost } from "Const"; import { SubscriptionType } from "Subscription"; -import { EventPublisher } from "System/EventPublisher"; export interface RevenueToday { donations: number; diff --git a/packages/app/src/SnortUtils/Utils.test.ts b/packages/app/src/SnortUtils/Utils.test.ts index e40c6979..b91d02ae 100644 --- a/packages/app/src/SnortUtils/Utils.test.ts +++ b/packages/app/src/SnortUtils/Utils.test.ts @@ -1,5 +1,4 @@ -import { NostrPrefix } from "System"; -import { parseNostrLink, tryParseNostrLink } from "."; +import { NostrPrefix } from "@snort/system"; import { splitByUrl, magnetURIDecode, getRelayName } from "."; import { describe, expect } from "@jest/globals"; @@ -93,47 +92,3 @@ describe("getRelayName", () => { expect(output).toEqual("relay.example2.com?broadcast=true"); }); }); - -describe("tryParseNostrLink", () => { - it("is a valid nostr link", () => { - expect(parseNostrLink("nostr:npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg")).toMatchObject({ - id: "7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e", - type: NostrPrefix.PublicKey, - }); - expect(parseNostrLink("web+nostr:npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg")).toMatchObject({ - id: "7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e", - type: NostrPrefix.PublicKey, - }); - expect(parseNostrLink("nostr:note15449edq4qa5wzgqvh8td0q0dp6hwtes4pknsrm7eygeenhlj99xsq94wu9")).toMatchObject({ - id: "a56a5cb4150768e1200cb9d6d781ed0eaee5e6150da701efd9223399dff2294d", - type: NostrPrefix.Note, - }); - expect( - parseNostrLink( - "nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p" - ) - ).toMatchObject({ - id: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", - type: NostrPrefix.Profile, - relays: ["wss://r.x.com", "wss://djbas.sadkb.com"], - }); - expect(parseNostrLink("nostr:nevent1qqs226juks2sw68pyqxtn4khs8ksath9uc2smfcpalvjyvuemlezjngrd87dq")).toMatchObject({ - id: "a56a5cb4150768e1200cb9d6d781ed0eaee5e6150da701efd9223399dff2294d", - type: NostrPrefix.Event, - }); - expect( - parseNostrLink( - "nostr:naddr1qqzkjurnw4ksz9thwden5te0wfjkccte9ehx7um5wghx7un8qgs2d90kkcq3nk2jry62dyf50k0h36rhpdtd594my40w9pkal876jxgrqsqqqa28pccpzu" - ) - ).toMatchObject({ - id: "ipsum", - type: NostrPrefix.Address, - relays: ["wss://relay.nostr.org"], - author: "a695f6b60119d9521934a691347d9f78e8770b56da16bb255ee286ddf9fda919", - kind: 30023, - }); - }); - test.each(["nostr:npub", "web+nostr:npub", "nostr:nevent1xxx"])("should return false for invalid nostr links", lb => { - expect(tryParseNostrLink(lb)).toBeUndefined(); - }); -}); diff --git a/packages/app/src/SnortUtils/index.ts b/packages/app/src/SnortUtils/index.ts index 5ff04ed4..df89d9f5 100644 --- a/packages/app/src/SnortUtils/index.ts +++ b/packages/app/src/SnortUtils/index.ts @@ -13,12 +13,9 @@ import { EventKind, encodeTLV, NostrPrefix, - decodeTLV, - TLVEntryType, NostrEvent, -} from "System"; -import { MetadataCache } from "Cache"; -import NostrLink from "Element/NostrLink"; + MetadataCache, +} from "@snort/system"; export const sha256 = (str: string | Uint8Array): u256 => { return utils.bytesToHex(hash(str)); @@ -506,113 +503,6 @@ export function getUrlHostname(url?: string) { } } -export interface NostrLink { - type: NostrPrefix; - id: string; - kind?: number; - author?: string; - relays?: Array; - encode(): string; -} - -export function validateNostrLink(link: string): boolean { - try { - const parsedLink = parseNostrLink(link); - if (!parsedLink) { - return false; - } - if (parsedLink.type === NostrPrefix.PublicKey || parsedLink.type === NostrPrefix.Note) { - return parsedLink.id.length === 64; - } - - return true; - } catch { - return false; - } -} - -export function tryParseNostrLink(link: string, prefixHint?: NostrPrefix): NostrLink | undefined { - try { - return parseNostrLink(link, prefixHint); - } catch { - return undefined; - } -} - -export function parseNostrLink(link: string, prefixHint?: NostrPrefix): NostrLink { - const entity = link.startsWith("web+nostr:") || link.startsWith("nostr:") ? link.split(":")[1] : link; - - const isPrefix = (prefix: NostrPrefix) => { - return entity.startsWith(prefix); - }; - - if (isPrefix(NostrPrefix.PublicKey)) { - const id = bech32ToHex(entity); - if (id.length !== 64) throw new Error("Invalid nostr link, must contain 32 byte id"); - return { - type: NostrPrefix.PublicKey, - id: id, - encode: () => hexToBech32(NostrPrefix.PublicKey, id), - }; - } else if (isPrefix(NostrPrefix.Note)) { - const id = bech32ToHex(entity); - if (id.length !== 64) throw new Error("Invalid nostr link, must contain 32 byte id"); - return { - type: NostrPrefix.Note, - id: id, - encode: () => hexToBech32(NostrPrefix.Note, id), - }; - } else if (isPrefix(NostrPrefix.Profile) || isPrefix(NostrPrefix.Event) || isPrefix(NostrPrefix.Address)) { - const decoded = decodeTLV(entity); - - const id = decoded.find(a => a.type === TLVEntryType.Special)?.value as string; - const relays = decoded.filter(a => a.type === TLVEntryType.Relay).map(a => a.value as string); - const author = decoded.find(a => a.type === TLVEntryType.Author)?.value as string; - const kind = decoded.find(a => a.type === TLVEntryType.Kind)?.value as number; - - const encode = () => { - return entity; // return original - }; - if (isPrefix(NostrPrefix.Profile)) { - if (id.length !== 64) throw new Error("Invalid nostr link, must contain 32 byte id"); - return { - type: NostrPrefix.Profile, - id, - relays, - kind, - author, - encode, - }; - } else if (isPrefix(NostrPrefix.Event)) { - if (id.length !== 64) throw new Error("Invalid nostr link, must contain 32 byte id"); - return { - type: NostrPrefix.Event, - id, - relays, - kind, - author, - encode, - }; - } else if (isPrefix(NostrPrefix.Address)) { - return { - type: NostrPrefix.Address, - id, - relays, - kind, - author, - encode, - }; - } - } else if (prefixHint) { - return { - type: prefixHint, - id: link, - encode: () => hexToBech32(prefixHint, link), - }; - } - throw new Error("Invalid nostr link"); -} - export function sanitizeRelayUrl(url: string) { try { return new URL(url).toString(); diff --git a/packages/app/src/State/NoteCreator.ts b/packages/app/src/State/NoteCreator.ts index f0bf02fd..e6a8dda8 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 { NostrEvent, TaggedRawEvent } from "System"; +import { NostrEvent, TaggedRawEvent } from "@snort/system"; interface NoteCreatorStore { show: boolean; diff --git a/packages/app/src/State/ReBroadcast.ts b/packages/app/src/State/ReBroadcast.ts index 76d0165d..23cc253b 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 { NostrEvent } from "System"; +import { NostrEvent } from "@snort/system"; interface ReBroadcastStore { show: boolean; diff --git a/packages/app/src/System/Const.ts b/packages/app/src/System/Const.ts deleted file mode 100644 index e3502ce0..00000000 --- a/packages/app/src/System/Const.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Websocket re-connect timeout - */ -export const DefaultConnectTimeout = 2000; diff --git a/packages/app/src/System/Util.test.ts b/packages/app/src/System/Util.test.ts deleted file mode 100644 index 14812019..00000000 --- a/packages/app/src/System/Util.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { distance } from "./Util"; - -describe("distance", () => { - it("should have 0 distance", () => { - const a = { - ids: "a", - }; - const b = { - ids: "a", - }; - expect(distance(a, b)).toEqual(0); - }); - it("should have 1 distance", () => { - const a = { - ids: "a", - }; - const b = { - ids: "b", - }; - expect(distance(a, b)).toEqual(1); - }); - it("should have 10 distance", () => { - const a = { - ids: "a", - }; - const b = { - ids: "a", - kinds: 1, - }; - expect(distance(a, b)).toEqual(10); - }); - it("should have 11 distance", () => { - const a = { - ids: "a", - }; - const b = { - ids: "b", - kinds: 1, - }; - expect(distance(a, b)).toEqual(11); - }); - it("should have 1 distance, arrays", () => { - const a = { - since: 1, - until: 100, - kinds: [1], - authors: ["kieran", "snort", "c", "d", "e"], - }; - const b = { - since: 1, - until: 100, - kinds: [6969], - authors: ["kieran", "snort", "c", "d", "e"], - }; - expect(distance(a, b)).toEqual(1); - }); - it("should have 1 distance, array change extra", () => { - const a = { - since: 1, - until: 100, - kinds: [1], - authors: ["f", "kieran", "snort", "c", "d"], - }; - const b = { - since: 1, - until: 100, - kinds: [1], - authors: ["kieran", "snort", "c", "d", "e"], - }; - expect(distance(a, b)).toEqual(1); - }); -}); diff --git a/packages/app/src/System/Util.ts b/packages/app/src/System/Util.ts deleted file mode 100644 index c5698739..00000000 --- a/packages/app/src/System/Util.ts +++ /dev/null @@ -1,86 +0,0 @@ -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 - } -} - -export function unixNow() { - return Math.floor(unixNowMs() / 1000); -} - -export function unixNowMs() { - return new Date().getTime(); -} - -export function deepEqual(x: any, y: any): boolean { - const ok = Object.keys, - tx = typeof x, - ty = typeof y; - - return x && y && tx === "object" && tx === ty - ? ok(x).length === ok(y).length && ok(x).every(key => deepEqual(x[key], y[key])) - : x === y; -} - -/** - * Compute the "distance" between two objects by comparing their difference in properties - * Missing/Added keys result in +10 distance - * This is not recursive - */ -export function distance(a: any, b: any): number { - const keys1 = Object.keys(a); - const keys2 = Object.keys(b); - const maxKeys = keys1.length > keys2.length ? keys1 : keys2; - - let distance = 0; - for (const key of maxKeys) { - if (key in a && key in b) { - if (Array.isArray(a[key]) && Array.isArray(b[key])) { - const aa = a[key] as Array; - const bb = b[key] as Array; - if (aa.length === bb.length) { - if (aa.some(v => !bb.includes(v))) { - distance++; - } - } else { - distance++; - } - } else if (a[key] !== b[key]) { - distance++; - } - } else { - distance += 10; - } - } - - return distance; -} diff --git a/packages/app/src/Tasks/Nip5Task.tsx b/packages/app/src/Tasks/Nip5Task.tsx index fd711a24..c737ca61 100644 --- a/packages/app/src/Tasks/Nip5Task.tsx +++ b/packages/app/src/Tasks/Nip5Task.tsx @@ -1,6 +1,6 @@ import { FormattedMessage } from "react-intl"; import { Link } from "react-router-dom"; -import { MetadataCache } from "Cache"; +import { MetadataCache } from "@snort/system"; import { BaseUITask } from "Tasks"; export class Nip5Task extends BaseUITask { diff --git a/packages/app/src/Tasks/index.ts b/packages/app/src/Tasks/index.ts index 384c80af..c966d1e6 100644 --- a/packages/app/src/Tasks/index.ts +++ b/packages/app/src/Tasks/index.ts @@ -1,4 +1,4 @@ -import { MetadataCache } from "Cache"; +import { MetadataCache } from "@snort/system"; export interface UITask { id: string; diff --git a/packages/app/src/Upload/VoidCat.ts b/packages/app/src/Upload/VoidCat.ts index 269d57cc..8eb8a11a 100644 --- a/packages/app/src/Upload/VoidCat.ts +++ b/packages/app/src/Upload/VoidCat.ts @@ -1,8 +1,7 @@ -import { EventKind } from "System"; +import { EventKind, EventPublisher } from "@snort/system"; import { VoidApi } from "@void-cat/api"; import { FileExtensionRegex, VoidCatHost } from "Const"; -import { EventPublisher } from "System/EventPublisher"; import { UploadResult } from "Upload"; import { magnetURIDecode } from "SnortUtils"; diff --git a/packages/app/src/Upload/index.ts b/packages/app/src/Upload/index.ts index 8cf57586..ce993af4 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 { NostrEvent } from "System"; +import { NostrEvent } from "@snort/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 3b4abe53..8a94a523 100644 --- a/packages/app/src/Wallet/NostrWalletConnect.ts +++ b/packages/app/src/Wallet/NostrWalletConnect.ts @@ -1,6 +1,4 @@ -import { Connection, EventKind, NostrEvent } from "System"; -import { EventBuilder } from "System"; -import { EventExt } from "System/EventExt"; +import { Connection, EventKind, NostrEvent, EventBuilder, EventExt } from "@snort/system"; import { LNWallet, WalletError, WalletErrorCode, WalletInfo, WalletInvoice, WalletInvoiceState } from "Wallet"; import debug from "debug"; diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index 8dfb2a8a..e7f30e0a 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -33,10 +33,9 @@ import { SubscribeRoutes } from "Pages/subscribe"; import ZapPoolPage from "Pages/ZapPool"; import DebugPage from "Pages/Debug"; import { db } from "Db"; -import { preload } from "Cache"; +import { preload, UserCache } from "Cache"; import { LoginStore } from "Login"; -import { ProfileLoaderService } from "System/ProfileCache"; -import { NostrSystem } from "System"; +import { NostrSystem, ProfileLoaderService } from "@snort/system"; import { UserRelays } from "Cache/UserRelayCache"; /** @@ -49,7 +48,7 @@ export const System = new NostrSystem({ /** * Singleton user profile loader */ -export const ProfileLoader = new ProfileLoaderService(System); +export const ProfileLoader = new ProfileLoaderService(System, UserCache); // @ts-expect-error Setting webpack nonce window.__webpack_nonce__ = "ZmlhdGphZiBzYWlkIHNub3J0LnNvY2lhbCBpcyBwcmV0dHkgZ29vZCwgd2UgbWFkZSBpdCE="; diff --git a/packages/system/.npmignore b/packages/system/.npmignore new file mode 100644 index 00000000..91aa0ab2 --- /dev/null +++ b/packages/system/.npmignore @@ -0,0 +1,6 @@ +tests/ +src/ +*.tgz +jest.config.js +worker.ts +yarn* \ No newline at end of file diff --git a/packages/system/jest.config.js b/packages/system/jest.config.js new file mode 100644 index 00000000..1ccd5800 --- /dev/null +++ b/packages/system/jest.config.js @@ -0,0 +1,9 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + bail: true, + preset: "ts-jest", + testEnvironment: "jsdom", + roots: ["src"], + moduleDirectories: ["src"], + setupFiles: ["./tests/setupTests.ts"], +}; diff --git a/packages/system/package.json b/packages/system/package.json new file mode 100644 index 00000000..38616bfe --- /dev/null +++ b/packages/system/package.json @@ -0,0 +1,33 @@ +{ + "name": "@snort/system", + "version": "1.0.1", + "description": "Snort nostr system package", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "repository": "https://git.v0l.io/Kieran/snort", + "author": "v0l", + "license": "GPL-3.0-or-later", + "scripts": { + "build": "rm -rf dist && tsc", + "test": "jest" + }, + "files": [ + "src", + "dist" + ], + "devDependencies": { + "@jest/globals": "^29.5.0", + "@types/jest": "^29.5.1", + "jest": "^29.5.0", + "jest-environment-jsdom": "^29.5.0", + "ts-jest": "^29.1.0", + "typescript": "^5.0.4" + }, + "dependencies": { + "@noble/curves": "^1.0.0", + "@protobufjs/base64": "^1.1.2", + "bech32": "^2.0.0", + "debug": "^4.3.4", + "uuid": "^9.0.0" + } +} diff --git a/packages/app/src/System/Connection.ts b/packages/system/src/Connection.ts similarity index 99% rename from packages/app/src/System/Connection.ts rename to packages/system/src/Connection.ts index 109410e6..c4e11052 100644 --- a/packages/app/src/System/Connection.ts +++ b/packages/system/src/Connection.ts @@ -5,7 +5,7 @@ import { ConnectionStats } from "./ConnectionStats"; import { NostrEvent, ReqCommand, TaggedRawEvent, u256 } from "./Nostr"; import { RelayInfo } from "./RelayInfo"; import { unwrap } from "./Util"; -import ExternalStore from "ExternalStore"; +import ExternalStore from "./ExternalStore"; export type AuthHandler = (challenge: string, relay: string) => Promise; diff --git a/packages/app/src/System/ConnectionStats.ts b/packages/system/src/ConnectionStats.ts similarity index 100% rename from packages/app/src/System/ConnectionStats.ts rename to packages/system/src/ConnectionStats.ts diff --git a/packages/system/src/Const.ts b/packages/system/src/Const.ts new file mode 100644 index 00000000..f2e94a65 --- /dev/null +++ b/packages/system/src/Const.ts @@ -0,0 +1,16 @@ +/** + * Websocket re-connect timeout + */ +export const DefaultConnectTimeout = 2000; + +/** + * Hashtag regex + */ +// eslint-disable-next-line no-useless-escape +export const HashtagRegex = /(#[^\s!@#$%^&*()=+.\/,\[{\]};:'"?><]+)/g; + + +/** + * How long profile cache should be considered valid for + */ + export const ProfileCacheExpire = 1_000 * 60 * 60 * 6; \ No newline at end of file diff --git a/packages/app/src/System/EventBuilder.ts b/packages/system/src/EventBuilder.ts similarity index 91% rename from packages/app/src/System/EventBuilder.ts rename to packages/system/src/EventBuilder.ts index 73a285e1..d59bd647 100644 --- a/packages/app/src/System/EventBuilder.ts +++ b/packages/system/src/EventBuilder.ts @@ -1,7 +1,8 @@ -import { EventKind, HexKey, NostrPrefix, NostrEvent } from "System"; -import { HashtagRegex } from "Const"; -import { getPublicKey, parseNostrLink, unixNow } from "SnortUtils"; +import { EventKind, HexKey, NostrPrefix, NostrEvent } from "."; +import { HashtagRegex } from "./Const"; +import { getPublicKey, unixNow } from "./Util"; import { EventExt } from "./EventExt"; +import { parseNostrLink } from "./NostrLink"; export class EventBuilder { #kind?: EventKind; diff --git a/packages/app/src/System/EventExt.ts b/packages/system/src/EventExt.ts similarity index 92% rename from packages/app/src/System/EventExt.ts rename to packages/system/src/EventExt.ts index ebbf0f15..4df025ad 100644 --- a/packages/app/src/System/EventExt.ts +++ b/packages/system/src/EventExt.ts @@ -1,8 +1,8 @@ import * as secp from "@noble/curves/secp256k1"; import * as utils from "@noble/curves/abstract/utils"; -import { EventKind, HexKey, NostrEvent, Tag } from "System"; +import { EventKind, HexKey, NostrEvent, Tag } from "."; import base64 from "@protobufjs/base64"; -import { sha256, unixNow } from "SnortUtils"; +import { sha256, unixNow } from "./Util"; export interface Thread { root?: Tag; @@ -18,6 +18,7 @@ export abstract class EventExt { static getRootPubKey(e: NostrEvent): HexKey { const delegation = e.tags.find(a => a[0] === "delegation"); if (delegation?.[1]) { + // todo: verify sig return delegation[1]; } return e.pubkey; @@ -26,12 +27,12 @@ export abstract class EventExt { /** * Sign this message with a private key */ - static async sign(e: NostrEvent, key: HexKey) { + static sign(e: NostrEvent, key: HexKey) { e.id = this.createId(e); - const sig = await secp.schnorr.sign(e.id, key); + const sig = secp.schnorr.sign(e.id, key); e.sig = utils.bytesToHex(sig); - if (!(await secp.schnorr.verify(e.sig, e.id, e.pubkey))) { + if (!(secp.schnorr.verify(e.sig, e.id, e.pubkey))) { throw new Error("Signing failed"); } } @@ -40,9 +41,9 @@ export abstract class EventExt { * Check the signature of this message * @returns True if valid signature */ - static async verify(e: NostrEvent) { + static verify(e: NostrEvent) { const id = this.createId(e); - const result = await secp.schnorr.verify(e.sig, id, e.pubkey); + const result = secp.schnorr.verify(e.sig, id, e.pubkey); return result; } diff --git a/packages/app/src/System/EventKind.ts b/packages/system/src/EventKind.ts similarity index 100% rename from packages/app/src/System/EventKind.ts rename to packages/system/src/EventKind.ts diff --git a/packages/app/src/System/EventPublisher.ts b/packages/system/src/EventPublisher.ts similarity index 93% rename from packages/app/src/System/EventPublisher.ts rename to packages/system/src/EventPublisher.ts index 8aa29bad..b452bf48 100644 --- a/packages/app/src/System/EventPublisher.ts +++ b/packages/system/src/EventPublisher.ts @@ -11,13 +11,12 @@ import { TaggedRawEvent, u256, UserMetadata, -} from "System"; +} from "."; -import { DefaultRelays } from "Const"; -import { unwrap } from "SnortUtils"; +import { unwrap } from "./Util"; import { EventBuilder } from "./EventBuilder"; import { EventExt } from "./EventExt"; -import { barrierQueue, processWorkQueue, WorkQueueItem } from "WorkQueue"; +import { barrierQueue, processWorkQueue, WorkQueueItem } from "./WorkQueue"; const Nip7Queue: Array = []; processWorkQueue(Nip7Queue); @@ -118,17 +117,6 @@ export class EventPublisher { this.#system.BroadcastEvent(ev); } - /** - * Write event to DefaultRelays, this is important for profiles / relay lists to prevent bugs - * If a user removes all the DefaultRelays from their relay list and saves that relay list, - * When they open the site again we wont see that updated relay list and so it will appear to reset back to the previous state - */ - broadcastForBootstrap(ev: NostrEvent) { - for (const [k] of DefaultRelays) { - this.#system.WriteOnceToRelay(k, ev); - } - } - /** * Write event to all given relays. */ diff --git a/packages/system/src/ExternalStore.ts b/packages/system/src/ExternalStore.ts new file mode 100644 index 00000000..4b1dedea --- /dev/null +++ b/packages/system/src/ExternalStore.ts @@ -0,0 +1,41 @@ +type HookFn = (e?: TSnapshot) => void; + +interface HookFilter { + fn: HookFn; +} + +/** + * Simple React hookable store with manual change notifications + */ +export default abstract class ExternalStore { + #hooks: Array> = []; + #snapshot: Readonly = {} as Readonly; + #changed = true; + + hook(fn: HookFn) { + this.#hooks.push({ + fn, + }); + return () => { + const idx = this.#hooks.findIndex(a => a.fn === fn); + if (idx >= 0) { + this.#hooks.splice(idx, 1); + } + }; + } + + snapshot() { + if (this.#changed) { + this.#snapshot = this.takeSnapshot(); + this.#changed = false; + } + return this.#snapshot; + } + + protected notifyChange(sn?: TSnapshot) { + this.#changed = true; + this.#hooks.forEach(h => h.fn(sn)); + } + + abstract takeSnapshot(): TSnapshot; +} diff --git a/packages/app/src/System/GossipModel.ts b/packages/system/src/GossipModel.ts similarity index 96% rename from packages/app/src/System/GossipModel.ts rename to packages/system/src/GossipModel.ts index 5cdd2d41..07f8817e 100644 --- a/packages/app/src/System/GossipModel.ts +++ b/packages/system/src/GossipModel.ts @@ -1,5 +1,5 @@ -import { FullRelaySettings, ReqFilter } from "System"; -import { unwrap } from "SnortUtils"; +import { FullRelaySettings, ReqFilter } from "."; +import { unwrap } from "./Util"; import debug from "debug"; const PickNRelays = 2; diff --git a/packages/app/src/System/Links.ts b/packages/system/src/Links.ts similarity index 100% rename from packages/app/src/System/Links.ts rename to packages/system/src/Links.ts diff --git a/packages/app/src/System/Nips.ts b/packages/system/src/Nips.ts similarity index 100% rename from packages/app/src/System/Nips.ts rename to packages/system/src/Nips.ts diff --git a/packages/app/src/System/Nostr.ts b/packages/system/src/Nostr.ts similarity index 100% rename from packages/app/src/System/Nostr.ts rename to packages/system/src/Nostr.ts diff --git a/packages/system/src/NostrLink.ts b/packages/system/src/NostrLink.ts new file mode 100644 index 00000000..cb663b3b --- /dev/null +++ b/packages/system/src/NostrLink.ts @@ -0,0 +1,110 @@ +import { bech32ToHex, hexToBech32 } from "./Util"; +import { NostrPrefix, decodeTLV, TLVEntryType } from "."; + +export interface NostrLink { + type: NostrPrefix; + id: string; + kind?: number; + author?: string; + relays?: Array; + encode(): string; + } + + export function validateNostrLink(link: string): boolean { + try { + const parsedLink = parseNostrLink(link); + if (!parsedLink) { + return false; + } + if (parsedLink.type === NostrPrefix.PublicKey || parsedLink.type === NostrPrefix.Note) { + return parsedLink.id.length === 64; + } + + return true; + } catch { + return false; + } + } + + export function tryParseNostrLink(link: string, prefixHint?: NostrPrefix): NostrLink | undefined { + try { + return parseNostrLink(link, prefixHint); + } catch { + return undefined; + } + } + + export function parseNostrLink(link: string, prefixHint?: NostrPrefix): NostrLink { + const entity = link.startsWith("web+nostr:") || link.startsWith("nostr:") ? link.split(":")[1] : link; + + const isPrefix = (prefix: NostrPrefix) => { + return entity.startsWith(prefix); + }; + + if (isPrefix(NostrPrefix.PublicKey)) { + const id = bech32ToHex(entity); + if (id.length !== 64) throw new Error("Invalid nostr link, must contain 32 byte id"); + return { + type: NostrPrefix.PublicKey, + id: id, + encode: () => hexToBech32(NostrPrefix.PublicKey, id), + }; + } else if (isPrefix(NostrPrefix.Note)) { + const id = bech32ToHex(entity); + if (id.length !== 64) throw new Error("Invalid nostr link, must contain 32 byte id"); + return { + type: NostrPrefix.Note, + id: id, + encode: () => hexToBech32(NostrPrefix.Note, id), + }; + } else if (isPrefix(NostrPrefix.Profile) || isPrefix(NostrPrefix.Event) || isPrefix(NostrPrefix.Address)) { + const decoded = decodeTLV(entity); + + const id = decoded.find(a => a.type === TLVEntryType.Special)?.value as string; + const relays = decoded.filter(a => a.type === TLVEntryType.Relay).map(a => a.value as string); + const author = decoded.find(a => a.type === TLVEntryType.Author)?.value as string; + const kind = decoded.find(a => a.type === TLVEntryType.Kind)?.value as number; + + const encode = () => { + return entity; // return original + }; + if (isPrefix(NostrPrefix.Profile)) { + if (id.length !== 64) throw new Error("Invalid nostr link, must contain 32 byte id"); + return { + type: NostrPrefix.Profile, + id, + relays, + kind, + author, + encode, + }; + } else if (isPrefix(NostrPrefix.Event)) { + if (id.length !== 64) throw new Error("Invalid nostr link, must contain 32 byte id"); + return { + type: NostrPrefix.Event, + id, + relays, + kind, + author, + encode, + }; + } else if (isPrefix(NostrPrefix.Address)) { + return { + type: NostrPrefix.Address, + id, + relays, + kind, + author, + encode, + }; + } + } else if (prefixHint) { + return { + type: prefixHint, + id: link, + encode: () => hexToBech32(prefixHint, link), + }; + } + throw new Error("Invalid nostr link"); + } + \ No newline at end of file diff --git a/packages/app/src/System/NostrSystem.ts b/packages/system/src/NostrSystem.ts similarity index 95% rename from packages/app/src/System/NostrSystem.ts rename to packages/system/src/NostrSystem.ts index a34a641f..c20b96a8 100644 --- a/packages/app/src/System/NostrSystem.ts +++ b/packages/system/src/NostrSystem.ts @@ -1,15 +1,14 @@ import debug from "debug"; -import { v4 as uuid } from "uuid"; -import ExternalStore from "ExternalStore"; -import { NostrEvent, ReqFilter, TaggedRawEvent } from "./Nostr"; +import ExternalStore from "./ExternalStore"; +import { NostrEvent, TaggedRawEvent } from "./Nostr"; import { AuthHandler, Connection, RelaySettings, ConnectionStateSnapshot } from "./Connection"; -import { Query, QueryBase } from "./Query"; +import { Query } from "./Query"; import { RelayCache } from "./GossipModel"; import { NoteStore } from "./NoteCollection"; import { BuiltRawReqFilter, RequestBuilder } from "./RequestBuilder"; -import { unwrap, sanitizeRelayUrl, unixNowMs } from "./Util"; -import { SystemInterface, SystemSnapshot } from "System"; +import { unwrap, sanitizeRelayUrl } from "./Util"; +import { SystemInterface, SystemSnapshot } from "."; /** * Manages nostr content retrieval system diff --git a/packages/app/src/System/NoteCollection.ts b/packages/system/src/NoteCollection.ts similarity index 98% rename from packages/app/src/System/NoteCollection.ts rename to packages/system/src/NoteCollection.ts index 85974f14..f33e41f7 100644 --- a/packages/app/src/System/NoteCollection.ts +++ b/packages/system/src/NoteCollection.ts @@ -1,5 +1,5 @@ -import { TaggedRawEvent, u256 } from "System"; -import { appendDedupe, findTag } from "SnortUtils"; +import { TaggedRawEvent, u256 } from "."; +import { appendDedupe, findTag } from "./Util"; export interface StoreSnapshot { data: TSnapshot | undefined; diff --git a/packages/app/src/System/ProfileCache.ts b/packages/system/src/ProfileCache.ts similarity index 83% rename from packages/app/src/System/ProfileCache.ts rename to packages/system/src/ProfileCache.ts index fe49f371..7a21c6c9 100644 --- a/packages/app/src/System/ProfileCache.ts +++ b/packages/system/src/ProfileCache.ts @@ -1,13 +1,12 @@ -import { EventKind, HexKey, SystemInterface, TaggedRawEvent } from "System"; -import { ProfileCacheExpire } from "Const"; -import { mapEventToProfile, MetadataCache } from "Cache"; -import { UserCache } from "Cache/UserCache"; -import { PubkeyReplaceableNoteStore, RequestBuilder } from "System"; -import { unixNowMs } from "SnortUtils"; +import { EventKind, HexKey, SystemInterface, TaggedRawEvent, PubkeyReplaceableNoteStore, RequestBuilder } from "."; +import { ProfileCacheExpire } from "./Const"; +import { CacheStore, mapEventToProfile, MetadataCache } from "./cache"; +import { unixNowMs } from "./Util"; import debug from "debug"; export class ProfileLoaderService { #system: SystemInterface; + #cache: CacheStore; /** * List of pubkeys to fetch metadata for @@ -16,8 +15,9 @@ export class ProfileLoaderService { readonly #log = debug("ProfileCache"); - constructor(system: SystemInterface) { + constructor(system: SystemInterface, cache: CacheStore) { this.#system = system; + this.#cache = cache; this.#FetchMetadata(); } @@ -31,7 +31,7 @@ export class ProfileLoaderService { bufferNow.push(p); } } - UserCache.buffer(bufferNow); + this.#cache.buffer(bufferNow); } /** @@ -48,17 +48,17 @@ export class ProfileLoaderService { async onProfileEvent(e: Readonly) { const profile = mapEventToProfile(e); if (profile) { - await UserCache.update(profile); + await this.#cache.update(profile); } } async #FetchMetadata() { - const missingFromCache = await UserCache.buffer([...this.WantsMetadata]); + const missingFromCache = await this.#cache.buffer([...this.WantsMetadata]); const expire = unixNowMs() - ProfileCacheExpire; const expired = [...this.WantsMetadata] .filter(a => !missingFromCache.includes(a)) - .filter(a => (UserCache.getFromCache(a)?.loaded ?? 0) < expire); + .filter(a => (this.#cache.getFromCache(a)?.loaded ?? 0) < expire); const missing = new Set([...missingFromCache, ...expired]); if (missing.size > 0) { this.#log("Wants profiles: %d missing, %d expired", missingFromCache.length, expired.length); @@ -104,7 +104,7 @@ export class ProfileLoaderService { if (couldNotFetch.length > 0) { this.#log("No profiles: %o", couldNotFetch); const empty = couldNotFetch.map(a => - UserCache.update({ + this.#cache.update({ pubkey: a, loaded: unixNowMs() - ProfileCacheExpire + 5_000, // expire in 5s created: 69, diff --git a/packages/app/src/System/Query.ts b/packages/system/src/Query.ts similarity index 96% rename from packages/app/src/System/Query.ts rename to packages/system/src/Query.ts index 581d37f1..4ec3a4df 100644 --- a/packages/app/src/System/Query.ts +++ b/packages/system/src/Query.ts @@ -1,10 +1,9 @@ import { v4 as uuid } from "uuid"; import debug from "debug"; -import { Connection, ReqFilter, Nips, TaggedRawEvent } from "System"; -import { unixNowMs, unwrap } from "SnortUtils"; +import { Connection, ReqFilter, Nips, TaggedRawEvent } from "."; +import { unixNowMs, unwrap } from "./Util"; import { NoteStore } from "./NoteCollection"; -import { flatMerge, mergeSimilar, simpleMerge } from "./RequestMerger"; -import { eventMatchesFilter } from "./RequestMatcher"; +import { flatMerge } from "./RequestMerger"; import { BuiltRawReqFilter } from "./RequestBuilder"; import { expandFilter } from "./RequestExpander"; diff --git a/packages/app/src/System/RelayInfo.ts b/packages/system/src/RelayInfo.ts similarity index 100% rename from packages/app/src/System/RelayInfo.ts rename to packages/system/src/RelayInfo.ts diff --git a/packages/app/src/System/RequestBuilder.ts b/packages/system/src/RequestBuilder.ts similarity index 98% rename from packages/app/src/System/RequestBuilder.ts rename to packages/system/src/RequestBuilder.ts index dfbbb8b5..a565da9a 100644 --- a/packages/app/src/System/RequestBuilder.ts +++ b/packages/system/src/RequestBuilder.ts @@ -1,5 +1,5 @@ -import { ReqFilter, u256, HexKey, EventKind } from "System"; -import { appendDedupe, dedupe } from "SnortUtils"; +import { ReqFilter, u256, HexKey, EventKind } from "."; +import { appendDedupe, dedupe } from "./Util"; import { diffFilters } from "./RequestSplitter"; import { RelayCache, splitAllByWriteRelays, splitByWriteRelays } from "./GossipModel"; import { mergeSimilar } from "./RequestMerger"; diff --git a/packages/app/src/System/RequestExpander.ts b/packages/system/src/RequestExpander.ts similarity index 100% rename from packages/app/src/System/RequestExpander.ts rename to packages/system/src/RequestExpander.ts diff --git a/packages/app/src/System/RequestMatcher.ts b/packages/system/src/RequestMatcher.ts similarity index 100% rename from packages/app/src/System/RequestMatcher.ts rename to packages/system/src/RequestMatcher.ts diff --git a/packages/app/src/System/RequestMerger.ts b/packages/system/src/RequestMerger.ts similarity index 72% rename from packages/app/src/System/RequestMerger.ts rename to packages/system/src/RequestMerger.ts index bd588c25..900a1fbc 100644 --- a/packages/app/src/System/RequestMerger.ts +++ b/packages/system/src/RequestMerger.ts @@ -1,6 +1,5 @@ -import { ReqFilter } from "System"; +import { ReqFilter } from "."; import { FlatReqFilter } from "./RequestExpander"; -import { distance } from "./Util"; /** * Keys which can change the entire meaning of the filter outside the array types @@ -17,7 +16,70 @@ export function canMergeFilters(a: FlatReqFilter | ReqFilter, b: FlatReqFilter | } } } - return distance(aObj, bObj) <= 1; + let flag = false; + if (!equalProp(a.ids, b.ids)) { + flag = true; + } + if (!equalProp(a.authors, b.authors)) { + if (flag) return false; + flag = true; + } + if (!equalProp(a.kinds, b.kinds)) { + if (flag) return false; + flag = true; + } + if (!equalProp(a.limit, b.limit)) { + if (flag) return false; + flag = true; + } + if (!equalProp(a.until, b.until)) { + if (flag) return false; + flag = true; + } + if (!equalProp(a.since, b.since)) { + if (flag) return false; + flag = true; + } + if (!equalProp(a.search, b.search)) { + if (flag) return false; + flag = true; + } + if (!equalProp(a["#e"], b["#e"])) { + if (flag) return false; + flag = true; + } + if (!equalProp(a["#p"], b["#p"])) { + if (flag) return false; + flag = true; + } + if (!equalProp(a["#d"], b["#d"])) { + if (flag) return false; + flag = true; + } + if (!equalProp(a["#r"], b["#r"])) { + if (flag) return false; + flag = true; + } + if (!equalProp(a["#t"], b["#t"])) { + if (flag) return false; + flag = true; + } + return true; +} + +function equalProp(a: string | number | Array | undefined, b: string | number | Array | undefined) { + if ((a !== undefined && b === undefined) || (a === undefined && b !== undefined)) { + return false; + } + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) { + return false; + } + if (!a.every(v => b.includes(v))) { + return false; + } + } + return a === b; } export function mergeSimilar(filters: Array): Array { diff --git a/packages/app/src/System/RequestSplitter.ts b/packages/system/src/RequestSplitter.ts similarity index 77% rename from packages/app/src/System/RequestSplitter.ts rename to packages/system/src/RequestSplitter.ts index e1a3d653..0210a442 100644 --- a/packages/app/src/System/RequestSplitter.ts +++ b/packages/system/src/RequestSplitter.ts @@ -1,5 +1,5 @@ -import { ReqFilter } from "System"; -import { deepEqual } from "./Util"; +import { ReqFilter } from "."; +import { flatReqFilterEq } from "./Util"; import { expandFilter } from "./RequestExpander"; import { flatMerge } from "./RequestMerger"; @@ -7,8 +7,8 @@ export function diffFilters(prev: Array, next: Array) { const prevExpanded = prev.flatMap(expandFilter); const nextExpanded = next.flatMap(expandFilter); - const added = flatMerge(nextExpanded.filter(a => !prevExpanded.some(b => deepEqual(a, b)))); - const removed = flatMerge(prevExpanded.filter(a => !nextExpanded.some(b => deepEqual(a, b)))); + const added = flatMerge(nextExpanded.filter(a => !prevExpanded.some(b => flatReqFilterEq(a, b)))); + const removed = flatMerge(prevExpanded.filter(a => !nextExpanded.some(b => flatReqFilterEq(a, b)))); return { added, diff --git a/packages/app/src/System/SystemWorker.ts b/packages/system/src/SystemWorker.ts similarity index 80% rename from packages/app/src/System/SystemWorker.ts rename to packages/system/src/SystemWorker.ts index fb80e02a..ab9dfaf3 100644 --- a/packages/app/src/System/SystemWorker.ts +++ b/packages/system/src/SystemWorker.ts @@ -1,15 +1,10 @@ -import ExternalStore from "ExternalStore"; -import { - NoteStore, - Query, - NostrEvent, - RelaySettings, - RequestBuilder, - SystemSnapshot, - SystemInterface, - ConnectionStateSnapshot, - AuthHandler, -} from "System"; +import { SystemSnapshot, SystemInterface } from "."; +import { AuthHandler, ConnectionStateSnapshot, RelaySettings } from "./Connection"; +import ExternalStore from "./ExternalStore"; +import { NostrEvent } from "./Nostr"; +import { NoteStore } from "./NoteCollection"; +import { Query } from "./Query"; +import { RequestBuilder } from "./RequestBuilder"; export class SystemWorker extends ExternalStore implements SystemInterface { #port: MessagePort; diff --git a/packages/app/src/System/Tag.ts b/packages/system/src/Tag.ts similarity index 100% rename from packages/app/src/System/Tag.ts rename to packages/system/src/Tag.ts diff --git a/packages/system/src/Util.ts b/packages/system/src/Util.ts new file mode 100644 index 00000000..5d177f71 --- /dev/null +++ b/packages/system/src/Util.ts @@ -0,0 +1,104 @@ +import * as utils from "@noble/curves/abstract/utils"; +import * as secp from "@noble/curves/secp256k1"; +import { sha256 as sha2 } from "@noble/hashes/sha256"; +import { bech32 } from "bech32"; +import { NostrEvent, u256 } from "./Nostr"; +import { FlatReqFilter } from "RequestExpander"; + +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 + } +} + +export function unixNow() { + return Math.floor(unixNowMs() / 1000); +} + +export function unixNowMs() { + return new Date().getTime(); +} + +export function deepEqual(x: any, y: any): boolean { + const ok = Object.keys, + tx = typeof x, + ty = typeof y; + + return x && y && tx === "object" && tx === ty + ? ok(x).length === ok(y).length && ok(x).every(key => deepEqual(x[key], y[key])) + : x === y; +} + +export function flatReqFilterEq(a: FlatReqFilter, b: FlatReqFilter): boolean { + return a.ids === b.ids + && a.kinds === b.kinds + && a.authors === b.authors + && a.limit === b.limit + && a.since === b.since + && a.until === b.until + && a.search === b.search + && a["#e"] === b["#e"] + && a["#p"] === b["#p"] + && a["#t"] === b["#t"] + && a["#d"] === b["#d"] + && a["#r"] === b["#r"]; +} + +export function dedupe(v: Array) { + return [...new Set(v)]; +} + +export function appendDedupe(a?: Array, b?: Array) { + return dedupe([...(a ?? []), ...(b ?? [])]); +} + +export function findTag(e: NostrEvent, tag: string) { + const maybeTag = e.tags.find(evTag => { + return evTag[0] === tag; + }); + return maybeTag && maybeTag[1]; +} + +export const sha256 = (str: string | Uint8Array): u256 => { + return utils.bytesToHex(sha2(str)); +} + +export function getPublicKey(privKey: string) { + return utils.bytesToHex(secp.schnorr.getPublicKey(privKey)); +} + +export function bech32ToHex(str: string) { + try { + const nKey = bech32.decode(str, 1_000); + const buff = bech32.fromWords(nKey.words); + return utils.bytesToHex(Uint8Array.from(buff)); + } catch (e) { + return str; + } +} diff --git a/packages/system/src/WorkQueue.ts b/packages/system/src/WorkQueue.ts new file mode 100644 index 00000000..37df22e0 --- /dev/null +++ b/packages/system/src/WorkQueue.ts @@ -0,0 +1,30 @@ +export interface WorkQueueItem { + next: () => Promise; + resolve(v: unknown): void; + reject(e: unknown): void; +} + +export async function processWorkQueue(queue?: Array, queueDelay = 200) { + while (queue && queue.length > 0) { + const v = queue.shift(); + if (v) { + try { + const ret = await v.next(); + v.resolve(ret); + } catch (e) { + v.reject(e); + } + } + } + setTimeout(() => processWorkQueue(queue, queueDelay), queueDelay); +} + +export const barrierQueue = async (queue: Array, then: () => Promise): Promise => { + return new Promise((resolve, reject) => { + queue.push({ + next: then, + resolve, + reject, + }); + }); +}; diff --git a/packages/system/src/cache/index.ts b/packages/system/src/cache/index.ts new file mode 100644 index 00000000..431318a0 --- /dev/null +++ b/packages/system/src/cache/index.ts @@ -0,0 +1,68 @@ +import { HexKey, NostrEvent, UserMetadata } from ".."; +import { hexToBech32, unixNowMs } from "../Util"; + +export interface MetadataCache extends UserMetadata { + /** + * When the object was saved in cache + */ + loaded: number; + + /** + * When the source metadata event was created + */ + created: number; + + /** + * The pubkey of the owner of this metadata + */ + pubkey: HexKey; + + /** + * The bech32 encoded pubkey + */ + npub: string; + + /** + * Pubkey of zapper service + */ + zapService?: HexKey; + + /** + * If the nip05 is valid for this user + */ + isNostrAddressValid: boolean; +} + +export function mapEventToProfile(ev: NostrEvent) { + try { + const data: UserMetadata = JSON.parse(ev.content); + return { + ...data, + pubkey: ev.pubkey, + npub: hexToBech32("npub", ev.pubkey), + created: ev.created_at, + loaded: unixNowMs(), + } as MetadataCache; + } catch (e) { + console.error("Failed to parse JSON", ev, e); + } +} + +export interface CacheStore { + preload(): Promise; + getFromCache(key?: string): T | undefined; + get(key?: string): Promise; + bulkGet(keys: Array): Promise>; + set(obj: T): Promise; + bulkSet(obj: Array): Promise; + update(m: TCachedWithCreated): Promise<"new" | "updated" | "refresh" | "no_change"> + + /** + * Loads a list of rows from disk cache + * @param keys List of ids to load + * @returns Keys that do not exist on disk cache + */ + buffer(keys: Array): Promise>; + + clear(): Promise; +} diff --git a/packages/app/src/System/index.ts b/packages/system/src/index.ts similarity index 58% rename from packages/app/src/System/index.ts rename to packages/system/src/index.ts index fd40e81e..4f11456c 100644 --- a/packages/app/src/System/index.ts +++ b/packages/system/src/index.ts @@ -1,13 +1,6 @@ -import { AuthHandler, Connection, RelaySettings, ConnectionStateSnapshot } from "./Connection"; +import { AuthHandler, RelaySettings, ConnectionStateSnapshot } from "./Connection"; import { RequestBuilder } from "./RequestBuilder"; -import { EventBuilder } from "./EventBuilder"; -import { - FlatNoteStore, - NoteStore, - PubkeyReplaceableNoteStore, - ParameterizedReplaceableNoteStore, - ReplaceableNoteStore, -} from "./NoteCollection"; +import { NoteStore } from "./NoteCollection"; import { Query } from "./Query"; import { NostrEvent, ReqFilter } from "./Nostr"; @@ -18,6 +11,15 @@ export * from "./Links"; export { default as Tag } from "./Tag"; export * from "./Nips"; export * from "./RelayInfo"; +export * from "./EventExt"; +export * from "./Connection"; +export * from "./NoteCollection"; +export * from "./RequestBuilder"; +export * from "./EventPublisher"; +export * from "./EventBuilder"; +export * from "./NostrLink"; +export * from "./cache"; +export * from "./ProfileCache"; export interface SystemInterface { /** @@ -26,7 +28,7 @@ export interface SystemInterface { HandleAuth?: AuthHandler; get Sockets(): Array; GetQuery(id: string): Query | undefined; - Query(type: { new (): T }, req: RequestBuilder | null): Query | undefined; + Query(type: { new(): T }, req: RequestBuilder | null): Query | undefined; ConnectToRelay(address: string, options: RelaySettings): Promise; DisconnectRelay(address: string): void; BroadcastEvent(ev: NostrEvent): void; @@ -39,19 +41,4 @@ export interface SystemSnapshot { filters: Array; subFilters: Array; }>; -} - -export { - NoteStore, - RequestBuilder, - FlatNoteStore, - PubkeyReplaceableNoteStore, - ParameterizedReplaceableNoteStore, - ReplaceableNoteStore, - Query, - EventBuilder, - AuthHandler, - Connection, - RelaySettings, - ConnectionStateSnapshot, -}; +} \ No newline at end of file diff --git a/packages/app/src/System/NoteCollection.test.ts b/packages/system/tests/NoteCollection.test.ts similarity index 93% rename from packages/app/src/System/NoteCollection.test.ts rename to packages/system/tests/NoteCollection.test.ts index 00eb6086..613bc63d 100644 --- a/packages/app/src/System/NoteCollection.test.ts +++ b/packages/system/tests/NoteCollection.test.ts @@ -1,6 +1,6 @@ -import { TaggedRawEvent } from "System"; +import { TaggedRawEvent } from "../src/Nostr"; import { describe, expect } from "@jest/globals"; -import { FlatNoteStore, ReplaceableNoteStore } from "./NoteCollection"; +import { FlatNoteStore, ReplaceableNoteStore } from "../src/NoteCollection"; describe("NoteStore", () => { describe("flat", () => { diff --git a/packages/app/src/System/Query.test.ts b/packages/system/tests/Query.test.ts similarity index 92% rename from packages/app/src/System/Query.test.ts rename to packages/system/tests/Query.test.ts index ad80a1a4..3c026319 100644 --- a/packages/app/src/System/Query.test.ts +++ b/packages/system/tests/Query.test.ts @@ -1,9 +1,9 @@ -import { Connection } from "System"; +import { Connection } from "../src"; import { describe, expect } from "@jest/globals"; -import { Query, QueryBase } from "./Query"; +import { Query } from "../src/Query"; import { getRandomValues } from "crypto"; -import { FlatNoteStore } from "./NoteCollection"; -import { RequestStrategy } from "./RequestBuilder"; +import { FlatNoteStore } from "../src/NoteCollection"; +import { RequestStrategy } from "../src/RequestBuilder"; window.crypto = {} as any; window.crypto.getRandomValues = getRandomValues as any; diff --git a/packages/app/src/System/RequestBuilder.test.ts b/packages/system/tests/RequestBuilder.test.ts similarity index 97% rename from packages/app/src/System/RequestBuilder.test.ts rename to packages/system/tests/RequestBuilder.test.ts index e2546cda..b0ec6f99 100644 --- a/packages/app/src/System/RequestBuilder.test.ts +++ b/packages/system/tests/RequestBuilder.test.ts @@ -1,5 +1,5 @@ -import { RelayCache } from "./GossipModel"; -import { RequestBuilder, RequestStrategy } from "./RequestBuilder"; +import { RelayCache } from "../src/GossipModel"; +import { RequestBuilder, RequestStrategy } from "../src/RequestBuilder"; import { describe, expect } from "@jest/globals"; const DummyCache = { diff --git a/packages/app/src/System/RequestExpander.test.ts b/packages/system/tests/RequestExpander.test.ts similarity index 96% rename from packages/app/src/System/RequestExpander.test.ts rename to packages/system/tests/RequestExpander.test.ts index 0ccb58d1..d87faf7b 100644 --- a/packages/app/src/System/RequestExpander.test.ts +++ b/packages/system/tests/RequestExpander.test.ts @@ -1,4 +1,4 @@ -import { expandFilter } from "./RequestExpander"; +import { expandFilter } from "../src/RequestExpander"; describe("RequestExpander", () => { test("expand filter", () => { diff --git a/packages/app/src/System/RequestMatcher.test.ts b/packages/system/tests/RequestMatcher.test.ts similarity index 88% rename from packages/app/src/System/RequestMatcher.test.ts rename to packages/system/tests/RequestMatcher.test.ts index 49754ff5..0b7a4b39 100644 --- a/packages/app/src/System/RequestMatcher.test.ts +++ b/packages/system/tests/RequestMatcher.test.ts @@ -1,4 +1,4 @@ -import { eventMatchesFilter } from "./RequestMatcher"; +import { eventMatchesFilter } from "../src/RequestMatcher"; describe("RequestMatcher", () => { it("should match simple filter", () => { diff --git a/packages/app/src/System/RequestMerger.test.ts b/packages/system/tests/RequestMerger.test.ts similarity index 93% rename from packages/app/src/System/RequestMerger.test.ts rename to packages/system/tests/RequestMerger.test.ts index b2020d36..d9ac51cf 100644 --- a/packages/app/src/System/RequestMerger.test.ts +++ b/packages/system/tests/RequestMerger.test.ts @@ -1,7 +1,7 @@ -import { ReqFilter } from "System"; -import { filterIncludes, flatMerge, mergeSimilar, simpleMerge } from "./RequestMerger"; -import { FlatReqFilter, expandFilter } from "./RequestExpander"; -import { distance } from "./Util"; +import { ReqFilter } from "../src"; +import { filterIncludes, flatMerge, mergeSimilar, simpleMerge } from "../src/RequestMerger"; +import { FlatReqFilter, expandFilter } from "../src/RequestExpander"; +import { distance } from "../src/Util"; describe("RequestMerger", () => { it("should simple merge authors", () => { diff --git a/packages/app/src/System/RequestSplitter.test.ts b/packages/system/tests/RequestSplitter.test.ts similarity index 96% rename from packages/app/src/System/RequestSplitter.test.ts rename to packages/system/tests/RequestSplitter.test.ts index e6caa5a7..7e639e34 100644 --- a/packages/app/src/System/RequestSplitter.test.ts +++ b/packages/system/tests/RequestSplitter.test.ts @@ -1,6 +1,6 @@ -import { ReqFilter } from "System"; +import { ReqFilter } from "../src"; import { describe, expect } from "@jest/globals"; -import { diffFilters } from "./RequestSplitter"; +import { diffFilters } from "../src/RequestSplitter"; describe("RequestSplitter", () => { test("single filter add value", () => { diff --git a/packages/system/tests/Util.test.ts b/packages/system/tests/Util.test.ts new file mode 100644 index 00000000..baebd7a2 --- /dev/null +++ b/packages/system/tests/Util.test.ts @@ -0,0 +1,117 @@ +import { distance } from "../src/Util"; + +describe("distance", () => { + it("should have 0 distance", () => { + const a = { + ids: "a", + }; + const b = { + ids: "a", + }; + expect(distance(a, b)).toEqual(0); + }); + it("should have 1 distance", () => { + const a = { + ids: "a", + }; + const b = { + ids: "b", + }; + expect(distance(a, b)).toEqual(1); + }); + it("should have 10 distance", () => { + const a = { + ids: "a", + }; + const b = { + ids: "a", + kinds: 1, + }; + expect(distance(a, b)).toEqual(10); + }); + it("should have 11 distance", () => { + const a = { + ids: "a", + }; + const b = { + ids: "b", + kinds: 1, + }; + expect(distance(a, b)).toEqual(11); + }); + it("should have 1 distance, arrays", () => { + const a = { + since: 1, + until: 100, + kinds: [1], + authors: ["kieran", "snort", "c", "d", "e"], + }; + const b = { + since: 1, + until: 100, + kinds: [6969], + authors: ["kieran", "snort", "c", "d", "e"], + }; + expect(distance(a, b)).toEqual(1); + }); + it("should have 1 distance, array change extra", () => { + const a = { + since: 1, + until: 100, + kinds: [1], + authors: ["f", "kieran", "snort", "c", "d"], + }; + const b = { + since: 1, + until: 100, + kinds: [1], + authors: ["kieran", "snort", "c", "d", "e"], + }; + expect(distance(a, b)).toEqual(1); + }); +}); + + +describe("tryParseNostrLink", () => { + it("is a valid nostr link", () => { + expect(parseNostrLink("nostr:npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg")).toMatchObject({ + id: "7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e", + type: NostrPrefix.PublicKey, + }); + expect(parseNostrLink("web+nostr:npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg")).toMatchObject({ + id: "7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e", + type: NostrPrefix.PublicKey, + }); + expect(parseNostrLink("nostr:note15449edq4qa5wzgqvh8td0q0dp6hwtes4pknsrm7eygeenhlj99xsq94wu9")).toMatchObject({ + id: "a56a5cb4150768e1200cb9d6d781ed0eaee5e6150da701efd9223399dff2294d", + type: NostrPrefix.Note, + }); + expect( + parseNostrLink( + "nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p" + ) + ).toMatchObject({ + id: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", + type: NostrPrefix.Profile, + relays: ["wss://r.x.com", "wss://djbas.sadkb.com"], + }); + expect(parseNostrLink("nostr:nevent1qqs226juks2sw68pyqxtn4khs8ksath9uc2smfcpalvjyvuemlezjngrd87dq")).toMatchObject({ + id: "a56a5cb4150768e1200cb9d6d781ed0eaee5e6150da701efd9223399dff2294d", + type: NostrPrefix.Event, + }); + expect( + parseNostrLink( + "nostr:naddr1qqzkjurnw4ksz9thwden5te0wfjkccte9ehx7um5wghx7un8qgs2d90kkcq3nk2jry62dyf50k0h36rhpdtd594my40w9pkal876jxgrqsqqqa28pccpzu" + ) + ).toMatchObject({ + id: "ipsum", + type: NostrPrefix.Address, + relays: ["wss://relay.nostr.org"], + author: "a695f6b60119d9521934a691347d9f78e8770b56da16bb255ee286ddf9fda919", + kind: 30023, + }); + }); + test.each(["nostr:npub", "web+nostr:npub", "nostr:nevent1xxx"])("should return false for invalid nostr links", lb => { + expect(tryParseNostrLink(lb)).toBeUndefined(); + }); +}); diff --git a/packages/system/tests/setupTests.ts b/packages/system/tests/setupTests.ts new file mode 100644 index 00000000..4930f245 --- /dev/null +++ b/packages/system/tests/setupTests.ts @@ -0,0 +1,3 @@ +import { TextEncoder, TextDecoder } from "util"; + +Object.assign(global, { TextDecoder, TextEncoder }); diff --git a/packages/system/tsconfig.json b/packages/system/tsconfig.json new file mode 100644 index 00000000..dca184be --- /dev/null +++ b/packages/system/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "baseUrl": "src", + "target": "ES2020", + "moduleResolution": "node", + "esModuleInterop": true, + "noImplicitOverride": true, + "module": "CommonJS", + "strict": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "dist", + "skipLibCheck": true + }, + "include": ["src"], + "files": ["src/index.ts"] +} diff --git a/packages/app/src/System/worker.ts b/packages/system/worker.ts similarity index 88% rename from packages/app/src/System/worker.ts rename to packages/system/worker.ts index 4f47dad8..88315188 100644 --- a/packages/app/src/System/worker.ts +++ b/packages/system/worker.ts @@ -1,5 +1,5 @@ /// -import { UsersRelaysCache } from "Cache/UserRelayCache"; +import { UsersRelaysCache } from "../Cache/UserRelayCache"; import { NostrSystem } from "."; declare const self: SharedWorkerGlobalScope;