]> git.proxmox.com Git - mirror_novnc.git/blame - include/websock.js
Remove unecessarily nested loop
[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 */
ff4bfcb7
JM
18/*global Util, Base64 */
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
JM
36 Websock_native = false;
37 (function () {
6f4b1e40 38 window.WEB_SOCKET_SWF_LOCATION = Util.get_include_uri() +
61fc1f22 39 "web-socket-js/WebSocketMain.swf";
0981845e
JM
40 if (Util.Engine.trident) {
41 Util.Debug("Forcing uncached load of WebSocketMain.swf");
ff4bfcb7 42 window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
0981845e 43 }
6f4b1e40
JM
44 Util.load_scripts(["web-socket-js/swfobject.js",
45 "web-socket-js/web_socket.js"]);
2cccf753 46 })();
72f1348b
JM
47}
48
49
50function Websock() {
2cccf753
SR
51 "use strict";
52
53 this._websocket = null; // WebSocket object
54 this._rQ = []; // Receive queue
55 this._rQi = 0; // Receive queue index
56 this._rQmax = 10000; // Max receive queue size before compacting
57 this._sQ = []; // Send queue
58
59 this._mode = 'base64'; // Current WebSocket mode: 'binary', 'base64'
60 this.maxBufferedAmount = 200;
61
62 this._eventHandlers = {
63 'message': function () {},
64 'open': function () {},
65 'close': function () {},
66 'error': function () {}
67 };
72f1348b
JM
68}
69
2cccf753
SR
70(function () {
71 "use strict";
72 Websock.prototype = {
73 // Getters and Setters
74 get_sQ: function () {
75 return this._sQ;
76 },
77
78 get_rQ: function () {
79 return this._rQ;
80 },
81
82 get_rQi: function () {
83 return this._rQi;
84 },
85
86 set_rQi: function (val) {
87 this._rQi = val;
88 },
89
90 // Receive Queue
91 rQlen: function () {
92 return this._rQ.length - this._rQi;
93 },
94
95 rQpeek8: function () {
96 return this._rQ[this._rQi];
97 },
98
99 rQshift8: function () {
100 return this._rQ[this._rQi++];
101 },
102
b1dee947
SR
103 rQskip8: function () {
104 this._rQi++;
105 },
106
107 rQskipBytes: function (num) {
108 this._rQi += num;
109 },
110
2cccf753
SR
111 rQunshift8: function (num) {
112 if (this._rQi === 0) {
113 this._rQ.unshift(num);
114 } else {
115 this._rQi--;
116 this._rQ[this._rQi] = num;
117 }
118 },
119
120 rQshift16: function () {
121 return (this._rQ[this._rQi++] << 8) +
122 this._rQ[this._rQi++];
123 },
124
125 rQshift32: function () {
126 return (this._rQ[this._rQi++] << 24) +
127 (this._rQ[this._rQi++] << 16) +
128 (this._rQ[this._rQi++] << 8) +
129 this._rQ[this._rQi++];
130 },
131
132 rQshiftStr: function (len) {
133 if (typeof(len) === 'undefined') { len = this.rQlen(); }
134 var arr = this._rQ.slice(this._rQi, this._rQi + len);
135 this._rQi += len;
136 return String.fromCharCode.apply(null, arr);
137 },
138
139 rQshiftBytes: function (len) {
140 if (typeof(len) === 'undefined') { len = this.rQlen(); }
141 this._rQi += len;
142 return this._rQ.slice(this._rQi - len, this._rQi);
143 },
144
145 rQslice: function (start, end) {
146 if (end) {
147 return this._rQ.slice(this._rQi + start, this._rQi + end);
148 } else {
149 return this._rQ.slice(this._rQi + start);
150 }
151 },
152
153 // Check to see if we must wait for 'num' bytes (default to FBU.bytes)
154 // to be available in the receive queue. Return true if we need to
155 // wait (and possibly print a debug message), otherwise false.
156 rQwait: function (msg, num, goback) {
157 var rQlen = this._rQ.length - this._rQi; // Skip rQlen() function call
158 if (rQlen < num) {
159 if (goback) {
160 if (this._rQi < goback) {
161 throw new Error("rQwait cannot backup " + goback + " bytes");
162 }
163 this._rQi -= goback;
164 }
165 return true; // true means need more data
166 }
167 return false;
168 },
72f1348b 169
2cccf753 170 // Send Queue
72f1348b 171
2cccf753
SR
172 flush: function () {
173 if (this._websocket.bufferedAmount !== 0) {
174 Util.Debug("bufferedAmount: " + this._websocket.bufferedAmount);
175 }
72f1348b 176
2cccf753
SR
177 if (this._websocket.bufferedAmount < this.maxBufferedAmount) {
178 if (this._sQ.length > 0) {
179 this._websocket.send(this._encode_message());
180 this._sQ = [];
181 }
fc003a13 182
2cccf753
SR
183 return true;
184 } else {
185 Util.Info("Delaying send, bufferedAmount: " +
186 this._websocket.bufferedAmount);
187 return false;
188 }
189 },
190
191 send: function (arr) {
192 this._sQ = this._sQ.concat(arr);
193 return this.flush();
194 },
195
196 send_string: function (str) {
197 this.send(str.split('').map(function (chr) {
198 return chr.charCodeAt(0);
199 }));
200 },
201
202 // Event Handlers
155d78b3
JS
203 off: function (evt) {
204 this._eventHandlers[evt] = function () {};
205 },
206
2cccf753
SR
207 on: function (evt, handler) {
208 this._eventHandlers[evt] = handler;
209 },
210
211 init: function (protocols, ws_schema) {
212 this._rQ = [];
213 this._rQi = 0;
214 this._sQ = [];
215 this._websocket = null;
216
217 // Check for full typed array support
218 var bt = false;
219 if (('Uint8Array' in window) &&
220 ('set' in Uint8Array.prototype)) {
221 bt = true;
222 }
72f1348b 223
2cccf753
SR
224 // Check for full binary type support in WebSockets
225 // Inspired by:
226 // https://github.com/Modernizr/Modernizr/issues/370
227 // https://github.com/Modernizr/Modernizr/blob/master/feature-detects/websockets/binary.js
228 var wsbt = false;
229 try {
230 if (bt && ('binaryType' in WebSocket.prototype ||
231 !!(new WebSocket(ws_schema + '://.').binaryType))) {
232 Util.Info("Detected binaryType support in WebSockets");
233 wsbt = true;
234 }
235 } catch (exc) {
236 // Just ignore failed test localhost connection
72f1348b 237 }
72f1348b 238
2cccf753
SR
239 // Default protocols if not specified
240 if (typeof(protocols) === "undefined") {
241 if (wsbt) {
242 protocols = ['binary', 'base64'];
243 } else {
244 protocols = 'base64';
245 }
246 }
72f1348b 247
2cccf753
SR
248 if (!wsbt) {
249 if (protocols === 'binary') {
250 throw new Error('WebSocket binary sub-protocol requested but not supported');
251 }
72f1348b 252
2cccf753
SR
253 if (typeof(protocols) === 'object') {
254 var new_protocols = [];
255
256 for (var i = 0; i < protocols.length; i++) {
257 if (protocols[i] === 'binary') {
258 Util.Error('Skipping unsupported WebSocket binary sub-protocol');
259 } else {
260 new_protocols.push(protocols[i]);
261 }
262 }
263
264 if (new_protocols.length > 0) {
265 protocols = new_protocols;
266 } else {
267 throw new Error("Only WebSocket binary sub-protocol was requested and is not supported.");
268 }
269 }
270 }
72f1348b 271
2cccf753
SR
272 return protocols;
273 },
72f1348b 274
2cccf753
SR
275 open: function (uri, protocols) {
276 var ws_schema = uri.match(/^([a-z]+):\/\//)[1];
277 protocols = this.init(protocols, ws_schema);
72f1348b 278
2cccf753 279 this._websocket = new WebSocket(uri, protocols);
72f1348b 280
2cccf753
SR
281 if (protocols.indexOf('binary') >= 0) {
282 this._websocket.binaryType = 'arraybuffer';
72f1348b 283 }
72f1348b 284
2cccf753
SR
285 this._websocket.onmessage = this._recv_message.bind(this);
286 this._websocket.onopen = (function () {
287 Util.Debug('>> WebSock.onopen');
288 if (this._websocket.protocol) {
289 this._mode = this._websocket.protocol;
290 Util.Info("Server choose sub-protocol: " + this._websocket.protocol);
204675c8 291 } else {
2cccf753
SR
292 this._mode = 'base64';
293 Util.Error('Server select no sub-protocol!: ' + this._websocket.protocol);
fcff386b 294 }
2cccf753
SR
295 this._eventHandlers.open();
296 Util.Debug("<< WebSock.onopen");
297 }).bind(this);
298 this._websocket.onclose = (function (e) {
299 Util.Debug(">> WebSock.onclose");
300 this._eventHandlers.close(e);
301 Util.Debug("<< WebSock.onclose");
302 }).bind(this);
303 this._websocket.onerror = (function (e) {
304 Util.Debug(">> WebSock.onerror: " + e);
305 this._eventHandlers.error(e);
306 Util.Debug("<< WebSock.onerror: " + e);
307 }).bind(this);
308 },
309
310 close: function () {
311 if (this._websocket) {
312 if ((this._websocket.readyState === WebSocket.OPEN) ||
313 (this._websocket.readyState === WebSocket.CONNECTING)) {
314 Util.Info("Closing WebSocket connection");
315 this._websocket.close();
316 }
317
318 this._websocket.onmessage = function (e) { return; };
fcff386b 319 }
2cccf753
SR
320 },
321
322 // private methods
323 _encode_message: function () {
324 if (this._mode === 'binary') {
325 // Put in a binary arraybuffer
326 return (new Uint8Array(this._sQ)).buffer;
204675c8 327 } else {
2cccf753
SR
328 // base64 encode
329 return Base64.encode(this._sQ);
204675c8 330 }
2cccf753
SR
331 },
332
333 _decode_message: function (data) {
334 if (this._mode === 'binary') {
335 // push arraybuffer values onto the end
336 var u8 = new Uint8Array(data);
337 for (var i = 0; i < u8.length; i++) {
338 this._rQ.push(u8[i]);
339 }
340 } else {
341 // base64 decode and concat to end
342 this._rQ = this._rQ.concat(Base64.decode(data, 0));
343 }
344 },
345
346 _recv_message: function (e) {
347 try {
348 this._decode_message(e.data);
349 if (this.rQlen() > 0) {
350 this._eventHandlers.message();
351 // Compact the receive queue
352 if (this._rQ.length > this._rQmax) {
353 this._rQ = this._rQ.slice(this._rQi);
354 this._rQi = 0;
355 }
356 } else {
357 Util.Debug("Ignoring empty message");
358 }
359 } catch (exc) {
360 var exception_str = "";
361 if (exc.name) {
362 exception_str += "\n name: " + exc.name + "\n";
363 exception_str += " message: " + exc.message + "\n";
364 }
fcff386b 365
2cccf753
SR
366 if (typeof exc.description !== 'undefined') {
367 exception_str += " description: " + exc.description + "\n";
368 }
72f1348b 369
2cccf753
SR
370 if (typeof exc.stack !== 'undefined') {
371 exception_str += exc.stack;
372 }
72f1348b 373
2cccf753
SR
374 if (exception_str.length > 0) {
375 Util.Error("recv_message, caught exception: " + exception_str);
376 } else {
377 Util.Error("recv_message, caught exception: " + exc);
378 }
72f1348b 379
2cccf753
SR
380 if (typeof exc.name !== 'undefined') {
381 this._eventHandlers.error(exc.name + ": " + exc.message);
382 } else {
383 this._eventHandlers.error(exc);
384 }
385 }
72f1348b 386 }
2cccf753
SR
387 };
388})();