mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-09-20 13:01:07 +02:00
convert decryption cache to service
This commit is contained in:
@@ -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 = [
|
||||||
|
29
src/hooks/use-kind4-decryption.ts
Normal file
29
src/hooks/use-kind4-decryption.ts
Normal 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 };
|
||||||
|
}
|
@@ -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>;
|
|
||||||
}
|
|
@@ -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>
|
||||||
|
@@ -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],
|
||||||
);
|
);
|
||||||
|
126
src/services/decryption-cache.ts
Normal file
126
src/services/decryption-cache.ts
Normal 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;
|
@@ -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();
|
||||||
|
@@ -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));
|
||||||
};
|
};
|
||||||
|
@@ -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);
|
||||||
|
@@ -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);
|
||||||
|
@@ -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));
|
||||||
};
|
};
|
||||||
|
@@ -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>"}
|
||||||
|
@@ -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));
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user