API Docs for: 2.0.0

public/src/websocket.io.js

// SAGE2 is available for use under the SAGE2 Software License
//
// University of Illinois at Chicago's Electronic Visualization Laboratory (EVL)
// and University of Hawai'i at Manoa's Laboratory for Advanced Visualization and
// Applications (LAVA)
//
// See full text, terms and conditions in the LICENSE.txt included file
//
// Copyright (c) 2014

"use strict";

/**
 * @module client
 * @submodule WebsocketIO
 */

/**
 * Lightweight object around websocket, handles string and binary communication
 *
 * @class WebsocketIO
 * @constructor
 */
function WebsocketIO(url) {
	if (url !== undefined && url !== null) {
		this.url = url;
	} else {
		this.url = (window.location.protocol === "https:" ? "wss" : "ws") + "://" + window.location.host +
					"/" + window.location.pathname.split("/")[1];
	}

	/**
	 * websocket object handling the communication with the server
	 *
	 * @property ws
	 * @type WebSocket
	 */
	this.ws = null;

	/**
	 * list of messages to be handled (name + callback)
	 *
	 * @property messages
	 * @type Object
	 */
	this.messages = {};

	/**
	 * number of aliases created for listeners
	 *
	 * @property aliasCount
	 * @type Integer
	 */
	this.aliasCount = 1;

	/**
	 * list of listeners on other side of connection
	 *
	 * @property remoteListeners
	 * @type Object
	 */
	this.remoteListeners = {"#WSIO#addListener": "0000"};

	/**
	 * list of local listeners on this side of connection
	 *
	 * @property localListeners
	 * @type Object
	 */
	this.localListeners = {"0000": "#WSIO#addListener"};

	/**
	 * bytes sent property
	 * @property bytesWritten
	 * type       {Number}
	 */
	this._bytesWritten = 0;

	/**
	 * getter for the bytesRead property
	 */
	Object.defineProperty(this, "bytesRead", {
		get: function () {
			return this._bytesRead;
		}
	});

	/**
	 * bytes received property
	 * @property bytesWritten
	 * type       {Number}
	 */
	this._bytesRead = 0;

	/**
	 * getter for the bytesWritten property
	 */
	Object.defineProperty(this, "bytesWritten", {
		get: function () {
			return this._bytesWritten;
		}
	});

	/**
	* Open a websocket
	*
	* @method open
	* @param callback {Function} function to be called when the socket is ready
	*/
	this.open = function(callback) {
		var _this = this;

		console.log('WebsocketIO> open', this.url);
		this.ws = new WebSocket(this.url);
		this.ws.binaryType = "arraybuffer";
		this.ws.onopen = callback;

		// Handler when a message arrives
		this.ws.onmessage = function(message) {
			var fName;
			// text message
			if (typeof message.data === "string") {
				// update the bytes read value
				_this._bytesRead += message.data.length;
				// decode the message
				var msg = JSON.parse(message.data);
				fName = _this.localListeners[msg.f];
				if (fName === undefined) {
					console.log('WebsocketIO> No handler for message');
				}

				if (fName === "#WSIO#addListener") {
					_this.remoteListeners[msg.d.listener] = msg.d.alias;
					return;
				}
				_this.messages[fName](msg.d);
			} else {
				// update the bytes read value
				_this._bytesRead += message.data.byteLength;
				// decode the message
				var uInt8 = new Uint8Array(message.data);
				var func  = String.fromCharCode(uInt8[0]) +
							String.fromCharCode(uInt8[1]) +
							String.fromCharCode(uInt8[2]) +
							String.fromCharCode(uInt8[3]);
				fName = _this.localListeners[func];
				var buffer = uInt8.subarray(4, uInt8.length);
				_this.messages[fName](buffer);
			}
		};
		// triggered by unexpected close event
		this.ws.onclose = function(evt) {
			console.log("WebsocketIO> socket closed");
			if ('close' in _this.messages) {
				_this.messages.close(evt);
			}
		};
	};

	/**
	* Set a message handler for a given name
	*
	* @method on
	* @param name {String} name for the handler
	* @param callback {Function} handler to be called for a given name
	*/
	this.on = function(name, callback) {
		var alias = ("0000" + this.aliasCount.toString(16)).substr(-4);
		this.localListeners[alias] = name;
		this.messages[name] = callback;
		this.aliasCount++;
		if (name === "close") {
			return;
		}
		this.emit('#WSIO#addListener', {listener: name, alias: alias});
	};

	/**
	* Send a message with a given name and payload (format> f:name d:payload)
	*
	* @method emit
	* @param name {String} name of the message (i.e. RPC)
	* @param data {Object} data to be sent with the message
	*/
	this.emit = function(name, data, attempts) {
		if (name === null || name === "") {
			console.log("Error: no message name specified");
			return;
		}

		var _this = this;
		var message;
		var alias = this.remoteListeners[name];
		if (alias === undefined) {
			if (attempts === undefined) {
				attempts = 16;
			}
			if (attempts >= 0) {
				setTimeout(function() {
					_this.emit(name, data, attempts - 1);
				}, 4);
			} else {
				console.log("Warning: not sending message, recipient has no listener (" + name + ")");
			}
			return;
		}

		// send binary data as array buffer
		if (data instanceof Uint8Array) {
			// build an array with the name of the function
			var funcName = new Uint8Array(4);
			funcName[0] = alias.charCodeAt(0);
			funcName[1] = alias.charCodeAt(1);
			funcName[2] = alias.charCodeAt(2);
			funcName[3] = alias.charCodeAt(3);
			message = new Uint8Array(4 + data.length);
			// copy the name of the function first
			message.set(funcName, 0);
			// then copy the payload
			message.set(data, 4);
			// send the message using websocket
			this.ws.send(message.buffer);
			// update property
			this._bytesWritten += message.buffer.byteLength;
		} else {
			// send data as JSON string
			message = JSON.stringify({f: alias, d: data});
			this.ws.send(message);
			// update property
			this._bytesWritten += message.length;
		}
	};

	/**
	* Deliberate close function
	*
	* @method emit
	*/
	this.close = function() {
		// disable onclose handler first
		this.ws.onclose = function() {};
		// then close
		this.ws.close();
	};

}