Newer
Older
BlackoutClient / Assets / WebGLTemplates / WebApp / sw.js
// TODO: increase `version` number to force cache update when publishing a new release
const version = 'v1';

const config = {
    cacheRemote: true,
    version: version+'::',
    preCachingItems: [
        'index.html',
        'offline.html',
        '404.html',
        'sw.js'
    ],
    blacklistCacheItems: [
        'index.html',
        'service-worker.js'
    ],
    offlineImage: '<svg role="img" aria-labelledby="offline-title"' + ' viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">' + '<title id="offline-title">Offline</title>' + '<g fill="none" fill-rule="evenodd"><path fill="#aaa" d="M0 0h400v300H0z"/>' + '<text fill="#222" font-family="monospace" font-size="32" font-weight="bold">' + '<tspan x="136" y="156">offline</tspan></text></g></svg>',
    offlinePage: 'offline.html',
    notFoundPage: '404.html'
};

function cacheName(key, opts) {
    return `${opts.version}${key}`;
}

function addToCache(cacheKey, request, response) {
    if (response.ok) {
        const copy = response.clone();
        caches.open(cacheKey).then(cache => {
            cache.put(request, copy);
        });
    }
    return response;
}

function fetchFromCache(event) {
    return caches.match(event.request).then(response => {
        if (!response) {
            throw Error(`${event.request.url} not found in cache`);
        } else if (response.status === 404) {
            return caches.match(config.notFoundPage);
        }
        return response;
    });
}

function offlineResponse(resourceType, opts) {
    if (resourceType === 'content') {
        return caches.match(opts.offlinePage);
    }
    if (resourceType === 'image') {
        return new Response(opts.offlineImage, {
            headers: { 'Content-Type': 'image/svg+xml' }
        });
    }
    return undefined;
}

self.addEventListener('install', event => {
    event.waitUntil(caches.open(
        cacheName('static', config)
        )
            .then(cache => cache.addAll(config.preCachingItems))
            .then(() => self.skipWaiting())
    );
});
self.addEventListener('activate', event => {
    function clearCacheIfDifferent(event, opts) {
        return caches.keys().then(cacheKeys => {
            const oldCacheKeys = cacheKeys.filter(key => key.indexOf(opts.version) !== 0);
            const deletePromises = oldCacheKeys.map(oldKey => caches.delete(oldKey));
            return Promise.all(deletePromises);
        });
    }
    event.waitUntil(
        clearCacheIfDifferent(event, config)
            .then(() => self.clients.claim())
    );
});
self.addEventListener('fetch', event => {
    const request = event.request;
    const url = new URL(request.url);
    if (request.method !== 'GET'
        || (config.cacheRemote !== true && url.origin !== self.location.origin)
        || (config.blacklistCacheItems.length > 0 && config.blacklistCacheItems.indexOf(url.pathname) !== -1)) {
        // default browser behavior
        return;
    }
    let cacheKey;
    let resourceType = 'content';
    if (/(.jpg|.jpeg|.webp|.png|.svg|.gif)$/.test(url.pathname)) {
        resourceType = 'image';
    } else if (/.\/fonts.(?:googleapis|gstatic).com/.test(url.origin)) {
        resourceType = 'font';
    }
    cacheKey = cacheName(resourceType, config);
    if (resourceType === 'content') {
        // Network First Strategy
        event.respondWith(
            fetch(request)
                .then(response => addToCache(cacheKey, request, response))
                .catch(() => fetchFromCache(event))
                .catch(() => offlineResponse(resourceType, config))
        );
    } else {
        // Cache First Strategy
        event.respondWith(
            fetchFromCache(event)
                .catch(() => fetch(request))
                .then(response => addToCache(cacheKey, request, response))
                .catch(() => offlineResponse(resourceType, config))
        );
    }
});