How to use MVC Bundling + Service Worker Cache - caching

Problem
If I load the page without a service worker, everything is fine but when I introduce a service worker, the page does not load the first time (when online) because it is missing my bundled files which causes both CSS and Script issues. If I refresh the page then everything works because the service worker caches fetch requests when they occur.
Setup
Say I have a bundle such as
bundles.Add(new ScriptBundle("~/js/main").Include(
"~/Scripts/jquery-3.3.1.min.js",
"~/Scripts/moment.min.js",
"~/node_modules/sweetalert/dist/sweetalert.min.js"));
and a service worker in my root directory such as
var cacheName = 'v1:static';
self.addEventListener('install', function (e) {
e.waitUntil(
caches.open(cacheName).then(function (cache) {
return cache.addAll([
'/images/keypad/number 0.png',
'/',
'/Menu/MainMenu',
'**TODO: Cache Bundles**'
]).then(function () {
self.skipWaiting();
});
})
);
});
self.addEventListener('fetch', function (event) {
if (event.request.method != "POST") {
event.respondWith(
caches.match(event.request).then(function (response) {
if (response) {
return response;
}
fetch(event.request).then(function (fetchResponse) {
caches.open(cacheName).then(function (cache) {
cache.put(event.request, fetchResponse.clone());
});
return fetchResponse;
});
})
);
}
});
Question
How do I cache my bundled files in my service worker, in both Debug and Release mode because debug displays them as individual files while Release will combine them, and I don't know the URLs to the bundles?
Additional Thoughts
You can't put a Razor #... in this file because it isn't cshtml.
I don't want to list every file out in both places and maintain both.
I thought about using a server side handler to generate my service-worker.js file but I was wondering if there is an actual clean way to do this without going crazy.
Thank You!

#Scripts.Url("~/bundles/yourBundleName") use this to get the absolute URL for the URL from there you can get the bundle name map that into a variable and use that variable in service worker install event so that assets are cached on load itself.
note:
one mistake which i used to do Also please don’t refer serviceworker.js file directly in to index.html, this will be loaded at the time of registering from sw-registration.js file

Related

service worker we are facing cache issue

I am a fresher for service worker. I am trying to implement static and dynamic caching.
When I add the single file to the static cache request, it is taking all the files whatever I have. Currently, all the files are running from the service worker when I started from the offline mode. Please someone help me.
This is the code I have in index.html.
self.addEventListener('install',function(event)
{
console.log('[service worker]Installing service
worker....',event);
event.waitUntil(caches.open('static')
.then(function(cache)
{
console.log('[Service Worker] Precaching App Shell');
cache.addAll([
'/',
'/signin.html',
]);
})
)
});
In Static caching some of the requests(pages) are cached(saved in Browser's local storage), it is generally done in install event of Service Worker. Whereas in Dynamic caching pages & files are cached whenever you request(fetch) for them hence, it makes use of fetch event of Service Worker.
So, in your install event when you add '/' in cache.addAll service worker adds all the files and resources needed to display '/' ie root directory.
Now, to make use of those cached files and to implement Dynamic Caching you'll need to implement something like this:
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request) // if data has been cached already
.then(function (response) {
if (response) {
return response;
} else { // else fetch from internet & cache in DYNAMIC cache
return fetch(event.request)
.then(function (res) {
return caches.open(CACHE_DYNAMIC_NAME)
.then(function (cache) {
cache.put(event.request.url, res.clone());
return res;
})
})
.catch(function (err) { //fallback mechanism
return caches.open(CACHE_STATIC_NAME)
.then(function (cache) {
if (event.request.headers.get('accept').includes('text/html')) {
return cache.match('/offline.html');
}
});
});
}
})
);
});
NOTE: To avoid over crowding your local storage you might wanna implement some strategy before actually saving the file in storage.
For more info read this to learn more about strategies.

Create React App with Service Workers

I have upgraded my CRA to version 3.10.8 as it has built in support for PWA.
As a next step I have registered my service worker in the index.js and I think it got registered succesfully.
Now my main goal is to have some offline caching for our API calls (backend in Rails), so that when there is no network I can serve the cached response .
Is there anything else that I need to do to serve cached API responses.
When I built my app with Create react App, all it did was create a file called
registerServiceWorker.js and then this gets called from the index.js.
Also the final app we are building is packaged with Codova so most of the Assets will be in local , our main aim is to cache the API calls. Is this the right way to go. We are using Redux for state management, but have not use any persistence as of now.
Any help/tips would be highly appreciated.
registerServiceWorker.js code below...
// In production, we register a service worker to serve assets from local cache.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on the "N+1" visit to a page, since previously
// cached resources are updated in the background.
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export default function register() {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (!isLocalhost) {
// Is not local host. Just register service worker
registerValidSW(swUrl);
} else {
// This is running on localhost. Lets check if a service worker still exists or not.
checkValidServiceWorker(swUrl);
}
});
}
}
function registerValidSW(swUrl) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and
// the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in your web app.
console.log('New content is available; please refresh.');
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl) {
// 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.
if (
response.status === 404 ||
response.headers.get('content-type').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);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}
I am using the Create-react-app version 3.
change the condition statement, Remove the codition (process.env.NODE_ENV === 'production' &&) it should only have if('serviceWorker' in navigator).
create your custom-service-worker file in public folder rewrite the following code as const swUrl = ${process.env.PUBLIC_URL}/service-worker.js as swUrl = ./custom-service-worker.js.
In the custom-service-worker.js file in public folder add the follow code, please refer the sample external api calls( place your api urls to be cached)
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
if (workbox) {
console.log('workbok loaded', workbox.routing)
}
//to cache the css html js and images files
workbox.routing.registerRoute(
/\.(?:js|html|css|images|svg)$/,
new workbox.strategies.NetworkFirst()
);
workbox.routing.registerRoute(
'http://localhost:3000',
new workbox.strategies.NetworkFirst()
);
//to cache the external api calls
workbox.routing.registerRoute(
new RegExp('https://jsonplaceholder.typicode.com/users'),
new workbox.strategies.StaleWhileRevalidate()
);
//to cache the external api calls
workbox.routing.registerRoute(new RegExp('http://insight.dev.schoolwires.com/HelpAssets/C2Assets/C2Files/C2ImportUsersSample.csv'),
new workbox.strategies.StaleWhileRevalidate()
);

my browser registers the service worker and caches the urls but it doesn't work offline?

This registers and works perfectly fine online. But when the server is turned off, and when the page is refreshed, the registered serviceworker no longer shows up in the console and no caches in the cache storage.
if ("serviceWorker" in navigator){
navigator.serviceWorker.register("/sw.js").then(function(registration){
console.log("service worker reg", registration.scope)
}).catch(function(error){
console.log("Error:", error);
})
}
in sw.js
var CACHE_NAME = 'cache-v1';
var urlsToCache = [
'/index.html'
];
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(CACHE_NAME).then(function(cache) {
return cache.addAll(urlsToCache);
})
);
//event.waitUntil(self.skipWaiting());
});
You're properly adding your file to cache, but you're missing returning your cached file on request.
Your sw.js should also have following code:
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);
}
)
);
});
It's from Introduction to service workers.
Morover, you should rather cache / instead of /index.html as usually, you don't hit index.html file directly.
Your service worker doesn't show any console.log when offline, because you don't have any activate code. This article - offline cookbook is very useful to understand details.

Conditionally Load Angular Library

Trying to load Angular library conditionally using Modernizr, if CDN fails want it to load the library from local machine but it is not working, so what could be the reason
Modernizr.load([
{
load: "//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.4/angular.min.js",
complete: function () {
if(!window.angular){
Modernizr.load("bower_components/angular/angular.min.js");
}
}
}]);
Thanks
Ok so like the error says, Modernizr doesn't know what angular is, which means your fallback doesn't work. Take a look at this code:
Modernizr.load([
{
load: '//ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.js',
complete: function () {
if (!window.jQuery) {
Modernizr.load('js/libs/jquery-1.6.4.min.js');
}
}
},
{
// This will wait for the fallback to load and
// execute if it needs to.
load: 'needs-jQuery.js'
}
]);
This code attempts to load jQuery from the Google CDN first. Once the script is downloaded (or if it fails) the function associated with complete will be called. The function checks to make sure that the jQuery object is available and if it’s not Modernizr is used to load a local jQuery script. After all of that occurs a script named needs-jQuery.js will be loaded. http://weblogs.asp.net/dwahlin/detecting-html5-css3-features-using-modernizr
Edit
so change this
load: "//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.4/angular.min.js",
to this
load: https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.4/angular.min.js,

Make AJAX call in AngularJS in Cordova App

I have built an app using Ionic Framework, AngularJS, and Cordova. In it, I have made AJAX calls to several php files using $http.get(), which works perfectly in a browser, but not within the app. The app is able to render internet pages through an iframe so the network is working. I have whitelisted my server where the php files reside inside of the app.js file and using . Also, in my php files I've added header('Access-Control-Allow-Origin: *');
Any suggestions on how to get this AJAX call to work?
.controller('StuffCtrl', function($scope, StuffService, LoadingService) {
StuffService.getStuff().then(function(data) {
LoadingService.show();
$scope.stuff = data;
LoadingService.hide();
});
})
.service('StuffService', function($http){
var myStuff;
return {
getStuff: function(){
return $http.get('http://mydomain/stuff.php').then(function(items) {
myStuff = items.data; return myStuff;
});
}
});
});
I had the same error while working on a hybrid app and then I added these lines in my route config function and it started working
$httpProvider.defaults.useXDomain=true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
This is to enable CORS.
More info on the same can be found here
Add a catch handler to get the error :
StuffService.getStuff().then(function(data) {
LoadingService.show();
$scope.stuff = data;
LoadingService.hide();
}).catch(function(error){
$scope.error = error;
});
And pretty display the error :
<pre>{{error|json}}</pre>
This should tell you what is going on.

Resources