diff --git a/AUTHORS b/AUTHORS
index 0d139781bb6fd..9e9d5748acb4e 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -78,4 +78,11 @@ a license to everyone to use it as detailed in LICENSE.)
 * James Gregory <jgregory@zynga.com> (copyright owned by Zynga, Inc)
 * Dan Gohman <sunfish@google.com> (copyright owned by Google, Inc.)
 * Jeff Gilbert <jgilbert@mozilla.com> (copyright owned by Mozilla Foundation)
+* Frits Talbot <frits@metapathy.com>
+* Onno Jongbloed <hey@onnoj.net>
+* Jez Ng <me@jezng.com>
+* Marc Feeley <mfeeley@mozilla.com> (copyright owned by Mozilla Foundation)
+* Ludovic Perrine <jazzzz@gmail.com>
+
+
 
diff --git a/emcc b/emcc
index 65dec978a3b36..2a7e10d0fcad6 100755
--- a/emcc
+++ b/emcc
@@ -539,7 +539,7 @@ if CONFIGURE_CONFIG or CMAKE_CONFIG:
         skip_next = False
         idx += 1
         continue
-      if el == '-s' and is_minus_s_for_emcc(argv, idx):
+      if not use_js and el == '-s' and is_minus_s_for_emcc(argv, idx): # skip -s X=Y if not using js for configure
         skip_next = True
       else:
         yield el
@@ -995,6 +995,7 @@ try:
     key, value = change.split('=')
     if value[0] == '@':
       value = '"@' + os.path.abspath(value[1:]) + '"'
+      value = value.replace('\\\\', '/').replace('\\', '/') # Convert backslash paths to forward slashes on Windows as well, since the JS compiler otherwise needs the backslashes escaped (alternative is to escape all input paths passing to JS, which feels clumsier to read)
     exec('shared.Settings.' + key + ' = ' + value)
 
   # Apply effects from settings
diff --git a/src/jsifier.js b/src/jsifier.js
index 77aff895bac5d..156fd65dee774 100644
--- a/src/jsifier.js
+++ b/src/jsifier.js
@@ -460,6 +460,7 @@ function JSify(data, functionsOnly, givenFunctions) {
         } else {
           ident = '_' + ident;
         }
+        if (VERBOSE) printErr('adding ' + ident + ' and deps ' + deps);
         var depsText = (deps ? '\n' + deps.map(addFromLibrary).filter(function(x) { return x != '' }).join('\n') : '');
         var contentText = isFunction ? snippet : ('var ' + ident + '=' + snippet + ';');
         if (ASM_JS) {
diff --git a/src/library.js b/src/library.js
index e65754ba3aee0..5779327f293ae 100644
--- a/src/library.js
+++ b/src/library.js
@@ -63,6 +63,36 @@ LibraryManager.library = {
     // This is set to false when the runtime is initialized, allowing you
     // to modify the filesystem freely before run() is called.
     ignorePermissions: true,
+    createFileHandle: function(stream, fd) {
+      if (typeof stream === 'undefined') {
+        stream = null;
+      }
+      if (!fd) {
+        if (stream && stream.socket) {
+          for (var i = 1; i < 64; i++) {
+            if (!FS.streams[i]) {
+              fd = i;
+              break;
+            }
+          }
+          assert(fd, 'ran out of low fds for sockets');
+        } else {
+          fd = Math.max(FS.streams.length, 64);
+          for (var i = FS.streams.length; i < fd; i++) {
+            FS.streams[i] = null; // Keep dense
+          }
+        }
+      }
+      // Close WebSocket first if we are about to replace the fd (i.e. dup2)
+      if (FS.streams[fd] && FS.streams[fd].socket && FS.streams[fd].socket.close) {
+        FS.streams[fd].socket.close();
+      }
+      FS.streams[fd] = stream;
+      return fd;
+    },
+    removeFileHandle: function(fd) {
+      FS.streams[fd] = null;
+    },
     joinPath: function(parts, forceRelative) {
       var ret = parts[0];
       for (var i = 1; i < parts.length; i++) {
@@ -326,24 +356,25 @@ LibraryManager.library = {
 #else
             var chunkSize = 1024*1024; // Chunk size in bytes
 #endif
+
             if (!hasByteServing) chunkSize = datalength;
-      
+
             // Function to get a range from the remote URL.
             var doXHR = (function(from, to) {
               if (from > to) throw new Error("invalid range (" + from + ", " + to + ") or no bytes requested!");
               if (to > datalength-1) throw new Error("only " + datalength + " bytes available! programmer error!");
-      
+
               // TODO: Use mozResponseArrayBuffer, responseStream, etc. if available.
               var xhr = new XMLHttpRequest();
               xhr.open('GET', url, false);
               if (datalength !== chunkSize) xhr.setRequestHeader("Range", "bytes=" + from + "-" + to);
-      
+
               // Some hints to the browser that we want binary data.
               if (typeof Uint8Array != 'undefined') xhr.responseType = 'arraybuffer';
               if (xhr.overrideMimeType) {
                 xhr.overrideMimeType('text/plain; charset=x-user-defined');
               }
-      
+
               xhr.send(null);
               if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status);
               if (xhr.response !== undefined) {
@@ -368,7 +399,7 @@ LibraryManager.library = {
             this._chunkSize = chunkSize;
             this.lengthKnown = true;
         }
-  
+
         var lazyArray = new LazyUint8Array();
         Object.defineProperty(lazyArray, "length", {
             get: function() {
@@ -560,6 +591,7 @@ LibraryManager.library = {
       var stdout = FS.createDevice(devFolder, 'stdout', null, output);
       var stderr = FS.createDevice(devFolder, 'stderr', null, error);
       FS.createDevice(devFolder, 'tty', input, output);
+      FS.createDevice(devFolder, 'null', function(){}, function(){});
 
       // Create default streams.
       FS.streams[1] = {
@@ -675,10 +707,9 @@ LibraryManager.library = {
       ___setErrNo(ERRNO_CODES.EACCES);
       return 0;
     }
-    var id = FS.streams.length; // Keep dense
     var contents = [];
     for (var key in target.contents) contents.push(key);
-    FS.streams[id] = {
+    var id = FS.createFileHandle({
       path: path,
       object: target,
       // An index into contents. Special values: -2 is ".", -1 is "..".
@@ -695,7 +726,7 @@ LibraryManager.library = {
       contents: contents,
       // Each stream has its own area for readdir() returns.
       currentEntry: _malloc(___dirent_struct_layout.__size__)
-    };
+    });
 #if ASSERTIONS
     FS.checkStreams();
 #endif
@@ -1213,7 +1244,7 @@ LibraryManager.library = {
       finalPath = path.parentPath + '/' + path.name;
     }
     // Actually create an open stream.
-    var id = FS.streams.length; // Keep dense
+    var id;
     if (target.isFolder) {
       var entryBuffer = 0;
       if (___dirent_struct_layout) {
@@ -1221,7 +1252,7 @@ LibraryManager.library = {
       }
       var contents = [];
       for (var key in target.contents) contents.push(key);
-      FS.streams[id] = {
+      id = FS.createFileHandle({
         path: finalPath,
         object: target,
         // An index into contents. Special values: -2 is ".", -1 is "..".
@@ -1238,9 +1269,9 @@ LibraryManager.library = {
         contents: contents,
         // Each stream has its own area for readdir() returns.
         currentEntry: entryBuffer
-      };
+      });
     } else {
-      FS.streams[id] = {
+      id = FS.createFileHandle({
         path: finalPath,
         object: target,
         position: 0,
@@ -1250,7 +1281,7 @@ LibraryManager.library = {
         error: false,
         eof: false,
         ungotten: []
-      };
+      });
     }
 #if ASSERTIONS
     FS.checkStreams();
@@ -1293,10 +1324,7 @@ LibraryManager.library = {
           newStream[member] = stream[member];
         }
         arg = dup2 ? arg : Math.max(arg, FS.streams.length); // dup2 wants exactly arg; fcntl wants a free descriptor >= arg
-        for (var i = FS.streams.length; i < arg; i++) {
-          FS.streams[i] = null; // Keep dense
-        }
-        FS.streams[arg] = newStream;
+        FS.createFileHandle(newStream, arg);
 #if ASSERTIONS
         FS.checkStreams();
 #endif
@@ -1773,12 +1801,14 @@ LibraryManager.library = {
       return bytesRead;
     }
   },
-  read__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'pread'],
+  read__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'recv', 'pread'],
   read: function(fildes, buf, nbyte) {
     // ssize_t read(int fildes, void *buf, size_t nbyte);
     // http://pubs.opengroup.org/onlinepubs/000095399/functions/read.html
     var stream = FS.streams[fildes];
-    if (!stream) {
+    if (stream && ('socket' in stream)) {
+      return _recv(fildes, buf, nbyte, 0);
+    } else if (!stream) {
       ___setErrNo(ERRNO_CODES.EBADF);
       return -1;
     } else if (!stream.isRead) {
@@ -1804,6 +1834,10 @@ LibraryManager.library = {
               ___setErrNo(ERRNO_CODES.EIO);
               return -1;
             }
+            if (result === undefined && bytesRead === 0) {
+              ___setErrNo(ERRNO_CODES.EAGAIN);
+              return -1;
+            }
             if (result === null || result === undefined) break;
             bytesRead++;
             {{{ makeSetValue('buf', 'i', 'result', 'i8') }}}
@@ -1969,12 +2003,14 @@ LibraryManager.library = {
       return i;
     }
   },
-  write__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'pwrite'],
+  write__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', 'send', 'pwrite'],
   write: function(fildes, buf, nbyte) {
     // ssize_t write(int fildes, const void *buf, size_t nbyte);
     // http://pubs.opengroup.org/onlinepubs/000095399/functions/write.html
     var stream = FS.streams[fildes];
-    if (!stream) {
+    if (stream && ('socket' in stream)) {
+        return _send(fildes, buf, nbyte, 0);
+    } else if (!stream) {
       ___setErrNo(ERRNO_CODES.EBADF);
       return -1;
     } else if (!stream.isWrite) {
@@ -2572,7 +2608,7 @@ LibraryManager.library = {
         if (format[formatIndex] == 'l') {
           long_ = true;
           formatIndex++;
-          if(format[formatIndex] == 'l') {
+          if (format[formatIndex] == 'l') {
             longLong = true;
             formatIndex++;
           }
@@ -2585,7 +2621,8 @@ LibraryManager.library = {
         var curr = 0;
         var buffer = [];
         // Read characters according to the format. floats are trickier, they may be in an unfloat state in the middle, then be a valid float later
-        if (type == 'f' || type == 'e' || type == 'g' || type == 'E') {
+        if (type == 'f' || type == 'e' || type == 'g' ||
+            type == 'F' || type == 'E' || type == 'G') {
           var last = 0;
           next = get();
           while (next > 0) {
@@ -2607,7 +2644,7 @@ LibraryManager.library = {
                 (type == 's' ||
                  ((type === 'd' || type == 'u' || type == 'i') && ((next >= {{{ charCode('0') }}} && next <= {{{ charCode('9') }}}) ||
                                                                    (first && next == {{{ charCode('-') }}}))) ||
-                 (type === 'x' && (next >= {{{ charCode('0') }}} && next <= {{{ charCode('9') }}} ||
+                 ((type === 'x' || type === 'X') && (next >= {{{ charCode('0') }}} && next <= {{{ charCode('9') }}} ||
                                    next >= {{{ charCode('a') }}} && next <= {{{ charCode('f') }}} ||
                                    next >= {{{ charCode('A') }}} && next <= {{{ charCode('F') }}}))) &&
                 (formatIndex >= format.length || next !== format[formatIndex].charCodeAt(0))) { // Stop when we read something that is coming up
@@ -2631,17 +2668,21 @@ LibraryManager.library = {
           case 'd': case 'u': case 'i':
             if (half) {
               {{{ makeSetValue('argPtr', 0, 'parseInt(text, 10)', 'i16') }}};
-            } else if(longLong) {
+            } else if (longLong) {
               {{{ makeSetValue('argPtr', 0, 'parseInt(text, 10)', 'i64') }}};
             } else {
               {{{ makeSetValue('argPtr', 0, 'parseInt(text, 10)', 'i32') }}};
             }
             break;
+          case 'X':
           case 'x':
             {{{ makeSetValue('argPtr', 0, 'parseInt(text, 16)', 'i32') }}}
             break;
+          case 'F':
           case 'f':
+          case 'E':
           case 'e':
+          case 'G':
           case 'g':
           case 'E':
             // fallthrough intended
@@ -3797,14 +3838,14 @@ LibraryManager.library = {
      * implementation (replaced by dlmalloc normally) so
      * not an issue.
      */
-#if ASSERTIONS
+#if ASSERTIONS == 2
     Runtime.warnOnce('using stub malloc (reference it from C to have the real one included)');
 #endif
     var ptr = Runtime.dynamicAlloc(bytes + 8);
     return (ptr+8) & 0xFFFFFFF8;
   },
   free: function() {
-#if ASSERTIONS
+#if ASSERTIONS == 2
     Runtime.warnOnce('using stub free (reference it from C to have the real one included)');
 #endif
 },
@@ -3924,8 +3965,8 @@ LibraryManager.library = {
             {{{ makeGetValue('str+1', 0, 'i8') }}} == {{{ charCode('X') }}}) {
           str += 2;
         }
-      }      
-    } 
+      }
+    }
     if (!finalBase) finalBase = 10;
 
     // Get digits.
@@ -3979,7 +4020,7 @@ LibraryManager.library = {
     var isNegative = false;
     // Skip space.
     while (_isspace({{{ makeGetValue('str', 0, 'i8') }}})) str++;
-    
+
     // Check for a plus/minus sign.
     if ({{{ makeGetValue('str', 0, 'i8') }}} == {{{ charCode('-') }}}) {
       str++;
@@ -4008,8 +4049,8 @@ LibraryManager.library = {
             {{{ makeGetValue('str+1', 0, 'i8') }}} == {{{ charCode('X') }}}) {
           str += 2;
         }
-      }      
-    } 
+      }
+    }
     if (!finalBase) finalBase = 10;
     start = str;
 
@@ -4484,24 +4525,24 @@ LibraryManager.library = {
     }
     return pdest|0;
   },
-  
+
   strlwr__deps:['tolower'],
   strlwr: function(pstr){
     var i = 0;
     while(1) {
       var x = {{{ makeGetValue('pstr', 'i', 'i8') }}};
-      if(x == 0) break;
+      if (x == 0) break;
       {{{ makeSetValue('pstr', 'i', '_tolower(x)', 'i8') }}};
       i++;
     }
   },
-  
+
   strupr__deps:['toupper'],
   strupr: function(pstr){
     var i = 0;
     while(1) {
       var x = {{{ makeGetValue('pstr', 'i', 'i8') }}};
-      if(x == 0) break;
+      if (x == 0) break;
       {{{ makeSetValue('pstr', 'i', '_toupper(x)', 'i8') }}};
       i++;
     }
@@ -4677,7 +4718,7 @@ LibraryManager.library = {
     if (size < 0) {
       size = 0;
     }
-    
+
     var newStr = _malloc(size + 1);
     {{{ makeCopyValues('newStr', 'ptr', 'size', 'null', null, 1) }}};
     {{{ makeSetValue('newStr', 'size', '0', 'i8') }}};
@@ -5599,10 +5640,15 @@ LibraryManager.library = {
   frexp: function(x, exp_addr) {
     var sig = 0, exp_ = 0;
     if (x !== 0) {
+      var sign = 1;
+      if (x < 0) {
+        x = -x;
+        sign = -1;
+      }
       var raw_exp = Math.log(x)/Math.log(2);
       exp_ = Math.ceil(raw_exp);
       if (exp_ === raw_exp) exp_ += 1;
-      sig = x/Math.pow(2, exp_);
+      sig = sign*x/Math.pow(2, exp_);
     }
     {{{ makeSetValue('exp_addr', 0, 'exp_', 'i32') }}}
     return sig;
@@ -6671,168 +6717,262 @@ LibraryManager.library = {
   // ==========================================================================
 
   $ERRNO_CODES: {
+    EPERM: 1,
+    ENOENT: 2,
+    ESRCH: 3,
+    EINTR: 4,
+    EIO: 5,
+    ENXIO: 6,
     E2BIG: 7,
-    EACCES: 13,
-    EADDRINUSE: 98,
-    EADDRNOTAVAIL: 99,
-    EAFNOSUPPORT: 97,
-    EAGAIN: 11,
-    EALREADY: 114,
+    ENOEXEC: 8,
     EBADF: 9,
-    EBADMSG: 74,
-    EBUSY: 16,
-    ECANCELED: 125,
     ECHILD: 10,
-    ECONNABORTED: 103,
-    ECONNREFUSED: 111,
-    ECONNRESET: 104,
-    EDEADLK: 35,
-    EDESTADDRREQ: 89,
-    EDOM: 33,
-    EDQUOT: 122,
-    EEXIST: 17,
+    EAGAIN: 11,
+    EWOULDBLOCK: 11,
+    ENOMEM: 12,
+    EACCES: 13,
     EFAULT: 14,
-    EFBIG: 27,
-    EHOSTUNREACH: 113,
-    EIDRM: 43,
-    EILSEQ: 84,
-    EINPROGRESS: 115,
-    EINTR: 4,
-    EINVAL: 22,
-    EIO: 5,
-    EISCONN: 106,
-    EISDIR: 21,
-    ELOOP: 40,
-    EMFILE: 24,
-    EMLINK: 31,
-    EMSGSIZE: 90,
-    EMULTIHOP: 72,
-    ENAMETOOLONG: 36,
-    ENETDOWN: 100,
-    ENETRESET: 102,
-    ENETUNREACH: 101,
-    ENFILE: 23,
-    ENOBUFS: 105,
-    ENODATA: 61,
+    ENOTBLK: 15,
+    EBUSY: 16,
+    EEXIST: 17,
+    EXDEV: 18,
     ENODEV: 19,
-    ENOENT: 2,
-    ENOEXEC: 8,
-    ENOLCK: 37,
-    ENOLINK: 67,
-    ENOMEM: 12,
-    ENOMSG: 42,
-    ENOPROTOOPT: 92,
-    ENOSPC: 28,
-    ENOSR: 63,
-    ENOSTR: 60,
-    ENOSYS: 38,
-    ENOTCONN: 107,
     ENOTDIR: 20,
-    ENOTEMPTY: 39,
-    ENOTRECOVERABLE: 131,
-    ENOTSOCK: 88,
-    ENOTSUP: 95,
+    EISDIR: 21,
+    EINVAL: 22,
+    ENFILE: 23,
+    EMFILE: 24,
     ENOTTY: 25,
-    ENXIO: 6,
-    EOPNOTSUPP: 45,
-    EOVERFLOW: 75,
-    EOWNERDEAD: 130,
-    EPERM: 1,
+    ETXTBSY: 26,
+    EFBIG: 27,
+    ENOSPC: 28,
+    ESPIPE: 29,
+    EROFS: 30,
+    EMLINK: 31,
     EPIPE: 32,
-    EPROTO: 71,
-    EPROTONOSUPPORT: 93,
-    EPROTOTYPE: 91,
+    EDOM: 33,
     ERANGE: 34,
-    EROFS: 30,
-    ESPIPE: 29,
-    ESRCH: 3,
-    ESTALE: 116,
+    ENOMSG: 35,
+    EIDRM: 36,
+    ECHRNG: 37,
+    EL2NSYNC: 38,
+    EL3HLT: 39,
+    EL3RST: 40,
+    ELNRNG: 41,
+    EUNATCH: 42,
+    ENOCSI: 43,
+    EL2HLT: 44,
+    EDEADLK: 45,
+    ENOLCK: 46,
+    EBADE: 50,
+    EBADR: 51,
+    EXFULL: 52,
+    ENOANO: 53,
+    EBADRQC: 54,
+    EBADSLT: 55,
+    EDEADLOCK: 56,
+    EBFONT: 57,
+    ENOSTR: 60,
+    ENODATA: 61,
     ETIME: 62,
-    ETIMEDOUT: 110,
-    ETXTBSY: 26,
-    EWOULDBLOCK: 11,
-    EXDEV: 18,
+    ENOSR: 63,
+    ENONET: 64,
+    ENOPKG: 65,
+    EREMOTE: 66,
+    ENOLINK: 67,
+    EADV: 68,
+    ESRMNT: 69,
+    ECOMM: 70,
+    EPROTO: 71,
+    EMULTIHOP: 74,
+    ELBIN: 75,
+    EDOTDOT: 76,
+    EBADMSG: 77,
+    EFTYPE: 79,
+    ENOTUNIQ: 80,
+    EBADFD: 81,
+    EREMCHG: 82,
+    ELIBACC: 83,
+    ELIBBAD: 84,
+    ELIBSCN: 85,
+    ELIBMAX: 86,
+    ELIBEXEC: 87,
+    ENOSYS: 88,
+    ENMFILE: 89,
+    ENOTEMPTY: 90,
+    ENAMETOOLONG: 91,
+    ELOOP: 92,
+    EOPNOTSUPP: 95,
+    EPFNOSUPPORT: 96,
+    ECONNRESET: 104,
+    ENOBUFS: 105,
+    EAFNOSUPPORT: 106,
+    EPROTOTYPE: 107,
+    ENOTSOCK: 108,
+    ENOPROTOOPT: 109,
+    ESHUTDOWN: 110,
+    ECONNREFUSED: 111,
+    EADDRINUSE: 112,
+    ECONNABORTED: 113,
+    ENETUNREACH: 114,
+    ENETDOWN: 115,
+    ETIMEDOUT: 116,
+    EHOSTDOWN: 117,
+    EHOSTUNREACH: 118,
+    EINPROGRESS: 119,
+    EALREADY: 120,
+    EDESTADDRREQ: 121,
+    EMSGSIZE: 122,
+    EPROTONOSUPPORT: 123,
+    ESOCKTNOSUPPORT: 124,
+    EADDRNOTAVAIL: 125,
+    ENETRESET: 126,
+    EISCONN: 127,
+    ENOTCONN: 128,
+    ETOOMANYREFS: 129,
+    EPROCLIM: 130,
+    EUSERS: 131,
+    EDQUOT: 132,
+    ESTALE: 133,
+    ENOTSUP: 134,
+    ENOMEDIUM: 135,
+    ENOSHARE: 136,
+    ECASECLASH: 137,
+    EILSEQ: 138,
+    EOVERFLOW: 139,
+    ECANCELED: 140,
+    ENOTRECOVERABLE: 141,
+    EOWNERDEAD: 142,
+    ESTRPIPE: 143
   },
   $ERRNO_MESSAGES: {
+    0: 'Success',
+    1: 'Not super-user',
     2: 'No such file or directory',
+    3: 'No such process',
+    4: 'Interrupted system call',
+    5: 'I/O error',
+    6: 'No such device or address',
+    7: 'Arg list too long',
+    8: 'Exec format error',
+    9: 'Bad file number',
+    10: 'No children',
+    11: 'No more processes',
+    12: 'Not enough core',
     13: 'Permission denied',
-    98: 'Address already in use',
-    99: 'Cannot assign requested address',
-    97: 'Address family not supported by protocol',
-    11: 'Resource temporarily unavailable',
-    114: 'Operation already in progress',
-    9: 'Bad file descriptor',
-    74: 'Bad message',
-    16: 'Device or resource busy',
-    125: 'Operation canceled',
-    10: 'No child processes',
-    103: 'Software caused connection abort',
-    111: 'Connection refused',
-    104: 'Connection reset by peer',
-    35: 'Resource deadlock avoided',
-    89: 'Destination address required',
-    33: 'Numerical argument out of domain',
-    122: 'Disk quota exceeded',
-    17: 'File exists',
     14: 'Bad address',
-    27: 'File too large',
-    113: 'No route to host',
-    43: 'Identifier removed',
-    84: 'Invalid or incomplete multibyte or wide character',
-    115: 'Operation now in progress',
-    4: 'Interrupted system call',
-    22: 'Invalid argument',
-    5: 'Input/output error',
-    106: 'Transport endpoint is already connected',
+    15: 'Block device required',
+    16: 'Mount device busy',
+    17: 'File exists',
+    18: 'Cross-device link',
+    19: 'No such device',
+    20: 'Not a directory',
     21: 'Is a directory',
-    40: 'Too many levels of symbolic links',
-    24: 'Too many open files',
-    31: 'Too many links',
-    90: 'Message too long',
-    72: 'Multihop attempted',
-    36: 'File name too long',
-    100: 'Network is down',
-    102: 'Network dropped connection on reset',
-    101: 'Network is unreachable',
+    22: 'Invalid argument',
     23: 'Too many open files in system',
-    105: 'No buffer space available',
-    61: 'No data available',
-    19: 'No such device',
-    8: 'Exec format error',
-    37: 'No locks available',
-    67: 'Link has been severed',
-    12: 'Cannot allocate memory',
-    42: 'No message of desired type',
-    92: 'Protocol not available',
+    24: 'Too many open files',
+    25: 'Not a typewriter',
+    26: 'Text file busy',
+    27: 'File too large',
     28: 'No space left on device',
-    63: 'Out of streams resources',
-    60: 'Device not a stream',
-    38: 'Function not implemented',
-    107: 'Transport endpoint is not connected',
-    20: 'Not a directory',
-    39: 'Directory not empty',
-    131: 'State not recoverable',
-    88: 'Socket operation on non-socket',
-    95: 'Operation not supported',
-    25: 'Inappropriate ioctl for device',
-    6: 'No such device or address',
-    45: 'Op not supported on transport endpoint',
-    75: 'Value too large for defined data type',
-    130: 'Owner died',
-    1: 'Operation not permitted',
-    32: 'Broken pipe',
-    71: 'Protocol error',
-    93: 'Protocol not supported',
-    91: 'Protocol wrong type for socket',
-    34: 'Numerical result out of range',
-    30: 'Read-only file system',
     29: 'Illegal seek',
-    3: 'No such process',
-    116: 'Stale NFS file handle',
+    30: 'Read only file system',
+    31: 'Too many links',
+    32: 'Broken pipe',
+    33: 'Math arg out of domain of func',
+    34: 'Math result not representable',
+    35: 'No message of desired type',
+    36: 'Identifier removed',
+    37: 'Channel number out of range',
+    38: 'Level 2 not synchronized',
+    39: 'Level 3 halted',
+    40: 'Level 3 reset',
+    41: 'Link number out of range',
+    42: 'Protocol driver not attached',
+    43: 'No CSI structure available',
+    44: 'Level 2 halted',
+    45: 'Deadlock condition',
+    46: 'No record locks available',
+    50: 'Invalid exchange',
+    51: 'Invalid request descriptor',
+    52: 'Exchange full',
+    53: 'No anode',
+    54: 'Invalid request code',
+    55: 'Invalid slot',
+    56: 'File locking deadlock error',
+    57: 'Bad font file fmt',
+    60: 'Device not a stream',
+    61: 'No data (for no delay io)',
     62: 'Timer expired',
-    110: 'Connection timed out',
-    26: 'Text file busy',
-    18: 'Invalid cross-device link'
+    63: 'Out of streams resources',
+    64: 'Machine is not on the network',
+    65: 'Package not installed',
+    66: 'The object is remote',
+    67: 'The link has been severed',
+    68: 'Advertise error',
+    69: 'Srmount error',
+    70: 'Communication error on send',
+    71: 'Protocol error',
+    74: 'Multihop attempted',
+    75: 'Inode is remote (not really error)',
+    76: 'Cross mount point (not really error)',
+    77: 'Trying to read unreadable message',
+    79: 'Inappropriate file type or format',
+    80: 'Given log. name not unique',
+    81: 'f.d. invalid for this operation',
+    82: 'Remote address changed',
+    83: 'Can\t access a needed shared lib',
+    84: 'Accessing a corrupted shared lib',
+    85: '.lib section in a.out corrupted',
+    86: 'Attempting to link in too many libs',
+    87: 'Attempting to exec a shared library',
+    88: 'Function not implemented',
+    89: 'No more files',
+    90: 'Directory not empty',
+    91: 'File or path name too long',
+    92: 'Too many symbolic links',
+    95: 'Operation not supported on transport endpoint',
+    96: 'Protocol family not supported',
+    104: 'Connection reset by peer',
+    105: 'No buffer space available',
+    106: 'Address family not supported by protocol family',
+    107: 'Protocol wrong type for socket',
+    108: 'Socket operation on non-socket',
+    109: 'Protocol not available',
+    110: 'Can\'t send after socket shutdown',
+    111: 'Connection refused',
+    112: 'Address already in use',
+    113: 'Connection aborted',
+    114: 'Network is unreachable',
+    115: 'Network interface is not configured',
+    116: 'Connection timed out',
+    117: 'Host is down',
+    118: 'Host is unreachable',
+    119: 'Connection already in progress',
+    120: 'Socket already connected',
+    121: 'Destination address required',
+    122: 'Message too long',
+    123: 'Unknown protocol',
+    124: 'Socket type not supported',
+    125: 'Address not available',
+    126: 'ENETRESET',
+    127: 'Socket is already connected',
+    128: 'Socket is not connected',
+    129: 'TOOMANYREFS',
+    130: 'EPROCLIM',
+    131: 'EUSERS',
+    132: 'EDQUOT',
+    133: 'ESTALE',
+    134: 'Not supported',
+    135: 'No medium (in tape drive)',
+    136: 'No such host or network path',
+    137: 'Filename exists with different case',
+    138: 'EILSEQ',
+    139: 'Value too large for defined data type',
+    140: 'Operation canceled',
+    141: 'State not recoverable',
+    142: 'Previous owner died',
+    143: 'Streams pipe error',
   },
   __errno_state: 0,
   __setErrNo__deps: ['__errno_state'],
@@ -7093,6 +7233,7 @@ LibraryManager.library = {
     ['i32', 'h_length'],
     ['i8**', 'h_addr_list'],
   ]),
+
   gethostbyname__deps: ['__hostent_struct_layout'],
   gethostbyname: function(name) {
     name = Pointer_stringify(name);
@@ -7135,17 +7276,28 @@ LibraryManager.library = {
   // sockets. Note that the implementation assumes all sockets are always
   // nonblocking
   // ==========================================================================
-
+#if SOCKET_WEBRTC
+  $Sockets__deps: ['__setErrNo', '$ERRNO_CODES',
+    function() { return 'var SocketIO = ' + read('socket.io.js') + ';\n' },
+    function() { return 'var Peer = ' + read('wrtcp.js') + ';\n' }],
+#else
   $Sockets__deps: ['__setErrNo', '$ERRNO_CODES'],
+#endif
   $Sockets: {
-    BACKEND_WEBSOCKETS: 0,
-    BACKEND_WEBRTC: 1,
     BUFFER_SIZE: 10*1024, // initial size
     MAX_BUFFER_SIZE: 10*1024*1024, // maximum size we will grow the buffer
 
-    backend: 0, // default to websockets
     nextFd: 1,
     fds: {},
+    nextport: 1,
+    maxport: 65535,
+    peer: null,
+    connections: {},
+    portmap: {},
+    localAddr: 0xfe00000a, // Local address is always 10.0.0.254
+    addrPool: [            0x0200000a, 0x0300000a, 0x0400000a, 0x0500000a,
+               0x0600000a, 0x0700000a, 0x0800000a, 0x0900000a, 0x0a00000a,
+               0x0b00000a, 0x0c00000a, 0x0d00000a, 0x0e00000a], /* 0x0100000a is reserved */
     sockaddr_in_layout: Runtime.generateStructInfo([
       ['i32', 'sin_family'],
       ['i16', 'sin_port'],
@@ -7162,135 +7314,403 @@ LibraryManager.library = {
       ['i32', 'msg_controllen'],
       ['i32', 'msg_flags'],
     ]),
+  },
+
+#if SOCKET_WEBRTC
+  /* WebRTC sockets supports several options on the Module object.
 
-    backends: {
-      0: { // websockets
-        connect: function(info) {
-          console.log('opening ws://' + info.host + ':' + info.port);
-          info.socket = new WebSocket('ws://' + info.host + ':' + info.port, ['binary']);
-          info.socket.binaryType = 'arraybuffer';
+     * Module['host']: true if this peer is hosting, false otherwise
+     * Module['webrtc']['broker']: hostname for the p2p broker that this peer should use
+     * Module['webrtc']['session']: p2p session for that this peer will join, or undefined if this peer is hosting
+     * Module['webrtc']['hostOptions']: options to pass into p2p library if this peer is hosting
+     * Module['webrtc']['onpeer']: function(peer, route), invoked when this peer is ready to connect
+     * Module['webrtc']['onconnect']: function(peer), invoked when a new peer connection is ready
+     * Module['webrtc']['ondisconnect']: function(peer), invoked when an existing connection is closed
+     * Module['webrtc']['onerror']: function(error), invoked when an error occurs
+   */
+  socket__deps: ['$Sockets'],
+  socket: function(family, type, protocol) {
+    var INCOMING_QUEUE_LENGTH = 64;
+    var fd = FS.createFileHandle({
+      addr: null,
+      port: null,
+      inQueue: new CircularBuffer(INCOMING_QUEUE_LENGTH),
+      header: new Uint16Array(2),
+      bound: false,
+      socket: true
+    });
+    assert(fd < 64); // select() assumes socket fd values are in 0..63
+    var stream = type == {{{ cDefine('SOCK_STREAM') }}};
+    if (protocol) {
+      assert(stream == (protocol == {{{ cDefine('IPPROTO_TCP') }}})); // if stream, must be tcp
+    }
 
-          var i32Temp = new Uint32Array(1);
-          var i8Temp = new Uint8Array(i32Temp.buffer);
+    // Open the peer connection if we don't have it already
+    if (null == Sockets.peer) {
+      var host = Module['host'];
+      var broker = Module['webrtc']['broker'];
+      var session = Module['webrtc']['session'];
+      var peer = new Peer(broker);
+      var listenOptions = Module['webrtc']['hostOptions'] || {};
+      peer.onconnection = function(connection) {
+        console.log('connected');
+        var addr;
+        /* If this peer is connecting to the host, assign 10.0.0.1 to the host so it can be
+           reached at a known address.
+         */
+        // Assign 10.0.0.1 to the host
+        if (session && session === connection['route']) {
+          addr = 0x0100000a; // 10.0.0.1
+        } else {
+          addr = Sockets.addrPool.shift();
+        }
+        connection['addr'] = addr;
+        Sockets.connections[addr] = connection;
+        connection.ondisconnect = function() {
+          console.log('disconnect');
+          // Don't return the host address (10.0.0.1) to the pool
+          if (!(session && session === Sockets.connections[addr]['route'])) {
+            Sockets.addrPool.push(addr);
+          }
+          delete Sockets.connections[addr];
 
-          info.inQueue = [];
-          info.hasData = function() { return info.inQueue.length > 0 }
-          if (!info.stream) {
-            var partialBuffer = null; // in datagram mode, inQueue contains full dgram messages; this buffers incomplete data. Must begin with the beginning of a message
+          if (Module['webrtc']['ondisconnect'] && 'function' === typeof Module['webrtc']['ondisconnect']) {
+            Module['webrtc']['ondisconnect'](peer);
+          }
+        };
+        connection.onerror = function(error) {
+          if (Module['webrtc']['onerror'] && 'function' === typeof Module['webrtc']['onerror']) {
+            Module['webrtc']['onerror'](error);
           }
+        };
+        connection.onmessage = function(label, message) {
+          if ('unreliable' === label) {
+            handleMessage(addr, message.data);
+          }
+        }
 
-          info.socket.onmessage = function(event) {
-            assert(typeof event.data !== 'string' && event.data.byteLength); // must get binary data!
-            var data = new Uint8Array(event.data); // make a typed array view on the array buffer
+        if (Module['webrtc']['onconnect'] && 'function' === typeof Module['webrtc']['onconnect']) {
+          Module['webrtc']['onconnect'](peer);
+        }
+      };
+      peer.onpending = function(pending) {
+        console.log('pending from: ', pending['route'], '; initiated by: ', (pending['incoming']) ? 'remote' : 'local');
+      };
+      peer.onerror = function(error) {
+        console.error(error);
+      };
+      peer.onroute = function(route) {
+        if (Module['webrtc']['onpeer'] && 'function' === typeof Module['webrtc']['onpeer']) {
+          Module['webrtc']['onpeer'](peer, route);
+        }
+      };
+      function handleMessage(addr, message) {
 #if SOCKET_DEBUG
-            Module.print(['onmessage', data.length, '|', Array.prototype.slice.call(data)]);
+        Module.print("received " + message.byteLength + " raw bytes");
 #endif
-            if (info.stream) {
-              info.inQueue.push(data);
-            } else {
-              // we added headers with message sizes, read those to find discrete messages
-              if (partialBuffer) {
-                // append to the partial buffer
-                var newBuffer = new Uint8Array(partialBuffer.length + data.length);
-                newBuffer.set(partialBuffer);
-                newBuffer.set(data, partialBuffer.length);
-                // forget the partial buffer and work on data
-                data = newBuffer;
-                partialBuffer = null;
-              }
-              var currPos = 0;
-              while (currPos+4 < data.length) {
-                i8Temp.set(data.subarray(currPos, currPos+4));
-                var currLen = i32Temp[0];
-                assert(currLen > 0);
-                if (currPos + 4 + currLen > data.length) {
-                  break; // not enough data has arrived
-                }
-                currPos += 4;
+        var header = new Uint16Array(message, 0, 2);
+        if (Sockets.portmap[header[1]]) {
+          Sockets.portmap[header[1]].inQueue.push([addr, message]);
+        } else {
+          console.log("unable to deliver message: ", addr, header[1], message);
+        }
+      }
+      window.onbeforeunload = function() {
+        var ids = Object.keys(Sockets.connections);
+        ids.forEach(function(id) {
+          Sockets.connections[id].close();
+        });
+      }
+      Sockets.peer = peer;
+    }
+
+    function CircularBuffer(max_length) {
+      var buffer = new Array(++ max_length);
+      var head = 0;
+      var tail = 0;
+      var length = 0;
+
+      return {
+        push: function(element) {
+          buffer[tail ++] = element;
+          length = Math.min(++ length, max_length - 1);
+          tail = tail % max_length;
+          if (tail === head) {
+            head = (head + 1) % max_length;
+          }
+        },
+        shift: function(element) {
+          if (length < 1) return undefined;
+
+          var element = buffer[head];
+          -- length;
+          head = (head + 1) % max_length;
+          return element;
+        },
+        length: function() {
+          return length;
+        }
+      };
+    };
+
+    return fd;
+  },
+
+  mkport__deps: ['$Sockets'],
+  mkport: function() {
+    for(var i = 0; i < Sockets.maxport; ++ i) {
+      var port = Sockets.nextport ++;
+      Sockets.nextport = (Sockets.nextport > Sockets.maxport) ? 1 : Sockets.nextport;
+      if (!Sockets.portmap[port]) {
+        return port;
+      }
+    }
+    assert(false, 'all available ports are in use!');
+  },
+
+  connect: function() {
+    // Stub: connection-oriented sockets are not supported yet.
+  },
+
+  bind__deps: ['$Sockets', '_inet_ntop_raw', 'ntohs', 'mkport'],
+  bind: function(fd, addr, addrlen) {
+    var info = FS.streams[fd];
+    if (!info) return -1;
+    if (addr) {
+      info.port = _ntohs(getValue(addr + Sockets.sockaddr_in_layout.sin_port, 'i16'));
+      // info.addr = getValue(addr + Sockets.sockaddr_in_layout.sin_addr, 'i32');
+    }
+    if (!info.port) {
+      info.port = _mkport();
+    }
+    info.addr = Sockets.localAddr; // 10.0.0.254
+    info.host = __inet_ntop_raw(info.addr);
+    info.close = function() {
+      Sockets.portmap[info.port] = undefined;
+    }
+    Sockets.portmap[info.port] = info;
+    console.log("bind: ", info.host, info.port);
+    info.bound = true;
+  },
+
+  sendmsg__deps: ['$Sockets', 'bind', '_inet_ntop_raw', 'ntohs'],
+  sendmsg: function(fd, msg, flags) {
+    var info = FS.streams[fd];
+    if (!info) return -1;
+    // if we are not connected, use the address info in the message
+    if (!info.bound) {
+      _bind(fd);
+    }
+
+    var name = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_name', '*') }}};
+    assert(name, 'sendmsg on non-connected socket, and no name/address in the message');
+    var port = _ntohs(getValue(name + Sockets.sockaddr_in_layout.sin_port, 'i16'));
+    var addr = getValue(name + Sockets.sockaddr_in_layout.sin_addr, 'i32');
+    var connection = Sockets.connections[addr];
+    // var host = __inet_ntop_raw(addr);
+
+    if (!(connection && connection.connected)) {
+      ___setErrNo(ERRNO_CODES.EWOULDBLOCK);
+      return -1;
+    }
+
+    var iov = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iov', 'i8*') }}};
+    var num = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iovlen', 'i32') }}};
 #if SOCKET_DEBUG
-                Module.print(['onmessage message', currLen, '|', Array.prototype.slice.call(data.subarray(currPos, currPos+currLen))]);
+    Module.print('sendmsg vecs: ' + num);
 #endif
-                info.inQueue.push(data.subarray(currPos, currPos+currLen));
-                currPos += currLen;
-              }
-              // If data remains, buffer it
-              if (currPos < data.length) {
-                partialBuffer = data.subarray(currPos);
-              }
-            }
-          }
-          function send(data) {
-            // TODO: if browser accepts views, can optimize this
+    var totalSize = 0;
+    for (var i = 0; i < num; i++) {
+      totalSize += {{{ makeGetValue('iov', '8*i + 4', 'i32') }}};
+    }
+    var data = new Uint8Array(totalSize);
+    var ret = 0;
+    for (var i = 0; i < num; i++) {
+      var currNum = {{{ makeGetValue('iov', '8*i + 4', 'i32') }}};
 #if SOCKET_DEBUG
-            Module.print('sender actually sending ' + Array.prototype.slice.call(data));
+    Module.print('sendmsg curr size: ' + currNum);
 #endif
-            // ok to use the underlying buffer, we created data and know that the buffer starts at the beginning
-            info.socket.send(data.buffer);
-          }
-          var outQueue = [];
-          var intervalling = false, interval;
-          function trySend() {
-            if (info.socket.readyState != info.socket.OPEN) {
-              if (!intervalling) {
-                intervalling = true;
-                console.log('waiting for socket in order to send');
-                interval = setInterval(trySend, 100);
-              }
-              return;
-            }
-            for (var i = 0; i < outQueue.length; i++) {
-              send(outQueue[i]);
-            }
-            outQueue.length = 0;
-            if (intervalling) {
-              intervalling = false;
-              clearInterval(interval);
-            }
+      if (!currNum) continue;
+      var currBuf = {{{ makeGetValue('iov', '8*i', 'i8*') }}};
+      data.set(HEAPU8.subarray(currBuf, currBuf+currNum), ret);
+      ret += currNum;
+    }
+
+    info.header[0] = info.port; // src port
+    info.header[1] = port; // dst port
+#if SOCKET_DEBUG
+    Module.print('sendmsg port: ' + info.header[0] + ' -> ' + info.header[1]);
+    Module.print('sendmsg bytes: ' + data.length + ' | ' + Array.prototype.slice.call(data));
+#endif
+    var buffer = new Uint8Array(info.header.byteLength + data.byteLength);
+    buffer.set(new Uint8Array(info.header.buffer));
+    buffer.set(data, info.header.byteLength);
+
+    connection.send('unreliable', buffer.buffer);
+  },
+
+  recvmsg__deps: ['$Sockets', 'bind', '__setErrNo', '$ERRNO_CODES', 'htons'],
+  recvmsg: function(fd, msg, flags) {
+    var info = FS.streams[fd];
+    if (!info) return -1;
+    // if we are not connected, use the address info in the message
+    if (!info.port) {
+      console.log('recvmsg on unbound socket');
+      assert(false, 'cannot receive on unbound socket');
+    }
+    if (info.inQueue.length() == 0) {
+      ___setErrNo(ERRNO_CODES.EWOULDBLOCK);
+      return -1;
+    }
+
+    var entry = info.inQueue.shift();
+    var addr = entry[0];
+    var message = entry[1];
+    var header = new Uint16Array(message, 0, info.header.length);
+    var buffer = new Uint8Array(message, info.header.byteLength);
+
+    var bytes = buffer.length;
+#if SOCKET_DEBUG
+    Module.print('recvmsg port: ' + header[1] + ' <- ' + header[0]);
+    Module.print('recvmsg bytes: ' + bytes + ' | ' + Array.prototype.slice.call(buffer));
+#endif
+    // write source
+    var name = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_name', '*') }}};
+    {{{ makeSetValue('name', 'Sockets.sockaddr_in_layout.sin_addr', 'addr', 'i32') }}};
+    {{{ makeSetValue('name', 'Sockets.sockaddr_in_layout.sin_port', '_htons(header[0])', 'i16') }}};
+    // write data
+    var ret = bytes;
+    var iov = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iov', 'i8*') }}};
+    var num = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iovlen', 'i32') }}};
+    var bufferPos = 0;
+    for (var i = 0; i < num && bytes > 0; i++) {
+      var currNum = {{{ makeGetValue('iov', '8*i + 4', 'i32') }}};
+#if SOCKET_DEBUG
+      Module.print('recvmsg loop ' + [i, num, bytes, currNum]);
+#endif
+      if (!currNum) continue;
+      currNum = Math.min(currNum, bytes); // XXX what should happen when we partially fill a buffer..?
+      bytes -= currNum;
+      var currBuf = {{{ makeGetValue('iov', '8*i', 'i8*') }}};
+#if SOCKET_DEBUG
+      Module.print('recvmsg call recv ' + currNum);
+#endif
+      HEAPU8.set(buffer.subarray(bufferPos, bufferPos + currNum), currBuf);
+      bufferPos += currNum;
+    }
+    return ret;
+  },
+
+  shutdown: function(fd, how) {
+    var info = FS.streams[fd];
+    if (!info) return -1;
+    info.close();
+    FS.removeFileHandle(fd);
+  },
+
+  ioctl: function(fd, request, varargs) {
+    var info = FS.streams[fd];
+    if (!info) return -1;
+    var bytes = 0;
+    if (info.hasData()) {
+      bytes = info.inQueue[0].length;
+    }
+    var dest = {{{ makeGetValue('varargs', '0', 'i32') }}};
+    {{{ makeSetValue('dest', '0', 'bytes', 'i32') }}};
+    return 0;
+  },
+
+  setsockopt: function(d, level, optname, optval, optlen) {
+    console.log('ignoring setsockopt command');
+    return 0;
+  },
+
+  accept: function(fd, addr, addrlen) {
+    // TODO: webrtc queued incoming connections, etc.
+    // For now, the model is that bind does a connect, and we "accept" that one connection,
+    // which has host:port the same as ours. We also return the same socket fd.
+    var info = FS.streams[fd];
+    if (!info) return -1;
+    if (addr) {
+      setValue(addr + Sockets.sockaddr_in_layout.sin_addr, info.addr, 'i32');
+      setValue(addr + Sockets.sockaddr_in_layout.sin_port, info.port, 'i32');
+      setValue(addrlen, Sockets.sockaddr_in_layout.__size__, 'i32');
+    }
+    return fd;
+  },
+
+  select: function(nfds, readfds, writefds, exceptfds, timeout) {
+    // readfds are supported,
+    // writefds checks socket open status
+    // exceptfds not supported
+    // timeout is always 0 - fully async
+    assert(!exceptfds);
+
+    var errorCondition = 0;
+
+    function canRead(info) {
+      return info.inQueue.length() > 0;
+    }
+
+    function canWrite(info) {
+      return true;
+    }
+
+    function checkfds(nfds, fds, can) {
+      if (!fds) return 0;
+
+      var bitsSet = 0;
+      var dstLow  = 0;
+      var dstHigh = 0;
+      var srcLow  = {{{ makeGetValue('fds', 0, 'i32') }}};
+      var srcHigh = {{{ makeGetValue('fds', 4, 'i32') }}};
+      nfds = Math.min(64, nfds); // fd sets have 64 bits
+
+      for (var fd = 0; fd < nfds; fd++) {
+        var mask = 1 << (fd % 32), int = fd < 32 ? srcLow : srcHigh;
+        if (int & mask) {
+          // index is in the set, check if it is ready for read
+          var info = FS.streams[fd];
+          if (info && can(info)) {
+            // set bit
+            fd < 32 ? (dstLow = dstLow | mask) : (dstHigh = dstHigh | mask);
+            bitsSet++;
           }
-          info.sender = function(data) {
-            if (!info.stream) {
-              // add a header with the message size
-              var header = new Uint8Array(4);
-              i32Temp[0] = data.length;
-              header.set(i8Temp);
-              outQueue.push(header);
-            }
-            outQueue.push(new Uint8Array(data));
-            trySend();
-          };
         }
-      },
-      1: { // webrtc
       }
+
+      {{{ makeSetValue('fds', 0, 'dstLow', 'i32') }}};
+      {{{ makeSetValue('fds', 4, 'dstHigh', 'i32') }}};
+      return bitsSet;
     }
-  },
 
-  emscripten_set_network_backend__deps: ['$Sockets'],
-  emscripten_set_network_backend: function(backend) {
-    Sockets.backend = backend;
+    var totalHandles = checkfds(nfds, readfds, canRead) + checkfds(nfds, writefds, canWrite);
+    if (errorCondition) {
+      ___setErrNo(ERRNO_CODES.EBADF);
+      return -1;
+    } else {
+      return totalHandles;
+    }
   },
-
+#else
   socket__deps: ['$Sockets'],
   socket: function(family, type, protocol) {
-    var fd = Sockets.nextFd++;
-    assert(fd < 64); // select() assumes socket fd values are in 0..63
     var stream = type == {{{ cDefine('SOCK_STREAM') }}};
     if (protocol) {
-      assert(stream == (protocol == {{{ cDefine('IPPROTO_TCP') }}})); // if stream, must be tcp
-    }
-    if (Sockets.backend == Sockets.BACKEND_WEBRTC) {
-      assert(!stream); // If WebRTC, we can only support datagram, not stream
+      assert(stream == (protocol == {{{ cDefine('IPPROTO_TCP') }}})); // if SOCK_STREAM, must be tcp
     }
-    Sockets.fds[fd] = {
+    var fd = FS.createFileHandle({
       connected: false,
-      stream: stream
-    };
+      stream: stream,
+      socket: true
+    });
+    assert(fd < 64); // select() assumes socket fd values are in 0..63
     return fd;
   },
 
-  connect__deps: ['$Sockets', '_inet_ntop_raw', 'htons', 'gethostbyname'],
+  connect__deps: ['$FS', '$Sockets', '_inet_ntop_raw', 'ntohs', 'gethostbyname'],
   connect: function(fd, addr, addrlen) {
-    var info = Sockets.fds[fd];
+    var info = FS.streams[fd];
     if (!info) return -1;
     info.connected = true;
     info.addr = getValue(addr + Sockets.sockaddr_in_layout.sin_addr, 'i32');
@@ -7305,18 +7725,110 @@ LibraryManager.library = {
       assert(info.host, 'problem translating fake ip ' + parts);
     }
     try {
-      Sockets.backends[Sockets.backend].connect(info);
+      console.log('opening ws://' + info.host + ':' + info.port);
+      info.socket = new WebSocket('ws://' + info.host + ':' + info.port, ['binary']);
+      info.socket.binaryType = 'arraybuffer';
+
+      var i32Temp = new Uint32Array(1);
+      var i8Temp = new Uint8Array(i32Temp.buffer);
+
+      info.inQueue = [];
+      info.hasData = function() { return info.inQueue.length > 0 }
+      if (!info.stream) {
+        var partialBuffer = null; // in datagram mode, inQueue contains full dgram messages; this buffers incomplete data. Must begin with the beginning of a message
+      }
+
+      info.socket.onmessage = function(event) {
+        assert(typeof event.data !== 'string' && event.data.byteLength); // must get binary data!
+        var data = new Uint8Array(event.data); // make a typed array view on the array buffer
+#if SOCKET_DEBUG
+        Module.print(['onmessage', data.length, '|', Array.prototype.slice.call(data)]);
+#endif
+        if (info.stream) {
+          info.inQueue.push(data);
+        } else {
+          // we added headers with message sizes, read those to find discrete messages
+          if (partialBuffer) {
+            // append to the partial buffer
+            var newBuffer = new Uint8Array(partialBuffer.length + data.length);
+            newBuffer.set(partialBuffer);
+            newBuffer.set(data, partialBuffer.length);
+            // forget the partial buffer and work on data
+            data = newBuffer;
+            partialBuffer = null;
+          }
+          var currPos = 0;
+          while (currPos+4 < data.length) {
+            i8Temp.set(data.subarray(currPos, currPos+4));
+            var currLen = i32Temp[0];
+            assert(currLen > 0);
+            if (currPos + 4 + currLen > data.length) {
+              break; // not enough data has arrived
+            }
+            currPos += 4;
+#if SOCKET_DEBUG
+            Module.print(['onmessage message', currLen, '|', Array.prototype.slice.call(data.subarray(currPos, currPos+currLen))]);
+#endif
+            info.inQueue.push(data.subarray(currPos, currPos+currLen));
+            currPos += currLen;
+          }
+          // If data remains, buffer it
+          if (currPos < data.length) {
+            partialBuffer = data.subarray(currPos);
+          }
+        }
+      }
+      function send(data) {
+        // TODO: if browser accepts views, can optimize this
+#if SOCKET_DEBUG
+        Module.print('sender actually sending ' + Array.prototype.slice.call(data));
+#endif
+        // ok to use the underlying buffer, we created data and know that the buffer starts at the beginning
+        info.socket.send(data.buffer);
+      }
+      var outQueue = [];
+      var intervalling = false, interval;
+      function trySend() {
+        if (info.socket.readyState != info.socket.OPEN) {
+          if (!intervalling) {
+            intervalling = true;
+            console.log('waiting for socket in order to send');
+            interval = setInterval(trySend, 100);
+          }
+          return;
+        }
+        for (var i = 0; i < outQueue.length; i++) {
+          send(outQueue[i]);
+        }
+        outQueue.length = 0;
+        if (intervalling) {
+          intervalling = false;
+          clearInterval(interval);
+        }
+      }
+      info.sender = function(data) {
+        if (!info.stream) {
+          // add a header with the message size
+          var header = new Uint8Array(4);
+          i32Temp[0] = data.length;
+          header.set(i8Temp);
+          outQueue.push(header);
+        }
+        outQueue.push(new Uint8Array(data));
+        trySend();
+      };
     } catch(e) {
       Module.printErr('Error in connect(): ' + e);
       ___setErrNo(ERRNO_CODES.EACCES);
       return -1;
     }
+
     return 0;
   },
 
-  recv__deps: ['$Sockets'],
+  recv__deps: ['$FS'],
   recv: function(fd, buf, len, flags) {
-    var info = Sockets.fds[fd];
+    var info = FS.streams[fd];
     if (!info) return -1;
     if (!info.hasData()) {
       ___setErrNo(ERRNO_CODES.EAGAIN); // no data, and all sockets are nonblocking, so this is the right behavior
@@ -7340,17 +7852,17 @@ LibraryManager.library = {
     return buffer.length;
   },
 
-  send__deps: ['$Sockets'],
+  send__deps: ['$FS'],
   send: function(fd, buf, len, flags) {
-    var info = Sockets.fds[fd];
+    var info = FS.streams[fd];
     if (!info) return -1;
     info.sender(HEAPU8.subarray(buf, buf+len));
     return len;
   },
 
-  sendmsg__deps: ['$Sockets', 'connect'],
+  sendmsg__deps: ['$FS', '$Sockets', 'connect'],
   sendmsg: function(fd, msg, flags) {
-    var info = Sockets.fds[fd];
+    var info = FS.streams[fd];
     if (!info) return -1;
     // if we are not connected, use the address info in the message
     if (!info.connected) {
@@ -7361,7 +7873,7 @@ LibraryManager.library = {
     var iov = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iov', 'i8*') }}};
     var num = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iovlen', 'i32') }}};
 #if SOCKET_DEBUG
-      Module.print('sendmsg vecs: ' + num);
+    Module.print('sendmsg vecs: ' + num);
 #endif
     var totalSize = 0;
     for (var i = 0; i < num; i++) {
@@ -7383,14 +7895,14 @@ LibraryManager.library = {
     return ret;
   },
 
-  recvmsg__deps: ['$Sockets', 'connect', 'recv', '__setErrNo', '$ERRNO_CODES', 'htons'],
+  recvmsg__deps: ['$FS', '$Sockets', 'connect', 'recv', '__setErrNo', '$ERRNO_CODES', 'htons'],
   recvmsg: function(fd, msg, flags) {
-    var info = Sockets.fds[fd];
+    var info = FS.streams[fd];
     if (!info) return -1;
     // if we are not connected, use the address info in the message
     if (!info.connected) {
 #if SOCKET_DEBUG
-      Module.print('recvmsg connecting');
+    Module.print('recvmsg connecting');
 #endif
       var name = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_name', '*') }}};
       assert(name, 'sendmsg on non-connected socket, and no name/address in the message');
@@ -7441,9 +7953,9 @@ LibraryManager.library = {
     return ret;
   },
 
-  recvfrom__deps: ['$Sockets', 'connect', 'recv'],
+  recvfrom__deps: ['$FS', 'connect', 'recv'],
   recvfrom: function(fd, buf, len, flags, addr, addrlen) {
-    var info = Sockets.fds[fd];
+    var info = FS.streams[fd];
     if (!info) return -1;
     // if we are not connected, use the address info in the message
     if (!info.connected) {
@@ -7454,14 +7966,14 @@ LibraryManager.library = {
   },
 
   shutdown: function(fd, how) {
-    var info = Sockets.fds[fd];
+    var info = FS.streams[fd];
     if (!info) return -1;
     info.socket.close();
-    Sockets.fds[fd] = null;
+    FS.removeFileHandle(fd);
   },
 
   ioctl: function(fd, request, varargs) {
-    var info = Sockets.fds[fd];
+    var info = FS.streams[fd];
     if (!info) return -1;
     var bytes = 0;
     if (info.hasData()) {
@@ -7486,11 +7998,12 @@ LibraryManager.library = {
     return 0;
   },
 
+  accept__deps: ['$FS', '$Sockets'],
   accept: function(fd, addr, addrlen) {
     // TODO: webrtc queued incoming connections, etc.
     // For now, the model is that bind does a connect, and we "accept" that one connection,
     // which has host:port the same as ours. We also return the same socket fd.
-    var info = Sockets.fds[fd];
+    var info = FS.streams[fd];
     if (!info) return -1;
     if (addr) {
       setValue(addr + Sockets.sockaddr_in_layout.sin_addr, info.addr, 'i32');
@@ -7500,18 +8013,19 @@ LibraryManager.library = {
     return fd;
   },
 
+  select__deps: ['$FS'],
   select: function(nfds, readfds, writefds, exceptfds, timeout) {
     // readfds are supported,
     // writefds checks socket open status
     // exceptfds not supported
     // timeout is always 0 - fully async
     assert(!exceptfds);
-    
+
     var errorCondition = 0;
 
     function canRead(info) {
-      // make sure hasData exists. 
-      // we do create it when the socket is connected, 
+      // make sure hasData exists.
+      // we do create it when the socket is connected,
       // but other implementations may create it lazily
       if ((info.socket.readyState == WebSocket.CLOSING || info.socket.readyState == WebSocket.CLOSED) && info.inQueue.length == 0) {
         errorCondition = -1;
@@ -7521,8 +8035,8 @@ LibraryManager.library = {
     }
 
     function canWrite(info) {
-      // make sure socket exists. 
-      // we do create it when the socket is connected, 
+      // make sure socket exists.
+      // we do create it when the socket is connected,
       // but other implementations may create it lazily
       if ((info.socket.readyState == WebSocket.CLOSING || info.socket.readyState == WebSocket.CLOSED)) {
         errorCondition = -1;
@@ -7545,7 +8059,7 @@ LibraryManager.library = {
         var mask = 1 << (fd % 32), int = fd < 32 ? srcLow : srcHigh;
         if (int & mask) {
           // index is in the set, check if it is ready for read
-          var info = Sockets.fds[fd];
+          var info = FS.streams[fd];
           if (info && can(info)) {
             // set bit
             fd < 32 ? (dstLow = dstLow | mask) : (dstHigh = dstHigh | mask);
@@ -7567,6 +8081,7 @@ LibraryManager.library = {
       return totalHandles;
     }
   },
+#endif
 
   socketpair__deps: ['__setErrNo', '$ERRNO_CODES'],
   socketpair: function(domain, type, protocol, sv) {
diff --git a/src/library_browser.js b/src/library_browser.js
index 1a79a49b25a43..a690286bf83e2 100644
--- a/src/library_browser.js
+++ b/src/library_browser.js
@@ -6,7 +6,8 @@ mergeInto(LibraryManager.library, {
   $Browser__postset: 'Module["requestFullScreen"] = function(lockPointer, resizeCanvas) { Browser.requestFullScreen(lockPointer, resizeCanvas) };\n' + // exports
                      'Module["requestAnimationFrame"] = function(func) { Browser.requestAnimationFrame(func) };\n' +
                      'Module["pauseMainLoop"] = function() { Browser.mainLoop.pause() };\n' +
-                     'Module["resumeMainLoop"] = function() { Browser.mainLoop.resume() };\n',
+                     'Module["resumeMainLoop"] = function() { Browser.mainLoop.resume() };\n' +
+                     'Module["getUserMedia"] = function() { Browser.getUserMedia() }',
   $Browser: {
     mainLoop: {
       scheduler: null,
@@ -346,7 +347,7 @@ mergeInto(LibraryManager.library, {
       canvas.requestFullScreen = canvas['requestFullScreen'] ||
                                  canvas['mozRequestFullScreen'] ||
                                  (canvas['webkitRequestFullScreen'] ? function() { canvas['webkitRequestFullScreen'](Element['ALLOW_KEYBOARD_INPUT']) } : null);
-      canvas.requestFullScreen(); 
+      canvas.requestFullScreen();
     },
 
     requestAnimationFrame: function(func) {
@@ -385,6 +386,14 @@ mergeInto(LibraryManager.library, {
       }, timeout);
     },
 
+    getUserMedia: function(func) {
+      if(!window.getUserMedia) {
+        window.getUserMedia = navigator['getUserMedia'] ||
+                              navigator['mozGetUserMedia'];
+      }
+      window.getUserMedia(func);
+    },
+
     getMovementX: function(event) {
       return event['movementX'] ||
              event['mozMovementX'] ||
@@ -499,7 +508,7 @@ mergeInto(LibraryManager.library, {
       {{{ makeSetValue('SDL.screen+Runtime.QUANTUM_SIZE*0', '0', 'flags', 'i32') }}}
       Browser.updateResizeListeners();
     },
-    
+
     setWindowedCanvasSize: function() {
       var canvas = Module['canvas'];
       canvas.width = this.windowedWidth;
@@ -509,7 +518,7 @@ mergeInto(LibraryManager.library, {
       {{{ makeSetValue('SDL.screen+Runtime.QUANTUM_SIZE*0', '0', 'flags', 'i32') }}}
       Browser.updateResizeListeners();
     }
-    
+
   },
 
   emscripten_async_wget: function(url, file, onload, onerror) {
@@ -546,11 +555,11 @@ mergeInto(LibraryManager.library, {
     var _request = Pointer_stringify(request);
     var _param = Pointer_stringify(param);
     var index = _file.lastIndexOf('/');
-     
+
     var http = new XMLHttpRequest();
     http.open(_request, _url, true);
     http.responseType = 'arraybuffer';
-    
+
     // LOAD
     http.onload = function(e) {
       if (http.status == 200) {
@@ -560,20 +569,20 @@ mergeInto(LibraryManager.library, {
         if (onerror) Runtime.dynCall('vii', onerror, [arg, http.status]);
       }
     };
-      
+
     // ERROR
     http.onerror = function(e) {
       if (onerror) Runtime.dynCall('vii', onerror, [arg, http.status]);
     };
-	
+
     // PROGRESS
     http.onprogress = function(e) {
       var percentComplete = (e.position / e.totalSize)*100;
       if (onprogress) Runtime.dynCall('vii', onprogress, [arg, percentComplete]);
     };
-	  
+
     // Useful because the browser can limit the number of redirection
-    try {  
+    try {
       if (http.channel instanceof Ci.nsIHttpChannel)
       http.channel.redirectionLimit = 0;
     } catch (ex) { /* whatever */ }
@@ -588,7 +597,7 @@ mergeInto(LibraryManager.library, {
       http.send(null);
     }
   },
-  
+
   emscripten_async_prepare: function(file, onload, onerror) {
     var _file = Pointer_stringify(file);
     var data = FS.analyzePath(_file);
diff --git a/src/library_gl.js b/src/library_gl.js
index 46055304f131b..1fa0cc9c52d04 100644
--- a/src/library_gl.js
+++ b/src/library_gl.js
@@ -3965,7 +3965,7 @@ var LibraryGL = {
   },
 
   // Vertex array object (VAO) support. TODO: when the WebGL extension is popular, use that and remove this code and GL.vaos
-  glGenVertexArrays__deps: ['$GLEMulation'],
+  glGenVertexArrays__deps: ['$GLEmulation'],
   glGenVertexArrays__sig: 'vii',
   glGenVertexArrays: function(n, vaos) {
     for (var i = 0; i < n; i++) {
diff --git a/src/library_sdl.js b/src/library_sdl.js
index 4f871f9de8548..e8419536928a0 100644
--- a/src/library_sdl.js
+++ b/src/library_sdl.js
@@ -936,7 +936,10 @@ var LibrarySDL = {
   },
 
   SDL_GetError: function() {
-    return allocate(intArrayFromString("unknown SDL-emscripten error"), 'i8');
+    if (!SDL.errorMessage) {
+      SDL.errorMessage = allocate(intArrayFromString("unknown SDL-emscripten error"), 'i8', ALLOC_NORMAL);
+    }
+    return SDL.errorMessage;
   },
 
   SDL_CreateRGBSurface: function(flags, width, height, depth, rmask, gmask, bmask, amask) {
@@ -1160,9 +1163,6 @@ var LibrarySDL = {
   SDL_OpenAudio: function(desired, obtained) {
     SDL.allocateChannels(32);
 
-    // FIXME: Assumes 16-bit audio
-    assert(obtained === 0, 'Cannot return obtained SDL audio params');
-
     SDL.audio = {
       freq: {{{ makeGetValue('desired', 'SDL.structs.AudioSpec.freq', 'i32', 0, 1) }}},
       format: {{{ makeGetValue('desired', 'SDL.structs.AudioSpec.format', 'i16', 0, 1) }}},
@@ -1174,6 +1174,16 @@ var LibrarySDL = {
       timer: null
     };
 
+    if (obtained) {
+      {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.freq', 'SDL.audio.freq', 'i32') }}}; // no good way for us to know if the browser can really handle this
+      {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.format', 33040, 'i16') }}}; // float, signed, 16-bit
+      {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.channels', 'SDL.audio.channels', 'i8') }}};
+      {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.silence', makeGetValue('desired', 'SDL.structs.AudioSpec.silence', 'i8', 0, 1), 'i8') }}}; // unclear if browsers can provide this
+      {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.samples', 'SDL.audio.samples', 'i16') }}};
+      {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.callback', 'SDL.audio.callback', '*') }}};
+      {{{ makeSetValue('obtained', 'SDL.structs.AudioSpec.userdata', 'SDL.audio.userdata', '*') }}};
+    }
+
     var totalSamples = SDL.audio.samples*SDL.audio.channels;
     SDL.audio.bufferSize = totalSamples*2; // hardcoded 16-bit audio
     SDL.audio.buffer = _malloc(SDL.audio.bufferSize);
diff --git a/src/parseTools.js b/src/parseTools.js
index 090d85acafcd3..3949491edde4c 100644
--- a/src/parseTools.js
+++ b/src/parseTools.js
@@ -2047,8 +2047,9 @@ function processMathop(item) {
 
   if ((type == 'i64' || paramTypes[0] == 'i64' || paramTypes[1] == 'i64' || idents[1] == '(i64)' || rawBits > 32) && USE_TYPED_ARRAYS == 2) {
     // this code assumes i64 for the most part
-    if (ASSERTIONS && rawBits < 64) {
-      warnOnce('processMathop processing illegal non-i64 value: ' + [type, paramTypes, idents])
+    if (ASSERTIONS && rawBits > 1 && rawBits < 64) {
+      warnOnce('processMathop processing illegal non-i64 value');
+      if (VERBOSE) printErr([op, item.type, rawBits, type, paramTypes, idents]);
     }
 
     var warnI64_1 = function() {
diff --git a/src/relooper/Relooper.cpp b/src/relooper/Relooper.cpp
index 8c72b0a601ec1..1939831025fad 100644
--- a/src/relooper/Relooper.cpp
+++ b/src/relooper/Relooper.cpp
@@ -522,14 +522,60 @@ void Relooper::Calculate(Block *Entry) {
         }
       }
 
+#if 0
+      // We can avoid multiple next entries by hoisting them into the loop.
+      if (NextEntries.size() > 1) {
+        BlockBlockSetMap IndependentGroups;
+        FindIndependentGroups(NextEntries, IndependentGroups, &InnerBlocks);
+
+        while (IndependentGroups.size() > 0 && NextEntries.size() > 1) {
+          Block *Min = NULL;
+          int MinSize = 0;
+          for (BlockBlockSetMap::iterator iter = IndependentGroups.begin(); iter != IndependentGroups.end(); iter++) {
+            Block *Entry = iter->first;
+            BlockSet &Blocks = iter->second;
+            if (!Min || Blocks.size() < MinSize) { // TODO: code size, not # of blocks
+              Min = Entry;
+              MinSize = Blocks.size();
+            }
+          }
+          // check how many new entries this would cause
+          BlockSet &Hoisted = IndependentGroups[Min];
+          bool abort = false;
+          for (BlockSet::iterator iter = Hoisted.begin(); iter != Hoisted.end() && !abort; iter++) {
+            Block *Curr = *iter;
+            for (BlockBranchMap::iterator iter = Curr->BranchesOut.begin(); iter != Curr->BranchesOut.end(); iter++) {
+              Block *Target = iter->first;
+              if (Hoisted.find(Target) == Hoisted.end() && NextEntries.find(Target) == NextEntries.end()) {
+                // abort this hoisting
+                abort = true;
+                break;
+              }
+            }
+          }
+          if (abort) {
+            IndependentGroups.erase(Min);
+            continue;
+          }
+          // hoist this entry
+          PrintDebug("hoisting %d into loop\n", Min->Id);
+          NextEntries.erase(Min);
+          for (BlockSet::iterator iter = Hoisted.begin(); iter != Hoisted.end(); iter++) {
+            Block *Curr = *iter;
+            InnerBlocks.insert(Curr);
+            Blocks.erase(Curr);
+          }
+          IndependentGroups.erase(Min);
+        }
+      }
+#endif
+
       PrintDebug("creating loop block:\n");
       DebugDump(InnerBlocks, "  inner blocks:");
       DebugDump(Entries, "  inner entries:");
       DebugDump(Blocks, "  outer blocks:");
       DebugDump(NextEntries, "  outer entries:");
 
-      // TODO: Optionally hoist additional blocks into the loop
-
       LoopShape *Loop = new LoopShape();
       Notice(Loop);
 
@@ -551,7 +597,8 @@ void Relooper::Calculate(Block *Entry) {
     // For each entry, find the independent group reachable by it. The independent group is
     // the entry itself, plus all the blocks it can reach that cannot be directly reached by another entry. Note that we
     // ignore directly reaching the entry itself by another entry.
-    void FindIndependentGroups(BlockSet &Blocks, BlockSet &Entries, BlockBlockSetMap& IndependentGroups) {
+    //   @param Ignore - previous blocks that are irrelevant
+    void FindIndependentGroups(BlockSet &Entries, BlockBlockSetMap& IndependentGroups, BlockSet *Ignore=NULL) {
       typedef std::map<Block*, Block*> BlockBlockMap;
 
       struct HelperClass {
@@ -639,6 +686,7 @@ void Relooper::Calculate(Block *Entry) {
           Block *Child = *iter;
           for (BlockSet::iterator iter = Child->BranchesIn.begin(); iter != Child->BranchesIn.end(); iter++) {
             Block *Parent = *iter;
+            if (Ignore && Ignore->find(Parent) != Ignore->end()) continue;
             if (Helper.Ownership[Parent] != Helper.Ownership[Child]) {
               ToInvalidate.push_back(Child);
             }
@@ -756,7 +804,7 @@ void Relooper::Calculate(Block *Entry) {
         // independent blocks from an entry/ies. It is important to remove through
         // multiples as opposed to looping since the former is more performant.
         BlockBlockSetMap IndependentGroups;
-        FindIndependentGroups(Blocks, *Entries, IndependentGroups);
+        FindIndependentGroups(*Entries, IndependentGroups);
 
         PrintDebug("Independent groups: %d\n", IndependentGroups.size());
 
diff --git a/src/settings.js b/src/settings.js
index c156a40c831d5..7e6079966bad3 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -36,6 +36,7 @@ var ASSERTIONS = 1; // Whether we should add runtime assertions, for example to
                     // exceed it's size, whether all allocations (stack and static) are
                     // of positive size, etc., whether we should throw if we encounter a bad __label__, i.e.,
                     // if code flow runs into a fault
+                    // ASSERTIONS == 2 gives even more runtime checks
 var VERBOSE = 0; // When set to 1, will generate more verbose output during compilation.
 
 var INVOKE_RUN = 1; // Whether we will run the main() function. Disable if you embed the generated
@@ -174,6 +175,7 @@ var LIBRARY_DEBUG = 0; // Print out when we enter a library call (library*.js).
                        // want it back. A simple way to set it in C++ is
                        //   emscripten_run_script("Runtime.debug = ...;");
 var SOCKET_DEBUG = 0; // Log out socket/network data transfer.
+var SOCKET_WEBRTC = 0; // Select socket backend, either webrtc or websockets.
 
 var OPENAL_DEBUG = 0; // Print out debugging information from our OpenAL implementation.
 
@@ -193,7 +195,7 @@ var DISABLE_EXCEPTION_CATCHING = 0; // Disables generating code to actually catc
                                     // introduce silent failures, which is good).
                                     // DISABLE_EXCEPTION_CATCHING = 0 - generate code to actually catch exceptions
                                     // DISABLE_EXCEPTION_CATCHING = 1 - disable exception catching at all
-                                    // DISABLE_EXCEPTION_CATCHING = 2 - disable exception catching, but enables 
+                                    // DISABLE_EXCEPTION_CATCHING = 2 - disable exception catching, but enables
                                     // catching in whitelist
                                     // TODO: Make this also remove cxa_begin_catch etc., optimize relooper
                                     //       for it, etc. (perhaps do all of this as preprocessing on .ll?)
diff --git a/src/shell.js b/src/shell.js
index 20db25a72fb72..873bcc65aa73d 100644
--- a/src/shell.js
+++ b/src/shell.js
@@ -12,6 +12,10 @@ var ENVIRONMENT_IS_WEB = typeof window === 'object';
 var ENVIRONMENT_IS_WORKER = typeof importScripts === 'function';
 var ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER;
 
+if (typeof module === "object") {
+  module.exports = Module;
+}
+
 if (ENVIRONMENT_IS_NODE) {
   // Expose functionality in the same simple way that the shells work
   // Note that we pollute the global namespace here, otherwise we break in node
diff --git a/src/socket.io.js b/src/socket.io.js
new file mode 100644
index 0000000000000..3fe6a5a79fb6d
--- /dev/null
+++ b/src/socket.io.js
@@ -0,0 +1,3870 @@
+/*! Socket.IO.js build:0.9.11, development. Copyright(c) 2011 LearnBoost <dev@learnboost.com> MIT Licensed */
+
+(function(global) {
+
+var io = ('undefined' === typeof module ? {} : module.exports);
+
+/**
+ * socket.io
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+(function (exports) {
+
+  /**
+   * IO namespace.
+   *
+   * @namespace
+   */
+
+  var io = exports;
+
+  /**
+   * Socket.IO version
+   *
+   * @api public
+   */
+
+  io.version = '0.9.11';
+
+  /**
+   * Protocol implemented.
+   *
+   * @api public
+   */
+
+  io.protocol = 1;
+
+  /**
+   * Available transports, these will be populated with the available transports
+   *
+   * @api public
+   */
+
+  io.transports = [];
+
+  /**
+   * Keep track of jsonp callbacks.
+   *
+   * @api private
+   */
+
+  io.j = [];
+
+  /**
+   * Keep track of our io.Sockets
+   *
+   * @api private
+   */
+  io.sockets = {};
+
+
+  /**
+   * Manages connections to hosts.
+   *
+   * @param {String} uri
+   * @Param {Boolean} force creation of new socket (defaults to false)
+   * @api public
+   */
+
+  io.connect = function (host, details) {
+    var uri = io.util.parseUri(host)
+      , uuri
+      , socket;
+
+    if (global && global.location) {
+      uri.protocol = uri.protocol || global.location.protocol.slice(0, -1);
+      uri.host = uri.host || (global.document
+        ? global.document.domain : global.location.hostname);
+      uri.port = uri.port || global.location.port;
+    }
+
+    uuri = io.util.uniqueUri(uri);
+
+    var options = {
+        host: uri.host
+      , secure: 'https' == uri.protocol
+      , port: uri.port || ('https' == uri.protocol ? 443 : 80)
+      , query: uri.query || ''
+    };
+
+    io.util.merge(options, details);
+
+    if (options['force new connection'] || !io.sockets[uuri]) {
+      socket = new io.Socket(options);
+    }
+
+    if (!options['force new connection'] && socket) {
+      io.sockets[uuri] = socket;
+    }
+
+    socket = socket || io.sockets[uuri];
+
+    // if path is different from '' or /
+    return socket.of(uri.path.length > 1 ? uri.path : '');
+  };
+
+})('object' === typeof module ? module.exports : (io = {}));
+/**
+ * socket.io
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+(function (exports) {
+
+  /**
+   * Utilities namespace.
+   *
+   * @namespace
+   */
+
+  var util = exports.util = {};
+
+  /**
+   * Parses an URI
+   *
+   * @author Steven Levithan <stevenlevithan.com> (MIT license)
+   * @api public
+   */
+
+  var re = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
+
+  var parts = ['source', 'protocol', 'authority', 'userInfo', 'user', 'password',
+               'host', 'port', 'relative', 'path', 'directory', 'file', 'query',
+               'anchor'];
+
+  util.parseUri = function (str) {
+    var m = re.exec(str || '')
+      , uri = {}
+      , i = 14;
+
+    while (i--) {
+      uri[parts[i]] = m[i] || '';
+    }
+
+    return uri;
+  };
+
+  /**
+   * Produces a unique url that identifies a Socket.IO connection.
+   *
+   * @param {Object} uri
+   * @api public
+   */
+
+  util.uniqueUri = function (uri) {
+    var protocol = uri.protocol
+      , host = uri.host
+      , port = uri.port;
+
+    if ('document' in global) {
+      host = host || document.domain;
+      port = port || (protocol == 'https'
+        && document.location.protocol !== 'https:' ? 443 : document.location.port);
+    } else {
+      host = host || 'localhost';
+
+      if (!port && protocol == 'https') {
+        port = 443;
+      }
+    }
+
+    return (protocol || 'http') + '://' + host + ':' + (port || 80);
+  };
+
+  /**
+   * Mergest 2 query strings in to once unique query string
+   *
+   * @param {String} base
+   * @param {String} addition
+   * @api public
+   */
+
+  util.query = function (base, addition) {
+    var query = util.chunkQuery(base || '')
+      , components = [];
+
+    util.merge(query, util.chunkQuery(addition || ''));
+    for (var part in query) {
+      if (query.hasOwnProperty(part)) {
+        components.push(part + '=' + query[part]);
+      }
+    }
+
+    return components.length ? '?' + components.join('&') : '';
+  };
+
+  /**
+   * Transforms a querystring in to an object
+   *
+   * @param {String} qs
+   * @api public
+   */
+
+  util.chunkQuery = function (qs) {
+    var query = {}
+      , params = qs.split('&')
+      , i = 0
+      , l = params.length
+      , kv;
+
+    for (; i < l; ++i) {
+      kv = params[i].split('=');
+      if (kv[0]) {
+        query[kv[0]] = kv[1];
+      }
+    }
+
+    return query;
+  };
+
+  /**
+   * Executes the given function when the page is loaded.
+   *
+   *     io.util.load(function () { console.log('page loaded'); });
+   *
+   * @param {Function} fn
+   * @api public
+   */
+
+  var pageLoaded = false;
+
+  util.load = function (fn) {
+    if ('document' in global && document.readyState === 'complete' || pageLoaded) {
+      return fn();
+    }
+
+    util.on(global, 'load', fn, false);
+  };
+
+  /**
+   * Adds an event.
+   *
+   * @api private
+   */
+
+  util.on = function (element, event, fn, capture) {
+    if (element.attachEvent) {
+      element.attachEvent('on' + event, fn);
+    } else if (element.addEventListener) {
+      element.addEventListener(event, fn, capture);
+    }
+  };
+
+  /**
+   * Generates the correct `XMLHttpRequest` for regular and cross domain requests.
+   *
+   * @param {Boolean} [xdomain] Create a request that can be used cross domain.
+   * @returns {XMLHttpRequest|false} If we can create a XMLHttpRequest.
+   * @api private
+   */
+
+  util.request = function (xdomain) {
+
+    if (xdomain && 'undefined' != typeof XDomainRequest && !util.ua.hasCORS) {
+      return new XDomainRequest();
+    }
+
+    if ('undefined' != typeof XMLHttpRequest && (!xdomain || util.ua.hasCORS)) {
+      return new XMLHttpRequest();
+    }
+
+    if (!xdomain) {
+      try {
+        return new window[(['Active'].concat('Object').join('X'))]('Microsoft.XMLHTTP');
+      } catch(e) { }
+    }
+
+    return null;
+  };
+
+  /**
+   * XHR based transport constructor.
+   *
+   * @constructor
+   * @api public
+   */
+
+  /**
+   * Change the internal pageLoaded value.
+   */
+
+  if ('undefined' != typeof window) {
+    util.load(function () {
+      pageLoaded = true;
+    });
+  }
+
+  /**
+   * Defers a function to ensure a spinner is not displayed by the browser
+   *
+   * @param {Function} fn
+   * @api public
+   */
+
+  util.defer = function (fn) {
+    if (!util.ua.webkit || 'undefined' != typeof importScripts) {
+      return fn();
+    }
+
+    util.load(function () {
+      setTimeout(fn, 100);
+    });
+  };
+
+  /**
+   * Merges two objects.
+   *
+   * @api public
+   */
+
+  util.merge = function merge (target, additional, deep, lastseen) {
+    var seen = lastseen || []
+      , depth = typeof deep == 'undefined' ? 2 : deep
+      , prop;
+
+    for (prop in additional) {
+      if (additional.hasOwnProperty(prop) && util.indexOf(seen, prop) < 0) {
+        if (typeof target[prop] !== 'object' || !depth) {
+          target[prop] = additional[prop];
+          seen.push(additional[prop]);
+        } else {
+          util.merge(target[prop], additional[prop], depth - 1, seen);
+        }
+      }
+    }
+
+    return target;
+  };
+
+  /**
+   * Merges prototypes from objects
+   *
+   * @api public
+   */
+
+  util.mixin = function (ctor, ctor2) {
+    util.merge(ctor.prototype, ctor2.prototype);
+  };
+
+  /**
+   * Shortcut for prototypical and static inheritance.
+   *
+   * @api private
+   */
+
+  util.inherit = function (ctor, ctor2) {
+    function f() {};
+    f.prototype = ctor2.prototype;
+    ctor.prototype = new f;
+  };
+
+  /**
+   * Checks if the given object is an Array.
+   *
+   *     io.util.isArray([]); // true
+   *     io.util.isArray({}); // false
+   *
+   * @param Object obj
+   * @api public
+   */
+
+  util.isArray = Array.isArray || function (obj) {
+    return Object.prototype.toString.call(obj) === '[object Array]';
+  };
+
+  /**
+   * Intersects values of two arrays into a third
+   *
+   * @api public
+   */
+
+  util.intersect = function (arr, arr2) {
+    var ret = []
+      , longest = arr.length > arr2.length ? arr : arr2
+      , shortest = arr.length > arr2.length ? arr2 : arr;
+
+    for (var i = 0, l = shortest.length; i < l; i++) {
+      if (~util.indexOf(longest, shortest[i]))
+        ret.push(shortest[i]);
+    }
+
+    return ret;
+  };
+
+  /**
+   * Array indexOf compatibility.
+   *
+   * @see bit.ly/a5Dxa2
+   * @api public
+   */
+
+  util.indexOf = function (arr, o, i) {
+
+    for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0;
+         i < j && arr[i] !== o; i++) {}
+
+    return j <= i ? -1 : i;
+  };
+
+  /**
+   * Converts enumerables to array.
+   *
+   * @api public
+   */
+
+  util.toArray = function (enu) {
+    var arr = [];
+
+    for (var i = 0, l = enu.length; i < l; i++)
+      arr.push(enu[i]);
+
+    return arr;
+  };
+
+  /**
+   * UA / engines detection namespace.
+   *
+   * @namespace
+   */
+
+  util.ua = {};
+
+  /**
+   * Whether the UA supports CORS for XHR.
+   *
+   * @api public
+   */
+
+  util.ua.hasCORS = 'undefined' != typeof XMLHttpRequest && (function () {
+    try {
+      var a = new XMLHttpRequest();
+    } catch (e) {
+      return false;
+    }
+
+    return a.withCredentials != undefined;
+  })();
+
+  /**
+   * Detect webkit.
+   *
+   * @api public
+   */
+
+  util.ua.webkit = 'undefined' != typeof navigator
+    && /webkit/i.test(navigator.userAgent);
+
+   /**
+   * Detect iPad/iPhone/iPod.
+   *
+   * @api public
+   */
+
+  util.ua.iDevice = 'undefined' != typeof navigator
+      && /iPad|iPhone|iPod/i.test(navigator.userAgent);
+
+})('undefined' != typeof io ? io : module.exports);
+/**
+ * socket.io
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+(function (exports, io) {
+
+  /**
+   * Expose constructor.
+   */
+
+  exports.EventEmitter = EventEmitter;
+
+  /**
+   * Event emitter constructor.
+   *
+   * @api public.
+   */
+
+  function EventEmitter () {};
+
+  /**
+   * Adds a listener
+   *
+   * @api public
+   */
+
+  EventEmitter.prototype.on = function (name, fn) {
+    if (!this.$events) {
+      this.$events = {};
+    }
+
+    if (!this.$events[name]) {
+      this.$events[name] = fn;
+    } else if (io.util.isArray(this.$events[name])) {
+      this.$events[name].push(fn);
+    } else {
+      this.$events[name] = [this.$events[name], fn];
+    }
+
+    return this;
+  };
+
+  EventEmitter.prototype.addListener = EventEmitter.prototype.on;
+
+  /**
+   * Adds a volatile listener.
+   *
+   * @api public
+   */
+
+  EventEmitter.prototype.once = function (name, fn) {
+    var self = this;
+
+    function on () {
+      self.removeListener(name, on);
+      fn.apply(this, arguments);
+    };
+
+    on.listener = fn;
+    this.on(name, on);
+
+    return this;
+  };
+
+  /**
+   * Removes a listener.
+   *
+   * @api public
+   */
+
+  EventEmitter.prototype.removeListener = function (name, fn) {
+    if (this.$events && this.$events[name]) {
+      var list = this.$events[name];
+
+      if (io.util.isArray(list)) {
+        var pos = -1;
+
+        for (var i = 0, l = list.length; i < l; i++) {
+          if (list[i] === fn || (list[i].listener && list[i].listener === fn)) {
+            pos = i;
+            break;
+          }
+        }
+
+        if (pos < 0) {
+          return this;
+        }
+
+        list.splice(pos, 1);
+
+        if (!list.length) {
+          delete this.$events[name];
+        }
+      } else if (list === fn || (list.listener && list.listener === fn)) {
+        delete this.$events[name];
+      }
+    }
+
+    return this;
+  };
+
+  /**
+   * Removes all listeners for an event.
+   *
+   * @api public
+   */
+
+  EventEmitter.prototype.removeAllListeners = function (name) {
+    if (name === undefined) {
+      this.$events = {};
+      return this;
+    }
+
+    if (this.$events && this.$events[name]) {
+      this.$events[name] = null;
+    }
+
+    return this;
+  };
+
+  /**
+   * Gets all listeners for a certain event.
+   *
+   * @api publci
+   */
+
+  EventEmitter.prototype.listeners = function (name) {
+    if (!this.$events) {
+      this.$events = {};
+    }
+
+    if (!this.$events[name]) {
+      this.$events[name] = [];
+    }
+
+    if (!io.util.isArray(this.$events[name])) {
+      this.$events[name] = [this.$events[name]];
+    }
+
+    return this.$events[name];
+  };
+
+  /**
+   * Emits an event.
+   *
+   * @api public
+   */
+
+  EventEmitter.prototype.emit = function (name) {
+    if (!this.$events) {
+      return false;
+    }
+
+    var handler = this.$events[name];
+
+    if (!handler) {
+      return false;
+    }
+
+    var args = Array.prototype.slice.call(arguments, 1);
+
+    if ('function' == typeof handler) {
+      handler.apply(this, args);
+    } else if (io.util.isArray(handler)) {
+      var listeners = handler.slice();
+
+      for (var i = 0, l = listeners.length; i < l; i++) {
+        listeners[i].apply(this, args);
+      }
+    } else {
+      return false;
+    }
+
+    return true;
+  };
+
+})(
+    'undefined' != typeof io ? io : module.exports
+  , 'undefined' != typeof io ? io : module.parent.exports
+);
+
+/**
+ * socket.io
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+/**
+ * Based on JSON2 (http://www.JSON.org/js.html).
+ */
+
+(function (exports, nativeJSON) {
+  "use strict";
+
+  // use native JSON if it's available
+  if (nativeJSON && nativeJSON.parse){
+    return exports.JSON = {
+      parse: nativeJSON.parse
+    , stringify: nativeJSON.stringify
+    };
+  }
+
+  var JSON = exports.JSON = {};
+
+  function f(n) {
+      // Format integers to have at least two digits.
+      return n < 10 ? '0' + n : n;
+  }
+
+  function date(d, key) {
+    return isFinite(d.valueOf()) ?
+        d.getUTCFullYear()     + '-' +
+        f(d.getUTCMonth() + 1) + '-' +
+        f(d.getUTCDate())      + 'T' +
+        f(d.getUTCHours())     + ':' +
+        f(d.getUTCMinutes())   + ':' +
+        f(d.getUTCSeconds())   + 'Z' : null;
+  };
+
+  var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+      escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+      gap,
+      indent,
+      meta = {    // table of character substitutions
+          '\b': '\\b',
+          '\t': '\\t',
+          '\n': '\\n',
+          '\f': '\\f',
+          '\r': '\\r',
+          '"' : '\\"',
+          '\\': '\\\\'
+      },
+      rep;
+
+
+  function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+      escapable.lastIndex = 0;
+      return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
+          var c = meta[a];
+          return typeof c === 'string' ? c :
+              '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+      }) + '"' : '"' + string + '"';
+  }
+
+
+  function str(key, holder) {
+
+// Produce a string from holder[key].
+
+      var i,          // The loop counter.
+          k,          // The member key.
+          v,          // The member value.
+          length,
+          mind = gap,
+          partial,
+          value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+      if (value instanceof Date) {
+          value = date(key);
+      }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+      if (typeof rep === 'function') {
+          value = rep.call(holder, key, value);
+      }
+
+// What happens next depends on the value's type.
+
+      switch (typeof value) {
+      case 'string':
+          return quote(value);
+
+      case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+          return isFinite(value) ? String(value) : 'null';
+
+      case 'boolean':
+      case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+          return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+      case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+          if (!value) {
+              return 'null';
+          }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+          gap += indent;
+          partial = [];
+
+// Is the value an array?
+
+          if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+              length = value.length;
+              for (i = 0; i < length; i += 1) {
+                  partial[i] = str(i, value) || 'null';
+              }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+              v = partial.length === 0 ? '[]' : gap ?
+                  '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :
+                  '[' + partial.join(',') + ']';
+              gap = mind;
+              return v;
+          }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+          if (rep && typeof rep === 'object') {
+              length = rep.length;
+              for (i = 0; i < length; i += 1) {
+                  if (typeof rep[i] === 'string') {
+                      k = rep[i];
+                      v = str(k, value);
+                      if (v) {
+                          partial.push(quote(k) + (gap ? ': ' : ':') + v);
+                      }
+                  }
+              }
+          } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+              for (k in value) {
+                  if (Object.prototype.hasOwnProperty.call(value, k)) {
+                      v = str(k, value);
+                      if (v) {
+                          partial.push(quote(k) + (gap ? ': ' : ':') + v);
+                      }
+                  }
+              }
+          }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+          v = partial.length === 0 ? '{}' : gap ?
+              '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' :
+              '{' + partial.join(',') + '}';
+          gap = mind;
+          return v;
+      }
+  }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+  JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+      var i;
+      gap = '';
+      indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+      if (typeof space === 'number') {
+          for (i = 0; i < space; i += 1) {
+              indent += ' ';
+          }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+      } else if (typeof space === 'string') {
+          indent = space;
+      }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+      rep = replacer;
+      if (replacer && typeof replacer !== 'function' &&
+              (typeof replacer !== 'object' ||
+              typeof replacer.length !== 'number')) {
+          throw new Error('JSON.stringify');
+      }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+      return str('', {'': value});
+  };
+
+// If the JSON object does not yet have a parse method, give it one.
+
+  JSON.parse = function (text, reviver) {
+  // The parse method takes a text and an optional reviver function, and returns
+  // a JavaScript value if the text is a valid JSON text.
+
+      var j;
+
+      function walk(holder, key) {
+
+  // The walk method is used to recursively walk the resulting structure so
+  // that modifications can be made.
+
+          var k, v, value = holder[key];
+          if (value && typeof value === 'object') {
+              for (k in value) {
+                  if (Object.prototype.hasOwnProperty.call(value, k)) {
+                      v = walk(value, k);
+                      if (v !== undefined) {
+                          value[k] = v;
+                      } else {
+                          delete value[k];
+                      }
+                  }
+              }
+          }
+          return reviver.call(holder, key, value);
+      }
+
+
+  // Parsing happens in four stages. In the first stage, we replace certain
+  // Unicode characters with escape sequences. JavaScript handles many characters
+  // incorrectly, either silently deleting them, or treating them as line endings.
+
+      text = String(text);
+      cx.lastIndex = 0;
+      if (cx.test(text)) {
+          text = text.replace(cx, function (a) {
+              return '\\u' +
+                  ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+          });
+      }
+
+  // In the second stage, we run the text against regular expressions that look
+  // for non-JSON patterns. We are especially concerned with '()' and 'new'
+  // because they can cause invocation, and '=' because it can cause mutation.
+  // But just to be safe, we want to reject all unexpected forms.
+
+  // We split the second stage into 4 regexp operations in order to work around
+  // crippling inefficiencies in IE's and Safari's regexp engines. First we
+  // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+  // replace all simple value tokens with ']' characters. Third, we delete all
+  // open brackets that follow a colon or comma or that begin the text. Finally,
+  // we look to see that the remaining characters are only whitespace or ']' or
+  // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+      if (/^[\],:{}\s]*$/
+              .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
+                  .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
+                  .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+  // In the third stage we use the eval function to compile the text into a
+  // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+  // in JavaScript: it can begin a block or an object literal. We wrap the text
+  // in parens to eliminate the ambiguity.
+
+          j = eval('(' + text + ')');
+
+  // In the optional fourth stage, we recursively walk the new structure, passing
+  // each name/value pair to a reviver function for possible transformation.
+
+          return typeof reviver === 'function' ?
+              walk({'': j}, '') : j;
+      }
+
+  // If the text is not JSON parseable, then a SyntaxError is thrown.
+
+      throw new SyntaxError('JSON.parse');
+  };
+
+})(
+    'undefined' != typeof io ? io : module.exports
+  , typeof JSON !== 'undefined' ? JSON : undefined
+);
+
+/**
+ * socket.io
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+(function (exports, io) {
+
+  /**
+   * Parser namespace.
+   *
+   * @namespace
+   */
+
+  var parser = exports.parser = {};
+
+  /**
+   * Packet types.
+   */
+
+  var packets = parser.packets = [
+      'disconnect'
+    , 'connect'
+    , 'heartbeat'
+    , 'message'
+    , 'json'
+    , 'event'
+    , 'ack'
+    , 'error'
+    , 'noop'
+  ];
+
+  /**
+   * Errors reasons.
+   */
+
+  var reasons = parser.reasons = [
+      'transport not supported'
+    , 'client not handshaken'
+    , 'unauthorized'
+  ];
+
+  /**
+   * Errors advice.
+   */
+
+  var advice = parser.advice = [
+      'reconnect'
+  ];
+
+  /**
+   * Shortcuts.
+   */
+
+  var JSON = io.JSON
+    , indexOf = io.util.indexOf;
+
+  /**
+   * Encodes a packet.
+   *
+   * @api private
+   */
+
+  parser.encodePacket = function (packet) {
+    var type = indexOf(packets, packet.type)
+      , id = packet.id || ''
+      , endpoint = packet.endpoint || ''
+      , ack = packet.ack
+      , data = null;
+
+    switch (packet.type) {
+      case 'error':
+        var reason = packet.reason ? indexOf(reasons, packet.reason) : ''
+          , adv = packet.advice ? indexOf(advice, packet.advice) : '';
+
+        if (reason !== '' || adv !== '')
+          data = reason + (adv !== '' ? ('+' + adv) : '');
+
+        break;
+
+      case 'message':
+        if (packet.data !== '')
+          data = packet.data;
+        break;
+
+      case 'event':
+        var ev = { name: packet.name };
+
+        if (packet.args && packet.args.length) {
+          ev.args = packet.args;
+        }
+
+        data = JSON.stringify(ev);
+        break;
+
+      case 'json':
+        data = JSON.stringify(packet.data);
+        break;
+
+      case 'connect':
+        if (packet.qs)
+          data = packet.qs;
+        break;
+
+      case 'ack':
+        data = packet.ackId
+          + (packet.args && packet.args.length
+              ? '+' + JSON.stringify(packet.args) : '');
+        break;
+    }
+
+    // construct packet with required fragments
+    var encoded = [
+        type
+      , id + (ack == 'data' ? '+' : '')
+      , endpoint
+    ];
+
+    // data fragment is optional
+    if (data !== null && data !== undefined)
+      encoded.push(data);
+
+    return encoded.join(':');
+  };
+
+  /**
+   * Encodes multiple messages (payload).
+   *
+   * @param {Array} messages
+   * @api private
+   */
+
+  parser.encodePayload = function (packets) {
+    var decoded = '';
+
+    if (packets.length == 1)
+      return packets[0];
+
+    for (var i = 0, l = packets.length; i < l; i++) {
+      var packet = packets[i];
+      decoded += '\ufffd' + packet.length + '\ufffd' + packets[i];
+    }
+
+    return decoded;
+  };
+
+  /**
+   * Decodes a packet
+   *
+   * @api private
+   */
+
+  var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/;
+
+  parser.decodePacket = function (data) {
+    var pieces = data.match(regexp);
+
+    if (!pieces) return {};
+
+    var id = pieces[2] || ''
+      , data = pieces[5] || ''
+      , packet = {
+            type: packets[pieces[1]]
+          , endpoint: pieces[4] || ''
+        };
+
+    // whether we need to acknowledge the packet
+    if (id) {
+      packet.id = id;
+      if (pieces[3])
+        packet.ack = 'data';
+      else
+        packet.ack = true;
+    }
+
+    // handle different packet types
+    switch (packet.type) {
+      case 'error':
+        var pieces = data.split('+');
+        packet.reason = reasons[pieces[0]] || '';
+        packet.advice = advice[pieces[1]] || '';
+        break;
+
+      case 'message':
+        packet.data = data || '';
+        break;
+
+      case 'event':
+        try {
+          var opts = JSON.parse(data);
+          packet.name = opts.name;
+          packet.args = opts.args;
+        } catch (e) { }
+
+        packet.args = packet.args || [];
+        break;
+
+      case 'json':
+        try {
+          packet.data = JSON.parse(data);
+        } catch (e) { }
+        break;
+
+      case 'connect':
+        packet.qs = data || '';
+        break;
+
+      case 'ack':
+        var pieces = data.match(/^([0-9]+)(\+)?(.*)/);
+        if (pieces) {
+          packet.ackId = pieces[1];
+          packet.args = [];
+
+          if (pieces[3]) {
+            try {
+              packet.args = pieces[3] ? JSON.parse(pieces[3]) : [];
+            } catch (e) { }
+          }
+        }
+        break;
+
+      case 'disconnect':
+      case 'heartbeat':
+        break;
+    };
+
+    return packet;
+  };
+
+  /**
+   * Decodes data payload. Detects multiple messages
+   *
+   * @return {Array} messages
+   * @api public
+   */
+
+  parser.decodePayload = function (data) {
+    // IE doesn't like data[i] for unicode chars, charAt works fine
+    if (data.charAt(0) == '\ufffd') {
+      var ret = [];
+
+      for (var i = 1, length = ''; i < data.length; i++) {
+        if (data.charAt(i) == '\ufffd') {
+          ret.push(parser.decodePacket(data.substr(i + 1).substr(0, length)));
+          i += Number(length) + 1;
+          length = '';
+        } else {
+          length += data.charAt(i);
+        }
+      }
+
+      return ret;
+    } else {
+      return [parser.decodePacket(data)];
+    }
+  };
+
+})(
+    'undefined' != typeof io ? io : module.exports
+  , 'undefined' != typeof io ? io : module.parent.exports
+);
+/**
+ * socket.io
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+(function (exports, io) {
+
+  /**
+   * Expose constructor.
+   */
+
+  exports.Transport = Transport;
+
+  /**
+   * This is the transport template for all supported transport methods.
+   *
+   * @constructor
+   * @api public
+   */
+
+  function Transport (socket, sessid) {
+    this.socket = socket;
+    this.sessid = sessid;
+  };
+
+  /**
+   * Apply EventEmitter mixin.
+   */
+
+  io.util.mixin(Transport, io.EventEmitter);
+
+
+  /**
+   * Indicates whether heartbeats is enabled for this transport
+   *
+   * @api private
+   */
+
+  Transport.prototype.heartbeats = function () {
+    return true;
+  };
+
+  /**
+   * Handles the response from the server. When a new response is received
+   * it will automatically update the timeout, decode the message and
+   * forwards the response to the onMessage function for further processing.
+   *
+   * @param {String} data Response from the server.
+   * @api private
+   */
+
+  Transport.prototype.onData = function (data) {
+    this.clearCloseTimeout();
+
+    // If the connection in currently open (or in a reopening state) reset the close
+    // timeout since we have just received data. This check is necessary so
+    // that we don't reset the timeout on an explicitly disconnected connection.
+    if (this.socket.connected || this.socket.connecting || this.socket.reconnecting) {
+      this.setCloseTimeout();
+    }
+
+    if (data !== '') {
+      // todo: we should only do decodePayload for xhr transports
+      var msgs = io.parser.decodePayload(data);
+
+      if (msgs && msgs.length) {
+        for (var i = 0, l = msgs.length; i < l; i++) {
+          this.onPacket(msgs[i]);
+        }
+      }
+    }
+
+    return this;
+  };
+
+  /**
+   * Handles packets.
+   *
+   * @api private
+   */
+
+  Transport.prototype.onPacket = function (packet) {
+    this.socket.setHeartbeatTimeout();
+
+    if (packet.type == 'heartbeat') {
+      return this.onHeartbeat();
+    }
+
+    if (packet.type == 'connect' && packet.endpoint == '') {
+      this.onConnect();
+    }
+
+    if (packet.type == 'error' && packet.advice == 'reconnect') {
+      this.isOpen = false;
+    }
+
+    this.socket.onPacket(packet);
+
+    return this;
+  };
+
+  /**
+   * Sets close timeout
+   *
+   * @api private
+   */
+
+  Transport.prototype.setCloseTimeout = function () {
+    if (!this.closeTimeout) {
+      var self = this;
+
+      this.closeTimeout = setTimeout(function () {
+        self.onDisconnect();
+      }, this.socket.closeTimeout);
+    }
+  };
+
+  /**
+   * Called when transport disconnects.
+   *
+   * @api private
+   */
+
+  Transport.prototype.onDisconnect = function () {
+    if (this.isOpen) this.close();
+    this.clearTimeouts();
+    this.socket.onDisconnect();
+    return this;
+  };
+
+  /**
+   * Called when transport connects
+   *
+   * @api private
+   */
+
+  Transport.prototype.onConnect = function () {
+    this.socket.onConnect();
+    return this;
+  };
+
+  /**
+   * Clears close timeout
+   *
+   * @api private
+   */
+
+  Transport.prototype.clearCloseTimeout = function () {
+    if (this.closeTimeout) {
+      clearTimeout(this.closeTimeout);
+      this.closeTimeout = null;
+    }
+  };
+
+  /**
+   * Clear timeouts
+   *
+   * @api private
+   */
+
+  Transport.prototype.clearTimeouts = function () {
+    this.clearCloseTimeout();
+
+    if (this.reopenTimeout) {
+      clearTimeout(this.reopenTimeout);
+    }
+  };
+
+  /**
+   * Sends a packet
+   *
+   * @param {Object} packet object.
+   * @api private
+   */
+
+  Transport.prototype.packet = function (packet) {
+    this.send(io.parser.encodePacket(packet));
+  };
+
+  /**
+   * Send the received heartbeat message back to server. So the server
+   * knows we are still connected.
+   *
+   * @param {String} heartbeat Heartbeat response from the server.
+   * @api private
+   */
+
+  Transport.prototype.onHeartbeat = function (heartbeat) {
+    this.packet({ type: 'heartbeat' });
+  };
+
+  /**
+   * Called when the transport opens.
+   *
+   * @api private
+   */
+
+  Transport.prototype.onOpen = function () {
+    this.isOpen = true;
+    this.clearCloseTimeout();
+    this.socket.onOpen();
+  };
+
+  /**
+   * Notifies the base when the connection with the Socket.IO server
+   * has been disconnected.
+   *
+   * @api private
+   */
+
+  Transport.prototype.onClose = function () {
+    var self = this;
+
+    /* FIXME: reopen delay causing a infinit loop
+    this.reopenTimeout = setTimeout(function () {
+      self.open();
+    }, this.socket.options['reopen delay']);*/
+
+    this.isOpen = false;
+    this.socket.onClose();
+    this.onDisconnect();
+  };
+
+  /**
+   * Generates a connection url based on the Socket.IO URL Protocol.
+   * See <https://github.com/learnboost/socket.io-node/> for more details.
+   *
+   * @returns {String} Connection url
+   * @api private
+   */
+
+  Transport.prototype.prepareUrl = function () {
+    var options = this.socket.options;
+
+    return this.scheme() + '://'
+      + options.host + ':' + options.port + '/'
+      + options.resource + '/' + io.protocol
+      + '/' + this.name + '/' + this.sessid;
+  };
+
+  /**
+   * Checks if the transport is ready to start a connection.
+   *
+   * @param {Socket} socket The socket instance that needs a transport
+   * @param {Function} fn The callback
+   * @api private
+   */
+
+  Transport.prototype.ready = function (socket, fn) {
+    fn.call(this);
+  };
+})(
+    'undefined' != typeof io ? io : module.exports
+  , 'undefined' != typeof io ? io : module.parent.exports
+);
+/**
+ * socket.io
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+(function (exports, io) {
+
+  /**
+   * Expose constructor.
+   */
+
+  exports.Socket = Socket;
+
+  /**
+   * Create a new `Socket.IO client` which can establish a persistent
+   * connection with a Socket.IO enabled server.
+   *
+   * @api public
+   */
+
+  function Socket (options) {
+    this.options = {
+        port: 80
+      , secure: false
+      , document: 'document' in global ? document : false
+      , resource: 'socket.io'
+      , transports: io.transports
+      , 'connect timeout': 10000
+      , 'try multiple transports': true
+      , 'reconnect': true
+      , 'reconnection delay': 500
+      , 'reconnection limit': Infinity
+      , 'reopen delay': 3000
+      , 'max reconnection attempts': 10
+      , 'sync disconnect on unload': false
+      , 'auto connect': true
+      , 'flash policy port': 10843
+      , 'manualFlush': false
+    };
+
+    io.util.merge(this.options, options);
+
+    this.connected = false;
+    this.open = false;
+    this.connecting = false;
+    this.reconnecting = false;
+    this.namespaces = {};
+    this.buffer = [];
+    this.doBuffer = false;
+
+    if (this.options['sync disconnect on unload'] &&
+        (!this.isXDomain() || io.util.ua.hasCORS)) {
+      var self = this;
+      io.util.on(global, 'beforeunload', function () {
+        self.disconnectSync();
+      }, false);
+    }
+
+    if (this.options['auto connect']) {
+      this.connect();
+    }
+};
+
+  /**
+   * Apply EventEmitter mixin.
+   */
+
+  io.util.mixin(Socket, io.EventEmitter);
+
+  /**
+   * Returns a namespace listener/emitter for this socket
+   *
+   * @api public
+   */
+
+  Socket.prototype.of = function (name) {
+    if (!this.namespaces[name]) {
+      this.namespaces[name] = new io.SocketNamespace(this, name);
+
+      if (name !== '') {
+        this.namespaces[name].packet({ type: 'connect' });
+      }
+    }
+
+    return this.namespaces[name];
+  };
+
+  /**
+   * Emits the given event to the Socket and all namespaces
+   *
+   * @api private
+   */
+
+  Socket.prototype.publish = function () {
+    this.emit.apply(this, arguments);
+
+    var nsp;
+
+    for (var i in this.namespaces) {
+      if (this.namespaces.hasOwnProperty(i)) {
+        nsp = this.of(i);
+        nsp.$emit.apply(nsp, arguments);
+      }
+    }
+  };
+
+  /**
+   * Performs the handshake
+   *
+   * @api private
+   */
+
+  function empty () { };
+
+  Socket.prototype.handshake = function (fn) {
+    var self = this
+      , options = this.options;
+
+    function complete (data) {
+      if (data instanceof Error) {
+        self.connecting = false;
+        self.onError(data.message);
+      } else {
+        fn.apply(null, data.split(':'));
+      }
+    };
+
+    var url = [
+          'http' + (options.secure ? 's' : '') + ':/'
+        , options.host + ':' + options.port
+        , options.resource
+        , io.protocol
+        , io.util.query(this.options.query, 't=' + +new Date)
+      ].join('/');
+
+    if (this.isXDomain() && !io.util.ua.hasCORS) {
+      var insertAt = document.getElementsByTagName('script')[0]
+        , script = document.createElement('script');
+
+      script.src = url + '&jsonp=' + io.j.length;
+      insertAt.parentNode.insertBefore(script, insertAt);
+
+      io.j.push(function (data) {
+        complete(data);
+        script.parentNode.removeChild(script);
+      });
+    } else {
+      var xhr = io.util.request();
+
+      xhr.open('GET', url, true);
+      if (this.isXDomain()) {
+        xhr.withCredentials = true;
+      }
+      xhr.onreadystatechange = function () {
+        if (xhr.readyState == 4) {
+          xhr.onreadystatechange = empty;
+
+          if (xhr.status == 200) {
+            complete(xhr.responseText);
+          } else if (xhr.status == 403) {
+            self.onError(xhr.responseText);
+          } else {
+            self.connecting = false;
+            !self.reconnecting && self.onError(xhr.responseText);
+          }
+        }
+      };
+      xhr.send(null);
+    }
+  };
+
+  /**
+   * Find an available transport based on the options supplied in the constructor.
+   *
+   * @api private
+   */
+
+  Socket.prototype.getTransport = function (override) {
+    var transports = override || this.transports, match;
+
+    for (var i = 0, transport; transport = transports[i]; i++) {
+      if (io.Transport[transport]
+        && io.Transport[transport].check(this)
+        && (!this.isXDomain() || io.Transport[transport].xdomainCheck(this))) {
+        return new io.Transport[transport](this, this.sessionid);
+      }
+    }
+
+    return null;
+  };
+
+  /**
+   * Connects to the server.
+   *
+   * @param {Function} [fn] Callback.
+   * @returns {io.Socket}
+   * @api public
+   */
+
+  Socket.prototype.connect = function (fn) {
+    if (this.connecting) {
+      return this;
+    }
+
+    var self = this;
+    self.connecting = true;
+
+    this.handshake(function (sid, heartbeat, close, transports) {
+      self.sessionid = sid;
+      self.closeTimeout = close * 1000;
+      self.heartbeatTimeout = heartbeat * 1000;
+      if(!self.transports)
+          self.transports = self.origTransports = (transports ? io.util.intersect(
+              transports.split(',')
+            , self.options.transports
+          ) : self.options.transports);
+
+      self.setHeartbeatTimeout();
+
+      function connect (transports){
+        if (self.transport) self.transport.clearTimeouts();
+
+        self.transport = self.getTransport(transports);
+        if (!self.transport) return self.publish('connect_failed');
+
+        // once the transport is ready
+        self.transport.ready(self, function () {
+          self.connecting = true;
+          self.publish('connecting', self.transport.name);
+          self.transport.open();
+
+          if (self.options['connect timeout']) {
+            self.connectTimeoutTimer = setTimeout(function () {
+              if (!self.connected) {
+                self.connecting = false;
+
+                if (self.options['try multiple transports']) {
+                  var remaining = self.transports;
+
+                  while (remaining.length > 0 && remaining.splice(0,1)[0] !=
+                         self.transport.name) {}
+
+                    if (remaining.length){
+                      connect(remaining);
+                    } else {
+                      self.publish('connect_failed');
+                    }
+                }
+              }
+            }, self.options['connect timeout']);
+          }
+        });
+      }
+
+      connect(self.transports);
+
+      self.once('connect', function (){
+        clearTimeout(self.connectTimeoutTimer);
+
+        fn && typeof fn == 'function' && fn();
+      });
+    });
+
+    return this;
+  };
+
+  /**
+   * Clears and sets a new heartbeat timeout using the value given by the
+   * server during the handshake.
+   *
+   * @api private
+   */
+
+  Socket.prototype.setHeartbeatTimeout = function () {
+    clearTimeout(this.heartbeatTimeoutTimer);
+    if(this.transport && !this.transport.heartbeats()) return;
+
+    var self = this;
+    this.heartbeatTimeoutTimer = setTimeout(function () {
+      self.transport.onClose();
+    }, this.heartbeatTimeout);
+  };
+
+  /**
+   * Sends a message.
+   *
+   * @param {Object} data packet.
+   * @returns {io.Socket}
+   * @api public
+   */
+
+  Socket.prototype.packet = function (data) {
+    if (this.connected && !this.doBuffer) {
+      this.transport.packet(data);
+    } else {
+      this.buffer.push(data);
+    }
+
+    return this;
+  };
+
+  /**
+   * Sets buffer state
+   *
+   * @api private
+   */
+
+  Socket.prototype.setBuffer = function (v) {
+    this.doBuffer = v;
+
+    if (!v && this.connected && this.buffer.length) {
+      if (!this.options['manualFlush']) {
+        this.flushBuffer();
+      }
+    }
+  };
+
+  /**
+   * Flushes the buffer data over the wire.
+   * To be invoked manually when 'manualFlush' is set to true.
+   *
+   * @api public
+   */
+
+  Socket.prototype.flushBuffer = function() {
+    this.transport.payload(this.buffer);
+    this.buffer = [];
+  };
+
+
+  /**
+   * Disconnect the established connect.
+   *
+   * @returns {io.Socket}
+   * @api public
+   */
+
+  Socket.prototype.disconnect = function () {
+    if (this.connected || this.connecting) {
+      if (this.open) {
+        this.of('').packet({ type: 'disconnect' });
+      }
+
+      // handle disconnection immediately
+      this.onDisconnect('booted');
+    }
+
+    return this;
+  };
+
+  /**
+   * Disconnects the socket with a sync XHR.
+   *
+   * @api private
+   */
+
+  Socket.prototype.disconnectSync = function () {
+    // ensure disconnection
+    var xhr = io.util.request();
+    var uri = [
+        'http' + (this.options.secure ? 's' : '') + ':/'
+      , this.options.host + ':' + this.options.port
+      , this.options.resource
+      , io.protocol
+      , ''
+      , this.sessionid
+    ].join('/') + '/?disconnect=1';
+
+    xhr.open('GET', uri, false);
+    xhr.send(null);
+
+    // handle disconnection immediately
+    this.onDisconnect('booted');
+  };
+
+  /**
+   * Check if we need to use cross domain enabled transports. Cross domain would
+   * be a different port or different domain name.
+   *
+   * @returns {Boolean}
+   * @api private
+   */
+
+  Socket.prototype.isXDomain = function () {
+
+    var port = global.location.port ||
+      ('https:' == global.location.protocol ? 443 : 80);
+
+    return this.options.host !== global.location.hostname
+      || this.options.port != port;
+  };
+
+  /**
+   * Called upon handshake.
+   *
+   * @api private
+   */
+
+  Socket.prototype.onConnect = function () {
+    if (!this.connected) {
+      this.connected = true;
+      this.connecting = false;
+      if (!this.doBuffer) {
+        // make sure to flush the buffer
+        this.setBuffer(false);
+      }
+      this.emit('connect');
+    }
+  };
+
+  /**
+   * Called when the transport opens
+   *
+   * @api private
+   */
+
+  Socket.prototype.onOpen = function () {
+    this.open = true;
+  };
+
+  /**
+   * Called when the transport closes.
+   *
+   * @api private
+   */
+
+  Socket.prototype.onClose = function () {
+    this.open = false;
+    clearTimeout(this.heartbeatTimeoutTimer);
+  };
+
+  /**
+   * Called when the transport first opens a connection
+   *
+   * @param text
+   */
+
+  Socket.prototype.onPacket = function (packet) {
+    this.of(packet.endpoint).onPacket(packet);
+  };
+
+  /**
+   * Handles an error.
+   *
+   * @api private
+   */
+
+  Socket.prototype.onError = function (err) {
+    if (err && err.advice) {
+      if (err.advice === 'reconnect' && (this.connected || this.connecting)) {
+        this.disconnect();
+        if (this.options.reconnect) {
+          this.reconnect();
+        }
+      }
+    }
+
+    this.publish('error', err && err.reason ? err.reason : err);
+  };
+
+  /**
+   * Called when the transport disconnects.
+   *
+   * @api private
+   */
+
+  Socket.prototype.onDisconnect = function (reason) {
+    var wasConnected = this.connected
+      , wasConnecting = this.connecting;
+
+    this.connected = false;
+    this.connecting = false;
+    this.open = false;
+
+    if (wasConnected || wasConnecting) {
+      this.transport.close();
+      this.transport.clearTimeouts();
+      if (wasConnected) {
+        this.publish('disconnect', reason);
+
+        if ('booted' != reason && this.options.reconnect && !this.reconnecting) {
+          this.reconnect();
+        }
+      }
+    }
+  };
+
+  /**
+   * Called upon reconnection.
+   *
+   * @api private
+   */
+
+  Socket.prototype.reconnect = function () {
+    this.reconnecting = true;
+    this.reconnectionAttempts = 0;
+    this.reconnectionDelay = this.options['reconnection delay'];
+
+    var self = this
+      , maxAttempts = this.options['max reconnection attempts']
+      , tryMultiple = this.options['try multiple transports']
+      , limit = this.options['reconnection limit'];
+
+    function reset () {
+      if (self.connected) {
+        for (var i in self.namespaces) {
+          if (self.namespaces.hasOwnProperty(i) && '' !== i) {
+              self.namespaces[i].packet({ type: 'connect' });
+          }
+        }
+        self.publish('reconnect', self.transport.name, self.reconnectionAttempts);
+      }
+
+      clearTimeout(self.reconnectionTimer);
+
+      self.removeListener('connect_failed', maybeReconnect);
+      self.removeListener('connect', maybeReconnect);
+
+      self.reconnecting = false;
+
+      delete self.reconnectionAttempts;
+      delete self.reconnectionDelay;
+      delete self.reconnectionTimer;
+      delete self.redoTransports;
+
+      self.options['try multiple transports'] = tryMultiple;
+    };
+
+    function maybeReconnect () {
+      if (!self.reconnecting) {
+        return;
+      }
+
+      if (self.connected) {
+        return reset();
+      };
+
+      if (self.connecting && self.reconnecting) {
+        return self.reconnectionTimer = setTimeout(maybeReconnect, 1000);
+      }
+
+      if (self.reconnectionAttempts++ >= maxAttempts) {
+        if (!self.redoTransports) {
+          self.on('connect_failed', maybeReconnect);
+          self.options['try multiple transports'] = true;
+          self.transports = self.origTransports;
+          self.transport = self.getTransport();
+          self.redoTransports = true;
+          self.connect();
+        } else {
+          self.publish('reconnect_failed');
+          reset();
+        }
+      } else {
+        if (self.reconnectionDelay < limit) {
+          self.reconnectionDelay *= 2; // exponential back off
+        }
+
+        self.connect();
+        self.publish('reconnecting', self.reconnectionDelay, self.reconnectionAttempts);
+        self.reconnectionTimer = setTimeout(maybeReconnect, self.reconnectionDelay);
+      }
+    };
+
+    this.options['try multiple transports'] = false;
+    this.reconnectionTimer = setTimeout(maybeReconnect, this.reconnectionDelay);
+
+    this.on('connect', maybeReconnect);
+  };
+
+})(
+    'undefined' != typeof io ? io : module.exports
+  , 'undefined' != typeof io ? io : module.parent.exports
+);
+/**
+ * socket.io
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+(function (exports, io) {
+
+  /**
+   * Expose constructor.
+   */
+
+  exports.SocketNamespace = SocketNamespace;
+
+  /**
+   * Socket namespace constructor.
+   *
+   * @constructor
+   * @api public
+   */
+
+  function SocketNamespace (socket, name) {
+    this.socket = socket;
+    this.name = name || '';
+    this.flags = {};
+    this.json = new Flag(this, 'json');
+    this.ackPackets = 0;
+    this.acks = {};
+  };
+
+  /**
+   * Apply EventEmitter mixin.
+   */
+
+  io.util.mixin(SocketNamespace, io.EventEmitter);
+
+  /**
+   * Copies emit since we override it
+   *
+   * @api private
+   */
+
+  SocketNamespace.prototype.$emit = io.EventEmitter.prototype.emit;
+
+  /**
+   * Creates a new namespace, by proxying the request to the socket. This
+   * allows us to use the synax as we do on the server.
+   *
+   * @api public
+   */
+
+  SocketNamespace.prototype.of = function () {
+    return this.socket.of.apply(this.socket, arguments);
+  };
+
+  /**
+   * Sends a packet.
+   *
+   * @api private
+   */
+
+  SocketNamespace.prototype.packet = function (packet) {
+    packet.endpoint = this.name;
+    this.socket.packet(packet);
+    this.flags = {};
+    return this;
+  };
+
+  /**
+   * Sends a message
+   *
+   * @api public
+   */
+
+  SocketNamespace.prototype.send = function (data, fn) {
+    var packet = {
+        type: this.flags.json ? 'json' : 'message'
+      , data: data
+    };
+
+    if ('function' == typeof fn) {
+      packet.id = ++this.ackPackets;
+      packet.ack = true;
+      this.acks[packet.id] = fn;
+    }
+
+    return this.packet(packet);
+  };
+
+  /**
+   * Emits an event
+   *
+   * @api public
+   */
+
+  SocketNamespace.prototype.emit = function (name) {
+    var args = Array.prototype.slice.call(arguments, 1)
+      , lastArg = args[args.length - 1]
+      , packet = {
+            type: 'event'
+          , name: name
+        };
+
+    if ('function' == typeof lastArg) {
+      packet.id = ++this.ackPackets;
+      packet.ack = 'data';
+      this.acks[packet.id] = lastArg;
+      args = args.slice(0, args.length - 1);
+    }
+
+    packet.args = args;
+
+    return this.packet(packet);
+  };
+
+  /**
+   * Disconnects the namespace
+   *
+   * @api private
+   */
+
+  SocketNamespace.prototype.disconnect = function () {
+    if (this.name === '') {
+      this.socket.disconnect();
+    } else {
+      this.packet({ type: 'disconnect' });
+      this.$emit('disconnect');
+    }
+
+    return this;
+  };
+
+  /**
+   * Handles a packet
+   *
+   * @api private
+   */
+
+  SocketNamespace.prototype.onPacket = function (packet) {
+    var self = this;
+
+    function ack () {
+      self.packet({
+          type: 'ack'
+        , args: io.util.toArray(arguments)
+        , ackId: packet.id
+      });
+    };
+
+    switch (packet.type) {
+      case 'connect':
+        this.$emit('connect');
+        break;
+
+      case 'disconnect':
+        if (this.name === '') {
+          this.socket.onDisconnect(packet.reason || 'booted');
+        } else {
+          this.$emit('disconnect', packet.reason);
+        }
+        break;
+
+      case 'message':
+      case 'json':
+        var params = ['message', packet.data];
+
+        if (packet.ack == 'data') {
+          params.push(ack);
+        } else if (packet.ack) {
+          this.packet({ type: 'ack', ackId: packet.id });
+        }
+
+        this.$emit.apply(this, params);
+        break;
+
+      case 'event':
+        var params = [packet.name].concat(packet.args);
+
+        if (packet.ack == 'data')
+          params.push(ack);
+
+        this.$emit.apply(this, params);
+        break;
+
+      case 'ack':
+        if (this.acks[packet.ackId]) {
+          this.acks[packet.ackId].apply(this, packet.args);
+          delete this.acks[packet.ackId];
+        }
+        break;
+
+      case 'error':
+        if (packet.advice){
+          this.socket.onError(packet);
+        } else {
+          if (packet.reason == 'unauthorized') {
+            this.$emit('connect_failed', packet.reason);
+          } else {
+            this.$emit('error', packet.reason);
+          }
+        }
+        break;
+    }
+  };
+
+  /**
+   * Flag interface.
+   *
+   * @api private
+   */
+
+  function Flag (nsp, name) {
+    this.namespace = nsp;
+    this.name = name;
+  };
+
+  /**
+   * Send a message
+   *
+   * @api public
+   */
+
+  Flag.prototype.send = function () {
+    this.namespace.flags[this.name] = true;
+    this.namespace.send.apply(this.namespace, arguments);
+  };
+
+  /**
+   * Emit an event
+   *
+   * @api public
+   */
+
+  Flag.prototype.emit = function () {
+    this.namespace.flags[this.name] = true;
+    this.namespace.emit.apply(this.namespace, arguments);
+  };
+
+})(
+    'undefined' != typeof io ? io : module.exports
+  , 'undefined' != typeof io ? io : module.parent.exports
+);
+
+/**
+ * socket.io
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+(function (exports, io) {
+
+  /**
+   * Expose constructor.
+   */
+
+  exports.websocket = WS;
+
+  /**
+   * The WebSocket transport uses the HTML5 WebSocket API to establish an
+   * persistent connection with the Socket.IO server. This transport will also
+   * be inherited by the FlashSocket fallback as it provides a API compatible
+   * polyfill for the WebSockets.
+   *
+   * @constructor
+   * @extends {io.Transport}
+   * @api public
+   */
+
+  function WS (socket) {
+    io.Transport.apply(this, arguments);
+  };
+
+  /**
+   * Inherits from Transport.
+   */
+
+  io.util.inherit(WS, io.Transport);
+
+  /**
+   * Transport name
+   *
+   * @api public
+   */
+
+  WS.prototype.name = 'websocket';
+
+  /**
+   * Initializes a new `WebSocket` connection with the Socket.IO server. We attach
+   * all the appropriate listeners to handle the responses from the server.
+   *
+   * @returns {Transport}
+   * @api public
+   */
+
+  WS.prototype.open = function () {
+    var query = io.util.query(this.socket.options.query)
+      , self = this
+      , Socket
+
+
+    if (!Socket) {
+      Socket = global.MozWebSocket || global.WebSocket;
+    }
+
+    this.websocket = new Socket(this.prepareUrl() + query);
+
+    this.websocket.onopen = function () {
+      self.onOpen();
+      self.socket.setBuffer(false);
+    };
+    this.websocket.onmessage = function (ev) {
+      self.onData(ev.data);
+    };
+    this.websocket.onclose = function () {
+      self.onClose();
+      self.socket.setBuffer(true);
+    };
+    this.websocket.onerror = function (e) {
+      self.onError(e);
+    };
+
+    return this;
+  };
+
+  /**
+   * Send a message to the Socket.IO server. The message will automatically be
+   * encoded in the correct message format.
+   *
+   * @returns {Transport}
+   * @api public
+   */
+
+  // Do to a bug in the current IDevices browser, we need to wrap the send in a
+  // setTimeout, when they resume from sleeping the browser will crash if
+  // we don't allow the browser time to detect the socket has been closed
+  if (io.util.ua.iDevice) {
+    WS.prototype.send = function (data) {
+      var self = this;
+      setTimeout(function() {
+         self.websocket.send(data);
+      },0);
+      return this;
+    };
+  } else {
+    WS.prototype.send = function (data) {
+      this.websocket.send(data);
+      return this;
+    };
+  }
+
+  /**
+   * Payload
+   *
+   * @api private
+   */
+
+  WS.prototype.payload = function (arr) {
+    for (var i = 0, l = arr.length; i < l; i++) {
+      this.packet(arr[i]);
+    }
+    return this;
+  };
+
+  /**
+   * Disconnect the established `WebSocket` connection.
+   *
+   * @returns {Transport}
+   * @api public
+   */
+
+  WS.prototype.close = function () {
+    this.websocket.close();
+    return this;
+  };
+
+  /**
+   * Handle the errors that `WebSocket` might be giving when we
+   * are attempting to connect or send messages.
+   *
+   * @param {Error} e The error.
+   * @api private
+   */
+
+  WS.prototype.onError = function (e) {
+    this.socket.onError(e);
+  };
+
+  /**
+   * Returns the appropriate scheme for the URI generation.
+   *
+   * @api private
+   */
+  WS.prototype.scheme = function () {
+    return this.socket.options.secure ? 'wss' : 'ws';
+  };
+
+  /**
+   * Checks if the browser has support for native `WebSockets` and that
+   * it's not the polyfill created for the FlashSocket transport.
+   *
+   * @return {Boolean}
+   * @api public
+   */
+
+  WS.check = function () {
+    return ('WebSocket' in global && !('__addTask' in WebSocket))
+          || 'MozWebSocket' in global;
+  };
+
+  /**
+   * Check if the `WebSocket` transport support cross domain communications.
+   *
+   * @returns {Boolean}
+   * @api public
+   */
+
+  WS.xdomainCheck = function () {
+    return true;
+  };
+
+  /**
+   * Add the transport to your public io.transports array.
+   *
+   * @api private
+   */
+
+  io.transports.push('websocket');
+
+})(
+    'undefined' != typeof io ? io.Transport : module.exports
+  , 'undefined' != typeof io ? io : module.parent.exports
+);
+
+/**
+ * socket.io
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+(function (exports, io) {
+
+  /**
+   * Expose constructor.
+   */
+
+  exports.flashsocket = Flashsocket;
+
+  /**
+   * The FlashSocket transport. This is a API wrapper for the HTML5 WebSocket
+   * specification. It uses a .swf file to communicate with the server. If you want
+   * to serve the .swf file from a other server than where the Socket.IO script is
+   * coming from you need to use the insecure version of the .swf. More information
+   * about this can be found on the github page.
+   *
+   * @constructor
+   * @extends {io.Transport.websocket}
+   * @api public
+   */
+
+  function Flashsocket () {
+    io.Transport.websocket.apply(this, arguments);
+  };
+
+  /**
+   * Inherits from Transport.
+   */
+
+  io.util.inherit(Flashsocket, io.Transport.websocket);
+
+  /**
+   * Transport name
+   *
+   * @api public
+   */
+
+  Flashsocket.prototype.name = 'flashsocket';
+
+  /**
+   * Disconnect the established `FlashSocket` connection. This is done by adding a
+   * new task to the FlashSocket. The rest will be handled off by the `WebSocket`
+   * transport.
+   *
+   * @returns {Transport}
+   * @api public
+   */
+
+  Flashsocket.prototype.open = function () {
+    var self = this
+      , args = arguments;
+
+    WebSocket.__addTask(function () {
+      io.Transport.websocket.prototype.open.apply(self, args);
+    });
+    return this;
+  };
+
+  /**
+   * Sends a message to the Socket.IO server. This is done by adding a new
+   * task to the FlashSocket. The rest will be handled off by the `WebSocket`
+   * transport.
+   *
+   * @returns {Transport}
+   * @api public
+   */
+
+  Flashsocket.prototype.send = function () {
+    var self = this, args = arguments;
+    WebSocket.__addTask(function () {
+      io.Transport.websocket.prototype.send.apply(self, args);
+    });
+    return this;
+  };
+
+  /**
+   * Disconnects the established `FlashSocket` connection.
+   *
+   * @returns {Transport}
+   * @api public
+   */
+
+  Flashsocket.prototype.close = function () {
+    WebSocket.__tasks.length = 0;
+    io.Transport.websocket.prototype.close.call(this);
+    return this;
+  };
+
+  /**
+   * The WebSocket fall back needs to append the flash container to the body
+   * element, so we need to make sure we have access to it. Or defer the call
+   * until we are sure there is a body element.
+   *
+   * @param {Socket} socket The socket instance that needs a transport
+   * @param {Function} fn The callback
+   * @api private
+   */
+
+  Flashsocket.prototype.ready = function (socket, fn) {
+    function init () {
+      var options = socket.options
+        , port = options['flash policy port']
+        , path = [
+              'http' + (options.secure ? 's' : '') + ':/'
+            , options.host + ':' + options.port
+            , options.resource
+            , 'static/flashsocket'
+            , 'WebSocketMain' + (socket.isXDomain() ? 'Insecure' : '') + '.swf'
+          ];
+
+      // Only start downloading the swf file when the checked that this browser
+      // actually supports it
+      if (!Flashsocket.loaded) {
+        if (typeof WEB_SOCKET_SWF_LOCATION === 'undefined') {
+          // Set the correct file based on the XDomain settings
+          WEB_SOCKET_SWF_LOCATION = path.join('/');
+        }
+
+        if (port !== 843) {
+          WebSocket.loadFlashPolicyFile('xmlsocket://' + options.host + ':' + port);
+        }
+
+        WebSocket.__initialize();
+        Flashsocket.loaded = true;
+      }
+
+      fn.call(self);
+    }
+
+    var self = this;
+    if (document.body) return init();
+
+    io.util.load(init);
+  };
+
+  /**
+   * Check if the FlashSocket transport is supported as it requires that the Adobe
+   * Flash Player plug-in version `10.0.0` or greater is installed. And also check if
+   * the polyfill is correctly loaded.
+   *
+   * @returns {Boolean}
+   * @api public
+   */
+
+  Flashsocket.check = function () {
+    if (
+        typeof WebSocket == 'undefined'
+      || !('__initialize' in WebSocket) || !swfobject
+    ) return false;
+
+    return swfobject.getFlashPlayerVersion().major >= 10;
+  };
+
+  /**
+   * Check if the FlashSocket transport can be used as cross domain / cross origin
+   * transport. Because we can't see which type (secure or insecure) of .swf is used
+   * we will just return true.
+   *
+   * @returns {Boolean}
+   * @api public
+   */
+
+  Flashsocket.xdomainCheck = function () {
+    return true;
+  };
+
+  /**
+   * Disable AUTO_INITIALIZATION
+   */
+
+  if (typeof window != 'undefined') {
+    WEB_SOCKET_DISABLE_AUTO_INITIALIZATION = true;
+  }
+
+  /**
+   * Add the transport to your public io.transports array.
+   *
+   * @api private
+   */
+
+  io.transports.push('flashsocket');
+})(
+    'undefined' != typeof io ? io.Transport : module.exports
+  , 'undefined' != typeof io ? io : module.parent.exports
+);
+/*	SWFObject v2.2 <http://code.google.com/p/swfobject/>
+	is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
+*/
+if ('undefined' != typeof window) {
+var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O[(['Active'].concat('Object').join('X'))]!=D){try{var ad=new window[(['Active'].concat('Object').join('X'))](W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y<X;Y++){U[Y]()}}function K(X){if(J){X()}else{U[U.length]=X}}function s(Y){if(typeof O.addEventListener!=D){O.addEventListener("load",Y,false)}else{if(typeof j.addEventListener!=D){j.addEventListener("load",Y,false)}else{if(typeof O.attachEvent!=D){i(O,"onload",Y)}else{if(typeof O.onload=="function"){var X=O.onload;O.onload=function(){X();Y()}}else{O.onload=Y}}}}}function h(){if(T){V()}else{H()}}function V(){var X=j.getElementsByTagName("body")[0];var aa=C(r);aa.setAttribute("type",q);var Z=X.appendChild(aa);if(Z){var Y=0;(function(){if(typeof Z.GetVariable!=D){var ab=Z.GetVariable("$version");if(ab){ab=ab.split(" ")[1].split(",");M.pv=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}else{if(Y<10){Y++;setTimeout(arguments.callee,10);return}}X.removeChild(aa);Z=null;H()})()}else{H()}}function H(){var ag=o.length;if(ag>0){for(var af=0;af<ag;af++){var Y=o[af].id;var ab=o[af].callbackFn;var aa={success:false,id:Y};if(M.pv[0]>0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad<ac;ad++){if(X[ad].getAttribute("name").toLowerCase()!="movie"){ah[X[ad].getAttribute("name")]=X[ad].getAttribute("value")}}P(ai,ah,Y,ab)}else{p(ae);if(ab){ab(aa)}}}}}else{w(Y,true);if(ab){var Z=z(Y);if(Z&&typeof Z.SetVariable!=D){aa.success=true;aa.ref=Z}ab(aa)}}}}}function z(aa){var X=null;var Y=c(aa);if(Y&&Y.nodeName=="OBJECT"){if(typeof Y.SetVariable!=D){X=Y}else{var Z=Y.getElementsByTagName(r)[0];if(Z){X=Z}}}return X}function A(){return !a&&F("6.0.65")&&(M.win||M.mac)&&!(M.wk&&M.wk<312)}function P(aa,ab,X,Z){a=true;E=Z||null;B={success:false,id:X};var ae=c(X);if(ae){if(ae.nodeName=="OBJECT"){l=g(ae);Q=null}else{l=ae;Q=X}aa.id=R;if(typeof aa.width==D||(!/%$/.test(aa.width)&&parseInt(aa.width,10)<310)){aa.width="310"}if(typeof aa.height==D||(!/%$/.test(aa.height)&&parseInt(aa.height,10)<137)){aa.height="137"}j.title=j.title.slice(0,47)+" - Flash Player Installation";var ad=M.ie&&M.win?(['Active'].concat('').join('X')):"PlugIn",ac="MMredirectURL="+O.location.toString().replace(/&/g,"%26")+"&MMplayerType="+ad+"&MMdoctitle="+j.title;if(typeof ab.flashvars!=D){ab.flashvars+="&"+ac}else{ab.flashvars=ac}if(M.ie&&M.win&&ae.readyState!=4){var Y=C("div");X+="SWFObjectNew";Y.setAttribute("id",X);ae.parentNode.insertBefore(Y,ae);ae.style.display="none";(function(){if(ae.readyState==4){ae.parentNode.removeChild(ae)}else{setTimeout(arguments.callee,10)}})()}u(aa,ab,X)}}function p(Y){if(M.ie&&M.win&&Y.readyState!=4){var X=C("div");Y.parentNode.insertBefore(X,Y);X.parentNode.replaceChild(g(Y),X);Y.style.display="none";(function(){if(Y.readyState==4){Y.parentNode.removeChild(Y)}else{setTimeout(arguments.callee,10)}})()}else{Y.parentNode.replaceChild(g(Y),Y)}}function g(ab){var aa=C("div");if(M.win&&M.ie){aa.innerHTML=ab.innerHTML}else{var Y=ab.getElementsByTagName(r)[0];if(Y){var ad=Y.childNodes;if(ad){var X=ad.length;for(var Z=0;Z<X;Z++){if(!(ad[Z].nodeType==1&&ad[Z].nodeName=="PARAM")&&!(ad[Z].nodeType==8)){aa.appendChild(ad[Z].cloneNode(true))}}}}}return aa}function u(ai,ag,Y){var X,aa=c(Y);if(M.wk&&M.wk<312){return X}if(aa){if(typeof ai.id==D){ai.id=Y}if(M.ie&&M.win){var ah="";for(var ae in ai){if(ai[ae]!=Object.prototype[ae]){if(ae.toLowerCase()=="data"){ag.movie=ai[ae]}else{if(ae.toLowerCase()=="styleclass"){ah+=' class="'+ai[ae]+'"'}else{if(ae.toLowerCase()!="classid"){ah+=" "+ae+'="'+ai[ae]+'"'}}}}}var af="";for(var ad in ag){if(ag[ad]!=Object.prototype[ad]){af+='<param name="'+ad+'" value="'+ag[ad]+'" />'}}aa.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+ah+">"+af+"</object>";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab<ac;ab++){I[ab][0].detachEvent(I[ab][1],I[ab][2])}var Z=N.length;for(var aa=0;aa<Z;aa++){y(N[aa])}for(var Y in M){M[Y]=null}M=null;for(var X in swfobject){swfobject[X]=null}swfobject=null})}}();return{registerObject:function(ab,X,aa,Z){if(M.w3&&ab&&X){var Y={};Y.id=ab;Y.swfVersion=X;Y.expressInstall=aa;Y.callbackFn=Z;o[o.length]=Y;w(ab,false)}else{if(Z){Z({success:false,id:ab})}}},getObjectById:function(X){if(M.w3){return z(X)}},embedSWF:function(ab,ah,ae,ag,Y,aa,Z,ad,af,ac){var X={success:false,id:ah};if(M.w3&&!(M.wk&&M.wk<312)&&ab&&ah&&ae&&ag&&Y){w(ah,false);K(function(){ae+="";ag+="";var aj={};if(af&&typeof af===r){for(var al in af){aj[al]=af[al]}}aj.data=ab;aj.width=ae;aj.height=ag;var am={};if(ad&&typeof ad===r){for(var ak in ad){am[ak]=ad[ak]}}if(Z&&typeof Z===r){for(var ai in Z){if(typeof am.flashvars!=D){am.flashvars+="&"+ai+"="+Z[ai]}else{am.flashvars=ai+"="+Z[ai]}}}if(F(Y)){var an=u(aj,am,ah);if(aj.id==ah){w(ah,true)}X.success=true;X.ref=an}else{if(aa&&A()){aj.data=aa;P(aj,am,ah,ac);return}else{w(ah,true)}}if(ac){ac(X)}})}else{if(ac){ac(X)}}},switchOffAutoHideShow:function(){m=false},ua:M,getFlashPlayerVersion:function(){return{major:M.pv[0],minor:M.pv[1],release:M.pv[2]}},hasFlashPlayerVersion:F,createSWF:function(Z,Y,X){if(M.w3){return u(Z,Y,X)}else{return undefined}},showExpressInstall:function(Z,aa,X,Y){if(M.w3&&A()){P(Z,aa,X,Y)}},removeSWF:function(X){if(M.w3){y(X)}},createCSS:function(aa,Z,Y,X){if(M.w3){v(aa,Z,Y,X)}},addDomLoadEvent:K,addLoadEvent:s,getQueryParamValue:function(aa){var Z=j.location.search||j.location.hash;if(Z){if(/\?/.test(Z)){Z=Z.split("?")[1]}if(aa==null){return L(Z)}var Y=Z.split("&");for(var X=0;X<Y.length;X++){if(Y[X].substring(0,Y[X].indexOf("="))==aa){return L(Y[X].substring((Y[X].indexOf("=")+1)))}}}return""},expressInstallCallback:function(){if(a){var X=c(R);if(X&&l){X.parentNode.replaceChild(l,X);if(Q){w(Q,true);if(M.ie&&M.win){l.style.display="block"}}if(E){E(B)}}a=false}}}}();
+}
+// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
+// License: New BSD License
+// Reference: http://dev.w3.org/html5/websockets/
+// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol
+
+(function() {
+
+  if ('undefined' == typeof window || window.WebSocket) return;
+
+  var console = window.console;
+  if (!console || !console.log || !console.error) {
+    console = {log: function(){ }, error: function(){ }};
+  }
+
+  if (!swfobject.hasFlashPlayerVersion("10.0.0")) {
+    console.error("Flash Player >= 10.0.0 is required.");
+    return;
+  }
+  if (location.protocol == "file:") {
+    console.error(
+      "WARNING: web-socket-js doesn't work in file:///... URL " +
+      "unless you set Flash Security Settings properly. " +
+      "Open the page via Web server i.e. http://...");
+  }
+
+  /**
+   * This class represents a faux web socket.
+   * @param {string} url
+   * @param {array or string} protocols
+   * @param {string} proxyHost
+   * @param {int} proxyPort
+   * @param {string} headers
+   */
+  WebSocket = function(url, protocols, proxyHost, proxyPort, headers) {
+    var self = this;
+    self.__id = WebSocket.__nextId++;
+    WebSocket.__instances[self.__id] = self;
+    self.readyState = WebSocket.CONNECTING;
+    self.bufferedAmount = 0;
+    self.__events = {};
+    if (!protocols) {
+      protocols = [];
+    } else if (typeof protocols == "string") {
+      protocols = [protocols];
+    }
+    // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
+    // Otherwise, when onopen fires immediately, onopen is called before it is set.
+    setTimeout(function() {
+      WebSocket.__addTask(function() {
+        WebSocket.__flash.create(
+            self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null);
+      });
+    }, 0);
+  };
+
+  /**
+   * Send data to the web socket.
+   * @param {string} data  The data to send to the socket.
+   * @return {boolean}  True for success, false for failure.
+   */
+  WebSocket.prototype.send = function(data) {
+    if (this.readyState == WebSocket.CONNECTING) {
+      throw "INVALID_STATE_ERR: Web Socket connection has not been established";
+    }
+    // We use encodeURIComponent() here, because FABridge doesn't work if
+    // the argument includes some characters. We don't use escape() here
+    // because of this:
+    // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions
+    // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't
+    // preserve all Unicode characters either e.g. "\uffff" in Firefox.
+    // Note by wtritch: Hopefully this will not be necessary using ExternalInterface.  Will require
+    // additional testing.
+    var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data));
+    if (result < 0) { // success
+      return true;
+    } else {
+      this.bufferedAmount += result;
+      return false;
+    }
+  };
+
+  /**
+   * Close this web socket gracefully.
+   */
+  WebSocket.prototype.close = function() {
+    if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
+      return;
+    }
+    this.readyState = WebSocket.CLOSING;
+    WebSocket.__flash.close(this.__id);
+  };
+
+  /**
+   * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
+   *
+   * @param {string} type
+   * @param {function} listener
+   * @param {boolean} useCapture
+   * @return void
+   */
+  WebSocket.prototype.addEventListener = function(type, listener, useCapture) {
+    if (!(type in this.__events)) {
+      this.__events[type] = [];
+    }
+    this.__events[type].push(listener);
+  };
+
+  /**
+   * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
+   *
+   * @param {string} type
+   * @param {function} listener
+   * @param {boolean} useCapture
+   * @return void
+   */
+  WebSocket.prototype.removeEventListener = function(type, listener, useCapture) {
+    if (!(type in this.__events)) return;
+    var events = this.__events[type];
+    for (var i = events.length - 1; i >= 0; --i) {
+      if (events[i] === listener) {
+        events.splice(i, 1);
+        break;
+      }
+    }
+  };
+
+  /**
+   * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
+   *
+   * @param {Event} event
+   * @return void
+   */
+  WebSocket.prototype.dispatchEvent = function(event) {
+    var events = this.__events[event.type] || [];
+    for (var i = 0; i < events.length; ++i) {
+      events[i](event);
+    }
+    var handler = this["on" + event.type];
+    if (handler) handler(event);
+  };
+
+  /**
+   * Handles an event from Flash.
+   * @param {Object} flashEvent
+   */
+  WebSocket.prototype.__handleEvent = function(flashEvent) {
+    if ("readyState" in flashEvent) {
+      this.readyState = flashEvent.readyState;
+    }
+    if ("protocol" in flashEvent) {
+      this.protocol = flashEvent.protocol;
+    }
+
+    var jsEvent;
+    if (flashEvent.type == "open" || flashEvent.type == "error") {
+      jsEvent = this.__createSimpleEvent(flashEvent.type);
+    } else if (flashEvent.type == "close") {
+      // TODO implement jsEvent.wasClean
+      jsEvent = this.__createSimpleEvent("close");
+    } else if (flashEvent.type == "message") {
+      var data = decodeURIComponent(flashEvent.message);
+      jsEvent = this.__createMessageEvent("message", data);
+    } else {
+      throw "unknown event type: " + flashEvent.type;
+    }
+
+    this.dispatchEvent(jsEvent);
+  };
+
+  WebSocket.prototype.__createSimpleEvent = function(type) {
+    if (document.createEvent && window.Event) {
+      var event = document.createEvent("Event");
+      event.initEvent(type, false, false);
+      return event;
+    } else {
+      return {type: type, bubbles: false, cancelable: false};
+    }
+  };
+
+  WebSocket.prototype.__createMessageEvent = function(type, data) {
+    if (document.createEvent && window.MessageEvent && !window.opera) {
+      var event = document.createEvent("MessageEvent");
+      event.initMessageEvent("message", false, false, data, null, null, window, null);
+      return event;
+    } else {
+      // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes.
+      return {type: type, data: data, bubbles: false, cancelable: false};
+    }
+  };
+
+  /**
+   * Define the WebSocket readyState enumeration.
+   */
+  WebSocket.CONNECTING = 0;
+  WebSocket.OPEN = 1;
+  WebSocket.CLOSING = 2;
+  WebSocket.CLOSED = 3;
+
+  WebSocket.__flash = null;
+  WebSocket.__instances = {};
+  WebSocket.__tasks = [];
+  WebSocket.__nextId = 0;
+
+  /**
+   * Load a new flash security policy file.
+   * @param {string} url
+   */
+  WebSocket.loadFlashPolicyFile = function(url){
+    WebSocket.__addTask(function() {
+      WebSocket.__flash.loadManualPolicyFile(url);
+    });
+  };
+
+  /**
+   * Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
+   */
+  WebSocket.__initialize = function() {
+    if (WebSocket.__flash) return;
+
+    if (WebSocket.__swfLocation) {
+      // For backword compatibility.
+      window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
+    }
+    if (!window.WEB_SOCKET_SWF_LOCATION) {
+      console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
+      return;
+    }
+    var container = document.createElement("div");
+    container.id = "webSocketContainer";
+    // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
+    // Flash from loading at least in IE. So we move it out of the screen at (-100, -100).
+    // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash
+    // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is
+    // the best we can do as far as we know now.
+    container.style.position = "absolute";
+    if (WebSocket.__isFlashLite()) {
+      container.style.left = "0px";
+      container.style.top = "0px";
+    } else {
+      container.style.left = "-100px";
+      container.style.top = "-100px";
+    }
+    var holder = document.createElement("div");
+    holder.id = "webSocketFlash";
+    container.appendChild(holder);
+    document.body.appendChild(container);
+    // See this article for hasPriority:
+    // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
+    swfobject.embedSWF(
+      WEB_SOCKET_SWF_LOCATION,
+      "webSocketFlash",
+      "1" /* width */,
+      "1" /* height */,
+      "10.0.0" /* SWF version */,
+      null,
+      null,
+      {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"},
+      null,
+      function(e) {
+        if (!e.success) {
+          console.error("[WebSocket] swfobject.embedSWF failed");
+        }
+      });
+  };
+
+  /**
+   * Called by Flash to notify JS that it's fully loaded and ready
+   * for communication.
+   */
+  WebSocket.__onFlashInitialized = function() {
+    // We need to set a timeout here to avoid round-trip calls
+    // to flash during the initialization process.
+    setTimeout(function() {
+      WebSocket.__flash = document.getElementById("webSocketFlash");
+      WebSocket.__flash.setCallerUrl(location.href);
+      WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);
+      for (var i = 0; i < WebSocket.__tasks.length; ++i) {
+        WebSocket.__tasks[i]();
+      }
+      WebSocket.__tasks = [];
+    }, 0);
+  };
+
+  /**
+   * Called by Flash to notify WebSockets events are fired.
+   */
+  WebSocket.__onFlashEvent = function() {
+    setTimeout(function() {
+      try {
+        // Gets events using receiveEvents() instead of getting it from event object
+        // of Flash event. This is to make sure to keep message order.
+        // It seems sometimes Flash events don't arrive in the same order as they are sent.
+        var events = WebSocket.__flash.receiveEvents();
+        for (var i = 0; i < events.length; ++i) {
+          WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
+        }
+      } catch (e) {
+        console.error(e);
+      }
+    }, 0);
+    return true;
+  };
+
+  // Called by Flash.
+  WebSocket.__log = function(message) {
+    console.log(decodeURIComponent(message));
+  };
+
+  // Called by Flash.
+  WebSocket.__error = function(message) {
+    console.error(decodeURIComponent(message));
+  };
+
+  WebSocket.__addTask = function(task) {
+    if (WebSocket.__flash) {
+      task();
+    } else {
+      WebSocket.__tasks.push(task);
+    }
+  };
+
+  /**
+   * Test if the browser is running flash lite.
+   * @return {boolean} True if flash lite is running, false otherwise.
+   */
+  WebSocket.__isFlashLite = function() {
+    if (!window.navigator || !window.navigator.mimeTypes) {
+      return false;
+    }
+    var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"];
+    if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) {
+      return false;
+    }
+    return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false;
+  };
+
+  if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
+    if (window.addEventListener) {
+      window.addEventListener("load", function(){
+        WebSocket.__initialize();
+      }, false);
+    } else {
+      window.attachEvent("onload", function(){
+        WebSocket.__initialize();
+      });
+    }
+  }
+
+})();
+
+/**
+ * socket.io
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+(function (exports, io) {
+
+  /**
+   * Expose constructor.
+   *
+   * @api public
+   */
+
+  exports.XHR = XHR;
+
+  /**
+   * XHR constructor
+   *
+   * @costructor
+   * @api public
+   */
+
+  function XHR (socket) {
+    if (!socket) return;
+
+    io.Transport.apply(this, arguments);
+    this.sendBuffer = [];
+  };
+
+  /**
+   * Inherits from Transport.
+   */
+
+  io.util.inherit(XHR, io.Transport);
+
+  /**
+   * Establish a connection
+   *
+   * @returns {Transport}
+   * @api public
+   */
+
+  XHR.prototype.open = function () {
+    this.socket.setBuffer(false);
+    this.onOpen();
+    this.get();
+
+    // we need to make sure the request succeeds since we have no indication
+    // whether the request opened or not until it succeeded.
+    this.setCloseTimeout();
+
+    return this;
+  };
+
+  /**
+   * Check if we need to send data to the Socket.IO server, if we have data in our
+   * buffer we encode it and forward it to the `post` method.
+   *
+   * @api private
+   */
+
+  XHR.prototype.payload = function (payload) {
+    var msgs = [];
+
+    for (var i = 0, l = payload.length; i < l; i++) {
+      msgs.push(io.parser.encodePacket(payload[i]));
+    }
+
+    this.send(io.parser.encodePayload(msgs));
+  };
+
+  /**
+   * Send data to the Socket.IO server.
+   *
+   * @param data The message
+   * @returns {Transport}
+   * @api public
+   */
+
+  XHR.prototype.send = function (data) {
+    this.post(data);
+    return this;
+  };
+
+  /**
+   * Posts a encoded message to the Socket.IO server.
+   *
+   * @param {String} data A encoded message.
+   * @api private
+   */
+
+  function empty () { };
+
+  XHR.prototype.post = function (data) {
+    var self = this;
+    this.socket.setBuffer(true);
+
+    function stateChange () {
+      if (this.readyState == 4) {
+        this.onreadystatechange = empty;
+        self.posting = false;
+
+        if (this.status == 200){
+          self.socket.setBuffer(false);
+        } else {
+          self.onClose();
+        }
+      }
+    }
+
+    function onload () {
+      this.onload = empty;
+      self.socket.setBuffer(false);
+    };
+
+    this.sendXHR = this.request('POST');
+
+    if (global.XDomainRequest && this.sendXHR instanceof XDomainRequest) {
+      this.sendXHR.onload = this.sendXHR.onerror = onload;
+    } else {
+      this.sendXHR.onreadystatechange = stateChange;
+    }
+
+    this.sendXHR.send(data);
+  };
+
+  /**
+   * Disconnects the established `XHR` connection.
+   *
+   * @returns {Transport}
+   * @api public
+   */
+
+  XHR.prototype.close = function () {
+    this.onClose();
+    return this;
+  };
+
+  /**
+   * Generates a configured XHR request
+   *
+   * @param {String} url The url that needs to be requested.
+   * @param {String} method The method the request should use.
+   * @returns {XMLHttpRequest}
+   * @api private
+   */
+
+  XHR.prototype.request = function (method) {
+    var req = io.util.request(this.socket.isXDomain())
+      , query = io.util.query(this.socket.options.query, 't=' + +new Date);
+
+    req.open(method || 'GET', this.prepareUrl() + query, true);
+
+    if (method == 'POST') {
+      try {
+        if (req.setRequestHeader) {
+          req.setRequestHeader('Content-type', 'text/plain;charset=UTF-8');
+        } else {
+          // XDomainRequest
+          req.contentType = 'text/plain';
+        }
+      } catch (e) {}
+    }
+
+    return req;
+  };
+
+  /**
+   * Returns the scheme to use for the transport URLs.
+   *
+   * @api private
+   */
+
+  XHR.prototype.scheme = function () {
+    return this.socket.options.secure ? 'https' : 'http';
+  };
+
+  /**
+   * Check if the XHR transports are supported
+   *
+   * @param {Boolean} xdomain Check if we support cross domain requests.
+   * @returns {Boolean}
+   * @api public
+   */
+
+  XHR.check = function (socket, xdomain) {
+    try {
+      var request = io.util.request(xdomain),
+          usesXDomReq = (global.XDomainRequest && request instanceof XDomainRequest),
+          socketProtocol = (socket && socket.options && socket.options.secure ? 'https:' : 'http:'),
+          isXProtocol = (global.location && socketProtocol != global.location.protocol);
+      if (request && !(usesXDomReq && isXProtocol)) {
+        return true;
+      }
+    } catch(e) {}
+
+    return false;
+  };
+
+  /**
+   * Check if the XHR transport supports cross domain requests.
+   *
+   * @returns {Boolean}
+   * @api public
+   */
+
+  XHR.xdomainCheck = function (socket) {
+    return XHR.check(socket, true);
+  };
+
+})(
+    'undefined' != typeof io ? io.Transport : module.exports
+  , 'undefined' != typeof io ? io : module.parent.exports
+);
+/**
+ * socket.io
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+(function (exports, io) {
+
+  /**
+   * Expose constructor.
+   */
+
+  exports.htmlfile = HTMLFile;
+
+  /**
+   * The HTMLFile transport creates a `forever iframe` based transport
+   * for Internet Explorer. Regular forever iframe implementations will
+   * continuously trigger the browsers buzy indicators. If the forever iframe
+   * is created inside a `htmlfile` these indicators will not be trigged.
+   *
+   * @constructor
+   * @extends {io.Transport.XHR}
+   * @api public
+   */
+
+  function HTMLFile (socket) {
+    io.Transport.XHR.apply(this, arguments);
+  };
+
+  /**
+   * Inherits from XHR transport.
+   */
+
+  io.util.inherit(HTMLFile, io.Transport.XHR);
+
+  /**
+   * Transport name
+   *
+   * @api public
+   */
+
+  HTMLFile.prototype.name = 'htmlfile';
+
+  /**
+   * Creates a new Ac...eX `htmlfile` with a forever loading iframe
+   * that can be used to listen to messages. Inside the generated
+   * `htmlfile` a reference will be made to the HTMLFile transport.
+   *
+   * @api private
+   */
+
+  HTMLFile.prototype.get = function () {
+    this.doc = new window[(['Active'].concat('Object').join('X'))]('htmlfile');
+    this.doc.open();
+    this.doc.write('<html></html>');
+    this.doc.close();
+    this.doc.parentWindow.s = this;
+
+    var iframeC = this.doc.createElement('div');
+    iframeC.className = 'socketio';
+
+    this.doc.body.appendChild(iframeC);
+    this.iframe = this.doc.createElement('iframe');
+
+    iframeC.appendChild(this.iframe);
+
+    var self = this
+      , query = io.util.query(this.socket.options.query, 't='+ +new Date);
+
+    this.iframe.src = this.prepareUrl() + query;
+
+    io.util.on(window, 'unload', function () {
+      self.destroy();
+    });
+  };
+
+  /**
+   * The Socket.IO server will write script tags inside the forever
+   * iframe, this function will be used as callback for the incoming
+   * information.
+   *
+   * @param {String} data The message
+   * @param {document} doc Reference to the context
+   * @api private
+   */
+
+  HTMLFile.prototype._ = function (data, doc) {
+    this.onData(data);
+    try {
+      var script = doc.getElementsByTagName('script')[0];
+      script.parentNode.removeChild(script);
+    } catch (e) { }
+  };
+
+  /**
+   * Destroy the established connection, iframe and `htmlfile`.
+   * And calls the `CollectGarbage` function of Internet Explorer
+   * to release the memory.
+   *
+   * @api private
+   */
+
+  HTMLFile.prototype.destroy = function () {
+    if (this.iframe){
+      try {
+        this.iframe.src = 'about:blank';
+      } catch(e){}
+
+      this.doc = null;
+      this.iframe.parentNode.removeChild(this.iframe);
+      this.iframe = null;
+
+      CollectGarbage();
+    }
+  };
+
+  /**
+   * Disconnects the established connection.
+   *
+   * @returns {Transport} Chaining.
+   * @api public
+   */
+
+  HTMLFile.prototype.close = function () {
+    this.destroy();
+    return io.Transport.XHR.prototype.close.call(this);
+  };
+
+  /**
+   * Checks if the browser supports this transport. The browser
+   * must have an `Ac...eXObject` implementation.
+   *
+   * @return {Boolean}
+   * @api public
+   */
+
+  HTMLFile.check = function (socket) {
+    if (typeof window != "undefined" && (['Active'].concat('Object').join('X')) in window){
+      try {
+        var a = new window[(['Active'].concat('Object').join('X'))]('htmlfile');
+        return a && io.Transport.XHR.check(socket);
+      } catch(e){}
+    }
+    return false;
+  };
+
+  /**
+   * Check if cross domain requests are supported.
+   *
+   * @returns {Boolean}
+   * @api public
+   */
+
+  HTMLFile.xdomainCheck = function () {
+    // we can probably do handling for sub-domains, we should
+    // test that it's cross domain but a subdomain here
+    return false;
+  };
+
+  /**
+   * Add the transport to your public io.transports array.
+   *
+   * @api private
+   */
+
+  io.transports.push('htmlfile');
+
+})(
+    'undefined' != typeof io ? io.Transport : module.exports
+  , 'undefined' != typeof io ? io : module.parent.exports
+);
+
+/**
+ * socket.io
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+(function (exports, io) {
+
+  /**
+   * Expose constructor.
+   */
+
+  exports['xhr-polling'] = XHRPolling;
+
+  /**
+   * The XHR-polling transport uses long polling XHR requests to create a
+   * "persistent" connection with the server.
+   *
+   * @constructor
+   * @api public
+   */
+
+  function XHRPolling () {
+    io.Transport.XHR.apply(this, arguments);
+  };
+
+  /**
+   * Inherits from XHR transport.
+   */
+
+  io.util.inherit(XHRPolling, io.Transport.XHR);
+
+  /**
+   * Merge the properties from XHR transport
+   */
+
+  io.util.merge(XHRPolling, io.Transport.XHR);
+
+  /**
+   * Transport name
+   *
+   * @api public
+   */
+
+  XHRPolling.prototype.name = 'xhr-polling';
+
+  /**
+   * Indicates whether heartbeats is enabled for this transport
+   *
+   * @api private
+   */
+
+  XHRPolling.prototype.heartbeats = function () {
+    return false;
+  };
+
+  /**
+   * Establish a connection, for iPhone and Android this will be done once the page
+   * is loaded.
+   *
+   * @returns {Transport} Chaining.
+   * @api public
+   */
+
+  XHRPolling.prototype.open = function () {
+    var self = this;
+
+    io.Transport.XHR.prototype.open.call(self);
+    return false;
+  };
+
+  /**
+   * Starts a XHR request to wait for incoming messages.
+   *
+   * @api private
+   */
+
+  function empty () {};
+
+  XHRPolling.prototype.get = function () {
+    if (!this.isOpen) return;
+
+    var self = this;
+
+    function stateChange () {
+      if (this.readyState == 4) {
+        this.onreadystatechange = empty;
+
+        if (this.status == 200) {
+          self.onData(this.responseText);
+          self.get();
+        } else {
+          self.onClose();
+        }
+      }
+    };
+
+    function onload () {
+      this.onload = empty;
+      this.onerror = empty;
+      self.retryCounter = 1;
+      self.onData(this.responseText);
+      self.get();
+    };
+
+    function onerror () {
+      self.retryCounter ++;
+      if(!self.retryCounter || self.retryCounter > 3) {
+        self.onClose();
+      } else {
+        self.get();
+      }
+    };
+
+    this.xhr = this.request();
+
+    if (global.XDomainRequest && this.xhr instanceof XDomainRequest) {
+      this.xhr.onload = onload;
+      this.xhr.onerror = onerror;
+    } else {
+      this.xhr.onreadystatechange = stateChange;
+    }
+
+    this.xhr.send(null);
+  };
+
+  /**
+   * Handle the unclean close behavior.
+   *
+   * @api private
+   */
+
+  XHRPolling.prototype.onClose = function () {
+    io.Transport.XHR.prototype.onClose.call(this);
+
+    if (this.xhr) {
+      this.xhr.onreadystatechange = this.xhr.onload = this.xhr.onerror = empty;
+      try {
+        this.xhr.abort();
+      } catch(e){}
+      this.xhr = null;
+    }
+  };
+
+  /**
+   * Webkit based browsers show a infinit spinner when you start a XHR request
+   * before the browsers onload event is called so we need to defer opening of
+   * the transport until the onload event is called. Wrapping the cb in our
+   * defer method solve this.
+   *
+   * @param {Socket} socket The socket instance that needs a transport
+   * @param {Function} fn The callback
+   * @api private
+   */
+
+  XHRPolling.prototype.ready = function (socket, fn) {
+    var self = this;
+
+    io.util.defer(function () {
+      fn.call(self);
+    });
+  };
+
+  /**
+   * Add the transport to your public io.transports array.
+   *
+   * @api private
+   */
+
+  io.transports.push('xhr-polling');
+
+})(
+    'undefined' != typeof io ? io.Transport : module.exports
+  , 'undefined' != typeof io ? io : module.parent.exports
+);
+
+/**
+ * socket.io
+ * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
+ * MIT Licensed
+ */
+
+(function (exports, io) {
+  /**
+   * There is a way to hide the loading indicator in Firefox. If you create and
+   * remove a iframe it will stop showing the current loading indicator.
+   * Unfortunately we can't feature detect that and UA sniffing is evil.
+   *
+   * @api private
+   */
+
+  var indicator = global.document && "MozAppearance" in
+    global.document.documentElement.style;
+
+  /**
+   * Expose constructor.
+   */
+
+  exports['jsonp-polling'] = JSONPPolling;
+
+  /**
+   * The JSONP transport creates an persistent connection by dynamically
+   * inserting a script tag in the page. This script tag will receive the
+   * information of the Socket.IO server. When new information is received
+   * it creates a new script tag for the new data stream.
+   *
+   * @constructor
+   * @extends {io.Transport.xhr-polling}
+   * @api public
+   */
+
+  function JSONPPolling (socket) {
+    io.Transport['xhr-polling'].apply(this, arguments);
+
+    this.index = io.j.length;
+
+    var self = this;
+
+    io.j.push(function (msg) {
+      self._(msg);
+    });
+  };
+
+  /**
+   * Inherits from XHR polling transport.
+   */
+
+  io.util.inherit(JSONPPolling, io.Transport['xhr-polling']);
+
+  /**
+   * Transport name
+   *
+   * @api public
+   */
+
+  JSONPPolling.prototype.name = 'jsonp-polling';
+
+  /**
+   * Posts a encoded message to the Socket.IO server using an iframe.
+   * The iframe is used because script tags can create POST based requests.
+   * The iframe is positioned outside of the view so the user does not
+   * notice it's existence.
+   *
+   * @param {String} data A encoded message.
+   * @api private
+   */
+
+  JSONPPolling.prototype.post = function (data) {
+    var self = this
+      , query = io.util.query(
+             this.socket.options.query
+          , 't='+ (+new Date) + '&i=' + this.index
+        );
+
+    if (!this.form) {
+      var form = document.createElement('form')
+        , area = document.createElement('textarea')
+        , id = this.iframeId = 'socketio_iframe_' + this.index
+        , iframe;
+
+      form.className = 'socketio';
+      form.style.position = 'absolute';
+      form.style.top = '0px';
+      form.style.left = '0px';
+      form.style.display = 'none';
+      form.target = id;
+      form.method = 'POST';
+      form.setAttribute('accept-charset', 'utf-8');
+      area.name = 'd';
+      form.appendChild(area);
+      document.body.appendChild(form);
+
+      this.form = form;
+      this.area = area;
+    }
+
+    this.form.action = this.prepareUrl() + query;
+
+    function complete () {
+      initIframe();
+      self.socket.setBuffer(false);
+    };
+
+    function initIframe () {
+      if (self.iframe) {
+        self.form.removeChild(self.iframe);
+      }
+
+      try {
+        // ie6 dynamic iframes with target="" support (thanks Chris Lambacher)
+        iframe = document.createElement('<iframe name="'+ self.iframeId +'">');
+      } catch (e) {
+        iframe = document.createElement('iframe');
+        iframe.name = self.iframeId;
+      }
+
+      iframe.id = self.iframeId;
+
+      self.form.appendChild(iframe);
+      self.iframe = iframe;
+    };
+
+    initIframe();
+
+    // we temporarily stringify until we figure out how to prevent
+    // browsers from turning `\n` into `\r\n` in form inputs
+    this.area.value = io.JSON.stringify(data);
+
+    try {
+      this.form.submit();
+    } catch(e) {}
+
+    if (this.iframe.attachEvent) {
+      iframe.onreadystatechange = function () {
+        if (self.iframe.readyState == 'complete') {
+          complete();
+        }
+      };
+    } else {
+      this.iframe.onload = complete;
+    }
+
+    this.socket.setBuffer(true);
+  };
+
+  /**
+   * Creates a new JSONP poll that can be used to listen
+   * for messages from the Socket.IO server.
+   *
+   * @api private
+   */
+
+  JSONPPolling.prototype.get = function () {
+    var self = this
+      , script = document.createElement('script')
+      , query = io.util.query(
+             this.socket.options.query
+          , 't='+ (+new Date) + '&i=' + this.index
+        );
+
+    if (this.script) {
+      this.script.parentNode.removeChild(this.script);
+      this.script = null;
+    }
+
+    script.async = true;
+    script.src = this.prepareUrl() + query;
+    script.onerror = function () {
+      self.onClose();
+    };
+
+    var insertAt = document.getElementsByTagName('script')[0];
+    insertAt.parentNode.insertBefore(script, insertAt);
+    this.script = script;
+
+    if (indicator) {
+      setTimeout(function () {
+        var iframe = document.createElement('iframe');
+        document.body.appendChild(iframe);
+        document.body.removeChild(iframe);
+      }, 100);
+    }
+  };
+
+  /**
+   * Callback function for the incoming message stream from the Socket.IO server.
+   *
+   * @param {String} data The message
+   * @api private
+   */
+
+  JSONPPolling.prototype._ = function (msg) {
+    this.onData(msg);
+    if (this.isOpen) {
+      this.get();
+    }
+    return this;
+  };
+
+  /**
+   * The indicator hack only works after onload
+   *
+   * @param {Socket} socket The socket instance that needs a transport
+   * @param {Function} fn The callback
+   * @api private
+   */
+
+  JSONPPolling.prototype.ready = function (socket, fn) {
+    var self = this;
+    if (!indicator) return fn.call(this);
+
+    io.util.load(function () {
+      fn.call(self);
+    });
+  };
+
+  /**
+   * Checks if browser supports this transport.
+   *
+   * @return {Boolean}
+   * @api public
+   */
+
+  JSONPPolling.check = function () {
+    return 'document' in global;
+  };
+
+  /**
+   * Check if cross domain requests are supported
+   *
+   * @returns {Boolean}
+   * @api public
+   */
+
+  JSONPPolling.xdomainCheck = function () {
+    return true;
+  };
+
+  /**
+   * Add the transport to your public io.transports array.
+   *
+   * @api private
+   */
+
+  io.transports.push('jsonp-polling');
+
+})(
+    'undefined' != typeof io ? io.Transport : module.exports
+  , 'undefined' != typeof io ? io : module.parent.exports
+);
+
+if (typeof define === "function" && define.amd) {
+  define([], function () { return io; });
+} else {
+  return io;
+}
+
+})(window);
diff --git a/src/wrtcp.js b/src/wrtcp.js
new file mode 100644
index 0000000000000..e3f0e43bb9f4d
--- /dev/null
+++ b/src/wrtcp.js
@@ -0,0 +1,821 @@
+/*
+The MIT License
+
+Copyright (c) 2012, Mozilla Foundation
+Copyright (c) 2012, Alan Kligman
+All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+(function() {
+
+  /* Notes
+   *
+   * - Continue using prefixed names for now.
+   *
+   */
+
+  var io = SocketIO;
+  var webrtcSupported = true;
+
+  var RTCPeerConnection;
+  if(window.mozRTCPeerConnection)
+    RTCPeerConnection = window.mozRTCPeerConnection;
+  else if(window.webkitRTCPeerConnection)
+    RTCPeerConnection = window.webkitRTCPeerConnection;
+  else if(window.RTCPeerConnection)
+    RTCPeerConnection = window.RTCPeerConnection
+  else
+    webrtcSupported = false;
+
+  var RTCSessionDescription;
+  if(window.mozRTCSessionDescription)
+    RTCSessionDescription = window.mozRTCSessionDescription;
+  else if(window.webkitRTCSessionDescription)
+    RTCSessionDescription = window.webkitRTCSessionDescription;
+  else if(window.RTCSessionDescription)
+    RTCSessionDescription = window.RTCSessionDescription
+  else
+    webrtcSupported = false;
+
+  var RTCIceCandidate;
+  if(window.mozRTCIceCandidate)
+    RTCIceCandidate = window.mozRTCIceCandidate;
+  else if(window.webkitRTCIceCandidate)
+    RTCIceCandidate = window.webkitRTCIceCandidate;
+  else if(window.RTCIceCandidate)
+    RTCIceCandidate = window.RTCIceCandidate;
+  else
+    webrtcSupported = false;
+
+  var getUserMedia;
+  if(!navigator.getUserMedia) {
+    if(navigator.mozGetUserMedia)
+      getUserMedia = navigator.mozGetUserMedia.bind(navigator);
+    else if(navigator.webkitGetUserMedia)
+      getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
+    else
+      webrtcSupported = false;
+  } else {
+    getUserMedia = navigator.getUserMedia.bind(navigator);
+  }
+
+  // FIXME: browser detection is gross, but I don't see another way to do this
+  var RTCConnectProtocol;
+  if(window.mozRTCPeerConnection) {
+    RTCConnectProtocol = mozRTCConnectProtocol;
+  } else if(window.webkitRTCPeerConnection) {
+    RTCConnectProtocol = webkitRTCConnectProtocol;
+  } else {
+    webrtcSupported = false;
+  }
+
+  function callback(object, method, args) {
+    if(!Array.isArray(args))
+      args = [args];
+    if(method in object && 'function' === typeof object[method]) {
+      object[method].apply(object, args);
+    }
+  };
+
+  function fail(object, method, error) {
+    if (!(error instanceof Error))
+      error = new Error(error);
+    callback(object, method, [error]);
+  };
+
+  function defer(queue, object, method, args) {
+    if(queue) {
+      queue.push([object, method, args]);
+      return true;
+    } else {
+      return false;
+    }
+  };
+
+  function processDeferredQueue(queue) {
+    while(queue.length) {
+      var deferred = queue.shift();
+      callback(deferred[0], deferred[1], deferred[2]);
+    }
+  };
+
+  var ONE_SECOND = 1000; // milliseconds
+  var DEFAULT_CONNECTION_TIMEOUT = 10 * ONE_SECOND;
+  var DEFAULT_PING_TIMEOUT = 1 * ONE_SECOND;
+  var RELIABLE_CHANNEL_OPTIONS = {
+    reliable: false
+  };
+  var UNRELIABLE_CHANNEL_OPTIONS = {
+    outOfOrderAllowed: true,
+    maxRetransmitNum: 0,
+    reliable: false
+  };
+
+  function PendingConnectionAbortError(message) {
+    this.name = "PendingConnectionAbortError";
+    this.message = (message || "");
+  };
+  PendingConnectionAbortError.prototype = Error.prototype;
+
+  function ConnectionFailedError(message) {
+    this.name = "ConnectionFailedError";
+    this.message = (message || "");
+  };
+  ConnectionFailedError.prototype = Error.prototype;
+
+  var E = {
+    PendingConnectionAbortError: PendingConnectionAbortError,
+    ConnectionFailedError: ConnectionFailedError
+  };
+
+  function WebSocketBroker(brokerUrl) {
+    this.brokerUrl = brokerUrl;
+    this.state = WebSocketBroker.OFFLINE;
+
+    this.onstatechange = null;
+    this.onreceive = null;
+    this.onerror = null;
+
+    this.socket = null;
+    this.route = null;
+  };
+
+  // States
+  WebSocketBroker.OFFLINE     = 0x00;
+  WebSocketBroker.CONNECTING  = 0x01;
+  WebSocketBroker.CONNECTED   = 0x02;
+  // Flags
+  WebSocketBroker.ROUTED      = 0x10;
+  WebSocketBroker.LISTENING   = 0x20;
+
+  WebSocketBroker.prototype.setState = function setState(state, clearFlags) {
+    var clear = clearFlags ? 0x00 : 0xF0;
+    this.state &= clear >>> 0;
+    this.state |= state >>> 0;
+    callback(this, 'onstatechange', [this.state, (state | (clear & 0x0)) >>> 0]);
+  };
+  WebSocketBroker.prototype.setFlag = function setFlag(flag) {
+    this.state = (this.state | flag) >>> 0;
+    callback(this, 'onstatechange', [this.state, flag])
+  };
+  WebSocketBroker.prototype.clearFlag = function clearFlag(flag) {
+    flag = (~flag) >>> 0;
+    this.state = (this.state & flag) >>> 0;
+    callback(this, 'onstatechange', [this.state, flag])
+  };
+  WebSocketBroker.prototype.checkState = function checkState(mask) {
+    return !!(this.state & mask);
+  };
+  WebSocketBroker.prototype.connect = function connect() {
+    var that = this;
+    var socket = io.connect(this.brokerUrl + '/peer', {
+      'sync disconnect on unload': true // partially fixes 'interrupted while page loading' warning
+    });
+
+    socket.on('connecting', function onconnecting() {
+      that.setState(WebSocketBroker.CONNECTING, true);
+    });
+
+    socket.on('connect', function onconnect() {
+      that.setState(WebSocketBroker.CONNECTED, true);
+    });
+
+    socket.on('connect_failed', function onconnect_failed() {
+      that.setState(WebSocketBroker.OFFLINE, true);
+    });
+
+    socket.on('route', function onroute(route) {
+      that.route = route;
+      that.setFlag(WebSocketBroker.ROUTED);
+    });
+
+    socket.on('disconnect', function ondisconnect() {
+      that.setState(WebSocketBroker.OFFLINE, true);
+    });
+
+    socket.on('error', function onerror(error) {
+      fail(that, 'onerror', error);
+    });
+
+    socket.on('receive', function onreceive(message) {
+      var from = message['from'];
+      var data = message['data'];
+      callback(that, 'onreceive', [from, data]);
+    });
+
+    this.socket = socket;
+  };
+  WebSocketBroker.prototype.disconnect = function disconnect() {
+    if(this.checkState(WebSocketBroker.CONNECTED)) {
+      this.socket.disconnect();
+      this.setState(WebSocketBroker.OFFLINE, true);
+      return true;
+    } else {
+      return false;
+    }
+  };
+  WebSocketBroker.prototype.listen = function listen(options) {
+    var that = this;
+    if(this.checkState(WebSocketBroker.CONNECTED)) {
+      this.socket.emit('listen', options, function onresponse(response) {
+        if(response && response['error']) {
+          var error = new Error(response['error']);
+          fail(that, 'onerror', error);
+        } else {
+          that.setFlag(WebSocketBroker.LISTENING);
+        }
+      });
+    }
+  };
+  WebSocketBroker.prototype.ignore = function ignore() {
+    var that = this;
+    if(this.checkState(WebSocketBroker.CONNECTED)) {
+      this.socket.emit('ignore', null, function onresponse(response) {
+        if(response && response['error']) {
+          var error = new Error(response['error']);
+          fail(that, 'onerror', error)
+        } else {
+          that.clearFlag(WebSocketBroker.LISTENING);
+        }
+      });
+    }
+  };
+  WebSocketBroker.prototype.send = function send(to, message) {
+    var that = this;
+    if(this.checkState(WebSocketBroker.CONNECTED)) {
+      this.socket.emit('send', {'to': to, 'data': message}, function onresponse(response) {
+        if(response && response['error']) {
+          var error = new Error(response['error']);
+          fail(that, 'onerror', error)
+        }
+      });
+    };
+  };
+
+  var dataChannels = {
+    'reliable': 'RELIABLE',
+    'unreliable': 'UNRELIABLE',
+    '@control': 'RELIABLE'
+  };
+  var nextDataConnectionPort = 1;
+  function CommonRTCConnectProtocol() {
+    // FIXME: these timeouts should be configurable
+    this.connectionTimeout = 10 * ONE_SECOND;
+    this.pingTimeout = 1 * ONE_SECOND;
+  };
+  CommonRTCConnectProtocol.prototype.process = function process(message) {
+    var that = this;
+
+    var type = message['type'];
+    switch(type) {
+      case 'ice':
+        var candidate = JSON.parse(message['candidate']);
+        if(candidate)
+          this.handleIce(candidate);
+        break;
+
+      case 'offer':
+        that.ports.remote = message['port'];
+        var offer = {
+          'type': 'offer',
+          'sdp': message['description']
+        };
+        this.handleOffer(offer);
+        break;
+
+      case 'answer':
+        that.ports.remote = message['port'];
+        var answer = {
+          'type': 'answer',
+          'sdp': message['description']
+        };
+        this.handleAnswer(answer);
+        break;
+
+      case 'abort':
+        this.handleAbort();
+        break;
+
+      default:
+        fail(this, 'onerror', 'unknown message');
+    }
+  };
+  CommonRTCConnectProtocol.prototype.handleAbort = function handleAbort() {
+    fail(this, 'onerror', new Error(E.RTCConnectProtocolAbort));
+  };
+  CommonRTCConnectProtocol.prototype.initialize = function initialize(cb) {
+    var that = this;
+
+    if(this.peerConnection)
+      return cb();
+
+    // FIXME: peer connection servers should be configurable
+    this.peerConnection = new RTCPeerConnection(this.connectionServers, this.connectionOptions);
+    this.peerConnection.onicecandidate = function(event) {
+      var message = {
+        'type': 'ice',
+        'candidate': JSON.stringify(event.candidate)
+      };
+      callback(that, 'onmessage', message);
+    };
+    this.peerConnection.onaddstream = function(event) {
+      that.streams['remote'] = event.stream;
+    };
+    this.peerConnection.onstatechange = function(event) {
+      console.log(event.target.readyState);
+    };
+
+    function createStream(useFake) {
+      useFake = (!useVideo && !useAudio) ? true : useFake;
+      var useVideo = !!that.options['video'];
+      var useAudio = !!that.options['audio'];
+      var mediaOptions = {
+        video: useVideo,
+        audio: (!useVideo && !useAudio) ? true : useAudio,
+        fake: useFake
+      };
+      getUserMedia(mediaOptions,
+        function(stream) {
+          that.peerConnection.addStream(stream);
+          that.streams['local'] = stream;
+          cb();
+        },
+        function(error) {
+          console.error('!', error);
+          if(!useFake)
+            createStream(true);
+          else
+            fail(that, 'onerror', error);
+        }
+      );
+    }
+
+    createStream();
+  };
+  CommonRTCConnectProtocol.prototype.handleIce = function handleIce(candidate) {
+    var that = this;
+
+    function setIce() {
+      if(!that.peerConnection.remoteDescription) {
+        return
+      }
+      that.peerConnection.addIceCandidate(new RTCIceCandidate(candidate),
+        function(error) {
+          fail(that, 'onerror', error);
+        }
+      );
+    };
+
+    this.initialize(setIce);
+  };
+  CommonRTCConnectProtocol.prototype.initiate = function initiate() {
+    var that = this;
+    this.initiator = true;
+
+    function createDataChannels() {
+      var labels = Object.keys(dataChannels);
+      labels.forEach(function(label) {
+        var channelOptions = that.channelOptions[dataChannels[label]];
+        var channel = that._pending[label] = that.peerConnection.createDataChannel(label, channelOptions);
+        channel.binaryType = that.options['binaryType'];
+        channel.onopen = function() {
+          that.channels[label] = channel;
+          delete that._pending[label];
+          if(Object.keys(that.channels).length === labels.length) {
+            that.complete = true;
+            callback(that, 'oncomplete', []);
+          }
+        };
+        channel.onerror = function(error) {
+          console.error(error);
+          fail(that, 'onerror', error);
+        };
+      });
+      createOffer();
+    };
+
+    function createOffer() {
+      that.peerConnection.createOffer(setLocal,
+        function(error) {
+          fail(that, 'onerror', error);
+        }
+      );
+    };
+
+    function setLocal(description) {
+      that.peerConnection.setLocalDescription(new RTCSessionDescription(description), complete,
+        function(error) {
+          fail(that, 'onerror', error);
+        }
+      );
+
+      function complete() {
+        var message = {
+          'type': 'offer',
+          'description': description['sdp'],
+          'port': that.ports.local
+        };
+        callback(that, 'onmessage', message);
+      };
+    };
+
+    this.initialize(createDataChannels);
+  };
+  CommonRTCConnectProtocol.prototype.handleOffer = function handleOffer(offer) {
+    var that = this;
+
+    function handleDataChannels() {
+      var labels = Object.keys(dataChannels);
+      that.peerConnection.ondatachannel = function(event) {
+        var channel = event.channel;
+        var label = channel.label;
+        that._pending[label] = channel;
+        channel.binaryType = that.options['binaryType'];
+        channel.onopen = function() {
+          that.channels[label] = channel;
+          delete that._pending[label];
+          if(Object.keys(that.channels).length === labels.length) {
+            that.complete = true;
+            callback(that, 'oncomplete', []);
+          }
+        };
+        channel.onerror = function(error) {
+          console.error(error);
+          fail(that, 'onerror', error);
+        };
+      };
+      setRemote();
+    };
+
+    function setRemote() {
+      that.peerConnection.setRemoteDescription(new RTCSessionDescription(offer), createAnswer,
+        function(error) {
+          fail(that, 'onerror', error);
+        }
+      );
+    };
+
+    function createAnswer() {
+      that.peerConnection.createAnswer(setLocal,
+        function(error) {
+          fail(that, 'onerror', error);
+        }
+      );
+    };
+
+    function setLocal(description) {
+      that.peerConnection.setLocalDescription(new RTCSessionDescription(description), complete,
+        function(error) {
+          fail(that, 'onerror', error);
+        }
+      );
+
+      function complete() {
+        var message = {
+          'type': 'answer',
+          'description': description['sdp'],
+          'port': that.ports.local
+        };
+        callback(that, 'onmessage', message);
+      };
+    };
+
+    this.initialize(handleDataChannels);
+  };
+  CommonRTCConnectProtocol.prototype.handleAnswer = function handleAnswer(answer) {
+    var that = this;
+
+    function setRemote() {
+      that.peerConnection.setRemoteDescription(new RTCSessionDescription(answer), complete,
+        function(error) {
+          fail(that, 'onerror', error);
+        }
+      );
+    };
+
+    function complete() {
+    };
+
+    this.initialize(setRemote);
+  };
+
+  function mozRTCConnectProtocol(options) {
+    this.options = options;
+    this.onmessage = null;
+    this.oncomplete = null;
+    this.onerror = null;
+
+    this.complete = false;
+    this.ports = {
+      local: nextDataConnectionPort ++,
+      remote: null
+    };
+    this.streams = {
+      local: null,
+      remote: null
+    };
+    this.initiator = false;
+
+    this.peerConnection = null;
+    this.channels = {};
+    this._pending = {};
+    this.connectionServers = null;
+    this.connectionOptions = null;
+    this.channelOptions = {
+      RELIABLE: {
+        // defaults
+      },
+      UNRELIABLE: {
+        outOfOrderAllowed: true,
+        maxRetransmitNum: 0
+      }
+    };
+  };
+  mozRTCConnectProtocol.prototype = new CommonRTCConnectProtocol();
+  mozRTCConnectProtocol.prototype.constructor = mozRTCConnectProtocol;
+
+  function webkitRTCConnectProtocol(options) {
+    this.options = options;
+    this.onmessage = null;
+    this.oncomplete = null;
+    this.onerror = null;
+
+    this.complete = false;
+    this.ports = {
+      local: nextDataConnectionPort ++,
+      remote: null
+    };
+    this.streams = {
+      local: null,
+      remote: null
+    };
+    this.initiator = false;
+
+    this.peerConnection = null;
+    this.channels = {};
+    this._pending = {};
+    this.connectionServers = {iceServers:[{url:'stun:23.21.150.121'}]};
+    this.connectionOptions = {
+      'optional': [{ 'RtpDataChannels': true }]
+    };
+    this.channelOptions = {
+      RELIABLE: {
+        // FIXME: reliable channels do not work in chrome yet
+        reliable: false
+      },
+      UNRELIABLE: {
+        reliable: false
+      }
+    };
+  };
+  webkitRTCConnectProtocol.prototype = new CommonRTCConnectProtocol();
+  webkitRTCConnectProtocol.prototype.constructor = webkitRTCConnectProtocol;
+
+  // FIXME: this could use a cleanup
+  var nextConnectionId = 1;
+  function Connection(options, peerConnection, streams, channels) {
+    var that = this;
+    this.id = nextConnectionId ++;
+    this.streams = streams;
+    this.connected = false;
+    this.messageFlag = false;
+
+    this.onmessage = null;
+    this.ondisconnect = null;
+    this.onerror = null;
+
+    this.peerConnection = peerConnection;
+
+    // DataChannels
+    this.channels = channels;
+
+    this.connectionTimer = null;
+    this.pingTimer = null;
+
+    function handleConnectionTimerExpired() {
+      if(!that.connected)
+        return
+      this.connectionTimer = null;
+      if(false === that.messageFlag) {
+        that.channels['@control'].send('ping');
+        this.pingTimer = window.setTimeout(handlePingTimerExpired, options['pingTimeout']);
+      } else {
+        that.messageFlag = false;
+        this.connectionTimer = window.setTimeout(handleConnectionTimerExpired, options['connectionTimeout']);
+      }
+    };
+    function handlePingTimerExpired() {
+      if(!that.connected)
+        return
+      this.pingTimer = null;
+      if(false === that.messageFlag) {
+        that.connected = false;
+        that.close();
+      } else {
+        that.messageFlag = false;
+        this.connectionTimer = window.setTimeout(handleConnectionTimerExpired, options['connectionTimeout']);
+      }
+    };
+
+    Object.keys(this.channels).forEach(function(label) {
+      var channel = that.channels[label];
+      if(label.match('^@')) // check for internal channels
+        return;
+
+      channel.onmessage = function onmessage(message) {
+        that.messageFlag = true;
+        callback(that, 'onmessage', [label, message]);
+      };
+    });
+    this.channels['@control'].onmessage = function onmessage(message) {
+      that.messageFlag = true;
+      if(that.connected) {
+        var data = message.data;
+        if('ping' === data) {
+          that.channels['@control'].send('pong');
+        } else if('pong' === data) {
+          // ok
+        } else if('quit' === data) {
+          that.close();
+        }
+      }
+    };
+
+    this.connected = true;
+    this.connectionTimer = window.setTimeout(handleConnectionTimerExpired, options['connectionTimeout']);
+  };
+  Connection.prototype.close = function close() {
+    console.log('close connection');
+    if(this.connected) {
+      this.channels['@control'].send('quit');
+    }
+    this.connected = false;
+    this.peerConnection.close();
+    if(this.connectionTimer) {
+      window.clearInterval(this.connectionTimer);
+      this.connectionTimer = null;
+    }
+    if(this.pingTimer) {
+      window.clearInterval(this.pingTimer);
+      this.pingTimer = null;
+    }
+    this.peerConnection = null;
+    callback(this, 'ondisconnect', []);
+  };
+  Connection.prototype.send = function send(label, message) {
+    this.channels[label].send(message);
+  };
+
+  function PendingConnection(route, incoming) {
+    this.route = route;
+    this.incoming = incoming;
+    this.proceed = true;
+  };
+  PendingConnection.prototype.accept = function accept() {
+    this.proceed = true;
+  };
+  PendingConnection.prototype.reject = function reject() {
+    this.proceed = false;
+  };
+
+  function Peer(brokerUrl, options) {
+    if(!webrtcSupported)
+      throw new Error("WebRTC not supported");
+
+    var that = this;
+    this.brokerUrl = brokerUrl;
+    this.options = options = options || {};
+    options['binaryType'] = options['binaryType'] || 'arraybuffer';
+    options['connectionTimeout'] = options['connectionTimeout'] || 10 * ONE_SECOND;
+    options['pingTimeout'] = options['pingTimeout'] || 1 * ONE_SECOND;
+
+    this.onconnection = null;
+    this.onpending = null;
+    this.onroute = null;
+    this.onerror = null;
+
+    this.broker = new WebSocketBroker(brokerUrl);
+    this.pending = {};
+
+    this.queues = {
+      connected: [],
+      listening: []
+    };
+
+    this.broker.onstatechange = function onstatechange(state, mask) {
+      if(that.queues.connected.length && that.broker.checkState(WebSocketBroker.ROUTED)) {
+        processDeferredQueue(that.queues.connected);
+        if(that.queues.listening.length && that.broker.checkState(WebSocketBroker.LISTENING)) {
+          processDeferredQueue(that.queues.listening);
+        }
+      }
+      if(mask & WebSocketBroker.ROUTED) {
+        callback(that, 'onroute', that.broker.route);
+      }
+    };
+
+    this.broker.onreceive = function onreceive(from, message) {
+      var handshake;
+      if(!that.pending.hasOwnProperty(from)) {
+        if(!that.broker.checkState(WebSocketBroker.LISTENING)) {
+          return;
+        }
+
+        var pendingConnection = new PendingConnection(from, /*incoming*/ true);
+        callback(that, 'onpending', [pendingConnection]);
+        if(!pendingConnection['proceed'])
+          return;
+
+        var handshake = that.pending[from] = new RTCConnectProtocol(that.options);
+        handshake.oncomplete = function() {
+          var connection = new Connection(that.options, handshake.peerConnection, handshake.streams, handshake.channels);
+          connection['route'] = from;
+          delete that.pending[from];
+          callback(that, 'onconnection', [connection]);
+        };
+        handshake.onmessage = function(message) {
+          that.broker.send(from, message);
+        };
+        handshake.onerror = function(error) {
+          delete that.pending[from];
+          callback(that, 'onerror', [error]);
+        };
+      } else {
+        handshake = that.pending[from];
+      }
+      handshake.process(message);
+    };
+
+    this.broker.connect();
+  };
+  Peer.prototype.listen = function listen(options) {
+    if(!this.broker.checkState(WebSocketBroker.ROUTED))
+      return defer(this.queues.connected, this, 'listen', [options]);
+
+    options = options || {};
+    options['url'] = options['url'] || window.location.toString();
+    options['listed'] = (undefined !== options['listed']) ? options['listed'] : true;
+    options['metadata'] = options['metadata'] || {};
+
+    this.broker.listen(options);
+  };
+  Peer.prototype.ignore = function ignore() {
+    throw new Error('not implemented');
+  };
+  Peer.prototype.connect = function connect(route) {
+    if(!this.broker.checkState(WebSocketBroker.ROUTED))
+      return defer(this.queues.connected, this, 'connect', [route]);
+
+    var that = this;
+
+    if(this.pending.hasOwnProperty(route))
+      throw new Error('already connecting to this host'); // FIXME: we can handle this better
+
+    var pendingConnection = new PendingConnection(route, /*incoming*/ false);
+    callback(that, 'onpending', [pendingConnection]);
+    if(!pendingConnection['proceed'])
+      return;
+
+    var handshake = this.pending[route] = new RTCConnectProtocol(this.options);
+    handshake.oncomplete = function() {
+      var connection = new Connection(this.options, handshake.peerConnection, handshake.streams, handshake.channels);
+      connection['route'] = route;
+      delete that.pending[route];
+      callback(that, 'onconnection', [connection]);
+    };
+    handshake.onmessage = function(message) {
+      that.broker.send(route, message);
+    };
+    handshake.onerror = function(error) {
+      delete that.pending[route];
+      fail(that, 'onerror', error);
+    };
+
+    handshake.initiate();
+  };
+  Peer.prototype.close = function close() {
+    this.broker.disconnect();
+  };
+  Peer.E = E;
+
+  return Peer;
+
+})();
\ No newline at end of file
diff --git a/system/include/emscripten/emscripten.h b/system/include/emscripten/emscripten.h
index 77aa1df2c6dae..28e6063cac1ae 100644
--- a/system/include/emscripten/emscripten.h
+++ b/system/include/emscripten/emscripten.h
@@ -1,3 +1,6 @@
+#ifndef __emscripten_h__
+#define __emscripten_h__
+
 /**
  * This file contains a few useful things for compiling C/C++ code
  * with Emscripten, an LLVM-to-JavaScript compiler.
@@ -381,3 +384,5 @@ void emscripten_jcache_printf_(...); /* internal use */
 }
 #endif
 
+#endif // __emscripten_h__
+
diff --git a/tests/cases/fp80.ll b/tests/cases/fp80_ta2.ll
similarity index 100%
rename from tests/cases/fp80.ll
rename to tests/cases/fp80_ta2.ll
diff --git a/tests/cases/muli33.ll b/tests/cases/muli33_ta2.ll
similarity index 100%
rename from tests/cases/muli33.ll
rename to tests/cases/muli33_ta2.ll
diff --git a/tests/cases/muli33.txt b/tests/cases/muli33_ta2.txt
similarity index 100%
rename from tests/cases/muli33.txt
rename to tests/cases/muli33_ta2.txt
diff --git a/tests/cases/philoop.ll b/tests/cases/philoop_ta2.ll
similarity index 100%
rename from tests/cases/philoop.ll
rename to tests/cases/philoop_ta2.ll
diff --git a/tests/cases/philoop.txt b/tests/cases/philoop_ta2.txt
similarity index 100%
rename from tests/cases/philoop.txt
rename to tests/cases/philoop_ta2.txt
diff --git a/tests/files.cpp b/tests/files.cpp
index 04baa1514c2e3..176cdb62020fd 100644
--- a/tests/files.cpp
+++ b/tests/files.cpp
@@ -56,6 +56,9 @@ int main()
   fwrite(data, 1, 5, outf);
   fclose(outf);
 
+  FILE *devNull = fopen("/dev/null", "rb");
+  assert(devNull);
+
   char data2[10];
   FILE *inf = fopen("go.out", "rb");
   int num = fread(data2, 1, 10, inf);
diff --git a/tests/runner.py b/tests/runner.py
index b783a2d86ad74..53eb56feeefb3 100755
--- a/tests/runner.py
+++ b/tests/runner.py
@@ -1916,6 +1916,47 @@ def test_math_hyperbolic(self):
         expected = open(path_from_root('tests', 'hyperbolic', 'output.txt'), 'r').read()
         self.do_run(src, expected)
 
+    def test_frexp(self):
+        src = '''
+          #include <stdio.h>
+          #include <math.h>
+          #include <assert.h>
+
+          static const double tol=1e-16;
+
+          void test_value(double value)
+          {
+            int exponent;
+            double x=frexp(value, &exponent);
+            double expected=x*pow(2.0, exponent);
+
+            printf("%f=%f*2^%d\\n", value, x, exponent);
+
+            assert(fabs(expected-value)<tol);
+            assert(x==0 || (fabs(x)>=5e-1 && fabs(x)<1)); // x has a magnitude in the interval [1/2, 1)
+          }
+
+          int main()
+          {
+            test_value(0);
+            test_value(100.1);
+            test_value(-100.1);
+            test_value(.5);
+            test_value(-.5);
+            test_value(1-1e-16);
+            test_value(-(1-1e-16));
+
+            return 0;
+          }
+        '''
+        self.do_run(src, '''0.000000=0.000000*2^0
+100.100000=0.782031*2^7
+-100.100000=-0.782031*2^7
+0.500000=0.500000*2^0
+-0.500000=-0.500000*2^0
+1.000000=1.000000*2^0
+-1.000000=-1.000000*2^0''')
+
     def test_getgep(self):
         # Generated code includes getelementptr (getelementptr, 0, 1), i.e., GEP as the first param to GEP
         src = '''
@@ -2203,8 +2244,8 @@ def test_errar(self):
           }
           '''
         expected = '''
-          <Numerical argument out of domain>
-          <Resource temporarily unavailable>
+          <Math arg out of domain of func>
+          <No more processes>
           <34>
           <123>
           '''
@@ -3613,36 +3654,36 @@ def test_life(self):
       src = open(path_from_root('tests', 'life.c'), 'r').read()
       self.do_run(src, '''--------------------------------
 []                                    []                  [][][]
-                    []  []    []    [][]  []            []  []
-[]                [][]  [][]              [][][]      []
+                    []  []    []    [][]  []            []  []  
+[]                [][]  [][]              [][][]      []        
                   []    []      []      []  [][]    []        []
                   []  [][]    []        []    []  []    [][][][]
-                    [][]      [][]  []    [][][]  []        []
-                                []  [][]  [][]    [][]  [][][]
+                    [][]      [][]  []    [][][]  []        []  
+                                []  [][]  [][]    [][]  [][][]  
                                     [][]          [][][]  []  []
                                     [][]              [][]    []
                                                           [][][]
-                                                            []
-
-
-
-
-                                        [][][]
-                                      []      [][]      [][]
-                                      [][]      []  [][]  [][]
-                                                    [][]  [][]
-                                                      []
-                  [][]
+                                                            []  
+                                                                
+                                                                
+                                                                
+                                                                
+                                        [][][]                  
+                                      []      [][]      [][]    
+                                      [][]      []  [][]  [][]  
+                                                    [][]  [][]  
+                                                      []        
+                  [][]                                          
                   [][]                                        []
 []                                                      [][]  []
                                                   [][][]      []
-                                                []      [][]
+                                                []      [][]    
 []                                                    []      []
-                                                          []
+                                                          []    
 []                                                        []  []
-                                              [][][]
-
-                                  []
+                                              [][][]            
+                                                                
+                                  []                            
                               [][][]                          []
 --------------------------------
 ''', ['2'], force_c=True)
@@ -4533,6 +4574,8 @@ def test_stdlibs(self):
         self.do_run(src, '*1*', force_c=True)
 
     def test_strtoll_hex(self):
+      if self.emcc_args is None: return self.skip('requires emcc')
+
       # tests strtoll for hex strings (0x...) 
       src = r'''
         #include <stdio.h>
@@ -4559,6 +4602,8 @@ def test_strtoll_hex(self):
       self.do_run(src, '111111')
 
     def test_strtoll_dec(self):
+      if self.emcc_args is None: return self.skip('requires emcc')
+
       # tests strtoll for decimal strings (0x...) 
       src = r'''
         #include <stdio.h>
@@ -4585,6 +4630,8 @@ def test_strtoll_dec(self):
       self.do_run(src, '111111')
 
     def test_strtoll_bin(self):
+      if self.emcc_args is None: return self.skip('requires emcc')
+
       # tests strtoll for binary strings (0x...) 
       src = r'''
         #include <stdio.h>
@@ -4606,6 +4653,8 @@ def test_strtoll_bin(self):
       self.do_run(src, '111')
 
     def test_strtoll_oct(self):
+      if self.emcc_args is None: return self.skip('requires emcc')
+
       # tests strtoll for decimal strings (0x...) 
       src = r'''
         #include <stdio.h>
@@ -6210,13 +6259,13 @@ def test_sscanf(self):
           printf("|%s|\n", buffy);
 
           int numverts = -1;
-          printf("%d\n", sscanf(" numverts 1499\n", " numverts %d", &numverts)); // white space is the same, even if tab vs space
+          printf("%d\n", sscanf("	numverts 1499\n", " numverts %d", &numverts)); // white space is the same, even if tab vs space
           printf("%d\n", numverts);
 
           int index;
           float u, v;
           short start, count;
-          printf("%d\n", sscanf(" vert 87 ( 0.481565 0.059481 ) 0 1\n", " vert %d ( %f %f ) %hu %hu", &index, &u, &v, &start, &count));
+          printf("%d\n", sscanf("	vert 87 ( 0.481565 0.059481 ) 0 1\n", " vert %d ( %f %f ) %hu %hu", &index, &u, &v, &start, &count));
           printf("%d,%.6f,%.6f,%hu,%hu\n", index, u, v, start, count);
 
           int neg, neg2, neg3 = 0;
@@ -6459,6 +6508,19 @@ def test_sscanf_skip(self):
       '''
       self.do_run(src, '1\n30\n2\n1000000,-123\n')
 
+    def test_sscanf_caps(self):
+      src = r'''
+        #include "stdio.h"
+
+        int main(){
+          unsigned int a;
+          float e, f, g;
+          sscanf("a 1.1 1.1 1.1", "%X %E %F %G", &a, &e, &f, &g);
+          printf("%d %.1F %.1F %.1F\n", a, e, f, g);
+        }
+      '''
+      self.do_run(src, '10 1.1 1.1 1.1');
+
     def test_langinfo(self):
       src = open(path_from_root('tests', 'langinfo', 'test.c'), 'r').read()
       expected = open(path_from_root('tests', 'langinfo', 'output.txt'), 'r').read()
@@ -6824,7 +6886,7 @@ def test_statvfs(self):
           printf("f_blocks: %lu\n", s.f_blocks);
           printf("f_bfree: %lu\n", s.f_bfree);
           printf("f_bavail: %lu\n", s.f_bavail);
-          printf("f_files: %lu\n", s.f_files);
+          printf("f_files: %d\n", s.f_files > 5);
           printf("f_ffree: %lu\n", s.f_ffree);
           printf("f_favail: %lu\n", s.f_favail);
           printf("f_fsid: %lu\n", s.f_fsid);
@@ -6842,7 +6904,7 @@ def test_statvfs(self):
         f_blocks: 1000000
         f_bfree: 500000
         f_bavail: 500000
-        f_files: 10
+        f_files: 1
         f_ffree: 1000000
         f_favail: 1000000
         f_fsid: 42
@@ -10307,6 +10369,8 @@ def test_l_link(self):
       assert not os.path.exists('a.out') and not os.path.exists('a.exe'), 'Must not leave unneeded linker stubs'
 
     def test_symlink(self):
+      if os.name == 'nt':
+        return self.skip('Windows FS does not need to be tested for symlinks support, since it does not have them.')
       open(os.path.join(self.get_dir(), 'foobar.xxx'), 'w').write('int main(){ return 0; }')
       os.symlink(os.path.join(self.get_dir(), 'foobar.xxx'), os.path.join(self.get_dir(), 'foobar.c'))
       Popen([PYTHON, EMCC, os.path.join(self.get_dir(), 'foobar.c'), '-o', os.path.join(self.get_dir(), 'foobar')], stdout=PIPE, stderr=PIPE).communicate()
@@ -11132,7 +11196,7 @@ def test_llvm_nativizer(self):
       output = Popen([os.path.join(self.get_dir(), 'files.o.run')], stdin=open(os.path.join(self.get_dir(), 'stdin')), stdout=PIPE, stderr=PIPE).communicate()
       self.assertContained('''size: 37
 data: 119,97,107,97,32,119,97,107,97,35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,35,35
-loop: 119 97 107 97 32 119 97 107 97 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35
+loop: 119 97 107 97 32 119 97 107 97 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 
 input:inter-active
 texto
 $
@@ -12868,20 +12932,22 @@ def relay_server(q):
 
     def test_websockets_bi(self):
       for datagram in [0,1]:
-        try:
-          with self.WebsockHarness(8992, self.make_relay_server(8992, 8994)):
-            with self.WebsockHarness(8994, no_server=True):
-              Popen([PYTHON, EMCC, path_from_root('tests', 'websockets_bi_side.c'), '-o', 'side.html', '-DSOCKK=8995', '-DTEST_DGRAM=%d' % datagram]).communicate()
-              self.btest('websockets_bi.c', expected='2499', args=['-DTEST_DGRAM=%d' % datagram])
-        finally:
-          self.clean_pids()
+        for fileops in [0,1]:
+          try:
+            print >> sys.stderr, 'test_websocket_bi datagram %d, fileops %d' % (datagram, fileops)
+            with self.WebsockHarness(8992, self.make_relay_server(8992, 8994)):
+              with self.WebsockHarness(8994, no_server=True):
+                Popen([PYTHON, EMCC, path_from_root('tests', 'websockets_bi_side.c'), '-o', 'side.html', '-DSOCKK=8995', '-DTEST_DGRAM=%d' % datagram]).communicate()
+                self.btest('websockets_bi.c', expected='2499', args=['-DSOCKK=8993', '-DTEST_DGRAM=%d' % datagram, '-DTEST_FILE_OPS=%s' % fileops])
+          finally:
+            self.clean_pids()
 
     def test_websockets_bi_listen(self):
       try:
         with self.WebsockHarness(6992, self.make_relay_server(6992, 6994)):
           with self.WebsockHarness(6994, no_server=True):
             Popen([PYTHON, EMCC, path_from_root('tests', 'websockets_bi_side.c'), '-o', 'side.html', '-DSOCKK=6995']).communicate()
-            self.btest('websockets_bi_listener.c', expected='2499')
+            self.btest('websockets_bi_listener.c', expected='2499', args=['-DSOCKK=6993'])
       finally:
         self.clean_pids()
 
@@ -12897,7 +12963,7 @@ def test_websockets_bi_bigdata(self):
         with self.WebsockHarness(3992, self.make_relay_server(3992, 3994)):
           with self.WebsockHarness(3994, no_server=True):
             Popen([PYTHON, EMCC, path_from_root('tests', 'websockets_bi_side_bigdata.c'), '-o', 'side.html', '-DSOCKK=3995', '-s', 'SOCKET_DEBUG=0', '-I' + path_from_root('tests')]).communicate()
-            self.btest('websockets_bi_bigdata.c', expected='0', args=['-s', 'SOCKET_DEBUG=0', '-I' + path_from_root('tests')])
+            self.btest('websockets_bi_bigdata.c', expected='0', args=['-DSOCKK=3993', '-s', 'SOCKET_DEBUG=0', '-I' + path_from_root('tests')])
       finally:
         self.clean_pids()
 
diff --git a/tests/unistd/login.out b/tests/unistd/login.out
index c1919c3cc747c..50e850ae80991 100644
--- a/tests/unistd/login.out
+++ b/tests/unistd/login.out
@@ -3,7 +3,7 @@ errno: 0
 
 gethostname/2 ret: -1
 gethostname/2: em------------------------
-errno: 36
+errno: 91
 
 gethostname/256 ret: 0
 gethostname/256: emscripten
diff --git a/tests/unistd/misc.out b/tests/unistd/misc.out
index a872a2581fbb6..4397195594e98 100644
--- a/tests/unistd/misc.out
+++ b/tests/unistd/misc.out
@@ -11,8 +11,8 @@ lockf(good): 0, errno: 0
 lockf(bad): -1, errno: 9
 nice: 0, errno: 1
 pause: -1, errno: 4
-pipe(good): -1, errno: 38
-pipe(bad): -1, errno: 38
+pipe(good): -1, errno: 88
+pipe(bad): -1, errno: 88
 execl: -1, errno: 8
 execle: -1, errno: 8
 execlp: -1, errno: 8
@@ -29,8 +29,8 @@ alarm: 0, errno: 0
 ualarm: 0, errno: 0
 fork: -1, errno: 11
 vfork: -1, errno: 11
-crypt: (null), errno: 38
-encrypt, errno: 38
+crypt: (null), errno: 88
+encrypt, errno: 88
 getgid: 0, errno: 0
 getegid: 0, errno: 0
 getuid: 0, errno: 0
diff --git a/tests/unistd/unlink.out b/tests/unistd/unlink.out
index 1dcec761edce2..f7a894cb50663 100644
--- a/tests/unistd/unlink.out
+++ b/tests/unistd/unlink.out
@@ -37,6 +37,6 @@ access(/empty-forbidden) after rmdir: 0
 access(/full) before: 0
 errno: 21
 access(/full) after unlink: 0
-errno: 39
+errno: 90
 access(/full) after rmdir: 0
 
diff --git a/tests/websockets_bi.c b/tests/websockets_bi.c
index 18cdd66428786..fb60177b56f9f 100644
--- a/tests/websockets_bi.c
+++ b/tests/websockets_bi.c
@@ -14,6 +14,10 @@
 
 #define EXPECTED_BYTES 28
 
+#ifndef SOCKK
+#define SOCKK 8992
+#endif
+
 int SocketFD;
 
 unsigned int get_all_buf(int sock, char* output, unsigned int maxsize)
@@ -25,7 +29,11 @@ unsigned int get_all_buf(int sock, char* output, unsigned int maxsize)
   char buffer[1024];
   int n;
   unsigned int offset = 0;
+#if TEST_FILE_OPS
+  while((errno = 0, (n = read(sock, buffer, sizeof(buffer)))>0) ||
+#else
   while((errno = 0, (n = recv(sock, buffer, sizeof(buffer), 0))>0) ||
+#endif
     errno == EINTR) {
     if(n>0)
     {
@@ -96,13 +104,7 @@ int main(void)
   memset(&stSockAddr, 0, sizeof(stSockAddr));
 
   stSockAddr.sin_family = AF_INET;
-  stSockAddr.sin_port = htons(
-#if EMSCRIPTEN
-    8993
-#else
-    8992
-#endif
-  );
+  stSockAddr.sin_port = htons(SOCKK);
   Res = inet_pton(AF_INET, "127.0.0.1", &stSockAddr.sin_addr);
 
   if (0 > Res) {
diff --git a/tests/websockets_bi_side.c b/tests/websockets_bi_side.c
index 12b790fd89770..1d557ed80a5f5 100644
--- a/tests/websockets_bi_side.c
+++ b/tests/websockets_bi_side.c
@@ -54,10 +54,17 @@ int main(void)
     exit(EXIT_FAILURE);
   }
 
+#if TEST_FILE_OPS
+  printf("write..\n");
+
+  char data[] = "hello from the other siide (fileops)\n";
+  write(SocketFD, data, sizeof(data));
+#else
   printf("send..\n");
 
   char data[] = "hello from the other siide\n";
   send(SocketFD, data, sizeof(data), 0);
+#endif
 
   printf("stall..\n");
 
diff --git a/tools/shared.py b/tools/shared.py
index 799a5bbe9defb..4225be264e6fb 100644
--- a/tools/shared.py
+++ b/tools/shared.py
@@ -295,7 +295,7 @@ def check_node_version():
 # we re-check sanity when the settings are changed)
 # We also re-check sanity and clear the cache when the version changes
 
-EMSCRIPTEN_VERSION = '1.4.7'
+EMSCRIPTEN_VERSION = '1.4.8'
 
 def generate_sanity():
   return EMSCRIPTEN_VERSION + '|' + get_llvm_target()