How to force client reload after deployment? - javascript

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);
}
};

Related

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

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...

Updating Offline Cache (Chrome On Mobile)

Afternoon All,
A bit of background - I'm building a custom calendar for a company where jobs can be scheduled and engineers can access it from their mobile to know when and where they're going. They previously used Google calendar but now want something bespoke.
All is fine until somebody loses phone signal and gets a horrible offline page in Chrome and can't access any information. What I'm wanting to do is have it save an offline version of the calendar but also update it when they re-visit it with a better connection - as job times often change.
I've tried saving the page and enabling offline mode in Chrome but the page doesn't update until you manually clear the cache so no good.
I've tried adding some javascript to hard refresh the page in the hope it clears the browser cache but again it doesn't update the page.
<script>location.reload(true);</script>
I read about cache manifests and have tried that too but although it feels like it wants to work it also doesn't update the page until I go to chrome://appcache-internals and remove the file.
CACHE MANIFEST
/calendar.php
/css/style.css
Neither PHP or Javascript headers work either as they either don't update the file on re-visit or simply don't save any files in the first place.
header("Cache-Control: no-cache, must-revalidate");
<meta http-equiv="Cache-Control" content="no-store" />
As far as I can tell there's no way to manually delete a user's website cache and re-download it and once the cache has been saved there's no way to force it to update. If you set it to expire then it's not there to access and you don't know when they will next have a connection to update so I'm going round in circles.
I've been trying for several hours now to find something that works and can't believe it's not a simple thing to do and therefore I'm now throwing myself on the mercy of you fine coders to point me in the right direction before my boss hangs me from the first floor window.
Many Thanks
UPDATE
Using what Clarence said as a starting point I came up with the following code in my appcache file:
CACHE MANIFEST
CACHE:
/css/bootstrap.css
/css/style.css
/calendar.php
NETWORK:
/calendar.php
# UPDATED: 03-04-2018 15:55:57
What this does is caches the calendar.php file BUT if there is a connection then it has another look at those files under the NETWORK heading so I also put it in there. If the appcache files hasn't changed then the browser doesn't bother looking so I've used the following code to write to the file when a job has been altered:
$manifest = file_get_contents(__DIR__ . '/cache.appcache');
$newFile = substr($manifest, 0, (strpos($manifest, '# UPDATED: ') + 11));
$newFile = $newFile . date('d-m-Y H:i:s');
file_put_contents('cache.appcache', $newFile);
Basically just searches the file for "UPDATED" and inserts a new time thus updating the file and requiring a re-check from returning users.
Somebody might point out this isn't the right way to do it but it seems to work from my tests so would like to thank those that contributed.
Have you tried changing the contents of your cache manifest whenever you change one of the files? The APPCACHE is a bit finicky when it comes to changes in the files and can be troublesome to handle. I usually include a comment with a timestamp and version number just to force it to update in the browser, like so:
CACHE MANIFEST
# 01-01-2001 v1.0 (Change whenever you need to force an update of the cache)
CACHE:
/css/file.css
/js/file.js
NETWORK:
*
FALLBACK:
well the best option would be creating a PWA. This should include the manifest files and Service workers as well. It enables you to cache the content of the websites and update it once the connection has been reestablished. However it is very new and would require a decent amount of research into service workers. If you need any help regarding the development of PWA would be happy to help to an extent
The only way without HTTP-Header is to rename files continuously.
And the depending HTML tag file-name.
So the files are loaded afresh.
With HTTP-Header look here.
How to control web page caching, across all browsers?
You can do this whenever a new change in the calendar was made.

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.

Removing localStorage file automatically in PhantomJS

I am using PhantomJS for testing purposes. I need PhantomJS to remove all the information related to the previous session after the following command,
phantom.exit()
Actually, it does that and it removes all the cookies, cache and the history in fact. But, It does not remove the saved information for localStorage command automatically. I need to go to the default path for saving this information and remove the file manually. I am wondering if there is any automatic way for removing this file. I tested following ways but none of them worked for me.
first of all, I used the following command to set the new path for this information, but PhantomJS did not care about it and just used its previous saved information again.
--local-storage-path = path
second, I used the page.open part of PhantomJS to clear the path using following command which did not work either for me.
localStorage.clear();
It's always good to clear localStorage after you're done with testing. You have to keep in mind that you can have multiple pages open in PhantomJS at the same time, but localStorage is only bound to a specific domain.
The localStorage.clear(); has to be executed on the page and not in phantom context:
page.evaluate(function(){
localStorage.clear();
});
So every time before you exit the script, you should clear the localStorage (maybe multiple times depending on how many domains you visited). Alternatively, you can try to do this at the beginning of your script/page load, but it will be hard to do it well. A page must already have a target URL, but the page cannot be yet loaded. Otherwise, the clear may come at a time when the page javascript already executed. Then this should probably be done only once per domain. Otherwise, navigation over different pages of a site will be broken.
Another simple solution would be to use the fs module to delete all localStorage files in the Ofi labs directory at the beginning of the script, but this might delete the localStorage of pages that you didn't want deleted.
If you're using phantomJS 1.9.8 on linux x64 you can test this binary which has the localStoragePath fix included: https://github.com/PatrickHuetter/phantomjs/releases/tag/1.9.8-fixedStoragePath
If you're using phantomjs on another operating system you could checkout my fork and compile it on your plattform. https://github.com/PatrickHuetter/phantomjs/tree/localStoragePathFix
PhantomJS 2.0 already has this fix included, so you can go with the official binary if you're using the 2.0 version or newer.

Javascript debugging difficult as browser doesn't refresh the scripts!

I'm trying to debug a Javascript written in the Mootools framework. Right now I am developing a web application on top of Rails and my webserver is the rails s that boots WEBrick.
When I modify a particular tree.js file thats called with in one a mootools init script,
require: {
css: [MUI.path.plugins + 'tree/css/style.css'],
js: [MUI.path.plugins + 'tree/scripts/tree.js'],
onload: function(){
if (buildTree) buildTree('tree1');
}
},
the changes are not loaded as the headers being sent to the client are Last Modified: 10 July, 2010..... which is obviously not true since I just modified the file.
How do I get rid of this annoying caching. If I go directly to the script in my browser (Chrome) it doesn't show the changes until I hit refresh, but this doesn't fix my problem when I go back to my application and hit refresh, it still loads the pre-modified script.
This has happen to me also in FF, I think it is a cache header sent by the server or the browser itself.
Anyway a simple way to avoid this problem while in development is adding a random param to the file name of the script.
instead of calling 'tree/scripts/tree.js' use 'tree/scripts/tree.js?'+random that should invalidate all caches.
As frisco says, adding a random number in development does the trick but you will likely find that the problem still affects you production. You want to push new JavaScript changes to your users but can't until their browsers stop caching the file. In order to do this, just get the files mtime and add that as the random string. This will only change when the file is modified and so the JavaScript will be loaded from cache if it has not been changed or it will be loaded from the server, if it has.
PHP has the function filemtime but as I'm not familiar with Ruby, I'm afraid I can't help you further in that direction (sorry!). However, this answer seems to accomplish what you want.
Try the Ctrl+F5 trick. To avoid hitting browser cache.
More info here:
What requests do browsers' "F5" and "Ctrl + F5" refreshes generate?

Categories

Resources