Random Service Worker Response Error - javascript

I'm using Service Worker and Cache API to cache static resources but randomly http request to REST API endpoint (which is not being cached in SW) fails and the only message I have is xhr.statusText with text Service Worker Response Error and response code 500.
I can't tell if this error happens only with URLs that are not being cached or not. There is not enough evidence about either. This happens only in Chrome (50.0.2661.75 - 64b) and it works in Firefox 45
I wasn't able to reproduce it manually as it happens in Selenium tests and it appears to be random. Moreover it happens on localhost (where SW should work despite plain http) but also in domain with HTTPS that has self-signed certificate and as such SW should not even work there ...
Selenium tests are often refreshing pages and closing browser window but I have no idea if it matters.
Any ideas why it could be happening or how to get more information?
Update:
Service Worker code:
var VERSION = "sdlkhdfsdfu89q3473lja";
var CACHE_NAME = "cache" + VERSION;
var CACHE_PATTERN = /\.(js|html|css|png|gif|woff|ico)\?v=\S+?$/;
function fetchedFromNetwork(response, event) {
var cacheCopy = response.clone();
var url = event.request.url;
if (url.indexOf("/api/") === -1 // must not be a REST API call
&& url.indexOf(VERSION) > -1 // only versioned requests
&& VERSION !== "$CACHE_VERSION"
&& CACHE_PATTERN.test(url)) { //
caches.open(CACHE_NAME)
.then(function add(cache) {
cache.put(event.request, cacheCopy);
});
}
return response;
}
function unableToResolve() {
return new Response("Service Unavailable", {
status: 503,
statusText: "Service Unavailable",
headers: new Headers({
"Content-Type": "text/plain"
})
});
}
this.addEventListener("fetch", function (event) {
// cache GET only
if (event.request.method !== "GET") {
return;
}
event.respondWith(
caches.match(event.request)
.then(function (cached) {
if (cached) {
return cached;
} else {
return fetch(event.request)
.then(function (response) {
return fetchedFromNetwork(response, event);
}, unableToResolve)
.catch(unableToResolve);
}
}, function () { // in case caches.match throws error, simply fetch the request from network and rather don't cache it this time
return fetch(event.request);
}));
});
this.addEventListener("activate", function (event) {
event.waitUntil(
caches.keys()
.then(function (keys) {
return Promise.all(
keys.filter(function (key) {
// Filter out caches not matching current versioned name
return !key.startsWith(CACHE_NAME);
})
.map(function (key) {
// remove obsolete caches
return caches.delete(key);
}));
}));
});

Related

Service worker.js is not updating changes. Only if cache is cleared

This is my first PWA app with laravel. This code is working,it gets registered well, but if I do a change in the code, for example in the HTML, it is not getting update, and the console is not throwing errors, and I dont know why.
I'm using this code to call the service-worker.js
if ('serviceWorker' in navigator ) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/service-worker.js').then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}, function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
});
}
And this is the code of the sw.js
var cache_name = 'SW_CACHE';
var urlsToCache = [
'/',
'/register'
];
self.addEventListener('install', function(event) {
event.waitUntil(precache());
});
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;
}
);
})
);
});
var fromCache = function (request) {
return caches.open(cache_name).then(function (cache) {
cache.match(request).then(function (matching) {
return matching || Promise.resolve('no-match');
});
});
}
var update = function (request) {
return caches.open(cache_name).then(function (cache) {
return fetch(request).then(function (response) {
return cache.put(request, response);
});
});
}
var precache = function() {
return caches.open(cache_name).then(function (cache) {
return cache.addAll(urlsToCache);
});
}
Y also used skipWaiting(); method inner Install method, but it crash my app and have to unload the sw from chrome://serviceworker-internals/
This is what service worker lifecycle suppose to work: a new service worker won't take place, unless:
The window or tabs controlled by the older service worker are closed and reopened
'Update on reload' option is checked in Chrome devtools
Here is an official tutorial explained it well: The Service Worker Lifecycle
Service worker will always use the existing worker. Two thinks you can do is in chrome there is an option to set update on load
Goto InspectorWindow (f12) -> application -> and check update on reload.
if you want immediate update you can choose the network first cache approach. which will take the latest from server always and use the cache only in offline mode. see the link for more information
How API is getting cached effectively, Using Service worker in Angular 5 using Angular CLI

ServiceWorker blocks any external resource

I've implemented ServiceWorker on some of my webapps to leverage offline capabilities and some other goodies related to the ServiceWorkers. Now, everything is working fine until I have added some external embedded scripts. There are about 3 external scripts that I've added on my webapp. Some of which fetch ads and display it on my app and some of which are used to gather analytics.
But, to my surprise, every external scripts are failing when the ServiceWorkers are enabled and throws below error in the console
I'm not sure why is this happening? Do I have to cache these scripts some way in ServiceWorker? Any help would be appreciated.
Here is my ServiceWorker code that I've added on my app.
importScripts('js/cache-polyfill.js');
var CACHE_VERSION = 'app-v18';
var CACHE_FILES = [
'/',
'index.html',
'js/app.js',
'js/jquery.min.js',
'js/bootstrap.min.js',
'css/bootstrap.min.css',
'css/style.css',
'favicon.ico',
'manifest.json',
'img/icon-48.png',
'img/icon-96.png',
'img/icon-144.png',
'img/icon-196.png'
];
self.addEventListener('install', function (event) {
event.waitUntil(
caches.open(CACHE_VERSION)
.then(function (cache) {
console.log('Opened cache');
return cache.addAll(CACHE_FILES);
})
);
});
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request).then(function(res){
if(res){
return res;
}
requestBackend(event);
})
)
});
function requestBackend(event){
var url = event.request.clone();
return fetch(url).then(function(res){
//if not a valid response send the error
if(!res || res.status !== 200 || res.type !== 'basic'){
return res;
}
var response = res.clone();
caches.open(CACHE_VERSION).then(function(cache){
cache.put(event.request, response);
});
return res;
})
}
self.addEventListener('activate', function (event) {
event.waitUntil(
caches.keys().then(function(keys){
return Promise.all(keys.map(function(key, i){
if(key !== CACHE_VERSION){
return caches.delete(keys[i]);
}
}))
})
)
});
Here's what I did to resolve the issue.
I tried checking if the app is online or not using Navigator.onLine API in the fetch event listener and if it's online, then serve the response from the server and from ServiceWorker otherwise. This way the requests won't get blocked while the app is online and thus resolves this particular issue.
Here's how I've implemented it.
self.addEventListener('fetch', function (event) {
let online = navigator.onLine;
if(!online){
event.respondWith(
caches.match(event.request).then(function(res){
if(res){
return res;
}
requestBackend(event);
})
)
}
});
You could also check the entire ServiceWorker script here: https://github.com/amitmerchant1990/notepad/blob/master/sw.js

Service Worker : How to handle a 302 redirect response

I installed a service worker on my application, it gets installed well, activated well, and the caching is ok too.
But when the caching is done when I click on a page that is a 302, it tells me:
The FetchEvent for "http://localhost:8000/form/" resulted in a network error response: a redirected response was used for a request whose redirect mode is not "follow".
I've been reading a lot on the subject, I've consulted the posts here : Service Worker breaking 301 redirects,
and there https://github.com/w3c/ServiceWorker/issues/737
and there https://github.com/GoogleChromeLabs/sw-precache/issues/220
As I understand the default redirect mode when fetching is {redirect: "follow"}, but when I catch the redirect mode from my redirected page I can see it is {redirect: "manual"} So basically I would have to do something when it is "manual".
Thought I'm a bit confused and I'm struggling on how to implement this in my code.
Here's my code:
const STATIC_CACHE_NAME = 'exell-static-v28';
const DYNAMIC_CACHE_NAME = 'exell-dynamic-v4';
// INSTALLING THE SERVICE WORKER AND PRECACHING APPSHELL
self.addEventListener('install', function(event) {
console.log('[Service Worker] Service Worker installed');
event.waitUntil(
caches.open(STATIC_CACHE_NAME) // Create a static cache
.then(function(cache) {
console.log('[Service Worker] Precaching App Shell');
cache.addAll([ // Add static files to the cache
'/',
'/build/app.js',
'/build/global.css',
'login',
'logout',
'offline',
'form/',
'form/new/first_page',
'form/new/second_page',
'form/new/third_page',
'form/new/fourth_page',
'form/new/fifth_page',
'form/new/sixth_page',
'profile/',
'build/fonts/BrandonGrotesque-Medium.a989c5b7.otf',
'build/fonts/BrandonText-Regular.cc4e72bd.otf',
]);
})
);
});
// ACTIVATING THE SERVICE WORKER
self.addEventListener('activate', function(event) {
console.log('[Service Worker] Service Worker activated');
event.waitUntil(
caches.keys()
.then(function(keyList) {
return Promise.all(keyList.map(function(key) {
if (key !== STATIC_CACHE_NAME && key !== DYNAMIC_CACHE_NAME) { // If old cache exists
console.log('[Service Worker] Deleting old cache', key);
return caches.delete(key); // Delete it and replace by new one
}
}));
})
);
return self.clients.claim();
});
// FETCHING
self.addEventListener('fetch', function(event) {
// Do not waste time with files we don't want to cache
if (event.request.url.match(/ajax.js/)) {
return;
}
event.respondWith(
caches.match(event.request) // Retrieve data from the cache
.then(function(response) {
if (response) {
return response; // If there is a response, return it
} else {
return fetch(event.request) // Otherwise fetch from network
.then(function(res) {
return caches.open(DYNAMIC_CACHE_NAME)
.then(function(cache) {
cache.put(event.request.url, res.clone()); // Store the response in the dynamic cache
return res; // And return the response
});
})
.catch(function() { // If no network
return caches.open(STATIC_CACHE_NAME) // Open the static cache
.then(function(cache) {
cache.match('offline'); // Look for the offline default template and return it
});
});
}
})
);
});
Any 30x responses are expected to have a type property that resolves to "opaqueredirect", which you could check for, and react to appropriately. Maybe you would want to check this link: Response.type
opaqueredirect: The fetch request was made with redirect: "manual".The Response's status is 0, headers are empty, body is null and trailer is empty.
Therefore to solve your issue, you should check for:
response.type === 'opaqueredirect'
instead of any checks relating to response.status, for example similar to(the checks bellow will not work as response.status will be 0)
response.status === 301 || response.status === 302
Cheers, and happy coding!
Possible solution
For the pages that can possibly throw a 3xx ... simply don’t cache stuff.
https://medium.com/#boopathi/service-workers-gotchas-44bec65eab3f
self.addEventListener('fetch', event => {
// path throw a 3xx - don’t cache
if(event.request.url == '{{url}}' ) {
return;
}
...
}

Service worker Fetch

I'm using a Service Worker for working offline. so for Each Fetch request, i store it in the cache.
Now,
I'd like that the service worker will make a request, and store it as well for next time.
the problem is when i use fetch(myUrl).then... , the Fetch Listener self.addEventListener('fetch', function(e)...in the service worker doesn't catch it.
I wouldn't like to duplicate code.. any Ideas ?
The fetch listener is:
self.addEventListener('fetch', function(e) {
// e.respondWidth Responds to the fetch event
e.respondWith(
// Check in cache for the request being made
caches.match(e.request)
.then(function(response) {
// If the request is in the cache
if ( response ) {
console.log("[ServiceWorker] Found in Cache", e.request.url, response);
// Return the cached version
return response;
}
// If the request is NOT in the cache, fetch and cache
var requestClone = e.request.clone();
return fetch(requestClone)
.then(function(response) {
if ( !response ) {
console.log("[ServiceWorker] No response from fetch ")
return response;
}
var responseClone = response.clone();
// Open the cache
caches.open(cacheName).then(function(cache) {
// Put the fetched response in the cache
cache.put(e.request, responseClone);
console.log('[ServiceWorker] New Data Cached', e.request.url);
// Return the response
return response;
}); // end caches.open
// returns the fresh response (not cached..)
return response;
})
.catch(function(err) {
console.log('[ServiceWorker] Error Fetching & Caching New Data', err);
});
}) // end caches.match(e.request)
.catch(function(e){
// this is - if we still dont have this in the cache !!!
console.log("[ServiceWorker] ERROR WITH THIS MATCH !!!",e, arguments)
})// enf of caches.match
); // end e.respondWith
});
Since i cant comment to get any specific details and your code seems right to me, my guesses are:
You Service worker is not registered, or running. you can mak sure It is running by checking the application tab of your inspector. It should look like the following:
You assume It is not working because of the messages not being logged to the console.
Service workers run on a different environment from your page, so the messages are logged to a different console that you can check by clicking the inspect button in the image above.
The Cache then Network strategy is probably what you are looking for:
https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#cache-then-network
var networkDataReceived = false;
startSpinner();
// fetch fresh data
var networkUpdate = fetch('/data.json').then(function(response) {
return response.json();
}).then(function(data) {
networkDataReceived = true;
updatePage();
});
// fetch cached data
caches.match('/data.json').then(function(response) {
if (!response) throw Error("No data");
return response.json();
}).then(function(data) {
// don't overwrite newer network data
if (!networkDataReceived) {
updatePage(data);
}
}).catch(function() {
// we didn't get cached data, the network is our last hope:
return networkUpdate;
}).catch(showErrorMessage).then(stopSpinner);

Javascript service worker: Fetch resource from cache, but also update it

I'm using a service worker on chrome to cache network responses. What I intend to do when a client requests a resource:
Check cache - If it exists, return from cache, but also send a request to server and update cache if file differs from the cached version.
If cache does not have it, send a request for it to the server and then cache the response.
Here's my current code for doing the same:
self.addEventListener('fetch', function (event) {
var requestURL = new URL(event.request.url);
var freshResource = fetch(event.request).then(function (response) {
if (response.ok && requestURL.origin === location.origin) {
// All good? Update the cache with the network response
caches.open(CACHE_NAME).then(function (cache) {
cache.put(event.request, response);
});
}
// Return the clone as the response would be consumed while caching it
return response.clone();
});
var cachedResource = caches.open(CACHE_NAME).then(function (cache) {
return cache.match(event.request);
});
event.respondWith(cachedResource.catch(function () {
return freshResource;
}));
});
This code does not work as it throws an error:
The FetchEvent for url resulted in a network error response: an object that was not a Response was passed to respondWith().
Can anyone point me in the right direction?
Okay, I fiddled with the code after people pointed out suggestions (thank you for that) and found a solution.
self.addEventListener('fetch', function (event) {
var requestURL = new URL(event.request.url);
var freshResource = fetch(event.request).then(function (response) {
var clonedResponse = response.clone();
// Don't update the cache with error pages!
if (response.ok) {
// All good? Update the cache with the network response
caches.open(CACHE_NAME).then(function (cache) {
cache.put(event.request, clonedResponse);
});
}
return response;
});
var cachedResource = caches.open(CACHE_NAME).then(function (cache) {
return cache.match(event.request).then(function(response) {
return response || freshResource;
});
}).catch(function (e) {
return freshResource;
});
event.respondWith(cachedResource);
});
The entire problem originated in the case where the item is not present in cache and cache.match returned an error. All I needed to do was fetch actual network response in that case (Notice return response || freshResource)
This answer was the Aha! moment for me (although the implementation is different):
Use ServiceWorker cache only when offline

Categories

Resources