From 012466383dfdbfb1c01b7c15f63158a7d0348664 Mon Sep 17 00:00:00 2001 From: hzrd149 Date: Tue, 7 Feb 2023 17:04:18 -0600 Subject: [PATCH] show usernames on posts cache user metadata --- src/components/post.tsx | 15 +++++++--- src/helpers/date.ts | 3 ++ src/hooks/use-user-metadata.ts | 11 +++++++ src/services/db/index.ts | 4 --- src/services/db/schema.ts | 3 +- src/services/user-metadata.ts | 55 ++++++++++++++++++++++++++-------- src/types/nostr-event.ts | 4 +++ src/views/global/index.tsx | 2 ++ src/views/home.tsx | 3 ++ src/views/user/index.tsx | 13 ++------ src/views/user/posts.tsx | 2 ++ 11 files changed, 84 insertions(+), 31 deletions(-) create mode 100644 src/helpers/date.ts create mode 100644 src/hooks/use-user-metadata.ts diff --git a/src/components/post.tsx b/src/components/post.tsx index 06512189c..2f32b9817 100644 --- a/src/components/post.tsx +++ b/src/components/post.tsx @@ -18,12 +18,14 @@ import { Link } from "react-router-dom"; import moment from "moment"; import { PostModal } from "./post-modal"; import { NostrEvent } from "../types/nostr-event"; +import { useUserMetadata } from "../hooks/use-user-metadata"; export type PostProps = { event: NostrEvent; }; -export const Post = ({ event }: PostProps) => { +export const Post = React.memo(({ event }: PostProps) => { const { isOpen, onClose, onOpen } = useDisclosure(); + const userMetadata = useUserMetadata(event.pubkey); const isLong = event.content.length > 800; @@ -32,11 +34,16 @@ export const Post = ({ event }: PostProps) => { - + - {event.pubkey} + + {userMetadata?.name ?? event.pubkey} + {moment(event.created_at * 1000).fromNow()} @@ -62,4 +69,4 @@ export const Post = ({ event }: PostProps) => { ); -}; +}); diff --git a/src/helpers/date.ts b/src/helpers/date.ts new file mode 100644 index 000000000..8aecadf6f --- /dev/null +++ b/src/helpers/date.ts @@ -0,0 +1,3 @@ +export function convertTimestampToDate(timestamp: number) { + return new Date(timestamp * 1000); +} diff --git a/src/hooks/use-user-metadata.ts b/src/hooks/use-user-metadata.ts new file mode 100644 index 000000000..f4c9c04ba --- /dev/null +++ b/src/hooks/use-user-metadata.ts @@ -0,0 +1,11 @@ +import { useMemo } from "react"; +import { useObservable } from "react-use"; +import userMetadata from "../services/user-metadata"; + +export function useUserMetadata(pubkey: string) { + const observable = useMemo( + () => userMetadata.requestUserMetadata(pubkey), + [pubkey] + ); + return useObservable(observable); +} diff --git a/src/services/db/index.ts b/src/services/db/index.ts index e3032994a..68aebfe90 100644 --- a/src/services/db/index.ts +++ b/src/services/db/index.ts @@ -23,10 +23,6 @@ const MIGRATIONS: MigrationFunction[] = [ const eventsSeen = db.createObjectStore("events-seen", { keyPath: "id" }); eventsSeen.createIndex("lastSeen", "lastSeen"); - // db.createObjectStore("contacts", { - // keyPath: "pubkey", - // }); - // setup data const settings = db.createObjectStore("settings"); settings.put(["wss://nostr.rdfriedl.com"], "relays"); diff --git a/src/services/db/schema.ts b/src/services/db/schema.ts index 8fc9fcba2..eb7fac51d 100644 --- a/src/services/db/schema.ts +++ b/src/services/db/schema.ts @@ -1,9 +1,10 @@ import { DBSchema } from "idb"; +import { NostrEvent } from "../../types/nostr-event"; export interface CustomSchema extends DBSchema { "user-metadata": { key: string; - value: any; + value: NostrEvent; }; "events-seen": { key: string; diff --git a/src/services/user-metadata.ts b/src/services/user-metadata.ts index cba33730c..9938ef979 100644 --- a/src/services/user-metadata.ts +++ b/src/services/user-metadata.ts @@ -1,10 +1,12 @@ import { BehaviorSubject } from "rxjs"; -import { NostrEvent } from "../types/nostr-event"; +import { Kind0ParsedContent } from "../types/nostr-event"; +import db from "./db"; import settingsService from "./settings"; import { Subscription } from "./subscriptions"; class UserMetadata { - requests = new Map>(); + requests = new Set(); + subjects = new Map>(); subscription: Subscription; constructor(relayUrls: string[] = []) { @@ -13,7 +15,8 @@ class UserMetadata { this.subscription.onEvent.subscribe((event) => { try { const metadata = JSON.parse(event.content); - this.requests.get(event.pubkey)?.next(metadata); + this.getUserSubject(event.pubkey).next(metadata); + db.put("user-metadata", event); } catch (e) {} }); @@ -22,12 +25,40 @@ class UserMetadata { }, 1000 * 10); } - requestUserMetadata(pubkey: string) { - if (!this.requests.has(pubkey)) { - this.requests.set(pubkey, new BehaviorSubject(null)); - this.updateSubscription(); + private getUserSubject(pubkey: string) { + if (!this.subjects.has(pubkey)) { + this.subjects.set( + pubkey, + new BehaviorSubject(null) + ); } - return this.requests.get(pubkey); + return this.subjects.get( + pubkey + ) as BehaviorSubject; + } + + requestUserMetadata(pubkey: string, useCache = true) { + const subject = this.getUserSubject(pubkey); + + const request = () => { + if (!this.requests.has(pubkey)) { + this.requests.add(pubkey); + this.updateSubscription(); + } + }; + if (useCache && !subject.getValue()) { + db.get("user-metadata", pubkey).then((cachedEvent) => { + if (cachedEvent) { + try { + subject.next(JSON.parse(cachedEvent.content)); + } catch (e) { + request(); + } + } else request(); + }); + } else request(); + + return subject; } updateSubscription() { @@ -45,10 +76,10 @@ class UserMetadata { pruneRequests() { let removed = false; - const requests = Array.from(this.requests.entries()); - for (const [pubkey, subject] of requests) { - if (!subject.observed) { - subject.complete(); + const subjects = Array.from(this.subjects.entries()); + for (const [pubkey, subject] of subjects) { + // if there is a request for the pubkey and no one is observing it. close the request + if (this.requests.has(pubkey) && !subject.observed) { this.requests.delete(pubkey); removed = true; } diff --git a/src/types/nostr-event.ts b/src/types/nostr-event.ts index 2f9f225c7..836eec21c 100644 --- a/src/types/nostr-event.ts +++ b/src/types/nostr-event.ts @@ -11,3 +11,7 @@ export type NostrEvent = { export type IncomingNostrEvent = | ["EVENT", string, NostrEvent] | ["NOTICE", string]; + +export type Kind0ParsedContent = { + name: string; +}; diff --git a/src/views/global/index.tsx b/src/views/global/index.tsx index daacd9ecf..ca5281e5b 100644 --- a/src/views/global/index.tsx +++ b/src/views/global/index.tsx @@ -37,6 +37,8 @@ export const GlobalView = () => { return ; } + if (timeline.length > 20) timeline.length = 20; + return ( <> {timeline.map((event) => ( diff --git a/src/views/home.tsx b/src/views/home.tsx index f370d2510..2b98f4ee8 100644 --- a/src/views/home.tsx +++ b/src/views/home.tsx @@ -11,6 +11,9 @@ export const HomeView = () => { consistent look and feel, not just in our design specs, but in production. + + jb55 + self diff --git a/src/views/user/index.tsx b/src/views/user/index.tsx index e50c37c64..e92bcb743 100644 --- a/src/views/user/index.tsx +++ b/src/views/user/index.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from "react"; +import React from "react"; import { Heading, Tab, @@ -9,8 +9,7 @@ import { } from "@chakra-ui/react"; import { useParams } from "react-router-dom"; import { UserPostsTab } from "./posts"; -import { useObservable } from "react-use"; -import userMetadata from "../../services/user-metadata"; +import { useUserMetadata } from "../../hooks/use-user-metadata"; export const UserView = () => { const { pubkey } = useParams(); @@ -19,16 +18,10 @@ export const UserView = () => { throw new Error("No pubkey"); } - const observable = useMemo( - () => userMetadata.requestUserMetadata(pubkey), - [pubkey] - ); - // @ts-ignore - const metadata = useObservable(observable); + const metadata = useUserMetadata(pubkey); return ( <> - {/* @ts-ignore */} {metadata?.name ?? pubkey} diff --git a/src/views/user/posts.tsx b/src/views/user/posts.tsx index 2ca41c33c..40af684f3 100644 --- a/src/views/user/posts.tsx +++ b/src/views/user/posts.tsx @@ -37,6 +37,8 @@ export const UserPostsTab = ({ pubkey }: { pubkey: string }) => { return ; } + if (timeline.length > 20) timeline.length = 20; + return ( <> {timeline.map((event) => (