feat: Use session to remember login

This commit is contained in:
florian 2024-03-08 19:41:47 +00:00
parent a3525e05a2
commit 72575edcc3
17 changed files with 111 additions and 77 deletions

View File

@ -6,8 +6,8 @@ import { ViewMode } from '../SlideShow';
import IconLink from '../Icons/IconLink';
import FollowButton from '../FollowButton/FollowButton';
import { nip19 } from 'nostr-tools';
import { useGlobalState } from '../../utils/globalState';
import React from 'react';
import { useSession } from '../../ngine/state';
type AvatarImageProps = {
src?: string;
@ -28,9 +28,9 @@ const AuthorProfile = ({
}: AvatarImageProps) => {
const avatarLoaded = useImageLoaded(src);
const { nav, currentSettings } = useNav();
const [state] = useGlobalState();
const followButtonAvailable = followButton && state.userNPub;
const session = useSession();
const followButtonAvailable = followButton && session?.pubkey;
return (
<div className="author-info">

View File

@ -6,9 +6,10 @@ import { Settings } from '../../utils/useNav';
import AuthorProfile from '../AuthorProfile/AuthorProfile';
import { useSwipeable } from 'react-swipeable';
import { Helmet } from 'react-helmet';
import useProfile from '../../utils/useProfile';
import useActiveProfile from '../../utils/useActiveProfile';
import { ViewMode } from '../SlideShow';
import { useGlobalState } from '../../utils/globalState';
import useTitle from '../../utils/useTitle';
type GridViewProps = {
settings: Settings;
@ -19,7 +20,8 @@ type GridViewProps = {
};
const GridView = ({ settings, images, currentImage, setCurrentImage, setViewMode }: GridViewProps) => {
const { activeProfile, title, profileNpub } = useProfile(settings);
const { activeProfile, activeNpub } = useActiveProfile(settings);
const title = useTitle(settings, activeProfile);
const [_, setState] = useGlobalState();
const showNextImage = () => {
setCurrentImage(idx => (idx !== undefined ? idx + 1 : 0));
@ -78,7 +80,7 @@ const GridView = ({ settings, images, currentImage, setCurrentImage, setViewMode
<AuthorProfile
src={urlFix(activeProfile.image || '')}
author={activeProfile.displayName || activeProfile.name}
npub={profileNpub}
npub={activeNpub}
setViewMode={setViewMode}
followButton
externalLink

View File

@ -1,20 +1,26 @@
import { topics } from '../env';
import useNav from '../../utils/useNav';
import './Home.css';
import { useGlobalState } from '../../utils/globalState';
import usePeopleLists from '../../utils/useLists';
import { createImgProxyUrl } from '../nostrImageDownload';
import { useState } from 'react';
import { Helmet } from 'react-helmet';
import useProfile from '../../utils/useProfile';
import useActiveProfile from '../../utils/useActiveProfile';
import useTitle from '../../utils/useTitle';
import { useSession } from '../../ngine/state';
import { nip19 } from 'nostr-tools';
import useProfile from '../../ngine/hooks/useProfile';
const Home = () => {
const { nav, currentSettings } = useNav();
const { title } = useProfile(currentSettings);
const { activeProfile } = useActiveProfile(currentSettings);
const title = useTitle(currentSettings, activeProfile);
const [showAdult, setShowAdult] = useState(currentSettings.showAdult || false);
const [state] = useGlobalState();
const topicKeys = Object.keys(topics);
const lists = usePeopleLists(state.userNPub);
const session = useSession();
const lists = usePeopleLists(session?.pubkey);
const profile = useProfile(session?.pubkey || ' ');
const userNPub = session && nip19.npubEncode(session?.pubkey);
return (
<div className="home-container">
@ -55,7 +61,7 @@ const Home = () => {
<div>All content posted on nostr. Use with care!</div>
</div>
</div>
{state.userNPub && (
{session?.pubkey && (
<>
<h2>Your...</h2>
<div className="topics">
@ -82,14 +88,14 @@ const Home = () => {
className="topic"
style={{
backgroundImage:
state.profile?.banner &&
`linear-gradient(170deg, rgba(0, 0, 0, .8) 0%, rgba(0, 0, 0, 0) 50%, rgba(0, 0, 0, 0) 100%), url(${createImgProxyUrl(state.profile?.banner, 600, -1)})`,
profile?.banner &&
`linear-gradient(170deg, rgba(0, 0, 0, .8) 0%, rgba(0, 0, 0, 0) 50%, rgba(0, 0, 0, 0) 100%), url(${createImgProxyUrl(profile?.banner, 600, -1)})`,
}}
onClick={() =>
nav({
...currentSettings,
topic: undefined,
npubs: [state.userNPub || ''],
npubs: userNPub ? [userNPub] : [],
tags: [],
list: undefined,
follows: false,

View File

@ -6,7 +6,7 @@ import './InfoPanel.css';
import IconChevronDown from '../Icons/IconChevronDown';
import { ViewMode } from '../SlideShow';
import AuthorProfile from '../AuthorProfile/AuthorProfile';
import useProfile from '../../utils/useProfile';
import useActiveProfile from '../../utils/useActiveProfile';
import { useGlobalState } from '../../utils/globalState';
import IconLink from '../Icons/IconLink';
import { nip19 } from 'nostr-tools';
@ -21,13 +21,13 @@ type InfoPanelProps = {
const InfoPanel = ({ image, onClose, setViewMode, settings }: InfoPanelProps) => {
const { nav, currentSettings } = useNav();
const [state] = useGlobalState();
const { activeProfile, profileNpub } = useProfile(settings, state.activeImage);
const { activeProfile, activeNpub } = useActiveProfile(settings, state.activeImage);
const profile = activeProfile && (
<AuthorProfile
src={urlFix(activeProfile.image || '')}
author={activeProfile.displayName || activeProfile.name}
npub={profileNpub}
npub={activeNpub}
setViewMode={setViewMode}
followButton
></AuthorProfile>

View File

@ -1,16 +1,22 @@
import './Layout.css';
import { useState } from 'react';
import Login from '../Login/Login';
import { useGlobalState } from '../../utils/globalState';
import IconUser from '../Icons/IconUser';
import { createImgProxyUrl } from '../nostrImageDownload';
import { Outlet } from 'react-router-dom';
import React from 'react';
import { useLogOut } from '../../ngine/context';
import { useSession } from '../../ngine/state';
import useProfile from '../../ngine/hooks/useProfile';
import { NDKSubscriptionCacheUsage } from '@nostr-dev-kit/ndk';
const Layout = () => {
//const { disclaimerAccepted, setDisclaimerAccepted } = useDisclaimerState();
const [state, setState] = useGlobalState();
const [showLogin, setShowLogin] = useState(false);
const logOut = useLogOut();
const session = useSession();
const profile = useProfile(session?.pubkey || '', NDKSubscriptionCacheUsage.CACHE_FIRST);
// useEffect(() => {
// if (currentSettings.npubs.length == 0 && currentSettings.tags.length == 0) {
@ -19,22 +25,22 @@ const Layout = () => {
// }, []);
const onLogout = () => {
//setAutoLogin(false);
setState({ userNPub: undefined, profile: undefined });
logOut();
};
return (
<>
{showLogin && <Login onClose={() => setShowLogin(false)} />}
<div className="top-right-controls">
{state.userNPub && state.profile ? (
state.profile.image && (
{session?.pubkey && profile ? (
profile.image && (
<img
referrerPolicy="no-referrer"
className="profile"
onClick={onLogout}
src={createImgProxyUrl(state.profile.image, 80, 80)}
src={createImgProxyUrl(profile.image, 80, 80)}
/>
)
) : (

View File

@ -1,7 +1,6 @@
import { useState } from 'react';
import './Login.css';
import { useBunkerLogin, useExtensionLogin } from '../../ngine/context';
import { useGlobalState } from '../../utils/globalState';
import React from 'react';
type LoginProps = {
@ -10,7 +9,6 @@ type LoginProps = {
const Login = ({ onClose }: LoginProps) => {
const [address, setAddress] = useState('');
const [_, setState] = useGlobalState();
const bunkerLogin = useBunkerLogin();
const extensionLogin = useExtensionLogin();
/*
@ -43,7 +41,6 @@ const Login = ({ onClose }: LoginProps) => {
const loginWithAddress = async () => {
const user = await bunkerLogin(address);
if (user) {
setState({ userNPub: user.npub, profile: user.profile });
onClose();
} else {
console.error('Error loging in');
@ -54,7 +51,6 @@ const Login = ({ onClose }: LoginProps) => {
const user = await extensionLogin();
console.log(user);
if (user) {
setState({ userNPub: user.npub, profile: user.profile });
onClose();
} else {
console.error('Error loging in');

View File

@ -7,6 +7,7 @@ import LazyLoad from 'react-lazy-load';
import uniq from 'lodash/uniq';
import { timeDifference } from '../../utils/time';
import { unixNow } from '../../ngine/time';
import { NDKSubscriptionCacheUsage } from '@nostr-dev-kit/ndk';
interface MasonryImageProps {
image: NostrImage;
@ -35,7 +36,7 @@ const MasonryImage = ({ image, onClick, index }: MasonryImageProps) => {
};
const mediaIsVideo = isVideo(image.url);
const profile = useProfile(image.authorId);
const profile = useProfile(image.authorId, NDKSubscriptionCacheUsage.CACHE_FIRST);
const showAuthor = currentSettings.npubs == undefined || currentSettings.npubs.length != 1; // if we are looking at a single profile, don't show the author

View File

@ -5,12 +5,13 @@ import { Settings } from '../../utils/useNav';
import AuthorProfile from '../AuthorProfile/AuthorProfile';
import { useSwipeable } from 'react-swipeable';
import { Helmet } from 'react-helmet';
import useProfile from '../../utils/useProfile';
import useActiveProfile from '../../utils/useActiveProfile';
import { ViewMode } from '../SlideShow';
import { useGlobalState } from '../../utils/globalState';
import MasonryImage from './MasonryImage';
import useWindowSize from '../../utils/useWindowSize';
import { topics } from '../env';
import useTitle from '../../utils/useTitle';
type MasonryViewProps = {
settings: Settings;
@ -25,7 +26,8 @@ type MImage = NostrImage & {
};
const MasonryView = ({ settings, images, currentImage, setCurrentImage, setViewMode }: MasonryViewProps) => {
const { activeProfile, title, profileNpub } = useProfile(settings);
const { activeProfile, activeNpub } = useActiveProfile(settings);
const title = useTitle(settings, activeProfile);
const [_, setState] = useGlobalState();
const { width } = useWindowSize();
@ -119,7 +121,7 @@ const MasonryView = ({ settings, images, currentImage, setCurrentImage, setViewM
<AuthorProfile
src={urlFix(activeProfile.image || '')}
author={activeProfile.displayName || activeProfile.name}
npub={profileNpub}
npub={activeNpub}
setViewMode={setViewMode}
followButton
externalLink

View File

@ -3,12 +3,13 @@ import { NostrImage, urlFix } from '../nostrImageDownload';
import { Settings } from '../../utils/useNav';
import AuthorProfile from '../AuthorProfile/AuthorProfile';
import { Helmet } from 'react-helmet';
import useProfile from '../../utils/useProfile';
import useActiveProfile from '../../utils/useActiveProfile';
import ScrollImage from './ScrollImage';
import { ViewMode } from '../SlideShow';
import { useGlobalState } from '../../utils/globalState';
import IconChevronUp from '../Icons/IconChevronUp';
import InfoPanel from '../InfoPanel/InfoPanel';
import useTitle from '../../utils/useTitle';
type ScrollViewProps = {
settings: Settings;
@ -44,7 +45,8 @@ const ScrollView = ({ settings, images, currentImage, setCurrentImage, setViewMo
}
}, [images, currentImage, setState]);
const { activeProfile, profileNpub, title } = useProfile(settings, state.activeImage);
const { activeProfile, activeNpub } = useActiveProfile(settings, state.activeImage);
const title = useTitle(settings, activeProfile);
const infoPanelAvailable = state.activeImage && (state.activeImage.content || state.activeImage.tags.length > 0);
// console.log(JSON.stringify([state?.activeImage?.content, state?.activeImage?.tags]));
@ -68,7 +70,7 @@ const ScrollView = ({ settings, images, currentImage, setCurrentImage, setViewMo
<AuthorProfile
src={urlFix(activeProfile.image || '')}
author={activeProfile.displayName || activeProfile.name}
npub={profileNpub}
npub={activeNpub}
setViewMode={setViewMode}
></AuthorProfile>
)}

View File

@ -8,6 +8,7 @@ import { createImgProxyUrl } from './nostrImageDownload';
import { useGlobalState } from '../utils/globalState';
import { ViewMode } from './SlideShow';
import useProfile from '../ngine/hooks/useProfile';
import { NDKSubscriptionCacheUsage } from '@nostr-dev-kit/ndk';
type SettingsProps = {
onClose: () => void;
@ -105,7 +106,7 @@ const SettingsDialog = ({ onClose, setViewMode }: SettingsProps) => {
onClose();
};
const activeProfile = useProfile(npubs[0]);
const activeProfile = useProfile(npubs[0], NDKSubscriptionCacheUsage.CACHE_FIRST);
return (
<>

View File

@ -34,8 +34,9 @@ import GridView from './GridView';
import useWindowSize from '../utils/useWindowSize';
import { useNavigate } from 'react-router-dom';
import { useAtom } from 'jotai';
import { followsAtom } from '../ngine/state';
import { followsAtom, useSession } from '../ngine/state';
import IconRepost from './Icons/IconRepost';
import useProfile from '../ngine/hooks/useProfile';
// type AlbyNostr = typeof window.nostr & { enabled: boolean };
@ -86,9 +87,14 @@ const SlideShow = () => {
const { nav, currentSettings: settings } = useNav();
const [state] = useGlobalState();
const [imageIdx, setImageIdx] = useState<number | undefined>();
const session = useSession();
const profile = useProfile(session?.pubkey || ' ');
const userNPub = session ? nip19.npubEncode(session?.pubkey) as string : undefined;
const { zapClick, heartClick, zapState, heartState, repostClick, repostState } = useZapsAndReations(
state.activeImage,
state.userNPub
userNPub
);
const navigate = useNavigate();
const listAuthors = useAuthorsFromList(settings.list);
@ -325,7 +331,7 @@ const SlideShow = () => {
{state.showNavButtons && (
<div className="bottom-controls">
{state.userNPub && state.activeImage && (
{session?.pubkey && state.activeImage && (
<>
<button
className={`repost ${repostState ? 'reposted' : ''}`}
@ -339,7 +345,7 @@ const SlideShow = () => {
>
<IconHeart></IconHeart>
</button>
{(state.profile?.lud06 || state.profile?.lud16) && (
{(profile?.lud06 || profile?.lud16) && (
<button className={`zap ${zapState}`} onClick={() => state.activeImage && zapClick(state.activeImage)}>
<IconBolt></IconBolt>
</button>

View File

@ -8,8 +8,9 @@ import { Helmet } from 'react-helmet';
import IconPause from '../Icons/IconPause';
import IconSpinner from '../Icons/IconSpinner';
import { Settings } from '../../utils/useNav';
import useProfile from '../../utils/useProfile';
import useActiveProfile from '../../utils/useActiveProfile';
import { ViewMode } from '../SlideShow';
import useTitle from '../../utils/useTitle';
type SlideViewProps = {
settings: Settings;
@ -27,7 +28,8 @@ const SlideView = ({ settings, images, setViewMode }: SlideViewProps) => {
const [activeNpub, setActiveNpub] = useState<string | undefined>(undefined);
const [slideShowStarted, setSlideShowStarted] = useState(false);
const [activeContent, setActiveContent] = useState<string | undefined>(undefined);
const { activeProfile, title } = useProfile(settings);
const { activeProfile } = useActiveProfile(settings);
const title = useTitle(settings, activeProfile);
const queueNextImage = (waitTime: number) => {
clearTimeout(viewTimeoutHandle.current);

View File

@ -173,9 +173,8 @@ export const NgineProvider = ({ ndk, links, children, enableFiatRates = false }:
const pubkey = asURL.pathname.replace(/^\/\//, '');
return { relays, pubkey };
} else {
console.log(url);
//const user = await NDKUser.fromNip05(url, ndk, true); // TODO needs PR FIX in NDK
const user = await getNip05For(url);
// const user = await NDKUser.fromNip05(url, ndk, true); // TODO needs PR FIX in NDK
const user = await getNip05For(url); // WORKAROUND local implementation
if (user) {
const pubkey = user.pubkey;
const relays = user.nip46 && user.nip46.length > 0 ? user.nip46 : ['wss://relay.nsecbunker.com'];
@ -187,16 +186,30 @@ export const NgineProvider = ({ ndk, links, children, enableFiatRates = false }:
}
}
function createOrLoadPrivateKeySigner() {
const LS_NIP46_KEY = 'nip64key';
const existingKey = localStorage.getItem(LS_NIP46_KEY);
if (existingKey) {
return new NDKPrivateKeySigner(existingKey);
} else {
const generatedSigner = NDKPrivateKeySigner.generate();
const generatedKey = generatedSigner.privateKey;
if (generatedKey) {
localStorage.setItem(LS_NIP46_KEY, generatedKey);
}
return generatedSigner;
}
}
async function nip46Login(url: string) {
const settings = await getNostrConnectSettings(url);
if (settings) {
console.log(settings);
const { pubkey, relays } = settings;
const bunkerNDK = new NDK({
explicitRelayUrls: relays,
});
await bunkerNDK.connect();
const localSigner = NDKPrivateKeySigner.generate();
const localSigner = createOrLoadPrivateKeySigner();
console.log('localSigner', localSigner);
const signer = new NDKNip46Signer(bunkerNDK, pubkey, localSigner);
console.log('signer', signer);

View File

@ -1,16 +1,12 @@
import { NostrImage } from '@/components/nostrImageDownload';
import { NDKUserProfile } from '@nostr-dev-kit/ndk';
import React, { createContext, useContext, useReducer } from 'react';
// Interface for our state
interface GlobalState {
userNPub?: string;
profile?: NDKUserProfile;
showNavButtons: boolean;
showNavButtons: boolean; // can be removed when the settings dialog is refactored.
activeImage?: NostrImage;
}
const initialState: GlobalState = {
userNPub: undefined,
showNavButtons: true,
};

View File

@ -0,0 +1,17 @@
import { Settings } from './useNav';
import { NostrImage } from '../components/nostrImageDownload';
import { nip19 } from 'nostr-tools';
import { NDKSubscriptionCacheUsage } from '@nostr-dev-kit/ndk';
import useProfile from '../ngine/hooks/useProfile';
const useActiveProfile = (settings: Settings, activeImage?: NostrImage) => {
const activeNpub = settings.npubs.length == 1 ? settings.npubs[0] : activeImage && activeImage?.author;
const activePubKeyHex = activeNpub ? (nip19.decode(activeNpub).data as string) : '';
const activeProfile = useProfile(activePubKeyHex, NDKSubscriptionCacheUsage.CACHE_FIRST);
return {
activeProfile,
activeNpub,
};
};
export default useActiveProfile;

View File

@ -4,11 +4,10 @@ import { nip19 } from 'nostr-tools';
const KIND_PEOPLE_LIST = 30000;
const usePeopleLists = (npub?: string) => {
const pubkey = npub && (nip19.decode(npub).data as string);
const usePeopleLists = (pubkey?: string) => {
const { events } = useEvents(
{ kinds: [KIND_PEOPLE_LIST], authors: pubkey ? [pubkey] : [], limit: 50 },
{ disable: !npub }
{ disable: !pubkey }
);
const peopleLists = useMemo(() => {

View File

@ -1,24 +1,14 @@
import { appName, topics } from '../components/env';
import { useEffect, useState } from 'react';
import { Settings } from './useNav';
import { NostrImage } from '../components/nostrImageDownload';
import useProfileNgine from '../ngine/hooks/useProfile';
import { nip19 } from 'nostr-tools';
import { NDKSubscriptionCacheUsage } from '@nostr-dev-kit/ndk';
import { useLocation, useParams } from 'react-router-dom';
import { Settings } from './useNav';
import { NDKUserProfile } from '@nostr-dev-kit/ndk';
// TODO maybe remove profile and only build title here?? useTitle?
const useProfile = (settings: Settings, activeImage?: NostrImage) => {
const useTitle = (settings: Settings, activeProfile?: NDKUserProfile) => {
const [title, setTitle] = useState(appName);
const location = useLocation();
const { topic } = useParams();
const profileNpub = settings.npubs.length == 1 ? settings.npubs[0] : activeImage && activeImage?.author;
const pubKeyHex = profileNpub ? (nip19.decode(profileNpub).data as string) : '';
const activeProfile = useProfileNgine(pubKeyHex, NDKSubscriptionCacheUsage.ONLY_RELAY);
useEffect(() => {
if (settings.npubs.length > 0 && activeProfile && (activeProfile.displayName || activeProfile.name)) {
setTitle((activeProfile.displayName || activeProfile.name) + ` | ${appName}`);
@ -33,12 +23,7 @@ const useProfile = (settings: Settings, activeImage?: NostrImage) => {
}
}, [activeProfile, settings.npubs, settings.tags, topic, location]);
return {
activeProfile,
title,
profileNpub,
pubKeyHex,
};
return title;
};
export default useProfile;
export default useTitle;