Walkthrough: Deploy a JavaScript JMS App as a Hybrid iOS App

In this walkthrough, you will learn how to deploy an existing JavaScript JMS web app as a hybrid iOS app for iOS. This topic walks you through the following subjects:

  1. What You Will Accomplish
  2. Before You Begin
  3. Install the Gateway, Xcode 6.2, and Cordova 6.0
  4. Build the Hybrid Mobile App Using Cordova
  5. Run the Gateway and Apache ActiveMQ
  6. Run and Test the Hybrid Mobile App in iOS Simulator
  7. Test the Hybrid iOS app on an iOS device
  8. Troubleshooting
  9. Summary

Notes:
  • This walkthrough only covers development on the Mac OS X operating system using Xcode 6.2 and Cordova 6.0. You can attempt to perform this walkthrough using a later version of Xcode, but the walkthrough has been tested with Xcode 6.2. You can find different versions of Xcode here: https://developer.apple.com/downloads/ (requires Apple ID to log in).
  • A hybrid iOS app is a hybrid of a web-based app and a native iOS app. A native iOS app is built using Objective C and the iOS SDK. A hybrid iOS app is built using HTML, CSS, and JavaScript like a typical browser-based web app, and then packaged in a framework that allows it to be converted into an app that runs on iOS devices. Hybrid iOS apps enable web developers to leverage their experience to create apps for iOS and other mobile platforms. For information on native iOS development, see the iOS Developer Library.
  • A hybrid iOS app is also different from a browser-based web app that is intended for viewing in the Safari browser on iOS. If you have an existing Kaazing WebSocket Gateway JavaScript JMS browser-based web app, you can choose to create a hybrid iOS app or to modify the web content to make it compatible with Safari on iOS. For more information, see the Safari Web Content Guide.

What You Will Accomplish

At the end of this walkthrough, a JavaScript JMS demo created using the Kaazing WebSocket Gateway JavaScript JMS libraries runs as a hybrid iOS app on iOS, connects to the Apache ActiveMQ broker via the Gateway, and sends and receives JMS messages using a native or emulated WebSocket connection. Users can run the hybrid iOS app on any iOS device and connect via the Gateway to the Apache ActiveMQ broker.

This walkthrough uses the out of the box JavaScript JMS demo that is included with the Gateway as the example app, but the steps outlined in this walkthrough are the same for other JavaScript JMS client applications built with the Gateway.

Note: For this walkthrough, you can use any JMS-compliant message broker. By default, the Gateway is configured to connect to the server on tcp://localhost:61613. You can configure the connect URL in the file GATEWAY_HOME/conf/gateway-config.xml. See About Integrating Kaazing WebSocket Gateway and JMS-Compliant Message Brokers for more information.

Before You Begin

Before starting this walkthrough you need the following:

Learn about supported browsers, operating systems, and platform versions in Release Notes.

Note: Steps for installing and configuring Xcode 6.2 and Cordova 6.0 are included in this walkthrough.

Install the Gateway, Xcode 6.2, and Cordova 6.0

The following steps take you through the installation of the software required for deploying a hybrid iOS app. If you already have this software installed, you can simply note the locations of the installed software for later use with the shell script.

  1. Install the Gateway as described in Setting Up Kaazing WebSocket Gateway.
  2. Install Xcode 6.2 from http://developer.apple.com/devcenter/download.action?path=/Developer_Tools/Xcode_6.2/Xcode_6.2.dmg (requires Apple ID to log in). You can also install Xcode 6.2 from https://developer.apple.com/downloads/ by searching for Xcode 6.2. Once Xcode is installed, install the iOS Simulator.

    1. In Xcode, click Xcode, and then click Preferences.
    2. Click Downloads, and then click Components.
    3. Click the download link next to iOS 8.2 Simulator. You can choose to use different simulators, but iPad 2 / iOS 8.2 Simulator is used for this walkthrough.
  3. Quit Xcode. Xcode should not be running when you are installing Cordova.
  4. Download Cordova 6.0 by following the step in Installing the Cordova CLI.

Build the Hybrid Mobile App Using Cordova

The following steps build the hybrid iOS app using the Xcode project you created.

  1. Open a Terminal at the folder where you want to store your app.
  2. In Terminal, enter the following command: cordova create myApp com.example.myapp myApp --template GATEWAY_HOME/demo/cordova/www.

    For details on the command parameters, see Create the App from Cordova.

  3. Press Enter. Cordova will create the MyApp app.
  4. In the myApp folder created by Cordova, open the HTML file that Cordova will use for the GUI of your app. It located here: myApp/www/index.html.
  5. Replace the contents of index.html with the following:

    <!DOCTYPE html>
    <html class="no-js">
    <head>
    
      <link rel="stylesheet" href="resources/css/normalize.css">
      <link rel="stylesheet" href="resources/css/dev.css">
      <link rel="stylesheet" href="resources/css/demo.css">
    
      <style>
    
        body {
          margin: 2px;
          margin-top: 0;
          background-color: whitesmoke;
        }
    
        h1 {
          font-size: 24px;
          margin-top: 0;
        }
    
        div.setting {
          margin-top: 5px;
        }
        div.setting:first-of-type {
          margin-top: 0;
        }
        div.setting label {
          width: 80px;
        }
        div.setting input {
          width: 180px;
          margin-bottom: 0;
        }
        div.setting button {
          width: 85px;
        }
    
        #console {
          position: relative;
          float: left;
          overflow-y: scroll;
          width: 100%;
          height: 200px;
          border: solid 1px #dddddd;
          background-color: white; }
    
        #console pre {
          font-family: monospace; }
    
        #console > div {
          border-bottom: dashed 1px #dddddd;
          padding-top: 5px;
          padding-bottom: 5px; }
    
        #console div {
          font-family: monospace;
          padding-left: 5px;
          font-size: 15px;
          line-height: 18px; }
    
        #console > div:nth-child(even) {
          background-color: #fafafa; }
    
        #console div div {
          line-height: 15px; }
    
        #console .error {
          color: red; }
    
        #console > div > div {
          display: block;
          margin-left: 15px;
          font-size: smaller; }
    
        #console .sendMessage {
          color: #336633; }
    
        #console .sendMessage .destination {
          color: #577557; }
    
        #console .sendMessage .properties {
          color: #45ad45; }
    
        #console .sendMessage .headers {
          color: #8aad45; }
    
        #console.hidden .headers {
          /*    display: none; */
          background-color: red; }
    
        #console .receiveMessage {
          color: #0000ff; }
    
        #console .receiveMessage .destination {
          color: #6666ff; }
    
        #console .receiveMessage .properties {
          color: #4791ff; }
    
        #console .receiveMessage .headers {
          color: #7193eb; }
    
        #console .txSendMessage {
          color: #606; }
    
        #console .txSendMessage > div {
          color: #9e4d9e; }
    
        #subscriptions {
          border: 0;
          font-size: smaller;
          width: 100%;
          margin: 0;
        }
    
        #subscriptions th, #subscriptions td {
          background-color: whitesmoke;
          border: 0;
          text-align: left;
          padding-left: 5px;
          padding-right: 5px;
          line-height: 150%;
    /*      padding-top: 3px;
          padding-bottom: 3px;
    */    }
    
        #subscriptions th {
          padding-top: 2px;
          padding-bottom: 2px;
          line-height: 15px;
          font-size: 14px;
        }
    
        #subscriptions > thead, #subscriptions > tbody > tr:nth-child(even) {
          background-color: #fbfbfb; }
    
        #subscriptions th.destination, #subscriptions td.destination {
          white-space: nowrap;
    /*      min-width: 125px; */
          /*line-height: 15px;*/
        }
    
        #subscriptions th.unsubscribe, #subscriptions td.unsubscribe {
          width: 100%; }
    
        #subscriptions button {
          font-size: smaller; }
    
        #console_div label {
          width: auto;
          margin-left: 5px; }
    
        #middle .subscriptionTag {
          color: #aaaaaa; }
    
        div.section-heading {
          font-weight: bold;
        }
    
        div.section-heading-left {
          float: left;
        }
        div.toggle {
          float: right;
        }
    
        input[type=checkbox] {
          width: 20px;
        }
    
        label[for=toggleJmsHeadersCb] {
          width: 140px;
          float: none;
          font-weight: normal;
        }
    
        button#clear {
          float: right;
        }
    
        img#logo {
          width: 166px;
          margin-left: 3px;
        }
    
      </style>
    
      <script src="resources/js/jquery-1.9.1.min.js"></script>
      <script src="resources/js/modernizr.js"></script>
    
    </head>
    <body>
    
    <!--  Kaazing scripts -->
    <script type="text/javascript" language="javascript" src="WebSocket.js"></script>
    <script type="text/javascript" language="javascript" src="JmsClient.js"></script>
    
    <script type="text/javascript">
    
    var connection;
    var session;
    
    /* UI Elements */
    var logConsole, url, connectBut;
    var destination, message, subscribe, send;
    var rollback, clear;
    var receivedMessageCount, receivedMessageCounter = 0;
    var subscriptionsTable;
    var destinationCounter = 1;
    var toggleJmsHeadersCb;
    var logoHeight;
    
    function clearLog() {
      while (logConsole.childNodes.length > 0) {
        logConsole.removeChild(logConsole.lastChild);
      }
    }
    
    // Log a string message
    function log(message) {
      var div = document.createElement("div");
      div.className = "logMessage"
      div.innerHTML = message;
      logDiv(div);
    }
    
    function logDiv(div) {
      logConsole.appendChild(div);
      toggleJmsHeaders(); // Hide the headers if that's what the user specified
      // Make sure the last line is visible.
      logConsole.scrollTop = logConsole.scrollHeight;
      while (logConsole.childNodes.length > 20) {
        // Delete two rows to preserved the alternate background colors.
        logConsole.removeChild(logConsole.firstChild);
        logConsole.removeChild(logConsole.firstChild);
      }
    }
    
    function updateConnectionButtons(connected) {
      if (connected) {
        connectBut.innerHTML = "Disconnect";
      } else {
        connectBut.innerHTML = "Connect";
      }
      subscribe.disabled = !connected;
      send.disabled = !connected;
    }
    
    function createDestination(name, session) {
      if (name.indexOf("/topic/") == 0) {
        return session.createTopic(name);
      }
      else if (name.indexOf("/queue/") == 0) {
        return session.createQueue(name);
      }
      else {
        throw new Error("Destination must start with /topic/ or /queue/");
      }
    }
    
    function handleConnectBut() {
      if (connectBut.innerHTML == "Connect") {
        doConnect();
      } else {
        doDisconnect();
      }
    }
    
    function doConnect() {
      log("CONNECT: " + url.value);
    
      var jmsConnectionFactory = new JmsConnectionFactory(url.value);
    
      //setup challenge handler
      setupSSO(jmsConnectionFactory.getWebSocketFactory());
      try {
        var connectionFuture =
        jmsConnectionFactory.createConnection(null, null, function () {
          if (!connectionFuture.exception) {
            try {
              connection = connectionFuture.getValue();
              connection.setExceptionListener(handleException);
    
              log("CONNECTED");
    
              session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
              transactedSession = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
    
              connection.start(function () {
                updateConnectionButtons(true);
              });
            }
            catch (e) {
              handleException(e);
            }
          }
          else {
            handleException(connectionFuture.exception);
          }
        });
      }
      catch (e) {
        handleException(e);
      }
    }
    
    function handleException(e) {
      log("<span class='error'>EXCEPTION: " + e+"</span>");
    
      if (e.type == "ConnectionDisconnectedException") {
        updateConnectionButtons(false);
      }
    }
    
    function doDisconnect() {
      // Clear any subscriptions.
      if (document.getElementsByClassName) {
        var subscriptions = document.getElementsByClassName("unsubscribeButton");
        while (subscriptions[0]) {
          subscriptions[0].click();
        }
      } else {
        // The IE way.
        var unsubscribeButtons = subscriptionsTable.getElementsByTagName("button");
        while (unsubscribeButtons.length > 0) {
          var b = unsubscribeButtons[0];
          if (b.className == "unsubscribeButton") {
            b.click();
          }
        }
      }
    
      log("CLOSE");
      try {
        connection.close(function () {
          log("CONNECTION CLOSED");
          updateConnectionButtons(false);
        });
      }
      catch (e) {
        handleException(e);
      }
    }
    
    function handleSubscribe() {
      var name = destination.value;
    
      var destinationId = destinationCounter++;
    
      log("SUBSCRIBE: " + name + " <span class=\"subscriptionTag\">[#"+destinationId+"]</span>");
    
      var dest = createDestination(name, session);
    
      var consumer;
    
      consumer = session.createConsumer(dest);
    
      consumer.setMessageListener(function(message) {
        handleMessage(name, destinationId, message);
      });
    
      // Add a row to the subscriptions table.
      //
    
      var tBody = subscriptionsTable.tBodies[0];
    
      var rowCount = tBody.rows.length;
      var row = tBody.insertRow(rowCount);
    
      var destinationCell = row.insertCell(0);
      destinationCell.className = "destination";
      destinationCell.appendChild(document.createTextNode(name+" "));
      var destNode = document.createElement("span");
      destNode.className = "subscriptionTag";
      destNode.innerHTML = "[#"+destinationId+"]";
      destinationCell.appendChild(destNode);
    
      var unsubscribeCell = row.insertCell(1);
      unsubscribeCell.className = "unsubscribe";
      var unsubscribeButton = document.createElement("button");
      unsubscribeButton.className = "unsubscribeButton";
      unsubscribeButton.innerHTML = "Unsubscribe";
      unsubscribeButton.addEventListener('click', function(event) {
        var targ;
        if (event.target) {
          targ = event.target;
        } else {
          targ=event.srcElement; // The wonders of IE
        }
        log("UNSUBSCRIBE: " + name + " <span class=\"subscriptionTag\">[#"+destinationId+"]</span>");
        if (consumer) {
          consumer.close(null);
        }
        var rowIndex = targ.parentElement.parentElement.rowIndex
        subscriptionsTable.deleteRow(rowIndex);
          setLogPaneHeight();
      }, false);
      unsubscribeCell.appendChild(unsubscribeButton);
    
      setLogPaneHeight();
    }
    
    function handleMessage(destination, destinationId, message) {
      var content = "";
    
      if (message instanceof TextMessage) {
        content = "RECEIVED TextMessage: " + message.getText();
      }
      else if (message instanceof BytesMessage) {
        var body = [];
        message.readBytes(body);
        content = "RECEIVED BytesMessage: " + body;
      }
      else if (message instanceof MapMessage) {
        var keys = message.getMapNames();
        content = "RECEIVED MapMessage: <br/>";
    
        for (var i=0; i<keys.length; i++) {
          var key = keys[i];
          var value = message.getObject(key);
          var type;
          if (value == null) {
            type = "";
          }
          else if (value instanceof String) {
            type = "String";
          }
          else if (value instanceof Number) {
            type = "Number";
          }
          else if (value instanceof Boolean) {
            type = "Boolean";
          }
          else if (value instanceof Array) {
            type = "Array";
          }
          content += key + ": " + value;
          if(type != "") {
            content += " (" + type + ")"
          }
          content += "<br />";
        }
      }
      else {
        content = "RECEIVED UNKNOWN MESSAGE";
      }
    
      var div = document.createElement("div");
      div.className = "logMessage receiveMessage"
      div.innerHTML = content;
    
      div.appendChild(buildDestinationDiv("Destination", destination));
    
      div.appendChild(buildPropertiesDiv(message));
    
      div.appendChild(buildJMSHeadersDiv(message, true));
    
      logDiv(div);
    
      receivedMessageCount.innerHTML = ++receivedMessageCounter;
    }
    
    var logMessageSend = function(classname, prefix, destination, messageStr, message) {
      var div = document.createElement("div");
      div.className = "logMessage "+classname
      div.innerHTML = prefix + messageStr;
    
      div.appendChild(buildPropertiesDiv(message));
    
      div.appendChild(buildJMSHeadersDiv(message, false));
    
      div.appendChild(buildDestinationDiv("Destination", destination));
    
      logDiv(div);
    }
    
    var buildDestinationDiv = function(label, destName, destId) {
      var destinationDiv = document.createElement("div");
      destinationDiv.className = "destination";
      var destIdStr = "";
      if (destId != undefined) {
        destIdStr = " [#"+destId+"]";
      }
      destinationDiv.innerHTML += label + ": " + destName+destIdStr;
      return destinationDiv;
    }
    
    var buildPropertiesDiv = function(message) {
      var propsDiv = document.createElement("div");
      propsDiv.className = "properties";
      var props = message.getPropertyNames();
      while (props.hasMoreElements()) {
        var propName = props.nextElement();
        var propValue = message.getStringProperty(propName);
        propsDiv.innerHTML += "Property: "+propName+"="+propValue+"<br>";
      }
      return propsDiv;
    }
    
    var buildJMSHeadersDiv = function(message, receive) {
      var headersDiv = document.createElement("div");
      headersDiv.className = "headers";
      var deliveryModeStr;
      switch (message.getJMSDeliveryMode()) {
      case DeliveryMode.NON_PERSISTENT:
        deliveryModeStr = "NON_PERSISTENT";
        break;
      case DeliveryMode.PERSISTENT:
        deliveryModeStr = "PERSISTENT";
        break;
      default:
        deliveryModeStr = "UNKNOWN";
      }
    
      var jmsDestination = message.getJMSDestination();
      var destinationName = (jmsDestination instanceof Queue) ? jmsDestination.getQueueName()
      : jmsDestination.getTopicName();
      headersDiv.innerHTML += "JMSDestination: " + destinationName +"<br>";
    
      if (receive) {
        headersDiv.innerHTML += "JMSRedelivered: " + message.getJMSRedelivered()+"<br>";
      }
    
      headersDiv.innerHTML += "JMSDeliveryMode: "+message.getJMSDeliveryMode()+" ("+deliveryModeStr+")<br>";
      headersDiv.innerHTML += "JMSPriority: "+message.getJMSPriority()+"<br>";
      headersDiv.innerHTML += "JMSMessageID: "+message.getJMSMessageID()+"<br>";
      headersDiv.innerHTML += "JMSTimestamp: "+message.getJMSTimestamp()+"<br>";
      headersDiv.innerHTML += "JMSCorrelationID: "+message.getJMSCorrelationID()+"<br>";
      headersDiv.innerHTML += "JMSType: "+message.getJMSType()+"<br>";
      headersDiv.innerHTML += "JMSReplyTo: "+message.getJMSReplyTo()+"<br>";
      return headersDiv;
    }
    
    var addProperties = function(message) {
      var i = 1;
      var propName;
      while (propName = document.getElementById("propName"+i)) {
        if (propName.value.length > 0) {
          var propValue = document.getElementById("propValue"+i);
          message.setStringProperty(propName.value, propValue.value);
        }
        i++;
      }
    }
    
    function handleSend() {
      var name = destination.value;
      var dest = createDestination(name, session);
      var producer = session.createProducer(dest);
    
      var textMsg = session.createTextMessage(message.value);
    
      addProperties(textMsg);
    
      try {
        var future = producer.send(textMsg, function(){
          if (future.exception) {
            handleException(future.exception);
          }
        });
      } catch (e) {
        handleException(e);
      }
    
      logMessageSend("sendMessage", "SEND TextMessage: ", destination.value, message.value, textMsg);
    
      producer.close();
    }
    
    var toggleJmsHeaders = function(event) {
      $('div.headers').toggleClass('hidden', !toggleJmsHeadersCb.checked);
    }
    
    $( document ).ready(function() {
    
      // Initialize UI elements
      url = document.getElementById("url");
    
      connectBut = document.getElementById("connectBut");
    
      logConsole = document.getElementById("console")
      receivedMessageCount = document.getElementById("receivedMessageCount");
      toggleJmsHeadersCb = document.getElementById("toggleJmsHeadersCb");
    
      destination = document.getElementById("destination");
      message = document.getElementById("message");
      subscribe = document.getElementById("subscribe");
      send = document.getElementById("send");
    
      clear = document.getElementById("clear");
    
      subscriptionsTable = document.getElementById("subscriptions");
    
      // default the location
      url.value = "ws://localhost:8001/jms";
    
      updateConnectionButtons(false);
    
      connectBut.onclick = handleConnectBut;
      subscribe.onclick = handleSubscribe;
      send.onclick = handleSend;
    
      clear.onclick = clearLog;
      toggleJmsHeadersCb.onclick = toggleJmsHeaders;
    
      // initialize the disabled states
      connectBut.disabled = null;
    
      logoHeight = $("#logo").height();
    
      setLogPaneHeight();
    
    
    });
    
      // Grow the log pane to fit to the bottom of the window, if there is
      // space available.
    function setLogPaneHeight() {
      var windowHeight = $(window).height();
      var console = $("#console");
      if (console.position().top < windowHeight - logoHeight) {
        console.css("height", windowHeight - console.position().top - (console.outerHeight() - console.height()) - logoHeight);
      }
    }
    
    function setupSSO(webSocketFactory) {
      /* Respond to authentication challenges with popup login dialog */
      var basicHandler = new BasicChallengeHandler();
      basicHandler.loginHandler = function(callback) {
        popupLoginDialog(callback);
      }
      webSocketFactory.setChallengeHandler(basicHandler);
    }
    
    
    function popupLoginDialog(callback) {
      //popup dialog to get credentials
      var popup = document.getElementById("sso_logindiv");
      popup.style.display = "block";
      var login = document.getElementById("sso_login");
      var cancel = document.getElementById("sso_cancel");
    
      //"OK" button was clicked, invoke callback function with credential to login
      login.onclick = function() {
        var username = document.getElementById("sso_username");
        var password = document.getElementById("sso_password");
        var credentials = new PasswordAuthentication(username.value, password.value);
        //clear user input
        username.value = "";
        password.value = "";
        //hide popup
        popup.style.display = "none";
        callback(credentials);
      }
      //"Cancel" button has been clicked, invoke callback function with null argument to cancel login
      cancel.onclick = function() {
        var username = document.getElementById("sso_username");
        var password = document.getElementById("sso_password");
        //clear user input
        username.value = "";
        password.value = "";
        //hide popup
        popup.style.display = "none";
        callback(null);
      }
    }
    
    </script>
    
      <h1>Cordova JMS Messaging Demo</h1>
    
      <div class="setting">
      <label for="url">Location</label>
      <input id="url" />
      <button id="connectBut">Connect</button>
      <!-- Login dialog for testing authentication -->
      <div id="sso_logindiv" style="margin-left: 2px; position: absolute; border: 1px solid black; border-radius:10px; display: none; height: 190px; width: 318px; background-color:#d0e7fd; z-index: 999;">
        <div style="margin-left: 20px; height: 35px; margin-top: 20px; font-weight: bold;">Login</div>
        <div style="height: 124px; width: 296px; border: 1px solid black; border-radius:10px; background-color:white; margin-left: 10px;">
          <div style="margin-left:10px; margin-top: 10px;">
            <span style="width: 60px; font-size:11pt;">Username:</span><input id="sso_username" size="12" style="width: 180px" value="" />
          </div>
          <div style="margin-left:10px">
            <span style="width: 60px;  font-size:11pt;">Password:</span><input id="sso_password" type="password" size="12" style="width: 180px" value="" />
          </div>
          <div style="margin-left:45px">
            <button id="sso_login" style="margin-left:25px; width: 60px;">OK</button>
            <button id="sso_cancel"  style="margin-left:25px; width: 60px;">Cancel</button>
          </div>
        </div>
      </div>
      <!-- Login dialog for testing authentication -->
      </div>
    
      <div class="setting">
      <label for="destination">Destination</label>
      <input id="destination" value="/topic/destination">
      <button id="subscribe">Subscribe</button>
      </div>
    
      <div class="setting">
      <label for="message">Message</label>
      <input id="message" value="Hello, message">
      <button id="send">Send</button>
      </div>
    
      <hr />
    
      <div class="section-heading">Subscriptions</div>
      <table id="subscriptions">
    
        <tbody>
    
        </tbody>
    
      </table>
    
      <hr />
    
      <div class="section-heading section-heading-left">Log</div>
      <div class="toggle">
      <input type="checkbox" id="toggleJmsHeadersCb" class="cb">
      <label for="toggleJmsHeadersCb">Show JMS headers</label>
      </div>
      <div style="clear: both;"></div>
    
      Messages received : <span id="receivedMessageCount">0</span>
      <button id="clear">Clear Log</button>
      <div style="clear: both;"></div>
    
      <div id="console"></div>
      <div style="clear: both;"></div>
    
      <img id="logo" src="resources/images/nav-logo-kaazing.png" />
    
    </body>
    </html>
    

    Note: This walkthrough uses the out of the box JavaScript JMS demo as an example app, but when you create your own JavaScript JMS app, you can create it in your favorite IDE and then copy it to this www directory. You must name the HTML file for your app index.html and you must ensure that links in index.html point to the JavaScript WebSocket (by default, GATEWAY_HOME/lib/client/javascript) and JMS library (by default, GATEWAY_HOME/lib/client/javascript/jms) files are updated to reflect the new location:

          <script src="WebSocket.js" type="text/javascript" language="javascript"></script>    
          <script src="JmsClient.js" type="text/javascript" language="javascript"></script>    
          
  6. In Terminal, go to the myApp folder created by the Cordova command to see the files created by Cordova (cd myApp).
  7. To specify the target platform as iOS, enter the following command: cordova platform add ios. Note: To see a list of the current platforms available, enter: cordova platforms ls.
  8. To build the project, enter the following command: cordova build ios. The build process might take some time. When it is finsihed, Cordova generates platform-specific code within the project's platforms subdirectory (for example, myApp/platforms/ios/myApp.xcodeproj).

Run the Gateway and Apache ActiveMQ

The following steps start the Apache ActiveMQ service that is included with the Gateway, configure the cross-site constraint for the jms service on the Gateway, and then run the Gateway.

  1. Start Apache ActiveMQ. For steps on starting Apache ActiveMQ, see the setup information for Apache ActiveMQ. Typically, open a Terminal in ACTIVEMQ_HOME/bin and enter: ./activemq start.
  2. Open the GATEWAY_HOME/conf/gateway-config.xml file in a text editor. Modify the jms service cross-site-constraint setting to allow all cross-origin site access to the service. For example:

    <service>
      <name>JMS Service</name>
      <description>JMS Service</description>
      <accept>ws://${gateway.hostname}:${gateway.extras.port}/jms</accept>
    
      <type>jms</type>
    
      <properties>
        <connection.factory.name>ConnectionFactory</connection.factory.name>
        <context.lookup.topic.format>dynamicTopics/%s</context.lookup.topic.format>
        <context.lookup.queue.format>dynamicQueues/%s</context.lookup.queue.format>
        <env.java.naming.factory.initial>org.apache.activemq.jndi.ActiveMQInitialContextFactory</env.java.naming.factory.initial>
        <env.java.naming.provider.url>tcp://${gateway.hostname}:61616</env.java.naming.provider.url>
      </properties>
    
      <realm-name>demo</realm-name>
    
      <!--
      <authorization-constraint>
        <require-role>AUTHORIZED</require-role>
      </authorization-constraint>
      -->
    
      <cross-site-constraint>
        <allow-origin>*</allow-origin>
      </cross-site-constraint>
    </service>
    

    Note: This step is necessary because the hybrid iOS app in the iOS Simulator connects to the Gateway from the local file system.

  3. Save gateway-config.xml, and then invoke the gateway.start command by navigating to the GATEWAY_HOME/bin directory where you installed the Gateway, and then enter the following to run the gateway.start script: ./gateway.start

Run and Test the Hybrid Mobile App in iOS Simulator

The following steps run the hybrid iOS app and test the connection to the Gateway and Apache ActiveMQ.

  1. In Terminal, ensure that you are in the root of project directory (for example, myApp), and enter the following to see a list of all available Simulator targets: cordova run ios --list. For this walkthrough, we will use iPad-2.
  2. To run the app in the iOS Simulator, enter the following: cordova run ios --target="iPad-2". The iOS Simulator opens and displays the hybrid iOS app. It might take several minutes for the iOS Simulator to launch and display the app. The app icon might appear on the iOS Simulator screen for a while before displaying the GUI of the app. You might need to relaunch the app within the iOS Simulator.

    Note: You can also use Xcode to launch the app in the iOS Simulator:
    1. In Xcode, open your project, located in myApp/platforms/ios/myApp.xcodeproj.

    2. In the scheme selector, select iPad 2.
    3. Click the name of your project in Xcode and click Run. If you are prompted to confirm developer mode, click OK.

      The iOS Simulator opens and displays the hybrid iOS app.

  3. In the hybrid app in the iOS Simulator, click in the Location field, and enter the jms service URI (ws://localhost:8001/jms), and click Connect.

    The Log messages field reports a successful connection. If the app does not connect, ensure that you have the Gateway and Apache ActiveMQ running.

  4. Play with hybrid app to see the full functionality.
Notes:
  • If you make changes to the hybrid iOS app in Xcode, quit the iOS Simulator, clean the Xcode project, and then build the project again. This ensures that the iOS Simulator is using the most recent version of your project and hybrid iOS app.
  • If you are unable to connect to Apache ActiveMQ via the Gateway, confirm that the cross-site-constraint setting on the jms service is configured to allow all cross-origin site access to the service.
  • By default, the authorization-constraint setting on the jms service is commented out in gateway-config.xml. If you uncomment this setting in order to test authentication on the hybrid iOS app, ensure that the http-challenge-scheme for the demo realm is set to Application Basic. The Basic authentication scheme is intended for web browsers and the hybrid iOS app does not use the Safari browser on iOS. If the Basic authentication scheme is used, the hybrid iOS app will not connect to Apache ActiveMQ via the Gateway.

Test the Hybrid iOS app on an iOS device

The following steps build a hybrid iOS app that you can copy and test on an iOS device (iPhone, iPad, iPod Touch).

Note: To build a hybrid iOS app for use on an iOS device you must be a member of the Apple iOS Developer Program, you must code sign your app using a Mac Submission Certificate, and you must have a Provisioned Device to install your app onto. The following steps assume that you have met these requirements.

  1. In Xcode, click the stop button to stop running your hybrid iOS app in the iOS Simulator.
  2. Quit the iOS Simulator.
  3. In the Xcode, click Project, and then click Edit Scheme.
  4. In the Run scheme, and in the Build Configuration menu, select Release, and click OK.
  5. Click Project, click Build For, and then click Running.
  6. When the build is completed, expand your project folder in the left pane in order to see the Products folder. The completed product is listed in this folder.

    Note: To see the app file, right-click your product and click Show in Finder.

  7. Connect your iOS device to your development computer.
  8. In the toolbar, change iPad 2 to iOS Device.

    Note: In order to connect the hybrid iOS app on the iOS device to the Gateway and Apache ActiveMQ, the iOS device must be able to resolve the host name of computer running the Gateway on the network. This is the host name that you will use in the Location field of the hybrid iOS app. For example, ws://host_name:8001/jms. You can also configure the Gateway to use the IP Address of the computer running the Gateway (for example, ws://192.168.4.86:8001/jms). Using the IP Address is often the easiest method during testing.

  9. Click Run to run the hybrid iOS app on your iOS device, enter the jms service URI in the Location field, and click Connect.

    The Log messages field reports a successful connection.

Troubleshooting

For errors that occur when trying to use your hybrid iOS app on an iOS device, see iOS Development: Troubleshooting in the iOS Developer Library and the stackoverflow forum.

Summary

As you have seen in this walkthrough, building a hybrid iOS app using the JavaScript JMS demo is easy to accomplish. Building your own custom JavaScript JMS hybrid iOS app simply requires that you substitute your web app in the www folder in the Xcode project and then link the Kaazing WebSocket Gateway WebSocket and JavaScript JMS libraries in the index.html file correctly. For information about building a hybrid Android app, see the documentation.

Notes

Clients built using Kaazing WebSocket Gateway 3.x libraries will work against Kaazing WebSocket Gateway 4.x. If you wish to upgrade your 3.x client to the 4.x libraries, please note that the 3.x clients used a single JMS library and 4.x clients include and use separate WebSocket and JMS libraries. Update your client library file and code references to include both the WebSocket and JMS libraries, as described in the 4.x documentation. For more migration information, see Migrate JavaScript Applications to Kaazing WebSocket Gateway 4.x.

TOP