add ndk provider

This commit is contained in:
Ren Amamiya
2023-07-09 07:21:41 +07:00
parent 0109ec28c4
commit 24807b2758
26 changed files with 178 additions and 106 deletions

View File

@@ -1,21 +1,22 @@
import { NDKEvent, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
import { Body, fetch } from '@tauri-apps/api/http';
import { useContext, useState } from 'react';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useNDK } from '@libs/ndk/provider';
import { Button } from '@shared/button';
import { LoaderIcon } from '@shared/icons';
import { RelayContext } from '@shared/relayProvider';
import { useOnboarding } from '@stores/onboarding';
import { useAccount } from '@utils/hooks/useAccount';
export function CreateStep3Screen() {
const ndk = useContext(RelayContext);
const profile = useOnboarding((state: any) => state.profile);
const navigate = useNavigate();
const { ndk } = useNDK();
const { account } = useAccount();
const [username, setUsername] = useState('');

View File

@@ -1,10 +1,11 @@
import { NDKEvent, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useContext, useState } from 'react';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { User } from '@app/auth/components/user';
import { useNDK } from '@libs/ndk/provider';
import { updateAccount } from '@libs/storage';
import { CheckCircleIcon, LoaderIcon } from '@shared/icons';
@@ -113,13 +114,13 @@ const INITIAL_LIST = [
];
export function CreateStep4Screen() {
const ndk = useContext(RelayContext);
const queryClient = useQueryClient();
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const [follows, setFollows] = useState([]);
const { ndk } = useNDK();
const { account } = useAccount();
const { status, data } = useQuery(['trending-profiles'], async () => {
const res = await fetch('https://api.nostr.band/v0/trending/profiles');

View File

@@ -1,24 +1,25 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useContext, useState } from 'react';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { User } from '@app/auth/components/user';
import { useNDK } from '@libs/ndk/provider';
import { updateAccount } from '@libs/storage';
import { Button } from '@shared/button';
import { LoaderIcon } from '@shared/icons';
import { RelayContext } from '@shared/relayProvider';
import { useAccount } from '@utils/hooks/useAccount';
import { setToArray } from '@utils/transform';
export function ImportStep2Screen() {
const ndk = useContext(RelayContext);
const queryClient = useQueryClient();
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const { ndk } = useNDK();
const { status, account } = useAccount();
const update = useMutation({

View File

@@ -1,13 +1,12 @@
import { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { usePublish } from '@libs/ndk';
import { LoaderIcon } from '@shared/icons';
import { ArrowRightCircleIcon } from '@shared/icons/arrowRightCircle';
import { User } from '@shared/user';
import { useAccount } from '@utils/hooks/useAccount';
import { usePublish } from '@utils/hooks/usePublish';
export function OnboardingScreen() {
const publish = usePublish();

View File

@@ -1,11 +1,11 @@
import { nip04 } from 'nostr-tools';
import { useCallback, useState } from 'react';
import { usePublish } from '@libs/ndk';
import { EnterIcon } from '@shared/icons';
import { MediaUploader } from '@shared/mediaUploader';
import { usePublish } from '@utils/hooks/usePublish';
export function ChatMessageForm({
receiverPubkey,
userPrivkey,

View File

@@ -1,6 +1,6 @@
import { NDKSubscription } from '@nostr-dev-kit/ndk';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useCallback, useContext, useEffect, useRef } from 'react';
import { useCallback, useEffect, useRef } from 'react';
import { useParams } from 'react-router-dom';
import { Virtuoso } from 'react-virtuoso';
@@ -8,17 +8,16 @@ import { ChatMessageForm } from '@app/chat/components/messages/form';
import { ChatMessageItem } from '@app/chat/components/messages/item';
import { ChatSidebar } from '@app/chat/components/sidebar';
import { useNDK } from '@libs/ndk/provider';
import { createChat, getChatMessages } from '@libs/storage';
import { RelayContext } from '@shared/relayProvider';
import { useAccount } from '@utils/hooks/useAccount';
export function ChatScreen() {
const ndk = useContext(RelayContext);
const queryClient = useQueryClient();
const virtuosoRef = useRef(null);
const { ndk } = useNDK();
const { pubkey } = useParams();
const { account } = useAccount();
const { status, data } = useQuery(

View File

@@ -1,8 +1,9 @@
import { NDKFilter } from '@nostr-dev-kit/ndk';
import { useContext, useEffect, useRef } from 'react';
import { useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { prefetchEvents } from '@libs/ndk';
import { useNDK } from '@libs/ndk/provider';
import {
countTotalNotes,
createChat,
@@ -12,7 +13,6 @@ import {
} from '@libs/storage';
import { LoaderIcon, LumeIcon } from '@shared/icons';
import { RelayContext } from '@shared/relayProvider';
import { dateToUnix, getHourAgo } from '@utils/date';
import { useAccount } from '@utils/hooks/useAccount';
@@ -21,10 +21,10 @@ const totalNotes = await countTotalNotes();
const lastLogin = await getLastLogin();
export function Root() {
const ndk = useContext(RelayContext);
const now = useRef(new Date());
const navigate = useNavigate();
const { ndk } = useNDK();
const { status, account } = useAccount();
async function fetchNotes() {

View File

@@ -3,15 +3,15 @@ import { NDKEvent, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { open } from '@tauri-apps/api/dialog';
import { Body, fetch } from '@tauri-apps/api/http';
import { Fragment, useContext, useEffect, useRef, useState } from 'react';
import { Fragment, useEffect, useRef, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useHotkeys } from 'react-hotkeys-hook';
import { useNDK } from '@libs/ndk/provider';
import { createBlock } from '@libs/storage';
import { CancelIcon, CommandIcon } from '@shared/icons';
import { Image } from '@shared/image';
import { RelayContext } from '@shared/relayProvider';
import { DEFAULT_AVATAR } from '@stores/constants';
import { ADD_IMAGEBLOCK_SHORTCUT } from '@stores/shortcuts';
@@ -21,13 +21,13 @@ import { dateToUnix } from '@utils/date';
import { useAccount } from '@utils/hooks/useAccount';
export function AddImageBlock() {
const ndk = useContext(RelayContext);
const queryClient = useQueryClient();
const [loading, setLoading] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [image, setImage] = useState('');
const { ndk } = useNDK();
const { account } = useAccount();
const tags = useRef(null);

View File

@@ -1,16 +1,16 @@
import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useContext, useEffect, useRef } from 'react';
import { useEffect, useRef } from 'react';
import { useNDK } from '@libs/ndk/provider';
import { createReplyNote } from '@libs/storage';
import { RelayContext } from '@shared/relayProvider';
export function useLiveThread(id: string) {
const ndk = useContext(RelayContext);
const queryClient = useQueryClient();
const now = useRef(Math.floor(Date.now() / 1000));
const { ndk } = useNDK();
const thread = useMutation({
mutationFn: (data: NDKEvent) => {
return createReplyNote(

View File

@@ -1,22 +1,22 @@
import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk';
import { useContext, useEffect, useRef } from 'react';
import { useEffect, useRef } from 'react';
import { useNDK } from '@libs/ndk/provider';
import { createNote } from '@libs/storage';
import { RelayContext } from '@shared/relayProvider';
import { useNote } from '@stores/note';
import { useAccount } from '@utils/hooks/useAccount';
export function useNewsfeed() {
const ndk = useContext(RelayContext);
const sub = useRef(null);
const now = useRef(Math.floor(Date.now() / 1000));
const toggleHasNewNote = useNote((state) => state.toggleHasNewNote);
const { ndk } = useNDK();
const { status, account } = useAccount();
const toggleHasNewNote = useNote((state) => state.toggleHasNewNote);
useEffect(() => {
if (status === 'success' && account) {
const follows = account ? JSON.parse(account.follows) : [];

View File

@@ -1,15 +1,15 @@
import { NDKFilter } from '@nostr-dev-kit/ndk';
import { useQuery } from '@tanstack/react-query';
import { useContext } from 'react';
import { useNDK } from '@libs/ndk/provider';
import { Note } from '@shared/notes/note';
import { RelayContext } from '@shared/relayProvider';
import { dateToUnix, getHourAgo } from '@utils/date';
import { LumeEvent } from '@utils/types';
export function UserFeed({ pubkey }: { pubkey: string }) {
const ndk = useContext(RelayContext);
const { ndk } = useNDK();
const { status, data } = useQuery(['user-feed', pubkey], async () => {
const now = new Date();
const filter: NDKFilter = {

View File

@@ -5,9 +5,8 @@ import NDK, {
NDKKind,
NDKPrivateKeySigner,
} from '@nostr-dev-kit/ndk';
import { useContext } from 'react';
import { RelayContext } from '@shared/relayProvider';
import { useNDK } from '@libs/ndk/provider';
import { FULL_RELAYS } from '@stores/constants';
@@ -46,34 +45,3 @@ export async function prefetchEvents(
});
});
}
export function usePublish() {
const ndk = useContext(RelayContext);
const { account } = useAccount();
const publish = async ({
content,
kind,
tags,
}: {
content: string;
kind: NDKKind;
tags: string[][];
}): Promise<NDKEvent> => {
const event = new NDKEvent(ndk);
const signer = new NDKPrivateKeySigner(account.privkey);
event.content = content;
event.kind = kind;
event.created_at = Math.floor(Date.now() / 1000);
event.pubkey = account.pubkey;
event.tags = tags;
await event.sign(signer);
await event.publish();
return event;
};
return publish;
}

36
src/libs/ndk/instance.ts Normal file
View File

@@ -0,0 +1,36 @@
// source: https://github.com/nostr-dev-kit/ndk-react/
import NDK from '@nostr-dev-kit/ndk';
import { useEffect, useState } from 'react';
import { getSetting } from '@libs/storage';
const setting = await getSetting('relays');
const relays = JSON.parse(setting);
export const NDKInstance = () => {
const [ndk, setNDK] = useState<NDK | undefined>(undefined);
const [relayUrls, setRelayUrls] = useState<string[]>(relays);
useEffect(() => {
loadNdk(relays);
}, []);
async function loadNdk(explicitRelayUrls: string[]) {
const ndkInstance = new NDK({ explicitRelayUrls });
try {
await ndkInstance.connect();
} catch (error) {
console.error('ERROR loading NDK NDKInstance', error);
}
setNDK(ndkInstance);
setRelayUrls(explicitRelayUrls);
}
return {
ndk,
relayUrls,
loadNdk,
};
};

44
src/libs/ndk/provider.tsx Normal file
View File

@@ -0,0 +1,44 @@
// source: https://github.com/nostr-dev-kit/ndk-react/
import NDK from '@nostr-dev-kit/ndk';
import { PropsWithChildren, createContext, useContext } from 'react';
import { NDKInstance } from '@libs/ndk/instance';
interface NDKContext {
ndk: NDK;
relayUrls: string[];
loadNdk: (_: string[]) => void;
}
const NDKContext = createContext<NDKContext>({
ndk: new NDK({}),
relayUrls: [],
loadNdk: undefined,
});
const NDKProvider = ({ children }: PropsWithChildren<object>) => {
const { ndk, relayUrls, loadNdk } = NDKInstance();
if (ndk)
return (
<NDKContext.Provider
value={{
ndk,
relayUrls,
loadNdk,
}}
>
{children}
</NDKContext.Provider>
);
};
const useNDK = () => {
const context = useContext(NDKContext);
if (context === undefined) {
throw new Error('import NDKProvider to use useNDK');
}
return context;
};
export { NDKProvider, useNDK };

View File

@@ -2,10 +2,9 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { createRoot } from 'react-dom/client';
import { NDKProvider } from '@libs/ndk/provider';
import { getSetting } from '@libs/storage';
import { RelayProvider } from '@shared/relayProvider';
import App from './app';
const cacheTime = await getSetting('cache_time');
@@ -23,9 +22,9 @@ const root = createRoot(container);
root.render(
<QueryClientProvider client={queryClient}>
<RelayProvider>
<NDKProvider>
<App />
</RelayProvider>
</NDKProvider>
<ReactQueryDevtools initialIsOpen={false} position="top-right" />
</QueryClientProvider>
);

View File

@@ -1,13 +1,13 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { produce } from 'immer';
import { useContext, useEffect } from 'react';
import { useEffect } from 'react';
import { Link } from 'react-router-dom';
import { useNDK } from '@libs/ndk/provider';
import { createChat, getLastLogin } from '@libs/storage';
import { Image } from '@shared/image';
import { NetworkStatusIndicator } from '@shared/networkStatusIndicator';
import { RelayContext } from '@shared/relayProvider';
import { DEFAULT_AVATAR } from '@stores/constants';
@@ -17,9 +17,9 @@ import { sendNativeNotification } from '@utils/notification';
const lastLogin = await getLastLogin();
export function ActiveAccount({ data }: { data: any }) {
const ndk = useContext(RelayContext);
const queryClient = useQueryClient();
const { ndk } = useNDK();
const { status, user } = useProfile(data.pubkey);
const chat = useMutation({

View File

@@ -3,8 +3,6 @@ import { Node, Transforms, createEditor } from 'slate';
import { withHistory } from 'slate-history';
import { Editable, ReactEditor, Slate, useSlateStatic, withReact } from 'slate-react';
import { usePublish } from '@libs/ndk';
import { Button } from '@shared/button';
import { ImageUploader } from '@shared/composer/imageUploader';
import { CancelIcon, TrashIcon } from '@shared/icons';
@@ -13,6 +11,8 @@ import { MentionNote } from '@shared/notes/mentions/note';
import { useComposer } from '@stores/composer';
import { FULL_RELAYS } from '@stores/constants';
import { usePublish } from '@utils/hooks/usePublish';
const withImages = (editor) => {
const { isVoid } = editor;

View File

@@ -5,8 +5,6 @@ import { fetch } from '@tauri-apps/api/http';
import { Fragment, useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { usePublish } from '@libs/ndk';
import { AvatarUploader } from '@shared/avatarUploader';
import { BannerUploader } from '@shared/bannerUploader';
import { CancelIcon, CheckCircleIcon, LoaderIcon, UnverifiedIcon } from '@shared/icons';
@@ -15,6 +13,7 @@ import { Image } from '@shared/image';
import { DEFAULT_AVATAR } from '@stores/constants';
import { useAccount } from '@utils/hooks/useAccount';
import { usePublish } from '@utils/hooks/usePublish';
export function EditProfileModal() {
const queryClient = useQueryClient();

View File

@@ -2,8 +2,8 @@ import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk';
import * as Tooltip from '@radix-ui/react-tooltip';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { decode } from 'light-bolt11-decoder';
import { useContext } from 'react';
import { useNDK } from '@libs/ndk/provider';
import { createBlock, createReplyNote } from '@libs/storage';
import { LoaderIcon, ReplyIcon, RepostIcon, ZapIcon } from '@shared/icons';
@@ -11,7 +11,6 @@ import { ThreadIcon } from '@shared/icons/thread';
import { NoteReply } from '@shared/notes/metadata/reply';
import { NoteRepost } from '@shared/notes/metadata/repost';
import { NoteZap } from '@shared/notes/metadata/zap';
import { RelayContext } from '@shared/relayProvider';
export function NoteMetadata({
id,
@@ -22,9 +21,9 @@ export function NoteMetadata({
rootID?: string;
eventPubkey: string;
}) {
const ndk = useContext(RelayContext);
const queryClient = useQueryClient();
const { ndk } = useNDK();
const { status, data } = useQuery(['note-metadata', id], async () => {
let replies = 0;
let reposts = 0;

View File

@@ -1,11 +1,10 @@
import * as Tooltip from '@radix-ui/react-tooltip';
import { usePublish } from '@libs/ndk';
import { RepostIcon } from '@shared/icons';
import { FULL_RELAYS } from '@stores/constants';
import { usePublish } from '@utils/hooks/usePublish';
import { compactNumber } from '@utils/number';
export function NoteRepost({

View File

@@ -1,13 +1,12 @@
import { useState } from 'react';
import { usePublish } from '@libs/ndk';
import { Button } from '@shared/button';
import { Image } from '@shared/image';
import { DEFAULT_AVATAR, FULL_RELAYS } from '@stores/constants';
import { useProfile } from '@utils/hooks/useProfile';
import { usePublish } from '@utils/hooks/usePublish';
import { shortenKey } from '@utils/shortenKey';
export function NoteReplyForm({

View File

@@ -1,22 +1,21 @@
import { Dialog, Transition } from '@headlessui/react';
import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk';
import { useQuery } from '@tanstack/react-query';
import { Fragment, useContext, useRef, useState } from 'react';
import { Fragment, useRef, useState } from 'react';
import { useNDK } from '@libs/ndk/provider';
import { BellIcon, CancelIcon, LoaderIcon } from '@shared/icons';
import { RelayContext } from '@shared/relayProvider';
import { NotificationUser } from '@shared/notification/user';
import { User } from '@shared/user';
import { dateToUnix, getHourAgo } from '@utils/date';
import { NotificationUser } from './user';
export function NotificationModal({ pubkey }: { pubkey: string }) {
const ndk = useContext(RelayContext);
const now = useRef(new Date());
const [isOpen, setIsOpen] = useState(false);
const { ndk } = useNDK();
const { status, data } = useQuery(
['user-notification', pubkey],
async () => {

View File

@@ -1,14 +1,12 @@
import { useQuery } from '@tanstack/react-query';
import { useContext } from 'react';
import { useNDK } from '@libs/ndk/provider';
import { createNote, getNoteByID } from '@libs/storage';
import { RelayContext } from '@shared/relayProvider';
import { parser } from '@utils/parser';
export function useEvent(id: string) {
const ndk = useContext(RelayContext);
const { ndk } = useNDK();
const { status, data, error, isFetching } = useQuery(
['note', id],
async () => {

View File

@@ -1,12 +1,10 @@
import { useQuery } from '@tanstack/react-query';
import { useContext } from 'react';
import { useNDK } from '@libs/ndk/provider';
import { createMetadata, getUserMetadata } from '@libs/storage';
import { RelayContext } from '@shared/relayProvider';
export function useProfile(pubkey: string, fallback?: string) {
const ndk = useContext(RelayContext);
const { ndk } = useNDK();
const {
status,
data: user,

View File

@@ -0,0 +1,36 @@
import { NDKEvent, NDKKind, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
import { useNDK } from '@libs/ndk/provider';
import { useAccount } from '@utils/hooks/useAccount';
export function usePublish() {
const { ndk } = useNDK();
const { account } = useAccount();
const publish = async ({
content,
kind,
tags,
}: {
content: string;
kind: NDKKind;
tags: string[][];
}): Promise<NDKEvent> => {
const event = new NDKEvent(ndk);
const signer = new NDKPrivateKeySigner(account.privkey);
event.content = content;
event.kind = kind;
event.created_at = Math.floor(Date.now() / 1000);
event.pubkey = account.pubkey;
event.tags = tags;
await event.sign(signer);
await event.publish();
return event;
};
return publish;
}

View File

@@ -1,22 +1,19 @@
import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useContext } from 'react';
import { usePublish } from '@libs/ndk';
import { useNDK } from '@libs/ndk/provider';
import { createNote } from '@libs/storage';
import { RelayContext } from '@shared/relayProvider';
import { dateToUnix, getHourAgo } from '@utils/date';
import { useAccount } from '@utils/hooks/useAccount';
import { usePublish } from '@utils/hooks/usePublish';
import { nip02ToArray } from '@utils/transform';
import { useAccount } from './useAccount';
export function useSocial() {
const ndk = useContext(RelayContext);
const queryClient = useQueryClient();
const publish = usePublish();
const { ndk } = useNDK();
const { account } = useAccount();
const { status, data: userFollows } = useQuery(
['userFollows', account.pubkey],