show usernames on posts

cache user metadata
This commit is contained in:
hzrd149 2023-02-07 17:04:18 -06:00
parent 40352f77c7
commit 012466383d
11 changed files with 84 additions and 31 deletions

View File

@ -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) => {
<CardHeader>
<HStack spacing="4">
<Flex flex="1" gap="4" alignItems="center" flexWrap="wrap">
<Avatar name="Segun Adebayo" src="https://bit.ly/sage-adebayo" />
<Avatar
name={userMetadata?.name}
src="https://bit.ly/sage-adebayo"
/>
<Box>
<Heading size="sm">
<Link to={`/user/${event.pubkey}`}>{event.pubkey}</Link>
<Link to={`/user/${event.pubkey}`}>
{userMetadata?.name ?? event.pubkey}
</Link>
</Heading>
<Text>{moment(event.created_at * 1000).fromNow()}</Text>
</Box>
@ -62,4 +69,4 @@ export const Post = ({ event }: PostProps) => {
</CardBody>
</Card>
);
};
});

3
src/helpers/date.ts Normal file
View File

@ -0,0 +1,3 @@
export function convertTimestampToDate(timestamp: number) {
return new Date(timestamp * 1000);
}

View File

@ -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);
}

View File

@ -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");

View File

@ -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;

View File

@ -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<string, BehaviorSubject<NostrEvent | null>>();
requests = new Set<string>();
subjects = new Map<string, BehaviorSubject<Kind0ParsedContent | null>>();
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<NostrEvent | null>(null));
this.updateSubscription();
private getUserSubject(pubkey: string) {
if (!this.subjects.has(pubkey)) {
this.subjects.set(
pubkey,
new BehaviorSubject<Kind0ParsedContent | null>(null)
);
}
return this.requests.get(pubkey);
return this.subjects.get(
pubkey
) as BehaviorSubject<Kind0ParsedContent | null>;
}
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;
}

View File

@ -11,3 +11,7 @@ export type NostrEvent = {
export type IncomingNostrEvent =
| ["EVENT", string, NostrEvent]
| ["NOTICE", string];
export type Kind0ParsedContent = {
name: string;
};

View File

@ -37,6 +37,8 @@ export const GlobalView = () => {
return <SkeletonText />;
}
if (timeline.length > 20) timeline.length = 20;
return (
<>
{timeline.map((event) => (

View File

@ -11,6 +11,9 @@ export const HomeView = () => {
consistent look and feel, not just in our design specs, but in
production.
</span>
<Link to="/user/32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245">
jb55
</Link>
<Link to="/user/266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5">
self
</Link>

View File

@ -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 */}
<Heading>{metadata?.name ?? pubkey}</Heading>
<Tabs>
<TabList>

View File

@ -37,6 +37,8 @@ export const UserPostsTab = ({ pubkey }: { pubkey: string }) => {
return <SkeletonText />;
}
if (timeline.length > 20) timeline.length = 20;
return (
<>
{timeline.map((event) => (