Files
fizzy/app/views/pwa/service_worker.js.erb
T
Rosa Gutierrez 67d629f722 Use CORS fetch mode for Active Storage to enable maxEntrySize checks
Otherwise, for resources like images loaded via <img> tags, the browser
sets `mode: "no-cors"` (as these aren't CORS requests), so the service
worker gets an opaque response even though the server sends CORS
headers.

We could upgrade all no-cors requests to cors mode, sending a `mode:
"cors"` request to a server that doesn't send CORS headers will fail
entirely: the `fetch` call throws a `TypeError` (network error), and the
browser blocks the response. So the resource wouldn't load at all, not
even as opaque. We wouldn't be able to cache it at all.

By opting in via `fetchOptions`, we can do it only for rules where we
know the server will send CORS headers.
2026-03-10 10:20:54 +01:00

98 lines
2.6 KiB
Plaintext

importScripts("<%= javascript_url("turbo-offline-umd.min") %>")
// 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",
except: /\/(edit|pin|watch|new)$/,
handler: TurboOffline.handlers.networkFirst({
cacheName: "main",
maxAge: 60 * 60 * 24 * 3,
networkTimeout: 3,
fetchOptions: { cache: "no-cache" },
maxEntrySize: 1024 * 1024
})
})
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,
maxEntrySize: 1024 * 1024
})
})
TurboOffline.addRule({
match: /\/(boards|cards|users)\//,
except: /\/(edit|pin|watch|new)$/,
handler: TurboOffline.handlers.networkFirst({
cacheName: "main",
maxAge: 60 * 60 * 24 * 3,
networkTimeout: 2,
maxEntrySize: 1024 * 1024
})
})
TurboOffline.addRule({
match: /\/rails\/active_storage\//,
handler: TurboOffline.handlers.networkFirst({
cacheName: "storage",
maxAge: 60 * 60 * 24 * 7,
networkTimeout: 2,
maxEntrySize: 2 * 1024 * 1024, // 2MB covers about 95% of all Fizzy blobs
maxEntries: 500,
fetchOptions: { mode: "cors", credentials: "omit" }
})
})
// Everything else
TurboOffline.addRule({
except: /\/(service-worker\.js|edit|pin|watch|new)$/,
handler: TurboOffline.handlers.networkFirst({
cacheName: "main",
maxAge: 60 * 60 * 24,
networkTimeout: 3,
maxEntrySize: 1024 * 1024
})
})
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) ]))
})
async function showNotification({ title, options }) {
return self.registration.showNotification(title, options)
}
async function updateBadgeCount({ data: { badge } }) {
return self.navigator.setAppBadge?.(badge || 0)
}
self.addEventListener("notificationclick", (event) => {
event.notification.close()
const url = new URL(event.notification.data.url, self.location.origin).href
event.waitUntil(openURL(url))
})
async function openURL(url) {
const clients = await self.clients.matchAll({ type: "window" })
const focused = clients.find((client) => client.focused)
if (focused) {
await focused.navigate(url)
} else {
await self.clients.openWindow(url)
}
}