summaryrefslogtreecommitdiffstats
path: root/webclients/novnc/include/websock.js
diff options
context:
space:
mode:
Diffstat (limited to 'webclients/novnc/include/websock.js')
-rw-r--r--webclients/novnc/include/websock.js622
1 files changed, 323 insertions, 299 deletions
diff --git a/webclients/novnc/include/websock.js b/webclients/novnc/include/websock.js
index 20d51d6..1b89a91 100644
--- a/webclients/novnc/include/websock.js
+++ b/webclients/novnc/include/websock.js
@@ -1,7 +1,7 @@
/*
* Websock: high-performance binary WebSockets
* Copyright (C) 2012 Joel Martin
- * Licensed under LGPL-3 (see LICENSE.txt)
+ * Licensed under MPL 2.0 (see LICENSE.txt)
*
* Websock is similar to the standard WebSocket object but Websock
* enables communication with raw TCP sockets (i.e. the binary stream)
@@ -14,7 +14,7 @@
* read binary data off of the receive queue.
*/
-/*jslint browser: true, bitwise: false, plusplus: false */
+/*jslint browser: true, bitwise: true */
/*global Util, Base64 */
@@ -35,326 +35,350 @@ if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
Websock_native = false;
(function () {
- function get_INCLUDE_URI() {
- return (typeof INCLUDE_URI !== "undefined") ?
- INCLUDE_URI : "include/";
- }
-
- var start = "<script src='" + get_INCLUDE_URI(),
- end = "'><\/script>", extra = "";
-
- window.WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
+ window.WEB_SOCKET_SWF_LOCATION = Util.get_include_uri() +
"web-socket-js/WebSocketMain.swf";
if (Util.Engine.trident) {
Util.Debug("Forcing uncached load of WebSocketMain.swf");
window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
}
- extra += start + "web-socket-js/swfobject.js" + end;
- extra += start + "web-socket-js/web_socket.js" + end;
- document.write(extra);
- }());
+ Util.load_scripts(["web-socket-js/swfobject.js",
+ "web-socket-js/web_socket.js"]);
+ })();
}
function Websock() {
-"use strict";
-
-var api = {}, // Public API
- websocket = null, // WebSocket object
- rQ = [], // Receive queue
- rQi = 0, // Receive queue index
- rQmax = 10000, // Max receive queue size before compacting
- sQ = [], // Send queue
-
- eventHandlers = {
- 'message' : function() {},
- 'open' : function() {},
- 'close' : function() {},
- 'error' : function() {}
- },
-
- test_mode = false;
-
-
-//
-// Queue public functions
-//
-
-function get_sQ() {
- return sQ;
-}
-
-function get_rQ() {
- return rQ;
-}
-function get_rQi() {
- return rQi;
-}
-function set_rQi(val) {
- rQi = val;
-}
-
-function rQlen() {
- return rQ.length - rQi;
-}
-
-function rQpeek8() {
- return (rQ[rQi] );
-}
-function rQshift8() {
- return (rQ[rQi++] );
-}
-function rQunshift8(num) {
- if (rQi === 0) {
- rQ.unshift(num);
- } else {
- rQi -= 1;
- rQ[rQi] = num;
- }
-
-}
-function rQshift16() {
- return (rQ[rQi++] << 8) +
- (rQ[rQi++] );
-}
-function rQshift32() {
- return (rQ[rQi++] << 24) +
- (rQ[rQi++] << 16) +
- (rQ[rQi++] << 8) +
- (rQ[rQi++] );
+ "use strict";
+
+ this._websocket = null; // WebSocket object
+ this._rQ = []; // Receive queue
+ this._rQi = 0; // Receive queue index
+ this._rQmax = 10000; // Max receive queue size before compacting
+ this._sQ = []; // Send queue
+
+ this._mode = 'base64'; // Current WebSocket mode: 'binary', 'base64'
+ this.maxBufferedAmount = 200;
+
+ this._eventHandlers = {
+ 'message': function () {},
+ 'open': function () {},
+ 'close': function () {},
+ 'error': function () {}
+ };
}
-function rQshiftStr(len) {
- if (typeof(len) === 'undefined') { len = rQlen(); }
- var arr = rQ.slice(rQi, rQi + len);
- rQi += len;
- return arr.map(function (num) {
- return String.fromCharCode(num); } ).join('');
-}
-function rQshiftBytes(len) {
- if (typeof(len) === 'undefined') { len = rQlen(); }
- rQi += len;
- return rQ.slice(rQi-len, rQi);
-}
+(function () {
+ "use strict";
+ Websock.prototype = {
+ // Getters and Setters
+ get_sQ: function () {
+ return this._sQ;
+ },
+
+ get_rQ: function () {
+ return this._rQ;
+ },
+
+ get_rQi: function () {
+ return this._rQi;
+ },
+
+ set_rQi: function (val) {
+ this._rQi = val;
+ },
+
+ // Receive Queue
+ rQlen: function () {
+ return this._rQ.length - this._rQi;
+ },
+
+ rQpeek8: function () {
+ return this._rQ[this._rQi];
+ },
+
+ rQshift8: function () {
+ return this._rQ[this._rQi++];
+ },
+
+ rQskip8: function () {
+ this._rQi++;
+ },
+
+ rQskipBytes: function (num) {
+ this._rQi += num;
+ },
+
+ rQunshift8: function (num) {
+ if (this._rQi === 0) {
+ this._rQ.unshift(num);
+ } else {
+ this._rQi--;
+ this._rQ[this._rQi] = num;
+ }
+ },
+
+ rQshift16: function () {
+ return (this._rQ[this._rQi++] << 8) +
+ this._rQ[this._rQi++];
+ },
+
+ rQshift32: function () {
+ return (this._rQ[this._rQi++] << 24) +
+ (this._rQ[this._rQi++] << 16) +
+ (this._rQ[this._rQi++] << 8) +
+ this._rQ[this._rQi++];
+ },
+
+ rQshiftStr: function (len) {
+ if (typeof(len) === 'undefined') { len = this.rQlen(); }
+ var arr = this._rQ.slice(this._rQi, this._rQi + len);
+ this._rQi += len;
+ return String.fromCharCode.apply(null, arr);
+ },
+
+ rQshiftBytes: function (len) {
+ if (typeof(len) === 'undefined') { len = this.rQlen(); }
+ this._rQi += len;
+ return this._rQ.slice(this._rQi - len, this._rQi);
+ },
+
+ rQslice: function (start, end) {
+ if (end) {
+ return this._rQ.slice(this._rQi + start, this._rQi + end);
+ } else {
+ return this._rQ.slice(this._rQi + start);
+ }
+ },
+
+ // Check to see if we must wait for 'num' bytes (default to FBU.bytes)
+ // to be available in the receive queue. Return true if we need to
+ // wait (and possibly print a debug message), otherwise false.
+ rQwait: function (msg, num, goback) {
+ var rQlen = this._rQ.length - this._rQi; // Skip rQlen() function call
+ if (rQlen < num) {
+ if (goback) {
+ if (this._rQi < goback) {
+ throw new Error("rQwait cannot backup " + goback + " bytes");
+ }
+ this._rQi -= goback;
+ }
+ return true; // true means need more data
+ }
+ return false;
+ },
-function rQslice(start, end) {
- if (end) {
- return rQ.slice(rQi + start, rQi + end);
- } else {
- return rQ.slice(rQi + start);
- }
-}
+ // Send Queue
-// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
-// to be available in the receive queue. Return true if we need to
-// wait (and possibly print a debug message), otherwise false.
-function rQwait(msg, num, goback) {
- var rQlen = rQ.length - rQi; // Skip rQlen() function call
- if (rQlen < num) {
- if (goback) {
- if (rQi < goback) {
- throw("rQwait cannot backup " + goback + " bytes");
+ flush: function () {
+ if (this._websocket.bufferedAmount !== 0) {
+ Util.Debug("bufferedAmount: " + this._websocket.bufferedAmount);
}
- rQi -= goback;
- }
- //Util.Debug(" waiting for " + (num-rQlen) +
- // " " + msg + " byte(s)");
- return true; // true means need more data
- }
- return false;
-}
-//
-// Private utility routines
-//
+ if (this._websocket.bufferedAmount < this.maxBufferedAmount) {
+ if (this._sQ.length > 0) {
+ this._websocket.send(this._encode_message());
+ this._sQ = [];
+ }
+
+ return true;
+ } else {
+ Util.Info("Delaying send, bufferedAmount: " +
+ this._websocket.bufferedAmount);
+ return false;
+ }
+ },
+
+ send: function (arr) {
+ this._sQ = this._sQ.concat(arr);
+ return this.flush();
+ },
+
+ send_string: function (str) {
+ this.send(str.split('').map(function (chr) {
+ return chr.charCodeAt(0);
+ }));
+ },
+
+ // Event Handlers
+ on: function (evt, handler) {
+ this._eventHandlers[evt] = handler;
+ },
+
+ init: function (protocols, ws_schema) {
+ this._rQ = [];
+ this._rQi = 0;
+ this._sQ = [];
+ this._websocket = null;
+
+ // Check for full typed array support
+ var bt = false;
+ if (('Uint8Array' in window) &&
+ ('set' in Uint8Array.prototype)) {
+ bt = true;
+ }
-function encode_message() {
- /* base64 encode */
- return Base64.encode(sQ);
-}
+ // Check for full binary type support in WebSockets
+ // Inspired by:
+ // https://github.com/Modernizr/Modernizr/issues/370
+ // https://github.com/Modernizr/Modernizr/blob/master/feature-detects/websockets/binary.js
+ var wsbt = false;
+ try {
+ if (bt && ('binaryType' in WebSocket.prototype ||
+ !!(new WebSocket(ws_schema + '://.').binaryType))) {
+ Util.Info("Detected binaryType support in WebSockets");
+ wsbt = true;
+ }
+ } catch (exc) {
+ // Just ignore failed test localhost connection
+ }
-function decode_message(data) {
- //Util.Debug(">> decode_message: " + data);
- /* base64 decode */
- rQ = rQ.concat(Base64.decode(data, 0));
- //Util.Debug(">> decode_message, rQ: " + rQ);
-}
+ // Default protocols if not specified
+ if (typeof(protocols) === "undefined") {
+ if (wsbt) {
+ protocols = ['binary', 'base64'];
+ } else {
+ protocols = 'base64';
+ }
+ }
+ if (!wsbt) {
+ if (protocols === 'binary') {
+ throw new Error('WebSocket binary sub-protocol requested but not supported');
+ }
+
+ if (typeof(protocols) === 'object') {
+ var new_protocols = [];
+
+ for (var i = 0; i < protocols.length; i++) {
+ if (protocols[i] === 'binary') {
+ Util.Error('Skipping unsupported WebSocket binary sub-protocol');
+ } else {
+ new_protocols.push(protocols[i]);
+ }
+ }
+
+ if (new_protocols.length > 0) {
+ protocols = new_protocols;
+ } else {
+ throw new Error("Only WebSocket binary sub-protocol was requested and is not supported.");
+ }
+ }
+ }
-//
-// Public Send functions
-//
-
-function flush() {
- if (websocket.bufferedAmount !== 0) {
- Util.Debug("bufferedAmount: " + websocket.bufferedAmount);
- }
- if (websocket.bufferedAmount < api.maxBufferedAmount) {
- //Util.Debug("arr: " + arr);
- //Util.Debug("sQ: " + sQ);
- if (sQ.length > 0) {
- websocket.send(encode_message(sQ));
- sQ = [];
- }
- return true;
- } else {
- Util.Info("Delaying send, bufferedAmount: " +
- websocket.bufferedAmount);
- return false;
- }
-}
+ return protocols;
+ },
-// overridable for testing
-function send(arr) {
- //Util.Debug(">> send_array: " + arr);
- sQ = sQ.concat(arr);
- return flush();
-}
+ open: function (uri, protocols) {
+ var ws_schema = uri.match(/^([a-z]+):\/\//)[1];
+ protocols = this.init(protocols, ws_schema);
-function send_string(str) {
- //Util.Debug(">> send_string: " + str);
- api.send(str.split('').map(
- function (chr) { return chr.charCodeAt(0); } ) );
-}
+ this._websocket = new WebSocket(uri, protocols);
-//
-// Other public functions
-
-function recv_message(e) {
- //Util.Debug(">> recv_message: " + e.data.length);
-
- try {
- decode_message(e.data);
- if (rQlen() > 0) {
- eventHandlers.message();
- // Compact the receive queue
- if (rQ.length > rQmax) {
- //Util.Debug("Compacting receive queue");
- rQ = rQ.slice(rQi);
- rQi = 0;
+ if (protocols.indexOf('binary') >= 0) {
+ this._websocket.binaryType = 'arraybuffer';
}
- } else {
- Util.Debug("Ignoring empty message");
- }
- } catch (exc) {
- if (typeof exc.stack !== 'undefined') {
- Util.Warn("recv_message, caught exception: " + exc.stack);
- } else if (typeof exc.description !== 'undefined') {
- Util.Warn("recv_message, caught exception: " + exc.description);
- } else {
- Util.Warn("recv_message, caught exception:" + exc);
- }
- if (typeof exc.name !== 'undefined') {
- eventHandlers.error(exc.name + ": " + exc.message);
- } else {
- eventHandlers.error(exc);
- }
- }
- //Util.Debug("<< recv_message");
-}
-
-
-// Set event handlers
-function on(evt, handler) {
- eventHandlers[evt] = handler;
-}
-
-function init() {
- rQ = [];
- rQi = 0;
- sQ = [];
- websocket = null;
-}
-function open(uri) {
- init();
-
- if (test_mode) {
- websocket = {};
- } else {
- websocket = new WebSocket(uri, 'base64');
- // TODO: future native binary support
- //websocket = new WebSocket(uri, ['binary', 'base64']);
- }
-
- websocket.onmessage = recv_message;
- websocket.onopen = function() {
- Util.Debug(">> WebSock.onopen");
- if (websocket.protocol) {
- Util.Info("Server chose sub-protocol: " + websocket.protocol);
- } else {
- Util.Error("Server select no sub-protocol!: " + websocket.protocol);
+ this._websocket.onmessage = this._recv_message.bind(this);
+ this._websocket.onopen = (function () {
+ Util.Debug('>> WebSock.onopen');
+ if (this._websocket.protocol) {
+ this._mode = this._websocket.protocol;
+ Util.Info("Server choose sub-protocol: " + this._websocket.protocol);
+ } else {
+ this._mode = 'base64';
+ Util.Error('Server select no sub-protocol!: ' + this._websocket.protocol);
+ }
+ this._eventHandlers.open();
+ Util.Debug("<< WebSock.onopen");
+ }).bind(this);
+ this._websocket.onclose = (function (e) {
+ Util.Debug(">> WebSock.onclose");
+ this._eventHandlers.close(e);
+ Util.Debug("<< WebSock.onclose");
+ }).bind(this);
+ this._websocket.onerror = (function (e) {
+ Util.Debug(">> WebSock.onerror: " + e);
+ this._eventHandlers.error(e);
+ Util.Debug("<< WebSock.onerror: " + e);
+ }).bind(this);
+ },
+
+ close: function () {
+ if (this._websocket) {
+ if ((this._websocket.readyState === WebSocket.OPEN) ||
+ (this._websocket.readyState === WebSocket.CONNECTING)) {
+ Util.Info("Closing WebSocket connection");
+ this._websocket.close();
+ }
+
+ this._websocket.onmessage = function (e) { return; };
+ }
+ },
+
+ // private methods
+ _encode_message: function () {
+ if (this._mode === 'binary') {
+ // Put in a binary arraybuffer
+ return (new Uint8Array(this._sQ)).buffer;
+ } else {
+ // base64 encode
+ return Base64.encode(this._sQ);
+ }
+ },
+
+ _decode_message: function (data) {
+ if (this._mode === 'binary') {
+ // push arraybuffer values onto the end
+ var u8 = new Uint8Array(data);
+ for (var i = 0; i < u8.length; i++) {
+ this._rQ.push(u8[i]);
+ }
+ } else {
+ // base64 decode and concat to end
+ this._rQ = this._rQ.concat(Base64.decode(data, 0));
+ }
+ },
+
+ _recv_message: function (e) {
+ try {
+ this._decode_message(e.data);
+ if (this.rQlen() > 0) {
+ this._eventHandlers.message();
+ // Compact the receive queue
+ if (this._rQ.length > this._rQmax) {
+ this._rQ = this._rQ.slice(this._rQi);
+ this._rQi = 0;
+ }
+ } else {
+ Util.Debug("Ignoring empty message");
+ }
+ } catch (exc) {
+ var exception_str = "";
+ if (exc.name) {
+ exception_str += "\n name: " + exc.name + "\n";
+ exception_str += " message: " + exc.message + "\n";
+ }
+
+ if (typeof exc.description !== 'undefined') {
+ exception_str += " description: " + exc.description + "\n";
+ }
+
+ if (typeof exc.stack !== 'undefined') {
+ exception_str += exc.stack;
+ }
+
+ if (exception_str.length > 0) {
+ Util.Error("recv_message, caught exception: " + exception_str);
+ } else {
+ Util.Error("recv_message, caught exception: " + exc);
+ }
+
+ if (typeof exc.name !== 'undefined') {
+ this._eventHandlers.error(exc.name + ": " + exc.message);
+ } else {
+ this._eventHandlers.error(exc);
+ }
+ }
}
- eventHandlers.open();
- Util.Debug("<< WebSock.onopen");
- };
- websocket.onclose = function(e) {
- Util.Debug(">> WebSock.onclose");
- eventHandlers.close(e);
- Util.Debug("<< WebSock.onclose");
};
- websocket.onerror = function(e) {
- Util.Debug(">> WebSock.onerror: " + e);
- eventHandlers.error(e);
- Util.Debug("<< WebSock.onerror");
- };
-}
-
-function close() {
- if (websocket) {
- if ((websocket.readyState === WebSocket.OPEN) ||
- (websocket.readyState === WebSocket.CONNECTING)) {
- Util.Info("Closing WebSocket connection");
- websocket.close();
- }
- websocket.onmessage = function (e) { return; };
- }
-}
-
-// Override internal functions for testing
-// Takes a send function, returns reference to recv function
-function testMode(override_send) {
- test_mode = true;
- api.send = override_send;
- api.close = function () {};
- return recv_message;
-}
-
-function constructor() {
- // Configuration settings
- api.maxBufferedAmount = 200;
-
- // Direct access to send and receive queues
- api.get_sQ = get_sQ;
- api.get_rQ = get_rQ;
- api.get_rQi = get_rQi;
- api.set_rQi = set_rQi;
-
- // Routines to read from the receive queue
- api.rQlen = rQlen;
- api.rQpeek8 = rQpeek8;
- api.rQshift8 = rQshift8;
- api.rQunshift8 = rQunshift8;
- api.rQshift16 = rQshift16;
- api.rQshift32 = rQshift32;
- api.rQshiftStr = rQshiftStr;
- api.rQshiftBytes = rQshiftBytes;
- api.rQslice = rQslice;
- api.rQwait = rQwait;
-
- api.flush = flush;
- api.send = send;
- api.send_string = send_string;
-
- api.on = on;
- api.init = init;
- api.open = open;
- api.close = close;
- api.testMode = testMode;
-
- return api;
-}
-
-return constructor();
-
-}
+})();