Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Socket.IO intergration #34

Merged
merged 11 commits into from
Jun 19, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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