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 { 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 <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": {
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() {
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))}`;
}

View File

@ -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));
}
}

View File

@ -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 (
<div className="pfp">
<img src={hasImage ? user.picture : Nostrich} onClick={() => navigate(`/p/${pubkey}`)} />
<img src={hasImage ? user.picture : Nostrich} onClick={() => navigate(profileLink(pubkey))} />
<div>
<Link key={pubkey} to={`/p/${pubkey}`}>{name}</Link>
<Link key={pubkey} to={profileLink(pubkey)}>{name}</Link>
{subHeader ? <div>{subHeader}</div> : null}
</div>
</div>

View File

@ -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 (
<>
<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>
{renderChain(a)}
</>

View File

@ -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);

View File

@ -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)}`);

View File

@ -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);