# Extending the WebSocket-API

## When You Create a WebSocket Connection

Consider the following Javascript, that creates a web socket connection:

`var ws = new WebSocket('ws://localhost:8888/api/ws/VariableServer');`

In the URL: `ws://localhost:8888/api/ws/VariableServer`

- `ws://` specifies the protocol (web-socket in this case.)
- `localhost` specifies the domain,
- `:8888` specifies the port, and
- `/api/ws/VariableServer` specifies the path.

In the Trick web server, the path associated with a websocket must begin with `/api/ws/`. The remaining part of the path, i.e., `VariableServer` is the key that specifies the sub-protocol, prescribing what messages will be passed between client and server, and what those messages mean.

When a web-socket connection is established, the key will determine what type (sub-class) of `WebSocketSession` object to create, to manage the connection.

## WebSocketSession

A `WebSocketSession` is a pure virtual base class meant to represent the state of one of potentially many websocket connections. It provides methods to:

1. Synchronously marshall Trick simulation data for out-going messages
2. Send messages to the websocket client, and
3. Receive and process messages from the websocket client.

To implement a new websocket sub-protocol, one needs to derive a new class from this base class, and implement the required methods. `WebSocketSession.hh` can be found in `${TRICK_HOME}/include/trick/`.

### WebSocketSession.hh

    /*************************************************************************
    PURPOSE: (Represent Websocket connection.)
    **************************************************************************/
    #ifndef WEB_SOCKET_SESSION_HH
    #define WEB_SOCKET_SESSION_HH

    #include <string>
    #ifndef SWIG
    #include "mongoose/mongoose.h"
    #endif

    class WebSocketSession {
        public:
            WebSocketSession(struct mg_connection *nc):connection(nc){};
            virtual ~WebSocketSession() {};
            virtual void marshallData()=0;
            virtual void sendMessage()=0;
            virtual int  handleMessage(std::string)=0;

            struct mg_connection* connection;
    };
    #endif

### Adding Your New WebSocketSession Type to the WebServer

To install your new websocket protocol, you'll need to create a function that creates an instance of your new WebSocketSession type. Then you'll need to call `HTTP_Server::installWebSocketSessionMaker` to install the function, with a label.

The function you'll create will take `struct mg_connection *` as an argument and return `WebSocketSession*`.

## Example

Let's create a new web socket protocol that sends the time in GMT or local time.

First we'll derive a new type called `TimeSession` from `WebSocketSession`.

### TimeSession.hh

    /*************************************************************************
    PURPOSE: (Represent the state of a variable server websocket connection.)
    **************************************************************************/
    #ifndef TIMESESSION_HH
    #define TIMESESSION_HH
    #include <vector>
    #include <string>
    #include "time.h"
    #include "trick/WebSocketSession.hh"

    class TimeSession : public WebSocketSession {
        public:
            enum Zone { GMT, LOCAL};
            TimeSession(struct mg_connection *nc);
            ~TimeSession();
            void marshallData();
            void sendMessage();
            int  handleMessage(std::string);
        private:
            time_t now;
            Zone zone;
    };

    WebSocketSession* makeTimeSession( struct mg_connection *nc );
    #endif

Below is our implementation. Notice the function `makeTimeSession` at the bottom.

### TimeSession.cpp

    #include <stdio.h>
    #include <time.h>
    #include <iostream>
    #include "TimeSession.hh"

    // CONSTRUCTOR
    TimeSession::TimeSession( struct mg_connection *nc ) : WebSocketSession(nc) {
        time(&now);
    }

    // DESTRUCTOR
    TimeSession::~TimeSession() {}

    void TimeSession::marshallData() {
        time(&now);
    }

    void TimeSession::sendMessage() {

        char message[1024];
        struct tm *theTime;
        if (zone == TimeSession::LOCAL) {
            theTime = localtime(&now);
        } else {
            theTime = gmtime(&now);
        }
        int hours   = theTime->tm_hour;
        int minutes = theTime->tm_min;
        int seconds = theTime->tm_sec;
        int day     = theTime->tm_mday;
        int month   = theTime->tm_mon + 1;
        int year    = theTime->tm_year + 1900;

        sprintf(message, "Time: %02d:%02d:%02d Date: %02d/%02d/%d\n", hours, minutes, seconds, month, day, year);
        mg_send_websocket_frame(connection, WEBSOCKET_OP_TEXT, message, strlen(message));
    }

    int TimeSession::handleMessage(std::string client_msg) {

       if (client_msg.compare("GMT") == 0) {
           zone = TimeSession::GMT;
       } else if (client_msg.compare("LOCAL") == 0) {
           zone = TimeSession::LOCAL;
       } else {
          std::cerr << "ERROR: Unknown command \"" << client_msg << "\"." << std::endl;
       }
       return 0;
    }

    // WebSocketSessionMaker function for a TimeSession.
    WebSocketSession* makeTimeSession( struct mg_connection *nc ) {
        std::cerr << "DEBUG: Creating new TimeSession." << std::endl;
        return new TimeSession(nc);
    }

We put `TimeSession.cpp` and `TimeSession.cpp` into a models directory called `httpMethods/`.

### S_define Modifications

1. Specify the dependency on the `httpMethods/TimeSession.cpp` compilation unit.
2. We should already be including the WebServer sim object, otherwise we don't even have a webserver.
3. We need to include our new header file: `##include "httpMethods/TimeSession.hh"`
4. Finally, install our WebSocketSession type: `web.server.installWebSocketSessionMaker("Time", &makeTimeSession);` The label we use for our protocol here is "Time", but it can be whatever name you choose.

    /***********************TRICK HEADER*************************
    PURPOSE:
        (Cannon Numeric)
    LIBRARY DEPENDENCIES:
        (
         (cannon/gravity/src/cannon_init.c)
         (cannon/gravity/src/cannon_numeric.c)
         (httpMethods/TimeSession.cpp)                                    // <--(1)
        )
    *************************************************************/

    #include "sim_objects/default_trick_sys.sm"
    #include "sim_objects/WebServer.sm"                                   // <--(2)
    ##include "cannon/gravity/include/cannon_numeric.h"
    ##include "httpMethods/TimeSession.hh"                                // <--(3)

    class CannonSimObject : public Trick::SimObject {

        public:
            CANNON      cannon ;
            int foo;
            CannonSimObject() {
                ("default_data") cannon_default_data( &cannon ) ;
                ("initialization") cannon_init( &cannon ) ;
                ("derivative") cannon_deriv( &cannon ) ;
                ("integration") trick_ret = cannon_integ( &cannon ) ;
                ("dynamic_event") cannon_impact( &cannon) ;
            }
    } ;
    CannonSimObject dyn ;

    IntegLoop dyn_integloop (0.10) dyn;

    void create_connections() {
        dyn_integloop.getIntegrator(Runge_Kutta_4, 5);
        web.server.installWebSocketSessionMaker("Time", &makeTimeSession);  // <--(4)
    }

## Testing The New WebSocket Interface

To test your new web socket interface, put the following `time.html` file in `$YOUR_SIM_DIRECTORY/www/apps`. Then request `http://localhost:8888/apps/time.html` from your browser. You should see the time messages from your sim.

### time.html

    <!DOCTYPE html>
    <html>
      <head>
        <title>WS Example</title>
      </head>
      <body>
          <div id="output"></div>
          <script type="text/javascript">

              function log(s) {
                  var p = document.createElement("p");
                  p.style.wordWrap = "break-word";
                  p.textContent = s;
                  output.appendChild(p);
              }

              var ws = new WebSocket('ws://localhost:8888/api/ws/Time');

              // WebSocket Event Handlers
              ws.onopen = function(e) {
                  ws.send("GMT");
              };
              ws.onmessage = function(e) {
                 log(e.data);
              };
              ws.onerror = function(e) {
                 console.log("WebSocket Error: " , e);
                 handleErrors(e);
              };
              ws.onclose = function(e) {
                 console.log("Connection closed", e);
              };

          </script>
      </body>
    </html>