This commit is contained in:
2026-04-24 20:30:52 +08:00
commit 704bc34625
152 changed files with 45612 additions and 0 deletions

131
public/sw.js Normal file
View File

@@ -0,0 +1,131 @@
// 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);
}),
);
}
}
});