diff --git a/package.json b/package.json index 532f55a..e269886 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,11 @@ }, "dependencies": { "@noble/hashes": "^1.4.0", - "@snort/shared": "^1.0.15", - "@snort/system": "^1.3.2", - "@snort/system-react": "^1.3.2", - "@snort/system-wasm": "^1.0.2", - "@snort/worker-relay": "^1.0.10", + "@snort/shared": "^1.0.17", + "@snort/system": "^1.6.0", + "@snort/system-react": "^1.6.0", + "@snort/system-wasm": "^1.0.5", + "@snort/worker-relay": "^1.3.0", "classnames": "^2.3.2", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/src/element/profile-image.tsx b/src/element/profile-image.tsx index 320030f..a32c72d 100644 --- a/src/element/profile-image.tsx +++ b/src/element/profile-image.tsx @@ -11,8 +11,13 @@ type ProfileImageProps = HTMLProps & { export function ProfileImage({ pubkey, size, withName, children, ...props }: ProfileImageProps) { const profile = useUserProfile(pubkey); + const url = + (profile?.picture?.length ?? 0) > 0 + ? profile?.picture + : `https://nostr.api.v0l.io/api/v1/avatar/cyberpunks/${pubkey}`; + const v = { - backgroundImage: `url(${profile?.picture})`, + backgroundImage: `url(${url})`, } as CSSProperties; if (size) { v.width = `${size}px`; diff --git a/src/element/search.tsx b/src/element/search.tsx index 188716e..f83273e 100644 --- a/src/element/search.tsx +++ b/src/element/search.tsx @@ -15,7 +15,6 @@ export function Search(params: { term?: string; tags?: Array }) { setTerm(e.target.value)} onKeyDown={(e) => { diff --git a/src/index.css b/src/index.css index 158882a..b846786 100644 --- a/src/index.css +++ b/src/index.css @@ -19,9 +19,11 @@ body { h1 { font-size: 32px; } + h2 { font-size: 28px; } + h3 { font-size: 21px; } @@ -46,3 +48,9 @@ input[type="radio"] { position: absolute; cursor: pointer; } + +input[type="text"], +input[type="number"], +input[type="password"] { + @apply px-4 py-2 rounded-xl bg-neutral-800 focus-visible:outline-none; +} diff --git a/src/login.tsx b/src/login.tsx index 872f0e6..1516c44 100644 --- a/src/login.tsx +++ b/src/login.tsx @@ -1,13 +1,17 @@ import { ExternalStore } from "@snort/shared"; -import { EventPublisher, Nip7Signer } from "@snort/system"; +import { EventPublisher, Nip46Signer, Nip7Signer, PrivateKeySigner } from "@snort/system"; import { SnortContext } from "@snort/system-react"; import { useContext, useSyncExternalStore } from "react"; export interface LoginSession { + type: "nip7" | "nsec" | "nip46"; publicKey: string; + privateKey?: string; + bunker?: string; } class LoginStore extends ExternalStore { #session?: LoginSession; + #signer?: EventPublisher; constructor() { super(); @@ -21,15 +25,70 @@ class LoginStore extends ExternalStore { return this.#session ? { ...this.#session } : undefined; } - login(pubkey: string) { + logout() { + this.#session = undefined; + this.#signer = undefined; + this.#save(); + } + + login(pubkey: string, type: LoginSession["type"] = "nip7") { this.#session = { + type: type ?? "nip7", publicKey: pubkey, }; this.#save(); } + loginPrivateKey(key: string) { + const s = new PrivateKeySigner(key); + this.#session = { + type: "nsec", + publicKey: s.getPubKey(), + privateKey: key, + }; + this.#save(); + } + + loginBunker(url: string, localKey: string, remotePubkey: string) { + this.#session = { + type: "nip46", + publicKey: remotePubkey, + privateKey: localKey, + bunker: url, + }; + this.#save(); + } + + getSigner() { + if (!this.#signer && this.#session) { + switch (this.#session.type) { + case "nsec": + this.#signer = new EventPublisher(new PrivateKeySigner(this.#session.privateKey!), this.#session.publicKey); + break; + case "nip46": + this.#signer = new EventPublisher( + new Nip46Signer(this.#session.bunker!, new PrivateKeySigner(this.#session.privateKey!)), + this.#session.publicKey, + ); + break; + case "nip7": + this.#signer = new EventPublisher(new Nip7Signer(), this.#session.publicKey); + break; + } + } + + if (this.#signer) { + return this.#signer; + } + throw "Signer not setup!"; + } + #save() { - window.localStorage.setItem("session", JSON.stringify(this.#session)); + if (this.#session) { + window.localStorage.setItem("session", JSON.stringify(this.#session)); + } else { + window.localStorage.removeItem("session"); + } this.notifyChange(); } } @@ -44,8 +103,9 @@ export function useLogin() { const system = useContext(SnortContext); return session ? { - ...session, - builder: new EventPublisher(new Nip7Signer(), session.publicKey), + type: session.type, + publicKey: session.publicKey, + builder: LoginState.getSigner(), system, } : undefined; diff --git a/src/main.tsx b/src/main.tsx index c955781..c06e077 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -13,6 +13,7 @@ import { TorrentPage } from "./page/torrent"; import { SearchPage } from "./page/search"; import { System, initSystem } from "./system"; import { RelaysPage } from "./page/relays"; +import LoginPage from "./page/login"; const routes = [ { @@ -46,6 +47,10 @@ const routes = [ path: "/relays", element: , }, + { + path: "/login", + element: , + }, ], }, ] as Array; diff --git a/src/page/layout.tsx b/src/page/layout.tsx index 709c94f..e9ec9d6 100644 --- a/src/page/layout.tsx +++ b/src/page/layout.tsx @@ -1,6 +1,6 @@ -import { Link, Outlet } from "react-router-dom"; +import { Link, Outlet, useNavigate } from "react-router-dom"; import { Button } from "../element/button"; -import { LoginSession, LoginState, useLogin } from "../login"; +import { LoginSession, useLogin } from "../login"; import { ProfileImage } from "../element/profile-image"; import { Search } from "../element/search"; import { useRelays } from "../relays"; @@ -12,6 +12,7 @@ export function Layout() { const login = useLogin(); const system = useContext(SnortContext); const { relays } = useRelays(); + const navigate = useNavigate(); async function updateRelayConnections(system: SystemInterface, relays: Record) { if (import.meta.env.VITE_SINGLE_RELAY) { @@ -33,15 +34,6 @@ export function Layout() { updateRelayConnections(system, Object.fromEntries(relays.map((a) => [a, { read: true, write: true }]))); }, [system, relays]); - async function DoLogin() { - if ("nostr" in window) { - const pubkey = await window.nostr?.getPublicKey(); - if (pubkey) { - LoginState.login(pubkey); - } - } - } - return (
@@ -59,7 +51,7 @@ export function Layout() { {login ? ( ) : ( - )} diff --git a/src/page/login.tsx b/src/page/login.tsx new file mode 100644 index 0000000..4f38908 --- /dev/null +++ b/src/page/login.tsx @@ -0,0 +1,68 @@ +import { Nip46Signer, Nip7Signer, PrivateKeySigner } from "@snort/system"; +import { Button } from "../element/button"; +import { LoginState } from "../login"; +import { useNavigate } from "react-router-dom"; +import { useState } from "react"; +import { bech32ToHex } from "@snort/shared"; + +export default function LoginPage() { + const [key, setKey] = useState(""); + const navigate = useNavigate(); + + return ( + <> +
+

Login

+
+
+ setKey(e.target.value)} /> + +
+ {window.nostr && ( +
+ Browser Extension: + +
+ )} + +

Create Account

+
+ +
+ + ); +} diff --git a/src/page/new.tsx b/src/page/new.tsx index b591a01..4526292 100644 --- a/src/page/new.tsx +++ b/src/page/new.tsx @@ -223,7 +223,6 @@ export function NewPage() { setObj((o) => ({ ...o, name: e.target.value }))} @@ -233,7 +232,6 @@ export function NewPage() { setObj((o) => ({ ...o, btih: e.target.value }))} @@ -331,12 +329,7 @@ export function NewPage() { } } })()} - setNewLabelValue(e.target.value)} - /> + setNewLabelValue(e.target.value)} /> + )}
); diff --git a/src/page/relays.tsx b/src/page/relays.tsx index f469542..6235f2f 100644 --- a/src/page/relays.tsx +++ b/src/page/relays.tsx @@ -26,7 +26,6 @@ export function RelaysPage() { type="text" value={newRelay} onChange={(e) => setNewRelay(e.target.value)} - className="px-4 py-2 rounded-xl bg-neutral-800 focus-visible:outline-none" placeholder="wss://myrelay.com" />