From 595b3307419031693ef943add7238a6645d19b45 Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Thu, 2 Apr 2026 22:01:57 +0200 Subject: [PATCH] feat: dynamic manifest in Cloudflare Worker with cookie-based org identification Adds parseOrgIdFromCookie and buildManifest exports to the worker, intercepting /manifest.webmanifest to serve org-specific PWA icon URLs based on the x-org-id cookie. Removes legacy @ts-nocheck and biome-ignore-file comments. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- apps/main/worker/index.test.ts | 37 +++++++++++++++ apps/main/worker/index.ts | 82 +++++++++++++++++++++++++++++++--- 2 files changed, 112 insertions(+), 7 deletions(-) create mode 100644 apps/main/worker/index.test.ts diff --git a/apps/main/worker/index.test.ts b/apps/main/worker/index.test.ts new file mode 100644 index 0000000..75f05e9 --- /dev/null +++ b/apps/main/worker/index.test.ts @@ -0,0 +1,37 @@ +import { describe, it, expect } from "vitest"; +import { buildManifest, parseOrgIdFromCookie } from "./index"; + +describe("parseOrgIdFromCookie", () => { + it("returns null when no cookie header", () => { + expect(parseOrgIdFromCookie(null)).toBeNull(); + }); + + it("returns null when x-org-id cookie is missing", () => { + expect(parseOrgIdFromCookie("other=value; foo=bar")).toBeNull(); + }); + + it("parses x-org-id from cookie string", () => { + expect(parseOrgIdFromCookie("x-org-id=42; other=value")).toBe(42); + }); + + it("returns null for non-numeric x-org-id", () => { + expect(parseOrgIdFromCookie("x-org-id=abc")).toBeNull(); + }); +}); + +describe("buildManifest", () => { + it("returns default icons when orgId is null", () => { + const manifest = buildManifest(null); + expect(manifest.name).toBe("XTablo"); + expect(manifest.icons[0].src).toBe("/pwa-icons/pwa-192x192.png"); + }); + + it("returns org-specific icon URLs when orgId is provided", () => { + const manifest = buildManifest(42); + expect(manifest.name).toBe("XTablo"); + expect(manifest.icons[0].src).toBe("/api/v1/public/org-icons/42/icon-192.png"); + expect(manifest.icons[1].src).toBe("/api/v1/public/org-icons/42/icon-512.png"); + expect(manifest.icons[2].src).toBe("/api/v1/public/org-icons/42/icon-512-maskable.png"); + expect(manifest.icons[2].purpose).toBe("maskable"); + }); +}); diff --git a/apps/main/worker/index.ts b/apps/main/worker/index.ts index 8f89377..b841e1f 100644 --- a/apps/main/worker/index.ts +++ b/apps/main/worker/index.ts @@ -1,16 +1,84 @@ -// @ts-nocheck -// biome-ignore-file: Worker entry point with dynamic imports -/* eslint-disable */ +export function parseOrgIdFromCookie(cookieHeader: string | null): number | null { + if (!cookieHeader) return null; + const match = cookieHeader.match(/(?:^|;\s*)x-org-id=(\d+)/); + if (!match) return null; + const id = Number.parseInt(match[1], 10); + return Number.isNaN(id) ? null : id; +} + +interface ManifestIcon { + src: string; + sizes: string; + type: string; + purpose?: string; +} + +interface WebAppManifest { + name: string; + short_name: string; + description: string; + start_url: string; + display: string; + orientation: string; + theme_color: string; + background_color: string; + icons: ManifestIcon[]; +} + +export function buildManifest(orgId: number | null): WebAppManifest { + const base = { + name: "XTablo", + short_name: "XTablo", + description: "Collaborative project management for construction teams", + start_url: "/", + display: "standalone", + orientation: "any", + theme_color: "#1e1b2e", + background_color: "#1e1b2e", + }; + + if (orgId !== null) { + return { + ...base, + icons: [ + { src: `/api/v1/public/org-icons/${orgId}/icon-192.png`, sizes: "192x192", type: "image/png" }, + { src: `/api/v1/public/org-icons/${orgId}/icon-512.png`, sizes: "512x512", type: "image/png" }, + { src: `/api/v1/public/org-icons/${orgId}/icon-512-maskable.png`, sizes: "512x512", type: "image/png", purpose: "maskable" }, + ], + }; + } + + return { + ...base, + icons: [ + { src: "/pwa-icons/pwa-192x192.png", sizes: "192x192", type: "image/png" }, + { src: "/pwa-icons/pwa-512x512.png", sizes: "512x512", type: "image/png" }, + { src: "/pwa-icons/pwa-512x512-maskable.png", sizes: "512x512", type: "image/png", purpose: "maskable" }, + ], + }; +} export default { - fetch(request) { + fetch(request: Request) { const url = new URL(request.url); - if (url.pathname.startsWith("/api/")) { - return Response.json({ - name: "Cloudflare", + if (url.pathname === "/manifest.webmanifest") { + const cookieHeader = request.headers.get("cookie"); + const orgId = parseOrgIdFromCookie(cookieHeader); + const manifest = buildManifest(orgId); + + return new Response(JSON.stringify(manifest), { + headers: { + "Content-Type": "application/manifest+json", + "Cache-Control": "no-cache", + }, }); } + + if (url.pathname.startsWith("/api/")) { + return Response.json({ name: "Cloudflare" }); + } + return new Response(null, { status: 404 }); }, };