Use bech32 links for events/profiles

This commit is contained in:
Kieran 2023-01-06 14:36:13 +00:00
parent 25c292f47c
commit f864b682f5
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
8 changed files with 61 additions and 21 deletions

View File

@ -2,6 +2,7 @@ import { Link } from "react-router-dom";
import Invoice from "./element/Invoice"; import Invoice from "./element/Invoice";
import { UrlRegex, FileExtensionRegex, MentionRegex, InvoiceRegex } from "./Const"; import { UrlRegex, FileExtensionRegex, MentionRegex, InvoiceRegex } from "./Const";
import { eventLink, profileLink } from "./Util";
export function extractLinks(fragments) { export function extractLinks(fragments) {
return fragments.map(f => { return fragments.map(f => {
@ -54,11 +55,11 @@ export function extractMentions(fragments, tags, users) {
switch (ref.Key) { switch (ref.Key) {
case "p": { case "p": {
let pUser = users[ref.PubKey]?.name ?? ref.PubKey.substring(0, 8); let pUser = users[ref.PubKey]?.name ?? ref.PubKey.substring(0, 8);
return <Link key={ref.PubKey} to={`/p/${ref.PubKey}`} onClick={(ev) => ev.stopPropagation()}>@{pUser}</Link>; return <Link key={ref.PubKey} to={profileLink(ref.PubKey)} onClick={(ev) => ev.stopPropagation()}>@{pUser}</Link>;
} }
case "e": { case "e": {
let eText = ref.Event.substring(0, 8); let eText = ref.Event.substring(0, 8);
return <Link key={ref.Event} to={`/e/${ref.Event}`}>#{eText}</Link>; return <Link key={ref.Event} to={eventLink(ref.Event)}>#{eText}</Link>;
} }
} }
} }

View File

@ -1,3 +1,5 @@
import * as secp from "@noble/secp256k1";
import { bech32 } from "bech32";
export async function openFile() { export async function openFile() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -9,3 +11,43 @@ export async function openFile() {
elm.click(); 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))}`;
}

View File

@ -11,6 +11,7 @@ import ProfileImage from "./ProfileImage";
import useEventPublisher from "../feed/EventPublisher"; import useEventPublisher from "../feed/EventPublisher";
import { NoteCreator } from "./NoteCreator"; import { NoteCreator } from "./NoteCreator";
import { extractLinks, extractMentions, extractInvoices } from "../Text"; import { extractLinks, extractMentions, extractInvoices } from "../Text";
import { eventLink } from "../Util";
export default function Note(props) { export default function Note(props) {
const navigate = useNavigate(); const navigate = useNavigate();
@ -52,7 +53,7 @@ export default function Note(props) {
function goToEvent(e, id) { function goToEvent(e, id) {
if (!window.location.pathname.startsWith("/e/")) { if (!window.location.pathname.startsWith("/e/")) {
e.stopPropagation(); e.stopPropagation();
navigate(`/e/${id}`); navigate(eventLink(id));
} }
} }

View File

@ -1,11 +1,10 @@
import { useMemo } from "react"; import "./ProfileImage.css";
import { Link, useNavigate } from "react-router-dom";
import useProfile from "../feed/ProfileFeed";
import Nostrich from "../nostrich.jpg"; 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) { export default function ProfileImage(props) {
const pubkey = props.pubkey; const pubkey = props.pubkey;
@ -25,9 +24,9 @@ export default function ProfileImage(props) {
}, [user]); }, [user]);
return ( return (
<div className="pfp"> <div className="pfp">
<img src={hasImage ? user.picture : Nostrich} onClick={() => navigate(`/p/${pubkey}`)} /> <img src={hasImage ? user.picture : Nostrich} onClick={() => navigate(profileLink(pubkey))} />
<div> <div>
<Link key={pubkey} to={`/p/${pubkey}`}>{name}</Link> <Link key={pubkey} to={profileLink(pubkey)}>{name}</Link>
{subHeader ? <div>{subHeader}</div> : null} {subHeader ? <div>{subHeader}</div> : null}
</div> </div>
</div> </div>

View File

@ -2,6 +2,7 @@ import { useMemo } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import Event from "../nostr/Event"; import Event from "../nostr/Event";
import EventKind from "../nostr/EventKind"; import EventKind from "../nostr/EventKind";
import { eventLink } from "../Util";
import Note from "./Note"; import Note from "./Note";
import NoteGhost from "./NoteGhost"; import NoteGhost from "./NoteGhost";
@ -81,7 +82,7 @@ export default function Thread(props) {
return ( return (
<> <>
<NoteGhost key={a}> <NoteGhost key={a}>
Missing event <Link to={`/e/${a}`}>{a.substring(0, 8)}</Link> Missing event <Link to={eventLink(a)}>{a.substring(0, 8)}</Link>
</NoteGhost> </NoteGhost>
{renderChain(a)} {renderChain(a)}
</> </>

View File

@ -2,10 +2,11 @@ import { useMemo } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import Thread from "../element/Thread"; import Thread from "../element/Thread";
import useThreadFeed from "../feed/ThreadFeed"; import useThreadFeed from "../feed/ThreadFeed";
import { parseId } from "../Util";
export default function EventPage() { export default function EventPage() {
const params = useParams(); const params = useParams();
const id = params.id; const id = parseId(params.id);
const thread = useThreadFeed(id); const thread = useThreadFeed(id);

View File

@ -6,6 +6,7 @@ import { bech32 } from "bech32";
import { setPrivateKey, setPublicKey } from "../state/Login"; import { setPrivateKey, setPublicKey } from "../state/Login";
import { EmailRegex } from "../Const"; import { EmailRegex } from "../Const";
import { bech32ToHex } from "../Util";
export default function LoginPage() { export default function LoginPage() {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -20,12 +21,6 @@ export default function LoginPage() {
} }
}, [publicKey]); }, [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) { async function getNip05PubKey(addr) {
let [username, domain] = addr.split("@"); let [username, domain] = addr.split("@");
let rsp = await fetch(`https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(username)}`); let rsp = await fetch(`https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(username)}`);

View File

@ -15,14 +15,14 @@ import Modal from "../element/Modal";
import { logout } from "../state/Login"; import { logout } from "../state/Login";
import FollowButton from "../element/FollowButton"; import FollowButton from "../element/FollowButton";
import VoidUpload from "../feed/VoidUpload"; import VoidUpload from "../feed/VoidUpload";
import { openFile } from "../Util"; import { openFile, parseId } from "../Util";
import Timeline from "../element/Timeline"; import Timeline from "../element/Timeline";
import { extractLinks } from '../Text' import { extractLinks } from '../Text'
export default function ProfilePage() { export default function ProfilePage() {
const dispatch = useDispatch(); const dispatch = useDispatch();
const params = useParams(); const params = useParams();
const id = params.id; const id = parseId(params.id);
const user = useProfile(id); const user = useProfile(id);
const publisher = useEventPublisher(); const publisher = useEventPublisher();
const loginPubKey = useSelector(s => s.login.publicKey); const loginPubKey = useSelector(s => s.login.publicKey);