| 1 | import { OG_RENDER_VERSION } from "@g1t/contracts/og"; |
| 2 | |
| 3 | import { cardPath, clean } from "./resolve.ts"; |
| 4 | |
| 5 | /** |
| 6 | * The version of the cards' design, part of every cache key. Bump it (in |
| 7 | * `packages/contracts/src/og.ts`, which the site and the docs read too) |
| 8 | * whenever the card design changes, so the cards already cached are drawn |
| 9 | * again and every page links to a new address. |
| 10 | */ |
| 11 | export const RENDER_VERSION = OG_RENDER_VERSION; |
| 12 | |
| 13 | /** |
| 14 | * A `v` as the site and the docs write it (`ogVersion` in |
| 15 | * packages/contracts): the render version, then, for the site, a |
| 16 | * fingerprint of what the card shows, at most seven base-36 characters. |
| 17 | */ |
| 18 | const VERSION = new RegExp(`^${RENDER_VERSION.replace(/\./g, "\\.")}(\\.[0-9a-z]{1,7})?$`); |
| 19 | |
| 20 | /** |
| 21 | * The address a card is cached under: the render version, then only the |
| 22 | * parameters that change the card, in order, so stray ones (trackers, |
| 23 | * cache busters) cannot fill the cache. A page's address is the page whose |
| 24 | * card it shows (`cardPath`), the docs' text as the card draws it, and the |
| 25 | * page's own content version `v` only in the form the site writes it. |
| 26 | */ |
| 27 | export function cacheKey(url: URL): string { |
| 28 | const key = new URL(url.pathname, url.origin); |
| 29 | key.searchParams.set("render", RENDER_VERSION); |
| 30 | const set = (name: string, value: string | null) => { |
| 31 | if (value !== null) key.searchParams.set(name, value); |
| 32 | }; |
| 33 | if (url.pathname === "/docs") { |
| 34 | // As docsCard reads them. |
| 35 | set("title", clean(url.searchParams.get("title"), 160)); |
| 36 | set("section", clean(url.searchParams.get("section"), 60)); |
| 37 | set("description", clean(url.searchParams.get("description"), 300)); |
| 38 | } else if (url.pathname === "/image") { |
| 39 | set("path", cardPath(url.searchParams.get("path") ?? "/")); |
| 40 | } else { |
| 41 | return key.toString(); |
| 42 | } |
| 43 | const version = url.searchParams.get("v"); |
| 44 | if (version !== null && VERSION.test(version)) set("v", version); |
| 45 | return key.toString(); |
| 46 | } |
| 47 | |
| 48 | /** |
| 49 | * The address a drawn card is also kept under, by what it shows: two |
| 50 | * addresses for the same card (an old `v`, a made-up one) share one |
| 51 | * drawing rather than each being drawn. |
| 52 | */ |
| 53 | export async function drawnKey(origin: string, card: unknown): Promise<string> { |
| 54 | const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify(card))); |
| 55 | const hex = [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join(""); |
| 56 | const key = new URL("/drawn", origin); |
| 57 | key.searchParams.set("render", RENDER_VERSION); |
| 58 | key.searchParams.set("card", hex); |
| 59 | return key.toString(); |
| 60 | } |