// Parse the parameters in the current script's own URL const scriptUrl = new URL(self.location); // Get the incoming version, and provide a default value if it doesn't exist const version = scriptUrl.searchParams.get("v") || "default-version"; // Dynamically concatenate CACHE_NAME const CACHE_NAME = `risk-guard-cache-${version}`; console.log("Current Service Worker CACHE_NAME:", CACHE_NAME); // 例如:umi.1a2b3c4d.js, logo.7b3e198f.png const HASH_REGEX = /\.[0-9a-f]{8}\.(js|css|png|jpe?g|gif|webp|svg|ico|woff2?|eot|ttf)$/i; // 1. Installation phase: forcibly skip waiting and directly enter the activation state self.addEventListener("install", (event) => { self.skipWaiting(); }); // 2. Activation phase: clean up old caches and immediately take control of the page self.addEventListener("activate", (event) => { event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames.map((cache) => { if (cache !== CACHE_NAME) { console.log("Cleaning old cache:", cache); return caches.delete(cache); } }), ); }), ); self.clients.claim(); // Immediately control all open clients (tabs) }); // 3. Fetch event: this is the key to trigger the installation icon to be displayed self.addEventListener("fetch", (event) => { const { request } = event; const url = new URL(request.url); // If not a GET request, skip processing and go straight to the cache if (request.method !== "GET") return; // If not an http or https protocol (like chrome-extension), skip processing and go straight to the cache if (!(url.protocol === "http:" || url.protocol === "https:")) { return; } // Ignore HMR (hot module replacement) and API requests (you can adjust according to your actual API prefix) if ( url.pathname.includes("/api/") || url.pathname.includes("/sockjs-node/") ) { return; } // 1. Handle navigation requests: fetch from network first, then cache if successful if (request.mode === "navigate") { event.respondWith( fetch(request).catch(() => { return caches.match(request); }), ); return; } // 2. Handle static assets (js, css, images) - cache first const isStaticAsset = url.pathname.match( /\.(js|css|png|jpg|jpeg|svg|gif|woff2?|otf|ttf)$/, ); const isHashed = HASH_REGEX.test(url.pathname); if (isStaticAsset) { if (isHashed) { // ========================================== // Strategy A: Resources with Hash -> Cache First // ========================================== event.respondWith( caches.match(request).then((response) => { // If cache hit, return the cached response immediately if (response) return response; // Otherwise, fetch from network first return fetch(request).then((networkResponse) => { if ( !networkResponse || networkResponse.status !== 200 || (networkResponse.type !== "basic" && networkResponse.type !== "cors") ) { return networkResponse; } // Clone the response to allow multiple requests const responseToCache = networkResponse.clone(); caches.open(CACHE_NAME).then((cache) => { cache.put(request, responseToCache); }); return networkResponse; }); }), ); } else { // ========================================== // Strategy B: Resources without Hash (such as index.html) -> Network First // Ensure that the latest code is always retrieved, only fall back to cache when offline // ========================================== event.respondWith( fetch(request) .then((networkResponse) => { // Network request successful, updating cache if (networkResponse && networkResponse.status === 200) { const responseToCache = networkResponse.clone(); caches.open(CACHE_NAME).then((cache) => { cache.put(request, responseToCache); }); } return networkResponse; }) .catch(() => { // Network request failed (for example, the user disconnected from the Internet), try reading the historical version from the cache console.log("[SW] Network offline, try reading cache:", request.url); return caches.match(request); }), ); } } });