Use bech32 links for events/profiles
This commit is contained in:
parent
25c292f47c
commit
f864b682f5
@ -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>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
42
src/Util.js
42
src/Util.js
@ -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))}`;
|
||||||
|
}
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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)}
|
||||||
</>
|
</>
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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)}`);
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user