|
| 1 | +import type { Context } from "https://edge.netlify.com"; |
| 2 | +import * as css from "https://esm.sh/[email protected]"; |
| 3 | +import * as contentType from "https://deno.land/x/[email protected]/mod.ts"; |
| 4 | + |
| 5 | +export default async ( |
| 6 | + request: Request, |
| 7 | + _context: Context |
| 8 | +): Promise<Response> => { |
| 9 | + const { searchParams } = new URL(request.url); |
| 10 | + const url = `https://fonts.googleapis.com/css?${searchParams.toString()}`; |
| 11 | + const resp = await fetch(url, { |
| 12 | + headers: { |
| 13 | + // User-Agent is used to serve the right font format |
| 14 | + "User-Agent": request.headers.get("User-Agent") || "", |
| 15 | + }, |
| 16 | + }); |
| 17 | + if (!resp.ok) { |
| 18 | + return new Response("Request to upstream failed", { status: 503 }); |
| 19 | + } |
| 20 | + const ct = contentType.parse(resp.headers.get("Content-Type") ?? ""); |
| 21 | + if (ct.type != "text/css") { |
| 22 | + return new Response(`Response was not CSS, but was: ${ct}`, { |
| 23 | + status: 503, |
| 24 | + }); |
| 25 | + } |
| 26 | + |
| 27 | + try { |
| 28 | + const newCSS = rewriteURLs(await resp.text()); |
| 29 | + const headers = new Headers({ |
| 30 | + "Content-Type": "text/css; charset=utf-8", |
| 31 | + "Cache-Control": resp.headers.get("cache-control") ?? "", |
| 32 | + Expires: resp.headers.get("expires") ?? "", |
| 33 | + }); |
| 34 | + return new Response(newCSS, { headers }); |
| 35 | + } catch (e) { |
| 36 | + return new Response(e, { status: 500 }); |
| 37 | + } |
| 38 | +}; |
| 39 | + |
| 40 | +const urlRegexp = /url\(([^\)]+)\)/; |
| 41 | +const rewriteURLs = (text: string): string => { |
| 42 | + const sheet = css.parse(text); |
| 43 | + if (sheet.stylesheet?.parsingErrors?.length) { |
| 44 | + throw new Error(`Invalid CSS: ${sheet.stylesheet?.parsingErrors}`); |
| 45 | + } |
| 46 | + |
| 47 | + for (const rule of sheet.stylesheet?.rules || []) { |
| 48 | + if (rule.type !== "font-face") { |
| 49 | + continue; |
| 50 | + } |
| 51 | + for (const prop of (rule as css.FontFace).declarations || []) { |
| 52 | + const decl = prop as css.Declaration; |
| 53 | + if (decl.property !== "src" || !decl.value) { |
| 54 | + continue; |
| 55 | + } |
| 56 | + const matches = urlRegexp.exec(decl.value); |
| 57 | + if (!matches || matches.length < 2) { |
| 58 | + continue; |
| 59 | + } |
| 60 | + const url = new URL(matches[1]); |
| 61 | + decl.value = decl.value.replace( |
| 62 | + urlRegexp, |
| 63 | + `url(/fonts/file${url.pathname})` |
| 64 | + ); |
| 65 | + } |
| 66 | + } |
| 67 | + |
| 68 | + return css.stringify(sheet); |
| 69 | +}; |
0 commit comments