Cloudflare serves static assets before the worker runs, so the icon redirect logic was never reached. Renamed the default icon files to default-* prefix. The worker now handles all requests for the original icon paths: redirects to org-specific icons when cookie is set, or to the renamed defaults otherwise. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
104 lines
3.4 KiB
TypeScript
104 lines
3.4 KiB
TypeScript
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[];
|
|
}
|
|
|
|
const ASSETS_BASE_URL = "https://assets.xtablo.com";
|
|
|
|
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: `${ASSETS_BASE_URL}/org-icons/${orgId}/icon-192.png`, sizes: "192x192", type: "image/png" },
|
|
{ src: `${ASSETS_BASE_URL}/org-icons/${orgId}/icon-512.png`, sizes: "512x512", type: "image/png" },
|
|
{ src: `${ASSETS_BASE_URL}/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: Request) {
|
|
const url = new URL(request.url);
|
|
|
|
// Serve org-specific or default PWA icons
|
|
// Static files are renamed to default-* so the worker handles all requests for these paths
|
|
const iconMap: Record<string, { orgFile: string; defaultFile: string }> = {
|
|
"/pwa-icons/apple-touch-icon-180x180.png": { orgFile: "icon-180.png", defaultFile: "/pwa-icons/default-apple-touch-icon-180x180.png" },
|
|
"/pwa-icons/favicon-32x32.png": { orgFile: "icon-32.png", defaultFile: "/pwa-icons/default-favicon-32x32.png" },
|
|
"/pwa-icons/favicon-16x16.png": { orgFile: "icon-16.png", defaultFile: "/pwa-icons/default-favicon-16x16.png" },
|
|
};
|
|
if (iconMap[url.pathname]) {
|
|
const cookieHeader = request.headers.get("cookie");
|
|
const orgId = parseOrgIdFromCookie(cookieHeader);
|
|
const entry = iconMap[url.pathname];
|
|
if (orgId !== null) {
|
|
return Response.redirect(`${ASSETS_BASE_URL}/org-icons/${orgId}/${entry.orgFile}`, 302);
|
|
}
|
|
// No cookie — serve default icon
|
|
return Response.redirect(`${url.origin}${entry.defaultFile}`, 302);
|
|
}
|
|
|
|
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 });
|
|
},
|
|
};
|