Skip to content

Commit

Permalink
Merge pull request #34 from thoov/socketio
Browse files Browse the repository at this point in the history
Socket.IO intergration
  • Loading branch information
thoov committed Jun 19, 2015
2 parents b86779e + fa36ba0 commit 05f4b36
Show file tree
Hide file tree
Showing 22 changed files with 348 additions and 35 deletions.
3 changes: 2 additions & 1 deletion .jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"document",
"window",
"-Promise",
"URI"
"URI",
"io"
],
"browser": true,
"boss": true,
Expand Down
3 changes: 2 additions & 1 deletion Brocfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ var EmberAddon = require('ember-cli/lib/broccoli/ember-addon');

var app = new EmberAddon();

app.import('bower_components/mock-socket/dist/mock-socket.min.js');
app.import('bower_components/mock-socket/dist/mock-socket.js');
app.import('bower_components/uri.js/src/URI.min.js');
app.import('bower_components/socket.io-client/socket.io.js');

module.exports = app.toTree();
67 changes: 59 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# EmberJS WebSockets Addon

This addon aims to be a simple and easy way to integrate with any websocket
This addon aims to be a simple and easy way to integrate with any websocket or socket.io
backend. It has been designed to be minimalistic, flexible, and lightweight instead of
forcing certain conventions on the developer. This addon is compatible with EmberJS 2.0!

Expand Down Expand Up @@ -185,6 +185,56 @@ export default Ember.Controller.extend({
});
```

## Socket.IO Support

First run the socket.io generator via:

```shell
ember g socket-io
```

```javascript
import Ember from 'ember';

export default Ember.Controller.extend({

/*
* 1) First step you need to do is inject the socketio service into your object.
*/
socketIOService: Ember.inject.service('socket-io'),

init: function() {
this._super.apply(this, arguments);

/*
* 2) The next step you need to do is to create your actual socketIO.
*/
var socket = this.get('socketIOService').socketFor('http://localhost:7000/');

/*
* 3) Define any event handlers
*/
socket.on('connect', function() {

socket.send('Hello World');

socket.on('message', this.onMessage, this);

socket.on('myCustomNamespace', function() {
socket.emit('anotherNamespace', 'some data');
}, this);

}, this);
},

onMessage: function(data) {
// This is executed within the ember run loop
}
});
```

**Please visit**: [socket.io docs](https://github.com/thoov/ember-websockets/blob/master/docs/socket-io.md) for more details on ember-websocket + socket.io

## Detailed explanations of the APIs

### SocketFor
Expand Down Expand Up @@ -278,12 +328,13 @@ If you have any feedback, encounter any bugs, or just have a question, please fe

## FAQ

* **Recommended backend library/framework**: The only requirement for this mixin to work is a service that can handle ws or wss protocols.
For this reason socket.io will not work as it does not use the standard ws protocol. Instead, I would look at [ws](https://github.com/einaros/ws)
which is a great package.
### Recommended backend library/framework
* [ws](https://github.com/einaros/ws)
* [socket.io](http://socket.io)

* **Browser Support**: Current support for browsers is fairly good with all modern browsers and most mobile browsers
supporting websockets in their current and previously stable versions. It goes without saying that older versions of IE are
not supported. For a more detailed [break down](http://caniuse.com/#feat=websockets)
### Browser Support
Current support for browsers is fairly good with all modern browsers and most mobile browsers
supporting websockets in their current and previously stable versions. It goes without saying that older versions of IE are not supported. For a more detailed [break down](http://caniuse.com/#feat=websockets)

* **License**: This addon falls under the [MIT license](https://github.com/thoov/ember-websockets/blob/master/LICENSE.md)
### License
This addon falls under the [MIT license](https://github.com/thoov/ember-websockets/blob/master/LICENSE.md)
36 changes: 36 additions & 0 deletions addon/helpers/socketio-proxy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Ember from 'ember';

export default Ember.ObjectProxy.extend({
/*
* This method simply passes the arguments to the socketio on method except it binds the callback function to
* the run loop.
*/
on(type, callbackFn, context) {
this.socket.on(type, Ember.run.bind(context, callbackFn));
},

/*
* This method passes the argument to the socketio emit method. If an acknowledgement function is passed then
* we bind that in a run loop.
*/
emit(channel, data, acknowledgementFn, context) {
if(acknowledgementFn && context) {
this.socket.emit.call(this.socket, channel, data, Ember.run.bind(context, acknowledgementFn));
}
else {
this.socket.emit.apply(this.socket, arguments);
}
},

send() {
this.socket.send.apply(this.socket, arguments);
},

close() {
this.socket.close.apply(this.socket, arguments);
},

connect() {
this.socket.connect.apply(this.socket, arguments);
}
});
16 changes: 8 additions & 8 deletions addon/helpers/websocket-proxy.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import Ember from 'ember';

var events = ['close', 'error', 'message', 'open'];
var filter = Ember.EnumerableUtils.filter;
var indexOf = Ember.EnumerableUtils.indexOf;
var forEach = Ember.EnumerableUtils.forEach;
var filter = Array.prototype.filter;
var indexOf = Array.prototype.indexOf;
var forEach = Array.prototype.forEach;

export default Ember.ObjectProxy.extend({
/*
Expand All @@ -29,7 +29,7 @@ export default Ember.ObjectProxy.extend({
* type: must be either 'open', 'message', 'close', 'error'
*/
on(type, callback, context) {
Ember.assert(type + ' is not a recognized event name. Please use on of the following: ' + events.join(', '), indexOf(events, type) !== -1);
Ember.assert(type + ' is not a recognized event name. Please use on of the following: ' + events.join(', '), indexOf.call(events, type) !== -1);
Ember.assert('The second argument must be a function.', Ember.typeOf(callback) === 'function');
Ember.assert('The third argument must be the context of the surrounding object.', Ember.typeOf(context) !== 'undefined');

Expand All @@ -46,7 +46,7 @@ export default Ember.ObjectProxy.extend({
* will not longer be invoked when the given `type` event happens.
*/
off(type, callback) {
this.listeners = filter(this.listeners, listeners => {
this.listeners = filter.call(this.listeners, listeners => {
return !(listeners.callback === callback && listeners.type === type);
});
},
Expand Down Expand Up @@ -76,16 +76,16 @@ export default Ember.ObjectProxy.extend({
setupInternalListeners() {
var self = this;

forEach(events, eventName => {
forEach.call(events, eventName => {
this.socket['on' + eventName] = event => {
Ember.run(() => {
var activeListeners = filter(self.listeners, listener => {
var activeListeners = filter.call(self.listeners, listener => {
return listener.url === event.currentTarget.url && listener.type === eventName;
});

// TODO: filter active listeners for contexts that are not destroyed

activeListeners.forEach(item => {
forEach.call(activeListeners, item => {
item.callback.call(item.context, event);
});
});
Expand Down
77 changes: 77 additions & 0 deletions app/services/socket-io.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import Ember from 'ember';
import SocketIOProxy from 'ember-websockets/helpers/socketio-proxy';

var filter = Array.prototype.filter;

export default Ember.Service.extend({
/*
* Each element in the array is of the form:
*
* {
* url: 'string'
* socket: SocketIO Proxy object
* }
*/
sockets: null,

init() {
this._super(...arguments);
this.sockets = Ember.A();
},

/*
* socketFor returns a socketio proxy object. On this object there is a property `socket`
* which contains the actual socketio object. This socketio object is cached based off of the
* url meaning multiple requests for the same socket will return the same object.
*/
socketFor(url, options = {}) {
var proxy = this.findSocketInCache(this.get('sockets'), url);

if (proxy && this.socketIsNotClosed(proxy.socket)) { return proxy.socket; }

proxy = SocketIOProxy.create({
content: this,
socket: io(this.normalizeURL(url), options)
});

this.get('sockets').pushObject({
url: this.normalizeURL(url),
socket: proxy
});

return proxy;
},

/*
* The native websocket object will transform urls without a pathname to have just a /.
* As an example: ws://localhost:8080 would actually be ws://localhost:8080/ but ws://example.com/foo would not
* change. This function does this transformation to stay inline with the native websocket implementation.
*
*/
normalizeURL(url) {
var parsedUrl = new URI(url);

if(parsedUrl.path() === '/' && url.slice(-1) !== '/') {
return url + '/';
}

return url;
},

socketIsNotClosed(socket) {
return socket.socket.io.readyState !== 'closed';
},

/*
* Returns the socket object from the cache if one matches the url else undefined
*/
findSocketInCache(socketsCache, url) {
var cachedResults = filter.call(socketsCache, websocket => {
return websocket['url'] === this.normalizeURL(url);
});

if(cachedResults.length > 0) {
return cachedResults[0];
}
}
});
12 changes: 5 additions & 7 deletions app/services/websockets.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import Ember from 'ember';
import WebsocketProxy from 'ember-websockets/helpers/websocket-proxy';

var forEach = Ember.EnumerableUtils.forEach;
var filter = Ember.EnumerableUtils.filter;
var forEach = Array.prototype.forEach;
var filter = Array.prototype.filter;

export default Ember.Service.extend({

/*
* Each element in the array is of form:
* Each element in the array is of the form:
*
* {
* url: 'string'
Expand All @@ -27,7 +26,6 @@ export default Ember.Service.extend({
* multiple requests for the same socket will return the same object.
*/
socketFor(url) {
var sockets;
var proxy = this.findSocketInCache(this.get('sockets'), url);

if (proxy && this.websocketIsNotClosed(proxy.socket)) { return proxy.socket; }
Expand All @@ -51,7 +49,7 @@ export default Ember.Service.extend({
closeSocketFor(url) {
var filteredSockets = [];

forEach(this.get('sockets'), item => {
forEach.call(this.get('sockets'), item => {
if(item.url === this.normalizeURL(url)) {
item.socket.close();
}
Expand Down Expand Up @@ -87,7 +85,7 @@ export default Ember.Service.extend({
* Returns the socket object from the cache if one matches the url else undefined
*/
findSocketInCache(socketsCache, url) {
var cachedResults = filter(socketsCache, websocket => {
var cachedResults = filter.call(socketsCache, websocket => {
return websocket['url'] === this.normalizeURL(url);
});

Expand Down
7 changes: 7 additions & 0 deletions blueprints/socket-io/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
normalizeEntityName: function() {},

afterInstall: function() {
return this.addBowerPackageToProject('socket.io-client');
}
};
6 changes: 4 additions & 2 deletions bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@
"ember": "1.12.0",
"ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3",
"ember-cli-test-loader": "ember-cli-test-loader#0.1.3",
"ember-data": "1.0.0-beta.18",
"ember-load-initializers": "ember-cli/ember-load-initializers#0.1.4",
"ember-qunit": "0.3.3",
"ember-qunit-notifications": "0.0.7",
"ember-resolver": "~0.1.15",
"jquery": "^1.11.1",
"loader.js": "ember-cli/loader.js#3.2.0",
"mock-socket": "^0.4.0",
"mock-socket": "^0.5.1",
"qunit": "~1.17.1",
"uri.js": "~1.15.0"
},
"devDependencies": {
"socket.io-client": "~1.3.5"
}
}
Loading

0 comments on commit 05f4b36

Please sign in to comment.