I am new to PWA/ServiceWorker (SW), and I need some help.
On fetch event listener, I check first if the assets are available on cache first. If it's available, I return it, if not, fetch the new assets, update the cache. However, when the page goes offline and the user navigates to a new page, I am not seeing my offline page, this is the error I am getting on the console:
The FetchEvent for "http://localhost:3000/about" resulted in a network error response: an object that was not a Response was passed to respondWith().
Below is my code below to understand it clearly 🙏 Thank you.
functions
fromCache = (request) => {
return caches.open(CACHE).then(
(cache) => {
return cache.match(request).then(
(matching) => {
if (!matching || matching.status === 404) {
return Promise.reject("fromCache no match");
}
return matching;
}
);
}
);
}
updateCache = (request, response) => {
return caches.open(CACHE).then(
(cache) => {
return cache.put(request, response);
}
);
}
event listener
self.addEventListener("fetch", (event) => {
if (event.request.method !== "GET") {
return;
}
event.respondWith(
fromCache(event.request).then(
(response) => {
event.waitUntil(
fetch(event.request).then(
(response) => {
return updateCache(event.request, response);
}
)
);
return response;
},
() => {
return fetch(event.request)
.then(
(response) => {
event.waitUntil(updateCache(event.request, response.clone()));
return response;
}
)
.catch(
() => {
if (event.request.destination !== "document" || event.request.mode !== "navigate") {
return;
}
return caches.open(CACHE).then(
(cache) => {
cache.match(offlineFallbackPage);
}
);
}
);
}
)
);
});
Heres how I fixed my issues 🙂
variable
const cacheName = 'neo-genesis';
const offlineFallbackPage = 'offline.html';
const skipCacheFrom = [
'apple',
'bing',
'extension',
...
]
function
const isOnList = (property, list) =>
list.some((item) => property.indexOf(item) !== -1);
event listener
self.addEventListener('fetch', (event) => {
if (event.request.method === 'GET' && !isOnList(event.request.url, skipCacheFrom)) {
event.respondWith(
caches.match(event.request)
.then(
(cache) => (
cache || fetch(event.request)
.then(
(response) => (
caches.open(cacheName)
.then(
(cache) => {
cache.put(event.request.url, response.clone());
return response;
}
)
)
)
)
)
.catch(
(error) => {
if (event.request.destination !== 'document' || event.request.mode !== 'navigate') {
console.log(`This device is offline!\n${error}`);
return Promise.resolve();
}
return caches.match(offlineFallbackPage);
}
)
);
}
return;
});
Related
The below angular code switchMap is not working, I'm not sure what error I made. Under the switchMap second API call not triggering '/upload/file'
zip(
this.service.submitForm(formValue),
this.service.upload(fData)
).subscribe(
([submitForm, upload]) => {
if (submitForm === 'success' && upload === 'Ok') {
//Redirect confirmation page
}
},
(err) => {
console.log(err, 'ERORORO');
}
)
//Service code
upload(formData): Observable <any> {
return this.sessionService.keepAlive().pipe(
switchMap(data => {
let token = data.jwtToken;
console.log(token, 'TOKEN SESSION');
// getting output as Bearer xyz
// with formData as req
const request_config = {
headers: {
"Authorization": token
}
};
console.log("REQUEST CONFIG", request_config); // getting output
return this.http.post < any > (
'/upload/file',
formData,
request_config
).pipe( // this is not working
map((res) => {
console.log(res, 'RESPONSE');
return res.status;
}),
catchError((error: HttpErrorResponse) => {
throw error;
})
)
})
)
}
I'm trying to get my PWA to work in offline mode. So far, it's serving all the files from localhost, but the CSS doesn't render. All files requested from the cache are getting status 200. (javascript and html are fully functional) Here's my service-worker code.
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open("v1").then((cache) => {
return cache.addAll([
"list of assets",
]);
})
);
});
self.addEventListener("fetch", function (event) {
event.respondWith(
caches.open("v1").then((cache) => {
if (event.request.url == "http://localhost:3000/") {
return cache
.match("http://localhost:3000/index.html")
.then((response) => {
console.log({ response });
return response;
});
} else {
try {
return cache.match(event.request).then((response) => {
console.log(response);
if (response != undefined) {
console.log({ response: "Loading asset from cache." });
return response;
} else {
let asset = fetch(event.request);
cache.add(asset);
return asset;
}
});
} catch (error) {
console.error(error);
}
}
})
);
});
Did you try listing your stylesheet where you have 'list of assets'?
return cache.addAll([
‘./css/styles.css'
]);
I have been testing push notifications for a PWA(Progressive-Web-App) through Firebase but everytime i Recieve the notification it just says "Chrome Push Notification: This site has been updated in the background". I am testing it in localhost and tried testing similar answer but nothing seems to fix it.
And This is my serviceworker:
(function () {
'use strict';
// Update 'version' if you need to refresh the cache
var version = 'v1.0::CacheFirstSafe';
var offlineUrl = "Offline"; // <-- Offline/Index.cshtml
var urlsToCache = ['./', offlineUrl]; // <-- Add more URLs you would like to cache.
// Store core files in a cache (including a page to display when offline)
function updateStaticCache() {
return caches.open(version)
.then(function (cache) {
return cache.addAll(urlsToCache);
});
}
function addToCache(request, response) {
if (!response.ok && response.type !== 'opaque')
return;
var copy = response.clone();
caches.open(version)
.then(function (cache) {
cache.put(request, copy);
});
}
function serveOfflineImage(request) {
if (request.headers.get('Accept').indexOf('image') !== -1) {
return new Response('<svg role="img" aria-labelledby="offline-title" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg"><title id="offline-title">Offline</title><g fill="none" fill-rule="evenodd"><path fill="#D8D8D8" d="M0 0h400v300H0z"/><text fill="#9B9B9B" font-family="Helvetica Neue,Arial,Helvetica,sans-serif" font-size="72" font-weight="bold"><tspan x="93" y="172">offline</tspan></text></g></svg>', { headers: { 'Content-Type': 'image/svg+xml' } });
}
}
self.addEventListener('install', function (event) {
event.waitUntil(updateStaticCache());
});
self.addEventListener('activate', function (event) {
event.waitUntil(
caches.keys()
.then(function (keys) {
// Remove caches whose name is no longer valid
return Promise.all(keys
.filter(function (key) {
return key.indexOf(version) !== 0;
})
.map(function (key) {
return caches.delete(key);
})
);
})
);
});
self.addEventListener('fetch', function (event) {
var request = event.request;
// Always fetch non-GET requests from the network
if (request.method !== 'GET' || request.url.match(/\/browserLink/ig)) {
event.respondWith(
fetch(request)
.catch(function () {
return caches.match(offlineUrl);
})
);
return;
}
// For HTML requests, try the network first, fall back to the cache, finally the offline page
if (request.headers.get('Accept').indexOf('text/html') !== -1) {
event.respondWith(
fetch(request)
.then(function (response) {
// Stash a copy of this page in the cache
addToCache(request, response);
return response;
})
.catch(function () {
return caches.match(request)
.then(function (response) {
return response || caches.match(offlineUrl);
});
})
);
return;
}
// cache first for fingerprinted resources
if (request.url.match(/(\?|&)v=/ig)) {
event.respondWith(
caches.match(request)
.then(function (response) {
return response || fetch(request)
.then(function (response) {
addToCache(request, response);
return response || serveOfflineImage(request);
})
.catch(function () {
return serveOfflineImage(request);
});
})
);
return;
}
// network first for non-fingerprinted resources
event.respondWith(
fetch(request)
.then(function (response) {
// Stash a copy of this page in the cache
addToCache(request, response);
return response;
})
.catch(function () {
return caches.match(request)
.then(function (response) {
return response || serveOfflineImage(request);
})
.catch(function () {
return serveOfflineImage(request);
});
})
);
});
self.addEventListener('push', function (event) {
console.log('Received a push message', event);
var title = 'Yay a message.';
var body = 'We have received a push message.';
var icon = './Content/image/apple-icon-180x180.png';
var tag = 'simple-push-demo-notification-tag';
var data = {
doge: {
wow: 'such amaze notification data'
}
};
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag,
data: data
})
);
});
self.addEventListener('notificationclick', function (event) {
// here data you access from event using event.notification.data
console.log('On notification click: ', event.notification.tag);
});
})();
Whenever I make any updates to my website, which is developed using Laravel, I have to delete the browser cache in order to reflect the changes. This is for both while working on localhost and when I deploy it on the server. Is there any way to automate the process, either by service worker where the cache is deleted after every new update?
Here is my serviceworker.js
var staticCacheName = "pwa-v" + new Date().getTime();
var filesToCache = [
'/offline.html',
'/css/style.css',
'/js/app.js',
];
// Cache on install
self.addEventListener("install", event => {
this.skipWaiting();
event.waitUntil(
caches.open(staticCacheName)
.then(cache => {
return cache.addAll(filesToCache);
})
)
});
// Clear cache on activate
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames
.filter(cacheName => (cacheName.startsWith("pwa-")))
.filter(cacheName => (cacheName !== staticCacheName))
.map(cacheName => caches.delete(cacheName))
);
})
);
});
// Serve from Cache
self.addEventListener("fetch", event => {
event.respondWith(
caches.match(event.request)
.then(response => {
return response || fetch(event.request);
})
.catch(() => {
return caches.match('/offline.html');
})
)
});
Here is a snippet I am using to delete stale assets without manual intervention.
function deleteStaleAssets(url, asset) {
caches.open(CACHE_NAME).then(function(cache) {
cache.keys().then(function(keys) {
let cc = keys.filter(function(req) {
if(req.url.includes(asset) && req.url !== url) {
return true;
}
});
cc.forEach(function(r) {
cache.delete(r);
});
});
});
}
function onFetch(event) {
var assets = ['/assets/application', 'assets/spree/frontend/all'];
var asset = assets.find(function(asset) {
return event.request.url.includes(asset);
});
if(asset) {
event.respondWith(async function() {
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(event.request);
const networkResponsePromise = fetch(event.request);
event.waitUntil(async function() {
const networkResponse = await networkResponsePromise;
deleteStaleAssets(event.request.url, asset);
await cache.put(event.request, networkResponse.clone());
}());
// Returned the cached response if we have one, otherwise return the network response.
return cachedResponse || networkResponsePromise;
}());
}
else {
event.respondWith(
// try to return untouched request from network first
fetch(event.request).catch(function() {
// if it fails, try to return request from the cache
return caches.match(event.request).then(function(response) {
if (response) {
return response;
}
// if not found in cache, return default offline content for navigate requests
if (event.request.mode === 'navigate' ||
(event.request.method === 'GET' && event.request.headers.get('accept').includes('text/html'))) {
return caches.match('/offline.html');
}
})
})
);
}
}
My serviceworker looks like that :
const staticCacheName = 'restaurant-cache-v6';
const OFFLINE_URL = 'offline.html';
const filesToCache = [
'/',
'./manifest.json',
'./index.html',
'./offline.html',
'./restaurant.min.html',
'./css/styles.min.css',
'./js/lazyload.min.js',
'./js/idb.min.js',
'./js/dbhelper.min.js',
'./js/main.min.js',
'./js/restaurant_info.min.js',
'./data/restaurants.json',
'./img/1.jpg',
];
self.addEventListener('install', function (e) {
console.log('[ServiceWorker] Install');
e.waitUntil(caches.open(staticCacheName).then(function (cache) {
console.log('Cache is opened');
return cache.addAll(filesToCache);
})
);
});
self.addEventListener('active', function (e) {
e.waitUntil(
caches.keys().then(function (cacheNames) {
return Promise.all(
cacheNames.filter(function (cacheName) {
return cacheName.startsWith('restaurant-') && cacheName != staticCacheName;
}).map(function (cacheName) {
return caches.delete(cacheName);
})
);
}))
});
self.addEventListener('fetch', function (e) {
{
e.respondWith(
caches.match(e.request, { ignoreSearch: true }).then(function (res) {//now works with query string in the URL. !!!!
if (res) {
return res;
}
var fetchReq = e.request.clone();
return fetch(fetchReq).then(
function (res) {
if (!res || res.status !== 200 || res.type !== 'basic') {
return res;
}
var resToCache = res.clone();
caches.open(staticCacheName)
.then(function (cache) {
cache.put(e.request, resToCache);
});
return res;
}
);
})
);
}
});
self.addEventListener('message', function (e) {
if (e.data.action === 'skipWaiting') {
self.skipWaiting();
}
});
Everything works fine, sw registrations, when i switch in devtools offline its fetches from cache - my page looks like online
but when i'm going offline and clearing all cache i get :
Everything works fine, sw registrations, when i switch in devtools offline its fetches from cache - my page looks like online
but when i'm going offline and clearing all cache i get :
Is there a way to render OFFLINE_URL site from sw?