layout: null permalink: /sw.js
“use strict”; var CACHE_NAME = '{{ site.github.build_revision }}{{ site.time | date: '%Y%m%d%H%M%S' }}'; var urlsToCache = [
'{{ "/manifest.json" | relative_url }}',{% for post in site.posts limit:10 %}{% unless post.redirect_to %}'{{ post.url | relative_url }}', {% endunless %}{% endfor %}{% for page in site.pages %}{% if page.layout %}'{{ page.url | relative_url }}', {% endif %}{% endfor %}'{{ "/assets/css/main.css" | relative_url }}'
]; var idbDatabase; var IDB_VERSION = {{ site.time | date: '%Y%m%d' }}; var STOP_RETRYING_AFTER = 86400000; // One day, in milliseconds. var STORE_NAME = 'urls';
// This is basic boilerplate for interacting with IndexedDB. Adapted from // developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB function openDatabaseAndReplayRequests() {
var indexedDBOpenRequest = indexedDB.open('offline-analytics', IDB_VERSION); // This top-level error handler will be invoked any time there's an IndexedDB-related error. indexedDBOpenRequest.onerror = function(error) { console.error('IndexedDB error:', error); }; // This should only execute if there's a need to create a new database for the given IDB_VERSION. indexedDBOpenRequest.onupgradeneeded = function() { this.result.createObjectStore(STORE_NAME, {keyPath: 'url'}); }; // This will execute each time the database is opened. indexedDBOpenRequest.onsuccess = function() { idbDatabase = this.result; replayAnalyticsRequests(); };
}
// Helper method to get the object store that we care about. function getObjectStore(storeName, mode) {
return idbDatabase.transaction(storeName, mode).objectStore(storeName);
}
function replayAnalyticsRequests() {
var savedRequests = []; getObjectStore(STORE_NAME).openCursor().onsuccess = function(event) { // See https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB#Using_a_cursor var cursor = event.target.result; if (cursor) { // Keep moving the cursor forward and collecting saved requests. savedRequests.push(cursor.value); cursor.continue(); } else { // At this point, we have all the saved requests. //console.log('About to replay %d saved Google Analytics requests...', // savedRequests.length); savedRequests.forEach(function(savedRequest) { var queueTime = Date.now() - savedRequest.timestamp; if (queueTime > STOP_RETRYING_AFTER) { getObjectStore(STORE_NAME, 'readwrite').delete(savedRequest.url); //console.log(' Request has been queued for %d milliseconds. ' + //'No longer attempting to replay.', queueTime); } else { // The qt= URL parameter specifies the time delta in between right now, and when the // /collect request was initially intended to be sent. See // https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#qt var requestUrl = savedRequest.url + '&qt=' + queueTime; //console.log(' Replaying', requestUrl); fetch(requestUrl).then(function(response) { if (response.status < 400) { // If sending the /collect request was successful, then remove it from the IndexedDB. getObjectStore(STORE_NAME, 'readwrite').delete(savedRequest.url); //console.log(' Replaying succeeded.'); } else { // This will be triggered if, e.g., Google Analytics returns a HTTP 50x response. // The request will be replayed the next time the service worker starts up. //console.error(' Replaying failed:', response); } }).catch(function(error) { // This will be triggered if the network is still down. The request will be replayed again // the next time the service worker starts up. //console.error(' Replaying failed:', error); }); } }); } };
}
// Open the IndexedDB and check for requests to replay each time the service worker starts up. // Since the service worker is terminated fairly frequently, it should start up again for most // page navigations. It also might start up if it's used in a background sync or a push // notification context. openDatabaseAndReplayRequests();
self.addEventListener('install', event => {
event.waitUntil(caches.open(CACHE_NAME) .then(cache => cache.addAll(urlsToCache)).then(() => { return self.skipWaiting(); }) );
});
self.addEventListener('fetch',event => {
event.respondWith( caches.match(event.request) .then(response => { if (response) return response; var fetchRequest = event.request.clone(); return fetch(fetchRequest).then(response => { if (!response || response.status != 200 || response.type !== 'basic'){ if (response.status >= 500) { // If this is a Google Analytics ping then we want to retry it if a HTTP 5xx response // was returned, just like we'd retry it if the network was down. checkForAnalyticsRequest(event.request.url); } return response; } var responseToCache = response.clone(); caches.open(CACHE_NAME).then(cache => { cache.put(event.request, responseToCache); }); return response; }).catch(() => { // The catch() will be triggered for network failures. Let's see if it was a request for // a Google Analytics ping, and save it to be retried if it was. checkForAnalyticsRequest(event.request.url); return caches.match('/offline.html'); }) })); });
self.addEventListener('activate',event => {
var chacheWhiteList=[CACHE_NAME]; event.waitUntil( caches.keys().then(keyList => { return Promise.all(keyList.map(key => { if (chacheWhiteList.indexOf(key) === -1) return caches.delete(key); })); }).then(() => { return self.clients.claim(); }) ); }); function checkForAnalyticsRequest(requestUrl) { // Construct a URL object (https://developer.mozilla.org/en-US/docs/Web/API/URL.URL) // to make it easier to check the various components without dealing with string parsing. var url = new URL(requestUrl); if ((url.hostname === 'www.google-analytics.com' || url.hostname === 'ssl.google-analytics.com') && url.pathname === '/collect') { //console.log(' Storing Google Analytics request in IndexedDB to be replayed later.'); saveAnalyticsRequest(requestUrl); } } function saveAnalyticsRequest(requestUrl) { getObjectStore(STORE_NAME, 'readwrite').add({ url: requestUrl, timestamp: Date.now() }); }