Source: vertx/sockjs.js

/*
 * Copyright 2011-2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

if (typeof __vertxload === 'string') {
  throw "Use require() to load Vert.x API modules";
}
var Streams  = require('vertx/streams');
var MultiMap = require('vertx/multi_map').MultiMap;
var helpers  = require('vertx/helpers');
var asyncResultHandler = helpers.adaptAsyncResultHandler;

/**
 * <p>
 * SockJS enables browsers to communicate with the server using a simple
 * WebSocket-like api for sending and receiving messages. Under the hood
 * SockJS chooses to use one of several protocols depending on browser
 * capabilities and what apppears to be working across the network.
 * </p>
 *
 * <p>
 * Available protocols include:
 * </p>
 *
 * <ul>
 *   <li>WebSockets</li>
 *   <li>xhr-polling</li>
 *   <li>xhr-streaming</li>
 *   <li>json-polling</li>
 *   <li>event-source</li>
 *   <li>html-file</li>
 * </ul>
 *
 * <p>
 * This means, it should <i>just work</i> irrespective of what browser is being
 * used, and whether there are nasty things like proxies and load balancers
 * between the client and the server.
 * </p>
 *
 * <p>
 * For more detailed information on SockJS, see their website.
 * </p>
 *
 * @exports vertx/sockjs
 */
var sockJS = {};
var JsonObject   = org.vertx.java.core.json.JsonObject;
var JsonArray    = org.vertx.java.core.json.JsonArray;
var EventBusHook = org.vertx.java.core.sockjs.EventBusBridgeHook;

/**
 * Create a new SockJSServer
 * @param {module:vertx/http.HttpServer} httpServer the HTTP server to use
 * @return {module:vertx/sockjs.SockJSServer} the SockJS server instance
 */
sockJS.createSockJSServer = function(httpServer) {
  if (typeof httpServer._to_java_server !== 'function') {
    throw "Please construct a vertx.SockJSServer with an instance of vert.HttpServer";
  }
  return new sockJS.SockJSServer(httpServer);
};

/**
 * <p>
 * This is an implementation of the server side part of 
 * <a href="https://github.com/sockjs">SockJS</a>.
 * </p>
 * <p>
 * You can register multiple applications with the same SockJSServer, each
 * using different path prefixes, each application will have its own handler,
 * and configuration.
 * </p>
 * <p>
 *    Configuration options and their defaults are:
 *    <pre>
 *      - session_timeout: 5000ms
 *      - insert_JSESSIONID: true
 *      - heartbeat_period: 25000ms
 *      - max_bytes_streaming: 131072 (128*1024)
 *      - prefix: "/"
 *      - library_url: "http://cdn.sockjs.org/sockjs-0.3.4.min.js"
 *      - disabled_transports: []
 *    </pre>
 *  </p>
 *
 *
 * @constructor
 */
sockJS.SockJSServer = function(httpServer) {
  var jserver = __jvertx.createSockJSServer(httpServer._to_java_server());
  var hooks   = {};

  /**
   *  Specify a function to call on the given event. All functions take a
   *  SockJSSocket as the first (and perhaps only) parameter. The events are:
   *
   *  <ul>
   *    <li>socket-created - Called when a new socket is created. Use this to
   *        do things like check the origin header on the socket before accepting it.
   *        The function will be provided with a {module:vertx/sockjs.SockJSSocket}.
   *        Returning <code>true</code> will allow the socket to be accepted,
   *        <code>false</code> will cause it to be rejected.
   *    </li>
   *    <li>pre-register - Called when a socket registers a handler on the event bus.
   *        The function will be provided with a {module:vertx/sockjs.SockJSSocket}
   *        and the address as a {string}. Return true to let the registration occur,
   *        or false otherwise.
   *    </li>
   *    <li>post-register - Called after a socket registers a handler on the event bus.
   *        do things like check the origin header on the socket before accepting it.
   *        The function will be provided with a {module:vertx/sockjs.SockJSSocket}
   *        and the address as a {string}. 
   *    </li>
   *    <li>send-or-pub - Called when the client is sending or publishing on the socket.
   *        The function will be provided with a {module:vertx/sockjs.SockJSSocket};
   *        a {boolean} that, if true means the client is sending on the socket, or
   *        if false the client is publishing on the socket; a {JSON} object that is
   *        the body of the message; and finally the address as a {string}. Return
   *        <code>true</code> to allow the message to be published/sent, false otherwise.
   *    </li>
   *    <li>socket-closed - Called when the client socket has closed.
   *        The function will be provided with the {module:vertx/sockjs.SockJSSocket}.
   *    </li>
   *    <li>authorize - Called on client authorization.</li>
   *  </ul>
   * @param {string} evt The name of the event
   * @param {function} func The function to call when the event occurs
   */
  this.on = function(evt, func) {
    hooks[evt] = func;
  };

  /**
   * Install a SockJS application.
   * @param {JSON} config The application configuration
   * @param {SockJSHandler} handler The handler that will be called when SockJS sockets are created
   * @return {module:vertx/sockjs.SockJSServer} this
   */
  this.installApp = function(config, handler) {
    jserver.installApp(new JsonObject(JSON.stringify(config)), sockJSHandler(handler));
  };

  /**
   * Install an app which bridges the SockJS server to the event bus
   * @param {JSON} config The application configuration
   * @param {Array} inboundPermitted A list of JSON objects which define
   *                permitted matches for inbound (client->server) traffic 
   * @param {Array} outboundPermitted A list of JSON objects which define 
   *                permitted matches for outbound (server->client) traffic
   * @param {JSON} bridgeConfig A JSON object containing the configuration for
   *   the event bus bridge. Configuration options and their defaults are:
   *    <pre>
   *      auth_address: "vertx.basicauthmanager.authorise"
   *      auth_timeout: 300000ms
   *      ping_interval: 10000ms
   *      max_address_length: 200
   *      max_handlers_per_socket: 1000
   *    </pre>
   */
  this.bridge = function(config, inboundPermitted, outboundPermitted, bridgeConfig) {
    var jInboundPermitted = convertPermitted(inboundPermitted);
    var jOutboundPermitted = convertPermitted(outboundPermitted);

    jserver.setHook( new EventBusHook({
      handleSendOrPub    : lookup('send-or-pub',    true),
      handleSocketCreated: lookup('socket-created', true),
      handlePreRegister  : lookup('pre-register',   true),
      handleUnRegister   : lookup('unregister',     true),
      handleAuthorise    : lookup('authorize',      true),
      handlePostRegister : lookup('post-register'),
      handleSocketClosed : lookup('socket-closed')
    }));
    if (bridgeConfig) {
      jserver.bridge(new JsonObject(JSON.stringify(config)),
          jInboundPermitted, jOutboundPermitted, bridgeConfig);
    } else {
      jserver.bridge(new JsonObject(JSON.stringify(config)),
          jInboundPermitted, jOutboundPermitted);
    }
  };

  function convertPermitted(permitted) {
    var json_arr = new JsonArray();
    for (var i = 0; i < permitted.length; i++) {
      var match = permitted[i];
      var json_str = JSON.stringify(match);
      var jJson = new JsonObject(json_str);
      json_arr.add(jJson);
    }
    return json_arr;
  }

  function lookup(evt, defaultReturn) {
    return function( /* arguments */ ) {
      if (hooks[evt]) {
        var args = Array.prototype.slice.call(arguments);
        if (evt === 'authorize') {
          // authorize events have an async result handler as
          // their final argument. Convert it accordingly.
          args[args.length-1] = asyncResultHandler(args.slice(-1)[0]);
        } else {
          // all other events have a SockJSSocket as their first argument
          args[0] = new sockJS.SockJSSocket(args[0]);
        }
        return hooks[evt].call(hooks[evt], args);
      }
      return defaultReturn;
    };
  }

  function sockJSHandler(func) {
    return function(sock) {
      func.call(func, new sockJS.SockJSSocket(sock));
    };
  }
};

/**
 * A <code>SockJSHandler</code> is a {@linkcode Handler} that is called when
 * new SockJSSockets are created. It takes a {module:vertx/sockjs.SockJSSocket}
 * as its only parameter.
 *
 * @typedef {function} SockJSHandler
 * @param {module:vertx/sockjs.SockJSSocket} sockJSSocket The socket object
 */

/**
 * <p>Represents a SockJS socket.  You interact with SockJS clients through
 * instances of a SockJS socket.
 * The API is very similar to {@linkcode module:vertx/http.WebSocket}.</p>
 * <p>Instances of this class are created and provided to a {@linkcode SockJSHandler}.</p>
 * <p>It implements both {@linkcode ReadStream} and {@linkcode WriteStream} so
 * it can be used with {@linkcode module:vertx/Pump~Pump|Pump} to pump data
 * with flow control.</p>
 *
 * @constructor
 *
 * @param {org.vertx.java.core.sockjs.SockJSSocket} delegate The java SockJSSocket object
 * @see SockJSHandler
 * @augments module:vertx/streams~ReadStream
 * @augments module:vertx/streams~WriteStream
 */
sockJS.SockJSSocket = function(delegate) {
  /**
   * <p>
   * When a {@code SockJSSocket} is created it automatically registers an event
   * handler with the event bus, the ID of that handler is given by 
   * {@linkcode writeHandlerID}.
   * </p>
   * <p>
   * Given this ID, a different event loop can send a buffer to that event
   * handler using the event bus and that buffer will be received by this
   * instance in its own event loop and written to the underlying socket. This
   * allows you to write data to other sockets which are owned by different
   * event loops.
   * </p>
   * @return {string} the ID
   */
  this.writeHandlerID = function() {
    return delegate.writeHandlerID();
  };

  /**
   * Close the socket
   */
  this.close = function() {
    delegate.close();
  };

  /**
   * Get the local address for this socket
   * @return {} The address of the local socket
   */
  this.localAddress = function() {
    return helpers.convertInetSocketAddress(delegate.getLocalAddress());
  };

  /**
   * @return {module:vertx/multi_map~MultiMap} The headers map
   */
  this.headers = function() {
    if (_headers === null) {
      _headers = new MultiMap(delegate.headers());
    }
    return _headers;
  };

  /**
   * Return the URI corresponding to the last request for this socket or the
   * websocket handshake
   * @return {string} The URI string
   */
  this.uri = function() {
    return delegate.uri();
  };

  var _headers = null;
  Streams.WriteStream.call(this, delegate);
  Streams.ReadStream.call(this, delegate);
};


module.exports = sockJS;