How to make sure the browser read the latest static files immediately - javascript

I serve the static webapp on aws s3 that distributed through CDN (aws cloudfront). The files are ES6 based build version with rollup. For short info, except index.html rollup will generate new hash files every build the webapp. So the files are always unique every update, except index.html. Then on the aws cloudfront I put the index.html into invalidation cache list.
Well then, I will expect users always request the latest version of the webapp with that approach. Yes it works, but with a little note!
So once there is new updates, the browser is still loading the old index.html file on the first time. I have to refresh page to push the browser get the latest index.html. It's not good for the end users. They doen't want to know about refreshing, most will not know right?
One last experiment, I added small script inside on index.html to perform version validation like so :
<script>
fetch('/version.json').then(r => {
if (r.status == 200) {
return r.json()
} else {
alert("Found server updated, let us resync the contents!")
location.reload(true)
}
}).then(j => {
if (window.localStorage.getItem("app-version")) {
if (j.version != window.localStorage.getItem("app-version")) {
alert("Found server updated, let us sync the contents!")
window.localStorage.setItem("app-version", j.version)
location.reload(true)
}
} else {
window.localStorage.setItem("app-version", j.version)
}
})
</script>
That script worked as expected but wondering whether I will have better solution out there? Kindly to have another idea, please?
Expected behavior
Browser have knowledge the latest index.html immediately without any refresh/reload page from end users.
Thank you

Just use a timestamp in the URL to force the browser to get the latest version.
(()=>{
// Get the current timestamp
var now = new Date().getTime();
// Check for a ts parameter in the url (index.html?ts=2345234523)
const urlParams = new URLSearchParams(location.search);
var ts = urlParams.get('ts');
// If there is no timestamp parameter,
// or the timestamp parameter is older than 1 minute
// redirect the page to the latest version
if(!ts || now - ts > 60000){
location.href = `index.html?ts=${now}`;
}
})();
You'll want to put that near the top so you don't have a lot of things loading before the redirect.

I would take advantage of a service-worker to make it easier and more efficient to load and cache a cohesive version of the app.
The service worker has a built in mechanism for checking whether the app has been updated, which is does each time the app is launched.
What it won't do is trigger that check for you while the app is running. While you could poll regularly to check for updates, it's more efficient for both your server and users to have them subscribe for updates which you can do using something like Firebase RTDB (Real Time Database):
When you publish a new version and want to immediately force all running instances to update, you change the version or timestamp that they are subscribing to and have that trigger the service-worker update check, which then refreshes and reloads the app.
There are lots of available patterns for prompting users about any update in case they are in the middle of completing a form etc...

Related

IE 11 window.open() with javascript script loading race condition issue

Goal:
I have a ReactJs web app called "ImageGallery" that's currently integrated in our business suite system(which is a huge system contains older technologies, mainly web forms). In our system, we need to add a button to launch a browser window to spin the app.
ImageGallery app rely on the current system to pass data to it to show, which means, when the ImageGallery app loads, it calls a function on the business suite system side like
window.parent.loadData()
Since the data is passed in by JavaScript, there is no ajax call.
Code:
function showImage() {
var imageGalleryWindow = window.open('/ImageGallery', '_blank', 'height=' + window.screen.availHeight + ',width=' + window.screen.availWidth + ',scrollbars=yes,menubar=no,resizable=yes,toolbar=no,location=no,status=no');
imageGalleryWindow.loadData= loadData;
}
loadData() function is available on the current JavaScript file and will be passed to the new window so it can be used by the ReactJS app.
Problem:
The app works perfectly with Chrome/Firefox/Edge, but not IE 11. IE 11 sometimes loads, sometimes not. After taking a closer look, I found there's a race condition.
Basically whenever the app failed, loadData() function has not been passed to the newly created window. Again, that's only for IE11, all other browsers seems fine.
What I've tried:
I tried the following:
1> Tried to put a wait function in ImageGallery app like this:
static resolveDocuments(){
if(typeof window.parent.loadData === 'function'){
// do the work to show image
} else {
setTimeout(this.resolveDocuments, 2000);
}
}
I am using redux thunk, I didn't get any luck in getting this to work.
2> put imageGalleryWindow.loadData= loadData; into window.onload(), bad idea but tried anyways
3> put imageGalleryWindow.loadData= loadData; into imageViewerWindow.document.addEventListener("DOMContentLoaded", function (event) {} still bad idea but tried anyways
4> created a new ImageGallery.html, use an iframe to host ImageGallery app. In section, include the JavaScript file from parent. No luck
5> created a webform page ImageGallery.aspx, use an iframe to host ImageGallery app. In section, include the JavaScript file from parent. No luck.
Thanks for taking time to finish reading this problem, and please let me know if you have an idea I can try.
Thanks so much!
Ray
The solution I end up having is html 5 local storage:
Before calling window.open() to populate the new window, create a entry in html 5 local storage with all the data I need to pass in.
When the image gallery app loads, check if local storage items exists, if so, pull the data. The data will always be there, since it sets before new window generated, and since both pages are from the same domain, they could both access the same local storage.
To avoid security issues, after image gallery app read data from the local storage, I keep a copy of the data within the app, then delete the local storage entry.
Hope this can be helpful to other people.

Web browser cache issue

I have been developing many updates for my website, but many clients are complaining that they didn't get any updates. When i asked them to clear the browser cache, their problem is solved. But its difficult for clients with below average computer literacy to clear cache. So is there any way to detect cached data in browsers and reload cache if there's change in my site contents?
I had developed my site on HTML,Angular JS.
you can use some extensions with your js, css files callings like
<header>
<link href="MyFolder/my.css?v=1.5">
<script src="myFolder/my.js?v=1.5">
</header>
v = version you can use a global variable to hold value of version then you can increase version number with your updates
Simply add cache busting.
Add timestamp
"?ts=" + new Date().getTime()
or As in your case, for build deployment its better to use version.
Fetch your version number from config file
var ver = "?ver=" + config.version;
then you url look like
app.js + ver => app.js?ver=1.0.1
For version number in config you may keep it on client side config or get it from api on first hit and save it.
If you are using requirejs then its so simple just add value like this:
require.config({ urlArgs: ver});
It will automatically append it in every Js url.

How to force an update of a Single-page application

I work on a AngularJS Single-page application which once loaded (all the resources are fetched from the HTTP server) only accesses the REST backend to get/send data. The task is how to make sure users are always using the latest version of the frontend code (.js, .css and .html files) when the users never leave the page and never refresh the browser.
I think there are several options:
the frontend asks the HTTP server every N minutes what is the latest frontend code version
the backend includes the latest frontend code version in every response (e.g. an additional HTTP header) which is inspected by the frontend code
the backend returns a cookie with the latest frontend code version and checks if the version provided by the user is outdated
websocket connection between the browser and the backend (not an option for me)
Do you know other approaches?
you can also use html5 application cache, this way every time something changes, the website will update itself in the first visit, pretty handy and faster than ordinary cache.
Recently I have used the 2nd approach in my Agnularjs SPA. What I did is
In my angularjs APP's .htaccess and my API Server's .htaccess I have added a header:
<IfModule mod_headers.c>
Header add appVersion "1.2"
</IfModule>
Now I will receive this header whenever I am getting anything from API server or frontend(js, html, css, images)
now in my index.html i have defined a variable something like
var frontendVersion = 1.2
I have a http Response interceptor that will look for each response and I have done something like this
myAPP.factory('httpRespInterceptor', function () {
return {
response: function (response) {
var appVersion = response.headers('appVersion')
if(appVersion == frontendVersion) {
console.log('ok')
} else if (appVersion != null) {
location.reload();
}
return response;
}
};
});
I have added a null check because when i am loading a same view twice I am getting Empty Header Object and in my console i don't see any request that is being sent to the server..don't have a clue why this is happening if someone can shed a light it will be grateful :)
so whenever I will make a change in my code/update the application i will change my header appVersion to 1.3 and my index.html variable to var frontendVersion = 1.3
I hope this helps. And also Please let me know if there are best solutions other than this,
Thanks,

How to force client reload after deployment?

I'm using the MEAN stack (mongo, express, angular and node). I'm deploying relatively frequently to production...every couple of days. My concern is that I'm changing the client side code and the API at times and I would rather not have to ensure backwards compatibility of the API with previous versions of the client code.
In such a scenario, what is the most effective way of ensuring that all clients reload when I push to production? I have seen that Evernote for example has a pop-up that says something along the lines of please reload your browser for the latest version of Evernote. I would like to do something similiar...do I need to go down the path of socket.io or sock.js or am I missing something simple and there is a simpler way to achieve this?
Update:
AppCache was deprecated summer 2015 so the below is no longer the best solution. The new recommendation is to use Service Workers instead. However, Service Workers are currently still experimental with sketchy (read: probably no) support in IE and Safari.
Alternatively, many build tools now seamlessly incorporate cache-busting and file "versioning" techniques to address OPs question. WebPack is arguably the current leader in this space.
This might be a good use case for using HTML5's AppCache
You'd probably want to automate some of these steps into your deployment scripts, but here is some code you might find useful to get you started.
First, create your appcache manifest file. This will also allow you to cache resources in the client's browser until you explicitly modify the appcache manifest file's date.
/app.appcache:
CACHE MANIFEST
#v20150327.114142
CACHE:
/appcache.js
/an/image.jpg
/a/javascript/file.js
http://some.resource.com/a/css/file.css
NETWORK:
*
/
In app.appcache, the comment on line #v20150327.114142 is how we indicate to the browser that the manifest has changed and resources should be reloaded. It can be anything, really, as long as the file will look different to the browser from the previous version. During deployment of new code in your application, this line should be modified. Could also use a build ID instead.
Second, on any pages you want to use the appcache, modify the header tag as such:
<html manifest="/app.appcache"></html>
Finally, you'll need to add some Javascript to check the appcache for any changes, and if there are, do something about it. Here's an Angular module. For this answer, here's a vanilla example:
appcache.js:
window.applicationCache.addEventListener('updateready', function(e) {
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
// Browser downloaded a new app cache.
// Swap it in and reload the page to get the latest hotness.
window.applicationCache.swapCache();
if (confirm('A new version of the application is available. Would you like to load it?')) {
window.location.reload();
}
}
else {
// Manifest didn't changed. Don't do anything.
}
}, false);
Alternatively, if AppCache won't work for your situation, a more ghetto solution would be to create a simple API endpoint that returns the current build ID or last deployment date-time. Your Angular application occasionally hits this endpoint and compares the result to it's internal version, and if different, reloads itself.
Or, you may consider a live-reload script (example), but, while very helpful in development, I'm not sure how good of an idea it is to use live/in-place-reloading of assets in production.
I will tell you my problem first then I will recommend a tentative solution. I wanted to force my user to log out and then log in when a production build is been deployed. At any point in time, there will be two versions of software deployed on production. A version which software which FE knows and a version which Backend knows. Most of the time they would be the same. At any point in time if they go out of sync then we need to reload the client to let the client know that a new production build has been pushed.
I am assuming 99.99% of the time the backend would have the knowledge of the latest version of the deployed software on production.
following are the two approaches which I would love to recommend:-
The backend API should always return the latest version of the software in the response header. On the frontend, we should have a common piece of code that would check if the versions returned by the API and that present on the FE are the same. if not then reload.
Whenever a user logs in. the BE should encode the latest software version in the JWT. And the FE should keep sending this as a bearer token along with every API request. The BE should also write a common interceptor for every API request. which would compare the software version in the JWT received from the API request and the
Maybe you can add hash to your client code file name. eg app-abcd23.js.
So the browser will reload the file instead of get it from cache. or you can just add the hash to url.eg app.js?hash=abcd23 but some browser may still use the cached version.
i know rails has assets-pipline to handle it, but i am not familiar with MEAN stack. there should be some package in npm for that purpose.
And i dont think it is really necessary to use socket.io if you want to notify the user their client code is out of date. you can define your version in both html meta tag and js file,if mismatch, show a popup and tell the user to refresh.
Try to limit your js/files to expire within smaller periodic time, ie: 1 days.
But in case you want something that pop-out and tell your user to reload (ctrl+f5) their browser, then simply make a script that popup that news if you just changed some of your files, mark the ip/session who have just reload/told to reload, so they will not be annoyed with multiple popup.
I was facing the same problem recently. I fixed this by appending my app's build number with my js/css files. All my script and style tags were included by a script in a common include files so it was trivial to add a 'build number' at the end of the js/css file path like this
/foo/bar/main.js?123
This 123 is a number that I keep track of in my same header file. I increment it whenever I want the client to force download all the js files of the app. This gives me control over when new versions are downloaded but still allows the browser to leverage cache for every request after the first one. That is until I push another update by increment the build number.
This also means I can have a cache expiry header of however long I want.
Set a unique key to local storage during the build process
I am using react static and loading up my own data file, in there i set the ID each time my content changes
Then the frontend client reads the key with from local storage
(if the key does not exist it must be the first visit of the browser)
if the key from local storage does not match it means the content has changed
fire line below to force reload
window.replace(window.location.href + '?' + key)
in my case i had to run this same line again a second latter
like
setTimeout( (window.replace(window.location.href + '?' + key))=> {} , 1000)
full code below:
const reloadIfFilesChanged = (cnt: number = 0, manifest: IManifest) => {
try {
// will fail if window does not exist
if (cnt > 10) {
return;
}
const id = localStorage.getItem('id');
if (!id) {
localStorage.setItem('id', manifest.id);
} else {
if (id !== manifest.id) {
// manifest has changed fire reload
// and set new id
localStorage.setItem('id', manifest.id);
location.replace(window.location.href + '?' + manifest.id);
setTimeout(() => {
location.replace(window.location.href + '?' + manifest.id + '1');
}, 1000);
}
}
} catch (e) {
// tslint:disable-next-line:no-parameter-reassignment
cnt++;
setTimeout(() => reloadIfFilesChanged(cnt, manifest), 1000);
}
};

How to identify first call in Cordova

I have a cordova-based app that runs on Android, iOS and Windows Phone. The starting point in my app is the index.html page, which will not only be loaded on app start, but you can redirect to it from inside the app.
I want to execute some code if and only if the app was just started (so when the index.html was displayed the first time) and not if it was redirected to it. I tried to use cookies that expire when the session ends, but cordova does not work with cookies.
Also I do not want to use session storage because some older Androids (as well as Internet Explorer) cannot handle this.
My used cordova version is 4.0.0
EDIT:
I forgot to mention that it is not a single-page app, but I use multiple pages that can be accessed, so the deviceready-event does not work, because it would be fired every time I access the index.html-page
Try smth like this:
function documentReady() {
   document.addEventListener("deviceready", handleDeviceReady, false);
}
 
function handleDeviceReady(event) {
   //cordova api is ready for use
if (!localStorage.getItem('alreadyStarted')) {
// App just started!
localStorage.setItem('alreadyStarted', true);
}
}
UPD. Also you need to set the flag at first startup (i.e. alreadyStarted = true in sessions or LocalStorage) and check it in handleDeviceReady() later.
Write to the database at first run. Everytime the app starts, check the database to see if that string is present. If it it, ignore. Else, create and write.

Categories

Resources