Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
Blame · Line-by-line history

pwa.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

pwa.tsBlame114 lines · 1 contributor
eae38d1Claude1/**
2 * Block G1 — PWA (progressive web app) support.
3 *
4 * GET /manifest.webmanifest — app manifest (install prompt)
5 * GET /sw.js — service worker (cache-first for static, network-first for HTML)
6 * GET /icon.svg — monochrome logo used by the manifest
7 *
8 * The service worker deliberately keeps the cache small (static CSS-in-JS is
9 * inlined so there's nothing to cache beyond the manifest + icon). HTML pages
10 * fall through to the network; cached copies only serve offline fallback.
11 *
12 * Adding `<link rel="manifest" href="/manifest.webmanifest">` + a tiny SW
13 * registration snippet to `Layout` turns any repo page into an installable
14 * PWA on Chrome/Safari.
15 */
16
17import { Hono } from "hono";
18
19const pwa = new Hono();
20
21export const MANIFEST = {
22 name: "Gluecron",
23 short_name: "Gluecron",
24 description: "AI-native code intelligence + git hosting",
25 start_url: "/",
26 scope: "/",
27 display: "standalone",
28 background_color: "#0d1117",
29 theme_color: "#0d1117",
30 icons: [
31 {
32 src: "/icon.svg",
33 sizes: "any",
34 type: "image/svg+xml",
35 purpose: "any maskable",
36 },
37 ],
38 categories: ["developer", "productivity"],
39} as const;
40
41pwa.get("/manifest.webmanifest", (c) => {
42 c.header("content-type", "application/manifest+json");
43 c.header("cache-control", "public, max-age=3600");
44 return c.body(JSON.stringify(MANIFEST));
45});
46
47const ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
48 <rect width="128" height="128" rx="24" fill="#0d1117"/>
49 <g fill="#58a6ff" font-family="monospace" font-size="58" font-weight="700" text-anchor="middle">
50 <text x="64" y="82">gc</text>
51 </g>
52 <circle cx="28" cy="28" r="5" fill="#3fb950"/>
53</svg>`;
54
55pwa.get("/icon.svg", (c) => {
56 c.header("content-type", "image/svg+xml");
57 c.header("cache-control", "public, max-age=86400, immutable");
58 return c.body(ICON_SVG);
59});
60
61/**
e1c7aa6Claude62 * Bare-bones service worker — v4 NUKE EDITION.
63 *
64 * After repeated reports of stale HTML being served from old cache versions,
65 * this SW does ONE job: unregister itself and purge every cache. Browsers
66 * that previously installed v1/v2/v3 will load this v4, see the unregister
67 * call, and stop intercepting fetches. From now on EVERY page load goes
68 * straight to the network — no SW, no cache, instant fresh content on push.
69 *
70 * Once we trust the auto-deploy pipeline + want offline support back, ship
71 * a real SW with conservative network-first behaviour. Until then: instant
72 * deploys win.
eae38d1Claude73 */
e1c7aa6Claude74export const SERVICE_WORKER_SRC = `// gluecron service worker — v4 (self-nuke)
75self.addEventListener('install', () => self.skipWaiting());
eae38d1Claude76self.addEventListener('activate', (e) => {
e1c7aa6Claude77 e.waitUntil((async () => {
78 // Purge every cache from any prior SW version
79 const keys = await caches.keys();
80 await Promise.all(keys.map((k) => caches.delete(k)));
81 // Tell every client we're done so they reload onto clean network state
82 const clients = await self.clients.matchAll({ type: 'window' });
83 for (const c of clients) c.navigate(c.url).catch(() => {});
84 // Then unregister this SW so future loads skip the SW layer entirely
85 await self.registration.unregister();
86 })());
eae38d1Claude87});
e1c7aa6Claude88// No fetch handler — every request goes straight to the network.
eae38d1Claude89`;
90
91pwa.get("/sw.js", (c) => {
92 c.header("content-type", "application/javascript");
e1c7aa6Claude93 // No-cache: browser must check on every page load. Critical for the v4
94 // self-nuke SW to actually reach all returning visitors.
95 c.header("cache-control", "no-cache, no-store, must-revalidate");
96 c.header("pragma", "no-cache");
eae38d1Claude97 // Service-Worker-Allowed required for root-scope SW served from root
98 c.header("service-worker-allowed", "/");
99 return c.body(SERVICE_WORKER_SRC);
100});
101
102/**
103 * Inline script registering the SW. Loaded once at the bottom of every page.
104 * Kept tiny so we don't bloat TTI.
105 */
106export const PWA_REGISTER_SNIPPET = `
107if ('serviceWorker' in navigator) {
108 window.addEventListener('load', function() {
109 navigator.serviceWorker.register('/sw.js').catch(function() {});
110 });
111}
112`.trim();
113
114export default pwa;