Why 404.html causes uncaught error during service worker caching? - firebase

I have implemented service worker in my web app and attempted to cache all html, css, js, and images file. My hosting service is firebase, after a successful deployment I tested if the service will work and the files will be cached, unfortunately the following error will do occur.
service-worker.js
let cacheName = 'my-tools-v1';
let filesToCache = [
'/',
'/css/app.css',
'/css/diffview.css',
'/css/json-lint.css',
'/css/materialize.css',
'/images/favicon.png',
'/images/icons/apple-touch-icon.png',
'/images/icons/apple-touch-icon-57x57.png',
'/images/icons/apple-touch-icon-72x72.png',
'/images/icons/apple-touch-icon-76x76.png',
'/images/icons/apple-touch-icon-114x114.png',
'/images/icons/apple-touch-icon-120x120.png',
'/images/icons/apple-touch-icon-144x144.png',
'/images/icons/apple-touch-icon-152x152.png',
'/images/icons/apple-touch-icon-180x180.png',
'/images/icons/icon-72x72.png',
'/images/icons/icon-96x96.png',
'/images/icons/icon-128x128.png',
'/images/icons/icon-144x144.png',
'/images/icons/icon-152x152.png',
'/images/icons/icon-192x192.png',
'/images/icons/icon-384x384.png',
'/images/icons/icon-512x512.png',
'/js/index.js',
'/js/css-lint.js',
'/js/difflib.js',
'/js/diffview.js',
'/js/ipsum-generator.js',
'/js/json2.js',
'/js/json-lint.js',
'/js/jsonlint.js',
'/js/lorem-ipsum.js',
'/js/materialize.js',
'/js/visual-difference.js',
'/bower_components/codemirror/lib/codemirror.js',
'/bower_components/codemirror/mode/javascript/javascript.js',
'/bower_components/codemirror/mode/css/css.js',
'/bower_components/codemirror/addon/edit/matchbrackets.js',
'/bower_components/codemirror/addon/comment/continuecomment.js',
'/bower_components/codemirror/addon/comment/comment.js',
'/bower_components/ft-csslint/dist/csslint.js',
'/bower_components/jquery/dist/jquery.slim.min.js',
'/bower_components/kanye-ipsum/dist/jquery.kanye-ipsum.min.js',
'/bower_components/codemirror/lib/codemirror.css',
'/index.html',
'/css-lint.html',
'/ipsum-generator.html',
'/json-lint.html',
'/visual-difference.html',
'/notfound.html',
'/404.html'
];
self.addEventListener('install', (e) => {
console.log('[ServiceWorker] Install');
e.waitUntil(
caches.open(cacheName).then((cache) => {
console.log('[ServiceWorker] Caching app shell');
return cache.addAll(filesToCache);
})
);
});
self.addEventListener('activate', (e) => {
console.log('[ServiceWorker] Activate');
e.waitUntil(
caches.keys().then(function(keyList) {
return Promise.all(keyList.map((key) => {
if (key !== cacheName) {
console.log('[ServiceWorker] Removing old cache', key);
return caches.delete(key);
}
}));
})
);
});
self.addEventListener('fetch', (e) => {
console.log('[ServiceWorker] Fetch', e.request.url);
e.respondWith(
caches.match(e.request).then((response) => {
return response || fetch(e.request);
})
);
});
Then all files were not cached because of this. But if I remove the 404.html or rename it to other name the service worker will work fine and all files will be cached. It is also weird that in my local server the service worker works and caches 404.html but it fails in firebase.
Why 404.html causes uncaught error during service worker caching? How do I resolve this?

Cache.addAll() is an all or nothing API. If any response is not in the 200 HTTP status code range, nothing will be cached.
cache.addAll will reject if any of the resources fail to cache. This means the service worker will only install if all of the resources in cache.addAll have been cached.
Firebase returns 404 Not Found for the /404.html file.
An approach to resolve this is to have a file /notfound.html like you have and then return that in fetch when needed.

You cannot add 404 error html page into cache, if it returns 404 https status (as it should).
But still you can create 404 error page with service worker and use it for sites not in cache even if website is in offline mode.
Using catch() after fetch() request and create whole new Response
minimalistic example:
// self is ServiceWorkerGlobalScope
self.addEventListener( 'fetch', function ( /** #type {FetchEvent} */ event )
{
//const caches = ( event.target.caches );
event.respondWith(
caches.match( event.request ).then( ( /** #type {Response | undefined} */ response ) =>
{
if ( response ) {
return response;
}
return fetch( event.request.clone() ).then( ( /** #type {Response} */ response ) =>
{
//…
return response;
}
).catch( () =>
{
return new Response( '<h1>404 - Not found</h1><p>more HTML here …</p>', {
status: 404,
statusText: 'Not found',
headers: new Headers( {
'Content-Type': 'text/html'
} )
} );
} );
}
)
);
} );

Related

how to on-demand revalidate all the pages at once

I'm trying to revalidate all the pages of my website on a certain event,
the problem that I'm running through is that I have to do it page by page :
...
try {
await res.unstable_revalidate(
`/`
);
await res.unstable_revalidate(
`/about`
;
await res.unstable_revalidate(
`/shop`
);
...
return res.json({ revalidated: true });
} catch (err) {
return res.status(500).send('Error revalidating');
}
So my question is: Is there a way to on-demand ( using unstable_revalidate() ) revalidate all the pages of my website, Or do I have to do it page by page ?

Cypress has started throwing 417 Expectation Failed error on all POST and PUT XMLHttpRequest

All POST and PUT XMLHttpRequest made into Cypress have recently started throwing 417 Expectation Failed. However all these work on the web application when I navigate through it manually.
All my code used to work well in past without any issue.
I read about this error over internet and I'm not sure if this issue exists on the application under test, or on some firewall policy or there is some setting in Cypress which can fix it.
Cypress.Commands.add("Post_Client", () => {
cy.fixture(Cypress.env("ClientInputFile")).then(clientoBJ => {
cy.fixture(clientoBJ.imagePath, "binary").then(imageBin => {
Cypress.Blob.binaryStringToBlob(imageBin, clientoBJ.imageType).then(
blob => {
const xhr = new XMLHttpRequest();
const data = new FormData();
data.set(clientoBJ.nameatr, clientoBJ.nameVal);
data.set(clientoBJ.imageatr, blob);
xhr.open(
"POST",
Cypress.env("APIBaseURL") + Cypress.env("ClientPostURL"),
false
);
xhr.setRequestHeader("accept", "application/json");
xhr.setRequestHeader("access-token", accesstoken);
xhr.setRequestHeader("client", client);
xhr.setRequestHeader("expiry", expiry);
xhr.setRequestHeader("token-type", tokentype);
xhr.setRequestHeader("uid", uid);
xhr.onload = function() {
if (this.status === 201) {
cy.writeFile(
Cypress.env("ClientOutputFile"),
JSON.parse(this.responseText)
);
cy.readFile(Cypress.env("IDStore")).then(obj => {
obj.clientID = JSON.parse(this.responseText).client.id;
cy.writeFile(Cypress.env("IDStore"), obj);
});
}
};
xhr.send(data);
}
);
});
});
});
And then it is called in a Test
it.only("CLIENT API POST TEST", () => {
cy.Post_Client();
});
This stands fixed now.There were two problems causing this and both were at the application layer.
Problem# 1 - Somehow we chose 417 as the error code for any unhandled events.
Fix - We are now using 422 error code for unprocessable entities
Problem# 2 - A formData append method has three params -(name, value, filename) where filename is optional. It is made mandatory in the application code recently.
fix -
data.set(
clientoBJ.imageatr,
blob,
clientoBJ.imagePath.substring(
clientoBJ.imagePath.lastIndexOf("//") + 2
)
);

Hardcoded manifest start url still not found?

I am attempting to setup a WordPress Theme as a Progressive Web App. When I run Chromes Audit tool (lighthouse?) I get an uninformative error that I don't know what exactly the problem is. The error is:
Failures: Service worker does not successfully serve the manifest start_url. Unable to fetch start url via service worker
I have hardcoded my start url which is a valid url. Any suggestions on what the issue could be?
https://mywebsite.com/wp-content/themes/mytheme/web.manifest:
...
"scope": "/",
"start_url": "https://mywebsite.com",
"serviceworker": {
"src": "dist/assets/sw/service-worker.js",
"scope": "/aw/",
"update_via_cache": "none"
},
...
}
https://mywebsite.com/wp-content/themes/mytheme/dist/assets/sw/service-worker.js:
...
// The fetch handler serves responses for same-origin resources from a cache.
// If no response is found, it populates the runtime cache with the response
// from the network before returning it to the page.
self.addEventListener('fetch', event => {
// Skip cross-origin requests, like those for Google Analytics.
if (event.request.url.startsWith(self.location.origin)) {
event.respondWith(
caches.match(event.request).then(cachedResponse => {
if (cachedResponse) {
return cachedResponse;
}
return caches.open(RUNTIME).then(cache => {
return fetch(event.request).then(response => {
// Put a copy of the response in the runtime cache.
return cache.put(event.request, response.clone()).then(() => {
return response;
});
});
});
})
);
}
});
I register my SW with the following code and it outputs that it has successfully registered the SW:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register(Vue.prototype.$ASSETS_PATH + 'sw/service-worker.js')
.then(function(registration) {
console.log('Registration successful, scope is:', registration.scope);
})
.catch(function(error) {
console.log('Service worker registration failed, error:', error);
});
}
Please change your start_url to
"start_url": "/"
It has to be a relative url. Please see the documentaion

angular2 wait for error http response

I have a problem with angular2 http response.
I want to catch the error in my component.
How does my app work.
In my Component, I Call a function in a personal service :
var response = this.apiUser.login(username, password);
alert(response);
In my Service, I try to auth :
this.http.post(this.httpApiAdress + '/' + this.httpUserAutenticate, body, { headers: contentHeaders })
.subscribe(
response => {
localStorage.setItem('id_token', response.json().token);
this.router.navigate(['home']);
},
error => {
return error.json();
},
() => { }
);
When the auth is ok, all work fine. But when the Auth fail, i can't catch the response in my Component.
(Its undefinied because the alert is executed before the http call...)
Can u help me please !!! (It was working when all the code was only in my Component, but I wanted to slip my code...)
Ty.
Return the observable by using map() instead of subscribe()
return this.http.post(this.httpApiAdress + '/' + this.httpUserAutenticate, body, { headers: contentHeaders })
.map(
response => {
localStorage.setItem('id_token', response.json().token);
this.router.navigate(['home']);
},
);
and then use subscribe where you want to execute code when the response or error arrives
var response = this.apiUser.login(username, password)
.subscribe(
response => alert(response),
error => alert(error),
);

ServiceWorker WindowClient.navigate promise rejected

I'm using Firebase Cloud Messaging + Service worker to handle background push notifications.
When the notification (which contains some data + a URL) is clicked, I want to either:
Focus the window if it's already on the desired URL
Navigate to the URL and focus it if there is already an active tab open
Open a new window to the URL if neither of the above conditions are met
Points 1 and 3 work with the below SW code.
For some reason point #2 isn't working. The client.navigate() promise is being rejected with:
Uncaught (in promise) TypeError: Cannot navigate to URL: http://localhost:4200/tasks/-KMcCHZdQ2YKCgTA4ddd
I thought it might be due to a lack of https, but from my reading it appears as though localhost is whitelisted while developing with SW.
firebase-messaging-sw.js:
// 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/3.5.3/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/3.5.3/firebase-messaging.js');
// Initialize the Firebase app in the service worker by passing in the
// messagingSenderId.
firebase.initializeApp({
'messagingSenderId': 'XXXX'
});
const messaging = firebase.messaging();
messaging.setBackgroundMessageHandler(payload => {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
let notificationData = JSON.parse(payload.data.notification);
const notificationOptions = {
body: notificationData.body,
data: {
clickUrl: notificationData.clickUrl
}
};
return self.registration.showNotification(notificationData.title,
notificationOptions);
});
self.addEventListener('notificationclick', event => {
console.log('[firebase-messaging-sw.js] Notification OnClick: ', event);
// Android doesn’t close the notification when you click on it
// See: http://crbug.com/463146
event.notification.close();
// This looks to see if the current is already open and
// focuses if it is
event.notification.close();
let validUrls = /localhost:4200/;
let newUrl = event.notification.data.clickUrl || '';
function endsWith(str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
event.waitUntil(
clients.matchAll({
includeUncontrolled: true,
type: 'window'
})
.then(windowClients => {
for (let i = 0; i < windowClients.length; i++) {
let client = windowClients[i];
if (validUrls.test(client.url) && 'focus' in client) {
if (endsWith(client.url, newUrl)) {
console.log('URL already open, focusing.');
return client.focus();
} else {
console.log('Navigate to URL and focus', client.url, newUrl);
return client.navigate(newUrl).then(client => client.focus());
}
}
}
if (clients.openWindow) {
console.log('Opening new window', newUrl);
return clients.openWindow(newUrl);
}
})
);
});
The vast majority of my SW code is taken from:
https://gist.github.com/vibgy/0c5f51a8c5756a5c408da214da5aa7b0
I'd recommend leaving out includeUncontrolled: true from your clients.matchAll().
The WindowClient that you're acting on might not have the current service worker as its active service worker. As per item 4 in the specification for WindowClient.navigate():
If the context object’s associated service worker client’s active
service worker is not the context object’s relevant global object’s
service worker, return a promise rejected with a TypeError.
If you can reproduce the issue when you're sure the client is currently controlled by the service worker, then there might be something else going on, but that's what I'd try as a first step.
This worked for me:
1- create an observable and make sure not to call the messaging API before it resolves.
2- register the service worker yourself, and check first if its already registered
3- call event.waitUntil(clients.claim()); in your service worker
private isMessagingInitialized$: Subject<void>;
constructor(private firebaseApp: firebase.app.App) {
navigator.serviceWorker.getRegistration('/').then(registration => {
if (registration) {
// optionally update your service worker to the latest firebase-messaging-sw.js
registration.update().then(() => {
firebase.messaging(this.firebaseApp).useServiceWorker(registration);
this.isMessagingInitialized$.next();
});
}
else {
navigator.serviceWorker.register('firebase-messaging-sw.js', { scope:'/'}).then(
registration => {
firebase.messaging(this.firebaseApp).useServiceWorker(registration);
this.isMessagingInitialized$.next();
}
);
}
});
this.isMessagingInitialized$.subscribe(
() => {
firebase.messaging(this.firebaseApp).usePublicVapidKey('Your public api key');
firebase.messaging(this.firebaseApp).onTokenRefresh(() => {
this.getToken().subscribe((token: string) => {
})
});
firebase.messaging(this.firebaseApp).onMessage((payload: any) => {
});
}
);
}
firebase-messaging-sw.js
self.addEventListener('notificationclick', function (event) {
event.notification.close();
switch (event.action) {
case 'close': {
break;
}
default: {
event.waitUntil(clients.claim());// this
event.waitUntil(clients.matchAll({
includeUncontrolled: true,
type: "window"
}).then(function (clientList) {
...
clientList[i].navigate('you url');
...
}
}
}
}

Resources