Source: orasocket.js

/*
 * 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\xE2\x80?.
   * </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.");
};