feat(app): add offline mode composable and enhanced PWA caching

Create useOffline.ts with online/offline detection, sync queue for
pending actions, and auto-process on reconnect. Enhance vite PWA config
with CacheFirst for TMDB and Wikipedia images, StaleWhileRevalidate for
TMDB API responses, with size and TTL limits.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-03 21:08:50 +00:00
co-authored by Claude Opus 4.6
parent a442fbbe62
commit ea5e82bfe3
2 changed files with 99 additions and 0 deletions
@@ -0,0 +1,75 @@
import { ref, onMounted, onUnmounted } from 'vue'
export interface SyncAction {
type: string
payload: unknown
timestamp: number
}
const isOnline = ref(navigator.onLine)
const pendingSync = ref(0)
const syncQueue: SyncAction[] = []
function handleOnline() {
isOnline.value = true
processSyncQueue()
}
function handleOffline() {
isOnline.value = false
}
/** Queue an action to be synced when back online */
function queueForSync(action: SyncAction) {
syncQueue.push(action)
pendingSync.value = syncQueue.length
}
/** Process all pending sync actions */
async function processSyncQueue(): Promise<void> {
if (!isOnline.value || syncQueue.length === 0) return
while (syncQueue.length > 0) {
const action = syncQueue.shift()
if (!action) break
try {
// Route sync actions based on type
if (action.type === 'conversation') {
// Conversation sync handled by idb-storage auto-save
continue
}
if (action.type === 'favorite') {
// Favorites are local-only, no sync needed
continue
}
// Add more sync handlers as needed
} catch {
// Re-queue failed actions
syncQueue.unshift(action)
break
}
}
pendingSync.value = syncQueue.length
}
export function useOffline() {
onMounted(() => {
window.addEventListener('online', handleOnline)
window.addEventListener('offline', handleOffline)
isOnline.value = navigator.onLine
})
onUnmounted(() => {
window.removeEventListener('online', handleOnline)
window.removeEventListener('offline', handleOffline)
})
return {
isOnline,
pendingSync,
queueForSync,
processSyncQueue,
}
}
+24
View File
@@ -70,6 +70,30 @@ export default defineConfig({
urlPattern: /\/api\/rss-articles\?.*/i,
handler: 'NetworkOnly',
},
{
urlPattern: /\/api\/tmdb\/.*/i,
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'tmdb-cache',
expiration: { maxEntries: 200, maxAgeSeconds: 86400 },
},
},
{
urlPattern: /^https:\/\/image\.tmdb\.org\/.*/i,
handler: 'CacheFirst',
options: {
cacheName: 'tmdb-images',
expiration: { maxEntries: 500, maxAgeSeconds: 604800 },
},
},
{
urlPattern: /^https:\/\/upload\.wikimedia\.org\/.*/i,
handler: 'CacheFirst',
options: {
cacheName: 'wiki-images',
expiration: { maxEntries: 200, maxAgeSeconds: 604800 },
},
},
],
},
devOptions: {