mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-10-09 12:23:37 +02:00
added login with nip05
This commit is contained in:
@@ -6,7 +6,7 @@ import { Page } from "./components/page";
|
|||||||
import { SettingsView } from "./views/settings";
|
import { SettingsView } from "./views/settings";
|
||||||
import { LoginView } from "./views/login";
|
import { LoginView } from "./views/login";
|
||||||
import { ProfileView } from "./views/profile";
|
import { ProfileView } from "./views/profile";
|
||||||
import identityService from "./services/identity";
|
import accountService from "./services/account";
|
||||||
import { FollowingTab } from "./views/home/following-tab";
|
import { FollowingTab } from "./views/home/following-tab";
|
||||||
import { DiscoverTab } from "./views/home/discover-tab";
|
import { DiscoverTab } from "./views/home/discover-tab";
|
||||||
import { GlobalTab } from "./views/home/global-tab";
|
import { GlobalTab } from "./views/home/global-tab";
|
||||||
@@ -23,10 +23,11 @@ import { LoginNpubView } from "./views/login/npub";
|
|||||||
import NotificationsView from "./views/notifications";
|
import NotificationsView from "./views/notifications";
|
||||||
import { RelaysView } from "./views/relays";
|
import { RelaysView } from "./views/relays";
|
||||||
import useSubject from "./hooks/use-subject";
|
import useSubject from "./hooks/use-subject";
|
||||||
|
import { LoginNip05View } from "./views/login/nip05";
|
||||||
|
|
||||||
const RequireSetup = ({ children }: { children: JSX.Element }) => {
|
const RequireSetup = ({ children }: { children: JSX.Element }) => {
|
||||||
let location = useLocation();
|
let location = useLocation();
|
||||||
const setup = useSubject(identityService.setup);
|
const setup = useSubject(accountService.setup);
|
||||||
|
|
||||||
if (!setup) return <Navigate to="/login" state={{ from: location.pathname }} replace />;
|
if (!setup) return <Navigate to="/login" state={{ from: location.pathname }} replace />;
|
||||||
|
|
||||||
@@ -48,6 +49,7 @@ const router = createBrowserRouter([
|
|||||||
children: [
|
children: [
|
||||||
{ path: "", element: <LoginStartView /> },
|
{ path: "", element: <LoginStartView /> },
|
||||||
{ path: "npub", element: <LoginNpubView /> },
|
{ path: "npub", element: <LoginNpubView /> },
|
||||||
|
{ path: "nip05", element: <LoginNip05View /> },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -8,7 +8,7 @@ import { Bech32Prefix, normalizeToBech32 } from "../../helpers/nip-19";
|
|||||||
|
|
||||||
import { NoteContents } from "./note-contents";
|
import { NoteContents } from "./note-contents";
|
||||||
import { NoteMenu } from "./note-menu";
|
import { NoteMenu } from "./note-menu";
|
||||||
import identityService from "../../services/identity";
|
import accountService from "../../services/account";
|
||||||
import { useUserContacts } from "../../hooks/use-user-contacts";
|
import { useUserContacts } from "../../hooks/use-user-contacts";
|
||||||
import { UserTipButton } from "../user-tip-button";
|
import { UserTipButton } from "../user-tip-button";
|
||||||
import { NoteRelays } from "./note-relays";
|
import { NoteRelays } from "./note-relays";
|
||||||
@@ -31,7 +31,7 @@ export const Note = React.memo(({ event, maxHeight }: NoteProps) => {
|
|||||||
const readonly = useReadonlyMode();
|
const readonly = useReadonlyMode();
|
||||||
const { openModal } = useContext(PostModalContext);
|
const { openModal } = useContext(PostModalContext);
|
||||||
|
|
||||||
const pubkey = useSubject(identityService.pubkey) ?? "";
|
const pubkey = useSubject(accountService.pubkey) ?? "";
|
||||||
const contacts = useUserContacts(pubkey);
|
const contacts = useUserContacts(pubkey);
|
||||||
const following = contacts?.contacts || [];
|
const following = contacts?.contacts || [];
|
||||||
|
|
||||||
|
@@ -6,7 +6,7 @@ import { ErrorBoundary } from "./error-boundary";
|
|||||||
import { ConnectedRelays } from "./connected-relays";
|
import { ConnectedRelays } from "./connected-relays";
|
||||||
|
|
||||||
import { useIsMobile } from "../hooks/use-is-mobile";
|
import { useIsMobile } from "../hooks/use-is-mobile";
|
||||||
import identityService from "../services/identity";
|
import accountService from "../services/account";
|
||||||
import { FollowingList } from "./following-list";
|
import { FollowingList } from "./following-list";
|
||||||
import { ReloadPrompt } from "./reload-prompt";
|
import { ReloadPrompt } from "./reload-prompt";
|
||||||
import { PostModalProvider } from "../providers/post-modal-provider";
|
import { PostModalProvider } from "../providers/post-modal-provider";
|
||||||
@@ -16,7 +16,7 @@ import { UserAvatarLink } from "./user-avatar-link";
|
|||||||
import useSubject from "../hooks/use-subject";
|
import useSubject from "../hooks/use-subject";
|
||||||
|
|
||||||
const MobileProfileHeader = () => {
|
const MobileProfileHeader = () => {
|
||||||
const pubkey = useSubject(identityService.pubkey) ?? "";
|
const pubkey = useSubject(accountService.pubkey) ?? "";
|
||||||
const readonly = useReadonlyMode();
|
const readonly = useReadonlyMode();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -27,7 +27,7 @@ const MobileProfileHeader = () => {
|
|||||||
colorScheme="red"
|
colorScheme="red"
|
||||||
textAlign="center"
|
textAlign="center"
|
||||||
variant="link"
|
variant="link"
|
||||||
onClick={() => confirm("Exit readonly mode?") && identityService.logout()}
|
onClick={() => confirm("Exit readonly mode?") && accountService.logout()}
|
||||||
>
|
>
|
||||||
Readonly Mode
|
Readonly Mode
|
||||||
</Button>
|
</Button>
|
||||||
@@ -96,7 +96,7 @@ const DesktopSideNav = () => {
|
|||||||
<Button onClick={() => navigate("/settings")} leftIcon={<SettingsIcon />}>
|
<Button onClick={() => navigate("/settings")} leftIcon={<SettingsIcon />}>
|
||||||
Settings
|
Settings
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => identityService.logout()} leftIcon={<LogoutIcon />}>
|
<Button onClick={() => accountService.logout()} leftIcon={<LogoutIcon />}>
|
||||||
Logout
|
Logout
|
||||||
</Button>
|
</Button>
|
||||||
{readonly && (
|
{readonly && (
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Box, LinkBox, Text } from "@chakra-ui/react";
|
import { Box, LinkBox, Text } from "@chakra-ui/react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import identityService from "../services/identity";
|
import accountService from "../services/account";
|
||||||
import { UserAvatar } from "./user-avatar";
|
import { UserAvatar } from "./user-avatar";
|
||||||
import { useUserMetadata } from "../hooks/use-user-metadata";
|
import { useUserMetadata } from "../hooks/use-user-metadata";
|
||||||
import { normalizeToBech32 } from "../helpers/nip-19";
|
import { normalizeToBech32 } from "../helpers/nip-19";
|
||||||
@@ -8,7 +8,7 @@ import { truncatedId } from "../helpers/nostr-event";
|
|||||||
import useSubject from "../hooks/use-subject";
|
import useSubject from "../hooks/use-subject";
|
||||||
|
|
||||||
export const ProfileButton = () => {
|
export const ProfileButton = () => {
|
||||||
const pubkey = useSubject(identityService.pubkey) ?? "";
|
const pubkey = useSubject(accountService.pubkey) ?? "";
|
||||||
const metadata = useUserMetadata(pubkey);
|
const metadata = useUserMetadata(pubkey);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import identityService from "../services/identity";
|
import accountService from "../services/account";
|
||||||
import useSubject from "./use-subject";
|
import useSubject from "./use-subject";
|
||||||
|
|
||||||
export function useReadonlyMode() {
|
export function useReadonlyMode() {
|
||||||
return useSubject(identityService.readonly);
|
return useSubject(accountService.readonly);
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,7 @@ export type SavedIdentity = {
|
|||||||
useExtension: boolean;
|
useExtension: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
class IdentityService {
|
class AccountService {
|
||||||
loading = new PersistentSubject(false);
|
loading = new PersistentSubject(false);
|
||||||
setup = new PersistentSubject(false);
|
setup = new PersistentSubject(false);
|
||||||
pubkey = new Subject<string>();
|
pubkey = new Subject<string>();
|
||||||
@@ -81,11 +81,11 @@ class IdentityService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const identityService = new IdentityService();
|
const accountService = new AccountService();
|
||||||
|
|
||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
window.identity = identityService;
|
window.identity = accountService;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default identityService;
|
export default accountService;
|
@@ -3,7 +3,7 @@ import { nostrPostAction } from "../classes/nostr-post-action";
|
|||||||
import { PersistentSubject, Subject } from "../classes/subject";
|
import { PersistentSubject, Subject } from "../classes/subject";
|
||||||
import { DraftNostrEvent, PTag } from "../types/nostr-event";
|
import { DraftNostrEvent, PTag } from "../types/nostr-event";
|
||||||
import clientRelaysService from "./client-relays";
|
import clientRelaysService from "./client-relays";
|
||||||
import identityService from "./identity";
|
import accountService from "./account";
|
||||||
import userContactsService, { UserContacts } from "./user-contacts";
|
import userContactsService, { UserContacts } from "./user-contacts";
|
||||||
|
|
||||||
export type RelayDirectory = Record<string, { read: boolean; write: boolean }>;
|
export type RelayDirectory = Record<string, { read: boolean; write: boolean }>;
|
||||||
@@ -34,14 +34,14 @@ function updateSub() {
|
|||||||
sub = undefined;
|
sub = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (identityService.pubkey.value) {
|
if (accountService.pubkey.value) {
|
||||||
sub = userContactsService.requestContacts(identityService.pubkey.value, clientRelaysService.getReadUrls(), true);
|
sub = userContactsService.requestContacts(accountService.pubkey.value, clientRelaysService.getReadUrls(), true);
|
||||||
|
|
||||||
sub.subscribe(handleNewContacts);
|
sub.subscribe(handleNewContacts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
identityService.pubkey.subscribe(() => {
|
accountService.pubkey.subscribe(() => {
|
||||||
// clear the following list until a new one can be fetched
|
// clear the following list until a new one can be fetched
|
||||||
following.next([]);
|
following.next([]);
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@ import moment from "moment";
|
|||||||
import { nostrPostAction } from "../classes/nostr-post-action";
|
import { nostrPostAction } from "../classes/nostr-post-action";
|
||||||
import { unique } from "../helpers/array";
|
import { unique } from "../helpers/array";
|
||||||
import { DraftNostrEvent, RTag } from "../types/nostr-event";
|
import { DraftNostrEvent, RTag } from "../types/nostr-event";
|
||||||
import identityService from "./identity";
|
import accountService from "./account";
|
||||||
import { RelayConfig, RelayMode } from "../classes/relay";
|
import { RelayConfig, RelayMode } from "../classes/relay";
|
||||||
import userRelaysService, { UserRelays } from "./user-relays";
|
import userRelaysService, { UserRelays } from "./user-relays";
|
||||||
import { PersistentSubject, Subject } from "../classes/subject";
|
import { PersistentSubject, Subject } from "../classes/subject";
|
||||||
@@ -23,7 +23,7 @@ class ClientRelayService {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
let lastSubject: Subject<UserRelays> | undefined;
|
let lastSubject: Subject<UserRelays> | undefined;
|
||||||
identityService.pubkey.subscribe((pubkey) => {
|
accountService.pubkey.subscribe((pubkey) => {
|
||||||
// clear the relay list until a new one can be fetched
|
// clear the relay list until a new one can be fetched
|
||||||
// this.relays.next([]);
|
// this.relays.next([]);
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ class ClientRelayService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// add preset relays fromm nip07 extension to bootstrap list
|
// add preset relays fromm nip07 extension to bootstrap list
|
||||||
identityService.relays.subscribe((presetRelays) => {
|
accountService.relays.subscribe((presetRelays) => {
|
||||||
for (const [url, opts] of Object.entries(presetRelays)) {
|
for (const [url, opts] of Object.entries(presetRelays)) {
|
||||||
if (opts.read) {
|
if (opts.read) {
|
||||||
clientRelaysService.bootstrapRelays.add(url);
|
clientRelaysService.bootstrapRelays.add(url);
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import db from "./db";
|
import db from "./db";
|
||||||
|
|
||||||
function parseAddress(address: string) {
|
function parseAddress(address: string): { name?: string; domain?: string } {
|
||||||
const parts = address.split("@");
|
const parts = address.split("@");
|
||||||
return { name: parts[0], domain: parts[1] };
|
return { name: parts[0], domain: parts[1] };
|
||||||
}
|
}
|
||||||
@@ -18,10 +18,11 @@ export type DnsIdentity = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function getIdentityFromJson(name: string, domain: string, json: IdentityJson): DnsIdentity | undefined {
|
function getIdentityFromJson(name: string, domain: string, json: IdentityJson): DnsIdentity | undefined {
|
||||||
const relays: string[] = json.relays?.[name] ?? [];
|
|
||||||
const pubkey = json.names[name];
|
const pubkey = json.names[name];
|
||||||
|
if (!pubkey) return;
|
||||||
|
|
||||||
if (pubkey) return { name, domain, pubkey, relays };
|
const relays: string[] = json.relays?.[pubkey] ?? [];
|
||||||
|
return { name, domain, pubkey, relays };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchAllIdentities(domain: string) {
|
async function fetchAllIdentities(domain: string) {
|
||||||
@@ -32,6 +33,7 @@ async function fetchAllIdentities(domain: string) {
|
|||||||
|
|
||||||
async function fetchIdentity(address: string) {
|
async function fetchIdentity(address: string) {
|
||||||
const { name, domain } = parseAddress(address);
|
const { name, domain } = parseAddress(address);
|
||||||
|
if (!name || !domain) return undefined;
|
||||||
const json = await fetch(`https://${domain}/.well-known/nostr.json?name=${name}`).then(
|
const json = await fetch(`https://${domain}/.well-known/nostr.json?name=${name}`).then(
|
||||||
(res) => res.json() as Promise<IdentityJson>
|
(res) => res.json() as Promise<IdentityJson>
|
||||||
);
|
);
|
||||||
@@ -53,9 +55,9 @@ async function addToCache(domain: string, json: IdentityJson) {
|
|||||||
await Promise.all(wait);
|
await Promise.all(wait);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getIdentity(address: string) {
|
async function getIdentity(address: string, alwaysFetch = false) {
|
||||||
const cached = await db.get("dnsIdentifiers", address);
|
const cached = await db.get("dnsIdentifiers", address);
|
||||||
if (cached) return cached;
|
if (cached && !alwaysFetch) return cached;
|
||||||
|
|
||||||
// TODO: if it fails, maybe cache a failure message
|
// TODO: if it fails, maybe cache a failure message
|
||||||
return fetchIdentity(address);
|
return fetchIdentity(address);
|
||||||
@@ -74,9 +76,9 @@ async function pruneCache() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pending: Record<string, ReturnType<typeof getIdentity> | undefined> = {};
|
const pending: Record<string, ReturnType<typeof getIdentity> | undefined> = {};
|
||||||
function dedupedGetIdentity(address: string) {
|
function dedupedGetIdentity(address: string, alwaysFetch = false) {
|
||||||
if (pending[address]) return pending[address];
|
if (pending[address]) return pending[address];
|
||||||
return (pending[address] = getIdentity(address));
|
return (pending[address] = getIdentity(address, alwaysFetch));
|
||||||
}
|
}
|
||||||
|
|
||||||
export const dnsIdentityService = {
|
export const dnsIdentityService = {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { PersistentSubject } from "../classes/subject";
|
import { PersistentSubject } from "../classes/subject";
|
||||||
import db from "./db";
|
import db from "./db";
|
||||||
import { SavedIdentity } from "./identity";
|
import { SavedIdentity } from "./account";
|
||||||
|
|
||||||
const settings = {
|
const settings = {
|
||||||
identity: new PersistentSubject<SavedIdentity | null>(null),
|
identity: new PersistentSubject<SavedIdentity | null>(null),
|
||||||
|
@@ -3,7 +3,7 @@ import { Button, Flex, Spinner } from "@chakra-ui/react";
|
|||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { Note } from "../../components/note";
|
import { Note } from "../../components/note";
|
||||||
import { useUserContacts } from "../../hooks/use-user-contacts";
|
import { useUserContacts } from "../../hooks/use-user-contacts";
|
||||||
import identityService from "../../services/identity";
|
import accountService from "../../services/account";
|
||||||
import userContactsService from "../../services/user-contacts";
|
import userContactsService from "../../services/user-contacts";
|
||||||
import { useTimelineLoader } from "../../hooks/use-timeline-loader";
|
import { useTimelineLoader } from "../../hooks/use-timeline-loader";
|
||||||
import { isNote } from "../../helpers/nostr-event";
|
import { isNote } from "../../helpers/nostr-event";
|
||||||
@@ -41,7 +41,7 @@ function useExtendedContacts(pubkey: string) {
|
|||||||
|
|
||||||
export const DiscoverTab = () => {
|
export const DiscoverTab = () => {
|
||||||
useAppTitle("discover");
|
useAppTitle("discover");
|
||||||
const pubkey = useSubject(identityService.pubkey) ?? "";
|
const pubkey = useSubject(accountService.pubkey) ?? "";
|
||||||
const relays = useReadRelayUrls();
|
const relays = useReadRelayUrls();
|
||||||
|
|
||||||
const contactsOfContacts = useExtendedContacts(pubkey);
|
const contactsOfContacts = useExtendedContacts(pubkey);
|
||||||
|
@@ -5,7 +5,7 @@ import { Note } from "../../components/note";
|
|||||||
import { isNote } from "../../helpers/nostr-event";
|
import { isNote } from "../../helpers/nostr-event";
|
||||||
import { useTimelineLoader } from "../../hooks/use-timeline-loader";
|
import { useTimelineLoader } from "../../hooks/use-timeline-loader";
|
||||||
import { useUserContacts } from "../../hooks/use-user-contacts";
|
import { useUserContacts } from "../../hooks/use-user-contacts";
|
||||||
import identityService from "../../services/identity";
|
import accountService from "../../services/account";
|
||||||
import { AddIcon } from "@chakra-ui/icons";
|
import { AddIcon } from "@chakra-ui/icons";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { PostModalContext } from "../../providers/post-modal-provider";
|
import { PostModalContext } from "../../providers/post-modal-provider";
|
||||||
@@ -15,7 +15,7 @@ import useSubject from "../../hooks/use-subject";
|
|||||||
|
|
||||||
export const FollowingTab = () => {
|
export const FollowingTab = () => {
|
||||||
const readonly = useReadonlyMode();
|
const readonly = useReadonlyMode();
|
||||||
const pubkey = useSubject(identityService.pubkey) ?? "";
|
const pubkey = useSubject(accountService.pubkey) ?? "";
|
||||||
const relays = useReadRelayUrls();
|
const relays = useReadRelayUrls();
|
||||||
const { openModal } = useContext(PostModalContext);
|
const { openModal } = useContext(PostModalContext);
|
||||||
const contacts = useUserContacts(pubkey);
|
const contacts = useUserContacts(pubkey);
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import { Avatar, Box, Flex, Heading } from "@chakra-ui/react";
|
import { Avatar, Box, Flex, Heading } from "@chakra-ui/react";
|
||||||
import { Navigate, Outlet, useLocation } from "react-router-dom";
|
import { Navigate, Outlet, useLocation } from "react-router-dom";
|
||||||
import useSubject from "../../hooks/use-subject";
|
import useSubject from "../../hooks/use-subject";
|
||||||
import identityService from "../../services/identity";
|
import accountService from "../../services/account";
|
||||||
|
|
||||||
export const LoginView = () => {
|
export const LoginView = () => {
|
||||||
const setup = useSubject(identityService.setup);
|
const setup = useSubject(accountService.setup);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
if (setup) return <Navigate to={location.state?.from ?? "/"} replace />;
|
if (setup) return <Navigate to={location.state?.from ?? "/"} replace />;
|
||||||
|
137
src/views/login/nip05.tsx
Normal file
137
src/views/login/nip05.tsx
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Flex,
|
||||||
|
FormControl,
|
||||||
|
FormHelperText,
|
||||||
|
FormLabel,
|
||||||
|
Input,
|
||||||
|
InputGroup,
|
||||||
|
InputRightElement,
|
||||||
|
Link,
|
||||||
|
Spinner,
|
||||||
|
Text,
|
||||||
|
useToast,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { RelayUrlInput } from "../../components/relay-url-input";
|
||||||
|
import { normalizeToHex } from "../../helpers/nip-19";
|
||||||
|
import accountService from "../../services/account";
|
||||||
|
import clientRelaysService from "../../services/client-relays";
|
||||||
|
import { useDebounce } from "react-use";
|
||||||
|
import dnsIdentityService from "../../services/dns-identity";
|
||||||
|
import { CheckIcon } from "../../components/icons";
|
||||||
|
import { CloseIcon } from "@chakra-ui/icons";
|
||||||
|
|
||||||
|
export const LoginNip05View = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [nip05, setNip05] = useState("");
|
||||||
|
const [relayUrl, setRelayUrl] = useState("");
|
||||||
|
|
||||||
|
const [pubkey, setPubkey] = useState<string | undefined>();
|
||||||
|
const [relays, setRelays] = useState<string[] | undefined>();
|
||||||
|
|
||||||
|
const handleNip05Change: React.ChangeEventHandler<HTMLInputElement> = (event) => {
|
||||||
|
setNip05(event.target.value);
|
||||||
|
setPubkey(undefined);
|
||||||
|
setRelays(undefined);
|
||||||
|
if (event.target.value) setLoading(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
useDebounce(
|
||||||
|
async () => {
|
||||||
|
if (nip05) {
|
||||||
|
try {
|
||||||
|
const id = await dnsIdentityService.getIdentity(nip05, true);
|
||||||
|
setPubkey(id?.pubkey);
|
||||||
|
setRelays(id?.relays);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
},
|
||||||
|
1000,
|
||||||
|
[nip05, setPubkey, setRelays, setLoading]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSubmit: React.FormEventHandler<HTMLDivElement> = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!pubkey) return toast({ status: "error", title: "Invalid NIP-05 id" });
|
||||||
|
|
||||||
|
if ((!relays || relays.length === 0) && !relayUrl) {
|
||||||
|
return toast({ status: "error", title: "No relay selected" });
|
||||||
|
}
|
||||||
|
|
||||||
|
accountService.loginWithPubkey(pubkey);
|
||||||
|
|
||||||
|
if (relayUrl) {
|
||||||
|
clientRelaysService.bootstrapRelays.add(relayUrl);
|
||||||
|
}
|
||||||
|
if (relays) {
|
||||||
|
for (const url of relays) {
|
||||||
|
clientRelaysService.bootstrapRelays.add(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderInputIcon = () => {
|
||||||
|
if (loading) return <Spinner size="sm" />;
|
||||||
|
if (nip05) {
|
||||||
|
if (pubkey) return <CheckIcon color="green.500" />;
|
||||||
|
else return <CloseIcon color="red.500" />;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex as="form" direction="column" gap="4" onSubmit={handleSubmit} minWidth="400">
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Enter user NIP-05 id</FormLabel>
|
||||||
|
<InputGroup>
|
||||||
|
<Input
|
||||||
|
name="nip05"
|
||||||
|
placeholder="user@domain.com"
|
||||||
|
isRequired
|
||||||
|
value={nip05}
|
||||||
|
onChange={handleNip05Change}
|
||||||
|
colorScheme={"green"}
|
||||||
|
errorBorderColor={nip05 && !pubkey ? "red.500" : undefined}
|
||||||
|
/>
|
||||||
|
<InputRightElement children={renderInputIcon()} />
|
||||||
|
</InputGroup>
|
||||||
|
<FormHelperText>
|
||||||
|
Find NIP-05 ids here.{" "}
|
||||||
|
<Link isExternal href="https://nostrplebs.com/directory" color="blue.500" target="_blank">
|
||||||
|
nostrplebs.com/directory
|
||||||
|
</Link>
|
||||||
|
</FormHelperText>
|
||||||
|
</FormControl>
|
||||||
|
{relays ? (
|
||||||
|
relays.length > 0 ? (
|
||||||
|
<Text>Found {relays.length} relays</Text>
|
||||||
|
) : (
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Bootstrap relay</FormLabel>
|
||||||
|
<RelayUrlInput
|
||||||
|
placeholder="wss://nostr.example.com"
|
||||||
|
isRequired
|
||||||
|
value={relayUrl}
|
||||||
|
onChange={(e) => setRelayUrl(e.target.value)}
|
||||||
|
/>
|
||||||
|
<FormHelperText>The first relay to connect to.</FormHelperText>
|
||||||
|
</FormControl>
|
||||||
|
)
|
||||||
|
) : null}
|
||||||
|
<Flex justifyContent="space-between" gap="2">
|
||||||
|
<Button variant="link" onClick={() => navigate("../")}>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
<Button colorScheme="brand" ml="auto" type="submit" isDisabled={!pubkey}>
|
||||||
|
Login
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
@@ -3,7 +3,7 @@ import { Button, Flex, FormControl, FormHelperText, FormLabel, Input, Link, useT
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { RelayUrlInput } from "../../components/relay-url-input";
|
import { RelayUrlInput } from "../../components/relay-url-input";
|
||||||
import { normalizeToHex } from "../../helpers/nip-19";
|
import { normalizeToHex } from "../../helpers/nip-19";
|
||||||
import identityService from "../../services/identity";
|
import accountService from "../../services/account";
|
||||||
import clientRelaysService from "../../services/client-relays";
|
import clientRelaysService from "../../services/client-relays";
|
||||||
|
|
||||||
export const LoginNpubView = () => {
|
export const LoginNpubView = () => {
|
||||||
@@ -20,16 +20,13 @@ export const LoginNpubView = () => {
|
|||||||
return toast({ status: "error", title: "Invalid npub" });
|
return toast({ status: "error", title: "Invalid npub" });
|
||||||
}
|
}
|
||||||
|
|
||||||
identityService.loginWithPubkey(pubkey);
|
accountService.loginWithPubkey(pubkey);
|
||||||
|
|
||||||
clientRelaysService.bootstrapRelays.add(relayUrl);
|
clientRelaysService.bootstrapRelays.add(relayUrl);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex as="form" direction="column" gap="4" onSubmit={handleSubmit}>
|
<Flex as="form" direction="column" gap="4" onSubmit={handleSubmit} minWidth="400">
|
||||||
<Button variant="link" onClick={() => navigate("../")}>
|
|
||||||
Back
|
|
||||||
</Button>
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel>Enter user npub</FormLabel>
|
<FormLabel>Enter user npub</FormLabel>
|
||||||
<Input type="text" placeholder="npub1" isRequired value={npub} onChange={(e) => setNpub(e.target.value)} />
|
<Input type="text" placeholder="npub1" isRequired value={npub} onChange={(e) => setNpub(e.target.value)} />
|
||||||
@@ -50,9 +47,14 @@ export const LoginNpubView = () => {
|
|||||||
/>
|
/>
|
||||||
<FormHelperText>The first relay to connect to.</FormHelperText>
|
<FormHelperText>The first relay to connect to.</FormHelperText>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Button colorScheme="brand" ml="auto" type="submit">
|
<Flex justifyContent="space-between" gap="2">
|
||||||
Login
|
<Button variant="link" onClick={() => navigate("../")}>
|
||||||
</Button>
|
Back
|
||||||
|
</Button>
|
||||||
|
<Button colorScheme="brand" ml="auto" type="submit">
|
||||||
|
Login
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import { Alert, AlertDescription, AlertIcon, AlertTitle, Box, Button, Spinner } from "@chakra-ui/react";
|
import { Alert, AlertDescription, AlertIcon, AlertTitle, Box, Button, Spinner } from "@chakra-ui/react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import useSubject from "../../hooks/use-subject";
|
import useSubject from "../../hooks/use-subject";
|
||||||
import identityService from "../../services/identity";
|
import accountService from "../../services/account";
|
||||||
|
|
||||||
export const LoginStartView = () => {
|
export const LoginStartView = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const loading = useSubject(identityService.loading);
|
const loading = useSubject(accountService.loading);
|
||||||
if (loading) return <Spinner />;
|
if (loading) return <Spinner />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -17,9 +17,10 @@ export const LoginStartView = () => {
|
|||||||
<AlertDescription>There are bugs and things will break.</AlertDescription>
|
<AlertDescription>There are bugs and things will break.</AlertDescription>
|
||||||
</Box>
|
</Box>
|
||||||
</Alert>
|
</Alert>
|
||||||
<Button onClick={() => identityService.loginWithExtension()} colorScheme="brand">
|
<Button onClick={() => accountService.loginWithExtension()} colorScheme="brand">
|
||||||
Use browser extension
|
Use browser extension
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => navigate("./nip05")}>Login with Nip-05 Id</Button>
|
||||||
<Button variant="link" onClick={() => navigate("./npub")}>
|
<Button variant="link" onClick={() => navigate("./npub")}>
|
||||||
Login with npub
|
Login with npub
|
||||||
</Button>
|
</Button>
|
||||||
|
@@ -8,7 +8,7 @@ import { convertTimestampToDate } from "../../helpers/date";
|
|||||||
import { useReadRelayUrls } from "../../hooks/use-client-relays";
|
import { useReadRelayUrls } from "../../hooks/use-client-relays";
|
||||||
import useSubject from "../../hooks/use-subject";
|
import useSubject from "../../hooks/use-subject";
|
||||||
import { useTimelineLoader } from "../../hooks/use-timeline-loader";
|
import { useTimelineLoader } from "../../hooks/use-timeline-loader";
|
||||||
import identityService from "../../services/identity";
|
import accountService from "../../services/account";
|
||||||
import { NostrEvent } from "../../types/nostr-event";
|
import { NostrEvent } from "../../types/nostr-event";
|
||||||
|
|
||||||
const Kind1Notification = ({ event }: { event: NostrEvent }) => {
|
const Kind1Notification = ({ event }: { event: NostrEvent }) => {
|
||||||
@@ -39,7 +39,7 @@ const NotificationItem = memo(({ event }: { event: NostrEvent }) => {
|
|||||||
|
|
||||||
const NotificationsView = () => {
|
const NotificationsView = () => {
|
||||||
const readRelays = useReadRelayUrls();
|
const readRelays = useReadRelayUrls();
|
||||||
const pubkey = useSubject(identityService.pubkey) ?? "";
|
const pubkey = useSubject(accountService.pubkey) ?? "";
|
||||||
const { events, loading, loadMore } = useTimelineLoader(
|
const { events, loading, loadMore } = useTimelineLoader(
|
||||||
"notifications",
|
"notifications",
|
||||||
readRelays,
|
readRelays,
|
||||||
|
@@ -3,7 +3,7 @@ import { useMemo } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import useSubject from "../../hooks/use-subject";
|
import useSubject from "../../hooks/use-subject";
|
||||||
import { useUserMetadata } from "../../hooks/use-user-metadata";
|
import { useUserMetadata } from "../../hooks/use-user-metadata";
|
||||||
import identityService from "../../services/identity";
|
import accountService from "../../services/account";
|
||||||
|
|
||||||
type FormData = {
|
type FormData = {
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
@@ -60,7 +60,7 @@ const MetadataForm = ({ defaultValues, onSubmit }: MetadataFormProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ProfileEditView = () => {
|
export const ProfileEditView = () => {
|
||||||
const pubkey = useSubject(identityService.pubkey) ?? "";
|
const pubkey = useSubject(accountService.pubkey) ?? "";
|
||||||
const metadata = useUserMetadata(pubkey);
|
const metadata = useUserMetadata(pubkey);
|
||||||
|
|
||||||
const defaultValues = useMemo<FormData>(
|
const defaultValues = useMemo<FormData>(
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import useSubject from "../../hooks/use-subject";
|
import useSubject from "../../hooks/use-subject";
|
||||||
import identityService from "../../services/identity";
|
import accountService from "../../services/account";
|
||||||
import { ProfileEditView } from "./edit";
|
import { ProfileEditView } from "./edit";
|
||||||
|
|
||||||
export const ProfileView = () => {
|
export const ProfileView = () => {
|
||||||
const pubkey = useSubject(identityService.pubkey) ?? "";
|
const pubkey = useSubject(accountService.pubkey) ?? "";
|
||||||
|
|
||||||
return <ProfileEditView />;
|
return <ProfileEditView />;
|
||||||
};
|
};
|
||||||
|
@@ -17,7 +17,7 @@ import {
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import settings from "../../services/settings";
|
import settings from "../../services/settings";
|
||||||
import { clearCacheData, deleteDatabase } from "../../services/db";
|
import { clearCacheData, deleteDatabase } from "../../services/db";
|
||||||
import identityService from "../../services/identity";
|
import accountService from "../../services/account";
|
||||||
import useSubject from "../../hooks/use-subject";
|
import useSubject from "../../hooks/use-subject";
|
||||||
|
|
||||||
export const SettingsView = () => {
|
export const SettingsView = () => {
|
||||||
@@ -171,7 +171,7 @@ export const SettingsView = () => {
|
|||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Flex gap="2" padding="4">
|
<Flex gap="2" padding="4">
|
||||||
<Button onClick={() => identityService.logout()}>Logout</Button>
|
<Button onClick={() => accountService.logout()}>Logout</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
@@ -3,7 +3,7 @@ import { MenuIconButton, MenuIconButtonProps } from "../../../components/menu-ic
|
|||||||
|
|
||||||
import { IMAGE_ICONS, SpyIcon } from "../../../components/icons";
|
import { IMAGE_ICONS, SpyIcon } from "../../../components/icons";
|
||||||
import { Bech32Prefix, normalizeToBech32 } from "../../../helpers/nip-19";
|
import { Bech32Prefix, normalizeToBech32 } from "../../../helpers/nip-19";
|
||||||
import identityService from "../../../services/identity";
|
import accountService from "../../../services/account";
|
||||||
import { useUserMetadata } from "../../../hooks/use-user-metadata";
|
import { useUserMetadata } from "../../../hooks/use-user-metadata";
|
||||||
import { getUserDisplayName } from "../../../helpers/user-metadata";
|
import { getUserDisplayName } from "../../../helpers/user-metadata";
|
||||||
|
|
||||||
@@ -13,8 +13,8 @@ export const UserProfileMenu = ({ pubkey, ...props }: { pubkey: string } & Omit<
|
|||||||
|
|
||||||
const loginAsUser = () => {
|
const loginAsUser = () => {
|
||||||
if (confirm(`Do you want to logout and login as ${getUserDisplayName(metadata, pubkey)}?`)) {
|
if (confirm(`Do you want to logout and login as ${getUserDisplayName(metadata, pubkey)}?`)) {
|
||||||
identityService.logout();
|
accountService.logout();
|
||||||
identityService.loginWithPubkey(pubkey);
|
accountService.loginWithPubkey(pubkey);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -24,7 +24,7 @@ import { truncatedId } from "../../helpers/nostr-event";
|
|||||||
import { Bech32Prefix, normalizeToBech32 } from "../../helpers/nip-19";
|
import { Bech32Prefix, normalizeToBech32 } from "../../helpers/nip-19";
|
||||||
import { KeyIcon, SettingsIcon } from "../../components/icons";
|
import { KeyIcon, SettingsIcon } from "../../components/icons";
|
||||||
import { CopyIconButton } from "../../components/copy-icon-button";
|
import { CopyIconButton } from "../../components/copy-icon-button";
|
||||||
import identityService from "../../services/identity";
|
import accountService from "../../services/account";
|
||||||
import { UserFollowButton } from "../../components/user-follow-button";
|
import { UserFollowButton } from "../../components/user-follow-button";
|
||||||
import { useAppTitle } from "../../hooks/use-app-title";
|
import { useAppTitle } from "../../hooks/use-app-title";
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ const UserView = () => {
|
|||||||
|
|
||||||
const metadata = useUserMetadata(pubkey, [], true);
|
const metadata = useUserMetadata(pubkey, [], true);
|
||||||
const npub = normalizeToBech32(pubkey, Bech32Prefix.Pubkey);
|
const npub = normalizeToBech32(pubkey, Bech32Prefix.Pubkey);
|
||||||
const isSelf = pubkey === identityService.pubkey.value;
|
const isSelf = pubkey === accountService.pubkey.value;
|
||||||
|
|
||||||
useAppTitle(getUserDisplayName(metadata, npub ?? pubkey));
|
useAppTitle(getUserDisplayName(metadata, npub ?? pubkey));
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user