import { Hono } from 'hono'; import { GAMES_ROOT } from '../config.js'; import { resolveAsset } from './resolveAsset.js'; const app = new Hono(); /** * Fetch a PDF and stream it back inline so the browser renders it instead of * downloading. PDF hosts often send `Content-Disposition: attachment`, which * forces a download even when the URL is opened in a tab or iframe. This route * strips that header and forces `inline` so the PDF can be embedded in the web * app's viewer. Like `/asset`, a relative URL (no scheme) is treated as a game * asset path relative to `GAMES_ROOT`. */ app.get('/', async (c) => { const raw = c.req.query('url'); if (!raw) { return c.json({ error: 'Missing url query param' }, 400); } const result = await resolveAsset(raw, GAMES_ROOT); if (!result.ok) { return c.json( { error: result.reason === 'not-found' ? 'Asset not found' : 'Invalid url query param' }, result.reason === 'not-found' ? 404 : 400, ); } const asset = result.asset; // A local game asset is streamed from disk; a remote one is fetched. let body: BodyInit; if (asset.stream) { body = asset.stream as unknown as BodyInit; } else { let res: Response; try { res = await fetch(asset.url!); } catch (err) { return c.json({ error: `Failed to fetch pdf: ${String(err)}` }, 502); } if (!res.ok) { return c.json({ error: `Pdf responded ${res.status}` }, 502); } body = res.body!; } return new Response(body, { headers: { 'Content-Type': 'application/pdf', 'Content-Disposition': 'inline', 'Cache-Control': 'public, max-age=86400', }, }); }); export default app;