From cf8d92afeaf031980296da10340674477c339f4f Mon Sep 17 00:00:00 2001 From: Rosa Gutierrez Date: Wed, 28 Jan 2026 21:30:50 +0100 Subject: [PATCH] Enable offline caching for all web users Previously, offline caching was conditionally enabled only for Hotwire Native apps. This removes that restriction and enables offline support for all users, including PWAs and regular browsers. This Uses the new `fetchOptions` support in `TurboOffline` handlers to pass `cache: "no-cache"` for document fetches, which we need to work around a quite annoying Safari PWA bug (see https://github.com/basecamp/fizzy/pull/1014). Also, simplify a bit the cache names and remove the `misc` one. Co-Authored-By: Claude Opus 4.5 --- Gemfile.lock | 2 +- Gemfile.saas.lock | 2 +- app/views/pwa/service_worker.js.erb | 83 +++++++++++++++-------------- 3 files changed, 44 insertions(+), 43 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index a17769c07..3cad12c54 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -13,7 +13,7 @@ GIT GIT remote: https://github.com/hotwired/turbo-rails.git - revision: 45a2a40be0a677808a3a269311f1b5abaa3b30ba + revision: 9bcc244043b16ef0202f8fe93aaeb246d841f9eb branch: offline-cache specs: turbo-rails (2.0.21) diff --git a/Gemfile.saas.lock b/Gemfile.saas.lock index 03c8e9141..3517a420e 100644 --- a/Gemfile.saas.lock +++ b/Gemfile.saas.lock @@ -62,7 +62,7 @@ GIT GIT remote: https://github.com/hotwired/turbo-rails.git - revision: 45a2a40be0a677808a3a269311f1b5abaa3b30ba + revision: 9bcc244043b16ef0202f8fe93aaeb246d841f9eb branch: offline-cache specs: turbo-rails (2.0.21) diff --git a/app/views/pwa/service_worker.js.erb b/app/views/pwa/service_worker.js.erb index b3f59aa60..cfdf5468f 100644 --- a/app/views/pwa/service_worker.js.erb +++ b/app/views/pwa/service_worker.js.erb @@ -1,60 +1,61 @@ importScripts("<%= javascript_url("turbo-offline-umd.min") %>") -<% if hotwire_native_app? %> - TurboOffline.addRule({ - match: /\/assets\/.+[-\.][0-9a-f]+\.(js|css|svg|png|jpg|webp|woff2?|ico)$/, - handler: TurboOffline.handlers.cacheFirst({ - cacheName: "assets", - maxAge: 60 * 60 * 24 * 7 - }) +// Documents - top-level navigation requests only +// We need `cache: "no-cache"` here to work around +// an annoying Safari PWA bug that predates offline mode +TurboOffline.addRule({ + match: (request) => request.destination === "document", + handler: TurboOffline.handlers.networkFirst({ + cacheName: "main", + maxAge: 60 * 60 * 24 * 3, + networkTimeout: 3, + fetchOptions: { cache: "no-cache" } }) +}) - TurboOffline.addRule({ - match: /\/(boards|cards|users)\//, - except: /\/(edit|pin|watch|new)$/, - handler: TurboOffline.handlers.networkFirst({ - cacheName: "cards", - maxAge: 60 * 60 * 24 * 3, - networkTimeout: 2 - }) +TurboOffline.addRule({ + match: /\/assets\/.+[-\.][0-9a-f]+\.(js|css|svg|png|jpg|webp|woff2?|ico)$/, + handler: TurboOffline.handlers.cacheFirst({ + cacheName: "assets", + maxAge: 60 * 60 * 24 * 7 }) +}) - TurboOffline.addRule({ - match: /\/rails\/active_storage\//, - handler: TurboOffline.handlers.networkFirst({ - cacheName: "storage", - maxAge: 60 * 60 * 24 * 7, - networkTimeout: 2 - }) +TurboOffline.addRule({ + match: /\/(boards|cards|users)\//, + except: /\/(edit|pin|watch|new)$/, + handler: TurboOffline.handlers.networkFirst({ + cacheName: "main", + maxAge: 60 * 60 * 24 * 3, + networkTimeout: 2 }) +}) - // Everything else - TurboOffline.addRule({ - handler: TurboOffline.handlers.networkFirst({ - cacheName: "misc", - maxAge: 60 * 60 * 24, - networkTimeout: 3 - }) +TurboOffline.addRule({ + match: /\/rails\/active_storage\//, + handler: TurboOffline.handlers.networkFirst({ + cacheName: "storage", + maxAge: 60 * 60 * 24 * 7, + networkTimeout: 2 }) +}) - TurboOffline.start() -<% else %> - self.addEventListener("fetch", (event) => { - if (event.request.method !== "GET") return - - if (event.request.destination === "document") { - event.respondWith( - fetch(event.request, { cache: "no-cache" }) - .catch(() => caches.match(event.request)) - ) - } +// Everything else +TurboOffline.addRule({ + handler: TurboOffline.handlers.networkFirst({ + cacheName: "main", + maxAge: 60 * 60 * 24, + networkTimeout: 3 }) -<% end %> +}) self.addEventListener("activate", (event) => { event.waitUntil(self.clients.claim()) }) +TurboOffline.start() + + self.addEventListener("push", (event) => { const data = event.data.json() event.waitUntil(Promise.all([ showNotification(data), updateBadgeCount(data.options) ]))