67d629f722
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.
98 lines
2.6 KiB
Plaintext
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)
|
|
}
|
|
}
|