Skip to content
60 linesCodeBlameRaw
1import { OG_RENDER_VERSION } from "@g1t/contracts/og";
2
3import { 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 */
11export 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 */
18const 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 */
27export 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 */
53export 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}