I have a progressive web app that works offline, but sometimes when I'm offline and I'm not inside of the 'bookmark' and I open it, I get a white screen with the error: "Safari cannot open the page. The error was: "FetchEvent.respondWith received an error: TypeError: The Internet connection appears to be offline."
I suppose this is coming from the "Fetch" event on the service worker, and this is my code:
self.addEventListener('fetch', function (event) {
const {
request,
request: {
url,
method,
},
} = event;
if (event.request.url.startsWith(self.location.origin)) {
event.respondWith(
caches.match(event.request)
.catch(function(error){
return;
})
.then(function (res) {
if (event.request.method == 'POST' && !navigator.onLine) { //Prevent POST since they give errors.
if (res != 'undefined') {
return res;
}
return fetch(event.request);
} else {
return fetchAndUpdate(event.request);
}
})
);
}else{
return;
}
})
Am I missing something or am I looking at the wrong place? Any advice would be appreciated!
Related
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
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
I have integrated service worker in my website. Everything used to works perfectly, but now, I have an error when my service worker try to install :
Uncaught (in promise) TypeError: Request failed at anonymous service-worker.js:1
And my service worker is in the 'redundant' state.
I don't know why... I did not change my code, this is my index.html :
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js').then(function(reg) {
if(reg.installing) {
console.log('Service worker installing');
} else if(reg.waiting) {
console.log('Service worker installed');
} else if(reg.active) {
console.log('Service worker active');
}
}).catch(function(error) {
// registration failed
console.log('Registration failed with ' + error);
});
}
And here is my service-worker.js :
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open('v1').then(function(cache) {
return cache.addAll([
'/',
'/theme/website_mobile/js',
'/theme/website_mobile/css',
'/theme/website_mobile/js.js',
'/theme/website_mobile/css.css',
'/js/614cd8e.js',
'/css/f1407bb.css',
'/js/93779bc.js',
'/js/d228ec7.js',
'/theme/website_mobile/img/slider-home/slider1.jpg',
'/theme/website_mobile/img/slider-home/slider2.jpg',
'/theme/website_mobile/img/slider-home/slider3.jpg',
'/theme/website_mobile/img/slider-home/slider4.jpg',
'/theme/website_mobile/img/logo-website.png',
'/theme/website_mobile/img/picto-menu-close.png',
'/theme/website_mobile/img/picto-close.png',
'/var/website/storage/images/media/website-medias/website-materials/5163440-1-eng-GB/website-materials_article_list_main_website_enm.jpg',
'/theme/website_mobile/fonts/website-montserrat/Montserrat-Light.woff2',
'/theme/website_mobile/fonts/website-montserrat/Montserrat-Regular.woff2',
'/theme/website_mobile/fonts/website-montserrat/Montserrat-ExtraBold.woff2',
'/theme/website_mobile/fonts/website-avenir/Fonts/065a6b14-b2cc-446e-9428-271c570df0d9.woff2',
]);
})
);
});
self.addEventListener('fetch', function(event) {
event.respondWith(caches.match(event.request).then(function(response) {
// caches.match() always resolves
// but in case of success response will have value
if (response !== undefined) {
return response;
} else {
return fetch(event.request).then(function (response) {
// response may be used only once
// we need to save clone to put one copy in cache
// and serve second one
let responseClone = response.clone();
caches.open('v1').then(function (cache) {
cache.put(event.request, responseClone);
});
return response;
}).catch(function (e) {
return caches.match('/');
});
}
}));
});
A more stranger thing is when I tried to edit my service-worker.js, I added this :
self.addEventListener('install', function(event) {
**reg.update();**
which is a mistake, but I figured out that this works good ! I have an reg is undefined error in the console, but my service worker works good.
I tried to change the reg.update() part and put a simple console.log but, when I did that, the service-worker return in the redundant state and don't install...
I don't understand why when if I add an undefined object in the service worker code, it throw an error but it works great, and when I came back to my old code (which used to work before), it didn't install.
Maybe, I am doing it wrong somewhere... ?
Thanks
I just ran into this after pulling my hair out for several days.
In my case one of the URLs of the list was returning a 404, which made the entire Cache.addAll return promise reject with the extremely unspecific error TypeError: Request failed ("failed on what, why?!").
If your business logic allows for graceful caching, despite any one of the urls failing, you might want to change your approach to Cache.add of every item of the list:
var urls = ['/', ...]
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open('v1').then(function(cache) {
urls.forEach(function (url) {
cache.add(url).catch(/* optional error handling/logging */);
});
})
);
});
I'm trying to use a service worker to do the following tasks:
Cache a set of pages.
Return a page from the cache if requested - if the page is not in the cache return from the server.
If there is no response from either the cache or the server return a custom offline page.
I have the following code:
this.addEventListener('install', function(event) {
console.log('Service Worker: Installing');
event.waitUntil(
caches.open('v1').then(function(cache) {
console.log('Service Worker: Caching Files');
return cache.addAll([
'/assets/logo.svg',
'/assets/app.css',
'/assets/app.js',
'/assets/common.js',
'/offline.html'
]);
})
);
});
this.addEventListener('fetch', function(event) {
if (event.request.method != "GET" || event.request.mode == "cors" || event.request.url.startsWith('chrome-extension')) return;
console.log('Event to be passed', event);
event.respondWith(
caches.open('v1').then(function(cache) {
return cache.match(event.request).then(function(response) {
if (response) {
console.log('Service Worker: Returning Cached Response', response);
return response;
} else {
fetch(event.request).then(function(response) {
console.log('Service Worker: Returning Response from Server', response);
cache.put(event.request, response.clone());
return response;
}).catch((error) => {
console.log('Fetch failed; returning offline page instead.', error);
return caches.match('/offline.html');
});
}
});
})
);
});
this.addEventListener('activate', function activator(event) {
console.log('Service Worker: Activating', event);
event.waitUntil(
caches.keys().then(function(keys) {
return Promise.all(keys
.filter(function(key) {
return key.indexOf('v1') !== 0;
})
.map(function(key) {
return caches.delete(key);
})
);
})
);
});
The issue I have is that even when offline I get a 200 response for my request, even though I'm offline and not actually retrieving the file.
I understand this is one of the pre-requisites for a PWA to always return a 200 response, but how can I return an offline page if my response always returns as 200.
Any help, advice or resources on service workers would be useful.
Cheers.
you must handle all events, you can't just "return" if nothing special should happen.
this.addEventListener('fetch', function(event) {
if (event.request.method != "GET" || event.request.mode == "cors" || event.request.url.startsWith('chrome-extension')){
event.respondWith(fetch(event.request));
return;
}
....
then you must also return your fetch event, otherwise you break the promise chain.
} else {
return fetch(event.request).then(function(response) {
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);
}));
}));
});