/** * Tests dependencies. */ var http = require('http'); var https = require('https'); var fs = require('fs'); var path = require('path'); var exec = require('child_process').exec; var zlib = require('zlib'); var eio = require('..'); var eioc = require('engine.io-client'); var listen = require('./common').listen; var expect = require('expect.js'); var request = require('superagent'); var WebSocket = require('ws'); // are we running on node 0.8? var NODE_0_8 = /^v0\.8\./.test(process.version); // are we running on node < 4.4.3 ? var NODE_LT_443 = (function(){ var parts = process.versions.node.split('.'); return (parts[0] < 4 || parts[1] < 4 || parts[2] < 3); })(); // are we running uws wsEngine ? var UWS_ENGINE = process.env.EIO_WS_ENGINE == "uws"; /** * Tests. */ describe('server', function () { describe('verification', function () { it('should disallow non-existent transports', function (done) { var engine = listen(function (port) { request.get('http://localhost:%d/engine.io/default/'.s(port)) .query({ transport: 'tobi' }) // no tobi transport - outrageous .end(function (res) { expect(res.status).to.be(400); expect(res.body.code).to.be(0); expect(res.body.message).to.be('Transport unknown'); expect(res.header['access-control-allow-origin']).to.be('*'); done(); }); }); }); it('should disallow `constructor` as transports', function (done) { // make sure we check for actual properties - not those present on every {} var engine = listen(function (port) { request.get('http://localhost:%d/engine.io/default/'.s(port)) .set('Origin', 'http://engine.io') .query({ transport: 'constructor' }) .end(function (res) { expect(res.status).to.be(400); expect(res.body.code).to.be(0); expect(res.body.message).to.be('Transport unknown'); expect(res.header['access-control-allow-credentials']).to.be('true'); expect(res.header['access-control-allow-origin']).to.be('http://engine.io'); done(); }); }); }); it('should disallow non-existent sids', function (done) { var engine = listen(function (port) { request.get('http://localhost:%d/engine.io/default/'.s(port)) .set('Origin', 'http://engine.io') .query({ transport: 'polling', sid: 'test' }) .end(function (res) { expect(res.status).to.be(400); expect(res.body.code).to.be(1); expect(res.body.message).to.be('Session ID unknown'); expect(res.header['access-control-allow-credentials']).to.be('true'); expect(res.header['access-control-allow-origin']).to.be('http://engine.io'); done(); }); }); }); }); describe('handshake', function () { it('should send the io cookie', function (done) { var engine = listen(function (port) { request.get('http://localhost:%d/engine.io/default/'.s(port)) .query({ transport: 'polling', b64: 1 }) .end(function (res) { // hack-obtain sid var sid = res.text.match(/"sid":"([^"]+)"/)[1]; expect(res.headers['set-cookie'][0]).to.be('io=' + sid); done(); }); }); }); it('should send the io cookie custom name', function (done) { var engine = listen({ cookie: 'woot' }, function (port) { request.get('http://localhost:%d/engine.io/default/'.s(port)) .query({ transport: 'polling', b64: 1 }) .end(function (res) { var sid = res.text.match(/"sid":"([^"]+)"/)[1]; expect(res.headers['set-cookie'][0]).to.be('woot=' + sid); done(); }); }); }); it('should send the cookie with custom path', function (done) { var engine = listen({ cookiePath: '/' }, function (port) { request.get('http://localhost:%d/engine.io/default/'.s(port)) .query({ transport: 'polling', b64: 1 }) .end(function (res) { var sid = res.text.match(/"sid":"([^"]+)"/)[1]; expect(res.headers['set-cookie'][0]).to.be('io=' + sid + '; path=/'); done(); }); }); }); it('should not send the io cookie', function (done) { var engine = listen({ cookie: false }, function (port) { request.get('http://localhost:%d/engine.io/default/'.s(port)) .query({ transport: 'polling' }) .end(function (res) { expect(res.headers['set-cookie']).to.be(undefined); done(); }); }); }); it('should register a new client', function (done) { var engine = listen({ allowUpgrades: false }, function (port) { expect(Object.keys(engine.clients)).to.have.length(0); expect(engine.clientsCount).to.be(0); var socket = new eioc.Socket('ws://localhost:%d'.s(port)); socket.on('open', function () { expect(Object.keys(engine.clients)).to.have.length(1); expect(engine.clientsCount).to.be(1); done(); }); }); }); it('should register a new client with custom id', function (done) { var engine = listen({ allowUpgrades: false }, function (port) { expect(Object.keys(engine.clients)).to.have.length(0); expect(engine.clientsCount).to.be(0); var customId = 'CustomId' + Date.now(); engine.generateId = function(req) { return customId; }; var socket = new eioc.Socket('ws://localhost:%d'.s(port)); socket.once('open', function () { expect(Object.keys(engine.clients)).to.have.length(1); expect(engine.clientsCount).to.be(1); expect(socket.id).to.be(customId); expect(engine.clients[customId].id).to.be(customId); done(); }); }); }); it('should exchange handshake data', function (done) { var engine = listen({ allowUpgrades: false }, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port)); socket.on('handshake', function (obj) { expect(obj.sid).to.be.a('string'); expect(obj.pingTimeout).to.be.a('number'); expect(obj.upgrades).to.be.an('array'); done(); }); }); }); it('should allow custom ping timeouts', function (done) { var engine = listen({ allowUpgrades: false, pingTimeout: 123 }, function (port) { var socket = new eioc.Socket('http://localhost:%d'.s(port)); socket.on('handshake', function (obj) { expect(obj.pingTimeout).to.be(123); done(); }); }); }); it('should trigger a connection event with a Socket', function (done) { var engine = listen({ allowUpgrades: false }, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port)); engine.on('connection', function (socket) { expect(socket).to.be.an(eio.Socket); done(); }); }); }); it('should open with polling by default', function (done) { var engine = listen({ allowUpgrades: false }, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port)); engine.on('connection', function (socket) { expect(socket.transport.name).to.be('polling'); done(); }); }); }); it('should be able to open with ws directly', function (done) { var engine = listen({ transports: ['websocket'] }, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] }); engine.on('connection', function (socket) { expect(socket.transport.name).to.be('websocket'); done(); }); }); }); it('should not suggest any upgrades for websocket', function (done) { var engine = listen({ transports: ['websocket'] }, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] }); socket.on('handshake', function (obj) { expect(obj.upgrades).to.have.length(0); done(); }); }); }); it('should not suggest upgrades when none are availble', function (done) { var engine = listen({ transports: ['polling'] }, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port), { }); socket.on('handshake', function (obj) { expect(obj.upgrades).to.have.length(0); done(); }); }); }); it('should only suggest available upgrades', function (done) { var engine = listen({ transports: ['polling'] }, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port), { }); socket.on('handshake', function (obj) { expect(obj.upgrades).to.have.length(0); done(); }); }); }); it('should suggest all upgrades when no transports are disabled', function (done) { var engine = listen({}, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port), { }); socket.on('handshake', function (obj) { expect(obj.upgrades).to.have.length(1); expect(obj.upgrades).to.have.contain('websocket'); done(); }); }); }); it('default to polling when proxy doesn\'t support websocket', function (done) { var engine = listen({ allowUpgrades: false }, function (port) { engine.on('connection', function (socket) { socket.on('message', function (msg) { if ('echo' == msg) socket.send(msg); }); }); var socket = new eioc.Socket('ws://localhost:%d'.s(port)); socket.on('open', function () { request.get('http://localhost:%d/engine.io/'.s(port)) .set({ connection: 'close' }) .query({ transport: 'websocket', sid: socket.id }) .end(function (err, res) { expect(err).to.be(null); expect(res.status).to.be(400); expect(res.body.code).to.be(3); socket.send('echo'); socket.on('message', function (msg) { expect(msg).to.be('echo'); done(); }); }); }); }); }); it('should allow arbitrary data through query string', function (done) { var engine = listen({ allowUpgrades: false }, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port), { query: { a: 'b' } }); engine.on('connection', function (conn) { expect(conn.request._query).to.have.keys('transport', 'a'); expect(conn.request._query.a).to.be('b'); done(); }); }); }); it('should allow data through query string in uri', function (done) { var engine = listen({ allowUpgrades: false }, function (port) { var socket = new eioc.Socket('ws://localhost:%d?a=b&c=d'.s(port)); engine.on('connection', function (conn) { expect(conn.request._query.EIO).to.be.a('string'); expect(conn.request._query.a).to.be('b'); expect(conn.request._query.c).to.be('d'); done(); }); }); }); it('should disallow bad requests', function (done) { var engine = listen(function (port) { request.get('http://localhost:%d/engine.io/default/'.s(port)) .set('Origin', 'http://engine.io') .query({ transport: 'websocket' }) .end(function (res) { expect(res.status).to.be(400); expect(res.body.code).to.be(3); expect(res.body.message).to.be('Bad request'); expect(res.header['access-control-allow-credentials']).to.be('true'); expect(res.header['access-control-allow-origin']).to.be('http://engine.io'); done(); }); }); }); }); describe('close', function () { it('should be able to access non-empty writeBuffer at closing (server)', function(done) { var opts = {allowUpgrades: false}; var engine = listen(opts, function (port) { var socket = new eioc.Socket('http://localhost:%d'.s(port)); engine.on('connection', function (conn) { conn.on('close', function (reason) { expect(conn.writeBuffer.length).to.be(1); setTimeout(function () { expect(conn.writeBuffer.length).to.be(0); // writeBuffer has been cleared }, 10); done(); }); conn.writeBuffer.push({ type: 'message', data: 'foo'}); conn.onError(''); }); }); }); it('should be able to access non-empty writeBuffer at closing (client)', function(done) { var opts = {allowUpgrades: false}; var engine = listen(opts, function (port) { var socket = new eioc.Socket('http://localhost:%d'.s(port)); socket.on('open', function() { socket.on('close', function (reason) { expect(socket.writeBuffer.length).to.be(1); setTimeout(function() { expect(socket.writeBuffer.length).to.be(0); }, 10); done(); }); socket.writeBuffer.push({ type: 'message', data: 'foo'}); socket.onError(''); }); }); }); it('should trigger on server if the client does not pong', function (done) { var opts = { allowUpgrades: false, pingInterval: 5, pingTimeout: 5 }; var engine = listen(opts, function (port) { var socket = new eioc.Socket('http://localhost:%d'.s(port)); socket.sendPacket = function (){}; engine.on('connection', function (conn) { conn.on('close', function (reason) { expect(reason).to.be('ping timeout'); done(); }); }); }); }); it('should trigger on server even when there is no outstanding polling request (GH-198)', function (done) { var opts = { allowUpgrades: false, pingInterval: 500, pingTimeout: 500 }; var engine = listen(opts, function (port) { var socket = new eioc.Socket('http://localhost:%d'.s(port)); engine.on('connection', function (conn) { conn.on('close', function (reason) { expect(reason).to.be('ping timeout'); done(); }); // client abruptly disconnects, no polling request on this tick since we've just connected socket.sendPacket = socket.onPacket = function (){}; socket.close(); // then server app tries to close the socket, since client disappeared conn.close(); }); }); }); it('should trigger on client if server does not meet ping timeout', function (done) { var opts = { allowUpgrades: false, pingInterval: 50, pingTimeout: 30 }; var engine = listen(opts, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port)); socket.on('open', function () { // override onPacket and Transport#onClose to simulate an inactive server after handshake socket.onPacket = function(){}; socket.transport.onClose = function(){}; socket.on('close', function (reason, err) { expect(reason).to.be('ping timeout'); done(); }); }); }); }); it('should trigger on both ends upon ping timeout', function (done) { var opts = { allowUpgrades: false, pingTimeout: 10, pingInterval: 10 }; var engine = listen(opts, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port)) , total = 2; function onClose (reason, err) { expect(reason).to.be('ping timeout'); --total || done(); } engine.on('connection', function (conn) { conn.on('close', onClose); }); socket.on('open', function () { // override onPacket and Transport#onClose to simulate an inactive server after handshake socket.onPacket = socket.sendPacket = function(){}; socket.transport.onClose = function(){}; socket.on('close', onClose); }); }); }); it('should trigger when server closes a client', function (done) { var engine = listen({ allowUpgrades: false }, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port)) , total = 2; engine.on('connection', function (conn) { conn.on('close', function (reason) { expect(reason).to.be('forced close'); --total || done(); }); setTimeout(function () { conn.close(); }, 10); }); socket.on('open', function () { socket.on('close', function (reason) { expect(reason).to.be('transport close'); --total || done(); }); }); }); }); it('should trigger when server closes a client (ws)', function (done) { var opts = { allowUpgrades: false, transports: ['websocket'] }; var engine = listen(opts, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] }) , total = 2; engine.on('connection', function (conn) { conn.on('close', function (reason) { expect(reason).to.be('forced close'); --total || done(); }); setTimeout(function () { conn.close(); }, 10); }); socket.on('open', function () { socket.on('close', function (reason) { expect(reason).to.be('transport close'); --total || done(); }); }); }); }); it('should trigger when client closes', function (done) { var engine = listen({ allowUpgrades: false }, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port)) , total = 2; engine.on('connection', function (conn) { conn.on('close', function (reason) { expect(reason).to.be('transport close'); --total || done(); }); }); socket.on('open', function () { socket.on('close', function (reason) { expect(reason).to.be('forced close'); --total || done(); }); setTimeout(function () { socket.close(); }, 10); }); }); }); it('should trigger when client closes (ws)', function (done) { var opts = { allowUpgrades: false, transports: ['websocket'] }; var engine = listen(opts, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] }) , total = 2; engine.on('connection', function (conn) { conn.on('close', function (reason) { expect(reason).to.be('transport close'); --total || done(); }); }); socket.on('open', function () { socket.on('close', function (reason) { expect(reason).to.be('forced close'); --total || done(); }); setTimeout(function () { socket.close(); }, 10); }); }); }); it('should trigger when calling socket.close() in payload', function (done) { var engine = listen({ allowUpgrades: false }, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port)); engine.on('connection', function (conn) { conn.send(null, function () {socket.close();}); conn.send('this should not be handled'); conn.on('close', function (reason) { expect(reason).to.be('transport close'); done(); }); }); socket.on('open', function () { socket.on('message', function (msg) { expect(msg).to.not.be('this should not be handled'); }); socket.on('close', function (reason) { expect(reason).to.be('forced close'); }); }); }); }); it('should abort upgrade if socket is closed (GH-35)', function (done) { var engine = listen({ allowUpgrades: true }, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port)); socket.on('open', function () { socket.close(); // we wait until complete to see if we get an uncaught EPIPE setTimeout(function(){ done(); }, 100); }); }); }); it('should trigger if a poll request is ongoing and the underlying ' + 'socket closes, as in a browser tab close', function ($done) { var engine = listen({ allowUpgrades: false }, function (port) { // hack to access the sockets created by node-xmlhttprequest // see: https://github.com/driverdan/node-XMLHttpRequest/issues/44 var request = require('http').request; var sockets = []; http.request = function(opts){ var req = request.apply(null, arguments); req.on('socket', function(socket){ sockets.push(socket); }); return req; }; function done(){ http.request = request; $done(); } var socket = new eioc.Socket('ws://localhost:%d'.s(port)) , serverSocket; engine.on('connection', function(s){ serverSocket = s; }); socket.transport.on('poll', function(){ // we set a timer to wait for the request to actually reach setTimeout(function(){ // at this time server's `connection` should have been fired expect(serverSocket).to.be.an('object'); // OPENED readyState is expected - we qre actually polling expect(socket.transport.pollXhr.xhr.readyState).to.be(1); // 2 requests sent to the server over an unique port means // we should have been assigned 2 sockets expect(sockets.length).to.be(2); // expect the socket to be open at this point expect(serverSocket.readyState).to.be('open'); // kill the underlying connection sockets[1].end(); serverSocket.on('close', function(reason, err){ expect(reason).to.be('transport error'); expect(err.message).to.be('poll connection closed prematurely'); done(); }); }, 50); }); }); }); it('should not trigger with connection: close header', function($done){ var engine = listen({ allowUpgrades: false }, function(port){ // intercept requests to add connection: close var request = http.request; http.request = function(){ var opts = arguments[0]; opts.headers = opts.headers || {}; opts.headers.Connection = 'close'; return request.apply(this, arguments); }; function done(){ http.request = request; $done(); } engine.on('connection', function(socket){ socket.on('message', function(msg){ expect(msg).to.equal('test'); socket.send('woot'); }); }); var socket = new eioc.Socket('ws://localhost:%d'.s(port)); socket.on('open', function(){ socket.send('test'); }); socket.on('message', function(msg){ expect(msg).to.be('woot'); done(); }); }); }); it('should not trigger early with connection `ping timeout`' + 'after post handshake timeout', function (done) { // first timeout should trigger after `pingInterval + pingTimeout`, // not just `pingTimeout`. var opts = { allowUpgrades: false, pingInterval: 300, pingTimeout: 100 }; var engine = listen(opts, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port)); var clientCloseReason = null; socket.on('handshake', function() { socket.onPacket = function(){}; }); socket.on('open', function () { socket.on('close', function (reason) { clientCloseReason = reason; }); }); setTimeout(function() { expect(clientCloseReason).to.be(null); done(); }, 200); }); }); it('should not trigger early with connection `ping timeout` ' + 'after post ping timeout', function (done) { // ping timeout should trigger after `pingInterval + pingTimeout`, // not just `pingTimeout`. var opts = { allowUpgrades: false, pingInterval: 80, pingTimeout: 50 }; var engine = listen(opts, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port)); var clientCloseReason = null; engine.on('connection', function(conn){ conn.on('heartbeat', function() { conn.onPacket = function(){}; }); }); socket.on('open', function () { socket.on('close', function (reason) { clientCloseReason = reason; }); }); setTimeout(function() { expect(clientCloseReason).to.be(null); done(); }, 100); }); }); it('should trigger early with connection `transport close` ' + 'after missing pong', function (done) { // ping timeout should trigger after `pingInterval + pingTimeout`, // not just `pingTimeout`. var opts = { allowUpgrades: false, pingInterval: 80, pingTimeout: 50 }; var engine = listen(opts, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port)); var clientCloseReason = null; socket.on('open', function () { socket.on('close', function (reason) { clientCloseReason = reason; }); }); engine.on('connection', function(conn){ conn.on('heartbeat', function() { setTimeout(function() { conn.close(); }, 20); setTimeout(function() { expect(clientCloseReason).to.be('transport close'); done(); }, 100); }); }); }); }); it('should trigger with connection `ping timeout` ' + 'after `pingInterval + pingTimeout`', function (done) { var opts = { allowUpgrades: false, pingInterval: 300, pingTimeout: 100 }; var engine = listen(opts, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port)); var clientCloseReason = null; socket.on('open', function () { socket.on('close', function (reason) { clientCloseReason = reason; }); }); engine.on('connection', function(conn){ conn.once('heartbeat', function() { setTimeout(function() { socket.onPacket = function(){}; expect(clientCloseReason).to.be(null); }, 150); setTimeout(function() { expect(clientCloseReason).to.be(null); }, 350); setTimeout(function() { expect(clientCloseReason).to.be('ping timeout'); done(); }, 500); }); }); }); }); it('should abort the polling data request if it is ' + 'in progress', function (done) { var engine = listen({ transports: [ 'polling' ] }, function (port) { var socket = new eioc.Socket('http://localhost:%d'.s(port)); engine.on('connection', function (conn) { var onDataRequest = conn.transport.onDataRequest; conn.transport.onDataRequest = function (req, res) { engine.httpServer.close(done); onDataRequest.call(conn.transport, req, res); req.removeAllListeners(); conn.close(); }; }); socket.on('open', function () { socket.send('test'); }); }); }); // tests https://github.com/LearnBoost/engine.io-client/issues/207 // websocket test, transport error it('should trigger transport close before open for ws', function(done){ var opts = { transports: ['websocket'] }; var engine = listen(opts, function (port) { var url = 'ws://%s:%d'.s('0.0.0.50', port); var socket = new eioc.Socket(url); socket.on('open', function(){ done(new Error('Test invalidation')); }); socket.on('close', function(reason){ expect(reason).to.be('transport error'); done(); }); }); }); // tests https://github.com/LearnBoost/engine.io-client/issues/207 // polling test, transport error it('should trigger transport close before open for xhr', function(done){ var opts = { transports: ['polling'] }; var engine = listen(opts, function (port) { var socket = new eioc.Socket('http://invalidserver:%d'.s(port)); socket.on('open', function(){ done(new Error('Test invalidation')); }); socket.on('close', function(reason){ expect(reason).to.be('transport error'); done(); }); }); }); // tests https://github.com/LearnBoost/engine.io-client/issues/207 // websocket test, force close it('should trigger force close before open for ws', function(done){ var opts = { transports: ['websocket'] }; var engine = listen(opts, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port)); socket.on('open', function(){ done(new Error('Test invalidation')); }); socket.on('close', function(reason){ expect(reason).to.be('forced close'); done(); }); socket.close(); }); }); // tests https://github.com/LearnBoost/engine.io-client/issues/207 // polling test, force close it('should trigger force close before open for xhr', function(done){ var opts = { transports: ['polling'] }; var engine = listen(opts, function (port) { var socket = new eioc.Socket('http://localhost:%d'.s(port)); socket.on('open', function(){ done(new Error('Test invalidation')); }); socket.on('close', function(reason){ expect(reason).to.be('forced close'); done(); }); socket.close(); }); }); it('should close transport upon ping timeout (ws)', function (done) { var opts = { allowUpgrades: false, transports: ['websocket'], pingInterval: 50, pingTimeout: 30 }; var engine = listen(opts, function (port) { engine.on('connection', function (conn) { conn.transport.on('close', done); }); var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] }); // override to simulate an inactive client socket.sendPacket = socket.onHeartbeat = function (){}; }); }); it('should close transport upon ping timeout (polling)', function (done) { var opts = { allowUpgrades: false, transports: ['polling'], pingInterval: 50, pingTimeout: 30 }; var engine = listen(opts, function (port) { engine.on('connection', function (conn) { conn.transport.on('close', done); }); var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['polling'] }); // override to simulate an inactive client socket.sendPacket = socket.onHeartbeat = function (){}; }); }); it('should close transport upon parse error (ws)', function (done) { var opts = { allowUpgrades: false, transports: ['websocket'] }; var engine = listen(opts, function (port) { engine.on('connection', function (conn) { conn.transport.on('close', done); }); var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] }); socket.on('open', function () { socket.transport.ws.send('invalid'); }); }); }); it('should close transport upon parse error (polling)', function (done) { var opts = { allowUpgrades: false, transports: ['polling'] }; var engine = listen(opts, function (port) { engine.on('connection', function (conn) { conn.transport.closeTimeout = 100; conn.transport.on('close', done); }); var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['polling'] }); socket.on('open', function () { socket.transport.doWrite('invalid', function (){}); }); }); }); it('should close upgrading transport upon socket close', function (done) { var engine = listen(function (port) { engine.on('connection', function (conn) { conn.on('upgrading', function (transport) { transport.on('close', done); conn.close(); }); }); new eioc.Socket('ws://localhost:%d'.s(port)); }); }); it('should close upgrading transport upon upgrade timeout', function (done) { var opts = { upgradeTimeout: 100 }; var engine = listen(opts, function (port) { engine.on('connection', function (conn) { conn.on('upgrading', function (transport) { transport.on('close', done); }); }); var socket = new eioc.Socket('ws://localhost:%d'.s(port)); socket.on('upgrading', function (transport) { // override not to complete upgrading transport.send = function (){}; }); }); }); it('should not crash when messing with Object prototype', function(done){ Object.prototype.foo = 'bar'; var engine = listen({ allowUpgrades: true }, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port)); socket.on('open', function () { engine.close(); setTimeout(function(){ done(); }, 100); }); }); }); describe('graceful close', function () { function fixture(filename) { return process.execPath + ' ' + path.join(__dirname, 'fixtures', filename); } it('should stop socket and timers', function(done){ exec(fixture('server-close.js'), done); }); it('should stop upgraded socket and timers', function(done){ exec(fixture('server-close-upgraded.js'), done); }); it('should stop upgrading socket and timers', function(done){ exec(fixture('server-close-upgrading.js'), done); }); }); }); describe('messages', function () { this.timeout(5000); it('should arrive from server to client', function (done) { var engine = listen({ allowUpgrades: false }, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port)); engine.on('connection', function (conn) { conn.send('a'); }); socket.on('open', function () { socket.on('message', function (msg) { expect(msg).to.be('a'); done(); }); }); }); }); it('should arrive from server to client (multiple)', function (done) { var engine = listen({ allowUpgrades: false }, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port)) , expected = ['a', 'b', 'c'] , i = 0; engine.on('connection', function (conn) { conn.send('a'); // we use set timeouts to ensure the messages are delivered as part // of different. setTimeout(function () { conn.send('b'); setTimeout(function () { // here we make sure we buffer both the close packet and // a regular packet conn.send('c'); conn.close(); }, 50); }, 50); conn.on('close', function () { // since close fires right after the buffer is drained setTimeout(function () { expect(i).to.be(3); done(); }, 50); }); }); socket.on('open', function () { socket.on('message', function (msg) { expect(msg).to.be(expected[i++]); }); }); }); }); it('should not be receiving data when getting a message longer than maxHttpBufferSize when polling', function(done) { var opts = { allowUpgrades: false, transports: ['polling'], maxHttpBufferSize: 5 }; var engine = listen(opts, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port)); engine.on('connection', function (conn) { conn.on('message', function(msg) { console.log(msg); }); }); socket.on('open', function () { socket.send('aasdasdakjhasdkjhasdkjhasdkjhasdkjhasdkjhasdkjha'); }); }); setTimeout(done, 1000); }); it('should receive data when getting a message shorter than maxHttpBufferSize when polling', function(done) { var opts = { allowUpgrades: false, transports: ['polling'], maxHttpBufferSize: 5 }; var engine = listen(opts, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port)); engine.on('connection', function (conn) { conn.on('message', function(msg) { expect(msg).to.be('a'); done(); }); }); socket.on('open', function () { socket.send('a'); }); }); }); it('should arrive from server to client (ws)', function (done) { var opts = { allowUpgrades: false, transports: ['websocket'] }; var engine = listen(opts, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] }); engine.on('connection', function (conn) { conn.send('a'); }); socket.on('open', function () { socket.on('message', function (msg) { expect(msg).to.be('a'); done(); }); }); }); }); it('should arrive from server to client (multiple, ws)', function (done) { var opts = { allowUpgrades: false, transports: ['websocket'] }; var engine = listen(opts, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] }) , expected = ['a', 'b', 'c'] , i = 0; engine.on('connection', function (conn) { conn.send('a'); setTimeout(function () { conn.send('b'); setTimeout(function () { conn.send('c'); conn.close(); }, 50); }, 50); conn.on('close', function () { setTimeout(function () { expect(i).to.be(3); done(); }, 50); }); }); socket.on('open', function () { socket.on('message', function (msg) { expect(msg).to.be(expected[i++]); }); }); }); }); it('should arrive from server to client (multiple, no delay, ws)', function (done) { var opts = { allowUpgrades: false, transports: ['websocket'] }; var engine = listen(opts, function (port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] }) , expected = ['a', 'b', 'c'] , i = 0; engine.on('connection', function (conn) { conn.on('close', function () { setTimeout(function () { expect(i).to.be(3); done(); }, 50); }); conn.send('a'); conn.send('b'); conn.send('c'); conn.close(); }); socket.on('open', function () { socket.on('message', function (msg) { expect(msg).to.be(expected[i++]); }); }); }); }); it('should arrive when binary data is sent as Int8Array (ws)', function (done) { var binaryData = new Int8Array(5); for (var i = 0; i < binaryData.length; i++) { binaryData[i] = i; } var opts = { allowUpgrades: false, transports: ['websocket'] }; var engine = listen(opts, function(port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] }); engine.on('connection', function (conn) { conn.send(binaryData); }); socket.on('open', function () { socket.on('message', function(msg) { for (var i = 0; i < binaryData.length; i++) { var num = msg.readInt8(i); expect(num).to.be(i); } done(); }); }); }); }); it('should arrive when binary data is sent as Int32Array (ws)', function (done) { var binaryData = new Int32Array(5); for (var i = 0; i < binaryData.length; i++) { binaryData[i] = (i + 100) * 9823; } var opts = { allowUpgrades: false, transports: ['websocket'] }; var engine = listen(opts, function(port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] }); engine.on('connection', function (conn) { conn.send(binaryData); }); socket.on('open', function () { socket.on('message', function(msg) { for (var i = 0, ii = 0; ii < binaryData.length; i += 4, ii++) { var num = msg.readInt32LE(i); expect(num).to.be((ii + 100) * 9823); } done(); }); }); }); }); it('should arrive when binary data is sent as Int32Array, given as ArrayBuffer(ws)', function (done) { var binaryData = new Int32Array(5); for (var i = 0; i < binaryData.length; i++) { binaryData[i] = (i + 100) * 9823; } var opts = { allowUpgrades: false, transports: ['websocket'] }; var engine = listen(opts, function(port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] }); engine.on('connection', function (conn) { conn.send(binaryData.buffer); }); socket.on('open', function () { socket.on('message', function(msg) { for (var i = 0, ii = 0; ii < binaryData.length; i += 4, ii++) { var num = msg.readInt32LE(i); expect(num).to.be((ii + 100) * 9823); } done(); }); }); }); }); it('should arrive when binary data is sent as Buffer (ws)', function (done) { var binaryData = Buffer(5); for (var i = 0; i < binaryData.length; i++) { binaryData.writeInt8(i, i); } var opts = { allowUpgrades: false, transports: ['websocket'] }; var engine = listen(opts, function(port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] }); engine.on('connection', function (conn) { conn.send(binaryData); }); socket.on('open', function () { socket.on('message', function(msg) { for (var i = 0; i < binaryData.length; i++) { var num = msg.readInt8(i); expect(num).to.be(i); } done(); }); }); }); }); it('should arrive when binary data sent as Buffer (polling)', function (done) { var binaryData = Buffer(5); for (var i = 0; i < binaryData.length; i++) { binaryData.writeInt8(i, i); } var opts = { allowUpgrades: false, transports: ['polling'] }; var engine = listen(opts, function(port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['polling'] }); engine.on('connection', function (conn) { conn.send(binaryData); }); socket.on('open', function() { socket.on('message', function(msg) { for (var i = 0; i < binaryData.length; i++) { var num = msg.readInt8(i); expect(num).to.be(i); } done(); }); }); }); }); it('should arrive as ArrayBuffer if requested when binary data sent as Buffer (ws)', function (done) { var binaryData = Buffer(5); for (var i = 0; i < binaryData.length; i++) { binaryData.writeInt8(i, i); } var opts = { allowUpgrades: false, transports: ['websocket'] }; var engine = listen(opts, function(port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] }); socket.binaryType = 'arraybuffer'; engine.on('connection', function (conn) { conn.send(binaryData); }); socket.on('open', function() { socket.on('message', function(msg) { expect(msg instanceof ArrayBuffer).to.be(true); var intArray = new Int8Array(msg); for (var i = 0; i < binaryData.length; i++) { expect(intArray[i]).to.be(i); } done(); }); }); }); }); it('should arrive as ArrayBuffer if requested when binary data sent as Buffer (polling)', function (done) { var binaryData = Buffer(5); for (var i = 0; i < binaryData.length; i++) { binaryData.writeInt8(i, i); } var opts = { allowUpgrades: false, transports: ['polling'] }; var engine = listen(opts, function(port) { var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['polling'] }); socket.binaryType = 'arraybuffer'; engine.on('connection', function (conn) { conn.send(binaryData); }); socket.on('open', function() { socket.on('message', function(msg) { expect(msg instanceof ArrayBuffer).to.be(true); var intArray = new Int8Array(msg); for (var i = 0; i < binaryData.length; i++) { expect(intArray[i]).to.be(i); } done(); }); }); }); }); it('should trigger a flush/drain event', function(done){ var engine = listen({ allowUpgrades: false }, function(port){ engine.on('connection', function(socket){ var totalEvents = 4; engine.on('flush', function(sock, buf){ expect(sock).to.be(socket); expect(buf).to.be.an('array'); --totalEvents || done(); }); socket.on('flush', function(buf){ expect(buf).to.be.an('array'); --totalEvents || done(); }); engine.on('drain', function(sock){ expect(sock).to.be(socket); expect(socket.writeBuffer.length).to.be(0); --totalEvents || done(); }); socket.on('drain', function(){ expect(socket.writeBuffer.length).to.be(0); --totalEvents || done(); }); socket.send('aaaa'); }); new eioc.Socket('ws://localhost:%d'.s(port)); }); }); it('should interleave with pongs if many messages buffered ' + 'after connection open', function (done) { this.slow(4000); this.timeout(8000); var opts = { transports: ['websocket'], pingInterval: 200, pingTimeout: 100 }; var engine = listen(opts, function (port) { var messageCount = 100; var messagePayload = new Array(256 * 256).join('a'); var connection = null; engine.on('connection', function (conn) { connection = conn; }); var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] }); socket.on('open', function () { for (var i=0;i= 4) describe('wsEngine option', function() { it('should allow loading of other websocket server implementation like uws', function(done) { var engine = listen({ allowUpgrades: false, wsEngine: 'uws' }, function (port) { expect(engine.ws instanceof require('uws').Server).to.be.ok(); var socket = new eioc.Socket('ws://localhost:%d'.s(port)); engine.on('connection', function (conn) { conn.send('a'); }); socket.on('open', function () { socket.on('message', function (msg) { expect(msg).to.be('a'); done(); }); }); }); }); }); });