import type { Plugin } from 'vite' // Dev-only same-origin proxy for CORS-hostile online-font endpoints. // // unifont's `google` provider reads `https://fonts.google.com/metadata/fonts` to // build its family catalog and `https://fonts.googleapis.com/css2` to resolve // font faces. The metadata endpoint never sends `Access-Control-Allow-Origin` // and sets `cross-origin-resource-policy: same-site`, so a browser app served on // its own origin (any origin that is not Google) cannot read it directly — the // provider fails to initialize with "Could not initialize provider `google`". // The css2 endpoint's OPTIONS preflight carries no CORS headers either, so some // browsers (notably Firefox) block the font-discovery fetch. // // The browser relays such requests here instead (see `createBrowserFontFetch()` in // src/app/editor/fonts), which keeps the request same-origin and forwards it // server-side to Google. This is a W4C fork delta; upstream openpencil does not // ship it because Tauri goes through the Rust command that is not CORS-bound. export const FONT_PROXY_PREFIX = '/__font-proxy' // Only these hosts are CORS-hostile from the browser; everything else // (notably fonts.gstatic.com glyph files) already sends CORS headers and is // fetched directly by the browser. // - fonts.google.com — metadata never sends CORS headers. // - fonts.googleapis.com — css2 font-discovery OPTIONS preflight returns no // CORS headers, so Firefox blocks the discovery fetch unless it is proxied. const PROXIED_HOSTS = new Set(['fonts.google.com', 'fonts.googleapis.com']) const FONT_PROXY_USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126 Safari/537.36' export function createDevFontProxyPlugin(): Plugin { return { name: 'open-pencil-dev-font-proxy', configureServer(server) { server.middlewares.use(async (req, res, next) => { let pathname: string try { pathname = new URL(req.url ?? '/', 'http://dev').pathname } catch { next() return } if (pathname !== FONT_PROXY_PREFIX) { next() return } const target = new URL(req.url ?? '/', 'http://dev').searchParams.get('url') if (!target) { res.statusCode = 400 res.end('missing url') return } // The browser forwards the original `user-agent` here because it cannot // send one cross-origin; unifont uses it to pick the glyph format. const ua = new URL(req.url ?? '/', 'http://dev').searchParams.get('ua') let targetURL: URL try { targetURL = new URL(decodeURIComponent(target)) } catch { res.statusCode = 400 res.end('invalid url') return } if (targetURL.protocol !== 'https:' || !PROXIED_HOSTS.has(targetURL.host)) { res.statusCode = 403 res.end('proxied host denied') return } try { const upstream = await fetch(targetURL, { headers: { 'user-agent': ua ?? FONT_PROXY_USER_AGENT, accept: 'application/json, text/plain, */*', }, redirect: 'follow', }) const body = Buffer.from(await upstream.arrayBuffer()) res.statusCode = upstream.status const type = upstream.headers.get('content-type') if (type) res.setHeader('content-type', type) res.setHeader('access-control-allow-origin', '*') res.end(body) } catch (e) { res.statusCode = 502 res.end(String(e)) } }) }, } }