diff --git a/.zuul.yml b/.zuul.yml
index 566f6780..88b88d6e 100644
--- a/.zuul.yml
+++ b/.zuul.yml
@@ -3,11 +3,16 @@ server: ./test/support/server.js
browsers:
- name: chrome
version: 29..latest
+# Firefox disabled for now because it can cause infinite wait loops when
+# running any tests
# - name: firefox
# version: latest
- name: safari
version: latest
- name: ie
- version: 6..latest
+ version: 10
+ platform: Windows 2012
+ - name: ie
+ version: [6..9, latest]
- name: iphone
version: oldest..latest
diff --git a/Makefile b/Makefile
index 7c70beb5..664ca149 100644
--- a/Makefile
+++ b/Makefile
@@ -18,4 +18,4 @@ test-cov:
--reporter $(REPORTER) \
$(TESTS)
-.PHONY: test
+.PHONY: test build
diff --git a/README.md b/README.md
index 3b6d2725..af7fe2a0 100644
--- a/README.md
+++ b/README.md
@@ -56,6 +56,23 @@ browserify app.js > bundle.js
```
+### Sending and receiving binary
+
+```html
+
+
+```
+
### Node.JS
Add `engine.io-client` to your `package.json` and then:
@@ -78,6 +95,14 @@ socket.onopen = function(){
- Easy to debug
- Easy to unit test
- Runs inside HTML5 WebWorker
+- Can send and receive binary data
+ - Receives as ArrayBuffer or Blob when in browser, and Buffer or ArrayBuffer
+ in Node
+ - When XHR2 or WebSockets are used, binary is emitted directly. Otherwise
+ binary is encoded into base64 strings, and decoded when binary types are
+ supported.
+ - With browsers that don't support ArrayBuffer, an object { base64: true,
+ data: dataAsBase64String } is emitted in onmessage
## API
@@ -95,6 +120,9 @@ Exposed as `eio` in the browser standalone build.
- `message` event handler
- `onclose` (_Function_)
- `message` event handler
+- `binaryType` _(String)_ : can be set to 'arraybuffer' or 'blob' in browsers,
+ and `buffer` or `arraybuffer` in Node. Blob is only used in browser if it's
+ supported.
#### Events
@@ -103,7 +131,8 @@ Exposed as `eio` in the browser standalone build.
- `message`
- Fired when data is received from the server.
- **Arguments**
- - `String`: utf-8 encoded data
+ - `String` | `ArrayBuffer`: utf-8 encoded data or ArrayBuffer containing
+ binary data
- `close`
- Fired upon disconnection. In compliance with the WebSocket API spec, this event may be
fired even if the `open` event does not occur (i.e. due to connection error or `close()`).
@@ -130,6 +159,7 @@ Exposed as `eio` in the browser standalone build.
- `upgrade` (`Boolean`): defaults to true, whether the client should try
to upgrade the transport from long-polling to something better.
- `forceJSONP` (`Boolean`): forces JSONP for polling transport.
+ - `forceBase64` (`Boolean`): forces base 64 encoding for polling transport even when XHR2 responseType is available and WebSocket even if the used standard supports binary.
- `timestampRequests` (`Boolean`): whether to add the timestamp with
each transport request. Note: this is ignored if the browser is
IE or Android, in which case requests are always stamped (`false`)
@@ -150,7 +180,7 @@ Exposed as `eio` in the browser standalone build.
- `send`
- Sends a message to the server
- **Parameters**
- - `String`: data to send
+ - `String` | `ArrayBuffer` | `ArrayBufferView` | `Blob`: data to send
- `Function`: optional, callback upon `drain`
- `close`
- Disconnects the client.
diff --git a/lib/socket.js b/lib/socket.js
index 5b635b5a..4d019f1c 100644
--- a/lib/socket.js
+++ b/lib/socket.js
@@ -75,6 +75,7 @@ function Socket(uri, opts){
this.upgrade = false !== opts.upgrade;
this.path = (opts.path || '/engine.io').replace(/\/$/, '') + '/';
this.forceJSONP = !!opts.forceJSONP;
+ this.forceBase64 = !!opts.forceBase64;
this.timestampParam = opts.timestampParam || 't';
this.timestampRequests = opts.timestampRequests;
this.flashPath = opts.flashPath || '';
@@ -85,6 +86,8 @@ function Socket(uri, opts){
this.policyPort = opts.policyPort || 843;
this.rememberUpgrade = opts.rememberUpgrade || false;
this.open();
+ this.binaryType = null;
+ this.onlyBinaryUpgrades = opts.onlyBinaryUpgrades;
}
Socket.priorWebsocketSuccess = false;
@@ -144,10 +147,12 @@ Socket.prototype.createTransport = function (name) {
path: this.path,
query: query,
forceJSONP: this.forceJSONP,
+ forceBase64: this.forceBase64,
timestampRequests: this.timestampRequests,
timestampParam: this.timestampParam,
flashPath: this.flashPath,
- policyPort: this.policyPort
+ policyPort: this.policyPort,
+ socket: this
});
return transport;
@@ -231,6 +236,10 @@ Socket.prototype.probe = function (name) {
Socket.priorWebsocketSuccess = false;
transport.once('open', function () {
+ if (this.onlyBinaryUpgrades) {
+ var upgradeLosesBinary = !this.supportsBinary && self.transport.supportsBinary;
+ failed = failed || upgradeLosesBinary;
+ }
if (failed) return;
debug('probe transport "%s" opened', name);
@@ -366,6 +375,7 @@ Socket.prototype.onPacket = function (packet) {
event.toString = function () {
return packet.data;
};
+
this.onmessage && this.onmessage.call(this, event);
break;
}
@@ -444,7 +454,7 @@ Socket.prototype.ping = function () {
* @api private
*/
- Socket.prototype.onDrain = function() {
+Socket.prototype.onDrain = function() {
for (var i = 0; i < this.prevBufferLen; i++) {
if (this.callbackBuffer[i]) {
this.callbackBuffer[i]();
diff --git a/lib/transport.js b/lib/transport.js
index 9e7ea558..57af7f31 100644
--- a/lib/transport.js
+++ b/lib/transport.js
@@ -1,4 +1,3 @@
-
/**
* Module dependencies.
*/
@@ -30,6 +29,7 @@ function Transport (opts) {
this.timestampRequests = opts.timestampRequests;
this.readyState = '';
this.agent = opts.agent || false;
+ this.socket = opts.socket;
}
/**
@@ -119,7 +119,7 @@ Transport.prototype.onOpen = function () {
*/
Transport.prototype.onData = function (data) {
- this.onPacket(parser.decodePacket(data));
+ this.onPacket(parser.decodePacket(data, this.socket.binaryType));
};
/**
diff --git a/lib/transports/flashsocket.js b/lib/transports/flashsocket.js
index 67735f6a..aa31f9a3 100644
--- a/lib/transports/flashsocket.js
+++ b/lib/transports/flashsocket.js
@@ -51,6 +51,12 @@ util.inherits(FlashWS, WS);
FlashWS.prototype.name = 'flashsocket';
+/*
+ * FlashSockets only support binary as base64 encoded strings
+ */
+
+FlashWS.prototype.supportsBinary = false;
+
/**
* Opens the transport.
*
@@ -91,7 +97,7 @@ FlashWS.prototype.doOpen = function(){
load(deps, function(){
self.ready(function(){
WebSocket.__addTask(function () {
- self.socket = new WebSocket(self.uri());
+ self.ws = new WebSocket(self.uri());
self.addEventListeners();
});
});
@@ -105,7 +111,7 @@ FlashWS.prototype.doOpen = function(){
*/
FlashWS.prototype.doClose = function(){
- if (!this.socket) return;
+ if (!this.ws) return;
var self = this;
WebSocket.__addTask(function(){
WS.prototype.doClose.call(self);
diff --git a/lib/transports/polling-jsonp.js b/lib/transports/polling-jsonp.js
index 3ad0da74..b89f8c53 100644
--- a/lib/transports/polling-jsonp.js
+++ b/lib/transports/polling-jsonp.js
@@ -79,6 +79,12 @@ function JSONPPolling (opts) {
util.inherits(JSONPPolling, Polling);
+/*
+ * JSONP only supports binary as base64 encoded strings
+ */
+
+JSONPPolling.prototype.supportsBinary = false;
+
/**
* Closes the socket
*
diff --git a/lib/transports/polling-xhr.js b/lib/transports/polling-xhr.js
index 058cb6a8..f7953d6f 100755
--- a/lib/transports/polling-xhr.js
+++ b/lib/transports/polling-xhr.js
@@ -63,6 +63,12 @@ function XHR(opts){
util.inherits(XHR, Polling);
+/**
+ * XHR supports binary
+ */
+
+XHR.prototype.supportsBinary = true;
+
/**
* Creates a request.
*
@@ -75,6 +81,7 @@ XHR.prototype.request = function(opts){
opts.uri = this.uri();
opts.xd = this.xd;
opts.agent = this.agent || false;
+ opts.supportsBinary = this.supportsBinary;
return new Request(opts);
};
@@ -87,7 +94,8 @@ XHR.prototype.request = function(opts){
*/
XHR.prototype.doWrite = function(data, fn){
- var req = this.request({ method: 'POST', data: data });
+ var isBinary = typeof data !== 'string' && data !== undefined;
+ var req = this.request({ method: 'POST', data: data, isBinary: isBinary });
var self = this;
req.on('success', fn);
req.on('error', function(err){
@@ -129,7 +137,7 @@ function Request(opts){
this.async = false !== opts.async;
this.data = undefined != opts.data ? opts.data : null;
this.agent = opts.agent;
- this.create();
+ this.create(opts.isBinary, opts.supportsBinary);
}
/**
@@ -144,17 +152,26 @@ Emitter(Request.prototype);
* @api private
*/
-Request.prototype.create = function(){
+Request.prototype.create = function(isBinary, supportsBinary){
var xhr = this.xhr = new XMLHttpRequest({ agent: this.agent, xdomain: this.xd });
var self = this;
try {
debug('xhr open %s: %s', this.method, this.uri);
xhr.open(this.method, this.uri, this.async);
+ if (supportsBinary) {
+ // This has to be done after open because Firefox is stupid
+ // http://stackoverflow.com/questions/13216903/get-binary-data-with-xmlhttprequest-in-a-firefox-extension
+ xhr.responseType = 'arraybuffer';
+ }
if ('POST' == this.method) {
try {
- xhr.setRequestHeader('Content-type', 'text/plain;charset=UTF-8');
+ if (isBinary) {
+ xhr.setRequestHeader('Content-type', 'application/octet-stream');
+ } else {
+ xhr.setRequestHeader('Content-type', 'text/plain;charset=UTF-8');
+ }
} catch (e) {}
}
@@ -169,7 +186,16 @@ Request.prototype.create = function(){
try {
if (4 != xhr.readyState) return;
if (200 == xhr.status || 1223 == xhr.status) {
- data = xhr.responseText;
+ var contentType = xhr.getResponseHeader('Content-Type');
+ if (contentType === 'application/octet-stream') {
+ data = xhr.response;
+ } else {
+ if (!supportsBinary) {
+ data = xhr.responseText;
+ } else {
+ data = 'ok';
+ }
+ }
} else {
// make sure the `error` event handler that's user-set
// does not throw in the same tick and gets caught here
@@ -189,7 +215,7 @@ Request.prototype.create = function(){
debug('xhr data %s', this.data);
xhr.send(this.data);
} catch (e) {
- // Need to defer since .create() is called directly from the constructor
+ // Need to defer since .create() is called directly fhrom the constructor
// and thus the 'error' event can only be only bound *after* this exception
// occurs. Therefore, also, we cannot throw here at all.
setTimeout(function() {
@@ -280,11 +306,13 @@ if (hasAttachEvent) {
Request.requestsCount = 0;
Request.requests = {};
- global.attachEvent('onunload', function(){
+ function unloadHandler() {
for (var i in Request.requests) {
if (Request.requests.hasOwnProperty(i)) {
Request.requests[i].abort();
}
}
- });
+ }
+
+ global.attachEvent('onunload', unloadHandler);
}
diff --git a/lib/transports/polling.js b/lib/transports/polling.js
index 79f4c7c7..0294e8f2 100644
--- a/lib/transports/polling.js
+++ b/lib/transports/polling.js
@@ -19,6 +19,16 @@ module.exports = Polling;
var global = require('global');
+/**
+ * Is XHR2 supported?
+ */
+
+var hasXHR2 = (function() {
+ var XMLHttpRequest = require('xmlhttprequest');
+ var xhr = new XMLHttpRequest({ agent: this.agent, xdomain: false });
+ return null != xhr.responseType;
+})();
+
/**
* Polling interface.
*
@@ -27,6 +37,10 @@ var global = require('global');
*/
function Polling(opts){
+ var forceBase64 = (opts && opts.forceBase64);
+ if (!hasXHR2 || forceBase64) {
+ this.supportsBinary = false;
+ }
Transport.call(this, opts);
}
@@ -119,9 +133,7 @@ Polling.prototype.poll = function(){
Polling.prototype.onData = function(data){
var self = this;
debug('polling got data %s', data);
-
- // decode payload
- parser.decodePayload(data, function(packet, index, total) {
+ var callback = function(packet, index, total) {
// if its the first message we consider the transport open
if ('opening' == self.readyState) {
self.onOpen();
@@ -135,7 +147,10 @@ Polling.prototype.onData = function(data){
// otherwise bypass onData and handle the message
self.onPacket(packet);
- });
+ };
+
+ // decode payload
+ parser.decodePayload(data, this.socket.binaryType, callback);
// if an event did not trigger closing
if ('closed' != this.readyState) {
@@ -187,9 +202,14 @@ Polling.prototype.doClose = function(){
Polling.prototype.write = function(packets){
var self = this;
this.writable = false;
- this.doWrite(parser.encodePayload(packets), function(){
+ var callbackfn = function() {
self.writable = true;
self.emit('drain');
+ };
+
+ var self = this;
+ parser.encodePayload(packets, this.supportsBinary, function(data) {
+ self.doWrite(data, callbackfn);
});
};
@@ -215,6 +235,10 @@ Polling.prototype.uri = function(){
}
}
+ if (!this.supportsBinary && !query.sid) {
+ query.b64 = 1;
+ }
+
query = util.qs(query);
// avoid port if default for schema
diff --git a/lib/transports/websocket.js b/lib/transports/websocket.js
index a5f0102c..30bf1f2f 100644
--- a/lib/transports/websocket.js
+++ b/lib/transports/websocket.js
@@ -35,6 +35,10 @@ var global = require('global');
*/
function WS(opts){
+ var forceBase64 = (opts && opts.forceBase64);
+ if (forceBase64) {
+ this.supportsBinary = false;
+ }
Transport.call(this, opts);
}
@@ -52,6 +56,12 @@ util.inherits(WS, Transport);
WS.prototype.name = 'websocket';
+/*
+ * WebSockets support binary
+ */
+
+WS.prototype.supportsBinary = true;
+
/**
* Opens socket.
*
@@ -69,7 +79,13 @@ WS.prototype.doOpen = function(){
var protocols = void(0);
var opts = { agent: this.agent };
- this.socket = new WebSocket(uri, protocols, opts);
+ this.ws = new WebSocket(uri, protocols, opts);
+
+ if (this.ws.binaryType !== undefined) {
+ this.supportsBinary = false;
+ }
+
+ this.ws.binaryType = 'arraybuffer';
this.addEventListeners();
};
@@ -82,16 +98,16 @@ WS.prototype.doOpen = function(){
WS.prototype.addEventListeners = function(){
var self = this;
- this.socket.onopen = function(){
+ this.ws.onopen = function(){
self.onOpen();
};
- this.socket.onclose = function(){
+ this.ws.onclose = function(){
self.onClose();
};
- this.socket.onmessage = function(ev){
+ this.ws.onmessage = function(ev){
self.onData(ev.data);
};
- this.socket.onerror = function(e){
+ this.ws.onerror = function(e){
self.onError('websocket error', e);
};
};
@@ -126,8 +142,11 @@ WS.prototype.write = function(packets){
// encodePacket efficient as it uses WS framing
// no need for encodePayload
for (var i = 0, l = packets.length; i < l; i++) {
- this.socket.send(parser.encodePacket(packets[i]));
+ parser.encodePacket(packets[i], this.supportsBinary, function(data) {
+ self.ws.send(data);
+ });
}
+
function ondrain() {
self.writable = true;
self.emit('drain');
@@ -154,8 +173,8 @@ WS.prototype.onClose = function(){
*/
WS.prototype.doClose = function(){
- if (typeof this.socket !== 'undefined') {
- this.socket.close();
+ if (typeof this.ws !== 'undefined') {
+ this.ws.close();
}
};
@@ -181,6 +200,11 @@ WS.prototype.uri = function(){
query[this.timestampParam] = +new Date;
}
+ // communicate binary support capabilities
+ if (!this.supportsBinary) {
+ query.b64 = 1;
+ }
+
query = util.qs(query);
// prepend ? to query
diff --git a/package.json b/package.json
index 1e682826..4a10a5ff 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,7 @@
"debug": "0.7.4"
},
"devDependencies": {
- "zuul": "1.5.2",
+ "zuul": "1.5.4",
"mocha": "1.16.2",
"expect.js": "0.2.0",
"istanbul": "0.2.3",
diff --git a/test/arraybuffer/index.js b/test/arraybuffer/index.js
new file mode 100644
index 00000000..0dabe36e
--- /dev/null
+++ b/test/arraybuffer/index.js
@@ -0,0 +1,8 @@
+var wsSupport = require('has-cors');
+
+require('./polling.js');
+var uagent = navigator.userAgent;
+var isOldSimulator = ~uagent.indexOf('iPhone OS 4') || ~uagent.indexOf('iPhone OS 5');
+if (wsSupport && !isOldSimulator) {
+ require ('./ws.js');
+}
diff --git a/test/arraybuffer/polling.js b/test/arraybuffer/polling.js
new file mode 100644
index 00000000..4ed706b3
--- /dev/null
+++ b/test/arraybuffer/polling.js
@@ -0,0 +1,45 @@
+var expect = require('expect.js');
+var eio = require('../../');
+
+describe('arraybuffer', function() {
+ this.timeout(30000);
+
+ it('should be able to receive binary data when bouncing it back (polling)', function(done) {
+ var binaryData = new Int8Array(5);
+ for (var i = 0; i < 5; i++) {
+ binaryData[i] = i;
+ }
+ var socket = new eio.Socket({ transports: ['polling'] });
+ socket.on('open', function() {
+ socket.send(binaryData);
+ socket.on('message', function (data) {
+ if (data === 'hi') return;
+
+ expect(data).to.be.an(ArrayBuffer);
+ expect(new Int8Array(data)).to.eql(binaryData);
+ socket.close();
+ done();
+ });
+ });
+ });
+
+ it('should be able to receive binary data when forcing base64 (polling)', function(done) {
+ var binaryData = new Int8Array(5);
+ for (var i = 0; i < 5; i++) {
+ binaryData[i] = i;
+ }
+ var socket = new eio.Socket({ forceBase64: true });
+ socket.on('open', function() {
+ socket.send(binaryData);
+ socket.on('message', function (data) {
+ if (typeof data === 'string') return;
+
+ expect(data).to.be.an(ArrayBuffer);
+ var ia = new Int8Array(data);
+ expect(ia).to.eql(binaryData);
+ socket.close();
+ done();
+ });
+ });
+ });
+});
diff --git a/test/arraybuffer/ws.js b/test/arraybuffer/ws.js
new file mode 100644
index 00000000..26d03377
--- /dev/null
+++ b/test/arraybuffer/ws.js
@@ -0,0 +1,50 @@
+var expect = require('expect.js');
+var eio = require('../../');
+
+describe('arraybuffer', function() {
+ this.timeout(30000);
+
+ it('should be able to receive binary data when bouncing it back (ws)', function(done) {
+ var binaryData = new Int8Array(5);
+ for (var i = 0; i < 5; i++) {
+ binaryData[i] = i;
+ }
+ var socket = new eio.Socket();
+ socket.on('open', function() {
+ socket.on('upgrade', function() {
+ socket.send(binaryData);
+ socket.on('message', function (data) {
+ if (typeof data === 'string') return;
+
+ expect(data).to.be.an(ArrayBuffer);
+ expect(new Int8Array(data)).to.eql(binaryData);
+
+ socket.close();
+ done();
+ });
+ });
+ });
+ });
+
+ it('should be able to receive binary data when bouncing it back and forcing base64 (ws)', function(done) {
+ var binaryData = new Int8Array(5);
+ for (var i = 0; i < 5; i++) {
+ binaryData[i] = i;
+ }
+ var socket = new eio.Socket({ forceBase64: true });
+ socket.on('open', function() {
+ socket.on('upgrade', function() {
+ socket.send(binaryData);
+ socket.on('message', function (data) {
+ if (typeof data === 'string') return;
+
+ expect(data).to.be.an(ArrayBuffer);
+ expect(new Int8Array(data)).to.eql(binaryData);
+
+ socket.close();
+ done();
+ });
+ });
+ });
+ });
+});
diff --git a/test/binary-fallback.js b/test/binary-fallback.js
new file mode 100644
index 00000000..27de8a4b
--- /dev/null
+++ b/test/binary-fallback.js
@@ -0,0 +1,26 @@
+var expect = require('expect.js');
+var eio = require('../');
+
+describe('binary fallback', function() {
+ this.timeout(10000);
+
+ it('should be able to receive binary data when ArrayBuffer not available (polling)', function(done) {
+ var socket = new eio.Socket({ forceBase64: true });
+ socket.on('open', function() {
+ socket.send('give binary');
+ var firstPacket = true;
+ socket.on('message', function (data) {
+ if (firstPacket) {
+ firstPacket = false;
+ return;
+ }
+
+ expect(data.base64).to.be(true);
+ expect(data.data).to.equal('AAECAwQ=');
+
+ socket.close();
+ done();
+ });
+ });
+ });
+});
diff --git a/test/blob/index.js b/test/blob/index.js
new file mode 100644
index 00000000..f433cd54
--- /dev/null
+++ b/test/blob/index.js
@@ -0,0 +1,8 @@
+var wsSupport = require('has-cors');
+
+require('./polling.js');
+var uagent = navigator.userAgent;
+var isOldSimulator = ~uagent.indexOf('iPhone OS 4') || ~uagent.indexOf('iPhone OS 5');
+if (wsSupport && !isOldSimulator) {
+ require('./ws.js');
+}
diff --git a/test/blob/polling.js b/test/blob/polling.js
new file mode 100644
index 00000000..8098591b
--- /dev/null
+++ b/test/blob/polling.js
@@ -0,0 +1,66 @@
+var expect = require('expect.js');
+var eio = require('../../');
+
+var blobSupported = (function() {
+ try {
+ var b = new Blob(['hi']);
+ return b.size == 2;
+ } catch(e) {
+ return false;
+ }
+})();
+
+describe('blob', function() {
+ this.timeout(30000);
+
+ it('should be able to receive binary data as blob when bouncing it back (polling)', function(done) {
+ var binaryData = new Int8Array(5);
+ for (var i = 0; i < 5; i++) {
+ binaryData[i] = i;
+ }
+ var socket = new eio.Socket();
+ socket.binaryType = 'blob';
+ socket.on('open', function() {
+ socket.send(binaryData);
+ socket.on('message', function (data) {
+ if (typeof data === 'string') return;
+
+ expect(data).to.be.a(Blob);
+ var fr = new FileReader();
+ fr.onload = function() {
+ var ab = this.result;
+ var ia = new Int8Array(ab);
+ expect(ia).to.eql(binaryData);
+ socket.close();
+ done();
+ };
+ fr.readAsArrayBuffer(data);
+ });
+ });
+ });
+
+ it('should be able to send data as a blob when bouncing it back (polling)', function(done) {
+ var binaryData = new Int8Array(5);
+ for (var i = 0; i < 5; i++) {
+ binaryData[i] = i;
+ }
+ var socket = new eio.Socket();
+ socket.on('open', function() {
+ if (blobSupported) {
+ socket.send(new Blob([binaryData.buffer]));
+ } else {
+ var bb = new BlobBuilder();
+ bb.append(binaryData.buffer);
+ socket.send(bb.getBlob());
+ }
+ socket.on('message', function (data) {
+ if (typeof data == 'string') { return; }
+
+ expect(data).to.be.an(ArrayBuffer);
+ expect(new Int8Array(data)).to.eql(binaryData);
+ socket.close();
+ done();
+ });
+ });
+ });
+});
diff --git a/test/blob/ws.js b/test/blob/ws.js
new file mode 100644
index 00000000..e91510c5
--- /dev/null
+++ b/test/blob/ws.js
@@ -0,0 +1,91 @@
+var expect = require('expect.js');
+var eio = require('../../');
+
+var blobSupported = (function() {
+ try {
+ var b = new Blob(['hi']);
+ return b.size == 2;
+ } catch(e) {
+ return false;
+ }
+})();
+
+describe('blob', function() {
+ this.timeout(30000);
+
+ it('should be able to receive binary data as blob when bouncing it back (ws)', function(done) {
+ var binaryData = new Int8Array(5);
+ for (var i = 0; i < 5; i++) {
+ binaryData[i] = i;
+ }
+ var socket = new eio.Socket();
+ socket.binaryType = 'blob';
+ socket.on('open', function() {
+ socket.on('upgrade', function() {
+ socket.send(binaryData);
+ socket.on('message', function (data) {
+ expect(data).to.be.a(Blob);
+ var fr = new FileReader();
+ fr.onload = function() {
+ var ab = this.result;
+ var ia = new Int8Array(ab);
+ expect(ia).to.eql(binaryData);
+ socket.close();
+ done();
+ };
+ fr.readAsArrayBuffer(data);
+ });
+ });
+ });
+ });
+
+ it('should be able to send data as a blob when bouncing it back (ws)', function(done) {
+ var binaryData = new Int8Array(5);
+ for (var i = 0; i < 5; i++) {
+ binaryData[i] = i;
+ }
+ var socket = new eio.Socket();
+ socket.on('open', function() {
+ socket.on('upgrade', function() {
+ if (blobSupported) {
+ socket.send(new Blob([binaryData.buffer]));
+ } else {
+ var bb = new BlobBuilder();
+ bb.append(binaryData.buffer);
+ socket.send(bb.getBlob());
+ }
+ socket.on('message', function (data) {
+ expect(data).to.be.an(ArrayBuffer);
+ expect(new Int8Array(data)).to.eql(binaryData);
+ socket.close();
+ done();
+ });
+ });
+ });
+ });
+
+ it('should be able to send data as a blob encoded into base64 when bouncing it back (ws)', function(done) {
+ var binaryData = new Int8Array(5);
+ for (var i = 0; i < 5; i++) {
+ binaryData[i] = i;
+ }
+ var socket = new eio.Socket({ forceBase64: true });
+ socket.on('open', function() {
+ socket.on('upgrade', function() {
+ if (blobSupported) {
+ socket.send(new Blob([binaryData.buffer]));
+ } else {
+ var bb = new BlobBuilder();
+ bb.append(binaryData.buffer);
+ socket.send(bb.getBlob());
+ }
+ socket.on('message', function (data) {
+ expect(data).to.be.an(ArrayBuffer);
+ expect(new Int8Array(data)).to.eql(binaryData);
+ socket.close();
+ done();
+ });
+ });
+ });
+ });
+});
diff --git a/test/browser-only-parser.js b/test/browser-only-parser.js
new file mode 100644
index 00000000..37972eb9
--- /dev/null
+++ b/test/browser-only-parser.js
@@ -0,0 +1,152 @@
+
+/**
+ * Test dependencies.
+ */
+
+var expect = require('expect.js');
+var eio = require('../');
+
+var parser = eio.parser
+
+/**
+ * Shortcuts
+ */
+
+var encode = parser.encodePacket;
+var decode = parser.decodePacket;
+var encPayload = parser.encodePayload;
+var decPayload = parser.decodePayload;
+var encPayloadB = parser.encodePayloadAsArrayBuffer;
+var encPayloadBB = parser.encodePayloadAsBlob;
+var decPayloadB = parser.decodePayloadAsBinary;
+
+var canUseBlobs = (function() {
+ try {
+ new Blob(["hi"]);
+ return true;
+ } catch(e) {
+ return !!global.BlobBuilder;
+ }
+})();
+
+/**
+ * Tests.
+ */
+
+describe('browser-only-parser', function () {
+ it('should encode a binary message', function(done) {
+ var data = new Int8Array(5);
+ for (var i = 0; i < data.length; i++) data[i] = i;
+ encode({ type: 'message', data: data }, function (encoded) {
+ expect(decode(encoded)).to.eql({ type: 'message', data: data.buffer });
+ done();
+ });
+ });
+
+ it('should encode/decode mixed binary and string contents as b64', function(done) {
+ var data = new Int8Array(5);
+ for (var i = 0; i < data.length; i++) data[i] = i;
+ encPayload([{ type: 'message', data: data }, { type: 'message', data: 'hello' }], function(encoded) {
+ decPayload(encoded, function(packet, index, total) {
+ var isLast = index + 1 == total;
+ expect(packet.type).to.eql('message');
+ if (!isLast) {
+ expect(new Int8Array(packet.data)).to.eql(data);
+ } else {
+ expect(packet.data).to.eql('hello');
+ done();
+ }
+ });
+ });
+ });
+
+ it('should encode binary contents as binary (ArrayBuffer)', function(done) {
+ var first = new Int8Array(5);
+ for (var i = 0; i < first.length; i++) first[i] = i;
+ var second = new Int8Array(4);
+ for (var i = 0; i < second.length; i++) second[i] = first.length + i;
+
+ encPayloadB([{ type: 'message', data: first }, { type: 'message', data: second }], function(data) {
+ decPayloadB(data, function(packet, index, total) {
+ var isLast = index + 1 == total;
+ expect(packet.type).to.eql('message');
+ if (!isLast) {
+ expect(new Int8Array(packet.data)).to.eql(first);
+ } else {
+ expect(new Int8Array(packet.data)).to.eql(second);
+ done();
+ }
+ });
+ });
+ });
+
+ it('should encode mixed binary and string contents as binary (ArrayBuffer)', function(done) {
+ var first = new Int8Array(15);
+ for (var i = 0; i < first.length; i++) first[i] = i;
+
+ encPayloadB([ { type: 'message', data: first }, { type: 'message', data: 'hello' }, { type: 'close' } ], function(data) {
+ decPayloadB(data, function(packet, index, total) {
+ if (index == 0) {
+ expect(packet.type).to.eql('message');
+ expect(new Int8Array(packet.data)).to.eql(first);
+ } else if (index == 1) {
+ expect(packet.type).to.eql('message');
+ expect(packet.data).to.eql('hello');
+ } else {
+ expect(packet.type).to.eql('close');
+ done();
+ }
+ });
+ });
+ });
+
+ if (canUseBlobs) {
+ it('should encode binary contents as binary (Blob)', function(done) {
+ var first = new Int8Array(5);
+ for (var i = 0; i < first.length; i++) first[i] = i;
+ var second = new Int8Array(4);
+ for (var i = 0; i < second.length; i++) second[i] = first.length + i;
+
+ encPayloadBB([{ type: 'message', data: first }, { type: 'message', data: second }], function(data) {
+ var fr = new FileReader();
+ fr.onload = function() {
+ decPayloadB(this.result, function(packet, index, total) {
+ var isLast = index + 1 == total;
+ expect(packet.type).to.eql('message');
+ if (!isLast) {
+ expect(new Int8Array(packet.data)).to.eql(first);
+ } else {
+ expect(new Int8Array(packet.data)).to.eql(second);
+ done();
+ }
+ });
+ };
+ fr.readAsArrayBuffer(data);
+ });
+ });
+
+ it('should encode mixed binary and string contents as binary (Blob)', function(done) {
+ var first = new Int8Array(5);
+ for (var i = 0; i < first.length; i++) first[i] = i;
+
+ encPayloadBB([ { type: 'message', data: first }, { type: 'message', data: 'hello' }, { type: 'close' } ], function(data) {
+ var fr = new FileReader();
+ fr.onload = function() {
+ decPayloadB(this.result, function(packet, index, total) {
+ if (index == 0) {
+ expect(packet.type).to.eql('message');
+ expect(new Int8Array(packet.data)).to.eql(first);
+ } else if (index == 1) {
+ expect(packet.type).to.eql('message');
+ expect(packet.data).to.eql('hello');
+ } else {
+ expect(packet.type).to.eql('close');
+ done();
+ }
+ });
+ };
+ fr.readAsArrayBuffer(data);
+ });
+ });
+ }
+});
diff --git a/test/connection.js b/test/connection.js
index ecc64c4b..5b80b83f 100644
--- a/test/connection.js
+++ b/test/connection.js
@@ -2,7 +2,7 @@ var expect = require('expect.js');
var eio = require('../');
describe('connection', function() {
- this.timeout(10000);
+ this.timeout(20000);
it('should connect to localhost', function(done){
var socket = new eio.Socket();
diff --git a/test/index.js b/test/index.js
index 88d7c45a..b6d7bdba 100644
--- a/test/index.js
+++ b/test/index.js
@@ -7,6 +7,21 @@ global.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = null;
global.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION = null;
global.WEB_SOCKET_SWF_LOCATION = null;
+var blobSupported = (function() {
+ try {
+ new Blob(['hi']);
+ return true;
+ } catch(e) {}
+ return false;
+})();
+
+/**
+ * Create a blob builder even when vendor prefixes exist
+ */
+
+var BlobBuilder = global.BlobBuilder || global.WebKitBlobBuilder || global.MSBlobBuilder || global.MozBlobBuilder;
+var blobBuilderSupported = !!BlobBuilder && !!BlobBuilder.prototype.append && !!BlobBuilder.prototype.getBlob;
+
require('./engine.io-client');
require('./util');
require('./parser');
@@ -16,4 +31,14 @@ require('./transport');
// browser only tests
if (env.browser) {
require('./connection');
+ if (global.ArrayBuffer) {
+ require('./browser-only-parser');
+ require('./arraybuffer');
+ } else {
+ require('./binary-fallback');
+ }
+
+ if (blobSupported || blobBuilderSupported) {
+ require('./blob');
+ }
}
diff --git a/test/parser.js b/test/parser.js
index 285ce38d..d279d614 100644
--- a/test/parser.js
+++ b/test/parser.js
@@ -15,7 +15,7 @@ var parser = eio.parser
var encode = parser.encodePacket
, decode = parser.decodePacket
, encPayload = parser.encodePayload
- , decPayload = parser.decodePayload
+ , decPayload = parser.decodePayload;
/**
* Tests.
@@ -25,59 +25,85 @@ describe('parser', function () {
describe('packets', function () {
describe('basic functionality', function () {
- it('should encode packets as strings', function () {
- expect(encode({ type: 'message', data: 'test' })).to.be.a('string');
+ it('should encode packets as strings', function (done) {
+ encode({ type: 'message', data: 'test' }, function(data) {
+ expect(data).to.be.a('string');
+ done();
+ });
});
- it('should decode packets as objects', function () {
- expect(decode(encode({ type: 'message', data: 'test' }))).to.be.an('object');
+ it('should decode packets as objects', function (done) {
+ encode({ type: 'message', data: 'test' }, function(data) {
+ expect(decode(data)).to.be.an('object');
+ done();
+ });
});
});
describe('encoding and decoding', function () {
- it('should allow no data', function () {
- expect(decode(encode({ type: 'message' })))
- .to.eql({ type: 'message' });
+ it('should allow no data', function (done) {
+ encode({ type: 'message' }, function(data) {
+ expect(decode(data)).to.eql({ type: 'message' });
+ done();
+ });
});
- it('should encode an open packet', function () {
- expect(decode(encode({ type: 'open', data: '{"some":"json"}' })))
- .to.eql({ type: 'open', data: '{"some":"json"}' });
+ it('should encode an open packet', function (done) {
+ encode({ type: 'open', data: '{"some":"json"}' }, function(data) {
+ expect(decode(data)).to.eql({ type: 'open', data: '{"some":"json"}' });
+ done();
+ });
});
- it('should encode a close packet', function () {
- expect(decode(encode({ type: 'close' })))
- .to.eql({ type: 'close' });
+ it('should encode a close packet', function (done) {
+ encode({ type: 'close' }, function(data) {
+ expect(decode(data)).to.eql({ type: 'close' });
+ done();
+ });
});
- it('should encode a ping packet', function () {
- expect(decode(encode({ type: 'ping', data: '1' })))
- .to.eql({ type: 'ping', data: '1' });
+ it('should encode a ping packet', function (done) {
+ encode({ type: 'ping', data: '1' }, function(data) {
+ expect(decode(data)).to.eql({ type: 'ping', data: '1' });
+ done();
+ });
});
- it('should encode a pong packet', function () {
- expect(decode(encode({ type: 'pong', data: '1' })))
- .to.eql({ type: 'pong', data: '1' });
+ it('should encode a pong packet', function (done) {
+ encode({ type: 'pong', data: '1' }, function(data) {
+ expect(decode(data)).to.eql({ type: 'pong', data: '1' });
+ done();
+ });
});
- it('should encode a message packet', function () {
- expect(decode(encode({ type: 'message', data: 'aaa' })))
- .to.eql({ type: 'message', data: 'aaa' });
+ it('should encode a message packet', function (done) {
+ encode({ type: 'message', data: 'aaa' }, function(data) {
+ expect(decode(data)).to.eql({ type: 'message', data: 'aaa' });
+ done();
+ });
});
- it('should encode a message packet coercing to string', function () {
- expect(decode(encode({ type: 'message', data: 1 })))
- .to.eql({ type: 'message', data: '1' });
+ it('should encode a message packet coercing to string', function (done) {
+ encode({ type: 'message', data: 1 }, function(data) {
+ expect(decode(data)).to.eql({ type: 'message', data: 1 });
+ done();
+ });
});
- it('should encode an upgrade packet', function () {
- expect(decode(encode({ type: 'upgrade' })))
- .to.eql({ type: 'upgrade' });
+ it('should encode an upgrade packet', function (done) {
+ encode({ type: 'upgrade' }, function(data) {
+ expect(decode(data)).to.eql({ type: 'upgrade' });
+ done();
+ });
});
it('should match the encoding format', function () {
- expect(encode({ type: 'message', data: 'test' })).to.match(/^[0-9]/);
- expect(encode({ type: 'message' })).to.match(/^[0-9]$/);
+ encode({ type: 'message', data: 'test' }, function(data) {
+ expect(data).to.match(/^[0-9]/);
+ });
+ encode({ type: 'message' }, function(data) {
+ expect(data).to.match(/^[0-9]$/);
+ });
});
});
@@ -86,7 +112,7 @@ describe('parser', function () {
it('should disallow bad format', function () {
expect(decode(':::')).to.eql(err);
- })
+ });
it('should disallow inexistent types', function () {
expect(decode('94103')).to.eql(err);
@@ -94,93 +120,104 @@ describe('parser', function () {
});
});
- var packets, indices, totals;
-
- initCallback = function() {
- packets = []; indices = []; totals = [];
- }
-
- callback = function(packet, index, total) {
- packets.push(packet);
- indices.push(index);
- totals.push(total);
- }
-
describe('payloads', function () {
describe('basic functionality', function () {
- it('should encode payloads as strings', function () {
- expect(encPayload([{ type: 'ping' }, { type: 'post' }])).to.be.a('string');
- });
-
- it('should decode payloads as arrays', function () {
- initCallback();
- decPayload(encPayload(['1:a', '2:b']), callback)
- expect(packets).to.be.an('array');
+ it('should encode payloads as strings', function (done) {
+ encPayload([{ type: 'ping' }, { type: 'post' }], function(data) {
+ expect(data).to.be.a('string');
+ done();
+ });
});
});
describe('encoding and decoding', function () {
- it('should encode/decode packets', function () {
- initCallback();
- decPayload(encPayload([{ type: 'message', data: 'a' }]), callback);
- expect(packets).to.eql([{ type: 'message', data: 'a' }]);
+ var seen = 0;
+ it('should encode/decode packets', function (done) {
+ encPayload([{ type: 'message', data: 'a' }], function(data) {
+ decPayload(data,
+ function(packet, index, total) {
+ var isLast = index + 1 == total;
+ expect(isLast).to.eql(true);
+ seen++;
+ });
+ });
+ encPayload([{type: 'message', data: 'a'}, {type: 'ping'}], function(data) {
+ decPayload(data,
+ function(packet, index, total) {
+ var isLast = index + 1 == total;
+ if (!isLast) {
+ expect(packet.type).to.eql('message');
+ } else {
+ expect(packet.type).to.eql('ping');
+ if (seen == 2) { done(); }
+ }
+ seen++;
+ });
+ });
});
it('should encode/decode empty payloads', function () {
- initCallback();
- decPayload(encPayload([]), callback);
- expect(packets).to.have.length(0);
- expect(indices).to.have.length(0);
- expect(totals).to.have.length(0);
- });
-
- it('should encode/decode multiple packets with correct indices/totals', function () {
- initCallback();
- decPayload(encPayload([{ type: 'message', data: 'a' },
- { type: 'message', data: 'b' }, { type: 'message', data: 'c' }]), callback);
- expect(packets).to.eql([{ type: 'message', data: 'a' },
- { type: 'message', data: 'b' }, { type: 'message', data: 'c' }]);
- expect(indices).to.eql([ 3, 7, 11 ]);
- expect(totals).to.eql([ 12, 12, 12 ]);
+ encPayload([], function(data) {
+ decPayload(data,
+ function (packet, index, total) {
+ expect(packet.type).to.eql('open');
+ var isLast = index + 1 == total;
+ expect(isLast).to.eql(true);
+ });
+ });
});
});
describe('decoding error handling', function () {
- var err = [{ type: 'error', data: 'parser error' }];
+ var err = { type: 'error', data: 'parser error' };
+
it('should err on bad payload format', function () {
- initCallback();
- decPayload('1!', callback)
- expect(packets).to.eql(err);
-
- initCallback();
- decPayload('', callback);
- expect(packets).to.eql(err);
-
- initCallback();
- decPayload('))', callback);
- expect(packets).to.eql(err);
+ decPayload('1!', function (packet, index, total) {
+ var isLast = index + 1 == total;
+ expect(packet).to.eql(err);
+ expect(isLast).to.eql(true);
+ });
+ decPayload('', function (packet, index, total) {
+ var isLast = index + 1 == total;
+ expect(packet).to.eql(err);
+ expect(isLast).to.eql(true);
+ });
+ decPayload('))', function (packet, index, total) {
+ var isLast = index + 1 == total;
+ expect(packet).to.eql(err);
+ expect(isLast).to.eql(true);
+ });
});
it('should err on bad payload length', function () {
- initCallback();
- decPayload('1:aa', callback);
- expect(packets).to.eql(err);
-
- initCallback();
- decPayload('1:', callback);
- expect(packets).to.eql(err);
-
- initCallback();
- decPayload('1:a2:b', callback);
- expect(packets).to.eql(err);
+ // line 137
+ decPayload('1:', function (packet, index, total) {
+ var isLast = index + 1 == total;
+ expect(packet).to.eql(err);
+ expect(isLast).to.eql(true);
+ });
});
it('should err on bad packet format', function () {
- initCallback();
- decPayload('3:99:', callback);
- expect(packets).to.eql(err);
+ // line 137
+ decPayload('3:99:', function (packet, index, total) {
+ var isLast = index + 1 == total;
+ expect(packet).to.eql(err);
+ expect(isLast).to.eql(true);
+ });
+ // line 146
+ decPayload('1:aa', function (packet, index, total) {
+ var isLast = index + 1 == total;
+ expect(packet).to.eql(err);
+ expect(isLast).to.eql(true);
+ });
+ // line 137
+ decPayload('1:a2:b', function (packet, index, total) {
+ var isLast = index + 1 == total;
+ expect(packet).to.eql(err);
+ expect(isLast).to.eql(true);
+ });
});
});
});
-
});
diff --git a/test/support/server.js b/test/support/server.js
index e2d55249..732e8312 100644
--- a/test/support/server.js
+++ b/test/support/server.js
@@ -26,4 +26,18 @@ app.get('/test/support/engine.io.js', function(err, res, next) {
server.on('connection', function(socket){
socket.send('hi');
+
+ // Bounce any received messages back
+ socket.on('message', function (data) {
+ if (data === 'give binary') {
+ var abv = new Int8Array(5);
+ for (var i = 0; i < 5; i++) {
+ abv[i] = i;
+ }
+ socket.send(abv);
+ return;
+ }
+
+ socket.send(data);
+ });
});