aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/public
diff options
context:
space:
mode:
authorAriel Costas Guerrero <ariel@costas.dev>2025-08-06 21:52:21 +0200
committerAriel Costas Guerrero <ariel@costas.dev>2025-08-06 21:52:21 +0200
commitebfb7c1c8bc0a9ec50bde72eb9a0859c6e5dcee5 (patch)
tree35353c15726d7d036907df731b00d390c1d1f538 /src/frontend/public
parent5cc27f852b02446659e0ab85305916c9f5e5a5f0 (diff)
Fix this fucking pile of steaming garbage
Diffstat (limited to 'src/frontend/public')
-rw-r--r--src/frontend/public/manifest.webmanifest2
-rw-r--r--src/frontend/public/pwa-worker.js145
-rw-r--r--src/frontend/public/sw.js153
3 files changed, 146 insertions, 154 deletions
diff --git a/src/frontend/public/manifest.webmanifest b/src/frontend/public/manifest.webmanifest
index 619dfc6..fe0cf39 100644
--- a/src/frontend/public/manifest.webmanifest
+++ b/src/frontend/public/manifest.webmanifest
@@ -4,7 +4,7 @@
"name": "UrbanoVigo Web",
"description": "Aplicación web para encontrar paradas y tiempos de llegada de los autobuses urbanos de Vigo, España.",
"short_name": "UrbanoVigo",
- "start_url": "/",
+ "start_url": "/stops",
"display": "standalone",
"orientation": "portrait-primary",
"lang": "es",
diff --git a/src/frontend/public/pwa-worker.js b/src/frontend/public/pwa-worker.js
new file mode 100644
index 0000000..f47ede0
--- /dev/null
+++ b/src/frontend/public/pwa-worker.js
@@ -0,0 +1,145 @@
+const CACHE_VERSION = "2025-0806a";
+const API_CACHE_NAME = `api-cache-${CACHE_VERSION}`;
+const STATIC_CACHE_NAME = `static-cache-${CACHE_VERSION}`;
+
+const API_URL_PATTERN = /\/api\/(GetStopList|GetStopEstimates|GetStopTimetable)/;
+const API_MAX_AGE = 24 * 60 * 60 * 1000; // 24h
+const ESTIMATES_MIN_AGE = 15 * 1000;
+const ESTIMATES_MAX_AGE = 30 * 1000;
+
+self.addEventListener("install", (event) => {
+ console.log("SW: Installing new version");
+ event.waitUntil(
+ caches.open(STATIC_CACHE_NAME).then((cache) =>
+ cache.addAll([
+ "/favicon.ico",
+ "/logo-256.png",
+ "/logo-512.jpg",
+ "/stops.json"
+ ])
+ ).then(() => self.skipWaiting())
+ );
+});
+
+self.addEventListener("activate", (event) => {
+ console.log("SW: Activating new version");
+ event.waitUntil(
+ (async () => {
+ const cacheNames = await caches.keys();
+ await Promise.all(
+ cacheNames.map((name) => {
+ if (name !== API_CACHE_NAME && name !== STATIC_CACHE_NAME) {
+ console.log("SW: Deleting old cache:", name);
+ return caches.delete(name);
+ }
+ })
+ );
+ await self.clients.claim();
+ const clients = await self.clients.matchAll();
+ clients.forEach((client) =>
+ client.postMessage({ type: "SW_UPDATED" })
+ );
+ })()
+ );
+});
+
+self.addEventListener("fetch", (event) => {
+ const request = event.request;
+ const url = new URL(request.url);
+
+ // Ignore requests with unsupported schemes
+ if (!url.protocol.startsWith('http')) {
+ return;
+ }
+
+ // API
+ if (request.method === "GET" && API_URL_PATTERN.test(url.pathname)) {
+ event.respondWith(handleApiRequest(request));
+ return;
+ }
+
+ // Navegación -> network first
+ if (request.mode === "navigate") {
+ event.respondWith(handleNavigationRequest(request));
+ return;
+ }
+
+ // Estáticos -> cache first
+ if (request.method === "GET") {
+ event.respondWith(handleStaticRequest(request));
+ }
+});
+
+async function handleApiRequest(request) {
+ const url = new URL(request.url);
+ const isEstimates = url.pathname.includes("GetStopEstimates");
+ const maxAge = isEstimates
+ ? ESTIMATES_MIN_AGE + Math.random() * (ESTIMATES_MAX_AGE - ESTIMATES_MIN_AGE)
+ : API_MAX_AGE;
+
+ const cache = await caches.open(API_CACHE_NAME);
+ const cachedResponse = await cache.match(request);
+
+ if (cachedResponse) {
+ const dateHeader = cachedResponse.headers.get("date");
+ const age = dateHeader ? Date.now() - new Date(dateHeader).getTime() : 0;
+ if (age && age < maxAge) {
+ console.debug("SW: Cache HIT", request.url);
+ return cachedResponse;
+ }
+ cache.delete(request);
+ }
+
+ try {
+ const netResponse = await fetch(request);
+ if (netResponse.ok) cache.put(request, netResponse.clone());
+ return netResponse;
+ } catch (error) {
+ if (cachedResponse) return cachedResponse;
+ throw error;
+ }
+}
+
+async function handleStaticRequest(request) {
+ const cache = await caches.open(STATIC_CACHE_NAME);
+ const cachedResponse = await cache.match(request);
+ if (cachedResponse) return cachedResponse;
+
+ try {
+ const netResponse = await fetch(request);
+ if (netResponse.ok) cache.put(request, netResponse.clone());
+ return netResponse;
+ } catch (err) {
+ return new Response("Offline asset not available", {
+ status: 503,
+ headers: { "Content-Type": "text/plain" }
+ });
+ }
+}
+
+async function handleNavigationRequest(request) {
+ try {
+ const netResponse = await fetch(request);
+ return netResponse;
+ } catch (err) {
+ // Si no hay red, intenta fallback con caché estática
+ const cache = await caches.open(STATIC_CACHE_NAME);
+ const offline = await cache.match("/stops.json");
+ return (
+ offline ||
+ new Response("App offline", {
+ status: 503,
+ headers: { "Content-Type": "text/plain" }
+ })
+ );
+ }
+}
+
+self.addEventListener("message", (event) => {
+ if (event.data?.type === "SKIP_WAITING") self.skipWaiting();
+ if (event.data?.type === "CLEAR_CACHE") {
+ event.waitUntil(
+ caches.keys().then((names) => Promise.all(names.map((n) => caches.delete(n))))
+ );
+ }
+});
diff --git a/src/frontend/public/sw.js b/src/frontend/public/sw.js
deleted file mode 100644
index ca826f6..0000000
--- a/src/frontend/public/sw.js
+++ /dev/null
@@ -1,153 +0,0 @@
-const CACHE_VERSION = "v2";
-const API_CACHE_NAME = `api-cache-${CACHE_VERSION}`;
-const STATIC_CACHE_NAME = `static-cache-${CACHE_VERSION}`;
-const API_URL_PATTERN = /\/api\/(GetStopList|GetStopEstimates|GetStopTimetable)/;
-const API_MAX_AGE = 24 * 60 * 60 * 1000; // 24 hours
-const ESTIMATES_MIN_AGE = 15 * 1000; // 15 seconds minimum
-const ESTIMATES_MAX_AGE = 30 * 1000; // 30 seconds maximum
-
-self.addEventListener("install", (event) => {
- console.log("SW: Installing new version");
- event.waitUntil(
- caches.open(STATIC_CACHE_NAME).then((cache) => {
- return cache.addAll([
- "/",
- "/manifest.webmanifest",
- "/stops.json",
- "/favicon.ico",
- "/logo-256.png",
- "/logo-512.jpg"
- ]);
- }).then(() => {
- return self.skipWaiting();
- })
- );
-});
-
-self.addEventListener("activate", (event) => {
- console.log("SW: Activating new version");
- event.waitUntil(
- Promise.all([
- // Clean up old caches
- caches.keys().then((cacheNames) => {
- return Promise.all(
- cacheNames.map((cacheName) => {
- if (cacheName !== API_CACHE_NAME && cacheName !== STATIC_CACHE_NAME) {
- console.log("SW: Deleting old cache:", cacheName);
- return caches.delete(cacheName);
- }
- })
- );
- }),
- // Take control of all clients
- self.clients.claim(),
- // Notify clients about the update
- self.clients.matchAll().then((clients) => {
- clients.forEach((client) => {
- client.postMessage({ type: "SW_UPDATED" });
- });
- })
- ])
- );
-});
-
-self.addEventListener("fetch", async (event) => {
- const url = new URL(event.request.url);
-
- // Handle API requests with caching
- if (event.request.method === "GET" && API_URL_PATTERN.test(url.pathname)) {
- event.respondWith(handleApiRequest(event.request));
- return;
- }
-
- // Handle static assets
- if (event.request.method === "GET") {
- event.respondWith(handleStaticRequest(event.request));
- return;
- }
-});
-
-async function handleApiRequest(request) {
- const url = new URL(request.url);
- const isEstimates = url.pathname.includes("GetStopEstimates");
- // Random cache age between 15-30 seconds for estimates to prevent thundering herd
- const maxAge = isEstimates
- ? ESTIMATES_MIN_AGE + Math.random() * (ESTIMATES_MAX_AGE - ESTIMATES_MIN_AGE)
- : API_MAX_AGE;
-
- const cache = await caches.open(API_CACHE_NAME);
- const cachedResponse = await cache.match(request);
-
- if (cachedResponse) {
- const age = Date.now() - new Date(cachedResponse.headers.get("date")).getTime();
- if (age < maxAge) {
- console.debug(`SW: Cache HIT for ${request.url}`);
- return cachedResponse;
- }
- // Cache is too old, fetch a fresh copy
- cache.delete(request);
- }
-
- try {
- const netResponse = await fetch(request);
-
- if (netResponse.ok) {
- const responseToCache = netResponse.clone();
- cache.put(request, responseToCache);
- console.debug(`SW: Cache MISS for ${request.url}`);
- }
-
- return netResponse;
- } catch (error) {
- // If network fails and we have a cached response (even if old), return it
- if (cachedResponse) {
- console.debug(`SW: Network failed, returning stale cache for ${request.url}`);
- return cachedResponse;
- }
- throw error;
- }
-}
-
-async function handleStaticRequest(request) {
- const cache = await caches.open(STATIC_CACHE_NAME);
- const cachedResponse = await cache.match(request);
-
- if (cachedResponse) {
- return cachedResponse;
- }
-
- try {
- const netResponse = await fetch(request);
- if (netResponse.ok) {
- cache.put(request, netResponse.clone());
- }
- return netResponse;
- } catch (error) {
- // Return a basic offline page for navigation requests
- if (request.mode === 'navigate') {
- return new Response('App is offline', {
- status: 503,
- statusText: 'Service Unavailable',
- headers: { 'Content-Type': 'text/plain' }
- });
- }
- throw error;
- }
-}
-
-// Handle messages from the main thread
-self.addEventListener("message", (event) => {
- if (event.data && event.data.type === "SKIP_WAITING") {
- self.skipWaiting();
- }
-
- if (event.data && event.data.type === "CLEAR_CACHE") {
- event.waitUntil(
- caches.keys().then((cacheNames) => {
- return Promise.all(
- cacheNames.map((cacheName) => caches.delete(cacheName))
- );
- })
- );
- }
-});