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 && (
<>
@@ -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;