This is a guest post by Bart Sturm, co-founder of TutorMundi, a Brazilian on-demand tutoring app.
Hot Code Push is one of my favorite features of Meteor.
It’s amazing to be able to push new versions of your app to all users across all devices and platforms at once.
Though as a bridge between several different technologies with many moving parts, it doesn’t always work seamlessly. When you realize it’s not working reliably in your app, what can you do?
Common issues and solutions
Let’s not reinvent the wheel — let’s start with some time-tested solutions:
- Make sure your app fits the prerequisites
- If it started after updating Meteor or plugins, consider setting compatability versions
- Update any outdated custom reload logic or packages
- Check if you’re making your app download big files
- If it is only broken locally, set usesCleartextTraffic and --mobile-server
- Avoid URLs with hash fragments
- Set an AUTOUPDATE_VERSION
- Note that unlike in the browser, Cordova apps don’t refresh CSS without reload, this is expected behavior
How to dig deeper
But what if none of these solve your particular issue?
Then let’s start looking at your client side logs.
Of course, you may find the cause of your issue right then and there.
If not, that’s okay — we’re about to start logging a whole lot more. We’re going to follow Hot Code Push every step on the way.
(Note: I shared similar code samples in the Meteor Guide. Also, the libraries Tracker and lodash used here are helpful but not necessary. )
Step 1. Server creates new client hash
Whenever the server thinks the client side may have changed, it calculates a hash of your entire client bundle. We can log the following both on the server and client, and compare:
__meteor_runtime_config__.autoupdate.versions['web.cordova']
Step 2. The client subscribes to new hashes
The server publishes this hash to all clients. The clients subscribe to this publish.
To check the status of our subscription, we can log this in the client:
Meteor.default_connection._subscriptions
This shows lots of information about each of your subscriptions. For more readable output, I like to select the subscription I’m interested in and pick some specific properties. For example:
const { ready, inactive } = _.chain(Meteor)
.get('default_connection._subscriptions', {})
.toPairs()
.map(1)
.find({ name: 'meteor_autoupdate_clientVersions' })
.pick(['inactive', 'ready'])
.value();
console.log(‘ready:’, ready);
console.log(‘inactive:’, inactive);
We can modify this a bit to check the status of the subscription every time the subscription changes (for example, when ready goes from false to true):
const hcpSub = _.chain(Meteor)
.get('default_connection._subscriptions', {})
.toPairs()
.map(1)
.find({ name: 'meteor_autoupdate_clientVersions' })
.value(); // no .pick() this time; return whole object
Tracker.autorun(() => {
hcpSub.readyDeps.depend(); // Rerun when the subscription changes
console.log('hcpSub.ready', hcpSub.ready);
});
Step 3. A new client hash arrives
If the client receives client hashes different from its own, our Cordova app starts downloading the new version. (On web, the download only occurs after the reload; our Meteor app is simply a website after all.)
At this point the reactive Autoupdate.newClientAvailable() will start returning true:
Tracker.autorun(() => {
console.log(Autoupdate.newClientAvailable());
});
Step 4. (Cordova only): the new version was downloaded
On mobile, when the new version is downloaded, Meteor will trigger the callback passed to WebAppLocalServer.onNewVersionReady. There can only be one such callback. To log this step without breaking the next, our code can define a new callback that adds logs and then add the code from the original. At the time of writing, it looks like this:
WebAppLocalServer.onNewVersionReady(() => {
console.log('new version is ready!');
// Copied from original in autoupdate/autoupdate_cordova.js
if (Package.reload) {
Package.reload.Reload._reload();
}
});
Step 5. Ask for permission to reload
The client will now announce that it will reload. The app and packages get a chance to save their data or to deny or delay the reload. To find out if this point is reached, we can add a callback to _onMigrate:
Reload._onMigrate(() => {
console.log('going to reload now');
return [true];
});
Step 6. Reload
If every Reload._onMigrate callback grants permission, the app reloads.
As part of this, all startup callbacks are run again. To know whether a startup was the result of a Hot Code Push or simply of opening the app, we could use a Session variable, which are preserved across Hot Code Push updates:
Meteor.startup(() => {
console.log('Was HCP:', Session.get('wasHCP'));
Session.set('wasHCP', false);
Reload._onMigrate(() => {
Session.set('wasHCP', true);
return [true];
});
});
Going further
Want to know more? The new Hot Code Push article in the Meteor Guide explains this topic in more depth.
Hot Code Push is at its most powerful when you can rely on it without any doubts. I hope these articles help you solve your own Hot Code Push issues so you can speed up your development cycle and focus on serving your customers.
PS: did this article help you solve an undocumented issue? Congrats 🥳 you are in a unique position to do many developers just like you a big favor! You can add your solution to the Meteor guide or even suggest a fix to Meteor.
Hot Code Push, step by step was originally published in Meteor Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.