How to add webpack hash name to service worker file? - javascript

I'm studying service worker for my app.
This service worker cookbook(https://serviceworke.rs) is very helpful for me.
I'm configuring "Cache, update and refresh" strategy but can not know that how to add webpack hash name to service worker file.
For this, there is a few option, I know,
Workbox (https://github.com/GoogleChrome/workbox)
sw-precache (https://github.com/GoogleChromeLabs/sw-precache)
sw-precache-webpack-plugin (https://github.com/goldhand/sw-precache-webpack-plugin#readme)
but I just want to make service worker file without plugin or 3rd tools AND put webpack chunkhash file name into my service worker file.
my-sw.js
var CACHE = 'cache-update-and-refresh';
self.addEventListener('install', function(evt) {
console.log('The service worker is being installed.');
evt.waitUntil(caches.open(CACHE).then(function (cache) {
cache.addAll([
'./controlled.html',
'./asset',
// here is problem. how to put this?
/**
main.72b835f75acd535f504c.js,
vendor.d6b44fd0b72071c85ce2.js,
aboutUs.d31139a7f363d5254560.js,
...etc...
**/
]);
}));
});
self.addEventListener('fetch', function(evt) {
console.log('The service worker is serving the asset.');
evt.respondWith(fromCache(evt.request));
evt.waitUntil(
update(evt.request)
.then(refresh)
);
});
function fromCache(request) {
return caches.open(CACHE).then(function (cache) {
return cache.match(request);
});
}
function update(request) {
return caches.open(CACHE).then(function (cache) {
return fetch(request).then(function (response) {
return cache.put(request, response.clone()).then(function () {
return response;
});
});
});
}
// Sends a message to the clients.
function refresh(response) {
return self.clients.matchAll().then(function (clients) {
clients.forEach(function (client) {
var message = {
type: 'refresh',
url: response.url,
eTag: response.headers.get('ETag')
};
client.postMessage(JSON.stringify(message));
});
});
}

Related

PWA application: issues with caching

I am developing a PWA app. Although I am a beginner in JavaScript and HTML, I was able to almost complete the coding. But I have an issue with caching.
The app is quite simple: in a single page there are some images that are used to display PDF files. When no network is present, it should work locally from the cache. The app will be used on an Android tablet. I ma testing on Windows 10 using Chrome. That was working fine. During the tests, if a PDF file was changed on the server, and then the network was back I needed to empty the cache for the app to show the new PDF file. I modified the design to use the cache then network method, but that does not work. I just can't figure out what's wrong. Here is the actual code.
/sw.js
const staticAssets = [
'/',
'/index.html',
'/sw.js',
'css/bootstrap.min.css',
'js/app.js',
'js/bootstrap.min.js',
'js/jquery.min.js',
'js/jquery.slim.min.js',
'js/popper.min.js',
'/icons/generic_128.png',
'icons/generic_512.png',
'images/Ashkeepers.png',
'images/Canvases.png',
'images/Forfaits thématiques.png',
'images/Funeral urn.png',
'images/logo fictif.png',
'images/Memorial products.png',
'images/Produits commémoratifs.png',
'images/Reliquaires.png',
'images/Thematic packages.png',
'images/Toiles.png',
'images/Urnes.png',
'pdf/02_urnes_funeraires_02_ENG_HR.pdf',
'pdf/02_urnes_funeraires_04_HR.pdf',
'pdf/03_toiles_03_ENG_HR.pdf',
'pdf/03_toiles_04_HR.pdf',
'pdf/04_forfaits_thematiques_05_ENG_HR.pdf',
'pdf/04_forfaits_thematiques_12_HR.pdf',
'pdf/05_reliquaires_03_ENG_HR.pdf',
'pdf/05_reliquaires_04_HR.pdf',
'pdf/06_produits_funeraires_02_ENG_HR.pdf',
'pdf/06_produits_funeraires_03_HR.pdf'
];
self.addEventListener('install', function (event) {
console.log('App Installed');
event.waitUntil(
caches.open('static')
.then(function (cache) {
cache.addAll(staticAssets);
})
);
});
self.addEventListener('activate', function () {
console.log('App Activated');
});
/*
Version originale
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(res) {
if (res) {
console.log("From cache");
return res;
} else {
console.log("From Internet");
return fetch(event.request);
}
})
);
});
*/
/* Cache the network */
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.open('static').then(function(cache) {
return fetch(event.request).then(function(response) {
cache.put(event.request, response.clone());
console.log('Data en cache: ' + response.clone()); /* AJOUTÉ */
return response;
});
})
);
});
/js/App.js
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(function() {
console.log('SW registered');
});
}
/js/feed.js
var networkDataReceived = false;
// fetch fresh data
var networkUpdate = fetch('/data.json').then(function(response) {
return response.json();
}).then(function(data) {
networkDataReceived = true;
updatePage(data);
}).catch(function() {
/* Il faut attraper l'exception si data.json est introuvable */
});
// 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;
});
Can someone bring some light to my poor brain ?
Thanks a lot
EDITED EDITED EDITED
I forgot to tell how I display PDF files:
<a href="pdf/03_toiles_03_ENG_HR.pdf" class="mx-auto d-block">
Can that explains the issues I have ? I know that a service worker scope is specific, and I wonder if it could be that the SW don't work on the 'page' that's used to display the PDF.

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

Progressive web app Uncaught (in promise) TypeError: Failed to fetch

I started learning PWA (Progressive Web App) and I have problem, console "throws" error Uncaught (in promise) TypeError: Failed to fetch.
Anyone know what could be the cause?
let CACHE = 'cache';
self.addEventListener('install', function(evt) {
console.log('The service worker is being installed.');
evt.waitUntil(precache());
});
self.addEventListener('fetch', function(evt) {
console.log('The service worker is serving the asset.');
evt.respondWith(fromCache(evt.request));
});
function precache() {
return caches.open(CACHE).then(function (cache) {
return cache.addAll([
'/media/wysiwyg/homepage/desktop.jpg',
'/media/wysiwyg/homepage/bottom2_desktop.jpg'
]);
});
}
function fromCache(request) {
return caches.open(CACHE).then(function (cache) {
return cache.match(request).then(function (matching) {
return matching || Promise.reject('no-match');
});
});
}
I think this is due to the fact that you don't have a fallback strategy. event.respondWith comes with a promise which you have to catch if there's some error.
So, I'd suggest that you change your code from this:
self.addEventListener('fetch', function(evt) {
console.log('The service worker is serving the asset.');
evt.respondWith(fromCache(evt.request));
});
To something like this:
addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
if (response) {
return response; // if valid response is found in cache return it
} else {
return fetch(event.request) //fetch from internet
.then(function(res) {
return caches.open(CACHE_DYNAMIC_NAME)
.then(function(cache) {
cache.put(event.request.url, res.clone()); //save the response for future
return res; // return the fetched data
})
})
.catch(function(err) { // fallback mechanism
return caches.open(CACHE_CONTAINING_ERROR_MESSAGES)
.then(function(cache) {
return cache.match('/offline.html');
});
});
}
})
);
});
NOTE: There are many strategies for caching, what I've shown here is offline first approach. For more info this & this is a must read.
I found a solution to the same error, in my case the error showed when the service worker could not find a file*, fix it by following the network in dev tool of chrome session, and identified the nonexistent file that the service worker did not find and removed array of files to register.
'/processos/themes/base/js/processos/step/Validation.min.js',
'/processos/themes/base/js/processos/Acoes.min.js',
'/processos/themes/base/js/processos/Processos.min.js',
'/processos/themes/base/js/processos/jBPM.min.js',
'/processos/themes/base/js/highcharts/highcharts-options-white.js',
'/processos/themes/base/js/publico/ProcessoJsController.js',
// '/processos/gzip_457955466/bundles/plugins.jawrjs',
// '/processos/gzip_N1378055855/bundles/publico.jawrjs',
// '/processos/gzip_457955466/bundles/plugins.jawrjs',
'/mobile/js/about.js',
'/mobile/js/access.js',
*I bolded the solution for me... I start with just a file for cache and then add another... till I get the bad path to one, also define the scope {scope: '/'} or {scope: './'} - edit by lawrghita
I had the same error and in my case Adblock was blocking the fetch to an url which started by 'ad' (e.g. /adsomething.php)
In my case, the files to be cached were not found (check the network console), something to do with relative paths, since I am using localhost and the site is inside a sub-directory because I develop multiple projects on a XAMPP server.
So I changed
let cache_name = 'Custom_name_cache';
let cached_assets = [
'/',
'index.php',
'css/main.css',
'js/main.js'
];
self.addEventListener('install', function (e) {
e.waitUntil(
caches.open(cache_name).then(function (cache) {
return cache.addAll(cached_assets);
})
);
});
To below: note the "./" on the cached_assets
let cache_name = 'Custom_name_cache';
let cached_assets = [
'./',
'./index.php',
'./css/main.css',
'./js/main.js'
];
self.addEventListener('install', function (e) {
e.waitUntil(
caches.open(cache_name).then(function (cache) {
return cache.addAll(cached_assets);
})
);
});
Try to use / before adding or fetching any path like /offline.html or /main.js
Cached files reference should be correct otherwise the fetch will fail. Even if one reference is incorrect the whole fetch will fail.
let cache_name = 'Custom_name_cache';
let cached_files = [
'/',
'index.html',
'css/main.css',
'js/main.js'
];
// The reference here should be correct.
self.addEventListener('install', function (e) {
e.waitUntil(
caches.open(cache_name).then(function (cache) {
return cache.addAll(cached_files);
})
);
});

service worker image cache

My service worker perform a cache on my offline.html and resources ( jpg, js, css files) for this html page. That works so good for a few hours but after that, only remains cached the offline.html but not the jpg, js, css files.
I wonder if the browser has a limit (time, weigth, etc). the service worker fails? Do I should use cache.put instead cache.addAll?, why is failing?.
//This is the "Offline page" service worker
//Install stage sets up the offline page in the cahche and opens a new cache
self.addEventListener('install', function(event) {
var offlinePage = new Request('offline.html');
event.waitUntil(
fetch(offlinePage).then(function(response) {
return caches.open('offline1').then(function(cache) {
console.log('[off] Cached offline page during Install'+ response.url);
return cache.put(offlinePage, response);
});
}));
});
//If any fetch fails, it will show the offline page.
//Maybe this should be limited to HTML documents?
self.addEventListener('fetch', function(event) {
event.respondWith(
fetch(event.request).catch(function(error) {
console.error( '[off] Network request Failed. Serving offline page ' + error );
return caches.open('offline1').then(function(cache) {
return cache.match('offline.html');
});
}));
});
//cache of files that are part of offline.html cache.put
var ASSETS = [
'/pwa/offline/js/main.js',
'/upload/off-1about.jpg',
'/upload/off-1best-bg.jpg',
'/upload/off-1gallery-bg.jpg',
'/upload/off-1intro-bg.jpg',
'/upload/off-1logo.png',
'/upload/off-1menu-bg.jpg',
'/upload/off-best-01.jpg',
'/upload/off-best-02.jpg',
'/upload/off-best-03.jpg',
'/upload/off-call-me-32.png'
];
self.oninstall = function (evt) {
evt.waitUntil(caches.open('offline1').then(function (cache) {
return Promise.all(ASSETS.map(function (url) {
return fetch(url).then(function (response) {
return cache.put(url, response);
});
}));
}))
};
// or
//cache of files that are part of offline.html cache.addAll
/*
var ASSETS = [
'/pwa/offline/js/main.js',
'/upload/off-1about.jpg',
'/upload/off-1best-bg.jpg',
'/upload/off-1gallery-bg.jpg',
'/upload/off-1intro-bg.jpg',
'/upload/off-1logo.png',
'/upload/off-1menu-bg.jpg',
'/upload/off-best-01.jpg',
'/upload/off-best-02.jpg',
'/upload/off-best-03.jpg',
'/upload/off-call-me-32.png'
];
self.oninstall = function (evt) {
evt.waitUntil(caches.open('offline-cache-name').then(function (cache) {
return cache.addAll(ASSETS);
}))
};
*/
//This is a event that can be fired from your page to tell the SW to update the offline page
self.addEventListener('refreshOffline', function(response) {
return caches.open('offline1').then(function(cache) {
console.log('[off] Offline page updated from refreshOffline event: '+ response.url);
return cache.put(offlinePage, response);
});
});

Updating Web App User Interface when application is in background FCM

Am using FCM to handle notifications, it works fine up until when I need to update my UI from the firebase-messaging-sw.js when my web app is in the background.
My first question is: is it possible to update my web app UI in the background (When user is not focused on the web app) through a service worker
Secondly, if so, how? because I tried a couple of things and its not working, obviously am doing something wrong and when it does work, my web app is in the foreground. What am I doing wrong?
My codes are below.
my-firebase-service-sw.js
// [START initialize_firebase_in_sw]
// Give the service worker access to Firebase Messaging.
// Note that you can only use Firebase Messaging here, other Firebase
libraries
// are not available in the service worker.
importScripts('https://www.gstatic.com/firebasejs/4.1.1/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/4.1.1/firebase-messaging.js');
// My Custom Service Worker Codes
var CACHE_NAME = 'assembly-v0.1.3.1';
var urlsToCache = [
'/',
'lib/vendors/bower_components/animate.css/animate.min.css',
'lib/vendors/bower_components/sweetalert/dist/sweetalert.css',
'lib/css/app_1.min.css',
'lib/css/app_2.min.css',
'lib/css/design.css'
];
var myserviceWorker;
var servicePort;
// Install Service Worker
self.addEventListener('install', function (event) {
console.log('installing...');
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(function (cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
console.log('installed...');
});
// Service Worker Active
self.addEventListener('activate', function (event) {
console.log('activated!');
// here you can run cache management
var cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then(function (cacheNames) {
return Promise.all(
cacheNames.map(function (cacheName) {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});
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;
}
);
})
);
});
self.addEventListener('message', function (event) {
console.log("SW Received Message: " + event.data);
// servicePort = event;
event.ports[0].postMessage("SW Replying Test Testing 4567!");
});
myserviceWorker = self;
// Initialize the Firebase app in the service worker by passing in the
// messagingSenderId.
firebase.initializeApp({
'messagingSenderId': '393093818386'
});
// Retrieve an instance of Firebase Messaging so that it can handle background
// messages.
const messaging = firebase.messaging();
// [END initialize_firebase_in_sw]
// If you would like to customize notifications that are received in the
// background (Web app is closed or not in browser focus) then you should
// implement this optional method.
// [START background_handler]
messaging.setBackgroundMessageHandler(function (payload) {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
// Customize notification here
// send to client
console.log('Sending data to notification');
try {
myserviceWorker.clients.matchAll().then(function (clients) {
clients.forEach(function (client) {
console.log('sending to client ' + client);
client.postMessage({
"msg": "401",
"dta": payload.data
});
})
});
} catch (e) {
console.log(e);
}
const notificationTitle = payload.data.title;;
const notificationOptions = {
body: payload.data.body,
icon: payload.data.icon,
click_action: "value"
};
return self.registration.showNotification(notificationTitle,
notificationOptions);
});
// [END background_handler]
In my main javascript file, which receives the payload. it receives it when the application is in the foreground. My major concern and problem is receiving payload when the application is in the background, all activities on foreground works just fine.
It is possible to update the UI even your website is opening but unfocused.
Just add enable option includeUncontrolled when you get all client list.
Example:
messaging.setBackgroundMessageHandler(function (payload) {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
self.clients.matchAll({includeUncontrolled: true}).then(function (clients) {
console.log(clients);
//you can see your main window client in this list.
clients.forEach(function(client) {
client.postMessage('YOUR_MESSAGE_HERE');
})
})
});
In your main page, just add listener for message from service worker.
Ex:
navigator.serviceWorker.addEventListener('message', function (event) {
console.log('event listener', event);
});
See Clients.matchAll() for more details.

Categories

Resources