]> git.proxmox.com Git - mirror_novnc.git/blame - include/websock.js
Avoid Creating Small Objects Frequently
[mirror_novnc.git] / include / websock.js
CommitLineData
72f1348b
JM
1/*
2 * Websock: high-performance binary WebSockets
d58f8b51 3 * Copyright (C) 2012 Joel Martin
1d728ace 4 * Licensed under MPL 2.0 (see LICENSE.txt)
72f1348b
JM
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
2cccf753 17/*jslint browser: true, bitwise: true */
38781d93 18/*global Util*/
ff4bfcb7 19
72f1348b
JM
20
21// Load Flash WebSocket emulator if needed
22
bee36506 23// To force WebSocket emulator even when native WebSocket available
e5d5a7d3 24//window.WEB_SOCKET_FORCE_FLASH = true;
bee36506 25// To enable WebSocket emulator debug:
e5d5a7d3 26//window.WEB_SOCKET_DEBUG=1;
bee36506 27
f2d85676 28if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
72f1348b 29 Websock_native = true;
f2d85676 30} else if (window.MozWebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
ce3bdbcc
JM
31 Websock_native = true;
32 window.WebSocket = window.MozWebSocket;
72f1348b
JM
33} else {
34 /* no builtin WebSocket so load web_socket.js */
b688a909 35
72f1348b 36 Websock_native = false;
72f1348b
JM
37}
38
72f1348b 39function Websock() {
2cccf753
SR
40 "use strict";
41
42 this._websocket = null; // WebSocket object
38781d93 43
2cccf753 44 this._rQi = 0; // Receive queue index
38781d93
SR
45 this._rQlen = 0; // Next write position in the receive queue
46 this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB)
47 this._rQmax = this._rQbufferSize / 8;
2cccf753 48 this._sQ = []; // Send queue
38781d93
SR
49 // called in init: this._rQ = new Uint8Array(this._rQbufferSize);
50 this._rQ = null; // Receive queue
2cccf753 51
38781d93 52 this._mode = 'binary'; // Current WebSocket mode: 'binary', 'base64'
2cccf753
SR
53 this.maxBufferedAmount = 200;
54
55 this._eventHandlers = {
56 'message': function () {},
57 'open': function () {},
58 'close': function () {},
59 'error': function () {}
60 };
72f1348b
JM
61}
62
2cccf753
SR
63(function () {
64 "use strict";
38781d93
SR
65
66 var typedArrayToString = (function () {
67 // This is only for PhantomJS, which doesn't like apply-ing
68 // with Typed Arrays
69 try {
70 var arr = new Uint8Array([1, 2, 3]);
71 String.fromCharCode.apply(null, arr);
72 return function (a) { return String.fromCharCode.apply(null, a); };
73 } catch (ex) {
74 return function (a) {
75 return String.fromCharCode.apply(
76 null, Array.prototype.slice.call(a));
77 };
78 }
79 })();
80
2cccf753
SR
81 Websock.prototype = {
82 // Getters and Setters
83 get_sQ: function () {
84 return this._sQ;
85 },
86
87 get_rQ: function () {
88 return this._rQ;
89 },
90
91 get_rQi: function () {
92 return this._rQi;
93 },
94
95 set_rQi: function (val) {
96 this._rQi = val;
97 },
98
99 // Receive Queue
100 rQlen: function () {
38781d93 101 return this._rQlen - this._rQi;
2cccf753
SR
102 },
103
104 rQpeek8: function () {
105 return this._rQ[this._rQi];
106 },
107
108 rQshift8: function () {
109 return this._rQ[this._rQi++];
110 },
111
b1dee947
SR
112 rQskip8: function () {
113 this._rQi++;
114 },
115
116 rQskipBytes: function (num) {
117 this._rQi += num;
118 },
119
38781d93 120 // TODO(directxman12): test performance with these vs a DataView
2cccf753
SR
121 rQshift16: function () {
122 return (this._rQ[this._rQi++] << 8) +
123 this._rQ[this._rQi++];
124 },
125
126 rQshift32: function () {
127 return (this._rQ[this._rQi++] << 24) +
128 (this._rQ[this._rQi++] << 16) +
129 (this._rQ[this._rQi++] << 8) +
130 this._rQ[this._rQi++];
131 },
132
133 rQshiftStr: function (len) {
134 if (typeof(len) === 'undefined') { len = this.rQlen(); }
38781d93 135 var arr = new Uint8Array(this._rQ.buffer, this._rQi, len);
2cccf753 136 this._rQi += len;
38781d93 137 return typedArrayToString(arr);
2cccf753
SR
138 },
139
140 rQshiftBytes: function (len) {
141 if (typeof(len) === 'undefined') { len = this.rQlen(); }
142 this._rQi += len;
38781d93
SR
143 return new Uint8Array(this._rQ.buffer, this._rQi - len, len);
144 },
145
146 rQshiftTo: function (target, len) {
147 if (len === undefined) { len = this.rQlen(); }
148 // TODO: make this just use set with views when using a ArrayBuffer to store the rQ
149 target.set(new Uint8Array(this._rQ.buffer, this._rQi, len));
150 this._rQi += len;
2cccf753
SR
151 },
152
153 rQslice: function (start, end) {
154 if (end) {
38781d93 155 return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start);
2cccf753 156 } else {
38781d93 157 return new Uint8Array(this._rQ.buffer, this._rQi + start, this._rQlen - this._rQi - start);
2cccf753
SR
158 }
159 },
160
161 // Check to see if we must wait for 'num' bytes (default to FBU.bytes)
162 // to be available in the receive queue. Return true if we need to
163 // wait (and possibly print a debug message), otherwise false.
164 rQwait: function (msg, num, goback) {
38781d93 165 var rQlen = this._rQlen - this._rQi; // Skip rQlen() function call
2cccf753
SR
166 if (rQlen < num) {
167 if (goback) {
168 if (this._rQi < goback) {
169 throw new Error("rQwait cannot backup " + goback + " bytes");
170 }
171 this._rQi -= goback;
172 }
173 return true; // true means need more data
174 }
175 return false;
176 },
72f1348b 177
2cccf753 178 // Send Queue
72f1348b 179
2cccf753
SR
180 flush: function () {
181 if (this._websocket.bufferedAmount !== 0) {
182 Util.Debug("bufferedAmount: " + this._websocket.bufferedAmount);
183 }
72f1348b 184
2cccf753
SR
185 if (this._websocket.bufferedAmount < this.maxBufferedAmount) {
186 if (this._sQ.length > 0) {
187 this._websocket.send(this._encode_message());
188 this._sQ = [];
189 }
fc003a13 190
2cccf753
SR
191 return true;
192 } else {
193 Util.Info("Delaying send, bufferedAmount: " +
194 this._websocket.bufferedAmount);
195 return false;
196 }
197 },
198
199 send: function (arr) {
200 this._sQ = this._sQ.concat(arr);
201 return this.flush();
202 },
203
204 send_string: function (str) {
205 this.send(str.split('').map(function (chr) {
206 return chr.charCodeAt(0);
207 }));
208 },
209
210 // Event Handlers
155d78b3
JS
211 off: function (evt) {
212 this._eventHandlers[evt] = function () {};
213 },
214
2cccf753
SR
215 on: function (evt, handler) {
216 this._eventHandlers[evt] = handler;
217 },
218
38781d93
SR
219 _allocate_buffers: function () {
220 this._rQ = new Uint8Array(this._rQbufferSize);
221 },
222
2cccf753 223 init: function (protocols, ws_schema) {
38781d93 224 this._allocate_buffers();
2cccf753
SR
225 this._rQi = 0;
226 this._sQ = [];
227 this._websocket = null;
228
229 // Check for full typed array support
230 var bt = false;
231 if (('Uint8Array' in window) &&
232 ('set' in Uint8Array.prototype)) {
233 bt = true;
234 }
72f1348b 235
2cccf753
SR
236 // Check for full binary type support in WebSockets
237 // Inspired by:
238 // https://github.com/Modernizr/Modernizr/issues/370
239 // https://github.com/Modernizr/Modernizr/blob/master/feature-detects/websockets/binary.js
240 var wsbt = false;
241 try {
242 if (bt && ('binaryType' in WebSocket.prototype ||
243 !!(new WebSocket(ws_schema + '://.').binaryType))) {
244 Util.Info("Detected binaryType support in WebSockets");
245 wsbt = true;
246 }
247 } catch (exc) {
248 // Just ignore failed test localhost connection
72f1348b 249 }
72f1348b 250
2cccf753
SR
251 // Default protocols if not specified
252 if (typeof(protocols) === "undefined") {
38781d93 253 protocols = 'binary';
2cccf753 254 }
72f1348b 255
38781d93
SR
256 if (Array.isArray(protocols) && protocols.indexOf('binary') > -1) {
257 protocols = 'binary';
258 }
2cccf753 259
38781d93
SR
260 if (!wsbt) {
261 throw new Error("noVNC no longer supports base64 WebSockets. " +
262 "Please use a browser which supports binary WebSockets.");
263 }
2cccf753 264
38781d93
SR
265 if (protocols != 'binary') {
266 throw new Error("noVNC no longer supports base64 WebSockets. Please " +
267 "use the binary subprotocol instead.");
2cccf753 268 }
72f1348b 269
2cccf753
SR
270 return protocols;
271 },
72f1348b 272
2cccf753
SR
273 open: function (uri, protocols) {
274 var ws_schema = uri.match(/^([a-z]+):\/\//)[1];
275 protocols = this.init(protocols, ws_schema);
72f1348b 276
2cccf753 277 this._websocket = new WebSocket(uri, protocols);
72f1348b 278
2cccf753
SR
279 if (protocols.indexOf('binary') >= 0) {
280 this._websocket.binaryType = 'arraybuffer';
72f1348b 281 }
72f1348b 282
2cccf753
SR
283 this._websocket.onmessage = this._recv_message.bind(this);
284 this._websocket.onopen = (function () {
285 Util.Debug('>> WebSock.onopen');
286 if (this._websocket.protocol) {
287 this._mode = this._websocket.protocol;
288 Util.Info("Server choose sub-protocol: " + this._websocket.protocol);
204675c8 289 } else {
38781d93 290 this._mode = 'binary';
2cccf753 291 Util.Error('Server select no sub-protocol!: ' + this._websocket.protocol);
fcff386b 292 }
38781d93
SR
293
294 if (this._mode != 'binary') {
295 throw new Error("noVNC no longer supports base64 WebSockets. Please " +
296 "use the binary subprotocol instead.");
297
298 }
299
2cccf753
SR
300 this._eventHandlers.open();
301 Util.Debug("<< WebSock.onopen");
302 }).bind(this);
303 this._websocket.onclose = (function (e) {
304 Util.Debug(">> WebSock.onclose");
305 this._eventHandlers.close(e);
306 Util.Debug("<< WebSock.onclose");
307 }).bind(this);
308 this._websocket.onerror = (function (e) {
309 Util.Debug(">> WebSock.onerror: " + e);
310 this._eventHandlers.error(e);
311 Util.Debug("<< WebSock.onerror: " + e);
312 }).bind(this);
313 },
314
315 close: function () {
316 if (this._websocket) {
317 if ((this._websocket.readyState === WebSocket.OPEN) ||
318 (this._websocket.readyState === WebSocket.CONNECTING)) {
319 Util.Info("Closing WebSocket connection");
320 this._websocket.close();
321 }
322
323 this._websocket.onmessage = function (e) { return; };
fcff386b 324 }
2cccf753
SR
325 },
326
327 // private methods
328 _encode_message: function () {
38781d93
SR
329 // Put in a binary arraybuffer
330 return (new Uint8Array(this._sQ)).buffer;
2cccf753
SR
331 },
332
333 _decode_message: function (data) {
38781d93
SR
334 // push arraybuffer values onto the end
335 var u8 = new Uint8Array(data);
336 this._rQ.set(u8, this._rQlen);
337 this._rQlen += u8.length;
2cccf753
SR
338 },
339
340 _recv_message: function (e) {
341 try {
342 this._decode_message(e.data);
343 if (this.rQlen() > 0) {
344 this._eventHandlers.message();
345 // Compact the receive queue
38781d93
SR
346 if (this._rQlen == this._rQi) {
347 this._rQlen = 0;
348 this._rQi = 0;
349 } else if (this._rQlen > this._rQmax) {
350 if (this._rQlen - this._rQi > 0.5 * this._rQbufferSize) {
351 var old_rQbuffer = this._rQ.buffer;
352 this._rQbufferSize *= 2;
353 this._rQmax = this._rQbufferSize / 8;
354 this._rQ = new Uint8Array(this._rQbufferSize);
355 this._rQ.set(new Uint8Array(old_rQbuffer, this._rQi));
356 } else {
357 if (this._rQ.copyWithin) {
358 // Firefox only, ATM
359 this._rQ.copyWithin(0, this._rQi);
360 } else {
361 this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi));
362 }
363 }
364
365 this._rQlen = this._rQlen - this._rQi;
2cccf753
SR
366 this._rQi = 0;
367 }
368 } else {
369 Util.Debug("Ignoring empty message");
370 }
371 } catch (exc) {
372 var exception_str = "";
373 if (exc.name) {
374 exception_str += "\n name: " + exc.name + "\n";
375 exception_str += " message: " + exc.message + "\n";
376 }
fcff386b 377
2cccf753
SR
378 if (typeof exc.description !== 'undefined') {
379 exception_str += " description: " + exc.description + "\n";
380 }
72f1348b 381
2cccf753
SR
382 if (typeof exc.stack !== 'undefined') {
383 exception_str += exc.stack;
384 }
72f1348b 385
2cccf753
SR
386 if (exception_str.length > 0) {
387 Util.Error("recv_message, caught exception: " + exception_str);
388 } else {
389 Util.Error("recv_message, caught exception: " + exc);
390 }
72f1348b 391
2cccf753
SR
392 if (typeof exc.name !== 'undefined') {
393 this._eventHandlers.error(exc.name + ": " + exc.message);
394 } else {
395 this._eventHandlers.error(exc);
396 }
397 }
72f1348b 398 }
2cccf753
SR
399 };
400})();