使用 Service Worker 缓存网站资源

4 阅读5分钟

转载自我的个人博客,后续更新将同步在个人博客发布。

《Posts: 使用 Service Worker 缓存网站资源》

如有疑问,可在文章下方留言评论。

前言

使用 Service Worker 缓存网站资源,提高网站加载速度。

我在这分享我那上千行的 Service Worker 代码,希望能帮助到有需要的人。

可以在 swconfig 里配置缓存策略,如缓存时间、缓存大小等。

就不想讲解代码了,懒

GitHun Gist

代码就放 Gist 里了,后续更新会在 Gist 里更新。

GitHun Gist: gist.github.com/God-2077/9a…

(: ⬇️⬇️⬇️下面这个链接可以直接访问最新的代码,而且打得开页面⬇️⬇️⬇️

gist.githubusercontent.com/God-2077/9a…

代码

const swconfig = {
    CACHE_VERSION: "v4.4",
    backgroundSyncInterval: 60 * 60 * 24 * 1000, // 24小时,后台同步更新间隔,但可能出现 Permission denied 错误
    checkCacheOnPageLoad: true, // 是否在页面加载时检查缓存,并更新过期缓存
    maxCacheSize: 100 * 1024 * 1024, // 100MB 缓存大小限制,注意,无法记录 opaque 响应大小
    maxCacheEntries: 1000, // 最大缓存条目数
    opaqueResponse: true, // 是否缓存不透明响应
    runtimeCaching: [{
            urlPattern: RegExp('.*'),
            handler: "CacheFirst",
            maxAgeSeconds: 60 * 60 * 24,
            autoUpdate: false,
            // css js 字体
            contentType: ['text/css', 'text/javascript', 'font/*', 'application/javascript']
        },
        {
            urlPattern: /^https:\/\/unpkg\.com\/.*/,
            handler: "CacheFirst",
            maxAgeSeconds: 60 * 60 * 24 * 365,
            autoUpdate: true, // 新增:是否自动更新
            // 添加内容类型,
            // 注意,opaque 响应是获取不到 contentType
            // 可以用 * 或 unknown 匹配所有 opaque 响应
            contentType: ['text/*']
        },
        {
            urlPattern: RegExp('^https://unpkg.ihwx.cn'),
            handler: "CacheFirst",
            maxAgeSeconds: 60 * 60 * 24 * 365,
            autoUpdate: true
        },
        {
            urlPattern: RegExp('^https://www.favicon.vip/get.php'),
            handler: "CacheFirst",
            maxAgeSeconds: 60 * 60 * 24 * 365,
            autoUpdate: false // 设置为false时删除而不更新
        },
        {
            urlPattern: RegExp('^https://image.thum.io/'),
            handler: "CacheFirst",
            maxAgeSeconds: 60 * 60 * 24 * 30,
            autoUpdate: true
        },
        {
            urlPattern: /https?:\/\/[^\/]+\/.*\.(png|jpg|jpeg|gif|svg|ico|woff2|ttf|js|css?)(\?.*)?/,
            handler: "CacheFirst",
            maxAgeSeconds: 60 * 60 * 24 * 365,
            autoUpdate: true
        },
        {
            urlPattern: /^https:\/\/ik.imagekit.io\//,
            handler: "CacheFirst",
            maxAgeSeconds: 0,
            autoUpdate: true
        },
        {
            urlPattern: /^https:\/\/weavatar.com\/avatar\//,
            handler: "CacheFirst",
            maxAgeSeconds: 60 * 60 * 24 * 30,
            autoUpdate: true
        },
        {
            urlPattern: /^https:\/\/assets.ksable.top\/js\/my-js.js/,
            handler: "CacheFirst",
            maxAgeSeconds: 60 * 60 * 24 * 1,
            autoUpdate: true
        }
    ],
    // 正表达式匹配的 URL 不缓存
    exclude: [
        /.*temp.js/
    ],
    precacheUrls: [
        'https://unpkg.com/react@19.2.4/package.json'
    ]
};

const CACHE_NAME = `${swconfig.CACHE_VERSION}-cache`;
const OFFLINE_URL = '/offline.html';
const DB_NAME = 'CacheDB';
const DB_VERSION = 3;

// IndexedDB 管理类
class CacheDB {
    constructor() {
        this.db = null;
        this.totalCacheSize = 0;
    }

    async init() {
        return new Promise((resolve, reject) => {
            const request = indexedDB.open(DB_NAME, DB_VERSION);

            request.onerror = () => reject(request.error);
            request.onsuccess = async () => {
                this.db = request.result;
                // 初始化时计算总缓存大小
                await this.calculateTotalSize();
                resolve(this.db);
            };

            request.onupgradeneeded = (event) => {
                const db = event.target.result;

                if (!db.objectStoreNames.contains('cacheMeta')) {
                    const store = db.createObjectStore('cacheMeta', {
                        keyPath: 'url'
                    });
                    store.createIndex('timestamp', 'timestamp', {
                        unique: false
                    });
                    store.createIndex('cachedAt', 'cachedAt', {
                        unique: false
                    });
                    store.createIndex('size', 'size', {
                        unique: false
                    });
                    store.createIndex('isPrecached', 'isPrecached', {
                        unique: false
                    });
                }

                if (!db.objectStoreNames.contains('cacheLogs')) {
                    const store = db.createObjectStore('cacheLogs', {
                        keyPath: 'id',
                        autoIncrement: true
                    });
                    store.createIndex('timestamp', 'timestamp', {
                        unique: false
                    });
                    store.createIndex('type', 'type', {
                        unique: false
                    });
                }

                if (!db.objectStoreNames.contains('syncLogs')) {
                    const store = db.createObjectStore('syncLogs', {
                        keyPath: 'id',
                        autoIncrement: true
                    });
                    store.createIndex('timestamp', 'timestamp', {
                        unique: false
                    });
                    store.createIndex('url', 'url', {
                        unique: false
                    });
                }
            };
        });
    }

    // 计算总缓存大小
    async calculateTotalSize() {
        if (!this.db) await this.init();

        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction(['cacheMeta'], 'readonly');
            const store = transaction.objectStore('cacheMeta');
            const request = store.getAll();

            request.onsuccess = () => {
                this.totalCacheSize = request.result.reduce((sum, meta) => sum + (meta.size || 0), 0);
                resolve(this.totalCacheSize);
            };
            request.onerror = () => reject(request.error);
        });
    }

    // 检查是否需要清理缓存
    async needsCleanup() {
        await this.calculateTotalSize();
        const allMeta = await this.getAllCacheMeta();

        return this.totalCacheSize > swconfig.maxCacheSize ||
            allMeta.length > swconfig.maxCacheEntries;
    }

    // 清理过期或最旧的缓存
    async cleanupCache() {
        if (!this.db) await this.init();

        const allMeta = await this.getAllCacheMeta();
        const now = Date.now();

        // 按缓存时间排序(最旧的在前)
        allMeta.sort((a, b) => a.cachedAt - b.cachedAt);

        const cache = await caches.open(CACHE_NAME);
        let cleanedSize = 0;
        let cleanedCount = 0;

        for (const meta of allMeta) {
            // 检查是否超过限制
            const currentSize = await this.calculateTotalSize();
            const currentCount = allMeta.length - cleanedCount;

            if (currentSize - cleanedSize <= swconfig.maxCacheSize &&
                currentCount <= swconfig.maxCacheEntries) {
                break;
            }

            // 跳过预缓存资源
            if (meta.isPrecached) {
                continue;
            }

            try {
                // 删除缓存
                await cache.delete(meta.url);
                await this.deleteCacheMeta(meta.url);
                cleanedSize += meta.size || 0;
                cleanedCount++;

                await this.logCacheEvent('cache_cleaned', meta.url, {
                    reason: 'storage_limit',
                    size: meta.size
                });
            } catch (error) {
                console.error('Failed to clean cache:', error);
            }
        }

        await this.calculateTotalSize();
        return {
            cleanedSize,
            cleanedCount
        };
    }

    // 记录缓存日志
    async logCacheEvent(type, url, details = {}) {
        if (!this.db) await this.init();

        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction(['cacheLogs'], 'readwrite');
            const store = transaction.objectStore('cacheLogs');

            const log = {
                type,
                url,
                timestamp: Date.now(),
                details: JSON.stringify(details)
            };

            const request = store.add(log);

            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }

    // 记录后台同步事件
    async logSyncEvent(url, action, success = true, details = {}) {
        if (!this.db) await this.init();

        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction(['syncLogs'], 'readwrite');
            const store = transaction.objectStore('syncLogs');

            const log = {
                url,
                action,
                success,
                timestamp: Date.now(),
                details: JSON.stringify(details)
            };

            const request = store.add(log);

            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }

    // 获取缓存元数据
    async getCacheMeta(url) {
        if (!this.db) await this.init();

        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction(['cacheMeta'], 'readonly');
            const store = transaction.objectStore('cacheMeta');
            const request = store.get(url);

            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }

    // 更新缓存元数据
    async updateCacheMeta(url, response, timestamp, isPrecached = false) {
        if (!this.db) await this.init();

        const contentLength = response.headers.get('content-length');
        const size = contentLength ? parseInt(contentLength) : await getResponseSize(response);
        const contentType = response.headers.get('content-type') || 'unknown';
        const etag = response.headers.get('etag') || '';

        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction(['cacheMeta'], 'readwrite');
            const store = transaction.objectStore('cacheMeta');

            const meta = {
                url,
                timestamp,
                cachedAt: Date.now(),
                size,
                type: contentType,
                etag,
                lastSync: Date.now(),
                isPrecached
            };

            const request = store.put(meta);

            request.onsuccess = async () => {
                await this.calculateTotalSize();
                await this.logCacheEvent('cache_updated', url, meta);
                resolve(request.result);
            };
            request.onerror = () => reject(request.error);
        });
    }

    // 获取所有缓存元数据
    async getAllCacheMeta() {
        if (!this.db) await this.init();

        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction(['cacheMeta'], 'readonly');
            const store = transaction.objectStore('cacheMeta');
            const request = store.getAll();

            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }

    // 删除缓存元数据
    async deleteCacheMeta(url) {
        if (!this.db) await this.init();

        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction(['cacheMeta'], 'readwrite');
            const store = transaction.objectStore('cacheMeta');
            const request = store.delete(url);

            request.onsuccess = async () => {
                await this.calculateTotalSize();
                await this.logCacheEvent('cache_deleted', url);
                resolve(request.result);
            };
            request.onerror = () => reject(request.error);
        });
    }

    // 获取需要更新的过期缓存
    async getExpiredCacheUrls() {
        const allMeta = await this.getAllCacheMeta();
        const now = Date.now();
        const expiredUrls = [];

        for (const meta of allMeta) {
            const rule = matchRuleByUrl(meta.url);
            if (rule && rule.maxAgeSeconds > 0) {
                const age = (now - meta.cachedAt) / 1000;
                if (age > rule.maxAgeSeconds) {
                    expiredUrls.push({
                        url: meta.url,
                        rule: rule,
                        age: age
                    });
                }
            }
        }

        return expiredUrls;
    }
}

const cacheDB = new CacheDB();

// 检查响应是否是可缓存的内容类型
function isCacheableContentType(response, rule = {}) {
    const contentType = response.headers.get('content-type')?.toLowerCase()?.split(';')[0] || 'unknown';
    const isUnknown = contentType === 'unknown';
    // 主类型、子类型
    let mainType = '',
        subType = '';
    if (!isUnknown) {
        mainType = contentType.split('/')[0];
        subType = contentType.split('/')[1];
    }

    const RuleContentTypeList = rule.contentType || [];

    if (RuleContentTypeList.length === 0) return true;

    for (const item of RuleContentTypeList) {
        const contentTypeRule = item?.toLowerCase();
        if (
            contentTypeRule === undefined ||
            contentTypeRule === "" ||
            contentTypeRule === '*' ||
            contentTypeRule === "*/*") {
            return true;
        }
        if (contentType === contentTypeRule) {
            return true;
        }

        if (!isUnknown) {
            const contentTypeRuleMainType = contentTypeRule.split('/')[0];
            const contentTypeRuleSubType = contentTypeRule.split('/')[1];
            if (contentTypeRule.startsWith("*/")) {
                if (contentTypeRuleSubType === subType) {
                    return true;
                }
            } else if (contentTypeRule.endsWith("/*")) {
                if (contentTypeRuleMainType === mainType) {
                    return true;
                }
            }
        }
    }
    // 没有匹配的内容类型规则
    return false;
}

// 获取响应大小
async function getResponseSize(response) {
    // 优先使用 content-length
    const headers = response.headers
    // 检查是否有 content-length 头
    const contentLength = headers.get('content-length');
    if (
        contentLength !== null &&
        contentLength !== '' &&
        !isNaN(parseInt(contentLength)) &&
        parseInt(contentLength) > 0 &&
        contentLength !== undefined
    ) {
        return parseInt(contentLength);
    }

    // 克隆response,避免修改原始响应
    const blob = await response.clone().blob();
    return blob.size;
}

// fetch
async function _fetch(request) {
    // 回退
    return fetch(request);
    const url = request.url;
    const headers = new Headers(request.headers);
    // headers.set('Cache-Control', 'no-store');
    const response = await fetch(url, {
        method: "GET",
        mode: "cors",
        headers: headers,
        cache: 'no-store'
    });
    return response;
}

// 根据URL匹配规则(独立函数,供getExpiredCacheUrls使用)
function matchRuleByUrl(url) {
    // 检查排除规则
    for (const pattern of swconfig.exclude) {
        if (pattern.test(url)) {
            return null;
        }
    }

    // 反向遍历确保后面的规则优先级更高
    for (let i = swconfig.runtimeCaching.length - 1; i >= 0; i--) {
        const rule = swconfig.runtimeCaching[i];
        if (rule.urlPattern.test(url)) {
            return rule;
        }
    }
    return null;
}

// 匹配请求对应的规则
function matchRule(request) {
    const url = request.url;

    // 检查排除规则
    for (const pattern of swconfig.exclude) {
        if (pattern.test(url)) {
            cacheDB.logCacheEvent('cache_skipped', url, {
                reason: 'excluded'
            });
            return null;
        }
    }

    // 反向遍历确保后面的规则优先级更高
    for (let i = swconfig.runtimeCaching.length - 1; i >= 0; i--) {
        const rule = swconfig.runtimeCaching[i];
        if (rule.urlPattern.test(url)) {
            return rule;
        }
    }

    cacheDB.logCacheEvent('cache_skipped', url, {
        reason: 'no_matching_rule'
    });
    return null;
}

// 后台更新缓存
async function backgroundUpdate(request, rule, skipSyncInterval = false) {
    try {
        const cache = await caches.open(CACHE_NAME);
        const cachedResponse = await cache.match(request);

        if (!cachedResponse) return;

        // 检查是否需要更新(基于时间间隔)
        const meta = await cacheDB.getCacheMeta(request.url);
        const now = Date.now();

        if (!skipSyncInterval && (meta && meta.lastSync)) {
            const timeSinceLastSync = now - meta.lastSync;
            if (timeSinceLastSync < swconfig.backgroundSyncInterval) {
                await cacheDB.logSyncEvent(request.url, 'skip_sync', true, {
                    reason: 'within_interval',
                    timeSinceLastSync
                });
                return;
            }
        }

        // 新增:如果autoUpdate为false,则删除缓存
        if (rule.autoUpdate === false) {
            await cache.delete(request);
            await cacheDB.deleteCacheMeta(request.url);
            await cacheDB.logSyncEvent(request.url, 'cache_deleted', true, {
                reason: 'autoUpdate_false'
            });
            return;
        }

        // 准备验证头
        const headers = new Headers();
        let useConditionalRequest = false;

        // 如果有 ETag,使用条件请求
        if (meta && meta.etag) {
            headers.set('If-None-Match', meta.etag);
            useConditionalRequest = true;
            await cacheDB.logSyncEvent(request.url, 'conditional_sync_start', true, {
                etag: meta.etag
            });
        }

        // 发送验证请求
        const networkResponse = await fetch(request, {
            headers,
            cache: 'no-store'
        });

        if (useConditionalRequest && networkResponse.status === 304) {
            // 资源未修改,更新同步时间
            await cacheDB.updateCacheMeta(request.url, cachedResponse, Date.now());
            await cacheDB.logSyncEvent(request.url, 'conditional_sync_304', true, {
                reason: 'not_modified'
            });
        } else if (networkResponse.ok || networkResponse.type === 'opaque') {
            // 资源已更新,更新缓存
            const responseClone = networkResponse.clone();
            await cache.put(request, responseClone);
            await cacheDB.updateCacheMeta(request.url, networkResponse, Date.now());
            await cacheDB.logSyncEvent(request.url, 'cache_updated', true, {
                status: networkResponse.status,
                newEtag: networkResponse.headers.get('etag')
            });
        } else {
            await cacheDB.logSyncEvent(request.url, 'sync_failed', false, {
                status: networkResponse.status
            });
        }
    } catch (error) {
        console.error('Background update failed:', error);
        await cacheDB.logSyncEvent(request.url, 'sync_error', false, {
            error: error.message
        });
    }
}

// 页面打开时触发的后台同步任务,类似 runPeriodicSync
async function runPageLoadSync() {
    try {
        await cacheDB.logSyncEvent('system', 'page_load_sync_start', true);

        const expiredUrls = await cacheDB.getExpiredCacheUrls();

        let updatedCount = 0;
        let errorCount = 0;

        for (const item of expiredUrls) {
            try {
                const request = new Request(item.url);
                await backgroundUpdate(request, item.rule, true);
                updatedCount++;

                // 添加延迟避免过多请求
                await new Promise(resolve => setTimeout(resolve, 100));
            } catch (error) {
                errorCount++;
                console.error(`Failed to sync ${item.url}:`, error);
            }
        }

        await cacheDB.logSyncEvent('system', 'page_load_sync_complete', true, {
            total: expiredUrls.length,
            updated: updatedCount,
            errors: errorCount
        });
    } catch (error) {
        await cacheDB.logSyncEvent('system', 'page_load_sync_error', false, {
            error: error.message
        });
    }
}

// 定期后台同步任务
async function runPeriodicSync() {
    try {
        await cacheDB.logSyncEvent('system', 'periodic_sync_start', true);

        const expiredUrls = await cacheDB.getExpiredCacheUrls();
        let updatedCount = 0;
        let errorCount = 0;

        for (const item of expiredUrls) {
            try {
                const request = new Request(item.url);
                await backgroundUpdate(request, item.rule);
                updatedCount++;

                // 添加延迟避免过多请求
                await new Promise(resolve => setTimeout(resolve, 100));
            } catch (error) {
                errorCount++;
                console.error(`Failed to sync ${item.url}:`, error);
            }
        }

        await cacheDB.logSyncEvent('system', 'periodic_sync_complete', true, {
            total: expiredUrls.length,
            updated: updatedCount,
            errors: errorCount
        });
    } catch (error) {
        await cacheDB.logSyncEvent('system', 'periodic_sync_error', false, {
            error: error.message
        });
    }
}

// 检查并清理缓存(如果需要)
async function checkAndCleanupCache() {
    if (await cacheDB.needsCleanup()) {
        const result = await cacheDB.cleanupCache();
        await cacheDB.logCacheEvent('storage_cleanup', '', result);
    }
}

try {
    console.log('当前缓存版本: ', swconfig.CACHE_VERSION);
} catch (e) {}

// 安装阶段 - 初始化缓存和数据库
self.addEventListener('install', event => {
    event.waitUntil(
        (async () => {
            await cacheDB.init();
            await cacheDB.logCacheEvent('sw_install', '', {
                version: swconfig.CACHE_VERSION
            });

            const cache = await caches.open(CACHE_NAME);
            if (swconfig.precacheUrls.length > 0) {
                // 修改:单独处理每个预缓存URL
                for (const url of swconfig.precacheUrls) {
                    try {
                        const response = await fetch(url);
                        if (response.ok) {
                            await cache.put(url, response.clone());
                            // 标记为预缓存资源
                            await cacheDB.updateCacheMeta(url, response, Date.now(), true);
                            await cacheDB.logCacheEvent('precached', url);
                        }
                    } catch (error) {
                        console.error('Precache failed for:', url, error);
                    }
                }
            }

            return self.skipWaiting();
        })()
    );
});

// 激活阶段 - 清理旧缓存和旧数据库,启动定期同步
self.addEventListener('activate', event => {
    event.waitUntil(
        (async () => {
            // 清理旧缓存
            const cacheNames = await caches.keys();
            await Promise.all(
                cacheNames.map(name => {
                    if (name !== CACHE_NAME && name.startsWith('v') && name.endsWith('-cache')) {
                        return caches.delete(name);
                    }
                })
            );

            // 清理旧版本数据库
            const dbNames = await indexedDB.databases();
            for (const dbInfo of dbNames) {
                if (dbInfo.name === DB_NAME && dbInfo.version < DB_VERSION) {
                    indexedDB.deleteDatabase(DB_NAME);
                    break;
                }
            }

            await cacheDB.logCacheEvent('sw_activate', '', {
                version: swconfig.CACHE_VERSION
            });

            // 启动定期同步
            if ('periodicSync' in self.registration) {
                try {
                    await self.registration.periodicSync.register('background-sync', {
                        minInterval: swconfig.backgroundSyncInterval // 毫秒
                    });
                    await cacheDB.logSyncEvent('system', 'periodic_sync_registered', true);
                } catch (error) {
                    await cacheDB.logSyncEvent('system', 'periodic_sync_failed', false, {
                        error: error.message
                    });
                }
            }

            return self.clients.claim();
        })()
    );
});

// 监听定期同步事件
self.addEventListener('periodicsync', event => {
    if (event.tag === 'background-sync') {
        event.waitUntil(runPeriodicSync());
    }
});

// 请求处理
self.addEventListener('fetch', event => {
    const request = event.request;

    // 只处理GET请求
    if (request.method !== 'GET') return;

    // 特殊处理导航请求的离线回退
    if (request.mode === 'navigate') {
        event.respondWith(
            (async () => {
                try {
                    const networkResponse = await _fetch(request);
                    await cacheDB.logCacheEvent('navigate_network', request.url, {
                        status: networkResponse.status
                    });
                    return networkResponse;
                } catch (error) {
                    if (OFFLINE_URL) {
                        const offlineResponse = await caches.match(OFFLINE_URL);
                        if (offlineResponse) {
                            await cacheDB.logCacheEvent('navigate_offline', request.url);
                            return offlineResponse;
                        }
                    }
                    await cacheDB.logCacheEvent('navigate_error', request.url, {
                        error: error.message
                    });
                    return new Response('Offline', {
                        status: 503,
                        statusText: 'Service Unavailable'
                    });
                }
            })()
        );
        return;
    }

    // 匹配缓存规则
    const rule = matchRule(request);
    if (!rule) return;

    // 处理不同缓存策略
    switch (rule.handler) {
        case 'NetworkOnly':
            (async () => {
                await cacheDB.logCacheEvent('network_only', request.url);
                return // 交给网络请求处理
            })()
            return
            break;

        case 'CacheOnly':
            event.respondWith(
                (async () => {
                    const cachedResponse = await caches.match(request);
                    if (cachedResponse) {
                        await cacheDB.logCacheEvent('cache_only_hit', request.url);
                        return cachedResponse;
                    } else {
                        await cacheDB.logCacheEvent('cache_only_miss', request.url);
                        return Response('Cache miss', {
                            status: 404,
                            statusText: 'Not Found'
                        });
                    }
                })()
            );
            break;

        case 'NetworkFirst':
            event.respondWith(
                (async () => {
                    try {
                        const networkResponse = await _fetch(request);
                        await cacheDB.logCacheEvent('network_first_network', request.url, {
                            status: networkResponse.status
                        });
                        const isAllowCacheContentType = isCacheableContentType(networkResponse, rule);

                        // 缓存成功响应
                        if (
                            isAllowCacheContentType &&
                            (
                                networkResponse.ok ||
                                (swconfig.opaqueResponse && networkResponse.type === 'opaque')
                            )
                        ) {
                            const cache = await caches.open(CACHE_NAME);
                            const responseClone = networkResponse.clone();
                            event.waitUntil(
                                (async () => {
                                    await cache.put(request, responseClone);
                                    await cacheDB.updateCacheMeta(request.url, networkResponse, Date.now());
                                    await checkAndCleanupCache();
                                    await cacheDB.logCacheEvent('network_first_cache_added', request.url);
                                })());
                        } else if (!networkResponse.ok && networkResponse.type !== 'opaque') {
                            await cacheDB.logCacheEvent('network_first_cache_fetch_failed', request.url, {
                                status: networkResponse.status
                            });
                        } else if (!swconfig.opaqueResponse && networkResponse.type === 'opaque') {
                            await cacheDB.logCacheEvent('network_first_cache_skip_opaque', request.url, {
                                status: networkResponse.status
                            });
                        } else if (!isAllowCacheContentType) {
                            await cacheDB.logCacheEvent('network_first_cache_skip_uncacheable', request.url, {
                                status: networkResponse.status
                            });
                        }

                        return networkResponse;
                    } catch (error) {
                        const cachedResponse = await caches.match(request);
                        if (cachedResponse) {
                            await cacheDB.logCacheEvent('network_first_cache', request.url);
                            return cachedResponse;
                        }
                        await cacheDB.logCacheEvent('network_first_error', request.url, {
                            error: error.message
                        });
                        throw error;
                    }
                })()
            );
            break;

        case 'CacheFirst':
        default:
            event.respondWith((async () => {
                const cache = await caches.open(CACHE_NAME);
                const cachedResponse = await cache.match(request);

                // 如果找到缓存
                if (cachedResponse) {
                    await cacheDB.logCacheEvent('cache_hit', request.url);

                    // 修改:移除间隔检查,立即触发更新
                    if (rule.maxAgeSeconds > 0) {
                        const meta = await cacheDB.getCacheMeta(request.url);
                        if (meta) {
                            const age = (Date.now() - meta.cachedAt) / 1000;
                            if (age > rule.maxAgeSeconds) {
                                // 异步更新过期缓存
                                event.waitUntil(backgroundUpdate(request, rule, true));
                            }
                        }
                    }
                    return cachedResponse;
                }

                // 没有缓存则请求网络
                await cacheDB.logCacheEvent('cache_miss', request.url);
                try {
                    const networkResponse = await _fetch(request);
                    const isAllowCacheContentType = isCacheableContentType(networkResponse, rule);
                    // 缓存成功响应
                    if (
                        isAllowCacheContentType &&
                        (
                            networkResponse.ok ||
                            (swconfig.opaqueResponse && networkResponse.type === 'opaque')
                        )
                    ) {
                        const responseClone = networkResponse.clone();
                        await cache.put(request, responseClone);
                        await cacheDB.updateCacheMeta(request.url, networkResponse, Date.now());
                        await checkAndCleanupCache();
                        await cacheDB.logCacheEvent('cache_added', request.url, {
                            status: networkResponse.status
                        });
                    } else if (!networkResponse.ok && networkResponse.type !== 'opaque') {
                        await cacheDB.logCacheEvent('cache_fetch_failed', request.url, {
                            status: networkResponse.status
                        });
                    } else if (!swconfig.opaqueResponse && networkResponse.type === 'opaque') {
                        await cacheDB.logCacheEvent('cache_skip_opaque', request.url, {
                            status: networkResponse.status
                        });
                    } else if (!isAllowCacheContentType) {
                        await cacheDB.logCacheEvent('cache_skip_uncacheable', request.url, {
                            status: networkResponse.status
                        });
                    }
                    return networkResponse;
                } catch (error) {
                    await cacheDB.logCacheEvent('cache_fetch_error', request.url, {
                        error: error.message
                    });
                    return Response.error();
                }
            })());
            break;
    }
});
// 在 Service Worker 中
self.addEventListener('message', async event => {
    // 处理页面加载完成事件
    switch (event.data.type) {
        case 'PAGE_LOADED':
            // console.log('页面已打开:', event.data.url);
            // 执行你的函数
            // doSomethingOnPageLoad();
            // console.log('开始执行页面加载同步任务');
            await runPageLoadSync();
            // console.log('页面加载同步任务执行完成');
            break;
        case 'FORCE_DELETE_ALL_CACHE':
            try {
                // 强制删除所有缓存
                const cache = await caches.open(CACHE_NAME);
                await cache.keys().then(keys => {
                    keys.forEach(key => {
                        cache.delete(key);
                    });
                });
                // 删除所有缓存元数据
                await cacheDB.getAllCacheMeta().then(metas => {
                    metas.forEach(meta => {
                        cacheDB.deleteCacheMeta(meta.url);
                    });
                });
            } catch (error) {
                console.error('删除缓存失败');
                console.error(error);
            }
            break;
        case 'FORCE_DELETE_CACHE':
            try {
                // 强制删除指定缓存
                const cache = await caches.open(CACHE_NAME);
                cache.delete(event.data.url);
            } catch (error) {
                console.error('删除缓存失败');
                console.error(error);
            }
            break;
        default:
            break;
    }
});