So I've created and successfully registered a Service Worker when the browser is online. I can see that the resources are properly cached using the DevTools. The issue is when I switch to offline mode, the service worker seems to unregister itself and, as such, nothing but the google chrome offline page is displayed.
The code.
'use strict';
var CACHE_NAME = 'v1';
var urlsToCache = [
'/'
];
self.addEventListener('install', function(event) {
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
// IMPORTANT: Clone the request. A request is a stream and
// can only be consumed once. Since we are consuming this
// once by cache and once by the browser for fetch, we need
// to clone the response.
var fetchRequest = event.request.clone();
return fetch(fetchRequest).then(
function(response) {
// Check if we received a valid response
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// IMPORTANT: Clone the response. A response is a stream
// and because we want the browser to consume the response
// as well as the cache consuming the response, we need
// to clone it so we have two streams.
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
And the test script, if that helps.
'use strict';
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
var serviceWorker;
if (registration.installing) {
serviceWorker = registration.installing;
} else if (registration.waiting) {
serviceWorker = registration.waiting;
} else if (registration.active) {
serviceWorker = registration.active;
}
if (serviceWorker) {
console.log('ServiceWorker phase:', serviceWorker.state);
serviceWorker.addEventListener('statechange', function (e) {
console.log('ServiceWorker phase:', e.target.state);
});
}
}).catch(function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
}
Edit: Checking the console I've found this error. sw.js:1 An unknown error occurred when fetching the script.
Aslo, as per a suggestion, I've added this code yet the problem persists.
this.addEventListener('activate', function(event) {
var cacheWhitelist = ['v2'];
event.waitUntil(
caches.keys().then(function(keyList) {
return Promise.all(keyList.map(function(key) {
if (cacheWhitelist.indexOf(key) === -1) {
return caches.delete(key);
}
}));
})
);
});
it seems you haven't added any activate event which meant to render cached elements when available. Hope the code help you.
self.addEventListener('activate', function(e) {
/*service worker activated */
e.waitUntil(
caches.key().then(function(keyList) {
return Promise.all(keyList.map(function(key) {
if(key){
//remove old cache stuffs
return caches.delete(key);
}
}));
})
);
});
The problem appears to have fixed itself. To anyone here from google, try restarting the browser.
Related
The JavaScript code below retrieves some texts from server by using Fetch API.
fetch("index.php?user_id=1234", {
method: "GET"
}).then(function(response) {
return response.text();
}).then(function(output) {
document.getElementById("output").innerHTML = output;
});
But during network errors, it retrieves the offline page (offline.html) due to service-worker.
"use strict";
self.addEventListener("install", function() {
self.skipWaiting();
});
self.addEventListener("activate", function(activation) {
activation.waitUntil(
caches.keys().then(function(cache_names) {
for (let cache_name of cache_names) {
caches.delete(cache_name);
};
caches.open("client_cache").then(function(cache) {
return cache.add("offline.html");
});
})
);
});
self.addEventListener("fetch", function(fetching) {
fetching.respondWith(
caches.match(fetching.request).then(function(cached_response) {
return cached_response || fetch(fetching.request);
}).catch(function() {
return caches.match("offline.html");
})
);
});
I want to let the fetch request know about the network error.
And I do not want to use window.navigator. So, what can I do?
(I prefer vanilla solutions.)
You should structure your service worker's fetch event handler so that it only returns offline.html when there's a network error/cache miss and the original request is for a navigation. If the original request is not a navigation, then responding with offline.html is (as you've seen) going to result in getting back HTML for every failure.
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cachedResponse) => {
return cachedResponse || fetch(event.request);
}).catch((error) => {
if (event.request.mode === 'navigate') {
return caches.match('offline.html');
}
throw error;
})
);
});
I am trying to set up pwa app. My sw.js is
var urlsToCache = [
'/danger/index.html',
'/danger/css/main.css',
'/danger/css/fontawesome-all.min.css',
'/danger/css/font.css'
];
var CACHE_NAME = 'progressive';
self.addEventListener('install', function (event) {
event.waitUntil(
caches.open(CACHE_NAME)
.then(function (cache) {
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.open(CACHE_NAME).then(function (cache) {
return cache.match(event.request).then(function (response) {
return response || fetch(event.request).then(function (response) {
cache.put(event.request, response.clone());
return response;
});
});
})
);
});
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.open(CACHE_NAME).then(function (cache) {
return fetch(event.request).then(function (response) {
cache.put(event.request, response.clone());
return response;
});
})
);
});
I want to make sw check for first network, then cache. I got a code from net.
self.addEventListener('fetch', (event) => {
event.respondWith(async function() {
try {
return await fetch(event.request);
} catch (err) {
return caches.match(event.request);
}
}());
});
How to implement it in my sw.js. I asked it because my app not updating the website content in network enabled mode also.
Browser will always reach out to network first and get the service-worker.js I would suggest you to use Workbox instead of manually writing all these code.
Workbox documentation
We should not cache service-worker file as that is a source of truth for browser to know whether any other files have changed. Usually a service-worker file will have a series of files that are cached along with their hashes. When service worker file changes, then browser knows that it needs to take update from server.
Thanks
I am trying to setup a service worker so that my web-app can fully work offline. When I inspect the cache for static-v2 all of my assets that I want are there.
When I make a request and am online my SW is correctly doing the fetch and not falling into my catch statement.
However, when I am offline and I fall through to my cache the correct response is logged with the status of 200 but I get the chrome 'no internet' error.
Another interesting point is even when I comment out the line where I return the cache response and just return the hard coded response I still get the 'no internet' error.
Any help in figuring out why my service worker isn't working as expected would be greatly appreciated.
const CACHENAME = `static-v2`;
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(CACHENAME).then(function(cache) {
return cache
.addAll([
// your list of cache keys to store in cache
'bundle.js',
'index.html',
'manifest.json',
// etc.
])
.then(() => {
return self.skipWaiting();
});
}),
);
});
self.onactivate = (evt) => {
console.log(`on activate - ${CACHENAME}`);
evt.waitUntil(
caches.keys().then((cacheNames) => {
const deleteOldCaches = cacheNames.map((cacheName) => {
console.log(cacheName);
if (cacheName != CACHENAME) {
return caches.delete(cacheName);
}
return Promise.resolve();
});
return Promise.all(deleteOldCaches);
}),
);
self.clients.claim();
};
self.onfetch = (evt) => {
evt.waitUntil(
fetch(evt.request).catch((err) => {
caches.match(evt.request).then((response) => {
console.log(evt.request.url, response);
if (response) return response;
return new Response('<div><h2>Uh oh that did not work</h2></div>', {
headers: {
'Content-type': 'text/html',
},
});
});
}),
);
console.log(`on fetch - ${CACHENAME}`);
};
I am hosting all of my assets (inc SW) using http-server (https://www.npmjs.com/package/http-server)
It's a simple mistake.
In your fetch event handler, you should NOT call evt.waitUntil. You should call evt.respondWith.
I have registered, active and cached resources via service worker, now i would like to know how to fetch JSON data and update the app shell using service worker.
I have registered, active and cached resources via service worker, now i would like to know how to fetch JSON data and update the app shell using service worker.
var CACHE_NAME = 'my-cache-v1';
var urlsToCache = [
'/common.css',
'/common.js'
];
self.addEventListener('install', function (e) {
console.log('[Service Worker] Install');
e.waitUntil(
caches.open(CACHE_NAME).then(function (cache) {
console.log('[Service Worker] Caching app shell');
return cache.addAll(urlsToCache);
}).then(function(e){
return self.skipWaiting();
})
);
});
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
var fetchRequest = event.request.clone();
return fetch(fetchRequest).then(
function(response) {
// Check if we received a valid response
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
self.addEventListener('activate', function(e) {
console.log('[ServiceWorker] Activate');
e.waitUntil(
caches.keys().then(function(keyList) {
return Promise.all(keyList.map(function(key) {
if (key !== CACHE_NAME) {
console.log('[ServiceWorker] Removing old cache', key);
return caches.delete(key);
}
}));
})
);
return self.clients.claim();
});
One example to emulate is https://github.com/GoogleChrome/sw-precache/tree/master/app-shell-demo
It integrates sw-precache in a build process to keep the App Shell resources (HTML, JS, CSS) up to date.
It uses runtime caching (via a dependency on sw-toolbox) to keep JSON API responses cached, as well as images.
It's based on a slightly out of date version of React/react-router/react-redux, but the general principles should still apply.
I am trying to cache images from the LastFM API. Now, the API response itself is cached fine and the page loads as expected, however, there are no images.
Looking into the developer console, I can see a couple of errors regarding the retrieval of the images and they're as follows:
Fetch API cannot load http://img2-ak.lst.fm/i/u/300x300/a32cb06bb22bc5d10654a5156fe78cf6.png. The parent document page has been unloaded.
I'm very new to service workers and this has got me tearing my hair out (no, not literally).
I've followed a couple of tutorials and this is the code I've got so far (irrelevant code has been stripped out).
var CACHE_NAME = 'WELFORDIAN-CACHE-V2';
var urlsToCache = [
'https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=**USERNAME**&api_key=6136000ba0899c52db5ebcee77d4be15&format=json'
];
self.addEventListener('install', function(event) {
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', function(event) {
var reqURL = new URL(event.request.url);
if (/lst.fm/.test(reqURL)) {
event.respondWith(lastFMImageResponse(event.request));
} else {
event.respondWith(
caches.match(event.request).then(function(response) {
if (response) {
return response;
}
var fetchRequest = event.request.clone();
return fetch(fetchRequest).then(
function(response) {
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
}).catch(function(err) {
return err;
})
);
}
});
function lastFMImageResponse(request) {
return caches.match(request).then(function(response) {
if (response) {
return response;
}
return fetch(request).then(function(response) {
caches.open('lfm-images').then(function(cache) {
cache.put(request, response);
});
return response.clone();
});
});
}
I'm obviously doing something wrong, but can anyone with more experience than I explain what it is?
The problem is that your website is HTTPS, while the images are served via HTTP.
Mixed content is currently not working well with service workers: https://github.com/w3c/webappsec/issues/415.