diff --git a/src/Text.js b/src/Text.js index 591a5e31..9144b545 100644 --- a/src/Text.js +++ b/src/Text.js @@ -2,6 +2,7 @@ import { Link } from "react-router-dom"; import Invoice from "./element/Invoice"; import { UrlRegex, FileExtensionRegex, MentionRegex, InvoiceRegex } from "./Const"; +import { eventLink, profileLink } from "./Util"; export function extractLinks(fragments) { return fragments.map(f => { @@ -54,11 +55,11 @@ export function extractMentions(fragments, tags, users) { switch (ref.Key) { case "p": { let pUser = users[ref.PubKey]?.name ?? ref.PubKey.substring(0, 8); - return ev.stopPropagation()}>@{pUser}; + return ev.stopPropagation()}>@{pUser}; } case "e": { let eText = ref.Event.substring(0, 8); - return #{eText}; + return #{eText}; } } } diff --git a/src/Util.js b/src/Util.js index 7fc8f003..d1a7a2ba 100644 --- a/src/Util.js +++ b/src/Util.js @@ -1,3 +1,5 @@ +import * as secp from "@noble/secp256k1"; +import { bech32 } from "bech32"; export async function openFile() { return new Promise((resolve, reject) => { @@ -9,3 +11,43 @@ export async function openFile() { elm.click(); }); } + +/** + * Parse bech32 ids + * @param {string} id bech32 id + */ +export function parseId(id) { + const hrp = ["note1", "npub", "nsec"]; + try { + if (hrp.some(a => id.startsWith(a))) { + return bech32ToHex(id); + } + } catch (e) { } + return id; +} + +export function bech32ToHex(str) { + let nKey = bech32.decode(str); + let buff = bech32.fromWords(nKey.words); + return secp.utils.bytesToHex(Uint8Array.from(buff)); +} + +/** + * Convert hex note id to bech32 link url + * @param {string} hex + * @returns + */ +export function eventLink(hex) { + let buf = secp.utils.hexToBytes(hex); + return `/e/${bech32.encode("note1", bech32.toWords(buf))}`; +} + +/** + * Convert hex pubkey to bech32 link url + * @param {string} hex + * @returns + */ +export function profileLink(hex) { + let buf = secp.utils.hexToBytes(hex); + return `/p/${bech32.encode("npub", bech32.toWords(buf))}`; +} \ No newline at end of file diff --git a/src/element/Note.js b/src/element/Note.js index 6b63b956..64df27b6 100644 --- a/src/element/Note.js +++ b/src/element/Note.js @@ -11,6 +11,7 @@ import ProfileImage from "./ProfileImage"; import useEventPublisher from "../feed/EventPublisher"; import { NoteCreator } from "./NoteCreator"; import { extractLinks, extractMentions, extractInvoices } from "../Text"; +import { eventLink } from "../Util"; export default function Note(props) { const navigate = useNavigate(); @@ -52,7 +53,7 @@ export default function Note(props) { function goToEvent(e, id) { if (!window.location.pathname.startsWith("/e/")) { e.stopPropagation(); - navigate(`/e/${id}`); + navigate(eventLink(id)); } } diff --git a/src/element/ProfileImage.js b/src/element/ProfileImage.js index e9b3c85c..977d574b 100644 --- a/src/element/ProfileImage.js +++ b/src/element/ProfileImage.js @@ -1,11 +1,10 @@ -import { useMemo } from "react"; - -import { Link, useNavigate } from "react-router-dom"; - -import useProfile from "../feed/ProfileFeed"; +import "./ProfileImage.css"; import Nostrich from "../nostrich.jpg"; -import "./ProfileImage.css"; +import { useMemo } from "react"; +import { Link, useNavigate } from "react-router-dom"; +import useProfile from "../feed/ProfileFeed"; +import { profileLink } from "../Util"; export default function ProfileImage(props) { const pubkey = props.pubkey; @@ -25,9 +24,9 @@ export default function ProfileImage(props) { }, [user]); return (
- navigate(`/p/${pubkey}`)} /> + navigate(profileLink(pubkey))} />
- {name} + {name} {subHeader ?
{subHeader}
: null}
diff --git a/src/element/Thread.js b/src/element/Thread.js index 471f5e69..62137228 100644 --- a/src/element/Thread.js +++ b/src/element/Thread.js @@ -2,6 +2,7 @@ import { useMemo } from "react"; import { Link } from "react-router-dom"; import Event from "../nostr/Event"; import EventKind from "../nostr/EventKind"; +import { eventLink } from "../Util"; import Note from "./Note"; import NoteGhost from "./NoteGhost"; @@ -81,7 +82,7 @@ export default function Thread(props) { return ( <> - Missing event {a.substring(0, 8)} + Missing event {a.substring(0, 8)} {renderChain(a)} diff --git a/src/pages/EventPage.js b/src/pages/EventPage.js index 6975094c..6a1794a0 100644 --- a/src/pages/EventPage.js +++ b/src/pages/EventPage.js @@ -2,10 +2,11 @@ import { useMemo } from "react"; import { useParams } from "react-router-dom"; import Thread from "../element/Thread"; import useThreadFeed from "../feed/ThreadFeed"; +import { parseId } from "../Util"; export default function EventPage() { const params = useParams(); - const id = params.id; + const id = parseId(params.id); const thread = useThreadFeed(id); diff --git a/src/pages/Login.js b/src/pages/Login.js index 806770f3..288e319e 100644 --- a/src/pages/Login.js +++ b/src/pages/Login.js @@ -6,6 +6,7 @@ import { bech32 } from "bech32"; import { setPrivateKey, setPublicKey } from "../state/Login"; import { EmailRegex } from "../Const"; +import { bech32ToHex } from "../Util"; export default function LoginPage() { const dispatch = useDispatch(); @@ -20,12 +21,6 @@ export default function LoginPage() { } }, [publicKey]); - function bech32ToHex(str) { - let nKey = bech32.decode(str); - let buff = bech32.fromWords(nKey.words); - return secp.utils.bytesToHex(Uint8Array.from(buff)); - } - async function getNip05PubKey(addr) { let [username, domain] = addr.split("@"); let rsp = await fetch(`https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(username)}`); diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js index fbf89844..b4f343f6 100644 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -15,14 +15,14 @@ import Modal from "../element/Modal"; import { logout } from "../state/Login"; import FollowButton from "../element/FollowButton"; import VoidUpload from "../feed/VoidUpload"; -import { openFile } from "../Util"; +import { openFile, parseId } from "../Util"; import Timeline from "../element/Timeline"; import { extractLinks } from '../Text' export default function ProfilePage() { const dispatch = useDispatch(); const params = useParams(); - const id = params.id; + const id = parseId(params.id); const user = useProfile(id); const publisher = useEventPublisher(); const loginPubKey = useSelector(s => s.login.publicKey);