websock.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. /*
  2. * Websock: high-performance binary WebSockets
  3. * Copyright (C) 2012 Joel Martin
  4. * Licensed under MPL 2.0 (see LICENSE.txt)
  5. *
  6. * Websock is similar to the standard WebSocket object but Websock
  7. * enables communication with raw TCP sockets (i.e. the binary stream)
  8. * via websockify. This is accomplished by base64 encoding the data
  9. * stream between Websock and websockify.
  10. *
  11. * Websock has built-in receive queue buffering; the message event
  12. * does not contain actual data but is simply a notification that
  13. * there is new data available. Several rQ* methods are available to
  14. * read binary data off of the receive queue.
  15. */
  16. /*jslint browser: true, bitwise: true */
  17. /*global Util, Base64 */
  18. // Load Flash WebSocket emulator if needed
  19. // To force WebSocket emulator even when native WebSocket available
  20. //window.WEB_SOCKET_FORCE_FLASH = true;
  21. // To enable WebSocket emulator debug:
  22. //window.WEB_SOCKET_DEBUG=1;
  23. if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
  24. Websock_native = true;
  25. } else if (window.MozWebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
  26. Websock_native = true;
  27. window.WebSocket = window.MozWebSocket;
  28. } else {
  29. /* no builtin WebSocket so load web_socket.js */
  30. Websock_native = false;
  31. (function () {
  32. window.WEB_SOCKET_SWF_LOCATION = Util.get_include_uri() +
  33. "web-socket-js/WebSocketMain.swf";
  34. if (Util.Engine.trident) {
  35. Util.Debug("Forcing uncached load of WebSocketMain.swf");
  36. window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
  37. }
  38. Util.load_scripts(["web-socket-js/swfobject.js",
  39. "web-socket-js/web_socket.js"]);
  40. })();
  41. }
  42. function Websock() {
  43. "use strict";
  44. this._websocket = null; // WebSocket object
  45. this._rQ = []; // Receive queue
  46. this._rQi = 0; // Receive queue index
  47. this._rQmax = 10000; // Max receive queue size before compacting
  48. this._sQ = []; // Send queue
  49. this._mode = 'base64'; // Current WebSocket mode: 'binary', 'base64'
  50. this.maxBufferedAmount = 200;
  51. this._eventHandlers = {
  52. 'message': function () {},
  53. 'open': function () {},
  54. 'close': function () {},
  55. 'error': function () {}
  56. };
  57. }
  58. (function () {
  59. "use strict";
  60. Websock.prototype = {
  61. // Getters and Setters
  62. get_sQ: function () {
  63. return this._sQ;
  64. },
  65. get_rQ: function () {
  66. return this._rQ;
  67. },
  68. get_rQi: function () {
  69. return this._rQi;
  70. },
  71. set_rQi: function (val) {
  72. this._rQi = val;
  73. },
  74. // Receive Queue
  75. rQlen: function () {
  76. return this._rQ.length - this._rQi;
  77. },
  78. rQpeek8: function () {
  79. return this._rQ[this._rQi];
  80. },
  81. rQshift8: function () {
  82. return this._rQ[this._rQi++];
  83. },
  84. rQskip8: function () {
  85. this._rQi++;
  86. },
  87. rQskipBytes: function (num) {
  88. this._rQi += num;
  89. },
  90. rQunshift8: function (num) {
  91. if (this._rQi === 0) {
  92. this._rQ.unshift(num);
  93. } else {
  94. this._rQi--;
  95. this._rQ[this._rQi] = num;
  96. }
  97. },
  98. rQshift16: function () {
  99. return (this._rQ[this._rQi++] << 8) +
  100. this._rQ[this._rQi++];
  101. },
  102. rQshift32: function () {
  103. return (this._rQ[this._rQi++] << 24) +
  104. (this._rQ[this._rQi++] << 16) +
  105. (this._rQ[this._rQi++] << 8) +
  106. this._rQ[this._rQi++];
  107. },
  108. rQshiftStr: function (len) {
  109. if (typeof(len) === 'undefined') { len = this.rQlen(); }
  110. var arr = this._rQ.slice(this._rQi, this._rQi + len);
  111. this._rQi += len;
  112. return String.fromCharCode.apply(null, arr);
  113. },
  114. rQshiftBytes: function (len) {
  115. if (typeof(len) === 'undefined') { len = this.rQlen(); }
  116. this._rQi += len;
  117. return this._rQ.slice(this._rQi - len, this._rQi);
  118. },
  119. rQslice: function (start, end) {
  120. if (end) {
  121. return this._rQ.slice(this._rQi + start, this._rQi + end);
  122. } else {
  123. return this._rQ.slice(this._rQi + start);
  124. }
  125. },
  126. // Check to see if we must wait for 'num' bytes (default to FBU.bytes)
  127. // to be available in the receive queue. Return true if we need to
  128. // wait (and possibly print a debug message), otherwise false.
  129. rQwait: function (msg, num, goback) {
  130. var rQlen = this._rQ.length - this._rQi; // Skip rQlen() function call
  131. if (rQlen < num) {
  132. if (goback) {
  133. if (this._rQi < goback) {
  134. throw new Error("rQwait cannot backup " + goback + " bytes");
  135. }
  136. this._rQi -= goback;
  137. }
  138. return true; // true means need more data
  139. }
  140. return false;
  141. },
  142. // Send Queue
  143. flush: function () {
  144. if (this._websocket.bufferedAmount !== 0) {
  145. Util.Debug("bufferedAmount: " + this._websocket.bufferedAmount);
  146. }
  147. if (this._websocket.bufferedAmount < this.maxBufferedAmount) {
  148. if (this._sQ.length > 0) {
  149. this._websocket.send(this._encode_message());
  150. this._sQ = [];
  151. }
  152. return true;
  153. } else {
  154. Util.Info("Delaying send, bufferedAmount: " +
  155. this._websocket.bufferedAmount);
  156. return false;
  157. }
  158. },
  159. send: function (arr) {
  160. this._sQ = this._sQ.concat(arr);
  161. return this.flush();
  162. },
  163. send_string: function (str) {
  164. this.send(str.split('').map(function (chr) {
  165. return chr.charCodeAt(0);
  166. }));
  167. },
  168. // Event Handlers
  169. on: function (evt, handler) {
  170. this._eventHandlers[evt] = handler;
  171. },
  172. init: function (protocols, ws_schema) {
  173. this._rQ = [];
  174. this._rQi = 0;
  175. this._sQ = [];
  176. this._websocket = null;
  177. // Check for full typed array support
  178. var bt = false;
  179. if (('Uint8Array' in window) &&
  180. ('set' in Uint8Array.prototype)) {
  181. bt = true;
  182. }
  183. // Check for full binary type support in WebSockets
  184. // Inspired by:
  185. // https://github.com/Modernizr/Modernizr/issues/370
  186. // https://github.com/Modernizr/Modernizr/blob/master/feature-detects/websockets/binary.js
  187. var wsbt = false;
  188. try {
  189. if (bt && ('binaryType' in WebSocket.prototype ||
  190. !!(new WebSocket(ws_schema + '://.').binaryType))) {
  191. Util.Info("Detected binaryType support in WebSockets");
  192. wsbt = true;
  193. }
  194. } catch (exc) {
  195. // Just ignore failed test localhost connection
  196. }
  197. // Default protocols if not specified
  198. if (typeof(protocols) === "undefined") {
  199. if (wsbt) {
  200. protocols = ['binary', 'base64'];
  201. } else {
  202. protocols = 'base64';
  203. }
  204. }
  205. if (!wsbt) {
  206. if (protocols === 'binary') {
  207. throw new Error('WebSocket binary sub-protocol requested but not supported');
  208. }
  209. if (typeof(protocols) === 'object') {
  210. var new_protocols = [];
  211. for (var i = 0; i < protocols.length; i++) {
  212. if (protocols[i] === 'binary') {
  213. Util.Error('Skipping unsupported WebSocket binary sub-protocol');
  214. } else {
  215. new_protocols.push(protocols[i]);
  216. }
  217. }
  218. if (new_protocols.length > 0) {
  219. protocols = new_protocols;
  220. } else {
  221. throw new Error("Only WebSocket binary sub-protocol was requested and is not supported.");
  222. }
  223. }
  224. }
  225. return protocols;
  226. },
  227. open: function (uri, protocols) {
  228. var ws_schema = uri.match(/^([a-z]+):\/\//)[1];
  229. protocols = this.init(protocols, ws_schema);
  230. this._websocket = new WebSocket(uri, protocols);
  231. if (protocols.indexOf('binary') >= 0) {
  232. this._websocket.binaryType = 'arraybuffer';
  233. }
  234. this._websocket.onmessage = this._recv_message.bind(this);
  235. this._websocket.onopen = (function () {
  236. Util.Debug('>> WebSock.onopen');
  237. if (this._websocket.protocol) {
  238. this._mode = this._websocket.protocol;
  239. Util.Info("Server choose sub-protocol: " + this._websocket.protocol);
  240. } else {
  241. this._mode = 'base64';
  242. Util.Error('Server select no sub-protocol!: ' + this._websocket.protocol);
  243. }
  244. this._eventHandlers.open();
  245. Util.Debug("<< WebSock.onopen");
  246. }).bind(this);
  247. this._websocket.onclose = (function (e) {
  248. Util.Debug(">> WebSock.onclose");
  249. this._eventHandlers.close(e);
  250. Util.Debug("<< WebSock.onclose");
  251. }).bind(this);
  252. this._websocket.onerror = (function (e) {
  253. Util.Debug(">> WebSock.onerror: " + e);
  254. this._eventHandlers.error(e);
  255. Util.Debug("<< WebSock.onerror: " + e);
  256. }).bind(this);
  257. },
  258. close: function () {
  259. if (this._websocket) {
  260. if ((this._websocket.readyState === WebSocket.OPEN) ||
  261. (this._websocket.readyState === WebSocket.CONNECTING)) {
  262. Util.Info("Closing WebSocket connection");
  263. this._websocket.close();
  264. }
  265. this._websocket.onmessage = function (e) { return; };
  266. }
  267. },
  268. // private methods
  269. _encode_message: function () {
  270. if (this._mode === 'binary') {
  271. // Put in a binary arraybuffer
  272. return (new Uint8Array(this._sQ)).buffer;
  273. } else {
  274. // base64 encode
  275. return Base64.encode(this._sQ);
  276. }
  277. },
  278. _decode_message: function (data) {
  279. if (this._mode === 'binary') {
  280. // push arraybuffer values onto the end
  281. var u8 = new Uint8Array(data);
  282. for (var i = 0; i < u8.length; i++) {
  283. this._rQ.push(u8[i]);
  284. }
  285. } else {
  286. // base64 decode and concat to end
  287. this._rQ = this._rQ.concat(Base64.decode(data, 0));
  288. }
  289. },
  290. _recv_message: function (e) {
  291. try {
  292. this._decode_message(e.data);
  293. if (this.rQlen() > 0) {
  294. this._eventHandlers.message();
  295. // Compact the receive queue
  296. if (this._rQ.length > this._rQmax) {
  297. this._rQ = this._rQ.slice(this._rQi);
  298. this._rQi = 0;
  299. }
  300. } else {
  301. Util.Debug("Ignoring empty message");
  302. }
  303. } catch (exc) {
  304. var exception_str = "";
  305. if (exc.name) {
  306. exception_str += "\n name: " + exc.name + "\n";
  307. exception_str += " message: " + exc.message + "\n";
  308. }
  309. if (typeof exc.description !== 'undefined') {
  310. exception_str += " description: " + exc.description + "\n";
  311. }
  312. if (typeof exc.stack !== 'undefined') {
  313. exception_str += exc.stack;
  314. }
  315. if (exception_str.length > 0) {
  316. Util.Error("recv_message, caught exception: " + exception_str);
  317. } else {
  318. Util.Error("recv_message, caught exception: " + exc);
  319. }
  320. if (typeof exc.name !== 'undefined') {
  321. this._eventHandlers.error(exc.name + ": " + exc.message);
  322. } else {
  323. this._eventHandlers.error(exc);
  324. }
  325. }
  326. }
  327. };
  328. })();