From 72575edcc3236d929852f5c2f9ec3a3f50e64d01 Mon Sep 17 00:00:00 2001 From: florian <> Date: Fri, 8 Mar 2024 19:41:47 +0000 Subject: [PATCH] feat: Use session to remember login --- .../AuthorProfile/AuthorProfile.tsx | 8 +++--- src/components/GridView/index.tsx | 8 +++--- src/components/Home/index.tsx | 24 +++++++++++------- src/components/InfoPanel/InfoPanel.tsx | 6 ++--- src/components/Layout/Layout.tsx | 20 +++++++++------ src/components/Login/Login.tsx | 4 --- src/components/MasonryView/MasonryImage.tsx | 3 ++- src/components/MasonryView/MasonryView.tsx | 8 +++--- src/components/ScrollView/ScrollView.tsx | 8 +++--- src/components/Settings.tsx | 3 ++- src/components/SlideShow.tsx | 14 ++++++++--- src/components/SlideView/index.tsx | 6 +++-- src/ngine/context.tsx | 23 +++++++++++++---- src/utils/globalState.tsx | 6 +---- src/utils/useActiveProfile.ts | 17 +++++++++++++ src/utils/useLists.ts | 5 ++-- src/utils/{useProfile.ts => useTitle.ts} | 25 ++++--------------- 17 files changed, 111 insertions(+), 77 deletions(-) create mode 100644 src/utils/useActiveProfile.ts rename src/utils/{useProfile.ts => useTitle.ts} (56%) diff --git a/src/components/AuthorProfile/AuthorProfile.tsx b/src/components/AuthorProfile/AuthorProfile.tsx index 35838d3..25dc925 100644 --- a/src/components/AuthorProfile/AuthorProfile.tsx +++ b/src/components/AuthorProfile/AuthorProfile.tsx @@ -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 (
diff --git a/src/components/GridView/index.tsx b/src/components/GridView/index.tsx index 8109dbf..5b921fe 100644 --- a/src/components/GridView/index.tsx +++ b/src/components/GridView/index.tsx @@ -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 { 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 (
@@ -55,7 +61,7 @@ const Home = () => {
All content posted on nostr. Use with care!
- {state.userNPub && ( + {session?.pubkey && ( <>

Your...

@@ -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, diff --git a/src/components/InfoPanel/InfoPanel.tsx b/src/components/InfoPanel/InfoPanel.tsx index 3128a57..607bca1 100644 --- a/src/components/InfoPanel/InfoPanel.tsx +++ b/src/components/InfoPanel/InfoPanel.tsx @@ -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 && ( diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx index e6d3d04..5ee5f4f 100644 --- a/src/components/Layout/Layout.tsx +++ b/src/components/Layout/Layout.tsx @@ -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 && setShowLogin(false)} />}
- {state.userNPub && state.profile ? ( - state.profile.image && ( + {session?.pubkey && profile ? ( + profile.image && ( ) ) : ( diff --git a/src/components/Login/Login.tsx b/src/components/Login/Login.tsx index 871ff15..6464ad5 100644 --- a/src/components/Login/Login.tsx +++ b/src/components/Login/Login.tsx @@ -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'); diff --git a/src/components/MasonryView/MasonryImage.tsx b/src/components/MasonryView/MasonryImage.tsx index 31f2ef1..3e1302d 100644 --- a/src/components/MasonryView/MasonryImage.tsx +++ b/src/components/MasonryView/MasonryImage.tsx @@ -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 diff --git a/src/components/MasonryView/MasonryView.tsx b/src/components/MasonryView/MasonryView.tsx index 9669149..4718721 100644 --- a/src/components/MasonryView/MasonryView.tsx +++ b/src/components/MasonryView/MasonryView.tsx @@ -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 0); // console.log(JSON.stringify([state?.activeImage?.content, state?.activeImage?.tags])); @@ -68,7 +70,7 @@ const ScrollView = ({ settings, images, currentImage, setCurrentImage, setViewMo )} diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index 3871875..0528e2e 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -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 ( <> diff --git a/src/components/SlideShow.tsx b/src/components/SlideShow.tsx index dd66b78..ad1c0fc 100644 --- a/src/components/SlideShow.tsx +++ b/src/components/SlideShow.tsx @@ -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(); + + 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 && (
- {state.userNPub && state.activeImage && ( + {session?.pubkey && state.activeImage && ( <> - {(state.profile?.lud06 || state.profile?.lud16) && ( + {(profile?.lud06 || profile?.lud16) && ( diff --git a/src/components/SlideView/index.tsx b/src/components/SlideView/index.tsx index 1df0900..2466459 100644 --- a/src/components/SlideView/index.tsx +++ b/src/components/SlideView/index.tsx @@ -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(undefined); const [slideShowStarted, setSlideShowStarted] = useState(false); const [activeContent, setActiveContent] = useState(undefined); - const { activeProfile, title } = useProfile(settings); + const { activeProfile } = useActiveProfile(settings); + const title = useTitle(settings, activeProfile); const queueNextImage = (waitTime: number) => { clearTimeout(viewTimeoutHandle.current); diff --git a/src/ngine/context.tsx b/src/ngine/context.tsx index b082b7f..bb87091 100644 --- a/src/ngine/context.tsx +++ b/src/ngine/context.tsx @@ -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); diff --git a/src/utils/globalState.tsx b/src/utils/globalState.tsx index 4b31403..82794dc 100644 --- a/src/utils/globalState.tsx +++ b/src/utils/globalState.tsx @@ -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, }; diff --git a/src/utils/useActiveProfile.ts b/src/utils/useActiveProfile.ts new file mode 100644 index 0000000..d03423a --- /dev/null +++ b/src/utils/useActiveProfile.ts @@ -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; diff --git a/src/utils/useLists.ts b/src/utils/useLists.ts index 3632564..bf3b43b 100644 --- a/src/utils/useLists.ts +++ b/src/utils/useLists.ts @@ -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(() => { diff --git a/src/utils/useProfile.ts b/src/utils/useTitle.ts similarity index 56% rename from src/utils/useProfile.ts rename to src/utils/useTitle.ts index 50c1059..6136687 100644 --- a/src/utils/useProfile.ts +++ b/src/utils/useTitle.ts @@ -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;