I want to ask about service workers. I made a web application and try to implement services worker. I'm using .hbs for my views layout and when I cache static files I can't cache the .hbs, .css and .js files.
public/
css/
style.css
js/
app.js
manifest.json
service-worker.js
views/
home.hbs
partial/
header.hbs
*footer.hbs
When I deploy my app it fails to cache the home.hbs, style.css and my app.js file; I cant access my web offline.
What should I do to fix it?
This is my app.js:
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('./service-worker.js', { scope: './service-worker.js' })
.then(function(registration) {
console.log("Service Worker Registered");
})
.catch(function(err) {
console.log("Service Worker Failed to Register", err);
})
}
var get = function(url) { return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var result = xhr.responseText
result = JSON.parse(result);
resolve(result);
} else {
reject(xhr);
}
}
};
xhr.open("GET", url, true);
xhr.send();
}); };
This is my service worker:
var cacheName = 'v1'; var cacheFiles = [ './login', './css/styles.css', './js/app.js', ]; self.addEventListener('install', function(e) {
console.log('[ServiceWorker] Installed');
// e.waitUntil Delays the event until the Promise is resolved
e.waitUntil(
// Open the cache
caches.open(cacheName).then(function(cache) {
// Add all the default files to the cache console.log('[ServiceWorker] Caching cacheFiles'); return cache.addAll(cacheFiles);
}) ); // end e.waitUntil });
self.addEventListener('activate', function(e) {
console.log('[ServiceWorker] Activated');
e.waitUntil(
// Get all the cache keys (cacheName) caches.keys().then(function(cacheNames) { return Promise.all(cacheNames.map(function(thisCacheName) {
// If a cached item is saved under a previous cacheName
if (thisCacheName !== cacheName) {
// Delete that cached file
console.log('[ServiceWorker] Removing Cached Files from Cache - ', thisCacheName);
return caches.delete(thisCacheName);
} })); }) ); // end e.waitUntil
});
self.addEventListener('fetch', function(e) { console.log('[ServiceWorker] Fetch', e.request.url);
// 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();
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
})
.catch(function(err) {
console.log('[ServiceWorker] Error Fetching & Caching New Data', err);
});
}) // end caches.match(e.request) ); // end e.respondWith });
What should I do so I can cache the .hbs file?
I think you should call the .hbs the same way you would call them on your app router.
If you have in your router :
router.get('/home', (req, res) => {
res.render('home');
});
then in your service worker you can cache it like:
var filesToCache = [
'/home',
'css/style.css',
'js/app.js'
];
No dot before js, and you mispelled 'style'.
Hope that helps someone
Your service worker code is really bad. I hope there is a typo, but the cache.addAll() funciton is commented out!
Anyway, I will give this a try...
var cacheName ='v2';
var filesToCache = [
'/',
'public/css/style.css',
'js/app.js',
'views/home.hbs',
];
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);
}).then(function() {
return self.skipWaiting();
})
);
});
...
...
Related
I'm writing a bible webpage, and I want to use a service worker.
I've got the following code in my service-worker.js:
var CACHE_NAME = 'my-site-cache-v1';
var urlsToCache = [
'/',
'/verse/_verse.js',
'/verse/_verse.css',
'/home/_home.js',
'/home/_home.css',
'/bookmarks/_bookmarks.js',
'/bookmarks/_bookmarks.css',
'/search/_search.js',
'/search/_search.css'
];
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);
})
);
});
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
console.log("found response")
console.log(event.request.url)
return response;
}
return fetch(event.request).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;
}
);
})
);
});
Which is mostly code just taken from the google service worker introduction. However It seems to be only caching the images, css, js and font files - but I'd like it to cache the HTML of a page as well, to make it truly available offline.
All my content on my site is routed through to index.php and served from there, based off the REQUEST_URI. How can I modify the service worker code to save the HTML of a URI request and serve it back in case its offline?
I would like to use a service worker to cache files and improve user experience by providing offline pages. I used the pwabuilder.com to create the files for the website. Unfortunately, even when using the code without any elements to cache it issues the error "Uncaught (in promise) TypeError: Request failed"
I double checked the code, I tried the different bug fixes shown on Google Developper and Stackoverflow, but none of these helped me fix the issue.
I have this in the HTML file:
if ("serviceWorker" in navigator) {
if (navigator.serviceWorker.controller) {
console.log("[PWA Builder] active service worker found, no need to register");
} else {
// Register the service worker
navigator.serviceWorker
.register("pwabuilder-sw.js", {
scope: "./"
})
.then(function (reg) {
console.log("[PWA Builder] Service worker has been registered for scope: " + reg.scope);
});
}
}
The service worker registers sucessfully.
The pwabuilder-sw.js is this one:
//This is the service worker with the Advanced caching
const CACHE = "pwabuilder-adv-cache";
const precacheFiles = [
/* Add an array of files to precache for your app */
'/cms/stylesheets/bootstrap.css',
'/cms/stylesheets/ifpayroll.css',
'/cms/stylesheets/animate.css',
'/cms/stylesheets/fontawesome-webfont.css',
'/cms/javascript/main.js',
'/cms/javascript/aos.js',
'/cms/images/logo#3x.png',
];
// TODO: replace the following with the correct offline fallback page i.e.: const offlineFallbackPage = "offline.html";
const offlineFallbackPage = "ToDo-replace-this-name.html";
const networkFirstPaths = [
/* Add an array of regex of paths that should go network first */
// Example: /\/api\/.*/
];
const avoidCachingPaths = [
/* Add an array of regex of paths that shouldn't be cached */
// Example: /\/api\/.*/
];
function pathComparer(requestUrl, pathRegEx) {
return requestUrl.match(new RegExp(pathRegEx));
}
function comparePaths(requestUrl, pathsArray) {
if (requestUrl) {
for (let index = 0; index < pathsArray.length; index++) {
const pathRegEx = pathsArray[index];
if (pathComparer(requestUrl, pathRegEx)) {
return true;
}
}
}
return false;
}
self.addEventListener("install", function (event) {
console.log("[PWA Builder] Install Event processing");
console.log("[PWA Builder] Skip waiting on install");
self.skipWaiting();
event.waitUntil(
caches.open(CACHE).then(function (cache) {
console.log("[PWA Builder] Caching pages during install");
return cache.addAll(precacheFiles).then(function () {
if (offlineFallbackPage === "ToDo-replace-this-name.html") {
return cache.add(new Response("TODO: Update the value of the offlineFallbackPage constant in the serviceworker."));
}
return cache.add(offlineFallbackPage);
});
})
);
});
// Allow sw to control of current page
self.addEventListener("activate", function (event) {
console.log("[PWA Builder] Claiming clients for current page");
event.waitUntil(self.clients.claim());
});
// If any fetch fails, it will look for the request in the cache and serve it from there first
self.addEventListener("fetch", function (event) {
if (event.request.method !== "GET") return;
if (comparePaths(event.request.url, networkFirstPaths)) {
networkFirstFetch(event);
} else {
cacheFirstFetch(event);
}
});
function cacheFirstFetch(event) {
event.respondWith(
fromCache(event.request).then(
function (response) {
// The response was found in the cache so we responde with it and update the entry
// This is where we call the server to get the newest version of the
// file to use the next time we show view
event.waitUntil(
fetch(event.request).then(function (response) {
return updateCache(event.request, response);
})
);
return response;
},
function () {
// The response was not found in the cache so we look for it on the server
return fetch(event.request)
.then(function (response) {
// If request was success, add or update it in the cache
event.waitUntil(updateCache(event.request, response.clone()));
return response;
})
.catch(function (error) {
// The following validates that the request was for a navigation to a new document
if (event.request.destination !== "document" || event.request.mode !== "navigate") {
return;
}
console.log("[PWA Builder] Network request failed and no cache." + error);
// Use the precached offline page as fallback
return caches.open(CACHE).then(function (cache) {
cache.match(offlineFallbackPage);
});
});
}
)
);
}
function networkFirstFetch(event) {
event.respondWith(
fetch(event.request)
.then(function (response) {
// If request was success, add or update it in the cache
event.waitUntil(updateCache(event.request, response.clone()));
return response;
})
.catch(function (error) {
console.log("[PWA Builder] Network request Failed. Serving content from cache: " + error);
return fromCache(event.request);
})
);
}
function fromCache(request) {
// Check to see if you have it in the cache
// Return response
// If not in the cache, then return error page
return caches.open(CACHE).then(function (cache) {
return cache.match(request).then(function (matching) {
if (!matching || matching.status === 404) {
return Promise.reject("no-match");
}
return matching;
});
});
}
function updateCache(request, response) {
if (!comparePaths(request.url, avoidCachingPaths)) {
return caches.open(CACHE).then(function (cache) {
return cache.put(request, response);
});
}
return Promise.resolve();
}
It is 100% the same as the one provided on pwabuilder.com except for the cached files that have been added.
Manifest: unknown 'orientation' value ignored.
pwabuilder-sw.js:83 [PWA Builder] Install Event processing
pwabuilder-sw.js:87 [PWA Builder] Skip waiting on install
pwabuilder-sw.js:97 [PWA Builder] Caching pages during install
legal.html:63 [PWA] Service worker has been registered for scope: https://www.ifpayroll.lu/
pwabuilder-sw.js:1 Uncaught (in promise) TypeError: Request failed
This is what I get.
You have to change this line to reflect your real filename, and make sure that file exists:
const offlineFallbackPage = "ToDo-replace-this-name.html";
Then you can also delete these lines:
if (offlineFallbackPage === "ToDo-replace-this-name.html") {
return cache.add(new Response("TODO: Update the value of the offlineFallbackPage constant in the serviceworker."));
}
The offline fallback page instruction is badly documented IMO/annoying.
These lines can be removed:
if (offlineFallbackPage === "ToDo-replace-this-name.html") {
return cache.add(new Response("TODO: Update the value of the offlineFallbackPage constant in the serviceworker."));
}
I have a problem with my service worker. I use CAS for logging in to my application. When I close the web page and try to open it again, I receive the following error:
The page isn't redirecting properly
An error occurred during a connection to start.oacloud.org.
This problem can sometimes be caused by disabling or refusing to accept cookies.
Here is a screenshot showcasing the redirect loop.
Here is my service worker:
cacheName = 'portal-cache';
const staticAssets = [
'portal_.php',
'css/portal.css',
'css/portalI.css',
'css/portalJ.css',
'css/portalE.css',
'js/jquery-ui.css',
'js/jquery.js',
'js/jquery-ui.js',
'js/lodash.js',
'portal.js',
'js/jquery.ui.touch-punch.js',
'js/jquery-ui.structure.css',
'js/jquery-ui.structure.min.css',
'js/jquery-ui.theme.css',
'js/jquery-ui.theme.min.css'
];
self.addEventListener('install', function(event)
{
event.waitUntil(
caches.open(cacheName).then(function(cache) {
return cache.addAll(staticAssets);
})
);
});
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();
});
addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request).then(function(response)
{
if (response)
{
return response;
}
else
{
return fetch(event.request).then(function(res)
{
return caches.open('portal-dynamic')
.then(function(cache)
{
cache.put(event.request.url, res.clone());
return res;
})
}).catch(function(err)
{
return caches.open('error messages').then(function(cache)
{
return cache.match('portal_.php');
});
});
}
})
);
});
What exactly am I doing wrong that allows this loop to happen and how can I fix it?
I managed to fix this by fetching first from the internet, then from cache:
self.addEventListener('fetch', function(event) {
var requestURL = new URL(event.request.url);
if(requestURL.host =='your CAS server' || (requestURL.host =='page to redirect to' && requestURL.pathname == 'your server')){
console.log('IN: '+requestURL);
event.respondWith(fetch(event.request)
.catch(function() {
return new Response('{status: \'no internet connection\'}',
{headers: { 'Content-Type': 'text/html' }, status : 404}
);
}));
return;
}
console.log('The service worker is serving the asset.');
event.respondWith(
//check first if the request is for a static asset and serve it directly from cache
caches.open(cacheStatic).then(function(cache) {
return cache.match(event.request).then(function (response) {
return response ||
//if asset is not in static cache try to take it from the network
fetch(event.request).then(function(responseD) {
//save it in dynamic cache for when there is no network
return caches.open(cacheDynamic).then(function(cacheD) {
cacheD.put(event.request, responseD.clone());
return responseD;
});
}).catch(function(error) {
console.log('catch: '+requestURL);
//if unable to take it from the network, take it from dynamic cache
return caches.open(cacheDynamic).then(function(cacheD) {
return cacheD.match(event.request).then(function (response) {
return response || new Response('{status: \'no internet connection\'}',
{headers: { 'Content-Type': 'text/html' }, status : 404}
);
});
});
});
})
})
)
});
I am basically caching an offline page and loading it when my website is offline.
For this I have created a page say 'signIn' and the javascript file of 'SignIn' is javascript->pages->signIn here I have initiated the service worker like
javascript/page/script.js
if('serviceWorker' in navigator){
navigator.serviceWorker.register('./sw1.js').then(function(reg){
console.log("Service workers registered");
});
}
else{
console.log("Browser doesnt support service workers");
}
})
The sw1.js and the offline.html is basically in the same folder as the page SignIn.
self.addEventListener('install', function(event) {
var offlineRequest = new Request('./offline.html');
event.waitUntil(
fetch(offlineRequest).then(function(response) {
return caches.open('offline').then(function(cache) {
console.log('[oninstall] Cached offline page', response.url);
return cache.put(offlineRequest, response);
});
})
);
});
self.addEventListener('fetch', function(event) {
var request = event.request;
if (request.method === 'GET') {
console.log(request);
event.respondWith(
fetch(request).then(function(response){
console.log("From Network "+response);
return response;
}).catch(function(error) {
console.log(
'[onfetch] Failed. Serving cached offline fallback ' +
error
);
return caches.open('offline').then(function(cache) {
console.log("Opened Cache");
return cache.match('./offline.html');
});
})
);
}
});
Basically things work fine when I am online but When i am offline I get the following error in chrome-dev-tools-applications .
Please Help me.
I have the following code for a service worker:
'use strict';
// Incrementing CACHE_VERSION will kick off the install event and force previously cached
// resources to be cached again.
const CACHE_VERSION = 1;
let CURRENT_CACHES = {
offline: 'offline-v' + CACHE_VERSION
};
const OFFLINE_URL = 'offline.php';
function createCacheBustedRequest(url) {
let request = new Request(url, {cache: 'reload'});
// See https://fetch.spec.whatwg.org/#concept-request-mode
// This is not yet supported in Chrome as of M48, so we need to explicitly check to see
// if the cache: 'reload' option had any effect.
if ('cache' in request) {
return request;
}
// If {cache: 'reload'} didn't have any effect, append a cache-busting URL parameter instead.
let bustedUrl = new URL(url, self.location.href);
bustedUrl.search += (bustedUrl.search ? '&' : '') + 'cachebust=' + Date.now();
return new Request(bustedUrl);
}
self.addEventListener('install', event => {
event.waitUntil(
// We can't use cache.add() here, since we want OFFLINE_URL to be the cache key, but
// the actual URL we end up requesting might include a cache-busting parameter.
fetch(createCacheBustedRequest(OFFLINE_URL)).then(function(response) {
return caches.open(CURRENT_CACHES.offline).then(function(cache) {
return cache.put(OFFLINE_URL, response);
});
})
);
});
self.addEventListener('activate', event => {
// Delete all caches that aren't named in CURRENT_CACHES.
// While there is only one cache in this example, the same logic will handle the case where
// there are multiple versioned caches.
let expectedCacheNames = Object.keys(CURRENT_CACHES).map(function(key) {
return CURRENT_CACHES[key];
});
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (expectedCacheNames.indexOf(cacheName) === -1) {
// If this cache name isn't present in the array of "expected" cache names,
// then delete it.
console.log('Deleting out of date cache:', cacheName);
return caches.delete(cacheName);
}
})
);
})
);
});
self.addEventListener('fetch', event => {
// We only want to call event.respondWith() if this is a navigation request
// for an HTML page.
// request.mode of 'navigate' is unfortunately not supported in Chrome
// versions older than 49, so we need to include a less precise fallback,
// which checks for a GET request with an Accept: text/html header.
if (event.request.mode === 'navigate' ||
(event.request.method === 'GET' &&
event.request.headers.get('accept').includes('text/html'))) {
console.log('Handling fetch event for', event.request.url);
event.respondWith(
fetch(createCacheBustedRequest(event.request.url)).catch(error => {
// The catch is only triggered if fetch() throws an exception, which will most likely
// happen due to the server being unreachable.
// If fetch() returns a valid HTTP response with an response code in the 4xx or 5xx
// range, the catch() will NOT be called. If you need custom handling for 4xx or 5xx
// errors, see https://github.com/GoogleChrome/samples/tree/gh-pages/service-worker/fallback-response
console.log('Fetch failed; returning offline page instead.', error);
return caches.match(OFFLINE_URL);
})
);
}
// If our if() condition is false, then this fetch handler won't intercept the request.
// If there are any other fetch handlers registered, they will get a chance to call
// event.respondWith(). If no fetch handlers call event.respondWith(), the request will be
// handled by the browser as if there were no service worker involvement.
});
How could it be possible to make it in a way so that it does not have to save anything on cache? The webapp in question needs connection at all time. Therefore, the main purpose of this service worker is to qualify for phone installation and to have later on push notifications capabilities.
After some online research, here is the best solution:
sw_install.js
console.log('Started', self);
self.addEventListener('install', function(event) {
self.skipWaiting();
console.log('Installed', event);
});
self.addEventListener('activate', function(event) {
console.log('Activated', event);
});
self.addEventListener('push', function(event) {
console.log('Push message received', event);
// TODO
});
main.js
if ('serviceWorker' in navigator) {
console.log('Service Worker is supported');
navigator.serviceWorker.register('sw_install.js').then(function(reg){
console.log(':^)', reg);
// TODO
}).catch(function(err) {
console.log(':^(', err);
});
}
You can use npm sw-toolbox library (https://www.npmjs.com/package/sw-toolbox).
main.js
if (navigator.serviceWorker){
navigator.serviceWorker.register('/service-worker.js', {scope: './'})
.then(function (registration) {
console.log("sw registered",registration);
})
.catch(function (e) {
console.error("error",e);
})
} else {
console.log('Service Worker is not supported in this browser.')
}
}
service-worker.js
(global => {
'use strict';
// Load the sw-tookbox library.
importScripts('/js/sw-toolbox.js');
var precache_urls = [
'/index.html',
'/img/logo.png',
'/img/main.png'
];
//for debugging only
global.toolbox.options.debug = true;
global.toolbox.router.get('/img/(.*)', self.toolbox.cacheFirst, {
cache: {
name: "Images",
maxEntries: 10
}
});
global.toolbox.router.default = global.toolbox.networkFirst;
global.addEventListener('install', event => event.waitUntil(global.skipWaiting()));
global.addEventListener('activate', event => event.waitUntil(global.clients.claim()));
global.addEventListener('push', event => {
var pushObj = event.data.json();
var pushData = pushObj.data;
// push payload if there, if not make an ajax get call to get it (can use fetch)
var title = pushData && pushData.title;
var body = pushData && pushData.body;
var icon = '/img/logo.png';
event.waitUntil(global.registration.showNotification(title, {
body: body,
icon: icon,
badge: icon
data:pushData
}));
});
global.addEventListener('notificationclick', event => {
event.notification.close();
var url = event.notification.data.url|| '/';
event.waitUntil(
clients.matchAll({
type: 'window'
}).then(windowClients => {
console.log('WindowClients', windowClients);
for (var i = 0; i < windowClients.length; i++) {
var client = windowClients[i];
console.log('WindowClient', client);
if (client.url === url && 'focus' in client) {
return client.focus();
}
}
if (clients.openWindow) {
return clients.openWindow(url);
}
}));
});
})(self);