From 1063dc9cac9f16e3d885fe05eda3e079bd882565 Mon Sep 17 00:00:00 2001 From: Trevor Sharp Date: Thu, 9 Jan 2025 15:10:23 +0000 Subject: [PATCH] Refactor UI client side only / Fix memory leak with compression --- bun.lockb | Bin 247006 -> 247030 bytes package.json | 4 +- src/app/[feedId]/feed/route.ts | 8 ++-- src/app/[feedId]/page.tsx | 4 +- src/app/api/mod-config/[feedId]/route.ts | 24 ++++++++++ src/app/api/mod-config/feed-id/route.ts | 28 ++++++++++++ src/app/api/source-feed-data/route.ts | 30 +++++++++++++ src/components/CopyFeedButton.tsx | 54 +++++++++++++++++------ src/components/EpisodeCard.tsx | 2 + src/components/FeedPreview.tsx | 2 + src/components/Form.tsx | 6 +-- src/components/MainPage.tsx | 43 +++++++++++++++--- src/components/SectionHeader.tsx | 2 + src/formSections/mods.tsx | 2 - src/formSections/sources.tsx | 2 - src/services/compressionService.ts | 41 ++++++++++++----- src/services/feedService.ts | 3 +- 17 files changed, 207 insertions(+), 48 deletions(-) create mode 100644 src/app/api/mod-config/[feedId]/route.ts create mode 100644 src/app/api/mod-config/feed-id/route.ts create mode 100644 src/app/api/source-feed-data/route.ts diff --git a/bun.lockb b/bun.lockb index 325991cbadec0bbb2260aae8d203e47555fb9521..3650bd48bec9d114f9d0834c800423d6701432b3 100755 GIT binary patch delta 48725 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 V-np`gz1XY%rXFll_S3}Q{RjWKUkm^M delta 48917 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 LK`|7Zrds)b3Yrb@ diff --git a/package.json b/package.json index 5c8ea9c..1c034fc 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,11 @@ "@hookform/resolvers": "^3.3.4", "@t3-oss/env-nextjs": "^0.9.2", "@tailwindcss/forms": "^0.5.7", - "brotli-compress": "^1.3.3", + "brotli": "^1.3.3", "clsx": "^2.1.0", "fast-xml-parser": "^4.3.6", "next": "^14.1.3", + "prettier-plugin-style-order": "^0.2.2", "react": "18.2.0", "react-dom": "18.2.0", "react-hook-form": "^7.51.3", @@ -28,6 +29,7 @@ "zod": "^3.24.1" }, "devDependencies": { + "@types/brotli": "^1.3.4", "@types/eslint": "^8.56.2", "@types/node": "^20.11.20", "@types/react": "^18.2.57", diff --git a/src/app/[feedId]/feed/route.ts b/src/app/[feedId]/feed/route.ts index 3aa3f0d..bc21569 100644 --- a/src/app/[feedId]/feed/route.ts +++ b/src/app/[feedId]/feed/route.ts @@ -8,15 +8,13 @@ const GET = async (request: Request, { params }: { params: { feedId: string } }) const { feedId } = params; const host = request.headers.get("host") ?? ""; - const searchParams = - host && request.url ? new URL(`http://${host}${request.url}`).searchParams : undefined; - const modConfig = await decompressModConfig(feedId); + const modConfig = decompressModConfig(feedId); if (!modConfig) { - return new NextResponse("Bad Request - Invalid feed parameter in URL", { status: 400 }); + return new NextResponse("Bad Request - Invalid feed id", { status: 400 }); } - const feedData = await fetchFeedData(modConfig.sources, searchParams); + const feedData = await fetchFeedData(modConfig.sources); if (!feedData) throw "Could not find feed data for sources"; const moddedFeedData = applyMods(feedData, modConfig); diff --git a/src/app/[feedId]/page.tsx b/src/app/[feedId]/page.tsx index 51cb931..baef351 100644 --- a/src/app/[feedId]/page.tsx +++ b/src/app/[feedId]/page.tsx @@ -1,5 +1,4 @@ import MainPage from "~/components/MainPage"; -import { decompressModConfig } from "~/services/compressionService"; type PageProps = { params: { @@ -8,8 +7,7 @@ type PageProps = { }; const Page = async ({ params }: PageProps) => { - const modConfig = await decompressModConfig(params.feedId); - return ; + return ; }; export default Page; diff --git a/src/app/api/mod-config/[feedId]/route.ts b/src/app/api/mod-config/[feedId]/route.ts new file mode 100644 index 0000000..d26dc6c --- /dev/null +++ b/src/app/api/mod-config/[feedId]/route.ts @@ -0,0 +1,24 @@ +import { NextResponse } from "next/server"; +import { decompressModConfig } from "~/services/compressionService"; + +const GET = async (request: Request, { params }: { params: { feedId: string } }) => { + try { + const { feedId } = params; + + const modConfig = decompressModConfig(feedId); + if (!modConfig) { + return new NextResponse("Bad Request - Invalid feed id", { status: 400 }); + } + + return new NextResponse(JSON.stringify(modConfig), { + headers: { "Cache-Control": "max-age=86400", "Content-Type": "application/json" }, + }); + } catch (errorMessage) { + console.error(errorMessage); + return new NextResponse((errorMessage as string | undefined) ?? "Unexpected Error", { + status: 500, + }); + } +}; + +export { GET }; diff --git a/src/app/api/mod-config/feed-id/route.ts b/src/app/api/mod-config/feed-id/route.ts new file mode 100644 index 0000000..caaba6d --- /dev/null +++ b/src/app/api/mod-config/feed-id/route.ts @@ -0,0 +1,28 @@ +import { NextResponse } from "next/server"; +import modConfigSchema from "~/schemas/modConfig"; +import { compressModConfig } from "~/services/compressionService"; + +const POST = async (request: Request) => { + try { + const body = (await request.json()) as unknown; + + const { data: modConfig, error } = modConfigSchema.safeParse(body); + if (error) { + return new NextResponse("Bad Request - Invalid mod config", { status: 400 }); + } + + const feedId = compressModConfig(modConfig); + if (!feedId) { + return new NextResponse("Bad Request - Could not compress mod config", { status: 400 }); + } + + return new NextResponse(feedId, { headers: { "Cache-Control": "max-age=86400" } }); + } catch (errorMessage) { + console.error(errorMessage); + return new NextResponse((errorMessage as string | undefined) ?? "Unexpected Error", { + status: 500, + }); + } +}; + +export { POST }; diff --git a/src/app/api/source-feed-data/route.ts b/src/app/api/source-feed-data/route.ts new file mode 100644 index 0000000..5f9841c --- /dev/null +++ b/src/app/api/source-feed-data/route.ts @@ -0,0 +1,30 @@ +import { NextResponse } from "next/server"; +import { z } from "zod"; +import { fetchFeedData } from "~/services/feedService"; + +const POST = async (request: Request) => { + try { + const body = (await request.json()) as unknown; + + const { data: urls, error } = z.array(z.string().url()).safeParse(body); + if (error) { + return new NextResponse("Bad Request - Invalid urls", { status: 400 }); + } + + const feedData = await fetchFeedData(urls); + if (!feedData) { + return new NextResponse("Bad Request - Invalid sources", { status: 400 }); + } + + return new NextResponse(JSON.stringify(feedData), { + headers: { "Cache-Control": "max-age=900", "Content-Type": "application/json" }, + }); + } catch (errorMessage) { + console.error(errorMessage); + return new NextResponse((errorMessage as string | undefined) ?? "Unexpected Error", { + status: 500, + }); + } +}; + +export { POST }; diff --git a/src/components/CopyFeedButton.tsx b/src/components/CopyFeedButton.tsx index 7471a19..4aacb24 100644 --- a/src/components/CopyFeedButton.tsx +++ b/src/components/CopyFeedButton.tsx @@ -1,37 +1,63 @@ "use client"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { LinkIcon } from "@heroicons/react/20/solid"; -import { compressModConfig } from "~/services/compressionService"; import type { ModConfig } from "~/types/ModConfig"; -const defaultButtonText = "Copy Feed URL"; - type ButtonProps = { modConfig: ModConfig; }; const CopyFeedButton = ({ modConfig }: ButtonProps) => { - const [buttonText, setButtonText] = useState(defaultButtonText); + const [feedId, setFeedId] = useState(undefined); + const [buttonText, setButtonText] = useState("Generate Feed"); + const [loading, setLoading] = useState(false); + + useEffect(() => { + setFeedId(undefined); + setButtonText("Generate Feed"); + }, [JSON.stringify(modConfig)]); + + const generateFeed = async () => { + setLoading(true); + + return await fetch(`/api/mod-config/feed-id`, { + method: "POST", + body: JSON.stringify(modConfig), + headers: { "Content-Type": "application/json" }, + }) + .then((response) => response.text()) + .then((newFeedId) => { + setFeedId(newFeedId); + setButtonText("Copy Feed URL"); + }) + .catch(() => { + setButtonText("Failed to Generate"); + setTimeout(() => setButtonText("Generate Feed"), 2000); + }) + .finally(() => setLoading(false)); + }; - const onClick = () => - compressModConfig(modConfig) - .then((feedId) => `${window.location.origin}/${feedId}/feed`) - .then((textToCopy) => navigator.clipboard.writeText(textToCopy)) + const copyFeedUrl = async () => { + await navigator.clipboard + .writeText(`${window.location.origin}/${feedId}/feed`) .then(() => { - setButtonText("Copied!"); - setTimeout(() => setButtonText(defaultButtonText), 2000); - }); + setButtonText("Copied"); + setTimeout(() => setButtonText("Copy Feed URL"), 2000); + }) + .catch(() => setButtonText("Failed to Copy")); + }; return ( ); diff --git a/src/components/EpisodeCard.tsx b/src/components/EpisodeCard.tsx index b0e3b86..e22e062 100644 --- a/src/components/EpisodeCard.tsx +++ b/src/components/EpisodeCard.tsx @@ -1,3 +1,5 @@ +"use client"; + import formatDuration from "~/utils/formatDuration"; type EpisodeCardProps = { diff --git a/src/components/FeedPreview.tsx b/src/components/FeedPreview.tsx index 879038f..d9ab9e4 100644 --- a/src/components/FeedPreview.tsx +++ b/src/components/FeedPreview.tsx @@ -1,3 +1,5 @@ +"use client"; + import Image from "next/image"; import { applyMods } from "~/services/modService"; import getValue from "~/utils/getValue"; diff --git a/src/components/Form.tsx b/src/components/Form.tsx index 4932669..2b34840 100644 --- a/src/components/Form.tsx +++ b/src/components/Form.tsx @@ -12,11 +12,11 @@ import modConfigSchema from "~/schemas/modConfig"; import type { ModConfig } from "~/types/ModConfig"; type FormProps = { - initialModConfig: ModConfig | undefined; + modConfig: ModConfig | undefined; setModConfig: (_: ModConfig) => void; }; -const Form = ({ initialModConfig, setModConfig }: FormProps) => { +const Form = ({ modConfig, setModConfig }: FormProps) => { const form = useForm({ mode: "onChange", resolver: zodResolver( @@ -33,7 +33,7 @@ const Form = ({ initialModConfig, setModConfig }: FormProps) => { ...title.defaultValue, ...imageUrl.defaultValue, ...mods.defaultValue, - ...initialModConfig, + ...modConfig, }, }); diff --git a/src/components/MainPage.tsx b/src/components/MainPage.tsx index 1be26f0..5ba318f 100644 --- a/src/components/MainPage.tsx +++ b/src/components/MainPage.tsx @@ -1,7 +1,9 @@ "use client"; import { useEffect, useState } from "react"; -import { fetchFeedData } from "~/services/feedService"; +import { useRouter } from "next/navigation"; +import feed from "~/schemas/feed"; +import modConfigSchema from "~/schemas/modConfig"; import CopyFeedButton from "./CopyFeedButton"; import FeedPreview from "./FeedPreview"; import Form from "./Form"; @@ -9,19 +11,48 @@ import type { FeedData } from "~/types/FeedData"; import type { ModConfig } from "~/types/ModConfig"; type MainPageProps = { - initialModConfig?: ModConfig | undefined; + initialFeedId?: string | undefined; }; -const MainPage = ({ initialModConfig }: MainPageProps) => { - const [modConfig, setModConfig] = useState(initialModConfig); +const MainPage = ({ initialFeedId }: MainPageProps) => { + const router = useRouter(); + + const [loading, setLoading] = useState(!!initialFeedId); + const [modConfig, setModConfig] = useState(undefined); const [sourceFeedData, setSourceFeedData] = useState(undefined); + useEffect(() => { + setLoading(true); + fetch(`/api/mod-config/${initialFeedId}`) + .then((response) => response.json()) + .then((data) => modConfigSchema.parse(data)) + .then((initialModConfig) => setModConfig(initialModConfig)) + .catch(() => router.push("/")); + }, [initialFeedId]); + useEffect(() => { if (modConfig) { - const _ = fetchFeedData(modConfig?.sources).then((feedData) => setSourceFeedData(feedData)); + fetch(`/api/source-feed-data`, { + method: "POST", + body: JSON.stringify(modConfig.sources), + headers: { "Content-Type": "application/json" }, + }) + .then((response) => response.json()) + .then((data) => feed.parse(data)) + .then((feedData) => setSourceFeedData(feedData)) + .catch(console.error) + .finally(() => setLoading(false)); } }, [JSON.stringify(modConfig?.sources)]); + if (loading) { + return ( +

+ Loading Feed Data... +
+ ); + } + return (
@@ -40,7 +71,7 @@ const MainPage = ({ initialModConfig }: MainPageProps) => { {modConfig && sourceFeedData && }
-
+
diff --git a/src/components/SectionHeader.tsx b/src/components/SectionHeader.tsx index 2a4b9b7..a2e2bb8 100644 --- a/src/components/SectionHeader.tsx +++ b/src/components/SectionHeader.tsx @@ -1,3 +1,5 @@ +"use client"; + import type { ReactNode } from "react"; type SectionTitleProps = { diff --git a/src/formSections/mods.tsx b/src/formSections/mods.tsx index fb9e9fb..c47eb5b 100644 --- a/src/formSections/mods.tsx +++ b/src/formSections/mods.tsx @@ -1,5 +1,3 @@ -"use client"; - import { useFieldArray, useFormContext } from "react-hook-form"; import z from "zod"; import AddButton from "~/components/AddButton"; diff --git a/src/formSections/sources.tsx b/src/formSections/sources.tsx index 3930bf6..1604aad 100644 --- a/src/formSections/sources.tsx +++ b/src/formSections/sources.tsx @@ -1,5 +1,3 @@ -"use client"; - import { useFieldArray, useFormContext } from "react-hook-form"; import z from "zod"; import AddButton from "~/components/AddButton"; diff --git a/src/services/compressionService.ts b/src/services/compressionService.ts index b8e352d..5d576e5 100644 --- a/src/services/compressionService.ts +++ b/src/services/compressionService.ts @@ -1,17 +1,38 @@ -import { compress, decompress } from "brotli-compress"; +"use server"; + +import "server-only"; +import brotli from "brotli"; import modConfigSchema from "~/schemas/modConfig"; import type { ModConfig } from "~/types/ModConfig"; -const compressModConfig = async (modConfig: ModConfig): Promise => { - const compressedText = await compress(Buffer.from(JSON.stringify(modConfig))); - return Buffer.from(compressedText).toString("hex"); +const { compress, decompress } = brotli as { + compress: (buffer: Buffer, options?: never) => Uint8Array; + decompress: (buffer: Buffer, outputSize?: number) => Uint8Array; +}; + +const compressModConfig = (modConfig: ModConfig): string => { + const decompressedString = JSON.stringify(modConfig); + const decompressedData = Buffer.from(decompressedString); + + const compressedData = compress(decompressedData); + const compressedString = Buffer.from(compressedData.buffer).toString("hex"); + + return compressedString; }; -const decompressModConfig = async (compressedText: string): Promise => - decompress(Buffer.from(compressedText, "hex")) - .then((decompressedText) => Buffer.from(decompressedText).toString()) - .then(JSON.parse) - .then((data) => modConfigSchema.parse(data)) - .catch(() => undefined); +const decompressModConfig = (compressedText: string): ModConfig | undefined => { + try { + const compressedData = Buffer.from(compressedText, "hex"); + + const decompressedData = decompress(compressedData); + const decompressedText = Buffer.from(decompressedData.buffer).toString(); + + const modConfig = JSON.parse(decompressedText) as unknown; + + return modConfigSchema.parse(modConfig); + } catch { + return undefined; + } +}; export { compressModConfig, decompressModConfig }; diff --git a/src/services/feedService.ts b/src/services/feedService.ts index 7245207..7773bd5 100644 --- a/src/services/feedService.ts +++ b/src/services/feedService.ts @@ -28,11 +28,10 @@ const buildFeed = (feed: FeedData, feedId: string, host?: string) => { return builder.build(feed) as string; }; -const fetchFeedData = async (urls: string[], searchParams?: URLSearchParams) => { +const fetchFeedData = async (urls: string[]) => { const [firstFeed, ...otherFeeds] = await Promise.all( urls.map((urlString) => { const url = new URL(urlString); - if (searchParams) searchParams.forEach((value, key) => url.searchParams.set(key, value)); return fetch(url, { headers: {