Skip to content

Commit

Permalink
Adds WebSocketTransport - Fixes #10
Browse files Browse the repository at this point in the history
  • Loading branch information
eduardolundgren committed Sep 22, 2014
1 parent 5467ba7 commit ebb6255
Show file tree
Hide file tree
Showing 4 changed files with 296 additions and 0 deletions.
1 change: 1 addition & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ var mainFiles = [
'src/events/EventEmitter.js',
'src/net/Transport.js',
'src/net/XhrTransport.js',
'src/net/WebSocketTransport.js'
];

gulp.task('build', ['clean'], function() {
Expand Down
120 changes: 120 additions & 0 deletions src/net/WebSocketTransport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
(function() {
'use strict';

/**
* Provides implementation of transport-based cross-browser/cross-device
* bi-directional communication layer for Socket.IO.
* @constructor
* @extends {lfr.Transport}
*/
lfr.WebSocketTransport = function(uri) {
lfr.WebSocketTransport.base(this, 'constructor', uri);
};
lfr.inherits(lfr.WebSocketTransport, lfr.Transport);

/**
* Holds the underlying socket mechanism. Default mechanism uses Socket.IO.
* @type {Socket.IO}
* @default null
*/
lfr.WebSocketTransport.prototype.socket = null;

/**
* @inheritDoc
*/
lfr.WebSocketTransport.prototype.close = function() {
if (this.socket) {
this.socket.close();
}
return this;
};

/**
* Event handle for socket connect event. Fires transport open event.
* @param {?Object} event
* @protected
*/
lfr.WebSocketTransport.prototype.onSocketConnect_ = function() {
this.emit('open');
};

/**
* Event handle for socket data event. Fires transport data event.
* @param {*} data
* @protected
*/
lfr.WebSocketTransport.prototype.onSocketData_ = function(data) {
this.emit('data', {
data: data
});
};

/**
* Event handle for socket disconnect event. Fires transport close event.
* @protected
*/
lfr.WebSocketTransport.prototype.onSocketDisconnect_ = function() {
this.emit('close');
};

/**
* Event handle for socket error event. Fires transport error event.
* @param {Object} event
* @protected
*/
lfr.WebSocketTransport.prototype.onSocketError_ = function(event) {
var error = new Error('Transport request error');
error.socket = this.socket;
error.message = event;
this.emit('error', {
error: error
});
};

/**
* Event handle for socket message event. Fires transport message event.
* @protected
*/
lfr.WebSocketTransport.prototype.onSocketMessage_ = function(message) {
this.emit('message', {
data: message
});
};

/**
* @inheritDoc
*/
lfr.WebSocketTransport.prototype.open = function() {
if (this.isOpen()) {
console.warn('Transport is already open');
return;
}

this.emit('opening');

/*global io*/
if (!this.socket) {
if (!io) {
throw new Error('Socket.IO client not found');
}
this.socket = io();
this.socket.on('connect', lfr.bind(this.onSocketConnect_, this));
this.socket.on('disconnect', lfr.bind(this.onSocketDisconnect_, this));
this.socket.on('error', lfr.bind(this.onSocketError_, this));
this.socket.on('data', lfr.bind(this.onSocketData_, this));
this.socket.on('message', lfr.bind(this.onSocketMessage_, this));
}

this.socket.open();

return this;
};

/**
* @inheritDoc
*/
lfr.WebSocketTransport.prototype.write = function(packet) {
this.socket.send(packet);
};

}());
1 change: 1 addition & 0 deletions test/fixture/sandbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ require('../../src/events/EventHandler.js');
require('../../src/events/EventEmitter.js');
require('../../src/net/Transport.js');
require('../../src/net/XhrTransport.js');
require('../../src/net/WebSocketTransport.js');
174 changes: 174 additions & 0 deletions test/net/WebSocketTransport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
'use strict';

var assert = require('assert');
var sinon = require('sinon');
require('../fixture/sandbox.js');

describe('WebSocketTransport', function() {
before(function() {
global.FakeSocketIO = createFakeSocketIO();
});

beforeEach(function() {
global.io = function() {
return new global.FakeSocketIO();
};
});

it('should throw error when Socket.IO not found', function() {
var transport = new lfr.WebSocketTransport('http://liferay.com');
global.io = null;
assert.throws(function() {
transport.open();
}, Error);
});

it('should connection open', function(done) {
var transport = new lfr.WebSocketTransport('http://liferay.com');
var stubOpen = sinon.stub();
transport.on('open', stubOpen);
transport.open();
// Waits connection to open asynchronously
assert.strictEqual(0, stubOpen.callCount);
setTimeout(function() {
assert.strictEqual(1, stubOpen.callCount);
done();
}, 0);
});

it('should connection warn when open multiple times', function(done) {
var originalWarningFn = console.warn;
console.warn = sinon.stub();

var transport = new lfr.WebSocketTransport('http://liferay.com');
var stubOpen = sinon.stub();
transport.on('open', stubOpen);
transport.open();
transport.open();
transport.open();
// Waits connection to open asynchronously
assert.strictEqual(0, stubOpen.callCount);
setTimeout(function() {
assert.strictEqual(2, console.warn.callCount, 'Should warn when open');
assert.strictEqual(1, stubOpen.callCount, 'Should not emit open twice');

console.warn = originalWarningFn;
done();
}, 0);
});

it('should connection reopen without warn', function(done) {
var originalWarningFn = console.warn;
console.warn = sinon.stub();

var transport = new lfr.WebSocketTransport('http://liferay.com');
transport.close();

var stubClose = sinon.stub();
var stubOpen = sinon.stub();
transport.on('close', stubClose);
transport.on('open', stubOpen);
transport.open();
// Waits connection to open asynchronously
assert.strictEqual(0, stubOpen.callCount);
setTimeout(function() {
assert.strictEqual(1, stubOpen.callCount);
transport.close();
// Waits connection to close asynchronously
assert.strictEqual(0, stubClose.callCount);
setTimeout(function() {
transport.open();
// Waits connection to open asynchronously
assert.strictEqual(1, stubOpen.callCount);
setTimeout(function() {
assert.strictEqual(1, stubClose.callCount, 'Should not emit close twice');
assert.strictEqual(2, stubOpen.callCount, 'Should emit open twice');
assert.strictEqual(0, console.warn.callCount, 'Should warn when open');

console.warn = originalWarningFn;
done();
}, 0);
}, 0);
}, 0);
});

it('should handle successful send message', function(done) {
var transport = new lfr.WebSocketTransport('http://liferay.com');
var stubMessage = sinon.stub();
transport.on('message', stubMessage);
transport.open();
// Waits connection to open asynchronously
setTimeout(function() {
transport.send('message');
// Waits connection to send asynchronously
setTimeout(function() {
assert.strictEqual('message', stubMessage.getCall(0).args[0].data, 'Should set message content');
done();
}, 0);
}, 0);
});

it('should handle successful receive data', function(done) {
var transport = new lfr.WebSocketTransport('http://liferay.com');
var stubData = sinon.stub();
transport.on('data', stubData);
transport.open();
// Waits connection to open asynchronously
setTimeout(function() {
transport.socket.emit('data', 'data');
// Waits connection to send asynchronously
setTimeout(function() {
assert.strictEqual('data', stubData.getCall(0).args[0].data, 'Should receive emitted data');
done();
}, 0);
}, 0);
});

it('should handle failing send data', function(done) {
var transport = new lfr.WebSocketTransport('http://liferay.com');
var stubError = sinon.stub();
transport.on('error', stubError);

transport.open();
// Waits connection to open asynchronously
setTimeout(function() {
transport.send();
transport.socket.emit('error', 'reason');
// Waits connection to send asynchronously
setTimeout(function() {
var error = stubError.getCall(0).args[0].error;
assert.ok(error instanceof Error);
assert.ok(error.socket instanceof global.FakeSocketIO);
assert.strictEqual('reason', error.message);
done();
}, 0);
}, 0);
});
});

function createFakeSocketIO() {
var FakeSocketIO = function() {
FakeSocketIO.base(this, 'constructor');
};
lfr.inherits(FakeSocketIO, lfr.EventEmitter);

FakeSocketIO.prototype.close = function() {
var self = this;
setTimeout(function() {
self.emit('disconnect');
}, 0);
};
FakeSocketIO.prototype.open = function() {
var self = this;
setTimeout(function() {
self.emit('connect');
}, 0);
};
FakeSocketIO.prototype.send = function(message) {
var self = this;
setTimeout(function() {
self.emit('message', message);
}, 0);
};
return FakeSocketIO;
}

0 comments on commit ebb6255

Please sign in to comment.