import { LRUCache } from "lru-cache"; import { fetchDocument, extractPreview, fetchLexicon, getLexiconType, getLexiconPreviewDef } from "./lib/graphql"; import { generateOgImage, generateLexiconOgImage } from "./lib/og-image"; const PORT = process.env.PORT || 3000; const BASE_URL = process.env.BASE_URL || `http://localhost:${PORT}`; const CDN_URL = process.env.CDN_URL || "https://tools.slices.network"; // Cache for generated images (max 500, 1 hour TTL) const imageCache = new LRUCache({ max: 500, ttl: 1000 * 60 * 60, }); // Cache for HTML (5 min TTL) const htmlCache = new LRUCache({ max: 10, ttl: 1000 * 60 * 5, }); async function fetchHtml(filename: string): Promise { const cacheKey = filename; const cached = htmlCache.get(cacheKey); if (cached) return cached; const url = `${CDN_URL}/${filename}`; const res = await fetch(url); if (!res.ok) { throw new Error(`Failed to fetch ${url}: ${res.status}`); } const html = await res.text(); htmlCache.set(cacheKey, html); return html; } function escapeHtml(str: string): string { return str .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } const server = Bun.serve({ port: PORT, async fetch(req) { const url = new URL(req.url); const path = url.pathname; // Health check if (path === "/health") { return new Response("ok"); } // OG Image endpoint: /og/docs/:handle/:slug.png const ogMatch = path.match(/^\/og\/docs\/([^/]+)\/([^/]+)\.png$/); if (ogMatch) { const [, handle, slug] = ogMatch; try { const doc = await fetchDocument(handle, slug); if (!doc) { return new Response("Document not found", { status: 404 }); } // Cache by cid - auto-invalidates when doc changes const cacheKey = doc.cid; const cached = imageCache.get(cacheKey); if (cached) { return new Response(cached, { headers: { "Content-Type": "image/png" }, }); } const imageResponse = await generateOgImage({ title: doc.title, preview: extractPreview(doc.blocks), author: doc.actorHandle, avatarUrl: doc.appBskyActorProfileByDid?.avatar?.url, }); const buffer = new Uint8Array(await imageResponse.arrayBuffer()); imageCache.set(cacheKey, buffer); return new Response(buffer, { headers: { "Content-Type": "image/png" }, }); } catch (err) { console.error("OG image error:", err); return new Response("Error generating image", { status: 500 }); } } // Docs endpoint: /docs/:handle/:slug (with OG tags) const docsMatch = path.match(/^\/docs\/([^/]+)\/([^/]+)$/); if (docsMatch) { const [, handle, slug] = docsMatch; try { const docsHtml = await fetchHtml("docs"); const doc = await fetchDocument(handle, slug); if (!doc) { return new Response(docsHtml, { headers: { "Content-Type": "text/html" }, }); } const preview = extractPreview(doc.blocks); const ogImageUrl = `${BASE_URL}/og/docs/${encodeURIComponent(handle)}/${encodeURIComponent(slug)}.png`; const ogTags = ` `; const html = docsHtml.replace("", `${ogTags}`); return new Response(html, { headers: { "Content-Type": "text/html" }, }); } catch (err) { console.error("OG tag injection error:", err); return new Response("Error loading page", { status: 500 }); } } // Fallback for /docs/* (callback, settings, etc) - serve HTML without OG injection if (path.startsWith("/docs/")) { try { const docsHtml = await fetchHtml("docs"); return new Response(docsHtml, { headers: { "Content-Type": "text/html" }, }); } catch (err) { console.error("Docs fallback error:", err); return new Response("Error loading page", { status: 500 }); } } // OG Image endpoint for lexicon-explorer: /og/lexicon-explorer/:nsid.png const lexiconOgMatch = path.match(/^\/og\/lexicon-explorer\/(.+)\.png$/); if (lexiconOgMatch) { const nsid = decodeURIComponent(lexiconOgMatch[1]); try { const lexicon = await fetchLexicon(nsid); if (!lexicon) { return new Response("Lexicon not found", { status: 404 }); } // Cache by nsid const cacheKey = `lexicon:${nsid}`; const cached = imageCache.get(cacheKey); if (cached) { return new Response(cached, { headers: { "Content-Type": "image/png" }, }); } const previewDef = getLexiconPreviewDef(lexicon); const imageResponse = await generateLexiconOgImage({ nsid: lexicon.id, type: getLexiconType(lexicon), author: lexicon.actorHandle || "unknown", jsonDef: previewDef, }); const buffer = new Uint8Array(await imageResponse.arrayBuffer()); imageCache.set(cacheKey, buffer); return new Response(buffer, { headers: { "Content-Type": "image/png" }, }); } catch (err) { console.error("Lexicon OG image error:", err); return new Response("Error generating image", { status: 500 }); } } // Lexicon explorer endpoint: /lexicon-explorer?nsid=xxx (with OG tags) if (path === "/lexicon-explorer") { const nsid = url.searchParams.get("nsid"); try { const explorerHtml = await fetchHtml("lexicon-explorer"); if (!nsid) { // No nsid param, serve without OG injection return new Response(explorerHtml, { headers: { "Content-Type": "text/html" }, }); } const lexicon = await fetchLexicon(nsid); if (!lexicon) { // Lexicon not found, serve without OG injection return new Response(explorerHtml, { headers: { "Content-Type": "text/html" }, }); } const type = getLexiconType(lexicon); const description = lexicon.description || `${type} lexicon by @${lexicon.actorHandle}`; const ogImageUrl = `${BASE_URL}/og/lexicon-explorer/${encodeURIComponent(nsid)}.png`; const ogTags = ` `; const html = explorerHtml.replace("", `${ogTags}`); return new Response(html, { headers: { "Content-Type": "text/html" }, }); } catch (err) { console.error("Lexicon explorer error:", err); return new Response("Error loading page", { status: 500 }); } } return new Response("Not found", { status: 404 }); }, }); console.log(`OG server running on port ${server.port}`); console.log(`Fetching HTML from: ${CDN_URL}`);