Build a Cordova Hook to setup environment specific constants

After deploying my Cordova application about 100 times, I got really tired of changing environment specific constants. I was doing some work in the office and then packing up my laptop and then doing some more work at home. One of the most annoying things that I found myself doing was changing the server url to have the local ip address for my computer. I was using my local computer to serve up the JSON for my Cordova application via http. So instead of “http://localhost:8080/myserver”, I had to use “http://192.168.1.12:8080/myserver”, for example. I needed to do this so that the mobile device could make http calls to my local computer which was running my server side code.

So what I really wanted was to be able to dynamically determine my local ip address and replace a constant for this. I already had a constant file for the backend server url variable, I just needed a way to get the local ip address. Also, I wanted to be able to have a “local”, “qa” and a “prod” version of my constant file so I could change other attributes for each environment. So anywhere throughout the javascript in my application, I can reference these constants directly, instead of replacing constants directly in each javascript file.

I placed an identical config.js file in my “www” folder mainly so that I can use my desktop browser to test 90% of the application locally. I just can’t test camera or push notification functionality using the desktop browser. For desktop browser testing, I use python by typing this while at the root of my www folder in the project folder:

$ python -m SimpleHTTPServer

Back to the task at hand. So in the root of my Cordova project, I created a “config” folder with my config-xxxxx.js files.
www/config.js
config/config-local.js
config/config-qa.js
config/config-prod.js

config.js and config-local.js


BACKEND_SERVER_URL=http://localhost:8080/myserver
EMAILS_ENABLED=false

config-qa.js


BACKEND_SERVER_URL=http://qa_domain_name/myserver
EMAILS_ENABLED=false

config-prod.js


BACKEND_SERVER_URL=http://prod_domain_name/myserver
EMAILS_ENABLED=true

001_replace_config_variables.js


#!/usr/bin/env node
/**
 * Detect the current build by looking at the config_target environment variable.
 * 
 * Replace config.js with config/config-xxxx.js (where config_target=xxxx)
 * If config-xxxx.js doesn't exist, execution will fail.
 *
 * You can set the config_target as:
 *    export config_target=qa
 * or right before the cordova command like this:
 *    config_target=qa cordova build --release android ios
 *
 * If config_target is not available, by default "local" will be assumed.
 *    
 */
var fs = require("fs");
var path = require("path");
var rootdir = process.argv[2];
var config_target = process.env.config_target || "local"; // default to local

console.log("Running hook: " + path.basename(process.env.CORDOVA_HOOK));

// Return ip address otherwise return "localhost"
function get_ip_address() {
   var os=require('os');
   var ifaces=os.networkInterfaces();
   var ipAddresses = [];
   for (var dev in ifaces) {
      ifaces[dev].forEach(function(details){
          if (details.family=='IPv4' && !details.internal) {
           ipAddresses.push(details.address);
          }
      });
   }	
   return ipAddresses.length > 0 ? ipAddresses[0] : "localhost";
}

function replace_string_in_file(filename, to_replace, replace_with) {
    var data = fs.readFileSync(filename, 'utf8');
    var re = new RegExp(to_replace, "g");
    var result = data.replace(re, replace_with);
    fs.writeFileSync(filename, result, 'utf8');
}

var srcfile = path.join(rootdir, "config", "config-" + config_target + ".js");
 
// Define the destination paths for the config.js file for each platform
var configFilesToReplace = {
    "android" : "platforms/android/assets/www/config.js",
    "ios" : "platforms/ios/www/config.js"
}; 
    
var platforms = process.env.CORDOVA_PLATFORMS.split(',');
 
for(var i=0; i < platforms.length; i++) {
    console.log("Modifying config for platform " + platforms[i] + ", config_target=" + config_target);
    var destfile = path.join(rootdir, configFilesToReplace[platforms[i]]);
 
    if (!fs.existsSync(srcfile)) {
         throw "Missing config file: "+srcfile;
    } else {
        console.log("copying " + srcfile + " to " + destfile);
 
        var srcContent = fs.readFileSync(srcfile, 'utf8');
        fs.writeFileSync(destfile, srcContent, 'utf8');
        
        // if config_target is local(which is the default) then replace localhost with the actual ip address
        if (config_target == "local") {
           var ip_address = get_ip_address();
           console.log("replacing localhost with " + ip_address);
           replace_string_in_file(destfile, "localhost", ip_address);
        }
    }
}


In order to get this hook to be automatically executed as part of the build process, just place the hook javascript in the hooks/after_prepare/ folder. It will be executed after the prepare step. So for testing purposes, you can just execute:

$ cordova prepare ios android

If you are using platforms other than android and ios, you will need to modify the configFilesToReplace variable in the hook js routine with the destination paths appropriate for the platforms that you support.

After the script runs, a modified config.js file from the config folder will end up being copied into the www folder for each of the specified platforms.

Also, don't forget to include the config.js file in your index.html file

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
    <title>Home</title>
    <!-- cordova script (a 404 will occur when executed with a desktop browser) -->
    <script src="cordova.js"></script>
    <script src="config.js"></script>  
  </head>

  <body>
  </body>
  
</html>

I hope this post saves you some time and aggravation. Also, special thanks to the people posting the information in the links below. I used these three sources to come up with my solution.

http://intown.biz/2014/05/13/build-configurations-in-cordova-using-hooks/
http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/
http://stackoverflow.com/questions/3653065/get-local-ip-address-in-node-js

3 thoughts on “Build a Cordova Hook to setup environment specific constants

  1. Guilherme

    Man..
    Thank you.. this help me a lot.

    I just had to change the config.js to
    BACKEND_SERVER_URL=’http://localhost:8080/myserver’
    just add the ‘ ‘ around my url… otherwise, this give me an error…

    and can you explain to me why I have to add cordova.js to my index.html?

    thanks again

    1. keithdmoore Post author

      Ok. I didn’t need the single quotes for my constants but that is good to know and may help others if they have an issue with that. The ‘cordova.js’ needs to be in the index.html file when you are deploying to an actual mobile device. The ‘cordova.js’ file gets added when you add a platform like: cordova platform add ios

  2. Pingback: Release configuration for Ionic project, similar to flavours in Android studio - HTML CODE

Leave a Reply

Your email address will not be published. Required fields are marked *