/*
* Generic Client Side WebSocket
*/
"use strict";
/**
* This namespace contains debugging utilities and watch functions.
* @namespace wf
*/
var wf = // Debug Utilities, to use as watches
{
/**
* Get a readable operation code from the specified frame.
* @function oc
* @memberof wf
* @param {uInt8Array} framedData The array that contains the frame. The array may contain several frames.
* @param {integer} currentFrameIndex The index of the frame within the array that the <code>framedData</code> parameter specifies. The index of the first frame is zero.
* @returns The readable operation code, as defined by the WebSocket specification.
* <table>
* <tr><td>0x0</td><td>"Payload continues"</td></tr>
* <tr><td>0x1</td><td>"UTF8 data"</td></tr>
* <tr><td>0x2</td><td>"Binary data"</td></tr>
* <tr><td>0x8</td><td>"Connection terminated"</td></tr>
* <tr><td>0x9</td><td>"Ping"</td></tr>
* <tr><td>0xA</td><td>"Pong"</td></tr>
* <tr><td>otherwise</td><td>"Unknown opcode"</td></tr>
* </table>
* @inner
*/
oc : function(framedData, currentFrameIndex) // Readable Op Code
{ return OraSocketGlobal.FramingUtils.readableOpCode(OraSocketGlobal.FramingUtils.getOpCode(framedData, currentFrameIndex)); },
/**
* Get a string that contains the 4-element mask of the specified frame.
* @function dm
* @memberof wf
* @param {uInt8Array} framedData The array that contains the frame. The array may contain several frames.
* @param {integer} currentFrameIndex The index of the frame within the array that the <code>framedData</code> parameter specifies. The index of the first frame is zero.
* @returns A string that contains the 4-element frame mask, displayed in hexadecimal.
* @inner
*/
dm : function(framedData, currentFrameIndex) // Display Mask
{ return OraSocketGlobal.FramingUtils.displayMask(OraSocketGlobal.FramingUtils.getMaskingKey(framedData, currentFrameIndex)); },
/**
* Get a string that contains the header of the specified frame. The header is the part of a frame that precedes the payload that the frame is carrying.
* @function dh
* @memberof wf
* @param {uInt8Array} framedData The array that contains the frame. The array may contain several frames.
* @param {integer} currentFrameIndex The index of the frame within the array that the <code>framedData</code> parameter specifies. The index of the first frame is zero.
* @returns A string that contains the frame header, displayed in hexadecimal.
* @inner
*/
dh : function(framedData, currentFrameIndex) // Dump Frame Header
{ return OraSocketGlobal.FramingUtils.dumpHeader(framedData, currentFrameIndex); },
/**
* Get a string that contains the decrypted payload of the specified frame.<br>
* This function might not be suitable for non-text data.
* @function dp
* @memberof wf
* @param {uInt8Array} framedData The array that contains the frame. The array may contain several frames.
* @param {integer} currentFrameIndex The index of the frame within the array that the <code>framedData</code> parameter specifies. The index of the first frame is zero.
* @returns A string that contains the decrypted payload, displayed in hexadecimal.
* @inner
*/
dp : function(framedData, currentFrameIndex) // Display Payload (good for utf8)
{ return OraSocketGlobal.FramingUtils.getUnframedTextData(framedData, currentFrameIndex) ; }
};
// 1. observe
// A function to modify an object property's getters and
// setters so that a custom callback handler can be executed
// each time the property is changed
var observe = function(subject, property, callbackHandler)
{
Object.defineProperty(subject,
property,
{
// Return the default value of the property
// ("this.value" automatically gives you the property's current value)
get: function ()
{
return this.value;
},
// Set the property with a new value
set: function (newValue)
{
// Assign the new value
this.value = newValue;
// Bind the observer's changeHandler to the subject
subject.changeHandler = callbackHandler;
// Tell the subject to call the changeHandler when this
// property is changed. (This is like a custom event dispatcher)
subject.changeHandler(subject, property);
},
// Set the default parameters for how this property can be
// accessed and changed.You probably don't need to change these
// unless you want to lock down the property values to prevent your
// program from changing them
enumerable: true,
configurable: true
});
};
// 2. unobserve
// An optional function to stop watching properties.
// It normalizes the getter and setter and removes the callback handler
var unobserve = function(subject, property)
{
//Delete the changeHandler
delete subject.changeHandler;
//Reset the getter and setter
Object.defineProperty(subject,
property,
{
get: function ()
{
return this.value;
},
set: function (newValue)
{
this.value = newValue;
},
enumerable: true,
configurable: true
});
};
var OraSocketGlobal =
{
debugLevel : 0, // Tunes the verbose level. 0 means mute.
PING_INTERVAL : 25000, // in ms. Overriden by orasocket.config. Default is 25s - 29 is too close to 30 (less than 30s, which is the default max idle time on the server side).
PING_ENABLED : true, // Does the client ping the server? Overriden by orasocket.config
encodingBase : 16, // Used for IE, to encode the frames (so they fit in a real string). Must be in sync with the server side encoding.
ENCODE_FOR_IE_BELOW: 10, // BaseXX encoding required for IE, below (strictly, < ) this version number. Encoding base (XX) is encodingBase, above. Overriden by orasocket.config
WEBSOCKET_CREATION_TIMEOUT: 1000, // Consider that the WS creation has failed if not completed within this amount of ms
NB_TRY_FOR_EACH_TRANSPORT : 2, // Overriden by orasocket.config
/*
* If the connection has failed after less than this value (in ms), then try a different transport.
* If the connection has failed after being connected long enough (ie beyond that value), then try to connect again, with the same transport.
*/
TRY_AGAIN_INTERVAL : 10000, // 10s. Overriden by orasocket.config
ENFORCE_ENCODING : false, // Overriden by orasocket.config
wsAvailable : false,
flashSupported : false,
xhrAvailable : false,
$nativeWS : {},
// XHR readyState codes
XHR_UNINITIALIZED : 0, // UNSENT
XHR_LOADING : 1, // OPENED
XHR_RESPONSE_HEADERS_RECEIVED : 2, // HEADERS_RECEIVED
XHR_SOME_RESPONSE_BODY_RECEIVED : 3, // LOADING
XHR_REQUEST_COMPLETED : 4, // DONE
// XHR (HTTP) Status codes
HTTP_NO_DATA : 0,
HTTP_OK : 200,
HTTP_ERROR_1 : 403,
HTTP_ERROR_2 : 404,
HTTP_SERVER_ERROR: 500,
HTTP_ERROR_RETRY : 503,
ERROR_INTERNET_TIMEOUT : 12002,
ERROR_INTERNET_NAME_NOT_RESOLVED : 12007,
ERROR_INTERNET_CANNOT_CONNECT : 12029,
ERROR_INTERNET_CONNECTION_ABORTED : 12030,
ERROR_INTERNET_CONNECTION_RESET : 12031,
ERROR_HTTP_INVALID_SERVER_RESPONSE : 12152,
NATIVE_WEB_SOCKET : "WebSocket",
FLASH_SOCKET : "FlashSocket",
XML_HTTP_REQUEST : "XMLHttpRequest",
JSONP : "JSONP",
CLOSE_CODE_NORMAL: 1000,
CLOSE_CODE_GOING_AWAY: 1001,
CLOSE_CODE_PROTOCOL_ERROR: 1002,
CLOSE_CODE_CANNOT_ACCEPT_TYPE: 1003,
CLOSE_CODE_RESERVED: 1004,
CLOSE_CODE_NO_STATUS: 1005,
CLOSE_CODE_ABNORMAL: 1006,
CLOSE_CODE_INCONSISTENT: 1007,
CLOSE_CODE_POLICY_VIOLATION: 1008,
CLOSE_CODE_TOO_BIG: 1009,
CLOSE_CODE_NEGOTIATION_EXPECTED: 1010,
CLOSE_CODE_UNEXPECTED_CONDITION: 1011,
CLOSE_CODE_TLS_FAILURE: 1015,
readableCloseCode : function(code)
{
var readable = "Reserved or un-managed";
switch (code)
{
case OraSocketGlobal.CLOSE_CODE_NORMAL:
readable = "Normal";
break;
case OraSocketGlobal.CLOSE_CODE_GOING_AWAY:
readable = "Going Away";
break;
case OraSocketGlobal.CLOSE_CODE_PROTOCOL_ERROR:
readable = "Protocol Error";
break;
case OraSocketGlobal.CLOSE_CODE_CANNOT_ACCEPT_TYPE:
readable = "Cannot accept type";
break;
case OraSocketGlobal.CLOSE_CODE_RESERVED:
readable = "Reserved";
break;
case OraSocketGlobal.CLOSE_CODE_NO_STATUS:
readable = "No Status";
break;
case OraSocketGlobal.CLOSE_CODE_ABNORMAL:
readable = "Abnormal";
break;
case OraSocketGlobal.CLOSE_CODE_INCONSISTENT:
readable = "Inconsistent";
break;
case OraSocketGlobal.CLOSE_CODE_POLICY_VIOLATION:
readable = "Policy Violation";
break;
case OraSocketGlobal.CLOSE_CODE_TOO_BIG:
readable = "Too Big";
break;
case OraSocketGlobal.CLOSE_CODE_NEGOTIATION_EXPECTED:
readable = "Negotiation Excpected";
break;
case OraSocketGlobal.CLOSE_CODE_UNEXPECTED_CONDITION:
readable = "Unexpected Condition";
break;
case OraSocketGlobal.CLOSE_CODE_TLS_FAILURE:
readable = "TLS Failure";
break;
default:
break;
}
return code + "/" + readable;
},
fallbackOrder : [], // Populated at startup with the above
availableTransports : [], // Populated at the first POST request
clientID : {},
requestQueue : {},
XMLHttpRequest : function() // Always return an XMLHttpRequest. Distinction will be made in LongPoll.js
{
if (false && navigator.appName === "Microsoft Internet Explorer" && OraSocketGlobal.getIEVersion() < 10)
{
console.log("Instantiating an XDomainRequest");
return new XDomainRequest();
}
else
return new XMLHttpRequest();
},
validateCloseCode : function(code)
{
var okCode = true;
if (typeof(code) !== 'number' || (typeof(code) === 'number' && Math.round(code) !== code))
okCode = false;
// else if (code < 1000 || code > 4999)
// okCode = false;
else if (code !== 1000 && (code < 3000 || code > 4999))
okCode = false;
return okCode;
},
validateCloseReason : function(reason)
{
var okCode = true;
if (typeof(reason) !== 'string' || (typeof(reason) === 'string' && reason.length > 123))
okCode = false;
return okCode;
},
// Returns the version of Windows Internet Explorer or a -1
// (indicating the use of another browser).
getIEVersion : function()
{
var rv = -1; // Return value assumes failure.
if (navigator.appName === 'Microsoft Internet Explorer')
{
var ua = navigator.userAgent;
var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
if (re.exec(ua) !== null)
rv = parseFloat( RegExp.$1 );
}
return rv;
},
isFlashSupported : function()
{
var version = -1;
// ie looks something like this:
// "WIN 12,0,0,77"
if (typeof(ActiveXObject) !== "undefined")
{
var ie = true;
try
{
ie = true;
var swf = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
var versionString = swf.GetVariable("$version");
version = parseInt(versionString.split(" ")[1].split(",")[0], 10);
}
catch (e)
{
ie = false;
}
}
// On other browsers it looks like this:
// "Shockwave Flash 12.0 r0"
if (typeof navigator.plugins !== "undefined")
{
if (typeof navigator.plugins["Shockwave Flash"] !== "undefined")
{
var versionString = navigator.plugins["Shockwave Flash"].description;
version = parseInt(versionString.split(" ")[2], 10);
}
}
return (version >= 9.0);
},
Queue : function()
{
var queue = [];
this.getSize = function()
{
return queue.length;
};
this.isEmpty = function()
{
return queue.length === 0;
};
this.dequeue = function()
{
var obj = null;
if (!this.isEmpty())
{
obj = queue[0];
queue.splice(0, 1);
}
return obj;
};
this.enqueue = function(obj)
{
queue.push(obj);
};
},
FramingUtils :
{
/*
+-+-+-+-+-------+-+-------------+-------------------------------+
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| | Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
*/
/*-----------------------------------------------
Masking for arrays of 8 bit words (aka byte)
------------------------------------------------*/
// Frames opcodes
PAYLOAD_CONTINUES_FROM_LAST: 0x0,
UTF_8_DATA : 0x1,
BINARY_DATA : 0x2,
TERMINATES_CONNECTION : 0x8,
PING : 0x9,
PONG : 0xA,
formatHexa : function(str, len)
{
return OraSocketGlobal.FramingUtils.lpad(str, len, '0');
},
lpad : function(str, upto, filler)
{
var s = str;
while (s.length < upto)
s = filler + s;
return s;
},
getFin : function(data, fromIdx)
{
if (fromIdx === undefined)
fromIdx = 0;
return ((data[fromIdx + 0] & 0x80) >>> 7) & 0xFF;
},
getOpCode : function(data, fromIdx)
{
if (fromIdx === undefined)
fromIdx = 0;
return (data[fromIdx + 0] & 0x0F) & 0xFF;
},
getMask : function(data, fromIdx) // Is there a mask?
{
if (fromIdx === undefined)
fromIdx = 0;
return ((data[fromIdx + 1] & 0x80) >>> 7) & 0xFF;
},
getPayloadOffset : function(data, fromIdx)
{
if (fromIdx === undefined)
fromIdx = 0;
var offset = 2;
var payloadLength = data[fromIdx + 1] & 0x7F;
var mask = OraSocketGlobal.FramingUtils.getMask(data, fromIdx);
if (payloadLength === 126)
offset += 2;
else if (payloadLength === 127)
offset += 8;
if (mask === 1)
offset += 4;
return offset;
},
getMaskOffset : function(data, fromIdx)
{
if (fromIdx === undefined)
fromIdx = 0;
var offset = 2;
var payloadLength = data[fromIdx + 1] & 0x7F;
if (payloadLength === 126)
offset += 2;
else if (payloadLength === 127)
offset += 8;
return offset;
},
getPayloadLength : function(data, fromIdx)
{
if (fromIdx === undefined)
fromIdx = 0;
var payloadLength = data[fromIdx + 1] & 0x7F;
if (payloadLength === 126) // 16 bits
{
var part1 = data[fromIdx + 2];
var part2 = data[fromIdx + 3];
// console.log(formatHexa(part1.toString(16), 2) + " | " + formatHexa(part2.toString(16), 2))
payloadLength = (part1 << 8) | part2;
}
else if (payloadLength === 127) // 64 bits
{
var part1 = data[fromIdx + 2];
var part2 = data[fromIdx + 3];
var part3 = data[fromIdx + 4];
var part4 = data[fromIdx + 5];
var part5 = data[fromIdx + 6];
var part6 = data[fromIdx + 7];
var part7 = data[fromIdx + 8];
var part8 = data[fromIdx + 9];
var fullString = OraSocketGlobal.FramingUtils.formatHexa(part1.toString(16), 2) +
OraSocketGlobal.FramingUtils.formatHexa(part2.toString(16), 2) +
OraSocketGlobal.FramingUtils.formatHexa(part3.toString(16), 2) +
OraSocketGlobal.FramingUtils.formatHexa(part4.toString(16), 2) +
OraSocketGlobal.FramingUtils.formatHexa(part5.toString(16), 2) +
OraSocketGlobal.FramingUtils.formatHexa(part6.toString(16), 2) +
OraSocketGlobal.FramingUtils.formatHexa(part7.toString(16), 2) +
OraSocketGlobal.FramingUtils.formatHexa(part8.toString(16), 2);
if (OraSocketGlobal.debugLevel > 4)
console.debug("0x" + fullString);
payloadLength = parseInt(fullString, 16);
}
return payloadLength;
},
/* returns an array of 4 bytes */
getMaskingKey : function(data, fromIdx)
{
if (fromIdx === undefined)
fromIdx = 0;
var offset = OraSocketGlobal.FramingUtils.getMaskOffset(data, fromIdx);
var part1 = data[offset + fromIdx];
var part2 = data[offset + fromIdx + 1];
var part3 = data[offset + fromIdx + 2];
var part4 = data[offset + fromIdx + 3];
if (OraSocketGlobal.debugLevel > 100)
{
var displayString = "0x" + OraSocketGlobal.FramingUtils.formatHexa(part1.toString(16), 2) +
" 0x" + OraSocketGlobal.FramingUtils.formatHexa(part2.toString(16), 2) +
" 0x" + OraSocketGlobal.FramingUtils.formatHexa(part3.toString(16), 2) +
" 0x" + OraSocketGlobal.FramingUtils.formatHexa(part4.toString(16), 2);
console.debug("Mask : " + displayString);
}
return [part1, part2, part3, part4];
},
dumpHeader : function(data, fromIdx)
{
var po = OraSocketGlobal.FramingUtils.getPayloadOffset(data, fromIdx);
var str = "";
for (var i=0; i<po; i++)
{
str += "[" + OraSocketGlobal.FramingUtils.formatHexa(data[fromIdx + i].toString(16), 2) + "] ";
}
return str;
},
getFrameLength : function(data, fromIdx)
{
if (fromIdx === undefined)
fromIdx = 0;
var payloadOffset = OraSocketGlobal.FramingUtils.getPayloadOffset(data, fromIdx);
var payloadLength = OraSocketGlobal.FramingUtils.getPayloadLength(data, fromIdx);
return payloadOffset + payloadLength; // - fromIdx;
},
displayMask : function(mask)
{
var str = "";
for (var i=0; i<mask.length; i++)
{
str += ("0x" + OraSocketGlobal.FramingUtils.formatHexa(mask[i].toString(16), 2) + " ");
}
return str;
},
readableOpCode : function(opcode)
{
var meaning;
switch (opcode)
{
case OraSocketGlobal.FramingUtils.PAYLOAD_CONTINUES_FROM_LAST:
meaning = "Payload continues";
break;
case OraSocketGlobal.FramingUtils.UTF_8_DATA:
meaning = "UTF8 data";
break;
case OraSocketGlobal.FramingUtils.BINARY_DATA:
meaning = "Binary data";
break;
case OraSocketGlobal.FramingUtils.TERMINATES_CONNECTION:
meaning = "Connection terminated";
break;
case OraSocketGlobal.FramingUtils.PING:
meaning = "Ping";
break;
case OraSocketGlobal.FramingUtils.PONG:
meaning = "Pong";
break;
default:
meaning = "Unkonwn opcode [0x" + opcode.toString(16) + "]";
break;
}
return meaning;
},
generateMask : function()
{
var maskKey = [0x10, 0x20, 0x30, 0x40]; // Hard coded
for (var i=0; i<4; i++)
{
maskKey[i] = Math.floor(Math.random() * 128); // Random generated
}
return maskKey;
},
/*
* Encoding
*/
frameMessage : function(response, opcode, mask, fin)
{
if (response.length !== undefined && response.length > 0 && OraSocketGlobal.debugLevel > 1)
console.debug("Message to Frame:[" + response + "]");
// var payloadLength = (response instanceof ArrayBuffer) ? response.byteLength : response.length;
if (typeof(response) === 'number')
response = response.toString();
var payloadLength = (typeof(response) !== "string") ? (response.size !== undefined ? response.size : response.byteLength) : response.length;
if (mask === undefined && payloadLength > 0)
mask = 1;
if (mask === undefined && payloadLength === 0)
mask = 0;
if (fin === undefined)
fin = 1;
var maskKey = [ 0x0, 0x0, 0x0, 0x0 ];
if (mask === 1 && payloadLength > 0)
maskKey = OraSocketGlobal.FramingUtils.generateMask();
if (payloadLength > 0 && OraSocketGlobal.debugLevel > 1) // Not a ping, pong. or close
console.debug("Message length:" + payloadLength + " :" + response);
var frame = [];
var byte = (fin << 7) | opcode;
//console.log("fin | opcode :" + formatHexa(byte.toString(16), 2));
frame.push(byte);
if (payloadLength < 126) // 7E
{
byte = (mask << 7) | (payloadLength & 0x7F);
// console.log("mask | len :" + formatHexa(byte.toString(16), 2));
frame.push(byte);
// for (var i=0; i<8; i++)
// frame.push(0x0);
}
else if (payloadLength < 0xFFFF) // 615535) // 16 bits
{
var part1 = (payloadLength & 0xFF00) >> 8;
var part2 = payloadLength & 0xFF;
frame.push(0x7E | (mask << 7)); // 126 + mask
frame.push(part1);
frame.push(part2);
// for (var i=0; i<6; i++)
// frame.push(0x0);
}
else // 64 bits
{
var hexLen = OraSocketGlobal.FramingUtils.formatHexa(payloadLength.toString(16), 16);
frame.push(0x7F | (mask << 7)); // 127
for (var i=0; i<hexLen.length; i+=2)
{
var XX = hexLen.substring(i, i+2);
frame.push(parseInt(XX, 16));
}
}
// Maskink key
for (var i=0; (mask === 1) && i<maskKey.length; i++)
{
frame.push(maskKey[i]);
}
// Append actual payload
var framedResponse = frame; // frame.concat(response); // Nope.. Multitype array...
var uInt8Array;
var responseLength;
// if (response instanceof ArrayBuffer)
if (typeof(response) !== "string")
{
responseLength = (response.size !== undefined ? response.size : response.byteLength); // response.byteLength; // BUG #17625727
uInt8Array = new Uint8Array(response);
}
else
{
responseLength = response.length;
}
for (var i=0; i<responseLength; i++)
{
// var dataByte = (response instanceof ArrayBuffer) ? uInt8Array[i] : response.charCodeAt(i);
var dataByte = (typeof(response) !== "string") ? uInt8Array[i] : response.charCodeAt(i);
framedResponse.push(dataByte ^ maskKey[i % 4]); // apply mask, encode
}
// Dump it
if (payloadLength > 0 && OraSocketGlobal.debugLevel > 4) // Not a pong or a ping
{
var all = "";
for (var i=0; i<framedResponse.length; i++)
{
all += ("[" + OraSocketGlobal.FramingUtils.formatHexa(framedResponse[i].toString(16), 2) + "] ");
}
console.debug("Framed (" + framedResponse.length + ") :" + all);
}
return framedResponse;
},
unframedMessage : function(data, fromIdx)
{
if (fromIdx === undefined)
fromIdx = 0;
var maskingKey = [0x00, 0x00, 0x00, 0x00]; // identity, default.
if (OraSocketGlobal.FramingUtils.getMask(data) === 1)
maskingKey = OraSocketGlobal.FramingUtils.getMaskingKey(data, fromIdx);
var payload = [];
var offset = fromIdx + OraSocketGlobal.FramingUtils.getPayloadOffset(data, fromIdx);
var end = offset + OraSocketGlobal.FramingUtils.getPayloadLength(data, fromIdx);
for (var i=offset; i<end; i++)
{
var decoded = (data[i] ^ maskingKey[(i - offset) % 4]);
payload.push(decoded);
}
return payload;
},
getUnframedTextData : function(data, fromIdx)
{
var payload = "";
if (fromIdx === undefined)
fromIdx = 0;
if (OraSocketGlobal.FramingUtils.getOpCode(data, fromIdx) === OraSocketGlobal.FramingUtils.UTF_8_DATA)
{
var maskingKey = [0x00, 0x00, 0x00, 0x00]; // identity, default.
if (OraSocketGlobal.FramingUtils.getMask(data) === 1)
maskingKey = OraSocketGlobal.FramingUtils.getMaskingKey(data, fromIdx);
var offset = fromIdx + OraSocketGlobal.FramingUtils.getPayloadOffset(data, fromIdx);
var end = offset + OraSocketGlobal.FramingUtils.getPayloadLength(data, fromIdx);
for (var i=offset; i<end; i++)
{
var decoded = (data[i] ^ maskingKey[(i - offset) % 4]);
payload += String.fromCharCode(decoded);
}
}
return payload;
},
binaryString : function(dataArray)
{
// var bs = ""; // The name of this variable is right.
// for (var i=0; i<dataArray.length; i++)
// bs += (String.fromCharCode(dataArray[i]));
// return bs;
var len = dataArray.length;
var uInt8Array = new Uint8Array(len);
try
{
for (var idx=0; idx<len; idx++)
{
uInt8Array[idx] = (dataArray[idx] & 0xFF);
}
// for (var i=0; i<len; i++)
// uInt8Array[i] = (dataArray[i] & 0xFF);
}
catch (err)
{
console.log(err);
}
return uInt8Array.buffer;
},
string2ArrayBuffer : function(charSequence)
{
var ab = [];
for (var i=0; i<charSequence.length; i++)
{
ab.push(charSequence.charCodeAt(i) & 0xFF);
}
return ab;
},
/**
* Decodes the string containing "binary" data.
* The string looks like "ff;d8;ff;e0;00;10;4a;46;49;46;00;01;01;00;00;01;00;01;00;00;ff;db;00;84;00;09;06;06;14;10;10;10;11;10;..."
* Will be turned into a byte array containing [0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x00, ...]
*
* @param jsonEncoded
* @returns {Array}
*/
binJson2ArrayBuffer_v1 : function(jsonEncoded)
{
var ab = [];
var hx = jsonEncoded.split(";");
for (var i=0; i<hx.length; i++)
{
if (hx[i].length > 0)
ab.push(parseInt("0x" + hx[i]) & 0xFF);
}
return ab;
}
},
GnlUtils :
{
/**
* Decodes the string containing "binary" data.
* The string looks like "ffd8ffe000104a46494600010100000100010000ffdb008400090606141010101110..."
* Will be turned into a byte array containing [0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x00, ...]
*
* @param {string} encoded
* @returns {Array}
*/
base16decode : function(encoded)
{
var ab = [];
for (var i=0; i<encoded.length; i+=2)
{
var twoChar = encoded.substring(i, i+2);
ab.push(parseInt("0x" + twoChar) & 0xFF);
}
return ab;
},
/**
* Encoding.
* Turn a byte array like [0xFF, 0xCA, 0xFE, 0xBA, 0xBE, ...]
* into a string like "ffcafebabe..."
*
* @param {Array} dataArray
* @returns {string}
*/
base16encode : function(dataArray)
{
var str = ""; // will contain the hexa value on two characters.
for (var i=0; i<dataArray.length; i++)
{
if (typeof(dataArray) === 'string')
str += (OraSocketGlobal.FramingUtils.formatHexa(dataArray.charCodeAt(i).toString(16), 2));
else
str += (OraSocketGlobal.FramingUtils.formatHexa(dataArray[i].toString(16), 2));
}
return str;
},
// 0 1 2 3 4 5 6
// 01234567890123456789012345678901234567890123456789012345678901234
base64 : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
base64encode : function(str) // Encode
{
var chars = OraSocketGlobal.GnlUtils.base64;
var encoded = [];
var c = 0;
var b0, b1, b2;
while (c < str.length)
{
if (typeof(str) === 'string')
{
b0 = str.charCodeAt(c++);
b1 = str.charCodeAt(c++);
b2 = str.charCodeAt(c++);
}
else
{
b0 = str[c++];
b1 = str[c++];
b2 = str[c++];
}
var buf = (b0 << 16) + ((b1 || 0) << 8) + (b2 || 0);
var i0 = (buf & (63 << 18)) >> 18;
var i1 = (buf & (63 << 12)) >> 12;
var i2 = isNaN(b1) ? 64 : (buf & (63 << 6)) >> 6;
var i3 = isNaN(b2) ? 64 : (buf & 63);
encoded[encoded.length] = chars.charAt(i0);
encoded[encoded.length] = chars.charAt(i1);
encoded[encoded.length] = chars.charAt(i2);
encoded[encoded.length] = chars.charAt(i3);
}
return encoded.join('');
},
base64decode : function(str) // decode
{
var chars = OraSocketGlobal.GnlUtils.base64;
var invalid =
{
strlen: (str.length % 4 !== 0),
chars: new RegExp('[^' + chars + ']').test(str),
equals: (/=/.test(str) && (/=[^=]/.test(str) || /={3}/.test(str)))
};
if (invalid.strlen || invalid.chars || invalid.equals)
throw new Error('Invalid base64 data');
var decoded = [];
var c = 0;
while (c < str.length)
{
var i0 = chars.indexOf(str.charAt(c++));
var i1 = chars.indexOf(str.charAt(c++));
var i2 = chars.indexOf(str.charAt(c++));
var i3 = chars.indexOf(str.charAt(c++));
var buf = (i0 << 18) + (i1 << 12) + ((i2 & 63) << 6) + (i3 & 63);
var b0 = (buf & (255 << 16)) >> 16;
var b1 = (i2 === 64) ? -1 : (buf & (255 << 8)) >> 8;
var b2 = (i3 === 64) ? -1 : (buf & 255);
decoded[decoded.length] = String.fromCharCode(b0);
if (b1 >= 0) decoded[decoded.length] = String.fromCharCode(b1);
if (b2 >= 0) decoded[decoded.length] = String.fromCharCode(b2);
}
return decoded.join('');
},
decodeReadyState : function(status)
{
var code = "unknown";
switch (status)
{
case OraSocketGlobal.XHR_UNINITIALIZED:
code = "UNINITIALIZED";
break;
case OraSocketGlobal.XHR_LOADING:
code = "LOADING";
break;
case OraSocketGlobal.XHR_RESPONSE_HEADERS_RECEIVED:
code = "RESPONSE HEADERS RECEIVED";
break;
case OraSocketGlobal.XHR_SOME_RESPONSE_BODY_RECEIVED:
code = "SOME RESPONSE BODY RECEIVED";
break;
case OraSocketGlobal.XHR_REQUEST_COMPLETED:
code = "REQUEST COMPLETED";
break;
default:
code = "unknown";
}
return code;
},
decodeStatus : function(status)
{
var code = "unknown";
try
{
switch (status)
{
case OraSocketGlobal.HTTP_NO_DATA:
code = "NO DATA/" + status.toString();
break;
case OraSocketGlobal.HTTP_OK:
code = "OK/" + status.toString();
break;
case OraSocketGlobal.HTTP_ERROR_1:
code = "ERROR/" + status.toString();
break;
case OraSocketGlobal.HTTP_ERROR_2:
code = "ERROR/" + status.toString();
break;
case OraSocketGlobal.HTTP_ERROR_RETRY:
code = "ERROR RETRY/" + status.toString();
break;
case OraSocketGlobal.ERROR_INTERNET_TIMEOUT:
code = "ERROR_INTERNET_TIMEOUT/" + status.toString();
break;
case OraSocketGlobal.ERROR_INTERNET_NAME_NOT_RESOLVED:
code = "ERROR_INTERNET_NAME_NOT_RESOLVED/" + status.toString();
break;
case OraSocketGlobal.ERROR_INTERNET_CANNOT_CONNECT:
code = "ERROR_INTERNET_CANNOT_CONNECT/" + status.toString();
break;
case OraSocketGlobal.ERROR_INTERNET_CONNECTION_ABORTED:
code = "ERROR_INTERNET_CONNECTION_ABORTED/" + status.toString();
break;
case OraSocketGlobal.ERROR_INTERNET_CONNECTION_RESET:
code = "ERROR_INTERNET_CONNECTION_RESET/" + status.toString();
break;
case OraSocketGlobal.ERROR_HTTP_INVALID_SERVER_RESPONSE:
code = "ERROR_HTTP_INVALID_SERVER_RESPONSE/" + status.toString();
break;
default:
code = status.toString();
}
}
catch (err)
{
}
return code;
},
getClass : function(obj)
{
if (obj && typeof obj === 'object' &&
Object.prototype.toString.call(obj) !== '[object Array]' &&
obj.constructor)
{
var arr = obj.constructor.toString().match(/function\s*(\w+)/);
if (arr && arr.length === 2)
{
return arr[1];
}
}
return false;
},
/**
* Used to manage binary data, with some transparency.
*
* binData: Binary data returned by the onmessage callback of the WebSocket
* processor: Callback function, taking a Uint8Array parameter.
* desperate: Callback function. What to do in case we don't know what to do.
* this uInt8Array parameter is generated after the binData parameter.
*/
processAsUInt8Array : function(binData, processor, desperate)
{
var uInt8Array;
if (window.Blob && binData instanceof Blob) // No Blob on IE (9-), and no FileReader, by the way.
{
var fileReader = new FileReader();
fileReader.onload = function()
{
var ba = this.result;
uInt8Array = new Uint8Array(ba);
processor(uInt8Array);
};
setTimeout( function() { fileReader.readAsArrayBuffer(binData); }, 0);
}
else if (!window.Blob && binData instanceof Array) // Smells like IE...
{
uInt8Array = binData;
processor(uInt8Array);
}
else if (binData instanceof Uint8Array)
{
uInt8Array = binData;
processor(uInt8Array);
}
else if ((window.ArrayBuffer && binData instanceof ArrayBuffer) || (!window.ArrayBuffer && typeof(binData) !== "string"))
{
uInt8Array = new Uint8Array(binData);
processor(uInt8Array);
}
else // Dont know what to do
{
if (desperate)
desperate();
else
console.log("Don't know what to do with this payload [" + binData + "]");
}
}
},
baseXXencode : function(prm)
{
if (OraSocketGlobal.encodingBase === 16)
// return OraSocketGlobal.FramingUtils.binaryString(OraSocketGlobal.GnlUtils.base16encode(prm));
return OraSocketGlobal.GnlUtils.base16encode(prm);
else if (OraSocketGlobal.encodingBase === 64)
return OraSocketGlobal.GnlUtils.base64encode(prm);
else
throw new Error("Unsupported encoding");
},
baseXXdecode : function(prm)
{
if (OraSocketGlobal.encodingBase === 16)
return OraSocketGlobal.FramingUtils.binaryString(OraSocketGlobal.GnlUtils.base16decode(prm));
else if (OraSocketGlobal.encodingBase === 64)
return OraSocketGlobal.GnlUtils.base64decode(prm);
else
throw new Error("Unsupported encoding");
}
};
/**
* This namespace contains utilities for configuring the fallback behavior of a <code>WebSocket</code> object
* and displaying the resulting configuration.
* @namespace OraSocket
*/
var OraSocket =
{
ORASOCKET_PATH : "scripts/orasocket.js",
ORASOCKET_MIN_PATH : "scripts/orasocket.min.js",
baseUrl: ".", // Default location, where is the 'scripts' directory
enforcedTransport: "", // Used to enforce a transport. Can be WebSocket or XMLHttpRequest.
/**
* Configure the fallback behavior of a <code>WebSocket</code> object.
* <p>
* This function must be called <b>before</b> the <code>WebSocket</code> object is created. To ensure that the
* application does not fail if the <code>orasocket.js</code> library is unavailable , this function should be
* called in a <code>try/catch</code> block.
* </p>
* <p>
* This function takes a JSON object as a parameter, as shown in the following example:
* <pre>
var customConfig =
{
baseUrl: "ws-dir",
PING_INTERVAL: 1000,
ENCODE_FOR_IE_BELOW: 10,
WEBSOCKET_CREATION_TIMEOUT:1000,
NB_TRY_FOR_EACH_TRANSPORT: 2,
TRY_AGAIN_INTERVAL: 10000,
SERVER_PING_ENABLED: true,
ENFORCE_ENCODING: false
};
* </pre>
* </p>
* <p>
* For more information about this object, see “Parameters�?.
* </p>
* <p>
* The following example shows how to pass the JSON object <code>customConfig</code> from the previous
* example to the <code>configure</code> function:
* <pre>
try { OraSocket.configure(customConfig); } catch (err) {}
var ws = new WebSocket("ws://machine:8080/websocket/the-coolest-wsapp");
* </pre>
* The code in this example can be used in any JavaScript <code>WebSocket</code> client. The function call to
* create the <code>WebSocket</code> object conforms to the syntax in the W3C WebSocket specification. Calling
* the <code>configure</code> function in a <code>try/catch</code> block ensures that the application continues to run even if
* the <code>orasocket.js</code> library is unavailable.
* </p>
* <p>
* The following example shows how pass the JSON object <code>customConfig</code> as a parameter to the
* constructor of the <code>WebSocket</code> object:
* <pre>
var ws = new WebSocket("ws://machine:8080/websocket/the-coolest-wsapp", customConfig);
* </pre>
* The code in this example can be used only in JavaScript <code>WebSocket</code> clients to which the
* <code>orasocket.js</code> library is available. Otherwise, any application that contains this code fails.
* </p>
*
* @function configure
* @memberof OraSocket
* @param {object} configObject A JSON structure that contains the members in the following table
* <table>
* <tr><th>Name</th><th>Type</th><th>Default</th><th>Description</th></tr>
* <tr><td><code>baseUrl</code></td><td>string</td><td>"."</td><td>The location of the <code>scripts</code> directory, relative to the HTML context of the page.</td></tr>
* <tr><td><code>PING_INTERVAL</code></td><td>integer</td><td>25000</td><td>Interval in milliseconds between consecutive pings to the server.</td></tr>
* <tr><td><code>ENCODE_FOR_IE_BELOW</code></td><td>integer</td><td>10</td><td>The version of the Internet Explorer browser below which Base16 encoding is to be used for framed data.</td></tr>
* <tr><td><code>NB_TRY_FOR_EACH_TRANSPORT</code></td><td>integer</td><td>2</td><td>The maximum number of consecutive retries to establish a connection on a given transport.</td></tr>
* <tr><td><code>TRY_AGAIN_INTERVAL</code></td><td>integer</td><td>10000</td><td>The number of milliseconds after which an unsuccessful connection attempt is repeated with the same transport. The retry count for the transport is <b>not</b> incremented. <br>If the attempt fails within this number of milliseconds, the retry count is incremented by 1.</td></tr>
* <tr><td><code>ENFORCE_ENCODING</code></td><td>Boolean</td><td>false</td><td>Whether Base16 encoding should be used.</td></tr>
* <tr><td><code>SERVER_PING_ENABLED</code></td><td>Boolean</td><td>true</td><td>Whether pings from the client to the server are enabled.</td></tr>
* <tr><td><code>WEBSOCKET_CREATION_TIMEOUT</code></td><td>integer</td><td>1000</td><td>The number of milliseconds after which creation of a WebSocket connection is considered to have failed.</td></tr>
* <tr><td><code>debug</code></td><td>integer</td><td>0</td><td>The debug level.</td></tr>
* <tr><td><code>transport</code></td><td>string</td><td>none</td><td>The enforced transport, which can be one of the following transports: <ul><li><code>WebSocket</code></li><li><code>XMLHttpRequest</code></li></ul></td></tr>
* </table>
* Every member has a default value. No member is required.
* @returns {void}
* @inner
*/
configure: function(configObject)
{
if (configObject.baseUrl !== undefined)
OraSocket.baseUrl = configObject.baseUrl;
if (configObject.PING_INTERVAL !== undefined)
OraSocketGlobal.PING_INTERVAL = configObject.PING_INTERVAL;
if (configObject.ENCODE_FOR_IE_BELOW !== undefined)
OraSocketGlobal.ENCODE_FOR_IE_BELOW = configObject.ENCODE_FOR_IE_BELOW;
if (configObject.WEBSOCKET_CREATION_TIMEOUT !== undefined)
OraSocketGlobal.WEBSOCKET_CREATION_TIMEOUT = configObject.WEBSOCKET_CREATION_TIMEOUT;
if (configObject.NB_TRY_FOR_EACH_TRANSPORT !== undefined)
OraSocketGlobal.NB_TRY_FOR_EACH_TRANSPORT = configObject.NB_TRY_FOR_EACH_TRANSPORT;
if (configObject.TRY_AGAIN_INTERVAL !== undefined)
OraSocketGlobal.TRY_AGAIN_INTERVAL = configObject.TRY_AGAIN_INTERVAL;
if (configObject.SERVER_PING_ENABLED !== undefined)
OraSocketGlobal.PING_ENABLED = configObject.SERVER_PING_ENABLED;
if (configObject.ENFORCE_ENCODING !== undefined)
OraSocketGlobal.ENFORCE_ENCODING = configObject.ENFORCE_ENCODING;
if (configObject.debug !== undefined)
OraSocketGlobal.debugLevel = configObject.debug;
if (configObject.transport !== undefined)
OraSocket.enforcedTransport = configObject.transport;
},
/**
* Display the configuration of a <code>WebSocket</code> object's fallback behavior. This function uses the <code>console.log</code> function.
* <p>
* This function can be called from debugging tools such as WebKit and similar tools.
* </p>
* The following example shows the output from this function:
* <pre>
Base URL :.
Ping interval :1000 ms
Encoding required for IE below version:10
WebSocket creation timeout :1000 ms
Nb try for each transport :2 time(s)
Try-again interval :10000 ms
Ping enabled :true
Enforce encoding :false
Debug level :0
Enforced transport :none
* </pre>
*
* @function displayConfig
* @memberof OraSocket
* @see {@link OraSocket} for <code>OraSocket.configure</code>
* @see {@link WebSocket}
* @returns {void}
* @inner
*/
displayConfig: function()
{
console.log("Base URL :" + OraSocket.baseUrl);
console.log("Ping interval :" + OraSocketGlobal.PING_INTERVAL + " ms");
console.log("Encoding required for IE below version:" + OraSocketGlobal.ENCODE_FOR_IE_BELOW);
console.log("WebSocket creation timeout :" + OraSocketGlobal.WEBSOCKET_CREATION_TIMEOUT + " ms");
console.log("Nb try for each transport :" + OraSocketGlobal.NB_TRY_FOR_EACH_TRANSPORT + " time(s)");
console.log("Try-again interval :" + OraSocketGlobal.TRY_AGAIN_INTERVAL + " ms");
console.log("Ping enabled :" + OraSocketGlobal.PING_ENABLED);
console.log("Enforce encoding :" + OraSocketGlobal.ENFORCE_ENCODING);
console.log("Debug level :" + OraSocketGlobal.debugLevel);
console.log("Enforced transport :" + ((OraSocket.enforcedTransport !== undefined && OraSocket.enforcedTransport.length > 0) ? OraSocket.enforcedTransport : "none"));
}
};
var RequestParameter =
{
names:
{
WS_ATTEMPT : "tyrus-ws-attempt",
REQUIRE_ENCODING : "tyrus-encoding-required",
CLIENT_OPERATION : "tyrus-client-operation",
CLIENT_ID : "tyrus-connection-id",
CLIENT_TRANSPORT : "tyrus-client-transport",
CORS_REQUIRED : "tyrus-cors-headers",
SUB_PROTOCOLS : "tyrus-sub-protocol",
UNIQUE_REQ_ID : "unique-req-id",
CHAR_ENCODED : "char-encoded"
},
values:
{
WS_HAND_SHAKE : "Hand-Shake", // Value for WS_ATTEMPT
NONE : "none", // value of CHAR_ENCODED
BASE_16 : "base16", // value of CHAR_ENCODED
BASE_64 : "base64" // value of CHAR_ENCODED
}
};
var WSOperation =
{
WAIT_FOR_INPUT : "tyrus-wait-for-input",
SEND_MESSAGE : "tyrus-send-message"
};
var SUPPORTED_TRANSPORTS = "tyrus-fallback-transports";
var MAY_ADD_WS_HEADER = "tyrus-may-add-ws-transports";
var getScriptPath = function(scriptPath)
{
var path = OraSocket.baseUrl;
if (scriptPath.startsWith('/') && path.endsWith('/'))
path = path + '.' + scriptPath;
else if (!scriptPath.startsWith('/') && !path.endsWith('/'))
path = path + '/' + scriptPath;
else
path += scriptPath;
if (OraSocketGlobal.debugLevel > 10)
console.log("[" + scriptPath + "] becomes\n[" + path + "] with\n[" + OraSocket.baseUrl + "]");
return path;
};
/*
* That one is pretty ugly.
* This is the last resort...
*/
var getScriptPathFromDOM = function()
{
var scriptPath = "";
if (document.head !== undefined)
{
var scripts = document.head.getElementsByTagName("script");
for (var i=0; i<scripts.length; i++)
{
if (scripts[i].src !== undefined)
{
if (scripts[i].src.endsWith(OraSocket.ORASOCKET_PATH))
{
var src = scripts[i].src;
scriptPath = src.substring(0, src.length - OraSocket.ORASOCKET_PATH.length);
break;
}
else if (scripts[i].src.endsWith(OraSocket.ORASOCKET_MIN_PATH))
{
var src = scripts[i].src;
scriptPath = src.substring(0, src.length - OraSocket.ORASOCKET_MIN_PATH.length);
break;
}
}
}
}
else
{
var scripts = document.getElementsByTagName('head')[0].getElementsByTagName("script");
for (var i=0; i<scripts.length; i++)
{
if (scripts[i].src !== undefined)
{
if (scripts[i].src.endsWith(OraSocket.ORASOCKET_PATH))
{
var src = scripts[i].src;
scriptPath = src.substring(0, src.length - OraSocket.ORASOCKET_PATH.length);
break;
}
else if (scripts[i].src.endsWith(OraSocket.ORASOCKET_MIN_PATH))
{
var src = scripts[i].src;
scriptPath = src.substring(0, src.length - OraSocket.ORASOCKET_MIN_PATH.length);
break;
}
}
}
}
return scriptPath;
};
var importScript = function(scriptURL, callback)
{
var script = document.createElement('script');
var src = getScriptPath(scriptURL);
if (script.readyState) // IE
{
script.onreadystatechange = function()
{
if (script.readyState === "loaded" || script.readyState === "complete")
{
script.onreadystatechange = null;
if (OraSocketGlobal.debugLevel > 2)
console.log(src + " loaded.");
if (callback)
{
callback();
if (OraSocketGlobal.debugLevel > 2)
console.log("Callback after " + src + " completed.");
}
}
};
}
else // Others
{
script.onload = function()
{
if (OraSocketGlobal.debugLevel > 10)
console.log(src + " loaded.");
if (callback)
{
callback();
if (OraSocketGlobal.debugLevel > 10)
console.log("Callback after " + src + " completed.");
}
};
script.onerror = function(err)
{
console.log(err.toString());
};
}
script.src = src;
script.type = "text/javascript";
if (document.head !== undefined)
{
// 1 - Do we have one already?
var scripts = document.head.getElementsByTagName("script");
for (var i=0; i<scripts.length; i++)
{
// console.log("Comparing [" + scriptURL + "] with [" + scripts[i].src + "]");
if (scripts[i].src !== undefined && scripts[i].src.endsWith(scriptURL))
{
// console.log("--- " + scriptURL + " is already there.");
document.head.removeChild(scripts[i]);
break;
}
}
document.head.appendChild(script);
}
else
{
// 1 - Do we have one already?
var scripts = document.getElementsByTagName('head')[0].getElementsByTagName("script");
for (var i=0; i<scripts.length; i++)
{
// console.log("Comparing [" + scriptURL + "] with [" + scripts[i].src + "]");
if (scripts[i].src !== undefined && scripts[i].src.endsWith(scriptURL))
{
console.log("--- Removing " + scriptURL + ", already there.");
document.getElementsByTagName('head')[0].removeChild(scripts[i]);
break;
}
}
document.getElementsByTagName('head')[0].appendChild(script); // IE8...
}
};
var loadJSON = function(filePath)
{
var json;
try { json = loadJSONFile(getScriptPath(filePath), "application/json"); }
catch (err)
{
// Misplaced?
var pathTrial = OraSocket.baseUrl;
OraSocket.configure({ baseUrl: getScriptPathFromDOM() });
try
{
json = loadJSONFile(getScriptPath(filePath), "application/json");
console.log("Enforced baseUrl to [" + OraSocket.baseUrl + "], was [" + pathTrial + "]");
}
catch (err)
{
// Definitely not...
alert("Cannot find " + filePath + "\nTried:\n - " + pathTrial + "\n - " + OraSocket.baseUrl);
}
}
return JSON.parse(json);
};
var loadJSONFile = function(filePath, mimeType)
{
//console.log("Loading JSON data from " + filePath);
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", filePath, false);
if (mimeType !== null && mimeType !== undefined)
{
if (xmlhttp.overrideMimeType)
{
xmlhttp.overrideMimeType(mimeType);
}
}
xmlhttp.send();
if (xmlhttp.status === OraSocketGlobal.HTTP_OK)
{
return xmlhttp.responseText;
}
else
{
// Throw exception
if (xmlhttp.status === OraSocketGlobal.HTTP_SERVER_ERROR ||
xmlhttp.status === OraSocketGlobal.HTTP_ERROR_2)
throw {err: "Not Found"};
return null; // ta mere.
}
};
// Check availabilities, initializations, prototyping, etc.
var orasocketInit = function()
{
//console.log("Initializing orasocket");
if (typeof String.prototype.startsWith !== 'function')
{
String.prototype.startsWith = function (str)
{
return this.indexOf(str) === 0;
};
}
if (typeof String.prototype.endsWith !== 'function')
{
String.prototype.endsWith = function(suffix)
{
return this.indexOf(suffix, this.length - suffix.length) !== -1;
};
}
OraSocketGlobal.wsAvailable = window.WebSocket || window.MozWebSocket;
if (OraSocketGlobal.wsAvailable);
{
OraSocketGlobal.$nativeWS = window.WebSocket;
// if (!OraSocketGlobal.$nativeWS)
if (window.MozWebSocket)
{
if (OraSocketGlobal !== undefined && OraSocketGlobal.debugLevel > 5)
console.log("-- Using MozWebSocket.");
OraSocketGlobal.$nativeWS = window.MozWebSocket;
}
}
OraSocketGlobal.xhrAvailable = window.XMLHttpRequest;
OraSocketGlobal.flashSupported = OraSocketGlobal.isFlashSupported();
// For IE (when the debug console is closed, sucker)...
var alertFallback = false;
var statusFallback = true;
if (window.console === undefined || window.console.log === undefined || window.console.debug === undefined || window.console.warn === undefined)
{
if (window.console === undefined || window.console.log === undefined)
{
console = {};
if (alertFallback || statusFallback)
{
console.log = function(msg)
{
if (alertFallback)
alert(msg);
if (statusFallback)
{
if (window.status !== undefined)
window.status= msg;
else
alert("window.status not available (display your status bar...), switching to alert.\n" + msg);
}
};
}
else
console.log = function() {}; // Noting. No error, but just nothing.
}
if (window.console.debug === undefined || window.console.warn === undefined)
{
if (OraSocketGlobal !== undefined && OraSocketGlobal.debugLevel > 5)
console.log("Redifining console.warn & console.debug.");
console.warn = console.log;
console.debug = console.log;
}
}
if (window.Uint8Array === undefined)
{
var subarray = function(start, end)
{
return this.slice(start, end);
};
var set_ = function(array, offset)
{
if (arguments.length < 2) offset = 0;
for (var i=0, n=array.length; i<n; ++i, ++offset)
{
this[offset] = array[i] & 0xFF;
}
};
var TypedArray = function(arg1)
{
var result;
if (typeof arg1 === "number")
{
result = new Array(arg1);
for (var i=0; i<arg1; ++i)
{
result[i] = 0;
}
}
else
result = arg1.slice(0);
result.subarray = subarray;
result.buffer = result;
result.byteLength = result.length;
result.set = set_;
if (typeof arg1 === "object" && arg1.buffer)
result.buffer = arg1.buffer;
return result;
};
window.Uint8Array = TypedArray;
window.Uint32Array = TypedArray;
window.Int32Array = TypedArray;
}
// if (typeof btoa === 'undefined')
// {
// var btoa = function(str) { return OraSocketGlobal.GnlUtils.base64encode(str); }
// }
// if (typeof atob === 'undefined')
// {
// var atob = function(str) { return OraSocketGlobal.GnlUtils.base64decode(str); }
// }
// Populate the dynamic fallback order sequence, and other config stuff.
//OraSocketGlobal.fallbackOrder = [OraSocketGlobal.NATIVE_WEB_SOCKET, OraSocketGlobal.XML_HTTP_REQUEST]; // , OraSocketGlobal.JSONP];
OraSocketGlobal.fallbackOrder = [OraSocketGlobal.NATIVE_WEB_SOCKET, OraSocketGlobal.FLASH_SOCKET, OraSocketGlobal.XML_HTTP_REQUEST]; // , OraSocketGlobal.JSONP];
OraSocketGlobal.requestQueue = new OraSocketGlobal.Queue();
};
(function() { orasocketInit(); })();
/**
* WebSocket constructor.
* <p>
* This object is instantiated by the client.
* It has the same features as a native <code>WebSocket</code> object.
* Fallback to an alternative transport mechanism is invisible to users of the client.
* </p>
* <p>
* Optionally, the behavior of the JavaScript client can be configured through either of the following items:
* <ul>
* <li>The <code>optionalConfig</code> parameter of this constructor</li>
* <li>The <code>OraSocket.configure</code> function</li>
* </ul>
* For more information, see the description of the OraSocket configure function.
* </p>
* @see {@link OraSocket} for <code>OraSocket.configure</code>
* @constructor
* @param {string} serverURI Required. The URI of the server WebSocket endpoint to which the client should connect. The format of the URI is as follows:<br>
* <i><code>scheme://host[:port]/ws-application/path</code></i>
* <dl>
* <dt><i>scheme</i></dt>
* <dd>
* The URI scheme, which is either <code>ws</code> or <code>wss</code>.
* <ul>
* <li>The <code>ws</code> scheme represents an unencrypted WebSocket connection.</li>
* <li>The <code>wss</code> scheme represents an encrypted WebSocket connection.</li>
* </ul>
* </dd>
* <dt><i>host</i></dt>
* <dd>The host on which the server application is running.</dd>
* <dt><i>port</i></dt>
* <dd>
* Optional. The port on which the server listens for client requests.<br>
* The default port number is 80 for unencrypted connections and 443 for encrypted connections.
* </dd>
* <dt><i>ws-application</i></dt>
* <dd>The name with which the WebSocket server application is deployed.</dd>
* <dt><i>path</i></dt>
* <dd>The location of the server WebSocket endpoint within the server.</dd>
* </dl>
* @param {(string|string[])} [subProtocol] Optional. A string or an array of strings that contains one or more subprotocols to negotiate.<br>
* @param {object} [optionalConfig] Optional. A JSON Object that contains properties for configuring WebSocket fallback.<br>
* Specify this parameter only if the fallback mechanism is guaranteed to be present. Otherwise, use the <code>OraSocket.configure</code> function to set properties for configuring WebSocket fallback.<br>
* The format of this parameter is identical to the format of the object to pass to the <code>OraSocket.configure</code> function.
*
* @returns {WebSocket}
*
*/
var WebSocket = function(serverURI)
{
this.protocol = "";
this.extensions = "";
this.bufferedAmount = 0;
this.readyState = 0;
this.binaryType = "";
var preferredTransport, scheme, secured, machine, port, query;
var uri = serverURI;
var self = this;
var connectionTrialStatus;
//orasocketInit();
/**
* Set the debug level, which determines the amount of debugging information that is displayed on
* the console. A higher debug level specifies that more information is displayed.<br>
* <strong>Note:</strong> This function is not part of the WebSocket specification. It should be called within a
* <code>try/catch</code> block.
* @function setDebugLevel
* @param {int} dl The debug level in the range 0-200. A value of 0 specifies that no debugging information is displayed.
* @memberof WebSocket
* @returns {void}
* @inner
*/
this.setDebugLevel = function(dl)
{
OraSocketGlobal.debugLevel = dl;
};
var constructorOptions = {};
/**
* Get the configuration object, if any, that was passed as the <code>optionalConfig</code> parameter when this WebSocket object was created.
* <p>
* This function takes no parameters.
* </p>
* <strong>Note:</strong> This function is not part of the WebSocket specification. It should be called within a <code>try/catch</code> block.
* @function getConstructorOptions
* @memberof WebSocket
* @returns {object}
* @see The {@link WebSocket} constructor.
* @see The {@link OraSocket} <code>configure</code> function.
* @inner
*/
this.getConstructorOptions = function()
{
return constructorOptions;
};
/**
* Get the URI that was passed as the <code>serverURI</code> parameter when this <code>WebSocket</code> object was created.
* <p>
* This function takes no parameters.
* </p>
* <strong>Note:</strong> This function is not part of the WebSocket specification. It should be called within a <code>try/catch</code> block.
* @function getServerURI
* @memberof WebSocket
* @returns {object}
* @see The {@link WebSocket} constructor.
* @inner
*/
this.getServerURI = function()
{
return uri;
};
/**
* Get a JSON object that contains information about the status of a connection attempt.
* <p>
* This function takes no parameters.
* </p>
* <strong>Note:</strong> This function is not part of the WebSocket specification. It should be called within a <code>try/catch</code> block.<br>
* This status depends on the fallback requirements and options. It is returned as a JSON object.
* @see the {@link WebSocket} constructor.
* @function getConnectionTrialStatus
* @memberof WebSocket
* @returns {object} A JSON object in the following format:
* <pre>
* {
* transport: "{transport}",
* timestamp: {timestamp},
* nbshot: {nbshot},
* actual: {actual}
* }
* </pre>
* <dl>
* <dt><i>transport</i></dt>
* <dd>A <code>string</code> that denotes the transport currently used.</dd>
* <dt><i>timestamp</i></dt>
* <dd>A <code>long</code> that represents the time at which the information was obtained.</dd>
* <dt><i>nbshot</i></dt>
* <dd>An <code>int</code> that represents the maximum number of consecutive retries to establish a connection on a given transport.</dd>
* <dt><i>actual</i></dt>
* <dd>
* An <code>int</code> that represents the number of times the connection has been <i>actually</i> reset.
* <!--For those two last elements semantic, refer to the <code>NB_TRY_FOR_EACH_TRANSPORT</code> and <code>TRY_AGAIN_INTERVAL</code> members of the
* JSON object, parameter of <code>OraSocket.configure</code>.-->
* </dd>
* </dl>
* @inner
*/
this.getConnectionTrialStatus = function()
{
return connectionTrialStatus;
};
this.setConnectionTrialStatus = function(cts)
{
connectionTrialStatus = cts;
};
var manageDynamicFallbackStatus = function()
{
var now = (new Date().getTime());
if (connectionTrialStatus !== undefined)
{
if (self.getTransport() !== undefined && connectionTrialStatus.transport === self.getTransport())
{
var interval = (now - connectionTrialStatus.timestamp);
if (OraSocketGlobal !== undefined && OraSocketGlobal.debugLevel > 10)
console.debug('... Connection (' + self.getTransport() + ') re-requested after ' + interval + " ms (" + OraSocketGlobal.TRY_AGAIN_INTERVAL + "). nbshot=" + connectionTrialStatus.nbshot);
if (interval < OraSocketGlobal.TRY_AGAIN_INTERVAL)
{
connectionTrialStatus.nbshot++;
}
else
{
connectionTrialStatus.nbshot = 1; // Reset, to give more subsequent chances. That was just a hiccup.
}
connectionTrialStatus.actual++;
if (OraSocketGlobal !== undefined && OraSocketGlobal.debugLevel > 5)
console.log(" ... In manageDynamicFallbackStatus, actual is now " + connectionTrialStatus.actual);
connectionTrialStatus.timestamp = now;
}
else
{
connectionTrialStatus.timestamp = now;
}
}
else
{ // then create the object
connectionTrialStatus = {
transport: (self.getTransport() !== undefined ? self.getTransport() : "WebSocket"),
timestamp: now,
nbshot: 1,
actual: 1
};
if (OraSocketGlobal !== undefined && OraSocketGlobal.debugLevel > 5)
console.log(" ... In manageDynamicFallbackStatus (init), actual is now " + connectionTrialStatus.actual);
}
};
var dynamicFallbackOnError = function(error)
{
if (OraSocketGlobal.debugLevel > 10)
console.debug("Transport is (was) " + self.getTransport() + " -> " + JSON.stringify(error));
// console.log("Fallback Order:");
// for (var i=0; i<OraSocketGlobal.fallbackOrder.length; i++)
// console.log("-> " + OraSocketGlobal.fallbackOrder[i]);
// console.log("Available transports:");
// for (var i=0; i<OraSocketGlobal.availableTransports.length; i++)
// console.log("-> " + OraSocketGlobal.availableTransports[i]);
// Find index of current transport in the fallback list
var currentTransportIdx = -1;
for (var i=0; i<OraSocketGlobal.fallbackOrder.length; i++)
{
if (OraSocketGlobal.fallbackOrder[i] === self.getTransport())
{
currentTransportIdx = i;
break;
}
}
if (currentTransportIdx > -1)
{
// Find next transport for fallback
if (currentTransportIdx < OraSocketGlobal.fallbackOrder.length)
{
var next = 0;
var goodToGo = true;
if (connectionTrialStatus !== undefined)
{
if (connectionTrialStatus.nbshot <= OraSocketGlobal.NB_TRY_FOR_EACH_TRANSPORT)
next = 0; // retry the same
else
next = 1; // Next one.
}
else
console.warn(".....................connectionTrialStatus is not defined (weird).");
var newTransport = OraSocketGlobal.fallbackOrder[currentTransportIdx + next];
if (newTransport === undefined) // Last transport, beyond last chance
{
newTransport = "no more.";
goodToGo = false;
}
var errMess = (self.getTransport() + " transport just failed [" + error.err + "] (" + connectionTrialStatus.nbshot.toString() + " time" + (connectionTrialStatus.nbshot > 1 ? "s" : "") +
((connectionTrialStatus.actual === connectionTrialStatus.nbshot) ? "" : ", actually " + connectionTrialStatus.actual.toString()) + "), " + (next === 0 ? ("retrying (after " + ((new Date().getTime() - connectionTrialStatus.timestamp) / 1000) + " s)") : "trying " + newTransport));
self.onerror({ err: errMess });
if (goodToGo)
{
if (next > 0)
{
connectionTrialStatus.nbshot = 1; // Reset. Will be incremented to 1 in manageDynamicFallbackStatus
connectionTrialStatus.transport = newTransport;
self.setTransport(newTransport);
}
var constructorOptions = self.getConstructorOptions();
// var serverURI = self.getServerURI();
constructorOptions.transport = newTransport;
var webSocketCreated = false;
var watcherID, workerID;
var watcher = function(cb)
{
if (webSocketCreated)
cb("WebSocket (" + newTransport + ") re-created Ok"); // This one should actually never show up. If successfully created, the worker cancels the watcher.
else
{
cb("WebSocket (" + newTransport + ") NOT re-created within timeout...");
clearTimeout(workerID);
dynamicFallbackOnError({ err: "Creation timeout [" + OraSocketGlobal.WEBSOCKET_CREATION_TIMEOUT + " ms], at " + (new Date().getTime()).toString() });
}
};
var worker = function(cb, onok)
{
cb("... Re-creating WebSocket (from worker)");
try
{
// if (OraSocketGlobal !== undefined && OraSocketGlobal.debugLevel > 5)
// console.log(" ... Invoking manageDynamicFallbackStatus from Worker");
// manageDynamicFallbackStatus(); // That will be done in the onopen function.
try
{
self.quit(); // Mostly to stop the ping
}
catch (err)
{
console.warn("Quit errored:" + JSON.stringify(err));
}
loadInterface();
webSocketCreated = true;
clearTimeout(watcherID);
cb("WebSocket re-created successfully with transport " + self.getTransport());
onok(self);
}
catch (err)
{
cb(" ---> Error re-creating WebSocket:" + JSON.stringify(err));
}
};
var workAndWatch = function(work, watch, timeout, callback, onSuccessfulCreation)
{
watcherID = setTimeout(function() { watch(callback); }, timeout);
workerID = setTimeout(function() { work(callback, onSuccessfulCreation); }, 0);
};
var callback = function(str)
{
if (OraSocketGlobal.debugLevel > 10)
console.log(str);
};
var onSuccess = function(prm)
{
// self.onopen(); // Will be invoked when WebSocket is created.
if (OraSocketGlobal.debugLevel > 5)
console.debug(" Created in time. (onSuccess)");
};
workAndWatch(worker, watcher, OraSocketGlobal.WEBSOCKET_CREATION_TIMEOUT, callback, onSuccess);
if (constructorOptions.debug !== undefined)
self.setDebugLevel(constructorOptions.debug);
}
else
{
self.onerror({ err: "All transport options exhausted. You are not connected to the server." });
self.onclose();
}
}
}
else
self.onerror({ err: "Current transport NOT FOUND in the available transport list"});
};
var transport, instanceName, subprotocols;
for (var i=0; i<arguments.length; i++)
{
// console.log("Type of Arg[" + i + "] is " + typeof(arguments[i]));
if (i > 0)
{
if (typeof(arguments[i]) === 'string')
{
subprotocols = [ arguments[i] ];
}
else if ((Array.isArray !== undefined && Array.isArray(arguments[i])) ||
(Array.isArray === undefined /* IE8 */ && arguments[i] instanceof Array)) // Subprotocols?
{
// console.log("Subprotocols detected.");
subprotocols = arguments[i];
// Make sure subprotocols are not duplicated (Bug #18620190)
if (subprotocols.length > 1)
{
var dup = false;
var errmess = "";
for (var p=0; p<subprotocols.length - 1; p++)
{
for (var d=p+1; d<subprotocols.length; d++)
{
if (subprotocols[p] === subprotocols[d])
{
dup = true;
errmess = "SubProtocol [" + subprotocols[d] + "] duplicated";
break;
}
}
if (dup)
break;
}
if (dup)
{
throw new SyntaxError(errmess);
}
}
}
else if (typeof(arguments[i]) === 'object')
{
OraSocket.configure(arguments[i]);
if (OraSocket.enforcedTransport !== undefined && OraSocket.enforcedTransport.length > 0)
{
transport = OraSocket.enforcedTransport;
preferredTransport = transport;
}
// if (arguments[i].transport !== undefined) // enforcing a transport
// {
// transport = arguments[i].transport;
// preferredTransport = transport;
// }
// if (arguments[i].debug !== undefined) // debug level, beginning st once.
// this.setDebugLevel(arguments[i].debug);
// if (arguments[i].instName !== undefined) // instance name in the main script (for JSON)
// instanceName = arguments[i].instName;
constructorOptions = arguments[i]; // TODO Make sure there is only one (object).
}
}
}
if (OraSocketGlobal.debugLevel > 1)
console.debug("WebSocket constructor invoked, transport is " + transport);
/**
* Callback function that is called when the client receives a message from the server.
* <br>
* Typically, the client-side logic of a WebSocket application is defined in this function.
* @function
* @param {object} mess The message received by the client.
* @returns {void}
*/
this.onmessage = function(mess) { console.log("Default onMessage:"); console.log(mess); };
/**
* Callback function that is called when a connection to the server is established.
* <p>
* This function takes no parameters.
* </p>
* @function
* @returns {void}
*/
this.onopen = function() { console.log('*** Default onopen'); };
/**
* Callback function that is called when a connection to the server is closed.
* <p>
* This function takes no parameters.
* </p>
* @function
* @returns {void}
*/
this.onclose = function() {};
/**
* Callback function that is called when a connection error occurs.
* @function
* @param {object} error The error that was raised.
* @returns {void}
*/
this.onerror = function(error){};
/**
* Send a message to the server within the context of the WebSocket application. This function is called from the client application.
* @function
* @param {object} mess The message to send to the server.
* @returns {void}
*/
this.send = function(mess){ /* This is here only for documentation purpose. Will be overriden. */ };
if (OraSocketGlobal.debugLevel > 10)
{
console.debug("Your browser " + (OraSocketGlobal.wsAvailable?"supports":"doesn't support") + " WebSockets.");
console.debug("Your browser " + (OraSocketGlobal.flashSupported?"supports":"doesn't support") + " Flash.");
console.debug("Your browser " + (OraSocketGlobal.xhrAvailable?"supports":"doesn't support") + " XMLHttpRequest.");
}
/*
* Invoked at the end of the handShake function, itself invoked after the first POST request (HandShake).
* This is the one loading the WebSocket object when possible, or flipping back to HTTPLongPoll (and others, when available)
* if necessary.
*
* Important: the onopen method must be called on completion.
*/
var loadInterface = function()
{
if (OraSocketGlobal.debugLevel > 10)
console.debug("Loading prototype functions...");
if (preferredTransport === OraSocketGlobal.NATIVE_WEB_SOCKET)
{
// Load scripts/transports/native/NativeWebSocket.js here
importScript("scripts/transports/native/NativeWebSocket.js",
function()
{
var backEndWebSocket = new NativeWebSocket(OraSocketGlobal.$nativeWS,
"ws" + secured + "://" + machine + ":" + port + "/" + query,
subprotocols);
if (OraSocketGlobal !== undefined && OraSocketGlobal.debugLevel > 5)
console.log(" ... Invoking manageDynamicFallbackStatus from NativeWebSocket");
manageDynamicFallbackStatus();
// backEndWebSocket.onopen = function() { self.onopen(); };
// backEndWebSocket.onclose = function() { self.onclose(); };
backEndWebSocket.onopen = self.onopen;
backEndWebSocket.onclose = self.onclose;
backEndWebSocket.onerror = function(error)
{
dynamicFallbackOnError(error);
// self.onerror(error);
};
// backEndWebSocket.onmessage = function(message) { self.onmessage(message); };
// self.send = function(mess) { backEndWebSocket.send(mess); };
backEndWebSocket.onmessage = self.onmessage;
self.send = backEndWebSocket.send;
self.close = function(code, reason)
{
// self.readyState = backEndWebSocket.getReadyState();
self.readyState = NativeWebSocketStatic.WebSocketReadyState.CLOSING;
backEndWebSocket.close(code, reason);
self.readyState = NativeWebSocketStatic.WebSocketReadyState.CLOSED;
};
self.quit = function() { backEndWebSocket.quit(); };
self.protocol = backEndWebSocket.getProtocol();
self.binaryType = backEndWebSocket.binaryType;
self.readyState = backEndWebSocket.getReadyState(); // Should be observed, see below. But seems not to work for Native WebSocket
var observerCallbackHandler = function(subject, property)
{
//Find out if a subject changed any of its properties
if (subject === backEndWebSocket)
{
if (OraSocketGlobal !== undefined && OraSocketGlobal.debugLevel > 5)
console.log("A property (" + property + ") changed on Native WebSocket");
//Do something if a specific property you're observing changes
if (property === "readyState")
{
if (OraSocketGlobal !== undefined && OraSocketGlobal.debugLevel > 5)
console.log("Its readyState property changed, now " + subject.readyState);
self.readyState = backEndWebSocket.readyState; // Propagate!
}
}
};
// observe(backEndWebSocket, "readyState", observerCallbackHandler.bind(this));
if (OraSocketGlobal.debugLevel > 5)
console.debug("End of Native WebSocket callback, invoking onopen");
setTimeout(function()
{
self.readyState = NativeWebSocketStatic.WebSocketReadyState.OPEN;
self.protocol = backEndWebSocket.getProtocol();
self.onopen();
}, 50);
});
}
else if (preferredTransport === OraSocketGlobal.FLASH_SOCKET)
{
console.log('FLASH: Work in progress');
}
else if (preferredTransport === OraSocketGlobal.XML_HTTP_REQUEST)
{
// Load scripts/transports/http/xhr/LongPoll.js here
importScript("scripts/transports/http/xhr/LongPoll.js",
function()
{
var backEndWebSocket = new LongPoll(OraSocketGlobal.clientID,
"http" + secured + "://" + machine + ":" + port + "/" + query,
subprotocols);
var bindSupported = true;
if (OraSocketGlobal !== undefined && OraSocketGlobal.debugLevel > 5)
console.log(" ... Invoking manageDynamicFallbackStatus from LongPoll");
manageDynamicFallbackStatus();
// backEndWebSocket.onopen = function() { self.onopen(); };
// backEndWebSocket.onclose = function() { self.onclose(); };
backEndWebSocket.onopen = self.onopen;
backEndWebSocket.onclose = self.onclose;
backEndWebSocket.onerror = function(error)
{
dynamicFallbackOnError(error);
// self.onerror(error);
};
// backEndWebSocket.onmessage = function(message) { self.onmessage(message); };
// self.send = function(mess) { backEndWebSocket.send(mess); };
backEndWebSocket.onmessage = self.onmessage;
self.send = backEndWebSocket.send;
self.close = function(code, reason)
{
if (!bindSupported)
self.readyState = LongPollStatic.CLOSING;
backEndWebSocket.close(code, reason);
if (!bindSupported)
self.readyState = LongPollStatic.CLOSED;
};
self.quit = function() { backEndWebSocket.quit(); };
self.protocol = backEndWebSocket.protocol; // TODO Get that one
self.binaryType = backEndWebSocket.binaryType;
// self.readyState = backEndWebSocket.readyState; // Should be observed
var observerCallbackHandler = function(subject, property)
{
//Find out if a subject changed any of its properties
if (subject === backEndWebSocket)
{
if (OraSocketGlobal !== undefined && OraSocketGlobal.debugLevel > 5)
console.log("A property (" + property + ") changed on LongPoll Emulation");
//Do something if a specific property you're observing changes
if (property === "readyState")
{
if (OraSocketGlobal !== undefined && OraSocketGlobal.debugLevel > 5)
console.log("Its readyState property changed, now " + subject.readyState);
self.readyState = backEndWebSocket.readyState; // Propagate!
}
else if (property === "protocol")
{
if (OraSocketGlobal !== undefined && OraSocketGlobal.debugLevel > 5)
console.log("Its protocol property changed, now " + subject.protocol);
self.protocol = backEndWebSocket.protocol; // Propagate!
}
}
};
try
{
observe(backEndWebSocket, "readyState", observerCallbackHandler.bind(this)); // Not suppported in IE8
observe(backEndWebSocket, "protocol", observerCallbackHandler.bind(this)); // Not suppported in IE8
}
catch (err)
{
console.log('Observing WebSocket...');
bindSupported = false;
}
if (OraSocketGlobal.debugLevel > 5)
console.debug("End of XHR callback, invoking onopen");
setTimeout(function()
{
self.readyState = LongPollStatic.OPEN;
if (backEndWebSocket.getProtocol() !== undefined && backEndWebSocket.getProtocol().length > 0)
self.protocol = backEndWebSocket.getProtocol();
self.onopen();
}, 50);
});
}
else if (preferredTransport === OraSocketGlobal.JSONP)
{
// Load scripts/transports/http/xhr/LongPoll.js here
importScript("scripts/transports/http/jsonp/JSONP.js",
function()
{
var backEndWebSocket = new JSONP(OraSocketGlobal.clientID,
"http" + secured + "://" + machine + ":" + port + "/" + query,
instanceName,
subprotocols);
if (OraSocketGlobal !== undefined && OraSocketGlobal.debugLevel > 5)
console.log(" ... Invoking manageDynamicFallbackStatus from JSONP");
manageDynamicFallbackStatus();
backEndWebSocket.onopen = self.onopen;
backEndWebSocket.onclose = self.onclose;
backEndWebSocket.onerror = function(error)
{
dynamicFallbackOnError(error);
// self.onerror(error);
};
backEndWebSocket.onmessage = self.onmessage;
WebSocket.prototype.send = backEndWebSocket.send;
WebSocket.prototype.close = function(code, reason) { backEndWebSocket.close(code, reason); };
WebSocket.prototype.parseJSONP = function(json, rnk) { backEndWebSocket.parseJSONP(json, rnk); };
WebSocket.prototype.quit = function() { backEndWebSocket.quit(); };
if (OraSocketGlobal.debugLevel > 5)
console.debug("End of JSONP callback, invoking onopen");
setTimeout(function() { self.onopen(); }, 50);
});
}
else
{
if (OraSocketGlobal.debugLevel > 0)
{
console.warn('Unmanaged transport:' + preferredTransport);
alert('Unmanaged transport:' + preferredTransport);
}
}
self.url = self.URL = serverURI;
// Extra functions
/**
* Get the transport that is being used.<br>
* <strong>Note:</strong> This function is not part of the WebSocket specification. It should be called within a <code>try/catch</code> block.
* @function getTransport
* @memberof WebSocket
* @returns {string} The transport that is being used.
* @inner
*/
self.getTransport = function() { return preferredTransport; };
/**
* Enforce the specified transport.<br>
* <strong>Note:</strong> This function is not part of the WebSocket specification. It should be called within a
* <code>try/catch</code> block.
* @function setTransport
* @memberof WebSocket
* @param {string} transport The transport to use, which <b>must</b> be one of the following transports:<ul><li><code>WebSocket</code></li><li><code>XMLHttpRequest</code></li></ul>
* @returns {void}
* @inner
*/
self.setTransport = function(str) { preferredTransport = str; };
// Just for tests...
self.onInitCompleted = function(mess)
{
console.log("WebSocket.onInitCompleted:" + mess);
};
if (OraSocketGlobal.debugLevel > 10)
console.debug("... Prototype functions loaded.");
};
/*
* Makes the first POST request (HandShake) that returns the list of the available transports and unique client-id.
* Will itself eventually invoke the loadInterface function.
* @param {string} serverURI the full http URL od the app on the server
* @returns {void}
*/
var handShake = function(serverURI)
{
if (OraSocketGlobal.debugLevel > 10)
console.debug("Sending first (POST) request to " + serverURI + "..." + (transport !== undefined ? ", enforcing " + transport : ""));
// var xhr = new XMLHttpRequest();
var xhr = new OraSocketGlobal.XMLHttpRequest();
var completed = false;
xhr.onreadystatechange = function()
{
if (xhr.readyState === OraSocketGlobal.XHR_REQUEST_COMPLETED && xhr.status === OraSocketGlobal.HTTP_OK)
{
// No response expected (empty string in xhr.responseText)
var headers = xhr.getAllResponseHeaders();
if (OraSocketGlobal.debugLevel > 10)
console.debug("All headers:\n" + headers);
var supportedTransports = xhr.getResponseHeader(SUPPORTED_TRANSPORTS);
if (OraSocketGlobal.debugLevel > 10)
console.debug("Fallback Transports:" + supportedTransports);
var transports = supportedTransports.split(",");
// Do we have WebSocket is the supported transport list? Add it if not.
var containsWS = false;
var mayAddWSValue = xhr.getResponseHeader(MAY_ADD_WS_HEADER);
if (mayAddWSValue !== "NO")
{
var mayAddWS = (mayAddWSValue !== "NO");
for (var i=0; mayAddWS && i<transports.length; i++)
{
if (transports[i] === OraSocketGlobal.NATIVE_WEB_SOCKET)
{
containsWS = true;
break;
}
}
if (!containsWS && mayAddWS)
transports.splice(0, 0, OraSocketGlobal.NATIVE_WEB_SOCKET);
}
OraSocketGlobal.availableTransports = transports;
for (var i=0; i<transports.length; i++)
{
if (OraSocketGlobal.wsAvailable && transports[i] === OraSocketGlobal.NATIVE_WEB_SOCKET &&
(transport === undefined || (transport !== undefined && transport === OraSocketGlobal.NATIVE_WEB_SOCKET)))
{
preferredTransport = transports[i];
break;
}
else if (OraSocketGlobal.xhrAvailable && transports[i] === OraSocketGlobal.XML_HTTP_REQUEST &&
(transport === undefined || (transport !== undefined && transport === OraSocketGlobal.XML_HTTP_REQUEST)))
{
preferredTransport = transports[i];
break;
}
else if (transports[i] === OraSocketGlobal.JSONP &&
(transport === undefined || (transport !== undefined && transport === OraSocketGlobal.JSONP)))
{
preferredTransport = transports[i];
break;
}
}
if (preferredTransport === undefined || preferredTransport.length === 0)
{
console.warn("No available transport can be used...");
alert("No available transport can be used...");
}
else if (OraSocketGlobal.debugLevel > 10)
console.debug("Preferred transport is " + preferredTransport);
// console.log("First POST: clientID = " + OraSocketGlobal.clientID);
if (JSON.stringify(OraSocketGlobal.clientID) === "{}") // Then create new, otherwise re-use
OraSocketGlobal.clientID = xhr.getResponseHeader(RequestParameter.names.CLIENT_ID);
if (OraSocketGlobal.debugLevel > 10)
console.debug("Client ID:" + OraSocketGlobal.clientID);
completed = true; // successfully
loadInterface();
}
};
// WARNING: a synchronous request on "localhost" would NEVER come back if the server is down...
serverURI = serverURI.replace("localhost", "127.0.0.1");
if (OraSocketGlobal.debugLevel > 10)
console.debug("First POST request (handshake) to:" + serverURI);
xhr.open("POST", serverURI, false); // false:Synchronously!!!
try
{
xhr.setRequestHeader(RequestParameter.names.WS_ATTEMPT, RequestParameter.values.WS_HAND_SHAKE); // Means return the transport list, and my unique ID
if (subprotocols !== undefined)
{
var protocolList = "";
if (typeof(subprotocols) === 'string')
protocolList = subprotocols;
else if (Array.isArray(subprotocols))
{
for (var i=0; i<subprotocols.length; i++)
protocolList += ((protocolList.length > 0 ? ", " : "") + subprotocols[i]);
}
xhr.setRequestHeader("tyrus-sub-protocol", protocolList); // TODO Agree on the header name
}
}
catch (err) { console.warn(err.toString()); }
try
{
xhr.send();
completed = true;
// clearTimeout(to);
}
catch (err)
{
var errMess = (err.stack !== undefined ? err.stack : JSON.stringify(err));
console.warn("First POST (aka HandShake):" + errMess);
self.onerror(err); // The onerror is the default one, client ones not set yet...
throw err;
}
};
if (OraSocketGlobal.debugLevel > 10)
console.debug("Parsing server URI: [" + serverURI + "]");
// open connection. Use document.baseURI for the machine and port, http://machine:port/etc/etc...
var regExp = new RegExp("^(http|ws)(.?):[/]{2}([^/|^:]*):?(\\d*)/(.*)");
var matches = regExp.exec(serverURI);
try
{
scheme = matches[1];
secured = matches[2];
machine = matches[3];
port = matches[4];
query = matches[5];
if (port === null || port.length === 0)
port = "80";
}
catch (err)
{
if (OraSocketGlobal.debugLevel > 10)
console.debug("Error parsing server URI: [" + serverURI + "]:" + err);
}
var fullScheme = "" + scheme + secured;
if (fullScheme.toUpperCase() !== "WS" && fullScheme.toUpperCase() !== "WSS")
{
throw new SyntaxError("The URL's scheme must be either 'ws' or 'wss'. 'http' is not allowed.");
}
handShake("http" + secured + "://" + machine + ":" + port + "/" + query);
if (OraSocketGlobal.debugLevel > 20)
console.log("Hand Shake with [" + serverURI + "] completed.");
};