From f5aca7434fe6e1e0680a962927624513212208f0 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Wed, 15 Oct 2025 10:40:25 +0200 Subject: [PATCH 1/3] DM message caching --- package-lock.json | 32 +++++++++++++++++++++++++++++++- package.json | 3 ++- src/app/chat/[id]/page.tsx | 4 +++- src/app/chat/page.tsx | 4 +++- src/components/Sidebar.tsx | 2 +- src/hooks/use-messages.ts | 27 +++++++++++++++++++++++++++ src/lib/auth.ts | 5 +++-- 7 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 src/hooks/use-messages.ts diff --git a/package-lock.json b/package-lock.json index 6211150..e1c1033 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,7 +45,8 @@ "react-markdown": "^10.1.0", "remark-gfm": "^4.0.1", "sonner": "^2.0.7", - "tailwind-merge": "^3.3.1" + "tailwind-merge": "^3.3.1", + "zustand": "^5.0.8" }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -5443,6 +5444,35 @@ "url": "https://github.com/sponsors/dmonad" } }, + "node_modules/zustand": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz", + "integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index 1f0707e..6b383a7 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,8 @@ "react-markdown": "^10.1.0", "remark-gfm": "^4.0.1", "sonner": "^2.0.7", - "tailwind-merge": "^3.3.1" + "tailwind-merge": "^3.3.1", + "zustand": "^5.0.8" }, "devDependencies": { "@tailwindcss/postcss": "^4", diff --git a/src/app/chat/[id]/page.tsx b/src/app/chat/[id]/page.tsx index eb3d23d..a4715d4 100644 --- a/src/app/chat/[id]/page.tsx +++ b/src/app/chat/[id]/page.tsx @@ -7,6 +7,7 @@ import { Message } from "@/types/types"; import { UserProfile } from "@/hooks/get-user"; import { StringMap } from "@/types/typeUtils"; import { useParams } from "next/navigation"; +import { useMessages } from "@/hooks/use-messages"; // In this testing instance we are assuming that the target's node is localhost // To make this functional we need to add a settings feature to the api @@ -14,7 +15,8 @@ import { useParams } from "next/navigation"; export default function DMs() { const { id } = useParams<{ id: string }>(); const wsRef = useRef(null); - const [messages, setMessages] = useState([]); + const messages = useMessages((s) => s.messages); + const setMessages = useMessages((s) => s.setMessages); const [userList, setUserList] = useState>({}); const [user, setUser] = useState(); diff --git a/src/app/chat/page.tsx b/src/app/chat/page.tsx index 5369886..9ebd33d 100644 --- a/src/app/chat/page.tsx +++ b/src/app/chat/page.tsx @@ -7,11 +7,13 @@ import { StringMap } from "@/types/typeUtils"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import ProfilePicture from "@/components/ProfilePicture"; import { Message } from "@/types/types"; +import { useMessages } from "@/hooks/use-messages"; export default function Chat() { const wsRef = useRef(null); const [userList, setUserList] = useState>({}); - const [messages, setMessages] = useState([]); + const messages = useMessages((s) => s.messages); + const setMessages = useMessages((s) => s.setMessages); return ( ; messages?: Message[]; - setMessages: React.Dispatch>; + setMessages: (m: Message[]) => void; onNewMessage?: (msg: Message) => void; userList: StringMap; setUserList: React.Dispatch>>; diff --git a/src/hooks/use-messages.ts b/src/hooks/use-messages.ts new file mode 100644 index 0000000..6d5ab5e --- /dev/null +++ b/src/hooks/use-messages.ts @@ -0,0 +1,27 @@ +import { Message } from "@/types/types"; +import { create } from "zustand"; +import { persist } from "zustand/middleware"; + +interface MessageStore { + messages: Message[]; + setMessages: (msgs: Message[]) => void; + addMessage: (msg: Message) => void; + clearMessages: () => void; +} + +export const useMessages = create()( + persist( + (set) => ({ + messages: [], + setMessages: (msgs) => set({ messages: msgs }), + addMessage: (msg) => + set((state) => + state.messages.some((m) => m.id === msg.id) + ? state + : { messages: [...state.messages, msg] } + ), + clearMessages: () => set({ messages: [] }), + }), + { name: "chat-messages" } + ) +); diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 629e5d3..083d5fd 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -6,7 +6,8 @@ export default async function auth( id: string, wsRef: RefObject, setServer: Dispatch>, - setMessages: Dispatch>, + setMessages: (m: Message[]) => void, + addMessage: (m: Message) => void, onNewMessage?: (m: Message) => void ) { console.log("Authenticating with server id:", id); @@ -50,7 +51,7 @@ export default async function auth( case "message_create": if (onNewMessage) onNewMessage(data.params as Message); - setMessages((prev) => [...prev, data.params]); + addMessage(data.params as Message); break; } }; From a183974d20cfd18fbc28febf71ce1fcee062fa76 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Wed, 15 Oct 2025 11:13:58 +0200 Subject: [PATCH 2/3] User profile request caching --- package-lock.json | 47 ++++++++++++++++++++++++++++++++++---- package.json | 3 ++- src/components/Sidebar.tsx | 19 +++++++-------- src/hooks/get-user.ts | 6 ++--- src/lib/auth.ts | 4 ++-- src/lib/request.ts | 32 ++++++++++++++++++++++++++ 6 files changed, 91 insertions(+), 20 deletions(-) create mode 100644 src/lib/request.ts diff --git a/package-lock.json b/package-lock.json index e1c1033..b8236ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "@types/http-status-codes": "^1.2.0", "@types/js-cookie": "^3.0.6", "axios": "^1.12.2", + "axios-cache-interceptor": "^1.8.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", @@ -50,7 +51,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4", - "@types/node": "^20.19.11", + "@types/node": "^20.19.21", "@types/pg": "^8.15.5", "@types/react": "^19", "@types/react-dom": "^19", @@ -2322,9 +2323,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.11.tgz", - "integrity": "sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==", + "version": "20.19.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.21.tgz", + "integrity": "sha512-CsGG2P3I5y48RPMfprQGfy4JPRZ6csfC3ltBZSRItG3ngggmNY/qs2uZKp4p9VbrpqNNSMzUZNFZKzgOGnd/VA==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -2417,6 +2418,26 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/axios-cache-interceptor": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/axios-cache-interceptor/-/axios-cache-interceptor-1.8.3.tgz", + "integrity": "sha512-ifuSBoCEkVaiugg1UTjVuTdK+SjSOJ35pdv2OrzhRT3wDMr52QiayQxUqs7jd7GDsfPOjMcw3T3ek0TysbyZZw==", + "license": "MIT", + "dependencies": { + "cache-parser": "1.2.5", + "fast-defer": "1.1.8", + "object-code": "1.3.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/arthurfiorette/axios-cache-interceptor?sponsor=1" + }, + "peerDependencies": { + "axios": "^1" + } + }, "node_modules/bail": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", @@ -2427,6 +2448,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/cache-parser": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/cache-parser/-/cache-parser-1.2.5.tgz", + "integrity": "sha512-Md/4VhAHByQ9frQ15WD6LrMNiVw9AEl/J7vWIXw+sxT6fSOpbtt6LHTp76vy8+bOESPBO94117Hm2bIjlI7XjA==", + "license": "MIT" + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -2839,6 +2866,12 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, + "node_modules/fast-defer": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/fast-defer/-/fast-defer-1.1.8.tgz", + "integrity": "sha512-lEJeOH5VL5R09j6AA0D4Uvq7AgsHw0dAImQQ+F3iSyHZuAxyQfWobsagGpTcOPvJr3urmKRHrs+Gs9hV+/Qm/Q==", + "license": "MIT" + }, "node_modules/flairup": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/flairup/-/flairup-1.0.0.tgz", @@ -4513,6 +4546,12 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/object-code": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/object-code/-/object-code-1.3.3.tgz", + "integrity": "sha512-/Ds4Xd5xzrtUOJ+xJQ57iAy0BZsZltOHssnDgcZ8DOhgh41q1YJCnTPnWdWSLkNGNnxYzhYChjc5dgC9mEERCA==", + "license": "MIT" + }, "node_modules/parse-entities": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", diff --git a/package.json b/package.json index 6b383a7..9106001 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@types/http-status-codes": "^1.2.0", "@types/js-cookie": "^3.0.6", "axios": "^1.12.2", + "axios-cache-interceptor": "^1.8.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", @@ -53,7 +54,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4", - "@types/node": "^20.19.11", + "@types/node": "^20.19.21", "@types/pg": "^8.15.5", "@types/react": "^19", "@types/react-dom": "^19", diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index d2ee6d3..a88c951 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -34,6 +34,7 @@ import { StringMap } from "@/types/typeUtils"; import SettingsDialog from "./settings/SettingsDialog"; import Link from "next/link"; import axios from "axios"; +import { get, Response } from "@/lib/request"; export default function AppSidebar({ children, @@ -327,15 +328,15 @@ function getUser( ) { if (userList[id]) return userList[id]; - axios - .get(`/api/profile/?id=${id}`) - ?.then((res) => { - console.log("Fetched user", res.data); - setUserList((prev) => { - prev[id] = res.data as UserProfile; - return prev; - }); - }) + const onResponse = (res: Response) => { + setUserList((prev) => { + prev[id] = res.data as UserProfile; + return prev; + }); + }; + + get(`/api/profile/?id=${id}`, onResponse) + ?.then(onResponse) .catch((e) => { console.error(e); }); diff --git a/src/hooks/get-user.ts b/src/hooks/get-user.ts index 1c693f9..901c6da 100644 --- a/src/hooks/get-user.ts +++ b/src/hooks/get-user.ts @@ -1,10 +1,10 @@ "use client"; import { ProfileSettings } from "@/types/settings"; -import axios from "axios"; import Cookies from "js-cookie"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; +import { get } from "@/lib/request"; export interface UserProfile extends ProfileSettings { id: string; @@ -23,9 +23,7 @@ export default function useUser() { } try { - const res = (await axios.get("/api/profile")).data as UserProfile; - - setUser(res); + setUser((await get("/api/profile")).data); } catch { router.push("/login"); } diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 083d5fd..9022e5c 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -1,6 +1,7 @@ import { Message, Server } from "@/types/types"; import axios from "axios"; import { Dispatch, RefObject, SetStateAction } from "react"; +import { get } from "./request"; export default async function auth( id: string, @@ -12,8 +13,7 @@ export default async function auth( ) { console.log("Authenticating with server id:", id); - const ip = ((await axios.get(`/api/server/${id}`)).data as any) - .address as string; + const ip = ((await get(`/api/server/${id}`)).data as any).address as string; console.log("Authenticating with server at:", ip); const server_auth = ( diff --git a/src/lib/request.ts b/src/lib/request.ts new file mode 100644 index 0000000..221ec47 --- /dev/null +++ b/src/lib/request.ts @@ -0,0 +1,32 @@ +import Axios from "axios"; +import { CacheRequestConfig, setupCache } from "axios-cache-interceptor"; + +const axiosInstance = Axios.create({}); +const axios = setupCache(axiosInstance); + +export interface Response { + data: T; + status: number; + statusText: string; + headers: { [index: string]: any }; + cached: boolean; +} + +export async function get( + url: string, + onResponse?: (response: Response) => void, + config?: CacheRequestConfig +): Promise> { + const response = (await axios.get( + url, + config + )) as unknown as Response; + + if (onResponse && response.cached) { + Axios.get(url).then((response) => + onResponse(response as unknown as Response) + ); + } + + return response; +} From 5e49bef808f05bb964328b6ca8a2859ff53afcf8 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Wed, 15 Oct 2025 11:19:07 +0200 Subject: [PATCH 3/3] Fixed error --- src/app/server/[ip]/page.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/server/[ip]/page.tsx b/src/app/server/[ip]/page.tsx index 3042c31..1d2dde2 100644 --- a/src/app/server/[ip]/page.tsx +++ b/src/app/server/[ip]/page.tsx @@ -19,7 +19,9 @@ export default function Server() { useEffect(() => { if (!ip) return; - auth(ip, wsServerRef, setServer, setMessages); + auth(ip, wsServerRef, setServer, setMessages, (m) => { + setMessages((prev) => [...prev, m]); + }); }, [ip]); return (