Replace the empty 204 at /favicon.ico with the landing wordmark — cream nzip, coral z — in a rounded square on the landing background, baked as a 64x64 PNG so the letterforms render identically on every platform. Pushed sites keep controlling their own icons; this is the origin fallback.
- commit
- 2c2fce25e3dcfdc3a80027333905768a7169666d
- parent
- 87c3a377af445c0a4f1498c1c507aac32952d397
- author
- Dami <dami.etoile@gmail.com>
| A | worker/ | 23 | +++++++++++++++++++++++ |
| M | worker/ | 10 | +++++++++- |
| M | worker/ | 13 | +++++++++++++ |
@@ -0,0 +1,23 @@
1+// The service favicon: the landing wordmark (cream "nzip", coral z) in a2+// rounded square on the landing background #1e1810. Baked as a 64x64 PNG so3+// every browser shows the same letterforms — an SVG <text> favicon would4+// render with whatever font the viewer's platform substitutes.5+//6+// Source artwork (regenerate the PNG by rendering this at 64x64 with7+// transparent background, e.g. a headless-browser element screenshot):8+//9+// <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">10+// <rect width="64" height="64" rx="14" fill="#1e1810"/>11+// <text x="33" y="33" text-anchor="middle" dominant-baseline="central"12+// font-family="ui-monospace, SFMono-Regular, Menlo, monospace"13+// font-weight="700" font-size="26" letter-spacing="-4.7"14+// fill="#f2e8d4">n<tspan fill="#ff6b4a">z</tspan>ip</text>15+// </svg>16+17+const FAVICON_PNG_BASE64 =18+ "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";19+20+export const FAVICON_PNG: Uint8Array = Uint8Array.from(21+ atob(FAVICON_PNG_BASE64),22+ (c) => c.charCodeAt(0),23+);@@ -1,6 +1,7 @@
11 import { parseAddress, parseManifest } from "../../shared/mod.ts";22 import { getSiteByAddress, type SiteRow } from "./db.ts";33 import type { Env } from "./env.ts";4+import { FAVICON_PNG } from "./favicon.ts";45 import { hasValidUnlockCookie, makeUnlockCookie, verifyPassword } from "./password.ts";56 67 const GONE_PAGE = `<!doctype html><meta charset="utf-8"><title>expired — nzip</title>@@ -61,7 +62,14 @@ export async function serve(req: Request, env: Env, url: URL): Promise<Response>
6162 const path = url.pathname;6263 6364 if (path === "/") return htmlResponse(landingPage(env), 200);64- if (path === "/favicon.ico") return new Response(null, { status: 204 });65+ // Served for the browsers' default probe; pushed sites under /{4hex}/ still66+ // control their own icons through their HTML. PNG bytes at the .ico path are67+ // accepted everywhere.68+ if (path === "/favicon.ico") {69+ return new Response(FAVICON_PNG, {70+ headers: { "content-type": "image/png", "cache-control": "public, max-age=86400" },71+ });72+ }6573 if (path === "/robots.txt") {6674 return new Response("User-agent: *\nAllow: /\n", {6775 headers: { "content-type": "text/plain" },@@ -104,3 +104,16 @@ test("missing index path still returns not found", async () => {
104104 105105 assert.equal(response.status, 404);106106 });107+108+test("favicon serves the wordmark PNG with a cacheable response", async () => {109+ const url = new URL("https://n.zip/favicon.ico");110+ const response = await serve(new Request(url), envFor({}), url);111+112+ assert.equal(response.status, 200);113+ assert.equal(response.headers.get("content-type"), "image/png");114+ assert.equal(response.headers.get("cache-control"), "public, max-age=86400");115+ const bytes = new Uint8Array(await response.arrayBuffer());116+ // PNG signature: the embedded icon decoded from base64 intact.117+ assert.deepEqual([...bytes.slice(0, 8)], [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);118+ assert.ok(bytes.length > 500);119+});