How to make PWA detect any HTML changes and update it automatically? - javascript

Essentially I'm wondering what to change in my sw.js file so that if I add a new HTML element, when the user opens up the PWA it takes a moment to update and refresh itself and display the new content as well?
Currently when I'm connected to the Wifi, the PWA does not make any changes, and when I disconnect my Wifi, it only caches my HTML file and gets rid of my CSS. So I guess my follow up question is how do I make the other pages cache as well so that they're available offline? (My app is only 200kb).
Here is my sw.js code:
// On install - caching the application shell
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open('sw-cache').then(function(cache) {
// cache any static files that make up the application shell
return cache.add('index.html');
})
);
});
// On network request
self.addEventListener('fetch', function(event) {
event.respondWith(
// Try the cache
caches.match(event.request).then(function(response) {
//If response found return it, else fetch again
return response || fetch(event.request);
})
);
});

Related

Can can I reset an app's styling with a PWA and service worker

I have a PWA made with HTML, CSS and JS with Node.js, everytime I change the styles.css of the app, I have to upload it again, I.e. change the port. For example in localhost:3000 it would have the old styling, but if I upload it to localhost:3100, the styling changed to the new one, how can I make it so that cached css files will be deleted and uploaded with the new ones?
This is my service worker:
var CACHE_NAME = 'version-1'; // bump this version when you make changes.
// Put all your urls that you want to cache in this array
var urlsToCache = [
'index.html',
'assets/logo-192.png',
'images/airplane.png',
'images/backspace.png',
'images/calcToggle.png',
'images/diamond.png',
'images/favicon.png',
'images/hamburger.png',
'images/history.png',
'images/like.png',
'images/love.png',
'images/menu2.png',
'images/menu3.png',
'images/menu4.png',
'images/menu5.png',
'images/menu6.png',
'images/menu7.png',
'images/menu8.png',
'images/plane.png',
'images/science.png',
'images/settings.png',
'images/trash.png',
'styles.css'
];
// Install the service worker and open the cache and add files mentioned in array to cache
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
// keep fetching the requests from the user
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);
})
);
});
self.addEventListener('activate', function(event) {
var cacheWhitelist = []; // add cache names which you do not want to delete
cacheWhitelist.push(CACHE_NAME);
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(cacheName) {
if (!cacheWhitelist.includes(cacheName)) {
return caches.delete(cacheName);
}
})
);
})
);
});
If you are doing this for development just open your dev tools. Select the application tab, then the service worker panel.
Click the 'Bypass for Network' option.
I wrote an article on service worker dev best practices that might help:
https://love2dev.com/serviceworker/development-best-practices/
If you need to update in production that is different. I generally do a periodic HEAD request on the network asset (CSS file in your example). If the resource is newer than the cached version I update to the latest version as needed.
I have other techniques I use from time to time as well. It varies by application and the requirements, etc.

How to make persistent PWA cache?

I've been trying to make offline only PWAs for Android, but the site's cache keeps clearing every so often. Is there any way to make the cache stay permanently?
You can define caching strategies for static assets and data requests for your service worker.
In the following article about service workers and caching strategies I list the different strategies and describe when it makes more sense to implement a specific one.
You can cache static assets and provide them offline when the SW is installing. Those files should be only the "minimum" version of your app (usually called app shell). Because of this, the cache.addAll method rejects the promise if it is not possible to get any of the resources. This means the service worker will install successfully only if all the targeted resources are cached.
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open('staticAssetsCache').then(function(cache) {
return cache.addAll(
[
'/css/bootstrap.css',
'/css/styles.css',
'/js/jquery.min.js',
'/offline.html'
]
);
})
);
});
You can also cache HTTP GET Requests, for example below the stale while revalidate strategy that returns the data from the cache, if available, and in the background attempts to fetch and cache a newer version from the network:
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.open('www.my-web-app.com')
.then(function(cache) {
return cache.match(event.request)
.then(function(response) {
var fetchPromise = fetch(event.request).then(function(networkResponse) {
cache.put(event.request, networkResponse.clone());
return networkResponse;
})
// response contains cached data, if available
return response || fetchPromise;
})
})
);
});
If you are using Angular or Workbox library, https://dev.to/paco_ita/create-progressive-web-apps-with-angular-workbox-pwa-builder-step-4-27d for more details.
I believe I read somewhere iOS Safari and Chrome would invalidate the cache frequently to get new updates. No logic behind it, just re-fetching the files.
Solution:
(In a recent Chrome devlog, it mentions a reduction in frequency from 3 days to 1)
to prevent the clearing of the cache / IndexDB I found this.
if (navigator.storage && navigator.storage.persist)
// '.persist()' will silently pass or trigger a dialog
navigator.storage.persist().then(function(persistent) {
alert(persistent ? 'persistent' : 'denied');
})
else
alert('not available - iOS / ancient Android?');

Service Worker: How to cache the first (dynamic) page

I have this one-page app with a dynamic URL built with a token, like example.com/XV252GTH and various assets, like css, favicon and such.
Here is how I register the Service Worker:
navigator.serviceWorker.register('sw.js');
And in said sw.js, I pre-cache the assets while installing:
var cacheName = 'v1';
var cacheAssets = [
'index.html',
'app.js',
'style.css',
'favicon.ico'
];
function precache() {
return caches.open(cacheName).then(function (cache) {
return cache.addAll(cacheAssets);
});
}
self.addEventListener('install', function(event) {
event.waitUntil(precache());
});
Note that the index.html (that registers the Service Worker) page is just a template, that gets populated on the server before being sent to the client ; so in this pre-caching phase, I'm only caching the template, not the page.
Now, in the fetch event, any requested resource that is not in the cache gets copied to it:
addEventListener('fetch', event => {
event.respondWith(async function() {
const cachedResponse = await caches.match(event.request);
if (cachedResponse) return cachedResponse;
return fetch(event.request).then(updateCache(event.request));
}());
});
Using this update function
function updateCache(request) {
return caches.open(cacheName).then(cache => {
return fetch(request).then(response => {
const resClone = response.clone();
if (response.status < 400)
return cache.put(request, resClone);
return response;
});
});
}
At this stage, all the assets are in the cache, but not the dynamically generated page. Only after a reload, can I see another entry in the cache: /XV252GTH. Now, the app is offline-ready ; But this reloading of the page kind of defeats the whole Service Worker purpose.
Question: How can I send the request (/XV252GTH) from the client (the page that registers the worker) to the SW? I guess I can set up a listener in sw.js
self.addEventListener('message', function(event){
updateCache(event.request)
});
But how can I be sure that it will be honored in time, ie: sent by the client after the SW has finished installing? What is a good practice in this case?
OK, I got the answer from this page: To cache the very page that registers the worker at activation time, just list all the SW's clients, and get their URL (href attribute).
self.clients.matchAll({includeUncontrolled: true}).then(clients => {
for (const client of clients) {
updateCache(new URL(client.url).href);
}
});
Correct me if I understood you wrong!
You precache your files right here:
var cacheAssets = [
'index.html',
'app.js',
'style.css',
'favicon.ico'
];
function precache() {
return caches.open(cacheName).then(function (cache) {
return cache.addAll(cacheAssets);
});
}
It should be clear that you cache the template since you cache it before the site gets build and this approach is not wrong, at least not for all types of files.
Your favicon.ico for example is a file that you would probably consider as static. Also, it does not change very often or not at all and it isn't dynamic like your index.html.
Source
It should also be clear why you have the correct version after reloading the page since you have an update function.
The solution to this problem is the answer to your question:
How can I send the request (/XV252GTH) from the client (the page that registers the worker) to the SW?
Instead of caching it before the service-worker is installed you want to cache it if the back end built your web page. So here is how it works:
You have an empty cache or at least a cache without your index.html.
Normally a request would be sent to the server to get the index.html. Instead, we do a request to the cache and check if the index.html is in the cache, at least if you load the page for the first time.
Since there is no match in the cache, do a request to the server to fetch it. This is the same request the page would do if it would load the page normally. So the server builds your index.html and sends it back to the page.
After receiving the index.html load it to the page and store it in the cache.
An example method would be Stale-while-revalidate:
If there's a cached version available, use it, but fetch an update for next time.
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.open('mysite-dynamic').then(function(cache) {
return cache.match(event.request).then(function(response) {
var fetchPromise = fetch(event.request).then(function(networkResponse) {
cache.put(event.request, networkResponse.clone());
return networkResponse;
})
return response || fetchPromise;
})
})
);
});
Source
Those are the basics for your problem. Now you got a wide variety of options you can choose from that use the same method but have some additional features. Which one you choose is up to you and without knowing your project in detail no one can tell you which one to choose. You are also not limited to one option. In some cases you might combine two or more options together.
Google wrote a great guide about all the options you have and provided code examples for everything. They also explained your current version. Not every option will be interesting and relevant for you but I recommend you to read them all and read them thoroughly.
This is the way to go.

Create React App ServiceWorker.js Redirect When Offline

I have a React app created by using create-react-app. By default, this tool creates a serviceWorker.js file for us and I am using this to register a service-worker. Furthermore, the documents suggest using google's workbox wizard to create a service-worker.js used to manage my website for offline purposes. The goal is for me to store an offline.html page in the browsers cache and whenever there is no online connection, render the cached offline.html page.
I am successful in storing the offline.html in cache and as you can see below, it is stored in the precached URLS (check last two rows).
I can also manually navigate to the offline.html if i change the URL in my browser.
However, I am having trouble automatically grabbing this file and rendering it whenever there isn't a connection.
In the serviceWorker.js code that is generated for me from CRA theres a function called checkValidServiceWorker:
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
const OFFLINE_URL = '/.offline/offline.html';
return caches.match(OFFLINE_URL).then((response) => {
console.log(response)
});
});
}
So in the catch part of the function, I want to do my redirect because thats the logic that runs when we are offline. I read a lot of docs and my current solution doesn't work. Any ideas on how to redirect in my serviceWorker?

Why is my service worker only caching visited pages?

Im currently developing a simple PWA with an index file that contains an inputfield where you can enter your medication barcode and hit submit. A PHP-file will then be displayed showing some information about the drugs recieved from a MySQL database.
Im now trying to cache the app shell in order to load the necessairy resources (html, css, scripts etc.) once the app is working offline. I kinda succeeded in this, but it only seems to work when the page that you're trying to cache is already visited once when the app was online. And this only is the case when I register that same service-worker on the result page, not only on the index page.
Apparantly, this kind of caching in which only the visited pages are cached is called 'partial' caching by my professor. I don't want this partial caching, I want a complete caching in which the service-worker is registered once on the index file and then caches all the necessairy files into to cache so that I can use it when offline.
This is how my service-worker looks right:
var cacheName = 'pillexplainerPWA';
var filesToCache = [
'http://localhost:80/MedicationProject/',
'http://localhost:80/MedicationProject/index.html',
'http://localhost:80/MedicationProject/result.php',
'http://localhost:80/MedicationProject/result2.html',
'http://localhost:80/MedicationProject/scripts/app.js',
'http://localhost:80/MedicationProject/styles/main.css',
'http://localhost:80/MedicationProject/styles/result.css',
'http://localhost:80/MedicationProject/images/back-button.jpg',
'http://localhost:80/MedicationProject/images/barcode.jpg'
];
self.addEventListener('install', function(e) {
console.log('[ServiceWorker] Install');
e.waitUntil(
caches.open(cacheName).then(function(cache) {
console.log('[ServiceWorker] Caching app shell');
return cache.addAll(filesToCache);
})
);
});
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 !== cacheName) {
console.log('[ServiceWorker] Removing old cache', key);
return caches.delete(key);
}
}));
})
);
return self.clients.claim();
});
self.addEventListener('fetch', function(e) {
console.log('[ServiceWorker] Fetch', e.request.url);
e.respondWith(
caches.match(e.request).then(function(response) {
return response || fetch(e.request);
})
);
});
Can someone explain to me what I'm doing wrong and what needs to be adjusted to this code so that it won't cache only what is visited but everything the moment that index.html is loaded? The strange thing is, when I look into the cache when index.html is fired..it does show me all my files being there. But when I go offline and then hit that submit button to go to the result page, it doesn't load the cache.

Categories

Resources