Hot deploy updates with the cordova-hot-code-push plugin

Published: January 18, 2017  •  Updated: May 18, 2017  •  ionic3, cordova

In the previous two posts about Ionic Deploy and CodePush we explored the possibilities of updating Cordova applications directly over the air without re-submitting them to the mobile app stores.

In this article we will look at another solution to send updates directly to a Cordova app. This time it's a Cordova plugin without any backend service. You have to host the updates on your own server or on a third party service that provides static file hosting (i.e. Amazon S3, Firebase Hosting).

The plugin is called cordova-hot-code-push and you find the source code on GitHub: https://github.com/nordnet/cordova-hot-code-push


Setup

The plugin needs some additional files with metadata information to work properly. You could create them manually, but the plugin provides a CLI that creates these files for you. To install the CLI enter this command

npm install -g cordova-hot-code-push-cli

Don't forget to prefix the command with sudo when you are working on macOS or Linux.


Initial Version

Let's start with the initial version of our demo app. We create an app based on the blank starter template, add the Android platform and install the plugin.

ionic start hotcodepush blank
cd hotcodepush
cordova platform add android
cordova plugin add cordova-hot-code-push-plugin

Next we call the init command of the CLI to initialize the plugin. This command creates a file called cordova-hcp.json in the project's root directory. The CLI will use this information later when it creates the metadata files for the update.

cordova-hcp init

The init command does ask you several questions.

Running init
Please provide: Enter project name (required):  hotcodepush
Please provide: Amazon S3 Bucket name (required for cordova-hcp deploy):
Please provide: Path in S3 bucket (optional for cordova-hcp deploy):
Please provide: Amazon S3 region (required for cordova-hcp deploy):  (us-east-1)
Please provide: IOS app identifier:
Please provide: Android app identifier:  
Please provide: Update method (required):  (resume)
Please provide: Enter full URL to directory where cordova-hcp build result will be uploaded:  https://static.rasc.ch/hcp
Project initialized and cordova-hcp.json file created.
If you wish to exclude files from being published, specify them in .chcpignore
Before you can push updates you need to run "cordova-hcp login" in project directory

The CLI has built-in support for Amazon S3, but I will host the update files on my own server, therefore I leave the answers to the Amazon S3 questions empty. When you don't have your own server you could start a local http server and open a ngrok tunnel. The identifiers are optional too. Add them if you want to use the "Request application update through the store" feature.

The plugin supports three update methods:

The last question asks for an URL where the update will be located.

Next we need to configure the plugin. Open the file config.xml and add these lines. The url should be the same we provided to the init command with the path /chcp.json attached.

<chcp>
 <config-file url="https://static.rasc.ch/hcp/chcp.json"/>
</chcp>

The url specifies the location where the plugin finds the update metadata file. We will create this file in the next step with the cordova-hcp build command. By default the plugin does everything automatically. We don't have to add any TypeScript code. Everything is in place and we can build and install the initial version of our app.

npm run build --prod
cordova-hcp build
cordova run android
// cordova emulate android

It's important that we call cordova-hcp build. This command creates the chcp.json and chcp.manifest files in the www folder and they need to be packaged with our app. The chcp.json file contains information about the update, most importantly the release tag. This string has to change with every update.

{
 "name": "hotcodepush",
 "ios_identifier": "",
 "android_identifier": "",
 "update": "resume",
 "content_url": "https://static.rasc.ch/hcp",
 "release": "2017.01.18-17.26.42"
}

The chcp.manifest file lists all the files in the www folder. The plugin only downloads files onto the device that have changed. To determine a change it will compare the hash codes.

[
 {
   "file": "assets/fonts/ionicons.eot",
   "hash": "bdf1d30681cf87986c385eea78e8de9a"
 },
 {
   "file": "assets/fonts/ionicons.scss",
   "hash": "d500900a39e39e0b966a5ed077e97a66"
 },
.....
]

Update

Next we create an update in our app by changing the version string in the home.html file.

<ion-card-content>
 0.0.2
</ion-card-content>

Now we build the app and run the build command again. It's important that this command is called everytime we want to release an update.

npm run build --prod
cordova-hcp build

Next we need to copy the complete www folder to the update location. To check if the paths are correct open the url you entered in the config.xml in a browser. In my case this is the url https://static.rasc.ch/hcp/chcp.json. If you see the content in the browser everything is okay.

The plugin checks for new updates every time the app launches or when the user resumes the app from the background. And because we set the update method to resume, an update will only be installed after a resume from the background. When the initial app is in the foreground press the home button and open it again. This triggers the update check and the plugin downloads the changed files. To install the update we need to send the app again back to background and open it again. You should see the changed string on the home screen (0.0.2).

If we change the update method to now ("update": "now") an update would be applied immediately after the download.

Manual mode

As seen in the example above, the plugin does everything automatically. We didn't have to write a single line of code for the update process to work. But if you need more control over the process you can disable the automatic mode and do everything manually in the TypeScript code.

To turn off the automatic update we add this lines to the config.xml file. We no longer need the config url because we can set the url in the code.

<chcp>
 <auto-download enabled="false" />
 <auto-install enabled="false" />
</chcp>

https://github.com/ralscha/blog/blob/master/hotcodepush/config.xml

As an example in the file src/app/app.component.ts we add a resume listener that downloads the update and installs it.

export class MyApp {
  rootPage: any = HomePage;

  constructor(private platform: Platform, 
              private statusBar: StatusBar, 
              private splashScreen: SplashScreen) {
    platform.ready().then(() => {
      statusBar.styleDefault();
      splashScreen.hide();
    });

    //manual mode. remove this for automatic mode
    platform.resume.subscribe(() => this.fetchUpdate());
  }

  fetchUpdate() {
    const options = {
      'config-file': 'https://static.rasc.ch/hcp/chcp.json'
    };
    chcp.fetchUpdate(this.updateCallback, options);
  }

  updateCallback(error, data) {
    if (error) {
      console.log('Failed to load the update with error code: ' + error.code);
      console.log(error.description);
    } else {
      console.log('Update is loaded');

      chcp.installUpdate(error => {
        if (error) {
          console.log('Failed to install the update with error code: ' + error.code);
          console.log(error.description);
        } else {
          console.log('Update installed!');
        }
      });
    }
  }

}

https://github.com/ralscha/blog/blob/master/hotcodepush/src/app/app.component.ts

The chcp.fetchUpdate function downloads an update if it's available. It will throw an error with the code -1 if it does not find an update. If the plugin successfully downloaded an update the code will call chcp.installUpdate to apply the update. The app will immediately restart and present the new content.

You find the complete source code for this project on GitHub.