/** * Pulpie WebUI - Cloudflare Worker Edge Handler * * Serves static assets and provides a high-performance, CORS-enabled * edge proxy endpoint (/api/proxy) for fetching external documentation/web pages. */ const CORS_HEADERS = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS', 'Access-Control-Allow-Headers': '*', 'Access-Control-Max-Age': '86400', }; const BROWSER_HEADERS = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.9', 'Sec-Ch-Ua': '"Not-A.Brand";v="99", "Chromium";v="124"', 'Sec-Ch-Ua-Mobile': '?0', 'Sec-Ch-Ua-Platform': '"Linux"', }; async function handleProxy(request) { if (request.method === 'OPTIONS') { return new Response(null, { status: 204, headers: CORS_HEADERS }); } const url = new URL(request.url); const targetUrl = url.searchParams.get('url'); if (!targetUrl) { return new Response(JSON.stringify({ error: "Missing 'url' query parameter." }), { status: 400, headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' }, }); } let parsedTarget; try { parsedTarget = new URL(targetUrl); if (!['http:', 'https:'].includes(parsedTarget.protocol)) { throw new Error('Invalid protocol'); } } catch { return new Response( JSON.stringify({ error: "Invalid target URL. Must start with http:// or https://." }), { status: 400, headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' } } ); } try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 15000); const upstreamResponse = await fetch(parsedTarget.toString(), { signal: controller.signal, headers: BROWSER_HEADERS, redirect: 'follow', }); clearTimeout(timeout); const body = await upstreamResponse.text(); const contentType = upstreamResponse.headers.get('content-type') || 'text/html; charset=utf-8'; return new Response(body, { status: 200, headers: { ...CORS_HEADERS, 'Content-Type': contentType, 'X-Final-Url': upstreamResponse.url || parsedTarget.toString(), }, }); } catch (err) { return new Response( JSON.stringify({ error: `Failed to fetch target URL: ${err.message}` }), { status: 502, headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' } } ); } } export default { async fetch(request, env) { const url = new URL(request.url); if (url.pathname === '/api/proxy') { return handleProxy(request); } if (env.ASSETS) { return env.ASSETS.fetch(request); } return new Response('Not Found', { status: 404 }); }, };