2
* Websock: high-performance binary WebSockets
3
* Copyright (C) 2012 Joel Martin
4
* Licensed under MPL 2.0 (see LICENSE.txt)
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.
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.
17
/*jslint browser: true, bitwise: false, plusplus: false */
18
/*global Util, Base64 */
21
// Load Flash WebSocket emulator if needed
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;
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;
34
/* no builtin WebSocket so load web_socket.js */
36
Websock_native = false;
38
window.WEB_SOCKET_SWF_LOCATION = Util.get_include_uri() +
39
"web-socket-js/WebSocketMain.swf";
40
if (Util.Engine.trident) {
41
Util.Debug("Forcing uncached load of WebSocketMain.swf");
42
window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
44
Util.load_scripts(["web-socket-js/swfobject.js",
45
"web-socket-js/web_socket.js"]);
53
var api = {}, // Public API
54
websocket = null, // WebSocket object
55
mode = 'base64', // Current WebSocket mode: 'binary', 'base64'
56
rQ = [], // Receive queue
57
rQi = 0, // Receive queue index
58
rQmax = 10000, // Max receive queue size before compacting
59
sQ = [], // Send queue
62
'message' : function() {},
63
'open' : function() {},
64
'close' : function() {},
65
'error' : function() {}
72
// Queue public functions
85
function set_rQi(val) {
90
return rQ.length - rQi;
99
function rQunshift8(num) {
108
function rQshift16() {
109
return (rQ[rQi++] << 8) +
112
function rQshift32() {
113
return (rQ[rQi++] << 24) +
118
function rQshiftStr(len) {
119
if (typeof(len) === 'undefined') { len = rQlen(); }
120
var arr = rQ.slice(rQi, rQi + len);
122
return String.fromCharCode.apply(null, arr);
124
function rQshiftBytes(len) {
125
if (typeof(len) === 'undefined') { len = rQlen(); }
127
return rQ.slice(rQi-len, rQi);
130
function rQslice(start, end) {
132
return rQ.slice(rQi + start, rQi + end);
134
return rQ.slice(rQi + start);
138
// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
139
// to be available in the receive queue. Return true if we need to
140
// wait (and possibly print a debug message), otherwise false.
141
function rQwait(msg, num, goback) {
142
var rQlen = rQ.length - rQi; // Skip rQlen() function call
146
throw("rQwait cannot backup " + goback + " bytes");
150
//Util.Debug(" waiting for " + (num-rQlen) +
151
// " " + msg + " byte(s)");
152
return true; // true means need more data
158
// Private utility routines
161
function encode_message() {
162
if (mode === 'binary') {
163
// Put in a binary arraybuffer
164
return (new Uint8Array(sQ)).buffer;
167
return Base64.encode(sQ);
171
function decode_message(data) {
172
//Util.Debug(">> decode_message: " + data);
173
if (mode === 'binary') {
174
// push arraybuffer values onto the end
175
var u8 = new Uint8Array(data);
176
for (var i = 0; i < u8.length; i++) {
180
// base64 decode and concat to the end
181
rQ = rQ.concat(Base64.decode(data, 0));
183
//Util.Debug(">> decode_message, rQ: " + rQ);
188
// Public Send functions
192
if (websocket.bufferedAmount !== 0) {
193
Util.Debug("bufferedAmount: " + websocket.bufferedAmount);
195
if (websocket.bufferedAmount < api.maxBufferedAmount) {
196
//Util.Debug("arr: " + arr);
197
//Util.Debug("sQ: " + sQ);
199
websocket.send(encode_message(sQ));
204
Util.Info("Delaying send, bufferedAmount: " +
205
websocket.bufferedAmount);
210
// overridable for testing
212
//Util.Debug(">> send_array: " + arr);
217
function send_string(str) {
218
//Util.Debug(">> send_string: " + str);
219
api.send(str.split('').map(
220
function (chr) { return chr.charCodeAt(0); } ) );
224
// Other public functions
226
function recv_message(e) {
227
//Util.Debug(">> recv_message: " + e.data.length);
230
decode_message(e.data);
232
eventHandlers.message();
233
// Compact the receive queue
234
if (rQ.length > rQmax) {
235
//Util.Debug("Compacting receive queue");
240
Util.Debug("Ignoring empty message");
243
if (typeof exc.stack !== 'undefined') {
244
Util.Warn("recv_message, caught exception: " + exc.stack);
245
} else if (typeof exc.description !== 'undefined') {
246
Util.Warn("recv_message, caught exception: " + exc.description);
248
Util.Warn("recv_message, caught exception:" + exc);
250
if (typeof exc.name !== 'undefined') {
251
eventHandlers.error(exc.name + ": " + exc.message);
253
eventHandlers.error(exc);
256
//Util.Debug("<< recv_message");
260
// Set event handlers
261
function on(evt, handler) {
262
eventHandlers[evt] = handler;
265
function init(protocols) {
275
// Check for full typed array support
276
if (('Uint8Array' in window) &&
277
('set' in Uint8Array.prototype)) {
281
// Check for full binary type support in WebSockets
282
// TODO: this sucks, the property should exist on the prototype
285
if (bt && ('binaryType' in (new WebSocket("ws://localhost:17523")))) {
286
Util.Info("Detected binaryType support in WebSockets");
290
// Just ignore failed test localhost connections
293
// Default protocols if not specified
294
if (typeof(protocols) === "undefined") {
296
protocols = ['binary', 'base64'];
298
protocols = 'base64';
302
// If no binary support, make sure it was not requested
304
if (protocols === 'binary') {
305
throw("WebSocket binary sub-protocol requested but not supported");
307
if (typeof(protocols) === "object") {
308
var new_protocols = [];
309
for (var i = 0; i < protocols.length; i++) {
310
if (protocols[i] === 'binary') {
311
Util.Error("Skipping unsupported WebSocket binary sub-protocol");
313
new_protocols.push(protocols[i]);
316
if (new_protocols.length > 0) {
317
protocols = new_protocols;
319
throw("Only WebSocket binary sub-protocol was requested and not supported.");
327
function open(uri, protocols) {
328
protocols = init(protocols);
333
websocket = new WebSocket(uri, protocols);
334
if (protocols.indexOf('binary') >= 0) {
335
websocket.binaryType = 'arraybuffer';
339
websocket.onmessage = recv_message;
340
websocket.onopen = function() {
341
Util.Debug(">> WebSock.onopen");
342
if (websocket.protocol) {
343
mode = websocket.protocol;
344
Util.Info("Server chose sub-protocol: " + websocket.protocol);
347
Util.Error("Server select no sub-protocol!: " + websocket.protocol);
349
eventHandlers.open();
350
Util.Debug("<< WebSock.onopen");
352
websocket.onclose = function(e) {
353
Util.Debug(">> WebSock.onclose");
354
eventHandlers.close(e);
355
Util.Debug("<< WebSock.onclose");
357
websocket.onerror = function(e) {
358
Util.Debug(">> WebSock.onerror: " + e);
359
eventHandlers.error(e);
360
Util.Debug("<< WebSock.onerror");
366
if ((websocket.readyState === WebSocket.OPEN) ||
367
(websocket.readyState === WebSocket.CONNECTING)) {
368
Util.Info("Closing WebSocket connection");
371
websocket.onmessage = function (e) { return; };
375
// Override internal functions for testing
376
// Takes a send function, returns reference to recv function
377
function testMode(override_send, data_mode) {
380
api.send = override_send;
381
api.close = function () {};
385
function constructor() {
386
// Configuration settings
387
api.maxBufferedAmount = 200;
389
// Direct access to send and receive queues
392
api.get_rQi = get_rQi;
393
api.set_rQi = set_rQi;
395
// Routines to read from the receive queue
397
api.rQpeek8 = rQpeek8;
398
api.rQshift8 = rQshift8;
399
api.rQunshift8 = rQunshift8;
400
api.rQshift16 = rQshift16;
401
api.rQshift32 = rQshift32;
402
api.rQshiftStr = rQshiftStr;
403
api.rQshiftBytes = rQshiftBytes;
404
api.rQslice = rQslice;
409
api.send_string = send_string;
415
api.testMode = testMode;
420
return constructor();