├── .travis.yml ├── .jshintrc ├── .gitignore ├── tests ├── server.js └── index.js ├── LICENSE ├── package.json ├── ampersand-io.js └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | before_install: 5 | - "export DISPLAY=:99.0" 6 | - "sh -e /etc/init.d/xvfb start" -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "camelcase": true, 4 | "freeze": true, 5 | "indent": 2, 6 | "newcap": true, 7 | "noempty": true, 8 | "quotmark": "single", 9 | "undef": true, 10 | "unused": false, 11 | "maxdepth": 3, 12 | 13 | "asi": false, 14 | "expr": true, 15 | 16 | "node": true, 17 | "browser": true 18 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | -------------------------------------------------------------------------------- /tests/server.js: -------------------------------------------------------------------------------- 1 | var io = require('socket.io')(); 2 | 3 | var events = { 4 | requestTests: 'request', 5 | test1: 'test-1', 6 | test2: 'test-2', 7 | test3: ['test-3-1', 'test-3-2'], 8 | test4: 'test-4' 9 | }; 10 | 11 | console.log('server running'); 12 | 13 | io.on('connection', function(socket){ 14 | 15 | socket.on(events.requestTests, function(){ 16 | socket.emit(events.test1); 17 | socket.emit(events.test2); 18 | socket.emit(events.test3[0]); 19 | socket.emit(events.test3[1]); 20 | socket.emit(events.test4); 21 | }); 22 | 23 | socket.on('test-1', function(request, cb){ 24 | cb(request); 25 | }); 26 | 27 | socket.on(events.test2, function(request, cb){ 28 | socket.emit('response'); 29 | }); 30 | 31 | socket.on(events.test3[0], function(request, cb){ 32 | cb(request); 33 | }); 34 | 35 | socket.on(events.test3[1], function(request, cb){ 36 | cb(request); 37 | }); 38 | 39 | }); 40 | io.listen(3000); 41 | 42 | setTimeout(function timeouExit(){ 43 | io.close(); 44 | process.exit(); 45 | }, 30000); 46 | 47 | module.exports = io; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 João Antunes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ampersand-io", 3 | "version": "0.4.2", 4 | "description": "Provides a socket.io wrapper to be used with ampersand modules", 5 | "main": "ampersand-io.js", 6 | "scripts": { 7 | "test": "npm run test-server | npm run test-client", 8 | "test-server": "node tests/server", 9 | "test-client": "browserify tests | tape-run | tap-spec", 10 | "lint": "node_modules/.bin/jshint -c .jshintrc ampersand-io.js" 11 | }, 12 | "pre-commit": [ 13 | "lint", 14 | "test" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/sinfo/ampersand-io" 19 | }, 20 | "keywords": [ 21 | "ampersand", 22 | "io", 23 | "socket", 24 | "js", 25 | "realtime" 26 | ], 27 | "author": "Joao Antunes (http://github.com/JGAntunes)", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/sinfo/ampersand-io/issues" 31 | }, 32 | "homepage": "https://github.com/sinfo/ampersand-io", 33 | "dependencies": { 34 | "ampersand-class-extend": "^1.0.2", 35 | "socket.io-client": "^1.3.5" 36 | }, 37 | "devDependencies": { 38 | "browserify": "^9.0.8", 39 | "jshint": "^2.7.0", 40 | "pre-commit": "^1.0.6", 41 | "socket.io": "^1.3.5", 42 | "tap-spec": "^3.0.0", 43 | "tape": "^4.0.0", 44 | "tape-run": "^1.0.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ampersand-io.js: -------------------------------------------------------------------------------- 1 | /*$AMPERSAND_VERSION*/ 2 | var extend = require('ampersand-class-extend'); 3 | var io = require ('socket.io-client'); 4 | 5 | var AmpersandIO = function(socket, options){ 6 | options || (options = {}); 7 | this.events = options.events || this.events || {}; 8 | 9 | if(socket){ 10 | this.socket = {}; 11 | if(typeof socket === 'string'){ 12 | socket = io(socket); 13 | } 14 | this.socket = socket; 15 | } 16 | else{ 17 | // this.socket = clone(this.socket, true); 18 | this.socket = this.socket.connect(); 19 | } 20 | if(options.listeners){ 21 | this.listeners = {}; 22 | this.addListeners(options.listeners); 23 | } 24 | else{ 25 | this.listeners = this.listeners || {}; 26 | if(options.initListeners){ 27 | this.setListeners(); 28 | } 29 | } 30 | }; 31 | 32 | AmpersandIO.prototype = { 33 | 34 | socket: io, 35 | 36 | _setCallback: function(fn){ 37 | var self = this; 38 | return function(){ 39 | fn.apply(self, arguments); 40 | }; 41 | }, 42 | 43 | _setListener: function(event, listener){ 44 | this.listeners[listener]._callbacks[event] = this._setCallback(this.listeners[listener].fn); 45 | this.socket.on(event, this.listeners[listener]._callbacks[event]); 46 | }, 47 | 48 | _removeListener: function(event, listener){ 49 | this.socket.removeListener(event, this.listeners[listener]._callbacks[event]); 50 | }, 51 | 52 | addListeners: function(listeners){ 53 | for(var listenerID in listeners){ 54 | if(!listeners.hasOwnProperty(listenerID)){ 55 | continue; 56 | } 57 | if(this.listeners[listenerID] && this.listeners[listenerID].active){ 58 | continue; 59 | } 60 | var listener = listeners[listenerID], events = listenerID; 61 | this.listeners[listenerID] = listener; 62 | this.listeners[listenerID]._callbacks = {}; 63 | if(this.events[listenerID]){ 64 | events = this.events[listenerID]; 65 | } 66 | if(typeof events === 'string'){ 67 | events = [events]; 68 | } 69 | for(var i = 0; i < events.length; i++){ 70 | var id = events[i]; 71 | if(listener.active){ 72 | this._setListener(id, listenerID); 73 | } 74 | } 75 | } 76 | }, 77 | 78 | setListeners: function (listeners){ 79 | if(!listeners){ 80 | listeners = Object.keys(this.listeners); 81 | } 82 | for(var i = 0; i < listeners.length; i++){ 83 | var listenerID = listeners[i]; 84 | var listener = this.listeners[listenerID], events = listenerID; 85 | if(!listener.active){ 86 | listener.active = true; 87 | listener._callbacks = {}; 88 | events = this.events[listenerID] || events; 89 | if(typeof events === 'string'){ 90 | events = [events]; 91 | } 92 | for(var j = 0; j < events.length; j++){ 93 | var id = events[j]; 94 | this._setListener(id, listenerID); 95 | } 96 | } 97 | } 98 | }, 99 | 100 | removeListeners: function (listeners){ 101 | if(!listeners){ 102 | listeners = Object.keys(this.listeners); 103 | } 104 | for(var i = 0; i < listeners.length; i++){ 105 | var listenerID = listeners[i]; 106 | var listener = this.listeners[listenerID], events = listenerID; 107 | if(listener.active){ 108 | listener.active = false; 109 | events = this.events[listenerID] || events; 110 | if(typeof events === 'string'){ 111 | events = [events]; 112 | } 113 | for(var j = 0; j < events.length; j++){ 114 | var id = events[j]; 115 | this._removeListener(id, listenerID); 116 | } 117 | } 118 | } 119 | }, 120 | 121 | emit: function (event, data, options, cb){ 122 | options || (options = {}); 123 | if(typeof options === 'function'){ 124 | cb = options; 125 | options = {}; 126 | } 127 | cb || (cb = options.callback); 128 | if(this.events[event]){ 129 | event = this.events[event]; 130 | } 131 | if(typeof event === 'string'){ 132 | event = [event]; 133 | } 134 | for(var i = 0; i < event.length; i++){ 135 | if(options.room){ 136 | io.to(options.room).emit(event[i], {data: data, options: options}, cb); 137 | } 138 | else{ 139 | this.socket.emit(event[i], {data: data, options: options}, cb); 140 | } 141 | } 142 | } 143 | }; 144 | 145 | AmpersandIO.extend = extend; 146 | 147 | module.exports = AmpersandIO; -------------------------------------------------------------------------------- /tests/index.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var AmpersandIO = require('../ampersand-io'); 3 | var IOClient = require('socket.io-client'); 4 | 5 | console.log('Starting'); 6 | 7 | var MyClass = AmpersandIO.extend({ 8 | events: { 9 | test1: 'test-1', 10 | test2: 'test-2', 11 | test3: ['test-3-1', 'test-3-2'], 12 | } 13 | }); 14 | 15 | test('extend', function(t){ 16 | t.plan(3); 17 | var testObj = {test: 'test-extend'}; 18 | var testMethod = {test: function(){return 'test';}}; 19 | var testClass = AmpersandIO.extend({events: testObj, listeners: testObj}, testMethod); 20 | 21 | t.deepEqual(testMethod.test(), testClass.prototype.test()); 22 | t.deepEqual(testObj, testClass.prototype.events); 23 | t.deepEqual(testObj, testClass.prototype.listeners); 24 | }); 25 | 26 | test('constructor', function(t){ 27 | 28 | t.test('no arguments', function(subt){ 29 | subt.plan(4); 30 | var IO = new MyClass('http://localhost:3000'); 31 | 32 | subt.ok(IO.socket, 'socket object exists'); 33 | subt.equal(IO.events, MyClass.prototype.events); 34 | subt.deepEqual(IO.listeners, {}, 'listeners obj should not exist on prototype class #6'); 35 | subt.ok(typeof MyClass.prototype.listeners === 'undefined', 'listeners obj should not exist on prototype class #6'); 36 | }); 37 | 38 | t.test('socket', function(subt){ 39 | subt.plan(2); 40 | var socket = new IOClient('http://localhost:3000/otherChat'); 41 | var namespaceIO = new MyClass('http://localhost:3000/chat'); 42 | var socketIO = new MyClass(socket); 43 | 44 | subt.equal(namespaceIO.socket.nsp, '/chat'); 45 | subt.equal(socketIO.socket, socket); 46 | }); 47 | 48 | t.test('string and options', function (subt){ 49 | subt.plan(1); 50 | var options = { 51 | events:{ 52 | event: 'options-test' 53 | } 54 | }; 55 | var IO = new MyClass('http://localhost:3000', options); 56 | 57 | subt.equal(IO.events, options.events); 58 | }); 59 | 60 | t.test('socket and options', function (subt){ 61 | subt.plan(2); 62 | var socket = new IOClient('otherChat'); 63 | var options = { 64 | events: 'options-test' 65 | }; 66 | var socketIO = new MyClass(socket, options); 67 | 68 | subt.equal(socketIO.socket, socket); 69 | subt.equal(socketIO.events, options.events); 70 | }); 71 | 72 | }); 73 | 74 | test('emit', function(t){ 75 | var IO = new MyClass('http://localhost:3000'); 76 | t.plan(9); 77 | 78 | setTimeout(function waitForConnection(){ 79 | var testObj = { 80 | data: 'my-data', 81 | options: 'my-options' 82 | }; 83 | var cbTestObj = { 84 | data: 'my-other-data', 85 | options: { 86 | other: 'my-other-options' 87 | } 88 | }; 89 | 90 | cbTestObj.options.callback = function(request){ 91 | t.equal(request.data, cbTestObj.data, 'options callback'); 92 | t.equal(request.options.other, cbTestObj.options.other, 'options callback'); 93 | }; 94 | 95 | IO.emit('test1', testObj.data, testObj.options, function(request){ 96 | t.equal(request.data, testObj.data, 'simple event callback'); 97 | t.equal(request.options, testObj.options, 'simple event callback'); 98 | }); 99 | 100 | IO.emit('test3', testObj.data, testObj.options, function(request){ 101 | t.equal(request.data, testObj.data, 'multiple event callback'); 102 | t.equal(request.options, testObj.options, 'multiple event callback'); 103 | }); 104 | 105 | IO.emit('test1', cbTestObj.data, cbTestObj.options); 106 | 107 | IO.socket.on('response', function(){ 108 | t.pass('emit without data and callback'); 109 | }); 110 | 111 | IO.emit('test2'); 112 | 113 | setTimeout(function timeoutEnd(){ 114 | t.end(); 115 | },10000); 116 | 117 | },5000); 118 | }); 119 | 120 | test('listeners', function(t){ 121 | var IO = new MyClass('http://localhost:3000'); 122 | var IOExtend = new (MyClass.extend(IO, { 123 | events:{ 124 | test4: 'test-4' 125 | }, 126 | listeners: { 127 | test4:{ 128 | fn: function(){ 129 | t.pass('setListeners option'); 130 | t.deepEqual(this, IOExtend, 'correct \'this\' reference inside listener callback #4'); 131 | t.ok(this.otherMethod(), 'correct \'this\' reference inside extended ampersand io #4'); 132 | t.ok(this.otherProp, 'correct \'this\' reference inside extended ampersand io #4'); 133 | } 134 | } 135 | }, 136 | otherMethod: function(){ 137 | return 'test method #4'; 138 | }, 139 | otherProp: 'test prop #4' 140 | }))(null, {initListeners: true}); 141 | 142 | t.plan(24); 143 | 144 | IO.addListeners({ 145 | test1:{ 146 | fn: function(){ 147 | t.pass('listening to event property'); 148 | }, 149 | active: true 150 | }, 151 | test2:{ 152 | fn: function(){ 153 | t.fail('added listener without active flag'); 154 | } 155 | }, 156 | test3:{ 157 | fn: function(){ 158 | t.pass('listening to multiple events [array]'); 159 | }, 160 | active: true 161 | } 162 | }); 163 | 164 | t.ok(IO.listeners.test1.active, 'listener should be active'); 165 | t.notOk(IO.listeners.test2.active, 'listener should not be active'); 166 | t.ok(IO.listeners.test3.active, 'listener should be active'); 167 | 168 | IO.emit('request'); 169 | 170 | setTimeout(function() { 171 | IO.removeListeners(); 172 | t.notOk(IO.listeners.test1.active, 'listener should not be active'); 173 | t.notOk(IO.listeners.test2.active, 'listener should not be active'); 174 | t.notOk(IO.listeners.test3.active, 'listener should not be active'); 175 | 176 | t.equal((IO.socket._callbacks[IO.events.test1] || []).length, 0, 'listener should not be in the sockets callbacks'); 177 | t.equal((IO.socket._callbacks[IO.events.test2] || []).length, 0, 'listener should not be in the sockets callbacks'); 178 | t.equal((IO.socket._callbacks[IO.events.test3[0]] || []).length, 0, 'listener should not be in the sockets callbacks'); 179 | t.equal((IO.socket._callbacks[IO.events.test3[1]] || []).length, 0, 'listener should not be in the sockets callbacks'); 180 | 181 | IO.setListeners(); 182 | t.ok(IO.listeners.test1.active, 'listener should be active'); 183 | t.ok(IO.listeners.test2.active, 'listener should be active'); 184 | t.ok(IO.listeners.test3.active, 'listener should be active'); 185 | 186 | t.ok(IO.socket._callbacks[IO.events.test1], 'listener should be in the sockets callbacks'); 187 | t.ok(IO.socket._callbacks[IO.events.test2], 'listener should be in the sockets callbacks'); 188 | t.ok(IO.socket._callbacks[IO.events.test3[0]], 'listener should be in the sockets callbacks'); 189 | t.ok(IO.socket._callbacks[IO.events.test3[1]], 'listener should be in the sockets callbacks'); 190 | }, 5000); 191 | }); 192 | 193 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/sinfo/ampersand-io.svg?branch=master)](https://travis-ci.org/sinfo/ampersand-io) 2 | [![Dependency Status](https://david-dm.org/sinfo/ampersand-io.svg)](https://david-dm.org/sinfo/ampersand-io) 3 | [![devDependency Status](https://david-dm.org/sinfo/ampersand-io/dev-status.svg)](https://david-dm.org/sinfo/ampersand-io#info=devDependencies) 4 | 5 | ampersand-io 6 | ============ 7 | 8 | Provides a [socket.io](http://socket.io) wrapper to be used with ampersand modules. Developed mainly to be used with [ampersand-io-model](https://github.com/sinfo/ampersand-io-model) and [ampersand-io-collection](https://github.com/sinfo/ampersand-io-collection) 9 | 10 | 11 | ## Install 12 | 13 | ``` 14 | npm install ampersand-io 15 | ``` 16 | 17 | ## API Reference 18 | 19 | ### extend `AmpersandIO.extend([attributes])` 20 | 21 | Supports the normal extend behavior (through usage of [ampersand-class-extend](https://github.com/ampersandjs/ampersand-class-extend)). 22 | 23 | **Note** that neither the [events](#events-ioevents) property nor the [listeners](#listeners-iolisteners) property are extendable through this method. 24 | 25 | ### socket `IO.socket` 26 | 27 | Override this property to specify the socket that will be used when a new object reference is created. Useful for when you have a pre-existing socket connection that you would like to use. Defaults to an socket.io-client instance resulting from `io.connect()`. 28 | 29 | ```javascript 30 | var io = require('socket.io-client'); 31 | var IO = AmpersandIO.extend({ 32 | socket: io() 33 | }); 34 | ``` 35 | 36 | ### events `IO.events` 37 | 38 | Overridable property containing a key-value reference to the events to be used by the socket conection. Useful for usage with the `listeners` property and methods, as well as with the `emit` emit. 39 | 40 | ```javascript 41 | events: { 42 | eventOne: 'my-awesome-event', 43 | eventTwo: 'my-super-awesome-event' 44 | } 45 | ``` 46 | 47 | It also supports the usage of arrays of different events tied to a single key. 48 | 49 | ```javascript 50 | events: { 51 | eventArray: ['this-event', 'other-event'] 52 | } 53 | ``` 54 | 55 | ### listeners `IO.listeners` 56 | 57 | Overridable property containing a set of listeners to be used by the socket connection. The key may be an entirely unreferenced event or one of properties from the `events` property object. The `fn` property contains the callback function to be called when the event is fired. The `active` option is a Boolean that, if set to true, will set this listener to be initialized upon construction. 58 | 59 | **Note:** `this` property inside the `fn` callback will be a reference to the whole ampersand-io instance, allowing you to access any property in your object, including the [socket](#socket-iosocket) object. 60 | 61 | ```javascript 62 | events:{ 63 | myEvent: 'thisEvent', 64 | arrayOfEvents: ['event1', 'event2'] 65 | } 66 | 67 | listeners: { 68 | myEvent: { 69 | fn: function(data, cb){ 70 | console.log('This is an event callback'); 71 | console.log(this.events); 72 | //Will output: 73 | //events:{ 74 | // myEvent: 'thisEvent', 75 | // arrayOfEvents: ['event1', 'event2'] 76 | //} 77 | } 78 | }, 79 | arrayOfEvents: { 80 | fn: function(data, cb){ 81 | console.log('This callback will be called when all the events of arrayOfEvents are fired'); 82 | }, 83 | active: true //will be active when the object is created 84 | }, 85 | 'otherEvent': { 86 | fn: function(data, cb){ 87 | console.log('This event is not listed in the events property'); 88 | } 89 | } 90 | } 91 | ``` 92 | 93 | ### constructor/initialize `new AmpersandIO([socket], [options])` 94 | 95 | When creating an `AmpersandIO` object, you may choose to pass in either a `socket` object or a string to be used as a namespace for a new socket.io-client instance. If none of those are provided the `AmpersandIO` instance will use the [socket](#socket-iosocket) object defined in the class. Options support a [listeners](#listeners-iolisteners) and [events](#events-ioevents) objects according to the ones mentioned above (note that these will override the class definitions) and also an `initListeners` prop which, if passed `true`, will [set the class listeners](#setlisteners-iosetlistenerslisteners) active. 96 | 97 | ```javascript 98 | var IO = new AmpersandIO('chat', { 99 | events: { 100 | receive: 'new-message' 101 | }, 102 | listeners: { 103 | receive: { 104 | fn: function(data, cb){ 105 | console.log('New message: ' + data); 106 | }, 107 | active: true 108 | } 109 | } 110 | }); 111 | ``` 112 | 113 | ### addListeners `IO.addListeners(listeners)` 114 | 115 | Add a set of listeners to the listeners property of the object. Pass a `listeners` object as [described above](#listeners-iolisteners). If a listener by that name already exists and is currently active it should be [removed](#removelisteners-ioremovelistenerslisteners) first or else it will be ignored. 116 | 117 | ### setListeners `IO.setListeners([listeners])` 118 | 119 | Set the given listeners active. Accepts an array of strings containing the names of the events associated with the [listeners](#listeners-iolisteners). If no argument is provided the method will set all the listeners from the current object. Nothing done to listeners which are already active. 120 | 121 | ```javascript 122 | // sets the listeners associated with the receive and send events 123 | IO.setListeners(['receive', 'send']); 124 | 125 | // sets all the listeners in the IO object 126 | IO.setListeners(); 127 | ``` 128 | 129 | ### removeListeners `IO.removeListeners([listeners])` 130 | 131 | Sets the given listeners unactive. Accepts an array of strings containing the names of the events associated with the [listeners](#listeners-iolisteners). If no argument is provided the method will `remove` all the listeners from the current object. Nothing done to listeners which are already unactive. 132 | 133 | **Note:** the respective properties from the [listeners](#listeners-iolisteners) property aren't deleted. The `active` property is set to `false`. 134 | 135 | ```javascript 136 | // removes the listeners associated with the receive and send events 137 | IO.removeListeners(['receive', 'send']); 138 | 139 | // removes all the listeners in the IO object 140 | IO.removeListeners(); 141 | ``` 142 | 143 | ### emit `IO.emit(event, data, [options], [callback])` 144 | 145 | Method responsible for emitting events. The `event` name may be one of the events listed in the [events](#events-ioevents) property or other of your choice. The data sent to the socket connections will be an object `{data: data, options: options}` containing the arguments passed to this function. Pass `options.room` if you want to emit to a particular room and an `options.callback` that will be also passed to the socket `emit` method. You may choose to pass the `callback` function directly as an argument (`options.callback` will be ignored in this case). 146 | 147 | ```javascript 148 | IO.emit('send', 'hi', function(){ 149 | console.log('acked hi'); 150 | }); 151 | ``` 152 | 153 | ## credits 154 | 155 | Created by [@JGAntunes](http://github.com/JGAntunes), with the support of [@SINFO](http://github.com/sinfo) and based on a series of Ampersand Modules. 156 | 157 | 158 | ## license 159 | 160 | MIT 161 | --------------------------------------------------------------------------------