]> git.proxmox.com Git - mirror_novnc.git/blob - include/websock.js
Change noVNC license to from LGPLv3 to MPL 2.0
[mirror_novnc.git] / include / websock.js
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
17 /*jslint browser: true, bitwise: false, plusplus: false */
18 /*global Util, Base64 */
19
20
21 // Load Flash WebSocket emulator if needed
22
23 // To force WebSocket emulator even when native WebSocket available
24 //window.WEB_SOCKET_FORCE_FLASH = true;
25 // To enable WebSocket emulator debug:
26 //window.WEB_SOCKET_DEBUG=1;
27
28 if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
29 Websock_native = true;
30 } else if (window.MozWebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
31 Websock_native = true;
32 window.WebSocket = window.MozWebSocket;
33 } else {
34 /* no builtin WebSocket so load web_socket.js */
35
36 Websock_native = false;
37 (function () {
38 function get_INCLUDE_URI() {
39 return (typeof INCLUDE_URI !== "undefined") ?
40 INCLUDE_URI : "include/";
41 }
42
43 var start = "<script src='" + get_INCLUDE_URI(),
44 end = "'><\/script>", extra = "";
45
46 window.WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
47 "web-socket-js/WebSocketMain.swf";
48 if (Util.Engine.trident) {
49 Util.Debug("Forcing uncached load of WebSocketMain.swf");
50 window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
51 }
52 extra += start + "web-socket-js/swfobject.js" + end;
53 extra += start + "web-socket-js/web_socket.js" + end;
54 document.write(extra);
55 }());
56 }
57
58
59 function Websock() {
60 "use strict";
61
62 var api = {}, // Public API
63 websocket = null, // WebSocket object
64 mode = 'base64', // Current WebSocket mode: 'binary', 'base64'
65 rQ = [], // Receive queue
66 rQi = 0, // Receive queue index
67 rQmax = 10000, // Max receive queue size before compacting
68 sQ = [], // Send queue
69
70 eventHandlers = {
71 'message' : function() {},
72 'open' : function() {},
73 'close' : function() {},
74 'error' : function() {}
75 },
76
77 test_mode = false;
78
79
80 //
81 // Queue public functions
82 //
83
84 function get_sQ() {
85 return sQ;
86 }
87
88 function get_rQ() {
89 return rQ;
90 }
91 function get_rQi() {
92 return rQi;
93 }
94 function set_rQi(val) {
95 rQi = val;
96 }
97
98 function rQlen() {
99 return rQ.length - rQi;
100 }
101
102 function rQpeek8() {
103 return (rQ[rQi] );
104 }
105 function rQshift8() {
106 return (rQ[rQi++] );
107 }
108 function rQunshift8(num) {
109 if (rQi === 0) {
110 rQ.unshift(num);
111 } else {
112 rQi -= 1;
113 rQ[rQi] = num;
114 }
115
116 }
117 function rQshift16() {
118 return (rQ[rQi++] << 8) +
119 (rQ[rQi++] );
120 }
121 function rQshift32() {
122 return (rQ[rQi++] << 24) +
123 (rQ[rQi++] << 16) +
124 (rQ[rQi++] << 8) +
125 (rQ[rQi++] );
126 }
127 function rQshiftStr(len) {
128 if (typeof(len) === 'undefined') { len = rQlen(); }
129 var arr = rQ.slice(rQi, rQi + len);
130 rQi += len;
131 return String.fromCharCode.apply(null, arr);
132 }
133 function rQshiftBytes(len) {
134 if (typeof(len) === 'undefined') { len = rQlen(); }
135 rQi += len;
136 return rQ.slice(rQi-len, rQi);
137 }
138
139 function rQslice(start, end) {
140 if (end) {
141 return rQ.slice(rQi + start, rQi + end);
142 } else {
143 return rQ.slice(rQi + start);
144 }
145 }
146
147 // Check to see if we must wait for 'num' bytes (default to FBU.bytes)
148 // to be available in the receive queue. Return true if we need to
149 // wait (and possibly print a debug message), otherwise false.
150 function rQwait(msg, num, goback) {
151 var rQlen = rQ.length - rQi; // Skip rQlen() function call
152 if (rQlen < num) {
153 if (goback) {
154 if (rQi < goback) {
155 throw("rQwait cannot backup " + goback + " bytes");
156 }
157 rQi -= goback;
158 }
159 //Util.Debug(" waiting for " + (num-rQlen) +
160 // " " + msg + " byte(s)");
161 return true; // true means need more data
162 }
163 return false;
164 }
165
166 //
167 // Private utility routines
168 //
169
170 function encode_message() {
171 if (mode === 'binary') {
172 // Put in a binary arraybuffer
173 return (new Uint8Array(sQ)).buffer;
174 } else {
175 // base64 encode
176 return Base64.encode(sQ);
177 }
178 }
179
180 function decode_message(data) {
181 //Util.Debug(">> decode_message: " + data);
182 if (mode === 'binary') {
183 // push arraybuffer values onto the end
184 rQ.push.apply(rQ, (new Uint8Array(data)));
185 } else {
186 // base64 decode and concat to the end
187 rQ = rQ.concat(Base64.decode(data, 0));
188 }
189 //Util.Debug(">> decode_message, rQ: " + rQ);
190 }
191
192
193 //
194 // Public Send functions
195 //
196
197 function flush() {
198 if (websocket.bufferedAmount !== 0) {
199 Util.Debug("bufferedAmount: " + websocket.bufferedAmount);
200 }
201 if (websocket.bufferedAmount < api.maxBufferedAmount) {
202 //Util.Debug("arr: " + arr);
203 //Util.Debug("sQ: " + sQ);
204 if (sQ.length > 0) {
205 websocket.send(encode_message(sQ));
206 sQ = [];
207 }
208 return true;
209 } else {
210 Util.Info("Delaying send, bufferedAmount: " +
211 websocket.bufferedAmount);
212 return false;
213 }
214 }
215
216 // overridable for testing
217 function send(arr) {
218 //Util.Debug(">> send_array: " + arr);
219 sQ = sQ.concat(arr);
220 return flush();
221 }
222
223 function send_string(str) {
224 //Util.Debug(">> send_string: " + str);
225 api.send(str.split('').map(
226 function (chr) { return chr.charCodeAt(0); } ) );
227 }
228
229 //
230 // Other public functions
231
232 function recv_message(e) {
233 //Util.Debug(">> recv_message: " + e.data.length);
234
235 try {
236 decode_message(e.data);
237 if (rQlen() > 0) {
238 eventHandlers.message();
239 // Compact the receive queue
240 if (rQ.length > rQmax) {
241 //Util.Debug("Compacting receive queue");
242 rQ = rQ.slice(rQi);
243 rQi = 0;
244 }
245 } else {
246 Util.Debug("Ignoring empty message");
247 }
248 } catch (exc) {
249 if (typeof exc.stack !== 'undefined') {
250 Util.Warn("recv_message, caught exception: " + exc.stack);
251 } else if (typeof exc.description !== 'undefined') {
252 Util.Warn("recv_message, caught exception: " + exc.description);
253 } else {
254 Util.Warn("recv_message, caught exception:" + exc);
255 }
256 if (typeof exc.name !== 'undefined') {
257 eventHandlers.error(exc.name + ": " + exc.message);
258 } else {
259 eventHandlers.error(exc);
260 }
261 }
262 //Util.Debug("<< recv_message");
263 }
264
265
266 // Set event handlers
267 function on(evt, handler) {
268 eventHandlers[evt] = handler;
269 }
270
271 function init(protocols) {
272 rQ = [];
273 rQi = 0;
274 sQ = [];
275 websocket = null;
276
277 var bt = false,
278 wsbt = false,
279 try_binary = false;
280
281 // Check for full typed array support
282 if (('Uint8Array' in window) &&
283 ('set' in Uint8Array.prototype)) {
284 bt = true;
285 }
286
287 // Check for full binary type support in WebSockets
288 // TODO: this sucks, the property should exist on the prototype
289 // but it does not.
290 try {
291 if (bt && ('binaryType' in (new WebSocket("ws://localhost:17523")))) {
292 Util.Info("Detected binaryType support in WebSockets");
293 wsbt = true;
294 }
295 } catch (exc) {
296 // Just ignore failed test localhost connections
297 }
298
299 // Default protocols if not specified
300 if (typeof(protocols) === "undefined") {
301 if (wsbt) {
302 protocols = ['binary', 'base64'];
303 } else {
304 protocols = 'base64';
305 }
306 }
307
308 // If no binary support, make sure it was not requested
309 if (!wsbt) {
310 if (protocols === 'binary') {
311 throw("WebSocket binary sub-protocol requested but not supported");
312 }
313 if (typeof(protocols) === "object") {
314 var new_protocols = [];
315 for (var i = 0; i < protocols.length; i++) {
316 if (protocols[i] === 'binary') {
317 Util.Error("Skipping unsupported WebSocket binary sub-protocol");
318 } else {
319 new_protocols.push(protocols[i]);
320 }
321 }
322 if (new_protocols.length > 0) {
323 protocols = new_protocols;
324 } else {
325 throw("Only WebSocket binary sub-protocol was requested and not supported.");
326 }
327 }
328 }
329
330 return protocols;
331 }
332
333 function open(uri, protocols) {
334 protocols = init(protocols);
335
336 if (test_mode) {
337 websocket = {};
338 } else {
339 websocket = new WebSocket(uri, protocols);
340 }
341
342 websocket.onmessage = recv_message;
343 websocket.onopen = function() {
344 Util.Debug(">> WebSock.onopen");
345 if (websocket.protocol) {
346 mode = websocket.protocol;
347 Util.Info("Server chose sub-protocol: " + websocket.protocol);
348 } else {
349 mode = 'base64';
350 Util.Error("Server select no sub-protocol!: " + websocket.protocol);
351 }
352 if (mode === 'binary') {
353 websocket.binaryType = 'arraybuffer';
354 }
355 eventHandlers.open();
356 Util.Debug("<< WebSock.onopen");
357 };
358 websocket.onclose = function(e) {
359 Util.Debug(">> WebSock.onclose");
360 eventHandlers.close(e);
361 Util.Debug("<< WebSock.onclose");
362 };
363 websocket.onerror = function(e) {
364 Util.Debug(">> WebSock.onerror: " + e);
365 eventHandlers.error(e);
366 Util.Debug("<< WebSock.onerror");
367 };
368 }
369
370 function close() {
371 if (websocket) {
372 if ((websocket.readyState === WebSocket.OPEN) ||
373 (websocket.readyState === WebSocket.CONNECTING)) {
374 Util.Info("Closing WebSocket connection");
375 websocket.close();
376 }
377 websocket.onmessage = function (e) { return; };
378 }
379 }
380
381 // Override internal functions for testing
382 // Takes a send function, returns reference to recv function
383 function testMode(override_send) {
384 test_mode = true;
385 api.send = override_send;
386 api.close = function () {};
387 return recv_message;
388 }
389
390 function constructor() {
391 // Configuration settings
392 api.maxBufferedAmount = 200;
393
394 // Direct access to send and receive queues
395 api.get_sQ = get_sQ;
396 api.get_rQ = get_rQ;
397 api.get_rQi = get_rQi;
398 api.set_rQi = set_rQi;
399
400 // Routines to read from the receive queue
401 api.rQlen = rQlen;
402 api.rQpeek8 = rQpeek8;
403 api.rQshift8 = rQshift8;
404 api.rQunshift8 = rQunshift8;
405 api.rQshift16 = rQshift16;
406 api.rQshift32 = rQshift32;
407 api.rQshiftStr = rQshiftStr;
408 api.rQshiftBytes = rQshiftBytes;
409 api.rQslice = rQslice;
410 api.rQwait = rQwait;
411
412 api.flush = flush;
413 api.send = send;
414 api.send_string = send_string;
415
416 api.on = on;
417 api.init = init;
418 api.open = open;
419 api.close = close;
420 api.testMode = testMode;
421
422 return api;
423 }
424
425 return constructor();
426
427 }