├── .gitignore ├── .travis.yml ├── README.md ├── bower.json ├── gulpfile.js ├── karma.conf.js ├── mock └── socket-io.js ├── npm-install.sh ├── package.json ├── socket.js ├── socket.min.js ├── socket.min.js.map └── socket.spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | node_modules/ 3 | bower_components/ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | env: 5 | - VERSION=1.2 6 | - VERSION=1.3 7 | before_script: 8 | - ./npm-install.sh 9 | - export DISPLAY=:99.0 10 | - sh -e /etc/init.d/xvfb start 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-socket-io [![Build Status](https://travis-ci.org/btford/angular-socket-io.svg)](https://travis-ci.org/btford/angular-socket-io) 2 | 3 | Bower Component for using AngularJS with [Socket.IO](http://socket.io/), 4 | based on [this](http://briantford.com/blog/angular-socket-io.html). 5 | 6 | 7 | ## Install 8 | 9 | 1. `bower install angular-socket-io` or [download the zip](https://github.com/btford/angular-socket-io/archive/master.zip). 10 | 2. Make sure the Socket.IO client lib is loaded. It's often served at `/socket.io/socket.io.js`. 11 | 3. Include the `socket.js` script provided by this component into your app. 12 | 4. Add `btford.socket-io` as a module dependency to your app. 13 | 14 | 15 | ## Usage 16 | 17 | This module exposes a `socketFactory`, which is an API for instantiating 18 | sockets that are integrated with Angular's digest cycle. 19 | 20 | 21 | ### Making a Socket Instance 22 | 23 | ```javascript 24 | // in the top-level module of the app 25 | angular.module('myApp', [ 26 | 'btford.socket-io', 27 | 'myApp.MyCtrl' 28 | ]). 29 | factory('mySocket', function (socketFactory) { 30 | return socketFactory(); 31 | }); 32 | ``` 33 | 34 | With that, you can inject your `mySocket` service into controllers and 35 | other serivices within your application! 36 | 37 | ### Using Your Socket Instance 38 | 39 | Building on the example above: 40 | 41 | ```javascript 42 | // in the top-level module of the app 43 | angular.module('myApp', [ 44 | 'btford.socket-io', 45 | 'myApp.MyCtrl' 46 | ]). 47 | factory('mySocket', function (socketFactory) { 48 | return socketFactory(); 49 | }). 50 | controller('MyCtrl', function (mySocket) { 51 | // ... 52 | }); 53 | ``` 54 | 55 | 56 | ## API 57 | 58 | For the most part, this component works exactly like you would expect. 59 | The only API addition is `socket.forward`, which makes it easier to add/remove listeners in a way that works with [AngularJS's scope](http://docs.angularjs.org/api/ng.$rootScope.Scope). 60 | 61 | ### `socket.on` / `socket.addListener` 62 | Takes an event name and callback. 63 | Works just like the method of the same name from Socket.IO. 64 | 65 | ### `socket.removeListener` 66 | Takes an event name and callback. 67 | Works just like the method of the same name from Socket.IO. 68 | 69 | ### `socket.removeAllListeners` 70 | Takes an event name. 71 | Works just like the method of the same name from Socket.IO. 72 | 73 | ### `socket.emit` 74 | Sends a message to the server. 75 | Optionally takes a callback. 76 | 77 | Works just like the method of the same name from Socket.IO. 78 | 79 | ### `socket.forward` 80 | 81 | `socket.forward` allows you to forward the events received by Socket.IO's socket to AngularJS's event system. 82 | You can then listen to the event with `$scope.$on`. 83 | By default, socket-forwarded events are namespaced with `socket:`. 84 | 85 | The first argument is a string or array of strings listing the event names to be forwarded. 86 | The second argument is optional, and is the scope on which the events are to be broadcast. 87 | If an argument is not provided, it defaults to `$rootScope`. 88 | As a reminder, broadcasted events are propagated down to descendant scopes. 89 | 90 | #### Examples 91 | 92 | An easy way to make socket error events available across your app: 93 | 94 | ```javascript 95 | // in the top-level module of the app 96 | angular.module('myApp', [ 97 | 'btford.socket-io', 98 | 'myApp.MyCtrl' 99 | ]). 100 | factory('mySocket', function (socketFactory) { 101 | var mySocket = socketFactory(); 102 | mySocket.forward('error'); 103 | return mySocket; 104 | }); 105 | 106 | // in one of your controllers 107 | angular.module('myApp.MyCtrl', []). 108 | controller('MyCtrl', function ($scope) { 109 | $scope.$on('socket:error', function (ev, data) { 110 | 111 | }); 112 | }); 113 | ``` 114 | 115 | Avoid duplicating event handlers when a user navigates back and forth between routes: 116 | 117 | ```javascript 118 | angular.module('myMod', ['btford.socket-io']). 119 | controller('MyCtrl', function ($scope, socket) { 120 | socket.forward('someEvent', $scope); 121 | $scope.$on('socket:someEvent', function (ev, data) { 122 | $scope.theData = data; 123 | }); 124 | }); 125 | ``` 126 | 127 | 128 | ### `socketFactory({ ioSocket: }}` 129 | 130 | This option allows you to provide the `socket` service with a `Socket.IO socket` object to be used internally. 131 | This is useful if you want to connect on a different path, or need to hold a reference to the `Socket.IO socket` object for use elsewhere. 132 | 133 | ```javascript 134 | angular.module('myApp', [ 135 | 'btford.socket-io' 136 | ]). 137 | factory('mySocket', function (socketFactory) { 138 | var myIoSocket = io.connect('/some/path'); 139 | 140 | mySocket = socketFactory({ 141 | ioSocket: myIoSocket 142 | }); 143 | 144 | return mySocket; 145 | }); 146 | ``` 147 | 148 | ### `socketFactory({ scope: })` 149 | 150 | This option allows you to set the scope on which `$broadcast` is forwarded to when using the `forward` method. 151 | It defaults to `$rootScope`. 152 | 153 | 154 | ### `socketFactory({ prefix: })` 155 | 156 | The default prefix is `socket:`. 157 | 158 | 159 | #### Example 160 | 161 | To remove the prefix: 162 | 163 | ```javascript 164 | angular.module('myApp', [ 165 | 'btford.socket-io' 166 | ]). 167 | config(function (socketFactoryProvider) { 168 | socketFactoryProvider.prefix(''); 169 | }); 170 | ``` 171 | 172 | 173 | 174 | ## Migrating from 0.2 to 0.3 175 | 176 | `angular-socket-io` version `0.3` changes X to make fewer assumptions 177 | about the lifecycle of the socket. Previously, the assumption was that your 178 | application has a single socket created at config time. While this holds 179 | for most apps I've seen, there's no reason you shouldn't be able to 180 | lazily create sockets, or have multiple connections. 181 | 182 | In `0.2`, `angular-socket-io` exposed a `socket` service. In `0.3`, it 183 | instead exposes a `socketFactory` service which returns socket instances. 184 | Thus, getting the old API is as simple as making your own `socket` service 185 | with `socketFactory`. The examples below demonstrate how to do this. 186 | 187 | ### Simple Example 188 | 189 | In most cases, adding the following to your app should suffice: 190 | 191 | ```javascript 192 | // ... 193 | factory('socket', function (socketFactory) { 194 | return socketFactory(); 195 | }); 196 | // ... 197 | ``` 198 | 199 | ### Example with Configuration 200 | 201 | Before: 202 | 203 | ```javascript 204 | angular.module('myApp', [ 205 | 'btford.socket-io' 206 | ]). 207 | config(function (socketFactoryProvider) { 208 | socketFactoryProvider.prefix('foo~'); 209 | socketFactoryProvider.ioSocket(io.connect('/some/path')); 210 | }). 211 | controller('MyCtrl', function (socket) { 212 | socket.on('foo~bar', function () { 213 | $scope.bar = true; 214 | }); 215 | }); 216 | ``` 217 | 218 | After: 219 | 220 | ```javascript 221 | angular.module('myApp', [ 222 | 'btford.socket-io' 223 | ]). 224 | factory('socket', function (socketFactory) { 225 | return socketFactory({ 226 | prefix: 'foo~', 227 | ioSocket: io.connect('/some/path') 228 | }); 229 | }). 230 | controller('MyCtrl', function (socket) { 231 | socket.on('foo~bar', function () { 232 | $scope.bar = true; 233 | }); 234 | }); 235 | ``` 236 | 237 | 238 | ## FAQ 239 | 240 | [Closed issues labelled `FAQ`](https://github.com/btford/angular-socket-io/issues?labels=faq&page=1&state=closed) might have the answer to your question. 241 | 242 | 243 | ## See Also 244 | 245 | * [ngSocket](https://github.com/jeffbcross/ngSocket) 246 | * [angular-socket.io-mock](https://github.com/nullivex/angular-socket.io-mock) 247 | 248 | 249 | ## License 250 | MIT 251 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-socket-io", 3 | "version": "0.7.0", 4 | "main": "socket.js", 5 | "dependencies": { 6 | "angular": "^1.2.6" 7 | }, 8 | "devDependencies": { 9 | "angular-mocks": "^1.2.6" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | uglify = require('gulp-uglify'), 3 | rename = require('gulp-rename'); 4 | 5 | gulp.task('scripts', function() { 6 | return gulp.src('socket.js') 7 | .pipe(rename('socket.min.js')) 8 | .pipe(uglify({ 9 | preserveComments: 'some', 10 | outSourceMap: true 11 | })) 12 | .pipe(gulp.dest('.')); 13 | }); 14 | 15 | gulp.task('default', ['scripts']); 16 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (config) { 3 | config.set({ 4 | basePath: '', 5 | files: [ 6 | 'mock/socket-io.js', 7 | 'node_modules/angular/angular.js', 8 | 'node_modules/angular-mocks/angular-mocks.js', 9 | 'socket.js', 10 | '*.spec.js' 11 | ], 12 | 13 | reporters: ['progress'], 14 | 15 | port: 9876, 16 | colors: true, 17 | 18 | logLevel: config.LOG_INFO, 19 | 20 | browsers: ['Chrome'], 21 | frameworks: ['jasmine'], 22 | 23 | captureTimeout: 60000, 24 | 25 | autoWatch: true, 26 | singleRun: false 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /mock/socket-io.js: -------------------------------------------------------------------------------- 1 | var io = { 2 | connect: createMockSocketObject 3 | }; 4 | 5 | function createMockSocketObject () { 6 | 7 | var socket = { 8 | on: function (ev, fn) { 9 | (this._listeners[ev] = this._listeners[ev] || []).push(fn); 10 | }, 11 | once: function (ev, fn) { 12 | (this._listeners[ev] = this._listeners[ev] || []).push(fn); 13 | fn._once = true; 14 | }, 15 | emit: function (ev, data) { 16 | if (this._listeners[ev]) { 17 | var args = arguments; 18 | this._listeners[ev].forEach(function (listener) { 19 | if (listener._once) { 20 | this.removeListener(ev, listener); 21 | } 22 | listener.apply(null, Array.prototype.slice.call(args, 1)); 23 | }.bind(this)); 24 | } 25 | }, 26 | _listeners: {}, 27 | removeListener: function (ev, fn) { 28 | if (fn) { 29 | var index = this._listeners[ev].indexOf(fn); 30 | if (index > -1) { 31 | this._listeners[ev].splice(index, 1); 32 | } 33 | } else { 34 | delete this._listeners[ev]; 35 | } 36 | }, 37 | removeAllListeners: function (ev) { 38 | if (ev) { 39 | delete this._listeners[ev]; 40 | } else { 41 | this._listeners = {}; 42 | } 43 | }, 44 | disconnect: function () {}, 45 | connect: function () {} 46 | }; 47 | 48 | return socket; 49 | } 50 | -------------------------------------------------------------------------------- /npm-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | npm install angular@$VERSION angular-mocks@$VERSION 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-socket-io", 3 | "version": "0.7.0", 4 | "main": "socket.js", 5 | "directories": { 6 | "test": "test" 7 | }, 8 | "scripts": { 9 | "test": "./node_modules/.bin/karma start --browsers Firefox --single-run" 10 | }, 11 | "author": "Brian Ford", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "angular": "^1.3.5", 15 | "angular-mocks": "^1.3.5", 16 | "gulp": "^3.8.10", 17 | "gulp-rename": "~1.2.0", 18 | "gulp-uglify": "~0.2.1", 19 | "karma": "~0.10.2", 20 | "karma-firefox-launcher": "~0.1.0", 21 | "karma-jasmine": "~0.1.3" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /socket.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @license 3 | * angular-socket-io v0.7.0 4 | * (c) 2014 Brian Ford http://briantford.com 5 | * License: MIT 6 | */ 7 | 8 | angular.module('btford.socket-io', []). 9 | provider('socketFactory', function () { 10 | 11 | 'use strict'; 12 | 13 | // when forwarding events, prefix the event name 14 | var defaultPrefix = 'socket:', 15 | ioSocket; 16 | 17 | // expose to provider 18 | this.$get = ['$rootScope', '$timeout', function ($rootScope, $timeout) { 19 | 20 | var asyncAngularify = function (socket, callback) { 21 | return callback ? function () { 22 | var args = arguments; 23 | $timeout(function () { 24 | callback.apply(socket, args); 25 | }, 0); 26 | } : angular.noop; 27 | }; 28 | 29 | return function socketFactory (options) { 30 | options = options || {}; 31 | var socket = options.ioSocket || io.connect(); 32 | var prefix = options.prefix === undefined ? defaultPrefix : options.prefix ; 33 | var defaultScope = options.scope || $rootScope; 34 | 35 | var addListener = function (eventName, callback) { 36 | socket.on(eventName, callback.__ng = asyncAngularify(socket, callback)); 37 | }; 38 | 39 | var addOnceListener = function (eventName, callback) { 40 | socket.once(eventName, callback.__ng = asyncAngularify(socket, callback)); 41 | }; 42 | 43 | var wrappedSocket = { 44 | on: addListener, 45 | addListener: addListener, 46 | once: addOnceListener, 47 | 48 | emit: function (eventName, data, callback) { 49 | var lastIndex = arguments.length - 1; 50 | var callback = arguments[lastIndex]; 51 | if(typeof callback == 'function') { 52 | callback = asyncAngularify(socket, callback); 53 | arguments[lastIndex] = callback; 54 | } 55 | return socket.emit.apply(socket, arguments); 56 | }, 57 | 58 | removeListener: function (ev, fn) { 59 | if (fn && fn.__ng) { 60 | arguments[1] = fn.__ng; 61 | } 62 | return socket.removeListener.apply(socket, arguments); 63 | }, 64 | 65 | removeAllListeners: function() { 66 | return socket.removeAllListeners.apply(socket, arguments); 67 | }, 68 | 69 | disconnect: function (close) { 70 | return socket.disconnect(close); 71 | }, 72 | 73 | connect: function() { 74 | return socket.connect(); 75 | }, 76 | 77 | // when socket.on('someEvent', fn (data) { ... }), 78 | // call scope.$broadcast('someEvent', data) 79 | forward: function (events, scope) { 80 | if (events instanceof Array === false) { 81 | events = [events]; 82 | } 83 | if (!scope) { 84 | scope = defaultScope; 85 | } 86 | events.forEach(function (eventName) { 87 | var prefixedEvent = prefix + eventName; 88 | var forwardBroadcast = asyncAngularify(socket, function () { 89 | Array.prototype.unshift.call(arguments, prefixedEvent); 90 | scope.$broadcast.apply(scope, arguments); 91 | }); 92 | scope.$on('$destroy', function () { 93 | socket.removeListener(eventName, forwardBroadcast); 94 | }); 95 | socket.on(eventName, forwardBroadcast); 96 | }); 97 | } 98 | }; 99 | 100 | return wrappedSocket; 101 | }; 102 | }]; 103 | }); 104 | -------------------------------------------------------------------------------- /socket.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @license 3 | * angular-socket-io v0.7.0 4 | * (c) 2014 Brian Ford http://briantford.com 5 | * License: MIT 6 | */ 7 | angular.module("btford.socket-io",[]).provider("socketFactory",function(){"use strict";var n="socket:";this.$get=["$rootScope","$timeout",function(t,e){var r=function(n,t){return t?function(){var r=arguments;e(function(){t.apply(n,r)},0)}:angular.noop};return function(e){e=e||{};var o=e.ioSocket||io.connect(),c=void 0===e.prefix?n:e.prefix,u=e.scope||t,i=function(n,t){o.on(n,t.__ng=r(o,t))},a=function(n,t){o.once(n,t.__ng=r(o,t))},s={on:i,addListener:i,once:a,emit:function(n,t,e){var c=arguments.length-1,e=arguments[c];return"function"==typeof e&&(e=r(o,e),arguments[c]=e),o.emit.apply(o,arguments)},removeListener:function(n,t){return t&&t.__ng&&(arguments[1]=t.__ng),o.removeListener.apply(o,arguments)},removeAllListeners:function(){return o.removeAllListeners.apply(o,arguments)},disconnect:function(n){return o.disconnect(n)},connect:function(){return o.connect()},forward:function(n,t){n instanceof Array==!1&&(n=[n]),t||(t=u),n.forEach(function(n){var e=c+n,u=r(o,function(){Array.prototype.unshift.call(arguments,e),t.$broadcast.apply(t,arguments)});t.$on("$destroy",function(){o.removeListener(n,u)}),o.on(n,u)})}};return s}}]}); 8 | //# sourceMappingURL=socket.min.js.map -------------------------------------------------------------------------------- /socket.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"socket.min.js.map","sources":["socket.min.js"],"names":["angular","module","provider","defaultPrefix","this","$get","$rootScope","$timeout","asyncAngularify","socket","callback","args","arguments","apply","noop","options","ioSocket","io","connect","prefix","undefined","defaultScope","scope","addListener","eventName","on","__ng","addOnceListener","once","wrappedSocket","emit","data","lastIndex","length","removeListener","ev","fn","removeAllListeners","disconnect","close","forward","events","Array","forEach","prefixedEvent","forwardBroadcast","prototype","unshift","call","$broadcast","$on"],"mappings":";;;;;;AAOAA,QAAQC,OAAO,uBACbC,SAAS,gBAAiB,WAExB,YAGA,IAAIC,GAAgB,SAIpBC,MAAKC,MAAQ,aAAc,WAAY,SAAUC,EAAYC,GAE3D,GAAIC,GAAkB,SAAUC,EAAQC,GACtC,MAAOA,GAAW,WAChB,GAAIC,GAAOC,SACXL,GAAS,WACPG,EAASG,MAAMJ,EAAQE,IACtB,IACDX,QAAQc,KAGd,OAAO,UAAwBC,GAC7BA,EAAUA,KACV,IAAIN,GAASM,EAAQC,UAAYC,GAAGC,UAChCC,EAA4BC,SAAnBL,EAAQI,OAAuBhB,EAAgBY,EAAQI,OAChEE,EAAeN,EAAQO,OAAShB,EAEhCiB,EAAc,SAAUC,EAAWd,GACrCD,EAAOgB,GAAGD,EAAWd,EAASgB,KAAOlB,EAAgBC,EAAQC,KAG3DiB,EAAkB,SAAUH,EAAWd,GACzCD,EAAOmB,KAAKJ,EAAWd,EAASgB,KAAOlB,EAAgBC,EAAQC,KAG7DmB,GACFJ,GAAIF,EACJA,YAAaA,EACbK,KAAMD,EAENG,KAAM,SAAUN,EAAWO,EAAMrB,GAC/B,GAAIsB,GAAYpB,UAAUqB,OAAS,EAC/BvB,EAAWE,UAAUoB,EAKzB,OAJsB,kBAAZtB,KACRA,EAAWF,EAAgBC,EAAQC,GACnCE,UAAUoB,GAAatB,GAElBD,EAAOqB,KAAKjB,MAAMJ,EAAQG,YAGnCsB,eAAgB,SAAUC,EAAIC,GAI5B,MAHIA,IAAMA,EAAGV,OACXd,UAAU,GAAKwB,EAAGV,MAEbjB,EAAOyB,eAAerB,MAAMJ,EAAQG,YAG7CyB,mBAAoB,WAClB,MAAO5B,GAAO4B,mBAAmBxB,MAAMJ,EAAQG,YAGjD0B,WAAY,SAAUC,GACpB,MAAO9B,GAAO6B,WAAWC,IAG3BrB,QAAS,WACP,MAAOT,GAAOS,WAKhBsB,QAAS,SAAUC,EAAQnB,GACrBmB,YAAkBC,SAAU,IAC9BD,GAAUA,IAEPnB,IACHA,EAAQD,GAEVoB,EAAOE,QAAQ,SAAUnB,GACvB,GAAIoB,GAAgBzB,EAASK,EACzBqB,EAAmBrC,EAAgBC,EAAQ,WAC7CiC,MAAMI,UAAUC,QAAQC,KAAKpC,UAAWgC,GACxCtB,EAAM2B,WAAWpC,MAAMS,EAAOV,YAEhCU,GAAM4B,IAAI,WAAY,WACpBzC,EAAOyB,eAAeV,EAAWqB,KAEnCpC,EAAOgB,GAAGD,EAAWqB,MAK3B,OAAOhB"} -------------------------------------------------------------------------------- /socket.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * angular-socket-io v0.4.1 3 | * (c) 2014 Brian Ford http://briantford.com 4 | * License: MIT 5 | */ 6 | 7 | 'use strict'; 8 | 9 | 10 | describe('socketFactory', function () { 11 | 12 | beforeEach(module('btford.socket-io')); 13 | 14 | var socket, 15 | scope, 16 | $timeout, 17 | $browser, 18 | mockIoSocket, 19 | spy; 20 | 21 | beforeEach(inject(function (socketFactory, _$browser_, $rootScope, _$timeout_) { 22 | $browser = _$browser_; 23 | $timeout = _$timeout_; 24 | scope = $rootScope.$new(); 25 | spy = jasmine.createSpy('emitSpy'); 26 | mockIoSocket = io.connect(); 27 | socket = socketFactory({ 28 | ioSocket: mockIoSocket, 29 | scope: scope 30 | }); 31 | })); 32 | 33 | 34 | describe('#on', function () { 35 | 36 | it('should apply asynchronously', function () { 37 | socket.on('event', spy); 38 | 39 | mockIoSocket.emit('event'); 40 | 41 | expect(spy).not.toHaveBeenCalled(); 42 | $timeout.flush(); 43 | 44 | expect(spy).toHaveBeenCalled(); 45 | }); 46 | 47 | }); 48 | 49 | 50 | describe('#disconnect', function () { 51 | 52 | it('should call the underlying socket.disconnect', function () { 53 | mockIoSocket.disconnect = spy; 54 | socket.disconnect(); 55 | expect(spy).toHaveBeenCalled(); 56 | }); 57 | 58 | }); 59 | 60 | describe('#connect', function () { 61 | 62 | it('should call the underlying socket.connect', function () { 63 | mockIoSocket.connect = spy; 64 | socket.connect(); 65 | expect(spy).toHaveBeenCalled(); 66 | }); 67 | 68 | }); 69 | 70 | 71 | describe('#once', function () { 72 | 73 | it('should apply asynchronously', function () { 74 | socket.once('event', spy); 75 | 76 | mockIoSocket.emit('event'); 77 | 78 | expect(spy).not.toHaveBeenCalled(); 79 | $timeout.flush(); 80 | 81 | expect(spy).toHaveBeenCalled(); 82 | }); 83 | 84 | it('should only run once', function () { 85 | var counter = 0; 86 | socket.once('event', function () { 87 | counter += 1; 88 | }); 89 | 90 | mockIoSocket.emit('event'); 91 | mockIoSocket.emit('event'); 92 | $timeout.flush(); 93 | 94 | expect(counter).toBe(1); 95 | }); 96 | 97 | }); 98 | 99 | 100 | describe('#emit', function () { 101 | 102 | it('should call the delegate socket\'s emit', function () { 103 | spyOn(mockIoSocket, 'emit'); 104 | 105 | socket.emit('event', {foo: 'bar'}); 106 | 107 | expect(mockIoSocket.emit).toHaveBeenCalled(); 108 | }); 109 | 110 | it('should allow multiple data arguments', function () { 111 | spyOn(mockIoSocket, 'emit'); 112 | socket.emit('event', 'x', 'y'); 113 | expect(mockIoSocket.emit).toHaveBeenCalledWith('event', 'x', 'y'); 114 | }); 115 | 116 | it('should wrap the callback with multiple data arguments', function () { 117 | spyOn(mockIoSocket, 'emit'); 118 | socket.emit('event', 'x', 'y', spy); 119 | expect(mockIoSocket.emit.mostRecentCall.args[3]).toNotBe(spy); 120 | 121 | mockIoSocket.emit.mostRecentCall.args[3](); 122 | expect(spy).not.toHaveBeenCalled(); 123 | $timeout.flush(); 124 | 125 | expect(spy).toHaveBeenCalled(); 126 | }); 127 | 128 | }); 129 | 130 | 131 | describe('#removeListener', function () { 132 | 133 | it('should not call after removing an event', function () { 134 | socket.on('event', spy); 135 | socket.removeListener('event', spy); 136 | 137 | mockIoSocket.emit('event'); 138 | 139 | expect($browser.deferredFns.length).toBe(0); 140 | }); 141 | 142 | }); 143 | 144 | 145 | describe('#removeAllListeners', function () { 146 | 147 | it('should not call after removing listeners for an event', function () { 148 | socket.on('event', spy); 149 | socket.removeAllListeners('event'); 150 | 151 | mockIoSocket.emit('event'); 152 | 153 | expect($browser.deferredFns.length).toBe(0); 154 | }); 155 | 156 | it('should not call after removing all listeners', function () { 157 | socket.on('event', spy); 158 | socket.on('event2', spy); 159 | socket.removeAllListeners(); 160 | 161 | mockIoSocket.emit('event'); 162 | mockIoSocket.emit('event2'); 163 | 164 | expect($browser.deferredFns.length).toBe(0); 165 | }); 166 | 167 | }); 168 | 169 | 170 | describe('#forward', function () { 171 | 172 | it('should forward events', function () { 173 | socket.forward('event'); 174 | 175 | scope.$on('socket:event', spy); 176 | mockIoSocket.emit('event'); 177 | $timeout.flush(); 178 | 179 | expect(spy).toHaveBeenCalled(); 180 | }); 181 | 182 | it('should forward an array of events', function () { 183 | socket.forward(['e1', 'e2']); 184 | 185 | scope.$on('socket:e1', spy); 186 | scope.$on('socket:e2', spy); 187 | 188 | mockIoSocket.emit('e1'); 189 | mockIoSocket.emit('e2'); 190 | $timeout.flush(); 191 | expect(spy.callCount).toBe(2); 192 | }); 193 | 194 | it('should remove watchers when the scope is removed', function () { 195 | 196 | socket.forward('event'); 197 | scope.$on('socket:event', spy); 198 | mockIoSocket.emit('event'); 199 | $timeout.flush(); 200 | 201 | expect(spy).toHaveBeenCalled(); 202 | 203 | scope.$destroy(); 204 | spy.reset(); 205 | mockIoSocket.emit('event'); 206 | expect(spy).not.toHaveBeenCalled(); 207 | }); 208 | 209 | it('should use the specified prefix', inject(function (socketFactory) { 210 | var socket = socketFactory({ 211 | ioSocket: mockIoSocket, 212 | scope: scope, 213 | prefix: 'custom:' 214 | }); 215 | 216 | socket.forward('event'); 217 | 218 | scope.$on('custom:event', spy); 219 | mockIoSocket.emit('event'); 220 | $timeout.flush(); 221 | 222 | expect(spy).toHaveBeenCalled(); 223 | })); 224 | 225 | it('should use an empty prefix if specified', inject(function (socketFactory) { 226 | var socket = socketFactory({ 227 | ioSocket: mockIoSocket, 228 | scope: scope, 229 | prefix: '' 230 | }); 231 | 232 | socket.forward('event'); 233 | 234 | scope.$on('event', spy); 235 | mockIoSocket.emit('event'); 236 | $timeout.flush(); 237 | 238 | expect(spy).toHaveBeenCalled(); 239 | })); 240 | 241 | it('should forward to the specified scope when one is provided', function () { 242 | var child = scope.$new(); 243 | spyOn(child, '$broadcast'); 244 | socket.forward('event', child); 245 | 246 | scope.$on('socket:event', spy); 247 | mockIoSocket.emit('event'); 248 | $timeout.flush(); 249 | 250 | expect(child.$broadcast).toHaveBeenCalled(); 251 | }); 252 | 253 | it('should pass all arguments to scope.$on', function () { 254 | socket.forward('event'); 255 | scope.$on('socket:event', spy); 256 | mockIoSocket.emit('event', 1, 2, 3); 257 | $timeout.flush(); 258 | 259 | expect(spy.calls[0].args.slice(1)).toEqual([1, 2, 3]); 260 | }); 261 | }); 262 | 263 | }); 264 | --------------------------------------------------------------------------------