From ba2de823c6010eb1d862607b2ea8b134fdcd9f17 Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Thu, 2 Apr 2026 22:44:28 +0200 Subject: [PATCH] fix: redirect apple-touch-icon and favicons to org-specific icons on iOS iOS uses the apple-touch-icon link tag for the home screen icon, not the manifest. The worker now intercepts requests for apple-touch-icon and favicon PNGs, redirecting to the org-specific version from R2 when the x-org-id cookie is set. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- apps/main/worker/index.test.ts | 28 ++++++++++++++++++++++++++++ apps/main/worker/index.ts | 14 ++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/apps/main/worker/index.test.ts b/apps/main/worker/index.test.ts index 8ee2184..48c5ab6 100644 --- a/apps/main/worker/index.test.ts +++ b/apps/main/worker/index.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect } from "vitest"; import { buildManifest, parseOrgIdFromCookie } from "./index"; +import worker from "./index"; describe("parseOrgIdFromCookie", () => { it("returns null when no cookie header", () => { @@ -35,3 +36,30 @@ describe("buildManifest", () => { expect(manifest.icons[2].purpose).toBe("maskable"); }); }); + +describe("worker icon redirects", () => { + it("redirects apple-touch-icon to org-specific icon when cookie is set", async () => { + const request = new Request("https://app.xtablo.com/pwa-icons/apple-touch-icon-180x180.png", { + headers: { cookie: "x-org-id=42" }, + }); + const response = await worker.fetch(request); + expect(response.status).toBe(302); + expect(response.headers.get("location")).toBe("https://assets.xtablo.com/org-icons/42/icon-180.png"); + }); + + it("does not redirect apple-touch-icon when no cookie", async () => { + const request = new Request("https://app.xtablo.com/pwa-icons/apple-touch-icon-180x180.png"); + const response = await worker.fetch(request); + // Falls through to 404 (no static assets in test) + expect(response.status).toBe(404); + }); + + it("redirects favicon to org-specific icon when cookie is set", async () => { + const request = new Request("https://app.xtablo.com/pwa-icons/favicon-32x32.png", { + headers: { cookie: "x-org-id=7" }, + }); + const response = await worker.fetch(request); + expect(response.status).toBe(302); + expect(response.headers.get("location")).toBe("https://assets.xtablo.com/org-icons/7/icon-32.png"); + }); +}); diff --git a/apps/main/worker/index.ts b/apps/main/worker/index.ts index 57eca2d..30e6ff1 100644 --- a/apps/main/worker/index.ts +++ b/apps/main/worker/index.ts @@ -64,6 +64,20 @@ export default { fetch(request: Request) { const url = new URL(request.url); + // Redirect PWA icons to org-specific versions when cookie is set + const iconRedirects: Record = { + "/pwa-icons/apple-touch-icon-180x180.png": "icon-180.png", + "/pwa-icons/favicon-32x32.png": "icon-32.png", + "/pwa-icons/favicon-16x16.png": "icon-16.png", + }; + if (iconRedirects[url.pathname]) { + const cookieHeader = request.headers.get("cookie"); + const orgId = parseOrgIdFromCookie(cookieHeader); + if (orgId !== null) { + return Response.redirect(`${ASSETS_BASE_URL}/org-icons/${orgId}/${iconRedirects[url.pathname]}`, 302); + } + } + if (url.pathname === "/manifest.webmanifest") { const cookieHeader = request.headers.get("cookie"); const orgId = parseOrgIdFromCookie(cookieHeader);