Skip to content

Commit

Permalink
Nostr improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
DustinBrett committed Oct 15, 2023
1 parent 63b349c commit 74ade7b
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 55 deletions.
13 changes: 2 additions & 11 deletions components/apps/Messenger/ChatLog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import {
import StyledChatLog from "components/apps/Messenger/StyledChatLog";
import { UNKNOWN_PUBLIC_KEY } from "components/apps/Messenger/constants";
import {
convertImageLinksToHtml,
convertNewLinesToBreaks,
decryptMessage,
prettyChatTimestamp,
} from "components/apps/Messenger/functions";
Expand Down Expand Up @@ -89,15 +87,8 @@ const ChatLog: FC<{ recipientPublicKey: string }> = ({
</div>
)}
<SanitizedContent
content={
typeof decryptedContent[id] === "string"
? convertImageLinksToHtml(
convertNewLinesToBreaks(
decryptedContent[id] as string
)
)
: content
}
content={decryptedContent[id] || content}
decrypted={typeof decryptedContent[id] === "string"}
/>
{publicKey === pubkey &&
gropupIndex === messages.length - 1 &&
Expand Down
36 changes: 35 additions & 1 deletion components/apps/Messenger/HistoryContext.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import type { NostrProfile } from "components/apps/Messenger/types";
import { useFileSystem } from "contexts/fileSystem";
import type { Event } from "nostr-tools";
import { createContext, memo, useContext, useMemo, useState } from "react";
import {
createContext,
memo,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { SEEN_EVENT_IDS_PATH } from "./constants";

type Profiles = Record<string, NostrProfile>;

Expand All @@ -18,9 +28,33 @@ const HistoryContext = createContext({} as History);
export const useHistoryContext = (): History => useContext(HistoryContext);

export const HistoryProvider = memo<FC>(({ children }) => {
const { readFile, writeFile } = useFileSystem();
const [seenEventIds, setSeenEventIds] = useState<string[]>([]);
const [outgoingEvents, setOutgoingEvents] = useState<Event[]>([]);
const [profiles, setProfiles] = useState<Profiles>({});
const initialized = useRef(false);

useEffect(() => {
if (!readFile || initialized.current) return;

initialized.current = true;

readFile(SEEN_EVENT_IDS_PATH).then((eventIds) => {
if (eventIds) {
try {
setSeenEventIds(JSON.parse(eventIds.toString()) as string[]);
} catch {
// Ignore failure to read seen events
}
}
});
}, [readFile]);

useEffect(() => {
if (!writeFile || !initialized.current) return;

writeFile(SEEN_EVENT_IDS_PATH, JSON.stringify(seenEventIds), true);
}, [seenEventIds, writeFile]);

return (
<HistoryContext.Provider
Expand Down
8 changes: 6 additions & 2 deletions components/apps/Messenger/NostrContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ import {
} from "react";

interface NostrContextType {
connectToRelays: (urls: string[]) => void;
connectedRelays: Relay[];
publish: (event: NostrEvent) => void;
}

/* eslint-disable @typescript-eslint/no-empty-function */
const NostrContext = createContext<NostrContextType>({
connectToRelays: () => {},
connectedRelays: [],
// eslint-disable-next-line @typescript-eslint/no-empty-function
publish: () => {},
});
/* eslint-enable @typescript-eslint/no-empty-function */

export const useNostr = (): NostrContextType => useContext(NostrContext);

Expand Down Expand Up @@ -93,10 +96,11 @@ export const NostrProvider: FC<{ relayUrls: string[] }> = ({
<NostrContext.Provider
value={useMemo(
() => ({
connectToRelays,
connectedRelays: Object.values(connectedRelays),
publish,
}),
[connectedRelays, publish]
[connectToRelays, connectedRelays, publish]
)}
>
{children}
Expand Down
8 changes: 4 additions & 4 deletions components/apps/Messenger/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useState } from "react";

type ProfileProps = {
nip05?: string;
onClick?: () => void;
onMouseDown?: () => void;
picture?: string;
pubkey?: string;
userName?: string;
Expand All @@ -14,7 +14,7 @@ type ProfileProps = {
const Profile: FC<ProfileProps> = ({
children,
nip05,
onClick,
onMouseDown,
picture,
pubkey,
userName = "Unknown",
Expand All @@ -23,9 +23,9 @@ const Profile: FC<ProfileProps> = ({
const [loadedImage, setLoadedImage] = useState("");

return (
<StyledProfile $clickable={Boolean(onClick)}>
<StyledProfile $clickable={Boolean(onMouseDown)}>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
<div onClick={onClick}>
<div onMouseDown={onMouseDown}>
{picture && (
<img
alt={userName}
Expand Down
15 changes: 12 additions & 3 deletions components/apps/Messenger/ProfileBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const ProfileBanner: FC<ProfileBannerProps> = ({
picture,
userName = "New message",
} = useNostrProfile(pubkey);
const { connectedRelays } = useNostr();
const { connectToRelays, connectedRelays } = useNostr();
const connectedRelayData = useMemo(
() =>
Object.fromEntries(
Expand Down Expand Up @@ -124,7 +124,16 @@ const ProfileBanner: FC<ProfileBannerProps> = ({
<div className="relays">
<ol>
{relayUrls.sort().map((relayUrl) => (
<li key={relayUrl} title={relayUrl}>
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
<li
key={relayUrl}
onClick={
connectedRelayData[relayUrl]
? undefined
: () => connectToRelays([relayUrl])
}
title={relayUrl}
>
{getWebSocketStatusIcon(connectedRelayData[relayUrl])}
</li>
))}
Expand All @@ -133,7 +142,7 @@ const ProfileBanner: FC<ProfileBannerProps> = ({
)}
<Profile
nip05={nip05}
onClick={onContextMenuCapture}
onMouseDown={onContextMenuCapture}
picture={picture}
pubkey={pubkey}
userName={userName}
Expand Down
40 changes: 29 additions & 11 deletions components/apps/Messenger/SanitizedContent.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,33 @@
import {
convertImageLinksToHtml,
convertNewLinesToBreaks,
} from "components/apps/Messenger/functions";
import { sanitize } from "dompurify";
import { useMemo } from "react";

const SanitizedContent: FC<{ content: string }> = ({ content }) => (
<div
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: sanitize(content, {
ALLOWED_ATTR: ["src"],
ALLOWED_TAGS: ["br", "img"],
}),
}}
/>
);
const SanitizedContent: FC<{ content: string; decrypted: boolean }> = ({
content,
decrypted,
}) => {
const decryptedContent = useMemo(
() =>
decrypted
? convertImageLinksToHtml(convertNewLinesToBreaks(content))
: "",
[content, decrypted]
);

return (
<div
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: sanitize(decryptedContent || content, {
ALLOWED_ATTR: ["src"],
ALLOWED_TAGS: ["br", "img"],
}),
}}
/>
);
};

export default SanitizedContent;
3 changes: 3 additions & 0 deletions components/apps/Messenger/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { MotionProps } from "framer-motion";
import {
HOME,
MILLISECONDS_IN_MINUTE,
MILLISECONDS_IN_SECOND,
} from "utils/constants";
Expand All @@ -25,6 +26,8 @@ export const UNKNOWN_PUBLIC_KEY = "?";

export const BASE_NIP05_URL = "/.well-known/nostr.json";

export const SEEN_EVENT_IDS_PATH = `${HOME}/seenEvents.json`;

export const GROUP_TIME_GAP_IN_SECONDS =
(MILLISECONDS_IN_MINUTE / MILLISECONDS_IN_SECOND) * 30;

Expand Down
10 changes: 3 additions & 7 deletions components/apps/Messenger/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,7 @@ export const useNip05 = (): NIP05Result => {

export const useNostrContacts = (
publicKey: string,
wellKnownNames: Record<string, string>,
loginTime: number
wellKnownNames: Record<string, string>
): NostrContacts => {
const globalContacts = useMemo(
() =>
Expand Down Expand Up @@ -167,12 +166,9 @@ export const useNostrContacts = (
const unreadEvents = useMemo(
() =>
events.filter(
({ created_at, id, pubkey }) =>
pubkey !== publicKey &&
created_at > loginTime &&
!seenEventIds.includes(id)
({ id, pubkey }) => pubkey !== publicKey && !seenEventIds.includes(id)
),
[events, loginTime, publicKey, seenEventIds]
[events, publicKey, seenEventIds]
);

return { contactKeys, events, lastEvents, unreadEvents };
Expand Down
25 changes: 9 additions & 16 deletions components/apps/Messenger/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import { MILLISECONDS_IN_DAY } from "utils/constants";
import { haltEvent } from "utils/functions";

type NostrChatProps = {
loginTime: number;
processId: string;
publicKey: string;
relayUrls: string[];
Expand All @@ -48,7 +47,6 @@ type NostrChatProps = {

const NostrChat: FC<NostrChatProps> = ({
processId,
loginTime,
publicKey,
relayUrls,
setSince,
Expand All @@ -63,10 +61,8 @@ const NostrChat: FC<NostrChatProps> = ({
setSeenEventIds((currentSeenEventIds) => [
...new Set([
...currentEvents
.filter(
({ created_at, pubkey }) =>
[recipientKey, currenRecipientKey].includes(pubkey) &&
created_at > loginTime
.filter(({ pubkey }) =>
[recipientKey, currenRecipientKey].includes(pubkey)
)
.map(({ id }) => id),
...currentSeenEventIds,
Expand All @@ -76,12 +72,11 @@ const NostrChat: FC<NostrChatProps> = ({

return recipientKey;
}),
[loginTime, setSeenEventIds]
[setSeenEventIds]
);
const { contactKeys, events, lastEvents, unreadEvents } = useNostrContacts(
publicKey,
wellKnownNames,
loginTime
wellKnownNames
);
const setRecipientKey = useCallback(
(recipientKey: string): boolean => {
Expand All @@ -99,7 +94,10 @@ const NostrChat: FC<NostrChatProps> = ({
} = useProcesses();
const { url } = process || {};

useUnreadStatus(processId, unreadEvents.length);
useUnreadStatus(
processId,
new Set(unreadEvents.map(({ pubkey }) => pubkey)).size
);

useEffect(() => {
if (
Expand Down Expand Up @@ -178,7 +176,6 @@ const NostrChat: FC<NostrChatProps> = ({
};

const Messenger: FC<ComponentProcessProps> = ({ id }) => {
const [loginTime, setLoginTime] = useState(0);
const [since, setSince] = useState(() => MILLISECONDS_IN_DAY);
const timeSince = useMemo(
() => Math.floor((Date.now() - since) / 1000),
Expand All @@ -194,18 +191,14 @@ const Messenger: FC<ComponentProcessProps> = ({ id }) => {

initStarted.current = true;

getRelayUrls().then((foundRelays) => {
setRelayUrls(foundRelays);
setLoginTime(Math.floor(Date.now() / 1000));
});
getRelayUrls().then(setRelayUrls);
}, [publicKey]);

return publicKey && relayUrls ? (
<NostrProvider relayUrls={relayUrls}>
<HistoryProvider>
<MessageProvider publicKey={publicKey} since={timeSince}>
<NostrChat
loginTime={loginTime}
processId={id}
publicKey={publicKey}
relayUrls={relayUrls}
Expand Down

0 comments on commit 74ade7b

Please sign in to comment.