如何让Service Worker在特定页面仅返回缓存响应并实现工单离线功能?
Got it, let’s break down how to implement this offline-capable ticket portal step by step—this is a classic progressive web app (PWA) use case, so we’ll combine Service Workers, client-side storage, and intentional state management to make it work smoothly.
First, we’ll need three key pieces to pull this off:
- Service Worker: Handles caching for specific pages and intercepts network requests.
- Client-Side Storage: Use IndexedDB (or a friendly wrapper like
localForage) to store checked-out tickets, offline edits, and new draft tickets. - State Management: Track online/offline status, ticket sync state, and handle reconnection logic.
Step 1: Handle "Check Out" Action (Online Only)
When a user clicks to check out a ticket while online:
- Fetch the full ticket data from your backend API.
- Store it in IndexedDB with a
checked-outstatus and alastSyncedtimestamp to track when it was pulled from the server. - Update the UI to show the ticket is available offline.
Example code using localForage (simplified):
async function checkOutTicket(ticketId) { if (!navigator.onLine) { alert("You need to be online to check out a ticket!"); return; } // Fetch ticket from API const response = await fetch(`/api/tickets/${ticketId}`); const ticketData = await response.json(); // Store in local storage with checkout metadata await localForage.setItem(`ticket-${ticketId}`, { ...ticketData, status: "checked-out", lastSynced: new Date().toISOString(), needsSync: false }); }
Step 2: Offline View & Edit
When the app detects it’s offline (via navigator.onLine or navigator.connection):
- Load checked-out tickets directly from IndexedDB instead of hitting the API.
- For edits, save changes locally and mark the ticket with
needsSync: trueto flag it for later synchronization.
Example edit logic:
async function saveOfflineTicketEdit(ticketId, updatedFields) { const existingTicket = await localForage.getItem(`ticket-${ticketId}`); const updatedTicket = { ...existingTicket, ...updatedFields, needsSync: true, lastEdited: new Date().toISOString() }; await localForage.setItem(`ticket-${ticketId}`, updatedTicket); // Update UI to confirm edit saved offline }
Step 1: Create Tickets Offline
When offline, generate a temporary local ID (using crypto.randomUUID() for uniqueness) and store the new ticket in IndexedDB with status: "new-ticket" and needsSync: true.
Example:
async function createOfflineTicket(ticketDetails) { const localId = crypto.randomUUID(); const newTicket = { id: localId, ...ticketDetails, status: "new-ticket", needsSync: true, createdAt: new Date().toISOString() }; await localForage.setItem(`ticket-${localId}`, newTicket); return localId; // Return local ID for UI tracking }
Step 2: Sync on Reconnection
Listen for the online event to trigger synchronization when the user gets back online:
- Iterate through all tickets in IndexedDB marked with
needsSync: true. - For existing checked-out tickets: Send a PUT request to your API with the updated data. On success, clear the
needsSyncflag and updatelastSynced. - For new tickets: Send a POST request to your API. On success, replace the local ID with the server-generated ID, update the ticket status, and clear
needsSync.
Example sync logic:
async function syncOfflineTickets() { if (!navigator.onLine) return; const allTickets = await localForage.keys(); for (const key of allTickets) { const ticket = await localForage.getItem(key); if (!ticket.needsSync) continue; try { if (ticket.status === "new-ticket") { // Create new ticket on server const response = await fetch("/api/tickets", { method: "POST", body: JSON.stringify(ticket), headers: { "Content-Type": "application/json" } }); const serverTicket = await response.json(); // Replace local ticket with server's version await localForage.removeItem(key); await localForage.setItem(`ticket-${serverTicket.id}`, { ...serverTicket, needsSync: false, lastSynced: new Date().toISOString() }); } else if (ticket.status === "checked-out") { // Update existing ticket on server await fetch(`/api/tickets/${ticket.id}`, { method: "PUT", body: JSON.stringify(ticket), headers: { "Content-Type": "application/json" } }); // Mark as synced await localForage.setItem(key, { ...ticket, needsSync: false, lastSynced: new Date().toISOString() }); } } catch (error) { // Handle sync failure (e.g., retry later, notify user) console.error(`Failed to sync ticket ${ticket.id}:`, error); } } } // Listen for reconnection window.addEventListener("online", syncOfflineTickets);
To make specific pages always return cached responses (no network requests), configure your Service Worker to intercept those requests and serve from cache exclusively.
Step 1: Pre-Cache Pages During Installation
First, cache the target pages and their dependencies (CSS, JS) when the Service Worker installs:
self.addEventListener("install", (event) => { event.waitUntil( caches.open("offline-ticket-pages-v1").then((cache) => { return cache.addAll([ "/ticket/offline-dashboard", "/ticket/checked-out", "/css/offline-ticket.css", "/js/offline-ticket.js" // Add any other assets these pages need ]); }) ); });
Step 2: Intercept Requests for Cache-Only Pages
In the fetch event, check if the request is for one of your target pages. If so, serve the cached response directly—skip the network entirely. If the page isn’t cached (e.g., first load while offline), return a friendly error or fallback page.
self.addEventListener("fetch", (event) => { const requestUrl = new URL(event.request.url); // Define your cache-only page paths const cacheOnlyPaths = ["/ticket/offline-dashboard", "/ticket/checked-out"]; if (cacheOnlyPaths.some(path => requestUrl.pathname.startsWith(path))) { event.respondWith( caches.match(event.request).then((cachedResponse) => { // Return cached response if available; else, return a fallback return cachedResponse || new Response( "<h1>Offline Page Unavailable</h1><p>Please check your connection or try again later.</p>", { status: 503, headers: { "Content-Type": "text/html" } } ); }) ); } });
- Conflict Resolution: If a ticket was modified on the server while the user edited it offline, you’ll need logic to resolve conflicts (e.g., show both versions to the user, auto-merge non-overlapping changes).
- Storage Limits: IndexedDB has browser-specific storage limits. Add logic to clean up old, synced tickets or notify users when storage is low.
- Service Worker Updates: When you update your Service Worker, make sure to handle cache invalidation so users get the latest versions of your cache-only pages.
- UI Feedback: Clearly indicate when the app is in offline mode, when edits are saved locally, and when sync is in progress.
内容的提问来源于stack exchange,提问作者Ryan Griggs

