Take a first stab at offline mode support

This commit is contained in:
Rosa Gutierrez
2026-01-05 12:10:57 +01:00
parent 5d084a46ce
commit ada4688c4e
7 changed files with 96 additions and 47 deletions
+2 -2
View File
@@ -13,10 +13,10 @@ GIT
GIT
remote: https://github.com/hotwired/turbo-rails.git
revision: e3795ef6aac6a3d18039751febe6a301cfe71a7b
revision: 49be3f2d43edeeedad48cb492b35242fdb27af74
branch: offline-cache
specs:
turbo-rails (2.0.20)
turbo-rails (2.0.21)
actionpack (>= 7.1.0)
railties (>= 7.1.0)
+10 -4
View File
@@ -60,6 +60,15 @@ GIT
rails (>= 6.1)
yabeda (~> 0.6)
GIT
remote: https://github.com/hotwired/turbo-rails.git
revision: 49be3f2d43edeeedad48cb492b35242fdb27af74
branch: offline-cache
specs:
turbo-rails (2.0.21)
actionpack (>= 7.1.0)
railties (>= 7.1.0)
GIT
remote: https://github.com/rails/rails.git
revision: 12e24eaf2f0a9613e015653f013dd131317d9bf5
@@ -633,9 +642,6 @@ GEM
trilogy (2.10.0)
bigdecimal
tsort (0.2.0)
turbo-rails (2.0.23)
actionpack (>= 7.1.0)
railties (>= 7.1.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (3.2.0)
@@ -752,7 +758,7 @@ DEPENDENCIES
stripe (~> 18.0)
thruster
trilogy (~> 2.10)
turbo-rails
turbo-rails!
useragent!
vcr
web-console!
+1
View File
@@ -1,2 +1,3 @@
import "initializers/current"
import "initializers/bridge/bridge_element"
import "initializers/offline"
+6
View File
@@ -0,0 +1,6 @@
import { Turbo } from "@hotwired/turbo-rails"
Turbo.offline.start("/service-worker.js", {
scope: "/",
native: true
})
-41
View File
@@ -1,41 +0,0 @@
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))
)
}
})
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)
}
}
+76
View File
@@ -0,0 +1,76 @@
importScripts("<%= javascript_url("turbo-offline-umd.min") %>")
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: (request) => {
const url = request.url
return /\/(boards|cards|users)\//.test(url) && !/\/(edit|pin|watch|new)$/.test(url)
},
handler: TurboOffline.handlers.networkFirst({
cacheName: "cards",
maxAge: 60 * 60 * 24 * 3,
networkTimeout: 3
})
})
TurboOffline.addRule({
match: /\/rails\/active_storage\//,
handler: TurboOffline.handlers.networkFirst({
cacheName: "storage",
maxAge: 60 * 60 * 24 * 7,
networkTimeout: 3
})
})
// Everything else
TurboOffline.addRule({
handler: TurboOffline.handlers.networkFirst({
cacheName: "misc",
maxAge: 60 * 60 * 24,
networkTimeout: 3
})
})
TurboOffline.start()
self.addEventListener('activate', (event) => {
event.waitUntil(self.clients.claim())
})
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)
}
}
+1
View File
@@ -2,6 +2,7 @@
pin "application"
pin "@hotwired/turbo-rails", to: "turbo.min.js"
pin "@hotwired/turbo/offline", to: "turbo-offline.min.js"
pin "@hotwired/stimulus", to: "stimulus.min.js"
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
pin "@hotwired/hotwire-native-bridge", to: "@hotwired--hotwire-native-bridge.js"