aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlan Kligman <ack@mozilla.com>2013-04-16 12:45:03 -0400
committerAlan Kligman <ack@mozilla.com>2013-05-23 15:30:25 -0400
commit4fb9a415268e59744afa683b84268c56e86cca8b (patch)
tree47266d0ba2fb0ad537d6d419be813ebd0447cd74
parent780fee9f3e1f3d6e461be33bd452ed8ecb1d497c (diff)
Add support for webrtc-based sockets. Moved both backends behind a settings flag, SOCKET_WEBRTC.
-rw-r--r--src/library.js579
-rw-r--r--src/library_browser.js29
-rw-r--r--src/settings.js3
-rw-r--r--src/socket.io.js3870
-rw-r--r--src/wrtcp.js855
5 files changed, 5210 insertions, 126 deletions
diff --git a/src/library.js b/src/library.js
index d897556f..285d1359 100644
--- a/src/library.js
+++ b/src/library.js
@@ -326,24 +326,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 +369,7 @@ LibraryManager.library = {
this._chunkSize = chunkSize;
this.lengthKnown = true;
}
-
+
var lazyArray = new LazyUint8Array();
Object.defineProperty(lazyArray, "length", {
get: function() {
@@ -4467,7 +4468,7 @@ LibraryManager.library = {
}
return pdest|0;
},
-
+
strlwr__deps:['tolower'],
strlwr: function(pstr){
var i = 0;
@@ -4478,7 +4479,7 @@ LibraryManager.library = {
i++;
}
},
-
+
strupr__deps:['toupper'],
strupr: function(pstr){
var i = 0;
@@ -4660,7 +4661,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') }}};
@@ -7070,9 +7071,11 @@ LibraryManager.library = {
['i32', 'h_length'],
['i8**', 'h_addr_list'],
]),
+
gethostbyname__deps: ['__hostent_struct_layout'],
gethostbyname: function(name) {
name = Pointer_stringify(name);
+ console.log("gethostbyname: ", name);
if (!_gethostbyname.id) {
_gethostbyname.id = 1;
_gethostbyname.table = {};
@@ -7112,17 +7115,24 @@ LibraryManager.library = {
// sockets. Note that the implementation assumes all sockets are always
// nonblocking
// ==========================================================================
-
- $Sockets__deps: ['__setErrNo', '$ERRNO_CODES'],
+ $Sockets__deps: ['__setErrNo', '$ERRNO_CODES',
+ function() { return 'io = ' + read('socket.io.js') + ';\n' },
+ function() { return 'Peer = ' + read('wrtcp.js') + ';\n' }],
$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,
+ 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'],
@@ -7139,114 +7149,365 @@ LibraryManager.library = {
['i32', 'msg_controllen'],
['i32', 'msg_flags'],
]),
+ },
- 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';
+#if SOCKET_WEBRTC
+ 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
+ }
- 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;
+ // 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];
+
+ 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.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']['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("received " + message.byteLength + " raw bytes");
+#endif
+ 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;
+ }
+
+ var INCOMING_QUEUE_LENGTH = 64;
+
+ 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;
+ }
+ };
+ };
+
+ Sockets.fds[fd] = {
+ addr: null,
+ port: null,
+ inQueue: new CircularBuffer(INCOMING_QUEUE_LENGTH),
+ header: new Uint16Array(2),
+ bound: false
+ };
+ 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() {
+
+ },
+
+ bind__deps: ['$Sockets', '_inet_ntop_raw', 'ntohs', 'mkport'],
+ bind: function(fd, addr, addrlen) {
+ var info = Sockets.fds[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 = Sockets.fds[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;
+ }
- 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
+ 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', data.length, '|', Array.prototype.slice.call(data)]);
+ Module.print('sendmsg vecs: ' + num);
#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 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(['onmessage message', currLen, '|', Array.prototype.slice.call(data.subarray(currPos, currPos+currLen))]);
+ Module.print('sendmsg curr size: ' + currNum);
#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 (!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('sender actually sending ' + Array.prototype.slice.call(data));
+ Module.print('sendmsg port: ' + info.header[0] + ' -> ' + info.header[1]);
+ Module.print('sendmsg bytes: ' + data.length + ' | ' + 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);
- }
+ 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 = Sockets.fds[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 = Sockets.fds[fd];
+ if (!info) return -1;
+ info.close();
+ Sockets.fds[fd] = null;
+ },
+
+ ioctl: function(fd, request, varargs) {
+ var info = Sockets.fds[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 = Sockets.fds[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 = Sockets.fds[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++;
@@ -7255,9 +7516,6 @@ LibraryManager.library = {
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
- }
Sockets.fds[fd] = {
connected: false,
stream: stream
@@ -7282,12 +7540,104 @@ 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;
},
@@ -7483,12 +7833,12 @@ LibraryManager.library = {
// 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;
@@ -7498,8 +7848,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;
@@ -7544,6 +7894,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 2e6c9150..b81dc852 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) {
@@ -383,6 +384,12 @@ mergeInto(LibraryManager.library, {
setInterval(function() {
if (!ABORT) func();
}, timeout);
+ getUserMedia: function(func) {
+ if(!window.getUserMedia) {
+ window.getUserMedia = navigator['getUserMedia'] ||
+ navigator['mozGetUserMedia'];
+ }
+ window.getUserMedia(func);
},
getMovementX: function(event) {
@@ -499,7 +506,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 +516,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 +553,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 +567,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 +595,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/settings.js b/src/settings.js
index d3abb06e..b258ce04 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -174,6 +174,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 = 1; // Select socket backend, either webrtc or websockets.
var OPENAL_DEBUG = 0; // Print out debugging information from our OpenAL implementation.
@@ -193,7 +194,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/socket.io.js b/src/socket.io.js
new file mode 100644
index 00000000..3fe6a5a7
--- /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;