convert decryption cache to service

This commit is contained in:
hzrd149
2024-08-08 08:23:59 -05:00
parent cbddaaa44f
commit 7ebf09c24f
13 changed files with 226 additions and 197 deletions

View File

@@ -25,6 +25,11 @@ export const DEFAULT_ICE_SERVERS: RTCIceServer[] = [
{ {
urls: ["stun:stun.l.google.com:19302"], urls: ["stun:stun.l.google.com:19302"],
}, },
{
urls: ["turn:172.234.18.173:3478"],
username: "free",
credential: "free",
},
]; ];
export const NOSTR_CONNECT_PERMISSIONS = [ export const NOSTR_CONNECT_PERMISSIONS = [

View File

@@ -0,0 +1,29 @@
import { useCallback, useMemo } from "react";
import { NostrEvent } from "nostr-tools";
import decryptionCacheService from "../services/decryption-cache";
import useCurrentAccount from "./use-current-account";
import useSubject from "./use-subject";
import { getDMRecipient, getDMSender } from "../helpers/nostr/dms";
export function useKind4Decrypt(event: NostrEvent, pubkey?: string) {
const account = useCurrentAccount()!;
pubkey = pubkey || event.pubkey === account.pubkey ? getDMRecipient(event) : getDMSender(event);
const container = useMemo(
() => decryptionCacheService.getOrCreateContainer(event.id, "nip04", pubkey, event.content),
[event, pubkey],
);
const plaintext = useSubject(container.plaintext);
const error = useSubject(container.error);
const requestDecrypt = useCallback(() => {
const p = decryptionCacheService.requestDecrypt(container);
decryptionCacheService.startDecryptionQueue();
return p;
}, [container]);
return { container, error, plaintext, requestDecrypt };
}

View File

@@ -1,142 +0,0 @@
import { PropsWithChildren, createContext, useCallback, useContext, useMemo, useRef } from "react";
import { nanoid } from "nanoid";
import Subject from "../../classes/subject";
import { useSigningContext } from "./signing-provider";
import useSubject from "../../hooks/use-subject";
import createDefer, { Deferred } from "../../classes/deferred";
class DecryptionContainer {
id = nanoid(8);
pubkey: string;
data: string;
plaintext = new Subject<string>();
error = new Subject<Error>();
constructor(pubkey: string, data: string) {
this.pubkey = pubkey;
this.data = data;
}
}
type DecryptionContextType = {
getOrCreateContainer: (pubkey: string, data: string) => DecryptionContainer;
startQueue: () => void;
clearQueue: () => void;
addToQueue: (container: DecryptionContainer) => Promise<string>;
getQueue: () => DecryptionContainer[];
};
const DecryptionContext = createContext<DecryptionContextType>({
getOrCreateContainer: () => {
throw new Error("No DecryptionProvider");
},
startQueue: () => {},
clearQueue: () => {},
addToQueue: () => Promise.reject(new Error("No DecryptionProvider")),
getQueue: () => [],
});
export function useDecryptionContext() {
return useContext(DecryptionContext);
}
export function useDecryptionContainer(pubkey: string, data: string) {
const { getOrCreateContainer, addToQueue, startQueue } = useContext(DecryptionContext);
const container = getOrCreateContainer(pubkey, data);
const plaintext = useSubject(container.plaintext);
const error = useSubject(container.error);
const requestDecrypt = useCallback(() => {
const p = addToQueue(container);
startQueue();
return p;
}, [addToQueue, startQueue]);
return { container, error, plaintext, requestDecrypt };
}
export default function DecryptionProvider({ children }: PropsWithChildren) {
const { requestDecrypt } = useSigningContext();
const containers = useRef<DecryptionContainer[]>([]);
const queue = useRef<DecryptionContainer[]>([]);
const promises = useRef<Map<DecryptionContainer, Deferred<string>>>(new Map());
const running = useRef<boolean>(false);
const getQueue = useCallback(() => queue.current, []);
const clearQueue = useCallback(() => {
queue.current = [];
promises.current.clear();
}, []);
const addToQueue = useCallback((container: DecryptionContainer) => {
queue.current.unshift(container);
let p = promises.current.get(container);
if (!p) {
p = createDefer<string>();
promises.current.set(container, p);
}
return p;
}, []);
const getOrCreateContainer = useCallback((pubkey: string, data: string) => {
let container = containers.current.find((c) => c.pubkey === pubkey && c.data === data);
if (!container) {
container = new DecryptionContainer(pubkey, data);
containers.current.push(container);
}
return container;
}, []);
const startQueue = useCallback(() => {
if (running.current === true) return;
running.current = false;
async function decryptNext() {
if (running.current === true) return;
const container = queue.current.pop();
if (!container) {
running.current = false;
promises.current.clear();
return;
}
const promise = promises.current.get(container)!;
try {
const plaintext = await requestDecrypt(container.data, container.pubkey);
// set plaintext
container.plaintext.next(plaintext);
promise.resolve(plaintext);
// remove promise
promises.current.delete(container);
setTimeout(() => decryptNext(), 100);
} catch (e) {
if (e instanceof Error) {
// set error
container.error.next(e);
promise.reject(e);
// clear queue
running.current = false;
queue.current = [];
promises.current.clear();
}
}
}
// start cycle
decryptNext();
}, [requestDecrypt]);
const context = useMemo(
() => ({ getQueue, addToQueue, clearQueue, getOrCreateContainer, startQueue }),
[getQueue, addToQueue, clearQueue, getOrCreateContainer, startQueue],
);
return <DecryptionContext.Provider value={context}>{children}</DecryptionContext.Provider>;
}

View File

@@ -8,7 +8,6 @@ import NotificationsProvider from "./notifications-provider";
import { DefaultEmojiProvider, UserEmojiProvider } from "./emoji-provider"; import { DefaultEmojiProvider, UserEmojiProvider } from "./emoji-provider";
import { AllUserSearchDirectoryProvider } from "./user-directory-provider"; import { AllUserSearchDirectoryProvider } from "./user-directory-provider";
import BreakpointProvider from "./breakpoint-provider"; import BreakpointProvider from "./breakpoint-provider";
import DecryptionProvider from "./decryption-provider";
import DMTimelineProvider from "./dms-provider"; import DMTimelineProvider from "./dms-provider";
import PublishProvider from "./publish-provider"; import PublishProvider from "./publish-provider";
import WebOfTrustProvider from "./web-of-trust-provider"; import WebOfTrustProvider from "./web-of-trust-provider";
@@ -26,19 +25,17 @@ export const GlobalProviders = ({ children }: { children: React.ReactNode }) =>
<BreakpointProvider> <BreakpointProvider>
<SigningProvider> <SigningProvider>
<PublishProvider> <PublishProvider>
<DecryptionProvider> <NotificationsProvider>
<NotificationsProvider> <DMTimelineProvider>
<DMTimelineProvider> <DefaultEmojiProvider>
<DefaultEmojiProvider> <UserEmojiProvider>
<UserEmojiProvider> <AllUserSearchDirectoryProvider>
<AllUserSearchDirectoryProvider> <WebOfTrustProvider>{children}</WebOfTrustProvider>
<WebOfTrustProvider>{children}</WebOfTrustProvider> </AllUserSearchDirectoryProvider>
</AllUserSearchDirectoryProvider> </UserEmojiProvider>
</UserEmojiProvider> </DefaultEmojiProvider>
</DefaultEmojiProvider> </DMTimelineProvider>
</DMTimelineProvider> </NotificationsProvider>
</NotificationsProvider>
</DecryptionProvider>
</PublishProvider> </PublishProvider>
</SigningProvider> </SigningProvider>
</BreakpointProvider> </BreakpointProvider>

View File

@@ -42,14 +42,14 @@ export function SigningProvider({ children }: { children: React.ReactNode }) {
const requestDecrypt = useCallback( const requestDecrypt = useCallback(
async (data: string, pubkey: string) => { async (data: string, pubkey: string) => {
if (!current) throw new Error("No account"); if (!current) throw new Error("No account");
return await signingService.requestDecrypt(data, pubkey, current); return await signingService.nip04Decrypt(data, pubkey, current);
}, },
[toast, current], [toast, current],
); );
const requestEncrypt = useCallback( const requestEncrypt = useCallback(
async (data: string, pubkey: string) => { async (data: string, pubkey: string) => {
if (!current) throw new Error("No account"); if (!current) throw new Error("No account");
return await signingService.requestEncrypt(data, pubkey, current); return await signingService.nip04Encrypt(data, pubkey, current);
}, },
[toast, current], [toast, current],
); );

View File

@@ -0,0 +1,126 @@
import Subject from "../classes/subject";
import _throttle from "lodash.throttle";
import createDefer, { Deferred } from "../classes/deferred";
import signingService from "./signing";
import accountService from "./account";
import { logger } from "../helpers/debug";
type EncryptionType = "nip04" | "nip44";
class DecryptionContainer {
/** event id */
id: string;
type: "nip04" | "nip44";
pubkey: string;
cipherText: string;
plaintext = new Subject<string>();
error = new Subject<Error>();
constructor(id: string, type: EncryptionType = "nip04", pubkey: string, cipherText: string) {
this.id = id;
this.pubkey = pubkey;
this.cipherText = cipherText;
this.type = type;
}
}
class DecryptionCache {
containers = new Map<string, DecryptionContainer>();
log = logger.extend("DecryptionCache");
getContainer(id: string) {
return this.containers.get(id);
}
getOrCreateContainer(id: string, type: EncryptionType, pubkey: string, cipherText: string) {
let container = this.containers.get(id);
if (!container) {
container = new DecryptionContainer(id, type, pubkey, cipherText);
this.containers.set(id, container);
}
return container;
}
private async decryptContainer(container: DecryptionContainer) {
const account = accountService.current.value;
if (!account) throw new Error("Missing account");
switch (container.type) {
case "nip04":
return await signingService.nip04Decrypt(container.cipherText, container.pubkey, account);
case "nip44":
return await signingService.nip44Decrypt(container.cipherText, container.pubkey, account);
}
}
promises = new Map<DecryptionContainer, Deferred<string>>();
private decryptQueue: DecryptionContainer[] = [];
private decryptQueueRunning = false;
private async decryptNext() {
const container = this.decryptQueue.pop();
if (!container) {
this.decryptQueueRunning = false;
this.decryptQueue = [];
return;
}
const promise = this.promises.get(container)!;
try {
if (!container.plaintext.value) {
const plaintext = await this.decryptContainer(container);
// set plaintext
container.plaintext.next(plaintext);
promise.resolve(plaintext);
// remove promise
this.promises.delete(container);
}
setTimeout(() => this.decryptNext(), 100);
} catch (e) {
if (e instanceof Error) {
// set error
container.error.next(e);
promise.reject(e);
// clear queue
this.decryptQueueRunning = false;
this.decryptQueue = [];
}
}
}
startDecryptionQueue() {
if (!this.decryptQueueRunning) {
this.decryptQueueRunning = true;
this.decryptNext();
}
}
requestDecrypt(container: DecryptionContainer) {
if (container.plaintext.value) return Promise.resolve(container.plaintext.value);
let p = this.promises.get(container);
if (!p) {
p = createDefer<string>();
this.promises.set(container, p);
this.decryptQueue.unshift(container);
this.startDecryptionQueue();
}
return p;
}
}
const decryptionCacheService = new DecryptionCache();
if (import.meta.env.DEV) {
// @ts-expect-error
window.decryptionCacheService = decryptionCacheService;
}
export default decryptionCacheService;

View File

@@ -26,7 +26,7 @@ class SigningService {
return signed; return signed;
} }
async requestEncrypt(plaintext: string, pubkey: string, account: Account) { async nip04Encrypt(plaintext: string, pubkey: string, account: Account) {
if (account.readonly) throw new Error("Can not encrypt in readonly mode"); if (account.readonly) throw new Error("Can not encrypt in readonly mode");
await this.unlockAccount(account); await this.unlockAccount(account);
@@ -35,7 +35,7 @@ class SigningService {
return account.signer.nip04.encrypt(pubkey, plaintext); return account.signer.nip04.encrypt(pubkey, plaintext);
} }
async requestDecrypt(ciphertext: string, pubkey: string, account: Account) { async nip04Decrypt(ciphertext: string, pubkey: string, account: Account) {
if (account.readonly) throw new Error("Can not decrypt in readonly mode"); if (account.readonly) throw new Error("Can not decrypt in readonly mode");
await this.unlockAccount(account); await this.unlockAccount(account);
@@ -43,6 +43,24 @@ class SigningService {
if (!account.signer.nip04) throw new Error("Signer does not support NIP-04"); if (!account.signer.nip04) throw new Error("Signer does not support NIP-04");
return account.signer.nip04.decrypt(pubkey, ciphertext); return account.signer.nip04.decrypt(pubkey, ciphertext);
} }
async nip44Encrypt(plaintext: string, pubkey: string, account: Account) {
if (account.readonly) throw new Error("Can not encrypt in readonly mode");
await this.unlockAccount(account);
if (!account.signer) throw new Error("Account missing signer");
if (!account.signer.nip44) throw new Error("Signer does not support NIP-44");
return account.signer.nip44.encrypt(pubkey, plaintext);
}
async nip44Decrypt(ciphertext: string, pubkey: string, account: Account) {
if (account.readonly) throw new Error("Can not decrypt in readonly mode");
await this.unlockAccount(account);
if (!account.signer) throw new Error("Account missing signer");
if (!account.signer.nip44) throw new Error("Signer does not support NIP-44");
return account.signer.nip44.decrypt(pubkey, ciphertext);
}
} }
const signingService = new SigningService(); const signingService = new SigningService();

View File

@@ -3,7 +3,7 @@ import { Button, ButtonGroup, Card, Flex, IconButton } from "@chakra-ui/react";
import { UNSAFE_DataRouterContext, useLocation, useNavigate } from "react-router-dom"; import { UNSAFE_DataRouterContext, useLocation, useNavigate } from "react-router-dom";
import { NostrEvent, kinds } from "nostr-tools"; import { NostrEvent, kinds } from "nostr-tools";
import { ChevronLeftIcon, ThreadIcon } from "../../components/icons"; import { ThreadIcon } from "../../components/icons";
import UserAvatar from "../../components/user/user-avatar"; import UserAvatar from "../../components/user/user-avatar";
import UserLink from "../../components/user/user-link"; import UserLink from "../../components/user/user-link";
import useSubject from "../../hooks/use-subject"; import useSubject from "../../hooks/use-subject";
@@ -14,7 +14,6 @@ import IntersectionObserverProvider from "../../providers/local/intersection-obs
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback"; import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
import TimelineActionAndStatus from "../../components/timeline/timeline-action-and-status"; import TimelineActionAndStatus from "../../components/timeline/timeline-action-and-status";
import UserDnsIdentity from "../../components/user/user-dns-identity"; import UserDnsIdentity from "../../components/user/user-dns-identity";
import { useDecryptionContext } from "../../providers/global/decryption-provider";
import SendMessageForm from "./components/send-message-form"; import SendMessageForm from "./components/send-message-form";
import { groupMessages } from "../../helpers/nostr/dms"; import { groupMessages } from "../../helpers/nostr/dms";
import ThreadDrawer from "./components/thread-drawer"; import ThreadDrawer from "./components/thread-drawer";
@@ -27,7 +26,8 @@ import RelaySet from "../../classes/relay-set";
import useAppSettings from "../../hooks/use-app-settings"; import useAppSettings from "../../hooks/use-app-settings";
import { truncateId } from "../../helpers/string"; import { truncateId } from "../../helpers/string";
import useRouterMarker from "../../hooks/use-router-marker"; import useRouterMarker from "../../hooks/use-router-marker";
import BackButton, { BackIconButton } from "../../components/router/back-button"; import { BackIconButton } from "../../components/router/back-button";
import decryptionCacheService from "../../services/decryption-cache";
/** This is broken out from DirectMessageChatPage for performance reasons. Don't use outside of file */ /** This is broken out from DirectMessageChatPage for performance reasons. Don't use outside of file */
const ChatLog = memo(({ timeline }: { timeline: TimelineLoader }) => { const ChatLog = memo(({ timeline }: { timeline: TimelineLoader }) => {
@@ -52,7 +52,6 @@ function DirectMessageChatPage({ pubkey }: { pubkey: string }) {
const { autoDecryptDMs } = useAppSettings(); const { autoDecryptDMs } = useAppSettings();
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const { getOrCreateContainer, addToQueue, startQueue } = useDecryptionContext();
const { router } = useContext(UNSAFE_DataRouterContext)!; const { router } = useContext(UNSAFE_DataRouterContext)!;
const marker = useRouterMarker(router); const marker = useRouterMarker(router);
@@ -104,13 +103,11 @@ function DirectMessageChatPage({ pubkey }: { pubkey: string }) {
const decryptAll = async () => { const decryptAll = async () => {
const promises = timeline.timeline.value const promises = timeline.timeline.value
.map((message) => { .map((message) => {
const container = getOrCreateContainer(pubkey, message.content); const container = decryptionCacheService.getOrCreateContainer(message.id, "nip04", pubkey, message.content);
if (container.plaintext.value === undefined) return addToQueue(container); return decryptionCacheService.requestDecrypt(container);
}) })
.filter(Boolean); .filter(Boolean);
startQueue();
setLoading(true); setLoading(true);
Promise.all(promises).finally(() => setLoading(false)); Promise.all(promises).finally(() => setLoading(false));
}; };

View File

@@ -3,11 +3,9 @@ import { Alert, AlertDescription, AlertIcon, Button, ButtonProps } from "@chakra
import { NostrEvent } from "nostr-tools"; import { NostrEvent } from "nostr-tools";
import { UnlockIcon } from "../../../components/icons"; import { UnlockIcon } from "../../../components/icons";
import { useDecryptionContainer } from "../../../providers/global/decryption-provider";
import useCurrentAccount from "../../../hooks/use-current-account";
import { getDMRecipient, getDMSender } from "../../../helpers/nostr/dms";
import DebugEventButton from "../../../components/debug-modal/debug-event-button"; import DebugEventButton from "../../../components/debug-modal/debug-event-button";
import useAppSettings from "../../../hooks/use-app-settings"; import useAppSettings from "../../../hooks/use-app-settings";
import { useKind4Decrypt } from "../../../hooks/use-kind4-decryption";
export default function DecryptPlaceholder({ export default function DecryptPlaceholder({
children, children,
@@ -17,14 +15,9 @@ export default function DecryptPlaceholder({
children: (decrypted: string) => JSX.Element; children: (decrypted: string) => JSX.Element;
message: NostrEvent; message: NostrEvent;
} & Omit<ButtonProps, "children">): JSX.Element { } & Omit<ButtonProps, "children">): JSX.Element {
const account = useCurrentAccount();
const { autoDecryptDMs } = useAppSettings(); const { autoDecryptDMs } = useAppSettings();
const isOwn = account?.pubkey === message.pubkey;
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { requestDecrypt, plaintext, error } = useDecryptionContainer( const { requestDecrypt, plaintext, error } = useKind4Decrypt(message);
isOwn ? getDMRecipient(message) : getDMSender(message),
message.content,
);
const decrypt = async () => { const decrypt = async () => {
setLoading(true); setLoading(true);

View File

@@ -8,10 +8,10 @@ import { useSigningContext } from "../../../providers/global/signing-provider";
import MagicTextArea, { RefType } from "../../../components/magic-textarea"; import MagicTextArea, { RefType } from "../../../components/magic-textarea";
import { useTextAreaUploadFileWithForm } from "../../../hooks/use-textarea-upload-file"; import { useTextAreaUploadFileWithForm } from "../../../hooks/use-textarea-upload-file";
import { DraftNostrEvent } from "../../../types/nostr-event"; import { DraftNostrEvent } from "../../../types/nostr-event";
import { useDecryptionContext } from "../../../providers/global/decryption-provider";
import useUserMailboxes from "../../../hooks/use-user-mailboxes"; import useUserMailboxes from "../../../hooks/use-user-mailboxes";
import { usePublishEvent } from "../../../providers/global/publish-provider"; import { usePublishEvent } from "../../../providers/global/publish-provider";
import useCacheForm from "../../../hooks/use-cache-form"; import useCacheForm from "../../../hooks/use-cache-form";
import decryptionCacheService from "../../../services/decryption-cache";
export default function SendMessageForm({ export default function SendMessageForm({
pubkey, pubkey,
@@ -20,7 +20,6 @@ export default function SendMessageForm({
}: { pubkey: string; rootId?: string } & Omit<FlexProps, "children">) { }: { pubkey: string; rootId?: string } & Omit<FlexProps, "children">) {
const publish = usePublishEvent(); const publish = usePublishEvent();
const { requestEncrypt } = useSigningContext(); const { requestEncrypt } = useSigningContext();
const { getOrCreateContainer } = useDecryptionContext();
const [loadingMessage, setLoadingMessage] = useState(""); const [loadingMessage, setLoadingMessage] = useState("");
const { getValues, setValue, watch, handleSubmit, formState, reset } = useForm({ const { getValues, setValue, watch, handleSubmit, formState, reset } = useForm({
@@ -64,7 +63,9 @@ export default function SendMessageForm({
reset({ content: "" }); reset({ content: "" });
// add plaintext to decryption context // add plaintext to decryption context
getOrCreateContainer(pubkey, encrypted).plaintext.next(values.content); decryptionCacheService
.getOrCreateContainer(pub.event.id, "nip04", pubkey, encrypted)
.plaintext.next(values.content);
// refocus input // refocus input
setTimeout(() => textAreaRef.current?.focus(), 50); setTimeout(() => textAreaRef.current?.focus(), 50);

View File

@@ -27,8 +27,8 @@ import { Thread, useThreadsContext } from "../../../providers/local/thread-provi
import ThreadButton from "../../../components/message/thread-button"; import ThreadButton from "../../../components/message/thread-button";
import SendMessageForm from "./send-message-form"; import SendMessageForm from "./send-message-form";
import { groupMessages } from "../../../helpers/nostr/dms"; import { groupMessages } from "../../../helpers/nostr/dms";
import { useDecryptionContext } from "../../../providers/global/decryption-provider";
import DirectMessageBlock from "./direct-message-block"; import DirectMessageBlock from "./direct-message-block";
import decryptionCacheService from "../../../services/decryption-cache";
function MessagePreview({ message, ...props }: { message: NostrEvent } & Omit<TextProps, "children">) { function MessagePreview({ message, ...props }: { message: NostrEvent } & Omit<TextProps, "children">) {
return ( return (
@@ -102,7 +102,6 @@ export default function ThreadDrawer({
...props ...props
}: Omit<DrawerProps, "children"> & { threadId: string; pubkey: string }) { }: Omit<DrawerProps, "children"> & { threadId: string; pubkey: string }) {
const { threads, getRoot } = useThreadsContext(); const { threads, getRoot } = useThreadsContext();
const { startQueue, getOrCreateContainer, addToQueue } = useDecryptionContext();
const thread = threads[threadId]; const thread = threads[threadId];
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -111,18 +110,21 @@ export default function ThreadDrawer({
const promises = thread.messages const promises = thread.messages
.map((message) => { .map((message) => {
const container = getOrCreateContainer(pubkey, message.content); const container = decryptionCacheService.getOrCreateContainer(message.id, "nip04", pubkey, message.content);
if (container.plaintext.value === undefined) return addToQueue(container); if (container.plaintext.value === undefined) return decryptionCacheService.requestDecrypt(container);
}) })
.filter(Boolean); .filter(Boolean);
if (thread.root) { if (thread.root) {
const rootContainer = getOrCreateContainer(pubkey, thread.root.content); const rootContainer = decryptionCacheService.getOrCreateContainer(
if (rootContainer.plaintext.value === undefined) addToQueue(rootContainer); thread.root.id,
"nip04",
pubkey,
thread.root.content,
);
if (rootContainer.plaintext.value === undefined) decryptionCacheService.requestDecrypt(rootContainer);
} }
startQueue();
setLoading(true); setLoading(true);
Promise.all(promises).finally(() => setLoading(false)); Promise.all(promises).finally(() => setLoading(false));
}; };

View File

@@ -16,16 +16,16 @@ import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-
import TimelineActionAndStatus from "../../components/timeline/timeline-action-and-status"; import TimelineActionAndStatus from "../../components/timeline/timeline-action-and-status";
import { useDMTimeline } from "../../providers/global/dms-provider"; import { useDMTimeline } from "../../providers/global/dms-provider";
import UserName from "../../components/user/user-name"; import UserName from "../../components/user/user-name";
import { useDecryptionContainer } from "../../providers/global/decryption-provider";
import { NostrEvent } from "../../types/nostr-event"; import { NostrEvent } from "../../types/nostr-event";
import { CheckIcon } from "../../components/icons"; import { CheckIcon } from "../../components/icons";
import UserDnsIdentity from "../../components/user/user-dns-identity"; import UserDnsIdentity from "../../components/user/user-dns-identity";
import useEventIntersectionRef from "../../hooks/use-event-intersection-ref"; import useEventIntersectionRef from "../../hooks/use-event-intersection-ref";
import { useKind4Decrypt } from "../../hooks/use-kind4-decryption";
function MessagePreview({ message, pubkey }: { message: NostrEvent; pubkey: string }) { function MessagePreview({ message, pubkey }: { message: NostrEvent; pubkey: string }) {
const ref = useEventIntersectionRef(message); const ref = useEventIntersectionRef(message);
const { plaintext } = useDecryptionContainer(pubkey, message.content); const { plaintext } = useKind4Decrypt(message);
return ( return (
<Text isTruncated ref={ref}> <Text isTruncated ref={ref}>
{plaintext || "<Encrypted>"} {plaintext || "<Encrypted>"}

View File

@@ -19,11 +19,12 @@ import UserAvatar from "../../../components/user/user-avatar";
import HoverLinkOverlay from "../../../components/hover-link-overlay"; import HoverLinkOverlay from "../../../components/hover-link-overlay";
import UserName from "../../../components/user/user-name"; import UserName from "../../../components/user/user-name";
import UserDnsIdentity from "../../../components/user/user-dns-identity"; import UserDnsIdentity from "../../../components/user/user-dns-identity";
import { useDecryptionContainer, useDecryptionContext } from "../../../providers/global/decryption-provider";
import Timestamp from "../../../components/timestamp"; import Timestamp from "../../../components/timestamp";
import { useKind4Decrypt } from "../../../hooks/use-kind4-decryption";
import decryptionCacheService from "../../../services/decryption-cache";
function MessagePreview({ message, pubkey }: { message: NostrEvent; pubkey: string }) { function MessagePreview({ message, pubkey }: { message: NostrEvent; pubkey: string }) {
const { plaintext } = useDecryptionContainer(pubkey, message.content); const { plaintext } = useKind4Decrypt(message);
return <Text isTruncated>{plaintext || "<Encrypted>"}</Text>; return <Text isTruncated>{plaintext || "<Encrypted>"}</Text>;
} }
@@ -50,7 +51,6 @@ function Conversation({ conversation }: { conversation: KnownConversation }) {
export default function DMsCard({ ...props }: Omit<CardProps, "children">) { export default function DMsCard({ ...props }: Omit<CardProps, "children">) {
const navigate = useNavigate(); const navigate = useNavigate();
const account = useCurrentAccount()!; const account = useCurrentAccount()!;
const { getOrCreateContainer, addToQueue, startQueue } = useDecryptionContext();
const timeline = useDMTimeline(); const timeline = useDMTimeline();
@@ -74,13 +74,16 @@ export default function DMsCard({ ...props }: Omit<CardProps, "children">) {
const last = conversation.messages.find((m) => m.pubkey === conversation.correspondent); const last = conversation.messages.find((m) => m.pubkey === conversation.correspondent);
if (!last) return; if (!last) return;
const container = getOrCreateContainer(conversation.correspondent, last.content); const container = decryptionCacheService.getOrCreateContainer(
if (container.plaintext.value === undefined) return addToQueue(container); last.id,
"nip04",
conversation.correspondent,
last.content,
);
return decryptionCacheService.requestDecrypt(container);
}) })
.filter(Boolean); .filter(Boolean);
startQueue();
setLoading(true); setLoading(true);
Promise.all(promises).finally(() => setLoading(false)); Promise.all(promises).finally(() => setLoading(false));
}; };