Ksable' 小屋

首页留言板
友链关于

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

发表于 2026-02-15 00:20
更新于 2026-02-15 09:00
3554 字
18 min read

kissablecho

kissablecho 的个人博客 / 记录生活，分享技术 / 喜欢二次元和白丝。

128 文章

4 分类

7 标签
首页
留言板
分类
标签
归档
友链
关于
一些实用的网站
astro-koharu 使用指南
歌单
暂无系列文章
使用 Service Worker 缓存网站资源，提高网站加载速度。我在这分享我那上千行的 Service Worker 代码，希望能帮助到有需要的人。可以在 swconfig 里配置缓存策略，如缓存时间、缓存大小等。

## 前言

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

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

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

就不想讲解代码了，懒

## GitHub Gist

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

GitHub Gist: https://gist.github.com/God-2077/9a18554dd48962f81064e487f694de4a

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

https://gist.githubusercontent.com/God-2077/9a18554dd48962f81064e487f694de4a/raw/8f334f68887f7ef7dc3468e585484aaff9f321a0/sw_v6.js

## 代码

    const swconfig = {
        CACHE_VERSION: "v4.4",
        maxLogEntries: 1000, // 最大日志数
        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;
            // 最大日志数
            this.maxLogEntries = swconfig?.maxLogEntries ?? 1000;
        }

        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(async (resolve, reject) => {

                await this._trimOldLogs('cacheLogs', this.maxLogEntries);

                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(async (resolve, reject) => {

                await this._trimOldLogs('syncLogs', this.maxLogEntries);

                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;
        }
        // 新增方法：清理超出数量限制的旧日志
        async _trimOldLogs(storeName, maxEntries) {
            if (!this.db) await this.init();
            if (!maxEntries || maxEntries <= 0) return;

            return new Promise((resolve, reject) => {
                const transaction = this.db.transaction([storeName], 'readwrite');
                const store = transaction.objectStore(storeName);
                const index = store.index('timestamp'); // 假设日志表都有 timestamp 索引

                // 获取当前所有日志的数量
                const countRequest = store.count();

                countRequest.onsuccess = () => {
                    const currentCount = countRequest.result;
                    const entriesToRemove = currentCount - maxEntries;

                    if (entriesToRemove <= 0) {
                        resolve(0); // 未超出限制，无需清理
                        return;
                    }

                    // 按时间戳升序获取最旧的日志ID
                    const getAllRequest = index.getAll(null, entriesToRemove);

                    getAllRequest.onsuccess = () => {
                        const oldLogs = getAllRequest.result;
                        let deletedCount = 0;

                        // 删除最旧的日志
                        oldLogs.forEach(log => {
                            const deleteRequest = store.delete(log.id);
                            deleteRequest.onsuccess = () => deletedCount++;
                        });

                        transaction.oncomplete = () => resolve(deletedCount);
                    };

                    getAllRequest.onerror = () => reject(getAllRequest.error);
                };

                countRequest.onerror = () => reject(countRequest.error);
            });
        }
    }

    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;
        }
    });

喜欢的话，留下你的评论吧～

中文
中文 English 日本語

kissablecho

kissablecho 的个人博客 / 记录生活，分享技术 / 喜欢二次元和白丝。

128 文章

4 分类

7 标签
首页
留言板
分类
标签
归档
友链
关于
一些实用的网站
astro-koharu 使用指南
歌单
暂无系列文章