Is it possible to show an offline cache of my website when the server is down?
All the examples I can find regarding offline pages has to do with the client being offline. What I need is to show the user a cached version of my site if the server can't be reached.
I've read about the Cache Manifest in HMTL 5 but it's getting removed and it causes to many problems.
What can be done without using any other loadbalancing servers and such?
I recently learned that with Fetch API and service workers its dead simple:
First, you register the Service worker:
if (!navigator.serviceWorker) return;
navigator.serviceWorker.register('/sw.js')
Then configure it to cache whats needed:
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(staticCacheName).then(function(cache) {
return cache.addAll([
'/',
'js/main.js',
'css/main.css',
'imgs/icon.png',
]);
})
);
});
And use Fetch API to get cached peaces if no response from the call:
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request).then(function(response) {
return response || fetch(event.request);
})
);
});
if need to get cached version only if server is down, try something like:
self.addEventListener('fetch', function(event) {
event.respondWith(
return fetch(event.request).then(function(response) {
if (response.status !== 200) {
caches.match(event.request).then(function(response) {
return response;
}).catch(function(error) {
console.error('Fetching failed:', error);
throw error;
});
})
);
});
p.s. using Fetch API seems much nicer way than implementing old and nasty XMLHttpRequest.
Related
Although I see similar questions regarding the subject, there are not the same. So, I have a pwa app that is basically a simple form that users must fill out. The service worker catch the resources and serves to the app, as usual. The strategy is 'cache first, then network'. All is ok, when is onLine, BUT in offLine mode, the cached resources are not used by the app, I mean, in spite that (you can see) in the cache are the resources(fetch requests) that the app needs, it anyway try to fetch to the web and obviously because there is offLine, the fetch fail and the app crash. So, the code lines ...
caches.match(e.request)
.then( res => {
if (res ){
return res;
}
...
is not working. My question....Why???.
I will appreciate you help/comments.
You should add some more context and code, to let others better understand your situation.
Do you serve data from the cache like the following example?
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.open('mysite-dynamic').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;
});
});
})
);
});
Our platform is old angular (v4) and we use plain JavaScript file (sw.js) to register Service worker in index html page. I see install and activate are working fine. Just simple events [for testing]
self.addEventListener('install', function(event) {
event.waitUntil(self.skipWaiting()); // Activate worker immediately
});
self.addEventListener('activate', function(event) {
event.waitUntil(self.clients.claim()); // Become available to all pages
});
Then Angular component routes to our main page via router.navigate('pagename')
and browser even routes to that page, but service worker fetch event is not triggered..
self.addEventListener('fetch', event => event.respondWith(onFetch(event)));
Any idea how to fix this issue? Thank you.
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
return fetch(event.request);
}
)
);
});
I transformed my React application into a PWA and it is working partially fine.
I followed this tutorial: https://medium.com/#toricpope/transform-a-react-app-into-a-progressive-web-app-pwa-dea336bd96e6
However this article only shows how to cache static data and I also need to store data stemming from the server, I could do this following the instruction of the first answer of this post: How can I cache data from API to Cache Storage in React PWA? and inserting the firebase adresses where the data is stored into the array urlsToCache, populated by the files that should be stored into the cache.
So far so good, however after the data be stored into the cache, the application stops fetching data from the server and loads the page only with data from the cache, even if the server is updated. This is what I need to fix.
In short, I need to fetch the data from the server, store it into the cache in order to use it when the application is offline and update the cache every time the server is reached.
I am trying to follow this guide, but without success: https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#serving-suggestions
This is my worker.js file:
var CACHE_NAME = 'pwa-task-manager';
var urlsToCache = [
'/',
'/completed',
'/index.html',
'/static/js/main.chunk.js',
'/static/js/0.chunk.js',
'/static/js/1.chunk.js',
'/static/js/bundle.js',
'/calculadora',
'https://calc-marmo.firebaseio.com/clientes.json',
'https://calc-marmo.firebaseio.com/adm.json',
];
// Install a service worker
self.addEventListener('install', event => {
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
// Cache and return requests
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
return fetch(event.request);
}
)
);
});
// Update a service worker
self.addEventListener('activate', event => {
var cacheWhitelist = ['pwa-task-manager'];
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});
Any help would be much appreciated.
This sound like you need a Network First strategy which is not mention in the cookbook. This strategy is similar to Network falling back to cache but additionally stores the response always in the cache.
Explanation: https://developers.google.com/web/tools/workbox/modules/workbox-strategies#network_first_network_falling_back_to_cache
Code sample (if you don't use workbox): https://gist.github.com/JMPerez/8ca8d5ffcc0cc45a8b4e1c279efd8a94
I'm building an PWA with limited offline capability, I'm using this code to save page content to dynamic cache every time user visits a new url:
self.addEventListener('fetch', function(event) {
event.respondWith(
fetch(event.request)
.then(function(res) {
return caches.open('cache')
.then(function(cache) {
cache.put(event.request.url, res.clone());
return res;
})
})
.catch(function(err) {
console.log( err );
return caches.match(event.request);
})
);
});
This works great, after a page is loaded all of it assets are cached and can be seen in offline mode.
But, I would also like to add the option to automatically cache some of the more important urls when the user comes back online.
I do that by putting the list of urls in the array, loop through it and send a fetch request to each url, so those pages can be cached without user visiting/revisiting the page.
Problem is that when I do that some of the assets on some pages are not cached, for example google map on one page, is there a way to simulate real visit to a page, that gets all of the assets from an url with fetch request?
Fetch code:
function fillDynamicCache(user_id = false) {
let urls = [
'/homepage',
'/someotherpage',
'/thirdpage',
'/...',
];
urls.map((url, id) => (
fetch(url)
.then(
function(response) {
if (response.status !== 200) {
console.log('Looks like there was a problem. Status Code: ' +
response.status);
return;
}
console.log( 'in fetch: ' + url );
}
)
.catch(function(err) {
console.log('Fetch Error :-S', err);
})
));
}
self.addEventListener('message', (event) => {
// refresh cache when user comes back online
if (event.data == 'is_online') {
fillDynamicCache();
} else if (event.data == 'is_updated') {
self.skipWaiting();
Typically if you have important assets you want to provide the users, even when they are offline, you should consider an offline first strategy, meaning you prefetch those resources while the service worker is installing.
This way the matching requests will be served from the cache, improving the performance because you skip the relative network calls entirely.
In case the target resources tend to update/change frequently on the server, then you can opt for a stale while revalidate strategy (after the data is provided from the cache, the SW will update its value with a newer one from the network, if available) or even network first, fallback to cache, the latter if you want to provide always the latest values and provide cache data only if the network connection times out or is unavailable.
I wrote an article about service worker and caching strategies, in case you want to go deeper into the topic.
I'm trying to integrate service workers into my app, but I've found the service worker tries to retrieve cached content even when online, but I want it to prefer the network in these situations. How can I do this? Below is the code I have now, but I don't believe it is working. SW Install code is omitted for brevity.
var CACHE_NAME = 'my-cache-v1';
var urlsToCache = [
/* my cached file list */
];
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);
})
);
});
/* request is being made */
self.addEventListener('fetch', function(event) {
event.respondWith(
//first try to run the request normally
fetch(event.request).catch(function() {
//catch errors by attempting to match in cache
return caches.match(event.request).then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
});
})
);
});
This seems to lead to warnings like The FetchEvent for "[url]" resulted in a network error response: an object that was not a Response was passed to respondWith(). I'm new to service workers, so apologies for any mistaken terminology or bad practices, would welcome any tips. Thank you!
Without testing this out, my guess is that you're not resolving respondWith() correctly in the case where there is no cache match. According to MDN, the code passed to respondWith() is supposed to "resolve by returning a Response or network error to Fetch." So why not try just doing this:
self.addEventListener('fetch', function(event) {
event.respondWith(
fetch(event.request).catch(function() {
return caches.match(event.request);
})
);
});
Why don't you open the cache for your fetch event?
I think the process of a service worker is :
Open your cache
Check if the request match with an answer in your cache
Then you answer
OR (if the answer is not in the cache) :
Check the request via the network
Clone your answer from the network
Put the request and the clone of the answer in your cache for future use
I would write :
self.addEventListener('fetch', event => {
event.respondWith(
caches.open(CACHE_NAME).then(cache => {
return cache.match(event.request).then(response => {
return response || fetch(event.request)
.then(response => {
const responseClone = response.clone();
cache.put(event.request, responseClone);
})
})
}
);
});
event.respondWith() expects a promise that resolves to Response. So in case of a cache miss, you still need to return a Response, but above, you are returning nothing. I'd also try to use the cache first, then fetch, but in any case, as the last resort, you can always create a synthetic Response, for example something like this:
return new Response("Network error happened", {"status" : 408, "headers" : {"Content-Type" : "text/plain"}});