diff options
Diffstat (limited to 'webclients/novnc/include/websock.js')
-rw-r--r-- | webclients/novnc/include/websock.js | 622 |
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(); - -} +})(); |