Sign events from NIP-07 ext

This commit is contained in:
Kieran 2022-12-29 15:21:03 +00:00
parent 987ef0ed7b
commit 523d1951fa
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
8 changed files with 131 additions and 38 deletions

View File

@ -3,6 +3,9 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^6.2.1",
"@fortawesome/react-fontawesome": "^0.2.0",
"@noble/secp256k1": "^1.7.0", "@noble/secp256k1": "^1.7.0",
"@reduxjs/toolkit": "^1.9.1", "@reduxjs/toolkit": "^1.9.1",
"bech32": "^2.0.0", "bech32": "^2.0.0",

View File

@ -39,12 +39,17 @@ code {
user-select: none; user-select: none;
background-color: #000; background-color: #000;
border: 1px solid; border: 1px solid;
display: inline-block;
} }
.btn:hover { .btn:hover {
background-color: #333; background-color: #333;
} }
.btn-sm {
padding: 5px;
}
input[type="text"], input[type="password"] { input[type="text"], input[type="password"] {
padding: 10px; padding: 10px;
border-radius: 5px; border-radius: 5px;
@ -93,6 +98,7 @@ div.form-group {
div.form-group > div { div.form-group > div {
padding: 3px 5px; padding: 3px 5px;
word-break: break-word;
} }
div.form-group > div:first-child { div.form-group > div:first-child {

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { setPrivateKey, setPublicKey } from "../state/Login"; import { setPrivateKey, setNip07PubKey } from "../state/Login";
import * as secp from '@noble/secp256k1'; import * as secp from '@noble/secp256k1';
import { bech32 } from "bech32"; import { bech32 } from "bech32";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
@ -32,12 +32,11 @@ export default function LoginPage() {
async function doNip07Login() { async function doNip07Login() {
let pubKey = await window.nostr.getPublicKey(); let pubKey = await window.nostr.getPublicKey();
dispatch(setPublicKey(pubKey)); dispatch(setNip07PubKey(pubKey));
} }
function altLogins() { function altLogins() {
let nip07 = 'nostr' in window; let nip07 = 'nostr' in window;
if (!nip07) { if (!nip07) {
return null; return null;
} }

View File

@ -8,8 +8,8 @@
} }
.profile .avatar { .profile .avatar {
width: 256px; width: 128px;
height: 256px; height: 128px;
background-size: contain; background-size: contain;
cursor: pointer; cursor: pointer;
} }
@ -26,4 +26,15 @@
.profile .avatar .edit:hover { .profile .avatar .edit:hover {
opacity: 0.5; opacity: 0.5;
} }
@media(max-width: 720px) {
.profile {
flex-direction: column;
align-items: center;
}
.profile > div:last-child {
margin: 0;
width: 100%;
}
}

View File

@ -8,6 +8,9 @@ import Nostrich from "../nostrich.jpg";
import useEventPublisher from "./feed/EventPublisher"; import useEventPublisher from "./feed/EventPublisher";
import useTimelineFeed from "./feed/TimelineFeed"; import useTimelineFeed from "./feed/TimelineFeed";
import Note from "../element/Note"; import Note from "../element/Note";
import { bech32 } from "bech32";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faQrcode } from "@fortawesome/free-solid-svg-icons";
export default function ProfilePage() { export default function ProfilePage() {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -37,6 +40,23 @@ export default function ProfilePage() {
} }
}, [user]); }, [user]);
useEffect(() => {
// some clients incorrectly set this to LNURL service, patch this
if (lud16.toLowerCase().startsWith("lnurl")) {
let decoded = bech32.decode(lud16, 1000);
let url = new TextDecoder().decode(Uint8Array.from(bech32.fromWords(decoded.words)));
if (url.startsWith("http")) {
let parsedUri = new URL(url);
// is lightning address
if (parsedUri.pathname.startsWith("/.well-known/lnurlp/")) {
let pathParts = parsedUri.pathname.split('/');
let username = pathParts[pathParts.length - 1];
setLud16(`${username}@${parsedUri.hostname}`);
}
}
}
}, [lud16]);
async function saveProfile() { async function saveProfile() {
let ev = await publisher.metadata({ let ev = await publisher.metadata({
name, name,
@ -79,7 +99,7 @@ export default function ProfilePage() {
</div> </div>
</div> </div>
<div className="form-group"> <div className="form-group">
<div>Lightning Address:</div> <div>LN Address:</div>
<div> <div>
<input type="text" value={lud16} onChange={(e) => setLud16(e.target.value)} /> <input type="text" value={lud16} onChange={(e) => setLud16(e.target.value)} />
</div> </div>
@ -116,18 +136,23 @@ export default function ProfilePage() {
{website} {website}
</div> </div>
</div> : null} </div> : null}
<div className="form-group"> {nip05 ?
<div>NIP-05:</div> <div className="form-group">
<div> <div>NIP-05:</div>
{nip05} <div>
</div> {nip05}
</div> </div>
<div className="form-group"> </div> : null}
<div>Lightning Address:</div> {lud16 ?
<div> <div className="form-group">
{lud16} <div>LN Address:</div>
</div> <div>
</div> {lud16}&nbsp;
<div className="btn btn-sm" onClick={() => { }}>
<FontAwesomeIcon icon={faQrcode} size="lg" />
</div>
</div>
</div> : null}
</> </>
) )
} }
@ -135,15 +160,13 @@ export default function ProfilePage() {
return ( return (
<> <>
<div className="profile"> <div className="profile">
<div> <div style={{ backgroundImage: `url(${picture})` }} className="avatar">
<div style={{ backgroundImage: `url(${picture})` }} className="avatar"> {isMe ?
{isMe ? <div className="edit">
<div className="edit"> <div>Edit</div>
<div>Edit</div> </div>
</div> : null
: null }
}
</div>
</div> </div>
<div> <div>
{isMe ? editor() : details()} {isMe ? editor() : details()}

View File

@ -9,6 +9,26 @@ export default function useEventPublisher() {
const system = useContext(NostrContext); const system = useContext(NostrContext);
const pubKey = useSelector(s => s.login.publicKey); const pubKey = useSelector(s => s.login.publicKey);
const privKey = useSelector(s => s.login.privateKey); const privKey = useSelector(s => s.login.privateKey);
const nip07 = useSelector(s => s.login.nip07);
const hasNip07 = 'nostr' in window;
/**
*
* @param {Event} ev
* @param {*} privKey
* @returns
*/
async function signEvent(ev, privKey) {
if(nip07 === true && hasNip07) {
ev.Id = await ev.CreateId();
let tmpEv = await window.nostr.signEvent(ev.ToObject());
console.log(tmpEv);
return Event.FromObject(tmpEv);
} else {
await ev.Sign(privKey);
}
return ev;
}
return { return {
broadcast: (ev) => { broadcast: (ev) => {
@ -19,8 +39,7 @@ export default function useEventPublisher() {
let ev = Event.ForPubKey(pubKey); let ev = Event.ForPubKey(pubKey);
ev.Kind = EventKind.SetMetadata; ev.Kind = EventKind.SetMetadata;
ev.Content = JSON.stringify(obj); ev.Content = JSON.stringify(obj);
await ev.Sign(privKey); return await signEvent(ev, privKey);
return ev;
}, },
note: async (msg) => { note: async (msg) => {
if(typeof msg !== "string") { if(typeof msg !== "string") {
@ -29,8 +48,7 @@ export default function useEventPublisher() {
let ev = Event.ForPubKey(pubKey); let ev = Event.ForPubKey(pubKey);
ev.Kind = EventKind.TextNote; ev.Kind = EventKind.TextNote;
ev.Content = msg; ev.Content = msg;
await ev.Sign(privKey); return await signEvent(ev, privKey);
return ev;
}, },
like: async (evRef) => { like: async (evRef) => {
let ev = Event.ForPubKey(pubKey); let ev = Event.ForPubKey(pubKey);
@ -38,8 +56,7 @@ export default function useEventPublisher() {
ev.Content = "+"; ev.Content = "+";
ev.Tags.push(new Tag(["e", evRef.Id], 0)); ev.Tags.push(new Tag(["e", evRef.Id], 0));
ev.Tags.push(new Tag(["p", evRef.PubKey], 1)); ev.Tags.push(new Tag(["p", evRef.PubKey], 1));
await ev.Sign(privKey); return await signEvent(ev, privKey);
return ev;
}, },
dislike: async (evRef) => { dislike: async (evRef) => {
let ev = Event.ForPubKey(pubKey); let ev = Event.ForPubKey(pubKey);
@ -47,8 +64,7 @@ export default function useEventPublisher() {
ev.Content = "-"; ev.Content = "-";
ev.Tags.push(new Tag(["e", evRef.Id], 0)); ev.Tags.push(new Tag(["e", evRef.Id], 0));
ev.Tags.push(new Tag(["p", evRef.PubKey], 1)); ev.Tags.push(new Tag(["p", evRef.PubKey], 1));
await ev.Sign(privKey); return await signEvent(ev, privKey);
return ev;
} }
} }
} }

View File

@ -24,7 +24,12 @@ const LoginSlice = createSlice({
/** /**
* A list of pubkeys this user follows * A list of pubkeys this user follows
*/ */
follows: [] follows: [],
/**
* Login keys are managed by extension
*/
nip07: false,
}, },
reducers: { reducers: {
init: (state) => { init: (state) => {
@ -47,6 +52,10 @@ const LoginSlice = createSlice({
setPublicKey: (state, action) => { setPublicKey: (state, action) => {
state.publicKey = action.payload; state.publicKey = action.payload;
}, },
setNip07PubKey: (state, action) => {
state.publicKey = action.payload;
state.nip07 = true;
},
setRelays: (state, action) => { setRelays: (state, action) => {
state.relays = action.payload; state.relays = action.payload;
}, },
@ -60,5 +69,5 @@ const LoginSlice = createSlice({
} }
}); });
export const { init, setPrivateKey, setPublicKey, setRelays, setFollows, logout } = LoginSlice.actions; export const { init, setPrivateKey, setPublicKey, setNip07PubKey, setRelays, setFollows, logout } = LoginSlice.actions;
export const reducer = LoginSlice.reducer; export const reducer = LoginSlice.reducer;

View File

@ -1200,6 +1200,32 @@
minimatch "^3.1.2" minimatch "^3.1.2"
strip-json-comments "^3.1.1" strip-json-comments "^3.1.1"
"@fortawesome/fontawesome-common-types@6.2.1":
version "6.2.1"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.2.1.tgz#411e02a820744d3f7e0d8d9df9d82b471beaa073"
integrity sha512-Sz07mnQrTekFWLz5BMjOzHl/+NooTdW8F8kDQxjWwbpOJcnoSg4vUDng8d/WR1wOxM0O+CY9Zw0nR054riNYtQ==
"@fortawesome/fontawesome-svg-core@^6.2.1":
version "6.2.1"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.2.1.tgz#e87e905e444b5e7b715af09b64d27b53d4c8f9d9"
integrity sha512-HELwwbCz6C1XEcjzyT1Jugmz2NNklMrSPjZOWMlc+ZsHIVk+XOvOXLGGQtFBwSyqfJDNgRq4xBCwWOaZ/d9DEA==
dependencies:
"@fortawesome/fontawesome-common-types" "6.2.1"
"@fortawesome/free-solid-svg-icons@^6.2.1":
version "6.2.1"
resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.2.1.tgz#2290ea5adcf1537cbd0c43de6feb38af02141d27"
integrity sha512-oKuqrP5jbfEPJWTij4sM+/RvgX+RMFwx3QZCZcK9PrBDgxC35zuc7AOFsyMjMd/PIFPeB2JxyqDr5zs/DZFPPw==
dependencies:
"@fortawesome/fontawesome-common-types" "6.2.1"
"@fortawesome/react-fontawesome@^0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz#d90dd8a9211830b4e3c08e94b63a0ba7291ddcf4"
integrity sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==
dependencies:
prop-types "^15.8.1"
"@humanwhocodes/config-array@^0.11.6": "@humanwhocodes/config-array@^0.11.6":
version "0.11.8" version "0.11.8"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9"